Skip to content

Commit 14318a5

Browse files
committed
Address performance and migration review feedback
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 48b21772-7262-40a9-9524-c2b21582d201
1 parent 7544c22 commit 14318a5

9 files changed

Lines changed: 227 additions & 74 deletions

File tree

common/reviews/api/rush-reporter.api.md

Lines changed: 82 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ export function computeWallTimeRegressionPercent(baselineMs: number, candidateMs
7474
export const COPILOT_CLI_ENV_VAR: 'COPILOT_CLI';
7575

7676
// @beta
77-
export function createBeforeLogAdapter(hooks: readonly LegacyBeforeLogHook[]): (aggregate: ITelemetryAggregate) => void;
77+
export function createBeforeLogAdapter(hooks: readonly LegacyBeforeLogHook[]): (aggregate: ITelemetryAggregate) => Record<string, unknown>;
7878

7979
// @beta
8080
export function createColorizer(enabled: boolean): IColorizer;
@@ -83,7 +83,7 @@ export function createColorizer(enabled: boolean): IColorizer;
8383
export function createEngineSink(providedSink?: IReporterEventSink): IEngineSinkResolution;
8484

8585
// @beta
86-
export function createPluginApiIncompatibleDiagnostic(manifest: IRushPluginManifest, supportedApiVersion?: string): IRushDiagnostic;
86+
export function createPluginApiIncompatibleDiagnostic(manifest: IRushPluginManifest, rushVersion: string): IRushDiagnostic;
8787

8888
// @beta
8989
export function createRushDiagnostic(code: RushDiagnosticCodes, options?: ICreateRushDiagnosticOptions): IRushDiagnostic;
@@ -97,6 +97,9 @@ export function createScopedReporter(options: ICreateScopedReporterOptions): ISc
9797
// @beta
9898
export function createTelemetryReporter(subscriber: TelemetrySubscriber): IReporter;
9999

100+
// @beta
101+
export const DAEMON_ALIGNED_MAJOR_REPORTER_DEFAULTS: IReporterMajorDefaults;
102+
100103
// @beta
101104
export const DEFAULT_FLUSH_TIMEOUT_MS: number;
102105

@@ -136,6 +139,9 @@ export function detectAgent(env: Record<string, string | undefined>, configuredV
136139
// @beta
137140
export function encodeNdjsonRecord(value: unknown, options?: INdjsonOptions): string;
138141

142+
// @beta
143+
export function evaluatePluginApplyGate(manifests: readonly IRushPluginManifest[], options: IPluginApplyGateOptions): IPluginApplyDecision[];
144+
139145
// @beta
140146
export const EXIT_CODE_FAILURE: 1;
141147

@@ -164,6 +170,9 @@ export class FileReporter implements IReporter {
164170
// @beta
165171
export function filterEventsForLogLevel(logLevel: ReporterLogLevel, events: readonly IReporterEventEnvelope<unknown>[]): IReporterEventEnvelope<unknown>[];
166172

173+
// @beta
174+
export function getBlockedPlugins(decisions: readonly IPluginApplyDecision[]): IPluginApplyDecision[];
175+
167176
// @beta
168177
export function getEventMinimumLogLevel(event: IReporterEventEnvelope<unknown>): ReporterLogLevel;
169178

@@ -173,6 +182,9 @@ export function getLogLevelRank(level: ReporterLogLevel): number;
173182
// @beta
174183
export function getPrivacyClassificationRank(classification: ReporterPrivacyClassification): number;
175184

185+
// @beta
186+
export function getReporterMigrationPhase(id: ReporterMigrationPhaseId): IReporterMigrationPhase;
187+
176188
// @beta
177189
export function getSignalExitCode(signal: NodeJS.Signals): number;
178190

@@ -276,6 +288,13 @@ export interface IAutomaticReporterPlan {
276288
readonly stdoutOwner: 'machine' | 'human';
277289
}
278290

291+
// @beta
292+
export interface IAutomaticSelectionContext {
293+
readonly emergencyLegacyFallback?: boolean;
294+
readonly experimentalSettingEnabled?: boolean;
295+
readonly explicitOptIn?: boolean;
296+
}
297+
279298
// @beta
280299
export interface IBootstrapEventBufferOptions {
281300
readonly maxBytes?: number;
@@ -622,6 +641,19 @@ export interface IPlaintextReporterOptions {
622641
readonly write: (text: string) => void;
623642
}
624643

644+
// @beta
645+
export interface IPluginApplyDecision {
646+
readonly allowed: boolean;
647+
readonly diagnostic?: IRushDiagnostic;
648+
readonly manifest: IRushPluginManifest;
649+
}
650+
651+
// @beta
652+
export interface IPluginApplyGateOptions {
653+
readonly gateEnabled?: boolean;
654+
readonly rushVersion: string;
655+
}
656+
625657
// @beta
626658
export interface IProblemMatch {
627659
readonly code?: string;
@@ -779,6 +811,18 @@ export interface IReporterHostOptions {
779811
readonly retentionMs?: number;
780812
}
781813

814+
// @beta
815+
export interface IReporterMajorDefaults {
816+
readonly automaticSelectionEnabledByDefault: boolean;
817+
readonly emergencyFallbackEnvVar: string;
818+
readonly emergencyFallbackReporterName: string;
819+
readonly gateIncompatiblePluginsBeforeApply: boolean;
820+
readonly legacyRendererRetained: boolean;
821+
readonly removedTerminalApis: readonly string[];
822+
readonly sentinelBridgeRetained: boolean;
823+
readonly verbosityAliasesRetained: boolean;
824+
}
825+
782826
// @beta
783827
export interface IReporterManagerOptions {
784828
readonly coalesceThreshold?: number;
@@ -787,6 +831,16 @@ export interface IReporterManagerOptions {
787831
readonly protocolVersion?: IReporterProtocolVersion;
788832
}
789833

834+
// @beta
835+
export interface IReporterMigrationPhase {
836+
readonly id: ReporterMigrationPhaseId;
837+
readonly independentlyReleasable: boolean;
838+
readonly ordinal: number;
839+
readonly revertible: boolean;
840+
readonly summary: string;
841+
readonly title: string;
842+
}
843+
790844
// @beta
791845
export interface IReporterOutputTarget {
792846
readonly params: {
@@ -917,8 +971,8 @@ export interface IRushFileDiagnosticSource {
917971

918972
// @beta
919973
export interface IRushPluginManifest {
920-
readonly pluginApiVersion: string;
921974
readonly pluginName: string;
975+
readonly rushVersionRange: string;
922976
}
923977

924978
// @beta
@@ -949,6 +1003,9 @@ export function isAgentVariableActive(value: string | undefined): boolean;
9491003
// @beta
9501004
export function isAlreadyReportedSentinel(error: unknown): boolean;
9511005

1006+
// @beta
1007+
export function isAutomaticSelectionEnabled(defaults: IReporterMajorDefaults, context?: IAutomaticSelectionContext): boolean;
1008+
9521009
// @beta
9531010
export function isBootstrapHandoffFileName(fileName: string): boolean;
9541011

@@ -977,6 +1034,9 @@ export interface IScopedReporter {
9771034
emitMessage(options: IScopedMessageOptions): string;
9781035
}
9791036

1037+
// @beta
1038+
export function isEmergencyLegacyFallback(env: Record<string, string | undefined>, defaults?: IReporterMajorDefaults): boolean;
1039+
9801040
// @beta
9811041
export interface ISessionCompletedPayload {
9821042
readonly durationMs?: number;
@@ -1005,9 +1065,6 @@ export function isLegacyEmergencyFallbackRequested(env: Record<string, string |
10051065
// @beta
10061066
export function isMachineReporter(reporter: ReporterName): boolean;
10071067

1008-
// @beta
1009-
export function isPluginApiVersionSupported(declaredApiVersion: string, supportedApiVersion?: string): boolean;
1010-
10111068
// @beta
10121069
export function isReporterEventRequired(type: ReporterEventType): boolean;
10131070

@@ -1017,12 +1074,18 @@ export function isReporterExtensionEventName(name: string): name is ReporterExte
10171074
// @beta
10181075
export function isReporterProtocolCompatible(consumer: IReporterProtocolVersion, producer: IReporterProtocolVersion): boolean;
10191076

1077+
// @beta
1078+
export function isRushVersionSupported(rushVersionRange: string, rushVersion: string): boolean;
1079+
10201080
// @beta
10211081
export function isSupportedLogLevel(level: string): level is ReporterLogLevel;
10221082

10231083
// @beta
10241084
export function isSupportedReporterName(name: string): name is ReporterName;
10251085

1086+
// @beta
1087+
export function isTerminalApiRemoved(api: string, defaults?: IReporterMajorDefaults): boolean;
1088+
10261089
// @beta
10271090
export function isValidRushDiagnosticCode(code: string): boolean;
10281091

@@ -1187,7 +1250,7 @@ export class OldEngineOutputAdapter {
11871250
export type OneOrMoreRushDiagnosticCodeSegments<TSegments extends string = string> = string extends TSegments ? `_${Uppercase<string>}` : TSegments extends `_${infer Segments}` ? Segments extends '' ? never : TSegments extends Uppercase<TSegments> ? TSegments : never : never;
11881251

11891252
// @beta
1190-
export type OperationStatus = 'ready' | 'executing' | 'success' | 'successWithWarnings' | 'failure' | 'blocked' | 'skipped' | 'fromCache' | 'noOp';
1253+
export type OperationStatus = 'ready' | 'waiting' | 'queued' | 'executing' | 'success' | 'successWithWarnings' | 'failure' | 'blocked' | 'skipped' | 'fromCache' | 'noOp' | 'aborted';
11911254

11921255
// @beta
11931256
export class OperationStreamEmitter {
@@ -1234,6 +1297,9 @@ export type PlaintextVariant = 'detailed' | 'concise';
12341297
// @beta
12351298
export function planAutomaticReporters(selection: IReporterSelection): IAutomaticReporterPlan;
12361299

1300+
// @beta
1301+
export const PRE_FLIP_REPORTER_DEFAULTS: IReporterMajorDefaults;
1302+
12371303
// @beta
12381304
export class ProblemMatcherRegistry {
12391305
getMatchers(tool: string, options?: IGetMatchersOptions): IProblemMatcher[];
@@ -1256,6 +1322,9 @@ export function regroupOperationOutput(events: readonly IReporterEventEnvelope<u
12561322
// @beta
12571323
export function relayHeftChildOutput(child: IHeftChildOutputStreams, targets?: IHeftChildOutputTargets): void;
12581324

1325+
// @beta
1326+
export const REMOVED_TERMINAL_APIS: readonly string[];
1327+
12591328
// @beta
12601329
export function renderActiveProjectsRow(projects: readonly string[], width: number): string;
12611330

@@ -1268,6 +1337,9 @@ export const REPORTER_EVENT_TYPES: readonly ["sessionStarted", "sessionCompleted
12681337
// @beta
12691338
export const REPORTER_KNOWN_CAPABILITIES: readonly [];
12701339

1340+
// @beta
1341+
export const REPORTER_MIGRATION_PHASES: readonly IReporterMigrationPhase[];
1342+
12711343
// @beta
12721344
export const REPORTER_PACKAGE_NAME: '@rushstack/rush-reporter';
12731345

@@ -1330,6 +1402,9 @@ export class ReporterManager implements IReporterEventSink {
13301402
// @beta
13311403
export type ReporterMessageSeverity = 'debug' | 'info' | 'warning' | 'error';
13321404

1405+
// @beta
1406+
export type ReporterMigrationPhaseId = 'contractsAndBaselines' | 'bootstrapAndCompatAdapters' | 'shadowStructuredEmission' | 'optInReporters' | 'heftProtocolTrack' | 'daemonAlignedMajorFlip' | 'laterCleanupMajor';
1407+
13331408
// @beta
13341409
export class ReporterMultiplexer implements IReporter {
13351410
constructor(name: string, reporters: readonly IReporter[]);
@@ -1449,9 +1524,6 @@ export const RUSH_INTERNAL_ERROR_CODE: 'RUSH_INTERNAL_UNEXPECTED';
14491524
// @beta
14501525
export const RUSH_LOGS_DIR_NAME: 'rush-logs';
14511526

1452-
// @beta
1453-
export const RUSH_PLUGIN_API_VERSION: '1.0.0';
1454-
14551527
// @beta
14561528
export const RUSH_REPORTER_BOOTSTRAP_HANDOFF_ENV_VAR: '_RUSH_REPORTER_BOOTSTRAP_HANDOFF';
14571529

libraries/reporter/src/lifecycle/LifecycleEmitter.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,8 +97,8 @@ export class LifecycleEmitter {
9797
public emitOperationRegistered(payload: IOperationRegisteredPayload): string {
9898
return this._emit('operationRegistered', payload, 'public', {
9999
operationId: payload.operationId,
100-
projectName: payload.projectName,
101-
phaseName: payload.phaseName
100+
...(payload.projectName === undefined ? {} : { projectName: payload.projectName }),
101+
...(payload.phaseName === undefined ? {} : { phaseName: payload.phaseName })
102102
});
103103
}
104104

libraries/reporter/src/manager/ReporterManager.ts

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,9 @@ export class ReporterManager implements IReporterEventSink {
135135
this._ownedDestinations = new Set();
136136
this._protocolVersion = protocolVersion;
137137
this._now = now;
138+
if (!Number.isSafeInteger(coalesceThreshold) || coalesceThreshold < 1) {
139+
throw new RangeError('coalesceThreshold must be a positive integer.');
140+
}
138141
this._coalesceThreshold = coalesceThreshold;
139142
this._emergencyDiagnosticWriter = emergencyDiagnosticWriter;
140143
this._nextSequence = 1;
@@ -241,10 +244,10 @@ export class ReporterManager implements IReporterEventSink {
241244
* queues.
242245
*
243246
* @remarks
244-
* This is an observability hook for verifying bounded streaming: because each
245-
* queue drains incrementally and coalesces replaceable status events, the
246-
* pending count stays bounded rather than growing to the whole-build event
247-
* total. After {@link ReporterManager.flushAsync} resolves it is `0`.
247+
* This is an observability hook for verifying bounded streaming. Each queue
248+
* coalesces replaceable status events and applies synchronous backpressure at
249+
* the configured threshold for protected events. After
250+
* {@link ReporterManager.flushAsync} resolves it is `0`.
248251
*/
249252
public getPendingEventCount(): number {
250253
let total: number = 0;
@@ -338,6 +341,14 @@ export class ReporterManager implements IReporterEventSink {
338341
// coalesced or dropped.
339342
entry.queue[lastIndex] = envelope;
340343
} else {
344+
if (entry.queue.length >= this._coalesceThreshold) {
345+
const oldestEnvelope: IReporterEventEnvelope<unknown> = entry.queue.shift()!;
346+
this._deliverEnvelope(entry, oldestEnvelope);
347+
if (entry.disabled) {
348+
entry.queue.length = 0;
349+
return;
350+
}
351+
}
341352
entry.queue.push(envelope);
342353
}
343354

@@ -351,14 +362,10 @@ export class ReporterManager implements IReporterEventSink {
351362
try {
352363
while (entry.queue.length > 0) {
353364
const envelope: IReporterEventEnvelope<unknown> = entry.queue.shift()!;
354-
try {
355-
entry.reporter.report(envelope);
356-
} catch (error) {
357-
this._handleReporterFailure(entry, error as Error);
358-
if (entry.disabled) {
359-
entry.queue.length = 0;
360-
break;
361-
}
365+
this._deliverEnvelope(entry, envelope);
366+
if (entry.disabled) {
367+
entry.queue.length = 0;
368+
break;
362369
}
363370
// Yield so producers and coalescing can interleave with delivery.
364371
await Promise.resolve();
@@ -368,6 +375,14 @@ export class ReporterManager implements IReporterEventSink {
368375
}
369376
}
370377

378+
private _deliverEnvelope(entry: IReporterEntry, envelope: IReporterEventEnvelope<unknown>): void {
379+
try {
380+
entry.reporter.report(envelope);
381+
} catch (error) {
382+
this._handleReporterFailure(entry, error as Error);
383+
}
384+
}
385+
371386
private _handleReporterFailure(entry: IReporterEntry, error: Error): void {
372387
if (entry.required) {
373388
if (!this._fatalError) {

libraries/reporter/src/migration/MigrationPhase.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,8 @@ export const REPORTER_MIGRATION_PHASES: readonly IReporterMigrationPhase[] = [
7878
id: 'contractsAndBaselines',
7979
ordinal: 1,
8080
title: 'Contracts and baselines',
81-
summary: 'Publish @rushstack/reporter, freeze legacy snapshots, add protocol and compatibility goldens.',
81+
summary:
82+
'Publish @rushstack/rush-reporter, freeze legacy snapshots, add protocol and compatibility goldens.',
8283
independentlyReleasable: true,
8384
revertible: true
8485
},

libraries/reporter/src/reporters/AiReporter.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,9 @@ import type { IReporterProtocolVersion } from '../events/ReporterProtocolVersion
55
import type { IReporterEventEnvelope } from '../events/IReporterEventEnvelope';
66
import type { IReporter } from '../manager/IReporter';
77
import type { IRushRemediationAction } from '../diagnostics/IRushRemediationAction';
8+
import { REPORTER_PERFORMANCE_BUDGETS } from '../perf/PerformanceBudgets';
89
import { REPORTER_PROTOCOL_VERSION } from '../protocol/ReporterProtocol';
910

10-
const DEFAULT_AI_MAX_BYTES: number = 64 * 1024;
11-
const DEFAULT_AI_MAX_DETAILED_DIAGNOSTICS: number = 20;
1211
const MIN_AI_MAX_BYTES: number = 512;
1312
const TERMINAL_STATUSES: ReadonlySet<string> = new Set([
1413
'success',
@@ -127,8 +126,9 @@ export class AiReporter implements IReporter {
127126

128127
public constructor(options: IAiReporterOptions) {
129128
this._write = options.write;
130-
this._maxBytes = options.maxBytes ?? DEFAULT_AI_MAX_BYTES;
131-
this._maxDetailedDiagnostics = options.maxDetailedDiagnostics ?? DEFAULT_AI_MAX_DETAILED_DIAGNOSTICS;
129+
this._maxBytes = options.maxBytes ?? REPORTER_PERFORMANCE_BUDGETS.maxAiOutputBytes;
130+
this._maxDetailedDiagnostics =
131+
options.maxDetailedDiagnostics ?? REPORTER_PERFORMANCE_BUDGETS.maxAiDetailedDiagnostics;
132132
if (!Number.isInteger(this._maxBytes) || this._maxBytes < MIN_AI_MAX_BYTES) {
133133
throw new RangeError(`maxBytes must be an integer of at least ${MIN_AI_MAX_BYTES}`);
134134
}

libraries/reporter/src/reporters/InteractiveRendering.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
22
// See LICENSE in the project root for license information.
33

4+
import { REPORTER_PERFORMANCE_BUDGETS } from '../perf/PerformanceBudgets';
5+
46
/**
57
* The spinner frames used by the interactive live region.
68
*
@@ -13,7 +15,7 @@ export const SPINNER_FRAMES: readonly string[] = ['⠋', '⠙', '⠹', '⠸', '
1315
*
1416
* @beta
1517
*/
16-
export const MIN_REFRESH_INTERVAL_MS: number = 100;
18+
export const MIN_REFRESH_INTERVAL_MS: number = 1000 / REPORTER_PERFORMANCE_BUDGETS.maxInteractiveRefreshHz;
1719

1820
/**
1921
* The snapshot of live state rendered into the three-row region.

0 commit comments

Comments
 (0)