blob: 43c21d878f37a0a44f6fb0642586fc0bed57277a [file] [log] [blame]
// Copyright 2017 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "garnet/bin/trace/spec.h"
#include <memory>
#include <rapidjson/document.h>
#include <rapidjson/error/en.h>
#include <rapidjson/schema.h>
#include <rapidjson/stringbuffer.h>
#include "lib/fxl/logging.h"
namespace tracing {
namespace {
// Top-level schema.
const char kRootSchema[] = R"({
"type": "object",
"additionalProperties": false,
"properties": {
"test_name": {
"type": "string"
},
"app": {
"type": "string"
},
"args": {
"type": "array",
"items": {
"type": "string"
}
},
"spawn": {
"type": "boolean"
},
"categories": {
"type": "array",
"items": {
"type": "string"
}
},
"buffering_mode": {
"type": "string"
},
"buffer_size_in_mb": {
"type": "integer",
"minimum": 1
},
"duration": {
"type": "integer",
"minimum": 0
},
"measure": {
"type": "array",
"items": {
"type": "object",
"properties": {
"type": {
"type": "string"
},
"output_test_name": {
"type": "string"
},
"split_first": {
"type": "boolean"
},
"expected_sample_count": {
"type": "integer",
"minimum": 1
},
"required": ["type"]
}
}
},
"test_suite_name": {
"type": "string"
}
}
})";
const char kTestNameKey[] = "test_name";
const char kAppKey[] = "app";
const char kArgsKey[] = "args";
const char kSpawnKey[] = "spawn";
const char kDurationKey[] = "duration";
const char kCategoriesKey[] = "categories";
const char kBufferingModeKey[] = "buffering_mode";
const char kBufferSizeInMbKey[] = "buffer_size_in_mb";
const char kMeasurementsKey[] = "measure";
const char kTypeKey[] = "type";
const char kOutputTestName[] = "output_test_name";
const char kSplitFirstKey[] = "split_first";
const char kExpectedSampleCountKey[] = "expected_sample_count";
const char kTestSuiteNameKey[] = "test_suite_name";
const char kMeasureDurationType[] = "duration";
const char kMeasureArgumentValueType[] = "argument_value";
const char kMeasureTimeBetweenType[] = "time_between";
// Schema for "duration" measurements.
const char kDurationSchema[] = R"({
"type": "object",
"properties": {
"event_category": {
"type": "string"
},
"event_name": {
"type": "string"
}
},
"required": ["event_category", "event_name"]
})";
const char kEventCategoryKey[] = "event_category";
const char kEventNameKey[] = "event_name";
// Schema for "time between" measurements.
const char kTimeBetweenSchema[] = R"({
"type": "object",
"properties": {
"first_event_name": {
"type": "string"
},
"first_event_category": {
"type": "string"
},
"first_event_anchor": {
"type": "string"
},
"second_event_name": {
"type": "string"
},
"second_event_category": {
"type": "string"
},
"second_event_anchor": {
"type": "string"
}
},
"required": [
"first_event_name", "first_event_category", "second_event_name",
"second_event_category"
]
})";
const char kFirstEventNameKey[] = "first_event_name";
const char kFirstEventCategoryKey[] = "first_event_category";
const char kFirstEventAnchorKey[] = "first_event_anchor";
const char kSecondEventNameKey[] = "second_event_name";
const char kSecondEventCategoryKey[] = "second_event_category";
const char kSecondEventAnchorKey[] = "second_event_anchor";
const char kAnchorBegin[] = "begin";
const char kAnchorEnd[] = "end";
// Schema for "argument value" measurements.
const char kArgumentValueSchema[] = R"({
"type": "object",
"properties": {
"event_category": {
"type": "string"
},
"event_name": {
"type": "string"
},
"argument_name": {
"type": "string"
},
"argument_unit": {
"type": "string"
}
},
"required": ["event_category", "event_name", "argument_name", "argument_unit"]
})";
const char kArgumentNameKey[] = "argument_name";
const char kArgumentUnitKey[] = "argument_unit";
bool DecodeMeasureDuration(const rapidjson::Value& value,
measure::DurationSpec* result) {
result->event.name = value[kEventNameKey].GetString();
result->event.category = value[kEventCategoryKey].GetString();
return true;
}
bool DecodeMeasureArgumentValue(const rapidjson::Value& value,
measure::ArgumentValueSpec* result) {
result->event.name = value[kEventNameKey].GetString();
result->event.category = value[kEventCategoryKey].GetString();
result->argument_name = value[kArgumentNameKey].GetString();
result->argument_unit = value[kArgumentUnitKey].GetString();
return true;
}
bool DecodeAnchor(std::string anchor_str, const char* key,
measure::Anchor* result) {
if (anchor_str == kAnchorBegin) {
*result = measure::Anchor::Begin;
} else if (anchor_str == kAnchorEnd) {
*result = measure::Anchor::End;
} else {
FXL_LOG(ERROR) << "Incorrect value of " << key;
return false;
}
return true;
}
bool DecodeMeasureTimeBetween(const rapidjson::Value& value,
measure::TimeBetweenSpec* result) {
result->first_event.name = value[kFirstEventNameKey].GetString();
result->first_event.category = value[kFirstEventCategoryKey].GetString();
if (value.HasMember(kFirstEventAnchorKey)) {
if (!DecodeAnchor(value[kFirstEventAnchorKey].GetString(),
kFirstEventAnchorKey, &result->first_anchor)) {
return false;
}
}
result->second_event.name = value[kSecondEventNameKey].GetString();
result->second_event.category = value[kSecondEventCategoryKey].GetString();
if (value.HasMember(kSecondEventAnchorKey)) {
if (!DecodeAnchor(value[kSecondEventAnchorKey].GetString(),
kSecondEventAnchorKey, &result->second_anchor)) {
return false;
}
}
return true;
}
std::unique_ptr<rapidjson::SchemaDocument> InitSchema(const char schemaSpec[]) {
rapidjson::Document schema_document;
if (schema_document.Parse(schemaSpec).HasParseError()) {
FXL_DCHECK(false) << "Schema validation spec itself is not valid JSON.";
return nullptr;
}
return std::make_unique<rapidjson::SchemaDocument>(schema_document);
}
bool ValidateSchema(const rapidjson::Value& value,
const rapidjson::SchemaDocument& schema) {
rapidjson::SchemaValidator validator(schema);
if (!value.Accept(validator)) {
rapidjson::StringBuffer uri_buffer;
validator.GetInvalidSchemaPointer().StringifyUriFragment(uri_buffer);
FXL_LOG(ERROR) << "Incorrect schema of tracing spec at "
<< uri_buffer.GetString() << " , schema violation: "
<< validator.GetInvalidSchemaKeyword();
return false;
}
return true;
}
} // namespace
bool DecodeSpec(const std::string& json, Spec* spec) {
// Initialize schemas for JSON validation.
auto root_schema = InitSchema(kRootSchema);
auto duration_schema = InitSchema(kDurationSchema);
auto time_between_schema = InitSchema(kTimeBetweenSchema);
auto argument_value_schema = InitSchema(kArgumentValueSchema);
if (!root_schema || !duration_schema || !time_between_schema ||
!argument_value_schema) {
return false;
}
Spec result;
rapidjson::Document document;
document.Parse<rapidjson::kParseCommentsFlag>(json.c_str(), json.size());
if (document.HasParseError()) {
auto offset = document.GetErrorOffset();
auto code = document.GetParseError();
FXL_LOG(ERROR) << "Couldn't parse the tracing spec file: offset " << offset
<< ", " << GetParseError_En(code);
return false;
}
if (!ValidateSchema(document, *root_schema)) {
return false;
}
if (document.HasMember(kTestNameKey)) {
result.test_name =
std::make_unique<std::string>(document[kTestNameKey].GetString());
}
if (document.HasMember(kAppKey)) {
result.app = std::make_unique<std::string>(document[kAppKey].GetString());
}
if (document.HasMember(kArgsKey)) {
result.args = std::make_unique<std::vector<std::string>>();
for (auto& arg_value : document[kArgsKey].GetArray()) {
result.args->push_back(arg_value.GetString());
}
}
if (document.HasMember(kSpawnKey)) {
result.spawn = std::make_unique<bool>(document[kSpawnKey].GetBool());
}
if (document.HasMember(kCategoriesKey)) {
result.categories = std::make_unique<std::vector<std::string>>();
for (auto& arg_value : document[kCategoriesKey].GetArray()) {
result.categories->push_back(arg_value.GetString());
}
}
if (document.HasMember(kBufferingModeKey)) {
result.buffering_mode =
std::make_unique<std::string>(document[kBufferingModeKey].GetString());
}
if (document.HasMember(kBufferSizeInMbKey)) {
result.buffer_size_in_mb =
std::make_unique<size_t>(document[kBufferSizeInMbKey].GetUint());
}
if (document.HasMember(kDurationKey)) {
result.duration = std::make_unique<fxl::TimeDelta>(
fxl::TimeDelta::FromSeconds(document[kDurationKey].GetUint()));
}
if (document.HasMember(kTestSuiteNameKey)) {
result.test_suite_name =
std::make_unique<std::string>(document[kTestSuiteNameKey].GetString());
}
if (!document.HasMember(kMeasurementsKey)) {
*spec = std::move(result);
return true;
}
result.measurements = std::make_unique<measure::Measurements>();
// Used to assign a unique id to each measurement, in the order they were
// defined.
uint64_t counter = 0u;
for (auto& measurement : document[kMeasurementsKey].GetArray()) {
std::string type = measurement[kTypeKey].GetString();
measure::MeasurementSpecCommon common;
common.id = counter;
if (measurement.HasMember(kOutputTestName)) {
common.output_test_name = measurement[kOutputTestName].GetString();
}
if (measurement.HasMember(kSplitFirstKey)) {
common.split_first = measurement[kSplitFirstKey].GetBool();
}
if (measurement.HasMember(kExpectedSampleCountKey)) {
common.expected_sample_count =
measurement[kExpectedSampleCountKey].GetUint();
}
if (type == kMeasureDurationType) {
measure::DurationSpec spec;
spec.common = std::move(common);
if (!ValidateSchema(measurement, *duration_schema) ||
!DecodeMeasureDuration(measurement, &spec)) {
return false;
}
result.measurements->duration.push_back(std::move(spec));
} else if (type == kMeasureTimeBetweenType) {
measure::TimeBetweenSpec spec;
spec.common = std::move(common);
if (!ValidateSchema(measurement, *time_between_schema) ||
!DecodeMeasureTimeBetween(measurement, &spec)) {
return false;
}
result.measurements->time_between.push_back(std::move(spec));
} else if (type == kMeasureArgumentValueType) {
measure::ArgumentValueSpec spec;
spec.common = std::move(common);
if (!ValidateSchema(measurement, *argument_value_schema) ||
!DecodeMeasureArgumentValue(measurement, &spec)) {
return false;
}
result.measurements->argument_value.push_back(std::move(spec));
} else {
FXL_LOG(ERROR) << "Unrecognized measurement type: " << type;
return false;
}
counter++;
}
*spec = std::move(result);
return true;
}
bool GetBufferingMode(const std::string& buffering_mode_name,
BufferingMode* out_mode) {
if (buffering_mode_name == "oneshot") {
*out_mode = BufferingMode::kOneshot;
} else if (buffering_mode_name == "circular") {
*out_mode = BufferingMode::kCircular;
} else if (buffering_mode_name == "streaming") {
*out_mode = BufferingMode::kStreaming;
} else {
return false;
}
return true;
}
} // namespace tracing