metriks 0.8.1
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/Gemfile +7 -0
- data/LICENSE +21 -0
- data/README.md +333 -0
- data/Rakefile +150 -0
- data/lib/metriks.rb +27 -0
- data/lib/metriks/counter.rb +44 -0
- data/lib/metriks/ewma.rb +63 -0
- data/lib/metriks/histogram.rb +103 -0
- data/lib/metriks/meter.rb +73 -0
- data/lib/metriks/registry.rb +174 -0
- data/lib/metriks/reporter/logger.rb +94 -0
- data/lib/metriks/reporter/proc_title.rb +59 -0
- data/lib/metriks/simple_moving_average.rb +60 -0
- data/lib/metriks/timer.rb +93 -0
- data/lib/metriks/uniform_sample.rb +35 -0
- data/lib/metriks/utilization_timer.rb +43 -0
- data/metriks.gemspec +90 -0
- data/test/counter_test.rb +21 -0
- data/test/histogram_test.rb +38 -0
- data/test/logger_reporter_test.rb +30 -0
- data/test/meter_test.rb +28 -0
- data/test/metriks_test.rb +27 -0
- data/test/proc_title_reporter_test.rb +25 -0
- data/test/registry_test.rb +37 -0
- data/test/test_helper.rb +8 -0
- data/test/timer_test.rb +29 -0
- data/test/utilization_timer_test.rb +25 -0
- metadata +135 -0
data/lib/metriks.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
|
2
|
+
module Metriks
|
3
|
+
VERSION = '0.8.1'
|
4
|
+
|
5
|
+
def self.get(name)
|
6
|
+
Metriks::Registry.default.get(name)
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.counter(name)
|
10
|
+
Metriks::Registry.default.counter(name)
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.timer(name)
|
14
|
+
Metriks::Registry.default.timer(name)
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.utilization_timer(name)
|
18
|
+
Metriks::Registry.default.utilization_timer(name)
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.meter(name)
|
22
|
+
Metriks::Registry.default.meter(name)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
require 'metriks/registry'
|
27
|
+
require 'metriks/reporter/proc_title'
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'atomic'
|
2
|
+
|
3
|
+
module Metriks
|
4
|
+
# Public: Counters are one of the simplest metrics whose only operations
|
5
|
+
# are increment and decrement.
|
6
|
+
class Counter
|
7
|
+
# Public: Initialize a new Counter.
|
8
|
+
def initialize
|
9
|
+
@count = Atomic.new(0)
|
10
|
+
end
|
11
|
+
|
12
|
+
# Public: Reset the counter back to 0
|
13
|
+
#
|
14
|
+
# Returns nothing.
|
15
|
+
def clear
|
16
|
+
@count.value = 0
|
17
|
+
end
|
18
|
+
|
19
|
+
# Public: Increment the counter.
|
20
|
+
#
|
21
|
+
# incr - The value to add to the counter.
|
22
|
+
#
|
23
|
+
# Returns nothing.
|
24
|
+
def increment(incr = 1)
|
25
|
+
@count.update { |v| v + incr }
|
26
|
+
end
|
27
|
+
|
28
|
+
# Public: Decrement the counter.
|
29
|
+
#
|
30
|
+
# decr - The value to subtract from the counter.
|
31
|
+
#
|
32
|
+
# Returns nothing.
|
33
|
+
def decrement(decr = 1)
|
34
|
+
@count.update { |v| v - decr }
|
35
|
+
end
|
36
|
+
|
37
|
+
# Public: The current count.
|
38
|
+
#
|
39
|
+
# Returns the count.
|
40
|
+
def count
|
41
|
+
@count.value
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
data/lib/metriks/ewma.rb
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'atomic'
|
2
|
+
|
3
|
+
module Metriks
|
4
|
+
class EWMA
|
5
|
+
INTERVAL = 5.0
|
6
|
+
SECONDS_PER_MINUTE = 60.0
|
7
|
+
|
8
|
+
ONE_MINUTE = 1
|
9
|
+
FIVE_MINUTES = 5
|
10
|
+
FIFTEEN_MINUTES = 15
|
11
|
+
|
12
|
+
M1_ALPHA = 1 - Math.exp(-INTERVAL / SECONDS_PER_MINUTE / ONE_MINUTE)
|
13
|
+
M5_ALPHA = 1 - Math.exp(-INTERVAL / SECONDS_PER_MINUTE / FIVE_MINUTES)
|
14
|
+
M15_ALPHA = 1 - Math.exp(-INTERVAL / SECONDS_PER_MINUTE / FIFTEEN_MINUTES)
|
15
|
+
|
16
|
+
def self.new_m1
|
17
|
+
new(M1_ALPHA, INTERVAL)
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.new_m5
|
21
|
+
new(M5_ALPHA, INTERVAL)
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.new_m15
|
25
|
+
new(M15_ALPHA, INTERVAL)
|
26
|
+
end
|
27
|
+
|
28
|
+
def initialize(alpha, interval)
|
29
|
+
@alpha = alpha
|
30
|
+
@interval = interval
|
31
|
+
|
32
|
+
@initialized = false
|
33
|
+
@rate = Atomic.new(0.0)
|
34
|
+
@uncounted = Atomic.new(0)
|
35
|
+
end
|
36
|
+
|
37
|
+
def clear
|
38
|
+
@initialized = false
|
39
|
+
@rate.value = 0.0
|
40
|
+
@uncounted.value = 0
|
41
|
+
end
|
42
|
+
|
43
|
+
def update(value)
|
44
|
+
@uncounted.update { |v| v + value }
|
45
|
+
end
|
46
|
+
|
47
|
+
def tick
|
48
|
+
count = @uncounted.swap(0)
|
49
|
+
instant_rate = count / @interval.to_f
|
50
|
+
|
51
|
+
if @initialized
|
52
|
+
@rate.update { |v| v + @alpha * (instant_rate - v) }
|
53
|
+
else
|
54
|
+
@rate.value = instant_rate
|
55
|
+
@initialized = true
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def rate
|
60
|
+
@rate.value
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
require 'atomic'
|
2
|
+
require 'metriks/uniform_sample'
|
3
|
+
|
4
|
+
module Metriks
|
5
|
+
class Histogram
|
6
|
+
DEFAULT_SAMPLE_SIZE = 1028
|
7
|
+
DEFAULT_ALPHA = 0.015
|
8
|
+
|
9
|
+
def self.new_uniform
|
10
|
+
new(Metriks::UniformSample.new(DEFAULT_SAMPLE_SIZE))
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize(sample)
|
14
|
+
@sample = sample
|
15
|
+
@count = Atomic.new(0)
|
16
|
+
@min = Atomic.new(nil)
|
17
|
+
@max = Atomic.new(nil)
|
18
|
+
@sum = Atomic.new(0)
|
19
|
+
@variance = Atomic.new([ -1, 0 ])
|
20
|
+
end
|
21
|
+
|
22
|
+
def clear
|
23
|
+
@sample.clear
|
24
|
+
@count.value = 0
|
25
|
+
@min.value = nil
|
26
|
+
@max.value = nil
|
27
|
+
@sum.value = 0
|
28
|
+
@variance.value = [ -1, 0 ]
|
29
|
+
end
|
30
|
+
|
31
|
+
def update(value)
|
32
|
+
@count.update { |v| v + 1 }
|
33
|
+
@sample.update(value)
|
34
|
+
self.max = value
|
35
|
+
self.min = value
|
36
|
+
@sum.update { |v| v + value }
|
37
|
+
update_variance(value)
|
38
|
+
end
|
39
|
+
|
40
|
+
def count
|
41
|
+
@count.value
|
42
|
+
end
|
43
|
+
|
44
|
+
def max
|
45
|
+
count > 0 ? @max.value : 0.0
|
46
|
+
end
|
47
|
+
|
48
|
+
def min
|
49
|
+
count > 0 ? @min.value : 0.0
|
50
|
+
end
|
51
|
+
|
52
|
+
def mean
|
53
|
+
count > 0 ? @sum.value / count : 0.0
|
54
|
+
end
|
55
|
+
|
56
|
+
def stddev
|
57
|
+
count > 0 ? variance : 0.0
|
58
|
+
end
|
59
|
+
|
60
|
+
def variance
|
61
|
+
count <= 1 ? 0.0 : @variance.value[1] / (count - 1)
|
62
|
+
end
|
63
|
+
|
64
|
+
def max=(potential_max)
|
65
|
+
done = false
|
66
|
+
|
67
|
+
while !done
|
68
|
+
current_max = @max.value
|
69
|
+
done = (!current_max.nil? && current_max >= potential_max) || @max.compare_and_swap(current_max, potential_max)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def min=(potential_min)
|
74
|
+
done = false
|
75
|
+
|
76
|
+
while !done
|
77
|
+
current_min = @min.value
|
78
|
+
done = (!current_min.nil? && current_min <= potential_min) || @min.compare_and_swap(current_min, potential_min)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def update_variance(value)
|
83
|
+
@variance.update do |old_values|
|
84
|
+
new_values = Array.new(2)
|
85
|
+
if old_values[0] == -1
|
86
|
+
new_values[0] = value
|
87
|
+
new_values[1] = 0
|
88
|
+
else
|
89
|
+
old_m = old_values[0]
|
90
|
+
old_s = old_values[1]
|
91
|
+
|
92
|
+
new_m = old_m + ((value - old_m) / count)
|
93
|
+
new_s = old_s + ((value - old_m) * (value - new_m))
|
94
|
+
|
95
|
+
new_values[0] = new_m
|
96
|
+
new_values[1] = new_s
|
97
|
+
end
|
98
|
+
|
99
|
+
new_values
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'atomic'
|
2
|
+
|
3
|
+
require 'metriks/ewma'
|
4
|
+
|
5
|
+
module Metriks
|
6
|
+
class Meter
|
7
|
+
def initialize(averager_klass = Metriks::EWMA)
|
8
|
+
@count = Atomic.new(0)
|
9
|
+
@start_time = Time.now
|
10
|
+
|
11
|
+
@m1_rate = averager_klass.new_m1
|
12
|
+
@m5_rate = averager_klass.new_m5
|
13
|
+
@m15_rate = averager_klass.new_m15
|
14
|
+
|
15
|
+
@thread = Thread.new do
|
16
|
+
loop do
|
17
|
+
sleep averager_klass::INTERVAL
|
18
|
+
tick
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def clear
|
24
|
+
@count.value = 0
|
25
|
+
@start_time = Time.now
|
26
|
+
@m1_rate.clear
|
27
|
+
@m5_rate.clear
|
28
|
+
@m15_rate.clear
|
29
|
+
end
|
30
|
+
|
31
|
+
def tick
|
32
|
+
@m1_rate.tick
|
33
|
+
@m5_rate.tick
|
34
|
+
@m15_rate.tick
|
35
|
+
end
|
36
|
+
|
37
|
+
def mark(val = 1)
|
38
|
+
@count.update { |v| v + val }
|
39
|
+
@m1_rate.update(val)
|
40
|
+
@m5_rate.update(val)
|
41
|
+
@m15_rate.update(val)
|
42
|
+
end
|
43
|
+
|
44
|
+
def count
|
45
|
+
@count.value
|
46
|
+
end
|
47
|
+
|
48
|
+
def one_minute_rate
|
49
|
+
@m1_rate.rate
|
50
|
+
end
|
51
|
+
|
52
|
+
def five_minute_rate
|
53
|
+
@m5_rate.rate
|
54
|
+
end
|
55
|
+
|
56
|
+
def fifteen_minute_rate
|
57
|
+
@m15_rate.rate
|
58
|
+
end
|
59
|
+
|
60
|
+
def mean_rate
|
61
|
+
if count == 0
|
62
|
+
return 0.0
|
63
|
+
else
|
64
|
+
elapsed = Time.now - @start_time
|
65
|
+
count / elapsed
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def stop
|
70
|
+
@thread.kill
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,174 @@
|
|
1
|
+
require 'metriks/counter'
|
2
|
+
require 'metriks/timer'
|
3
|
+
require 'metriks/utilization_timer'
|
4
|
+
require 'metriks/meter'
|
5
|
+
|
6
|
+
module Metriks
|
7
|
+
# Public: A collection of metrics
|
8
|
+
class Registry
|
9
|
+
# Public: The default registry for the process.
|
10
|
+
#
|
11
|
+
# Returns the default Registry for the process.
|
12
|
+
def self.default
|
13
|
+
@default ||= new
|
14
|
+
end
|
15
|
+
|
16
|
+
# Public: Initializes a new Registry.
|
17
|
+
def initialize
|
18
|
+
@mutex = Mutex.new
|
19
|
+
@metrics = {}
|
20
|
+
end
|
21
|
+
|
22
|
+
# Public: Clear all of the metrics in the Registry. This ensures all
|
23
|
+
# metrics that have been added are stopped.
|
24
|
+
#
|
25
|
+
# Returns nothing.
|
26
|
+
def clear
|
27
|
+
@mutex.synchronize do
|
28
|
+
@metrics.each do |key, metric|
|
29
|
+
metric.stop if metric.respond_to?(:stop)
|
30
|
+
end
|
31
|
+
|
32
|
+
@metrics = {}
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# Public: Clear all of the metrics in the Registry. This has the same
|
37
|
+
# effect as calling #clear.
|
38
|
+
#
|
39
|
+
# Returns nothing.
|
40
|
+
def stop
|
41
|
+
clear
|
42
|
+
end
|
43
|
+
|
44
|
+
# Public: Iterate over all of the counters.
|
45
|
+
#
|
46
|
+
# Examples
|
47
|
+
#
|
48
|
+
# registry.each do |name, metric|
|
49
|
+
# puts name
|
50
|
+
# end
|
51
|
+
#
|
52
|
+
# Returns nothing.
|
53
|
+
def each(&block)
|
54
|
+
metrics = @mutex.synchronize do
|
55
|
+
@metrics.dup
|
56
|
+
end
|
57
|
+
|
58
|
+
metrics.each(&block)
|
59
|
+
end
|
60
|
+
|
61
|
+
# Public: Fetch or create a new counter metric. Counters are one of the
|
62
|
+
# simplest metrics whose only operations are increment and decrement.
|
63
|
+
#
|
64
|
+
# name - The String name of the metric to define or fetch
|
65
|
+
#
|
66
|
+
# Examples
|
67
|
+
#
|
68
|
+
# registry.counter('method.calls')
|
69
|
+
#
|
70
|
+
# Returns the Metricks::Counter identified by the name.
|
71
|
+
def counter(name)
|
72
|
+
add_or_get(name, Metriks::Counter)
|
73
|
+
end
|
74
|
+
|
75
|
+
# Public: Fetch or create a new meter metric. Meters are a counter that
|
76
|
+
# tracks throughput along with the count.
|
77
|
+
#
|
78
|
+
# name - The String name of the metric to define or fetch
|
79
|
+
#
|
80
|
+
# Examples
|
81
|
+
#
|
82
|
+
# registry.meter('resque.calls')
|
83
|
+
#
|
84
|
+
# Returns the Metricks::Meter identified by the name.
|
85
|
+
def meter(name)
|
86
|
+
add_or_get(name, Metriks::Meter)
|
87
|
+
end
|
88
|
+
|
89
|
+
# Public: Fetch or create a new timer metric. Timers provide the means to
|
90
|
+
# time the execution of a method including statistics on the number of
|
91
|
+
# invocations, average length of time, throughput.
|
92
|
+
#
|
93
|
+
# name - The String name of the metric to define or fetch
|
94
|
+
#
|
95
|
+
# Examples
|
96
|
+
#
|
97
|
+
# registry.timer('resque.worker')
|
98
|
+
#
|
99
|
+
# Returns the Metricks::Timer identified by the name.
|
100
|
+
def timer(name)
|
101
|
+
add_or_get(name, Metriks::Timer)
|
102
|
+
end
|
103
|
+
|
104
|
+
# Public: Fetch or create a new utilization timer metric.
|
105
|
+
#
|
106
|
+
# Utilization timers are a specialized version of a timer that calculate
|
107
|
+
# the percentage of wall-clock time (between 0 and 1) that was spent in
|
108
|
+
# the method. This metric is most valuable in a single-threaded
|
109
|
+
# environment where a processes is waiting on an external resource like a
|
110
|
+
# message queue or HTTP server.
|
111
|
+
#
|
112
|
+
# name - The String name of the metric to define or fetch
|
113
|
+
#
|
114
|
+
# Examples
|
115
|
+
#
|
116
|
+
# registry.utilization_timer('rack.utilization')
|
117
|
+
#
|
118
|
+
# Returns the Metricks::UtilizationTimer identified by the name.
|
119
|
+
def utilization_timer(name)
|
120
|
+
add_or_get(name, Metriks::UtilizationTimer)
|
121
|
+
end
|
122
|
+
|
123
|
+
# Public: Fetch an existing metric.
|
124
|
+
#
|
125
|
+
# name - The String name of the metric to fetch
|
126
|
+
#
|
127
|
+
# Examples
|
128
|
+
#
|
129
|
+
# registry.get('rack.utilization')
|
130
|
+
#
|
131
|
+
# Returns the metric or nil.
|
132
|
+
def get(name)
|
133
|
+
@mutex.synchronize do
|
134
|
+
@metrics[name]
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
# Public: Add a new metric.
|
139
|
+
#
|
140
|
+
# name - The String name of the metric to add
|
141
|
+
# metric - The metric instance to add
|
142
|
+
#
|
143
|
+
# Examples
|
144
|
+
#
|
145
|
+
# registry.add('method.calls', Metriks::Counter.new)
|
146
|
+
#
|
147
|
+
# Returns nothing.
|
148
|
+
# Raises RuntimeError if the metric name is already defined
|
149
|
+
def add(name, metric)
|
150
|
+
@mutex.synchronize do
|
151
|
+
if @metrics[name]
|
152
|
+
raise "Metric '#{name}' already defined"
|
153
|
+
else
|
154
|
+
@metrics[name] = metric
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
protected
|
160
|
+
def add_or_get(name, klass)
|
161
|
+
@mutex.synchronize do
|
162
|
+
if metric = @metrics[name]
|
163
|
+
if !metric.is_a?(klass)
|
164
|
+
raise "Metric already defined as '#{metric.class}'"
|
165
|
+
else
|
166
|
+
return metric
|
167
|
+
end
|
168
|
+
else
|
169
|
+
@metrics[name] = klass.new
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|