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 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: []