librato.go 7.4 KB

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