blob: 5b5caab16b3f36c510eb9206fb2cdedd98e61480 [file] [edit]
// server/server.js
import { Sys as sys9 } from "gpaccore";
// server/JSClient/Messaging/MessageHandler.js
import { Sys as sys } from "gpaccore";
// server/config/live.config.js
var UPDATE_INTERVALS = {
SESSION_STATS: 1e3,
FILTER_STATS: 1e3,
CPU_STATS: 500
};
var LOG_RETENTION = {
maxHistorySize: 500,
maxHistorySizeVerbose: 2e3,
keepRatio: {
error: 1,
warning: 0.8,
info: 0.2,
debug: 0.05
}
};
// server/JSClient/config.js
var DEFAULT_FILTER_FIELDS = [
"idx",
"status",
"bytes_done",
"bytes_sent",
"pck_sent",
"pck_done",
"time",
"nb_ipid",
"nb_opid",
"errors",
"current_errors",
"last_task_time"
];
var FILTER_PROPS_LITE = [
"name",
"status",
"bytes_done",
"type",
"ID",
"nb_ipid",
"nb_opid",
"idx",
"itag",
"pck_sent",
"pck_done",
"time",
"current_errors",
"po"
];
var FILTER_ARGS_LITE = [];
var PID_PROPS_LITE = [];
var FILTER_SUBSCRIPTION_FIELDS = [
"status",
"bytes_done",
"bytes_sent",
"pck_done",
"pck_sent",
"time",
"nb_ipid",
"nb_opid",
"errors",
"current_errors",
"last_task_time"
];
// server/JSClient/Cache/CacheManager.js
function CacheManager() {
this.cache = /* @__PURE__ */ Object.create(null);
this.hits = 0;
this.misses = 0;
this.get = function(key, maxAgeMs) {
const entry = this.cache[key];
if (!entry) {
this.misses++;
return null;
}
const now = Date.now();
if (now - entry.ts > maxAgeMs) {
delete this.cache[key];
this.misses++;
return null;
}
this.hits++;
return entry.data;
};
this.set = function(key, data) {
this.cache[key] = { data, ts: Date.now() };
};
this.getOrSet = function(key, ttlMs, computeStringFn) {
const cached = this.get(key, ttlMs);
if (cached) return cached;
const value = computeStringFn();
this.set(key, value);
return value;
};
this.stats = function() {
return {
size: Object.keys(this.cache).length,
hits: this.hits,
misses: this.misses,
keys: Object.keys(this.cache)
};
};
this.clear = function(key) {
if (key) {
delete this.cache[key];
} else {
this.cache = /* @__PURE__ */ Object.create(null);
}
};
}
var cacheManager = new CacheManager();
// server/history/HistoryFileReader.js
import * as std from "std";
import * as os from "os";
var ALLOWED_EXACT_FILES = ["snapshot.json", "events.jsonl", "manifest.json", "logs.jsonl", "journal_index.json"];
var VALID_SESSION_ID = /^\d{4}-\d{2}-\d{2}_\d{2}-\d{2}-\d{2}$/;
var VALID_CHUNK_FILE = /^chunks\/chunk_\d{4}\.jsonl$/;
var VALID_LOG_CHUNK_FILE = /^logs\/logs_\d{4}\.jsonl$/;
var VALID_CHECKPOINT_FILE = /^checkpoints\/cp_\d{4}\.json$/;
function HistoryFileReader(historyDir) {
const baseDir = historyDir || "history";
this.listSessions = function() {
const sessions = [];
const [entries, err] = os.readdir(baseDir);
if (err) return sessions;
for (const entry of entries) {
if (!VALID_SESSION_ID.test(entry)) continue;
const dir = `${baseDir}/${entry}`;
const hasSnapshot = fileExists(`${dir}/snapshot.json`);
const hasManifest = fileExists(`${dir}/manifest.json`);
const hasEvents = fileExists(`${dir}/events.jsonl`);
if (!hasSnapshot && !hasManifest && !hasEvents) continue;
const hasCheckpoints = dirHasFiles(`${dir}/checkpoints`, /^cp_\d{4}\.json$/);
const sizeBytes = hasEvents ? fileSize(`${dir}/events.jsonl`) : 0;
const isComplete = fileExists(`${dir}/done`);
sessions.push({ sessionId: entry, hasSnapshot, hasEvents, hasManifest, hasCheckpoints, sizeBytes, isComplete });
}
sessions.sort((a, b) => a.sessionId < b.sessionId ? 1 : a.sessionId > b.sessionId ? -1 : 0);
return sessions;
};
this.readEventsRange = function(sessionId, fromUs, toUs) {
const manifestResult = this.readFile(sessionId, "manifest.json");
if (manifestResult.ok) {
return readEventsFromChunks(this, sessionId, manifestResult.content, fromUs, toUs);
}
const legacyResult = this.readFile(sessionId, "events.jsonl");
if (!legacyResult.ok) return [];
return parseAndFilterEvents(legacyResult.content, fromUs, toUs);
};
this.readFile = function(sessionId, fileName) {
if (!VALID_SESSION_ID.test(sessionId)) {
return { ok: false, error: "session_not_found", detail: `Invalid sessionId: ${sessionId}` };
}
const isAllowed = ALLOWED_EXACT_FILES.includes(fileName) || VALID_CHUNK_FILE.test(fileName) || VALID_LOG_CHUNK_FILE.test(fileName) || VALID_CHECKPOINT_FILE.test(fileName);
if (!isAllowed) {
return { ok: false, error: "file_not_allowed", detail: `File not allowed: ${fileName}` };
}
const path = `${baseDir}/${sessionId}/${fileName}`;
const file = std.open(path, "r");
if (!file) {
return { ok: false, error: "file_not_found", detail: `${fileName} not found in session ${sessionId}` };
}
try {
const content = file.readAsString();
return { ok: true, content };
} catch (e) {
return { ok: false, error: "read_error", detail: String(e) };
} finally {
file.close();
}
};
}
function readEventsFromChunks(reader, sessionId, manifestContent, fromUs, toUs) {
let manifest;
try {
manifest = JSON.parse(manifestContent);
} catch (e) {
print(`[HistoryFileReader] Failed to parse manifest for session ${sessionId}: ${e}`);
return [];
}
const { startUs, chunkCount, chunkDurationUs } = manifest;
const fromChunk = fromUs !== void 0 ? Math.max(0, Math.floor((fromUs - startUs) / chunkDurationUs)) : 0;
const toChunk = toUs !== void 0 ? Math.min(chunkCount - 1, Math.floor((toUs - startUs) / chunkDurationUs)) : chunkCount - 1;
let events = [];
for (let i = fromChunk; i <= toChunk; i++) {
const file = `chunks/chunk_${String(i).padStart(4, "0")}.jsonl`;
const chunkResult = reader.readFile(sessionId, file);
if (!chunkResult.ok) continue;
events = events.concat(parseAndFilterEvents(chunkResult.content, fromUs, toUs));
}
return events;
}
function parseAndFilterEvents(content, fromUs, toUs) {
return content.split("\n").filter((line) => line.trim()).map((line) => {
try {
return JSON.parse(line);
} catch (_e) {
return null;
}
}).filter((event) => event !== null).filter(
(event) => (fromUs === void 0 || event.ts_us >= fromUs) && (toUs === void 0 || event.ts_us <= toUs)
);
}
function fileExists(path) {
const file = std.open(path, "r");
if (!file) return false;
file.close();
return true;
}
function fileSize(path) {
const [stat3, err] = os.stat(path);
if (err) return 0;
return stat3.size || 0;
}
function dirHasFiles(dir, pattern) {
const [entries, err] = os.readdir(dir);
if (err) return false;
return entries.some((entry) => pattern.test(entry));
}
// server/JSClient/Messaging/MessageHandler.js
var historyReader = new HistoryFileReader(sys.get_opt("core", "rmt-log") || "history");
function MessageHandler(client) {
this.client = client;
this.handleMessage = function(msg, all_clients2) {
let text = msg;
if (text.startsWith("json:")) {
try {
let jtext = JSON.parse(text.substr(5));
if (!("message" in jtext)) return;
const handlers = {
"get_all_filters": () => {
this.client.filterManager.sendAllFilters();
},
"filter_args_details": () => {
let idx = jtext["idx"];
this.client.filterManager.requestDetails(idx);
},
"stop_filter_args": () => {
let idx = jtext["idx"];
this.client.filterManager.stopDetails(idx);
},
"subscribe_session": () => {
const interval = jtext["interval"] || UPDATE_INTERVALS.SESSION_STATS;
const fields = jtext["fields"] || DEFAULT_FILTER_FIELDS;
this.client.sessionStatsManager.subscribe(interval, fields);
this.client.ensureMonitoringLoop();
},
"unsubscribe_session": () => {
this.client.sessionStatsManager.unsubscribe();
},
"subscribe_filter": () => {
const idx = jtext.idx;
let pidScope = jtext.pidScope || "both";
if (!pidScope) {
pidScope = "both";
}
this.client.filterManager.subscribeToFilter(idx, pidScope);
this.client.ensureMonitoringLoop();
},
"unsubscribe_filter": () => {
const idx = jtext.idx;
this.client.filterManager.unsubscribeFromFilter(idx);
},
"update_arg": () => {
this.client.filterManager.updateArgument(jtext["idx"], jtext["name"], jtext["argName"], jtext["newValue"]);
},
"get_png": () => {
this.client.filterManager.addPngProbe(jtext["idx"], jtext["name"]);
},
"subscribe_cpu_stats": () => {
const interval = jtext["interval"] || UPDATE_INTERVALS.CPU_STATS;
const fields = jtext["fields"] || [];
this.client.cpuStatsManager.subscribe(interval, fields);
this.client.ensureMonitoringLoop();
},
"unsubscribe_cpu_stats": () => {
this.client.cpuStatsManager.unsubscribe();
},
"subscribe_logs": () => {
const logLevel = jtext["logLevel"] || "all@warning";
this.client.logManager.subscribe(logLevel);
this.client.ensureMonitoringLoop();
},
"unsubscribe_logs": () => {
this.client.logManager.unsubscribe();
},
"update_log_level": () => {
const logLevel = jtext["logLevel"] || "all@warning";
this.client.logManager.updateLogLevel(logLevel);
},
"get_log_status": () => {
const status = this.client.logManager.getStatus();
this.client.client.send(JSON.stringify({
message: "log_status",
status
}));
},
"get_ipid_props": () => {
const props = this.client.pidPropsCollector.collectIpidProps(
jtext["filterIdx"],
jtext["ipidIdx"]
);
this.client.client.send(JSON.stringify({
message: "ipid_props_response",
filterIdx: jtext["filterIdx"],
ipidIdx: jtext["ipidIdx"],
properties: props
}));
},
"get_command_line": () => {
this.client.commandLineManager.sendCommandLine();
},
"get_cache_stats": () => {
const stats = cacheManager.stats();
this.client.client.send(JSON.stringify({
message: "cache_stats",
stats
}));
},
"list_sessions": () => {
const sessions = historyReader.listSessions();
this.client.client.send(JSON.stringify({
command: "list_sessions",
status: "ok",
sessions
}));
},
"read_events_range": () => {
const { sessionId, fromUs, toUs } = jtext;
const events = historyReader.readEventsRange(sessionId, fromUs, toUs);
this.client.client.send(JSON.stringify({
command: "events_range",
status: "ok",
sessionId,
events
}));
},
"read_file": () => {
const { sessionId, file } = jtext;
const result = historyReader.readFile(sessionId, file);
if (result.ok) {
this.client.client.send(JSON.stringify({
command: "read_file",
status: "ok",
sessionId,
file,
content: result.content
}));
} else {
this.client.client.send(JSON.stringify({
command: "read_file",
status: "error",
error: result.error,
detail: result.detail
}));
}
}
};
const handler = handlers[jtext["message"]];
if (handler) {
handler();
}
} catch (e) {
console.error("MessageHandler: Error handling message:", e);
}
}
};
}
// server/JSClient/Session/SessionStatsManager.js
function SessionStatsManager(client) {
this.client = client;
this.isSubscribed = false;
this.interval = UPDATE_INTERVALS.SESSION_STATS;
this.fields = [];
this.lastSentMetrics = "";
this.subscribe = function(interval, fields) {
this.isSubscribed = true;
this.interval = interval || UPDATE_INTERVALS.SESSION_STATS;
this.fields = fields || DEFAULT_FILTER_FIELDS;
};
this.unsubscribe = function() {
this.isSubscribed = false;
};
this.computeAllPacketsDone = function(filters) {
if (filters.length === 0) return false;
for (const f of filters) {
if (f.nb_ipid === 0) continue;
for (let i = 0; i < f.nb_ipid; i++) {
const eos = f.ipid_props(i, "eos");
if (!eos) {
return false;
}
}
}
return true;
};
this.tick = function(now) {
if (!this.isSubscribed) return;
const serialized = cacheManager.getOrSet("session_stats", 50, () => {
const stats = [];
const filters = [];
session.lock_filters(true);
for (let i = 0; i < session.nb_filters; i++) {
const f = session.get_filter(i);
if (f.is_destroyed()) continue;
filters.push(f);
const obj = {};
for (const field of this.fields) {
obj[field] = f[field];
}
let allInputsEos = f.nb_ipid > 0;
for (let j = 0; j < f.nb_ipid; j++) {
if (!f.ipid_props(j, "eos")) {
allInputsEos = false;
break;
}
}
obj.is_eos = allInputsEos;
obj.last_ts_sent = f.last_ts_sent || null;
stats.push(obj);
}
const allFiltersEos = this.computeAllPacketsDone(filters);
const all_packets_done = session.last_task && allFiltersEos;
session.lock_filters(false);
return JSON.stringify({
message: "session_stats",
all_packets_done,
ts_us: now,
stats
});
});
if (this.client.client) {
this.client.client.send(serialized);
}
const sessionMetrics = session.session_metrics;
if (sessionMetrics && sessionMetrics !== this.lastSentMetrics && this.client.client) {
this.lastSentMetrics = sessionMetrics;
this.client.client.send(JSON.stringify({
message: "session_metrics",
data: sessionMetrics
}));
}
};
this.cleanup = function() {
this.isSubscribed = false;
this.lastSentMetrics = "";
};
this.handleSessionEnd = function() {
this.unsubscribe();
};
}
// server/JSClient/Session/SessionManager.js
function SessionManager(client) {
this.client = client;
this.hasActiveSubscriptions = function() {
return this.client.sessionStatsManager.isSubscribed || this.client.cpuStatsManager.isSubscribed || this.client.logManager.isSubscribed || Object.keys(this.client.filterManager.filterSubscriptions).length > 0;
};
this.getMinInterval = function() {
let interval = 1e3;
if (this.client.sessionStatsManager.isSubscribed) {
interval = Math.min(interval, this.client.sessionStatsManager.interval);
}
if (this.client.cpuStatsManager.isSubscribed) {
interval = Math.min(interval, this.client.cpuStatsManager.interval);
}
return interval;
};
this.tick = function(now) {
this.client.cpuStatsManager.tick(now);
this.client.logManager.tick(now);
this.client.filterManager.tick(now);
this.client.sessionStatsManager.tick(now);
};
this.handleSessionEnd = function(now) {
this.tick(now);
this.client.cpuStatsManager.handleSessionEnd();
this.client.logManager.handleSessionEnd();
this.client.filterManager.handleSessionEnd();
this.client.sessionStatsManager.handleSessionEnd();
try {
this.client.client.send(JSON.stringify({
message: "session_end",
reason: "completed",
timestamp: now
}));
} catch (e) {
print("[SessionManager] Error sending session_end:", e);
}
};
}
// server/JSClient/filterUtils.js
function gpac_filter_to_object(f, full = false) {
let jsf = {};
for (let prop in f) {
if (full || FILTER_PROPS_LITE.includes(prop))
jsf[prop] = f[prop];
}
jsf["gpac_args"] = [];
if (full) {
let all_args = f.all_args(true);
for (let arg of all_args) {
if (arg && (full || FILTER_ARGS_LITE.includes(arg.name)))
jsf["gpac_args"].push(arg);
}
}
jsf["ipid"] = {};
jsf["opid"] = {};
for (let d = 0; d < f.nb_ipid; d++) {
let pidname = f.ipid_props(d, "name");
let jspid = {};
f.ipid_props(d, (name, type, val) => {
if (full || PID_PROPS_LITE.includes(name))
jspid[name] = { "type": type, "val": val };
});
jspid["buffer"] = f.ipid_props(d, "buffer");
jspid["buffer_total"] = f.ipid_props(d, "buffer_total");
jspid["source_idx"] = f.ipid_source(d).idx;
jsf["ipid"][pidname] = jspid;
}
for (let d = 0; d < f.nb_opid; d++) {
let pidname = f.opid_props(d, "name");
let jspid = {};
f.opid_props(d, (name, type, val) => {
if (full || PID_PROPS_LITE.includes(name))
jspid[name] = { "type": type, "val": val };
});
jspid["buffer"] = f.opid_props(d, "buffer");
jspid["buffer_total"] = f.opid_props(d, "buffer_total");
jsf["opid"][pidname] = jspid;
}
return jsf;
}
function gpac_filter_to_minimal_object(f) {
const minimalFilters = {
idx: f.idx,
name: f.name,
type: f.type,
status: f.status,
itag: f.itag || null,
ID: f.ID || null,
nb_ipid: f.nb_ipid,
nb_opid: f.nb_opid,
ipid: [],
opid: []
};
for (let i = 0; i < f.nb_ipid; i++) {
minimalFilters.ipid.push({
pid_index: i,
name: f.ipid_props(i, "name"),
source_idx: f.ipid_source(i).idx,
stream_type: f.ipid_props(i, "StreamType")
});
}
for (let o = 0; o < f.nb_opid; o++) {
minimalFilters.opid.push({
pid_index: o,
name: f.opid_props(o, "name"),
stream_type: f.opid_props(o, "StreamType")
});
}
return minimalFilters;
}
function on_all_connected(cb) {
session.post_task(() => {
let all_filters_instances = [];
session.lock_filters(true);
for (let i = 0; i < session.nb_filters; i++) {
const f = session.get_filter(i);
if (!f.is_destroyed()) all_filters_instances.push(f);
}
session.lock_filters(false);
cb(all_filters_instances);
return false;
});
}
// server/JSClient/Filters/PID/PidDataCollector.js
function _collectMediaProps(getProp) {
return {
timescale: getProp("Timescale"),
codec: getProp("CodecID"),
type: getProp("StreamType"),
width: getProp("Width"),
height: getProp("Height"),
pixelformat: getProp("PixelFormat"),
bitrate: getProp("Bitrate"),
samplerate: getProp("SampleRate"),
channels: getProp("Channels")
};
}
function _collectStats(stats, filter) {
if (!stats) return null;
const result = {
disconnected: stats.disconnected,
average_process_rate: stats.average_process_rate,
max_process_rate: stats.max_process_rate,
average_bitrate: stats.average_bitrate,
max_bitrate: stats.max_bitrate,
nb_processed: stats.nb_processed,
max_process_time: stats.max_process_time,
total_process_time: stats.total_process_time
};
const lastTs = stats.last_ts_sent ?? filter.last_ts_sent;
if (lastTs) result.last_ts_sent = lastTs;
if (stats.last_process_time) result.last_process_time = stats.last_process_time;
if (stats.buffer_time) result.buffer_time = stats.buffer_time;
if (stats.nb_buffer_units) result.nb_buffer_units = stats.nb_buffer_units;
if (stats.max_buffer_time) result.max_buffer_time = stats.max_buffer_time;
if (stats.max_playout_time) result.max_playout_time = stats.max_playout_time;
if (stats.min_playout_time) result.min_playout_time = stats.min_playout_time;
if (stats.total_process_time > 0) result.average_process_time = stats.total_process_time / stats.nb_processed;
if (stats.first_process_time) result.first_process_time = stats.first_process_time;
return result;
}
function PidDataCollector() {
this.collectInputPids = function(filter, withPidProperties, statsOnly) {
const ipids = {};
for (let i = 0; i < filter.nb_ipid; i++) {
const originalName = filter.ipid_props(i, "name");
const getProp = (name) => filter.ipid_props(i, name);
const pid = {
name: filter.nb_ipid > 1 && originalName ? `${originalName}_${i}` : originalName,
buffer: getProp("buffer"),
nb_pck_queued: getProp("nb_pck_queued"),
would_block: getProp("would_block"),
eos: getProp("eos"),
playing: getProp("playing"),
..._collectMediaProps(getProp)
};
const source = filter.ipid_source(i);
if (source) pid.source_idx = source.idx;
const stats = _collectStats(filter.ipid_stats(i), filter);
if (stats) pid.stats = stats;
if (withPidProperties) {
const allProps = {};
filter.ipid_props(i, function(pname, ptype, pval) {
allProps[pname] = { name: pname, type: ptype, value: pval };
});
pid.properties = allProps;
}
const key = pid.name || `ipid_${i}`;
ipids[key] = statsOnly ? { buffer: pid.buffer, bitrate: pid.bitrate, ...pid.stats && { stats: pid.stats } } : pid;
}
return ipids;
};
this.collectOutputPids = function(filter, statsOnly) {
const opids = {};
for (let i = 0; i < filter.nb_opid; i++) {
const originalName = filter.opid_props(i, "name");
const getProp = (name) => filter.opid_props(i, name);
const rawStats = filter.opid_stats(i);
const pid = {
name: filter.nb_opid > 1 && originalName ? `${originalName}_${i}` : originalName,
buffer: getProp("buffer"),
max_buffer: getProp("max_buffer"),
nb_pck_queued: getProp("nb_pck_queued"),
would_block: getProp("would_block"),
eos_received: rawStats?.eos_received,
playing: getProp("playing"),
..._collectMediaProps(getProp),
id: getProp("ID"),
trackNumber: getProp("TrackNumber"),
serviceID: getProp("ServiceID"),
language: getProp("Language"),
role: getProp("Role")
};
const stats = _collectStats(rawStats, filter);
if (stats) pid.stats = stats;
const key = pid.name || `opid_${i}`;
opids[key] = statsOnly ? { buffer: pid.buffer, max_buffer: pid.max_buffer, bitrate: pid.bitrate, ...pid.stats && { stats: pid.stats } } : pid;
}
return opids;
};
}
// server/JSClient/Filters/ArgumentHandler.js
function ArgumentHandler(client) {
this.client = client;
this.sendDetails = function(idx) {
session.post_task(() => {
let Args = [];
session.lock_filters(true);
for (let i = 0; i < session.nb_filters; i++) {
let f = session.get_filter(i);
if (f.idx == idx) {
const fullObj = gpac_filter_to_object(f, true);
Args = fullObj.gpac_args;
break;
}
}
session.lock_filters(false);
if (this.client.client) {
this.client.client.send(JSON.stringify({
message: "details",
filter: {
idx,
gpac_args: Args
}
}));
}
return false;
});
};
this.updateArgument = function(idx, name, argName, newValue) {
let filter = session.get_filter("" + idx);
if (!filter) {
print("Error: Filter with idx " + idx + " not found");
return;
}
try {
filter.update(argName, newValue);
if (this.client.historyCollector) {
this.client.historyCollector.recordFilterArgsUpdate(idx, argName, newValue);
}
} catch (e) {
print("Error: Failed to update argument: " + e.toString());
}
};
}
// server/JSClient/Filters/FilterManager.js
function FilterManager(client) {
this.client = client;
this.details_needed = {};
this.filterSubscriptions = {};
this.lastSentByFilter = {};
this.pidDataCollector = new PidDataCollector();
this.argumentHandler = new ArgumentHandler(client);
this.sendAllFilters = function() {
on_all_connected((all_js_filters) => {
const serialized = cacheManager.getOrSet("all_filters", 100, () => {
const minimalFiltersList = all_js_filters.map((f) => {
return gpac_filter_to_minimal_object(f);
});
return JSON.stringify({
"message": "filters",
"filters": minimalFiltersList
});
});
if (this.client.client) {
this.client.client.send(serialized);
}
});
};
this.requestDetails = function(idx) {
this.details_needed[idx] = true;
this.argumentHandler.sendDetails(idx);
};
this.stopDetails = function(idx) {
this.details_needed[idx] = false;
};
this.subscribeToFilter = function(idx, pidScope) {
this.filterSubscriptions[idx] = {
interval: UPDATE_INTERVALS.FILTER_STATS,
fields: FILTER_SUBSCRIPTION_FIELDS,
pidScope: pidScope || "both"
};
this.lastSentByFilter[idx] = 0;
this.client.ensureMonitoringLoop();
};
this.unsubscribeFromFilter = function(idx) {
delete this.filterSubscriptions[idx];
delete this.lastSentByFilter[idx];
};
this.tick = function(now) {
for (const idxStr in this.filterSubscriptions) {
const idx = parseInt(idxStr);
const sub = this.filterSubscriptions[idxStr];
const lastSent = this.lastSentByFilter[idxStr] || 0;
if (now - lastSent < sub.interval) continue;
const cacheKey = `filter_stats_${idx}`;
const serialized = cacheManager.getOrSet(cacheKey, 50, () => {
session.lock_filters(true);
let fObj = null;
for (let i = 0; i < session.nb_filters; i++) {
const f = session.get_filter(i);
if (f.is_destroyed()) continue;
if (f.idx === idx) {
fObj = f;
break;
}
}
session.lock_filters(false);
if (!fObj) return null;
const payload = { idx };
for (const field of sub.fields) {
payload[field] = fObj[field];
}
switch (sub.pidScope) {
case "ipid":
payload.ipids = this.pidDataCollector.collectInputPids(fObj, true);
break;
case "opid":
payload.opids = this.pidDataCollector.collectOutputPids(fObj);
break;
case "both":
payload.ipids = this.pidDataCollector.collectInputPids(fObj, true);
payload.opids = this.pidDataCollector.collectOutputPids(fObj);
break;
default:
break;
}
return JSON.stringify({ message: "filter_stats", ts_us: now, ...payload });
});
if (serialized && this.client.client) {
this.client.client.send(serialized);
this.lastSentByFilter[idxStr] = now;
}
}
};
this.cleanup = function() {
this.filterSubscriptions = {};
this.lastSentByFilter = {};
};
this.handleSessionEnd = function() {
this.filterSubscriptions = {};
this.lastSentByFilter = {};
};
this.updateArgument = function(idx, name, argName, newValue) {
this.argumentHandler.updateArgument(idx, name, argName, newValue);
};
}
// server/JSClient/Sys/buildCpuStatsPayload.js
import { Sys as sys2 } from "gpaccore";
function buildCpuStatsPayload() {
const stats = {
total_cpu_usage: sys2.total_cpu_usage,
process_cpu_usage: sys2.process_cpu_usage,
process_memory: sys2.process_memory,
physical_memory: sys2.physical_memory,
physical_memory_avail: sys2.physical_memory_avail,
gpac_memory: sys2.gpac_memory,
nb_cores: sys2.nb_cores,
thread_count: sys2.thread_count,
memory_usage_percent: 0,
process_memory_percent: 0,
gpac_memory_percent: 0,
cpu_efficiency: 0
};
if (sys2.physical_memory > 0) {
stats.memory_usage_percent = (sys2.physical_memory - sys2.physical_memory_avail) / sys2.physical_memory * 100;
stats.process_memory_percent = sys2.process_memory / sys2.physical_memory * 100;
stats.gpac_memory_percent = sys2.gpac_memory / sys2.physical_memory * 100;
}
if (sys2.total_cpu_usage > 0) {
stats.cpu_efficiency = sys2.process_cpu_usage / sys2.total_cpu_usage * 100;
}
return { stats };
}
// server/JSClient/Sys/CpuStatsManager.js
function CpuStatsManager(client) {
this.client = client;
this.isSubscribed = false;
this.interval = UPDATE_INTERVALS.CPU_STATS;
this.lastSent = 0;
this.subscribe = function() {
this.isSubscribed = true;
this.interval = UPDATE_INTERVALS.CPU_STATS;
this.lastSent = 0;
this.client.ensureMonitoringLoop();
};
this.unsubscribe = function() {
this.isSubscribed = false;
};
this.tick = function(now) {
if (!this.isSubscribed) return;
if (now - this.lastSent < this.interval) return;
const serialized = cacheManager.getOrSet("cpu_stats", 50, () => {
const { stats } = buildCpuStatsPayload();
return JSON.stringify({ message: "cpu_stats", stats: { timestamp: now, ...stats } });
});
if (this.client.client) {
this.client.client.send(serialized);
}
this.lastSent = now;
};
this.cleanup = function() {
this.isSubscribed = false;
};
this.handleSessionEnd = function() {
this.unsubscribe();
};
}
// server/JSClient/Sys/LogManager.js
import { Sys as sys4 } from "gpaccore";
// server/JSClient/Sys/Utils/LogHub.js
import { Sys as sys3 } from "gpaccore";
var logHub = {
subscribers: /* @__PURE__ */ new Map(),
originalLogConfig: null,
activeLogLevel: null,
add(id, manager) {
const wasEmpty = this.subscribers.size === 0;
this.subscribers.set(id, manager);
if (wasEmpty) {
this.originalLogConfig = sys3.get_logs(true);
sys3.use_logx = true;
sys3.on_log = (tool, level, msg, tid, caller) => {
for (const manager2 of this.subscribers.values()) manager2.handleLog(tool, level, msg, tid, caller);
};
}
},
remove(id) {
this.subscribers.delete(id);
if (this.subscribers.size === 0) this._teardown();
},
/** Force-clear everything (session end) — stops all log capture immediately */
shutdown() {
this.subscribers.clear();
this._teardown();
},
setLogLevel(logLevel) {
this.activeLogLevel = logLevel;
sys3.set_logs(logLevel);
for (const manager of this.subscribers.values()) {
manager.logLevel = logLevel;
manager.sendToClient({ message: "log_config_changed", logLevel });
}
},
_teardown() {
sys3.on_log = void 0;
if (this.originalLogConfig) sys3.set_logs(this.originalLogConfig);
this.originalLogConfig = null;
this.activeLogLevel = null;
}
};
// server/JSClient/Sys/LogManager.js
function LogManager(client) {
this.client = client;
this.isSubscribed = false;
this.logLevel = "all@warning";
this.pendingLogs = [];
this.batchTimer = null;
this.subscribe = function(logLevel) {
if (this.isSubscribed) {
this.updateLogLevel(logLevel);
return;
}
this.logLevel = logLevel;
this.isSubscribed = true;
try {
logHub.add(this.client.id, this);
if (logHub.activeLogLevel) {
this.logLevel = logHub.activeLogLevel;
this.sendToClient({ message: "log_config_changed", logLevel: this.logLevel });
} else {
logHub.setLogLevel(this.logLevel);
}
this.client.ensureMonitoringLoop();
} catch (error) {
console.error("LogManager: Failed to start log capturing:", error);
this.isSubscribed = false;
}
};
this.unsubscribe = function() {
if (!this.isSubscribed) return;
try {
this.flushPendingLogs();
this.isSubscribed = false;
logHub.remove(this.client.id);
this.pendingLogs = [];
this.batchTimer = null;
} catch (error) {
console.error("LogManager: Failed to stop log capturing:", error);
}
};
this.handleLog = function(tool, level, message, thread_id, caller) {
this.pendingLogs.push({
timestamp: sys4.clock_us(),
timestampMs: Date.now(),
tool,
level,
message: message?.length > 500 ? message.substring(0, 500) + "..." : message,
thread_id,
caller: caller?.idx !== void 0 ? caller.idx : caller?.name || null
});
if (!this.batchTimer) {
this.batchTimer = true;
session.post_task(() => {
if (!this.isSubscribed) return false;
this.flushPendingLogs();
return false;
}, 50);
}
};
this.tick = function(now) {
};
this.updateLogLevel = function(logLevel) {
if (!this.isSubscribed) return;
try {
this.pendingLogs = [];
logHub.setLogLevel(logLevel);
} catch (error) {
console.error("LogManager: Failed to update log level:", error);
}
};
this.getStatus = function() {
return {
isSubscribed: this.isSubscribed,
logLevel: this.logLevel,
currentLogConfig: sys4.get_logs()
};
};
this.flushPendingLogs = function() {
if (this.pendingLogs.length === 0) {
this.batchTimer = null;
return;
}
this.sendToClient({ message: "log_batch", logs: this.pendingLogs });
this.pendingLogs = [];
this.batchTimer = null;
};
this.sendToClient = function(data) {
if (this.client.client && typeof this.client.client.send === "function") {
this.client.client.send(JSON.stringify(data));
}
};
this.forceUnsubscribe = function() {
try {
this.flushPendingLogs();
this.isSubscribed = false;
logHub.remove(this.client.id);
this.pendingLogs = [];
this.batchTimer = null;
} catch (error) {
console.error("LogManager: Error during force cleanup:", error);
}
};
this.handleSessionEnd = function() {
logHub.shutdown();
this.isSubscribed = false;
this.pendingLogs = [];
this.batchTimer = null;
};
}
// server/JSClient/CommandLineManager.js
import { Sys as sys5 } from "gpaccore";
function CommandLineManager(client) {
this.client = client;
this.getCommandLine = function() {
try {
if (typeof sys5 !== "undefined" && sys5.args) {
if (Array.isArray(sys5.args) && sys5.args.length > 0) {
const commandLine = sys5.args.join(" ");
return commandLine;
}
}
} catch (e) {
print("[CommandLineManager] Error getting command line: " + e);
return null;
}
};
this.sendCommandLine = function() {
const commandLine = this.getCommandLine();
if (commandLine) {
this.client.client.send(JSON.stringify({
message: "command_line_response",
commandLine,
timestamp: Date.now()
}));
} else {
this.client.client.send(JSON.stringify({
message: "command_line_response",
commandLine: null,
error: "Could not retrieve command line",
timestamp: Date.now()
}));
}
};
}
// server/JSClient/index.js
function JSClient(id, client, all_clients2, ensureMonitoringLoop2, historyCollector2) {
this.id = id;
this.client = client;
this.ensureMonitoringLoop = ensureMonitoringLoop2;
this.historyCollector = historyCollector2;
this.messageHandler = new MessageHandler(this);
this.sessionStatsManager = new SessionStatsManager(this);
this.sessionManager = new SessionManager(this);
this.filterManager = new FilterManager(this);
this.cpuStatsManager = new CpuStatsManager(this);
this.logManager = new LogManager(this);
this.commandLineManager = new CommandLineManager(this);
this.on_client_data = function(msg) {
this.messageHandler.handleMessage(msg, all_clients2);
};
this.sendMonitorConfig = function() {
this.client.send(JSON.stringify({
message: "monitor_config",
intervals: UPDATE_INTERVALS,
logRetention: LOG_RETENTION
}));
};
this.cleanup = function() {
try {
this.logManager.forceUnsubscribe();
this.sessionStatsManager.cleanup();
this.cpuStatsManager.cleanup();
this.filterManager.cleanup();
} catch (error) {
console.error(`JSClient ${this.id}: Error during cleanup:`, error);
}
};
}
// server/history/HistoryCollector.js
import { Sys as sys6 } from "gpaccore";
// server/history/HistoryWriter.js
import * as std3 from "std";
import * as os2 from "os";
// server/history/helpers/ChunkStream.js
import * as std2 from "std";
function ChunkStream(dir, prefix, maxDuration, maxCount) {
this._dir = dir;
this._prefix = prefix;
this._maxDuration = maxDuration;
this._maxCount = maxCount || Infinity;
this._file = null;
this._index = 0;
this._count = 0;
this._startUs = null;
this._lastUs = null;
this._completed = [];
this._path = function(index) {
const padded = String(index).padStart(4, "0");
return `${this._dir}/${this._prefix}_${padded}.jsonl`;
};
this._open = function() {
if (this._file) {
this._file.close();
this._file = null;
}
const path = this._path(this._index);
this._file = std2.open(path, "a");
if (!this._file) {
print(`[ChunkStream] Failed to open ${path}`);
}
this._count = 0;
this._startUs = null;
};
this._finalizeCurrentChunk = function() {
const dirName = this._dir.split("/").pop();
return {
file: `${dirName}/${this._prefix}_${String(this._index).padStart(4, "0")}.jsonl`,
fromUs: this._startUs,
toUs: this._lastUs,
count: this._count
};
};
this.write = function(line, tsUs) {
if (!this._file) this._open();
if (!this._file) return false;
if (this._startUs === null) this._startUs = tsUs;
this._file.puts(line + "\n");
this._count++;
this._lastUs = tsUs;
const shouldRotate = tsUs - this._startUs >= this._maxDuration || this._count >= this._maxCount;
if (shouldRotate) {
this._completed.push(this._finalizeCurrentChunk());
this._index++;
this._open();
return true;
}
return false;
};
this.getChunkCount = function() {
return this._index + 1;
};
this.getAllChunks = function() {
return this._completed.slice();
};
this.getAllChunksIncludingOpen = function() {
const all = this._completed.slice();
if (this._file && this._count > 0) all.push(this._finalizeCurrentChunk());
return all;
};
this.close = function() {
if (this._file) {
if (this._count > 0) this._completed.push(this._finalizeCurrentChunk());
this._file.close();
this._file = null;
}
};
this._open();
}
// server/config/history.config.js
var RATE_LIMIT_US = 1e3 * 1e3;
var CHUNK_DURATION_US = 10 * 1e3 * 1e3;
var MAX_LOG_PER_CHUNK = 5e3;
var MAX_LOG_MESSAGE_LENGTH = 500;
var MANIFEST_REFRESH_US = 2 * 1e3 * 1e3;
// server/history/HistoryWriter.js
function writeFileAtomic(path, content) {
const tmpPath = `${path}.tmp`;
const tmpFile = std3.open(tmpPath, "w");
if (!tmpFile) {
print(`[HistoryWriter] Failed to write ${tmpPath}`);
return;
}
tmpFile.puts(content);
tmpFile.close();
const err = os2.rename(tmpPath, path);
if (err !== 0) {
print(`[HistoryWriter] Failed to rename ${tmpPath} to ${path} (errno ${err})`);
}
}
function formatSessionId(date) {
const pad = (value) => String(value).padStart(2, "0");
const datePart = `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`;
const timePart = `${pad(date.getHours())}-${pad(date.getMinutes())}-${pad(date.getSeconds())}`;
return `${datePart}_${timePart}`;
}
function HistoryWriter(historyDir, sessionId) {
const baseDir = historyDir || "history";
const id = sessionId || formatSessionId(/* @__PURE__ */ new Date());
const dir = `${baseDir}/${id}`;
const chunksDir = `${dir}/chunks`;
const logsDir = `${dir}/logs`;
const checkpointsDir = `${dir}/checkpoints`;
this.sessionId = id;
this.sessionDir = dir;
this.snapshotPath = `${dir}/snapshot.json`;
this._initialized = false;
this._events = null;
this._logs = null;
this._sessionStartUs = null;
this._lastEventUs = null;
this._checkpoints = [];
this._eventsIndex = [];
this._journalTsUs = [];
this._journalTypes = [];
this._lastManifestUs = null;
this._init = function() {
if (this._initialized) return;
this._initialized = true;
try {
os2.mkdir(baseDir);
} catch (_e) {
}
try {
os2.mkdir(dir);
} catch (_e) {
}
try {
os2.mkdir(chunksDir);
} catch (_e) {
}
try {
os2.mkdir(logsDir);
} catch (_e) {
}
try {
os2.mkdir(checkpointsDir);
} catch (_e) {
}
const [, statErr] = os2.stat(dir);
if (statErr !== 0) print(`[HistoryWriter] Cannot create ${dir} \u2014 check the -rmt-log path and permissions`);
else print(`[HistoryWriter] Recording session ${id} to ${dir}/`);
this._events = new ChunkStream(chunksDir, "chunk", CHUNK_DURATION_US);
this._logs = new ChunkStream(logsDir, "logs", CHUNK_DURATION_US, MAX_LOG_PER_CHUNK);
};
this._updateTimestamps = function(tsUs) {
if (this._sessionStartUs === null) this._sessionStartUs = tsUs;
this._lastEventUs = tsUs;
};
this.addEventIndex = function(tsUs, type, metadata) {
if (!Number.isFinite(tsUs)) return;
this._eventsIndex.push(metadata ? { ts_us: tsUs, type, ...metadata } : { ts_us: tsUs, type });
};
this.recordJournalFact = function(tsUs, type) {
if (!Number.isFinite(tsUs)) return;
this._journalTsUs.push(tsUs);
this._journalTypes.push(type);
};
this._writeJournalIndex = function() {
if (this._journalTsUs.length === 0) return;
const baseTsUs = this._journalTsUs[0];
const journalIndex = {
baseTsUs,
tsDeltaUs: this._journalTsUs.map((tsUs) => tsUs - baseTsUs),
types: this._journalTypes
};
writeFileAtomic(`${dir}/journal_index.json`, JSON.stringify(journalIndex) + "\n");
};
this._getJournalPointer = function() {
if (this._journalTsUs.length === 0) return void 0;
const errorCount = this._journalTypes.filter((type) => type === 1).length;
const warningCount = this._journalTypes.filter((type) => type === 2).length;
return {
file: "journal_index.json",
format: "columnar-delta-v1",
eventCount: this._journalTsUs.length,
errorCount,
warningCount
};
};
this._writeManifest = function() {
const manifest = {
version: 1,
startUs: this._sessionStartUs,
endUs: this._lastEventUs,
chunkDurationUs: CHUNK_DURATION_US,
chunkCount: this._events ? this._events.getChunkCount() : 1,
snapshot: "snapshot.json",
checkpoints: this._checkpoints,
eventChunks: this._events ? this._events.getAllChunksIncludingOpen() : [],
logChunks: this._logs ? this._logs.getAllChunksIncludingOpen() : [],
eventsIndex: [...this._eventsIndex].sort((a, b) => a.ts_us - b.ts_us),
journalIndex: this._getJournalPointer()
};
this._writeJournalIndex();
writeFileAtomic(`${dir}/manifest.json`, JSON.stringify(manifest) + "\n");
this._lastManifestUs = this._lastEventUs;
};
this._maybeWriteManifest = function() {
if (this.getCurrentChunkIndex() !== 0) return;
if (this._lastManifestUs !== null && this._lastEventUs - this._lastManifestUs < MANIFEST_REFRESH_US) return;
this._writeManifest();
};
this.writeSnapshot = function(obj) {
this._init();
const snapshotFile = std3.open(this.snapshotPath, "w");
if (!snapshotFile) {
print(`[HistoryWriter] Failed to write snapshot`);
return;
}
snapshotFile.puts(JSON.stringify(obj) + "\n");
snapshotFile.close();
};
this.writeLog = function(jsonString, tsUs) {
this._init();
this._updateTimestamps(tsUs);
if (this._logs.write(jsonString, tsUs)) this._writeManifest();
else this._maybeWriteManifest();
};
this.writeEvent = function(jsonString, tsUs) {
this._init();
this._updateTimestamps(tsUs);
const rotated = this._events.write(jsonString, tsUs);
if (rotated) this._writeManifest();
else this._maybeWriteManifest();
return rotated;
};
this.writeCheckpoint = function(chunkIndex, obj) {
const padded = String(chunkIndex).padStart(4, "0");
const file = `checkpoints/cp_${padded}.json`;
const cpFile = std3.open(`${dir}/${file}`, "w");
if (!cpFile) {
print(`[HistoryWriter] Failed to write checkpoint ${file}`);
return;
}
cpFile.puts(JSON.stringify(obj) + "\n");
cpFile.close();
this._checkpoints.push({ chunkIndex, file });
this._writeManifest();
};
this.getCurrentChunkIndex = function() {
return this._events ? this._events._index : 0;
};
this.getCurrentLogChunkIndex = function() {
return this._logs ? this._logs._index : 0;
};
this.close = function() {
if (this._events) this._events.close();
if (this._logs) this._logs.close();
this._writeManifest();
const doneFile = std3.open(`${dir}/done`, "w");
if (doneFile) doneFile.close();
};
}
// server/history/HistoryCollector.js
var EVENT_VERSION = 1;
var LOG_ID = "_hist_";
var LOG_LEVEL_ERROR = 1;
var LOG_LEVEL_WARNING = 2;
function HistoryCollector(historyDir) {
this.enabled = !!historyDir;
this.writer = new HistoryWriter(historyDir);
this.pidCollector = new PidDataCollector();
this.snapshotWritten = false;
this.lastRecordUs = 0;
this.lastCpuRecordUs = 0;
this.pendingLogs = [];
this.logBatchTimer = null;
this._latestStructural = null;
this._currentPidState = null;
this._currentArgState = {};
this._chunkNeedsCheckpoint = false;
this._lastEventTsUs = 0;
this._writeCheckpointIfNeeded = function(chunkIndex, tsUs) {
if (!this._latestStructural) return;
if (!this._chunkNeedsCheckpoint) return;
if (chunkIndex <= 0) {
this._chunkNeedsCheckpoint = false;
return;
}
const checkpoint = {
version: this._latestStructural.version,
ts_us: tsUs,
graph_v: this._latestStructural.graph_v,
filters: this._latestStructural.filters,
pid_state: this._currentPidState,
metric_defs: session.session_metrics || null
};
if (Object.keys(this._currentArgState).length > 0) {
checkpoint.arg_state = this._currentArgState;
this._currentArgState = {};
}
this.writer.writeCheckpoint(chunkIndex, checkpoint);
this._chunkNeedsCheckpoint = false;
};
this._onChunkRotated = function() {
const newChunkIndex = this.writer.getCurrentChunkIndex();
this._writeCheckpointIfNeeded(newChunkIndex, this._lastEventTsUs);
};
this.startLogCapture = function() {
this.initialLogConfig = sys6.get_logs(true);
logHub.add(LOG_ID, this);
};
this.writeSnapshot = function(data) {
if (this.snapshotWritten) return;
data.log_config = this.initialLogConfig || null;
this.writer.writeSnapshot(data);
this.snapshotWritten = true;
};
this.recordGraph = function(filters, filterInstances, graphVersion) {
const pidCollector2 = new PidDataCollector();
const eventFilters = filters.map((filter, index) => {
const { ipid, opid, gpac_args, ...rest } = filter;
const filterInstance = filterInstances[index];
return {
...rest,
ipids: ipid ?? [],
opids: opid ?? [],
gpac_args: filterInstance.all_args(true).filter(Boolean),
properties: {
ipids: pidCollector2.collectInputPids(filterInstance, true),
opids: pidCollector2.collectOutputPids(filterInstance)
}
};
});
const checkpointFilters = eventFilters.map((filter) => {
const { gpac_args, ...rest } = filter;
const strippedIpids = Object.fromEntries(
Object.entries(filter.properties.ipids).map(([k, v]) => {
const { properties, ...pidRest } = v;
return [k, pidRest];
})
);
return {
...rest,
properties: { ...filter.properties, ipids: strippedIpids }
};
});
const filtersTsUs = sys6.clock_us();
this.writer.addEventIndex(filtersTsUs, "graph-change");
if (!this.snapshotWritten) {
this.writeSnapshot({
version: EVENT_VERSION,
ts_us: filtersTsUs,
command_line: null,
graph_v: graphVersion,
filters: eventFilters
});
}
const rotated = this.writer.writeEvent(JSON.stringify({
version: EVENT_VERSION,
message: "filters",
ts_us: filtersTsUs,
graph_v: graphVersion,
filters: eventFilters
}), filtersTsUs);
this._lastEventTsUs = filtersTsUs;
this._latestStructural = {
version: EVENT_VERSION,
graph_v: graphVersion,
filters: checkpointFilters
};
this._currentPidState = eventFilters.reduce((acc, filter) => {
const allPidProperties = {};
for (const [key, pid] of Object.entries(filter.properties.ipids)) {
if (pid.properties) allPidProperties[key] = pid.properties;
}
if (Object.keys(allPidProperties).length > 0) acc[filter.idx] = allPidProperties;
return acc;
}, {});
this._currentArgState = {};
this._chunkNeedsCheckpoint = true;
if (rotated) this._onChunkRotated(filtersTsUs);
};
this.recordSessionStats = function(payload, force) {
const ts_us = sys6.clock_us();
if (!force && ts_us - this.lastRecordUs < RATE_LIMIT_US) return;
this.lastRecordUs = ts_us;
const filterMap = {};
session.lock_filters(true);
for (let i = 0; i < session.nb_filters; i++) {
const f = session.get_filter(i);
if (!f.is_destroyed()) filterMap[f.idx] = f;
}
const enrichedStats = payload.stats.map((stat3) => {
const f = filterMap[stat3.idx];
if (!f) return stat3;
const entry = { ...stat3 };
if (f.nb_ipid > 0) entry.ipids = this.pidCollector.collectInputPids(f, false, true);
if (f.nb_opid > 0) entry.opids = this.pidCollector.collectOutputPids(f, true);
return entry;
});
session.lock_filters(false);
const rotated = this.writer.writeEvent(JSON.stringify({
version: EVENT_VERSION,
message: "session_stats",
ts_us,
all_packets_done: payload.all_packets_done,
stats: enrichedStats
}), ts_us);
this._lastEventTsUs = ts_us;
if (rotated) this._onChunkRotated(ts_us);
};
this.recordCpuStats = function(payload) {
const cpuTsUs = sys6.clock_us();
if (cpuTsUs - this.lastCpuRecordUs < RATE_LIMIT_US) return;
this.lastCpuRecordUs = cpuTsUs;
const rotated = this.writer.writeEvent(JSON.stringify({
version: EVENT_VERSION,
message: "cpu_stats",
ts_us: cpuTsUs,
...payload
}), cpuTsUs);
this._lastEventTsUs = cpuTsUs;
if (rotated) this._onChunkRotated(cpuTsUs);
};
this.recordPidReconfigured = function(indexes, pidsByFilter) {
const tsUs = sys6.clock_us();
this.writer.addEventIndex(tsUs, "pid-reconfig");
if (!this._currentPidState) {
this._currentPidState = {};
}
for (const idx of indexes) {
if (pidsByFilter[idx]) {
const allPidProperties = {};
for (const [key, pid] of Object.entries(pidsByFilter[idx])) {
if (pid.properties) allPidProperties[key] = pid.properties;
}
if (Object.keys(allPidProperties).length > 0) this._currentPidState[idx] = allPidProperties;
}
}
if (indexes.length > 0) {
this._chunkNeedsCheckpoint = true;
}
const rotated = this.writer.writeEvent(JSON.stringify({
version: EVENT_VERSION,
message: "filter_pid_reconfigured",
ts_us: tsUs,
indexes,
pidsByFilter
}), tsUs);
this._lastEventTsUs = tsUs;
if (rotated) this._onChunkRotated(tsUs);
};
this.recordArgUpdated = function(indexes, argsByFilter) {
const tsUs = sys6.clock_us();
this.writer.addEventIndex(tsUs, "args-change");
for (const idx of indexes) {
if (argsByFilter[idx]) {
this._currentArgState[idx] = argsByFilter[idx];
}
}
if (indexes.length > 0) {
this._chunkNeedsCheckpoint = true;
}
const rotated = this.writer.writeEvent(JSON.stringify({
version: EVENT_VERSION,
message: "filter_arg_updated",
ts_us: tsUs,
indexes,
argsByFilter
}), tsUs);
this._lastEventTsUs = tsUs;
if (rotated) this._onChunkRotated(tsUs);
};
this.recordFilterArgsUpdate = function(filterIdx, argName, newValue) {
const argsTsUs = sys6.clock_us();
const rotated = this.writer.writeEvent(JSON.stringify({
version: EVENT_VERSION,
message: "filter_args_update",
ts_us: argsTsUs,
payload: { filter_idx: filterIdx, arg_name: argName, value: newValue }
}), argsTsUs);
this._lastEventTsUs = argsTsUs;
if (rotated) this._onChunkRotated(argsTsUs);
};
this.recordLogConfigChanged = function(logLevel) {
const tsUs = sys6.clock_us();
this.writer.writeLog(JSON.stringify({
version: EVENT_VERSION,
message: "log_config_changed",
ts_us: tsUs,
logLevel
}), tsUs);
};
this.handleLog = function(tool, level, message, thread_id, caller) {
this.pendingLogs.push({
timestamp: sys6.clock_us(),
tool,
level,
message: message?.length > MAX_LOG_MESSAGE_LENGTH ? message.substring(0, MAX_LOG_MESSAGE_LENGTH) + "..." : message,
thread_id,
caller: caller?.idx !== void 0 ? caller.idx : caller?.name || null
});
if (!this.logBatchTimer) {
this.logBatchTimer = true;
session.post_task(() => {
this.flushLogs();
return false;
});
}
};
this.flushLogs = function() {
if (this.pendingLogs.length) {
const tsUs = sys6.clock_us();
this.writer.writeLog(JSON.stringify({
version: EVENT_VERSION,
message: "log_batch",
ts_us: tsUs,
logs: this.pendingLogs
}), tsUs);
this.pendingLogs.forEach((log) => {
if (log.level !== LOG_LEVEL_ERROR && log.level !== LOG_LEVEL_WARNING) return;
this.writer.recordJournalFact(log.timestamp, log.level);
});
this.pendingLogs = [];
}
this.logBatchTimer = null;
};
this.sendToClient = function(data) {
if (data.message === "log_config_changed") {
this.recordLogConfigChanged(data.logLevel);
}
};
this.close = function() {
logHub.remove(LOG_ID);
this.flushLogs();
this.writer.close();
};
if (!this.enabled) {
this.snapshotWritten = true;
for (const key in this) {
if (typeof this[key] === "function") this[key] = () => {
};
}
}
}
// server/GraphManager.js
import { Sys as sys8 } from "gpaccore";
// server/history/SnapshotBuilder.js
import { Sys as sys7 } from "gpaccore";
function SnapshotBuilder() {
this.pidCollector = new PidDataCollector();
this.buildFilterEntry = function(f) {
const entry = gpac_filter_to_object(f, true);
delete entry.ipid;
delete entry.opid;
const minimal = gpac_filter_to_minimal_object(f);
entry.ipids = minimal.ipid;
entry.opids = minimal.opid;
entry.properties = {
ipids: this.pidCollector.collectInputPids(f, true),
opids: this.pidCollector.collectOutputPids(f)
};
return entry;
};
this.build = function(graphVersion, commandLine) {
const filters = [];
session.lock_filters(true);
for (let i = 0; i < session.nb_filters; i++) {
const f = session.get_filter(i);
if (!f.is_destroyed()) filters.push(this.buildFilterEntry(f));
}
session.lock_filters(false);
return {
version: 1,
ts_us: sys7.clock_us(),
command_line: commandLine,
graph_v: graphVersion,
filters,
session_metrics: session.session_metrics || null
};
};
}
// server/GraphManager.js
var GRAPH_DEBOUNCE_US = 500 * 1e3;
var GRAPH_MAX_WAIT_US = 3e3 * 1e3;
function GraphManager(deps) {
const { getClients, historyCollector: historyCollector2, ensureMonitoringLoop: ensureMonitoringLoop2 } = deps;
let graphVersion = 0;
let graphDirty = false;
let lastGraphEventTime = 0;
let graphBuildStartTime = 0;
let debounceRunning = false;
const snapshotBuilder = new SnapshotBuilder();
this.getGraphVersion = function() {
return graphVersion;
};
this.onGraphEvent = function() {
const now = sys8.clock_us();
graphDirty = true;
lastGraphEventTime = now;
if (!graphBuildStartTime) graphBuildStartTime = now;
ensureMonitoringLoop2();
if (debounceRunning) return;
debounceRunning = true;
session.post_task(() => {
const now2 = sys8.clock_us();
const sinceLast = now2 - lastGraphEventTime;
const sinceBuildStart = now2 - graphBuildStartTime;
const graphStable = sinceLast >= GRAPH_DEBOUNCE_US;
const maxBuildWaitReached = sinceBuildStart >= GRAPH_MAX_WAIT_US;
if (graphStable || maxBuildWaitReached) {
this._stabilize();
debounceRunning = false;
graphBuildStartTime = 0;
return false;
}
return 100;
});
};
this._stabilize = function() {
graphDirty = false;
graphVersion++;
on_all_connected((allFilterInstances) => {
session.lock_filters(true);
const filters = allFilterInstances.map((f) => gpac_filter_to_minimal_object(f));
session.lock_filters(false);
if (!historyCollector2.snapshotWritten) {
let commandLine = null;
try {
commandLine = sys8.args ? sys8.args.join(" ") : null;
} catch (_e) {
}
const snapshot = snapshotBuilder.build(graphVersion, commandLine);
historyCollector2.writeSnapshot(snapshot);
}
historyCollector2.recordGraph(filters, allFilterInstances, graphVersion);
const filtersMsg = JSON.stringify({ message: "filters", filters });
const notifMsg = JSON.stringify({
message: "notification",
type: "graph_changed",
graphVersion
});
for (const client of getClients()) {
if (client.client) {
client.client.send(filtersMsg);
client.client.send(notifMsg);
}
}
});
};
}
// server/JSClient/Session/buildSessionStatsPayload.js
function buildSessionStatsPayload(session2, fields) {
const resolvedFields = fields || DEFAULT_FILTER_FIELDS;
const stats = [];
const filters = [];
session2.lock_filters(true);
for (let i = 0; i < session2.nb_filters; i++) {
const f = session2.get_filter(i);
if (f.is_destroyed()) continue;
filters.push(f);
const obj = {};
for (const field of resolvedFields) obj[field] = f[field];
let allInputsEos = f.nb_ipid > 0;
for (let j = 0; j < f.nb_ipid; j++) {
if (!f.ipid_props(j, "eos")) {
allInputsEos = false;
break;
}
}
obj.is_eos = allInputsEos;
obj.last_ts_sent = f.last_ts_sent || null;
stats.push(obj);
}
let allFiltersEos = filters.length > 0;
for (const f of filters) {
if (f.nb_ipid === 0) continue;
for (let i = 0; i < f.nb_ipid; i++) {
if (!f.ipid_props(i, "eos")) {
allFiltersEos = false;
break;
}
}
}
const all_packets_done = session2.last_task && allFiltersEos;
session2.lock_filters(false);
return { all_packets_done, stats };
}
// server/server.js
var recordPath = sys9.get_opt("core", "rmt-log");
var historyCollector = new HistoryCollector(recordPath);
print(recordPath ? `[History] Recording enabled -> ${recordPath}` : "[History] Recording disabled (no -rmt-log)");
var all_clients = [];
var cid = 0;
var filter_uid = 0;
var all_filters = [];
var graphManager = new GraphManager({
getClients: () => all_clients,
historyCollector,
ensureMonitoringLoop
});
var monitoringRunning = false;
function ensureMonitoringLoop() {
if (monitoringRunning) return;
monitoringRunning = true;
session.post_task(() => {
const now = sys9.clock_us();
if (session.last_task) {
for (const client of all_clients) client.sessionManager.handleSessionEnd(now);
historyCollector.recordSessionStats(buildSessionStatsPayload(session), true);
historyCollector.recordCpuStats(buildCpuStatsPayload());
historyCollector.close();
monitoringRunning = false;
return false;
}
let active = false;
let interval = 1e3;
for (const client of all_clients) {
client.sessionManager.tick(now);
if (client.sessionManager.hasActiveSubscriptions()) {
active = true;
interval = Math.min(interval, client.sessionManager.getMinInterval());
}
}
historyCollector.recordSessionStats(buildSessionStatsPayload(session));
historyCollector.recordCpuStats(buildCpuStatsPayload());
return active ? interval : 1e3;
});
}
session.reporting(true);
historyCollector.startLogCapture();
var remove_client = function(client_id) {
for (let i = 0; i < all_clients.length; i++) {
if (all_clients[i].id == client_id) {
all_clients.splice(i, 1);
return;
}
}
};
session.set_new_filter_fun((f) => {
f.idx = filter_uid++;
f.iname = "" + f.idx;
all_filters.push(f);
if (f.itag == "NODISPLAY") return;
graphManager.onGraphEvent();
});
session.set_del_filter_fun((f) => {
let idx = all_filters.indexOf(f);
if (idx >= 0) all_filters.splice(idx, 1);
if (f.itag == "NODISPLAY") return;
graphManager.onGraphEvent();
});
var pidCollector = new PidDataCollector();
var pidReconfigured = /* @__PURE__ */ new Set();
session.set_filter_pid_modified_fun((f) => {
pidReconfigured.add(f.idx);
if (pidReconfigured.size > 1) return;
session.post_task(() => {
const indexes = [...pidReconfigured];
pidReconfigured.clear();
const pidsByFilter = {};
for (const idx of indexes) {
const filter = all_filters.find((f2) => f2.idx === idx);
if (filter) pidsByFilter[idx] = pidCollector.collectInputPids(filter, true);
}
const msg = JSON.stringify({ message: "filter_pid_reconfigured", indexes });
for (const c of all_clients) if (c.client) c.client.send(msg);
historyCollector.recordPidReconfigured(indexes, pidsByFilter);
return false;
});
});
var argUpdated = /* @__PURE__ */ new Set();
session.set_filter_arg_updated_fun((f) => {
argUpdated.add(f.idx);
if (argUpdated.size > 1) return;
session.post_task(() => {
const indexes = [...argUpdated];
argUpdated.clear();
const argsByFilter = {};
for (const idx of indexes) {
const filter = all_filters.find((f2) => f2.idx === idx);
if (filter) argsByFilter[idx] = filter.all_args(true).filter(Boolean);
}
const msg = JSON.stringify({ message: "filter_arg_updated", indexes });
for (const c of all_clients) if (c.client) c.client.send(msg);
historyCollector.recordArgUpdated(indexes, argsByFilter);
return false;
});
});
sys9.rmt_on_new_client = function(client) {
let js_client = new JSClient(++cid, client, all_clients, ensureMonitoringLoop, historyCollector);
all_clients.push(js_client);
js_client.sendMonitorConfig();
js_client.client.on_data = (msg) => {
if (typeof msg == "string")
js_client.on_client_data(msg);
};
js_client.client.on_close = function() {
js_client.cleanup();
remove_client(js_client.id);
js_client.client = null;
};
};