Line data Source code
1 : /* SPDX-License-Identifier: LGPL-2.1+ */
2 :
3 : #include <errno.h>
4 :
5 : #include "alloc-util.h"
6 : #include "def.h"
7 : #include "fd-util.h"
8 : #include "fileio.h"
9 : #include "parse-util.h"
10 : #include "process-util.h"
11 : #include "procfs-util.h"
12 : #include "stdio-util.h"
13 : #include "string-util.h"
14 :
15 19 : int procfs_tasks_get_limit(uint64_t *ret) {
16 19 : _cleanup_free_ char *value = NULL;
17 : uint64_t pid_max, threads_max;
18 : int r;
19 :
20 19 : assert(ret);
21 :
22 : /* So there are two sysctl files that control the system limit of processes:
23 : *
24 : * 1. kernel.threads-max: this is probably the sysctl that makes more sense, as it directly puts a limit on
25 : * concurrent tasks.
26 : *
27 : * 2. kernel.pid_max: this limits the numeric range PIDs can take, and thus indirectly also limits the number
28 : * of concurrent threads. AFAICS it's primarily a compatibility concept: some crappy old code used a signed
29 : * 16bit type for PIDs, hence the kernel provides a way to ensure the PIDs never go beyond INT16_MAX by
30 : * default.
31 : *
32 : * By default #2 is set to much lower values than #1, hence the limit people come into contact with first, as
33 : * it's the lowest boundary they need to bump when they want higher number of processes.
34 : *
35 : * Also note the weird definition of #2: PIDs assigned will be kept below this value, which means the number of
36 : * tasks that can be created is one lower, as PID 0 is not a valid process ID. */
37 :
38 19 : r = read_one_line_file("/proc/sys/kernel/pid_max", &value);
39 19 : if (r < 0)
40 0 : return r;
41 :
42 19 : r = safe_atou64(value, &pid_max);
43 19 : if (r < 0)
44 0 : return r;
45 :
46 19 : value = mfree(value);
47 19 : r = read_one_line_file("/proc/sys/kernel/threads-max", &value);
48 19 : if (r < 0)
49 0 : return r;
50 :
51 19 : r = safe_atou64(value, &threads_max);
52 19 : if (r < 0)
53 0 : return r;
54 :
55 : /* Subtract one from pid_max, since PID 0 is not a valid PID */
56 19 : *ret = MIN(pid_max-1, threads_max);
57 19 : return 0;
58 : }
59 :
60 3 : int procfs_tasks_set_limit(uint64_t limit) {
61 : char buffer[DECIMAL_STR_MAX(uint64_t)+1];
62 3 : _cleanup_free_ char *value = NULL;
63 : uint64_t pid_max;
64 : int r;
65 :
66 3 : if (limit == 0) /* This makes no sense, we are userspace and hence count as tasks too, and we want to live,
67 : * hence the limit conceptually has to be above 0. Also, most likely if anyone asks for a zero
68 : * limit he/she probably means "no limit", hence let's better refuse this to avoid
69 : * confusion. */
70 0 : return -EINVAL;
71 :
72 : /* The Linux kernel doesn't allow this value to go below 20, hence don't allow this either, higher values than
73 : * TASKS_MAX are not accepted by the pid_max sysctl. We'll treat anything this high as "unbounded" and hence
74 : * set it to the maximum. */
75 3 : limit = CLAMP(limit, 20U, TASKS_MAX);
76 :
77 3 : r = read_one_line_file("/proc/sys/kernel/pid_max", &value);
78 3 : if (r < 0)
79 0 : return r;
80 3 : r = safe_atou64(value, &pid_max);
81 3 : if (r < 0)
82 0 : return r;
83 :
84 : /* As pid_max is about the numeric pid_t range we'll bump it if necessary, but only ever increase it, never
85 : * decrease it, as threads-max is the much more relevant sysctl. */
86 3 : if (limit > pid_max-1) {
87 0 : sprintf(buffer, "%" PRIu64, limit+1); /* Add one, since PID 0 is not a valid PID */
88 0 : r = write_string_file("/proc/sys/kernel/pid_max", buffer, WRITE_STRING_FILE_DISABLE_BUFFER);
89 0 : if (r < 0)
90 0 : return r;
91 : }
92 :
93 3 : sprintf(buffer, "%" PRIu64, limit);
94 3 : r = write_string_file("/proc/sys/kernel/threads-max", buffer, WRITE_STRING_FILE_DISABLE_BUFFER);
95 3 : if (r < 0) {
96 : uint64_t threads_max;
97 :
98 : /* Hmm, we couldn't write this? If so, maybe it was already set properly? In that case let's not
99 : * generate an error */
100 :
101 3 : value = mfree(value);
102 3 : if (read_one_line_file("/proc/sys/kernel/threads-max", &value) < 0)
103 1 : return r; /* return original error */
104 :
105 3 : if (safe_atou64(value, &threads_max) < 0)
106 0 : return r; /* return original error */
107 :
108 3 : if (MIN(pid_max-1, threads_max) != limit)
109 1 : return r; /* return original error */
110 :
111 : /* Yay! Value set already matches what we were trying to set, hence consider this a success. */
112 : }
113 :
114 2 : return 0;
115 : }
116 :
117 1 : int procfs_tasks_get_current(uint64_t *ret) {
118 1 : _cleanup_free_ char *value = NULL;
119 : const char *p, *nr;
120 : size_t n;
121 : int r;
122 :
123 1 : assert(ret);
124 :
125 1 : r = read_one_line_file("/proc/loadavg", &value);
126 1 : if (r < 0)
127 0 : return r;
128 :
129 : /* Look for the second part of the fourth field, which is separated by a slash from the first part. None of the
130 : * earlier fields use a slash, hence let's use this to find the right spot. */
131 1 : p = strchr(value, '/');
132 1 : if (!p)
133 0 : return -EINVAL;
134 :
135 1 : p++;
136 1 : n = strspn(p, DIGITS);
137 1 : nr = strndupa(p, n);
138 :
139 1 : return safe_atou64(nr, ret);
140 : }
141 :
142 1 : static uint64_t calc_gcd64(uint64_t a, uint64_t b) {
143 :
144 2 : while (b > 0) {
145 : uint64_t t;
146 :
147 1 : t = a % b;
148 :
149 1 : a = b;
150 1 : b = t;
151 : }
152 :
153 1 : return a;
154 : }
155 :
156 1 : int procfs_cpu_get_usage(nsec_t *ret) {
157 1 : _cleanup_free_ char *first_line = NULL;
158 : unsigned long user_ticks, nice_ticks, system_ticks, irq_ticks, softirq_ticks,
159 1 : guest_ticks = 0, guest_nice_ticks = 0;
160 : long ticks_per_second;
161 : uint64_t sum, gcd, a, b;
162 : const char *p;
163 : int r;
164 :
165 1 : assert(ret);
166 :
167 1 : r = read_one_line_file("/proc/stat", &first_line);
168 1 : if (r < 0)
169 0 : return r;
170 :
171 1 : p = first_word(first_line, "cpu");
172 1 : if (!p)
173 0 : return -EINVAL;
174 :
175 1 : if (sscanf(p, "%lu %lu %lu %*u %*u %lu %lu %*u %lu %lu",
176 : &user_ticks,
177 : &nice_ticks,
178 : &system_ticks,
179 : &irq_ticks,
180 : &softirq_ticks,
181 : &guest_ticks,
182 : &guest_nice_ticks) < 5) /* we only insist on the first five fields */
183 0 : return -EINVAL;
184 :
185 1 : ticks_per_second = sysconf(_SC_CLK_TCK);
186 1 : if (ticks_per_second < 0)
187 0 : return -errno;
188 1 : assert(ticks_per_second > 0);
189 :
190 2 : sum = (uint64_t) user_ticks + (uint64_t) nice_ticks + (uint64_t) system_ticks +
191 1 : (uint64_t) irq_ticks + (uint64_t) softirq_ticks +
192 1 : (uint64_t) guest_ticks + (uint64_t) guest_nice_ticks;
193 :
194 : /* Let's reduce this fraction before we apply it to avoid overflows when converting this to µsec */
195 1 : gcd = calc_gcd64(NSEC_PER_SEC, ticks_per_second);
196 :
197 1 : a = (uint64_t) NSEC_PER_SEC / gcd;
198 1 : b = (uint64_t) ticks_per_second / gcd;
199 :
200 1 : *ret = DIV_ROUND_UP((nsec_t) sum * (nsec_t) a, (nsec_t) b);
201 1 : return 0;
202 : }
203 :
204 1 : int procfs_memory_get(uint64_t *ret_total, uint64_t *ret_used) {
205 1 : uint64_t mem_total = UINT64_MAX, mem_free = UINT64_MAX;
206 1 : _cleanup_fclose_ FILE *f = NULL;
207 : int r;
208 :
209 1 : f = fopen("/proc/meminfo", "re");
210 1 : if (!f)
211 0 : return -errno;
212 :
213 1 : for (;;) {
214 2 : _cleanup_free_ char *line = NULL;
215 : uint64_t *v;
216 : char *p, *e;
217 : size_t n;
218 :
219 2 : r = read_line(f, LONG_LINE_MAX, &line);
220 2 : if (r < 0)
221 0 : return r;
222 2 : if (r == 0)
223 0 : return -EINVAL; /* EOF: Couldn't find one or both fields? */
224 :
225 2 : p = first_word(line, "MemTotal:");
226 2 : if (p)
227 1 : v = &mem_total;
228 : else {
229 1 : p = first_word(line, "MemFree:");
230 1 : if (p)
231 1 : v = &mem_free;
232 : else
233 0 : continue;
234 : }
235 :
236 : /* Determine length of numeric value */
237 2 : n = strspn(p, DIGITS);
238 2 : if (n == 0)
239 0 : return -EINVAL;
240 2 : e = p + n;
241 :
242 : /* Ensure the line ends in " kB" */
243 2 : n = strspn(e, WHITESPACE);
244 2 : if (n == 0)
245 0 : return -EINVAL;
246 2 : if (!streq(e + n, "kB"))
247 0 : return -EINVAL;
248 :
249 2 : *e = 0;
250 2 : r = safe_atou64(p, v);
251 2 : if (r < 0)
252 0 : return r;
253 2 : if (*v == UINT64_MAX)
254 0 : return -EINVAL;
255 :
256 2 : if (mem_total != UINT64_MAX && mem_free != UINT64_MAX)
257 1 : break;
258 : }
259 :
260 1 : if (mem_free > mem_total)
261 0 : return -EINVAL;
262 :
263 1 : if (ret_total)
264 0 : *ret_total = mem_total * 1024U;
265 1 : if (ret_used)
266 1 : *ret_used = (mem_total - mem_free) * 1024U;
267 1 : return 0;
268 : }
|