Skip to content

Commit 0ea6d55

Browse files
ekoopspoiana
authored andcommitted
perf(userspace/libscap): rewrite /proc/pid/fdinfo/fd parsing logic
The previous logic used 1 single `fopen()` to open the `/proc/<pid>/fdinfo/<fd>` file, and 1 `fgets()` for each single line in the file; then, each line was matched against a set of prefixes using `strncmp()` and, for each match, `strtoul()` was used to parse the numeric content. The new parsing logic introduces the following optimizations: - use `open()` and `read_exact()` to avoid `fopen()` and `fgets()` allocation, buffering and lock acquisition overheads - try to read the entire file content with a single `read()`, reducing the overhead of issuing ~3 `fgets()` (3 is the position of the last interesting row in the `/proc/<pid>/fdinfo/<fd>` file, which is the `mnt_id: ...` line for the current logic). The needed information are present in the first few lines (within 512 bytes), so no new `read()` are issued if the file content is bigger - do line prefix-matching using a switch lookup table, to avoid wasting cycles on unneeded comparisons Finally, replace `strncmp()` and `strtoul()` usage with calls to `MEMCMP_LITERAL()` and `str_parse_u64()`. Signed-off-by: Leonardo Di Giovanna <[email protected]>
1 parent ba1619a commit 0ea6d55

1 file changed

Lines changed: 66 additions & 33 deletions

File tree

userspace/libscap/linux/scap_fds.c

Lines changed: 66 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ limitations under the License.
5353
#include <linux/rtnetlink.h>
5454
// #include <linux/sock_diag.h>
5555
// #include <linux/unix_diag.h>
56+
#include <libscap/linux/str_helpers.h>
57+
#include <libscap/linux/read_helpers.h>
5658

5759
#define SOCKET_SCAN_BUFFER_SIZE 1024 * 1024
5860

@@ -336,49 +338,80 @@ uint32_t scap_linux_get_device_by_mount_id(struct scap_platform *platform,
336338
}
337339

338340
void scap_fd_flags_file(scap_fdinfo *fdi, const char *procdir) {
339-
char fd_dir_name[SCAP_MAX_PATH_SIZE];
340-
char line[SCAP_MAX_PATH_SIZE];
341-
FILE *finfo;
342-
343-
snprintf(fd_dir_name, SCAP_MAX_PATH_SIZE, "%sfdinfo/%" PRId64, procdir, fdi->fd);
344-
finfo = fopen(fd_dir_name, "r");
345-
if(finfo == NULL) {
341+
char filename[SCAP_MAX_PATH_SIZE];
342+
snprintf(filename, SCAP_MAX_PATH_SIZE, "%sfdinfo/%" PRId64, procdir, fdi->fd);
343+
const int fd = open(filename, O_RDONLY, 0);
344+
if(fd < 0) {
346345
return;
347346
}
347+
348348
fdi->info.regularinfo.mount_id = 0;
349349
fdi->info.regularinfo.dev = 0;
350350

351-
while(fgets(line, sizeof(line), finfo) != NULL) {
352-
// We are interested in the flags and the mnt_id.
353-
//
354-
// The format of the file is:
355-
// pos: XXXX
356-
// flags: YYYYYYYY
357-
// mnt_id: ZZZ
358-
359-
if(!strncmp(line, "flags:\t", sizeof("flags:\t") - 1)) {
360-
uint32_t open_flags;
361-
errno = 0;
362-
unsigned long flags = strtoul(line + sizeof("flags:\t") - 1, NULL, 8);
363-
364-
if(errno == ERANGE) {
365-
open_flags = PPM_O_NONE;
366-
} else {
367-
open_flags = open_flags_to_scap(flags);
368-
}
351+
// note: `flags` and `mnt_id` rows appears early in the file (first few lines), so reading only
352+
// the first 512 bytes is sufficient to locate it reliably.
353+
char buffer[512];
354+
const ssize_t read_bytes = read_exact(fd, buffer, sizeof(buffer) - 1);
355+
close(fd);
356+
if(read_bytes <= 0) {
357+
return;
358+
}
359+
buffer[read_bytes] = '\0';
369360

370-
fdi->info.regularinfo.open_flags = open_flags;
371-
} else if(!strncmp(line, "mnt_id:\t", sizeof("mnt_id:\t") - 1)) {
372-
errno = 0;
373-
unsigned long mount_id = strtoul(line + sizeof("mnt_id:\t") - 1, NULL, 10);
361+
const char *line_start = buffer;
362+
const char *const last_newline = memrchr(buffer, '\n', read_bytes);
363+
if(last_newline == NULL) {
364+
ASSERT(false);
365+
return;
366+
}
374367

375-
if(errno != ERANGE) {
376-
fdi->info.regularinfo.mount_id = mount_id;
368+
const int VALUES_TO_ACQUIRE = 2;
369+
int acquired_values = 0;
370+
371+
while(line_start < last_newline) {
372+
char *const line_end = memchr(line_start, '\n', last_newline - line_start + 1);
373+
if(!line_end) {
374+
// bug: if we enter the loop, the range [line_start, buff_valid_end] contains '\n', so
375+
// it's impossible to end up here.
376+
ASSERT(false);
377+
return;
378+
}
379+
380+
const size_t line_len = line_end - line_start;
381+
382+
// Replace '\n' with '\0' to make string-related API work.
383+
*line_end = '\0';
384+
switch(*line_start) {
385+
case 'f':
386+
if(MEMCMP_LITERAL(line_start, line_len, "flags:")) {
387+
uint64_t flags;
388+
if(str_parse_u64(line_start, sizeof("flags:") - 1, 8, &flags)) {
389+
fdi->info.regularinfo.open_flags = open_flags_to_scap(flags);
390+
} else {
391+
fdi->info.regularinfo.open_flags = PPM_O_NONE;
392+
}
393+
acquired_values++;
394+
}
395+
break;
396+
case 'm':
397+
if(MEMCMP_LITERAL(line_start, line_len, "mnt_id:")) {
398+
uint64_t mount_id;
399+
if(str_parse_u64(line_start, sizeof("mnt_id:") - 1, 10, &mount_id)) {
400+
fdi->info.regularinfo.mount_id = mount_id;
401+
}
402+
acquired_values++;
377403
}
404+
break;
405+
default:
406+
break;
378407
}
379-
}
380408

381-
fclose(finfo);
409+
if(acquired_values == VALUES_TO_ACQUIRE) {
410+
return;
411+
}
412+
413+
line_start = line_end + 1;
414+
}
382415
}
383416

384417
int32_t scap_fd_handle_regular_file(struct scap_proclist *proclist,

0 commit comments

Comments
 (0)