mirror of
https://github.com/SerenityOS/serenity.git
synced 2025-01-24 02:12:09 -05:00
eb798d5538
This turns the perfcore format into more a log than it was before, which lets us properly log process, thread and region creation/destruction. This also makes it unnecessary to dump the process' regions every time it is scheduled like we did before. Incidentally this also fixes 'profile -c' because we previously ended up incorrectly dumping the parent's region map into the profile data. Log-based mmap support enables profiling shared libraries which are loaded at runtime, e.g. via dlopen(). This enables profiling both the parent and child process for programs which use execve(). Previously we'd discard the profiling data for the old process. The Profiler tool has been updated to not treat thread IDs as process IDs anymore. This enables support for processes with more than one thread. Also, there's a new widget to filter which process should be displayed.
227 lines
8 KiB
C++
227 lines
8 KiB
C++
/*
|
|
* Copyright (c) 2018-2021, Andreas Kling <kling@serenityos.org>
|
|
*
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*/
|
|
|
|
#include "IndividualSampleModel.h"
|
|
#include "ProcessPickerWidget.h"
|
|
#include "Profile.h"
|
|
#include "ProfileTimelineWidget.h"
|
|
#include <LibCore/ArgsParser.h>
|
|
#include <LibCore/ElapsedTimer.h>
|
|
#include <LibCore/EventLoop.h>
|
|
#include <LibCore/ProcessStatisticsReader.h>
|
|
#include <LibCore/Timer.h>
|
|
#include <LibDesktop/Launcher.h>
|
|
#include <LibGUI/Action.h>
|
|
#include <LibGUI/Application.h>
|
|
#include <LibGUI/BoxLayout.h>
|
|
#include <LibGUI/Button.h>
|
|
#include <LibGUI/Label.h>
|
|
#include <LibGUI/Menu.h>
|
|
#include <LibGUI/Menubar.h>
|
|
#include <LibGUI/MessageBox.h>
|
|
#include <LibGUI/Model.h>
|
|
#include <LibGUI/ProcessChooser.h>
|
|
#include <LibGUI/Splitter.h>
|
|
#include <LibGUI/TabWidget.h>
|
|
#include <LibGUI/TableView.h>
|
|
#include <LibGUI/TreeView.h>
|
|
#include <LibGUI/Window.h>
|
|
#include <serenity.h>
|
|
#include <string.h>
|
|
|
|
static bool generate_profile(pid_t& pid);
|
|
|
|
int main(int argc, char** argv)
|
|
{
|
|
int pid = 0;
|
|
const char* perfcore_file_arg = nullptr;
|
|
Core::ArgsParser args_parser;
|
|
args_parser.add_option(pid, "PID to profile", "pid", 'p', "PID");
|
|
args_parser.add_positional_argument(perfcore_file_arg, "Path of perfcore file", "perfcore-file", Core::ArgsParser::Required::No);
|
|
args_parser.parse(argc, argv);
|
|
|
|
if (pid && perfcore_file_arg) {
|
|
warnln("-p/--pid option and perfcore-file argument must not be used together!");
|
|
return 1;
|
|
}
|
|
|
|
auto app = GUI::Application::construct(argc, argv);
|
|
auto app_icon = GUI::Icon::default_icon("app-profiler");
|
|
|
|
String perfcore_file;
|
|
if (!perfcore_file_arg) {
|
|
if (!generate_profile(pid))
|
|
return 0;
|
|
perfcore_file = String::formatted("/proc/{}/perf_events", pid);
|
|
} else {
|
|
perfcore_file = perfcore_file_arg;
|
|
}
|
|
|
|
auto profile_or_error = Profile::load_from_perfcore_file(perfcore_file);
|
|
if (profile_or_error.is_error()) {
|
|
GUI::MessageBox::show(nullptr, profile_or_error.error(), "Profiler", GUI::MessageBox::Type::Error);
|
|
return 0;
|
|
}
|
|
|
|
auto& profile = profile_or_error.value();
|
|
|
|
auto window = GUI::Window::construct();
|
|
|
|
if (!Desktop::Launcher::add_allowed_handler_with_only_specific_urls(
|
|
"/bin/Help",
|
|
{ URL::create_with_file_protocol("/usr/share/man/man1/Profiler.md") })
|
|
|| !Desktop::Launcher::seal_allowlist()) {
|
|
warnln("Failed to set up allowed launch URLs");
|
|
return 1;
|
|
}
|
|
|
|
window->set_title("Profiler");
|
|
window->set_icon(app_icon.bitmap_for_size(16));
|
|
window->resize(800, 600);
|
|
|
|
auto& main_widget = window->set_main_widget<GUI::Widget>();
|
|
main_widget.set_fill_with_background_color(true);
|
|
main_widget.set_layout<GUI::VerticalBoxLayout>();
|
|
|
|
main_widget.add<ProfileTimelineWidget>(*profile);
|
|
main_widget.add<ProcessPickerWidget>(*profile);
|
|
|
|
auto& tab_widget = main_widget.add<GUI::TabWidget>();
|
|
|
|
auto& tree_tab = tab_widget.add_tab<GUI::Widget>("Call Tree");
|
|
tree_tab.set_layout<GUI::VerticalBoxLayout>();
|
|
tree_tab.layout()->set_margins({ 4, 4, 4, 4 });
|
|
auto& bottom_splitter = tree_tab.add<GUI::VerticalSplitter>();
|
|
|
|
auto& tree_view = bottom_splitter.add<GUI::TreeView>();
|
|
tree_view.set_should_fill_selected_rows(true);
|
|
tree_view.set_column_headers_visible(true);
|
|
tree_view.set_model(profile->model());
|
|
|
|
auto& disassembly_view = bottom_splitter.add<GUI::TableView>();
|
|
|
|
tree_view.on_selection = [&](auto& index) {
|
|
profile->set_disassembly_index(index);
|
|
disassembly_view.set_model(profile->disassembly_model());
|
|
};
|
|
|
|
auto& samples_tab = tab_widget.add_tab<GUI::Widget>("Samples");
|
|
samples_tab.set_layout<GUI::VerticalBoxLayout>();
|
|
samples_tab.layout()->set_margins({ 4, 4, 4, 4 });
|
|
|
|
auto& samples_splitter = samples_tab.add<GUI::HorizontalSplitter>();
|
|
auto& samples_table_view = samples_splitter.add<GUI::TableView>();
|
|
samples_table_view.set_model(profile->samples_model());
|
|
|
|
auto& individual_sample_view = samples_splitter.add<GUI::TableView>();
|
|
samples_table_view.on_selection = [&](const GUI::ModelIndex& index) {
|
|
auto model = IndividualSampleModel::create(*profile, index.data(GUI::ModelRole::Custom).to_integer<size_t>());
|
|
individual_sample_view.set_model(move(model));
|
|
};
|
|
|
|
auto menubar = GUI::Menubar::construct();
|
|
auto& app_menu = menubar->add_menu("&File");
|
|
app_menu.add_action(GUI::CommonActions::make_quit_action([&](auto&) { app->quit(); }));
|
|
|
|
auto& view_menu = menubar->add_menu("&View");
|
|
|
|
auto invert_action = GUI::Action::create_checkable("&Invert Tree", { Mod_Ctrl, Key_I }, [&](auto& action) {
|
|
profile->set_inverted(action.is_checked());
|
|
});
|
|
invert_action->set_checked(false);
|
|
view_menu.add_action(invert_action);
|
|
|
|
auto top_functions_action = GUI::Action::create_checkable("&Top Functions", { Mod_Ctrl, Key_T }, [&](auto& action) {
|
|
profile->set_show_top_functions(action.is_checked());
|
|
});
|
|
top_functions_action->set_checked(false);
|
|
view_menu.add_action(top_functions_action);
|
|
|
|
auto percent_action = GUI::Action::create_checkable("Show &Percentages", { Mod_Ctrl, Key_P }, [&](auto& action) {
|
|
profile->set_show_percentages(action.is_checked());
|
|
tree_view.update();
|
|
disassembly_view.update();
|
|
});
|
|
percent_action->set_checked(false);
|
|
view_menu.add_action(percent_action);
|
|
|
|
auto& help_menu = menubar->add_menu("&Help");
|
|
help_menu.add_action(GUI::CommonActions::make_help_action([](auto&) {
|
|
Desktop::Launcher::open(URL::create_with_file_protocol("/usr/share/man/man1/Profiler.md"), "/bin/Help");
|
|
}));
|
|
help_menu.add_action(GUI::CommonActions::make_about_action("Profiler", app_icon, window));
|
|
|
|
window->set_menubar(move(menubar));
|
|
window->show();
|
|
return app->exec();
|
|
}
|
|
|
|
static bool prompt_to_stop_profiling(pid_t pid, const String& process_name)
|
|
{
|
|
auto window = GUI::Window::construct();
|
|
window->set_title(String::formatted("Profiling {}({})", process_name, pid));
|
|
window->resize(240, 100);
|
|
window->set_icon(Gfx::Bitmap::load_from_file("/res/icons/16x16/app-profiler.png"));
|
|
window->center_on_screen();
|
|
|
|
auto& widget = window->set_main_widget<GUI::Widget>();
|
|
widget.set_fill_with_background_color(true);
|
|
auto& layout = widget.set_layout<GUI::VerticalBoxLayout>();
|
|
layout.set_margins(GUI::Margins(0, 0, 0, 16));
|
|
|
|
auto& timer_label = widget.add<GUI::Label>("...");
|
|
Core::ElapsedTimer clock;
|
|
clock.start();
|
|
auto update_timer = Core::Timer::construct(100, [&] {
|
|
timer_label.set_text(String::formatted("{:.1} seconds", clock.elapsed() / 1000.0f));
|
|
});
|
|
|
|
auto& stop_button = widget.add<GUI::Button>("Stop");
|
|
stop_button.set_fixed_size(140, 22);
|
|
stop_button.on_click = [&](auto) {
|
|
GUI::Application::the()->quit();
|
|
};
|
|
|
|
window->show();
|
|
return GUI::Application::the()->exec() == 0;
|
|
}
|
|
|
|
bool generate_profile(pid_t& pid)
|
|
{
|
|
if (!pid) {
|
|
auto process_chooser = GUI::ProcessChooser::construct("Profiler", "Profile", Gfx::Bitmap::load_from_file("/res/icons/16x16/app-profiler.png"));
|
|
if (process_chooser->exec() == GUI::Dialog::ExecCancel)
|
|
return false;
|
|
pid = process_chooser->pid();
|
|
}
|
|
|
|
String process_name;
|
|
|
|
auto all_processes = Core::ProcessStatisticsReader::get_all();
|
|
if (all_processes.has_value()) {
|
|
if (auto it = all_processes.value().find(pid); it != all_processes.value().end())
|
|
process_name = it->value.name;
|
|
else
|
|
process_name = "(unknown)";
|
|
} else {
|
|
process_name = "(unknown)";
|
|
}
|
|
|
|
if (profiling_enable(pid) < 0) {
|
|
int saved_errno = errno;
|
|
GUI::MessageBox::show(nullptr, String::formatted("Unable to profile process {}({}): {}", process_name, pid, strerror(saved_errno)), "Profiler", GUI::MessageBox::Type::Error);
|
|
return false;
|
|
}
|
|
|
|
if (!prompt_to_stop_profiling(pid, process_name))
|
|
return false;
|
|
|
|
if (profiling_disable(pid) < 0) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|