• Stars
    star
    118
  • Rank 299,834 (Top 6 %)
  • Language
    Clojure
  • Created over 11 years ago
  • Updated about 2 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Clojure data structures for performance metrics over discrete time intervals.

interval-metrics

Data structures for measuring performance. Provides lockfree, high-performance mutable state, wrapped in idiomatic Clojure identities, without any external dependencies.

Codahale's metrics library is designed for slowly-evolving metrics with stable dynamics, where multiple readers may request the current value of a metric at any time. Sometimes, you have a single reader which collects metrics over a specific time window--and you want the value of the metric at the end of the window to reflect observations from that window only, rather than including observations from prior windows.

Clojars

https://clojars.org/interval-metrics

Rates

The Metric protocol defines an identity which wraps some mutable measurements. Think of it like an atom or ref, only instead of (swap!) or (alter), it accepts new measurements to merge into its state. All values in this implementation are longs. Let's keep track of a rate of events per second:

user=> (use 'interval-metrics.core)
nil
user=> (def r (rate))
#'user/r

All operations are thread-safe, naturally. Let's tell the rate that we handled 2 events, then 5 more events, than... I dunno, negative 200 events.

user=> (update! r 2)
#<Rate@df69935: 0.5458097134611932>
user=> (update! r 5)
#<Rate@df69935: 1.0991987655505422>
user=> (update! r -200)
#<Rate@df69935: -22.796646374437813>

Metrics implement IDeref, so you can always ask for their current value without changing anything. Rate's value is the sum of all updates, divided by the time since the last snapshot:

user=> (deref r)
-21.14793138384688

The Snapshot protocol defines an operation which gets the current value of an identity and atomically resets the value. For instance, you might call (snapshot! some-rate) every second, and log the resulting value or send it to Graphite:

user=> (snapshot! r)
-14.292050358924806
user=> r
#<Rate@df69935: 0.0>

Note that the rate became zero when we took a snapshot. It'll start accumulating new state afresh.

Reservoirs

Sometimes you want a probabilistic sample of numbers. uniform-reservoir creates a pool of longs which represents a uniformly distributed sample of the updates. Let's create a reservoir which holds three numbers:

user=> (def r (uniform-reservoir 3))
#'user/r

And update it with some values:

user=> (update! r 2)
#<UniformReservoir@49f17507: (2)>
user=> (update! r 1)
#<UniformReservoir@49f17507: (1 2)>
user=> (update! r 3)
#<UniformReservoir@49f17507: (1 2 3)>

The value of a reservoir is a sorted list of the numbers in its current sample. What happens when we add more than three elements?

#<UniformReservoir@399d2d53: (1 2 3)>
user=> (update! r 4)
#<UniformReservoir@399d2d53: (1 3 4)>
user=> (update! r 5)
#<UniformReservoir@399d2d53: (1 3 4)>

Sampling is probabilistic: 4 made it in, but 5 didn't. Updates are always constant time.

user=> (update! r -2)
#<UniformReservoir@399d2d53: (-2 1 3)>
user=> (update! r -3)
#<UniformReservoir@399d2d53: (-2 1 3)>
user=> (update! r -4)
#<UniformReservoir@399d2d53: (-4 -2 3)>

Snapshots (like deref) return the sorted list in O(n log n) time. Note that when we take a snapshot, the reservoir becomes empty again (nil).

user=> (snapshot! r)
(-4 -2 3)
user=> r
#<AtomicMetric@3018fc1a: nil>
user=> (update! r 42)
#<UniformReservoir@593c7b26: (42)>

There are also functions to extract specific values from sorted sequences. To get the 95th percentile value:

#'user/r
user=> (dotimes [_ 10000] (update! r (rand 1000)))
nil
user=> (quantile @r 0.95)
965

Typically, you'd run have a thread periodically take snapshots of your metrics and report some derived values. Here, we extract the median, 95th, 99th, and maximum percentiles seen since the last snapshot:

user=> (map (partial quantile (snapshot! r)) [0.5 0.95 0.99 1])
(496 945 983 999)

Measuring your code's performance

The interval-metrics.measure namespace has some helpers for measuring common things about your code.

(use ['interval-metrics.measure :only '[periodically measure-latency]]
     ['interval-metrics.core    :only '[snapshot! rate+latency]])

Define a hybrid metric which tracks both rates and latency distributions.

(def latencies (rate+latency))

Start a thread to snapshot the latencies every 5 seconds.

(def poller
  (periodically 5 
    (clojure.pprint/pprint (snapshot! latencies))))

The measure-latency macro times how long its body takes to execute, and updates the latencies metric each time.

(while true
  (measure-latency latencies
    (into [] (range 10))))

You'll see a map like this printed every 5 seconds, showing the rate of calls per second, and the latency distribution, in milliseconds.

{:time 1369438387321/1000,
 :rate 316831.2493798337,
 :latencies
 {0.0 2397/1000000,
  0.5 2463/1000000,
  0.95 2641/1000000,
  0.99 2371/500000,
  0.999 9597/1000000}}

Don't like rationals? Who doesn't! It's easy to map those latencies to a less precise type:

(measure/periodically 5
  (-> latencies
      metrics/snapshot!
      (update-in [:latencies]
                 (partial map (juxt key (comp float val))))
      pprint))

...

{:time 699491725283/500,
 :rate 554.994854087713,
 :latencies
 ([0.0 9.388433]
  [0.5 39.118896]
  [0.95 50.673603]
  [0.99 53.583065]
  [0.999 57.83346])}

Kill the loop with ^C, then shut down the poller thread by calling (poller).

You can configure the quantiles, reservoir size, and units for both the rate and the latencies by passing an options map to rate+latency:

(def latencies (rate+latency {:latency-unit :microseconds
                              :rate-unit    :weeks}))

Performance

All algorithms are lockless. I sacrifice some correctness for performance, but never drop writes. Synchronization drift should be negligible compared to typical (~1s) sampling intervals. Rates on a highly contended JVM are accurate, in experiments, to at least three sigfigs.

With four threads updating, and one thread taking a snapshot every n seconds, my laptop can push between 10 to 18 million updates per second to a single rate+latency, reservoir, or rate object, saturating 99% of four cores.

License

Licensed under the Eclipse Public License.

How to run the tests

lein midje will run all tests.

lein midje namespace.* will run only tests beginning with "namespace.".

lein midje :autotest will run all the tests indefinitely. It sets up a watcher on the code files. If they change, only the relevant tests will be run again.

More Repositories

1

distsys-class

Class materials for a distributed systems lecture series
8,983
star
2

tesser

Clojure reducers, but for parallel execution: locally and on distributed systems.
Clojure
867
star
3

meangirls

Convergent Replicated Data Types
Ruby
650
star
4

tund

SSH reverse tunnel daemon
Ruby
418
star
5

tea-time

Lightweight Clojure task scheduler
Clojure
240
star
6

salticid

A deployment system, with design goals 1: Magic and 2: More Magic
Ruby
222
star
7

dom-top

Unorthodox control flow, for Clojurists with masochistic sensibilities.
Clojure
204
star
8

timelike

A library for simulating parallel systems, in Clojure
Clojure
184
star
9

partitions-post

A blog post on network partitions in practice
182
star
10

clj-antlr

Clojure bindings for the ANTLR 4 parser
Clojure
167
star
11

less-awful-ssl

Sssh no tears, only TLS now. For Clojure.
Clojure
154
star
12

dist-sagas

A paper on sagas in distributed systems
TeX
91
star
13

gretchen

Offline serializability verification, in Clojure
Clojure
68
star
14

prism

Automatically re-run clojure tests
Clojure
59
star
15

verschlimmbesserung

An etcd client with modern Clojure sensibilities
Clojure
56
star
16

merkle

Clojure Merkle Trees
Clojure
51
star
17

gnuplot

Clojure gnuplot bindings
Clojure
47
star
18

risky

A lightweight Ruby ORM for Riak
Ruby
40
star
19

schadenfreude

Clojure benchmarking tools
Clojure
35
star
20

jepsen-talks

Slides and resources for talks on partition tolerance
Clojure
33
star
21

meitner

Explodes Clojure functions and macros into dependency graphs
Clojure
30
star
22

aesahaettr

Sharding, partitioning, and consistent hashing for Clojure. May release spectres.
Clojure
26
star
23

bitcask-ruby

An (incomplete) interface to the Bitcask storage system
Ruby
19
star
24

ustate

micro state daemon
Ruby
18
star
25

salesfear

A Clojure salesforce client.
Clojure
17
star
26

construct

Extensible, persistent, structured configuration for Ruby
Ruby
16
star
27

skewbinheap

A Skew Binomial Heap for Erlang.
Erlang
15
star
28

yamr

A Linux Yammer client.
Ruby
12
star
29

bifurcan-clj

Clojure wrapper for the Bifurcan family of data structures
Clojure
12
star
30

tumblr-archiver

Hacky Clojure program to download media from tumblr liked posts
Clojure
12
star
31

cyclic.js

Cyclic time series data structures for javascript
JavaScript
10
star
32

riemann-bench

An example for using the Reimann Clojure client
Clojure
10
star
33

london-gen

Silly London Landmarks
Clojure
8
star
34

gifdex

A gif tagging server for local use
Clojure
8
star
35

mtrc

Ruby metrics
Ruby
8
star
36

adaptive-executor

Adaptive threadpool executor experiment
Clojure
6
star
37

lights

Change your Hue lights to randomly generated colors, continuously
Clojure
5
star
38

thought-leaders

The definitive list of thought leaders
5
star
39

mastodon-utils

Utilities for working with Mastodon's API
Clojure
5
star
40

prometheus-mastodon-exporter

Exports Mastodon statistics for polling by Prometheus
Clojure
5
star
41

cortex-reaver

A dangerous Ruby blog engine, with a photographic memory.
Ruby
5
star
42

hangman

A ridiculously overpowered hangman AI in Clojure
Clojure
4
star
43

qsd-phase-space-reconstruction

Some ruby scripts for exploring datasets in an attempt to reconstruct phase space dynamics (and to identify lyapunov exponents) of a QSD-simulated Duffing oscillator
Ruby
4
star
44

exocora

A lightweight CGI script framework
Ruby
3
star
45

joedahato

Predict's Joe Damato's hat choices
Clojure
3
star
46

tattoo

Overkill
Clojure
2
star
47

producer_consumer

Ruby queue-backed producer consumer gem
Ruby
2
star
48

ruby-vodpod

Ruby bindings for the Vodpod API.
Ruby
2
star
49

frisk-management

NLP + erotic fiction -> PowerPoint slides
Clojure
2
star
50

heliotrope

A client for the distributed processing system Fabric
Ruby
2
star
51

qsd-tangent

Fork of QSD library with tangent space evolution
C++
2
star
52

caremad

Consistent Commutative Replicated DataTypes
2
star
53

euler-clj

Project Euler solutions in Clojure
Clojure
1
star
54

req-replay

silly experiment
Clojure
1
star
55

autotags

Jquery javascript tag editor with autocomplete
JavaScript
1
star
56

riakeys

Riak key cache (apparently impossible)
Clojure
1
star
57

clojure-perf

Mucking around with clojure performance testing
Clojure
1
star
58

mecha-query

Clojure
1
star