twingly-metrics 0.5.0 → 0.6.0
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.
- checksums.yaml +4 -4
- data/README.md +1 -2
- data/lib/twingly/metrics/counter.rb +48 -0
- data/lib/twingly/metrics/ewma.rb +67 -0
- data/lib/twingly/metrics/exponentially_decaying_sample.rb +107 -0
- data/lib/twingly/metrics/gauge.rb +31 -0
- data/lib/twingly/metrics/grafana_cloud_reporter.rb +8 -8
- data/lib/twingly/metrics/histogram.rb +123 -0
- data/lib/twingly/metrics/meter.rb +86 -0
- data/lib/twingly/metrics/registry.rb +209 -0
- data/lib/twingly/metrics/simple_moving_average.rb +68 -0
- data/lib/twingly/metrics/snapshot.rb +65 -0
- data/lib/twingly/metrics/time_tracker.rb +30 -0
- data/lib/twingly/metrics/timer.rb +106 -0
- data/lib/twingly/metrics/uniform_sample.rb +42 -0
- data/lib/twingly/metrics/utilization_timer.rb +47 -0
- data/lib/twingly/metrics/version.rb +1 -1
- data/lib/twingly/metrics.rb +25 -8
- metadata +33 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3d5cfa50a735b939760dc8242470280217152c47bca4bc4e0de092d405529399
|
|
4
|
+
data.tar.gz: e754a813aeeb419c14c33c342f04781c6cd544af203b3a2c2f74e9d999a2260e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9d41769505fd9f03261afc987f33cb469b61ca5273aeedb624718062848c05866dde2abc28afbe9ec409ab5406aa21c087da0e663515cfdb921bd1cefb962699
|
|
7
|
+
data.tar.gz: 0bf2a9be9289336f4cd04be6d1af51f23125c20c986768be19d74e613790427d3b8cd4704f813539f38df8ecc5593da24b145091d43ef27099399dccdd5ff1e5
|
data/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
A gem for collecting and reporting metrics from our Ruby applications.
|
|
6
6
|
|
|
7
|
-
This gem
|
|
7
|
+
This gem provides its own implementation of common metric types and the ability to report them to [Grafana Cloud][grafana-cloud] using their [Graphite HTTP API][grafana-cloud-graphite-api].
|
|
8
8
|
|
|
9
9
|
The code here was inspired by the reporter in the [`metriks-librato_metrics` gem][metriks-librato_metrics].
|
|
10
10
|
|
|
@@ -86,7 +86,6 @@ Twingly::Metrics.mark("my_meter", 1)
|
|
|
86
86
|
Twingly::Metrics.time("my_timer") { ... }
|
|
87
87
|
```
|
|
88
88
|
|
|
89
|
-
[metriks]: https://github.com/eric/metriks
|
|
90
89
|
[grafana-cloud]: https://grafana.com/products/cloud/
|
|
91
90
|
[grafana-cloud-graphite-api]: https://grafana.com/docs/grafana-cloud/send-data/metrics/metrics-graphite/http-api/#adding-new-data-posting-to-metrics
|
|
92
91
|
[metriks-librato_metrics]: https://github.com/eric/metriks-librato_metrics
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "atomic"
|
|
4
|
+
|
|
5
|
+
module Twingly
|
|
6
|
+
module Metrics
|
|
7
|
+
# Public: Counters are one of the simplest metrics whose only operations
|
|
8
|
+
# are increment and decrement.
|
|
9
|
+
class Counter
|
|
10
|
+
# Public: Initialize a new Counter.
|
|
11
|
+
def initialize
|
|
12
|
+
@count = Atomic.new(0)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Public: Reset the counter back to 0
|
|
16
|
+
#
|
|
17
|
+
# Returns nothing.
|
|
18
|
+
def clear
|
|
19
|
+
@count.value = 0
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Public: Increment the counter.
|
|
23
|
+
#
|
|
24
|
+
# incr - The value to add to the counter.
|
|
25
|
+
#
|
|
26
|
+
# Returns nothing.
|
|
27
|
+
def increment(incr = 1)
|
|
28
|
+
@count.update { |v| v + incr }
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Public: Decrement the counter.
|
|
32
|
+
#
|
|
33
|
+
# decr - The value to subtract from the counter.
|
|
34
|
+
#
|
|
35
|
+
# Returns nothing.
|
|
36
|
+
def decrement(decr = 1)
|
|
37
|
+
@count.update { |v| v - decr }
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Public: The current count.
|
|
41
|
+
#
|
|
42
|
+
# Returns the count.
|
|
43
|
+
def count
|
|
44
|
+
@count.value
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "atomic"
|
|
4
|
+
|
|
5
|
+
module Twingly
|
|
6
|
+
module Metrics
|
|
7
|
+
class EWMA
|
|
8
|
+
INTERVAL = 5.0
|
|
9
|
+
SECONDS_PER_MINUTE = 60.0
|
|
10
|
+
|
|
11
|
+
ONE_MINUTE = 1
|
|
12
|
+
FIVE_MINUTES = 5
|
|
13
|
+
FIFTEEN_MINUTES = 15
|
|
14
|
+
|
|
15
|
+
M1_ALPHA = 1 - Math.exp(-INTERVAL / SECONDS_PER_MINUTE / ONE_MINUTE)
|
|
16
|
+
M5_ALPHA = 1 - Math.exp(-INTERVAL / SECONDS_PER_MINUTE / FIVE_MINUTES)
|
|
17
|
+
M15_ALPHA = 1 - Math.exp(-INTERVAL / SECONDS_PER_MINUTE / FIFTEEN_MINUTES)
|
|
18
|
+
|
|
19
|
+
def self.new_m1
|
|
20
|
+
new(M1_ALPHA, INTERVAL)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def self.new_m5
|
|
24
|
+
new(M5_ALPHA, INTERVAL)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def self.new_m15
|
|
28
|
+
new(M15_ALPHA, INTERVAL)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def initialize(alpha, interval)
|
|
32
|
+
@alpha = alpha
|
|
33
|
+
@interval = interval
|
|
34
|
+
|
|
35
|
+
@initialized = false
|
|
36
|
+
@rate = Atomic.new(0.0)
|
|
37
|
+
@uncounted = Atomic.new(0)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def clear
|
|
41
|
+
@initialized = false
|
|
42
|
+
@rate.value = 0.0
|
|
43
|
+
@uncounted.value = 0
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def update(value)
|
|
47
|
+
@uncounted.update { |v| v + value }
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def tick
|
|
51
|
+
count = @uncounted.swap(0)
|
|
52
|
+
instant_rate = count / @interval.to_f
|
|
53
|
+
|
|
54
|
+
if @initialized
|
|
55
|
+
@rate.update { |v| v + (@alpha * (instant_rate - v)) }
|
|
56
|
+
else
|
|
57
|
+
@rate.value = instant_rate
|
|
58
|
+
@initialized = true
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def rate
|
|
63
|
+
@rate.value
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "atomic"
|
|
4
|
+
require "red_black_tree"
|
|
5
|
+
require_relative "snapshot"
|
|
6
|
+
|
|
7
|
+
module Twingly
|
|
8
|
+
module Metrics
|
|
9
|
+
class ExponentiallyDecayingSample
|
|
10
|
+
RESCALE_THRESHOLD = 60 * 60 # 1 hour
|
|
11
|
+
|
|
12
|
+
def initialize(reservoir_size, alpha, values = nil)
|
|
13
|
+
@values = values || ConcurrentRedBlackTree.new
|
|
14
|
+
@count = Atomic.new(0)
|
|
15
|
+
@next_scale_time = Atomic.new(0)
|
|
16
|
+
@alpha = alpha
|
|
17
|
+
@reservoir_size = reservoir_size
|
|
18
|
+
@mutex = Mutex.new
|
|
19
|
+
clear
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def clear
|
|
23
|
+
@mutex.synchronize do
|
|
24
|
+
@values.clear
|
|
25
|
+
@count.value = 0
|
|
26
|
+
@next_scale_time.value = Time.now + RESCALE_THRESHOLD
|
|
27
|
+
@start_time = Time.now
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def size
|
|
32
|
+
count = @count.value
|
|
33
|
+
[count, @reservoir_size].min
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def snapshot
|
|
37
|
+
@mutex.synchronize do
|
|
38
|
+
Snapshot.new(@values.values)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def update(value, timestamp = Time.now) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
|
|
43
|
+
@mutex.synchronize do
|
|
44
|
+
priority = weight(timestamp - @start_time) / rand
|
|
45
|
+
priority = Float::MAX if priority.infinite?
|
|
46
|
+
new_count = @count.update { |v| v + 1 }
|
|
47
|
+
|
|
48
|
+
if priority.nan?
|
|
49
|
+
warn "ExponentiallyDecayingSample found priority of NaN. " \
|
|
50
|
+
"timestamp: #{timestamp.to_f} start_time: #{@start_time.to_f}"
|
|
51
|
+
return
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
if new_count <= @reservoir_size
|
|
55
|
+
@values[priority] = value
|
|
56
|
+
else
|
|
57
|
+
first_priority = @values.first[0]
|
|
58
|
+
if first_priority < priority
|
|
59
|
+
unless @values[priority] # rubocop:disable Style/SoleNestedConditional
|
|
60
|
+
@values[priority] = value
|
|
61
|
+
|
|
62
|
+
first_priority = @values.first[0] until @values.delete(first_priority) # rubocop:disable Metrics/BlockNesting
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
now = Time.new
|
|
69
|
+
next_time = @next_scale_time.value
|
|
70
|
+
return unless now >= next_time
|
|
71
|
+
|
|
72
|
+
rescale(now, next_time)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def weight(time)
|
|
76
|
+
Math.exp(@alpha * time)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def rescale(now, next_time) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
|
|
80
|
+
return unless @next_scale_time.compare_and_swap(next_time, now + RESCALE_THRESHOLD)
|
|
81
|
+
|
|
82
|
+
@mutex.synchronize do
|
|
83
|
+
old_start_time = @start_time
|
|
84
|
+
@start_time = Time.now
|
|
85
|
+
@values.keys.each do |key|
|
|
86
|
+
value = @values.delete(key)
|
|
87
|
+
new_key = key * Math.exp(-@alpha * (@start_time - old_start_time))
|
|
88
|
+
|
|
89
|
+
if key.nan?
|
|
90
|
+
warn "ExponentiallyDecayingSample found a key of NaN. " \
|
|
91
|
+
"old_start_time: #{old_start_time.to_f} start_time: #{@start_time.to_f}"
|
|
92
|
+
next
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
if new_key.nan?
|
|
96
|
+
warn "ExponentiallyDecayingSample found a new_key of NaN. " \
|
|
97
|
+
"key: #{key} old_start_time: #{old_start_time.to_f} start_time: #{@start_time.to_f}"
|
|
98
|
+
next
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
@values[new_key] = value
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "atomic"
|
|
4
|
+
|
|
5
|
+
module Twingly
|
|
6
|
+
module Metrics
|
|
7
|
+
class Gauge
|
|
8
|
+
# Public: Initialize a new Gauge.
|
|
9
|
+
def initialize(callable = nil, &block)
|
|
10
|
+
@gauge = Atomic.new(nil)
|
|
11
|
+
@callback = callable || block
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Public: Set a new value.
|
|
15
|
+
#
|
|
16
|
+
# val - The new value.
|
|
17
|
+
#
|
|
18
|
+
# Returns nothing.
|
|
19
|
+
def set(val)
|
|
20
|
+
@gauge.value = val
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Public: The current value.
|
|
24
|
+
#
|
|
25
|
+
# Returns the gauge value.
|
|
26
|
+
def value
|
|
27
|
+
@callback ? @callback.call : @gauge.value
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
require "net/https"
|
|
4
4
|
require "json"
|
|
5
|
-
require "metriks"
|
|
6
5
|
require "socket"
|
|
6
|
+
require_relative "registry"
|
|
7
7
|
|
|
8
8
|
module Twingly
|
|
9
9
|
module Metrics
|
|
@@ -28,10 +28,10 @@ module Twingly
|
|
|
28
28
|
end
|
|
29
29
|
|
|
30
30
|
METRIC_TYPE_TO_BUILD_METHOD = {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
31
|
+
Twingly::Metrics::Meter => :build_meter_metric,
|
|
32
|
+
Twingly::Metrics::Counter => :build_counter_metric,
|
|
33
|
+
Twingly::Metrics::Gauge => :build_gauge_metric,
|
|
34
|
+
Twingly::Metrics::Timer => :build_timer_metric,
|
|
35
35
|
}.freeze
|
|
36
36
|
|
|
37
37
|
attr_reader :endpoint
|
|
@@ -55,7 +55,7 @@ module Twingly
|
|
|
55
55
|
|
|
56
56
|
log_missing_credentials unless credentials_provided?
|
|
57
57
|
|
|
58
|
-
@registry = options[:registry] ||
|
|
58
|
+
@registry = options[:registry] || Twingly::Metrics::Registry.default
|
|
59
59
|
@interval = options[:interval] || 60
|
|
60
60
|
@on_error = options[:on_error] || proc { |ex| @logger&.error(ex) }
|
|
61
61
|
@timeout = options[:timeout] || 10
|
|
@@ -158,7 +158,7 @@ module Twingly
|
|
|
158
158
|
|
|
159
159
|
begin
|
|
160
160
|
yield
|
|
161
|
-
rescue RequestFailedError => e
|
|
161
|
+
rescue RequestFailedError, Net::OpenTimeout, Net::ReadTimeout => e
|
|
162
162
|
retries += 1
|
|
163
163
|
|
|
164
164
|
raise if retries > max_retries
|
|
@@ -223,7 +223,7 @@ module Twingly
|
|
|
223
223
|
|
|
224
224
|
time_metrics << build_grafana_metric("#{name}.median", snapshot.median, time)
|
|
225
225
|
|
|
226
|
-
build_timer_metric_percentiles(name, snapshot, time) { |
|
|
226
|
+
build_timer_metric_percentiles(name, snapshot, time) { |percentile_metric| time_metrics << percentile_metric }
|
|
227
227
|
end
|
|
228
228
|
end
|
|
229
229
|
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "atomic"
|
|
4
|
+
require_relative "snapshot"
|
|
5
|
+
require_relative "uniform_sample"
|
|
6
|
+
require_relative "exponentially_decaying_sample"
|
|
7
|
+
|
|
8
|
+
module Twingly
|
|
9
|
+
module Metrics
|
|
10
|
+
class Histogram
|
|
11
|
+
DEFAULT_SAMPLE_SIZE = 1028
|
|
12
|
+
DEFAULT_ALPHA = 0.015
|
|
13
|
+
|
|
14
|
+
def self.new_uniform
|
|
15
|
+
new(Twingly::Metrics::UniformSample.new(DEFAULT_SAMPLE_SIZE))
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def self.new_exponentially_decaying
|
|
19
|
+
new(Twingly::Metrics::ExponentiallyDecayingSample.new(DEFAULT_SAMPLE_SIZE, DEFAULT_ALPHA))
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def initialize(sample)
|
|
23
|
+
@sample = sample
|
|
24
|
+
@count = Atomic.new(0)
|
|
25
|
+
@min = Atomic.new(nil)
|
|
26
|
+
@max = Atomic.new(nil)
|
|
27
|
+
@sum = Atomic.new(0)
|
|
28
|
+
@variance = Atomic.new([-1, 0])
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def clear
|
|
32
|
+
@sample.clear
|
|
33
|
+
@count.value = 0
|
|
34
|
+
@min.value = nil
|
|
35
|
+
@max.value = nil
|
|
36
|
+
@sum.value = 0
|
|
37
|
+
@variance.value = [-1, 0]
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def update(value)
|
|
41
|
+
@count.update { |v| v + 1 }
|
|
42
|
+
@sample.update(value)
|
|
43
|
+
self.max = value
|
|
44
|
+
self.min = value
|
|
45
|
+
@sum.update { |v| v + value }
|
|
46
|
+
update_variance(value)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def snapshot
|
|
50
|
+
@sample.snapshot
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def count
|
|
54
|
+
@count.value
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def sum
|
|
58
|
+
@sum.value
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def max
|
|
62
|
+
count.positive? ? @max.value : 0.0
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def min
|
|
66
|
+
count.positive? ? @min.value : 0.0
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def mean
|
|
70
|
+
count.positive? ? @sum.value / count : 0.0
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def stddev
|
|
74
|
+
count.positive? ? variance**0.5 : 0.0
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def variance
|
|
78
|
+
count <= 1 ? 0.0 : @variance.value[1] / (count - 1)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def max=(potential_max)
|
|
82
|
+
done = false
|
|
83
|
+
|
|
84
|
+
until done
|
|
85
|
+
current_max = @max.value
|
|
86
|
+
done = (!current_max.nil? && current_max >= potential_max) || @max.compare_and_swap(current_max,
|
|
87
|
+
potential_max)
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def min=(potential_min)
|
|
92
|
+
done = false
|
|
93
|
+
|
|
94
|
+
until done
|
|
95
|
+
current_min = @min.value
|
|
96
|
+
done = (!current_min.nil? && current_min <= potential_min) || @min.compare_and_swap(current_min,
|
|
97
|
+
potential_min)
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def update_variance(value) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
|
|
102
|
+
@variance.update do |old_values|
|
|
103
|
+
new_values = Array.new(2)
|
|
104
|
+
if old_values[0] == -1
|
|
105
|
+
new_values[0] = value
|
|
106
|
+
new_values[1] = 0
|
|
107
|
+
else
|
|
108
|
+
old_m = old_values[0]
|
|
109
|
+
old_s = old_values[1]
|
|
110
|
+
|
|
111
|
+
new_m = old_m + ((value - old_m) / count)
|
|
112
|
+
new_s = old_s + ((value - old_m) * (value - new_m))
|
|
113
|
+
|
|
114
|
+
new_values[0] = new_m
|
|
115
|
+
new_values[1] = new_s
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
new_values
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "atomic"
|
|
4
|
+
|
|
5
|
+
require_relative "ewma"
|
|
6
|
+
|
|
7
|
+
module Twingly
|
|
8
|
+
module Metrics
|
|
9
|
+
class Meter
|
|
10
|
+
TICK_INTERVAL = 5.0
|
|
11
|
+
|
|
12
|
+
def initialize(averager_klass = Twingly::Metrics::EWMA)
|
|
13
|
+
@count = Atomic.new(0)
|
|
14
|
+
@start_time = Time.now.to_f
|
|
15
|
+
@last_tick = Atomic.new(@start_time)
|
|
16
|
+
|
|
17
|
+
@m1_rate = averager_klass.new_m1
|
|
18
|
+
@m5_rate = averager_klass.new_m5
|
|
19
|
+
@m15_rate = averager_klass.new_m15
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def clear
|
|
23
|
+
@count.value = 0
|
|
24
|
+
@start_time = Time.now.to_f
|
|
25
|
+
@last_tick.value = @start_time
|
|
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 tick_if_nessesary
|
|
38
|
+
old_tick = @last_tick.value
|
|
39
|
+
new_tick = Time.new.to_f
|
|
40
|
+
age = new_tick - old_tick
|
|
41
|
+
return unless age > TICK_INTERVAL && @last_tick.compare_and_swap(old_tick, new_tick)
|
|
42
|
+
|
|
43
|
+
required_ticks = age / TICK_INTERVAL
|
|
44
|
+
required_ticks.to_i.times do
|
|
45
|
+
tick
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def mark(val = 1)
|
|
50
|
+
tick_if_nessesary
|
|
51
|
+
@count.update { |v| v + val }
|
|
52
|
+
@m1_rate.update(val)
|
|
53
|
+
@m5_rate.update(val)
|
|
54
|
+
@m15_rate.update(val)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def count
|
|
58
|
+
@count.value
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def one_minute_rate
|
|
62
|
+
tick_if_nessesary
|
|
63
|
+
@m1_rate.rate
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def five_minute_rate
|
|
67
|
+
tick_if_nessesary
|
|
68
|
+
@m5_rate.rate
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def fifteen_minute_rate
|
|
72
|
+
tick_if_nessesary
|
|
73
|
+
@m15_rate.rate
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def mean_rate
|
|
77
|
+
return 0.0 if count.zero?
|
|
78
|
+
|
|
79
|
+
elapsed = Time.now.to_f - @start_time
|
|
80
|
+
count / elapsed
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def stop; end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "counter"
|
|
4
|
+
require_relative "timer"
|
|
5
|
+
require_relative "utilization_timer"
|
|
6
|
+
require_relative "meter"
|
|
7
|
+
require_relative "gauge"
|
|
8
|
+
|
|
9
|
+
module Twingly
|
|
10
|
+
module Metrics
|
|
11
|
+
# Public: A collection of metrics
|
|
12
|
+
class Registry
|
|
13
|
+
# Public: The default registry for the process.
|
|
14
|
+
#
|
|
15
|
+
# Returns the default Registry for the process.
|
|
16
|
+
def self.default
|
|
17
|
+
@default ||= new
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Public: Initializes a new Registry.
|
|
21
|
+
def initialize
|
|
22
|
+
@mutex = Mutex.new
|
|
23
|
+
@metrics = {}
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Public: Clear all of the metrics in the Registry. This ensures all
|
|
27
|
+
# metrics that have been added are stopped.
|
|
28
|
+
#
|
|
29
|
+
# Returns nothing.
|
|
30
|
+
def clear
|
|
31
|
+
@mutex.synchronize do
|
|
32
|
+
@metrics.each_value do |metric|
|
|
33
|
+
metric.stop if metric.respond_to?(:stop)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
@metrics = {}
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Public: Clear all of the metrics in the Registry. This has the same
|
|
41
|
+
# effect as calling #clear.
|
|
42
|
+
#
|
|
43
|
+
# Returns nothing.
|
|
44
|
+
def stop
|
|
45
|
+
clear
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Public: Iterate over all of the counters.
|
|
49
|
+
#
|
|
50
|
+
# Examples
|
|
51
|
+
#
|
|
52
|
+
# registry.each do |name, metric|
|
|
53
|
+
# puts name
|
|
54
|
+
# end
|
|
55
|
+
#
|
|
56
|
+
# Returns nothing.
|
|
57
|
+
def each(&block)
|
|
58
|
+
metrics = @mutex.synchronize do
|
|
59
|
+
@metrics.dup
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
metrics.each(&block)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Public: Fetch or create a new counter metric. Counters are one of the
|
|
66
|
+
# simplest metrics whose only operations are increment and decrement.
|
|
67
|
+
#
|
|
68
|
+
# name - The String name of the metric to define or fetch
|
|
69
|
+
#
|
|
70
|
+
# Examples
|
|
71
|
+
#
|
|
72
|
+
# registry.counter('method.calls')
|
|
73
|
+
#
|
|
74
|
+
# Returns the Metrics::Counter identified by the name.
|
|
75
|
+
def counter(name)
|
|
76
|
+
add_or_get(name, Metrics::Counter)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Public: Fetch or create a new gauge metric.
|
|
80
|
+
#
|
|
81
|
+
# name - The String name of the metric to define or fetch
|
|
82
|
+
#
|
|
83
|
+
# Examples
|
|
84
|
+
#
|
|
85
|
+
# registry.gauge('disk_space.used') { 1 }
|
|
86
|
+
#
|
|
87
|
+
# Returns the Metrics::Gauge identified by the name.
|
|
88
|
+
def gauge(name, callable = nil, &block)
|
|
89
|
+
add_or_get(name, Metrics::Gauge) do
|
|
90
|
+
Metrics::Gauge.new(callable, &block)
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Public: Fetch or create a new meter metric. Meters are a counter that
|
|
95
|
+
# tracks throughput along with the count.
|
|
96
|
+
#
|
|
97
|
+
# name - The String name of the metric to define or fetch
|
|
98
|
+
#
|
|
99
|
+
# Examples
|
|
100
|
+
#
|
|
101
|
+
# registry.meter('resque.calls')
|
|
102
|
+
#
|
|
103
|
+
# Returns the Metrics::Meter identified by the name.
|
|
104
|
+
def meter(name)
|
|
105
|
+
add_or_get(name, Metrics::Meter)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Public: Fetch or create a new timer metric. Timers provide the means to
|
|
109
|
+
# time the execution of a method including statistics on the number of
|
|
110
|
+
# invocations, average length of time, throughput.
|
|
111
|
+
#
|
|
112
|
+
# name - The String name of the metric to define or fetch
|
|
113
|
+
#
|
|
114
|
+
# Examples
|
|
115
|
+
#
|
|
116
|
+
# registry.timer('resque.worker')
|
|
117
|
+
#
|
|
118
|
+
# Returns the Metrics::Timer identified by the name.
|
|
119
|
+
def timer(name)
|
|
120
|
+
add_or_get(name, Metrics::Timer)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Public: Fetch or create a new utilization timer metric.
|
|
124
|
+
#
|
|
125
|
+
# Utilization timers are a specialized version of a timer that calculate
|
|
126
|
+
# the percentage of wall-clock time (between 0 and 1) that was spent in
|
|
127
|
+
# the method. This metric is most valuable in a single-threaded
|
|
128
|
+
# environment where a processes is waiting on an external resource like a
|
|
129
|
+
# message queue or HTTP server.
|
|
130
|
+
#
|
|
131
|
+
# name - The String name of the metric to define or fetch
|
|
132
|
+
#
|
|
133
|
+
# Examples
|
|
134
|
+
#
|
|
135
|
+
# registry.utilization_timer('rack.utilization')
|
|
136
|
+
#
|
|
137
|
+
# Returns the Metrics::UtilizationTimer identified by the name.
|
|
138
|
+
def utilization_timer(name)
|
|
139
|
+
add_or_get(name, Metrics::UtilizationTimer)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# Public: Fetch or create a new histogram metric. Histograms record values
|
|
143
|
+
# and expose statistics about the distribution of the data like median and
|
|
144
|
+
# 95th percentile.
|
|
145
|
+
#
|
|
146
|
+
# name - The String name of the metric to define or fetch
|
|
147
|
+
#
|
|
148
|
+
# Examples
|
|
149
|
+
#
|
|
150
|
+
# registry.histogram('backlog.wait')
|
|
151
|
+
#
|
|
152
|
+
# Returns the Metrics::Histogram identified by the name.
|
|
153
|
+
def histogram(name)
|
|
154
|
+
add_or_get(name, Metrics::Histogram) do
|
|
155
|
+
Metrics::Histogram.new_exponentially_decaying
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# Public: Fetch an existing metric.
|
|
160
|
+
#
|
|
161
|
+
# name - The String name of the metric to fetch
|
|
162
|
+
#
|
|
163
|
+
# Examples
|
|
164
|
+
#
|
|
165
|
+
# registry.get('rack.utilization')
|
|
166
|
+
#
|
|
167
|
+
# Returns the metric or nil.
|
|
168
|
+
def get(name)
|
|
169
|
+
@mutex.synchronize do
|
|
170
|
+
@metrics[name]
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# Public: Add a new metric.
|
|
175
|
+
#
|
|
176
|
+
# name - The String name of the metric to add
|
|
177
|
+
# metric - The metric instance to add
|
|
178
|
+
#
|
|
179
|
+
# Examples
|
|
180
|
+
#
|
|
181
|
+
# registry.add('method.calls', Metrics::Counter.new)
|
|
182
|
+
#
|
|
183
|
+
# Returns nothing.
|
|
184
|
+
# Raises RuntimeError if the metric name is already defined
|
|
185
|
+
def add(name, metric)
|
|
186
|
+
@mutex.synchronize do
|
|
187
|
+
raise "Metric '#{name}' already defined" if @metrics[name]
|
|
188
|
+
|
|
189
|
+
@metrics[name] = metric
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
protected
|
|
194
|
+
|
|
195
|
+
def add_or_get(name, klass, &create_metric)
|
|
196
|
+
@mutex.synchronize do
|
|
197
|
+
if (metric = @metrics[name])
|
|
198
|
+
raise "Metric already defined as '#{metric.class}'" unless metric.is_a?(klass)
|
|
199
|
+
|
|
200
|
+
return metric
|
|
201
|
+
|
|
202
|
+
else
|
|
203
|
+
@metrics[name] = create_metric ? create_metric.call : klass.new
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
end
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "atomic"
|
|
4
|
+
|
|
5
|
+
module Twingly
|
|
6
|
+
module Metrics
|
|
7
|
+
class SimpleMovingAverage
|
|
8
|
+
INTERVAL = 5.0
|
|
9
|
+
SECONDS_PER_MINUTE = 60.0
|
|
10
|
+
|
|
11
|
+
ONE_MINUTE = 1
|
|
12
|
+
FIVE_MINUTES = 5
|
|
13
|
+
FIFTEEN_MINUTES = 15
|
|
14
|
+
|
|
15
|
+
def self.new_m1
|
|
16
|
+
new(ONE_MINUTE * SECONDS_PER_MINUTE, INTERVAL)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def self.new_m5
|
|
20
|
+
new(FIVE_MINUTES * SECONDS_PER_MINUTE, INTERVAL)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def self.new_m15
|
|
24
|
+
new(FIFTEEN_MINUTES * SECONDS_PER_MINUTE, INTERVAL)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def initialize(duration, interval)
|
|
28
|
+
@interval = interval
|
|
29
|
+
@duration = duration
|
|
30
|
+
|
|
31
|
+
@values = Array.new((duration / interval).to_i) { Atomic.new(nil) }
|
|
32
|
+
@index = Atomic.new(0)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def clear
|
|
36
|
+
@values.each do |value|
|
|
37
|
+
value.value = nil
|
|
38
|
+
end
|
|
39
|
+
@index.value = 0
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def update(value)
|
|
43
|
+
@values[@index.value].update { |v| v ? v + value : value }
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def tick
|
|
47
|
+
next_index = @index.update { |v| v < @values.length - 1 ? v + 1 : 0 }
|
|
48
|
+
@values[next_index].value = nil
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def rate
|
|
52
|
+
num = 0.0
|
|
53
|
+
count = 0.0
|
|
54
|
+
|
|
55
|
+
@values.each do |value|
|
|
56
|
+
if (v = value.value)
|
|
57
|
+
num += v
|
|
58
|
+
count += 1
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
return 0.0 if count.zero?
|
|
63
|
+
|
|
64
|
+
num / count / @interval.to_f
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Twingly
|
|
4
|
+
module Metrics
|
|
5
|
+
class Snapshot
|
|
6
|
+
MEDIAN_Q = 0.5
|
|
7
|
+
P75_Q = 0.75
|
|
8
|
+
P95_Q = 0.95
|
|
9
|
+
P98_Q = 0.98
|
|
10
|
+
P99_Q = 0.99
|
|
11
|
+
P999_Q = 0.999
|
|
12
|
+
|
|
13
|
+
attr_reader :values
|
|
14
|
+
|
|
15
|
+
def initialize(values)
|
|
16
|
+
@values = values.sort
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def value(quantile) # rubocop:disable Metrics/AbcSize
|
|
20
|
+
raise ArgumentError, "quantile must be between 0.0 and 1.0" if quantile < 0.0 || quantile > 1.0
|
|
21
|
+
|
|
22
|
+
return 0.0 if @values.empty?
|
|
23
|
+
|
|
24
|
+
pos = quantile * (@values.length + 1)
|
|
25
|
+
|
|
26
|
+
return @values.first if pos < 1
|
|
27
|
+
return @values.last if pos >= @values.length
|
|
28
|
+
|
|
29
|
+
lower = @values[pos.to_i - 1]
|
|
30
|
+
upper = @values[pos.to_i]
|
|
31
|
+
lower + ((pos - pos.floor) * (upper - lower))
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def size
|
|
35
|
+
@values.length
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def median
|
|
39
|
+
value(MEDIAN_Q)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# rubocop:disable Naming/AccessorMethodName
|
|
43
|
+
def get_75th_percentile
|
|
44
|
+
value(P75_Q)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def get_95th_percentile
|
|
48
|
+
value(P95_Q)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def get_98th_percentile
|
|
52
|
+
value(P98_Q)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def get_99th_percentile
|
|
56
|
+
value(P99_Q)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def get_999th_percentile
|
|
60
|
+
value(P999_Q)
|
|
61
|
+
end
|
|
62
|
+
# rubocop:enable Naming/AccessorMethodName
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Twingly
|
|
4
|
+
module Metrics
|
|
5
|
+
class TimeTracker
|
|
6
|
+
def initialize(interval)
|
|
7
|
+
@interval = interval
|
|
8
|
+
@next_time = Time.now.to_f
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def sleep
|
|
12
|
+
sleep_time = next_time - Time.now.to_f
|
|
13
|
+
return unless sleep_time.positive?
|
|
14
|
+
|
|
15
|
+
Kernel.sleep(sleep_time)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def now_floored
|
|
19
|
+
time = Time.now.to_i
|
|
20
|
+
time - (time % @interval)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def next_time
|
|
24
|
+
now = Time.now.to_f
|
|
25
|
+
@next_time = now if @next_time <= now
|
|
26
|
+
@next_time += @interval - (@next_time % @interval)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "atomic"
|
|
4
|
+
|
|
5
|
+
require_relative "meter"
|
|
6
|
+
require_relative "histogram"
|
|
7
|
+
|
|
8
|
+
module Twingly
|
|
9
|
+
module Metrics
|
|
10
|
+
class Timer
|
|
11
|
+
class Context
|
|
12
|
+
def initialize(timer)
|
|
13
|
+
@timer = timer
|
|
14
|
+
@interval = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def restart
|
|
18
|
+
@interval = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def stop
|
|
22
|
+
duration = Process.clock_gettime(Process::CLOCK_MONOTONIC) - @interval
|
|
23
|
+
@timer.update(duration)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def initialize(histogram = Twingly::Metrics::Histogram.new_exponentially_decaying)
|
|
28
|
+
@meter = Twingly::Metrics::Meter.new
|
|
29
|
+
@histogram = histogram
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def clear
|
|
33
|
+
@meter.clear
|
|
34
|
+
@histogram.clear
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def update(duration)
|
|
38
|
+
return unless duration >= 0
|
|
39
|
+
|
|
40
|
+
@meter.mark
|
|
41
|
+
@histogram.update(duration)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def time(callable = nil, &block)
|
|
45
|
+
callable ||= block
|
|
46
|
+
context = Context.new(self)
|
|
47
|
+
|
|
48
|
+
return context if callable.nil?
|
|
49
|
+
|
|
50
|
+
begin
|
|
51
|
+
callable.call
|
|
52
|
+
ensure
|
|
53
|
+
context.stop
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def snapshot
|
|
58
|
+
@histogram.snapshot
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def count
|
|
62
|
+
@histogram.count
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def sum
|
|
66
|
+
@histogram.sum
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def one_minute_rate
|
|
70
|
+
@meter.one_minute_rate
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def five_minute_rate
|
|
74
|
+
@meter.five_minute_rate
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def fifteen_minute_rate
|
|
78
|
+
@meter.fifteen_minute_rate
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def mean_rate
|
|
82
|
+
@meter.mean_rate
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def min
|
|
86
|
+
@histogram.min
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def max
|
|
90
|
+
@histogram.max
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def mean
|
|
94
|
+
@histogram.mean
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def stddev
|
|
98
|
+
@histogram.stddev
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def stop
|
|
102
|
+
@meter.stop
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "atomic"
|
|
4
|
+
require_relative "snapshot"
|
|
5
|
+
|
|
6
|
+
module Twingly
|
|
7
|
+
module Metrics
|
|
8
|
+
class UniformSample
|
|
9
|
+
def initialize(reservoir_size)
|
|
10
|
+
@values = Array.new(reservoir_size, 0)
|
|
11
|
+
@count = Atomic.new(0)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def clear
|
|
15
|
+
@values.length.times do |idx|
|
|
16
|
+
@values[idx] = 0
|
|
17
|
+
end
|
|
18
|
+
@count.value = 0
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def size
|
|
22
|
+
count = @count.value
|
|
23
|
+
[count, @values.length].min
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def snapshot
|
|
27
|
+
Snapshot.new(@values.slice(0, size))
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def update(value)
|
|
31
|
+
new_count = @count.update { |v| v + 1 }
|
|
32
|
+
|
|
33
|
+
if new_count <= @values.length
|
|
34
|
+
@values[new_count - 1] = value
|
|
35
|
+
else
|
|
36
|
+
idx = rand(new_count)
|
|
37
|
+
@values[idx] = value if idx < @values.length
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "timer"
|
|
4
|
+
|
|
5
|
+
module Twingly
|
|
6
|
+
module Metrics
|
|
7
|
+
class UtilizationTimer < Metrics::Timer
|
|
8
|
+
def initialize
|
|
9
|
+
super
|
|
10
|
+
@duration_meter = Metrics::Meter.new
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def clear
|
|
14
|
+
super
|
|
15
|
+
@duration_meter.clear
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def update(duration)
|
|
19
|
+
return unless duration >= 0
|
|
20
|
+
|
|
21
|
+
super
|
|
22
|
+
@duration_meter.mark(duration)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def one_minute_utilization
|
|
26
|
+
@duration_meter.one_minute_rate
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def five_minute_utilization
|
|
30
|
+
@duration_meter.five_minute_rate
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def fifteen_minute_utilization
|
|
34
|
+
@duration_meter.fifteen_minute_rate
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def mean_utilization
|
|
38
|
+
@duration_meter.mean_rate
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def stop
|
|
42
|
+
super
|
|
43
|
+
@duration_meter.stop
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
data/lib/twingly/metrics.rb
CHANGED
|
@@ -1,40 +1,57 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "metriks"
|
|
4
|
-
|
|
5
3
|
require_relative "metrics/version"
|
|
4
|
+
require_relative "metrics/registry"
|
|
6
5
|
require_relative "metrics/grafana_cloud_reporter"
|
|
6
|
+
require_relative "metrics/counter"
|
|
7
|
+
require_relative "metrics/timer"
|
|
8
|
+
require_relative "metrics/utilization_timer"
|
|
9
|
+
require_relative "metrics/meter"
|
|
10
|
+
require_relative "metrics/gauge"
|
|
11
|
+
require_relative "metrics/histogram"
|
|
7
12
|
|
|
8
13
|
module Twingly
|
|
9
14
|
module Metrics
|
|
15
|
+
def self.get(name)
|
|
16
|
+
Registry.default.get(name)
|
|
17
|
+
end
|
|
18
|
+
|
|
10
19
|
def self.counter(name)
|
|
11
|
-
|
|
20
|
+
Registry.default.counter(name)
|
|
12
21
|
end
|
|
13
22
|
|
|
14
23
|
def self.gauge(name, callable = nil, &block)
|
|
15
|
-
|
|
24
|
+
Registry.default.gauge(name, callable, &block)
|
|
16
25
|
end
|
|
17
26
|
|
|
18
27
|
def self.timer(name)
|
|
19
|
-
|
|
28
|
+
Registry.default.timer(name)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def self.utilization_timer(name)
|
|
32
|
+
Registry.default.utilization_timer(name)
|
|
20
33
|
end
|
|
21
34
|
|
|
22
35
|
def self.meter(name)
|
|
23
|
-
|
|
36
|
+
Registry.default.meter(name)
|
|
24
37
|
end
|
|
25
38
|
|
|
26
39
|
def self.mark(name, val = 1)
|
|
27
40
|
meter(name).mark(val)
|
|
28
41
|
end
|
|
29
42
|
|
|
43
|
+
def self.set(name, val)
|
|
44
|
+
gauge(name).set(val)
|
|
45
|
+
end
|
|
46
|
+
|
|
30
47
|
def self.time(name, duration = nil, &block)
|
|
31
48
|
return timer(name).update(duration) if duration
|
|
32
49
|
|
|
33
50
|
timer(name).time(&block)
|
|
34
51
|
end
|
|
35
52
|
|
|
36
|
-
def self.
|
|
37
|
-
|
|
53
|
+
def self.histogram(name)
|
|
54
|
+
Registry.default.histogram(name)
|
|
38
55
|
end
|
|
39
56
|
end
|
|
40
57
|
end
|
metadata
CHANGED
|
@@ -1,28 +1,42 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: twingly-metrics
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.6.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Twingly AB
|
|
8
8
|
bindir: bin
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date:
|
|
10
|
+
date: 2026-07-13 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
|
-
name:
|
|
13
|
+
name: atomic
|
|
14
14
|
requirement: !ruby/object:Gem::Requirement
|
|
15
15
|
requirements:
|
|
16
16
|
- - "~>"
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
|
-
version: '0
|
|
18
|
+
version: '1.0'
|
|
19
19
|
type: :runtime
|
|
20
20
|
prerelease: false
|
|
21
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
22
22
|
requirements:
|
|
23
23
|
- - "~>"
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
|
-
version: '0
|
|
25
|
+
version: '1.0'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: avl_tree
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - "~>"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: 1.2.0
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: 1.2.0
|
|
26
40
|
description: Collect and report metrics in Ruby applications, and report them to Graphite.
|
|
27
41
|
email:
|
|
28
42
|
- support@twingly.com
|
|
@@ -32,7 +46,20 @@ extra_rdoc_files: []
|
|
|
32
46
|
files:
|
|
33
47
|
- README.md
|
|
34
48
|
- lib/twingly/metrics.rb
|
|
49
|
+
- lib/twingly/metrics/counter.rb
|
|
50
|
+
- lib/twingly/metrics/ewma.rb
|
|
51
|
+
- lib/twingly/metrics/exponentially_decaying_sample.rb
|
|
52
|
+
- lib/twingly/metrics/gauge.rb
|
|
35
53
|
- lib/twingly/metrics/grafana_cloud_reporter.rb
|
|
54
|
+
- lib/twingly/metrics/histogram.rb
|
|
55
|
+
- lib/twingly/metrics/meter.rb
|
|
56
|
+
- lib/twingly/metrics/registry.rb
|
|
57
|
+
- lib/twingly/metrics/simple_moving_average.rb
|
|
58
|
+
- lib/twingly/metrics/snapshot.rb
|
|
59
|
+
- lib/twingly/metrics/time_tracker.rb
|
|
60
|
+
- lib/twingly/metrics/timer.rb
|
|
61
|
+
- lib/twingly/metrics/uniform_sample.rb
|
|
62
|
+
- lib/twingly/metrics/utilization_timer.rb
|
|
36
63
|
- lib/twingly/metrics/version.rb
|
|
37
64
|
homepage: https://github.com/twingly/twingly-metrics
|
|
38
65
|
licenses: []
|
|
@@ -52,7 +79,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
52
79
|
- !ruby/object:Gem::Version
|
|
53
80
|
version: '0'
|
|
54
81
|
requirements: []
|
|
55
|
-
rubygems_version: 3.6.
|
|
82
|
+
rubygems_version: 3.6.2
|
|
56
83
|
specification_version: 4
|
|
57
84
|
summary: Ruby library for collecting and reporting metrics.
|
|
58
85
|
test_files: []
|