evidence 0.0.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/README.md +0 -0
- data/examples/mingle_logs_analysis.rb +66 -0
- data/lib/evidence/littles_law_analysis.rb +56 -0
- data/lib/evidence/log_parser.rb +25 -0
- data/lib/evidence/rails_action_parser.rb +83 -0
- data/lib/evidence/stream.rb +109 -0
- data/lib/evidence.rb +51 -0
- metadata +71 -0
data/README.md
ADDED
File without changes
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'evidence'
|
2
|
+
require 'time'
|
3
|
+
|
4
|
+
include Evidence
|
5
|
+
|
6
|
+
mingle_log_pattern = /^
|
7
|
+
\w{3}\s+\d+\s+\d{2}\:\d{2}\:\d{2}\s+
|
8
|
+
(?<host_name>[^\s]+)\s+
|
9
|
+
[\w-_]+\:\s+
|
10
|
+
INFO\s+
|
11
|
+
\[(?<timestamp>[^\]]+)\]\s+
|
12
|
+
\[(?<thread_label>[^\]]+)\]\s+
|
13
|
+
\[(?<log4j_label>[^\]]+)\]\s+
|
14
|
+
\[tenant\:(?<tenant>[^\]]*)\]\s+
|
15
|
+
(?<message>.*)
|
16
|
+
$/x
|
17
|
+
mingle_bg_log_pattern = /^
|
18
|
+
\w{3}\s+\d+\s+\d{2}\:\d{2}\:\d{2}\s+
|
19
|
+
(?<host_name>[^\s]+)\s+
|
20
|
+
[\w-_]+\:\s+
|
21
|
+
INFO\s+
|
22
|
+
\[(?<timestamp>[^\]]+)\]\s+
|
23
|
+
\[[\w-_\d]+\[(?<thread_label>[^\]]+)\]\]\s+
|
24
|
+
\[(?<log4j_label>[^\]]+)\]\s+
|
25
|
+
\[tenant\:(?<tenant>[^\]]*)\]\s+
|
26
|
+
(?<message>.*)
|
27
|
+
$/x
|
28
|
+
|
29
|
+
pid = lambda {|log| "#{log[:host_name]}-#{log[:thread_label]}"}
|
30
|
+
message = lambda {|log| log[:message]}
|
31
|
+
|
32
|
+
def parse_timestamp
|
33
|
+
lambda do |block|
|
34
|
+
lambda do |log|
|
35
|
+
block.call(log.merge(timestamp: Time.parse(log[:timestamp])))
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
logs = Dir['./../mingle-saas/log/dumpling/**/mingle-cluster*'].reject do |f|
|
41
|
+
File.new(f).first(100).any? do |l|
|
42
|
+
l =~ mingle_bg_log_pattern
|
43
|
+
end
|
44
|
+
end.map do |f|
|
45
|
+
stream(File.new(f), log_parser(mingle_log_pattern))
|
46
|
+
end
|
47
|
+
puts "[DEBUG]logs.size => #{logs.size.inspect}"
|
48
|
+
|
49
|
+
merged = merge_streams(logs, lambda {|log1, log2| log1[:timestamp] <=> log2[:timestamp]})
|
50
|
+
|
51
|
+
#counter = 0
|
52
|
+
|
53
|
+
#stream(merged, lambda {|block| lambda {|log| block.call(counter += 1)}}).each do |count|
|
54
|
+
# puts "#{Time.now}: #{count}"
|
55
|
+
#end
|
56
|
+
|
57
|
+
actions_stream = stream(merged, rails_action_parser(pid, message))
|
58
|
+
|
59
|
+
time_window = (ARGV[0] || 60).to_i
|
60
|
+
File.open('datafile', 'w') do |f|
|
61
|
+
stream(actions_stream, littles_law_analysis(time_window)).map do |start_time, end_time, avg_stay_in_system|
|
62
|
+
t1 = start_time.strftime("%m-%d %H:%M")
|
63
|
+
t2 = end_time.strftime("%H:%M")
|
64
|
+
f.write("#{t1}-#{t2} #{avg_stay_in_system}\n".tap{|r| puts r})
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'time'
|
2
|
+
|
3
|
+
module Evidence
|
4
|
+
# Little's Law analysis, input stream log should include the following data
|
5
|
+
# {:request => {:timestamp}, :response => {:completed_time}}
|
6
|
+
class LittlesLawAnalysis
|
7
|
+
class Result
|
8
|
+
def initialize(start, time_window)
|
9
|
+
@start, @time_window = start, time_window
|
10
|
+
@end = @start + @time_window
|
11
|
+
@arrival_count = 0
|
12
|
+
@response_time = 0
|
13
|
+
end
|
14
|
+
|
15
|
+
def ended?(timestamp)
|
16
|
+
@end <= timestamp
|
17
|
+
end
|
18
|
+
|
19
|
+
def add(millisecond)
|
20
|
+
@arrival_count += 1
|
21
|
+
@response_time += millisecond
|
22
|
+
end
|
23
|
+
|
24
|
+
def next
|
25
|
+
Result.new(@start + @time_window, @time_window)
|
26
|
+
end
|
27
|
+
|
28
|
+
def value
|
29
|
+
avg_sec_arrival_rate = @arrival_count.to_f/@time_window
|
30
|
+
avg_sec_response_time = @response_time.to_f/1000/@arrival_count
|
31
|
+
[@start, @end, avg_sec_arrival_rate * avg_sec_response_time]
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# time_window: second
|
36
|
+
def initialize(time_window)
|
37
|
+
@time_window = time_window
|
38
|
+
end
|
39
|
+
|
40
|
+
def [](block)
|
41
|
+
result = nil
|
42
|
+
lambda do |log|
|
43
|
+
timestamp = Time.parse(log[:request][:timestamp])
|
44
|
+
if result.nil?
|
45
|
+
result = Result.new(timestamp, @time_window)
|
46
|
+
else
|
47
|
+
if result.ended?(timestamp)
|
48
|
+
block.call(result.value)
|
49
|
+
result = result.next
|
50
|
+
end
|
51
|
+
end
|
52
|
+
result.add(log[:response][:completed_time].to_i)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Evidence
|
2
|
+
class LogParser
|
3
|
+
def initialize(pattern, unmatched)
|
4
|
+
@pattern, @unmatched = pattern, unmatched
|
5
|
+
end
|
6
|
+
|
7
|
+
def [](block)
|
8
|
+
single_pattern_parser(block)
|
9
|
+
end
|
10
|
+
|
11
|
+
def single_pattern_parser(block)
|
12
|
+
lambda do |line|
|
13
|
+
if m = @pattern.match(line)
|
14
|
+
block.call(to_hash(m))
|
15
|
+
else
|
16
|
+
@unmatched.call(line)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_hash(m)
|
22
|
+
Hash[m.names.map(&:to_sym).zip(m.captures)]
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
module Evidence
|
2
|
+
class RailsActionParser
|
3
|
+
|
4
|
+
def initialize(pid, message, unmatched)
|
5
|
+
@pid, @message = pid, message
|
6
|
+
@unmatched = unmatched
|
7
|
+
end
|
8
|
+
|
9
|
+
def [](block)
|
10
|
+
processes = Hash.new
|
11
|
+
lambda do |log|
|
12
|
+
pid = @pid[log]
|
13
|
+
if processes.has_key?(pid)
|
14
|
+
processes[pid] << log
|
15
|
+
if end_action?(@message[log])
|
16
|
+
block.call(parse_action_logs(processes.delete(pid)))
|
17
|
+
end
|
18
|
+
else
|
19
|
+
if start_action?(@message[log])
|
20
|
+
processes[pid] = [log]
|
21
|
+
else
|
22
|
+
@unmatched.call(log)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def parse_action_logs(logs)
|
29
|
+
{
|
30
|
+
request: request(@message[logs[0]]),
|
31
|
+
response: response(@message[logs[-1]]),
|
32
|
+
logs: logs
|
33
|
+
}
|
34
|
+
end
|
35
|
+
|
36
|
+
def end_action?(msg)
|
37
|
+
msg =~ end_action_pattern
|
38
|
+
end
|
39
|
+
|
40
|
+
def start_action?(msg)
|
41
|
+
msg =~ start_action_pattern
|
42
|
+
end
|
43
|
+
|
44
|
+
def request(msg)
|
45
|
+
to_hash(start_action_pattern.match(msg))
|
46
|
+
end
|
47
|
+
|
48
|
+
def response(msg)
|
49
|
+
to_hash(end_action_pattern.match(msg))
|
50
|
+
end
|
51
|
+
|
52
|
+
def start_action_pattern
|
53
|
+
/^
|
54
|
+
(\#012\#012)? # ignore encoded newlines
|
55
|
+
Processing\s+
|
56
|
+
(?<controller>\w+)\#(?<action>\w+)\s+
|
57
|
+
\(for\s+
|
58
|
+
(?<remote_addr>[^\s]+)\s+
|
59
|
+
at\s+
|
60
|
+
(?<timestamp>[^\)]+)\)\s+
|
61
|
+
\[(?<method>[\w]+)\]
|
62
|
+
$/x
|
63
|
+
end
|
64
|
+
|
65
|
+
# Completed in 755ms (View: 330, DB: 215) | 200 OK [url]
|
66
|
+
def end_action_pattern
|
67
|
+
/^
|
68
|
+
Completed\sin\s
|
69
|
+
(?<completed_time>\d+)ms\s+
|
70
|
+
\(View\:\s(?<view_time>\d+)
|
71
|
+
(,\s*DB\:\s(?<db_time>\d+))?
|
72
|
+
\)\s+\|\s+
|
73
|
+
(?<code>\d+)\s+
|
74
|
+
(?<status>\w+)\s+
|
75
|
+
\[(?<url>.+)\]
|
76
|
+
$/x
|
77
|
+
end
|
78
|
+
|
79
|
+
def to_hash(m)
|
80
|
+
Hash[m.names.map(&:to_sym).zip(m.captures)]
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
module Evidence
|
2
|
+
class Stream
|
3
|
+
include Enumerable
|
4
|
+
|
5
|
+
def initialize(upstream, processor)
|
6
|
+
@upstream, @processor = upstream, processor
|
7
|
+
end
|
8
|
+
|
9
|
+
def eos?
|
10
|
+
@upstream.eos?
|
11
|
+
end
|
12
|
+
|
13
|
+
def each(&block)
|
14
|
+
@upstream.each(&@processor[block])
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class FileStream
|
19
|
+
include Enumerable
|
20
|
+
|
21
|
+
def initialize(file)
|
22
|
+
@file = file
|
23
|
+
end
|
24
|
+
|
25
|
+
def eos?
|
26
|
+
@file.eof?
|
27
|
+
end
|
28
|
+
|
29
|
+
def each(&block)
|
30
|
+
@file.each(&block)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
class ArrayStream
|
35
|
+
include Enumerable
|
36
|
+
def initialize(array)
|
37
|
+
@array = array
|
38
|
+
end
|
39
|
+
|
40
|
+
def eos?
|
41
|
+
@array.empty?
|
42
|
+
end
|
43
|
+
|
44
|
+
def each(&block)
|
45
|
+
while(item = @array.shift) do
|
46
|
+
block.call(item)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def to_s
|
51
|
+
"$[#{@array.inspect}]"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
class MergedStream
|
56
|
+
include Enumerable
|
57
|
+
|
58
|
+
def initialize(streams, comparator)
|
59
|
+
@comparator = comparator
|
60
|
+
@heads = streams.map{|s| {stream: s}}
|
61
|
+
end
|
62
|
+
|
63
|
+
def eos?
|
64
|
+
@heads.empty?
|
65
|
+
end
|
66
|
+
|
67
|
+
def each(&block)
|
68
|
+
pull_heads
|
69
|
+
loop do
|
70
|
+
if min = @heads.min{|a, b| @comparator.call(a[:element], b[:element])}
|
71
|
+
block.call(min.delete(:element).tap{ pull_heads })
|
72
|
+
else
|
73
|
+
break if @heads.empty?
|
74
|
+
pull_heads
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def pull_heads
|
80
|
+
@heads.select!{|h| h[:element] ||= pull(h[:stream])}
|
81
|
+
end
|
82
|
+
|
83
|
+
def pull(stream)
|
84
|
+
loop do
|
85
|
+
return nil if stream.eos?
|
86
|
+
if n = stream.first
|
87
|
+
return n
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
class Counter
|
94
|
+
include Enumerable
|
95
|
+
def initialize
|
96
|
+
@count = 0
|
97
|
+
end
|
98
|
+
|
99
|
+
def eos?
|
100
|
+
false
|
101
|
+
end
|
102
|
+
|
103
|
+
def each(&block)
|
104
|
+
loop do
|
105
|
+
block.call(@count += 1)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
data/lib/evidence.rb
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'evidence/stream'
|
2
|
+
require 'evidence/log_parser'
|
3
|
+
require 'evidence/rails_action_parser'
|
4
|
+
require 'evidence/littles_law_analysis'
|
5
|
+
|
6
|
+
module Evidence
|
7
|
+
module_function
|
8
|
+
|
9
|
+
# A stream is an Enumerable with a processor processing the data comming
|
10
|
+
# from upstream and yield to downstream
|
11
|
+
def stream(obj, processor=nil)
|
12
|
+
up_stream = case obj
|
13
|
+
when Array
|
14
|
+
ArrayStream.new(obj)
|
15
|
+
when File
|
16
|
+
FileStream.new(obj)
|
17
|
+
else
|
18
|
+
obj
|
19
|
+
end
|
20
|
+
Stream.new(up_stream, processor || lambda {|b| b})
|
21
|
+
end
|
22
|
+
|
23
|
+
def merge_streams(streams, comparator)
|
24
|
+
loop do
|
25
|
+
s1 = streams.shift
|
26
|
+
return s1 if streams.empty?
|
27
|
+
s2 = streams.shift
|
28
|
+
streams << MergedStream.new([s1, s2], comparator)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def counter
|
33
|
+
Counter.new
|
34
|
+
end
|
35
|
+
|
36
|
+
def log_parser(pattern, unmatched=default_unmatched_processor)
|
37
|
+
LogParser.new(pattern, unmatched)
|
38
|
+
end
|
39
|
+
|
40
|
+
def rails_action_parser(pid, message, unmatched=default_unmatched_processor)
|
41
|
+
RailsActionParser.new(pid, message, unmatched)
|
42
|
+
end
|
43
|
+
|
44
|
+
def littles_law_analysis(time_window)
|
45
|
+
LittlesLawAnalysis.new(time_window)
|
46
|
+
end
|
47
|
+
|
48
|
+
def default_unmatched_processor
|
49
|
+
lambda {|log| }
|
50
|
+
end
|
51
|
+
end
|
metadata
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: evidence
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Xiao Li
|
9
|
+
- Sheroy Marker
|
10
|
+
autorequire:
|
11
|
+
bindir: bin
|
12
|
+
cert_chain: []
|
13
|
+
date: 2013-08-10 00:00:00.000000000 Z
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: rake
|
17
|
+
requirement: !ruby/object:Gem::Requirement
|
18
|
+
none: false
|
19
|
+
requirements:
|
20
|
+
- - ! '>='
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: '0'
|
23
|
+
type: :development
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
none: false
|
27
|
+
requirements:
|
28
|
+
- - ! '>='
|
29
|
+
- !ruby/object:Gem::Version
|
30
|
+
version: '0'
|
31
|
+
description:
|
32
|
+
email:
|
33
|
+
- swing1979@gmail.com
|
34
|
+
- smarker@thoughtworks.com
|
35
|
+
executables: []
|
36
|
+
extensions: []
|
37
|
+
extra_rdoc_files: []
|
38
|
+
files:
|
39
|
+
- README.md
|
40
|
+
- lib/evidence/littles_law_analysis.rb
|
41
|
+
- lib/evidence/log_parser.rb
|
42
|
+
- lib/evidence/rails_action_parser.rb
|
43
|
+
- lib/evidence/stream.rb
|
44
|
+
- lib/evidence.rb
|
45
|
+
- examples/mingle_logs_analysis.rb
|
46
|
+
homepage: https://github.com/ThoughtWorksStudios/evidence
|
47
|
+
licenses:
|
48
|
+
- MIT
|
49
|
+
post_install_message:
|
50
|
+
rdoc_options: []
|
51
|
+
require_paths:
|
52
|
+
- lib
|
53
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
54
|
+
none: false
|
55
|
+
requirements:
|
56
|
+
- - ! '>='
|
57
|
+
- !ruby/object:Gem::Version
|
58
|
+
version: '0'
|
59
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
60
|
+
none: false
|
61
|
+
requirements:
|
62
|
+
- - ! '>='
|
63
|
+
- !ruby/object:Gem::Version
|
64
|
+
version: '0'
|
65
|
+
requirements: []
|
66
|
+
rubyforge_project:
|
67
|
+
rubygems_version: 1.8.23
|
68
|
+
signing_key:
|
69
|
+
specification_version: 3
|
70
|
+
summary: Log Analysis Tool
|
71
|
+
test_files: []
|