github.com/msales/pkg/v3@v3.24.0/stats/l2met.go (about) 1 package stats 2 3 import ( 4 "math/rand" 5 "time" 6 7 "github.com/msales/pkg/v3/bytes" 8 "github.com/msales/pkg/v3/log" 9 ) 10 11 // SamplerFunc represents a function that samples the L2met stats. 12 type SamplerFunc func(float32) bool 13 14 func defaultSampler(rate float32) bool { 15 if rand.Float32() < rate { 16 return true 17 } 18 return false 19 } 20 21 // L2metFunc represents a function that configures L2met. 22 type L2metFunc func(*L2met) 23 24 // UseRates turns on sample rates in l2met. 25 func UseRates() L2metFunc { 26 return func(s *L2met) { 27 s.useRates = true 28 } 29 } 30 31 // UseSampler sets the sampler for l2met. 32 func UseSampler(sampler SamplerFunc) L2metFunc { 33 return func(s *L2met) { 34 s.sampler = sampler 35 } 36 } 37 38 // L2met represents a l2met client. 39 type L2met struct { 40 log log.Logger 41 prefix string 42 43 useRates bool 44 sampler SamplerFunc 45 } 46 47 // NewL2met create a l2met instance. 48 func NewL2met(l log.Logger, prefix string, opts ...L2metFunc) Stats { 49 if len(prefix) > 0 { 50 prefix += "." 51 } 52 53 s := &L2met{ 54 log: l, 55 prefix: prefix, 56 sampler: defaultSampler, 57 } 58 59 for _, opt := range opts { 60 opt(s) 61 } 62 63 return s 64 } 65 66 // Inc increments a count by the value. 67 func (s *L2met) Inc(name string, value int64, rate float32, tags ...interface{}) error { 68 s.render( 69 "count", 70 name, 71 value, 72 rate, 73 tags, 74 ) 75 76 return nil 77 } 78 79 // Dec decrements a count by the value. 80 func (s *L2met) Dec(name string, value int64, rate float32, tags ...interface{}) error { 81 s.render( 82 "count", 83 name, 84 value*-1, 85 rate, 86 tags, 87 ) 88 89 return nil 90 } 91 92 // Gauge measures the value of a metric. 93 func (s *L2met) Gauge(name string, value float64, rate float32, tags ...interface{}) error { 94 s.render( 95 "sample", 96 name, 97 value, 98 rate, 99 tags, 100 ) 101 102 return nil 103 } 104 105 // Timing sends the value of a Duration. 106 func (s *L2met) Timing(name string, value time.Duration, rate float32, tags ...interface{}) error { 107 s.render( 108 "measure", 109 name, 110 formatDuration(value), 111 rate, 112 tags, 113 ) 114 115 return nil 116 } 117 118 // render outputs the metric to the logger 119 func (s *L2met) render(measure, name string, value interface{}, rate float32, tags []interface{}) { 120 if !s.includeStat(rate) { 121 return 122 } 123 124 tags = deduplicateTags(normalizeTags(tags)) 125 126 ctx := make([]interface{}, len(tags)+2) 127 ctx[0] = measure + "#" + s.prefix + name + s.formatL2metRate(rate) 128 ctx[1] = value 129 copy(ctx[2:], tags) 130 131 s.log.Info("", ctx...) 132 } 133 134 func (s *L2met) includeStat(rate float32) bool { 135 if !s.useRates || rate == 1.0 { 136 return true 137 } 138 139 return s.sampler(rate) 140 } 141 142 // Close closes the client and flushes buffered stats, if applicable 143 func (s *L2met) Close() error { 144 return nil 145 } 146 147 var l2metPool = bytes.NewPool(100) 148 149 // formatL2metKey creates an l2met compatible rate suffix. 150 func (s *L2met) formatL2metRate(rate float32) string { 151 if !s.useRates || rate == 1.0 { 152 return "" 153 } 154 155 buf := l2metPool.Get() 156 buf.WriteByte('@') 157 buf.AppendFloat(float64(rate), 'f', -1, 32) 158 res := string(buf.Bytes()) 159 l2metPool.Put(buf) 160 161 return res 162 } 163 164 // formatDuration converts duration into fractional milliseconds 165 // with no trailing zeros. 166 func formatDuration(d time.Duration) string { 167 buf := l2metPool.Get() 168 buf.AppendUint(uint64(d / time.Millisecond)) 169 170 p := uint64(d % time.Millisecond) 171 if p > 0 { 172 om := 0 173 m := uint64(100000) 174 for p < m { 175 om++ 176 m /= 10 177 } 178 179 for { 180 if p%10 == 0 { 181 p /= 10 182 continue 183 } 184 break 185 } 186 187 buf.WriteByte('.') 188 189 for om > 0 { 190 buf.WriteByte('0') 191 om-- 192 } 193 194 buf.AppendUint(p) 195 } 196 197 buf.WriteString("ms") 198 res := string(buf.Bytes()) 199 l2metPool.Put(buf) 200 201 return res 202 }