Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions darwin/DarwinProcess.c
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,19 @@ in the source distribution for its full text.
#include "darwin/DarwinProcess.h"

#include <libproc.h>
#include <limits.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <mach/mach.h>
#include <sys/dirent.h>

#include "CRT.h"
#include "Macros.h"
#include "Process.h"
#include "Settings.h"
#include "XUtils.h"
#include "darwin/DarwinMachine.h"
#include "darwin/Platform.h"

Expand Down Expand Up @@ -52,6 +57,11 @@ const ProcessFieldData Process_fields[LAST_PROCESSFIELD] = {
[PROC_EXE] = { .name = "EXE", .title = "EXE ", .description = "Basename of exe of the process from /proc/[pid]/exe", .flags = 0, },
[CWD] = { .name = "CWD", .title = "CWD ", .description = "The current working directory of the process", .flags = PROCESS_FLAG_CWD, },
[TRANSLATED] = { .name = "TRANSLATED", .title = "T ", .description = "Translation info (T translated, N native)", .flags = 0, },
[RBYTES] = { .name = "RBYTES", .title = " IO_R ", .description = "Bytes of read(2) I/O for the process", .flags = PROCESS_FLAG_IO, .defaultSortDesc = true, },
[WBYTES] = { .name = "WBYTES", .title = " IO_W ", .description = "Bytes of write(2) I/O for the process", .flags = PROCESS_FLAG_IO, .defaultSortDesc = true, },
[IO_READ_RATE] = { .name = "IO_READ_RATE", .title = " DISK READ ", .description = "The I/O rate of read(2) in bytes per second for the process", .flags = PROCESS_FLAG_IO, .defaultSortDesc = true, },
[IO_WRITE_RATE] = { .name = "IO_WRITE_RATE", .title = " DISK WRITE ", .description = "The I/O rate of write(2) in bytes per second for the process", .flags = PROCESS_FLAG_IO, .defaultSortDesc = true, },
[IO_RATE] = { .name = "IO_RATE", .title = " DISK R/W ", .description = "Total I/O rate in bytes per second", .flags = PROCESS_FLAG_IO, .defaultSortDesc = true, },
};

Process* DarwinProcess_new(const Machine* host) {
Expand All @@ -63,6 +73,11 @@ Process* DarwinProcess_new(const Machine* host) {
this->stime = 0;
this->taskAccess = true;
this->translated = false;
this->io_read_bytes = 0;
this->io_write_bytes = 0;
this->io_last_scan_time_ms = 0;
this->io_rate_read_bps = NAN;
this->io_rate_write_bps = NAN;
this->super.state = UNKNOWN;

return (Process*)this;
Expand All @@ -75,16 +90,36 @@ void Process_delete(Object* cast) {
free(this);
}

static double DarwinProcess_totalIORate(const DarwinProcess* dp) {
double totalRate = NAN;
if (isNonnegative(dp->io_rate_read_bps)) {
totalRate = dp->io_rate_read_bps;
if (isNonnegative(dp->io_rate_write_bps)) {
totalRate += dp->io_rate_write_bps;
}
} else if (isNonnegative(dp->io_rate_write_bps)) {
totalRate = dp->io_rate_write_bps;
}
return totalRate;
}

static void DarwinProcess_rowWriteField(const Row* super, RichString* str, ProcessField field) {
const DarwinProcess* dp = (const DarwinProcess*) super;
const Machine* host = super->host;

bool coloring = host->settings->highlightMegabytes;
char buffer[256]; buffer[255] = '\0';
int attr = CRT_colors[DEFAULT_COLOR];
size_t n = sizeof(buffer) - 1;

switch (field) {
// add Platform-specific fields here
case TRANSLATED: xSnprintf(buffer, n, "%c ", dp->translated ? 'T' : 'N'); break;
case RBYTES: Row_printBytes(str, dp->io_read_bytes, coloring); return;
case WBYTES: Row_printBytes(str, dp->io_write_bytes, coloring); return;
case IO_READ_RATE: Row_printRate(str, dp->io_rate_read_bps, coloring); return;
case IO_WRITE_RATE: Row_printRate(str, dp->io_rate_write_bps, coloring); return;
case IO_RATE: Row_printRate(str, DarwinProcess_totalIORate(dp), coloring); return;
default:
Process_writeField(&dp->super, str, field);
return;
Expand All @@ -101,6 +136,20 @@ static int DarwinProcess_compareByKey(const Process* v1, const Process* v2, Proc
// add Platform-specific fields here
case TRANSLATED:
return SPACESHIP_NUMBER(p1->translated, p2->translated);
case RBYTES:
if (p1->io_read_bytes == ULLONG_MAX || p2->io_read_bytes == ULLONG_MAX)
return (p1->io_read_bytes != ULLONG_MAX) - (p2->io_read_bytes != ULLONG_MAX);
return SPACESHIP_NUMBER(p1->io_read_bytes, p2->io_read_bytes);
case WBYTES:
if (p1->io_write_bytes == ULLONG_MAX || p2->io_write_bytes == ULLONG_MAX)
return (p1->io_write_bytes != ULLONG_MAX) - (p2->io_write_bytes != ULLONG_MAX);
return SPACESHIP_NUMBER(p1->io_write_bytes, p2->io_write_bytes);
Comment thread
samm-git marked this conversation as resolved.
case IO_READ_RATE:
return compareRealNumbers(p1->io_rate_read_bps, p2->io_rate_read_bps);
case IO_WRITE_RATE:
return compareRealNumbers(p1->io_rate_write_bps, p2->io_rate_write_bps);
case IO_RATE:
return compareRealNumbers(DarwinProcess_totalIORate(p1), DarwinProcess_totalIORate(p2));
default:
return Process_compareByKey_Base(v1, v2, key);
}
Expand Down
15 changes: 15 additions & 0 deletions darwin/DarwinProcess.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,21 @@ typedef struct DarwinProcess_ {
uint64_t stime;
bool taskAccess;
bool translated;

/* Storage data read (in bytes) */
uint64_t io_read_bytes;

/* Storage data written (in bytes) */
uint64_t io_write_bytes;

/* Point in time of last io scan (monotonic milliseconds) */
uint64_t io_last_scan_time_ms;

/* Storage data read (in bytes per second) */
double io_rate_read_bps;

/* Storage data written (in bytes per second) */
double io_rate_write_bps;
} DarwinProcess;

extern const ProcessClass DarwinProcess_class;
Expand Down
52 changes: 52 additions & 0 deletions darwin/DarwinProcessTable.c
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ in the source distribution for its full text.

#include <errno.h>
#include <libproc.h>
#include <limits.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
Expand All @@ -19,7 +20,9 @@ in the source distribution for its full text.
#include <sys/sysctl.h>

#include "CRT.h"
#include "Macros.h"
#include "ProcessTable.h"
#include "Settings.h"
#include "darwin/DarwinMachine.h"
#include "darwin/DarwinProcess.h"
#include "darwin/Platform.h"
Expand Down Expand Up @@ -69,6 +72,51 @@ void ProcessTable_delete(Object* cast) {
free(this);
}

/*
* Read per-process disk I/O counters (proc_pid_rusage).
*
* Unlike Linux, macOS does not expose separate char/syscall counters; it only
* provides cumulative storage bytes read and written. Rates are computed from
* the difference between consecutive scans, mirroring LinuxProcessTable_readIoFile.
*/
static void DarwinProcessTable_readProcRusage(DarwinProcess* proc) {
const Machine* host = proc->super.super.host;

struct rusage_info_v2 ri;
if (proc_pid_rusage(Process_getPid(&proc->super), RUSAGE_INFO_V2, (rusage_info_t *)&ri) != 0) {
proc->io_rate_read_bps = NAN;
proc->io_rate_write_bps = NAN;
proc->io_read_bytes = ULLONG_MAX;
proc->io_write_bytes = ULLONG_MAX;
proc->io_last_scan_time_ms = host->monotonicMs;
return;
}

uint64_t last_read = proc->io_read_bytes;
uint64_t last_write = proc->io_write_bytes;

/* Only compute a rate once a previous valid sample exists; otherwise the
* delta is meaningless on the first scan and zero after a failed scan.
* Machine_scanTables seeds monotonicMs with 1 on its first scan, so treat
* that sentinel as "no timestamp yet". Use the monotonic clock so that
* wall-clock adjustments cannot produce a zero or inflated time delta. */
bool haveBaseline = proc->io_last_scan_time_ms > 1 && last_read != ULLONG_MAX;
uint64_t time_delta = saturatingSub(host->monotonicMs, proc->io_last_scan_time_ms);

proc->io_read_bytes = ri.ri_diskio_bytesread;
proc->io_write_bytes = ri.ri_diskio_byteswritten;

if (haveBaseline && time_delta) {
proc->io_rate_read_bps = saturatingSub(proc->io_read_bytes, last_read) * /*ms to s*/1000. / time_delta;
proc->io_rate_write_bps = saturatingSub(proc->io_write_bytes, last_write) * /*ms to s*/1000. / time_delta;
Comment thread
BenBE marked this conversation as resolved.
} else {
proc->io_rate_read_bps = NAN;
proc->io_rate_write_bps = NAN;
}

proc->io_last_scan_time_ms = host->monotonicMs;
}

void ProcessTable_goThroughEntries(ProcessTable* super) {
const Machine* host = super->super.host;
const DarwinMachine* dhost = (const DarwinMachine*) host;
Expand Down Expand Up @@ -103,6 +151,10 @@ void ProcessTable_goThroughEntries(ProcessTable* super) {
DarwinProcess_setFromKInfoProc(&proc->super, &ps[i], preExisting);
DarwinProcess_setFromLibprocPidinfo(proc, dpt, time_interval_ns);

if (host->settings->ss->flags & PROCESS_FLAG_IO) {
DarwinProcessTable_readProcRusage(proc);
}

// Deduce further process states not covered in the libproc call above
if (ps[i].kp_proc.p_stat == SZOMB) {
proc->super.state = ZOMBIE;
Expand Down
5 changes: 5 additions & 0 deletions darwin/Platform.c
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,11 @@ const ScreenDefaults Platform_defaultScreens[] = {
.columns = "PID USER PRIORITY NICE M_VIRT M_RESIDENT STATE PERCENT_CPU PERCENT_MEM TIME Command",
.sortKey = "PERCENT_CPU",
},
{
.name = "I/O",
.columns = "PID USER IO_RATE IO_READ_RATE IO_WRITE_RATE RBYTES WBYTES Command",
.sortKey = "IO_RATE",
},
};

const unsigned int Platform_numberOfDefaultScreens = ARRAYSIZE(Platform_defaultScreens);
Expand Down
5 changes: 5 additions & 0 deletions darwin/ProcessField.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ in the source distribution for its full text.

#define PLATFORM_PROCESS_FIELDS \
TRANSLATED = 100, \
RBYTES = 107, \
WBYTES = 108, \
IO_READ_RATE = 110, \
IO_WRITE_RATE = 111, \
IO_RATE = 112, \
\
DUMMY_BUMP_FIELD = CWD, \
// End of list
Expand Down
Loading