blob: 451e65dab5173c48a950f64582c0636ed7bf8cc8 [file] [log] [blame]
Primiano Tuccid7b59c42017-12-19 01:43:07 +01001/*
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/tracing/core/trace_writer_impl.h"
18
19#include <string.h>
20
21#include <type_traits>
22#include <utility>
23
24#include "perfetto/base/logging.h"
25#include "perfetto/protozero/proto_utils.h"
Oystein Eftevaag6d0bc7f2018-01-13 12:21:55 -080026#include "src/tracing/core/shared_memory_arbiter_impl.h"
Primiano Tuccid7b59c42017-12-19 01:43:07 +010027
28#include "protos/trace_packet.pbzero.h"
29
30// TODO(primiano): right now this class is accumulating a patchlist but not
31// sending it to the service.
32
33using protozero::proto_utils::kMessageLengthFieldSize;
34using protozero::proto_utils::WriteRedundantVarInt;
35using ChunkHeader = perfetto::SharedMemoryABI::ChunkHeader;
36
37namespace perfetto {
38
39namespace {
40constexpr size_t kPacketHeaderSize = SharedMemoryABI::kPacketHeaderSize;
41} // namespace
42
Oystein Eftevaag6d0bc7f2018-01-13 12:21:55 -080043TraceWriterImpl::TraceWriterImpl(SharedMemoryArbiterImpl* shmem_arbiter,
Primiano Tuccid7b59c42017-12-19 01:43:07 +010044 WriterID id,
45 BufferID target_buffer)
46 : shmem_arbiter_(shmem_arbiter),
47 id_(id),
48 target_buffer_(target_buffer),
49 protobuf_stream_writer_(this) {
50 // TODO(primiano): we could handle the case of running out of TraceWriterID(s)
51 // more gracefully and always return a no-op TracePacket in NewTracePacket().
52 PERFETTO_CHECK(id_ != 0);
53
54 cur_packet_.reset(new protos::pbzero::TracePacket());
55 cur_packet_->Finalize(); // To avoid the DCHECK in NewTracePacket().
56}
57
58TraceWriterImpl::~TraceWriterImpl() {
59 // TODO(primiano): this should also return the current chunk. Add tests.
60 shmem_arbiter_->ReleaseWriterID(id_);
61}
62
63TraceWriterImpl::TracePacketHandle TraceWriterImpl::NewTracePacket() {
64 // If we hit this, the caller is calling NewTracePacket() without having
65 // finalized the previous packet.
66 PERFETTO_DCHECK(cur_packet_->is_finalized());
67
68 fragmenting_packet_ = false;
69
70 // TODO: hack to get a new page every time and reduce fragmentation (that
71 // requires stitching support in the service).
72 protobuf_stream_writer_.Reset(GetNewBuffer());
73
74 // Reserve space for the size of the message. Note: this call might re-enter
75 // into this class invoking GetNewBuffer() if there isn't enough space or if
76 // this is the very first call to NewTracePacket().
77 static_assert(
78 kPacketHeaderSize == kMessageLengthFieldSize,
79 "The packet header must match the ProtoZeroMessage header size");
80 cur_packet_->Reset(&protobuf_stream_writer_);
81 uint8_t* header = protobuf_stream_writer_.ReserveBytes(kPacketHeaderSize);
82 memset(header, 0, kPacketHeaderSize);
83 cur_packet_->set_size_field(header);
84 cur_chunk_.IncrementPacketCount();
85 TracePacketHandle handle(cur_packet_.get());
86 cur_fragment_start_ = protobuf_stream_writer_.write_ptr();
87 fragmenting_packet_ = true;
88 return handle;
89}
90
91// Called by the ProtoZeroMessage. We can get here in two cases:
92// 1. In the middle of writing a ProtoZeroMessage,
93// when |fragmenting_packet_| == true. In this case we want to update the
94// chunk header with a partial packet and start a new partial packet in the
95// new chunk.
96// 2. While calling ReserveBytes() for the packet header in NewTracePacket().
97// In this case |fragmenting_packet_| == false and we just want a new chunk
98// without creating any fragments.
99protozero::ContiguousMemoryRange TraceWriterImpl::GetNewBuffer() {
100 if (fragmenting_packet_) {
101 uint8_t* const wptr = protobuf_stream_writer_.write_ptr();
102 PERFETTO_DCHECK(wptr >= cur_fragment_start_);
103 uint32_t partial_size = static_cast<uint32_t>(wptr - cur_fragment_start_);
104 PERFETTO_DCHECK(partial_size < cur_chunk_.size());
105
106 // Backfill the packet header with the fragment size.
107 cur_packet_->inc_size_already_written(partial_size);
108 cur_chunk_.SetFlag(ChunkHeader::kLastPacketContinuesOnNextChunk);
109 WriteRedundantVarInt(partial_size, cur_packet_->size_field());
110
111 // Descend in the stack of non-finalized nested submessages (if any) and
112 // detour their |size_field| into the |patch_list_|. At this point we have
113 // to release the chunk and they cannot write anymore into that.
114 // TODO(primiano): add tests to cover this logic.
115 for (auto* nested_msg = cur_packet_->nested_message(); nested_msg;
116 nested_msg = nested_msg->nested_message()) {
117 uint8_t* const cur_hdr = nested_msg->size_field();
118 PERFETTO_DCHECK(cur_hdr >= cur_chunk_.payload_begin() &&
119 cur_hdr + kMessageLengthFieldSize <= cur_chunk_.end());
120 auto cur_hdr_offset = static_cast<uint16_t>(cur_hdr - cur_chunk_.begin());
121 patch_list_.emplace_front(cur_chunk_id_, cur_hdr_offset);
122 Patch& patch = patch_list_.front();
123 nested_msg->set_size_field(patch.size_field);
124 PERFETTO_DLOG("Created new patchlist entry for protobuf nested message");
125 }
126 }
127
128 if (cur_chunk_.is_valid())
129 shmem_arbiter_->ReturnCompletedChunk(std::move(cur_chunk_));
130
131 // Start a new chunk.
132 ChunkHeader::Identifier identifier = {};
133 identifier.writer_id = id_;
134 identifier.chunk_id = cur_chunk_id_++;
135
136 ChunkHeader::PacketsState packets_state = {};
137 if (fragmenting_packet_) {
138 packets_state.count = 1;
139 packets_state.flags = ChunkHeader::kFirstPacketContinuesFromPrevChunk;
140 }
141
142 // The memory order of the stores below doesn't really matter. This |header|
143 // is just a local temporary object. The GetNewChunk() call below will copy it
144 // into the shared buffer with the proper barriers.
145 ChunkHeader header = {};
146 header.identifier.store(identifier, std::memory_order_relaxed);
147 header.packets_state.store(packets_state, std::memory_order_relaxed);
148
149 cur_chunk_ = shmem_arbiter_->GetNewChunk(header, target_buffer_);
150 uint8_t* payload_begin = cur_chunk_.payload_begin();
151 if (fragmenting_packet_) {
152 cur_packet_->set_size_field(payload_begin);
153 memset(payload_begin, 0, kPacketHeaderSize);
154 payload_begin += kPacketHeaderSize;
155 cur_fragment_start_ = payload_begin;
156 }
157
158 return protozero::ContiguousMemoryRange{payload_begin, cur_chunk_.end()};
159}
160
161WriterID TraceWriterImpl::writer_id() const {
162 return id_;
163};
164
165TraceWriterImpl::Patch::Patch(uint16_t cid, uint16_t offset)
166 : chunk_id(cid), offset_in_chunk(offset) {}
167
168// Base class ctor/dtor definition.
169TraceWriter::TraceWriter() = default;
170TraceWriter::~TraceWriter() = default;
171
172} // namespace perfetto