• 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 #include "src/trace_processor/rpc/query_result_serializer.h"
18 
19 #include <vector>
20 
21 #include "perfetto/protozero/packed_repeated_fields.h"
22 #include "perfetto/protozero/proto_utils.h"
23 #include "perfetto/protozero/scattered_heap_buffer.h"
24 #include "src/trace_processor/iterator_impl.h"
25 
26 #include "protos/perfetto/trace_processor/trace_processor.pbzero.h"
27 
28 namespace perfetto {
29 namespace trace_processor {
30 
31 namespace {
32 
33 namespace pu = ::protozero::proto_utils;
34 using BatchProto = protos::pbzero::QueryResult::CellsBatch;
35 using ResultProto = protos::pbzero::QueryResult;
36 
37 // The reserved field in trace_processor.proto.
38 static constexpr uint32_t kPaddingFieldId = 7;
39 
MakeLenDelimTag(uint32_t field_num)40 uint8_t MakeLenDelimTag(uint32_t field_num) {
41   uint32_t tag = pu::MakeTagLengthDelimited(field_num);
42   PERFETTO_DCHECK(tag <= 127);  // Must fit in one byte.
43   return static_cast<uint8_t>(tag);
44 }
45 
46 }  // namespace
47 
QueryResultSerializer(Iterator iter)48 QueryResultSerializer::QueryResultSerializer(Iterator iter)
49     : iter_(iter.take_impl()), num_cols_(iter_->ColumnCount()) {}
50 
51 QueryResultSerializer::~QueryResultSerializer() = default;
52 
Serialize(std::vector<uint8_t> * buf)53 bool QueryResultSerializer::Serialize(std::vector<uint8_t>* buf) {
54   const size_t slice = batch_split_threshold_ + 4096;
55   protozero::HeapBuffered<protos::pbzero::QueryResult> result(slice, slice);
56   bool has_more = Serialize(result.get());
57   auto arr = result.SerializeAsArray();
58   buf->insert(buf->end(), arr.begin(), arr.end());
59   return has_more;
60 }
61 
Serialize(protos::pbzero::QueryResult * res)62 bool QueryResultSerializer::Serialize(protos::pbzero::QueryResult* res) {
63   PERFETTO_CHECK(!eof_reached_);
64 
65   if (!did_write_column_names_) {
66     SerializeColumnNames(res);
67     did_write_column_names_ = true;
68   }
69 
70   // In case of an error we still want to go through SerializeBatch(). That will
71   // write an empty batch with the EOF marker. Errors can happen also in the
72   // middle of a query, not just before starting it.
73 
74   SerializeBatch(res);
75   MaybeSerializeError(res);
76   return !eof_reached_;
77 }
78 
SerializeBatch(protos::pbzero::QueryResult * res)79 void QueryResultSerializer::SerializeBatch(protos::pbzero::QueryResult* res) {
80   // The buffer is filled in this way:
81   // - Append all the strings as we iterate through the results. The rationale
82   //   is that strings are typically the largest part of the result and we want
83   //   to avoid copying these.
84   // - While iterating, buffer all other types of cells. They will be appended
85   //   at the end of the batch, after the string payload is known.
86 
87   // Note: this function uses uint32_t instead of size_t because Wasm doesn't
88   // have yet native 64-bit integers and this is perf-sensitive.
89 
90   const auto& writer = *res->stream_writer();
91   auto* batch = res->add_batch();
92 
93   // Start the |string_cells|.
94   auto* strings = batch->BeginNestedMessage<protozero::Message>(
95       BatchProto::kStringCellsFieldNumber);
96 
97   // This keeps track of the overall size of the batch. It is used to decide if
98   // we need to prematurely end the batch, even if the batch_split_threshold_ is
99   // not reached. This is to guard against the degenerate case of appending a
100   // lot of very large strings and ending up with an enormous batch.
101   uint32_t approx_batch_size = 16;
102 
103   std::vector<uint8_t> cell_types(cells_per_batch_);
104 
105   // Varints and doubles are written on stack-based storage and appended later.
106   protozero::PackedVarInt varints;
107   protozero::PackedFixedSizeInt<double> doubles;
108 
109   // We write blobs on a temporary heap buffer and append it at the end. Blobs
110   // are extremely rare, trying to avoid copies is not worth the complexity.
111   std::vector<uint8_t> blobs;
112 
113   uint32_t cell_idx = 0;
114   bool batch_full = false;
115 
116   for (;; ++cell_idx, ++col_) {
117     // This branch is hit before starting each row. Note that iter_->Next() must
118     // be called before iterating on a row. col_ is initialized at MAX_INT in
119     // the constructor.
120     if (col_ >= num_cols_) {
121       col_ = 0;
122       // If num_cols_ == 0 and the query didn't return any result (e.g. CREATE
123       // TABLE) we should exit at this point. We still need to advance the
124       // iterator via Next() otherwise the statement will have no effect.
125       if (!iter_->Next())
126         break;  // EOF or error.
127 
128       PERFETTO_DCHECK(num_cols_ > 0);
129       // We need to guarantee that a batch contains whole rows. Before moving to
130       // the next row, make sure that: (i) there is space for all the columns;
131       // (ii) the batch didn't grow too much.
132       if (cell_idx + num_cols_ > cells_per_batch_ ||
133           approx_batch_size > batch_split_threshold_) {
134         batch_full = true;
135         break;
136       }
137     }
138 
139     auto value = iter_->Get(col_);
140     uint8_t cell_type = BatchProto::CELL_INVALID;
141     switch (value.type) {
142       case SqlValue::Type::kNull: {
143         cell_type = BatchProto::CELL_NULL;
144         break;
145       }
146       case SqlValue::Type::kLong: {
147         cell_type = BatchProto::CELL_VARINT;
148         varints.Append(value.long_value);
149         approx_batch_size += 4;  // Just a guess, doesn't need to be accurate.
150         break;
151       }
152       case SqlValue::Type::kDouble: {
153         cell_type = BatchProto::CELL_FLOAT64;
154         approx_batch_size += sizeof(double);
155         doubles.Append(value.double_value);
156         break;
157       }
158       case SqlValue::Type::kString: {
159         // Append the string to the one |string_cells| proto field, just use
160         // \0 to separate each string. We are deliberately NOT emitting one
161         // proto repeated field for each string. Doing so significantly slows
162         // down parsing on the JS side (go/postmessage-benchmark).
163         cell_type = BatchProto::CELL_STRING;
164         uint32_t len_with_nul =
165             static_cast<uint32_t>(strlen(value.string_value)) + 1;
166         const char* str_begin = value.string_value;
167         strings->AppendRawProtoBytes(str_begin, len_with_nul);
168         approx_batch_size += len_with_nul + 4;  // 4 is a guess on the preamble.
169         break;
170       }
171       case SqlValue::Type::kBytes: {
172         // Each blob is stored as its own repeated proto field, unlike strings.
173         // Blobs don't incur in text-decoding overhead (and are also rare).
174         cell_type = BatchProto::CELL_BLOB;
175         auto* src = static_cast<const uint8_t*>(value.bytes_value);
176         uint32_t len = static_cast<uint32_t>(value.bytes_count);
177         uint8_t preamble[16];
178         uint8_t* preamble_end = &preamble[0];
179         *(preamble_end++) = MakeLenDelimTag(BatchProto::kBlobCellsFieldNumber);
180         preamble_end = pu::WriteVarInt(len, preamble_end);
181         blobs.insert(blobs.end(), preamble, preamble_end);
182         blobs.insert(blobs.end(), src, src + len);
183         approx_batch_size += len + 4;  // 4 is a guess on the preamble size.
184         break;
185       }
186     }
187 
188     PERFETTO_DCHECK(cell_type != BatchProto::CELL_INVALID);
189     cell_types[cell_idx] = cell_type;
190   }  // for (cell)
191 
192   // Backfill the string size.
193   strings->Finalize();
194   strings = nullptr;
195 
196   // Write the cells headers (1 byte per cell).
197   batch->AppendBytes(BatchProto::kCellsFieldNumber, cell_types.data(),
198                      cell_idx);
199 
200   // Append the |varint_cells|, copying over the packed varint buffer.
201   if (varints.size())
202     batch->set_varint_cells(varints);
203 
204   // Append the |float64_cells|, copying over the packed fixed64 buffer. This is
205   // appended at a 64-bit aligned offset, so that JS can access these by overlay
206   // a TypedArray, without extra copies.
207   const uint32_t doubles_size = static_cast<uint32_t>(doubles.size());
208   if (doubles_size > 0) {
209     uint8_t preamble[16];
210     uint8_t* preamble_end = &preamble[0];
211     *(preamble_end++) = MakeLenDelimTag(BatchProto::kFloat64CellsFieldNumber);
212     preamble_end = pu::WriteVarInt(doubles_size, preamble_end);
213     uint32_t preamble_size = static_cast<uint32_t>(preamble_end - &preamble[0]);
214 
215     // The byte after the preamble must start at a 64bit-aligned offset.
216     // The padding needs to be > 1 Byte because of proto encoding.
217     const uint32_t off =
218         static_cast<uint32_t>(writer.written() + preamble_size);
219     const uint32_t aligned_off = (off + 7) & ~7u;
220     uint32_t padding = aligned_off - off;
221     padding = padding == 1 ? 9 : padding;
222     if (padding > 0) {
223       uint8_t pad_buf[10];
224       uint8_t* pad = pad_buf;
225       *(pad++) = pu::MakeTagVarInt(kPaddingFieldId);
226       for (uint32_t i = 0; i < padding - 2; i++)
227         *(pad++) = 0x80;
228       *(pad++) = 0;
229       batch->AppendRawProtoBytes(pad_buf, static_cast<size_t>(pad - pad_buf));
230     }
231     batch->AppendRawProtoBytes(preamble, preamble_size);
232     PERFETTO_CHECK(writer.written() % 8 == 0);
233     batch->AppendRawProtoBytes(doubles.data(), doubles_size);
234   }  // if (doubles_size > 0)
235 
236   // Append the blobs.
237   batch->AppendRawProtoBytes(blobs.data(), blobs.size());
238 
239   // If this is the last batch, write the EOF field.
240   if (!batch_full) {
241     eof_reached_ = true;
242     batch->set_is_last_batch(true);
243   }
244 
245   // Finally backfill the size of the whole |batch| sub-message.
246   batch->Finalize();
247 }
248 
MaybeSerializeError(protos::pbzero::QueryResult * res)249 void QueryResultSerializer::MaybeSerializeError(
250     protos::pbzero::QueryResult* res) {
251   if (iter_->Status().ok())
252     return;
253   std::string err = iter_->Status().message();
254   // Make sure the |error| field is always non-zero if the query failed, so
255   // the client can tell some error happened.
256   if (err.empty())
257     err = "Unknown error";
258   res->set_error(err);
259 }
260 
SerializeColumnNames(protos::pbzero::QueryResult * res)261 void QueryResultSerializer::SerializeColumnNames(
262     protos::pbzero::QueryResult* res) {
263   PERFETTO_DCHECK(!did_write_column_names_);
264   for (uint32_t c = 0; c < num_cols_; c++)
265     res->add_column_names(iter_->GetColumnName(c));
266 }
267 
268 }  // namespace trace_processor
269 }  // namespace perfetto
270