blob: 700357feb7ac385814f4e59c4fca224179dd8d52 [file] [log] [blame]
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <vector>
#include <google/protobuf/descriptor.h>
#include <google/protobuf/descriptor.pb.h>
#include <google/protobuf/message.h>
#include "perfetto/base/logging.h"
#include "perfetto/ext/base/string_utils.h"
#include "src/trace_processor/util/proto_to_json.h"
namespace perfetto {
namespace trace_processor {
namespace proto_to_json {
namespace {
std::string QuoteAndEscapeJsonString(const std::string& raw) {
std::string ret;
for (auto it = raw.cbegin(); it != raw.cend(); it++) {
char c = *it;
switch (c) {
case '"':
// Double quote needs to be escaped.
ret += "\\\"";
break;
case '\n':
// Escape new line specially because it appears often and so is worth
// treating specially.
ret += "\\n";
break;
default:
if (c < 0x20) {
// All 32 ASCII control codes need to be escaped. Instead of using the
// short forms, we just always use \u escape sequences instead to make
// things simpler.
ret += "\\u00";
// Print |c| as a hex character. We reserve 3 bytes of space: 2 for
// the hex code and one for the null terminator.
base::StackString<3> buf("%02X", c);
ret += buf.c_str();
} else {
// Everything else can be passed through directly.
ret += c;
}
break;
}
}
return '"' + ret + '"';
}
std::string FieldToJson(const google::protobuf::Message& message,
const google::protobuf::FieldDescriptor* field_desc,
int idx,
uint32_t indent) {
using google::protobuf::FieldDescriptor;
const google::protobuf::Reflection* ref = message.GetReflection();
bool is_repeated = field_desc->is_repeated();
switch (field_desc->cpp_type()) {
case FieldDescriptor::CppType::CPPTYPE_BOOL:
return std::to_string(is_repeated
? ref->GetRepeatedBool(message, field_desc, idx)
: ref->GetBool(message, field_desc));
case FieldDescriptor::CppType::CPPTYPE_ENUM:
return QuoteAndEscapeJsonString(
is_repeated ? ref->GetRepeatedEnum(message, field_desc, idx)->name()
: ref->GetEnum(message, field_desc)->name());
case FieldDescriptor::CppType::CPPTYPE_FLOAT:
return std::to_string(
is_repeated
? static_cast<double>(
ref->GetRepeatedFloat(message, field_desc, idx))
: static_cast<double>(ref->GetFloat(message, field_desc)));
case FieldDescriptor::CppType::CPPTYPE_INT32:
return std::to_string(
is_repeated ? ref->GetRepeatedInt32(message, field_desc, idx)
: ref->GetInt32(message, field_desc));
case FieldDescriptor::CppType::CPPTYPE_INT64:
return std::to_string(
is_repeated ? ref->GetRepeatedInt64(message, field_desc, idx)
: ref->GetInt64(message, field_desc));
case FieldDescriptor::CppType::CPPTYPE_DOUBLE:
return std::to_string(
is_repeated ? ref->GetRepeatedDouble(message, field_desc, idx)
: ref->GetDouble(message, field_desc));
case FieldDescriptor::CppType::CPPTYPE_STRING:
return QuoteAndEscapeJsonString(
is_repeated ? ref->GetRepeatedString(message, field_desc, idx)
: ref->GetString(message, field_desc));
case FieldDescriptor::CppType::CPPTYPE_UINT32:
return std::to_string(
is_repeated ? ref->GetRepeatedUInt32(message, field_desc, idx)
: ref->GetUInt32(message, field_desc));
case FieldDescriptor::CppType::CPPTYPE_UINT64:
return std::to_string(
is_repeated ? ref->GetRepeatedInt64(message, field_desc, idx)
: ref->GetInt64(message, field_desc));
case FieldDescriptor::CppType::CPPTYPE_MESSAGE:
return MessageToJson(
is_repeated ? ref->GetRepeatedMessage(message, field_desc, idx)
: ref->GetMessage(message, field_desc),
indent);
}
PERFETTO_FATAL("For GCC");
}
std::string RepeatedFieldValuesToJson(
const google::protobuf::Message& message,
const google::protobuf::FieldDescriptor* field_desc,
uint32_t indent) {
const google::protobuf::Reflection* ref = message.GetReflection();
std::string ret;
for (int i = 0; i < ref->FieldSize(message, field_desc); ++i) {
if (i != 0) {
ret += ",";
}
ret += "\n" + std::string(indent, ' ') +
FieldToJson(message, field_desc, i, indent);
}
return ret;
}
std::string MessageFieldsToJson(const google::protobuf::Message& message,
uint32_t indent) {
const google::protobuf::Reflection* ref = message.GetReflection();
std::vector<const google::protobuf::FieldDescriptor*> field_descs;
ref->ListFields(message, &field_descs);
std::string ret;
uint32_t next_field_idx = 0;
for (const google::protobuf::FieldDescriptor* field_desc : field_descs) {
if (next_field_idx++ != 0) {
ret += ",";
}
std::string value;
if (field_desc->is_repeated()) {
value = "[" + RepeatedFieldValuesToJson(message, field_desc, indent + 2) +
"\n" + std::string(indent, ' ') + "]";
} else {
value = FieldToJson(message, field_desc, 0, indent);
}
const std::string& name = field_desc->is_extension()
? field_desc->full_name()
: field_desc->name();
ret += "\n" + std::string(indent, ' ') + "\"" + name + "\": " + value;
}
return ret;
}
// This is a class helps avoid the situation where every function has to take
// field_options_prototype as an argument, which becomes distracting.
class OptionsConverter {
public:
explicit OptionsConverter(
const google::protobuf::Message* field_options_prototype)
: field_options_prototype_(field_options_prototype) {}
// Prints all field options for non-empty fields of a message. Example:
// --- Message definitions ---
// FooMessage {
// repeated int64 foo = 1 [op1 = val1, op2 = val2];
// optional BarMessage bar = 2 [op3 = val3];
// }
//
// BarMessage {
// optional int64 baz = 1 [op4 = val4];
// }
// --- MessageInstance ---
// foo_msg = { // (As JSON)
// foo: [23, 24, 25],
// bar: {
// baz: 42
// }
// }
// --- Output of MessageFieldOptionsToJson(foo_msg) ---
// foo: {
// __field_options: {
// op1: val1,
// op2: val2,
// },
// __repeated: true
// }
// bar: {
// __field_options: {
// op3 = val3,
// },
// baz: {
// __field_options: {
// op4 = val4
// },
// }
// }
// --- Notes ---
// This function does not produce the surrounding braces for easier use in
// recursive use cases. The caller needs to surround the output with braces.
std::string MessageFieldOptionsToJson(
const google::protobuf::Message& message,
uint32_t indent) {
using google::protobuf::FieldDescriptor;
std::vector<const FieldDescriptor*> field_descs;
message.GetReflection()->ListFields(message, &field_descs);
std::vector<std::string> field_outputs;
for (auto* field_desc : field_descs) {
std::vector<std::string> field_entries;
if (HasFieldOptions(field_desc)) {
std::string options_entry;
options_entry +=
std::string(indent + 2, ' ') + R"("__field_options": )";
options_entry += FieldOptionsToJson(field_desc, indent + 4);
field_entries.push_back(std::move(options_entry));
}
std::string nested_fields =
NestedMessageFieldOptionsToJson(message, field_desc, indent + 2);
if (!nested_fields.empty()) {
field_entries.push_back(std::move(nested_fields));
}
// We don't output annotations for a field if that field and all its
// descendants have no field options.
if (!field_entries.empty()) {
if (field_desc->is_repeated()) {
field_entries.push_back(std::string(indent, ' ') +
R"("__repeated": true)");
}
std::string field_output;
const std::string& name = field_desc->is_extension()
? field_desc->full_name()
: field_desc->name();
field_output += std::string(indent, ' ') + "\"" + name + "\": {\n";
field_output += base::Join(field_entries, ",\n") + "\n";
field_output += std::string(indent, ' ') + "}";
field_outputs.push_back(std::move(field_output));
}
}
return base::Join(field_outputs, ",\n");
}
private:
static bool HasFieldOptions(
const google::protobuf::FieldDescriptor* field_desc) {
return field_desc->options().ByteSizeLong() > 0;
}
std::string NestedMessageFieldOptionsToJson(
const google::protobuf::Message& message,
const google::protobuf::FieldDescriptor* field_desc,
uint32_t indent) {
using google::protobuf::FieldDescriptor;
if (field_desc->cpp_type() != FieldDescriptor::CppType::CPPTYPE_MESSAGE)
return "";
const auto* reflection = message.GetReflection();
const google::protobuf::Message& nested_message =
field_desc->is_repeated()
? reflection->GetRepeatedMessage(message, field_desc, 0)
: reflection->GetMessage(message, field_desc);
return MessageFieldOptionsToJson(nested_message, indent);
}
std::string FieldOptionsToJson(
const google::protobuf::FieldDescriptor* field_desc,
uint32_t indent) {
PERFETTO_DCHECK(HasFieldOptions(field_desc));
std::unique_ptr<google::protobuf::Message> options(
field_options_prototype_->New());
// Field option extensions are compiled at runtime as opposed to being
// compiled in and being part of the generated pool, so the field option
// must be re-parsed as a dynamic message for the extensions to show up. If
// we do not do this, the extension fields remain "unknown fields" to the
// reflection API.
options->ParseFromString(field_desc->options().SerializeAsString());
return MessageToJson(*options, indent);
}
const google::protobuf::Message* field_options_prototype_;
};
} // namespace
std::string MessageToJson(const google::protobuf::Message& message,
uint32_t indent) {
return "{" + MessageFieldsToJson(message, indent + 2) + '\n' +
std::string(indent, ' ') + "}";
}
std::string MessageToJsonWithAnnotations(
const google::protobuf::Message& message,
const google::protobuf::Message* field_options_prototype,
uint32_t indent) {
std::string ret;
OptionsConverter options_converter(field_options_prototype);
ret = "{" + MessageFieldsToJson(message, indent + 2);
std::string annotation_fields =
options_converter.MessageFieldOptionsToJson(message, indent + 4);
if (annotation_fields.empty()) {
ret += "\n";
} else {
ret += ",\n";
ret += std::string(indent + 2, ' ') + "\"__annotations\": {\n";
ret += annotation_fields + "\n";
ret += std::string(indent + 2, ' ') + "}\n";
}
ret += std::string(indent, ' ') + "}\n";
return ret;
}
} // namespace proto_to_json
} // namespace trace_processor
} // namespace perfetto