logfile_interval 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,12 @@
1
+ require "logfile_interval/version"
2
+ require "logfile_interval/file_backward"
3
+ require "logfile_interval/interval"
4
+ require "logfile_interval/interval_builder"
5
+ require "logfile_interval/logfile"
6
+ require "logfile_interval/logfile_set"
7
+ require "logfile_interval/line_parser/base"
8
+ require "logfile_interval/line_parser/aggregator"
9
+ require "logfile_interval/line_parser/counter"
10
+
11
+ module LogfileInterval
12
+ end
@@ -0,0 +1,49 @@
1
+ module LogfileInterval
2
+ # Based on Perl's File::ReadBackwards module, by Uri Guttman.
3
+ class FileBackward
4
+ MAX_READ_SIZE = 1 << 10 # 1024
5
+
6
+ def initialize( *args )
7
+ return unless File.exist?(args[0])
8
+ @file = File.new(*args)
9
+ @file.seek(0, IO::SEEK_END)
10
+
11
+ @current_pos = @file.pos
12
+
13
+ @read_size = @file.pos % MAX_READ_SIZE
14
+ @read_size = MAX_READ_SIZE if @read_size.zero?
15
+
16
+ @line_buffer = Array.new
17
+ end
18
+
19
+ def gets( sep_string = $/ )
20
+ return nil unless @file
21
+ return @line_buffer.pop if @line_buffer.size > 2 or @current_pos.zero?
22
+
23
+ @current_pos -= @read_size
24
+ @file.seek(@current_pos, IO::SEEK_SET)
25
+
26
+ @line_buffer[0] = "#{@file.read(@read_size)}#{@line_buffer[0]}"
27
+ @read_size = MAX_READ_SIZE # Set a size for the next read.
28
+
29
+ @line_buffer[0] =
30
+ @line_buffer[0].scan(/.*?#{Regexp.escape(sep_string)}|.+/)
31
+ @line_buffer.flatten!
32
+
33
+ gets(sep_string)
34
+ end
35
+
36
+ def close
37
+ return unless @file
38
+ @file.close()
39
+ end
40
+ end
41
+ end
42
+
43
+ # f = FileBackward.new('../log/development.log')
44
+ # i = 0
45
+ # while(line = f.gets())
46
+ # puts line
47
+ # i += 1
48
+ # break if i>30
49
+ # end
@@ -0,0 +1,42 @@
1
+ module LogfileInterval
2
+ class Interval
3
+ attr_reader :start_time, :end_time, :length, :parser
4
+ attr_reader :size
5
+
6
+ class OutOfRange < StandardError; end
7
+ class ParserMismatch < StandardError; end
8
+
9
+ def initialize(end_time, length, parser)
10
+ raise ArgumentError, 'end_time must be round' unless (end_time.to_i % length.to_i == 0)
11
+ @end_time = end_time
12
+ @start_time = end_time - length
13
+ @length = length
14
+ @parser = parser
15
+ @size = 0
16
+
17
+ @data = {}
18
+ parser.columns.each do |name, options|
19
+ next unless agg = options[:aggregator]
20
+ @data[name] = agg.new
21
+ end
22
+ end
23
+
24
+ def [](name)
25
+ @data[name].value
26
+ end
27
+
28
+ def add_record(record)
29
+ return unless record.valid?
30
+ raise ParserMismatch unless record.class == parser
31
+ raise OutOfRange, 'too recent' if record.time>@end_time
32
+ raise OutOfRange, 'too old' if record.time<=@start_time
33
+
34
+ @size += 1
35
+
36
+ parser.columns.each do |name, options|
37
+ next unless @data[name]
38
+ @data[name].add(record[name])
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,34 @@
1
+ module LogfileInterval
2
+ class IntervalBuilder
3
+ attr_reader :logfile_set, :parser, :length
4
+
5
+ def initialize(logfile_set, length)
6
+ @logfile_set = logfile_set
7
+ @parser = logfile_set.parser
8
+ @length = length
9
+ end
10
+
11
+ def each_interval
12
+ secs = (Time.now.to_i / length.to_i) * length.to_i
13
+ rounded_end_time = Time.at(secs)
14
+ current_interval = Interval.new(rounded_end_time, length, parser)
15
+
16
+ logfile_set.each_parsed_line do |record|
17
+ next if record.time > current_interval.end_time
18
+ while record.time <= current_interval.start_time
19
+ yield current_interval
20
+ current_interval = Interval.new(current_interval.start_time, length, parser)
21
+ end
22
+ current_interval.add_record(record)
23
+ end
24
+
25
+ yield current_interval if current_interval.size>0
26
+ end
27
+
28
+ def last_interval
29
+ each_interval do |interval|
30
+ return interval
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,47 @@
1
+ module LogfileInterval
2
+ class IntervalLength
3
+ MAX_PERIODS = { 5 * 60 => 6 * 3600,
4
+ 3600 => 3600 * 24,
5
+ 3600 * 24 => 3600 * 24 * 30,
6
+ 3600 * 24 * 30 => 365 * 3600 * 24 }
7
+ LENGTHS = MAX_PERIODS.keys.sort
8
+
9
+ attr_reader :length
10
+
11
+ def initialize(l)
12
+ raise ArgumentError unless LENGTHS.include?(l)
13
+ @length = l
14
+ end
15
+
16
+ def lower
17
+ pos = LENGTHS.index(@length)
18
+ return nil if pos==0
19
+ IntervalLength.new(LENGTHS[pos-1])
20
+ end
21
+
22
+ def higher
23
+ pos = LENGTHS.index(@length)
24
+ return nil if pos==LENGTHS.size-1
25
+ IntervalLength.new(LENGTHS[pos+1])
26
+ end
27
+
28
+ def smallest?
29
+ pos = LENGTHS.index(@length)
30
+ pos == 0
31
+ end
32
+
33
+ def start_time(t)
34
+ ts = (t.to_i / @length.to_i) * @length.to_i
35
+ ts -= @length.to_i if t.to_i % @length.to_i == 0
36
+ Time.at(ts)
37
+ end
38
+
39
+ def end_time(t)
40
+ start_time(t) + @length
41
+ end
42
+
43
+ def to_i
44
+ @length.to_i
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,61 @@
1
+ module LogfileInterval
2
+ module LineParser
3
+ module Aggregator
4
+ def self.klass(agg_function)
5
+ case agg_function
6
+ when :sum then Sum
7
+ when :average then Average
8
+ when :group then Group
9
+ end
10
+ end
11
+
12
+ class Sum
13
+ def initialize
14
+ @val = 0
15
+ end
16
+
17
+ def add(value)
18
+ @val += value
19
+ end
20
+
21
+ def value
22
+ @val
23
+ end
24
+ end
25
+
26
+ class Average
27
+ def initialize
28
+ @val = 0
29
+ @size = 0
30
+ end
31
+
32
+ def add(value)
33
+ @val += value
34
+ @size += 1
35
+ end
36
+
37
+ def value
38
+ if @size > 0
39
+ @val.to_f / @size.to_f
40
+ else
41
+ 0
42
+ end
43
+ end
44
+ end
45
+
46
+ class Group
47
+ def initialize
48
+ @val = Counter.new
49
+ end
50
+
51
+ def add(value)
52
+ @val.increment(value)
53
+ end
54
+
55
+ def value
56
+ @val
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,94 @@
1
+ module LogfileInterval
2
+ module LineParser
3
+ AGGREGATION_FUNCTIONS = [ :sum, :average, :timestamp, :group ]
4
+
5
+ class InvalidLine < StandardError; end
6
+ class ConfigurationError < StandardError; end
7
+
8
+ class Base
9
+ class << self
10
+ attr_reader :regex
11
+
12
+ def columns
13
+ @columns ||= {}
14
+ end
15
+
16
+ def set_regex(regex)
17
+ @regex = regex
18
+ end
19
+
20
+ def add_column(options)
21
+ name = options.fetch(:name)
22
+ pos = options.fetch(:pos)
23
+ agg_function = options.fetch(:agg_function)
24
+ conversion = options.fetch(:conversion, :string)
25
+ unless AGGREGATION_FUNCTIONS.include?(agg_function)
26
+ raise ArgumentError, "agg_function must be one of #{AGGREGATION_FUNCTIONS.join(', ')}"
27
+ end
28
+
29
+ aggregator = Aggregator.klass(agg_function)
30
+ columns[name] = { :pos => pos, :aggregator => aggregator, :conversion => conversion }
31
+
32
+ define_method(name) do
33
+ @data[name]
34
+ end
35
+ end
36
+
37
+ def parse(line)
38
+ raise ConfigurationError, 'There must be at least 1 configured column' unless columns.any?
39
+ raise ConfigurationError, 'A regex must be set' unless regex
40
+
41
+ match_data = regex.match(line)
42
+ return nil unless match_data
43
+ return nil unless match_data.size >= columns.size+1
44
+
45
+ data = {}
46
+ columns.each do |name, options|
47
+ val = match_data[options[:pos]]
48
+ data[name] = convert(val, options[:conversion])
49
+ end
50
+ data
51
+ end
52
+
53
+ def create_record(line)
54
+ record = new(line)
55
+ return record if record.valid?
56
+ return nil
57
+ end
58
+
59
+ private
60
+
61
+ def validate_column_options(options)
62
+ end
63
+
64
+ def convert(val, conversion)
65
+ case conversion
66
+ when :integer then val.to_i
67
+ when :float then val.to_f
68
+ else val
69
+ end
70
+ end
71
+ end
72
+
73
+ def initialize(line)
74
+ @data = self.class.parse(line)
75
+ @valid = @data ? true : false
76
+ end
77
+
78
+ def valid?
79
+ @valid
80
+ end
81
+
82
+ def time
83
+ raise NotImplemented
84
+ end
85
+
86
+ def [](name)
87
+ @data[name]
88
+ end
89
+ end
90
+ end
91
+ end
92
+
93
+
94
+
@@ -0,0 +1,27 @@
1
+ module LogfileInterval
2
+ module LineParser
3
+ class Counter < Hash
4
+ def increment(val)
5
+ if self.has_key?(val)
6
+ self[val] += 1
7
+ else
8
+ self[val] = 1
9
+ end
10
+ end
11
+
12
+ def add(val, num)
13
+ if self.has_key?(val)
14
+ self[val] += num
15
+ else
16
+ self[val] = num
17
+ end
18
+ end
19
+
20
+ def merge(c)
21
+ c.keys.each do |k|
22
+ self.add c[k]
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,33 @@
1
+ module LogfileInterval
2
+ class Logfile
3
+ attr_reader :filename, :parser
4
+
5
+ def initialize(filename, parser)
6
+ @filename = filename
7
+ @parser = parser
8
+ end
9
+
10
+ def first_timestamp
11
+ return nil unless File.exist?(@filename)
12
+ File.open(@filename) do |f|
13
+ line = parser.create_record(f.gets)
14
+ line.time
15
+ end
16
+ end
17
+
18
+ def each_line
19
+ f = FileBackward.new(@filename)
20
+ while(line = f.gets)
21
+ yield line.chomp
22
+ end
23
+ f.close
24
+ end
25
+
26
+ def each_parsed_line
27
+ each_line do |line|
28
+ record = parser.create_record(line)
29
+ yield record if record
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,47 @@
1
+ module LogfileInterval
2
+ class LogfileSet
3
+ attr_reader :parser
4
+
5
+ def initialize(filenames, parser)
6
+ @parser = parser
7
+ @filenames = filenames
8
+ end
9
+
10
+ def existing_filenames
11
+ @existing_filenames ||= @filenames.select { |f| File.exist?(f) }
12
+ end
13
+
14
+ def ordered_filenames
15
+ time_for_file = existing_filenames.inject({}) do |h, filename|
16
+ file = Logfile.new(filename, parser)
17
+ h[filename] = file.first_timestamp
18
+ h
19
+ end
20
+ time_for_file.to_a.sort_by { |arr| arr[1] }.map { |arr| arr[0] }.reverse
21
+ end
22
+
23
+ def each_parsed_line
24
+ ordered_filenames.each do |filename|
25
+ tfile = Logfile.new(filename, parser)
26
+ tfile.each_parsed_line do |record|
27
+ yield record
28
+ end
29
+ end
30
+ end
31
+
32
+ def each_line
33
+ ordered_filenames.each do |filename|
34
+ tfile = Logfile.new(filename, parser)
35
+ tfile.each_line do |line|
36
+ yield line
37
+ end
38
+ end
39
+ end
40
+
41
+ def last_record
42
+ each_parsed_line do |record|
43
+ return record
44
+ end
45
+ end
46
+ end
47
+ end