librato.go 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  1. package librato
  2. // TODO: use map[string]interface{} and constants for keys for everything to
  3. // avoid the omitempty/0 problem; remove dependency on samuel/go-librato
  4. // TODO WIP status: test that the resulting JSON is actually correct..
  5. import (
  6. "fmt"
  7. "github.com/rcrowley/go-metrics"
  8. "log"
  9. "math"
  10. "time"
  11. )
  12. type LibratoReporter struct {
  13. Email, Token string
  14. Source string
  15. Interval time.Duration
  16. Registry metrics.Registry
  17. Percentiles []float64 // percentiles to report on histogram metrics
  18. }
  19. func Librato(r metrics.Registry, d time.Duration, e string, t string, s string, p []float64) {
  20. reporter := &LibratoReporter{e, t, s, d, r, p}
  21. reporter.Run()
  22. }
  23. func (self *LibratoReporter) Run() {
  24. ticker := time.Tick(self.Interval)
  25. metricsApi := &LibratoClient{self.Email, self.Token}
  26. for now := range ticker {
  27. var metrics Batch
  28. var err error
  29. if metrics, err = self.BuildRequest(now, self.Registry); err != nil {
  30. log.Printf("ERROR constructing librato request body %s", err)
  31. }
  32. if err := metricsApi.PostMetrics(metrics); err != nil {
  33. log.Printf("ERROR sending metrics to librato %s", err)
  34. }
  35. }
  36. }
  37. // calculate sum of squares from data provided by metrics.Histogram
  38. // see http://en.wikipedia.org/wiki/Standard_deviation#Rapid_calculation_methods
  39. func sumSquares(m metrics.Histogram) float64 {
  40. count := float64(m.Count())
  41. sum := m.Mean() * float64(m.Count())
  42. sumSquared := math.Pow(float64(sum), 2)
  43. sumSquares := math.Pow(count*m.StdDev(), 2) + sumSquared/float64(m.Count())
  44. if math.IsNaN(sumSquares) {
  45. return 0.0
  46. }
  47. return sumSquared
  48. }
  49. func sumSquaresTimer(m metrics.Timer) float64 {
  50. count := float64(m.Count())
  51. sum := m.Mean() * float64(m.Count())
  52. sumSquared := math.Pow(float64(sum), 2)
  53. sumSquares := math.Pow(count*m.StdDev(), 2) + sumSquared/float64(m.Count())
  54. if math.IsNaN(sumSquares) {
  55. return 0.0
  56. }
  57. return sumSquares
  58. }
  59. func (self *LibratoReporter) BuildRequest(now time.Time, r metrics.Registry) (snapshot Batch, err error) {
  60. snapshot = Batch{
  61. MeasureTime: now.Unix(),
  62. Source: self.Source,
  63. }
  64. snapshot.MeasureTime = now.Unix()
  65. snapshot.Gauges = make([]Measurement, 0)
  66. snapshot.Counters = make([]Measurement, 0)
  67. histogramGaugeCount := 1 + len(self.Percentiles)
  68. r.Each(func(name string, metric interface{}) {
  69. measurement := Measurement{}
  70. measurement[Period] = self.Interval.Seconds()
  71. switch m := metric.(type) {
  72. case metrics.Counter:
  73. measurement[Name] = fmt.Sprintf("%s.%s", name, "count")
  74. measurement[Value] = float64(m.Count())
  75. snapshot.Counters = append(snapshot.Counters, measurement)
  76. case metrics.Gauge:
  77. measurement[Name] = name
  78. measurement[Value] = float64(m.Value())
  79. snapshot.Gauges = append(snapshot.Gauges, measurement)
  80. case metrics.Histogram:
  81. if m.Count() > 0 {
  82. gauges := make([]Measurement, histogramGaugeCount, histogramGaugeCount)
  83. measurement[Name] = fmt.Sprintf("%s.%s", name, "hist")
  84. measurement[Count] = uint64(m.Count())
  85. measurement[Sum] = m.Mean() * float64(m.Count())
  86. measurement[Max] = float64(m.Max())
  87. measurement[Min] = float64(m.Min())
  88. measurement[SumSquares] = sumSquares(m)
  89. gauges[0] = measurement
  90. for i, p := range self.Percentiles {
  91. pMeasurement := Measurement{}
  92. pMeasurement[Name] = fmt.Sprintf("%s.%.2f", measurement[Name], p)
  93. pMeasurement[Value] = m.Percentile(p)
  94. pMeasurement[Period] = measurement[Period]
  95. gauges[i+1] = pMeasurement
  96. }
  97. snapshot.Gauges = append(snapshot.Gauges, gauges...)
  98. }
  99. case metrics.Meter:
  100. measurement[Name] = name
  101. measurement[Value] = float64(m.Count())
  102. snapshot.Counters = append(snapshot.Counters, measurement)
  103. snapshot.Gauges = append(snapshot.Gauges,
  104. Measurement{
  105. Name: fmt.Sprintf("%s.%s", name, "1min"),
  106. Value: m.Rate1(),
  107. Period: int64(self.Interval.Seconds()),
  108. },
  109. Measurement{
  110. Name: fmt.Sprintf("%s.%s", name, "5min"),
  111. Value: m.Rate5(),
  112. Period: int64(self.Interval.Seconds()),
  113. },
  114. Measurement{
  115. Name: fmt.Sprintf("%s.%s", name, "15min"),
  116. Value: m.Rate15(),
  117. Period: int64(self.Interval.Seconds()),
  118. },
  119. )
  120. case metrics.Timer:
  121. if m.Count() > 0 {
  122. libratoName := fmt.Sprintf("%s.%s", name, "timer.mean")
  123. gauges := make([]Measurement, histogramGaugeCount, histogramGaugeCount)
  124. gauges[0] = Measurement{
  125. Name: libratoName,
  126. Count: uint64(m.Count()),
  127. Sum: m.Mean() * float64(m.Count()),
  128. Max: float64(m.Max()),
  129. Min: float64(m.Min()),
  130. SumSquares: sumSquaresTimer(m),
  131. Period: int64(self.Interval.Seconds()),
  132. Attributes: map[string]interface{}{
  133. DisplayTransform: "x/1000000",
  134. DisplayUnitsLong: "milliseconds",
  135. DisplayUnitsShort: "ms",
  136. },
  137. }
  138. for i, p := range self.Percentiles {
  139. gauges[i+1] = Measurement{
  140. Name: fmt.Sprintf("%s.timer.%2.0f", name, p*100),
  141. Value: m.Percentile(p),
  142. Period: int64(self.Interval.Seconds()),
  143. Attributes: map[string]interface{}{
  144. DisplayTransform: "x/1000000",
  145. DisplayUnitsLong: "milliseconds",
  146. DisplayUnitsShort: "ms",
  147. },
  148. }
  149. }
  150. snapshot.Gauges = append(snapshot.Gauges, gauges...)
  151. snapshot.Gauges = append(snapshot.Gauges,
  152. Measurement{
  153. Name: fmt.Sprintf("%s.%s", name, "rate.1min"),
  154. Value: m.Rate1(),
  155. Period: int64(self.Interval.Seconds()),
  156. Attributes: map[string]interface{}{
  157. DisplayUnitsLong: "occurences",
  158. DisplayUnitsShort: "occ",
  159. DisplayMin: "0",
  160. },
  161. },
  162. Measurement{
  163. Name: fmt.Sprintf("%s.%s", name, "rate.5min"),
  164. Value: m.Rate5(),
  165. Period: int64(self.Interval.Seconds()),
  166. Attributes: map[string]interface{}{
  167. DisplayUnitsLong: "occurences",
  168. DisplayUnitsShort: "occ",
  169. DisplayMin: "0",
  170. },
  171. },
  172. Measurement{
  173. Name: fmt.Sprintf("%s.%s", name, "rate.15min"),
  174. Value: m.Rate15(),
  175. Period: int64(self.Interval.Seconds()),
  176. Attributes: map[string]interface{}{
  177. DisplayUnitsLong: "occurences",
  178. DisplayUnitsShort: "occ",
  179. DisplayMin: "0",
  180. },
  181. },
  182. )
  183. }
  184. }
  185. })
  186. return
  187. }