Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,11 @@ target_sources(gpufl PRIVATE
include/gpufl/core/monitor_batch_manager.cpp
include/gpufl/core/monitor_record_builders.cpp
include/gpufl/core/monitor.cpp
include/gpufl/core/client_startup.cpp
include/gpufl/core/monitor_configuration.cpp
include/gpufl/core/gpufl.cpp
include/gpufl/core/session_bootstrap.cpp
include/gpufl/core/startup_configuration.cpp
include/gpufl/core/common.cpp
include/gpufl/core/stack_trace.cpp
include/gpufl/core/itanium_demangle.cpp
Expand Down
1 change: 1 addition & 0 deletions daemon/launcher/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ add_executable(gpufl_launcher
cli_trace_options.cpp
info_command.cpp
trace_command_common.cpp
trace_run_plan.cpp
deep_window_env.cpp
segmentation_env.cpp
${GPUFL_LAUNCHER_TRACE_IMPL}
Expand Down
95 changes: 9 additions & 86 deletions daemon/launcher/trace_command_common.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#include "trace_command_common.hpp"
#include "trace_run_plan.hpp"

#include <zlib.h>

Expand All @@ -11,8 +12,6 @@
#include <filesystem>
#include <fstream>
#include <future>
#include <iomanip>
#include <random>
#include <sstream>
#include <string>
#include <thread>
Expand Down Expand Up @@ -56,48 +55,6 @@ std::string firstEngineOfToken(const std::string& token) {
return plus == std::string::npos ? token : token.substr(0, plus);
}

std::string makeSessionId() {
static std::mt19937_64 rng{
static_cast<uint64_t>(
std::chrono::steady_clock::now().time_since_epoch().count())};
uint64_t v = rng();
std::ostringstream os;
os << std::hex << std::setw(8) << std::setfill('0') << (v & 0xffffffffu);
return os.str();
}

std::string makeAnalysisId() {
static std::mt19937_64 rng{
static_cast<uint64_t>(
std::chrono::steady_clock::now().time_since_epoch().count()) ^
0x9e3779b97f4a7c15ULL};
const uint64_t a = rng();
const uint64_t b = rng();
char buf[40];
std::snprintf(buf, sizeof(buf), "%08x-%04x-%04x-%04x-%012llx",
static_cast<unsigned>(a >> 32),
static_cast<unsigned>((a >> 16) & 0xffff),
static_cast<unsigned>(a & 0xffff),
static_cast<unsigned>((b >> 48) & 0xffff),
static_cast<unsigned long long>(b & 0xffffffffffffULL));
return buf;
}

// Filesystem-safe slug for a run-folder name: keep [A-Za-z0-9-_.], turn anything else into '-',
// trim leading/trailing dashes, cap length. Empty input falls back to "session".
std::string slugForPath(const std::string& in) {
auto safe = [](const char c) {
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||
(c >= '0' && c <= '9') || c == '-' || c == '_' || c == '.';
};
std::string out;
for (const char c : in) out.push_back(safe(c) ? c : '-');
while (!out.empty() && out.front() == '-') out.erase(out.begin());
while (!out.empty() && out.back() == '-') out.pop_back();
if (out.size() > 48) out.resize(48);
return out.empty() ? std::string("session") : out;
}

int64_t nowNs() {
return std::chrono::duration_cast<std::chrono::nanoseconds>(
std::chrono::system_clock::now().time_since_epoch()).count();
Expand Down Expand Up @@ -758,37 +715,13 @@ int runTraceCommon(const TraceArgs& args, const TracePlatform& platform) {
return 3;
}

const std::vector<std::string> plan = resolvePassPlan(args);
const bool multipass = plan.size() > 1;

const std::string analysis_id = multipass ? makeAnalysisId() : std::string();
const std::string run_id = segmented ? generateRunId() : std::string();
const std::string dir_tag =
multipass ? analysis_id : (segmented ? run_id : makeSessionId());

const std::string app_name = args.name.empty()
? platform.defaultAppName(args.command.front())
: args.name;

// Where this run's session folder(s) land. The default output dir is already
// per-run (defaultOutputDir(dir_tag)), so sessions sit flat inside it. An
// explicit --output is a dir the user reuses across runs:
// - multi-pass nests its passes under a readable "run-<name>-<analysis_id>"
// folder so the passes of one analysis stay grouped (a flat dir would lose
// which sessions belong to the same run);
// - single-pass is one session, so it lands flat as <output>/<session_id>/ -
// matching embedded gpufl and the upload agent's <parent>/<session_id>
// discovery, with no redundant per-session wrapper.
fs::path output_dir;
if (args.output_dir.empty()) {
output_dir = platform.defaultOutputDir(dir_tag);
} else if (multipass) {
const std::string run_folder =
"run-" + slugForPath(app_name) + "-" + dir_tag.substr(0, 8);
output_dir = fs::path(args.output_dir) / run_folder;
} else {
output_dir = fs::path(args.output_dir);
}
const TraceRunPlan trace_plan = createTraceRunPlan(args, platform);
const std::vector<std::string>& plan = trace_plan.passes;
const bool multipass = trace_plan.multipass;
const std::string& analysis_id = trace_plan.analysis_id;
const std::string& run_id = trace_plan.run_id;
const std::string& app_name = trace_plan.app_name;
const fs::path& output_dir = trace_plan.output_dir;

std::error_code ec;
fs::create_directories(output_dir, ec);
Expand Down Expand Up @@ -838,17 +771,7 @@ int runTraceCommon(const TraceArgs& args, const TracePlatform& platform) {
if (!applyDeepWindowEnv(args, platform)) return 2;
if (!applySegmentationEnv(args, run_id, platform)) return 2;

// A bounded window stops the target after warmup+window wall-clock;
// run_ms == 0 keeps the historical "run until the target exits" behavior.
RunOptions run_opts;
if (args.window_ms > 0) {
run_opts.run_ms = args.warmup_ms + args.window_ms;
}
if (args.window_timeout_ms > 0) {
run_opts.run_ms = run_opts.run_ms > 0
? std::min(run_opts.run_ms, args.window_timeout_ms)
: args.window_timeout_ms;
}
const RunOptions& run_opts = trace_plan.run_options;

AgentProcess agent;
if (args.upload) {
Expand Down
96 changes: 96 additions & 0 deletions daemon/launcher/trace_run_plan.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
#include "trace_run_plan.hpp"

#include <algorithm>
#include <chrono>
#include <cstdio>
#include <iomanip>
#include <random>
#include <sstream>
#include <string>

namespace gpufl::launcher {
namespace {

std::string makeSessionId() {
static std::mt19937_64 rng{
static_cast<uint64_t>(
std::chrono::steady_clock::now().time_since_epoch().count())};
const uint64_t value = rng();
std::ostringstream out;
out << std::hex << std::setw(8) << std::setfill('0')
<< (value & 0xffffffffu);
return out.str();
}

std::string makeAnalysisId() {
static std::mt19937_64 rng{
static_cast<uint64_t>(
std::chrono::steady_clock::now().time_since_epoch().count()) ^
0x9e3779b97f4a7c15ULL};
const uint64_t a = rng();
const uint64_t b = rng();
char buffer[40];
std::snprintf(buffer, sizeof(buffer), "%08x-%04x-%04x-%04x-%012llx",
static_cast<unsigned>(a >> 32),
static_cast<unsigned>((a >> 16) & 0xffff),
static_cast<unsigned>(a & 0xffff),
static_cast<unsigned>((b >> 48) & 0xffff),
static_cast<unsigned long long>(b & 0xffffffffffffULL));
return buffer;
}

// Filesystem-safe slug for a multi-pass run-folder name. Empty input falls
// back to "session" so planning never creates an invalid or invisible folder.
std::string slugForPath(const std::string& input) {
const auto safe = [](const char c) {
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||
(c >= '0' && c <= '9') || c == '-' || c == '_' || c == '.';
};
std::string output;
for (const char c : input) output.push_back(safe(c) ? c : '-');
while (!output.empty() && output.front() == '-') output.erase(output.begin());
while (!output.empty() && output.back() == '-') output.pop_back();
if (output.size() > 48) output.resize(48);
return output.empty() ? std::string("session") : output;
}

} // namespace

TraceRunPlan createTraceRunPlan(const TraceArgs& args,
const TracePlatform& platform) {
TraceRunPlan plan;
plan.passes = resolvePassPlan(args);
plan.multipass = plan.passes.size() > 1;
plan.segmented = segmentationRequested(args);
plan.analysis_id = plan.multipass ? makeAnalysisId() : std::string();
plan.run_id = plan.segmented ? generateRunId() : std::string();
plan.directory_tag = plan.multipass
? plan.analysis_id
: (plan.segmented ? plan.run_id : makeSessionId());
plan.app_name = args.name.empty()
? platform.defaultAppName(args.command.front())
: args.name;

if (args.output_dir.empty()) {
plan.output_dir = platform.defaultOutputDir(plan.directory_tag);
} else if (plan.multipass) {
const std::string run_folder =
"run-" + slugForPath(plan.app_name) + "-" +
plan.directory_tag.substr(0, 8);
plan.output_dir = fs::path(args.output_dir) / run_folder;
} else {
plan.output_dir = fs::path(args.output_dir);
}

if (args.window_ms > 0) {
plan.run_options.run_ms = args.warmup_ms + args.window_ms;
}
if (args.window_timeout_ms > 0) {
plan.run_options.run_ms = plan.run_options.run_ms > 0
? std::min(plan.run_options.run_ms, args.window_timeout_ms)
: args.window_timeout_ms;
}
return plan;
}

} // namespace gpufl::launcher
28 changes: 28 additions & 0 deletions daemon/launcher/trace_run_plan.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#pragma once

#include "trace_command_common.hpp"

#include <string>
#include <vector>

namespace gpufl::launcher {

// The pure, per-invocation decisions made before trace starts mutating the
// target environment or filesystem. output_dir is intentionally only a path
// here; runTraceCommon owns creating and canonicalising it.
struct TraceRunPlan {
std::vector<std::string> passes;
bool segmented = false;
bool multipass = false;
std::string analysis_id;
std::string run_id;
std::string directory_tag;
std::string app_name;
fs::path output_dir;
RunOptions run_options;
};

TraceRunPlan createTraceRunPlan(const TraceArgs& args,
const TracePlatform& platform);

} // namespace gpufl::launcher
Loading
Loading