You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

296 regels
8.0 KiB

  1. // Copyright 2017, OpenCensus Authors
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. // Package prometheus contains a Prometheus exporter that supports exporting
  15. // OpenCensus views as Prometheus metrics.
  16. package prometheus // import "go.opencensus.io/exporter/prometheus"
  17. import (
  18. "bytes"
  19. "fmt"
  20. "log"
  21. "net/http"
  22. "sync"
  23. "go.opencensus.io/internal"
  24. "go.opencensus.io/stats/view"
  25. "go.opencensus.io/tag"
  26. "github.com/prometheus/client_golang/prometheus"
  27. "github.com/prometheus/client_golang/prometheus/promhttp"
  28. )
  29. // Exporter exports stats to Prometheus, users need
  30. // to register the exporter as an http.Handler to be
  31. // able to export.
  32. type Exporter struct {
  33. opts Options
  34. g prometheus.Gatherer
  35. c *collector
  36. handler http.Handler
  37. }
  38. // Options contains options for configuring the exporter.
  39. type Options struct {
  40. Namespace string
  41. Registry *prometheus.Registry
  42. OnError func(err error)
  43. ConstLabels prometheus.Labels // ConstLabels will be set as labels on all views.
  44. }
  45. // NewExporter returns an exporter that exports stats to Prometheus.
  46. func NewExporter(o Options) (*Exporter, error) {
  47. if o.Registry == nil {
  48. o.Registry = prometheus.NewRegistry()
  49. }
  50. collector := newCollector(o, o.Registry)
  51. e := &Exporter{
  52. opts: o,
  53. g: o.Registry,
  54. c: collector,
  55. handler: promhttp.HandlerFor(o.Registry, promhttp.HandlerOpts{}),
  56. }
  57. return e, nil
  58. }
  59. var _ http.Handler = (*Exporter)(nil)
  60. var _ view.Exporter = (*Exporter)(nil)
  61. func (c *collector) registerViews(views ...*view.View) {
  62. count := 0
  63. for _, view := range views {
  64. sig := viewSignature(c.opts.Namespace, view)
  65. c.registeredViewsMu.Lock()
  66. _, ok := c.registeredViews[sig]
  67. c.registeredViewsMu.Unlock()
  68. if !ok {
  69. desc := prometheus.NewDesc(
  70. viewName(c.opts.Namespace, view),
  71. view.Description,
  72. tagKeysToLabels(view.TagKeys),
  73. c.opts.ConstLabels,
  74. )
  75. c.registeredViewsMu.Lock()
  76. c.registeredViews[sig] = desc
  77. c.registeredViewsMu.Unlock()
  78. count++
  79. }
  80. }
  81. if count == 0 {
  82. return
  83. }
  84. c.ensureRegisteredOnce()
  85. }
  86. // ensureRegisteredOnce invokes reg.Register on the collector itself
  87. // exactly once to ensure that we don't get errors such as
  88. // cannot register the collector: descriptor Desc{fqName: *}
  89. // already exists with the same fully-qualified name and const label values
  90. // which is documented by Prometheus at
  91. // https://github.com/prometheus/client_golang/blob/fcc130e101e76c5d303513d0e28f4b6d732845c7/prometheus/registry.go#L89-L101
  92. func (c *collector) ensureRegisteredOnce() {
  93. c.registerOnce.Do(func() {
  94. if err := c.reg.Register(c); err != nil {
  95. c.opts.onError(fmt.Errorf("cannot register the collector: %v", err))
  96. }
  97. })
  98. }
  99. func (o *Options) onError(err error) {
  100. if o.OnError != nil {
  101. o.OnError(err)
  102. } else {
  103. log.Printf("Failed to export to Prometheus: %v", err)
  104. }
  105. }
  106. // ExportView exports to the Prometheus if view data has one or more rows.
  107. // Each OpenCensus AggregationData will be converted to
  108. // corresponding Prometheus Metric: SumData will be converted
  109. // to Untyped Metric, CountData will be a Counter Metric,
  110. // DistributionData will be a Histogram Metric.
  111. func (e *Exporter) ExportView(vd *view.Data) {
  112. if len(vd.Rows) == 0 {
  113. return
  114. }
  115. e.c.addViewData(vd)
  116. }
  117. // ServeHTTP serves the Prometheus endpoint.
  118. func (e *Exporter) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  119. e.handler.ServeHTTP(w, r)
  120. }
  121. // collector implements prometheus.Collector
  122. type collector struct {
  123. opts Options
  124. mu sync.Mutex // mu guards all the fields.
  125. registerOnce sync.Once
  126. // reg helps collector register views dynamically.
  127. reg *prometheus.Registry
  128. // viewData are accumulated and atomically
  129. // appended to on every Export invocation, from
  130. // stats. These views are cleared out when
  131. // Collect is invoked and the cycle is repeated.
  132. viewData map[string]*view.Data
  133. registeredViewsMu sync.Mutex
  134. // registeredViews maps a view to a prometheus desc.
  135. registeredViews map[string]*prometheus.Desc
  136. }
  137. func (c *collector) addViewData(vd *view.Data) {
  138. c.registerViews(vd.View)
  139. sig := viewSignature(c.opts.Namespace, vd.View)
  140. c.mu.Lock()
  141. c.viewData[sig] = vd
  142. c.mu.Unlock()
  143. }
  144. func (c *collector) Describe(ch chan<- *prometheus.Desc) {
  145. c.registeredViewsMu.Lock()
  146. registered := make(map[string]*prometheus.Desc)
  147. for k, desc := range c.registeredViews {
  148. registered[k] = desc
  149. }
  150. c.registeredViewsMu.Unlock()
  151. for _, desc := range registered {
  152. ch <- desc
  153. }
  154. }
  155. // Collect fetches the statistics from OpenCensus
  156. // and delivers them as Prometheus Metrics.
  157. // Collect is invoked everytime a prometheus.Gatherer is run
  158. // for example when the HTTP endpoint is invoked by Prometheus.
  159. func (c *collector) Collect(ch chan<- prometheus.Metric) {
  160. // We need a copy of all the view data up until this point.
  161. viewData := c.cloneViewData()
  162. for _, vd := range viewData {
  163. sig := viewSignature(c.opts.Namespace, vd.View)
  164. c.registeredViewsMu.Lock()
  165. desc := c.registeredViews[sig]
  166. c.registeredViewsMu.Unlock()
  167. for _, row := range vd.Rows {
  168. metric, err := c.toMetric(desc, vd.View, row)
  169. if err != nil {
  170. c.opts.onError(err)
  171. } else {
  172. ch <- metric
  173. }
  174. }
  175. }
  176. }
  177. func (c *collector) toMetric(desc *prometheus.Desc, v *view.View, row *view.Row) (prometheus.Metric, error) {
  178. switch data := row.Data.(type) {
  179. case *view.CountData:
  180. return prometheus.NewConstMetric(desc, prometheus.CounterValue, float64(data.Value), tagValues(row.Tags, v.TagKeys)...)
  181. case *view.DistributionData:
  182. points := make(map[float64]uint64)
  183. // Histograms are cumulative in Prometheus.
  184. // Get cumulative bucket counts.
  185. cumCount := uint64(0)
  186. for i, b := range v.Aggregation.Buckets {
  187. cumCount += uint64(data.CountPerBucket[i])
  188. points[b] = cumCount
  189. }
  190. return prometheus.NewConstHistogram(desc, uint64(data.Count), data.Sum(), points, tagValues(row.Tags, v.TagKeys)...)
  191. case *view.SumData:
  192. return prometheus.NewConstMetric(desc, prometheus.UntypedValue, data.Value, tagValues(row.Tags, v.TagKeys)...)
  193. case *view.LastValueData:
  194. return prometheus.NewConstMetric(desc, prometheus.GaugeValue, data.Value, tagValues(row.Tags, v.TagKeys)...)
  195. default:
  196. return nil, fmt.Errorf("aggregation %T is not yet supported", v.Aggregation)
  197. }
  198. }
  199. func tagKeysToLabels(keys []tag.Key) (labels []string) {
  200. for _, key := range keys {
  201. labels = append(labels, internal.Sanitize(key.Name()))
  202. }
  203. return labels
  204. }
  205. func newCollector(opts Options, registrar *prometheus.Registry) *collector {
  206. return &collector{
  207. reg: registrar,
  208. opts: opts,
  209. registeredViews: make(map[string]*prometheus.Desc),
  210. viewData: make(map[string]*view.Data),
  211. }
  212. }
  213. func tagValues(t []tag.Tag, expectedKeys []tag.Key) []string {
  214. var values []string
  215. // Add empty string for all missing keys in the tags map.
  216. idx := 0
  217. for _, t := range t {
  218. for t.Key != expectedKeys[idx] {
  219. idx++
  220. values = append(values, "")
  221. }
  222. values = append(values, t.Value)
  223. idx++
  224. }
  225. for idx < len(expectedKeys) {
  226. idx++
  227. values = append(values, "")
  228. }
  229. return values
  230. }
  231. func viewName(namespace string, v *view.View) string {
  232. var name string
  233. if namespace != "" {
  234. name = namespace + "_"
  235. }
  236. return name + internal.Sanitize(v.Name)
  237. }
  238. func viewSignature(namespace string, v *view.View) string {
  239. var buf bytes.Buffer
  240. buf.WriteString(viewName(namespace, v))
  241. for _, k := range v.TagKeys {
  242. buf.WriteString("-" + k.Name())
  243. }
  244. return buf.String()
  245. }
  246. func (c *collector) cloneViewData() map[string]*view.Data {
  247. c.mu.Lock()
  248. defer c.mu.Unlock()
  249. viewDataCopy := make(map[string]*view.Data)
  250. for sig, viewData := range c.viewData {
  251. viewDataCopy[sig] = viewData
  252. }
  253. return viewDataCopy
  254. }