• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 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 SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_PROTO_TRACE_TOKENIZER_H_
18 #define SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_PROTO_TRACE_TOKENIZER_H_
19 
20 #include <vector>
21 
22 #include "perfetto/protozero/proto_utils.h"
23 #include "perfetto/trace_processor/status.h"
24 #include "perfetto/trace_processor/trace_blob.h"
25 #include "perfetto/trace_processor/trace_blob_view.h"
26 #include "src/trace_processor/util/gzip_utils.h"
27 #include "src/trace_processor/util/status_macros.h"
28 
29 #include "protos/perfetto/trace/trace.pbzero.h"
30 #include "protos/perfetto/trace/trace_packet.pbzero.h"
31 
32 namespace perfetto {
33 namespace trace_processor {
34 
35 // Reads a protobuf trace in chunks and extracts boundaries of trace packets
36 // (or subfields, for the case of ftrace) with their timestamps.
37 class ProtoTraceTokenizer {
38  public:
39   ProtoTraceTokenizer();
40 
41   template <typename Callback = util::Status(TraceBlobView)>
Tokenize(TraceBlobView blob,Callback callback)42   util::Status Tokenize(TraceBlobView blob, Callback callback) {
43     const uint8_t* data = blob.data();
44     size_t size = blob.size();
45     if (!partial_buf_.empty()) {
46       // It takes ~5 bytes for a proto preamble + the varint size.
47       const size_t kHeaderBytes = 5;
48       if (PERFETTO_UNLIKELY(partial_buf_.size() < kHeaderBytes)) {
49         size_t missing_len = std::min(kHeaderBytes - partial_buf_.size(), size);
50         partial_buf_.insert(partial_buf_.end(), &data[0], &data[missing_len]);
51         if (partial_buf_.size() < kHeaderBytes)
52           return util::OkStatus();
53         data += missing_len;
54         size -= missing_len;
55       }
56 
57       // At this point we have enough data in |partial_buf_| to read at least
58       // the field header and know the size of the next TracePacket.
59       const uint8_t* pos = &partial_buf_[0];
60       uint8_t proto_field_tag = *pos;
61       uint64_t field_size = 0;
62       // We cannot do &partial_buf_[partial_buf_.size()] because that crashes
63       // on MSVC STL debug builds, so does &*partial_buf_.end().
64       const uint8_t* next = protozero::proto_utils::ParseVarInt(
65           ++pos, &partial_buf_.front() + partial_buf_.size(), &field_size);
66       bool parse_failed = next == pos;
67       pos = next;
68       if (proto_field_tag != kTracePacketTag || field_size == 0 ||
69           parse_failed) {
70         return util::ErrStatus(
71             "Failed parsing a TracePacket from the partial buffer");
72       }
73 
74       // At this point we know how big the TracePacket is.
75       size_t hdr_size = static_cast<size_t>(pos - &partial_buf_[0]);
76       size_t size_incl_header = static_cast<size_t>(field_size + hdr_size);
77       PERFETTO_DCHECK(size_incl_header > partial_buf_.size());
78 
79       // There is a good chance that between the |partial_buf_| and the new
80       // |data| of the current call we have enough bytes to parse a TracePacket.
81       if (partial_buf_.size() + size >= size_incl_header) {
82         // Create a new buffer for the whole TracePacket and copy into that:
83         // 1) The beginning of the TracePacket (including the proto header) from
84         //    the partial buffer.
85         // 2) The rest of the TracePacket from the current |data| buffer (note
86         //    that we might have consumed already a few bytes form |data|
87         //    earlier in this function, hence we need to keep |off| into
88         //    account).
89         TraceBlob glued = TraceBlob::Allocate(size_incl_header);
90         memcpy(glued.data(), partial_buf_.data(), partial_buf_.size());
91         // |size_missing| is the number of bytes for the rest of the TracePacket
92         // in |data|.
93         size_t size_missing = size_incl_header - partial_buf_.size();
94         memcpy(glued.data() + partial_buf_.size(), &data[0], size_missing);
95         data += size_missing;
96         size -= size_missing;
97         partial_buf_.clear();
98         RETURN_IF_ERROR(
99             ParseInternal(TraceBlobView(std::move(glued)), callback));
100       } else {
101         partial_buf_.insert(partial_buf_.end(), data, &data[size]);
102         return util::OkStatus();
103       }
104     }
105     return ParseInternal(blob.slice(data, size), callback);
106   }
107 
108  private:
109   static constexpr uint8_t kTracePacketTag =
110       protozero::proto_utils::MakeTagLengthDelimited(
111           protos::pbzero::Trace::kPacketFieldNumber);
112 
113   template <typename Callback = util::Status(TraceBlobView)>
ParseInternal(TraceBlobView whole_buf,Callback callback)114   util::Status ParseInternal(TraceBlobView whole_buf, Callback callback) {
115     const uint8_t* const start = whole_buf.data();
116     protos::pbzero::Trace::Decoder decoder(whole_buf.data(), whole_buf.size());
117     for (auto it = decoder.packet(); it; ++it) {
118       protozero::ConstBytes packet = *it;
119       TraceBlobView sliced = whole_buf.slice(packet.data, packet.size);
120       RETURN_IF_ERROR(ParsePacket(std::move(sliced), callback));
121     }
122 
123     const size_t bytes_left = decoder.bytes_left();
124     if (bytes_left > 0) {
125       PERFETTO_DCHECK(partial_buf_.empty());
126       partial_buf_.insert(partial_buf_.end(), &start[decoder.read_offset()],
127                           &start[decoder.read_offset() + bytes_left]);
128     }
129     return util::OkStatus();
130   }
131 
132   template <typename Callback = util::Status(TraceBlobView)>
ParsePacket(TraceBlobView packet,Callback callback)133   util::Status ParsePacket(TraceBlobView packet, Callback callback) {
134     protos::pbzero::TracePacket::Decoder decoder(packet.data(),
135                                                  packet.length());
136     if (decoder.has_compressed_packets()) {
137       if (!util::IsGzipSupported()) {
138         return util::Status(
139             "Cannot decode compressed packets. Zlib not enabled");
140       }
141 
142       protozero::ConstBytes field = decoder.compressed_packets();
143       TraceBlobView compressed_packets = packet.slice(field.data, field.size);
144       TraceBlobView packets;
145 
146       RETURN_IF_ERROR(Decompress(std::move(compressed_packets), &packets));
147 
148       const uint8_t* start = packets.data();
149       const uint8_t* end = packets.data() + packets.length();
150       const uint8_t* ptr = start;
151       while ((end - ptr) > 2) {
152         const uint8_t* packet_outer = ptr;
153         if (PERFETTO_UNLIKELY(*ptr != kTracePacketTag))
154           return util::ErrStatus("Expected TracePacket tag");
155         uint64_t packet_size = 0;
156         ptr = protozero::proto_utils::ParseVarInt(++ptr, end, &packet_size);
157         const uint8_t* packet_start = ptr;
158         ptr += packet_size;
159         if (PERFETTO_UNLIKELY((ptr - packet_outer) < 2 || ptr > end))
160           return util::ErrStatus("Invalid packet size");
161 
162         TraceBlobView sliced =
163             packets.slice(packet_start, static_cast<size_t>(packet_size));
164         RETURN_IF_ERROR(ParsePacket(std::move(sliced), callback));
165       }
166       return util::OkStatus();
167     }
168     return callback(std::move(packet));
169   }
170 
171   util::Status Decompress(TraceBlobView input, TraceBlobView* output);
172 
173   // Used to glue together trace packets that span across two (or more)
174   // Parse() boundaries.
175   std::vector<uint8_t> partial_buf_;
176 
177   // Allows support for compressed trace packets.
178   util::GzipDecompressor decompressor_;
179 };
180 
181 }  // namespace trace_processor
182 }  // namespace perfetto
183 
184 #endif  // SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_PROTO_TRACE_TOKENIZER_H_
185