panoptimon 0.0.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.
Files changed (174) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +24 -0
  3. data/Gemfile +4 -0
  4. data/LICENSE +29 -0
  5. data/README.md +78 -0
  6. data/Rakefile +14 -0
  7. data/bin/panoptimon +118 -0
  8. data/collectors/cpu/cpu +39 -0
  9. data/collectors/cpu/cpu.json +1 -0
  10. data/collectors/disk/disk +37 -0
  11. data/collectors/disk/disk.json +1 -0
  12. data/collectors/disk/requires +1 -0
  13. data/collectors/disk_free/disk_free +15 -0
  14. data/collectors/disk_free/disk_free.json +1 -0
  15. data/collectors/dns/README.md +33 -0
  16. data/collectors/dns/dns +9 -0
  17. data/collectors/dns/dns.json +7 -0
  18. data/collectors/dns/lib/panoptimon-collector-dns/dns.rb +43 -0
  19. data/collectors/dns/lib/panoptimon-collector-dns.rb +2 -0
  20. data/collectors/dns/panoptimon-collector-dns.gemspec +4 -0
  21. data/collectors/files/README.md +32 -0
  22. data/collectors/files/files +129 -0
  23. data/collectors/files/files.json +14 -0
  24. data/collectors/files/spec/files_spec.rb +57 -0
  25. data/collectors/haproxy/README.md +40 -0
  26. data/collectors/haproxy/haproxy +16 -0
  27. data/collectors/haproxy/haproxy.json +3 -0
  28. data/collectors/haproxy/lib/panoptimon-collector-haproxy/haproxy.rb +149 -0
  29. data/collectors/haproxy/lib/panoptimon-collector-haproxy.rb +1 -0
  30. data/collectors/haproxy/notes.txt +13 -0
  31. data/collectors/haproxy/spec/haproxy_spec.rb +98 -0
  32. data/collectors/haproxy/spec/haproxy_spec.rb-get.html +22 -0
  33. data/collectors/haproxy/spec/haproxy_spec.rb-show_info.txt +21 -0
  34. data/collectors/haproxy/spec/haproxy_spec.rb-show_stat.csv +25 -0
  35. data/collectors/haproxy/spec/haproxy_spec.rb-show_stat2.csv +11 -0
  36. data/collectors/http/README.md +49 -0
  37. data/collectors/http/http +27 -0
  38. data/collectors/http/http.json +3 -0
  39. data/collectors/http/lib/panoptimon-collector-http/http.rb +74 -0
  40. data/collectors/http/lib/panoptimon-collector-http/version.rb +7 -0
  41. data/collectors/http/lib/panoptimon-collector-http.rb +2 -0
  42. data/collectors/interfaces/interfaces +33 -0
  43. data/collectors/interfaces/interfaces.json +1 -0
  44. data/collectors/iostat/iostat +53 -0
  45. data/collectors/iostat/iostat.json +1 -0
  46. data/collectors/json/README.md +27 -0
  47. data/collectors/json/json +37 -0
  48. data/collectors/json/json.json +1 -0
  49. data/collectors/load/load +15 -0
  50. data/collectors/load/load.json +1 -0
  51. data/collectors/memcached/memcached +55 -0
  52. data/collectors/memcached/memcached.json +7 -0
  53. data/collectors/memcached/test-notes.txt +3 -0
  54. data/collectors/memory/memory +33 -0
  55. data/collectors/memory/memory.json +1 -0
  56. data/collectors/mysql_status/mysql_status +52 -0
  57. data/collectors/mysql_status/mysql_status.json +4 -0
  58. data/collectors/network/network +67 -0
  59. data/collectors/network/network.json +18 -0
  60. data/collectors/nginx/README.md +32 -0
  61. data/collectors/nginx/lib/panoptimon-collector-nginx/nginx.rb +45 -0
  62. data/collectors/nginx/lib/panoptimon-collector-nginx.rb +2 -0
  63. data/collectors/nginx/nginx +11 -0
  64. data/collectors/nginx/nginx.json +3 -0
  65. data/collectors/nginx/panoptimon-collector-nginx.gemspec +4 -0
  66. data/collectors/ping/README.md +54 -0
  67. data/collectors/ping/ping +57 -0
  68. data/collectors/ping/ping.json +7 -0
  69. data/collectors/process/README.md +36 -0
  70. data/collectors/process/process +61 -0
  71. data/collectors/process/process.json +7 -0
  72. data/collectors/service/README.md +51 -0
  73. data/collectors/service/samples/.gitignore +1 -0
  74. data/collectors/service/samples/data/disconnect +11 -0
  75. data/collectors/service/samples/data/flappy +7 -0
  76. data/collectors/service/samples/data/solid +18 -0
  77. data/collectors/service/samples/replay +27 -0
  78. data/collectors/service/service +86 -0
  79. data/collectors/service/service.json +7 -0
  80. data/collectors/smtp/lib/panoptimon-collector-smtp/smtp.rb +30 -0
  81. data/collectors/smtp/lib/panoptimon-collector-smtp.rb +1 -0
  82. data/collectors/smtp/smtp +27 -0
  83. data/collectors/smtp/smtp.json +10 -0
  84. data/collectors/socket/README.md +36 -0
  85. data/collectors/socket/lib/panoptimon-collector-socket/socket.rb +38 -0
  86. data/collectors/socket/lib/panoptimon-collector-socket/tcp.rb +34 -0
  87. data/collectors/socket/lib/panoptimon-collector-socket/unix.rb +28 -0
  88. data/collectors/socket/lib/panoptimon-collector-socket.rb +3 -0
  89. data/collectors/socket/socket +13 -0
  90. data/collectors/socket/socket.json +16 -0
  91. data/collectors/socket/tests/tcp_spec.rb +21 -0
  92. data/collectors/socket/tests/unix_spec.rb +35 -0
  93. data/collectors/ssh/README.md +27 -0
  94. data/collectors/ssh/ssh +41 -0
  95. data/collectors/ssh/ssh.json +3 -0
  96. data/lib/panoptimon/collector.rb +135 -0
  97. data/lib/panoptimon/eventmonkeypatch/popen3.rb +40 -0
  98. data/lib/panoptimon/http.rb +63 -0
  99. data/lib/panoptimon/logger.rb +19 -0
  100. data/lib/panoptimon/monitor.rb +154 -0
  101. data/lib/panoptimon/util/string-with-as_number.rb +5 -0
  102. data/lib/panoptimon/util.rb +23 -0
  103. data/lib/panoptimon/version.rb +5 -0
  104. data/lib/panoptimon.rb +144 -0
  105. data/misc/collector_setup.rb +23 -0
  106. data/misc/monitor_setup.rb +25 -0
  107. data/misc/plugins_setup.rb +25 -0
  108. data/misc/riemann-cli.rb +33 -0
  109. data/panoptimon.gemspec +33 -0
  110. data/plugins/daemon_health/README.md +31 -0
  111. data/plugins/daemon_health/daemon_health.json +4 -0
  112. data/plugins/daemon_health/daemon_health.rb +34 -0
  113. data/plugins/daemon_health/lib/panoptimon-plugin-daemon_health/rollup.rb +64 -0
  114. data/plugins/daemon_health/panoptimon-plugin-daemon_health.gemspec +10 -0
  115. data/plugins/daemon_health/spec/moving_avg_spec.rb +24 -0
  116. data/plugins/email/README.md +30 -0
  117. data/plugins/email/email.json +3 -0
  118. data/plugins/email/email.rb +52 -0
  119. data/plugins/log_to_file/log_to_file.json +1 -0
  120. data/plugins/log_to_file/log_to_file.rb +8 -0
  121. data/plugins/log_to_logger/log_to_logger.json +3 -0
  122. data/plugins/log_to_logger/log_to_logger.rb +7 -0
  123. data/plugins/metrics_http/README.md +23 -0
  124. data/plugins/metrics_http/metrics_http.json +1 -0
  125. data/plugins/metrics_http/metrics_http.rb +17 -0
  126. data/plugins/riemann_stream/requires +1 -0
  127. data/plugins/riemann_stream/riemann_stream.json +3 -0
  128. data/plugins/riemann_stream/riemann_stream.rb +23 -0
  129. data/plugins/status_http/requires +1 -0
  130. data/plugins/status_http/status_http.json +1 -0
  131. data/plugins/status_http/status_http.rb +60 -0
  132. data/sample_configs/1/collectors/alls_well.json +6 -0
  133. data/sample_configs/1/collectors/clock/clock +12 -0
  134. data/sample_configs/1/collectors/clock.json +1 -0
  135. data/sample_configs/1/collectors/df/df.json +6 -0
  136. data/sample_configs/1/collectors/df/wrap_df +21 -0
  137. data/sample_configs/1/collectors/load.json +1 -0
  138. data/sample_configs/1/panoptimon.json +4 -0
  139. data/sample_configs/1/plugins/isup/isup.rb +3 -0
  140. data/sample_configs/1/plugins/isup.json +1 -0
  141. data/sample_configs/1/plugins/log_to_file.json +1 -0
  142. data/sample_configs/err_handler/collectors/fail.json +4 -0
  143. data/sample_configs/err_handler/collectors/noisy.json +5 -0
  144. data/sample_configs/err_handler/collectors/noisy_failure.json +5 -0
  145. data/sample_configs/err_handler/collectors/notfound.json +4 -0
  146. data/sample_configs/err_handler/panoptimon.json +3 -0
  147. data/sample_configs/err_handler/plugins/.exists +0 -0
  148. data/sample_configs/passthru/collectors/beep.json +5 -0
  149. data/sample_configs/passthru/collectors/cat/collect_this.json +1 -0
  150. data/sample_configs/passthru/collectors/cat.json +5 -0
  151. data/sample_configs/passthru/panoptimon.json +3 -0
  152. data/sample_configs/passthru/plugins/okcat/okcat.rb +17 -0
  153. data/sample_configs/passthru/plugins/okcat.json +1 -0
  154. data/sample_configs/plugin_error/collectors/beep.json +5 -0
  155. data/sample_configs/plugin_error/panoptimon.json +1 -0
  156. data/sample_configs/plugin_error/plugins/error_always/error_always.rb +1 -0
  157. data/sample_configs/plugin_error/plugins/error_always.json +1 -0
  158. data/sample_configs/slow/collectors/slowbeep.json +6 -0
  159. data/sample_configs/slow/panoptimon.json +1 -0
  160. data/sample_configs/slow/plugins/.exists +0 -0
  161. data/sample_configs/timeout_newline/collectors/slow_lf.json +8 -0
  162. data/sample_configs/timeout_newline/panoptimon.json +1 -0
  163. data/sample_configs/timeout_newline/plugins/.exists +0 -0
  164. data/sample_configs/timeout_not/collectors/slowly.json +7 -0
  165. data/sample_configs/timeout_not/panoptimon.json +1 -0
  166. data/sample_configs/timeout_not/plugins/.exists +0 -0
  167. data/spec/collector/config_spec.rb +30 -0
  168. data/spec/collector/initialize_spec.rb +24 -0
  169. data/spec/collector/metric_spec.rb +22 -0
  170. data/spec/passthru_spec.rb +13 -0
  171. data/spec/util_spec.rb +37 -0
  172. data/tools/link_and_enable +37 -0
  173. data/tools/metricify +8 -0
  174. metadata +319 -0
@@ -0,0 +1,135 @@
1
+ # Copyright (C) 2012 Sourcefire, Inc.
2
+
3
+ module Panoptimon
4
+
5
+ require 'json';
6
+
7
+ class Collector
8
+
9
+ include Panoptimon::Logger
10
+
11
+ attr_reader :name, :cmd, :config, :bus, :last_run_time, :interval
12
+ def initialize (args)
13
+ cmd = args.delete(:command) or raise "must have 'command' argument"
14
+ @cmd = cmd.class == Array ? cmd : Shellwords.shellsplit(cmd)
15
+ ->(exe) {
16
+ raise "no such file '#{exe}'" unless File.exist?(exe)
17
+ raise "command '#{exe}' is not executable" unless File.executable?(exe)
18
+ }.call(@cmd[0]) # TODO or maybe args[:interpreter]
19
+ @bus = args.delete(:bus) or raise "must have 'bus' argument"
20
+ args.each { |k,v| instance_variable_set("@#{k}", v) }
21
+
22
+ @name ||= 'unnamed'
23
+ @config ||= {}
24
+
25
+ @interval = config[:interval] ||= 60
26
+ @last_run_time = Time.at(-@interval)
27
+ end
28
+
29
+ def run
30
+ cmdc = @cmd + [JSON.generate(config)]
31
+
32
+ @last_run_time = Time.now # TODO .to_i ?
33
+
34
+ logger.info {"run command: #{cmdc}"}
35
+ @child = EM.popen3b(cmdc, CollectorSink, self)
36
+ @child.on_unbind { |status, errmess|
37
+ logger.error {"collector #{name} failed: #{status}" +
38
+ (errmess.nil? ? '' :
39
+ "\n #{errmess.chomp.split(/\n/).join("\n ")}")
40
+ } if(not(status.nil?) and status != 0)
41
+ @child = nil
42
+ }
43
+ logger.debug "timeout is: #{config[:timeout]}"
44
+ # XXX afaict, eventmachine just did not implement this:
45
+ # @child.set_comm_inactivity_timeout(config[:timeout])
46
+ end
47
+
48
+ def noise(mess)
49
+ logger.warn "collector/#{name} noise: #{mess.chomp}"
50
+ end
51
+
52
+ def running?
53
+ @child.nil? ? false : true
54
+ end
55
+
56
+ end
57
+
58
+ class Metric < Hash
59
+
60
+ def initialize (name, data)
61
+ name = data.delete('_name') if data['_name']
62
+ self.merge!(_flatten_hash({}, name, data))
63
+ end
64
+
65
+ def _flatten_hash (i,p,h)
66
+ h.each {|k,v|
67
+ ok = "#{p}|#{k}"
68
+ # TODO reject non-numeric data?
69
+ if k != '_info' and v.is_a?(Hash)
70
+ _flatten_hash(i, ok, v)
71
+ else
72
+ i[ok] = v
73
+ end
74
+ }
75
+ return i
76
+ end
77
+ end
78
+
79
+ module CollectorSink
80
+
81
+ def initialize (handler)
82
+ @handler = handler
83
+ @timeout = @handler.config[:timeout]
84
+ @interval = @handler.config[:interval]
85
+ timer_on
86
+ end
87
+
88
+ # reset / start timeout timer
89
+ def timer_on (opts={})
90
+ @timer.cancel unless @timer.nil?
91
+ length = @timeout + (opts[:with_interval] ? @interval : 0)
92
+ @timer = EventMachine::Timer.new(length) {
93
+ scrap = @buf ? " - #{@buf.flush}" : ''
94
+ @handler.logger.error "timeout on #{@handler.name}" + scrap
95
+ @handler.logger.debug {"pid #{get_pid}"}
96
+ close_connection()
97
+ }
98
+ end
99
+
100
+ def timer_off
101
+ @timer.cancel
102
+ end
103
+
104
+ def receive_data (data)
105
+ timer_on
106
+ @handler.logger.debug "incoming"
107
+ @buf ||= BufferedTokenizer.new("\n")
108
+ @buf.extract(data).each do |line|
109
+ timer_on(with_interval: true)
110
+ begin
111
+ data = JSON.parse(line)
112
+ rescue
113
+ # TODO feed errors up to the monitor
114
+ $stderr.puts "error parsing #{line.dump} - #{$!}"
115
+ end
116
+ @handler.logger.debug "line: #{line}"
117
+ @handler.bus.notify(Metric.new(@handler.name, data))
118
+ end
119
+ end
120
+
121
+ def receive_stderr (mess)
122
+ @handler.noise(mess)
123
+ (@err_mess ||= '') << mess
124
+ end
125
+
126
+ def on_unbind (&block); @on_unbind = block; end
127
+ def unbind
128
+ timer_off
129
+ @on_unbind.call(get_status.exitstatus, @err_mess)
130
+ end
131
+
132
+ end
133
+
134
+ end
135
+
@@ -0,0 +1,40 @@
1
+ # Copyright (C) 2012 Sourcefire, Inc.
2
+
3
+ # adapted from http://pastebin.com/C4hvAyKM
4
+ # and https://gist.github.com/1333428
5
+
6
+ # feed stderr into connection's receive_stderr()
7
+
8
+ module EventMachine
9
+ class StderrHandler < EventMachine::Connection
10
+ def initialize(connection); @connection = connection; end
11
+ def receive_data(data); @connection.receive_stderr(data); end
12
+ def unbind; detach; end
13
+ end
14
+
15
+ def self.popen3b(cmd, handler=nil, *args)
16
+ klass = klass_from_handler(Connection, handler, *args)
17
+ raise "no command?" unless cmd.first
18
+ cmd.unshift(cmd.first) # -> execvp
19
+
20
+ original_stderr = $stderr.dup
21
+
22
+ begin
23
+ rd, wr = IO.pipe
24
+
25
+ $stderr.reopen wr
26
+ s = invoke_popen(cmd)
27
+ $stderr.reopen original_stderr
28
+
29
+ connection = klass.new(s, *args)
30
+ EM.attach(rd, StderrHandler, connection)
31
+ @conns[s] = connection
32
+ yield(connection) if block_given?
33
+ connection
34
+ rescue
35
+ $stderr.reopen(original_stderr)
36
+ raise $!
37
+ end
38
+ end
39
+
40
+ end
@@ -0,0 +1,63 @@
1
+ # Copyright (C) 2012 Sourcefire, Inc.
2
+
3
+ require 'thin'
4
+
5
+ module Panoptimon
6
+ class HTTP
7
+
8
+ include Logger
9
+
10
+ def initialize (args={})
11
+ @match = []
12
+ @mount = []
13
+ # TODO args[:config].http_port, ssl, etc
14
+ @http = Thin::Server.new('0.0.0.0', 8080, self);
15
+ end
16
+
17
+ def start
18
+ @http.backend.start
19
+ end
20
+
21
+ def call (env)
22
+ path = env['PATH_INFO']
23
+ return favicon(env) if path == '/favicon.ico'
24
+ # logger.debug { "#{path} => " + env.inspect }
25
+ if go = @match.find {|x| path =~ x[0]}
26
+ elsif go = @mount.find {|x| path =~ %r{^#{x[0]}(/|$)}}
27
+ env['SCRIPT_NAME'] = go[0]
28
+ env['PATH_INFO'] = path.sub(%r{^#{go[0]}}, '')
29
+ else
30
+ return [404, {'Content-Type' => 'text/html'},
31
+ '<html><head><title>Not Found</title></head>' +
32
+ '<body><p>nope</p></body></html>']
33
+ end
34
+ env['rack.logger'] = logger
35
+ begin
36
+ return go[1].call(env)
37
+ rescue => ex
38
+ logger.error { "error: #{ex.message} #{ex.backtrace.join("\n ")}" }
39
+ return [500, {'Content-Type' => 'text/html'}, ['bah']]
40
+ end
41
+ end
42
+
43
+ def favicon(env)
44
+ # TODO bundle / configure favicon?
45
+ # NOTE why doesn't rack/thin support .to_path per spec?
46
+ return [200, {'Content-Type' => 'image/x-icon'},
47
+ Pathname.new('/tmp/favicon.ico').open]
48
+ end
49
+
50
+ # regexp-match
51
+ def match (regexp, app)
52
+ regexp = %r{^#{regexp}} if regexp.class == String
53
+ @match.push([regexp, app])
54
+ end
55
+
56
+ # path prefix
57
+ def mount (point, app)
58
+ point.sub(%r{/*$}, '')
59
+ @mount.push([point, app])
60
+ end
61
+
62
+ end
63
+ end
@@ -0,0 +1,19 @@
1
+ # Copyright (C) 2012 Sourcefire, Inc.
2
+
3
+ require "logger"
4
+
5
+ module Panoptimon::Logger
6
+
7
+ def logger
8
+ Panoptimon::Logger.logger
9
+ end
10
+
11
+ def self.logger
12
+ @logger ||= Logger.new($stderr).tap {|l|
13
+ env_l = ENV.delete('LOG_LEVEL')
14
+ l.level = env_l.nil? ?
15
+ Logger::WARN : Logger.const_get(env_l.upcase)
16
+ }
17
+ end
18
+
19
+ end
@@ -0,0 +1,154 @@
1
+ # Copyright (C) 2012 Sourcefire, Inc.
2
+
3
+ module Panoptimon
4
+ class Monitor
5
+
6
+ include Panoptimon::Logger
7
+
8
+ attr_reader :config, :collectors, :plugins, :cached, :owd,
9
+ :loaded_plugins, :bus
10
+
11
+ def initialize (args={})
12
+ @collectors = []
13
+ @plugins = {}
14
+ @loaded_plugins = {}
15
+ args.each { |k,v| instance_variable_set("@#{k}", v) }
16
+
17
+ @owd = Dir.pwd
18
+
19
+ me = self
20
+ @bus = EM.spawn { |metric| me.bus_driver(metric) }
21
+ end
22
+
23
+ def _dirjson (x)
24
+ x = Pathname.new(x)
25
+ x.entries.find_all {|f| f.to_s =~ /\.json$/i}.
26
+ map {|f| x + f}
27
+ end
28
+
29
+ def find_collectors; _dirjson(config.collectors_dir); end
30
+ def find_plugins; _dirjson(config.plugins_dir); end
31
+
32
+ def load_collectors
33
+ find_collectors.each {|f|
34
+ begin
35
+ _init_collector(_load_collector_config(f))
36
+ rescue => ex
37
+ logger.error "collector #{f} failed to load: \n" +
38
+ " #{ex.message} \n #{ex.backtrace[0]}"
39
+ end
40
+ }
41
+ end
42
+
43
+ def _load_collector_config (file)
44
+ conf = JSON.parse(file.read, {:symbolize_names => true})
45
+ base = file.basename.sub(/\.json$/, '').to_s
46
+ command = conf[:exec] ||= base
47
+ command = file.dirname + base + command unless command =~ /^\//
48
+ return conf.
49
+ merge({
50
+ name: base,
51
+ interval: (self.config.collector_interval || 99).to_i,
52
+ timeout: (self.config.collector_timeout || 99).to_i
53
+ }) {|k,a,b| a}.
54
+ merge({command: command})
55
+ end
56
+
57
+ def _init_collector (conf)
58
+ name = conf.delete(:name)
59
+ command = conf.delete(:command)
60
+ collector = Collector.new(
61
+ name: name,
62
+ bus: @bus,
63
+ command: [command.to_s] + conf[:args].to_a,
64
+ config: conf,
65
+ )
66
+ collectors << collector
67
+ end
68
+
69
+ def http
70
+ return @http unless @http.nil?
71
+ # TODO rescue LoadError => nicer error message
72
+ require 'panoptimon/http'
73
+ @http = HTTP.new
74
+ end
75
+
76
+ def empty_binding; binding; end
77
+ def load_plugins
78
+ find_plugins.each {|f| _init_plugin(_load_plugin_config(f)) }
79
+ end
80
+
81
+ def _load_plugin_config (file)
82
+ conf = JSON.parse(file.read, {:symbolize_names => true})
83
+ base = file.basename.sub(/\.json$/, '').to_s
84
+
85
+ # TODO support conf[:require] -> class.setup(conf) scheme?
86
+ rb = conf[:require] || "#{base}.rb"
87
+ rb = file.dirname + base + rb
88
+ return conf.
89
+ merge({
90
+ name: base,
91
+ }) {|k,a,b| a}.
92
+ merge({
93
+ base: base,
94
+ rb: rb
95
+ })
96
+ end
97
+
98
+ def _init_plugin (conf)
99
+ name = conf.delete(:name)
100
+ rb = conf.delete(:rb)
101
+ setup = eval("->(name, config, monitor) {#{rb.open.read}\n}",
102
+ empty_binding, rb.to_s, 1)
103
+ callback = begin; setup.call(name, conf, self)
104
+ rescue; raise "error loading plugin '#{name}' - #{$!}"; end
105
+ logger.debug "plugin #{callback} - #{plugins[name]}"
106
+ plugins[name] = callback unless callback.nil?
107
+ loaded_plugins[name] = conf.clone # XXX need a plugin object?
108
+ end
109
+
110
+ def run
111
+
112
+ runall = ->() {
113
+ logger.debug "beep"
114
+ collectors.each {|c|
115
+ logger.info "#{c.cmd} (#{c.running? ? 'on ' : 'off'
116
+ }) last run time: #{c.last_run_time}"
117
+ next if c.last_run_time + c.interval > Time.now or c.running?
118
+ c.run
119
+ }
120
+ }
121
+ EM.next_tick(&runall)
122
+ logger.warn 'no collectors' if collectors.length == 0
123
+ minterval = collectors.map{|c| c.interval}.min
124
+ minterval = 67 if minterval.nil? # XXX should never happen
125
+ logger.debug "minimum: #{minterval}"
126
+ EM.add_periodic_timer(minterval, &runall);
127
+
128
+ @http.start if @http
129
+
130
+ end
131
+
132
+ def stop
133
+ EM.stop
134
+ end
135
+
136
+ def enable_cache(arg=true);
137
+ if arg; @cached ||= {}; else; @cached = nil; end
138
+ end
139
+
140
+ def bus_driver(metric)
141
+ logger.debug {"metric: #{metric.inspect}"}
142
+ metric.each {|k,v| @cached[k] = v} if @cached
143
+ plugins.each {|n,p|
144
+ begin p.call(metric)
145
+ rescue => e
146
+ logger.warn "plugin '#{n}' error: " +
147
+ "#{e}\n #{e.backtrace[0].sub(%r{^.*/?(plugins/)}, '\1')}"
148
+ plugins.delete(n)
149
+ end
150
+ }
151
+ end
152
+
153
+ end
154
+ end
@@ -0,0 +1,5 @@
1
+ class String; def as_number # from perlfaq4
2
+ self =~ %r{\A[+-]?(?=\.?\d)\d*\.?\d*(?:[Ee][+-]?\d+)?\z} \
3
+ ? (self =~ %r{[\.Ee]} ? self.to_f : self.to_i)
4
+ : nil
5
+ end; end
@@ -0,0 +1,23 @@
1
+ # Copyright (C) 2012 Sourcefire, Inc.
2
+
3
+ module Panoptimon
4
+ module Util
5
+ VERSION = '0.0.1'
6
+
7
+ def self._os; @os ||= Gem::Platform.local.os.to_sym; end
8
+
9
+ # return osname
10
+ # or, given a hash, return the corresponding hash element and raise
11
+ # error if os not in hash keys
12
+ def self.os (dispatch={})
13
+ # TODO or mess with rbconfig + Config::CONFIG['host_os']
14
+ os = _os
15
+ return os unless dispatch.length > 0
16
+
17
+ it = dispatch[os] || dispatch[:default] or
18
+ raise "unsupported OS: #{os}"
19
+ return it.is_a?(Proc) ? it.call() : it
20
+ end
21
+
22
+ end
23
+ end
@@ -0,0 +1,5 @@
1
+ # Copyright (C) 2012 Sourcefire, Inc.
2
+
3
+ module Panoptimon
4
+ VERSION = "0.0.2"
5
+ end
data/lib/panoptimon.rb ADDED
@@ -0,0 +1,144 @@
1
+ # Copyright (C) 2012 Sourcefire, Inc.
2
+
3
+ require 'panoptimon/version'
4
+ require 'panoptimon/logger'
5
+ require 'panoptimon/monitor'
6
+ require 'panoptimon/collector'
7
+
8
+ require 'optparse'
9
+ require 'ostruct'
10
+
11
+ require 'rubygems'
12
+ require 'eventmachine'
13
+ require 'panoptimon/eventmonkeypatch/popen3.rb'
14
+
15
+ module Panoptimon
16
+
17
+ class Panoptimon
18
+ end
19
+
20
+ def self.load_options (args)
21
+ defaults = {
22
+ :daemonize => true,
23
+ :config_dir => '/etc/panoptimon/',
24
+ :config_file => '%/panoptimon.json',
25
+ :collectors_dir => '%/collectors',
26
+ :plugins_dir => '%/plugins',
27
+ :collector_interval => 60,
28
+ :collector_timeout => 120,
29
+ }
30
+
31
+ options = ->() {
32
+ o = {}
33
+ OptionParser.new do |opts|
34
+
35
+ opts.on('-C', '--config-dir DIR',
36
+ "Config directory (#{defaults[:config_dir]})"
37
+ ) { |v| o[:config_dir] = v }
38
+
39
+ opts.on('-c', '--config-file FILENAME',
40
+ "Alternative configuration file ",
41
+ "(#{defaults[:config_file]})"
42
+ ) { |v| o[:config_file] = v.nil? ? '' : v }
43
+
44
+ opts.on('-D', '--[no-]foreground',
45
+ "Don't daemonize (#{not defaults[:daemonize]})"
46
+ ) { |v| o[:daemonize] = ! v }
47
+
48
+ ['collectors', 'plugins'].each { |x|
49
+ k = "#{x}_dir".to_sym
50
+ opts.on("--#{x}-dir DIR",
51
+ "#{x.capitalize} directory (#{defaults[k]})"
52
+ ) { |v| o[k] = v }
53
+ }
54
+
55
+ [:collectors, :plugins].each { |x|
56
+ opts.on('--list-'+x.to_s, "list all #{x} found"
57
+ ) { (o[:lists] ||= []).push(x) }
58
+ }
59
+
60
+ opts.on('-o', '--configure X=Y',
61
+ 'Set configuration values'
62
+ ) { |x|
63
+ (k,v) = x.split(/=/, 2)
64
+ (o[:configure] ||= {})[k.to_sym] = v
65
+ }
66
+
67
+ opts.on('--show WHAT',
68
+ %q{Show/validate settings for:}, %q{ 'config' / collector:foo / plugin:foo}
69
+ ) { |x| (k,v) = x.split(/:/, 2)
70
+ o[:show] = {k.to_sym => v||true}
71
+ }
72
+
73
+ opts.on('--plugin-test FILE',
74
+ 'Load and test plugin(s).'
75
+ ) { |x| (o[:plugin_test] ||= []).push(x) }
76
+
77
+ opts.on('-d', '--debug', "Enable debugging."
78
+ ) { |v| o[:debug] = v }
79
+
80
+ opts.on('--verbose', "Enable verbose output"
81
+ ) { |v| o[:verbose] = v }
82
+
83
+ opts.on('-v', '--version', "Print version"
84
+ ) {
85
+ puts "panoptimon version #{Panoptimon::VERSION}"
86
+ o[:quit] = true
87
+ opts.terminate
88
+ }
89
+
90
+ opts.on('--help-defaults', 'Show default config values'
91
+ ) {
92
+ puts JSON.pretty_generate(defaults)
93
+ o[:quit] = true
94
+ opts.terminate
95
+ }
96
+
97
+ opts.on("-h", "--help", "Show this message"
98
+ ) {
99
+ puts opts
100
+ o[:quit] = true
101
+ opts.terminate
102
+ }
103
+
104
+ end.parse!(args)
105
+
106
+ return o
107
+ }.call
108
+
109
+ return false if options[:quit]
110
+
111
+ render = ->(d, x) { # x with '%/' can be relative to dir d
112
+ f = "#{x}"; f.sub!(/^%\//, '').nil? ? f : File.join(d, f)
113
+ }
114
+
115
+ # default config file is relative to config dir
116
+ cfile = render.call(
117
+ options[:config_dir] || defaults[:config_dir],
118
+ options[:config_file] || defaults[:config_file])
119
+
120
+ config = defaults.merge(
121
+ options[:config_file] == '' ? {} :
122
+ JSON.parse(File.read(cfile), {:symbolize_names => true})
123
+ ).merge(options);
124
+
125
+ config[:config_file] = cfile # for diagnostics
126
+
127
+ (config.delete(:configure) || {}).each { | k,v| config[k] = v }
128
+
129
+ [:collectors_dir, :plugins_dir].each { |d|
130
+ config[d] = render.call(config[:config_dir], config[d])
131
+ }
132
+
133
+ # make all paths absolute
134
+ [:config_file, :config_dir, :collectors_dir, :plugins_dir].each { |d|
135
+ config[d] = File.expand_path(config[d]) unless config[d] == ''
136
+ }
137
+
138
+ return OpenStruct.new(config).freeze
139
+
140
+ end
141
+
142
+ end
143
+
144
+ # vim:ts=2:sw=2:et:sta
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/ruby
2
+
3
+ require 'panoptimon'
4
+
5
+ count = 0
6
+ duck = EM.spawn { |metric|
7
+ count += 1
8
+ puts "metric: #{metric.inspect} (#{count})"
9
+ EM.stop if count >= 500
10
+ }
11
+
12
+ c = Panoptimon::Collector.new(bus: duck,
13
+ command: 'sample_configs/1/collectors/clock/clock',
14
+ # command: %q{echo -e '{"everythings_ok" : 1}\n\c' },
15
+ config: {:interval => 0.5})
16
+
17
+ puts "collector: #{c.inspect}"
18
+ #c.logger.level = ::Logger::DEBUG
19
+
20
+ EM.run {
21
+ c.run
22
+ EM.add_periodic_timer(1) { puts "running: #{c.running?}" }
23
+ }
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/ruby
2
+
3
+ require 'panoptimon'
4
+
5
+ count = 0
6
+ bus = EM.spawn { |metric|
7
+ count += 1
8
+ puts "metric: #{metric.inspect} (#{count})"
9
+ EM.stop if count >= 2000
10
+ }
11
+
12
+ m = Panoptimon::Monitor.new(
13
+ :collectors => [
14
+ Panoptimon::Collector.new(bus: bus,
15
+ command: 'sample_configs/1/collectors/clock/clock',
16
+ config: {:interval => 0.5}),
17
+ Panoptimon::Collector.new(bus: bus,
18
+ command: %q{echo -e '{"everythings_ok" : 1}\n\c' },
19
+ config: {:interval => 0.03})
20
+ ]
21
+ )
22
+
23
+ EM.run {
24
+ m.run
25
+ }
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/ruby
2
+
3
+ require 'panoptimon'
4
+
5
+ m = Panoptimon::Monitor.new
6
+
7
+ count = 0
8
+ bus = EM.spawn { |metric|
9
+ count += 1
10
+ puts "metric: #{metric.inspect} (#{count})"
11
+ m.bus_driver(metric)
12
+ EM.stop if count >= 10
13
+ }
14
+
15
+ m.collectors << Panoptimon::Collector.new(
16
+ bus: bus,
17
+ command: 'sample_configs/1/collectors/clock/clock',
18
+ config: {:interval => 0.5},
19
+ )
20
+
21
+ m.plugins[:hello] = ->(metric){
22
+ puts "#{Time.now} - should do something with #{metric.inspect}"
23
+ }
24
+
25
+ EM.run { m.run }
@@ -0,0 +1,33 @@
1
+ #!/opt/ruby/bin/ruby
2
+ #
3
+ # Riemann data feeder
4
+ # Feed json directly to a Riemann server
5
+ #
6
+ # Copyright Sourcefire, 2012
7
+ # Benjamin Krueger <bkrueger@sourcefire.com>
8
+
9
+ require 'rubygems'
10
+ require 'json'
11
+ require 'riemann/client'
12
+
13
+ input_json = JSON[STDIN.read]
14
+
15
+ puts "Service: #{input_json['service']}"
16
+ puts "State: #{input_json['state']}"
17
+ puts "Metric: #{input_json['metric']}"
18
+ puts "Description: #{input_json['description']}"
19
+
20
+ def submitEvent(event)
21
+ # New Riemann client
22
+ c = Riemann::Client.new host: 'riemann.example.com', port: 5555
23
+
24
+ # Send event to Riemann server
25
+ c << {
26
+ service: event['service'],
27
+ state: event['state'],
28
+ metric: event['metric'].to_i,
29
+ description: event['description']
30
+ }
31
+ end
32
+
33
+ submitEvent(input_json)