• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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