terelius | ee37e86 | 2017-04-28 07:48:17 -0700 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (c) 2017 The WebRTC project authors. All Rights Reserved. |
| 3 | * |
| 4 | * Use of this source code is governed by a BSD-style license |
| 5 | * that can be found in the LICENSE file in the root of the source |
| 6 | * tree. An additional intellectual property rights grant can be found |
| 7 | * in the file PATENTS. All contributing project authors may |
| 8 | * be found in the AUTHORS file in the root of the source tree. |
| 9 | */ |
| 10 | |
| 11 | #include <inttypes.h> |
| 12 | #include <stdio.h> |
| 13 | |
| 14 | #include <fstream> |
| 15 | #include <iostream> |
| 16 | #include <map> |
| 17 | #include <string> |
| 18 | #include <tuple> |
| 19 | #include <utility> |
| 20 | #include <vector> |
| 21 | |
terelius | ee37e86 | 2017-04-28 07:48:17 -0700 | [diff] [blame] | 22 | #include "webrtc/logging/rtc_event_log/rtc_event_log.h" |
Edward Lemur | c20978e | 2017-07-06 19:44:34 +0200 | [diff] [blame] | 23 | #include "webrtc/rtc_base/checks.h" |
oprypin | 6e09d87 | 2017-08-31 03:21:39 -0700 | [diff] [blame] | 24 | #include "webrtc/rtc_base/flags.h" |
Edward Lemur | c20978e | 2017-07-06 19:44:34 +0200 | [diff] [blame] | 25 | #include "webrtc/rtc_base/ignore_wundef.h" |
| 26 | #include "webrtc/rtc_base/logging.h" |
terelius | ee37e86 | 2017-04-28 07:48:17 -0700 | [diff] [blame] | 27 | |
| 28 | // Files generated at build-time by the protobuf compiler. |
| 29 | RTC_PUSH_IGNORING_WUNDEF() |
| 30 | #ifdef WEBRTC_ANDROID_PLATFORM_BUILD |
| 31 | #include "external/webrtc/webrtc/logging/rtc_event_log/rtc_event_log.pb.h" |
| 32 | #else |
| 33 | #include "webrtc/logging/rtc_event_log/rtc_event_log.pb.h" |
| 34 | #endif |
| 35 | RTC_POP_IGNORING_WUNDEF() |
| 36 | |
| 37 | namespace { |
| 38 | |
oprypin | 6e09d87 | 2017-08-31 03:21:39 -0700 | [diff] [blame] | 39 | DEFINE_bool(help, false, "Prints this message."); |
| 40 | |
terelius | ee37e86 | 2017-04-28 07:48:17 -0700 | [diff] [blame] | 41 | struct Stats { |
| 42 | int count = 0; |
| 43 | size_t total_size = 0; |
| 44 | }; |
| 45 | |
| 46 | // We are duplicating some parts of the parser here because we want to get |
| 47 | // access to raw protobuf events. |
| 48 | std::pair<uint64_t, bool> ParseVarInt(std::istream& stream) { |
| 49 | uint64_t varint = 0; |
| 50 | for (size_t bytes_read = 0; bytes_read < 10; ++bytes_read) { |
| 51 | // The most significant bit of each byte is 0 if it is the last byte in |
| 52 | // the varint and 1 otherwise. Thus, we take the 7 least significant bits |
| 53 | // of each byte and shift them 7 bits for each byte read previously to get |
| 54 | // the (unsigned) integer. |
| 55 | int byte = stream.get(); |
| 56 | if (stream.eof()) { |
| 57 | return std::make_pair(varint, false); |
| 58 | } |
kwiberg | ee89e78 | 2017-08-09 17:22:01 -0700 | [diff] [blame] | 59 | RTC_DCHECK_GE(byte, 0); |
| 60 | RTC_DCHECK_LE(byte, 255); |
terelius | ee37e86 | 2017-04-28 07:48:17 -0700 | [diff] [blame] | 61 | varint |= static_cast<uint64_t>(byte & 0x7F) << (7 * bytes_read); |
| 62 | if ((byte & 0x80) == 0) { |
| 63 | return std::make_pair(varint, true); |
| 64 | } |
| 65 | } |
| 66 | return std::make_pair(varint, false); |
| 67 | } |
| 68 | |
| 69 | bool ParseEvents(const std::string& filename, |
| 70 | std::vector<webrtc::rtclog::Event>* events) { |
| 71 | std::ifstream stream(filename, std::ios_base::in | std::ios_base::binary); |
| 72 | if (!stream.good() || !stream.is_open()) { |
| 73 | LOG(LS_WARNING) << "Could not open file for reading."; |
| 74 | return false; |
| 75 | } |
| 76 | |
| 77 | const size_t kMaxEventSize = (1u << 16) - 1; |
| 78 | std::vector<char> tmp_buffer(kMaxEventSize); |
| 79 | uint64_t tag; |
| 80 | uint64_t message_length; |
| 81 | bool success; |
| 82 | |
| 83 | RTC_DCHECK(stream.good()); |
| 84 | |
| 85 | while (1) { |
| 86 | // Check whether we have reached end of file. |
| 87 | stream.peek(); |
| 88 | if (stream.eof()) { |
| 89 | return true; |
| 90 | } |
| 91 | |
| 92 | // Read the next message tag. The tag number is defined as |
| 93 | // (fieldnumber << 3) | wire_type. In our case, the field number is |
| 94 | // supposed to be 1 and the wire type for an length-delimited field is 2. |
| 95 | const uint64_t kExpectedTag = (1 << 3) | 2; |
| 96 | std::tie(tag, success) = ParseVarInt(stream); |
| 97 | if (!success) { |
| 98 | LOG(LS_WARNING) << "Missing field tag from beginning of protobuf event."; |
| 99 | return false; |
| 100 | } else if (tag != kExpectedTag) { |
| 101 | LOG(LS_WARNING) << "Unexpected field tag at beginning of protobuf event."; |
| 102 | return false; |
| 103 | } |
| 104 | |
| 105 | // Read the length field. |
| 106 | std::tie(message_length, success) = ParseVarInt(stream); |
| 107 | if (!success) { |
| 108 | LOG(LS_WARNING) << "Missing message length after protobuf field tag."; |
| 109 | return false; |
| 110 | } else if (message_length > kMaxEventSize) { |
| 111 | LOG(LS_WARNING) << "Protobuf message length is too large."; |
| 112 | return false; |
| 113 | } |
| 114 | |
| 115 | // Read the next protobuf event to a temporary char buffer. |
| 116 | stream.read(tmp_buffer.data(), message_length); |
| 117 | if (stream.gcount() != static_cast<int>(message_length)) { |
| 118 | LOG(LS_WARNING) << "Failed to read protobuf message from file."; |
| 119 | return false; |
| 120 | } |
| 121 | |
| 122 | // Parse the protobuf event from the buffer. |
| 123 | webrtc::rtclog::Event event; |
| 124 | if (!event.ParseFromArray(tmp_buffer.data(), message_length)) { |
| 125 | LOG(LS_WARNING) << "Failed to parse protobuf message."; |
| 126 | return false; |
| 127 | } |
| 128 | events->push_back(event); |
| 129 | } |
| 130 | } |
| 131 | |
| 132 | // TODO(terelius): Should this be placed in some utility file instead? |
| 133 | std::string EventTypeToString(webrtc::rtclog::Event::EventType event_type) { |
| 134 | switch (event_type) { |
| 135 | case webrtc::rtclog::Event::UNKNOWN_EVENT: |
| 136 | return "UNKNOWN_EVENT"; |
| 137 | case webrtc::rtclog::Event::LOG_START: |
| 138 | return "LOG_START"; |
| 139 | case webrtc::rtclog::Event::LOG_END: |
| 140 | return "LOG_END"; |
| 141 | case webrtc::rtclog::Event::RTP_EVENT: |
| 142 | return "RTP_EVENT"; |
| 143 | case webrtc::rtclog::Event::RTCP_EVENT: |
| 144 | return "RTCP_EVENT"; |
| 145 | case webrtc::rtclog::Event::AUDIO_PLAYOUT_EVENT: |
| 146 | return "AUDIO_PLAYOUT_EVENT"; |
| 147 | case webrtc::rtclog::Event::LOSS_BASED_BWE_UPDATE: |
| 148 | return "LOSS_BASED_BWE_UPDATE"; |
| 149 | case webrtc::rtclog::Event::DELAY_BASED_BWE_UPDATE: |
| 150 | return "DELAY_BASED_BWE_UPDATE"; |
| 151 | case webrtc::rtclog::Event::VIDEO_RECEIVER_CONFIG_EVENT: |
| 152 | return "VIDEO_RECV_CONFIG"; |
| 153 | case webrtc::rtclog::Event::VIDEO_SENDER_CONFIG_EVENT: |
| 154 | return "VIDEO_SEND_CONFIG"; |
| 155 | case webrtc::rtclog::Event::AUDIO_RECEIVER_CONFIG_EVENT: |
| 156 | return "AUDIO_RECV_CONFIG"; |
| 157 | case webrtc::rtclog::Event::AUDIO_SENDER_CONFIG_EVENT: |
| 158 | return "AUDIO_SEND_CONFIG"; |
| 159 | case webrtc::rtclog::Event::AUDIO_NETWORK_ADAPTATION_EVENT: |
| 160 | return "AUDIO_NETWORK_ADAPTATION"; |
| 161 | case webrtc::rtclog::Event::BWE_PROBE_CLUSTER_CREATED_EVENT: |
| 162 | return "BWE_PROBE_CREATED"; |
| 163 | case webrtc::rtclog::Event::BWE_PROBE_RESULT_EVENT: |
| 164 | return "BWE_PROBE_RESULT"; |
terelius | ee37e86 | 2017-04-28 07:48:17 -0700 | [diff] [blame] | 165 | } |
| 166 | RTC_NOTREACHED(); |
| 167 | return "UNKNOWN_EVENT"; |
| 168 | } |
| 169 | |
| 170 | } // namespace |
| 171 | |
| 172 | // This utility will print basic information about each packet to stdout. |
| 173 | // Note that parser will assert if the protobuf event is missing some required |
| 174 | // fields and we attempt to access them. We don't handle this at the moment. |
| 175 | int main(int argc, char* argv[]) { |
| 176 | std::string program_name = argv[0]; |
| 177 | std::string usage = |
| 178 | "Tool for file usage statistics from an RtcEventLog.\n" |
| 179 | "Run " + |
| 180 | program_name + |
oprypin | 6e09d87 | 2017-08-31 03:21:39 -0700 | [diff] [blame] | 181 | " --help for usage.\n" |
terelius | ee37e86 | 2017-04-28 07:48:17 -0700 | [diff] [blame] | 182 | "Example usage:\n" + |
| 183 | program_name + " input.rel\n"; |
oprypin | 6e09d87 | 2017-08-31 03:21:39 -0700 | [diff] [blame] | 184 | if (rtc::FlagList::SetFlagsFromCommandLine(&argc, argv, true) || |
| 185 | FLAG_help || argc != 2) { |
| 186 | std::cout << usage; |
| 187 | if (FLAG_help) { |
| 188 | rtc::FlagList::Print(nullptr, false); |
| 189 | return 0; |
| 190 | } |
| 191 | return 1; |
terelius | ee37e86 | 2017-04-28 07:48:17 -0700 | [diff] [blame] | 192 | } |
| 193 | std::string file_name = argv[1]; |
| 194 | |
| 195 | std::vector<webrtc::rtclog::Event> events; |
| 196 | if (!ParseEvents(file_name, &events)) { |
| 197 | LOG(LS_ERROR) << "Failed to parse event log."; |
| 198 | return -1; |
| 199 | } |
| 200 | |
| 201 | // Get file size |
| 202 | FILE* file = fopen(file_name.c_str(), "rb"); |
| 203 | fseek(file, 0L, SEEK_END); |
| 204 | int64_t file_size = ftell(file); |
| 205 | fclose(file); |
| 206 | |
| 207 | // We are deliberately using low level protobuf functions to get the stats |
| 208 | // since the convenience functions in the parser would CHECK that the events |
| 209 | // are well formed. |
| 210 | std::map<webrtc::rtclog::Event::EventType, Stats> stats; |
| 211 | int malformed_events = 0; |
| 212 | size_t malformed_event_size = 0; |
| 213 | size_t accumulated_event_size = 0; |
| 214 | for (const webrtc::rtclog::Event& event : events) { |
| 215 | size_t serialized_size = event.ByteSize(); |
| 216 | // When the event is written on the disk, it is part of an EventStream |
| 217 | // object. The event stream will prepend a 1 byte field number/wire type, |
| 218 | // and a varint encoding (base 128) of the event length. |
| 219 | serialized_size = |
| 220 | 1 + (1 + (serialized_size > 127) + (serialized_size > 16383)) + |
| 221 | serialized_size; |
| 222 | |
| 223 | if (event.has_type() && event.has_timestamp_us()) { |
| 224 | stats[event.type()].count++; |
| 225 | stats[event.type()].total_size += serialized_size; |
| 226 | } else { |
| 227 | // The event is missing the type or the timestamp field. |
| 228 | malformed_events++; |
| 229 | malformed_event_size += serialized_size; |
| 230 | } |
| 231 | accumulated_event_size += serialized_size; |
| 232 | } |
| 233 | |
| 234 | printf("Type \tCount\tTotal size\tAverage size\tPercent\n"); |
| 235 | printf( |
| 236 | "-----------------------------------------------------------------------" |
| 237 | "\n"); |
| 238 | for (const auto it : stats) { |
| 239 | printf("%-22s\t%5d\t%10zu\t%12.2lf\t%7.2lf\n", |
| 240 | EventTypeToString(it.first).c_str(), it.second.count, |
| 241 | it.second.total_size, |
| 242 | static_cast<double>(it.second.total_size) / it.second.count, |
| 243 | static_cast<double>(it.second.total_size) / file_size * 100); |
| 244 | } |
| 245 | if (malformed_events != 0) { |
| 246 | printf("%-22s\t%5d\t%10zu\t%12.2lf\t%7.2lf\n", "MALFORMED", |
| 247 | malformed_events, malformed_event_size, |
| 248 | static_cast<double>(malformed_event_size) / malformed_events, |
| 249 | static_cast<double>(malformed_event_size) / file_size * 100); |
| 250 | } |
| 251 | if (file_size - accumulated_event_size != 0) { |
| 252 | printf("WARNING: %" PRId64 " bytes not accounted for\n", |
| 253 | file_size - accumulated_event_size); |
| 254 | } |
| 255 | |
| 256 | return 0; |
| 257 | } |