Skip to content

Commit

Permalink
Merge pull request #801 from WordPress/add/output-buffering-checkbox
Browse files Browse the repository at this point in the history
Add output buffering checkbox to Server-Timing screen
  • Loading branch information
felixarntz committed Aug 18, 2023
2 parents 16033f8 + 381590c commit 57ca48a
Show file tree
Hide file tree
Showing 6 changed files with 192 additions and 35 deletions.
131 changes: 115 additions & 16 deletions admin/server-timing.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,34 @@ function perflab_add_server_timing_page() {
* @since n.e.x.t
*/
function perflab_load_server_timing_page() {
/*
* This settings section technically includes a field, however it is directly rendered as part of the section
* callback due to requiring custom markup.
*/
add_settings_section(
'output-buffering',
__( 'Output Buffering', 'performance-lab' ),
'perflab_render_server_timing_page_output_buffering_section',
PERFLAB_SERVER_TIMING_SCREEN
);

// Minor style tweaks to improve appearance similar to other core settings screen instances.
add_action(
'admin_print_styles',
static function () {
?>
<style>
.wrap p {
max-width: 800px;
}
.wrap .form-table .td-full {
padding-top: 0;
}
</style>
<?php
}
);

add_settings_section(
'benchmarking',
__( 'Benchmarking', 'performance-lab' ),
Expand All @@ -58,28 +86,31 @@ static function() {
),
array( 'code' => array() )
);
?>
<br>
<?php
echo ' ';
echo wp_kses(
__( 'For any hook name provided, the <strong>cumulative duration between all callbacks</strong> attached to the hook is measured, in milliseconds.', 'performance-lab' ),
array( 'strong' => array() )
);
if ( ! perflab_server_timing_use_output_buffer() ) {
?>
<br>
?>
</p>
<?php if ( ! perflab_server_timing_use_output_buffer() ) : ?>
<p>
<?php
echo wp_kses(
sprintf(
/* translators: 1: Server-Timing, 2: template_include */
__( 'Since the %1$s header is sent before the template is loaded, only hooks before the %2$s filter can be measured.', 'performance-lab' ),
/* translators: 1: Server-Timing, 2: template_include, 3: anchor link */
__( 'Since the %1$s header is sent before the template is loaded, only hooks before the %2$s filter can be measured. Enable <a href="%3$s">Output Buffering</a> to measure hooks during template rendering.', 'performance-lab' ),
'<code>Server-Timing</code>',
'<code>template_include</code>'
'<code>template_include</code>',
esc_url( '#server_timing_output_buffering' )
),
array( 'code' => array() )
array(
'code' => array(),
'a' => array( 'href' => true ),
)
);
}
?>
?>
<?php endif; ?>
</p>
<?php
},
Expand All @@ -95,7 +126,7 @@ static function() {
'benchmarking_actions',
__( 'Actions', 'performance-lab' ),
static function() {
perflab_render_server_timing_page_field( 'benchmarking_actions' );
perflab_render_server_timing_page_hooks_field( 'benchmarking_actions' );
},
PERFLAB_SERVER_TIMING_SCREEN,
'benchmarking',
Expand All @@ -105,7 +136,7 @@ static function() {
'benchmarking_filters',
__( 'Filters', 'performance-lab' ),
static function() {
perflab_render_server_timing_page_field( 'benchmarking_filters' );
perflab_render_server_timing_page_hooks_field( 'benchmarking_filters' );
},
PERFLAB_SERVER_TIMING_SCREEN,
'benchmarking',
Expand Down Expand Up @@ -136,13 +167,13 @@ function perflab_render_server_timing_page() {
}

/**
* Renders a field for the given Server-Timing option.
* Renders a hooks field for the given Server-Timing option.
*
* @since n.e.x.t
*
* @param string $slug Slug of the field and sub-key in the Server-Timing option.
*/
function perflab_render_server_timing_page_field( $slug ) {
function perflab_render_server_timing_page_hooks_field( $slug ) {
$options = (array) get_option( PERFLAB_SERVER_TIMING_SETTING, array() );

// Value for the sub-key is an array of hook names.
Expand All @@ -168,3 +199,71 @@ class="large-text code"
</p>
<?php
}

/**
* Renders the section for enabling output buffering for Server-Timing.
*
* @since n.e.x.t
*/
function perflab_render_server_timing_page_output_buffering_section() {
$slug = 'output_buffering';
$field_id = "server_timing_{$slug}";
$field_name = PERFLAB_SERVER_TIMING_SETTING . '[' . $slug . ']';
$description_id = "{$field_id}_description";
$has_filter = has_filter( 'perflab_server_timing_use_output_buffer' );
$is_enabled = perflab_server_timing_use_output_buffer();

/*
* The hard-coded .form-table output here overall matches the WordPress core markup generated by
* `do_settings_sections()` and `do_settings_fields()`, however since it is impossible to modify the CSS classes on
* the `<td>` elements, it needs to be hard-coded to achieve the same appearance as e.g. the UI control for the
* `uploads_use_yearmonth_folders` option in the _Settings > Media_ screen, which is hard-coded as well.
*/
?>
<table class="form-table" role="presentation">
<tr>
<td class="td-full">
<label for="<?php echo esc_attr( $field_id ); ?>">
<input
type="checkbox"
id="<?php echo esc_attr( $field_id ); ?>"
name="<?php echo esc_attr( $field_name ); ?>"
aria-describedby="<?php echo esc_attr( $description_id ); ?>"
<?php disabled( $has_filter ); ?>
<?php checked( $is_enabled ); ?>
>
<?php esc_html_e( 'Enable output buffering of template rendering', 'performance-lab' ); ?>
</label>
<p id="<?php echo esc_attr( $description_id ); ?>" class="description">
<?php if ( $has_filter ) : ?>
<?php if ( $is_enabled ) : ?>
<?php
echo wp_kses(
sprintf(
/* translators: %s: perflab_server_timing_use_output_buffer */
__( 'Output buffering has been forcibly enabled via the %s filter.', 'performance-lab' ),
'<code>perflab_server_timing_use_output_buffer</code>'
),
array( 'code' => array() )
);
?>
<?php else : ?>
<?php
echo wp_kses(
sprintf(
/* translators: %s: perflab_server_timing_use_output_buffer */
__( 'Output buffering has been forcibly disabled via the %s filter.', 'performance-lab' ),
'<code>perflab_server_timing_use_output_buffer</code>'
),
array( 'code' => array() )
);
?>
<?php endif; ?>
<?php endif; ?>
<?php esc_html_e( 'Output buffering is needed to capture metrics after headers have been sent and while the template is being rendered. Note that output buffering may possibly cause an increase in TTFB if the response would be flushed multiple times.', 'performance-lab' ); ?>
</p>
</td>
</tr>
</table>
<?php
}
5 changes: 4 additions & 1 deletion server-timing/class-perflab-server-timing.php
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,9 @@ static function( $value ) {
* @return bool True if an output buffer should be used, false otherwise.
*/
public function use_output_buffer() {
$options = (array) get_option( PERFLAB_SERVER_TIMING_SETTING, array() );
$enabled = ! empty( $options['output_buffering'] );

/**
* Filters whether an output buffer should be used to be able to gather additional Server-Timing metrics.
*
Expand All @@ -206,7 +209,7 @@ public function use_output_buffer() {
*
* @param bool $use_output_buffer Whether to use an output buffer.
*/
return apply_filters( 'perflab_server_timing_use_output_buffer', false );
return (bool) apply_filters( 'perflab_server_timing_use_output_buffer', $enabled );
}

/**
Expand Down
5 changes: 4 additions & 1 deletion server-timing/load.php
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ function perflab_sanitize_server_timing_setting( $value ) {
static $allowed_keys = array(
'benchmarking_actions' => true,
'benchmarking_filters' => true,
'output_buffering' => true,
);

if ( ! is_array( $value ) ) {
Expand All @@ -175,7 +176,7 @@ function perflab_sanitize_server_timing_setting( $value ) {
* Ensure that every element is an indexed array of hook names.
* Any duplicates across a group of hooks are removed.
*/
foreach ( $value as $key => $hooks ) {
foreach ( wp_array_slice_assoc( $value, array( 'benchmarking_actions', 'benchmarking_filters' ) ) as $key => $hooks ) {
if ( ! is_array( $hooks ) ) {
$hooks = explode( "\n", $hooks );
}
Expand Down Expand Up @@ -203,5 +204,7 @@ static function( $hookname ) {
);
}

$value['output_buffering'] = ! empty( $value['output_buffering'] );

return $value;
}
8 changes: 4 additions & 4 deletions tests/admin/server-timing-tests.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ public function test_perflab_load_server_timing_page() {
perflab_load_server_timing_page();
$this->assertArrayHasKey( PERFLAB_SERVER_TIMING_SCREEN, $wp_settings_sections );
$this->assertEqualSets(
array( 'benchmarking' ),
array( 'output-buffering', 'benchmarking' ),
array_keys( $wp_settings_sections[ PERFLAB_SERVER_TIMING_SCREEN ] )
);
$this->assertEqualSets(
Expand All @@ -75,7 +75,7 @@ public function test_perflab_render_server_timing_page_field() {
$slug = 'benchmarking_actions';

ob_start();
perflab_render_server_timing_page_field( $slug );
perflab_render_server_timing_page_hooks_field( $slug );
$output = ob_get_clean();

$this->assertStringContainsString( '<textarea', $output );
Expand All @@ -87,7 +87,7 @@ public function test_perflab_render_server_timing_page_field_empty_option() {
delete_option( PERFLAB_SERVER_TIMING_SETTING );

ob_start();
perflab_render_server_timing_page_field( 'benchmarking_actions' );
perflab_render_server_timing_page_hooks_field( 'benchmarking_actions' );
$output = ob_get_clean();

$this->assertStringContainsString( '></textarea>', $output );
Expand All @@ -100,7 +100,7 @@ public function test_perflab_render_server_timing_page_field_populated_option()
);

ob_start();
perflab_render_server_timing_page_field( 'benchmarking_actions' );
perflab_render_server_timing_page_hooks_field( 'benchmarking_actions' );
$output = ob_get_clean();

// Array is formatted/imploded as strings, one per line.
Expand Down
22 changes: 13 additions & 9 deletions tests/server-timing/load-tests.php
Original file line number Diff line number Diff line change
Expand Up @@ -103,39 +103,43 @@ public function data_perflab_sanitize_server_timing_setting() {
),
'empty list, array' => array(
array( 'benchmarking_actions' => array() ),
array( 'benchmarking_actions' => array() ),
array( 'benchmarking_actions' => array(), 'output_buffering' => false ),
),
'empty list, string' => array(
array( 'benchmarking_actions' => '' ),
array( 'benchmarking_actions' => array() ),
array( 'benchmarking_actions' => array(), 'output_buffering' => false ),
),
'empty list, string with whitespace' => array(
array( 'benchmarking_actions' => ' ' ),
array( 'benchmarking_actions' => array() ),
array( 'benchmarking_actions' => array(), 'output_buffering' => false ),
),
'regular list, array' => array(
array( 'benchmarking_actions' => array( 'after_setup_theme', 'init', 'wp_loaded' ) ),
array( 'benchmarking_actions' => array( 'after_setup_theme', 'init', 'wp_loaded' ) ),
array( 'benchmarking_actions' => array( 'after_setup_theme', 'init', 'wp_loaded' ), 'output_buffering' => false ),
),
'regular list, string' => array(
array( 'benchmarking_actions' => "after_setup_theme\ninit\nwp_loaded" ),
array( 'benchmarking_actions' => array( 'after_setup_theme', 'init', 'wp_loaded' ) ),
array( 'benchmarking_actions' => array( 'after_setup_theme', 'init', 'wp_loaded' ), 'output_buffering' => false ),
),
'regular list, string with whitespace' => array(
array( 'benchmarking_actions' => "after_setup_ theme \ninit \n\nwp_loaded\n" ),
array( 'benchmarking_actions' => array( 'after_setup_theme', 'init', 'wp_loaded' ) ),
array( 'benchmarking_actions' => array( 'after_setup_theme', 'init', 'wp_loaded' ), 'output_buffering' => false ),
),
'regular list, array with duplicates' => array(
array( 'benchmarking_actions' => array( 'after_setup_theme', 'init', 'wp_loaded', 'init' ) ),
array( 'benchmarking_actions' => array( 'after_setup_theme', 'init', 'wp_loaded' ) ),
array( 'benchmarking_actions' => array( 'after_setup_theme', 'init', 'wp_loaded' ), 'output_buffering' => false ),
),
'regular list, array with special hook chars' => array(
array( 'benchmarking_actions' => array( 'namespace/hookname', 'namespace.hookname' ) ),
array( 'benchmarking_actions' => array( 'namespace/hookname', 'namespace.hookname' ) ),
array( 'benchmarking_actions' => array( 'namespace/hookname', 'namespace.hookname' ), 'output_buffering' => false ),
),
'output buffering enabled' => array(
array( 'output_buffering' => 'on' ),
array( 'output_buffering' => true ),
),
'regular list, disallowed key' => array(
array( 'not_allowed' => array( 'after_setup_theme', 'init', 'wp_loaded' ) ),
array(),
array( 'output_buffering' => false ),
),
);
}
Expand Down
56 changes: 52 additions & 4 deletions tests/server-timing/perflab-server-timing-tests.php
Original file line number Diff line number Diff line change
Expand Up @@ -187,10 +187,58 @@ public function data_get_header() {
);
}

public function test_use_output_buffer() {
$this->assertFalse( $this->server_timing->use_output_buffer() );
public function get_data_to_test_use_output_buffer() {
$enable_option = static function () {
$option = (array) get_option( PERFLAB_SERVER_TIMING_SETTING );
$option['output_buffering'] = true;
update_option( PERFLAB_SERVER_TIMING_SETTING, $option );
};
$disable_option = static function () {
$option = (array) get_option( PERFLAB_SERVER_TIMING_SETTING );
$option['output_buffering'] = false;
update_option( PERFLAB_SERVER_TIMING_SETTING, $option );
};

add_filter( 'perflab_server_timing_use_output_buffer', '__return_true' );
$this->assertTrue( $this->server_timing->use_output_buffer() );
return array(
'default' => array(
'set_up' => static function () {},
'expected' => false,
),
'option-enabled' => array(
'set_up' => $enable_option,
'expected' => true,
),
'option-disabled' => array(
'set_up' => $disable_option,
'expected' => false,
),
'filter-enabled' => array(
'set_up' => static function () use ( $disable_option ) {
$disable_option();
add_filter( 'perflab_server_timing_use_output_buffer', '__return_true' );
},
'expected' => true,
),
'filter-disabled' => array(
'set_up' => static function () use ( $enable_option ) {
$enable_option();
add_filter( 'perflab_server_timing_use_output_buffer', '__return_false' );
},
'expected' => false,
),
);
}

/**
* @covers Perflab_Server_Timing::use_output_buffer
*
* @dataProvider get_data_to_test_use_output_buffer
*
* @param callable $set_up Set up.
* @param bool $expected Expected value.
*/
public function test_use_output_buffer( callable $set_up, $expected ) {
$set_up();
$this->assertSame( $expected, $this->server_timing->use_output_buffer() );
}
}

0 comments on commit 57ca48a

Please sign in to comment.