New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement Server-Timing API foundation as well as basic load time metrics #553
Conversation
…c for WordPress execution prior to serving the template.
…emplate execution time if it is enabled.
In 51c4222, I have revamped the API to simplify its usage quite a bit. I've updated the PR description accordingly and created a new Gist with updated examples (https://gist.github.com/felixarntz/43fb19e6491a1bc65a7e930dace17c47). For reference, I've kept the original example Gist (https://gist.github.com/felixarntz/c343a33242b6527ad3f446c8f7f4fec2). By comparing the two, you will see that the new API usage is much simplified. Most importantly the new approach has the following benefits:
|
@johnbillion @tillkruss @jjgrainger I'd like to get your feedback on this. I'm still keeping it as a draft PR until I have added test coverage, but at this point I think the implementation would be suitable for the Performance Lab plugin. Some high-level considerations:
For the latter, it would be a hack, but I'm thinking potentially we could place e.g. |
The |
Can you give me a gist how an Object Cache implementation would supply the Server Timing API with let's say a single metric "total time waited for Datastore", similar to "total time waited for SQL"? |
@tillkruss For the actual measurement, you would probably need to add support for that in the You could then register the data like this: add_action(
'perflab_server_timing_send_header',
function() {
perflab_server_timing_register_metric(
'object-cache-lookups',
array(
'measure_callback' => function( $metric ) {
// The function here should return the time in milliseconds.
$total_cache_time = demo_function_that_returns_total_cache_time();
$metric->set_value( $total_cache_time );
},
'access_cap' => 'exist',
)
);
}
); The hook is called as late as possible (either before serving the HTML template or on shutdown, depending on whether the feature is configured with an output buffer or not for the site), so all lookups should be calculated by then at least if an output buffer is used. For sites without an output buffer (see |
@felixarntz: This is great 👍 |
… for additional key measurements.
@tillkruss @johnbillion In 096bb87, I've added logic to place an I will work on tests within the next 2 weeks and get this ready for a full code review. |
…me, since both of them are commonly used.
…ormance into add/551-server-timing-api
…e.php drop-in is present.
@adamsilverstein @eugene-manuilov Addressed all your feedback either with new commits or comment replies. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good to me!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, @felixarntz for all your answers. looks good to me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM. Great work, @felixarntz!
// Otherwise, call the measuring callback and pass the metric instance to it. | ||
call_user_func( $args['measure_callback'], $this->registered_metrics[ $metric_slug ] ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm curious why this method doesn't rather return
the $this->registered_metrics[ $metric_slug ]
value here? Why is measure_callback
needed?
Then instead of:
performance/server-timing/load.php
Lines 105 to 115 in 0e10d5a
if ( ! perflab_server_timing()->has_registered_metric( $metric_slug ) ) { | |
perflab_server_timing_register_metric( | |
$metric_slug, | |
array( | |
'measure_callback' => static function ( $metric ) use ( &$server_timing_metric ) { | |
$server_timing_metric = $metric; | |
}, | |
'access_cap' => $access_cap, | |
) | |
); | |
} |
It could be simplified to:
if ( ! perflab_server_timing()->has_registered_metric( $metric_slug ) ) {
$server_timing_metric = perflab_server_timing_register_metric(
$metric_slug,
array(
'access_cap' => $access_cap,
)
);
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@westonruter I think that scenario is an edge-case, which is only needed because of the way this function is a wrapper to an action callback.
Overall, I consider that a discouraged pattern. Passing a measurement callback allows for that callback to only be called on other conditions (e.g. depending on the "access cap"). If the metric was returned, it would encourage implementing the measurement logic there, which then would be decoupled from those conditions for whether the metric should be measured in the first place. Alternatively, we could return the metric only if the conditions are satisfied, but that would be a strange pattern IMO, as not returning it wouldn't mean that the metric wasn't registered correctly - whereas the return value of a "register" function would be expected to only return null
or false
if the registration itself failed.
Summary
Fixes #551
Relevant technical choices
Perflab_Server_Timing
class to control the Server-Timing header.Perflab_Server_Timing_Metric
class to represent a single metric, so that measuring callbacks can set a value on it without having access to the full API.perflab_server_timing_register_metric()
function which can be used to register a Server-Timing metric. This function can be called at any point as long as it is before or during theperflab_server_timing_send_header
action.perflab_server_timing_use_output_buffer()
function and filter of the same name. By default output buffering is disabled, but it can be enabled to record additional measurements that occur while rendering the template.perflab_server_timing()
function which provides access to the API instance, e.g. to use it for lower-level functions, which are typically not necessary due to the wrapper functions mentioned above.wp-before-template
measures how long WordPress execution time takes prior to serving the HTML templatewp-before-template-db-queries
measures total execution of database queries prior to serving the HTML template (only ifSAVEQUERIES
constant is enabled)wp-template
measures the overall HTML template execution (only if using an output buffer)wp-template-db-queries
measures total execution of database queries during HTML template execution (only if using an output buffer and ifSAVEQUERIES
constant is enabled)wp-load-alloptions-query
measures execution of the alloptions database query (only if the Performance Labobject-cache.php
drop in is placed, see below)perflab_wrap_server_timing()
utility function which can be used to measure execution of any callback (e.g. for an action or filter).object-cache.php
drop-in, which is used to initialize the Server-Timing API early, in order to measure events that would not be possible from within a plugin.object-cache.php
file, it will be renamed toobject-cache-plst-orig.php
and then required from the newobject-cache.php
file, so nothing will change in the behavior of any persistent object cache present.define( 'PERFLAB_DISABLE_OBJECT_CACHE_DROPIN', true );
Testing
Server-Timing header information can be inspected in JS via:
Use the following code snippet to test with output buffering enabled:
You can test the
perflab_wrap_server_timing()
function e.g. by adding a code snippet like the following:Some other example use cases to test with can be found in this Gist: https://gist.github.com/felixarntz/43fb19e6491a1bc65a7e930dace17c47
Checklist
[Focus]
orInfrastructure
label.[Type]
label.no milestone
label.