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