1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316
|
/* ========================================================================= */
/**
* @file wlmmemgraph.c
*
* Memory usage graph dock-app for wlmaker.
*
* @copyright
* Copyright 2026 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "wlm_graph_shared.h"
#include <libbase/libbase.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/** Application name. */
static const char _app_name[] = "wlmmemgraph";
/** Application help. */
static const char _app_help[] =
"Displays memory usage as a scrolling graph.\n"
"\n"
"Shows stacked memory categories:\n"
" - Cached: top of graph (dark blue)\n"
" - Buffers: middle of graph (dark cyan)\n"
" - Used: bottom of graph (green)\n"
"\n"
"The label displays total memory usage.";
/** Line buffer size for /proc/meminfo parsing. */
#define LINE_BUFFER_SIZE 256
/** Number of fields to parse from /proc/meminfo. */
#define MEMINFO_FIELD_COUNT 5
/* == Definitions ========================================================== */
/** Number of memory categories tracked. */
#define MEM_CATEGORY_COUNT 3
/** Memory category indices. */
enum {
/** Cached (including SReclaimable). */
MEM_CATEGORY_CACHED = 0,
/** Buffers. */
MEM_CATEGORY_BUFFERS = 1,
/** Used (non-reclaimable). */
MEM_CATEGORY_USED = 2,
};
/* ------------------------------------------------------------------------- */
/**
* Generates blue-to-green gradient LUT (256 entries).
*
* With 3 memory categories, only indices 0, 127, and 255 are sampled
* (mapping count 1, 2, 3 respectively). The full 256 entries are required
* by the API but only 3 discrete colors matter for this use case.
*
* @param lut Output LUT array (256 entries).
*/
static void _memgraph_lut_init(uint32_t lut[256])
{
for (uint32_t i = 0; i < 256; i++) {
// Blue to green gradient, with lower values (cached/buffers) darker.
// Cached and buffers are darker as this memory is technically free.
// Brightness scales from 1/3 at i=0 to 2/3 at i=255.
const uint32_t brightness = 85 + (i * 85) / 255; // 85-170 (1/3-2/3)
const uint8_t g = (uint8_t)((i * brightness) / 255);
const uint8_t b = (uint8_t)(((255 - i) * brightness) / 255);
lut[i] = 0xff000000 | ((uint32_t)g << 8) | (uint32_t)b;
}
}
/** Maximum length of the label string. */
#define LABEL_BUFFER_SIZE 16
/** Kilobytes per megabyte. */
#define KB_PER_MB 1024UL
/** Kilobytes per gigabyte. */
#define KB_PER_GB (1024UL * 1024)
/** Kilobytes per terabyte. */
#define KB_PER_TB (1024UL * 1024 * 1024)
/** State for the memory graph (mutable runtime data). */
typedef struct {
/** Open file handle to /proc/meminfo. */
FILE *proc_fp;
/** Total memory in kB (from /proc/meminfo). */
unsigned long mem_total_kb;
/** Used memory in kB (non-reclaimable). */
unsigned long mem_used_kb;
/** Formatted label string. */
char label[LABEL_BUFFER_SIZE];
} memgraph_state_t;
/* == Cleanup ============================================================== */
/* ------------------------------------------------------------------------- */
/** Frees all allocated state. */
static void _state_free(void *app_state)
{
memgraph_state_t *state = app_state;
if (NULL != state->proc_fp) {
fclose(state->proc_fp);
state->proc_fp = NULL;
}
}
/* == Label ================================================================ */
/* ------------------------------------------------------------------------- */
/**
* Formats memory size with appropriate suffix (GB, MB, kB).
*
* @param buf Output buffer.
* @param buf_size Size of output buffer.
* @param kb Memory size in kilobytes.
*/
static void _format_memory_size(char *buf, const size_t buf_size, const unsigned long kb)
{
BS_ASSERT(0 < buf_size);
if (kb >= KB_PER_TB) {
// TB range: X.XXXX TB (4 decimal places).
const unsigned long scale = 10000;
const unsigned long val = (kb * scale) / KB_PER_TB;
snprintf(buf, buf_size, "%lu.%04lu TB", val / scale, val % scale);
} else if (kb >= KB_PER_GB) {
// GB range: X.XX GB (2 decimal places).
const unsigned long scale = 100;
const unsigned long val = (kb * scale) / KB_PER_GB;
snprintf(buf, buf_size, "%lu.%02lu GB", val / scale, val % scale);
} else if (kb >= KB_PER_MB) {
// MB range: X.X MB (1 decimal place).
const unsigned long scale = 10;
const unsigned long val = (kb * scale) / KB_PER_MB;
snprintf(buf, buf_size, "%lu.%lu MB", val / scale, val % scale);
} else {
// kB range.
snprintf(buf, buf_size, "%lu kB", kb);
}
buf[buf_size - 1] = '\0';
}
/* ------------------------------------------------------------------------- */
/** Returns the formatted memory usage label. */
static const char *_label_fn(void *app_state)
{
memgraph_state_t *state = app_state;
return state->label;
}
/* == Memory statistics ==================================================== */
/**
* Matches line label against a string literal.
*
* Compares `label_len` bytes of `line` against the literal. Used for parsing
* /proc/meminfo where each line has format "Label: value kB".
*
* @param literal String literal to match against.
*/
#define LABEL_MATCH(literal) \
(sizeof(literal) - 1 == label_len && 0 == memcmp(line, literal, sizeof(literal) - 1))
/* ------------------------------------------------------------------------- */
/**
* Reads memory statistics from /proc/meminfo.
*
* Parses MemTotal, MemFree, Buffers, Cached, and SReclaimable to compute
* per-category usage percentages.
*
* @param app_state App state (memgraph_state_t pointer).
* @param values Buffer to fill (may reallocate data/num).
*
* @return WLM_GRAPH_READ_OK on success, WLM_GRAPH_READ_ERROR on error.
*/
static wlm_graph_read_result_t _stats_read_fn(void *app_state, wlm_graph_values_t *values)
{
memgraph_state_t * const state = app_state;
FILE * const fp = state->proc_fp;
if (NULL == fp) {
return WLM_GRAPH_READ_ERROR;
}
// Reallocate buffer if size doesn't match.
if (MEM_CATEGORY_COUNT != values->num) {
uint8_t *new_buf = realloc(values->data, MEM_CATEGORY_COUNT);
if (NULL == new_buf) {
return WLM_GRAPH_READ_ERROR;
}
values->data = new_buf;
values->num = MEM_CATEGORY_COUNT;
}
rewind(fp);
unsigned long mem_total = 0;
unsigned long mem_free = 0;
unsigned long buffers = 0;
unsigned long cached = 0;
unsigned long sreclaimable = 0;
char line[LINE_BUFFER_SIZE];
size_t label_len = 0;
int fields_found = 0;
while (NULL != fgets(line, sizeof(line), fp) &&
fields_found < MEMINFO_FIELD_COUNT) {
// Find the colon to extract the value efficiently.
char * const colon = strchr(line, ':');
if (NULL == colon) {
continue;
}
unsigned long value;
if (1 != sscanf(colon + 1, "%lu", &value)) {
continue;
}
// Match label by prefix length (colon position).
label_len = colon - line;
if (LABEL_MATCH("MemTotal")) {
mem_total = value;
fields_found++;
} else if (LABEL_MATCH("MemFree")) {
mem_free = value;
fields_found++;
} else if (LABEL_MATCH("Buffers")) {
buffers = value;
fields_found++;
} else if (LABEL_MATCH("Cached")) {
cached = value;
fields_found++;
} else if (LABEL_MATCH("SReclaimable")) {
sreclaimable = value;
fields_found++;
}
}
if (0 == mem_total) {
return WLM_GRAPH_READ_ERROR;
}
// Calculate usage percentages (0-255).
// Used = MemTotal - MemFree - Buffers - Cached - SReclaimable
const unsigned long reclaimable = buffers + cached + sreclaimable;
unsigned long used = 0;
if (mem_total > mem_free + reclaimable) {
used = mem_total - mem_free - reclaimable;
}
// Scale each category to 0-255 based on total memory.
values->data[MEM_CATEGORY_USED] = (uint8_t)((used * 255) / mem_total);
values->data[MEM_CATEGORY_BUFFERS] = (uint8_t)((buffers * 255) / mem_total);
values->data[MEM_CATEGORY_CACHED] = (uint8_t)(((cached + sreclaimable) * 255) / mem_total);
// Store values and format label for display.
state->mem_total_kb = mem_total;
state->mem_used_kb = used;
_format_memory_size(state->label, sizeof(state->label), used);
return WLM_GRAPH_READ_OK;
}
#undef LABEL_MATCH
/* == Main program ========================================================= */
/** Main program. */
int main(const int argc, const char **argv)
{
memgraph_state_t state = {};
state.proc_fp = fopen("/proc/meminfo", "r");
if (NULL == state.proc_fp) {
bs_log(BS_ERROR | BS_ERRNO, "Failed to open /proc/meminfo");
return EXIT_FAILURE;
}
// Initialize custom LUT (blue-to-green gradient).
uint32_t pixel_lut[256];
_memgraph_lut_init(pixel_lut);
const wlm_graph_app_config_t config = {
.app_name = _app_name,
.app_help = _app_help,
.accumulate_mode = WLM_GRAPH_ACCUMULATE_MODE_STACKED,
.stats_read_fn = _stats_read_fn,
.app_state = &state,
.state_free_fn = _state_free,
.pixel_lut = pixel_lut,
.label_fn = _label_fn,
};
return wlm_graph_app_run(argc, argv, &config);
}
/* == End of wlmmemgraph.c ================================================= */
|