perfmonger 0.6.1

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 (66) hide show
  1. checksums.yaml +15 -0
  2. data/.dir-locals.el +2 -0
  3. data/.gitignore +4 -0
  4. data/.rspec +1 -0
  5. data/.travis.yml +12 -0
  6. data/COPYING +674 -0
  7. data/Gemfile +5 -0
  8. data/HOWTO.md +15 -0
  9. data/NEWS +115 -0
  10. data/README.md +61 -0
  11. data/Rakefile +8 -0
  12. data/bin/perfmonger +6 -0
  13. data/data/NOTICE +8 -0
  14. data/data/Twitter_Bootstrap_LICENSE.txt +176 -0
  15. data/data/assets/css/bootstrap-responsive.css +1109 -0
  16. data/data/assets/css/bootstrap.css +6167 -0
  17. data/data/assets/css/perfmonger.css +17 -0
  18. data/data/assets/dashboard.erb +319 -0
  19. data/data/assets/img/glyphicons-halflings-white.png +0 -0
  20. data/data/assets/img/glyphicons-halflings.png +0 -0
  21. data/data/assets/js/bootstrap.js +2280 -0
  22. data/data/assets/js/bootstrap.min.js +6 -0
  23. data/data/assets/js/canvasjs.js +9042 -0
  24. data/data/assets/js/canvasjs.min.js +271 -0
  25. data/data/sysstat.ioconf +268 -0
  26. data/ext/perfmonger/extconf.rb +19 -0
  27. data/ext/perfmonger/perfmonger.h +58 -0
  28. data/ext/perfmonger/perfmonger_record.c +754 -0
  29. data/ext/perfmonger/sysstat/common.c +627 -0
  30. data/ext/perfmonger/sysstat/common.h +207 -0
  31. data/ext/perfmonger/sysstat/ioconf.c +515 -0
  32. data/ext/perfmonger/sysstat/ioconf.h +84 -0
  33. data/ext/perfmonger/sysstat/iostat.c +1100 -0
  34. data/ext/perfmonger/sysstat/iostat.h +121 -0
  35. data/ext/perfmonger/sysstat/libsysstat.h +19 -0
  36. data/ext/perfmonger/sysstat/mpstat.c +953 -0
  37. data/ext/perfmonger/sysstat/mpstat.h +79 -0
  38. data/ext/perfmonger/sysstat/rd_stats.c +2388 -0
  39. data/ext/perfmonger/sysstat/rd_stats.h +651 -0
  40. data/ext/perfmonger/sysstat/sysconfig.h +13 -0
  41. data/lib/perfmonger/cli.rb +115 -0
  42. data/lib/perfmonger/command/base_command.rb +39 -0
  43. data/lib/perfmonger/command/fingerprint.rb +453 -0
  44. data/lib/perfmonger/command/plot.rb +429 -0
  45. data/lib/perfmonger/command/record.rb +32 -0
  46. data/lib/perfmonger/command/record_option.rb +149 -0
  47. data/lib/perfmonger/command/server.rb +294 -0
  48. data/lib/perfmonger/command/stat.rb +60 -0
  49. data/lib/perfmonger/command/stat_option.rb +29 -0
  50. data/lib/perfmonger/command/summary.rb +402 -0
  51. data/lib/perfmonger/config.rb +6 -0
  52. data/lib/perfmonger/version.rb +5 -0
  53. data/lib/perfmonger.rb +12 -0
  54. data/misc/release-howto.txt +17 -0
  55. data/misc/sample-cpu.png +0 -0
  56. data/misc/sample-read-iops.png +0 -0
  57. data/perfmonger.gemspec +44 -0
  58. data/test/run-test.sh +39 -0
  59. data/test/spec/bin_spec.rb +37 -0
  60. data/test/spec/data/2devices.expected +42 -0
  61. data/test/spec/data/2devices.output +42 -0
  62. data/test/spec/spec_helper.rb +20 -0
  63. data/test/spec/summary_spec.rb +193 -0
  64. data/test/test-perfmonger.c +145 -0
  65. data/test/test.h +9 -0
  66. metadata +154 -0
@@ -0,0 +1,294 @@
1
+
2
+ require 'optparse'
3
+ require 'json'
4
+ require 'webrick'
5
+ require 'thread'
6
+ require 'tmpdir'
7
+ require 'fileutils'
8
+ require 'erb'
9
+
10
+ # monkey patching HTTPResponse for server-sent events
11
+ class WEBrick::HTTPResponse
12
+ CRLF = "\r\n"
13
+ def send_body_io(socket)
14
+ begin
15
+ if @request_method == "HEAD"
16
+ # do nothing
17
+ elsif chunked?
18
+ begin
19
+ buf = ''
20
+ data = ''
21
+ while true
22
+ @body.readpartial( @buffer_size, buf ) # there is no need to clear buf?
23
+ data << format("%x", buf.bytesize) << CRLF
24
+ data << buf << CRLF
25
+ _write_data(socket, data)
26
+ data.clear
27
+ @sent_size += buf.bytesize
28
+ end
29
+ rescue EOFError # do nothing
30
+ rescue IOError # do nothing
31
+ end
32
+ _write_data(socket, "0#{CRLF}#{CRLF}")
33
+ else
34
+ size = @header['content-length'].to_i
35
+ _send_file(socket, @body, 0, size)
36
+ @sent_size = size
37
+ end
38
+ ensure
39
+ begin; @body.close; rescue IOError; end
40
+ end
41
+ end
42
+ end
43
+
44
+
45
+ module PerfMonger
46
+ module Command
47
+
48
+ class ServerCommand < BaseCommand
49
+ register_command 'server', 'Launch self-contained HTML5 realtime graph server'
50
+
51
+ def initialize
52
+ @parser = OptionParser.new
53
+ @parser.banner = <<EOS
54
+ Usage: perfmonger server [options] -- [perfmonger record options]
55
+
56
+ Launch TCP and HTTP server which enables users to get current
57
+ perfmonger data record via network.
58
+
59
+ Options:
60
+ EOS
61
+
62
+ @hostname = `hostname -f`.strip
63
+ @http_hostname = nil
64
+ @http_port = 20202
65
+ @tcp_port = 20203
66
+ end
67
+
68
+ def parse_args(argv)
69
+ @parser.on('-H', '--hostname NAME', "Host name to display (default: #{@hostname})") do |hostname|
70
+ @hostname = hostname
71
+ end
72
+
73
+ @parser.on('--http-hostname NAME',
74
+ "Host name for HTTP server URL. If not specified, value of '--hostname' option is used.") do |hostname|
75
+ @http_hostname = hostname
76
+ end
77
+
78
+ @parser.on('--http-port PORT', 'HTTP server port to listen.') do |port|
79
+ if ! port =~ /\A\d+\Z/
80
+ puts("ERROR: invalid port number value: #{port}")
81
+ puts(@parser.help)
82
+ exit(false)
83
+ end
84
+ @http_port = port.to_i
85
+ end
86
+
87
+ # @parser.on('--tcp-port PORT', 'TCP data server port to listen.') do |port|
88
+ # if ! port =~ /\A\d+\Z/
89
+ # puts("ERROR: invalid port number value: #{port}")
90
+ # puts(@parser.help)
91
+ # exit(false)
92
+ # end
93
+ # @tcp_port = port.to_i
94
+ # end
95
+
96
+ @parser.parse!(argv)
97
+
98
+ if @http_hostname.nil?
99
+ @http_hostname = @hostname
100
+ end
101
+
102
+ @record_cmd_args = argv
103
+ end
104
+
105
+ def run(argv)
106
+ tmp_rootdir = Dir.mktmpdir
107
+ parse_args(argv)
108
+
109
+ _, record_option = PerfMonger::Command::RecordOption.parse(@record_cmd_args)
110
+
111
+ # find perfmonger command
112
+ perfmonger_bin = File.expand_path('bin/perfmonger', PerfMonger::ROOTDIR)
113
+ if ! File.executable?(perfmonger_bin)
114
+ puts("ERROR: perfmonger not found!")
115
+ exit(false)
116
+ end
117
+
118
+ record_cmd = [perfmonger_bin, 'record',
119
+ *@record_cmd_args]
120
+
121
+ @recorder = Recorder.new(record_cmd).start
122
+
123
+ puts("PerfMonger Realtime Monitor: http://#{@http_hostname}:#{@http_port}/dashboard")
124
+ puts("")
125
+
126
+ @http_server = WEBrick::HTTPServer.new({:DocumentRoot => tmp_rootdir,
127
+ :BindAddress => '0.0.0.0',
128
+ :Port => @http_port})
129
+ setup_webrick(@http_server, record_option)
130
+
131
+ trap(:INT) do
132
+ @http_server.stop
133
+ @recorder.stop
134
+ end
135
+ @http_server.start
136
+ ensure
137
+ FileUtils.rm_rf(tmp_rootdir)
138
+ end
139
+
140
+ private
141
+ class Recorder
142
+ def initialize(record_cmd)
143
+ @current_record = nil
144
+ @mutex = Mutex.new
145
+ @cond = ConditionVariable.new
146
+ @thread = nil
147
+ @record_cmd = record_cmd
148
+
149
+ @working = false
150
+ end
151
+
152
+ def start
153
+ @mutex.synchronize do
154
+ if @thread.nil?
155
+ @thread = true
156
+ @working = true
157
+ else
158
+ return
159
+ end
160
+ end
161
+
162
+ @thread = Thread.start do
163
+ begin
164
+ IO.popen(@record_cmd, "r") do |io|
165
+ io.each_line do |line|
166
+ @mutex.synchronize do
167
+ @current_perf_data = line.strip
168
+ @cond.broadcast
169
+ end
170
+ end
171
+ end
172
+ rescue Exception => err
173
+ puts("ERROR: Exception in record_thread(#{@record_thread}) in perfmonger-server")
174
+ puts("#{err.class.to_s}: #{err.message}")
175
+ puts(err.backtrace)
176
+ end
177
+ end
178
+
179
+ self
180
+ end
181
+
182
+ def stop
183
+ @mutex.synchronize do
184
+ @working = false
185
+ @thread.terminate
186
+ @cond.broadcast
187
+ end
188
+ end
189
+
190
+ def get_current_record
191
+ @mutex.synchronize do
192
+ if @working
193
+ @cond.wait(@mutex)
194
+ current_perf_data = @current_perf_data
195
+ else
196
+ raise EOFError
197
+ end
198
+ end
199
+ end
200
+ end
201
+
202
+ class DashboardServlet < WEBrick::HTTPServlet::AbstractServlet
203
+ def initialize(server, assets_dir, record_option, opt = {})
204
+ @assets_dir = assets_dir
205
+ @record_option = record_option
206
+ @opt = opt
207
+ super
208
+ end
209
+
210
+ def escape_device_name(dev)
211
+ dev.gsub(' ', '_').gsub('-', '_')
212
+ end
213
+
214
+ def do_GET(req, res)
215
+ res.content_type = 'text/html'
216
+ res['cache-control'] = 'no-cache'
217
+
218
+ # Variables for erb template
219
+ devices = @record_option.devices
220
+ report_cpu = @record_option.report_cpu
221
+ hostname = @opt[:hostname]
222
+
223
+ erb = ERB.new(File.read(File.expand_path('dashboard.erb', @assets_dir)))
224
+ res.body = erb.result(Kernel.binding)
225
+ end
226
+ end
227
+
228
+ class FaucetServlet < WEBrick::HTTPServlet::AbstractServlet
229
+ def initialize(server, recorder)
230
+ super(server)
231
+ @recorder = recorder
232
+ end
233
+
234
+ def do_GET(req, res)
235
+ res.chunked = true
236
+ res.content_type = 'text/event-stream'
237
+ res['cache-control'] = 'no-cache'
238
+ r, w = IO.pipe
239
+
240
+ Thread.start do
241
+ begin
242
+ while record = @recorder.get_current_record
243
+ w << "data: " << record << "\r\n" << "\r\n"
244
+ end
245
+ rescue Errno::EPIPE
246
+ # puts("Connection closed for /faucet")
247
+ # connection closed
248
+ rescue EOFError
249
+ # puts("Recorder has been terminated")
250
+ # connection closed
251
+ rescue Exception => err
252
+ puts("ERROR: Exception in faucet pipe writer")
253
+ puts("#{err.class.to_s}: #{err.message}")
254
+ puts(err.backtrace)
255
+ ensure
256
+ # puts("[FaucetServlet][do_GET] close w,r pipe")
257
+ begin; w.close; rescue IOError; end
258
+ begin; r.close; rescue IOError; end
259
+ end
260
+ end
261
+
262
+ res.body = r
263
+ end
264
+ end
265
+
266
+ def setup_webrick(webrick_server, record_option)
267
+ # find assets dir
268
+ # Search build environment first, then installed dir
269
+ assets_dir = [File.expand_path('../../../../../data/assets', __FILE__),
270
+ File.expand_path('assets', PerfMonger::DATAROOTDIR)].find do |dir|
271
+ File.directory?(dir)
272
+ end
273
+ if assets_dir.nil?
274
+ puts("ERROR: Assets for PerfMonger monitor not found!")
275
+ exit(false)
276
+ end
277
+
278
+ webrick_server.mount_proc('/') do |req, res|
279
+ puts("Request Path: " + req.path)
280
+ if req.path == '/favicon.ico'
281
+ res.set_redirect(WEBrick::HTTPStatus::TemporaryRedirect, '/assets/favicon.ico')
282
+ else
283
+ res.set_redirect(WEBrick::HTTPStatus::TemporaryRedirect, '/dashboard')
284
+ end
285
+ end
286
+ webrick_server.mount('/dashboard', DashboardServlet, assets_dir, record_option,
287
+ :hostname => @hostname)
288
+ webrick_server.mount('/assets', WEBrick::HTTPServlet::FileHandler, assets_dir)
289
+ webrick_server.mount('/faucet', FaucetServlet, @recorder)
290
+ end
291
+ end
292
+
293
+ end # module Command
294
+ end # module PerfMonger
@@ -0,0 +1,60 @@
1
+
2
+ require 'optparse'
3
+ require 'json'
4
+
5
+ module PerfMonger
6
+ module Command
7
+
8
+ class StatCommand < BaseCommand
9
+ register_command 'stat', "Run a command and record system performance during execution"
10
+
11
+ def initialize
12
+ super
13
+ end
14
+
15
+ def run(argv)
16
+ @argv, @option = PerfMonger::Command::StatOption.parse(argv)
17
+
18
+ if @argv.size == 0
19
+ puts("ERROR: No command given.")
20
+ exit(false)
21
+ end
22
+
23
+ record_cmd = @option.make_command
24
+
25
+ begin
26
+ if RUBY_VERSION >= '1.9'
27
+ record_pid = Process.spawn(*record_cmd)
28
+ else
29
+ record_pid = Process.fork do
30
+ Process.exec(*record_cmd)
31
+ end
32
+ end
33
+
34
+ Signal.trap(:INT) do
35
+ Process.kill("INT", record_pid)
36
+ end
37
+
38
+ @start_time = Time.now
39
+ if RUBY_VERSION >= '1.9'
40
+ command_pid = Process.spawn(*@argv)
41
+ else
42
+ command_pid = Process.fork do
43
+ system(*@argv)
44
+ end
45
+ end
46
+ Process.wait(command_pid)
47
+ ensure
48
+ @end_time = Time.now
49
+ Process.kill(:INT, record_pid)
50
+ Process.wait(record_pid)
51
+ end
52
+
53
+ puts("")
54
+ printf("Execution time: %.4f\n", @end_time - @start_time)
55
+ summary_command = SummaryCommand.new.run([@option.logfile], @argv.join(" "))
56
+ end
57
+ end
58
+
59
+ end # module Command
60
+ end # module PerfMonger
@@ -0,0 +1,29 @@
1
+
2
+ module PerfMonger
3
+ module Command
4
+
5
+ class StatOption < RecordOption
6
+ attr_reader :json
7
+
8
+ private
9
+ def initialize
10
+ super()
11
+ @parser.banner = <<EOS
12
+ Usage: perfmonger stat [options] -- <command>
13
+
14
+ Run a command and gather performance information during its execution.
15
+
16
+ Options:
17
+ EOS
18
+
19
+ @logfile = './perfmonger.log'
20
+ @json = false
21
+
22
+ @parser.on('--json', "Output summary in JSON") do
23
+ @json = true
24
+ end
25
+ end
26
+ end
27
+
28
+ end # module Command
29
+ end # module PerfMonger