github.com/haraldrudell/parl@v0.4.176/slow-detector.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  	"time"
    10  
    11  	"github.com/haraldrudell/parl/pruntime"
    12  	"github.com/haraldrudell/parl/ptime"
    13  )
    14  
    15  const (
    16  	newFrames    = 1
    17  	ensureFrames = 1
    18  )
    19  
    20  // SlowDetector measures latency via Start-Stop invocations and prints
    21  // max latency values above threshold to stderr. Thread-safe
    22  type SlowDetector struct {
    23  	sd     SlowDetectorCore
    24  	printf PrintfFunc
    25  	label  string
    26  }
    27  
    28  type SlowInvocation interface {
    29  	Stop(value ...time.Time)
    30  	Interval(label string, t ...time.Time)
    31  }
    32  
    33  // NewSlowDetector returns a Start-Stop variable detecting slowness
    34  //   - label is a name for the measured activity, default the code location of caller
    35  //   - slowTyp is most commonly SlowDefault using a shared thread
    36  //   - default printf is parl.Log, ie. ouput to stderr
    37  //   - first optional duration is minimum latency to report, default 100 ms
    38  //     if first optional duration is 0, all max-slowness invocations are printed
    39  //   - second optional duration is reporting period of non-return, default 1 minute
    40  //   - output is to stderr
    41  func NewSlowDetector(label string, slowTyp slowType, printf PrintfFunc, goGen GoGen, threshold ...time.Duration) (slowDetector *SlowDetector) {
    42  	if label == "" {
    43  		label = pruntime.NewCodeLocation(newFrames).Short()
    44  	}
    45  	if printf == nil {
    46  		printf = Log
    47  	}
    48  	sd := SlowDetector{
    49  		label:  label,
    50  		printf: printf,
    51  	}
    52  	sd.sd = *NewSlowDetectorCore(sd.callback, slowTyp, goGen, threshold...)
    53  	return &sd
    54  }
    55  
    56  func (sd *SlowDetector) IsValid() (isValid bool) {
    57  	if sd == nil {
    58  		return
    59  	}
    60  	isValid = sd.printf != nil
    61  	return
    62  }
    63  
    64  func (sd *SlowDetector) Start0() (slowInvocation SlowInvocation) {
    65  	return sd.sd.Start(pruntime.NewCodeLocation(ensureFrames).Short())
    66  }
    67  
    68  func (sd *SlowDetector) Start(label string, value ...time.Time) (slowInvocation SlowInvocation) {
    69  	if label == "" {
    70  		label = pruntime.NewCodeLocation(ensureFrames).Short()
    71  	}
    72  	return sd.sd.Start(label, value...)
    73  }
    74  
    75  func (sd *SlowDetector) Values() (last, average, max time.Duration, hasValue bool) {
    76  	return sd.sd.Values()
    77  }
    78  
    79  // last-duration / average duration / max duration
    80  func (sd *SlowDetector) Status0() (s string) {
    81  	last, average, max, hasValue := sd.sd.Values()
    82  	if !hasValue {
    83  		return "-/-/-"
    84  	}
    85  	s = ptime.Duration(last) + "/" +
    86  		ptime.Duration(average) + "/" +
    87  		ptime.Duration(max)
    88  	return
    89  }
    90  
    91  func (sd *SlowDetector) Status() (s string) {
    92  	return sd.label + ": " + sd.Status0()
    93  }
    94  
    95  func (sd *SlowDetector) callback(sdi *SlowDetectorInvocation, didReturn bool, duration time.Duration) {
    96  
    97  	var inProgressStr string
    98  	if !didReturn {
    99  		inProgressStr = " in progress…"
   100  	}
   101  
   102  	var threadIDStr string
   103  	if threadID := sdi.ThreadID(); threadID.IsValid() {
   104  		threadIDStr = " threadID: " + threadID.String()
   105  	}
   106  
   107  	var intervalStr = sdi.Intervals()
   108  
   109  	sd.printf("Slowness: %s %s duration: %s%s%s%s",
   110  		sd.label, sdi.Label(),
   111  		ptime.Duration(duration),
   112  		intervalStr,
   113  		threadIDStr,
   114  		inProgressStr,
   115  	)
   116  }