File: wlmmemgraph.c

package info (click to toggle)
wlmaker 0.7.1-2
  • links: PTS, VCS
  • area: main
  • in suites: experimental
  • size: 7,696 kB
  • sloc: ansic: 58,587; xml: 1,424; python: 1,400; cpp: 253; yacc: 118; sh: 73; lex: 70; makefile: 8
file content (316 lines) | stat: -rw-r--r-- 10,226 bytes parent folder | download | duplicates (2)
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 ================================================= */