Skip to content

Commit f95dc37

Browse files
benchmarking for react components with samples
1 parent 98eb58e commit f95dc37

9 files changed

Lines changed: 420 additions & 162 deletions

File tree

code_to_optimize/js/code_to_optimize_react/jest.config.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,7 @@ const config: Config = {
2828
]
2929
],
3030
transform: {
31-
'^.+\\.tsx?$': ['ts-jest', {
32-
useESM: false,
31+
"^.+\\\\.(ts | tsx)$": ['ts-jest', {
3332
tsconfig: 'tsconfig.json'
3433
}]
3534
}

codeflash/languages/javascript/test_runner.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -842,6 +842,7 @@ def run_jest_behavioral_tests(
842842
wall_clock_ns = time.perf_counter_ns() - start_time_ns
843843
logger.debug(f"Jest behavioral tests completed in {wall_clock_ns / 1e9:.2f}s")
844844

845+
print(result.stdout)
845846
return result_file_path, result, coverage_json_path, None
846847

847848

packages/codeflash/runtime/capture.js

Lines changed: 34 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -29,26 +29,21 @@
2929
const fs = require('fs');
3030
const path = require('path');
3131
const Database = require('better-sqlite3');
32+
const { requireFromRoot } = require("./utils")
3233

3334
// Load the codeflash serializer for robust value serialization
3435
const serializer = require('./serializer');
3536

36-
// Lazy-cached React instance resolved from the user's project (not from codeflash's
37-
// own dependencies). We resolve from process.cwd() so Node finds the react package
38-
// in the project's node_modules rather than looking inside codeflash's package tree.
39-
let _cachedReact = null;
37+
4038
function _getReact() {
41-
if (_cachedReact) return _cachedReact;
4239
try {
43-
const reactPath = require.resolve('react', { paths: [process.cwd()] });
44-
_cachedReact = require(reactPath);
40+
return requireFromRoot("react");
4541
} catch (e) {
4642
throw new Error(
4743
`codeflash: Could not resolve 'react' from project root (${process.cwd()}). ` +
4844
`Ensure react is installed in your project: npm install react`
4945
);
5046
}
51-
return _cachedReact;
5247
}
5348

5449
// Try to load better-sqlite3, fall back to JSON if not available
@@ -1082,106 +1077,50 @@ function captureRender(funcName, lineId, renderFn, Component, ...createElementAr
10821077
* @throws {Error} - Re-throws any error from rendering
10831078
*/
10841079
function captureRenderPerf(funcName, lineId, renderFn, Component, ...createElementArgs) {
1085-
const shouldLoop = getPerfLoopCount() > 1 && !checkSharedTimeLimit();
1080+
const runBenchmark = require('./react-benchmark/run');
10861081

1087-
const { testModulePath, testClassName, testFunctionName, safeModulePath, safeTestFunctionName } = _getTestContext();
1082+
const { testClassName, safeModulePath, safeTestFunctionName } = _getTestContext();
10881083

10891084
const invocationKey = `${safeModulePath}:${testClassName}:${safeTestFunctionName}:${funcName}:${lineId}`;
10901085

1091-
// Check if we've already completed all loops for this invocation
1092-
const peekLoopIndex = (sharedPerfState.invocationLoopCounts[invocationKey] || 0);
1093-
const currentBatch = parseInt(process.env.CODEFLASH_PERF_CURRENT_BATCH || '1', 10);
1094-
const nextGlobalIndex = (currentBatch - 1) * getPerfBatchSize() + peekLoopIndex + 1;
1095-
1096-
const React = _getReact();
1097-
1098-
if (shouldLoop && nextGlobalIndex > getPerfLoopCount()) {
1099-
// All loops completed, just render once for test assertions
1100-
const element = React.createElement(Component, ...createElementArgs);
1101-
return renderFn(element);
1102-
}
1103-
1104-
let lastReturnValue;
1105-
let lastError = null;
1106-
1107-
const hasExternalLoopRunner = process.env.CODEFLASH_PERF_CURRENT_BATCH !== undefined;
1108-
const batchSize = hasExternalLoopRunner ? 1 : (shouldLoop ? getPerfLoopCount() : 1);
1109-
1110-
if (!sharedPerfState.invocationRuntimes[invocationKey]) {
1111-
sharedPerfState.invocationRuntimes[invocationKey] = [];
1112-
}
1113-
const runtimes = sharedPerfState.invocationRuntimes[invocationKey];
1114-
const getStabilityWindow = () => Math.max(getPerfMinLoops(), Math.ceil(runtimes.length * STABILITY_WINDOW_SIZE));
1115-
1116-
for (let batchIndex = 0; batchIndex < batchSize; batchIndex++) {
1117-
if (!hasExternalLoopRunner && shouldLoop && checkSharedTimeLimit()) {
1118-
break;
1119-
}
1120-
if (!hasExternalLoopRunner && getPerfStabilityCheck() && sharedPerfState.stableInvocations[invocationKey]) {
1121-
break;
1122-
}
1123-
1124-
const loopIndex = getInvocationLoopIndex(invocationKey);
1125-
1126-
const totalIterations = getTotalIterations(invocationKey);
1127-
if (!hasExternalLoopRunner && totalIterations > getPerfLoopCount()) {
1128-
break;
1129-
}
1130-
1131-
const testId = `${safeModulePath}:${testClassName}:${safeTestFunctionName}:${lineId}:${loopIndex}`;
1132-
const invocationIndex = getInvocationIndex(testId);
1133-
const invocationId = `${lineId}_${invocationIndex}`;
1134-
const testStdoutTag = `${safeModulePath}:${testClassName ? testClassName + '.' : ''}${safeTestFunctionName}:${funcName}:${loopIndex}:${invocationId}`;
1086+
const numSamples = getPerfLoopCount() > 1 ? getPerfLoopCount() : 50;
11351087

1136-
let durationNs;
1137-
try {
1138-
// Unmount previous render to keep DOM clean between iterations
1139-
if (lastReturnValue && lastReturnValue.unmount) {
1140-
lastReturnValue.unmount();
1141-
}
1088+
// createElementArgs matches React.createElement signature: (props, ...children)
1089+
const props = createElementArgs[0] || {};
11421090

1143-
const element = React.createElement(Component, ...createElementArgs);
1144-
const startTime = getTimeNs();
1145-
lastReturnValue = renderFn(element);
1146-
const endTime = getTimeNs();
1147-
durationNs = getDurationNs(startTime, endTime);
1091+
const MS_TO_NS = 1e6;
11481092

1149-
lastError = null;
1150-
} catch (e) {
1151-
durationNs = 0;
1152-
lastError = e;
1153-
}
1154-
1155-
console.log(`!######${testStdoutTag}:${durationNs}######!`);
1156-
1157-
sharedPerfState.totalLoopsCompleted++;
1158-
1159-
if (durationNs > 0) {
1160-
runtimes.push(durationNs / 1000);
1161-
}
1093+
return runBenchmark({
1094+
component: Component,
1095+
props,
1096+
samples: numSamples,
1097+
type: 'mount',
1098+
}).then((results) => {
1099+
// Emit perf markers for each sample so the Python parser can collect timings
1100+
for (let i = 0; i < results.samples.length; i++) {
1101+
const sample = results.samples[i];
1102+
const durationNs = Math.round(sample.elapsed * MS_TO_NS);
11621103

1163-
if (!hasExternalLoopRunner && getPerfStabilityCheck() && runtimes.length >= getPerfMinLoops()) {
1164-
const window = getStabilityWindow();
1165-
if (shouldStopStability(runtimes, window, getPerfMinLoops())) {
1166-
sharedPerfState.stableInvocations[invocationKey] = true;
1167-
break;
1168-
}
1169-
}
1104+
const loopIndex = getInvocationLoopIndex(invocationKey);
1105+
const testId = `${safeModulePath}:${testClassName}:${safeTestFunctionName}:${lineId}:${loopIndex}`;
1106+
const invocationIndex = getInvocationIndex(testId);
1107+
const invocationId = `${lineId}_${invocationIndex}`;
1108+
const testStdoutTag = `${safeModulePath}:${testClassName ? testClassName + '.' : ''}${safeTestFunctionName}:${funcName}:${loopIndex}:${invocationId}`;
11701109

1171-
if (!hasExternalLoopRunner && lastError) {
1172-
break;
1110+
console.log(`!######${testStdoutTag}:${durationNs}######!`);
1111+
sharedPerfState.totalLoopsCompleted++;
11731112
}
1174-
}
1175-
1176-
if (lastError) throw lastError;
11771113

1178-
// If we never executed (e.g., hit loop limit on first iteration), render once for assertion
1179-
if (lastReturnValue === undefined && !lastError) {
1114+
// Render once more so the test's own assertions (e.g. screen.getByText) still pass
1115+
const React = _getReact();
11801116
const element = React.createElement(Component, ...createElementArgs);
11811117
return renderFn(element);
1182-
}
1183-
1184-
return lastReturnValue;
1118+
}).catch(() => {
1119+
// If benchmark fails, render once so test assertions can still run
1120+
const React = _getReact();
1121+
const element = React.createElement(Component, ...createElementArgs);
1122+
return renderFn(element);
1123+
});
11851124
}
11861125

11871126
/**

packages/codeflash/runtime/index.js

Lines changed: 65 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -16,80 +16,80 @@
1616
* import { capture, capturePerf } from 'codeflash';
1717
*/
1818

19-
'use strict';
19+
"use strict";
2020

2121
// Main capture functions (instrumentation)
22-
const capture = require('./capture');
22+
const capture = require("./capture");
2323

2424
// Serialization utilities
25-
const serializer = require('./serializer');
25+
const serializer = require("./serializer");
2626

2727
// Comparison utilities
28-
const comparator = require('./comparator');
28+
const comparator = require("./comparator");
2929

3030
// Result comparison (used by CLI)
31-
const compareResults = require('./compare-results');
31+
const compareResults = require("./compare-results");
3232

3333
// Re-export all public APIs
3434
module.exports = {
35-
// === Main Instrumentation API ===
36-
capture: capture.capture,
37-
capturePerf: capture.capturePerf,
38-
39-
captureRender: capture.captureRender,
40-
captureRenderPerf: capture.captureRenderPerf,
41-
42-
captureMultiple: capture.captureMultiple,
43-
44-
// === Test Lifecycle ===
45-
writeResults: capture.writeResults,
46-
clearResults: capture.clearResults,
47-
getResults: capture.getResults,
48-
setTestName: capture.setTestName,
49-
initDatabase: capture.initDatabase,
50-
resetInvocationCounters: capture.resetInvocationCounters,
51-
52-
// === Serialization ===
53-
serialize: serializer.serialize,
54-
deserialize: serializer.deserialize,
55-
getSerializerType: serializer.getSerializerType,
56-
safeSerialize: capture.safeSerialize,
57-
safeDeserialize: capture.safeDeserialize,
58-
59-
// === Comparison ===
60-
comparator: comparator.comparator,
61-
createComparator: comparator.createComparator,
62-
strictComparator: comparator.strictComparator,
63-
looseComparator: comparator.looseComparator,
64-
isClose: comparator.isClose,
65-
66-
// === Result Comparison (CLI helpers) ===
67-
readTestResults: compareResults.readTestResults,
68-
compareResults: compareResults.compareResults,
69-
compareBuffers: compareResults.compareBuffers,
70-
71-
// === Utilities ===
72-
getInvocationIndex: capture.getInvocationIndex,
73-
sanitizeTestId: capture.sanitizeTestId,
74-
75-
// === Constants ===
76-
LOOP_INDEX: capture.LOOP_INDEX,
77-
OUTPUT_FILE: capture.OUTPUT_FILE,
78-
TEST_ITERATION: capture.TEST_ITERATION,
79-
80-
// === Batch Looping Control (used by loop-runner) ===
81-
incrementBatch: capture.incrementBatch,
82-
getCurrentBatch: capture.getCurrentBatch,
83-
checkSharedTimeLimit: capture.checkSharedTimeLimit,
84-
// Getter functions for dynamic env var reading (not constants)
85-
getPerfBatchSize: capture.getPerfBatchSize,
86-
getPerfLoopCount: capture.getPerfLoopCount,
87-
getPerfMinLoops: capture.getPerfMinLoops,
88-
getPerfTargetDurationMs: capture.getPerfTargetDurationMs,
89-
getPerfStabilityCheck: capture.getPerfStabilityCheck,
90-
getPerfCurrentBatch: capture.getPerfCurrentBatch,
91-
92-
// === Feature Detection ===
93-
hasV8: serializer.hasV8,
94-
hasMsgpack: serializer.hasMsgpack,
35+
// === Main Instrumentation API ===
36+
capture: capture.capture,
37+
capturePerf: capture.capturePerf,
38+
39+
captureRender: capture.captureRender,
40+
captureRenderPerf: capture.captureRenderPerf,
41+
42+
captureMultiple: capture.captureMultiple,
43+
44+
// === Test Lifecycle ===
45+
writeResults: capture.writeResults,
46+
clearResults: capture.clearResults,
47+
getResults: capture.getResults,
48+
setTestName: capture.setTestName,
49+
initDatabase: capture.initDatabase,
50+
resetInvocationCounters: capture.resetInvocationCounters,
51+
52+
// === Serialization ===
53+
serialize: serializer.serialize,
54+
deserialize: serializer.deserialize,
55+
getSerializerType: serializer.getSerializerType,
56+
safeSerialize: capture.safeSerialize,
57+
safeDeserialize: capture.safeDeserialize,
58+
59+
// === Comparison ===
60+
comparator: comparator.comparator,
61+
createComparator: comparator.createComparator,
62+
strictComparator: comparator.strictComparator,
63+
looseComparator: comparator.looseComparator,
64+
isClose: comparator.isClose,
65+
66+
// === Result Comparison (CLI helpers) ===
67+
readTestResults: compareResults.readTestResults,
68+
compareResults: compareResults.compareResults,
69+
compareBuffers: compareResults.compareBuffers,
70+
71+
// === Utilities ===
72+
getInvocationIndex: capture.getInvocationIndex,
73+
sanitizeTestId: capture.sanitizeTestId,
74+
75+
// === Constants ===
76+
LOOP_INDEX: capture.LOOP_INDEX,
77+
OUTPUT_FILE: capture.OUTPUT_FILE,
78+
TEST_ITERATION: capture.TEST_ITERATION,
79+
80+
// === Batch Looping Control (used by loop-runner) ===
81+
incrementBatch: capture.incrementBatch,
82+
getCurrentBatch: capture.getCurrentBatch,
83+
checkSharedTimeLimit: capture.checkSharedTimeLimit,
84+
// Getter functions for dynamic env var reading (not constants)
85+
getPerfBatchSize: capture.getPerfBatchSize,
86+
getPerfLoopCount: capture.getPerfLoopCount,
87+
getPerfMinLoops: capture.getPerfMinLoops,
88+
getPerfTargetDurationMs: capture.getPerfTargetDurationMs,
89+
getPerfStabilityCheck: capture.getPerfStabilityCheck,
90+
getPerfCurrentBatch: capture.getPerfCurrentBatch,
91+
92+
// === Feature Detection ===
93+
hasV8: serializer.hasV8,
94+
hasMsgpack: serializer.hasMsgpack,
9595
};

0 commit comments

Comments
 (0)