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
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,15 @@ types are additive.
`--agent-drain-ms`, `--agent-cursor` and `--all-sessions` control it, and the
launcher sends upload-complete only after a clean agent drain. `--upload` is
scoped to a per-run folder rather than the shared `--output` directory.
- **The launcher no longer picks the agent's channels.** `gpufl trace --upload`
and `gpufl upload` used to pass `device,scope,system,sass` to the agent they
start. They now pass no list, so the agent uploads every channel it finds in
the session directory, and `gpufl trace --log-types` only narrows that. A
`GPUFL_LOG_TYPES` inherited from the shell is removed from the agent's
environment rather than passed on. `gpufl monitor` still passes `system`.
Uploads do not change: a native session holds only those four channels, an
older agent with no list uses its own default (the same four), and an older
launcher's list acts as a filter on a newer agent.
- **Linux injection lifecycle (`gpufl trace`).**
- **Warmup.** `--warmup` now sets `GPUFL_INJECT_WARMUP_MS` instead of
`GPUFL_INJECT_INIT_DELAY_MS`. Nothing waits for a warmup, so a warmup run
Expand Down
2 changes: 1 addition & 1 deletion daemon/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ The named volume persists the agent's read cursor so it resumes from where it le
|---|---|---|
| `GPUFL_SOURCE_FOLDER` | same as `GPUFL_MONITOR_LOG_DIR` | Directory the agent watches for log files |
| `GPUFL_SOURCE_PREFIX` | `session` | Filename prefix of the log files (e.g. `session.device.log`) |
| `GPUFL_LOG_TYPES` | `device,scope,system` | Comma-separated log channels to tail |
| `GPUFL_LOG_TYPES` | unset: every channel the agent finds (agents before gpufl-agent#31 default to `device,scope,system,sass`) | Comma-separated log channels to tail; only narrows the set |
| `GPUFL_CURSOR_FILE` | `/var/gpufl/monitor/cursor.json` | Path to the cursor file for resume-on-restart |

### Agent - HTTP publisher
Expand Down
19 changes: 14 additions & 5 deletions daemon/launcher/agent_launcher.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,16 @@ bool setEnv(const char* name, const std::string& value) {
#endif
}

// Removing a variable that is not set succeeds: that is the state asked for.
bool unsetEnv(const char* name) {
#ifdef _WIN32
return SetEnvironmentVariableA(name, nullptr) != 0 ||
GetLastError() == ERROR_ENVVAR_NOT_FOUND;
#else
return ::unsetenv(name) == 0;
#endif
}

void appendQuotedArg(const std::string& arg, std::string& cmd) {
if (!arg.empty() && arg.find_first_of(" \t\n\v\"") == std::string::npos) {
cmd += arg;
Expand Down Expand Up @@ -258,10 +268,6 @@ bool configureAgentEnvironment(const AgentOptions& opts, std::string& error) {
error = "agent source folder is empty";
return false;
}
if (opts.log_types.empty()) {
error = "agent log types are empty";
return false;
}
if (opts.cursor_file.empty()) {
error = "agent cursor file is empty";
return false;
Expand All @@ -276,7 +282,10 @@ bool configureAgentEnvironment(const AgentOptions& opts, std::string& error) {
}
if (!setEnv("GPUFL_SOURCE_FOLDER", opts.source_folders) ||
!setEnv("GPUFL_SOURCE_FOLDERS", opts.source_folders) ||
!setEnv("GPUFL_LOG_TYPES", opts.log_types) ||
// An empty list leaves the channels to the agent (every channel it finds), so
// a GPUFL_LOG_TYPES inherited from the caller's shell is removed, not passed on.
!(opts.log_types.empty() ? unsetEnv("GPUFL_LOG_TYPES")
: setEnv("GPUFL_LOG_TYPES", opts.log_types)) ||
!setEnv("GPUFL_CURSOR_FILE", opts.cursor_file) ||
!setEnv("GPUFL_PUBLISHER_TYPE", "http") ||
!setEnv("GPUFL_HTTP_HOST", opts.backend_url) ||
Expand Down
2 changes: 2 additions & 0 deletions daemon/launcher/agent_launcher.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ namespace gpufl::launcher {

struct AgentOptions {
std::string source_folders;
// Channel filter (GPUFL_LOG_TYPES). Empty: no filter, the agent uploads every
// channel it finds.
std::string log_types;
std::string cursor_file;
std::string backend_url;
Expand Down
2 changes: 1 addition & 1 deletion daemon/launcher/cli_parse.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ struct TraceArgs {
std::string api_version = "v1"; // --api-version
std::string agent_jar; // --agent-jar; else GPUFL_AGENT_JAR
std::string agent_cursor; // --agent-cursor; default <output>/cursor.json
std::string log_types = "device,scope,system,sass"; // --log-types (sass carries cubin disassembly + source artifacts)
std::string log_types; // --log-types; empty: the agent uploads every channel it finds
int agent_drain_ms = 60000; // --agent-drain-ms: cap on waiting for the agent to finish uploading
// Bounded window profiling (`gpufl trace` only): bound a capture of a
// long-running target that never exits on its own. warmup defers the
Expand Down
3 changes: 2 additions & 1 deletion daemon/launcher/cli_trace_options.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -516,7 +516,8 @@ const CliOptionManager<TraceArgs>& traceOptions() {
kSection(TraceHelpSection::Runtime),
&parseNonEmptyStringOption<&TraceArgs::agent_cursor>)
.add({"--log-types"}, "<LIST>",
"Agent channels to upload (default: device,scope,system,sass)",
"Upload only these agent channels (default: every channel "
"the agent finds)",
kSection(TraceHelpSection::Runtime),
&parseNonEmptyStringOption<&TraceArgs::log_types>)
.add({"--agent-drain-ms"}, "<MS>",
Expand Down
2 changes: 1 addition & 1 deletion daemon/launcher/upload_command.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ int runUpload(const UploadArgs& args) {

AgentOptions agent_opts;
agent_opts.source_folders = args.log_path;
agent_opts.log_types = "device,scope,system,sass";
// log_types stays empty: the agent uploads every channel it finds.
agent_opts.backend_url = backend_url;
agent_opts.api_key = api_key;
agent_opts.api_version = apiVersionFrom(resolve(args.api_path, gpufl::env::kApiPath));
Expand Down
60 changes: 60 additions & 0 deletions tests/launcher/test_agent_launcher.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#include <gtest/gtest.h>

#include <cstdlib>
#include <string>
#include <vector>

Expand All @@ -15,6 +16,41 @@ std::vector<std::string> exitCommand(const int code) {
#endif
}

// A child that exits 0 only when GPUFL_LOG_TYPES is absent from its environment.
std::vector<std::string> logTypesAbsentCommand() {
#ifdef _WIN32
return {"cmd.exe", "/d", "/s", "/c",
"if defined GPUFL_LOG_TYPES (exit 1) else (exit 0)"};
#else
return {"/bin/sh", "-c", "test -z \"${GPUFL_LOG_TYPES+set}\""};
#endif
}

void setEnv(const char* name, const char* value) {
#ifdef _WIN32
_putenv_s(name, value);
#else
::setenv(name, value, /*overwrite=*/1);
#endif
}

void unsetEnv(const char* name) {
#ifdef _WIN32
_putenv_s(name, "");
#else
::unsetenv(name);
#endif
}

// The child's exit code, or -1 when it could not be started or did not exit.
int childExitCode(const std::vector<std::string>& command) {
gpufl::launcher::AgentProcess process;
std::string error;
if (!process.start(command, error)) return -1;
const auto result = process.waitForExit(5000);
return result.exited ? result.exit_code : -1;
}

} // namespace

TEST(AgentProcessTest, AZeroExitIsACompletedUpload) {
Expand All @@ -40,3 +76,27 @@ TEST(AgentProcessTest, ANonzeroExitIsNotACompletedUpload) {
EXPECT_EQ(result.exit_code, 7);
EXPECT_FALSE(result.succeeded());
}

TEST(AgentEnvironmentTest, AnEmptyChannelListRemovesAnInheritedFilter) {
setEnv("GPUFL_LOG_TYPES", "device,scope"); // as if exported by the caller's shell
ASSERT_EQ(childExitCode(logTypesAbsentCommand()), 1); // the child sees it

gpufl::launcher::AgentOptions opts;
opts.source_folders = "session-dir";
opts.cursor_file = "cursor.json";
opts.backend_url = "http://localhost:1";
opts.api_key = "test-key";
std::string error;
ASSERT_TRUE(gpufl::launcher::configureAgentEnvironment(opts, error)) << error;

EXPECT_EQ(childExitCode(logTypesAbsentCommand()), 0);

// configureAgentEnvironment publishes into this process; leave it clean.
for (const char* name :
{"GPUFL_SOURCE_FOLDER", "GPUFL_SOURCE_FOLDERS", "GPUFL_CURSOR_FILE",
"GPUFL_PUBLISHER_TYPE", "GPUFL_HTTP_HOST", "GPUFL_HTTP_TOKEN",
"GPUFL_HTTP_API_VERSION", "GPUFL_AGENT_UPLOAD_MODE",
"GPUFL_AGENT_EXIT_WHEN_DRAINED"}) {
unsetEnv(name);
}
}
2 changes: 1 addition & 1 deletion tests/launcher/test_cli_parse.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -398,7 +398,7 @@ TEST(CliParseTrace, UploadFlag) {
ASSERT_TRUE(r.args.has_value()) << r.error;
EXPECT_TRUE(r.args->upload);
EXPECT_EQ(r.args->api_version, "v1");
EXPECT_EQ(r.args->log_types, "device,scope,system,sass");
EXPECT_TRUE(r.args->log_types.empty()); // the agent uploads every channel
EXPECT_EQ(r.args->agent_drain_ms, 60000);
}

Expand Down
Loading