Skip to content
Merged
2 changes: 2 additions & 0 deletions resource_detectors/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ cc_library(
srcs = [
"container_detector.cc",
"container_detector_utils.cc",
"process_detector.cc",
"process_detector_utils.cc",
],
deps = [
"//api",
Expand Down
6 changes: 4 additions & 2 deletions resource_detectors/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
# Copyright The OpenTelemetry Authors
# SPDX-License-Identifier: Apache-2.0

add_library(opentelemetry_resource_detectors container_detector_utils.cc
container_detector.cc)
add_library(
opentelemetry_resource_detectors
container_detector_utils.cc container_detector.cc process_detector.cc
process_detector_utils.cc)

set_target_properties(opentelemetry_resource_detectors
PROPERTIES EXPORT_NAME resource_detectors)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

#pragma once

#include "opentelemetry/sdk/resource/resource.h"
#include "opentelemetry/sdk/resource/resource_detector.h"
#include "opentelemetry/version.h"

OPENTELEMETRY_BEGIN_NAMESPACE
namespace resource_detector
{

/**
* ProcessResourceDetector to detect resource attributes when running in a process.
* This detector extracts metadata such as process ID, executable path, and command line arguments
* and sets attributes like process.pid, process.executable.path, and process.command following
* the OpenTelemetry semantic conventions.
*/
class ProcessResourceDetector : public opentelemetry::sdk::resource::ResourceDetector
{
public:
/**
* Detect retrieves the resource attributes for the current process.
* It reads:
* - process.pid from the current process ID
* - process.executable.path from the executable path of the current process
* - process.command from the command used to launch the process
* and returns a Resource with these attributes set.
*/
opentelemetry::sdk::resource::Resource Detect() noexcept override;
};

} // namespace resource_detector
OPENTELEMETRY_END_NAMESPACE
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

#pragma once

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

#include "opentelemetry/version.h"

OPENTELEMETRY_BEGIN_NAMESPACE
namespace resource_detector
{
namespace detail
{

/**
* Forms a file path for a process type based on the given PID.
* for example - /proc/<pid>/cmdline, /proc/<pid>/exe
*/
std::string FormFilePath(const int32_t &pid, const char *process_type);

/**
* Retrieves the absolute file system path to the executable for a given PID.
*
* Platform-specific behavior:
* - Windows: Uses OpenProcess() + GetProcessImageFileNameW().
* - Linux/Unix: Reads the /proc/<pid>/exe symbolic link.
*
* @param pid Process ID.
*/
std::string GetExecutablePath(const int32_t &pid);

/**
* Retrieves the command used to launch the process for a given PID.
* 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.
*/
std::string ExtractCommand(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().
*/
std::string GetCommand(const int32_t &pid);

} // namespace detail
} // namespace resource_detector
OPENTELEMETRY_END_NAMESPACE
50 changes: 50 additions & 0 deletions resource_detectors/process_detector.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

#include "opentelemetry/resource_detectors/process_detector.h"
#include "opentelemetry/nostd/variant.h"
#include "opentelemetry/resource_detectors/process_detector_utils.h"
#include "opentelemetry/sdk/resource/resource.h"
#include "opentelemetry/sdk/resource/resource_detector.h"
#include "opentelemetry/semconv/incubating/process_attributes.h"
#include "opentelemetry/version.h"

#include <stdint.h>
#include <string>
#include <unordered_map>
#include <utility>

#ifdef _MSC_VER
# include <process.h>
# define getpid _getpid
#else
# include <unistd.h>
#endif

OPENTELEMETRY_BEGIN_NAMESPACE
namespace resource_detector
{

opentelemetry::sdk::resource::Resource ProcessResourceDetector::Detect() noexcept
{
int32_t pid = getpid();
opentelemetry::sdk::resource::ResourceAttributes attributes;
attributes[semconv::process::kProcessPid] = pid;

std::string executable_path = opentelemetry::resource_detector::detail::GetExecutablePath(pid);
if (!executable_path.empty())
{
attributes[semconv::process::kProcessExecutablePath] = std::move(executable_path);
}

std::string command = opentelemetry::resource_detector::detail::GetCommand(pid);
if (!command.empty())
{
attributes[semconv::process::kProcessCommand] = std::move(command);
}

return ResourceDetector::Create(attributes);
}

} // namespace resource_detector
OPENTELEMETRY_END_NAMESPACE
113 changes: 113 additions & 0 deletions resource_detectors/process_detector_utils.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

#include "opentelemetry/resource_detectors/process_detector_utils.h"

#include <fstream>
#include <string>

#ifdef _MSC_VER
// clang-format off
# include <windows.h>
# include <psapi.h>
// clang-format on
#else
# include <sys/types.h>
# include <unistd.h>
# include <cstdio>
#endif

#include "opentelemetry/version.h"

OPENTELEMETRY_BEGIN_NAMESPACE
namespace resource_detector
{
namespace detail
{

constexpr const char *kExecutableName = "exe";
constexpr const char *kCmdlineName = "cmdline";

std::string GetExecutablePath(const int32_t &pid)
{
#ifdef _MSC_VER
HANDLE hProcess =
OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, static_cast<DWORD>(pid));
if (!hProcess)
{
return std::string();
}

WCHAR wbuffer[MAX_PATH];
DWORD len = GetProcessImageFileNameW(hProcess, wbuffer, MAX_PATH);
CloseHandle(hProcess);

if (len == 0)
{
return std::string();
}

// Convert UTF-16 to UTF-8
int size_needed = WideCharToMultiByte(CP_UTF8, 0, wbuffer, len, NULL, 0, NULL, NULL);
std::string utf8_path(size_needed, 0);
WideCharToMultiByte(CP_UTF8, 0, wbuffer, len, &utf8_path[0], size_needed, NULL, NULL);

return utf8_path;
#else
std::string path = FormFilePath(pid, kExecutableName);
char buffer[4096];

ssize_t len = readlink(path.c_str(), buffer, sizeof(buffer) - 1);
if (len != -1)
{
buffer[len] = '\0';
return std::string(buffer);
}

return std::string();
#endif
}

std::string GetCommand(const int32_t &pid)
{
#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)
return std::string();

// Convert UTF-16 to UTF-8
int size_needed = WideCharToMultiByte(CP_UTF8, 0, wcmd, -1, NULL, 0, NULL, NULL);
if (size_needed <= 0)
return std::string();

std::string utf8_command(size_needed - 1, 0); // exclude null terminator
WideCharToMultiByte(CP_UTF8, 0, wcmd, -1, &utf8_command[0], size_needed, NULL, NULL);

return utf8_command;
#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);
#endif
}

std::string ExtractCommand(const std::string &command_line_path)
{
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 FormFilePath(const int32_t &pid, const char *process_type)
{
char buff[64];
int len = std::snprintf(buff, sizeof(buff), "/proc/%d/%s", pid, process_type);
return std::string(buff, len);
}

} // namespace detail
} // namespace resource_detector
OPENTELEMETRY_END_NAMESPACE
1 change: 1 addition & 0 deletions resource_detectors/test/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ cc_test(
name = "resource_detector_test",
srcs = [
"container_detector_test.cc",
"process_detector_test.cc",
],
tags = ["test"],
deps = [
Expand Down
3 changes: 2 additions & 1 deletion resource_detectors/test/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# Copyright The OpenTelemetry Authors
# SPDX-License-Identifier: Apache-2.0

add_executable(resource_detector_test container_detector_test.cc)
add_executable(resource_detector_test container_detector_test.cc
process_detector_test.cc)

# Link the required dependencies
target_link_libraries(
Expand Down
48 changes: 48 additions & 0 deletions resource_detectors/test/process_detector_test.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

#include <gtest/gtest.h>
#include <stdint.h>
#include <cstdio>
#include <fstream>
#include <string>

#include "opentelemetry/resource_detectors/process_detector_utils.h"

TEST(ProcessDetectorUtilsTest, FormFilePath)
{
int32_t pid = 1234;
std::string cmdline_path = opentelemetry::resource_detector::detail::FormFilePath(pid, "cmdline");
std::string exe_path = opentelemetry::resource_detector::detail::FormFilePath(pid, "exe");

EXPECT_EQ(cmdline_path, "/proc/1234/cmdline");
EXPECT_EQ(exe_path, "/proc/1234/exe");
}

TEST(ProcessDetectorUtilsTest, ExtractCommand)
{
std::string filename{"test_command.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::remove(filename.c_str()); // Cleanup
}

TEST(ProcessDetectorUtilsTest, EmptyCommandFile)
{
std::string filename{"empty_command.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::remove(filename.c_str()); // Cleanup
}
Loading