aggregate 0.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.
- data/LICENSE +22 -0
- data/README +2 -0
- data/Rakefile +15 -0
- data/VERSION +1 -0
- data/aggregate.gemspec +46 -0
- data/lib/aggregate.rb +277 -0
- data/test/ts_aggregate.rb +145 -0
- metadata +62 -0
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2009 Joseph Ruscio
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person
|
4
|
+
obtaining a copy of this software and associated documentation
|
5
|
+
files (the "Software"), to deal in the Software without
|
6
|
+
restriction, including without limitation the rights to use,
|
7
|
+
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
8
|
+
copies of the Software, and to permit persons to whom the
|
9
|
+
Software is furnished to do so, subject to the following
|
10
|
+
conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be
|
13
|
+
included in all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
17
|
+
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
19
|
+
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
20
|
+
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
21
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
22
|
+
OTHER DEALINGS IN THE SOFTWARE.
|
data/README
ADDED
data/Rakefile
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'rake'
|
2
|
+
|
3
|
+
begin
|
4
|
+
require 'jeweler'
|
5
|
+
Jeweler::Tasks.new do |gemspec|
|
6
|
+
gemspec.name = "aggregate"
|
7
|
+
gemspec.summary = "Aggregate is a Ruby class for accumulating aggregate statistics and includes histogram support"
|
8
|
+
gemspec.description = "Aggregate is a Ruby class for accumulating aggregate statistics and includes histogram support"
|
9
|
+
gemspec.email = "jruscio@gmail.com"
|
10
|
+
gemspec.homepage = "http://github.com/josephruscio/aggregate"
|
11
|
+
gemspec.authors = ["Joseph Ruscio"]
|
12
|
+
end
|
13
|
+
rescue LoadError
|
14
|
+
puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
|
15
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.2
|
data/aggregate.gemspec
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run `rake gemspec`
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{aggregate}
|
8
|
+
s.version = "0.1.2"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Joseph Ruscio"]
|
12
|
+
s.date = %q{2009-08-16}
|
13
|
+
s.description = %q{Aggregate is a Ruby class for accumulating aggregate statistics and includes histogram support}
|
14
|
+
s.email = %q{jruscio@gmail.com}
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"LICENSE",
|
17
|
+
"README"
|
18
|
+
]
|
19
|
+
s.files = [
|
20
|
+
"LICENSE",
|
21
|
+
"README",
|
22
|
+
"Rakefile",
|
23
|
+
"VERSION",
|
24
|
+
"aggregate.gemspec",
|
25
|
+
"lib/aggregate.rb",
|
26
|
+
"test/ts_aggregate.rb"
|
27
|
+
]
|
28
|
+
s.homepage = %q{http://github.com/josephruscio/aggregate}
|
29
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
30
|
+
s.require_paths = ["lib"]
|
31
|
+
s.rubygems_version = %q{1.3.3}
|
32
|
+
s.summary = %q{Aggregate is a Ruby class for accumulating aggregate statistics and includes histogram support}
|
33
|
+
s.test_files = [
|
34
|
+
"test/ts_aggregate.rb"
|
35
|
+
]
|
36
|
+
|
37
|
+
if s.respond_to? :specification_version then
|
38
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
39
|
+
s.specification_version = 3
|
40
|
+
|
41
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
42
|
+
else
|
43
|
+
end
|
44
|
+
else
|
45
|
+
end
|
46
|
+
end
|
data/lib/aggregate.rb
ADDED
@@ -0,0 +1,277 @@
|
|
1
|
+
# Implements aggregate statistics and maintains
|
2
|
+
# configurable histogram for a set of given samples. Convenient for tracking
|
3
|
+
# high throughput data.
|
4
|
+
class Aggregate
|
5
|
+
#The current average of all samples
|
6
|
+
attr_reader :mean
|
7
|
+
|
8
|
+
#The current number of samples
|
9
|
+
attr_reader :count
|
10
|
+
|
11
|
+
#The maximum sample value
|
12
|
+
attr_reader :max
|
13
|
+
|
14
|
+
#The minimum samples value
|
15
|
+
attr_reader :min
|
16
|
+
|
17
|
+
#The sum of all samples
|
18
|
+
attr_reader :sum
|
19
|
+
|
20
|
+
#The number of samples falling below the lowest valued histogram bucket
|
21
|
+
attr_reader :outliers_low
|
22
|
+
|
23
|
+
#The number of samples falling above the highest valued histogram bucket
|
24
|
+
attr_reader :outliers_high
|
25
|
+
|
26
|
+
# The number of buckets in the binary logarithmic histogram (low => 2**0, high => 2**@@LOG_BUCKETS)
|
27
|
+
@@LOG_BUCKETS = 128
|
28
|
+
|
29
|
+
# Create a new Aggregate that maintains a binary logarithmic histogram
|
30
|
+
# by default. Specifying values for low, high, and width configures
|
31
|
+
# the aggregate to maintain a linear histogram with (high - low)/width buckets
|
32
|
+
def initialize (low=nil, high=nil, width=nil)
|
33
|
+
@count = 0
|
34
|
+
@sum = 0.0
|
35
|
+
@sum2 = 0.0
|
36
|
+
@outliers_low = 0
|
37
|
+
@outliers_high = 0
|
38
|
+
|
39
|
+
# If the user asks we maintain a linear histogram
|
40
|
+
if (nil != low && nil != high && nil != width)
|
41
|
+
|
42
|
+
#Validate linear specification
|
43
|
+
if high <= low
|
44
|
+
raise ArgumentError, "High bucket must be > Low bucket"
|
45
|
+
end
|
46
|
+
|
47
|
+
if high - low < width
|
48
|
+
raise ArgumentError, "Histogram width must be <= histogram range"
|
49
|
+
end
|
50
|
+
|
51
|
+
@low = low
|
52
|
+
@high = high
|
53
|
+
@width = width
|
54
|
+
else
|
55
|
+
@low = 1
|
56
|
+
@high = to_bucket(@@LOG_BUCKETS - 1)
|
57
|
+
end
|
58
|
+
|
59
|
+
#Initialize all buckets to 0
|
60
|
+
@buckets = Array.new(bucket_count, 0)
|
61
|
+
end
|
62
|
+
|
63
|
+
# Include a sample in the aggregate
|
64
|
+
def << data
|
65
|
+
|
66
|
+
# Update min/max
|
67
|
+
if 0 == @count
|
68
|
+
@min = data
|
69
|
+
@max = data
|
70
|
+
else
|
71
|
+
@max = [data, @max].max
|
72
|
+
@min = [data, @min].min
|
73
|
+
end
|
74
|
+
|
75
|
+
# Update the running info
|
76
|
+
@count += 1
|
77
|
+
@sum += data
|
78
|
+
@sum2 += (data * data)
|
79
|
+
|
80
|
+
# Update the bucket
|
81
|
+
@buckets[to_index(data)] += 1 unless outlier?(data)
|
82
|
+
end
|
83
|
+
|
84
|
+
def mean
|
85
|
+
@sum / @count
|
86
|
+
end
|
87
|
+
|
88
|
+
#Calculate the standard deviation
|
89
|
+
def std_dev
|
90
|
+
Math.sqrt((@sum2.to_f - ((@sum.to_f * @sum.to_f)/@count.to_f)) / (@count.to_f - 1))
|
91
|
+
end
|
92
|
+
|
93
|
+
# Combine two aggregates
|
94
|
+
#def +(b)
|
95
|
+
# a = self
|
96
|
+
# c = Aggregate.new
|
97
|
+
|
98
|
+
# c.count = a.count + b.count
|
99
|
+
#end
|
100
|
+
|
101
|
+
#Generate a pretty-printed ASCII representation of the histogram
|
102
|
+
def to_s(columns=nil)
|
103
|
+
|
104
|
+
#default to an 80 column terminal, don't support < 80 for now
|
105
|
+
if nil == columns
|
106
|
+
columns = 80
|
107
|
+
else
|
108
|
+
raise ArgumentError if columns < 80
|
109
|
+
end
|
110
|
+
|
111
|
+
#Find the largest bucket and create an array of the rows we intend to print
|
112
|
+
disp_buckets = Array.new
|
113
|
+
max_count = 0
|
114
|
+
total = 0
|
115
|
+
@buckets.each_with_index do |count, idx|
|
116
|
+
next if 0 == count
|
117
|
+
max_count = [max_count, count].max
|
118
|
+
disp_buckets << [idx, to_bucket(idx), count]
|
119
|
+
total += count
|
120
|
+
end
|
121
|
+
|
122
|
+
#Figure out how wide the value and count columns need to be based on their
|
123
|
+
#largest respective numbers
|
124
|
+
value_str = "value"
|
125
|
+
count_str = "count"
|
126
|
+
total_str = "Total"
|
127
|
+
value_width = [disp_buckets.last[1].to_s.length, value_str.length].max
|
128
|
+
value_width = [value_width, total_str.length].max
|
129
|
+
count_width = [total.to_s.length, count_str.length].max
|
130
|
+
max_bar_width = columns - (value_width + " |".length + "| ".length + count_width)
|
131
|
+
|
132
|
+
#Determine the value of a '@'
|
133
|
+
weight = [max_count.to_f/max_bar_width.to_f, 1.0].max
|
134
|
+
|
135
|
+
#format the header
|
136
|
+
histogram = sprintf("%#{value_width}s |", value_str)
|
137
|
+
max_bar_width.times { histogram << "-"}
|
138
|
+
histogram << sprintf("| %#{count_width}s\n", count_str)
|
139
|
+
|
140
|
+
# We denote empty buckets with a '~'
|
141
|
+
def skip_row(value_width)
|
142
|
+
sprintf("%#{value_width}s ~\n", " ")
|
143
|
+
end
|
144
|
+
|
145
|
+
#Loop through each bucket to be displayed and output the correct number
|
146
|
+
prev_index = disp_buckets[0][0] - 1
|
147
|
+
|
148
|
+
disp_buckets.each do |x|
|
149
|
+
#Denote skipped empty buckets with a ~
|
150
|
+
histogram << skip_row(value_width) unless prev_index == x[0] - 1
|
151
|
+
prev_index = x[0]
|
152
|
+
|
153
|
+
#Add the value
|
154
|
+
row = sprintf("%#{value_width}d |", x[1])
|
155
|
+
|
156
|
+
#Add the bar
|
157
|
+
bar_size = (x[2]/weight).to_i
|
158
|
+
bar_size.times { row += "@"}
|
159
|
+
(max_bar_width - bar_size).times { row += " " }
|
160
|
+
|
161
|
+
#Add the count
|
162
|
+
row << sprintf("| %#{count_width}d\n", x[2])
|
163
|
+
|
164
|
+
#Append the finished row onto the histogram
|
165
|
+
histogram << row
|
166
|
+
end
|
167
|
+
|
168
|
+
#End the table
|
169
|
+
histogram << skip_row(value_width) if disp_buckets.last[0] != bucket_count-1
|
170
|
+
histogram << sprintf("%#{value_width}s", "Total")
|
171
|
+
histogram << " |"
|
172
|
+
max_bar_width.times {histogram << "-"}
|
173
|
+
histogram << "| "
|
174
|
+
histogram << sprintf("%#{count_width}d\n", total)
|
175
|
+
end
|
176
|
+
|
177
|
+
#Iterate through each bucket in the histogram regardless of
|
178
|
+
#its contents
|
179
|
+
def each
|
180
|
+
@buckets.each_with_index do |count, index|
|
181
|
+
yield(to_bucket(index), count)
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
#Iterate through only the buckets in the histogram that contain
|
186
|
+
#samples
|
187
|
+
def each_nonzero
|
188
|
+
@buckets.each_with_index do |count, index|
|
189
|
+
yield(to_bucket(index), count) if count != 0
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
private
|
194
|
+
|
195
|
+
def linear?
|
196
|
+
nil != @width
|
197
|
+
end
|
198
|
+
|
199
|
+
def outlier? (data)
|
200
|
+
|
201
|
+
if data < @low
|
202
|
+
@outliers_low += 1
|
203
|
+
elsif data > @high
|
204
|
+
@outliers_high += 1
|
205
|
+
else
|
206
|
+
return false
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
def bucket_count
|
211
|
+
if linear?
|
212
|
+
return (@high-@low)/@width
|
213
|
+
else
|
214
|
+
return @@LOG_BUCKETS
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
def to_bucket(index)
|
219
|
+
if linear?
|
220
|
+
return @low + (index * @width)
|
221
|
+
else
|
222
|
+
return 2**(index)
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
def right_bucket? index, data
|
227
|
+
|
228
|
+
# check invariant
|
229
|
+
raise unless linear?
|
230
|
+
|
231
|
+
bucket = to_bucket(index)
|
232
|
+
|
233
|
+
#It's the right bucket if data falls between bucket and next bucket
|
234
|
+
bucket <= data && data < bucket + @width
|
235
|
+
end
|
236
|
+
|
237
|
+
=begin
|
238
|
+
def find_bucket(lower, upper, target)
|
239
|
+
#Classic binary search
|
240
|
+
return upper if right_bucket?(upper, target)
|
241
|
+
|
242
|
+
# Cut the search range in half
|
243
|
+
middle = (upper/2).to_i
|
244
|
+
|
245
|
+
# Determine which half contains our value and recurse
|
246
|
+
if (to_bucket(middle) >= target)
|
247
|
+
return find_bucket(lower, middle, target)
|
248
|
+
else
|
249
|
+
return find_bucket(middle, upper, target)
|
250
|
+
end
|
251
|
+
end
|
252
|
+
=end
|
253
|
+
|
254
|
+
# A data point is added to the bucket[n] where the data point
|
255
|
+
# is less than the value represented by bucket[n], but greater
|
256
|
+
# than the value represented by bucket[n+1]
|
257
|
+
def to_index (data)
|
258
|
+
|
259
|
+
# basic case is simple
|
260
|
+
return log2(data).to_i if !linear?
|
261
|
+
|
262
|
+
# Search for the right bucket in the linear case
|
263
|
+
@buckets.each_with_index do |count, idx|
|
264
|
+
return idx if right_bucket?(idx, data)
|
265
|
+
end
|
266
|
+
#find_bucket(0, bucket_count-1, data)
|
267
|
+
|
268
|
+
#Should not get here
|
269
|
+
raise "#{data}"
|
270
|
+
end
|
271
|
+
|
272
|
+
# log2(x) returns j, | i = j-1 and 2**i <= data < 2**j
|
273
|
+
def log2( x )
|
274
|
+
Math.log(x) / Math.log(2)
|
275
|
+
end
|
276
|
+
|
277
|
+
end
|
@@ -0,0 +1,145 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'lib/aggregate'
|
3
|
+
|
4
|
+
class SimpleStatsTest < Test::Unit::TestCase
|
5
|
+
|
6
|
+
def setup
|
7
|
+
@stats = Aggregate.new
|
8
|
+
|
9
|
+
@@DATA.each do |x|
|
10
|
+
@stats << x
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_stats_count
|
15
|
+
assert_equal @@DATA.length, @stats.count
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_stats_min_max
|
19
|
+
sorted_data = @@DATA.sort
|
20
|
+
|
21
|
+
assert_equal sorted_data[0], @stats.min
|
22
|
+
assert_equal sorted_data.last, @stats.max
|
23
|
+
end
|
24
|
+
|
25
|
+
def test_stats_mean
|
26
|
+
sum = 0
|
27
|
+
@@DATA.each do |x|
|
28
|
+
sum += x
|
29
|
+
end
|
30
|
+
|
31
|
+
assert_equal sum.to_f/@@DATA.length.to_f, @stats.mean
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_bucket_counts
|
35
|
+
|
36
|
+
#Test each iterator
|
37
|
+
total_bucket_sum = 0
|
38
|
+
i = 0
|
39
|
+
@stats.each do |bucket, count|
|
40
|
+
assert_equal 2**i, bucket
|
41
|
+
|
42
|
+
total_bucket_sum += count
|
43
|
+
i += 1
|
44
|
+
end
|
45
|
+
|
46
|
+
assert_equal total_bucket_sum, @@DATA.length
|
47
|
+
|
48
|
+
#Test each_nonzero iterator
|
49
|
+
prev_bucket = 0
|
50
|
+
total_bucket_sum = 0
|
51
|
+
@stats.each_nonzero do |bucket, count|
|
52
|
+
assert bucket > prev_bucket
|
53
|
+
assert_not_equal count, 0
|
54
|
+
|
55
|
+
total_bucket_sum += count
|
56
|
+
end
|
57
|
+
|
58
|
+
assert_equal total_bucket_sum, @@DATA.length
|
59
|
+
end
|
60
|
+
|
61
|
+
=begin
|
62
|
+
def test_addition
|
63
|
+
stats1 = Aggregate.new
|
64
|
+
stats2 = Aggregate.new
|
65
|
+
|
66
|
+
stats1 << 1
|
67
|
+
stats2 << 3
|
68
|
+
|
69
|
+
stats_sum = stats1 + stats2
|
70
|
+
|
71
|
+
assert_equal stats_sum.count, stats1.count + stats2.count
|
72
|
+
end
|
73
|
+
=end
|
74
|
+
|
75
|
+
#XXX: Update test_bucket_contents() if you muck with @@DATA
|
76
|
+
@@DATA = [ 1, 5, 4, 6, 1028, 1972, 16384, 16385, 16383 ]
|
77
|
+
def test_bucket_contents
|
78
|
+
#XXX: This is the only test so far that cares about the actual contents
|
79
|
+
# of @@DATA, so if you update that array ... update this method too
|
80
|
+
expected_buckets = [1, 4, 1024, 8192, 16384]
|
81
|
+
expected_counts = [1, 3, 2, 1, 2]
|
82
|
+
|
83
|
+
i = 0
|
84
|
+
@stats.each_nonzero do |bucket, count|
|
85
|
+
assert_equal expected_buckets[i], bucket
|
86
|
+
assert_equal expected_counts[i], count
|
87
|
+
# Increment for the next test
|
88
|
+
i += 1
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def test_histogram
|
93
|
+
puts @stats.to_s
|
94
|
+
end
|
95
|
+
|
96
|
+
def test_outlier
|
97
|
+
assert_equal 0, @stats.outliers_low
|
98
|
+
assert_equal 0, @stats.outliers_high
|
99
|
+
|
100
|
+
@stats << -1
|
101
|
+
@stats << -2
|
102
|
+
@stats << 2**129
|
103
|
+
|
104
|
+
assert_equal 2, @stats.outliers_low
|
105
|
+
assert_equal 1, @stats.outliers_high
|
106
|
+
end
|
107
|
+
|
108
|
+
def test_std_dev
|
109
|
+
@stats.std_dev
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
class LinearHistogramTest < Test::Unit::TestCase
|
114
|
+
def setup
|
115
|
+
@stats = Aggregate.new(0, 32768, 1024)
|
116
|
+
|
117
|
+
@@DATA.each do |x|
|
118
|
+
@stats << x
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def test_validation
|
123
|
+
assert_raise(ArgumentError) {bad_stats = Aggregate.new(32,32,4)}
|
124
|
+
assert_raise(ArgumentError) {bad_stats = Aggregate.new(32,16,4)}
|
125
|
+
assert_raise(ArgumentError) {bad_stats = Aggregate.new(16,32,17)}
|
126
|
+
end
|
127
|
+
|
128
|
+
#XXX: Update test_bucket_contents() if you muck with @@DATA
|
129
|
+
@@DATA = [ 1, 5, 4, 6, 1028, 1972, 16384, 16385, 16383 ]
|
130
|
+
def test_bucket_contents
|
131
|
+
#XXX: This is the only test so far that cares about the actual contents
|
132
|
+
# of @@DATA, so if you update that array ... update this method too
|
133
|
+
expected_buckets = [0, 1024, 15360, 16384]
|
134
|
+
expected_counts = [4, 2, 1, 2]
|
135
|
+
|
136
|
+
i = 0
|
137
|
+
@stats.each_nonzero do |bucket, count|
|
138
|
+
assert_equal expected_buckets[i], bucket
|
139
|
+
assert_equal expected_counts[i], count
|
140
|
+
# Increment for the next test
|
141
|
+
i += 1
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
end
|
metadata
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: aggregate
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Joseph Ruscio
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-08-16 00:00:00 -07:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description: Aggregate is a Ruby class for accumulating aggregate statistics and includes histogram support
|
17
|
+
email: jruscio@gmail.com
|
18
|
+
executables: []
|
19
|
+
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files:
|
23
|
+
- LICENSE
|
24
|
+
- README
|
25
|
+
files:
|
26
|
+
- LICENSE
|
27
|
+
- README
|
28
|
+
- Rakefile
|
29
|
+
- VERSION
|
30
|
+
- aggregate.gemspec
|
31
|
+
- lib/aggregate.rb
|
32
|
+
- test/ts_aggregate.rb
|
33
|
+
has_rdoc: true
|
34
|
+
homepage: http://github.com/josephruscio/aggregate
|
35
|
+
licenses: []
|
36
|
+
|
37
|
+
post_install_message:
|
38
|
+
rdoc_options:
|
39
|
+
- --charset=UTF-8
|
40
|
+
require_paths:
|
41
|
+
- lib
|
42
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - ">="
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: "0"
|
47
|
+
version:
|
48
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
49
|
+
requirements:
|
50
|
+
- - ">="
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: "0"
|
53
|
+
version:
|
54
|
+
requirements: []
|
55
|
+
|
56
|
+
rubyforge_project:
|
57
|
+
rubygems_version: 1.3.3
|
58
|
+
signing_key:
|
59
|
+
specification_version: 3
|
60
|
+
summary: Aggregate is a Ruby class for accumulating aggregate statistics and includes histogram support
|
61
|
+
test_files:
|
62
|
+
- test/ts_aggregate.rb
|