metriks 0.8.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
@@ -0,0 +1,94 @@
|
|
1
|
+
require 'logger'
|
2
|
+
|
3
|
+
module Metriks::Reporter
|
4
|
+
class Logger
|
5
|
+
def initialize(options = {})
|
6
|
+
@registry = options[:registry] || Metriks::Registry.default
|
7
|
+
@logger = options[:logger] || ::Logger.new(STDOUT)
|
8
|
+
@log_level = options[:log_level] || ::Logger::INFO
|
9
|
+
@prefix = options[:prefix] || 'metriks:'
|
10
|
+
@interval = options[:interval] || 60
|
11
|
+
@on_errror = options[:on_error] || proc { |ex| }
|
12
|
+
end
|
13
|
+
|
14
|
+
def start
|
15
|
+
@thread ||= Thread.new do
|
16
|
+
loop do
|
17
|
+
sleep @interval
|
18
|
+
|
19
|
+
Thread.new do
|
20
|
+
begin
|
21
|
+
write
|
22
|
+
rescue Exception => ex
|
23
|
+
@on_error[ex] rescue nil
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def stop
|
31
|
+
@thread.kill if @thread
|
32
|
+
@thread = nil
|
33
|
+
end
|
34
|
+
|
35
|
+
def write
|
36
|
+
@registry.each do |name, metric|
|
37
|
+
case metric
|
38
|
+
when Metriks::Meter
|
39
|
+
log_metric name, 'meter', metric, [
|
40
|
+
:count, :one_minute_rate, :five_minute_rate,
|
41
|
+
:fifteen_minute_rate, :mean_rate
|
42
|
+
]
|
43
|
+
when Metriks::Counter
|
44
|
+
log_metric name, 'counter', metric, [
|
45
|
+
:count
|
46
|
+
]
|
47
|
+
when Metriks::UtilizationTimer
|
48
|
+
log_metric name, 'utilization_timer', metric, [
|
49
|
+
:count, :one_minute_rate, :five_minute_rate,
|
50
|
+
:fifteen_minute_rate, :mean_rate,
|
51
|
+
:min, :max, :mean, :stddev,
|
52
|
+
:one_minute_utilization, :five_minute_utilization,
|
53
|
+
:fifteen_minute_utilization, :mean_utilization,
|
54
|
+
]
|
55
|
+
when Metriks::Timer
|
56
|
+
log_metric name, 'timer', metric, [
|
57
|
+
:count, :one_minute_rate, :five_minute_rate,
|
58
|
+
:fifteen_minute_rate, :mean_rate,
|
59
|
+
:min, :max, :mean, :stddev
|
60
|
+
]
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def extract_from_metric(metric, *keys)
|
66
|
+
keys.flatten.collect do |key|
|
67
|
+
[ { key => metric.send(key) } ]
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def log_metric(name, type, metric, *keys)
|
72
|
+
message = []
|
73
|
+
|
74
|
+
message << @prefix if @prefix
|
75
|
+
message << { :time => Time.now.to_i }
|
76
|
+
|
77
|
+
message << { :name => name }
|
78
|
+
message << { :type => type }
|
79
|
+
message += extract_from_metric(metric, keys)
|
80
|
+
|
81
|
+
@logger.add(@log_level, format_message(message))
|
82
|
+
end
|
83
|
+
|
84
|
+
def format_message(args)
|
85
|
+
args.map do |arg|
|
86
|
+
case arg
|
87
|
+
when Hash then arg.map { |name, value| "#{name}=#{format_message([value])}" }
|
88
|
+
when Array then format_message(arg)
|
89
|
+
else arg
|
90
|
+
end
|
91
|
+
end.join(' ')
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module Metriks::Reporter
|
2
|
+
class ProcTitle
|
3
|
+
def initialize(options = {})
|
4
|
+
@interval = options[:interval] || 5
|
5
|
+
@rounding = options[:rounding] || 1
|
6
|
+
@on_errror = options[:on_error] || proc { |ex| }
|
7
|
+
|
8
|
+
@prefix = $0.dup
|
9
|
+
@metrics = []
|
10
|
+
end
|
11
|
+
|
12
|
+
def add(name, suffix = nil, &block)
|
13
|
+
@metrics << [ name, suffix, block ]
|
14
|
+
end
|
15
|
+
|
16
|
+
def empty?
|
17
|
+
@metrics.empty?
|
18
|
+
end
|
19
|
+
|
20
|
+
def start
|
21
|
+
@thread ||= Thread.new do
|
22
|
+
loop do
|
23
|
+
begin
|
24
|
+
unless @metrics.empty?
|
25
|
+
title = generate_title
|
26
|
+
if title && !title.empty?
|
27
|
+
$0 = "#{@prefix} #{title}"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
rescue Exception => ex
|
31
|
+
@on_errror[ex] rescue nil
|
32
|
+
end
|
33
|
+
sleep @interval
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def stop
|
39
|
+
@thread.kill if @thread
|
40
|
+
@thread = nil
|
41
|
+
end
|
42
|
+
|
43
|
+
protected
|
44
|
+
def generate_title
|
45
|
+
@metrics.collect do |name, suffix, block|
|
46
|
+
val = block.call
|
47
|
+
val = "%.#{@rounding}f" % val if val.is_a?(Float)
|
48
|
+
|
49
|
+
if suffix == '%'
|
50
|
+
"#{name}: #{val}#{suffix}"
|
51
|
+
elsif suffix
|
52
|
+
"#{name}: #{val}/#{suffix}"
|
53
|
+
else
|
54
|
+
"#{name}: #{val}"
|
55
|
+
end
|
56
|
+
end.join(' ')
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'atomic'
|
2
|
+
|
3
|
+
module Metriks
|
4
|
+
class SimpleMovingAverage
|
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
|
+
def self.new_m1
|
13
|
+
new(ONE_MINUTE * SECONDS_PER_MINUTE, INTERVAL)
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.new_m5
|
17
|
+
new(FIVE_MINUTES * SECONDS_PER_MINUTE, INTERVAL)
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.new_m15
|
21
|
+
new(FIFTEEN_MINUTES * SECONDS_PER_MINUTE, INTERVAL)
|
22
|
+
end
|
23
|
+
|
24
|
+
def initialize(duration, interval)
|
25
|
+
@interval = interval
|
26
|
+
@duration = duration
|
27
|
+
|
28
|
+
@values = Array.new((duration / interval).to_i) { Atomic.new(nil) }
|
29
|
+
@index = Atomic.new(0)
|
30
|
+
end
|
31
|
+
|
32
|
+
def clear
|
33
|
+
@values.each do |value|
|
34
|
+
value.value = nil
|
35
|
+
end
|
36
|
+
@index.value = 0
|
37
|
+
end
|
38
|
+
|
39
|
+
def update(value)
|
40
|
+
@values[@index.value].update { |v| v ? v + value : value }
|
41
|
+
end
|
42
|
+
|
43
|
+
def tick
|
44
|
+
@index.update { |v| v < @values.length - 1 ? v + 1 : 0 }
|
45
|
+
end
|
46
|
+
|
47
|
+
def rate
|
48
|
+
num, count = 0.0, 0.0
|
49
|
+
|
50
|
+
@values.each do |value|
|
51
|
+
if v = value.value
|
52
|
+
num += v
|
53
|
+
count += 1
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
num / count / @interval.to_f
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
require 'atomic'
|
2
|
+
require 'hitimes'
|
3
|
+
|
4
|
+
require 'metriks/meter'
|
5
|
+
require 'metriks/histogram'
|
6
|
+
|
7
|
+
module Metriks
|
8
|
+
class Timer
|
9
|
+
class Context
|
10
|
+
def initialize(timer)
|
11
|
+
@timer = timer
|
12
|
+
@interval = Hitimes::Interval.now
|
13
|
+
end
|
14
|
+
|
15
|
+
def stop
|
16
|
+
@interval.stop
|
17
|
+
@timer.update(@interval.duration)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def initialize
|
22
|
+
@meter = Metriks::Meter.new
|
23
|
+
@histogram = Metriks::Histogram.new_uniform
|
24
|
+
end
|
25
|
+
|
26
|
+
def clear
|
27
|
+
@meter.clear
|
28
|
+
@histogram.clear
|
29
|
+
end
|
30
|
+
|
31
|
+
def update(duration)
|
32
|
+
if duration >= 0
|
33
|
+
@meter.mark
|
34
|
+
@histogram.update(duration)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def time(callable = nil, &block)
|
39
|
+
callable ||= block
|
40
|
+
context = Context.new(self)
|
41
|
+
|
42
|
+
if callable.nil?
|
43
|
+
return context
|
44
|
+
end
|
45
|
+
|
46
|
+
begin
|
47
|
+
return callable.call
|
48
|
+
ensure
|
49
|
+
context.stop
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def count
|
54
|
+
@histogram.count
|
55
|
+
end
|
56
|
+
|
57
|
+
def one_minute_rate
|
58
|
+
@meter.one_minute_rate
|
59
|
+
end
|
60
|
+
|
61
|
+
def five_minute_rate
|
62
|
+
@meter.five_minute_rate
|
63
|
+
end
|
64
|
+
|
65
|
+
def fifteen_minute_rate
|
66
|
+
@meter.fifteen_minute_rate
|
67
|
+
end
|
68
|
+
|
69
|
+
def mean_rate
|
70
|
+
@meter.mean_rate
|
71
|
+
end
|
72
|
+
|
73
|
+
def min
|
74
|
+
@histogram.min
|
75
|
+
end
|
76
|
+
|
77
|
+
def max
|
78
|
+
@histogram.max
|
79
|
+
end
|
80
|
+
|
81
|
+
def mean
|
82
|
+
@histogram.mean
|
83
|
+
end
|
84
|
+
|
85
|
+
def stddev
|
86
|
+
@histogram.stddev
|
87
|
+
end
|
88
|
+
|
89
|
+
def stop
|
90
|
+
@meter.stop
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'atomic'
|
2
|
+
|
3
|
+
module Metriks
|
4
|
+
class UniformSample
|
5
|
+
def initialize(reservoir_size)
|
6
|
+
@values = Array.new(reservoir_size, 0)
|
7
|
+
@count = Atomic.new(0)
|
8
|
+
end
|
9
|
+
|
10
|
+
def clear
|
11
|
+
@values.length.times do |idx|
|
12
|
+
@values[idx] = 0
|
13
|
+
end
|
14
|
+
@count.value = 0
|
15
|
+
end
|
16
|
+
|
17
|
+
def size
|
18
|
+
count = @count.value
|
19
|
+
count > @values.length ? @values.length : count
|
20
|
+
end
|
21
|
+
|
22
|
+
def update(value)
|
23
|
+
new_count = @count.update { |v| v + 1 }
|
24
|
+
|
25
|
+
if new_count <= @values.length
|
26
|
+
@values[new_count - 1] = value
|
27
|
+
else
|
28
|
+
idx = rand(new_count)
|
29
|
+
if idx < @values.length
|
30
|
+
@values[idx] = value
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'metriks/timer'
|
2
|
+
|
3
|
+
module Metriks
|
4
|
+
class UtilizationTimer < Metriks::Timer
|
5
|
+
def initialize
|
6
|
+
super
|
7
|
+
@duration_meter = Metriks::Meter.new
|
8
|
+
end
|
9
|
+
|
10
|
+
def clear
|
11
|
+
super
|
12
|
+
@duration_meter.clear
|
13
|
+
end
|
14
|
+
|
15
|
+
def update(duration)
|
16
|
+
super
|
17
|
+
if duration >= 0
|
18
|
+
@duration_meter.mark(duration)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def one_minute_utilization
|
23
|
+
@duration_meter.one_minute_rate
|
24
|
+
end
|
25
|
+
|
26
|
+
def five_minute_utilization
|
27
|
+
@duration_meter.five_minute_rate
|
28
|
+
end
|
29
|
+
|
30
|
+
def fifteen_minute_utilization
|
31
|
+
@duration_meter.fifteen_minute_rate
|
32
|
+
end
|
33
|
+
|
34
|
+
def mean_utilization
|
35
|
+
@duration_meter.mean_rate
|
36
|
+
end
|
37
|
+
|
38
|
+
def stop
|
39
|
+
super
|
40
|
+
@duration_meter.stop
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
data/metriks.gemspec
ADDED
@@ -0,0 +1,90 @@
|
|
1
|
+
## This is the rakegem gemspec template. Make sure you read and understand
|
2
|
+
## all of the comments. Some sections require modification, and others can
|
3
|
+
## be deleted if you don't need them. Once you understand the contents of
|
4
|
+
## this file, feel free to delete any comments that begin with two hash marks.
|
5
|
+
## You can find comprehensive Gem::Specification documentation, at
|
6
|
+
## http://docs.rubygems.org/read/chapter/20
|
7
|
+
Gem::Specification.new do |s|
|
8
|
+
s.specification_version = 2 if s.respond_to? :specification_version=
|
9
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
10
|
+
s.rubygems_version = '1.3.5'
|
11
|
+
|
12
|
+
## Leave these as is they will be modified for you by the rake gemspec task.
|
13
|
+
## If your rubyforge_project name is different, then edit it and comment out
|
14
|
+
## the sub! line in the Rakefile
|
15
|
+
s.name = 'metriks'
|
16
|
+
s.version = '0.8.1'
|
17
|
+
s.date = '2012-02-27'
|
18
|
+
|
19
|
+
## Make sure your summary is short. The description may be as long
|
20
|
+
## as you like.
|
21
|
+
s.summary = "An experimental metrics client"
|
22
|
+
s.description = "An experimental metrics client."
|
23
|
+
|
24
|
+
## List the primary authors. If there are a bunch of authors, it's probably
|
25
|
+
## better to set the email to an email list or something. If you don't have
|
26
|
+
## a custom homepage, consider using your GitHub URL or the like.
|
27
|
+
s.authors = ["Eric Lindvall"]
|
28
|
+
s.email = 'eric@sevenscale.com'
|
29
|
+
s.homepage = 'https://github.com/eric/metriks_client'
|
30
|
+
|
31
|
+
## This gets added to the $LOAD_PATH so that 'lib/NAME.rb' can be required as
|
32
|
+
## require 'NAME.rb' or'/lib/NAME/file.rb' can be as require 'NAME/file.rb'
|
33
|
+
s.require_paths = %w[lib]
|
34
|
+
|
35
|
+
## If your gem includes any executables, list them here.
|
36
|
+
# s.executables = ["name"]
|
37
|
+
|
38
|
+
## Specify any RDoc options here. You'll want to add your README and
|
39
|
+
## LICENSE files to the extra_rdoc_files list.
|
40
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
41
|
+
s.extra_rdoc_files = %w[README.md LICENSE]
|
42
|
+
|
43
|
+
## List your runtime dependencies here. Runtime dependencies are those
|
44
|
+
## that are needed for an end user to actually USE your code.
|
45
|
+
s.add_dependency('atomic', ["~> 1.0"])
|
46
|
+
s.add_dependency('hitimes', [ "~> 1.1"])
|
47
|
+
|
48
|
+
## List your development dependencies here. Development dependencies are
|
49
|
+
## those that are only needed during development
|
50
|
+
s.add_development_dependency('tomdoc', ["~> 0.2"])
|
51
|
+
|
52
|
+
## Leave this section as-is. It will be automatically generated from the
|
53
|
+
## contents of your Git repository via the gemspec task. DO NOT REMOVE
|
54
|
+
## THE MANIFEST COMMENTS, they are used as delimiters by the task.
|
55
|
+
# = MANIFEST =
|
56
|
+
s.files = %w[
|
57
|
+
Gemfile
|
58
|
+
LICENSE
|
59
|
+
README.md
|
60
|
+
Rakefile
|
61
|
+
lib/metriks.rb
|
62
|
+
lib/metriks/counter.rb
|
63
|
+
lib/metriks/ewma.rb
|
64
|
+
lib/metriks/histogram.rb
|
65
|
+
lib/metriks/meter.rb
|
66
|
+
lib/metriks/registry.rb
|
67
|
+
lib/metriks/reporter/logger.rb
|
68
|
+
lib/metriks/reporter/proc_title.rb
|
69
|
+
lib/metriks/simple_moving_average.rb
|
70
|
+
lib/metriks/timer.rb
|
71
|
+
lib/metriks/uniform_sample.rb
|
72
|
+
lib/metriks/utilization_timer.rb
|
73
|
+
metriks.gemspec
|
74
|
+
test/counter_test.rb
|
75
|
+
test/histogram_test.rb
|
76
|
+
test/logger_reporter_test.rb
|
77
|
+
test/meter_test.rb
|
78
|
+
test/metriks_test.rb
|
79
|
+
test/proc_title_reporter_test.rb
|
80
|
+
test/registry_test.rb
|
81
|
+
test/test_helper.rb
|
82
|
+
test/timer_test.rb
|
83
|
+
test/utilization_timer_test.rb
|
84
|
+
]
|
85
|
+
# = MANIFEST =
|
86
|
+
|
87
|
+
## Test files will be grabbed from the file list. Make sure the path glob
|
88
|
+
## matches what you actually use.
|
89
|
+
s.test_files = s.files.select { |path| path =~ /^test\/.*_test\.rb/ }
|
90
|
+
end
|