panoptimon 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
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)