Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

new-log-viewer: Add ClpIrDecoder. #62

Merged
merged 11 commits into from
Sep 1, 2024
7 changes: 7 additions & 0 deletions new-log-viewer/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions new-log-viewer/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"homepage": "https://github.com/y-scope/yscope-log-viewer#readme",
"dependencies": {
"axios": "^1.7.2",
"clp-ffi-js": "^0.1.0",
"dayjs": "^1.11.11",
"monaco-editor": "^0.50.0",
"react": "^18.3.1",
Expand Down
85 changes: 49 additions & 36 deletions new-log-viewer/src/services/LogFileManager.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
Decoder,
DecoderOptionsType,
FULL_RANGE_END_IDX,
} from "../typings/decoders";
import {MAX_V8_STRING_LENGTH} from "../typings/js";
import {
Expand All @@ -13,6 +14,7 @@ import {getUint8ArrayFrom} from "../utils/http";
import {getChunkNum} from "../utils/math";
import {formatSizeInBytes} from "../utils/units";
import {getBasenameFromUrlOrDefault} from "../utils/url";
import ClpIrDecoder from "./decoders/ClpIrDecoder";
import JsonlDecoder from "./decoders/JsonlDecoder";


Expand Down Expand Up @@ -48,8 +50,6 @@ const loadFile = async (fileSrc: FileSrcType)
class LogFileManager {
readonly #pageSize: number;

readonly #fileData: Uint8Array;

readonly #fileName: string;
kirkrodrigues marked this conversation as resolved.
Show resolved Hide resolved

#decoder: Decoder;
Expand All @@ -60,21 +60,27 @@ class LogFileManager {
* Private constructor for LogFileManager. This is not intended to be invoked publicly.
* Instead, use LogFileManager.create() to create a new instance of the class.
*
* @param decoder
* @param fileName
* @param fileData
* @param pageSize Page size for setting up pagination.
* @param decoderOptions Initial decoder options.
*/
constructor (
decoder: Decoder,
fileName: string,
fileData: Uint8Array,
pageSize: number,
decoderOptions: DecoderOptionsType
) {
this.#fileName = fileName;
this.#fileData = fileData;
this.#pageSize = pageSize;
this.#decoder = this.#initDecoder(decoderOptions);
this.#decoder = decoder;

// Build indices for full range
junhaoliao marked this conversation as resolved.
Show resolved Hide resolved
const buildIdxResult = decoder.buildIdx(0, FULL_RANGE_END_IDX);
if (null !== buildIdxResult && 0 < buildIdxResult.numInvalidEvents) {
console.error("Invalid events found in decoder.buildIdx():", buildIdxResult);
}

this.#numEvents = decoder.getEstimatedNumEvents();
console.log(`Found ${this.#numEvents} log events.`);
}

get numEvents () {
Expand All @@ -96,7 +102,41 @@ class LogFileManager {
decoderOptions: DecoderOptionsType
): Promise<LogFileManager> {
const {fileName, fileData} = await loadFile(fileSrc);
return new LogFileManager(fileName, fileData, pageSize, decoderOptions);
const decoder = await LogFileManager.#initDecoder(fileName, fileData, decoderOptions);

return new LogFileManager(decoder, fileName, pageSize);
}

/**
* Constructs a decoder instance based on the file extension.
*
* @param fileName
* @param fileData
* @param decoderOptions Initial decoder options.
* @return The constructed decoder.
* @throws {Error} if a decoder cannot be found.
junhaoliao marked this conversation as resolved.
Show resolved Hide resolved
*/
static async #initDecoder (
fileName: string,
fileData: Uint8Array,
decoderOptions: DecoderOptionsType
): Promise<Decoder> {
let decoder: Decoder;
if (fileName.endsWith(".jsonl")) {
decoder = new JsonlDecoder(fileData, decoderOptions);
} else if (fileName.endsWith(".clp.zst")) {
decoder = await ClpIrDecoder.create(fileData);
} else {
throw new Error(`No decoder supports ${fileName}`);
}

if (fileData.length > MAX_V8_STRING_LENGTH) {
throw new Error(`Cannot handle files larger than ${
formatSizeInBytes(MAX_V8_STRING_LENGTH)
} due to a limitation in Chromium-based browsers.`);
}

return decoder;
}

/**
Expand Down Expand Up @@ -154,33 +194,6 @@ class LogFileManager {
};
}

/**
* Constructs a decoder instance based on the file extension.
*
* @param decoderOptions Initial decoder options.
* @return The constructed decoder.
* @throws {Error} if a decoder cannot be found.
*/
#initDecoder = (decoderOptions: DecoderOptionsType): Decoder => {
let decoder: Decoder;
if (this.#fileName.endsWith(".jsonl")) {
decoder = new JsonlDecoder(this.#fileData, decoderOptions);
} else {
throw new Error(`No decoder supports ${this.#fileName}`);
}

if (this.#fileData.length > MAX_V8_STRING_LENGTH) {
throw new Error(`Cannot handle files larger than ${
formatSizeInBytes(MAX_V8_STRING_LENGTH)
} due to a limitation in Chromium-based browsers.`);
}

this.#numEvents = decoder.getEstimatedNumEvents();
console.log(`Found ${this.#numEvents} log events.`);

return decoder;
};

/**
* Gets the range of log event numbers for the page containing the given cursor.
*
Expand Down
51 changes: 51 additions & 0 deletions new-log-viewer/src/services/decoders/ClpIrDecoder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import clpFfiJsModuleInit, {ClpIrStreamReader} from "clp-ffi-js";

import {Nullable} from "../../typings/common";
import {
Decoder,
DecodeResultType,
LogEventCount,
} from "../../typings/decoders";


class ClpIrDecoder implements Decoder {
#streamReader: ClpIrStreamReader;

constructor (streamReader: ClpIrStreamReader) {
this.#streamReader = streamReader;
}

/**
* Creates a new ClpIrDecoder instance.
*
* @param dataArray The input data array to be passed to the decoder.
* @return The created ClpIrDecoder instance.
*/
static async create (dataArray: Uint8Array): Promise<ClpIrDecoder> {
const module = await clpFfiJsModuleInit();
const streamReader = new module.ClpIrStreamReader(dataArray);
return new ClpIrDecoder(streamReader);
}

getEstimatedNumEvents (): number {
return this.#streamReader.getNumEventsBuffered();
}

buildIdx (beginIdx: number, endIdx: number): Nullable<LogEventCount> {
return {
numInvalidEvents: 0,
numValidEvents: this.#streamReader.deserializeRange(beginIdx, endIdx),
};
}

// eslint-disable-next-line class-methods-use-this
setDecoderOptions (): boolean {
return true;
}

decode (beginIdx: number, endIdx: number): Nullable<DecodeResultType[]> {
return this.#streamReader.decodeRange(beginIdx, endIdx);
}
}

export default ClpIrDecoder;
5 changes: 5 additions & 0 deletions new-log-viewer/src/typings/decoders.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,12 @@ interface Decoder {
decode(beginIdx: number, endIdx: number): Nullable<DecodeResultType[]>;
}

/**
* The end index for specifying full range when the exact number of log events is unknown.
junhaoliao marked this conversation as resolved.
Show resolved Hide resolved
*/
const FULL_RANGE_END_IDX: number = 0;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about LOG_EVENT_FILE_END_IDX?


export {FULL_RANGE_END_IDX};
export type {
Decoder,
DecodeResultType,
Expand Down