|
| 1 | +package server |
| 2 | + |
| 3 | +import ( |
| 4 | + "strings" |
| 5 | + "sync" |
| 6 | + "time" |
| 7 | + "unicode" |
| 8 | + |
| 9 | + "github.com/sirupsen/logrus" |
| 10 | + "go.opencensus.io/stats" |
| 11 | + view "go.opencensus.io/stats/view" |
| 12 | + "go.opencensus.io/trace" |
| 13 | +) |
| 14 | + |
| 15 | +// Exporter exports stats to Prometheus, users need |
| 16 | +// to register the exporter as an http.Handler to be |
| 17 | +// able to export. |
| 18 | +type Converter struct { |
| 19 | + opts Options |
| 20 | + measures map[string]*stats.Float64Measure |
| 21 | + viewsMu sync.Mutex |
| 22 | + e view.Exporter |
| 23 | +} |
| 24 | + |
| 25 | +// Options contains options for configuring the exporter. |
| 26 | +type Options struct { |
| 27 | + Namespace string |
| 28 | + Exporter view.Exporter |
| 29 | +} |
| 30 | + |
| 31 | +func NewConverter(o Options) (*Converter, error) { |
| 32 | + e := &Converter{ |
| 33 | + opts: o, |
| 34 | + e: o.Exporter, |
| 35 | + measures: make(map[string]*stats.Float64Measure), |
| 36 | + } |
| 37 | + return e, nil |
| 38 | +} |
| 39 | + |
| 40 | +// ExportView exports to the Prometheus if view data has one or more rows. |
| 41 | +// Each OpenCensus AggregationData will be converted to |
| 42 | +// corresponding Prometheus Metric: SumData will be converted |
| 43 | +// to Untyped Metric, CountData will be a Counter Metric, |
| 44 | +// DistributionData will be a Histogram Metric. |
| 45 | +func (c *Converter) ExportSpan(sd *trace.SpanData) { |
| 46 | + m := c.getMeasure(sd) |
| 47 | + |
| 48 | + spanTimeNanos := sd.EndTime.Sub(sd.StartTime) |
| 49 | + spanTimeMillis := float64(int64(spanTimeNanos / time.Millisecond)) |
| 50 | + |
| 51 | + m.M(spanTimeMillis) |
| 52 | +} |
| 53 | + |
| 54 | +func (c *Converter) getMeasure(span *trace.SpanData) *stats.Float64Measure { |
| 55 | + sig := spanName(c.opts.Namespace, span) |
| 56 | + c.viewsMu.Lock() |
| 57 | + m, ok := c.measures[sig] |
| 58 | + c.viewsMu.Unlock() |
| 59 | + |
| 60 | + if !ok { |
| 61 | + logrus.Info("Creating Measure: %s", sig) |
| 62 | + m = stats.Float64("span length", "The span length in milliseconds", "ms") |
| 63 | + v := &view.View{ |
| 64 | + Name: spanName(c.opts.Namespace, span), |
| 65 | + Description: spanName(c.opts.Namespace, span), |
| 66 | + Measure: m, |
| 67 | + Aggregation: view.Distribution(0, 1<<32, 2<<32, 3<<32), |
| 68 | + } |
| 69 | + // Buckets: []float64{1, |
| 70 | + // 10, |
| 71 | + // 50, |
| 72 | + // 100, |
| 73 | + // 250, |
| 74 | + // 500, |
| 75 | + // 1000, |
| 76 | + // 10000, |
| 77 | + // 60000, |
| 78 | + // 120000}, |
| 79 | + c.viewsMu.Lock() |
| 80 | + c.measures[sig] = m |
| 81 | + view.Register(v) |
| 82 | + c.viewsMu.Unlock() |
| 83 | + } |
| 84 | + |
| 85 | + return m |
| 86 | +} |
| 87 | + |
| 88 | +func spanName(namespace string, s *trace.SpanData) string { |
| 89 | + var name string |
| 90 | + if namespace != "" { |
| 91 | + name = namespace + "_" |
| 92 | + } |
| 93 | + return name + sanitize(s.Name) |
| 94 | +} |
| 95 | + |
| 96 | +const labelKeySizeLimit = 100 |
| 97 | + |
| 98 | +// sanitize returns a string that is trunacated to 100 characters if it's too |
| 99 | +// long, and replaces non-alphanumeric characters to underscores. |
| 100 | +func sanitize(s string) string { |
| 101 | + if len(s) == 0 { |
| 102 | + return s |
| 103 | + } |
| 104 | + if len(s) > labelKeySizeLimit { |
| 105 | + s = s[:labelKeySizeLimit] |
| 106 | + } |
| 107 | + s = strings.Map(sanitizeRune, s) |
| 108 | + if unicode.IsDigit(rune(s[0])) { |
| 109 | + s = "key_" + s |
| 110 | + } |
| 111 | + if s[0] == '_' { |
| 112 | + s = "key" + s |
| 113 | + } |
| 114 | + return s |
| 115 | +} |
| 116 | + |
| 117 | +// converts anything that is not a letter or digit to an underscore |
| 118 | +func sanitizeRune(r rune) rune { |
| 119 | + if unicode.IsLetter(r) || unicode.IsDigit(r) { |
| 120 | + return r |
| 121 | + } |
| 122 | + // Everything else turns into an underscore |
| 123 | + return '_' |
| 124 | +} |
| 125 | + |
| 126 | +//Gin creates spans for all paths, containing ID values. |
| 127 | +//We can safely discard these, as other histograms are being created for them. |
| 128 | +func urlName(s string) bool { |
| 129 | + return strings.HasPrefix(s, "/") |
| 130 | +} |
0 commit comments