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