ganymed 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +22 -0
- data/.rspec +1 -0
- data/.rvmrc +1 -0
- data/.yardopts +1 -0
- data/Gemfile +20 -0
- data/LICENSE +20 -0
- data/README.md +28 -0
- data/Rakefile +8 -0
- data/bin/ganymed +20 -0
- data/ganymed.gemspec +38 -0
- data/lib/ganymed/client.rb +132 -0
- data/lib/ganymed/collector/cpu.rb +30 -0
- data/lib/ganymed/collector/disk.rb +35 -0
- data/lib/ganymed/collector/iostat.rb +25 -0
- data/lib/ganymed/collector/load.rb +18 -0
- data/lib/ganymed/collector/metadata.rb +31 -0
- data/lib/ganymed/collector/network.rb +30 -0
- data/lib/ganymed/collector/uptime.rb +15 -0
- data/lib/ganymed/collector.rb +89 -0
- data/lib/ganymed/config.yml +49 -0
- data/lib/ganymed/event.rb +102 -0
- data/lib/ganymed/master.rb +150 -0
- data/lib/ganymed/mongodb.rb +39 -0
- data/lib/ganymed/processor.rb +93 -0
- data/lib/ganymed/sampler/counter.rb +32 -0
- data/lib/ganymed/sampler/datasource.rb +92 -0
- data/lib/ganymed/sampler/derive.rb +36 -0
- data/lib/ganymed/sampler/gauge.rb +24 -0
- data/lib/ganymed/sampler.rb +106 -0
- data/lib/ganymed/version.rb +3 -0
- data/lib/ganymed/websocket/authentication.rb +37 -0
- data/lib/ganymed/websocket/connection.rb +71 -0
- data/lib/ganymed/websocket/filter.rb +23 -0
- data/lib/ganymed/websocket/metadata.rb +21 -0
- data/lib/ganymed/websocket/query.rb +26 -0
- data/lib/ganymed/websocket/subscribe.rb +45 -0
- data/lib/ganymed/websocket.rb +45 -0
- data/lib/ganymed.rb +6 -0
- data/spec/sampler/counter_spec.rb +11 -0
- data/spec/sampler/datasource_examples.rb +49 -0
- data/spec/sampler/datasource_spec.rb +23 -0
- data/spec/sampler/derive_spec.rb +34 -0
- data/spec/sampler/gauge_spec.rb +35 -0
- data/spec/sampler_spec.rb +5 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +10 -0
- data/tasks/reek.rake +5 -0
- data/tasks/rspec.rake +7 -0
- data/tasks/yard.rake +5 -0
- 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,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
|
data/spec/spec.opts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--require spec_helper --color --format documentation
|
data/spec/spec_helper.rb
ADDED
data/tasks/reek.rake
ADDED
data/tasks/rspec.rake
ADDED