prometheus.go 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  1. // +build go1.3
  2. package prometheus
  3. import (
  4. "fmt"
  5. "strings"
  6. "sync"
  7. "time"
  8. "regexp"
  9. "github.com/armon/go-metrics"
  10. "github.com/prometheus/client_golang/prometheus"
  11. )
  12. var (
  13. // DefaultPrometheusOpts is the default set of options used when creating a
  14. // PrometheusSink.
  15. DefaultPrometheusOpts = PrometheusOpts{
  16. Expiration: 60 * time.Second,
  17. }
  18. )
  19. // PrometheusOpts is used to configure the Prometheus Sink
  20. type PrometheusOpts struct {
  21. // Expiration is the duration a metric is valid for, after which it will be
  22. // untracked. If the value is zero, a metric is never expired.
  23. Expiration time.Duration
  24. }
  25. type PrometheusSink struct {
  26. mu sync.Mutex
  27. gauges map[string]prometheus.Gauge
  28. summaries map[string]prometheus.Summary
  29. counters map[string]prometheus.Counter
  30. updates map[string]time.Time
  31. expiration time.Duration
  32. }
  33. // NewPrometheusSink creates a new PrometheusSink using the default options.
  34. func NewPrometheusSink() (*PrometheusSink, error) {
  35. return NewPrometheusSinkFrom(DefaultPrometheusOpts)
  36. }
  37. // NewPrometheusSinkFrom creates a new PrometheusSink using the passed options.
  38. func NewPrometheusSinkFrom(opts PrometheusOpts) (*PrometheusSink, error) {
  39. sink := &PrometheusSink{
  40. gauges: make(map[string]prometheus.Gauge),
  41. summaries: make(map[string]prometheus.Summary),
  42. counters: make(map[string]prometheus.Counter),
  43. updates: make(map[string]time.Time),
  44. expiration: opts.Expiration,
  45. }
  46. return sink, prometheus.Register(sink)
  47. }
  48. // Describe is needed to meet the Collector interface.
  49. func (p *PrometheusSink) Describe(c chan<- *prometheus.Desc) {
  50. // We must emit some description otherwise an error is returned. This
  51. // description isn't shown to the user!
  52. prometheus.NewGauge(prometheus.GaugeOpts{Name: "Dummy", Help: "Dummy"}).Describe(c)
  53. }
  54. // Collect meets the collection interface and allows us to enforce our expiration
  55. // logic to clean up ephemeral metrics if their value haven't been set for a
  56. // duration exceeding our allowed expiration time.
  57. func (p *PrometheusSink) Collect(c chan<- prometheus.Metric) {
  58. p.mu.Lock()
  59. defer p.mu.Unlock()
  60. expire := p.expiration != 0
  61. now := time.Now()
  62. for k, v := range p.gauges {
  63. last := p.updates[k]
  64. if expire && last.Add(p.expiration).Before(now) {
  65. delete(p.updates, k)
  66. delete(p.gauges, k)
  67. } else {
  68. v.Collect(c)
  69. }
  70. }
  71. for k, v := range p.summaries {
  72. last := p.updates[k]
  73. if expire && last.Add(p.expiration).Before(now) {
  74. delete(p.updates, k)
  75. delete(p.summaries, k)
  76. } else {
  77. v.Collect(c)
  78. }
  79. }
  80. for k, v := range p.counters {
  81. last := p.updates[k]
  82. if expire && last.Add(p.expiration).Before(now) {
  83. delete(p.updates, k)
  84. delete(p.counters, k)
  85. } else {
  86. v.Collect(c)
  87. }
  88. }
  89. }
  90. var forbiddenChars = regexp.MustCompile("[ .=\\-/]")
  91. func (p *PrometheusSink) flattenKey(parts []string, labels []metrics.Label) (string, string) {
  92. key := strings.Join(parts, "_")
  93. key = forbiddenChars.ReplaceAllString(key, "_")
  94. hash := key
  95. for _, label := range labels {
  96. hash += fmt.Sprintf(";%s=%s", label.Name, label.Value)
  97. }
  98. return key, hash
  99. }
  100. func prometheusLabels(labels []metrics.Label) prometheus.Labels {
  101. l := make(prometheus.Labels)
  102. for _, label := range labels {
  103. l[label.Name] = label.Value
  104. }
  105. return l
  106. }
  107. func (p *PrometheusSink) SetGauge(parts []string, val float32) {
  108. p.SetGaugeWithLabels(parts, val, nil)
  109. }
  110. func (p *PrometheusSink) SetGaugeWithLabels(parts []string, val float32, labels []metrics.Label) {
  111. p.mu.Lock()
  112. defer p.mu.Unlock()
  113. key, hash := p.flattenKey(parts, labels)
  114. g, ok := p.gauges[hash]
  115. if !ok {
  116. g = prometheus.NewGauge(prometheus.GaugeOpts{
  117. Name: key,
  118. Help: key,
  119. ConstLabels: prometheusLabels(labels),
  120. })
  121. p.gauges[hash] = g
  122. }
  123. g.Set(float64(val))
  124. p.updates[hash] = time.Now()
  125. }
  126. func (p *PrometheusSink) AddSample(parts []string, val float32) {
  127. p.AddSampleWithLabels(parts, val, nil)
  128. }
  129. func (p *PrometheusSink) AddSampleWithLabels(parts []string, val float32, labels []metrics.Label) {
  130. p.mu.Lock()
  131. defer p.mu.Unlock()
  132. key, hash := p.flattenKey(parts, labels)
  133. g, ok := p.summaries[hash]
  134. if !ok {
  135. g = prometheus.NewSummary(prometheus.SummaryOpts{
  136. Name: key,
  137. Help: key,
  138. MaxAge: 10 * time.Second,
  139. ConstLabels: prometheusLabels(labels),
  140. })
  141. p.summaries[hash] = g
  142. }
  143. g.Observe(float64(val))
  144. p.updates[hash] = time.Now()
  145. }
  146. // EmitKey is not implemented. Prometheus doesn’t offer a type for which an
  147. // arbitrary number of values is retained, as Prometheus works with a pull
  148. // model, rather than a push model.
  149. func (p *PrometheusSink) EmitKey(key []string, val float32) {
  150. }
  151. func (p *PrometheusSink) IncrCounter(parts []string, val float32) {
  152. p.IncrCounterWithLabels(parts, val, nil)
  153. }
  154. func (p *PrometheusSink) IncrCounterWithLabels(parts []string, val float32, labels []metrics.Label) {
  155. p.mu.Lock()
  156. defer p.mu.Unlock()
  157. key, hash := p.flattenKey(parts, labels)
  158. g, ok := p.counters[hash]
  159. if !ok {
  160. g = prometheus.NewCounter(prometheus.CounterOpts{
  161. Name: key,
  162. Help: key,
  163. ConstLabels: prometheusLabels(labels),
  164. })
  165. p.counters[hash] = g
  166. }
  167. g.Add(float64(val))
  168. p.updates[hash] = time.Now()
  169. }