summaryrefslogtreecommitdiff
path: root/Userland/Utilities/profile.cpp
blob: 8be7b5d362715bae00915838fed91b35c8129095 (plain)
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
/*
 * Copyright (c) 2018-2021, Andreas Kling <kling@serenityos.org>
 *
 * SPDX-License-Identifier: BSD-2-Clause
 */

#include <LibCore/ArgsParser.h>
#include <LibCore/System.h>
#include <LibMain/Main.h>
#include <serenity.h>
#include <stdio.h>
#include <stdlib.h>

ErrorOr<int> serenity_main(Main::Arguments arguments)
{
    Core::ArgsParser args_parser;

    StringView pid_argument {};
    Vector<StringView> command;
    bool wait = false;
    bool free = false;
    bool enable = false;
    bool disable = false;
    bool all_processes = false;
    u64 event_mask = PERF_EVENT_MMAP | PERF_EVENT_MUNMAP | PERF_EVENT_PROCESS_CREATE
        | PERF_EVENT_PROCESS_EXEC | PERF_EVENT_PROCESS_EXIT | PERF_EVENT_THREAD_CREATE | PERF_EVENT_THREAD_EXIT
        | PERF_EVENT_SIGNPOST;
    bool seen_event_type_arg = false;

    args_parser.add_option(pid_argument, "Target PID", nullptr, 'p', "PID");
    args_parser.add_option(all_processes, "Profile all processes (super-user only), result at /sys/kernel/profile", nullptr, 'a');
    args_parser.add_option(enable, "Enable", nullptr, 'e');
    args_parser.add_option(disable, "Disable", nullptr, 'd');
    args_parser.add_option(free, "Free the profiling buffer for the associated process(es).", nullptr, 'f');
    args_parser.add_option(wait, "Enable profiling and wait for user input to disable.", nullptr, 'w');
    args_parser.add_option(Core::ArgsParser::Option {
        Core::ArgsParser::OptionArgumentMode::Required,
        "Enable tracking specific event type", nullptr, 't', "event_type",
        [&](DeprecatedString event_type) {
            seen_event_type_arg = true;
            if (event_type == "sample")
                event_mask |= PERF_EVENT_SAMPLE;
            else if (event_type == "context_switch")
                event_mask |= PERF_EVENT_CONTEXT_SWITCH;
            else if (event_type == "kmalloc")
                event_mask |= PERF_EVENT_KMALLOC;
            else if (event_type == "kfree")
                event_mask |= PERF_EVENT_KFREE;
            else if (event_type == "page_fault")
                event_mask |= PERF_EVENT_PAGE_FAULT;
            else if (event_type == "syscall")
                event_mask |= PERF_EVENT_SYSCALL;
            else if (event_type == "read")
                event_mask |= PERF_EVENT_READ;
            else {
                warnln("Unknown event type '{}' specified.", event_type);
                exit(1);
            }
            return true;
        } });
    args_parser.add_positional_argument(command, "Command to profile", "command", Core::ArgsParser::Required::No);
    args_parser.set_stop_on_first_non_option(true);

    auto print_types = [] {
        outln();
        outln("Event type can be one of: sample, context_switch, page_fault, syscall, read, kmalloc and kfree.");
    };

    if (!args_parser.parse(arguments, Core::ArgsParser::FailureBehavior::PrintUsage)) {
        print_types();
        exit(0);
    }

    if (pid_argument.is_empty() && command.is_empty() && !all_processes) {
        args_parser.print_usage(stdout, arguments.argv[0]);
        print_types();
        return 0;
    }

    if (!seen_event_type_arg)
        event_mask |= PERF_EVENT_SAMPLE;

    if (!pid_argument.is_empty() || all_processes) {
        if (!(enable ^ disable ^ wait ^ free)) {
            warnln("-p <PID> requires -e xor -d xor -w xor -f.");
            return 1;
        }

        // FIXME: Handle error case.
        pid_t pid = all_processes ? -1 : pid_argument.to_int().release_value();

        if (wait || enable) {
            TRY(Core::System::profiling_enable(pid, event_mask));

            if (!wait)
                return 0;
        }

        if (wait) {
            outln("Profiling enabled, waiting for user input to disable...");
            (void)getchar();
        }

        if (wait || disable)
            TRY(Core::System::profiling_disable(pid));

        if (free)
            TRY(Core::System::profiling_free_buffer(pid));

        return 0;
    }

    dbgln("Enabling profiling for PID {}", getpid());
    TRY(Core::System::profiling_enable(getpid(), event_mask));
    TRY(Core::System::exec(command[0], command, Core::System::SearchInPath::Yes));

    return 0;
}