perfmonger 0.6.1

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