blob: 2bee2f05181bc98437e7308a52630b923ec8cc70 [file] [log] [blame] [edit]
/*
* Copyright (C) 2019 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/perfetto_cmd/packet_writer.h"
#include <array>
#include <fcntl.h>
#include <signal.h>
#include <stdio.h>
#include <sys/stat.h>
#include "perfetto/base/build_config.h"
#include "perfetto/ext/base/getopt.h"
#include "perfetto/ext/base/paged_memory.h"
#include "perfetto/ext/base/utils.h"
#include "perfetto/ext/tracing/core/trace_packet.h"
#include "perfetto/protozero/proto_utils.h"
#if PERFETTO_BUILDFLAG(PERFETTO_ZLIB)
#include <zlib.h>
#endif
namespace perfetto {
namespace {
using protozero::proto_utils::MakeTagLengthDelimited;
using protozero::proto_utils::WriteVarInt;
using Preamble = std::array<char, 16>;
// ID of the |packet| field in trace.proto. Hardcoded as this we don't
// want to depend on protos/trace:lite for binary size saving reasons.
constexpr uint32_t kPacketId = 1;
#if PERFETTO_BUILDFLAG(PERFETTO_ZLIB)
// ID of |compressed_packets| in trace_packet.proto.
constexpr uint32_t kCompressedPacketsId = 50;
// Some transport mechanisms have a 512kb limit on packet size.
// ZipPacketWriter respects this limit where possible and does
// not produce compressed packets larger than 512kb. This is
// constant is deliberately conservative to leave plenty of
// room for the transport to add additional headers etc.
const size_t kMaxPacketSize = 500 * 1024;
// After every kPendingBytesLimit we do a Z_SYNC_FLUSH in the zlib stream.
const size_t kPendingBytesLimit = 32 * 1024;
#endif // PERFETTO_BUILDFLAG(PERFETTO_ZLIB)
template <uint32_t id>
size_t GetPreamble(size_t sz, Preamble* preamble) {
uint8_t* ptr = reinterpret_cast<uint8_t*>(preamble->data());
constexpr uint32_t tag = MakeTagLengthDelimited(id);
ptr = WriteVarInt(tag, ptr);
ptr = WriteVarInt(sz, ptr);
size_t preamble_size = reinterpret_cast<uintptr_t>(ptr) -
reinterpret_cast<uintptr_t>(preamble->data());
PERFETTO_DCHECK(preamble_size < preamble->size());
return preamble_size;
}
class FilePacketWriter : public PacketWriter {
public:
FilePacketWriter(FILE* fd);
~FilePacketWriter() override;
bool WritePacket(const TracePacket& packet) override;
private:
FILE* fd_;
};
FilePacketWriter::FilePacketWriter(FILE* fd) : fd_(fd) {}
FilePacketWriter::~FilePacketWriter() {
fflush(fd_);
}
bool FilePacketWriter::WritePacket(const TracePacket& packet) {
Preamble preamble;
size_t size = GetPreamble<kPacketId>(packet.size(), &preamble);
if (fwrite(preamble.data(), 1, size, fd_) != size)
return false;
for (const Slice& slice : packet.slices()) {
if (fwrite(reinterpret_cast<const char*>(slice.start), 1, slice.size,
fd_) != slice.size) {
return false;
}
}
return true;
}
#if PERFETTO_BUILDFLAG(PERFETTO_ZLIB)
class ZipPacketWriter : public PacketWriter {
public:
ZipPacketWriter(std::unique_ptr<PacketWriter>);
~ZipPacketWriter() override;
bool WritePacket(const TracePacket& packet) override;
private:
void CheckEq(int actual_code, int expected_code);
bool FinalizeCompressedPacket();
inline void Deflate(const char* ptr, size_t size) {
return Deflate(reinterpret_cast<const uint8_t*>(ptr), size);
}
inline void Deflate(const void* ptr, size_t size) {
return Deflate(reinterpret_cast<const uint8_t*>(ptr), size);
}
void Deflate(const uint8_t* ptr, size_t size);
std::unique_ptr<PacketWriter> writer_;
z_stream stream_{};
base::PagedMemory buf_;
uint8_t* const start_;
uint8_t* const end_;
bool is_compressing_ = false;
size_t pending_bytes_ = 0;
};
ZipPacketWriter::ZipPacketWriter(std::unique_ptr<PacketWriter> writer)
: writer_(std::move(writer)),
buf_(base::PagedMemory::Allocate(kMaxPacketSize)),
start_(static_cast<uint8_t*>(buf_.Get())),
end_(start_ + buf_.size()) {}
ZipPacketWriter::~ZipPacketWriter() {
if (is_compressing_)
FinalizeCompressedPacket();
}
bool ZipPacketWriter::WritePacket(const TracePacket& packet) {
// If we have already written one compressed packet, check whether we should
// flush the buffer.
if (is_compressing_) {
// We have two goals:
// - Fit as much data as possible into each packet
// - Ensure each packet is under 512KB
// We keep track of two numbers:
// - the number of remaining bytes in the output buffer
// - the number of (pending) uncompressed bytes written since the last flush
// The pending bytes may or may not have appeared in output buffer.
// Assuming in the worst case each uncompressed input byte can turn into
// two compressed bytes we can ensure we don't go over 512KB by not letting
// the number of pending bytes go over remaining bytes/2 - however often
// each input byte will not turn into 2 output bytes but less than 1 output
// byte - so this underfills the packet. To avoid this every 32kb we deflate
// with Z_SYNC_FLUSH ensuring all pending bytes are present in the output
// buffer.
if (pending_bytes_ > kPendingBytesLimit) {
CheckEq(deflate(&stream_, Z_SYNC_FLUSH), Z_OK);
pending_bytes_ = 0;
}
PERFETTO_DCHECK(end_ >= stream_.next_out);
size_t remaining = static_cast<size_t>(end_ - stream_.next_out);
if ((pending_bytes_ + packet.size() + 1024) * 2 > remaining) {
if (!FinalizeCompressedPacket()) {
return false;
}
}
}
// Do not attempt to compress large packets (since they may overflow our
// output buffer) instead insert them directly into the output stream:
if (packet.size() > kMaxPacketSize) {
// We can't be compressing here, if we were we would have attempted
// to FinalizeCompressedPacket() above:
PERFETTO_DCHECK(!is_compressing_);
return writer_->WritePacket(packet);
}
// Reinitialize the compresser if needed:
if (!is_compressing_) {
memset(&stream_, 0, sizeof(stream_));
CheckEq(deflateInit(&stream_, 6), Z_OK);
is_compressing_ = true;
stream_.next_out = start_;
stream_.avail_out = static_cast<unsigned int>(end_ - start_);
}
// Compress the trace packet header:
Preamble packet_hdr;
size_t packet_hdr_size = GetPreamble<kPacketId>(packet.size(), &packet_hdr);
Deflate(packet_hdr.data(), packet_hdr_size);
// Compress the trace packet itself:
for (const Slice& slice : packet.slices()) {
Deflate(slice.start, slice.size);
}
return true;
}
bool ZipPacketWriter::FinalizeCompressedPacket() {
PERFETTO_DCHECK(is_compressing_);
CheckEq(deflate(&stream_, Z_FINISH), Z_STREAM_END);
size_t size = static_cast<size_t>(stream_.next_out - start_);
Preamble preamble;
size_t preamble_size = GetPreamble<kCompressedPacketsId>(size, &preamble);
std::vector<TracePacket> out_packets(1);
TracePacket& out_packet = out_packets[0];
out_packet.AddSlice(preamble.data(), preamble_size);
out_packet.AddSlice(start_, size);
if (!writer_->WritePackets(out_packets))
return false;
is_compressing_ = false;
pending_bytes_ = 0;
CheckEq(deflateEnd(&stream_), Z_OK);
return true;
}
void ZipPacketWriter::CheckEq(int actual_code, int expected_code) {
if (actual_code == expected_code)
return;
PERFETTO_FATAL("Expected %d got %d: %s", actual_code, expected_code,
stream_.msg);
}
void ZipPacketWriter::Deflate(const uint8_t* ptr, size_t size) {
PERFETTO_CHECK(is_compressing_);
stream_.next_in = const_cast<uint8_t*>(ptr);
stream_.avail_in = static_cast<unsigned int>(size);
CheckEq(deflate(&stream_, Z_NO_FLUSH), Z_OK);
PERFETTO_CHECK(stream_.avail_in == 0);
pending_bytes_ += size;
}
#endif // PERFETTO_BUILDFLAG(PERFETTO_ZLIB)
} // namespace
PacketWriter::PacketWriter() {}
PacketWriter::~PacketWriter() {}
std::unique_ptr<PacketWriter> CreateFilePacketWriter(FILE* fd) {
return std::unique_ptr<PacketWriter>(new FilePacketWriter(fd));
}
#if PERFETTO_BUILDFLAG(PERFETTO_ZLIB)
std::unique_ptr<PacketWriter> CreateZipPacketWriter(
std::unique_ptr<PacketWriter> writer) {
return std::unique_ptr<PacketWriter>(new ZipPacketWriter(std::move(writer)));
}
#endif
} // namespace perfetto