559 lines
21 KiB
C
559 lines
21 KiB
C
/*
|
|
* process_info.c — process and per-tab performance diagnostics
|
|
*
|
|
* See process_info.h for the two-layer design. This file implements:
|
|
*
|
|
* Layer 1 — /proc enumeration for the main PID, WebKit child PIDs
|
|
* (discovered via webkit_web_view_get_process_id() per tab
|
|
* plus a PPID scan for network/gpu/storage processes), and
|
|
* managed Tor/FIPS PIDs from net_services. CPU% is computed
|
|
* from utime+stime deltas between successive calls.
|
|
*
|
|
* Layer 2 — storage of the most recent perf-probe.js report per tab
|
|
* index, exposed to the UI and MCP tools.
|
|
*
|
|
* All /proc parsing is Linux-specific and uses only libc + glib.
|
|
*/
|
|
|
|
#include "process_info.h"
|
|
#include "tab_manager.h"
|
|
#include "net_services.h"
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <time.h>
|
|
#include <dirent.h>
|
|
#include <ctype.h>
|
|
#include <sys/types.h>
|
|
|
|
#include <glib.h>
|
|
|
|
/* ── CPU% baseline tracking ──────────────────────────────────────────── */
|
|
|
|
typedef struct {
|
|
guint64 prev_jiffies; /* utime + stime (in clock ticks) */
|
|
gint64 prev_wall_us; /* g_get_monotonic_time() at last sample (microseconds) */
|
|
} cpu_sample_t;
|
|
|
|
static GHashTable *g_cpu_samples = NULL; /* pid (guint) -> cpu_sample_t* */
|
|
|
|
static cpu_sample_t *cpu_sample_get(guint pid) {
|
|
if (g_cpu_samples == NULL) {
|
|
g_cpu_samples = g_hash_table_new_full(g_direct_hash, g_direct_equal,
|
|
NULL, g_free);
|
|
}
|
|
cpu_sample_t *s = g_hash_table_lookup(g_cpu_samples, GUINT_TO_POINTER(pid));
|
|
if (s == NULL) {
|
|
s = g_new0(cpu_sample_t, 1);
|
|
g_hash_table_insert(g_cpu_samples, GUINT_TO_POINTER(pid), s);
|
|
}
|
|
return s;
|
|
}
|
|
|
|
static long g_clk_tck = 0;
|
|
static long clk_tck(void) {
|
|
if (g_clk_tck == 0) g_clk_tck = sysconf(_SC_CLK_TCK);
|
|
return g_clk_tck > 0 ? g_clk_tck : 100;
|
|
}
|
|
|
|
/* ── /proc readers ──────────────────────────────────────────────────── */
|
|
|
|
/* Read the first line of a /proc file into buf (NUL-terminated). */
|
|
static int read_proc_file(const char *path, char *buf, size_t buflen) {
|
|
FILE *f = fopen(path, "r");
|
|
if (f == NULL) return -1;
|
|
size_t n = fread(buf, 1, buflen - 1, f);
|
|
buf[n] = '\0';
|
|
fclose(f);
|
|
return (int)n;
|
|
}
|
|
|
|
/* Parse /proc/<pid>/stat. Fields (1-indexed, per man proc):
|
|
* (2) comm (3) state (4) ppid (14) utime (15) stime
|
|
* (22) starttime (in clock ticks since boot)
|
|
* comm is wrapped in parens and may contain spaces, so we parse by
|
|
* finding the last ')' and tokenizing after it. */
|
|
static int parse_stat(guint pid, char *state_out, size_t state_sz,
|
|
guint *ppid_out, guint64 *utime_out, guint64 *stime_out,
|
|
guint64 *starttime_out, char *comm_out, size_t comm_sz) {
|
|
char path[64];
|
|
snprintf(path, sizeof(path), "/proc/%u/stat", pid);
|
|
char buf[4096];
|
|
if (read_proc_file(path, buf, sizeof(buf)) < 0) return -1;
|
|
|
|
/* comm: between first '(' and last ')'. */
|
|
char *lp = strchr(buf, '(');
|
|
char *rp = strrchr(buf, ')');
|
|
if (lp == NULL || rp == NULL || rp <= lp) return -1;
|
|
size_t comm_len = (size_t)(rp - lp - 1);
|
|
if (comm_len >= comm_sz) comm_len = comm_sz - 1;
|
|
memcpy(comm_out, lp + 1, comm_len);
|
|
comm_out[comm_len] = '\0';
|
|
|
|
/* The rest after ") " is space-separated fields starting at field 3. */
|
|
char *rest = rp + 2;
|
|
/* rest = "state ppid ... " — tokenize. */
|
|
char *save = NULL;
|
|
char *tok = strtok_r(rest, " ", &save); /* field 3: state */
|
|
if (tok == NULL) return -1;
|
|
if (state_out) {
|
|
size_t sl = strlen(tok);
|
|
if (sl >= state_sz) sl = state_sz - 1;
|
|
memcpy(state_out, tok, sl);
|
|
state_out[sl] = '\0';
|
|
}
|
|
tok = strtok_r(NULL, " ", &save); /* field 4: ppid */
|
|
if (tok == NULL) return -1;
|
|
if (ppid_out) *ppid_out = (guint)strtoul(tok, NULL, 10);
|
|
|
|
/* Fields 5..13 are pgrp, session, tty, tpgid, flags, minflt, cminflt,
|
|
* majflt, cmajflt. Skip them. */
|
|
for (int i = 0; i < 9; i++) strtok_r(NULL, " ", &save);
|
|
tok = strtok_r(NULL, " ", &save); /* field 14: utime */
|
|
if (tok == NULL) return -1;
|
|
if (utime_out) *utime_out = strtoull(tok, NULL, 10);
|
|
tok = strtok_r(NULL, " ", &save); /* field 15: stime */
|
|
if (tok == NULL) return -1;
|
|
if (stime_out) *stime_out = strtoull(tok, NULL, 10);
|
|
|
|
/* Fields 16..21: cutime, cstime, priority, nice, numthreads, itrealvalue. */
|
|
for (int i = 0; i < 6; i++) strtok_r(NULL, " ", &save);
|
|
tok = strtok_r(NULL, " ", &save); /* field 22: starttime */
|
|
if (tok == NULL) return -1;
|
|
if (starttime_out) *starttime_out = strtoull(tok, NULL, 10);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Read a named field from /proc/<pid>/status (e.g. "VmRSS:"). Returns
|
|
* the integer value in KB, or -1 if not found. */
|
|
static long status_field_kb(guint pid, const char *field) {
|
|
char path[64];
|
|
snprintf(path, sizeof(path), "/proc/%u/status", pid);
|
|
FILE *f = fopen(path, "r");
|
|
if (f == NULL) return -1;
|
|
char line[256];
|
|
long val = -1;
|
|
size_t flen = strlen(field);
|
|
while (fgets(line, sizeof(line), f)) {
|
|
if (strncmp(line, field, flen) == 0) {
|
|
/* e.g. "VmRSS: 12345 kB" */
|
|
char *p = line + flen;
|
|
while (*p == ' ' || *p == '\t') p++;
|
|
val = strtol(p, NULL, 10);
|
|
break;
|
|
}
|
|
}
|
|
fclose(f);
|
|
return val;
|
|
}
|
|
|
|
/* Parse /proc/<pid>/smaps_rollup for Pss / Private_Clean+Private_Dirty.
|
|
* Returns 0 on success. */
|
|
static int parse_smaps_rollup(guint pid, long *pss_kb, long *uss_kb) {
|
|
char path[64];
|
|
snprintf(path, sizeof(path), "/proc/%u/smaps_rollup", pid);
|
|
FILE *f = fopen(path, "r");
|
|
if (f == NULL) return -1;
|
|
char line[256];
|
|
long pss = -1, priv_clean = 0, priv_dirty = 0;
|
|
while (fgets(line, sizeof(line), f)) {
|
|
if (strncmp(line, "Pss:", 4) == 0) {
|
|
pss = strtol(line + 4, NULL, 10);
|
|
} else if (strncmp(line, "Private_Clean:", 14) == 0) {
|
|
priv_clean = strtol(line + 14, NULL, 10);
|
|
} else if (strncmp(line, "Private_Dirty:", 14) == 0) {
|
|
priv_dirty = strtol(line + 14, NULL, 10);
|
|
}
|
|
}
|
|
fclose(f);
|
|
if (pss_kb) *pss_kb = pss;
|
|
if (uss_kb) *uss_kb = priv_clean + priv_dirty;
|
|
return (pss < 0) ? -1 : 0;
|
|
}
|
|
|
|
/* Read /proc/<pid>/io fields. Returns 0 on success. */
|
|
static int parse_io(guint pid, long *read_bytes, long *write_bytes) {
|
|
char path[64];
|
|
snprintf(path, sizeof(path), "/proc/%u/io", pid);
|
|
FILE *f = fopen(path, "r");
|
|
if (f == NULL) return -1;
|
|
char line[256];
|
|
long rb = 0, wb = 0;
|
|
while (fgets(line, sizeof(line), f)) {
|
|
if (strncmp(line, "read_bytes:", 11) == 0) {
|
|
rb = strtoll(line + 11, NULL, 10);
|
|
} else if (strncmp(line, "write_bytes:", 12) == 0) {
|
|
wb = strtoll(line + 12, NULL, 10);
|
|
}
|
|
}
|
|
fclose(f);
|
|
if (read_bytes) *read_bytes = rb;
|
|
if (write_bytes) *write_bytes = wb;
|
|
return 0;
|
|
}
|
|
|
|
/* Read /proc/<pid>/cmdline (NUL-separated args) into a space-joined buf. */
|
|
static void read_cmdline(guint pid, char *buf, size_t buflen) {
|
|
char path[64];
|
|
snprintf(path, sizeof(path), "/proc/%u/cmdline", pid);
|
|
FILE *f = fopen(path, "r");
|
|
if (f == NULL) { buf[0] = '\0'; return; }
|
|
size_t n = fread(buf, 1, buflen - 1, f);
|
|
fclose(f);
|
|
if (n == 0) { buf[0] = '\0'; return; }
|
|
buf[n] = '\0';
|
|
/* Replace NUL separators with spaces. */
|
|
for (size_t i = 0; i < n; i++) {
|
|
if (buf[i] == '\0') buf[i] = ' ';
|
|
}
|
|
/* Trim trailing space. */
|
|
while (n > 0 && buf[n - 1] == ' ') buf[--n] = '\0';
|
|
}
|
|
|
|
/* Count entries in a /proc directory (used for threads and fds). */
|
|
static int count_dir_entries(const char *path) {
|
|
DIR *d = opendir(path);
|
|
if (d == NULL) return -1;
|
|
int count = 0;
|
|
struct dirent *e;
|
|
while ((e = readdir(d)) != NULL) {
|
|
if (e->d_name[0] == '.') continue;
|
|
count++;
|
|
}
|
|
closedir(d);
|
|
return count;
|
|
}
|
|
|
|
/* Read /proc/stat's btime (boot time in seconds since epoch). */
|
|
static time_t g_btime = 0;
|
|
static time_t boot_time(void) {
|
|
if (g_btime != 0) return g_btime;
|
|
FILE *f = fopen("/proc/stat", "r");
|
|
if (f == NULL) return 0;
|
|
char line[256];
|
|
while (fgets(line, sizeof(line), f)) {
|
|
if (strncmp(line, "btime", 5) == 0) {
|
|
g_btime = (time_t)strtoll(line + 5, NULL, 10);
|
|
break;
|
|
}
|
|
}
|
|
fclose(f);
|
|
return g_btime;
|
|
}
|
|
|
|
/* ── Probe storage (Layer 2) ────────────────────────────────────────── */
|
|
|
|
typedef struct {
|
|
cJSON *probe; /* most recent probe report (owned) */
|
|
time_t ts; /* when recorded */
|
|
} probe_slot_t;
|
|
|
|
/* Sparse array indexed by tab index. Grows as needed. */
|
|
static probe_slot_t *g_probes = NULL;
|
|
static int g_probe_cap = 0;
|
|
|
|
static void probe_ensure(int idx) {
|
|
if (idx < 0) return;
|
|
if (idx >= g_probe_cap) {
|
|
int new_cap = g_probe_cap == 0 ? 16 : g_probe_cap;
|
|
while (new_cap <= idx) new_cap *= 2;
|
|
probe_slot_t *arr = g_realloc(g_probes, new_cap * sizeof(probe_slot_t));
|
|
if (arr == NULL) return;
|
|
for (int i = g_probe_cap; i < new_cap; i++) {
|
|
arr[i].probe = NULL;
|
|
arr[i].ts = 0;
|
|
}
|
|
g_probes = arr;
|
|
g_probe_cap = new_cap;
|
|
}
|
|
}
|
|
|
|
void process_info_record_probe(int tab_index, cJSON *probe) {
|
|
if (tab_index < 0 || probe == NULL) {
|
|
if (probe) cJSON_Delete(probe);
|
|
return;
|
|
}
|
|
probe_ensure(tab_index);
|
|
if (tab_index >= g_probe_cap) {
|
|
cJSON_Delete(probe);
|
|
return;
|
|
}
|
|
if (g_probes[tab_index].probe) {
|
|
cJSON_Delete(g_probes[tab_index].probe);
|
|
}
|
|
g_probes[tab_index].probe = probe; /* take ownership */
|
|
g_probes[tab_index].ts = time(NULL);
|
|
}
|
|
|
|
/* Return a *reference* (not a copy) to the stored probe for a tab, or
|
|
* NULL. Caller must NOT free. */
|
|
static cJSON *probe_get(int tab_index) {
|
|
if (tab_index < 0 || tab_index >= g_probe_cap) return NULL;
|
|
return g_probes[tab_index].probe;
|
|
}
|
|
|
|
/* Deep-copy a probe (so the caller can free independently). */
|
|
static cJSON *probe_copy(int tab_index) {
|
|
cJSON *p = probe_get(tab_index);
|
|
if (p == NULL) return NULL;
|
|
char *s = cJSON_PrintUnformatted(p);
|
|
cJSON *copy = cJSON_Parse(s);
|
|
free(s);
|
|
return copy;
|
|
}
|
|
|
|
/* ── Process enumeration ────────────────────────────────────────────── */
|
|
|
|
/* Ownership tag for a discovered PID. */
|
|
typedef enum {
|
|
OWN_MAIN,
|
|
OWN_WEBKIT_RENDERER,
|
|
OWN_WEBKIT_NETWORK,
|
|
OWN_WEBKIT_GPU,
|
|
OWN_WEBKIT_STORAGE,
|
|
OWN_TOR,
|
|
OWN_FIPS,
|
|
OWN_OTHER
|
|
} proc_own_t;
|
|
|
|
/* Build a cJSON object for one process. `own` and `service_state`
|
|
* (NULL for non-services) are caller-supplied. `hosted_tabs` is an
|
|
* array (may be NULL) for renderers. */
|
|
static cJSON *build_proc_obj(guint pid, proc_own_t own,
|
|
const char *service_state,
|
|
cJSON *hosted_tabs) {
|
|
char comm[256] = "";
|
|
char state[8] = "?";
|
|
guint ppid = 0;
|
|
guint64 utime = 0, stime = 0, starttime = 0;
|
|
if (parse_stat(pid, state, sizeof(state), &ppid, &utime, &stime,
|
|
&starttime, comm, sizeof(comm)) != 0) {
|
|
return NULL;
|
|
}
|
|
|
|
/* CPU% via delta. Use g_get_monotonic_time() (microsecond
|
|
* resolution) instead of time(NULL) (1-second resolution) so the
|
|
* value updates on every poll even when the poll interval is ~1s. */
|
|
cpu_sample_t *s = cpu_sample_get(pid);
|
|
guint64 cur_jiffies = utime + stime;
|
|
gint64 now_us = g_get_monotonic_time();
|
|
double cpu_pct = 0.0;
|
|
if (s->prev_wall_us > 0) {
|
|
double dt = (double)(now_us - s->prev_wall_us) / 1000000.0;
|
|
if (dt > 0) {
|
|
double djiffies = (double)(cur_jiffies - s->prev_jiffies);
|
|
cpu_pct = (djiffies / (double)clk_tck()) / dt * 100.0;
|
|
if (cpu_pct < 0) cpu_pct = 0;
|
|
}
|
|
}
|
|
s->prev_jiffies = cur_jiffies;
|
|
s->prev_wall_us = now_us;
|
|
|
|
long rss_kb = status_field_kb(pid, "VmRSS:");
|
|
long vmpeak_kb = status_field_kb(pid, "VmPeak:");
|
|
long vmswap_kb = status_field_kb(pid, "VmSwap:");
|
|
long pss_kb = -1, uss_kb = -1;
|
|
parse_smaps_rollup(pid, &pss_kb, &uss_kb);
|
|
long io_read = 0, io_write = 0;
|
|
parse_io(pid, &io_read, &io_write);
|
|
|
|
char task_path[64];
|
|
snprintf(task_path, sizeof(task_path), "/proc/%u/task", pid);
|
|
int threads = count_dir_entries(task_path);
|
|
if (threads < 0) threads = 0;
|
|
|
|
char fd_path[64];
|
|
snprintf(fd_path, sizeof(fd_path), "/proc/%u/fd", pid);
|
|
int fds = count_dir_entries(fd_path);
|
|
if (fds < 0) fds = 0;
|
|
|
|
char cmdline[1024];
|
|
read_cmdline(pid, cmdline, sizeof(cmdline));
|
|
|
|
/* Uptime: starttime is in ticks since boot; btime is boot seconds
|
|
* since epoch. process_start = btime + starttime/clk. */
|
|
time_t start_sec = boot_time() + (time_t)(starttime / (guint64)clk_tck());
|
|
time_t uptime = time(NULL) - start_sec;
|
|
if (uptime < 0) uptime = 0;
|
|
|
|
const char *own_str = "other";
|
|
switch (own) {
|
|
case OWN_MAIN: own_str = "main"; break;
|
|
case OWN_WEBKIT_RENDERER: own_str = "webkit-renderer"; break;
|
|
case OWN_WEBKIT_NETWORK: own_str = "webkit-network"; break;
|
|
case OWN_WEBKIT_GPU: own_str = "webkit-gpu"; break;
|
|
case OWN_WEBKIT_STORAGE: own_str = "webkit-storage"; break;
|
|
case OWN_TOR: own_str = "tor"; break;
|
|
case OWN_FIPS: own_str = "fips"; break;
|
|
default: own_str = "other"; break;
|
|
}
|
|
|
|
cJSON *o = cJSON_CreateObject();
|
|
cJSON_AddNumberToObject(o, "pid", (double)pid);
|
|
cJSON_AddStringToObject(o, "name", comm);
|
|
cJSON_AddStringToObject(o, "cmdline", cmdline);
|
|
cJSON_AddStringToObject(o, "state", state);
|
|
cJSON_AddNumberToObject(o, "ppid", (double)ppid);
|
|
cJSON_AddNumberToObject(o, "cpu_percent", cpu_pct);
|
|
cJSON_AddNumberToObject(o, "rss_kb", rss_kb < 0 ? 0 : rss_kb);
|
|
cJSON_AddNumberToObject(o, "pss_kb", pss_kb < 0 ? rss_kb : pss_kb);
|
|
cJSON_AddNumberToObject(o, "uss_kb", uss_kb < 0 ? 0 : uss_kb);
|
|
cJSON_AddNumberToObject(o, "vmpeak_kb", vmpeak_kb < 0 ? 0 : vmpeak_kb);
|
|
cJSON_AddNumberToObject(o, "vmswap_kb", vmswap_kb < 0 ? 0 : vmswap_kb);
|
|
cJSON_AddNumberToObject(o, "threads", threads);
|
|
cJSON_AddNumberToObject(o, "uptime_sec", (double)uptime);
|
|
cJSON_AddNumberToObject(o, "io_read_kb", (double)(io_read / 1024));
|
|
cJSON_AddNumberToObject(o, "io_write_kb", (double)(io_write / 1024));
|
|
cJSON_AddNumberToObject(o, "fd_count", fds);
|
|
cJSON_AddStringToObject(o, "ownership", own_str);
|
|
if (service_state) {
|
|
cJSON_AddStringToObject(o, "service_state", service_state);
|
|
}
|
|
if (hosted_tabs) {
|
|
cJSON_AddItemToObject(o, "hosted_tabs", hosted_tabs);
|
|
} else {
|
|
cJSON_AddItemToObject(o, "hosted_tabs", cJSON_CreateArray());
|
|
}
|
|
return o;
|
|
}
|
|
|
|
/* Map a net_service_type_t to a service_state string via net_services. */
|
|
static const char *service_state_for(net_service_type_t t) {
|
|
const net_service_t *s = net_service_get_status(t);
|
|
if (s == NULL) return NULL;
|
|
switch (s->state) {
|
|
case SERVICE_DISABLED: return "disabled";
|
|
case SERVICE_DISCOVERING: return "discovering";
|
|
case SERVICE_ATTACHING: return "attaching";
|
|
case SERVICE_STARTING: return "starting";
|
|
case SERVICE_BOOTSTRAPPING: return "bootstrapping";
|
|
case SERVICE_READY: return "ready";
|
|
case SERVICE_STOPPING: return "stopping";
|
|
case SERVICE_EXITED: return "exited";
|
|
case SERVICE_FAILED: return "failed";
|
|
default: return "unknown";
|
|
}
|
|
}
|
|
|
|
/* Scan /proc for child processes of main_pid whose comm starts with
|
|
* "WebKit". Classifies each as a renderer (WebKitWebProcess), network
|
|
* (WebKitNetworkProcess), GPU (WebKitGPUProcess), or storage
|
|
* (WebKitStorageProcess) and adds it to the `out` array with the
|
|
* appropriate ownership tag.
|
|
*
|
|
* Note: WebKitGTK 4.1 does not expose a per-webview OS PID, so we
|
|
* cannot map individual tabs to specific renderer PIDs here. Per-tab
|
|
* CPU attribution is handled by the Layer 2 probe instead. Renderer
|
|
* rows therefore show an empty hosted_tabs array. */
|
|
static void scan_webkit_procs(guint main_pid, cJSON *out) {
|
|
DIR *d = opendir("/proc");
|
|
if (d == NULL) return;
|
|
struct dirent *e;
|
|
while ((e = readdir(d)) != NULL) {
|
|
if (!isdigit((unsigned char)e->d_name[0])) continue;
|
|
guint pid = (guint)strtoul(e->d_name, NULL, 10);
|
|
if (pid == 0 || pid == main_pid) continue;
|
|
char comm[256] = "";
|
|
char state[8] = "?";
|
|
guint ppid = 0;
|
|
guint64 utime = 0, stime = 0, starttime = 0;
|
|
if (parse_stat(pid, state, sizeof(state), &ppid, &utime, &stime,
|
|
&starttime, comm, sizeof(comm)) != 0) continue;
|
|
if (ppid != main_pid) continue;
|
|
if (strncmp(comm, "WebKit", 6) != 0) continue;
|
|
proc_own_t own = OWN_OTHER;
|
|
if (strstr(comm, "Network")) own = OWN_WEBKIT_NETWORK;
|
|
else if (strstr(comm, "GPU")) own = OWN_WEBKIT_GPU;
|
|
else if (strstr(comm, "Storage")) own = OWN_WEBKIT_STORAGE;
|
|
else if (strstr(comm, "Web")) own = OWN_WEBKIT_RENDERER;
|
|
else continue; /* unknown WebKit aux — skip */
|
|
cJSON *obj = build_proc_obj(pid, own, NULL, NULL);
|
|
if (obj) cJSON_AddItemToArray(out, obj);
|
|
}
|
|
closedir(d);
|
|
}
|
|
|
|
cJSON *process_info_get_processes_json(void) {
|
|
cJSON *arr = cJSON_CreateArray();
|
|
guint main_pid = (guint)getpid();
|
|
|
|
/* Main process. */
|
|
cJSON *main_obj = build_proc_obj(main_pid, OWN_MAIN, NULL, NULL);
|
|
if (main_obj) cJSON_AddItemToArray(arr, main_obj);
|
|
|
|
/* WebKit child processes (renderers, network, gpu, storage).
|
|
* Discovered via /proc PPID scan — WebKitGTK 4.1 doesn't expose
|
|
* a per-webview OS PID, so renderer rows have empty hosted_tabs. */
|
|
scan_webkit_procs(main_pid, arr);
|
|
|
|
/* Tor + FIPS managed subprocesses. */
|
|
const net_service_t *tor = net_service_get_status(NET_SERVICE_TOR);
|
|
if (tor && tor->ownership == OWNERSHIP_MANAGED && tor->pid > 0) {
|
|
cJSON *obj = build_proc_obj((guint)tor->pid, OWN_TOR,
|
|
service_state_for(NET_SERVICE_TOR), NULL);
|
|
if (obj) cJSON_AddItemToArray(arr, obj);
|
|
}
|
|
const net_service_t *fips = net_service_get_status(NET_SERVICE_FIPS);
|
|
if (fips && fips->ownership == OWNERSHIP_MANAGED && fips->pid > 0) {
|
|
cJSON *obj = build_proc_obj((guint)fips->pid, OWN_FIPS,
|
|
service_state_for(NET_SERVICE_FIPS), NULL);
|
|
if (obj) cJSON_AddItemToArray(arr, obj);
|
|
}
|
|
|
|
return arr;
|
|
}
|
|
|
|
/* ── Tab list (Layer 2) ─────────────────────────────────────────────── */
|
|
|
|
cJSON *process_info_get_tabs_json(void) {
|
|
cJSON *arr = cJSON_CreateArray();
|
|
int n = tab_manager_count();
|
|
for (int i = 0; i < n; i++) {
|
|
tab_info_t *tab = tab_manager_get(i);
|
|
if (tab == NULL) continue;
|
|
cJSON *o = cJSON_CreateObject();
|
|
cJSON_AddNumberToObject(o, "index", i);
|
|
cJSON_AddStringToObject(o, "title", tab->title[0] ? tab->title : "");
|
|
cJSON_AddStringToObject(o, "url",
|
|
tab->current_url[0] ? tab->current_url : "");
|
|
gboolean is_internal = (strncmp(tab->current_url, "sovereign://", 12) == 0);
|
|
cJSON_AddBoolToObject(o, "is_internal", is_internal);
|
|
/* WebKitGTK 4.1 does not expose a per-webview OS PID, so we
|
|
* report 0 here. The Processes tab lists renderer PIDs from
|
|
* /proc; per-tab CPU attribution is via the Layer 2 probe. */
|
|
cJSON_AddNumberToObject(o, "webprocess_pid", 0);
|
|
cJSON *probe = probe_copy(i);
|
|
if (probe) {
|
|
cJSON_AddItemToObject(o, "probe", probe);
|
|
} else {
|
|
cJSON_AddNullToObject(o, "probe");
|
|
}
|
|
cJSON_AddItemToArray(arr, o);
|
|
}
|
|
return arr;
|
|
}
|
|
|
|
cJSON *process_info_get_tab_probe_json(int tab_index) {
|
|
tab_info_t *tab = tab_manager_get(tab_index);
|
|
if (tab == NULL) return NULL;
|
|
cJSON *root = cJSON_CreateObject();
|
|
cJSON_AddNumberToObject(root, "index", tab_index);
|
|
cJSON_AddStringToObject(root, "title", tab->title[0] ? tab->title : "");
|
|
cJSON_AddStringToObject(root, "url",
|
|
tab->current_url[0] ? tab->current_url : "");
|
|
gboolean is_internal = (strncmp(tab->current_url, "sovereign://", 12) == 0);
|
|
cJSON_AddBoolToObject(root, "is_internal", is_internal);
|
|
/* WebKitGTK 4.1 does not expose a per-webview OS PID. */
|
|
cJSON_AddNumberToObject(root, "webprocess_pid", 0);
|
|
cJSON *probe = probe_copy(tab_index);
|
|
if (probe) {
|
|
cJSON_AddItemToObject(root, "probe", probe);
|
|
} else {
|
|
cJSON_AddNullToObject(root, "probe");
|
|
}
|
|
return root;
|
|
}
|