nx 23.1.0-beta.3 → 23.1.0-beta.4
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/dist/src/command-line/migrate/migrate.d.ts +1 -0
- package/dist/src/command-line/migrate/migrate.js +25 -4
- package/dist/src/daemon/server/project-graph-incremental-recomputation.js +30 -11
- package/dist/src/native/index.d.ts +48 -1
- package/dist/src/native/native-bindings.js +1 -0
- package/dist/src/native/nx.wasm32-wasi.debug.wasm +0 -0
- package/dist/src/native/nx.wasm32-wasi.wasm +0 -0
- package/dist/src/tasks-runner/default-tasks-runner.js +6 -3
- package/dist/src/tasks-runner/init-tasks-runner.js +1 -1
- package/dist/src/tasks-runner/life-cycle.d.ts +10 -5
- package/dist/src/tasks-runner/life-cycle.js +4 -4
- package/dist/src/tasks-runner/life-cycles/performance-analysis.d.ts +156 -0
- package/dist/src/tasks-runner/life-cycles/performance-analysis.js +486 -0
- package/dist/src/tasks-runner/life-cycles/performance-life-cycle.d.ts +59 -0
- package/dist/src/tasks-runner/life-cycles/performance-life-cycle.js +152 -0
- package/dist/src/tasks-runner/life-cycles/performance-report.d.ts +62 -0
- package/dist/src/tasks-runner/life-cycles/performance-report.js +318 -0
- package/dist/src/tasks-runner/run-command.d.ts +1 -1
- package/dist/src/tasks-runner/run-command.js +12 -2
- package/dist/src/utils/min-release-age/behavior/pnpm.d.ts +6 -5
- package/dist/src/utils/min-release-age/behavior/pnpm.js +87 -339
- package/dist/src/utils/package-manager.d.ts +6 -3
- package/dist/src/utils/package-manager.js +11 -7
- package/dist/src/utils/terminal-link.d.ts +10 -6
- package/dist/src/utils/terminal-link.js +9 -11
- package/package.json +11 -11
|
@@ -192,16 +192,37 @@ class Migrator {
|
|
|
192
192
|
!this.areIncompatiblePackagesPresent(packageUpdate.incompatibleWith) &&
|
|
193
193
|
(!this.interactive ||
|
|
194
194
|
(await this.runPackageJsonUpdatesConfirmationPrompt(packageUpdate, packageUpdateKey, packageToCheck.package)))) {
|
|
195
|
-
Object.entries(packageUpdate.packages)
|
|
195
|
+
const updateEntries = Object.entries(packageUpdate.packages);
|
|
196
|
+
// Validate all up front so invalid metadata fails fast, before any
|
|
197
|
+
// resolution does I/O.
|
|
198
|
+
for (const [name, update] of updateEntries) {
|
|
196
199
|
this.validatePackageUpdateVersion(packageToCheck.package, name, update);
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
+
}
|
|
201
|
+
// Resolve serially: resolution can prompt (pnpm strict cooldown) and
|
|
202
|
+
// append to minimumReleaseAgeExclude, so a serial loop avoids
|
|
203
|
+
// overlapping prompts and keeps packageUpdates ordering stable.
|
|
204
|
+
for (const [name, update] of updateEntries) {
|
|
205
|
+
const resolvedUpdate = {
|
|
206
|
+
...update,
|
|
207
|
+
version: await this.resolveVersionForCascade(name, update.version),
|
|
208
|
+
};
|
|
209
|
+
filteredUpdates[name] = resolvedUpdate;
|
|
210
|
+
this.packageUpdates[name] = resolvedUpdate;
|
|
211
|
+
}
|
|
200
212
|
}
|
|
201
213
|
}
|
|
202
214
|
await Promise.all(Object.entries(filteredUpdates).map(([name, update]) => this.buildPackageJsonUpdates(name, update)));
|
|
203
215
|
}
|
|
204
216
|
}
|
|
217
|
+
async resolveVersionForCascade(packageName, version) {
|
|
218
|
+
// Already a fully-qualified semver (incl. prereleases) - nothing to resolve.
|
|
219
|
+
if ((0, semver_1.valid)(version)) {
|
|
220
|
+
return version;
|
|
221
|
+
}
|
|
222
|
+
// Otherwise resolve the spec (range/tag) through the min-release-age policy,
|
|
223
|
+
// which also honors any configured minimumReleaseAgeExclude entries.
|
|
224
|
+
return (0, resolve_package_version_1.resolvePackageVersionRespectingMinReleaseAge)(packageName, version);
|
|
225
|
+
}
|
|
205
226
|
async populatePackageJsonUpdatesAndGetPackagesToCheck(targetPackage, target) {
|
|
206
227
|
let targetVersion = target.version;
|
|
207
228
|
if (this.to[targetPackage]) {
|
|
@@ -178,9 +178,26 @@ async function getCachedSerializedProjectGraphPromise(socket) {
|
|
|
178
178
|
}
|
|
179
179
|
function scheduleProjectGraphRecomputation(createdFiles, updatedFiles, deletedFiles) {
|
|
180
180
|
++fileChangeCounter;
|
|
181
|
-
|
|
181
|
+
// Hash the changed files up front and drop no-op rewrites before they can
|
|
182
|
+
// trigger an expensive recompute. Restoring a cached task output, a
|
|
183
|
+
// `git checkout` back to the same content, or a formatter that changes
|
|
184
|
+
// nothing all rewrite a file (new inode) the watcher reports as changed
|
|
185
|
+
// even though the bytes are identical. updateFilesInContext updates the
|
|
186
|
+
// workspace context and returns only the files whose content actually
|
|
187
|
+
// changed. Hashing here — once per watcher batch — rather than inside the
|
|
188
|
+
// recompute keeps it off the stale-retry path, which would otherwise see
|
|
189
|
+
// "no change" after the first pass already updated the context hashes.
|
|
190
|
+
perf_hooks_1.performance.mark('hash-watched-changes-start');
|
|
191
|
+
const changedFileHashes = createdFiles.length > 0 ||
|
|
192
|
+
updatedFiles.length > 0 ||
|
|
193
|
+
deletedFiles.length > 0
|
|
194
|
+
? ((0, workspace_context_1.updateFilesInContext)(workspace_root_1.workspaceRoot, [...createdFiles, ...updatedFiles], deletedFiles) ?? {})
|
|
195
|
+
: {};
|
|
196
|
+
perf_hooks_1.performance.mark('hash-watched-changes-end');
|
|
197
|
+
perf_hooks_1.performance.measure('hash changed files from watcher', 'hash-watched-changes-start', 'hash-watched-changes-end');
|
|
198
|
+
for (const [f, hash] of Object.entries(changedFileHashes)) {
|
|
182
199
|
collectedDeletedFiles.delete(f);
|
|
183
|
-
collectedUpdatedFiles.set(f, fileChangeCounter);
|
|
200
|
+
collectedUpdatedFiles.set(f, { version: fileChangeCounter, hash });
|
|
184
201
|
}
|
|
185
202
|
for (let f of deletedFiles) {
|
|
186
203
|
collectedUpdatedFiles.delete(f);
|
|
@@ -188,9 +205,7 @@ function scheduleProjectGraphRecomputation(createdFiles, updatedFiles, deletedFi
|
|
|
188
205
|
}
|
|
189
206
|
// The native watcher already coalesces a burst of events into one batch,
|
|
190
207
|
// so socket + listener notifications dispatch immediately.
|
|
191
|
-
if (
|
|
192
|
-
updatedFiles.length > 0 ||
|
|
193
|
-
deletedFiles.length > 0) {
|
|
208
|
+
if (Object.keys(changedFileHashes).length > 0 || deletedFiles.length > 0) {
|
|
194
209
|
(0, file_change_events_1.notifyFileChangeListeners)({ createdFiles, updatedFiles, deletedFiles });
|
|
195
210
|
(0, file_watcher_sockets_1.notifyFileWatcherSockets)(createdFiles, updatedFiles, deletedFiles);
|
|
196
211
|
// Bump generation synchronously so any in-flight compute fails its
|
|
@@ -284,14 +299,18 @@ async function processFilesAndCreateAndSerializeProjectGraph(separatedPlugins) {
|
|
|
284
299
|
return cachedSerializedProjectGraphPromise;
|
|
285
300
|
};
|
|
286
301
|
try {
|
|
287
|
-
perf_hooks_1.performance.mark('hash-watched-changes-start');
|
|
288
302
|
const updatedFilesSnapshot = new Map(collectedUpdatedFiles);
|
|
289
303
|
const deletedFilesSnapshot = new Map(collectedDeletedFiles);
|
|
290
304
|
const updatedFiles = [...updatedFilesSnapshot.keys()];
|
|
291
305
|
const deletedFiles = [...deletedFilesSnapshot.keys()];
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
306
|
+
// Hashes were already computed (and the workspace context updated) in
|
|
307
|
+
// scheduleProjectGraphRecomputation, which also dropped no-op rewrites.
|
|
308
|
+
// Reuse them so the context isn't re-hashed on every (possibly stale)
|
|
309
|
+
// recompute attempt.
|
|
310
|
+
const updatedFileHashes = {};
|
|
311
|
+
for (const [f, { hash }] of updatedFilesSnapshot) {
|
|
312
|
+
updatedFileHashes[f] = hash;
|
|
313
|
+
}
|
|
295
314
|
logger_1.serverLogger.requestLog(`Updated workspace context based on watched changes, recomputing project graph...`);
|
|
296
315
|
logger_1.serverLogger.requestLog(updatedFiles);
|
|
297
316
|
logger_1.serverLogger.requestLog(deletedFiles);
|
|
@@ -333,8 +352,8 @@ async function processFilesAndCreateAndSerializeProjectGraph(separatedPlugins) {
|
|
|
333
352
|
// from the daemon's view (project graph misses recently added files).
|
|
334
353
|
// Match version-stamps so a file modified mid-flight (higher version)
|
|
335
354
|
// stays in the queue for reprocessing.
|
|
336
|
-
for (const [f, version] of updatedFilesSnapshot) {
|
|
337
|
-
if (collectedUpdatedFiles.get(f) === version) {
|
|
355
|
+
for (const [f, { version }] of updatedFilesSnapshot) {
|
|
356
|
+
if (collectedUpdatedFiles.get(f)?.version === version) {
|
|
338
357
|
collectedUpdatedFiles.delete(f);
|
|
339
358
|
}
|
|
340
359
|
}
|
|
@@ -34,7 +34,7 @@ export declare class AppLifeCycle {
|
|
|
34
34
|
startTasks(tasks: Array<Task>, metadata: object): void
|
|
35
35
|
printTaskTerminalOutput(task: Task, status: string, output: string): void
|
|
36
36
|
endTasks(taskResults: Array<TaskResult>, metadata: object): void
|
|
37
|
-
endCommand(): void
|
|
37
|
+
endCommand(summary?: PerformanceSummaryPayload | undefined | null): void
|
|
38
38
|
__init(doneCallback: (() => unknown)): void
|
|
39
39
|
registerRunningTask(taskId: string, parserAndWriter: ExternalObject<[ParserArc, WriterArc]>): void
|
|
40
40
|
registerRunningTaskWithEmptyParser(taskId: string): void
|
|
@@ -275,6 +275,15 @@ export interface CachedResult {
|
|
|
275
275
|
size?: number
|
|
276
276
|
}
|
|
277
277
|
|
|
278
|
+
/**
|
|
279
|
+
* Cache hits vs total; present only when there was a cache outcome. A bypassed
|
|
280
|
+
* cache is signalled separately by `cache_skipped`.
|
|
281
|
+
*/
|
|
282
|
+
export interface CacheStat {
|
|
283
|
+
hits: number
|
|
284
|
+
total: number
|
|
285
|
+
}
|
|
286
|
+
|
|
278
287
|
export declare function canInstallNxConsole(): Promise<boolean>
|
|
279
288
|
|
|
280
289
|
export declare function canInstallNxConsoleForEditor(editor: SupportedEditor): Promise<boolean>
|
|
@@ -375,6 +384,13 @@ export declare function findImports(projectFileMap: Record<string, Array<string>
|
|
|
375
384
|
*/
|
|
376
385
|
export declare function flushTelemetry(): void
|
|
377
386
|
|
|
387
|
+
/**
|
|
388
|
+
* The single duration formatter — used by the task list, terminal report, and TUI
|
|
389
|
+
* popup. Exposed to JS as `formatDuration` so all three share one implementation.
|
|
390
|
+
* 0 (or sub-millisecond) → "<1ms", then "470ms", "13.4s", "1m 30s".
|
|
391
|
+
*/
|
|
392
|
+
export declare function formatDuration(ms: number): string
|
|
393
|
+
|
|
378
394
|
export declare function getBinaryTarget(): string
|
|
379
395
|
|
|
380
396
|
export declare function getDefaultMaxCacheSize(cachePath: string): number
|
|
@@ -524,6 +540,15 @@ export declare function killProcessTree(rootPid: number, signal?: string | numbe
|
|
|
524
540
|
*/
|
|
525
541
|
export declare function killProcessTreeGraceful(rootPid: number, signal?: string | number | undefined | null, gracePeriodMs?: number | undefined | null): Promise<void>
|
|
526
542
|
|
|
543
|
+
/**
|
|
544
|
+
* A docs link rendered as an OSC 8 hyperlink. Both fields come from TS so the
|
|
545
|
+
* popup never hardcodes a URL.
|
|
546
|
+
*/
|
|
547
|
+
export interface Link {
|
|
548
|
+
text: string
|
|
549
|
+
href: string
|
|
550
|
+
}
|
|
551
|
+
|
|
527
552
|
export declare function logDebug(message: string): void
|
|
528
553
|
|
|
529
554
|
/** Combined metadata for groups and processes */
|
|
@@ -564,6 +589,28 @@ export interface NxWorkspaceFilesExternals {
|
|
|
564
589
|
|
|
565
590
|
export declare function parseTaskStatus(stringStatus: string): TaskStatus
|
|
566
591
|
|
|
592
|
+
/**
|
|
593
|
+
* Structured run report shown in the exit-countdown popup. The TUI builds the
|
|
594
|
+
* visual from these numbers rather than receiving a pre-formatted string.
|
|
595
|
+
*/
|
|
596
|
+
export interface PerformanceSummaryPayload {
|
|
597
|
+
runDurationMs: number
|
|
598
|
+
criticalPathMs: number
|
|
599
|
+
criticalPathTaskCount: number
|
|
600
|
+
recoverableMs: number
|
|
601
|
+
cache?: CacheStat
|
|
602
|
+
cacheSkipped: boolean
|
|
603
|
+
/** Already in display order; a multi-line entry embeds a task list. */
|
|
604
|
+
recommendations: Array<string>
|
|
605
|
+
/** The docs footer link, rendered as a bullet and hyperlinked. */
|
|
606
|
+
footer: Link
|
|
607
|
+
/**
|
|
608
|
+
* Phrases already in `recommendations` to hyperlink in place (e.g. the
|
|
609
|
+
* remote-cache CTA); empty when none apply.
|
|
610
|
+
*/
|
|
611
|
+
links: Array<Link>
|
|
612
|
+
}
|
|
613
|
+
|
|
567
614
|
/** Process metadata (static, doesn't change during process lifetime) */
|
|
568
615
|
export interface ProcessMetadata {
|
|
569
616
|
ppid: number
|
|
@@ -605,6 +605,7 @@ module.exports.EventType = nativeBinding.EventType
|
|
|
605
605
|
module.exports.expandOutputs = nativeBinding.expandOutputs
|
|
606
606
|
module.exports.findImports = nativeBinding.findImports
|
|
607
607
|
module.exports.flushTelemetry = nativeBinding.flushTelemetry
|
|
608
|
+
module.exports.formatDuration = nativeBinding.formatDuration
|
|
608
609
|
module.exports.getBinaryTarget = nativeBinding.getBinaryTarget
|
|
609
610
|
module.exports.getDefaultMaxCacheSize = nativeBinding.getDefaultMaxCacheSize
|
|
610
611
|
module.exports.getEventDimensions = nativeBinding.getEventDimensions
|
|
Binary file
|
|
Binary file
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.defaultTasksRunner = exports.RemoteCacheV2 = void 0;
|
|
4
4
|
const task_orchestrator_1 = require("./task-orchestrator");
|
|
5
|
+
const performance_life_cycle_1 = require("./life-cycles/performance-life-cycle");
|
|
5
6
|
const cache_directory_1 = require("../utils/cache-directory");
|
|
6
7
|
const promises_1 = require("fs/promises");
|
|
7
8
|
const path_1 = require("path");
|
|
@@ -54,13 +55,15 @@ class RemoteCacheV2 {
|
|
|
54
55
|
}
|
|
55
56
|
exports.RemoteCacheV2 = RemoteCacheV2;
|
|
56
57
|
const defaultTasksRunner = async (tasks, options, context) => {
|
|
57
|
-
const { total: threadCount } = (0, task_orchestrator_1.getThreadPoolSize)(options, context.taskGraph);
|
|
58
|
-
|
|
58
|
+
const { total: threadCount, discrete } = (0, task_orchestrator_1.getThreadPoolSize)(options, context.taskGraph);
|
|
59
|
+
// Total thread count drives the TUI; the discrete `--parallel` feeds the perf report.
|
|
60
|
+
await options.lifeCycle.startCommand(threadCount, discrete);
|
|
59
61
|
try {
|
|
60
62
|
return await runAllTasks(options, context);
|
|
61
63
|
}
|
|
62
64
|
finally {
|
|
63
|
-
|
|
65
|
+
// Hand the TUI exit popup the perf report (multi-task TUI runs); other modes flush it.
|
|
66
|
+
await options.lifeCycle.endCommand((0, performance_life_cycle_1.getPerformanceReport)(tasks.length));
|
|
64
67
|
}
|
|
65
68
|
};
|
|
66
69
|
exports.defaultTasksRunner = defaultTasksRunner;
|
|
@@ -15,7 +15,7 @@ async function createOrchestrator(tasks, projectGraph, fullTaskGraph, nxJson, li
|
|
|
15
15
|
const invokeRunnerTerminalLifecycle = new invoke_runner_terminal_output_life_cycle_1.InvokeRunnerTerminalOutputLifeCycle(tasks);
|
|
16
16
|
const taskResultsLifecycle = new task_results_life_cycle_1.TaskResultsLifeCycle();
|
|
17
17
|
const compositedLifeCycle = new life_cycle_1.CompositeLifeCycle([
|
|
18
|
-
...(0, run_command_1.constructLifeCycles)(invokeRunnerTerminalLifecycle),
|
|
18
|
+
...(0, run_command_1.constructLifeCycles)(invokeRunnerTerminalLifecycle, fullTaskGraph, nxJson),
|
|
19
19
|
taskResultsLifecycle,
|
|
20
20
|
lifeCycle,
|
|
21
21
|
]);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Task } from '../config/task-graph';
|
|
2
|
-
import { BatchInfo, BatchStatus, ExternalObject, TaskResult, TaskStatus as NativeTaskStatus } from '../native';
|
|
2
|
+
import { BatchInfo, BatchStatus, ExternalObject, PerformanceSummaryPayload, TaskResult, TaskStatus as NativeTaskStatus } from '../native';
|
|
3
3
|
import { TaskStatus } from './tasks-runner';
|
|
4
4
|
/**
|
|
5
5
|
* The result of a completed {@link Task}.
|
|
@@ -21,8 +21,13 @@ export interface TaskMetadata {
|
|
|
21
21
|
groupId: number;
|
|
22
22
|
}
|
|
23
23
|
export interface LifeCycle {
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
/**
|
|
25
|
+
* @param threadCount total thread-pool size (drives the TUI display)
|
|
26
|
+
* @param parallel resolved `--parallel` (discrete slots), for the performance report
|
|
27
|
+
*/
|
|
28
|
+
startCommand?(threadCount?: number, parallel?: number): void | Promise<void>;
|
|
29
|
+
/** @param summary performance report payload for the TUI exit popup (TUI runs only) */
|
|
30
|
+
endCommand?(summary?: PerformanceSummaryPayload): void | Promise<void>;
|
|
26
31
|
scheduleTask?(task: Task): void | Promise<void>;
|
|
27
32
|
/**
|
|
28
33
|
* @deprecated use startTasks
|
|
@@ -53,8 +58,8 @@ export interface LifeCycle {
|
|
|
53
58
|
export declare class CompositeLifeCycle implements LifeCycle {
|
|
54
59
|
private readonly lifeCycles;
|
|
55
60
|
constructor(lifeCycles: LifeCycle[]);
|
|
56
|
-
startCommand(parallel?: number): Promise<void>;
|
|
57
|
-
endCommand(): Promise<void>;
|
|
61
|
+
startCommand(threadCount?: number, parallel?: number): Promise<void>;
|
|
62
|
+
endCommand(summary?: PerformanceSummaryPayload): Promise<void>;
|
|
58
63
|
scheduleTask(task: Task): Promise<void>;
|
|
59
64
|
startTask(task: Task): void;
|
|
60
65
|
endTask(task: Task, code: number): void;
|
|
@@ -5,17 +5,17 @@ class CompositeLifeCycle {
|
|
|
5
5
|
constructor(lifeCycles) {
|
|
6
6
|
this.lifeCycles = lifeCycles;
|
|
7
7
|
}
|
|
8
|
-
async startCommand(parallel) {
|
|
8
|
+
async startCommand(threadCount, parallel) {
|
|
9
9
|
for (let l of this.lifeCycles) {
|
|
10
10
|
if (l.startCommand) {
|
|
11
|
-
await l.startCommand(parallel);
|
|
11
|
+
await l.startCommand(threadCount, parallel);
|
|
12
12
|
}
|
|
13
13
|
}
|
|
14
14
|
}
|
|
15
|
-
async endCommand() {
|
|
15
|
+
async endCommand(summary) {
|
|
16
16
|
for (let l of this.lifeCycles) {
|
|
17
17
|
if (l.endCommand) {
|
|
18
|
-
await l.endCommand();
|
|
18
|
+
await l.endCommand(summary);
|
|
19
19
|
}
|
|
20
20
|
}
|
|
21
21
|
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { TaskGraph } from '../../config/task-graph';
|
|
2
|
+
import { NxJsonConfiguration } from '../../config/nx-json';
|
|
3
|
+
import { type Recommendation } from './performance-report';
|
|
4
|
+
import { TaskResult } from '../life-cycle';
|
|
5
|
+
export interface TaskTiming {
|
|
6
|
+
startTime?: number;
|
|
7
|
+
endTime?: number;
|
|
8
|
+
continuous: boolean;
|
|
9
|
+
}
|
|
10
|
+
/** A discrete task that ran, with its window and whether it monopolizes the pool (`parallelism: false`). */
|
|
11
|
+
export interface TimedTask {
|
|
12
|
+
id: string;
|
|
13
|
+
start: number;
|
|
14
|
+
end: number;
|
|
15
|
+
nonParallel: boolean;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* One [start, end) slice of the run where the counts below hold steady — the gap between
|
|
19
|
+
* two adjacent sweep boundaries (see {@link buildTimespans}). Because nothing changes
|
|
20
|
+
* within the slice, the analysis treats it as a flat rectangle of `end - start` ms.
|
|
21
|
+
*/
|
|
22
|
+
export interface Timespan {
|
|
23
|
+
/** Slice bounds (epoch ms); occ/waiting/nonParallel are constant across [start, end). */
|
|
24
|
+
start: number;
|
|
25
|
+
end: number;
|
|
26
|
+
/** Slots busy for the whole slice (a `parallelism: false` task counts as the entire pool). */
|
|
27
|
+
occ: number;
|
|
28
|
+
/** Tasks eligible to run but still queued for a free slot. */
|
|
29
|
+
waiting: number;
|
|
30
|
+
/** `parallelism: false` tasks running during the slice (each one holds every slot). */
|
|
31
|
+
nonParallel: number;
|
|
32
|
+
}
|
|
33
|
+
export interface PerformanceSummary {
|
|
34
|
+
runDuration: number;
|
|
35
|
+
criticalPathDuration: number;
|
|
36
|
+
criticalPathTaskCount: number;
|
|
37
|
+
/** Longest critical-path tasks that ran (desc, capped at a few), cache hits excluded; empty when the path was fully cached. */
|
|
38
|
+
criticalPathTop: Array<{
|
|
39
|
+
id: string;
|
|
40
|
+
duration: number;
|
|
41
|
+
}>;
|
|
42
|
+
/** runDuration − criticalPathDuration. */
|
|
43
|
+
overhead: number;
|
|
44
|
+
/** Overhead split by lever; these + coordinatorOverhead sum to `overhead`. Their sum is the slot-contention time (derived, never stored, so the halves can't drift). */
|
|
45
|
+
recoverableByParallel: number;
|
|
46
|
+
recoverableByMachines: number;
|
|
47
|
+
coordinatorOverhead: number;
|
|
48
|
+
parallel: number;
|
|
49
|
+
cores: number;
|
|
50
|
+
isCI: boolean;
|
|
51
|
+
/** One structured rec per lever (with link parts); drives the report + payload renderers. */
|
|
52
|
+
structuredRecommendations: Recommendation[];
|
|
53
|
+
/** Coordinator overhead outweighs task work, so the longest tasks aren't the lever (typical cached run). */
|
|
54
|
+
coordinatorDominated: boolean;
|
|
55
|
+
cacheHits: number;
|
|
56
|
+
/** Tasks with any cache outcome (hits + tasks that ran). */
|
|
57
|
+
cacheableCount: number;
|
|
58
|
+
cacheSkipped: boolean;
|
|
59
|
+
remoteCacheEnabled: boolean;
|
|
60
|
+
}
|
|
61
|
+
/** Construction-time inputs for {@link PerformanceLifeCycle}. */
|
|
62
|
+
export interface PerformanceLifeCycleOptions {
|
|
63
|
+
/** Whether `--skip-nx-cache` was passed (feeds the cache-skipped check). */
|
|
64
|
+
skipNxCache?: boolean;
|
|
65
|
+
/** Passed in at construction so the lifecycle doesn't re-read nx.json. */
|
|
66
|
+
nxJson?: NxJsonConfiguration;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Pure analysis over one finished run's collected timings — no lifecycle state and no
|
|
70
|
+
* mutation. Built once per run from the {@link PerformanceLifeCycle}'s accumulated
|
|
71
|
+
* maps; everything here is a function of that snapshot plus the environment it reads.
|
|
72
|
+
*/
|
|
73
|
+
export declare class PerformanceAnalysis {
|
|
74
|
+
private readonly timings;
|
|
75
|
+
private readonly statuses;
|
|
76
|
+
private readonly taskGraph;
|
|
77
|
+
private readonly batchSiblings;
|
|
78
|
+
/** Resolved `--parallel` (getThreadPoolSize's `discrete`), set on the lifecycle by the runner; falls back to 1. */
|
|
79
|
+
private readonly parallel;
|
|
80
|
+
private readonly options;
|
|
81
|
+
constructor(timings: Map<string, TaskTiming>, statuses: Map<string, TaskResult['status']>, taskGraph: TaskGraph, batchSiblings: Map<string, string[]>,
|
|
82
|
+
/** Resolved `--parallel` (getThreadPoolSize's `discrete`), set on the lifecycle by the runner; falls back to 1. */
|
|
83
|
+
parallel: number, options: PerformanceLifeCycleOptions);
|
|
84
|
+
/**
|
|
85
|
+
* Whether a remote (Nx Cloud) cache is active, from the nx.json handed in at
|
|
86
|
+
* construction. Assumes no remote cache when it wasn't provided rather than
|
|
87
|
+
* re-reading nx.json from disk — the CLI path always provides it.
|
|
88
|
+
*/
|
|
89
|
+
private remoteCacheEnabled;
|
|
90
|
+
/** Discrete tasks that ran and have a start + end timestamp. */
|
|
91
|
+
private timedTasks;
|
|
92
|
+
/**
|
|
93
|
+
* Longest-duration chain through the dependency DAG — the floor with unlimited
|
|
94
|
+
* slots. `finishEstimate(t) = duration[t] + max(finishEstimate(p) for predecessors
|
|
95
|
+
* p)`. Predecessors are real dependencies *plus* earlier batch siblings (a batch runs
|
|
96
|
+
* sequentially in one process, so that ordering is part of the floor too). On equal
|
|
97
|
+
* finish estimates keep the longer-running predecessor.
|
|
98
|
+
*/
|
|
99
|
+
private criticalPath;
|
|
100
|
+
/**
|
|
101
|
+
* Real predecessors of `id`: dependencies plus any earlier batch sibling that FINISHED
|
|
102
|
+
* before `id` started. A batch runs sequentially in one process, so that ordering is
|
|
103
|
+
* part of the floor; concurrent batch executors aren't, and chaining them would inflate
|
|
104
|
+
* it. (Matches readyTime.)
|
|
105
|
+
*/
|
|
106
|
+
private predecessorsFor;
|
|
107
|
+
/**
|
|
108
|
+
* Longest finish time of any chain reaching `id` — its duration plus the best
|
|
109
|
+
* predecessor's finish estimate — memoized in `ctx`. `ctx.visiting` guards a cycle in
|
|
110
|
+
* a malformed graph (the back-edge contributes nothing).
|
|
111
|
+
*/
|
|
112
|
+
private finishEstimate;
|
|
113
|
+
/**
|
|
114
|
+
* Earliest this task became eligible, independent of slots: the latest of run
|
|
115
|
+
* start, dependency ends, any continuous dependency's *start* (an ordering
|
|
116
|
+
* constraint, not contention), and any earlier batch sibling's end.
|
|
117
|
+
*/
|
|
118
|
+
private readyTime;
|
|
119
|
+
/**
|
|
120
|
+
* Longest critical-path tasks that RAN (desc, capped at 3). Cache hits are excluded
|
|
121
|
+
* (their duration is just restore time); no-status tasks (synthetic test runs) are
|
|
122
|
+
* kept.
|
|
123
|
+
*/
|
|
124
|
+
private computeCriticalPathTop;
|
|
125
|
+
/** Cache outcome: tasks restored (`cacheHits`) and the total with a cache outcome (`cacheableCount` = hits + ran). No-status tasks count for neither. */
|
|
126
|
+
private computeCacheStats;
|
|
127
|
+
/** The structured performance summary, or `null` when no discrete task timings were recorded. */
|
|
128
|
+
summary(): PerformanceSummary | null;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Union a set of `[start, end]` intervals into an ordered, non-overlapping set, e.g.
|
|
132
|
+
* `[[1, 3], [2, 5]]` → `[[1, 5]]`. Sort by start, then sweep once: each interval
|
|
133
|
+
* either extends the previous merged one (if it overlaps or touches it) or begins a
|
|
134
|
+
* new one.
|
|
135
|
+
*/
|
|
136
|
+
export declare function mergeIntervals(intervals: Array<[number, number]>): Array<[number, number]>;
|
|
137
|
+
/** Total time [a, b] overlaps the given (unsorted, possibly overlapping) intervals. */
|
|
138
|
+
export declare function overlap(a: number, b: number, intervals: Array<[number, number]>): number;
|
|
139
|
+
/**
|
|
140
|
+
* Coordinator hashing wall-clock that ran *before* the first task started — the run
|
|
141
|
+
* window (first task start → last task end) would otherwise miss it. Matters on a
|
|
142
|
+
* cached run where hashing dominates but the tasks restore in milliseconds.
|
|
143
|
+
*
|
|
144
|
+
* `hashWindows` are absolute `[start, end]` spans from nx's `hash*` perf measures.
|
|
145
|
+
*/
|
|
146
|
+
export declare function preDispatchHashTime(firstTaskStart: number, hashWindows: Array<[number, number]>): number;
|
|
147
|
+
/**
|
|
148
|
+
* The occupancy timeline (contiguous {@link Timespan}s) — single source of truth for
|
|
149
|
+
* slot contention. A `parallelism: false` task occupies the whole pool. Integrates
|
|
150
|
+
* over time rather than sampling one instant, so it's robust to when tasks hand off.
|
|
151
|
+
*
|
|
152
|
+
* Builds it via a delta sweep: each task bumps a counter up at its start and down at
|
|
153
|
+
* its end, then we walk the sorted boundaries accumulating the running totals into one
|
|
154
|
+
* timespan per gap.
|
|
155
|
+
*/
|
|
156
|
+
export declare function buildTimespans(timed: TimedTask[], eligible: Map<string, number>, parallel: number): Timespan[];
|