github.com/xmidt-org/webpa-common@v1.11.9/semaphore/instrument.go (about)

     1  package semaphore
     2  
     3  import (
     4  	"context"
     5  	"time"
     6  
     7  	"github.com/go-kit/kit/metrics/discard"
     8  	"github.com/xmidt-org/webpa-common/xmetrics"
     9  )
    10  
    11  const (
    12  	MetricOpen   float64 = 1.0
    13  	MetricClosed float64 = 0.0
    14  )
    15  
    16  type instrumentOptions struct {
    17  	failures  xmetrics.Adder
    18  	resources xmetrics.Adder
    19  	closed    xmetrics.Setter
    20  }
    21  
    22  var defaultOptions = instrumentOptions{
    23  	failures:  discard.NewCounter(),
    24  	resources: discard.NewCounter(),
    25  	closed:    discard.NewGauge(),
    26  }
    27  
    28  // InstrumentOption represents a configurable option for instrumenting a semaphore
    29  type InstrumentOption func(*instrumentOptions)
    30  
    31  // WithResources establishes a metric that tracks the resource count of the semaphore.
    32  // If a nil counter is supplied, resource counts are discarded.
    33  func WithResources(a xmetrics.Adder) InstrumentOption {
    34  	return func(io *instrumentOptions) {
    35  		if a != nil {
    36  			io.resources = a
    37  		} else {
    38  			io.resources = discard.NewCounter()
    39  		}
    40  	}
    41  }
    42  
    43  // WithFailures establishes a metric that tracks how many times a resource was unable to
    44  // be acquired, due to timeouts, context cancellations, etc.
    45  func WithFailures(a xmetrics.Adder) InstrumentOption {
    46  	return func(io *instrumentOptions) {
    47  		if a != nil {
    48  			io.failures = a
    49  		} else {
    50  			io.failures = discard.NewCounter()
    51  		}
    52  	}
    53  }
    54  
    55  // WithClosed sets a gauge that records the state of a Closeable semaphore, 1.0 for open and 0.0 for closed.
    56  // This option is ignored for regular semaphores.
    57  func WithClosed(s xmetrics.Setter) InstrumentOption {
    58  	return func(io *instrumentOptions) {
    59  		if s != nil {
    60  			io.closed = s
    61  		} else {
    62  			io.closed = discard.NewGauge()
    63  		}
    64  	}
    65  }
    66  
    67  // Instrument decorates an existing semaphore with instrumentation.  The available options
    68  // allow tracking the number of resources currently acquired and the total count of failures over time.
    69  // The returned Interface object will not implement Closeable, even if the decorated semaphore does.
    70  func Instrument(s Interface, o ...InstrumentOption) Interface {
    71  	if s == nil {
    72  		panic("A delegate semaphore is required")
    73  	}
    74  
    75  	io := defaultOptions
    76  
    77  	for _, f := range o {
    78  		f(&io)
    79  	}
    80  
    81  	return &instrumentedSemaphore{
    82  		delegate:  s,
    83  		failures:  io.failures,
    84  		resources: io.resources,
    85  	}
    86  }
    87  
    88  // InstrumentCloseable is similar to Instrument, but works with Closeable semaphores.  The WithClosed
    89  // option is honored by this factory function.
    90  func InstrumentCloseable(c Closeable, o ...InstrumentOption) Closeable {
    91  	if c == nil {
    92  		panic("A delegate semaphore is required")
    93  	}
    94  
    95  	io := defaultOptions
    96  
    97  	for _, f := range o {
    98  		f(&io)
    99  	}
   100  
   101  	ic := &instrumentedCloseable{
   102  		instrumentedSemaphore: instrumentedSemaphore{
   103  			delegate:  c,
   104  			failures:  io.failures,
   105  			resources: io.resources,
   106  		},
   107  		closed: io.closed,
   108  	}
   109  
   110  	ic.closed.Set(MetricOpen)
   111  	return ic
   112  }
   113  
   114  // instrumentedSemaphore is the internal decorator around Interface that applies appropriate metrics.
   115  type instrumentedSemaphore struct {
   116  	delegate  Interface
   117  	resources xmetrics.Adder
   118  	failures  xmetrics.Adder
   119  }
   120  
   121  func (is *instrumentedSemaphore) Acquire() (err error) {
   122  	err = is.delegate.Acquire()
   123  	if err != nil {
   124  		is.failures.Add(1.0)
   125  	} else {
   126  		is.resources.Add(1.0)
   127  	}
   128  
   129  	return
   130  }
   131  
   132  func (is *instrumentedSemaphore) AcquireWait(t <-chan time.Time) (err error) {
   133  	err = is.delegate.AcquireWait(t)
   134  	if err != nil {
   135  		is.failures.Add(1.0)
   136  	} else {
   137  		is.resources.Add(1.0)
   138  	}
   139  
   140  	return
   141  }
   142  
   143  func (is *instrumentedSemaphore) AcquireCtx(ctx context.Context) (err error) {
   144  	err = is.delegate.AcquireCtx(ctx)
   145  	if err != nil {
   146  		is.failures.Add(1.0)
   147  	} else {
   148  		is.resources.Add(1.0)
   149  	}
   150  
   151  	return
   152  }
   153  
   154  func (is *instrumentedSemaphore) TryAcquire() (acquired bool) {
   155  	acquired = is.delegate.TryAcquire()
   156  	if acquired {
   157  		is.resources.Add(1.0)
   158  	} else {
   159  		is.failures.Add(1.0)
   160  	}
   161  
   162  	return
   163  }
   164  
   165  func (is *instrumentedSemaphore) Release() (err error) {
   166  	err = is.delegate.Release()
   167  	if err == nil {
   168  		is.resources.Add(-1.0)
   169  	}
   170  
   171  	return
   172  }
   173  
   174  type instrumentedCloseable struct {
   175  	instrumentedSemaphore
   176  	closed xmetrics.Setter
   177  }
   178  
   179  func (ic *instrumentedCloseable) Close() (err error) {
   180  	err = (ic.instrumentedSemaphore.delegate).(Closeable).Close()
   181  	ic.closed.Set(MetricClosed)
   182  
   183  	// NOTE: we don't set the resources metric to 0 as a way of preserving the state
   184  	// for debugging.  Can change this if desired.
   185  
   186  	return
   187  }
   188  
   189  func (ic *instrumentedCloseable) Closed() <-chan struct{} {
   190  	return (ic.instrumentedSemaphore.delegate).(Closeable).Closed()
   191  }