viberag 0.7.0 → 0.8.0

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.
@@ -0,0 +1,200 @@
1
+ import crypto from 'node:crypto';
2
+ const BYTES_PER_MB = 1024 * 1024;
3
+ function toMB(bytes) {
4
+ return Number((bytes / BYTES_PER_MB).toFixed(1));
5
+ }
6
+ function sha256Hex(value) {
7
+ return crypto.createHash('sha256').update(value).digest('hex');
8
+ }
9
+ function buildDefaultProcessSnapshot() {
10
+ return {
11
+ pid: process.pid,
12
+ uptimeSec: Math.round(process.uptime()),
13
+ nodeVersion: process.version,
14
+ platform: process.platform,
15
+ arch: process.arch,
16
+ resourceUsage: process.resourceUsage(),
17
+ };
18
+ }
19
+ export function buildMemoryMonitorSentryEvent(args) {
20
+ const nowMs = args.nowMs ?? Date.now();
21
+ const processSnapshot = args.processSnapshot ?? buildDefaultProcessSnapshot();
22
+ const { report, state, watcherStatus } = args;
23
+ const lastProgressAtMs = state.indexing.lastProgressAt
24
+ ? new Date(state.indexing.lastProgressAt).getTime()
25
+ : null;
26
+ const indexingElapsedMs = state.indexing.startedAt
27
+ ? Math.max(0, nowMs - new Date(state.indexing.startedAt).getTime())
28
+ : null;
29
+ const secondsSinceProgress = lastProgressAtMs === null
30
+ ? null
31
+ : Math.max(0, Math.round((nowMs - lastProgressAtMs) / 1000));
32
+ const slotSummary = state.slots.reduce((acc, slot) => {
33
+ acc[slot.state] += 1;
34
+ return acc;
35
+ }, {
36
+ idle: 0,
37
+ processing: 0,
38
+ 'rate-limited': 0,
39
+ });
40
+ const triggerKinds = Array.from(new Set(report.triggers.map(trigger => trigger.kind))).sort();
41
+ const triggerSummary = triggerKinds.join(',');
42
+ const triggerFingerprint = triggerKinds.join('+') || 'none';
43
+ const rssMB = toMB(report.usage.rss);
44
+ const rootHash = sha256Hex(args.projectRoot);
45
+ return {
46
+ message: 'Daemon memory monitor alert',
47
+ level: 'error',
48
+ fingerprint: ['viberag', 'daemon', 'memory-monitor', triggerFingerprint],
49
+ tags: {
50
+ service: 'daemon',
51
+ event: 'memory_monitor_alert',
52
+ trigger_threshold: triggerKinds.includes('threshold') ? 'true' : 'false',
53
+ trigger_rapid_growth: triggerKinds.includes('rapid_growth')
54
+ ? 'true'
55
+ : 'false',
56
+ indexing_status: state.indexing.status,
57
+ warmup_status: state.warmup.status,
58
+ },
59
+ contexts: {
60
+ memory_usage: {
61
+ rssMB,
62
+ heapUsedMB: toMB(report.usage.heapUsed),
63
+ heapTotalMB: toMB(report.usage.heapTotal),
64
+ externalMB: toMB(report.usage.external),
65
+ arrayBuffersMB: toMB(report.usage.arrayBuffers),
66
+ },
67
+ monitor: {
68
+ triggerKinds: triggerSummary,
69
+ thresholdMB: report.threshold.thresholdMB,
70
+ recoveryMB: report.threshold.recoveryMB,
71
+ growthThresholdMB: report.config.growthThresholdMB,
72
+ growthWindowSec: report.config.growthWindowSec,
73
+ minReportIntervalSec: report.config.minReportIntervalSec,
74
+ maxReportsPerDay: report.config.maxReportsPerDay,
75
+ sentToday: report.rateLimit.sentToday,
76
+ lastReportedAt: report.rateLimit.lastReportedAt,
77
+ },
78
+ indexing: {
79
+ status: state.indexing.status,
80
+ phase: state.indexing.phase,
81
+ stage: state.indexing.stage,
82
+ current: state.indexing.current,
83
+ total: state.indexing.total,
84
+ unit: state.indexing.unit,
85
+ chunksProcessed: state.indexing.chunksProcessed,
86
+ elapsedMs: indexingElapsedMs,
87
+ secondsSinceProgress,
88
+ },
89
+ warmup: {
90
+ status: state.warmup.status,
91
+ provider: state.warmup.provider,
92
+ startedAt: state.warmup.startedAt,
93
+ readyAt: state.warmup.readyAt,
94
+ error: state.warmup.error,
95
+ },
96
+ process: {
97
+ pid: processSnapshot.pid,
98
+ uptimeSec: processSnapshot.uptimeSec,
99
+ nodeVersion: processSnapshot.nodeVersion,
100
+ platform: processSnapshot.platform,
101
+ arch: processSnapshot.arch,
102
+ },
103
+ },
104
+ extra: {
105
+ observedAt: report.observedAt,
106
+ triggers: report.triggers,
107
+ monitor: {
108
+ threshold: report.threshold,
109
+ growth: report.growth,
110
+ rateLimit: report.rateLimit,
111
+ samples: report.samples,
112
+ config: report.config,
113
+ },
114
+ process: {
115
+ pid: processSnapshot.pid,
116
+ uptimeSec: processSnapshot.uptimeSec,
117
+ nodeVersion: processSnapshot.nodeVersion,
118
+ platform: processSnapshot.platform,
119
+ arch: processSnapshot.arch,
120
+ resourceUsage: processSnapshot.resourceUsage,
121
+ },
122
+ project: {
123
+ rootHash,
124
+ },
125
+ indexing: {
126
+ status: state.indexing.status,
127
+ phase: state.indexing.phase,
128
+ stage: state.indexing.stage,
129
+ current: state.indexing.current,
130
+ total: state.indexing.total,
131
+ unit: state.indexing.unit,
132
+ chunksProcessed: state.indexing.chunksProcessed,
133
+ throttleMessage: state.indexing.throttleMessage,
134
+ error: state.indexing.error,
135
+ startedAt: state.indexing.startedAt,
136
+ lastProgressAt: state.indexing.lastProgressAt,
137
+ elapsedMs: indexingElapsedMs,
138
+ secondsSinceProgress,
139
+ cancelRequestedAt: state.indexing.cancelRequestedAt,
140
+ cancelledAt: state.indexing.cancelledAt,
141
+ cancelReason: state.indexing.cancelReason,
142
+ },
143
+ warmup: {
144
+ status: state.warmup.status,
145
+ provider: state.warmup.provider,
146
+ startedAt: state.warmup.startedAt,
147
+ readyAt: state.warmup.readyAt,
148
+ error: state.warmup.error,
149
+ cancelRequestedAt: state.warmup.cancelRequestedAt,
150
+ cancelledAt: state.warmup.cancelledAt,
151
+ cancelReason: state.warmup.cancelReason,
152
+ },
153
+ watcher: {
154
+ watching: watcherStatus.watching,
155
+ filesWatched: watcherStatus.filesWatched,
156
+ pendingChanges: watcherStatus.pendingChanges,
157
+ pendingPathsSample: watcherStatus.pendingPaths
158
+ .slice(0, 20)
159
+ .map(path => ({
160
+ sha256: sha256Hex(path),
161
+ length: path.length,
162
+ })),
163
+ indexUpToDate: watcherStatus.indexUpToDate,
164
+ lastIndexUpdate: watcherStatus.lastIndexUpdate,
165
+ lastError: watcherStatus.lastError,
166
+ autoIndexPausedUntil: watcherStatus.autoIndexPausedUntil,
167
+ autoIndexPauseReason: watcherStatus.autoIndexPauseReason,
168
+ },
169
+ slots: {
170
+ summary: slotSummary,
171
+ active: state.slots
172
+ .map((slot, index) => ({ slot: index, ...slot }))
173
+ .filter(slot => slot.state === 'processing' || slot.state === 'rate-limited')
174
+ .slice(0, 20),
175
+ },
176
+ recentFailures: state.failures.slice(-10).map(failure => ({
177
+ batchInfo: failure.batchInfo,
178
+ error: failure.error,
179
+ timestamp: failure.timestamp,
180
+ chunkCount: failure.chunkCount,
181
+ fileCount: failure.files.length,
182
+ fileHashes: failure.files.slice(0, 20).map(filepath => ({
183
+ sha256: sha256Hex(filepath),
184
+ length: filepath.length,
185
+ })),
186
+ })),
187
+ },
188
+ triggerSummary,
189
+ rssMB,
190
+ };
191
+ }
192
+ export function toSentryCaptureContext(event) {
193
+ return {
194
+ level: event.level,
195
+ fingerprint: event.fingerprint,
196
+ tags: event.tags,
197
+ contexts: event.contexts,
198
+ extra: event.extra,
199
+ };
200
+ }
@@ -0,0 +1,136 @@
1
+ type MemoryMonitorLogLevel = 'debug' | 'info' | 'warn' | 'error';
2
+ type MemoryMonitorLogger = (level: MemoryMonitorLogLevel, message: string) => void;
3
+ type MemoryUsageReader = () => NodeJS.MemoryUsage;
4
+ type NowProvider = () => number;
5
+ type PersistedLimiterState = {
6
+ dayKey: string;
7
+ sentCount: number;
8
+ lastSentAtMs: number;
9
+ };
10
+ export type MemoryMonitorTrigger = {
11
+ kind: 'threshold';
12
+ rssMB: number;
13
+ thresholdMB: number;
14
+ firstTriggeredAt: string | null;
15
+ } | {
16
+ kind: 'rapid_growth';
17
+ growthMB: number;
18
+ growthThresholdMB: number;
19
+ windowSec: number;
20
+ durationSec: number;
21
+ fromRssMB: number;
22
+ toRssMB: number;
23
+ fromAt: string;
24
+ toAt: string;
25
+ };
26
+ export type MemoryMonitorReport = {
27
+ observedAt: string;
28
+ observedAtMs: number;
29
+ usage: NodeJS.MemoryUsage;
30
+ triggers: MemoryMonitorTrigger[];
31
+ threshold: {
32
+ active: boolean;
33
+ thresholdMB: number;
34
+ recoveryMB: number;
35
+ firstTriggeredAt: string | null;
36
+ };
37
+ growth: {
38
+ windowSec: number;
39
+ sampleCount: number;
40
+ fromAt: string;
41
+ toAt: string;
42
+ fromRssMB: number;
43
+ toRssMB: number;
44
+ minRssMB: number;
45
+ maxRssMB: number;
46
+ growthMB: number;
47
+ durationSec: number;
48
+ } | null;
49
+ rateLimit: {
50
+ dayKey: string;
51
+ sentToday: number;
52
+ maxReportsPerDay: number;
53
+ minReportIntervalSec: number;
54
+ lastReportedAt: string | null;
55
+ suppressedByCooldown: number;
56
+ suppressedByDailyCap: number;
57
+ };
58
+ samples: {
59
+ retentionSec: number;
60
+ totalSampleCount: number;
61
+ recent: Array<{
62
+ at: string;
63
+ rssMB: number;
64
+ heapUsedMB: number;
65
+ externalMB: number;
66
+ }>;
67
+ };
68
+ config: {
69
+ pollIntervalSec: number;
70
+ thresholdMB: number;
71
+ recoveryMB: number;
72
+ growthThresholdMB: number;
73
+ growthWindowSec: number;
74
+ minReportIntervalSec: number;
75
+ maxReportsPerDay: number;
76
+ };
77
+ };
78
+ export interface MemoryMonitorStateStore {
79
+ load(): Promise<PersistedLimiterState | null>;
80
+ save(state: PersistedLimiterState): Promise<void>;
81
+ }
82
+ export type DaemonMemoryMonitorOptions = {
83
+ projectRoot: string;
84
+ onReport: (report: MemoryMonitorReport) => void | Promise<void>;
85
+ logger?: MemoryMonitorLogger;
86
+ readMemoryUsage?: MemoryUsageReader;
87
+ now?: NowProvider;
88
+ pollIntervalMs?: number;
89
+ thresholdBytes?: number;
90
+ recoveryBytes?: number;
91
+ growthThresholdBytes?: number;
92
+ growthWindowMs?: number;
93
+ minReportIntervalMs?: number;
94
+ maxReportsPerDay?: number;
95
+ sampleRetentionMs?: number;
96
+ stateStore?: MemoryMonitorStateStore;
97
+ };
98
+ export declare class DaemonMemoryMonitor {
99
+ private readonly projectRoot;
100
+ private readonly onReport;
101
+ private readonly logger;
102
+ private readonly readMemoryUsage;
103
+ private readonly now;
104
+ private readonly pollIntervalMs;
105
+ private readonly thresholdBytes;
106
+ private readonly recoveryBytes;
107
+ private readonly growthThresholdBytes;
108
+ private readonly growthWindowMs;
109
+ private readonly minReportIntervalMs;
110
+ private readonly maxReportsPerDay;
111
+ private readonly sampleRetentionMs;
112
+ private readonly stateStore;
113
+ private timer;
114
+ private checkInFlight;
115
+ private samples;
116
+ private thresholdActive;
117
+ private thresholdFirstTriggeredAtMs;
118
+ private limiterState;
119
+ private suppressedByCooldown;
120
+ private suppressedByDailyCap;
121
+ private stateLoaded;
122
+ constructor(options: DaemonMemoryMonitorOptions);
123
+ start(): Promise<void>;
124
+ stop(): Promise<void>;
125
+ checkNow(): Promise<void>;
126
+ private runCheck;
127
+ private ensureLimiterStateLoaded;
128
+ private persistLimiterState;
129
+ private rollLimiterDayIfNeeded;
130
+ private getLimiterBlockReason;
131
+ private recordSample;
132
+ private computeGrowth;
133
+ private buildReport;
134
+ private log;
135
+ }
136
+ export {};