logfile_interval 1.0.0

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.
@@ -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