-- -- 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 ) );