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 }