forked from rfjakob/earlyoom
-
Notifications
You must be signed in to change notification settings - Fork 0
/
meminfo.c
286 lines (260 loc) · 8.15 KB
/
meminfo.c
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
// SPDX-License-Identifier: MIT
/* Parse /proc/meminfo
* Returned values are in kiB */
#include <errno.h>
#include <signal.h>
#include <stddef.h> // for size_t
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include "globals.h"
#include "meminfo.h"
#include "msg.h"
#include "proc_pid.h"
/* Parse the contents of /proc/meminfo (in buf), return value of "name"
* (example: "MemTotal:")
* Returns -errno if the entry cannot be found. */
static long long get_entry(const char* name, const char* buf)
{
char* hit = strstr(buf, name);
if (hit == NULL) {
return -ENODATA;
}
errno = 0;
long long val = strtoll(hit + strlen(name), NULL, 10);
if (errno != 0) {
int strtoll_errno = errno;
warn("%s: strtol() failed: %s", __func__, strerror(errno));
return -strtoll_errno;
}
return val;
}
/* Like get_entry(), but exit if the value cannot be found */
static long long get_entry_fatal(const char* name, const char* buf)
{
long long val = get_entry(name, buf);
if (val < 0) {
warn("%s: fatal error, dumping buffer for later diagnosis:\n%s", __func__, buf);
fatal(104, "could not find entry '%s' in /proc/meminfo: %s\n", name, strerror((int)-val));
}
return val;
}
/* If the kernel does not provide MemAvailable (introduced in Linux 3.14),
* approximate it using other data we can get */
static long long available_guesstimate(const char* buf)
{
long long Cached = get_entry_fatal("Cached:", buf);
long long MemFree = get_entry_fatal("MemFree:", buf);
long long Buffers = get_entry_fatal("Buffers:", buf);
long long Shmem = get_entry_fatal("Shmem:", buf);
return MemFree + Cached + Buffers - Shmem;
}
/* Parse /proc/meminfo.
* This function either returns valid data or kills the process
* with a fatal error.
*/
meminfo_t parse_meminfo()
{
// Note that we do not need to close static FDs that we ensure to
// `fopen()` maximally once.
static FILE* fd;
static int guesstimate_warned = 0;
// On Linux 5.3, "wc -c /proc/meminfo" counts 1391 bytes.
// 8192 should be enough for the foreseeable future.
char buf[8192] = { 0 };
meminfo_t m = { 0 };
if (fd == NULL) {
char buf[PATH_LEN] = { 0 };
snprintf(buf, sizeof(buf), "%s/%s", procdir_path, "meminfo");
fd = fopen(buf, "r");
}
if (fd == NULL) {
fatal(102, "could not open /proc/meminfo: %s\n", strerror(errno));
}
rewind(fd);
size_t len = fread(buf, 1, sizeof(buf) - 1, fd);
if (ferror(fd)) {
fatal(103, "could not read /proc/meminfo: %s\n", strerror(errno));
}
if (len == 0) {
fatal(103, "could not read /proc/meminfo: 0 bytes returned\n");
}
m.MemTotalKiB = get_entry_fatal("MemTotal:", buf);
m.SwapTotalKiB = get_entry_fatal("SwapTotal:", buf);
m.AnonPagesKiB = get_entry_fatal("AnonPages:", buf);
m.SwapFreeKiB = get_entry_fatal("SwapFree:", buf);
m.MemAvailableKiB = get_entry("MemAvailable:", buf);
if (m.MemAvailableKiB < 0) {
m.MemAvailableKiB = available_guesstimate(buf);
if (guesstimate_warned == 0) {
fprintf(stderr, "Warning: Your kernel does not provide MemAvailable data (needs 3.14+)\n"
" Falling back to guesstimate\n");
guesstimate_warned = 1;
}
}
// Calculated values
m.UserMemTotalKiB = m.MemAvailableKiB + m.AnonPagesKiB;
// Calculate percentages
m.MemAvailablePercent = (double)m.MemAvailableKiB * 100 / (double)m.UserMemTotalKiB;
if (m.SwapTotalKiB > 0) {
m.SwapFreePercent = (double)m.SwapFreeKiB * 100 / (double)m.SwapTotalKiB;
} else {
m.SwapFreePercent = 0;
}
return m;
}
bool is_alive(int pid)
{
// whole process group (-g flag)?
if (pid < 0) {
// signal 0 does nothing, but we do get an error when the process
// group does not exist.
int res = kill(pid, 0);
if (res == 0) {
return true;
}
return false;
}
pid_stat_t stat;
if (!parse_proc_pid_stat(&stat, pid)) {
return false;
}
debug("%s: state=%c num_threads=%ld\n", __func__, stat.state, stat.num_threads);
if (stat.state == 'Z' && stat.num_threads == 1) {
// A zombie process without subthreads does not use any memory. Consider it dead.
return false;
}
return true;
}
/* Read /proc/[pid]/[name] and convert to integer.
* As the value may legitimately be < 0 (think oom_score_adj),
* it is stored in the `out` pointer, and the return value is either
* 0 (success) or -errno (failure).
*/
static int read_proc_file_integer(const int pid, const char* name, int* out)
{
char path[PATH_LEN] = { 0 };
snprintf(path, sizeof(path), "%s/%d/%s", procdir_path, pid, name);
FILE* f = fopen(path, "r");
if (f == NULL) {
return -errno;
}
int matches = fscanf(f, "%d", out);
fclose(f);
if (matches != 1) {
return -ENODATA;
}
return 0;
}
/* Read /proc/[pid]/oom_score.
* Returns the value (>= 0) or -errno on error.
*/
int get_oom_score(const int pid)
{
int out = 0;
int res = read_proc_file_integer(pid, "oom_score", &out);
if (res < 0) {
return res;
}
return out;
}
/* Read /proc/[pid]/oom_score_adj.
* As the value may legitimately be negative, the return value is
* only used for error indication, and the value is stored in
* the `out` pointer.
* Returns 0 on success and -errno on error.
*/
int get_oom_score_adj(const int pid, int* out)
{
return read_proc_file_integer(pid, "oom_score_adj", out);
}
/* Read /proc/[pid]/comm (process name truncated to 16 bytes).
* Returns 0 on success and -errno on error.
*/
int get_comm(int pid, char* out, size_t outlen)
{
char path[PATH_LEN] = { 0 };
snprintf(path, sizeof(path), "%s/%d/comm", procdir_path, pid);
FILE* f = fopen(path, "r");
if (f == NULL) {
return -errno;
}
size_t n = fread(out, 1, outlen - 1, f);
if (ferror(f)) {
int fread_errno = errno;
perror("get_comm: fread() failed");
fclose(f);
return -fread_errno;
}
fclose(f);
// Process name may be empty, but we should get at least a newline
// Example for empty process name: perl -MPOSIX -e '$0=""; pause'
if (n < 1) {
return -ENODATA;
}
// Strip trailing space
out[n - 1] = 0;
fix_truncated_utf8(out);
return 0;
}
/* Read /proc/[pid]/cmdline (process command line truncated to 256 bytes).
* The null bytes are replaced by space.
* Returns 0 on success and -errno on error.
*/
int get_cmdline(int pid, char* out, size_t outlen)
{
char path[PATH_LEN] = { 0 };
snprintf(path, sizeof(path), "%s/%d/cmdline", procdir_path, pid);
FILE* f = fopen(path, "r");
if (f == NULL) {
return -errno;
}
size_t n = fread(out, 1, outlen - 1, f);
if (ferror(f)) {
int fread_errno = errno;
perror("get_cmdline: fread() failed");
fclose(f);
return -fread_errno;
}
fclose(f);
/* replace null character with space */
for (size_t i = 0; i < n; i++) {
if (out[i] == '\0') {
out[i] = ' ';
}
}
// Strip trailing space
out[n - 1] = 0;
fix_truncated_utf8(out);
return 0;
}
// Get the effective uid (EUID) of `pid`.
// Returns the uid (>= 0) or -errno on error.
int get_uid(int pid)
{
char path[PATH_LEN] = { 0 };
snprintf(path, sizeof(path), "%s/%d", procdir_path, pid);
struct stat st = { 0 };
int res = stat(path, &st);
if (res < 0) {
return -errno;
}
return (int)st.st_uid;
}
/* Print a status line like
* mem avail: 5259 MiB (67 %), swap free: 0 MiB (0 %)"
* as an informational message to stdout (default), or
* as a warning to stderr.
*/
void print_mem_stats(int __attribute__((format(printf, 1, 2))) (*out_func)(const char* fmt, ...), const meminfo_t m)
{
out_func("mem avail: %5lld of %5lld MiB (" PRIPCT "), swap free: %4lld of %4lld MiB (" PRIPCT ")\n",
m.MemAvailableKiB / 1024,
m.UserMemTotalKiB / 1024,
m.MemAvailablePercent,
m.SwapFreeKiB / 1024,
m.SwapTotalKiB / 1024,
m.SwapFreePercent);
}