/* * Copyright (C) 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "src/traceconv/trace_to_text.h" #include "perfetto/base/logging.h" #include "perfetto/ext/base/file_utils.h" #include "perfetto/ext/base/scoped_file.h" #include "perfetto/ext/protozero/proto_ring_buffer.h" #include "src/traceconv/trace.descriptor.h" #include "src/traceconv/winscope.descriptor.h" #include "src/traceconv/utils.h" #include "protos/perfetto/trace/trace.pbzero.h" #include "protos/perfetto/trace/trace_packet.pbzero.h" #include "src/trace_processor/util/descriptors.h" #include "src/trace_processor/util/gzip_utils.h" #include "src/trace_processor/util/protozero_to_text.h" #include "src/trace_processor/util/trace_type.h" namespace perfetto { namespace trace_to_text { namespace { using perfetto::trace_processor::DescriptorPool; using trace_processor::TraceType; using trace_processor::util::GzipDecompressor; template static void WriteToOutput(std::ostream* output, const char (&str)[N]) { output->write(str, sizeof(str) - 1); } // Online algorithm to covert trace binary to text format. // Usage: // - Feed the trace-binary in a sequence of memblock, and it will continue to // write the output in given std::ostream*. class OnlineTraceToText { public: OnlineTraceToText(std::ostream* output) : output_(output) { pool_.AddFromFileDescriptorSet(kTraceDescriptor.data(), kTraceDescriptor.size()); pool_.AddFromFileDescriptorSet(kWinscopeDescriptor.data(), kWinscopeDescriptor.size()); } OnlineTraceToText(const OnlineTraceToText&) = delete; OnlineTraceToText& operator=(const OnlineTraceToText&) = delete; void Feed(const uint8_t* data, size_t len); bool ok() const { return ok_; } private: std::string TracePacketToText(protozero::ConstBytes packet, uint32_t indent_depth); void PrintCompressedPackets(protozero::ConstBytes packets); bool ok_ = true; std::ostream* output_; protozero::ProtoRingBuffer ring_buffer_; DescriptorPool pool_; size_t bytes_processed_ = 0; size_t packet_ = 0; }; std::string OnlineTraceToText::TracePacketToText(protozero::ConstBytes packet, uint32_t indent_depth) { namespace pb0_to_text = trace_processor::protozero_to_text; return pb0_to_text::ProtozeroToText(pool_, ".perfetto.protos.TracePacket", packet, pb0_to_text::kIncludeNewLines, indent_depth); } void OnlineTraceToText::PrintCompressedPackets(protozero::ConstBytes packets) { WriteToOutput(output_, "compressed_packets {\n"); if (trace_processor::util::IsGzipSupported()) { std::vector whole_data = GzipDecompressor::DecompressFully(packets.data, packets.size); protos::pbzero::Trace::Decoder decoder(whole_data.data(), whole_data.size()); for (auto it = decoder.packet(); it; ++it) { WriteToOutput(output_, " packet {\n"); std::string text = TracePacketToText(*it, 2); output_->write(text.data(), std::streamsize(text.size())); WriteToOutput(output_, "\n }\n"); } } else { static const char kErrMsg[] = "Cannot decode compressed packets. zlib not enabled in the build " "config"; WriteToOutput(output_, kErrMsg); static bool log_once = [] { PERFETTO_ELOG("%s", kErrMsg); return true; }(); base::ignore_result(log_once); } WriteToOutput(output_, "}\n"); } void OnlineTraceToText::Feed(const uint8_t* data, size_t len) { ring_buffer_.Append(data, static_cast(len)); while (true) { auto token = ring_buffer_.ReadMessage(); if (token.fatal_framing_error) { PERFETTO_ELOG("Failed to tokenize trace packet"); ok_ = false; return; } if (!token.valid()) { // no need to set `ok_ = false` here because this just means // we've run out of packets in the ring buffer. break; } if (token.field_id != protos::pbzero::Trace::kPacketFieldNumber) { PERFETTO_ELOG("Skipping invalid field"); continue; } protos::pbzero::TracePacket::Decoder decoder(token.start, token.len); bytes_processed_ += token.len; if ((packet_++ & 0x3f) == 0) { fprintf(stderr, "Processing trace: %8zu KB%c", bytes_processed_ / 1024, kProgressChar); fflush(stderr); } if (decoder.has_compressed_packets()) { PrintCompressedPackets(decoder.compressed_packets()); } else { WriteToOutput(output_, "packet {\n"); protozero::ConstBytes packet = {token.start, token.len}; std::string text = TracePacketToText(packet, 1 /* indent_depth */); output_->write(text.data(), std::streamsize(text.size())); WriteToOutput(output_, "\n}\n"); } } } class InputReader { public: InputReader(std::istream* input) : input_(input) {} // Request the input-stream to read next |len_limit| bytes and load // it in |data|. It also updates the |len| with actual number of bytes loaded // in |data|. This can be less than requested |len_limit| if we have reached // at the end of the file. bool Read(uint8_t* data, uint32_t* len, uint32_t len_limit) { if (input_->eof()) return false; input_->read(reinterpret_cast(data), std::streamsize(len_limit)); if (input_->bad() || (input_->fail() && !input_->eof())) { PERFETTO_ELOG("Failed while reading trace"); ok_ = false; return false; } *len = uint32_t(input_->gcount()); return true; } bool ok() const { return ok_; } private: std::istream* input_; bool ok_ = true; }; } // namespace bool TraceToText(std::istream* input, std::ostream* output) { constexpr size_t kMaxMsgSize = protozero::ProtoRingBuffer::kMaxMsgSize; std::unique_ptr buffer(new uint8_t[kMaxMsgSize]); uint32_t buffer_len = 0; InputReader input_reader(input); OnlineTraceToText online_trace_to_text(output); input_reader.Read(buffer.get(), &buffer_len, kMaxMsgSize); TraceType type = trace_processor::GuessTraceType(buffer.get(), buffer_len); if (type == TraceType::kGzipTraceType) { GzipDecompressor decompressor; auto consumer = [&](const uint8_t* data, size_t len) { online_trace_to_text.Feed(data, len); }; using ResultCode = GzipDecompressor::ResultCode; do { ResultCode code = decompressor.FeedAndExtract(buffer.get(), buffer_len, consumer); if (code == ResultCode::kError || !online_trace_to_text.ok()) return false; } while (input_reader.Read(buffer.get(), &buffer_len, kMaxMsgSize)); return input_reader.ok(); } else if (type == TraceType::kProtoTraceType) { do { online_trace_to_text.Feed(buffer.get(), buffer_len); if (!online_trace_to_text.ok()) return false; } while (input_reader.Read(buffer.get(), &buffer_len, kMaxMsgSize)); return input_reader.ok(); } else { PERFETTO_ELOG("Unrecognised file."); return false; } } } // namespace trace_to_text } // namespace perfetto