wvanbergen-request-log-analyzer 1.2.0 → 1.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) hide show
  1. data/{DESIGN → DESIGN.rdoc} +13 -10
  2. data/README.rdoc +7 -5
  3. data/RELEASE_NOTES.rdoc +10 -0
  4. data/bin/request-log-analyzer +7 -3
  5. data/lib/cli/tools.rb +2 -2
  6. data/lib/request_log_analyzer/aggregator/summarizer.rb +51 -2
  7. data/lib/request_log_analyzer/controller.rb +4 -0
  8. data/lib/request_log_analyzer/output/fixed_width.rb +36 -1
  9. data/lib/request_log_analyzer/output/html.rb +20 -2
  10. data/lib/request_log_analyzer/output.rb +36 -1
  11. data/lib/request_log_analyzer/request.rb +2 -2
  12. data/lib/request_log_analyzer/source/database.rb +19 -4
  13. data/lib/request_log_analyzer/source/log_parser.rb +26 -3
  14. data/lib/request_log_analyzer/tracker/duration.rb +49 -8
  15. data/lib/request_log_analyzer/tracker/frequency.rb +30 -7
  16. data/lib/request_log_analyzer/tracker/hourly_spread.rb +41 -15
  17. data/lib/request_log_analyzer/tracker/timespan.rb +22 -2
  18. data/lib/request_log_analyzer/tracker.rb +38 -1
  19. data/lib/request_log_analyzer.rb +2 -4
  20. data/spec/fixtures/decompression.log +12 -0
  21. data/spec/fixtures/decompression.log.bz2 +0 -0
  22. data/spec/fixtures/decompression.log.gz +0 -0
  23. data/spec/fixtures/decompression.log.zip +0 -0
  24. data/spec/fixtures/decompression.tar.gz +0 -0
  25. data/spec/fixtures/decompression.tgz +0 -0
  26. data/spec/integration/command_line_usage_spec.rb +14 -14
  27. data/spec/lib/helper.rb +19 -3
  28. data/spec/lib/testing_format.rb +1 -0
  29. data/spec/spec_helper.rb +2 -0
  30. data/spec/unit/aggregator/summarizer_spec.rb +1 -1
  31. data/spec/unit/source/log_parser_spec.rb +39 -0
  32. data/spec/unit/tracker/{tracker_api_test.rb → tracker_api_spec.rb} +7 -1
  33. data/tasks/github-gem.rake +1 -2
  34. data/tasks/request_log_analyzer.rake +23 -7
  35. metadata +12 -5
  36. data/HACKING +0 -7
@@ -1,38 +1,41 @@
1
- Request-log-analyzer is set up like a simple pipe and filter system.
1
+ === Request-log-analyzer
2
+ RLA is set up like a simple pipe and filter system.
2
3
 
3
4
  This allows you to easily add extra reports, filters and outputs.
4
-
5
- 1) Build pipeline.
6
5
  -> Aggregator (database)
7
6
  Source -> Filter -> Filter -> Aggregator (summary report) -> Output
8
7
  -> Aggregator (...)
9
-
10
- 2) Start chunk producer and push chunks through pipeline.
11
- Controller.start
12
-
8
+
9
+ When the pipeline has been constructed, we Start chunk producer (source) and push requests through pipeline.
10
+
11
+ Controller.start
12
+
13
+ === Source
13
14
  RequestLogAnalyzer::Source is an Object that pushes requests into the chain.
14
15
  At the moment you can only use the log-parser as a source.
15
16
  It accepts files or stdin and can parse then into request objects using a RequestLogAnalyzer::FileFormat definition.
16
17
  In the future we want to be able to have a generated request database as source as this will make interactive
17
18
  down drilling possible.
18
19
 
20
+ === Filter
19
21
  The filters are all subclasses of the RequestLogAnalyzer::Filter class.
20
22
  They accept a request object, manipulate or drop it, and then pass the request object on to the next filter
21
23
  in the chain.
22
24
  At the moment there are three types of filters available: Anonymize, Field and Timespan.
23
25
 
26
+ === Aggregator
24
27
  The Aggregators all inherit from the RequestLogAnalyzer::Aggregator class.
25
28
  All the requests that come out of the Filterchain are fed into all the aggregators in parallel.
26
29
  These aggregators can do anything what they want with the given request.
27
30
  For example: the Database aggregator will just store all the requests into a SQLite database while the Summarizer will
28
31
  generate a wide range of statistical reports from them.
29
32
 
30
- 3) Gather output from pipeline.
31
- Controller.report
32
-
33
+ === Running the pipeline
33
34
  All Aggregators are asked to report what they have done. For example the database will report: I stuffed x requests
34
35
  into SQLite database Y. The Summarizer will output its reports.
35
36
 
37
+ Controller.report
38
+
36
39
  The output is pushed to a RequestLogAnalyzer::Output object, which takes care of the output.
37
40
  It can generate either ASCII, UTF8 or even HTML output.
38
41
 
data/README.rdoc CHANGED
@@ -3,11 +3,10 @@
3
3
  This is a simple command line tool to analyze request log files of both Rails and
4
4
  Merb to produce a performance report. Its purpose is to find what actions are best candidates for optimization.
5
5
 
6
- * Analyzes Rails log files (all versions), Merb logs, or any other log format
7
- * Can combine multiple files (handy if you are using logrotate)
6
+ * Analyzes Rails log files (all versions), Merb logs, or any other log format you specify
7
+ * Combines multiple files and decompresses compressed files (handy if you are using logrotate)
8
8
  * Uses several metrics, including cumulative request time, average request time, process blockers, database and rendering time, HTTP methods and statuses, Rails action cache statistics, etc.) (Sample output: http://wiki.github.com/wvanbergen/request-log-analyzer/sample-output)
9
- * Low memory footprint (server-safe)
10
- * Fast
9
+ * Low memory footprint and reasonably fast (server-safe)
11
10
  * MIT licensed
12
11
 
13
12
  Request log analyzer was designed and built by Willem van Bergen and Bart ten Brinke.
@@ -36,7 +35,10 @@ For more details and available command line options, see the project's wiki: htt
36
35
 
37
36
  == Additional information
38
37
 
38
+ Do you have a rails application that is not performing as it should?
39
+ If you need an expert to analyze your application, feel free to contact either Willem van Bergen (willem@railsdoctors.com) or Bart ten Brinke (bart@railsdoctors.com).
40
+
39
41
  * Project wiki at GitHub: http://wiki.github.com/wvanbergen/request-log-analyzer
40
- * RDoc documentation: http://wvanbergen.github.com/request-log-analyzer
42
+ * railsdoctors homepage: http://railsdoctors.com
41
43
  * wvanbergen's blog posts: http://techblog.floorplanner.com/tag/request-log-analyzer
42
44
  * barttenbrinke's blog posts: http://movesonrails.com/articles/tag/analyzer
@@ -0,0 +1,10 @@
1
+ === 1.2.1
2
+ * Compressed logfile support
3
+ * Parsable YAML results as output (use --dump <filename> )
4
+ * Full Rdoc
5
+ * Release notes
6
+ * Updated rails take tasks
7
+
8
+ === 1.2.0
9
+ * Ruby 1.9 support
10
+ * Rcov coverage
@@ -17,13 +17,14 @@ begin
17
17
  strip.option(:format, :alias => :f, :default => 'rails')
18
18
  strip.option(:output, :alias => :o)
19
19
  strip.switch(:discard_teaser_lines, :t)
20
- strip.switch(:keep_junk_lines, :j)
20
+ strip.switch(:keep_junk_lines, :j)
21
21
  end
22
22
 
23
23
  command_line.option(:format, :alias => :f, :default => 'rails')
24
24
  command_line.option(:file, :alias => :e)
25
25
  command_line.option(:parse_strategy, :default => 'assume-correct')
26
-
26
+ command_line.option(:dump)
27
+
27
28
  command_line.option(:aggregator, :alias => :a, :multiple => true)
28
29
  command_line.option(:database, :alias => :d)
29
30
 
@@ -44,6 +45,8 @@ begin
44
45
 
45
46
  rescue CommandLine::Error => e
46
47
  puts "Request-log-analyzer, by Willem van Bergen and Bart ten Brinke - version #{RequestLogAnalyzer::VERSION}"
48
+ puts "Website: http://railsdoctors.com"
49
+ puts
47
50
  puts "ARGUMENT ERROR: " + e.message if e.message
48
51
  puts
49
52
  puts "Usage: request-log-analyzer [LOGFILES*] <OPTIONS>"
@@ -61,6 +64,7 @@ rescue CommandLine::Error => e
61
64
  puts " --debug Print debug information while parsing."
62
65
  puts " --file <filename> Output to file."
63
66
  puts " --output <format> Output format. Supports 'HTML' and 'FixedWidth' (default)"
67
+ puts " --dump <filename> Dump the YAML formatted results in the given file"
64
68
  puts
65
69
  puts "Examples:"
66
70
  puts " request-log-analyzer development.log"
@@ -82,7 +86,7 @@ when :strip
82
86
  RequestLogAnalyzer::LogProcessor.build(:strip, arguments).run!
83
87
  else
84
88
  puts "Request-log-analyzer, by Willem van Bergen and Bart ten Brinke - version #{RequestLogAnalyzer::VERSION}"
85
- puts "Website: http://github.com/wvanbergen/request-log-analyzer"
89
+ puts "Website: http://railsdoctors.com"
86
90
  puts
87
91
 
88
92
  # Run the request_log_analyzer!
data/lib/cli/tools.rb CHANGED
@@ -31,9 +31,9 @@ def install_rake_tasks(install_type = :rails)
31
31
  if install_type.to_sym == :rails
32
32
  require 'ftools'
33
33
  if File.directory?('./lib/tasks/')
34
- File.copy(File.dirname(__FILE__) + '/../tasks/request_log_analyzer.rake', './lib/tasks/request_log_analyze.rake')
34
+ File.copy(File.dirname(__FILE__) + '/../../tasks/request_log_analyzer.rake', './lib/tasks/request_log_analyze.rake')
35
35
  puts "Installed rake tasks."
36
- puts "To use, run: rake log:analyze"
36
+ puts "To use, run: rake rla:report"
37
37
  else
38
38
  puts "Cannot find /lib/tasks folder. Are you in your Rails directory?"
39
39
  puts "Installation aborted."
@@ -5,23 +5,31 @@ module RequestLogAnalyzer::Aggregator
5
5
  class Definer
6
6
 
7
7
  attr_reader :trackers
8
-
8
+
9
+ # Initialize tracker array
9
10
  def initialize
10
11
  @trackers = []
11
12
  end
12
13
 
14
+ # Initialize tracker summarizer by duping the trackers of another summarizer
15
+ # <tt>other</tt> The other Summarizer
13
16
  def initialize_copy(other)
14
17
  @trackers = other.trackers.dup
15
18
  end
16
19
 
20
+ # Drop all trackers
17
21
  def reset!
18
22
  @trackers = []
19
23
  end
20
24
 
25
+ # Include missing trackers through method missing.
21
26
  def method_missing(tracker_method, *args)
22
27
  track(tracker_method, args.first)
23
28
  end
24
29
 
30
+ # Track the frequency of a specific category
31
+ # <tt>category_field</tt> Field to track
32
+ # <tt>options</tt> options are passed to new frequency tracker
25
33
  def frequency(category_field, options = {})
26
34
  if category_field.kind_of?(Symbol)
27
35
  track(:frequency, options.merge(:category => category_field))
@@ -30,6 +38,9 @@ module RequestLogAnalyzer::Aggregator
30
38
  end
31
39
  end
32
40
 
41
+ # Track the duration of a specific category
42
+ # <tt>duration_field</tt> Field to track
43
+ # <tt>options</tt> options are passed to new frequency tracker
33
44
  def duration(duration_field, options = {})
34
45
  if duration_field.kind_of?(Symbol)
35
46
  track(:duration, options.merge(:duration => duration_field))
@@ -38,6 +49,9 @@ module RequestLogAnalyzer::Aggregator
38
49
  end
39
50
  end
40
51
 
52
+ # Helper function to initialize a tracker and add it to the tracker array.
53
+ # <tt>tracker_class</tt> The class to include
54
+ # <tt>optiont</tt> The options to pass to the trackers.
41
55
  def track(tracker_klass, options = {})
42
56
  tracker_klass = RequestLogAnalyzer::Tracker.const_get(RequestLogAnalyzer::to_camelcase(tracker_klass)) if tracker_klass.kind_of?(Symbol)
43
57
  @trackers << tracker_klass.new(options)
@@ -47,6 +61,8 @@ module RequestLogAnalyzer::Aggregator
47
61
  attr_reader :trackers
48
62
  attr_reader :warnings_encountered
49
63
 
64
+ # Initialize summarizer.
65
+ # Generate trackers from speciefied source.file_format.report_trackers and set them up
50
66
  def initialize(source, options = {})
51
67
  super(source, options)
52
68
  @warnings_encountered = {}
@@ -57,21 +73,44 @@ module RequestLogAnalyzer::Aggregator
57
73
  def setup
58
74
  end
59
75
 
76
+ # Call prepare on all trackers.
60
77
  def prepare
61
78
  raise "No trackers set up in Summarizer!" if @trackers.nil? || @trackers.empty?
62
79
  @trackers.each { |tracker| tracker.prepare }
63
80
  end
64
81
 
82
+ # Pass all requests to trackers and let them update if necessary.
83
+ # <tt>request</tt> The request to pass.
65
84
  def aggregate(request)
66
85
  @trackers.each do |tracker|
67
86
  tracker.update(request) if tracker.should_update?(request)
68
87
  end
69
88
  end
70
89
 
90
+ # Call finalize on all trackers. Saves a YAML dump if this is set in the options.
71
91
  def finalize
72
92
  @trackers.each { |tracker| tracker.finalize }
93
+ save_results_dump(options[:dump]) if options[:dump]
94
+ end
95
+
96
+ # Saves the results of all the trackers in YAML format to a file.
97
+ # <tt>filename</tt> The file to store the YAML dump in.
98
+ def save_results_dump(filename)
99
+ File.open(filename, 'w') { |file| file.write(to_yaml) }
100
+ end
101
+
102
+ # Exports all the tracker results to YAML. It will call the to_yaml_object method
103
+ # for every tracker and combines these into a single YAML export.
104
+ def to_yaml
105
+ require 'yaml'
106
+ trackers_export = @trackers.inject({}) do |export, tracker|
107
+ export[tracker.title] = tracker.to_yaml_object; export
108
+ end
109
+ YAML::dump(trackers_export)
73
110
  end
74
111
 
112
+ # Call report on all trackers.
113
+ # <tt>output</tt> RequestLogAnalyzer::Output object to output to
75
114
  def report(output)
76
115
  report_header(output)
77
116
  if source.parsed_requests > 0
@@ -83,6 +122,8 @@ module RequestLogAnalyzer::Aggregator
83
122
  report_footer(output)
84
123
  end
85
124
 
125
+ # Generate report header.
126
+ # <tt>output</tt> RequestLogAnalyzer::Output object to output to
86
127
  def report_header(output)
87
128
  output.title("Request summary")
88
129
 
@@ -98,6 +139,8 @@ module RequestLogAnalyzer::Aggregator
98
139
  output << "\n"
99
140
  end
100
141
 
142
+ # Generate report footer.
143
+ # <tt>output</tt> RequestLogAnalyzer::Output object to output to
101
144
  def report_footer(output)
102
145
  if has_log_ordering_warnings?
103
146
  output.title("Parse warnings")
@@ -110,14 +153,20 @@ module RequestLogAnalyzer::Aggregator
110
153
  end
111
154
  end
112
155
 
156
+ # Returns true if there were any warnings generated by the trackers
113
157
  def has_warnings?
114
- @warnings_encountered.inject(0) { |result, (key, value)| result += value } > 0
158
+ @warnings_encountered.inject(0) { |result, (key, value)| result += value } > 0
115
159
  end
116
160
 
161
+ # Returns true if there were any log ordering warnings
117
162
  def has_log_ordering_warnings?
118
163
  @warnings_encountered[:no_current_request] && @warnings_encountered[:no_current_request] > 0
119
164
  end
120
165
 
166
+ # Store an encountered warning
167
+ # <tt>type</tt> Type of warning
168
+ # <tt>message</tt> Warning message
169
+ # <tt>lineno</tt> The line on which the error was encountered
121
170
  def warning(type, message, lineno)
122
171
  @warnings_encountered[type] ||= 0
123
172
  @warnings_encountered[type] += 1
@@ -34,6 +34,7 @@ module RequestLogAnalyzer
34
34
 
35
35
  options[:database] = arguments[:database] if arguments[:database]
36
36
  options[:debug] = arguments[:debug]
37
+ options[:dump] = arguments[:dump]
37
38
 
38
39
  output_class = RequestLogAnalyzer::Output::const_get(arguments[:output])
39
40
  if arguments[:file]
@@ -63,6 +64,7 @@ module RequestLogAnalyzer
63
64
  end
64
65
 
65
66
  controller = Controller.new(RequestLogAnalyzer::Source::LogParser.new(file_format, options), options)
67
+ #controller = Controller.new(RequestLogAnalyzer::Source::Database.new(file_format, options), options)
66
68
 
67
69
  options[:parse_strategy] = arguments[:parse_strategy]
68
70
 
@@ -210,6 +212,8 @@ module RequestLogAnalyzer
210
212
  if @output.io.kind_of?(File)
211
213
  puts
212
214
  puts "Report written to: " + File.expand_path(@output.io.path)
215
+ puts "Need an expert to analyze your application?"
216
+ puts "Mail to contact@railsdoctors.com or visit us at http://railsdoctors.com"
213
217
  puts "Thanks for using request-log-analyzer!"
214
218
  @output.io.close
215
219
  end
@@ -5,17 +5,28 @@ module RequestLogAnalyzer::Output
5
5
  # Outputs a fixed width ASCII or UF8 report.
6
6
  class FixedWidth < Base
7
7
 
8
+ # Mixin module. Will disable any colorizing.
8
9
  module Monochrome
9
10
  def colorize(text, *options)
10
11
  text
11
12
  end
12
13
  end
13
14
 
15
+ # Colorize module
14
16
  module Color
15
17
 
16
18
  STYLES = { :normal => 0, :bold => 1, :underscore => 4, :blink => 5, :inverse => 7, :concealed => 8 }
17
19
  COLORS = { :black => 0, :blue => 4, :green => 2, :cyan => 6, :red => 1, :purple => 5, :brown => 3, :white => 7 }
18
20
 
21
+ # Colorize text
22
+ # <tt>text</tt> The text to colorize
23
+ # Options
24
+ # * <tt>:background</tt> The background color to paint. Defined in Color::COLORS
25
+ # * <tt>:color</tt> The foreground color to paint. Defined in Color::COLORS
26
+ # * <tt>:on</tt> Alias for :background
27
+ # * <tt>:style</tt> Font style, defined in Color::STYLES
28
+ #
29
+ # Returns ASCII colored string
19
30
  def colorize(text, *options)
20
31
 
21
32
  font_style = ''
@@ -49,6 +60,12 @@ module RequestLogAnalyzer::Output
49
60
  :utf => { :horizontal_line => '━', :vertical_line => '┃', :block => '░' }
50
61
  }
51
62
 
63
+ # Initialize a report
64
+ # <tt>io</tt> iO Object (file, STDOUT, etc.)
65
+ # <tt>options</tt>
66
+ # * <tt>:characters</tt> :utf for UTF8 or :ascii for ANSI compatible output. Defaults to :utf.
67
+ # * <tt>:color</tt> If true, ASCII colorization is used, else Monochrome. Defaults to Monochrome.
68
+ # * <tt>:width</tt> Output width in characters. Defaults to 80.
52
69
  def initialize(io, options = {})
53
70
  super(io, options)
54
71
  @options[:width] ||= 80
@@ -59,26 +76,36 @@ module RequestLogAnalyzer::Output
59
76
  (class << self; self; end).send(:include, color_module)
60
77
  end
61
78
 
79
+ # Write a string to the output object.
80
+ # <tt>str</tt> The string to write.
62
81
  def print(str)
63
82
  @io << str
64
83
  end
65
84
 
66
85
  alias :<< :print
67
86
 
87
+ # Write a string to the output object with a newline at the end.
88
+ # <tt>str</tt> The string to write.
68
89
  def puts(str = '')
69
90
  @io << str << "\n"
70
91
  end
71
92
 
93
+ # Write the title of a report
94
+ # <tt>title</tt> The title to write
72
95
  def title(title)
73
96
  puts
74
97
  puts colorize(title, :bold, :white)
75
98
  line(:green)
76
99
  end
77
100
 
101
+ # Write a line
78
102
  def line(*font)
79
103
  puts colorize(characters[:horizontal_line] * @options[:width], *font)
80
104
  end
81
-
105
+
106
+ # Write a link
107
+ # <tt>text</tt> The text in the link
108
+ # <tt>url</tt> The url to link to.
82
109
  def link(text, url = nil)
83
110
  if url.nil?
84
111
  colorize(text, :blue, :bold)
@@ -87,6 +114,7 @@ module RequestLogAnalyzer::Output
87
114
  end
88
115
  end
89
116
 
117
+ # Generate a header for a report
90
118
  def header
91
119
  if io.kind_of?(File)
92
120
  puts "Request-log-analyzer summary report"
@@ -96,11 +124,17 @@ module RequestLogAnalyzer::Output
96
124
  end
97
125
  end
98
126
 
127
+ # Generate a footer for a report
99
128
  def footer
100
129
  puts
130
+ puts "Need an expert to analyze your application?"
131
+ puts "Mail to contact@railsdoctors.com or visit us at http://railsdoctors.com"
101
132
  puts "Thanks for using request-log-analyzer!"
102
133
  end
103
134
 
135
+ # Generate a report table and push it into the output object.
136
+ # <tt>*colums<tt> Columns hash
137
+ # <tt>&block</tt>: A block yeilding the rows.
104
138
  def table(*columns, &block)
105
139
 
106
140
  rows = Array.new
@@ -150,6 +184,7 @@ module RequestLogAnalyzer::Output
150
184
  line(:green)
151
185
  end
152
186
 
187
+ # Print the rows
153
188
  rows.each do |row|
154
189
  row_values = []
155
190
  columns.each_with_index do |column, index|
@@ -7,29 +7,40 @@ module RequestLogAnalyzer::Output
7
7
  # super(io, options)
8
8
  # end
9
9
 
10
+ # Print a string to the io object.
10
11
  def print(str)
11
12
  @io << str
12
13
  end
13
14
 
14
15
  alias :<< :print
15
16
 
17
+ # Put a string with newline
16
18
  def puts(str = '')
17
19
  @io << str << "<br/>\n"
18
20
  end
19
21
 
22
+ # Place a title
20
23
  def title(title)
21
24
  @io.puts(tag(:h2, title))
22
25
  end
23
26
 
27
+ # Render a single line
28
+ # <tt>*font</tt> The font.
24
29
  def line(*font)
25
30
  @io.puts(tag(:hr))
26
31
  end
27
-
32
+
33
+ # Write a link
34
+ # <tt>text</tt> The text in the link
35
+ # <tt>url</tt> The url to link to.
28
36
  def link(text, url = nil)
29
37
  url = text if url.nil?
30
38
  tag(:a, text, :href => url)
31
39
  end
32
40
 
41
+ # Generate a report table in HTML and push it into the output object.
42
+ # <tt>*colums<tt> Columns hash
43
+ # <tt>&block</tt>: A block yeilding the rows.
33
44
  def table(*columns, &block)
34
45
  rows = Array.new
35
46
  yield(rows)
@@ -56,6 +67,7 @@ module RequestLogAnalyzer::Output
56
67
 
57
68
  end
58
69
 
70
+ # Genrate HTML header and associated stylesheet
59
71
  def header
60
72
  @io << "<html>"
61
73
  @io << tag(:head) do |headers|
@@ -127,14 +139,20 @@ module RequestLogAnalyzer::Output
127
139
  @io << tag(:p, "Version #{RequestLogAnalyzer::VERSION} - written by Willem van Bergen and Bart ten Brinke")
128
140
  end
129
141
 
142
+ # Generate a footer for a report
130
143
  def footer
131
144
  @io << tag(:hr) << tag(:h2, 'Thanks for using request-log-analyzer')
132
- @io << tag(:p, 'Please visit the ' + link('Request-log-analyzer website', 'http://github.com/wvanbergen/request-log-analyzer'))
145
+ @io << tag(:p, 'For more information please visit the ' + link('Request-log-analyzer website', 'http://github.com/wvanbergen/request-log-analyzer'))
146
+ @io << tag(:p, 'If you need an expert who can analyze your application, mail to ' + link('contact@railsdoctors.com', 'mailto:contact@railsdoctors.com') + ' or visit us at ' + link('http://railsdoctors.com', 'http://railsdoctors.com') + '.')
133
147
  @io << "</body></html>\n"
134
148
  end
135
149
 
136
150
  protected
137
151
 
152
+ # HTML tag writer helper
153
+ # <tt>tag</tt> The tag to generate
154
+ # <tt>content</tt> The content inside the tag
155
+ # <tt>attributes</tt> Attributes to write in the tag
138
156
  def tag(tag, content = nil, attributes = nil)
139
157
  if block_given?
140
158
  attributes = content.nil? ? '' : ' ' + content.map { |(key, value)| "#{key}=\"#{value}\"" }.join(' ')
@@ -1,19 +1,27 @@
1
+ # Module for generating output
1
2
  module RequestLogAnalyzer::Output
2
3
 
4
+ # Load class files if needed
3
5
  def self.const_missing(const)
4
6
  RequestLogAnalyzer::load_default_class_file(self, const)
5
7
  end
6
8
 
9
+ # Base Class used for generating output for reports.
10
+ # All output should inherit fromt this class.
7
11
  class Base
8
12
 
9
13
  attr_accessor :io, :options, :style
10
14
 
15
+ # Initialize a report
16
+ # <tt>io</tt> iO Object (file, STDOUT, etc.)
17
+ # <tt>options</tt> Specific style options
11
18
  def initialize(io, options = {})
12
19
  @io = io
13
20
  @options = options
14
21
  @style = options[:style] || { :cell_separator => true, :table_border => false }
15
22
  end
16
23
 
24
+ # Apply a style block.. with style :)
17
25
  def with_style(temp_style = {})
18
26
  old_style = @style
19
27
  @style = @style.merge(temp_style)
@@ -21,14 +29,41 @@ module RequestLogAnalyzer::Output
21
29
  @style = old_style
22
30
  end
23
31
 
32
+ # Generate a header for a report
24
33
  def header
25
34
  end
26
35
 
36
+ # Generate the footer of a report
27
37
  def footer
28
38
  end
39
+
40
+ # Generate a report table and push it into the output object.
41
+ # Yeilds a rows array into which the rows can be pushed
42
+ # <tt>*colums<tt> Array of Column hashes (see Column options).
43
+ # <tt>&block</tt>: A block yeilding the rows.
44
+ #
45
+ # === Column options
46
+ # Columns is an array of hashes containing the column definitions.
47
+ # * <tt>:align</tt> Alignment :left or :right
48
+ # * <tt>:treshold</tt> Width in characters or :rest
49
+ # * <tt>:type</tt> :ratio or nil
50
+ # * <tt>:width</tt> Width in characters or :rest
51
+ #
52
+ # === Example
53
+ # The output object should support table definitions:
54
+ #
55
+ # output.table({:align => :left}, {:align => :right }, {:align => :right}, {:type => :ratio, :width => :rest}) do |rows|
56
+ # sorted_frequencies.each do |(cat, count)|
57
+ # rows << [cat, "#{count} hits", '%0.1f%%' % ((count.to_f / total_hits.to_f) * 100.0), (count.to_f / total_hits.to_f)]
58
+ # end
59
+ # end
60
+ #
61
+ def table(*columns, &block)
62
+ end
29
63
 
30
64
  protected
31
-
65
+ # Check if a given table defination hash includes a header (title)
66
+ # <tt>columns</tt> The columns hash
32
67
  def table_has_header?(columns)
33
68
  columns.any? { |column| !column[:title].nil? }
34
69
  end
@@ -60,9 +60,9 @@ module RequestLogAnalyzer
60
60
 
61
61
  # Initializes a new Request object.
62
62
  # It will apply the the provided FileFormat module to this instance.
63
- def initialize(file_format)
63
+ def initialize(file_format, attributes = {})
64
64
  @lines = []
65
- @attributes = {}
65
+ @attributes = attributes
66
66
  register_file_format(file_format)
67
67
  end
68
68
 
@@ -4,12 +4,25 @@ require 'activerecord'
4
4
  module RequestLogAnalyzer::Source
5
5
 
6
6
  # Active Resource hook
7
- class CompletedLine < ActiveRecord::Base
7
+ class Request < ActiveRecord::Base
8
+ has_many :completed_lines
9
+ has_many :processing_lines
8
10
  def convert(file_format)
9
- RequestLogAnalyzer::Request.create(file_format, self.attributes)
11
+ send_attributes = self.attributes
12
+ send_attributes.merge!(self.completed_lines.first.attributes) if self.completed_lines.first
13
+ send_attributes.merge!(self.processing_lines.first.attributes) if self.processing_lines.first
14
+ return RequestLogAnalyzer::Request.new(file_format, send_attributes)
10
15
  end
11
16
  end
12
17
 
18
+ class CompletedLine < ActiveRecord::Base
19
+ belongs_to :request
20
+ end
21
+
22
+ class ProcessingLine < ActiveRecord::Base
23
+ belongs_to :request
24
+ end
25
+
13
26
  # The Database class gets log data from the database.
14
27
  class Database < Base
15
28
 
@@ -40,9 +53,11 @@ module RequestLogAnalyzer::Source
40
53
  ActiveRecord::Base.establish_connection(:adapter => 'sqlite3', :database => @source_files)
41
54
 
42
55
  @progress_handler.call(:started, @source_files) if @progress_handler
43
- RequestLogAnalyzer::Source::CompletedLine.find(:all).each do |request|
56
+ RequestLogAnalyzer::Source::Request.find(:all).each do |request|
44
57
  @parsed_requests += 1
45
- yield(request.convert(self.file_format))
58
+ @progress_handler.call(:progress, @parsed_requests) if @progress_handler
59
+
60
+ yield request.convert(self.file_format)
46
61
  end
47
62
 
48
63
  @progress_handler.call(:finished, @source_files) if @progress_handler