Index: chromecast/base/metrics/cast_metrics_helper.cc |
diff --git a/chromecast/base/metrics/cast_metrics_helper.cc b/chromecast/base/metrics/cast_metrics_helper.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..e4923552e1ff328ae79a0e205c17eeb8efc0599d |
--- /dev/null |
+++ b/chromecast/base/metrics/cast_metrics_helper.cc |
@@ -0,0 +1,231 @@ |
+// Copyright 2014 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+#include "chromecast/base/metrics/cast_metrics_helper.h" |
+ |
+#include "base/bind.h" |
+#include "base/bind_helpers.h" |
+#include "base/location.h" |
+#include "base/message_loop/message_loop_proxy.h" |
+#include "base/metrics/histogram.h" |
+#include "base/metrics/user_metrics.h" |
+#include "chromecast/base/metrics/cast_histograms.h" |
+ |
+namespace chromecast { |
+namespace metrics { |
+ |
+// A useful macro to make sure current member function runs on the valid thread. |
+#define MAKE_SURE_THREAD(callback, ...) \ |
+ if (!message_loop_proxy_->BelongsToCurrentThread()) { \ |
+ message_loop_proxy_->PostTask(FROM_HERE, base::Bind( \ |
+ &CastMetricsHelper::callback, \ |
+ base::Unretained(this), ##__VA_ARGS__)); \ |
+ return; \ |
+ } |
+ |
+namespace { |
+ |
+CastMetricsHelper* g_instance = NULL; |
+ |
+// Displayed frames are logged in frames per second (but sampling can be over |
+// a longer period of time, e.g. 5 seconds). |
+const int kDisplayedFramesPerSecondPeriod = 1000000; |
+ |
+// Sample every 5 seconds, represented in microseconds. |
+const int kNominalVideoSamplePeriod = 5000000; |
+ |
+} // namespace |
+ |
+CastMetricsHelper* CastMetricsHelper::GetInstance() { |
+ DCHECK(g_instance); |
+ return g_instance; |
+} |
+ |
+CastMetricsHelper::CastMetricsHelper( |
+ scoped_refptr<base::MessageLoopProxy> message_loop_proxy) |
+ : message_loop_proxy_(message_loop_proxy), |
+ metrics_sink_(NULL) { |
+ DCHECK(message_loop_proxy_.get()); |
+ DCHECK(!g_instance); |
+ g_instance = this; |
+} |
+ |
+CastMetricsHelper::CastMetricsHelper() |
+ : metrics_sink_(NULL) { |
+ DCHECK(!g_instance); |
+ g_instance = this; |
+} |
+ |
+CastMetricsHelper::~CastMetricsHelper() { |
+ DCHECK_EQ(g_instance, this); |
+ g_instance = NULL; |
+} |
+ |
+void CastMetricsHelper::TagAppStart(const std::string& arg_app_name) { |
+ MAKE_SURE_THREAD(TagAppStart, arg_app_name); |
+ app_name_ = arg_app_name; |
+ app_start_time_ = base::TimeTicks::Now(); |
+ new_startup_time_ = true; |
+} |
+ |
+void CastMetricsHelper::LogMediaPlay() { |
+ MAKE_SURE_THREAD(LogMediaPlay); |
+ base::RecordComputedAction(GetMetricsNameWithAppName("MediaPlay", "")); |
+} |
+ |
+void CastMetricsHelper::LogMediaPause() { |
+ MAKE_SURE_THREAD(LogMediaPause); |
+ base::RecordComputedAction(GetMetricsNameWithAppName("MediaPause", "")); |
+} |
+ |
+void CastMetricsHelper::LogTimeToDisplayVideo() { |
+ if (!new_startup_time_) { // For faster check. |
+ return; |
+ } |
+ MAKE_SURE_THREAD(LogTimeToDisplayVideo); |
+ new_startup_time_ = false; |
+ base::TimeDelta launch_time = base::TimeTicks::Now() - app_start_time_; |
+ const std::string uma_name(GetMetricsNameWithAppName("Startup", |
+ "TimeToDisplayVideo")); |
+ LogMediumTimeHistogramEvent(uma_name, launch_time); |
+ LOG(INFO) << uma_name << " is " << launch_time.InSecondsF() << " seconds."; |
+} |
+ |
+void CastMetricsHelper::LogTimeToBufferAv(BufferingType buffering_type, |
+ base::TimeDelta time) { |
+ MAKE_SURE_THREAD(LogTimeToBufferAv, buffering_type, time); |
+ if (time < base::TimeDelta::FromSeconds(0)) { |
+ LOG(WARNING) << "Negative time"; |
+ return; |
+ } |
+ |
+ const std::string uma_name(GetMetricsNameWithAppName( |
+ "Media", |
+ (buffering_type == kInitialBuffering ? "TimeToBufferAv" : |
+ buffering_type == kBufferingAfterUnderrun ? |
+ "TimeToBufferAvAfterUnderrun" : |
+ buffering_type == kAbortedBuffering ? "TimeToBufferAvAfterAbort" : ""))); |
+ |
+ // Histogram from 250ms to 30s with 50 buckets. |
+ // The ratio between 2 consecutive buckets is: |
+ // exp( (ln(30000) - ln(250)) / 50 ) = 1.1 |
+ LogTimeHistogramEvent( |
+ uma_name, |
+ time, |
+ base::TimeDelta::FromMilliseconds(250), |
+ base::TimeDelta::FromMilliseconds(30000), |
+ 50); |
+} |
+ |
+void CastMetricsHelper::ResetVideoFrameSampling() { |
+ MAKE_SURE_THREAD(ResetVideoFrameSampling); |
+ previous_video_stat_sample_time_ = base::TimeTicks(); |
+} |
+ |
+void CastMetricsHelper::LogFramesPer5Seconds(int displayed_frames, |
+ int dropped_frames, |
+ int delayed_frames, |
+ int error_frames) { |
+ MAKE_SURE_THREAD(LogFramesPer5Seconds, displayed_frames, dropped_frames, |
+ delayed_frames, error_frames); |
+ base::TimeTicks sample_time = base::TimeTicks::Now(); |
+ |
+ if (!previous_video_stat_sample_time_.is_null()) { |
+ base::TimeDelta time_diff = sample_time - previous_video_stat_sample_time_; |
+ int value = 0; |
+ const int64 rounding = time_diff.InMicroseconds() / 2; |
+ |
+ if (displayed_frames >= 0) { |
+ value = (displayed_frames * kDisplayedFramesPerSecondPeriod + rounding) / |
+ time_diff.InMicroseconds(); |
+ LogEnumerationHistogramEvent( |
+ GetMetricsNameWithAppName("Media", "DisplayedFramesPerSecond"), |
+ value, 50); |
+ } |
+ if (delayed_frames >= 0) { |
+ value = (delayed_frames * kNominalVideoSamplePeriod + rounding) / |
+ time_diff.InMicroseconds(); |
+ LogEnumerationHistogramEvent( |
+ GetMetricsNameWithAppName("Media", "DelayedVideoFramesPer5Sec"), |
+ value, 50); |
+ } |
+ if (dropped_frames >= 0) { |
+ value = (dropped_frames * kNominalVideoSamplePeriod + rounding) / |
+ time_diff.InMicroseconds(); |
+ LogEnumerationHistogramEvent( |
+ GetMetricsNameWithAppName("Media", "DroppedVideoFramesPer5Sec"), |
+ value, 50); |
+ } |
+ if (error_frames >= 0) { |
+ value = (error_frames * kNominalVideoSamplePeriod + rounding) / |
+ time_diff.InMicroseconds(); |
+ LogEnumerationHistogramEvent( |
+ GetMetricsNameWithAppName("Media", "ErrorVideoFramesPer5Sec"), |
+ value, 50); |
+ } |
+ } |
+ |
+ previous_video_stat_sample_time_ = sample_time; |
+} |
+ |
+std::string CastMetricsHelper::GetMetricsNameWithAppName( |
+ const std::string& prefix, |
+ const std::string& suffix) const { |
+ DCHECK(message_loop_proxy_->BelongsToCurrentThread()); |
+ std::string metrics_name(prefix); |
+ if (!app_name_.empty()) { |
+ if (!metrics_name.empty()) |
+ metrics_name.push_back('.'); |
+ metrics_name.append(app_name_); |
+ } |
+ if (!suffix.empty()) { |
+ if (!metrics_name.empty()) |
+ metrics_name.push_back('.'); |
+ metrics_name.append(suffix); |
+ } |
+ return metrics_name; |
+} |
+ |
+void CastMetricsHelper::SetMetricsSink(MetricsSink* delegate) { |
+ MAKE_SURE_THREAD(SetMetricsSink, delegate); |
+ metrics_sink_ = delegate; |
+} |
+ |
+void CastMetricsHelper::LogEnumerationHistogramEvent( |
+ const std::string& name, int value, int num_buckets) { |
+ MAKE_SURE_THREAD(LogEnumerationHistogramEvent, name, value, num_buckets); |
+ |
+ if (metrics_sink_) { |
+ metrics_sink_->OnEnumerationEvent(name, value, num_buckets); |
+ } else { |
+ UMA_HISTOGRAM_ENUMERATION_NO_CACHE(name, value, num_buckets); |
+ } |
+} |
+ |
+void CastMetricsHelper::LogTimeHistogramEvent(const std::string& name, |
+ const base::TimeDelta& value, |
+ const base::TimeDelta& min, |
+ const base::TimeDelta& max, |
+ int num_buckets) { |
+ MAKE_SURE_THREAD(LogTimeHistogramEvent, name, value, min, max, num_buckets); |
+ |
+ if (metrics_sink_) { |
+ metrics_sink_->OnTimeEvent(name, value, min, max, num_buckets); |
+ } else { |
+ UMA_HISTOGRAM_CUSTOM_TIMES_NO_CACHE(name, value, min, max, num_buckets); |
+ } |
+} |
+ |
+void CastMetricsHelper::LogMediumTimeHistogramEvent( |
+ const std::string& name, |
+ const base::TimeDelta& value) { |
+ // Follow UMA_HISTOGRAM_MEDIUM_TIMES definition. |
+ LogTimeHistogramEvent(name, value, |
+ base::TimeDelta::FromMilliseconds(10), |
+ base::TimeDelta::FromMinutes(3), |
+ 50); |
+} |
+ |
+} // namespace metrics |
+} // namespace chromecast |