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

     1  package stats
     2  
     3  import (
     4  	"context"
     5  	"io"
     6  	"time"
     7  )
     8  
     9  type key int
    10  
    11  const (
    12  	ctxKey key = iota
    13  )
    14  
    15  var (
    16  	// Null is the null Stats instance.
    17  	Null = &nullStats{}
    18  )
    19  
    20  // Stats represents a stats instance.
    21  type Stats interface {
    22  	io.Closer
    23  
    24  	// Inc increments a count by the value.
    25  	Inc(name string, value int64, rate float32, tags ...interface{}) error
    26  
    27  	// Dec decrements a count by the value.
    28  	Dec(name string, value int64, rate float32, tags ...interface{}) error
    29  
    30  	// Gauge measures the value of a metric.
    31  	Gauge(name string, value float64, rate float32, tags ...interface{}) error
    32  
    33  	// Timing sends the value of a Duration.
    34  	Timing(name string, value time.Duration, rate float32, tags ...interface{}) error
    35  }
    36  
    37  // WithStats sets Stats in the context.
    38  func WithStats(ctx context.Context, stats Stats) context.Context {
    39  	if stats == nil {
    40  		stats = Null
    41  	}
    42  	return context.WithValue(ctx, ctxKey, stats)
    43  }
    44  
    45  // FromContext returns the instance of Stats in the context.
    46  func FromContext(ctx context.Context) (Stats, bool) {
    47  	stats, ok := ctx.Value(ctxKey).(Stats)
    48  	return stats, ok
    49  }
    50  
    51  // Inc increments a count by the value.
    52  func Inc(ctx context.Context, name string, value int64, rate float32, tags ...interface{}) error {
    53  	return withStats(ctx, func(s Stats) error {
    54  		return s.Inc(name, value, rate, tags...)
    55  	})
    56  }
    57  
    58  // Dec decrements a count by the value.
    59  func Dec(ctx context.Context, name string, value int64, rate float32, tags ...interface{}) error {
    60  	return withStats(ctx, func(s Stats) error {
    61  		return s.Dec(name, value, rate, tags...)
    62  	})
    63  }
    64  
    65  // Gauge measures the value of a metric.
    66  func Gauge(ctx context.Context, name string, value float64, rate float32, tags ...interface{}) error {
    67  	return withStats(ctx, func(s Stats) error {
    68  		return s.Gauge(name, value, rate, tags...)
    69  	})
    70  }
    71  
    72  // Timing sends the value of a Duration.
    73  func Timing(ctx context.Context, name string, value time.Duration, rate float32, tags ...interface{}) error {
    74  	return withStats(ctx, func(s Stats) error {
    75  		return s.Timing(name, value, rate, tags...)
    76  	})
    77  }
    78  
    79  // Close closes the client and flushes buffered stats, if applicable
    80  func Close(ctx context.Context) error {
    81  	return withStats(ctx, func(s Stats) error {
    82  		return s.Close()
    83  	})
    84  }
    85  
    86  func withStats(ctx context.Context, fn func(s Stats) error) error {
    87  	if s, ok := FromContext(ctx); ok {
    88  		return fn(s)
    89  	}
    90  	return fn(Null)
    91  }
    92  
    93  type nullStats struct{}
    94  
    95  func (s nullStats) Inc(name string, value int64, rate float32, tags ...interface{}) error {
    96  	return nil
    97  }
    98  
    99  func (s nullStats) Dec(name string, value int64, rate float32, tags ...interface{}) error {
   100  	return nil
   101  }
   102  
   103  func (s nullStats) Gauge(name string, value float64, rate float32, tags ...interface{}) error {
   104  	return nil
   105  }
   106  
   107  func (s nullStats) Timing(name string, value time.Duration, rate float32, tags ...interface{}) error {
   108  	return nil
   109  }
   110  
   111  func (s nullStats) Close() error {
   112  	return nil
   113  }
   114  
   115  // TaggedStats wraps a Stats instance applying tags to all metrics.
   116  type TaggedStats struct {
   117  	stats Stats
   118  	tags  []interface{}
   119  }
   120  
   121  // NewTaggedStats creates a new TaggedStats instance.
   122  func NewTaggedStats(stats Stats, tags ...interface{}) *TaggedStats {
   123  	if t, ok := stats.(*TaggedStats); ok {
   124  		stats = t.stats
   125  		tags = append(t.tags, tags...)
   126  	}
   127  
   128  	return &TaggedStats{
   129  		stats: stats,
   130  		tags:  normalizeTags(tags),
   131  	}
   132  }
   133  
   134  // Inc increments a count by the value.
   135  func (s TaggedStats) Inc(name string, value int64, rate float32, tags ...interface{}) error {
   136  	return s.stats.Inc(name, value, rate, mergeTags(tags, s.tags)...)
   137  }
   138  
   139  // Dec decrements a count by the value.
   140  func (s TaggedStats) Dec(name string, value int64, rate float32, tags ...interface{}) error {
   141  	return s.stats.Dec(name, value, rate, mergeTags(tags, s.tags)...)
   142  }
   143  
   144  // Gauge measures the value of a metric.
   145  func (s TaggedStats) Gauge(name string, value float64, rate float32, tags ...interface{}) error {
   146  	return s.stats.Gauge(name, value, rate, mergeTags(tags, s.tags)...)
   147  }
   148  
   149  // Timing sends the value of a Duration.
   150  func (s TaggedStats) Timing(name string, value time.Duration, rate float32, tags ...interface{}) error {
   151  	return s.stats.Timing(name, value, rate, mergeTags(tags, s.tags)...)
   152  }
   153  
   154  // Close closes the client and flushes buffered stats, if applicable
   155  func (s TaggedStats) Close() error {
   156  	return s.stats.Close()
   157  }
   158  
   159  func normalizeTags(tags []interface{}) []interface{} {
   160  	// If Tags object was passed, then expand it
   161  	if len(tags) == 1 {
   162  		if ctxMap, ok := tags[0].(Tags); ok {
   163  			tags = ctxMap.toArray()
   164  		}
   165  	}
   166  
   167  	// tags need to be even as they are key/value pairs
   168  	if len(tags)%2 != 0 {
   169  		tags = append(tags, nil, "STATS_ERROR", "Normalised odd number of tags by adding nil")
   170  	}
   171  
   172  	return tags
   173  }
   174  
   175  func mergeTags(prefix, suffix []interface{}) []interface{} {
   176  	newTags := make([]interface{}, len(prefix)+len(suffix))
   177  
   178  	n := copy(newTags, prefix)
   179  	copy(newTags[n:], suffix)
   180  
   181  	return newTags
   182  }
   183  
   184  func deduplicateTags(tags []interface{}) []interface{} {
   185  	for i := 0; i < len(tags); i += 2 {
   186  		for j := i + 2; j < len(tags); j += 2 {
   187  			if tags[i] == tags[j] {
   188  				tags[i+1] = tags[j+1]
   189  				tags = append(tags[:j], tags[j+2:]...)
   190  				j -= 2
   191  			}
   192  		}
   193  	}
   194  
   195  	return tags
   196  }