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
34 changes: 24 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ free [options]
| -t | --total | Show total for RAM + swap |
| -s N | --seconds N | Repeat printing every N seconds |
| -c N | --count N | Repeat printing N times, then exit |
| -P | --pressure | Show macOS memory pressure (kernel signal) |
| -V | --version | Output version information and exit |
| | --help | Display help and exit |

Expand All @@ -83,7 +84,7 @@ free [options]
```txt
$ free
total used free shared buff/cache available
Mem: 16777216 8388608 2097152 524288 6291456 8388608
Mem: 16777216 8388608 2097152 0 6291456 8388608
Swap: 2097152 524288 1572864
```

Expand All @@ -92,7 +93,7 @@ Swap: 2097152 524288 1572864
```shell
$ free -h
total used free shared buff/cache available
Mem: 16.0Gi 8.0Gi 2.0Gi 512.0Mi 6.0Gi 8.0Gi
Mem: 16.0Gi 8.0Gi 2.0Gi 0B 6.0Gi 8.0Gi
Swap: 2.0Gi 512.0Mi 1.5Gi
```

Expand All @@ -117,12 +118,22 @@ free -h -s 2
free -h -s 1 -c 5
```

**Show macOS memory pressure (real kernel signal):**

```txt
$ free -h -P
total used free shared buff/cache available
Mem: 16.0Gi 8.0Gi 2.0Gi 0B 6.0Gi 8.0Gi
Swap: 2.0Gi 512.0Mi 1.5Gi
Pressure: normal (50% available)
```

**Show totals:**

```txt
$ free -h -t
total used free shared buff/cache available
Mem: 16.0Gi 8.0Gi 2.0Gi 512.0Mi 6.0Gi 8.0Gi
Mem: 16.0Gi 8.0Gi 2.0Gi 0B 6.0Gi 8.0Gi
Swap: 2.0Gi 512.0Mi 1.5Gi
Total: 18.0Gi 8.5Gi 3.5Gi
```
Expand All @@ -136,8 +147,8 @@ Total: 18.0Gi 8.5Gi 3.5Gi
| **total** | Total installed physical memory |
| **used** | Used memory(Active + Wired + Compressed) |
| **free** | Completely unused memory |
| **shared** | Memory used by shared mappings |
| **buff/cache** | Memory used for file buffers and cache(mapped to Inactive on macOS) |
| **shared** | Always `0`: macOS exposes no system-wide shared-memory counter |
| **buff/cache** | Reclaimable file cache (Inactive + Purgeable + Speculative) |
| **available** | Estimate of memory available for starting new applications |

### Wide Output(macOS - specific)
Expand All @@ -155,24 +166,27 @@ This utility uses macOS - specific APIs to gather memory information:

- **`host_statistics64()`** - Retrieves VM statistics including page counts for different memory states
- **`sysctl()`** - Gets total physical memory and swap usage
- **`sysctlbyname("kern.memorystatus_vm_pressure_level")`** - Real kernel memory-pressure level (for `-P`)
- **`vm_page_size`** - System page size for converting page counts to bytes

### Memory Calculation Notes

- **Used Memory** = Active + Wired + Compressed (speculative pages excluded)
- **Available Memory** = Free + Inactive (approximation, as macOS can reclaim inactive pages)
- **Shared Memory** = Pages marked as purgeable/shared in VM statistics
- **Used Memory** = Active + Wired + Compressed
- **Available Memory** = Free + Inactive + Purgeable + Speculative (approximation, as macOS can reclaim these pages)
- **Shared Memory** = reported as `0`; macOS has no cheap system-wide shared-memory counter (unlike Linux `Shmem`). Deriving a real value would require summing the shared footprint across every task (as `top`/`libtop` do).

## Differences from Linux `free`

1. **buff/cache**: On Linux, this shows buffer and cache memory separately. On macOS, we map this to inactive memory, which serves a similar purpose.
1. **buff/cache**: On Linux, this shows buffer and cache memory separately. On macOS, we sum the reclaimable file cache: inactive + purgeable + speculative pages.

2. **shared**: Linux tracks shared memory segments. On macOS, we report purgeable/shared page statistics.
2. **shared**: Linux tracks shared memory segments (`Shmem`). macOS exposes no equivalent system-wide counter, so this is reported as `0` rather than a misleading approximation.

3. **available**: Both systems estimate available memory, but the calculation differs due to different memory management strategies.

4. **Wide mode**: Shows macOS-specific categories (compressed memory is unique to macOS).

5. **Memory pressure** (`-P`): macOS relieves memory pressure with an in-RAM compressor *before* it touches swap, so the Linux-style `free`/`swap` columns can look healthy while the system is actually thrashing. `-P` prints the real kernel signal (`kern.memorystatus_vm_pressure_level` — the same one Activity Monitor uses): `normal`, `warning`, or `critical`.

## Uninstall

```bash
Expand Down
43 changes: 39 additions & 4 deletions src/display.c
Original file line number Diff line number Diff line change
Expand Up @@ -100,10 +100,15 @@ void print_numeric(const mem_info_t *mem, const swap_info_t *swap,
}
else
{
/* "shared" - using compressed as approximation */
print_value(mem->compressed, opts);
/* "buff/cache" */
print_value(mem->cached + mem->inactive, opts);
/*
* "shared" - macOS has no simple equivalent of Linux Shmem/tmpfs
* accounting, so report 0 rather than a misleading value. Compressed
* memory (previously shown here) is anonymous, not shared, and is
* visible in the wide (-w) view.
*/
print_value(0, opts);
/* "buff/cache" - purgeable + inactive + speculative file cache */
print_value(mem->cached + mem->inactive + mem->speculative, opts);
}
print_value(mem->available, opts);
printf("\n");
Expand Down Expand Up @@ -189,6 +194,31 @@ void print_separator(const options_t *opts)
putchar('\n');
}

void print_pressure(const mem_info_t *mem)
{
const char *label;
switch (get_pressure_level())
{
case 1:
label = "normal";
break;
case 2:
label = "warning";
break;
case 4:
label = "critical";
break;
default:
label = "unknown";
break;
}

unsigned int avail_pct =
mem->total ? (unsigned int)((mem->available * 100) / mem->total) : 0;

printf("Pressure: %s (%u%% available)\n", label, avail_pct);
}

void print_memory_info(const system_memory_t *sys_mem, const options_t *opts)
{
print_header(opts);
Expand All @@ -206,4 +236,9 @@ void print_memory_info(const system_memory_t *sys_mem, const options_t *opts)
{
print_totals(&sys_mem->mem, &sys_mem->swap, opts);
}

if (opts->pressure)
{
print_pressure(&sys_mem->mem);
}
}
7 changes: 7 additions & 0 deletions src/display.h
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,13 @@ void print_memory_info(const system_memory_t *sys_mem, const options_t *opts);
*/
void print_separator(const options_t *opts);

/**
* Print the macOS memory pressure line (kernel signal + available %).
*
* @param mem Physical memory information
*/
void print_pressure(const mem_info_t *mem);

/**
* Print totals row
*
Expand Down
6 changes: 5 additions & 1 deletion src/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -66,14 +66,15 @@ static int parse_args(int argc, char *argv[], options_t *opts)
{"seconds", required_argument, NULL, 's'},
{"count", required_argument, NULL, 'c'},
{"lohi", no_argument, NULL, 'l'},
{"pressure", no_argument, NULL, 'P'},
{"help", no_argument, NULL, 'H'},
{"version", no_argument, NULL, 'V'},
{NULL, 0, NULL, 0}};

int opt;
int option_index = 0;

while ((opt = getopt_long(argc, argv, "bkmghwts:c:lV", long_options,
while ((opt = getopt_long(argc, argv, "bkmghwts:c:lPV", long_options,
&option_index)) != -1)
{
switch (opt)
Expand Down Expand Up @@ -119,6 +120,9 @@ static int parse_args(int argc, char *argv[], options_t *opts)
case 'l':
opts->lohi = 1;
break;
case 'P':
opts->pressure = 1;
break;
case 'H':
print_usage(argv[0]);
exit(EXIT_SUCCESS);
Expand Down
21 changes: 19 additions & 2 deletions src/memory.c
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,9 @@ int get_memory_info(mem_info_t *mem)
/* Purgeable memory (cached) */
mem->cached = (uint64_t)vm_stats.purgeable_count * page_size;

/* Speculative read-ahead pages (reclaimable file cache) */
mem->speculative = (uint64_t)vm_stats.speculative_count * page_size;

/*
* Calculate used memory
* Used = Active + Wired + Compressed
Expand All @@ -113,10 +116,10 @@ int get_memory_info(mem_info_t *mem)

/*
* Calculate available memory
* Available = Free + Inactive + Cached
* Available = Free + Inactive + Cached + Speculative
* This represents memory that can be reclaimed if needed
*/
mem->available = mem->free + mem->inactive + mem->cached;
mem->available = mem->free + mem->inactive + mem->cached + mem->speculative;

/*
* App memory is roughly the active memory minus cached files
Expand Down Expand Up @@ -178,6 +181,20 @@ int get_system_memory(system_memory_t *sys_mem)
return 0;
}

int get_pressure_level(void)
{
int level = 0;
size_t length = sizeof(level);

if (sysctlbyname("kern.memorystatus_vm_pressure_level", &level, &length,
NULL, 0) != 0)
{
return -1;
}

return level;
}

double calculate_memory_pressure(const mem_info_t *mem)
{
if (mem == NULL || mem->total == 0)
Expand Down
34 changes: 24 additions & 10 deletions src/memory.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,17 @@
/* Physical memory information */
typedef struct
{
uint64_t total; /* Total physical memory */
uint64_t used; /* Used memory (active + wired + compressed) */
uint64_t free; /* Free memory */
uint64_t active; /* Active pages */
uint64_t inactive; /* Inactive pages */
uint64_t wired; /* Wired (non-pageable) pages */
uint64_t compressed; /* Compressed pages */
uint64_t cached; /* Cached/purgeable pages */
uint64_t app_memory; /* Memory used by applications */
uint64_t available; /* Available memory (free + inactive + cached) */
uint64_t total; /* Total physical memory */
uint64_t used; /* Used memory (active + wired + compressed) */
uint64_t free; /* Free memory */
uint64_t active; /* Active pages */
uint64_t inactive; /* Inactive pages */
uint64_t wired; /* Wired (non-pageable) pages */
uint64_t compressed; /* Compressed pages */
uint64_t cached; /* Cached/purgeable pages */
uint64_t speculative; /* Speculative (read-ahead file cache) pages */
uint64_t app_memory; /* Memory used by applications */
uint64_t available; /* Available (reclaimable) memory */
} mem_info_t;

/* Swap/virtual memory information */
Expand Down Expand Up @@ -108,4 +109,17 @@ int get_system_memory(system_memory_t *sys_mem);
*/
double calculate_memory_pressure(const mem_info_t *mem);

/**
* Get the kernel's memory pressure level.
*
* Reads the real signal macOS itself uses
* (kern.memorystatus_vm_pressure_level), the same one Activity Monitor's
* pressure graph is based on. This exists because macOS relieves pressure via
* the in-RAM compressor before touching swap, so the Linux-style free/swap
* columns cannot reveal it.
*
* @return 1 = normal, 2 = warning, 4 = critical, or -1 on error
*/
int get_pressure_level(void);

#endif /* MEMORY_H */
15 changes: 9 additions & 6 deletions src/utils.c
Original file line number Diff line number Diff line change
Expand Up @@ -110,12 +110,13 @@ void init_options(options_t *opts)
return;
}

opts->unit = UNIT_KIBI;
opts->wide = 0;
opts->seconds = 0;
opts->count = -1;
opts->totals = 0;
opts->lohi = 0;
opts->unit = UNIT_KIBI;
opts->wide = 0;
opts->seconds = 0;
opts->count = -1;
opts->totals = 0;
opts->lohi = 0;
opts->pressure = 0;
}

/*
Expand All @@ -142,6 +143,8 @@ void print_usage(const char *prog_name)
printf(" -t, --total Show total for RAM + swap\n");
printf(" -s N, --seconds N Repeat printing every N seconds\n");
printf(" -c N, --count N Repeat printing N times, then exit\n");
printf(
" -P, --pressure Show macOS memory pressure (kernel signal)\n");
printf(" --help Display this help message\n");
printf(" -V, --version Display version information\n");
printf("\n");
Expand Down
13 changes: 7 additions & 6 deletions src/utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,13 @@ typedef enum
/* Command-line options */
typedef struct
{
unit_type_t unit; /* Output unit */
int wide; /* Wide output mode */
int seconds; /* Refresh interval (0 = no refresh) */
int count; /* Number of iterations (-1 = infinite) */
int totals; /* Show totals line */
int lohi; /* Show low/high memory stats */
unit_type_t unit; /* Output unit */
int wide; /* Wide output mode */
int seconds; /* Refresh interval (0 = no refresh) */
int count; /* Number of iterations (-1 = infinite) */
int totals; /* Show totals line */
int lohi; /* Show low/high memory stats */
int pressure; /* Show macOS memory pressure line */
} options_t;

/*
Expand Down
Loading