-
Notifications
You must be signed in to change notification settings - Fork 3
Intro pinpoint php aop
A php library that supports pinpoint-php agent:
- auto inject php built-in library, such as redis,pdo, mysqli
- auto injection on user class, as guzzlehttp, predis
what's built-in library, as for PHP
PHP comes standard with many functions and constructs. There are also functions that require specific
PHP extensions compiled in, otherwise fatal "undefined function" errors will appear. For example, to
use image functions such as imagecreatetruecolor(), PHP must be compiled with GD support. Or, to use
mysqli_connect(), PHP must be compiled with MySQLi support. There are many core functions that are
included in every version of PHP, such as the string and variable functions. A call to phpinfo()
or get_loaded_extensions() will show which extensions are loaded into PHP. Also note that many
extensions are enabled by default and that the PHP manual is split up by extension. ...
> https://www.php.net/manual/en/functions.internal.php#functions.internal
Inspired by https://www.phpinternalsbook.com/php7/extensions_design/hooks.html#overwriting-an-internal-function
PHP kernel exposes class_table that user can redirect the handler to user's function, by that we wrapper all cared functions and insert all pinpointed plugins.
- Support interceptor in build-in function.
// https://github.com/pinpoint-apm/pinpoint-c-agent/blob/9c544f139665dde3a9cee2a244a9c3be2f32bff9/src/PHP/pinpoint_php.cpp#L887
zend_function *func = (zend_function *)zend_hash_str_find_ptr(
CG(function_table), ZSTR_VAL(name), ZSTR_LEN(name));
if (func != NULL &&
func->internal_function.handler == pinpoint_interceptor_handler_entry) {
pp_trace("function `%s` interceptor already added", ZSTR_VAL(name));
} else if (func != NULL) {
pp_interceptor_v_t *interceptor =
make_interceptor(name, before, end, exception, func);
// insert into hash
if (!zend_hash_add_ptr(PPG(interceptors), name, interceptor)) {
free_interceptor(interceptor);
pp_trace("added interceptor on `function`: %s failed. reason: already "
"exist ",
ZSTR_VAL(name));
return;
}
func->internal_function.handler = pinpoint_interceptor_handler_entry;
pp_trace("added interceptor on `function`: %s success", ZSTR_VAL(name));
- Add pinpointed plugins into injection point.
// https://github.com/pinpoint-apm/pinpoint-php-aop/blob/5994253869d516c38d528a8ef784a5c1c18b20f3/lib/Pinpoint/Plugins/SysV2/_curl/curl.php#L78
pinpoint_join_cut(
["curl_close"],
function ($ch) use (&$ch_res) {
unset($ch_res[(int) $ch]);
pinpoint_start_trace();
pinpoint_add_clue(PP_INTERCEPTOR_NAME, "curl_close");
pinpoint_add_clue(PP_SERVER_TYPE, PP_PHP_METHOD);
},
function ($ret) {
pinpoint_end_trace();
},
function ($e) {
}
);
- Load all built-in plugins when needs.
// https://github.com/pinpoint-apm/pinpoint-php-aop/blob/5994253869d516c38d528a8ef784a5c1c18b20f3/lib/Pinpoint/Plugins/PinpointPerRequestPlugins.php#L126C12-L126C58
if(sampled){
require_once __DIR__ . "/SysV2/__init__.php";
}else{
require_once __DIR__ . "/SysV2/_curl/__init__.php";
}
Before that, you should know php class loader.
By registering autoloaders, PHP is given a last chance to load the class or interface before it fails with an error.
> https://www.php.net/manual/en/language.oop5.autoload.php
For php, when user use keyword use
to import a class/function, if kernel does not find that class in class map, it will call auto_loader to find and import that file. Here is what pinpoint-php-aop intercepted.
-
When the autoloader is ready, it modifies all autoloader function/class, intercepts all function/class when be loaded. When a intercepted class is imported, it will be mapped to new file which is rendered by pinpoint plugins.
-
When pinpoint autoloader find that file does not rendered by pinpoint plugins, it will generate a class map and rendered class files and insert into autoloader class map. The most important, these files will be cached in cache_dir,it reused in subsequence request which can be save lots of time.
Let's use Pinpoint\Plugins\autoload\_MongoPlugin
as an example to explain whole process.
- User new a mongo client
//https://github.com/pinpoint-apm/pinpoint-c-agent/blob/9c544f139665dde3a9cee2a244a9c3be2f32bff9/testapps/SimplePHP/run.php#L92-L93
$client = new MongoDB\Client("mongodb://$mongodb_host:27017");
- Register pinpoint plugins on method
__construct
from MongoDB\Client
//https://github.com/pinpoint-apm/pinpoint-php-aop/blob/5994253869d516c38d528a8ef784a5c1c18b20f3/lib/Pinpoint/Plugins/autoload/_MongoPlugin/__init__.php#L25
$classHandler = new AspectClassHandle(\MongoDB\Client::class);
$classHandler->addJoinPoint('__construct', MongoPlugin::class);
$cls[] = $classHandler;
- After pinpoint rendered, you can get a new class map at
/tmp/.cache/__class_index.php
default cache directory is
/tmp/
$pinpoint_class_map = array('MongoDB\\Client' => '/tmp/.cache/MongoDB/Client.php', ...);
return $pinpoint_class_map;
php rendered class
//Client.php
namespace MongoDB;
class Client{
...
// origin methods
public function __pinpoint____construct(?string $uri = null, array $uriOptions = [], array $driverOptions = [])
{
}
// rendered methods
public function __construct(?string $uri = null, array $uriOptions = [], array $driverOptions = [])
{
$_pinpoint___construct_var = new \Pinpoint\Plugins\autoload\_MongoPlugin\MongoPlugin(__METHOD__, $this, $uri, $uriOptions, $driverOptions);
try {
$_pinpoint___construct_var->onBefore();
$this->__pinpoint____construct($uri, $uriOptions, $driverOptions);
$_pinpoint___construct_var->onEnd($_pinpoint___construct_ret);
} catch (\Exception $e) {
$_pinpoint___construct_var->onException($e);
throw $e;
}
}
...
}
- When rendered class (client.php) is loaded into php kernel, that means pinpoint plugins works into your project.
i.e. User class was intercept by pinpoint-php-aop