xref: /aosp_15_r20/external/federated-compute/fcp/tracing/tools/tracing_traits_generator.cc (revision 14675a029014e728ec732f129a32e299b2da0601)
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