github.com/msales/pkg/v3@v3.24.0/stats/stats.go (about) 1 package stats 2 3 import ( 4 "context" 5 "io" 6 "time" 7 ) 8 9 type key int 10 11 const ( 12 ctxKey key = iota 13 ) 14 15 var ( 16 // Null is the null Stats instance. 17 Null = &nullStats{} 18 ) 19 20 // Stats represents a stats instance. 21 type Stats interface { 22 io.Closer 23 24 // Inc increments a count by the value. 25 Inc(name string, value int64, rate float32, tags ...interface{}) error 26 27 // Dec decrements a count by the value. 28 Dec(name string, value int64, rate float32, tags ...interface{}) error 29 30 // Gauge measures the value of a metric. 31 Gauge(name string, value float64, rate float32, tags ...interface{}) error 32 33 // Timing sends the value of a Duration. 34 Timing(name string, value time.Duration, rate float32, tags ...interface{}) error 35 } 36 37 // WithStats sets Stats in the context. 38 func WithStats(ctx context.Context, stats Stats) context.Context { 39 if stats == nil { 40 stats = Null 41 } 42 return context.WithValue(ctx, ctxKey, stats) 43 } 44 45 // FromContext returns the instance of Stats in the context. 46 func FromContext(ctx context.Context) (Stats, bool) { 47 stats, ok := ctx.Value(ctxKey).(Stats) 48 return stats, ok 49 } 50 51 // Inc increments a count by the value. 52 func Inc(ctx context.Context, name string, value int64, rate float32, tags ...interface{}) error { 53 return withStats(ctx, func(s Stats) error { 54 return s.Inc(name, value, rate, tags...) 55 }) 56 } 57 58 // Dec decrements a count by the value. 59 func Dec(ctx context.Context, name string, value int64, rate float32, tags ...interface{}) error { 60 return withStats(ctx, func(s Stats) error { 61 return s.Dec(name, value, rate, tags...) 62 }) 63 } 64 65 // Gauge measures the value of a metric. 66 func Gauge(ctx context.Context, name string, value float64, rate float32, tags ...interface{}) error { 67 return withStats(ctx, func(s Stats) error { 68 return s.Gauge(name, value, rate, tags...) 69 }) 70 } 71 72 // Timing sends the value of a Duration. 73 func Timing(ctx context.Context, name string, value time.Duration, rate float32, tags ...interface{}) error { 74 return withStats(ctx, func(s Stats) error { 75 return s.Timing(name, value, rate, tags...) 76 }) 77 } 78 79 // Close closes the client and flushes buffered stats, if applicable 80 func Close(ctx context.Context) error { 81 return withStats(ctx, func(s Stats) error { 82 return s.Close() 83 }) 84 } 85 86 func withStats(ctx context.Context, fn func(s Stats) error) error { 87 if s, ok := FromContext(ctx); ok { 88 return fn(s) 89 } 90 return fn(Null) 91 } 92 93 type nullStats struct{} 94 95 func (s nullStats) Inc(name string, value int64, rate float32, tags ...interface{}) error { 96 return nil 97 } 98 99 func (s nullStats) Dec(name string, value int64, rate float32, tags ...interface{}) error { 100 return nil 101 } 102 103 func (s nullStats) Gauge(name string, value float64, rate float32, tags ...interface{}) error { 104 return nil 105 } 106 107 func (s nullStats) Timing(name string, value time.Duration, rate float32, tags ...interface{}) error { 108 return nil 109 } 110 111 func (s nullStats) Close() error { 112 return nil 113 } 114 115 // TaggedStats wraps a Stats instance applying tags to all metrics. 116 type TaggedStats struct { 117 stats Stats 118 tags []interface{} 119 } 120 121 // NewTaggedStats creates a new TaggedStats instance. 122 func NewTaggedStats(stats Stats, tags ...interface{}) *TaggedStats { 123 if t, ok := stats.(*TaggedStats); ok { 124 stats = t.stats 125 tags = append(t.tags, tags...) 126 } 127 128 return &TaggedStats{ 129 stats: stats, 130 tags: normalizeTags(tags), 131 } 132 } 133 134 // Inc increments a count by the value. 135 func (s TaggedStats) Inc(name string, value int64, rate float32, tags ...interface{}) error { 136 return s.stats.Inc(name, value, rate, mergeTags(tags, s.tags)...) 137 } 138 139 // Dec decrements a count by the value. 140 func (s TaggedStats) Dec(name string, value int64, rate float32, tags ...interface{}) error { 141 return s.stats.Dec(name, value, rate, mergeTags(tags, s.tags)...) 142 } 143 144 // Gauge measures the value of a metric. 145 func (s TaggedStats) Gauge(name string, value float64, rate float32, tags ...interface{}) error { 146 return s.stats.Gauge(name, value, rate, mergeTags(tags, s.tags)...) 147 } 148 149 // Timing sends the value of a Duration. 150 func (s TaggedStats) Timing(name string, value time.Duration, rate float32, tags ...interface{}) error { 151 return s.stats.Timing(name, value, rate, mergeTags(tags, s.tags)...) 152 } 153 154 // Close closes the client and flushes buffered stats, if applicable 155 func (s TaggedStats) Close() error { 156 return s.stats.Close() 157 } 158 159 func normalizeTags(tags []interface{}) []interface{} { 160 // If Tags object was passed, then expand it 161 if len(tags) == 1 { 162 if ctxMap, ok := tags[0].(Tags); ok { 163 tags = ctxMap.toArray() 164 } 165 } 166 167 // tags need to be even as they are key/value pairs 168 if len(tags)%2 != 0 { 169 tags = append(tags, nil, "STATS_ERROR", "Normalised odd number of tags by adding nil") 170 } 171 172 return tags 173 } 174 175 func mergeTags(prefix, suffix []interface{}) []interface{} { 176 newTags := make([]interface{}, len(prefix)+len(suffix)) 177 178 n := copy(newTags, prefix) 179 copy(newTags[n:], suffix) 180 181 return newTags 182 } 183 184 func deduplicateTags(tags []interface{}) []interface{} { 185 for i := 0; i < len(tags); i += 2 { 186 for j := i + 2; j < len(tags); j += 2 { 187 if tags[i] == tags[j] { 188 tags[i+1] = tags[j+1] 189 tags = append(tags[:j], tags[j+2:]...) 190 j -= 2 191 } 192 } 193 } 194 195 return tags 196 }