github.com/instana/go-sensor@v1.62.2-0.20240520081010-4919868049e1/autoprofile/internal/profile.go (about) 1 // (c) Copyright IBM Corp. 2021 2 // (c) Copyright Instana Inc. 2020 3 4 package internal 5 6 import ( 7 "bytes" 8 "strconv" 9 "sync" 10 "time" 11 ) 12 13 // Supported profile runtimes 14 const ( 15 RuntimeGolang = "golang" 16 ) 17 18 // Supported profile categories 19 const ( 20 CategoryCPU = "cpu" 21 CategoryMemory = "memory" 22 CategoryTime = "time" 23 ) 24 25 // Supported profile types 26 const ( 27 TypeCPUUsage = "cpu-usage" 28 TypeMemoryAllocation = "memory-allocations" 29 TypeBlockingCalls = "blocking-calls" 30 ) 31 32 // Human-readable measurement units 33 const ( 34 UnitSample = "sample" 35 UnitMillisecond = "millisecond" 36 UnitMicrosecond = "microsecond" 37 UnitNanosecond = "nanosecond" 38 UnitByte = "byte" 39 UnitKilobyte = "kilobyte" 40 UnitPercent = "percent" 41 ) 42 43 // AgentProfile is a presenter type used to serialize a collected profile 44 // to JSON format supported by Instana profile sensor 45 type AgentProfile struct { 46 ID string `json:"id"` 47 Runtime string `json:"runtime"` 48 Category string `json:"category"` 49 Type string `json:"type"` 50 Unit string `json:"unit"` 51 Roots []AgentCallSite `json:"roots"` 52 Duration int64 `json:"duration"` 53 Timespan int64 `json:"timespan"` 54 Timestamp int64 `json:"timestamp"` 55 } 56 57 // NewAgentProfile creates a new profile payload for the host agent 58 func NewAgentProfile(p *Profile) AgentProfile { 59 callSites := make([]AgentCallSite, 0, len(p.Roots)) 60 for _, root := range p.Roots { 61 callSites = append(callSites, NewAgentCallSite(root)) 62 } 63 64 return AgentProfile{ 65 ID: p.ID, 66 Runtime: p.Runtime, 67 Category: p.Category, 68 Type: p.Type, 69 Unit: p.Unit, 70 Roots: callSites, 71 Duration: p.Duration, 72 Timespan: p.Timespan, 73 Timestamp: p.Timestamp, 74 } 75 } 76 77 // Profile holds the gathered profiling data 78 type Profile struct { 79 ID string 80 Runtime string 81 Category string 82 Type string 83 Unit string 84 Roots []*CallSite 85 Duration int64 86 Timespan int64 87 Timestamp int64 88 } 89 90 // NewProfile inititalizes a new profile 91 func NewProfile(category string, typ string, unit string, roots []*CallSite, duration int64, timespan int64) *Profile { 92 return &Profile{ 93 ID: GenerateUUID(), 94 Runtime: RuntimeGolang, 95 Category: category, 96 Type: typ, 97 Unit: unit, 98 Roots: roots, 99 Duration: duration / int64(time.Millisecond), 100 Timespan: timespan * 1000, 101 Timestamp: time.Now().Unix() * 1000, 102 } 103 } 104 105 // AgentCallSite is a presenter type used to serialize a call site 106 // to JSON format supported by Instana profile sensor 107 type AgentCallSite struct { 108 MethodName string `json:"method_name"` 109 FileName string `json:"file_name"` 110 FileLine int64 `json:"file_line"` 111 Measurement float64 `json:"measurement"` 112 NumSamples int64 `json:"num_samples"` 113 Children []AgentCallSite `json:"children"` 114 } 115 116 // NewAgentCallSite initializes a new call site payload for the host agent 117 func NewAgentCallSite(cs *CallSite) AgentCallSite { 118 children := make([]AgentCallSite, 0, len(cs.children)) 119 for _, child := range cs.children { 120 children = append(children, NewAgentCallSite(child)) 121 } 122 123 m, ns := cs.Measurement() 124 125 return AgentCallSite{ 126 MethodName: cs.MethodName, 127 FileName: cs.FileName, 128 FileLine: cs.FileLine, 129 Measurement: m, 130 NumSamples: ns, 131 Children: children, 132 } 133 } 134 135 // CallSite represents a recorded method call 136 type CallSite struct { 137 MethodName string 138 FileName string 139 FileLine int64 140 Metadata map[string]string 141 measurement float64 142 numSamples int64 143 children map[string]*CallSite 144 updateLock *sync.RWMutex 145 } 146 147 // NewCallSite initializes a new CallSite 148 func NewCallSite(methodName string, fileName string, fileLine int64) *CallSite { 149 cn := &CallSite{ 150 MethodName: methodName, 151 FileName: fileName, 152 FileLine: fileLine, 153 children: make(map[string]*CallSite), 154 updateLock: &sync.RWMutex{}, 155 } 156 157 return cn 158 } 159 160 // FindOrAddChild adds a new subcall to a call tree. It returns the existing record the subcall already present 161 func (cs *CallSite) FindOrAddChild(methodName, fileName string, fileLine int64) *CallSite { 162 child := cs.findChild(methodName, fileName, fileLine) 163 if child == nil { 164 child = NewCallSite(methodName, fileName, fileLine) 165 cs.addChild(child) 166 } 167 168 return child 169 } 170 171 // Increment increases the sampled measurement while adding up the number of samples used 172 func (cs *CallSite) Increment(value float64, numSamples int64) { 173 cs.measurement += value 174 cs.numSamples += numSamples 175 } 176 177 // Measurement returns the sampled measurement along with the number of samples 178 func (cs *CallSite) Measurement() (value float64, numSamples int64) { 179 return cs.measurement, cs.numSamples 180 } 181 182 func (cs *CallSite) findChild(methodName, fileName string, fileLine int64) *CallSite { 183 cs.updateLock.RLock() 184 defer cs.updateLock.RUnlock() 185 186 if child, exists := cs.children[createKey(methodName, fileName, fileLine)]; exists { 187 return child 188 } 189 190 return nil 191 } 192 193 func (cs *CallSite) addChild(child *CallSite) { 194 cs.updateLock.Lock() 195 defer cs.updateLock.Unlock() 196 197 cs.children[createKey(child.MethodName, child.FileName, child.FileLine)] = child 198 } 199 200 func createKey(methodName, fileName string, fileLine int64) string { 201 var b bytes.Buffer 202 203 b.WriteString(methodName) 204 b.WriteString(" (") 205 b.WriteString(fileName) 206 b.WriteString(":") 207 b.WriteString(strconv.FormatInt(fileLine, 10)) 208 b.WriteString(")") 209 210 return b.String() 211 }