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 }