metriks 0.8.1 → 0.8.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/Gemfile CHANGED
@@ -2,6 +2,10 @@ source :rubygems
2
2
 
3
3
  gemspec
4
4
 
5
+ group :development do
6
+ gem 'perftools.rb'
7
+ end
8
+
5
9
  group :test do
6
10
  gem 'turn'
7
11
  end
data/README.md CHANGED
@@ -9,13 +9,12 @@ I find needs while developing [Papertrail](https://papertrailapp.com/).
9
9
 
10
10
  # Installing
11
11
 
12
- Everything is still in flux, so for the time being I have been installing
13
- the gem from git with bundler. If I get a request, I can definitely start
14
- releasing a gem.
12
+ The API is still in flux, but you can add this to your project by installing
13
+ the gem.
15
14
 
16
15
  To install, add this to your `Gemfile`:
17
16
 
18
- gem 'metriks', :git => 'git://github.com/eric/metriks.git'
17
+ gem 'metriks'
19
18
 
20
19
  and re-run `bundle`.
21
20
 
@@ -0,0 +1,58 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'benchmark'
4
+ require 'metriks'
5
+
6
+
7
+ def fib(n)
8
+ n < 2 ? n : fib(n-1) + fib(n-2)
9
+ end
10
+
11
+ uniform_timer = Metriks::Timer.new(Metriks::Histogram.new_uniform)
12
+ exponential_timer = Metriks::Timer.new(Metriks::Histogram.new_exponentially_decaying)
13
+
14
+ fib_times = ARGV[0] ? ARGV[0].to_i : 10
15
+ iter = ARGV[1] ? ARGV[1].to_i : 100000
16
+
17
+ puts "fib(#{fib_times}): #{iter} iterations"
18
+ puts "-" * 50
19
+
20
+ plain = Benchmark.realtime do
21
+ for i in 1..iter
22
+ fib(fib_times)
23
+ end
24
+ end
25
+
26
+ puts "%15s: %f secs %f secs/call" % [ 'plain', plain, plain / iter ]
27
+
28
+ uniform = Benchmark.realtime do
29
+ for i in 1..iter
30
+ uniform_timer.time do
31
+ fib(fib_times)
32
+ end
33
+ end
34
+ end
35
+
36
+ puts "%15s: %f secs %f secs/call -- %.1f%% slower than plain (%f secs/call)" % [
37
+ 'uniform', uniform, uniform / iter,
38
+ (uniform - plain) / plain * 100 ,
39
+ (uniform - plain) / iter,
40
+ ]
41
+
42
+ exponential = Benchmark.realtime do
43
+ for i in 1..iter
44
+ exponential_timer.time do
45
+ fib(fib_times)
46
+ end
47
+ end
48
+ end
49
+
50
+ puts "%15s: %f secs %f secs/call -- %.1f%% slower than plain (%f secs/call) -- %.1f%% slower than uniform (%f secs/call)" % [
51
+ 'exponential', exponential, exponential / iter,
52
+ (exponential - plain) / plain * 100 ,
53
+ (exponential - plain) / iter,
54
+ (exponential - uniform) / uniform * 100 ,
55
+ (exponential - uniform) / iter
56
+ ]
57
+
58
+
data/lib/metriks.rb CHANGED
@@ -1,6 +1,6 @@
1
1
 
2
2
  module Metriks
3
- VERSION = '0.8.1'
3
+ VERSION = '0.8.2'
4
4
 
5
5
  def self.get(name)
6
6
  Metriks::Registry.default.get(name)
@@ -0,0 +1,80 @@
1
+ require 'atomic'
2
+ require 'rbtree'
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)
10
+ @values = RBTree.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 = Math.exp(timestamp - @start_time) / rand
42
+ new_count = @count.update { |v| v + 1 }
43
+ if new_count <= @reservoir_size
44
+ @values[priority] = value
45
+ else
46
+ first_priority = @values.first[0]
47
+ if first_priority < priority
48
+ unless @values[priority]
49
+ @values[priority] = value
50
+
51
+ until @values.delete(first_priority)
52
+ first_priority = @values.first[0]
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
58
+
59
+ now = Time.new
60
+ next_time = @next_scale_time.value
61
+ if now >= next_time
62
+ rescale(now, next_time)
63
+ end
64
+ end
65
+
66
+ def rescale(now, next_time)
67
+ if @next_scale_time.compare_and_swap(next_time, now + RESCALE_THRESHOLD)
68
+ @mutex.synchronize do
69
+ old_start_time = @start_time
70
+ @start_time = Time.now
71
+ keys = @values.keys
72
+ keys.each do |key|
73
+ value = @values.delete(key)
74
+ @values[key* Math.exp(-@alpha * (@start_time - old_start_time))] = value
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
@@ -1,5 +1,6 @@
1
1
  require 'atomic'
2
2
  require 'metriks/uniform_sample'
3
+ require 'metriks/exponentially_decaying_sample'
3
4
 
4
5
  module Metriks
5
6
  class Histogram
@@ -10,6 +11,10 @@ module Metriks
10
11
  new(Metriks::UniformSample.new(DEFAULT_SAMPLE_SIZE))
11
12
  end
12
13
 
14
+ def self.new_exponentially_decaying
15
+ new(Metriks::ExponentiallyDecayingSample.new(DEFAULT_SAMPLE_SIZE, DEFAULT_ALPHA))
16
+ end
17
+
13
18
  def initialize(sample)
14
19
  @sample = sample
15
20
  @count = Atomic.new(0)
@@ -37,6 +42,10 @@ module Metriks
37
42
  update_variance(value)
38
43
  end
39
44
 
45
+ def snapshot
46
+ @sample.snapshot
47
+ end
48
+
40
49
  def count
41
50
  @count.value
42
51
  end
@@ -51,12 +51,16 @@ module Metriks::Reporter
51
51
  :min, :max, :mean, :stddev,
52
52
  :one_minute_utilization, :five_minute_utilization,
53
53
  :fifteen_minute_utilization, :mean_utilization,
54
+ ], [
55
+ :median, :get_95th_percentile
54
56
  ]
55
57
  when Metriks::Timer
56
58
  log_metric name, 'timer', metric, [
57
59
  :count, :one_minute_rate, :five_minute_rate,
58
60
  :fifteen_minute_rate, :mean_rate,
59
61
  :min, :max, :mean, :stddev
62
+ ], [
63
+ :median, :get_95th_percentile
60
64
  ]
61
65
  end
62
66
  end
@@ -64,11 +68,12 @@ module Metriks::Reporter
64
68
 
65
69
  def extract_from_metric(metric, *keys)
66
70
  keys.flatten.collect do |key|
67
- [ { key => metric.send(key) } ]
71
+ name = key.to_s.gsub(/^get_/, '')
72
+ [ { name => metric.send(key) } ]
68
73
  end
69
74
  end
70
75
 
71
- def log_metric(name, type, metric, *keys)
76
+ def log_metric(name, type, metric, keys, snapshot_keys = [])
72
77
  message = []
73
78
 
74
79
  message << @prefix if @prefix
@@ -78,6 +83,11 @@ module Metriks::Reporter
78
83
  message << { :type => type }
79
84
  message += extract_from_metric(metric, keys)
80
85
 
86
+ unless snapshot_keys.empty?
87
+ snapshot = metric.snapshot
88
+ message += extract_from_metric(snapshot, snapshot_keys)
89
+ end
90
+
81
91
  @logger.add(@log_level, format_message(message))
82
92
  end
83
93
 
@@ -0,0 +1,56 @@
1
+
2
+ class Snapshot
3
+ MEDIAN_Q = 0.5
4
+ P75_Q = 0.75
5
+ P95_Q = 0.95
6
+ P98_Q = 0.98
7
+ P99_Q = 0.99
8
+ P999_Q = 0.999
9
+
10
+ def initialize(values)
11
+ @values = values.sort
12
+ end
13
+
14
+ def value(quantile)
15
+ raise ArgumentError, "quantile must be between 0.0 and 1.0" if quantile < 0.0 || quantile > 1.0
16
+
17
+ return 0.0 if @values.empty?
18
+
19
+ pos = quantile * (@values.length + 1)
20
+
21
+ return @values.first if pos < 1
22
+ return @values.last if pos >= @values.length
23
+
24
+ lower = @values[pos.to_i - 1]
25
+ upper = @values[pos.to_i]
26
+ lower + (pos - pos.floor) + (upper - lower)
27
+ end
28
+
29
+ def size
30
+ @values.length
31
+ end
32
+
33
+ def median
34
+ value(MEDIAN_Q)
35
+ end
36
+
37
+ def get_75th_percentile
38
+ value(P75_Q)
39
+ end
40
+
41
+ def get_95th_percentile
42
+ value(P95_Q)
43
+ end
44
+
45
+ def get_98th_percentile
46
+ value(P98_Q)
47
+ end
48
+
49
+ def get_99th_percentile
50
+ value(P99_Q)
51
+ end
52
+
53
+ def get_999th_percentile
54
+ value(P999_Q)
55
+ end
56
+ end
data/lib/metriks/timer.rb CHANGED
@@ -18,9 +18,9 @@ module Metriks
18
18
  end
19
19
  end
20
20
 
21
- def initialize
21
+ def initialize(histogram = Metriks::Histogram.new_exponentially_decaying)
22
22
  @meter = Metriks::Meter.new
23
- @histogram = Metriks::Histogram.new_uniform
23
+ @histogram = histogram
24
24
  end
25
25
 
26
26
  def clear
@@ -50,6 +50,10 @@ module Metriks
50
50
  end
51
51
  end
52
52
 
53
+ def snapshot
54
+ @histogram.snapshot
55
+ end
56
+
53
57
  def count
54
58
  @histogram.count
55
59
  end
@@ -1,4 +1,5 @@
1
1
  require 'atomic'
2
+ require 'metriks/snapshot'
2
3
 
3
4
  module Metriks
4
5
  class UniformSample
@@ -19,6 +20,10 @@ module Metriks
19
20
  count > @values.length ? @values.length : count
20
21
  end
21
22
 
23
+ def snapshot
24
+ Snapshot.new(@values.slice(0, size))
25
+ end
26
+
22
27
  def update(value)
23
28
  new_count = @count.update { |v| v + 1 }
24
29
 
data/metriks.gemspec CHANGED
@@ -13,7 +13,7 @@ Gem::Specification.new do |s|
13
13
  ## If your rubyforge_project name is different, then edit it and comment out
14
14
  ## the sub! line in the Rakefile
15
15
  s.name = 'metriks'
16
- s.version = '0.8.1'
16
+ s.version = '0.8.2'
17
17
  s.date = '2012-02-27'
18
18
 
19
19
  ## Make sure your summary is short. The description may be as long
@@ -44,6 +44,7 @@ Gem::Specification.new do |s|
44
44
  ## that are needed for an end user to actually USE your code.
45
45
  s.add_dependency('atomic', ["~> 1.0"])
46
46
  s.add_dependency('hitimes', [ "~> 1.1"])
47
+ s.add_dependency('rbtree', [ "~> 0.3" ])
47
48
 
48
49
  ## List your development dependencies here. Development dependencies are
49
50
  ## those that are only needed during development
@@ -58,15 +59,18 @@ Gem::Specification.new do |s|
58
59
  LICENSE
59
60
  README.md
60
61
  Rakefile
62
+ benchmark/samplers.rb
61
63
  lib/metriks.rb
62
64
  lib/metriks/counter.rb
63
65
  lib/metriks/ewma.rb
66
+ lib/metriks/exponentially_decaying_sample.rb
64
67
  lib/metriks/histogram.rb
65
68
  lib/metriks/meter.rb
66
69
  lib/metriks/registry.rb
67
70
  lib/metriks/reporter/logger.rb
68
71
  lib/metriks/reporter/proc_title.rb
69
72
  lib/metriks/simple_moving_average.rb
73
+ lib/metriks/snapshot.rb
70
74
  lib/metriks/timer.rb
71
75
  lib/metriks/uniform_sample.rb
72
76
  lib/metriks/utilization_timer.rb
@@ -4,35 +4,104 @@ require 'metriks/histogram'
4
4
 
5
5
  class HistogramTest < Test::Unit::TestCase
6
6
  def setup
7
+ end
8
+
9
+ def test_uniform_sample_min
10
+ @histogram = Metriks::Histogram.new(Metriks::UniformSample.new(Metriks::Histogram::DEFAULT_SAMPLE_SIZE))
11
+
12
+ @histogram.update(5)
13
+ @histogram.update(10)
14
+
15
+ assert_equal 5, @histogram.min
16
+ end
17
+
18
+ def test_uniform_sample_max
19
+ @histogram = Metriks::Histogram.new(Metriks::UniformSample.new(Metriks::Histogram::DEFAULT_SAMPLE_SIZE))
20
+
21
+ @histogram.update(5)
22
+ @histogram.update(10)
23
+
24
+ assert_equal 10, @histogram.max
25
+ end
26
+
27
+ def test_uniform_sample_mean
28
+ @histogram = Metriks::Histogram.new(Metriks::UniformSample.new(Metriks::Histogram::DEFAULT_SAMPLE_SIZE))
29
+
30
+ @histogram.update(5)
31
+ @histogram.update(10)
32
+
33
+ assert_equal 7, @histogram.mean
34
+ end
35
+
36
+ def test_uniform_sample_2000
7
37
  @histogram = Metriks::Histogram.new(Metriks::UniformSample.new(Metriks::Histogram::DEFAULT_SAMPLE_SIZE))
38
+
39
+ 2000.times do |idx|
40
+ @histogram.update(idx)
41
+ end
42
+
43
+ assert_equal 1999, @histogram.max
44
+ end
45
+
46
+ def test_uniform_sample_snashot
47
+ @histogram = Metriks::Histogram.new(Metriks::UniformSample.new(Metriks::Histogram::DEFAULT_SAMPLE_SIZE))
48
+
49
+ 50.times do |idx|
50
+ @histogram.update(idx)
51
+ end
52
+
53
+ snapshot = @histogram.snapshot
54
+
55
+ assert_equal 25.5, snapshot.median
8
56
  end
9
57
 
10
- def test_min
58
+ def test_exponential_sample_min
59
+ @histogram = Metriks::Histogram.new(Metriks::ExponentiallyDecayingSample.new(Metriks::Histogram::DEFAULT_SAMPLE_SIZE, Metriks::Histogram::DEFAULT_ALPHA))
60
+
11
61
  @histogram.update(5)
12
62
  @histogram.update(10)
13
63
 
14
64
  assert_equal 5, @histogram.min
15
65
  end
16
66
 
17
- def test_max
67
+ def test_exponential_sample_max
68
+ @histogram = Metriks::Histogram.new(Metriks::ExponentiallyDecayingSample.new(Metriks::Histogram::DEFAULT_SAMPLE_SIZE, Metriks::Histogram::DEFAULT_ALPHA))
69
+
18
70
  @histogram.update(5)
19
71
  @histogram.update(10)
20
72
 
21
73
  assert_equal 10, @histogram.max
22
74
  end
23
75
 
24
- def test_mean
76
+ def test_exponential_sample_mean
77
+ @histogram = Metriks::Histogram.new(Metriks::ExponentiallyDecayingSample.new(Metriks::Histogram::DEFAULT_SAMPLE_SIZE, Metriks::Histogram::DEFAULT_ALPHA))
78
+
25
79
  @histogram.update(5)
26
80
  @histogram.update(10)
27
81
 
28
82
  assert_equal 7, @histogram.mean
29
83
  end
30
84
 
31
- def test_2000
85
+ def test_exponential_sample_2000
86
+ @histogram = Metriks::Histogram.new(Metriks::ExponentiallyDecayingSample.new(Metriks::Histogram::DEFAULT_SAMPLE_SIZE, Metriks::Histogram::DEFAULT_ALPHA))
87
+
32
88
  2000.times do |idx|
33
89
  @histogram.update(idx)
34
90
  end
35
91
 
36
92
  assert_equal 1999, @histogram.max
37
93
  end
94
+
95
+ def test_exponential_sample_snashot
96
+ @histogram = Metriks::Histogram.new(Metriks::ExponentiallyDecayingSample.new(Metriks::Histogram::DEFAULT_SAMPLE_SIZE, Metriks::Histogram::DEFAULT_ALPHA))
97
+
98
+ 50.times do |idx|
99
+ @histogram.update(idx)
100
+ end
101
+
102
+ snapshot = @histogram.snapshot
103
+
104
+ assert_equal 25.5, snapshot.median
105
+ end
106
+
38
107
  end
@@ -26,5 +26,6 @@ class LoggerReporterTest < Test::Unit::TestCase
26
26
  @reporter.write
27
27
 
28
28
  assert_match /time=\d/, @stringio.string
29
+ assert_match /median=\d/, @stringio.string
29
30
  end
30
31
  end
metadata CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
5
5
  segments:
6
6
  - 0
7
7
  - 8
8
- - 1
9
- version: 0.8.1
8
+ - 2
9
+ version: 0.8.2
10
10
  platform: ruby
11
11
  authors:
12
12
  - Eric Lindvall
@@ -44,9 +44,22 @@ dependencies:
44
44
  type: :runtime
45
45
  version_requirements: *id002
46
46
  - !ruby/object:Gem::Dependency
47
- name: tomdoc
47
+ name: rbtree
48
48
  prerelease: false
49
49
  requirement: &id003 !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ~>
52
+ - !ruby/object:Gem::Version
53
+ segments:
54
+ - 0
55
+ - 3
56
+ version: "0.3"
57
+ type: :runtime
58
+ version_requirements: *id003
59
+ - !ruby/object:Gem::Dependency
60
+ name: tomdoc
61
+ prerelease: false
62
+ requirement: &id004 !ruby/object:Gem::Requirement
50
63
  requirements:
51
64
  - - ~>
52
65
  - !ruby/object:Gem::Version
@@ -55,7 +68,7 @@ dependencies:
55
68
  - 2
56
69
  version: "0.2"
57
70
  type: :development
58
- version_requirements: *id003
71
+ version_requirements: *id004
59
72
  description: An experimental metrics client.
60
73
  email: eric@sevenscale.com
61
74
  executables: []
@@ -70,15 +83,18 @@ files:
70
83
  - LICENSE
71
84
  - README.md
72
85
  - Rakefile
86
+ - benchmark/samplers.rb
73
87
  - lib/metriks.rb
74
88
  - lib/metriks/counter.rb
75
89
  - lib/metriks/ewma.rb
90
+ - lib/metriks/exponentially_decaying_sample.rb
76
91
  - lib/metriks/histogram.rb
77
92
  - lib/metriks/meter.rb
78
93
  - lib/metriks/registry.rb
79
94
  - lib/metriks/reporter/logger.rb
80
95
  - lib/metriks/reporter/proc_title.rb
81
96
  - lib/metriks/simple_moving_average.rb
97
+ - lib/metriks/snapshot.rb
82
98
  - lib/metriks/timer.rb
83
99
  - lib/metriks/uniform_sample.rb
84
100
  - lib/metriks/utilization_timer.rb