Gems/AWSMetrics/Code/Source/MetricsEvent.cpp (162 lines of code) (raw):
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <MetricsEvent.h>
#include <AWSMetricsConstant.h>
#include <Framework/JsonWriter.h>
#include <AzCore/JSON/schema.h>
#include <AzCore/Serialization/Json/JsonSerialization.h>
#include <AzCore/Serialization/Json/JsonUtils.h>
#include <sstream>
namespace AWSMetrics
{
void MetricsEvent::AddAttribute(const MetricsAttribute& attribute)
{
AZStd::string attributeName = attribute.GetName();
if (attributeName.empty())
{
AZ_Error("AWSMetrics", false, "Invalid metrics attribute. Attribute name is empty.");
return;
}
if (AttributeExists(attributeName))
{
// Avoid overwriting the existing attribute value since it's not clear which one developers need to keep.
AZ_Error("AWSMetrics", false, "Metrics attribute %s already exists.", attributeName.c_str());
return;
}
m_sizeSerializedToJson += attribute.GetSizeInBytes();
m_attributes.emplace_back(AZStd::move(attribute));
}
bool MetricsEvent::AttributeExists(const AZStd::string& attributeName) const
{
auto itr = AZStd::find_if(m_attributes.begin(), m_attributes.end(),
[attributeName](const MetricsAttribute& existingAttribute) -> bool
{
return (attributeName == existingAttribute.GetName());
});
return itr != m_attributes.end();
}
void MetricsEvent::AddAttributes(const AZStd::vector<MetricsAttribute>& attributes)
{
for (const MetricsAttribute& attribute : attributes)
{
AddAttribute(attribute);
}
}
int MetricsEvent::GetNumAttributes() const
{
return static_cast<int>(m_attributes.size());
}
size_t MetricsEvent::GetSizeInBytes() const
{
return m_sizeSerializedToJson;
}
bool MetricsEvent::SerializeToJson(AWSCore::JsonWriter& writer) const
{
bool ok = true;
ok = ok && writer.StartObject();
AZStd::vector<MetricsAttribute> customAttributes;
for (const auto& attr : m_attributes)
{
if (attr.IsDefault())
{
ok = ok && writer.Key(attr.GetName().c_str());
ok = ok && attr.SerializeToJson(writer);
}
else
{
customAttributes.emplace_back(attr);
}
}
if (customAttributes.size() > 0)
{
// Wrap up the cutom event attributes in a separate event_data field
ok = ok && writer.Key(AwsMetricsAttributeKeyEventData);
ok = ok && writer.StartObject();
for (const auto& attr : customAttributes)
{
ok = ok && writer.Key(attr.GetName().c_str());
ok = ok && attr.SerializeToJson(writer);
}
ok = ok && writer.EndObject();
}
ok = ok && writer.EndObject();
return ok;
}
bool MetricsEvent::ReadFromJson(rapidjson::Value& metricsObjVal)
{
if (!metricsObjVal.IsObject())
{
AZ_Error("AWSMetrics", false, "Invalid JSON value type. Expect an object");
return false;
}
int attributeIndex = 0;
for (auto it = metricsObjVal.MemberBegin(); it != metricsObjVal.MemberEnd(); ++it, ++attributeIndex)
{
if (strcmp(it->name.GetString(), AwsMetricsAttributeKeyEventData) == 0)
{
// The event_data field contains a flat json dictionary.
// Read the JSON value of this field to add all the custom metrics attributes.
if (!ReadFromJson(it->value))
{
return false;
}
}
else
{
MetricsAttribute attribute;
// Read through each element in the array and add it as a new metrics attribute
if (!attribute.ReadFromJson(it->name, it->value))
{
AZ_Error("AWSMetrics", false, "Metrics attribute %s is invalid", it->name.GetString());
return false;
}
AddAttribute(attribute);
}
}
return true;
}
bool MetricsEvent::ValidateAgainstSchema()
{
std::stringstream stringStream;
AWSCore::JsonOutputStream jsonStream{stringStream};
AWSCore::JsonWriter writer{jsonStream};
if (!SerializeToJson(writer))
{
return false;
}
auto result = AZ::JsonSerializationUtils::ReadJsonString(stringStream.str().c_str());
if (!result.IsSuccess())
{
return false;
}
rapidjson::Document jsonSchemaDocument;
if (jsonSchemaDocument.Parse(AwsMetricsEventJsonSchema).HasParseError())
{
AZ_Error("AWSMetrics", false, "Invalid metrics event json schema.");
return false;
}
auto jsonSchema = rapidjson::SchemaDocument(jsonSchemaDocument);
rapidjson::SchemaValidator validator(jsonSchema);
if (!result.GetValue().Accept(validator))
{
rapidjson::StringBuffer error;
validator.GetInvalidSchemaPointer().StringifyUriFragment(error);
AZ_Warning("AWSMetrics", false, "Failed to load the metrics event, invalid schema: %s.", error.GetString());
AZ_Warning("AWSMetrics", false, "Failed to load the metrics event, invalid keyword: %s.", validator.GetInvalidSchemaKeyword());
error.Clear();
validator.GetInvalidDocumentPointer().StringifyUriFragment(error);
AZ_Warning("AWSMetrics", false, "Failed to load the metrics event, invalid document: %s.", error.GetString());
return false;
}
return true;
}
void MetricsEvent::MarkFailedSubmission()
{
++m_numFailures;
}
int MetricsEvent::GetNumFailures() const
{
return m_numFailures;
}
void MetricsEvent::SetEventPriority(int priority)
{
m_eventPriority = priority;
}
int MetricsEvent::GetEventPriority() const
{
return m_eventPriority;
}
}