librato.go 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230
  1. package librato
  2. import (
  3. "fmt"
  4. "log"
  5. "math"
  6. "regexp"
  7. "time"
  8. "github.com/rcrowley/go-metrics"
  9. )
  10. // a regexp for extracting the unit from time.Duration.String
  11. var unitRegexp = regexp.MustCompile("[^\\d]+$")
  12. // a helper that turns a time.Duration into librato display attributes for timer metrics
  13. func translateTimerAttributes(d time.Duration) (attrs map[string]interface{}) {
  14. attrs = make(map[string]interface{})
  15. attrs[DisplayTransform] = fmt.Sprintf("x/%d", int64(d))
  16. attrs[DisplayUnitsShort] = string(unitRegexp.Find([]byte(d.String())))
  17. return
  18. }
  19. type Reporter struct {
  20. Email, Token string
  21. Source string
  22. Interval time.Duration
  23. Registry metrics.Registry
  24. Percentiles []float64 // percentiles to report on histogram metrics
  25. TimerAttributes map[string]interface{} // units in which timers will be displayed
  26. intervalSec int64
  27. }
  28. func NewReporter(r metrics.Registry, d time.Duration, e string, t string, s string, p []float64, u time.Duration) *Reporter {
  29. return &Reporter{e, t, s, d, r, p, translateTimerAttributes(u), int64(d / time.Second)}
  30. }
  31. func Librato(r metrics.Registry, d time.Duration, e string, t string, s string, p []float64, u time.Duration) {
  32. NewReporter(r, d, e, t, s, p, u).Run()
  33. }
  34. func (self *Reporter) Run() {
  35. ticker := time.Tick(self.Interval)
  36. metricsApi := &LibratoClient{self.Email, self.Token}
  37. for now := range ticker {
  38. var metrics Batch
  39. var err error
  40. if metrics, err = self.BuildRequest(now, self.Registry); err != nil {
  41. log.Printf("ERROR constructing librato request body %s", err)
  42. continue
  43. }
  44. if err := metricsApi.PostMetrics(metrics); err != nil {
  45. log.Printf("ERROR sending metrics to librato %s", err)
  46. continue
  47. }
  48. }
  49. }
  50. // calculate sum of squares from data provided by metrics.Histogram
  51. // see http://en.wikipedia.org/wiki/Standard_deviation#Rapid_calculation_methods
  52. func sumSquares(s metrics.Sample) float64 {
  53. count := float64(s.Count())
  54. sumSquared := math.Pow(count*s.Mean(), 2)
  55. sumSquares := math.Pow(count*s.StdDev(), 2) + sumSquared/count
  56. if math.IsNaN(sumSquares) {
  57. return 0.0
  58. }
  59. return sumSquares
  60. }
  61. func sumSquaresTimer(t metrics.Timer) float64 {
  62. count := float64(t.Count())
  63. sumSquared := math.Pow(count*t.Mean(), 2)
  64. sumSquares := math.Pow(count*t.StdDev(), 2) + sumSquared/count
  65. if math.IsNaN(sumSquares) {
  66. return 0.0
  67. }
  68. return sumSquares
  69. }
  70. func (self *Reporter) BuildRequest(now time.Time, r metrics.Registry) (snapshot Batch, err error) {
  71. snapshot = Batch{
  72. // coerce timestamps to a stepping fn so that they line up in Librato graphs
  73. MeasureTime: (now.Unix() / self.intervalSec) * self.intervalSec,
  74. Source: self.Source,
  75. }
  76. snapshot.Gauges = make([]Measurement, 0)
  77. snapshot.Counters = make([]Measurement, 0)
  78. histogramGaugeCount := 1 + len(self.Percentiles)
  79. r.Each(func(name string, metric interface{}) {
  80. measurement := Measurement{}
  81. measurement[Period] = self.Interval.Seconds()
  82. switch m := metric.(type) {
  83. case metrics.Counter:
  84. if m.Count() > 0 {
  85. measurement[Name] = fmt.Sprintf("%s.%s", name, "count")
  86. measurement[Value] = float64(m.Count())
  87. measurement[Attributes] = map[string]interface{}{
  88. DisplayUnitsLong: Operations,
  89. DisplayUnitsShort: OperationsShort,
  90. DisplayMin: "0",
  91. }
  92. snapshot.Counters = append(snapshot.Counters, measurement)
  93. }
  94. case metrics.Gauge:
  95. measurement[Name] = name
  96. measurement[Value] = float64(m.Value())
  97. snapshot.Gauges = append(snapshot.Gauges, measurement)
  98. case metrics.GaugeFloat64:
  99. measurement[Name] = name
  100. measurement[Value] = float64(m.Value())
  101. snapshot.Gauges = append(snapshot.Gauges, measurement)
  102. case metrics.Histogram:
  103. if m.Count() > 0 {
  104. gauges := make([]Measurement, histogramGaugeCount, histogramGaugeCount)
  105. s := m.Sample()
  106. measurement[Name] = fmt.Sprintf("%s.%s", name, "hist")
  107. measurement[Count] = uint64(s.Count())
  108. measurement[Max] = float64(s.Max())
  109. measurement[Min] = float64(s.Min())
  110. measurement[Sum] = float64(s.Sum())
  111. measurement[SumSquares] = sumSquares(s)
  112. gauges[0] = measurement
  113. for i, p := range self.Percentiles {
  114. gauges[i+1] = Measurement{
  115. Name: fmt.Sprintf("%s.%.2f", measurement[Name], p),
  116. Value: s.Percentile(p),
  117. Period: measurement[Period],
  118. }
  119. }
  120. snapshot.Gauges = append(snapshot.Gauges, gauges...)
  121. }
  122. case metrics.Meter:
  123. measurement[Name] = name
  124. measurement[Value] = float64(m.Count())
  125. snapshot.Counters = append(snapshot.Counters, measurement)
  126. snapshot.Gauges = append(snapshot.Gauges,
  127. Measurement{
  128. Name: fmt.Sprintf("%s.%s", name, "1min"),
  129. Value: m.Rate1(),
  130. Period: int64(self.Interval.Seconds()),
  131. Attributes: map[string]interface{}{
  132. DisplayUnitsLong: Operations,
  133. DisplayUnitsShort: OperationsShort,
  134. DisplayMin: "0",
  135. },
  136. },
  137. Measurement{
  138. Name: fmt.Sprintf("%s.%s", name, "5min"),
  139. Value: m.Rate5(),
  140. Period: int64(self.Interval.Seconds()),
  141. Attributes: map[string]interface{}{
  142. DisplayUnitsLong: Operations,
  143. DisplayUnitsShort: OperationsShort,
  144. DisplayMin: "0",
  145. },
  146. },
  147. Measurement{
  148. Name: fmt.Sprintf("%s.%s", name, "15min"),
  149. Value: m.Rate15(),
  150. Period: int64(self.Interval.Seconds()),
  151. Attributes: map[string]interface{}{
  152. DisplayUnitsLong: Operations,
  153. DisplayUnitsShort: OperationsShort,
  154. DisplayMin: "0",
  155. },
  156. },
  157. )
  158. case metrics.Timer:
  159. measurement[Name] = name
  160. measurement[Value] = float64(m.Count())
  161. snapshot.Counters = append(snapshot.Counters, measurement)
  162. if m.Count() > 0 {
  163. libratoName := fmt.Sprintf("%s.%s", name, "timer.mean")
  164. gauges := make([]Measurement, histogramGaugeCount, histogramGaugeCount)
  165. gauges[0] = Measurement{
  166. Name: libratoName,
  167. Count: uint64(m.Count()),
  168. Sum: m.Mean() * float64(m.Count()),
  169. Max: float64(m.Max()),
  170. Min: float64(m.Min()),
  171. SumSquares: sumSquaresTimer(m),
  172. Period: int64(self.Interval.Seconds()),
  173. Attributes: self.TimerAttributes,
  174. }
  175. for i, p := range self.Percentiles {
  176. gauges[i+1] = Measurement{
  177. Name: fmt.Sprintf("%s.timer.%2.0f", name, p*100),
  178. Value: m.Percentile(p),
  179. Period: int64(self.Interval.Seconds()),
  180. Attributes: self.TimerAttributes,
  181. }
  182. }
  183. snapshot.Gauges = append(snapshot.Gauges, gauges...)
  184. snapshot.Gauges = append(snapshot.Gauges,
  185. Measurement{
  186. Name: fmt.Sprintf("%s.%s", name, "rate.1min"),
  187. Value: m.Rate1(),
  188. Period: int64(self.Interval.Seconds()),
  189. Attributes: map[string]interface{}{
  190. DisplayUnitsLong: Operations,
  191. DisplayUnitsShort: OperationsShort,
  192. DisplayMin: "0",
  193. },
  194. },
  195. Measurement{
  196. Name: fmt.Sprintf("%s.%s", name, "rate.5min"),
  197. Value: m.Rate5(),
  198. Period: int64(self.Interval.Seconds()),
  199. Attributes: map[string]interface{}{
  200. DisplayUnitsLong: Operations,
  201. DisplayUnitsShort: OperationsShort,
  202. DisplayMin: "0",
  203. },
  204. },
  205. Measurement{
  206. Name: fmt.Sprintf("%s.%s", name, "rate.15min"),
  207. Value: m.Rate15(),
  208. Period: int64(self.Interval.Seconds()),
  209. Attributes: map[string]interface{}{
  210. DisplayUnitsLong: Operations,
  211. DisplayUnitsShort: OperationsShort,
  212. DisplayMin: "0",
  213. },
  214. },
  215. )
  216. }
  217. }
  218. })
  219. return
  220. }