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.
- data/{DESIGN → DESIGN.rdoc} +13 -10
- data/README.rdoc +7 -5
- data/RELEASE_NOTES.rdoc +10 -0
- data/bin/request-log-analyzer +7 -3
- data/lib/cli/tools.rb +2 -2
- data/lib/request_log_analyzer/aggregator/summarizer.rb +51 -2
- data/lib/request_log_analyzer/controller.rb +4 -0
- data/lib/request_log_analyzer/output/fixed_width.rb +36 -1
- data/lib/request_log_analyzer/output/html.rb +20 -2
- data/lib/request_log_analyzer/output.rb +36 -1
- data/lib/request_log_analyzer/request.rb +2 -2
- data/lib/request_log_analyzer/source/database.rb +19 -4
- data/lib/request_log_analyzer/source/log_parser.rb +26 -3
- data/lib/request_log_analyzer/tracker/duration.rb +49 -8
- data/lib/request_log_analyzer/tracker/frequency.rb +30 -7
- data/lib/request_log_analyzer/tracker/hourly_spread.rb +41 -15
- data/lib/request_log_analyzer/tracker/timespan.rb +22 -2
- data/lib/request_log_analyzer/tracker.rb +38 -1
- data/lib/request_log_analyzer.rb +2 -4
- data/spec/fixtures/decompression.log +12 -0
- data/spec/fixtures/decompression.log.bz2 +0 -0
- data/spec/fixtures/decompression.log.gz +0 -0
- data/spec/fixtures/decompression.log.zip +0 -0
- data/spec/fixtures/decompression.tar.gz +0 -0
- data/spec/fixtures/decompression.tgz +0 -0
- data/spec/integration/command_line_usage_spec.rb +14 -14
- data/spec/lib/helper.rb +19 -3
- data/spec/lib/testing_format.rb +1 -0
- data/spec/spec_helper.rb +2 -0
- data/spec/unit/aggregator/summarizer_spec.rb +1 -1
- data/spec/unit/source/log_parser_spec.rb +39 -0
- data/spec/unit/tracker/{tracker_api_test.rb → tracker_api_spec.rb} +7 -1
- data/tasks/github-gem.rake +1 -2
- data/tasks/request_log_analyzer.rake +23 -7
- metadata +16 -29
- data/HACKING +0 -7
data/{DESIGN → DESIGN.rdoc}
RENAMED
@@ -1,38 +1,41 @@
|
|
1
|
-
Request-log-analyzer
|
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
|
-
|
11
|
-
|
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
|
-
|
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
|
-
*
|
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
|
-
*
|
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
|
data/RELEASE_NOTES.rdoc
ADDED
data/bin/request-log-analyzer
CHANGED
@@ -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://
|
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__) + '
|
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
|
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
|
-
|
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, '
|
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
|
7
|
+
class Request < ActiveRecord::Base
|
8
|
+
has_many :completed_lines
|
9
|
+
has_many :processing_lines
|
8
10
|
def convert(file_format)
|
9
|
-
|
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::
|
56
|
+
RequestLogAnalyzer::Source::Request.find(:all).each do |request|
|
44
57
|
@parsed_requests += 1
|
45
|
-
|
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
|