1 /* 2 * Copyright (C) 2017 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 #ifndef INCLUDE_PERFETTO_PROTOZERO_SCATTERED_STREAM_WRITER_H_ 18 #define INCLUDE_PERFETTO_PROTOZERO_SCATTERED_STREAM_WRITER_H_ 19 20 #include <assert.h> 21 #include <stddef.h> 22 #include <stdint.h> 23 #include <string.h> 24 25 #include <algorithm> 26 27 #include "perfetto/base/compiler.h" 28 #include "perfetto/base/export.h" 29 #include "perfetto/base/logging.h" 30 #include "perfetto/protozero/contiguous_memory_range.h" 31 32 namespace protozero { 33 34 // This class deals with the following problem: append-only proto messages want 35 // to write a stream of bytes, without caring about the implementation of the 36 // underlying buffer (which concretely will be either the trace ring buffer 37 // or a heap-allocated buffer). The main deal is: proto messages don't know in 38 // advance what their size will be. 39 // Due to the tracing buffer being split into fixed-size chunks, on some 40 // occasions, these writes need to be spread over two (or more) non-contiguous 41 // chunks of memory. Similarly, when the buffer is backed by the heap, we want 42 // to avoid realloc() calls, as they might cause a full copy of the contents 43 // of the buffer. 44 // The purpose of this class is to abstract away the non-contiguous write logic. 45 // This class knows how to deal with writes as long as they fall in the same 46 // ContiguousMemoryRange and defers the chunk-chaining logic to the Delegate. 47 class PERFETTO_EXPORT_COMPONENT ScatteredStreamWriter { 48 public: 49 class PERFETTO_EXPORT_COMPONENT Delegate { 50 public: 51 static constexpr size_t kPatchSize = 4; 52 virtual ~Delegate(); 53 54 // Returns a new chunk for writing. 55 virtual ContiguousMemoryRange GetNewBuffer() = 0; 56 57 // Signals the delegate that the location pointed by `to_patch` (which must 58 // be in the last chunk returned by GetNewBuffer()), kPatchSize long, needs 59 // to be updated later (after potentially multiple GetNewBuffer calls). 60 // 61 // The caller must write to the returned location later. If the returned 62 // pointer is nullptr, the caller should not write anything. 63 // 64 // The implementation considers the patch ready to apply when the caller 65 // writes the first byte a value that's different than 0 (the 66 // implementation periodically checks for this). 67 virtual uint8_t* AnnotatePatch(uint8_t* patch_addr); 68 }; 69 70 explicit ScatteredStreamWriter(Delegate* delegate); 71 ~ScatteredStreamWriter(); 72 WriteByte(uint8_t value)73 inline void WriteByte(uint8_t value) { 74 if (write_ptr_ >= cur_range_.end) 75 Extend(); 76 *write_ptr_++ = value; 77 } 78 79 // Assumes that the caller checked that there is enough headroom. 80 // TODO(primiano): perf optimization, this is a tracing hot path. The 81 // compiler can make strong optimization on std::copy if the size arg is a 82 // constexpr. Make a templated variant of this for fixed-size writes. 83 // TODO(primiano): restrict / noalias might also help. WriteBytesUnsafe(const uint8_t * src,size_t size)84 inline void WriteBytesUnsafe(const uint8_t* src, size_t size) { 85 uint8_t* const end = write_ptr_ + size; 86 assert(end <= cur_range_.end); 87 std::copy(src, src + size, write_ptr_); 88 write_ptr_ = end; 89 } 90 WriteBytes(const uint8_t * src,size_t size)91 inline void WriteBytes(const uint8_t* src, 92 size_t size) PERFETTO_NO_SANITIZE_UNDEFINED { 93 // If the stream writer hasn't been initialized, constructing the end 94 // pointer below invokes undefined behavior because `write_ptr_` is null. 95 // Since this function is on the hot path, we suppress the warning instead 96 // of adding a conditional branch. 97 uint8_t* const end = write_ptr_ + size; 98 if (PERFETTO_LIKELY(end <= cur_range_.end)) 99 return WriteBytesUnsafe(src, size); 100 WriteBytesSlowPath(src, size); 101 } 102 103 void WriteBytesSlowPath(const uint8_t* src, size_t size); 104 105 // Reserves a fixed amount of bytes to be backfilled later. The reserved range 106 // is guaranteed to be contiguous and not span across chunks. |size| has to be 107 // <= than the size of a new buffer returned by the Delegate::GetNewBuffer(). 108 uint8_t* ReserveBytes(size_t size); 109 110 // Fast (but unsafe) version of the above. The caller must have previously 111 // checked that there are at least |size| contiguous bytes available. 112 // Returns only the start pointer of the reservation. ReserveBytesUnsafe(size_t size)113 uint8_t* ReserveBytesUnsafe(size_t size) { 114 uint8_t* begin = write_ptr_; 115 write_ptr_ += size; 116 assert(write_ptr_ <= cur_range_.end); 117 return begin; 118 } 119 120 // Shifts the previously written `size` bytes backwards in memory by `offset` 121 // bytes, moving the write pointer back accordingly. The shifted result must 122 // still be fully contained by the current range. Rewind(size_t size,size_t offset)123 void Rewind(size_t size, size_t offset) { 124 uint8_t* src = write_ptr_ - size; 125 uint8_t* dst = src - offset; 126 PERFETTO_DCHECK(src >= cur_range_.begin); 127 PERFETTO_DCHECK(src + size <= cur_range_.end); 128 PERFETTO_DCHECK(dst >= cur_range_.begin); 129 PERFETTO_DCHECK(dst + size <= cur_range_.end); 130 memmove(dst, src, size); 131 write_ptr_ -= offset; 132 } 133 134 // Resets the buffer boundaries and the write pointer to the given |range|. 135 // Subsequent WriteByte(s) will write into |range|. 136 void Reset(ContiguousMemoryRange range); 137 138 // Commits the current chunk and gets a new chunk from the delegate. 139 void Extend(); 140 141 // Number of contiguous free bytes in |cur_range_| that can be written without 142 // requesting a new buffer. bytes_available()143 size_t bytes_available() const { 144 return static_cast<size_t>(cur_range_.end - write_ptr_); 145 } 146 cur_range()147 ContiguousMemoryRange cur_range() const { return cur_range_; } 148 write_ptr()149 uint8_t* write_ptr() const { return write_ptr_; } 150 set_write_ptr(uint8_t * write_ptr)151 void set_write_ptr(uint8_t* write_ptr) { 152 assert(cur_range_.begin <= write_ptr && write_ptr <= cur_range_.end); 153 write_ptr_ = write_ptr; 154 } 155 written()156 uint64_t written() const { 157 return written_previously_ + 158 static_cast<uint64_t>(write_ptr_ - cur_range_.begin); 159 } 160 written_previously()161 uint64_t written_previously() const { return written_previously_; } 162 AnnotatePatch(uint8_t * patch_addr)163 uint8_t* AnnotatePatch(uint8_t* patch_addr) { 164 return delegate_->AnnotatePatch(patch_addr); 165 } 166 167 private: 168 ScatteredStreamWriter(const ScatteredStreamWriter&) = delete; 169 ScatteredStreamWriter& operator=(const ScatteredStreamWriter&) = delete; 170 171 Delegate* const delegate_; 172 ContiguousMemoryRange cur_range_; 173 uint8_t* write_ptr_; 174 uint64_t written_previously_ = 0; 175 }; 176 177 } // namespace protozero 178 179 #endif // INCLUDE_PERFETTO_PROTOZERO_SCATTERED_STREAM_WRITER_H_ 180