insert( Schema::table( 'sessions' ), array( 'started_at' => current_time( 'mysql', true ), 'mode' => $mode, 'passes' => $passes, 'urls' => wp_json_encode( array_values( $urls ) ), 'exclude_slug' => '' !== $exclude ? $exclude : null, 'baseline_id' => $baseline > 0 ? $baseline : null, 'status' => 'running', 'progress' => wp_json_encode( array( 'next' => 0, 'done' => 0, 'total' => count( $urls ) * $passes, 'errors' => array(), 'cached' => 0, ) ), 'label' => '' !== $label ? $label : null, ) ); return (int) $wpdb->insert_id; } /** * Find. * * @param int $id Id. */ public function find( int $id ): ?array { global $wpdb; $row = $wpdb->get_row( $wpdb->prepare( 'SELECT * FROM %i WHERE id = %d', Schema::table( 'sessions' ), $id ), ARRAY_A ); return $row ? $this->cast( $row ) : null; } /** * Recent sessions (without summary blobs unless asked). * * @param int $limit Limit. * @param bool $with_summary Include summary. * @return array[] */ public function all( int $limit = 20, bool $with_summary = false ): array { global $wpdb; if ( $with_summary ) { $rows = $wpdb->get_results( $wpdb->prepare( 'SELECT * FROM %i ORDER BY id DESC LIMIT %d', Schema::table( 'sessions' ), $limit ), ARRAY_A ); } else { $rows = $wpdb->get_results( $wpdb->prepare( 'SELECT id, started_at, finished_at, mode, passes, urls, exclude_slug, baseline_id, status, progress, label FROM %i ORDER BY id DESC LIMIT %d', Schema::table( 'sessions' ), $limit ), ARRAY_A ); } return array_map( array( $this, 'cast' ), (array) $rows ); } /** * Last finished session before the given one (for comparisons). * * @param int $before_id Id. */ public function previous_done( int $before_id ): ?array { global $wpdb; $row = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM %i WHERE id < %d AND status = 'done' AND exclude_slug IS NULL ORDER BY id DESC LIMIT 1", Schema::table( 'sessions' ), $before_id ), ARRAY_A ); return $row ? $this->cast( $row ) : null; } /** * Newest automatic session that is still running (0 when none). */ public function running_auto(): int { global $wpdb; return (int) $wpdb->get_var( $wpdb->prepare( "SELECT id FROM %i WHERE status = 'running' AND mode = 'auto' ORDER BY id DESC LIMIT 1", Schema::table( 'sessions' ) ) ); } /** * Update. * * @param int $id Id. * @param array $data Data (arrays are JSON encoded). */ public function update( int $id, array $data ): void { global $wpdb; foreach ( $data as $k => $v ) { if ( is_array( $v ) ) { $data[ $k ] = wp_json_encode( $v ); } } $wpdb->update( Schema::table( 'sessions' ), $data, array( 'id' => $id ) ); } /** * Delete session + requests. * * @param int $id Id. */ public function delete( int $id ): bool { global $wpdb; $wpdb->delete( Schema::table( 'requests' ), array( 'session_id' => $id ), array( '%d' ) ); return (bool) $wpdb->delete( Schema::table( 'sessions' ), array( 'id' => $id ), array( '%d' ) ); } /** * Keep only the newest N sessions. * * @param int $keep Keep. */ public function prune( int $keep ): int { global $wpdb; $ids = $wpdb->get_col( $wpdb->prepare( 'SELECT id FROM %i ORDER BY id DESC LIMIT %d, 1000000', Schema::table( 'sessions' ), max( 1, $keep ) ) ); $n = 0; foreach ( (array) $ids as $id ) { if ( $this->delete( (int) $id ) ) { ++$n; } } return $n; } /** * Requests for a session. * * @param int $session_id Session. * @return array[] rows with decoded data. */ public function requests( int $session_id ): array { global $wpdb; $rows = $wpdb->get_results( $wpdb->prepare( 'SELECT * FROM %i WHERE session_id = %d ORDER BY id ASC', Schema::table( 'requests' ), $session_id ), ARRAY_A ); $out = array(); foreach ( (array) $rows as $r ) { $data = json_decode( (string) $r['data'], true ); $r['data'] = is_array( $data ) ? $data : array(); $r['id'] = (int) $r['id']; $r['pass'] = (int) $r['pass']; $r['total_ms'] = (float) $r['total_ms']; $r['peak_mem_kb'] = (int) $r['peak_mem_kb']; $r['query_count'] = (int) $r['query_count']; $out[] = $r; } return $out; } /** * URL and pass of every recorded request (no data blobs). * * @param int $session_id Session. * @return array */ public function recorded( int $session_id ): array { global $wpdb; $rows = $wpdb->get_results( $wpdb->prepare( 'SELECT url, pass FROM %i WHERE session_id = %d', Schema::table( 'requests' ), $session_id ), ARRAY_A ); $out = array(); foreach ( (array) $rows as $r ) { $out[] = array( 'url' => (string) $r['url'], 'pass' => (int) $r['pass'], ); } return $out; } /** * Count of recorded requests. * * @param int $session_id Session. */ public function request_count( int $session_id ): int { global $wpdb; return (int) $wpdb->get_var( $wpdb->prepare( 'SELECT COUNT(*) FROM %i WHERE session_id = %d', Schema::table( 'requests' ), $session_id ) ); } /** * Cast. * * @param array $row Row. */ private function cast( array $row ): array { $row['id'] = (int) $row['id']; $row['passes'] = (int) $row['passes']; $urls = json_decode( (string) $row['urls'], true ); $row['urls'] = is_array( $urls ) ? $urls : array(); if ( array_key_exists( 'progress', $row ) ) { $p = json_decode( (string) $row['progress'], true ); $row['progress'] = is_array( $p ) ? $p : array(); } if ( array_key_exists( 'summary', $row ) ) { $s = json_decode( (string) $row['summary'], true ); $row['summary'] = is_array( $s ) ? $s : null; } $row['exclude_slug'] = (string) ( $row['exclude_slug'] ?? '' ); if ( ! empty( $row['summary'] ) && '' !== $row['exclude_slug'] && is_array( $row['summary']['owners'] ?? null ) ) { $row['summary']['owners'] = \Bloatwright\Services\Aggregator::mark_excluded( $row['summary']['owners'], $row['exclude_slug'] ); } $row['baseline_id'] = (int) ( $row['baseline_id'] ?? 0 ); $row['label'] = (string) ( $row['label'] ?? '' ); return $row; } }