| OLD | NEW |
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. | 1 // Copyright 2015 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "remoting/protocol/performance_tracker.h" | 5 #include "remoting/protocol/performance_tracker.h" |
| 6 | 6 |
| 7 #include "remoting/proto/video.pb.h" | 7 #include "remoting/protocol/frame_stats.h" |
| 8 | 8 |
| 9 namespace { | 9 namespace { |
| 10 | 10 |
| 11 // We take the last 10 latency numbers and report the average. | 11 // We take the last 10 latency numbers and report the average. |
| 12 const int kLatencySampleSize = 10; | 12 const int kLatencySampleSize = 10; |
| 13 | 13 |
| 14 // UMA histogram names. | 14 // UMA histogram names. |
| 15 const char kRoundTripLatencyHistogram[] = "Chromoting.Video.RoundTripLatency"; | 15 const char kRoundTripLatencyHistogram[] = "Chromoting.Video.RoundTripLatency"; |
| 16 const char kVideoCaptureLatencyHistogram[] = "Chromoting.Video.CaptureLatency"; | 16 const char kVideoCaptureLatencyHistogram[] = "Chromoting.Video.CaptureLatency"; |
| 17 const char kVideoEncodeLatencyHistogram[] = "Chromoting.Video.EncodeLatency"; | 17 const char kVideoEncodeLatencyHistogram[] = "Chromoting.Video.EncodeLatency"; |
| (...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 55 const int kBandwidthHistogramBuckets = 100; | 55 const int kBandwidthHistogramBuckets = 100; |
| 56 | 56 |
| 57 // Frame rate is stored in a custom enum histogram, because we we want to record | 57 // Frame rate is stored in a custom enum histogram, because we we want to record |
| 58 // the frequency of each discrete value, rather than using log-scaled buckets. | 58 // the frequency of each discrete value, rather than using log-scaled buckets. |
| 59 // We don't expect video frame rate to be greater than 40fps. Setting a maximum | 59 // We don't expect video frame rate to be greater than 40fps. Setting a maximum |
| 60 // of 100fps will leave some room for future improvements, and account for any | 60 // of 100fps will leave some room for future improvements, and account for any |
| 61 // bursts of packets. Enum histograms expect samples to be less than the | 61 // bursts of packets. Enum histograms expect samples to be less than the |
| 62 // boundary value, so set to 101. | 62 // boundary value, so set to 101. |
| 63 const int kMaxFramesPerSec = 101; | 63 const int kMaxFramesPerSec = 101; |
| 64 | 64 |
| 65 | |
| 66 void UpdateUmaEnumHistogramStub(const std::string& histogram_name, | 65 void UpdateUmaEnumHistogramStub(const std::string& histogram_name, |
| 67 int64_t value, | 66 int64_t value, |
| 68 int histogram_max) {} | 67 int histogram_max) {} |
| 69 | 68 |
| 70 void UpdateUmaCustomHistogramStub(const std::string& histogram_name, | 69 void UpdateUmaCustomHistogramStub(const std::string& histogram_name, |
| 71 int64_t value, | 70 int64_t value, |
| 72 int histogram_min, | 71 int histogram_min, |
| 73 int histogram_max, | 72 int histogram_max, |
| 74 int histogram_buckets) {} | 73 int histogram_buckets) {} |
| 75 } // namespace | 74 } // namespace |
| 76 | 75 |
| 77 namespace remoting { | 76 namespace remoting { |
| 78 namespace protocol { | 77 namespace protocol { |
| 79 | 78 |
| 80 PerformanceTracker::FrameTimestamps::FrameTimestamps() {} | |
| 81 PerformanceTracker::FrameTimestamps::~FrameTimestamps() {} | |
| 82 | |
| 83 PerformanceTracker::PerformanceTracker() | 79 PerformanceTracker::PerformanceTracker() |
| 84 : video_bandwidth_(base::TimeDelta::FromSeconds(kStatsUpdatePeriodSeconds)), | 80 : video_bandwidth_(base::TimeDelta::FromSeconds(kStatsUpdatePeriodSeconds)), |
| 85 video_frame_rate_( | 81 video_frame_rate_( |
| 86 base::TimeDelta::FromSeconds(kStatsUpdatePeriodSeconds)), | 82 base::TimeDelta::FromSeconds(kStatsUpdatePeriodSeconds)), |
| 87 video_packet_rate_( | 83 video_packet_rate_( |
| 88 base::TimeDelta::FromSeconds(kStatsUpdatePeriodSeconds)), | 84 base::TimeDelta::FromSeconds(kStatsUpdatePeriodSeconds)), |
| 89 video_capture_ms_(kLatencySampleSize), | 85 video_capture_ms_(kLatencySampleSize), |
| 90 video_encode_ms_(kLatencySampleSize), | 86 video_encode_ms_(kLatencySampleSize), |
| 91 video_decode_ms_(kLatencySampleSize), | 87 video_decode_ms_(kLatencySampleSize), |
| 92 video_paint_ms_(kLatencySampleSize), | 88 video_paint_ms_(kLatencySampleSize), |
| (...skipping 11 matching lines...) Expand all Loading... |
| 104 UpdateUmaEnumHistogramCallback update_uma_enum_histogram_callback) { | 100 UpdateUmaEnumHistogramCallback update_uma_enum_histogram_callback) { |
| 105 DCHECK(!update_uma_custom_counts_callback.is_null()); | 101 DCHECK(!update_uma_custom_counts_callback.is_null()); |
| 106 DCHECK(!update_uma_custom_times_callback.is_null()); | 102 DCHECK(!update_uma_custom_times_callback.is_null()); |
| 107 DCHECK(!update_uma_enum_histogram_callback.is_null()); | 103 DCHECK(!update_uma_enum_histogram_callback.is_null()); |
| 108 | 104 |
| 109 uma_custom_counts_updater_ = update_uma_custom_counts_callback; | 105 uma_custom_counts_updater_ = update_uma_custom_counts_callback; |
| 110 uma_custom_times_updater_ = update_uma_custom_times_callback; | 106 uma_custom_times_updater_ = update_uma_custom_times_callback; |
| 111 uma_enum_histogram_updater_ = update_uma_enum_histogram_callback; | 107 uma_enum_histogram_updater_ = update_uma_enum_histogram_callback; |
| 112 } | 108 } |
| 113 | 109 |
| 114 void PerformanceTracker::RecordVideoPacketStats(const VideoPacket& packet) { | 110 void PerformanceTracker::RecordVideoFrameStats(const FrameStats& stats) { |
| 115 if (!is_paused_ && !upload_uma_stats_timer_.IsRunning()) { | 111 if (!is_paused_ && !upload_uma_stats_timer_.IsRunning()) { |
| 116 upload_uma_stats_timer_.Start( | 112 upload_uma_stats_timer_.Start( |
| 117 FROM_HERE, base::TimeDelta::FromSeconds(kStatsUpdatePeriodSeconds), | 113 FROM_HERE, base::TimeDelta::FromSeconds(kStatsUpdatePeriodSeconds), |
| 118 base::Bind(&PerformanceTracker::UploadRateStatsToUma, | 114 base::Bind(&PerformanceTracker::UploadRateStatsToUma, |
| 119 base::Unretained(this))); | 115 base::Unretained(this))); |
| 120 } | 116 } |
| 121 | 117 |
| 122 // Record this received packet, even if it is empty. | 118 // Record this received packet, even if it is empty. |
| 123 video_packet_rate_.Record(1); | 119 video_packet_rate_.Record(1); |
| 124 | 120 |
| 125 FrameTimestamps timestamps; | 121 // Use only non-empty frames to estimate frame rate. |
| 126 timestamps.time_received = base::TimeTicks::Now(); | 122 if (stats.frame_size) |
| 127 if (packet.has_latest_event_timestamp()) { | 123 video_frame_rate_.Record(1); |
| 128 base::TimeTicks timestamp = | |
| 129 base::TimeTicks::FromInternalValue(packet.latest_event_timestamp()); | |
| 130 // Only use latest_event_timestamp field if it has changed from the | |
| 131 // previous frame. | |
| 132 if (timestamp > latest_event_timestamp_) { | |
| 133 timestamps.latest_event_timestamp = timestamp; | |
| 134 latest_event_timestamp_ = timestamp; | |
| 135 } | |
| 136 } | |
| 137 | 124 |
| 138 // If the host didn't specify any of the latency fields then set | 125 video_bandwidth_.Record(stats.frame_size); |
| 139 // |total_host_latency| to Max, to indicate that the latency is unknown. | |
| 140 timestamps.total_host_latency = base::TimeDelta::Max(); | |
| 141 if (packet.has_capture_time_ms() && packet.has_encode_time_ms() && | |
| 142 packet.has_capture_pending_time_ms() && | |
| 143 packet.has_capture_overhead_time_ms() && | |
| 144 packet.has_encode_pending_time_ms() && | |
| 145 packet.has_send_pending_time_ms()) { | |
| 146 timestamps.total_host_latency = base::TimeDelta::FromMilliseconds( | |
| 147 packet.capture_time_ms() + packet.encode_time_ms() + | |
| 148 packet.capture_pending_time_ms() + packet.capture_overhead_time_ms() + | |
| 149 packet.encode_pending_time_ms() + packet.send_pending_time_ms()); | |
| 150 } | |
| 151 | 126 |
| 152 // If the packet is empty, there are no other stats to update. | 127 if (stats.capture_delay != base::TimeDelta::Max()) { |
| 153 if (!packet.data().size()) { | 128 video_capture_ms_.Record(stats.capture_delay.InMilliseconds()); |
| 154 // Record the RTT, even for empty packets, otherwise input events that | |
| 155 // do not cause an on-screen change can give very large, bogus RTTs. | |
| 156 RecordRoundTripLatency(timestamps); | |
| 157 return; | |
| 158 } | |
| 159 | |
| 160 DCHECK(packet.has_frame_id()); | |
| 161 frame_timestamps_[packet.frame_id()] = timestamps; | |
| 162 | |
| 163 video_frame_rate_.Record(1); | |
| 164 video_bandwidth_.Record(packet.data().size()); | |
| 165 | |
| 166 if (packet.has_capture_time_ms()) { | |
| 167 video_capture_ms_.Record(packet.capture_time_ms()); | |
| 168 uma_custom_times_updater_.Run( | 129 uma_custom_times_updater_.Run( |
| 169 kVideoCaptureLatencyHistogram, packet.capture_time_ms(), | 130 kVideoCaptureLatencyHistogram, stats.capture_delay.InMilliseconds(), |
| 170 kVideoActionsHistogramsMinMs, kVideoActionsHistogramsMaxMs, | 131 kVideoActionsHistogramsMinMs, kVideoActionsHistogramsMaxMs, |
| 171 kVideoActionsHistogramsBuckets); | 132 kVideoActionsHistogramsBuckets); |
| 172 } | 133 } |
| 173 | 134 |
| 174 if (packet.has_encode_time_ms()) { | 135 if (stats.encode_delay != base::TimeDelta::Max()) { |
| 175 video_encode_ms_.Record(packet.encode_time_ms()); | 136 video_encode_ms_.Record(stats.encode_delay.InMilliseconds()); |
| 176 uma_custom_times_updater_.Run( | 137 uma_custom_times_updater_.Run( |
| 177 kVideoEncodeLatencyHistogram, packet.encode_time_ms(), | 138 kVideoEncodeLatencyHistogram, stats.encode_delay.InMilliseconds(), |
| 178 kVideoActionsHistogramsMinMs, kVideoActionsHistogramsMaxMs, | 139 kVideoActionsHistogramsMinMs, kVideoActionsHistogramsMaxMs, |
| 179 kVideoActionsHistogramsBuckets); | 140 kVideoActionsHistogramsBuckets); |
| 180 } | 141 } |
| 181 | 142 |
| 182 if (packet.has_capture_pending_time_ms()) { | 143 if (stats.capture_pending_delay != base::TimeDelta::Max()) { |
| 144 uma_custom_times_updater_.Run(kCapturePendingLatencyHistogram, |
| 145 stats.capture_pending_delay.InMilliseconds(), |
| 146 kVideoActionsHistogramsMinMs, |
| 147 kVideoActionsHistogramsMaxMs, |
| 148 kVideoActionsHistogramsBuckets); |
| 149 } |
| 150 |
| 151 if (stats.capture_overhead_delay != base::TimeDelta::Max()) { |
| 152 uma_custom_times_updater_.Run(kCaptureOverheadHistogram, |
| 153 stats.capture_overhead_delay.InMilliseconds(), |
| 154 kVideoActionsHistogramsMinMs, |
| 155 kVideoActionsHistogramsMaxMs, |
| 156 kVideoActionsHistogramsBuckets); |
| 157 } |
| 158 |
| 159 if (stats.encode_pending_delay != base::TimeDelta::Max()) { |
| 160 uma_custom_times_updater_.Run(kEncodePendingLatencyHistogram, |
| 161 stats.encode_pending_delay.InMilliseconds(), |
| 162 kVideoActionsHistogramsMinMs, |
| 163 kVideoActionsHistogramsMaxMs, |
| 164 kVideoActionsHistogramsBuckets); |
| 165 } |
| 166 |
| 167 if (stats.send_pending_delay != base::TimeDelta::Max()) { |
| 183 uma_custom_times_updater_.Run( | 168 uma_custom_times_updater_.Run( |
| 184 kCapturePendingLatencyHistogram, packet.capture_pending_time_ms(), | 169 kSendPendingLatencyHistogram, stats.send_pending_delay.InMilliseconds(), |
| 185 kVideoActionsHistogramsMinMs, kVideoActionsHistogramsMaxMs, | 170 kVideoActionsHistogramsMinMs, kVideoActionsHistogramsMaxMs, |
| 186 kVideoActionsHistogramsBuckets); | 171 kVideoActionsHistogramsBuckets); |
| 187 } | 172 } |
| 188 | 173 |
| 189 if (packet.has_capture_overhead_time_ms()) { | 174 DCHECK(!stats.time_received.is_null()); |
| 175 |
| 176 // Report decode and render delay only for non-empty frames. |
| 177 if (stats.frame_size > 0) { |
| 178 DCHECK(!stats.time_rendered.is_null()); |
| 179 DCHECK(!stats.time_decoded.is_null()); |
| 180 base::TimeDelta decode_delay = stats.time_decoded - stats.time_received; |
| 181 video_decode_ms_.Record(decode_delay.InMilliseconds()); |
| 190 uma_custom_times_updater_.Run( | 182 uma_custom_times_updater_.Run( |
| 191 kCaptureOverheadHistogram, packet.capture_overhead_time_ms(), | 183 kVideoDecodeLatencyHistogram, decode_delay.InMilliseconds(), |
| 184 kVideoActionsHistogramsMinMs, kVideoActionsHistogramsMaxMs, |
| 185 kVideoActionsHistogramsBuckets); |
| 186 |
| 187 base::TimeDelta render_delay = stats.time_rendered - stats.time_decoded; |
| 188 video_paint_ms_.Record(render_delay.InMilliseconds()); |
| 189 uma_custom_times_updater_.Run( |
| 190 kVideoPaintLatencyHistogram, render_delay.InMilliseconds(), |
| 192 kVideoActionsHistogramsMinMs, kVideoActionsHistogramsMaxMs, | 191 kVideoActionsHistogramsMinMs, kVideoActionsHistogramsMaxMs, |
| 193 kVideoActionsHistogramsBuckets); | 192 kVideoActionsHistogramsBuckets); |
| 194 } | 193 } |
| 195 | 194 |
| 196 if (packet.has_encode_pending_time_ms()) { | 195 // |latest_event_timestamp| is set only for the first frame after an input |
| 197 uma_custom_times_updater_.Run( | 196 // event. |
| 198 kEncodePendingLatencyHistogram, packet.encode_pending_time_ms(), | 197 if (stats.latest_event_timestamp.is_null()) |
| 199 kVideoActionsHistogramsMinMs, kVideoActionsHistogramsMaxMs, | |
| 200 kVideoActionsHistogramsBuckets); | |
| 201 } | |
| 202 | |
| 203 if (packet.has_send_pending_time_ms()) { | |
| 204 uma_custom_times_updater_.Run( | |
| 205 kSendPendingLatencyHistogram, packet.send_pending_time_ms(), | |
| 206 kVideoActionsHistogramsMinMs, kVideoActionsHistogramsMaxMs, | |
| 207 kVideoActionsHistogramsBuckets); | |
| 208 } | |
| 209 } | |
| 210 | |
| 211 void PerformanceTracker::OnFrameDecoded(int32_t frame_id) { | |
| 212 FramesTimestampsMap::iterator it = frame_timestamps_.find(frame_id); | |
| 213 DCHECK(it != frame_timestamps_.end()); | |
| 214 it->second.time_decoded = base::TimeTicks::Now(); | |
| 215 base::TimeDelta delay = it->second.time_decoded - it->second.time_received; | |
| 216 | |
| 217 video_decode_ms_.Record(delay.InMilliseconds()); | |
| 218 uma_custom_times_updater_.Run( | |
| 219 kVideoDecodeLatencyHistogram, delay.InMilliseconds(), | |
| 220 kVideoActionsHistogramsMinMs, kVideoActionsHistogramsMaxMs, | |
| 221 kVideoActionsHistogramsBuckets); | |
| 222 } | |
| 223 | |
| 224 void PerformanceTracker::OnFramePainted(int32_t frame_id) { | |
| 225 base::TimeTicks now = base::TimeTicks::Now(); | |
| 226 | |
| 227 while (!frame_timestamps_.empty() && | |
| 228 frame_timestamps_.begin()->first <= frame_id) { | |
| 229 FrameTimestamps& timestamps = frame_timestamps_.begin()->second; | |
| 230 | |
| 231 // time_decoded may be null if OnFrameDecoded() was never called, e.g. if | |
| 232 // the frame was dropped or decoding has failed. | |
| 233 if (!timestamps.time_decoded.is_null()) { | |
| 234 base::TimeDelta delay = now - timestamps.time_decoded; | |
| 235 video_paint_ms_.Record(delay.InMilliseconds()); | |
| 236 uma_custom_times_updater_.Run( | |
| 237 kVideoPaintLatencyHistogram, delay.InMilliseconds(), | |
| 238 kVideoActionsHistogramsMinMs, kVideoActionsHistogramsMaxMs, | |
| 239 kVideoActionsHistogramsBuckets); | |
| 240 } | |
| 241 | |
| 242 RecordRoundTripLatency(timestamps); | |
| 243 frame_timestamps_.erase(frame_timestamps_.begin()); | |
| 244 } | |
| 245 } | |
| 246 | |
| 247 void PerformanceTracker::RecordRoundTripLatency( | |
| 248 const FrameTimestamps& timestamps) { | |
| 249 if (timestamps.latest_event_timestamp.is_null()) | |
| 250 return; | 198 return; |
| 251 | 199 |
| 252 base::TimeTicks now = base::TimeTicks::Now(); | 200 // For empty frames use time_received as time_rendered. |
| 201 base::TimeTicks time_rendered = |
| 202 (stats.frame_size > 0) ? stats.time_rendered : stats.time_received; |
| 253 base::TimeDelta round_trip_latency = | 203 base::TimeDelta round_trip_latency = |
| 254 now - timestamps.latest_event_timestamp; | 204 time_rendered - stats.latest_event_timestamp; |
| 255 | |
| 256 round_trip_ms_.Record(round_trip_latency.InMilliseconds()); | 205 round_trip_ms_.Record(round_trip_latency.InMilliseconds()); |
| 257 uma_custom_times_updater_.Run( | 206 uma_custom_times_updater_.Run( |
| 258 kRoundTripLatencyHistogram, round_trip_latency.InMilliseconds(), | 207 kRoundTripLatencyHistogram, round_trip_latency.InMilliseconds(), |
| 259 kLatencyHistogramMinMs, kLatencyHistogramMaxMs, kLatencyHistogramBuckets); | 208 kLatencyHistogramMinMs, kLatencyHistogramMaxMs, kLatencyHistogramBuckets); |
| 260 | 209 |
| 261 if (!timestamps.total_host_latency.is_max()) { | 210 // Report estimated network latency. |
| 211 if (stats.capture_delay != base::TimeDelta::Max() && |
| 212 stats.encode_delay != base::TimeDelta::Max() && |
| 213 stats.capture_pending_delay != base::TimeDelta::Max() && |
| 214 stats.capture_overhead_delay != base::TimeDelta::Max() && |
| 215 stats.encode_pending_delay != base::TimeDelta::Max() && |
| 216 stats.send_pending_delay != base::TimeDelta::Max()) { |
| 262 // Calculate total processing time on host and client. | 217 // Calculate total processing time on host and client. |
| 263 base::TimeDelta total_processing_latency = | 218 base::TimeDelta total_processing_latency = |
| 264 timestamps.total_host_latency + (now - timestamps.time_received); | 219 stats.capture_delay + stats.encode_delay + stats.capture_pending_delay + |
| 220 stats.capture_overhead_delay + stats.encode_pending_delay + |
| 221 stats.send_pending_delay + (time_rendered - stats.time_received); |
| 265 base::TimeDelta network_latency = | 222 base::TimeDelta network_latency = |
| 266 round_trip_latency - total_processing_latency; | 223 round_trip_latency - total_processing_latency; |
| 267 uma_custom_times_updater_.Run( | 224 uma_custom_times_updater_.Run( |
| 268 kNetworkLatencyHistogram, network_latency.InMilliseconds(), | 225 kNetworkLatencyHistogram, network_latency.InMilliseconds(), |
| 269 kVideoActionsHistogramsMinMs, kVideoActionsHistogramsMaxMs, | 226 kVideoActionsHistogramsMinMs, kVideoActionsHistogramsMaxMs, |
| 270 kVideoActionsHistogramsBuckets); | 227 kVideoActionsHistogramsBuckets); |
| 271 } | 228 } |
| 272 } | 229 } |
| 273 | 230 |
| 274 void PerformanceTracker::UploadRateStatsToUma() { | 231 void PerformanceTracker::UploadRateStatsToUma() { |
| 275 uma_enum_histogram_updater_.Run(kVideoFrameRateHistogram, video_frame_rate(), | 232 uma_enum_histogram_updater_.Run(kVideoFrameRateHistogram, video_frame_rate(), |
| 276 kMaxFramesPerSec); | 233 kMaxFramesPerSec); |
| 277 uma_enum_histogram_updater_.Run(kVideoPacketRateHistogram, | 234 uma_enum_histogram_updater_.Run(kVideoPacketRateHistogram, |
| 278 video_packet_rate(), kMaxFramesPerSec); | 235 video_packet_rate(), kMaxFramesPerSec); |
| 279 uma_custom_counts_updater_.Run( | 236 uma_custom_counts_updater_.Run( |
| 280 kVideoBandwidthHistogram, video_bandwidth(), kBandwidthHistogramMinBps, | 237 kVideoBandwidthHistogram, video_bandwidth(), kBandwidthHistogramMinBps, |
| 281 kBandwidthHistogramMaxBps, kBandwidthHistogramBuckets); | 238 kBandwidthHistogramMaxBps, kBandwidthHistogramBuckets); |
| 282 } | 239 } |
| 283 | 240 |
| 284 void PerformanceTracker::OnPauseStateChanged(bool paused) { | 241 void PerformanceTracker::OnPauseStateChanged(bool paused) { |
| 285 is_paused_ = paused; | 242 is_paused_ = paused; |
| 286 if (is_paused_) { | 243 if (is_paused_) { |
| 287 // Pause the UMA timer when the video is paused. It will be unpaused in | 244 // Pause the UMA timer when the video is paused. It will be unpaused in |
| 288 // RecordVideoPacketStats() when a new frame is received. | 245 // RecordVideoFrameStats() when a new frame is received. |
| 289 upload_uma_stats_timer_.Stop(); | 246 upload_uma_stats_timer_.Stop(); |
| 290 } | 247 } |
| 291 } | 248 } |
| 292 | 249 |
| 293 } // namespace protocol | 250 } // namespace protocol |
| 294 } // namespace remoting | 251 } // namespace remoting |
| OLD | NEW |