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  }