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

Commit

Permalink
profile: Host debug daemon for opening in Speedscope
Browse files Browse the repository at this point in the history
By running `scripts/debugd.py` on the host machine with speedscope installed, one can now pass the `-r` flag to `profile` in order to send the output directly to the host via networking and open it in speedscope.
  • Loading branch information
byteduck committed Jul 20, 2024
1 parent d080b91 commit bd5f4d0
Show file tree
Hide file tree
Showing 3 changed files with 116 additions and 35 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,8 @@ The code for these can be found in [programs](programs/).
- There is only support for one redirection at a time right now.
- open (/bin/open): A utility to open files and applications from the command line.
- play (/bin/play): Plays audio files.
- profile (/bin/profile): Profiles a running application and outputs a [FlameGraph](https://github.com/brendangregg/FlameGraph) / [SpeedScope](https://speedscope.app) compatible file.
- profile (/bin/profile): Profiles a running application and outputs a [FlameGraph](https://github.com/brendangregg/FlameGraph) / [SpeedScope](https://speedscope.app) compatible file.
- You can run `scripts/debugd.py` on the host (with speedscope installed) and pass the `-r` parameter to profile to send the output directly to the host via networking and open it in speedscope.

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

Expand Down
108 changes: 74 additions & 34 deletions programs/coreutils/profile.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,19 @@
#include <sys/wait.h>
#include <libduck/FormatStream.h>
#include <libduck/Args.h>
#include <libduck/Socket.h>
#include <ctime>

constexpr int debugd_port = 59336;
constexpr const char* debugd_start = "DEBUGD\nPROFILE\n";

using namespace Debug;
using Duck::OutputStream;

int pid;
int interval = 10;
int duration = 5000;
bool remote = false;
std::string filename;

struct Thread {
Expand All @@ -23,6 +28,43 @@ struct Thread {
std::vector<std::vector<size_t>> stacks;
};

Duck::Result output_profile(
Duck::OutputStream& stream,
std::map<size_t, AddressInfo>& symbols,
std::vector<Duck::Ptr<Thread>>& threads) {
for (auto& thread : threads){
for (auto& stk : thread->stacks) {
stream << "thread " << thread->tid << ";";
for(size_t i = stk.size(); i > 0; i--) {
auto pos = stk[i - 1];
auto info = symbols.find(pos);
if (info == symbols.end()) {
auto info_res = thread->debugger.info_at(pos);
if(info_res.is_error())
symbols[pos] = {"???", pos, nullptr};
else
symbols[pos] = info_res.value();
info = symbols.find(pos);
}

auto& symbol = info->second;
if (!symbol.object)
stream % "?? @ {#x}" % symbol.symbol_offset;
else if(symbol.symbol_name == "__syscall_trap__")
stream << "<kernel>";
else
stream % "{} @ {}" % symbol.symbol_name % symbol.object->name;

if (i != 1)
stream << ";";
}
stream << " 1\n";
}
}

return Duck::Result::SUCCESS;
}

Duck::Result profile() {
auto proc = TRY(Sys::Process::get(pid));

Expand Down Expand Up @@ -58,44 +100,41 @@ Duck::Result profile() {
Duck::println("Done! Symbolicating and dumping...");
std::map<size_t, AddressInfo> symbols;

if (filename.empty())
filename = "profile-" + proc.name() + "-" + std::to_string(std::time(nullptr)) + ".txt";
auto out = TRY(Duck::File::open(filename, "w"));
Duck::FileOutputStream stream {out};

for (auto& thread : threads) {
for (auto& stk : thread->stacks) {
stream << "thread " << thread->tid << ";";
for(size_t i = stk.size(); i > 0; i--) {
auto pos = stk[i - 1];
auto info = symbols.find(pos);
if (info == symbols.end()) {
auto info_res = thread->debugger.info_at(pos);
if(info_res.is_error())
symbols[pos] = {"???", pos, nullptr};
else
symbols[pos] = info_res.value();
info = symbols.find(pos);
}

auto& symbol = info->second;
if (!symbol.object)
stream % "?? @ {#x}" % symbol.symbol_offset;
else if(symbol.symbol_name == "__syscall_trap__")
stream << "<kernel>";
else
stream % "{} @ {}" % symbol.symbol_name % symbol.object->name;

if (i != 1)
stream << ";";
}
stream << " 1\n";
if (remote) {
// Collect into StringOutputStream
Duck::StringOutputStream stream;
TRYRES(output_profile(stream, symbols, threads));

// Connect to socket
Duck::print("Connecting to debug daemon... ", debugd_port);
fflush(stdout);
auto sock = TRY(Duck::Socket::open(Duck::Socket::Inet, Duck::Socket::Stream, Duck::Socket::TCP));
TRYRES(sock.connect({10, 0, 2, 2}, debugd_port));

// Write to socket
Duck::print("Sending... ");
fflush(stdout);

TRYRES(sock.send(debugd_start, strlen(debugd_start)));
for (size_t i = 0; i < stream.string().length() + 1; i += 1024) {
auto len = std::min((size_t) 1024, stream.string().length() + 1 - i);
TRYRES(sock.send(stream.string().c_str() + i, len));
}

Duck::println("Sent!");
sock.close();
} else {
// Write to file
if (filename.empty())
filename = "profile-" + proc.name() + "-" + std::to_string(std::time(nullptr)) + ".txt";
auto out = TRY(Duck::File::open(filename, "w"));
Duck::FileOutputStream fs {out};
TRYRES(output_profile(fs, symbols, threads));
out.close();
Duck::println("Done! Saved to {}.", filename);
}

out.close();

Duck::println("Done! Saved to {}.", filename);
return Duck::Result::SUCCESS;
}

Expand All @@ -105,6 +144,7 @@ int main(int argc, char** argv) {
args.add_named(interval, "i", "interval", "The interval with which to sample, in ms. (Default: 10)");
args.add_named(duration, "d", "duration", "The duration to sample for, in ms. (Default: 5000)");
args.add_named(filename, "o", "output", "The output file.");
args.add_flag(remote, "r", "remote", "Sends the output to a remote debug server.");
args.parse(argc, argv);

auto res = profile();
Expand Down
40 changes: 40 additions & 0 deletions scripts/debugd.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import socketserver
import tempfile
import subprocess

DEBUGD_HEADER = "DEBUGD"
PROFILE_HEADER = f"{DEBUGD_HEADER}\nPROFILE\n"

class DebugDaemonHandler(socketserver.BaseRequestHandler):
def handle(self):
# Receive whole request into a string
outstr = ""
while True:
data = self.request.recv(1024)
outstr += str(data, encoding="utf-8")
if data[-1] == 0:
break

# Check validity
if not outstr.startswith(DEBUGD_HEADER):
print(f"Received invalid header from {self.client_address[0]}")
if not outstr.startswith(PROFILE_HEADER):
print(f"Received invalid request {outstr.splitlines()[1]} from {self.client_address[0]}")

# Write to tmp file and open speedscope
print(f"Received profile from {self.client_address[0]}")
out = tempfile.NamedTemporaryFile(delete=False)
out.write(outstr[len(PROFILE_HEADER):].encode("utf-8"))
out.close()
subprocess.run(["speedscope", out.name])


if __name__ == "__main__":
HOST, PORT = "localhost", 59336

with socketserver.TCPServer((HOST, PORT), DebugDaemonHandler) as server:
# Activate the server; this will keep running until you
# interrupt the program with Ctrl-C
print(f"Running duckOS debugd on {HOST}:{PORT}")
server.serve_forever()

0 comments on commit bd5f4d0

Please sign in to comment.