dsh-remote-plugin 0.6.8 → 0.6.10-rc.1

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.
Binary file
package/gateway-stats.cjs CHANGED
@@ -282,6 +282,15 @@ class StatsStore {
282
282
  let processed = 0
283
283
  let currentModel = ''
284
284
  let headerParsed = false
285
+ let lineCount = 0
286
+ let yielding = false
287
+ const dirtyDays = new Map()
288
+ let scanError = ''
289
+ const flush = () => {
290
+ for (const day of dirtyDays.values()) this._saveDay(day)
291
+ dirtyDays.clear()
292
+ if (lastSeq >= 0) this._setCursor(sessionId, lastSeq)
293
+ }
285
294
 
286
295
  const zstd = spawn('zstd', ['-dc', file], { stdio: ['ignore', 'pipe', 'ignore'] })
287
296
  const rl = readline.createInterface({ input: zstd.stdout })
@@ -292,11 +301,12 @@ class StatsStore {
292
301
  } else {
293
302
  console.warn(`[stats] zstd 解压失败 ${file}: ${err.message}`)
294
303
  }
304
+ scanError = err.code || err.message
295
305
  rl.close()
296
- resolvePromise({ sessionId, processed, skipped: 0, error: err.code || err.message })
297
306
  })
298
307
 
299
308
  rl.on('line', (line) => {
309
+ lineCount++
300
310
  let event
301
311
  try {
302
312
  event = JSON.parse(line)
@@ -324,24 +334,28 @@ class StatsStore {
324
334
  const model = eventModel(event) || currentModel || 'unknown'
325
335
  const { date, hour, period } = eventKey(event.time)
326
336
  if (date >= PRICING_START_DATE) {
327
- const day = this._loadDay(date)
337
+ const day = dirtyDays.get(date) || this._loadDay(date)
338
+ dirtyDays.set(date, day)
328
339
  const hourBucket = day.hours[hour] || (day.hours[hour] = {})
329
340
  const modelBucket = hourBucket[model] || (hourBucket[model] = emptyBucket())
330
341
  addUsage(modelBucket, model, period, usage)
331
- this._saveDay(day)
332
342
  processed++
333
343
  }
334
344
  }
335
345
  }
336
346
  lastSeq = event.seq
347
+ // 大历史文件按批次让出事件循环,避免回填长期占住实时 HTTP/WS 处理。
348
+ if (lineCount % 500 === 0 && !yielding) {
349
+ yielding = true
350
+ rl.pause()
351
+ setImmediate(() => { yielding = false; rl.resume() })
352
+ }
337
353
  })
338
354
 
339
355
  rl.on('close', () => {
340
- if (lastSeq >= 0) {
341
- this._setCursor(sessionId, lastSeq)
342
- }
356
+ flush()
343
357
  if (onProgress) onProgress({ sessionId, processed })
344
- resolvePromise({ sessionId, processed })
358
+ resolvePromise({ sessionId, processed, ...(scanError ? { error: scanError } : {}) })
345
359
  })
346
360
  })
347
361
  }
@@ -351,13 +365,14 @@ class StatsStore {
351
365
  const root = sessionsRoot || path.join(os.homedir(), '.dsh', 'sessions')
352
366
  let files = []
353
367
  try {
354
- const walk = (dir) => {
355
- for (const ent of fs.readdirSync(dir, { withFileTypes: true })) {
356
- if (ent.isDirectory()) walk(path.join(dir, ent.name))
368
+ const walk = async (dir) => {
369
+ const entries = await fs.promises.readdir(dir, { withFileTypes: true })
370
+ for (const ent of entries) {
371
+ if (ent.isDirectory()) await walk(path.join(dir, ent.name))
357
372
  else if (ent.name === 'session.jsonl.zstd') files.push(path.join(dir, ent.name))
358
373
  }
359
374
  }
360
- walk(root)
375
+ await walk(root)
361
376
  } catch (err) {
362
377
  console.warn('[stats] 扫描会话目录失败: ' + (err.message || err))
363
378
  return { files: 0, processed: 0 }