| -- | 
 | -- Copyright 2021 The Android Open Source Project | 
 | -- | 
 | -- Licensed under the Apache License, Version 2.0 (the 'License'); | 
 | -- you may not use this file except in compliance with the License. | 
 | -- You may obtain a copy of the License at | 
 | -- | 
 | --     https://www.apache.org/licenses/LICENSE-2.0 | 
 | -- | 
 | -- Unless required by applicable law or agreed to in writing, software | 
 | -- distributed under the License is distributed on an 'AS IS' BASIS, | 
 | -- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
 | -- See the License for the specific language governing permissions and | 
 | -- limitations under the License. | 
 |  | 
 | SELECT RUN_METRIC('chrome/chrome_processes.sql'); | 
 |  | 
 | -- Helper for thread slices | 
 | DROP VIEW IF EXISTS thread_slice; | 
 | CREATE VIEW thread_slice AS | 
 | SELECT s.*, thread.utid, thread.upid | 
 | FROM slice s | 
 | INNER JOIN thread_track ON s.track_id = thread_track.id | 
 | INNER JOIN thread USING(utid); | 
 |  | 
 | -------------------------------------------------------------------------------- | 
 | -- Find all playbacks on renderer main threads. | 
 |  | 
 | DROP VIEW IF EXISTS PlaybackStart; | 
 | CREATE VIEW PlaybackStart AS | 
 | SELECT | 
 |   EXTRACT_ARG(s.arg_set_id, 'debug.id') AS playback_id, | 
 |   s.ts AS playback_start, | 
 |   upid | 
 | FROM slice s | 
 | INNER JOIN thread_track ON s.track_id = thread_track.id | 
 | INNER JOIN thread USING(utid) | 
 | WHERE | 
 |   s.name = 'WebMediaPlayerImpl::DoLoad' | 
 |   AND thread.name = 'CrRendererMain'; | 
 |  | 
 | -------------------------------------------------------------------------------- | 
 | -- Find the first video render time after the playback to compute | 
 | -- time_to_video_play. | 
 |  | 
 | DROP VIEW IF EXISTS VideoStart; | 
 | CREATE VIEW VideoStart AS | 
 | SELECT | 
 |   playback_id, | 
 |   playback_start, | 
 |   PlaybackStart.upid, | 
 |   MIN(s.ts) AS video_start | 
 | FROM PlaybackStart, thread_slice s | 
 | WHERE | 
 |   s.name = 'VideoRendererImpl::Render' | 
 |   AND EXTRACT_ARG(s.arg_set_id, 'debug.id') = playback_id | 
 |   AND s.upid = PlaybackStart.upid | 
 | GROUP BY playback_id, playback_start, PlaybackStart.upid; | 
 |  | 
 | -------------------------------------------------------------------------------- | 
 | -- Find the first audio render time after the playback to compute | 
 | -- time_to_audio_play. | 
 |  | 
 | DROP VIEW IF EXISTS AudioStart; | 
 | CREATE VIEW AudioStart AS | 
 | SELECT | 
 |   playback_id, | 
 |   playback_start, | 
 |   PlaybackStart.upid, | 
 |   MIN(s.ts) AS audio_start | 
 | FROM PlaybackStart, thread_slice s | 
 | WHERE | 
 |   s.name = 'AudioRendererImpl::Render' | 
 |   AND EXTRACT_ARG(s.arg_set_id, 'debug.id') = playback_id | 
 |   AND s.upid = PlaybackStart.upid | 
 | GROUP BY playback_id, playback_start, PlaybackStart.upid; | 
 |  | 
 | -------------------------------------------------------------------------------- | 
 | -- Sum up the dropped frame count from all such events for each playback to | 
 | -- compute dropped_frame_count. | 
 |  | 
 | DROP VIEW IF EXISTS DroppedFrameCount; | 
 | CREATE VIEW DroppedFrameCount AS | 
 | SELECT | 
 |   playback_id, | 
 |   vs.upid, | 
 |   SUM( | 
 |     CASE | 
 |     WHEN s.arg_set_id IS NULL THEN 0 | 
 |     ELSE EXTRACT_ARG(s.arg_set_id, 'debug.count') END | 
 |   ) AS dropped_frame_count | 
 | FROM VideoStart vs | 
 | LEFT JOIN thread_slice s ON | 
 |   s.name = 'VideoFramesDropped' | 
 |   AND EXTRACT_ARG(s.arg_set_id, 'debug.id') = playback_id | 
 |   AND s.upid = vs.upid | 
 | GROUP BY playback_id, vs.upid; | 
 |  | 
 | -------------------------------------------------------------------------------- | 
 | -- Compute seek times. | 
 |  | 
 | -- Find the seeks. | 
 | DROP VIEW IF EXISTS SeekStart; | 
 | CREATE VIEW SeekStart AS | 
 | SELECT | 
 |   playback_id, | 
 |   PlaybackStart.upid, | 
 |   s.ts AS seek_start, | 
 |   EXTRACT_ARG(s.arg_set_id, 'debug.target') AS seek_target | 
 | FROM PlaybackStart | 
 | LEFT JOIN thread_slice s | 
 | WHERE | 
 |   s.name = 'WebMediaPlayerImpl::DoSeek' | 
 |   AND EXTRACT_ARG(s.arg_set_id, 'debug.id') = playback_id | 
 |   AND s.upid = PlaybackStart.upid; | 
 |  | 
 | -- Partition by the next seek's ts, so that we can filter for events occurring | 
 | -- within each seek's window below. | 
 | DROP VIEW IF EXISTS SeekPartitioned; | 
 | CREATE VIEW SeekPartitioned AS | 
 | SELECT | 
 |   *, | 
 |   LEAD(seek_start) OVER ( | 
 |     PARTITION BY playback_id, upid | 
 |     ORDER BY seek_start ASC | 
 |   ) AS seek_end | 
 | FROM SeekStart; | 
 |  | 
 | -- Find the subsequent matching pipeline seeks that occur before the next seek. | 
 | DROP VIEW IF EXISTS PipelineSeek; | 
 | CREATE VIEW PipelineSeek AS | 
 | SELECT | 
 |   seek.*, | 
 |   ( | 
 |     SELECT MIN(s.ts) | 
 |     FROM thread_slice s | 
 |     WHERE | 
 |       s.name = 'WebMediaPlayerImpl::OnPipelineSeeked' | 
 |       AND EXTRACT_ARG(s.arg_set_id, 'debug.id') = seek.playback_id | 
 |       AND EXTRACT_ARG(s.arg_set_id, 'debug.target') = seek.seek_target | 
 |       AND s.upid = seek.upid | 
 |       AND s.ts >= seek.seek_start | 
 |       AND (seek.seek_end IS NULL OR s.ts < seek.seek_end) | 
 |   ) AS pipeline_seek | 
 | FROM SeekPartitioned seek; | 
 |  | 
 | -- Find the subsequent buffering events that occur before the next seek. | 
 | DROP VIEW IF EXISTS SeekComplete; | 
 | CREATE VIEW SeekComplete AS | 
 | SELECT | 
 |   seek.*, | 
 |   ( | 
 |     SELECT MIN(s.ts) | 
 |     FROM thread_slice s | 
 |     WHERE | 
 |       s.name = 'WebMediaPlayerImpl::BufferingHaveEnough' | 
 |       AND EXTRACT_ARG(s.arg_set_id, 'debug.id') = seek.playback_id | 
 |       AND s.upid = seek.upid | 
 |       AND s.ts >= seek.pipeline_seek | 
 |       AND (seek.seek_end IS NULL OR s.ts < seek.seek_end) | 
 |   ) AS seek_complete | 
 | FROM PipelineSeek seek; | 
 |  | 
 | -- Find the subsequent buffering events that occur before the next seek. | 
 | DROP VIEW IF EXISTS ValidSeek; | 
 | CREATE VIEW ValidSeek AS | 
 | SELECT | 
 |   s.* | 
 | FROM SeekComplete s | 
 | WHERE | 
 |   s.pipeline_seek IS NOT NULL | 
 |   AND s.seek_complete IS NOT NULL; | 
 |  | 
 | -------------------------------------------------------------------------------- | 
 | -- Find playback end timestamps and their duration for playbacks without seeks | 
 | -- to compute buffering_time. | 
 |  | 
 | -- Helper view that shows either video or audio start for each playback | 
 | DROP VIEW IF EXISTS AVStart; | 
 | CREATE VIEW AVStart AS | 
 | SELECT | 
 |   v.playback_id, | 
 |   v.playback_start, | 
 |   v.upid, | 
 |   v.video_start AS av_start | 
 | FROM VideoStart v | 
 | UNION | 
 | SELECT | 
 |   a.playback_id, | 
 |   a.playback_start, | 
 |   a.upid, | 
 |   a.audio_start AS av_start | 
 | FROM AudioStart a | 
 | WHERE a.playback_id NOT IN (SELECT playback_id FROM VideoStart); | 
 |  | 
 | -- Find the corresponding media end events and their reported duration. | 
 | DROP VIEW IF EXISTS PlaybackEnd; | 
 | CREATE VIEW PlaybackEnd AS | 
 | SELECT | 
 |   AVStart.*, | 
 |   slice.ts AS playback_end, | 
 |   EXTRACT_ARG(slice.arg_set_id, 'debug.duration') * 1e9 AS duration | 
 | FROM AVStart | 
 | INNER JOIN slice ON slice.id = ( | 
 |   SELECT s.id | 
 |   FROM thread_slice s | 
 |   WHERE | 
 |     s.name = 'WebMediaPlayerImpl::OnEnded' | 
 |     AND EXTRACT_ARG(s.arg_set_id, 'debug.id') = AVStart.playback_id | 
 |     AND s.upid = AVStart.upid | 
 |   ORDER BY s.ts ASC | 
 |   LIMIT 1 | 
 | ) | 
 | WHERE NOT EXISTS ( | 
 |   SELECT 1 FROM SeekStart | 
 |   WHERE SeekStart.playback_id = AVStart.playback_id | 
 | ); | 
 |  | 
 | -------------------------------------------------------------------------------- | 
 | -- Find maximum video roughness and freezing events per playback. | 
 |  | 
 | DROP VIEW IF EXISTS VideoRoughness; | 
 | CREATE VIEW VideoRoughness AS | 
 | SELECT | 
 |   playback_id, | 
 |   playback_start, | 
 |   PlaybackStart.upid, | 
 |   MAX(EXTRACT_ARG(s.arg_set_id, 'debug.roughness')) AS roughness | 
 | FROM PlaybackStart | 
 | INNER JOIN thread_slice s | 
 | WHERE | 
 |   s.name = 'VideoPlaybackRoughness' | 
 |   AND EXTRACT_ARG(s.arg_set_id, 'debug.id') = playback_id | 
 |   AND s.upid = PlaybackStart.upid | 
 | GROUP BY playback_id, playback_start, PlaybackStart.upid; | 
 |  | 
 | DROP VIEW IF EXISTS VideoFreezing; | 
 | CREATE VIEW VideoFreezing AS | 
 | SELECT | 
 |   playback_id, | 
 |   playback_start, | 
 |   PlaybackStart.upid, | 
 |   MAX(EXTRACT_ARG(s.arg_set_id, 'debug.freezing')) AS freezing | 
 | FROM PlaybackStart | 
 | INNER JOIN thread_slice s | 
 | WHERE | 
 |   s.name = 'VideoPlaybackFreezing' | 
 |   AND EXTRACT_ARG(s.arg_set_id, 'debug.id') = playback_id | 
 |   AND s.upid = PlaybackStart.upid | 
 | GROUP BY playback_id, playback_start, PlaybackStart.upid; | 
 |  | 
 | -------------------------------------------------------------------------------- | 
 | -- Output to proto | 
 |  | 
 | DROP VIEW IF EXISTS media_metric_output; | 
 | CREATE VIEW media_metric_output AS | 
 | SELECT MediaMetric( | 
 |   'time_to_video_play', ( | 
 |     SELECT RepeatedField((video_start - playback_start) / 1e6) | 
 |     FROM VideoStart | 
 |   ), | 
 |   'time_to_audio_play', ( | 
 |     SELECT RepeatedField((audio_start - playback_start) / 1e6) | 
 |     FROM AudioStart | 
 |   ), | 
 |   'dropped_frame_count', ( | 
 |     SELECT RepeatedField(CAST(dropped_frame_count AS INTEGER)) | 
 |     FROM DroppedFrameCount | 
 |   ), | 
 |   'buffering_time', ( | 
 |     SELECT RepeatedField((playback_end - duration - av_start) / 1e6) | 
 |     FROM PlaybackEnd | 
 |   ), | 
 |   'roughness', ( | 
 |     SELECT RepeatedField(roughness / 1e0) | 
 |     FROM VideoRoughness | 
 |   ), | 
 |   'freezing', ( | 
 |     SELECT RepeatedField(freezing / 1e0) | 
 |     FROM VideoFreezing | 
 |   ), | 
 |   'seek_time', ( | 
 |     SELECT RepeatedField((seek_complete - seek_start) / 1e6) | 
 |     FROM ValidSeek | 
 |   ), | 
 |   'pipeline_seek_time', ( | 
 |     SELECT RepeatedField((pipeline_seek - seek_start) / 1e6) | 
 |     FROM ValidSeek | 
 |   ) | 
 | ); |