All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.twitter.ostrich.stats.StatsProvider.scala Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2009 Twitter, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may
 * not use this file except in compliance with the License. You may obtain
 * a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.twitter.ostrich.stats

import scala.util.matching.Regex
import scala.collection.{Map, mutable, immutable}
import com.twitter.json.Json
import com.twitter.util.{Future, Stopwatch}
import com.twitter.logging.Logger

/**
 * Immutable summary of counters, metrics, gauges, and labels.
 */
case class StatsSummary(
  counters: Map[String, Long],
  metrics: Map[String, Distribution],
  gauges: Map[String, Double],
  labels: Map[String, String]
) {
  /**
   * Dump a nested map of the stats in this collection, suitable for json output.
   */
  def toMap: Map[String, Any] = {
    val jsonGauges = Map[String, Any]() ++ gauges.map { case (k, v) =>
      if (v.longValue == v) { (k, v.longValue) } else { (k, v) }
    }
    Map(
      "counters" -> counters,
      "metrics" -> metrics,
      "gauges" -> jsonGauges,
      "labels" -> labels
    )
  }

  /**
   * Dump a json-encoded map of the stats in this collection.
   */
  def toJson = {
    Json.build(toMap).toString
  }

  def filterOut(regex: Regex) = {
    StatsSummary(
      counters.filterKeys { !regex.pattern.matcher(_).matches },
      metrics.filterKeys { !regex.pattern.matcher(_).matches },
      gauges.filterKeys { !regex.pattern.matcher(_).matches },
      labels.filterKeys { !regex.pattern.matcher(_).matches }
    )
  }
}

/**
 * Trait for anything that collects counters, timings, and gauges, and can report them in
 * name/value maps.
 *
 * Many helper methods are provided, with default implementations that just call back into the
 * abstract methods, so a concrete implementation only needs to fill in the abstract methods.
 *
 * To recap the README:
 *
 * - counter: a value that never decreases (like "exceptions" or "completed_transactions")
 * - gauge: a discrete instantaneous value (like "heap_used" or "current_temperature")
 * - metric: a distribution (min, max, median, 99th percentile, ...) like "event_timing"
 * - label: an instantaneous informational string for debugging or status checking
 */
trait StatsProvider {
  val log = Logger.get(getClass.getName)

  /**
   * Adds a value to a named metric, which tracks min, max, mean, and a histogram.
   */
  def addMetric(name: String, value: Int) {
    if (value >= 0) {
      getMetric(name).add(value)
    } else {
      log.warning("Tried to add a negative data point: %s", name)
    }
  }

  /**
   * Adds a set of values to a named metric. Effectively the incoming distribution is merged with
   * the named metric.
   */
  def addMetric(name: String, distribution: Distribution) {
    getMetric(name).add(distribution)
  }

  /**
   * Increments a counter, returning the new value.
   */
  def incr(name: String, count: Int): Long = {
    getCounter(name).incr(count)
  }

  /**
   * Increments a counter by one, returning the new value.
   */
  def incr(name: String): Long = incr(name, 1)

  /**
   * Add a gauge function, which is used to sample instantaneous values.
   */
  def addGauge(name: String)(gauge: => Double)

  /**
   * Set a gauge to a specific value. This overwrites any previous value or function.
   */
  def setGauge(name: String, value: Double) {
    addGauge(name)(value)
  }

  /**
   * Remove a gauge from the provided stats.
   */
  def clearGauge(name: String)

  /**
   * Set a label to a string.
   */
  def setLabel(name: String, value: String)

  /**
   * Clear an existing label.
   */
  def clearLabel(name: String)

  /**
   * Get the Counter object representing a named counter.
   */
  def getCounter(name: String): Counter

  /**
   * Get the Metric object representing a named metric.
   */
  def getMetric(name: String): Metric

  /**
   * Get the current value of a named gauge.
   */
  def getGauge(name: String): Option[Double]

  /**
   * Get the current value of a named label, if it exists.
   */
  def getLabel(name: String): Option[String]

  /**
   * Summarize all the counters in this collection.
   */
  def getCounters(): Map[String, Long]

  /**
   * Summarize all the metrics in this collection.
   */
  def getMetrics(): Map[String, Distribution]

  /**
   * Summarize all the gauges in this collection.
   */
  def getGauges(): Map[String, Double]

  /**
   * Summarize all the labels in this collection.
   */
  def getLabels(): Map[String, String]

  /**
   * Summarize all the counters, metrics, gauges, and labels in this collection.
   */
  def get(): StatsSummary = StatsSummary(getCounters(), getMetrics(), getGauges(), getLabels())

  /**
   * Reset all collected stats and erase the history.
   * Probably only useful for unit tests.
   */
  def clearAll()

  /**
   * Runs the function f and logs that duration, in milliseconds, with the given name.
   */
  def time[T](name: String)(f: => T): T = {
    val duration = Stopwatch.start()
    val rv: T = f 
    addMetric(name + "_msec", duration().inMilliseconds.toInt)
    rv
  }

  /**
   * Runs the function f and logs that duration until the future is satisfied, in microseconds, with
   * the given name.
   */
  def timeFutureMicros[T](name: String)(f: Future[T]): Future[T] = {
    val elapsed = Stopwatch.start()
    f.respond { _ =>
      addMetric(name + "_usec", elapsed().inMicroseconds.toInt)
    }
    f
  }

  /**
   * Runs the function f and logs that duration until the future is satisfied, in milliseconds, with
   * the given name.
   */
  @deprecated("Use timeFutureMillisLazy instead")
  def timeFutureMillis[T](name: String)(f: Future[T]): Future[T] = {
    timeFutureMillisLazy(name) { f }
  }

  /**
   * Lazily runs the Future that `f` returns and measure the duration of Future creation and time
   * until it is satisfied, in milliseconds, with the given name
   */
  def timeFutureMillisLazy[T](name: String)(f: => Future[T]): Future[T] = {
    val elapsed = Stopwatch.start()
    f.ensure {
      addMetric(name + "_msec", elapsed().inMilliseconds.toInt)
    }
  }

  /**
   * Runs the function f and logs that duration until the future is satisfied, in nanoseconds, with
   * the given name.
   */
  def timeFutureNanos[T](name: String)(f: Future[T]): Future[T] = {
    val elapsed = Stopwatch.start()
    f.respond { _ =>
      addMetric(name + "_nsec", elapsed().inNanoseconds.toInt)
    }
    f
  }

  /**
   * Runs the function f and logs that duration, in microseconds, with the given name.
   */
  def timeMicros[T](name: String)(f: => T): T = {
    val duration = Stopwatch.start()
    val rv: T = f
    addMetric(name + "_usec", duration().inMicroseconds.toInt)
    rv
  }

  /**
   * Runs the function f and logs that duration, in nanoseconds, with the given name.
   */
  def timeNanos[T](name: String)(f: => T): T = {
    val duration = Stopwatch.start()
    val rv: T = f
    addMetric(name + "_nsec", duration().inNanoseconds.toInt)
    rv
  }
}

/**
 * A StatsProvider that doesn't actually save or report anything.
 */
object DevNullStats extends StatsProvider {
  def addGauge(name: String)(gauge: => Double) = ()
  def clearGauge(name: String) = ()
  def setLabel(name: String, value: String) = ()
  def clearLabel(name: String) = ()
  def getCounter(name: String) = new Counter()
  def getMetric(name: String) = new Metric()
  def getGauge(name: String) = None
  def getLabel(name: String) = None
  def getCounters() = Map.empty
  def getMetrics() = Map.empty
  def getGauges() = Map.empty
  def getLabels() = Map.empty
  def clearAll() = ()
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy