evidence 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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: []