Skip to content

Commit ec10d19

Browse files
committed
feat(agent): initial prototype
1 parent a86c5f5 commit ec10d19

File tree

3 files changed

+182
-55
lines changed

3 files changed

+182
-55
lines changed

agent/fw_hooks.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ extern void nr_fw_zend2_enable(TSRMLS_D);
4646

4747
/* Libraries. */
4848
extern void nr_aws_sdk_php_enable();
49+
extern void nr_composer_detected();
4950
extern void nr_doctrine2_enable(TSRMLS_D);
5051
extern void nr_guzzle3_enable(TSRMLS_D);
5152
extern void nr_guzzle4_enable(TSRMLS_D);

agent/php_execute.c

Lines changed: 173 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
#include "util_metrics.h"
2626
#include "util_number_converter.h"
2727
#include "util_strings.h"
28+
#include "util_syscalls.h"
2829
#include "util_url.h"
2930
#include "util_url.h"
3031
#include "util_metrics.h"
@@ -476,7 +477,6 @@ typedef struct _nr_library_table_t {
476477
const char* library_name;
477478
const char* file_to_check;
478479
size_t file_to_check_len;
479-
nr_composer_special_fn_t special;
480480
nr_library_enable_fn_t enable;
481481
} nr_library_table_t;
482482

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

491491
/* Doctrine < 2.18 */
492-
{"Doctrine 2", NR_PSTR("doctrine/orm/query.php"), 0, nr_doctrine2_enable},
492+
{"Doctrine 2", NR_PSTR("doctrine/orm/query.php"), nr_doctrine2_enable},
493493
/* Doctrine 2.18 reworked the directory structure */
494-
{"Doctrine 2", NR_PSTR("doctrine/orm/src/query.php"), 0, nr_doctrine2_enable},
494+
{"Doctrine 2", NR_PSTR("doctrine/orm/src/query.php"), nr_doctrine2_enable},
495495

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

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

502502
/*
503503
* The first path is for Composer installs, the second is for
504504
* /usr/local/bin.
505505
*/
506-
{"PHPUnit", NR_PSTR("phpunit/src/framework/test.php"), 0, nr_phpunit_enable},
507-
{"PHPUnit", NR_PSTR("phpunit/framework/test.php"), 0, nr_phpunit_enable},
506+
{"PHPUnit", NR_PSTR("phpunit/src/framework/test.php"), nr_phpunit_enable},
507+
{"PHPUnit", NR_PSTR("phpunit/framework/test.php"), nr_phpunit_enable},
508508

509-
{"Predis", NR_PSTR("predis/src/client.php"), 0, nr_predis_enable},
510-
{"Predis", NR_PSTR("predis/client.php"), 0, nr_predis_enable},
509+
{"Predis", NR_PSTR("predis/src/client.php"), nr_predis_enable},
510+
{"Predis", NR_PSTR("predis/client.php"), nr_predis_enable},
511+
{"PHP_Package", NR_PSTR("vendor/autoload.php"), nr_composer_detected},
511512

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

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

527528
/*
528529
* Other frameworks, detected only, but not specifically
529530
* instrumented. We detect these as libraries so that we don't prevent
530531
* detection of a supported framework or library later (since a transaction
531532
* can only have one framework).
532533
*/
533-
{"Aura1", NR_PSTR("aura/framework/system.php"), 0, NULL},
534-
{"Aura2", NR_PSTR("aura/di/src/containerinterface.php"), 0, NULL},
535-
{"Aura3", NR_PSTR("aura/di/src/containerconfiginterface.php"), 0, NULL},
536-
{"CakePHP3", NR_PSTR("cakephp/src/core/functions.php"), 0, NULL},
537-
{"Fuel", NR_PSTR("fuel/core/classes/fuel.php"), 0, NULL},
538-
{"Lithium", NR_PSTR("lithium/core/libraries.php"), 0, NULL},
539-
{"Phpbb", NR_PSTR("phpbb/request/request.php"), 0, NULL},
540-
{"Phpixie2", NR_PSTR("phpixie/core/classes/phpixie/pixie.php"), 0, NULL},
541-
{"Phpixie3", NR_PSTR("phpixie/framework.php"), 0, NULL},
542-
{"React", NR_PSTR("react/event-loop/src/loopinterface.php"), 0, NULL},
543-
{"SilverStripe", NR_PSTR("injector/silverstripeinjectioncreator.php"), 0, NULL},
544-
{"SilverStripe4", NR_PSTR("silverstripeserviceconfigurationlocator.php"), 0, NULL},
545-
{"Typo3", NR_PSTR("classes/typo3/flow/core/bootstrap.php"), 0, NULL},
546-
{"Typo3", NR_PSTR("typo3/sysext/core/classes/core/bootstrap.php"), 0, NULL},
534+
{"Aura1", NR_PSTR("aura/framework/system.php"), NULL},
535+
{"Aura2", NR_PSTR("aura/di/src/containerinterface.php"), NULL},
536+
{"Aura3", NR_PSTR("aura/di/src/containerconfiginterface.php"), NULL},
537+
{"CakePHP3", NR_PSTR("cakephp/src/core/functions.php"), NULL},
538+
{"Fuel", NR_PSTR("fuel/core/classes/fuel.php"), NULL},
539+
{"Lithium", NR_PSTR("lithium/core/libraries.php"), NULL},
540+
{"Phpbb", NR_PSTR("phpbb/request/request.php"), NULL},
541+
{"Phpixie2", NR_PSTR("phpixie/core/classes/phpixie/pixie.php"), NULL},
542+
{"Phpixie3", NR_PSTR("phpixie/framework.php"), NULL},
543+
{"React", NR_PSTR("react/event-loop/src/loopinterface.php"), NULL},
544+
{"SilverStripe", NR_PSTR("injector/silverstripeinjectioncreator.php"), NULL},
545+
{"SilverStripe4", NR_PSTR("silverstripeserviceconfigurationlocator.php"), NULL},
546+
{"Typo3", NR_PSTR("classes/typo3/flow/core/bootstrap.php"), NULL},
547+
{"Typo3", NR_PSTR("typo3/sysext/core/classes/core/bootstrap.php"), NULL},
547548

548549
/*
549550
* Other CMS (content management systems), detected only, but
550551
* not specifically instrumented.
551552
*/
552-
{"Moodle", NR_PSTR("moodlelib.php"), 0, NULL},
553+
{"Moodle", NR_PSTR("moodlelib.php"), NULL},
553554
/*
554555
* It is likely that this will never be found, since the CodeIgniter.php
555556
* will get loaded first, and as such mark this transaction as belonging to
556557
* CodeIgniter, and not Expession Engine.
557558
*/
558-
{"ExpressionEngine", NR_PSTR("system/expressionengine/config/config.php"), 0, NULL},
559+
{"ExpressionEngine", NR_PSTR("system/expressionengine/config/config.php"), NULL},
559560
/*
560561
* ExpressionEngine 5, however, has a very obvious file we can look for.
561562
*/
562-
{"ExpressionEngine5", NR_PSTR("expressionengine/boot/boot.php"), 0, NULL},
563+
{"ExpressionEngine5", NR_PSTR("expressionengine/boot/boot.php"), NULL},
563564
/*
564565
* DokuWiki uses doku.php as an entry point, but has other files that are
565566
* loaded directly that this won't pick up. That's probably OK for
566567
* supportability metrics, but we'll add the most common name for the
567568
* configuration file as well just in case.
568569
*/
569-
{"DokuWiki", NR_PSTR("doku.php"), 0, NULL},
570-
{"DokuWiki", NR_PSTR("conf/dokuwiki.php"), 0, NULL},
570+
{"DokuWiki", NR_PSTR("doku.php"), NULL},
571+
{"DokuWiki", NR_PSTR("conf/dokuwiki.php"), NULL},
571572

572573
/*
573574
* SugarCRM no longer has a community edition, so this likely only works
574575
* with older versions.
575576
*/
576-
{"SugarCRM", NR_PSTR("sugarobjects/sugarconfig.php"), 0, NULL},
577+
{"SugarCRM", NR_PSTR("sugarobjects/sugarconfig.php"), NULL},
577578

578-
{"Xoops", NR_PSTR("class/xoopsload.php"), 0, NULL},
579-
{"E107", NR_PSTR("e107_handlers/e107_class.php"), 0, NULL},
579+
{"Xoops", NR_PSTR("class/xoopsload.php"), NULL},
580+
{"E107", NR_PSTR("e107_handlers/e107_class.php"), NULL},
580581
};
581582
// clang-format: on
582583

@@ -585,15 +586,15 @@ static size_t num_libraries = sizeof(libraries) / sizeof(nr_library_table_t);
585586
// clang-format: off
586587
static nr_library_table_t logging_frameworks[] = {
587588
/* Monolog - Logging for PHP */
588-
{"Monolog", NR_PSTR("monolog/logger.php"), 0, nr_monolog_enable},
589+
{"Monolog", NR_PSTR("monolog/logger.php"), nr_monolog_enable},
589590
/* Consolidation/Log - Logging for PHP */
590-
{"Consolidation/Log", NR_PSTR("consolidation/log/src/logger.php"), 0, NULL},
591+
{"Consolidation/Log", NR_PSTR("consolidation/log/src/logger.php"), NULL},
591592
/* laminas-log - Logging for PHP */
592-
{"laminas-log", NR_PSTR("laminas-log/src/logger.php"), 0, NULL},
593+
{"laminas-log", NR_PSTR("laminas-log/src/logger.php"), NULL},
593594
/* cakephp-log - Logging for PHP */
594-
{"cakephp-log", NR_PSTR("cakephp/log/log.php"), 0, NULL},
595+
{"cakephp-log", NR_PSTR("cakephp/log/log.php"), NULL},
595596
/* Analog - Logging for PHP */
596-
{"Analog", NR_PSTR("analog/analog.php"), 0, NULL},
597+
{"Analog", NR_PSTR("analog/analog.php"), NULL},
597598
};
598599
// clang-format: on
599600

@@ -932,24 +933,137 @@ static void nr_execute_handle_library(const char* filename,
932933
}
933934
}
934935

935-
static void nr_execute_handle_composer(const char* filename,
936-
const size_t filename_len TSRMLS_DC) {
937-
size_t i;
936+
void nr_composer_detected() {
937+
if (NULL != NRPRG(txn) && !NRPRG(txn)->detection_status.composer_detected) {
938+
const char* magic_file_1
939+
= "vendor/composer/autoload_real.php";
940+
const char* composer_file = "vendor/composer/installed.php";
941+
// the below should be using the absolute path
942+
if (0 == nr_access(magic_file_1, F_OK | R_OK)) {
943+
if (0 == nr_access(composer_file, F_OK | R_OK)) {
944+
NRPRG(txn)->detection_status.composer_detected = 1;
945+
NRPRG(txn)->detection_status.file_exists = 1;
946+
NRPRG(txn)->detection_status.api_called = 0;
947+
NRPRG(txn)->detection_status.inside_eval_string = 0;
948+
}
949+
}
950+
}
951+
}
938952

939-
for (i = 0; i < num_libraries; i++) {
940-
if (nr_striendswith(STR_AND_LEN(filename),
941-
STR_AND_LEN(libraries[i].file_to_check))) {
942-
if (libraries[i].special) {
943-
nr_composer_classification_t special
944-
= libraries[i].special(filename TSRMLS_CC);
945-
if (FILE_EXISTS == special) {
946-
nr_fw_support_add_library_supportability_metric(
947-
NRPRG(txn), libraries[i].library_name);
948-
if (NULL != libraries[i].enable) {
949-
libraries[i].enable(TSRMLS_C);
953+
static void nr_get_composer_package_information() {
954+
zval retval;
955+
int result = -1;
956+
957+
if (NRPRG(txn)->detection_status.inside_eval_string) {
958+
return;
959+
}
960+
961+
char* check
962+
= ""
963+
"(function() {"
964+
" try {"
965+
" if (class_exists('Composer\\InstalledVersions')) {"
966+
" if (method_exists('Composer\\InstalledVersions', "
967+
" 'getInstalledPackages') && method_exists('Composer\\InstalledVersions', "
968+
" 'getVersion')) {"
969+
" return true;"
970+
" } else {"
971+
" return false;"
972+
" }"
973+
" } else {"
974+
" return false;"
975+
" }"
976+
" } catch (Exception $e) {"
977+
" return NULL;"
978+
" }"
979+
"})();";
980+
981+
char* getpackagename =
982+
""
983+
"(function() {"
984+
" try {"
985+
" return Composer\\InstalledVersions::getInstalledPackages();"
986+
" } catch (Exception $e) {"
987+
" return NULL;"
988+
" }"
989+
"})();";
990+
991+
char* getversion =
992+
""
993+
"(function() {"
994+
" try {"
995+
" return Composer\\InstalledVersions::getVersion(\"%s\");"
996+
" } catch (Exception $e) {"
997+
" return NULL;"
998+
" }"
999+
"})();";
1000+
1001+
NRPRG(txn)->detection_status.inside_eval_string = 1;
1002+
result = zend_eval_string(check, &retval, "check if class and methods exist" TSRMLS_CC);
1003+
NRPRG(txn)->detection_status.inside_eval_string = 0;
1004+
1005+
if (result == SUCCESS) {
1006+
if (Z_TYPE(retval) == IS_NULL) {
1007+
if (NULL != NRPRG(txn)) {
1008+
NRPRG(txn)->detection_status.api_called = 1;
1009+
}
1010+
nrl_verbosedebug(NRL_TXN, "IS_NULL");
1011+
return;
1012+
} else if (Z_TYPE(retval) == IS_FALSE) {
1013+
nrl_verbosedebug(NRL_TXN, "IS_FALSE");
1014+
return;
1015+
} else if (Z_TYPE(retval) != IS_TRUE) {
1016+
nrl_verbosedebug(NRL_TXN, "NOT_TRUE");
1017+
return;
1018+
}
1019+
}
1020+
zval_dtor(&retval);
1021+
NRPRG(txn)->detection_status.inside_eval_string = 1;
1022+
result = zend_eval_string(getpackagename, &retval,
1023+
"get installed packages by name" TSRMLS_CC);
1024+
NRPRG(txn)->detection_status.inside_eval_string = 0;
1025+
if (result == SUCCESS) {
1026+
if (Z_TYPE(retval) == IS_ARRAY) {
1027+
zval* value;
1028+
char* buf;
1029+
int result2;
1030+
zval retval2;
1031+
char* version = NULL;
1032+
(void)version;
1033+
ZEND_HASH_FOREACH_VAL(Z_ARRVAL(retval), value) {
1034+
if (Z_TYPE_P(value) == IS_STRING) {
1035+
buf = nr_formatf(getversion, Z_STRVAL_P(value));
1036+
NRPRG(txn)->detection_status.inside_eval_string = 1;
1037+
result2 = zend_eval_string(buf, &retval2, "retrieve version for packages");
1038+
NRPRG(txn)->detection_status.inside_eval_string = 0;
1039+
nr_free(buf);
1040+
if (SUCCESS == result2) {
1041+
if (nr_php_is_zval_valid_string(&retval2)) {
1042+
version = Z_STRVAL(retval2);
1043+
}
9501044
}
9511045
}
1046+
zval_dtor(&retval2);
1047+
nrl_verbosedebug(NRL_TXN, "package %s, version %s",
1048+
NRSAFESTR(Z_STRVAL_P(value)), NRSAFESTR(version));
9521049
}
1050+
ZEND_HASH_FOREACH_END();
1051+
} else {
1052+
if (NULL != NRPRG(txn)) {
1053+
NRPRG(txn)->detection_status.api_called = 1;
1054+
}
1055+
zval_dtor(&retval);
1056+
return;
1057+
}
1058+
zval_dtor(&retval);
1059+
NRPRG(txn)->detection_status.api_called = 1;
1060+
}
1061+
}
1062+
1063+
static void nr_execute_handle_composer() {
1064+
if (NRPRG(txn)->detection_status.file_exists) {
1065+
if (!NRPRG(txn)->detection_status.api_called) {
1066+
nr_get_composer_package_information();
9531067
}
9541068
}
9551069
}
@@ -1060,6 +1174,10 @@ static void nr_php_execute_file(const zend_op_array* op_array,
10601174
}
10611175

10621176
nr_php_add_user_instrumentation(TSRMLS_C);
1177+
1178+
if (NRPRG(txn)->detection_status.composer_detected && !NRPRG(txn)->detection_status.api_called) {
1179+
nr_execute_handle_composer(filename, filename_len);
1180+
}
10631181
}
10641182

10651183
/*

axiom/nr_txn.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,13 @@ typedef enum _nr_cpu_usage_t {
203203
NR_CPU_USAGE_COUNT = 2
204204
} nr_cpu_usage_t;
205205

206+
typedef struct _nr_composer_info_t {
207+
int composer_detected;
208+
int api_called;
209+
int file_exists;
210+
int inside_eval_string;
211+
} nr_composer_info_t;
212+
206213
/*
207214
* Possible transaction types, which go into the type bitfield in the nrtxn_t
208215
* struct.
@@ -302,6 +309,7 @@ typedef struct _nrtxn_t {
302309
nr_distributed_trace_t*
303310
distributed_trace; /* distributed tracing metadata for the transaction */
304311
nr_span_queue_t* span_queue; /* span queue when 8T is enabled */
312+
nr_composer_info_t detection_status;
305313

306314
/*
307315
* flag to indicate if one time (per transaction) logging metrics

0 commit comments

Comments
 (0)