metriks 0.8.1 → 0.8.2

Sign up to get free protection for your applications and to get access to all the features.
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