Skip to content

Commit

Permalink
Debug option to collect diagnostic information from PHP worker process (
Browse files Browse the repository at this point in the history
  • Loading branch information
intuibase authored Sep 21, 2023
1 parent 1c6f09c commit b80bba4
Show file tree
Hide file tree
Showing 20 changed files with 422 additions and 20 deletions.
4 changes: 2 additions & 2 deletions agent/native/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ set(dependencies
"zlib/1.3"
"libcurl/8.0.1"
"libunwind/1.6.2"
# "boost/1.82.0"
"boost/1.82.0"
"gtest/1.13.0"
)

Expand All @@ -104,7 +104,7 @@ endforeach()

conan_cmake_run(REQUIRES ${dependencies}
OPTIONS Pkg/*:shared=False
# boost:header_only=True
boost:header_only=True
libcurl:shared=True
libcurl:with_libssh2=True
BUILD missing
Expand Down
8 changes: 8 additions & 0 deletions agent/native/ext/ConfigManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -844,6 +844,8 @@ ELASTIC_APM_DEFINE_FIELD_ACCESS_FUNCS( stringValue, transactionMaxSpans )
ELASTIC_APM_DEFINE_FIELD_ACCESS_FUNCS( stringValue, transactionSampleRate )
ELASTIC_APM_DEFINE_FIELD_ACCESS_FUNCS( stringValue, urlGroups )
ELASTIC_APM_DEFINE_FIELD_ACCESS_FUNCS( boolValue, verifyServerCert )
ELASTIC_APM_DEFINE_FIELD_ACCESS_FUNCS( stringValue, debugDiagnosticsFile )


#undef ELASTIC_APM_DEFINE_FIELD_ACCESS_FUNCS
#undef ELASTIC_APM_DEFINE_ENUM_FIELD_ACCESS_FUNCS
Expand Down Expand Up @@ -1225,6 +1227,12 @@ static void initOptionsMetadata( OptionMetadata* optsMeta )
ELASTIC_APM_CFG_OPT_NAME_VERIFY_SERVER_CERT,
/* defaultValue: */ true );

ELASTIC_APM_INIT_METADATA(
buildStringOptionMetadata,
debugDiagnosticsFile,
ELASTIC_APM_CFG_OPT_NAME_DEBUG_DIAGNOSTICS_FILE,
/* defaultValue: */ nullptr );

ELASTIC_APM_ASSERT_EQ_UINT64( i, numberOfOptions );
}

Expand Down
4 changes: 4 additions & 0 deletions agent/native/ext/ConfigManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ enum OptionId
optionId_transactionSampleRate,
optionId_urlGroups,
optionId_verifyServerCert,
optionId_debugDiagnosticsFile,

numberOfOptions
};
Expand Down Expand Up @@ -336,4 +337,7 @@ const ConfigSnapshot* getGlobalCurrentConfigSnapshot();
#define ELASTIC_APM_CFG_OPT_NAME_URL_GROUPS "url_groups"
#define ELASTIC_APM_CFG_OPT_NAME_VERIFY_SERVER_CERT "verify_server_cert"

#define ELASTIC_APM_CFG_OPT_NAME_DEBUG_DIAGNOSTICS_FILE "debug_diagnostic_file"


#define ELASTIC_APM_CFG_CONVERT_OPT_NAME_TO_INI_NAME( optNameStringLiteral ) ( "elastic_apm." optNameStringLiteral )
1 change: 1 addition & 0 deletions agent/native/ext/ConfigSnapshot.h
Original file line number Diff line number Diff line change
Expand Up @@ -88,4 +88,5 @@ struct ConfigSnapshot
String transactionSampleRate = nullptr;
String urlGroups = nullptr;
bool verifyServerCert = false;
String debugDiagnosticsFile = nullptr;
};
4 changes: 3 additions & 1 deletion agent/native/ext/elastic_apm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,8 @@ PHP_INI_BEGIN()
ELASTIC_APM_INI_ENTRY( ELASTIC_APM_CFG_OPT_NAME_TRANSACTION_SAMPLE_RATE )
ELASTIC_APM_INI_ENTRY( ELASTIC_APM_CFG_OPT_NAME_URL_GROUPS )
ELASTIC_APM_INI_ENTRY( ELASTIC_APM_CFG_OPT_NAME_VERIFY_SERVER_CERT )

ELASTIC_APM_INI_ENTRY( ELASTIC_APM_CFG_OPT_NAME_DEBUG_DIAGNOSTICS_FILE )
PHP_INI_END()

#undef ELASTIC_APM_INI_ENTRY_IMPL
Expand Down Expand Up @@ -273,7 +275,7 @@ static PHP_GINIT_FUNCTION(elastic_apm)
});

try {
elastic_apm_globals->globals = new elasticapm::php::AgentGlobals(std::move(phpBridge), {}, std::move(inferredSpans));
elastic_apm_globals->globals = new elasticapm::php::AgentGlobals(std::move(phpBridge), {}, std::move(inferredSpans), std::make_shared<elasticapm::php::SharedMemoryState>());
} catch (std::exception const &e) {
ELASTIC_APM_LOG_DIRECT_CRITICAL( "Unable to allocate AgentGlobals. '%s'", e.what());
}
Expand Down
30 changes: 21 additions & 9 deletions agent/native/ext/lifecycle.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
#include "AST_instrumentation.h"
#include "Hooking.h"
#include "CommonUtils.h"
#include "Diagnostics.h"

#define ELASTIC_APM_CURRENT_LOG_CATEGORY ELASTIC_APM_LOG_CATEGORY_LIFECYCLE

Expand Down Expand Up @@ -658,6 +659,22 @@ void elasticApmRequestInit()
{
requestCounter++;

Tracer* const tracer = getGlobalTracer();
const ConfigSnapshot* config = getTracerCurrentConfigSnapshot( tracer );

enableAccessToServerGlobal();
bool preloadDetected = requestCounter == 1 ? detectOpcachePreload() : false;

if (config && config->debugDiagnosticsFile && !preloadDetected && requestCounter <= 2) {
if (ELASTICAPM_G(globals)->sharedMemory_->shouldExecuteOneTimeTaskAmongWorkers()) {
try {
elasticapm::utils::storeDiagnosticInformation(elasticapm::utils::getParameterizedString(config->debugDiagnosticsFile), *(ELASTICAPM_G(globals)->bridge_));
} catch (std::exception const &e) {
ELASTIC_APM_LOG_WARNING( "Unable to write agent diagnostics: %s", e.what() );
}
}
}

tracerPhpPartOnRequestInitSetInitialTracerState();

TimePoint requestInitStartTime;
Expand All @@ -672,8 +689,6 @@ void elasticApmRequestInit()
ELASTIC_APM_LOG_DEBUG_FUNCTION_ENTRY_MSG( "parent PID: %d", (int)(getParentProcessId()) );

ResultCode resultCode;
Tracer* const tracer = getGlobalTracer();
const ConfigSnapshot* config = getTracerCurrentConfigSnapshot( tracer );

if ( ! tracer->isInited )
{
Expand Down Expand Up @@ -707,13 +722,10 @@ void elasticApmRequestInit()

enableAccessToServerGlobal();

if (requestCounter == 1) {
bool preloadDetected = detectOpcachePreload();
if (preloadDetected) {
ELASTIC_APM_LOG_DEBUG( "opcache.preload request detected on init" );
resultCode = resultSuccess;
goto finally;
}
if (requestCounter == 1 && preloadDetected) {
ELASTIC_APM_LOG_DEBUG( "opcache.preload request detected on init" );
resultCode = resultSuccess;
goto finally;
}

if (!config->captureErrors) {
Expand Down
7 changes: 5 additions & 2 deletions agent/native/libcommon/code/AgentGlobals.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,24 @@
#include "InferredSpans.h"
#include "PeriodicTaskExecutor.h"
#include "PhpBridgeInterface.h"
#include "SharedMemoryState.h"
#include <memory>

namespace elasticapm::php {

class AgentGlobals {
public:
AgentGlobals(std::shared_ptr<PhpBridgeInterface> bridge, std::unique_ptr<PeriodicTaskExecutor> periodicTaskExecutor, std::shared_ptr<InferredSpans> inferredSpans) :
AgentGlobals(std::shared_ptr<PhpBridgeInterface> bridge, std::unique_ptr<PeriodicTaskExecutor> periodicTaskExecutor, std::shared_ptr<InferredSpans> inferredSpans, std::shared_ptr<SharedMemoryState> sharedMemory) :
bridge_(std::move(bridge)),
periodicTaskExecutor_(std::move(periodicTaskExecutor)),
inferredSpans_(std::move(inferredSpans)) {
inferredSpans_(std::move(inferredSpans)),
sharedMemory_(std::move(sharedMemory)) {
}

std::shared_ptr<PhpBridgeInterface> bridge_;
std::unique_ptr<PeriodicTaskExecutor> periodicTaskExecutor_;
std::shared_ptr<InferredSpans> inferredSpans_;
std::shared_ptr<SharedMemoryState> sharedMemory_;
};


Expand Down
6 changes: 5 additions & 1 deletion agent/native/libcommon/code/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,9 @@ set (_Target libcommon)
add_library (${_Target}
STATIC ${SrcFiles}
)
target_include_directories(${_Target} PUBLIC "./")


target_include_directories(${_Target} PUBLIC "./"
"${CONAN_INCLUDE_DIRS_BOOST}"
)

33 changes: 33 additions & 0 deletions agent/native/libcommon/code/CommonUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
#include <string_view>
#include <signal.h>
#include <stddef.h>
#include <sys/types.h>
#include <unistd.h>


namespace elasticapm::utils {
Expand Down Expand Up @@ -53,5 +55,36 @@ std::chrono::milliseconds convertDurationWithUnit(std::string timeWithUnit) {
throw std::invalid_argument("Invalid time unit.");
}

std::string getParameterizedString(std::string_view format) {

std::string out;

for (auto c = format.begin(); c < format.end(); ++c) {
if (*c == '%') {
c++;
if (c == format.end()) {
out.append(1, '%');
break;
}

switch (*c) {
case 'p':
out.append(std::to_string(getpid()));
break;
case 't':
out.append(std::to_string(std::chrono::milliseconds(std::time(NULL)).count()));
break;
default:
out.append(1, '%');
out.append(1, *c);
}
} else {
out.append({*c});
}
}

return out;
}


}
2 changes: 2 additions & 0 deletions agent/native/libcommon/code/CommonUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,6 @@ namespace elasticapm::utils {

std::chrono::milliseconds convertDurationWithUnit(std::string timeWithUnit); // default unit - ms, handles ms, s, m, throws std::invalid_argument if unit is unknown

std::string getParameterizedString(std::string_view format);

}
84 changes: 84 additions & 0 deletions agent/native/libcommon/code/Diagnostics.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@

#include "Diagnostics.h"

#include <sys/types.h>
#include <unistd.h>

#include <chrono>
#include <fstream>
#include <sstream>
#include <iomanip>

namespace elasticapm::utils {

namespace detail {
static constexpr int separatorWidth = 60;
static constexpr char separator = '=';
}

static void getProcessDiags(std::ostream &out, std::string_view name) {
out << std::setfill(detail::separator) << std::setw(detail::separatorWidth) << detail::separator << std::endl;
out << "Process " << name << ":" << std::endl;
out << std::setfill(detail::separator) << std::setw(detail::separatorWidth) << detail::separator << std::endl;
try {
std::stringstream mapsname;
mapsname << "/proc/self/" << name;
std::ifstream maps;
maps.exceptions(std::ios_base::failbit);
maps.open(mapsname.str());
out << maps.rdbuf();
maps.close();
} catch (std::exception const &e) {
out << "Unable to get process " << name << ": " << e.what() << std::endl;
}
}

static void getDiagnosticInformation(std::ostream &out, elasticapm::php::PhpBridgeInterface const &bridge) {
out << std::setfill(detail::separator) << std::setw(detail::separatorWidth) << detail::separator << std::endl;
out << "Elastic APM PHP agent diagnostics:" << std::endl;
out << std::setfill(detail::separator) << std::setw(detail::separatorWidth) << detail::separator << std::endl;
std::time_t time = std::time({});
char timeString[std::size("yyyy-mm-ddThh:mm:ssZ")];
std::strftime(std::data(timeString), std::size(timeString), "%FT%TZ", std::gmtime(&time));

out << "Time: " << timeString << " UTC (" << std::chrono::milliseconds(time).count() << ')' << std::endl;
out << "PID: " << getpid() << std::endl;
out << "PPID: " << getppid() << std::endl;
out << "UID: " << getuid() << std::endl;

auto extensions = bridge.getExtensionList();
if (extensions.size() > 0) {
out << std::setfill(detail::separator) << std::setw(detail::separatorWidth) << detail::separator << std::endl;
out << "Loaded extensions:" << std::endl;
out << std::setfill(detail::separator) << std::setw(detail::separatorWidth) << detail::separator << std::endl;

for (auto const &extension : extensions) {
out << extension.name << " " << extension.version << std::endl;
}
}

out << std::setfill(detail::separator) << std::setw(detail::separatorWidth) << detail::separator << std::endl << std::endl;
out << "phpinfo() output:" << std::endl;
out << std::setfill(detail::separator) << std::setw(detail::separatorWidth) << detail::separator << std::endl;
out << bridge.getPhpInfo() << std::endl;

getProcessDiags(out, "maps");
getProcessDiags(out, "smaps_rollup");
getProcessDiags(out, "status");
getProcessDiags(out, "limits");
getProcessDiags(out, "cgroup");
getProcessDiags(out, "cmdline");
getProcessDiags(out, "mountinfo");
getProcessDiags(out, "mounts");
getProcessDiags(out, "mountstats");
getProcessDiags(out, "stat");
}

void storeDiagnosticInformation(std::string_view outputFileName, elasticapm::php::PhpBridgeInterface const &bridge) {
std::ofstream out;
out.exceptions(std::ios_base::failbit);
out.open(outputFileName.data());
getDiagnosticInformation(out, bridge);
}

}
10 changes: 10 additions & 0 deletions agent/native/libcommon/code/Diagnostics.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@

#pragma once

#include "PhpBridgeInterface.h"

namespace elasticapm::utils {

void storeDiagnosticInformation(std::string_view outputFileName, elasticapm::php::PhpBridgeInterface const &bridge); //throws

}
14 changes: 13 additions & 1 deletion agent/native/libcommon/code/PhpBridgeInterface.h
Original file line number Diff line number Diff line change
@@ -1,15 +1,27 @@
#pragma once

#include <chrono>
#include <string>
#include <vector>

namespace elasticapm::php {


class PhpBridgeInterface {
public:

struct phpExtensionInfo_t {
std::string name;
std::string version;
};

virtual ~PhpBridgeInterface() = default;

virtual bool callInferredSpans(std::chrono::milliseconds duration) = 0;
virtual bool callInferredSpans(std::chrono::milliseconds duration) const = 0;
virtual std::vector<phpExtensionInfo_t> getExtensionList() const = 0;
virtual std::string getPhpInfo() const = 0;


};

}
42 changes: 42 additions & 0 deletions agent/native/libcommon/code/SharedMemoryState.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@


#include <boost/interprocess/anonymous_shared_memory.hpp>
#include <boost/interprocess/mapped_region.hpp>
#include <boost/interprocess/sync/interprocess_upgradable_mutex.hpp>
#include <boost/interprocess/sync/scoped_lock.hpp>
#include <boost/interprocess/sync/sharable_lock.hpp>

namespace elasticapm::php {

class SharedMemoryState {
public:
struct SharedData {
boost::interprocess::interprocess_upgradable_mutex mutex;
bool oneTimeTaskAmongWorkersExecuted = false;
};

bool shouldExecuteOneTimeTaskAmongWorkers() {
{
boost::interprocess::sharable_lock< decltype( SharedData::mutex ) > lock( data_->mutex );
if ( data_->oneTimeTaskAmongWorkersExecuted )
{
return false;
}
}

boost::interprocess::scoped_lock< decltype( SharedData::mutex ) > ulock( data_->mutex );
if ( data_->oneTimeTaskAmongWorkersExecuted )
{
return false;
}
data_->oneTimeTaskAmongWorkersExecuted = true;
return true;
}


protected:
boost::interprocess::mapped_region region_{ boost::interprocess::anonymous_shared_memory( sizeof( SharedData ) ) };
SharedData* data_{ new (region_.get_address()) SharedData };
};

}
Loading

0 comments on commit b80bba4

Please sign in to comment.