logfile_interval 1.1.1 → 1.1.2

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,208 @@
1
+ require 'spec_helper'
2
+
3
+ module LogfileInterval
4
+ module Aggregator
5
+ describe Aggregator do
6
+ it 'finds the aggregator class' do
7
+ Aggregator.klass(:sum).should == Sum
8
+ Aggregator.klass(:average).should == Average
9
+ Aggregator.klass(:count).should == Count
10
+ Aggregator.klass(:group_and_count).should == GroupAndCount
11
+ Aggregator.klass(:delta).should == Delta
12
+ end
13
+ end
14
+
15
+ shared_examples 'an aggregator' do
16
+ let(:aggregator) { described_class.new }
17
+
18
+ [ :add, :value, :values ].each do |method|
19
+ it "responds to #{method}" do
20
+ aggregator.should respond_to(method)
21
+ end
22
+ end
23
+
24
+ context 'values' do
25
+ context 'with one group' do
26
+ before :each do
27
+ aggregator.add(5, :key1)
28
+ end
29
+
30
+ it 'returns a hash' do
31
+ aggregator.values.should be_a(Hash) unless aggregator.is_a?(Delta)
32
+ end
33
+ end
34
+
35
+ context 'with several groups' do
36
+ before :each do
37
+ aggregator.add(5, :key1)
38
+ aggregator.add(3, :key2)
39
+ aggregator.add(3, :key1)
40
+ end
41
+
42
+ it 'returns a hash' do
43
+ aggregator.values.should be_a(Hash)
44
+ end
45
+ end
46
+
47
+ context 'with no group' do
48
+ before :each do
49
+ aggregator.add(5)
50
+ aggregator.add(3)
51
+ end
52
+
53
+ it 'returns a numeric' do
54
+ aggregator.values.should be_a(Numeric) unless aggregator.is_a?(Count)
55
+ end
56
+ end
57
+ end
58
+ end
59
+
60
+ [ Count, Sum, Average, Delta ]. each do |klass|
61
+ describe klass do
62
+ it_behaves_like 'an aggregator'
63
+ end
64
+ end
65
+
66
+
67
+ describe 'without group_by key' do
68
+ describe Sum do
69
+ it 'sums up values' do
70
+ sum = Sum.new
71
+ sum.add(3)
72
+ sum.add(5)
73
+ sum.value.should == 8
74
+ end
75
+ end
76
+
77
+ describe Average do
78
+ it 'averages values' do
79
+ avg = Average.new
80
+ avg.add(3)
81
+ avg.add(5)
82
+ avg.value.should == 4
83
+ end
84
+ end
85
+
86
+ describe Delta do
87
+ it 'averages delta values' do
88
+ d = Delta.new
89
+ d.add(1.4)
90
+ d.add(1.1)
91
+ d.add(1.0)
92
+ d.value.round(5).should == 0.2
93
+ end
94
+ end
95
+
96
+ describe Count do
97
+ it 'groups values and increment counters' do
98
+ g = Count.new
99
+ g.add('200')
100
+ g.add('500')
101
+ g.add('301')
102
+ g.add('200')
103
+ g.value.should == 4
104
+ end
105
+ end
106
+ end
107
+
108
+ describe 'with group_by key' do
109
+
110
+ describe Sum do
111
+ it 'sums up values by key' do
112
+ sum = Sum.new
113
+ sum.add(3, :key1)
114
+ sum.add(5, :key2)
115
+ sum.add(5, :key1)
116
+ sum.values.should be_a(Hash)
117
+ sum.values.size.should == 2
118
+ sum.value(:key1).should == 8
119
+ sum.values[:key1].should == 8
120
+ sum.value(:key2).should == 5
121
+ sum.values[:key2].should == 5
122
+ end
123
+ end
124
+
125
+
126
+ describe Average do
127
+ it 'averages values by key' do
128
+ avg = Average.new
129
+ avg.add(3, :key1)
130
+ avg.add(5, :key2)
131
+ avg.add(5, :key1)
132
+ avg.values.should be_a(Hash)
133
+ avg.values.size.should == 2
134
+ avg.value(:key1).should == 4
135
+ avg.values[:key1].should == 4
136
+ avg.value(:key2).should == 5
137
+ avg.values[:key2].should == 5
138
+ end
139
+ end
140
+
141
+ describe Count do
142
+ it 'groups values and increment counters' do
143
+ g = Count.new
144
+ g.add('200', '200')
145
+ g.add('500', '500')
146
+ g.add('301', '301')
147
+ g.add('200', '200')
148
+ g.values.should be_a(Hash)
149
+ g.values.should include({'200' => 2})
150
+ g.values.should include({'301' => 1})
151
+ g.values.should include({'500' => 1})
152
+ end
153
+ end
154
+
155
+ describe GroupAndCount do
156
+ it 'each yields a key and a hash' do
157
+ gac = GroupAndCount.new
158
+ gac.add :key1, :subkey1
159
+ gac.first.should be_an(Array)
160
+ gac.first.size.should == 2
161
+ gac.first[1].should be_a(Hash)
162
+ end
163
+
164
+ context :add do
165
+ before :each do
166
+ @gac = GroupAndCount.new
167
+ end
168
+
169
+ it 'requires a group_by argument' do
170
+ lambda { @gac.add('foo') }.should raise_error ArgumentError
171
+ end
172
+
173
+ it 'counts number of occurence of subkey for key' do
174
+ @gac.add :key1, :subkey1
175
+ @gac.add :key1, :subkey2
176
+ @gac.add :key2, :subkey1
177
+ @gac.add :key2, :subkey1
178
+ @gac.add :key2, :subkey3
179
+
180
+ @gac.values[:key1][:subkey1].should == 1
181
+ @gac.values[:key1][:subkey2].should == 1
182
+ @gac.values[:key2][:subkey1].should == 2
183
+ @gac.values[:key2][:subkey2].should == 0
184
+ @gac.values[:key2][:subkey3].should == 1
185
+ end
186
+ end
187
+ end
188
+
189
+ describe Delta do
190
+ it 'averages deltas by key' do
191
+ d = Delta.new
192
+ d.add(9, :key1)
193
+ d.add(10, :key2)
194
+ d.add(5, :key1)
195
+ d.add(8, :key2)
196
+ d.add(3, :key1)
197
+ d.add(5, :key2)
198
+ d.values.should be_a(Hash)
199
+ d.values.size.should == 2
200
+ d.value(:key1).should == 3
201
+ d.values[:key1].should == 3
202
+ d.value(:key2).should == 2.5
203
+ d.values[:key2].should == 2.5
204
+ end
205
+ end
206
+ end
207
+ end
208
+ end
@@ -1,7 +1,7 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  module LogfileInterval
4
- module LineParser
4
+ module Util
5
5
  describe Counter do
6
6
  it 'behaves like a hash' do
7
7
  c = Counter.new
@@ -47,6 +47,14 @@ module LogfileInterval
47
47
  @intervals.last[:action].should == 2
48
48
  end
49
49
  end
50
+
51
+ context 'without a block' do
52
+ it 'should return an iterator' do
53
+ e = @builder.each_interval
54
+ e.should be_an(Enumerator)
55
+ e.next.end_time.should == Time.new(2013,12,01,16,0,0,'-08:00')
56
+ end
57
+ end
50
58
  end
51
59
 
52
60
  context :last_interval do
@@ -5,9 +5,13 @@ module LogfileInterval
5
5
  data_dir = File.join(File.dirname(__FILE__), '..', 'support/logfiles')
6
6
 
7
7
  describe Interval do
8
+ before :each do
9
+ @end_time = Time.new(2013, 12, 01, 16, 00, 00, '-08:00')
10
+ @length = 300
11
+ end
12
+
8
13
  it 'gets instantiated with empty data' do
9
- end_time = Time.new(2013, 12, 01, 16, 00, 00, '-08:00')
10
- interval = Interval.new(end_time, 300, LineParser::TimingLog)
14
+ interval = Interval.new(@end_time, @length, LineParser::TimingLog)
11
15
  interval.size.should == 0
12
16
  interval[:total_time].should == 0
13
17
  interval[:num_bytes].should == 0
@@ -15,12 +19,32 @@ module LogfileInterval
15
19
  interval[:ip].should == 0
16
20
  end
17
21
 
18
- context :add_record do
19
- before :each do
20
- @end_time = Time.new(2013, 12, 01, 16, 00, 00, '-08:00')
21
- @length = 300
22
+ context :to_hash do
23
+ it 'returns a hash' do
24
+ interval = Interval.new(@end_time, @length, LineParser::TimingLog)
25
+ interval.to_hash.should be_a(Hash)
26
+ end
27
+
28
+ it 'has a key for all columns' do
29
+ record = LineParser::TimingLog.create_record('1385942400, 192.168.0.5, posts#index, 100, 2000, 53.0')
30
+ interval = Interval.new(@end_time, @length, LineParser::TimingLog)
31
+ interval.add_record(record)
32
+ hinterval = interval.to_hash
33
+ hinterval.keys.should include(:ip, :total_time, :action, :num_bytes, :rss)
22
34
  end
23
35
 
36
+ it 'with no data, should have keys with 0 values' do
37
+ interval = Interval.new(@end_time, @length, LineParser::TimingLog)
38
+ hinterval = interval.to_hash
39
+ hinterval[:ip].should == 0
40
+ hinterval[:action].should == 0
41
+ hinterval[:total_time].should == 0
42
+ hinterval[:num_bytes].should == 0
43
+ hinterval[:rss].should == 0
44
+ end
45
+ end
46
+
47
+ context :add_record do
24
48
  context 'basics' do
25
49
  before :each do
26
50
  @interval = Interval.new(@end_time, @length, LineParser::TimingLog)
@@ -50,7 +74,7 @@ module LogfileInterval
50
74
 
51
75
  context 'with count and group by options' do
52
76
  it 'creates an aggregator of type GroupAndCount' do
53
- expect(LineParser::Aggregator::GroupAndCount).to receive(:new)
77
+ expect(Aggregator::GroupAndCount).to receive(:new)
54
78
  interval = Interval.new(@end_time, @length, LineParser::TimingLogWithGrouping)
55
79
  end
56
80
 
@@ -8,32 +8,57 @@ module LogfileInterval
8
8
  before :each do
9
9
  @logfiles = ["#{data_dir}/access.log.2", "#{data_dir}/access.log.1"]
10
10
  @set = LogfileSet.new(@logfiles, LineParser::AccessLog)
11
+ @first_line = '66.249.67.176 - - [23/Jun/2013:17:00:01 -0800] "GET /package/core/raring/universe/proposed/openldap HTTP/1.1" 200 185 "-" "Google"'
12
+ @second_line = '12.24.48.96 - - [23/Jun/2013:16:59:00 -0800] "GET /package/core/raring/universe/proposed/openldap HTTP/1.1" 200 4555 "-" "Bing)"'
13
+ @last_line = '12.24.48.96 - - [23/Jun/2013:16:49:00 -0800] "GET /package/core/raring/universe/proposed/bash HTTP/1.1" 200 4555 "-" "Bing)"'
11
14
  end
12
15
 
13
16
  it 'ordered_filenames should return the most recent file first' do
14
17
  @set.ordered_filenames.should == @logfiles.reverse
15
18
  end
16
19
 
17
- it 'each_line should enumerate each line in file backwards' do
18
- lines = []
19
- @set.each_line do |line|
20
- lines << line
20
+ describe :each_line do
21
+ it 'should enumerate each line in file backwards' do
22
+ lines = []
23
+ @set.each_line do |line|
24
+ lines << line
25
+ end
26
+
27
+ lines.first.should == @first_line
28
+ lines.last.should == @last_line
21
29
  end
22
30
 
23
- lines.first.should == '66.249.67.176 - - [23/Jun/2013:17:00:01 -0800] "GET /package/core/raring/universe/proposed/openldap HTTP/1.1" 200 185 "-" "Google"'
24
- lines.last.should == '12.24.48.96 - - [23/Jun/2013:16:49:00 -0800] "GET /package/core/raring/universe/proposed/bash HTTP/1.1" 200 4555 "-" "Bing)"'
31
+ context 'without a block' do
32
+ it 'should return an enumerator' do
33
+ e = @set.each_line
34
+ e.should be_a(Enumerator)
35
+ e.first.should == @first_line
36
+ e.next.should == @first_line
37
+ e.next.should == @second_line
38
+ end
39
+ end
25
40
  end
26
41
 
27
- it 'each_parsed_line should enumerate each line backwards' do
28
- records = []
29
- @set.each_parsed_line do |record|
30
- records << record
42
+ describe :each_parsed_line do
43
+ it 'should enumerate each line backwards' do
44
+ records = []
45
+ @set.each_parsed_line do |record|
46
+ records << record
47
+ end
48
+
49
+ records.first.time.should == Time.new(2013, 06, 23, 17, 00, 01, '-08:00')
50
+ records.first.code.should == '200'
51
+ records.last.time.should == Time.new(2013, 06, 23, 16, 49, 00, '-08:00')
52
+ records.last.code.should == '200'
31
53
  end
32
54
 
33
- records.first.time.should == Time.new(2013, 06, 23, 17, 00, 01, '-08:00')
34
- records.first.code.should == '200'
35
- records.last.time.should == Time.new(2013, 06, 23, 16, 49, 00, '-08:00')
36
- records.last.code.should == '200'
55
+ context 'without a block' do
56
+ it 'should return an enumerator' do
57
+ e = @set.each_parsed_line
58
+ e.should be_a(Enumerator)
59
+ e.next.time.should == Time.new(2013, 06, 23, 17, 00, 01, '-08:00')
60
+ end
61
+ end
37
62
  end
38
63
  end
39
64
  end
@@ -7,6 +7,9 @@ module LogfileInterval
7
7
  describe Logfile do
8
8
  before :each do
9
9
  @alf = Logfile.new("#{data_dir}/access.log", LineParser::AccessLog)
10
+ @first_line = '78.54.172.146 - - [01/Jan/2012:16:30:51 -0800] "GET /package/core/oneiric/main/base/abrowser-6.0 HTTP/1.1" 200 6801 "http://www.google.com/url?sa=t&rct=j&q=abrowser 6.0&esrc=s&source=web&cd=4&sqi=2&ved=0CDYQFjAD&url=http%3A%2F%2Fwww.ubuntuupdates.org%2Fpackages%2Fshow%2F268762&ei=s-QlT8vJFon1sgb54unBDg&usg=AFQjCNHCHC0bxTf6aXAfUwT6Erjta6WLaQ&sig2=ceCi1odtaB8Vcf6IWg2a3w" "Mozilla/5.0 (Ubuntu; X11; Linux x86_64; rv:9.0.1) Gecko/20100101 Firefox/9.0.1"'
11
+ @second_line = '78.54.172.146 - - [01/Jan/2012:16:30:51 -0800] "GET /package/show/2 HTTP/1.1" 302 6801 "http://www.google.com/url?sa=t&rct=j&q=abrowser 6.0&esrc=s&source=web&cd=4&sqi=2&ved=0CDYQFjAD&url=http%3A%2F%2Fwww.ubuntuupdates.org%2Fpackages%2Fshow%2F268762&ei=s-QlT8vJFon1sgb54unBDg&usg=AFQjCNHCHC0bxTf6aXAfUwT6Erjta6WLaQ&sig2=ceCi1odtaB8Vcf6IWg2a3w" "Mozilla/5.0 (Ubuntu; X11; Linux x86_64; rv:9.0.1) Gecko/20100101 Firefox/9.0.1"'
12
+ @last_line = '66.249.67.176 - - [01/Jan/2012:00:57:47 -0800] "GET /packages/show/1 HTTP/1.1" 301 185 "-" "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)"'
10
13
  end
11
14
 
12
15
  it 'first_timestamp returns time of first line in file' do
@@ -14,30 +17,52 @@ module LogfileInterval
14
17
  @alf.first_timestamp.should == Time.new(2012, 01, 01, 00, 57, 47, '-08:00')
15
18
  end
16
19
 
17
- it 'each_line should enumerate each line in file backwards' do
18
- lines = []
19
- @alf.each_line do |line|
20
- lines << line
20
+ describe :each_line do
21
+ it 'should enumerate each line in file backwards' do
22
+ lines = []
23
+ @alf.each_line do |line|
24
+ lines << line
25
+ end
26
+
27
+ lines.first.should == @first_line
28
+ lines.last.should == @last_line
21
29
  end
22
30
 
23
- lines.first.should == '78.54.172.146 - - [01/Jan/2012:16:30:51 -0800] "GET /package/core/oneiric/main/base/abrowser-6.0 HTTP/1.1" 200 6801 "http://www.google.com/url?sa=t&rct=j&q=abrowser 6.0&esrc=s&source=web&cd=4&sqi=2&ved=0CDYQFjAD&url=http%3A%2F%2Fwww.ubuntuupdates.org%2Fpackages%2Fshow%2F268762&ei=s-QlT8vJFon1sgb54unBDg&usg=AFQjCNHCHC0bxTf6aXAfUwT6Erjta6WLaQ&sig2=ceCi1odtaB8Vcf6IWg2a3w" "Mozilla/5.0 (Ubuntu; X11; Linux x86_64; rv:9.0.1) Gecko/20100101 Firefox/9.0.1"'
24
- lines.last.should == '66.249.67.176 - - [01/Jan/2012:00:57:47 -0800] "GET /packages/show/1 HTTP/1.1" 301 185 "-" "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)"'
31
+ context 'without a block' do
32
+ it 'should return an enumerator' do
33
+ e = @alf.each_line
34
+ e.should be_a(Enumerator)
35
+ e.first.should == @first_line
36
+ e.next.should == @first_line
37
+ e.next.should == @second_line
38
+ end
39
+ end
25
40
  end
26
41
 
27
- it 'each_parsed_line should enumerate each line backwards' do
28
- records = []
29
- @alf.each_parsed_line do |record|
30
- records << record
42
+ describe :each_parsed_line do
43
+ it 'should enumerate each line backwards' do
44
+ records = []
45
+ @alf.each_parsed_line do |record|
46
+ records << record
47
+ end
48
+
49
+ records.first.time.should == Time.new(2012, 01, 01, 16, 30, 51, '-08:00')
50
+ records.first.code.should == '200'
51
+ records.first.length.should == 6801
52
+ records.first.length_by_ip.should == 6801
53
+ records.last.time.should == Time.new(2012, 01, 01, 00, 57, 47, '-08:00')
54
+ records.last.code.should == '301'
55
+ records.last.length.should == 185
56
+ records.last.length_by_ip.should == 185
31
57
  end
32
58
 
33
- records.first.time.should == Time.new(2012, 01, 01, 16, 30, 51, '-08:00')
34
- records.first.code.should == '200'
35
- records.first.length.should == 6801
36
- records.first.length_by_ip.should == 6801
37
- records.last.time.should == Time.new(2012, 01, 01, 00, 57, 47, '-08:00')
38
- records.last.code.should == '301'
39
- records.last.length.should == 185
40
- records.last.length_by_ip.should == 185
59
+ context 'without a block' do
60
+ it 'should return an enumerator' do
61
+ e = @alf.each_parsed_line
62
+ e.should be_a(Enumerator)
63
+ e.next.time.should == Time.new(2012, 01, 01, 16, 30, 51, '-08:00')
64
+ end
65
+ end
41
66
  end
42
67
  end
43
68
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: logfile_interval
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.1
4
+ version: 1.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Philippe Le Rohellec
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-01-03 00:00:00.000000000 Z
11
+ date: 2014-01-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -99,25 +99,28 @@ files:
99
99
  - bin/aggregate_access_log.rb
100
100
  - bin/readme.rb
101
101
  - docs/design.rb
102
- - docs/design2.rb
103
- - docs/design3.rb
104
102
  - lib/logfile_interval.rb
105
- - lib/logfile_interval/file_backward.rb
103
+ - lib/logfile_interval/aggregator.rb
104
+ - lib/logfile_interval/aggregator/average.rb
105
+ - lib/logfile_interval/aggregator/base.rb
106
+ - lib/logfile_interval/aggregator/count.rb
107
+ - lib/logfile_interval/aggregator/delta.rb
108
+ - lib/logfile_interval/aggregator/group_and_count.rb
109
+ - lib/logfile_interval/aggregator/sum.rb
106
110
  - lib/logfile_interval/interval.rb
107
111
  - lib/logfile_interval/interval_builder.rb
108
- - lib/logfile_interval/interval_length.rb
109
- - lib/logfile_interval/line_parser/aggregator.rb
110
112
  - lib/logfile_interval/line_parser/base.rb
111
- - lib/logfile_interval/line_parser/counter.rb
112
113
  - lib/logfile_interval/logfile.rb
113
114
  - lib/logfile_interval/logfile_set.rb
115
+ - lib/logfile_interval/util/counter.rb
116
+ - lib/logfile_interval/util/file_backward.rb
114
117
  - lib/logfile_interval/version.rb
115
118
  - logfile_interval.gemspec
119
+ - spec/lib/aggregator_spec.rb
120
+ - spec/lib/counter_spec.rb
116
121
  - spec/lib/interval_builder_spec.rb
117
122
  - spec/lib/interval_spec.rb
118
- - spec/lib/line_parser/aggregator_spec.rb
119
123
  - spec/lib/line_parser/base_spec.rb
120
- - spec/lib/line_parser/counter_spec.rb
121
124
  - spec/lib/logfile_set_spec.rb
122
125
  - spec/lib/logfile_spec.rb
123
126
  - spec/spec_helper.rb
@@ -154,11 +157,11 @@ signing_key:
154
157
  specification_version: 4
155
158
  summary: Aggregate logfile data into intervals
156
159
  test_files:
160
+ - spec/lib/aggregator_spec.rb
161
+ - spec/lib/counter_spec.rb
157
162
  - spec/lib/interval_builder_spec.rb
158
163
  - spec/lib/interval_spec.rb
159
- - spec/lib/line_parser/aggregator_spec.rb
160
164
  - spec/lib/line_parser/base_spec.rb
161
- - spec/lib/line_parser/counter_spec.rb
162
165
  - spec/lib/logfile_set_spec.rb
163
166
  - spec/lib/logfile_spec.rb
164
167
  - spec/spec_helper.rb
data/docs/design2.rb DELETED
@@ -1,77 +0,0 @@
1
- module LogfileInterval
2
- class Logfile
3
- end
4
-
5
- class LogfileSet
6
- end
7
-
8
- class Interval
9
- end
10
-
11
- class LineParser
12
- def initialize(regex, columns)
13
- end
14
-
15
- def parse(line)
16
- @data = {}
17
-
18
- match_data = regex.match(line)
19
- columns.each do |name, options|
20
- val = match_data[options[:pos]]
21
- @data[name] = convert(val, options[:conversion])
22
- end
23
- @data
24
- end
25
-
26
- def convert(val, conversion)
27
- case options[:conversion]
28
- when :integer then val.to_i
29
- else val
30
- end
31
- end
32
- end
33
-
34
- class Record
35
- def initialize(parser, line)
36
- @parser = parser
37
- @data = parser.parse(line)
38
- end
39
-
40
- def valid_columns
41
- @parser.columns.keys
42
- end
43
-
44
- def method_missing(meth, *args)
45
- if valid_columns.include?(meth) && args.none
46
- self[meth]
47
- else
48
- super
49
- end
50
- end
51
- end
52
- end
53
-
54
- logfiles = [ 'access.log', 'access.log.1', 'access.log.2' ]
55
- logfile = logfiles.first
56
- parser = LineParser.new(/blah/, [ { :name=>:ip , :pos => 1 }, ...])
57
-
58
- logfile_iterator = LogfileInterval::Logfile.new(parser, logfile)
59
-
60
- logfile_iterator.each_line do |line|
61
- puts line
62
- end
63
-
64
- logfile_iterator.each_parsed_line do |record|
65
- puts record.ip
66
- puts record.time
67
- end
68
-
69
- interval_builder = LogfileInterval::Interval.new(parser, logfiles)
70
-
71
- interval_builder.each_interval do |interval|
72
- puts interval.start_time
73
- puts interval.length
74
- interval[:ip].each do |ip, count|
75
- puts "#{ip}, #{count}"
76
- end
77
- end