request-log-analyzer 1.0.4 → 1.1.0
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 +10 -0
- data/README.rdoc +3 -0
- data/bin/request-log-analyzer +3 -1
- data/lib/cli/progressbar.rb +182 -188
- data/lib/request_log_analyzer/aggregator/base.rb +1 -1
- data/lib/request_log_analyzer/aggregator/database.rb +4 -4
- data/lib/request_log_analyzer/aggregator/echo.rb +3 -5
- data/lib/request_log_analyzer/aggregator/summarizer.rb +26 -21
- data/lib/request_log_analyzer/controller.rb +18 -11
- data/lib/request_log_analyzer/filter/base.rb +3 -0
- data/lib/request_log_analyzer/filter/field.rb +5 -0
- data/lib/request_log_analyzer/filter/timespan.rb +8 -1
- data/lib/request_log_analyzer/output/fixed_width.rb +158 -0
- data/lib/request_log_analyzer/output/html.rb +80 -0
- data/lib/request_log_analyzer/output.rb +39 -0
- data/lib/request_log_analyzer/tracker/base.rb +1 -1
- data/lib/request_log_analyzer/tracker/category.rb +12 -21
- data/lib/request_log_analyzer/tracker/duration.rb +18 -23
- data/lib/request_log_analyzer/tracker/hourly_spread.rb +12 -22
- data/lib/request_log_analyzer/tracker/timespan.rb +10 -10
- data/lib/request_log_analyzer.rb +1 -1
- data/spec/controller_spec.rb +6 -2
- data/tasks/github-gem.rake +41 -7
- metadata +6 -3
- data/lib/cli/bashcolorizer.rb +0 -60
@@ -9,6 +9,7 @@ module RequestLogAnalyzer::Filter
|
|
9
9
|
|
10
10
|
attr_reader :field, :value, :mode
|
11
11
|
|
12
|
+
# Setup mode, field and value.
|
12
13
|
def prepare
|
13
14
|
@mode = (@options[:mode] || :accept).to_sym
|
14
15
|
@field = @options[:field].to_sym
|
@@ -21,6 +22,10 @@ module RequestLogAnalyzer::Filter
|
|
21
22
|
end
|
22
23
|
end
|
23
24
|
|
25
|
+
# Keep request if @mode == :select and request has the field and value.
|
26
|
+
# Drop request if @mode == :reject and request has the field and value.
|
27
|
+
# Returns nil otherwise.
|
28
|
+
# <tt>request</tt> Request Object
|
24
29
|
def filter(request)
|
25
30
|
return nil unless request
|
26
31
|
|
@@ -8,12 +8,19 @@ module RequestLogAnalyzer::Filter
|
|
8
8
|
|
9
9
|
attr_reader :before, :after
|
10
10
|
|
11
|
+
# Convert the timestamp to the correct formats for quick timestamp comparisons.
|
12
|
+
# These are stored in the before and after attr_reader fields.
|
11
13
|
def prepare
|
12
|
-
# Convert the timestamp to the correct formats for quick timestamp comparisons
|
13
14
|
@after = @options[:after].strftime('%Y%m%d%H%M%S').to_i if options[:after]
|
14
15
|
@before = @options[:before].strftime('%Y%m%d%H%M%S').to_i if options[:before]
|
15
16
|
end
|
16
17
|
|
18
|
+
# Returns request if:
|
19
|
+
# * @after <= request.timestamp <= @before
|
20
|
+
# * @after <= request.timestamp
|
21
|
+
# * request.timestamp <= @before
|
22
|
+
# Returns nil otherwise
|
23
|
+
# <tt>request</tt> Request object.
|
17
24
|
def filter(request)
|
18
25
|
return nil unless request
|
19
26
|
|
@@ -0,0 +1,158 @@
|
|
1
|
+
class RequestLogAnalyzer::Output::FixedWidth < RequestLogAnalyzer::Output
|
2
|
+
|
3
|
+
module Monochrome
|
4
|
+
def colorize(text, *options)
|
5
|
+
text
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
module Color
|
10
|
+
|
11
|
+
STYLES = { :normal => 0, :bold => 1, :underscore => 4, :blink => 5, :inverse => 7, :concealed => 8 }
|
12
|
+
COLORS = { :black => 0, :blue => 4, :green => 2, :cyan => 6, :red => 1, :purple => 5, :brown => 3, :white => 7 }
|
13
|
+
|
14
|
+
def colorize(text, *options)
|
15
|
+
|
16
|
+
font_style = ''
|
17
|
+
foreground_color = '0'
|
18
|
+
background_color = ''
|
19
|
+
|
20
|
+
options.each do |option|
|
21
|
+
if option.kind_of?(Symbol)
|
22
|
+
foreground_color = "3#{COLORS[option]}" if COLORS.include?(option)
|
23
|
+
font_style = "#{STYLES[option]};" if STYLES.include?(option)
|
24
|
+
elsif option.kind_of?(Hash)
|
25
|
+
options.each do |key, value|
|
26
|
+
case key
|
27
|
+
when :color; foreground_color = "3#{COLORS[value]}" if COLORS.include?(value)
|
28
|
+
when :background; background_color = "4#{COLORS[value]};" if COLORS.include?(value)
|
29
|
+
when :on; background_color = "4#{COLORS[value]};" if COLORS.include?(value)
|
30
|
+
when :style; font_style = "#{STYLES[value]};" if STYLES.include?(value)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
return "\e[#{background_color}#{font_style}#{foreground_color}m#{text}\e[0m"
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
attr_reader :characters
|
41
|
+
|
42
|
+
CHARACTERS = {
|
43
|
+
:ascii => { :horizontal_line => '-', :vertical_line => '|', :block => '=' },
|
44
|
+
:utf => { :horizontal_line => '━', :vertical_line => '┃', :block => '░' }
|
45
|
+
}
|
46
|
+
|
47
|
+
def initialize(io, options = {})
|
48
|
+
super(io, options)
|
49
|
+
@options[:width] ||= 80
|
50
|
+
@options[:characters] ||= :utf
|
51
|
+
@characters = CHARACTERS[@options[:characters]]
|
52
|
+
|
53
|
+
color_module = @options[:color] ? Color : Monochrome
|
54
|
+
(class << self; self; end).send(:include, color_module)
|
55
|
+
end
|
56
|
+
|
57
|
+
def print(str)
|
58
|
+
@io << str
|
59
|
+
end
|
60
|
+
|
61
|
+
alias :<< :print
|
62
|
+
|
63
|
+
def puts(str = '')
|
64
|
+
@io << str << "\n"
|
65
|
+
end
|
66
|
+
|
67
|
+
def title(title)
|
68
|
+
puts
|
69
|
+
puts colorize(title, :bold, :white)
|
70
|
+
line(:green)
|
71
|
+
end
|
72
|
+
|
73
|
+
def line(*font)
|
74
|
+
puts colorize(characters[:horizontal_line] * @options[:width], *font)
|
75
|
+
end
|
76
|
+
|
77
|
+
def link(text, url = nil)
|
78
|
+
if url.nil?
|
79
|
+
colorize(text, :blue, :bold)
|
80
|
+
else
|
81
|
+
"#{text} (#{colorize(url, :blue, :bold)})"
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def table(*columns, &block)
|
86
|
+
|
87
|
+
rows = Array.new
|
88
|
+
yield(rows)
|
89
|
+
|
90
|
+
# determine maximum cell widths
|
91
|
+
max_cell_widths = rows.inject(Array.new(columns.length, 0)) do |result, row|
|
92
|
+
lengths = row.map { |column| column.to_s.length }
|
93
|
+
result.each_with_index { |length, index| result[index] = ([length, lengths[index]].max rescue length) }
|
94
|
+
end
|
95
|
+
columns.each_with_index { |col, index| col[:actual_width] ||= max_cell_widths[index] }
|
96
|
+
|
97
|
+
# determine actual column width
|
98
|
+
column_widths = columns.map do |column|
|
99
|
+
if column[:width] == :rest
|
100
|
+
nil
|
101
|
+
elsif column[:width]
|
102
|
+
column[:width]
|
103
|
+
elsif column[:min_width]
|
104
|
+
[column[:min_width], column[:actual_width]].max
|
105
|
+
elsif column[:max_width]
|
106
|
+
[column[:max_width], column[:actual_width]].min
|
107
|
+
else
|
108
|
+
column[:actual_width]
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
if column_widths.include?(nil)
|
113
|
+
width_left = options[:width] - ((columns.length - 1) * (style[:cell_separator] ? 3 : 1)) - column_widths.compact.inject(0) { |sum, col| sum + col}
|
114
|
+
column_widths[column_widths.index(nil)] = width_left
|
115
|
+
end
|
116
|
+
|
117
|
+
# Print table header
|
118
|
+
if table_has_header?(columns)
|
119
|
+
column_titles = []
|
120
|
+
columns.each_with_index do |column, index|
|
121
|
+
width = column_widths[index]
|
122
|
+
alignment = (column[:align] == :right ? '' : '-')
|
123
|
+
column_titles.push(colorize("%#{alignment}#{width}s" % column[:title].to_s[0...width], :bold))
|
124
|
+
end
|
125
|
+
|
126
|
+
puts
|
127
|
+
puts column_titles.join(style[:cell_separator] ? " #{characters[:vertical_line]} " : ' ')
|
128
|
+
line(:green)
|
129
|
+
end
|
130
|
+
|
131
|
+
rows.each do |row|
|
132
|
+
row_values = []
|
133
|
+
columns.each_with_index do |column, index|
|
134
|
+
width = column_widths[index]
|
135
|
+
case column[:type]
|
136
|
+
when :ratio
|
137
|
+
if width > 4
|
138
|
+
if column[:treshold] && column[:treshold] < row[index].to_f
|
139
|
+
bar = ''
|
140
|
+
bar << characters[:block] * (width.to_f * column[:treshold]).round
|
141
|
+
bar << colorize(characters[:block] * (width.to_f * (row[index].to_f - column[:treshold])).round, :red)
|
142
|
+
row_values.push(bar)
|
143
|
+
else
|
144
|
+
row_values.push(characters[:block] * (width.to_f * row[index].to_f).round)
|
145
|
+
end
|
146
|
+
else
|
147
|
+
row_values.push('')
|
148
|
+
end
|
149
|
+
else
|
150
|
+
alignment = (columns[index][:align] == :right ? '' : '-')
|
151
|
+
row_values.push("%#{alignment}#{width}s" % row[index].to_s[0...width])
|
152
|
+
end
|
153
|
+
end
|
154
|
+
puts row_values.join(style[:cell_separator] ? " #{characters[:vertical_line]} " : ' ')
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
class RequestLogAnalyzer::Output::HTML < RequestLogAnalyzer::Output
|
2
|
+
|
3
|
+
# def initialize(io, options = {})
|
4
|
+
# super(io, options)
|
5
|
+
# end
|
6
|
+
|
7
|
+
def print(str)
|
8
|
+
@io << str
|
9
|
+
end
|
10
|
+
|
11
|
+
alias :<< :print
|
12
|
+
|
13
|
+
def puts(str = '')
|
14
|
+
@io << str << "<br />\n"
|
15
|
+
end
|
16
|
+
|
17
|
+
def title(title)
|
18
|
+
@io.puts(tag(:h2, title))
|
19
|
+
end
|
20
|
+
|
21
|
+
def line(*font)
|
22
|
+
@io.puts(tag(:hr))
|
23
|
+
end
|
24
|
+
|
25
|
+
def link(text, url = nil)
|
26
|
+
url = text if url.nil?
|
27
|
+
tag(:a, text, :href => url)
|
28
|
+
end
|
29
|
+
|
30
|
+
def table(*columns, &block)
|
31
|
+
rows = Array.new
|
32
|
+
yield(rows)
|
33
|
+
|
34
|
+
@io << tag(:table) do |content|
|
35
|
+
if table_has_header?(columns)
|
36
|
+
content << tag(:tr) do
|
37
|
+
columns.map { |col| tag(:th, col[:title]) }.join("\n")
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
rows.each do |row|
|
42
|
+
content << tag(:tr) do
|
43
|
+
row.map { |cell| tag(:td, cell) }.join("\n")
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
def header
|
51
|
+
@io << "<html>"
|
52
|
+
@io << tag(:head) do |headers|
|
53
|
+
headers << tag(:title, 'Request-log-analyzer report')
|
54
|
+
end
|
55
|
+
@io << '<body>'
|
56
|
+
end
|
57
|
+
|
58
|
+
def footer
|
59
|
+
@io << "</body></html>\n"
|
60
|
+
end
|
61
|
+
|
62
|
+
protected
|
63
|
+
|
64
|
+
def tag(tag, content = nil, attributes = nil)
|
65
|
+
if block_given?
|
66
|
+
attributes = content.nil? ? '' : ' ' + content.map { |(key, value)| "#{key}=\"#{value}\"" }.join(' ')
|
67
|
+
content_string = ''
|
68
|
+
content = yield(content_string)
|
69
|
+
content = content_string unless content_string.empty?
|
70
|
+
"<#{tag}#{attributes}>#{content}</#{tag}>"
|
71
|
+
else
|
72
|
+
attributes = attributes.nil? ? '' : ' ' + attributes.map { |(key, value)| "#{key}=\"#{value}\"" }.join(' ')
|
73
|
+
if content.nil?
|
74
|
+
"<#{tag}#{attributes} />"
|
75
|
+
else
|
76
|
+
"<#{tag}#{attributes}>#{content}</#{tag}>"
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module RequestLogAnalyzer
|
2
|
+
|
3
|
+
class Output
|
4
|
+
|
5
|
+
attr_accessor :io, :options, :style
|
6
|
+
|
7
|
+
def self.const_missing(const)
|
8
|
+
filename = const.to_s.gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').gsub(/([a-z\d])([A-Z])/,'\1_\2').tr("-", "_").downcase
|
9
|
+
require File.dirname(__FILE__) + '/output/' + filename
|
10
|
+
self.const_get(const)
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize(io, options = {})
|
14
|
+
@io = io
|
15
|
+
@options = options
|
16
|
+
@style = options[:style] || { :cell_separator => true, :table_border => false }
|
17
|
+
end
|
18
|
+
|
19
|
+
def with_style(temp_style = {})
|
20
|
+
old_style = @style
|
21
|
+
@style = @style.merge(temp_style)
|
22
|
+
yield(self) if block_given?
|
23
|
+
@style = old_style
|
24
|
+
end
|
25
|
+
|
26
|
+
def header
|
27
|
+
end
|
28
|
+
|
29
|
+
def footer
|
30
|
+
end
|
31
|
+
|
32
|
+
protected
|
33
|
+
|
34
|
+
def table_has_header?(columns)
|
35
|
+
columns.any? { |column| !column[:title].nil? }
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
end
|
@@ -14,11 +14,11 @@ module RequestLogAnalyzer::Tracker
|
|
14
14
|
#
|
15
15
|
# Example output:
|
16
16
|
# HTTP methods
|
17
|
-
#
|
18
|
-
# GET
|
19
|
-
# PUT
|
20
|
-
# POST
|
21
|
-
# DELETE
|
17
|
+
# ----------------------------------------------------------------------
|
18
|
+
# GET | 22248 hits (46.2%) |░░░░░░░░░░░░░░░░░
|
19
|
+
# PUT | 13685 hits (28.4%) |░░░░░░░░░░░
|
20
|
+
# POST | 11662 hits (24.2%) |░░░░░░░░░
|
21
|
+
# DELETE | 512 hits (1.1%) |
|
22
22
|
class Category < RequestLogAnalyzer::Tracker::Base
|
23
23
|
|
24
24
|
attr_reader :categories
|
@@ -39,12 +39,9 @@ module RequestLogAnalyzer::Tracker
|
|
39
39
|
end
|
40
40
|
end
|
41
41
|
|
42
|
-
def report(output
|
43
|
-
if options[:title]
|
44
|
-
|
45
|
-
output << green(('━' * report_width), color) + "\n"
|
46
|
-
end
|
47
|
-
|
42
|
+
def report(output)
|
43
|
+
output.title(options[:title]) if options[:title]
|
44
|
+
|
48
45
|
if @categories.empty?
|
49
46
|
output << "None found.\n"
|
50
47
|
else
|
@@ -52,18 +49,12 @@ module RequestLogAnalyzer::Tracker
|
|
52
49
|
total_hits = sorted_categories.inject(0) { |carry, item| carry + item[1] }
|
53
50
|
sorted_categories = sorted_categories.slice(0...options[:amount]) if options[:amount]
|
54
51
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
text = "%-#{max_cat_length+1}s┃%7d hits %s" % [cat.to_s[0..max_cat_length], count, (green("(%0.01f%%)", color) % [(count.to_f / total_hits) * 100])]
|
59
|
-
space_left = report_width - (max_cat_length + adjuster + 3)
|
60
|
-
if space_left > 3
|
61
|
-
bar_chars = (space_left * (count.to_f / total_hits)).round
|
62
|
-
output << "%-#{max_cat_length + adjuster}s %s%s" % [text, '┃', '░' * bar_chars] + "\n"
|
63
|
-
else
|
64
|
-
output << text + "\n"
|
52
|
+
output.table({:align => :left}, {:align => :right }, {:align => :right}, {:type => :ratio, :width => :rest}) do |rows|
|
53
|
+
sorted_categories.each do |(cat, count)|
|
54
|
+
rows << [cat, "#{count} hits", '%0.1f%%' % ((count.to_f / total_hits.to_f) * 100.0), (count.to_f / total_hits.to_f)]
|
65
55
|
end
|
66
56
|
end
|
57
|
+
|
67
58
|
end
|
68
59
|
end
|
69
60
|
|
@@ -2,7 +2,7 @@ module RequestLogAnalyzer::Tracker
|
|
2
2
|
|
3
3
|
# Analyze the duration of a specific attribute
|
4
4
|
#
|
5
|
-
#
|
5
|
+
# Options:
|
6
6
|
# * <tt>:line_type</tt> The line type that contains the duration field (determined by the category proc).
|
7
7
|
# * <tt>:if</tt> Proc that has to return !nil for a request to be passed to the tracker.
|
8
8
|
# * <tt>:title</tt> Title do be displayed above the report
|
@@ -13,11 +13,11 @@ module RequestLogAnalyzer::Tracker
|
|
13
13
|
# The items in the update request hash are set during the creation of the Duration tracker.
|
14
14
|
#
|
15
15
|
# Example output:
|
16
|
-
# Request duration - top 20 by cumulative time
|
17
|
-
#
|
18
|
-
# EmployeeController#show.html [GET]
|
19
|
-
# EmployeeController#update.html [POST]
|
20
|
-
# EmployeeController#index.html [GET]
|
16
|
+
# Request duration - top 20 by cumulative time | Hits | Sum. | Avg.
|
17
|
+
# ---------------------------------------------------------------------------------
|
18
|
+
# EmployeeController#show.html [GET] | 4742 | 4922.56s | 1.04s
|
19
|
+
# EmployeeController#update.html [POST] | 4647 | 2731.23s | 0.59s
|
20
|
+
# EmployeeController#index.html [GET] | 5802 | 1477.32s | 0.25s
|
21
21
|
# .............
|
22
22
|
class Duration < RequestLogAnalyzer::Tracker::Base
|
23
23
|
attr_reader :categories
|
@@ -40,25 +40,20 @@ module RequestLogAnalyzer::Tracker
|
|
40
40
|
end
|
41
41
|
end
|
42
42
|
|
43
|
-
def report_table(output
|
43
|
+
def report_table(output, amount = 10, options = {}, &block)
|
44
44
|
|
45
45
|
top_categories = @categories.sort { |a, b| yield(b[1]) <=> yield(a[1]) }.slice(0...amount)
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
top_categories.each do |(cat, info)|
|
54
|
-
hits = info[:count]
|
55
|
-
total = "%0.02f" % info[:total_duration]
|
56
|
-
avg = "%0.02f" % (info[:total_duration] / info[:count])
|
57
|
-
output << "%-#{space_left+1}s┃%8d ┃%9ss ┃%9ss" % [cat[0...space_left], hits, total, avg] + "\n"
|
46
|
+
output.table({:title => options[:title]}, {:title => 'Hits', :align => :right, :min_width => 4},
|
47
|
+
{:title => 'Cumulative', :align => :right, :min_width => 10}, {:title => 'Average', :align => :right, :min_width => 8}) do |rows|
|
48
|
+
|
49
|
+
top_categories.each do |(cat, info)|
|
50
|
+
rows << [cat, info[:count], "%0.02fs" % info[:total_duration], "%0.02fs" % (info[:total_duration] / info[:count])]
|
51
|
+
end
|
58
52
|
end
|
53
|
+
|
59
54
|
end
|
60
55
|
|
61
|
-
def report(output
|
56
|
+
def report(output)
|
62
57
|
|
63
58
|
options[:title] ||= 'Request duration'
|
64
59
|
options[:report] ||= [:total, :average]
|
@@ -67,11 +62,11 @@ module RequestLogAnalyzer::Tracker
|
|
67
62
|
options[:report].each do |report|
|
68
63
|
case report
|
69
64
|
when :average
|
70
|
-
report_table(output, options[:top], :title => "#{options[:title]} - top #{options[:top]} by average time"
|
65
|
+
report_table(output, options[:top], :title => "#{options[:title]} - top #{options[:top]} by average time") { |request| request[:total_duration] / request[:count] }
|
71
66
|
when :total
|
72
|
-
report_table(output, options[:top], :title => "#{options[:title]} - top #{options[:top]} by cumulative time"
|
67
|
+
report_table(output, options[:top], :title => "#{options[:title]} - top #{options[:top]} by cumulative time") { |request| request[:total_duration] }
|
73
68
|
when :hits
|
74
|
-
report_table(output, options[:top], :title => "#{options[:title]} - top #{options[:top]} by hits"
|
69
|
+
report_table(output, options[:top], :title => "#{options[:title]} - top #{options[:top]} by hits") { |request| request[:count] }
|
75
70
|
else
|
76
71
|
output << "Unknown duration report specified\n"
|
77
72
|
end
|
@@ -12,7 +12,7 @@ module RequestLogAnalyzer::Tracker
|
|
12
12
|
#
|
13
13
|
# Example output:
|
14
14
|
# Requests graph - average per day per hour
|
15
|
-
#
|
15
|
+
# --------------------------------------------------
|
16
16
|
# 7:00 - 330 hits : ░░░░░░░
|
17
17
|
# 8:00 - 704 hits : ░░░░░░░░░░░░░░░░░
|
18
18
|
# 9:00 - 830 hits : ░░░░░░░░░░░░░░░░░░░░
|
@@ -44,37 +44,27 @@ module RequestLogAnalyzer::Tracker
|
|
44
44
|
@last = timestamp if @last.nil? || timestamp > @last
|
45
45
|
end
|
46
46
|
|
47
|
-
def report(output
|
48
|
-
output
|
49
|
-
output << "Requests graph - average per day per hour\n"
|
50
|
-
output << green("━" * report_width, color) + "\n"
|
47
|
+
def report(output)
|
48
|
+
output.title("Requests graph - average per day per hour")
|
51
49
|
|
52
50
|
if @request_time_graph == [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
|
53
51
|
output << "None found.\n"
|
54
52
|
return
|
55
53
|
end
|
56
54
|
|
57
|
-
first_date
|
58
|
-
last_date
|
59
|
-
days
|
60
|
-
|
61
|
-
deviation = 1 if deviation == 0
|
62
|
-
color_cutoff = 15
|
55
|
+
first_date = DateTime.parse(@first.to_s, '%Y%m%d%H%M%S')
|
56
|
+
last_date = DateTime.parse(@last.to_s, '%Y%m%d%H%M%S')
|
57
|
+
days = (@last && @first) ? (last_date - first_date).ceil : 1
|
58
|
+
total_requests = @request_time_graph.inject(0) { |sum, value| sum + value }
|
63
59
|
|
64
|
-
|
65
|
-
|
66
|
-
|
60
|
+
output.table({}, {:align => :right}, {:type => :ratio, :width => :rest, :treshold => 0.15}) do |rows|
|
61
|
+
@request_time_graph.each_with_index do |requests, index|
|
62
|
+
ratio = requests.to_f / total_requests.to_f
|
63
|
+
requests_per_day = requests / days
|
67
64
|
|
68
|
-
|
69
|
-
display_chars_string = green(('░' * color_cutoff), color) + red(('░' * (display_chars - color_cutoff)), color)
|
70
|
-
else
|
71
|
-
display_chars_string = green(('░' * display_chars), color)
|
65
|
+
rows << ["#{index.to_s.rjust(3)}:00", "#{requests_per_day} hits", ratio]
|
72
66
|
end
|
73
|
-
|
74
|
-
output << "#{index.to_s.rjust(3)}:00 - #{(request_today.to_s + ' hits').ljust(15)} : #{display_chars_string}\n"
|
75
67
|
end
|
76
|
-
output << "\n"
|
77
|
-
|
78
68
|
end
|
79
69
|
end
|
80
70
|
end
|
@@ -31,23 +31,23 @@ module RequestLogAnalyzer::Tracker
|
|
31
31
|
@last = timestamp if @last.nil? || timestamp > @last
|
32
32
|
end
|
33
33
|
|
34
|
-
def report(output
|
35
|
-
if options[:title]
|
36
|
-
output << "\n#{options[:title]}\n"
|
37
|
-
output << green('━' * options[:title].length, color) + "\n"
|
38
|
-
end
|
34
|
+
def report(output)
|
35
|
+
output.title(options[:title]) if options[:title]
|
39
36
|
|
40
37
|
first_date = DateTime.parse(@first.to_s, '%Y%m%d%H%M%S') rescue nil
|
41
38
|
last_date = DateTime.parse(@last.to_s, '%Y%m%d%H%M%S') rescue nil
|
42
39
|
|
43
40
|
if @last && @first
|
44
41
|
days = (@last && @first) ? (last_date - first_date).ceil : 1
|
45
|
-
|
46
|
-
output
|
47
|
-
|
48
|
-
|
42
|
+
|
43
|
+
output.with_style(:cell_separator => false) do
|
44
|
+
output.table({:width => 20}, {}) do |rows|
|
45
|
+
rows << ['First request:', first_date.strftime('%Y-%m-%d %H:%M:%I')]
|
46
|
+
rows << ['Last request:', last_date.strftime('%Y-%m-%d %H:%M:%I')]
|
47
|
+
rows << ['Total time analyzed:', "#{days} days"]
|
48
|
+
end
|
49
|
+
end
|
49
50
|
end
|
50
|
-
output << "\n"
|
51
51
|
|
52
52
|
end
|
53
53
|
end
|
data/lib/request_log_analyzer.rb
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
require 'date'
|
2
2
|
require File.dirname(__FILE__) + '/cli/progressbar'
|
3
|
-
require File.dirname(__FILE__) + '/cli/bashcolorizer'
|
4
3
|
|
5
4
|
require File.dirname(__FILE__) + '/request_log_analyzer/file_format'
|
6
5
|
require File.dirname(__FILE__) + '/request_log_analyzer/line_definition'
|
@@ -12,3 +11,4 @@ require File.dirname(__FILE__) + '/request_log_analyzer/filter/base'
|
|
12
11
|
require File.dirname(__FILE__) + '/request_log_analyzer/controller'
|
13
12
|
require File.dirname(__FILE__) + '/request_log_analyzer/source/base'
|
14
13
|
require File.dirname(__FILE__) + '/request_log_analyzer/source/log_file'
|
14
|
+
require File.dirname(__FILE__) + '/request_log_analyzer/output'
|
data/spec/controller_spec.rb
CHANGED
@@ -10,9 +10,14 @@ describe RequestLogAnalyzer::Controller do
|
|
10
10
|
# end
|
11
11
|
|
12
12
|
it "should call the aggregators when run" do
|
13
|
+
|
14
|
+
mock_output = mock('output')
|
15
|
+
mock_output.should_receive(:header)
|
16
|
+
mock_output.should_receive(:footer)
|
17
|
+
|
13
18
|
file_format = RequestLogAnalyzer::FileFormat.load(:rails)
|
14
19
|
source = RequestLogAnalyzer::Source::LogFile.new(file_format, :source_files => log_fixture(:rails_1x))
|
15
|
-
controller = RequestLogAnalyzer::Controller.new(source)
|
20
|
+
controller = RequestLogAnalyzer::Controller.new(source, :output => mock_output)
|
16
21
|
|
17
22
|
mock_aggregator = mock('aggregator')
|
18
23
|
mock_aggregator.should_receive(:prepare).once.ordered
|
@@ -26,7 +31,6 @@ describe RequestLogAnalyzer::Controller do
|
|
26
31
|
another_mock_aggregator.should_receive(:finalize).once.ordered
|
27
32
|
another_mock_aggregator.should_receive(:report).once.ordered
|
28
33
|
|
29
|
-
|
30
34
|
controller.aggregators << mock_aggregator << another_mock_aggregator
|
31
35
|
controller.run!
|
32
36
|
end
|