add metrics for masking

This commit is contained in:
Abhi
2026-01-30 19:54:17 -05:00
parent f8b0ec787c
commit 29abbfaa80
6 changed files with 127 additions and 2 deletions

View File

@@ -32,6 +32,7 @@ describe('ObservationMaskingService', () => {
storage: {
getHistoryDir: () => '/mock/history',
},
getUsageStatisticsEnabled: () => false,
} as unknown as Config;
vi.clearAllMocks();
});

View File

@@ -10,6 +10,8 @@ import * as fsPromises from 'node:fs/promises';
import { estimateTokenCountSync } from '../utils/tokenCalculation.js';
import { debugLogger } from '../utils/debugLogger.js';
import type { Config } from '../config/config.js';
import { logObservationMasking } from '../telemetry/loggers.js';
import { ObservationMaskingEvent } from '../telemetry/types.js';
export const TOOL_PROTECTION_THRESHOLD = 50_000;
export const HYSTERESIS_THRESHOLD = 30_000;
@@ -176,11 +178,23 @@ export class ObservationMaskingService {
`[ObservationMasking] Masked ${prunableParts.length} tool outputs. Saved ~${actualTokensSaved.toLocaleString()} tokens.`,
);
return {
const result = {
newHistory,
maskedCount: prunableParts.length,
tokensSaved: actualTokensSaved,
};
logObservationMasking(
config,
new ObservationMaskingEvent({
tokens_before: totalPrunableTokens,
tokens_after: totalPrunableTokens - actualTokensSaved,
masked_count: prunableParts.length,
total_prunable_tokens: totalPrunableTokens,
}),
);
return result;
}
private getObservationContent(part: Part): string | null {

View File

@@ -44,6 +44,7 @@ import type {
HookCallEvent,
ApprovalModeSwitchEvent,
ApprovalModeDurationEvent,
ObservationMaskingEvent,
} from '../types.js';
import { EventMetadataKey } from './event-metadata-key.js';
import type { Config } from '../../config/config.js';
@@ -104,6 +105,7 @@ export enum EventNames {
HOOK_CALL = 'hook_call',
APPROVAL_MODE_SWITCH = 'approval_mode_switch',
APPROVAL_MODE_DURATION = 'approval_mode_duration',
OBSERVATION_MASKING = 'observation_masking',
}
export interface LogResponse {
@@ -1201,8 +1203,40 @@ export class ClearcutLogger {
},
];
const logEvent = this.createLogEvent(
EventNames.TOOL_OUTPUT_TRUNCATED,
data,
);
this.enqueueLogEvent(logEvent);
this.flushIfNeeded();
}
logObservationMaskingEvent(event: ObservationMaskingEvent): void {
const data: EventValue[] = [
{
gemini_cli_key:
EventMetadataKey.GEMINI_CLI_OBSERVATION_MASKING_TOKENS_BEFORE,
value: event.tokens_before.toString(),
},
{
gemini_cli_key:
EventMetadataKey.GEMINI_CLI_OBSERVATION_MASKING_TOKENS_AFTER,
value: event.tokens_after.toString(),
},
{
gemini_cli_key:
EventMetadataKey.GEMINI_CLI_OBSERVATION_MASKING_MASKED_COUNT,
value: event.masked_count.toString(),
},
{
gemini_cli_key:
EventMetadataKey.GEMINI_CLI_OBSERVATION_MASKING_TOTAL_PRUNABLE_TOKENS,
value: event.total_prunable_tokens.toString(),
},
];
this.enqueueLogEvent(
this.createLogEvent(EventNames.TOOL_OUTPUT_TRUNCATED, data),
this.createLogEvent(EventNames.OBSERVATION_MASKING, data),
);
this.flushIfNeeded();
}

View File

@@ -542,4 +542,20 @@ export enum EventMetadataKey {
// Logs the duration spent in an approval mode in milliseconds.
GEMINI_CLI_APPROVAL_MODE_DURATION_MS = 143,
// ==========================================================================
// Observation Masking Event Keys
// ==========================================================================
// Logs the total tokens in the prunable block before masking.
GEMINI_CLI_OBSERVATION_MASKING_TOKENS_BEFORE = 144,
// Logs the total tokens in the masked remnants after masking.
GEMINI_CLI_OBSERVATION_MASKING_TOKENS_AFTER = 145,
// Logs the number of tool outputs masked in this operation.
GEMINI_CLI_OBSERVATION_MASKING_MASKED_COUNT = 146,
// Logs the total prunable tokens identified at the trigger point.
GEMINI_CLI_OBSERVATION_MASKING_TOTAL_PRUNABLE_TOKENS = 147,
}

View File

@@ -53,6 +53,7 @@ import type {
HookCallEvent,
StartupStatsEvent,
LlmLoopCheckEvent,
ObservationMaskingEvent,
} from './types.js';
import {
recordApiErrorMetrics,
@@ -159,6 +160,21 @@ export function logToolOutputTruncated(
});
}
export function logObservationMasking(
config: Config,
event: ObservationMaskingEvent,
): void {
ClearcutLogger.getInstance(config)?.logObservationMaskingEvent(event);
bufferTelemetryEvent(() => {
const logger = logs.getLogger(SERVICE_NAME);
const logRecord: LogRecord = {
body: event.toLogBody(),
attributes: event.toOpenTelemetryAttributes(config),
};
logger.emit(logRecord);
});
}
export function logFileOperation(
config: Config,
event: FileOperationEvent,

View File

@@ -1350,6 +1350,49 @@ export class ToolOutputTruncatedEvent implements BaseTelemetryEvent {
}
}
export const EVENT_OBSERVATION_MASKING = 'gemini_cli.observation_masking';
export class ObservationMaskingEvent implements BaseTelemetryEvent {
'event.name': 'observation_masking';
'event.timestamp': string;
tokens_before: number;
tokens_after: number;
masked_count: number;
total_prunable_tokens: number;
constructor(details: {
tokens_before: number;
tokens_after: number;
masked_count: number;
total_prunable_tokens: number;
}) {
this['event.name'] = 'observation_masking';
this['event.timestamp'] = new Date().toISOString();
this.tokens_before = details.tokens_before;
this.tokens_after = details.tokens_after;
this.masked_count = details.masked_count;
this.total_prunable_tokens = details.total_prunable_tokens;
}
toOpenTelemetryAttributes(config: Config): LogAttributes {
return {
...getCommonAttributes(config),
'event.name': EVENT_OBSERVATION_MASKING,
'event.timestamp': this['event.timestamp'],
tokens_before: this.tokens_before,
tokens_after: this.tokens_after,
masked_count: this.masked_count,
total_prunable_tokens: this.total_prunable_tokens,
};
}
toLogBody(): string {
return `Observation masking (Masked ${this.masked_count} tool outputs. Saved ${
this.tokens_before - this.tokens_after
} tokens)`;
}
}
export const EVENT_EXTENSION_UNINSTALL = 'gemini_cli.extension_uninstall';
export class ExtensionUninstallEvent implements BaseTelemetryEvent {
'event.name': 'extension_uninstall';
@@ -1576,6 +1619,7 @@ export type TelemetryEvent =
| LlmLoopCheckEvent
| StartupStatsEvent
| WebFetchFallbackAttemptEvent
| ObservationMaskingEvent
| EditStrategyEvent
| EditCorrectionEvent;