github.com/google/cloudprober@v0.11.3/metrics/eventmetrics.go (about) 1 // Copyright 2017 The Cloudprober Authors. 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 15 package metrics 16 17 import ( 18 "errors" 19 "fmt" 20 "strconv" 21 "strings" 22 "sync" 23 "time" 24 ) 25 26 // Kind represents EventMetrics type. There are currently only two kinds 27 // of EventMetrics supported: CUMULATIVE and GAUGE 28 type Kind int 29 30 const ( 31 // CUMULATIVE metrics accumulate with time and are usually used to 32 // represent counters, e.g. number of requests. 33 CUMULATIVE = iota 34 // GAUGE metrics are used to represent values at a certain point of 35 // time, e.g. pending queries. 36 GAUGE 37 ) 38 39 // EventMetrics respresents metrics associated with a particular time event. 40 type EventMetrics struct { 41 mu sync.RWMutex 42 Timestamp time.Time 43 Kind Kind 44 45 // Keys are metrics names 46 metrics map[string]Value 47 metricsKeys []string 48 49 // Labels are the labels associated with a particular set of metrics, 50 // e.g. ptype=ping, dst=google.com, etc. 51 labels map[string]string 52 labelsKeys []string 53 54 LatencyUnit time.Duration 55 } 56 57 // NewEventMetrics return a new EventMetrics object with internals maps initialized. 58 func NewEventMetrics(ts time.Time) *EventMetrics { 59 return &EventMetrics{ 60 Timestamp: ts, 61 metrics: make(map[string]Value), 62 labels: make(map[string]string), 63 } 64 } 65 66 // AddMetric adds a metric (name & value) into the receiver EventMetric. If a 67 // metric with the same name exists already, new metric is ignored. AddMetric 68 // returns the receiver EventMetrics to allow for the chaining of these calls, 69 // for example: 70 // em := metrics.NewEventMetrics(time.Now()). 71 // AddMetric("sent", &prr.sent). 72 // AddMetric("rcvd", &prr.rcvd). 73 // AddMetric("rtt", &prr.rtt) 74 func (em *EventMetrics) AddMetric(name string, val Value) *EventMetrics { 75 em.mu.Lock() 76 defer em.mu.Unlock() 77 78 if _, ok := em.metrics[name]; ok { 79 // TODO(manugarg): We should probably log such cases. We'll have to 80 // plumb logger for that. 81 return em 82 } 83 em.metrics[name] = val 84 em.metricsKeys = append(em.metricsKeys, name) 85 return em 86 } 87 88 // Metric returns an EventMetrics metric value by name. Metric will return nil 89 // for a non-existent metric. 90 func (em *EventMetrics) Metric(name string) Value { 91 em.mu.RLock() 92 defer em.mu.RUnlock() 93 return em.metrics[name] 94 } 95 96 // MetricsKeys returns the list of all metric keys. 97 func (em *EventMetrics) MetricsKeys() []string { 98 em.mu.RLock() 99 defer em.mu.RUnlock() 100 return append([]string{}, em.metricsKeys...) 101 } 102 103 // AddLabel adds a label (name & value) into the receiver EventMetrics. If a 104 // label with the same name exists already, new label is ignored. AddLabel 105 // returns the receiver EventMetrics to allow for the chaining of these calls, 106 // for example: 107 // em := metrics.NewEventMetrics(time.Now()). 108 // AddMetric("sent", &prr.sent). 109 // AddLabel("ptype", "http"). 110 // AddLabel("dst", target) 111 func (em *EventMetrics) AddLabel(name string, val string) *EventMetrics { 112 em.mu.Lock() 113 defer em.mu.Unlock() 114 if _, ok := em.labels[name]; ok { 115 // TODO(manugarg): We should probably log such cases. We'll have to 116 // plumb logger for that. 117 return em 118 } 119 em.labels[name] = val 120 em.labelsKeys = append(em.labelsKeys, name) 121 return em 122 } 123 124 // Label returns an EventMetrics label value by name. Label will return a 125 // zero-string ("") for a non-existent label. 126 func (em *EventMetrics) Label(name string) string { 127 em.mu.RLock() 128 defer em.mu.RUnlock() 129 return em.labels[name] 130 } 131 132 // LabelsKeys returns the list of all label keys. 133 func (em *EventMetrics) LabelsKeys() []string { 134 em.mu.RLock() 135 defer em.mu.RUnlock() 136 return append([]string{}, em.labelsKeys...) 137 } 138 139 // Clone clones the underlying fields. This is useful for creating copies of the EventMetrics objects. 140 func (em *EventMetrics) Clone() *EventMetrics { 141 em.mu.RLock() 142 defer em.mu.RUnlock() 143 newEM := &EventMetrics{ 144 Timestamp: em.Timestamp, 145 Kind: em.Kind, 146 metrics: make(map[string]Value), 147 labels: make(map[string]string), 148 } 149 for _, lk := range em.labelsKeys { 150 newEM.labels[lk] = em.labels[lk] 151 newEM.labelsKeys = append(newEM.labelsKeys, lk) 152 } 153 for _, mk := range em.metricsKeys { 154 newEM.metrics[mk] = em.metrics[mk].Clone() 155 newEM.metricsKeys = append(newEM.metricsKeys, mk) 156 } 157 return newEM 158 } 159 160 // Update updates the receiver EventMetrics with the incoming one. 161 func (em *EventMetrics) Update(in *EventMetrics) error { 162 if em.Kind != in.Kind { 163 return fmt.Errorf("EventMetrics of different kind cannot be merged. Receiver's kind: %d, incoming: %d", em.Kind, in.Kind) 164 } 165 switch em.Kind { 166 case GAUGE: 167 for name, newVal := range in.metrics { 168 _, ok := em.metrics[name] 169 if !ok { 170 return fmt.Errorf("receiver EventMetrics doesn't have %s metric", name) 171 } 172 em.metrics[name] = newVal.Clone() 173 } 174 return nil 175 case CUMULATIVE: 176 for name, newVal := range in.metrics { 177 val, ok := em.metrics[name] 178 if !ok { 179 return fmt.Errorf("receiver EventMetrics doesn't have %s metric", name) 180 } 181 val.Add(newVal) 182 } 183 return nil 184 default: 185 return errors.New("Unknown metrics kind") 186 } 187 } 188 189 // SubtractLast subtracts the provided (last) EventMetrics from the receiver 190 // EventMetrics and return the result as a GAUGE EventMetrics. 191 func (em *EventMetrics) SubtractLast(lastEM *EventMetrics) (*EventMetrics, error) { 192 if em.Kind != CUMULATIVE || lastEM.Kind != CUMULATIVE { 193 return nil, fmt.Errorf("incorrect eventmetrics kind (current: %v, last: %v), SubtractLast works only for CUMULATIVE metrics", em.Kind, lastEM.Kind) 194 } 195 196 gaugeEM := em.Clone() 197 gaugeEM.Kind = GAUGE 198 199 for name, lastVal := range lastEM.metrics { 200 val, ok := gaugeEM.metrics[name] 201 if !ok { 202 return nil, fmt.Errorf("receiver EventMetrics doesn't have %s metric", name) 203 } 204 wasReset, err := val.SubtractCounter(lastVal) 205 if err != nil { 206 return nil, err 207 } 208 209 // If any metric is reset, consider it a full reset of EventMetrics. 210 // TODO(manugarg): See if we can track this event somehow. 211 if wasReset { 212 gaugeEM := em.Clone() 213 gaugeEM.Kind = GAUGE 214 return gaugeEM, nil 215 } 216 } 217 218 return gaugeEM, nil 219 } 220 221 // String returns the string representation of the EventMetrics. 222 // Note that this is compatible with what vmwatcher understands. 223 // Example output string: 224 // 1519084040 labels=ptype=http sent=62 rcvd=52 resp-code=map:code,200:44,204:8 225 func (em *EventMetrics) String() string { 226 em.mu.RLock() 227 defer em.mu.RUnlock() 228 229 var b strings.Builder 230 b.Grow(128) 231 232 b.WriteString(strconv.FormatInt(em.Timestamp.Unix(), 10)) 233 // Labels section: labels=ptype=http,probe=homepage 234 b.WriteString(" labels=") 235 for i, key := range em.labelsKeys { 236 if i != 0 { 237 b.WriteByte(',') 238 } 239 b.WriteString(key) 240 b.WriteByte('=') 241 b.WriteString(em.labels[key]) 242 } 243 // Values section: " sent=62 rcvd=52 resp-code=map:code,200:44,204:8" 244 for _, name := range em.metricsKeys { 245 b.WriteByte(' ') 246 b.WriteString(name) 247 b.WriteByte('=') 248 b.WriteString(em.metrics[name].String()) 249 } 250 return b.String() 251 } 252 253 // Key returns a string key that uniquely identifies an eventmetrics. 254 func (em *EventMetrics) Key() string { 255 em.mu.RLock() 256 defer em.mu.RUnlock() 257 258 var keys []string 259 keys = append(keys, em.metricsKeys...) 260 for _, k := range em.LabelsKeys() { 261 keys = append(keys, k+"="+em.labels[k]) 262 } 263 return strings.Join(keys, ",") 264 }