Skip to content
This repository was archived by the owner on Sep 11, 2024. It is now read-only.

Commit 5d99e72

Browse files
committed
New 'profile' coreutil for stacktrace sampling / profiling
1 parent 52ce614 commit 5d99e72

File tree

3 files changed

+121
-1
lines changed

3 files changed

+121
-1
lines changed

README.md

+3
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ The code for these can be found in [programs](programs/).
9292
- There is only support for one redirection at a time right now.
9393
- open (/bin/open): A utility to open files and applications from the command line.
9494
- play (/bin/play): Plays audio files.
95+
- profile (/bin/profile): Profiles a running application and outputs a [FlameGraph](https://github.com/brendangregg/FlameGraph) / [SpeedScope](https://speedscope.app) compatible file.
9596

9697
Programs that take arguments will provide you with the correct usage when you run them without arguments.
9798

@@ -111,6 +112,8 @@ Programs that take arguments will provide you with the correct usage when you ru
111112
- [libsound](/libraries/libsound): Provides a framework for audio applications and interfacing with the sound server, Quack.
112113
- [lib3d](/libraries/lib3d): Provides basic software 3D rendering functionality.
113114
- [libmatrix](/libraries/libmatrix): Provides matrix math utilities.
115+
- [libexec](/libraries/libexec): Provides ELF support.
116+
- [libdebug](/libraries/libdebug): Provides debugging functionality.
114117

115118
### Ports
116119

programs/coreutils/CMakeLists.txt

+3-1
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,6 @@ MAKE_COREUTIL(date)
3131
MAKE_COREUTIL(uname)
3232
TARGET_LINK_LIBRARIES(uname libduck)
3333
MAKE_COREUTIL(kill)
34-
TARGET_LINK_LIBRARIES(kill libduck)
34+
TARGET_LINK_LIBRARIES(kill libduck)
35+
MAKE_COREUTIL(profile)
36+
TARGET_LINK_LIBRARIES(profile libdebug)

programs/coreutils/profile.cpp

+115
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
/* SPDX-License-Identifier: GPL-3.0-or-later */
2+
/* Copyright © 2016-2023 Byteduck */
3+
4+
#include <libdebug/LiveDebugger.h>
5+
#include <csignal>
6+
#include <memory>
7+
#include <sys/wait.h>
8+
#include <libduck/FormatStream.h>
9+
#include <libduck/Args.h>
10+
#include <ctime>
11+
12+
using namespace Debug;
13+
using Duck::OutputStream;
14+
15+
int pid;
16+
int interval = 10;
17+
int duration = 5000;
18+
std::string filename;
19+
20+
struct Thread {
21+
tid_t tid;
22+
LiveDebugger debugger;
23+
std::vector<std::vector<size_t>> stacks;
24+
};
25+
26+
Duck::Result profile() {
27+
auto proc = TRY(Sys::Process::get(pid));
28+
29+
// First, attach to all the threads
30+
const int loops = duration / interval;
31+
std::vector<Duck::Ptr<Thread>> threads;
32+
for (auto tid : proc.threads()) {
33+
auto thread = std::make_shared<Thread>();
34+
thread->tid = tid;
35+
thread->stacks.reserve(loops);
36+
auto attach_res = thread->debugger.attach(pid, tid);
37+
if (attach_res.is_error())
38+
Duck::printerrln("Warning: Failed to attach to thread {}: {}", tid, attach_res);
39+
else
40+
threads.push_back(thread);
41+
}
42+
43+
if (threads.empty())
44+
return Duck::Result("Could not attach to any threads");
45+
46+
Duck::println("Sampling {} for ~{}ms...", proc.name(), duration);
47+
for (size_t i = 0; i < loops; i++) {
48+
waitpid(pid, nullptr, 0);
49+
for (auto& thread : threads)
50+
thread->stacks.push_back(TRY(thread->debugger.walk_stack_unsymbolicated()));
51+
kill(pid, SIGCONT);
52+
usleep(interval * 1000);
53+
kill(pid, SIGSTOP);
54+
}
55+
kill(pid, SIGCONT);
56+
57+
Duck::println("Done! Symbolicating...");
58+
std::map<size_t, AddressInfo> symbols;
59+
60+
if (filename.empty())
61+
filename = "profile-" + proc.name() + "-" + std::to_string(std::time(nullptr)) + ".txt";
62+
auto out = TRY(Duck::File::open(filename, "w"));
63+
Duck::FileOutputStream stream {out};
64+
65+
for (auto& thread : threads) {
66+
for (auto& stk : thread->stacks) {
67+
stream << "thread " << thread->tid << ";";
68+
for(size_t i = stk.size(); i > 0; i--) {
69+
auto pos = stk[i - 1];
70+
auto info = symbols.find(pos);
71+
if (info == symbols.end()) {
72+
auto info_res = thread->debugger.info_at(pos);
73+
if(info_res.is_error())
74+
symbols[pos] = {"???", pos, nullptr};
75+
else
76+
symbols[pos] = info_res.value();
77+
info = symbols.find(pos);
78+
}
79+
80+
auto& symbol = info->second;
81+
if (!symbol.object)
82+
stream % "?? @ {#x}" % symbol.symbol_offset;
83+
else if(symbol.symbol_name == "__syscall_trap__")
84+
stream << "<kernel>";
85+
else
86+
stream % "{} @ {}" % symbol.symbol_name % symbol.object->name;
87+
88+
if (i != 1)
89+
stream << ";";
90+
}
91+
stream << " 1\n";
92+
}
93+
}
94+
95+
out.close();
96+
97+
Duck::println("Done! Saved to {}.", filename);
98+
return Duck::Result::SUCCESS;
99+
}
100+
101+
int main(int argc, char** argv) {
102+
Duck::Args args;
103+
args.add_positional(pid, true, "pid", "The PID of the program to profile.");
104+
args.add_named(interval, "i", "interval", "The interval with which to sample, in ms. (Default: 10)");
105+
args.add_named(duration, "d", "duration", "The duration to sample for, in ms. (Default: 5000)");
106+
args.add_named(filename, "o", "output", "The output file.");
107+
args.parse(argc, argv);
108+
109+
auto res = profile();
110+
if (res.is_error()) {
111+
printf("Error: %s\n", res.message().c_str());
112+
return res.code();
113+
}
114+
return 0;
115+
}

0 commit comments

Comments
 (0)