github.com/haraldrudell/parl@v0.4.176/slow-detector-core.go (about) 1 /* 2 © 2023–present Harald Rudell <harald.rudell@gmail.com> (https://haraldrudell.github.io/haraldrudell/) 3 ISC License 4 */ 5 6 package parl 7 8 import ( 9 "sync/atomic" 10 "time" 11 12 "github.com/haraldrudell/parl/perrors" 13 "github.com/haraldrudell/parl/ptime" 14 ) 15 16 const ( 17 defaultMinReportDuration = 100 * time.Millisecond 18 defaultNonReturnPeriod = time.Minute 19 ) 20 21 type CbSlowDetector func(sdi *SlowDetectorInvocation, hasReturned bool, duration time.Duration) 22 type slowID uint64 23 24 var slowIDGenerator UniqueIDTypedUint64[slowID] 25 26 // SlowDetectorCore measures latency via Start-Stop invocations 27 // - Thread-Safe and multi-threaded, parallel invocations 28 // - Separate thread measures time of non-returning, hung invocations 29 type SlowDetectorCore struct { 30 ID slowID 31 callback CbSlowDetector 32 thread *SlowDetectorThread 33 34 max AtomicMax[time.Duration] 35 alwaysMax AtomicMax[time.Duration] 36 last time.Duration // atomic 37 average ptime.Averager[time.Duration] 38 } 39 40 // NewSlowDetectorCore returns an object tracking nonm-returning or slow function invocations 41 // - callback receives offending slow-detector invocations, cannot be nil 42 // - slowTyp configures whether the support-thread is shared 43 // - goGen is used for a possible deferred thread-launch 44 // - optional values are: 45 // - — nonReturnPeriod: how often non-returning invocations are reported, default once per minute 46 // - — minimum slowness duration that is being reported, default 100 ms 47 func NewSlowDetectorCore(callback CbSlowDetector, slowTyp slowType, goGen GoGen, nonReturnPeriod ...time.Duration) (slowDetector *SlowDetectorCore) { 48 if callback == nil { 49 panic(perrors.NewPF("callback cannot be nil")) 50 } 51 52 // nonReturnPeriod[0]: time between non-return reports, default 1 minute 53 var nonReturnPeriod0 time.Duration 54 if len(nonReturnPeriod) > 0 { 55 nonReturnPeriod0 = nonReturnPeriod[0] 56 } else { 57 nonReturnPeriod0 = defaultNonReturnPeriod 58 } 59 60 // nonReturnPeriod[1]: minimum duration for slowness to be reported, default 100 ms 61 var minReportedDuration time.Duration 62 if len(nonReturnPeriod) > 1 { 63 minReportedDuration = nonReturnPeriod[1] 64 } else { 65 minReportedDuration = defaultMinReportDuration 66 } 67 68 return &SlowDetectorCore{ 69 ID: slowIDGenerator.ID(), 70 callback: callback, 71 thread: NewSlowDetectorThread(slowTyp, nonReturnPeriod0, goGen), 72 max: *NewAtomicMax[time.Duration](minReportedDuration), 73 average: *ptime.NewAverager[time.Duration](), 74 } 75 } 76 77 // Start returns the effective start time for a new timing cycle 78 // - value is optional start time, default time.Now() 79 func (sd *SlowDetectorCore) Start(invoLabel string, value ...time.Time) (invocation *SlowDetectorInvocation) { 80 81 // get time value for this operation 82 var t0 time.Time 83 if len(value) > 0 { 84 t0 = value[0] 85 } else { 86 t0 = time.Now() 87 } 88 89 // save in map, launch thread if not already running 90 s := SlowDetectorInvocation{ 91 sID: slowIDGenerator.ID(), 92 invoLabel: invoLabel, 93 threadID: goID(), 94 t0: t0, 95 stop: sd.stop, 96 sd: sd, 97 } 98 sd.thread.Start(&s) 99 return &s 100 } 101 102 func (sd *SlowDetectorCore) Values() ( 103 last, average, max time.Duration, 104 hasValue bool, 105 ) { 106 last = time.Duration(atomic.LoadInt64((*int64)(&sd.last))) 107 averageFloat, _ := sd.average.Average() 108 average = time.Duration(averageFloat) 109 max, hasValue = sd.alwaysMax.Max() 110 return 111 } 112 113 // Stop is invoked via SlowDetectorInvocation 114 func (sd *SlowDetectorCore) stop(sdi *SlowDetectorInvocation, value ...time.Time) { 115 116 // remove from map and possibly shutdown thread 117 sd.thread.Stop(sdi) 118 119 // get time value for this operation 120 var t1 time.Time 121 if len(value) > 0 { 122 t1 = value[0] 123 } else { 124 t1 = time.Now() 125 } 126 127 // store last and average 128 duration := t1.Sub(sdi.t0) 129 atomic.StoreInt64((*int64)(&sd.last), int64(duration)) 130 sd.average.Add(duration, t1) 131 sd.alwaysMax.Value(duration) 132 133 // check against max 134 if sd.max.Value(duration) { 135 sd.callback(sdi, true, duration) 136 } 137 }