Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
92.31% covered (success)
92.31%
24 / 26
100.00% covered (success)
100.00%
4 / 4
CRAP
100.00% covered (success)
100.00%
1 / 1
Speed_Score_Durations
100.00% covered (success)
100.00%
24 / 24
100.00% covered (success)
100.00%
4 / 4
13
100.00% covered (success)
100.00%
1 / 1
 record_finished_run
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
5
 record
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
3
 expected
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
3
 all
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2/**
3 * How long this site's recent speed score runs took.
4 *
5 * @package automattic/jetpack-boost-speed-score
6 */
7
8namespace Automattic\Jetpack\Boost_Speed_Score;
9
10if ( ! defined( 'ABSPATH' ) ) {
11    exit( 0 );
12}
13
14/**
15 * Class Speed_Score_Durations
16 */
17class Speed_Score_Durations {
18    const OPTION_NAME = 'jetpack_boost_speed_score_durations';
19
20    /**
21     * Number of recent runs to keep.
22     */
23    const LIMIT = 5;
24
25    /**
26     * Expected run length before this site has finished one: roughly the fleet average plus one 5 s poll.
27     */
28    const DEFAULT_SECONDS = 40;
29
30    /**
31     * Longest run worth recording; the dashboard stops polling at 4 minutes, so anything longer
32     * means nobody was watching when the run finished.
33     */
34    const MAX_SECONDS = 240;
35
36    /**
37     * A dashboard watching a run polls every 5 s, so a longer gap before the final poll means
38     * nobody was watching when it finished.
39     */
40    const WATCHED_POLL_GAP = 15;
41
42    /**
43     * Record the run once the last of its requests succeeds, timed from the earliest dispatch.
44     *
45     * @param Speed_Score_Request[] $polled Requests that were pending before this poll.
46     */
47    public static function record_finished_run( array $polled ) {
48        if ( empty( $polled ) ) {
49            return;
50        }
51
52        $started = microtime( true );
53        foreach ( $polled as $request ) {
54            if ( ! $request->is_success() || $request->get_poll_gap() > self::WATCHED_POLL_GAP ) {
55                return;
56            }
57            $started = min( $started, $request->get_created() );
58        }
59
60        self::record( microtime( true ) - $started );
61    }
62
63    /**
64     * Record how long a finished run took.
65     *
66     * @param int|float $seconds Seconds from dispatch to the poll that saw the result.
67     */
68    public static function record( $seconds ) {
69        $seconds = (int) round( $seconds );
70        if ( $seconds <= 0 || $seconds > self::MAX_SECONDS ) {
71            return;
72        }
73
74        $durations   = self::all();
75        $durations[] = $seconds;
76        update_option( self::OPTION_NAME, array_slice( $durations, - self::LIMIT ), false );
77    }
78
79    /**
80     * How long the next run is likely to take, in seconds.
81     *
82     * @return int
83     */
84    public static function expected() {
85        $durations = self::all();
86        if ( empty( $durations ) ) {
87            return self::DEFAULT_SECONDS;
88        }
89
90        sort( $durations );
91        $middle = intdiv( count( $durations ), 2 );
92        if ( count( $durations ) % 2 ) {
93            return $durations[ $middle ];
94        }
95
96        return (int) round( ( $durations[ $middle - 1 ] + $durations[ $middle ] ) / 2 );
97    }
98
99    /**
100     * Recorded durations, oldest first.
101     *
102     * @return int[]
103     */
104    private static function all() {
105        $durations = get_option( self::OPTION_NAME, array() );
106
107        return is_array( $durations ) ? array_values( array_map( 'intval', $durations ) ) : array();
108    }
109}