com.twitter.finagle.stats.StatsReceiver.scala Maven / Gradle / Ivy
The newest version!
package com.twitter.finagle.stats
import com.twitter.util.{Future, Stopwatch, JavaSingleton}
import java.util.concurrent.TimeUnit
/**
* A writeable Counter. Only sums are kept of Counters. An example
* Counter is "number of requests served".
*/
trait Counter {
def incr(delta: Int)
def incr() { incr(1) }
}
/**
* An append-only collection of time-series data. Example Stats are
* "queue depth" or "query width in a stream of requests".
*/
trait Stat {
def add(value: Float)
}
/**
* Exposes the value of a function. For example, one could add a gauge for a
* computed health metric.
*/
trait Gauge {
def remove()
}
object StatsReceiver {
private[StatsReceiver] var immortalGauges: List[Gauge] = Nil
}
/**
* An interface for recording metrics. Named
* [[com.twitter.finagle.stats.Counter Counters]],
* [[com.twitter.finagle.stats.Stat Stats]], and
* [[com.twitter.finagle.stats.Gauge Gauges]] can be accessed through the
* corresponding methods of this class.
*/
trait StatsReceiver {
/**
* Specifies the representative receiver. This is in order to
* expose an object we can use for comparison so that global stats
* are only reported once per receiver.
*/
val repr: AnyRef
/**
* Accurately indicates if this is a NullStatsReceiver.
* Because equality is not forwarded via scala.Proxy, this
* is helpful to check for a NullStatsReceiver.
*/
def isNull: Boolean = false
/**
* Time a given function using the given TimeUnit
*/
def time[T](unit: TimeUnit, stat: Stat)(f: => T): T = {
val elapsed = Stopwatch.start()
val result = f
stat.add(elapsed().inUnit(unit))
result
}
/**
* Time a given function using the given TimeUnit
*/
def time[T](unit: TimeUnit, name: String*)(f: => T): T = {
time(unit, stat(name: _*))(f)
}
/**
* Time a given function in milliseconds
*/
def time[T](name: String*)(f: => T): T = {
time(TimeUnit.MILLISECONDS, name: _*)(f)
}
/**
* Time a given future using the given TimeUnit
*/
def timeFuture[T](unit: TimeUnit, stat: Stat)(f: => Future[T]): Future[T] = {
val elapsed = Stopwatch.start()
f ensure {
stat.add(elapsed().inUnit(unit))
}
}
/**
* Time a given future using the given TimeUnit
*/
def timeFuture[T](unit: TimeUnit, name: String*)(f: => Future[T]): Future[T] = {
timeFuture(unit, stat(name: _*))(f)
}
/**
* Time a given future in milliseconds
*/
def timeFuture[T](name: String*)(f: => Future[T]): Future[T] = {
timeFuture(TimeUnit.MILLISECONDS, name: _*)(f)
}
/**
* Get a Counter with the description
*/
def counter(name: String*): Counter
/**
* Get a Counter with the description. This method is a convenience for Java program.
*/
def counter0(name: String): Counter = counter(name)
/**
* Get a Stat with the description
*/
def stat(name: String*): Stat
/**
* Get a Stat with the description. This method is a convenience for Java programs.
*/
def stat0(name: String): Stat = stat(name)
/**
* Register a function to be periodically measured. This measurement
* exists in perpetuity. Measurements under the same name are added
* together.
*/
def provideGauge(name: String*)(f: => Float) {
val gauge = addGauge(name: _*)(f)
StatsReceiver.synchronized {
StatsReceiver.immortalGauges ::= gauge
}
}
/**
* Add the function ``f'' as a gauge with the given name. The
* returned gauge value is only weakly referenced by the
* StatsReceiver, and if garbage collected will cease to be a part
* of this measurement: thus, it needs to be retained by the
* caller. Immortal measurements are made with ``provideGauge''. As
* with ``provideGauge'', gauges with equal names are added
* together.
*/
def addGauge(name: String*)(f: => Float): Gauge
/**
* Prepend ``namespace'' to the names of this receiver.
*/
def scope(namespace: String): StatsReceiver = {
if (namespace == "") this
else {
val seqPrefix = Seq(namespace)
new NameTranslatingStatsReceiver(this) {
protected[this] def translate(name: Seq[String]) = seqPrefix ++ name
}
}
}
/**
* Prepend a suffix value to the next scope.
* stats.scopeSuffix("toto").scope("client").counter("adds") will generate
* /client/toto/adds
*/
def scopeSuffix(suffix: String): StatsReceiver = {
if (suffix == "") this
else {
val self = this
new StatsReceiver {
val repr = self.repr
def counter(names: String*) = self.counter(names: _*)
def stat(names: String*) = self.stat(names: _*)
def addGauge(names: String*)(f: => Float) = self.addGauge(names: _*)(f)
override def scope(namespace: String) = self.scope(namespace).scope(suffix)
}
}
}
}
trait StatsReceiverProxy extends StatsReceiver {
def self: StatsReceiver
val repr = self
override def isNull = self.isNull
def counter(names: String*) = self.counter(names:_*)
def stat(names: String*) = self.stat(names:_*)
def addGauge(names: String*)(f: => Float) = self.addGauge(names:_*)(f)
}
/**
* A StatsReceiver receiver proxy that translates all counter, stat, and gauge
* names according to a `translate` function.
*
* @param self The underlying StatsReceiver to which translated names are passed
*/
abstract class NameTranslatingStatsReceiver(val self: StatsReceiver)
extends StatsReceiver
{
protected[this] def translate(name: Seq[String]): Seq[String]
val repr = self.repr
override def isNull = self.isNull
def counter(name: String*) = self.counter(translate(name): _*)
def stat(name: String*) = self.stat(translate(name): _*)
def addGauge(name: String*)(f: => Float) = self.addGauge(translate(name): _*)(f)
}
/**
* A noop StatsReceiver. Metrics are not recorded, making this receiver useful
* in unit tests and as defaults in situations where metrics are not strictly
* required.
*/
class NullStatsReceiver extends StatsReceiver with JavaSingleton {
val repr = this
override def isNull = true
private[this] val NullCounter = new Counter { def incr(delta: Int) {} }
private[this] val NullStat = new Stat { def add(value: Float) {}}
private[this] val NullGauge = new Gauge { def remove() {} }
def counter(name: String*) = NullCounter
def stat(name: String*) = NullStat
def addGauge(name: String*)(f: => Float) = NullGauge
override def toString = "NullStatsReceiver"
}
object NullStatsReceiver extends NullStatsReceiver
/**
* A "default" StatsReceiver loaded by Finagle's
* [[com.twitter.finagle.util.LoadService]] mechanism.
*/
object DefaultStatsReceiver extends {
val self: StatsReceiver = LoadedStatsReceiver
} with StatsReceiverProxy
/**
* A client-specific StatsReceiver. All stats recorded using this receiver
* are prefixed with the string "clnt" by default.
*/
object ClientStatsReceiver extends StatsReceiverProxy {
@volatile private[this] var _self: StatsReceiver = LoadedStatsReceiver.scope("clnt")
def self: StatsReceiver = _self
def setRootScope(rootScope: String) {
_self = LoadedStatsReceiver.scope(rootScope)
}
}
/**
* A server-specific StatsReceiver. All stats recorded using this receiver
* are prefixed with the string "srv" by default.
*/
object ServerStatsReceiver extends StatsReceiverProxy {
@volatile private[this] var _self: StatsReceiver = LoadedStatsReceiver.scope("srv")
def self: StatsReceiver = _self
def setRootScope(rootScope: String) {
_self = LoadedStatsReceiver.scope(rootScope)
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy