github.com/facebookincubator/go-belt@v0.0.0-20230703220935-39cd348f1a38/tool/experimental/metrics/implementation/simplemetrics/metrics.go (about)

     1  // Copyright 2022 Meta Platforms, Inc. and affiliates.
     2  //
     3  // Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
     4  //
     5  // 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
     6  //
     7  // 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
     8  //
     9  // 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
    10  //
    11  // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    12  
    13  // Copyright (c) Facebook, Inc. and its affiliates.
    14  //
    15  // This source code is licensed under the MIT license found in the
    16  // LICENSE file in the root directory of this source tree.
    17  
    18  package simplemetrics
    19  
    20  import (
    21  	"fmt"
    22  	"sort"
    23  	"strings"
    24  	"sync"
    25  
    26  	"github.com/facebookincubator/go-belt/tool/experimental/metrics/types"
    27  )
    28  
    29  var _ types.Metrics = &Metrics{}
    30  
    31  type storage struct {
    32  	locker           sync.RWMutex
    33  	intGaugeFamilies map[string]*intGaugeFamily
    34  	gaugeFamilies    map[string]*gaugeFamily
    35  	countFamilies    map[string]*countFamily
    36  }
    37  
    38  // Metrics is a naive implementation of Metrics
    39  type Metrics struct {
    40  	*storage
    41  	fields *FieldsChain
    42  }
    43  
    44  // New returns an instance of Metrics.
    45  func New() *Metrics {
    46  	return &Metrics{
    47  		storage: &storage{
    48  			intGaugeFamilies: make(map[string]*intGaugeFamily),
    49  			gaugeFamilies:    make(map[string]*gaugeFamily),
    50  			countFamilies:    make(map[string]*countFamily),
    51  		},
    52  	}
    53  }
    54  
    55  func cleanStringForKey(in string) string {
    56  	return strings.ReplaceAll(strings.ReplaceAll(strings.ReplaceAll(in, "=", "=="), "_", "__"), ",", "_")
    57  }
    58  
    59  func fieldsToString(fields AbstractFields) string {
    60  	var fieldStrings []string
    61  	fields.ForEachField(func(f *Field) bool {
    62  		if !f.Properties.Has(types.FieldPropInclude) {
    63  			return true
    64  		}
    65  		key := cleanStringForKey(f.Key)
    66  		value := cleanStringForKey(fmt.Sprint(f.Value))
    67  		fieldStrings = append(fieldStrings, fmt.Sprintf("%s=%s", key, value))
    68  		return true
    69  	})
    70  	sort.Slice(fieldStrings, func(i, j int) bool {
    71  		return fieldStrings[i] < fieldStrings[j]
    72  	})
    73  	return strings.Join(fieldStrings, ",")
    74  }
    75  
    76  // Count implements metrics.Metrics.
    77  func (metrics *Metrics) Count(key string) types.Count {
    78  	return metrics.CountFields(key, nil)
    79  }
    80  
    81  // CountFields implements metrics.Metrics.
    82  func (metrics *Metrics) CountFields(key string, additionalFields AbstractFields) types.Count {
    83  	fields := metrics.fields.WithFields(additionalFields)
    84  	metrics.storage.locker.RLock()
    85  	family := metrics.countFamilies[key]
    86  	metrics.storage.locker.RUnlock()
    87  	if family != nil {
    88  		return family.get(fields)
    89  	}
    90  
    91  	metrics.storage.locker.Lock()
    92  	defer metrics.storage.locker.Unlock()
    93  	family = metrics.countFamilies[key]
    94  	if family != nil {
    95  		return family.get(fields)
    96  	}
    97  
    98  	family = &countFamily{
    99  		Metrics: make(map[string]*Count),
   100  	}
   101  	metrics.countFamilies[key] = family
   102  
   103  	return family.get(fields)
   104  }
   105  
   106  // ForEachCount iterates through all Count metrics. Stops at first `false` returned by the callback function.
   107  func (metrics *Metrics) ForEachCount(callback func(types.Count) bool) bool {
   108  	metrics.storage.locker.RLock()
   109  	defer metrics.storage.locker.RUnlock()
   110  	for _, family := range metrics.countFamilies {
   111  		for _, metric := range family.Metrics {
   112  			if !callback(metric) {
   113  				return false
   114  			}
   115  		}
   116  	}
   117  	return true
   118  }
   119  
   120  // Gauge implements metrics.Metrics.
   121  func (metrics *Metrics) Gauge(key string) types.Gauge {
   122  	return metrics.GaugeFields(key, nil)
   123  }
   124  
   125  // GaugeFields implements metrics.Metrics.
   126  func (metrics *Metrics) GaugeFields(key string, additionalFields AbstractFields) types.Gauge {
   127  	fields := metrics.fields.WithFields(additionalFields)
   128  	metrics.storage.locker.RLock()
   129  	family := metrics.gaugeFamilies[key]
   130  	metrics.storage.locker.RUnlock()
   131  	if family != nil {
   132  		return family.get(fields)
   133  	}
   134  
   135  	metrics.storage.locker.Lock()
   136  	defer metrics.storage.locker.Unlock()
   137  	family = metrics.gaugeFamilies[key]
   138  	if family != nil {
   139  		return family.get(fields)
   140  	}
   141  
   142  	family = &gaugeFamily{
   143  		Metrics: make(map[string]*Gauge),
   144  	}
   145  	metrics.gaugeFamilies[key] = family
   146  
   147  	return family.get(fields)
   148  }
   149  
   150  // ForEachGauge iterates through all Gauge metrics. Stops at first `false` returned by the callback function.
   151  func (metrics *Metrics) ForEachGauge(callback func(types.Gauge) bool) bool {
   152  	metrics.storage.locker.RLock()
   153  	defer metrics.storage.locker.RUnlock()
   154  	for _, family := range metrics.gaugeFamilies {
   155  		for _, metric := range family.Metrics {
   156  			if !callback(metric) {
   157  				return false
   158  			}
   159  		}
   160  	}
   161  	return true
   162  }
   163  
   164  // IntGauge implements metrics.Metrics.
   165  func (metrics *Metrics) IntGauge(key string) types.IntGauge {
   166  	return metrics.IntGaugeFields(key, nil)
   167  }
   168  
   169  // IntGaugeFields implements metrics.Metrics.
   170  func (metrics *Metrics) IntGaugeFields(key string, additionalFields AbstractFields) types.IntGauge {
   171  	fields := metrics.fields.WithFields(additionalFields)
   172  	metrics.storage.locker.RLock()
   173  	family := metrics.intGaugeFamilies[key]
   174  	metrics.storage.locker.RUnlock()
   175  	if family != nil {
   176  		return family.get(fields)
   177  	}
   178  
   179  	metrics.storage.locker.Lock()
   180  	defer metrics.storage.locker.Unlock()
   181  	family = metrics.intGaugeFamilies[key]
   182  	if family != nil {
   183  		return family.get(fields)
   184  	}
   185  
   186  	family = &intGaugeFamily{
   187  		Metrics: make(map[string]*IntGauge),
   188  	}
   189  	metrics.intGaugeFamilies[key] = family
   190  
   191  	return family.get(fields)
   192  }
   193  
   194  // ForEachIntGauge iterates through all IntGauge metrics. Stops at first `false` returned by the callback function.
   195  func (metrics *Metrics) ForEachIntGauge(callback func(types.IntGauge) bool) bool {
   196  	metrics.storage.locker.RLock()
   197  	defer metrics.storage.locker.RUnlock()
   198  	for _, family := range metrics.intGaugeFamilies {
   199  		for _, metric := range family.Metrics {
   200  			if !callback(metric) {
   201  				return false
   202  			}
   203  		}
   204  	}
   205  	return true
   206  }
   207  
   208  // WithContextFields implements metrics.Metrics.
   209  func (metrics Metrics) WithContextFields(allFields *FieldsChain, newFieldsCount int) Tool {
   210  	metrics.fields = allFields
   211  	return &metrics
   212  }
   213  
   214  // WithTraceIDs implements metrics.Metrics.
   215  func (metrics *Metrics) WithTraceIDs(traceIDs TraceIDs, newTraceIDsCount int) Tool {
   216  	// Should be ignored per metrics.Metrics interface description, so returning
   217  	// as is:
   218  	return metrics
   219  }
   220  
   221  // Flush implements metrics.Metrics (or more specifically belt.Tool).
   222  func (*Metrics) Flush() {}