| /* |
| * Copyright (C) 2025 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. |
| */ |
| |
| #ifndef SRC_TRACING_SERVICE_TRACE_BUFFER_V2_H_ |
| #define SRC_TRACING_SERVICE_TRACE_BUFFER_V2_H_ |
| |
| #include <stdint.h> |
| #include <string.h> |
| |
| #include <limits> |
| #include <optional> |
| #include <unordered_map> |
| |
| #include "perfetto/base/flat_set.h" |
| #include "perfetto/base/logging.h" |
| #include "perfetto/ext/base/circular_queue.h" |
| #include "perfetto/ext/base/flat_hash_map.h" |
| #include "perfetto/ext/base/paged_memory.h" |
| #include "perfetto/ext/base/small_vector.h" |
| #include "perfetto/ext/base/thread_annotations.h" |
| #include "perfetto/ext/base/utils.h" |
| #include "perfetto/ext/tracing/core/basic_types.h" |
| #include "perfetto/ext/tracing/core/client_identity.h" |
| #include "perfetto/ext/tracing/core/slice.h" |
| #include "perfetto/ext/tracing/core/trace_packet.h" |
| #include "perfetto/ext/tracing/core/trace_stats.h" |
| #include "src/tracing/service/histogram.h" |
| #include "src/tracing/service/trace_buffer.h" |
| |
| namespace perfetto { |
| |
| class TracePacket; |
| class TraceBufferV2; |
| |
| namespace protovm { |
| class Vm; |
| } |
| |
| namespace internal { |
| |
| // See /docs/design-docs/trace-buffer.md for details about the design and |
| // implementation details of TraceBuffer. |
| |
| // +---------------------------------------------------------------------------+ |
| // | TBChunk | |
| // +---------------------------------------------------------------------------+ |
| // TBChunk is the struct, stored in the trace buffer memory as a result of |
| // calling CopyChunkUntrusted from a SMB chunk. |
| // TBChunk exists only in the TraceBuffer PagedMemory `data_`, never on the |
| // stack or on the heap. It is followed by the fragments and alignment padding. |
| // A TBChunk is very similar to a SMB chunk with the following caveats: |
| // - The sizeof() both is the same (16 bytes). This is very important to keep |
| // patches offsets consistent. |
| // - The SMB chunk maintains a counter of fragments. TBChunk instead does |
| // byte-based bookkeeping, as that reduces the complexity of the iterators. |
| // - The layout of the fields is slightly different, but they both contains |
| // ProducerID, WriterID, ChunkID, fragment counts/sizes and flags. |
| // The SMB chunk layout is an ABI. The TCHunk layout is not: it is an |
| // implementation detail and can be changed. |
| // - TBChunk maintains a basic checksum for each chunk (only for debug builds). |
| struct TBChunk { |
| static constexpr size_t kMaxSize = std::numeric_limits<uint16_t>::max(); |
| static uint8_t Checksum(size_t off, size_t size) { |
| // Note: the checksum must be 0 for (off=0,size=0). See the comment in |
| // ReadNextTracePacket() about the edge case of the buffer completely empty. |
| return ((off >> 24) ^ (off >> 16) ^ (off >> 8) ^ off ^ (size >> 8) ^ size) & |
| 0xFF; |
| } |
| |
| explicit TBChunk(size_t off, size_t size_) |
| : size(static_cast<uint16_t>(size_)), checksum(Checksum(off, size)) { |
| PERFETTO_DCHECK(size_ <= kMaxSize); |
| } |
| |
| // The ChunkID, as specified by the TraceWriter in the original SMB chunk. |
| ChunkID chunk_id = 0; |
| |
| // A combination of producer and writer ID. This forms the primary key to |
| // look up the corresponding SequenceState from TraceBuffer.sequences_. |
| ProducerAndWriterID pri_wri_id = 0; |
| |
| // Size of the chunk, excluding the TBCHunk header itself, and without |
| // accounting for any alignment. This doesn't change throughout the lifecycle |
| // of a chunk. |
| uint16_t size = 0; |
| |
| // The size of the valid fragments payload. This is typically == size, with |
| // the exception of incomplete chunks committed while scraping. |
| // The payload of incomplete chunks can increase (up to the original chunk |
| // size). Wheh we scrape we set size = SMB chunk size, and |
| // payload_size = all_frag_size. |
| uint16_t payload_size = 0; |
| |
| // The number of payload bytes unconsumed. This starts at payload_size and |
| // shrinks until it reaches 0 as we consume fragments. |
| // It is always <= size and <= payload_size. |
| // Effectively (payload_size - payload-avail) is the offset of the next |
| // unconsumed fragment header (the varint with the size). |
| uint16_t payload_avail = 0; |
| |
| // These are == the SharedMemoryABI's chunk flags, with the addition of |
| // MSB flags added by TraceBufferV2 like kChunkIncomplete (0x80) which doesn't |
| // exist at the ABI level, but are synthesized here. |
| uint8_t flags = 0; |
| |
| // This is used for (D)CHECKS to verify the integrity of the chunk. |
| // This is a hash of the offset in the buffer and the size. |
| uint8_t checksum = 0; |
| |
| // Returns the offset to the next unread fragment in the chunk. Note that this |
| // points to the next fragment header (the varint with the size) NOT payload. |
| uint16_t unread_payload_off() { |
| PERFETTO_DCHECK((payload_avail <= payload_size)); |
| return payload_size - payload_avail; |
| } |
| |
| // TODO(primiano): in theory we could align just up to alignof(TBChunk) |
| // rather than sizeof(TBChunk), which is 4 rather than 16 bytes. That would |
| // reduce internal fragmentation. |
| // However, doing so requires some careful thinking as that creates the |
| // opportunity for more interesting edge cases, where overwriting a chunk |
| // leaves less than sizeof(TBChunk), leaving no space for a padding header |
| // after it. See TraceBufferV2Test.Overwrite_SizeDiffLessThanChunkHeader. |
| static inline constexpr size_t alignment() { return sizeof(TBChunk); } |
| |
| static inline size_t OuterSize(size_t sz) { |
| return base::AlignUp(sizeof(TBChunk) + sz, alignment()); |
| } |
| size_t outer_size() { return OuterSize(size); } |
| |
| bool is_padding() const { return pri_wri_id == 0; } |
| |
| uint8_t* fragments_begin() { |
| return reinterpret_cast<uint8_t*>(this) + sizeof(TBChunk); |
| } |
| |
| uint8_t* fragments_end() { return fragments_begin() + payload_size; } |
| |
| bool IsChecksumValid(size_t off) { return Checksum(off, size) == checksum; } |
| }; |
| |
| // +---------------------------------------------------------------------------+ |
| // | SequenceState | |
| // +---------------------------------------------------------------------------+ |
| // Holds the state for each sequence that has TBCHunk(s) in the buffer. |
| // Remember that this struct must be copyable for CloneReadOnly(). Don't hold |
| // onto any pointers in here. |
| // SequenceState(s) are not deleted aggressively to preserve the |
| // last_chunk_id_consumed and detect data losses in long tracing mode. We allow |
| // the last kKeepLastEmptySeq to stay alive to balance data loss detection with |
| // memory bloats. |
| struct SequenceState { |
| SequenceState(ProducerID, WriterID, ClientIdentity); |
| ~SequenceState(); |
| SequenceState(const SequenceState&) noexcept; |
| SequenceState& operator=(const SequenceState&) noexcept; |
| |
| ProducerID producer_id = 0; |
| WriterID writer_id = 0; |
| ClientIdentity client_identity{}; |
| |
| // This is semantically a boolean that resets every time BeginRead() |
| // increments the generation counter. The semantic is: |
| // skip := skip_in_generation == TraceBuffer.read_generation_. |
| uint64_t skip_in_generation = 0; |
| |
| // Used by DeleteStaleEmptySequences() to keep the latest kKeepLastEmptySeq |
| // objects around. |
| uint64_t age_for_gc = 0; |
| |
| std::optional<ChunkID> last_chunk_id_consumed; |
| |
| // This is set whenever a data loss is detected and cleared when reading the |
| // next packet for the sequence (which will report previous_packet_dropped). |
| bool data_loss = false; |
| |
| // An ordered list of chunk offsets, sorted by their ChunkID. Each member |
| // corresponsds to the offset within buf_ for the chunk. |
| // We store buffer offsets rather than pointers to make buffer cloning easier. |
| // This is effectively a deque of TBChunk* (% a call to GetTBChunkAt(off)). |
| base::CircularQueue<size_t> chunks; |
| }; |
| |
| // +---------------------------------------------------------------------------+ |
| // | FragIterator | |
| // +---------------------------------------------------------------------------+ |
| // A tokenized fragment in the buffer. Holds the fragment size & boundaries. |
| // This struct is returned by FragIterator when tokenizing the fragments in |
| // CopyChunkUntrusted() or reading the buffer. |
| // Frag instances are short lived. |
| struct Frag { |
| enum FragType : uint8_t { |
| // 1 packet == 1 fragment. |
| kFragWholePacket, |
| |
| // Fragmentation cases: |
| |
| // The last fragment of a chunk, when kLastPacketContinuesOnNextChunk |
| kFragBegin, |
| |
| // The only fragment of a chunk when both kLastPacketContinuesOnNextChunk & |
| // kFirstPacketContinuesFromPrevChunk |
| kFragContinue, |
| |
| // The first fragment of a chunk when kFirstPacketContinuesFromPrevChunk |
| kFragEnd, |
| }; |
| |
| // Diagram for the member variables below. |
| // +- `begin` |
| // | |
| // [VarInt header][ Fragment payload ] |
| // ( hdr_size )( size ) |
| // ( size_with_header ) |
| |
| // Pointes to the fragment payload, immediately after the header. |
| const uint8_t* const begin = nullptr; |
| FragType const type = kFragWholePacket; |
| uint8_t const hdr_size = 0; // Size of the varint that tells the frag size. |
| uint16_t const size = 0; // Size of the payload (the varint value). |
| |
| uint16_t size_with_header() { return size + hdr_size; } |
| |
| Frag(const uint8_t* b, FragType t, uint8_t h, uint16_t s) |
| : begin(b), type(t), hdr_size(h), size(s) {} |
| }; |
| |
| // A simple class that tokenizes fragments in a chunk and allows forward-only |
| // iteration. |
| // It deals with untrusted data, detecting malformed / out of bounds scenarios. |
| // It does not alter the state of the buffer. |
| // This is used in three places: |
| // - In CopyChunkUntrusted(): to tokenize the fragments and figure out the |
| // "effective" size of the chunk, to get rid of chunk padding. |
| // - In ChunkSeqReader::ReadNextPacket(): for the main read logic. |
| // - In ReassembleFragmentedPacket(): for the fragment reassembly logic. |
| class FragIterator { |
| public: |
| explicit FragIterator(TBChunk* chunk) |
| : chunk_begin_(chunk->fragments_begin()), |
| chunk_size_(chunk->payload_size), |
| next_frag_off_(chunk->unread_payload_off()), |
| chunk_flags_(chunk->flags) {} |
| |
| FragIterator(const uint8_t* begin, size_t size, uint8_t flags) |
| : chunk_begin_(begin), chunk_size_(size), chunk_flags_(flags) {} |
| |
| std::optional<Frag> NextFragmentInChunk(); |
| size_t next_frag_off() const { return next_frag_off_; } |
| bool chunk_corrupted() { return chunk_corrupted_; } |
| bool trace_writer_data_drop() { return trace_writer_data_drop_; } |
| |
| private: |
| const uint8_t* chunk_begin_ = nullptr; |
| size_t chunk_size_ = 0; |
| size_t next_frag_off_ = 0; |
| uint8_t chunk_flags_ = 0; |
| bool chunk_corrupted_ = false; |
| bool trace_writer_data_drop_ = false; |
| }; |
| |
| // +---------------------------------------------------------------------------+ |
| // | ChunkSeqIterator | |
| // +---------------------------------------------------------------------------+ |
| // A simple utility class that iterates over the ordered list of TBChunk for |
| // a given SequenceState. It merely follows the SequenceState.chunks queue |
| // and detects gaps. |
| class ChunkSeqIterator { |
| public: |
| // Rewinds to the first chunk of the sequence. |
| explicit ChunkSeqIterator(TraceBufferV2*, SequenceState*); |
| ChunkSeqIterator() = default; // Creates an invalid object, for default init. |
| ChunkSeqIterator(const ChunkSeqIterator&) = default; // Allow copy. |
| ChunkSeqIterator& operator=(const ChunkSeqIterator&) = default; |
| |
| TBChunk* NextChunkInSequence(); |
| void EraseCurrentChunk(); |
| TBChunk* chunk() const { return chunk_; } |
| bool sequence_gap_detected() const { return sequence_gap_detected_; } |
| bool valid() const { return !!seq_ && !!chunk_; } |
| |
| private: |
| TraceBufferV2* buf_ = nullptr; |
| SequenceState* seq_ = nullptr; |
| TBChunk* chunk_ = nullptr; |
| bool sequence_gap_detected_ = false; |
| size_t list_idx_ = 0; // Offset of the current chunk in seq_.chunks. |
| }; |
| |
| // +---------------------------------------------------------------------------+ |
| // | ChunkSeqReader | |
| // +---------------------------------------------------------------------------+ |
| // Encapsulates most of the readback complexity. It reads and consumes chunks |
| // in sequence order, as follows: |
| // |
| // When constructed, the caller must pass a target TBChunk as argument. This |
| // is the chunk where we will stop the iteration *. |
| // At readback time this is the next chunk in the buffer that we want to read. |
| // At overwrite time this is the chunk that we are about to overwriter. |
| // In both cases, because of OOO commits, the next chunk in buffer-order might |
| // not necessarily be the next chunk that should be consumed in FIFO order |
| // (although in the vast majority cases we expect them to be in order). |
| // Upon construction, it rewinds all the way back in the `SequenceState.chunks` |
| // (using `ChunkSeqIterator`) and starts the iteration from there. |
| // It keeps reading packets until we reach the target TBChunk passed in the |
| // constructor. |
| // In some cases (fragmentation) it might read beyon the target chunk. This is |
| // to reassembly a packet that started in the target chunk and continued later |
| // on. |
| // When doing so it just consumes the fragment required for reassembly and |
| // leaves the other packets in the chunk untouched, to preserve global FIFOness. |
| class ChunkSeqReader { |
| public: |
| enum Mode { |
| kReadMode, // For standard readback. |
| kEraseMode, // For read-while-overwriting in DeleteNextChunksFor(). |
| }; |
| |
| ChunkSeqReader(TraceBufferV2*, TBChunk*, Mode); |
| |
| bool ReadNextPacketInSeqOrder(TracePacket*); |
| TBChunk* end() { return end_; } |
| TBChunk* iter() { return iter_; } |
| SequenceState* seq() { return seq_; } |
| |
| private: |
| ChunkSeqReader(const ChunkSeqReader&) = delete; |
| ChunkSeqReader& operator=(const ChunkSeqReader&) = delete; |
| ChunkSeqReader(ChunkSeqReader&&) = delete; |
| ChunkSeqReader& operator=(ChunkSeqReader&&) = delete; |
| |
| enum class FragReassemblyResult { kSuccess = 0, kNotEnoughData, kDataLoss }; |
| FragReassemblyResult ReassembleFragmentedPacket(TracePacket* out_packet, |
| Frag* initial_frag); |
| void ConsumeFragment(TBChunk*, Frag*); |
| |
| TraceBufferV2* const buf_ = nullptr; |
| Mode const mode_; |
| |
| // This is the chunk passed in the constructor and is our stopping point. |
| // It never changes throughout the lifetime of ChunkSeqReader. |
| // Note that this is NOT the end of the sequence. This is simply where we |
| // want to stop iterating, which might be < seq_.end(). |
| TBChunk* const end_ = nullptr; |
| |
| SequenceState* const seq_ = nullptr; |
| |
| ChunkSeqIterator seq_iter_; |
| |
| // This is initially reset to the first chunk of the sequence, and advanced |
| // until we hit end_. chunk_ and end_ always belong to the same seq_. |
| TBChunk* iter_ = nullptr; |
| |
| FragIterator frag_iter_; |
| }; |
| |
| } // namespace internal |
| |
| // +---------------------------------------------------------------------------+ |
| // | TraceBufferV2 | |
| // +---------------------------------------------------------------------------+ |
| class TraceBufferV2 : public TraceBuffer { |
| public: |
| using TBChunk = internal::TBChunk; |
| using OverwritePolicy = TraceBuffer::OverwritePolicy; |
| using Patch = TraceBuffer::Patch; |
| using PacketSequenceProperties = TraceBuffer::PacketSequenceProperties; |
| |
| // Represents a ProtoVM instance and some metadata |
| struct Vm { |
| Vm(); |
| ~Vm(); |
| Vm(Vm&&) noexcept; |
| Vm CloneReadOnly() const; |
| |
| std::unique_ptr<protovm::Vm> instance; |
| std::string data_source_name; |
| uint64_t program_hash = 0; |
| uint32_t memory_limit_kb = 0; |
| base::FlatSet<ProducerID> producers; |
| }; |
| |
| // Can return nullptr if the memory allocation fails. |
| static std::unique_ptr<TraceBufferV2> Create(size_t size_in_bytes, |
| OverwritePolicy = kOverwrite); |
| |
| // Copies a Chunk from a producer Shared Memory Buffer into the trace buffer. |
| // |src| points to the first packet in the SharedMemoryABI's chunk shared with |
| // an untrusted producer. "untrusted" here means: the producer might be |
| // malicious and might change |src| concurrently while we read it (internally |
| // this method memcpy()-s first the chunk before processing it). None of the |
| // arguments should be trusted, unless otherwise stated. We can trust that |
| // |src| points to a valid memory area, but not its contents. |
| // |
| // This method may be called multiple times for the same chunk. In this case, |
| // the original chunk's payload will be overridden and its number of fragments |
| // and flags adjusted to match |num_fragments| and |chunk_flags|. The service |
| // may use this to insert partial chunks (|chunk_complete = false|) before the |
| // producer has committed them. |
| // |
| // If |chunk_complete| is |false|, we will only consider the first |
| // |num_fragments - 1| fragments to be complete, since the producer may have |
| // not finished writing the latest packet. Reading from a sequence will also |
| // not progress past any incomplete chunks until they were rewritten with |
| // |chunk_complete = true|, e.g. after a producer's commit. |
| void CopyChunkUntrusted(ProducerID producer_id_trusted, |
| const ClientIdentity& client_identity_trusted, |
| WriterID writer_id, |
| ChunkID chunk_id, |
| uint16_t num_fragments, |
| uint8_t chunk_flags, |
| bool chunk_complete, |
| const uint8_t* src, |
| size_t size) override; |
| |
| // Applies a batch of |patches| to the given chunk, if the given chunk is |
| // still in the buffer. Does nothing if the given ChunkID is gone. |
| // Returns true if the chunk has been found and patched, false otherwise. |
| // |other_patches_pending| is used to determine whether this is the only |
| // batch of patches for the chunk or there is more. |
| // If |other_patches_pending| == false, the chunk is marked as ready to be |
| // consumed. If true, the state of the chunk is not altered. |
| // |
| // Note: If the producer is batching commits (see shared_memory_arbiter.h), it |
| // will also attempt to do patching locally. Namely, if nested messages are |
| // completed while the chunk on which they started is being batched (i.e. |
| // before it has been committed to the service), the producer will apply the |
| // respective patches to the batched chunk. These patches will not be sent to |
| // the service - i.e. only the patches that the producer did not manage to |
| // apply before committing the chunk will be applied here. |
| bool TryPatchChunkContents(ProducerID, |
| WriterID, |
| ChunkID, |
| const Patch* patches, |
| size_t patches_size, |
| bool other_patches_pending) override; |
| |
| void MaybeSetUpProtoVm(const std::string& data_source_name, |
| const std::string& program_bytes, |
| uint32_t memory_limit_kb, |
| ProducerID producer_id); |
| |
| const std::vector<Vm>& GetProtoVmInstances() const { return protovms_; } |
| |
| // To read the contents of the buffer the caller needs to: |
| // BeginRead() |
| // while (ReadNextTracePacket(packet_fragments)) { ... } |
| // No other calls to any other method should be interleaved between |
| // BeginRead() and ReadNextTracePacket(). |
| // Reads in the TraceBufferV2 are NOT idempotent. |
| void BeginRead() override; |
| |
| // Returns the next packet in the buffer, if any, and the producer/writer |
| // identity that wrote it (as passed in the CopyChunkUntrusted() call). |
| // Returns false if no packets can be read at this point. |
| // If a packet was read successfully, |previous_packet_on_sequence_dropped| |
| // signals whether any data loss has been detected on the sequence |
| // (e.g. because its chunk was overridden due to the ring buffer wrapping or |
| // due to an ABI violation), and to |false| otherwise. |
| // |
| // This function returns only complete packets. Specifically: |
| // When there is at least one complete packet in the buffer, this function |
| // returns true and populates the TracePacket argument with the boundaries of |
| // each fragment for one packet. |
| // TracePacket will have at least one slice when this function returns true. |
| // When there are no whole packets eligible to read (e.g. we are still missing |
| // fragments) this function returns false. |
| // This function guarantees also that packets for a given |
| // {ProducerID, WriterID} are read in FIFO order. |
| // This function does not guarantee any ordering w.r.t. packets belonging to |
| // different WriterID(s). For instance, given the following packets copied |
| // into the buffer: |
| // {ProducerID: 1, WriterID: 1}: P1 P2 P3 |
| // {ProducerID: 1, WriterID: 2}: P4 P5 P6 |
| // {ProducerID: 2, WriterID: 1}: P7 P8 P9 |
| // The following read sequence is possible: |
| // P1, P4, P7, P2, P3, P5, P8, P9, P6 |
| // But the following is guaranteed to NOT happen: |
| // P1, P5, P7, P4 (P4 cannot come after P5) |
| bool ReadNextTracePacket(TracePacket*, |
| PacketSequenceProperties* sequence_properties, |
| bool* previous_packet_on_sequence_dropped) override; |
| |
| // Creates a read-only clone of the trace buffer. The read iterators of the |
| // new buffer will be reset, as if no Read() had been called. Calls to |
| // CopyChunkUntrusted() and TryPatchChunkContents() on the returned cloned |
| // TraceBuffer will CHECK(). |
| std::unique_ptr<TraceBuffer> CloneReadOnly() const override; |
| |
| size_t size() const override { return size_; } |
| size_t used_size() const override { return used_size_; } |
| size_t GetMemoryUsageBytes() const override; |
| OverwritePolicy overwrite_policy() const override { |
| return overwrite_policy_; |
| } |
| const TraceStats::BufferStats& stats() const override { return stats_; } |
| const WriterStats& writer_stats() const override { return writer_stats_; } |
| bool has_data() const override { return used_size_ > 0; } |
| void set_read_only() override { read_only_ = true; } |
| BufType buf_type() const override { return kV2; } |
| |
| void DumpForTesting(); |
| |
| private: |
| using Frag = internal::Frag; |
| using SequenceState = internal::SequenceState; |
| using ChunkSeqReader = internal::ChunkSeqReader; |
| |
| friend class TraceBufferV2Test; |
| friend class internal::ChunkSeqReader; |
| friend class internal::ChunkSeqIterator; |
| |
| explicit TraceBufferV2(OverwritePolicy); |
| TraceBufferV2(const TraceBufferV2&) = delete; |
| TraceBufferV2& operator=(const TraceBufferV2&) = delete; |
| |
| // Not using the implicit copy ctor to avoid unintended copies. |
| // This tagged ctor should be used only for Clone(). |
| struct CloneCtor {}; |
| TraceBufferV2(CloneCtor, const TraceBufferV2&); |
| |
| bool Initialize(size_t size); |
| TBChunk* CreateTBChunk(size_t off, size_t payload_size); |
| void DeleteNextChunksFor(size_t bytes_to_clear); |
| |
| void DcheckIsAlignedAndWithinBounds(size_t off) const { |
| PERFETTO_DCHECK((off & (alignof(TBChunk) - 1)) == 0); |
| PERFETTO_DCHECK(off <= size_ - sizeof(TBChunk)); |
| } |
| |
| // This should only be used when followed by a placement new. |
| TBChunk* GetTBChunkAtUnchecked(size_t off) { |
| DcheckIsAlignedAndWithinBounds(off); |
| return reinterpret_cast<TBChunk*>(begin() + off); |
| } |
| |
| TBChunk* GetTBChunkAt(size_t off) { |
| TBChunk* tbchunk = GetTBChunkAtUnchecked(off); |
| PERFETTO_CHECK(tbchunk->outer_size() <= (size_ - off)); |
| |
| // TODO(primiano): consider turning this into a DCHECK (and #ifdef-ing away |
| // the checksum code) once TBV2 proves to be reliable. |
| PERFETTO_CHECK(tbchunk->IsChecksumValid(off)); |
| return tbchunk; |
| } |
| |
| // Can return nullptr for padding chunks (or in case of programming errors). |
| SequenceState* GetSeqForChunk(const TBChunk* chunk) { |
| auto it = sequences_.find(chunk->pri_wri_id); |
| return it == sequences_.end() ? nullptr : &it->second; |
| } |
| |
| size_t OffsetOf(const TBChunk* chunk) { |
| uintptr_t addr = reinterpret_cast<uintptr_t>(chunk); |
| uintptr_t buf_start = reinterpret_cast<uintptr_t>(begin()); |
| PERFETTO_DCHECK(addr >= buf_start && buf_start <= addr + size_); |
| return static_cast<size_t>(addr - buf_start); |
| } |
| |
| void DiscardWrite(); |
| void DeleteStaleEmptySequences(); |
| void MaybeProcessOverwrittenPacketWithProtoVm(const TracePacket&, ProducerID); |
| |
| uint8_t* begin() const { return reinterpret_cast<uint8_t*>(data_.Get()); } |
| uint8_t* end() const { return begin() + size_; } |
| size_t size_to_end() const { return size_ - wr_; } |
| |
| base::PagedMemory data_; |
| size_t size_ = 0; // Size in bytes of |data_|. |
| |
| // High watermark. The number of bytes (<= |size_|) written into the buffer |
| // before the first wraparound. This increases as data is written into the |
| // buffer and then saturates at |size_|. |
| size_t used_size_ = 0; |
| |
| size_t wr_ = 0; // Write cursor (offset since start()). |
| size_t rd_ = 0; // Read cursor. Reset to wr_ on every BeginRead(). |
| std::optional<ChunkSeqReader> chunk_seq_reader_; |
| |
| // Statistics about buffer usage. |
| TraceStats::BufferStats stats_; |
| |
| // Statistics about TraceWriters. |
| WriterStats writer_stats_; |
| |
| OverwritePolicy overwrite_policy_ = kOverwrite; |
| |
| // Note: we need stable pointers for SequenceState, as they get cached in |
| // BufIterator. |
| std::unordered_map<ProducerAndWriterID, SequenceState> sequences_; |
| |
| // COUNT(sequences_) WHERE sequence.chunks.empty(). |
| // This is maintained best effort and needs revalidation against sequences_. |
| size_t empty_sequences_ = 0; |
| |
| // A generation counter incremented every time BeginRead() is called. |
| uint64_t read_generation_ = 0; |
| |
| // A monotonic counter incremented every time a SequenceState becomes empty. |
| // This is used to sort SequenceState by least-recently cleared. |
| uint64_t seq_age_ = 0; |
| |
| // This buffer is a read-only snapshot obtained via Clone(). If this is true |
| // calls to CopyChunkUntrusted() and TryPatchChunkContents() will CHECK(). |
| bool read_only_ = false; |
| |
| // Only used when |overwrite_policy_ == kDiscard|. This is set the first time |
| // a write fails because it would overwrite unread chunks. |
| bool discard_writes_ = false; |
| |
| // When true disable some DCHECKs that have been put in place to detect |
| // bugs in the producers. This is for tests that feed malicious inputs and |
| // hence mimic a buggy producer. |
| bool suppress_client_dchecks_for_testing_ = false; |
| |
| // ProtoVMs used to process overwritten packets (go/perfetto-proto-vm) |
| std::vector<Vm> protovms_; |
| |
| // Used to collect slices of the overwritten packet. Note that this is a |
| // member variable (instead of local) so that the memory (internal |
| // std::vector<Slice>) is re-used across overwritten packets, thus involving |
| // allocations only when the vector needs to be expanded (in practice only a |
| // few times during the initial iterations). |
| TracePacket overwritten_packet_; |
| |
| // Storage used to re-write overwritten packets (from TBv2) into contiguous |
| // memory to be used as ProtoVM patches (must be continguous to be decoded |
| // with protozero). Note that this is a member variable (instead of local) so |
| // that the memory is re-used across overwritten packets, thus involving |
| // allocations only when the storage needs to be expanded. |
| std::string protovm_patch_; |
| }; |
| |
| } // namespace perfetto |
| |
| #endif // SRC_TRACING_SERVICE_TRACE_BUFFER_V2_H_ |