perf(query): optimize large-value result diagnostics
This commit is contained in:
6 files changed
+429
-33
No files matched your search
@@ -1,5 +1,34 @@
|
||||
import { describe, expect, test, vi } from "vitest";
|
||||
import { backendResponseError, importAgentDriver, installJdbcPluginLocal } from "@/lib/backend/http";
|
||||
import { backendResponseError, executeMulti, importAgentDriver, installJdbcPluginLocal } from "@/lib/backend/http";
|
||||
import { getDebugLogText } from "@/lib/backend/debugLog";
|
||||
|
||||
class MemoryStorage implements Storage {
|
||||
private data = new Map<string, string>();
|
||||
|
||||
get length() {
|
||||
return this.data.size;
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.data.clear();
|
||||
}
|
||||
|
||||
getItem(key: string) {
|
||||
return this.data.get(key) ?? null;
|
||||
}
|
||||
|
||||
key(index: number) {
|
||||
return [...this.data.keys()][index] ?? null;
|
||||
}
|
||||
|
||||
removeItem(key: string) {
|
||||
this.data.delete(key);
|
||||
}
|
||||
|
||||
setItem(key: string, value: string) {
|
||||
this.data.set(key, value);
|
||||
}
|
||||
}
|
||||
import { BackendErrorException } from "@/lib/backend/errorUtils";
|
||||
|
||||
const envelope = {
|
||||
@@ -70,3 +99,60 @@ describe("HTTP backend error parsing", () => {
|
||||
vi.unstubAllGlobals();
|
||||
});
|
||||
});
|
||||
|
||||
describe("HTTP query transport diagnostics", () => {
|
||||
test("records timing and payload sizes without logging query contents", async () => {
|
||||
vi.stubGlobal("localStorage", new MemoryStorage());
|
||||
const sql = "SELECT 'private-query-marker'";
|
||||
localStorage.setItem("dbx-debug-logging-enabled", "1");
|
||||
localStorage.removeItem("dbx-debug-log-entries");
|
||||
vi.stubGlobal(
|
||||
"fetch",
|
||||
vi.fn().mockResolvedValue(
|
||||
new Response(JSON.stringify([{ columns: ["value"], rows: [["private-cell-marker"]] }]), {
|
||||
status: 200,
|
||||
headers: {
|
||||
"content-type": "application/json",
|
||||
"x-dbx-core-ms": "41",
|
||||
"x-dbx-serialize-ms": "7",
|
||||
},
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
await executeMulti("private-connection-marker", "private-database-marker", sql, undefined, "trace-12345678");
|
||||
|
||||
const logs = getDebugLogText();
|
||||
expect(logs).toContain("[DBX][query-transport:http]");
|
||||
expect(logs).toContain('"backendCoreMs":"41"');
|
||||
expect(logs).toContain('"backendSerializeMs":"7"');
|
||||
expect(logs).toContain('"requestBytes":');
|
||||
expect(logs).toContain('"responseBytes":');
|
||||
expect(logs).toContain('"jsonParseMs":');
|
||||
expect(logs).not.toContain("private-query-marker");
|
||||
expect(logs).not.toContain("private-cell-marker");
|
||||
expect(logs).not.toContain("private-connection-marker");
|
||||
expect(logs).not.toContain("private-database-marker");
|
||||
|
||||
vi.unstubAllGlobals();
|
||||
});
|
||||
|
||||
test("preserves structured backend errors while logging only safe failure metadata", async () => {
|
||||
vi.stubGlobal("localStorage", new MemoryStorage());
|
||||
localStorage.setItem("dbx-debug-logging-enabled", "1");
|
||||
localStorage.removeItem("dbx-debug-log-entries");
|
||||
vi.stubGlobal("fetch", vi.fn().mockResolvedValue(new Response(JSON.stringify(envelope), { status: 400 })));
|
||||
|
||||
const error = await executeMulti("connection-1", "database-1", "SELECT 'failed-query-marker'", undefined, "trace-87654321").catch((value: unknown) => value);
|
||||
|
||||
expect(error).toBeInstanceOf(BackendErrorException);
|
||||
expect(error).toMatchObject({ backendError: expect.objectContaining({ code: envelope.code }) });
|
||||
const logs = getDebugLogText();
|
||||
expect(logs).toContain("[DBX][query-transport:http:error]");
|
||||
expect(logs).toContain('"status":400');
|
||||
expect(logs).not.toContain("failed-query-marker");
|
||||
expect(logs).not.toContain(envelope.detail);
|
||||
|
||||
vi.unstubAllGlobals();
|
||||
});
|
||||
});
|
||||
@@ -203,6 +203,7 @@ import type {
|
||||
NacosSearchProgress,
|
||||
} from "@/types/nacos";
|
||||
import { safeLocalStorageGet, safeLocalStorageSet } from "@/lib/backend/safeStorage";
|
||||
import { appendDebugLog, isDebugLoggingEnabled } from "@/lib/backend/debugLog";
|
||||
import { normalizeConnectionTestResult } from "@/lib/connection/connectionDatabaseInfo";
|
||||
import type { AnnotationFile, SchemaSnapshot } from "@/docs/types";
|
||||
|
||||
@@ -236,6 +237,55 @@ async function post<T>(url: string, body: unknown): Promise<T> {
|
||||
return res.json();
|
||||
}
|
||||
|
||||
async function postQueryWithDiagnostics<T>(url: string, body: unknown, traceId?: string): Promise<T> {
|
||||
if (!isDebugLoggingEnabled()) return post(url, body);
|
||||
|
||||
const startedAt = performance.now();
|
||||
const serializedBody = JSON.stringify(body);
|
||||
const response = await fetch(apiUrl(url), {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: serializedBody,
|
||||
});
|
||||
const headersAt = performance.now();
|
||||
const responseText = await response.text();
|
||||
const bodyAt = performance.now();
|
||||
if (!response.ok) {
|
||||
appendDebugLog("warn", "[DBX][query-transport:http:error]", {
|
||||
traceId: traceId?.slice(0, 8),
|
||||
status: response.status,
|
||||
requestBytes: new TextEncoder().encode(serializedBody).byteLength,
|
||||
responseBytes: new TextEncoder().encode(responseText).byteLength,
|
||||
responseHeadersMs: Math.round(headersAt - startedAt),
|
||||
responseBodyMs: Math.round(bodyAt - headersAt),
|
||||
totalMs: Math.round(bodyAt - startedAt),
|
||||
backendCoreMs: response.headers.get("x-dbx-core-ms"),
|
||||
backendSerializeMs: response.headers.get("x-dbx-serialize-ms"),
|
||||
});
|
||||
throw await backendResponseError(
|
||||
new Response(responseText, {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
headers: response.headers,
|
||||
}),
|
||||
);
|
||||
}
|
||||
const result = JSON.parse(responseText) as T;
|
||||
const parsedAt = performance.now();
|
||||
appendDebugLog("info", "[DBX][query-transport:http]", {
|
||||
traceId: traceId?.slice(0, 8),
|
||||
requestBytes: new TextEncoder().encode(serializedBody).byteLength,
|
||||
responseBytes: new TextEncoder().encode(responseText).byteLength,
|
||||
responseHeadersMs: Math.round(headersAt - startedAt),
|
||||
responseBodyMs: Math.round(bodyAt - headersAt),
|
||||
jsonParseMs: Math.round(parsedAt - bodyAt),
|
||||
totalMs: Math.round(parsedAt - startedAt),
|
||||
backendCoreMs: response.headers.get("x-dbx-core-ms"),
|
||||
backendSerializeMs: response.headers.get("x-dbx-serialize-ms"),
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
async function get<T>(url: string): Promise<T> {
|
||||
const res = await fetch(apiUrl(url));
|
||||
if (!res.ok) throw await backendResponseError(res);
|
||||
@@ -940,14 +990,18 @@ export async function executeMulti(
|
||||
executionMode?: "simple";
|
||||
},
|
||||
): Promise<QueryResult[]> {
|
||||
return post("/api/query/execute-multi", {
|
||||
connectionId,
|
||||
database,
|
||||
sql,
|
||||
schema,
|
||||
return postQueryWithDiagnostics(
|
||||
"/api/query/execute-multi",
|
||||
{
|
||||
connectionId,
|
||||
database,
|
||||
sql,
|
||||
schema,
|
||||
executionId,
|
||||
...options,
|
||||
},
|
||||
executionId,
|
||||
...options,
|
||||
});
|
||||
);
|
||||
}
|
||||
|
||||
export interface ExecuteMultiProgress {
|
||||
|
||||
@@ -3,6 +3,7 @@ import { BackendErrorException, type BackendError } from "@/lib/backend/errorUti
|
||||
import { listen, type UnlistenFn } from "@tauri-apps/api/event";
|
||||
import { normalizeRustMongoCommand, type MongoCommand } from "@/lib/mongo/mongoShellCommand";
|
||||
import { ExternalSqlFileTooLargeError } from "@/lib/sql/sqlFileOpen";
|
||||
import { appendDebugLog, isDebugLoggingEnabled } from "@/lib/backend/debugLog";
|
||||
|
||||
/** Normalize Tauri rejections once at the public backend boundary. */
|
||||
async function invokeBackend<T>(command: string, args?: Record<string, unknown>): Promise<T> {
|
||||
@@ -1159,8 +1160,10 @@ export async function executeMulti(
|
||||
executionMode?: "simple";
|
||||
},
|
||||
): Promise<QueryResult[]> {
|
||||
const diagnosticsEnabled = isDebugLoggingEnabled();
|
||||
const startedAt = diagnosticsEnabled ? performance.now() : 0;
|
||||
try {
|
||||
return await invoke("execute_multi", {
|
||||
const results = await invoke<QueryResult[]>("execute_multi", {
|
||||
connectionId,
|
||||
database,
|
||||
sql,
|
||||
@@ -1168,7 +1171,23 @@ export async function executeMulti(
|
||||
executionId,
|
||||
...options,
|
||||
});
|
||||
if (diagnosticsEnabled) {
|
||||
appendDebugLog("info", "[DBX][query-transport:tauri]", {
|
||||
traceId: executionId?.slice(0, 8),
|
||||
totalMs: Math.round(performance.now() - startedAt),
|
||||
resultCount: results.length,
|
||||
rowCounts: results.map((result) => result.rows.length),
|
||||
columnCounts: results.map((result) => result.columns.length),
|
||||
});
|
||||
}
|
||||
return results;
|
||||
} catch (error) {
|
||||
if (diagnosticsEnabled) {
|
||||
appendDebugLog("warn", "[DBX][query-transport:tauri:error]", {
|
||||
traceId: executionId?.slice(0, 8),
|
||||
totalMs: Math.round(performance.now() - startedAt),
|
||||
});
|
||||
}
|
||||
throw new BackendErrorException(error);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user