From 143f6df2c121159cde71ea9e3eb7b7081b36d429 Mon Sep 17 00:00:00 2001 From: jialin Date: Mon, 30 Jun 2025 17:06:51 +0800 Subject: [PATCH] perf: do not handle ansi sequence for parsing logs --- public/static/favicon.ico | Bin 0 -> 3109 bytes src/components/echarts/h-bar.tsx | 2 +- src/components/echarts/mix-line-bar.tsx | 1 + src/components/logs-viewer/parse-worker.ts | 34 +++++++++++++++--- .../logs-viewer/virtual-log-list.tsx | 17 ++++++++- .../components/usage-inner/export-data.tsx | 2 +- .../components/usage-inner/index.tsx | 12 +++++-- .../components/usage-inner/use-usage-data.tsx | 31 ++++++++++++++-- 8 files changed, 87 insertions(+), 12 deletions(-) create mode 100644 public/static/favicon.ico diff --git a/public/static/favicon.ico b/public/static/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..c3fe7ef1f77f7ceb8ea2f443e84c9a22c25f2cb3 GIT binary patch literal 3109 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 Cx@3X? literal 0 HcmV?d00001 diff --git a/src/components/echarts/h-bar.tsx b/src/components/echarts/h-bar.tsx index 1bcf1398..e4a6786c 100644 --- a/src/components/echarts/h-bar.tsx +++ b/src/components/echarts/h-bar.tsx @@ -53,7 +53,7 @@ const BarChart: React.FC = (props) => { axisLabel: { ...yAxis.axisLabel, overflow: 'truncate', - width: 60, + width: 75, ellipsis: '...' } }, diff --git a/src/components/echarts/mix-line-bar.tsx b/src/components/echarts/mix-line-bar.tsx index d0a31b8e..12eb8f3e 100644 --- a/src/components/echarts/mix-line-bar.tsx +++ b/src/components/echarts/mix-line-bar.tsx @@ -101,6 +101,7 @@ const MixLineBarChart: React.FC< stack: 'total', yAxisIndex: 0, itemStyle: { + ...item.itemStyle, color: item.color } }; diff --git a/src/components/logs-viewer/parse-worker.ts b/src/components/logs-viewer/parse-worker.ts index ebe8da02..05a7a22a 100644 --- a/src/components/logs-viewer/parse-worker.ts +++ b/src/components/logs-viewer/parse-worker.ts @@ -30,6 +30,8 @@ class AnsiParser { private percent: number = 0; private isComplete: boolean = false; private chunked: boolean = true; // true: send data in chunks, false: send all data at once + private reminder: string = ''; + private lines: string[] = []; private pageSize: number = 500; private colorMap = { '30': 'black', @@ -52,6 +54,8 @@ class AnsiParser { this.screen = [['']]; this.rawDataRows = 0; this.uid = this.uid + 1; + this.lines = []; + this.reminder = ''; this.page = 1; } @@ -109,7 +113,6 @@ class AnsiParser { const n = parseInt(match[1] || '1', 10); const m = parseInt(match[2] || '1', 10); const command = match[3]; - switch (command) { case 'A': this.cursorRow = Math.max(0, this.cursorRow - n); @@ -183,6 +186,28 @@ class AnsiParser { return result; } + private processInputByLine(input: string): { + data: string[]; + lines: number; + remainder: string; + } { + const lines = input?.split(/\r?\n/) || []; + const remainder = lines.pop() || ''; + + // const data = lines.join('\n'); + this.rawDataRows += lines.length; + this.lines.push(...lines); + return { + data: this.lines, + lines: this.rawDataRows, + remainder + }; + } + + private getAllLines() { + return this.lines.join('\n'); + } + private async processQueue(): Promise { if (this.isProcessing) { return; @@ -191,11 +216,12 @@ class AnsiParser { this.isProcessing = true; while (this.taskQueue.length > 0) { - const input = this.taskQueue.shift(); + const input = this.reminder + this.taskQueue.shift(); if (input) { try { - const result = this.processInput(input); + const result = this.processInputByLine(input); + this.reminder = result.remainder; if (this.chunked) { self.postMessage({ result: result.data, lines: result.lines }); } else if (!this.isComplete) { @@ -220,7 +246,7 @@ class AnsiParser { this.processQueue(); } else if (this.isComplete && !this.chunked) { self.postMessage({ - result: this.getScreenText(), + result: this.getAllLines(), percent: this.percent, isComplete: true }); diff --git a/src/components/logs-viewer/virtual-log-list.tsx b/src/components/logs-viewer/virtual-log-list.tsx index ed2e46d6..5f947828 100644 --- a/src/components/logs-viewer/virtual-log-list.tsx +++ b/src/components/logs-viewer/virtual-log-list.tsx @@ -44,7 +44,7 @@ const LogsViewer: React.FC = forwardRef((props, ref) => { const pageRef = useRef(page); const totalPageRef = useRef(totalPage); const isLoadingMoreRef = useRef(false); - const [currentData, setCurrentData] = useState([]); + const [currentData, setCurrentPageData] = useState([]); const scrollPosRef = useRef({ pos: 'bottom', page: 1 @@ -59,6 +59,21 @@ const LogsViewer: React.FC = forwardRef((props, ref) => { } })); + const removeBracketsFromLine = (row: string) => { + return row.startsWith('(…)') ? row.slice(3) : row; + }; + + const setCurrentData = (lines: string[]) => { + const dataList = lines.map((line, index) => { + return { + content: removeBracketsFromLine(line), + uid: `${pageRef.current}-${index}` + }; + }); + + setCurrentPageData(dataList); + }; + const debounceLoading = _.debounce(() => { setLoading(false); isLoadingMoreRef.current = false; diff --git a/src/pages/dashboard/components/usage-inner/export-data.tsx b/src/pages/dashboard/components/usage-inner/export-data.tsx index e7c996d7..c1147a9c 100644 --- a/src/pages/dashboard/components/usage-inner/export-data.tsx +++ b/src/pages/dashboard/components/usage-inner/export-data.tsx @@ -119,7 +119,7 @@ const ExportData: React.FC<{ init(); } else { setQuery({ - start_date: dayjs().subtract(30, 'days').format('YYYY-MM-DD'), + start_date: dayjs().subtract(29, 'days').format('YYYY-MM-DD'), end_date: dayjs().format('YYYY-MM-DD'), model_ids: [], user_ids: [] diff --git a/src/pages/dashboard/components/usage-inner/index.tsx b/src/pages/dashboard/components/usage-inner/index.tsx index 4bd6be92..edca6cad 100644 --- a/src/pages/dashboard/components/usage-inner/index.tsx +++ b/src/pages/dashboard/components/usage-inner/index.tsx @@ -50,11 +50,19 @@ const UsageInner: FC<{ maxWidth: number }> = ({ maxWidth }) => { const topUserNames = topUsers.map((item: any) => { topUserPrompt.data.push({ name: item.username, - value: item.prompt_token_count + value: item.prompt_token_count, + itemStyle: { + borderRadius: !item.completion_token_count + ? [2, 2, 2, 2] + : [0, 2, 2, 0] + } }); topUserCompletion.data.push({ name: item.username, - value: item.completion_token_count + value: item.completion_token_count, + itemStyle: { + borderRadius: !item.prompt_token_count ? [2, 2, 2, 2] : [2, 0, 0, 2] + } }); return item.username; }); diff --git a/src/pages/dashboard/components/usage-inner/use-usage-data.tsx b/src/pages/dashboard/components/usage-inner/use-usage-data.tsx index c35e5df7..55b58560 100644 --- a/src/pages/dashboard/components/usage-inner/use-usage-data.tsx +++ b/src/pages/dashboard/components/usage-inner/use-usage-data.tsx @@ -85,7 +85,7 @@ const generateValueMap = (list: { timestamp: number; value: number }[]) => { }; const generateData = (dateRage: string[], valueMap: Map) => { - return dateRage.map((date) => { + return dateRage.map((date, index) => { const value = valueMap.get(date) || 0; return { time: date, @@ -201,15 +201,40 @@ export default function useUseageData(config: { }; // =========== token usage data ============== + const completionDataList = generateData( + dateRange, + generateValueMap(completionTokenHistory) + ); + const promptDataList = generateData( + dateRange, + generateValueMap(promptTokenHistory) + ); + const completionData: any = { name: 'Completion tokens', color: baseColorMap.base, - data: generateData(dateRange, generateValueMap(completionTokenHistory)) + data: completionDataList.map((item, index) => { + return { + ...item, + itemStyle: { + borderRadius: !promptDataList[index].value + ? [2, 2, 0, 0] + : [0, 0, 0, 0] + } + }; + }) }; const promptData: any = { name: 'Prompt tokens', color: baseColorMap.baseR3, - data: generateData(dateRange, generateValueMap(promptTokenHistory)) + data: promptDataList.map((item, index) => { + return { + ...item, + itemStyle: { + borderRadius: [2, 2, 0, 0] + } + }; + }) }; return {