diff --git a/CHANGELOG.md b/CHANGELOG.md index fd1257d..c2a9a3a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,8 @@ **Internal** +- Remove iOS legacy profile support. ([#296](https://github.com/getsentry/vroom/pull/296)) + - Bump golang.org/x/net from 0.8.0 to 0.17.0 ([#335](https://github.com/getsentry/vroom/pull/335)) - Bump actions/github-script from 6.3.3 to 6.4.1 ([#342](https://github.com/getsentry/vroom/pull/342)) diff --git a/internal/profile/ios.go b/internal/profile/ios.go deleted file mode 100644 index 566dc86..0000000 --- a/internal/profile/ios.go +++ /dev/null @@ -1,431 +0,0 @@ -package profile - -import ( - "fmt" - "hash" - "hash/fnv" - "path" - "sort" - "strconv" - - "github.com/getsentry/vroom/internal/frame" - "github.com/getsentry/vroom/internal/nodetree" - "github.com/getsentry/vroom/internal/packageutil" - "github.com/getsentry/vroom/internal/speedscope" -) - -type IosFrame struct { - AbsPath string `json:"abs_path,omitempty"` - Filename string `json:"filename,omitempty"` - Function string `json:"function,omitempty"` - InstructionAddr string `json:"instruction_addr,omitempty"` - Lang string `json:"lang,omitempty"` - LineNo uint32 `json:"lineno,omitempty"` - OriginalIndex int `json:"original_index,omitempty"` - Package string `json:"package"` - Status string `json:"status,omitempty"` - SymAddr string `json:"sym_addr,omitempty"` - Symbol string `json:"symbol,omitempty"` -} - -func (f IosFrame) Frame() frame.Frame { - inApp := packageutil.IsCocoaApplicationPackage(f.Package) - return frame.Frame{ - Data: frame.Data{ - SymbolicatorStatus: f.Status, - }, - File: f.Filename, - Function: f.Function, - InApp: &inApp, - InstructionAddr: f.InstructionAddr, - Lang: f.Lang, - Line: f.LineNo, - Package: f.PackageBaseName(), - Path: f.AbsPath, - SymAddr: f.SymAddr, - Symbol: f.Symbol, - } -} - -// IsMain returns true if the function is considered the main function. -// It also returns an offset indicate if we need to keep the previous frame or not. -func (f IosFrame) IsMain() (bool, int) { - if f.Status != "symbolicated" { - return false, 0 - } else if f.Function == "main" { - return true, 0 - } else if f.Function == "UIApplicationMain" { - return true, -1 - } - return false, 0 -} - -func (f IosFrame) WriteToHash(h hash.Hash) { - if f.Package == "" && f.Function == "" { - h.Write([]byte("-")) - } else { - h.Write([]byte(f.PackageBaseName())) - h.Write([]byte(f.Function)) - } -} - -func (f IosFrame) PackageBaseName() string { - if f.Package == "" { - return "" - } - return path.Base(f.Package) -} - -func (f IosFrame) Address() string { - if f.SymAddr != "" { - return f.SymAddr - } - return f.InstructionAddr -} - -type Sample struct { - Frames []IosFrame `json:"frames,omitempty"` - Priority int `json:"priority,omitempty"` - QueueAddress string `json:"queue_address,omitempty"` - RelativeTimestampNS uint64 `json:"relative_timestamp_ns,omitempty"` - State string `json:"state,omitempty"` - ThreadID uint64 `json:"thread_id,omitempty"` -} - -func (s Sample) ContainsMain() bool { - for i := len(s.Frames) - 1; i >= 0; i-- { - isMain, _ := s.Frames[i].IsMain() - if isMain { - return true - } - } - return false -} - -type IOS struct { - QueueMetadata map[string]QueueMetadata `json:"queue_metadata"` - Samples []Sample `json:"samples"` - ThreadMetadata map[string]ThreadMetadata `json:"thread_metadata"` -} - -type candidate struct { - ThreadID uint64 - FrameCount int -} - -// ActiveThreadID returns what we believe is the main thread ID in the profile. -func (p IOS) ActiveThreadID() uint64 { - // Use metadata - for threadID, m := range p.ThreadMetadata { - if m.IsMain { - id, err := strconv.ParseUint(threadID, 10, 64) - if err != nil { - break - } - return id - } - } - - // Check for a main frame - queues := make(map[uint64]map[QueueMetadata]int) - for _, s := range p.Samples { - var isMain bool - for _, f := range s.Frames { - if isMain, _ = f.IsMain(); isMain { - // If we found a frame identified as a main frame, we're sure it's the main thread - return s.ThreadID - } - } - // Otherwise, we collect queue information to select which queue seems the right one - if tq, qExists := p.QueueMetadata[s.QueueAddress]; qExists { - if qm, qmExists := queues[s.ThreadID]; !qmExists { - queues[s.ThreadID] = make(map[QueueMetadata]int) - } else { - frameCount := len(s.Frames) - if q, qExists := qm[tq]; !qExists { - qm[tq] = frameCount - } else if q < frameCount { - qm[tq] = frameCount - } - } - } - } - // Check for the right queue name - var candidates []candidate - for threadID, qm := range queues { - // Only threads with 1 main queue are considered - if len(qm) == 1 { - for q, frameCount := range qm { - if q.LabeledAsMainThread() { - candidates = append(candidates, candidate{threadID, frameCount}) - } - } - } - } - // Whoops - if len(candidates) == 0 { - return 0 - } - // Sort possible candidates by deepest stack then lowest thread ID - sort.Slice(candidates, func(i, j int) bool { - if candidates[i].FrameCount == candidates[j].FrameCount { - return candidates[i].ThreadID < candidates[j].ThreadID - } - return candidates[i].FrameCount > candidates[j].FrameCount - }) - return candidates[0].ThreadID -} - -func (p IOS) CallTrees() map[uint64][]*nodetree.Node { - sort.Slice(p.Samples, func(i, j int) bool { - return p.Samples[i].RelativeTimestampNS < p.Samples[j].RelativeTimestampNS - }) - - activeThreadID := p.ActiveThreadID() - - var current *nodetree.Node - trees := make(map[uint64][]*nodetree.Node) - h := fnv.New64() - previousTimestamp := make(map[uint64]uint64) - for _, s := range p.Samples { - if s.ThreadID != activeThreadID { - continue - } - - frameCount := len(s.Frames) - // Filter out a bogus root address that appears in some iOS backtraces, this symbol - // can never be symbolicated and usually contains 1 child. - if frameCount > 2 && s.Frames[frameCount-1].InstructionAddr == "0xffffffffc" { - s.Frames = s.Frames[:frameCount-2] - } - for i := len(s.Frames) - 1; i >= 0; i-- { - f := s.Frames[i] - f.WriteToHash(h) - fingerprint := h.Sum64() - if current == nil { - i := len(trees[s.ThreadID]) - 1 - if i >= 0 && trees[s.ThreadID][i].Fingerprint == fingerprint && - trees[s.ThreadID][i].EndNS == previousTimestamp[s.ThreadID] { - current = trees[s.ThreadID][i] - current.Update(s.RelativeTimestampNS) - } else { - n := nodetree.NodeFromFrame(f.Frame(), previousTimestamp[s.ThreadID], s.RelativeTimestampNS, fingerprint) - trees[s.ThreadID] = append(trees[s.ThreadID], n) - current = n - } - } else { - i := len(current.Children) - 1 - if i >= 0 && current.Children[i].Fingerprint == fingerprint && current.Children[i].EndNS == previousTimestamp[s.ThreadID] { - current = current.Children[i] - current.Update(s.RelativeTimestampNS) - } else { - n := nodetree.NodeFromFrame(f.Frame(), previousTimestamp[s.ThreadID], s.RelativeTimestampNS, fingerprint) - current.Children = append(current.Children, n) - current = n - } - } - } - h.Reset() - previousTimestamp[s.ThreadID] = s.RelativeTimestampNS - current = nil - } - - return trees -} - -func (p IOS) FindNextActiveSample(threadID uint64, i int) Sample { - for ; i < len(p.Samples); i++ { - if p.Samples[i].ThreadID == threadID && len(p.Samples[i].Frames) != 0 { - return p.Samples[i] - } - } - return Sample{} -} - -func findCommonFrames(a, b []IosFrame) []IosFrame { - var c []IosFrame - for i, j := len(a)-1, len(b)-1; i >= 0 && j >= 0; i, j = i-1, j-1 { - if a[i].SymAddr == b[j].SymAddr { - c = append(c, a[i]) - continue - } - break - } - reverse(c) - return c -} - -func reverse(a []IosFrame) { - for i, j := 0, len(a)-1; i < j; i, j = i+1, j-1 { - a[i], a[j] = a[j], a[i] - } -} - -func (p *IOS) ReplaceIdleStacks() { - previousActiveSamplePerThreadID := make(map[uint64]int) - - for i, s := range p.Samples { - if len(s.Frames) != 0 { - // keep track of the previous active sample as we go - previousActiveSamplePerThreadID[s.ThreadID] = i - continue - } - - // if there's no frame, the thread is considired idle - p.Samples[i].State = "idle" - - previousSample, exists := previousActiveSamplePerThreadID[s.ThreadID] - if !exists { - continue - } - - previousFrames := p.Samples[previousSample].Frames - nextFrames := p.FindNextActiveSample(s.ThreadID, i).Frames - if len(previousFrames) == 0 || len(nextFrames) == 0 { - continue - } - - common := findCommonFrames(previousFrames, nextFrames) - - // replace all idle stacks until next active sample - for j := i; j < len(p.Samples); j++ { - if p.Samples[j].ThreadID == s.ThreadID { - if len(p.Samples[j].Frames) != 0 { - break - } - p.Samples[j].Frames = common - } - } - } -} - -type ThreadMetadata struct { - IsMain bool `json:"is_main_thread,omitempty"` - Name string `json:"name,omitempty"` - Priority int `json:"priority,omitempty"` -} - -type QueueMetadata struct { - Label string `json:"label"` -} - -func (q QueueMetadata) LabeledAsMainThread() bool { - return q.Label == "com.apple.main-thread" -} - -func (p IOS) Speedscope() (speedscope.Output, error) { - threadIDToProfile := make(map[uint64]*speedscope.SampledProfile) - addressToFrameIndex := make(map[string]int) - threadIDToPreviousTimestampNS := make(map[uint64]uint64) - frames := make([]speedscope.Frame, 0) - // we need to find the frame index of the main function so we can remove the frames before it - mainFunctionFrameIndex := -1 - mainThreadID := p.ActiveThreadID() - for _, sample := range p.Samples { - threadID := strconv.FormatUint(sample.ThreadID, 10) - sampProfile, ok := threadIDToProfile[sample.ThreadID] - queueMetadata, qmExists := p.QueueMetadata[sample.QueueAddress] - if !ok { - threadMetadata, tmExists := p.ThreadMetadata[threadID] - threadName := threadMetadata.Name - if threadName == "" && qmExists && - (!queueMetadata.LabeledAsMainThread() || sample.ThreadID != mainThreadID) { - threadName = queueMetadata.Label - } - sampProfile = &speedscope.SampledProfile{ - Name: threadName, - Queues: make(map[string]speedscope.Queue), - StartValue: sample.RelativeTimestampNS, - ThreadID: sample.ThreadID, - IsMainThread: sample.ThreadID == mainThreadID, - Type: speedscope.ProfileTypeSampled, - Unit: speedscope.ValueUnitNanoseconds, - } - if qmExists { - sampProfile.Queues[queueMetadata.Label] = speedscope.Queue{ - Label: queueMetadata.Label, - StartNS: sample.RelativeTimestampNS, - EndNS: sample.RelativeTimestampNS, - } - } - if tmExists { - sampProfile.Priority = threadMetadata.Priority - } - threadIDToProfile[sample.ThreadID] = sampProfile - } else { - if qmExists { - q, qExists := sampProfile.Queues[queueMetadata.Label] - if !qExists { - sampProfile.Queues[queueMetadata.Label] = speedscope.Queue{Label: queueMetadata.Label, StartNS: sample.RelativeTimestampNS, EndNS: sample.RelativeTimestampNS} - } else { - q.EndNS = sample.RelativeTimestampNS - sampProfile.Queues[queueMetadata.Label] = q - } - } - sampProfile.Weights = append(sampProfile.Weights, sample.RelativeTimestampNS-threadIDToPreviousTimestampNS[sample.ThreadID]) - } - - sampProfile.EndValue = sample.RelativeTimestampNS - threadIDToPreviousTimestampNS[sample.ThreadID] = sample.RelativeTimestampNS - - samp := make([]int, 0, len(sample.Frames)) - for i := len(sample.Frames) - 1; i >= 0; i-- { - fr := sample.Frames[i] - address := fr.Address() - frameIndex, ok := addressToFrameIndex[address] - if !ok { - frameIndex = len(frames) - symbolName := fr.Function - if symbolName == "" { - symbolName = fmt.Sprintf("unknown (%s)", address) - } else if mainFunctionFrameIndex == -1 { - if isMainFrame, i := fr.IsMain(); isMainFrame { - mainFunctionFrameIndex = frameIndex + i - } - } - addressToFrameIndex[address] = frameIndex - frames = append(frames, speedscope.Frame{ - File: fr.Filename, - Image: fr.PackageBaseName(), - IsApplication: packageutil.IsCocoaApplicationPackage(fr.Package), - Line: fr.LineNo, - Name: symbolName, - }) - } - samp = append(samp, frameIndex) - } - sampProfile.Samples = append(sampProfile.Samples, samp) - } // end loop speedscope.SampledProfiles - var mainThreadProfileIndex int - allProfiles := make([]interface{}, 0) - for _, prof := range threadIDToProfile { - if prof.IsMainThread { - mainThreadProfileIndex = len(allProfiles) - // Remove all frames before main is called on the main thread - if mainFunctionFrameIndex != -1 { - for i, sample := range prof.Samples { - for j, frameIndex := range sample { - if frameIndex == mainFunctionFrameIndex { - prof.Samples[i] = prof.Samples[i][j:] - break - } - } - } - } - } - prof.Weights = append(prof.Weights, 0) - allProfiles = append(allProfiles, prof) - } - return speedscope.Output{ - ActiveProfileIndex: mainThreadProfileIndex, - Profiles: allProfiles, - Shared: speedscope.SharedData{Frames: frames}, - }, nil -} - -func (p IOS) DurationNS() uint64 { - if len(p.Samples) == 0 { - return 0 - } - return p.Samples[len(p.Samples)-1].RelativeTimestampNS - p.Samples[0].RelativeTimestampNS -} diff --git a/internal/profile/ios_test.go b/internal/profile/ios_test.go deleted file mode 100644 index 6e5de6f..0000000 --- a/internal/profile/ios_test.go +++ /dev/null @@ -1,1950 +0,0 @@ -package profile - -import ( - "encoding/json" - "os" - "testing" - - "github.com/getsentry/vroom/internal/frame" - "github.com/getsentry/vroom/internal/nodetree" - "github.com/getsentry/vroom/internal/testutil" -) - -var ( - falseValue = false -) - -func TestCallTreeGenerationFromSingleThreadedSamples(t *testing.T) { - tests := []struct { - name string - profile IOS - want map[uint64][]*nodetree.Node - }{ - { - name: "single root call tree", - profile: IOS{ - Samples: []Sample{ - { - ThreadID: 1, - RelativeTimestampNS: 1, - Frames: []IosFrame{ - {InstructionAddr: "2", Function: "symbol2"}, - {InstructionAddr: "1", Function: "symbol1"}, - {InstructionAddr: "0", Function: "symbol0"}, - }, - }, - }, - ThreadMetadata: map[string]ThreadMetadata{ - "1": {IsMain: true}, - }, - }, - want: map[uint64][]*nodetree.Node{ - 1: { - { - ProfileIDs: make(map[string]struct{}), - DurationNS: 1, - EndNS: 1, - Fingerprint: 1124161485517443911, - Name: "symbol0", - SampleCount: 1, - Frame: frame.Frame{ - Function: "symbol0", - InstructionAddr: "0", - InApp: &falseValue, - }, - Children: []*nodetree.Node{ - { - ProfileIDs: make(map[string]struct{}), - DurationNS: 1, - EndNS: 1, - Fingerprint: 17905447077897174944, - Name: "symbol1", - SampleCount: 1, - Frame: frame.Frame{ - Function: "symbol1", - InstructionAddr: "1", - InApp: &falseValue, - }, - Children: []*nodetree.Node{ - { - ProfileIDs: make(map[string]struct{}), - DurationNS: 1, - EndNS: 1, - Fingerprint: 16084607411097338726, - Name: "symbol2", - SampleCount: 1, - Frame: frame.Frame{ - Function: "symbol2", - InstructionAddr: "2", - InApp: &falseValue, - }, - }, - }, - }, - }, - }, - }, - }, - }, - { - name: "multiple root call trees", - profile: IOS{ - Samples: []Sample{ - { - ThreadID: 1, - RelativeTimestampNS: 1, - Frames: []IosFrame{ - {InstructionAddr: "2", Function: "symbol2"}, - {InstructionAddr: "1", Function: "symbol1"}, - {InstructionAddr: "0", Function: "symbol0"}, - }, - }, - { - ThreadID: 1, - RelativeTimestampNS: 2, - Frames: []IosFrame{ - {InstructionAddr: "5", Function: "symbol5"}, - {InstructionAddr: "4", Function: "symbol4"}, - {InstructionAddr: "3", Function: "symbol3"}, - }, - }, - }, - ThreadMetadata: map[string]ThreadMetadata{ - "1": {IsMain: true}, - }, - }, - want: map[uint64][]*nodetree.Node{ - 1: { - { - ProfileIDs: make(map[string]struct{}), - DurationNS: 1, - EndNS: 1, - Fingerprint: 1124161485517443911, - Name: "symbol0", - SampleCount: 1, - Frame: frame.Frame{ - Function: "symbol0", - InstructionAddr: "0", - InApp: &falseValue, - }, - Children: []*nodetree.Node{ - { - ProfileIDs: make(map[string]struct{}), - DurationNS: 1, - EndNS: 1, - Fingerprint: 17905447077897174944, - Name: "symbol1", - SampleCount: 1, - Frame: frame.Frame{ - Function: "symbol1", - InstructionAddr: "1", - InApp: &falseValue, - }, - Children: []*nodetree.Node{ - { - ProfileIDs: make(map[string]struct{}), - DurationNS: 1, - EndNS: 1, - Fingerprint: 16084607411097338726, - Name: "symbol2", - SampleCount: 1, - Frame: frame.Frame{ - Function: "symbol2", - InstructionAddr: "2", - InApp: &falseValue, - }, - }, - }, - }, - }, - }, - { - ProfileIDs: make(map[string]struct{}), - DurationNS: 1, - EndNS: 2, - Fingerprint: 1124161485517443908, - Name: "symbol3", - SampleCount: 1, - StartNS: 1, - Frame: frame.Frame{ - Function: "symbol3", - InstructionAddr: "3", - InApp: &falseValue, - }, - Children: []*nodetree.Node{ - { - ProfileIDs: make(map[string]struct{}), - DurationNS: 1, - EndNS: 2, - Fingerprint: 7967440964543288636, - Name: "symbol4", - SampleCount: 1, - StartNS: 1, - Frame: frame.Frame{ - Function: "symbol4", - InstructionAddr: "4", - InApp: &falseValue, - }, - Children: []*nodetree.Node{ - { - ProfileIDs: make(map[string]struct{}), - DurationNS: 1, - EndNS: 2, - Fingerprint: 13274796176329250277, - Name: "symbol5", - SampleCount: 1, - StartNS: 1, - Frame: frame.Frame{ - Function: "symbol5", - InstructionAddr: "5", - InApp: &falseValue, - }, - }, - }, - }, - }, - }, - }, - }, - }, - { - name: "single root call tree with disappearing leaf", - profile: IOS{ - Samples: []Sample{ - { - ThreadID: 1, - RelativeTimestampNS: 1, - Frames: []IosFrame{ - {InstructionAddr: "2", Function: "symbol2"}, - {InstructionAddr: "1", Function: "symbol1"}, - {InstructionAddr: "0", Function: "symbol0"}, - }, - }, - { - ThreadID: 1, - RelativeTimestampNS: 2, - Frames: []IosFrame{ - {InstructionAddr: "1", Function: "symbol1"}, - {InstructionAddr: "0", Function: "symbol0"}, - }, - }, - }, - ThreadMetadata: map[string]ThreadMetadata{ - "1": {IsMain: true}, - }, - }, - want: map[uint64][]*nodetree.Node{ - 1: { - { - ProfileIDs: make(map[string]struct{}), - DurationNS: 2, - EndNS: 2, - Fingerprint: 1124161485517443911, - Name: "symbol0", - SampleCount: 2, - Frame: frame.Frame{ - Function: "symbol0", - InstructionAddr: "0", - InApp: &falseValue, - }, - Children: []*nodetree.Node{ - { - ProfileIDs: make(map[string]struct{}), - DurationNS: 2, - EndNS: 2, - Fingerprint: 17905447077897174944, - Name: "symbol1", - SampleCount: 2, - Frame: frame.Frame{ - Function: "symbol1", - InstructionAddr: "1", - InApp: &falseValue, - }, - Children: []*nodetree.Node{ - { - ProfileIDs: make(map[string]struct{}), - DurationNS: 1, - EndNS: 1, - Fingerprint: 16084607411097338726, - Name: "symbol2", - SampleCount: 1, - Frame: frame.Frame{ - Function: "symbol2", - InstructionAddr: "2", - InApp: &falseValue, - }, - }, - }, - }, - }, - }, - }, - }, - }, - { - name: "single root call tree with appearing leaf", - profile: IOS{ - Samples: []Sample{ - { - ThreadID: 1, - RelativeTimestampNS: 1, - Frames: []IosFrame{ - {InstructionAddr: "1", Function: "symbol1"}, - {InstructionAddr: "0", Function: "symbol0"}, - }, - }, - { - ThreadID: 1, - RelativeTimestampNS: 2, - Frames: []IosFrame{ - {InstructionAddr: "2", Function: "symbol2"}, - {InstructionAddr: "1", Function: "symbol1"}, - {InstructionAddr: "0", Function: "symbol0"}, - }, - }, - }, - ThreadMetadata: map[string]ThreadMetadata{ - "1": {IsMain: true}, - }, - }, - want: map[uint64][]*nodetree.Node{ - 1: { - { - ProfileIDs: make(map[string]struct{}), - DurationNS: 2, - EndNS: 2, - Fingerprint: 1124161485517443911, - Name: "symbol0", - SampleCount: 2, - Frame: frame.Frame{ - Function: "symbol0", - InstructionAddr: "0", - InApp: &falseValue, - }, - Children: []*nodetree.Node{ - { - ProfileIDs: make(map[string]struct{}), - DurationNS: 2, - EndNS: 2, - Fingerprint: 17905447077897174944, - Name: "symbol1", - SampleCount: 2, - Frame: frame.Frame{ - Function: "symbol1", - InstructionAddr: "1", - InApp: &falseValue, - }, - Children: []*nodetree.Node{ - { - ProfileIDs: make(map[string]struct{}), - DurationNS: 1, - EndNS: 2, - Fingerprint: 16084607411097338726, - Name: "symbol2", - SampleCount: 1, - StartNS: 1, - Frame: frame.Frame{ - Function: "symbol2", - InstructionAddr: "2", - InApp: &falseValue, - }, - }, - }, - }, - }, - }, - }, - }, - }, - { - name: "single root call tree with repeated disappearing leaf", - profile: IOS{ - Samples: []Sample{ - { - ThreadID: 1, - RelativeTimestampNS: 1, - Frames: []IosFrame{ - {InstructionAddr: "2", Function: "symbol2"}, - {InstructionAddr: "1", Function: "symbol1"}, - {InstructionAddr: "0", Function: "symbol0"}, - }, - }, - { - ThreadID: 1, - RelativeTimestampNS: 2, - Frames: []IosFrame{ - {InstructionAddr: "1", Function: "symbol1"}, - {InstructionAddr: "0", Function: "symbol0"}, - }, - }, - { - ThreadID: 1, - RelativeTimestampNS: 3, - Frames: []IosFrame{ - {InstructionAddr: "2", Function: "symbol2"}, - {InstructionAddr: "1", Function: "symbol1"}, - {InstructionAddr: "0", Function: "symbol0"}, - }, - }, - { - ThreadID: 1, - RelativeTimestampNS: 4, - Frames: []IosFrame{ - {InstructionAddr: "1", Function: "symbol1"}, - {InstructionAddr: "0", Function: "symbol0"}, - }, - }, - }, - ThreadMetadata: map[string]ThreadMetadata{ - "1": {IsMain: true}, - }, - }, - want: map[uint64][]*nodetree.Node{ - 1: { - { - ProfileIDs: make(map[string]struct{}), - DurationNS: 4, - EndNS: 4, - Fingerprint: 1124161485517443911, - Name: "symbol0", - SampleCount: 4, - Frame: frame.Frame{ - Function: "symbol0", - InstructionAddr: "0", - InApp: &falseValue, - }, - Children: []*nodetree.Node{ - { - ProfileIDs: make(map[string]struct{}), - DurationNS: 4, - EndNS: 4, - Fingerprint: 17905447077897174944, - Name: "symbol1", - SampleCount: 4, - Frame: frame.Frame{ - Function: "symbol1", - InstructionAddr: "1", - InApp: &falseValue, - }, - Children: []*nodetree.Node{ - { - ProfileIDs: make(map[string]struct{}), - DurationNS: 1, - EndNS: 1, - Fingerprint: 16084607411097338726, - Name: "symbol2", - SampleCount: 1, - Frame: frame.Frame{ - Function: "symbol2", - InstructionAddr: "2", - InApp: &falseValue, - }, - }, - { - ProfileIDs: make(map[string]struct{}), - DurationNS: 1, - EndNS: 3, - StartNS: 2, - Fingerprint: 16084607411097338726, - Name: "symbol2", - SampleCount: 1, - Frame: frame.Frame{ - Function: "symbol2", - InstructionAddr: "2", - InApp: &falseValue, - }, - }, - }, - }, - }, - }, - }, - }, - }, - { - name: "single root call tree with repeated appearing leaf", - profile: IOS{ - Samples: []Sample{ - { - ThreadID: 1, - RelativeTimestampNS: 1, - Frames: []IosFrame{ - {InstructionAddr: "1", Function: "symbol1"}, - {InstructionAddr: "0", Function: "symbol0"}, - }, - }, - { - ThreadID: 1, - RelativeTimestampNS: 2, - Frames: []IosFrame{ - {InstructionAddr: "2", Function: "symbol2"}, - {InstructionAddr: "1", Function: "symbol1"}, - {InstructionAddr: "0", Function: "symbol0"}, - }, - }, - { - ThreadID: 1, - RelativeTimestampNS: 3, - Frames: []IosFrame{ - {InstructionAddr: "1", Function: "symbol1"}, - {InstructionAddr: "0", Function: "symbol0"}, - }, - }, - { - ThreadID: 1, - RelativeTimestampNS: 4, - Frames: []IosFrame{ - {InstructionAddr: "2", Function: "symbol2"}, - {InstructionAddr: "1", Function: "symbol1"}, - {InstructionAddr: "0", Function: "symbol0"}, - }, - }, - }, - ThreadMetadata: map[string]ThreadMetadata{ - "1": {IsMain: true}, - }, - }, - want: map[uint64][]*nodetree.Node{ - 1: { - { - ProfileIDs: make(map[string]struct{}), - DurationNS: 4, - EndNS: 4, - Fingerprint: 1124161485517443911, - Name: "symbol0", - SampleCount: 4, - Frame: frame.Frame{ - Function: "symbol0", - InstructionAddr: "0", - InApp: &falseValue, - }, - Children: []*nodetree.Node{ - { - ProfileIDs: make(map[string]struct{}), - DurationNS: 4, - EndNS: 4, - Fingerprint: 17905447077897174944, - Name: "symbol1", - SampleCount: 4, - Frame: frame.Frame{ - Function: "symbol1", - InstructionAddr: "1", - InApp: &falseValue, - }, - Children: []*nodetree.Node{ - { - ProfileIDs: make(map[string]struct{}), - DurationNS: 1, - EndNS: 2, - Fingerprint: 16084607411097338726, - Name: "symbol2", - SampleCount: 1, - StartNS: 1, - Frame: frame.Frame{ - Function: "symbol2", - InstructionAddr: "2", - InApp: &falseValue, - }, - }, - { - ProfileIDs: make(map[string]struct{}), - DurationNS: 1, - EndNS: 4, - Fingerprint: 16084607411097338726, - Name: "symbol2", - SampleCount: 1, - StartNS: 3, - Frame: frame.Frame{ - Function: "symbol2", - InstructionAddr: "2", - InApp: &falseValue, - }, - }, - }, - }, - }, - }, - }, - }, - }, - { - name: "single root call tree with disappearing leaves", - profile: IOS{ - Samples: []Sample{ - { - ThreadID: 1, - RelativeTimestampNS: 1, - Frames: []IosFrame{ - {InstructionAddr: "3", Function: "symbol3"}, - {InstructionAddr: "2", Function: "symbol2"}, - {InstructionAddr: "1", Function: "symbol1"}, - {InstructionAddr: "0", Function: "symbol0"}, - }, - }, - { - ThreadID: 1, - RelativeTimestampNS: 2, - Frames: []IosFrame{ - {InstructionAddr: "2", Function: "symbol2"}, - {InstructionAddr: "1", Function: "symbol1"}, - {InstructionAddr: "0", Function: "symbol0"}, - }, - }, - { - ThreadID: 1, - RelativeTimestampNS: 3, - Frames: []IosFrame{ - {InstructionAddr: "1", Function: "symbol1"}, - {InstructionAddr: "0", Function: "symbol0"}, - }, - }, - { - ThreadID: 1, - RelativeTimestampNS: 4, - Frames: []IosFrame{ - {InstructionAddr: "0", Function: "symbol0"}, - }, - }, - }, - ThreadMetadata: map[string]ThreadMetadata{ - "1": {IsMain: true}, - }, - }, - want: map[uint64][]*nodetree.Node{ - 1: { - { - ProfileIDs: make(map[string]struct{}), - DurationNS: 4, - EndNS: 4, - Fingerprint: 1124161485517443911, - Name: "symbol0", - SampleCount: 4, - Frame: frame.Frame{ - Function: "symbol0", - InstructionAddr: "0", - InApp: &falseValue, - }, - Children: []*nodetree.Node{ - { - ProfileIDs: make(map[string]struct{}), - DurationNS: 3, - EndNS: 3, - Fingerprint: 17905447077897174944, - Name: "symbol1", - SampleCount: 3, - Frame: frame.Frame{ - Function: "symbol1", - InstructionAddr: "1", - InApp: &falseValue, - }, - Children: []*nodetree.Node{ - { - ProfileIDs: make(map[string]struct{}), - DurationNS: 2, - EndNS: 2, - Fingerprint: 16084607411097338726, - Name: "symbol2", - SampleCount: 2, - Frame: frame.Frame{ - Function: "symbol2", - InstructionAddr: "2", - InApp: &falseValue, - }, - Children: []*nodetree.Node{ - { - ProfileIDs: make(map[string]struct{}), - DurationNS: 1, - EndNS: 1, - Fingerprint: 14019447401716285969, - Name: "symbol3", - SampleCount: 1, - Frame: frame.Frame{ - Function: "symbol3", - InstructionAddr: "3", - InApp: &falseValue, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - { - name: "single root call tree with appearing leaves", - profile: IOS{ - Samples: []Sample{ - { - ThreadID: 1, - RelativeTimestampNS: 1, - Frames: []IosFrame{ - {InstructionAddr: "0", Function: "symbol0"}, - }, - }, - { - ThreadID: 1, - RelativeTimestampNS: 2, - Frames: []IosFrame{ - {InstructionAddr: "1", Function: "symbol1"}, - {InstructionAddr: "0", Function: "symbol0"}, - }, - }, - { - ThreadID: 1, - RelativeTimestampNS: 3, - Frames: []IosFrame{ - {InstructionAddr: "2", Function: "symbol2"}, - {InstructionAddr: "1", Function: "symbol1"}, - {InstructionAddr: "0", Function: "symbol0"}, - }, - }, - { - ThreadID: 1, - RelativeTimestampNS: 4, - Frames: []IosFrame{ - {InstructionAddr: "3", Function: "symbol3"}, - {InstructionAddr: "2", Function: "symbol2"}, - {InstructionAddr: "1", Function: "symbol1"}, - {InstructionAddr: "0", Function: "symbol0"}, - }, - }, - }, - ThreadMetadata: map[string]ThreadMetadata{ - "1": {IsMain: true}, - }, - }, - want: map[uint64][]*nodetree.Node{ - 1: { - { - ProfileIDs: make(map[string]struct{}), - DurationNS: 4, - EndNS: 4, - Fingerprint: 1124161485517443911, - Name: "symbol0", - SampleCount: 4, - Frame: frame.Frame{ - Function: "symbol0", - InstructionAddr: "0", - InApp: &falseValue, - }, - Children: []*nodetree.Node{ - { - ProfileIDs: make(map[string]struct{}), - DurationNS: 3, - EndNS: 4, - Fingerprint: 17905447077897174944, - Name: "symbol1", - SampleCount: 3, - StartNS: 1, - Frame: frame.Frame{ - Function: "symbol1", - InstructionAddr: "1", - InApp: &falseValue, - }, - Children: []*nodetree.Node{ - { - ProfileIDs: make(map[string]struct{}), - DurationNS: 2, - EndNS: 4, - Fingerprint: 16084607411097338726, - Name: "symbol2", - SampleCount: 2, - StartNS: 2, - Frame: frame.Frame{ - Function: "symbol2", - InstructionAddr: "2", - InApp: &falseValue, - }, - Children: []*nodetree.Node{ - { - ProfileIDs: make(map[string]struct{}), - DurationNS: 1, - EndNS: 4, - Fingerprint: 14019447401716285969, - Name: "symbol3", - SampleCount: 1, - StartNS: 3, - Frame: frame.Frame{ - Function: "symbol3", - InstructionAddr: "3", - InApp: &falseValue, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - }, - { - name: "single root call tree with multiple unique leaves", - profile: IOS{ - Samples: []Sample{ - { - ThreadID: 1, - RelativeTimestampNS: 1, - Frames: []IosFrame{ - {InstructionAddr: "1", Function: "symbol1"}, - {InstructionAddr: "0", Function: "symbol0"}, - }, - }, - { - ThreadID: 1, - RelativeTimestampNS: 2, - Frames: []IosFrame{ - {InstructionAddr: "2", Function: "symbol2"}, - {InstructionAddr: "1", Function: "symbol1"}, - {InstructionAddr: "0", Function: "symbol0"}, - }, - }, - { - ThreadID: 1, - RelativeTimestampNS: 3, - Frames: []IosFrame{ - {InstructionAddr: "3", Function: "symbol3"}, - {InstructionAddr: "1", Function: "symbol1"}, - {InstructionAddr: "0", Function: "symbol0"}, - }, - }, - { - ThreadID: 1, - RelativeTimestampNS: 4, - Frames: []IosFrame{ - {InstructionAddr: "4", Function: "symbol4"}, - {InstructionAddr: "1", Function: "symbol1"}, - {InstructionAddr: "0", Function: "symbol0"}, - }, - }, - }, - ThreadMetadata: map[string]ThreadMetadata{ - "1": {IsMain: true}, - }, - }, - want: map[uint64][]*nodetree.Node{ - 1: { - { - ProfileIDs: make(map[string]struct{}), - DurationNS: 4, - EndNS: 4, - Fingerprint: 1124161485517443911, - Name: "symbol0", - SampleCount: 4, - Frame: frame.Frame{ - Function: "symbol0", - InstructionAddr: "0", - InApp: &falseValue, - }, - Children: []*nodetree.Node{ - { - ProfileIDs: make(map[string]struct{}), - DurationNS: 4, - EndNS: 4, - Fingerprint: 17905447077897174944, - Name: "symbol1", - SampleCount: 4, - Frame: frame.Frame{ - Function: "symbol1", - InstructionAddr: "1", - InApp: &falseValue, - }, - Children: []*nodetree.Node{ - { - ProfileIDs: make(map[string]struct{}), - DurationNS: 1, - EndNS: 2, - Fingerprint: 16084607411097338726, - Name: "symbol2", - SampleCount: 1, - StartNS: 1, - Frame: frame.Frame{ - Function: "symbol2", - InstructionAddr: "2", - InApp: &falseValue, - }, - }, - { - ProfileIDs: make(map[string]struct{}), - DurationNS: 1, - EndNS: 3, - Fingerprint: 16084607411097338727, - Name: "symbol3", - SampleCount: 1, - StartNS: 2, - Frame: frame.Frame{ - Function: "symbol3", - InstructionAddr: "3", - InApp: &falseValue, - }, - }, - { - ProfileIDs: make(map[string]struct{}), - DurationNS: 1, - EndNS: 4, - Fingerprint: 16084607411097338720, - Name: "symbol4", - SampleCount: 1, - StartNS: 3, - Frame: frame.Frame{ - Function: "symbol4", - InstructionAddr: "4", - InApp: &falseValue, - }, - }, - }, - }, - }, - }, - }, - }, - }, - { - name: "single root call tree with multiple unique leaves at different levels", - profile: IOS{ - Samples: []Sample{ - { - ThreadID: 1, - RelativeTimestampNS: 1, - Frames: []IosFrame{ - {InstructionAddr: "1", Function: "symbol1"}, - {InstructionAddr: "0", Function: "symbol0"}, - }, - }, - { - ThreadID: 1, - RelativeTimestampNS: 2, - Frames: []IosFrame{ - {InstructionAddr: "6", Function: "symbol6"}, - {InstructionAddr: "1", Function: "symbol1"}, - {InstructionAddr: "0", Function: "symbol0"}, - }, - }, - { - ThreadID: 1, - RelativeTimestampNS: 3, - Frames: []IosFrame{ - {InstructionAddr: "7", Function: "symbol7"}, - {InstructionAddr: "6", Function: "symbol6"}, - {InstructionAddr: "1", Function: "symbol1"}, - {InstructionAddr: "0", Function: "symbol0"}, - }, - }, - { - ThreadID: 1, - RelativeTimestampNS: 4, - Frames: []IosFrame{ - {InstructionAddr: "7", Function: "symbol7"}, - {InstructionAddr: "5", Function: "symbol5"}, - {InstructionAddr: "1", Function: "symbol1"}, - {InstructionAddr: "0", Function: "symbol0"}, - }, - }, - { - ThreadID: 1, - RelativeTimestampNS: 5, - Frames: []IosFrame{ - {InstructionAddr: "4", Function: "symbol4"}, - {InstructionAddr: "2", Function: "symbol2"}, - {InstructionAddr: "0", Function: "symbol0"}, - }, - }, - { - ThreadID: 1, - RelativeTimestampNS: 6, - Frames: []IosFrame{ - {InstructionAddr: "4", Function: "symbol4"}, - {InstructionAddr: "2", Function: "symbol2"}, - {InstructionAddr: "0", Function: "symbol0"}, - }, - }, - { - ThreadID: 1, - RelativeTimestampNS: 7, - Frames: []IosFrame{ - {InstructionAddr: "3", Function: "symbol3"}, - {InstructionAddr: "0", Function: "symbol0"}, - }, - }, - { - ThreadID: 1, - RelativeTimestampNS: 8, - Frames: []IosFrame{ - {InstructionAddr: "8", Function: "symbol8"}, - }, - }, - }, - ThreadMetadata: map[string]ThreadMetadata{ - "1": {IsMain: true}, - }, - }, - want: map[uint64][]*nodetree.Node{ - 1: { - { - ProfileIDs: make(map[string]struct{}), - DurationNS: 7, - EndNS: 7, - Fingerprint: 1124161485517443911, - Name: "symbol0", - SampleCount: 7, - Frame: frame.Frame{ - Function: "symbol0", - InstructionAddr: "0", - InApp: &falseValue, - }, - Children: []*nodetree.Node{ - { - ProfileIDs: make(map[string]struct{}), - DurationNS: 4, - EndNS: 4, - Fingerprint: 17905447077897174944, - Name: "symbol1", - SampleCount: 4, - Frame: frame.Frame{ - Function: "symbol1", - InstructionAddr: "1", - InApp: &falseValue, - }, - Children: []*nodetree.Node{ - { - ProfileIDs: make(map[string]struct{}), - DurationNS: 2, - EndNS: 3, - Fingerprint: 16084607411097338722, - Name: "symbol6", - SampleCount: 2, - StartNS: 1, - Frame: frame.Frame{ - Function: "symbol6", - InstructionAddr: "6", - InApp: &falseValue, - }, - Children: []*nodetree.Node{ - { - ProfileIDs: make(map[string]struct{}), - DurationNS: 1, - EndNS: 3, - Fingerprint: 3157437670125180841, - Name: "symbol7", - SampleCount: 1, - StartNS: 2, - Frame: frame.Frame{ - Function: "symbol7", - InstructionAddr: "7", - InApp: &falseValue, - }, - }, - }, - }, - { - ProfileIDs: make(map[string]struct{}), - DurationNS: 1, - EndNS: 4, - Fingerprint: 16084607411097338721, - Name: "symbol5", - SampleCount: 1, - StartNS: 3, - Frame: frame.Frame{ - Function: "symbol5", - InstructionAddr: "5", - InApp: &falseValue, - }, - Children: []*nodetree.Node{ - { - ProfileIDs: make(map[string]struct{}), - DurationNS: 1, - EndNS: 4, - Fingerprint: 18225866209492738612, - Name: "symbol7", - SampleCount: 1, - StartNS: 3, - Frame: frame.Frame{ - Function: "symbol7", - InstructionAddr: "7", - InApp: &falseValue, - }, - }, - }, - }, - }, - }, - { - ProfileIDs: make(map[string]struct{}), - DurationNS: 2, - EndNS: 6, - Fingerprint: 17905447077897174947, - Name: "symbol2", - SampleCount: 2, - StartNS: 4, - Frame: frame.Frame{ - Function: "symbol2", - InstructionAddr: "2", - InApp: &falseValue, - }, - Children: []*nodetree.Node{ - { - ProfileIDs: make(map[string]struct{}), - DurationNS: 2, - EndNS: 6, - Fingerprint: 11661425725218473465, - Name: "symbol4", - SampleCount: 2, - StartNS: 4, - Frame: frame.Frame{ - Function: "symbol4", - InstructionAddr: "4", - InApp: &falseValue, - }, - }, - }, - }, - { - ProfileIDs: make(map[string]struct{}), - DurationNS: 1, - EndNS: 7, - Fingerprint: 17905447077897174946, - Name: "symbol3", - SampleCount: 1, - StartNS: 6, - Frame: frame.Frame{ - Function: "symbol3", - InstructionAddr: "3", - InApp: &falseValue, - }, - }, - }, - }, - { - ProfileIDs: make(map[string]struct{}), - DurationNS: 1, - EndNS: 8, - Fingerprint: 1124161485517443919, - Name: "symbol8", - SampleCount: 1, - StartNS: 7, - Frame: frame.Frame{ - Function: "symbol8", - InstructionAddr: "8", - InApp: &falseValue, - }, - }, - }, - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got := tt.profile.CallTrees() - if diff := testutil.Diff(got, tt.want); diff != "" { - t.Fatalf("Result mismatch: got - want +\n%s", diff) - } - }) - } -} - -func TestCallTreeGenerationFromMultiThreadedSamples(t *testing.T) { - tests := []struct { - name string - profile IOS - want map[uint64][]*nodetree.Node - }{ - { - name: "multiple threads with the same call tree", - profile: IOS{ - Samples: []Sample{ - { - ThreadID: 1, - RelativeTimestampNS: 1, - Frames: []IosFrame{ - {InstructionAddr: "2", Function: "symbol2"}, - {InstructionAddr: "1", Function: "symbol1"}, - {InstructionAddr: "0", Function: "symbol0"}, - }, - }, - { - ThreadID: 2, - RelativeTimestampNS: 1, - Frames: []IosFrame{ - {InstructionAddr: "2", Function: "symbol2"}, - {InstructionAddr: "1", Function: "symbol1"}, - {InstructionAddr: "0", Function: "symbol0"}, - }, - }, - }, - ThreadMetadata: map[string]ThreadMetadata{ - "1": {IsMain: true}, - }, - }, - want: map[uint64][]*nodetree.Node{ - 1: { - { - ProfileIDs: make(map[string]struct{}), - DurationNS: 1, - EndNS: 1, - Fingerprint: 1124161485517443911, - Name: "symbol0", - SampleCount: 1, - Frame: frame.Frame{ - Function: "symbol0", - InstructionAddr: "0", - InApp: &falseValue, - }, - Children: []*nodetree.Node{ - { - ProfileIDs: make(map[string]struct{}), - DurationNS: 1, - EndNS: 1, - Fingerprint: 17905447077897174944, - Name: "symbol1", - SampleCount: 1, - Frame: frame.Frame{ - Function: "symbol1", - InstructionAddr: "1", - InApp: &falseValue, - }, - Children: []*nodetree.Node{ - { - ProfileIDs: make(map[string]struct{}), - DurationNS: 1, - EndNS: 1, - Fingerprint: 16084607411097338726, - Name: "symbol2", - SampleCount: 1, - Frame: frame.Frame{ - Function: "symbol2", - InstructionAddr: "2", - InApp: &falseValue, - }, - }, - }, - }, - }, - }, - }, - }, - }, - { - name: "multiple threads with different call trees", - profile: IOS{ - Samples: []Sample{ - { - ThreadID: 1, - RelativeTimestampNS: 1, - Frames: []IosFrame{ - {InstructionAddr: "2", Function: "symbol2"}, - {InstructionAddr: "1", Function: "symbol1"}, - {InstructionAddr: "0", Function: "symbol0"}, - }, - }, - { - ThreadID: 2, - RelativeTimestampNS: 1, - Frames: []IosFrame{ - {InstructionAddr: "5", Function: "symbol5"}, - {InstructionAddr: "4", Function: "symbol4"}, - {InstructionAddr: "3", Function: "symbol3"}, - }, - }, - }, - ThreadMetadata: map[string]ThreadMetadata{ - "1": {IsMain: true}, - }, - }, - want: map[uint64][]*nodetree.Node{ - 1: { - { - ProfileIDs: make(map[string]struct{}), - DurationNS: 1, - EndNS: 1, - Fingerprint: 1124161485517443911, - Name: "symbol0", - SampleCount: 1, - Frame: frame.Frame{ - Function: "symbol0", - InstructionAddr: "0", - InApp: &falseValue, - }, - Children: []*nodetree.Node{ - { - ProfileIDs: make(map[string]struct{}), - DurationNS: 1, - EndNS: 1, - Fingerprint: 17905447077897174944, - Name: "symbol1", - SampleCount: 1, - Frame: frame.Frame{ - Function: "symbol1", - InstructionAddr: "1", - InApp: &falseValue, - }, - Children: []*nodetree.Node{ - { - ProfileIDs: make(map[string]struct{}), - DurationNS: 1, - EndNS: 1, - Fingerprint: 16084607411097338726, - Name: "symbol2", - SampleCount: 1, - Frame: frame.Frame{ - Function: "symbol2", - InstructionAddr: "2", - InApp: &falseValue, - }, - }, - }, - }, - }, - }, - }, - }, - }, - { - name: "multiple threads with sequential samples", - profile: IOS{ - Samples: []Sample{ - { - ThreadID: 1, - RelativeTimestampNS: 1, - Frames: []IosFrame{ - {InstructionAddr: "2", Function: "symbol2"}, - {InstructionAddr: "1", Function: "symbol1"}, - {InstructionAddr: "0", Function: "symbol0"}, - }, - }, - { - ThreadID: 1, - RelativeTimestampNS: 2, - Frames: []IosFrame{ - {InstructionAddr: "5", Function: "symbol5"}, - {InstructionAddr: "4", Function: "symbol4"}, - {InstructionAddr: "3", Function: "symbol3"}, - }, - }, - { - ThreadID: 2, - RelativeTimestampNS: 1, - Frames: []IosFrame{ - {InstructionAddr: "2", Function: "symbol2"}, - {InstructionAddr: "1", Function: "symbol1"}, - {InstructionAddr: "0", Function: "symbol0"}, - }, - }, - { - ThreadID: 2, - RelativeTimestampNS: 2, - Frames: []IosFrame{ - {InstructionAddr: "5", Function: "symbol5"}, - {InstructionAddr: "4", Function: "symbol4"}, - {InstructionAddr: "3", Function: "symbol3"}, - }, - }, - }, - ThreadMetadata: map[string]ThreadMetadata{ - "1": {IsMain: true}, - }, - }, - want: map[uint64][]*nodetree.Node{ - 1: { - { - ProfileIDs: make(map[string]struct{}), - DurationNS: 1, - EndNS: 1, - Fingerprint: 1124161485517443911, - Name: "symbol0", - SampleCount: 1, - Frame: frame.Frame{ - Function: "symbol0", - InstructionAddr: "0", - InApp: &falseValue, - }, - Children: []*nodetree.Node{ - { - ProfileIDs: make(map[string]struct{}), - DurationNS: 1, - EndNS: 1, - Fingerprint: 17905447077897174944, - Name: "symbol1", - SampleCount: 1, - Frame: frame.Frame{ - Function: "symbol1", - InstructionAddr: "1", - InApp: &falseValue, - }, - Children: []*nodetree.Node{ - { - ProfileIDs: make(map[string]struct{}), - DurationNS: 1, - EndNS: 1, - Fingerprint: 16084607411097338726, - Name: "symbol2", - SampleCount: 1, - Frame: frame.Frame{ - Function: "symbol2", - InstructionAddr: "2", - InApp: &falseValue, - }, - }, - }, - }, - }, - }, - { - ProfileIDs: make(map[string]struct{}), - DurationNS: 1, - EndNS: 2, - Fingerprint: 1124161485517443908, - Name: "symbol3", - SampleCount: 1, - StartNS: 1, - Frame: frame.Frame{ - Function: "symbol3", - InstructionAddr: "3", - InApp: &falseValue, - }, - Children: []*nodetree.Node{ - { - ProfileIDs: make(map[string]struct{}), - DurationNS: 1, - EndNS: 2, - Fingerprint: 7967440964543288636, - Name: "symbol4", - SampleCount: 1, - StartNS: 1, - Frame: frame.Frame{ - Function: "symbol4", - InstructionAddr: "4", - InApp: &falseValue, - }, - Children: []*nodetree.Node{ - { - ProfileIDs: make(map[string]struct{}), - DurationNS: 1, - EndNS: 2, - Fingerprint: 13274796176329250277, - Name: "symbol5", - SampleCount: 1, - StartNS: 1, - Frame: frame.Frame{ - Function: "symbol5", - InstructionAddr: "5", - InApp: &falseValue, - }, - }, - }, - }, - }, - }, - }, - }, - }, - { - name: "multiple threads with non-sequential samples", - profile: IOS{ - Samples: []Sample{ - { - ThreadID: 1, - RelativeTimestampNS: 1, - Frames: []IosFrame{ - {InstructionAddr: "2", Function: "symbol2"}, - {InstructionAddr: "1", Function: "symbol1"}, - {InstructionAddr: "0", Function: "symbol0"}, - }, - }, - { - ThreadID: 1, - RelativeTimestampNS: 2, - Frames: []IosFrame{ - {InstructionAddr: "5", Function: "symbol5"}, - {InstructionAddr: "4", Function: "symbol4"}, - {InstructionAddr: "3", Function: "symbol3"}, - }, - }, - { - ThreadID: 2, - RelativeTimestampNS: 5, - Frames: []IosFrame{ - {InstructionAddr: "2", Function: "symbol2"}, - {InstructionAddr: "1", Function: "symbol1"}, - {InstructionAddr: "0", Function: "symbol0"}, - }, - }, - { - ThreadID: 2, - RelativeTimestampNS: 6, - Frames: []IosFrame{ - {InstructionAddr: "5", Function: "symbol5"}, - {InstructionAddr: "4", Function: "symbol4"}, - {InstructionAddr: "3", Function: "symbol3"}, - }, - }, - }, - ThreadMetadata: map[string]ThreadMetadata{ - "1": {IsMain: true}, - }, - }, - want: map[uint64][]*nodetree.Node{ - 1: { - { - ProfileIDs: make(map[string]struct{}), - DurationNS: 1, - EndNS: 1, - Fingerprint: 1124161485517443911, - Name: "symbol0", - SampleCount: 1, - Frame: frame.Frame{ - Function: "symbol0", - InstructionAddr: "0", - InApp: &falseValue, - }, - Children: []*nodetree.Node{ - { - ProfileIDs: make(map[string]struct{}), - DurationNS: 1, - EndNS: 1, - Fingerprint: 17905447077897174944, - Name: "symbol1", - SampleCount: 1, - Frame: frame.Frame{ - Function: "symbol1", - InstructionAddr: "1", - InApp: &falseValue, - }, - Children: []*nodetree.Node{ - { - ProfileIDs: make(map[string]struct{}), - DurationNS: 1, - EndNS: 1, - Fingerprint: 16084607411097338726, - Name: "symbol2", - SampleCount: 1, - Frame: frame.Frame{ - Function: "symbol2", - InstructionAddr: "2", - InApp: &falseValue, - }, - }, - }, - }, - }, - }, - { - ProfileIDs: make(map[string]struct{}), - DurationNS: 1, - EndNS: 2, - Fingerprint: 1124161485517443908, - Name: "symbol3", - SampleCount: 1, - StartNS: 1, - Frame: frame.Frame{ - Function: "symbol3", - InstructionAddr: "3", - InApp: &falseValue, - }, - Children: []*nodetree.Node{ - { - ProfileIDs: make(map[string]struct{}), - DurationNS: 1, - EndNS: 2, - Fingerprint: 7967440964543288636, - Name: "symbol4", - SampleCount: 1, - StartNS: 1, - Frame: frame.Frame{ - Function: "symbol4", - InstructionAddr: "4", - InApp: &falseValue, - }, - Children: []*nodetree.Node{ - { - ProfileIDs: make(map[string]struct{}), - DurationNS: 1, - EndNS: 2, - Fingerprint: 13274796176329250277, - Name: "symbol5", - SampleCount: 1, - StartNS: 1, - Frame: frame.Frame{ - Function: "symbol5", - InstructionAddr: "5", - InApp: &falseValue, - }, - }, - }, - }, - }, - }, - }, - }, - }, - { - name: "multiple threads with interleaved sequential samples", - profile: IOS{ - Samples: []Sample{ - { - ThreadID: 1, - RelativeTimestampNS: 1, - Frames: []IosFrame{ - {InstructionAddr: "2", Function: "symbol2"}, - {InstructionAddr: "1", Function: "symbol1"}, - {InstructionAddr: "0", Function: "symbol0"}, - }, - }, - { - ThreadID: 2, - RelativeTimestampNS: 1, - Frames: []IosFrame{ - {InstructionAddr: "2", Function: "symbol2"}, - {InstructionAddr: "1", Function: "symbol1"}, - {InstructionAddr: "0", Function: "symbol0"}, - }, - }, - { - ThreadID: 1, - RelativeTimestampNS: 2, - Frames: []IosFrame{ - {InstructionAddr: "5", Function: "symbol5"}, - {InstructionAddr: "4", Function: "symbol4"}, - {InstructionAddr: "3", Function: "symbol3"}, - }, - }, - - { - ThreadID: 2, - RelativeTimestampNS: 2, - Frames: []IosFrame{ - {InstructionAddr: "5", Function: "symbol5"}, - {InstructionAddr: "4", Function: "symbol4"}, - {InstructionAddr: "3", Function: "symbol3"}, - }, - }, - }, - ThreadMetadata: map[string]ThreadMetadata{ - "1": {IsMain: true}, - }, - }, - want: map[uint64][]*nodetree.Node{ - 1: { - { - ProfileIDs: make(map[string]struct{}), - DurationNS: 1, - EndNS: 1, - Fingerprint: 1124161485517443911, - Name: "symbol0", - SampleCount: 1, - Frame: frame.Frame{ - Function: "symbol0", - InstructionAddr: "0", - InApp: &falseValue, - }, - Children: []*nodetree.Node{ - { - ProfileIDs: make(map[string]struct{}), - DurationNS: 1, - EndNS: 1, - Fingerprint: 17905447077897174944, - Name: "symbol1", - SampleCount: 1, - Frame: frame.Frame{ - Function: "symbol1", - InstructionAddr: "1", - InApp: &falseValue, - }, - Children: []*nodetree.Node{ - { - ProfileIDs: make(map[string]struct{}), - DurationNS: 1, - EndNS: 1, - Fingerprint: 16084607411097338726, - Name: "symbol2", - SampleCount: 1, - Frame: frame.Frame{ - Function: "symbol2", - InstructionAddr: "2", - InApp: &falseValue, - }, - }, - }, - }, - }, - }, - { - ProfileIDs: make(map[string]struct{}), - DurationNS: 1, - EndNS: 2, - Fingerprint: 1124161485517443908, - Name: "symbol3", - SampleCount: 1, - StartNS: 1, - Frame: frame.Frame{ - Function: "symbol3", - InstructionAddr: "3", - InApp: &falseValue, - }, - Children: []*nodetree.Node{ - { - ProfileIDs: make(map[string]struct{}), - DurationNS: 1, - EndNS: 2, - Fingerprint: 7967440964543288636, - Name: "symbol4", - SampleCount: 1, - StartNS: 1, - Frame: frame.Frame{ - Function: "symbol4", - InstructionAddr: "4", - InApp: &falseValue, - }, - Children: []*nodetree.Node{ - { - ProfileIDs: make(map[string]struct{}), - DurationNS: 1, - EndNS: 2, - Fingerprint: 13274796176329250277, - Name: "symbol5", - SampleCount: 1, - StartNS: 1, - Frame: frame.Frame{ - Function: "symbol5", - InstructionAddr: "5", - InApp: &falseValue, - }, - }, - }, - }, - }, - }, - }, - }, - }, - { - name: "multiple threads with interleaved non-sequential samples", - profile: IOS{ - Samples: []Sample{ - { - ThreadID: 1, - RelativeTimestampNS: 1, - Frames: []IosFrame{ - {InstructionAddr: "2", Function: "symbol2"}, - {InstructionAddr: "1", Function: "symbol1"}, - {InstructionAddr: "0", Function: "symbol0"}, - }, - }, - { - ThreadID: 2, - RelativeTimestampNS: 2, - Frames: []IosFrame{ - {InstructionAddr: "2", Function: "symbol2"}, - {InstructionAddr: "1", Function: "symbol1"}, - {InstructionAddr: "0", Function: "symbol0"}, - }, - }, - { - ThreadID: 1, - RelativeTimestampNS: 3, - Frames: []IosFrame{ - {InstructionAddr: "5", Function: "symbol5"}, - {InstructionAddr: "4", Function: "symbol4"}, - {InstructionAddr: "3", Function: "symbol3"}, - }, - }, - { - ThreadID: 2, - RelativeTimestampNS: 4, - Frames: []IosFrame{ - {InstructionAddr: "5", Function: "symbol5"}, - {InstructionAddr: "4", Function: "symbol4"}, - {InstructionAddr: "3", Function: "symbol3"}, - }, - }, - }, - ThreadMetadata: map[string]ThreadMetadata{ - "1": {IsMain: true}, - }, - }, - want: map[uint64][]*nodetree.Node{ - 1: { - { - ProfileIDs: make(map[string]struct{}), - DurationNS: 1, - EndNS: 1, - Fingerprint: 1124161485517443911, - Name: "symbol0", - SampleCount: 1, - Frame: frame.Frame{ - Function: "symbol0", - InstructionAddr: "0", - InApp: &falseValue, - }, - Children: []*nodetree.Node{ - { - ProfileIDs: make(map[string]struct{}), - DurationNS: 1, - EndNS: 1, - Fingerprint: 17905447077897174944, - Name: "symbol1", - SampleCount: 1, - Frame: frame.Frame{ - Function: "symbol1", - InstructionAddr: "1", - InApp: &falseValue, - }, - Children: []*nodetree.Node{ - { - ProfileIDs: make(map[string]struct{}), - DurationNS: 1, - EndNS: 1, - Fingerprint: 16084607411097338726, - Name: "symbol2", - SampleCount: 1, - Frame: frame.Frame{ - Function: "symbol2", - InstructionAddr: "2", - InApp: &falseValue, - }, - }, - }, - }, - }, - }, - { - ProfileIDs: make(map[string]struct{}), - DurationNS: 2, - EndNS: 3, - Fingerprint: 1124161485517443908, - Name: "symbol3", - SampleCount: 1, - StartNS: 1, - Frame: frame.Frame{ - Function: "symbol3", - InstructionAddr: "3", - InApp: &falseValue, - }, - Children: []*nodetree.Node{ - { - ProfileIDs: make(map[string]struct{}), - DurationNS: 2, - EndNS: 3, - Fingerprint: 7967440964543288636, - Name: "symbol4", - SampleCount: 1, - StartNS: 1, - Frame: frame.Frame{ - Function: "symbol4", - InstructionAddr: "4", - InApp: &falseValue, - }, - Children: []*nodetree.Node{ - { - ProfileIDs: make(map[string]struct{}), - DurationNS: 2, - EndNS: 3, - Fingerprint: 13274796176329250277, - Name: "symbol5", - SampleCount: 1, - StartNS: 1, - Frame: frame.Frame{ - Function: "symbol5", - InstructionAddr: "5", - InApp: &falseValue, - }, - }, - }, - }, - }, - }, - }, - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got := tt.profile.CallTrees() - if diff := testutil.Diff(got, tt.want); diff != "" { - t.Fatalf("Result mismatch: got - want +\n%s", diff) - } - }) - } -} - -func TestCallTreeGenerationFromMultipleProfiles(t *testing.T) { - p1 := IOS{ - Samples: []Sample{ - { - ThreadID: 1, - RelativeTimestampNS: 1, - Frames: []IosFrame{ - {InstructionAddr: "2", Function: "symbol2", Package: "/home/pierre/release1/package1"}, - {InstructionAddr: "1", Function: "symbol1", Package: "/home/pierre/release1/package1"}, - {InstructionAddr: "0", Function: "symbol0", Package: "/home/pierre/release1/package1"}, - }, - }, - { - ThreadID: 2, - RelativeTimestampNS: 1, - Frames: []IosFrame{ - {InstructionAddr: "2", Function: "symbol2", Package: "/home/pierre/release1/package1"}, - {InstructionAddr: "1", Function: "symbol1", Package: "/home/pierre/release1/package1"}, - {InstructionAddr: "0", Function: "symbol0", Package: "/home/pierre/release1/package1"}, - }, - }, - }, - } - p2 := IOS{ - Samples: []Sample{ - { - ThreadID: 1, - RelativeTimestampNS: 1, - Frames: []IosFrame{ - {InstructionAddr: "2", Function: "symbol2", Package: "/home/pierre/release2/package1"}, - {InstructionAddr: "1", Function: "symbol1", Package: "/home/pierre/release2/package1"}, - {InstructionAddr: "0", Function: "symbol0", Package: "/home/pierre/release2/package1"}, - }, - }, - { - ThreadID: 2, - RelativeTimestampNS: 1, - Frames: []IosFrame{ - {InstructionAddr: "2", Function: "symbol2", Package: "/home/pierre/release2/package1"}, - {InstructionAddr: "1", Function: "symbol1", Package: "/home/pierre/release2/package1"}, - {InstructionAddr: "0", Function: "symbol0", Package: "/home/pierre/release2/package1"}, - }, - }, - }, - } - - if diff := testutil.Diff(p1.CallTrees(), p2.CallTrees()); diff != "" { - t.Fatalf("Result mismatch: got - want +\n%s", diff) - } -} - -func BenchmarkCallTrees(b *testing.B) { - var p LegacyProfile - f, err := os.Open("../../test/data/cocoa.json") - if err != nil { - b.Fatal(err) - } - err = json.NewDecoder(f).Decode(&p) - if err != nil { - b.Fatal(err) - } - var iosProfile IOS - err = json.Unmarshal([]byte(p.Profile), &iosProfile) - if err != nil { - b.Fatal(err) - } - - b.ReportAllocs() - - var total int - for n := 0; n < b.N; n++ { - c := iosProfile.CallTrees() - total += len(c) - } - b.Logf("Total call trees generated: %d", total) -} diff --git a/internal/profile/legacy.go b/internal/profile/legacy.go index bad1bc1..7a11b2e 100644 --- a/internal/profile/legacy.go +++ b/internal/profile/legacy.go @@ -113,14 +113,6 @@ func (p *LegacyProfile) UnmarshalJSON(b []byte) error { raw = p.Profile } switch p.Platform { - case platform.Cocoa: - var t IOS - err := json.Unmarshal(raw, &t) - if err != nil { - return err - } - p.Trace = &t - p.Profile = nil case platform.Android: var t Android err := json.Unmarshal(raw, &t) @@ -235,8 +227,6 @@ func (p LegacyProfile) GetReceived() time.Time { func (p *LegacyProfile) Normalize() { switch t := p.Trace.(type) { - case *IOS: - t.ReplaceIdleStacks() case *Android: t.NormalizeMethods(p) }