From 5477d0a7fa2ab4623fbf7b3bc2b4997011f26547 Mon Sep 17 00:00:00 2001 From: Henry <50559854+Henry8192@users.noreply.github.com> Date: Sat, 30 Nov 2024 20:05:43 -0500 Subject: [PATCH 01/13] snapshot --- src/contexts/StateContextProvider.tsx | 12 +++++++++++- src/contexts/UrlContextProvider.tsx | 20 +++++++++++++------- src/typings/url.ts | 2 ++ 3 files changed, 26 insertions(+), 8 deletions(-) diff --git a/src/contexts/StateContextProvider.tsx b/src/contexts/StateContextProvider.tsx index 996cb226..9bc0bea9 100644 --- a/src/contexts/StateContextProvider.tsx +++ b/src/contexts/StateContextProvider.tsx @@ -243,7 +243,7 @@ const updateUrlIfEventOnPage = ( // eslint-disable-next-line max-lines-per-function, max-statements const StateContextProvider = ({children}: StateContextProviderProps) => { const {postPopUp} = useContext(NotificationContext); - const {filePath, logEventNum} = useContext(UrlContext); + const {filePath, logEventNum, timestamp} = useContext(UrlContext); // States const [exportProgress, setExportProgress] = @@ -462,6 +462,16 @@ const StateContextProvider = ({children}: StateContextProviderProps) => { logEventNumRef.current = logEventNum; }, [logEventNum]); + useEffect(() => { + if (null !== timestamp) { + const newLogEventIdx = getLogEventIndexByTimestamp(timestamp); + if (-1 !== newLogEventIdx) { + updateWindowUrlHashParams({timestamp: null, logEventNum: newLogEventIdx + 1}); + } + updateWindowUrlHashParams({timestamp: null}); + } + }, [timestamp]); + // Synchronize `pageNumRef` with `pageNum`. useEffect(() => { pageNumRef.current = pageNum; diff --git a/src/contexts/UrlContextProvider.tsx b/src/contexts/UrlContextProvider.tsx index 5862aa9d..5b4b3cd2 100644 --- a/src/contexts/UrlContextProvider.tsx +++ b/src/contexts/UrlContextProvider.tsx @@ -31,6 +31,7 @@ const URL_SEARCH_PARAMS_DEFAULT = Object.freeze({ */ const URL_HASH_PARAMS_DEFAULT = Object.freeze({ [HASH_PARAM_NAMES.LOG_EVENT_NUM]: null, + [HASH_PARAM_NAMES.TIMESTAMP]: null, }); /** @@ -204,13 +205,18 @@ const getWindowUrlHashParams = () => { ); const hashParams = new URLSearchParams(window.location.hash.substring(1)); - const logEventNum = hashParams.get(HASH_PARAM_NAMES.LOG_EVENT_NUM); - if (null !== logEventNum) { - const parsed = Number(logEventNum); - urlHashParams[HASH_PARAM_NAMES.LOG_EVENT_NUM] = Number.isNaN(parsed) ? - null : - parsed; - } + const checkAndSetHashParam = (hashParamName: string) => { + const hashParam = hashParams.get(hashParamName); + if (null !== hashParam) { + const parsed = Number(hashParam); + urlHashParams[HASH_PARAM_NAMES.LOG_EVENT_NUM] = Number.isNaN(parsed) ? + null : + parsed; + } + }; + + checkAndSetHashParam(HASH_PARAM_NAMES.LOG_EVENT_NUM); + checkAndSetHashParam(HASH_PARAM_NAMES.TIMESTAMP); return urlHashParams; }; diff --git a/src/typings/url.ts b/src/typings/url.ts index 0aabb157..063a3936 100644 --- a/src/typings/url.ts +++ b/src/typings/url.ts @@ -7,6 +7,7 @@ enum SEARCH_PARAM_NAMES { enum HASH_PARAM_NAMES { LOG_EVENT_NUM = "logEventNum", + TIMESTAMP = "timestamp", } interface UrlSearchParams { @@ -15,6 +16,7 @@ interface UrlSearchParams { interface UrlHashParams { logEventNum: number, + timestamp: number, } type UrlSearchParamUpdatesType = { From 4a8aeb7d34d11ba3b8365b88ab5bcacc8464d1dd Mon Sep 17 00:00:00 2001 From: Henry8192 <50559854+Henry8192@users.noreply.github.com> Date: Thu, 5 Dec 2024 17:30:53 -0500 Subject: [PATCH 02/13] draft without implementing getLogEventIdxByTimestamp --- src/contexts/StateContextProvider.tsx | 7 ++++- src/services/LogFileManager/index.ts | 40 ++++++++++++++------------- src/services/MainWorker.ts | 8 ++++++ src/typings/decoders.ts | 2 ++ 4 files changed, 37 insertions(+), 20 deletions(-) diff --git a/src/contexts/StateContextProvider.tsx b/src/contexts/StateContextProvider.tsx index 9bc0bea9..da603d28 100644 --- a/src/contexts/StateContextProvider.tsx +++ b/src/contexts/StateContextProvider.tsx @@ -463,12 +463,16 @@ const StateContextProvider = ({children}: StateContextProviderProps) => { }, [logEventNum]); useEffect(() => { - if (null !== timestamp) { + if (null !== mainWorkerRef.current) { + + + /* const newLogEventIdx = getLogEventIndexByTimestamp(timestamp); if (-1 !== newLogEventIdx) { updateWindowUrlHashParams({timestamp: null, logEventNum: newLogEventIdx + 1}); } updateWindowUrlHashParams({timestamp: null}); + */ } }, [timestamp]); @@ -529,6 +533,7 @@ const StateContextProvider = ({children}: StateContextProviderProps) => { return; } + // need to check if timestamp property is present in URLHashParams let cursor: CursorType = {code: CURSOR_CODE.LAST_EVENT, args: null}; if (URL_HASH_PARAMS_DEFAULT.logEventNum !== logEventNumRef.current) { cursor = { diff --git a/src/services/LogFileManager/index.ts b/src/services/LogFileManager/index.ts index 10566999..9a8a9023 100644 --- a/src/services/LogFileManager/index.ts +++ b/src/services/LogFileManager/index.ts @@ -411,26 +411,28 @@ class LogFileManager { */ #getCursorData (cursor: CursorType, numActiveEvents: number): CursorData { const {code, args} = cursor; - switch (code) { - case CURSOR_CODE.PAGE_NUM: - return getPageNumCursorData( - args.pageNum, - args.eventPositionOnPage, - numActiveEvents, - this.#pageSize, - ); - case CURSOR_CODE.LAST_EVENT: - return getLastEventCursorData(numActiveEvents, this.#pageSize); - case CURSOR_CODE.EVENT_NUM: - return getEventNumCursorData( - args.eventNum, - numActiveEvents, - this.#pageSize, - this.#decoder.getFilteredLogEventMap(), - ); - default: - throw new Error(`Unsupported cursor type: ${code}`); + let eventNum: number = 0; + if (CURSOR_CODE.PAGE_NUM === code) { + return getPageNumCursorData( + args.pageNum, + args.eventPositionOnPage, + numActiveEvents, + this.#pageSize, + ); + } else if (CURSOR_CODE.LAST_EVENT === code) { + return getLastEventCursorData(numActiveEvents, this.#pageSize); + } else if (CURSOR_CODE.EVENT_NUM === code) { + ({eventNum} = args); + } else { + eventNum = this.#decoder.getLogEventIdxByTimestamp(args.timestamp) + 1; } + + return getEventNumCursorData( + eventNum, + numActiveEvents, + this.#pageSize, + this.#decoder.getFilteredLogEventMap(), + ); } } diff --git a/src/services/MainWorker.ts b/src/services/MainWorker.ts index d7ce638d..da7ae7df 100644 --- a/src/services/MainWorker.ts +++ b/src/services/MainWorker.ts @@ -137,6 +137,14 @@ onmessage = async (ev: MessageEvent) => { args.isCaseSensitive ); break; + + /* + case WORKER_REQ_CODE.QUERY_TIMESTAMP: + if (null === LOG_FILE_MANAGER) { + throw new Error("Log file manager hasn't been initialized"); + } + LOG_FILE_MANAGER.getLogEventIndexByTimestamp(timestamp); + */ default: console.error(`Unexpected ev.data: ${JSON.stringify(ev.data)}`); break; diff --git a/src/typings/decoders.ts b/src/typings/decoders.ts index 1eaa0cb5..392d9c62 100644 --- a/src/typings/decoders.ts +++ b/src/typings/decoders.ts @@ -98,6 +98,8 @@ interface Decoder { endIdx: number, useFilter: boolean ): Nullable; + + getLogEventIdxByTimestamp(timestamp: number): number; } export type { From 7f5cc7f8f60476d03924a1af6146043b252de650 Mon Sep 17 00:00:00 2001 From: Henry <50559854+Henry8192@users.noreply.github.com> Date: Thu, 5 Dec 2024 19:21:13 -0500 Subject: [PATCH 03/13] patch --- src/contexts/StateContextProvider.tsx | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/src/contexts/StateContextProvider.tsx b/src/contexts/StateContextProvider.tsx index da603d28..c5a25d96 100644 --- a/src/contexts/StateContextProvider.tsx +++ b/src/contexts/StateContextProvider.tsx @@ -269,6 +269,7 @@ const StateContextProvider = ({children}: StateContextProviderProps) => { const mainWorkerRef = useRef(null); const numPagesRef = useRef(numPages); const pageNumRef = useRef(pageNum); + const timestampRef = useRef(timestamp); const uiStateRef = useRef(uiState); const handleMainWorkerResp = useCallback((ev: MessageEvent) => { @@ -458,21 +459,20 @@ const StateContextProvider = ({children}: StateContextProviderProps) => { }, []); // Synchronize `logEventNumRef` with `logEventNum`. + // FIXME: is this necessary? Can we integrate it with the [numEvents, logEventNum] useEffect? useEffect(() => { logEventNumRef.current = logEventNum; }, [logEventNum]); useEffect(() => { - if (null !== mainWorkerRef.current) { - + timestampRef.current = timestamp; - /* - const newLogEventIdx = getLogEventIndexByTimestamp(timestamp); - if (-1 !== newLogEventIdx) { - updateWindowUrlHashParams({timestamp: null, logEventNum: newLogEventIdx + 1}); - } + if (null !== mainWorkerRef.current && null !== timestamp) { + loadPageByCursor(mainWorkerRef.current, { + code: CURSOR_CODE.TIMESTAMP, + args: {timestamp: timestamp}, + }); updateWindowUrlHashParams({timestamp: null}); - */ } }, [timestamp]); @@ -533,7 +533,6 @@ const StateContextProvider = ({children}: StateContextProviderProps) => { return; } - // need to check if timestamp property is present in URLHashParams let cursor: CursorType = {code: CURSOR_CODE.LAST_EVENT, args: null}; if (URL_HASH_PARAMS_DEFAULT.logEventNum !== logEventNumRef.current) { cursor = { @@ -541,6 +540,12 @@ const StateContextProvider = ({children}: StateContextProviderProps) => { args: {eventNum: logEventNumRef.current}, }; } + if (URL_HASH_PARAMS_DEFAULT.timestamp !== timestampRef.current) { + cursor = { + code: CURSOR_CODE.TIMESTAMP, + args: {timestamp: timestampRef.current}, + }; + } loadFile(filePath, cursor); }, [ filePath, From 54ef03cf13b54844844ae835c2dadfa2066464d6 Mon Sep 17 00:00:00 2001 From: Henry8192 <50559854+Henry8192@users.noreply.github.com> Date: Mon, 9 Dec 2024 13:47:26 -0500 Subject: [PATCH 04/13] implementing JSONL getLogEventIdxByTimestamp, fix timestamp type to bigint --- src/services/decoders/JsonlDecoder/index.ts | 26 +++++++++++++++++++-- src/typings/decoders.ts | 9 ++++++- src/typings/logs.ts | 2 +- 3 files changed, 33 insertions(+), 4 deletions(-) diff --git a/src/services/decoders/JsonlDecoder/index.ts b/src/services/decoders/JsonlDecoder/index.ts index 1ff3f4d7..5ea0f626 100644 --- a/src/services/decoders/JsonlDecoder/index.ts +++ b/src/services/decoders/JsonlDecoder/index.ts @@ -122,6 +122,28 @@ class JsonlDecoder implements Decoder { return results; } + getLogEventIdxByTimestamp (timestamp: bigint): number { + let low = 0; + let high = this.#logEvents.length - 1; + let result = -1; + + while (low <= high) { + const mid = Math.floor((low + high) / 2); + const midTimestamp = BigInt(this.#logEvents[mid].timestamp.valueOf()); + + if (midTimestamp === timestamp) { + result = mid; + low = mid + 1; + } else if (midTimestamp < timestamp) { + low = mid + 1; + } else { + high = mid - 1; + } + } + + return result; + } + /** * Parses each line from the data array and buffers it internally. * @@ -214,7 +236,7 @@ class JsonlDecoder implements Decoder { * @return The decoded log event. */ #decodeLogEvent = (logEventIdx: number): DecodeResult => { - let timestamp: number; + let timestamp: bigint; let message: string; let logLevel: LOG_LEVEL; @@ -231,7 +253,7 @@ class JsonlDecoder implements Decoder { const logEvent = this.#logEvents[logEventIdx] as LogEvent; logLevel = logEvent.level; message = this.#formatter.formatLogEvent(logEvent); - timestamp = logEvent.timestamp.valueOf(); + timestamp = BigInt(logEvent.timestamp.valueOf()); } return [ diff --git a/src/typings/decoders.ts b/src/typings/decoders.ts index 392d9c62..b8c7afd5 100644 --- a/src/typings/decoders.ts +++ b/src/typings/decoders.ts @@ -99,7 +99,14 @@ interface Decoder { useFilter: boolean ): Nullable; - getLogEventIdxByTimestamp(timestamp: number): number; + /** + * Retrieves the last index of the log event that matches the given timestamp. + * If no such log event exists, returns -1. + * + * @param timestamp + * @return + */ + getLogEventIdxByTimestamp(timestamp: bigint): number; } export type { diff --git a/src/typings/logs.ts b/src/typings/logs.ts index e5cd333f..93d8c2b7 100644 --- a/src/typings/logs.ts +++ b/src/typings/logs.ts @@ -43,7 +43,7 @@ const LOG_LEVEL_VALUES = Object.freeze( const MAX_LOG_LEVEL = Math.max(...LOG_LEVEL_VALUES); -const INVALID_TIMESTAMP_VALUE = 0; +const INVALID_TIMESTAMP_VALUE = 0n; export type { From 6c2b45af0ebc87c9764c7af11d0ddf99342daf0a Mon Sep 17 00:00:00 2001 From: Henry <50559854+Henry8192@users.noreply.github.com> Date: Wed, 11 Dec 2024 16:36:05 -0500 Subject: [PATCH 05/13] fix JSONL getEventIdxByTimestamp + timestamp hash param not cleared --- src/contexts/UrlContextProvider.tsx | 4 +++- src/services/decoders/JsonlDecoder/index.ts | 4 +++- src/typings/worker.ts | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/contexts/UrlContextProvider.tsx b/src/contexts/UrlContextProvider.tsx index 5b4b3cd2..f7862382 100644 --- a/src/contexts/UrlContextProvider.tsx +++ b/src/contexts/UrlContextProvider.tsx @@ -209,7 +209,9 @@ const getWindowUrlHashParams = () => { const hashParam = hashParams.get(hashParamName); if (null !== hashParam) { const parsed = Number(hashParam); - urlHashParams[HASH_PARAM_NAMES.LOG_EVENT_NUM] = Number.isNaN(parsed) ? + + // FIXME: hashParamName type + urlHashParams[hashParamName] = Number.isNaN(parsed) ? null : parsed; } diff --git a/src/services/decoders/JsonlDecoder/index.ts b/src/services/decoders/JsonlDecoder/index.ts index 5ea0f626..a1baa1bd 100644 --- a/src/services/decoders/JsonlDecoder/index.ts +++ b/src/services/decoders/JsonlDecoder/index.ts @@ -131,7 +131,9 @@ class JsonlDecoder implements Decoder { const mid = Math.floor((low + high) / 2); const midTimestamp = BigInt(this.#logEvents[mid].timestamp.valueOf()); - if (midTimestamp === timestamp) { + console.log(`midTimestamp: ${midTimestamp}, mid: ${mid}, low: ${low}, high: ${high}`); + if (midTimestamp == timestamp) { + console.error(`result recorded: ${mid}`); result = mid; low = mid + 1; } else if (midTimestamp < timestamp) { diff --git a/src/typings/worker.ts b/src/typings/worker.ts index 2ccbe1a5..b33da6b0 100644 --- a/src/typings/worker.ts +++ b/src/typings/worker.ts @@ -40,7 +40,7 @@ enum CURSOR_CODE { type CursorArgMap = { [CURSOR_CODE.LAST_EVENT]: null; [CURSOR_CODE.EVENT_NUM]: { eventNum: number }; - [CURSOR_CODE.TIMESTAMP]: { timestamp: number }; + [CURSOR_CODE.TIMESTAMP]: { timestamp: bigint }; [CURSOR_CODE.PAGE_NUM]: { pageNum: number, eventPositionOnPage: EVENT_POSITION_ON_PAGE }; }; From 255f3e357f53829ae38d15cbbd0a57366e2c438f Mon Sep 17 00:00:00 2001 From: Henry8192 <50559854+Henry8192@users.noreply.github.com> Date: Wed, 11 Dec 2024 21:39:52 -0500 Subject: [PATCH 06/13] revert timestamp type back to Number; Only keeping bigint for DecodeResult timestamp --- src/contexts/UrlContextProvider.tsx | 4 +--- src/services/decoders/ClpIrDecoder.ts | 4 ++++ src/services/decoders/JsonlDecoder/index.ts | 9 ++++----- src/typings/decoders.ts | 2 +- src/typings/worker.ts | 2 +- 5 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/contexts/UrlContextProvider.tsx b/src/contexts/UrlContextProvider.tsx index f7862382..cba482d7 100644 --- a/src/contexts/UrlContextProvider.tsx +++ b/src/contexts/UrlContextProvider.tsx @@ -205,12 +205,10 @@ const getWindowUrlHashParams = () => { ); const hashParams = new URLSearchParams(window.location.hash.substring(1)); - const checkAndSetHashParam = (hashParamName: string) => { + const checkAndSetHashParam = (hashParamName: keyof UrlHashParams) => { const hashParam = hashParams.get(hashParamName); if (null !== hashParam) { const parsed = Number(hashParam); - - // FIXME: hashParamName type urlHashParams[hashParamName] = Number.isNaN(parsed) ? null : parsed; diff --git a/src/services/decoders/ClpIrDecoder.ts b/src/services/decoders/ClpIrDecoder.ts index 305ad740..7d8758a9 100644 --- a/src/services/decoders/ClpIrDecoder.ts +++ b/src/services/decoders/ClpIrDecoder.ts @@ -77,6 +77,10 @@ class ClpIrDecoder implements Decoder { return this.#streamReader.getFilteredLogEventMap(); } + getLogEventIdxByTimestamp (timestamp: number): number { + return this.#streamReader.getLogEventIdxByTimestamp(timestamp); + } + setLogLevelFilter (logLevelFilter: LogLevelFilter): boolean { this.#streamReader.filterLogEvents(logLevelFilter); diff --git a/src/services/decoders/JsonlDecoder/index.ts b/src/services/decoders/JsonlDecoder/index.ts index a1baa1bd..b5bcfffe 100644 --- a/src/services/decoders/JsonlDecoder/index.ts +++ b/src/services/decoders/JsonlDecoder/index.ts @@ -122,18 +122,17 @@ class JsonlDecoder implements Decoder { return results; } - getLogEventIdxByTimestamp (timestamp: bigint): number { + getLogEventIdxByTimestamp (timestamp: number): number { let low = 0; let high = this.#logEvents.length - 1; let result = -1; while (low <= high) { const mid = Math.floor((low + high) / 2); - const midTimestamp = BigInt(this.#logEvents[mid].timestamp.valueOf()); - console.log(`midTimestamp: ${midTimestamp}, mid: ${mid}, low: ${low}, high: ${high}`); - if (midTimestamp == timestamp) { - console.error(`result recorded: ${mid}`); + // @ts-expect-error TS2532: Object is possibly 'undefined'. + const midTimestamp = this.#logEvents[mid].timestamp.valueOf(); + if (midTimestamp === timestamp) { result = mid; low = mid + 1; } else if (midTimestamp < timestamp) { diff --git a/src/typings/decoders.ts b/src/typings/decoders.ts index b8c7afd5..2f511551 100644 --- a/src/typings/decoders.ts +++ b/src/typings/decoders.ts @@ -106,7 +106,7 @@ interface Decoder { * @param timestamp * @return */ - getLogEventIdxByTimestamp(timestamp: bigint): number; + getLogEventIdxByTimestamp(timestamp: number): number; } export type { diff --git a/src/typings/worker.ts b/src/typings/worker.ts index b33da6b0..2ccbe1a5 100644 --- a/src/typings/worker.ts +++ b/src/typings/worker.ts @@ -40,7 +40,7 @@ enum CURSOR_CODE { type CursorArgMap = { [CURSOR_CODE.LAST_EVENT]: null; [CURSOR_CODE.EVENT_NUM]: { eventNum: number }; - [CURSOR_CODE.TIMESTAMP]: { timestamp: bigint }; + [CURSOR_CODE.TIMESTAMP]: { timestamp: number }; [CURSOR_CODE.PAGE_NUM]: { pageNum: number, eventPositionOnPage: EVENT_POSITION_ON_PAGE }; }; From 3972319dc0a7c690de66a216fb68e67981b5d600 Mon Sep 17 00:00:00 2001 From: Henry8192 <50559854+Henry8192@users.noreply.github.com> Date: Thu, 19 Dec 2024 15:49:02 -0500 Subject: [PATCH 07/13] address changes from code review --- src/contexts/StateContextProvider.tsx | 24 ++++++++++----------- src/contexts/UrlContextProvider.tsx | 14 ++++++------ src/services/MainWorker.ts | 8 ------- src/services/decoders/JsonlDecoder/index.ts | 13 +++++------ src/typings/decoders.ts | 5 ++--- 5 files changed, 28 insertions(+), 36 deletions(-) diff --git a/src/contexts/StateContextProvider.tsx b/src/contexts/StateContextProvider.tsx index c5a25d96..25c9794e 100644 --- a/src/contexts/StateContextProvider.tsx +++ b/src/contexts/StateContextProvider.tsx @@ -459,21 +459,13 @@ const StateContextProvider = ({children}: StateContextProviderProps) => { }, []); // Synchronize `logEventNumRef` with `logEventNum`. - // FIXME: is this necessary? Can we integrate it with the [numEvents, logEventNum] useEffect? useEffect(() => { logEventNumRef.current = logEventNum; }, [logEventNum]); + // Synchronize `timestampRef` with `timestamp`. useEffect(() => { timestampRef.current = timestamp; - - if (null !== mainWorkerRef.current && null !== timestamp) { - loadPageByCursor(mainWorkerRef.current, { - code: CURSOR_CODE.TIMESTAMP, - args: {timestamp: timestamp}, - }); - updateWindowUrlHashParams({timestamp: null}); - } }, [timestamp]); // Synchronize `pageNumRef` with `pageNum`. @@ -495,13 +487,21 @@ const StateContextProvider = ({children}: StateContextProviderProps) => { } }, [uiState]); - // On `logEventNum` update, clamp it then switch page if necessary or simply update the URL. useEffect(() => { - if (null === mainWorkerRef.current) { + if (null === mainWorkerRef.current || null === timestamp) { return; } - if (URL_HASH_PARAMS_DEFAULT.logEventNum === logEventNum) { + loadPageByCursor(mainWorkerRef.current, { + code: CURSOR_CODE.TIMESTAMP, + args: {timestamp: timestamp}, + }); + updateWindowUrlHashParams({timestamp: null}); + }, [timestamp]); + + // On `logEventNum` update, clamp it then switch page if necessary or simply update the URL. + useEffect(() => { + if (null === mainWorkerRef.current || URL_HASH_PARAMS_DEFAULT.logEventNum === logEventNum) { return; } diff --git a/src/contexts/UrlContextProvider.tsx b/src/contexts/UrlContextProvider.tsx index cba482d7..0052b0e8 100644 --- a/src/contexts/UrlContextProvider.tsx +++ b/src/contexts/UrlContextProvider.tsx @@ -205,18 +205,18 @@ const getWindowUrlHashParams = () => { ); const hashParams = new URLSearchParams(window.location.hash.substring(1)); - const checkAndSetHashParam = (hashParamName: keyof UrlHashParams) => { - const hashParam = hashParams.get(hashParamName); + const numberHashParamNames = [HASH_PARAM_NAMES.LOG_EVENT_NUM, + HASH_PARAM_NAMES.TIMESTAMP]; + + for (const paramName of numberHashParamNames) { + const hashParam = hashParams.get(paramName); if (null !== hashParam) { const parsed = Number(hashParam); - urlHashParams[hashParamName] = Number.isNaN(parsed) ? + urlHashParams[paramName] = Number.isNaN(parsed) ? null : parsed; } - }; - - checkAndSetHashParam(HASH_PARAM_NAMES.LOG_EVENT_NUM); - checkAndSetHashParam(HASH_PARAM_NAMES.TIMESTAMP); + } return urlHashParams; }; diff --git a/src/services/MainWorker.ts b/src/services/MainWorker.ts index da7ae7df..d7ce638d 100644 --- a/src/services/MainWorker.ts +++ b/src/services/MainWorker.ts @@ -137,14 +137,6 @@ onmessage = async (ev: MessageEvent) => { args.isCaseSensitive ); break; - - /* - case WORKER_REQ_CODE.QUERY_TIMESTAMP: - if (null === LOG_FILE_MANAGER) { - throw new Error("Log file manager hasn't been initialized"); - } - LOG_FILE_MANAGER.getLogEventIndexByTimestamp(timestamp); - */ default: console.error(`Unexpected ev.data: ${JSON.stringify(ev.data)}`); break; diff --git a/src/services/decoders/JsonlDecoder/index.ts b/src/services/decoders/JsonlDecoder/index.ts index b5bcfffe..60ee7b75 100644 --- a/src/services/decoders/JsonlDecoder/index.ts +++ b/src/services/decoders/JsonlDecoder/index.ts @@ -25,6 +25,8 @@ import { } from "./utils"; +const INVALID_LOG_EVENT_IDX = -1; + /** * A decoder for JSONL (JSON lines) files that contain log events. See `DecoderOptions` for * properties that are specific to log events (compared to generic JSON records). @@ -125,15 +127,14 @@ class JsonlDecoder implements Decoder { getLogEventIdxByTimestamp (timestamp: number): number { let low = 0; let high = this.#logEvents.length - 1; - let result = -1; + let lastFoundLogEventIdx = INVALID_LOG_EVENT_IDX; while (low <= high) { const mid = Math.floor((low + high) / 2); - - // @ts-expect-error TS2532: Object is possibly 'undefined'. - const midTimestamp = this.#logEvents[mid].timestamp.valueOf(); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const midTimestamp = this.#logEvents[mid]!.timestamp.valueOf(); if (midTimestamp === timestamp) { - result = mid; + lastFoundLogEventIdx = mid; low = mid + 1; } else if (midTimestamp < timestamp) { low = mid + 1; @@ -142,7 +143,7 @@ class JsonlDecoder implements Decoder { } } - return result; + return lastFoundLogEventIdx; } /** diff --git a/src/typings/decoders.ts b/src/typings/decoders.ts index 2f511551..073a3594 100644 --- a/src/typings/decoders.ts +++ b/src/typings/decoders.ts @@ -100,11 +100,10 @@ interface Decoder { ): Nullable; /** - * Retrieves the last index of the log event that matches the given timestamp. - * If no such log event exists, returns -1. + * Finds the index of the last log event that matches the specified timestamp. * * @param timestamp - * @return + * @return The index of the matching log event, or -1 if no match is found. */ getLogEventIdxByTimestamp(timestamp: number): number; } From f1f93f73f435d25e6dea10f17017348a943e6361 Mon Sep 17 00:00:00 2001 From: Henry <50559854+Henry8192@users.noreply.github.com> Date: Mon, 10 Feb 2025 22:29:31 +0800 Subject: [PATCH 08/13] rename getLogEventIdxByTimestamp to getLogEventIdxByTimestamp --- src/services/LogFileManager/index.ts | 2 +- src/services/decoders/ClpIrDecoder.ts | 4 ++-- src/services/decoders/JsonlDecoder/index.ts | 2 +- src/typings/decoders.ts | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/services/LogFileManager/index.ts b/src/services/LogFileManager/index.ts index 668685be..d268a9ac 100644 --- a/src/services/LogFileManager/index.ts +++ b/src/services/LogFileManager/index.ts @@ -446,7 +446,7 @@ class LogFileManager { } else if (CURSOR_CODE.EVENT_NUM === code) { ({eventNum} = args); } else { - eventNum = this.#decoder.getLogEventIdxByTimestamp(args.timestamp) + 1; + eventNum = this.#decoder.findNearestLogEventByTimestamp(args.timestamp) + 1; } return getEventNumCursorData( diff --git a/src/services/decoders/ClpIrDecoder.ts b/src/services/decoders/ClpIrDecoder.ts index 3dea5468..2922bc16 100644 --- a/src/services/decoders/ClpIrDecoder.ts +++ b/src/services/decoders/ClpIrDecoder.ts @@ -77,8 +77,8 @@ class ClpIrDecoder implements Decoder { return this.#streamReader.getFilteredLogEventMap(); } - getLogEventIdxByTimestamp (timestamp: number): number { - return this.#streamReader.getLogEventIdxByTimestamp(timestamp); + findNearestLogEventByTimestamp (timestamp: number): number { + return this.#streamReader.findNearestLogEventByTimestamp(timestamp); } setLogLevelFilter (logLevelFilter: LogLevelFilter): boolean { diff --git a/src/services/decoders/JsonlDecoder/index.ts b/src/services/decoders/JsonlDecoder/index.ts index 60ee7b75..f4b458a2 100644 --- a/src/services/decoders/JsonlDecoder/index.ts +++ b/src/services/decoders/JsonlDecoder/index.ts @@ -124,7 +124,7 @@ class JsonlDecoder implements Decoder { return results; } - getLogEventIdxByTimestamp (timestamp: number): number { + findNearestLogEventByTimestamp (timestamp: number): number { let low = 0; let high = this.#logEvents.length - 1; let lastFoundLogEventIdx = INVALID_LOG_EVENT_IDX; diff --git a/src/typings/decoders.ts b/src/typings/decoders.ts index 32dca478..a1174121 100644 --- a/src/typings/decoders.ts +++ b/src/typings/decoders.ts @@ -105,7 +105,7 @@ interface Decoder { * @param timestamp * @return The index of the matching log event, or -1 if no match is found. */ - getLogEventIdxByTimestamp(timestamp: number): number; + findNearestLogEventByTimestamp(timestamp: number): number; } export type { From 683350aca4f7a3450c1bd658e5a1ec7c4d895983 Mon Sep 17 00:00:00 2001 From: Henry <50559854+Henry8192@users.noreply.github.com> Date: Tue, 11 Feb 2025 18:15:45 +0800 Subject: [PATCH 09/13] update clp-ffi-js dependency --- package-lock.json | 9 ++++----- package.json | 2 +- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index 71e1c15a..b0efeaa8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,7 @@ "@mui/icons-material": "^6.4.1", "@mui/joy": "^5.0.0-beta.51", "axios": "^1.7.9", - "clp-ffi-js": "^0.3.4", + "clp-ffi-js": "^0.3.5", "dayjs": "^1.11.13", "monaco-editor": "0.50.0", "react": "^19.0.0", @@ -6322,10 +6322,9 @@ } }, "node_modules/clp-ffi-js": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/clp-ffi-js/-/clp-ffi-js-0.3.4.tgz", - "integrity": "sha512-pBRHF5dFpv1P1L6GlKehfMtHjjydA1FmSmBNCkSwyOvUNSgVzOLfwwgozU0SvsNK9oW+yUftW7e6ylPCpWqN/Q==", - "license": "Apache-2.0" + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/clp-ffi-js/-/clp-ffi-js-0.3.5.tgz", + "integrity": "sha512-Ckun6Qu1l0dD/3YMUCLdQr2kxBiTEss/DQtQa1YPWXbHaUxJJpCi44ZyAg9oGnPmEhoXZgW74vtTjxSFp3fJIA==" }, "node_modules/clsx": { "version": "2.1.1", diff --git a/package.json b/package.json index 448c7720..ab37fc2a 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,7 @@ "@mui/icons-material": "^6.4.1", "@mui/joy": "^5.0.0-beta.51", "axios": "^1.7.9", - "clp-ffi-js": "^0.3.4", + "clp-ffi-js": "^0.3.5", "dayjs": "^1.11.13", "monaco-editor": "0.50.0", "react": "^19.0.0", From 0cc364953d61b5a3901b244953d163d6bae2787b Mon Sep 17 00:00:00 2001 From: Henry <50559854+Henry8192@users.noreply.github.com> Date: Thu, 13 Feb 2025 23:33:54 +0800 Subject: [PATCH 10/13] Add docstring for findNearestLogEventByTimestamp; Change function behavior by returning the nearest log event index instead of invalid log event index --- src/services/LogFileManager/index.ts | 1 + src/services/decoders/JsonlDecoder/index.ts | 13 ++++--------- src/typings/decoders.ts | 15 +++++++++++++-- 3 files changed, 18 insertions(+), 11 deletions(-) diff --git a/src/services/LogFileManager/index.ts b/src/services/LogFileManager/index.ts index d268a9ac..f677e1d9 100644 --- a/src/services/LogFileManager/index.ts +++ b/src/services/LogFileManager/index.ts @@ -433,6 +433,7 @@ class LogFileManager { */ #getCursorData (cursor: CursorType, numActiveEvents: number): CursorData { const {code, args} = cursor; + // eslint-disable-next-line no-useless-assignment let eventNum: number = 0; if (CURSOR_CODE.PAGE_NUM === code) { return getPageNumCursorData( diff --git a/src/services/decoders/JsonlDecoder/index.ts b/src/services/decoders/JsonlDecoder/index.ts index f4b458a2..42500823 100644 --- a/src/services/decoders/JsonlDecoder/index.ts +++ b/src/services/decoders/JsonlDecoder/index.ts @@ -25,8 +25,6 @@ import { } from "./utils"; -const INVALID_LOG_EVENT_IDX = -1; - /** * A decoder for JSONL (JSON lines) files that contain log events. See `DecoderOptions` for * properties that are specific to log events (compared to generic JSON records). @@ -127,23 +125,20 @@ class JsonlDecoder implements Decoder { findNearestLogEventByTimestamp (timestamp: number): number { let low = 0; let high = this.#logEvents.length - 1; - let lastFoundLogEventIdx = INVALID_LOG_EVENT_IDX; + let mid = low; while (low <= high) { - const mid = Math.floor((low + high) / 2); + mid = Math.floor((low + high) / 2); // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const midTimestamp = this.#logEvents[mid]!.timestamp.valueOf(); - if (midTimestamp === timestamp) { - lastFoundLogEventIdx = mid; - low = mid + 1; - } else if (midTimestamp < timestamp) { + if (midTimestamp <= timestamp) { low = mid + 1; } else { high = mid - 1; } } - return lastFoundLogEventIdx; + return mid; } /** diff --git a/src/typings/decoders.ts b/src/typings/decoders.ts index a1174121..1cef692b 100644 --- a/src/typings/decoders.ts +++ b/src/typings/decoders.ts @@ -100,10 +100,21 @@ interface Decoder { ): Nullable; /** - * Finds the index of the last log event that matches the specified timestamp. + * Finds the log event, L, where if we assume: + * + * - the collection of log events is sorted in chronological order; + * - and we insert a marker log event, M, with timestamp `timestamp` into the collection (if log + * events with timestamp `timestamp` already exist in the collection, M should be inserted + * after them). + * + * L is the event just before M, if M is not the first event in the collection; otherwise L is + * the event just after M. + * + * NOTE: If the collection of log events isn't in chronological order, this method has undefined + * behaviour. * * @param timestamp - * @return The index of the matching log event, or -1 if no match is found. + * @return The index of the log event L. */ findNearestLogEventByTimestamp(timestamp: number): number; } From a81d57088bdbec9e652d71f893719e1762ca1dae Mon Sep 17 00:00:00 2001 From: Henry <50559854+Henry8192@users.noreply.github.com> Date: Sun, 16 Feb 2025 13:46:16 +0800 Subject: [PATCH 11/13] Fix JSONL findNearestLogEventByTimestamp not always returning the correct value; Correct findNearestLogEventByTimestamp signature. --- src/services/LogFileManager/index.ts | 7 +++++-- src/services/decoders/ClpIrDecoder.ts | 4 ++-- src/services/decoders/JsonlDecoder/index.ts | 10 ++++++---- src/typings/decoders.ts | 2 +- 4 files changed, 14 insertions(+), 9 deletions(-) diff --git a/src/services/LogFileManager/index.ts b/src/services/LogFileManager/index.ts index f677e1d9..edad4499 100644 --- a/src/services/LogFileManager/index.ts +++ b/src/services/LogFileManager/index.ts @@ -433,7 +433,7 @@ class LogFileManager { */ #getCursorData (cursor: CursorType, numActiveEvents: number): CursorData { const {code, args} = cursor; - // eslint-disable-next-line no-useless-assignment + let eventNum: number = 0; if (CURSOR_CODE.PAGE_NUM === code) { return getPageNumCursorData( @@ -447,7 +447,10 @@ class LogFileManager { } else if (CURSOR_CODE.EVENT_NUM === code) { ({eventNum} = args); } else { - eventNum = this.#decoder.findNearestLogEventByTimestamp(args.timestamp) + 1; + const eventIdx = this.#decoder.findNearestLogEventByTimestamp(args.timestamp); + if (null !== eventIdx) { + eventNum = eventIdx + 1; + } } return getEventNumCursorData( diff --git a/src/services/decoders/ClpIrDecoder.ts b/src/services/decoders/ClpIrDecoder.ts index 2922bc16..05ad1d0f 100644 --- a/src/services/decoders/ClpIrDecoder.ts +++ b/src/services/decoders/ClpIrDecoder.ts @@ -77,8 +77,8 @@ class ClpIrDecoder implements Decoder { return this.#streamReader.getFilteredLogEventMap(); } - findNearestLogEventByTimestamp (timestamp: number): number { - return this.#streamReader.findNearestLogEventByTimestamp(timestamp); + findNearestLogEventByTimestamp (timestamp: number): Nullable { + return this.#streamReader.findNearestLogEventByTimestamp(BigInt(timestamp)); } setLogLevelFilter (logLevelFilter: LogLevelFilter): boolean { diff --git a/src/services/decoders/JsonlDecoder/index.ts b/src/services/decoders/JsonlDecoder/index.ts index 42500823..06f5e0e8 100644 --- a/src/services/decoders/JsonlDecoder/index.ts +++ b/src/services/decoders/JsonlDecoder/index.ts @@ -122,13 +122,15 @@ class JsonlDecoder implements Decoder { return results; } - findNearestLogEventByTimestamp (timestamp: number): number { + findNearestLogEventByTimestamp (timestamp: number): Nullable { let low = 0; let high = this.#logEvents.length - 1; - let mid = low; + if (high < low) { + return null; + } while (low <= high) { - mid = Math.floor((low + high) / 2); + const mid = Math.floor((low + high) / 2); // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const midTimestamp = this.#logEvents[mid]!.timestamp.valueOf(); if (midTimestamp <= timestamp) { @@ -138,7 +140,7 @@ class JsonlDecoder implements Decoder { } } - return mid; + return high; } /** diff --git a/src/typings/decoders.ts b/src/typings/decoders.ts index 1cef692b..5064af87 100644 --- a/src/typings/decoders.ts +++ b/src/typings/decoders.ts @@ -116,7 +116,7 @@ interface Decoder { * @param timestamp * @return The index of the log event L. */ - findNearestLogEventByTimestamp(timestamp: number): number; + findNearestLogEventByTimestamp(timestamp: number): Nullable; } export type { From e8e58f543b20e65e00ee46f79bd90ce8ccfcbc3f Mon Sep 17 00:00:00 2001 From: Henry8192 <50559854+Henry8192@users.noreply.github.com> Date: Thu, 20 Feb 2025 16:58:25 -0500 Subject: [PATCH 12/13] fix corner case --- src/services/decoders/JsonlDecoder/index.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/services/decoders/JsonlDecoder/index.ts b/src/services/decoders/JsonlDecoder/index.ts index 06f5e0e8..a1e406d2 100644 --- a/src/services/decoders/JsonlDecoder/index.ts +++ b/src/services/decoders/JsonlDecoder/index.ts @@ -140,6 +140,11 @@ class JsonlDecoder implements Decoder { } } + // corner case: all log events have timestamps >= timestamp + if (0 > high) { + return 0; + } + return high; } From 206b01ff58c9d7ec7bb92a1c7aef36b0c305477c Mon Sep 17 00:00:00 2001 From: Henry8192 <50559854+Henry8192@users.noreply.github.com> Date: Mon, 24 Feb 2025 15:52:11 -0500 Subject: [PATCH 13/13] minor format fixes --- src/contexts/StateContextProvider.tsx | 1 + src/contexts/UrlContextProvider.tsx | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/contexts/StateContextProvider.tsx b/src/contexts/StateContextProvider.tsx index 953030ec..35b3719f 100644 --- a/src/contexts/StateContextProvider.tsx +++ b/src/contexts/StateContextProvider.tsx @@ -485,6 +485,7 @@ const StateContextProvider = ({children}: StateContextProviderProps) => { } }, [uiState]); + // On `timestamp` update, findNearestLogEventByTimestamp and clear itself from URL. useEffect(() => { if (null === mainWorkerRef.current || null === timestamp) { return; diff --git a/src/contexts/UrlContextProvider.tsx b/src/contexts/UrlContextProvider.tsx index e1023873..a1d2a58b 100644 --- a/src/contexts/UrlContextProvider.tsx +++ b/src/contexts/UrlContextProvider.tsx @@ -205,8 +205,10 @@ const getWindowUrlHashParams = () => { ); const hashParams = new URLSearchParams(window.location.hash.substring(1)); - const numberHashParamNames = [HASH_PARAM_NAMES.LOG_EVENT_NUM, - HASH_PARAM_NAMES.TIMESTAMP]; + const numberHashParamNames = [ + HASH_PARAM_NAMES.LOG_EVENT_NUM, + HASH_PARAM_NAMES.TIMESTAMP, + ]; for (const paramName of numberHashParamNames) { const hashParam = hashParams.get(paramName);