/src/perfetto/src/ipc/buffered_frame_deserializer.cc
Line | Count | Source |
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 | | #include "src/ipc/buffered_frame_deserializer.h" |
18 | | |
19 | | #include <algorithm> |
20 | | #include <cinttypes> |
21 | | #include <type_traits> |
22 | | #include <utility> |
23 | | |
24 | | #include "perfetto/base/logging.h" |
25 | | #include "perfetto/ext/base/utils.h" |
26 | | |
27 | | #include "protos/perfetto/ipc/wire_protocol.gen.h" |
28 | | |
29 | | namespace perfetto { |
30 | | namespace ipc { |
31 | | |
32 | | namespace { |
33 | | |
34 | | // The header is just the number of bytes of the Frame protobuf message. |
35 | | constexpr size_t kHeaderSize = sizeof(uint32_t); |
36 | | } // namespace |
37 | | |
38 | | BufferedFrameDeserializer::BufferedFrameDeserializer(size_t max_capacity) |
39 | 8.44k | : capacity_(max_capacity) { |
40 | 8.44k | PERFETTO_CHECK(max_capacity % base::GetSysPageSize() == 0); |
41 | 8.44k | PERFETTO_CHECK(max_capacity >= base::GetSysPageSize()); |
42 | 8.44k | } |
43 | | |
44 | 8.44k | BufferedFrameDeserializer::~BufferedFrameDeserializer() = default; |
45 | | |
46 | | BufferedFrameDeserializer::ReceiveBuffer |
47 | 14.8k | BufferedFrameDeserializer::BeginReceive() { |
48 | | // Upon the first recv initialize the buffer to the max message size but |
49 | | // release the physical memory for all but the first page. The kernel will |
50 | | // automatically give us physical pages back as soon as we page-fault on them. |
51 | 14.8k | if (!buf_.IsValid()) { |
52 | 8.16k | PERFETTO_DCHECK(size_ == 0); |
53 | | // TODO(eseckler): Don't commit all of the buffer at once on Windows. |
54 | 8.16k | buf_ = base::PagedMemory::Allocate(capacity_); |
55 | | |
56 | | // Surely we are going to use at least the first page, but we may not need |
57 | | // the rest for a bit. |
58 | 8.16k | const auto page_size = base::GetSysPageSize(); |
59 | 8.16k | buf_.AdviseDontNeed(buf() + page_size, capacity_ - page_size); |
60 | 8.16k | } |
61 | | |
62 | 14.8k | PERFETTO_CHECK(capacity_ > size_); |
63 | 14.8k | return ReceiveBuffer{buf() + size_, capacity_ - size_}; |
64 | 14.8k | } |
65 | | |
66 | 14.8k | bool BufferedFrameDeserializer::EndReceive(size_t recv_size) { |
67 | 14.8k | const auto page_size = base::GetSysPageSize(); |
68 | 14.8k | PERFETTO_CHECK(recv_size + size_ <= capacity_); |
69 | 14.8k | size_ += recv_size; |
70 | | |
71 | | // At this point the contents buf_ can contain: |
72 | | // A) Only a fragment of the header (the size of the frame). E.g., |
73 | | // 03 00 00 (the header is 4 bytes, one is missing). |
74 | | // |
75 | | // B) A header and a part of the frame. E.g., |
76 | | // 05 00 00 00 11 22 33 |
77 | | // [ header, size=5 ] [ Partial frame ] |
78 | | // |
79 | | // C) One or more complete header+frame. E.g., |
80 | | // 05 00 00 00 11 22 33 44 55 03 00 00 00 AA BB CC |
81 | | // [ header, size=5 ] [ Whole frame ] [ header, size=3 ] [ Whole frame ] |
82 | | // |
83 | | // D) Some complete header+frame(s) and a partial header or frame (C + A/B). |
84 | | // |
85 | | // C Is the more likely case and the one we are optimizing for. A, B, D can |
86 | | // happen because of the streaming nature of the socket. |
87 | | // The invariant of this function is that, when it returns, buf_ is either |
88 | | // empty (we drained all the complete frames) or starts with the header of the |
89 | | // next, still incomplete, frame. |
90 | | |
91 | 14.8k | size_t consumed_size = 0; |
92 | 4.85M | for (;;) { |
93 | 4.85M | if (size_ < consumed_size + kHeaderSize) |
94 | 12.5k | break; // Case A, not enough data to read even the header. |
95 | | |
96 | | // Read the header into |payload_size|. |
97 | 4.84M | uint32_t payload_size = 0; |
98 | 4.84M | const char* rd_ptr = buf() + consumed_size; |
99 | 4.84M | memcpy(base::AssumeLittleEndian(&payload_size), rd_ptr, kHeaderSize); |
100 | | |
101 | | // Saturate the |payload_size| to prevent overflows. The > capacity_ check |
102 | | // below will abort the parsing. |
103 | 4.84M | size_t next_frame_size = |
104 | 4.84M | std::min(static_cast<size_t>(payload_size), capacity_); |
105 | 4.84M | next_frame_size += kHeaderSize; |
106 | 4.84M | rd_ptr += kHeaderSize; |
107 | | |
108 | 4.84M | if (size_ < consumed_size + next_frame_size) { |
109 | | // Case B. We got the header but not the whole frame. |
110 | 2.28k | if (next_frame_size > capacity_) { |
111 | | // The caller is expected to shut down the socket and give up at this |
112 | | // point. If it doesn't do that and insists going on at some point it |
113 | | // will hit the capacity check in BeginReceive(). |
114 | 174 | PERFETTO_LOG("IPC Frame too large (size %zu)", next_frame_size); |
115 | 174 | return false; |
116 | 174 | } |
117 | 2.11k | break; |
118 | 2.28k | } |
119 | | |
120 | | // Case C. We got at least one header and whole frame. |
121 | 4.84M | DecodeFrame(rd_ptr, payload_size); |
122 | 4.84M | consumed_size += next_frame_size; |
123 | 4.84M | } |
124 | | |
125 | 14.8k | PERFETTO_DCHECK(consumed_size <= size_); |
126 | 14.6k | if (consumed_size > 0) { |
127 | | // Shift out the consumed data from the buffer. In the typical case (C) |
128 | | // there is nothing to shift really, just setting size_ = 0 is enough. |
129 | | // Shifting is only for the (unlikely) case D. |
130 | 8.63k | size_ -= consumed_size; |
131 | 8.63k | if (size_ > 0) { |
132 | | // Case D. We consumed some frames but there is a leftover at the end of |
133 | | // the buffer. Shift out the consumed bytes, so that on the next round |
134 | | // |buf_| starts with the header of the next unconsumed frame. |
135 | 1.66k | const char* move_begin = buf() + consumed_size; |
136 | 1.66k | PERFETTO_CHECK(move_begin > buf()); |
137 | 1.66k | PERFETTO_CHECK(move_begin + size_ <= buf() + capacity_); |
138 | 1.66k | memmove(buf(), move_begin, size_); |
139 | 1.66k | } |
140 | | // If we just finished decoding a large frame that used more than one page, |
141 | | // release the extra memory in the buffer. Large frames should be quite |
142 | | // rare. |
143 | 8.63k | if (consumed_size > page_size) { |
144 | 1.25k | size_t size_rounded_up = (size_ / page_size + 1) * page_size; |
145 | 1.25k | if (size_rounded_up < capacity_) { |
146 | 1.25k | char* madvise_begin = buf() + size_rounded_up; |
147 | 1.25k | const size_t madvise_size = capacity_ - size_rounded_up; |
148 | 1.25k | PERFETTO_CHECK(madvise_begin > buf() + size_); |
149 | 1.25k | PERFETTO_CHECK(madvise_begin + madvise_size <= buf() + capacity_); |
150 | 1.25k | buf_.AdviseDontNeed(madvise_begin, madvise_size); |
151 | 1.25k | } |
152 | 1.25k | } |
153 | 8.63k | } |
154 | | // At this point |size_| == 0 for case C, > 0 for cases A, B, D. |
155 | 14.6k | return true; |
156 | 14.6k | } |
157 | | |
158 | 1.10M | std::unique_ptr<Frame> BufferedFrameDeserializer::PopNextFrame() { |
159 | 1.10M | if (decoded_frames_.empty()) |
160 | 5.79k | return nullptr; |
161 | 1.09M | std::unique_ptr<Frame> frame = std::move(decoded_frames_.front()); |
162 | 1.09M | decoded_frames_.pop_front(); |
163 | 1.09M | return frame; |
164 | 1.10M | } |
165 | | |
166 | 4.84M | void BufferedFrameDeserializer::DecodeFrame(const char* data, size_t size) { |
167 | 4.84M | if (size == 0) |
168 | 3.44M | return; |
169 | 1.40M | std::unique_ptr<Frame> frame(new Frame); |
170 | 1.40M | if (frame->ParseFromArray(data, size)) |
171 | 1.34M | decoded_frames_.push_back(std::move(frame)); |
172 | 1.40M | } |
173 | | |
174 | | // static |
175 | 197k | std::string BufferedFrameDeserializer::Serialize(const Frame& frame) { |
176 | 197k | std::vector<uint8_t> payload = frame.SerializeAsArray(); |
177 | 197k | const uint32_t payload_size = static_cast<uint32_t>(payload.size()); |
178 | 197k | std::string buf; |
179 | 197k | buf.resize(kHeaderSize + payload_size); |
180 | 197k | memcpy(&buf[0], base::AssumeLittleEndian(&payload_size), kHeaderSize); |
181 | 197k | memcpy(&buf[kHeaderSize], payload.data(), payload.size()); |
182 | 197k | return buf; |
183 | 197k | } |
184 | | |
185 | | } // namespace ipc |
186 | | } // namespace perfetto |