Index: media/cast/logging/stats_event_subscriber.cc |
diff --git a/media/cast/logging/stats_event_subscriber.cc b/media/cast/logging/stats_event_subscriber.cc |
index 03c669cfcbcf4e2a0173079fdae6fbdc1b7426e1..87e5b425fa0be4c5b3a11a1e6e898c65a7b5f3b5 100644 |
--- a/media/cast/logging/stats_event_subscriber.cc |
+++ b/media/cast/logging/stats_event_subscriber.cc |
@@ -4,7 +4,9 @@ |
#include "media/cast/logging/stats_event_subscriber.h" |
+#include "base/format_macros.h" |
#include "base/logging.h" |
+#include "base/strings/stringprintf.h" |
#include "base/values.h" |
#define STAT_ENUM_TO_STRING(enum) \ |
@@ -30,6 +32,63 @@ bool IsReceiverEvent(CastLoggingEvent event) { |
} // namespace |
+StatsEventSubscriber::SimpleHistogram::SimpleHistogram(int64 min, |
+ int64 max, |
+ int64 width) |
+ : min_(min), max_(max), width_(width), buckets_((max - min) / width + 2) { |
+ CHECK_GT(buckets_.size(), 2u); |
+ CHECK_EQ(0, (max_ - min_) % width_); |
+} |
+ |
+StatsEventSubscriber::SimpleHistogram::~SimpleHistogram() { |
+} |
+ |
+void StatsEventSubscriber::SimpleHistogram::Add(int64 sample) { |
+ if (sample < min_) { |
+ ++buckets_.front(); |
+ } else if (sample >= max_) { |
+ ++buckets_.back(); |
+ } else { |
+ size_t index = 1 + (sample - min_) / width_; |
+ DCHECK_LT(index, buckets_.size()); |
+ ++buckets_[index]; |
+ } |
+} |
+ |
+void StatsEventSubscriber::SimpleHistogram::Reset() { |
+ buckets_.assign(buckets_.size(), 0); |
+} |
+ |
+scoped_ptr<base::ListValue> |
+StatsEventSubscriber::SimpleHistogram::GetHistogram() const { |
+ scoped_ptr<base::ListValue> histo(new base::ListValue); |
+ |
+ scoped_ptr<base::DictionaryValue> bucket(new base::DictionaryValue); |
+ |
+ bucket->SetString("bucket", base::StringPrintf("< %" PRId64, min_)); |
+ bucket->SetInteger("count", buckets_.front()); |
+ histo->Append(bucket.release()); |
+ |
+ for (size_t i = 1; i < buckets_.size() - 1; i++) { |
+ bucket.reset(new base::DictionaryValue); |
+ |
+ int64 lower = min_ + (i - 1) * width_; |
+ int64 upper = lower + width_ - 1; |
+ bucket->SetString( |
+ "bucket", base::StringPrintf("%" PRId64 " - %" PRId64, lower, upper)); |
+ bucket->SetInteger("count", buckets_[i]); |
+ histo->Append(bucket.release()); |
+ } |
+ |
+ bucket.reset(new base::DictionaryValue); |
+ |
+ bucket->SetString("bucket", base::StringPrintf(">= %" PRId64, max_)); |
+ bucket->SetInteger("count", buckets_.back()); |
+ histo->Append(bucket.release()); |
+ |
+ return histo.Pass(); |
+} |
+ |
StatsEventSubscriber::StatsEventSubscriber( |
EventMediaType event_media_type, |
base::TickClock* clock, |
@@ -40,11 +99,11 @@ StatsEventSubscriber::StatsEventSubscriber( |
network_latency_datapoints_(0), |
e2e_latency_datapoints_(0), |
num_frames_dropped_by_encoder_(0), |
- num_frames_late_(0) { |
+ num_frames_late_(0), |
+ start_time_(clock_->NowTicks()) { |
DCHECK(event_media_type == AUDIO_EVENT || event_media_type == VIDEO_EVENT); |
- base::TimeTicks now = clock_->NowTicks(); |
- start_time_ = now; |
- last_response_received_time_ = base::TimeTicks(); |
+ |
+ InitHistograms(); |
} |
StatsEventSubscriber::~StatsEventSubscriber() { |
@@ -71,17 +130,26 @@ void StatsEventSubscriber::OnReceiveFrameEvent(const FrameEvent& frame_event) { |
it->second.sum_delay += frame_event.delay_delta; |
} |
+ bool is_receiver_event = IsReceiverEvent(type); |
+ UpdateFirstLastEventTime(frame_event.timestamp, is_receiver_event); |
+ |
if (type == FRAME_CAPTURE_BEGIN) { |
RecordFrameCaptureTime(frame_event); |
+ } else if (type == FRAME_CAPTURE_END) { |
+ RecordCaptureLatency(frame_event); |
} else if (type == FRAME_ENCODED) { |
- MarkAsEncoded(frame_event.rtp_timestamp); |
+ RecordEncodeLatency(frame_event); |
+ } else if (type == FRAME_ACK_SENT) { |
+ RecordFrameTxLatency(frame_event); |
} else if (type == FRAME_PLAYOUT) { |
RecordE2ELatency(frame_event); |
- if (frame_event.delay_delta <= base::TimeDelta()) |
+ base::TimeDelta delay_delta = frame_event.delay_delta; |
+ histograms_[PLAYOUT_DELAY_MS_HISTO]->Add(delay_delta.InMillisecondsF()); |
+ if (delay_delta <= base::TimeDelta()) |
num_frames_late_++; |
} |
- if (IsReceiverEvent(type)) |
+ if (is_receiver_event) |
UpdateLastResponseTime(frame_event.timestamp); |
} |
@@ -104,6 +172,9 @@ void StatsEventSubscriber::OnReceivePacketEvent( |
it->second.sum_size += packet_event.size; |
} |
+ bool is_receiver_event = IsReceiverEvent(type); |
+ UpdateFirstLastEventTime(packet_event.timestamp, is_receiver_event); |
+ |
if (type == PACKET_SENT_TO_NETWORK || |
type == PACKET_RECEIVED) { |
RecordNetworkLatency(packet_event); |
@@ -113,10 +184,31 @@ void StatsEventSubscriber::OnReceivePacketEvent( |
ErasePacketSentTime(packet_event); |
} |
- if (IsReceiverEvent(type)) |
+ if (is_receiver_event) |
UpdateLastResponseTime(packet_event.timestamp); |
} |
+void StatsEventSubscriber::UpdateFirstLastEventTime(base::TimeTicks timestamp, |
+ bool is_receiver_event) { |
+ if (is_receiver_event) { |
+ base::TimeDelta receiver_offset; |
+ if (!GetReceiverOffset(&receiver_offset)) |
+ return; |
+ timestamp -= receiver_offset; |
+ } |
+ |
+ if (first_event_time_.is_null()) { |
+ first_event_time_ = timestamp; |
+ } else { |
+ first_event_time_ = std::min(first_event_time_, timestamp); |
+ } |
+ if (last_event_time_.is_null()) { |
+ last_event_time_ = timestamp; |
+ } else { |
+ last_event_time_ = std::max(last_event_time_, timestamp); |
+ } |
+} |
+ |
scoped_ptr<base::DictionaryValue> StatsEventSubscriber::GetStats() const { |
StatsMap stats_map; |
GetStatsInternal(&stats_map); |
@@ -127,6 +219,12 @@ scoped_ptr<base::DictionaryValue> StatsEventSubscriber::GetStats() const { |
++it) { |
stats->SetDouble(CastStatToString(it->first), it->second); |
} |
+ for (HistogramMap::const_iterator it = histograms_.begin(); |
+ it != histograms_.end(); |
+ ++it) { |
+ stats->Set(CastStatToString(it->first), |
+ it->second->GetHistogram().release()); |
+ } |
ret->Set(event_media_type_ == AUDIO_EVENT ? "audio" : "video", |
stats.release()); |
@@ -145,10 +243,17 @@ void StatsEventSubscriber::Reset() { |
e2e_latency_datapoints_ = 0; |
num_frames_dropped_by_encoder_ = 0; |
num_frames_late_ = 0; |
- recent_captured_frames_.clear(); |
+ recent_frame_infos_.clear(); |
packet_sent_times_.clear(); |
start_time_ = clock_->NowTicks(); |
last_response_received_time_ = base::TimeTicks(); |
+ for (HistogramMap::iterator it = histograms_.begin(); it != histograms_.end(); |
+ ++it) { |
+ it->second->Reset(); |
+ } |
+ |
+ first_event_time_ = base::TimeTicks(); |
+ last_event_time_ = base::TimeTicks(); |
} |
// static |
@@ -172,17 +277,43 @@ const char* StatsEventSubscriber::CastStatToString(CastStat stat) { |
STAT_ENUM_TO_STRING(NUM_PACKETS_SENT); |
STAT_ENUM_TO_STRING(NUM_PACKETS_RETRANSMITTED); |
STAT_ENUM_TO_STRING(NUM_PACKETS_RTX_REJECTED); |
+ STAT_ENUM_TO_STRING(DURATION_MS); |
Alpha Left Google
2014/08/22 01:49:04
This seems redundant since we have first event tim
imcheng
2014/08/22 19:14:17
Removed.
|
+ STAT_ENUM_TO_STRING(FIRST_EVENT_TIME_MS); |
+ STAT_ENUM_TO_STRING(LAST_EVENT_TIME_MS); |
+ STAT_ENUM_TO_STRING(CAPTURE_LATENCY_MS_HISTO); |
+ STAT_ENUM_TO_STRING(ENCODE_LATENCY_MS_HISTO); |
+ STAT_ENUM_TO_STRING(PACKET_LATENCY_MS_HISTO); |
+ STAT_ENUM_TO_STRING(FRAME_LATENCY_MS_HISTO); |
+ STAT_ENUM_TO_STRING(PLAYOUT_DELAY_MS_HISTO); |
} |
NOTREACHED(); |
return ""; |
} |
+const int kMaxLatencyBucketMs = 800; |
+const int kBucketWidthMs = 20; |
+ |
+void StatsEventSubscriber::InitHistograms() { |
+ histograms_[CAPTURE_LATENCY_MS_HISTO].reset( |
+ new SimpleHistogram(0, kMaxLatencyBucketMs, kBucketWidthMs)); |
+ histograms_[ENCODE_LATENCY_MS_HISTO].reset( |
+ new SimpleHistogram(0, kMaxLatencyBucketMs, kBucketWidthMs)); |
+ histograms_[PACKET_LATENCY_MS_HISTO].reset( |
+ new SimpleHistogram(0, kMaxLatencyBucketMs, kBucketWidthMs)); |
+ histograms_[FRAME_LATENCY_MS_HISTO].reset( |
+ new SimpleHistogram(0, kMaxLatencyBucketMs, kBucketWidthMs)); |
+ histograms_[PLAYOUT_DELAY_MS_HISTO].reset( |
+ new SimpleHistogram(0, kMaxLatencyBucketMs, kBucketWidthMs)); |
+} |
+ |
void StatsEventSubscriber::GetStatsInternal(StatsMap* stats_map) const { |
DCHECK(thread_checker_.CalledOnValidThread()); |
stats_map->clear(); |
base::TimeTicks end_time = clock_->NowTicks(); |
+ stats_map->insert( |
+ std::make_pair(DURATION_MS, (end_time - start_time_).InMillisecondsF())); |
PopulateFpsStat( |
end_time, FRAME_CAPTURE_BEGIN, CAPTURE_FPS, stats_map); |
@@ -231,6 +362,12 @@ void StatsEventSubscriber::GetStatsInternal(StatsMap* stats_map) const { |
stats_map->insert(std::make_pair(NUM_FRAMES_DROPPED_BY_ENCODER, |
num_frames_dropped_by_encoder_)); |
stats_map->insert(std::make_pair(NUM_FRAMES_LATE, num_frames_late_)); |
+ stats_map->insert(std::make_pair( |
+ FIRST_EVENT_TIME_MS, |
+ (first_event_time_ - base::TimeTicks::UnixEpoch()).InMillisecondsF())); |
+ stats_map->insert(std::make_pair( |
+ LAST_EVENT_TIME_MS, |
+ (last_event_time_ - base::TimeTicks::UnixEpoch()).InMillisecondsF())); |
} |
bool StatsEventSubscriber::GetReceiverOffset(base::TimeDelta* offset) { |
@@ -245,22 +382,83 @@ bool StatsEventSubscriber::GetReceiverOffset(base::TimeDelta* offset) { |
return true; |
} |
+void StatsEventSubscriber::MaybeInsertFrameInfo(RtpTimestamp rtp_timestamp, |
+ const FrameInfo& frame_info) { |
+ // No need to insert if |rtp_timestamp| is the smaller than every key in the |
+ // map as it is just going to get erased anyway. |
+ if (recent_frame_infos_.size() == kMaxFrameInfoMapSize && |
+ rtp_timestamp < recent_frame_infos_.begin()->first) { |
+ return; |
+ } |
+ |
+ recent_frame_infos_.insert(std::make_pair(rtp_timestamp, frame_info)); |
+ |
+ if (recent_frame_infos_.size() >= kMaxFrameInfoMapSize) { |
+ FrameInfoMap::iterator erase_it = recent_frame_infos_.begin(); |
+ if (erase_it->second.encode_time.is_null()) |
+ num_frames_dropped_by_encoder_++; |
+ recent_frame_infos_.erase(erase_it); |
+ } |
+} |
+ |
void StatsEventSubscriber::RecordFrameCaptureTime( |
const FrameEvent& frame_event) { |
- recent_captured_frames_.insert(std::make_pair( |
- frame_event.rtp_timestamp, FrameInfo(frame_event.timestamp))); |
- if (recent_captured_frames_.size() > kMaxFrameInfoMapSize) { |
- FrameInfoMap::iterator erase_it = recent_captured_frames_.begin(); |
- if (!erase_it->second.encoded) |
- num_frames_dropped_by_encoder_++; |
- recent_captured_frames_.erase(erase_it); |
+ FrameInfo frame_info; |
+ frame_info.capture_time = frame_event.timestamp; |
+ MaybeInsertFrameInfo(frame_event.rtp_timestamp, frame_info); |
+} |
+ |
+void StatsEventSubscriber::RecordCaptureLatency(const FrameEvent& frame_event) { |
+ FrameInfoMap::iterator it = |
+ recent_frame_infos_.find(frame_event.rtp_timestamp); |
+ if (it == recent_frame_infos_.end()) |
+ return; |
+ |
+ if (!it->second.capture_time.is_null()) { |
+ double capture_latency_ms = |
+ (it->second.capture_time - frame_event.timestamp).InMillisecondsF(); |
+ histograms_[CAPTURE_LATENCY_MS_HISTO]->Add(capture_latency_ms); |
} |
+ |
+ it->second.capture_end_time = frame_event.timestamp; |
} |
-void StatsEventSubscriber::MarkAsEncoded(RtpTimestamp rtp_timestamp) { |
- FrameInfoMap::iterator it = recent_captured_frames_.find(rtp_timestamp); |
- if (it != recent_captured_frames_.end()) |
- it->second.encoded = true; |
+void StatsEventSubscriber::RecordEncodeLatency(const FrameEvent& frame_event) { |
+ FrameInfoMap::iterator it = |
+ recent_frame_infos_.find(frame_event.rtp_timestamp); |
+ if (it == recent_frame_infos_.end()) { |
+ FrameInfo frame_info; |
+ frame_info.encode_time = frame_event.timestamp; |
+ MaybeInsertFrameInfo(frame_event.rtp_timestamp, frame_info); |
+ return; |
+ } |
+ |
+ if (!it->second.capture_end_time.is_null()) { |
+ double encode_latency_ms = |
+ (frame_event.timestamp - it->second.capture_end_time).InMillisecondsF(); |
+ histograms_[ENCODE_LATENCY_MS_HISTO]->Add(encode_latency_ms); |
+ } |
+ |
+ it->second.encode_time = frame_event.timestamp; |
+} |
+ |
+void StatsEventSubscriber::RecordFrameTxLatency(const FrameEvent& frame_event) { |
+ FrameInfoMap::iterator it = |
+ recent_frame_infos_.find(frame_event.rtp_timestamp); |
+ if (it == recent_frame_infos_.end()) |
+ return; |
+ |
+ if (it->second.encode_time.is_null()) |
+ return; |
+ |
+ base::TimeDelta receiver_offset; |
+ if (!GetReceiverOffset(&receiver_offset)) |
+ return; |
+ |
+ base::TimeTicks sender_time = frame_event.timestamp - receiver_offset; |
+ double frame_tx_latency_ms = |
+ (sender_time - it->second.encode_time).InMillisecondsF(); |
+ histograms_[FRAME_LATENCY_MS_HISTO]->Add(frame_tx_latency_ms); |
} |
void StatsEventSubscriber::RecordE2ELatency(const FrameEvent& frame_event) { |
@@ -269,8 +467,8 @@ void StatsEventSubscriber::RecordE2ELatency(const FrameEvent& frame_event) { |
return; |
FrameInfoMap::iterator it = |
- recent_captured_frames_.find(frame_event.rtp_timestamp); |
- if (it == recent_captured_frames_.end()) |
+ recent_frame_infos_.find(frame_event.rtp_timestamp); |
+ if (it == recent_frame_infos_.end()) |
return; |
// Playout time is event time + playout delay. |
@@ -306,8 +504,6 @@ void StatsEventSubscriber::RecordNetworkLatency( |
std::make_pair(packet_event.rtp_timestamp, packet_event.packet_id)); |
PacketEventTimeMap::iterator it = packet_sent_times_.find(key); |
if (it == packet_sent_times_.end()) { |
- std::pair<RtpTimestamp, uint16> key( |
- std::make_pair(packet_event.rtp_timestamp, packet_event.packet_id)); |
std::pair<base::TimeTicks, CastLoggingEvent> value = |
std::make_pair(packet_event.timestamp, packet_event.type); |
packet_sent_times_.insert(std::make_pair(key, value)); |
@@ -333,9 +529,14 @@ void StatsEventSubscriber::RecordNetworkLatency( |
if (match) { |
// Subtract by offset. |
packet_received_time -= receiver_offset; |
+ base::TimeDelta latency_delta = packet_received_time - packet_sent_time; |
- total_network_latency_ += packet_received_time - packet_sent_time; |
+ total_network_latency_ += latency_delta; |
network_latency_datapoints_++; |
+ |
+ histograms_[PACKET_LATENCY_MS_HISTO]->Add( |
+ latency_delta.InMillisecondsF()); |
+ |
packet_sent_times_.erase(it); |
} |
} |
@@ -447,8 +648,7 @@ StatsEventSubscriber::PacketLogStats::PacketLogStats() |
: event_counter(0), sum_size(0) {} |
StatsEventSubscriber::PacketLogStats::~PacketLogStats() {} |
-StatsEventSubscriber::FrameInfo::FrameInfo(base::TimeTicks capture_time) |
- : capture_time(capture_time), encoded(false) { |
+StatsEventSubscriber::FrameInfo::FrameInfo() : encoded(false) { |
} |
StatsEventSubscriber::FrameInfo::~FrameInfo() { |
} |