diff --git a/index.js b/index.js index 7ab16e8..254685b 100644 --- a/index.js +++ b/index.js @@ -1666,6 +1666,10 @@ function normalizeLoadDiagnosticsMs(value = 0) { return Math.round((Number(value) || 0) * 10) / 10; } +function normalizePersistDeltaDiagnosticsMs(value = 0) { + return Math.round((Number(value) || 0) * 10) / 10; +} + function updatePersistDeltaDiagnostics(snapshot = null) { const nextSnapshot = snapshot && typeof snapshot === "object" && !Array.isArray(snapshot) @@ -9418,6 +9422,7 @@ async function loadGraphFromIndexedDb( totalMs: normalizeLoadDiagnosticsMs(readLoadDiagnosticsNow() - loadStartedAt), }); let exportSnapshotMs = 0; + let preApplyMs = 0; let exportSnapshotSource = ""; if (!normalizedChatId) { const result = { @@ -9801,6 +9806,7 @@ async function loadGraphFromIndexedDb( }; } + preApplyMs = readLoadDiagnosticsNow() - loadStartedAt; const applyInvokeStartedAt = readLoadDiagnosticsNow(); const loadResult = applyIndexedDbSnapshotToRuntime(normalizedChatId, snapshot, { source, @@ -9811,6 +9817,8 @@ async function loadGraphFromIndexedDb( reasonPrefix: snapshotStore.reasonPrefix, }); const applyInvokeMs = readLoadDiagnosticsNow() - applyInvokeStartedAt; + const totalLoadMs = readLoadDiagnosticsNow() - loadStartedAt; + const loadAccountedMs = preApplyMs + applyInvokeMs; if (commitMarkerDiagnostic?.reason && loadResult?.loaded) { updateGraphPersistenceState({ persistMismatchReason: commitMarkerDiagnostic.reason, @@ -9828,7 +9836,14 @@ async function loadGraphFromIndexedDb( commitMarkerMismatched: commitMarkerMismatch.mismatched === true, exportSnapshotSource: exportSnapshotSource || "snapshot-prepared", exportSnapshotMs: normalizeLoadDiagnosticsMs(exportSnapshotMs), + preApplyMs: normalizeLoadDiagnosticsMs(preApplyMs), + preApplyOtherMs: normalizeLoadDiagnosticsMs( + Math.max(0, preApplyMs - exportSnapshotMs), + ), applyInvokeMs: normalizeLoadDiagnosticsMs(applyInvokeMs), + untrackedMs: normalizeLoadDiagnosticsMs( + Math.max(0, totalLoadMs - loadAccountedMs), + ), }); return loadResult; } catch (error) { @@ -9862,6 +9877,16 @@ async function loadGraphFromIndexedDb( error: error?.message || String(error), exportSnapshotSource: exportSnapshotSource || "unknown", exportSnapshotMs: normalizeLoadDiagnosticsMs(exportSnapshotMs), + preApplyMs: normalizeLoadDiagnosticsMs( + preApplyMs || (readLoadDiagnosticsNow() - loadStartedAt), + ), + preApplyOtherMs: normalizeLoadDiagnosticsMs( + Math.max( + 0, + (preApplyMs || (readLoadDiagnosticsNow() - loadStartedAt)) - + exportSnapshotMs, + ), + ), }); return result; } @@ -13363,12 +13388,19 @@ async function saveGraphToIndexedDb( let nativePersistPreloadStatus = "not-requested"; let nativePersistPreloadError = ""; let nativePersistPreloadMs = 0; + let baseSnapshotReadMs = 0; + let graphSnapshotBuildMs = 0; const persistDeltaStartedAt = readPersistDeltaDiagnosticsNow(); if (!delta) { - baseSnapshot = - readCachedIndexedDbSnapshot(normalizedChatId, localStore) || - (await db.exportSnapshot()); + const baseSnapshotReadStartedAt = readPersistDeltaDiagnosticsNow(); + baseSnapshot = readCachedIndexedDbSnapshot(normalizedChatId, localStore); + if (!baseSnapshot) { + baseSnapshot = await db.exportSnapshot(); + } + baseSnapshotReadMs = + readPersistDeltaDiagnosticsNow() - baseSnapshotReadStartedAt; + const graphSnapshotBuildStartedAt = readPersistDeltaDiagnosticsNow(); snapshot = buildSnapshotFromGraph(graph, { chatId: normalizedChatId, revision: requestedRevision, @@ -13383,6 +13415,8 @@ async function saveGraphToIndexedDb( hostChatId: currentIdentity.hostChatId || "", }, }); + graphSnapshotBuildMs = + readPersistDeltaDiagnosticsNow() - graphSnapshotBuildStartedAt; } const nativePersistBridgeMode = String( currentSettings.persistNativeDeltaBridgeMode || "json", @@ -13555,6 +13589,12 @@ async function saveGraphToIndexedDb( requestedRevision, markSyncDirty: true, }); + const commitDiagnostics = + commitResult?.diagnostics && + typeof commitResult.diagnostics === "object" && + !Array.isArray(commitResult.diagnostics) + ? cloneRuntimeDebugValue(commitResult.diagnostics, {}) + : null; const committedRevision = normalizeIndexedDbRevision( commitResult?.revision, requestedRevision, @@ -13564,6 +13604,7 @@ async function saveGraphToIndexedDb( let scheduleUploadWarning = ""; if (graph) { if (!snapshot) { + const graphSnapshotBuildStartedAt = readPersistDeltaDiagnosticsNow(); snapshot = buildSnapshotFromGraph(graph, { chatId: normalizedChatId, revision: committedRevision, @@ -13578,6 +13619,8 @@ async function saveGraphToIndexedDb( hostChatId: currentIdentity.hostChatId || "", }, }); + graphSnapshotBuildMs += + readPersistDeltaDiagnosticsNow() - graphSnapshotBuildStartedAt; } if (!snapshot.meta || typeof snapshot.meta !== "object" || Array.isArray(snapshot.meta)) { snapshot.meta = {}; @@ -13620,6 +13663,14 @@ async function saveGraphToIndexedDb( } } + const persistTotalMs = readPersistDeltaDiagnosticsNow() - persistDeltaStartedAt; + const persistAccountedMs = + Number(nativePersistPreloadMs || 0) + + Number(baseSnapshotReadMs || 0) + + Number(graphSnapshotBuildMs || 0) + + Number(persistDeltaBuildDiagnostics?.buildMs || 0) + + Number(commitDiagnostics?.queueWaitMs || 0) + + Number(commitDiagnostics?.commitMs || 0); const persistDeltaDiagnostics = { ...cloneRuntimeDebugValue(persistDeltaBuildDiagnostics, {}), chatId: normalizedChatId, @@ -13669,13 +13720,59 @@ async function saveGraphToIndexedDb( moduleError: String( nativePersistModuleStatus?.error || nativePersistPreloadError || "", ), + baseSnapshotReadMs: normalizePersistDeltaDiagnosticsMs(baseSnapshotReadMs), + snapshotBuildMs: normalizePersistDeltaDiagnosticsMs(graphSnapshotBuildMs), + commitStorageKind: String( + commitDiagnostics?.storageKind || localStore.storagePrimary || "", + ), + commitStoreMode: String( + commitDiagnostics?.storeMode || localStore.storageMode || "", + ), + commitQueueWaitMs: normalizePersistDeltaDiagnosticsMs( + commitDiagnostics?.queueWaitMs, + ), + commitMs: normalizePersistDeltaDiagnosticsMs(commitDiagnostics?.commitMs), + commitTxMs: normalizePersistDeltaDiagnosticsMs(commitDiagnostics?.txMs), + commitSnapshotReadMs: normalizePersistDeltaDiagnosticsMs( + commitDiagnostics?.snapshotReadMs, + ), + commitSnapshotWriteMs: normalizePersistDeltaDiagnosticsMs( + commitDiagnostics?.snapshotWriteMs, + ), + commitManifestReadMs: normalizePersistDeltaDiagnosticsMs( + commitDiagnostics?.manifestReadMs, + ), + commitWalWriteMs: normalizePersistDeltaDiagnosticsMs( + commitDiagnostics?.walWriteMs, + ), + commitManifestWriteMs: normalizePersistDeltaDiagnosticsMs( + commitDiagnostics?.manifestWriteMs, + ), + commitCacheApplyMs: normalizePersistDeltaDiagnosticsMs( + commitDiagnostics?.cacheApplyMs, + ), + commitPayloadBytes: Math.max( + 0, + Math.floor(Number(commitDiagnostics?.payloadBytes || 0)), + ), + commitWalBytes: Math.max( + 0, + Math.floor(Number(commitDiagnostics?.walBytes || 0)), + ), + commitRuntimeMetaKeyCount: Math.max( + 0, + Math.floor(Number(commitDiagnostics?.runtimeMetaKeyCount || 0)), + ), status: "committed", commitRevision: normalizeIndexedDbRevision( commitResult?.revision, requestedRevision, ), commitDelta: cloneRuntimeDebugValue(commitResult?.delta, null), - totalMs: readPersistDeltaDiagnosticsNow() - persistDeltaStartedAt, + totalMs: normalizePersistDeltaDiagnosticsMs(persistTotalMs), + untrackedMs: normalizePersistDeltaDiagnosticsMs( + Math.max(0, persistTotalMs - persistAccountedMs), + ), }; persistDeltaDiagnostics.fallbackReason = persistDeltaDiagnostics.requestedNative && !persistDeltaDiagnostics.usedNative diff --git a/sync/bme-db.js b/sync/bme-db.js index b4a7337..8c75c83 100644 --- a/sync/bme-db.js +++ b/sync/bme-db.js @@ -109,6 +109,26 @@ function normalizeNonNegativeInteger(value, fallback = 0) { return Math.max(0, Math.floor(parsed)); } +function readPersistCommitNow() { + if (typeof performance === "object" && typeof performance.now === "function") { + return performance.now(); + } + return Date.now(); +} + +function normalizePersistCommitMs(value = 0) { + return Math.round((Number(value) || 0) * 10) / 10; +} + +function estimatePersistPayloadBytes(value = null) { + if (value == null) return 0; + try { + return JSON.stringify(value).length; + } catch { + return 0; + } +} + function toPlainData(value, fallbackValue = null) { if (value == null) { return fallbackValue; @@ -2530,6 +2550,7 @@ export class BmeDatabase { async commitDelta(delta = {}, options = {}) { const db = await this.open(); + const commitRequestedAt = readPersistCommitNow(); const nowMs = Date.now(); const normalizedDelta = delta && typeof delta === "object" && !Array.isArray(delta) ? delta : {}; @@ -2554,6 +2575,7 @@ export class BmeDatabase { const reason = String(options.reason || "commitDelta"); const requestedRevision = normalizeRevision(options.requestedRevision); const shouldMarkSyncDirty = options.markSyncDirty !== false; + const payloadBytes = estimatePersistPayloadBytes(normalizedDelta); const normalizedCountDelta = normalizedDelta.countDelta && typeof normalizedDelta.countDelta === "object" && @@ -2567,7 +2589,9 @@ export class BmeDatabase { edges: 0, tombstones: 0, }; + let transactionMs = 0; + const transactionStartedAt = readPersistCommitNow(); await db.transaction( "rw", db.table("nodes"), @@ -2614,6 +2638,7 @@ export class BmeDatabase { ); }, ); + transactionMs = readPersistCommitNow() - transactionStartedAt; return { revision: nextRevision, @@ -2630,6 +2655,17 @@ export class BmeDatabase { deleteEdgeIds: deleteEdgeIds.length, tombstones: tombstones.length, }, + diagnostics: { + storageKind: "indexeddb", + storeMode: "indexeddb", + queueWaitMs: 0, + commitMs: normalizePersistCommitMs( + readPersistCommitNow() - commitRequestedAt, + ), + txMs: normalizePersistCommitMs(transactionMs), + payloadBytes, + runtimeMetaKeyCount: Object.keys(runtimeMetaPatch).length, + }, }; } diff --git a/sync/bme-opfs-store.js b/sync/bme-opfs-store.js index 68eac7c..9aad9d3 100644 --- a/sync/bme-opfs-store.js +++ b/sync/bme-opfs-store.js @@ -112,6 +112,26 @@ function normalizeNonNegativeInteger(value, fallback = 0) { return Math.floor(parsed); } +function readPersistCommitNow() { + if (typeof performance === "object" && typeof performance.now === "function") { + return performance.now(); + } + return Date.now(); +} + +function normalizePersistCommitMs(value = 0) { + return Math.round((Number(value) || 0) * 10) / 10; +} + +function estimatePersistPayloadBytes(value = null) { + if (value == null) return 0; + try { + return JSON.stringify(value).length; + } catch { + return 0; + } +} + function deriveNodeSourceFloor(node = {}) { const directSourceFloor = normalizeSourceFloor(node?.sourceFloor); if (directSourceFloor != null) return directSourceFloor; @@ -970,13 +990,19 @@ class LegacyOpfsGraphStore { } async commitDelta(delta = {}, options = {}) { + const commitRequestedAt = readPersistCommitNow(); return await this._runSerializedWrite( String(options?.reason || "commitDelta"), async () => { + const commitStartedAt = readPersistCommitNow(); + const queueWaitMs = commitStartedAt - commitRequestedAt; const nowMs = Date.now(); const normalizedDelta = delta && typeof delta === "object" && !Array.isArray(delta) ? delta : {}; + const payloadBytes = estimatePersistPayloadBytes(normalizedDelta); + const snapshotReadStartedAt = readPersistCommitNow(); const currentSnapshot = await this._loadSnapshot({ awaitWrites: false }); + const snapshotReadMs = readPersistCommitNow() - snapshotReadStartedAt; const nodeMap = new Map(); const edgeMap = new Map(); const tombstoneMap = new Map(); @@ -1093,7 +1119,9 @@ class LegacyOpfsGraphStore { edges: Array.from(edgeMap.values()), tombstones: Array.from(tombstoneMap.values()), }; + const snapshotWriteStartedAt = readPersistCommitNow(); await this._writeResolvedSnapshot(nextSnapshot); + const snapshotWriteMs = readPersistCommitNow() - snapshotWriteStartedAt; return { revision: nextRevision, @@ -1110,6 +1138,18 @@ class LegacyOpfsGraphStore { deleteEdgeIds: deleteEdgeIds.length, tombstones: tombstones.length, }, + diagnostics: { + storageKind: OPFS_STORE_KIND, + storeMode: this.storeMode, + queueWaitMs: normalizePersistCommitMs(queueWaitMs), + commitMs: normalizePersistCommitMs( + readPersistCommitNow() - commitStartedAt, + ), + snapshotReadMs: normalizePersistCommitMs(snapshotReadMs), + snapshotWriteMs: normalizePersistCommitMs(snapshotWriteMs), + payloadBytes, + runtimeMetaKeyCount: Object.keys(runtimeMetaPatch).length, + }, }; }, ); @@ -2326,12 +2366,18 @@ export class OpfsGraphStore { } async commitDelta(delta = {}, options = {}) { + const commitRequestedAt = readPersistCommitNow(); return await this._runSerializedWrite( String(options?.reason || "commitDelta"), async () => { + const commitStartedAt = readPersistCommitNow(); + const queueWaitMs = commitStartedAt - commitRequestedAt; + const manifestReadStartedAt = readPersistCommitNow(); const manifest = await this._ensureV2Ready({ awaitWrites: false }); + const manifestReadMs = readPersistCommitNow() - manifestReadStartedAt; const nowMs = Date.now(); const normalizedDelta = sanitizeOpfsV2Delta(delta, nowMs); + const payloadBytes = estimatePersistPayloadBytes(normalizedDelta); const requestedRevision = normalizeRevision(options.requestedRevision); const shouldMarkSyncDirty = options.markSyncDirty !== false; const reason = String(options.reason || "commitDelta"); @@ -2370,10 +2416,12 @@ export class OpfsGraphStore { runtimeMetaPatch: normalizedDelta.runtimeMetaPatch, countDelta: nextCountDelta, }; + const walWriteStartedAt = readPersistCommitNow(); const walDirectory = await this._getWalDirectory(); const walFilename = buildOpfsV2WalFilename(nextRevision); await writeJsonFile(walDirectory, walFilename, walRecord); const walByteLength = JSON.stringify(walRecord).length; + const walWriteMs = readPersistCommitNow() - walWriteStartedAt; const hadPendingWal = normalizeRevision(manifest?.pendingLogFromRevision) <= currentHeadRevision; @@ -2400,9 +2448,13 @@ export class OpfsGraphStore { lastReason: reason, }, }; + const manifestWriteStartedAt = readPersistCommitNow(); await this._writeManifest(nextManifest); + const manifestWriteMs = readPersistCommitNow() - manifestWriteStartedAt; + let cacheApplyMs = 0; if (this._snapshotCache) { + const cacheApplyStartedAt = readPersistCommitNow(); const nextSnapshot = applyOpfsV2DeltaToSnapshot( this._snapshotCache, normalizedDelta, @@ -2414,6 +2466,7 @@ export class OpfsGraphStore { }; nextSnapshot.state = normalizeSnapshotState(nextSnapshot); this._snapshotCache = nextSnapshot; + cacheApplyMs = readPersistCommitNow() - cacheApplyStartedAt; } this._maybeScheduleCompaction(nextManifest, reason); @@ -2433,6 +2486,23 @@ export class OpfsGraphStore { deleteEdgeIds: normalizedDelta.deleteEdgeIds.length, tombstones: normalizedDelta.tombstones.length, }, + diagnostics: { + storageKind: OPFS_STORE_KIND, + storeMode: this.storeMode, + queueWaitMs: normalizePersistCommitMs(queueWaitMs), + commitMs: normalizePersistCommitMs( + readPersistCommitNow() - commitStartedAt, + ), + manifestReadMs: normalizePersistCommitMs(manifestReadMs), + walWriteMs: normalizePersistCommitMs(walWriteMs), + manifestWriteMs: normalizePersistCommitMs(manifestWriteMs), + cacheApplyMs: normalizePersistCommitMs(cacheApplyMs), + payloadBytes, + walBytes: walByteLength, + runtimeMetaKeyCount: Object.keys( + normalizedDelta.runtimeMetaPatch || {}, + ).length, + }, }; }, ); diff --git a/tests/index-esm-entry-smoke.mjs b/tests/index-esm-entry-smoke.mjs index 739ec37..7799358 100644 --- a/tests/index-esm-entry-smoke.mjs +++ b/tests/index-esm-entry-smoke.mjs @@ -123,6 +123,7 @@ function evaluatePersistNativeDeltaGate() { }; } function readPersistDeltaDiagnosticsNow() { return Date.now(); } +function normalizePersistDeltaDiagnosticsMs(value = 0) { return Math.round((Number(value) || 0) * 10) / 10; } function updatePersistDeltaDiagnostics() {} function buildPersistDelta() { return { diff --git a/ui/panel.js b/ui/panel.js index 67b4622..c080d21 100644 --- a/ui/panel.js +++ b/ui/panel.js @@ -1532,13 +1532,64 @@ function _formatPersistencePersistDeltaSummary(persistDelta = null) { const pathText = String(diagnostics.path || "").trim() || "—"; const totalText = _formatDurationMs(diagnostics.totalMs || diagnostics.buildMs); + const commitText = _formatDurationMs(diagnostics.commitMs); const gateText = String(_formatPersistDeltaGateText(diagnostics) || "").trim(); const parts = [pathText]; if (totalText !== "—") parts.push(totalText); + if (commitText !== "—") parts.push(`commit ${commitText}`); if (gateText) parts.push(`native ${gateText}`); return parts.join(" · "); } +function _formatPersistCommitPhaseText(diagnostics = null) { + const snapshot = _readPersistenceDiagnosticObject(diagnostics); + if (!snapshot) return "—"; + const queueText = _formatDurationMs(snapshot.commitQueueWaitMs); + const commitText = _formatDurationMs(snapshot.commitMs); + if (queueText === "—" && commitText === "—") return "—"; + return `${queueText} / ${commitText}`; +} + +function _formatPersistCommitBreakdownText(diagnostics = null) { + const snapshot = _readPersistenceDiagnosticObject(diagnostics); + if (!snapshot) return "—"; + const parts = [ + snapshot.commitTxMs ? `tx ${_formatDurationMs(snapshot.commitTxMs)}` : "", + snapshot.commitSnapshotReadMs + ? `snapshot-read ${_formatDurationMs(snapshot.commitSnapshotReadMs)}` + : "", + snapshot.commitSnapshotWriteMs + ? `snapshot-write ${_formatDurationMs(snapshot.commitSnapshotWriteMs)}` + : "", + snapshot.commitManifestReadMs + ? `manifest-read ${_formatDurationMs(snapshot.commitManifestReadMs)}` + : "", + snapshot.commitWalWriteMs + ? `wal ${_formatDurationMs(snapshot.commitWalWriteMs)}` + : "", + snapshot.commitManifestWriteMs + ? `manifest-write ${_formatDurationMs(snapshot.commitManifestWriteMs)}` + : "", + snapshot.commitCacheApplyMs + ? `cache ${_formatDurationMs(snapshot.commitCacheApplyMs)}` + : "", + ].filter(Boolean); + return parts.join(" · ") || "—"; +} + +function _formatPersistCommitBytesText(diagnostics = null) { + const snapshot = _readPersistenceDiagnosticObject(diagnostics); + if (!snapshot) return "—"; + const parts = []; + const payloadText = _formatDataSizeBytes(snapshot.commitPayloadBytes); + const walText = _formatDataSizeBytes(snapshot.commitWalBytes); + const metaKeyCount = Number(snapshot.commitRuntimeMetaKeyCount || 0); + if (payloadText !== "—") parts.push(`payload ${payloadText}`); + if (walText !== "—") parts.push(`wal ${walText}`); + if (metaKeyCount > 0) parts.push(`meta ${metaKeyCount} keys`); + return parts.join(" · ") || "—"; +} + function _buildLoadDiagnosticRows(loadDiagnostics = null) { const diagnostics = _readPersistenceDiagnosticObject(loadDiagnostics); if (!diagnostics) { @@ -1561,10 +1612,13 @@ function _buildLoadDiagnosticRows(loadDiagnostics = null) { ["Load 状态", statusText], ["Load 原因", String(diagnostics.reason || "—")], ["Load 总耗时", _formatDurationMs(diagnostics.totalMs)], + ["Load 前置", _formatDurationMs(diagnostics.preApplyMs)], ["导出快照", _formatDurationMs(diagnostics.exportSnapshotMs)], + ["前置(除导出)", _formatDurationMs(diagnostics.preApplyOtherMs)], ["Hydrate", _formatDurationMs(diagnostics.hydrateMs)], ["Apply 调用", _formatDurationMs(diagnostics.applyInvokeMs)], ["Apply 运行", _formatDurationMs(diagnostics.applyRuntimeMs)], + ["Load 未归因", _formatDurationMs(diagnostics.untrackedMs)], ["Load 更新时间", updatedAtText], ]; } @@ -1586,6 +1640,12 @@ function _buildPersistDeltaDiagnosticRows(persistDelta = null) { )}E / ${Number(diagnostics.deleteNodeCount || 0)}DN / ${Number( diagnostics.deleteEdgeCount || 0, )}DE`; + const commitStoreText = `${String(diagnostics.commitStorageKind || "—")} / ${String( + diagnostics.commitStoreMode || "—", + )}`; + const commitPhaseText = _formatPersistCommitPhaseText(diagnostics); + const commitBreakdownText = _formatPersistCommitBreakdownText(diagnostics); + const commitBytesText = _formatPersistCommitBytesText(diagnostics); const updatedAtText = diagnostics.updatedAt ? _formatTaskProfileTime(diagnostics.updatedAt) : "—"; @@ -1594,8 +1654,11 @@ function _buildPersistDeltaDiagnosticRows(persistDelta = null) { ["Persist 路径", String(diagnostics.path || "—")], ["Native Gate", _formatPersistDeltaGateText(diagnostics)], ["Bridge 模式", bridgeText], + ["Commit 存储", commitStoreText], ["Persist 总耗时", _formatDurationMs(diagnostics.totalMs || diagnostics.buildMs)], ["构建耗时", _formatDurationMs(diagnostics.buildMs)], + ["Base 快照读取", _formatDurationMs(diagnostics.baseSnapshotReadMs)], + ["图谱快照构建", _formatDurationMs(diagnostics.snapshotBuildMs)], [ "Prepare / Native", `${_formatDurationMs(diagnostics.prepareMs)} / ${_formatDurationMs(diagnostics.nativeAttemptMs)}`, @@ -1605,11 +1668,15 @@ function _buildPersistDeltaDiagnosticRows(persistDelta = null) { `${_formatDurationMs(diagnostics.lookupMs)} / ${_formatDurationMs(diagnostics.jsDiffMs)}`, ], ["Hydrate", _formatDurationMs(diagnostics.hydrateMs)], + ["Commit 排队 / 提交", commitPhaseText], + ["Commit 细分", commitBreakdownText], + ["Commit Payload", commitBytesText], ["Preload", String(diagnostics.preloadStatus || "—")], ["Native 来源", String(diagnostics.moduleSource || "—")], ["Fallback 原因", String(diagnostics.fallbackReason || "—")], ["Preload / Native 错误", errorText || "—"], ["增量规模", deltaSizeText], + ["Persist 未归因", _formatDurationMs(diagnostics.untrackedMs)], ["Persist 更新时间", updatedAtText], ]; } @@ -8293,6 +8360,16 @@ function _formatDurationMs(durationMs) { return `${(normalized / 1000).toFixed(normalized >= 10000 ? 0 : 1)}s`; } +function _formatDataSizeBytes(byteCount) { + const normalized = Number(byteCount); + if (!Number.isFinite(normalized) || normalized <= 0) return "—"; + if (normalized < 1024) return `${Math.round(normalized)} B`; + if (normalized < 1024 * 1024) { + return `${(normalized / 1024).toFixed(normalized >= 10 * 1024 ? 0 : 1)} KB`; + } + return `${(normalized / (1024 * 1024)).toFixed(normalized >= 10 * 1024 * 1024 ? 0 : 1)} MB`; +} + function _getMonitorTaskTypeLabel(taskType = "") { const normalized = String(taskType || "").trim().toLowerCase(); const labels = { @@ -8767,6 +8844,12 @@ function _renderPersistDeltaTraceCard(state) { const payloadCharsText = diagnostics.combinedSerializedChars ? `${Number(diagnostics.combinedSerializedChars || 0)} / ${Number(diagnostics.minCombinedSerializedChars || 0)}` : "—"; + const snapshotBuildText = `${_formatDurationMs(diagnostics.baseSnapshotReadMs)} / ${_formatDurationMs( + diagnostics.snapshotBuildMs, + )}`; + const commitPhaseText = _formatPersistCommitPhaseText(diagnostics); + const commitBreakdownText = _formatPersistCommitBreakdownText(diagnostics); + const commitBytesText = _formatPersistCommitBytesText(diagnostics); const cacheText = `${Number(diagnostics.serializationCacheHits || 0)}H / ${Number( diagnostics.serializationCacheMisses || 0, )}M`; @@ -8819,6 +8902,10 @@ function _renderPersistDeltaTraceCard(state) { 构建耗时 ${_escHtml(_formatDurationMs(diagnostics.buildMs))} +
+ Base / Snapshot + ${_escHtml(snapshotBuildText)} +
Prepare / Native ${_escHtml( @@ -8837,6 +8924,18 @@ function _renderPersistDeltaTraceCard(state) { `${_formatDurationMs(diagnostics.hydrateMs)} / ${cacheText}`, )}
+
+ Commit 排队 / 提交 + ${_escHtml(commitPhaseText)} +
+
+ Commit 细分 + ${_escHtml(commitBreakdownText)} +
+
+ Commit Payload + ${_escHtml(commitBytesText)} +
PreparedSet Cache ${_escHtml(preparedSetCacheText)} @@ -8855,6 +8954,10 @@ function _renderPersistDeltaTraceCard(state) { `${Number(diagnostics.upsertNodeCount || 0)}N / ${Number(diagnostics.upsertEdgeCount || 0)}E / ${Number(diagnostics.deleteNodeCount || 0)}DN / ${Number(diagnostics.deleteEdgeCount || 0)}DE`, )}
+
+ 未归因 + ${_escHtml(_formatDurationMs(diagnostics.untrackedMs))} +
${_renderMessageTraceTextBlock( "Fallback reason",