rubycut-metriks 0.9.9.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -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,102 @@
1
+ require 'atomic'
2
+ require 'red_black_tree'
3
+ require 'metriks/snapshot'
4
+
5
+ module Metriks
6
+ class ExponentiallyDecayingSample
7
+ RESCALE_THRESHOLD = 60 * 60 # 1 hour
8
+
9
+ def initialize(reservoir_size, alpha, values = nil)
10
+ @values = values || RedBlackTree.new
11
+ @count = Atomic.new(0)
12
+ @next_scale_time = Atomic.new(0)
13
+ @alpha = alpha
14
+ @reservoir_size = reservoir_size
15
+ @mutex = Mutex.new
16
+ clear
17
+ end
18
+
19
+ def clear
20
+ @mutex.synchronize do
21
+ @values.clear
22
+ @count.value = 0
23
+ @next_scale_time.value = Time.now + RESCALE_THRESHOLD
24
+ @start_time = Time.now
25
+ end
26
+ end
27
+
28
+ def size
29
+ count = @count.value
30
+ count < @reservoir_size ? count : @reservoir_size
31
+ end
32
+
33
+ def snapshot
34
+ @mutex.synchronize do
35
+ Snapshot.new(@values.values)
36
+ end
37
+ end
38
+
39
+ def update(value, timestamp = Time.now)
40
+ @mutex.synchronize do
41
+ priority = weight(timestamp - @start_time) / rand
42
+ priority = Float::MAX if priority.infinite?
43
+ new_count = @count.update { |v| v + 1 }
44
+
45
+ if priority.nan?
46
+ warn "ExponentiallyDecayingSample found priority of NaN. timestamp: #{timestamp.to_f} start_time: #{@start_time.to_f}"
47
+ return
48
+ end
49
+
50
+ if new_count <= @reservoir_size
51
+ @values[priority] = value
52
+ else
53
+ first_priority = @values.first[0]
54
+ if first_priority < priority
55
+ unless @values[priority]
56
+ @values[priority] = value
57
+
58
+ until @values.delete(first_priority)
59
+ first_priority = @values.first[0]
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
65
+
66
+ now = Time.new
67
+ next_time = @next_scale_time.value
68
+ if now >= next_time
69
+ rescale(now, next_time)
70
+ end
71
+ end
72
+
73
+ def weight(time)
74
+ Math.exp(@alpha * time)
75
+ end
76
+
77
+ def rescale(now, next_time)
78
+ if @next_scale_time.compare_and_swap(next_time, now + RESCALE_THRESHOLD)
79
+ @mutex.synchronize do
80
+ old_start_time = @start_time
81
+ @start_time = Time.now
82
+ @values.keys.each do |key|
83
+ value = @values.delete(key)
84
+ new_key = key * Math.exp(-@alpha * (@start_time - old_start_time))
85
+
86
+ if key.nan?
87
+ warn "ExponentiallyDecayingSample found a key of NaN. old_start_time: #{old_start_time.to_f} start_time: #{@start_time.to_f}"
88
+ next
89
+ end
90
+
91
+ if new_key.nan?
92
+ warn "ExponentiallyDecayingSample found a new_key of NaN. key: #{key} old_start_time: #{old_start_time.to_f} start_time: #{@start_time.to_f}"
93
+ next
94
+ end
95
+
96
+ @values[new_key] = value
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,112 @@
1
+ require 'atomic'
2
+ require 'metriks/uniform_sample'
3
+ require 'metriks/exponentially_decaying_sample'
4
+
5
+ module Metriks
6
+ class Histogram
7
+ DEFAULT_SAMPLE_SIZE = 1028
8
+ DEFAULT_ALPHA = 0.015
9
+
10
+ def self.new_uniform
11
+ new(Metriks::UniformSample.new(DEFAULT_SAMPLE_SIZE))
12
+ end
13
+
14
+ def self.new_exponentially_decaying
15
+ new(Metriks::ExponentiallyDecayingSample.new(DEFAULT_SAMPLE_SIZE, DEFAULT_ALPHA))
16
+ end
17
+
18
+ def initialize(sample)
19
+ @sample = sample
20
+ @count = Atomic.new(0)
21
+ @min = Atomic.new(nil)
22
+ @max = Atomic.new(nil)
23
+ @sum = Atomic.new(0)
24
+ @variance = Atomic.new([ -1, 0 ])
25
+ end
26
+
27
+ def clear
28
+ @sample.clear
29
+ @count.value = 0
30
+ @min.value = nil
31
+ @max.value = nil
32
+ @sum.value = 0
33
+ @variance.value = [ -1, 0 ]
34
+ end
35
+
36
+ def update(value)
37
+ @count.update { |v| v + 1 }
38
+ @sample.update(value)
39
+ self.max = value
40
+ self.min = value
41
+ @sum.update { |v| v + value }
42
+ update_variance(value)
43
+ end
44
+
45
+ def snapshot
46
+ @sample.snapshot
47
+ end
48
+
49
+ def count
50
+ @count.value
51
+ end
52
+
53
+ def max
54
+ count > 0 ? @max.value : 0.0
55
+ end
56
+
57
+ def min
58
+ count > 0 ? @min.value : 0.0
59
+ end
60
+
61
+ def mean
62
+ count > 0 ? @sum.value / count : 0.0
63
+ end
64
+
65
+ def stddev
66
+ count > 0 ? variance ** 0.5 : 0.0
67
+ end
68
+
69
+ def variance
70
+ count <= 1 ? 0.0 : @variance.value[1] / (count - 1)
71
+ end
72
+
73
+ def max=(potential_max)
74
+ done = false
75
+
76
+ while !done
77
+ current_max = @max.value
78
+ done = (!current_max.nil? && current_max >= potential_max) || @max.compare_and_swap(current_max, potential_max)
79
+ end
80
+ end
81
+
82
+ def min=(potential_min)
83
+ done = false
84
+
85
+ while !done
86
+ current_min = @min.value
87
+ done = (!current_min.nil? && current_min <= potential_min) || @min.compare_and_swap(current_min, potential_min)
88
+ end
89
+ end
90
+
91
+ def update_variance(value)
92
+ @variance.update do |old_values|
93
+ new_values = Array.new(2)
94
+ if old_values[0] == -1
95
+ new_values[0] = value
96
+ new_values[1] = 0
97
+ else
98
+ old_m = old_values[0]
99
+ old_s = old_values[1]
100
+
101
+ new_m = old_m + ((value - old_m) / count)
102
+ new_s = old_s + ((value - old_m) * (value - new_m))
103
+
104
+ new_values[0] = new_m
105
+ new_values[1] = new_s
106
+ end
107
+
108
+ new_values
109
+ end
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,85 @@
1
+ require 'atomic'
2
+
3
+ require 'metriks/ewma'
4
+
5
+ module Metriks
6
+ class Meter
7
+ TICK_INTERVAL = 5.0
8
+
9
+ def initialize(averager_klass = Metriks::EWMA)
10
+ @count = Atomic.new(0)
11
+ @start_time = Time.now.to_f
12
+ @last_tick = Atomic.new(@start_time)
13
+
14
+ @m1_rate = averager_klass.new_m1
15
+ @m5_rate = averager_klass.new_m5
16
+ @m15_rate = averager_klass.new_m15
17
+ end
18
+
19
+ def clear
20
+ @count.value = 0
21
+ @start_time = Time.now.to_f
22
+ @last_tick.value = @start_time
23
+ @m1_rate.clear
24
+ @m5_rate.clear
25
+ @m15_rate.clear
26
+ end
27
+
28
+ def tick
29
+ @m1_rate.tick
30
+ @m5_rate.tick
31
+ @m15_rate.tick
32
+ end
33
+
34
+ def tick_if_nessesary
35
+ old_tick = @last_tick.value
36
+ new_tick = Time.new.to_f
37
+ age = new_tick - old_tick
38
+ if age > TICK_INTERVAL && @last_tick.compare_and_swap(old_tick, new_tick)
39
+ required_ticks = age / TICK_INTERVAL
40
+ required_ticks.to_i.times do
41
+ tick
42
+ end
43
+ end
44
+ end
45
+
46
+ def mark(val = 1)
47
+ tick_if_nessesary
48
+ @count.update { |v| v + val }
49
+ @m1_rate.update(val)
50
+ @m5_rate.update(val)
51
+ @m15_rate.update(val)
52
+ end
53
+
54
+ def count
55
+ @count.value
56
+ end
57
+
58
+ def one_minute_rate
59
+ tick_if_nessesary
60
+ @m1_rate.rate
61
+ end
62
+
63
+ def five_minute_rate
64
+ tick_if_nessesary
65
+ @m5_rate.rate
66
+ end
67
+
68
+ def fifteen_minute_rate
69
+ tick_if_nessesary
70
+ @m15_rate.rate
71
+ end
72
+
73
+ def mean_rate
74
+ if count == 0
75
+ return 0.0
76
+ else
77
+ elapsed = Time.now.to_f - @start_time
78
+ count / elapsed
79
+ end
80
+ end
81
+
82
+ def stop
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,207 @@
1
+ require 'metriks/counter'
2
+ require 'metriks/timer'
3
+ require 'metriks/utilization_timer'
4
+ require 'metriks/meter'
5
+ require 'metriks/gauge'
6
+
7
+ module Metriks
8
+ # Public: A collection of metrics
9
+ class Registry
10
+ # Public: The default registry for the process.
11
+ #
12
+ # Returns the default Registry for the process.
13
+ def self.default
14
+ @default ||= new
15
+ end
16
+
17
+ # Public: Initializes a new Registry.
18
+ def initialize
19
+ @mutex = Mutex.new
20
+ @metrics = {}
21
+ end
22
+
23
+ # Public: Clear all of the metrics in the Registry. This ensures all
24
+ # metrics that have been added are stopped.
25
+ #
26
+ # Returns nothing.
27
+ def clear
28
+ @mutex.synchronize do
29
+ @metrics.each do |key, metric|
30
+ metric.stop if metric.respond_to?(:stop)
31
+ end
32
+
33
+ @metrics = {}
34
+ end
35
+ end
36
+
37
+ # Public: Clear all of the metrics in the Registry. This has the same
38
+ # effect as calling #clear.
39
+ #
40
+ # Returns nothing.
41
+ def stop
42
+ clear
43
+ end
44
+
45
+ # Public: Iterate over all of the counters.
46
+ #
47
+ # Examples
48
+ #
49
+ # registry.each do |name, metric|
50
+ # puts name
51
+ # end
52
+ #
53
+ # Returns nothing.
54
+ def each(&block)
55
+ metrics = @mutex.synchronize do
56
+ @metrics.dup
57
+ end
58
+
59
+ metrics.each(&block)
60
+ end
61
+
62
+ # Public: Fetch or create a new counter metric. Counters are one of the
63
+ # simplest metrics whose only operations are increment and decrement.
64
+ #
65
+ # name - The String name of the metric to define or fetch
66
+ #
67
+ # Examples
68
+ #
69
+ # registry.counter('method.calls')
70
+ #
71
+ # Returns the Metriks::Counter identified by the name.
72
+ def counter(name)
73
+ add_or_get(name, Metriks::Counter)
74
+ end
75
+
76
+ # Public: Fetch or create a new gauge metric.
77
+ #
78
+ # name - The String name of the metric to define or fetch
79
+ #
80
+ # Examples
81
+ #
82
+ # registry.gauge('disk_space.used') { 1 }
83
+ #
84
+ # Returns the Metriks::Gauge identified by the name.
85
+ def gauge(name, callable = nil, &block)
86
+ add_or_get(name, Metriks::Gauge) do
87
+ Metriks::Gauge.new(callable, &block)
88
+ end
89
+ end
90
+
91
+ # Public: Fetch or create a new meter metric. Meters are a counter that
92
+ # tracks throughput along with the count.
93
+ #
94
+ # name - The String name of the metric to define or fetch
95
+ #
96
+ # Examples
97
+ #
98
+ # registry.meter('resque.calls')
99
+ #
100
+ # Returns the Metriks::Meter identified by the name.
101
+ def meter(name)
102
+ add_or_get(name, Metriks::Meter)
103
+ end
104
+
105
+ # Public: Fetch or create a new timer metric. Timers provide the means to
106
+ # time the execution of a method including statistics on the number of
107
+ # invocations, average length of time, throughput.
108
+ #
109
+ # name - The String name of the metric to define or fetch
110
+ #
111
+ # Examples
112
+ #
113
+ # registry.timer('resque.worker')
114
+ #
115
+ # Returns the Metriks::Timer identified by the name.
116
+ def timer(name)
117
+ add_or_get(name, Metriks::Timer)
118
+ end
119
+
120
+ # Public: Fetch or create a new utilization timer metric.
121
+ #
122
+ # Utilization timers are a specialized version of a timer that calculate
123
+ # the percentage of wall-clock time (between 0 and 1) that was spent in
124
+ # the method. This metric is most valuable in a single-threaded
125
+ # environment where a processes is waiting on an external resource like a
126
+ # message queue or HTTP server.
127
+ #
128
+ # name - The String name of the metric to define or fetch
129
+ #
130
+ # Examples
131
+ #
132
+ # registry.utilization_timer('rack.utilization')
133
+ #
134
+ # Returns the Metriks::UtilizationTimer identified by the name.
135
+ def utilization_timer(name)
136
+ add_or_get(name, Metriks::UtilizationTimer)
137
+ end
138
+
139
+ # Public: Fetch or create a new histogram metric. Histograms record values
140
+ # and expose statistics about the distribution of the data like median and
141
+ # 95th percentile.
142
+ #
143
+ # name - The String name of the metric to define or fetch
144
+ #
145
+ # Examples
146
+ #
147
+ # registry.histogram('backlog.wait')
148
+ #
149
+ # Returns the Metriks::Histogram identified by the name.
150
+ def histogram(name)
151
+ add_or_get(name, Metriks::Histogram) do
152
+ Metriks::Histogram.new_exponentially_decaying
153
+ end
154
+ end
155
+
156
+ # Public: Fetch an existing metric.
157
+ #
158
+ # name - The String name of the metric to fetch
159
+ #
160
+ # Examples
161
+ #
162
+ # registry.get('rack.utilization')
163
+ #
164
+ # Returns the metric or nil.
165
+ def get(name)
166
+ @mutex.synchronize do
167
+ @metrics[name]
168
+ end
169
+ end
170
+
171
+ # Public: Add a new metric.
172
+ #
173
+ # name - The String name of the metric to add
174
+ # metric - The metric instance to add
175
+ #
176
+ # Examples
177
+ #
178
+ # registry.add('method.calls', Metriks::Counter.new)
179
+ #
180
+ # Returns nothing.
181
+ # Raises RuntimeError if the metric name is already defined
182
+ def add(name, metric)
183
+ @mutex.synchronize do
184
+ if @metrics[name]
185
+ raise "Metric '#{name}' already defined"
186
+ else
187
+ @metrics[name] = metric
188
+ end
189
+ end
190
+ end
191
+
192
+ protected
193
+ def add_or_get(name, klass, &create_metric)
194
+ @mutex.synchronize do
195
+ if metric = @metrics[name]
196
+ if !metric.is_a?(klass)
197
+ raise "Metric already defined as '#{metric.class}'"
198
+ else
199
+ return metric
200
+ end
201
+ else
202
+ @metrics[name] = create_metric ? create_metric.call : klass.new
203
+ end
204
+ end
205
+ end
206
+ end
207
+ end