// pstat.c: per-process memory/CPU/wakeup counters for every process the current
// user can see, read with libproc (no root, no task port, read-only).
//
//   pstat            -> one JSON object per line for every visible process
//   pstat PID...     -> only those processes
//
// Fields: pid, ppid, rpid (the "responsible" process macOS attributes the
// process to, e.g. an app for its XPC services), uid, path,
// footprint (phys_footprint: what Activity Monitor calls "Memory"),
// lifetime_max_footprint, resident (RSS), cpu_ns (user+system),
// child_cpu_ns (CPU of terminated children it reaped), wakeups
// (package idle + interrupt wakeups) and child_wakeups.
// rusage times are in mach absolute time units; converted to ns here.
#include <libproc.h>
#include <mach/mach_time.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/proc_info.h>
#include <sys/resource.h>
#include <unistd.h>

extern int responsibility_get_pid_responsible_for_pid(pid_t);

static mach_timebase_info_data_t tb;
static unsigned long long ns(unsigned long long t) { return t * tb.numer / tb.denom; }

static void json_str(const char *s) {
    putchar('"');
    for (; *s; s++) {
        if (*s == '"' || *s == '\\') { putchar('\\'); putchar(*s); }
        else if ((unsigned char)*s < 0x20) printf("\\u%04x", *s);
        else putchar(*s);
    }
    putchar('"');
}

static void one(pid_t pid) {
    struct proc_bsdinfo bi;
    if (proc_pidinfo(pid, PROC_PIDTBSDINFO, 0, &bi, sizeof bi) != sizeof bi) return;
    char path[PROC_PIDPATHINFO_MAXSIZE] = "";
    proc_pidpath(pid, path, sizeof path);
    struct rusage_info_v4 ru;
    int ok = proc_pid_rusage(pid, RUSAGE_INFO_V4, (rusage_info_t *)&ru) == 0;
    printf("{\"pid\":%d,\"ppid\":%d,\"rpid\":%d,\"uid\":%d,\"comm\":", pid, bi.pbi_ppid,
           responsibility_get_pid_responsible_for_pid(pid), bi.pbi_uid);
    json_str(bi.pbi_comm);
    printf(",\"path\":");
    json_str(path);
    if (ok)
        printf(",\"footprint\":%llu,\"lifetime_max_footprint\":%llu,\"resident\":%llu,"
               "\"cpu_ns\":%llu,\"child_cpu_ns\":%llu,\"wakeups\":%llu,\"child_wakeups\":%llu",
               ru.ri_phys_footprint, ru.ri_lifetime_max_phys_footprint, ru.ri_resident_size,
               ns(ru.ri_user_time + ru.ri_system_time),
               ns(ru.ri_child_user_time + ru.ri_child_system_time),
               ru.ri_pkg_idle_wkups + ru.ri_interrupt_wkups,
               ru.ri_child_pkg_idle_wkups + ru.ri_child_interrupt_wkups);
    printf("}\n");
}

int main(int argc, char **argv) {
    mach_timebase_info(&tb);
    if (argc > 1) {
        for (int i = 1; i < argc; i++) one(atoi(argv[i]));
        return 0;
    }
    int n = proc_listallpids(NULL, 0);
    pid_t *pids = calloc(n + 64, sizeof(pid_t));
    n = proc_listallpids(pids, (n + 64) * sizeof(pid_t));
    for (int i = 0; i < n; i++) if (pids[i] > 0) one(pids[i]);
    return 0;
}
