evidence 0.0.4 → 0.0.5
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.
- checksums.yaml +6 -14
- data/examples/mingle_logs_analysis.rb +7 -10
- data/lib/evidence/lazy.rb +8 -0
- data/lib/evidence/rails_action_parser.rb +8 -10
- data/lib/evidence.rb +44 -25
- metadata +9 -10
- data/lib/evidence/log_parser.rb +0 -21
- data/lib/evidence/stream.rb +0 -195
checksums.yaml
CHANGED
@@ -1,15 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
metadata.gz: !binary |-
|
9
|
-
M2MyMjY3Y2U3Y2IxODRiM2M2NzczODdjYzAxYThiODUwNjNlNDNhZTEyNzUz
|
10
|
-
MjIyMjg0MGMyMTVmZDA1ZDRlNDQ5MDAyOTczMzFjYjU0OTBlMDBmYWE0NWU1
|
11
|
-
ZmE1YWU4MDllYWZmODAxYTUyMzIxMGIwZWIzNDc5NDkzZTQ5ZGE=
|
12
|
-
data.tar.gz: !binary |-
|
13
|
-
Y2Q1MWQwMmIwZjRlMjU3MzQyNTZjYzNjMGQ3NjAwZTAxZGNhNjYxMWYzNjI3
|
14
|
-
MWUxOTdlYTQxM2FmMjg3NTVjY2Y0NGNjOTVhM2FjMjU5OTQ3MjRjZGU0Yzg4
|
15
|
-
NzgwNjJlNjAzOGNkMmFjMTdhOWIwNmM5Zjk5Y2EzNGJmNzg0MjE=
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: f2314c07b4b1873ae89834bd97ac81be2f0c1783
|
4
|
+
data.tar.gz: b297e819e4f1792b05dc3a49dde97066378aed13
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 28d755cd6038e0c97bbee4ea1da7ed25940aaa8162887685a0da565b708c30cedcae3720ef0568d1d38fae1ee9560ce2a310fa01dafa3cc02c085b2f7745802d
|
7
|
+
data.tar.gz: 763e35c45812f677a1dc0448b80eab549396913cad12033dca7d5543ea822e63f0945ec746857460483c1dfbf4ebfe179f77bb3d561a7ebb97a7e34352665ff1
|
@@ -7,7 +7,7 @@ include Evidence
|
|
7
7
|
mingle_log_pattern = /^
|
8
8
|
\w{3}\s+\d+\s+\d{2}\:\d{2}\:\d{2}\s+
|
9
9
|
(?<host_name>[^\s]+)\s+
|
10
|
-
[\w
|
10
|
+
[-_\w]+\:\s+
|
11
11
|
INFO\s+
|
12
12
|
\[(?<timestamp>[^\]]+)\]\s+
|
13
13
|
\[(?<thread_label>[^\]]+)\]\s+
|
@@ -19,19 +19,16 @@ $/x
|
|
19
19
|
pid = lambda {|log| "#{log[:host_name]}-#{log[:thread_label]}"}
|
20
20
|
message = lambda {|log| log[:message]}
|
21
21
|
|
22
|
-
logs = Dir['./log
|
22
|
+
logs = Dir['./log/*'].sort.lazy.map { |f| File.new(f).lazy.map(&parse_log(mingle_log_pattern)).compact }.flat_map {|a| a}
|
23
23
|
time_window = (ARGV[0] || 60).to_i
|
24
24
|
puts "[DEBUG]logs.size => #{logs.size.inspect}"
|
25
25
|
puts "[DEBUG] time_window => #{time_window.inspect}"
|
26
26
|
|
27
|
-
merged_logs = merge_streams(logs, lambda {|log1, log2| log1[:timestamp] <=> log2[:timestamp]})
|
28
|
-
result = merged_logs | rails_action_parser(pid, message) | request_timestamp_parser | slice_stream(lambda {|action| action[:request][:timestamp]}, time_window) | littles_law_analysis
|
29
|
-
|
30
27
|
FileUtils.mkdir_p('out')
|
31
28
|
File.open('out/mingle_logs_littles_law_analysis', 'w') do |f|
|
32
|
-
|
33
|
-
t1 = range.min.strftime("%m-%d %H:%M")
|
34
|
-
t2 = range.max.strftime("%H:%M")
|
35
|
-
f.write("#{t1}-#{t2} #{
|
36
|
-
end
|
29
|
+
logs.map(&rails_action_parser(pid, message)).compact.map(&request_timestamp_parser).chunk(&by_time_window(time_window)).map(&littles_law_analysis).map do |stat|
|
30
|
+
t1 = stat[:range].min.strftime("%m-%d %H:%M")
|
31
|
+
t2 = stat[:range].max.strftime("%H:%M")
|
32
|
+
f.write("#{t1}-#{t2} #{stat[:value]}\n".tap{|r| puts r})
|
33
|
+
end.force
|
37
34
|
end
|
@@ -1,26 +1,24 @@
|
|
1
1
|
module Evidence
|
2
2
|
class RailsActionParser
|
3
3
|
|
4
|
-
def initialize(pid, message
|
4
|
+
def initialize(pid, message)
|
5
5
|
@pid, @message = pid, message
|
6
|
-
@
|
6
|
+
@processes = Hash.new
|
7
7
|
end
|
8
8
|
|
9
|
-
def
|
10
|
-
processes = Hash.new
|
9
|
+
def to_proc
|
11
10
|
lambda do |log|
|
12
11
|
pid = @pid[log]
|
13
|
-
if processes.has_key?(pid)
|
14
|
-
processes[pid] << log
|
12
|
+
if @processes.has_key?(pid)
|
13
|
+
@processes[pid] << log
|
15
14
|
if end_action?(@message[log])
|
16
|
-
|
15
|
+
parse_action_logs(@processes.delete(pid))
|
17
16
|
end
|
18
17
|
else
|
19
18
|
if start_action?(@message[log])
|
20
|
-
processes[pid] = [log]
|
21
|
-
else
|
22
|
-
@unmatched.call(log)
|
19
|
+
@processes[pid] = [log]
|
23
20
|
end
|
21
|
+
nil
|
24
22
|
end
|
25
23
|
end
|
26
24
|
end
|
data/lib/evidence.rb
CHANGED
@@ -1,51 +1,70 @@
|
|
1
|
-
require 'evidence/
|
2
|
-
require 'evidence/log_parser'
|
1
|
+
require 'evidence/lazy'
|
3
2
|
require 'evidence/rails_action_parser'
|
4
3
|
|
4
|
+
Enumerator::Lazy.send(:include, Evidence::Lazy)
|
5
|
+
|
5
6
|
module Evidence
|
6
7
|
module_function
|
7
8
|
|
8
9
|
# Parse log file stream by given pattern
|
9
10
|
# pattern: ruby regex expression, has named group specified
|
10
|
-
#
|
11
|
-
def
|
12
|
-
|
11
|
+
# example: logs.map(&parse_log(pattern)).compact
|
12
|
+
def parse_log(pattern)
|
13
|
+
lambda do |log|
|
14
|
+
if m = pattern.match(log)
|
15
|
+
Hash[m.names.map(&:to_sym).zip(m.captures)]
|
16
|
+
end
|
17
|
+
end
|
13
18
|
end
|
14
19
|
|
15
20
|
# Parse out rails actions by given:
|
16
21
|
# pid: a lambda returns process id used to group logs
|
17
22
|
# message: a lambda returns rails log string message
|
18
|
-
|
19
|
-
|
23
|
+
# example: logs.map(&rails_action_parser(pid, message)).compact
|
24
|
+
def rails_action_parser(pid, message)
|
25
|
+
RailsActionParser.new(pid, message)
|
20
26
|
end
|
21
27
|
|
22
28
|
# Rails action request timestamp parser
|
23
|
-
# log
|
29
|
+
# log.map(&rails_action_parser(pid, message)).compact.map(&request_timestamp_parser)
|
24
30
|
def request_timestamp_parser(format="%Y-%m-%d %H:%M:%S")
|
25
|
-
|
31
|
+
lambda do |action|
|
26
32
|
action[:request][:timestamp] = Time.strptime(action[:request][:timestamp], format)
|
33
|
+
action
|
27
34
|
end
|
28
35
|
end
|
29
36
|
|
30
|
-
#
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
memo[:sum] += action[:response][:completed_time].to_i
|
39
|
-
memo
|
40
|
-
end
|
41
|
-
avg_sec_arrival_rate = statistics[:count].to_f/(actions[:range].max - actions[:range].min)
|
42
|
-
avg_sec_response_time = statistics[:sum].to_f / statistics[:count] /1000
|
43
|
-
output[range: actions[:range], value: avg_sec_arrival_rate * avg_sec_response_time]
|
37
|
+
# actions.chunk(&time_window(60))
|
38
|
+
def by_time_window(time_window, start=nil)
|
39
|
+
range = nil
|
40
|
+
lambda do |ele|
|
41
|
+
start ||= ele[:request][:timestamp]
|
42
|
+
range ||= start..(start + time_window)
|
43
|
+
while(range.max <= ele[:request][:timestamp]) do
|
44
|
+
range = range.max..(range.max + time_window)
|
44
45
|
end
|
46
|
+
range
|
45
47
|
end
|
46
48
|
end
|
47
49
|
|
48
|
-
|
49
|
-
|
50
|
+
# Do the little's law analysis on rails actions stream
|
51
|
+
# usage example:
|
52
|
+
# rails_action_parser(pid, message).parse(parse_log(logs,
|
53
|
+
# pattern)).each(&request_timestamp_parser).chunk({start: nil},
|
54
|
+
# &time_window(60 seconds)).map do |range, actions|
|
55
|
+
# littles_law_analysis(range, actions)
|
56
|
+
# end
|
57
|
+
def littles_law_analysis
|
58
|
+
lambda do |args|
|
59
|
+
range, actions = args
|
60
|
+
statistics = actions.inject(sum: 0, count: 0) do |memo, action|
|
61
|
+
memo[:count] += 1
|
62
|
+
memo[:sum] += action[:response][:completed_time].to_i
|
63
|
+
memo
|
64
|
+
end
|
65
|
+
avg_sec_arrival_rate = statistics[:count].to_f/(range.max - range.min)
|
66
|
+
avg_sec_response_time = statistics[:sum].to_f / statistics[:count] /1000
|
67
|
+
{range: range, value: avg_sec_arrival_rate * avg_sec_response_time}
|
68
|
+
end
|
50
69
|
end
|
51
70
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: evidence
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Xiao Li
|
@@ -9,20 +9,20 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-08-
|
12
|
+
date: 2013-08-16 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rake
|
16
16
|
requirement: !ruby/object:Gem::Requirement
|
17
17
|
requirements:
|
18
|
-
- -
|
18
|
+
- - '>='
|
19
19
|
- !ruby/object:Gem::Version
|
20
20
|
version: '0'
|
21
21
|
type: :development
|
22
22
|
prerelease: false
|
23
23
|
version_requirements: !ruby/object:Gem::Requirement
|
24
24
|
requirements:
|
25
|
-
- -
|
25
|
+
- - '>='
|
26
26
|
- !ruby/object:Gem::Version
|
27
27
|
version: '0'
|
28
28
|
description:
|
@@ -34,9 +34,8 @@ extensions: []
|
|
34
34
|
extra_rdoc_files: []
|
35
35
|
files:
|
36
36
|
- README.md
|
37
|
-
- lib/evidence/
|
37
|
+
- lib/evidence/lazy.rb
|
38
38
|
- lib/evidence/rails_action_parser.rb
|
39
|
-
- lib/evidence/stream.rb
|
40
39
|
- lib/evidence.rb
|
41
40
|
- examples/mingle_logs_analysis.rb
|
42
41
|
homepage: https://github.com/ThoughtWorksStudios/evidence
|
@@ -49,17 +48,17 @@ require_paths:
|
|
49
48
|
- lib
|
50
49
|
required_ruby_version: !ruby/object:Gem::Requirement
|
51
50
|
requirements:
|
52
|
-
- -
|
51
|
+
- - '>='
|
53
52
|
- !ruby/object:Gem::Version
|
54
|
-
version:
|
53
|
+
version: 2.0.0
|
55
54
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
56
55
|
requirements:
|
57
|
-
- -
|
56
|
+
- - '>='
|
58
57
|
- !ruby/object:Gem::Version
|
59
58
|
version: '0'
|
60
59
|
requirements: []
|
61
60
|
rubyforge_project:
|
62
|
-
rubygems_version: 2.0.
|
61
|
+
rubygems_version: 2.0.0
|
63
62
|
signing_key:
|
64
63
|
specification_version: 4
|
65
64
|
summary: Log Analysis Tool
|
data/lib/evidence/log_parser.rb
DELETED
@@ -1,21 +0,0 @@
|
|
1
|
-
module Evidence
|
2
|
-
class LogParser
|
3
|
-
def initialize(pattern, unmatched)
|
4
|
-
@pattern, @unmatched = pattern, unmatched
|
5
|
-
end
|
6
|
-
|
7
|
-
def [](output)
|
8
|
-
lambda do |line|
|
9
|
-
if m = @pattern.match(line)
|
10
|
-
output.call(to_hash(m))
|
11
|
-
else
|
12
|
-
@unmatched.call(line)
|
13
|
-
end
|
14
|
-
end
|
15
|
-
end
|
16
|
-
|
17
|
-
def to_hash(m)
|
18
|
-
Hash[m.names.map(&:to_sym).zip(m.captures)]
|
19
|
-
end
|
20
|
-
end
|
21
|
-
end
|
data/lib/evidence/stream.rb
DELETED
@@ -1,195 +0,0 @@
|
|
1
|
-
module Evidence
|
2
|
-
module Stream
|
3
|
-
include Enumerable
|
4
|
-
|
5
|
-
def |(process)
|
6
|
-
case process
|
7
|
-
when SliceStream
|
8
|
-
process.slice(self)
|
9
|
-
else
|
10
|
-
PipeStream.new(self, process)
|
11
|
-
end
|
12
|
-
end
|
13
|
-
end
|
14
|
-
|
15
|
-
class PipeStream
|
16
|
-
include Stream
|
17
|
-
|
18
|
-
def initialize(upstream, process)
|
19
|
-
@upstream, @process = upstream, process
|
20
|
-
end
|
21
|
-
|
22
|
-
def eos?
|
23
|
-
@upstream.eos?
|
24
|
-
end
|
25
|
-
|
26
|
-
def each(&output)
|
27
|
-
@upstream.each(&@process[output])
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
class EnumStream
|
32
|
-
include Stream
|
33
|
-
|
34
|
-
def initialize(enum)
|
35
|
-
@enum = enum
|
36
|
-
end
|
37
|
-
|
38
|
-
def eos?
|
39
|
-
@enum.peek
|
40
|
-
false
|
41
|
-
rescue StopIteration
|
42
|
-
true
|
43
|
-
end
|
44
|
-
|
45
|
-
def each(&output)
|
46
|
-
loop do
|
47
|
-
output[@enum.next]
|
48
|
-
end
|
49
|
-
rescue StopIteration
|
50
|
-
end
|
51
|
-
|
52
|
-
def to_s
|
53
|
-
"$[#{@enum.inspect}]"
|
54
|
-
end
|
55
|
-
end
|
56
|
-
|
57
|
-
class MergedStream
|
58
|
-
include Stream
|
59
|
-
|
60
|
-
def initialize(streams, comparator)
|
61
|
-
@comparator = comparator
|
62
|
-
@heads = streams.map{|s| {stream: s}}
|
63
|
-
end
|
64
|
-
|
65
|
-
def eos?
|
66
|
-
@heads.empty?
|
67
|
-
end
|
68
|
-
|
69
|
-
def each(&output)
|
70
|
-
pull_heads
|
71
|
-
loop do
|
72
|
-
if min = @heads.min{|a, b| @comparator.call(a[:element], b[:element])}
|
73
|
-
output.call(min.delete(:element).tap{ pull_heads })
|
74
|
-
else
|
75
|
-
break if @heads.empty?
|
76
|
-
pull_heads
|
77
|
-
end
|
78
|
-
end
|
79
|
-
end
|
80
|
-
|
81
|
-
def pull_heads
|
82
|
-
@heads.select!{|h| h[:element] ||= pull(h[:stream])}
|
83
|
-
end
|
84
|
-
|
85
|
-
def pull(stream)
|
86
|
-
loop do
|
87
|
-
return nil if stream.eos?
|
88
|
-
if n = stream.first
|
89
|
-
return n
|
90
|
-
end
|
91
|
-
end
|
92
|
-
end
|
93
|
-
end
|
94
|
-
|
95
|
-
class SlicedStreams
|
96
|
-
include Stream
|
97
|
-
|
98
|
-
def initialize(stream, index, start_index, end_index)
|
99
|
-
@stream, @index, @start_index, @end_index = stream, index, start_index, end_index
|
100
|
-
@slice_start_index, @slice_end_index = nil
|
101
|
-
end
|
102
|
-
|
103
|
-
def eos?
|
104
|
-
@stream.eos?
|
105
|
-
end
|
106
|
-
|
107
|
-
def each(&output)
|
108
|
-
return if eos?
|
109
|
-
@head ||= @stream.first
|
110
|
-
@slice_start_index ||= @start_index || @index[@head]
|
111
|
-
@slice_end_index ||= @end_index[@slice_start_index]
|
112
|
-
@eos_in_slice ||= false
|
113
|
-
loop do
|
114
|
-
if @slice_start_index > @index[@head]
|
115
|
-
return if eos?
|
116
|
-
@head = @stream.first
|
117
|
-
else
|
118
|
-
break
|
119
|
-
end
|
120
|
-
end
|
121
|
-
|
122
|
-
loop do
|
123
|
-
break if @eos_in_slice
|
124
|
-
range = @slice_start_index..@slice_end_index
|
125
|
-
slice_enum = Enumerator.new do |y|
|
126
|
-
loop do
|
127
|
-
break if range.max <= @index[@head]
|
128
|
-
if @eos_in_slice = eos?
|
129
|
-
y << @head
|
130
|
-
break
|
131
|
-
end
|
132
|
-
head, @head = @head, @stream.first
|
133
|
-
y << head
|
134
|
-
end
|
135
|
-
end
|
136
|
-
@slice_start_index, @slice_end_index = range.max, @end_index[range.max]
|
137
|
-
output[range: range, stream: EnumStream.new(slice_enum)]
|
138
|
-
end
|
139
|
-
end
|
140
|
-
end
|
141
|
-
|
142
|
-
class SliceStream
|
143
|
-
def initialize(index, start_index, end_index)
|
144
|
-
@index, @start_index, @end_index = index, start_index, end_index
|
145
|
-
end
|
146
|
-
|
147
|
-
def slice(stream)
|
148
|
-
SlicedStreams.new(stream, @index, @start_index, @end_index)
|
149
|
-
end
|
150
|
-
end
|
151
|
-
|
152
|
-
module_function
|
153
|
-
def stream(obj)
|
154
|
-
EnumStream.new(obj.to_enum)
|
155
|
-
end
|
156
|
-
|
157
|
-
def merge_streams(streams, comparator)
|
158
|
-
loop do
|
159
|
-
s1 = streams.shift
|
160
|
-
return s1 if streams.empty?
|
161
|
-
s2 = streams.shift
|
162
|
-
streams << MergedStream.new([s1, s2], comparator)
|
163
|
-
end
|
164
|
-
end
|
165
|
-
|
166
|
-
def slice_stream(index, step, start_index=nil)
|
167
|
-
end_index = step.is_a?(Proc) ? step : lambda { |index| index + step }
|
168
|
-
SliceStream.new(index, start_index, end_index)
|
169
|
-
end
|
170
|
-
|
171
|
-
def stream_each(&block)
|
172
|
-
lambda do |output|
|
173
|
-
lambda do |i|
|
174
|
-
block[i]
|
175
|
-
output[i]
|
176
|
-
end
|
177
|
-
end
|
178
|
-
end
|
179
|
-
|
180
|
-
def stream_map(&block)
|
181
|
-
lambda { |output| lambda { |i| output[block[i]] } }
|
182
|
-
end
|
183
|
-
|
184
|
-
def stream_filter(&block)
|
185
|
-
lambda { |output| lambda { |i| output[i] if block[i] } }
|
186
|
-
end
|
187
|
-
alias :stream_select :stream_filter
|
188
|
-
|
189
|
-
def counter
|
190
|
-
count = 0
|
191
|
-
counter = Enumerator.new { |y| loop { y << (count += 1) } }
|
192
|
-
stream(counter)
|
193
|
-
end
|
194
|
-
|
195
|
-
end
|