github.com/hashicorp/go-metrics@v0.5.3/metrics.go (about)

     1  package metrics
     2  
     3  import (
     4  	"runtime"
     5  	"strings"
     6  	"time"
     7  
     8  	iradix "github.com/hashicorp/go-immutable-radix"
     9  )
    10  
    11  type Label struct {
    12  	Name  string
    13  	Value string
    14  }
    15  
    16  func (m *Metrics) SetGauge(key []string, val float32) {
    17  	m.SetGaugeWithLabels(key, val, nil)
    18  }
    19  
    20  func (m *Metrics) SetGaugeWithLabels(key []string, val float32, labels []Label) {
    21  	if m.HostName != "" {
    22  		if m.EnableHostnameLabel {
    23  			labels = append(labels, Label{"host", m.HostName})
    24  		} else if m.EnableHostname {
    25  			key = insert(0, m.HostName, key)
    26  		}
    27  	}
    28  	if m.EnableTypePrefix {
    29  		key = insert(0, "gauge", key)
    30  	}
    31  	if m.ServiceName != "" {
    32  		if m.EnableServiceLabel {
    33  			labels = append(labels, Label{"service", m.ServiceName})
    34  		} else {
    35  			key = insert(0, m.ServiceName, key)
    36  		}
    37  	}
    38  	allowed, labelsFiltered := m.allowMetric(key, labels)
    39  	if !allowed {
    40  		return
    41  	}
    42  	m.sink.SetGaugeWithLabels(key, val, labelsFiltered)
    43  }
    44  
    45  func (m *Metrics) SetPrecisionGauge(key []string, val float64) {
    46  	m.SetPrecisionGaugeWithLabels(key, val, nil)
    47  }
    48  
    49  func (m *Metrics) SetPrecisionGaugeWithLabels(key []string, val float64, labels []Label) {
    50  	if m.HostName != "" {
    51  		if m.EnableHostnameLabel {
    52  			labels = append(labels, Label{"host", m.HostName})
    53  		} else if m.EnableHostname {
    54  			key = insert(0, m.HostName, key)
    55  		}
    56  	}
    57  	if m.EnableTypePrefix {
    58  		key = insert(0, "gauge", key)
    59  	}
    60  	if m.ServiceName != "" {
    61  		if m.EnableServiceLabel {
    62  			labels = append(labels, Label{"service", m.ServiceName})
    63  		} else {
    64  			key = insert(0, m.ServiceName, key)
    65  		}
    66  	}
    67  	allowed, labelsFiltered := m.allowMetric(key, labels)
    68  	if !allowed {
    69  		return
    70  	}
    71  	sink, ok := m.sink.(PrecisionGaugeMetricSink)
    72  	if !ok {
    73  		// Sink does not implement PrecisionGaugeMetricSink.
    74  	} else {
    75  		sink.SetPrecisionGaugeWithLabels(key, val, labelsFiltered)
    76  	}
    77  }
    78  
    79  func (m *Metrics) EmitKey(key []string, val float32) {
    80  	if m.EnableTypePrefix {
    81  		key = insert(0, "kv", key)
    82  	}
    83  	if m.ServiceName != "" {
    84  		key = insert(0, m.ServiceName, key)
    85  	}
    86  	allowed, _ := m.allowMetric(key, nil)
    87  	if !allowed {
    88  		return
    89  	}
    90  	m.sink.EmitKey(key, val)
    91  }
    92  
    93  func (m *Metrics) IncrCounter(key []string, val float32) {
    94  	m.IncrCounterWithLabels(key, val, nil)
    95  }
    96  
    97  func (m *Metrics) IncrCounterWithLabels(key []string, val float32, labels []Label) {
    98  	if m.HostName != "" && m.EnableHostnameLabel {
    99  		labels = append(labels, Label{"host", m.HostName})
   100  	}
   101  	if m.EnableTypePrefix {
   102  		key = insert(0, "counter", key)
   103  	}
   104  	if m.ServiceName != "" {
   105  		if m.EnableServiceLabel {
   106  			labels = append(labels, Label{"service", m.ServiceName})
   107  		} else {
   108  			key = insert(0, m.ServiceName, key)
   109  		}
   110  	}
   111  	allowed, labelsFiltered := m.allowMetric(key, labels)
   112  	if !allowed {
   113  		return
   114  	}
   115  	m.sink.IncrCounterWithLabels(key, val, labelsFiltered)
   116  }
   117  
   118  func (m *Metrics) AddSample(key []string, val float32) {
   119  	m.AddSampleWithLabels(key, val, nil)
   120  }
   121  
   122  func (m *Metrics) AddSampleWithLabels(key []string, val float32, labels []Label) {
   123  	if m.HostName != "" && m.EnableHostnameLabel {
   124  		labels = append(labels, Label{"host", m.HostName})
   125  	}
   126  	if m.EnableTypePrefix {
   127  		key = insert(0, "sample", key)
   128  	}
   129  	if m.ServiceName != "" {
   130  		if m.EnableServiceLabel {
   131  			labels = append(labels, Label{"service", m.ServiceName})
   132  		} else {
   133  			key = insert(0, m.ServiceName, key)
   134  		}
   135  	}
   136  	allowed, labelsFiltered := m.allowMetric(key, labels)
   137  	if !allowed {
   138  		return
   139  	}
   140  	m.sink.AddSampleWithLabels(key, val, labelsFiltered)
   141  }
   142  
   143  func (m *Metrics) MeasureSince(key []string, start time.Time) {
   144  	m.MeasureSinceWithLabels(key, start, nil)
   145  }
   146  
   147  func (m *Metrics) MeasureSinceWithLabels(key []string, start time.Time, labels []Label) {
   148  	if m.HostName != "" && m.EnableHostnameLabel {
   149  		labels = append(labels, Label{"host", m.HostName})
   150  	}
   151  	if m.EnableTypePrefix {
   152  		key = insert(0, "timer", key)
   153  	}
   154  	if m.ServiceName != "" {
   155  		if m.EnableServiceLabel {
   156  			labels = append(labels, Label{"service", m.ServiceName})
   157  		} else {
   158  			key = insert(0, m.ServiceName, key)
   159  		}
   160  	}
   161  	allowed, labelsFiltered := m.allowMetric(key, labels)
   162  	if !allowed {
   163  		return
   164  	}
   165  	now := time.Now()
   166  	elapsed := now.Sub(start)
   167  	msec := float32(elapsed.Nanoseconds()) / float32(m.TimerGranularity)
   168  	m.sink.AddSampleWithLabels(key, msec, labelsFiltered)
   169  }
   170  
   171  // UpdateFilter overwrites the existing filter with the given rules.
   172  func (m *Metrics) UpdateFilter(allow, block []string) {
   173  	m.UpdateFilterAndLabels(allow, block, m.AllowedLabels, m.BlockedLabels)
   174  }
   175  
   176  // UpdateFilterAndLabels overwrites the existing filter with the given rules.
   177  func (m *Metrics) UpdateFilterAndLabels(allow, block, allowedLabels, blockedLabels []string) {
   178  	m.filterLock.Lock()
   179  	defer m.filterLock.Unlock()
   180  
   181  	m.AllowedPrefixes = allow
   182  	m.BlockedPrefixes = block
   183  
   184  	if allowedLabels == nil {
   185  		// Having a white list means we take only elements from it
   186  		m.allowedLabels = nil
   187  	} else {
   188  		m.allowedLabels = make(map[string]bool)
   189  		for _, v := range allowedLabels {
   190  			m.allowedLabels[v] = true
   191  		}
   192  	}
   193  	m.blockedLabels = make(map[string]bool)
   194  	for _, v := range blockedLabels {
   195  		m.blockedLabels[v] = true
   196  	}
   197  	m.AllowedLabels = allowedLabels
   198  	m.BlockedLabels = blockedLabels
   199  
   200  	m.filter = iradix.New()
   201  	for _, prefix := range m.AllowedPrefixes {
   202  		m.filter, _, _ = m.filter.Insert([]byte(prefix), true)
   203  	}
   204  	for _, prefix := range m.BlockedPrefixes {
   205  		m.filter, _, _ = m.filter.Insert([]byte(prefix), false)
   206  	}
   207  }
   208  
   209  func (m *Metrics) Shutdown() {
   210  	if ss, ok := m.sink.(ShutdownSink); ok {
   211  		ss.Shutdown()
   212  	}
   213  }
   214  
   215  // labelIsAllowed return true if a should be included in metric
   216  // the caller should lock m.filterLock while calling this method
   217  func (m *Metrics) labelIsAllowed(label *Label) bool {
   218  	labelName := (*label).Name
   219  	if m.blockedLabels != nil {
   220  		_, ok := m.blockedLabels[labelName]
   221  		if ok {
   222  			// If present, let's remove this label
   223  			return false
   224  		}
   225  	}
   226  	if m.allowedLabels != nil {
   227  		_, ok := m.allowedLabels[labelName]
   228  		return ok
   229  	}
   230  	// Allow by default
   231  	return true
   232  }
   233  
   234  // filterLabels return only allowed labels
   235  // the caller should lock m.filterLock while calling this method
   236  func (m *Metrics) filterLabels(labels []Label) []Label {
   237  	if labels == nil {
   238  		return nil
   239  	}
   240  	toReturn := []Label{}
   241  	for _, label := range labels {
   242  		if m.labelIsAllowed(&label) {
   243  			toReturn = append(toReturn, label)
   244  		}
   245  	}
   246  	return toReturn
   247  }
   248  
   249  // Returns whether the metric should be allowed based on configured prefix filters
   250  // Also return the applicable labels
   251  func (m *Metrics) allowMetric(key []string, labels []Label) (bool, []Label) {
   252  	m.filterLock.RLock()
   253  	defer m.filterLock.RUnlock()
   254  
   255  	if m.filter == nil || m.filter.Len() == 0 {
   256  		return m.Config.FilterDefault, m.filterLabels(labels)
   257  	}
   258  
   259  	_, allowed, ok := m.filter.Root().LongestPrefix([]byte(strings.Join(key, ".")))
   260  	if !ok {
   261  		return m.Config.FilterDefault, m.filterLabels(labels)
   262  	}
   263  
   264  	return allowed.(bool), m.filterLabels(labels)
   265  }
   266  
   267  // Periodically collects runtime stats to publish
   268  func (m *Metrics) collectStats() {
   269  	for {
   270  		time.Sleep(m.ProfileInterval)
   271  		m.EmitRuntimeStats()
   272  	}
   273  }
   274  
   275  // Emits various runtime statsitics
   276  func (m *Metrics) EmitRuntimeStats() {
   277  	// Export number of Goroutines
   278  	numRoutines := runtime.NumGoroutine()
   279  	m.SetGauge([]string{"runtime", "num_goroutines"}, float32(numRoutines))
   280  
   281  	// Export memory stats
   282  	var stats runtime.MemStats
   283  	runtime.ReadMemStats(&stats)
   284  	m.SetGauge([]string{"runtime", "alloc_bytes"}, float32(stats.Alloc))
   285  	m.SetGauge([]string{"runtime", "sys_bytes"}, float32(stats.Sys))
   286  	m.SetGauge([]string{"runtime", "malloc_count"}, float32(stats.Mallocs))
   287  	m.SetGauge([]string{"runtime", "free_count"}, float32(stats.Frees))
   288  	m.SetGauge([]string{"runtime", "heap_objects"}, float32(stats.HeapObjects))
   289  	m.SetGauge([]string{"runtime", "total_gc_pause_ns"}, float32(stats.PauseTotalNs))
   290  	m.SetGauge([]string{"runtime", "total_gc_runs"}, float32(stats.NumGC))
   291  
   292  	// Export info about the last few GC runs
   293  	num := stats.NumGC
   294  
   295  	// Handle wrap around
   296  	if num < m.lastNumGC {
   297  		m.lastNumGC = 0
   298  	}
   299  
   300  	// Ensure we don't scan more than 256
   301  	if num-m.lastNumGC >= 256 {
   302  		m.lastNumGC = num - 255
   303  	}
   304  
   305  	for i := m.lastNumGC; i < num; i++ {
   306  		pause := stats.PauseNs[i%256]
   307  		m.AddSample([]string{"runtime", "gc_pause_ns"}, float32(pause))
   308  	}
   309  	m.lastNumGC = num
   310  }
   311  
   312  // Creates a new slice with the provided string value as the first element
   313  // and the provided slice values as the remaining values.
   314  // Ordering of the values in the provided input slice is kept in tact in the output slice.
   315  func insert(i int, v string, s []string) []string {
   316  	// Allocate new slice to avoid modifying the input slice
   317  	newS := make([]string, len(s)+1)
   318  
   319  	// Copy s[0, i-1] into newS
   320  	for j := 0; j < i; j++ {
   321  		newS[j] = s[j]
   322  	}
   323  
   324  	// Insert provided element at index i
   325  	newS[i] = v
   326  
   327  	// Copy s[i, len(s)-1] into newS starting at newS[i+1]
   328  	for j := i; j < len(s); j++ {
   329  		newS[j+1] = s[j]
   330  	}
   331  
   332  	return newS
   333  }