ganymed 0.1.1 → 0.1.2

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 CHANGED
@@ -14,6 +14,7 @@ lib/bundler/man
14
14
  node_modules/
15
15
  old/
16
16
  pkg
17
+ prof/
17
18
  rdoc
18
19
  spec/reports
19
20
  test.js
@@ -23,6 +23,7 @@ Gem::Specification.new do |s|
23
23
  s.add_dependency "madvertise-logging", ">= 0.3.2"
24
24
  s.add_dependency "mixlib-cli"
25
25
  s.add_dependency "servolux"
26
+ s.add_dependency "RubyInline"
26
27
 
27
28
  # processor
28
29
  s.add_dependency "msgpack"
@@ -65,7 +65,7 @@ module Ganymed
65
65
  # @param [Fixnum, Float] value Sample value.
66
66
  # @param [Time] now Sample timestamp.
67
67
  def emit(ds, ns, value, now=Time.now.utc)
68
- data = [ds.to_s, ns, @origin, now.to_i + (now.usec * 1e-6), value.to_f]
68
+ data = [ds.to_s, ns, @origin, now.to_f, value.to_f]
69
69
  send(data.pack("Z*Z*Z*GG"))
70
70
  end
71
71
  end
@@ -82,23 +82,23 @@ module Ganymed
82
82
  # @param [Hash] opts Options
83
83
  # @option opts [String] cf Consolidation function used in this event.
84
84
  # @option opts [Time] now {Event} timestamp.
85
+ # @option opts [Fixnum] resolution {Event} resolution.
85
86
  # @option opts [String] origin {Event} origin.
86
- # @option opts [Array] modifiers Event modifiers.
87
87
  def event(ns, value, opts={})
88
88
  opts = {
89
89
  :cf => nil,
90
90
  :now => Time.now.utc,
91
+ :resolution => 0,
91
92
  :origin => @origin,
92
- :modifiers => [],
93
93
  }.merge(opts)
94
94
 
95
95
  {
96
96
  :_type => :event,
97
97
  :n => ns,
98
- :m => opts[:modifiers],
99
98
  :c => opts[:cf],
100
99
  :o => opts[:origin],
101
100
  :t => opts[:now].to_i,
101
+ :r => opts[:resolution].to_i,
102
102
  :v => value
103
103
  }.tap do |data|
104
104
  send(data.to_msgpack)
@@ -21,8 +21,8 @@ module Ganymed
21
21
  # @param [Configuration] config The configuration object.
22
22
  def initialize(config)
23
23
  @config = config
24
- @client = Client.new(:processor => @config.collector.processor,
25
- :sampler => @config.collector.sampler)
24
+ @client = Client.new(:processor => @config.client.processor,
25
+ :sampler => @config.client.sampler)
26
26
 
27
27
  load_collectors
28
28
  @config.collectors.each do |collector|
@@ -24,15 +24,12 @@ generic:
24
24
 
25
25
  sampler:
26
26
  enabled: true
27
+ ticks: [1]
27
28
  listen:
28
29
  host: 127.0.0.1
29
30
  port: 1337
30
- emit:
31
- host: 127.0.0.1
32
- port: 1336
33
31
 
34
- collector:
35
- enabled: true
32
+ client:
36
33
  processor:
37
34
  host: 127.0.0.1
38
35
  port: 1336
@@ -4,11 +4,11 @@ module Ganymed
4
4
  # An incoming event is a hash-like object and contains the following keys:
5
5
  #
6
6
  # n:: namespace (e.g. +os.cpu.idle+, +nginx.requests+, etc)
7
- # m:: event modifiers (e.g. +realtime+)
8
7
  # c:: a consolidation function used to build this event from multiple
9
8
  # samples
10
9
  # o:: origin (e.g. +web1.example.com+, +myapplication+)
11
10
  # t:: timestamp as seconds since epoch
11
+ # r:: resolution of this event (i.e. how often is this event sent to the processor)
12
12
  # v:: the value in integer or floating-point representation
13
13
  #
14
14
  class Event
@@ -16,10 +16,6 @@ module Ganymed
16
16
  # @return [String]
17
17
  attr_accessor :ns
18
18
 
19
- # The event modifiers.
20
- # @return [Array]
21
- attr_accessor :modifiers
22
-
23
19
  # The consolidation function used to build this event from multiple
24
20
  # samples.
25
21
  # @return [String]
@@ -33,6 +29,10 @@ module Ganymed
33
29
  # @return [Fixnum]
34
30
  attr_accessor :timestamp
35
31
 
32
+ # The event resolution
33
+ # @return [Fixnum]
34
+ attr_accessor :resolution
35
+
36
36
  # The actual value of this sample.
37
37
  # @return [Object]
38
38
  attr_accessor :value
@@ -43,10 +43,10 @@ module Ganymed
43
43
  def self.parse(obj)
44
44
  new.tap do |event|
45
45
  event.ns = obj['n']
46
- event.modifiers = obj['m']
47
46
  event.cf = obj['c']
48
47
  event.origin = obj['o']
49
48
  event.timestamp = obj['t'].to_i
49
+ event.resolution = obj['r'].to_i
50
50
 
51
51
  value = obj['v']
52
52
  if value.is_a?(Float) and (value.nan? or value.infinite?)
@@ -73,20 +73,13 @@ module Ganymed
73
73
  }
74
74
  end
75
75
 
76
- # Check if this event is a realtime event.
77
- #
78
- # @return [TrueClass,FalseClass]
79
- def realtime?
80
- @modifiers.include?("realtime")
81
- end
82
-
83
76
  # Validate this event.
84
77
  def validate
85
78
  unless @ns =~ /^[a-z][a-z0-9_\.-]+[a-z0-9]$/
86
79
  raise "invalid namespace: #{@ns.inspect}"
87
80
  end
88
81
 
89
- if @ns =~ /^(system|ganymed)\./
82
+ if @ns =~ /^(system\.|metadata)/
90
83
  raise "namespace is reserved: #{@ns.inspect}"
91
84
  end
92
85
 
@@ -20,6 +20,9 @@ module Ganymed
20
20
  @default_config_file = File.join(LIB_DIR, 'ganymed/config.yml')
21
21
  @config_file = cli[:config_file]
22
22
 
23
+ # store fqdn for later use
24
+ config.fqdn = ::Socket.gethostbyname(::Socket.gethostname).first
25
+
23
26
  # initialize servolux
24
27
  super('ganymed',
25
28
  :interval => 1,
@@ -53,7 +56,7 @@ module Ganymed
53
56
  def setup_reactor
54
57
  Processor.new(config) if config.processor.enabled
55
58
  Sampler.new(config) if config.sampler.enabled
56
- Collector.new(config) if config.collector.enabled
59
+ Collector.new(config) if config.collectors.any?
57
60
  end
58
61
 
59
62
  def before_starting
@@ -11,6 +11,7 @@ module Ganymed
11
11
  @config = config
12
12
  @db = MongoDB.new(config.processor.mongodb)
13
13
  @websocket = Websocket.new(config.processor.websocket, @db)
14
+ @client = Client.new(:sampler => @config.client.sampler)
14
15
  listen!
15
16
  end
16
17
 
@@ -18,75 +19,81 @@ module Ganymed
18
19
  @config.processor.listen.tap do |listen|
19
20
  log.info("processing metrics on udp##{listen.host}:#{listen.port}")
20
21
  EM.open_datagram_socket(listen.host, listen.port, Connection) do |connection|
21
- connection.db = @db
22
- connection.websocket = @websocket
22
+ connection.processor = self
23
23
  end
24
24
  end
25
25
  end
26
26
 
27
- module Connection
28
- attr_accessor :db, :websocket
29
-
30
- def receive_data(data)
31
- EM.defer do
32
- begin
33
- data = MessagePack.unpack(data)
34
- case data['_type'].to_sym
35
- when :event
36
- process_event(data)
37
- when :metadata
38
- process_metadata(data)
39
- end
40
- rescue Exception => exc
41
- log.exception(exc)
42
- end
43
- end
27
+ def process(data)
28
+ data = MessagePack.unpack(data)
29
+ case data['_type'].to_sym
30
+ when :event
31
+ process_event(Event.parse(data))
32
+ @client.sampler.emit(:counter, 'ganymed.processor.events', 1)
33
+ when :metadata
34
+ process_metadata(data)
44
35
  end
36
+ end
45
37
 
46
- def process_event(data)
47
- event = Event.parse(data)
38
+ def process_event(event)
39
+ # insert origin + namespace into metadata
40
+ metadata = @db['metadata'].find_and_modify({
41
+ query: {:_id => event.origin},
42
+ update: {:$addToSet => {:namespaces => event.ns}},
43
+ upsert: true,
44
+ })
48
45
 
49
- # insert origin + namespace into metadata
50
- metadata = db['metadata'].find_and_modify({
51
- query: {:_id => event.origin},
52
- update: {:$addToSet => {:namespaces => event.ns}},
53
- upsert: true,
54
- })
46
+ # set defaults in case find_and_modify returned an empty document
47
+ metadata['_id'] ||= event.origin
48
+ metadata['namespaces'] ||= []
55
49
 
56
- # set defaults in case find_and_modify returned an empty document
57
- metadata['_id'] ||= event.origin
58
- metadata['namespaces'] ||= []
50
+ # if metadata has changed notify websocket clients
51
+ if not metadata['namespaces'].include?(event.ns)
52
+ metadata['namespaces'] |= [event.ns]
53
+ @websocket.send(:metadata, [metadata])
54
+ end
59
55
 
60
- # if metadata has changed notify websocket clients
61
- if not metadata['namespaces'].include?(event.ns)
62
- metadata['namespaces'] |= [event.ns]
63
- websocket.send(:metadata, [metadata])
64
- end
56
+ # send the event to websocket clients
57
+ @websocket.each do |connection|
58
+ connection.publish(event)
59
+ end
65
60
 
66
- # send the event to websocket clients
67
- websocket.each do |connection|
68
- connection.publish(event)
69
- end
61
+ # skip events that are produced below the mongodb storage threshold
62
+ return if (1..@config.resolution).include?(event.resolution)
70
63
 
71
- # do not store realtime events into mongodb
72
- return if event.realtime?
64
+ # insert event into ns collection
65
+ @db.collection(event.ns).update({
66
+ :c => event.cf,
67
+ :o => event.origin,
68
+ :t => event.timestamp
69
+ }, {
70
+ :$set => {:v => event.value}
71
+ }, :upsert => true)
72
+ end
73
73
 
74
- # insert event into ns collection
75
- db.collection(event.ns).update({
76
- :c => event.cf,
77
- :o => event.origin,
78
- :t => event.timestamp
79
- }, {
80
- :$set => {:v => event.value}
81
- }, :upsert => true)
82
- end
74
+ def process_metadata(data)
75
+ metadata = @db['metadata'].find_and_modify({
76
+ query: {:_id => data['origin']},
77
+ update: {:$set => {:data => data['data']}},
78
+ upsert: true,
79
+ new: true
80
+ })
81
+
82
+ @websocket.send(:metadata, [metadata])
83
+ end
83
84
 
84
- def process_metadata(data)
85
- db['metadata'].update({
86
- :_id => data['origin'],
87
- }, {
88
- :$set => {:data => data['data']}
89
- }, :upsert => true)
85
+ # @private
86
+ module Connection
87
+ attr_accessor :processor
88
+
89
+ def receive_data(data)
90
+ EM.defer do
91
+ begin
92
+ processor.process(data)
93
+ rescue Exception => exc
94
+ log.exception(exc)
95
+ end
96
+ end
90
97
  end
91
98
  end
92
99
  end
@@ -3,6 +3,7 @@ require 'eventmachine'
3
3
  require 'madvertise/ext/enumerable'
4
4
  require 'msgpack'
5
5
  require 'socket'
6
+ require 'inline'
6
7
 
7
8
  require 'ganymed'
8
9
 
@@ -16,7 +17,8 @@ module Ganymed
16
17
  class Sampler
17
18
  def initialize(config)
18
19
  @config = config
19
- @ticks = [1, config.resolution]
20
+ @config.sampler.ticks |= [@config.resolution]
21
+ @fqdn = @config.fqdn
20
22
  listen! and emit! and tick!
21
23
  end
22
24
 
@@ -24,21 +26,22 @@ module Ganymed
24
26
  @config.sampler.listen.tap do |listen|
25
27
  log.info("processing samples on udp##{listen.host}:#{listen.port}")
26
28
  EM.open_datagram_socket(listen.host, listen.port, Connection) do |connection|
29
+ connection.fqdn = @fqdn
27
30
  connection.datasources = datasources
28
31
  end
29
32
  end
30
33
  end
31
34
 
32
35
  def emit!
33
- @config.sampler.emit.tap do |emit|
34
- log.info("emitting consolidated samples to udp##{emit.host}:#{emit.port}")
35
- @client = Client.new(:processor => emit)
36
+ @config.client.processor.tap do |processor|
37
+ log.info("emitting consolidated samples to udp##{processor.host}:#{processor.port}")
38
+ @client = Client.new(:processor => processor)
36
39
  @processor = @client.processor
37
40
  end
38
41
  end
39
42
 
40
43
  def tick!
41
- @ticks.each do |tick|
44
+ @config.sampler.ticks.each do |tick|
42
45
  EM.add_periodic_timer(tick) { flush(tick) }
43
46
  end
44
47
  end
@@ -46,16 +49,15 @@ module Ganymed
46
49
  def datasources
47
50
  @datasources ||= Hash.new do |hsh, key|
48
51
  klass = "Ganymed::Sampler::#{key.classify}".constantize
49
- hsh[key] = klass.new(@ticks)
52
+ hsh[key] = klass.new(@config.sampler.ticks)
50
53
  end
51
54
  end
52
55
 
53
56
  def flush(tick)
54
- modifiers = tick == 1 ? ['realtime'] : []
55
57
  datasources.each do |_, datasource|
56
58
  EM.defer do
57
59
  datasource.flush(tick) do |ns, origin, values|
58
- emit(ns, modifiers, origin, values)
60
+ emit(ns, origin, tick, values)
59
61
  end
60
62
  end
61
63
  end
@@ -70,13 +72,15 @@ module Ganymed
70
72
  :u90 => [:percentile, 0.9],
71
73
  }
72
74
 
73
- def emit(ns, modifiers, origin, values)
75
+ def emit(ns, origin, tick, values)
74
76
  CFS.each do |cf, args|
77
+ # do not calculate the consolidations below threshold
78
+ next if tick < @config.resolution and cf != :avg
75
79
  begin
76
80
  @processor.event(ns, values.send(*args), {
77
81
  :cf => cf,
78
82
  :origin => origin,
79
- :modifiers => modifiers,
83
+ :resolution => tick,
80
84
  })
81
85
  rescue Exception => exc
82
86
  log.warn("failed to emit #{ns}@#{origin}")
@@ -87,14 +91,26 @@ module Ganymed
87
91
 
88
92
  # @private
89
93
  module Connection
90
- attr_accessor :datasources
94
+ attr_accessor :datasources, :fqdn
95
+
96
+ # Time.now.utc.to_f is way too expensive
97
+ inline :C do |builder|
98
+ builder.include "<sys/time.h>"
99
+ builder.c <<-EOC
100
+ double timestamp(void) {
101
+ struct timeval tv;
102
+ gettimeofday(&tv, NULL);
103
+ return ((double)tv.tv_sec + ((double)tv.tv_usec / 10000000.0));
104
+ }
105
+ EOC
106
+ end
91
107
 
92
108
  def receive_data(data)
93
109
  begin
94
110
  # pack format: <datasource><namespace><origin><timestamp><value>
95
111
  data = data.unpack("Z*Z*Z*GG")
96
- datasource = datasources[data.slice!(0)]
97
- datasource.feed(*data)
112
+ datasources[data.slice!(0)].feed(*data)
113
+ datasources['counter'].feed('ganymed.sampler.samples', @fqdn, timestamp, 1)
98
114
  rescue Exception => exc
99
115
  log.exception(exc)
100
116
  end
@@ -1,3 +1,3 @@
1
1
  module Ganymed
2
- VERSION = '0.1.1'
2
+ VERSION = '0.1.2'
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ganymed
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.2
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-05-23 00:00:00.000000000 Z
12
+ date: 2012-05-25 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activesupport
16
- requirement: &11641160 !ruby/object:Gem::Requirement
16
+ requirement: &12783700 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: '3.2'
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *11641160
24
+ version_requirements: *12783700
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: eventmachine
27
- requirement: &11637760 !ruby/object:Gem::Requirement
27
+ requirement: &12781700 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ! '>='
@@ -32,10 +32,10 @@ dependencies:
32
32
  version: 0.12.10
33
33
  type: :runtime
34
34
  prerelease: false
35
- version_requirements: *11637760
35
+ version_requirements: *12781700
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: madvertise-ext
38
- requirement: &11636500 !ruby/object:Gem::Requirement
38
+ requirement: &12780400 !ruby/object:Gem::Requirement
39
39
  none: false
40
40
  requirements:
41
41
  - - ! '>='
@@ -43,10 +43,10 @@ dependencies:
43
43
  version: 0.1.2
44
44
  type: :runtime
45
45
  prerelease: false
46
- version_requirements: *11636500
46
+ version_requirements: *12780400
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: madvertise-logging
49
- requirement: &11634080 !ruby/object:Gem::Requirement
49
+ requirement: &12806280 !ruby/object:Gem::Requirement
50
50
  none: false
51
51
  requirements:
52
52
  - - ! '>='
@@ -54,10 +54,10 @@ dependencies:
54
54
  version: 0.3.2
55
55
  type: :runtime
56
56
  prerelease: false
57
- version_requirements: *11634080
57
+ version_requirements: *12806280
58
58
  - !ruby/object:Gem::Dependency
59
59
  name: mixlib-cli
60
- requirement: &11650400 !ruby/object:Gem::Requirement
60
+ requirement: &12805060 !ruby/object:Gem::Requirement
61
61
  none: false
62
62
  requirements:
63
63
  - - ! '>='
@@ -65,10 +65,10 @@ dependencies:
65
65
  version: '0'
66
66
  type: :runtime
67
67
  prerelease: false
68
- version_requirements: *11650400
68
+ version_requirements: *12805060
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: servolux
71
- requirement: &11648860 !ruby/object:Gem::Requirement
71
+ requirement: &12803060 !ruby/object:Gem::Requirement
72
72
  none: false
73
73
  requirements:
74
74
  - - ! '>='
@@ -76,10 +76,21 @@ dependencies:
76
76
  version: '0'
77
77
  type: :runtime
78
78
  prerelease: false
79
- version_requirements: *11648860
79
+ version_requirements: *12803060
80
+ - !ruby/object:Gem::Dependency
81
+ name: RubyInline
82
+ requirement: &12800700 !ruby/object:Gem::Requirement
83
+ none: false
84
+ requirements:
85
+ - - ! '>='
86
+ - !ruby/object:Gem::Version
87
+ version: '0'
88
+ type: :runtime
89
+ prerelease: false
90
+ version_requirements: *12800700
80
91
  - !ruby/object:Gem::Dependency
81
92
  name: msgpack
82
- requirement: &11647780 !ruby/object:Gem::Requirement
93
+ requirement: &12815620 !ruby/object:Gem::Requirement
83
94
  none: false
84
95
  requirements:
85
96
  - - ! '>='
@@ -87,10 +98,10 @@ dependencies:
87
98
  version: '0'
88
99
  type: :runtime
89
100
  prerelease: false
90
- version_requirements: *11647780
101
+ version_requirements: *12815620
91
102
  - !ruby/object:Gem::Dependency
92
103
  name: mongo
93
- requirement: &11647060 !ruby/object:Gem::Requirement
104
+ requirement: &12812240 !ruby/object:Gem::Requirement
94
105
  none: false
95
106
  requirements:
96
107
  - - ! '>='
@@ -98,10 +109,10 @@ dependencies:
98
109
  version: '1.6'
99
110
  type: :runtime
100
111
  prerelease: false
101
- version_requirements: *11647060
112
+ version_requirements: *12812240
102
113
  - !ruby/object:Gem::Dependency
103
114
  name: em-websocket
104
- requirement: &11645980 !ruby/object:Gem::Requirement
115
+ requirement: &12810000 !ruby/object:Gem::Requirement
105
116
  none: false
106
117
  requirements:
107
118
  - - ! '>='
@@ -109,10 +120,10 @@ dependencies:
109
120
  version: '0'
110
121
  type: :runtime
111
122
  prerelease: false
112
- version_requirements: *11645980
123
+ version_requirements: *12810000
113
124
  - !ruby/object:Gem::Dependency
114
125
  name: yajl-ruby
115
- requirement: &11644260 !ruby/object:Gem::Requirement
126
+ requirement: &12807700 !ruby/object:Gem::Requirement
116
127
  none: false
117
128
  requirements:
118
129
  - - ! '>='
@@ -120,10 +131,10 @@ dependencies:
120
131
  version: '0'
121
132
  type: :runtime
122
133
  prerelease: false
123
- version_requirements: *11644260
134
+ version_requirements: *12807700
124
135
  - !ruby/object:Gem::Dependency
125
136
  name: sys-filesystem
126
- requirement: &11660200 !ruby/object:Gem::Requirement
137
+ requirement: &12823980 !ruby/object:Gem::Requirement
127
138
  none: false
128
139
  requirements:
129
140
  - - ! '>='
@@ -131,10 +142,10 @@ dependencies:
131
142
  version: '0'
132
143
  type: :runtime
133
144
  prerelease: false
134
- version_requirements: *11660200
145
+ version_requirements: *12823980
135
146
  - !ruby/object:Gem::Dependency
136
147
  name: ohai
137
- requirement: &11658260 !ruby/object:Gem::Requirement
148
+ requirement: &12822240 !ruby/object:Gem::Requirement
138
149
  none: false
139
150
  requirements:
140
151
  - - ! '>='
@@ -142,7 +153,7 @@ dependencies:
142
153
  version: 0.6.12
143
154
  type: :runtime
144
155
  prerelease: false
145
- version_requirements: *11658260
156
+ version_requirements: *12822240
146
157
  description: Ganymed is an event collection daemon
147
158
  email:
148
159
  - bb@xnull.de
@@ -217,7 +228,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
217
228
  version: '0'
218
229
  segments:
219
230
  - 0
220
- hash: 3485584446673225312
231
+ hash: -813213877420202571
221
232
  required_rubygems_version: !ruby/object:Gem::Requirement
222
233
  none: false
223
234
  requirements:
@@ -226,7 +237,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
226
237
  version: '0'
227
238
  segments:
228
239
  - 0
229
- hash: 3485584446673225312
240
+ hash: -813213877420202571
230
241
  requirements: []
231
242
  rubyforge_project:
232
243
  rubygems_version: 1.8.17