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,57 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rspec'
4
+ load File.expand_path('../../files', __FILE__)
5
+ require 'ostruct'
6
+ require 'tmpdir'
7
+
8
+ describe('filters') {
9
+ f = ->(props) { return OpenStruct.new(props) }
10
+ it('passes with one true property') {
11
+ filters(%w{directory}).call(f.call(directory?: true))
12
+ .should == true
13
+ }
14
+ it('fails with one false property') {
15
+ filters(%w{directory}).call(f.call(directory?: false))
16
+ .should == false
17
+ }
18
+ it('passes with 2 true properties') {
19
+ filters(%w{directory world_readable}).
20
+ call(f.call(directory?: true, world_readable?: true))
21
+ .should == true
22
+ }
23
+ it('passes with 3 true properties') {
24
+ filters(%w{directory world_readable world_writable}).
25
+ call(f.call(directory?: true, world_readable?: true,
26
+ world_writable?: true))
27
+ .should == true
28
+ }
29
+ it('fails with some false properties') {
30
+ filters(%w{directory world_readable world_writable}).
31
+ call(f.call(directory?: true, world_readable?: true,
32
+ world_writable?: false))
33
+ .should == false
34
+ }
35
+ it('works for real tmpdir (aka /tmp/)') {
36
+ filters(%w{directory world_readable world_writable}).
37
+ call(Pathname.new(Dir.tmpdir).stat)
38
+ .should == true
39
+ }
40
+ it('works for real /') {
41
+ filters(%w{directory world_readable}).
42
+ call(Pathname.new('/').stat)
43
+ .should == true
44
+ }
45
+ it('works for real /') {
46
+ filters(%w{file world_readable}).
47
+ call(Pathname.new('/').stat)
48
+ .should == false
49
+ }
50
+ it('works for real / (or so one would hope)') {
51
+ filters(%w{directory world_readable world_writable}).
52
+ call(Pathname.new('/').stat)
53
+ .should == false
54
+ }
55
+
56
+ }
57
+
@@ -0,0 +1,40 @@
1
+ # Description
2
+
3
+ This collector provides a check on all defined resources within HAProxy.
4
+
5
+ # Configuration
6
+
7
+ The `stats_url` can point at either the http: or socket: protocol
8
+ (socket is assumed if the protocol is missing.) Note that socket usage
9
+ requires appropriate ownership/group permissions.
10
+
11
+ ```json
12
+ {
13
+ "stats_url": "socket://var/run/haproxy/stats"
14
+ # or http://localhost:8080
15
+ }
16
+ ```
17
+
18
+ # Output
19
+
20
+ ```json
21
+ {
22
+ "uptime_sec" : ...,
23
+ "status|up" : 13,
24
+ "status|down" : 1,
25
+ "status|open" : 6,
26
+ "status|no_check" : 4,
27
+ "process_num" : 1,
28
+ "pid" : 3720,
29
+ "nbproc" : 3,
30
+ "run_queue" : 1,
31
+ "tasks" : 17,
32
+ "_info" : {
33
+ "version" : "1.4.22",
34
+ "status" : {
35
+ "FRONTEND" : {"x" : "open", "y" : "open", ...},
36
+ "BACKEND" : {"x" : "up", "y" : "down", ...
37
+ },
38
+ },
39
+ }
40
+ ```
@@ -0,0 +1,16 @@
1
+ #! /usr/bin/env ruby
2
+ #
3
+ # == Panoptimon::Collector::HAProxy
4
+ #
5
+ # Collect HAProxy metrics.
6
+
7
+ $LOAD_PATH.unshift(File.expand_path(File.join(File.dirname(__FILE__), 'lib')))
8
+
9
+ require 'json'
10
+ require 'panoptimon-collector-haproxy'
11
+
12
+ raise "Configuration required" unless ARGV[0]
13
+
14
+ config = JSON.parse(ARGV[0], symbolize_names: true)
15
+ client = Panoptimon::Collector::HAProxy.new(config)
16
+ puts client.info.to_json
@@ -0,0 +1,3 @@
1
+ {
2
+ "socket": "/var/run/haproxy/stats"
3
+ }
@@ -0,0 +1,149 @@
1
+ require 'panoptimon/util/string-with-as_number'
2
+
3
+ class Array; def to_h; Hash[self]; end; end
4
+
5
+ module Panoptimon
6
+ module Collector
7
+ class HAProxy
8
+
9
+ attr_reader :collector, :stats_url
10
+
11
+ def initialize(options={})
12
+ url = options[:stats_url] || '/var/run/haproxy.sock'
13
+ @stats_url = url.sub(%r{^socket:/}, '')
14
+ # slash after bare hostname:port
15
+ @stats_url += '/' if @stats_url =~ %r{^https?://[^/]+$}
16
+ @collector = @stats_url !~ %r{^\w+://} \
17
+ ? :stats_from_sock
18
+ : :stats_from_http
19
+ end
20
+
21
+ def info
22
+ it = self.class.send(collector, stats_url)
23
+ out = {
24
+ uptime_sec: it[:info][:uptime_sec] ||
25
+ self.class._dhms_as_sec(it[:info][:uptime]),
26
+ status: it[:stats].values.reduce({}) {|c,v|
27
+ v.values.each {|h| s = h[:status].downcase.gsub(/ /, '_')
28
+ c[s] ||= 0; c[s] += 1 }
29
+ c
30
+ },
31
+ _info: {
32
+ status: [:FRONTEND, :BACKEND].map {|s|
33
+ [s, it[:stats][s].map {|n,v|
34
+ [n, v[:status].downcase]}.to_h]
35
+ }.to_h,
36
+ }.merge([:version].
37
+ map {|k| [k, it[:info][k]]}.to_h),
38
+ }.merge(
39
+ [:process_num, :pid, :nbproc, :run_queue, :tasks].
40
+ map {|k| [k, it[:info][k]]}.to_h)
41
+ end
42
+
43
+ def self._dhms_as_sec(dhms)
44
+ f = {'d' => 24*60**2, 'h' => 60**2, 'm' => 60, 's' => 1}
45
+ s = 0;
46
+ dhms.split(/(d|h|m|s) ?/).reverse.each_slice(2) {|p|
47
+ (k, v) = p
48
+ s += v.to_i * f[k]
49
+ }
50
+ return s
51
+ end
52
+
53
+ def self.stats_from_sock(path)
54
+ {
55
+ stats: _parse_stats_csv( _sock_get(path, 'show stat') ),
56
+ info: _parse_show_info( _sock_get(path, 'show info') )
57
+ }
58
+ end
59
+
60
+ def self.stats_from_http(uri)
61
+ # NOTE uri is expected to have trailing slash if needed
62
+ {
63
+ stats: _parse_stats_csv(
64
+ _http_get(uri + ';csv').split(/\n/) ),
65
+ info: _parse_html_info( _http_get(uri) )
66
+ }
67
+ end
68
+
69
+ def self._parse_html_info(body)
70
+ body =~ %r{General\sprocess\sinformation</[^>]+>
71
+ (.*?Running\stasks:\s\d+/\d+)<}xm or
72
+ raise "body: #{body} does not match expectations"
73
+ p = $1
74
+ info = {}
75
+ # TODO proper dishtml?
76
+ p.gsub!(%r{\s+}, ' ')
77
+ p.gsub!(%r{<br>}, "\n")
78
+ p.gsub!(%r{<[^>]+>}, '')
79
+ p.gsub!(%r{ +}, ' ')
80
+ { # harvest some numbers
81
+ pid: %r{pid =\s+(\d+)},
82
+ process_num: %r{process #(\d+)},
83
+ nbproc: %r{nbproc = (\d+)},
84
+ uptime: %r{uptime = (\d+d \d+h\d+m\d+s)},
85
+ memmax_mb: %r{memmax = (unlimited|\d+)},
86
+ :'ulimit-n' => %r{ulimit-n = (\d+)},
87
+ maxsock: %r{maxsock = (\d+)},
88
+ maxconn: %r{maxconn = (\d+)},
89
+ maxpipes: %r{maxpipes = (\d+)},
90
+ currcons: %r{current conns = (\d+)},
91
+ pipesused: %r{current pipes = (\d+)/\d+},
92
+ pipesfree: %r{current pipes = \d+/(\d+)},
93
+ run_queue: %r{Running tasks: (\d+)/\d+},
94
+ tasks: %r{Running tasks: \d+/(\d+)},
95
+ }.each {|k,v|
96
+ got = p.match(v) or raise "no match for #{k} (#{v})"
97
+ info[k] = got[1].as_number || got[1]
98
+ }
99
+ info[:memmax_mb] = 0 if info[:memmax_mb] == 'unlimited'
100
+
101
+ vi = body.match(%r{<body>.*?>([^<]+)\ version\ (\d+\.\d+\.\d+),
102
+ \ released\ (\d{4}/\d{2}/\d{2})}x) or
103
+ raise "failed to find version info"
104
+ info.merge!( name: vi[1], version: vi[2], release_date: vi[3] )
105
+ return info
106
+ end
107
+
108
+ def self._http_get(uri)
109
+ require 'net/http'
110
+ uri = URI(uri)
111
+ res = ::Net::HTTP.start(uri.host, uri.port,
112
+ :use_ssl => uri.scheme == 'https'
113
+ ).request(::Net::HTTP::Get.new(uri.request_uri))
114
+ raise "error: #{res.code} #{res.message}" unless
115
+ res.is_a?(::Net::HTTPSuccess)
116
+ return res.body
117
+ end
118
+
119
+ def self._parse_show_info(lines)
120
+ Hash[lines.map {|l|
121
+ (k,v) = * l.chomp.split(/:\s+/, 2);
122
+ k or next
123
+ [k.downcase.to_sym, v.as_number || v]}
124
+ ]
125
+ end
126
+
127
+ def self._parse_stats_csv(lines)
128
+ head = lines.shift.chomp.sub(/^# /, '') or raise "no header row?"
129
+ hk = head.split(/,/).map {|k| k.to_sym}; hk.shift(2)
130
+ imax = hk.length - 1
131
+ h = Hash.new {|hash,key| hash[key] = {}}
132
+ lines.each {|l| f = l.chomp.split(/,/)
133
+ (n,s) = f.shift(2)
134
+ h[s.to_sym][n] = Hash[(0..imax).map {|i|
135
+ [hk[i], (f[i].nil? or f[i] == "") ? nil :
136
+ f[i].as_number || f[i]]}]
137
+ }
138
+ return h
139
+ end
140
+
141
+ def self._sock_get(path, cmd)
142
+ require "socket"
143
+ stat_socket = UNIXSocket.new(path)
144
+ stat_socket.puts(cmd)
145
+ stat_socket.readlines
146
+ end
147
+ end
148
+ end
149
+ end
@@ -0,0 +1 @@
1
+ require 'panoptimon-collector-haproxy/haproxy'
@@ -0,0 +1,13 @@
1
+ ruby -Icollectors/haproxy/lib -e 'require "json"; require "panoptimon-collector-haproxy";
2
+ puts Panoptimon::Collector::HAProxy._parse_stats_csv(
3
+ File.open("/tmp/haproxy-out2.csv").readlines).to_json'
4
+
5
+ ruby -Icollectors/haproxy/lib -e 'require "json"; require "panoptimon-collector-haproxy";
6
+ puts Panoptimon::Collector::HAProxy._parse_show_info(
7
+ File.open("/tmp/haproxy-show-info.txt").readlines).to_json'
8
+
9
+ ruby -Icollectors/haproxy/lib -e 'require "json"; require "panoptimon-collector-haproxy";
10
+ puts Panoptimon::Collector::HAProxy.stats_from_http("http://localhost:8099/")'
11
+
12
+ ruby -Icollectors/haproxy/lib -e 'require "json"; require "panoptimon-collector-haproxy";
13
+ puts Panoptimon::Collector::HAProxy._parse_html_info(File.open("/tmp/haproxy-out.html").readlines.join("")).to_json'
@@ -0,0 +1,98 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rspec'
4
+
5
+ $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
6
+ require 'panoptimon-collector-haproxy'
7
+
8
+ describe('socket usage') {
9
+ c = Panoptimon::Collector::HAProxy
10
+ it('automatically assumes a socket') {
11
+ c.new().tap {|me|
12
+ me.collector.should == :stats_from_sock
13
+ me.stats_url.should == '/var/run/haproxy.sock'
14
+ }
15
+ }
16
+ it('automatically assumes a socket (with argument)') {
17
+ c.new(stats_url: "/var/lib/haproxy.sock").tap {|me|
18
+ me.collector.should == :stats_from_sock
19
+ me.stats_url.should == '/var/lib/haproxy.sock'
20
+ }
21
+ }
22
+ it('takes explicit socket:// too') {
23
+ c.new(stats_url: "socket://var/lib/haproxy.sock").tap {|me|
24
+ me.collector.should == :stats_from_sock
25
+ me.stats_url.should == '/var/lib/haproxy.sock'
26
+ }
27
+ }
28
+ it('knows when you said otherwise') {
29
+ c.new(stats_url: "sprocket://var/lib/haproxy.sock").tap {|me|
30
+ me.collector.should_not == :stats_from_sock
31
+ }
32
+ }
33
+
34
+ it('connects and such') {
35
+
36
+ plan = [
37
+ ['show stat', '-show_stat.csv'],
38
+ ['show info', '-show_info.txt'],
39
+ ]
40
+ c.stub(:_sock_get) {|path, cmd|
41
+ x = plan.shift
42
+ cmd.should == x[0]
43
+ File.open(File.expand_path(__FILE__ + x[1])).
44
+ readlines
45
+ }
46
+ # TODO refactor this to run through self.info or something
47
+ info = c.stats_from_sock('/fakely')
48
+ info[:stats][:BACKEND]['qrstuv'][:status].should == 'UP'
49
+ info[:info][:maxsock].should == 8018
50
+ info[:info][:maxsock].class.should == Fixnum
51
+ info[:info][:tasks].should == 6
52
+
53
+ }
54
+ }
55
+
56
+ describe('http usage') {
57
+ c = Panoptimon::Collector::HAProxy
58
+ it('recognizes url') {
59
+ c.new(stats_url: 'http://localhost:8080').tap {|me|
60
+ me.collector.should == :stats_from_http
61
+ me.stats_url.should == 'http://localhost:8080/'
62
+ }
63
+ }
64
+
65
+ it('recognizes https url') {
66
+ c.new(stats_url: 'https://localhost:8080').tap {|me|
67
+ me.collector.should == :stats_from_http
68
+ me.stats_url.should == 'https://localhost:8080/'
69
+ }
70
+ }
71
+
72
+ it('does not mangle path') {
73
+ c.new(stats_url: 'http://localhost:8080/bob').tap {|me|
74
+ me.collector.should == :stats_from_http
75
+ me.stats_url.should == 'http://localhost:8080/bob'
76
+ }
77
+ }
78
+
79
+ it('connects and such') {
80
+ plan = [
81
+ ['http://localhost:8080/;csv', '-show_stat.csv'],
82
+ ['http://localhost:8080/', '-get.html'],
83
+ ]
84
+ c.stub(:_http_get) {|uri|
85
+ x = plan.shift
86
+ uri.should == x[0]
87
+ File.open(File.expand_path(__FILE__ + x[1])).
88
+ readlines.join('')
89
+ }
90
+
91
+ info = c.stats_from_http('http://localhost:8080/')
92
+ info[:stats][:BACKEND]['qrstuv'][:status].should == 'UP'
93
+ info[:info][:maxsock].should == 8018
94
+ info[:info][:maxsock].class.should == Fixnum
95
+ info[:info][:tasks].should == 17
96
+ }
97
+
98
+ }
@@ -0,0 +1,22 @@
1
+ <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
2
+ "http://www.w3.org/TR/html4/loose.dtd">
3
+ <html><head><title>Statistics Report for HAProxy</title>
4
+ <meta http-equiv="content-type" content="text/html; charset=iso-8859-1">
5
+ <style type="text/css"><!-- --> </style></head>
6
+ <body><h1><a href="http://haproxy.1wt.eu/" style="text-decoration: none;">HAProxy version 1.4.18, released 2011/09/16</a></h1>
7
+ <h2>Statistics Report for pid 528</h2>
8
+ <hr width="100%" class="hr">
9
+ <h3>&gt; General process information</h3>
10
+ <table border=0><tr><td align="left" nowrap width="1%">
11
+ <p><b>pid = </b> 528 (process #1, nbproc = 1)<br>
12
+ <b>uptime = </b> 36d 7h17m01s<br>
13
+ <b>system limits:</b> memmax = unlimited; ulimit-n = 8018<br>
14
+ <b>maxsock = </b> 8018; <b>maxconn = </b> 1024; <b>maxpipes = </b> 0<br>
15
+ current conns = 17; current pipes = 0/0<br>
16
+ Running tasks: 1/17<br>
17
+ </td>
18
+ </tr>
19
+ <!-- blah blah blah -->
20
+ </table>
21
+ </body>
22
+ </html>
@@ -0,0 +1,21 @@
1
+ Name: HAProxy
2
+ Version: 1.4.22
3
+ Release_date: 2012/08/09
4
+ Nbproc: 1
5
+ Process_num: 1
6
+ Pid: 1656
7
+ Uptime: 0d 9h31m50s
8
+ Uptime_sec: 34310
9
+ Memmax_MB: 0
10
+ Ulimit-n: 8018
11
+ Maxsock: 8018
12
+ Maxconn: 4000
13
+ Maxpipes: 0
14
+ CurrConns: 1
15
+ PipesUsed: 0
16
+ PipesFree: 0
17
+ Tasks: 6
18
+ Run_queue: 1
19
+ node: localhost.localdomain
20
+ description:
21
+
@@ -0,0 +1,25 @@
1
+ # pxname,svname,qcur,qmax,scur,smax,slim,stot,bin,bout,dreq,dresp,ereq,econ,eresp,wretr,wredis,status,weight,act,bck,chkfail,chkdown,lastchg,downtime,qlimit,pid,iid,sid,throttle,lbtot,tracked,type,rate,rate_lim,rate_max,check_status,check_code,check_duration,hrsp_1xx,hrsp_2xx,hrsp_3xx,hrsp_4xx,hrsp_5xx,hrsp_other,hanafail,req_rate,req_rate_max,req_tot,cli_abrt,srv_abrt,
2
+ stats,FRONTEND,,,1,2,1000,26,6696,444851,0,0,1,,,,,OPEN,,,,,,,,,1,1,0,,,,0,1,0,2,,,,0,24,0,1,0,0,,1,2,26,,,
3
+ stats,BACKEND,0,0,0,0,1000,0,6696,444851,0,0,,0,0,0,0,UP,0,0,0,,0,3136647,0,,1,1,0,,0,,1,0,,0,,,,0,0,0,0,0,0,,,,,0,0,
4
+ bert_qaas,FRONTEND,,,8,8,1000,467,1541583624,5705953051,0,0,0,,,,,OPEN,,,,,,,,,1,2,0,,,,0,0,0,5,,,,,,,,,,,0,0,0,,,
5
+ bert_qaas,xq1.in.example.com,0,0,4,5,1000,234,1120374095,2366260217,,0,,0,224,0,0,no check,1,1,0,,,,,,1,2,1,,234,,2,0,,3,,,,,,,,,,0,,,,6,224,
6
+ bert_qaas,xq3.in.example.com,0,0,4,6,1000,233,421209529,3339692834,,0,,0,222,0,0,no check,1,1,0,,,,,,1,2,2,,233,,2,0,,2,,,,,,,,,,0,,,,7,222,
7
+ bert_qaas,BACKEND,0,0,8,8,1000,467,1541583624,5705953051,0,0,,0,446,0,0,UP,2,2,0,,0,3136647,0,,1,2,0,,467,,1,0,,5,,,,,,,,,,,,,,13,446,
8
+ bert_knobs,FRONTEND,,,0,0,1000,0,0,0,0,0,0,,,,,OPEN,,,,,,,,,1,3,0,,,,0,0,0,0,,,,0,0,0,0,0,0,,0,0,0,,,
9
+ bert_knobs,xq1.in.example.com,0,0,0,0,1000,0,0,0,,0,,0,0,0,0,no check,1,1,0,,,,,,1,3,1,,0,,2,0,,0,,,,0,0,0,0,0,0,0,,,,0,0,
10
+ bert_knobs,xq3.in.example.com,0,0,0,0,1000,0,0,0,,0,,0,0,0,0,no check,1,1,0,,,,,,1,3,2,,0,,2,0,,0,,,,0,0,0,0,0,0,0,,,,0,0,
11
+ bert_knobs,BACKEND,0,0,0,0,1000,0,0,0,0,0,,0,0,0,0,UP,2,2,0,,0,3136647,0,,1,3,0,,0,,1,0,,0,,,,0,0,0,0,0,0,,,,,0,0,
12
+ echo_http,FRONTEND,,,0,0,1000,0,0,0,0,0,0,,,,,OPEN,,,,,,,,,1,4,0,,,,0,0,0,0,,,,0,0,0,0,0,0,,0,0,0,,,
13
+ echo_http,f1.in.example.com,0,0,0,0,1000,0,0,0,,0,,0,0,0,0,no check,1,1,0,,,,,,1,4,1,,0,,2,0,,0,,,,0,0,0,0,0,0,0,,,,0,0,
14
+ echo_http,f2.in.example.com,0,0,0,0,1000,0,0,0,,0,,0,0,0,0,no check,1,1,0,,,,,,1,4,2,,0,,2,0,,0,,,,0,0,0,0,0,0,0,,,,0,0,
15
+ echo_http,f3.in.example.com,0,0,0,0,1000,0,0,0,,0,,0,0,0,0,no check,1,1,0,,,,,,1,4,3,,0,,2,0,,0,,,,0,0,0,0,0,0,0,,,,0,0,
16
+ echo_http,f4.in.example.com,0,0,0,0,1000,0,0,0,,0,,0,0,0,0,no check,1,1,0,,,,,,1,4,4,,0,,2,0,,0,,,,0,0,0,0,0,0,0,,,,0,0,
17
+ echo_http,f5.in.example.com,0,0,0,0,1000,0,0,0,,0,,0,0,0,0,no check,1,1,0,,,,,,1,4,5,,0,,2,0,,0,,,,0,0,0,0,0,0,0,,,,0,0,
18
+ echo_http,BACKEND,0,0,0,0,1000,0,0,0,0,0,,0,0,0,0,UP,5,5,0,,0,3136647,0,,1,4,0,,0,,1,0,,0,,,,0,0,0,0,0,0,,,,,0,0,
19
+ qrstuv,FRONTEND,,,8,51,1000,1333,5218374317,4978521250,0,0,0,,,,,OPEN,,,,,,,,,1,5,0,,,,0,0,0,33,,,,,,,,,,,0,0,0,,,
20
+ qrstuv,abcd.in.example.com,0,0,1,11,1000,263,762637239,729424629,,0,,0,0,0,0,no check,1,1,0,,,,,,1,5,1,,263,,2,0,,6,,,,,,,,,,0,,,,0,0,
21
+ qrstuv,abcd.in.example.com,0,0,2,11,1000,271,922439480,896042928,,0,,0,0,0,0,no check,1,1,0,,,,,,1,5,2,,271,,2,0,,7,,,,,,,,,,0,,,,0,0,
22
+ qrstuv,abcd.in.example.com,0,0,2,11,1000,272,1392243548,1312264244,,0,,0,0,0,0,no check,1,1,0,,,,,,1,5,3,,272,,2,0,,7,,,,,,,,,,0,,,,0,0,
23
+ qrstuv,abcd.in.example.com,0,0,2,11,1000,267,1401121383,1321460656,,0,,0,0,0,0,no check,1,1,0,,,,,,1,5,4,,267,,2,0,,7,,,,,,,,,,0,,,,0,0,
24
+ qrstuv,abcd.in.example.com,0,0,1,11,1000,260,739932667,719328793,,0,,0,0,0,0,no check,1,1,0,,,,,,1,5,5,,260,,2,0,,7,,,,,,,,,,0,,,,0,0,
25
+ qrstuv,BACKEND,0,0,8,51,1000,1333,5218374317,4978521250,0,0,,0,0,0,0,UP,5,5,0,,0,3136647,0,,1,5,0,,1333,,1,0,,33,,,,,,,,,,,,,,0,0,
@@ -0,0 +1,11 @@
1
+ # pxname,svname,qcur,qmax,scur,smax,slim,stot,bin,bout,dreq,dresp,ereq,econ,eresp,wretr,wredis,status,weight,act,bck,chkfail,chkdown,lastchg,downtime,qlimit,pid,iid,sid,throttle,lbtot,tracked,type,rate,rate_lim,rate_max,check_status,check_code,check_duration,hrsp_1xx,hrsp_2xx,hrsp_3xx,hrsp_4xx,hrsp_5xx,hrsp_other,hanafail,req_rate,req_rate_max,req_tot,cli_abrt,srv_abrt,
2
+ main,FRONTEND,,,0,0,3000,0,0,0,0,0,0,,,,,OPEN,,,,,,,,,1,1,0,,,,0,0,0,0,,,,0,0,0,0,0,0,,0,0,0,,,
3
+ static,static,0,0,0,0,,0,0,0,,0,,0,0,0,0,DOWN,1,1,0,0,1,32987,32987,,1,2,1,,0,,2,0,,0,L4CON,,0,0,0,0,0,0,0,0,,,,0,0,
4
+ static,BACKEND,0,0,0,0,0,0,0,0,0,0,,0,0,0,0,DOWN,0,0,0,,1,32987,32987,,1,2,0,,0,,1,0,,0,,,,0,0,0,0,0,0,,,,,0,0,
5
+ app,app1,0,0,0,0,,0,0,0,,0,,0,0,0,0,DOWN,1,1,0,0,1,32987,32987,,1,3,1,,0,,2,0,,0,L4CON,,0,0,0,0,0,0,0,0,,,,0,0,
6
+ app,app2,0,0,0,0,,0,0,0,,0,,0,0,0,0,DOWN,1,1,0,0,1,32986,32986,,1,3,2,,0,,2,0,,0,L4CON,,0,0,0,0,0,0,0,0,,,,0,0,
7
+ app,app3,0,0,0,0,,0,0,0,,0,,0,0,0,0,DOWN,1,1,0,0,1,32986,32986,,1,3,3,,0,,2,0,,0,L4CON,,0,0,0,0,0,0,0,0,,,,0,0,
8
+ app,app4,0,0,0,0,,0,0,0,,0,,0,0,0,0,DOWN,1,1,0,0,1,32986,32986,,1,3,4,,0,,2,0,,0,L4CON,,0,0,0,0,0,0,0,0,,,,0,0,
9
+ app,BACKEND,0,0,0,0,0,0,0,0,0,0,,0,0,0,0,DOWN,0,0,0,,1,32986,32986,,1,3,0,,0,,1,0,,0,,,,0,0,0,0,0,0,,,,,0,0,
10
+ stats,FRONTEND,,,1,2,3000,7,1694,62774,0,0,1,,,,,OPEN,,,,,,,,,1,4,0,,,,0,1,0,2,,,,0,5,0,1,0,0,,1,1,7,,,
11
+ stats,BACKEND,0,0,0,0,3000,0,1694,62774,0,0,,0,0,0,0,UP,0,0,0,,0,32987,0,,1,4,0,,0,,1,0,,0,,,,0,0,0,0,0,0,,,,,0,0,
@@ -0,0 +1,49 @@
1
+ # Config
2
+
3
+ The list of urls may include simple urls (http:// is assumed if the
4
+ scheme is omitted), or hashes for more specific behavior.
5
+
6
+ * 'name' - a shorthand name for the report rather than the full url
7
+ * 'method' - 'get' or 'head'
8
+ * 'match' - regexp match against the content (implies the 'get' method)
9
+ * 'timeout' - timeout
10
+
11
+ ```json
12
+ {
13
+ "urls" : [
14
+ "localhost",
15
+ "example.com",
16
+ "https://github.com",
17
+ {"name" : "example", "url": "https://ssl.example.com:6983/whatever",
18
+ "match" : "<title>What", "timeout": 7},
19
+ ],
20
+ "default_method" : "head",
21
+ "default_timeout" : 3,
22
+
23
+ "interval" : 60,
24
+ "timeout" : 25,
25
+ }
26
+ ```
27
+
28
+ # Output
29
+
30
+ Output will typically contain the following, depending on the status and
31
+ request. If the request timed-out, the 'timeout' metric will be true.
32
+
33
+ ```json
34
+ {
35
+ "http|localhost|code" => 200,
36
+ "http|localhost|elapsed" => 0.02, # seconds
37
+ "http|localhost|content_length" => 97, # bytes
38
+ ...
39
+ "http|github.com|ssl|expires_in" => 604809, # seconds
40
+ "http|github.com|ssl|_info" => {
41
+ "issuer" : "Digicert Inc",
42
+ "serial" : "...",
43
+ "valid" : "2010-06-19 00:00:00 UTC",
44
+ "expires" : "2011-06-19 00:00:00 UTC"
45
+ }
46
+ ...
47
+ "http|example|ok" => true, # only if there is a match spec'd
48
+ }
49
+ ```
@@ -0,0 +1,27 @@
1
+ #! /usr/bin/env ruby
2
+ $LOAD_PATH.unshift File.expand_path(File.join(File.dirname(__FILE__), 'lib'))
3
+
4
+ require 'json'
5
+ require 'panoptimon-collector-http'
6
+
7
+ ARGV[0] or raise "arguments required"
8
+ conf = JSON.parse(ARGV[0], {symbolize_names: true})
9
+
10
+ defaults = {
11
+ method: conf[:default_method] || 'head',
12
+ timeout: conf[:default_timeout] || 3,
13
+ }
14
+
15
+ raise "must have 'urls' value in config" unless conf[:urls]
16
+ setup = conf[:urls].map {|u|
17
+ o = defaults.merge(u.is_a?(Hash) ? u : {url: u})
18
+ o[:url] = 'http://' . o[:url] unless o[:url].match(%r{^\w+://})
19
+ o[:name] ||= o[:url].to_s
20
+ o
21
+ }
22
+
23
+ output = Hash[setup.map {|o|
24
+ [o[:name], Panoptimon::Collector::HTTP.new(o[:url], o).go]
25
+ }]
26
+ puts JSON.generate(output)
27
+
@@ -0,0 +1,3 @@
1
+ {
2
+ "urls": [ "localhost" ]
3
+ }
@@ -0,0 +1,74 @@
1
+ require 'json'
2
+ require 'socket'
3
+ require 'openssl'
4
+ require 'net/http'
5
+
6
+ module Panoptimon
7
+ module Collector
8
+ class HTTP
9
+
10
+ attr_reader :uri, :use_ssl, :timeout, :match, :method
11
+ def initialize(uri, opt = {})
12
+ @uri = URI(uri)
13
+ @use_ssl = @uri.scheme == 'https'
14
+ @timeout = opt[:timeout]
15
+ @match = opt[:match] ? %r{#{opt[:match]}} : nil
16
+ @method = opt[:method].downcase.to_sym
17
+ @method = :get if @method == :head and @match
18
+ end
19
+
20
+ def connect
21
+ @connect ||= ::Net::HTTP.start(uri.host, uri.port,
22
+ :use_ssl => use_ssl,
23
+ :open_timeout => timeout,
24
+ )
25
+ end
26
+
27
+ def request
28
+ crass = {
29
+ head: ::Net::HTTP::Head,
30
+ get: ::Net::HTTP::Get,
31
+ }[method]
32
+ raise "method #{method} not implemented" unless crass
33
+ crass.new(uri.request_uri)
34
+ end
35
+
36
+ def certificate_info (cert, now=Time.now)
37
+ return {
38
+ expires_in: (cert.not_after - now).round(0),
39
+ _info: {
40
+ issuer: cert.issuer.to_s.match(%r{/O=([^/]+)})[1],
41
+ valid: cert.not_before.to_s,
42
+ expires: cert.not_after.to_s,
43
+ serial: sprintf('%032x', cert.serial).to_s.gsub(/(..)(?!$)/, '\1:'),
44
+ }
45
+ }
46
+ end
47
+
48
+ def go
49
+ start = Time.now
50
+ response = begin; connect.request(request)
51
+ rescue Timeout::Error; nil ; end
52
+
53
+ ans = {
54
+ elapsed: (Time.now - start).round(6),
55
+ }
56
+
57
+ return ans.merge({timeout: true}) unless response
58
+
59
+ ans.merge!({
60
+ content_length: response.header.content_length,
61
+ code: response.code,
62
+ })
63
+ ans[:ssl] = certificate_info(connect.peer_cert) if use_ssl
64
+
65
+ ans[:ok] = response.body.match(match) ? true : false if match
66
+
67
+ (ans[:_info] ||= {})[:redirect] = response.header['Location'] \
68
+ if ans[:code].to_s.match(/^3/)
69
+
70
+ return ans
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,7 @@
1
+ module Panoptimon
2
+ module Collector
3
+ module Http
4
+ VERSION = "0.0.1"
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,2 @@
1
+ require 'panoptimon-collector-http/version'
2
+ require 'panoptimon-collector-http/http'
@@ -0,0 +1,33 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'json'
4
+ require 'panoptimon/util'
5
+ Panoptimon::Util.os(linux: true)
6
+
7
+ head = [
8
+ 'rx bytes',
9
+ 'rx packets',
10
+ 'rx errs',
11
+ 'rx drop',
12
+ 'rx fifo',
13
+ 'rx frame',
14
+ 'rx compressed',
15
+ 'rx multicast',
16
+ 'tx bytes',
17
+ 'tx packets',
18
+ 'tx drops',
19
+ 'tx fifo',
20
+ 'tx colls',
21
+ 'tx carrier',
22
+ 'tx compressed'
23
+ ]
24
+
25
+ class Array; def to_h ; Hash[*self.flatten]; end; end
26
+
27
+ state = File.read('/proc/net/dev').
28
+ split("\n").drop(2).map {|l|
29
+ (iface, row) = *l.sub(/^\s+/, '').split(/:\s+/, 2)
30
+ [iface, head.zip(row.split(/\s+/)).to_h]
31
+ }.to_h
32
+
33
+ puts JSON::generate(state)
@@ -0,0 +1 @@
1
+ {}