Skip to content

Commit 1581b37

Browse files
committed
fix: serialize call history first to show more context
1 parent be263b6 commit 1581b37

File tree

4 files changed

+171
-35
lines changed

4 files changed

+171
-35
lines changed

e2e/capture/performance/performance.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ test("time-to-(mount | update | nested-update)", async ({ page }) => {
6666
assertAbsolutePeformance(baseline, profilerEntries, 10);
6767
})
6868

69-
test.only("time-to-click-handled", async ({ page }) => {
69+
test("time-to-click-handled", async ({ page }) => {
7070
const baseline = {
7171
'click-handled': 40.09999999962747,
7272
};

src/core/captureLimits.ts

Lines changed: 60 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,70 @@
11
import { Err, Ok } from 'ts-results'
2-
import { CaptureAmountLimit, CapturedCall, CapturedFunction } from './types'
2+
import { CaptureAmountLimit, CaptureInvocation, CapturedCall, CapturedFunction } from './types'
33
import {
44
parseCaptureAmountLimit,
55
parseFilepathFromFunctionId,
66
sortCapturesByTimestamp
77
} from './util'
88
import { getCaptureSize } from './stringify'
99

10+
export function getSizeLimitedCaptures<T extends CapturedCall | CapturedFunction>(
11+
callsOrFunctions: T[],
12+
sizeLimitBytes: number
13+
) {
14+
let currentSize = 0
15+
let result: T[] = []
16+
17+
const callsOrFunctionsClone = callsOrFunctions.map((callOrFunc) => ({
18+
id: callOrFunc.id,
19+
invocations: [...callOrFunc.invocations]
20+
}))
21+
22+
for (let i = 0; i < callsOrFunctions.length; i++) {
23+
result.push({ id: callsOrFunctions[i].id, invocations: [] as T['invocations'] } as T)
24+
}
25+
26+
let addedThisRound = false
27+
do {
28+
addedThisRound = false // Reset for the current round
29+
30+
for (let i = 0; i < callsOrFunctionsClone.length; i++) {
31+
const call = callsOrFunctionsClone[i]
32+
if (call.invocations.length > 0) {
33+
// Instead of popping the last, we take the newest invocation directly
34+
const invocation = call.invocations[call.invocations.length - 1] // Access the newest invocation
35+
const invocationSize = getCaptureSize(invocation as any).unwrapOr(0)
36+
37+
if (currentSize + invocationSize <= sizeLimitBytes) {
38+
// Insert the invocation in the correct order (newest first)
39+
result[i].invocations.unshift(invocation) // Add the invocation to the beginning
40+
call.invocations.pop() // Remove the newest invocation from the clone
41+
currentSize += invocationSize
42+
addedThisRound = true
43+
}
44+
}
45+
}
46+
} while (addedThisRound && currentSize < sizeLimitBytes)
47+
48+
// Filter out calls without invocations
49+
result = result.filter((callOrFunc) => callOrFunc.invocations.length > 0)
50+
51+
const resultCalls: CapturedCall[] = []
52+
const resultFunctions: CapturedFunction[] = []
53+
54+
for (let i = 0; i < result.length; i++) {
55+
if (result[i].id.includes('-call-_')) {
56+
resultCalls.push(result[i])
57+
} else {
58+
resultFunctions.push(result[i])
59+
}
60+
}
61+
62+
return Ok({
63+
calls: resultCalls,
64+
functions: resultFunctions
65+
})
66+
}
67+
1068
export function getLimitedCaptures(
1169
calls: CapturedCall[],
1270
functions: CapturedFunction[],
@@ -59,35 +117,5 @@ export function getLimitedCaptures(
59117
}
60118

61119
// Handle byte limits
62-
const duplicateCalls: typeof calls = []
63-
const duplicateFunctions: typeof functions = []
64-
65-
let sizeCounter = 0
66-
67-
for (let i = 0; i < Math.max(calls.length, functions.length); i++) {
68-
if (sizeCounter >= parsedCaptureAmountLimit.val.sizeLimit) {
69-
break
70-
}
71-
const callEntry = calls.at(-(i + 1))
72-
const functionEntry = functions.at(-(i + 1))
73-
if (callEntry) {
74-
duplicateCalls.push(callEntry)
75-
76-
const captureSize = getCaptureSize(callEntry)
77-
if (captureSize.err === false) {
78-
sizeCounter += captureSize.val
79-
}
80-
}
81-
if (functionEntry) {
82-
duplicateFunctions.push(functionEntry)
83-
84-
const captureSize = getCaptureSize(functionEntry)
85-
86-
if (captureSize.err === false) {
87-
sizeCounter += captureSize.val
88-
}
89-
}
90-
}
91-
92-
return Ok({ calls: duplicateCalls, functions: duplicateFunctions })
120+
return getSizeLimitedCaptures(sortedCombined, parsedCaptureAmountLimit.val.sizeLimit)
93121
}

test/captureLimits.test.ts

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,38 @@
11
import { describe, expect, it } from 'vitest'
22
import { parseCaptureAmountLimit, parseFilepathFromFunctionId } from '../src/core/util'
3+
import { CapturedCall } from '../src/core/types'
4+
import { getLimitedCaptures, getSizeLimitedCaptures } from '../src/core/captureLimits'
5+
6+
const createData = (bytes: number) => {
7+
return 'H'.repeat(bytes)
8+
}
9+
10+
const callsFixture: CapturedCall[] = [
11+
{
12+
id: '/a.js-call-_a',
13+
invocations: [1, 2].map((timestamp) => ({
14+
args: [],
15+
output: createData(2),
16+
timestamp
17+
}))
18+
},
19+
{
20+
id: '/b.js-call-_b',
21+
invocations: [3, 4, 5, 6, 7].map((timestamp) => ({
22+
args: [],
23+
output: createData(2),
24+
timestamp
25+
}))
26+
},
27+
{
28+
id: '/c.js-call-_c',
29+
invocations: [8, 9].map((timestamp) => ({
30+
args: [],
31+
output: createData(2),
32+
timestamp
33+
}))
34+
}
35+
]
336

437
describe('capture limits', () => {
538
it('parseCaptureAmountLimit', () => {
@@ -43,4 +76,79 @@ describe('capture limits', () => {
4376
)
4477
expect(parseFilepathFromFunctionId(``).val).toBe(`Invalid function ID "".`)
4578
})
79+
80+
it('saves everything when capture limit is more than data', () => {
81+
const limitedResult = getSizeLimitedCaptures(callsFixture, 6_000).unwrap()
82+
const { calls: newCalls } = limitedResult
83+
expect(newCalls).toStrictEqual(callsFixture)
84+
})
85+
86+
it('does capture limiting depth (functions & calls) first, instead of bredth first (invocations)', () => {
87+
const SERIALIZATION_ADDON = 46 * 3
88+
const limitedResult = getSizeLimitedCaptures(callsFixture, 6 + SERIALIZATION_ADDON).unwrap()
89+
const { calls: newCalls } = limitedResult
90+
91+
expect(newCalls.length).toBe(3)
92+
expect(newCalls).toStrictEqual([
93+
{
94+
id: '/a.js-call-_a',
95+
invocations: [2].map((timestamp) => ({
96+
args: [],
97+
output: createData(2),
98+
timestamp
99+
}))
100+
},
101+
{
102+
id: '/b.js-call-_b',
103+
invocations: [7].map((timestamp) => ({
104+
args: [],
105+
output: createData(2),
106+
timestamp
107+
}))
108+
},
109+
{
110+
id: '/c.js-call-_c',
111+
invocations: [9].map((timestamp) => ({
112+
args: [],
113+
output: createData(2),
114+
timestamp
115+
}))
116+
}
117+
])
118+
119+
// Extra serialization bytes * num functions * num invocations
120+
const SERIALIZATION_ADDON_SECOND = 46 * 3 * 2
121+
122+
const { calls: secondTestCaseCalls } = getSizeLimitedCaptures(
123+
callsFixture,
124+
12 + SERIALIZATION_ADDON_SECOND
125+
).unwrap()
126+
expect(secondTestCaseCalls.length).toBe(3)
127+
expect(secondTestCaseCalls).toStrictEqual([
128+
{
129+
id: '/a.js-call-_a',
130+
invocations: [1, 2].map((timestamp) => ({
131+
args: [],
132+
output: createData(2),
133+
timestamp
134+
}))
135+
},
136+
{
137+
id: '/b.js-call-_b',
138+
invocations: [6, 7].map((timestamp) => ({
139+
args: [],
140+
output: createData(2),
141+
timestamp
142+
}))
143+
},
144+
{
145+
id: '/c.js-call-_c',
146+
invocations: [8, 9].map((timestamp) => ({
147+
args: [],
148+
output: createData(2),
149+
timestamp
150+
}))
151+
}
152+
])
153+
})
46154
})

test/transform.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -542,7 +542,7 @@ describe('uff(Async) transform', () => {
542542
})
543543

544544
it('loads config', async () => {
545-
const config = await loadConfig()
545+
const { generateBuildId, ...config } = (await loadConfig())!
546546

547547
expect(config).toEqual({
548548
projectId: 'flytrap',
@@ -579,7 +579,7 @@ it('doesnt transform reserved words', () => {
579579
})
580580

581581
// @todo: add this test back
582-
it.only('transforms .vue files', async () => {
582+
it('transforms .vue files', async () => {
583583
const fixture = `
584584
<script setup>
585585
function foo() {

0 commit comments

Comments
 (0)