logfile_interval 1.1.1 → 1.1.2

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