github.com/lulzWill/go-agent@v2.1.2+incompatible/internal/sampler.go (about)

     1  package internal
     2  
     3  import (
     4  	"runtime"
     5  	"time"
     6  
     7  	"github.com/lulzWill/go-agent/internal/logger"
     8  	"github.com/lulzWill/go-agent/internal/sysinfo"
     9  )
    10  
    11  // Sample is a system/runtime snapshot.
    12  type Sample struct {
    13  	when         time.Time
    14  	memStats     runtime.MemStats
    15  	usage        sysinfo.Usage
    16  	numGoroutine int
    17  	numCPU       int
    18  }
    19  
    20  func bytesToMebibytesFloat(bts uint64) float64 {
    21  	return float64(bts) / (1024 * 1024)
    22  }
    23  
    24  // GetSample gathers a new Sample.
    25  func GetSample(now time.Time, lg logger.Logger) *Sample {
    26  	s := Sample{
    27  		when:         now,
    28  		numGoroutine: runtime.NumGoroutine(),
    29  		numCPU:       runtime.NumCPU(),
    30  	}
    31  
    32  	if usage, err := sysinfo.GetUsage(); err == nil {
    33  		s.usage = usage
    34  	} else {
    35  		lg.Warn("unable to usage", map[string]interface{}{
    36  			"error": err.Error(),
    37  		})
    38  	}
    39  
    40  	runtime.ReadMemStats(&s.memStats)
    41  
    42  	return &s
    43  }
    44  
    45  type cpuStats struct {
    46  	used     time.Duration
    47  	fraction float64 // used / (elapsed * numCPU)
    48  }
    49  
    50  // Stats contains system information for a period of time.
    51  type Stats struct {
    52  	numGoroutine    int
    53  	allocBytes      uint64
    54  	heapObjects     uint64
    55  	user            cpuStats
    56  	system          cpuStats
    57  	gcPauseFraction float64
    58  	deltaNumGC      uint32
    59  	deltaPauseTotal time.Duration
    60  	minPause        time.Duration
    61  	maxPause        time.Duration
    62  }
    63  
    64  // Samples is used as the parameter to GetStats to avoid mixing up the previous
    65  // and current sample.
    66  type Samples struct {
    67  	Previous *Sample
    68  	Current  *Sample
    69  }
    70  
    71  // GetStats combines two Samples into a Stats.
    72  func GetStats(ss Samples) Stats {
    73  	cur := ss.Current
    74  	prev := ss.Previous
    75  	elapsed := cur.when.Sub(prev.when)
    76  
    77  	s := Stats{
    78  		numGoroutine: cur.numGoroutine,
    79  		allocBytes:   cur.memStats.Alloc,
    80  		heapObjects:  cur.memStats.HeapObjects,
    81  	}
    82  
    83  	// CPU Utilization
    84  	totalCPUSeconds := elapsed.Seconds() * float64(cur.numCPU)
    85  	if prev.usage.User != 0 && cur.usage.User > prev.usage.User {
    86  		s.user.used = cur.usage.User - prev.usage.User
    87  		s.user.fraction = s.user.used.Seconds() / totalCPUSeconds
    88  	}
    89  	if prev.usage.System != 0 && cur.usage.System > prev.usage.System {
    90  		s.system.used = cur.usage.System - prev.usage.System
    91  		s.system.fraction = s.system.used.Seconds() / totalCPUSeconds
    92  	}
    93  
    94  	// GC Pause Fraction
    95  	deltaPauseTotalNs := cur.memStats.PauseTotalNs - prev.memStats.PauseTotalNs
    96  	frac := float64(deltaPauseTotalNs) / float64(elapsed.Nanoseconds())
    97  	s.gcPauseFraction = frac
    98  
    99  	// GC Pauses
   100  	if deltaNumGC := cur.memStats.NumGC - prev.memStats.NumGC; deltaNumGC > 0 {
   101  		// In case more than 256 pauses have happened between samples
   102  		// and we are examining a subset of the pauses, we ensure that
   103  		// the min and max are not on the same side of the average by
   104  		// using the average as the starting min and max.
   105  		maxPauseNs := deltaPauseTotalNs / uint64(deltaNumGC)
   106  		minPauseNs := deltaPauseTotalNs / uint64(deltaNumGC)
   107  		for i := prev.memStats.NumGC + 1; i <= cur.memStats.NumGC; i++ {
   108  			pause := cur.memStats.PauseNs[(i+255)%256]
   109  			if pause > maxPauseNs {
   110  				maxPauseNs = pause
   111  			}
   112  			if pause < minPauseNs {
   113  				minPauseNs = pause
   114  			}
   115  		}
   116  		s.deltaPauseTotal = time.Duration(deltaPauseTotalNs) * time.Nanosecond
   117  		s.deltaNumGC = deltaNumGC
   118  		s.minPause = time.Duration(minPauseNs) * time.Nanosecond
   119  		s.maxPause = time.Duration(maxPauseNs) * time.Nanosecond
   120  	}
   121  
   122  	return s
   123  }
   124  
   125  // MergeIntoHarvest implements Harvestable.
   126  func (s Stats) MergeIntoHarvest(h *Harvest) {
   127  	h.Metrics.addValue(heapObjectsAllocated, "", float64(s.heapObjects), forced)
   128  	h.Metrics.addValue(runGoroutine, "", float64(s.numGoroutine), forced)
   129  	h.Metrics.addValueExclusive(memoryPhysical, "", bytesToMebibytesFloat(s.allocBytes), 0, forced)
   130  	h.Metrics.addValueExclusive(cpuUserUtilization, "", s.user.fraction, 0, forced)
   131  	h.Metrics.addValueExclusive(cpuSystemUtilization, "", s.system.fraction, 0, forced)
   132  	h.Metrics.addValue(cpuUserTime, "", s.user.used.Seconds(), forced)
   133  	h.Metrics.addValue(cpuSystemTime, "", s.system.used.Seconds(), forced)
   134  	h.Metrics.addValueExclusive(gcPauseFraction, "", s.gcPauseFraction, 0, forced)
   135  	if s.deltaNumGC > 0 {
   136  		h.Metrics.add(gcPauses, "", metricData{
   137  			countSatisfied:  float64(s.deltaNumGC),
   138  			totalTolerated:  s.deltaPauseTotal.Seconds(),
   139  			exclusiveFailed: 0,
   140  			min:             s.minPause.Seconds(),
   141  			max:             s.maxPause.Seconds(),
   142  			sumSquares:      s.deltaPauseTotal.Seconds() * s.deltaPauseTotal.Seconds(),
   143  		}, forced)
   144  	}
   145  }