[DRE-commits] [ruby-metriks] 01/04: Imported Upstream version 0.9.9.6
Tim Potter
tpot-guest at moszumanska.debian.org
Tue Aug 5 06:22:47 UTC 2014
This is an automated email from the git hooks/post-receive script.
tpot-guest pushed a commit to branch master
in repository ruby-metriks.
commit 0d0305af5a7c571cec0a2db5f0bb5c089c58737a
Author: Tim Potter <tpot at hp.com>
Date: Fri Aug 1 13:25:31 2014 +1000
Imported Upstream version 0.9.9.6
---
Gemfile | 9 +
LICENSE | 21 ++
README.md | 409 +++++++++++++++++++++++++++
Rakefile | 150 ++++++++++
benchmark/samplers.rb | 92 ++++++
checksums.yaml.gz | Bin 0 -> 269 bytes
lib/metriks.rb | 35 +++
lib/metriks/counter.rb | 44 +++
lib/metriks/ewma.rb | 63 +++++
lib/metriks/exponentially_decaying_sample.rb | 102 +++++++
lib/metriks/gauge.rb | 27 ++
lib/metriks/histogram.rb | 112 ++++++++
lib/metriks/meter.rb | 85 ++++++
lib/metriks/registry.rb | 207 ++++++++++++++
lib/metriks/reporter/graphite.rb | 119 ++++++++
lib/metriks/reporter/librato_metrics.rb | 173 +++++++++++
lib/metriks/reporter/logger.rb | 129 +++++++++
lib/metriks/reporter/proc_title.rb | 59 ++++
lib/metriks/reporter/riemann.rb | 119 ++++++++
lib/metriks/simple_moving_average.rb | 60 ++++
lib/metriks/snapshot.rb | 59 ++++
lib/metriks/time_tracker.rb | 26 ++
lib/metriks/timer.rb | 101 +++++++
lib/metriks/uniform_sample.rb | 40 +++
lib/metriks/utilization_timer.rb | 43 +++
metadata.yml | 154 ++++++++++
metriks.gemspec | 102 +++++++
test/counter_test.rb | 39 +++
test/gauge_test.rb | 46 +++
test/graphite_reporter_test.rb | 41 +++
test/histogram_test.rb | 199 +++++++++++++
test/librato_metrics_reporter_test.rb | 35 +++
test/logger_reporter_test.rb | 49 ++++
test/meter_test.rb | 38 +++
test/metriks_test.rb | 31 ++
test/proc_title_reporter_test.rb | 25 ++
test/registry_test.rb | 49 ++++
test/riemann_reporter_test.rb | 88 ++++++
test/test_helper.rb | 33 +++
test/thread_error_handling_tests.rb | 20 ++
test/timer_test.rb | 32 +++
test/utilization_timer_test.rb | 25 ++
42 files changed, 3290 insertions(+)
diff --git a/Gemfile b/Gemfile
new file mode 100644
index 0000000..0fa6145
--- /dev/null
+++ b/Gemfile
@@ -0,0 +1,9 @@
+source :rubygems
+
+gemspec
+
+group :test do
+ gem 'rake', '0.8.7'
+ gem 'riemann-client', '~> 0.0.7'
+ gem 'rbtree', :platform => :mri_18
+end
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..8368051
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+The MIT License
+
+Copyright (c) 2012 Eric Lindvall
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..656f128
--- /dev/null
+++ b/README.md
@@ -0,0 +1,409 @@
+# Metriks Client
+
+This is an experiment in making a threadsafe, low impact library to measure
+aspects of your ruby.
+
+The library is very much a work-in-progress. It is being developed as
+I find needs while developing [Papertrail](https://papertrailapp.com/).
+
+
+# Installing
+
+The API is still in flux, but you can add this to your project by installing
+the gem.
+
+To install, add this to your `Gemfile`:
+
+``` ruby
+gem 'metriks'
+```
+
+and re-run `bundle`.
+
+
+# Metric API Overview
+
+## Counters
+
+Basic atomic counter. Used as an underlying metric for many of the other
+more advanced metrics.
+
+
+### increment(incr = 1)
+
+Increment the counter. Without an argument it will increment by `1`.
+
+``` ruby
+ counter = Metriks.counter('calls')
+ counter.increment
+```
+
+### decrement(decr = 1)
+
+Decrement the counter. Without an argument it will decrement by `1`.
+
+``` ruby
+ counter = Metriks.counter('calls')
+ counter.decrement
+```
+
+#### count()
+
+Return the current value of the counter.
+
+``` ruby
+ counter = Metriks.counter('calls')
+ puts "counter: #{counter.count}"
+```
+
+## Gauges
+
+A gauge is an instantaneous measurement of a value.
+
+It takes a callback to measure the value in form of a block or a callable
+object.
+
+**WARNING:** The code in the callback is executed every time the `#value`
+method is called on the gauge. Most of the time this will be done by a
+metriks reporter that is running in a separate thread.
+
+``` ruby
+ # Callback as block
+ gauge = Metriks.gauge('queue.size') { queue.size }
+
+ # Callback as object responding to #call
+ callable = proc { queue.size }
+ gauge = Metriks.gauge('queue.size', callable)
+```
+
+### set(val)
+
+Set the current value.
+
+``` ruby
+ gauge = Metriks.gauge('queue_size')
+ gauge.set(queue.size)
+```
+
+### value()
+
+Returns the value returned by the callback (if one is defined), returns the
+value set via `#set` (or the default of 0) otherwise.
+
+``` ruby
+ gauge = Metriks.gauge('queue_size')
+ puts "queue size: #{gauge.value}"
+```
+
+## Meters
+
+A meter that measures the mean throughput and the one-, five-, and
+fifteen-minute exponentially-weighted moving average throughputs.
+
+### mark(val = 1)
+
+Record an event with the meter. Without an argument it will record one event.
+
+``` ruby
+ meter = Metriks.meter('requests')
+ meter.mark
+```
+
+### count()
+
+Returns the total number of events that have been recorded.
+
+``` ruby
+ meter = Metriks.meter('requests')
+ puts "total: #{meter.count}"
+```
+
+### one_minute_rate()
+
+Returns the one-minute average rate.
+
+``` ruby
+ meter = Metriks.meter('requests')
+ puts "rate: #{meter.one_minute_rate}/sec"
+```
+
+### five_minute_rate()
+
+Returns the five-minute average rate.
+
+``` ruby
+ meter = Metriks.meter('requests')
+ puts "rate: #{meter.five_minute_rate}/sec"
+```
+
+### fifteen_minute_rate()
+
+Returns the fifteen-minute average rate.
+
+``` ruby
+ meter = Metriks.meter('requests')
+ puts "rate: #{meter.fifteen_minute_rate}/sec"
+```
+
+### mean_rate()
+
+Returns the mean (average) rate of the events since the start of the process.
+
+``` ruby
+ meter = Metriks.meter('requests')
+ puts "rate: #{meter.mean_rate}/sec"
+```
+
+## Timers
+
+A timer that measures the average time as well as throughput metrics via
+a meter.
+
+### update(duration)
+
+Records the duration of an operation. This normally wouldn't need to be
+called — the `#time` method is provided to simplify recording a duration.
+
+``` ruby
+ timer = Metriks.timer('requests')
+ t0 = Time.now
+ work
+ timer.update(Time.now - t0)
+```
+
+### time(callable = nil, &block)
+
+Measure the amount of time a proc takes to execute. Takes either a block
+or an object responding to `#call` (normally a `proc` or `lambda`).
+
+``` ruby
+ timer = Metriks.timer('requests')
+ timer.time do
+ work
+ end
+```
+
+If neither a block or an object is passed to the method, an object that
+responds to `#stop` will be returned. When `#stop` is called, the time
+will be recorded.
+
+``` ruby
+ timer = Metriks.timer('requests')
+ t = timer.time
+ work
+ t.stop
+```
+
+### count()
+
+Returns the number of measurements that have been made.
+
+``` ruby
+ timer = Metriks.timer('requests')
+ puts "calls: #{timer.count}"
+```
+
+### one_minute_rate()
+
+Returns the one-minute average rate.
+
+``` ruby
+ meter = Metriks.timer('requests')
+ puts "rate: #{meter.one_minute_rate}/sec"
+```
+
+### five_minute_rate()
+
+Returns the five-minute average rate.
+
+``` ruby
+ meter = Metriks.timer('requests')
+ puts "rate: #{meter.five_minute_rate}/sec"
+```
+
+### fifteen_minute_rate()
+
+Returns the fifteen-minute average rate.
+
+``` ruby
+ meter = Metriks.timer('requests')
+ puts "rate: #{meter.fifteen_minute_rate}/sec"
+```
+
+### mean_rate()
+
+Returns the mean (average) rate of the events since the start of the process.
+
+``` ruby
+ meter = Metriks.timer('requests')
+ puts "rate: #{meter.mean_rate}/sec"
+```
+
+### min()
+
+Returns the minimum amount of time spent in the operation.
+
+``` ruby
+ meter = Metriks.timer('requests')
+ puts "time: #{meter.min} seconds"
+```
+
+### max()
+
+Returns the maximum time spent in the operation.
+
+``` ruby
+ meter = Metriks.timer('requests')
+ puts "time: #{meter.max} seconds"
+```
+
+### mean()
+
+Returns the mean (average) time spent in the operation.
+
+``` ruby
+ meter = Metriks.timer('requests')
+ puts "time: #{meter.mean} seconds"
+```
+
+### stddev()
+
+Returns the standard deviation of the mean spent in the operation.
+
+``` ruby
+ meter = Metriks.timer('requests')
+ puts "time: #{meter.stddev} seconds"
+```
+
+
+## Utilization Timer
+
+A specialized `Timer` that calculates the percentage (between `0.0` and `1.0`) of
+wall-clock time that was spent. It includes all of the methods of `Timer`.
+
+
+### one_minute_utilization()
+
+Returns the one-minute average utilization as a percentage between `0.0` and `1.0`.
+
+``` ruby
+ meter = Metriks.utilization_timer('requests')
+ puts "utilization: #{meter.one_minute_utilization * 100}%"
+```
+
+### five_minute_utilization()
+
+Returns the five-minute average utilization as a percentage between `0.0` and `1.0`.
+
+``` ruby
+ meter = Metriks.utilization_timer('requests')
+ puts "utilization: #{meter.five_minute_utilization * 100}%"
+```
+
+### fifteen_minute_utilization()
+
+Returns the fifteen-minute average utilization as a percentage between `0.0` and `1.0`.
+
+``` ruby
+ meter = Metriks.utilization_timer('requests')
+ puts "utilization: #{meter.fifteen_minute_utilization * 100}%"
+```
+
+### mean_utilization()
+
+Returns the mean (average) utilization as a percentage between `0.0` and `1.0`
+since the process started.
+
+``` ruby
+ meter = Metriks.utilization_timer('requests')
+ puts "utilization: #{meter.mean_utilization * 100}%"
+```
+
+
+# Reporter Overview
+
+How to get metrics out of the process.
+
+## Graphite Reporter
+
+Sends metrics to Graphite every 60 seconds.
+
+``` ruby
+ reporter = Metriks::Reporter::Graphite.new 'localhost', 3004
+ reporter.start
+```
+
+
+## Logger Reporter
+
+Send metrics to a logger every 60 seconds.
+
+``` ruby
+ reporter = Metriks::Reporter::Logger.new(:logger => Logger.new('log/metrics.log'))
+ reporter.start
+```
+
+
+## Librato Metrics Reporter
+
+Send metrics to Librato Metrics every 60 seconds.
+
+``` ruby
+ reporter = Metriks::Reporter::LibratoMetrics.new('email', 'token')
+ reporter.start
+```
+
+
+## Proc Title Reporter
+
+Provides a simple way to get up-to-date statistics from a process by
+updating the proctitle every 5 seconds (default).
+
+``` ruby
+ reporter = Metriks::Reporter::ProcTitle.new :interval => 5
+ reporter.add 'reqs', 'sec' do
+ Metriks.meter('rack.requests').one_minute_rate
+ end
+ reporter.start
+```
+
+will display:
+
+```
+501 17015 26.0 1.9 416976 246956 ? Ss 18:54 11:43 thin reqs: 273.3/sec
+```
+
+## Sematext Metrics Reporter
+
+[metriks-sematext](https://github.com/sematext/metriks-sematext) gem provides reporter for sending metrics to [SPM](http://sematext.com/spm/index.html).
+
+# Application Server Configuration
+
+Depending on how your application server operates, you may need to configure how reporters are created. Please look at [Troubleshooting](https://github.com/eric/metriks/wiki/Troubleshooting) for more information.
+
+# Plans
+
+An incomplete list of things I would like to see added:
+
+* Rack middleware to measure utilization, throughput and worker time
+* Basic reporters:
+ * Rack endpoint returning JSON
+ * [Statsd](https://github.com/etsy/statsd) reporter
+* Metaprogramming instrumentation hooks like [Shopify's statsd-instrument](https://github.com/Shopify/statsd-instrument)
+
+
+# Credits
+
+Most of the inspiration for this project comes from Coda Hale's amazing
+[Metrics, Metrics Everywhere][metrics-talk] talk at CodeConf and his sweet
+[Metrics][metrics] Java Library.
+
+[metrics-talk]: http://pivotallabs.com/talks/139-metrics-metrics-everywhere
+[metrics]: https://github.com/codahale/metrics
+
+
+# License
+
+Copyright (c) 2012 Eric Lindvall
+
+Published under the MIT License, see LICENSE
diff --git a/Rakefile b/Rakefile
new file mode 100644
index 0000000..99da70b
--- /dev/null
+++ b/Rakefile
@@ -0,0 +1,150 @@
+require 'rubygems'
+require 'rake'
+require 'date'
+
+#############################################################################
+#
+# Helper functions
+#
+#############################################################################
+
+def name
+ @name ||= Dir['*.gemspec'].first.split('.').first
+end
+
+def version
+ line = File.read("lib/#{name}.rb")[/^\s*VERSION\s*=\s*.*/]
+ line.match(/.*VERSION\s*=\s*['"](.*)['"]/)[1]
+end
+
+def date
+ Date.today.to_s
+end
+
+def rubyforge_project
+ name
+end
+
+def gemspec_file
+ "#{name}.gemspec"
+end
+
+def gem_file
+ "#{name}-#{version}.gem"
+end
+
+def replace_header(head, header_name)
+ head.sub!(/(\.#{header_name}\s*= ').*'/) { "#{$1}#{send(header_name)}'"}
+end
+
+#############################################################################
+#
+# Standard tasks
+#
+#############################################################################
+
+task :default => :test
+
+require 'rake/testtask'
+Rake::TestTask.new(:test) do |test|
+ test.libs << 'lib' << 'test'
+ test.pattern = 'test/**/*_test.rb'
+ test.verbose = true
+end
+
+desc "Generate RCov test coverage and open in your browser"
+task :coverage do
+ require 'rcov'
+ sh "rm -fr coverage"
+ sh "rcov test/*_test.rb"
+ sh "open coverage/index.html"
+end
+
+require 'rake/rdoctask'
+Rake::RDocTask.new do |rdoc|
+ rdoc.rdoc_dir = 'rdoc'
+ rdoc.title = "#{name} #{version}"
+ rdoc.rdoc_files.include('README*')
+ rdoc.rdoc_files.include('lib/**/*.rb')
+end
+
+desc "Open an irb session preloaded with this library"
+task :console do
+ sh "irb -rubygems -r ./lib/#{name}.rb"
+end
+
+#############################################################################
+#
+# Custom tasks (add your own tasks here)
+#
+#############################################################################
+
+
+
+#############################################################################
+#
+# Packaging tasks
+#
+#############################################################################
+
+desc "Create tag v#{version} and build and push #{gem_file} to Rubygems"
+task :release => :build do
+ unless `git branch` =~ /^\* master$/
+ puts "You must be on the master branch to release!"
+ exit!
+ end
+ sh "git commit --allow-empty -a -m 'Release #{version}'"
+ sh "git tag v#{version}"
+ sh "git push origin master"
+ sh "git push origin v#{version}"
+ sh "gem push pkg/#{name}-#{version}.gem"
+end
+
+desc "Build #{gem_file} into the pkg directory"
+task :build => :gemspec do
+ sh "mkdir -p pkg"
+ sh "gem build #{gemspec_file}"
+ sh "mv #{gem_file} pkg"
+end
+
+desc "Generate #{gemspec_file}"
+task :gemspec => :validate do
+ # read spec file and split out manifest section
+ spec = File.read(gemspec_file)
+ head, manifest, tail = spec.split(" # = MANIFEST =\n")
+
+ # replace name version and date
+ replace_header(head, :name)
+ replace_header(head, :version)
+ replace_header(head, :date)
+ #comment this out if your rubyforge_project has a different name
+ replace_header(head, :rubyforge_project)
+
+ # determine file list from git ls-files
+ files = `git ls-files`.
+ split("\n").
+ sort.
+ reject { |file| file =~ /^\./ }.
+ reject { |file| file =~ /^(rdoc|pkg)/ }.
+ map { |file| " #{file}" }.
+ join("\n")
+
+ # piece file back together and write
+ manifest = " s.files = %w[\n#{files}\n ]\n"
+ spec = [head, manifest, tail].join(" # = MANIFEST =\n")
+ File.open(gemspec_file, 'w') { |io| io.write(spec) }
+ puts "Updated #{gemspec_file}"
+end
+
+desc "Validate #{gemspec_file}"
+task :validate do
+ libfiles = Dir['lib/*'] - ["lib/#{name}.rb", "lib/#{name}"]
+ unless libfiles.empty?
+ puts "Directory `lib` should only contain a `#{name}.rb` file and `#{name}` dir."
+ exit!
+ end
+ unless Dir['VERSION*'].empty?
+ puts "A `VERSION` file at root level violates Gem best practices."
+ exit!
+ end
+end
diff --git a/benchmark/samplers.rb b/benchmark/samplers.rb
new file mode 100644
index 0000000..bc2fa4c
--- /dev/null
+++ b/benchmark/samplers.rb
@@ -0,0 +1,92 @@
+#!/usr/bin/env ruby
+
+require 'benchmark'
+require 'metriks'
+require 'rbtree'
+require 'avl_tree'
+require 'red_black_tree'
+
+fib_times = ARGV[0] ? ARGV[0].to_i : 10
+iter = ARGV[1] ? ARGV[1].to_i : 100000
+
+
+class TimerBenchmarker
+ attr_reader :iter, :fib_times
+
+ def initialize(fib_times, iter)
+ @fib_times = fib_times
+ @iter = iter
+ @mapping = { :plain => nil }
+ end
+
+ def measure(key, value)
+ @mapping[key] = value
+ end
+
+ def run
+ @results = {}
+ @mapping.each do |key, timer|
+ @results[key] = Benchmark.realtime do
+ if timer
+ for i in 1..iter
+ timer.time do
+ fib(fib_times)
+ end
+ end
+ else
+ for i in 1..iter
+ fib(fib_times)
+ end
+ end
+ end
+ end
+ report
+ end
+
+ def report
+ results = @results.sort_by { |k,v| v }
+ results.each_with_index do |(name, time), idx|
+ puts "%23s: %f secs %f secs/call" % [
+ name, time, time / iter
+ ]
+
+ if idx > 0
+ prev_name, prev_time = results[idx - 1]
+ puts "#{' ' * 25} - %.1f%% slower than %s (%f secs/call)" % [
+ (time - prev_time) / prev_time * 100, prev_name,
+ (time - prev_time) / iter
+ ]
+ end
+
+ if idx > 1
+ plain_name, plain_time = results[0]
+ puts "#{' ' * 25} - %.1f%% slower than %s (%f secs/call)" % [
+ (time - plain_time) / plain_time * 100, plain_name,
+ (time - plain_time) / iter
+ ]
+ end
+ end
+ end
+
+ def fib(n)
+ n < 2 ? n : fib(n-1) + fib(n-2)
+ end
+end
+
+reporter = TimerBenchmarker.new(fib_times, iter)
+
+reporter.measure :uniform, Metriks::Timer.new(Metriks::Histogram.new_uniform)
+
+reporter.measure :exponential, Metriks::Timer.new(Metriks::ExponentiallyDecayingSample.new(
+ Metriks::Histogram::DEFAULT_SAMPLE_SIZE, Metriks::Histogram::DEFAULT_ALPHA, RBTree.new))
+
+reporter.measure :exponential_avl, Metriks::Timer.new(Metriks::ExponentiallyDecayingSample.new(
+ Metriks::Histogram::DEFAULT_SAMPLE_SIZE, Metriks::Histogram::DEFAULT_ALPHA, AVLTree.new))
+
+reporter.measure :exponential_red_black, Metriks::Timer.new(Metriks::ExponentiallyDecayingSample.new(
+ Metriks::Histogram::DEFAULT_SAMPLE_SIZE, Metriks::Histogram::DEFAULT_ALPHA, RedBlackTree.new))
+
+puts "fib(#{fib_times}): #{iter} iterations"
+puts "-" * 50
+
+reporter.run
diff --git a/checksums.yaml.gz b/checksums.yaml.gz
new file mode 100644
index 0000000..59c8af5
Binary files /dev/null and b/checksums.yaml.gz differ
diff --git a/lib/metriks.rb b/lib/metriks.rb
new file mode 100644
index 0000000..b09f0cb
--- /dev/null
+++ b/lib/metriks.rb
@@ -0,0 +1,35 @@
+
+module Metriks
+ VERSION = '0.9.9.6'
+
+ def self.get(name)
+ Metriks::Registry.default.get(name)
+ end
+
+ def self.counter(name)
+ Metriks::Registry.default.counter(name)
+ end
+
+ def self.gauge(name, callable = nil, &block)
+ Metriks::Registry.default.gauge(name, callable, &block)
+ end
+
+ def self.timer(name)
+ Metriks::Registry.default.timer(name)
+ end
+
+ def self.utilization_timer(name)
+ Metriks::Registry.default.utilization_timer(name)
+ end
+
+ def self.meter(name)
+ Metriks::Registry.default.meter(name)
+ end
+
+ def self.histogram(name)
+ Metriks::Registry.default.histogram(name)
+ end
+end
+
+require 'metriks/registry'
+require 'metriks/reporter/proc_title'
diff --git a/lib/metriks/counter.rb b/lib/metriks/counter.rb
new file mode 100644
index 0000000..93b9662
--- /dev/null
+++ b/lib/metriks/counter.rb
@@ -0,0 +1,44 @@
+require 'atomic'
+
+module Metriks
+ # Public: Counters are one of the simplest metrics whose only operations
+ # are increment and decrement.
+ class Counter
+ # Public: Initialize a new Counter.
+ def initialize
+ @count = Atomic.new(0)
+ end
+
+ # Public: Reset the counter back to 0
+ #
+ # Returns nothing.
+ def clear
+ @count.value = 0
+ end
+
+ # Public: Increment the counter.
+ #
+ # incr - The value to add to the counter.
+ #
+ # Returns nothing.
+ def increment(incr = 1)
+ @count.update { |v| v + incr }
+ end
+
+ # Public: Decrement the counter.
+ #
+ # decr - The value to subtract from the counter.
+ #
+ # Returns nothing.
+ def decrement(decr = 1)
+ @count.update { |v| v - decr }
+ end
+
+ # Public: The current count.
+ #
+ # Returns the count.
+ def count
+ @count.value
+ end
+ end
+end
\ No newline at end of file
diff --git a/lib/metriks/ewma.rb b/lib/metriks/ewma.rb
new file mode 100644
index 0000000..d169dfe
--- /dev/null
+++ b/lib/metriks/ewma.rb
@@ -0,0 +1,63 @@
+require 'atomic'
+
+module Metriks
+ class EWMA
+ INTERVAL = 5.0
+ SECONDS_PER_MINUTE = 60.0
+
+ ONE_MINUTE = 1
+ FIVE_MINUTES = 5
+ FIFTEEN_MINUTES = 15
+
+ M1_ALPHA = 1 - Math.exp(-INTERVAL / SECONDS_PER_MINUTE / ONE_MINUTE)
+ M5_ALPHA = 1 - Math.exp(-INTERVAL / SECONDS_PER_MINUTE / FIVE_MINUTES)
+ M15_ALPHA = 1 - Math.exp(-INTERVAL / SECONDS_PER_MINUTE / FIFTEEN_MINUTES)
+
+ def self.new_m1
+ new(M1_ALPHA, INTERVAL)
+ end
+
+ def self.new_m5
+ new(M5_ALPHA, INTERVAL)
+ end
+
+ def self.new_m15
+ new(M15_ALPHA, INTERVAL)
+ end
+
+ def initialize(alpha, interval)
+ @alpha = alpha
+ @interval = interval
+
+ @initialized = false
+ @rate = Atomic.new(0.0)
+ @uncounted = Atomic.new(0)
+ end
+
+ def clear
+ @initialized = false
+ @rate.value = 0.0
+ @uncounted.value = 0
+ end
+
+ def update(value)
+ @uncounted.update { |v| v + value }
+ end
+
+ def tick
+ count = @uncounted.swap(0)
+ instant_rate = count / @interval.to_f
+
+ if @initialized
+ @rate.update { |v| v + @alpha * (instant_rate - v) }
+ else
+ @rate.value = instant_rate
+ @initialized = true
+ end
+ end
+
+ def rate
+ @rate.value
+ end
+ end
+end
\ No newline at end of file
diff --git a/lib/metriks/exponentially_decaying_sample.rb b/lib/metriks/exponentially_decaying_sample.rb
new file mode 100644
index 0000000..05da0f5
--- /dev/null
+++ b/lib/metriks/exponentially_decaying_sample.rb
@@ -0,0 +1,102 @@
+require 'atomic'
+require 'red_black_tree'
+require 'metriks/snapshot'
+
+module Metriks
+ class ExponentiallyDecayingSample
+ RESCALE_THRESHOLD = 60 * 60 # 1 hour
+
+ def initialize(reservoir_size, alpha, values = nil)
+ @values = values || RedBlackTree.new
+ @count = Atomic.new(0)
+ @next_scale_time = Atomic.new(0)
+ @alpha = alpha
+ @reservoir_size = reservoir_size
+ @mutex = Mutex.new
+ clear
+ end
+
+ def clear
+ @mutex.synchronize do
+ @values.clear
+ @count.value = 0
+ @next_scale_time.value = Time.now + RESCALE_THRESHOLD
+ @start_time = Time.now
+ end
+ end
+
+ def size
+ count = @count.value
+ count < @reservoir_size ? count : @reservoir_size
+ end
+
+ def snapshot
+ @mutex.synchronize do
+ Snapshot.new(@values.values)
+ end
+ end
+
+ def update(value, timestamp = Time.now)
+ @mutex.synchronize do
+ priority = weight(timestamp - @start_time) / rand
+ priority = Float::MAX if priority.infinite?
+ new_count = @count.update { |v| v + 1 }
+
+ if priority.nan?
+ warn "ExponentiallyDecayingSample found priority of NaN. timestamp: #{timestamp.to_f} start_time: #{@start_time.to_f}"
+ return
+ end
+
+ if new_count <= @reservoir_size
+ @values[priority] = value
+ else
+ first_priority = @values.first[0]
+ if first_priority < priority
+ unless @values[priority]
+ @values[priority] = value
+
+ until @values.delete(first_priority)
+ first_priority = @values.first[0]
+ end
+ end
+ end
+ end
+ end
+
+ now = Time.new
+ next_time = @next_scale_time.value
+ if now >= next_time
+ rescale(now, next_time)
+ end
+ end
+
+ def weight(time)
+ Math.exp(@alpha * time)
+ end
+
+ def rescale(now, next_time)
+ if @next_scale_time.compare_and_swap(next_time, now + RESCALE_THRESHOLD)
+ @mutex.synchronize do
+ old_start_time = @start_time
+ @start_time = Time.now
+ @values.keys.each do |key|
+ value = @values.delete(key)
+ new_key = key * Math.exp(- at alpha * (@start_time - old_start_time))
+
+ if key.nan?
+ warn "ExponentiallyDecayingSample found a key of NaN. old_start_time: #{old_start_time.to_f} start_time: #{@start_time.to_f}"
+ next
+ end
+
+ if new_key.nan?
+ warn "ExponentiallyDecayingSample found a new_key of NaN. key: #{key} old_start_time: #{old_start_time.to_f} start_time: #{@start_time.to_f}"
+ next
+ end
+
+ @values[new_key] = value
+ end
+ end
+ end
+ end
+ end
+end
\ No newline at end of file
diff --git a/lib/metriks/gauge.rb b/lib/metriks/gauge.rb
new file mode 100644
index 0000000..19c222b
--- /dev/null
+++ b/lib/metriks/gauge.rb
@@ -0,0 +1,27 @@
+require 'atomic'
+
+module Metriks
+ class Gauge
+ # Public: Initialize a new Gauge.
+ def initialize(callable = nil, &block)
+ @gauge = Atomic.new(nil)
+ @callback = callable || block
+ end
+
+ # Public: Set a new value.
+ #
+ # val - The new value.
+ #
+ # Returns nothing.
+ def set(val)
+ @gauge.value = val
+ end
+
+ # Public: The current value.
+ #
+ # Returns the gauge value.
+ def value
+ @callback ? @callback.call : @gauge.value
+ end
+ end
+end
diff --git a/lib/metriks/histogram.rb b/lib/metriks/histogram.rb
new file mode 100644
index 0000000..7237c50
--- /dev/null
+++ b/lib/metriks/histogram.rb
@@ -0,0 +1,112 @@
+require 'atomic'
+require 'metriks/uniform_sample'
+require 'metriks/exponentially_decaying_sample'
+
+module Metriks
+ class Histogram
+ DEFAULT_SAMPLE_SIZE = 1028
+ DEFAULT_ALPHA = 0.015
+
+ def self.new_uniform
+ new(Metriks::UniformSample.new(DEFAULT_SAMPLE_SIZE))
+ end
+
+ def self.new_exponentially_decaying
+ new(Metriks::ExponentiallyDecayingSample.new(DEFAULT_SAMPLE_SIZE, DEFAULT_ALPHA))
+ end
+
+ def initialize(sample)
+ @sample = sample
+ @count = Atomic.new(0)
+ @min = Atomic.new(nil)
+ @max = Atomic.new(nil)
+ @sum = Atomic.new(0)
+ @variance = Atomic.new([ -1, 0 ])
+ end
+
+ def clear
+ @sample.clear
+ @count.value = 0
+ @min.value = nil
+ @max.value = nil
+ @sum.value = 0
+ @variance.value = [ -1, 0 ]
+ end
+
+ def update(value)
+ @count.update { |v| v + 1 }
+ @sample.update(value)
+ self.max = value
+ self.min = value
+ @sum.update { |v| v + value }
+ update_variance(value)
+ end
+
+ def snapshot
+ @sample.snapshot
+ end
+
+ def count
+ @count.value
+ end
+
+ def max
+ count > 0 ? @max.value : 0.0
+ end
+
+ def min
+ count > 0 ? @min.value : 0.0
+ end
+
+ def mean
+ count > 0 ? @sum.value / count : 0.0
+ end
+
+ def stddev
+ count > 0 ? variance ** 0.5 : 0.0
+ end
+
+ def variance
+ count <= 1 ? 0.0 : @variance.value[1] / (count - 1)
+ end
+
+ def max=(potential_max)
+ done = false
+
+ while !done
+ current_max = @max.value
+ done = (!current_max.nil? && current_max >= potential_max) || @max.compare_and_swap(current_max, potential_max)
+ end
+ end
+
+ def min=(potential_min)
+ done = false
+
+ while !done
+ current_min = @min.value
+ done = (!current_min.nil? && current_min <= potential_min) || @min.compare_and_swap(current_min, potential_min)
+ end
+ end
+
+ def update_variance(value)
+ @variance.update do |old_values|
+ new_values = Array.new(2)
+ if old_values[0] == -1
+ new_values[0] = value
+ new_values[1] = 0
+ else
+ old_m = old_values[0]
+ old_s = old_values[1]
+
+ new_m = old_m + ((value - old_m) / count)
+ new_s = old_s + ((value - old_m) * (value - new_m))
+
+ new_values[0] = new_m
+ new_values[1] = new_s
+ end
+
+ new_values
+ end
+ end
+ end
+end
diff --git a/lib/metriks/meter.rb b/lib/metriks/meter.rb
new file mode 100644
index 0000000..3e4d64d
--- /dev/null
+++ b/lib/metriks/meter.rb
@@ -0,0 +1,85 @@
+require 'atomic'
+
+require 'metriks/ewma'
+
+module Metriks
+ class Meter
+ TICK_INTERVAL = 5.0
+
+ def initialize(averager_klass = Metriks::EWMA)
+ @count = Atomic.new(0)
+ @start_time = Time.now.to_f
+ @last_tick = Atomic.new(@start_time)
+
+ @m1_rate = averager_klass.new_m1
+ @m5_rate = averager_klass.new_m5
+ @m15_rate = averager_klass.new_m15
+ end
+
+ def clear
+ @count.value = 0
+ @start_time = Time.now.to_f
+ @last_tick.value = @start_time
+ @m1_rate.clear
+ @m5_rate.clear
+ @m15_rate.clear
+ end
+
+ def tick
+ @m1_rate.tick
+ @m5_rate.tick
+ @m15_rate.tick
+ end
+
+ def tick_if_nessesary
+ old_tick = @last_tick.value
+ new_tick = Time.new.to_f
+ age = new_tick - old_tick
+ if age > TICK_INTERVAL && @last_tick.compare_and_swap(old_tick, new_tick)
+ required_ticks = age / TICK_INTERVAL
+ required_ticks.to_i.times do
+ tick
+ end
+ end
+ end
+
+ def mark(val = 1)
+ tick_if_nessesary
+ @count.update { |v| v + val }
+ @m1_rate.update(val)
+ @m5_rate.update(val)
+ @m15_rate.update(val)
+ end
+
+ def count
+ @count.value
+ end
+
+ def one_minute_rate
+ tick_if_nessesary
+ @m1_rate.rate
+ end
+
+ def five_minute_rate
+ tick_if_nessesary
+ @m5_rate.rate
+ end
+
+ def fifteen_minute_rate
+ tick_if_nessesary
+ @m15_rate.rate
+ end
+
+ def mean_rate
+ if count == 0
+ return 0.0
+ else
+ elapsed = Time.now.to_f - @start_time
+ count / elapsed
+ end
+ end
+
+ def stop
+ end
+ end
+end
\ No newline at end of file
diff --git a/lib/metriks/registry.rb b/lib/metriks/registry.rb
new file mode 100644
index 0000000..480ce02
--- /dev/null
+++ b/lib/metriks/registry.rb
@@ -0,0 +1,207 @@
+require 'metriks/counter'
+require 'metriks/timer'
+require 'metriks/utilization_timer'
+require 'metriks/meter'
+require 'metriks/gauge'
+
+module Metriks
+ # Public: A collection of metrics
+ class Registry
+ # Public: The default registry for the process.
+ #
+ # Returns the default Registry for the process.
+ def self.default
+ @default ||= new
+ end
+
+ # Public: Initializes a new Registry.
+ def initialize
+ @mutex = Mutex.new
+ @metrics = {}
+ end
+
+ # Public: Clear all of the metrics in the Registry. This ensures all
+ # metrics that have been added are stopped.
+ #
+ # Returns nothing.
+ def clear
+ @mutex.synchronize do
+ @metrics.each do |key, metric|
+ metric.stop if metric.respond_to?(:stop)
+ end
+
+ @metrics = {}
+ end
+ end
+
+ # Public: Clear all of the metrics in the Registry. This has the same
+ # effect as calling #clear.
+ #
+ # Returns nothing.
+ def stop
+ clear
+ end
+
+ # Public: Iterate over all of the counters.
+ #
+ # Examples
+ #
+ # registry.each do |name, metric|
+ # puts name
+ # end
+ #
+ # Returns nothing.
+ def each(&block)
+ metrics = @mutex.synchronize do
+ @metrics.dup
+ end
+
+ metrics.each(&block)
+ end
+
+ # Public: Fetch or create a new counter metric. Counters are one of the
+ # simplest metrics whose only operations are increment and decrement.
+ #
+ # name - The String name of the metric to define or fetch
+ #
+ # Examples
+ #
+ # registry.counter('method.calls')
+ #
+ # Returns the Metriks::Counter identified by the name.
+ def counter(name)
+ add_or_get(name, Metriks::Counter)
+ end
+
+ # Public: Fetch or create a new gauge metric.
+ #
+ # name - The String name of the metric to define or fetch
+ #
+ # Examples
+ #
+ # registry.gauge('disk_space.used') { 1 }
+ #
+ # Returns the Metriks::Gauge identified by the name.
+ def gauge(name, callable = nil, &block)
+ add_or_get(name, Metriks::Gauge) do
+ Metriks::Gauge.new(callable, &block)
+ end
+ end
+
+ # Public: Fetch or create a new meter metric. Meters are a counter that
+ # tracks throughput along with the count.
+ #
+ # name - The String name of the metric to define or fetch
+ #
+ # Examples
+ #
+ # registry.meter('resque.calls')
+ #
+ # Returns the Metriks::Meter identified by the name.
+ def meter(name)
+ add_or_get(name, Metriks::Meter)
+ end
+
+ # Public: Fetch or create a new timer metric. Timers provide the means to
+ # time the execution of a method including statistics on the number of
+ # invocations, average length of time, throughput.
+ #
+ # name - The String name of the metric to define or fetch
+ #
+ # Examples
+ #
+ # registry.timer('resque.worker')
+ #
+ # Returns the Metriks::Timer identified by the name.
+ def timer(name)
+ add_or_get(name, Metriks::Timer)
+ end
+
+ # Public: Fetch or create a new utilization timer metric.
+ #
+ # Utilization timers are a specialized version of a timer that calculate
+ # the percentage of wall-clock time (between 0 and 1) that was spent in
+ # the method. This metric is most valuable in a single-threaded
+ # environment where a processes is waiting on an external resource like a
+ # message queue or HTTP server.
+ #
+ # name - The String name of the metric to define or fetch
+ #
+ # Examples
+ #
+ # registry.utilization_timer('rack.utilization')
+ #
+ # Returns the Metriks::UtilizationTimer identified by the name.
+ def utilization_timer(name)
+ add_or_get(name, Metriks::UtilizationTimer)
+ end
+
+ # Public: Fetch or create a new histogram metric. Histograms record values
+ # and expose statistics about the distribution of the data like median and
+ # 95th percentile.
+ #
+ # name - The String name of the metric to define or fetch
+ #
+ # Examples
+ #
+ # registry.histogram('backlog.wait')
+ #
+ # Returns the Metriks::Histogram identified by the name.
+ def histogram(name)
+ add_or_get(name, Metriks::Histogram) do
+ Metriks::Histogram.new_exponentially_decaying
+ end
+ end
+
+ # Public: Fetch an existing metric.
+ #
+ # name - The String name of the metric to fetch
+ #
+ # Examples
+ #
+ # registry.get('rack.utilization')
+ #
+ # Returns the metric or nil.
+ def get(name)
+ @mutex.synchronize do
+ @metrics[name]
+ end
+ end
+
+ # Public: Add a new metric.
+ #
+ # name - The String name of the metric to add
+ # metric - The metric instance to add
+ #
+ # Examples
+ #
+ # registry.add('method.calls', Metriks::Counter.new)
+ #
+ # Returns nothing.
+ # Raises RuntimeError if the metric name is already defined
+ def add(name, metric)
+ @mutex.synchronize do
+ if @metrics[name]
+ raise "Metric '#{name}' already defined"
+ else
+ @metrics[name] = metric
+ end
+ end
+ end
+
+ protected
+ def add_or_get(name, klass, &create_metric)
+ @mutex.synchronize do
+ if metric = @metrics[name]
+ if !metric.is_a?(klass)
+ raise "Metric already defined as '#{metric.class}'"
+ else
+ return metric
+ end
+ else
+ @metrics[name] = create_metric ? create_metric.call : klass.new
+ end
+ end
+ end
+ end
+end
diff --git a/lib/metriks/reporter/graphite.rb b/lib/metriks/reporter/graphite.rb
new file mode 100644
index 0000000..cc2a347
--- /dev/null
+++ b/lib/metriks/reporter/graphite.rb
@@ -0,0 +1,119 @@
+require 'socket'
+
+module Metriks::Reporter
+ class Graphite
+ attr_reader :host, :port
+
+ def initialize(host, port, options = {})
+ @host = host
+ @port = port
+
+ @prefix = options[:prefix]
+
+ @registry = options[:registry] || Metriks::Registry.default
+ @interval = options[:interval] || 60
+ @on_error = options[:on_error] || proc { |ex| }
+ end
+
+ def socket
+ @socket = nil if @socket && @socket.closed?
+ @socket ||= TCPSocket.new(@host, @port)
+ end
+
+ def start
+ @thread ||= Thread.new do
+ loop do
+ sleep @interval
+
+ Thread.new do
+ begin
+ write
+ rescue Exception => ex
+ @on_error[ex] rescue nil
+ end
+ end
+ end
+ end
+ end
+
+ def stop
+ @thread.kill if @thread
+ @thread = nil
+ end
+
+ def restart
+ stop
+ start
+ end
+
+ def write
+ @registry.each do |name, metric|
+ case metric
+ when Metriks::Meter
+ write_metric name, metric, [
+ :count, :one_minute_rate, :five_minute_rate,
+ :fifteen_minute_rate, :mean_rate
+ ]
+ when Metriks::Counter
+ write_metric name, metric, [
+ :count
+ ]
+ when Metriks::Gauge
+ write_metric name, metric, [
+ :value
+ ]
+ when Metriks::UtilizationTimer
+ write_metric name, metric, [
+ :count, :one_minute_rate, :five_minute_rate,
+ :fifteen_minute_rate, :mean_rate,
+ :min, :max, :mean, :stddev,
+ :one_minute_utilization, :five_minute_utilization,
+ :fifteen_minute_utilization, :mean_utilization,
+ ], [
+ :median, :get_95th_percentile
+ ]
+ when Metriks::Timer
+ write_metric name, metric, [
+ :count, :one_minute_rate, :five_minute_rate,
+ :fifteen_minute_rate, :mean_rate,
+ :min, :max, :mean, :stddev
+ ], [
+ :median, :get_95th_percentile
+ ]
+ when Metriks::Histogram
+ write_metric name, metric, [
+ :count, :min, :max, :mean, :stddev
+ ], [
+ :median, :get_95th_percentile
+ ]
+ end
+ end
+ end
+
+ def write_metric(base_name, metric, keys, snapshot_keys = [])
+ time = Time.now.to_i
+
+ base_name = base_name.to_s.gsub(/ +/, '_')
+ if @prefix
+ base_name = "#{@prefix}.#{base_name}"
+ end
+
+ keys.flatten.each do |key|
+ name = key.to_s.gsub(/^get_/, '')
+ value = metric.send(key)
+ socket.write("#{base_name}.#{name} #{value} #{time}\n")
+ end
+
+ unless snapshot_keys.empty?
+ snapshot = metric.snapshot
+ snapshot_keys.flatten.each do |key|
+ name = key.to_s.gsub(/^get_/, '')
+ value = snapshot.send(key)
+ socket.write("#{base_name}.#{name} #{value} #{time}\n")
+ end
+ end
+ rescue Errno::EPIPE
+ socket.close
+ end
+ end
+end
diff --git a/lib/metriks/reporter/librato_metrics.rb b/lib/metriks/reporter/librato_metrics.rb
new file mode 100644
index 0000000..bdcaa2c
--- /dev/null
+++ b/lib/metriks/reporter/librato_metrics.rb
@@ -0,0 +1,173 @@
+require 'metriks/time_tracker'
+require 'net/https'
+
+module Metriks::Reporter
+ class LibratoMetrics
+ attr_accessor :prefix, :source
+
+ def initialize(email, token, options = {})
+ @email = email
+ @token = token
+
+ @prefix = options[:prefix]
+ @source = options[:source]
+
+ @registry = options[:registry] || Metriks::Registry.default
+ @time_tracker = Metriks::TimeTracker.new(options[:interval] || 60)
+ @on_error = options[:on_error] || proc { |ex| }
+ end
+
+ def start
+ @thread ||= Thread.new do
+ loop do
+ @time_tracker.sleep
+
+ Thread.new do
+ begin
+ write
+ rescue Exception => ex
+ @on_error[ex] rescue nil
+ end
+ end
+ end
+ end
+ end
+
+ def stop
+ @thread.kill if @thread
+ @thread = nil
+ end
+
+ def restart
+ stop
+ start
+ end
+
+ def write
+ gauges = []
+ @registry.each do |name, metric|
+ gauges << case metric
+ when Metriks::Meter
+ prepare_metric name, metric, [
+ :count, :one_minute_rate, :five_minute_rate,
+ :fifteen_minute_rate, :mean_rate
+ ]
+ when Metriks::Counter
+ prepare_metric name, metric, [
+ :count
+ ]
+ when Metriks::Gauge
+ prepare_metric name, metric, [
+ :value
+ ]
+ when Metriks::UtilizationTimer
+ prepare_metric name, metric, [
+ :count, :one_minute_rate, :five_minute_rate,
+ :fifteen_minute_rate, :mean_rate,
+ :min, :max, :mean, :stddev,
+ :one_minute_utilization, :five_minute_utilization,
+ :fifteen_minute_utilization, :mean_utilization,
+ ], [
+ :median, :get_95th_percentile
+ ]
+ when Metriks::Timer
+ prepare_metric name, metric, [
+ :count, :one_minute_rate, :five_minute_rate,
+ :fifteen_minute_rate, :mean_rate,
+ :min, :max, :mean, :stddev
+ ], [
+ :median, :get_95th_percentile
+ ]
+ when Metriks::Histogram
+ prepare_metric name, metric, [
+ :count, :min, :max, :mean, :stddev
+ ], [
+ :median, :get_95th_percentile
+ ]
+ end
+ end
+
+ gauges.flatten!
+
+ unless gauges.empty?
+ submit(form_data(gauges.flatten))
+ end
+ end
+
+ def submit(data)
+ url = URI.parse('https://metrics-api.librato.com/v1/metrics')
+ req = Net::HTTP::Post.new(url.path)
+ req.basic_auth(@email, @token)
+ req.set_form_data(data)
+
+ http = Net::HTTP.new(url.host, url.port)
+ http.verify_mode = OpenSSL::SSL::VERIFY_PEER
+ http.use_ssl = true
+ store = OpenSSL::X509::Store.new
+ store.set_default_paths
+ http.cert_store = store
+
+ case res = http.start { |http| http.request(req) }
+ when Net::HTTPSuccess, Net::HTTPRedirection
+ # OK
+ else
+ res.error!
+ end
+ end
+
+ def form_data(metrics)
+ data = {}
+
+ metrics.each_with_index do |gauge, idx|
+ gauge.each do |key, value|
+ if value
+ data["gauges[#{idx}][#{key}]"] = value.to_s
+ end
+ end
+ end
+
+ data
+ end
+
+ def prepare_metric(base_name, metric, keys, snapshot_keys = [])
+ results = []
+ time = @time_tracker.now_floored
+
+ base_name = base_name.to_s.gsub(/ +/, '_')
+ if @prefix
+ base_name = "#{@prefix}.#{base_name}"
+ end
+
+ keys.flatten.each do |key|
+ name = key.to_s.gsub(/^get_/, '')
+ value = metric.send(key)
+
+ results << {
+ :type => "gauge",
+ :name => "#{base_name}.#{name}",
+ :source => @source,
+ :measure_time => time,
+ :value => value
+ }
+ end
+
+ unless snapshot_keys.empty?
+ snapshot = metric.snapshot
+ snapshot_keys.flatten.each do |key|
+ name = key.to_s.gsub(/^get_/, '')
+ value = snapshot.send(key)
+
+ results << {
+ :type => "gauge",
+ :name => "#{base_name}.#{name}",
+ :source => @source,
+ :measure_time => time,
+ :value => value
+ }
+ end
+ end
+
+ results
+ end
+ end
+end
diff --git a/lib/metriks/reporter/logger.rb b/lib/metriks/reporter/logger.rb
new file mode 100644
index 0000000..5904f9b
--- /dev/null
+++ b/lib/metriks/reporter/logger.rb
@@ -0,0 +1,129 @@
+require 'logger'
+require 'metriks/time_tracker'
+
+module Metriks::Reporter
+ class Logger
+ attr_accessor :prefix, :log_level, :logger
+
+ def initialize(options = {})
+ @logger = options[:logger] || ::Logger.new(STDOUT)
+ @log_level = options[:log_level] || ::Logger::INFO
+ @prefix = options[:prefix] || 'metriks:'
+
+ @registry = options[:registry] || Metriks::Registry.default
+ @time_tracker = Metriks::TimeTracker.new(options[:interval] || 60)
+ @on_error = options[:on_error] || proc { |ex| }
+ end
+
+ def start
+ @thread ||= Thread.new do
+ loop do
+ @time_tracker.sleep
+
+ begin
+ write
+ rescue Exception => ex
+ @on_error[ex] rescue nil
+ end
+ end
+ end
+ end
+
+ def stop
+ @thread.kill if @thread
+ @thread = nil
+ end
+
+ def restart
+ stop
+ start
+ end
+
+ def flush
+ if !@last_write || @last_write.min != Time.now.min
+ write
+ end
+ end
+
+ def write
+ @last_write = Time.now
+
+ @registry.each do |name, metric|
+ case metric
+ when Metriks::Meter
+ log_metric name, 'meter', metric, [
+ :count, :one_minute_rate, :five_minute_rate,
+ :fifteen_minute_rate, :mean_rate
+ ]
+ when Metriks::Counter
+ log_metric name, 'counter', metric, [
+ :count
+ ]
+ when Metriks::Gauge
+ log_metric name, 'gauge', metric, [
+ :value
+ ]
+ when Metriks::UtilizationTimer
+ log_metric name, 'utilization_timer', metric, [
+ :count, :one_minute_rate, :five_minute_rate,
+ :fifteen_minute_rate, :mean_rate,
+ :min, :max, :mean, :stddev,
+ :one_minute_utilization, :five_minute_utilization,
+ :fifteen_minute_utilization, :mean_utilization,
+ ], [
+ :median, :get_95th_percentile
+ ]
+ when Metriks::Timer
+ log_metric name, 'timer', metric, [
+ :count, :one_minute_rate, :five_minute_rate,
+ :fifteen_minute_rate, :mean_rate,
+ :min, :max, :mean, :stddev
+ ], [
+ :median, :get_95th_percentile
+ ]
+ when Metriks::Histogram
+ log_metric name, 'histogram', metric, [
+ :count, :min, :max, :mean, :stddev
+ ], [
+ :median, :get_95th_percentile
+ ]
+ end
+ end
+ end
+
+ def extract_from_metric(metric, *keys)
+ keys.flatten.collect do |key|
+ name = key.to_s.gsub(/^get_/, '')
+ [ { name => metric.send(key) } ]
+ end
+ end
+
+ def log_metric(name, type, metric, keys, snapshot_keys = [])
+ message = []
+
+ message << @prefix if @prefix
+ message << { :time => Time.now.to_i }
+
+ message << { :name => name }
+ message << { :type => type }
+ message += extract_from_metric(metric, keys)
+
+ unless snapshot_keys.empty?
+ snapshot = metric.snapshot
+ message += extract_from_metric(snapshot, snapshot_keys)
+ end
+
+ @logger.add(@log_level, format_message(message))
+ end
+
+ def format_message(args)
+ args.map do |arg|
+ case arg
+ when Hash then arg.map { |name, value| "#{name}=#{format_message([value])}" }
+ when Array then format_message(arg)
+ else arg
+ end
+ end.join(' ')
+ end
+ end
+end
diff --git a/lib/metriks/reporter/proc_title.rb b/lib/metriks/reporter/proc_title.rb
new file mode 100644
index 0000000..0ed3eb0
--- /dev/null
+++ b/lib/metriks/reporter/proc_title.rb
@@ -0,0 +1,59 @@
+module Metriks::Reporter
+ class ProcTitle
+ def initialize(options = {})
+ @rounding = options[:rounding] || 1
+ @prefix = options[:prefix] || $0.dup
+
+ @interval = options[:interval] || 5
+ @on_error = options[:on_error] || proc { |ex| }
+
+ @metrics = []
+ end
+
+ def add(name, suffix = nil, &block)
+ @metrics << [ name, suffix, block ]
+ end
+
+ def empty?
+ @metrics.empty?
+ end
+
+ def start
+ @thread ||= Thread.new do
+ loop do
+ begin
+ unless @metrics.empty?
+ title = generate_title
+ if title && !title.empty?
+ $0 = "#{@prefix} #{title}"
+ end
+ end
+ rescue Exception => ex
+ @on_error[ex] rescue nil
+ end
+ sleep @interval
+ end
+ end
+ end
+
+ def stop
+ @thread.kill if @thread
+ @thread = nil
+ end
+
+ def restart
+ stop
+ start
+ end
+
+ protected
+ def generate_title
+ @metrics.collect do |name, suffix, block|
+ val = block.call
+ val = "%.#{@rounding}f" % val if val.is_a?(Float)
+
+ "#{name}: #{val}#{suffix}"
+ end.join(' ')
+ end
+ end
+end
\ No newline at end of file
diff --git a/lib/metriks/reporter/riemann.rb b/lib/metriks/reporter/riemann.rb
new file mode 100644
index 0000000..bdab081
--- /dev/null
+++ b/lib/metriks/reporter/riemann.rb
@@ -0,0 +1,119 @@
+module Metriks::Reporter
+ class Riemann
+ require 'riemann/client'
+
+ attr_accessor :client
+ def initialize(options = {})
+ @client = ::Riemann::Client.new(
+ :host => options[:host],
+ :port => options[:port]
+ )
+ @registry = options[:registry] || Metriks::Registry.default
+ @interval = options[:interval] || 60
+ @on_error = options[:on_error] || proc { |ex| }
+
+ @default_event = options[:default_event] || {}
+ @default_event[:ttl] ||= @interval * 1.5
+ end
+
+ def start
+ @thread ||= Thread.new do
+ loop do
+ sleep @interval
+
+ Thread.new do
+ begin
+ write
+ rescue Exception => ex
+ @on_error[ex] rescue nil
+ end
+ end
+ end
+ end
+ end
+
+ def stop
+ @thread.kill if @thread
+ @thread = nil
+ end
+
+ def restart
+ stop
+ start
+ end
+
+ def flush
+ # Is this supposed to take interval into account? --aphyr
+ if !@last_write || @last_write.min != Time.now.min
+ write
+ end
+ end
+
+ def write
+ @last_write = Time.now
+
+ @registry.each do |name, metric|
+ case metric
+ when Metriks::Meter
+ send_metric name, 'meter', metric, [
+ :count, :one_minute_rate, :five_minute_rate,
+ :fifteen_minute_rate, :mean_rate
+ ]
+ when Metriks::Counter
+ send_metric name, 'counter', metric, [
+ :count
+ ]
+ when Metriks::Gauge
+ send_metric name, 'gauge', metric, [
+ :value
+ ]
+ when Metriks::UtilizationTimer
+ send_metric name, 'utilization_timer', metric, [
+ :count, :one_minute_rate, :five_minute_rate,
+ :fifteen_minute_rate, :mean_rate,
+ :min, :max, :mean, :stddev,
+ :one_minute_utilization, :five_minute_utilization,
+ :fifteen_minute_utilization, :mean_utilization,
+ ], [
+ :median, :get_95th_percentile
+ ]
+ when Metriks::Timer
+ send_metric name, 'timer', metric, [
+ :count, :one_minute_rate, :five_minute_rate,
+ :fifteen_minute_rate, :mean_rate,
+ :min, :max, :mean, :stddev
+ ], [
+ :median, :get_95th_percentile
+ ]
+ when Metriks::Histogram
+ send_metric name, 'histogram', metric, [
+ :count, :min, :max, :mean, :stddev
+ ], [
+ :median, :get_95th_percentile
+ ]
+ end
+ end
+ end
+
+ def send_metric(name, type, metric, keys, snapshot_keys = [])
+ keys.each do |key|
+ @client << @default_event.merge(
+ :service => "#{name} #{key}",
+ :metric => metric.send(key),
+ :tags => [type]
+ )
+ end
+
+ unless snapshot_keys.empty?
+ snapshot = metric.snapshot
+ snapshot_keys.each do |key|
+ @client << @default_event.merge(
+ :service => "#{name} #{key}",
+ :metric => snapshot.send(key),
+ :tags => [type]
+ )
+ end
+ end
+ end
+ end
+end
diff --git a/lib/metriks/simple_moving_average.rb b/lib/metriks/simple_moving_average.rb
new file mode 100644
index 0000000..fb0d08f
--- /dev/null
+++ b/lib/metriks/simple_moving_average.rb
@@ -0,0 +1,60 @@
+require 'atomic'
+
+module Metriks
+ class SimpleMovingAverage
+ INTERVAL = 5.0
+ SECONDS_PER_MINUTE = 60.0
+
+ ONE_MINUTE = 1
+ FIVE_MINUTES = 5
+ FIFTEEN_MINUTES = 15
+
+ def self.new_m1
+ new(ONE_MINUTE * SECONDS_PER_MINUTE, INTERVAL)
+ end
+
+ def self.new_m5
+ new(FIVE_MINUTES * SECONDS_PER_MINUTE, INTERVAL)
+ end
+
+ def self.new_m15
+ new(FIFTEEN_MINUTES * SECONDS_PER_MINUTE, INTERVAL)
+ end
+
+ def initialize(duration, interval)
+ @interval = interval
+ @duration = duration
+
+ @values = Array.new((duration / interval).to_i) { Atomic.new(nil) }
+ @index = Atomic.new(0)
+ end
+
+ def clear
+ @values.each do |value|
+ value.value = nil
+ end
+ @index.value = 0
+ end
+
+ def update(value)
+ @values[@index.value].update { |v| v ? v + value : value }
+ end
+
+ def tick
+ @index.update { |v| v < @values.length - 1 ? v + 1 : 0 }
+ end
+
+ def rate
+ num, count = 0.0, 0.0
+
+ @values.each do |value|
+ if v = value.value
+ num += v
+ count += 1
+ end
+ end
+
+ num / count / @interval.to_f
+ end
+ end
+end
\ No newline at end of file
diff --git a/lib/metriks/snapshot.rb b/lib/metriks/snapshot.rb
new file mode 100644
index 0000000..7885c5f
--- /dev/null
+++ b/lib/metriks/snapshot.rb
@@ -0,0 +1,59 @@
+module Metriks
+ class Snapshot
+ MEDIAN_Q = 0.5
+ P75_Q = 0.75
+ P95_Q = 0.95
+ P98_Q = 0.98
+ P99_Q = 0.99
+ P999_Q = 0.999
+
+ attr_reader :values
+
+ def initialize(values)
+ @values = values.sort
+ end
+
+ def value(quantile)
+ raise ArgumentError, "quantile must be between 0.0 and 1.0" if quantile < 0.0 || quantile > 1.0
+
+ return 0.0 if @values.empty?
+
+ pos = quantile * (@values.length + 1)
+
+ return @values.first if pos < 1
+ return @values.last if pos >= @values.length
+
+ lower = @values[pos.to_i - 1]
+ upper = @values[pos.to_i]
+ lower + (pos - pos.floor) * (upper - lower)
+ end
+
+ def size
+ @values.length
+ end
+
+ def median
+ value(MEDIAN_Q)
+ end
+
+ def get_75th_percentile
+ value(P75_Q)
+ end
+
+ def get_95th_percentile
+ value(P95_Q)
+ end
+
+ def get_98th_percentile
+ value(P98_Q)
+ end
+
+ def get_99th_percentile
+ value(P99_Q)
+ end
+
+ def get_999th_percentile
+ value(P999_Q)
+ end
+ end
+end
diff --git a/lib/metriks/time_tracker.rb b/lib/metriks/time_tracker.rb
new file mode 100644
index 0000000..6c5f620
--- /dev/null
+++ b/lib/metriks/time_tracker.rb
@@ -0,0 +1,26 @@
+module Metriks
+ class TimeTracker
+ def initialize(interval)
+ @interval = interval
+ @next_time = Time.now.to_f
+ end
+
+ def sleep
+ sleep_time = next_time - Time.now.to_f
+ if sleep_time > 0
+ Kernel.sleep(sleep_time)
+ end
+ end
+
+ def now_floored
+ time = Time.now.to_i
+ time - (time % @interval)
+ end
+
+ def next_time
+ now = Time.now.to_f
+ @next_time = now if @next_time <= now
+ @next_time += @interval - (@next_time % @interval)
+ end
+ end
+end
diff --git a/lib/metriks/timer.rb b/lib/metriks/timer.rb
new file mode 100644
index 0000000..cf1181f
--- /dev/null
+++ b/lib/metriks/timer.rb
@@ -0,0 +1,101 @@
+require 'atomic'
+require 'hitimes'
+
+require 'metriks/meter'
+require 'metriks/histogram'
+
+module Metriks
+ class Timer
+ class Context
+ def initialize(timer)
+ @timer = timer
+ @interval = Hitimes::Interval.now
+ end
+
+ def restart
+ @interval = Hitimes::Interval.now
+ end
+
+ def stop
+ @interval.stop
+ @timer.update(@interval.duration)
+ end
+ end
+
+ def initialize(histogram = Metriks::Histogram.new_exponentially_decaying)
+ @meter = Metriks::Meter.new
+ @histogram = histogram
+ end
+
+ def clear
+ @meter.clear
+ @histogram.clear
+ end
+
+ def update(duration)
+ if duration >= 0
+ @meter.mark
+ @histogram.update(duration)
+ end
+ end
+
+ def time(callable = nil, &block)
+ callable ||= block
+ context = Context.new(self)
+
+ if callable.nil?
+ return context
+ end
+
+ begin
+ return callable.call
+ ensure
+ context.stop
+ end
+ end
+
+ def snapshot
+ @histogram.snapshot
+ end
+
+ def count
+ @histogram.count
+ end
+
+ def one_minute_rate
+ @meter.one_minute_rate
+ end
+
+ def five_minute_rate
+ @meter.five_minute_rate
+ end
+
+ def fifteen_minute_rate
+ @meter.fifteen_minute_rate
+ end
+
+ def mean_rate
+ @meter.mean_rate
+ end
+
+ def min
+ @histogram.min
+ end
+
+ def max
+ @histogram.max
+ end
+
+ def mean
+ @histogram.mean
+ end
+
+ def stddev
+ @histogram.stddev
+ end
+
+ def stop
+ @meter.stop
+ end
+ end
+end
\ No newline at end of file
diff --git a/lib/metriks/uniform_sample.rb b/lib/metriks/uniform_sample.rb
new file mode 100644
index 0000000..b3d65ac
--- /dev/null
+++ b/lib/metriks/uniform_sample.rb
@@ -0,0 +1,40 @@
+require 'atomic'
+require 'metriks/snapshot'
+
+module Metriks
+ class UniformSample
+ def initialize(reservoir_size)
+ @values = Array.new(reservoir_size, 0)
+ @count = Atomic.new(0)
+ end
+
+ def clear
+ @values.length.times do |idx|
+ @values[idx] = 0
+ end
+ @count.value = 0
+ end
+
+ def size
+ count = @count.value
+ count > @values.length ? @values.length : count
+ end
+
+ def snapshot
+ Snapshot.new(@values.slice(0, size))
+ end
+
+ def update(value)
+ new_count = @count.update { |v| v + 1 }
+
+ if new_count <= @values.length
+ @values[new_count - 1] = value
+ else
+ idx = rand(new_count)
+ if idx < @values.length
+ @values[idx] = value
+ end
+ end
+ end
+ end
+end
\ No newline at end of file
diff --git a/lib/metriks/utilization_timer.rb b/lib/metriks/utilization_timer.rb
new file mode 100644
index 0000000..171c45c
--- /dev/null
+++ b/lib/metriks/utilization_timer.rb
@@ -0,0 +1,43 @@
+require 'metriks/timer'
+
+module Metriks
+ class UtilizationTimer < Metriks::Timer
+ def initialize
+ super
+ @duration_meter = Metriks::Meter.new
+ end
+
+ def clear
+ super
+ @duration_meter.clear
+ end
+
+ def update(duration)
+ super
+ if duration >= 0
+ @duration_meter.mark(duration)
+ end
+ end
+
+ def one_minute_utilization
+ @duration_meter.one_minute_rate
+ end
+
+ def five_minute_utilization
+ @duration_meter.five_minute_rate
+ end
+
+ def fifteen_minute_utilization
+ @duration_meter.fifteen_minute_rate
+ end
+
+ def mean_utilization
+ @duration_meter.mean_rate
+ end
+
+ def stop
+ super
+ @duration_meter.stop
+ end
+ end
+end
\ No newline at end of file
diff --git a/metadata.yml b/metadata.yml
new file mode 100644
index 0000000..1b2e20e
--- /dev/null
+++ b/metadata.yml
@@ -0,0 +1,154 @@
+--- !ruby/object:Gem::Specification
+name: metriks
+version: !ruby/object:Gem::Version
+ version: 0.9.9.6
+platform: ruby
+authors:
+- Eric Lindvall
+autorequire:
+bindir: bin
+cert_chain: []
+date: 2014-02-24 00:00:00.000000000 Z
+dependencies:
+- !ruby/object:Gem::Dependency
+ name: atomic
+ requirement: !ruby/object:Gem::Requirement
+ requirements:
+ - - ~>
+ - !ruby/object:Gem::Version
+ version: '1.0'
+ type: :runtime
+ prerelease: false
+ version_requirements: !ruby/object:Gem::Requirement
+ requirements:
+ - - ~>
+ - !ruby/object:Gem::Version
+ version: '1.0'
+- !ruby/object:Gem::Dependency
+ name: hitimes
+ requirement: !ruby/object:Gem::Requirement
+ requirements:
+ - - ~>
+ - !ruby/object:Gem::Version
+ version: '1.1'
+ type: :runtime
+ prerelease: false
+ version_requirements: !ruby/object:Gem::Requirement
+ requirements:
+ - - ~>
+ - !ruby/object:Gem::Version
+ version: '1.1'
+- !ruby/object:Gem::Dependency
+ name: avl_tree
+ requirement: !ruby/object:Gem::Requirement
+ requirements:
+ - - ~>
+ - !ruby/object:Gem::Version
+ version: 1.1.2
+ type: :runtime
+ prerelease: false
+ version_requirements: !ruby/object:Gem::Requirement
+ requirements:
+ - - ~>
+ - !ruby/object:Gem::Version
+ version: 1.1.2
+- !ruby/object:Gem::Dependency
+ name: mocha
+ requirement: !ruby/object:Gem::Requirement
+ requirements:
+ - - ~>
+ - !ruby/object:Gem::Version
+ version: '0.10'
+ type: :development
+ prerelease: false
+ version_requirements: !ruby/object:Gem::Requirement
+ requirements:
+ - - ~>
+ - !ruby/object:Gem::Version
+ version: '0.10'
+description: An experimental metrics client.
+email: eric at sevenscale.com
+executables: []
+extensions: []
+extra_rdoc_files:
+- README.md
+- LICENSE
+files:
+- Gemfile
+- LICENSE
+- README.md
+- Rakefile
+- benchmark/samplers.rb
+- lib/metriks.rb
+- lib/metriks/counter.rb
+- lib/metriks/ewma.rb
+- lib/metriks/exponentially_decaying_sample.rb
+- lib/metriks/gauge.rb
+- lib/metriks/histogram.rb
+- lib/metriks/meter.rb
+- lib/metriks/registry.rb
+- lib/metriks/reporter/graphite.rb
+- lib/metriks/reporter/librato_metrics.rb
+- lib/metriks/reporter/logger.rb
+- lib/metriks/reporter/proc_title.rb
+- lib/metriks/reporter/riemann.rb
+- lib/metriks/simple_moving_average.rb
+- lib/metriks/snapshot.rb
+- lib/metriks/time_tracker.rb
+- lib/metriks/timer.rb
+- lib/metriks/uniform_sample.rb
+- lib/metriks/utilization_timer.rb
+- metriks.gemspec
+- test/counter_test.rb
+- test/gauge_test.rb
+- test/graphite_reporter_test.rb
+- test/histogram_test.rb
+- test/librato_metrics_reporter_test.rb
+- test/logger_reporter_test.rb
+- test/meter_test.rb
+- test/metriks_test.rb
+- test/proc_title_reporter_test.rb
+- test/registry_test.rb
+- test/riemann_reporter_test.rb
+- test/test_helper.rb
+- test/thread_error_handling_tests.rb
+- test/timer_test.rb
+- test/utilization_timer_test.rb
+homepage: https://github.com/eric/metriks
+licenses: []
+metadata: {}
+post_install_message:
+rdoc_options:
+- --charset=UTF-8
+require_paths:
+- lib
+required_ruby_version: !ruby/object:Gem::Requirement
+ requirements:
+ - - '>='
+ - !ruby/object:Gem::Version
+ version: '0'
+required_rubygems_version: !ruby/object:Gem::Requirement
+ requirements:
+ - - '>='
+ - !ruby/object:Gem::Version
+ version: '0'
+requirements: []
+rubyforge_project:
+rubygems_version: 2.0.3
+signing_key:
+specification_version: 2
+summary: An experimental metrics client
+test_files:
+- test/counter_test.rb
+- test/gauge_test.rb
+- test/graphite_reporter_test.rb
+- test/histogram_test.rb
+- test/librato_metrics_reporter_test.rb
+- test/logger_reporter_test.rb
+- test/meter_test.rb
+- test/metriks_test.rb
+- test/proc_title_reporter_test.rb
+- test/registry_test.rb
+- test/riemann_reporter_test.rb
+- test/timer_test.rb
+- test/utilization_timer_test.rb
diff --git a/metriks.gemspec b/metriks.gemspec
new file mode 100644
index 0000000..31502d2
--- /dev/null
+++ b/metriks.gemspec
@@ -0,0 +1,102 @@
+## This is the rakegem gemspec template. Make sure you read and understand
+## all of the comments. Some sections require modification, and others can
+## be deleted if you don't need them. Once you understand the contents of
+## this file, feel free to delete any comments that begin with two hash marks.
+## You can find comprehensive Gem::Specification documentation, at
+## http://docs.rubygems.org/read/chapter/20
+Gem::Specification.new do |s|
+ s.specification_version = 2 if s.respond_to? :specification_version=
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
+ s.rubygems_version = '1.3.5'
+
+ ## Leave these as is they will be modified for you by the rake gemspec task.
+ ## If your rubyforge_project name is different, then edit it and comment out
+ ## the sub! line in the Rakefile
+ s.name = 'metriks'
+ s.version = '0.9.9.6'
+ s.date = '2014-02-24'
+
+ ## Make sure your summary is short. The description may be as long
+ ## as you like.
+ s.summary = "An experimental metrics client"
+ s.description = "An experimental metrics client."
+
+ ## List the primary authors. If there are a bunch of authors, it's probably
+ ## better to set the email to an email list or something. If you don't have
+ ## a custom homepage, consider using your GitHub URL or the like.
+ s.authors = ["Eric Lindvall"]
+ s.email = 'eric at sevenscale.com'
+ s.homepage = 'https://github.com/eric/metriks'
+
+ ## This gets added to the $LOAD_PATH so that 'lib/NAME.rb' can be required as
+ ## require 'NAME.rb' or'/lib/NAME/file.rb' can be as require 'NAME/file.rb'
+ s.require_paths = %w[lib]
+
+ ## Specify any RDoc options here. You'll want to add your README and
+ ## LICENSE files to the extra_rdoc_files list.
+ s.rdoc_options = ["--charset=UTF-8"]
+ s.extra_rdoc_files = %w[README.md LICENSE]
+
+ ## List your runtime dependencies here. Runtime dependencies are those
+ ## that are needed for an end user to actually USE your code.
+ s.add_dependency('atomic', ["~> 1.0"])
+ s.add_dependency('hitimes', [ "~> 1.1"])
+ s.add_dependency('avl_tree', [ "~> 1.1.2" ])
+
+ ## List your development dependencies here. Development dependencies are
+ ## those that are only needed during development
+ # s.add_development_dependency('tomdoc', ["~> 0.2"])
+ s.add_development_dependency('mocha', ['~> 0.10'])
+
+ ## Leave this section as-is. It will be automatically generated from the
+ ## contents of your Git repository via the gemspec task. DO NOT REMOVE
+ ## THE MANIFEST COMMENTS, they are used as delimiters by the task.
+ # = MANIFEST =
+ s.files = %w[
+ Gemfile
+ LICENSE
+ README.md
+ Rakefile
+ benchmark/samplers.rb
+ lib/metriks.rb
+ lib/metriks/counter.rb
+ lib/metriks/ewma.rb
+ lib/metriks/exponentially_decaying_sample.rb
+ lib/metriks/gauge.rb
+ lib/metriks/histogram.rb
+ lib/metriks/meter.rb
+ lib/metriks/registry.rb
+ lib/metriks/reporter/graphite.rb
+ lib/metriks/reporter/librato_metrics.rb
+ lib/metriks/reporter/logger.rb
+ lib/metriks/reporter/proc_title.rb
+ lib/metriks/reporter/riemann.rb
+ lib/metriks/simple_moving_average.rb
+ lib/metriks/snapshot.rb
+ lib/metriks/time_tracker.rb
+ lib/metriks/timer.rb
+ lib/metriks/uniform_sample.rb
+ lib/metriks/utilization_timer.rb
+ metriks.gemspec
+ test/counter_test.rb
+ test/gauge_test.rb
+ test/graphite_reporter_test.rb
+ test/histogram_test.rb
+ test/librato_metrics_reporter_test.rb
+ test/logger_reporter_test.rb
+ test/meter_test.rb
+ test/metriks_test.rb
+ test/proc_title_reporter_test.rb
+ test/registry_test.rb
+ test/riemann_reporter_test.rb
+ test/test_helper.rb
+ test/thread_error_handling_tests.rb
+ test/timer_test.rb
+ test/utilization_timer_test.rb
+ ]
+ # = MANIFEST =
+
+ ## Test files will be grabbed from the file list. Make sure the path glob
+ ## matches what you actually use.
+ s.test_files = s.files.select { |path| path =~ /^test\/.*_test\.rb/ }
+end
diff --git a/test/counter_test.rb b/test/counter_test.rb
new file mode 100644
index 0000000..8540a43
--- /dev/null
+++ b/test/counter_test.rb
@@ -0,0 +1,39 @@
+require 'test_helper'
+
+require 'metriks/counter'
+
+class CounterTest < Test::Unit::TestCase
+ include ThreadHelper
+
+ def setup
+ @counter = Metriks::Counter.new
+ end
+
+ def test_increment
+ @counter.increment
+
+ assert_equal 1, @counter.count
+ end
+
+ def test_increment_threaded
+ thread 10, :n => 100 do
+ @counter.increment
+ end
+
+ assert_equal 1000, @counter.count
+ end
+
+ def test_increment_by_more
+ @counter.increment 10
+
+ assert_equal 10, @counter.count
+ end
+
+ def test_increment_by_more_threaded
+ thread 10, :n => 100 do
+ @counter.increment 10
+ end
+
+ assert_equal 10000, @counter.count
+ end
+end
diff --git a/test/gauge_test.rb b/test/gauge_test.rb
new file mode 100644
index 0000000..66a22c6
--- /dev/null
+++ b/test/gauge_test.rb
@@ -0,0 +1,46 @@
+require 'test_helper'
+
+require 'metriks/gauge'
+
+class GaugeTest < Test::Unit::TestCase
+ def test_gauge
+ gauge = Metriks::Gauge.new
+
+ 3.times do |i|
+ gauge.set(i + 1)
+ end
+
+ assert_equal 3, gauge.value
+
+ gauge.set(1)
+
+ assert_equal 1, gauge.value
+ end
+
+ def test_gauge_default
+ gauge = Metriks::Gauge.new
+ assert_equal nil, gauge.value
+ end
+
+ def test_gauge_callback_via_block
+ gauge = Metriks::Gauge.new { 56 }
+
+ assert_equal 56, gauge.value
+ end
+
+ def test_gauge_callback_via_callable_object
+ callable = Class.new(Struct.new(:value)) {
+ def call
+ value
+ end
+ }
+
+ gauge = Metriks::Gauge.new(callable.new(987))
+
+ assert_equal 987, gauge.value
+
+ gauge = Metriks::Gauge.new(proc { 123 })
+
+ assert_equal 123, gauge.value
+ end
+end
diff --git a/test/graphite_reporter_test.rb b/test/graphite_reporter_test.rb
new file mode 100644
index 0000000..51dc9c4
--- /dev/null
+++ b/test/graphite_reporter_test.rb
@@ -0,0 +1,41 @@
+require 'test_helper'
+require 'thread_error_handling_tests'
+
+require 'metriks/reporter/graphite'
+
+class GraphiteReporterTest < Test::Unit::TestCase
+ include ThreadErrorHandlingTests
+
+ def build_reporter(options={})
+ Metriks::Reporter::Graphite.new('localhost', 3333, { :registry => @registry }.merge(options))
+ end
+
+ def setup
+ @registry = Metriks::Registry.new
+ @reporter = build_reporter
+ @stringio = StringIO.new
+
+ @reporter.stubs(:socket).returns(@stringio)
+ end
+
+ def teardown
+ @reporter.stop
+ @registry.stop
+ end
+
+ def test_write
+ @registry.meter('meter.testing').mark
+ @registry.counter('counter.testing').increment
+ @registry.timer('timer.testing').update(1.5)
+ @registry.histogram('histogram.testing').update(1.5)
+ @registry.utilization_timer('utilization_timer.testing').update(1.5)
+ @registry.gauge('gauge.testing').set(123)
+ @registry.gauge('gauge.testing.block') { 456 }
+
+ @reporter.write
+
+ assert_match /timer.testing.median \d/, @stringio.string
+ assert_match /gauge.testing.value 123/, @stringio.string
+ assert_match /gauge.testing.block.value 456/, @stringio.string
+ end
+end
diff --git a/test/histogram_test.rb b/test/histogram_test.rb
new file mode 100644
index 0000000..41642ba
--- /dev/null
+++ b/test/histogram_test.rb
@@ -0,0 +1,199 @@
+require 'test_helper'
+
+require 'metriks/histogram'
+
+class HistogramTest < Test::Unit::TestCase
+ include ThreadHelper
+
+ def setup
+ end
+
+ def test_uniform_sample_min
+ @histogram = Metriks::Histogram.new(Metriks::UniformSample.new(Metriks::Histogram::DEFAULT_SAMPLE_SIZE))
+
+ @histogram.update(5)
+ @histogram.update(10)
+
+ assert_equal 5, @histogram.min
+ end
+
+ def test_uniform_sample_max
+ @histogram = Metriks::Histogram.new(Metriks::UniformSample.new(Metriks::Histogram::DEFAULT_SAMPLE_SIZE))
+
+ @histogram.update(5)
+ @histogram.update(10)
+
+ assert_equal 10, @histogram.max
+ end
+
+ def test_uniform_sample_mean
+ @histogram = Metriks::Histogram.new(Metriks::UniformSample.new(Metriks::Histogram::DEFAULT_SAMPLE_SIZE))
+
+ @histogram.update(5)
+ @histogram.update(10)
+
+ assert_equal 7, @histogram.mean
+ end
+
+ def test_uniform_sample_mean_threaded
+ @histogram = Metriks::Histogram.new(Metriks::UniformSample.new(Metriks::Histogram::DEFAULT_SAMPLE_SIZE))
+
+ thread 10, :n => 100 do
+ @histogram.update(5)
+ @histogram.update(10)
+ end
+
+ assert_equal 7, @histogram.mean
+ end
+
+ def test_uniform_sample_2000
+ @histogram = Metriks::Histogram.new(Metriks::UniformSample.new(Metriks::Histogram::DEFAULT_SAMPLE_SIZE))
+
+ 2000.times do |idx|
+ @histogram.update(idx)
+ end
+
+ assert_equal 1999, @histogram.max
+ end
+
+ def test_uniform_sample_2000_threaded
+ @histogram = Metriks::Histogram.new(Metriks::UniformSample.new(Metriks::Histogram::DEFAULT_SAMPLE_SIZE))
+
+ t = 10
+ thread t do |i|
+ 2000.times do |x|
+ if (x % t) == i
+ @histogram.update x
+ end
+ end
+ end
+
+ assert_equal 1999, @histogram.max
+ end
+
+ def test_uniform_sample_snashot
+ @histogram = Metriks::Histogram.new(Metriks::UniformSample.new(Metriks::Histogram::DEFAULT_SAMPLE_SIZE))
+
+ 100.times do |idx|
+ @histogram.update(idx)
+ end
+
+ snapshot = @histogram.snapshot
+
+ assert_equal 49.5, snapshot.median
+ end
+
+ def test_uniform_sample_snapshot_threaded
+ @histogram = Metriks::Histogram.new(Metriks::UniformSample.new(Metriks::Histogram::DEFAULT_SAMPLE_SIZE))
+
+ thread 10 do
+ 100.times do |idx|
+ @histogram.update(idx)
+ end
+ end
+
+ snapshot = @histogram.snapshot
+
+ assert_equal 49.5, snapshot.median
+ end
+
+ def test_exponential_sample_min
+ @histogram = Metriks::Histogram.new(Metriks::ExponentiallyDecayingSample.new(Metriks::Histogram::DEFAULT_SAMPLE_SIZE, Metriks::Histogram::DEFAULT_ALPHA))
+
+ @histogram.update(5)
+ @histogram.update(10)
+
+ assert_equal 5, @histogram.min
+ end
+
+ def test_exponential_sample_max
+ @histogram = Metriks::Histogram.new(Metriks::ExponentiallyDecayingSample.new(Metriks::Histogram::DEFAULT_SAMPLE_SIZE, Metriks::Histogram::DEFAULT_ALPHA))
+
+ @histogram.update(5)
+ @histogram.update(10)
+
+ assert_equal 10, @histogram.max
+ end
+
+ def test_exponential_sample_mean
+ @histogram = Metriks::Histogram.new(Metriks::ExponentiallyDecayingSample.new(Metriks::Histogram::DEFAULT_SAMPLE_SIZE, Metriks::Histogram::DEFAULT_ALPHA))
+
+ @histogram.update(5)
+ @histogram.update(10)
+
+ assert_equal 7, @histogram.mean
+ end
+
+ def test_exponential_sample_mean_threaded
+ @histogram = Metriks::Histogram.new(Metriks::ExponentiallyDecayingSample.new(Metriks::Histogram::DEFAULT_SAMPLE_SIZE, Metriks::Histogram::DEFAULT_ALPHA))
+
+ thread 10, :n => 100 do
+ @histogram.update(5)
+ @histogram.update(10)
+ end
+
+ assert_equal 7, @histogram.mean
+ end
+
+ def test_exponential_sample_2000
+ @histogram = Metriks::Histogram.new(Metriks::ExponentiallyDecayingSample.new(Metriks::Histogram::DEFAULT_SAMPLE_SIZE, Metriks::Histogram::DEFAULT_ALPHA))
+
+ 2000.times do |idx|
+ @histogram.update(idx)
+ end
+
+ assert_equal 1999, @histogram.max
+ end
+
+ def test_exponential_sample_2000_threaded
+ @histogram = Metriks::Histogram.new(Metriks::ExponentiallyDecayingSample.new(Metriks::Histogram::DEFAULT_SAMPLE_SIZE, Metriks::Histogram::DEFAULT_ALPHA))
+
+ t = 10
+ thread t do |i|
+ 2000.times do |idx|
+ if (idx % t) == i
+ @histogram.update(idx)
+ end
+ end
+ end
+
+ assert_equal 1999, @histogram.max
+ end
+
+ def test_exponential_sample_snashot
+ @histogram = Metriks::Histogram.new(Metriks::ExponentiallyDecayingSample.new(Metriks::Histogram::DEFAULT_SAMPLE_SIZE, Metriks::Histogram::DEFAULT_ALPHA))
+
+ 100.times do |idx|
+ @histogram.update(idx)
+ end
+
+ snapshot = @histogram.snapshot
+
+ assert_equal 49.5, snapshot.median
+ end
+
+ def test_exponential_sample_snapshot_threaded
+ @histogram = Metriks::Histogram.new(Metriks::ExponentiallyDecayingSample.new(Metriks::Histogram::DEFAULT_SAMPLE_SIZE, Metriks::Histogram::DEFAULT_ALPHA))
+
+ thread 10 do
+ 100.times do |idx|
+ @histogram.update(idx)
+ end
+ end
+
+ snapshot = @histogram.snapshot
+
+ assert_equal 49.5, snapshot.median
+ end
+
+ def test_long_idle_sample
+ Time.stubs(:now).returns(Time.at(2000))
+ sample = Metriks::ExponentiallyDecayingSample.new(Metriks::Histogram::DEFAULT_SAMPLE_SIZE, Metriks::Histogram::DEFAULT_ALPHA)
+ Time.unstub(:now)
+ @histogram = Metriks::Histogram.new(sample)
+
+ @histogram.update(5)
+
+ assert_equal 5, @histogram.min
+ end
+end
diff --git a/test/librato_metrics_reporter_test.rb b/test/librato_metrics_reporter_test.rb
new file mode 100644
index 0000000..4c98455
--- /dev/null
+++ b/test/librato_metrics_reporter_test.rb
@@ -0,0 +1,35 @@
+require 'test_helper'
+require 'thread_error_handling_tests'
+
+require 'metriks/reporter/librato_metrics'
+
+class LibratoMetricsReporterTest < Test::Unit::TestCase
+ include ThreadErrorHandlingTests
+
+ def build_reporter(options={})
+ Metriks::Reporter::LibratoMetrics.new('user', 'password', { :registry => @registry }.merge(options))
+ end
+
+ def setup
+ @registry = Metriks::Registry.new
+ @reporter = build_reporter
+ end
+
+ def teardown
+ @reporter.stop
+ @registry.stop
+ end
+
+ def test_write
+ @registry.meter('meter.testing').mark
+ @registry.counter('counter.testing').increment
+ @registry.timer('timer.testing').update(1.5)
+ @registry.histogram('histogram.testing').update(1.5)
+ @registry.utilization_timer('utilization_timer.testing').update(1.5)
+ @registry.gauge('gauge.testing') { 123 }
+
+ @reporter.expects(:submit)
+
+ @reporter.write
+ end
+end
diff --git a/test/logger_reporter_test.rb b/test/logger_reporter_test.rb
new file mode 100644
index 0000000..7dafa1c
--- /dev/null
+++ b/test/logger_reporter_test.rb
@@ -0,0 +1,49 @@
+require 'test_helper'
+require 'thread_error_handling_tests'
+
+require 'logger'
+require 'metriks/reporter/logger'
+
+class LoggerReporterTest < Test::Unit::TestCase
+ include ThreadErrorHandlingTests
+
+ def build_reporter(options={})
+ Metriks::Reporter::Logger.new({ :registry => @registry, :logger => @logger }.merge(options))
+ end
+
+ def setup
+ @stringio = StringIO.new
+ @logger = ::Logger.new(@stringio)
+ @registry = Metriks::Registry.new
+
+ @reporter = build_reporter
+
+ @registry.meter('meter.testing').mark
+ @registry.counter('counter.testing').increment
+ @registry.timer('timer.testing').update(1.5)
+ @registry.histogram('histogram.testing').update(1.5)
+ @registry.utilization_timer('utilization_timer.testing').update(1.5)
+ @registry.gauge('gauge.testing').set(123)
+ end
+
+ def teardown
+ @reporter.stop
+ @registry.stop
+ end
+
+ def test_write
+ @reporter.write
+
+ assert_match /time=\d/, @stringio.string
+ assert_match /median=\d/, @stringio.string
+ assert_match /value=123/, @stringio.string
+ end
+
+ def test_flush
+ @reporter.flush
+
+ assert_match /time=\d/, @stringio.string
+ assert_match /median=\d/, @stringio.string
+ assert_match /value=123/, @stringio.string
+ end
+end
diff --git a/test/meter_test.rb b/test/meter_test.rb
new file mode 100644
index 0000000..8d4e0c0
--- /dev/null
+++ b/test/meter_test.rb
@@ -0,0 +1,38 @@
+require 'test_helper'
+
+require 'metriks/meter'
+
+class MeterTest < Test::Unit::TestCase
+ include ThreadHelper
+
+ def setup
+ @meter = Metriks::Meter.new
+ end
+
+ def teardown
+ @meter.stop
+ end
+
+ def test_meter
+ @meter.mark
+
+ assert_equal 1, @meter.count
+ end
+
+ def test_meter_threaded
+ thread 10, :n => 100 do
+ @meter.mark
+ end
+
+ assert_equal 1000, @meter.count
+ end
+
+ def test_one_minute_rate
+ @meter.mark 1000
+
+ # Pretend it's been 5 seconds
+ @meter.tick
+
+ assert_equal 200, @meter.one_minute_rate
+ end
+end
diff --git a/test/metriks_test.rb b/test/metriks_test.rb
new file mode 100644
index 0000000..41f8dd1
--- /dev/null
+++ b/test/metriks_test.rb
@@ -0,0 +1,31 @@
+require 'test_helper'
+
+class MetriksTest < Test::Unit::TestCase
+ def setup
+ Metriks::Registry.default.clear
+ end
+
+ def teardown
+ Metriks::Registry.default.clear
+ end
+
+ def test_counter
+ assert_not_nil Metriks.counter('testing')
+ end
+
+ def test_meter
+ assert_not_nil Metriks.meter('testing')
+ end
+
+ def test_timer
+ assert_not_nil Metriks.timer('testing')
+ end
+
+ def test_utilization_timer
+ assert_not_nil Metriks.utilization_timer('testing')
+ end
+
+ def test_histogram
+ assert_not_nil Metriks.histogram('testing')
+ end
+end
\ No newline at end of file
diff --git a/test/proc_title_reporter_test.rb b/test/proc_title_reporter_test.rb
new file mode 100644
index 0000000..f35c943
--- /dev/null
+++ b/test/proc_title_reporter_test.rb
@@ -0,0 +1,25 @@
+require 'test_helper'
+
+require 'metriks/reporter/proc_title'
+
+class ProcTitleReporterTest < Test::Unit::TestCase
+ def setup
+ @reporter = Metriks::Reporter::ProcTitle.new
+ @original_proctitle = $0.dup
+ end
+
+ def teardown
+ @reporter.stop
+ $0 = @original_proctitle
+ end
+
+ def test_generate_title
+ @reporter.add 'test', 'sec' do
+ 50.333
+ end
+
+ title = @reporter.send(:generate_title)
+
+ assert_equal 'test: 50.3/sec', title
+ end
+end
\ No newline at end of file
diff --git a/test/registry_test.rb b/test/registry_test.rb
new file mode 100644
index 0000000..22122cf
--- /dev/null
+++ b/test/registry_test.rb
@@ -0,0 +1,49 @@
+require 'test_helper'
+
+require 'metriks/registry'
+
+class RegistryTest < Test::Unit::TestCase
+ def setup
+ @registry = Metriks::Registry.new
+ end
+
+ def teardown
+ @registry.stop
+ end
+
+ def test_counter
+ assert_not_nil @registry.counter('testing')
+ end
+
+ def test_meter
+ assert_not_nil @registry.meter('testing')
+ end
+
+ def test_timer
+ assert_not_nil @registry.timer('testing')
+ end
+
+ def test_utilization_timer
+ assert_not_nil @registry.utilization_timer('testing')
+ end
+
+ def test_histogram
+ assert_not_nil @registry.histogram('testing')
+ end
+
+ def test_mismatched_metrics
+ @registry.histogram('histogram')
+ assert_raises(RuntimeError) { @registry.timer('histogram') }
+
+ @registry.timer('timer')
+ assert_raises(RuntimeError) { @registry.histogram('timer') }
+ end
+
+ def test_calling_counter_twice
+ assert_not_nil @registry.counter('testing')
+ end
+
+ def test_default
+ assert_not_nil Metriks::Registry.default
+ end
+end
\ No newline at end of file
diff --git a/test/riemann_reporter_test.rb b/test/riemann_reporter_test.rb
new file mode 100644
index 0000000..98662ab
--- /dev/null
+++ b/test/riemann_reporter_test.rb
@@ -0,0 +1,88 @@
+require 'test_helper'
+require 'thread_error_handling_tests'
+
+require 'metriks/reporter/riemann'
+
+class RiemannReporterTest < Test::Unit::TestCase
+ include ThreadErrorHandlingTests
+
+ def build_reporter(options={})
+ Metriks::Reporter::Riemann.new({
+ :host => "foo",
+ :port => 1234,
+ :registry => @registry,
+ :default_event => {:host => "h"}
+ }.merge(options))
+ end
+
+ def setup
+ @registry = Metriks::Registry.new
+ @reporter = build_reporter
+ end
+
+ def teardown
+ @reporter.stop
+ @registry.stop
+ end
+
+ def test_init
+ assert_equal @reporter.client.host, "foo"
+ assert_equal @reporter.client.port, 1234
+ end
+
+ def test_write
+ @registry.meter('meter.testing').mark
+ @registry.counter('counter.testing').increment
+ @registry.timer('timer.testing').update(1.5)
+ @registry.histogram('histogram.testing').update(1.5)
+ @registry.utilization_timer('utilization_timer.testing').update(1.5)
+ @registry.gauge('gauge.testing') { 123 }
+
+ @reporter.client.expects(:<<).at_least_once
+ @reporter.client.expects(:<<).with(
+ :host => "h",
+ :service => "meter.testing count",
+ :metric => 1,
+ :tags => ["meter"],
+ :ttl => 90
+ )
+ @reporter.client.expects(:<<).with(
+ :host => "h",
+ :service => "counter.testing count",
+ :metric => 1,
+ :tags => ["counter"],
+ :ttl => 90
+ )
+ @reporter.client.expects(:<<).with(
+ :host => "h",
+ :service => "timer.testing max",
+ :metric => 1.5,
+ :tags => ["timer"],
+ :ttl => 90
+ )
+ @reporter.client.expects(:<<).with(
+ :host => "h",
+ :service => "histogram.testing max",
+ :metric => 1.5,
+ :tags => ["histogram"],
+ :ttl => 90
+ )
+ @reporter.client.expects(:<<).with(
+ :host => "h",
+ :service => "utilization_timer.testing mean",
+ :metric => 1.5,
+ :tags => ["utilization_timer"],
+ :ttl => 90
+ )
+
+ @reporter.client.expects(:<<).with(
+ :host => "h",
+ :service => "gauge.testing value",
+ :metric => 123,
+ :tags => ["gauge"],
+ :ttl => 90
+ )
+
+ @reporter.write
+ end
+end
diff --git a/test/test_helper.rb b/test/test_helper.rb
new file mode 100644
index 0000000..e5cd9ee
--- /dev/null
+++ b/test/test_helper.rb
@@ -0,0 +1,33 @@
+require 'test/unit'
+require 'pp'
+
+require 'mocha'
+
+require 'metriks'
+
+Thread.abort_on_exception = true
+
+module ThreadHelper
+ require 'thread'
+
+ # Run the given block on n threads in parallel. Returns an array of the
+ # return values of each thread's last invocation of block. Options:
+
+ # :n: call block n times per thread. Default 1.
+ def thread(threads = 2, opts = {})
+ n = opts[:n] || 1
+ results = []
+
+ threads.times.map do |i|
+ Thread.new do
+ n.times do
+ results[i] = yield i
+ end
+ end
+ end.each do |thread|
+ thread.join
+ end
+
+ results
+ end
+end
diff --git a/test/thread_error_handling_tests.rb b/test/thread_error_handling_tests.rb
new file mode 100644
index 0000000..3f4094d
--- /dev/null
+++ b/test/thread_error_handling_tests.rb
@@ -0,0 +1,20 @@
+module ThreadErrorHandlingTests
+ def test_passes_errors_in_thread_loop_to_on_error_handler
+ rescued_error = nil
+ error_handler_called = false
+ reporter = build_reporter(:interval => 0.0001, :on_error => lambda { |e|
+ error_handler_called = true
+ rescued_error = e
+ })
+
+ reporter.stubs(:write).raises(StandardError, "boom")
+
+ reporter.start
+ sleep 0.02
+ assert_equal true, error_handler_called
+ assert_equal "boom", rescued_error.message
+ ensure
+ reporter.stop
+ end
+end
+
diff --git a/test/timer_test.rb b/test/timer_test.rb
new file mode 100644
index 0000000..87eb0d4
--- /dev/null
+++ b/test/timer_test.rb
@@ -0,0 +1,32 @@
+require 'test_helper'
+
+require 'metriks/timer'
+
+class TimerTest < Test::Unit::TestCase
+ def setup
+ @timer = Metriks::Timer.new
+ end
+
+ def teardown
+ @timer.stop
+ end
+
+ def test_timer
+ 3.times do
+ @timer.time do
+ sleep 0.1
+ end
+ end
+
+ assert_in_delta 0.1, @timer.mean, 0.01
+ assert_in_delta 0.1, @timer.snapshot.median, 0.01
+ end
+
+ def test_timer_without_block
+ t = @timer.time
+ sleep 0.1
+ t.stop
+
+ assert_in_delta 0.1, @timer.mean, 0.01
+ end
+end
\ No newline at end of file
diff --git a/test/utilization_timer_test.rb b/test/utilization_timer_test.rb
new file mode 100644
index 0000000..11961ee
--- /dev/null
+++ b/test/utilization_timer_test.rb
@@ -0,0 +1,25 @@
+require 'test_helper'
+
+require 'metriks/utilization_timer'
+
+class UtilizationTimerTest < Test::Unit::TestCase
+ def setup
+ @timer = Metriks::UtilizationTimer.new
+ end
+
+ def teardown
+ @timer.stop
+ end
+
+ def test_timer
+ 5.times do
+ @timer.update(0.10)
+ @timer.update(0.15)
+ end
+
+ @timer.instance_variable_get(:@meter).tick
+ @timer.instance_variable_get(:@duration_meter).tick
+
+ assert_in_delta 0.25, @timer.one_minute_utilization, 0.1
+ end
+end
\ No newline at end of file
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-ruby-extras/ruby-metriks.git
More information about the Pkg-ruby-extras-commits
mailing list