Skip to content

Commit

Permalink
feat(agent): initial prototype
Browse files Browse the repository at this point in the history
  • Loading branch information
hahuja2 committed Aug 16, 2024
1 parent a86c5f5 commit ec10d19
Show file tree
Hide file tree
Showing 3 changed files with 182 additions and 55 deletions.
1 change: 1 addition & 0 deletions agent/fw_hooks.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ extern void nr_fw_zend2_enable(TSRMLS_D);

/* Libraries. */
extern void nr_aws_sdk_php_enable();
extern void nr_composer_detected();
extern void nr_doctrine2_enable(TSRMLS_D);
extern void nr_guzzle3_enable(TSRMLS_D);
extern void nr_guzzle4_enable(TSRMLS_D);
Expand Down
228 changes: 173 additions & 55 deletions agent/php_execute.c
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
#include "util_metrics.h"
#include "util_number_converter.h"
#include "util_strings.h"
#include "util_syscalls.h"
#include "util_url.h"
#include "util_url.h"
#include "util_metrics.h"
Expand Down Expand Up @@ -476,7 +477,6 @@ typedef struct _nr_library_table_t {
const char* library_name;
const char* file_to_check;
size_t file_to_check_len;
nr_composer_special_fn_t special;
nr_library_enable_fn_t enable;
} nr_library_table_t;

Expand All @@ -486,97 +486,98 @@ typedef struct _nr_library_table_t {
// clang-format: off
static nr_library_table_t libraries[] = {
/* AWS-SDK-PHP 3 */
{"AWS-SDK-PHP", NR_PSTR("aws-sdk-php/src/awsclient.php"), 0, nr_aws_sdk_php_enable},
{"AWS-SDK-PHP", NR_PSTR("aws-sdk-php/src/awsclient.php"), nr_aws_sdk_php_enable},

/* Doctrine < 2.18 */
{"Doctrine 2", NR_PSTR("doctrine/orm/query.php"), 0, nr_doctrine2_enable},
{"Doctrine 2", NR_PSTR("doctrine/orm/query.php"), nr_doctrine2_enable},
/* Doctrine 2.18 reworked the directory structure */
{"Doctrine 2", NR_PSTR("doctrine/orm/src/query.php"), 0, nr_doctrine2_enable},
{"Doctrine 2", NR_PSTR("doctrine/orm/src/query.php"), nr_doctrine2_enable},

{"Guzzle 3", NR_PSTR("guzzle/http/client.php"), 0, nr_guzzle3_enable},
{"Guzzle 4-5", NR_PSTR("hasemitterinterface.php"), 0, nr_guzzle4_enable},
{"Guzzle 6", NR_PSTR("guzzle/src/functions_include.php"), 0, nr_guzzle6_enable},
{"Guzzle 3", NR_PSTR("guzzle/http/client.php"), nr_guzzle3_enable},
{"Guzzle 4-5", NR_PSTR("hasemitterinterface.php"), nr_guzzle4_enable},
{"Guzzle 6", NR_PSTR("guzzle/src/functions_include.php"), nr_guzzle6_enable},

{"MongoDB", NR_PSTR("mongodb/src/client.php"), 0, nr_mongodb_enable},
{"MongoDB", NR_PSTR("mongodb/src/client.php"), nr_mongodb_enable},

/*
* The first path is for Composer installs, the second is for
* /usr/local/bin.
*/
{"PHPUnit", NR_PSTR("phpunit/src/framework/test.php"), 0, nr_phpunit_enable},
{"PHPUnit", NR_PSTR("phpunit/framework/test.php"), 0, nr_phpunit_enable},
{"PHPUnit", NR_PSTR("phpunit/src/framework/test.php"), nr_phpunit_enable},
{"PHPUnit", NR_PSTR("phpunit/framework/test.php"), nr_phpunit_enable},

{"Predis", NR_PSTR("predis/src/client.php"), 0, nr_predis_enable},
{"Predis", NR_PSTR("predis/client.php"), 0, nr_predis_enable},
{"Predis", NR_PSTR("predis/src/client.php"), nr_predis_enable},
{"Predis", NR_PSTR("predis/client.php"), nr_predis_enable},
{"PHP_Package", NR_PSTR("vendor/autoload.php"), nr_composer_detected},

/*
* Allow Zend Framework 1.x to be detected as a library as well as a
* framework. This allows Zend_Http_Client to be instrumented when used
* with other frameworks or even without a framework at all. This is
* necessary for Magento in particular, which is built on ZF1.
*/
{"Zend_Http", NR_PSTR("zend/http/client.php"), 0, nr_zend_http_enable},
{"Zend_Http", NR_PSTR("zend/http/client.php"), nr_zend_http_enable},

/*
* Allow Laminas Framework 3.x to be detected as a library as well as a
* framework. This allows Laminas_Http_Client to be instrumented when used
* with other frameworks or even without a framework at all.
*/
{"Laminas_Http", NR_PSTR("laminas-http/src/client.php"), 0, nr_laminas_http_enable},
{"Laminas_Http", NR_PSTR("laminas-http/src/client.php"), nr_laminas_http_enable},

/*
* Other frameworks, detected only, but not specifically
* instrumented. We detect these as libraries so that we don't prevent
* detection of a supported framework or library later (since a transaction
* can only have one framework).
*/
{"Aura1", NR_PSTR("aura/framework/system.php"), 0, NULL},
{"Aura2", NR_PSTR("aura/di/src/containerinterface.php"), 0, NULL},
{"Aura3", NR_PSTR("aura/di/src/containerconfiginterface.php"), 0, NULL},
{"CakePHP3", NR_PSTR("cakephp/src/core/functions.php"), 0, NULL},
{"Fuel", NR_PSTR("fuel/core/classes/fuel.php"), 0, NULL},
{"Lithium", NR_PSTR("lithium/core/libraries.php"), 0, NULL},
{"Phpbb", NR_PSTR("phpbb/request/request.php"), 0, NULL},
{"Phpixie2", NR_PSTR("phpixie/core/classes/phpixie/pixie.php"), 0, NULL},
{"Phpixie3", NR_PSTR("phpixie/framework.php"), 0, NULL},
{"React", NR_PSTR("react/event-loop/src/loopinterface.php"), 0, NULL},
{"SilverStripe", NR_PSTR("injector/silverstripeinjectioncreator.php"), 0, NULL},
{"SilverStripe4", NR_PSTR("silverstripeserviceconfigurationlocator.php"), 0, NULL},
{"Typo3", NR_PSTR("classes/typo3/flow/core/bootstrap.php"), 0, NULL},
{"Typo3", NR_PSTR("typo3/sysext/core/classes/core/bootstrap.php"), 0, NULL},
{"Aura1", NR_PSTR("aura/framework/system.php"), NULL},
{"Aura2", NR_PSTR("aura/di/src/containerinterface.php"), NULL},
{"Aura3", NR_PSTR("aura/di/src/containerconfiginterface.php"), NULL},
{"CakePHP3", NR_PSTR("cakephp/src/core/functions.php"), NULL},
{"Fuel", NR_PSTR("fuel/core/classes/fuel.php"), NULL},
{"Lithium", NR_PSTR("lithium/core/libraries.php"), NULL},
{"Phpbb", NR_PSTR("phpbb/request/request.php"), NULL},
{"Phpixie2", NR_PSTR("phpixie/core/classes/phpixie/pixie.php"), NULL},
{"Phpixie3", NR_PSTR("phpixie/framework.php"), NULL},
{"React", NR_PSTR("react/event-loop/src/loopinterface.php"), NULL},
{"SilverStripe", NR_PSTR("injector/silverstripeinjectioncreator.php"), NULL},
{"SilverStripe4", NR_PSTR("silverstripeserviceconfigurationlocator.php"), NULL},
{"Typo3", NR_PSTR("classes/typo3/flow/core/bootstrap.php"), NULL},
{"Typo3", NR_PSTR("typo3/sysext/core/classes/core/bootstrap.php"), NULL},

/*
* Other CMS (content management systems), detected only, but
* not specifically instrumented.
*/
{"Moodle", NR_PSTR("moodlelib.php"), 0, NULL},
{"Moodle", NR_PSTR("moodlelib.php"), NULL},
/*
* It is likely that this will never be found, since the CodeIgniter.php
* will get loaded first, and as such mark this transaction as belonging to
* CodeIgniter, and not Expession Engine.
*/
{"ExpressionEngine", NR_PSTR("system/expressionengine/config/config.php"), 0, NULL},
{"ExpressionEngine", NR_PSTR("system/expressionengine/config/config.php"), NULL},
/*
* ExpressionEngine 5, however, has a very obvious file we can look for.
*/
{"ExpressionEngine5", NR_PSTR("expressionengine/boot/boot.php"), 0, NULL},
{"ExpressionEngine5", NR_PSTR("expressionengine/boot/boot.php"), NULL},
/*
* DokuWiki uses doku.php as an entry point, but has other files that are
* loaded directly that this won't pick up. That's probably OK for
* supportability metrics, but we'll add the most common name for the
* configuration file as well just in case.
*/
{"DokuWiki", NR_PSTR("doku.php"), 0, NULL},
{"DokuWiki", NR_PSTR("conf/dokuwiki.php"), 0, NULL},
{"DokuWiki", NR_PSTR("doku.php"), NULL},
{"DokuWiki", NR_PSTR("conf/dokuwiki.php"), NULL},

/*
* SugarCRM no longer has a community edition, so this likely only works
* with older versions.
*/
{"SugarCRM", NR_PSTR("sugarobjects/sugarconfig.php"), 0, NULL},
{"SugarCRM", NR_PSTR("sugarobjects/sugarconfig.php"), NULL},

{"Xoops", NR_PSTR("class/xoopsload.php"), 0, NULL},
{"E107", NR_PSTR("e107_handlers/e107_class.php"), 0, NULL},
{"Xoops", NR_PSTR("class/xoopsload.php"), NULL},
{"E107", NR_PSTR("e107_handlers/e107_class.php"), NULL},
};
// clang-format: on

Expand All @@ -585,15 +586,15 @@ static size_t num_libraries = sizeof(libraries) / sizeof(nr_library_table_t);
// clang-format: off
static nr_library_table_t logging_frameworks[] = {
/* Monolog - Logging for PHP */
{"Monolog", NR_PSTR("monolog/logger.php"), 0, nr_monolog_enable},
{"Monolog", NR_PSTR("monolog/logger.php"), nr_monolog_enable},
/* Consolidation/Log - Logging for PHP */
{"Consolidation/Log", NR_PSTR("consolidation/log/src/logger.php"), 0, NULL},
{"Consolidation/Log", NR_PSTR("consolidation/log/src/logger.php"), NULL},
/* laminas-log - Logging for PHP */
{"laminas-log", NR_PSTR("laminas-log/src/logger.php"), 0, NULL},
{"laminas-log", NR_PSTR("laminas-log/src/logger.php"), NULL},
/* cakephp-log - Logging for PHP */
{"cakephp-log", NR_PSTR("cakephp/log/log.php"), 0, NULL},
{"cakephp-log", NR_PSTR("cakephp/log/log.php"), NULL},
/* Analog - Logging for PHP */
{"Analog", NR_PSTR("analog/analog.php"), 0, NULL},
{"Analog", NR_PSTR("analog/analog.php"), NULL},
};
// clang-format: on

Expand Down Expand Up @@ -932,24 +933,137 @@ static void nr_execute_handle_library(const char* filename,
}
}

static void nr_execute_handle_composer(const char* filename,
const size_t filename_len TSRMLS_DC) {
size_t i;
void nr_composer_detected() {
if (NULL != NRPRG(txn) && !NRPRG(txn)->detection_status.composer_detected) {
const char* magic_file_1
= "vendor/composer/autoload_real.php";
const char* composer_file = "vendor/composer/installed.php";
// the below should be using the absolute path
if (0 == nr_access(magic_file_1, F_OK | R_OK)) {
if (0 == nr_access(composer_file, F_OK | R_OK)) {
NRPRG(txn)->detection_status.composer_detected = 1;
NRPRG(txn)->detection_status.file_exists = 1;
NRPRG(txn)->detection_status.api_called = 0;
NRPRG(txn)->detection_status.inside_eval_string = 0;
}
}
}
}

for (i = 0; i < num_libraries; i++) {
if (nr_striendswith(STR_AND_LEN(filename),
STR_AND_LEN(libraries[i].file_to_check))) {
if (libraries[i].special) {
nr_composer_classification_t special
= libraries[i].special(filename TSRMLS_CC);
if (FILE_EXISTS == special) {
nr_fw_support_add_library_supportability_metric(
NRPRG(txn), libraries[i].library_name);
if (NULL != libraries[i].enable) {
libraries[i].enable(TSRMLS_C);
static void nr_get_composer_package_information() {
zval retval;
int result = -1;

if (NRPRG(txn)->detection_status.inside_eval_string) {
return;
}

char* check
= ""
"(function() {"
" try {"
" if (class_exists('Composer\\InstalledVersions')) {"
" if (method_exists('Composer\\InstalledVersions', "
" 'getInstalledPackages') && method_exists('Composer\\InstalledVersions', "
" 'getVersion')) {"
" return true;"
" } else {"
" return false;"
" }"
" } else {"
" return false;"
" }"
" } catch (Exception $e) {"
" return NULL;"
" }"
"})();";

char* getpackagename =
""
"(function() {"
" try {"
" return Composer\\InstalledVersions::getInstalledPackages();"
" } catch (Exception $e) {"
" return NULL;"
" }"
"})();";

char* getversion =
""
"(function() {"
" try {"
" return Composer\\InstalledVersions::getVersion(\"%s\");"
" } catch (Exception $e) {"
" return NULL;"
" }"
"})();";

NRPRG(txn)->detection_status.inside_eval_string = 1;
result = zend_eval_string(check, &retval, "check if class and methods exist" TSRMLS_CC);
NRPRG(txn)->detection_status.inside_eval_string = 0;

if (result == SUCCESS) {
if (Z_TYPE(retval) == IS_NULL) {
if (NULL != NRPRG(txn)) {
NRPRG(txn)->detection_status.api_called = 1;
}
nrl_verbosedebug(NRL_TXN, "IS_NULL");
return;
} else if (Z_TYPE(retval) == IS_FALSE) {
nrl_verbosedebug(NRL_TXN, "IS_FALSE");
return;
} else if (Z_TYPE(retval) != IS_TRUE) {
nrl_verbosedebug(NRL_TXN, "NOT_TRUE");
return;
}
}
zval_dtor(&retval);
NRPRG(txn)->detection_status.inside_eval_string = 1;
result = zend_eval_string(getpackagename, &retval,
"get installed packages by name" TSRMLS_CC);
NRPRG(txn)->detection_status.inside_eval_string = 0;
if (result == SUCCESS) {
if (Z_TYPE(retval) == IS_ARRAY) {
zval* value;
char* buf;
int result2;
zval retval2;
char* version = NULL;
(void)version;
ZEND_HASH_FOREACH_VAL(Z_ARRVAL(retval), value) {
if (Z_TYPE_P(value) == IS_STRING) {
buf = nr_formatf(getversion, Z_STRVAL_P(value));
NRPRG(txn)->detection_status.inside_eval_string = 1;
result2 = zend_eval_string(buf, &retval2, "retrieve version for packages");
NRPRG(txn)->detection_status.inside_eval_string = 0;
nr_free(buf);
if (SUCCESS == result2) {
if (nr_php_is_zval_valid_string(&retval2)) {
version = Z_STRVAL(retval2);
}
}
}
zval_dtor(&retval2);
nrl_verbosedebug(NRL_TXN, "package %s, version %s",
NRSAFESTR(Z_STRVAL_P(value)), NRSAFESTR(version));
}
ZEND_HASH_FOREACH_END();
} else {
if (NULL != NRPRG(txn)) {
NRPRG(txn)->detection_status.api_called = 1;
}
zval_dtor(&retval);
return;
}
zval_dtor(&retval);
NRPRG(txn)->detection_status.api_called = 1;
}
}

static void nr_execute_handle_composer() {
if (NRPRG(txn)->detection_status.file_exists) {
if (!NRPRG(txn)->detection_status.api_called) {
nr_get_composer_package_information();
}
}
}
Expand Down Expand Up @@ -1060,6 +1174,10 @@ static void nr_php_execute_file(const zend_op_array* op_array,
}

nr_php_add_user_instrumentation(TSRMLS_C);

if (NRPRG(txn)->detection_status.composer_detected && !NRPRG(txn)->detection_status.api_called) {
nr_execute_handle_composer(filename, filename_len);
}
}

/*
Expand Down
8 changes: 8 additions & 0 deletions axiom/nr_txn.h
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,13 @@ typedef enum _nr_cpu_usage_t {
NR_CPU_USAGE_COUNT = 2
} nr_cpu_usage_t;

typedef struct _nr_composer_info_t {
int composer_detected;
int api_called;
int file_exists;
int inside_eval_string;
} nr_composer_info_t;

/*
* Possible transaction types, which go into the type bitfield in the nrtxn_t
* struct.
Expand Down Expand Up @@ -302,6 +309,7 @@ typedef struct _nrtxn_t {
nr_distributed_trace_t*
distributed_trace; /* distributed tracing metadata for the transaction */
nr_span_queue_t* span_queue; /* span queue when 8T is enabled */
nr_composer_info_t detection_status;

/*
* flag to indicate if one time (per transaction) logging metrics
Expand Down

0 comments on commit ec10d19

Please sign in to comment.