fix: memory analysis dark mode stripe, scan speed, and NaN total size

- Dark mode: add --el-fill-color-lighter and --el-table-current-row-bg-color
  to .el-table overrides so striped rows render with proper contrast
- Scan speed: add batchKeyMemoryUsage pipeline (TYPE + MEMORY USAGE in one
  Redis round-trip instead of 2×N sequential IPC calls)
- Scan speed: decouple scan loop from UI via 300ms flush buffer, keeping
  el-table re-render frequency capped at ~3/s
- Scan speed: use shallowRef for entries to skip deep reactive proxies
- NaN fix: handle ioredis stringNumbers:true by converting via Number()
  instead of typeof === 'number'
- formatSize: guard against non-finite input, add TB/PB units
This commit is contained in:
2026-07-12 10:01:30 +08:00
parent 72967c45e9
commit c8eeb7c0b9
6 changed files with 75 additions and 24 deletions
@@ -1,5 +1,5 @@
<script setup lang="ts">
import { ref, computed, onUnmounted } from 'vue'
import { ref, shallowRef, computed, onUnmounted } from 'vue'
import { useConnectionStore } from '@renderer/stores/connection'
import { useI18n } from '@renderer/i18n'
@@ -12,15 +12,18 @@ interface KeyEntry {
type: string
}
const entries = ref<KeyEntry[]>([])
const entries = shallowRef<KeyEntry[]>([])
const scanning = ref(false)
const paused = ref(false)
const totalSize = ref(0)
const scannedCount = ref(0)
const minSizeKB = ref(0)
const sortAsc = ref(false)
const scanAbort = ref<AbortController | null>(null)
const SCAN_COUNT = 200
/** How often (ms) to flush buffered scan results into the reactive ref */
const FLUSH_INTERVAL = 300
const sortedEntries = computed(() => {
const filtered = minSizeKB.value > 0
? entries.value.filter(e => e.size >= minSizeKB.value * 1024)
@@ -31,10 +34,11 @@ const sortedEntries = computed(() => {
const totalFiltered = computed(() => sortedEntries.value.reduce((s, e) => s + e.size, 0))
function formatSize(bytes: number): string {
if (bytes === 0) return '0 B'
const units = ['B', 'KB', 'MB', 'GB']
if (!Number.isFinite(bytes) || bytes <= 0) return '0 B'
const units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB']
const i = Math.floor(Math.log(bytes) / Math.log(1024))
return (bytes / Math.pow(1024, Math.floor(i))).toFixed(i > 0 ? 1 : 0) + ' ' + units[Math.min(i, units.length - 1)]
const idx = Math.min(i, units.length - 1)
return (bytes / Math.pow(1024, idx)).toFixed(idx > 0 ? 1 : 0) + ' ' + units[idx]
}
function formatType(type: string): string {
@@ -53,13 +57,24 @@ async function sleep(ms: number) {
async function startScan() {
if (!connStore.activeId) return
entries.value = []
totalSize.value = 0
scannedCount.value = 0
scanning.value = true
paused.value = false
const controller = new AbortController()
scanAbort.value = controller
// Non-reactive buffer: scan loop pushes here, timer flushes to entries.value
const buffer: KeyEntry[] = []
let flushTimer: ReturnType<typeof setInterval> | null = null
function flush() {
if (buffer.length === 0) return
const chunk = buffer.splice(0)
entries.value = [...entries.value, ...chunk]
}
flushTimer = setInterval(flush, FLUSH_INTERVAL)
try {
let cursor = '0'
do {
@@ -69,30 +84,23 @@ async function startScan() {
if (controller.signal.aborted) break
}
const result = await window.electronAPI.redis.scanKeys(
connStore.activeId, cursor, '*', 100
connStore.activeId, cursor, '*', SCAN_COUNT
)
cursor = result.cursor
if (controller.signal.aborted || result.keys.length === 0) continue
for (const key of result.keys) {
if (controller.signal.aborted) break
try {
const [type, size] = await Promise.all([
window.electronAPI.redis.getKeyType(connStore.activeId, key),
window.electronAPI.redis.memoryUsage(connStore.activeId, key),
])
const sz = size ?? 0
entries.value.push({ key, size: sz, type })
totalSize.value += sz
scannedCount.value++
} catch {
// skip keys that error
}
await sleep(10) // throttle to avoid overwhelming Redis
}
const batch = await window.electronAPI.redis.batchMemoryUsage(
connStore.activeId, result.keys
)
if (controller.signal.aborted) break
buffer.push(...batch)
scannedCount.value += batch.length
} while (cursor !== '0' && !controller.signal.aborted)
} catch (err: any) {
console.error('Scan error:', err)
} finally {
if (flushTimer) clearInterval(flushTimer)
flush() // final flush: push any remaining buffered entries
scanning.value = false
paused.value = false
scanAbort.value = null
+2
View File
@@ -147,6 +147,8 @@ body {
--el-table-text-color: var(--text-primary);
--el-table-header-text-color: var(--text-secondary);
--el-table-border-color: var(--border-color);
--el-fill-color-lighter: var(--bg-secondary);
--el-table-current-row-bg-color: var(--bg-card-active);
}
/* Dialog */