ganymed 0.3.4 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +1 -2
- data/README.md +6 -14
- data/ganymed.gemspec +4 -17
- data/lib/ganymed/collector.rb +6 -30
- data/lib/ganymed/collectors/cpu.rb +8 -8
- data/lib/ganymed/collectors/disk.rb +2 -2
- data/lib/ganymed/collectors/iostat.rb +2 -2
- data/lib/ganymed/collectors/load.rb +1 -1
- data/lib/ganymed/collectors/memory.rb +8 -8
- data/lib/ganymed/collectors/network.rb +6 -6
- data/lib/ganymed/collectors/process.rb +4 -4
- data/lib/ganymed/config.yml +0 -32
- data/lib/ganymed/master.rb +9 -8
- data/lib/ganymed/version.rb +1 -1
- metadata +8 -152
- data/contrib/cpuhog +0 -0
- data/contrib/cpuhog.c +0 -21
- data/lib/ganymed/collectors/uptime.rb +0 -10
- data/lib/ganymed/console.rb +0 -147
- data/lib/ganymed/event.rb +0 -97
- data/lib/ganymed/ext/array.rb +0 -38
- data/lib/ganymed/mongodb.rb +0 -44
- data/lib/ganymed/processor.rb +0 -137
- data/lib/ganymed/sampler/counter.rb +0 -19
- data/lib/ganymed/sampler/datasource.rb +0 -85
- data/lib/ganymed/sampler/derive.rb +0 -27
- data/lib/ganymed/sampler/gauge.rb +0 -20
- data/lib/ganymed/sampler.rb +0 -94
- data/lib/ganymed/websocket/authentication.rb +0 -37
- data/lib/ganymed/websocket/connection.rb +0 -71
- data/lib/ganymed/websocket/filter.rb +0 -23
- data/lib/ganymed/websocket/metadata.rb +0 -21
- data/lib/ganymed/websocket/query.rb +0 -30
- data/lib/ganymed/websocket/subscribe.rb +0 -45
- data/lib/ganymed/websocket.rb +0 -45
- data/spec/sampler/counter_spec.rb +0 -11
- data/spec/sampler/datasource_examples.rb +0 -49
- data/spec/sampler/datasource_spec.rb +0 -23
- data/spec/sampler/derive_spec.rb +0 -34
- data/spec/sampler/gauge_spec.rb +0 -35
- data/spec/sampler_spec.rb +0 -5
data/lib/ganymed/console.rb
DELETED
@@ -1,147 +0,0 @@
|
|
1
|
-
require 'ascii_charts'
|
2
|
-
require 'digest'
|
3
|
-
require 'madvertise/ext/config'
|
4
|
-
require 'ripl'
|
5
|
-
require 'terminal-table'
|
6
|
-
|
7
|
-
require 'ganymed'
|
8
|
-
require 'ganymed/mongodb'
|
9
|
-
|
10
|
-
module Ganymed
|
11
|
-
class Console
|
12
|
-
include Configuration::Helpers
|
13
|
-
|
14
|
-
attr_accessor :db
|
15
|
-
|
16
|
-
def initialize
|
17
|
-
@cache = {}
|
18
|
-
|
19
|
-
# load config file
|
20
|
-
@default_config_file = File.join(LIB_DIR, 'ganymed/config.yml')
|
21
|
-
|
22
|
-
# store fqdn for later use
|
23
|
-
config.fqdn = ::Socket.gethostbyname(::Socket.gethostname).first
|
24
|
-
|
25
|
-
# initialize db before loading ripl
|
26
|
-
log.silence do
|
27
|
-
@db = Ganymed::MongoDB.new(config.processor.mongodb)
|
28
|
-
end
|
29
|
-
|
30
|
-
Ripl.start(binding: binding)
|
31
|
-
end
|
32
|
-
|
33
|
-
def adduser(username, password)
|
34
|
-
hashed = Digest::SHA256.hexdigest(password)
|
35
|
-
db['users'].find_and_modify(
|
36
|
-
query: {_id: username},
|
37
|
-
update: {password: hashed},
|
38
|
-
new: true
|
39
|
-
)
|
40
|
-
end
|
41
|
-
|
42
|
-
def origins
|
43
|
-
headings = ["Origin"]
|
44
|
-
rows = db['metadata'].find.to_a.map do |row|
|
45
|
-
[row['_id']]
|
46
|
-
end
|
47
|
-
puts Terminal::Table.new(rows: rows, headings: headings)
|
48
|
-
end
|
49
|
-
|
50
|
-
alias_method :o, :origins
|
51
|
-
|
52
|
-
def list
|
53
|
-
db.collection_names.reject do |col|
|
54
|
-
%w(
|
55
|
-
metadata
|
56
|
-
system.indexes
|
57
|
-
users
|
58
|
-
).include?(col)
|
59
|
-
end.sort.each do |col|
|
60
|
-
puts col
|
61
|
-
end
|
62
|
-
nil
|
63
|
-
end
|
64
|
-
|
65
|
-
alias_method :ls, :list
|
66
|
-
alias_method :l, :list
|
67
|
-
|
68
|
-
def drop
|
69
|
-
db.connection.drop_database(config.processor.mongodb.database)
|
70
|
-
end
|
71
|
-
|
72
|
-
def method_missing(method, *args)
|
73
|
-
pattern = /^#{method}\./
|
74
|
-
|
75
|
-
namespaces = db.collection_names.select do |col|
|
76
|
-
col =~ pattern
|
77
|
-
end
|
78
|
-
|
79
|
-
if namespaces.empty?
|
80
|
-
super
|
81
|
-
else
|
82
|
-
@cache[method] ||= CollectionProxy.new(method, db, config)
|
83
|
-
end
|
84
|
-
end
|
85
|
-
|
86
|
-
class CollectionProxy
|
87
|
-
attr_reader :ns, :db, :config
|
88
|
-
|
89
|
-
def initialize(ns, db, config)
|
90
|
-
@ns, @db, @config = ns, db, config
|
91
|
-
@cache = {}
|
92
|
-
@consolidation = "avg"
|
93
|
-
|
94
|
-
metaclass = class << self; self; end
|
95
|
-
config.sampler.consolidations.each do |key, _|
|
96
|
-
metaclass.send(:define_method, key.to_sym) do
|
97
|
-
@consolidation = key
|
98
|
-
self
|
99
|
-
end
|
100
|
-
end
|
101
|
-
end
|
102
|
-
|
103
|
-
def top(limit=10, selector={})
|
104
|
-
selector.merge!({c: { :$in => [nil, @consolidation]}})
|
105
|
-
opts = {limit: limit, sort: [:t, -1]}
|
106
|
-
db[ns].find(selector, opts).to_a.map do |row|
|
107
|
-
row.delete('_id')
|
108
|
-
row.delete('c')
|
109
|
-
row['t'] = Time.at(row['t'])
|
110
|
-
row
|
111
|
-
end
|
112
|
-
end
|
113
|
-
|
114
|
-
def to_s
|
115
|
-
headings = ["Time", "Origin", "Value"]
|
116
|
-
rows = top.map do |row|
|
117
|
-
[row['t'], row['o'], row['v']]
|
118
|
-
end
|
119
|
-
table = Terminal::Table.new(rows: rows, headings: headings)
|
120
|
-
"top-10 most recent events:\n" + table.to_s
|
121
|
-
end
|
122
|
-
|
123
|
-
def chart(limit=10, origin=nil)
|
124
|
-
origin ||= config.fqdn
|
125
|
-
values = top(limit, {o: origin}).reverse.map do |event|
|
126
|
-
[event['t'].strftime("%H:%M"), event['v']]
|
127
|
-
end
|
128
|
-
puts AsciiCharts::Cartesian.new(values, bar: true).draw
|
129
|
-
end
|
130
|
-
|
131
|
-
def method_missing(method, *args)
|
132
|
-
subns = "#{@ns}.#{method}"
|
133
|
-
pattern = /^#{subns}(\.|$)/
|
134
|
-
|
135
|
-
namespaces = db.collection_names.select do |col|
|
136
|
-
col =~ pattern
|
137
|
-
end
|
138
|
-
|
139
|
-
if namespaces.empty?
|
140
|
-
db[ns].send(method, *args)
|
141
|
-
else
|
142
|
-
@cache[method] ||= CollectionProxy.new(subns, db, config)
|
143
|
-
end
|
144
|
-
end
|
145
|
-
end
|
146
|
-
end
|
147
|
-
end
|
data/lib/ganymed/event.rb
DELETED
@@ -1,97 +0,0 @@
|
|
1
|
-
module Ganymed
|
2
|
-
|
3
|
-
##
|
4
|
-
# An incoming event is a hash-like object and contains the following keys:
|
5
|
-
#
|
6
|
-
# n:: namespace (e.g. +os.cpu.idle+, +nginx.requests+, etc)
|
7
|
-
# c:: a consolidation function used to build this event from multiple
|
8
|
-
# samples (_optional_)
|
9
|
-
# o:: origin (e.g. +web1.example.com+, +myapplication+)
|
10
|
-
# t:: timestamp as seconds since epoch
|
11
|
-
# r:: resolution of this event (i.e. how often is this event sent to the processor, _optional_)
|
12
|
-
# v:: the event value (any object that can be serialized with BSON and JSON)
|
13
|
-
#
|
14
|
-
class Event
|
15
|
-
# The namespace of this event.
|
16
|
-
# @return [String]
|
17
|
-
attr_accessor :ns
|
18
|
-
|
19
|
-
# The consolidation function used to build this event from multiple
|
20
|
-
# samples if any.
|
21
|
-
# @return [String]
|
22
|
-
attr_accessor :cf
|
23
|
-
|
24
|
-
# The origin of this event.
|
25
|
-
# @return [String]
|
26
|
-
attr_accessor :origin
|
27
|
-
|
28
|
-
# The timestamp of this event.
|
29
|
-
# @return [Fixnum]
|
30
|
-
attr_accessor :timestamp
|
31
|
-
|
32
|
-
# The event resolution
|
33
|
-
# @return [Fixnum]
|
34
|
-
#
|
35
|
-
# @note +nil+ or +0+ will bypass range checks for MongoDB, refer to the
|
36
|
-
# {file:doc/configuration.md configuration} for details.
|
37
|
-
attr_accessor :resolution
|
38
|
-
|
39
|
-
# The actual value of this sample.
|
40
|
-
# @return [Object]
|
41
|
-
attr_accessor :value
|
42
|
-
|
43
|
-
# Create a new {Event} instance from +obj+.
|
44
|
-
#
|
45
|
-
# @return [Event] The parsed event instance.
|
46
|
-
def self.parse(obj)
|
47
|
-
event = new
|
48
|
-
event.ns = obj['n']
|
49
|
-
event.cf = obj['c']
|
50
|
-
event.origin = obj['o']
|
51
|
-
event.timestamp = obj['t'].to_i
|
52
|
-
event.resolution = obj['r'].to_i
|
53
|
-
|
54
|
-
value = obj['v']
|
55
|
-
if value.is_a?(Float) and (value.nan? or value.infinite?)
|
56
|
-
event.value = nil
|
57
|
-
else
|
58
|
-
event.value = value
|
59
|
-
end
|
60
|
-
|
61
|
-
event.validate
|
62
|
-
event
|
63
|
-
end
|
64
|
-
|
65
|
-
# Convert this event into a hash with long keys. This hash cannot be parsed
|
66
|
-
# by #parse which accepts short keys for effiency.
|
67
|
-
#
|
68
|
-
# @return [Hash]
|
69
|
-
def to_h
|
70
|
-
{
|
71
|
-
:ns => ns,
|
72
|
-
:cf => cf,
|
73
|
-
:origin => origin,
|
74
|
-
:timestamp => timestamp,
|
75
|
-
:resolution => resolution,
|
76
|
-
:value => value
|
77
|
-
}
|
78
|
-
end
|
79
|
-
|
80
|
-
# Validate this event.
|
81
|
-
#
|
82
|
-
# @return [void]
|
83
|
-
def validate
|
84
|
-
unless @ns =~ /^[a-z][a-z0-9_\.-]+[a-z0-9]$/i
|
85
|
-
raise "invalid namespace: #{@ns.inspect}"
|
86
|
-
end
|
87
|
-
|
88
|
-
if @ns =~ /^(system\.|metadata)/
|
89
|
-
raise "namespace is reserved: #{@ns.inspect}"
|
90
|
-
end
|
91
|
-
|
92
|
-
if @timestamp < 0
|
93
|
-
raise "invalid timestamp: #{@timestamp.inspect}"
|
94
|
-
end
|
95
|
-
end
|
96
|
-
end
|
97
|
-
end
|
data/lib/ganymed/ext/array.rb
DELETED
@@ -1,38 +0,0 @@
|
|
1
|
-
require 'inline'
|
2
|
-
|
3
|
-
class Array
|
4
|
-
# this is incredibly faster than doing ary.inject(0, :+)
|
5
|
-
# should only be called on numbers-only arrays, though ;-)
|
6
|
-
inline :C do |builder|
|
7
|
-
builder.c_raw <<-EOC
|
8
|
-
static VALUE sum(int argc, VALUE *argv, VALUE self) {
|
9
|
-
double result = 0;
|
10
|
-
VALUE *arr = RARRAY_PTR(self);
|
11
|
-
long i, len = RARRAY_LEN(self);
|
12
|
-
|
13
|
-
for (i = 0; i < len; i++) {
|
14
|
-
result += NUM2DBL(arr[i]);
|
15
|
-
}
|
16
|
-
|
17
|
-
return rb_float_new(result);
|
18
|
-
}
|
19
|
-
EOC
|
20
|
-
end
|
21
|
-
|
22
|
-
def mean
|
23
|
-
sum.to_f / length
|
24
|
-
end
|
25
|
-
|
26
|
-
def variance
|
27
|
-
m = mean
|
28
|
-
reduce(0) {|accum, item| accum + (item - m) ** 2}.to_f / (length - 1)
|
29
|
-
end
|
30
|
-
|
31
|
-
def stdev
|
32
|
-
Math.sqrt(variance)
|
33
|
-
end
|
34
|
-
|
35
|
-
def percentile(pc)
|
36
|
-
sort[(pc * length).ceil - 1]
|
37
|
-
end
|
38
|
-
end
|
data/lib/ganymed/mongodb.rb
DELETED
@@ -1,44 +0,0 @@
|
|
1
|
-
require 'mongo'
|
2
|
-
|
3
|
-
module Ganymed
|
4
|
-
# @private
|
5
|
-
class MongoDB
|
6
|
-
attr_reader :config
|
7
|
-
|
8
|
-
def initialize(config)
|
9
|
-
@config = config
|
10
|
-
@collections = {}
|
11
|
-
log.info("using MongoDB at #{config.host}:#{config.port}/#{config.database}")
|
12
|
-
end
|
13
|
-
|
14
|
-
def connection
|
15
|
-
@connection ||= ::Mongo::Connection.new(config.host, config.port,
|
16
|
-
:pool_size => config.pool_size,
|
17
|
-
:pool_timeout => config.pool_timeout)
|
18
|
-
end
|
19
|
-
|
20
|
-
def db
|
21
|
-
@db ||= connection.db(config.database)
|
22
|
-
end
|
23
|
-
|
24
|
-
def collection(ns)
|
25
|
-
return @collections[ns] if @collections.has_key?(ns)
|
26
|
-
col = @collections[ns] = db.collection(ns)
|
27
|
-
col.ensure_index([['c', ::Mongo::ASCENDING]])
|
28
|
-
col.ensure_index([['o', ::Mongo::ASCENDING]])
|
29
|
-
col.ensure_index([['t', ::Mongo::ASCENDING]])
|
30
|
-
col.ensure_index([['c', ::Mongo::ASCENDING], ['o', ::Mongo::ASCENDING]])
|
31
|
-
col.ensure_index([['o', ::Mongo::ASCENDING], ['t', ::Mongo::ASCENDING]])
|
32
|
-
col.ensure_index([['c', ::Mongo::ASCENDING], ['o', ::Mongo::ASCENDING], ['t', ::Mongo::ASCENDING]], :unique => true)
|
33
|
-
col
|
34
|
-
end
|
35
|
-
|
36
|
-
def [](*args)
|
37
|
-
db.collection(*args)
|
38
|
-
end
|
39
|
-
|
40
|
-
def method_missing(method, *args)
|
41
|
-
db.send(method, *args)
|
42
|
-
end
|
43
|
-
end
|
44
|
-
end
|
data/lib/ganymed/processor.rb
DELETED
@@ -1,137 +0,0 @@
|
|
1
|
-
require 'eventmachine'
|
2
|
-
require 'msgpack'
|
3
|
-
|
4
|
-
require 'ganymed/client'
|
5
|
-
require 'ganymed/event'
|
6
|
-
require 'ganymed/mongodb'
|
7
|
-
require 'ganymed/websocket'
|
8
|
-
|
9
|
-
module Ganymed
|
10
|
-
|
11
|
-
##
|
12
|
-
# The Processor accepts {Event events} from a UDP socket and stores these
|
13
|
-
# metrics into MongoDB. All metrics are also published to interested parties
|
14
|
-
# via WebSocket.
|
15
|
-
#
|
16
|
-
class Processor
|
17
|
-
def initialize(config)
|
18
|
-
@config = config
|
19
|
-
@db = MongoDB.new(config.processor.mongodb)
|
20
|
-
@websocket = Websocket.new(config.processor.websocket, @db)
|
21
|
-
|
22
|
-
@config.client.sampler.tap do |sampler|
|
23
|
-
# schedule connect on next tick so that our sampler socket is
|
24
|
-
# available. Processor#initialize is called from EM.run and therefore
|
25
|
-
# no sockets have been opened yet.
|
26
|
-
EM.next_tick { @sampler = Ganymed::Client::Sampler.connect(sampler.host, sampler.port) }
|
27
|
-
end
|
28
|
-
|
29
|
-
cache!
|
30
|
-
listen!
|
31
|
-
end
|
32
|
-
|
33
|
-
def cache!
|
34
|
-
@metadata = Hash.new do |hsh, key|
|
35
|
-
hsh[key] = []
|
36
|
-
end
|
37
|
-
|
38
|
-
@db['metadata'].find({}, fields: [:_id, :namespaces]).each do |result|
|
39
|
-
@metadata[result['_id']] = result['namespaces']
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
|
-
def listen!
|
44
|
-
@config.processor.listen.tap do |listen|
|
45
|
-
log.info("processing metrics on udp##{listen.host}:#{listen.port}")
|
46
|
-
EM.open_datagram_socket(listen.host, listen.port, Connection) do |connection|
|
47
|
-
connection.processor = self
|
48
|
-
end
|
49
|
-
|
50
|
-
log.info("processing metrics on tcp##{listen.host}:#{listen.port}")
|
51
|
-
EM.start_server(listen.host, listen.port, Connection) do |connection|
|
52
|
-
connection.processor = self
|
53
|
-
end
|
54
|
-
end
|
55
|
-
end
|
56
|
-
|
57
|
-
def process(data)
|
58
|
-
case data['_type'].to_sym
|
59
|
-
when :event
|
60
|
-
process_event(Event.parse(data))
|
61
|
-
when :metadata
|
62
|
-
process_metadata(data)
|
63
|
-
end
|
64
|
-
rescue Exception => exc
|
65
|
-
log.exception(exc)
|
66
|
-
end
|
67
|
-
|
68
|
-
def process_event(event)
|
69
|
-
@sampler.emit(:counter, 'ganymed.processor.events', 1) rescue nil
|
70
|
-
|
71
|
-
if not @metadata[event.origin].include?(event.ns)
|
72
|
-
# insert origin + namespace into metadata
|
73
|
-
metadata = @db['metadata'].find_and_modify({
|
74
|
-
query: {:_id => event.origin},
|
75
|
-
update: {:$addToSet => {:namespaces => event.ns}},
|
76
|
-
upsert: true,
|
77
|
-
})
|
78
|
-
|
79
|
-
# set defaults in case find_and_modify returned an empty document
|
80
|
-
metadata['_id'] ||= event.origin
|
81
|
-
metadata['namespaces'] ||= [event.ns]
|
82
|
-
|
83
|
-
# notify websocket clients
|
84
|
-
@websocket.send(:metadata, [metadata])
|
85
|
-
end
|
86
|
-
|
87
|
-
# normalize timestamp for events with a resolution
|
88
|
-
if event.resolution > 1
|
89
|
-
event.timestamp -= event.timestamp % event.resolution
|
90
|
-
end
|
91
|
-
|
92
|
-
# insert event into ns collection
|
93
|
-
@db.collection(event.ns).update({
|
94
|
-
:c => event.cf,
|
95
|
-
:o => event.origin,
|
96
|
-
:t => event.timestamp
|
97
|
-
}, {
|
98
|
-
:$set => {:v => event.value}
|
99
|
-
}, :upsert => true)
|
100
|
-
|
101
|
-
# send the event to websocket clients
|
102
|
-
@websocket.each do |connection|
|
103
|
-
connection.publish(event)
|
104
|
-
end
|
105
|
-
end
|
106
|
-
|
107
|
-
def process_metadata(data)
|
108
|
-
metadata = @db['metadata'].find_and_modify({
|
109
|
-
query: {:_id => data['origin']},
|
110
|
-
update: {:$set => {:data => data['data']}},
|
111
|
-
upsert: true,
|
112
|
-
new: true
|
113
|
-
})
|
114
|
-
|
115
|
-
@websocket.send(:metadata, [metadata])
|
116
|
-
end
|
117
|
-
|
118
|
-
# @private
|
119
|
-
class Connection < EM::Connection
|
120
|
-
attr_accessor :processor
|
121
|
-
|
122
|
-
def initialize
|
123
|
-
@pac = MessagePack::Unpacker.new
|
124
|
-
end
|
125
|
-
|
126
|
-
def receive_data(data)
|
127
|
-
@pac.feed(data)
|
128
|
-
@pac.each do |obj|
|
129
|
-
EM.defer { processor.process(obj) }
|
130
|
-
end
|
131
|
-
rescue Exception => exc
|
132
|
-
log.exception(exc)
|
133
|
-
close_connection
|
134
|
-
end
|
135
|
-
end
|
136
|
-
end
|
137
|
-
end
|
@@ -1,19 +0,0 @@
|
|
1
|
-
require 'ganymed/ext/array'
|
2
|
-
require 'ganymed/sampler/datasource'
|
3
|
-
|
4
|
-
module Ganymed
|
5
|
-
class Sampler
|
6
|
-
|
7
|
-
##
|
8
|
-
# A Counter {DataSource} processes samples counting a specific event (such
|
9
|
-
# as a webserver request, user login, etc) and produces a rate/s gauge.
|
10
|
-
#
|
11
|
-
class Counter < DataSource
|
12
|
-
def flush
|
13
|
-
each do |ns, origin, ts|
|
14
|
-
yield ns, origin, ts.values.map(&:sum)
|
15
|
-
end
|
16
|
-
end
|
17
|
-
end
|
18
|
-
end
|
19
|
-
end
|
@@ -1,85 +0,0 @@
|
|
1
|
-
require 'inline'
|
2
|
-
|
3
|
-
module Ganymed
|
4
|
-
class Sampler
|
5
|
-
|
6
|
-
##
|
7
|
-
# A DataSource processes samples from various sources. Samples are stored
|
8
|
-
# to an in-memory buffer using a sliding-window algorithm. Values are kept
|
9
|
-
# for a configurable time and consolidated values can be flushed to the
|
10
|
-
# processor at any time.
|
11
|
-
#
|
12
|
-
# All DataSources interpolate their samples to gauge style values before
|
13
|
-
# flushing to the processor, e.g. a counter (like requests, or bytes sent
|
14
|
-
# over the network) is interpolated to a rate/s gauge (e.g. requests/s or
|
15
|
-
# bytes/s).
|
16
|
-
#
|
17
|
-
class DataSource
|
18
|
-
|
19
|
-
attr_reader :buffer, :window
|
20
|
-
|
21
|
-
# Create a new DataSource.
|
22
|
-
#
|
23
|
-
# @param [Fixnum] window Sliding window size.
|
24
|
-
def initialize(window)
|
25
|
-
@window = window
|
26
|
-
@buffer = Hash.new do |buffer, origin|
|
27
|
-
buffer[origin] = Hash.new do |origin, ns|
|
28
|
-
origin[ns] = Hash.new do |ns, timestamp|
|
29
|
-
ns[timestamp] = []
|
30
|
-
end
|
31
|
-
end
|
32
|
-
end
|
33
|
-
end
|
34
|
-
|
35
|
-
# Feed data from the source.
|
36
|
-
#
|
37
|
-
# @param [String] ns The namespace for this value.
|
38
|
-
# @param [String] origin The origin of this value.
|
39
|
-
# @param [Fixnum,Float] value The actual value.
|
40
|
-
# @return [void]
|
41
|
-
def feed(ns, origin, value)
|
42
|
-
@buffer[origin][ns][now] << value
|
43
|
-
end
|
44
|
-
|
45
|
-
# Flush and consolidate the buffer.
|
46
|
-
#
|
47
|
-
# @yield [ns, origin, values] Run the block once for every
|
48
|
-
# namespace/origin, passing in all the
|
49
|
-
# consolidated values.
|
50
|
-
# @return [void]
|
51
|
-
def flush
|
52
|
-
raise NotImplementedError
|
53
|
-
end
|
54
|
-
|
55
|
-
# Yield values for every origin/ns combination and remove old elements
|
56
|
-
# from the buffer (sliding window). This is used internally to iterate
|
57
|
-
# through the values in the current window.
|
58
|
-
#
|
59
|
-
# @yield [ns, origin, ts] Run the block once for every namespace
|
60
|
-
# passing in all the collected samples grouped
|
61
|
-
# by timestamp.
|
62
|
-
def each(&block)
|
63
|
-
oldest = now - window
|
64
|
-
@buffer.each do |origin, ns|
|
65
|
-
ns.each do |ns, ts|
|
66
|
-
ts.delete_if { |ts, _| ts < oldest }
|
67
|
-
yield ns, origin, ts
|
68
|
-
end
|
69
|
-
end
|
70
|
-
end
|
71
|
-
|
72
|
-
# Time.now.utc.to_i is way too expensive
|
73
|
-
inline :C do |builder|
|
74
|
-
builder.include "<sys/time.h>"
|
75
|
-
builder.c <<-EOC
|
76
|
-
long now(void) {
|
77
|
-
struct timeval tv;
|
78
|
-
gettimeofday(&tv, NULL);
|
79
|
-
return tv.tv_sec;
|
80
|
-
}
|
81
|
-
EOC
|
82
|
-
end
|
83
|
-
end
|
84
|
-
end
|
85
|
-
end
|
@@ -1,27 +0,0 @@
|
|
1
|
-
require 'ganymed/sampler/datasource'
|
2
|
-
|
3
|
-
module Ganymed
|
4
|
-
class Sampler
|
5
|
-
|
6
|
-
##
|
7
|
-
# A Derive {DataSource data source} will store the derivative of the line
|
8
|
-
# going from the last to the current value of the data source. Derive is
|
9
|
-
# similar to a {Counter} {DataSource data source} in that it produces a
|
10
|
-
# rate/s gauge, but only accepts absolute values and produces the
|
11
|
-
# derivative instead of summation of all samples.
|
12
|
-
#
|
13
|
-
class Derive < DataSource
|
14
|
-
def flush
|
15
|
-
each do |ns, origin, ts|
|
16
|
-
values = ts.each_cons(2).map do |a, b|
|
17
|
-
(b[1].last - a[1].last)/(b[0] - a[0]) # ∆value / ∆ts
|
18
|
-
end.reject do |value|
|
19
|
-
value < 0 # ignore overflow/counter wrap
|
20
|
-
end
|
21
|
-
|
22
|
-
yield ns, origin, values
|
23
|
-
end
|
24
|
-
end
|
25
|
-
end
|
26
|
-
end
|
27
|
-
end
|
@@ -1,20 +0,0 @@
|
|
1
|
-
require 'ganymed/sampler/datasource'
|
2
|
-
|
3
|
-
module Ganymed
|
4
|
-
class Sampler
|
5
|
-
|
6
|
-
##
|
7
|
-
# A Gauge is the simplest DataSource type. It simply records the given
|
8
|
-
# value in the buffer and emits all values in the buffer upon flush
|
9
|
-
# assuming the given values are in gauge-style (e.g. free memory, users
|
10
|
-
# currently logged in, etc).
|
11
|
-
#
|
12
|
-
class Gauge < DataSource
|
13
|
-
def flush
|
14
|
-
each do |ns, origin, ts|
|
15
|
-
yield ns, origin, ts.values.flatten
|
16
|
-
end
|
17
|
-
end
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|