in server/src/main/scala/com/twitter/server/handler/HistogramQueryHandler.scala [218:275]
private[this] def midPoint(bc: BucketAndCount): Double =
if (bc.upperLimit >= Int.MaxValue) bc.lowerLimit
else (bc.upperLimit + bc.lowerLimit) / 2.0
private[handler] def generateSummary(histoName: String): Option[Summary] = {
histograms.get(histoName).map { detail =>
val bcs = detail.counts.sortBy(_.lowerLimit)
// first, the basic computations: sum, count, min, max, average
val min = bcs.headOption.map(_.lowerLimit)
val max = bcs.lastOption.map(_.upperLimit)
var sum = 0.0
var count = 0L
bcs.foreach { bc =>
count += bc.count
sum += bc.count.toDouble * midPoint(bc)
}
val average =
if (count == 0L) None
else Some(sum.toLong / count)
// note: this is modeled after `c.t.f.stats.BucketedHistogram.percentile`
def percentile(total: Long, p: Double): Long = {
if (p < 0.0 || p > 1.0)
throw new AssertionError(s"percentile must be within 0.0 to 1.0 inclusive: $p")
val target = Math.round(p * total)
val iter = bcs.iterator
var sum = 0L
var bc: BucketAndCount = null
while (iter.hasNext && sum < target) {
bc = iter.next()
sum += bc.count
}
bc match {
case null => 0
case _ if !iter.hasNext => max.getOrElse(0)
case _ => midPoint(bc).toLong
}
}
val percentiles: Map[String, Long] = SummaryThresholds.map {
case (name, p) =>
name -> percentile(count, p)
}.toMap
Summary(
name = histoName,
count = count,
sum = sum.toLong,
average = average,
min = min,
max = max,
percentiles = percentiles
)
}
}