215 lines
7.4 KiB
C++
215 lines
7.4 KiB
C++
/*
|
|
* 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 "tools/trace_to_text/trace_to_text.h"
|
|
|
|
#include "perfetto/base/logging.h"
|
|
#include "perfetto/ext/base/file_utils.h"
|
|
#include "perfetto/ext/base/scoped_file.h"
|
|
#include "src/protozero/proto_ring_buffer.h"
|
|
#include "tools/trace_to_text/trace.descriptor.h"
|
|
#include "tools/trace_to_text/utils.h"
|
|
|
|
#include "protos/perfetto/trace/trace.pbzero.h"
|
|
#include "protos/perfetto/trace/trace_packet.pbzero.h"
|
|
|
|
#include "src/trace_processor/forwarding_trace_parser.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"
|
|
|
|
namespace perfetto {
|
|
namespace trace_to_text {
|
|
namespace {
|
|
|
|
using perfetto::trace_processor::DescriptorPool;
|
|
using trace_processor::TraceType;
|
|
using trace_processor::util::GzipDecompressor;
|
|
|
|
template <size_t N>
|
|
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());
|
|
}
|
|
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<uint8_t> 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<size_t>(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<char*>(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<uint8_t[]> 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
|