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