autograph 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -24,3 +24,5 @@ load_test*
24
24
  eapis*
25
25
  *.svg
26
26
  *.gemspec
27
+
28
+ *.html
data/README.rdoc CHANGED
@@ -4,11 +4,10 @@
4
4
 
5
5
  Autograph drives httperf, varying the request rate and graphing the output. This exercise provides graphical data showing how the requested resources hold up under an increasing rate, particularly with request to response time and achieved request rate.
6
6
 
7
- Can work with SOAP.
8
-
9
7
  == NOTES:
10
8
 
11
- A note on stats, from Zed Shaw: http://zedshaw.com/essays/programmer_stats.html
9
+ Start off with broad ranges and zero in on rate/call/connection limits
10
+ Run from a server, not your laptop, you don't want to be limited by your internet connection.
12
11
 
13
12
  == PROBLEMS:
14
13
 
@@ -21,46 +20,6 @@ A note on stats, from Zed Shaw: http://zedshaw.com/essays/programmer_stats.html
21
20
 
22
21
  autograph --host example.com --low-rate 10 --high-rate 50 --rate-step 10 --output-file my_load_test.html
23
22
 
24
- == INSTALL:
25
-
26
- * Get httperf installed ('brew install httperf' if you've got homebrew installed)
27
- * Download project and run 'rake build install'
28
-
29
- nick-stielaus-computer-3:autograph nick$ httperf -h
30
- Usage: httperf [-hdvV] [--add-header S] [--burst-length N] [--client N/N]
31
- [--close-with-reset] [--debug N] [--failure-status N]
32
- [--help] [--hog] [--http-version S] [--max-connections N]
33
- [--max-piped-calls N] [--method S] [--no-host-hdr]
34
- [--num-calls N] [--num-conns N] [--period [d|u|e]T1[,T2]]
35
- [--port N] [--print-reply [header|body]] [--print-request [header|body]]
36
- [--rate X] [--recv-buffer N] [--retry-on-failure] [--send-buffer N]
37
- [--server S] [--server-name S] [--session-cookies]
38
- [--ssl] [--ssl-ciphers L] [--ssl-no-reuse]
39
- [--think-timeout X] [--timeout X] [--uri S] [--verbose] [--version]
40
- [--wlog y|n,file] [--wsess N,N,X] [--wsesslog N,X,file]
41
- [--wset N,X]
42
- nick-stielaus-computer-3:autograph nick$ autograph
43
- Usage: autograph [options]
44
- --host HOST The host to load test
45
- --port PORT The port to load test
46
- --uris PATH,PATH A comma separated list of pages to cycle through
47
- --output-file PATH Specify the file to output to.
48
- --output-dir PATH Specify a directory to write output files to.
49
- --notes NOTES Notes to be written to the report.
50
- --test Do not run benchmarks. Use test data to generate reports.
51
- Httperf Knobs:
52
- --timeout SECONDS The length in seconds before a request is marked as errored
53
- --num-call NUMCALLS The number of calls to make for each connection in the test (defaults to one).
54
- --num-conns NUMCONNS The number of connections to make for each test
55
- --low-rate LOWRATE The starting rate
56
- --high-rate HIGHRATE The highest rate at which to perform a test
57
- --rate-step RATESTEP The ammount at which to increment the rate for each interation of the test
58
- --wsesslog PATH Path to the wsesslog file.
59
- Common options:
60
- -h, --help Displays this help info
61
- -v, --verbose Verbose output
62
-
63
-
64
23
  == THANKS:
65
24
 
66
25
  Thanks to
@@ -80,7 +39,6 @@ http://www.hpl.hp.com/research/linux/httperf/
80
39
 
81
40
  == LICENSE:
82
41
 
83
-
84
42
  Autograph
85
43
  Copyright (c) 2009 Nick Stielau
86
44
 
data/Rakefile CHANGED
@@ -10,10 +10,6 @@ begin
10
10
  gem.email = "nick.stielau@gmail.com"
11
11
  gem.homepage = "http://github.com/nstielau/autograph"
12
12
  gem.authors = ["Nick Stielau"]
13
- gem.add_runtime_dependency 'builder', '= 2.1.2'
14
- gem.add_runtime_dependency 'ruport', '= 1.6.3'
15
- gem.add_runtime_dependency 'scruffy', '= 0.2.5'
16
- gem.add_runtime_dependency 'gchart', '= 1.0.0'
17
13
  # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
18
14
  end
19
15
  Jeweler::GemcutterTasks.new
@@ -30,7 +26,7 @@ end
30
26
 
31
27
 
32
28
  task :install_local do
33
- `sudo gem install --local ./pkg/autograph-0.1.2.gem --no-ri --no-rdoc`
29
+ `sudo gem install --local ./pkg/$(ls ./pkg | head -1) --no-ri --no-rdoc`
34
30
  end
35
31
 
36
32
  task :test => :check_dependencies
data/TODO CHANGED
@@ -1,32 +1,28 @@
1
- # Report for AVG says "Report for http://127.0.0.1Avg"
2
- # Skin report page
3
- # Compare two hosts
4
- # Config file?
1
+ # Compare multiple hosts hosts
5
2
 
6
3
  # Deal with AcceptEncoding, add options
7
4
 
8
- # Factor out outputs types into a report_renderers dir
9
5
  # Control verbose output with a Logger, instead of just going to STDOUT
10
6
 
11
7
  # Don't include overview for one page...
12
- # Flag maxes
13
- # Set report overview y-axis to the max request rate
14
8
 
15
9
  # Pass-through httperf args
16
- # Pick output format
17
- # Pick graph types
18
- # take a txt file for the urls
19
10
 
20
- # Remove default timeout httperf option
11
+ # Config file?
12
+ # Take a txt file for the urls
13
+
14
+ # Remove default timeout httperf option...(what happens if it is removed?)
21
15
  # Add interactive mode
22
- # Add badass testing server
23
16
 
24
- # JS for selecting right view after page refresh
17
+ # Trap exit and write output to file? (Can you have two traps?)
25
18
 
26
- # Fix Graphs x=y!
19
+ # Skinning
20
+ Improve table styles
27
21
 
28
- # --group graphs, or separate into different pages
22
+ # Put real connection/timeout etc. values in discussion.
29
23
 
30
24
 
31
- # Trap exit and write output to file? (Can you have two traps?)
25
+ # Docs
26
+ Add EC2 instructions
32
27
 
28
+ # Add gh-pages with graphs
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.2.0
1
+ 0.3.0
data/bin/autograph CHANGED
@@ -50,11 +50,6 @@ opt_parse_opts = OptionParser.new do |opts|
50
50
  command_parts << "--test"
51
51
  end
52
52
 
53
- opts.on("--graph-renderer RENDERER", "The class to use as a graph renderer (#{BaseRenderer::AVAILABLE_GRAPH_RENDERERS.join(', ')}).") do |opt|
54
- options['graph_renderer'] = opt
55
- command_parts << "--graph-renderer '#{opt}'"
56
- end
57
-
58
53
  opts.separator ""
59
54
  opts.separator "Httperf Knobs:"
60
55
 
@@ -93,11 +88,6 @@ opt_parse_opts = OptionParser.new do |opts|
93
88
  command_parts << "--rate-step #{opt}"
94
89
  end
95
90
 
96
- # opts.on("--wlog PATH", String, "A file a ASCII nul terminated pages to cycle through") do |opt|
97
- # options['httperf_wlog'] = "y,#{opt}";
98
- # command_parts << "--wlog #{opt}"
99
- # end
100
-
101
91
  opts.on("--wsesslog PATH", "Path to the wsesslog file.") do |opt|
102
92
  options['httperf_wsesslog'] = opt
103
93
  command_parts << "--wsesslog '#{opt}'"
@@ -150,5 +140,6 @@ end
150
140
  begin
151
141
  AutoPerf.new(options)
152
142
  rescue => e
153
- abort(e.to_s)
143
+ abort(e.to_s) unless options["verbose"]
144
+ raise e
154
145
  end
data/lib/autograph.rb CHANGED
@@ -2,15 +2,11 @@ $:.unshift(File.dirname(__FILE__)) unless $:.include?(File.dirname(__FILE__)) ||
2
2
 
3
3
  require 'rubygems'
4
4
  require 'optparse'
5
- require 'ruport'
6
5
  require 'pp'
7
6
 
8
7
  require 'autograph/autoperf'
8
+ require 'autograph/configuration'
9
+ require 'autograph/graph'
9
10
  require 'autograph/graph_series'
10
11
  require 'autograph/html_report'
11
- require 'autograph/configuration'
12
-
13
- require 'autograph/graph_renderers/base_renderer'
14
- require 'autograph/graph_renderers/gchart_renderer'
15
- require 'autograph/graph_renderers/scruffy_renderer'
16
- require 'autograph/graph_renderers/flot_renderer'
12
+ require 'autograph/table'
@@ -4,17 +4,32 @@ class AutoPerf
4
4
  def initialize(opts = {})
5
5
  conf = Configuration.new(opts)
6
6
 
7
- puts configuration.pretty_print if opts['verbose']
8
-
9
7
  if conf['use_test_data']
10
- conf['uris'] = ['/', '/page1', '/page2']
11
- @reports = load_test_data(conf)
8
+ reports = load_test_data(conf)
12
9
  else
13
- @reports = run_tests(conf)
10
+ reports = run_tests(conf)
14
11
  end
15
12
 
16
- graphs = BaseRenderer.generate_graphs(@reports, conf)
17
- HtmlReport.new(@reports, graphs, conf)
13
+ graphs = generate_graphs(reports, conf)
14
+ HtmlReport.new(reports, graphs, conf)
15
+ end
16
+
17
+ def generate_graphs(reports, configuration)
18
+ graphs = {}
19
+ graphs[:request_rate] = Graph.new(:title => "Demanded vs. Achieved Request Rate (r/s)")
20
+ graphs[:response_time] = Graph.new(:title => "Demanded Request Rate (r/s) vs. Response Time")
21
+ graphs[:max_request_rate] = Graph.new(:title => "Maximum Achieved Request Rate")
22
+
23
+ reports.each do |uri, report|
24
+ graphs[:request_rate].series << GraphSeries.new(report.column('rate'), report.column('conn/s').map{|x| x.to_f}, "Request rate for '#{uri}'", uri)
25
+ graphs[:response_time].series << GraphSeries.new(report.column('rate'), report.column('reply time'), "Response time for '#{uri}'", uri)
26
+ end
27
+
28
+ reports.keys.each_with_index do |key, index|
29
+ graphs[:max_request_rate].series << GraphSeries.new([index], [reports[key].max('conn/s')], "Max Request Rate for '#{key}'")
30
+ end
31
+
32
+ graphs
18
33
  end
19
34
 
20
35
  def benchmark(conf)
@@ -51,7 +66,7 @@ class AutoPerf
51
66
  def vary_rate(uri, configuration)
52
67
  puts "Config is #{configuration.inspect}" if configuration['verbose']
53
68
  results = {}
54
- report = Table(:column_names => COLUMN_NAMES)
69
+ report = Table.new(COLUMN_NAMES)
55
70
 
56
71
  (configuration['low_rate']..configuration['high_rate']).step(configuration['rate_step']) do |rate|
57
72
  results[rate] = benchmark(configuration.merge({'httperf_rate' => rate, 'httperf_uri' => uri}))
@@ -69,35 +84,25 @@ class AutoPerf
69
84
  configuration['uris'].uniq.each do |uri|
70
85
  reports[uri] = vary_rate(uri, configuration)
71
86
  end
72
-
73
- # TODO: Factor out to create_httperf_wlog
74
- if !configuration['httperf_wlog'] && configuration['uris'].length > 1 && configuration['average']
75
- replay_log = File.open('tmp_replay_log', 'w')
76
- path = replay_log.path
77
- puts "Tmp replay log is at #{path}" if configuration['verbose']
78
- index = 1
79
- configuration['uris'].each do |uri|
80
- replay_log.print uri
81
- replay_log.putc 0 if index < configuration['uris'].length # ASCII NUL Terminate join paths
82
- index = index + 1
83
- end
84
- replay_log.close
85
- puts "Replay log is at #{path}" if configuration['verbose']
86
- reports["Avg"] = vary_rate('httperf_wlog' => "y,#{path}")
87
- end
88
87
  reports
89
88
  end
90
89
 
91
90
  def load_test_data(configuration)
92
91
  reports = {}
92
+ configuration['host'] = "127.0.0.1"
93
+ configuration['uris'] = ['/', '/page1', '/page2']
93
94
  configuration['uris'].each do |uri|
94
- reports[uri] = ::Ruport::Data::Table.new(:column_names => COLUMN_NAMES)
95
+ reports[uri] = Table.new(COLUMN_NAMES)
95
96
  times = [130.7, 132.7, 180.4, 438.3, 591.9, 686.9, 739.4, 661.3, 727.1, 546.5, 711.1, 893.7, 870.0]
96
97
  conns = [5.0, 21.5, 28.8, 30.6, 26.3, 24.7, 23.0, 25.8, 28.0, 27.4, 27.9, 22.2, 22.7]
97
98
  1.upto(10) do |i|
98
99
  reports[uri] << {'rate' => i*10 - 10,
99
- 'conn/s' => conns[i % conns.length],
100
- 'reply time' => times[i % times.length]}
100
+ 'errors' => 0,
101
+ 'conn/s' => conns[((i + rand * 10) % conns.length).to_i],
102
+ 'req/s' => 0,
103
+ 'replies/s avg' => 0,
104
+ 'net io (KB/s)' => 100,
105
+ 'reply time' => times[((i + rand * 10) % times.length).to_i]}
101
106
  end
102
107
  end
103
108
  reports
@@ -1,6 +1,6 @@
1
1
  class Configuration
2
2
  def initialize(opts={})
3
- @conf = {'httperf_timeout' => 20,
3
+ @conf = {'httperf_timeout' => 120,
4
4
  'httperf_num-call' => 1,
5
5
  'httperf_num-conns' => 100,
6
6
  'httperf_rate' => 5,
@@ -29,6 +29,8 @@ class Configuration
29
29
  # TODO: Add AcceptEncoding: gzip,deflate option
30
30
  end
31
31
 
32
+ puts pretty_print if opts['verbose']
33
+
32
34
  @conf
33
35
  end
34
36
 
@@ -56,12 +58,4 @@ class Configuration
56
58
  io.puts
57
59
  io.read
58
60
  end
59
-
60
- def graph_renderer_class
61
- begin
62
- Object.const_get(@conf['graph_renderer'].to_s)
63
- rescue => e
64
- abort("#{@conf['graph_renderer'].to_s} is not one of the available graph renderers (#{BaseRenderer::AVAILABLE_GRAPH_RENDERERS.join(', ')})")
65
- end
66
- end
67
61
  end
@@ -0,0 +1,9 @@
1
+ class Graph
2
+ attr :title, true
3
+ attr :series
4
+
5
+ def initialize(options={})
6
+ @title = options[:title]
7
+ @series = []
8
+ end
9
+ end
@@ -2,11 +2,9 @@ class GraphSeries
2
2
  attr :x_values, true
3
3
  attr :y_values, true
4
4
  attr :label, true
5
- attr :type, true
6
5
  attr :path, true
7
-
8
- def initialize(t, xs, ys, l, p=nil)
9
- @type = t
6
+
7
+ def initialize(xs, ys, l, p=nil)
10
8
  @x_values = xs.map{|x| x.to_f}
11
9
  @y_values = ys.map{|x| x.to_f}
12
10
  @label = l
@@ -4,12 +4,9 @@ class HtmlReport
4
4
  def initialize(reports, graphs, configuration)
5
5
  date = Time.now
6
6
  host = configuration['host']
7
- title = "Report for #{host}"
8
7
  uris = configuration['uris']
9
8
  command_run = configuration["command_run"]
10
9
  notes = configuration["notes"]
11
- summary_graph = graphs['summary_graph']
12
- graph_header_html = configuration.graph_renderer_class.header_html
13
10
 
14
11
  output_file = HtmlReport.determine_output_file(configuration['output_file'], configuration['output_dir'])
15
12
 
@@ -1,175 +1,260 @@
1
- <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
2
- "http://www.w3.org/TR/html4/strict.dtd">
3
- <html lang="en">
1
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
2
+ "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
3
+ <html xml:lang="en" xmlns="http://www.w3.org/1999/xhtml">
4
4
  <head>
5
- <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
6
- <title><%= title %></title>
7
- <%= graph_header_html %>
8
- <style type="text/css">
9
- body,
10
- html {
11
- margin:0;
12
- padding:0;
13
- color:#000;
14
- }
15
- body {
16
- min-width:950px;
17
- }
18
- #wrap {
19
- margin:0 auto;
20
- width:950px;
21
- }
22
- #header h1 {
23
- padding:5px;
24
- margin:0;
25
- text-align: center;
26
- }
27
- #header h3 {
28
- margin: 0;
29
- }
30
- #nav {
31
-
32
- padding:5px;
33
- }
34
- #nav ul{
35
- margin:0;
36
- padding:0;
37
- list-style:none;
38
- }
39
- #nav li{
40
- display:inline;
41
- margin:0;
42
- padding:0;
43
- }
44
- #main {
45
- float:right;
46
- width:700px;
47
- }
48
- #main h2, #main h3, #main p {
49
- padding:0 10px;
50
- }
51
- #sidebar {
52
- float:left;
53
- width:240px;
54
- }
55
- #sidebar ul {
56
- margin-bottom:0;
57
- }
58
- #sidebar h3, #sidebar p {
59
- padding:0 10px 0 0;
60
- }
61
- #footer {
62
- clear:both;
63
- }
64
- #footer p {
65
- padding:5px;
66
- margin:0;
5
+ <title>Autograph report for <%= host %></title>
6
+ <!-- Favicon -->
7
+ <link rel="shortcut icon" href="http://nstielau.github.com/autograph/stylesheets/images/favicon.ico" />
8
+ <!--Stylesheets-->
9
+ <link href="http://nstielau.github.com/autograph/stylesheets/master.css" type="text/css" media="screen" rel="stylesheet" />
10
+ <!--[if lt IE 8]><link rel="stylesheet" type="text/css" media="screen" href="http://nstielau.github.com/autograph/stylesheets/ie.css" /><![endif]-->
11
+ <!--[if lte IE 6]><link rel="stylesheet" type="text/css" media="screen" href="http://nstielau.github.com/autograph/stylesheets/ie6.css" /><![endif]-->
12
+ <script type="text/javascript" src="http://nstielau.github.com/autograph/js/jquery.js"></script>
13
+ <script type="text/javascript" src="http://nstielau.github.com/autograph/js/jquery.flot.js"></script>
14
+ </head>
15
+ <body>
16
+ <div id="wrapper">
17
+ <div id="header" class="clear">
18
+ <h1 id="title">Autograph report for <%= host %></h1>
19
+ <div id="description">
20
+ <h2>Created at <%= date %></h2>
21
+ </div><!--end description-->
22
+ <div id="nav">
23
+ <ul>
24
+ </ul>
25
+ </div><!--end nav-->
26
+ </div><!--end header-->
27
+ <div id="content" class="pad">
28
+ <div id="post">
29
+ <div id="main">
30
+ <div id="graphs" class="section section_graphs">
31
+ <% graphs.each_pair do |k,g| %>
32
+ <h2><%= g.title %></h2>
33
+ <div class="flot-graph" id="<%= k %>" style="width:600px;height:300px;padding-bottom: 25px;"></div>
34
+ <% end %>
35
+ </div>
36
+
37
+ <div id="graph_discussion" class="section section_discussion" style="display:none;">
38
+ <h2>Methods</h2>
39
+ <p>
40
+ Use <a href="http://www.hpl.hp.com/research/linux/httperf/">httperf</a> to load different pages a fixed number of times, at a fixed rate, and record the response time and rate at which connections are accepted. By varying the rate, we can see how the response time and connection rate are effected by increased load on the server. Unlike log analysis, which relies on extrapolation to guess at maximum requests/second, Autograph actually stresses the system and yields real-world results.
41
+ </p>
42
+ <h2>Goals</h2>
43
+ <p>
44
+ Autograph will help you
45
+ <ul style="list-style: disc; font-size: 1.3em; padding-left: 25px; padding-bottom: 25px;">
46
+ <li>Compare the relative 'weight' of different pages, hopefully pointing you to which pages need more some caching or other form of optimization.</li>
47
+ <li>Determine rough maximum request rates that either a) block other calls, or b) increase response time beyond an acceptable limit.</li>
48
+ </ul>
49
+ </p>
50
+ <h2>Graphs</h2>
51
+ <h4>Demanded vs. Achieved request rate</h4>
52
+ <p>
53
+ Ideally, this graph would be a straight line at 45 degrees; for every request that is sent to the server, it accepts a connection, regardless of how many concurrent requests are made. Realistically, this graph will start linear and degrade as network or software bottlenecks are hit. The point at which this graph breaks linearity indicates the maximum possible concurrent requests the system can successfully serve.
54
+ </p>
55
+ <h4>Demanded Request Rate vs. response time</h4>
56
+ <p>
57
+ Ideally, this graph would be flat and low, indicating consistent and fast responses times. Realistically, this graph will either be slightly angled or flat followed by a dramatic uptick. The point at which the response times dramatically increase will coincide with the point at which it cannot serve all requests.
58
+ </p>
59
+ </div>
60
+ <div id="tables" class="section section_tables" style="display:none;">
61
+ <% reports.each do |uri, report| %>
62
+ <div id="page_<%= uri %>" class='report' >
63
+ <h2>Results for <%= "http://#{host}#{uri}" %></h2>
64
+ <div class="table_data" style="padding-bottom: 25px;">
65
+ <%= report.to_html %>
66
+ </div>
67
+ </div>
68
+ <% end %>
69
+ </div>
70
+ </div>
71
+ </div><!--end post-->
72
+ </div><!--end content-->
73
+ <div id="sidebar">
74
+ <ul>
75
+ <li class="widget widget_recent_entries">
76
+ <h2 class="widgettitle">Report Sections</h2>
77
+ <ul>
78
+ <li><a href="#" onclick="show_section('graphs');">Graphs</a></li>
79
+ <li><a href="#" onclick="show_section('tables');">Tables</a></li>
80
+ <li><a href="#" onclick="show_section('discussion');">Discussion</a></li>
81
+ </ul>
82
+ </li>
83
+ <li class="section section_graphs">
84
+ <h2 class="widgettitle">Tested Pages</h2>
85
+ <div id="choices"></div>
86
+ </li>
87
+ <% if notes %>
88
+ <li>
89
+ <h2>Report Notes</h2>
90
+ <p><%= notes %></p>
91
+ </li>
92
+ <% end %>
93
+ </ul>
94
+ </div><!--end sidebar-->
95
+ <div id="footer">
96
+ <p class="right">Generated fearlessly with <a href="http://nstielau.github.com/autograph">Autograph</a></p>
97
+ <p>Theme modified from "Vigilance Theme" by <a href="http://thethemefoundry.com">The Theme Foundry</a></p>
98
+ </div><!--end footer-->
99
+ </div><!--end wrapper-->
100
+ <script language="javascript" type="text/javascript">
101
+ function show_section(name) {
102
+ $('.section').hide();
103
+ $('.section_' + name).show();
67
104
  }
68
105
 
69
- div.report {
70
- background: #FFFFFF;
71
- }
72
- </style>
73
- <script type="text/javascript"``>
74
- function hide_reports() {
75
- var reports = document.getElementsByClassName('report');
76
- for (var i=0; i < reports.length; i++) {
77
- reports[i].style.display = 'none';
106
+ var acolor;
107
+ var default_graph_options = {
108
+ series: {
109
+ lines: { show: true, lineWidth: 3 },
110
+ points: { show: true, radius: 4 }
111
+ },
112
+ legend: {
113
+ show: true,
114
+ backgroundColor: '#FFF',
115
+ backgroundOpacity: 0.9
116
+ },
117
+ series: {
118
+ lines: { show: true, lineWidth: 3 },
119
+ points: { show: true, fill: false },
120
+ shadowSize: 0,
121
+ },
122
+ xaxis: {},
123
+ yaxis: {
124
+ show: true,
125
+ },
126
+ grid: {
127
+ show: true,
128
+ backgroundColor: null,
129
+ borderWidth: 2,
130
+ hoverable: true,
131
+ tickColor: "#E1E8F0",
132
+ },
133
+ colors: ["#5bba47","#d86b6d","#3d8aea","#333333"]
134
+ };
135
+
136
+ // Callback function to show the tooltip
137
+ function showTooltip(item) {
138
+ var contents = "(" + item.datapoint[1] + "," + item.datapoint[0] + ")";
139
+ var x = item.pageX;
140
+ var y = item.pageY - 10;
141
+
142
+ var obj = $('<div id="flot-tooltip">' + contents + '</div>').css( {
143
+ padding: '5px',
144
+ position: 'absolute',
145
+ minWidth: '5em',
146
+ display: 'block',
147
+ top: y+5,
148
+ left: x+5,
149
+ zIndex: 9999
150
+ });
151
+
152
+ obj.appendTo('body').fadeIn('200');
78
153
  }
79
- }
80
154
 
81
- function show_report(uri) {
82
- hide_reports();
83
- document.getElementById(uri).style.display = '';
84
- }
155
+ // Var to hold our previous point
156
+ var previousPoint = null;
85
157
 
86
- function uri_change_handler(uri) {
87
- var page_select = document.getElementById('pages');
88
- show_report(page_select.value);
89
- }
90
- </script>
158
+ // Bind to the plothover so we can show a tooltip
159
+ $(".flot-graph").bind("plothover", function (event, pos, item) {
160
+ if (item) {
161
+ if (previousPoint != item.datapoint) {
162
+ previousPoint = item.datapoint;
163
+ $("#flot-tooltip").remove();
164
+ showTooltip(item);
165
+ }
166
+ } else {
167
+ $('#flot-tooltip').remove().fadeOut('200');
168
+ previousPoint = null;
169
+ }
170
+ });
91
171
 
92
- </head>
93
- <body>
94
- <div id="wrap">
95
- <div id="header">
96
- <h1>Load testing report</h1>
97
- <h3>Date: <%= date %></h3>
98
- <h3>Host: <%= host %></h3>
99
- <h3>Page:
100
- <select id="pages" onchange="uri_change_handler()">
101
- <option value='overview'>Overview</option>
102
- <% uris.each do |uri|%>
103
- <option value='page_<%= uri %>'><%=uri%></option>
104
- <% end %>
105
- </select>
106
- </h3>
107
-
108
- </div>
109
- <div id="nav">
110
-
111
- </div>
112
- <div id="sidebar">
113
- <div style="display:none">
114
- <h3>Tested Pages</h3>
115
- <ul>
116
- <li><a href="#" onclick="show_report('overview');" title="Overview">Overview</a></li>
117
- <% uris.each do |uri|%>
118
- <li><a href="#" onclick="show_report('page_<%= uri %>');" title="<%= uri %>"><%= uri %></a></li>
119
- <% end %>
120
- </ul>
121
- </div>
122
- <% if notes %>
123
- <h3>Report Notes</h3>
124
- <p><%= notes %></p>
125
- <% end %>
126
- <h3>Info</h3>
127
- <ul>
128
- <li><a href="#" onclick="show_report('graph_discussion');" title="Discussion">Discussion</a></li>
129
- </ul>
172
+ var request_rate_datasets = {
173
+ <%=
174
+ dataset_id=0;
175
+ data_string = graphs[:request_rate].series.map do |s|
176
+ points = []
177
+ s.x_values.each_with_index do |x,i|
178
+ points << "[#{s.x_values[i]}, #{s.y_values[i]}]"
179
+ end
180
+ "'#{dataset_id=dataset_id+1}' : {label: \"#{s.label}\", data: [#{points.join(", ")}]}"
181
+ end.join(", \n")
182
+ # ideal_points = graphs[:request_rate].series[0].x_values.map{|x|"[#{x},#{x}]"}
183
+ # data_string + ",'#{dataset_id=dataset_id+1}' : {label: \"Ideal\", data: [#{ideal_points.join(", ")}]}"
184
+ data_string
185
+ %>
186
+ };
130
187
 
131
- </div>
132
- <div id="main">
133
- <h2>Report Detail</h2>
134
-
135
- <div id="overview" class='report'>
136
- <%= summary_graph.to_html %>
137
- </div>
138
-
139
- <div id="graph_discussion" class='report' style="display:none;">
140
- Discussion on what these graphs mean.
141
-
142
- • Increase concurrent connections, and see at which point your app will no longer serve at the demanded request rate
143
- • Find which pages are heavier/lighter than others
144
- • Replay a log (with a little work)
145
-
146
- • Actually hits your app with load, doesn't just extrapolate out from a few data points
147
- Start broad and zero in on connection limits
148
- Run from a server, not your laptop
149
- </div>
150
-
151
- <% reports.each do |uri, report| %>
152
- <div id="page_<%= uri %>" class='report' style="display:none;">
153
- <p>Report for <%= "http://#{host}#{uri}" %></p>
154
- <% graphs[uri].each do |graph| %>
155
- <%= graph.to_html %>
156
- <% end %>
157
- <pre>
158
- <%= report.to_s %>
159
- </pre>
160
- </div>
161
- <% end %>
162
- </div>
163
- <div id="footer" align="center">
164
- <p>Generated at <%= date %></p>
165
- <div>
166
- <p><a onclick="document.getElementById('command_container').style.display = 'block';">Show Command<a/></p>
167
- <div id="command_container" style="display:none">
168
- <pre><%= command_run %></pre>
169
- </div>
170
- </div>
171
- </div>
172
- </div>
173
- </body>
174
- </html>
188
+ var response_time_datasets = {
189
+ <%=
190
+ dataset_id=0;
191
+ data_string = graphs[:response_time].series.map do |s|
192
+ points = []
193
+ s.x_values.each_with_index do |x,i|
194
+ points << "[#{s.x_values[i]}, #{s.y_values[i]}]"
195
+ end
196
+ "'#{dataset_id=dataset_id+1}' : {label: \"#{s.label}\", data: [#{points.join(", ")}]}"
197
+ end.join(", \n")
198
+ data_string
199
+ %>
200
+ };
201
+
202
+ var max_request_rate_datasets = [
203
+ <%=
204
+ data_strings = []
205
+ data_string = graphs[:max_request_rate].series.each_with_index do |s, index|
206
+ data_strings << "{label: \"#{s.label}\", data: [[#{index}, #{s.y_values[0]}]]}"
207
+ end
208
+ data_strings.join(", \n")
209
+ %>
210
+ ];
211
+
212
+ // hard-code color indices to prevent them from shifting as
213
+ // countries are turned on/off
214
+ var i = 0;
215
+ $.each(request_rate_datasets, function(key, val) {
216
+ val.color = i;
217
+ ++i;
218
+ });
219
+ i = 0;
220
+ $.each(response_time_datasets, function(key, val) {
221
+ val.color = i;
222
+ ++i;
223
+ });
224
+
225
+ // insert checkboxes
226
+ var choiceContainer = $("#choices");
227
+ $.each(request_rate_datasets, function(key, val) {
228
+ choiceContainer.append('<br/><input type="checkbox" name="' + key +
229
+ '" checked="checked" id="id' + key + '">' +
230
+ '<label style="font-size: 1.5em; padding-left: 10px;" for="id' + key + '">'
231
+ + val.label.replace("Request rate for ", "") + '</label>');
232
+ });
233
+ choiceContainer.find("input").click(plotAccordingToChoices);
175
234
 
235
+
236
+ function plotAccordingToChoices() {
237
+ var data1 = [];
238
+ var data2 = [];
239
+
240
+ choiceContainer.find("input:checked").each(function () {
241
+ var key = $(this).attr("name");
242
+ if (key && request_rate_datasets[key])
243
+ data1.push(request_rate_datasets[key]);
244
+ if (key && response_time_datasets[key])
245
+ data2.push(response_time_datasets[key]);
246
+ });
247
+
248
+ if (data1.length > 0)
249
+ $.plot($("#request_rate"), data1, default_graph_options);
250
+
251
+
252
+ if (data2.length > 0)
253
+ $.plot($("#response_time"), data2, default_graph_options);
254
+ }
255
+
256
+ plotAccordingToChoices();
257
+ $.plot($("#max_request_rate"), max_request_rate_datasets, $.merge({series: {lines: {show: false}, bars : {show: true}}}, default_graph_options));
258
+ </script>
259
+ </body>
260
+ </html>