Include Linux runtime binaries in Shardok profiles (#8760)

This commit is contained in:
2026-07-23 13:16:04 -07:00
committed by GitHub
parent 132cf36673
commit 2641be9c91
4 changed files with 99 additions and 18 deletions
@@ -10,12 +10,14 @@
#include <algorithm>
#include <array>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <iterator>
#include <memory>
#include <ranges>
#include <string>
#include <string_view>
#include <thread>
#include <utility>
@@ -31,6 +33,8 @@
namespace shardok {
using grpc::Status;
using grpc::StatusCode;
using net::eagle0::common::CPU_PROFILE_ARTIFACT_TYPE_C_LIBRARY;
using net::eagle0::common::CPU_PROFILE_ARTIFACT_TYPE_DYNAMIC_LINKER;
using net::eagle0::common::CPU_PROFILE_ARTIFACT_TYPE_EXECUTABLE;
using net::eagle0::common::CPU_PROFILE_ARTIFACT_TYPE_PROFILE;
using net::eagle0::common::GameSetupInfo;
@@ -40,6 +44,28 @@ using net::eagle0::common::VictoryCondition;
using net::eagle0::shardok::api::PlacementCommand;
using net::eagle0::shardok::common::EndGameCondition;
namespace {
auto FindMappedRuntimeFile(
const std::string_view exactFilename,
const std::string_view filenamePrefix = {}) -> std::string {
std::ifstream maps("/proc/self/maps");
for (std::string line; std::getline(maps, line);) {
const auto pathStart = line.find('/');
if (pathStart == std::string::npos) { continue; }
auto path = line.substr(pathStart);
const auto filename = std::filesystem::path(path).filename().string();
if (filename == exactFilename ||
(!filenamePrefix.empty() && filename.starts_with(filenamePrefix))) {
return path;
}
}
return {};
}
} // namespace
class NewGameException : public std::exception {
private:
Status status;
@@ -168,9 +194,16 @@ auto EagleInterfaceImpl::DownloadCpuProfile(
path = cpuProfiler_.ProfilePath();
break;
case CPU_PROFILE_ARTIFACT_TYPE_EXECUTABLE: path = cpuProfiler_.ExecutablePath(); break;
case CPU_PROFILE_ARTIFACT_TYPE_C_LIBRARY: path = FindMappedRuntimeFile("libc.so.6"); break;
case CPU_PROFILE_ARTIFACT_TYPE_DYNAMIC_LINKER:
path = FindMappedRuntimeFile("", "ld-linux-");
break;
default: return Status(StatusCode::INVALID_ARGUMENT, "Unknown CPU profile artifact type");
}
if (path.empty()) {
return Status(StatusCode::NOT_FOUND, "CPU profile runtime artifact is not mapped");
}
std::ifstream input(path, std::ios::binary);
if (!input.is_open()) {
return Status(StatusCode::NOT_FOUND, "Unable to open CPU profile artifact");
@@ -37,6 +37,28 @@ type shardokProfileStatusResponse struct {
Error string `json:"error,omitempty"`
}
var shardokProfileArtifacts = []struct {
filename string
artifactType shardokpb.CpuProfileArtifactType
}{
{
filename: "shardok.prof",
artifactType: shardokpb.CpuProfileArtifactType_CPU_PROFILE_ARTIFACT_TYPE_PROFILE,
},
{
filename: "shardok-server",
artifactType: shardokpb.CpuProfileArtifactType_CPU_PROFILE_ARTIFACT_TYPE_EXECUTABLE,
},
{
filename: "libc.so.6",
artifactType: shardokpb.CpuProfileArtifactType_CPU_PROFILE_ARTIFACT_TYPE_C_LIBRARY,
},
{
filename: "ld-linux-aarch64.so.1",
artifactType: shardokpb.CpuProfileArtifactType_CPU_PROFILE_ARTIFACT_TYPE_DYNAMIC_LINKER,
},
}
func initializeShardokProfileClient() (*grpc.ClientConn, error) {
if *shardokProfileAddr == "" {
return nil, nil
@@ -231,23 +253,16 @@ func handleShardokProfileDownload(w http.ResponseWriter, r *http.Request) {
}
}()
if err := copyShardokProfileArtifact(
ctx,
zipWriter,
"shardok.prof",
shardokpb.CpuProfileArtifactType_CPU_PROFILE_ARTIFACT_TYPE_PROFILE,
); err != nil {
log.Printf("Failed to stream Shardok CPU profile: %v", err)
return
}
if err := copyShardokProfileArtifact(
ctx,
zipWriter,
"shardok-server",
shardokpb.CpuProfileArtifactType_CPU_PROFILE_ARTIFACT_TYPE_EXECUTABLE,
); err != nil {
log.Printf("Failed to stream Shardok executable: %v", err)
return
for _, artifact := range shardokProfileArtifacts {
if err := copyShardokProfileArtifact(
ctx,
zipWriter,
artifact.filename,
artifact.artifactType,
); err != nil {
log.Printf("Failed to stream Shardok CPU profile artifact %s: %v", artifact.filename, err)
return
}
}
readme, err := zipWriter.Create("README.txt")
@@ -255,7 +270,10 @@ func handleShardokProfileDownload(w http.ResponseWriter, r *http.Request) {
log.Printf("Failed to add Shardok profile README: %v", err)
return
}
if _, err := fmt.Fprintln(readme, "Analyze with: go tool pprof shardok-server shardok.prof"); err != nil {
if _, err := fmt.Fprintln(
readme,
"Analyze with: PPROF_BINARY_PATH=. go tool pprof shardok-server shardok.prof",
); err != nil {
log.Printf("Failed to write Shardok profile README: %v", err)
}
}
@@ -32,6 +32,34 @@ func TestShardokProfileStatusResponse(t *testing.T) {
}
}
func TestShardokProfileArtifactsIncludeRuntimeBinaries(t *testing.T) {
expected := map[string]shardokpb.CpuProfileArtifactType{
"shardok.prof": shardokpb.CpuProfileArtifactType_CPU_PROFILE_ARTIFACT_TYPE_PROFILE,
"shardok-server": shardokpb.CpuProfileArtifactType_CPU_PROFILE_ARTIFACT_TYPE_EXECUTABLE,
"libc.so.6": shardokpb.CpuProfileArtifactType_CPU_PROFILE_ARTIFACT_TYPE_C_LIBRARY,
"ld-linux-aarch64.so.1": shardokpb.CpuProfileArtifactType_CPU_PROFILE_ARTIFACT_TYPE_DYNAMIC_LINKER,
}
for _, artifact := range shardokProfileArtifacts {
expectedType, ok := expected[artifact.filename]
if !ok {
t.Fatalf("unexpected artifact %q", artifact.filename)
}
if artifact.artifactType != expectedType {
t.Fatalf(
"artifact %q has type %v, want %v",
artifact.filename,
artifact.artifactType,
expectedType,
)
}
delete(expected, artifact.filename)
}
if len(expected) != 0 {
t.Fatalf("missing artifacts: %v", expected)
}
}
func TestShardokProfileStatusDisabled(t *testing.T) {
previousClient := shardokProfileClient
shardokProfileClient = nil
@@ -58,6 +58,8 @@ enum CpuProfileArtifactType {
CPU_PROFILE_ARTIFACT_TYPE_UNSPECIFIED = 0;
CPU_PROFILE_ARTIFACT_TYPE_PROFILE = 1;
CPU_PROFILE_ARTIFACT_TYPE_EXECUTABLE = 2;
CPU_PROFILE_ARTIFACT_TYPE_C_LIBRARY = 3;
CPU_PROFILE_ARTIFACT_TYPE_DYNAMIC_LINKER = 4;
}
message CpuProfileDownloadRequest {