ganymed 0.1.2 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -6,6 +6,7 @@
6
6
  Gemfile.lock
7
7
  InstalledFiles
8
8
  _yardoc
9
+ config.yml
9
10
  coverage/
10
11
  doc/
11
12
  ganymed.pid
@@ -15,6 +16,7 @@ node_modules/
15
16
  old/
16
17
  pkg
17
18
  prof/
19
+ profile*
18
20
  rdoc
19
21
  spec/reports
20
22
  test.js
data/README.md CHANGED
@@ -2,18 +2,19 @@
2
2
 
3
3
  Ganymed is an event collection daemon.
4
4
 
5
- Ganymed processes system and application metrics in realtime and can easily
6
- handle thousands of samples per second.
5
+ Ganymed processes system and application metrics and events in realtime and can
6
+ easily handle thousands of samples per second. Ganymed provides these services
7
+ to administrators and third-party applications:
7
8
 
8
- * the *sampler* accepts high frequency samples. these samples are then
9
- consolidated based on the data source type and emitted to the *processor*.
9
+ * The *Sampler* accepts high frequency samples. these samples are then
10
+ consolidated based on the data source type and emitted to the *Processor*.
10
11
 
11
- * the *processor* accepts low frequency data points (possibly created by the
12
- *sampler*) and publishes these to interested parties via WebSocket in
13
- realtime. All samples are also stored in MongoDB for later analysis.
12
+ * The *Processor* accepts metrics (events) and stores these metrics into
13
+ MongoDB. All metrics are also published to interested parties via WebSocket.
14
14
 
15
- * the *collector* will send various system metrics (cpu, memory, network, etc)
16
- to the *sampler* and *processor*
15
+ * The *Collector* is a simple thread that polls various system-level metrics
16
+ that are not pushed to the *Sampler*/*Processor* from third-party
17
+ applications.
17
18
 
18
19
  ## Installation
19
20
 
@@ -19,11 +19,12 @@ Gem::Specification.new do |s|
19
19
  # master
20
20
  s.add_dependency "activesupport", ">= 3.2"
21
21
  s.add_dependency "eventmachine", ">= 0.12.10"
22
+ s.add_dependency "ganymed-client"
22
23
  s.add_dependency "madvertise-ext", ">= 0.1.2"
23
24
  s.add_dependency "madvertise-logging", ">= 0.3.2"
24
25
  s.add_dependency "mixlib-cli"
25
- s.add_dependency "servolux"
26
26
  s.add_dependency "RubyInline"
27
+ s.add_dependency "servolux"
27
28
 
28
29
  # processor
29
30
  s.add_dependency "msgpack"
@@ -1,4 +1,12 @@
1
+ ##
2
+ # Ganymed is an event collection daemon.
3
+ #
4
+ # For more information about Ganymed go to the {file:README.md README}
5
+ #
1
6
  module Ganymed
7
+ # @private
2
8
  GEM_ROOT = File.expand_path("../..", __FILE__).freeze
9
+
10
+ # @private
3
11
  LIB_DIR = File.join(GEM_ROOT, "lib").freeze
4
12
  end
@@ -6,96 +6,155 @@ require 'ganymed/client'
6
6
  module Ganymed
7
7
 
8
8
  ##
9
- # The Collector polls various system-level metrics that are not pushed to the
10
- # {Processor} automatically.
9
+ # The Collector is a simple thread that polls various system-level metrics
10
+ # that are not pushed to the {Sampler}/{Processor} from third-party
11
+ # applications.
12
+ #
13
+ # The Collector features a very simple DSL to implement custom collector
14
+ # plugins. Simply create a file with a +.rb+ file extension and a +collect+
15
+ # block:
16
+ #
17
+ # collect do
18
+ # File.open('/proc/foobar').each do |line|
19
+ # parse(line)
20
+ # end
21
+ # end
22
+ #
23
+ # Copy this plugin to a location of your choice and set
24
+ # +collector.load_paths+ in your configuration to point to the containing
25
+ # directory.
26
+ #
27
+ # The collector thread will then call this plugin once per resolution tick.
28
+ # See {file:doc/configuration.md} for details.
29
+ #
30
+ # Custom intervals can also be specified for high frequency sampling:
31
+ #
32
+ # collect(0.3) do
33
+ # File.open('/proc/foobar').each do |line|
34
+ # parse(line)
35
+ # end
36
+ # end
11
37
  #
12
38
  class Collector
13
39
  # The configuration object.
40
+ # @return [Section]
14
41
  attr_reader :config
15
42
 
16
- # The {Client} object.
43
+ # The client object.
44
+ # @return [Ganymed::Client]
17
45
  attr_reader :client
18
46
 
19
47
  # Create a new collector instance and initialize all configured collectors.
20
48
  #
21
- # @param [Configuration] config The configuration object.
49
+ # @param [Section] config The configuration object.
22
50
  def initialize(config)
23
51
  @config = config
24
- @client = Client.new(:processor => @config.client.processor,
25
- :sampler => @config.client.sampler)
26
-
52
+ @client = Client.new(@config.client)
27
53
  load_collectors
28
- @config.collectors.each do |collector|
29
- log.info("initializing collector #{collector.klass.demodulize}")
30
- collector.klass.constantize.new(collector, self).run
31
- end
32
54
  end
33
55
 
56
+ # @private
34
57
  def load_collectors
35
- # collectors shipped with ganymed
36
- Dir[File.join(Ganymed::LIB_DIR, 'ganymed/collector/*.rb')].each do |f|
37
- log.debug("Loading collector from #{f}")
38
- require f
39
- end
58
+ collectors.each do |file|
59
+ name = File.basename(file, '.rb')
60
+ config = @config.collectors[name.to_sym] || Section.new
61
+ config[:resolution] = @config.resolution
40
62
 
41
- # custom collectors
42
- if @config.collector.path.tap{}
43
- Dir[File.join(@config.collector.path, '*.rb')].each do |f|
44
- log.debug("Loading collector from #{f}")
45
- require f
63
+ log.debug("loading collector #{name} from #{file}")
64
+ Plugin.new(config, @client).from_file(file).tap do |collector|
65
+ log.info("collecting #{name} metrics every #{collector.interval} seconds")
66
+ collector.run
46
67
  end
47
68
  end
48
69
  end
49
70
 
71
+ # @private
72
+ def collectors
73
+ load_paths = [@config.collector.load_paths.tap{}].flatten.compact
74
+ load_paths << File.join(Ganymed::LIB_DIR, 'ganymed/collectors')
75
+
76
+ [].tap do |files|
77
+ load_paths.each do |load_path|
78
+ log.debug("loading collectors from #{load_path}")
79
+ Dir[File.join(load_path, '*.rb')].each do |file|
80
+ files << file
81
+ end
82
+ end
83
+ end.sort.uniq
84
+ end
85
+
50
86
  ##
51
- # A base class for collectors.
87
+ # Base class and DSL for {Collector} plugins.
52
88
  #
53
- class Base
89
+ class Plugin
90
+
91
+ # The configuration object.
92
+ # @return [Section]
93
+ attr_accessor :config
94
+
95
+ # Plugin interval
96
+ # @return [Fixnum,Float]
97
+ attr_accessor :interval
54
98
 
55
- # Create a new data collector instance.
99
+ # Processor socket.
100
+ # @return [Ganymed::Client::ProcessorSocket]
101
+ attr_accessor :processor
102
+
103
+ # Sampler socket.
104
+ # @return [Ganymed::Client::SamplerSocket]
105
+ attr_accessor :sampler
106
+
107
+ # Create a new plugin instance.
108
+ #
109
+ # @param [Section] config The configuration object.
110
+ # @param [Ganymed::Client] client A client instance.
111
+ def initialize(config, client)
112
+ @config, @client = config, client
113
+ @processor = @client.processor
114
+ @sampler = @client.sampler
115
+ end
116
+
117
+ # Set the block used to collect metrics with this plugin.
56
118
  #
57
- # @param [Configuration] config The configuration object.
58
- # @param [Collector] collector The collector instance.
59
- def initialize(config, collector)
60
- @config, @collector = config, collector
61
- @processor = @collector.client.processor
62
- @sampler = @collector.client.sampler
119
+ # @param [Fixnum,Float] interval Custom plugin interval.
120
+ # @return [void]
121
+ def collect(interval=nil, &block)
122
+ @interval ||= interval
123
+ @interval ||= @config.interval.tap{}
124
+ @interval ||= @config.resolution
125
+ @collector = Proc.new(&block)
63
126
  end
64
127
 
65
128
  # Start the EventMachine timer to collect metrics at the specified
66
129
  # interval.
130
+ # @return [void]
67
131
  def run
68
- timer do
69
- begin
70
- collect!
71
- rescue Exception => e
72
- log.exception(e)
73
- end
132
+ EM.add_periodic_timer(@interval) do
133
+ EM.defer { collect! }
74
134
  end
75
135
  end
76
136
 
77
137
  # @private
78
- def timer
79
- if interval
80
- EM.add_periodic_timer(interval) do
81
- EM.defer { yield }
82
- end
83
- else
84
- yield
138
+ def collect!
139
+ begin
140
+ @collector.call if @collector.is_a?(Proc)
141
+ rescue Exception => exc
142
+ log.exception(exc)
85
143
  end
86
144
  end
87
145
 
88
- # The interval used to collect metrics. Either taken from the
89
- # configuration file or overriden by subclasses.
90
- def interval
91
- @config.interval.tap{} or @collector.config.resolution
92
- end
93
-
94
- # This method must be implemented by subclasses to collect the desired
95
- # metrics. This function is called every #interval seconds after the
96
- # timer has been initialized by the #run method.
97
- def collect!
98
- raise NotImplementedError
146
+ # Loads a given ruby file, and runs instance_eval against it in the
147
+ # context of the current object.
148
+ #
149
+ # @param [String] filename The absolute path to a plugin file.
150
+ # @return [Plugin] The instance for easy method chaining.
151
+ def from_file(filename)
152
+ if File.exists?(filename) && File.readable?(filename)
153
+ self.instance_eval(IO.read(filename), filename, 1)
154
+ else
155
+ raise IOError, "Cannot open or read #{filename}!"
156
+ end
157
+ self
99
158
  end
100
159
  end
101
160
  end
@@ -0,0 +1,19 @@
1
+ collect(0.3) do
2
+ return if not File.readable?('/proc/stat')
3
+
4
+ File.open('/proc/stat').each do |line|
5
+ next if not line =~ /^cpu /
6
+
7
+ cpu = line.chomp.split[1,7].map do |x|
8
+ x.to_i / 100
9
+ end
10
+
11
+ sampler.emit(:derive, "os.cpu.user", cpu[0])
12
+ sampler.emit(:derive, "os.cpu.nice", cpu[1])
13
+ sampler.emit(:derive, "os.cpu.system", cpu[2])
14
+ sampler.emit(:derive, "os.cpu.idle", cpu[3])
15
+ sampler.emit(:derive, "os.cpu.iowait", cpu[4])
16
+ sampler.emit(:derive, "os.cpu.irq", cpu[5])
17
+ sampler.emit(:derive, "os.cpu.softirq", cpu[6])
18
+ end
19
+ end
@@ -0,0 +1,25 @@
1
+ require 'sys/filesystem'
2
+
3
+ collect do
4
+ Sys::Filesystem.mounts do |mount|
5
+ st = Sys::Filesystem.stat(mount.mount_point)
6
+ next if st.blocks == 0 or st.files == 0
7
+
8
+ case mp = mount.mount_point
9
+ when '/'
10
+ 'rootfs'
11
+ when /^\/.+$/
12
+ mp[1..-1].tr('^[a-z][0-9]', '-')
13
+ else
14
+ 'unknown'
15
+ end.tap do |name|
16
+ next if config.exclude.map {|e| Regexp.new(e).match(name)}.any?
17
+
18
+ block_pc = 1.0 - (st.blocks_free.to_f / st.blocks.to_f)
19
+ processor.event("os.disk.#{name}.blocks", block_pc)
20
+
21
+ files_pc = 1.0 - (st.files_free.to_f / st.files.to_f)
22
+ processor.event("os.disk.#{name}.files", block_pc)
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,16 @@
1
+ Struct.new("IOStat",
2
+ :major, :minor, :dev,
3
+ :rio, :rmerge, :rsect, :ruse,
4
+ :wio, :wmerge, :wsect, :wuse,
5
+ :running, :use, :aveq)
6
+
7
+ collect(0.3) do
8
+ return if not File.readable?('/proc/diskstats')
9
+ File.open('/proc/diskstats').each do |line|
10
+ ios = Struct::IOStat.new(*line.strip.split(/\s+/))
11
+ next if config.skip_numbered.tap{} and ios.dev =~ /\d+$/
12
+ next if config.exclude.map {|e| Regexp.new(e).match(ios.dev)}.any?
13
+ sampler.emit(:derive, "os.iostat.#{ios.dev}.rsect", ios.rsect)
14
+ sampler.emit(:derive, "os.iostat.#{ios.dev}.wsect", ios.wsect)
15
+ end
16
+ end
@@ -0,0 +1,7 @@
1
+ collect(5) do
2
+ return if not File.readable?('/proc/loadavg')
3
+ File.open('/proc/loadavg') do |f|
4
+ loadavg = f.read.chomp.split[0,3].map(&:to_f)
5
+ sampler.emit(:gauge, "os.loadavg", loadavg[0])
6
+ end
7
+ end
@@ -0,0 +1,39 @@
1
+ collect(5) do
2
+ return if not File.readable?('/proc/meminfo')
3
+
4
+ # calculate app memory from total
5
+ apps = 0
6
+
7
+ File.open('/proc/meminfo').each do |line|
8
+ key, value, unit = line.chomp.split
9
+ key.gsub!(/:/, '')
10
+ value = value.to_i * 1024
11
+
12
+ case key
13
+ when "MemTotal"
14
+ apps = value
15
+ when "MemFree"
16
+ sampler.emit(:gauge, "os.mem.unused", value)
17
+ apps -= value
18
+ when "Buffers"
19
+ sampler.emit(:gauge, "os.mem.buffers", value)
20
+ apps -= value
21
+ when "Cached"
22
+ sampler.emit(:gauge, "os.mem.cache", value)
23
+ apps -= value
24
+ when "SwapCached"
25
+ sampler.emit(:gauge, "os.mem.swap_cache", value)
26
+ apps -= value
27
+ when "Slab"
28
+ sampler.emit(:gauge, "os.mem.slab_cache", value)
29
+ apps -= value
30
+ when "PageTables"
31
+ sampler.emit(:gauge, "os.mem.page_tables", value)
32
+ apps -= value
33
+ when "Mapped"
34
+ sampler.emit(:gauge, "os.mem.mapped", value)
35
+ end
36
+ end
37
+
38
+ sampler.emit(:gauge, "os.mem.apps", apps)
39
+ end
@@ -0,0 +1,22 @@
1
+ require 'ohai'
2
+
3
+ collect do
4
+ Ohai::System.new.tap do |ohai|
5
+ %w(
6
+ os
7
+ kernel
8
+ hostname
9
+ keys
10
+ network
11
+ platform
12
+ uptime
13
+ virtualization
14
+ ).each do |plugin|
15
+ ohai.require_plugin(plugin)
16
+ end
17
+ processor.metadata(ohai.data)
18
+
19
+ # ohai doesn't cleanup after itself
20
+ true while Process.wait(-1, Process::WNOHANG) rescue nil
21
+ end
22
+ end
@@ -0,0 +1,26 @@
1
+ # bytes packets errs drop fifo frame compressed multicast|bytes packets errs drop fifo colls carrier compressed
2
+ Struct.new("NetworkStat", :dev,
3
+ :rbytes, :rpackets, :rerrs, :rdrop, :rfifo, :rframe, :rcompressed, :rmulticast,
4
+ :wbytes, :wpackets, :werrs, :wdrop, :wfifo, :wcolls, :wcarrier, :wcompressed)
5
+
6
+ collect(0.3) do
7
+ return if not File.readable?('/proc/net/dev')
8
+
9
+ File.open('/proc/net/dev') do |f|
10
+ f.each do |line|
11
+ next unless line =~ /:/
12
+
13
+ ns = Struct::NetworkStat.new(*line.strip.split(/\s+/))
14
+ ns.dev.sub!(/:$/, '')
15
+
16
+ next if config.exclude.map {|e| Regexp.new(e).match(ns.dev)}.any?
17
+
18
+ sampler.emit(:derive, "os.net.#{ns.dev}.rbytes", ns.rbytes)
19
+ sampler.emit(:derive, "os.net.#{ns.dev}.wbytes", ns.wbytes)
20
+ sampler.emit(:derive, "os.net.#{ns.dev}.rpackets", ns.rpackets)
21
+ sampler.emit(:derive, "os.net.#{ns.dev}.wpackets", ns.wpackets)
22
+ sampler.emit(:derive, "os.net.#{ns.dev}.rerrs", ns.rerrs)
23
+ sampler.emit(:derive, "os.net.#{ns.dev}.werrs", ns.werrs)
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,16 @@
1
+ collect(0.3) do
2
+ return if not File.readable?('/proc/stat')
3
+ File.open('/proc/stat').each do |line|
4
+ key, value = line.chomp.split
5
+ case key
6
+ when "ctxt"
7
+ sampler.emit(:derive, "os.procs.switch", value.to_i)
8
+ when "processes"
9
+ sampler.emit(:derive, "os.procs.forks", value.to_i)
10
+ when "procs_running"
11
+ sampler.emit(:gauge, "os.procs.running", value.to_i)
12
+ when "procs_blocked"
13
+ sampler.emit(:gauge, "os.procs.blocked", value.to_i)
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,8 @@
1
+ collect do
2
+ return if not File.readable?('/proc/stat')
3
+ File.open('/proc/stat').each do |line|
4
+ next if not line =~ /^btime /
5
+ boottime = Time.at(line.chomp.split[1].to_i)
6
+ processor.event("os.reboot", 1, :now => boottime)
7
+ end
8
+ end
@@ -38,22 +38,18 @@ generic:
38
38
  port: 1337
39
39
 
40
40
  collectors:
41
- - klass: Ganymed::Collector::Metadata
42
- - klass: Ganymed::Collector::Uptime
43
- - klass: Ganymed::Collector::Load
44
- - klass: Ganymed::Collector::CPU
45
- - klass: Ganymed::Collector::Procs
46
- - klass: Ganymed::Collector::Network
41
+ network:
47
42
  exclude:
48
43
  - ^lo$
49
44
  - ^sit
50
- - klass: Ganymed::Collector::Disk
45
+ disk:
51
46
  exclude:
52
47
  - ^dev$
53
48
  - ^dev-shm$
54
49
  - rc-init-d$
55
50
  - ^run$
56
51
  - ^sys-fs-cgroup$
57
- - klass: Ganymed::Collector::IOStat
52
+ - ^var-tmp-metro
53
+ iostat:
58
54
  skip_numbered: true
59
55
  exclude: []
@@ -5,11 +5,11 @@ module Ganymed
5
5
  #
6
6
  # n:: namespace (e.g. +os.cpu.idle+, +nginx.requests+, etc)
7
7
  # c:: a consolidation function used to build this event from multiple
8
- # samples
8
+ # samples (_optional_)
9
9
  # o:: origin (e.g. +web1.example.com+, +myapplication+)
10
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
- # v:: the value in integer or floating-point representation
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
13
  #
14
14
  class Event
15
15
  # The namespace of this event.
@@ -17,7 +17,7 @@ module Ganymed
17
17
  attr_accessor :ns
18
18
 
19
19
  # The consolidation function used to build this event from multiple
20
- # samples.
20
+ # samples if any.
21
21
  # @return [String]
22
22
  attr_accessor :cf
23
23
 
@@ -31,6 +31,9 @@ module Ganymed
31
31
 
32
32
  # The event resolution
33
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.
34
37
  attr_accessor :resolution
35
38
 
36
39
  # The actual value of this sample.
@@ -74,6 +77,8 @@ module Ganymed
74
77
  end
75
78
 
76
79
  # Validate this event.
80
+ #
81
+ # @return [void]
77
82
  def validate
78
83
  unless @ns =~ /^[a-z][a-z0-9_\.-]+[a-z0-9]$/
79
84
  raise "invalid namespace: #{@ns.inspect}"
@@ -86,6 +86,7 @@ module Ganymed
86
86
 
87
87
  if config.profiling.perftools
88
88
  PerfTools::CpuProfiler.stop
89
+ `pprof.rb --svg profile > profile.svg`
89
90
  end
90
91
 
91
92
  if config.profiling.rubyprof
@@ -7,6 +7,7 @@ module Ganymed
7
7
 
8
8
  def initialize(config)
9
9
  @config = config
10
+ @collections = {}
10
11
  log.info("using MongoDB at #{config.host}:#{config.port}/#{config.database}")
11
12
  end
12
13
 
@@ -22,7 +23,7 @@ module Ganymed
22
23
  end
23
24
 
24
25
  def collection(ns)
25
- db[ns].tap do |col|
26
+ @collections[ns] ||= db[ns].tap do |col|
26
27
  col.ensure_index([['c', ::Mongo::ASCENDING]])
27
28
  col.ensure_index([['o', ::Mongo::ASCENDING]])
28
29
  col.ensure_index([['t', ::Mongo::ASCENDING]])
@@ -1,18 +1,35 @@
1
1
  require 'eventmachine'
2
2
  require 'msgpack'
3
3
 
4
+ require 'ganymed/client'
4
5
  require 'ganymed/event'
5
6
  require 'ganymed/mongodb'
6
7
  require 'ganymed/websocket'
7
8
 
8
9
  module Ganymed
10
+
11
+ ##
12
+ # The Processor accept {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
+ #
9
16
  class Processor
10
17
  def initialize(config)
11
18
  @config = config
12
19
  @db = MongoDB.new(config.processor.mongodb)
13
20
  @websocket = Websocket.new(config.processor.websocket, @db)
14
21
  @client = Client.new(:sampler => @config.client.sampler)
15
- listen!
22
+ cache! and listen!
23
+ end
24
+
25
+ def cache!
26
+ @metadata = Hash.new do |hsh, key|
27
+ hsh[key] = []
28
+ end
29
+
30
+ @db['metadata'].find({}, fields: [:_id, :namespaces]).each do |result|
31
+ @metadata[result['_id']] = result['namespaces']
32
+ end
16
33
  end
17
34
 
18
35
  def listen!
@@ -36,20 +53,19 @@ module Ganymed
36
53
  end
37
54
 
38
55
  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
- })
56
+ if not @metadata[event.origin].include?(event.ns)
57
+ # insert origin + namespace into metadata
58
+ metadata = @db['metadata'].find_and_modify({
59
+ query: {:_id => event.origin},
60
+ update: {:$addToSet => {:namespaces => event.ns}},
61
+ upsert: true,
62
+ })
45
63
 
46
- # set defaults in case find_and_modify returned an empty document
47
- metadata['_id'] ||= event.origin
48
- metadata['namespaces'] ||= []
64
+ # set defaults in case find_and_modify returned an empty document
65
+ metadata['_id'] ||= event.origin
66
+ metadata['namespaces'] ||= [event.ns]
49
67
 
50
- # if metadata has changed notify websocket clients
51
- if not metadata['namespaces'].include?(event.ns)
52
- metadata['namespaces'] |= [event.ns]
68
+ # notify websocket clients
53
69
  @websocket.send(:metadata, [metadata])
54
70
  end
55
71
 
@@ -59,7 +75,7 @@ module Ganymed
59
75
  end
60
76
 
61
77
  # skip events that are produced below the mongodb storage threshold
62
- return if (1..@config.resolution).include?(event.resolution)
78
+ return if (1...@config.resolution).include?(event.resolution)
63
79
 
64
80
  # insert event into ns collection
65
81
  @db.collection(event.ns).update({
@@ -1,11 +1,10 @@
1
1
  require 'active_support/inflector'
2
2
  require 'eventmachine'
3
- require 'madvertise/ext/enumerable'
4
- require 'msgpack'
5
- require 'socket'
6
3
  require 'inline'
4
+ require 'madvertise/ext/enumerable'
7
5
 
8
6
  require 'ganymed'
7
+ require 'ganymed/client'
9
8
 
10
9
  module Ganymed
11
10
 
@@ -1,3 +1,4 @@
1
1
  module Ganymed
2
- VERSION = '0.1.2'
2
+ # @private
3
+ VERSION = '0.2.0'
3
4
  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.2
4
+ version: 0.2.0
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-25 00:00:00.000000000 Z
12
+ date: 2012-05-29 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activesupport
16
- requirement: &12783700 !ruby/object:Gem::Requirement
16
+ requirement: &19204520 !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: *12783700
24
+ version_requirements: *19204520
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: eventmachine
27
- requirement: &12781700 !ruby/object:Gem::Requirement
27
+ requirement: &19202420 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ! '>='
@@ -32,10 +32,21 @@ dependencies:
32
32
  version: 0.12.10
33
33
  type: :runtime
34
34
  prerelease: false
35
- version_requirements: *12781700
35
+ version_requirements: *19202420
36
+ - !ruby/object:Gem::Dependency
37
+ name: ganymed-client
38
+ requirement: &19227700 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: '0'
44
+ type: :runtime
45
+ prerelease: false
46
+ version_requirements: *19227700
36
47
  - !ruby/object:Gem::Dependency
37
48
  name: madvertise-ext
38
- requirement: &12780400 !ruby/object:Gem::Requirement
49
+ requirement: &19226300 !ruby/object:Gem::Requirement
39
50
  none: false
40
51
  requirements:
41
52
  - - ! '>='
@@ -43,10 +54,10 @@ dependencies:
43
54
  version: 0.1.2
44
55
  type: :runtime
45
56
  prerelease: false
46
- version_requirements: *12780400
57
+ version_requirements: *19226300
47
58
  - !ruby/object:Gem::Dependency
48
59
  name: madvertise-logging
49
- requirement: &12806280 !ruby/object:Gem::Requirement
60
+ requirement: &19225180 !ruby/object:Gem::Requirement
50
61
  none: false
51
62
  requirements:
52
63
  - - ! '>='
@@ -54,10 +65,10 @@ dependencies:
54
65
  version: 0.3.2
55
66
  type: :runtime
56
67
  prerelease: false
57
- version_requirements: *12806280
68
+ version_requirements: *19225180
58
69
  - !ruby/object:Gem::Dependency
59
70
  name: mixlib-cli
60
- requirement: &12805060 !ruby/object:Gem::Requirement
71
+ requirement: &19224340 !ruby/object:Gem::Requirement
61
72
  none: false
62
73
  requirements:
63
74
  - - ! '>='
@@ -65,10 +76,10 @@ dependencies:
65
76
  version: '0'
66
77
  type: :runtime
67
78
  prerelease: false
68
- version_requirements: *12805060
79
+ version_requirements: *19224340
69
80
  - !ruby/object:Gem::Dependency
70
- name: servolux
71
- requirement: &12803060 !ruby/object:Gem::Requirement
81
+ name: RubyInline
82
+ requirement: &19223140 !ruby/object:Gem::Requirement
72
83
  none: false
73
84
  requirements:
74
85
  - - ! '>='
@@ -76,10 +87,10 @@ dependencies:
76
87
  version: '0'
77
88
  type: :runtime
78
89
  prerelease: false
79
- version_requirements: *12803060
90
+ version_requirements: *19223140
80
91
  - !ruby/object:Gem::Dependency
81
- name: RubyInline
82
- requirement: &12800700 !ruby/object:Gem::Requirement
92
+ name: servolux
93
+ requirement: &19220580 !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: *12800700
101
+ version_requirements: *19220580
91
102
  - !ruby/object:Gem::Dependency
92
103
  name: msgpack
93
- requirement: &12815620 !ruby/object:Gem::Requirement
104
+ requirement: &19234260 !ruby/object:Gem::Requirement
94
105
  none: false
95
106
  requirements:
96
107
  - - ! '>='
@@ -98,10 +109,10 @@ dependencies:
98
109
  version: '0'
99
110
  type: :runtime
100
111
  prerelease: false
101
- version_requirements: *12815620
112
+ version_requirements: *19234260
102
113
  - !ruby/object:Gem::Dependency
103
114
  name: mongo
104
- requirement: &12812240 !ruby/object:Gem::Requirement
115
+ requirement: &19231900 !ruby/object:Gem::Requirement
105
116
  none: false
106
117
  requirements:
107
118
  - - ! '>='
@@ -109,10 +120,10 @@ dependencies:
109
120
  version: '1.6'
110
121
  type: :runtime
111
122
  prerelease: false
112
- version_requirements: *12812240
123
+ version_requirements: *19231900
113
124
  - !ruby/object:Gem::Dependency
114
125
  name: em-websocket
115
- requirement: &12810000 !ruby/object:Gem::Requirement
126
+ requirement: &19229040 !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: *12810000
134
+ version_requirements: *19229040
124
135
  - !ruby/object:Gem::Dependency
125
136
  name: yajl-ruby
126
- requirement: &12807700 !ruby/object:Gem::Requirement
137
+ requirement: &19257780 !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: *12807700
145
+ version_requirements: *19257780
135
146
  - !ruby/object:Gem::Dependency
136
147
  name: sys-filesystem
137
- requirement: &12823980 !ruby/object:Gem::Requirement
148
+ requirement: &19255820 !ruby/object:Gem::Requirement
138
149
  none: false
139
150
  requirements:
140
151
  - - ! '>='
@@ -142,10 +153,10 @@ dependencies:
142
153
  version: '0'
143
154
  type: :runtime
144
155
  prerelease: false
145
- version_requirements: *12823980
156
+ version_requirements: *19255820
146
157
  - !ruby/object:Gem::Dependency
147
158
  name: ohai
148
- requirement: &12822240 !ruby/object:Gem::Requirement
159
+ requirement: &19252280 !ruby/object:Gem::Requirement
149
160
  none: false
150
161
  requirements:
151
162
  - - ! '>='
@@ -153,7 +164,7 @@ dependencies:
153
164
  version: 0.6.12
154
165
  type: :runtime
155
166
  prerelease: false
156
- version_requirements: *12822240
167
+ version_requirements: *19252280
157
168
  description: Ganymed is an event collection daemon
158
169
  email:
159
170
  - bb@xnull.de
@@ -175,16 +186,16 @@ files:
175
186
  - contrib/cpuhog.c
176
187
  - ganymed.gemspec
177
188
  - lib/ganymed.rb
178
- - lib/ganymed/client.rb
179
189
  - lib/ganymed/collector.rb
180
- - lib/ganymed/collector/cpu.rb
181
- - lib/ganymed/collector/disk.rb
182
- - lib/ganymed/collector/iostat.rb
183
- - lib/ganymed/collector/load.rb
184
- - lib/ganymed/collector/metadata.rb
185
- - lib/ganymed/collector/network.rb
186
- - lib/ganymed/collector/procs.rb
187
- - lib/ganymed/collector/uptime.rb
190
+ - lib/ganymed/collectors/cpu.rb
191
+ - lib/ganymed/collectors/disk.rb
192
+ - lib/ganymed/collectors/iostat.rb
193
+ - lib/ganymed/collectors/load.rb
194
+ - lib/ganymed/collectors/memory.rb
195
+ - lib/ganymed/collectors/metadata.rb
196
+ - lib/ganymed/collectors/network.rb
197
+ - lib/ganymed/collectors/process.rb
198
+ - lib/ganymed/collectors/uptime.rb
188
199
  - lib/ganymed/config.yml
189
200
  - lib/ganymed/event.rb
190
201
  - lib/ganymed/master.rb
@@ -228,7 +239,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
228
239
  version: '0'
229
240
  segments:
230
241
  - 0
231
- hash: -813213877420202571
242
+ hash: -4122495108718523153
232
243
  required_rubygems_version: !ruby/object:Gem::Requirement
233
244
  none: false
234
245
  requirements:
@@ -237,7 +248,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
237
248
  version: '0'
238
249
  segments:
239
250
  - 0
240
- hash: -813213877420202571
251
+ hash: -4122495108718523153
241
252
  requirements: []
242
253
  rubyforge_project:
243
254
  rubygems_version: 1.8.17
@@ -1,132 +0,0 @@
1
- require 'active_support/core_ext/array/extract_options'
2
- require 'socket'
3
- require 'msgpack'
4
-
5
- module Ganymed
6
-
7
- ##
8
- # The Client connects to the {Sampler} and/or {Processor} and emits samples
9
- # and/or {Event events} respectively.
10
- #
11
- class Client
12
-
13
- # The {Processor} {ProcessorSocket socket}.
14
- attr_reader :processor
15
-
16
- # The {Sampler} {SamplerSocket socket}.
17
- attr_reader :sampler
18
-
19
- # Create a new client instance.
20
- #
21
- # @param [Hash] opts Client options.
22
- # @option opts [Hash] :processor Options passed to the {ProcessorSocket}.
23
- # @option opts [Hash] :sampler Options passed to the {SamplerSocket}.
24
- def initialize(opts)
25
- @processor = ProcessorSocket.new(opts[:processor]) if opts[:processor]
26
- @sampler = SamplerSocket.new(opts[:sampler]) if opts[:sampler]
27
- end
28
-
29
- ##
30
- # A simple UDPSocket wrapper.
31
- #
32
- class Socket
33
-
34
- # Create a new Socket instance.
35
- #
36
- # @param [Hash] opts Socket options.
37
- # @option opts [String] :host Host to connect to.
38
- # @option opts [Fixnum] :port Port to connect to.
39
- # @option opts [String] :origin Origin of Samples/Events. Defaults to the
40
- # fully-qualified hostname.
41
- def initialize(opts)
42
- @host, @port = opts[:host], opts[:port]
43
- @socket = UDPSocket.new
44
- @origin = opts[:origin] || ::Socket.gethostbyname(::Socket.gethostname).first
45
- end
46
-
47
- # Send data to the socket.
48
- #
49
- # @param [String] data The data to send.
50
- # @param [Fixnum] flags Socket flags.
51
- def send(data, flags=0)
52
- @socket.send(data, flags, @host, @port)
53
- end
54
- end
55
-
56
- ##
57
- # A {Socket} that emits samples to a {Sampler}.
58
- #
59
- class SamplerSocket < Socket
60
-
61
- # Emit a new sample.
62
- #
63
- # @param [String, Symbol] ds Sample data source.
64
- # @param [String] ns {Event} namespace.
65
- # @param [Fixnum, Float] value Sample value.
66
- # @param [Time] now Sample timestamp.
67
- def emit(ds, ns, value, now=Time.now.utc)
68
- data = [ds.to_s, ns, @origin, now.to_f, value.to_f]
69
- send(data.pack("Z*Z*Z*GG"))
70
- end
71
- end
72
-
73
- ##
74
- # A {Socket} that emits {Event events} to a {Processor}.
75
- #
76
- class ProcessorSocket < Socket
77
-
78
- # Emit a new {Event}.
79
- #
80
- # @param [String] ns {Event} namespace.
81
- # @param [Object] value {Event} value.
82
- # @param [Hash] opts Options
83
- # @option opts [String] cf Consolidation function used in this event.
84
- # @option opts [Time] now {Event} timestamp.
85
- # @option opts [Fixnum] resolution {Event} resolution.
86
- # @option opts [String] origin {Event} origin.
87
- def event(ns, value, opts={})
88
- opts = {
89
- :cf => nil,
90
- :now => Time.now.utc,
91
- :resolution => 0,
92
- :origin => @origin,
93
- }.merge(opts)
94
-
95
- {
96
- :_type => :event,
97
- :n => ns,
98
- :c => opts[:cf],
99
- :o => opts[:origin],
100
- :t => opts[:now].to_i,
101
- :r => opts[:resolution].to_i,
102
- :v => value
103
- }.tap do |data|
104
- send(data.to_msgpack)
105
- end
106
- end
107
-
108
- # Emit a metadata object to the {Processor}.
109
- #
110
- # A metadata object contains arbitrary data related to the origin. This
111
- # can be queried by {Websocket} clients for displaying information about
112
- # origins.
113
- #
114
- # @param [Hash] data Metadata object.
115
- # @param [Hash] opts Options
116
- # @option opts [String] origin Metadata origin. Defaults to the FQDN.
117
- def metadata(data, opts={})
118
- opts = {
119
- :origin => @origin,
120
- }.merge(opts)
121
-
122
- {
123
- :_type => :metadata,
124
- :origin => opts[:origin],
125
- :data => data,
126
- }.tap do |data|
127
- send(data.to_msgpack)
128
- end
129
- end
130
- end
131
- end
132
- end
@@ -1,32 +0,0 @@
1
- require 'ganymed/collector'
2
-
3
- module Ganymed
4
- class Collector
5
- class CPU < Base
6
- def collect!
7
- return if not File.readable?('/proc/stat')
8
- File.open('/proc/stat').each do |line|
9
- next if not line =~ /^cpu /
10
- cpu = line.chomp.split[1,7].map do |x|
11
- x.to_i / hz
12
- end
13
- @sampler.emit(:derive, "os.cpu.user", cpu[0])
14
- @sampler.emit(:derive, "os.cpu.nice", cpu[1])
15
- @sampler.emit(:derive, "os.cpu.system", cpu[2])
16
- @sampler.emit(:derive, "os.cpu.idle", cpu[3])
17
- @sampler.emit(:derive, "os.cpu.iowait", cpu[4])
18
- @sampler.emit(:derive, "os.cpu.irq", cpu[5])
19
- @sampler.emit(:derive, "os.cpu.softirq", cpu[6])
20
- end
21
- end
22
-
23
- def interval
24
- @config.interval.tap{} or 0.2
25
- end
26
-
27
- def hz
28
- @config.hz.tap{} or 100
29
- end
30
- end
31
- end
32
- end
@@ -1,36 +0,0 @@
1
- require 'sys/filesystem'
2
-
3
- module Ganymed
4
- class Collector
5
- class Disk < Base
6
- def collect!
7
- Sys::Filesystem.mounts do |mount|
8
- st = Sys::Filesystem.stat(mount.mount_point)
9
- next if st.blocks == 0 or st.files == 0
10
-
11
- name = name(mount.mount_point)
12
- next if @config.exclude.map {|e| Regexp.new(e).match(name)}.any?
13
-
14
- block_pc = 1.0 - (st.blocks_free.to_f / st.blocks.to_f)
15
- @processor.event("os.disk.#{name}.blocks", block_pc)
16
-
17
- files_pc = 1.0 - (st.files_free.to_f / st.files.to_f)
18
- @processor.event("os.disk.#{name}.files", block_pc)
19
- end
20
- end
21
-
22
- private
23
-
24
- def name(mount)
25
- case mount
26
- when '/'
27
- 'rootfs'
28
- when /^\/.+$/
29
- mount[1..-1].tr('^[a-z][0-9]', '-')
30
- else
31
- 'unknown'
32
- end
33
- end
34
- end
35
- end
36
- end
@@ -1,26 +0,0 @@
1
- Struct.new("IOStat",
2
- :major, :minor, :dev,
3
- :rio, :rmerge, :rsect, :ruse,
4
- :wio, :wmerge, :wsect, :wuse,
5
- :running, :use, :aveq)
6
-
7
- module Ganymed
8
- class Collector
9
- class IOStat < Base
10
- def collect!
11
- return if not File.readable?('/proc/diskstats')
12
- File.open('/proc/diskstats').each do |line|
13
- ios = Struct::IOStat.new(*line.strip.split(/\s+/))
14
- next if @config.skip_numbered.tap{} and ios.dev =~ /\d+$/
15
- next if @config.exclude.map {|e| Regexp.new(e).match(ios.dev)}.any?
16
- @sampler.emit(:derive, "os.iostat.#{ios.dev}.rsect", ios.rsect)
17
- @sampler.emit(:derive, "os.iostat.#{ios.dev}.wsect", ios.wsect)
18
- end
19
- end
20
-
21
- def interval
22
- @config.interval.tap{} or 0.2
23
- end
24
- end
25
- end
26
- end
@@ -1,19 +0,0 @@
1
- require 'ganymed/collector'
2
-
3
- module Ganymed
4
- class Collector
5
- class Load < Base
6
- def collect!
7
- return if not File.readable?('/proc/loadavg')
8
- File.open('/proc/loadavg') do |f|
9
- loadavg = f.read.chomp.split[0,3].map(&:to_f)
10
- @sampler.emit(:gauge, "os.loadavg", loadavg[0])
11
- end
12
- end
13
-
14
- def interval
15
- @config.interval.tap{} or 0.2
16
- end
17
- end
18
- end
19
- end
@@ -1,34 +0,0 @@
1
- require 'ohai'
2
-
3
- require 'ganymed/collector'
4
-
5
- module Ganymed
6
- class Collector
7
- class Metadata < Base
8
- def collect!
9
- Ohai::System.new.tap do |ohai|
10
- %w(
11
- os
12
- kernel
13
- hostname
14
- keys
15
- network
16
- platform
17
- uptime
18
- virtualization
19
- ).each do |plugin|
20
- ohai.require_plugin(plugin)
21
- end
22
- @processor.metadata(ohai.data)
23
-
24
- # ohai doesn't cleanup after itself
25
- true while Process.wait(-1, Process::WNOHANG) rescue nil
26
- end
27
- end
28
-
29
- def interval
30
- nil
31
- end
32
- end
33
- end
34
- end
@@ -1,36 +0,0 @@
1
- # bytes packets errs drop fifo frame compressed multicast|bytes packets errs drop fifo colls carrier compressed
2
- Struct.new("NetworkStat", :dev,
3
- :rbytes, :rpackets, :rerrs, :rdrop, :rfifo, :rframe, :rcompressed, :rmulticast,
4
- :wbytes, :wpackets, :werrs, :wdrop, :wfifo, :wcolls, :wcarrier, :wcompressed)
5
-
6
- module Ganymed
7
- class Collector
8
- class Network < Base
9
- def collect!
10
- return if not File.readable?('/proc/net/dev')
11
-
12
- File.open('/proc/net/dev') do |f|
13
- f.each do |line|
14
- next unless line =~ /:/
15
-
16
- ns = Struct::NetworkStat.new(*line.strip.split(/\s+/))
17
- ns.dev.sub!(/:$/, '')
18
-
19
- next if @config.exclude.map {|e| Regexp.new(e).match(ns.dev)}.any?
20
-
21
- @sampler.emit(:derive, "os.net.#{ns.dev}.rbytes", ns.rbytes)
22
- @sampler.emit(:derive, "os.net.#{ns.dev}.wbytes", ns.wbytes)
23
- @sampler.emit(:derive, "os.net.#{ns.dev}.rpackets", ns.rpackets)
24
- @sampler.emit(:derive, "os.net.#{ns.dev}.wpackets", ns.wpackets)
25
- @sampler.emit(:derive, "os.net.#{ns.dev}.rerrs", ns.rerrs)
26
- @sampler.emit(:derive, "os.net.#{ns.dev}.werrs", ns.werrs)
27
- end
28
- end
29
- end
30
-
31
- def interval
32
- @config.interval.tap{} or 0.1
33
- end
34
- end
35
- end
36
- end
@@ -1,22 +0,0 @@
1
- module Ganymed
2
- class Collector
3
- class Procs < Base
4
- def collect!
5
- return if not File.readable?('/proc/stat')
6
- File.open('/proc/stat').each do |line|
7
- next if not line =~ /^procs_/
8
- key, value = line.chomp.split
9
- if key == "procs_running"
10
- @sampler.emit(:gauge, "os.procs.running", value.to_i)
11
- elsif key == "procs_blocked"
12
- @sampler.emit(:gauge, "os.procs.blocked", value.to_i)
13
- end
14
- end
15
- end
16
-
17
- def interval
18
- @config.interval.tap{} or 0.2
19
- end
20
- end
21
- end
22
- end
@@ -1,16 +0,0 @@
1
- require 'ganymed/collector'
2
-
3
- module Ganymed
4
- class Collector
5
- class Uptime < Base
6
- def collect!
7
- return if not File.readable?('/proc/stat')
8
- File.open('/proc/stat').each do |line|
9
- next if not line =~ /^btime /
10
- boottime = Time.at(line.chomp.split[1].to_i)
11
- @processor.event("os.reboot", 1, :now => boottime)
12
- end
13
- end
14
- end
15
- end
16
- end