Skip to content

Commit

Permalink
fix(agent): fix mongodb instrumentation for PHP 8.0+ (#878)
Browse files Browse the repository at this point in the history
- split mongodb instrumentation into before/after inline with OAPI
paradigm.
- remove broken integration tests in favor of Multiverse tests
  • Loading branch information
bduranleau-nr authored Jun 21, 2024
1 parent 756c1aa commit a8663c7
Show file tree
Hide file tree
Showing 10 changed files with 196 additions and 232 deletions.
168 changes: 167 additions & 1 deletion agent/lib_mongodb.c
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,8 @@ void nr_mongodb_get_host_and_port_path_or_id(zval* server,
}
}

#if ZEND_MODULE_API_NO < ZEND_8_0_X_API_NO \
|| defined OVERWRITE_ZEND_EXECUTE_DATA
NR_PHP_WRAPPER(nr_mongodb_operation) {
const char* this_klass = "MongoDB\\Operation\\Executable";
zval* collection = NULL;
Expand Down Expand Up @@ -173,7 +175,169 @@ NR_PHP_WRAPPER(nr_mongodb_operation) {
}
NR_PHP_WRAPPER_END

void nr_mongodb_enable(TSRMLS_D) {
#else

NR_PHP_WRAPPER(nr_mongodb_operation_before) {
(void)wraprec;
nr_segment_t* segment = NULL;
segment = nr_segment_start(NRPRG(txn), NULL, NULL);
if (NULL != segment) {
segment->wraprec = auto_segment->wraprec;
}
}
NR_PHP_WRAPPER_END

NR_PHP_WRAPPER(nr_mongodb_operation_after) {
const char* this_klass = "MongoDB\\Operation\\Executable";
zval* collection = NULL;
zval* database = NULL;
zval* server = NULL;
zval* this_var = NULL;
bool discard_segment = false;
nr_datastore_instance_t instance = {
.host = NULL,
.port_path_or_id = NULL,
.database_name = NULL,
};

// tell the compiler to ignore the cast from const char * to char *
// to save having to do a strdup operation
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wcast-qual"
nr_segment_datastore_params_t params = {
.collection = NULL,
.datastore = {
.type = NR_DATASTORE_MONGODB,
},
.operation = (char *)wraprec->extra,
.instance = &instance,
.callbacks = {
.backtrace = nr_php_backtrace_callback,
},
};
#pragma GCC diagnostic pop
/*
* We check for the interface all Collection operations extend, rather than
* their specific class. Not all operations have the properties we need but
* the ones we hook do (as of mongo-php-library v.1.1).
*/
this_var = nr_php_scope_get(NR_EXECUTE_ORIG_ARGS);
if (!nr_php_object_instanceof_class(this_var, this_klass)) {
nrl_verbosedebug(NRL_FRAMEWORK, "%s: operation is not %s", __func__,
this_klass);
discard_segment = true;
goto leave;
}

collection = nr_php_get_zval_object_property(this_var, "collectionName");
if (nr_php_is_zval_valid_string(collection)) {
params.collection = Z_STRVAL_P(collection);
}

database = nr_php_get_zval_object_property(this_var, "databaseName");
if (nr_php_is_zval_valid_string(database)) {
instance.database_name = Z_STRVAL_P(database);
}

server = nr_php_arg_get(1, NR_EXECUTE_ORIG_ARGS);
nr_mongodb_get_host_and_port_path_or_id(server, &instance.host,
&instance.port_path_or_id);

leave:
if (discard_segment) {
nr_segment_discard(&auto_segment);
} else {
nr_segment_datastore_end(&auto_segment, &params);
}
nr_php_arg_release(&server);
nr_php_scope_release(&this_var);
nr_free(instance.host);
nr_free(instance.port_path_or_id);
}
NR_PHP_WRAPPER_END

#endif /* OAPI */

void nr_mongodb_enable() {
#if ZEND_MODULE_API_NO >= ZEND_8_0_X_API_NO \
&& !defined OVERWRITE_ZEND_EXECUTE_DATA

nr_php_wrap_user_function_before_after_clean_extra(
NR_PSTR("MongoDB\\Operation\\Aggregate::execute"),
nr_mongodb_operation_before, nr_mongodb_operation_after,
nr_mongodb_operation_after, "aggregate");

nr_php_wrap_user_function_before_after_clean_extra(
NR_PSTR("MongoDB\\Operation\\BulkWrite::execute"),
nr_mongodb_operation_before, nr_mongodb_operation_after,
nr_mongodb_operation_after, "bulkWrite");

nr_php_wrap_user_function_before_after_clean_extra(
NR_PSTR("MongoDB\\Operation\\Count::execute"),
nr_mongodb_operation_before, nr_mongodb_operation_after,
nr_mongodb_operation_after, "count");

nr_php_wrap_user_function_before_after_clean_extra(
NR_PSTR("MongoDB\\Operation\\CreateIndexes::execute"),
nr_mongodb_operation_before, nr_mongodb_operation_after,
nr_mongodb_operation_after, "createIndexes");

nr_php_wrap_user_function_before_after_clean_extra(
NR_PSTR("MongoDB\\Operation\\Delete::execute"),
nr_mongodb_operation_before, nr_mongodb_operation_after,
nr_mongodb_operation_after, "delete");

nr_php_wrap_user_function_before_after_clean_extra(
NR_PSTR("MongoDB\\Operation\\Distinct::execute"),
nr_mongodb_operation_before, nr_mongodb_operation_after,
nr_mongodb_operation_after, "distinct");

nr_php_wrap_user_function_before_after_clean_extra(
NR_PSTR("MongoDB\\Operation\\DropCollection::execute"),
nr_mongodb_operation_before, nr_mongodb_operation_after,
nr_mongodb_operation_after, "dropCollection");

nr_php_wrap_user_function_before_after_clean_extra(
NR_PSTR("MongoDB\\Operation\\DropIndexes::execute"),
nr_mongodb_operation_before, nr_mongodb_operation_after,
nr_mongodb_operation_after, "dropIndexes");

nr_php_wrap_user_function_before_after_clean_extra(
NR_PSTR("MongoDB\\Operation\\Find::execute"), nr_mongodb_operation_before,
nr_mongodb_operation_after, nr_mongodb_operation_after, "find");

nr_php_wrap_user_function_before_after_clean_extra(
NR_PSTR("MongoDB\\Operation\\FindAndModify::execute"),
nr_mongodb_operation_before, nr_mongodb_operation_after,
nr_mongodb_operation_after, "findAndModify");

nr_php_wrap_user_function_before_after_clean_extra(
NR_PSTR("MongoDB\\Operation\\InsertMany::execute"),
nr_mongodb_operation_before, nr_mongodb_operation_after,
nr_mongodb_operation_after, "insertMany");

nr_php_wrap_user_function_before_after_clean_extra(
NR_PSTR("MongoDB\\Operation\\InsertOne::execute"),
nr_mongodb_operation_before, nr_mongodb_operation_after,
nr_mongodb_operation_after, "insertOne");

nr_php_wrap_user_function_before_after_clean_extra(
NR_PSTR("MongoDB\\Operation\\ListIndexes::execute"),
nr_mongodb_operation_before, nr_mongodb_operation_after,
nr_mongodb_operation_after, "listIndexes");

nr_php_wrap_user_function_before_after_clean_extra(
NR_PSTR("MongoDB\\Operation\\Update::execute"),
nr_mongodb_operation_before, nr_mongodb_operation_after,
nr_mongodb_operation_after, "update");

nr_php_wrap_user_function_before_after_clean_extra(
NR_PSTR("MongoDB\\Operation\\DatabaseCommand::execute"),
nr_mongodb_operation_before, nr_mongodb_operation_after,
nr_mongodb_operation_after, "databaseCommand");

#else /* Non-OAPI */

/*
* We instrument interesting methods on the MongoDB\Collection class via their
* associated MongoDB\Operation classes.
Expand Down Expand Up @@ -265,6 +429,8 @@ void nr_mongodb_enable(TSRMLS_D) {
NR_PSTR("MongoDB\\Operation\\DatabaseCommand::execute"),
nr_mongodb_operation, "databaseCommand" TSRMLS_CC);

#endif /* OAPI */

if (NRINI(vulnerability_management_package_detection_enabled)) {
nr_txn_add_php_package(NRPRG(txn), "mongodb/mongodb",
PHP_PACKAGE_VERSION_UNKNOWN);
Expand Down
21 changes: 21 additions & 0 deletions agent/php_wrapper.c
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,27 @@ nruserfn_t* nr_php_wrap_user_function_before_after_clean(
return wraprec;
}

nruserfn_t* nr_php_wrap_user_function_before_after_clean_extra(
const char* name,
size_t namelen,
nrspecialfn_t before_callback,
nrspecialfn_t after_callback,
nrspecialfn_t clean_callback,
const char* extra) {
nruserfn_t* wraprec = nr_php_wrap_user_function_before_after_clean(
name, namelen, before_callback, after_callback, clean_callback);

if (nrunlikely(NULL == wraprec)) {
nrl_warning(NRL_INSTRUMENT, "%s: unable to wrap '%s'", __func__,
NRSAFESTR(name));
return wraprec;
}

wraprec->extra = extra;

return wraprec;
}

nruserfn_t* nr_php_wrap_callable_before_after_clean(
zend_function* callable,
nrspecialfn_t before_callback,
Expand Down
8 changes: 8 additions & 0 deletions agent/php_wrapper.h
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,14 @@ extern nruserfn_t* nr_php_wrap_callable_before_after_clean(
nrspecialfn_t before_callback,
nrspecialfn_t after_callback,
nrspecialfn_t clean_callback);

extern nruserfn_t* nr_php_wrap_user_function_before_after_clean_extra(
const char* name,
size_t namelen,
nrspecialfn_t before_callback,
nrspecialfn_t after_callback,
nrspecialfn_t clean_callback,
const char* extra);
#endif
extern nruserfn_t* nr_php_wrap_user_function(const char* name,
size_t namelen,
Expand Down
9 changes: 0 additions & 9 deletions docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,6 @@ services:
ports:
- "11211:11211"
container_name: memcached
# mongodb:
# image: mongo:latest
# restart: always
# ports:
# - "27019:27019"
postgres:
image: postgres
restart: always
Expand All @@ -57,8 +52,6 @@ services:
environment:
MEMCACHE_HOST: memcached

# MONGO_HOST: mongodb

MYSQL_DB: database
MYSQL_USER: admin
MYSQL_PASSWD: admin
Expand Down Expand Up @@ -86,8 +79,6 @@ services:
environment:
MEMCACHE_HOST: memcached

# MONGO_HOST: mongodb

MYSQL_DB: database
MYSQL_USER: admin
MYSQL_PASSWD: admin
Expand Down
8 changes: 0 additions & 8 deletions tests/include/config.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,6 @@ function isset_or($check, $alternate = NULL)
$MYSQL_SERVER = $MYSQL_HOST . ":" . $MYSQL_PORT;
}

if (class_exists('MongoClient')) {
$MONGO_HOST = isset_or('MONGO_HOST', MongoClient::DEFAULT_HOST);
$MONGO_PORT = isset_or('MONGO_PORT', MongoClient::DEFAULT_PORT);
} else {
$MONGO_HOST = null;
$MONGO_PORT = null;
}

$MEMCACHE_HOST = isset_or('MEMCACHE_HOST', '127.0.0.1');
$MEMCACHE_PORT = isset_or('MEMCACHE_PORT', '11211');

Expand Down
14 changes: 0 additions & 14 deletions tests/integration/mongo/mongo.inc

This file was deleted.

18 changes: 0 additions & 18 deletions tests/integration/mongo/skipif.inc

This file was deleted.

55 changes: 0 additions & 55 deletions tests/integration/mongo/test_execute.php

This file was deleted.

Loading

0 comments on commit a8663c7

Please sign in to comment.