Code Monkey home page Code Monkey logo

metriks's Introduction

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.

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:

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.

  counter = Metriks.counter('calls')
  counter.increment

decrement(decr = 1)

Decrement the counter. Without an argument it will decrement by 1.

  counter = Metriks.counter('calls')
  counter.decrement

count()

Return the current value of the counter.

  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.

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

  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.

  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.

  meter = Metriks.meter('requests')
  meter.mark

count()

Returns the total number of events that have been recorded.

  meter = Metriks.meter('requests')
  puts "total: #{meter.count}"

one_minute_rate()

Returns the one-minute average rate.

  meter = Metriks.meter('requests')
  puts "rate: #{meter.one_minute_rate}/sec"

five_minute_rate()

Returns the five-minute average rate.

  meter = Metriks.meter('requests')
  puts "rate: #{meter.five_minute_rate}/sec"

fifteen_minute_rate()

Returns the fifteen-minute average rate.

  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.

  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.

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

  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.

  timer = Metriks.timer('requests')
  t = timer.time
  work
  t.stop

count()

Returns the number of measurements that have been made.

  timer = Metriks.timer('requests')
  puts "calls: #{timer.count}"

one_minute_rate()

Returns the one-minute average rate.

  meter = Metriks.timer('requests')
  puts "rate: #{meter.one_minute_rate}/sec"

five_minute_rate()

Returns the five-minute average rate.

  meter = Metriks.timer('requests')
  puts "rate: #{meter.five_minute_rate}/sec"

fifteen_minute_rate()

Returns the fifteen-minute average rate.

  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.

  meter = Metriks.timer('requests')
  puts "rate: #{meter.mean_rate}/sec"

min()

Returns the minimum amount of time spent in the operation.

  meter = Metriks.timer('requests')
  puts "time: #{meter.min} seconds"

max()

Returns the maximum time spent in the operation.

  meter = Metriks.timer('requests')
  puts "time: #{meter.max} seconds"

mean()

Returns the mean (average) time spent in the operation.

  meter = Metriks.timer('requests')
  puts "time: #{meter.mean} seconds"

stddev()

Returns the standard deviation of the mean spent in the operation.

  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.

  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.

  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.

  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.

  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.

  reporter = Metriks::Reporter::Graphite.new 'localhost', 3004
  reporter.start

Logger Reporter

Send metrics to a logger every 60 seconds.

  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.

Proc Title Reporter

Provides a simple way to get up-to-date statistics from a process by updating the proctitle every 5 seconds (default).

  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 gem provides reporter for sending metrics to SPM.

Application Server Configuration

Depending on how your application server operates, you may need to configure how reporters are created. Please look at 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 reporter
  • Metaprogramming instrumentation hooks like Shopify's statsd-instrument

Credits

Most of the inspiration for this project comes from Coda Hale's amazing Metrics, Metrics Everywhere talk at CodeConf and his sweet Metrics Java Library.

License

Copyright (c) 2012 Eric Lindvall

Published under the MIT License, see LICENSE

metriks's People

Contributors

aphyr avatar bernd avatar brynary avatar eric avatar ethansr avatar jordansissel avatar lmarburger avatar lukaszx0 avatar myronmarston avatar phillbaker avatar roidrage avatar xrl avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

metriks's Issues

Release a new version

The last release 0.9.9.7 was made on October 7, 2014.
Many changes were made since then

Librato reporter gets 400 errors from Librato if it has no data to send

In our system, the activity of our job queues is very bursty: every day around midnight, millions of jobs are enqueued and processed, and then there are times later in the day where there is no work to do. We've been getting a bunch of 400 errors from Librato with an error like this:

 {"errors":{"request":["Must contain a measurement"]}}

Looking at the Metriks librato reporter this makes sense: it submits all measurements that were taken in the last minute regardless of whether or not there were any. When there were none, it gets the 400 error from Librato.

It would be nice if the Metriks librato reporter was a bit smarter about this and would not try to submit a request with no metrics.

@eric, if you're on board with that idea I'll try to make time to submit a PR for this in the next week or so.

what's the meter algorithm used here?

Hi,

I am using the Metrics plugin in logstash, and want to know how it works, then i found this repo, so i want to know what's the meter's calculation algorithm here, i have already read the code ewma,rb, but not quite understand it (especially what's the ALPHA means)... Can you explain it a bit?

Spontaneous Crashes in RedBlackTree

Hi mates, I'm seeing some spontaneous crashes in the RedBlackTree class imported from avl_tree. As far as I can tell, it appears the tree is becoming corrupted. Is this a bug in avl_tree or a bug in Metriks?

This is in a Rails application, running in unicorn.

Backtrace:

[GEM_ROOT]/gems/avl_tree-1.1.3/lib/red_black_tree.rb:362:in `delete_self'
[GEM_ROOT]/gems/avl_tree-1.1.3/lib/red_black_tree.rb:111:in `delete'
[GEM_ROOT]/gems/avl_tree-1.1.3/lib/red_black_tree.rb:105:in `delete'
[GEM_ROOT]/gems/avl_tree-1.1.3/lib/red_black_tree.rb:105:in `delete'
[GEM_ROOT]/gems/avl_tree-1.1.3/lib/red_black_tree.rb:105:in `delete'
[GEM_ROOT]/gems/avl_tree-1.1.3/lib/red_black_tree.rb:105:in `delete'
[GEM_ROOT]/gems/avl_tree-1.1.3/lib/red_black_tree.rb:105:in `delete'
[GEM_ROOT]/gems/avl_tree-1.1.3/lib/red_black_tree.rb:105:in `delete'
[GEM_ROOT]/gems/avl_tree-1.1.3/lib/red_black_tree.rb:519:in `delete'
[GEM_ROOT]/gems/metriks-0.9.9.2/lib/metriks/exponentially_decaying_sample.rb:58:in `block in update'
:
[GEM_ROOT]/gems/metriks-0.9.9.2/lib/metriks/exponentially_decaying_sample.rb:40:in `update'
[GEM_ROOT]/gems/metriks-0.9.9.2/lib/metriks/histogram.rb:38:in `update'
[GEM_ROOT]/gems/metriks-0.9.9.2/lib/metriks/timer.rb:38:in `update'

Error:

RuntimeError: should not happen

ArgumentError: comparison of Float with Float failed

I have no idea what this error means. :-( We've monkeypatched the redis gem to time the redis operations we do, in this case a del operation timing update failed.

[GEM_ROOT]/gems/metriks-0.9.3/lib/metriks/exponentially_decaying_sample.rb:77:in `[]='
[GEM_ROOT]/gems/metriks-0.9.3/lib/metriks/exponentially_decaying_sample.rb:77:in `rescale'
[GEM_ROOT]/gems/metriks-0.9.3/lib/metriks/exponentially_decaying_sample.rb:75:in `each'
[GEM_ROOT]/gems/metriks-0.9.3/lib/metriks/exponentially_decaying_sample.rb:75:in `rescale'
[GEM_ROOT]/gems/metriks-0.9.3/lib/metriks/exponentially_decaying_sample.rb:72:in `synchronize'
[GEM_ROOT]/gems/metriks-0.9.3/lib/metriks/exponentially_decaying_sample.rb:72:in `rescale'
[GEM_ROOT]/gems/metriks-0.9.3/lib/metriks/exponentially_decaying_sample.rb:62:in `update'
[GEM_ROOT]/gems/metriks-0.9.3/lib/metriks/histogram.rb:38:in `update'
[GEM_ROOT]/gems/metriks-0.9.3/lib/metriks/timer.rb:34:in `update'
[GEM_ROOT]/gems/metriks-0.9.3/lib/metriks/timer.rb:17:in `stop'
[GEM_ROOT]/gems/metriks-0.9.3/lib/metriks/timer.rb:49:in `time'
config/initializers/redis.rb:15:in `call'
[GEM_ROOT]/gems/redis-2.2.2/lib/redis.rb:681:in `del'
/usr/local/rvm/rubies/ree-1.8.7-2012.02/lib/ruby/1.8/monitor.rb:242:in `synchronize'
[GEM_ROOT]/gems/redis-2.2.2/lib/redis.rb:680:in `del'
app/models/account.rb:14:in `expire_redis_cache'

Is it possible to have a registry with two types simultaneously?

Have been trying to use a registry with a counter and a timer at the same time.
Multiple counters are created dynamically using @registry.counter("records:#{user}") and the timer is added once @registry.add('method_perf', Metriks::Timer.new).

I've tried multiple ways of adding the timer. Using the registry add method and just creating it when I want to add the value using @registry.timer("method_perf").update(time_value).

All seem to overwrite the counter when I run things, the counter value isn't there.

Sliding values for min/max/stddev/mean on a timer?

I see timers have {one,five,fifteen}_minute_rate for how often the timers are counted, but how about having a sliding window for the values as well?

Example: min, max, stddev, and mean ought to have "one_minute_min" etc...

Logger does not work

Logger does not appear to be working, I have tried three different flavors of Ruby all with the same results:

Ruby 2.0.0

irb(main):002:0> require 'metriks'
=> true
irb(main):003:0>   reporter = Metriks::Reporter::Logger.new(:logger => Logger.new('log/metrics.log'))
NameError: uninitialized constant Metriks::Reporter::Logger
    from (irb):3
    from /Users/jdyer/.rbenv/versions/2.0.0-p353/bin/irb:12:in `<main>'
irb(main):004:0>

Ruby 2.1.1

irb(main):001:0> require 'metriks'
=> true
irb(main):002:0>   reporter = Metriks::Reporter::Logger.new(:logger => Logger.new('log/metrics.log'))
NameError: uninitialized constant Metriks::Reporter::Logger
    from (irb):2
    from /Users/jdyer/.rbenv/versions/2.1.1/bin/irb:11:in `<main>'
irb(main):003:0>

Ruby 1.9

irb(main):001:0> require 'metriks'
=> true
irb(main):002:0> reporter = Metriks::Reporter::Logger.new(:logger => Logger.new('log/metrics.log'))
NameError: uninitialized constant Metriks::Reporter::Logger
    from (irb):2
    from /Users/jdyer/.rbenv/versions/1.9.3-p545/bin/irb:12:in `<main>'
irb(main):003:0>

Split out metriks into multiple gems to reduce dependencies

In an attempt to try to increase the usage of metriks and make it easier to integrate, @josephruscio has suggested that the reporters be split into separate gems (or maybe their own gems).

This would mean the core metriks gem would just contain:

  • the metric building blocks (Timer, Meter, etc)
  • the registry
  • a reporter base class? (if we ever have such a thing)

Reporters

We would need to decide if it made sense to have a single metriks-reporter gem that contains all of the reporters, or if it would make sense to have separate gems for each reporter. The benefit of separate gems is the reduction in dependencies that would be required, which seems like a nice gain.

Naming and upgrading

My first thought is to call the core gem metriks and have the reporters in their own gems. The downside of this is that anyone who is upgrading metriks will have their code break, as it will no longer contain any of the reporters. I'm not sure what the best way to solve this is a smooth upgrade path, so I'm open to suggestions here.

CC: @lmarburger, @josephruscio, @nextmat

Graphite doesn't work

graphite.rb:

def socket
  @socket = nil if @socket.closed?
  @socket ||= TCPSocket.new(host, port)
end

There are two bugs here:

  • calls a method on @socket before it's even been created
  • accesses host and port as local variables (use attr_accessor or ivars)

Barring this, so far your gem looks great.

LibratoMetrics failing to report on false data passed to `gauge`

I do not have a lot of time to debug this, unfortunately, but I thought I should at least report our observations.

We've accidentally passed a false value to Metriks.gauge(key).set(value). Specifically, we've passed a "humanized size" string (e.g. "10KB") instead of the numerical value.

We've then seen all other metrics to stop reporting to Librato, too. In other words the false value was just one out of many metrics (most of the time meter(key).mark and timer(key).time). With the false value passed to gauge all metrics stopped being reported until the bug was fixed.

Also, we do not set an on_error callback, so we could not inspect this in the logs.

I suspect that a buggy value passed to the reporter might somehow clog the queue, and internally raise the same exception over and over?

crash in exponentially_decaying_sample

Hi,

I just got a sudden crash in exponentially_decaying_sample:

shared/bundle/ruby/1.9.1/gems/avl_tree-1.1.2/lib/red_black_tree.rb:518:in `delete': undefined method `value' for nil:NilClass (NoMethodError)
        from shared/bundle/ruby/1.9.1/gems/metriks-0.9.7.3/lib/metriks/exponentially_decaying_sample.rb:82:in `block (2 levels) in rescale'
        from shared/bundle/ruby/1.9.1/gems/metriks-0.9.7.3/lib/metriks/exponentially_decaying_sample.rb:81:in `each'
        from shared/bundle/ruby/1.9.1/gems/metriks-0.9.7.3/lib/metriks/exponentially_decaying_sample.rb:81:in `block in rescale'
        from <internal:prelude>:10:in `synchronize'
        from shared/bundle/ruby/1.9.1/gems/metriks-0.9.7.3/lib/metriks/exponentially_decaying_sample.rb:78:in `rescale'
        from shared/bundle/ruby/1.9.1/gems/metriks-0.9.7.3/lib/metriks/exponentially_decaying_sample.rb:68:in `update'
        from shared/bundle/ruby/1.9.1/gems/metriks-0.9.7.3/lib/metriks/histogram.rb:38:in `update'
        from shared/bundle/ruby/1.9.1/gems/metriks-0.9.7.3/lib/metriks/timer.rb:34:in `update'
        from shared/bundle/ruby/1.9.1/gems/metriks-0.9.7.3/lib/metriks/timer.rb:17:in `stop'
        from shared/bundle/ruby/1.9.1/gems/metriks-0.9.7.3/lib/metriks/timer.rb:49:in `time'
        from releases/20120416124631/lib/custom_handler.rb:45:in `onmessage'
        from shared/bundle/ruby/1.9.1/bundler/gems/slanger-c07a3de1ea6e/lib/slanger/web_socket_server.rb:28:in `block (3 levels) in run'
        from shared/bundle/ruby/1.9.1/bundler/gems/em-websocket-b9d439cd3494/lib/em-websocket/connection.rb:19:in `call'
        from shared/bundle/ruby/1.9.1/bundler/gems/em-websocket-b9d439cd3494/lib/em-websocket/connection.rb:19:in `trigger_on_message'
        from shared/bundle/ruby/1.9.1/bundler/gems/em-websocket-b9d439cd3494/lib/em-websocket/message_processor_06.rb:45:in `message'
        from shared/bundle/ruby/1.9.1/bundler/gems/em-websocket-b9d439cd3494/lib/em-websocket/framing07.rb:106:in `process_data'
        from shared/bundle/ruby/1.9.1/bundler/gems/em-websocket-b9d439cd3494/lib/em-websocket/handler.rb:28:in `receive_data'
        from shared/bundle/ruby/1.9.1/bundler/gems/em-websocket-b9d439cd3494/lib/em-websocket/connection.rb:71:in `receive_data'
        from shared/bundle/ruby/1.9.1/gems/eventmachine-0.12.10/lib/eventmachine.rb:256:in `run_machine'
        from shared/bundle/ruby/1.9.1/gems/eventmachine-0.12.10/lib/eventmachine.rb:256:in `run'
        from shared/bundle/ruby/1.9.1/bundler/gems/slanger-c07a3de1ea6e/bin/slanger:72:in `<top (required)>'
        from shared/bundle/ruby/1.9.1/bin/slanger:21:in `load'
        from shared/bundle/ruby/1.9.1/bin/slanger:21:in `<main>'

Is this a Metriks problem or a problem of the RBTree implementation?

Redis backend?

Are you interested in a redis reporter backend? I have one I'm working on, and I'd be happy to submit a PR if you're interested. Thanks for a great project ;)

Troubleshooting a Rails/Unicorn/Sidekiq setup

Hi,

I'm trying to use Metriks in a Rails 4.1 app.

I've added an initializer to start a Librato reporter, and I've added a few ActiveSupport::Notification subscriptions to collect data and emit some metrics.

I also have Sidekiq to manage a queue of jobs with a few direct Metriks emissions (not through AS::N).

On my development system, it works really fine ; I see my various metrics in Librato.
But in staging, I don't have all the data in Librato.

If I keep the reporter set up in the initializer, the Sidekiq process sends its metrics fine, but the Unicorn workers don't. I've figured out that it's because of the forking model of Unicorn.

If I put the reporter's initialization in the after_fork block of Unicorn's config file, I have some data from my Rails processes, but not from Sidekiq.

I didn't find a good way of keeping my Metriks reporter configuration between Rails regular processes (console, rake, Sidekiq workers, …) and the application server's workers (Unicorn in my case, Passenger, …).

With ActiveRecord, there is a class method to re-establish the connection in Unicorn's special case, but there is no configuration.
I'd like to end up with a similar thing where the reporter is set up in a regular way and simply restarted when needed (after a fork).

Thanks for any help of insight.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.