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
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 2ec4c546129725a6c662ae42fab05ac37d2ef7d5
4
+ data.tar.gz: 3c52bd2b259bf5d5b43b168d6c0b078b29169681
5
+ SHA512:
6
+ metadata.gz: 0baa25a867349f8702542e9232e7e7478074cb918c901033a5de65ae0b310e2cfeee7e164f4666faa6ae1b4a9abca1735d5a17f7fa27e7b4e4b4294655002652
7
+ data.tar.gz: 332e77a896f7291841125ff938d4f80004f91c164e704027cc327c3ae9113e6534d5a3da809d77f630c7a31c41b628a0d3d172aaedc86951ed5704fe61374ed9
data/.gitignore ADDED
@@ -0,0 +1,24 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ /notes.txt
19
+ /metrics.log
20
+ /panoptimon.output
21
+
22
+ # for all your experimental needs e.g. symlinks of configs
23
+ /x/
24
+ .vagrant
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in panoptimon.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,29 @@
1
+ Copyright (C) 2012 Sourcefire, Inc.
2
+ All rights reserved.
3
+
4
+ Redistribution and use in source and binary forms, with or without
5
+ modification, are permitted provided that the following conditions are
6
+ met:
7
+
8
+ * Redistributions of source code must retain the above copyright
9
+ notice, this list of conditions and the following disclaimer.
10
+
11
+ * Redistributions in binary form must reproduce the above copyright
12
+ notice, this list of conditions and the following disclaimer in the
13
+ documentation and/or other materials provided with the distribution.
14
+
15
+ * Neither the name of *Sourcefire, Inc.* nor the names of its
16
+ contributors may be used to endorse or promote products derived from
17
+ this software without specific prior written permission.
18
+
19
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
20
+ IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
21
+ TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
22
+ PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23
+ HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
25
+ TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
26
+ PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
27
+ LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
28
+ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
29
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
data/README.md ADDED
@@ -0,0 +1,78 @@
1
+ # Panoptimon
2
+
3
+ The All-Seeing System Monitor Daemon
4
+
5
+ ## Installation
6
+
7
+ $ gem install panoptimon
8
+
9
+ ## Usage
10
+
11
+ The `--config-dir` option will automatically set paths to configuration
12
+ file, collectors, and plugins (and can be referred to as '%' as shown
13
+ below.)
14
+
15
+ $ panoptimon --help
16
+ Usage: panoptimon [options]
17
+ -C, --config-dir DIR Config directory (/etc/panoptimon/)
18
+ -c, --config-file FILENAME Alternative configuration file
19
+ (%/panoptimon.json)
20
+ -D, --[no-]foreground Don't daemonize (false)
21
+ --collectors-dir DIR Collectors directory (%/collectors)
22
+ --plugins-dir DIR Plugins directory (%/plugins)
23
+ --list-collectors list all collectors found
24
+ --list-plugins list all plugins found
25
+ -o, --configure X=Y Set configuration values
26
+ --show WHAT Show/validate settings for:
27
+ 'config' / collector:foo / plugin:foo
28
+ --plugin-test FILE Load and test plugin(s).
29
+ -d, --debug Enable debugging.
30
+ --verbose Enable verbose output
31
+ -v, --version Print version
32
+ --help-defaults Show default config values
33
+ -h, --help Show this message
34
+
35
+ See [the wiki](https://github.com/synthesist/panoptimon/wiki) for more
36
+ info.
37
+
38
+ ## Copyright and License
39
+
40
+ This software is released under the following (BSD 3-clause) license:
41
+
42
+ Copyright (C) 2012 Sourcefire, Inc.
43
+ All rights reserved.
44
+
45
+ Redistribution and use in source and binary forms, with or without
46
+ modification, are permitted provided that the following conditions are
47
+ met:
48
+
49
+ * Redistributions of source code must retain the above copyright
50
+ notice, this list of conditions and the following disclaimer.
51
+
52
+ * Redistributions in binary form must reproduce the above copyright
53
+ notice, this list of conditions and the following disclaimer in the
54
+ documentation and/or other materials provided with the distribution.
55
+
56
+ * Neither the name of *Sourcefire, Inc.* nor the names of its
57
+ contributors may be used to endorse or promote products derived from
58
+ this software without specific prior written permission.
59
+
60
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
61
+ IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
62
+ TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
63
+ PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
64
+ HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
65
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
66
+ TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
67
+ PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
68
+ LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
69
+ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
70
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
71
+
72
+ ## Contributing
73
+
74
+ 1. Fork it
75
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
76
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
77
+ 4. Push to the branch (`git push origin my-new-feature`)
78
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
3
+
4
+ require 'rspec/core/rake_task'
5
+
6
+ desc 'Default: run specs.'
7
+ task :default => :spec
8
+
9
+ desc "Run specs"
10
+ RSpec::Core::RakeTask.new do |t|
11
+ t.pattern = "./spec/**/*_spec.rb" # don't need this, it's default.
12
+ # Put spec opts in a file named .rspec in root
13
+ end
14
+
data/bin/panoptimon ADDED
@@ -0,0 +1,118 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # Panoptimon - The All-Seeing System Monitor Daemon
4
+
5
+ # Copyright (C) 2012 Sourcefire, Inc.
6
+
7
+ require 'panoptimon'
8
+
9
+ require 'json'
10
+ require 'pathname'
11
+
12
+ require 'rubygems'
13
+ require 'daemons'
14
+ require 'eventmachine'
15
+
16
+ opts = Panoptimon.load_options(ARGV) or exit
17
+
18
+ monitor = Panoptimon::Monitor.new(config: opts)
19
+ monitor.logger.level = ::Logger::DEBUG if opts.debug
20
+
21
+ if opts.show then opts.show.each {|show, what|
22
+
23
+ getconf = ->(type, name) {
24
+ file = monitor.send("find_#{type}s").find {|f|
25
+ f.basename.to_s == "#{name}.json"}
26
+ {file => monitor.send("_load_#{type}_config", file)}
27
+ }
28
+
29
+ puts JSON.pretty_generate(
30
+ case show
31
+ when :config; {config: opts.marshal_dump}
32
+ when :collector; getconf.call(show, what)
33
+ when :plugin; getconf.call(show,what)
34
+ when %r{/}
35
+ p = Pathname.new(show.to_s)
36
+ raise "no such file '#{p}'" unless p.exist?
37
+ {p => monitor.send(
38
+ "_load_#{p.dirname.basename.to_s.sub(/s$/,'')}_config", p)}
39
+ else raise "--show '#{show}' argument invalid"
40
+ end
41
+ ).sub(/^{\n /, '').sub(/\n}$/,'').gsub(/\n /, "\n")
42
+ exit
43
+ }; end
44
+
45
+ # XXX needs a real test
46
+ puts "config: ", JSON.pretty_generate(opts.marshal_dump) if opts.debug
47
+
48
+ if opts.lists
49
+
50
+ opts.lists.uniq.sort.each { |list|
51
+ does = {
52
+ collectors: ->() { monitor.find_collectors },
53
+ plugins: ->() { monitor.find_plugins },
54
+ }
55
+ puts does[list].call.tap {|x| x.push('(none)') unless x.length > 0}.
56
+ unshift("#{list}:").join("\n ");
57
+ }
58
+
59
+ exit
60
+ end
61
+
62
+ if opts.plugin_test
63
+
64
+ opts.plugin_test.each do |p|
65
+ monitor._init_plugin(monitor._load_plugin_config(Pathname.new(p)))
66
+ end
67
+
68
+ module MetricReader
69
+ def initialize (monitor)
70
+ @monitor = monitor
71
+ end
72
+
73
+ def notify_readable
74
+ line = @io.readline
75
+ begin
76
+ data = JSON.parse(line)
77
+ rescue
78
+ $stderr.puts "error parsing #{line.dump} - #{$!}"
79
+ end
80
+ EM.next_tick {
81
+ @monitor.bus_driver(Panoptimon::Metric.new(:input, data))
82
+ }
83
+ rescue EOFError
84
+ detach
85
+ EM.stop
86
+ end
87
+ end
88
+
89
+ $stderr.puts "Enter JSON metrics or type Ctrl-D to exit." if
90
+ $stdin.tty?
91
+
92
+ EM.run {
93
+ monitor.run
94
+ EM.watch($stdin, MetricReader, monitor).notify_readable = true
95
+ }
96
+
97
+ exit
98
+ end
99
+
100
+ ########################################################################
101
+ if opts.daemonize
102
+ # TODO could possibly use webrick::daemon.start or process.daemon,
103
+ # but this has support for redirecting io to logfiles... worth it?
104
+ Daemons.daemonize(
105
+ app_name: Pathname.new($0).basename.to_s,
106
+ log_output: true)
107
+ end
108
+
109
+ # TODO maybe split config loading / init across daemonize for better
110
+ # startup diagnostics about config errors
111
+ monitor.load_plugins
112
+ monitor.load_collectors
113
+
114
+ EM.run {
115
+ monitor.run
116
+ }
117
+
118
+ # vim:ts=2:sw=2:et:sta
@@ -0,0 +1,39 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $stdout.sync = true # persistent process
4
+
5
+ require 'json'
6
+ opt = ARGV[0] ? JSON::parse(ARGV[0]) : {'interval' => 1}
7
+
8
+ p = IO.popen([*%w{vmstat -n }, opt['interval'].to_s], 'r')
9
+ p.readline # scrap
10
+ optional = {stolen: 'st'}
11
+ want = {user: 'us', system: 'sy', idle: 'id', wait: 'wa'}.
12
+ merge(optional)
13
+
14
+ # index the header
15
+ head = p.readline.chomp.sub(/^\s+/, '').split(/\s+/)
16
+ head = Hash[*head.zip(0..(head.length-1)).flatten]
17
+
18
+ outs = {}; want.each {|k,v|
19
+ if head.include?(v)
20
+ outs[k] = head[v]
21
+ else
22
+ raise "missing key '#{v}' (#{k}) - expected in header" \
23
+ unless optional.include?(k)
24
+ end
25
+ }
26
+
27
+ # discard first (unprimed) output
28
+ p.readline; puts '{}'
29
+
30
+ while l = p.readline.sub(/^\s+/, '')
31
+ if l =~ /\d+\s+\d/
32
+ l = l.split(/\s+/)
33
+ puts JSON::generate(
34
+ Hash[*outs.keys.map {|k| [k, l[outs[k]]]}.flatten]
35
+ )
36
+ else
37
+ puts '{}'
38
+ end
39
+ end
@@ -0,0 +1 @@
1
+ {"interval" : 3}
@@ -0,0 +1,37 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'json'
4
+ require 'rubygems'
5
+ require 'sys/filesystem'
6
+
7
+ config = JSON::parse(ARGV[0])
8
+
9
+ $stdout.sync = true
10
+
11
+ def GB b; (b.to_f / 1024**3).round(6); end # GB significant down to 4kB
12
+
13
+ while(true) do
14
+ info =
15
+ Sys::Filesystem.mounts.
16
+ # map {|m| $stderr.puts m.name; m}.
17
+ find_all{|m| not(m.name =~ /^(devpts|udev|sysfs|tmpfs|none|proc)$/)}.
18
+ map {|m|
19
+ s = Sys::Filesystem.stat(m.mount_point)
20
+ # $stderr.puts "#{m.name} - #{s.inspect}"
21
+ [m.name, {
22
+ # block_size: s.block_size,
23
+ # blocks: s.blocks,
24
+ # blocks_available: s.blocks_available,
25
+ space_used: GB((s.blocks - s.blocks_free) * s.block_size),
26
+ space_free: GB(s.blocks_available * s.block_size),
27
+ space_priv: GB((s.blocks_free - s.blocks_available) * s.block_size),
28
+ files_used: s.files - s.files_available,
29
+ files_free: s.files_available,
30
+ # files_priv: s.files_free - s.files_available, # XXX any use?
31
+ }]
32
+ }
33
+ puts JSON::generate(Hash[*info.flatten])
34
+
35
+ break unless config.include?('interval')
36
+ sleep config['interval']
37
+ end
@@ -0,0 +1 @@
1
+ {"interval" : 1}
@@ -0,0 +1 @@
1
+ gem/sys-filesystem
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'json'
4
+
5
+ df_cmd = 'df -kP' # or without -P on non-gnu...
6
+ info = %x{#{df_cmd}}.split(/\n/).drop(1).
7
+ map{|l| l.split(/\s+/).values_at(0,2,3,5)} # fs, used, free, mount
8
+ .find_all{|x| not(x[0] =~ /^(udev|tmpfs|none)$/)}
9
+
10
+ out = {}
11
+ info.each {|x| out[x[0]] = {
12
+ used: (x[1].to_f / 1024**2).round(6), # GB significant down to 4kB
13
+ free: (x[2].to_f / 1024**2).round(6),
14
+ }}
15
+ puts JSON::generate(out)
@@ -0,0 +1 @@
1
+ {}
@@ -0,0 +1,33 @@
1
+ # Config
2
+
3
+ The 'hosts' section is a hash of hostnames and record types to be
4
+ queried. Valid types include 'a', 'mx', 'ns', 'ptr', 'txt', 'cname',
5
+ and 'any'. Query result types may vary from the request.
6
+
7
+ ```json
8
+
9
+ {
10
+ "hosts" : {
11
+ "example.com" : ["a", "mx", "ns"],
12
+ "www.example.com" : ["cname"],
13
+ },
14
+ "nameservers" : [null, "ns.example.com"]
15
+ }
16
+ ```
17
+
18
+ # Results
19
+
20
+ The results will include a count (`n`) and _info.records for each.
21
+
22
+ ```json
23
+ {
24
+ "dns|ns.example.com|www.example.com|cname|n" : 1,
25
+ "dns|default|www.example.com|cname|n" : 1,
26
+ "dns|ns.example.com|www.example.com|cname|_info" : {
27
+ "records" : [ "see-also.example.com. ]
28
+ },
29
+ "dns|default|www.example.com|cname|_info" : {
30
+ "records" : [ "see-also.example.com. ]
31
+ }
32
+ }
33
+ ```
@@ -0,0 +1,9 @@
1
+ #! /usr/bin/env ruby
2
+
3
+ $LOAD_PATH.unshift(File.expand_path(File.join(File.dirname(__FILE__), 'lib')))
4
+ require 'rubygems'
5
+ require 'json'
6
+ require 'panoptimon-collector-dns'
7
+
8
+ client = Panoptimon::Collector::DNS.new(JSON.parse(ARGV[0], {symbolize_names: true}))
9
+ puts client.query.to_json
@@ -0,0 +1,7 @@
1
+ {
2
+ "hosts" : {
3
+ "sourcefire.com" : ["a", "mx"],
4
+ "www.sourcefire.com" : ["cname"],
5
+ "console.amp.sourcefire.com" : ["cname"]
6
+ }
7
+ }
@@ -0,0 +1,43 @@
1
+ class Array; def to_h; Hash[self]; end; end
2
+ module Panoptimon
3
+ module Collector
4
+ class DNS
5
+
6
+ attr_accessor :options
7
+
8
+ def initialize(options={})
9
+ @options = options
10
+ end
11
+
12
+ # types: a, mx, ns, ptr, txt, cname, any
13
+
14
+ def query
15
+ hosts = @options[:hosts]
16
+ nslist = @options[:nameservers] || [nil]
17
+ nslist.map {|ns|
18
+ dns = ::Net::DNS::Resolver.new(
19
+ ns ? {nameservers: ns} : {})
20
+ [ns || 'default', hosts.map {|name,types|
21
+ # TODO allow aliased name for output?
22
+ # e.g. types.class == Hash ? ...
23
+ # collect results by type regardless of query
24
+ typed = Hash.new { |h,k|
25
+ h[k] = {n: 0, _info: {records: []}} }
26
+ types.each {|t|
27
+ dns.search(name.to_s, Net::DNS.const_get(t.upcase)).
28
+ answer.each { |rec|
29
+ stash = typed[rec.type.downcase]
30
+ stash[:n] += 1
31
+ stash[:_info][:records].push(rec.value)
32
+ }
33
+ }
34
+ [name, typed]
35
+ }.to_h]
36
+ }.to_h
37
+ end
38
+
39
+ end
40
+ end
41
+ end
42
+
43
+
@@ -0,0 +1,2 @@
1
+ require 'net/dns'
2
+ require 'panoptimon-collector-dns/dns'
@@ -0,0 +1,4 @@
1
+ Gem::Specification.new { |s|
2
+
3
+ s.add_dependency 'net-dns'
4
+ }
@@ -0,0 +1,32 @@
1
+ ```json
2
+
3
+ {
4
+ "paths" : {
5
+ "/tmp" : {}, // implicit name+path, just count everything + report
6
+ "/tmp swapfiles" : { // names must be unique
7
+ "path" : "/tmp", // explicit path
8
+ "match" : "^\\..*\.swp$", // regular expression match
9
+ "atime" : {"min": 1354051358}, // in absolute epoch seconds
10
+ // also max, (and relative time using -min/-max)
11
+ // also for: mtime, ctime, size (only absolute min/max)
12
+ //
13
+ // uid/gid : [35, 47], or by excluding: ["not", 0, 42]
14
+ //
15
+ // mode: "0[67][45]0" // a right-anchored regexp
16
+ // mode: "& 0111" // octal bitmask
17
+ },
18
+ "/opt" : {"no_list" : true}, // will skip stat() on contents
19
+ "/bin" : {"no_list" : true, // will only return the count,
20
+ "mtime" : {"-min" : 600}}, // but stat is needed for the check
21
+ "/vmlinuz" : {
22
+ "path" : "/",
23
+ "only" : ["vmlinuz"], // explicit list / skip readdir()
24
+ "filter" : ["symlink"], // must be a symlink
25
+ "mtime" : {"-min" : 3600}, // relative to Time.now
26
+ },
27
+ "/bin symlinks" : {"path" : "/bin/", "filter" : ["symlink"]},
28
+ "/usr/bin swapfiles" : {"path" : "/usr/bin/", "glob" : ".*.swp"}
29
+ }
30
+ }
31
+
32
+ ```
@@ -0,0 +1,129 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'json'
4
+ require 'pathname'
5
+
6
+ # given a list of attributes
7
+ # returns a function which expects a file or stat object and will answer
8
+ # true if all of the attributes are true about that object
9
+ def filters (list)
10
+ ok = %w{blockdev chardev directory file pipe setgid setuid socket sticky
11
+ symlink world_readable world_writable zero}
12
+ ok = ok + ok.map {|i| '!'+i}
13
+ not_ok = list - ok
14
+ raise "unsupported: '#{not_ok}'" if not_ok.length > 0
15
+ list.map! {|i| i = i+'?';
16
+ i.sub!(/^!/, '') ? ->(f) {not(f.send(i))} : ->(f){f.send(i)}
17
+ }
18
+ return ->(f) { return list.all? {|t| t.call(f)} ? true : false }
19
+ end
20
+
21
+ class Code < Proc
22
+ attr_accessor :source
23
+ def self.new (*args, src)
24
+ src = '->(' + args.join(',') + ') {' + src + '}'
25
+ block = eval(src)
26
+ super(&block).tap {|x| x.source = src}
27
+ end
28
+ end
29
+
30
+ def minmax (prop, c) # c is hash of min|-min / max|-max
31
+ unrel = ->(k) {
32
+ relk = '-'.+(k.to_s).to_sym
33
+ c[k] ? c[k].to_i :
34
+ c[relk] ? "Time.now.to_i - " + c[relk].to_i.to_s
35
+ : nil
36
+ }
37
+ checks = {min: '>=', max: '<='}.map {|k,v|
38
+ (abs = unrel[k]) ? ["#{v} #{abs}"] : []
39
+ }.flatten
40
+ Code.new(:s,
41
+ checks.length > 1 ?
42
+ "v = s.send('#{prop}').to_i; " +
43
+ checks.map {|cmp| 'v ' + cmp}.join(' && ')
44
+ : "s.send('#{prop}').to_i " + checks[0]
45
+ )
46
+ end
47
+
48
+ # should prep the report per key (so it can be prepped once, stored and
49
+ # re-run in persistent mode)
50
+ def report (key, c)
51
+ path = Pathname.new(c[:path] || key.to_s)
52
+
53
+ sf = [] # list of checks which must all return true per stat
54
+ sf.push(filters(c[:filters])) if c[:filters]
55
+ sf.push(->(p) {
56
+ p.sub!(/^& 0/, '') ?
57
+ ->(){p = p.to_i(8);
58
+ ->(s) { s.mode & 0777 & p > 0 }}[]
59
+ : ->(){p = /#{p}$/
60
+ ->(s) { sprintf("%04o", s.mode & 0777) =~ p }}[]
61
+ }.call(c[:mode])) if c[:mode]
62
+ [:atime, :mtime, :ctime].find_all {|k| c[k]}.each {|k|
63
+ sf.push(minmax(k, c[k]))
64
+ }
65
+ [:size].find_all {|k| c[k]}.each {|k|
66
+ raise "relative values are nonsensical for #{k}" if
67
+ c[k][:'-min'] or c[k][:'-max']
68
+ sf.push(minmax(k, c[k]))
69
+ }
70
+ [:uid, :gid].find_all {|k| c[k]}.each {|k|
71
+ l = c[k].kind_of?(Array) ? c[k] : [c[k]]
72
+ neg = l[0] == 'not' ? l.shift : nil
73
+ l = Hash[l.map {|i| [i.to_i, true]}]
74
+ sf.push(
75
+ neg ? ->(s) {not l.include?(s.send(k))}
76
+ : ->(s) {l.include?(s.send(k))}
77
+ )
78
+ }
79
+
80
+
81
+ no_stat = c[:no_list] && sf.length == 0 # OPTIMIZATION
82
+
83
+ nf = [] # list of checks which must all return true per name
84
+ if c[:skip]
85
+ nf.push(->(list){
86
+ skips = Hash[list.map {|n| [n,true]}]
87
+ return ->(f) { skips[f.to_s] ? false : true }
88
+ }.call(c[:skip]))
89
+ warn "'skip' and 'only' options nonsensical" if c[:only]
90
+ end
91
+
92
+
93
+ # TODO I think lstat is the way to go - assumes you want to know about
94
+ # symlinks more than their targets (make it an option?)
95
+
96
+ files = Hash[
97
+ c[:only] ?
98
+ c[:only].map {|f| [f, begin; path.+(f).lstat; rescue; nil; end]} :
99
+ ->(got) { no_stat ?
100
+ got.map {|f| [f.to_s, nil]}
101
+ : got.map {|f| [f.to_s, path.+(f).lstat]}
102
+ }.call(
103
+ c[:glob] ?
104
+ Pathname.glob(path + c[:glob]).map {|n| n.relative_path_from(path)} :
105
+ c[:match] ?
106
+ path.children(false).find_all {|x| x.to_s =~ /#{c[:match]}/o}
107
+ : path.children(false)
108
+ )]
109
+ files.keep_if {|f,s| nf.all? {|c| c.call(f)} } if nf.length > 0
110
+ files.keep_if {|f,s| sf.all? {|c| c.call(s)} } if sf.length > 0
111
+
112
+ res = {count: files.keys.length}
113
+ return res if c[:no_list]
114
+ files.length == 0 ? res : res.merge('' => Hash[files.map {|k,v|
115
+ [k,v ? Hash[
116
+ %w{dev ino mode nlink uid gid rdev size blksize blocks}
117
+ .map {|n| [n,v.send(n)]} +
118
+ %w{atime mtime ctime}.map {|n| [n,v.send(n).to_i]}] : nil]
119
+ }])
120
+ # TODO _info => {symlinks => {filename => readlink, ...}
121
+ end
122
+
123
+ def run (opt)
124
+ p = opt[:paths] or raise "'paths' argument required!"
125
+
126
+ puts JSON.generate(Hash[p.map {|k,v| [k, report(k,v)]}])
127
+ end
128
+
129
+ run(JSON::parse(ARGV[0], {symbolize_names: true})) if __FILE__ == $0
@@ -0,0 +1,14 @@
1
+ {
2
+ "description" : "count / stat some files",
3
+ "paths" : {
4
+ "/ writable dirs" : {"path" : "/",
5
+ "only" : ["bin", "boot", "dev", "etc", "home", "lib", "usr"],
6
+ "skip" : ["tmp"],
7
+ "filters" : ["directory", "world_writable"]}
8
+ },
9
+ "-examples" : [
10
+ {"/some/dir/" : {}},
11
+ {"/some/dir/" : {"match" : "^\\..*\\.swp$"},
12
+ "1+/some/dir" : {"path" : "/some/dir", "glob" : ".*.swp"}}
13
+ ]
14
+ }