Permalink
Browse files

Expose supervision information in json

sponges now provides a http server to expose information about pool's
health. By default, this server listen on port 5032. The port can be
configured with the `port` accessor on `config`.

This may be used for monitoring, and building tools like dashboard.

Response:

    {
      "supervisor":{
        "pid":11537,
        "pctcpu":0.0,
        "pctmem":0.22,
        "created_at":"2013-03-05 15:21:04 +0100"
      },
      "children":[
        {
          "pid":11540,
          "pctcpu":0.0,
          "pctmem":0.21,
          "created_at":"2013-03-05 15:21:04 +0100"
        },
        {
          "pid":11543,
          "pctcpu":0.0,
          "pctmem":0.21,
          "created_at":"2013-03-05 15:21:04 +0100"
        },
        {
          "pid":11546,
          "pctcpu":0.0,
          "pctmem":0.21,
          "created_at":"2013-03-05 15:21:04 +0100"
        },
        {
          "pid":11549,
          "pctcpu":0.0,
          "pctmem":0.21,
          "created_at":"2013-03-05 15:21:04 +0100"
        }
      ]
    }

Signed-off-by: chatgris <jboyer@af83.com>
  • Loading branch information...
chatgris
chatgris committed Mar 5, 2013
1 parent 58b6fbf commit f154f575706f708a7150780780383262bc3ec5a4
View
@@ -1,3 +1,4 @@
-source 'https://rubygems.org'
+# encoding: utf-8
+source :rubygems
gemspec
View
@@ -13,6 +13,7 @@ to work is your job. :)
Basically, sponges is a ruby supervisor that forks processes and controls their
execution and termination. For example the following will start a supervision
daemon and 8 processes of "a_worker".
+
```bash
ruby a_worker.rb start -d -s 8
```
@@ -69,10 +70,11 @@ class Worker
end
Sponges.configure do |config|
- config.logger = MyCustomLogger.new # optionnal
- config.redis = Redis.new # optionnal
- config.size = 3
- config.daemonize = true
+ config.logger = MyCustomLogger.new # optionnal
+ config.redis = Redis.new # optionnal
+ config.size = 3 # optionnal, default to cpu's size
+ config.daemonize = true # optionnal, default to false
+ config.port = 5032 # optionnal, default to 5032
config.after_fork do
puts "Execute code when a child process is created"
end
@@ -146,6 +148,53 @@ Show a list of workers and their children.
ruby example.rb list
```
+## Http supervision
+
+sponges provides an http interface to supervise pool's activity, and to expose
+pids. Http supervision can be enable in configuration:
+
+
+``` ruby
+Sponges.configure do |config|
+ config.port = 3333
+end
+```
+
+By default, sponges listens on port 5032, and responds in json. Here is an
+example of response:
+
+``` javascript
+{
+ "supervisor":{
+ "pid":11537,
+ "pctcpu":0.0,
+ "pctmem":0.22
+ },
+ "children":[
+ {
+ "pid":11540,
+ "pctcpu":0.0,
+ "pctmem":0.21
+ },
+ {
+ "pid":11543,
+ "pctcpu":0.0,
+ "pctmem":0.21
+ },
+ {
+ "pid":11546,
+ "pctcpu":0.0,
+ "pctmem":0.21
+ },
+ {
+ "pid":11549,
+ "pctcpu":0.0,
+ "pctmem":0.21
+ }
+ ]
+}
+```
+
## Stores
sponges can store pids in memory or in redis. Memory is the default store. The
@@ -155,7 +204,7 @@ To select the redis store, you need to add `nest` to your application's
Gemfile, and do the following.
``` ruby
-gem "sponges"
+gem "nest"
```
``` ruby
@@ -22,6 +22,8 @@ def run
end
Sponges.configure do |config|
+ config.port = 3333
+
config.after_fork do
puts "forked"
end
View
@@ -4,8 +4,13 @@
require 'logger'
require 'machine'
require 'forwardable'
+require 'socket'
+require 'json'
+require_relative 'sponges/version'
require_relative 'sponges/configuration'
require_relative 'sponges/handler'
+require_relative 'sponges/response'
+require_relative 'sponges/listener'
require_relative 'sponges/supervisor'
require_relative 'sponges/runner'
require_relative 'sponges/commander'
@@ -5,7 +5,8 @@ module Sponges
class Configuration
class << self
ACCESSOR = [:worker_name, :worker, :logger, :redis, :size,
- :daemonize, :after_fork, :timeout, :gracefully, :store
+ :daemonize, :after_fork, :timeout, :gracefully, :store,
+ :port
]
attr_accessor *ACCESSOR
@@ -32,6 +33,10 @@ def store
@store || :memory
end
+ def port
+ @port || 5032
+ end
+
def redis
@redis ||= Redis.new.tap { warn_redis }
end
@@ -44,6 +49,7 @@ def redis=(redis)
def warn_redis
Sponges.logger.warn "Redis's store will be removed in version 1.0!"
end
+
end
end
View
@@ -2,6 +2,7 @@
module Sponges
class Handler
extend Forwardable
+
attr_reader :supervisor, :queue, :notifier
def initialize(supervisor)
View
@@ -0,0 +1,43 @@
+# encoding: utf-8
+module Sponges
+ class Listener
+ attr_reader :supervisor
+ CRLF = "\r\n"
+
+ def initialize(supervisor)
+ @supervisor = supervisor
+ end
+
+ def call
+ Socket.tcp_server_loop("0.0.0.0", port) {|c| handle_connection c }
+ end
+
+ private
+
+ def port
+ Sponges::Configuration.port
+ end
+
+ def handle_connection(connection)
+ response = Response.new(supervisor).to_json
+ connection.write headers(response)
+ connection.write response
+ connection.close
+ rescue Errno::EPIPE
+ # Resist to ping
+ end
+
+ def headers(response)
+ [
+ "HTTP/1.1 200 OK",
+ "Date: #{Time.now.utc}",
+ "Status: OK",
+ "Server: Sponges #{Sponges::VERSION} #{supervisor.name} edition",
+ "Content-Type: application/json; charset=utf-8",
+ "Content-Length: #{response.length}",
+ CRLF
+ ].join(CRLF)
+ end
+
+ end
+end
View
@@ -0,0 +1,42 @@
+# encoding: utf-8
+module Sponges
+ class Response
+ attr_reader :supervisor
+
+ def initialize(supervisor)
+ @supervisor = supervisor
+ end
+
+ def to_json
+ as_json.to_json
+ end
+
+ def as_json(opts={})
+ {
+ supervisor: process_information(supervisor.pid),
+ children: children_information
+ }
+ end
+
+ private
+
+ def process_information(pid)
+ info = Machine::ProcessStatus.new(pid)
+ {
+ pid: pid,
+ pctcpu: info.pctcpu,
+ pctmem: info.pctmem,
+ created_at: info.created_at
+
+ }
+ end
+
+ def children_information
+ supervisor.children_pids.map do |pid|
+ process_information(pid)
+ end
+ end
+
+ end
+end
+
View
@@ -1,7 +1,10 @@
# encoding: utf-8
module Sponges
class Supervisor
- attr_reader :store, :name, :options, :handler
+ extend Forwardable
+ attr_reader :store, :name, :options, :handler, :listener
+ def_delegator :@store, :supervisor_pid, :pid
+ def_delegator :@store, :children_pids
def initialize(name, options, store, block)
@name, @options, @store, @block = name, options, store, block
@@ -10,6 +13,7 @@ def initialize(name, options, store, block)
store.register Process.pid
@children_seen = 0
@handler = Handler.new self
+ @listener = Listener.new(self)
end
def start
@@ -18,8 +22,8 @@ def start
options[:size].times do
handler.push :TTIN
end
- Sponges.logger.info "Supervisor started, waiting for messages."
- sleep
+ Sponges.logger.info "Supervisor started, waiting for messages, listening on port #{Sponges::Configuration.port}"
+ listener.call
rescue SystemExit => exception
raise exception
rescue Exception => exception
View
@@ -0,0 +1,20 @@
+#!/usr/bin/env ruby
+# encoding: UTF-8
+
+require_relative '../lib/sponges'
+
+class SleepRunner
+ def run
+ sleep 1
+ run
+ end
+end
+
+Sponges.configure do |config|
+ config.logger = Logger.new('/dev/null')
+end
+
+Sponges.start '_sponges_sleep_test' do
+ SleepRunner.new.run
+end
+
View
@@ -15,7 +15,6 @@
it 'can increase and decrease childs size' do
press_sponges { Process.kill :TTIN, find_supervisor.pid }
find_childs.size.should eq Machine::Info::Cpu.cores_size + 1
-
press_sponges { Process.kill :TTOU, find_supervisor.pid }
find_childs.size.should eq Machine::Info::Cpu.cores_size
end
@@ -76,4 +75,32 @@
find_childs.size.should eq(Machine::Info::Cpu.cores_size - 1)
end
end
+
+ context "http supervision" do
+ require "net/http"
+ require "uri"
+
+ before do
+ press_sponges { system('spec/worker_runner.rb restart -d') }
+ end
+
+ let(:uri) { URI.parse("http://localhost:5032") }
+ let(:response) { JSON.parse(Net::HTTP.get_response(uri).body) }
+
+ it "should expose the supervisor_pid" do
+ response["supervisor"]["pid"].should eq find_supervisor.pid
+ end
+
+ it "should expose the created_at" do
+ response["supervisor"]["created_at"].should_not be_nil
+ end
+
+ it "should return a collection of children" do
+ response["children"].size.should eq Machine::Info::Cpu.cores_size
+ end
+
+ it 'should exposes pids of children' do
+ response["children"].first["pid"].should be_an Integer
+ end
+ end
end
View
@@ -11,7 +11,7 @@ def run
end
Sponges.configure do |config|
- config.logger = Logger.new('spec.log')
+ config.logger = Logger.new('spec.log')
end
Sponges.start '_sponges_test' do
View
@@ -15,6 +15,6 @@ Gem::Specification.new do |s|
s.platform = Gem::Platform::RUBY
s.require_paths = ['lib']
s.add_dependency "boson"
- s.add_dependency "machine"
+ s.add_dependency "machine" , '~>0.0.4'
s.add_development_dependency 'rspec', '~>2.10.0'
end

0 comments on commit f154f57

Please sign in to comment.