getaimeter 0.1.4 → 0.1.6
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.
- package/cli.js +35 -2
- package/package.json +1 -1
- package/watcher.js +35 -69
package/cli.js
CHANGED
|
@@ -161,10 +161,43 @@ async function runSetup() {
|
|
|
161
161
|
// ---------------------------------------------------------------------------
|
|
162
162
|
|
|
163
163
|
function runWatch() {
|
|
164
|
+
const fs = require('fs');
|
|
165
|
+
const path = require('path');
|
|
166
|
+
const lockFile = path.join(AIMETER_DIR, 'watcher.lock');
|
|
167
|
+
|
|
168
|
+
// Check if another instance is already running
|
|
169
|
+
try {
|
|
170
|
+
if (fs.existsSync(lockFile)) {
|
|
171
|
+
const lockData = JSON.parse(fs.readFileSync(lockFile, 'utf8'));
|
|
172
|
+
// Check if the PID is still alive
|
|
173
|
+
try {
|
|
174
|
+
process.kill(lockData.pid, 0); // signal 0 = just check if alive
|
|
175
|
+
console.log(`Another watcher is already running (PID ${lockData.pid}). Use 'aimeter stop' first.`);
|
|
176
|
+
process.exitCode = 1;
|
|
177
|
+
return;
|
|
178
|
+
} catch {
|
|
179
|
+
// PID is dead — stale lock, remove it
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
} catch {}
|
|
183
|
+
|
|
184
|
+
// Write lock file
|
|
185
|
+
fs.mkdirSync(AIMETER_DIR, { recursive: true });
|
|
186
|
+
fs.writeFileSync(lockFile, JSON.stringify({ pid: process.pid, startedAt: new Date().toISOString() }));
|
|
187
|
+
|
|
164
188
|
const cleanup = startWatching();
|
|
165
189
|
|
|
166
|
-
|
|
167
|
-
|
|
190
|
+
const cleanupAll = () => {
|
|
191
|
+
cleanup();
|
|
192
|
+
try { fs.unlinkSync(lockFile); } catch {}
|
|
193
|
+
process.exit(0);
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
process.on('SIGINT', cleanupAll);
|
|
197
|
+
process.on('SIGTERM', cleanupAll);
|
|
198
|
+
process.on('exit', () => {
|
|
199
|
+
try { fs.unlinkSync(lockFile); } catch {}
|
|
200
|
+
});
|
|
168
201
|
|
|
169
202
|
process.on('uncaughtException', (err) => {
|
|
170
203
|
console.error('[aimeter] Uncaught:', err.message);
|
package/package.json
CHANGED
package/watcher.js
CHANGED
|
@@ -137,39 +137,20 @@ async function reportEvents(events) {
|
|
|
137
137
|
// File watcher
|
|
138
138
|
// ---------------------------------------------------------------------------
|
|
139
139
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
// Skip if already being processed
|
|
151
|
-
if (_processing.has(normalizedKey)) return;
|
|
152
|
-
|
|
153
|
-
// Debounce: wait 2000ms after last change before processing
|
|
154
|
-
const existing = _debounceTimers.get(normalizedKey);
|
|
155
|
-
if (existing) clearTimeout(existing);
|
|
156
|
-
|
|
157
|
-
_debounceTimers.set(normalizedKey, setTimeout(async () => {
|
|
158
|
-
_debounceTimers.delete(normalizedKey);
|
|
159
|
-
if (_processing.has(normalizedKey)) return;
|
|
160
|
-
_processing.add(normalizedKey);
|
|
161
|
-
try {
|
|
162
|
-
const events = extractNewUsage(filePath);
|
|
163
|
-
if (events.length > 0) {
|
|
164
|
-
await reportEvents(events);
|
|
165
|
-
saveState();
|
|
166
|
-
}
|
|
167
|
-
} catch (err) {
|
|
168
|
-
logError(`Processing ${filePath}:`, err.message);
|
|
169
|
-
} finally {
|
|
170
|
-
_processing.delete(normalizedKey);
|
|
140
|
+
/**
|
|
141
|
+
* Process a single file: extract new usage and report it.
|
|
142
|
+
* Called only from the poll loop — single-threaded, no races.
|
|
143
|
+
*/
|
|
144
|
+
async function processFile(filePath) {
|
|
145
|
+
try {
|
|
146
|
+
const events = extractNewUsage(filePath);
|
|
147
|
+
if (events.length > 0) {
|
|
148
|
+
await reportEvents(events);
|
|
149
|
+
saveState();
|
|
171
150
|
}
|
|
172
|
-
}
|
|
151
|
+
} catch (err) {
|
|
152
|
+
logError(`Processing ${filePath}:`, err.message);
|
|
153
|
+
}
|
|
173
154
|
}
|
|
174
155
|
|
|
175
156
|
/**
|
|
@@ -248,45 +229,32 @@ function startWatching() {
|
|
|
248
229
|
}
|
|
249
230
|
saveState();
|
|
250
231
|
|
|
251
|
-
//
|
|
252
|
-
|
|
253
|
-
for (const watchPath of watchPaths) {
|
|
254
|
-
try {
|
|
255
|
-
const w = fs.watch(watchPath, { recursive: true }, (eventType, filename) => {
|
|
256
|
-
if (!filename) return;
|
|
257
|
-
const fullPath = path.join(watchPath, filename);
|
|
258
|
-
handleFileChange(fullPath);
|
|
259
|
-
});
|
|
260
|
-
|
|
261
|
-
w.on('error', (err) => {
|
|
262
|
-
logError(`Watcher error on ${watchPath}:`, err.message);
|
|
263
|
-
});
|
|
264
|
-
|
|
265
|
-
watchers.push(w);
|
|
266
|
-
log('Watching (fs.watch):', watchPath);
|
|
267
|
-
} catch (err) {
|
|
268
|
-
logError(`Could not watch ${watchPath}:`, err.message);
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
// Polling fallback — fs.watch is unreliable on Windows for deeply nested dirs.
|
|
273
|
-
// Every 5 seconds, scan all known JSONL files for size changes.
|
|
232
|
+
// Poll every 5 seconds — simple, reliable, no race conditions.
|
|
233
|
+
// fs.watch is unreliable on Windows for deeply nested dirs and fires duplicates.
|
|
274
234
|
const POLL_INTERVAL = 5_000;
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
235
|
+
let polling = false;
|
|
236
|
+
|
|
237
|
+
const pollInterval = setInterval(async () => {
|
|
238
|
+
if (polling) return; // skip if previous poll still running
|
|
239
|
+
polling = true;
|
|
240
|
+
try {
|
|
241
|
+
for (const watchPath of watchPaths) {
|
|
242
|
+
const files = findJsonlFiles(watchPath);
|
|
243
|
+
for (const file of files) {
|
|
244
|
+
try {
|
|
245
|
+
const currentSize = fs.statSync(file).size;
|
|
246
|
+
const lastOffset = getOffset(file);
|
|
247
|
+
if (currentSize > lastOffset) {
|
|
248
|
+
await processFile(file);
|
|
249
|
+
}
|
|
250
|
+
} catch {}
|
|
251
|
+
}
|
|
286
252
|
}
|
|
253
|
+
} finally {
|
|
254
|
+
polling = false;
|
|
287
255
|
}
|
|
288
256
|
}, POLL_INTERVAL);
|
|
289
|
-
log(`Polling every ${POLL_INTERVAL / 1000}s
|
|
257
|
+
log(`Polling every ${POLL_INTERVAL / 1000}s`);
|
|
290
258
|
|
|
291
259
|
// Periodic state save
|
|
292
260
|
const saveInterval = setInterval(() => saveState(), 30_000);
|
|
@@ -295,8 +263,6 @@ function startWatching() {
|
|
|
295
263
|
return () => {
|
|
296
264
|
clearInterval(saveInterval);
|
|
297
265
|
clearInterval(pollInterval);
|
|
298
|
-
for (const w of watchers) w.close();
|
|
299
|
-
for (const t of _debounceTimers.values()) clearTimeout(t);
|
|
300
266
|
saveState();
|
|
301
267
|
log('Watcher stopped.');
|
|
302
268
|
};
|