From d280f40885fdee1a642a334ab00a2f1933391c06 Mon Sep 17 00:00:00 2001 From: Brian Gough Date: Fri, 6 Jun 2025 11:51:02 +0100 Subject: [PATCH] Merge pull request #26116 from overleaf/bg-history-redis-show-buffer add script to display redis buffer for a given history ID GitOrigin-RevId: 71c2e79480c0873d30801ed3c13aa9a7fc7873f6 --- .../history-v1/storage/scripts/show_buffer.js | 117 ++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 services/history-v1/storage/scripts/show_buffer.js diff --git a/services/history-v1/storage/scripts/show_buffer.js b/services/history-v1/storage/scripts/show_buffer.js new file mode 100644 index 0000000000..1d80ee227d --- /dev/null +++ b/services/history-v1/storage/scripts/show_buffer.js @@ -0,0 +1,117 @@ +#!/usr/bin/env node +// @ts-check + +const { rclientHistory: rclient } = require('../lib/redis') +const { keySchema } = require('../lib/chunk_store/redis') +const commandLineArgs = require('command-line-args') + +const optionDefinitions = [ + { name: 'historyId', type: String, defaultOption: true }, +] + +// Column width for key display alignment; can be overridden with COL_WIDTH env variable +const COLUMN_WIDTH = process.env.COL_WIDTH + ? parseInt(process.env.COL_WIDTH, 10) + : 45 + +let options +try { + options = commandLineArgs(optionDefinitions) +} catch (e) { + console.error( + 'Error parsing command line arguments:', + e instanceof Error ? e.message : String(e) + ) + console.error('Usage: ./show_buffer.js ') + process.exit(1) +} + +const { historyId } = options + +if (!historyId) { + console.error('Usage: ./show_buffer.js ') + process.exit(1) +} + +function format(str, indent = COLUMN_WIDTH + 2) { + const lines = str.split('\n') + for (let i = 1; i < lines.length; i++) { + lines[i] = ' '.repeat(indent) + lines[i] + } + return lines.join('\n') +} + +async function displayKeyValue( + rclient, + key, + { parseJson = false, formatDate = false } = {} +) { + const value = await rclient.get(key) + let displayValue = '(nil)' + if (value) { + if (parseJson) { + try { + displayValue = format(JSON.stringify(JSON.parse(value), null, 2)) + } catch (e) { + displayValue = ` Raw value: ${value}` + } + } else if (formatDate) { + const ts = parseInt(value, 10) + displayValue = `${new Date(ts).toISOString()} (${value})` + } else { + displayValue = value + } + } + console.log(`${key.padStart(COLUMN_WIDTH)}: ${displayValue}`) +} + +async function displayBuffer(projectId) { + console.log(`Buffer for history ID: ${projectId}`) + console.log('--------------------------------------------------') + + try { + const headKey = keySchema.head({ projectId }) + const headVersionKey = keySchema.headVersion({ projectId }) + const persistedVersionKey = keySchema.persistedVersion({ projectId }) + const expireTimeKey = keySchema.expireTime({ projectId }) + const persistTimeKey = keySchema.persistTime({ projectId }) + const changesKey = keySchema.changes({ projectId }) + + await displayKeyValue(rclient, headKey, { parseJson: true }) + await displayKeyValue(rclient, headVersionKey) + await displayKeyValue(rclient, persistedVersionKey) + await displayKeyValue(rclient, expireTimeKey, { formatDate: true }) + await displayKeyValue(rclient, persistTimeKey, { formatDate: true }) + + const changesList = await rclient.lrange(changesKey, 0, -1) + + // 6. changes + let changesListDisplay = '(nil)' + if (changesList) { + changesListDisplay = changesList.length + ? format( + changesList + .map((change, index) => `[${index}]: ${change}`) + .join('\n') + ) + : '(empty list)' + } + console.log(`${changesKey.padStart(COLUMN_WIDTH)}: ${changesListDisplay}`) + } catch (error) { + console.error('Error fetching data from Redis:', error) + throw error + } +} + +;(async () => { + let errorOccurred = false + try { + await displayBuffer(historyId) + } catch (error) { + errorOccurred = true + } finally { + rclient.quit(() => { + process.exit(errorOccurred ? 1 : 0) + }) + } +})()