/* * 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 #include #include #include #include #include #include #include #include #include "logging/rtc_event_log/rtc_event_log.h" #include "rtc_base/checks.h" #include "rtc_base/flags.h" #include "rtc_base/ignore_wundef.h" #include "rtc_base/logging.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 "logging/rtc_event_log/rtc_event_log.pb.h" #endif RTC_POP_IGNORING_WUNDEF() namespace { DEFINE_bool(help, false, "Prints this message."); 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 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_GE(byte, 0); RTC_DCHECK_LE(byte, 255); varint |= static_cast(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* events) { std::ifstream stream(filename, std::ios_base::in | std::ios_base::binary); if (!stream.good() || !stream.is_open()) { RTC_LOG(LS_WARNING) << "Could not open file for reading."; return false; } const size_t kMaxEventSize = (1u << 16) - 1; std::vector 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) { RTC_LOG(LS_WARNING) << "Missing field tag from beginning of protobuf event."; return false; } else if (tag != kExpectedTag) { RTC_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) { RTC_LOG(LS_WARNING) << "Missing message length after protobuf field tag."; return false; } else if (message_length > kMaxEventSize) { RTC_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(message_length)) { RTC_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)) { RTC_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"; case webrtc::rtclog::Event::ALR_STATE_EVENT: return "ALR_STATE_EVENT"; case webrtc::rtclog::Event::ICE_CANDIDATE_PAIR_CONFIG: return "ICE_CANDIDATE_PAIR_CONFIG"; case webrtc::rtclog::Event::ICE_CANDIDATE_PAIR_EVENT: return "ICE_CANDIDATE_PAIR_EVENT"; } 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 + " --help for usage.\n" "Example usage:\n" + program_name + " input.rel\n"; if (rtc::FlagList::SetFlagsFromCommandLine(&argc, argv, true) || FLAG_help || argc != 2) { std::cout << usage; if (FLAG_help) { rtc::FlagList::Print(nullptr, false); return 0; } return 1; } std::string file_name = argv[1]; std::vector events; if (!ParseEvents(file_name, &events)) { RTC_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 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.ByteSizeLong(); // 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(it.second.total_size) / it.second.count, static_cast(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(malformed_event_size) / malformed_events, static_cast(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; }