Create tool to print statistics about the file size usage of an RTC event log.

BUG=webrtc:7502

Review-Url: https://codereview.webrtc.org/2717553004
Cr-Original-Commit-Position: refs/heads/master@{#17932}
Cr-Mirrored-From: https://chromium.googlesource.com/external/webrtc
Cr-Mirrored-Commit: ee37e86de8bde6c7af020f1b1d222c00ba9694a3
diff --git a/logging/BUILD.gn b/logging/BUILD.gn
index 6032ba6..ffc64a3 100644
--- a/logging/BUILD.gn
+++ b/logging/BUILD.gn
@@ -175,4 +175,23 @@
       }
     }
   }
+  if (rtc_include_tests) {
+    rtc_executable("rtc_event_log2stats") {
+      testonly = true
+      sources = [
+        "rtc_event_log/rtc_event_log2stats.cc",
+      ]
+      deps = [
+        ":rtc_event_log_api",
+        ":rtc_event_log_impl",
+        ":rtc_event_log_proto",
+        "../base:rtc_base_approved",
+        "//third_party/gflags",
+      ]
+      if (!build_with_chromium && is_clang) {
+        # Suppress warnings from the Chromium Clang plugin (bugs.webrtc.org/163).
+        suppressed_configs += [ "//build/config/clang:find_bad_constructs" ]
+      }
+    }
+  }
 }
diff --git a/logging/rtc_event_log/rtc_event_log2stats.cc b/logging/rtc_event_log/rtc_event_log2stats.cc
new file mode 100644
index 0000000..8d24e31
--- /dev/null
+++ b/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;
+}