| 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;
|
| +}
|
|
|