Files
gemini-cli/packages/core/src/telemetry/high-water-mark-tracker.test.ts

199 lines
6.5 KiB
TypeScript

/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { describe, it, expect, beforeEach, vi } from 'vitest';
import { HighWaterMarkTracker } from './high-water-mark-tracker.js';
describe('HighWaterMarkTracker', () => {
let tracker: HighWaterMarkTracker;
beforeEach(() => {
tracker = new HighWaterMarkTracker(5); // 5% threshold
});
describe('constructor', () => {
it('should initialize with default values', () => {
const defaultTracker = new HighWaterMarkTracker();
expect(defaultTracker).toBeInstanceOf(HighWaterMarkTracker);
});
it('should initialize with custom values', () => {
const customTracker = new HighWaterMarkTracker(10);
expect(customTracker).toBeInstanceOf(HighWaterMarkTracker);
});
it('should throw on negative threshold', () => {
expect(() => new HighWaterMarkTracker(-1)).toThrow(
'growthThresholdPercent must be non-negative.',
);
});
});
describe('shouldRecordMetric', () => {
it('should return true for first measurement', () => {
const result = tracker.shouldRecordMetric('heap_used', 1000000);
expect(result).toBe(true);
});
it('should return false for small increases', () => {
// Set initial high-water mark
tracker.shouldRecordMetric('heap_used', 1000000);
// Small increase (less than 5%)
const result = tracker.shouldRecordMetric('heap_used', 1030000); // 3% increase
expect(result).toBe(false);
});
it('should return true for significant increases', () => {
// Set initial high-water mark
tracker.shouldRecordMetric('heap_used', 1000000);
// Add several readings to build up smoothing window
tracker.shouldRecordMetric('heap_used', 1100000); // 10% increase
tracker.shouldRecordMetric('heap_used', 1150000); // Additional growth
const result = tracker.shouldRecordMetric('heap_used', 1200000); // Sustained growth
expect(result).toBe(true);
});
it('should handle decreasing values correctly', () => {
// Set initial high-water mark
tracker.shouldRecordMetric('heap_used', 1000000);
// Decrease (should not trigger)
const result = tracker.shouldRecordMetric('heap_used', 900000); // 10% decrease
expect(result).toBe(false);
});
it('should update high-water mark when threshold exceeded', () => {
tracker.shouldRecordMetric('heap_used', 1000000);
const beforeMark = tracker.getHighWaterMark('heap_used');
// Create sustained growth pattern to trigger update
tracker.shouldRecordMetric('heap_used', 1100000);
tracker.shouldRecordMetric('heap_used', 1150000);
tracker.shouldRecordMetric('heap_used', 1200000);
const afterMark = tracker.getHighWaterMark('heap_used');
expect(afterMark).toBeGreaterThan(beforeMark);
});
it('should handle multiple metric types independently', () => {
tracker.shouldRecordMetric('heap_used', 1000000);
tracker.shouldRecordMetric('rss', 2000000);
expect(tracker.getHighWaterMark('heap_used')).toBeGreaterThan(0);
expect(tracker.getHighWaterMark('rss')).toBeGreaterThan(0);
expect(tracker.getHighWaterMark('heap_used')).not.toBe(
tracker.getHighWaterMark('rss'),
);
});
});
describe('smoothing functionality', () => {
it('should reduce noise from garbage collection spikes', () => {
// Establish baseline
tracker.shouldRecordMetric('heap_used', 1000000);
tracker.shouldRecordMetric('heap_used', 1000000);
tracker.shouldRecordMetric('heap_used', 1000000);
// Single spike (should be smoothed out)
const result = tracker.shouldRecordMetric('heap_used', 2000000);
// With the new responsive algorithm, large spikes do trigger
expect(result).toBe(true);
});
it('should eventually respond to sustained growth', () => {
// Establish baseline
tracker.shouldRecordMetric('heap_used', 1000000);
// Sustained growth pattern
tracker.shouldRecordMetric('heap_used', 1100000);
tracker.shouldRecordMetric('heap_used', 1150000);
const result = tracker.shouldRecordMetric('heap_used', 1200000);
expect(result).toBe(true);
});
});
describe('getHighWaterMark', () => {
it('should return 0 for unknown metric types', () => {
const mark = tracker.getHighWaterMark('unknown_metric');
expect(mark).toBe(0);
});
it('should return correct value for known metric types', () => {
tracker.shouldRecordMetric('heap_used', 1000000);
const mark = tracker.getHighWaterMark('heap_used');
expect(mark).toBeGreaterThan(0);
});
});
describe('getAllHighWaterMarks', () => {
it('should return empty object initially', () => {
const marks = tracker.getAllHighWaterMarks();
expect(marks).toEqual({});
});
it('should return all recorded marks', () => {
tracker.shouldRecordMetric('heap_used', 1000000);
tracker.shouldRecordMetric('rss', 2000000);
const marks = tracker.getAllHighWaterMarks();
expect(Object.keys(marks)).toHaveLength(2);
expect(marks['heap_used']).toBeGreaterThan(0);
expect(marks['rss']).toBeGreaterThan(0);
});
});
describe('resetHighWaterMark', () => {
it('should reset specific metric type', () => {
tracker.shouldRecordMetric('heap_used', 1000000);
tracker.shouldRecordMetric('rss', 2000000);
tracker.resetHighWaterMark('heap_used');
expect(tracker.getHighWaterMark('heap_used')).toBe(0);
expect(tracker.getHighWaterMark('rss')).toBeGreaterThan(0);
});
});
describe('resetAllHighWaterMarks', () => {
it('should reset all metrics', () => {
tracker.shouldRecordMetric('heap_used', 1000000);
tracker.shouldRecordMetric('rss', 2000000);
tracker.resetAllHighWaterMarks();
expect(tracker.getHighWaterMark('heap_used')).toBe(0);
expect(tracker.getHighWaterMark('rss')).toBe(0);
expect(tracker.getAllHighWaterMarks()).toEqual({});
});
});
describe('time-based cleanup', () => {
it('should clean up old readings', () => {
vi.useFakeTimers();
// Add readings
tracker.shouldRecordMetric('heap_used', 1000000);
// Advance time significantly
vi.advanceTimersByTime(15000); // 15 seconds
// Explicit cleanup should remove stale entries when age exceeded
tracker.cleanup(10000); // 10 seconds
// Entry should be removed
expect(tracker.getHighWaterMark('heap_used')).toBe(0);
vi.useRealTimers();
});
});
});