blob: 0df0c662e367e2da8d1cd074d809a863c95bb619 [file] [log] [blame]
Matteo Scandoloa4285862020-12-01 18:10:10 -08001// Copyright (c) 2017 Uber Technologies, Inc.
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
15package metrics
16
17import (
18 "fmt"
19 "reflect"
20 "strconv"
21 "strings"
22)
23
24// MustInit initializes the passed in metrics and initializes its fields using the passed in factory.
25//
26// It uses reflection to initialize a struct containing metrics fields
27// by assigning new Counter/Gauge/Timer values with the metric name retrieved
28// from the `metric` tag and stats tags retrieved from the `tags` tag.
29//
30// Note: all fields of the struct must be exported, have a `metric` tag, and be
31// of type Counter or Gauge or Timer.
32//
33// Errors during Init lead to a panic.
34func MustInit(metrics interface{}, factory Factory, globalTags map[string]string) {
35 if err := Init(metrics, factory, globalTags); err != nil {
36 panic(err.Error())
37 }
38}
39
40// Init does the same as MustInit, but returns an error instead of
41// panicking.
42func Init(m interface{}, factory Factory, globalTags map[string]string) error {
43 // Allow user to opt out of reporting metrics by passing in nil.
44 if factory == nil {
45 factory = NullFactory
46 }
47
48 counterPtrType := reflect.TypeOf((*Counter)(nil)).Elem()
49 gaugePtrType := reflect.TypeOf((*Gauge)(nil)).Elem()
50 timerPtrType := reflect.TypeOf((*Timer)(nil)).Elem()
51 histogramPtrType := reflect.TypeOf((*Histogram)(nil)).Elem()
52
53 v := reflect.ValueOf(m).Elem()
54 t := v.Type()
55 for i := 0; i < t.NumField(); i++ {
56 tags := make(map[string]string)
57 for k, v := range globalTags {
58 tags[k] = v
59 }
60 var buckets []float64
61 field := t.Field(i)
62 metric := field.Tag.Get("metric")
63 if metric == "" {
64 return fmt.Errorf("Field %s is missing a tag 'metric'", field.Name)
65 }
66 if tagString := field.Tag.Get("tags"); tagString != "" {
67 tagPairs := strings.Split(tagString, ",")
68 for _, tagPair := range tagPairs {
69 tag := strings.Split(tagPair, "=")
70 if len(tag) != 2 {
71 return fmt.Errorf(
72 "Field [%s]: Tag [%s] is not of the form key=value in 'tags' string [%s]",
73 field.Name, tagPair, tagString)
74 }
75 tags[tag[0]] = tag[1]
76 }
77 }
78 if bucketString := field.Tag.Get("buckets"); bucketString != "" {
79 if field.Type.AssignableTo(timerPtrType) {
80 // TODO: Parse timer duration buckets
81 return fmt.Errorf(
82 "Field [%s]: Buckets are not currently initialized for timer metrics",
83 field.Name)
84 } else if field.Type.AssignableTo(histogramPtrType) {
85 bucketValues := strings.Split(bucketString, ",")
86 for _, bucket := range bucketValues {
87 b, err := strconv.ParseFloat(bucket, 64)
88 if err != nil {
89 return fmt.Errorf(
90 "Field [%s]: Bucket [%s] could not be converted to float64 in 'buckets' string [%s]",
91 field.Name, bucket, bucketString)
92 }
93 buckets = append(buckets, b)
94 }
95 } else {
96 return fmt.Errorf(
97 "Field [%s]: Buckets should only be defined for Timer and Histogram metric types",
98 field.Name)
99 }
100 }
101 help := field.Tag.Get("help")
102 var obj interface{}
103 if field.Type.AssignableTo(counterPtrType) {
104 obj = factory.Counter(Options{
105 Name: metric,
106 Tags: tags,
107 Help: help,
108 })
109 } else if field.Type.AssignableTo(gaugePtrType) {
110 obj = factory.Gauge(Options{
111 Name: metric,
112 Tags: tags,
113 Help: help,
114 })
115 } else if field.Type.AssignableTo(timerPtrType) {
116 // TODO: Add buckets once parsed (see TODO above)
117 obj = factory.Timer(TimerOptions{
118 Name: metric,
119 Tags: tags,
120 Help: help,
121 })
122 } else if field.Type.AssignableTo(histogramPtrType) {
123 obj = factory.Histogram(HistogramOptions{
124 Name: metric,
125 Tags: tags,
126 Help: help,
127 Buckets: buckets,
128 })
129 } else {
130 return fmt.Errorf(
131 "Field %s is not a pointer to timer, gauge, or counter",
132 field.Name)
133 }
134 v.Field(i).Set(reflect.ValueOf(obj))
135 }
136 return nil
137}