Index: webrtc/logging/rtc_event_log/rtc_event_log2stats.cc |
diff --git a/webrtc/logging/rtc_event_log/rtc_event_log2stats.cc b/webrtc/logging/rtc_event_log/rtc_event_log2stats.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..8d24e318e79c1b91d907a47bf4325916836c82e7 |
--- /dev/null |
+++ b/webrtc/logging/rtc_event_log/rtc_event_log2stats.cc |
@@ -0,0 +1,252 @@ |
+/* |
+ * Copyright (c) 2017 The WebRTC project authors. All Rights Reserved. |
+ * |
+ * Use of this source code is governed by a BSD-style license |
+ * that can be found in the LICENSE file in the root of the source |
+ * tree. An additional intellectual property rights grant can be found |
+ * in the file PATENTS. All contributing project authors may |
+ * be found in the AUTHORS file in the root of the source tree. |
+ */ |
+ |
+#include <inttypes.h> |
+#include <stdio.h> |
+ |
+#include <fstream> |
+#include <iostream> |
+#include <map> |
+#include <string> |
+#include <tuple> |
+#include <utility> |
+#include <vector> |
+ |
+#include "gflags/gflags.h" |
+#include "webrtc/base/checks.h" |
+#include "webrtc/base/ignore_wundef.h" |
+#include "webrtc/base/logging.h" |
+#include "webrtc/logging/rtc_event_log/rtc_event_log.h" |
+ |
+// Files generated at build-time by the protobuf compiler. |
+RTC_PUSH_IGNORING_WUNDEF() |
+#ifdef WEBRTC_ANDROID_PLATFORM_BUILD |
+#include "external/webrtc/webrtc/logging/rtc_event_log/rtc_event_log.pb.h" |
+#else |
+#include "webrtc/logging/rtc_event_log/rtc_event_log.pb.h" |
+#endif |
+RTC_POP_IGNORING_WUNDEF() |
+ |
+namespace { |
+ |
+struct Stats { |
+ int count = 0; |
+ size_t total_size = 0; |
+}; |
+ |
+// We are duplicating some parts of the parser here because we want to get |
+// access to raw protobuf events. |
+std::pair<uint64_t, bool> ParseVarInt(std::istream& stream) { |
+ uint64_t varint = 0; |
+ for (size_t bytes_read = 0; bytes_read < 10; ++bytes_read) { |
+ // The most significant bit of each byte is 0 if it is the last byte in |
+ // the varint and 1 otherwise. Thus, we take the 7 least significant bits |
+ // of each byte and shift them 7 bits for each byte read previously to get |
+ // the (unsigned) integer. |
+ int byte = stream.get(); |
+ if (stream.eof()) { |
+ return std::make_pair(varint, false); |
+ } |
+ RTC_DCHECK(0 <= byte && byte <= 255); |
+ varint |= static_cast<uint64_t>(byte & 0x7F) << (7 * bytes_read); |
+ if ((byte & 0x80) == 0) { |
+ return std::make_pair(varint, true); |
+ } |
+ } |
+ return std::make_pair(varint, false); |
+} |
+ |
+bool ParseEvents(const std::string& filename, |
+ std::vector<webrtc::rtclog::Event>* events) { |
+ std::ifstream stream(filename, std::ios_base::in | std::ios_base::binary); |
+ if (!stream.good() || !stream.is_open()) { |
+ LOG(LS_WARNING) << "Could not open file for reading."; |
+ return false; |
+ } |
+ |
+ const size_t kMaxEventSize = (1u << 16) - 1; |
+ std::vector<char> tmp_buffer(kMaxEventSize); |
+ uint64_t tag; |
+ uint64_t message_length; |
+ bool success; |
+ |
+ RTC_DCHECK(stream.good()); |
+ |
+ while (1) { |
+ // Check whether we have reached end of file. |
+ stream.peek(); |
+ if (stream.eof()) { |
+ return true; |
+ } |
+ |
+ // Read the next message tag. The tag number is defined as |
+ // (fieldnumber << 3) | wire_type. In our case, the field number is |
+ // supposed to be 1 and the wire type for an length-delimited field is 2. |
+ const uint64_t kExpectedTag = (1 << 3) | 2; |
+ std::tie(tag, success) = ParseVarInt(stream); |
+ if (!success) { |
+ LOG(LS_WARNING) << "Missing field tag from beginning of protobuf event."; |
+ return false; |
+ } else if (tag != kExpectedTag) { |
+ LOG(LS_WARNING) << "Unexpected field tag at beginning of protobuf event."; |
+ return false; |
+ } |
+ |
+ // Read the length field. |
+ std::tie(message_length, success) = ParseVarInt(stream); |
+ if (!success) { |
+ LOG(LS_WARNING) << "Missing message length after protobuf field tag."; |
+ return false; |
+ } else if (message_length > kMaxEventSize) { |
+ LOG(LS_WARNING) << "Protobuf message length is too large."; |
+ return false; |
+ } |
+ |
+ // Read the next protobuf event to a temporary char buffer. |
+ stream.read(tmp_buffer.data(), message_length); |
+ if (stream.gcount() != static_cast<int>(message_length)) { |
+ LOG(LS_WARNING) << "Failed to read protobuf message from file."; |
+ return false; |
+ } |
+ |
+ // Parse the protobuf event from the buffer. |
+ webrtc::rtclog::Event event; |
+ if (!event.ParseFromArray(tmp_buffer.data(), message_length)) { |
+ LOG(LS_WARNING) << "Failed to parse protobuf message."; |
+ return false; |
+ } |
+ events->push_back(event); |
+ } |
+} |
+ |
+// TODO(terelius): Should this be placed in some utility file instead? |
+std::string EventTypeToString(webrtc::rtclog::Event::EventType event_type) { |
+ switch (event_type) { |
+ case webrtc::rtclog::Event::UNKNOWN_EVENT: |
+ return "UNKNOWN_EVENT"; |
+ case webrtc::rtclog::Event::LOG_START: |
+ return "LOG_START"; |
+ case webrtc::rtclog::Event::LOG_END: |
+ return "LOG_END"; |
+ case webrtc::rtclog::Event::RTP_EVENT: |
+ return "RTP_EVENT"; |
+ case webrtc::rtclog::Event::RTCP_EVENT: |
+ return "RTCP_EVENT"; |
+ case webrtc::rtclog::Event::AUDIO_PLAYOUT_EVENT: |
+ return "AUDIO_PLAYOUT_EVENT"; |
+ case webrtc::rtclog::Event::LOSS_BASED_BWE_UPDATE: |
+ return "LOSS_BASED_BWE_UPDATE"; |
+ case webrtc::rtclog::Event::DELAY_BASED_BWE_UPDATE: |
+ return "DELAY_BASED_BWE_UPDATE"; |
+ case webrtc::rtclog::Event::VIDEO_RECEIVER_CONFIG_EVENT: |
+ return "VIDEO_RECV_CONFIG"; |
+ case webrtc::rtclog::Event::VIDEO_SENDER_CONFIG_EVENT: |
+ return "VIDEO_SEND_CONFIG"; |
+ case webrtc::rtclog::Event::AUDIO_RECEIVER_CONFIG_EVENT: |
+ return "AUDIO_RECV_CONFIG"; |
+ case webrtc::rtclog::Event::AUDIO_SENDER_CONFIG_EVENT: |
+ return "AUDIO_SEND_CONFIG"; |
+ case webrtc::rtclog::Event::AUDIO_NETWORK_ADAPTATION_EVENT: |
+ return "AUDIO_NETWORK_ADAPTATION"; |
+ case webrtc::rtclog::Event::BWE_PROBE_CLUSTER_CREATED_EVENT: |
+ return "BWE_PROBE_CREATED"; |
+ case webrtc::rtclog::Event::BWE_PROBE_RESULT_EVENT: |
+ return "BWE_PROBE_RESULT"; |
+ } |
+ RTC_NOTREACHED(); |
+ return "UNKNOWN_EVENT"; |
+} |
+ |
+} // namespace |
+ |
+// This utility will print basic information about each packet to stdout. |
+// Note that parser will assert if the protobuf event is missing some required |
+// fields and we attempt to access them. We don't handle this at the moment. |
+int main(int argc, char* argv[]) { |
+ std::string program_name = argv[0]; |
+ std::string usage = |
+ "Tool for file usage statistics from an RtcEventLog.\n" |
+ "Run " + |
+ program_name + |
+ " --helpshort for usage.\n" |
+ "Example usage:\n" + |
+ program_name + " input.rel\n"; |
+ google::SetUsageMessage(usage); |
+ google::ParseCommandLineFlags(&argc, &argv, true); |
+ |
+ if (argc != 2) { |
+ std::cout << google::ProgramUsage(); |
+ return 0; |
+ } |
+ std::string file_name = argv[1]; |
+ |
+ std::vector<webrtc::rtclog::Event> events; |
+ if (!ParseEvents(file_name, &events)) { |
+ LOG(LS_ERROR) << "Failed to parse event log."; |
+ return -1; |
+ } |
+ |
+ // Get file size |
+ FILE* file = fopen(file_name.c_str(), "rb"); |
+ fseek(file, 0L, SEEK_END); |
+ int64_t file_size = ftell(file); |
+ fclose(file); |
+ |
+ // We are deliberately using low level protobuf functions to get the stats |
+ // since the convenience functions in the parser would CHECK that the events |
+ // are well formed. |
+ std::map<webrtc::rtclog::Event::EventType, Stats> stats; |
+ int malformed_events = 0; |
+ size_t malformed_event_size = 0; |
+ size_t accumulated_event_size = 0; |
+ for (const webrtc::rtclog::Event& event : events) { |
+ size_t serialized_size = event.ByteSize(); |
+ // When the event is written on the disk, it is part of an EventStream |
+ // object. The event stream will prepend a 1 byte field number/wire type, |
+ // and a varint encoding (base 128) of the event length. |
+ serialized_size = |
+ 1 + (1 + (serialized_size > 127) + (serialized_size > 16383)) + |
+ serialized_size; |
+ |
+ if (event.has_type() && event.has_timestamp_us()) { |
+ stats[event.type()].count++; |
+ stats[event.type()].total_size += serialized_size; |
+ } else { |
+ // The event is missing the type or the timestamp field. |
+ malformed_events++; |
+ malformed_event_size += serialized_size; |
+ } |
+ accumulated_event_size += serialized_size; |
+ } |
+ |
+ printf("Type \tCount\tTotal size\tAverage size\tPercent\n"); |
+ printf( |
+ "-----------------------------------------------------------------------" |
+ "\n"); |
+ for (const auto it : stats) { |
+ printf("%-22s\t%5d\t%10zu\t%12.2lf\t%7.2lf\n", |
+ EventTypeToString(it.first).c_str(), it.second.count, |
+ it.second.total_size, |
+ static_cast<double>(it.second.total_size) / it.second.count, |
+ static_cast<double>(it.second.total_size) / file_size * 100); |
+ } |
+ if (malformed_events != 0) { |
+ printf("%-22s\t%5d\t%10zu\t%12.2lf\t%7.2lf\n", "MALFORMED", |
+ malformed_events, malformed_event_size, |
+ static_cast<double>(malformed_event_size) / malformed_events, |
+ static_cast<double>(malformed_event_size) / file_size * 100); |
+ } |
+ if (file_size - accumulated_event_size != 0) { |
+ printf("WARNING: %" PRId64 " bytes not accounted for\n", |
+ file_size - accumulated_event_size); |
+ } |
+ |
+ return 0; |
+} |