metriks-0.9.9.8/0000755000004100000410000000000013323051360013401 5ustar www-datawww-datametriks-0.9.9.8/test/0000755000004100000410000000000013323051360014360 5ustar www-datawww-datametriks-0.9.9.8/test/histogram_test.rb0000644000004100000410000001226313323051360017745 0ustar www-datawww-datarequire '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 metriks-0.9.9.8/test/riemann_reporter_test.rb0000644000004100000410000000412113323051360021315 0ustar www-datawww-datarequire '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 metriks-0.9.9.8/test/metriks_test.rb0000644000004100000410000000105313323051360017421 0ustar www-datawww-datarequire '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 endmetriks-0.9.9.8/test/librato_metrics_reporter_test.rb0000644000004100000410000000156713323051360023061 0ustar www-datawww-datarequire '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 metriks-0.9.9.8/test/meter_test.rb0000644000004100000410000000105113323051360017055 0ustar www-datawww-datarequire '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 metriks-0.9.9.8/test/graphite_reporter_test.rb0000644000004100000410000000220313323051360021466 0ustar www-datawww-datarequire '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 metriks-0.9.9.8/test/proc_title_reporter_test.rb0000644000004100000410000000071713323051360022037 0ustar www-datawww-datarequire '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 endmetriks-0.9.9.8/test/registry_test.rb0000644000004100000410000000171413323051360017617 0ustar www-datawww-datarequire '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 endmetriks-0.9.9.8/test/thread_error_handling_tests.rb0000644000004100000410000000100113323051360022443 0ustar www-datawww-datamodule 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 metriks-0.9.9.8/test/logger_reporter_test.rb0000644000004100000410000000233213323051360021145 0ustar www-datawww-datarequire '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 metriks-0.9.9.8/test/counter_test.rb0000644000004100000410000000120713323051360017423 0ustar www-datawww-datarequire '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 metriks-0.9.9.8/test/gauge_test.rb0000644000004100000410000000144413323051360017037 0ustar www-datawww-datarequire '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 metriks-0.9.9.8/test/timer_test.rb0000644000004100000410000000076313323051360017072 0ustar www-datawww-datarequire '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 endmetriks-0.9.9.8/test/test_helper.rb0000644000004100000410000000115213323051360017222 0ustar www-datawww-datarequire '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 metriks-0.9.9.8/test/utilization_timer_test.rb0000644000004100000410000000073513323051360021524 0ustar www-datawww-datarequire '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 endmetriks-0.9.9.8/README.md0000644000004100000410000002112413323051360014660 0ustar www-datawww-data# 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') work_result = 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 The Librato Metrics reporter has been moved to [eric/metriks-librato_metrics](https://github.com/eric/metriks-librato_metrics). ## 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 metriks-0.9.9.8/benchmark/0000755000004100000410000000000013323051360015333 5ustar www-datawww-datametriks-0.9.9.8/benchmark/samplers.rb0000644000004100000410000000501713323051360017511 0ustar www-datawww-data#!/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)) reporter.measure :exponential_concurrent_red_black, Metriks::Timer.new(Metriks::ExponentiallyDecayingSample.new( Metriks::Histogram::DEFAULT_SAMPLE_SIZE, Metriks::Histogram::DEFAULT_ALPHA, ConcurrentRedBlackTree.new)) puts "fib(#{fib_times}): #{iter} iterations" puts "-" * 50 reporter.run metriks-0.9.9.8/LICENSE0000644000004100000410000000206113323051360014405 0ustar www-datawww-dataThe 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.metriks-0.9.9.8/Rakefile0000644000004100000410000000730313323051360015051 0ustar www-datawww-datarequire '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 'rdoc/task' # 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 metriks-0.9.9.8/lib/0000755000004100000410000000000013323051360014147 5ustar www-datawww-datametriks-0.9.9.8/lib/metriks.rb0000644000004100000410000000125613323051360016156 0ustar www-datawww-data module Metriks VERSION = '0.9.9.8' 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' metriks-0.9.9.8/lib/metriks/0000755000004100000410000000000013323051360015625 5ustar www-datawww-datametriks-0.9.9.8/lib/metriks/time_tracker.rb0000644000004100000410000000100713323051360020621 0ustar www-datawww-datamodule 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 metriks-0.9.9.8/lib/metriks/snapshot.rb0000644000004100000410000000201113323051360020003 0ustar www-datawww-datamodule 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 metriks-0.9.9.8/lib/metriks/timer.rb0000644000004100000410000000311513323051360017272 0ustar www-datawww-datarequire '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 sum @histogram.sum 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 endmetriks-0.9.9.8/lib/metriks/reporter/0000755000004100000410000000000013323051360017467 5ustar www-datawww-datametriks-0.9.9.8/lib/metriks/reporter/graphite.rb0000644000004100000410000000570513323051360021626 0ustar www-datawww-datarequire '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 metriks-0.9.9.8/lib/metriks/reporter/proc_title.rb0000644000004100000410000000226113323051360022161 0ustar www-datawww-datamodule 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 endmetriks-0.9.9.8/lib/metriks/reporter/librato_metrics.rb0000644000004100000410000001026413323051360023201 0ustar www-datawww-datarequire '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 metriks-0.9.9.8/lib/metriks/reporter/logger.rb0000644000004100000410000000652713323051360021305 0ustar www-datawww-datarequire '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 metriks-0.9.9.8/lib/metriks/reporter/riemann.rb0000644000004100000410000000607513323051360021455 0ustar www-datawww-datamodule 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 metriks-0.9.9.8/lib/metriks/uniform_sample.rb0000644000004100000410000000141213323051360021170 0ustar www-datawww-datarequire '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 endmetriks-0.9.9.8/lib/metriks/simple_moving_average.rb0000644000004100000410000000220313323051360022511 0ustar www-datawww-datarequire '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 endmetriks-0.9.9.8/lib/metriks/utilization_timer.rb0000644000004100000410000000132513323051360021726 0ustar www-datawww-datarequire '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 endmetriks-0.9.9.8/lib/metriks/exponentially_decaying_sample.rb0000644000004100000410000000532113323051360024252 0ustar www-datawww-datarequire '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 || ConcurrentRedBlackTree.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(-@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 endmetriks-0.9.9.8/lib/metriks/ewma.rb0000644000004100000410000000236013323051360017104 0ustar www-datawww-datarequire '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 endmetriks-0.9.9.8/lib/metriks/gauge.rb0000644000004100000410000000076213323051360017247 0ustar www-datawww-datarequire '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 metriks-0.9.9.8/lib/metriks/histogram.rb0000644000004100000410000000464213323051360020155 0ustar www-datawww-datarequire '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 sum @sum.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 metriks-0.9.9.8/lib/metriks/meter.rb0000644000004100000410000000312213323051360017264 0ustar www-datawww-datarequire '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 endmetriks-0.9.9.8/lib/metriks/registry.rb0000644000004100000410000001250213323051360020022 0ustar www-datawww-datarequire '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 metriks-0.9.9.8/lib/metriks/counter.rb0000644000004100000410000000155413323051360017636 0ustar www-datawww-datarequire '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 endmetriks-0.9.9.8/metriks.gemspec0000644000004100000410000000764613323051360016441 0ustar www-datawww-data## 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.8' s.date = '2017-04-26' ## 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@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.2.0" ]) ## 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 metriks-0.9.9.8/Gemfile0000644000004100000410000000025313323051360014674 0ustar www-datawww-datasource 'https://rubygems.org' gemspec gem 'beefcake', '< 1.0.0' group :test do gem 'rake' gem 'riemann-client', '~> 0.0.7' gem 'rbtree', :platform => :mri_18 end