1 // Copyright 2019 Google LLC
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 #include <algorithm>
16 #include <cassert>
17 #include <cstddef>
18 #include <iostream>
19 #include <string>
20 #include <unordered_map>
21 #include <unordered_set>
22 #include <vector>
23
24 #include "absl/container/flat_hash_map.h"
25 #include "absl/container/flat_hash_set.h"
26 #include "absl/strings/str_format.h"
27 #include "absl/strings/str_replace.h"
28 #include "absl/strings/string_view.h"
29 #include "fcp/tracing/tracing_severity.h"
30 #include "flatbuffers/flatbuffers.h"
31 #include "flatbuffers/idl.h"
32 #include "flatbuffers/reflection_generated.h"
33 #include "flatbuffers/util.h"
34
35 using ::reflection::BaseType;
36
37 namespace fcp {
38
39 struct TypeInfo {
40 BaseType flatbuf_type;
41 std::string cpp_type;
42 };
43
severity_string(const TracingSeverity tracing_severity)44 static std::string severity_string(const TracingSeverity tracing_severity) {
45 switch (tracing_severity) {
46 case TracingSeverity::kInfo:
47 return "fcp::TracingSeverity::kInfo";
48 case TracingSeverity::kWarning:
49 return "fcp::TracingSeverity::kWarning";
50 case TracingSeverity::kError:
51 return "fcp::TracingSeverity::kError";
52 }
53 }
54
gen_header_guard(absl::string_view output_filename)55 static std::string gen_header_guard(absl::string_view output_filename) {
56 std::string header_guard = absl::StrReplaceAll(
57 output_filename, {{"_generated", ""}, {"/", "_"}, {".", "_"}});
58 std::transform(header_guard.begin(), header_guard.end(), header_guard.begin(),
59 [](unsigned char c) { return std::toupper(c); });
60 return header_guard;
61 }
62
gen_fbs_filename(absl::string_view output_filename)63 static std::string gen_fbs_filename(absl::string_view output_filename) {
64 return absl::StrReplaceAll(output_filename, {{".h", ".fbs"}});
65 }
66
gen_table_name(absl::string_view fully_qualified_table_name)67 static absl::string_view gen_table_name(
68 absl::string_view fully_qualified_table_name) {
69 auto pos = fully_qualified_table_name.find_last_of(':');
70 if (pos != std::string::npos) {
71 return absl::ClippedSubstr(fully_qualified_table_name, pos + 1);
72 }
73 return fully_qualified_table_name;
74 }
75
gen_table_namespace(absl::string_view fully_qualified_table_name)76 static absl::string_view gen_table_namespace(
77 absl::string_view fully_qualified_table_name) {
78 auto pos = fully_qualified_table_name.find_last_of(':');
79 if (pos != std::string::npos) {
80 return absl::ClippedSubstr(fully_qualified_table_name, 0, pos + 1);
81 }
82 return "";
83 }
84 } // namespace fcp
85
86 // For codegen examples, see fcp/tracing/tools/testdata.
main(int argc,const char ** argv)87 int main(int argc, const char** argv) {
88 if (argc != 4) {
89 std::cerr << "Usage: tracing_traits_generator "
90 "<runtime/path/to/tracing_schema_generated.h> "
91 "<full/path/to/tracing_schema.bfbs>"
92 "<full/path/to/tracing_schema.fbs>"
93 << std::endl;
94 return 1;
95 }
96 const char* generated_filename = argv[1];
97 const char* bfbs_filename = argv[2];
98 const char* fbs_filename = argv[3];
99
100 // Loading binary schema file
101 std::string bfbs_file;
102 if (!flatbuffers::LoadFile(bfbs_filename, true, &bfbs_file)) {
103 std::cerr << "Error loading FlatBuffers binary schema (bfbs) file"
104 << std::endl;
105 return 2;
106 }
107
108 // Verify it, just in case:
109 flatbuffers::Verifier verifier(
110 reinterpret_cast<const uint8_t*>(bfbs_file.c_str()), bfbs_file.length());
111 if (!reflection::VerifySchemaBuffer(verifier)) {
112 std::cerr << "Error loading bfbs file" << std::endl;
113 return 3;
114 }
115
116 std::cout << "// Autogenerated by tracing_traits_generator, do not edit"
117 << std::endl;
118 std::cout << std::endl;
119
120 std::string output_filename =
121 absl::StrReplaceAll(generated_filename, {{"_generated", ""}});
122 std::string header_guard = fcp::gen_header_guard(output_filename);
123 std::cout << "#ifndef " << header_guard << std::endl;
124 std::cout << "#define " << header_guard << std::endl;
125 std::cout << std::endl;
126
127 // Workaround for inability of flatc to generate unique (path-dependent)
128 // include guards. Undefining the include guard below allows
129 // to include. Since all the flatc-generated schema files are wrapped
130 // by the guards above, it still remains protected against multiple includes.
131 std::cout << "#ifdef FLATBUFFERS_GENERATED_TRACINGSCHEMA_H_" << std::endl;
132 std::cout << "#undef FLATBUFFERS_GENERATED_TRACINGSCHEMA_H_" << std::endl;
133 std::cout << "#endif" << std::endl;
134
135 std::cout << "#include \"" << generated_filename << "\"" << std::endl;
136 std::cout << "#include \"absl/strings/string_view.h\""
137 << std::endl;
138 std::cout << "#include \"fcp/tracing/tracing_severity.h\""
139 << std::endl;
140 std::cout << "#include \"fcp/tracing/tracing_traits.h\""
141 << std::endl;
142 std::cout << "#include "
143 "\"flatbuffers/minireflect.h\""
144 << std::endl;
145 std::cout << "#include "
146 "\"flatbuffers/idl.h\""
147 << std::endl;
148 std::cout << "#include "
149 "\"fcp/base/platform.h\""
150 << std::endl;
151 std::cout << std::endl;
152
153 // Reflecting over schema and enumerating tables
154 auto& schema = *reflection::GetSchema(bfbs_file.c_str());
155 std::cout << "namespace fcp {" << std::endl;
156 std::cout << std::endl;
157
158 absl::flat_hash_map<BaseType, std::string> type_map = {
159 {BaseType::String, "absl::string_view"},
160 {BaseType::Byte, "std::int8_t"},
161 {BaseType::UByte, "std::uint8_t"},
162 {BaseType::Bool, "bool"},
163 {BaseType::Short, "std::int16_t"},
164 {BaseType::UShort, "std::uint16_t"},
165 {BaseType::Int, "std::int32_t"},
166 {BaseType::UInt, "std::uint32_t"},
167 {BaseType::Float, "float"},
168 {BaseType::Long, "std::int64_t"},
169 {BaseType::ULong, "std::uint64_t"},
170 {BaseType::Double, "double"}};
171 absl::flat_hash_set<std::string> tags;
172 for (const reflection::Object* const o : *schema.objects()) {
173 if (o->is_struct()) continue;
174 std::string fully_qualified_table_name =
175 absl::StrReplaceAll(o->name()->c_str(), {{".", "::"}});
176 absl::string_view table_name =
177 fcp::gen_table_name(fully_qualified_table_name);
178 absl::string_view table_namespace =
179 fcp::gen_table_namespace(fully_qualified_table_name);
180
181 // The fields are sorted in alphabetical order, rather than the order in
182 // which they should be passed to the Create method. Sort them by ID which
183 // determines the order in which the generated Create method accepts them.
184 // ID will be the order in which fields are declared in the table if it is
185 // not explicitly specified for each field.
186 std::vector<const reflection::Field*> fields_sorted;
187 fields_sorted.resize(o->fields()->size());
188 for (const reflection::Field* const f : *o->fields()) {
189 // FlatBuffers field IDs are guaranteed to be dense:
190 assert(f->id() < o->fields()->size());
191 fields_sorted[f->id()] = f;
192 }
193
194 std::vector<std::pair<std::string, fcp::TypeInfo>> fields;
195 for (const reflection::Field* const f : fields_sorted) {
196 // Filter out deprecated fields since the Create method no longer takes
197 // them as parameters.
198 if (f->deprecated()) continue;
199 BaseType flatbuf_type = f->type()->base_type();
200 auto type_map_entry = type_map.find(flatbuf_type);
201 if (type_map_entry == type_map.end()) {
202 std::cerr
203 << absl::StreamFormat(
204 "ERROR: %s contains unsupported type %s for field %s in "
205 "table %s",
206 fcp::gen_fbs_filename(output_filename),
207 reflection::EnumNameBaseType(flatbuf_type),
208 f->name()->c_str(), fully_qualified_table_name)
209 << std::endl;
210 return 4;
211 }
212 if (f->type()->index() != -1) {
213 // If the index of the type is set, it means this is a more complex
214 // type, and we can learn more about the type by indexing into one of
215 // the toplevel fields in the schema - either "objects" or "enums".
216 // Since we do not currently support base_type of kind Union, UnionType,
217 // or Object, if the index is anything other than -1, this type must be
218 // an integer derived from an enum, and we can determine more
219 // information by indexing into "enums". See
220 // https://groups.google.com/g/flatbuffers/c/nAi8MQu3A-U.
221 const reflection::Enum* enum_type =
222 schema.enums()->Get(f->type()->index());
223 fields.emplace_back(
224 f->name()->c_str(),
225 fcp::TypeInfo{
226 flatbuf_type,
227 // Replace '.' with '::' in the fully qualified enum name for
228 // C++ compatibility.
229 absl::StrReplaceAll(enum_type->name()->str(), {{".", "::"}})});
230 } else {
231 fields.emplace_back(
232 f->name()->c_str(),
233 fcp::TypeInfo{flatbuf_type, type_map_entry->second});
234 }
235 }
236
237 std::cout << "template<> class TracingTraits<" << fully_qualified_table_name
238 << ">: public TracingTraitsBase {" << std::endl;
239 std::cout << " public:" << std::endl;
240
241 fcp::TracingSeverity severity = fcp::TracingSeverity::kInfo;
242 std::string tag = "";
243 bool is_span = false;
244 if (o->attributes() == nullptr) {
245 std::cerr
246 << absl::StreamFormat(
247 "ERROR: %s contains table %s without a tag. All tables must "
248 "have a tag defined.",
249 fcp::gen_fbs_filename(output_filename),
250 fully_qualified_table_name)
251 << std::endl;
252 return 5;
253 }
254 for (const reflection::KeyValue* a : *o->attributes()) {
255 if (a->key()->str() == "tag") {
256 tag = a->value()->str();
257 if (tag.size() != 4) {
258 std::cerr
259 << absl::StreamFormat(
260 "ERROR: %s contains table %s with tag %s of length %d. "
261 "All tables must have a tag of length 4.",
262 fcp::gen_fbs_filename(output_filename),
263 fully_qualified_table_name, tag, tag.size())
264 << std::endl;
265 return 6;
266 }
267 }
268 if (a->key()->str() == "warning") {
269 severity = fcp::TracingSeverity::kWarning;
270 }
271 if (a->key()->str() == "error") {
272 severity = fcp::TracingSeverity::kError;
273 }
274 if (a->key()->str() == "span") {
275 is_span = true;
276 }
277 }
278 if (tag.empty()) {
279 std::cerr
280 << absl::StreamFormat(
281 "ERROR: %s contains table %s without a tag. All tables must "
282 "have a tag defined.",
283 fcp::gen_fbs_filename(output_filename),
284 fully_qualified_table_name)
285 << std::endl;
286 return 7;
287 }
288
289 if (!tags.insert(tag).second) {
290 std::cerr
291 << absl::StreamFormat(
292 "ERROR: %s contains table %s with tag %s which is already "
293 "present in the schema. All tags must be unique.",
294 fcp::gen_fbs_filename(output_filename),
295 fully_qualified_table_name, tag)
296 << std::endl;
297 return 8;
298 }
299
300 std::cout << " static constexpr TracingTag kTag = TracingTag(\"" << tag
301 << "\");" << std::endl;
302
303 std::cout << " static constexpr TracingSeverity kSeverity = "
304 << fcp::severity_string(severity) << ";" << std::endl;
305
306 std::cout << " static constexpr bool kIsSpan = "
307 << (is_span ? "true" : "false") << ";" << std::endl;
308
309 std::cout << " const char* Name() const override { return \""
310 << fully_qualified_table_name << "\"; }" << std::endl;
311
312 std::cout << " TracingSeverity Severity() const override {" << std::endl;
313 std::cout << " return " << fcp::severity_string(severity) << ";"
314 << std::endl;
315 std::cout << " }" << std::endl;
316 std::cout
317 << " std::string TextFormat(const flatbuffers::DetachedBuffer& buf) "
318 "const override {"
319 << std::endl;
320 std::cout << " return flatbuffers::FlatBufferToString(buf.data(), "
321 << fully_qualified_table_name << "TypeTable());" << std::endl;
322 std::cout << " }" << std::endl;
323 std::cout << " std::string JsonStringFormat(const uint8_t* flatbuf_bytes) "
324 "const override {"
325 << std::endl;
326 std::cout << " flatbuffers::Parser parser;" << std::endl;
327 std::cout << " std::string schema_file;" << std::endl;
328 std::cout << " std::string fbs_file = \"" << fbs_filename << "\";"
329 << std::endl;
330 std::cout << " flatbuffers::LoadFile(GetDataPath(fbs_file).c_str(), "
331 "true, &schema_file);"
332 << std::endl;
333 // Finds the directory in which the flatbuf class should look for
334 // dependencies of the .fbs file
335 // TODO(team) pass in tracing_schema_common to the script instead of
336 // hardcoding it.
337 std::cout << " std::string schema_path_common = "
338 "GetDataPath(\"fcp/tracing/"
339 "tracing_schema_common.fbs\");"
340 << std::endl;
341 std::cout
342 << " std::string directory_common = schema_path_common.substr(0, "
343 "schema_path_common.find(\"fcp/tracing/"
344 "tracing_schema_common.fbs\"));"
345 << std::endl;
346 // Parser.parse() requires the directories passed in to have a nullptr
347 std::cout << " const char *include_directories[] = {" << std::endl;
348 std::cout << " directory_common.c_str(), nullptr};"
349 << std::endl;
350 // Parse takes in the schema file and populates the FlatBufferBuilder from
351 // the unique schema.
352 std::cout << " parser.Parse(schema_file.c_str(), include_directories);"
353 << std::endl;
354 std::cout << " std::string jsongen;" << std::endl;
355 // The root sets the particular table from the Flatbuffer, since flatbuffers
356 // can have different tables.
357 std::cout << " parser.SetRootType(\"" << fully_qualified_table_name
358 << "\");" << std::endl;
359 std::cout << " GenerateText(parser, flatbuf_bytes, &jsongen);"
360 << std::endl;
361 std::cout << " return jsongen;" << std::endl;
362 std::cout << " }" << std::endl;
363 std::cout << " static flatbuffers::Offset<" << fully_qualified_table_name;
364 std::cout << "> Create(";
365 for (const auto& [name, type] : fields) {
366 std::cout << type.cpp_type << " " << name << ", ";
367 }
368 std::cout << "flatbuffers::FlatBufferBuilder* fbb) {" << std::endl;
369
370 // Strings require special handling because the Create method takes an
371 // Offset<String>. Copy each provided string view into an Offset<String>.
372 for (const auto& [name, type] : fields) {
373 if (type.flatbuf_type == BaseType::String) {
374 std::cout << " auto " << name << "__ = fbb->CreateString(" << name
375 << ".data(), " << name << ".size()"
376 << ");" << std::endl;
377 }
378 }
379
380 std::cout << " return " << table_namespace << "Create" << table_name
381 << "(";
382 std::cout << "*fbb";
383 for (const auto& [name, type] : fields) {
384 const char* suffix = (type.flatbuf_type == BaseType::String) ? "__" : "";
385 std::cout << ", " << name << suffix;
386 }
387 std::cout << ");" << std::endl;
388 std::cout << " }" << std::endl;
389
390 // MakeTuple helper, which allows to generate std::tuple from a table.
391 std::string tuple_type = "std::tuple<";
392 std::string make_tuple_args;
393 for (auto [name, type] : fields) {
394 if (!make_tuple_args.empty()) {
395 tuple_type += ", ";
396 make_tuple_args += ", ";
397 }
398 make_tuple_args += "table->" + name + "()";
399 if (type.flatbuf_type == BaseType::String) {
400 tuple_type += "std::string";
401 make_tuple_args += "->str()";
402 } else {
403 tuple_type += std::string(type.cpp_type);
404 }
405 }
406 tuple_type += ">";
407
408 std::cout << " using TupleType = " << tuple_type << ";" << std::endl;
409 std::cout << " static TupleType MakeTuple(const "
410 << fully_qualified_table_name << "* table) {" << std::endl;
411 std::cout << " return std::make_tuple(" << make_tuple_args << ");"
412 << std::endl;
413 std::cout << " }" << std::endl;
414
415 std::cout << "};" << std::endl;
416 std::cout << "static internal::TracingTraitsRegistrar<"
417 << fully_qualified_table_name << "> registrar_" << table_name
418 << ";" << std::endl;
419 }
420 std::cout << "} // namespace fcp" << std::endl;
421 std::cout << std::endl;
422 std::cout << "#endif // " << header_guard << std::endl;
423 return 0;
424 }
425