Tom Cherry | ec39a3f | 2020-07-09 09:51:16 -0700 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2020 The Android Open Source Project |
| 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. |
| 6 | * You may obtain a copy of the License at |
| 7 | * |
| 8 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | * |
| 10 | * Unless required by applicable law or agreed to in writing, software |
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | * See the License for the specific language governing permissions and |
| 14 | * limitations under the License. |
| 15 | */ |
| 16 | |
| 17 | #include <inttypes.h> |
| 18 | |
| 19 | #include <chrono> |
| 20 | #include <map> |
| 21 | |
| 22 | #include <android-base/file.h> |
| 23 | #include <android-base/mapped_file.h> |
| 24 | #include <android-base/parseint.h> |
| 25 | #include <android-base/strings.h> |
| 26 | #include <android-base/unique_fd.h> |
| 27 | #include <android/log.h> |
| 28 | #include <log/log_time.h> |
| 29 | #include <log/logprint.h> |
| 30 | |
| 31 | #include "ChattyLogBuffer.h" |
| 32 | #include "LogBuffer.h" |
| 33 | #include "LogStatistics.h" |
| 34 | #include "RecordedLogMessage.h" |
| 35 | #include "SerializedLogBuffer.h" |
| 36 | #include "SimpleLogBuffer.h" |
| 37 | |
| 38 | using android::base::MappedFile; |
| 39 | using android::base::ParseInt; |
| 40 | using android::base::ParseUint; |
| 41 | using android::base::Split; |
| 42 | |
Tom Cherry | ec39a3f | 2020-07-09 09:51:16 -0700 | [diff] [blame] | 43 | char* android::uidToName(uid_t) { |
| 44 | return nullptr; |
| 45 | } |
| 46 | |
| 47 | static size_t GetPrivateDirty() { |
| 48 | // Allocate once and hope that we don't need to reallocate >40000, to prevent heap fragmentation |
| 49 | static std::string smaps(40000, '\0'); |
| 50 | android::base::ReadFileToString("/proc/self/smaps", &smaps); |
| 51 | |
| 52 | size_t result = 0; |
| 53 | size_t base = 0; |
| 54 | size_t found; |
| 55 | while (true) { |
| 56 | found = smaps.find("Private_Dirty:", base); |
| 57 | if (found == smaps.npos) break; |
| 58 | |
| 59 | found += sizeof("Private_Dirty:"); |
| 60 | |
| 61 | result += atoi(&smaps[found]); |
| 62 | |
| 63 | base = found + 1; |
| 64 | } |
| 65 | |
| 66 | return result; |
| 67 | } |
| 68 | |
| 69 | static AndroidLogFormat* GetLogFormat() { |
| 70 | static AndroidLogFormat* format = [] { |
| 71 | auto* format = android_log_format_new(); |
| 72 | android_log_setPrintFormat(format, android_log_formatFromString("threadtime")); |
| 73 | android_log_setPrintFormat(format, android_log_formatFromString("uid")); |
| 74 | return format; |
| 75 | }(); |
| 76 | return format; |
| 77 | } |
| 78 | |
| 79 | static void PrintMessage(struct log_msg* buf) { |
| 80 | bool is_binary = |
| 81 | buf->id() == LOG_ID_EVENTS || buf->id() == LOG_ID_STATS || buf->id() == LOG_ID_SECURITY; |
| 82 | |
| 83 | AndroidLogEntry entry; |
| 84 | int err; |
| 85 | if (is_binary) { |
| 86 | char binaryMsgBuf[1024]; |
| 87 | err = android_log_processBinaryLogBuffer(&buf->entry, &entry, nullptr, binaryMsgBuf, |
| 88 | sizeof(binaryMsgBuf)); |
| 89 | } else { |
| 90 | err = android_log_processLogBuffer(&buf->entry, &entry); |
| 91 | } |
| 92 | if (err < 0) { |
| 93 | fprintf(stderr, "Error parsing log message\n"); |
| 94 | } |
| 95 | |
| 96 | android_log_printLogLine(GetLogFormat(), STDOUT_FILENO, &entry); |
| 97 | } |
| 98 | |
| 99 | static log_time GetFirstTimeStamp(const MappedFile& recorded_messages) { |
| 100 | if (sizeof(RecordedLogMessage) >= recorded_messages.size()) { |
| 101 | fprintf(stderr, "At least one log message must be present in the input\n"); |
| 102 | exit(1); |
| 103 | } |
| 104 | |
| 105 | auto* meta = reinterpret_cast<RecordedLogMessage*>(recorded_messages.data()); |
| 106 | return meta->realtime; |
| 107 | } |
| 108 | |
Tom Cherry | f1d938a | 2020-07-31 10:45:46 -0700 | [diff] [blame] | 109 | static LogMask BuffersToLogMask(const char* buffers) { |
| 110 | if (buffers == nullptr || !strcmp(buffers, "all")) { |
| 111 | return kLogMaskAll; |
| 112 | } |
| 113 | auto string_ids = Split(buffers, ","); |
| 114 | LogMask log_mask = 0; |
| 115 | for (const auto& string_id : string_ids) { |
| 116 | int buffer_id; |
| 117 | if (!ParseInt(string_id, &buffer_id, 0, 7)) { |
| 118 | fprintf(stderr, "Could not parse buffer_id '%s'\n", string_id.c_str()); |
| 119 | exit(1); |
| 120 | } |
| 121 | log_mask |= 1 << buffer_id; |
| 122 | } |
| 123 | return log_mask; |
| 124 | } |
| 125 | |
Tom Cherry | ec39a3f | 2020-07-09 09:51:16 -0700 | [diff] [blame] | 126 | class StdoutWriter : public LogWriter { |
| 127 | public: |
| 128 | StdoutWriter() : LogWriter(0, true) {} |
| 129 | bool Write(const logger_entry& entry, const char* message) override { |
| 130 | struct log_msg log_msg; |
| 131 | log_msg.entry = entry; |
| 132 | if (log_msg.entry.len > LOGGER_ENTRY_MAX_PAYLOAD) { |
| 133 | fprintf(stderr, "payload too large %" PRIu16, log_msg.entry.len); |
| 134 | exit(1); |
| 135 | } |
| 136 | memcpy(log_msg.msg(), message, log_msg.entry.len); |
| 137 | |
| 138 | PrintMessage(&log_msg); |
| 139 | |
| 140 | return true; |
| 141 | } |
| 142 | |
Tom Cherry | f1d938a | 2020-07-31 10:45:46 -0700 | [diff] [blame] | 143 | void Shutdown() override { |
| 144 | fprintf(stderr, "LogWriter::Shutdown() called\n"); |
| 145 | exit(1); |
| 146 | } |
| 147 | |
Tom Cherry | ec39a3f | 2020-07-09 09:51:16 -0700 | [diff] [blame] | 148 | std::string name() const override { return "stdout writer"; } |
| 149 | }; |
| 150 | |
| 151 | class Operation { |
| 152 | public: |
| 153 | virtual ~Operation() {} |
| 154 | |
| 155 | virtual void Begin() {} |
| 156 | virtual void Log(const RecordedLogMessage& meta, const char* msg) = 0; |
| 157 | virtual void End() {} |
| 158 | }; |
| 159 | |
| 160 | class PrintInteresting : public Operation { |
| 161 | public: |
| 162 | PrintInteresting(log_time first_log_timestamp) |
| 163 | : stats_simple_{false, false, first_log_timestamp}, |
| 164 | stats_chatty_{false, false, first_log_timestamp}, |
| 165 | stats_serialized_{false, true, first_log_timestamp} {} |
| 166 | |
| 167 | void Begin() override { |
| 168 | printf("message_count,simple_main_lines,simple_radio_lines,simple_events_lines,simple_" |
| 169 | "system_lines,simple_crash_lines,simple_stats_lines,simple_security_lines,simple_" |
| 170 | "kernel_lines,simple_main_size,simple_radio_size,simple_events_size,simple_system_" |
| 171 | "size,simple_crash_size,simple_stats_size,simple_security_size,simple_kernel_size," |
| 172 | "simple_main_overhead,simple_radio_overhead,simple_events_overhead,simple_system_" |
| 173 | "overhead,simple_crash_overhead,simple_stats_overhead,simple_security_overhead," |
| 174 | "simple_kernel_overhead,simple_main_range,simple_radio_range,simple_events_range," |
| 175 | "simple_system_range,simple_crash_range,simple_stats_range,simple_security_range," |
| 176 | "simple_kernel_range,chatty_main_lines,chatty_radio_lines,chatty_events_lines," |
| 177 | "chatty_system_lines,chatty_crash_lines,chatty_stats_lines,chatty_security_lines," |
| 178 | "chatty_" |
| 179 | "kernel_lines,chatty_main_size,chatty_radio_size,chatty_events_size,chatty_system_" |
| 180 | "size,chatty_crash_size,chatty_stats_size,chatty_security_size,chatty_kernel_size," |
| 181 | "chatty_main_overhead,chatty_radio_overhead,chatty_events_overhead,chatty_system_" |
| 182 | "overhead,chatty_crash_overhead,chatty_stats_overhead,chatty_security_overhead," |
| 183 | "chatty_kernel_overhead,chatty_main_range,chatty_radio_range,chatty_events_range," |
| 184 | "chatty_system_range,chatty_crash_range,chatty_stats_range,chatty_security_range," |
| 185 | "chatty_kernel_range,serialized_main_lines,serialized_radio_lines,serialized_events_" |
| 186 | "lines,serialized_" |
| 187 | "system_lines,serialized_crash_lines,serialized_stats_lines,serialized_security_" |
| 188 | "lines,serialized_" |
| 189 | "kernel_lines,serialized_main_size,serialized_radio_size,serialized_events_size," |
| 190 | "serialized_system_" |
| 191 | "size,serialized_crash_size,serialized_stats_size,serialized_security_size," |
| 192 | "serialized_kernel_size," |
| 193 | "serialized_main_overhead,serialized_radio_overhead,serialized_events_overhead," |
| 194 | "serialized_system_" |
| 195 | "overhead,serialized_crash_overhead,serialized_stats_overhead,serialized_security_" |
| 196 | "overhead," |
| 197 | "serialized_kernel_overhead,serialized_main_range,serialized_radio_range,serialized_" |
| 198 | "events_range," |
| 199 | "serialized_system_range,serialized_crash_range,serialized_stats_range,serialized_" |
| 200 | "security_range," |
| 201 | "serialized_kernel_range\n"); |
| 202 | } |
| 203 | |
| 204 | void Log(const RecordedLogMessage& meta, const char* msg) override { |
| 205 | simple_log_buffer_.Log(static_cast<log_id_t>(meta.log_id), meta.realtime, meta.uid, |
| 206 | meta.pid, meta.tid, msg, meta.msg_len); |
| 207 | |
| 208 | chatty_log_buffer_.Log(static_cast<log_id_t>(meta.log_id), meta.realtime, meta.uid, |
| 209 | meta.pid, meta.tid, msg, meta.msg_len); |
| 210 | |
| 211 | serialized_log_buffer_.Log(static_cast<log_id_t>(meta.log_id), meta.realtime, meta.uid, |
| 212 | meta.pid, meta.tid, msg, meta.msg_len); |
| 213 | |
| 214 | if (num_message_ % 10000 == 0) { |
| 215 | printf("%" PRIu64 ",%s,%s,%s\n", num_message_, |
| 216 | stats_simple_.ReportInteresting().c_str(), |
| 217 | stats_chatty_.ReportInteresting().c_str(), |
| 218 | stats_serialized_.ReportInteresting().c_str()); |
| 219 | } |
| 220 | |
| 221 | num_message_++; |
| 222 | } |
| 223 | |
| 224 | private: |
| 225 | uint64_t num_message_ = 1; |
| 226 | |
| 227 | LogReaderList reader_list_; |
| 228 | LogTags tags_; |
| 229 | PruneList prune_list_; |
| 230 | |
| 231 | LogStatistics stats_simple_; |
| 232 | SimpleLogBuffer simple_log_buffer_{&reader_list_, &tags_, &stats_simple_}; |
| 233 | |
| 234 | LogStatistics stats_chatty_; |
| 235 | ChattyLogBuffer chatty_log_buffer_{&reader_list_, &tags_, &prune_list_, &stats_chatty_}; |
| 236 | |
| 237 | LogStatistics stats_serialized_; |
| 238 | SerializedLogBuffer serialized_log_buffer_{&reader_list_, &tags_, &stats_serialized_}; |
| 239 | }; |
| 240 | |
| 241 | class SingleBufferOperation : public Operation { |
| 242 | public: |
| 243 | SingleBufferOperation(log_time first_log_timestamp, const char* buffer) { |
| 244 | if (!strcmp(buffer, "simple")) { |
| 245 | stats_.reset(new LogStatistics{false, false, first_log_timestamp}); |
| 246 | log_buffer_.reset(new SimpleLogBuffer(&reader_list_, &tags_, stats_.get())); |
| 247 | } else if (!strcmp(buffer, "chatty")) { |
| 248 | stats_.reset(new LogStatistics{false, false, first_log_timestamp}); |
| 249 | log_buffer_.reset( |
| 250 | new ChattyLogBuffer(&reader_list_, &tags_, &prune_list_, stats_.get())); |
| 251 | } else if (!strcmp(buffer, "serialized")) { |
| 252 | stats_.reset(new LogStatistics{false, true, first_log_timestamp}); |
| 253 | log_buffer_.reset(new SerializedLogBuffer(&reader_list_, &tags_, stats_.get())); |
| 254 | } else { |
| 255 | fprintf(stderr, "invalid log buffer type '%s'\n", buffer); |
| 256 | abort(); |
| 257 | } |
| 258 | } |
| 259 | |
| 260 | void Log(const RecordedLogMessage& meta, const char* msg) override { |
| 261 | PreOperation(); |
| 262 | log_buffer_->Log(static_cast<log_id_t>(meta.log_id), meta.realtime, meta.uid, meta.pid, |
| 263 | meta.tid, msg, meta.msg_len); |
| 264 | |
| 265 | Operation(); |
| 266 | |
| 267 | num_message_++; |
| 268 | } |
| 269 | |
| 270 | virtual void PreOperation() {} |
| 271 | virtual void Operation() {} |
| 272 | |
| 273 | protected: |
| 274 | uint64_t num_message_ = 1; |
| 275 | |
| 276 | LogReaderList reader_list_; |
| 277 | LogTags tags_; |
| 278 | PruneList prune_list_; |
| 279 | |
| 280 | std::unique_ptr<LogStatistics> stats_; |
| 281 | std::unique_ptr<LogBuffer> log_buffer_; |
| 282 | }; |
| 283 | |
| 284 | class PrintMemory : public SingleBufferOperation { |
| 285 | public: |
| 286 | PrintMemory(log_time first_log_timestamp, const char* buffer) |
| 287 | : SingleBufferOperation(first_log_timestamp, buffer) {} |
| 288 | |
| 289 | void Operation() override { |
| 290 | if (num_message_ % 100000 == 0) { |
| 291 | printf("%" PRIu64 ",%s\n", num_message_, |
| 292 | std::to_string(GetPrivateDirty() - baseline_memory_).c_str()); |
| 293 | } |
| 294 | } |
| 295 | |
| 296 | private: |
| 297 | size_t baseline_memory_ = GetPrivateDirty(); |
| 298 | }; |
| 299 | |
| 300 | class PrintLogs : public SingleBufferOperation { |
| 301 | public: |
| 302 | PrintLogs(log_time first_log_timestamp, const char* buffer, const char* buffers, |
| 303 | const char* print_point) |
| 304 | : SingleBufferOperation(first_log_timestamp, buffer) { |
Tom Cherry | f1d938a | 2020-07-31 10:45:46 -0700 | [diff] [blame] | 305 | mask_ = BuffersToLogMask(buffers); |
Tom Cherry | ec39a3f | 2020-07-09 09:51:16 -0700 | [diff] [blame] | 306 | if (print_point != nullptr) { |
| 307 | uint64_t result = 0; |
| 308 | if (!ParseUint(print_point, &result)) { |
| 309 | fprintf(stderr, "Could not parse print point '%s'\n", print_point); |
| 310 | exit(1); |
| 311 | } |
| 312 | print_point_ = result; |
| 313 | } |
| 314 | } |
| 315 | |
| 316 | void Operation() override { |
| 317 | if (print_point_ && num_message_ >= *print_point_) { |
| 318 | End(); |
| 319 | exit(0); |
| 320 | } |
| 321 | } |
| 322 | |
Tom Cherry | f1d938a | 2020-07-31 10:45:46 -0700 | [diff] [blame] | 323 | void End() override { |
Tom Cherry | ec39a3f | 2020-07-09 09:51:16 -0700 | [diff] [blame] | 324 | std::unique_ptr<LogWriter> test_writer(new StdoutWriter()); |
| 325 | std::unique_ptr<FlushToState> flush_to_state = log_buffer_->CreateFlushToState(1, mask_); |
| 326 | log_buffer_->FlushTo(test_writer.get(), *flush_to_state, nullptr); |
| 327 | |
| 328 | auto stats_string = stats_->Format(0, 0, mask_); |
| 329 | printf("%s\n", stats_string.c_str()); |
| 330 | } |
| 331 | |
| 332 | private: |
| 333 | LogMask mask_ = kLogMaskAll; |
| 334 | std::optional<uint64_t> print_point_; |
| 335 | }; |
| 336 | |
| 337 | class PrintLatency : public SingleBufferOperation { |
| 338 | public: |
| 339 | PrintLatency(log_time first_log_timestamp, const char* buffer) |
| 340 | : SingleBufferOperation(first_log_timestamp, buffer) {} |
| 341 | |
| 342 | void PreOperation() override { operation_start_ = std::chrono::steady_clock::now(); } |
| 343 | |
| 344 | void Operation() override { |
| 345 | auto end = std::chrono::steady_clock::now(); |
| 346 | auto duration = (end - operation_start_).count(); |
| 347 | durations_.emplace_back(duration); |
| 348 | } |
| 349 | |
Tom Cherry | f1d938a | 2020-07-31 10:45:46 -0700 | [diff] [blame] | 350 | void End() override { |
Tom Cherry | ec39a3f | 2020-07-09 09:51:16 -0700 | [diff] [blame] | 351 | std::sort(durations_.begin(), durations_.end()); |
| 352 | auto q1 = durations_.size() / 4; |
| 353 | auto q2 = durations_.size() / 2; |
| 354 | auto q3 = 3 * durations_.size() / 4; |
| 355 | |
| 356 | auto p95 = 95 * durations_.size() / 100; |
| 357 | auto p99 = 99 * durations_.size() / 100; |
| 358 | auto p9999 = 9999 * durations_.size() / 10000; |
| 359 | |
| 360 | printf("q1: %lld q2: %lld q3: %lld p95: %lld p99: %lld p99.99: %lld max: %lld\n", |
| 361 | durations_[q1], durations_[q2], durations_[q3], durations_[p95], durations_[p99], |
| 362 | durations_[p9999], durations_.back()); |
| 363 | } |
| 364 | |
| 365 | private: |
| 366 | std::chrono::steady_clock::time_point operation_start_; |
| 367 | std::vector<long long> durations_; |
| 368 | }; |
| 369 | |
Tom Cherry | f1d938a | 2020-07-31 10:45:46 -0700 | [diff] [blame] | 370 | class PrintAllLogs : public SingleBufferOperation { |
| 371 | public: |
| 372 | PrintAllLogs(log_time first_log_timestamp, const char* buffer, const char* buffers) |
| 373 | : SingleBufferOperation(first_log_timestamp, buffer) { |
| 374 | LogMask mask = BuffersToLogMask(buffers); |
| 375 | auto lock = std::unique_lock{reader_list_.reader_threads_lock()}; |
| 376 | std::unique_ptr<LogWriter> stdout_writer(new StdoutWriter()); |
| 377 | std::unique_ptr<LogReaderThread> log_reader( |
| 378 | new LogReaderThread(log_buffer_.get(), &reader_list_, std::move(stdout_writer), |
| 379 | false, 0, mask, 0, {}, 1, {})); |
| 380 | reader_list_.reader_threads().emplace_back(std::move(log_reader)); |
| 381 | } |
| 382 | |
| 383 | void Operation() override { |
| 384 | // If the rate of reading logs is slower than the rate of incoming logs, then the reader |
| 385 | // thread is disconnected to not overflow log buffers, therefore we artificially slow down |
| 386 | // the incoming log rate. |
| 387 | usleep(100); |
| 388 | } |
| 389 | }; |
| 390 | |
Tom Cherry | ec39a3f | 2020-07-09 09:51:16 -0700 | [diff] [blame] | 391 | int main(int argc, char** argv) { |
| 392 | if (argc < 3) { |
| 393 | fprintf(stderr, "Usage: %s FILE OPERATION [BUFFER] [OPTIONS]\n", argv[0]); |
| 394 | return 1; |
| 395 | } |
| 396 | |
| 397 | if (strcmp(argv[2], "interesting") != 0 && argc < 4) { |
| 398 | fprintf(stderr, "Operations other than 'interesting' require a BUFFER argument\n"); |
| 399 | return 1; |
| 400 | } |
| 401 | |
| 402 | int recorded_messages_fd = open(argv[1], O_RDONLY); |
| 403 | if (recorded_messages_fd == -1) { |
| 404 | fprintf(stderr, "Couldn't open input file\n"); |
| 405 | return 1; |
| 406 | } |
| 407 | struct stat fd_stat; |
| 408 | if (fstat(recorded_messages_fd, &fd_stat) != 0) { |
| 409 | fprintf(stderr, "Couldn't fstat input file\n"); |
| 410 | return 1; |
| 411 | } |
| 412 | auto recorded_messages = MappedFile::FromFd(recorded_messages_fd, 0, |
| 413 | static_cast<size_t>(fd_stat.st_size), PROT_READ); |
| 414 | if (recorded_messages == nullptr) { |
| 415 | fprintf(stderr, "Couldn't mmap input file\n"); |
| 416 | return 1; |
| 417 | } |
| 418 | |
| 419 | // LogStatistics typically uses 'now()' to initialize its log range state, but this doesn't work |
| 420 | // when replaying older logs, so we instead give it the timestamp from the first log. |
| 421 | log_time first_log_timestamp = GetFirstTimeStamp(*recorded_messages); |
| 422 | |
| 423 | std::unique_ptr<Operation> operation; |
| 424 | if (!strcmp(argv[2], "interesting")) { |
| 425 | operation.reset(new PrintInteresting(first_log_timestamp)); |
| 426 | } else if (!strcmp(argv[2], "memory_usage")) { |
| 427 | operation.reset(new PrintMemory(first_log_timestamp, argv[3])); |
| 428 | } else if (!strcmp(argv[2], "latency")) { |
| 429 | operation.reset(new PrintLatency(first_log_timestamp, argv[3])); |
| 430 | } else if (!strcmp(argv[2], "print_logs")) { |
| 431 | operation.reset(new PrintLogs(first_log_timestamp, argv[3], argc > 4 ? argv[4] : nullptr, |
| 432 | argc > 5 ? argv[5] : nullptr)); |
Tom Cherry | f1d938a | 2020-07-31 10:45:46 -0700 | [diff] [blame] | 433 | } else if (!strcmp(argv[2], "print_all_logs")) { |
| 434 | operation.reset( |
| 435 | new PrintAllLogs(first_log_timestamp, argv[3], argc > 4 ? argv[4] : nullptr)); |
Tom Cherry | ec39a3f | 2020-07-09 09:51:16 -0700 | [diff] [blame] | 436 | } else if (!strcmp(argv[2], "nothing")) { |
| 437 | operation.reset(new SingleBufferOperation(first_log_timestamp, argv[3])); |
| 438 | } else { |
| 439 | fprintf(stderr, "unknown operation '%s'\n", argv[2]); |
| 440 | return 1; |
| 441 | } |
| 442 | |
| 443 | // LogBuffer::Log() won't log without this on host. |
| 444 | __android_log_set_minimum_priority(ANDROID_LOG_VERBOSE); |
| 445 | // But we still want to suppress messages <= error to not interrupt the rest of the output. |
| 446 | __android_log_set_logger([](const struct __android_log_message* log_message) { |
| 447 | if (log_message->priority < ANDROID_LOG_ERROR) { |
| 448 | return; |
| 449 | } |
| 450 | __android_log_stderr_logger(log_message); |
| 451 | }); |
| 452 | |
| 453 | operation->Begin(); |
| 454 | |
| 455 | uint64_t read_position = 0; |
| 456 | while (read_position + sizeof(RecordedLogMessage) < recorded_messages->size()) { |
| 457 | auto* meta = |
| 458 | reinterpret_cast<RecordedLogMessage*>(recorded_messages->data() + read_position); |
| 459 | if (read_position + sizeof(RecordedLogMessage) + meta->msg_len >= |
| 460 | recorded_messages->size()) { |
| 461 | break; |
| 462 | } |
| 463 | char* msg = recorded_messages->data() + read_position + sizeof(RecordedLogMessage); |
| 464 | read_position += sizeof(RecordedLogMessage) + meta->msg_len; |
| 465 | |
| 466 | operation->Log(*meta, msg); |
| 467 | } |
| 468 | |
| 469 | operation->End(); |
| 470 | |
| 471 | return 0; |
| 472 | } |