github.com/GoogleCloudPlatform/testgrid@v0.0.174/util/metrics/logmetrics/log.go (about) 1 /* 2 Copyright 2021 The TestGrid Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package logmetrics 18 19 import ( 20 "context" 21 "fmt" 22 "strconv" 23 "sync" 24 "time" 25 26 "bitbucket.org/creachadair/stringset" 27 "github.com/GoogleCloudPlatform/testgrid/util/metrics" 28 "github.com/sirupsen/logrus" 29 ) 30 31 // Valuer extends a metric to include a report on its values. 32 type Valuer interface { 33 metrics.Metric 34 Values() map[string]map[string]interface{} 35 } 36 37 // Reporter is a collection of metric values to report. 38 type Reporter []Valuer 39 40 // ReportNow reports all metrics once, immediately 41 func (r *Reporter) ReportNow(log logrus.FieldLogger) { 42 if log == nil { 43 log = logrus.New() 44 } 45 for _, metric := range *r { 46 log := log.WithField("metric", metric.Name()) 47 for field, values := range metric.Values() { 48 log := log.WithField("field", field) 49 for value, measurement := range values { 50 log = log.WithField(value, measurement) 51 } 52 log.Info("Current status") 53 } 54 } 55 } 56 57 // Report the status of its metrics every freq until the context expires. 58 func (r *Reporter) Report(ctx context.Context, log logrus.FieldLogger, freq time.Duration) error { 59 if log == nil { 60 log = logrus.New() 61 } 62 ticker := time.NewTicker(freq) 63 defer ticker.Stop() 64 names := stringset.NewSize(len(*r)) 65 metricMap := make(map[string]int, len(*r)) 66 for i, m := range *r { 67 name := m.Name() 68 names.Add(name) 69 metricMap[name] = i 70 } 71 for { 72 select { 73 case <-ticker.C: 74 case <-ctx.Done(): 75 return ctx.Err() 76 } 77 78 for _, name := range names.Elements() { 79 i := metricMap[name] 80 metric := (*r)[i] 81 log := log.WithField("metric", metric.Name()) 82 for field, values := range metric.Values() { 83 log := log.WithField("field", field) 84 for value, measurement := range values { 85 log = log.WithField(value, measurement) 86 } 87 log.Info("Current status") 88 } 89 } 90 } 91 } 92 93 // Int64 configures a new Int64 metric to report. 94 func (r *Reporter) Int64(name, desc string, log logrus.FieldLogger, fields ...string) metrics.Int64 { 95 current := make([]map[string][]int64, len(fields)) 96 for i := range fields { 97 current[i] = make(map[string][]int64, 1) 98 } 99 100 out := &logInt64{ 101 name: name, 102 desc: desc, 103 log: log, 104 fields: fields, 105 current: current, 106 } 107 *r = append(*r, out) 108 return out 109 } 110 111 // Counter configures a new Counter metric to report 112 func (r *Reporter) Counter(name, desc string, log logrus.FieldLogger, fields ...string) metrics.Counter { 113 current := make([]map[string]int64, len(fields)) 114 previous := make([]map[string]int64, len(fields)) 115 for i := range fields { 116 current[i] = make(map[string]int64, 1) 117 previous[i] = make(map[string]int64, 1) 118 } 119 120 out := &logCounter{ 121 name: name, 122 desc: desc, 123 log: log, 124 fields: fields, 125 current: current, 126 previous: previous, 127 last: time.Now(), 128 } 129 *r = append(*r, out) 130 return out 131 } 132 133 type logInt64 struct { 134 name string 135 desc string 136 fields []string 137 log logrus.FieldLogger 138 current []map[string][]int64 139 lock sync.Mutex 140 } 141 142 // Name returns the metric's name. 143 func (m *logInt64) Name() string { 144 return m.name 145 } 146 147 // Set the metric's value to the given number. 148 func (m *logInt64) Set(n int64, fields ...string) { 149 m.lock.Lock() 150 defer m.lock.Unlock() 151 if len(fields) != len(m.fields) { 152 m.log.WithFields(logrus.Fields{ 153 "want": m.fields, 154 "got": fields, 155 }).Fatal("Wrong number of fields") 156 } 157 for i, fieldValue := range fields { 158 m.current[i][fieldValue] = append(m.current[i][fieldValue], n) 159 } 160 } 161 162 func (m *logInt64) Values() map[string]map[string]interface{} { 163 m.lock.Lock() 164 defer m.lock.Unlock() 165 166 trend := make(map[string]map[string]interface{}, len(m.current)) 167 for i, field := range m.fields { 168 current := m.current[i] 169 if len(current) == 0 { 170 continue 171 } 172 trend[field] = make(map[string]interface{}, len(current)) 173 for fieldValue, measurements := range current { 174 175 trend[field][fieldValue] = mean{measurements} 176 delete(current, fieldValue) 177 } 178 179 } 180 return trend 181 } 182 183 type mean struct { 184 values []int64 185 } 186 187 func (m mean) String() string { 188 tot := len(m.values) 189 switch tot { 190 case 0: 191 return "0 values" 192 case 1: 193 return strconv.FormatInt(m.values[0], 10) 194 } 195 var val float64 196 n := float64(tot) 197 for _, v := range m.values { 198 val += (float64(v) / n) 199 } 200 return fmt.Sprintf("%s average (%d values)", strconv.FormatFloat(val, 'g', 3, 64), tot) 201 } 202 203 type logCounter struct { 204 name string 205 desc string 206 fields []string 207 log logrus.FieldLogger 208 current []map[string]int64 209 previous []map[string]int64 210 last time.Time 211 lock sync.Mutex 212 } 213 214 // Name returns the metric's name. 215 func (m *logCounter) Name() string { 216 return m.name 217 } 218 219 // Add the given number to the counter. 220 func (m *logCounter) Add(n int64, fields ...string) { 221 m.lock.Lock() 222 defer m.lock.Unlock() 223 if len(fields) != len(m.fields) { 224 m.log.WithFields(logrus.Fields{ 225 "want": m.fields, 226 "got": fields, 227 }).Fatal("Wrong number of fields") 228 } 229 if n < 0 { 230 m.log.WithField("value", n).Fatal("Negative value are invalid") 231 } 232 for i, fieldValue := range fields { 233 m.current[i][fieldValue] += n 234 } 235 } 236 237 func (m *logCounter) Values() map[string]map[string]interface{} { 238 m.lock.Lock() 239 defer m.lock.Unlock() 240 241 dur := time.Since(m.last) 242 m.last = time.Now() 243 rate := make(map[string]map[string]interface{}, len(m.current)) 244 for i, field := range m.fields { 245 current := m.current[i] 246 previous := m.previous[i] 247 rate[field] = make(map[string]interface{}, len(current)) 248 for fieldValue, now := range current { 249 delta := now - previous[fieldValue] 250 rate[field][fieldValue] = gauge{now, delta, dur} 251 previous[fieldValue] = now 252 } 253 } 254 return rate 255 } 256 257 type gauge struct { 258 total int64 259 delta int64 260 dur time.Duration 261 } 262 263 func (g gauge) qps() string { 264 s := g.dur.Seconds() 265 if s == 0 { 266 return "0 per second" 267 } 268 qps := float64(g.delta) / s 269 if qps == 0 { 270 return "0 per second" 271 } 272 if qps > 0.5 { 273 return fmt.Sprintf("%.2f per second", qps) 274 } 275 seconds := time.Duration(1 / qps * float64(time.Second)) 276 seconds = seconds.Round(time.Millisecond) 277 return fmt.Sprintf("once per %s", seconds) 278 } 279 280 func (g gauge) String() string { 281 return fmt.Sprintf("%s (%d total)", g.qps(), g.total) 282 }