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

Commit 9defc53

Browse files
committed
profile: Host debug daemon for opening in Speedscope
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.
1 parent d080b91 commit 9defc53

File tree

2 files changed

+114
-34
lines changed

2 files changed

+114
-34
lines changed

programs/coreutils/profile.cpp

Lines changed: 74 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,19 @@
77
#include <sys/wait.h>
88
#include <libduck/FormatStream.h>
99
#include <libduck/Args.h>
10+
#include <libduck/Socket.h>
1011
#include <ctime>
1112

13+
constexpr int debugd_port = 59336;
14+
constexpr const char* debugd_start = "DEBUGD\nPROFILE\n";
15+
1216
using namespace Debug;
1317
using Duck::OutputStream;
1418

1519
int pid;
1620
int interval = 10;
1721
int duration = 5000;
22+
bool remote = false;
1823
std::string filename;
1924

2025
struct Thread {
@@ -23,6 +28,43 @@ struct Thread {
2328
std::vector<std::vector<size_t>> stacks;
2429
};
2530

31+
Duck::Result output_profile(
32+
Duck::OutputStream& stream,
33+
std::map<size_t, AddressInfo>& symbols,
34+
std::vector<Duck::Ptr<Thread>>& threads) {
35+
for (auto& thread : threads){
36+
for (auto& stk : thread->stacks) {
37+
stream << "thread " << thread->tid << ";";
38+
for(size_t i = stk.size(); i > 0; i--) {
39+
auto pos = stk[i - 1];
40+
auto info = symbols.find(pos);
41+
if (info == symbols.end()) {
42+
auto info_res = thread->debugger.info_at(pos);
43+
if(info_res.is_error())
44+
symbols[pos] = {"???", pos, nullptr};
45+
else
46+
symbols[pos] = info_res.value();
47+
info = symbols.find(pos);
48+
}
49+
50+
auto& symbol = info->second;
51+
if (!symbol.object)
52+
stream % "?? @ {#x}" % symbol.symbol_offset;
53+
else if(symbol.symbol_name == "__syscall_trap__")
54+
stream << "<kernel>";
55+
else
56+
stream % "{} @ {}" % symbol.symbol_name % symbol.object->name;
57+
58+
if (i != 1)
59+
stream << ";";
60+
}
61+
stream << " 1\n";
62+
}
63+
}
64+
65+
return Duck::Result::SUCCESS;
66+
}
67+
2668
Duck::Result profile() {
2769
auto proc = TRY(Sys::Process::get(pid));
2870

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

61-
if (filename.empty())
62-
filename = "profile-" + proc.name() + "-" + std::to_string(std::time(nullptr)) + ".txt";
63-
auto out = TRY(Duck::File::open(filename, "w"));
64-
Duck::FileOutputStream stream {out};
65-
66-
for (auto& thread : threads) {
67-
for (auto& stk : thread->stacks) {
68-
stream << "thread " << thread->tid << ";";
69-
for(size_t i = stk.size(); i > 0; i--) {
70-
auto pos = stk[i - 1];
71-
auto info = symbols.find(pos);
72-
if (info == symbols.end()) {
73-
auto info_res = thread->debugger.info_at(pos);
74-
if(info_res.is_error())
75-
symbols[pos] = {"???", pos, nullptr};
76-
else
77-
symbols[pos] = info_res.value();
78-
info = symbols.find(pos);
79-
}
80-
81-
auto& symbol = info->second;
82-
if (!symbol.object)
83-
stream % "?? @ {#x}" % symbol.symbol_offset;
84-
else if(symbol.symbol_name == "__syscall_trap__")
85-
stream << "<kernel>";
86-
else
87-
stream % "{} @ {}" % symbol.symbol_name % symbol.object->name;
88-
89-
if (i != 1)
90-
stream << ";";
91-
}
92-
stream << " 1\n";
103+
if (remote) {
104+
// Collect into StringOutputStream
105+
Duck::StringOutputStream stream;
106+
TRYRES(output_profile(stream, symbols, threads));
107+
108+
// Connect to socket
109+
Duck::print("Connecting to debug daemon... ", debugd_port);
110+
fflush(stdout);
111+
auto sock = TRY(Duck::Socket::open(Duck::Socket::Inet, Duck::Socket::Stream, Duck::Socket::TCP));
112+
TRYRES(sock.connect({10, 0, 2, 2}, debugd_port));
113+
114+
// Write to socket
115+
Duck::print("Sending... ");
116+
fflush(stdout);
117+
118+
TRYRES(sock.send(debugd_start, strlen(debugd_start)));
119+
for (size_t i = 0; i < stream.string().length() + 1; i += 1024) {
120+
auto len = std::min((size_t) 1024, stream.string().length() + 1 - i);
121+
TRYRES(sock.send(stream.string().c_str() + i, len));
93122
}
123+
124+
Duck::println("Sent!");
125+
sock.close();
126+
} else {
127+
// Write to file
128+
if (filename.empty())
129+
filename = "profile-" + proc.name() + "-" + std::to_string(std::time(nullptr)) + ".txt";
130+
auto out = TRY(Duck::File::open(filename, "w"));
131+
Duck::FileOutputStream fs {out};
132+
TRYRES(output_profile(fs, symbols, threads));
133+
out.close();
134+
Duck::println("Done! Saved to {}.", filename);
94135
}
95136

96-
out.close();
97137

98-
Duck::println("Done! Saved to {}.", filename);
99138
return Duck::Result::SUCCESS;
100139
}
101140

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

110150
auto res = profile();

scripts/debugd.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import socketserver
2+
import tempfile
3+
import subprocess
4+
5+
DEBUGD_HEADER = "DEBUGD"
6+
PROFILE_HEADER = f"{DEBUGD_HEADER}\nPROFILE\n"
7+
8+
class DebugDaemonHandler(socketserver.BaseRequestHandler):
9+
def handle(self):
10+
# Receive whole request into a string
11+
outstr = ""
12+
while True:
13+
data = self.request.recv(1024)
14+
outstr += str(data, encoding="utf-8")
15+
if data[-1] == 0:
16+
break
17+
18+
# Check validity
19+
if not outstr.startswith(DEBUGD_HEADER):
20+
print(f"Received invalid header from {self.client_address[0]}")
21+
if not outstr.startswith(PROFILE_HEADER):
22+
print(f"Received invalid request {outstr.splitlines()[1]} from {self.client_address[0]}")
23+
24+
# Write to tmp file and open speedscope
25+
print(f"Received profile from {self.client_address[0]}")
26+
out = tempfile.NamedTemporaryFile(delete=False)
27+
out.write(outstr[len(PROFILE_HEADER):].encode("utf-8"))
28+
out.close()
29+
subprocess.run(["speedscope", out.name])
30+
31+
32+
if __name__ == "__main__":
33+
HOST, PORT = "localhost", 59336
34+
35+
with socketserver.TCPServer((HOST, PORT), DebugDaemonHandler) as server:
36+
# Activate the server; this will keep running until you
37+
# interrupt the program with Ctrl-C
38+
print(f"Running duckOS debugd on {HOST}:{PORT}")
39+
server.serve_forever()
40+

0 commit comments

Comments
 (0)