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
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
+ }