ganymed 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. data/.gitignore +22 -0
  2. data/.rspec +1 -0
  3. data/.rvmrc +1 -0
  4. data/.yardopts +1 -0
  5. data/Gemfile +20 -0
  6. data/LICENSE +20 -0
  7. data/README.md +28 -0
  8. data/Rakefile +8 -0
  9. data/bin/ganymed +20 -0
  10. data/ganymed.gemspec +38 -0
  11. data/lib/ganymed/client.rb +132 -0
  12. data/lib/ganymed/collector/cpu.rb +30 -0
  13. data/lib/ganymed/collector/disk.rb +35 -0
  14. data/lib/ganymed/collector/iostat.rb +25 -0
  15. data/lib/ganymed/collector/load.rb +18 -0
  16. data/lib/ganymed/collector/metadata.rb +31 -0
  17. data/lib/ganymed/collector/network.rb +30 -0
  18. data/lib/ganymed/collector/uptime.rb +15 -0
  19. data/lib/ganymed/collector.rb +89 -0
  20. data/lib/ganymed/config.yml +49 -0
  21. data/lib/ganymed/event.rb +102 -0
  22. data/lib/ganymed/master.rb +150 -0
  23. data/lib/ganymed/mongodb.rb +39 -0
  24. data/lib/ganymed/processor.rb +93 -0
  25. data/lib/ganymed/sampler/counter.rb +32 -0
  26. data/lib/ganymed/sampler/datasource.rb +92 -0
  27. data/lib/ganymed/sampler/derive.rb +36 -0
  28. data/lib/ganymed/sampler/gauge.rb +24 -0
  29. data/lib/ganymed/sampler.rb +106 -0
  30. data/lib/ganymed/version.rb +3 -0
  31. data/lib/ganymed/websocket/authentication.rb +37 -0
  32. data/lib/ganymed/websocket/connection.rb +71 -0
  33. data/lib/ganymed/websocket/filter.rb +23 -0
  34. data/lib/ganymed/websocket/metadata.rb +21 -0
  35. data/lib/ganymed/websocket/query.rb +26 -0
  36. data/lib/ganymed/websocket/subscribe.rb +45 -0
  37. data/lib/ganymed/websocket.rb +45 -0
  38. data/lib/ganymed.rb +6 -0
  39. data/spec/sampler/counter_spec.rb +11 -0
  40. data/spec/sampler/datasource_examples.rb +49 -0
  41. data/spec/sampler/datasource_spec.rb +23 -0
  42. data/spec/sampler/derive_spec.rb +34 -0
  43. data/spec/sampler/gauge_spec.rb +35 -0
  44. data/spec/sampler_spec.rb +5 -0
  45. data/spec/spec.opts +1 -0
  46. data/spec/spec_helper.rb +10 -0
  47. data/tasks/reek.rake +5 -0
  48. data/tasks/rspec.rake +7 -0
  49. data/tasks/yard.rake +5 -0
  50. metadata +242 -0
@@ -0,0 +1,37 @@
1
+ require 'digest'
2
+
3
+ require 'ganymed/websocket'
4
+
5
+ module Ganymed
6
+ class Websocket
7
+ module Authentication
8
+ def self.included(base)
9
+ base.command :authenticate
10
+ end
11
+
12
+ def authenticate(data)
13
+ @authenticated = db['users'].find({
14
+ :_id => data['username'],
15
+ :password => Digest::SHA256.hexdigest(data['password'])
16
+ }).count > 0
17
+
18
+ if authenticated?
19
+ log.info("successful auth for #{data['username']} from #{peer}")
20
+ send(:state, {authenticated: true})
21
+ metadata # trigger metadata push
22
+ else
23
+ log.warn("bad auth for #{data['username']} from #{peer}")
24
+ send(:state, {authenticated: false})
25
+ end
26
+ rescue Exception => exc
27
+ log.warn("failed to authenticate #{peer}")
28
+ log.exception(exc)
29
+ send(:state, {authenticated: false})
30
+ end
31
+
32
+ def authenticated?
33
+ @authenticated ||= false
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,71 @@
1
+ require 'em-websocket'
2
+ require 'yajl'
3
+
4
+ require 'ganymed/websocket'
5
+ require 'ganymed/websocket/authentication'
6
+ require 'ganymed/websocket/subscribe'
7
+ require 'ganymed/websocket/metadata'
8
+ require 'ganymed/websocket/query'
9
+
10
+ module Ganymed
11
+ class Websocket
12
+ class Connection < EventMachine::WebSocket::Connection
13
+ attr_accessor :config, :db
14
+
15
+ def self.command(command)
16
+ @commands ||= []
17
+ @commands << command
18
+ end
19
+
20
+ def self.commands
21
+ @commands
22
+ end
23
+
24
+ include Authentication
25
+ include Metadata
26
+ include Subscribe
27
+ include Query
28
+
29
+ def peer
30
+ sin = Socket.unpack_sockaddr_in(get_peername)
31
+ "#{sin[1]}:#{sin[0]}"
32
+ rescue
33
+ "unknown"
34
+ end
35
+
36
+ def convert(data)
37
+ if data.is_a?(Hash)
38
+ data
39
+ elsif data.is_a?(Array)
40
+ data.map {|elem| convert(elem)}
41
+ else
42
+ if data.respond_to?(:as_json)
43
+ data.as_json
44
+ elsif data.respond_to?(:to_h)
45
+ data.to_h
46
+ elsif data.respond_to?(:to_a)
47
+ data.to_a
48
+ else
49
+ data
50
+ end
51
+ end
52
+ end
53
+
54
+ def send(type, data)
55
+ super(Yajl::Encoder.encode({type.to_s => convert(data)}))
56
+ end
57
+
58
+ def trigger_on_message(data)
59
+ data = Yajl::Parser.parse(data)
60
+ data.each do |command, data|
61
+ log.debug("command #{command}(#{data.inspect})")
62
+ if self.class.commands.include?(command.to_sym)
63
+ __send__(command.to_sym, data)
64
+ else
65
+ send(:error, "invalid command")
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,23 @@
1
+ require 'ganymed/websocket'
2
+
3
+ module Ganymed
4
+ class Websocket
5
+ class Filter
6
+ def initialize(spec)
7
+ @spec = spec
8
+ end
9
+
10
+ def match?(event)
11
+ @spec.map do |skey, svalue|
12
+ evalue = event.send(skey.to_sym)
13
+ case svalue
14
+ when String
15
+ svalue == evalue
16
+ when Array
17
+ svalue.include?(evalue)
18
+ end
19
+ end.all?
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,21 @@
1
+ require 'digest'
2
+
3
+ require 'ganymed/websocket'
4
+
5
+ module Ganymed
6
+ class Websocket
7
+ module Metadata
8
+ def self.included(base)
9
+ base.command :metadata
10
+ end
11
+
12
+ def metadata(data=nil)
13
+ metadata = db['metadata'].find()
14
+ send(:metadata, metadata)
15
+ rescue Exception => exc
16
+ log.warn("failed to send metadata to #{peer}")
17
+ log.exception(exc)
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,26 @@
1
+ require 'ganymed/websocket'
2
+ require 'ganymed/websocket/filter'
3
+
4
+ module Ganymed
5
+ class Websocket
6
+ module Query
7
+ def self.included(base)
8
+ base.command :query
9
+ end
10
+
11
+ def query(data)
12
+ return if not authenticated?
13
+ data.each do |ns, query|
14
+ query_id = query.delete('_id')
15
+ log.debug("query #{query_id} from #{peer}: #{ns}(#{query.inspect})")
16
+
17
+ events = db.collection(ns).find(query).map do |event|
18
+ Event.parse(event.merge({'n' => ns}))
19
+ end
20
+
21
+ send(:result, {query_id => convert(events)})
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,45 @@
1
+ require 'ganymed/websocket'
2
+ require 'ganymed/websocket/filter'
3
+
4
+ module Ganymed
5
+ class Websocket
6
+ module Subscribe
7
+ def self.included(base)
8
+ base.command :subscribe
9
+ base.command :unsubscribe
10
+ end
11
+
12
+ def filters
13
+ @filters ||= {}
14
+ end
15
+
16
+ def subscribe(data)
17
+ return if not authenticated?
18
+ data.each do |prefix, filter|
19
+ log.debug("client #{peer} subscribed to #{prefix} with filter=#{filter.inspect}")
20
+ filters[prefix] = Filter.new(filter)
21
+ end
22
+ end
23
+
24
+ def unsubscribe(data)
25
+ return if not authenticated?
26
+ data.each do |prefix, filter|
27
+ log.debug("client #{peer} unsubscribed prefix #{prefix}")
28
+ filters.delete(prefix)
29
+ end
30
+ end
31
+
32
+ def subscribed?(event)
33
+ filters.map do |prefix, filter|
34
+ filter if prefix == "all" or event.ns =~ /^#{prefix}/
35
+ end.compact.map do |filter|
36
+ filter.match?(event)
37
+ end.any?
38
+ end
39
+
40
+ def publish(event)
41
+ send(:event, [event]) if subscribed?(event)
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,45 @@
1
+ require 'em-websocket'
2
+ require 'yajl'
3
+
4
+ require 'ganymed'
5
+ require 'ganymed/websocket/connection'
6
+
7
+ module Ganymed
8
+ class Websocket
9
+ attr_accessor :config, :db
10
+
11
+ def initialize(config, db)
12
+ @config, @db = config, db
13
+ @connections = []
14
+ run
15
+ end
16
+
17
+ def run
18
+ log.info("accepting WebSocket connections on tcp##{config.host}:#{config.port}")
19
+ EM.start_server(config.host, config.port, Connection, {}) do |connection|
20
+ connection.config = config
21
+ connection.db = db
22
+
23
+ connection.onopen do
24
+ log.info("new connection from #{connection.peer}")
25
+ @connections << connection
26
+ end
27
+
28
+ connection.onclose do
29
+ log.info("#{connection.peer} has closed the connection")
30
+ @connections.delete(connection)
31
+ end
32
+ end
33
+ end
34
+
35
+ def each(&block)
36
+ @connections.each(&block)
37
+ end
38
+
39
+ def send(type, data)
40
+ each do |connection|
41
+ connection.send(type, data)
42
+ end
43
+ end
44
+ end
45
+ end
data/lib/ganymed.rb ADDED
@@ -0,0 +1,6 @@
1
+ require 'madvertise/ext/logging'
2
+
3
+ module Ganymed
4
+ GEM_ROOT = File.expand_path("../..", __FILE__).freeze
5
+ LIB_DIR = File.join(GEM_ROOT, "lib").freeze
6
+ end
@@ -0,0 +1,11 @@
1
+ require 'ganymed/sampler/counter'
2
+ require 'sampler/datasource_examples'
3
+
4
+ describe Ganymed::Sampler::Counter do
5
+ include_context 'DataSource'
6
+ subject { Ganymed::Sampler::Counter.new(ticks) }
7
+
8
+ let(:values) { 10.times.collect { rand } }
9
+
10
+ it_behaves_like Ganymed::Sampler::DataSource
11
+ end
@@ -0,0 +1,49 @@
1
+ require 'ganymed/sampler/datasource'
2
+
3
+ shared_context 'DataSource' do
4
+ let(:ns) { 'foo.bar' }
5
+ let(:origin) { 'example.com' }
6
+ let(:ticks) { [300, 1] }
7
+ let(:value) { rand }
8
+ end
9
+
10
+ shared_examples Ganymed::Sampler::DataSource do
11
+ %w(
12
+ add
13
+ buffer
14
+ each
15
+ feed
16
+ flush
17
+ ticks
18
+ ).each do |method|
19
+ it { should respond_to method }
20
+ end
21
+
22
+ describe "#add" do
23
+ it "should add the given value to each tick buffer" do
24
+ subject.add(ns, origin, value)
25
+ ticks.each do |tick|
26
+ subject.buffer[tick][ns][origin].should include(value)
27
+ end
28
+ end
29
+ end
30
+
31
+ describe "#each" do
32
+ before(:each) do
33
+ values.each do |value|
34
+ subject.add(ns, origin, value)
35
+ end
36
+ end
37
+
38
+ it "should clear the buffer" do
39
+ subject.each(1) { |ns, origin, values| }
40
+ subject.buffer[1].should be_empty
41
+ end
42
+
43
+ it "should yield all values" do
44
+ subject.should_receive(:each).with(1).once.and_yield(ns, origin, values).once
45
+ subject.each(1) { |ns, origin, values| }
46
+ end
47
+ end
48
+
49
+ end
@@ -0,0 +1,23 @@
1
+ require 'ganymed/sampler/derive'
2
+ require 'sampler/datasource_examples'
3
+
4
+ describe Ganymed::Sampler::DataSource do
5
+ include_context 'DataSource'
6
+ subject { Ganymed::Sampler::DataSource.new(ticks) }
7
+
8
+ let(:values) { 10.times.collect { rand } }
9
+
10
+ it_behaves_like Ganymed::Sampler::DataSource
11
+
12
+ describe "#flush" do
13
+ it "is not implemented" do
14
+ expect { subject.flush(1) }.to raise_error(NotImplementedError)
15
+ end
16
+ end
17
+
18
+ describe "#feed" do
19
+ it "is not implemented" do
20
+ expect { subject.feed(ns, origin, nil, value) }.to raise_error(NotImplementedError)
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,34 @@
1
+ require 'ganymed/sampler/derive'
2
+ require 'sampler/datasource_examples'
3
+
4
+ describe Ganymed::Sampler::Derive do
5
+ include_context 'DataSource'
6
+ subject { Ganymed::Sampler::Derive.new(ticks) }
7
+
8
+ let(:ts) { now = Time.now.utc; now.to_i + (now.usec * 1e-6) }
9
+ let(:values) { 10.times.collect { rand } }
10
+
11
+ it_behaves_like Ganymed::Sampler::DataSource
12
+
13
+ describe "#flush" do
14
+ before(:each) do
15
+ values.each do |value|
16
+ subject.feed(ns, origin, ts, value)
17
+ end
18
+ end
19
+
20
+ it "should call each" do
21
+ subject.should_receive(:each).with(1).once
22
+ subject.flush(1)
23
+ end
24
+
25
+ it "should yield derived values"
26
+ end
27
+
28
+ describe "#feed" do
29
+ it "should add fed samples" do
30
+ subject.should_receive(:add).with(ns, origin, [ts, value])
31
+ subject.feed(ns, origin, ts, value)
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,35 @@
1
+ require 'ganymed/sampler/gauge'
2
+ require 'sampler/datasource_examples'
3
+
4
+ describe Ganymed::Sampler::Gauge do
5
+ include_context 'DataSource'
6
+ subject { Ganymed::Sampler::Gauge.new(ticks) }
7
+
8
+ let(:values) { 10.times.collect { rand } }
9
+
10
+ it_behaves_like Ganymed::Sampler::DataSource
11
+
12
+ describe "#flush" do
13
+ before(:each) do
14
+ values.each do |value|
15
+ subject.feed(ns, origin, nil, value)
16
+ end
17
+ end
18
+
19
+ it "should call each" do
20
+ subject.should_receive(:each).with(1).once
21
+ subject.flush(1)
22
+ end
23
+
24
+ it "should yield all values" do
25
+ expect { |b| subject.flush(1, &b) }.to yield_with_args(ns, origin, values)
26
+ end
27
+ end
28
+
29
+ describe "#feed" do
30
+ it "should add fed samples" do
31
+ subject.should_receive(:add).with(ns, origin, value)
32
+ subject.feed(ns, origin, nil, value)
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,5 @@
1
+ require 'ganymed/sampler'
2
+
3
+ describe Ganymed::Sampler do
4
+
5
+ end
data/spec/spec.opts ADDED
@@ -0,0 +1 @@
1
+ --require spec_helper --color --format documentation
@@ -0,0 +1,10 @@
1
+ require 'rubygems'
2
+ $:.unshift(File.join(File.expand_path("../..", __FILE__), 'lib'))
3
+
4
+ require 'rspec'
5
+ require 'debugger'
6
+ require 'simplecov'
7
+ SimpleCov.start
8
+
9
+ RSpec.configure do |config|
10
+ end
data/tasks/reek.rake ADDED
@@ -0,0 +1,5 @@
1
+ require 'reek/rake/task'
2
+
3
+ Reek::Rake::Task.new do |t|
4
+ t.fail_on_error = false
5
+ end
data/tasks/rspec.rake ADDED
@@ -0,0 +1,7 @@
1
+ require 'rspec'
2
+ require 'rspec/core/rake_task'
3
+
4
+ desc "Run the specs"
5
+ RSpec::Core::RakeTask.new do |t|
6
+ t.rspec_opts = ['--options', "spec/spec.opts"]
7
+ end
data/tasks/yard.rake ADDED
@@ -0,0 +1,5 @@
1
+ require 'yard'
2
+
3
+ YARD::Rake::YardocTask.new do |t|
4
+ t.files = ['lib/**/*.rb', 'README.rdoc']
5
+ end