Skip to content
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

Enhance Server-Timing implementation to support new Chrome DevTools Performance Panel integration #1578

Draft
wants to merge 4 commits into
base: trunk
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,13 @@ class Perflab_Server_Timing_Metric {
private $value;

/**
* The value measured before relevant execution logic in seconds, if used.
* The value measured before relevant execution logic in milliseconds, if used.
*
* @since 1.8.0
* @var float|null
* @since n.e.x.t Renamed from $before_value to $start_value.
* @var int|float|null
*/
private $before_value;
private $start_value;

/**
* Constructor.
Expand Down Expand Up @@ -70,23 +71,32 @@ public function get_slug(): string {
* @param int|float|mixed $value The metric value to set, in milliseconds.
*/
public function set_value( $value ): void {
if ( ! is_numeric( $value ) ) {
_doing_it_wrong(
__METHOD__,
/* translators: %s: PHP parameter name */
sprintf( esc_html__( 'The %s parameter must be an integer, float, or numeric string.', 'performance-lab' ), '$value' ),
''
);
if ( ! $this->check_value( $value, __METHOD__ ) ) {
return;
}

if ( 0 !== did_action( 'perflab_server_timing_send_header' ) && ! doing_action( 'perflab_server_timing_send_header' ) ) {
_doing_it_wrong(
__METHOD__,
/* translators: %s: WordPress action name */
sprintf( esc_html__( 'The method must be called before or during the %s action.', 'performance-lab' ), 'perflab_server_timing_send_header' ),
''
);
// In case e.g. a numeric string is passed, cast it.
if ( ! is_int( $value ) && ! is_float( $value ) ) {
$value = (float) $value;
}

$this->value = $value;
}

/**
* Sets the start value for the metric.
*
* For metrics where the start value is set, this timestamp will be sent via an optional `start` parameter.
*
* Alternatively to setting the start value directly, the {@see Perflab_Server_Timing_Metric::measure_start()} and
* {@see Perflab_Server_Timing_Metric::measure_end()} methods can be used to further simplify measuring.
*
* @since n.e.x.t
*
* @param int|float|mixed $value The start value to set for the metric, in milliseconds.
*/
public function set_start_value( $value ): void {
if ( ! $this->check_value( $value, __METHOD__ ) ) {
return;
}

Expand All @@ -95,11 +105,11 @@ public function set_value( $value ): void {
$value = (float) $value;
}

$this->value = $value;
$this->start_value = $value;
}

/**
* Gets the metric value.
* Gets the metric value, if set.
*
* @since 1.8.0
*
Expand All @@ -110,36 +120,112 @@ public function get_value() {
}

/**
* Captures the current time, as a reference point to calculate the duration of a task afterward.
* Gets the metric start value, if set.
*
* @since n.e.x.t
*
* @return int|float|null The metric start value, or null if none set.
*/
public function get_start_value() {
return $this->start_value;
}

/**
* Captures the current time as metric start time, to calculate the duration of a task afterward.
*
* This should be used in combination with {@see Perflab_Server_Timing_Metric::measure_end()}. Alternatively,
* {@see Perflab_Server_Timing_Metric::set_value()} and {@see Perflab_Server_Timing_Metric::set_start_value()} can
* be used to set a calculated value manually.
*
* This should be used in combination with {@see Perflab_Server_Timing_Metric::measure_after()}. Alternatively,
* {@see Perflab_Server_Timing_Metric::set_value()} can be used to set a calculated value manually.
* @since n.e.x.t
*/
public function measure_start(): void {
$this->set_start_value( microtime( true ) * 1000.0 );
}

/**
* Captures the current time and compares it to the metric start time to calculate a task's duration.
*
* This should be used in combination with {@see Perflab_Server_Timing_Metric::measure_start()}. Alternatively,
* {@see Perflab_Server_Timing_Metric::set_value()} and {@see Perflab_Server_Timing_Metric::set_start_value()} can
* be used to set a calculated value manually.
*
* @since n.e.x.t
*/
public function measure_end(): void {
if ( null === $this->start_value ) {
_doing_it_wrong(
__METHOD__,
sprintf(
/* translators: 1: PHP method name, 2: alternative PHP method name */
esc_html__( 'The %1$s method or %2$s method must be called before.', 'performance-lab' ),
__CLASS__ . '::measure_start()',
__CLASS__ . '::set_start_value()'
),
''
);
return;
}

$this->set_value( microtime( true ) * 1000.0 - $this->start_value );
}

/**
* Captures the current time, as a reference point to calculate the duration of a task afterward.
*
* @since 1.8.0
* @deprecated n.e.x.t
* @see Perflab_Server_Timing_Metric::measure_start()
*/
public function measure_before(): void {
$this->before_value = microtime( true );
_deprecated_function( __METHOD__, 'n.e.x.t (Performance Lab)', __CLASS__ . '::measure_start()' );
$this->measure_start();
}

/**
* Captures the current time and compares it to the reference point to calculate a task's duration.
*
* This should be used in combination with {@see Perflab_Server_Timing_Metric::measure_before()}. Alternatively,
* {@see Perflab_Server_Timing_Metric::set_value()} can be used to set a calculated value manually.
*
* @since 1.8.0
* @deprecated n.e.x.t
* @see Perflab_Server_Timing_Metric::measure_end()
*/
public function measure_after(): void {
if ( null === $this->before_value ) {
_deprecated_function( __METHOD__, 'n.e.x.t (Performance Lab)', __CLASS__ . '::measure_end()' );
$this->measure_end();
}

/**
* Checks whether the passed metric value is valid and whether the timing of the method call is correct.
*
* @since n.e.x.t
*
* @param int|float|mixed $value The value to check, in milliseconds.
* @param string $method The method name originally called (typically passed via `__METHOD__`).
* @return bool True if the method call with the value is valid, false otherwise.
*/
private function check_value( $value, string $method ): bool {
if ( ! is_numeric( $value ) ) {
_doing_it_wrong(
__METHOD__,
/* translators: %s: PHP method name */
sprintf( esc_html__( 'The %s method must be called before.', 'performance-lab' ), __CLASS__ . '::measure_before()' ),
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
$method,
/* translators: %s: PHP parameter name */
sprintf( esc_html__( 'The %s parameter must be an integer, float, or numeric string.', 'performance-lab' ), '$value' ),
''
);
return;
return false;
}

if ( 0 !== did_action( 'perflab_server_timing_send_header' ) && ! doing_action( 'perflab_server_timing_send_header' ) ) {
_doing_it_wrong(
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
$method,
/* translators: %s: WordPress action name */
sprintf( esc_html__( 'The method must be called before or during the %s action.', 'performance-lab' ), 'perflab_server_timing_send_header' ),
''
);
return false;
}

$this->set_value( ( microtime( true ) - $this->before_value ) * 1000.0 );
return true;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,15 @@ public function send_header(): void {
return;
}

// Include non-standard metrics to provide timing context to the browser.
if ( $this->use_output_buffer() ) {
$start_time = round( $GLOBALS['timestart'] * 1000.0, 2 );
$end_time = round( microtime( true ) * 1000.0, 2 );

$header_value .= sprintf( ', response-start;start=%s', $start_time );
$header_value .= sprintf( ', response-end;start=%s', $end_time );
}

header( sprintf( 'Server-Timing: %s', $header_value ), false );
}

Expand Down Expand Up @@ -282,6 +291,7 @@ function ( string $output, ?int $phase ): string {
* Formats the header segment for a single metric.
*
* @since 1.8.0
* @since n.e.x.t Now includes optional `start` parameter if specified for the given metric.
*
* @param Perflab_Server_Timing_Metric $metric The metric to format.
* @return string|null Segment for the Server-Timing header, or null if no value set.
Expand All @@ -301,6 +311,16 @@ private function format_metric_header_value( Perflab_Server_Timing_Metric $metri
// See https://github.com/WordPress/performance/issues/955.
$name = preg_replace( '/[^!#$%&\'*+\-.^_`|~0-9a-zA-Z]/', '-', $metric->get_slug() );

return sprintf( 'wp-%1$s;dur=%2$s', $name, $value );
$header = sprintf( 'wp-%1$s;dur=%2$s', $name, $value );

$start_value = $metric->get_start_value();
if ( null !== $start_value ) {
if ( is_float( $start_value ) ) {
$start_value = round( $start_value, 2 );
}
$header .= sprintf( ';start=%s', $start_value );
}

return $header;
}
}
16 changes: 9 additions & 7 deletions plugins/performance-lab/includes/server-timing/defaults.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ function perflab_register_default_server_timing_before_template_metrics(): void
array(
'measure_callback' => static function ( $metric ): void {
// The 'timestart' global is set right at the beginning of WordPress execution.
$metric->set_value( ( microtime( true ) - $GLOBALS['timestart'] ) * 1000.0 );
$metric->set_start_value( $GLOBALS['timestart'] * 1000.0 );
$metric->measure_end();
},
'access_cap' => 'exist',
)
Expand Down Expand Up @@ -113,8 +114,8 @@ static function ( $passthrough = null ) {
'template',
array(
'measure_callback' => static function ( Perflab_Server_Timing_Metric $metric ): void {
$metric->measure_before();
add_action( 'perflab_server_timing_send_header', array( $metric, 'measure_after' ), PHP_INT_MAX );
$metric->measure_start();
add_action( 'perflab_server_timing_send_header', array( $metric, 'measure_end' ), PHP_INT_MAX );
},
'access_cap' => 'exist',
)
Expand All @@ -134,7 +135,8 @@ static function (): void {
array(
'measure_callback' => static function ( $metric ): void {
// The 'timestart' global is set right at the beginning of WordPress execution.
$metric->set_value( ( microtime( true ) - $GLOBALS['timestart'] ) * 1000.0 );
$metric->set_start_value( $GLOBALS['timestart'] * 1000.0 );
$metric->measure_end();
},
'access_cap' => 'exist',
)
Expand Down Expand Up @@ -242,17 +244,17 @@ static function ( $hook_name ) use ( $hooks_to_measure ): void {
}

$measure_callback = static function ( $metric ) use ( $hook_name, $hook_type ): void {
$metric->measure_before();
$metric->measure_start();

if ( 'action' === $hook_type ) {
$cb = static function () use ( $metric, $hook_name, &$cb ): void {
$metric->measure_after();
$metric->measure_end();
remove_action( $hook_name, $cb, PHP_INT_MAX );
};
add_action( $hook_name, $cb, PHP_INT_MAX );
} else {
$cb = static function ( $passthrough ) use ( $metric, $hook_name, &$cb ) {
$metric->measure_after();
$metric->measure_end();
remove_filter( $hook_name, $cb, PHP_INT_MAX );
return $passthrough;
};
Expand Down
4 changes: 2 additions & 2 deletions plugins/performance-lab/includes/server-timing/load.php
Original file line number Diff line number Diff line change
Expand Up @@ -132,13 +132,13 @@ function perflab_wrap_server_timing( callable $callback, string $metric_slug, st
}

// Measure time before the callback.
$server_timing_metric->measure_before();
$server_timing_metric->measure_start();

// Execute the callback.
$result = call_user_func_array( $callback, $callback_args );

// Measure time after the callback and calculate total.
$server_timing_metric->measure_after();
$server_timing_metric->measure_end();

// Return result (e.g. in case this is a filter callback).
return $result;
Expand Down
Loading