ganymed 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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