Skip to content

performance test#41

Draft
titanh3art wants to merge 5 commits intomainfrom
perf-test
Draft

performance test#41
titanh3art wants to merge 5 commits intomainfrom
perf-test

Conversation

@titanh3art
Copy link
Copy Markdown
Contributor

Have you...

  • Added relevant entry to the change log?

@github-actions
Copy link
Copy Markdown

github-actions bot commented Mar 30, 2026

Dependency Review

The following issues were found:
  • ✅ 0 vulnerable package(s)
  • ✅ 0 package(s) with incompatible licenses
  • ✅ 0 package(s) with invalid SPDX license definitions
  • ⚠️ 2 package(s) with unknown licenses.
See the Details below.

Snapshot Warnings

⚠️: No snapshots were found for the head SHA 1ce9e04.
Ensure that dependencies are being submitted on PR branches and consider enabling retry-on-snapshot-warnings. See the documentation for more information and troubleshooting advice.

License Issues

.github/workflows/performance-tests.yml

PackageVersionLicenseIssue Type
actions/checkout4.*.*NullUnknown License
actions/setup-node4.*.*NullUnknown License

OpenSSF Scorecard

PackageVersionScoreDetails
actions/actions/checkout 4.*.* 🟢 6
Details
CheckScoreReason
Maintained⚠️ 23 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 2
Code-Review🟢 10all changesets reviewed
Binary-Artifacts🟢 10no binaries found in the repo
Dangerous-Workflow🟢 10no dangerous workflow patterns detected
CII-Best-Practices⚠️ 0no effort to earn an OpenSSF best practices badge detected
Token-Permissions⚠️ 0detected GitHub workflow tokens with excessive permissions
Packaging⚠️ -1packaging workflow not detected
License🟢 10license file detected
Fuzzing⚠️ 0project is not fuzzed
Signed-Releases⚠️ -1no releases found
Pinned-Dependencies🟢 3dependency not pinned by hash detected -- score normalized to 3
Security-Policy🟢 9security policy file detected
Branch-Protection🟢 6branch protection is not maximal on development and all release branches
SAST🟢 8SAST tool detected but not run on all commits
actions/actions/setup-node 4.*.* 🟢 6
Details
CheckScoreReason
Maintained🟢 911 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 9
Code-Review🟢 10all changesets reviewed
Dangerous-Workflow🟢 10no dangerous workflow patterns detected
CII-Best-Practices⚠️ 0no effort to earn an OpenSSF best practices badge detected
Packaging⚠️ -1packaging workflow not detected
Binary-Artifacts🟢 9binaries present in source code
Token-Permissions⚠️ 0detected GitHub workflow tokens with excessive permissions
Pinned-Dependencies⚠️ 0dependency not pinned by hash detected -- score normalized to 0
Fuzzing⚠️ 0project is not fuzzed
License🟢 10license file detected
Signed-Releases⚠️ -1no releases found
Security-Policy🟢 9security policy file detected
Branch-Protection⚠️ 1branch protection is not maximal on development and all release branches
SAST🟢 9SAST tool is not run on all commits -- score normalized to 9

Scanned Files

  • .github/workflows/performance-tests.yml

@hyperspace-insights
Copy link
Copy Markdown
Contributor

Summary

The following content is AI-generated and provides a summary of the pull request:


Add Performance Testing Infrastructure

New Features

✨ Introduces a comprehensive performance testing suite for @cap-js/data-inspector. The suite benchmarks local processing cost of EntityDefinitionReader and DataReader across multiple input sizes (10→1000) to detect non-linear algorithmic scaling and regressions against a stored baseline. Tests are isolated from external I/O by stubbing the CDS runtime, database, and audit-log service.

Changes

  • .gitignore: Added exclusion for test/performance/performance-baseline.json (machine-specific local baseline) and removed duplicate .nyc_output/ entry.
  • .mocharc.json: Added ignore rule to exclude test/performance/** from the default test run (performance tests use a separate config).
  • package.json: Added three new npm scripts — test:performance, test:performance:update-baseline, and test:performance:check-drift.
  • test/tsconfig.json: Changed rootDir from "." to ".." to support TypeScript compilation of the new nested test/performance/ directory.
  • test/performance/.mocharc.performance.json: New Mocha config scoped to performance test files.
  • test/performance/ProcessingPerformance.test.ts: Main benchmark test file. Runs five benchmarks (A1–A3 for EntityDefinitionReader, B1–B2 for DataReader), builds a report, and emits advisory warnings (no hard failures) on regressions against the baseline.
  • test/performance/helpers/types.ts: TypeScript type definitions for measurement stats, benchmark results, baseline data, and the report structure.
  • test/performance/helpers/statistics.ts: Statistical utilities — median, mean, standard deviation, 95% confidence interval, and system health checks (CPU load, memory pressure).
  • test/performance/helpers/measurement.ts: Timing infrastructure — sync/async measurement functions, outlier trimming (mean-distance method), slope computation, R² linear fit, and high-level benchmarkSync/benchmarkAsync runners with warmup support.
  • test/performance/helpers/reporting.ts: Markdown report generator with emoji-coded indicators (🟢/🟡/🔴) for slope ratio, R², and CV%.
  • test/performance/helpers/synthetic-data.ts: Deterministic generators for synthetic CDS entities and database records, plus mock request builders for EntityDefinitionReader and DataReader.
  • test/performance/helpers/index.ts: Barrel export for all helper modules.
  • test/performance/check-baseline-drift.js: Standalone Node.js script that reads git history of performance-baseline.ci.json and detects gradual per-item cost drift (total increase > 20% or ≥3 consecutive increases).
  • test/performance/performance-baseline.ci.json: Committed CI baseline with initial measurements for all five benchmarks across sizes 10–1000.
  • test/performance/PERFORMANCE-TESTING-STRATEGY.md: Detailed strategy document covering SAP Performance Product Standards coverage, benchmark methodology, baseline management, regression detection design rationale, and a full educational appendix on performance testing concepts.

  • 🔄 Regenerate and Update Summary
  • ✏️ Insert as PR Description (deletes this comment)
  • 🗑️ Delete comment
PR Bot Information

Version: 1.19.9 | 📖 Documentation | 🚨 Create Incident | 💬 Feedback

  • Correlation ID: 16e478c0-2c25-11f1-899a-d218300bc287
  • File Content Strategy: Full file content
  • Event Trigger: pull_request.opened
  • LLM: anthropic--claude-4.6-sonnet
  • Output Template: Default Template
  • Summary Prompt: Default Prompt

💌 Have ideas or want to contribute? Create an issue and share your thoughts with us!
📑 Check out the documentation for more information.
📬 Subscribe to the Hyperspace PR Bot DL to get the latest announcements and pilot features!

Made with ❤️ by Hyperspace.

Copy link
Copy Markdown
Contributor

@hyperspace-insights hyperspace-insights bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR introduces a well-structured performance testing framework, but has several substantive issues: an O(n²) bug in the B2 benchmark harness (which contaminates the measurements the framework is designed to produce), a process.exit(0) that always signals success even on drift violations, and malformed Markdown table rows in the report generator. Please address those before merging.

PR Bot Information

Version: 1.19.9 | 📖 Documentation | 🚨 Create Incident | 💬 Feedback

  • Correlation ID: 16e478c0-2c25-11f1-899a-d218300bc287
  • Event Trigger: pull_request.opened
  • LLM: anthropic--claude-4.6-sonnet
  • File Content Strategy: Full file content

Comment on lines +298 to +301
const sensitiveRecords = records.map((r: any) => ({
...r,
email: `user_${r.id}@example.com`,
phone: `+1-555-${String(records.indexOf(r)).padStart(4, "0")}`,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Performance Bug: records.indexOf(r) inside map is O(n²)

Array.prototype.indexOf performs a linear scan, so calling it inside records.map(...) makes this O(n²) in the number of records. At size=1000 this computes 1000 linear scans. This poisons the B2 benchmark by adding quadratic overhead that belongs to the harness, not the code under test.

Instead, use the map index parameter directly:

Suggested change
const sensitiveRecords = records.map((r: any) => ({
...r,
email: `user_${r.id}@example.com`,
phone: `+1-555-${String(records.indexOf(r)).padStart(4, "0")}`,
const sensitiveRecords = records.map((r: any, idx: number) => ({
...r,
email: `user_${r.id}@example.com`,
phone: `+1-555-${String(idx).padStart(4, "0")}`,
name: `User ${r.id}`,
}));

Double-check suggestion before committing. Edit this comment for amendments.


Please provide feedback on the review comment by checking the appropriate box:

  • 🌟 Awesome comment, a human might have missed that.
  • ✅ Helpful comment
  • 🤷 Neutral
  • ❌ This comment is not helpful

Comment on lines +344 to +346
let baseline: BaselineData | undefined;
if (fs.existsSync(BASELINE_PATH)) {
baseline = JSON.parse(fs.readFileSync(BASELINE_PATH, "utf8")) as BaselineData;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Logic Error: Baseline check skipped when UPDATE_BASELINE=false but no baseline exists

When the baseline file is missing and UPDATE_BASELINE is false, this.skip() is correctly called in the first before hook. However, the second before hook (which pre-generates synthetic data) runs unconditionally before the first one's this.skip() takes effect for subsequent hooks. In Mocha, this.skip() inside a before hook does abort subsequent hooks and tests, so the ordering matters: the data-generation before hook should come first (or rely on skip propagating). This is technically safe in Mocha but the logical ordering (check prerequisites first) is already correct. No code change needed — this is an observation only.

More critically: the after hook writes reports even when the test was skipped via if (!report) return; which is correct. However, a baseline file that is present but whose JSON is malformed will throw at line 346 and crash the test with an unhandled exception rather than a graceful failure. Consider wrapping the read in a try/catch:

Suggested change
let baseline: BaselineData | undefined;
if (fs.existsSync(BASELINE_PATH)) {
baseline = JSON.parse(fs.readFileSync(BASELINE_PATH, "utf8")) as BaselineData;
let baseline: BaselineData | undefined;
if (fs.existsSync(BASELINE_PATH)) {
try {
baseline = JSON.parse(fs.readFileSync(BASELINE_PATH, "utf8")) as BaselineData;
report.baseline = baseline;
} catch (e) {
console.warn(` ⚠️ WARNING: Could not parse baseline file at ${BASELINE_PATH}: ${e}`);
}
}

Double-check suggestion before committing. Edit this comment for amendments.


Please provide feedback on the review comment by checking the appropriate box:

  • 🌟 Awesome comment, a human might have missed that.
  • ✅ Helpful comment
  • 🤷 Neutral
  • ❌ This comment is not helpful

} else {
console.log("\n[PASS] No significant drift detected.");
}
process.exit(0);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: hasViolation is true but process.exit(0) is always called

When drift violations are detected, hasViolation is set and the [WARN] message is printed, but then process.exit(0) is unconditionally called on line 267 — which signals success to the calling process/CI. The strategy doc states this check is a "FAIL" condition. Should exit with a non-zero code when hasViolation is true, otherwise npm run test:performance:check-drift will never signal a problem to CI.

Suggested change
process.exit(0);
process.exit(hasViolation ? 1 : 0);

Double-check suggestion before committing. Edit this comment for amendments.


Please provide feedback on the review comment by checking the appropriate box:

  • 🌟 Awesome comment, a human might have missed that.
  • ✅ Helpful comment
  • 🤷 Neutral
  • ❌ This comment is not helpful


const perItemMs = timingsMs.map((time, index) => time / sizes[index]);
const slopes = computeSlopes(timingsMs, sizes);
const slopeRatio = slopes.length >= 2 ? slopes[slopes.length - 1] / slopes[0] : 1;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Logic Error: slopeRatio is 1 when there is only one slope segment

When sizes has exactly 2 entries, slopes has length 1, so slopes.length >= 2 is false and slopeRatio defaults to 1. This silently hides any scaling signal. The ratio requires at least 2 segments (3 size points) to be meaningful; with only 1 segment the ratio is mathematically undefined, not 1. The default of 1 could mask a real problem. Consider returning NaN or documenting the invariant, and guard the same expression in benchmarkAsync (line 245).


Please provide feedback on the review comment by checking the appropriate box:

  • 🌟 Awesome comment, a human might have missed that.
  • ✅ Helpful comment
  • 🤷 Neutral
  • ❌ This comment is not helpful

Comment on lines +108 to +121
lines.push(
[
result.name,
formatList(result.timingsMs, 2),
formatNumber(avgCV, 1) + "% " + cvEmoji(avgCV),
formatList(result.perItemMs, 7),
formatList(result.memoryDeltaMB, 2),
formatNumber(result.slopeRatio, 4) + " " + slopeRatioEmoji(result.slopeRatio),
formatNumber(result.r2, 4) + " " + r2Emoji(result.r2),
baselinePerItem,
baselineSlope,
baselineR2,
].join(" | ")
);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Markdown table rows use join(" | ") without leading/trailing pipes

The header row (line 95) uses proper Markdown table format with leading and trailing |, but each data row is assembled with join(" | ") which produces col1 | col2 | ... — missing the leading | and trailing |. This makes the rows invalid Markdown table syntax; most renderers will not display them as table rows.

Suggested change
lines.push(
[
result.name,
formatList(result.timingsMs, 2),
formatNumber(avgCV, 1) + "% " + cvEmoji(avgCV),
formatList(result.perItemMs, 7),
formatList(result.memoryDeltaMB, 2),
formatNumber(result.slopeRatio, 4) + " " + slopeRatioEmoji(result.slopeRatio),
formatNumber(result.r2, 4) + " " + r2Emoji(result.r2),
baselinePerItem,
baselineSlope,
baselineR2,
].join(" | ")
);
lines.push(
"| " +
[
result.name,
formatList(result.timingsMs, 2),
formatNumber(avgCV, 1) + "% " + cvEmoji(avgCV),
formatList(result.perItemMs, 7),
formatList(result.memoryDeltaMB, 2),
formatNumber(result.slopeRatio, 4) + " " + slopeRatioEmoji(result.slopeRatio),
formatNumber(result.r2, 4) + " " + r2Emoji(result.r2),
baselinePerItem,
baselineSlope,
baselineR2,
].join(" | ") +
" |"
);

Double-check suggestion before committing. Edit this comment for amendments.


Please provide feedback on the review comment by checking the appropriate box:

  • 🌟 Awesome comment, a human might have missed that.
  • ✅ Helpful comment
  • 🤷 Neutral
  • ❌ This comment is not helpful

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant