request-log-analyzer 1.2.0 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|