github.com/msales/pkg/v3@v3.24.0/stats/l2met.go (about)

     1  package stats
     2  
     3  import (
     4  	"math/rand"
     5  	"time"
     6  
     7  	"github.com/msales/pkg/v3/bytes"
     8  	"github.com/msales/pkg/v3/log"
     9  )
    10  
    11  // SamplerFunc represents a function that samples the L2met stats.
    12  type SamplerFunc func(float32) bool
    13  
    14  func defaultSampler(rate float32) bool {
    15  	if rand.Float32() < rate {
    16  		return true
    17  	}
    18  	return false
    19  }
    20  
    21  // L2metFunc represents a function that configures L2met.
    22  type L2metFunc func(*L2met)
    23  
    24  // UseRates turns on sample rates in l2met.
    25  func UseRates() L2metFunc {
    26  	return func(s *L2met) {
    27  		s.useRates = true
    28  	}
    29  }
    30  
    31  // UseSampler sets the sampler for l2met.
    32  func UseSampler(sampler SamplerFunc) L2metFunc {
    33  	return func(s *L2met) {
    34  		s.sampler = sampler
    35  	}
    36  }
    37  
    38  // L2met represents a l2met client.
    39  type L2met struct {
    40  	log    log.Logger
    41  	prefix string
    42  
    43  	useRates bool
    44  	sampler  SamplerFunc
    45  }
    46  
    47  // NewL2met create a l2met instance.
    48  func NewL2met(l log.Logger, prefix string, opts ...L2metFunc) Stats {
    49  	if len(prefix) > 0 {
    50  		prefix += "."
    51  	}
    52  
    53  	s := &L2met{
    54  		log:     l,
    55  		prefix:  prefix,
    56  		sampler: defaultSampler,
    57  	}
    58  
    59  	for _, opt := range opts {
    60  		opt(s)
    61  	}
    62  
    63  	return s
    64  }
    65  
    66  // Inc increments a count by the value.
    67  func (s *L2met) Inc(name string, value int64, rate float32, tags ...interface{}) error {
    68  	s.render(
    69  		"count",
    70  		name,
    71  		value,
    72  		rate,
    73  		tags,
    74  	)
    75  
    76  	return nil
    77  }
    78  
    79  // Dec decrements a count by the value.
    80  func (s *L2met) Dec(name string, value int64, rate float32, tags ...interface{}) error {
    81  	s.render(
    82  		"count",
    83  		name,
    84  		value*-1,
    85  		rate,
    86  		tags,
    87  	)
    88  
    89  	return nil
    90  }
    91  
    92  // Gauge measures the value of a metric.
    93  func (s *L2met) Gauge(name string, value float64, rate float32, tags ...interface{}) error {
    94  	s.render(
    95  		"sample",
    96  		name,
    97  		value,
    98  		rate,
    99  		tags,
   100  	)
   101  
   102  	return nil
   103  }
   104  
   105  // Timing sends the value of a Duration.
   106  func (s *L2met) Timing(name string, value time.Duration, rate float32, tags ...interface{}) error {
   107  	s.render(
   108  		"measure",
   109  		name,
   110  		formatDuration(value),
   111  		rate,
   112  		tags,
   113  	)
   114  
   115  	return nil
   116  }
   117  
   118  // render outputs the metric to the logger
   119  func (s *L2met) render(measure, name string, value interface{}, rate float32, tags []interface{}) {
   120  	if !s.includeStat(rate) {
   121  		return
   122  	}
   123  
   124  	tags = deduplicateTags(normalizeTags(tags))
   125  
   126  	ctx := make([]interface{}, len(tags)+2)
   127  	ctx[0] = measure + "#" + s.prefix + name + s.formatL2metRate(rate)
   128  	ctx[1] = value
   129  	copy(ctx[2:], tags)
   130  
   131  	s.log.Info("", ctx...)
   132  }
   133  
   134  func (s *L2met) includeStat(rate float32) bool {
   135  	if !s.useRates || rate == 1.0 {
   136  		return true
   137  	}
   138  
   139  	return s.sampler(rate)
   140  }
   141  
   142  // Close closes the client and flushes buffered stats, if applicable
   143  func (s *L2met) Close() error {
   144  	return nil
   145  }
   146  
   147  var l2metPool = bytes.NewPool(100)
   148  
   149  // formatL2metKey creates an l2met compatible rate suffix.
   150  func (s *L2met) formatL2metRate(rate float32) string {
   151  	if !s.useRates || rate == 1.0 {
   152  		return ""
   153  	}
   154  
   155  	buf := l2metPool.Get()
   156  	buf.WriteByte('@')
   157  	buf.AppendFloat(float64(rate), 'f', -1, 32)
   158  	res := string(buf.Bytes())
   159  	l2metPool.Put(buf)
   160  
   161  	return res
   162  }
   163  
   164  // formatDuration converts duration into fractional milliseconds
   165  // with no trailing zeros.
   166  func formatDuration(d time.Duration) string {
   167  	buf := l2metPool.Get()
   168  	buf.AppendUint(uint64(d / time.Millisecond))
   169  
   170  	p := uint64(d % time.Millisecond)
   171  	if p > 0 {
   172  		om := 0
   173  		m := uint64(100000)
   174  		for p < m {
   175  			om++
   176  			m /= 10
   177  		}
   178  
   179  		for {
   180  			if p%10 == 0 {
   181  				p /= 10
   182  				continue
   183  			}
   184  			break
   185  		}
   186  
   187  		buf.WriteByte('.')
   188  
   189  		for om > 0 {
   190  			buf.WriteByte('0')
   191  			om--
   192  		}
   193  
   194  		buf.AppendUint(p)
   195  	}
   196  
   197  	buf.WriteString("ms")
   198  	res := string(buf.Bytes())
   199  	l2metPool.Put(buf)
   200  
   201  	return res
   202  }