fix(team): repair runtime snapshot caching and rss sampling regressions

Pre-existing regressions из 'cache transcript and telemetry scans':

- pidsMissingUsageStats теперь всегда фильтрует runtimeUsagePids по
  отсутствию stats, а не только когда runtimeProcessRowsForSnapshot
  null. Пустой/неполный results-row больше не блокирует pidusage
  fallback.
- Кэш runtimeProcessTableCache очищается при invalidateRuntime-
  SnapshotCaches и не репопулируется in-flight'ом, который начат до
  invalidate (через generation token + caller-aware cache context).
- runtimeProcessUsageStatsCacheByPid очищается в invalidate, а в
  shared OpenCode host refresh branch'е сбрасывается negative-cache
  для конкретного pid'а перед таргетным sample'ом.

Зеленит 25 теста в TeamProvisioningService > getTeamAgentRuntimeSnapshot,
включая 'uses batched pidusage rss values', 'captures CPU and memory
history', 'does not cache live runtime metadata when invalidated while
the probe is in flight', 'shows RSS for OpenCode secondary lane host
pids'.
This commit is contained in:
777genius 2026-05-29 00:51:03 +03:00
parent 9e3efa18ce
commit cb35992cff

View file

@ -3463,6 +3463,7 @@ export class TeamProvisioningService {
private runtimeProcessTableInFlight: private runtimeProcessTableInFlight:
| Promise<RuntimeTelemetryProcessTableRow[] | null> | Promise<RuntimeTelemetryProcessTableRow[] | null>
| undefined; | undefined;
private runtimeProcessTableCacheGeneration = 0;
private readonly runtimeProcessUsageStatsCacheByPid = new Map< private readonly runtimeProcessUsageStatsCacheByPid = new Map<
number, number,
{ {
@ -3781,6 +3782,9 @@ export class TeamProvisioningService {
this.liveTeamAgentRuntimeMetadataCache.delete(teamName); this.liveTeamAgentRuntimeMetadataCache.delete(teamName);
this.liveTeamAgentRuntimeMetadataInFlightByTeam.delete(teamName); this.liveTeamAgentRuntimeMetadataInFlightByTeam.delete(teamName);
this.runtimeProcessRowsForUsageSnapshotByTeam.delete(teamName); this.runtimeProcessRowsForUsageSnapshotByTeam.delete(teamName);
this.runtimeProcessUsageStatsCacheByPid.clear();
this.runtimeProcessTableCache = undefined;
this.runtimeProcessTableCacheGeneration += 1;
} }
private normalizeMemberKeyForTaskActivity(memberName: string): string { private normalizeMemberKeyForTaskActivity(memberName: string): string {
@ -14307,10 +14311,7 @@ export class TeamProvisioningService {
runtimeProcessRowsForSnapshot, runtimeProcessRowsForSnapshot,
runtimeUsagePids runtimeUsagePids
); );
const pidsMissingUsageStats = const pidsMissingUsageStats = runtimeUsagePids.filter((pid) => !usageStatsByPid.has(pid));
runtimeProcessRowsForSnapshot == null
? runtimeUsagePids.filter((pid) => !usageStatsByPid.has(pid))
: [];
if (pidsMissingUsageStats.length > 0) { if (pidsMissingUsageStats.length > 0) {
const sampledUsageStats = await this.readProcessUsageStatsByPid(pidsMissingUsageStats); const sampledUsageStats = await this.readProcessUsageStatsByPid(pidsMissingUsageStats);
for (const [pid, stats] of sampledUsageStats) { for (const [pid, stats] of sampledUsageStats) {
@ -14641,11 +14642,11 @@ export class TeamProvisioningService {
rssPid && rssPid &&
!usageStatsByPid.has(rssPid) && !usageStatsByPid.has(rssPid) &&
isSharedOpenCodeHost && isSharedOpenCodeHost &&
runtimeProcessRowsForSnapshot == null &&
typeof rssPid === 'number' && typeof rssPid === 'number' &&
rssPid > 0 rssPid > 0
) { ) {
try { try {
this.runtimeProcessUsageStatsCacheByPid.delete(rssPid);
const refreshedUsageStats = (await this.readProcessUsageStatsByPid([rssPid])).get(rssPid); const refreshedUsageStats = (await this.readProcessUsageStatsByPid([rssPid])).get(rssPid);
if (refreshedUsageStats) { if (refreshedUsageStats) {
usageStatsByPid.set(rssPid, refreshedUsageStats); usageStatsByPid.set(rssPid, refreshedUsageStats);
@ -25795,7 +25796,8 @@ export class TeamProvisioningService {
} }
const currentProcessRows = await this.readCurrentRuntimeProcessTableRows( const currentProcessRows = await this.readCurrentRuntimeProcessTableRows(
'process table runtime snapshot' 'process table runtime snapshot',
{ teamName, generationAtStart }
); );
const processRows = currentProcessRows ?? []; const processRows = currentProcessRows ?? [];
const processTableAvailable = currentProcessRows !== null; const processTableAvailable = currentProcessRows !== null;
@ -26610,7 +26612,8 @@ export class TeamProvisioningService {
} }
private async readCurrentRuntimeProcessTableRows( private async readCurrentRuntimeProcessTableRows(
label: string label: string,
cacheContext?: { teamName: string; generationAtStart: number }
): Promise<RuntimeTelemetryProcessTableRow[] | null> { ): Promise<RuntimeTelemetryProcessTableRow[] | null> {
const cached = this.runtimeProcessTableCache; const cached = this.runtimeProcessTableCache;
if (cached && cached.expiresAtMs > Date.now()) { if (cached && cached.expiresAtMs > Date.now()) {
@ -26621,6 +26624,7 @@ export class TeamProvisioningService {
return this.runtimeProcessTableInFlight; return this.runtimeProcessTableInFlight;
} }
const generationAtStart = this.runtimeProcessTableCacheGeneration;
const request = this.withRuntimeTelemetryTimeout( const request = this.withRuntimeTelemetryTimeout(
listRuntimeProcessTableForCurrentPlatform(), listRuntimeProcessTableForCurrentPlatform(),
TeamProvisioningService.RUNTIME_PROCESS_TABLE_TIMEOUT_MS, TeamProvisioningService.RUNTIME_PROCESS_TABLE_TIMEOUT_MS,
@ -26642,10 +26646,19 @@ export class TeamProvisioningService {
return null; return null;
}) })
.then((rows) => { .then((rows) => {
this.runtimeProcessTableCache = { const callerGenerationStillValid =
expiresAtMs: Date.now() + TeamProvisioningService.RUNTIME_PROCESS_TABLE_CACHE_TTL_MS, cacheContext === undefined ||
rows, this.getRuntimeSnapshotCacheGeneration(cacheContext.teamName) ===
}; cacheContext.generationAtStart;
if (
this.runtimeProcessTableCacheGeneration === generationAtStart &&
callerGenerationStillValid
) {
this.runtimeProcessTableCache = {
expiresAtMs: Date.now() + TeamProvisioningService.RUNTIME_PROCESS_TABLE_CACHE_TTL_MS,
rows,
};
}
return rows; return rows;
}) })
.finally(() => { .finally(() => {