]>
Commit | Line | Data |
---|---|---|
9aef9a67 LP |
1 | /* SPDX-License-Identifier: LGPL-2.1+ */ |
2 | ||
3 | #include <errno.h> | |
4 | ||
5 | #include "alloc-util.h" | |
a04fcf17 LP |
6 | #include "def.h" |
7 | #include "fd-util.h" | |
9aef9a67 LP |
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 | int procfs_tasks_get_limit(uint64_t *ret) { | |
16 | _cleanup_free_ char *value = NULL; | |
17 | uint64_t pid_max, threads_max; | |
18 | int r; | |
19 | ||
20 | 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 | r = read_one_line_file("/proc/sys/kernel/pid_max", &value); | |
39 | if (r < 0) | |
40 | return r; | |
41 | ||
42 | r = safe_atou64(value, &pid_max); | |
43 | if (r < 0) | |
44 | return r; | |
45 | ||
46 | value = mfree(value); | |
47 | r = read_one_line_file("/proc/sys/kernel/threads-max", &value); | |
48 | if (r < 0) | |
49 | return r; | |
50 | ||
51 | r = safe_atou64(value, &threads_max); | |
52 | if (r < 0) | |
53 | return r; | |
54 | ||
55 | /* Subtract one from pid_max, since PID 0 is not a valid PID */ | |
56 | *ret = MIN(pid_max-1, threads_max); | |
57 | return 0; | |
58 | } | |
59 | ||
60 | int procfs_tasks_set_limit(uint64_t limit) { | |
61 | char buffer[DECIMAL_STR_MAX(uint64_t)+1]; | |
62 | _cleanup_free_ char *value = NULL; | |
63 | uint64_t pid_max; | |
64 | int r; | |
65 | ||
66 | 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 | 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 | limit = CLAMP(limit, 20U, TASKS_MAX); | |
76 | ||
77 | r = read_one_line_file("/proc/sys/kernel/pid_max", &value); | |
78 | if (r < 0) | |
79 | return r; | |
80 | r = safe_atou64(value, &pid_max); | |
81 | if (r < 0) | |
82 | 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 | if (limit > pid_max-1) { | |
87 | sprintf(buffer, "%" PRIu64, limit+1); /* Add one, since PID 0 is not a valid PID */ | |
88 | r = write_string_file("/proc/sys/kernel/pid_max", buffer, WRITE_STRING_FILE_DISABLE_BUFFER); | |
89 | if (r < 0) | |
90 | return r; | |
91 | } | |
92 | ||
93 | sprintf(buffer, "%" PRIu64, limit); | |
94 | r = write_string_file("/proc/sys/kernel/threads-max", buffer, WRITE_STRING_FILE_DISABLE_BUFFER); | |
95 | 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 | value = mfree(value); | |
102 | if (read_one_line_file("/proc/sys/kernel/threads-max", &value) < 0) | |
103 | return r; /* return original error */ | |
104 | ||
105 | if (safe_atou64(value, &threads_max) < 0) | |
106 | return r; /* return original error */ | |
107 | ||
108 | if (MIN(pid_max-1, threads_max) != limit) | |
109 | 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 | return 0; | |
115 | } | |
116 | ||
117 | int procfs_tasks_get_current(uint64_t *ret) { | |
118 | _cleanup_free_ char *value = NULL; | |
119 | const char *p, *nr; | |
120 | size_t n; | |
121 | int r; | |
122 | ||
123 | assert(ret); | |
124 | ||
125 | r = read_one_line_file("/proc/loadavg", &value); | |
126 | if (r < 0) | |
127 | 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 | p = strchr(value, '/'); | |
132 | if (!p) | |
133 | return -EINVAL; | |
134 | ||
135 | p++; | |
136 | n = strspn(p, DIGITS); | |
137 | nr = strndupa(p, n); | |
138 | ||
139 | return safe_atou64(nr, ret); | |
140 | } | |
a04fcf17 LP |
141 | |
142 | static uint64_t calc_gcd64(uint64_t a, uint64_t b) { | |
143 | ||
144 | while (b > 0) { | |
145 | uint64_t t; | |
146 | ||
147 | t = a % b; | |
148 | ||
149 | a = b; | |
150 | b = t; | |
151 | } | |
152 | ||
153 | return a; | |
154 | } | |
155 | ||
156 | int procfs_cpu_get_usage(nsec_t *ret) { | |
157 | _cleanup_free_ char *first_line = NULL; | |
16a4f265 | 158 | unsigned long user_ticks, nice_ticks, system_ticks, irq_ticks, softirq_ticks, |
a04fcf17 LP |
159 | 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 | assert(ret); | |
166 | ||
167 | r = read_one_line_file("/proc/stat", &first_line); | |
168 | if (r < 0) | |
169 | return r; | |
170 | ||
171 | p = first_word(first_line, "cpu"); | |
172 | if (!p) | |
173 | return -EINVAL; | |
174 | ||
175 | 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 | return -EINVAL; | |
184 | ||
185 | ticks_per_second = sysconf(_SC_CLK_TCK); | |
186 | if (ticks_per_second < 0) | |
187 | return -errno; | |
188 | assert(ticks_per_second > 0); | |
189 | ||
190 | sum = (uint64_t) user_ticks + (uint64_t) nice_ticks + (uint64_t) system_ticks + | |
191 | (uint64_t) irq_ticks + (uint64_t) softirq_ticks + | |
192 | (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 | gcd = calc_gcd64(NSEC_PER_SEC, ticks_per_second); | |
196 | ||
197 | a = (uint64_t) NSEC_PER_SEC / gcd; | |
198 | b = (uint64_t) ticks_per_second / gcd; | |
199 | ||
200 | *ret = DIV_ROUND_UP((nsec_t) sum * (nsec_t) a, (nsec_t) b); | |
201 | return 0; | |
202 | } | |
203 | ||
c482724a | 204 | int procfs_memory_get(uint64_t *ret_total, uint64_t *ret_used) { |
a04fcf17 LP |
205 | uint64_t mem_total = UINT64_MAX, mem_free = UINT64_MAX; |
206 | _cleanup_fclose_ FILE *f = NULL; | |
207 | int r; | |
208 | ||
a04fcf17 LP |
209 | f = fopen("/proc/meminfo", "re"); |
210 | if (!f) | |
211 | return -errno; | |
212 | ||
213 | for (;;) { | |
214 | _cleanup_free_ char *line = NULL; | |
215 | uint64_t *v; | |
216 | char *p, *e; | |
217 | size_t n; | |
218 | ||
219 | r = read_line(f, LONG_LINE_MAX, &line); | |
220 | if (r < 0) | |
221 | return r; | |
222 | if (r == 0) | |
223 | return -EINVAL; /* EOF: Couldn't find one or both fields? */ | |
224 | ||
225 | p = first_word(line, "MemTotal:"); | |
226 | if (p) | |
227 | v = &mem_total; | |
228 | else { | |
229 | p = first_word(line, "MemFree:"); | |
230 | if (p) | |
231 | v = &mem_free; | |
232 | else | |
233 | continue; | |
234 | } | |
235 | ||
236 | /* Determine length of numeric value */ | |
237 | n = strspn(p, DIGITS); | |
238 | if (n == 0) | |
239 | return -EINVAL; | |
240 | e = p + n; | |
241 | ||
242 | /* Ensure the line ends in " kB" */ | |
243 | n = strspn(e, WHITESPACE); | |
244 | if (n == 0) | |
245 | return -EINVAL; | |
246 | if (!streq(e + n, "kB")) | |
247 | return -EINVAL; | |
248 | ||
249 | *e = 0; | |
250 | r = safe_atou64(p, v); | |
251 | if (r < 0) | |
252 | return r; | |
253 | if (*v == UINT64_MAX) | |
254 | return -EINVAL; | |
255 | ||
256 | if (mem_total != UINT64_MAX && mem_free != UINT64_MAX) | |
257 | break; | |
258 | } | |
259 | ||
260 | if (mem_free > mem_total) | |
261 | return -EINVAL; | |
262 | ||
c482724a ZJS |
263 | if (ret_total) |
264 | *ret_total = mem_total * 1024U; | |
265 | if (ret_used) | |
266 | *ret_used = (mem_total - mem_free) * 1024U; | |
a04fcf17 LP |
267 | return 0; |
268 | } |