softprops / stats   0.1.0

MIT License GitHub

exporter of numbers, speaks statsd

Scala versions: 2.10

stats

Build Status

numbers, exported.

install

At the following to your sbt project's build.sbt

resolvers += "softprops-maven" at "http://dl.bintray.com/content/softprops/maven"

libraryDependencies += "me.lessis" %% "stats" % "0.1.0"

usage

Stats is a non-blocking Scala frontend for reporting metrics to statsd over UPD.

connecting

Creating a new stats client is simple. The default client is configured for exporting stats to a statsd server hosted locally listening on port 8125.

import scala.concurrent.ExecutionContext.Implicits.global
val cli = stats.Stats()

You can specify a remote address as an InetSocketAddress as a Stats constructor argument or use the addr method to return a new client pointing to an alternative host address.

val hosted = cli.addr(host, port)

metric names

Statd servers are largely concerned with 2 things: metric names and metric values. Metric names are lists of period (.) separated path segments. These names translate to file paths so most naming rules that apply to file names also apply to metric names. If your application has specific name formatting needs, you can override the default escaping strategy with the client's formatNames member.

val formatted = cli.formatNames { segments: Iterable[String] =>
  segments.map(customFormat).mkString(".")
}

Names often represent an encoding of a metric's hierarchy of context. For this reason, many of the stats client interfaces support name scoping with the scope method.

val scoped = cli.scope("svr").scope("serviceName")

The above will prepend srv.serviceName to the name of all reported metrics

Metric values

Statsd defines a set of metric types which a statsd server is able to interpret. This client provides type-safe interfaces for each of those.

Counting

The simplest type of metric is a counter

val counts = cli.counter("requests", endpointName)

counts is a reference to a stat named "requests.endpointName" and defines a handful of operations

// counters can incr & decr a metric name
counts.incr
counts.decr

// counters may also be incr & and decr with a specific value
counts.incr(10)
counts.decr(5)

Note each operation results of a Future of type boolean where the boolean value represents the packet data being sent fully.

Counting, and other metric types, all support request sampling. By default, any metric operation will be reported to a statsd server. You can override this behavior by setting a custom sample rate, a number between 0 and 1.

val sampledRequests = requests.sample(0.8)

The above will only report metric data at a rate of 0.8. Sampling is sometimes helpful to reduce load under heavy periods of requests.

Timing

Timers are recorders of time based information in milliseconds. To remove any ambiguity, interface for timers operate on std library FiniteDurations.

val latency = cli.time("responses")

// record time
latency.record(200 millis)
latency.record(2 seconds)

Gauges

Gauges provide an interface for recording information about arbitrary countable values. Gauges have the unique property retaining only the last value recorded within a configured flush interval. Countable types are currently defined for Ints, Doubles, and Floats . Since a type bound is defined, you are required to specify the type of value you wish to record.

val memory = cli.gauge[Int]("memory")

// record int values
memory.record(1024)
memory.record(80000000)

Though the only the last value requested to be recorded within a flush interval is stored you may also modify that value in place during that window with add and subtract.

add 256 to the current value of memory

memory.add(256)

subtract 256 from the current value of memory

member.subtract(256)

Sets

Sets are similar to gauges except that only unique occurances of values are recorded within a configured flush interval.

val visitors = cli.set[Int]("visitors")

visitors.record(memberA)
visitors.record(memberB)
visitors.record(memberA) // only one memberA event will actually be recorded for a given flush interval

Multi stats

You can also send multiple stats at once by creating a set of stat "instances" and asking the stats client to send them all at once. A stat instance can be created by calling the apply method of the type of stat providing the value to record.

cli.multi(
  requests(1), // increment by one
  latency(1 second),
  memory(2014),
  visitors(memberB)
)

Each individual stat instances sample rate will be honored in a multi metric send. You should take note of your networks configured packet size limits. Some general guidelines are defined here. Stats will make a best effort to group stats according to this clients packetMax size which defaults to 1500. You can configure this as follows.

val sized = cli.packetMax(512)

Logging

It's useful to know when things fail. Since all stating operation result in a Future result you could register a logging listener for failures at each callsite but that can become error prone and tedious. Stats provides a convenient way to do this in one place but registering a logging function.

val logged = cli.addr("deadhost").log {
  case Failure(NonFatal(e)) =>
     println(s"failed to sent packet of one or more stats")
  case _ =>
}
logged.counter("this", "will", "fail").incr

Inspecting

You can inspect the value a given metric will report by calling the str member of a given stats instance. This can help you get to know what to expect with you look for your metric in a the graphite UI.

// foo.bar:1000|ms
val timerStr = cli.time("foo", "bar")(1.second).str

Note that metric names are specified as a varargs list of path segments. You may be template to hard code a pre-encoded path ( one containing a separator). In these cases those periods will be escaped to remove ambiguity.

// foo_bar.baz:1000|ms
val timerStr = cli.time("foo.bar", "baz")(1.second).str

Java

Care was taken to ensure stats interfaces are also tolerable to use in java-based applications;

import stats.Stats;
import scala
public class Main {
  public void main(String[] args) {
    Stats client = Stats.client().scope("foo", "bar");
    client.jcounter("baz", "boom").incr();
  }
}

Doug Tangren (softprops) 2014