metriks 0.8.1

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.
@@ -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
@@ -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