Skip to content

Commit fcc305b

Browse files
Pierre-Marie Batynatoscott
authored andcommitted
MemoryMeter: rework to allow full platform-specific control
FreeBSD required several corrections to memory calculations that proved impossible to reconcile with existing memory categories. Resolves #1725
1 parent 8211b10 commit fcc305b

27 files changed

Lines changed: 465 additions & 253 deletions

Action.c

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ in the source distribution for its full text.
2727
#include "ListItem.h"
2828
#include "Macros.h"
2929
#include "MainPanel.h"
30+
#include "MemoryMeter.h"
3031
#include "OpenFilesScreen.h"
3132
#include "Process.h"
3233
#include "ProcessLocksScreen.h"
@@ -750,16 +751,22 @@ static Htop_Reaction actionHelp(State* st) {
750751
attrset(CRT_colors[DEFAULT_COLOR]);
751752
mvaddstr(line++, 0, "Memory bar: ");
752753
addattrstr(CRT_colors[BAR_BORDER], "[");
753-
addbartext(CRT_colors[MEMORY_USED], "", "used");
754-
addbartext(CRT_colors[MEMORY_SHARED], "/", "shared");
755-
addbartext(CRT_colors[MEMORY_COMPRESSED], "/", "compressed");
756-
if (st->host->settings->showCachedMemory) {
757-
addbartext(CRT_colors[MEMORY_BUFFERS_TEXT], "/", "buffers");
758-
addbartext(CRT_colors[MEMORY_CACHE], "/", "cache");
759-
addbartext(CRT_colors[BAR_SHADOW], " ", "used");
760-
} else {
761-
addbartext(CRT_colors[BAR_SHADOW], " ", "used");
754+
// memory classes are OS-specific and provided in their <os>/Platform.c implementation
755+
// ideal length of memory bar == 56 chars. Any length < 45 requires padding to 45.
756+
// [0 1 2 3 4 5 ]
757+
// [12345678901234567890123456789012345678901234567890123456]
758+
// [ ^ 5 ]
759+
// [class1/class2/class3/.../classN used/total]
760+
int barTxtLen = 0;
761+
for (unsigned int i = 0; i < Platform_numberOfMemoryClasses; i++) {
762+
if (!st->host->settings->showCachedMemory && Platform_memoryClasses[i].countsAsCache)
763+
continue; // skip reclaimable cache memory classes if "show cached memory" is not ticked
764+
addbartext(CRT_colors[Platform_memoryClasses[i].color], (i == 0 ? "" : "/"), Platform_memoryClasses[i].label);
765+
barTxtLen += (i == 0 ? 0 : 1) + strlen (Platform_memoryClasses[i].label);
762766
}
767+
for (int i = barTxtLen; i < 45; i++)
768+
addattrstr(CRT_colors[BAR_SHADOW], " "); // pad to 45 chars if necessary
769+
addbartext(CRT_colors[BAR_SHADOW], " ", "used");
763770
addbartext(CRT_colors[BAR_SHADOW], "/", "total");
764771
addattrstr(CRT_colors[BAR_BORDER], "]");
765772

CRT.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,8 @@ typedef enum ColorElements_ {
161161
LAST_COLORELEMENT
162162
} ColorElements;
163163

164+
#define NUMBER_OF_DYNAMIC_COLORS 9 // number of DYNAMIC_<ColorName> entries in ColorElements
165+
164166
void CRT_fatalError(const char* note) ATTR_NORETURN;
165167

166168
#ifdef NDEBUG

Machine.h

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -50,12 +50,7 @@ typedef struct Machine_ {
5050
bool topologyOk;
5151
#endif
5252

53-
memory_t totalMem;
54-
memory_t usedMem;
55-
memory_t buffersMem;
56-
memory_t cachedMem;
57-
memory_t sharedMem;
58-
memory_t availableMem;
53+
/* NOTE: memory details were moved to the OS-specific machine subclass */
5954

6055
memory_t totalSwap;
6156
memory_t usedSwap;

MemoryMeter.c

Lines changed: 34 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,8 @@ in the source distribution for its full text.
2020
#include "RichString.h"
2121

2222

23-
static const int MemoryMeter_attributes[] = {
24-
MEMORY_USED,
25-
MEMORY_SHARED,
26-
MEMORY_COMPRESSED,
27-
MEMORY_BUFFERS,
28-
MEMORY_CACHE
29-
};
23+
extern const int Platform_memoryMeter_attributes[]; // OS-specific
24+
3025

3126
static void MemoryMeter_updateValues(Meter* this) {
3227
char* buffer = this->txtBuffer;
@@ -35,26 +30,30 @@ static void MemoryMeter_updateValues(Meter* this) {
3530

3631
Settings *settings = this->host->settings;
3732

38-
/* shared, compressed and available memory are not supported on all platforms */
39-
this->values[MEMORY_METER_SHARED] = NAN;
40-
this->values[MEMORY_METER_COMPRESSED] = NAN;
41-
this->values[MEMORY_METER_AVAILABLE] = NAN;
33+
/* not all memory classes are supported on all platforms */
34+
for (unsigned int memoryClassIdx = 0; memoryClassIdx < Platform_numberOfMemoryClasses; memoryClassIdx++) {
35+
this->values[memoryClassIdx] = NAN;
36+
}
37+
4238
Platform_setMemoryValues(this);
39+
this->curItems = (uint8_t) Platform_numberOfMemoryClasses;
40+
41+
/* compute the used memory */
42+
double used = 0.0;
43+
for (unsigned int memoryClassIdx = 0; memoryClassIdx < Platform_numberOfMemoryClasses; memoryClassIdx++) {
44+
if (Platform_memoryClasses[memoryClassIdx].countsAsUsed) {
45+
used += this->values[memoryClassIdx];
46+
}
47+
}
48+
49+
/* clear the values we don't want to see */
4350
if ((this->mode == GRAPH_METERMODE || this->mode == BAR_METERMODE) && !settings->showCachedMemory) {
44-
this->values[MEMORY_METER_BUFFERS] = 0;
45-
this->values[MEMORY_METER_CACHE] = 0;
51+
for (unsigned int memoryClassIdx = 0; memoryClassIdx < Platform_numberOfMemoryClasses; memoryClassIdx++) {
52+
if (Platform_memoryClasses[memoryClassIdx].countsAsCache) {
53+
this->values[memoryClassIdx] = NAN;
54+
}
55+
}
4656
}
47-
/* Do not print available memory in bar mode */
48-
static_assert(MEMORY_METER_AVAILABLE + 1 == MEMORY_METER_ITEMCOUNT,
49-
"MEMORY_METER_AVAILABLE is not the last item in MemoryMeterValues");
50-
this->curItems = MEMORY_METER_AVAILABLE;
51-
52-
/* we actually want to show "used + shared + compressed" */
53-
double used = this->values[MEMORY_METER_USED];
54-
if (isPositive(this->values[MEMORY_METER_SHARED]))
55-
used += this->values[MEMORY_METER_SHARED];
56-
if (isPositive(this->values[MEMORY_METER_COMPRESSED]))
57-
used += this->values[MEMORY_METER_COMPRESSED];
5857

5958
written = Meter_humanUnit(buffer, used, size);
6059
METER_BUFFER_CHECK(buffer, size, written);
@@ -73,37 +72,16 @@ static void MemoryMeter_display(const Object* cast, RichString* out) {
7372
Meter_humanUnit(buffer, this->total, sizeof(buffer));
7473
RichString_appendAscii(out, CRT_colors[METER_VALUE], buffer);
7574

76-
Meter_humanUnit(buffer, this->values[MEMORY_METER_USED], sizeof(buffer));
77-
RichString_appendAscii(out, CRT_colors[METER_TEXT], " used:");
78-
RichString_appendAscii(out, CRT_colors[MEMORY_USED], buffer);
79-
80-
/* shared memory is not supported on all platforms */
81-
if (isNonnegative(this->values[MEMORY_METER_SHARED])) {
82-
Meter_humanUnit(buffer, this->values[MEMORY_METER_SHARED], sizeof(buffer));
83-
RichString_appendAscii(out, CRT_colors[METER_TEXT], " shared:");
84-
RichString_appendAscii(out, CRT_colors[MEMORY_SHARED], buffer);
85-
}
86-
87-
/* compressed memory is not supported on all platforms */
88-
if (isNonnegative(this->values[MEMORY_METER_COMPRESSED])) {
89-
Meter_humanUnit(buffer, this->values[MEMORY_METER_COMPRESSED], sizeof(buffer));
90-
RichString_appendAscii(out, CRT_colors[METER_TEXT], " compressed:");
91-
RichString_appendAscii(out, CRT_colors[MEMORY_COMPRESSED], buffer);
92-
}
93-
94-
Meter_humanUnit(buffer, this->values[MEMORY_METER_BUFFERS], sizeof(buffer));
95-
RichString_appendAscii(out, settings->showCachedMemory ? CRT_colors[METER_TEXT] : CRT_colors[METER_SHADOW], " buffers:");
96-
RichString_appendAscii(out, settings->showCachedMemory ? CRT_colors[MEMORY_BUFFERS_TEXT] : CRT_colors[METER_SHADOW], buffer);
97-
98-
Meter_humanUnit(buffer, this->values[MEMORY_METER_CACHE], sizeof(buffer));
99-
RichString_appendAscii(out, settings->showCachedMemory ? CRT_colors[METER_TEXT] : CRT_colors[METER_SHADOW], " cache:");
100-
RichString_appendAscii(out, settings->showCachedMemory ? CRT_colors[MEMORY_CACHE] : CRT_colors[METER_SHADOW], buffer);
75+
/* print the OS-specific memory classes in the order supplied by their implementation */
76+
for (unsigned int memoryClassIdx = 0; memoryClassIdx < Platform_numberOfMemoryClasses; memoryClassIdx++) {
77+
if (!settings->showCachedMemory && Platform_memoryClasses[memoryClassIdx].countsAsCache)
78+
continue; // skip reclaimable cache memory classes if "show cached memory" is not ticked
10179

102-
/* available memory is not supported on all platforms */
103-
if (isNonnegative(this->values[MEMORY_METER_AVAILABLE])) {
104-
Meter_humanUnit(buffer, this->values[MEMORY_METER_AVAILABLE], sizeof(buffer));
105-
RichString_appendAscii(out, CRT_colors[METER_TEXT], " available:");
106-
RichString_appendAscii(out, CRT_colors[METER_VALUE], buffer);
80+
Meter_humanUnit(buffer, this->values[memoryClassIdx], sizeof(buffer));
81+
RichString_appendAscii(out, CRT_colors[METER_TEXT], " ");
82+
RichString_appendAscii(out, CRT_colors[METER_TEXT], Platform_memoryClasses[memoryClassIdx].label);
83+
RichString_appendAscii(out, CRT_colors[METER_TEXT], ":");
84+
RichString_appendAscii(out, CRT_colors[Platform_memoryClasses[memoryClassIdx].color], buffer);
10785
}
10886
}
10987

@@ -116,10 +94,10 @@ const MeterClass MemoryMeter_class = {
11694
.updateValues = MemoryMeter_updateValues,
11795
.defaultMode = BAR_METERMODE,
11896
.supportedModes = METERMODE_DEFAULT_SUPPORTED,
119-
.maxItems = MEMORY_METER_ITEMCOUNT,
97+
.maxItems = NUMBER_OF_DYNAMIC_COLORS, // all the range of DYNAMIC_xxxx colors are allowed
12098
.isPercentChart = true,
12199
.total = 100.0,
122-
.attributes = MemoryMeter_attributes,
100+
.attributes = Platform_memoryMeter_attributes, // OS-specific
123101
.name = "Memory",
124102
.uiName = "Memory",
125103
.caption = "Mem"

MemoryMeter.h

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,15 @@ in the source distribution for its full text.
99

1010
#include "Meter.h"
1111

12-
typedef enum {
13-
MEMORY_METER_USED = 0,
14-
MEMORY_METER_SHARED = 1,
15-
MEMORY_METER_COMPRESSED = 2,
16-
MEMORY_METER_BUFFERS = 3,
17-
MEMORY_METER_CACHE = 4,
18-
MEMORY_METER_AVAILABLE = 5,
19-
MEMORY_METER_ITEMCOUNT = 6, // number of entries in this enum
20-
} MemoryMeterValues;
12+
typedef struct MemoryClass_s {
13+
const char *label; // e.g. "used", "shared", "compressed", etc. Should reflect the system-specific 'top' classes
14+
bool countsAsUsed; // whether this memory class counts as "used" memory
15+
bool countsAsCache; // whether this memory class can be reclaimed under pressure (and displayed when "show cached memory" is checked)
16+
ColorElements color; // one of the DYNAMIC_xxx CRT color values
17+
} MemoryClass;
18+
19+
extern const MemoryClass Platform_memoryClasses[]; // defined in the platform-specific code
20+
extern const unsigned int Platform_numberOfMemoryClasses; // defined in the platform-specific code
2121

2222
extern const MeterClass MemoryMeter_class;
2323

darwin/DarwinMachine.h

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,7 @@ typedef struct DarwinMachine_ {
1919
Machine super;
2020

2121
host_basic_info_data_t host_info;
22-
#ifdef HAVE_STRUCT_VM_STATISTICS64
2322
vm_statistics64_data_t vm_stats;
24-
#else
25-
vm_statistics_data_t vm_stats;
26-
#endif
2723
processor_cpu_load_info_t prev_load;
2824
processor_cpu_load_info_t curr_load;
2925

darwin/Platform.c

Lines changed: 52 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,33 @@ const SignalItem Platform_signals[] = {
113113

114114
const unsigned int Platform_numberOfSignals = ARRAYSIZE(Platform_signals);
115115

116+
const MemoryClass Platform_memoryClasses[] = {
117+
#define MEMORY_CLASS_WIRED 0
118+
{ .label = "wired", .countsAsUsed = true, .countsAsCache = false, .color = DYNAMIC_RED }, // pages wired down to physical memory (kernel)
119+
#define MEMORY_CLASS_SPECULATIVE 1
120+
{ .label = "speculative", .countsAsUsed = true, .countsAsCache = true, .color = DYNAMIC_MAGENTA }, // readahead optimization caches
121+
#define MEMORY_CLASS_ACTIVE 2
122+
{ .label = "active", .countsAsUsed = true, .countsAsCache = false, .color = DYNAMIC_GREEN }, // userland pages actively being used
123+
#define MEMORY_CLASS_PURGEABLE 3
124+
{ .label = "purgeable", .countsAsUsed = false, .countsAsCache = true, .color = DYNAMIC_YELLOW }, // userland pages voluntarily marked "discardable" by apps
125+
#define MEMORY_CLASS_COMPRESSED 4
126+
{ .label = "compressed", .countsAsUsed = true, .countsAsCache = false, .color = DYNAMIC_BLUE }, // userland pages being compressed (means memory pressure++)
127+
#define MEMORY_CLASS_INACTIVE 5
128+
{ .label = "inactive", .countsAsUsed = true, .countsAsCache = true, .color = DYNAMIC_GRAY }, // pages no longer used; macOS counts them as "used" anyway...
129+
}; // N.B. the chart will display categories in this order
130+
131+
const unsigned int Platform_numberOfMemoryClasses = ARRAYSIZE(Platform_memoryClasses);
132+
133+
const int Platform_memoryMeter_attributes[] = {
134+
Platform_memoryClasses[0].color,
135+
Platform_memoryClasses[1].color,
136+
Platform_memoryClasses[2].color,
137+
Platform_memoryClasses[3].color,
138+
Platform_memoryClasses[4].color,
139+
Platform_memoryClasses[5].color
140+
}; // there MUST be as many entries in this attributes array as memory classes
141+
142+
116143
const MeterClass* const Platform_meterTypes[] = {
117144
&CPUMeter_class,
118145
&ClockMeter_class,
@@ -389,41 +416,41 @@ void Platform_setGPUValues(Meter* mtr, double* totalUsage, unsigned long long* t
389416

390417
void Platform_setMemoryValues(Meter* mtr) {
391418
const DarwinMachine* dhost = (const DarwinMachine*) mtr->host;
392-
#ifdef HAVE_STRUCT_VM_STATISTICS64
393-
const struct vm_statistics64* vm = &dhost->vm_stats;
394-
#else
395-
const struct vm_statistics* vm = &dhost->vm_stats;
396-
#endif
419+
const Settings* settings = mtr->host->settings;
397420
double page_K = (double)vm_page_size / (double)1024;
398421

399-
mtr->total = dhost->host_info.max_mem / 1024;
400-
401422
#ifdef HAVE_STRUCT_VM_STATISTICS64
423+
const struct vm_statistics64* vm = &dhost->vm_stats;
402424
#ifdef HAVE_STRUCT_VM_STATISTICS64_EXTERNAL_PAGE_COUNT
403425
const natural_t external_page_count = vm->external_page_count;
404426
#else
405427
const natural_t external_page_count = 0;
406428
#endif
407-
const natural_t used_counts_from_statistics64 = vm->inactive_count + vm->speculative_count
408-
- vm->purgeable_count - external_page_count;
429+
#ifdef HAVE_STRUCT_VM_STATISTICS64_COMPRESSOR_PAGE_COUNT
430+
const natural_t compressor_page_count = vm->compressor_page_count;
431+
#else
432+
const natural_t compressor_page_count = 0;
433+
#endif
409434
#else
410-
const natural_t used_counts_from_statistics64 = 0;
435+
const struct vm_statistics* vm = &dhost->vm_stats;
436+
const natural_t external_page_count = 0;
437+
const natural_t compressor_page_count = 0;
411438
#endif // HAVE_STRUCT_VM_STATISTICS64
412-
const natural_t used_count = vm->active_count + vm->wire_count + used_counts_from_statistics64;
413-
mtr->values[MEMORY_METER_USED] = (double)used_count * page_K;
414-
// mtr->values[MEMORY_METER_SHARED] = "shared memory, like tmpfs and shm"
415-
#ifdef HAVE_STRUCT_VM_STATISTICS64
416-
#ifdef HAVE_STRUCT_VM_STATISTICS64_COMPRESSOR_PAGE_COUNT
417-
mtr->values[MEMORY_METER_COMPRESSED] = (double)vm->compressor_page_count * page_K;
418-
#else
419-
// mtr->values[MEMORY_METER_COMPRESSED] = "compressed memory not available"
420-
#endif
421-
#else
422-
// mtr->values[MEMORY_METER_COMPRESSED] = "compressed memory, like zswap on linux"
423-
#endif
424-
mtr->values[MEMORY_METER_BUFFERS] = (double)vm->purgeable_count * page_K;
425-
mtr->values[MEMORY_METER_CACHE] = (double)vm->inactive_count * page_K;
426-
// mtr->values[MEMORY_METER_AVAILABLE] = "available memory"
439+
440+
mtr->total = dhost->host_info.max_mem / 1024;
441+
mtr->values[MEMORY_CLASS_WIRED] = page_K * vm->wire_count;
442+
if (settings->showCachedMemory) {
443+
mtr->values[MEMORY_CLASS_SPECULATIVE] = page_K * vm->speculative_count;
444+
mtr->values[MEMORY_CLASS_ACTIVE] = page_K * (vm->active_count - vm->purgeable_count - external_page_count); // external pages are pages swapped out
445+
mtr->values[MEMORY_CLASS_PURGEABLE] = page_K * vm->purgeable_count; // purgeable pages are flagged in the active pages
446+
}
447+
else { // if showCachedMemory is disabled, merge speculative and purgeable into the active pages
448+
mtr->values[MEMORY_CLASS_SPECULATIVE] = 0;
449+
mtr->values[MEMORY_CLASS_ACTIVE] = page_K * (vm->speculative_count + vm->active_count - external_page_count); // external pages are pages swapped out
450+
mtr->values[MEMORY_CLASS_PURGEABLE] = 0;
451+
}
452+
mtr->values[MEMORY_CLASS_COMPRESSED] = page_K * compressor_page_count;
453+
mtr->values[MEMORY_CLASS_INACTIVE] = page_K * vm->inactive_count; // for some reason macOS counts inactive pages in the "used" memory...
427454
}
428455

429456
void Platform_setSwapValues(Meter* mtr) {

0 commit comments

Comments
 (0)