librato.go 7.1 KB

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