github.com/hxx258456/ccgo@v0.0.5-0.20230213014102-48b35f46f66f/internal/profile/merge.go (about) 1 // Copyright 2019 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package profile 6 7 import ( 8 "fmt" 9 "sort" 10 "strconv" 11 "strings" 12 ) 13 14 // Merge merges all the profiles in profs into a single Profile. 15 // Returns a new profile independent of the input profiles. The merged 16 // profile is compacted to eliminate unused samples, locations, 17 // functions and mappings. Profiles must have identical profile sample 18 // and period types or the merge will fail. profile.Period of the 19 // resulting profile will be the maximum of all profiles, and 20 // profile.TimeNanos will be the earliest nonzero one. 21 func Merge(srcs []*Profile) (*Profile, error) { 22 if len(srcs) == 0 { 23 return nil, fmt.Errorf("no profiles to merge") 24 } 25 p, err := combineHeaders(srcs) 26 if err != nil { 27 return nil, err 28 } 29 30 pm := &profileMerger{ 31 p: p, 32 samples: make(map[sampleKey]*Sample, len(srcs[0].Sample)), 33 locations: make(map[locationKey]*Location, len(srcs[0].Location)), 34 functions: make(map[functionKey]*Function, len(srcs[0].Function)), 35 mappings: make(map[mappingKey]*Mapping, len(srcs[0].Mapping)), 36 } 37 38 for _, src := range srcs { 39 // Clear the profile-specific hash tables 40 pm.locationsByID = make(map[uint64]*Location, len(src.Location)) 41 pm.functionsByID = make(map[uint64]*Function, len(src.Function)) 42 pm.mappingsByID = make(map[uint64]mapInfo, len(src.Mapping)) 43 44 if len(pm.mappings) == 0 && len(src.Mapping) > 0 { 45 // The Mapping list has the property that the first mapping 46 // represents the main binary. Take the first Mapping we see, 47 // otherwise the operations below will add mappings in an 48 // arbitrary order. 49 pm.mapMapping(src.Mapping[0]) 50 } 51 52 for _, s := range src.Sample { 53 if !isZeroSample(s) { 54 pm.mapSample(s) 55 } 56 } 57 } 58 59 for _, s := range p.Sample { 60 if isZeroSample(s) { 61 // If there are any zero samples, re-merge the profile to GC 62 // them. 63 return Merge([]*Profile{p}) 64 } 65 } 66 67 return p, nil 68 } 69 70 // Normalize normalizes the source profile by multiplying each value in profile by the 71 // ratio of the sum of the base profile's values of that sample type to the sum of the 72 // source profile's value of that sample type. 73 func (p *Profile) Normalize(pb *Profile) error { 74 75 if err := p.compatible(pb); err != nil { 76 return err 77 } 78 79 baseVals := make([]int64, len(p.SampleType)) 80 for _, s := range pb.Sample { 81 for i, v := range s.Value { 82 baseVals[i] += v 83 } 84 } 85 86 srcVals := make([]int64, len(p.SampleType)) 87 for _, s := range p.Sample { 88 for i, v := range s.Value { 89 srcVals[i] += v 90 } 91 } 92 93 normScale := make([]float64, len(baseVals)) 94 for i := range baseVals { 95 if srcVals[i] == 0 { 96 normScale[i] = 0.0 97 } else { 98 normScale[i] = float64(baseVals[i]) / float64(srcVals[i]) 99 } 100 } 101 p.ScaleN(normScale) 102 return nil 103 } 104 105 func isZeroSample(s *Sample) bool { 106 for _, v := range s.Value { 107 if v != 0 { 108 return false 109 } 110 } 111 return true 112 } 113 114 type profileMerger struct { 115 p *Profile 116 117 // Memoization tables within a profile. 118 locationsByID map[uint64]*Location 119 functionsByID map[uint64]*Function 120 mappingsByID map[uint64]mapInfo 121 122 // Memoization tables for profile entities. 123 samples map[sampleKey]*Sample 124 locations map[locationKey]*Location 125 functions map[functionKey]*Function 126 mappings map[mappingKey]*Mapping 127 } 128 129 type mapInfo struct { 130 m *Mapping 131 offset int64 132 } 133 134 func (pm *profileMerger) mapSample(src *Sample) *Sample { 135 s := &Sample{ 136 Location: make([]*Location, len(src.Location)), 137 Value: make([]int64, len(src.Value)), 138 Label: make(map[string][]string, len(src.Label)), 139 NumLabel: make(map[string][]int64, len(src.NumLabel)), 140 NumUnit: make(map[string][]string, len(src.NumLabel)), 141 } 142 for i, l := range src.Location { 143 s.Location[i] = pm.mapLocation(l) 144 } 145 for k, v := range src.Label { 146 vv := make([]string, len(v)) 147 copy(vv, v) 148 s.Label[k] = vv 149 } 150 for k, v := range src.NumLabel { 151 u := src.NumUnit[k] 152 vv := make([]int64, len(v)) 153 uu := make([]string, len(u)) 154 copy(vv, v) 155 copy(uu, u) 156 s.NumLabel[k] = vv 157 s.NumUnit[k] = uu 158 } 159 // Check memoization table. Must be done on the remapped location to 160 // account for the remapped mapping. Add current values to the 161 // existing sample. 162 k := s.key() 163 if ss, ok := pm.samples[k]; ok { 164 for i, v := range src.Value { 165 ss.Value[i] += v 166 } 167 return ss 168 } 169 copy(s.Value, src.Value) 170 pm.samples[k] = s 171 pm.p.Sample = append(pm.p.Sample, s) 172 return s 173 } 174 175 // key generates sampleKey to be used as a key for maps. 176 func (sample *Sample) key() sampleKey { 177 ids := make([]string, len(sample.Location)) 178 for i, l := range sample.Location { 179 ids[i] = strconv.FormatUint(l.ID, 16) 180 } 181 182 labels := make([]string, 0, len(sample.Label)) 183 for k, v := range sample.Label { 184 labels = append(labels, fmt.Sprintf("%q%q", k, v)) 185 } 186 sort.Strings(labels) 187 188 numlabels := make([]string, 0, len(sample.NumLabel)) 189 for k, v := range sample.NumLabel { 190 numlabels = append(numlabels, fmt.Sprintf("%q%x%x", k, v, sample.NumUnit[k])) 191 } 192 sort.Strings(numlabels) 193 194 return sampleKey{ 195 strings.Join(ids, "|"), 196 strings.Join(labels, ""), 197 strings.Join(numlabels, ""), 198 } 199 } 200 201 type sampleKey struct { 202 locations string 203 labels string 204 numlabels string 205 } 206 207 func (pm *profileMerger) mapLocation(src *Location) *Location { 208 if src == nil { 209 return nil 210 } 211 212 if l, ok := pm.locationsByID[src.ID]; ok { 213 pm.locationsByID[src.ID] = l 214 return l 215 } 216 217 mi := pm.mapMapping(src.Mapping) 218 l := &Location{ 219 ID: uint64(len(pm.p.Location) + 1), 220 Mapping: mi.m, 221 Address: uint64(int64(src.Address) + mi.offset), 222 Line: make([]Line, len(src.Line)), 223 IsFolded: src.IsFolded, 224 } 225 for i, ln := range src.Line { 226 l.Line[i] = pm.mapLine(ln) 227 } 228 // Check memoization table. Must be done on the remapped location to 229 // account for the remapped mapping ID. 230 k := l.key() 231 if ll, ok := pm.locations[k]; ok { 232 pm.locationsByID[src.ID] = ll 233 return ll 234 } 235 pm.locationsByID[src.ID] = l 236 pm.locations[k] = l 237 pm.p.Location = append(pm.p.Location, l) 238 return l 239 } 240 241 // key generates locationKey to be used as a key for maps. 242 func (l *Location) key() locationKey { 243 key := locationKey{ 244 addr: l.Address, 245 isFolded: l.IsFolded, 246 } 247 if l.Mapping != nil { 248 // Normalizes address to handle address space randomization. 249 key.addr -= l.Mapping.Start 250 key.mappingID = l.Mapping.ID 251 } 252 lines := make([]string, len(l.Line)*2) 253 for i, line := range l.Line { 254 if line.Function != nil { 255 lines[i*2] = strconv.FormatUint(line.Function.ID, 16) 256 } 257 lines[i*2+1] = strconv.FormatInt(line.Line, 16) 258 } 259 key.lines = strings.Join(lines, "|") 260 return key 261 } 262 263 type locationKey struct { 264 addr, mappingID uint64 265 lines string 266 isFolded bool 267 } 268 269 func (pm *profileMerger) mapMapping(src *Mapping) mapInfo { 270 if src == nil { 271 return mapInfo{} 272 } 273 274 if mi, ok := pm.mappingsByID[src.ID]; ok { 275 return mi 276 } 277 278 // Check memoization tables. 279 mk := src.key() 280 if m, ok := pm.mappings[mk]; ok { 281 mi := mapInfo{m, int64(m.Start) - int64(src.Start)} 282 pm.mappingsByID[src.ID] = mi 283 return mi 284 } 285 m := &Mapping{ 286 ID: uint64(len(pm.p.Mapping) + 1), 287 Start: src.Start, 288 Limit: src.Limit, 289 Offset: src.Offset, 290 File: src.File, 291 BuildID: src.BuildID, 292 HasFunctions: src.HasFunctions, 293 HasFilenames: src.HasFilenames, 294 HasLineNumbers: src.HasLineNumbers, 295 HasInlineFrames: src.HasInlineFrames, 296 } 297 pm.p.Mapping = append(pm.p.Mapping, m) 298 299 // Update memoization tables. 300 pm.mappings[mk] = m 301 mi := mapInfo{m, 0} 302 pm.mappingsByID[src.ID] = mi 303 return mi 304 } 305 306 // key generates encoded strings of Mapping to be used as a key for 307 // maps. 308 func (m *Mapping) key() mappingKey { 309 // Normalize addresses to handle address space randomization. 310 // Round up to next 4K boundary to avoid minor discrepancies. 311 const mapsizeRounding = 0x1000 312 313 size := m.Limit - m.Start 314 size = size + mapsizeRounding - 1 315 size = size - (size % mapsizeRounding) 316 key := mappingKey{ 317 size: size, 318 offset: m.Offset, 319 } 320 321 switch { 322 case m.BuildID != "": 323 key.buildIDOrFile = m.BuildID 324 case m.File != "": 325 key.buildIDOrFile = m.File 326 default: 327 // A mapping containing neither build ID nor file name is a fake mapping. A 328 // key with empty buildIDOrFile is used for fake mappings so that they are 329 // treated as the same mapping during merging. 330 } 331 return key 332 } 333 334 type mappingKey struct { 335 size, offset uint64 336 buildIDOrFile string 337 } 338 339 func (pm *profileMerger) mapLine(src Line) Line { 340 ln := Line{ 341 Function: pm.mapFunction(src.Function), 342 Line: src.Line, 343 } 344 return ln 345 } 346 347 func (pm *profileMerger) mapFunction(src *Function) *Function { 348 if src == nil { 349 return nil 350 } 351 if f, ok := pm.functionsByID[src.ID]; ok { 352 return f 353 } 354 k := src.key() 355 if f, ok := pm.functions[k]; ok { 356 pm.functionsByID[src.ID] = f 357 return f 358 } 359 f := &Function{ 360 ID: uint64(len(pm.p.Function) + 1), 361 Name: src.Name, 362 SystemName: src.SystemName, 363 Filename: src.Filename, 364 StartLine: src.StartLine, 365 } 366 pm.functions[k] = f 367 pm.functionsByID[src.ID] = f 368 pm.p.Function = append(pm.p.Function, f) 369 return f 370 } 371 372 // key generates a struct to be used as a key for maps. 373 func (f *Function) key() functionKey { 374 return functionKey{ 375 f.StartLine, 376 f.Name, 377 f.SystemName, 378 f.Filename, 379 } 380 } 381 382 type functionKey struct { 383 startLine int64 384 name, systemName, fileName string 385 } 386 387 // combineHeaders checks that all profiles can be merged and returns 388 // their combined profile. 389 func combineHeaders(srcs []*Profile) (*Profile, error) { 390 for _, s := range srcs[1:] { 391 if err := srcs[0].compatible(s); err != nil { 392 return nil, err 393 } 394 } 395 396 var timeNanos, durationNanos, period int64 397 var comments []string 398 seenComments := map[string]bool{} 399 var defaultSampleType string 400 for _, s := range srcs { 401 if timeNanos == 0 || s.TimeNanos < timeNanos { 402 timeNanos = s.TimeNanos 403 } 404 durationNanos += s.DurationNanos 405 if period == 0 || period < s.Period { 406 period = s.Period 407 } 408 for _, c := range s.Comments { 409 if seen := seenComments[c]; !seen { 410 comments = append(comments, c) 411 seenComments[c] = true 412 } 413 } 414 if defaultSampleType == "" { 415 defaultSampleType = s.DefaultSampleType 416 } 417 } 418 419 p := &Profile{ 420 SampleType: make([]*ValueType, len(srcs[0].SampleType)), 421 422 DropFrames: srcs[0].DropFrames, 423 KeepFrames: srcs[0].KeepFrames, 424 425 TimeNanos: timeNanos, 426 DurationNanos: durationNanos, 427 PeriodType: srcs[0].PeriodType, 428 Period: period, 429 430 Comments: comments, 431 DefaultSampleType: defaultSampleType, 432 } 433 copy(p.SampleType, srcs[0].SampleType) 434 return p, nil 435 } 436 437 // compatible determines if two profiles can be compared/merged. 438 // returns nil if the profiles are compatible; otherwise an error with 439 // details on the incompatibility. 440 func (p *Profile) compatible(pb *Profile) error { 441 if !equalValueType(p.PeriodType, pb.PeriodType) { 442 return fmt.Errorf("incompatible period types %v and %v", p.PeriodType, pb.PeriodType) 443 } 444 445 if len(p.SampleType) != len(pb.SampleType) { 446 return fmt.Errorf("incompatible sample types %v and %v", p.SampleType, pb.SampleType) 447 } 448 449 for i := range p.SampleType { 450 if !equalValueType(p.SampleType[i], pb.SampleType[i]) { 451 return fmt.Errorf("incompatible sample types %v and %v", p.SampleType, pb.SampleType) 452 } 453 } 454 return nil 455 } 456 457 // equalValueType returns true if the two value types are semantically 458 // equal. It ignores the internal fields used during encode/decode. 459 func equalValueType(st1, st2 *ValueType) bool { 460 return st1.Type == st2.Type && st1.Unit == st2.Unit 461 }