1// Copyright 2019 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package prometheus
6
7import (
8	"bytes"
9	"context"
10	"fmt"
11	"net/http"
12	"sort"
13	"sync"
14
15	"golang.org/x/tools/internal/telemetry"
16	"golang.org/x/tools/internal/telemetry/metric"
17)
18
19func New() *Exporter {
20	return &Exporter{}
21}
22
23type Exporter struct {
24	mu      sync.Mutex
25	metrics []telemetry.MetricData
26}
27
28func (e *Exporter) StartSpan(ctx context.Context, span *telemetry.Span)  {}
29func (e *Exporter) FinishSpan(ctx context.Context, span *telemetry.Span) {}
30func (e *Exporter) Log(ctx context.Context, event telemetry.Event)       {}
31func (e *Exporter) Flush()                                               {}
32
33func (e *Exporter) Metric(ctx context.Context, data telemetry.MetricData) {
34	e.mu.Lock()
35	defer e.mu.Unlock()
36	name := data.Handle()
37	// We keep the metrics in name sorted order so the page is stable and easy
38	// to read. We do this with an insertion sort rather than sorting the list
39	// each time
40	index := sort.Search(len(e.metrics), func(i int) bool {
41		return e.metrics[i].Handle() >= name
42	})
43	if index >= len(e.metrics) || e.metrics[index].Handle() != name {
44		// we have a new metric, so we need to make a space for it
45		old := e.metrics
46		e.metrics = make([]telemetry.MetricData, len(old)+1)
47		copy(e.metrics, old[:index])
48		copy(e.metrics[index+1:], old[index:])
49	}
50	e.metrics[index] = data
51}
52
53func (e *Exporter) header(w http.ResponseWriter, name, description string, isGauge, isHistogram bool) {
54	kind := "counter"
55	if isGauge {
56		kind = "gauge"
57	}
58	if isHistogram {
59		kind = "histogram"
60	}
61	fmt.Fprintf(w, "# HELP %s %s\n", name, description)
62	fmt.Fprintf(w, "# TYPE %s %s\n", name, kind)
63}
64
65func (e *Exporter) row(w http.ResponseWriter, name string, group telemetry.TagList, extra string, value interface{}) {
66	fmt.Fprint(w, name)
67	buf := &bytes.Buffer{}
68	fmt.Fprint(buf, group)
69	if extra != "" {
70		if buf.Len() > 0 {
71			fmt.Fprint(buf, ",")
72		}
73		fmt.Fprint(buf, extra)
74	}
75	if buf.Len() > 0 {
76		fmt.Fprint(w, "{")
77		buf.WriteTo(w)
78		fmt.Fprint(w, "}")
79	}
80	fmt.Fprintf(w, " %v\n", value)
81}
82
83func (e *Exporter) Serve(w http.ResponseWriter, r *http.Request) {
84	e.mu.Lock()
85	defer e.mu.Unlock()
86	for _, data := range e.metrics {
87		switch data := data.(type) {
88		case *metric.Int64Data:
89			e.header(w, data.Info.Name, data.Info.Description, data.IsGauge, false)
90			for i, group := range data.Groups() {
91				e.row(w, data.Info.Name, group, "", data.Rows[i])
92			}
93
94		case *metric.Float64Data:
95			e.header(w, data.Info.Name, data.Info.Description, data.IsGauge, false)
96			for i, group := range data.Groups() {
97				e.row(w, data.Info.Name, group, "", data.Rows[i])
98			}
99
100		case *metric.HistogramInt64Data:
101			e.header(w, data.Info.Name, data.Info.Description, false, true)
102			for i, group := range data.Groups() {
103				row := data.Rows[i]
104				for j, b := range data.Info.Buckets {
105					e.row(w, data.Info.Name+"_bucket", group, fmt.Sprintf(`le="%v"`, b), row.Values[j])
106				}
107				e.row(w, data.Info.Name+"_bucket", group, `le="+Inf"`, row.Count)
108				e.row(w, data.Info.Name+"_count", group, "", row.Count)
109				e.row(w, data.Info.Name+"_sum", group, "", row.Sum)
110			}
111
112		case *metric.HistogramFloat64Data:
113			e.header(w, data.Info.Name, data.Info.Description, false, true)
114			for i, group := range data.Groups() {
115				row := data.Rows[i]
116				for j, b := range data.Info.Buckets {
117					e.row(w, data.Info.Name+"_bucket", group, fmt.Sprintf(`le="%v"`, b), row.Values[j])
118				}
119				e.row(w, data.Info.Name+"_bucket", group, `le="+Inf"`, row.Count)
120				e.row(w, data.Info.Name+"_count", group, "", row.Count)
121				e.row(w, data.Info.Name+"_sum", group, "", row.Sum)
122			}
123		}
124	}
125}
126