@monster-orange/den 0.0.0 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +67 -0
- package/LICENSE +21 -0
- package/README.md +165 -1
- package/bin/den +61 -0
- package/docs/distributions.md +256 -0
- package/make/demo.mk +82 -0
- package/make/den.mk +80 -0
- package/make/journeys.mk +57 -0
- package/make/local_dev.mk +154 -0
- package/make/security.mk +59 -0
- package/make/test.mk +405 -0
- package/package.json +99 -4
- package/scripts/auth-configure.ts +415 -0
- package/scripts/heal-config.ts +92 -0
- package/scripts/jsonl-playwright-reporter.js +70 -0
- package/scripts/manifest-read.cjs +91 -0
- package/scripts/setup-1password.ts +570 -0
- package/scripts/setup-secrets.ts +149 -0
- package/scripts/view-results.js +156 -0
- package/service/install.sh +112 -0
- package/src/pillars/api/fixtures/index.ts +128 -0
- package/src/pillars/api/fixtures/openapi-validator.ts +111 -0
- package/src/pillars/api/global-setup.ts +103 -0
- package/src/pillars/api/playwright.config.ts +105 -0
- package/src/pillars/api/schemas/example-api.json +110 -0
- package/src/pillars/api/tests/health/health.spec.ts +19 -0
- package/src/pillars/api/tests/health/manifest.yml +6 -0
- package/src/pillars/browser/fixtures/index.ts +232 -0
- package/src/pillars/browser/fixtures/package.json +10 -0
- package/src/pillars/browser/global-setup.ts +140 -0
- package/src/pillars/browser/global-teardown.ts +48 -0
- package/src/pillars/browser/playwright.config.ts +212 -0
- package/src/pillars/browser/setup.ts +219 -0
- package/src/pillars/journeys/app-config.ts +237 -0
- package/src/pillars/journeys/discover.ts +160 -0
- package/src/pillars/journeys/hooks/deny-shell.cjs +44 -0
- package/src/pillars/journeys/observability/events.ts +288 -0
- package/src/pillars/journeys/observability/sdk-mapper.ts +306 -0
- package/src/pillars/journeys/report/friction.ts +304 -0
- package/src/pillars/journeys/report/junit.ts +105 -0
- package/src/pillars/journeys/report/render.ts +273 -0
- package/src/pillars/journeys/report/results.ts +323 -0
- package/src/pillars/journeys/run.ts +673 -0
- package/src/pillars/journeys/runner.ts +1025 -0
- package/src/pillars/journeys/sandbox.ts +406 -0
- package/src/pillars/journeys/spec/loader.ts +152 -0
- package/src/pillars/journeys/spec/schema.ts +208 -0
- package/src/pillars/journeys/teardown.ts +98 -0
- package/src/pillars/journeys/tools/browser-session.ts +219 -0
- package/src/pillars/journeys/tools/browser-tool.ts +914 -0
- package/src/pillars/journeys/tools/http-tool.ts +564 -0
- package/src/pillars/journeys/tools/ledger.ts +278 -0
- package/src/pillars/journeys/tools/snapshot-store.ts +117 -0
- package/src/pillars/journeys/verify/assertions.ts +502 -0
- package/src/pillars/journeys/verify/verdict.ts +71 -0
- package/src/pillars/perf/build-report.ts +36 -0
- package/src/pillars/perf/k6.config.js +112 -0
- package/src/pillars/perf/run.ts +345 -0
- package/src/pillars/perf/scenarios/load-profile.js +97 -0
- package/src/pillars/perf/staging.ts +73 -0
- package/src/pillars/security/discover.ts +108 -0
- package/src/pillars/security/findings.ts +151 -0
- package/src/pillars/security/plan.ts +355 -0
- package/src/pillars/security/run.ts +729 -0
- package/src/pillars/smoke/admin-probe.ts +68 -0
- package/src/pillars/smoke/base-url.ts +56 -0
- package/src/pillars/smoke/cleanup.ts +8 -0
- package/src/pillars/smoke/discover.ts +237 -0
- package/src/pillars/smoke/ensure-smoke-teardown.ts +40 -0
- package/src/pillars/smoke/fixture-manager.ts +217 -0
- package/src/pillars/smoke/fixtures.ts +468 -0
- package/src/pillars/smoke/global-setup-all.ts +262 -0
- package/src/pillars/smoke/global-setup-global.ts +262 -0
- package/src/pillars/smoke/global-setup.ts +273 -0
- package/src/pillars/smoke/global-teardown-all.ts +8 -0
- package/src/pillars/smoke/global-teardown-global.ts +8 -0
- package/src/pillars/smoke/global-teardown.ts +139 -0
- package/src/pillars/smoke/playwright-all.config.ts +147 -0
- package/src/pillars/smoke/playwright-global.config.ts +141 -0
- package/src/pillars/smoke/playwright.config.ts +162 -0
- package/src/pillars/smoke/setup-env.ts +132 -0
- package/src/pillars/smoke/setup-global.ts +126 -0
- package/src/pillars/smoke/setup.ts +115 -0
- package/src/pillars/smoke/smoke-repo.ts +16 -0
- package/src/pillars/smoke/staging.ts +49 -0
- package/src/pillars/suites/run.ts +147 -0
- package/src/scripts/configure-aws-profiles.ts +114 -0
- package/src/scripts/register-repos.ts +307 -0
- package/src/scripts/setup-default-workspace.ts +203 -0
- package/src/shared/applications.ts +138 -0
- package/src/shared/auth/api-token.ts +416 -0
- package/src/shared/auth/auth0-helper.ts +284 -0
- package/src/shared/auth/headless-login.ts +265 -0
- package/src/shared/auth/jwt-helper.ts +186 -0
- package/src/shared/auth/sudo-vault.ts +69 -0
- package/src/shared/auth/test-accounts.ts +98 -0
- package/src/shared/auth/token-injection.ts +184 -0
- package/src/shared/auth/token-providers/auth0.ts +80 -0
- package/src/shared/auth/token-providers/index.ts +182 -0
- package/src/shared/auth/token-refresh.ts +273 -0
- package/src/shared/banner.ts +15 -0
- package/src/shared/brand.ts +79 -0
- package/src/shared/cli-tabs.ts +131 -0
- package/src/shared/constants.ts +128 -0
- package/src/shared/den-config.ts +566 -0
- package/src/shared/dev-metrics/emit.ts +354 -0
- package/src/shared/edge/config.ts +229 -0
- package/src/shared/edge/render.ts +221 -0
- package/src/shared/edge/runtime.ts +268 -0
- package/src/shared/edge/types.ts +62 -0
- package/src/shared/engine.ts +67 -0
- package/src/shared/environment/aws-profiles.ts +49 -0
- package/src/shared/environment/env-selector.ts +161 -0
- package/src/shared/environment/playwright-env.ts +82 -0
- package/src/shared/environment-manager/catalog.ts +77 -0
- package/src/shared/environment-manager/clone-guard.ts +163 -0
- package/src/shared/environment-manager/dag.ts +286 -0
- package/src/shared/environment-manager/default-workspace.ts +182 -0
- package/src/shared/environment-manager/derivation/alias.ts +90 -0
- package/src/shared/environment-manager/derivation/cache.ts +97 -0
- package/src/shared/environment-manager/derivation/derive-graph.ts +421 -0
- package/src/shared/environment-manager/derivation/derived-up.ts +354 -0
- package/src/shared/environment-manager/derivation/load-alias.ts +11 -0
- package/src/shared/environment-manager/derivation/rules-compose.ts +42 -0
- package/src/shared/environment-manager/derivation/rules-events.ts +50 -0
- package/src/shared/environment-manager/derivation/rules-http.ts +247 -0
- package/src/shared/environment-manager/derivation/rules-infra.ts +64 -0
- package/src/shared/environment-manager/derivation/rules-readiness.ts +96 -0
- package/src/shared/environment-manager/derivation/scan.ts +160 -0
- package/src/shared/environment-manager/derivation/text.ts +40 -0
- package/src/shared/environment-manager/derivation/types.ts +69 -0
- package/src/shared/environment-manager/health.ts +589 -0
- package/src/shared/environment-manager/index.ts +6 -0
- package/src/shared/environment-manager/load-applications.ts +78 -0
- package/src/shared/environment-manager/log-tail.ts +212 -0
- package/src/shared/environment-manager/make-targets.ts +78 -0
- package/src/shared/environment-manager/opctl.ts +334 -0
- package/src/shared/environment-manager/readiness.ts +93 -0
- package/src/shared/environment-manager/register-local.ts +72 -0
- package/src/shared/environment-manager/secrets-check.ts +260 -0
- package/src/shared/environment-manager/service-env.ts +110 -0
- package/src/shared/environment-manager/service-runner.ts +1588 -0
- package/src/shared/environment-manager/up-plan.ts +302 -0
- package/src/shared/environment-manager/workspace-manager.ts +905 -0
- package/src/shared/environment-manager/workspaces.ts +860 -0
- package/src/shared/manifest/deps.ts +27 -0
- package/src/shared/manifest/migrate-manifest.ts +101 -0
- package/src/shared/manifest/read-manifest.ts +160 -0
- package/src/shared/manifest/schema.ts +299 -0
- package/src/shared/manifest/service-links.ts +93 -0
- package/src/shared/manifest/validate-manifest.ts +140 -0
- package/src/shared/manifest/workspace-manifests.ts +92 -0
- package/src/shared/open-url.ts +122 -0
- package/src/shared/org.ts +44 -0
- package/src/shared/paths.ts +178 -0
- package/src/shared/perf/optional-fields.ts +105 -0
- package/src/shared/perf/report-normalizer.ts +404 -0
- package/src/shared/perf/report-paths.ts +19 -0
- package/src/shared/perf/report-store.ts +110 -0
- package/src/shared/perf/report-types.ts +70 -0
- package/src/shared/perf/test-paths.ts +25 -0
- package/src/shared/process/log-rotation.ts +147 -0
- package/src/shared/process/spawn-streaming.ts +229 -0
- package/src/shared/product.ts +92 -0
- package/src/shared/profile.ts +141 -0
- package/src/shared/repo-kind.ts +32 -0
- package/src/shared/reporting/jsonl-reporter.ts +131 -0
- package/src/shared/reporting/slack-reporter.ts +340 -0
- package/src/shared/results/result-reader.ts +266 -0
- package/src/shared/results/result-writer.ts +168 -0
- package/src/shared/secrets/doppler.ts +126 -0
- package/src/shared/secrets/env.ts +48 -0
- package/src/shared/secrets/index.ts +197 -0
- package/src/shared/secrets/keys.ts +33 -0
- package/src/shared/secrets/onepassword.ts +75 -0
- package/src/shared/secrets/types.ts +52 -0
- package/src/shared/service-url.ts +119 -0
- package/src/shared/smoke/branch-resolver.ts +141 -0
- package/src/shared/smoke/git-remote.ts +70 -0
- package/src/shared/smoke/repo-cloner.ts +151 -0
- package/src/shared/stacks/config.ts +137 -0
- package/src/shared/stacks/manifest-commands.ts +88 -0
- package/src/shared/stacks/render.ts +253 -0
- package/src/shared/stacks/runtime.ts +334 -0
- package/src/shared/stacks/types.ts +53 -0
- package/src/shared/terminal/tabs.ts +212 -0
- package/src/shared/test-discovery/module-scanner.ts +569 -0
- package/src/shared/types/index.ts +678 -0
- package/src/shared/ui-marker.ts +19 -0
- package/src/ui/cli/applications-command.ts +116 -0
- package/src/ui/cli/config-command.ts +68 -0
- package/src/ui/cli/create-main.ts +9 -0
- package/src/ui/cli/create.ts +360 -0
- package/src/ui/cli/edge-command.ts +91 -0
- package/src/ui/cli/flag-parser.ts +246 -0
- package/src/ui/cli/graph-command.ts +524 -0
- package/src/ui/cli/health-display.ts +63 -0
- package/src/ui/cli/index.ts +3656 -0
- package/src/ui/cli/init.ts +855 -0
- package/src/ui/cli/invocation-target.ts +175 -0
- package/src/ui/cli/logs-command.ts +104 -0
- package/src/ui/cli/module-selection.ts +31 -0
- package/src/ui/cli/prompts.ts +767 -0
- package/src/ui/cli/results-viewer.ts +436 -0
- package/src/ui/cli/secrets-preflight.ts +91 -0
- package/src/ui/cli/stack-command.ts +144 -0
- package/src/ui/cli/stalled-setup-recovery.ts +129 -0
- package/src/ui/cli/test-runner.ts +1312 -0
- package/src/ui/cli/web-command.ts +1073 -0
- package/src/ui/server/changelog-parser.ts +94 -0
- package/src/ui/server/daemon-reaper.ts +129 -0
- package/src/ui/server/editor-manager.ts +301 -0
- package/src/ui/server/github-service.ts +223 -0
- package/src/ui/server/http-helpers.ts +98 -0
- package/src/ui/server/index.ts +3236 -0
- package/src/ui/server/log-history.ts +452 -0
- package/src/ui/server/perf-report.ts +42 -0
- package/src/ui/server/public/environments.html +494 -0
- package/src/ui/server/public/favicon.ico +0 -0
- package/src/ui/server/public/header.js +1371 -0
- package/src/ui/server/public/index.html +2595 -0
- package/src/ui/server/public/local-dev.html +7405 -0
- package/src/ui/server/public/logs.html +1489 -0
- package/src/ui/server/public/perf-report.js +359 -0
- package/src/ui/server/public/release.html +2111 -0
- package/src/ui/server/public/services.html +1098 -0
- package/src/ui/server/public/workspace-picker.js +69 -0
- package/src/ui/server/release-deploy-session.ts +246 -0
- package/src/ui/server/release-service.ts +1125 -0
- package/src/ui/server/sudo-store.ts +111 -0
- package/src/ui/server/workspace-routes.ts +2154 -0
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* k6 Configuration
|
|
3
|
+
* Base configuration for performance tests
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export const options = {
|
|
7
|
+
// VUs (Virtual Users)
|
|
8
|
+
vus: 10,
|
|
9
|
+
duration: "30s",
|
|
10
|
+
|
|
11
|
+
// Thresholds - Define SLAs
|
|
12
|
+
thresholds: {
|
|
13
|
+
// 95% of requests should be below 500ms
|
|
14
|
+
http_req_duration: ["p(95)<500"],
|
|
15
|
+
|
|
16
|
+
// Error rate should be less than 1%
|
|
17
|
+
http_req_failed: ["rate<0.01"],
|
|
18
|
+
|
|
19
|
+
// 90% of requests should be below 300ms
|
|
20
|
+
"http_req_duration{name:health}": ["p(90)<300"],
|
|
21
|
+
},
|
|
22
|
+
|
|
23
|
+
// Stages for ramping VUs
|
|
24
|
+
stages: [
|
|
25
|
+
{ duration: "30s", target: 20 }, // Ramp up to 20 users
|
|
26
|
+
{ duration: "1m", target: 20 }, // Stay at 20 users
|
|
27
|
+
{ duration: "30s", target: 0 }, // Ramp down to 0 users
|
|
28
|
+
],
|
|
29
|
+
|
|
30
|
+
// Tags - For organizing metrics
|
|
31
|
+
tags: {
|
|
32
|
+
test_type: "load",
|
|
33
|
+
},
|
|
34
|
+
|
|
35
|
+
// Summaries
|
|
36
|
+
summaryTrendStats: ["avg", "min", "med", "max", "p(90)", "p(95)", "p(99)"],
|
|
37
|
+
|
|
38
|
+
// HTTP settings
|
|
39
|
+
http: {
|
|
40
|
+
timeout: "30s",
|
|
41
|
+
},
|
|
42
|
+
|
|
43
|
+
// Rate limiting
|
|
44
|
+
// rps: 100, // Max requests per second
|
|
45
|
+
|
|
46
|
+
// Scenarios - Advanced load patterns
|
|
47
|
+
scenarios: {
|
|
48
|
+
// Constant load
|
|
49
|
+
constant_load: {
|
|
50
|
+
executor: "constant-vus",
|
|
51
|
+
vus: 10,
|
|
52
|
+
duration: "1m",
|
|
53
|
+
tags: { scenario: "constant" },
|
|
54
|
+
},
|
|
55
|
+
|
|
56
|
+
// Ramping load
|
|
57
|
+
ramping_load: {
|
|
58
|
+
executor: "ramping-vus",
|
|
59
|
+
startVUs: 0,
|
|
60
|
+
stages: [
|
|
61
|
+
{ duration: "30s", target: 50 },
|
|
62
|
+
{ duration: "1m", target: 50 },
|
|
63
|
+
{ duration: "30s", target: 0 },
|
|
64
|
+
],
|
|
65
|
+
tags: { scenario: "ramping" },
|
|
66
|
+
},
|
|
67
|
+
|
|
68
|
+
// Per-VU iterations
|
|
69
|
+
// per_vu_iterations: {
|
|
70
|
+
// executor: 'per-vu-iterations',
|
|
71
|
+
// vus: 10,
|
|
72
|
+
// iterations: 100,
|
|
73
|
+
// maxDuration: '5m',
|
|
74
|
+
// tags: { scenario: 'iterations' },
|
|
75
|
+
// },
|
|
76
|
+
|
|
77
|
+
// Constant arrival rate
|
|
78
|
+
// constant_arrival_rate: {
|
|
79
|
+
// executor: 'constant-arrival-rate',
|
|
80
|
+
// rate: 100, // 100 iterations per timeUnit
|
|
81
|
+
// timeUnit: '1s',
|
|
82
|
+
// duration: '1m',
|
|
83
|
+
// preAllocatedVUs: 50,
|
|
84
|
+
// maxVUs: 100,
|
|
85
|
+
// tags: { scenario: 'arrival' },
|
|
86
|
+
// },
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Load environment configuration
|
|
92
|
+
*/
|
|
93
|
+
export function getEnvironment() {
|
|
94
|
+
const env = __ENV.E2E_ENVIRONMENT || "local";
|
|
95
|
+
const configPath = `./config/environments/${env}.json`;
|
|
96
|
+
|
|
97
|
+
// In k6, we need to read config from environment variables
|
|
98
|
+
// since k6 doesn't support direct file reading in init context
|
|
99
|
+
return {
|
|
100
|
+
name: env,
|
|
101
|
+
baseUrl: __ENV.API_BASE_URL || "http://localhost:3000",
|
|
102
|
+
apiTimeout: parseInt(__ENV.API_TIMEOUT || "30000", 10),
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Get authentication token
|
|
108
|
+
* Note: In real tests, you'd get this via OAuth flow or from ENV
|
|
109
|
+
*/
|
|
110
|
+
export function getAuthToken() {
|
|
111
|
+
return __ENV.AUTH_TOKEN || "";
|
|
112
|
+
}
|
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
import { spawn } from "child_process";
|
|
2
|
+
import * as fs from "fs";
|
|
3
|
+
import * as path from "path";
|
|
4
|
+
import { parseArgs } from "util";
|
|
5
|
+
import { resolveManifestBaseUrl } from "../../shared/service-url.js";
|
|
6
|
+
import { getDistRoot } from "../../shared/paths.js";
|
|
7
|
+
import type { Environment } from "../../shared/types/index.js";
|
|
8
|
+
import { findManifest, resolveRepoPath } from "../smoke/discover.js";
|
|
9
|
+
import { assertStorageAdminProbe } from "../smoke/admin-probe.js";
|
|
10
|
+
import {
|
|
11
|
+
acquireFixture,
|
|
12
|
+
releaseFixturesForConsumer,
|
|
13
|
+
} from "../smoke/fixture-manager.js";
|
|
14
|
+
import { resolvePerformanceStorageFixture } from "../smoke/fixtures.js";
|
|
15
|
+
import {
|
|
16
|
+
PERF_CURRENT_REPORT_DIR,
|
|
17
|
+
getPerfSummaryArtifactPath,
|
|
18
|
+
} from "../../shared/perf/report-paths.js";
|
|
19
|
+
import {
|
|
20
|
+
writeCurrentPerfRunReport,
|
|
21
|
+
resetCurrentPerfRunReport,
|
|
22
|
+
} from "../../shared/perf/report-store.js";
|
|
23
|
+
|
|
24
|
+
import { product } from "../../shared/product.js";
|
|
25
|
+
export interface PerfRunModule {
|
|
26
|
+
repoName: string;
|
|
27
|
+
moduleName: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface RunPerfModulesOptions {
|
|
31
|
+
modules: PerfRunModule[];
|
|
32
|
+
environment: Environment;
|
|
33
|
+
baseUrl: string;
|
|
34
|
+
useLocalCheckout?: boolean;
|
|
35
|
+
env?: NodeJS.ProcessEnv;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function spawnK6Process(
|
|
39
|
+
testFile: string,
|
|
40
|
+
env: NodeJS.ProcessEnv,
|
|
41
|
+
summaryExportPath?: string,
|
|
42
|
+
): Promise<{ success: boolean; exitCode: number }> {
|
|
43
|
+
const args = ["run"];
|
|
44
|
+
if (summaryExportPath) {
|
|
45
|
+
args.push("--summary-export", summaryExportPath);
|
|
46
|
+
}
|
|
47
|
+
args.push(testFile);
|
|
48
|
+
return new Promise((resolve) => {
|
|
49
|
+
const proc = spawn("k6", args, {
|
|
50
|
+
env,
|
|
51
|
+
stdio: "inherit",
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
proc.on("close", (code) => {
|
|
55
|
+
resolve({
|
|
56
|
+
success: code === 0,
|
|
57
|
+
exitCode: code ?? 1,
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
proc.on("error", (error) => {
|
|
62
|
+
console.error(`Failed to execute k6: ${error.message}`);
|
|
63
|
+
resolve({
|
|
64
|
+
success: false,
|
|
65
|
+
exitCode: 1,
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function logPerfTeardownFailure(error: unknown): void {
|
|
72
|
+
if (error instanceof AggregateError) {
|
|
73
|
+
console.error("Performance storage fixture teardown failed:");
|
|
74
|
+
for (const [index, err] of error.errors.entries()) {
|
|
75
|
+
console.error(` [${index + 1}/${error.errors.length}]`, err);
|
|
76
|
+
}
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
80
|
+
console.error(`Performance storage fixture teardown failed: ${message}`);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export async function runPerfModules(
|
|
84
|
+
options: RunPerfModulesOptions,
|
|
85
|
+
): Promise<{ success: boolean; exitCode: number }> {
|
|
86
|
+
const env: NodeJS.ProcessEnv = {
|
|
87
|
+
...process.env,
|
|
88
|
+
...(options.env ?? {}),
|
|
89
|
+
E2E_ENVIRONMENT: options.environment,
|
|
90
|
+
BASE_URL: options.baseUrl,
|
|
91
|
+
BASE_API_URL: options.baseUrl,
|
|
92
|
+
};
|
|
93
|
+
const releases: Array<() => Promise<void>> = [];
|
|
94
|
+
|
|
95
|
+
let k6Outcome = { success: true, exitCode: 0 };
|
|
96
|
+
let caughtError: unknown = undefined;
|
|
97
|
+
let teardownFailed = false;
|
|
98
|
+
let disabledSkipCount = 0;
|
|
99
|
+
const completedModuleKeys: string[] = [];
|
|
100
|
+
|
|
101
|
+
// Den-root anchored: this child inherits its spawner's (arbitrary) cwd.
|
|
102
|
+
fs.mkdirSync(path.resolve(getDistRoot(), PERF_CURRENT_REPORT_DIR), {
|
|
103
|
+
recursive: true,
|
|
104
|
+
});
|
|
105
|
+
// Clear the prior run's report so a stale one is never served.
|
|
106
|
+
resetCurrentPerfRunReport();
|
|
107
|
+
|
|
108
|
+
try {
|
|
109
|
+
for (const moduleRef of options.modules) {
|
|
110
|
+
const discovered = findManifest(moduleRef.repoName);
|
|
111
|
+
if (!discovered) {
|
|
112
|
+
throw new Error(
|
|
113
|
+
`Performance repo "${moduleRef.repoName}" is not registered with ${product().cli}`,
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const repoPath = resolveRepoPath(
|
|
118
|
+
discovered,
|
|
119
|
+
options.environment,
|
|
120
|
+
options.useLocalCheckout ?? false,
|
|
121
|
+
);
|
|
122
|
+
const moduleConfig =
|
|
123
|
+
discovered.manifest.performance?.[moduleRef.moduleName];
|
|
124
|
+
if (!moduleConfig) {
|
|
125
|
+
throw new Error(
|
|
126
|
+
`Performance module "${moduleRef.moduleName}" not found in ${moduleRef.repoName}/${product().manifestYml}`,
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (moduleConfig.enabled === false) {
|
|
131
|
+
console.warn(
|
|
132
|
+
`Skipping disabled performance module "${moduleRef.moduleName}" (${moduleRef.repoName})`,
|
|
133
|
+
);
|
|
134
|
+
disabledSkipCount += 1;
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const moduleEnv = {
|
|
139
|
+
...env,
|
|
140
|
+
PERF_TEST_ID: `${moduleRef.repoName}/${moduleRef.moduleName}`,
|
|
141
|
+
PERF_RUN_ID:
|
|
142
|
+
env.PERF_RUN_ID ??
|
|
143
|
+
`${moduleRef.repoName}-${moduleRef.moduleName}-${Date.now()}`,
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
if (moduleConfig.fixtures?.storage?.[options.environment]) {
|
|
147
|
+
const lifecycle = resolvePerformanceStorageFixture(
|
|
148
|
+
discovered.manifest,
|
|
149
|
+
moduleRef.moduleName,
|
|
150
|
+
options.environment,
|
|
151
|
+
moduleEnv,
|
|
152
|
+
);
|
|
153
|
+
if (lifecycle.adminProbePath) {
|
|
154
|
+
await assertStorageAdminProbe({
|
|
155
|
+
baseUrl: options.baseUrl,
|
|
156
|
+
adminProbePath: lifecycle.adminProbePath,
|
|
157
|
+
label: `${moduleRef.repoName} / ${moduleRef.moduleName} (${moduleConfig.service})`,
|
|
158
|
+
...(lifecycle.adminProbe403Hint
|
|
159
|
+
? { hintOn403: () => lifecycle.adminProbe403Hint! }
|
|
160
|
+
: {}),
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
const fixtureEnv = { ...moduleEnv, ...lifecycle.envVars };
|
|
164
|
+
const release = await acquireFixture({
|
|
165
|
+
fixtureKey: lifecycle.fixtureKey,
|
|
166
|
+
resolvedPayload: lifecycle.resolvedPayload as unknown as Record<
|
|
167
|
+
string,
|
|
168
|
+
unknown
|
|
169
|
+
>,
|
|
170
|
+
cwd: repoPath,
|
|
171
|
+
env: fixtureEnv,
|
|
172
|
+
setup: lifecycle.setup,
|
|
173
|
+
teardown: lifecycle.teardown,
|
|
174
|
+
});
|
|
175
|
+
releases.push(release);
|
|
176
|
+
Object.assign(moduleEnv, lifecycle.envVars);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const testFile = path.resolve(
|
|
180
|
+
repoPath,
|
|
181
|
+
moduleConfig.testDir,
|
|
182
|
+
moduleConfig.testMatch,
|
|
183
|
+
);
|
|
184
|
+
const moduleKey = `${moduleRef.repoName}/${moduleRef.moduleName}`;
|
|
185
|
+
// Absolute: k6 resolves relative paths against its inherited cwd.
|
|
186
|
+
const summaryPath = path.resolve(
|
|
187
|
+
getDistRoot(),
|
|
188
|
+
getPerfSummaryArtifactPath(moduleKey),
|
|
189
|
+
);
|
|
190
|
+
const result = await spawnK6Process(testFile, moduleEnv, summaryPath);
|
|
191
|
+
completedModuleKeys.push(moduleKey);
|
|
192
|
+
k6Outcome = result;
|
|
193
|
+
if (!result.success) {
|
|
194
|
+
break;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (
|
|
199
|
+
disabledSkipCount === options.modules.length &&
|
|
200
|
+
options.modules.length > 0
|
|
201
|
+
) {
|
|
202
|
+
throw new Error(
|
|
203
|
+
`All requested performance modules are disabled in manifest: ${options.modules
|
|
204
|
+
.map((m) => `${m.repoName}/${m.moduleName}`)
|
|
205
|
+
.join(", ")}`,
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
} catch (e) {
|
|
209
|
+
caughtError = e;
|
|
210
|
+
} finally {
|
|
211
|
+
try {
|
|
212
|
+
await releaseFixturesForConsumer(releases);
|
|
213
|
+
} catch (e) {
|
|
214
|
+
teardownFailed = true;
|
|
215
|
+
logPerfTeardownFailure(e);
|
|
216
|
+
}
|
|
217
|
+
console.log(" Storage fixture teardown completed.");
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (completedModuleKeys.length > 0) {
|
|
221
|
+
try {
|
|
222
|
+
writeCurrentPerfRunReport({
|
|
223
|
+
environment: options.environment,
|
|
224
|
+
modules: completedModuleKeys,
|
|
225
|
+
finishedAt: new Date().toISOString(),
|
|
226
|
+
});
|
|
227
|
+
} catch (e) {
|
|
228
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
229
|
+
console.warn(`Failed to generate perf report: ${msg}`);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if (caughtError !== undefined) {
|
|
234
|
+
throw caughtError;
|
|
235
|
+
}
|
|
236
|
+
if (teardownFailed) {
|
|
237
|
+
return {
|
|
238
|
+
success: false,
|
|
239
|
+
exitCode: k6Outcome.success ? 1 : k6Outcome.exitCode,
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
return k6Outcome;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function cliArgs() {
|
|
246
|
+
return parseArgs({
|
|
247
|
+
options: {
|
|
248
|
+
env: { type: "string" },
|
|
249
|
+
repo: { type: "string", multiple: true },
|
|
250
|
+
module: { type: "string", multiple: true },
|
|
251
|
+
"base-url": { type: "string" },
|
|
252
|
+
"use-local-checkout": { type: "boolean" },
|
|
253
|
+
},
|
|
254
|
+
}).values;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Auto-resolve a base URL for perf runs from the manifest's
|
|
259
|
+
* `services.*.health.<env>` entry, falling back to
|
|
260
|
+
* `config/environments/<env>.json → baseUrls.api`.
|
|
261
|
+
*/
|
|
262
|
+
export function resolveBaseUrlFromManifest(
|
|
263
|
+
repoName: string,
|
|
264
|
+
moduleName: string,
|
|
265
|
+
environment: Environment,
|
|
266
|
+
): string | null {
|
|
267
|
+
const discovered = findManifest(repoName);
|
|
268
|
+
if (!discovered) return null;
|
|
269
|
+
|
|
270
|
+
const moduleConfig = discovered.manifest.performance?.[moduleName];
|
|
271
|
+
if (!moduleConfig) return null;
|
|
272
|
+
|
|
273
|
+
const serviceName = moduleConfig.service ?? repoName;
|
|
274
|
+
const baseUrlKey = moduleConfig.baseUrlKey ?? "api";
|
|
275
|
+
|
|
276
|
+
const manifestUrl = resolveManifestBaseUrl({
|
|
277
|
+
manifest: discovered.manifest,
|
|
278
|
+
repoName,
|
|
279
|
+
serviceName,
|
|
280
|
+
environment,
|
|
281
|
+
baseUrlKey,
|
|
282
|
+
});
|
|
283
|
+
if (manifestUrl) return manifestUrl;
|
|
284
|
+
|
|
285
|
+
try {
|
|
286
|
+
const configPath = path.resolve(
|
|
287
|
+
`./config/environments/${environment}.json`,
|
|
288
|
+
);
|
|
289
|
+
const config = JSON.parse(fs.readFileSync(configPath, "utf-8"));
|
|
290
|
+
return config.baseUrls?.services?.[serviceName] ?? null;
|
|
291
|
+
} catch {
|
|
292
|
+
return null;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
export async function main(): Promise<number> {
|
|
297
|
+
const values = cliArgs();
|
|
298
|
+
const repos = values.repo ?? [];
|
|
299
|
+
const moduleNames = values.module ?? [];
|
|
300
|
+
if (repos.length !== moduleNames.length || repos.length === 0) {
|
|
301
|
+
throw new Error(
|
|
302
|
+
"Performance runner requires matching --repo and --module arguments",
|
|
303
|
+
);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
const environment = (values.env ??
|
|
307
|
+
process.env.E2E_ENVIRONMENT ??
|
|
308
|
+
"local") as Environment;
|
|
309
|
+
|
|
310
|
+
const baseUrl =
|
|
311
|
+
resolveBaseUrlFromManifest(repos[0], moduleNames[0], environment) ??
|
|
312
|
+
values["base-url"] ??
|
|
313
|
+
process.env.BASE_URL ??
|
|
314
|
+
process.env.BASE_API_URL;
|
|
315
|
+
if (!baseUrl) {
|
|
316
|
+
throw new Error(
|
|
317
|
+
"Could not determine base URL. Provide --base-url, set BASE_URL, " +
|
|
318
|
+
`or add a services.*.health.<env> entry in ${product().manifestYml}`,
|
|
319
|
+
);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
const result = await runPerfModules({
|
|
323
|
+
modules: repos.map((repoName, index) => ({
|
|
324
|
+
repoName,
|
|
325
|
+
moduleName: moduleNames[index],
|
|
326
|
+
})),
|
|
327
|
+
environment,
|
|
328
|
+
baseUrl,
|
|
329
|
+
useLocalCheckout: values["use-local-checkout"] ?? false,
|
|
330
|
+
env: process.env,
|
|
331
|
+
});
|
|
332
|
+
return result.exitCode;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
if (process.argv[1]?.endsWith("/run.ts")) {
|
|
336
|
+
main()
|
|
337
|
+
.then((exitCode) => {
|
|
338
|
+
process.exitCode = exitCode;
|
|
339
|
+
})
|
|
340
|
+
.catch((error: unknown) => {
|
|
341
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
342
|
+
console.error(message);
|
|
343
|
+
process.exitCode = 1;
|
|
344
|
+
});
|
|
345
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Load Test Scenarios
|
|
3
|
+
* Different load patterns for various testing needs
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Baseline Load Test
|
|
8
|
+
* Constant load to establish performance baseline
|
|
9
|
+
*/
|
|
10
|
+
export const baselineLoad = {
|
|
11
|
+
executor: "constant-vus",
|
|
12
|
+
vus: 10,
|
|
13
|
+
duration: "2m",
|
|
14
|
+
tags: { test_type: "baseline" },
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Stress Test
|
|
19
|
+
* Gradually increase load to find breaking point
|
|
20
|
+
*/
|
|
21
|
+
export const stressTest = {
|
|
22
|
+
executor: "ramping-vus",
|
|
23
|
+
startVUs: 0,
|
|
24
|
+
stages: [
|
|
25
|
+
{ duration: "2m", target: 50 }, // Ramp up to 50
|
|
26
|
+
{ duration: "5m", target: 100 }, // Ramp up to 100
|
|
27
|
+
{ duration: "2m", target: 200 }, // Ramp up to 200
|
|
28
|
+
{ duration: "5m", target: 200 }, // Stay at 200
|
|
29
|
+
{ duration: "2m", target: 0 }, // Ramp down
|
|
30
|
+
],
|
|
31
|
+
tags: { test_type: "stress" },
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Spike Test
|
|
36
|
+
* Sudden spike in traffic
|
|
37
|
+
*/
|
|
38
|
+
export const spikeTest = {
|
|
39
|
+
executor: "ramping-vus",
|
|
40
|
+
startVUs: 0,
|
|
41
|
+
stages: [
|
|
42
|
+
{ duration: "30s", target: 10 }, // Normal load
|
|
43
|
+
{ duration: "10s", target: 200 }, // Spike!
|
|
44
|
+
{ duration: "1m", target: 200 }, // Sustain spike
|
|
45
|
+
{ duration: "10s", target: 10 }, // Back to normal
|
|
46
|
+
{ duration: "30s", target: 10 }, // Stay at normal
|
|
47
|
+
{ duration: "10s", target: 0 }, // Ramp down
|
|
48
|
+
],
|
|
49
|
+
tags: { test_type: "spike" },
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Soak Test (Endurance Test)
|
|
54
|
+
* Sustained load over extended period
|
|
55
|
+
*/
|
|
56
|
+
export const soakTest = {
|
|
57
|
+
executor: "constant-vus",
|
|
58
|
+
vus: 50,
|
|
59
|
+
duration: "30m", // Run for 30 minutes
|
|
60
|
+
tags: { test_type: "soak" },
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Breakpoint Test
|
|
65
|
+
* Incrementally increase load until system breaks
|
|
66
|
+
*/
|
|
67
|
+
export const breakpointTest = {
|
|
68
|
+
executor: "ramping-arrival-rate",
|
|
69
|
+
startRate: 10,
|
|
70
|
+
timeUnit: "1s",
|
|
71
|
+
preAllocatedVUs: 50,
|
|
72
|
+
maxVUs: 500,
|
|
73
|
+
stages: [
|
|
74
|
+
{ duration: "1m", target: 10 },
|
|
75
|
+
{ duration: "1m", target: 20 },
|
|
76
|
+
{ duration: "1m", target: 40 },
|
|
77
|
+
{ duration: "1m", target: 80 },
|
|
78
|
+
{ duration: "1m", target: 160 },
|
|
79
|
+
{ duration: "1m", target: 320 },
|
|
80
|
+
],
|
|
81
|
+
tags: { test_type: "breakpoint" },
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Peak Traffic Test
|
|
86
|
+
* Simulate peak business hours
|
|
87
|
+
*/
|
|
88
|
+
export const peakTrafficTest = {
|
|
89
|
+
executor: "ramping-vus",
|
|
90
|
+
startVUs: 0,
|
|
91
|
+
stages: [
|
|
92
|
+
{ duration: "5m", target: 100 }, // Morning ramp up
|
|
93
|
+
{ duration: "15m", target: 100 }, // Peak hours
|
|
94
|
+
{ duration: "5m", target: 0 }, // Ramp down
|
|
95
|
+
],
|
|
96
|
+
tags: { test_type: "peak" },
|
|
97
|
+
};
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import * as fs from "fs";
|
|
2
|
+
import * as path from "path";
|
|
3
|
+
|
|
4
|
+
export interface StagePerfTreeResult {
|
|
5
|
+
copiedFiles: number;
|
|
6
|
+
matchedTestFiles: number;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function normalizePath(value: string): string {
|
|
10
|
+
return value.split(path.sep).join("/");
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function matchesTestPath(relativePath: string, testMatch: string): boolean {
|
|
14
|
+
const normalizedPath = normalizePath(relativePath);
|
|
15
|
+
|
|
16
|
+
if (normalizedPath === testMatch) {
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (testMatch.startsWith("**/*.")) {
|
|
21
|
+
return normalizedPath.endsWith(testMatch.slice("**/*".length));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (testMatch.startsWith("**/")) {
|
|
25
|
+
const suffix = testMatch.slice(3);
|
|
26
|
+
return normalizedPath === suffix || normalizedPath.endsWith(`/${suffix}`);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (!testMatch.includes("/")) {
|
|
30
|
+
return path.basename(normalizedPath) === testMatch;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function stagePerfTree(
|
|
37
|
+
sourceDir: string,
|
|
38
|
+
destinationDir: string,
|
|
39
|
+
testMatch: string,
|
|
40
|
+
): StagePerfTreeResult {
|
|
41
|
+
let copiedFiles = 0;
|
|
42
|
+
let matchedTestFiles = 0;
|
|
43
|
+
|
|
44
|
+
function copyRecursive(src: string, dst: string, relativeDir = ""): void {
|
|
45
|
+
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
|
|
46
|
+
const sourcePath = path.join(src, entry.name);
|
|
47
|
+
const destinationPath = path.join(dst, entry.name);
|
|
48
|
+
const relativePath = relativeDir
|
|
49
|
+
? path.join(relativeDir, entry.name)
|
|
50
|
+
: entry.name;
|
|
51
|
+
|
|
52
|
+
if (entry.isDirectory()) {
|
|
53
|
+
fs.mkdirSync(destinationPath, { recursive: true });
|
|
54
|
+
copyRecursive(sourcePath, destinationPath, relativePath);
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
fs.copyFileSync(sourcePath, destinationPath);
|
|
59
|
+
copiedFiles += 1;
|
|
60
|
+
if (matchesTestPath(relativePath, testMatch)) {
|
|
61
|
+
matchedTestFiles += 1;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
fs.mkdirSync(destinationDir, { recursive: true });
|
|
67
|
+
copyRecursive(sourceDir, destinationDir);
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
copiedFiles,
|
|
71
|
+
matchedTestFiles,
|
|
72
|
+
};
|
|
73
|
+
}
|