Skip to content
Merged
Show file tree
Hide file tree
Changes from 15 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
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

#include <stdint.h>
#include <string>
#include <vector>

#include "opentelemetry/version.h"

Expand Down Expand Up @@ -32,20 +33,27 @@ std::string FormFilePath(const int32_t &pid, const char *process_type);
std::string GetExecutablePath(const int32_t &pid);

/**
* Retrieves the command used to launch the process for a given PID.
* Extracts the command-line arguments and the command.
* Platform-specific behavior:
* - Windows: Uses GetCommandLineW() to get the command of the current process.
* - Linux/Unix: Reads the zeroth string of /proc/<pid>/cmdline file.
* - Windows: Uses CommandLineToArgvW() to parse the command line.
* - Linux/Unix: Reads the /proc/<pid>/cmdline file and splits it into command and arguments.
* - TODO: Need to implement for Darwin
*/
std::string ExtractCommand(const std::string &command_line_path);
std::vector<std::string> ExtractCommandWithArgs(const std::string &command_line_path);

/**
* Retrieves the command used to launch the process for a given PID.
* This function is a wrapper around ExtractCommand() and is provided for convenience and
* testability of ExtractCommand().
* Retrieves the command-line arguments and the command used to launch the process for a given PID.
* This function is a wrapper around ExtractCommandWithArgs() and is provided for convenience and
* testability of ExtractCommandWithArgs().
*/
std::string GetCommand(const int32_t &pid);
std::vector<std::string> GetCommandWithArgs(const int32_t &pid);

/**
* Converts a vector of command-line arguments and command into a single command-line string.
* process.command_line is the string representation of the command line that we collected using
* GetCommandWithArgs().
*/
std::string ConvertCommandArgsToString(const std::vector<std::string> &command_args);

} // namespace detail
} // namespace resource_detector
Expand Down
16 changes: 11 additions & 5 deletions resource_detectors/process_detector.cc
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#include <string>
#include <unordered_map>
#include <utility>
#include <vector>

#ifdef _MSC_VER
# include <process.h>
Expand Down Expand Up @@ -50,16 +51,21 @@ opentelemetry::sdk::resource::Resource ProcessResourceDetector::Detect() noexcep

try
{
std::string command = opentelemetry::resource_detector::detail::GetCommand(pid);
if (!command.empty())
std::vector<std::string> command_with_args =
opentelemetry::resource_detector::detail::GetCommandWithArgs(pid);
if (!command_with_args.empty())
{
attributes[semconv::process::kProcessCommand] = std::move(command);
std::string commandline_args =
opentelemetry::resource_detector::detail::ConvertCommandArgsToString(command_with_args);
attributes[semconv::process::kProcessCommandLine] = std::move(commandline_args);
attributes[semconv::process::kProcessCommand] = command_with_args[0];
attributes[semconv::process::kProcessCommandArgs] = std::move(command_with_args);
}
}
catch (const std::exception &ex)
{
OTEL_INTERNAL_LOG_ERROR("[Process Resource Detector] " << "Error extracting command: "
<< ex.what());
OTEL_INTERNAL_LOG_ERROR("[Process Resource Detector] "
<< "Error extracting command with arguments: " << ex.what());
}

return ResourceDetector::Create(attributes);
Expand Down
68 changes: 47 additions & 21 deletions resource_detectors/process_detector_utils.cc
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,14 @@

#include <fstream>
#include <string>
#include <vector>

#ifdef _MSC_VER
// clang-format off
# include <windows.h>
# include <psapi.h>
# include <shellapi.h>
# pragma comment(lib, "shell32.lib")
// clang-format on
#else
# include <sys/types.h>
Expand Down Expand Up @@ -68,41 +71,64 @@ std::string GetExecutablePath(const int32_t &pid)
#endif
}

std::string GetCommand(const int32_t &pid)
std::vector<std::string> ExtractCommandWithArgs(const std::string &command_line_path)
{
#ifdef _MSC_VER
// On Windows, GetCommandLineW only works for the CURRENT process,
// so we ignore `pid` and just return the current process's command line.
LPCWSTR wcmd = GetCommandLineW();
if (!wcmd)
std::vector<std::string> commands;
std::ifstream command_line_file(command_line_path, std::ios::in | std::ios::binary);
std::string command;
while (std::getline(command_line_file, command, '\0'))
{
return std::string();
if (!command.empty())
{
commands.push_back(command);
}
}
return commands;
}

// Convert UTF-16 to UTF-8
int size_needed = WideCharToMultiByte(CP_UTF8, 0, wcmd, -1, NULL, 0, NULL, NULL);
if (size_needed <= 0)
std::vector<std::string> GetCommandWithArgs(const int32_t &pid)
{
#ifdef _MSC_VER
int argc = 0;
LPWSTR *argvW = CommandLineToArgvW(GetCommandLineW(), &argc);
if (!argvW)
{
return std::string();
return {}; // returns an empty vector if CommandLineToArgvW fails
}

std::string utf8_command(size_needed - 1, 0); // exclude null terminator
WideCharToMultiByte(CP_UTF8, 0, wcmd, -1, &utf8_command[0], size_needed, NULL, NULL);
std::vector<std::string> args;
for (int i = 0; i < argc; i++)
{
// Convert UTF-16 to UTF-8
int size_needed = WideCharToMultiByte(CP_UTF8, 0, argvW[i], -1, NULL, 0, NULL, NULL);
if (size_needed > 0)
{
std::string arg(size_needed - 1, 0);
WideCharToMultiByte(CP_UTF8, 0, argvW[i], -1, &arg[0], size_needed, NULL, NULL);
args.push_back(arg);
}
}

return utf8_command;
LocalFree(argvW);
return args;
#else
// This is the path to get the command that was used to start the process
std::string command_line_path = FormFilePath(pid, kCmdlineName);
return ExtractCommand(command_line_path);
return ExtractCommandWithArgs(command_line_path);
#endif
}

std::string ExtractCommand(const std::string &command_line_path)
std::string ConvertCommandArgsToString(const std::vector<std::string> &command_args)
{
std::string command;
std::ifstream command_line_file(command_line_path, std::ios::in | std::ios::binary);
std::getline(command_line_file, command, '\0');
return command;
std::string command_line;
for (const auto &arg : command_args)
{
if (!command_line.empty())
{
command_line += " ";
}
command_line += arg;
}
return command_line;
}

std::string FormFilePath(const int32_t &pid, const char *process_type)
Expand Down
106 changes: 78 additions & 28 deletions resource_detectors/test/process_detector_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include <stdint.h>
#include <fstream>
#include <string>
#include <vector>

#ifdef _MSC_VER
// clang-format off
Expand All @@ -31,34 +32,44 @@ TEST(ProcessDetectorUtilsTest, FormFilePath)
EXPECT_EQ(exe_path, "/proc/1234/exe");
}

TEST(ProcessDetectorUtilsTest, ExtractCommand)
TEST(ProcessDetectorUtilsTest, ExtractCommandWithArgs)
{
std::string filename{"test_command.txt"};
std::string filename{"test_command_args.txt"};

{
std::ofstream outfile(filename, std::ios::binary);
const char raw_data[] = "test_command\0arg1\0arg2\0arg3\0";
outfile.write(raw_data, sizeof(raw_data) - 1);
}

std::string command = opentelemetry::resource_detector::detail::ExtractCommand(filename);
EXPECT_EQ(command, std::string{"test_command"});
std::vector<std::string> args =
opentelemetry::resource_detector::detail::ExtractCommandWithArgs(filename);
EXPECT_EQ(args, (std::vector<std::string>{"test_command", "arg1", "arg2", "arg3"}));

std::remove(filename.c_str()); // Cleanup
}

TEST(ProcessDetectorUtilsTest, EmptyCommandFile)
TEST(ProcessDetectorUtilsTest, EmptyCommandWithArgsFile)
{
std::string filename{"empty_command.txt"};
std::string filename{"empty_command_args.txt"};
std::ofstream outfile(filename, std::ios::binary);
outfile.close();

std::string command = opentelemetry::resource_detector::detail::ExtractCommand(filename);
EXPECT_EQ(command, std::string{""});
std::vector<std::string> args =
opentelemetry::resource_detector::detail::ExtractCommandWithArgs(filename);
EXPECT_TRUE(args.empty());

std::remove(filename.c_str()); // Cleanup
}

TEST(ProcessDetectorUtilsTest, ConvertCommandArgsToStringTest)
{
std::vector<std::string> args = {"test_command", "arg1", "arg2", "arg3"};
std::string command_line =
opentelemetry::resource_detector::detail::ConvertCommandArgsToString(args);
EXPECT_EQ(command_line, "test_command arg1 arg2 arg3");
}

TEST(ProcessDetectorUtilsTest, GetExecutablePathTest)
{
int32_t pid = getpid();
Expand Down Expand Up @@ -109,40 +120,79 @@ TEST(ProcessDetectorUtilsTest, GetExecutablePathTest)
EXPECT_EQ(path, expected_path);
}

TEST(ProcessDetectorUtilsTest, GetCommandTest)
TEST(ProcessDetectorUtilsTest, CommandTest)
{
int32_t pid = getpid();
std::string command;
#ifdef _MSC_VER
// On Windows, GetCommandLineW only works for the CURRENT process,
// so we ignore `pid` and just return the current process's command line.
LPCWSTR wcmd = GetCommandLineW();
if (!wcmd)
int argc = 0;
LPWSTR *argvW = CommandLineToArgvW(GetCommandLineW(), &argc);

if (argvW && argc > 0)
{
command = std::string();
int size_needed = WideCharToMultiByte(CP_UTF8, 0, argvW[0], -1, NULL, 0, NULL, NULL);
if (size_needed > 0)
{
std::string arg(size_needed - 1, 0);
WideCharToMultiByte(CP_UTF8, 0, argvW[0], -1, &arg[0], size_needed, NULL, NULL);
command = arg;
}

LocalFree(argvW);
}
else
{
command = std::string();
}
#else
std::string command_line_path =
opentelemetry::resource_detector::detail::FormFilePath(pid, "cmdline");
std::ifstream command_line_file(command_line_path, std::ios::in | std::ios::binary);
std::getline(command_line_file, command, '\0');
#endif
std::vector<std::string> expected_command_with_args =
opentelemetry::resource_detector::detail::GetCommandWithArgs(pid);
std::string expected_command;
if (!expected_command_with_args.empty())
{
expected_command = expected_command_with_args[0];
}
EXPECT_EQ(command, expected_command);
}

// Convert UTF-16 to UTF-8
int size_needed = WideCharToMultiByte(CP_UTF8, 0, wcmd, -1, NULL, 0, NULL, NULL);
if (size_needed <= 0)
{
command = std::string();
}
else
TEST(ProcessDetectorUtilsTest, GetCommandWithArgsTest)
{
int32_t pid = getpid();
std::vector<std::string> args;
#ifdef _MSC_VER
int argc = 0;
LPWSTR *argvW = CommandLineToArgvW(GetCommandLineW(), &argc);
if (!argvW)
{
args = {};
}
else
{
for (int i = 0; i < argc; i++)
{
std::string utf8_command(size_needed - 1, 0); // exclude null terminator
WideCharToMultiByte(CP_UTF8, 0, wcmd, -1, &utf8_command[0], size_needed, NULL, NULL);
command = utf8_command;
// Convert UTF-16 to UTF-8
int size_needed = WideCharToMultiByte(CP_UTF8, 0, argvW[i], -1, NULL, 0, NULL, NULL);
if (size_needed > 0)
{
std::string arg(size_needed - 1, 0);
WideCharToMultiByte(CP_UTF8, 0, argvW[i], -1, &arg[0], size_needed, NULL, NULL);
args.push_back(arg);
}
}
}

LocalFree(argvW);
#else
// This is the path to get the command that was used to start the process
std::string command_line_path =
opentelemetry::resource_detector::detail::FormFilePath(pid, "cmdline");
command = opentelemetry::resource_detector::detail::ExtractCommand(command_line_path);
args = opentelemetry::resource_detector::detail::ExtractCommandWithArgs(command_line_path);
#endif
std::string expected_command = opentelemetry::resource_detector::detail::GetCommand(pid);
EXPECT_EQ(command, expected_command);
std::vector<std::string> expected_args =
opentelemetry::resource_detector::detail::GetCommandWithArgs(pid);
EXPECT_EQ(args, expected_args);
}
Loading