From 46777b537e350ce4b115facebcf25533f81a0da4 Mon Sep 17 00:00:00 2001 From: AMZN-Olex <5432499+AMZN-Olex@users.noreply.github.com> Date: Mon, 26 Jul 2021 18:01:14 -0400 Subject: [PATCH 01/37] Brought over LY legacy imgui code Signed-off-by: AMZN-Olex <5432499+AMZN-Olex@users.noreply.github.com> --- .../Debug/MultiplayerDebugByteReporter.cpp | 222 ++++++++++ .../Debug/MultiplayerDebugByteReporter.h | 96 +++++ .../MultiplayerDebugPerEntityReporter.cpp | 385 ++++++++++++++++++ .../Debug/MultiplayerDebugPerEntityReporter.h | 90 ++++ .../Debug/MultiplayerDebugSystemComponent.cpp | 11 + .../Debug/MultiplayerDebugSystemComponent.h | 4 + .../Code/multiplayer_debug_files.cmake | 4 + 7 files changed, 812 insertions(+) create mode 100644 Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugByteReporter.cpp create mode 100644 Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugByteReporter.h create mode 100644 Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugPerEntityReporter.cpp create mode 100644 Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugPerEntityReporter.h diff --git a/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugByteReporter.cpp b/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugByteReporter.cpp new file mode 100644 index 0000000000..382bc70d9b --- /dev/null +++ b/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugByteReporter.cpp @@ -0,0 +1,222 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ +#include "MultiplayerDebugByteReporter.h" + +#include +#include +#include + +namespace MultiplayerDiagnostics +{ + void MultiplayerDebugByteReporter::ReportBytes(size_t byteSize) + { + m_count++; + m_totalBytes += byteSize; + m_totalBytesThisSecond += byteSize; + m_minBytes = AZStd::min(m_minBytes, byteSize); + m_maxBytes = AZStd::max(m_maxBytes, byteSize); + } + + void MultiplayerDebugByteReporter::AggregateBytes(size_t byteSize) + { + m_aggregateBytes += byteSize; + } + + void MultiplayerDebugByteReporter::ReportAggregateBytes() + { + ReportBytes(m_aggregateBytes); + m_aggregateBytes = 0; + } + + float MultiplayerDebugByteReporter::GetAverageBytes() const + { + if (m_count == 0) + { + AZ_Warning("MultiplayerDebugByteReporter", m_totalBytes == 0, "Attempted to average bytes with a zero count."); + return 0.0f; + } + + return (1.0f * m_totalBytes) / m_count; + } + + size_t MultiplayerDebugByteReporter::GetMaxBytes() const + { + return m_maxBytes; + } + + size_t MultiplayerDebugByteReporter::GetMinBytes() const + { + return m_minBytes; + } + + size_t MultiplayerDebugByteReporter::GetTotalBytes() const + { + return m_totalBytes; + } + + float MultiplayerDebugByteReporter::GetKbitsPerSecond() + { + auto now = AZStd::chrono::monotonic_clock::now(); + + // Check the amount of time elapsed and update totals if necessary. + // Time here is measured in whole seconds from the epoch, providing synchronization in + // reporting intervals across all byte reporters. + AZStd::chrono::seconds nowSeconds = AZStd::chrono::duration_cast(now.time_since_epoch()); + AZStd::chrono::seconds secondsSinceLastUpdate = nowSeconds - + AZStd::chrono::duration_cast(m_lastUpdateTime.time_since_epoch()); + if (secondsSinceLastUpdate.count()) + { + // normalize over elapsed milliseconds + const int k_millisecondsPerSecond = 1000; + auto msSinceLastUpdate = AZStd::chrono::duration_cast(now - m_lastUpdateTime); + m_totalBytesLastSecond = k_millisecondsPerSecond * (1.f * m_totalBytesThisSecond / msSinceLastUpdate.count()); + m_totalBytesThisSecond = 0; + m_lastUpdateTime = now; + } + + const float k_bitsPerByte = 8.0f; + const int k_bitsPerKilobit = 1024; + return k_bitsPerByte * m_totalBytesLastSecond / k_bitsPerKilobit; + } + + void MultiplayerDebugByteReporter::Combine(const MultiplayerDebugByteReporter& other) + { + m_count += other.m_count; + m_totalBytes += other.m_totalBytes; + m_totalBytesThisSecond += other.m_totalBytesThisSecond; + m_minBytes = AZStd::GetMin(m_minBytes, other.m_minBytes); + m_maxBytes = AZStd::GetMax(m_maxBytes, other.m_maxBytes); + } + + void MultiplayerDebugByteReporter::Reset() + { + m_count = 0; + m_totalBytes = 0; + m_totalBytesThisSecond = 0; + m_totalBytesLastSecond = 0; + m_minBytes = std::numeric_limits::max(); + m_maxBytes = 0; + m_aggregateBytes = 0; + } + + void ComponentReporter::ReportField(const char* fieldName, size_t byteSize) + { + MultiplayerDebugByteReporter::AggregateBytes(byteSize); + m_fieldReports[fieldName].ReportBytes(byteSize); + } + + void ComponentReporter::ReportFragmentEnd() + { + MultiplayerDebugByteReporter::ReportAggregateBytes(); + m_componentDirtyBytes.ReportAggregateBytes(); + } + + AZStd::vector ComponentReporter::GetFieldReports() + { + AZStd::vector copy; + for (auto field = m_fieldReports.begin(); field != m_fieldReports.end(); ++field) + { + copy.emplace_back(field->first, &field->second); + } + + auto sortByFrequency = [](const Report& a, const Report& b) + { + return a.second->GetTotalCount() > b.second->GetTotalCount(); + }; + + AZStd::sort(copy.begin(), copy.end(), sortByFrequency); + + return copy; + } + + void ComponentReporter::Combine(const ComponentReporter& other) + { + MultiplayerDebugByteReporter::Combine(other); + + for (const auto& fieldIter : other.m_fieldReports) + { + m_fieldReports[fieldIter.first].Combine(fieldIter.second); + } + + m_componentDirtyBytes.Combine(other.m_componentDirtyBytes); + } + + void EntityReporter::ReportField(AZ::u32 index, const char* componentName, + const char* fieldName, size_t byteSize) + { + if (m_currentComponentReport == nullptr) + { + std::stringstream component; + component << "[" << std::setw(2) << std::setfill('0') << static_cast(index) << "]" << " " << componentName; + m_currentComponentReport = &m_componentReports[component.str().c_str()]; + } + + m_currentComponentReport->ReportField(fieldName, byteSize); + MultiplayerDebugByteReporter::AggregateBytes(byteSize); + } + + void EntityReporter::ReportDirtyBits(AZ::u32 index, const char* componentName, size_t byteSize) + { + const char* const prefix = "MB::"; + if (strncmp(prefix, componentName, 4) == 0) + { + componentName += strlen(prefix); + } + + if (m_currentComponentReport == nullptr) + { + std::stringstream component; + component << "[" << std::setw(2) << std::setfill('0') << static_cast(index) << "]" << " " << componentName; + m_currentComponentReport = &m_componentReports[component.str().c_str()]; + } + + m_currentComponentReport->ReportDirtyBits(byteSize); + m_gdeDirtyBytes.AggregateBytes(byteSize); + } + + void EntityReporter::ReportFragmentEnd() + { + if (m_currentComponentReport) + { + m_currentComponentReport->ReportFragmentEnd(); + m_currentComponentReport = nullptr; + } + + m_gdeDirtyBytes.ReportAggregateBytes(); + MultiplayerDebugByteReporter::ReportAggregateBytes(); + } + + void EntityReporter::Combine(const EntityReporter& other) + { + MultiplayerDebugByteReporter::Combine(other); + + for (const auto& componentIter : other.m_componentReports) + { + m_componentReports[componentIter.first].Combine(componentIter.second); + } + + m_gdeDirtyBytes.Combine(other.m_gdeDirtyBytes); + } + + void EntityReporter::Reset() + { + MultiplayerDebugByteReporter::Reset(); + + m_componentReports.clear(); + m_gdeDirtyBytes.Reset(); + } + + AZStd::map& EntityReporter::GetComponentReports() + { + return m_componentReports; + } +} diff --git a/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugByteReporter.h b/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugByteReporter.h new file mode 100644 index 0000000000..7e6a04569d --- /dev/null +++ b/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugByteReporter.h @@ -0,0 +1,96 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ +#pragma once + +#include +#include +#include +#include + +namespace MultiplayerDiagnostics +{ + class MultiplayerDebugByteReporter + { + public: + MultiplayerDebugByteReporter() { MultiplayerDebugByteReporter::Reset(); } + virtual ~MultiplayerDebugByteReporter() = default; + + void ReportBytes(size_t byteSize); + void AggregateBytes(size_t byteSize); + void ReportAggregateBytes(); + + float GetAverageBytes() const; + size_t GetMaxBytes() const; + size_t GetMinBytes() const; + size_t GetTotalBytes() const; + float GetKbitsPerSecond(); + + void Combine(const MultiplayerDebugByteReporter& other); + virtual void Reset(); + + size_t GetTotalCount() const { return m_count; } + + private: + size_t m_count; + size_t m_totalBytes; + size_t m_totalBytesThisSecond; + float m_totalBytesLastSecond; + size_t m_minBytes; + size_t m_maxBytes; + size_t m_aggregateBytes; + + AZStd::chrono::monotonic_clock::time_point m_lastUpdateTime; + }; + + class ComponentReporter : public MultiplayerDebugByteReporter + { + public: + ComponentReporter() = default; + + void ReportField(const char* fieldName, size_t byteSize); + void ReportDirtyBits(size_t byteSize) { m_componentDirtyBytes.AggregateBytes(byteSize); } + void ReportFragmentEnd(); + + using Report = AZStd::pair; + AZStd::vector GetFieldReports(); + AZStd::size_t GetTotalDirtyBits() const { return m_componentDirtyBytes.GetTotalBytes(); } + float GetAvgDirtyBits() const { return m_componentDirtyBytes.GetAverageBytes(); } + + void Combine(const ComponentReporter& other); + + private: + AZStd::map m_fieldReports; + MultiplayerDebugByteReporter m_componentDirtyBytes; + }; + + class EntityReporter : public MultiplayerDebugByteReporter + { + public: + EntityReporter() = default; + + void ReportField(AZ::u32 index, const char* componentName, const char* fieldName, size_t byteSize); + void ReportDirtyBits(AZ::u32 index, const char* componentName, size_t byteSize); + void ReportFragmentEnd(); + + void Combine(const EntityReporter& other); + void Reset() override; + + AZStd::map& GetComponentReports(); + AZStd::size_t GetTotalDirtyBits() const { return m_gdeDirtyBytes.GetTotalBytes(); } + float GetAvgDirtyBits() const { return m_gdeDirtyBytes.GetAverageBytes(); } + + private: + ComponentReporter* m_currentComponentReport = nullptr; + AZStd::map m_componentReports; + MultiplayerDebugByteReporter m_gdeDirtyBytes; + }; +} diff --git a/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugPerEntityReporter.cpp b/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugPerEntityReporter.cpp new file mode 100644 index 0000000000..e60b800372 --- /dev/null +++ b/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugPerEntityReporter.cpp @@ -0,0 +1,385 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ +#include "MultiplayerDebugPerEntityReporter.h" +#include +#include +#include + +#if defined(IMGUI_ENABLED) +#include +#endif + +namespace MultiplayerDiagnostics +{ +#if defined(IMGUI_ENABLED) + static const ImVec4 k_ImGuiTomato = ImVec4(1.0f, 0.4f, 0.3f, 1.0f); + static const ImVec4 k_ImGuiKhaki = ImVec4(0.9f, 0.8f, 0.5f, 1.0f); + static const ImVec4 k_ImGuiCyan = ImVec4(0.5f, 1.0f, 1.0f, 1.0f); + static const ImVec4 k_ImGuiDusk = ImVec4(0.7f, 0.7f, 1.0f, 1.0f); + static const ImVec4 k_ImGuiWhite = ImVec4(1.0f, 1.0f, 1.0f, 1.0f); + + // -------------------------------------------------------------------------------------------- + template + bool ReplicatedStateTreeNode(const AZStd::string& name, Reporter& report, const ImVec4& color, int depth = 0) + { + const int defaultPadAmount = 55; + const int depthReduction = 3; + ImGui::PushStyleColor(ImGuiCol_Text, color); + + const bool expanded = ImGui::TreeNode(name.c_str(), + "%-*s %7.2f kbps %7.2f B Avg. %4zu B Max %10zu B Payload", + defaultPadAmount - depthReduction * depth, + name.c_str(), + report.GetKbitsPerSecond(), + report.GetAverageBytes(), + report.GetMaxBytes(), + report.GetTotalBytes()); + ImGui::PopStyleColor(); + return expanded; + } + + // -------------------------------------------------------------------------------------------- + void DisplayReplicatedStateReport(AZStd::map& componentReports, float kbpsWarn, float maxWarn) + { + for (auto& componentPair : componentReports) + { + ImGui::Separator(); + ComponentReporter& componentReport = componentPair.second; + + if (ReplicatedStateTreeNode(componentPair.first, componentReport, k_ImGuiCyan, 1)) + { + ImGui::Separator(); + ImGui::Columns(6, "replicated_field_columns"); + ImGui::NextColumn(); + ImGui::Text("kbps"); + ImGui::NextColumn(); + ImGui::Text("Avg. Bytes"); + ImGui::NextColumn(); + ImGui::Text("Min Bytes"); + ImGui::NextColumn(); + ImGui::Text("Max Bytes"); + ImGui::NextColumn(); + ImGui::Text("Total Bytes"); + ImGui::NextColumn(); + + auto fieldReports = componentReport.GetFieldReports(); + for (auto& fieldPair : fieldReports) + { + MultiplayerDebugByteReporter& fieldReport = *fieldPair.second; + const float kbitsLastSecond = fieldReport.GetKbitsPerSecond(); + + const ImVec4* textColor = &k_ImGuiWhite; + if (fieldReport.GetMaxBytes() > maxWarn) + { + textColor = &k_ImGuiKhaki; + } + + if (kbitsLastSecond > kbpsWarn) + { + textColor = &k_ImGuiTomato; + } + + ImGui::PushStyleColor(ImGuiCol_Text, *textColor); + + ImGui::Text("%s", fieldPair.first.c_str()); + ImGui::NextColumn(); + ImGui::Text("%.2f", kbitsLastSecond); + ImGui::NextColumn(); + ImGui::Text("%.2f", fieldReport.GetAverageBytes()); + ImGui::NextColumn(); + ImGui::Text("%zu", fieldReport.GetMinBytes()); + ImGui::NextColumn(); + ImGui::Text("%zu", fieldReport.GetMaxBytes()); + ImGui::NextColumn(); + ImGui::Text("%zu", fieldReport.GetTotalBytes()); + ImGui::NextColumn(); + + ImGui::PopStyleColor(); + } + + ImGui::Columns(1); + ImGui::TreePop(); + } + } + } +#endif + + MultiplayerDebugPerEntityReporter::MultiplayerDebugPerEntityReporter () + { + GridMate::Debug::CarrierDrillerBus::Handler::BusConnect(); + } + + // -------------------------------------------------------------------------------------------- + MultiplayerDebugPerEntityReporter::~MultiplayerDebugPerEntityReporter() + { + GridMate::Debug::ReplicaDrillerBus::Handler::BusDisconnect(); + GridMate::Debug::CarrierDrillerBus::Handler::BusDisconnect(); + } + + void MultiplayerDebugPerEntityReporter::OnReceiveReplicaBegin(GridMate::Replica*, const void*, size_t) + { + m_currentReceivingEntityReport.Reset(); + } + + void MultiplayerDebugPerEntityReporter::OnReceiveReplicaEnd(GridMate::Replica* replica) + { + m_receivingEntityReports[replica->GetDebugName()].Combine(m_currentReceivingEntityReport); + } + + void MultiplayerDebugPerEntityReporter::OnReceiveReplicaChunkEnd(GridMate::ReplicaChunkBase* chunk, AZ::u32 chunkIndex) + { + AZ_UNUSED(chunk); + AZ_UNUSED(chunkIndex); + m_currentReceivingEntityReport.ReportFragmentEnd(); + } + + void MultiplayerDebugPerEntityReporter::OnReceiveDataSet(GridMate::ReplicaChunkBase* chunk, AZ::u32 chunkIndex, + GridMate::DataSetBase* dataSet, GridMate::PeerId, GridMate::PeerId, const void*, size_t len) + { + m_currentReceivingEntityReport.ReportField(chunkIndex, chunk->GetDescriptor()->GetChunkName(), chunk->GetDescriptor()->GetDataSetName(chunk, dataSet), len); + } + + void MultiplayerDebugPerEntityReporter::OnReceiveRpc (GridMate::ReplicaChunkBase* chunk, + AZ::u32 chunkIndex, + GridMate::Internal::RpcRequest* rpc, + GridMate::PeerId from, + GridMate::PeerId to, + const void* data, + size_t len) + { + AZ_UNUSED( from ); + AZ_UNUSED( to ); + AZ_UNUSED( data ); + + m_currentReceivingEntityReport.ReportField(chunkIndex, chunk->GetDescriptor()->GetChunkName(), chunk->GetDescriptor()->GetRpcName(chunk, rpc->m_rpc), len); + } + + void MultiplayerDebugPerEntityReporter::OnSendReplicaBegin (GridMate::Replica*) + { + m_currentSendingEntityReport.Reset(); + } + + void MultiplayerDebugPerEntityReporter::OnSendReplicaEnd (GridMate::Replica* replica, const void*, size_t) + { + m_sendingEntityReports[replica->GetDebugName()].Combine(m_currentSendingEntityReport); + } + + void MultiplayerDebugPerEntityReporter::OnSendReplicaChunkEnd (GridMate::ReplicaChunkBase* chunk, + AZ::u32 chunkIndex, + const void*, + size_t) + { + AZ_UNUSED(chunk); + AZ_UNUSED(chunkIndex); + m_currentSendingEntityReport.ReportFragmentEnd(); + } + + void MultiplayerDebugPerEntityReporter::OnSendDataSet (GridMate::ReplicaChunkBase* chunk, + AZ::u32 chunkIndex, + GridMate::DataSetBase* dataSet, + GridMate::PeerId, + GridMate::PeerId, + const void*, + size_t len) + { + m_currentSendingEntityReport.ReportField(chunkIndex, chunk->GetDescriptor()->GetChunkName(), chunk->GetDescriptor()->GetDataSetName(chunk, dataSet), len); + } + + void MultiplayerDebugPerEntityReporter::OnSendRpc (GridMate::ReplicaChunkBase* chunk, + AZ::u32 chunkIndex, + GridMate::Internal::RpcRequest* rpc, + GridMate::PeerId, + GridMate::PeerId, + const void*, + size_t len) + { + m_currentSendingEntityReport.ReportField(chunkIndex, chunk->GetDescriptor()->GetChunkName(), chunk->GetDescriptor()->GetRpcName(chunk, rpc->m_rpc), len); + } + + void MultiplayerDebugPerEntityReporter::OnIncomingConnection (GridMate::Carrier*, GridMate::ConnectionID) + { + } + + void MultiplayerDebugPerEntityReporter::OnFailedToConnect (GridMate::Carrier*, + GridMate::ConnectionID, + GridMate::CarrierDisconnectReason) + { + m_lastSecondStats.clear(); + } + + void MultiplayerDebugPerEntityReporter::OnConnectionEstablished (GridMate::Carrier*, GridMate::ConnectionID) + { + } + + void MultiplayerDebugPerEntityReporter::OnDisconnect (GridMate::Carrier*, + GridMate::ConnectionID, + GridMate::CarrierDisconnectReason) + { + /* + * CarrierDrillerBus doesn't provide enough information to correctly keep track of network traffic for all peers. + * This is a work around until that is fixed to at least not over report the bandwidth amount. + */ + m_lastSecondStats.clear(); + } + + void MultiplayerDebugPerEntityReporter::OnDriverError (GridMate::Carrier*, + GridMate::ConnectionID, + const GridMate::DriverError&) + { + m_lastSecondStats.clear(); + } + + void MultiplayerDebugPerEntityReporter::OnSecurityError (GridMate::Carrier*, + GridMate::ConnectionID, + const GridMate::SecurityError&) + { + m_lastSecondStats.clear(); + } + + void MultiplayerDebugPerEntityReporter::OnUpdateStatistics (const GridMate::string& address, + const GridMate::TrafficControl::Statistics&, + const GridMate::TrafficControl::Statistics&, + const GridMate::TrafficControl::Statistics& effectiveLastSecond, + const GridMate::TrafficControl::Statistics&) + { + m_lastSecondStats[address] = effectiveLastSecond; + } + + void MultiplayerDebugPerEntityReporter::OnConnectionStateChanged (GridMate::Carrier*, + GridMate::ConnectionID, + GridMate::Carrier::ConnectionStates) + { + m_lastSecondStats.clear(); + } + + void MultiplayerDebugPerEntityReporter::UpdateTrafficStatistics() + { +#if defined(IMGUI_ENABLED) + AZ::u32 dataReceived = 0, dataSent = 0; + + for (auto& perConnection : m_lastSecondStats) + { + dataReceived += perConnection.second.m_dataReceived; + dataSent += perConnection.second.m_dataSend; + } + + if (dataReceived !=0 || dataSent != 0) + { + ImGui::Text("Total bandwidth: Sent %u kbps Received %u kbps.", dataSent * 8 / 1000, dataReceived * 8 / 1000); + } + else + { + ImGui::Text("Total bandwidth: Sent -- kbps Received -- kbps."); + } +#endif + } + + // -------------------------------------------------------------------------------------------- + void MultiplayerDebugPerEntityReporter::OnImGuiUpdate() + { +#if defined(IMGUI_ENABLED) + if (ImGui::BeginMainMenuBar()) + { + if (ImGui::BeginMenu("GridMate")) + { + if (m_showServerReportWindow) + { + if (ImGui::MenuItem("Hide Multiplayer Analytics Window")) + { + m_showServerReportWindow = false; + } + } + else if (ImGui::MenuItem("Show Multiplayer Analytics Window")) + { + m_showServerReportWindow = true; + } + + ImGui::End(); + } + + ImGui::EndMainMenuBar(); + } + + if (m_showServerReportWindow) + { + if (ImGui::Begin("Multiplayer Analytics", &m_showServerReportWindow)) + { + // General carrier stats + UpdateTrafficStatistics(); + + if (ImGui::Checkbox("Analyze network traffic", &m_isTrackingMessages)) + { + if (m_isTrackingMessages) + { + GridMate::Debug::ReplicaDrillerBus::Handler::BusConnect(); + } + else + { + GridMate::Debug::ReplicaDrillerBus::Handler::BusDisconnect(); + + m_currentReceivingEntityReport.Reset(); + m_receivingEntityReports.clear(); + + m_currentSendingEntityReport.Reset(); + m_sendingEntityReports.clear(); + } + } + + if (m_isTrackingMessages) + { + ImGui::Separator(); + + static ImGuiTextFilter filter; + filter.Draw(); + + if (ImGui::CollapsingHeader("Received replicas per type")) + { + for (auto& entityPair : m_receivingEntityReports) + { + if (!filter.PassFilter(entityPair.first.c_str())) + { + continue; + } + + ImGui::Separator(); + if (ReplicatedStateTreeNode(entityPair.first, entityPair.second, k_ImGuiDusk)) + { + DisplayReplicatedStateReport(entityPair.second.GetComponentReports(), m_replicatedStateKbpsWarn, m_replicatedStateMaxSizeWarn); + ImGui::TreePop(); + } + } + } + + if (ImGui::CollapsingHeader("Sent replicas per type")) + { + for (auto& entityPair : m_sendingEntityReports) + { + if (!filter.PassFilter(entityPair.first.c_str())) + { + continue; + } + + ImGui::Separator(); + if (ReplicatedStateTreeNode(entityPair.first, entityPair.second, k_ImGuiDusk)) + { + DisplayReplicatedStateReport(entityPair.second.GetComponentReports(), m_replicatedStateKbpsWarn, m_replicatedStateMaxSizeWarn); + ImGui::TreePop(); + } + } + } + } + } + ImGui::End(); + } +#endif + } +} diff --git a/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugPerEntityReporter.h b/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugPerEntityReporter.h new file mode 100644 index 0000000000..96cfadb14d --- /dev/null +++ b/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugPerEntityReporter.h @@ -0,0 +1,90 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ +#pragma once +#include +#include "MultiplayerDebugByteReporter.h" +#include + +namespace MultiplayerDiagnostics +{ + /** + * \brief GridMate network live analysis tool via ImGui. + */ + class MultiplayerDebugPerEntityReporter + : public GridMate::Debug::ReplicaDrillerBus::Handler + , public GridMate::Debug::CarrierDrillerBus::Handler + { + public: + MultiplayerDebugPerEntityReporter(); + virtual ~MultiplayerDebugPerEntityReporter(); + + // main update loop + void OnImGuiUpdate(); + + // ReplicaDrillerBus - receive + + void OnReceiveReplicaBegin(GridMate::Replica* replica, const void* data, size_t len) override; + void OnReceiveReplicaEnd(GridMate::Replica* replica) override; + void OnReceiveReplicaChunkEnd(GridMate::ReplicaChunkBase* chunk, AZ::u32 chunkIndex) override; + void OnReceiveDataSet(GridMate::ReplicaChunkBase* chunk, AZ::u32 chunkIndex, GridMate::DataSetBase* dataSet, GridMate::PeerId from, GridMate::PeerId to, const void* data, size_t len) override; + void OnReceiveRpc(GridMate::ReplicaChunkBase* chunk, AZ::u32 chunkIndex, GridMate::Internal::RpcRequest* rpc, GridMate::PeerId from, GridMate::PeerId to, const void* data, size_t len) override; + + // ReplicaDrillerBus - sending + + void OnSendReplicaBegin(GridMate::Replica* replica) override; + void OnSendReplicaEnd(GridMate::Replica* replica, const void* data, size_t len) override; + void OnSendReplicaChunkEnd(GridMate::ReplicaChunkBase* chunk, AZ::u32 chunkIndex, const void* data, size_t len) override; + void OnSendDataSet(GridMate::ReplicaChunkBase* chunk, AZ::u32 chunkIndex, GridMate::DataSetBase* dataSet, GridMate::PeerId from, GridMate::PeerId to, const void* data, size_t len) override; + void OnSendRpc(GridMate::ReplicaChunkBase* chunk, AZ::u32 chunkIndex, GridMate::Internal::RpcRequest* rpc, GridMate::PeerId from, GridMate::PeerId to, const void* data, size_t len) override; + + // CarrierDrillerBus + void OnIncomingConnection (GridMate::Carrier* carrier, GridMate::ConnectionID id) override; + void OnFailedToConnect (GridMate::Carrier* carrier, + GridMate::ConnectionID id, + GridMate::CarrierDisconnectReason reason) override; + void OnConnectionEstablished (GridMate::Carrier* carrier, GridMate::ConnectionID id) override; + void OnDisconnect (GridMate::Carrier* carrier, + GridMate::ConnectionID id, + GridMate::CarrierDisconnectReason reason) override; + void OnDriverError (GridMate::Carrier* carrier, + GridMate::ConnectionID id, + const GridMate::DriverError& error) override; + void OnSecurityError (GridMate::Carrier* carrier, + GridMate::ConnectionID id, + const GridMate::SecurityError& error) override; + void OnUpdateStatistics (const GridMate::string& address, + const GridMate::TrafficControl::Statistics& lastSecond, + const GridMate::TrafficControl::Statistics& lifeTime, + const GridMate::TrafficControl::Statistics& effectiveLastSecond, + const GridMate::TrafficControl::Statistics& effectiveLifeTime) override; + void OnConnectionStateChanged (GridMate::Carrier* carrier, + GridMate::ConnectionID id, + GridMate::Carrier::ConnectionStates newState) override; + + private: + + bool m_showServerReportWindow = false; + bool m_isTrackingMessages = false; + + AZStd::map m_sendingEntityReports{}; + EntityReporter m_currentSendingEntityReport; + + AZStd::map m_receivingEntityReports{}; + EntityReporter m_currentReceivingEntityReport; + + float m_replicatedStateKbpsWarn = 10.f; + float m_replicatedStateMaxSizeWarn = 30.f; + + void UpdateTrafficStatistics(); + AZStd::map m_lastSecondStats; + }; +} diff --git a/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugSystemComponent.cpp b/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugSystemComponent.cpp index 1611a3213d..b97fa053b2 100644 --- a/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugSystemComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugSystemComponent.cpp @@ -53,6 +53,11 @@ namespace Multiplayer #endif } + void MultiplayerDebugSystemComponent::OnImGuiInitialize() + { + m_reporter = AZStd::make_unique(); + } + #ifdef IMGUI_ENABLED void MultiplayerDebugSystemComponent::OnImGuiMainMenuUpdate() { @@ -318,6 +323,12 @@ namespace Multiplayer ImGui::End(); } } + + + if (m_reporter) + { + m_reporter->OnImGuiUpdate(); + } } #endif } diff --git a/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugSystemComponent.h b/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugSystemComponent.h index 77daaf9b7d..3dbbd06d6f 100644 --- a/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugSystemComponent.h +++ b/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugSystemComponent.h @@ -9,6 +9,7 @@ #pragma once #include +#include #ifdef IMGUI_ENABLED # include @@ -42,6 +43,7 @@ namespace Multiplayer #ifdef IMGUI_ENABLED //! ImGui::ImGuiUpdateListenerBus overrides //! @{ + void OnImGuiInitialize() override; void OnImGuiMainMenuUpdate() override; void OnImGuiUpdate() override; //! @} @@ -49,5 +51,7 @@ namespace Multiplayer private: bool m_displayNetworkingStats = false; bool m_displayMultiplayerStats = false; + + AZStd::unique_ptr m_reporter; }; } diff --git a/Gems/Multiplayer/Code/multiplayer_debug_files.cmake b/Gems/Multiplayer/Code/multiplayer_debug_files.cmake index 62bd632e7e..37a1c91640 100644 --- a/Gems/Multiplayer/Code/multiplayer_debug_files.cmake +++ b/Gems/Multiplayer/Code/multiplayer_debug_files.cmake @@ -7,6 +7,10 @@ # set(FILES + Source/Debug/MultiplayerDebugByteReporter.cpp + Source/Debug/MultiplayerDebugByteReporter.h + Source/Debug/MultiplayerDebugPerEntityReporter.cpp + Source/Debug/MultiplayerDebugPerEntityReporter.h Source/Debug/MultiplayerDebugModule.cpp Source/Debug/MultiplayerDebugModule.h Source/Debug/MultiplayerDebugSystemComponent.cpp From f29fd86a395812f07c28998758365e9e97b68d7f Mon Sep 17 00:00:00 2001 From: AMZN-Olex <5432499+AMZN-Olex@users.noreply.github.com> Date: Mon, 26 Jul 2021 19:11:28 -0400 Subject: [PATCH 02/37] More converstion in progress Signed-off-by: AMZN-Olex <5432499+AMZN-Olex@users.noreply.github.com> --- .../Components/MultiplayerComponent.h | 21 +- .../Include/Multiplayer/MultiplayerStats.h | 2 + .../Source/Components/NetBindComponent.cpp | 4 + .../Debug/MultiplayerDebugByteReporter.cpp | 17 +- .../MultiplayerDebugPerEntityInterface.h | 26 ++ .../MultiplayerDebugPerEntityReporter.cpp | 292 ++++++++++-------- .../Debug/MultiplayerDebugPerEntityReporter.h | 93 +++--- .../Code/Source/MultiplayerStats.cpp | 27 ++ .../Code/multiplayer_debug_files.cmake | 1 + 9 files changed, 288 insertions(+), 195 deletions(-) create mode 100644 Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugPerEntityInterface.h diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/Components/MultiplayerComponent.h b/Gems/Multiplayer/Code/Include/Multiplayer/Components/MultiplayerComponent.h index 8526b6c70b..48590e5393 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/Components/MultiplayerComponent.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/Components/MultiplayerComponent.h @@ -105,14 +105,15 @@ namespace Multiplayer template inline void SerializeNetworkPropertyHelper ( - AzNetworking::ISerializer& serializer, - bool modifyRecord, - AzNetworking::FixedSizeBitsetView& bitset, - int32_t bitIndex, - TYPE& value, - const char* name, - NetComponentId componentId, - PropertyIndex propertyIndex, + AzNetworking::ISerializer& serializer, + bool modifyRecord, + AzNetworking::FixedSizeBitsetView& bitset, + int32_t bitIndex, + TYPE& value, + const char* name, + AZ::EntityId entityId, + NetComponentId componentId, + PropertyIndex propertyIndex, MultiplayerStats& stats ) { @@ -133,11 +134,11 @@ namespace Multiplayer { if (modifyRecord) { - stats.RecordPropertyReceived(componentId, propertyIndex, updateSize); + stats.RecordPropertyReceived(entityId, componentId, propertyIndex, updateSize); } else { - stats.RecordPropertySent(componentId, propertyIndex, updateSize); + stats.RecordPropertySent(entityId, componentId, propertyIndex, updateSize); } } } diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/MultiplayerStats.h b/Gems/Multiplayer/Code/Include/Multiplayer/MultiplayerStats.h index d279355d7c..133cbb7aeb 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/MultiplayerStats.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/MultiplayerStats.h @@ -50,6 +50,8 @@ namespace Multiplayer AZStd::vector m_componentStats; void ReserveComponentStats(NetComponentId netComponentId, uint16_t propertyCount, uint16_t rpcCount); + void RecordEntitySerializeStart(AZ::EntityId entityId, const char* entityName); + void RecordEntitySerializeStop(AZ::EntityId entityId, const char* entityName); void RecordPropertySent(NetComponentId netComponentId, PropertyIndex propertyId, uint32_t totalBytes); void RecordPropertyReceived(NetComponentId netComponentId, PropertyIndex propertyId, uint32_t totalBytes); void RecordRpcSent(NetComponentId netComponentId, RpcIndex rpcId, uint32_t totalBytes); diff --git a/Gems/Multiplayer/Code/Source/Components/NetBindComponent.cpp b/Gems/Multiplayer/Code/Source/Components/NetBindComponent.cpp index e8da34fc7f..74091908e2 100644 --- a/Gems/Multiplayer/Code/Source/Components/NetBindComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Components/NetBindComponent.cpp @@ -447,12 +447,16 @@ namespace Multiplayer bool NetBindComponent::SerializeStateDeltaMessage(ReplicationRecord& replicationRecord, AzNetworking::ISerializer& serializer) { + GetMultiplayer()->GetStats().RecordEntitySerializeStart(GetEntityId(), GetEntity()->GetName().c_str()); + bool success = true; for (auto iter = m_multiplayerSerializationComponentVector.begin(); iter != m_multiplayerSerializationComponentVector.end(); ++iter) { success &= (*iter)->SerializeStateDeltaMessage(replicationRecord, serializer); } + GetMultiplayer()->GetStats().RecordEntitySerializeStop(GetEntityId(), GetEntity()->GetName().c_str()); + return success; } diff --git a/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugByteReporter.cpp b/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugByteReporter.cpp index 382bc70d9b..fd31298ad8 100644 --- a/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugByteReporter.cpp +++ b/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugByteReporter.cpp @@ -1,14 +1,11 @@ /* -* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -* its licensors. -* -* For complete copyright and license terms please see the LICENSE at the root of this -* distribution (the "License"). All use of this software is governed by the License, -* or, if provided, by the license below or the license accompanying this file. Do not -* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* -*/ + * 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 "MultiplayerDebugByteReporter.h" #include diff --git a/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugPerEntityInterface.h b/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugPerEntityInterface.h new file mode 100644 index 0000000000..8cd94944b2 --- /dev/null +++ b/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugPerEntityInterface.h @@ -0,0 +1,26 @@ +/* + * 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 + * + */ + +#pragma once + +#include +#include + +namespace MultiplayerDiagnostics +{ + class MultilayerIPerEntityStats + { + public: + virtual ~MultilayerIPerEntityStats(); + + virtual void RecordEntitySerializeStart(AZ::EntityId entityId, const char* entityName) = 0; + virtual void RecordEntitySerializeStop(AZ::EntityId entityId, const char* entityName) = 0; + virtual void RecordPropertySent(Multiplayer::NetComponentId netComponentId, Multiplayer::PropertyIndex propertyId, uint32_t totalBytes); + virtual void RecordPropertyReceived(Multiplayer::NetComponentId netComponentId, Multiplayer::PropertyIndex propertyId, uint32_t totalBytes); + }; +} diff --git a/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugPerEntityReporter.cpp b/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugPerEntityReporter.cpp index e60b800372..32ed7d5a59 100644 --- a/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugPerEntityReporter.cpp +++ b/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugPerEntityReporter.cpp @@ -1,18 +1,16 @@ /* -* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -* its licensors. -* -* For complete copyright and license terms please see the LICENSE at the root of this -* distribution (the "License"). All use of this software is governed by the License, -* or, if provided, by the license below or the license accompanying this file. Do not -* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* -*/ + * 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 "MultiplayerDebugPerEntityReporter.h" #include #include #include +#include #if defined(IMGUI_ENABLED) #include @@ -115,151 +113,151 @@ namespace MultiplayerDiagnostics MultiplayerDebugPerEntityReporter::MultiplayerDebugPerEntityReporter () { - GridMate::Debug::CarrierDrillerBus::Handler::BusConnect(); + //GridMate::Debug::CarrierDrillerBus::Handler::BusConnect(); } // -------------------------------------------------------------------------------------------- MultiplayerDebugPerEntityReporter::~MultiplayerDebugPerEntityReporter() { - GridMate::Debug::ReplicaDrillerBus::Handler::BusDisconnect(); - GridMate::Debug::CarrierDrillerBus::Handler::BusDisconnect(); + /*GridMate::Debug::ReplicaDrillerBus::Handler::BusDisconnect(); + GridMate::Debug::CarrierDrillerBus::Handler::BusDisconnect();*/ } - void MultiplayerDebugPerEntityReporter::OnReceiveReplicaBegin(GridMate::Replica*, const void*, size_t) - { - m_currentReceivingEntityReport.Reset(); - } + //void MultiplayerDebugPerEntityReporter::OnReceiveReplicaBegin(GridMate::Replica*, const void*, size_t) + //{ + // m_currentReceivingEntityReport.Reset(); + //} - void MultiplayerDebugPerEntityReporter::OnReceiveReplicaEnd(GridMate::Replica* replica) - { - m_receivingEntityReports[replica->GetDebugName()].Combine(m_currentReceivingEntityReport); - } + //void MultiplayerDebugPerEntityReporter::OnReceiveReplicaEnd(GridMate::Replica* replica) + //{ + // m_receivingEntityReports[replica->GetDebugName()].Combine(m_currentReceivingEntityReport); + //} - void MultiplayerDebugPerEntityReporter::OnReceiveReplicaChunkEnd(GridMate::ReplicaChunkBase* chunk, AZ::u32 chunkIndex) - { - AZ_UNUSED(chunk); - AZ_UNUSED(chunkIndex); - m_currentReceivingEntityReport.ReportFragmentEnd(); - } + //void MultiplayerDebugPerEntityReporter::OnReceiveReplicaChunkEnd(GridMate::ReplicaChunkBase* chunk, AZ::u32 chunkIndex) + //{ + // AZ_UNUSED(chunk); + // AZ_UNUSED(chunkIndex); + // m_currentReceivingEntityReport.ReportFragmentEnd(); + //} - void MultiplayerDebugPerEntityReporter::OnReceiveDataSet(GridMate::ReplicaChunkBase* chunk, AZ::u32 chunkIndex, - GridMate::DataSetBase* dataSet, GridMate::PeerId, GridMate::PeerId, const void*, size_t len) - { - m_currentReceivingEntityReport.ReportField(chunkIndex, chunk->GetDescriptor()->GetChunkName(), chunk->GetDescriptor()->GetDataSetName(chunk, dataSet), len); - } + //void MultiplayerDebugPerEntityReporter::OnReceiveDataSet(GridMate::ReplicaChunkBase* chunk, AZ::u32 chunkIndex, + // GridMate::DataSetBase* dataSet, GridMate::PeerId, GridMate::PeerId, const void*, size_t len) + //{ + // m_currentReceivingEntityReport.ReportField(chunkIndex, chunk->GetDescriptor()->GetChunkName(), chunk->GetDescriptor()->GetDataSetName(chunk, dataSet), len); + //} - void MultiplayerDebugPerEntityReporter::OnReceiveRpc (GridMate::ReplicaChunkBase* chunk, - AZ::u32 chunkIndex, - GridMate::Internal::RpcRequest* rpc, - GridMate::PeerId from, - GridMate::PeerId to, - const void* data, - size_t len) - { - AZ_UNUSED( from ); - AZ_UNUSED( to ); - AZ_UNUSED( data ); + //void MultiplayerDebugPerEntityReporter::OnReceiveRpc (GridMate::ReplicaChunkBase* chunk, + // AZ::u32 chunkIndex, + // GridMate::Internal::RpcRequest* rpc, + // GridMate::PeerId from, + // GridMate::PeerId to, + // const void* data, + // size_t len) + //{ + // AZ_UNUSED( from ); + // AZ_UNUSED( to ); + // AZ_UNUSED( data ); - m_currentReceivingEntityReport.ReportField(chunkIndex, chunk->GetDescriptor()->GetChunkName(), chunk->GetDescriptor()->GetRpcName(chunk, rpc->m_rpc), len); - } + // m_currentReceivingEntityReport.ReportField(chunkIndex, chunk->GetDescriptor()->GetChunkName(), chunk->GetDescriptor()->GetRpcName(chunk, rpc->m_rpc), len); + //} - void MultiplayerDebugPerEntityReporter::OnSendReplicaBegin (GridMate::Replica*) - { - m_currentSendingEntityReport.Reset(); - } + //void MultiplayerDebugPerEntityReporter::OnSendReplicaBegin (GridMate::Replica*) + //{ + // m_currentSendingEntityReport.Reset(); + //} - void MultiplayerDebugPerEntityReporter::OnSendReplicaEnd (GridMate::Replica* replica, const void*, size_t) - { - m_sendingEntityReports[replica->GetDebugName()].Combine(m_currentSendingEntityReport); - } + //void MultiplayerDebugPerEntityReporter::OnSendReplicaEnd (GridMate::Replica* replica, const void*, size_t) + //{ + // m_sendingEntityReports[replica->GetDebugName()].Combine(m_currentSendingEntityReport); + //} - void MultiplayerDebugPerEntityReporter::OnSendReplicaChunkEnd (GridMate::ReplicaChunkBase* chunk, - AZ::u32 chunkIndex, - const void*, - size_t) - { - AZ_UNUSED(chunk); - AZ_UNUSED(chunkIndex); - m_currentSendingEntityReport.ReportFragmentEnd(); - } + //void MultiplayerDebugPerEntityReporter::OnSendReplicaChunkEnd (GridMate::ReplicaChunkBase* chunk, + // AZ::u32 chunkIndex, + // const void*, + // size_t) + //{ + // AZ_UNUSED(chunk); + // AZ_UNUSED(chunkIndex); + // m_currentSendingEntityReport.ReportFragmentEnd(); + //} - void MultiplayerDebugPerEntityReporter::OnSendDataSet (GridMate::ReplicaChunkBase* chunk, - AZ::u32 chunkIndex, - GridMate::DataSetBase* dataSet, - GridMate::PeerId, - GridMate::PeerId, - const void*, - size_t len) - { - m_currentSendingEntityReport.ReportField(chunkIndex, chunk->GetDescriptor()->GetChunkName(), chunk->GetDescriptor()->GetDataSetName(chunk, dataSet), len); - } + //void MultiplayerDebugPerEntityReporter::OnSendDataSet (GridMate::ReplicaChunkBase* chunk, + // AZ::u32 chunkIndex, + // GridMate::DataSetBase* dataSet, + // GridMate::PeerId, + // GridMate::PeerId, + // const void*, + // size_t len) + //{ + // m_currentSendingEntityReport.ReportField(chunkIndex, chunk->GetDescriptor()->GetChunkName(), chunk->GetDescriptor()->GetDataSetName(chunk, dataSet), len); + //} - void MultiplayerDebugPerEntityReporter::OnSendRpc (GridMate::ReplicaChunkBase* chunk, - AZ::u32 chunkIndex, - GridMate::Internal::RpcRequest* rpc, - GridMate::PeerId, - GridMate::PeerId, - const void*, - size_t len) - { - m_currentSendingEntityReport.ReportField(chunkIndex, chunk->GetDescriptor()->GetChunkName(), chunk->GetDescriptor()->GetRpcName(chunk, rpc->m_rpc), len); - } + //void MultiplayerDebugPerEntityReporter::OnSendRpc (GridMate::ReplicaChunkBase* chunk, + // AZ::u32 chunkIndex, + // GridMate::Internal::RpcRequest* rpc, + // GridMate::PeerId, + // GridMate::PeerId, + // const void*, + // size_t len) + //{ + // m_currentSendingEntityReport.ReportField(chunkIndex, chunk->GetDescriptor()->GetChunkName(), chunk->GetDescriptor()->GetRpcName(chunk, rpc->m_rpc), len); + //} - void MultiplayerDebugPerEntityReporter::OnIncomingConnection (GridMate::Carrier*, GridMate::ConnectionID) - { - } + //void MultiplayerDebugPerEntityReporter::OnIncomingConnection (GridMate::Carrier*, GridMate::ConnectionID) + //{ + //} - void MultiplayerDebugPerEntityReporter::OnFailedToConnect (GridMate::Carrier*, - GridMate::ConnectionID, - GridMate::CarrierDisconnectReason) - { - m_lastSecondStats.clear(); - } + //void MultiplayerDebugPerEntityReporter::OnFailedToConnect (GridMate::Carrier*, + // GridMate::ConnectionID, + // GridMate::CarrierDisconnectReason) + //{ + // m_lastSecondStats.clear(); + //} - void MultiplayerDebugPerEntityReporter::OnConnectionEstablished (GridMate::Carrier*, GridMate::ConnectionID) - { - } + //void MultiplayerDebugPerEntityReporter::OnConnectionEstablished (GridMate::Carrier*, GridMate::ConnectionID) + //{ + //} - void MultiplayerDebugPerEntityReporter::OnDisconnect (GridMate::Carrier*, - GridMate::ConnectionID, - GridMate::CarrierDisconnectReason) - { - /* - * CarrierDrillerBus doesn't provide enough information to correctly keep track of network traffic for all peers. - * This is a work around until that is fixed to at least not over report the bandwidth amount. - */ - m_lastSecondStats.clear(); - } + //void MultiplayerDebugPerEntityReporter::OnDisconnect (GridMate::Carrier*, + // GridMate::ConnectionID, + // GridMate::CarrierDisconnectReason) + //{ + // /* + // * CarrierDrillerBus doesn't provide enough information to correctly keep track of network traffic for all peers. + // * This is a work around until that is fixed to at least not over report the bandwidth amount. + // */ + // m_lastSecondStats.clear(); + //} - void MultiplayerDebugPerEntityReporter::OnDriverError (GridMate::Carrier*, - GridMate::ConnectionID, - const GridMate::DriverError&) - { - m_lastSecondStats.clear(); - } + //void MultiplayerDebugPerEntityReporter::OnDriverError (GridMate::Carrier*, + // GridMate::ConnectionID, + // const GridMate::DriverError&) + //{ + // m_lastSecondStats.clear(); + //} - void MultiplayerDebugPerEntityReporter::OnSecurityError (GridMate::Carrier*, - GridMate::ConnectionID, - const GridMate::SecurityError&) - { - m_lastSecondStats.clear(); - } + //void MultiplayerDebugPerEntityReporter::OnSecurityError (GridMate::Carrier*, + // GridMate::ConnectionID, + // const GridMate::SecurityError&) + //{ + // m_lastSecondStats.clear(); + //} - void MultiplayerDebugPerEntityReporter::OnUpdateStatistics (const GridMate::string& address, - const GridMate::TrafficControl::Statistics&, - const GridMate::TrafficControl::Statistics&, - const GridMate::TrafficControl::Statistics& effectiveLastSecond, - const GridMate::TrafficControl::Statistics&) - { - m_lastSecondStats[address] = effectiveLastSecond; - } + //void MultiplayerDebugPerEntityReporter::OnUpdateStatistics (const GridMate::string& address, + // const GridMate::TrafficControl::Statistics&, + // const GridMate::TrafficControl::Statistics&, + // const GridMate::TrafficControl::Statistics& effectiveLastSecond, + // const GridMate::TrafficControl::Statistics&) + //{ + // m_lastSecondStats[address] = effectiveLastSecond; + //} - void MultiplayerDebugPerEntityReporter::OnConnectionStateChanged (GridMate::Carrier*, - GridMate::ConnectionID, - GridMate::Carrier::ConnectionStates) - { - m_lastSecondStats.clear(); - } + //void MultiplayerDebugPerEntityReporter::OnConnectionStateChanged (GridMate::Carrier*, + // GridMate::ConnectionID, + // GridMate::Carrier::ConnectionStates) + //{ + // m_lastSecondStats.clear(); + //} void MultiplayerDebugPerEntityReporter::UpdateTrafficStatistics() { @@ -320,11 +318,11 @@ namespace MultiplayerDiagnostics { if (m_isTrackingMessages) { - GridMate::Debug::ReplicaDrillerBus::Handler::BusConnect(); + //GridMate::Debug::ReplicaDrillerBus::Handler::BusConnect(); } else { - GridMate::Debug::ReplicaDrillerBus::Handler::BusDisconnect(); + //GridMate::Debug::ReplicaDrillerBus::Handler::BusDisconnect(); m_currentReceivingEntityReport.Reset(); m_receivingEntityReports.clear(); @@ -382,4 +380,34 @@ namespace MultiplayerDiagnostics } #endif } + + void MultiplayerDebugPerEntityReporter::RecordEntitySerializeStart(AZ::EntityId entityId, const char* entityName) + { + } + + void MultiplayerDebugPerEntityReporter::RecordEntitySerializeStop(AZ::EntityId entityId, const char* entityName) + { + } + + void MultiplayerDebugPerEntityReporter::RecordPropertySent( + AZ::EntityId entityId, + Multiplayer::NetComponentId netComponentId, + Multiplayer::PropertyIndex propertyId, + uint32_t totalBytes) + { + // TODO + } + + void MultiplayerDebugPerEntityReporter::RecordPropertyReceived( + Multiplayer::NetComponentId netComponentId, + Multiplayer::PropertyIndex propertyId, + uint32_t totalBytes) + { + if (Multiplayer::MultiplayerComponentRegistry* componentRegistry = Multiplayer::GetMultiplayerComponentRegistry()) + { + m_currentReceivingEntityReport.ReportField(static_cast(netComponentId), + componentRegistry->GetComponentName(netComponentId), + componentRegistry->GetComponentPropertyName(netComponentId, propertyId), totalBytes); + } + } } diff --git a/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugPerEntityReporter.h b/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugPerEntityReporter.h index 96cfadb14d..c89c889813 100644 --- a/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugPerEntityReporter.h +++ b/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugPerEntityReporter.h @@ -1,18 +1,20 @@ /* -* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -* its licensors. -* -* For complete copyright and license terms please see the LICENSE at the root of this -* distribution (the "License"). All use of this software is governed by the License, -* or, if provided, by the license below or the license accompanying this file. Do not -* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* -*/ + * 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 + * + */ + #pragma once #include #include "MultiplayerDebugByteReporter.h" + +#include +#include +#include #include +#include namespace MultiplayerDiagnostics { @@ -20,55 +22,60 @@ namespace MultiplayerDiagnostics * \brief GridMate network live analysis tool via ImGui. */ class MultiplayerDebugPerEntityReporter - : public GridMate::Debug::ReplicaDrillerBus::Handler - , public GridMate::Debug::CarrierDrillerBus::Handler + : public AZ::Interface::Registrar { public: MultiplayerDebugPerEntityReporter(); - virtual ~MultiplayerDebugPerEntityReporter(); + ~MultiplayerDebugPerEntityReporter() override; // main update loop void OnImGuiUpdate(); - // ReplicaDrillerBus - receive + //! MultilayerIPerEntityStats + // @{ + void RecordEntitySerializeStart(AZ::EntityId entityId, const char* entityName); + void RecordEntitySerializeStop(AZ::EntityId entityId, const char* entityName); + void RecordPropertySent(Multiplayer::NetComponentId netComponentId, Multiplayer::PropertyIndex propertyId, uint32_t totalBytes) override; + void RecordPropertyReceived(Multiplayer::NetComponentId netComponentId, Multiplayer::PropertyIndex propertyId, uint32_t totalBytes) override; + // }@ - void OnReceiveReplicaBegin(GridMate::Replica* replica, const void* data, size_t len) override; - void OnReceiveReplicaEnd(GridMate::Replica* replica) override; + /*void OnReceiveReplicaBegin(AZ::EntityId entityId, const void* data, size_t len) override; + void OnReceiveReplicaEnd(AZ::EntityId entityId) override; void OnReceiveReplicaChunkEnd(GridMate::ReplicaChunkBase* chunk, AZ::u32 chunkIndex) override; void OnReceiveDataSet(GridMate::ReplicaChunkBase* chunk, AZ::u32 chunkIndex, GridMate::DataSetBase* dataSet, GridMate::PeerId from, GridMate::PeerId to, const void* data, size_t len) override; - void OnReceiveRpc(GridMate::ReplicaChunkBase* chunk, AZ::u32 chunkIndex, GridMate::Internal::RpcRequest* rpc, GridMate::PeerId from, GridMate::PeerId to, const void* data, size_t len) override; + void OnReceiveRpc(GridMate::ReplicaChunkBase* chunk, AZ::u32 chunkIndex, GridMate::Internal::RpcRequest* rpc, GridMate::PeerId from, GridMate::PeerId to, const void* data, size_t len) override;*/ // ReplicaDrillerBus - sending - void OnSendReplicaBegin(GridMate::Replica* replica) override; - void OnSendReplicaEnd(GridMate::Replica* replica, const void* data, size_t len) override; + /*void OnSendReplicaBegin(AZ::EntityId entityId) override; + void OnSendReplicaEnd(AZ::EntityId entityId, const void* data, size_t len) override; void OnSendReplicaChunkEnd(GridMate::ReplicaChunkBase* chunk, AZ::u32 chunkIndex, const void* data, size_t len) override; void OnSendDataSet(GridMate::ReplicaChunkBase* chunk, AZ::u32 chunkIndex, GridMate::DataSetBase* dataSet, GridMate::PeerId from, GridMate::PeerId to, const void* data, size_t len) override; - void OnSendRpc(GridMate::ReplicaChunkBase* chunk, AZ::u32 chunkIndex, GridMate::Internal::RpcRequest* rpc, GridMate::PeerId from, GridMate::PeerId to, const void* data, size_t len) override; + void OnSendRpc(GridMate::ReplicaChunkBase* chunk, AZ::u32 chunkIndex, GridMate::Internal::RpcRequest* rpc, GridMate::PeerId from, GridMate::PeerId to, const void* data, size_t len) override;*/ - // CarrierDrillerBus - void OnIncomingConnection (GridMate::Carrier* carrier, GridMate::ConnectionID id) override; - void OnFailedToConnect (GridMate::Carrier* carrier, - GridMate::ConnectionID id, - GridMate::CarrierDisconnectReason reason) override; - void OnConnectionEstablished (GridMate::Carrier* carrier, GridMate::ConnectionID id) override; - void OnDisconnect (GridMate::Carrier* carrier, - GridMate::ConnectionID id, - GridMate::CarrierDisconnectReason reason) override; - void OnDriverError (GridMate::Carrier* carrier, - GridMate::ConnectionID id, - const GridMate::DriverError& error) override; - void OnSecurityError (GridMate::Carrier* carrier, - GridMate::ConnectionID id, - const GridMate::SecurityError& error) override; - void OnUpdateStatistics (const GridMate::string& address, - const GridMate::TrafficControl::Statistics& lastSecond, - const GridMate::TrafficControl::Statistics& lifeTime, - const GridMate::TrafficControl::Statistics& effectiveLastSecond, - const GridMate::TrafficControl::Statistics& effectiveLifeTime) override; - void OnConnectionStateChanged (GridMate::Carrier* carrier, - GridMate::ConnectionID id, - GridMate::Carrier::ConnectionStates newState) override; + //// CarrierDrillerBus + //void OnIncomingConnection (GridMate::Carrier* carrier, GridMate::ConnectionID id) override; + //void OnFailedToConnect (GridMate::Carrier* carrier, + // GridMate::ConnectionID id, + // GridMate::CarrierDisconnectReason reason) override; + //void OnConnectionEstablished (GridMate::Carrier* carrier, GridMate::ConnectionID id) override; + //void OnDisconnect (GridMate::Carrier* carrier, + // GridMate::ConnectionID id, + // GridMate::CarrierDisconnectReason reason) override; + //void OnDriverError (GridMate::Carrier* carrier, + // GridMate::ConnectionID id, + // const GridMate::DriverError& error) override; + //void OnSecurityError (GridMate::Carrier* carrier, + // GridMate::ConnectionID id, + // const GridMate::SecurityError& error) override; + //void OnUpdateStatistics (const GridMate::string& address, + // const GridMate::TrafficControl::Statistics& lastSecond, + // const GridMate::TrafficControl::Statistics& lifeTime, + // const GridMate::TrafficControl::Statistics& effectiveLastSecond, + // const GridMate::TrafficControl::Statistics& effectiveLifeTime) override; + //void OnConnectionStateChanged (GridMate::Carrier* carrier, + // GridMate::ConnectionID id, + // GridMate::Carrier::ConnectionStates newState) override; private: diff --git a/Gems/Multiplayer/Code/Source/MultiplayerStats.cpp b/Gems/Multiplayer/Code/Source/MultiplayerStats.cpp index 6ee9f09cf7..b3236fbf77 100644 --- a/Gems/Multiplayer/Code/Source/MultiplayerStats.cpp +++ b/Gems/Multiplayer/Code/Source/MultiplayerStats.cpp @@ -6,6 +6,7 @@ * */ +#include #include namespace Multiplayer @@ -29,8 +30,29 @@ namespace Multiplayer m_componentStats[netComponentIndex].m_rpcsRecv.resize(rpcCount); } + void MultiplayerStats::RecordEntitySerializeStart(AZ::EntityId entityId, const char* entityName) + { + if (auto* perEntityStats = AZ::Interface::Get()) + { + perEntityStats->RecordEntitySerializeStart(entityId, entityName); + } + } + + void MultiplayerStats::RecordEntitySerializeStop(AZ::EntityId entityId, const char* entityName) + { + if (auto* perEntityStats = AZ::Interface::Get()) + { + perEntityStats->RecordEntitySerializeStop(entityId, entityName); + } + } + void MultiplayerStats::RecordPropertySent(NetComponentId netComponentId, PropertyIndex propertyId, uint32_t totalBytes) { + if (auto* perEntityStats = AZ::Interface::Get()) + { + perEntityStats->RecordPropertySent(netComponentId, propertyId, totalBytes); + } + const uint16_t netComponentIndex = aznumeric_cast(netComponentId); const uint16_t propertyIndex = aznumeric_cast(propertyId); m_componentStats[netComponentIndex].m_propertyUpdatesSent[propertyIndex].m_totalCalls++; @@ -41,6 +63,11 @@ namespace Multiplayer void MultiplayerStats::RecordPropertyReceived(NetComponentId netComponentId, PropertyIndex propertyId, uint32_t totalBytes) { + if (auto* perEntityStats = AZ::Interface::Get()) + { + perEntityStats->RecordPropertyReceived(netComponentId, propertyId, totalBytes); + } + const uint16_t netComponentIndex = aznumeric_cast(netComponentId); const uint16_t propertyIndex = aznumeric_cast(propertyId); m_componentStats[netComponentIndex].m_propertyUpdatesRecv[propertyIndex].m_totalCalls++; diff --git a/Gems/Multiplayer/Code/multiplayer_debug_files.cmake b/Gems/Multiplayer/Code/multiplayer_debug_files.cmake index 37a1c91640..d333269a1d 100644 --- a/Gems/Multiplayer/Code/multiplayer_debug_files.cmake +++ b/Gems/Multiplayer/Code/multiplayer_debug_files.cmake @@ -9,6 +9,7 @@ set(FILES Source/Debug/MultiplayerDebugByteReporter.cpp Source/Debug/MultiplayerDebugByteReporter.h + Source/Debug/MultiplayerDebugPerEntityInterface.h Source/Debug/MultiplayerDebugPerEntityReporter.cpp Source/Debug/MultiplayerDebugPerEntityReporter.h Source/Debug/MultiplayerDebugModule.cpp From a2f3066fa36d0cb1e817f6a7517f067854e11ba9 Mon Sep 17 00:00:00 2001 From: AMZN-Olex <5432499+AMZN-Olex@users.noreply.github.com> Date: Mon, 26 Jul 2021 21:59:19 -0400 Subject: [PATCH 03/37] Imgui per entity works Signed-off-by: AMZN-Olex <5432499+AMZN-Olex@users.noreply.github.com> --- Gems/Multiplayer/Code/CMakeLists.txt | 2 +- .../Components/MultiplayerComponent.h | 5 +- .../Include/Multiplayer/MultiplayerStats.h | 5 +- .../Source/Components/NetBindComponent.cpp | 7 +- .../Debug/MultiplayerDebugByteReporter.cpp | 2 + .../Debug/MultiplayerDebugByteReporter.h | 17 +- .../MultiplayerDebugPerEntityInterface.h | 14 +- .../MultiplayerDebugPerEntityReporter.cpp | 337 ++++-------------- .../Debug/MultiplayerDebugPerEntityReporter.h | 57 +-- .../Debug/MultiplayerDebugSystemComponent.cpp | 14 +- .../Debug/MultiplayerDebugSystemComponent.h | 1 + .../Code/Source/MultiplayerStats.cpp | 29 +- .../EntityReplication/EntityReplicator.cpp | 9 +- 13 files changed, 155 insertions(+), 344 deletions(-) diff --git a/Gems/Multiplayer/Code/CMakeLists.txt b/Gems/Multiplayer/Code/CMakeLists.txt index 96527fbfc4..e8b38c8799 100644 --- a/Gems/Multiplayer/Code/CMakeLists.txt +++ b/Gems/Multiplayer/Code/CMakeLists.txt @@ -79,7 +79,7 @@ ly_add_target( # The "Multiplayer" target is used by clients and servers, Debug is used only on clients. ly_create_alias(NAME Multiplayer.Clients NAMESPACE Gem TARGETS Gem::Multiplayer Gem::Multiplayer.Debug) -ly_create_alias(NAME Multiplayer.Servers NAMESPACE Gem TARGETS Gem::Multiplayer) +ly_create_alias(NAME Multiplayer.Servers NAMESPACE Gem TARGETS Gem::Multiplayer Gem::Multiplayer.Debug) if (PAL_TRAIT_BUILD_HOST_TOOLS) ly_add_target( diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/Components/MultiplayerComponent.h b/Gems/Multiplayer/Code/Include/Multiplayer/Components/MultiplayerComponent.h index 48590e5393..64ceb6e16f 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/Components/MultiplayerComponent.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/Components/MultiplayerComponent.h @@ -111,7 +111,6 @@ namespace Multiplayer int32_t bitIndex, TYPE& value, const char* name, - AZ::EntityId entityId, NetComponentId componentId, PropertyIndex propertyIndex, MultiplayerStats& stats @@ -134,11 +133,11 @@ namespace Multiplayer { if (modifyRecord) { - stats.RecordPropertyReceived(entityId, componentId, propertyIndex, updateSize); + stats.RecordPropertyReceived(componentId, propertyIndex, updateSize); } else { - stats.RecordPropertySent(entityId, componentId, propertyIndex, updateSize); + stats.RecordPropertySent(componentId, propertyIndex, updateSize); } } } diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/MultiplayerStats.h b/Gems/Multiplayer/Code/Include/Multiplayer/MultiplayerStats.h index 133cbb7aeb..1299fe174d 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/MultiplayerStats.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/MultiplayerStats.h @@ -50,8 +50,9 @@ namespace Multiplayer AZStd::vector m_componentStats; void ReserveComponentStats(NetComponentId netComponentId, uint16_t propertyCount, uint16_t rpcCount); - void RecordEntitySerializeStart(AZ::EntityId entityId, const char* entityName); - void RecordEntitySerializeStop(AZ::EntityId entityId, const char* entityName); + void RecordEntitySerializeStart(AzNetworking::SerializerMode mode, AZ::EntityId entityId, const char* entityName); + void RecordComponentSerializeEnd(AzNetworking::SerializerMode mode, NetComponentId netComponentId); + void RecordEntitySerializeStop(AzNetworking::SerializerMode mode, AZ::EntityId entityId, const char* entityName); void RecordPropertySent(NetComponentId netComponentId, PropertyIndex propertyId, uint32_t totalBytes); void RecordPropertyReceived(NetComponentId netComponentId, PropertyIndex propertyId, uint32_t totalBytes); void RecordRpcSent(NetComponentId netComponentId, RpcIndex rpcId, uint32_t totalBytes); diff --git a/Gems/Multiplayer/Code/Source/Components/NetBindComponent.cpp b/Gems/Multiplayer/Code/Source/Components/NetBindComponent.cpp index 74091908e2..cfaeaedeff 100644 --- a/Gems/Multiplayer/Code/Source/Components/NetBindComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Components/NetBindComponent.cpp @@ -447,15 +447,18 @@ namespace Multiplayer bool NetBindComponent::SerializeStateDeltaMessage(ReplicationRecord& replicationRecord, AzNetworking::ISerializer& serializer) { - GetMultiplayer()->GetStats().RecordEntitySerializeStart(GetEntityId(), GetEntity()->GetName().c_str()); + auto& stats = GetMultiplayer()->GetStats(); + stats.RecordEntitySerializeStart(serializer.GetSerializerMode(), GetEntityId(), GetEntity()->GetName().c_str()); bool success = true; for (auto iter = m_multiplayerSerializationComponentVector.begin(); iter != m_multiplayerSerializationComponentVector.end(); ++iter) { success &= (*iter)->SerializeStateDeltaMessage(replicationRecord, serializer); + + stats.RecordComponentSerializeEnd(serializer.GetSerializerMode(), (*iter)->GetNetComponentId()); } - GetMultiplayer()->GetStats().RecordEntitySerializeStop(GetEntityId(), GetEntity()->GetName().c_str()); + stats.RecordEntitySerializeStop(serializer.GetSerializerMode(), GetEntityId(), GetEntity()->GetName().c_str()); return success; } diff --git a/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugByteReporter.cpp b/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugByteReporter.cpp index fd31298ad8..2c0a8b5db1 100644 --- a/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugByteReporter.cpp +++ b/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugByteReporter.cpp @@ -12,6 +12,8 @@ #include #include +#pragma optimize("", off) + namespace MultiplayerDiagnostics { void MultiplayerDebugByteReporter::ReportBytes(size_t byteSize) diff --git a/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugByteReporter.h b/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugByteReporter.h index 7e6a04569d..8977c2e6ba 100644 --- a/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugByteReporter.h +++ b/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugByteReporter.h @@ -1,14 +1,11 @@ /* -* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -* its licensors. -* -* For complete copyright and license terms please see the LICENSE at the root of this -* distribution (the "License"). All use of this software is governed by the License, -* or, if provided, by the license below or the license accompanying this file. Do not -* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* -*/ + * 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 + * + */ + #pragma once #include diff --git a/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugPerEntityInterface.h b/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugPerEntityInterface.h index 8cd94944b2..94bc11dafa 100644 --- a/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugPerEntityInterface.h +++ b/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugPerEntityInterface.h @@ -13,14 +13,16 @@ namespace MultiplayerDiagnostics { - class MultilayerIPerEntityStats + class MultiplayerIPerEntityStats { public: - virtual ~MultilayerIPerEntityStats(); + AZ_RTTI(MultiplayerIPerEntityStats, "{91A1E4F0-8AE6-44B2-89DF-DA34134C408A}"); - virtual void RecordEntitySerializeStart(AZ::EntityId entityId, const char* entityName) = 0; - virtual void RecordEntitySerializeStop(AZ::EntityId entityId, const char* entityName) = 0; - virtual void RecordPropertySent(Multiplayer::NetComponentId netComponentId, Multiplayer::PropertyIndex propertyId, uint32_t totalBytes); - virtual void RecordPropertyReceived(Multiplayer::NetComponentId netComponentId, Multiplayer::PropertyIndex propertyId, uint32_t totalBytes); + virtual void RecordEntitySerializeStart(AzNetworking::SerializerMode mode, AZ::EntityId entityId, const char* entityName) = 0; + virtual void RecordComponentSerializeEnd(AzNetworking::SerializerMode mode, Multiplayer::NetComponentId netComponentId) = 0; + virtual void RecordEntitySerializeStop(AzNetworking::SerializerMode mode, AZ::EntityId entityId, const char* entityName) = 0; + virtual void RecordPropertySent(Multiplayer::NetComponentId netComponentId, Multiplayer::PropertyIndex propertyId, uint32_t totalBytes) = 0; + virtual void RecordPropertyReceived(Multiplayer::NetComponentId netComponentId, Multiplayer::PropertyIndex propertyId, uint32_t totalBytes) = 0; + virtual void RecordRpcSent(Multiplayer::NetComponentId netComponentId, Multiplayer::RpcIndex rpcId, uint32_t totalBytes) = 0; }; } diff --git a/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugPerEntityReporter.cpp b/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugPerEntityReporter.cpp index 32ed7d5a59..c839083f63 100644 --- a/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugPerEntityReporter.cpp +++ b/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugPerEntityReporter.cpp @@ -7,23 +7,22 @@ */ #include "MultiplayerDebugPerEntityReporter.h" -#include -#include -#include #include #if defined(IMGUI_ENABLED) #include #endif +#pragma optimize("", off) + namespace MultiplayerDiagnostics { #if defined(IMGUI_ENABLED) static const ImVec4 k_ImGuiTomato = ImVec4(1.0f, 0.4f, 0.3f, 1.0f); - static const ImVec4 k_ImGuiKhaki = ImVec4(0.9f, 0.8f, 0.5f, 1.0f); - static const ImVec4 k_ImGuiCyan = ImVec4(0.5f, 1.0f, 1.0f, 1.0f); - static const ImVec4 k_ImGuiDusk = ImVec4(0.7f, 0.7f, 1.0f, 1.0f); - static const ImVec4 k_ImGuiWhite = ImVec4(1.0f, 1.0f, 1.0f, 1.0f); + static const ImVec4 k_ImGuiKhaki = ImVec4(0.9f, 0.8f, 0.5f, 1.0f); + static const ImVec4 k_ImGuiCyan = ImVec4(0.5f, 1.0f, 1.0f, 1.0f); + static const ImVec4 k_ImGuiDusk = ImVec4(0.7f, 0.7f, 1.0f, 1.0f); + static const ImVec4 k_ImGuiWhite = ImVec4(1.0f, 1.0f, 1.0f, 1.0f); // -------------------------------------------------------------------------------------------- template @@ -111,291 +110,103 @@ namespace MultiplayerDiagnostics } #endif - MultiplayerDebugPerEntityReporter::MultiplayerDebugPerEntityReporter () - { - //GridMate::Debug::CarrierDrillerBus::Handler::BusConnect(); - } - - // -------------------------------------------------------------------------------------------- - MultiplayerDebugPerEntityReporter::~MultiplayerDebugPerEntityReporter() - { - /*GridMate::Debug::ReplicaDrillerBus::Handler::BusDisconnect(); - GridMate::Debug::CarrierDrillerBus::Handler::BusDisconnect();*/ - } - - //void MultiplayerDebugPerEntityReporter::OnReceiveReplicaBegin(GridMate::Replica*, const void*, size_t) - //{ - // m_currentReceivingEntityReport.Reset(); - //} - - //void MultiplayerDebugPerEntityReporter::OnReceiveReplicaEnd(GridMate::Replica* replica) - //{ - // m_receivingEntityReports[replica->GetDebugName()].Combine(m_currentReceivingEntityReport); - //} - - //void MultiplayerDebugPerEntityReporter::OnReceiveReplicaChunkEnd(GridMate::ReplicaChunkBase* chunk, AZ::u32 chunkIndex) - //{ - // AZ_UNUSED(chunk); - // AZ_UNUSED(chunkIndex); - // m_currentReceivingEntityReport.ReportFragmentEnd(); - //} - - //void MultiplayerDebugPerEntityReporter::OnReceiveDataSet(GridMate::ReplicaChunkBase* chunk, AZ::u32 chunkIndex, - // GridMate::DataSetBase* dataSet, GridMate::PeerId, GridMate::PeerId, const void*, size_t len) - //{ - // m_currentReceivingEntityReport.ReportField(chunkIndex, chunk->GetDescriptor()->GetChunkName(), chunk->GetDescriptor()->GetDataSetName(chunk, dataSet), len); - //} - - //void MultiplayerDebugPerEntityReporter::OnReceiveRpc (GridMate::ReplicaChunkBase* chunk, - // AZ::u32 chunkIndex, - // GridMate::Internal::RpcRequest* rpc, - // GridMate::PeerId from, - // GridMate::PeerId to, - // const void* data, - // size_t len) - //{ - // AZ_UNUSED( from ); - // AZ_UNUSED( to ); - // AZ_UNUSED( data ); - - // m_currentReceivingEntityReport.ReportField(chunkIndex, chunk->GetDescriptor()->GetChunkName(), chunk->GetDescriptor()->GetRpcName(chunk, rpc->m_rpc), len); - //} - - //void MultiplayerDebugPerEntityReporter::OnSendReplicaBegin (GridMate::Replica*) - //{ - // m_currentSendingEntityReport.Reset(); - //} - - //void MultiplayerDebugPerEntityReporter::OnSendReplicaEnd (GridMate::Replica* replica, const void*, size_t) - //{ - // m_sendingEntityReports[replica->GetDebugName()].Combine(m_currentSendingEntityReport); - //} - - //void MultiplayerDebugPerEntityReporter::OnSendReplicaChunkEnd (GridMate::ReplicaChunkBase* chunk, - // AZ::u32 chunkIndex, - // const void*, - // size_t) - //{ - // AZ_UNUSED(chunk); - // AZ_UNUSED(chunkIndex); - // m_currentSendingEntityReport.ReportFragmentEnd(); - //} - - //void MultiplayerDebugPerEntityReporter::OnSendDataSet (GridMate::ReplicaChunkBase* chunk, - // AZ::u32 chunkIndex, - // GridMate::DataSetBase* dataSet, - // GridMate::PeerId, - // GridMate::PeerId, - // const void*, - // size_t len) - //{ - // m_currentSendingEntityReport.ReportField(chunkIndex, chunk->GetDescriptor()->GetChunkName(), chunk->GetDescriptor()->GetDataSetName(chunk, dataSet), len); - //} - - //void MultiplayerDebugPerEntityReporter::OnSendRpc (GridMate::ReplicaChunkBase* chunk, - // AZ::u32 chunkIndex, - // GridMate::Internal::RpcRequest* rpc, - // GridMate::PeerId, - // GridMate::PeerId, - // const void*, - // size_t len) - //{ - // m_currentSendingEntityReport.ReportField(chunkIndex, chunk->GetDescriptor()->GetChunkName(), chunk->GetDescriptor()->GetRpcName(chunk, rpc->m_rpc), len); - //} - - //void MultiplayerDebugPerEntityReporter::OnIncomingConnection (GridMate::Carrier*, GridMate::ConnectionID) - //{ - //} - - //void MultiplayerDebugPerEntityReporter::OnFailedToConnect (GridMate::Carrier*, - // GridMate::ConnectionID, - // GridMate::CarrierDisconnectReason) - //{ - // m_lastSecondStats.clear(); - //} - - //void MultiplayerDebugPerEntityReporter::OnConnectionEstablished (GridMate::Carrier*, GridMate::ConnectionID) - //{ - //} - - //void MultiplayerDebugPerEntityReporter::OnDisconnect (GridMate::Carrier*, - // GridMate::ConnectionID, - // GridMate::CarrierDisconnectReason) - //{ - // /* - // * CarrierDrillerBus doesn't provide enough information to correctly keep track of network traffic for all peers. - // * This is a work around until that is fixed to at least not over report the bandwidth amount. - // */ - // m_lastSecondStats.clear(); - //} - - //void MultiplayerDebugPerEntityReporter::OnDriverError (GridMate::Carrier*, - // GridMate::ConnectionID, - // const GridMate::DriverError&) - //{ - // m_lastSecondStats.clear(); - //} - - //void MultiplayerDebugPerEntityReporter::OnSecurityError (GridMate::Carrier*, - // GridMate::ConnectionID, - // const GridMate::SecurityError&) - //{ - // m_lastSecondStats.clear(); - //} - - //void MultiplayerDebugPerEntityReporter::OnUpdateStatistics (const GridMate::string& address, - // const GridMate::TrafficControl::Statistics&, - // const GridMate::TrafficControl::Statistics&, - // const GridMate::TrafficControl::Statistics& effectiveLastSecond, - // const GridMate::TrafficControl::Statistics&) - //{ - // m_lastSecondStats[address] = effectiveLastSecond; - //} - - //void MultiplayerDebugPerEntityReporter::OnConnectionStateChanged (GridMate::Carrier*, - // GridMate::ConnectionID, - // GridMate::Carrier::ConnectionStates) - //{ - // m_lastSecondStats.clear(); - //} - - void MultiplayerDebugPerEntityReporter::UpdateTrafficStatistics() - { -#if defined(IMGUI_ENABLED) - AZ::u32 dataReceived = 0, dataSent = 0; - - for (auto& perConnection : m_lastSecondStats) - { - dataReceived += perConnection.second.m_dataReceived; - dataSent += perConnection.second.m_dataSend; - } - - if (dataReceived !=0 || dataSent != 0) - { - ImGui::Text("Total bandwidth: Sent %u kbps Received %u kbps.", dataSent * 8 / 1000, dataReceived * 8 / 1000); - } - else - { - ImGui::Text("Total bandwidth: Sent -- kbps Received -- kbps."); - } -#endif - } - // -------------------------------------------------------------------------------------------- void MultiplayerDebugPerEntityReporter::OnImGuiUpdate() { #if defined(IMGUI_ENABLED) - if (ImGui::BeginMainMenuBar()) + static ImGuiTextFilter filter; + filter.Draw(); + + if (ImGui::CollapsingHeader("Receiving Entities")) { - if (ImGui::BeginMenu("GridMate")) + for (auto& entityPair : m_receivingEntityReports) { - if (m_showServerReportWindow) + if (!filter.PassFilter(entityPair.first.c_str())) { - if (ImGui::MenuItem("Hide Multiplayer Analytics Window")) - { - m_showServerReportWindow = false; - } - } - else if (ImGui::MenuItem("Show Multiplayer Analytics Window")) - { - m_showServerReportWindow = true; + continue; } - ImGui::End(); + ImGui::Separator(); + if (ReplicatedStateTreeNode(entityPair.first, entityPair.second, k_ImGuiDusk)) + { + DisplayReplicatedStateReport(entityPair.second.GetComponentReports(), m_replicatedStateKbpsWarn, m_replicatedStateMaxSizeWarn); + ImGui::TreePop(); + } } - - ImGui::EndMainMenuBar(); } - if (m_showServerReportWindow) + if (ImGui::CollapsingHeader("Sending Entities")) { - if (ImGui::Begin("Multiplayer Analytics", &m_showServerReportWindow)) + for (auto& entityPair : m_sendingEntityReports) { - // General carrier stats - UpdateTrafficStatistics(); - - if (ImGui::Checkbox("Analyze network traffic", &m_isTrackingMessages)) + if (!filter.PassFilter(entityPair.first.c_str())) { - if (m_isTrackingMessages) - { - //GridMate::Debug::ReplicaDrillerBus::Handler::BusConnect(); - } - else - { - //GridMate::Debug::ReplicaDrillerBus::Handler::BusDisconnect(); - - m_currentReceivingEntityReport.Reset(); - m_receivingEntityReports.clear(); - - m_currentSendingEntityReport.Reset(); - m_sendingEntityReports.clear(); - } + continue; } - if (m_isTrackingMessages) + ImGui::Separator(); + if (ReplicatedStateTreeNode(entityPair.first, entityPair.second, k_ImGuiDusk)) { - ImGui::Separator(); - - static ImGuiTextFilter filter; - filter.Draw(); - - if (ImGui::CollapsingHeader("Received replicas per type")) - { - for (auto& entityPair : m_receivingEntityReports) - { - if (!filter.PassFilter(entityPair.first.c_str())) - { - continue; - } - - ImGui::Separator(); - if (ReplicatedStateTreeNode(entityPair.first, entityPair.second, k_ImGuiDusk)) - { - DisplayReplicatedStateReport(entityPair.second.GetComponentReports(), m_replicatedStateKbpsWarn, m_replicatedStateMaxSizeWarn); - ImGui::TreePop(); - } - } - } - - if (ImGui::CollapsingHeader("Sent replicas per type")) - { - for (auto& entityPair : m_sendingEntityReports) - { - if (!filter.PassFilter(entityPair.first.c_str())) - { - continue; - } - - ImGui::Separator(); - if (ReplicatedStateTreeNode(entityPair.first, entityPair.second, k_ImGuiDusk)) - { - DisplayReplicatedStateReport(entityPair.second.GetComponentReports(), m_replicatedStateKbpsWarn, m_replicatedStateMaxSizeWarn); - ImGui::TreePop(); - } - } - } + DisplayReplicatedStateReport(entityPair.second.GetComponentReports(), m_replicatedStateKbpsWarn, m_replicatedStateMaxSizeWarn); + ImGui::TreePop(); } } - ImGui::End(); } #endif } - void MultiplayerDebugPerEntityReporter::RecordEntitySerializeStart(AZ::EntityId entityId, const char* entityName) + void MultiplayerDebugPerEntityReporter::RecordEntitySerializeStart(AzNetworking::SerializerMode mode, + [[maybe_unused]] AZ::EntityId entityId, [[maybe_unused]] const char* entityName) { + switch (mode) + { + case AzNetworking::SerializerMode::ReadFromObject: + m_currentSendingEntityReport.Reset(); + break; + case AzNetworking::SerializerMode::WriteToObject: + m_currentReceivingEntityReport.Reset(); + break; + } } - void MultiplayerDebugPerEntityReporter::RecordEntitySerializeStop(AZ::EntityId entityId, const char* entityName) + void MultiplayerDebugPerEntityReporter::RecordComponentSerializeEnd(AzNetworking::SerializerMode mode, [[maybe_unused]] Multiplayer::NetComponentId netComponentId) { + switch (mode) + { + case AzNetworking::SerializerMode::ReadFromObject: + m_currentSendingEntityReport.ReportFragmentEnd(); + break; + case AzNetworking::SerializerMode::WriteToObject: + m_currentReceivingEntityReport.ReportFragmentEnd(); + break; + } + } + + void MultiplayerDebugPerEntityReporter::RecordEntitySerializeStop(AzNetworking::SerializerMode mode, + [[maybe_unused]] AZ::EntityId entityId, const char* entityName) + { + switch (mode) + { + case AzNetworking::SerializerMode::ReadFromObject: + m_sendingEntityReports[entityName].Combine(m_currentSendingEntityReport); + break; + case AzNetworking::SerializerMode::WriteToObject: + m_receivingEntityReports[entityName].Combine(m_currentReceivingEntityReport); + break; + } } void MultiplayerDebugPerEntityReporter::RecordPropertySent( - AZ::EntityId entityId, Multiplayer::NetComponentId netComponentId, Multiplayer::PropertyIndex propertyId, uint32_t totalBytes) { - // TODO + if (const Multiplayer::MultiplayerComponentRegistry* componentRegistry = Multiplayer::GetMultiplayerComponentRegistry()) + { + m_currentSendingEntityReport.ReportField(static_cast(netComponentId), + componentRegistry->GetComponentName(netComponentId), + componentRegistry->GetComponentPropertyName(netComponentId, propertyId), totalBytes); + } } void MultiplayerDebugPerEntityReporter::RecordPropertyReceived( @@ -403,11 +214,21 @@ namespace MultiplayerDiagnostics Multiplayer::PropertyIndex propertyId, uint32_t totalBytes) { - if (Multiplayer::MultiplayerComponentRegistry* componentRegistry = Multiplayer::GetMultiplayerComponentRegistry()) + if (const Multiplayer::MultiplayerComponentRegistry* componentRegistry = Multiplayer::GetMultiplayerComponentRegistry()) { m_currentReceivingEntityReport.ReportField(static_cast(netComponentId), componentRegistry->GetComponentName(netComponentId), componentRegistry->GetComponentPropertyName(netComponentId, propertyId), totalBytes); } } + + void MultiplayerDebugPerEntityReporter::RecordRpcSent(Multiplayer::NetComponentId netComponentId, Multiplayer::RpcIndex rpcId, uint32_t totalBytes) + { + if (const Multiplayer::MultiplayerComponentRegistry* componentRegistry = Multiplayer::GetMultiplayerComponentRegistry()) + { + m_currentSendingEntityReport.ReportField(static_cast(netComponentId), + componentRegistry->GetComponentName(netComponentId), + componentRegistry->GetComponentRpcName(netComponentId, rpcId), totalBytes); + } + } } diff --git a/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugPerEntityReporter.h b/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugPerEntityReporter.h index c89c889813..121c4131ed 100644 --- a/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugPerEntityReporter.h +++ b/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugPerEntityReporter.h @@ -7,7 +7,6 @@ */ #pragma once -#include #include "MultiplayerDebugByteReporter.h" #include @@ -22,66 +21,27 @@ namespace MultiplayerDiagnostics * \brief GridMate network live analysis tool via ImGui. */ class MultiplayerDebugPerEntityReporter - : public AZ::Interface::Registrar + : public AZ::Interface::Registrar { public: - MultiplayerDebugPerEntityReporter(); - ~MultiplayerDebugPerEntityReporter() override; + MultiplayerDebugPerEntityReporter() = default; + ~MultiplayerDebugPerEntityReporter() override = default; // main update loop void OnImGuiUpdate(); //! MultilayerIPerEntityStats // @{ - void RecordEntitySerializeStart(AZ::EntityId entityId, const char* entityName); - void RecordEntitySerializeStop(AZ::EntityId entityId, const char* entityName); + void RecordEntitySerializeStart(AzNetworking::SerializerMode mode, AZ::EntityId entityId, const char* entityName) override; + void RecordComponentSerializeEnd(AzNetworking::SerializerMode mode, Multiplayer::NetComponentId netComponentId) override; + void RecordEntitySerializeStop(AzNetworking::SerializerMode mode, AZ::EntityId entityId, const char* entityName) override; void RecordPropertySent(Multiplayer::NetComponentId netComponentId, Multiplayer::PropertyIndex propertyId, uint32_t totalBytes) override; void RecordPropertyReceived(Multiplayer::NetComponentId netComponentId, Multiplayer::PropertyIndex propertyId, uint32_t totalBytes) override; + void RecordRpcSent(Multiplayer::NetComponentId netComponentId, Multiplayer::RpcIndex rpcId, uint32_t totalBytes) override; // }@ - /*void OnReceiveReplicaBegin(AZ::EntityId entityId, const void* data, size_t len) override; - void OnReceiveReplicaEnd(AZ::EntityId entityId) override; - void OnReceiveReplicaChunkEnd(GridMate::ReplicaChunkBase* chunk, AZ::u32 chunkIndex) override; - void OnReceiveDataSet(GridMate::ReplicaChunkBase* chunk, AZ::u32 chunkIndex, GridMate::DataSetBase* dataSet, GridMate::PeerId from, GridMate::PeerId to, const void* data, size_t len) override; - void OnReceiveRpc(GridMate::ReplicaChunkBase* chunk, AZ::u32 chunkIndex, GridMate::Internal::RpcRequest* rpc, GridMate::PeerId from, GridMate::PeerId to, const void* data, size_t len) override;*/ - - // ReplicaDrillerBus - sending - - /*void OnSendReplicaBegin(AZ::EntityId entityId) override; - void OnSendReplicaEnd(AZ::EntityId entityId, const void* data, size_t len) override; - void OnSendReplicaChunkEnd(GridMate::ReplicaChunkBase* chunk, AZ::u32 chunkIndex, const void* data, size_t len) override; - void OnSendDataSet(GridMate::ReplicaChunkBase* chunk, AZ::u32 chunkIndex, GridMate::DataSetBase* dataSet, GridMate::PeerId from, GridMate::PeerId to, const void* data, size_t len) override; - void OnSendRpc(GridMate::ReplicaChunkBase* chunk, AZ::u32 chunkIndex, GridMate::Internal::RpcRequest* rpc, GridMate::PeerId from, GridMate::PeerId to, const void* data, size_t len) override;*/ - - //// CarrierDrillerBus - //void OnIncomingConnection (GridMate::Carrier* carrier, GridMate::ConnectionID id) override; - //void OnFailedToConnect (GridMate::Carrier* carrier, - // GridMate::ConnectionID id, - // GridMate::CarrierDisconnectReason reason) override; - //void OnConnectionEstablished (GridMate::Carrier* carrier, GridMate::ConnectionID id) override; - //void OnDisconnect (GridMate::Carrier* carrier, - // GridMate::ConnectionID id, - // GridMate::CarrierDisconnectReason reason) override; - //void OnDriverError (GridMate::Carrier* carrier, - // GridMate::ConnectionID id, - // const GridMate::DriverError& error) override; - //void OnSecurityError (GridMate::Carrier* carrier, - // GridMate::ConnectionID id, - // const GridMate::SecurityError& error) override; - //void OnUpdateStatistics (const GridMate::string& address, - // const GridMate::TrafficControl::Statistics& lastSecond, - // const GridMate::TrafficControl::Statistics& lifeTime, - // const GridMate::TrafficControl::Statistics& effectiveLastSecond, - // const GridMate::TrafficControl::Statistics& effectiveLifeTime) override; - //void OnConnectionStateChanged (GridMate::Carrier* carrier, - // GridMate::ConnectionID id, - // GridMate::Carrier::ConnectionStates newState) override; - private: - bool m_showServerReportWindow = false; - bool m_isTrackingMessages = false; - AZStd::map m_sendingEntityReports{}; EntityReporter m_currentSendingEntityReport; @@ -90,8 +50,5 @@ namespace MultiplayerDiagnostics float m_replicatedStateKbpsWarn = 10.f; float m_replicatedStateMaxSizeWarn = 30.f; - - void UpdateTrafficStatistics(); - AZStd::map m_lastSecondStats; }; } diff --git a/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugSystemComponent.cpp b/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugSystemComponent.cpp index b97fa053b2..73afc45271 100644 --- a/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugSystemComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugSystemComponent.cpp @@ -13,6 +13,8 @@ #include #include +#pragma optimize("", off) + namespace Multiplayer { void MultiplayerDebugSystemComponent::Reflect(AZ::ReflectContext* context) @@ -65,6 +67,7 @@ namespace Multiplayer { ImGui::Checkbox("Networking Stats", &m_displayNetworkingStats); ImGui::Checkbox("Multiplayer Stats", &m_displayMultiplayerStats); + ImGui::Checkbox("Multiplayer Per Entity Stats", &m_displayPerEntityStats); ImGui::EndMenu(); } } @@ -324,10 +327,15 @@ namespace Multiplayer } } - - if (m_reporter) + if (m_displayPerEntityStats) { - m_reporter->OnImGuiUpdate(); + if (ImGui::Begin("Multiplayer Per Entity Analytics", &m_displayPerEntityStats)) + { + if (m_reporter) + { + m_reporter->OnImGuiUpdate(); + } + } } } #endif diff --git a/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugSystemComponent.h b/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugSystemComponent.h index 3dbbd06d6f..4972ec6bdf 100644 --- a/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugSystemComponent.h +++ b/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugSystemComponent.h @@ -51,6 +51,7 @@ namespace Multiplayer private: bool m_displayNetworkingStats = false; bool m_displayMultiplayerStats = false; + bool m_displayPerEntityStats = false; AZStd::unique_ptr m_reporter; }; diff --git a/Gems/Multiplayer/Code/Source/MultiplayerStats.cpp b/Gems/Multiplayer/Code/Source/MultiplayerStats.cpp index b3236fbf77..fd1b6a329d 100644 --- a/Gems/Multiplayer/Code/Source/MultiplayerStats.cpp +++ b/Gems/Multiplayer/Code/Source/MultiplayerStats.cpp @@ -30,25 +30,33 @@ namespace Multiplayer m_componentStats[netComponentIndex].m_rpcsRecv.resize(rpcCount); } - void MultiplayerStats::RecordEntitySerializeStart(AZ::EntityId entityId, const char* entityName) + void MultiplayerStats::RecordEntitySerializeStart(AzNetworking::SerializerMode mode, AZ::EntityId entityId, const char* entityName) { - if (auto* perEntityStats = AZ::Interface::Get()) + if (auto* perEntityStats = AZ::Interface::Get()) { - perEntityStats->RecordEntitySerializeStart(entityId, entityName); + perEntityStats->RecordEntitySerializeStart(mode, entityId, entityName); } } - void MultiplayerStats::RecordEntitySerializeStop(AZ::EntityId entityId, const char* entityName) + void MultiplayerStats::RecordComponentSerializeEnd(AzNetworking::SerializerMode mode, NetComponentId netComponentId) { - if (auto* perEntityStats = AZ::Interface::Get()) + if (auto* perEntityStats = AZ::Interface::Get()) { - perEntityStats->RecordEntitySerializeStop(entityId, entityName); + perEntityStats->RecordComponentSerializeEnd(mode, netComponentId); + } + } + + void MultiplayerStats::RecordEntitySerializeStop(AzNetworking::SerializerMode mode, AZ::EntityId entityId, const char* entityName) + { + if (auto* perEntityStats = AZ::Interface::Get()) + { + perEntityStats->RecordEntitySerializeStop(mode, entityId, entityName); } } void MultiplayerStats::RecordPropertySent(NetComponentId netComponentId, PropertyIndex propertyId, uint32_t totalBytes) { - if (auto* perEntityStats = AZ::Interface::Get()) + if (auto* perEntityStats = AZ::Interface::Get()) { perEntityStats->RecordPropertySent(netComponentId, propertyId, totalBytes); } @@ -63,7 +71,7 @@ namespace Multiplayer void MultiplayerStats::RecordPropertyReceived(NetComponentId netComponentId, PropertyIndex propertyId, uint32_t totalBytes) { - if (auto* perEntityStats = AZ::Interface::Get()) + if (auto* perEntityStats = AZ::Interface::Get()) { perEntityStats->RecordPropertyReceived(netComponentId, propertyId, totalBytes); } @@ -78,6 +86,11 @@ namespace Multiplayer void MultiplayerStats::RecordRpcSent(NetComponentId netComponentId, RpcIndex rpcId, uint32_t totalBytes) { + if (auto* perEntityStats = AZ::Interface::Get()) + { + perEntityStats->RecordRpcSent(netComponentId, rpcId, totalBytes); + } + const uint16_t netComponentIndex = aznumeric_cast(netComponentId); const uint16_t rpcIndex = aznumeric_cast(rpcId); m_componentStats[netComponentIndex].m_rpcsSent[rpcIndex].m_totalCalls++; diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicator.cpp b/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicator.cpp index 93fd3ea652..2afadbe4a4 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicator.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicator.cpp @@ -30,6 +30,8 @@ #include +#pragma optimize("", off) + namespace Multiplayer { EntityReplicator::EntityReplicator @@ -441,7 +443,12 @@ namespace Multiplayer { // Received rpc metrics, log rpc sent, number of bytes, and the componentId/rpcId for bandwidth metrics MultiplayerStats& stats = GetMultiplayer()->GetStats(); + stats.RecordEntitySerializeStart(AzNetworking::SerializerMode::ReadFromObject, + GetEntityHandle().GetEntity()->GetId(), GetEntityHandle().GetEntity()->GetName().c_str()); stats.RecordRpcSent(entityRpcMessage.GetComponentId(), entityRpcMessage.GetRpcIndex(), entityRpcMessage.GetEstimatedSerializeSize()); + stats.RecordComponentSerializeEnd(AzNetworking::SerializerMode::ReadFromObject, entityRpcMessage.GetComponentId()); + stats.RecordEntitySerializeStop(AzNetworking::SerializerMode::ReadFromObject, + GetEntityHandle().GetEntity()->GetId(), GetEntityHandle().GetEntity()->GetName().c_str()); m_replicationManager.AddDeferredRpcMessage(entityRpcMessage); } @@ -515,7 +522,7 @@ namespace Multiplayer && (GetRemoteNetworkRole() == NetEntityRole::Server)) { // We are on a server, and we received this message from another server, therefore we should forward this to our autonomous player - // This can occur if we've recently migrated + // This can occur if we've recently migrated result = RpcValidationResult::ForwardToAutonomous; } } From 59f65f265653cc73abeb2db3b94fb8b8bf64a231 Mon Sep 17 00:00:00 2001 From: AMZN-Olex <5432499+AMZN-Olex@users.noreply.github.com> Date: Mon, 26 Jul 2021 23:45:09 -0400 Subject: [PATCH 04/37] Refactored to store entities by entity ids Signed-off-by: AMZN-Olex <5432499+AMZN-Olex@users.noreply.github.com> --- .../Debug/MultiplayerDebugByteReporter.cpp | 20 +----------------- .../Debug/MultiplayerDebugByteReporter.h | 10 +++++++-- .../MultiplayerDebugPerEntityReporter.cpp | 21 +++++++++++-------- .../Debug/MultiplayerDebugPerEntityReporter.h | 4 ++-- 4 files changed, 23 insertions(+), 32 deletions(-) diff --git a/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugByteReporter.cpp b/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugByteReporter.cpp index 2c0a8b5db1..5e912612b7 100644 --- a/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugByteReporter.cpp +++ b/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugByteReporter.cpp @@ -163,25 +163,6 @@ namespace MultiplayerDiagnostics MultiplayerDebugByteReporter::AggregateBytes(byteSize); } - void EntityReporter::ReportDirtyBits(AZ::u32 index, const char* componentName, size_t byteSize) - { - const char* const prefix = "MB::"; - if (strncmp(prefix, componentName, 4) == 0) - { - componentName += strlen(prefix); - } - - if (m_currentComponentReport == nullptr) - { - std::stringstream component; - component << "[" << std::setw(2) << std::setfill('0') << static_cast(index) << "]" << " " << componentName; - m_currentComponentReport = &m_componentReports[component.str().c_str()]; - } - - m_currentComponentReport->ReportDirtyBits(byteSize); - m_gdeDirtyBytes.AggregateBytes(byteSize); - } - void EntityReporter::ReportFragmentEnd() { if (m_currentComponentReport) @@ -203,6 +184,7 @@ namespace MultiplayerDiagnostics m_componentReports[componentIter.first].Combine(componentIter.second); } + SetEntityName(other.GetEntityName()); m_gdeDirtyBytes.Combine(other.m_gdeDirtyBytes); } diff --git a/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugByteReporter.h b/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugByteReporter.h index 8977c2e6ba..3cef9caaac 100644 --- a/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugByteReporter.h +++ b/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugByteReporter.h @@ -54,7 +54,6 @@ namespace MultiplayerDiagnostics ComponentReporter() = default; void ReportField(const char* fieldName, size_t byteSize); - void ReportDirtyBits(size_t byteSize) { m_componentDirtyBytes.AggregateBytes(byteSize); } void ReportFragmentEnd(); using Report = AZStd::pair; @@ -75,12 +74,18 @@ namespace MultiplayerDiagnostics EntityReporter() = default; void ReportField(AZ::u32 index, const char* componentName, const char* fieldName, size_t byteSize); - void ReportDirtyBits(AZ::u32 index, const char* componentName, size_t byteSize); void ReportFragmentEnd(); void Combine(const EntityReporter& other); void Reset() override; + const char* GetEntityName() const { return m_entityName.c_str(); } + void SetEntityName(const char* entityName) + { + // copying because the entity might go away + m_entityName = entityName; + } + AZStd::map& GetComponentReports(); AZStd::size_t GetTotalDirtyBits() const { return m_gdeDirtyBytes.GetTotalBytes(); } float GetAvgDirtyBits() const { return m_gdeDirtyBytes.GetAverageBytes(); } @@ -89,5 +94,6 @@ namespace MultiplayerDiagnostics ComponentReporter* m_currentComponentReport = nullptr; AZStd::map m_componentReports; MultiplayerDebugByteReporter m_gdeDirtyBytes; + AZStd::string m_entityName; }; } diff --git a/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugPerEntityReporter.cpp b/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugPerEntityReporter.cpp index c839083f63..3517dfa86a 100644 --- a/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugPerEntityReporter.cpp +++ b/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugPerEntityReporter.cpp @@ -119,15 +119,15 @@ namespace MultiplayerDiagnostics if (ImGui::CollapsingHeader("Receiving Entities")) { - for (auto& entityPair : m_receivingEntityReports) + for (AZStd::pair& entityPair : m_receivingEntityReports) { - if (!filter.PassFilter(entityPair.first.c_str())) + if (!filter.PassFilter(entityPair.second.GetEntityName())) { continue; } ImGui::Separator(); - if (ReplicatedStateTreeNode(entityPair.first, entityPair.second, k_ImGuiDusk)) + if (ReplicatedStateTreeNode(entityPair.second.GetEntityName(), entityPair.second, k_ImGuiDusk)) { DisplayReplicatedStateReport(entityPair.second.GetComponentReports(), m_replicatedStateKbpsWarn, m_replicatedStateMaxSizeWarn); ImGui::TreePop(); @@ -137,15 +137,16 @@ namespace MultiplayerDiagnostics if (ImGui::CollapsingHeader("Sending Entities")) { - for (auto& entityPair : m_sendingEntityReports) + for (AZStd::pair& entityPair : m_sendingEntityReports) { - if (!filter.PassFilter(entityPair.first.c_str())) + const char* name = entityPair.second.GetEntityName(); + if (!filter.PassFilter(name)) { continue; } ImGui::Separator(); - if (ReplicatedStateTreeNode(entityPair.first, entityPair.second, k_ImGuiDusk)) + if (ReplicatedStateTreeNode(name, entityPair.second, k_ImGuiDusk)) { DisplayReplicatedStateReport(entityPair.second.GetComponentReports(), m_replicatedStateKbpsWarn, m_replicatedStateMaxSizeWarn); ImGui::TreePop(); @@ -162,9 +163,11 @@ namespace MultiplayerDiagnostics { case AzNetworking::SerializerMode::ReadFromObject: m_currentSendingEntityReport.Reset(); + m_currentSendingEntityReport.SetEntityName(entityName); break; case AzNetworking::SerializerMode::WriteToObject: m_currentReceivingEntityReport.Reset(); + m_currentReceivingEntityReport.SetEntityName(entityName); break; } } @@ -183,15 +186,15 @@ namespace MultiplayerDiagnostics } void MultiplayerDebugPerEntityReporter::RecordEntitySerializeStop(AzNetworking::SerializerMode mode, - [[maybe_unused]] AZ::EntityId entityId, const char* entityName) + [[maybe_unused]] AZ::EntityId entityId, [[maybe_unused]] const char* entityName) { switch (mode) { case AzNetworking::SerializerMode::ReadFromObject: - m_sendingEntityReports[entityName].Combine(m_currentSendingEntityReport); + m_sendingEntityReports[entityId].Combine(m_currentSendingEntityReport); break; case AzNetworking::SerializerMode::WriteToObject: - m_receivingEntityReports[entityName].Combine(m_currentReceivingEntityReport); + m_receivingEntityReports[entityId].Combine(m_currentReceivingEntityReport); break; } } diff --git a/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugPerEntityReporter.h b/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugPerEntityReporter.h index 121c4131ed..f34cc2cd35 100644 --- a/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugPerEntityReporter.h +++ b/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugPerEntityReporter.h @@ -42,10 +42,10 @@ namespace MultiplayerDiagnostics private: - AZStd::map m_sendingEntityReports{}; + AZStd::map m_sendingEntityReports{}; EntityReporter m_currentSendingEntityReport; - AZStd::map m_receivingEntityReports{}; + AZStd::map m_receivingEntityReports{}; EntityReporter m_currentReceivingEntityReport; float m_replicatedStateKbpsWarn = 10.f; From 8fa3addda3501ee44308f15e5331b1d27d280565 Mon Sep 17 00:00:00 2001 From: AMZN-Olex <5432499+AMZN-Olex@users.noreply.github.com> Date: Tue, 27 Jul 2021 00:21:40 -0400 Subject: [PATCH 05/37] Added debug overlay for entitie with high network costs Signed-off-by: AMZN-Olex <5432499+AMZN-Olex@users.noreply.github.com> --- .../MultiplayerDebugPerEntityReporter.cpp | 37 +++++++++++++++++++ .../Debug/MultiplayerDebugSystemComponent.cpp | 2 +- 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugPerEntityReporter.cpp b/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugPerEntityReporter.cpp index 3517dfa86a..30e6feb2f4 100644 --- a/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugPerEntityReporter.cpp +++ b/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugPerEntityReporter.cpp @@ -7,6 +7,10 @@ */ #include "MultiplayerDebugPerEntityReporter.h" + +#include +#include +#include #include #if defined(IMGUI_ENABLED) @@ -117,6 +121,17 @@ namespace MultiplayerDiagnostics static ImGuiTextFilter filter; filter.Draw(); + char status[100] = {}; + + struct NetworkEntityTraffic + { + const char* m_name = nullptr; + float m_up = 0.f; + float m_down = 0.f; + }; + + AZStd::fixed_unordered_map networkEntitiesTraffic; + if (ImGui::CollapsingHeader("Receiving Entities")) { for (AZStd::pair& entityPair : m_receivingEntityReports) @@ -132,6 +147,9 @@ namespace MultiplayerDiagnostics DisplayReplicatedStateReport(entityPair.second.GetComponentReports(), m_replicatedStateKbpsWarn, m_replicatedStateMaxSizeWarn); ImGui::TreePop(); } + + networkEntitiesTraffic[entityPair.first].m_name = entityPair.second.GetEntityName(); + networkEntitiesTraffic[entityPair.first].m_down = entityPair.second.GetKbitsPerSecond(); } } @@ -151,8 +169,27 @@ namespace MultiplayerDiagnostics DisplayReplicatedStateReport(entityPair.second.GetComponentReports(), m_replicatedStateKbpsWarn, m_replicatedStateMaxSizeWarn); ImGui::TreePop(); } + + networkEntitiesTraffic[entityPair.first].m_name = entityPair.second.GetEntityName(); + networkEntitiesTraffic[entityPair.first].m_up = entityPair.second.GetKbitsPerSecond(); } } + + constexpr float trafficThreshold = 0.1f; + for (AZStd::pair& networkEntity : networkEntitiesTraffic) + { + if (networkEntity.second.m_down < trafficThreshold && networkEntity.second.m_up < trafficThreshold) + { + continue; + } + + azsprintf(status, "%s - %.0f down / %0.f up (kbps)", networkEntity.second.m_name, networkEntity.second.m_down, networkEntity.second.m_up); + AZ::Vector3 entityPosition = AZ::Vector3::CreateZero(); + constexpr bool centerText = true; + AZ::TransformBus::EventResult(entityPosition, networkEntity.first, &AZ::TransformBus::Events::GetWorldTranslation); + AzFramework::DebugDisplayRequestBus::Broadcast(&AzFramework::DebugDisplayRequestBus::Events::DrawTextLabel, + entityPosition, 1.0f, status, centerText, 0, 0); + } #endif } diff --git a/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugSystemComponent.cpp b/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugSystemComponent.cpp index 73afc45271..44e2f9f6bf 100644 --- a/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugSystemComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugSystemComponent.cpp @@ -329,7 +329,7 @@ namespace Multiplayer if (m_displayPerEntityStats) { - if (ImGui::Begin("Multiplayer Per Entity Analytics", &m_displayPerEntityStats)) + if (ImGui::Begin("Multiplayer Per Entity Analytics", &m_displayPerEntityStats, ImGuiWindowFlags_None)) { if (m_reporter) { From 5ebb096d1a3420281a002f20a86351b8b7d1a690 Mon Sep 17 00:00:00 2001 From: AMZN-Olex <5432499+AMZN-Olex@users.noreply.github.com> Date: Tue, 27 Jul 2021 15:49:41 -0400 Subject: [PATCH 06/37] Debug overlay for kpbs per entity works Signed-off-by: AMZN-Olex <5432499+AMZN-Olex@users.noreply.github.com> --- .../MultiplayerDebugPerEntityInterface.h | 1 + .../MultiplayerDebugPerEntityReporter.cpp | 140 +++++++++++++----- .../Debug/MultiplayerDebugPerEntityReporter.h | 25 +++- .../Debug/MultiplayerDebugSystemComponent.cpp | 5 +- .../Code/Source/MultiplayerStats.cpp | 5 + 5 files changed, 138 insertions(+), 38 deletions(-) diff --git a/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugPerEntityInterface.h b/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugPerEntityInterface.h index 94bc11dafa..4f177affe3 100644 --- a/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugPerEntityInterface.h +++ b/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugPerEntityInterface.h @@ -24,5 +24,6 @@ namespace MultiplayerDiagnostics virtual void RecordPropertySent(Multiplayer::NetComponentId netComponentId, Multiplayer::PropertyIndex propertyId, uint32_t totalBytes) = 0; virtual void RecordPropertyReceived(Multiplayer::NetComponentId netComponentId, Multiplayer::PropertyIndex propertyId, uint32_t totalBytes) = 0; virtual void RecordRpcSent(Multiplayer::NetComponentId netComponentId, Multiplayer::RpcIndex rpcId, uint32_t totalBytes) = 0; + virtual void RecordRpcReceived(Multiplayer::NetComponentId netComponentId, Multiplayer::RpcIndex rpcId, uint32_t totalBytes) = 0; }; } diff --git a/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugPerEntityReporter.cpp b/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugPerEntityReporter.cpp index 30e6feb2f4..3c5495ec6a 100644 --- a/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugPerEntityReporter.cpp +++ b/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugPerEntityReporter.cpp @@ -19,6 +19,21 @@ #pragma optimize("", off) +AZ_CVAR(bool, net_DebugNetworkEntity_Bandwidth, true, nullptr, AZ::ConsoleFunctorFlags::Null, + "If true, prints debug text over entities that use a considerable amount of network traffic"); + +AZ_CVAR(float, net_DebugNetworkEntity_ShowAboveKbps, 1.f, nullptr, AZ::ConsoleFunctorFlags::Null, + "Prints bandwidth on network entities with higher kpbs than this value"); + +AZ_CVAR(float, net_DebugNetworkEntity_WarnAboveKbps, 10.f, nullptr, AZ::ConsoleFunctorFlags::Null, + "Prints bandwidth on network entities with higher kpbs than this value"); + +AZ_CVAR(AZ::Color, net_DebugNetworkEntity_WarningColor, AZ::Colors::Red, nullptr, AZ::ConsoleFunctorFlags::Null, + "If true, prints debug text over entities that use a considerable amount of network traffic"); + +AZ_CVAR(AZ::Color, net_DebugNetworkEntity_BelowWarningColor, AZ::Colors::Grey, nullptr, AZ::ConsoleFunctorFlags::Null, + "If true, prints debug text over entities that use a considerable amount of network traffic"); + namespace MultiplayerDiagnostics { #if defined(IMGUI_ENABLED) @@ -114,6 +129,17 @@ namespace MultiplayerDiagnostics } #endif + MultiplayerDebugPerEntityReporter::MultiplayerDebugPerEntityReporter() + : m_updateDebugOverlay([this]() { UpdateDebugOverlay(); }, AZ::Name("UpdateDebugPerEntityOverlay")) + { + m_updateDebugOverlay.Enqueue(AZ::TimeMs{ 0 }, true); + } + + MultiplayerDebugPerEntityReporter::~MultiplayerDebugPerEntityReporter() + { + m_updateDebugOverlay.RemoveFromQueue(); + } + // -------------------------------------------------------------------------------------------- void MultiplayerDebugPerEntityReporter::OnImGuiUpdate() { @@ -121,17 +147,6 @@ namespace MultiplayerDiagnostics static ImGuiTextFilter filter; filter.Draw(); - char status[100] = {}; - - struct NetworkEntityTraffic - { - const char* m_name = nullptr; - float m_up = 0.f; - float m_down = 0.f; - }; - - AZStd::fixed_unordered_map networkEntitiesTraffic; - if (ImGui::CollapsingHeader("Receiving Entities")) { for (AZStd::pair& entityPair : m_receivingEntityReports) @@ -147,9 +162,6 @@ namespace MultiplayerDiagnostics DisplayReplicatedStateReport(entityPair.second.GetComponentReports(), m_replicatedStateKbpsWarn, m_replicatedStateMaxSizeWarn); ImGui::TreePop(); } - - networkEntitiesTraffic[entityPair.first].m_name = entityPair.second.GetEntityName(); - networkEntitiesTraffic[entityPair.first].m_down = entityPair.second.GetKbitsPerSecond(); } } @@ -169,27 +181,8 @@ namespace MultiplayerDiagnostics DisplayReplicatedStateReport(entityPair.second.GetComponentReports(), m_replicatedStateKbpsWarn, m_replicatedStateMaxSizeWarn); ImGui::TreePop(); } - - networkEntitiesTraffic[entityPair.first].m_name = entityPair.second.GetEntityName(); - networkEntitiesTraffic[entityPair.first].m_up = entityPair.second.GetKbitsPerSecond(); } } - - constexpr float trafficThreshold = 0.1f; - for (AZStd::pair& networkEntity : networkEntitiesTraffic) - { - if (networkEntity.second.m_down < trafficThreshold && networkEntity.second.m_up < trafficThreshold) - { - continue; - } - - azsprintf(status, "%s - %.0f down / %0.f up (kbps)", networkEntity.second.m_name, networkEntity.second.m_down, networkEntity.second.m_up); - AZ::Vector3 entityPosition = AZ::Vector3::CreateZero(); - constexpr bool centerText = true; - AZ::TransformBus::EventResult(entityPosition, networkEntity.first, &AZ::TransformBus::Events::GetWorldTranslation); - AzFramework::DebugDisplayRequestBus::Broadcast(&AzFramework::DebugDisplayRequestBus::Events::DrawTextLabel, - entityPosition, 1.0f, status, centerText, 0, 0); - } #endif } @@ -271,4 +264,85 @@ namespace MultiplayerDiagnostics componentRegistry->GetComponentRpcName(netComponentId, rpcId), totalBytes); } } + + void MultiplayerDebugPerEntityReporter::RecordRpcReceived( + Multiplayer::NetComponentId netComponentId, + Multiplayer::RpcIndex rpcId, + uint32_t totalBytes) + { + if (const Multiplayer::MultiplayerComponentRegistry* componentRegistry = Multiplayer::GetMultiplayerComponentRegistry()) + { + m_currentReceivingEntityReport.ReportField(static_cast(netComponentId), + componentRegistry->GetComponentName(netComponentId), + componentRegistry->GetComponentRpcName(netComponentId, rpcId), totalBytes); + } + } + + void MultiplayerDebugPerEntityReporter::UpdateDebugOverlay() + { + if (net_DebugNetworkEntity_Bandwidth) + { + m_networkEntitiesTraffic.clear(); + + for (AZStd::pair& entityPair : m_receivingEntityReports) + { + m_networkEntitiesTraffic[entityPair.first].m_name = entityPair.second.GetEntityName(); + m_networkEntitiesTraffic[entityPair.first].m_down = entityPair.second.GetKbitsPerSecond(); + } + + for (AZStd::pair& entityPair : m_sendingEntityReports) + { + m_networkEntitiesTraffic[entityPair.first].m_name = entityPair.second.GetEntityName(); + m_networkEntitiesTraffic[entityPair.first].m_up = entityPair.second.GetKbitsPerSecond(); + } + + //get debug display interface for the viewport + if (m_debugDisplay == nullptr) + { + AzFramework::DebugDisplayRequestBus::BusPtr debugDisplayBus; + AzFramework::DebugDisplayRequestBus::Bind(debugDisplayBus, AzFramework::g_defaultSceneEntityDebugDisplayId); + m_debugDisplay = AzFramework::DebugDisplayRequestBus::FindFirstHandler(debugDisplayBus); + } + + const AZ::u32 stateBefore = m_debugDisplay->GetState(); + + for (AZStd::pair& networkEntity : m_networkEntitiesTraffic) + { + if (networkEntity.second.m_down < net_DebugNetworkEntity_ShowAboveKbps && networkEntity.second.m_up < net_DebugNetworkEntity_ShowAboveKbps) + { + continue; + } + + if (networkEntity.second.m_down > net_DebugNetworkEntity_WarnAboveKbps || networkEntity.second.m_up > net_DebugNetworkEntity_WarnAboveKbps) + { + m_debugDisplay->SetColor(net_DebugNetworkEntity_WarningColor); + } + else + { + m_debugDisplay->SetColor(net_DebugNetworkEntity_BelowWarningColor); + } + + if (networkEntity.second.m_down > net_DebugNetworkEntity_ShowAboveKbps && networkEntity.second.m_up > net_DebugNetworkEntity_ShowAboveKbps) + { + azsprintf(m_statusBuffer, "[%s] %.0f down / %0.f up (kbps)", networkEntity.second.m_name, + networkEntity.second.m_down, networkEntity.second.m_up); + } + else if (networkEntity.second.m_down > net_DebugNetworkEntity_ShowAboveKbps) + { + azsprintf(m_statusBuffer, "[%s] %.0f down (kbps)", networkEntity.second.m_name, networkEntity.second.m_down); + } + else + { + azsprintf(m_statusBuffer, "[%s] %.0f up (kbps)", networkEntity.second.m_name, networkEntity.second.m_up); + } + + AZ::Vector3 entityPosition = AZ::Vector3::CreateZero(); + constexpr bool centerText = true; + AZ::TransformBus::EventResult(entityPosition, networkEntity.first, &AZ::TransformBus::Events::GetWorldTranslation); + m_debugDisplay->DrawTextLabel(entityPosition, 1.0f, m_statusBuffer, centerText, 0, 0); + } + + m_debugDisplay->SetState(stateBefore); + } + } } diff --git a/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugPerEntityReporter.h b/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugPerEntityReporter.h index f34cc2cd35..23192062e3 100644 --- a/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugPerEntityReporter.h +++ b/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugPerEntityReporter.h @@ -10,7 +10,10 @@ #include "MultiplayerDebugByteReporter.h" #include +#include +#include #include +#include #include #include #include @@ -24,8 +27,8 @@ namespace MultiplayerDiagnostics : public AZ::Interface::Registrar { public: - MultiplayerDebugPerEntityReporter() = default; - ~MultiplayerDebugPerEntityReporter() override = default; + MultiplayerDebugPerEntityReporter(); + ~MultiplayerDebugPerEntityReporter() override; // main update loop void OnImGuiUpdate(); @@ -38,10 +41,15 @@ namespace MultiplayerDiagnostics void RecordPropertySent(Multiplayer::NetComponentId netComponentId, Multiplayer::PropertyIndex propertyId, uint32_t totalBytes) override; void RecordPropertyReceived(Multiplayer::NetComponentId netComponentId, Multiplayer::PropertyIndex propertyId, uint32_t totalBytes) override; void RecordRpcSent(Multiplayer::NetComponentId netComponentId, Multiplayer::RpcIndex rpcId, uint32_t totalBytes) override; + void RecordRpcReceived(Multiplayer::NetComponentId netComponentId, Multiplayer::RpcIndex rpcId, uint32_t totalBytes) override; // }@ + void UpdateDebugOverlay(); + private: + AZ::ScheduledEvent m_updateDebugOverlay; + AZStd::map m_sendingEntityReports{}; EntityReporter m_currentSendingEntityReport; @@ -50,5 +58,18 @@ namespace MultiplayerDiagnostics float m_replicatedStateKbpsWarn = 10.f; float m_replicatedStateMaxSizeWarn = 30.f; + + char m_statusBuffer[100] = {}; + + struct NetworkEntityTraffic + { + const char* m_name = nullptr; + float m_up = 0.f; + float m_down = 0.f; + }; + + AZStd::fixed_unordered_map m_networkEntitiesTraffic; + + AzFramework::DebugDisplayRequests* m_debugDisplay = nullptr; }; } diff --git a/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugSystemComponent.cpp b/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugSystemComponent.cpp index 44e2f9f6bf..835aae0d72 100644 --- a/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugSystemComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugSystemComponent.cpp @@ -13,8 +13,6 @@ #include #include -#pragma optimize("", off) - namespace Multiplayer { void MultiplayerDebugSystemComponent::Reflect(AZ::ReflectContext* context) @@ -329,7 +327,8 @@ namespace Multiplayer if (m_displayPerEntityStats) { - if (ImGui::Begin("Multiplayer Per Entity Analytics", &m_displayPerEntityStats, ImGuiWindowFlags_None)) + //ImGui::SetNextWindowSize({500, 400}); + if (ImGui::Begin("Multiplayer Per Entity Stats", &m_displayPerEntityStats, ImGuiWindowFlags_AlwaysAutoResize)) { if (m_reporter) { diff --git a/Gems/Multiplayer/Code/Source/MultiplayerStats.cpp b/Gems/Multiplayer/Code/Source/MultiplayerStats.cpp index fd1b6a329d..05be869872 100644 --- a/Gems/Multiplayer/Code/Source/MultiplayerStats.cpp +++ b/Gems/Multiplayer/Code/Source/MultiplayerStats.cpp @@ -101,6 +101,11 @@ namespace Multiplayer void MultiplayerStats::RecordRpcReceived(NetComponentId netComponentId, RpcIndex rpcId, uint32_t totalBytes) { + if (auto* perEntityStats = AZ::Interface::Get()) + { + perEntityStats->RecordRpcReceived(netComponentId, rpcId, totalBytes); + } + const uint16_t netComponentIndex = aznumeric_cast(netComponentId); const uint16_t rpcIndex = aznumeric_cast(rpcId); m_componentStats[netComponentIndex].m_rpcsRecv[rpcIndex].m_totalCalls++; From 462c31b5de49ffe05c1788741278d2865a685ae1 Mon Sep 17 00:00:00 2001 From: AMZN-Olex <5432499+AMZN-Olex@users.noreply.github.com> Date: Wed, 28 Jul 2021 09:53:44 -0400 Subject: [PATCH 07/37] Refactoring to use AZ::Events Signed-off-by: AMZN-Olex <5432499+AMZN-Olex@users.noreply.github.com> --- .../Include/Multiplayer/MultiplayerStats.h | 30 ++++++++- .../Debug/MultiplayerDebugByteReporter.cpp | 38 ++++++------ .../Debug/MultiplayerDebugByteReporter.h | 20 +++--- .../MultiplayerDebugPerEntityInterface.h | 29 --------- .../MultiplayerDebugPerEntityReporter.cpp | 61 ++++++++++++++++--- .../Debug/MultiplayerDebugPerEntityReporter.h | 34 +++++------ .../Debug/MultiplayerDebugSystemComponent.cpp | 4 +- .../Debug/MultiplayerDebugSystemComponent.h | 2 +- .../Code/Source/MultiplayerStats.cpp | 59 ++++++++---------- .../EntityReplication/EntityReplicator.cpp | 11 ++-- .../Code/multiplayer_debug_files.cmake | 1 - 11 files changed, 156 insertions(+), 133 deletions(-) delete mode 100644 Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugPerEntityInterface.h diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/MultiplayerStats.h b/Gems/Multiplayer/Code/Include/Multiplayer/MultiplayerStats.h index 1299fe174d..98a7b165f8 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/MultiplayerStats.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/MultiplayerStats.h @@ -55,8 +55,8 @@ namespace Multiplayer void RecordEntitySerializeStop(AzNetworking::SerializerMode mode, AZ::EntityId entityId, const char* entityName); void RecordPropertySent(NetComponentId netComponentId, PropertyIndex propertyId, uint32_t totalBytes); void RecordPropertyReceived(NetComponentId netComponentId, PropertyIndex propertyId, uint32_t totalBytes); - void RecordRpcSent(NetComponentId netComponentId, RpcIndex rpcId, uint32_t totalBytes); - void RecordRpcReceived(NetComponentId netComponentId, RpcIndex rpcId, uint32_t totalBytes); + void RecordRpcSent(AZ::EntityId entityId, const char* entityName, NetComponentId netComponentId, RpcIndex rpcId, uint32_t totalBytes); + void RecordRpcReceived(AZ::EntityId entityId, const char* entityName, NetComponentId netComponentId, RpcIndex rpcId, uint32_t totalBytes); void TickStats(AZ::TimeMs metricFrameTimeMs); Metric CalculateComponentPropertyUpdateSentMetrics(NetComponentId netComponentId) const; @@ -67,5 +67,31 @@ namespace Multiplayer Metric CalculateTotalPropertyUpdateRecvMetrics() const; Metric CalculateTotalRpcsSentMetrics() const; Metric CalculateTotalRpcsRecvMetrics() const; + + struct Events + { + AZ::Event m_entitySerializeStart; + AZ::Event m_componentSerializeEnd; + AZ::Event m_entitySerializeStop; + AZ::Event m_propertySent; + AZ::Event m_propertyReceived; + AZ::Event m_rpcSent; + AZ::Event m_rpcReceived; + }; + + Events m_events; + + struct EventHandlers + { + AZ::Event::Handler m_entitySerializeStart; + AZ::Event::Handler m_componentSerializeEnd; + AZ::Event::Handler m_entitySerializeStop; + AZ::Event::Handler m_propertySent; + AZ::Event::Handler m_propertyReceived; + AZ::Event::Handler m_rpcSent; + AZ::Event::Handler m_rpcReceived; + }; + + void ConnectHandlers(EventHandlers& handlers); }; } diff --git a/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugByteReporter.cpp b/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugByteReporter.cpp index 5e912612b7..a29350e43a 100644 --- a/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugByteReporter.cpp +++ b/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugByteReporter.cpp @@ -14,7 +14,7 @@ #pragma optimize("", off) -namespace MultiplayerDiagnostics +namespace Multiplayer { void MultiplayerDebugByteReporter::ReportBytes(size_t byteSize) { @@ -44,7 +44,7 @@ namespace MultiplayerDiagnostics return 0.0f; } - return (1.0f * m_totalBytes) / m_count; + return aznumeric_cast(m_totalBytes) / aznumeric_cast(m_count); } size_t MultiplayerDebugByteReporter::GetMaxBytes() const @@ -64,26 +64,26 @@ namespace MultiplayerDiagnostics float MultiplayerDebugByteReporter::GetKbitsPerSecond() { - auto now = AZStd::chrono::monotonic_clock::now(); + const auto now = AZStd::chrono::monotonic_clock::now(); // Check the amount of time elapsed and update totals if necessary. // Time here is measured in whole seconds from the epoch, providing synchronization in // reporting intervals across all byte reporters. - AZStd::chrono::seconds nowSeconds = AZStd::chrono::duration_cast(now.time_since_epoch()); - AZStd::chrono::seconds secondsSinceLastUpdate = nowSeconds - + const AZStd::chrono::seconds nowSeconds = AZStd::chrono::duration_cast(now.time_since_epoch()); + const AZStd::chrono::seconds secondsSinceLastUpdate = nowSeconds - AZStd::chrono::duration_cast(m_lastUpdateTime.time_since_epoch()); if (secondsSinceLastUpdate.count()) { // normalize over elapsed milliseconds - const int k_millisecondsPerSecond = 1000; - auto msSinceLastUpdate = AZStd::chrono::duration_cast(now - m_lastUpdateTime); - m_totalBytesLastSecond = k_millisecondsPerSecond * (1.f * m_totalBytesThisSecond / msSinceLastUpdate.count()); + constexpr int k_millisecondsPerSecond = 1000; + const auto msSinceLastUpdate = AZStd::chrono::duration_cast(now - m_lastUpdateTime); + m_totalBytesLastSecond = k_millisecondsPerSecond * aznumeric_cast(m_totalBytesThisSecond) / aznumeric_cast(msSinceLastUpdate.count()); m_totalBytesThisSecond = 0; m_lastUpdateTime = now; } - const float k_bitsPerByte = 8.0f; - const int k_bitsPerKilobit = 1024; + constexpr float k_bitsPerByte = 8.0f; + constexpr int k_bitsPerKilobit = 1024; return k_bitsPerByte * m_totalBytesLastSecond / k_bitsPerKilobit; } @@ -107,19 +107,19 @@ namespace MultiplayerDiagnostics m_aggregateBytes = 0; } - void ComponentReporter::ReportField(const char* fieldName, size_t byteSize) + void MultiplayerDebugComponentReporter::ReportField(const char* fieldName, size_t byteSize) { MultiplayerDebugByteReporter::AggregateBytes(byteSize); m_fieldReports[fieldName].ReportBytes(byteSize); } - void ComponentReporter::ReportFragmentEnd() + void MultiplayerDebugComponentReporter::ReportFragmentEnd() { MultiplayerDebugByteReporter::ReportAggregateBytes(); m_componentDirtyBytes.ReportAggregateBytes(); } - AZStd::vector ComponentReporter::GetFieldReports() + AZStd::vector MultiplayerDebugComponentReporter::GetFieldReports() { AZStd::vector copy; for (auto field = m_fieldReports.begin(); field != m_fieldReports.end(); ++field) @@ -137,7 +137,7 @@ namespace MultiplayerDiagnostics return copy; } - void ComponentReporter::Combine(const ComponentReporter& other) + void MultiplayerDebugComponentReporter::Combine(const MultiplayerDebugComponentReporter& other) { MultiplayerDebugByteReporter::Combine(other); @@ -149,7 +149,7 @@ namespace MultiplayerDiagnostics m_componentDirtyBytes.Combine(other.m_componentDirtyBytes); } - void EntityReporter::ReportField(AZ::u32 index, const char* componentName, + void MultiplayerDebugEntityReporter::ReportField(AZ::u32 index, const char* componentName, const char* fieldName, size_t byteSize) { if (m_currentComponentReport == nullptr) @@ -163,7 +163,7 @@ namespace MultiplayerDiagnostics MultiplayerDebugByteReporter::AggregateBytes(byteSize); } - void EntityReporter::ReportFragmentEnd() + void MultiplayerDebugEntityReporter::ReportFragmentEnd() { if (m_currentComponentReport) { @@ -175,7 +175,7 @@ namespace MultiplayerDiagnostics MultiplayerDebugByteReporter::ReportAggregateBytes(); } - void EntityReporter::Combine(const EntityReporter& other) + void MultiplayerDebugEntityReporter::Combine(const MultiplayerDebugEntityReporter& other) { MultiplayerDebugByteReporter::Combine(other); @@ -188,7 +188,7 @@ namespace MultiplayerDiagnostics m_gdeDirtyBytes.Combine(other.m_gdeDirtyBytes); } - void EntityReporter::Reset() + void MultiplayerDebugEntityReporter::Reset() { MultiplayerDebugByteReporter::Reset(); @@ -196,7 +196,7 @@ namespace MultiplayerDiagnostics m_gdeDirtyBytes.Reset(); } - AZStd::map& EntityReporter::GetComponentReports() + AZStd::map& MultiplayerDebugEntityReporter::GetComponentReports() { return m_componentReports; } diff --git a/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugByteReporter.h b/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugByteReporter.h index 3cef9caaac..934c6f3aac 100644 --- a/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugByteReporter.h +++ b/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugByteReporter.h @@ -13,7 +13,7 @@ #include #include -namespace MultiplayerDiagnostics +namespace Multiplayer { class MultiplayerDebugByteReporter { @@ -48,10 +48,10 @@ namespace MultiplayerDiagnostics AZStd::chrono::monotonic_clock::time_point m_lastUpdateTime; }; - class ComponentReporter : public MultiplayerDebugByteReporter + class MultiplayerDebugComponentReporter : public MultiplayerDebugByteReporter { public: - ComponentReporter() = default; + MultiplayerDebugComponentReporter() = default; void ReportField(const char* fieldName, size_t byteSize); void ReportFragmentEnd(); @@ -61,22 +61,22 @@ namespace MultiplayerDiagnostics AZStd::size_t GetTotalDirtyBits() const { return m_componentDirtyBytes.GetTotalBytes(); } float GetAvgDirtyBits() const { return m_componentDirtyBytes.GetAverageBytes(); } - void Combine(const ComponentReporter& other); + void Combine(const MultiplayerDebugComponentReporter& other); private: AZStd::map m_fieldReports; MultiplayerDebugByteReporter m_componentDirtyBytes; }; - class EntityReporter : public MultiplayerDebugByteReporter + class MultiplayerDebugEntityReporter : public MultiplayerDebugByteReporter { public: - EntityReporter() = default; + MultiplayerDebugEntityReporter() = default; void ReportField(AZ::u32 index, const char* componentName, const char* fieldName, size_t byteSize); void ReportFragmentEnd(); - void Combine(const EntityReporter& other); + void Combine(const MultiplayerDebugEntityReporter& other); void Reset() override; const char* GetEntityName() const { return m_entityName.c_str(); } @@ -86,13 +86,13 @@ namespace MultiplayerDiagnostics m_entityName = entityName; } - AZStd::map& GetComponentReports(); + AZStd::map& GetComponentReports(); AZStd::size_t GetTotalDirtyBits() const { return m_gdeDirtyBytes.GetTotalBytes(); } float GetAvgDirtyBits() const { return m_gdeDirtyBytes.GetAverageBytes(); } private: - ComponentReporter* m_currentComponentReport = nullptr; - AZStd::map m_componentReports; + MultiplayerDebugComponentReporter* m_currentComponentReport = nullptr; + AZStd::map m_componentReports; MultiplayerDebugByteReporter m_gdeDirtyBytes; AZStd::string m_entityName; }; diff --git a/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugPerEntityInterface.h b/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugPerEntityInterface.h deleted file mode 100644 index 4f177affe3..0000000000 --- a/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugPerEntityInterface.h +++ /dev/null @@ -1,29 +0,0 @@ -/* - * 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 - * - */ - -#pragma once - -#include -#include - -namespace MultiplayerDiagnostics -{ - class MultiplayerIPerEntityStats - { - public: - AZ_RTTI(MultiplayerIPerEntityStats, "{91A1E4F0-8AE6-44B2-89DF-DA34134C408A}"); - - virtual void RecordEntitySerializeStart(AzNetworking::SerializerMode mode, AZ::EntityId entityId, const char* entityName) = 0; - virtual void RecordComponentSerializeEnd(AzNetworking::SerializerMode mode, Multiplayer::NetComponentId netComponentId) = 0; - virtual void RecordEntitySerializeStop(AzNetworking::SerializerMode mode, AZ::EntityId entityId, const char* entityName) = 0; - virtual void RecordPropertySent(Multiplayer::NetComponentId netComponentId, Multiplayer::PropertyIndex propertyId, uint32_t totalBytes) = 0; - virtual void RecordPropertyReceived(Multiplayer::NetComponentId netComponentId, Multiplayer::PropertyIndex propertyId, uint32_t totalBytes) = 0; - virtual void RecordRpcSent(Multiplayer::NetComponentId netComponentId, Multiplayer::RpcIndex rpcId, uint32_t totalBytes) = 0; - virtual void RecordRpcReceived(Multiplayer::NetComponentId netComponentId, Multiplayer::RpcIndex rpcId, uint32_t totalBytes) = 0; - }; -} diff --git a/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugPerEntityReporter.cpp b/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugPerEntityReporter.cpp index 3c5495ec6a..da32bbb666 100644 --- a/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugPerEntityReporter.cpp +++ b/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugPerEntityReporter.cpp @@ -34,7 +34,7 @@ AZ_CVAR(AZ::Color, net_DebugNetworkEntity_WarningColor, AZ::Colors::Red, nullptr AZ_CVAR(AZ::Color, net_DebugNetworkEntity_BelowWarningColor, AZ::Colors::Grey, nullptr, AZ::ConsoleFunctorFlags::Null, "If true, prints debug text over entities that use a considerable amount of network traffic"); -namespace MultiplayerDiagnostics +namespace Multiplayer { #if defined(IMGUI_ENABLED) static const ImVec4 k_ImGuiTomato = ImVec4(1.0f, 0.4f, 0.3f, 1.0f); @@ -64,12 +64,12 @@ namespace MultiplayerDiagnostics } // -------------------------------------------------------------------------------------------- - void DisplayReplicatedStateReport(AZStd::map& componentReports, float kbpsWarn, float maxWarn) + void DisplayReplicatedStateReport(AZStd::map& componentReports, float kbpsWarn, float maxWarn) { for (auto& componentPair : componentReports) { ImGui::Separator(); - ComponentReporter& componentReport = componentPair.second; + MultiplayerDebugComponentReporter& componentReport = componentPair.second; if (ReplicatedStateTreeNode(componentPair.first, componentReport, k_ImGuiCyan, 1)) { @@ -94,7 +94,7 @@ namespace MultiplayerDiagnostics const float kbitsLastSecond = fieldReport.GetKbitsPerSecond(); const ImVec4* textColor = &k_ImGuiWhite; - if (fieldReport.GetMaxBytes() > maxWarn) + if (aznumeric_cast(fieldReport.GetMaxBytes()) > maxWarn) { textColor = &k_ImGuiKhaki; } @@ -133,6 +133,37 @@ namespace MultiplayerDiagnostics : m_updateDebugOverlay([this]() { UpdateDebugOverlay(); }, AZ::Name("UpdateDebugPerEntityOverlay")) { m_updateDebugOverlay.Enqueue(AZ::TimeMs{ 0 }, true); + + m_eventHandlers.m_entitySerializeStart = decltype(m_eventHandlers.m_entitySerializeStart)([this](AzNetworking::SerializerMode mode, AZ::EntityId entityId, const char* entityName) + { + RecordEntitySerializeStart(mode, entityId, entityName); + }); + m_eventHandlers.m_componentSerializeEnd = decltype(m_eventHandlers.m_componentSerializeEnd)([this](AzNetworking::SerializerMode mode, Multiplayer::NetComponentId netComponentId) + { + RecordComponentSerializeEnd(mode, netComponentId); + }); + m_eventHandlers.m_entitySerializeStop = decltype(m_eventHandlers.m_entitySerializeStop)([this](AzNetworking::SerializerMode mode, AZ::EntityId entityId, const char* entityName) + { + RecordEntitySerializeStop(mode, entityId, entityName); + }); + m_eventHandlers.m_propertySent = decltype(m_eventHandlers.m_propertySent)([this](Multiplayer::NetComponentId netComponentId, Multiplayer::PropertyIndex propertyId, uint32_t totalBytes) + { + RecordPropertySent(netComponentId, propertyId, totalBytes); + }); + m_eventHandlers.m_propertyReceived = decltype(m_eventHandlers.m_propertyReceived)([this](Multiplayer::NetComponentId netComponentId, Multiplayer::PropertyIndex propertyId, uint32_t totalBytes) + { + RecordPropertyReceived(netComponentId, propertyId, totalBytes); + }); + m_eventHandlers.m_rpcSent = decltype(m_eventHandlers.m_rpcSent)([this](AZ::EntityId entityId, const char* entityName, Multiplayer::NetComponentId netComponentId, Multiplayer::RpcIndex rpcId, uint32_t totalBytes) + { + RecordRpcSent(entityId, entityName, netComponentId, rpcId, totalBytes); + }); + m_eventHandlers.m_rpcReceived = decltype(m_eventHandlers.m_rpcReceived)([this](AZ::EntityId entityId, const char* entityName, Multiplayer::NetComponentId netComponentId, Multiplayer::RpcIndex rpcId, uint32_t totalBytes) + { + RecordRpcSent(entityId, entityName, netComponentId, rpcId, totalBytes); + }); + + GetMultiplayer()->GetStats().ConnectHandlers(m_eventHandlers); } MultiplayerDebugPerEntityReporter::~MultiplayerDebugPerEntityReporter() @@ -149,7 +180,7 @@ namespace MultiplayerDiagnostics if (ImGui::CollapsingHeader("Receiving Entities")) { - for (AZStd::pair& entityPair : m_receivingEntityReports) + for (AZStd::pair& entityPair : m_receivingEntityReports) { if (!filter.PassFilter(entityPair.second.GetEntityName())) { @@ -167,7 +198,7 @@ namespace MultiplayerDiagnostics if (ImGui::CollapsingHeader("Sending Entities")) { - for (AZStd::pair& entityPair : m_sendingEntityReports) + for (AZStd::pair& entityPair : m_sendingEntityReports) { const char* name = entityPair.second.GetEntityName(); if (!filter.PassFilter(name)) @@ -255,26 +286,38 @@ namespace MultiplayerDiagnostics } } - void MultiplayerDebugPerEntityReporter::RecordRpcSent(Multiplayer::NetComponentId netComponentId, Multiplayer::RpcIndex rpcId, uint32_t totalBytes) + void MultiplayerDebugPerEntityReporter::RecordRpcSent(AZ::EntityId entityId, const char* entityName, Multiplayer::NetComponentId netComponentId, Multiplayer::RpcIndex rpcId, uint32_t totalBytes) { if (const Multiplayer::MultiplayerComponentRegistry* componentRegistry = Multiplayer::GetMultiplayerComponentRegistry()) { + // MultiplayerDebugByteReporter requires a + RecordEntitySerializeStart(AzNetworking::SerializerMode::ReadFromObject, entityId, entityName); + m_currentSendingEntityReport.ReportField(static_cast(netComponentId), componentRegistry->GetComponentName(netComponentId), componentRegistry->GetComponentRpcName(netComponentId, rpcId), totalBytes); + + RecordComponentSerializeEnd(AzNetworking::SerializerMode::ReadFromObject, netComponentId); + RecordEntitySerializeStop(AzNetworking::SerializerMode::ReadFromObject, entityId, entityName); } } void MultiplayerDebugPerEntityReporter::RecordRpcReceived( + AZ::EntityId entityId, const char* entityName, Multiplayer::NetComponentId netComponentId, Multiplayer::RpcIndex rpcId, uint32_t totalBytes) { if (const Multiplayer::MultiplayerComponentRegistry* componentRegistry = Multiplayer::GetMultiplayerComponentRegistry()) { + RecordEntitySerializeStart(AzNetworking::SerializerMode::WriteToObject, entityId, entityName); + m_currentReceivingEntityReport.ReportField(static_cast(netComponentId), componentRegistry->GetComponentName(netComponentId), componentRegistry->GetComponentRpcName(netComponentId, rpcId), totalBytes); + + RecordComponentSerializeEnd(AzNetworking::SerializerMode::WriteToObject, netComponentId); + RecordEntitySerializeStop(AzNetworking::SerializerMode::WriteToObject, entityId, entityName); } } @@ -284,13 +327,13 @@ namespace MultiplayerDiagnostics { m_networkEntitiesTraffic.clear(); - for (AZStd::pair& entityPair : m_receivingEntityReports) + for (AZStd::pair& entityPair : m_receivingEntityReports) { m_networkEntitiesTraffic[entityPair.first].m_name = entityPair.second.GetEntityName(); m_networkEntitiesTraffic[entityPair.first].m_down = entityPair.second.GetKbitsPerSecond(); } - for (AZStd::pair& entityPair : m_sendingEntityReports) + for (AZStd::pair& entityPair : m_sendingEntityReports) { m_networkEntitiesTraffic[entityPair.first].m_name = entityPair.second.GetEntityName(); m_networkEntitiesTraffic[entityPair.first].m_up = entityPair.second.GetKbitsPerSecond(); diff --git a/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugPerEntityReporter.h b/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugPerEntityReporter.h index 23192062e3..d6ceaaf04c 100644 --- a/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugPerEntityReporter.h +++ b/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugPerEntityReporter.h @@ -10,38 +10,35 @@ #include "MultiplayerDebugByteReporter.h" #include -#include #include #include #include -#include -#include +#include #include -namespace MultiplayerDiagnostics +namespace Multiplayer { /** * \brief GridMate network live analysis tool via ImGui. */ class MultiplayerDebugPerEntityReporter - : public AZ::Interface::Registrar { public: MultiplayerDebugPerEntityReporter(); - ~MultiplayerDebugPerEntityReporter() override; + ~MultiplayerDebugPerEntityReporter(); // main update loop void OnImGuiUpdate(); - //! MultilayerIPerEntityStats + //! Event handlers // @{ - void RecordEntitySerializeStart(AzNetworking::SerializerMode mode, AZ::EntityId entityId, const char* entityName) override; - void RecordComponentSerializeEnd(AzNetworking::SerializerMode mode, Multiplayer::NetComponentId netComponentId) override; - void RecordEntitySerializeStop(AzNetworking::SerializerMode mode, AZ::EntityId entityId, const char* entityName) override; - void RecordPropertySent(Multiplayer::NetComponentId netComponentId, Multiplayer::PropertyIndex propertyId, uint32_t totalBytes) override; - void RecordPropertyReceived(Multiplayer::NetComponentId netComponentId, Multiplayer::PropertyIndex propertyId, uint32_t totalBytes) override; - void RecordRpcSent(Multiplayer::NetComponentId netComponentId, Multiplayer::RpcIndex rpcId, uint32_t totalBytes) override; - void RecordRpcReceived(Multiplayer::NetComponentId netComponentId, Multiplayer::RpcIndex rpcId, uint32_t totalBytes) override; + void RecordEntitySerializeStart(AzNetworking::SerializerMode mode, AZ::EntityId entityId, const char* entityName); + void RecordComponentSerializeEnd(AzNetworking::SerializerMode mode, Multiplayer::NetComponentId netComponentId); + void RecordEntitySerializeStop(AzNetworking::SerializerMode mode, AZ::EntityId entityId, const char* entityName); + void RecordPropertySent(Multiplayer::NetComponentId netComponentId, Multiplayer::PropertyIndex propertyId, uint32_t totalBytes); + void RecordPropertyReceived(Multiplayer::NetComponentId netComponentId, Multiplayer::PropertyIndex propertyId, uint32_t totalBytes); + void RecordRpcSent(AZ::EntityId entityId, const char* entityName, Multiplayer::NetComponentId netComponentId, Multiplayer::RpcIndex rpcId, uint32_t totalBytes); + void RecordRpcReceived(AZ::EntityId entityId, const char* entityName, Multiplayer::NetComponentId netComponentId, Multiplayer::RpcIndex rpcId, uint32_t totalBytes); // }@ void UpdateDebugOverlay(); @@ -49,12 +46,13 @@ namespace MultiplayerDiagnostics private: AZ::ScheduledEvent m_updateDebugOverlay; + Multiplayer::MultiplayerStats::EventHandlers m_eventHandlers; - AZStd::map m_sendingEntityReports{}; - EntityReporter m_currentSendingEntityReport; + AZStd::map m_sendingEntityReports{}; + MultiplayerDebugEntityReporter m_currentSendingEntityReport; - AZStd::map m_receivingEntityReports{}; - EntityReporter m_currentReceivingEntityReport; + AZStd::map m_receivingEntityReports{}; + MultiplayerDebugEntityReporter m_currentReceivingEntityReport; float m_replicatedStateKbpsWarn = 10.f; float m_replicatedStateMaxSizeWarn = 30.f; diff --git a/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugSystemComponent.cpp b/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugSystemComponent.cpp index 835aae0d72..03d79025f7 100644 --- a/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugSystemComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugSystemComponent.cpp @@ -55,7 +55,7 @@ namespace Multiplayer void MultiplayerDebugSystemComponent::OnImGuiInitialize() { - m_reporter = AZStd::make_unique(); + m_reporter = AZStd::make_unique(); } #ifdef IMGUI_ENABLED @@ -65,7 +65,7 @@ namespace Multiplayer { ImGui::Checkbox("Networking Stats", &m_displayNetworkingStats); ImGui::Checkbox("Multiplayer Stats", &m_displayMultiplayerStats); - ImGui::Checkbox("Multiplayer Per Entity Stats", &m_displayPerEntityStats); + ImGui::Checkbox("Multiplayer Entity Stats", &m_displayPerEntityStats); ImGui::EndMenu(); } } diff --git a/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugSystemComponent.h b/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugSystemComponent.h index 4972ec6bdf..a7f76e075a 100644 --- a/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugSystemComponent.h +++ b/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugSystemComponent.h @@ -53,6 +53,6 @@ namespace Multiplayer bool m_displayMultiplayerStats = false; bool m_displayPerEntityStats = false; - AZStd::unique_ptr m_reporter; + AZStd::unique_ptr m_reporter; }; } diff --git a/Gems/Multiplayer/Code/Source/MultiplayerStats.cpp b/Gems/Multiplayer/Code/Source/MultiplayerStats.cpp index 05be869872..7e56cd34f3 100644 --- a/Gems/Multiplayer/Code/Source/MultiplayerStats.cpp +++ b/Gems/Multiplayer/Code/Source/MultiplayerStats.cpp @@ -6,7 +6,6 @@ * */ -#include #include namespace Multiplayer @@ -32,86 +31,65 @@ namespace Multiplayer void MultiplayerStats::RecordEntitySerializeStart(AzNetworking::SerializerMode mode, AZ::EntityId entityId, const char* entityName) { - if (auto* perEntityStats = AZ::Interface::Get()) - { - perEntityStats->RecordEntitySerializeStart(mode, entityId, entityName); - } + m_events.m_entitySerializeStart.Signal(mode, entityId, entityName); } void MultiplayerStats::RecordComponentSerializeEnd(AzNetworking::SerializerMode mode, NetComponentId netComponentId) { - if (auto* perEntityStats = AZ::Interface::Get()) - { - perEntityStats->RecordComponentSerializeEnd(mode, netComponentId); - } + m_events.m_componentSerializeEnd.Signal(mode, netComponentId); } void MultiplayerStats::RecordEntitySerializeStop(AzNetworking::SerializerMode mode, AZ::EntityId entityId, const char* entityName) { - if (auto* perEntityStats = AZ::Interface::Get()) - { - perEntityStats->RecordEntitySerializeStop(mode, entityId, entityName); - } + m_events.m_entitySerializeStop.Signal(mode, entityId, entityName); } void MultiplayerStats::RecordPropertySent(NetComponentId netComponentId, PropertyIndex propertyId, uint32_t totalBytes) { - if (auto* perEntityStats = AZ::Interface::Get()) - { - perEntityStats->RecordPropertySent(netComponentId, propertyId, totalBytes); - } - const uint16_t netComponentIndex = aznumeric_cast(netComponentId); const uint16_t propertyIndex = aznumeric_cast(propertyId); m_componentStats[netComponentIndex].m_propertyUpdatesSent[propertyIndex].m_totalCalls++; m_componentStats[netComponentIndex].m_propertyUpdatesSent[propertyIndex].m_totalBytes += totalBytes; m_componentStats[netComponentIndex].m_propertyUpdatesSent[propertyIndex].m_callHistory[m_recordMetricIndex]++; m_componentStats[netComponentIndex].m_propertyUpdatesSent[propertyIndex].m_byteHistory[m_recordMetricIndex] += totalBytes; + + m_events.m_propertySent.Signal(netComponentId, propertyId, totalBytes); } void MultiplayerStats::RecordPropertyReceived(NetComponentId netComponentId, PropertyIndex propertyId, uint32_t totalBytes) { - if (auto* perEntityStats = AZ::Interface::Get()) - { - perEntityStats->RecordPropertyReceived(netComponentId, propertyId, totalBytes); - } - const uint16_t netComponentIndex = aznumeric_cast(netComponentId); const uint16_t propertyIndex = aznumeric_cast(propertyId); m_componentStats[netComponentIndex].m_propertyUpdatesRecv[propertyIndex].m_totalCalls++; m_componentStats[netComponentIndex].m_propertyUpdatesRecv[propertyIndex].m_totalBytes += totalBytes; m_componentStats[netComponentIndex].m_propertyUpdatesRecv[propertyIndex].m_callHistory[m_recordMetricIndex]++; m_componentStats[netComponentIndex].m_propertyUpdatesRecv[propertyIndex].m_byteHistory[m_recordMetricIndex] += totalBytes; + + m_events.m_propertyReceived.Signal(netComponentId, propertyId, totalBytes); } - void MultiplayerStats::RecordRpcSent(NetComponentId netComponentId, RpcIndex rpcId, uint32_t totalBytes) + void MultiplayerStats::RecordRpcSent(AZ::EntityId entityId, const char* entityName, NetComponentId netComponentId, RpcIndex rpcId, uint32_t totalBytes) { - if (auto* perEntityStats = AZ::Interface::Get()) - { - perEntityStats->RecordRpcSent(netComponentId, rpcId, totalBytes); - } - const uint16_t netComponentIndex = aznumeric_cast(netComponentId); const uint16_t rpcIndex = aznumeric_cast(rpcId); m_componentStats[netComponentIndex].m_rpcsSent[rpcIndex].m_totalCalls++; m_componentStats[netComponentIndex].m_rpcsSent[rpcIndex].m_totalBytes += totalBytes; m_componentStats[netComponentIndex].m_rpcsSent[rpcIndex].m_callHistory[m_recordMetricIndex]++; m_componentStats[netComponentIndex].m_rpcsSent[rpcIndex].m_byteHistory[m_recordMetricIndex] += totalBytes; + + m_events.m_rpcSent.Signal(entityId, entityName, netComponentId, rpcId, totalBytes); } - void MultiplayerStats::RecordRpcReceived(NetComponentId netComponentId, RpcIndex rpcId, uint32_t totalBytes) + void MultiplayerStats::RecordRpcReceived(AZ::EntityId entityId, const char* entityName, NetComponentId netComponentId, RpcIndex rpcId, uint32_t totalBytes) { - if (auto* perEntityStats = AZ::Interface::Get()) - { - perEntityStats->RecordRpcReceived(netComponentId, rpcId, totalBytes); - } - const uint16_t netComponentIndex = aznumeric_cast(netComponentId); const uint16_t rpcIndex = aznumeric_cast(rpcId); m_componentStats[netComponentIndex].m_rpcsRecv[rpcIndex].m_totalCalls++; m_componentStats[netComponentIndex].m_rpcsRecv[rpcIndex].m_totalBytes += totalBytes; m_componentStats[netComponentIndex].m_rpcsRecv[rpcIndex].m_callHistory[m_recordMetricIndex]++; m_componentStats[netComponentIndex].m_rpcsRecv[rpcIndex].m_byteHistory[m_recordMetricIndex] += totalBytes; + + m_events.m_rpcReceived.Signal(entityId, entityName, netComponentId, rpcId, totalBytes); } void MultiplayerStats::TickStats(AZ::TimeMs metricFrameTimeMs) @@ -231,4 +209,15 @@ namespace Multiplayer } return result; } + + void MultiplayerStats::ConnectHandlers(EventHandlers& handlers) + { + handlers.m_entitySerializeStart.Connect(m_events.m_entitySerializeStart); + handlers.m_componentSerializeEnd.Connect(m_events.m_componentSerializeEnd); + handlers.m_entitySerializeStop.Connect(m_events.m_entitySerializeStop); + handlers.m_propertySent.Connect(m_events.m_propertySent); + handlers.m_propertyReceived.Connect(m_events.m_propertyReceived); + handlers.m_rpcSent.Connect(m_events.m_rpcSent); + handlers.m_rpcReceived.Connect(m_events.m_rpcReceived); + } } diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicator.cpp b/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicator.cpp index 2afadbe4a4..457ed3eed5 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicator.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicator.cpp @@ -443,12 +443,8 @@ namespace Multiplayer { // Received rpc metrics, log rpc sent, number of bytes, and the componentId/rpcId for bandwidth metrics MultiplayerStats& stats = GetMultiplayer()->GetStats(); - stats.RecordEntitySerializeStart(AzNetworking::SerializerMode::ReadFromObject, - GetEntityHandle().GetEntity()->GetId(), GetEntityHandle().GetEntity()->GetName().c_str()); - stats.RecordRpcSent(entityRpcMessage.GetComponentId(), entityRpcMessage.GetRpcIndex(), entityRpcMessage.GetEstimatedSerializeSize()); - stats.RecordComponentSerializeEnd(AzNetworking::SerializerMode::ReadFromObject, entityRpcMessage.GetComponentId()); - stats.RecordEntitySerializeStop(AzNetworking::SerializerMode::ReadFromObject, - GetEntityHandle().GetEntity()->GetId(), GetEntityHandle().GetEntity()->GetName().c_str()); + stats.RecordRpcSent(GetEntityHandle().GetEntity()->GetId(), GetEntityHandle().GetEntity()->GetName().c_str(), + entityRpcMessage.GetComponentId(), entityRpcMessage.GetRpcIndex(), entityRpcMessage.GetEstimatedSerializeSize()); m_replicationManager.AddDeferredRpcMessage(entityRpcMessage); } @@ -631,7 +627,8 @@ namespace Multiplayer { // Received rpc metrics, log rpc received, time spent, number of bytes, and the componentId/rpcId for bandwidth metrics MultiplayerStats& stats = GetMultiplayer()->GetStats(); - stats.RecordRpcReceived(entityRpcMessage.GetComponentId(), entityRpcMessage.GetRpcIndex(), entityRpcMessage.GetEstimatedSerializeSize()); + stats.RecordRpcReceived(GetEntityHandle().GetEntity()->GetId(), GetEntityHandle().GetEntity()->GetName().c_str(), + entityRpcMessage.GetComponentId(), entityRpcMessage.GetRpcIndex(), entityRpcMessage.GetEstimatedSerializeSize()); if (!m_netBindComponent) { diff --git a/Gems/Multiplayer/Code/multiplayer_debug_files.cmake b/Gems/Multiplayer/Code/multiplayer_debug_files.cmake index d333269a1d..37a1c91640 100644 --- a/Gems/Multiplayer/Code/multiplayer_debug_files.cmake +++ b/Gems/Multiplayer/Code/multiplayer_debug_files.cmake @@ -9,7 +9,6 @@ set(FILES Source/Debug/MultiplayerDebugByteReporter.cpp Source/Debug/MultiplayerDebugByteReporter.h - Source/Debug/MultiplayerDebugPerEntityInterface.h Source/Debug/MultiplayerDebugPerEntityReporter.cpp Source/Debug/MultiplayerDebugPerEntityReporter.h Source/Debug/MultiplayerDebugModule.cpp From 93a3d3efa07abe909474ee81f981b9df98524c01 Mon Sep 17 00:00:00 2001 From: AMZN-Olex <5432499+AMZN-Olex@users.noreply.github.com> Date: Mon, 2 Aug 2021 15:57:29 -0400 Subject: [PATCH 08/37] Bandwith overlay works without imgui Signed-off-by: AMZN-Olex <5432499+AMZN-Olex@users.noreply.github.com> --- .../Include/Multiplayer/IMultiplayerDebug.h | 28 ++++ .../Debug/MultiplayerDebugByteReporter.cpp | 4 +- .../MultiplayerDebugPerEntityReporter.cpp | 130 ++++++++---------- .../Debug/MultiplayerDebugPerEntityReporter.h | 3 +- .../Debug/MultiplayerDebugSystemComponent.cpp | 35 ++++- .../Debug/MultiplayerDebugSystemComponent.h | 11 +- Gems/Multiplayer/Code/multiplayer_files.cmake | 1 + 7 files changed, 135 insertions(+), 77 deletions(-) create mode 100644 Gems/Multiplayer/Code/Include/Multiplayer/IMultiplayerDebug.h diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/IMultiplayerDebug.h b/Gems/Multiplayer/Code/Include/Multiplayer/IMultiplayerDebug.h new file mode 100644 index 0000000000..384db66317 --- /dev/null +++ b/Gems/Multiplayer/Code/Include/Multiplayer/IMultiplayerDebug.h @@ -0,0 +1,28 @@ +/* + * 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 + * + */ + +#pragma once + +namespace Multiplayer +{ + //! @class IMultiplayerDebug + //! @brief IMultiplayerDebug provides access to multiplayer debug overlays + class IMultiplayerDebug + { + public: + AZ_RTTI(IMultiplayerDebug, "{C5EB7F3A-E19F-4921-A604-C9BDC910123C}"); + + virtual ~IMultiplayerDebug() = default; + + //! + virtual void ShowEntityBandwidthDebugOverlay() = 0; + + //! + virtual void HideEntityBandwidthDebugOverlay() = 0; + }; +} diff --git a/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugByteReporter.cpp b/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugByteReporter.cpp index a29350e43a..38a1a27dcd 100644 --- a/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugByteReporter.cpp +++ b/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugByteReporter.cpp @@ -8,12 +8,10 @@ #include "MultiplayerDebugByteReporter.h" -#include +#include // for std::setfill #include #include -#pragma optimize("", off) - namespace Multiplayer { void MultiplayerDebugByteReporter::ReportBytes(size_t byteSize) diff --git a/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugPerEntityReporter.cpp b/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugPerEntityReporter.cpp index da32bbb666..b0bfe097e1 100644 --- a/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugPerEntityReporter.cpp +++ b/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugPerEntityReporter.cpp @@ -17,11 +17,6 @@ #include #endif -#pragma optimize("", off) - -AZ_CVAR(bool, net_DebugNetworkEntity_Bandwidth, true, nullptr, AZ::ConsoleFunctorFlags::Null, - "If true, prints debug text over entities that use a considerable amount of network traffic"); - AZ_CVAR(float, net_DebugNetworkEntity_ShowAboveKbps, 1.f, nullptr, AZ::ConsoleFunctorFlags::Null, "Prints bandwidth on network entities with higher kpbs than this value"); @@ -304,8 +299,8 @@ namespace Multiplayer void MultiplayerDebugPerEntityReporter::RecordRpcReceived( AZ::EntityId entityId, const char* entityName, - Multiplayer::NetComponentId netComponentId, - Multiplayer::RpcIndex rpcId, + NetComponentId netComponentId, + RpcIndex rpcId, uint32_t totalBytes) { if (const Multiplayer::MultiplayerComponentRegistry* componentRegistry = Multiplayer::GetMultiplayerComponentRegistry()) @@ -323,69 +318,66 @@ namespace Multiplayer void MultiplayerDebugPerEntityReporter::UpdateDebugOverlay() { - if (net_DebugNetworkEntity_Bandwidth) + m_networkEntitiesTraffic.clear(); + + for (AZStd::pair& entityPair : m_receivingEntityReports) { - m_networkEntitiesTraffic.clear(); - - for (AZStd::pair& entityPair : m_receivingEntityReports) - { - m_networkEntitiesTraffic[entityPair.first].m_name = entityPair.second.GetEntityName(); - m_networkEntitiesTraffic[entityPair.first].m_down = entityPair.second.GetKbitsPerSecond(); - } - - for (AZStd::pair& entityPair : m_sendingEntityReports) - { - m_networkEntitiesTraffic[entityPair.first].m_name = entityPair.second.GetEntityName(); - m_networkEntitiesTraffic[entityPair.first].m_up = entityPair.second.GetKbitsPerSecond(); - } - - //get debug display interface for the viewport - if (m_debugDisplay == nullptr) - { - AzFramework::DebugDisplayRequestBus::BusPtr debugDisplayBus; - AzFramework::DebugDisplayRequestBus::Bind(debugDisplayBus, AzFramework::g_defaultSceneEntityDebugDisplayId); - m_debugDisplay = AzFramework::DebugDisplayRequestBus::FindFirstHandler(debugDisplayBus); - } - - const AZ::u32 stateBefore = m_debugDisplay->GetState(); - - for (AZStd::pair& networkEntity : m_networkEntitiesTraffic) - { - if (networkEntity.second.m_down < net_DebugNetworkEntity_ShowAboveKbps && networkEntity.second.m_up < net_DebugNetworkEntity_ShowAboveKbps) - { - continue; - } - - if (networkEntity.second.m_down > net_DebugNetworkEntity_WarnAboveKbps || networkEntity.second.m_up > net_DebugNetworkEntity_WarnAboveKbps) - { - m_debugDisplay->SetColor(net_DebugNetworkEntity_WarningColor); - } - else - { - m_debugDisplay->SetColor(net_DebugNetworkEntity_BelowWarningColor); - } - - if (networkEntity.second.m_down > net_DebugNetworkEntity_ShowAboveKbps && networkEntity.second.m_up > net_DebugNetworkEntity_ShowAboveKbps) - { - azsprintf(m_statusBuffer, "[%s] %.0f down / %0.f up (kbps)", networkEntity.second.m_name, - networkEntity.second.m_down, networkEntity.second.m_up); - } - else if (networkEntity.second.m_down > net_DebugNetworkEntity_ShowAboveKbps) - { - azsprintf(m_statusBuffer, "[%s] %.0f down (kbps)", networkEntity.second.m_name, networkEntity.second.m_down); - } - else - { - azsprintf(m_statusBuffer, "[%s] %.0f up (kbps)", networkEntity.second.m_name, networkEntity.second.m_up); - } - - AZ::Vector3 entityPosition = AZ::Vector3::CreateZero(); - constexpr bool centerText = true; - AZ::TransformBus::EventResult(entityPosition, networkEntity.first, &AZ::TransformBus::Events::GetWorldTranslation); - m_debugDisplay->DrawTextLabel(entityPosition, 1.0f, m_statusBuffer, centerText, 0, 0); - } - - m_debugDisplay->SetState(stateBefore); + m_networkEntitiesTraffic[entityPair.first].m_name = entityPair.second.GetEntityName(); + m_networkEntitiesTraffic[entityPair.first].m_down = entityPair.second.GetKbitsPerSecond(); } + + for (AZStd::pair& entityPair : m_sendingEntityReports) + { + m_networkEntitiesTraffic[entityPair.first].m_name = entityPair.second.GetEntityName(); + m_networkEntitiesTraffic[entityPair.first].m_up = entityPair.second.GetKbitsPerSecond(); + } + + //get debug display interface for the viewport + if (m_debugDisplay == nullptr) + { + AzFramework::DebugDisplayRequestBus::BusPtr debugDisplayBus; + AzFramework::DebugDisplayRequestBus::Bind(debugDisplayBus, AzFramework::g_defaultSceneEntityDebugDisplayId); + m_debugDisplay = AzFramework::DebugDisplayRequestBus::FindFirstHandler(debugDisplayBus); + } + + const AZ::u32 stateBefore = m_debugDisplay->GetState(); + + for (AZStd::pair& networkEntity : m_networkEntitiesTraffic) + { + if (networkEntity.second.m_down < net_DebugNetworkEntity_ShowAboveKbps && networkEntity.second.m_up < net_DebugNetworkEntity_ShowAboveKbps) + { + continue; + } + + if (networkEntity.second.m_down > net_DebugNetworkEntity_WarnAboveKbps || networkEntity.second.m_up > net_DebugNetworkEntity_WarnAboveKbps) + { + m_debugDisplay->SetColor(net_DebugNetworkEntity_WarningColor); + } + else + { + m_debugDisplay->SetColor(net_DebugNetworkEntity_BelowWarningColor); + } + + if (networkEntity.second.m_down > net_DebugNetworkEntity_ShowAboveKbps && networkEntity.second.m_up > net_DebugNetworkEntity_ShowAboveKbps) + { + azsprintf(m_statusBuffer, "[%s] %.0f down / %0.f up (kbps)", networkEntity.second.m_name, + networkEntity.second.m_down, networkEntity.second.m_up); + } + else if (networkEntity.second.m_down > net_DebugNetworkEntity_ShowAboveKbps) + { + azsprintf(m_statusBuffer, "[%s] %.0f down (kbps)", networkEntity.second.m_name, networkEntity.second.m_down); + } + else + { + azsprintf(m_statusBuffer, "[%s] %.0f up (kbps)", networkEntity.second.m_name, networkEntity.second.m_up); + } + + AZ::Vector3 entityPosition = AZ::Vector3::CreateZero(); + constexpr bool centerText = true; + AZ::TransformBus::EventResult(entityPosition, networkEntity.first, &AZ::TransformBus::Events::GetWorldTranslation); + m_debugDisplay->DrawTextLabel(entityPosition, 1.0f, m_statusBuffer, centerText, 0, 0); + } + + m_debugDisplay->SetState(stateBefore); } } diff --git a/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugPerEntityReporter.h b/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugPerEntityReporter.h index d6ceaaf04c..8a33bc78ee 100644 --- a/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugPerEntityReporter.h +++ b/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugPerEntityReporter.h @@ -11,7 +11,6 @@ #include #include -#include #include #include #include @@ -66,7 +65,7 @@ namespace Multiplayer float m_down = 0.f; }; - AZStd::fixed_unordered_map m_networkEntitiesTraffic; + AZStd::unordered_map m_networkEntitiesTraffic; AzFramework::DebugDisplayRequests* m_debugDisplay = nullptr; }; diff --git a/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugSystemComponent.cpp b/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugSystemComponent.cpp index 03d79025f7..f892ee9724 100644 --- a/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugSystemComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugSystemComponent.cpp @@ -13,6 +13,11 @@ #include #include +void OnDebugNetworkEntity_ShowBandwidth_Changed(const bool& showBandwidth); + +AZ_CVAR(bool, net_DebugNetworkEntity_ShowBandwidth, false, &OnDebugNetworkEntity_ShowBandwidth_Changed, AZ::ConsoleFunctorFlags::Null, + "If true, prints bandwidth values over entities that use a considerable amount of network traffic"); + namespace Multiplayer { void MultiplayerDebugSystemComponent::Reflect(AZ::ReflectContext* context) @@ -53,11 +58,20 @@ namespace Multiplayer #endif } - void MultiplayerDebugSystemComponent::OnImGuiInitialize() + void MultiplayerDebugSystemComponent::ShowEntityBandwidthDebugOverlay() { m_reporter = AZStd::make_unique(); } + void MultiplayerDebugSystemComponent::HideEntityBandwidthDebugOverlay() + { + m_reporter.reset(); + } + + void MultiplayerDebugSystemComponent::OnImGuiInitialize() + { + } + #ifdef IMGUI_ENABLED void MultiplayerDebugSystemComponent::OnImGuiMainMenuUpdate() { @@ -327,15 +341,32 @@ namespace Multiplayer if (m_displayPerEntityStats) { - //ImGui::SetNextWindowSize({500, 400}); + // This overrides @net_DebugNetworkEntity_ShowBandwidth value if (ImGui::Begin("Multiplayer Per Entity Stats", &m_displayPerEntityStats, ImGuiWindowFlags_AlwaysAutoResize)) { if (m_reporter) { m_reporter->OnImGuiUpdate(); } + else + { + ShowEntityBandwidthDebugOverlay(); + } } } } #endif } + +void OnDebugNetworkEntity_ShowBandwidth_Changed(const bool& showBandwidth) +{ + if (showBandwidth) + { + AZ::Interface::Get()->ShowEntityBandwidthDebugOverlay(); + } + else + { + AZ::Interface::Get()->HideEntityBandwidthDebugOverlay(); + } +} + diff --git a/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugSystemComponent.h b/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugSystemComponent.h index a7f76e075a..87b238c79b 100644 --- a/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugSystemComponent.h +++ b/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugSystemComponent.h @@ -9,7 +9,9 @@ #pragma once #include +#include #include +#include #ifdef IMGUI_ENABLED # include @@ -20,6 +22,7 @@ namespace Multiplayer { class MultiplayerDebugSystemComponent final : public AZ::Component + , public AZ::Interface::Registrar #ifdef IMGUI_ENABLED , public ImGui::ImGuiUpdateListenerBus::Handler #endif @@ -30,7 +33,7 @@ namespace Multiplayer static void Reflect(AZ::ReflectContext* context); static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided); static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required); - static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatbile); + static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible); ~MultiplayerDebugSystemComponent() override = default; @@ -40,6 +43,12 @@ namespace Multiplayer void Deactivate() override; //! @} + //! IMultiplayerDebugSystem overrides + //! @{ + void ShowEntityBandwidthDebugOverlay() override; + void HideEntityBandwidthDebugOverlay() override; + //! @} + #ifdef IMGUI_ENABLED //! ImGui::ImGuiUpdateListenerBus overrides //! @{ diff --git a/Gems/Multiplayer/Code/multiplayer_files.cmake b/Gems/Multiplayer/Code/multiplayer_files.cmake index 7392ea6856..0b2adb1530 100644 --- a/Gems/Multiplayer/Code/multiplayer_files.cmake +++ b/Gems/Multiplayer/Code/multiplayer_files.cmake @@ -8,6 +8,7 @@ set(FILES Include/Multiplayer/IMultiplayer.h + Include/Multiplayer/IMultiplayerDebug.h Include/Multiplayer/IMultiplayerTools.h Include/Multiplayer/MultiplayerConstants.h Include/Multiplayer/MultiplayerStats.h From 0620f6dff3267d2a5efda4ee89babd064083c562 Mon Sep 17 00:00:00 2001 From: AMZN-Olex <5432499+AMZN-Olex@users.noreply.github.com> Date: Mon, 2 Aug 2021 19:06:02 -0400 Subject: [PATCH 09/37] Minor refactoring Signed-off-by: AMZN-Olex <5432499+AMZN-Olex@users.noreply.github.com> --- .../Source/Debug/MultiplayerDebugByteReporter.cpp | 3 --- .../Source/Debug/MultiplayerDebugByteReporter.h | 5 ----- .../Debug/MultiplayerDebugSystemComponent.cpp | 14 +++++++++----- .../Source/Debug/MultiplayerDebugSystemComponent.h | 2 ++ 4 files changed, 11 insertions(+), 13 deletions(-) diff --git a/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugByteReporter.cpp b/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugByteReporter.cpp index 38a1a27dcd..361f9d943d 100644 --- a/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugByteReporter.cpp +++ b/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugByteReporter.cpp @@ -169,7 +169,6 @@ namespace Multiplayer m_currentComponentReport = nullptr; } - m_gdeDirtyBytes.ReportAggregateBytes(); MultiplayerDebugByteReporter::ReportAggregateBytes(); } @@ -183,7 +182,6 @@ namespace Multiplayer } SetEntityName(other.GetEntityName()); - m_gdeDirtyBytes.Combine(other.m_gdeDirtyBytes); } void MultiplayerDebugEntityReporter::Reset() @@ -191,7 +189,6 @@ namespace Multiplayer MultiplayerDebugByteReporter::Reset(); m_componentReports.clear(); - m_gdeDirtyBytes.Reset(); } AZStd::map& MultiplayerDebugEntityReporter::GetComponentReports() diff --git a/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugByteReporter.h b/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugByteReporter.h index 934c6f3aac..279f7fb360 100644 --- a/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugByteReporter.h +++ b/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugByteReporter.h @@ -58,8 +58,6 @@ namespace Multiplayer using Report = AZStd::pair; AZStd::vector GetFieldReports(); - AZStd::size_t GetTotalDirtyBits() const { return m_componentDirtyBytes.GetTotalBytes(); } - float GetAvgDirtyBits() const { return m_componentDirtyBytes.GetAverageBytes(); } void Combine(const MultiplayerDebugComponentReporter& other); @@ -87,13 +85,10 @@ namespace Multiplayer } AZStd::map& GetComponentReports(); - AZStd::size_t GetTotalDirtyBits() const { return m_gdeDirtyBytes.GetTotalBytes(); } - float GetAvgDirtyBits() const { return m_gdeDirtyBytes.GetAverageBytes(); } private: MultiplayerDebugComponentReporter* m_currentComponentReport = nullptr; AZStd::map m_componentReports; - MultiplayerDebugByteReporter m_gdeDirtyBytes; AZStd::string m_entityName; }; } diff --git a/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugSystemComponent.cpp b/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugSystemComponent.cpp index f892ee9724..0e015fc86d 100644 --- a/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugSystemComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugSystemComponent.cpp @@ -341,17 +341,21 @@ namespace Multiplayer if (m_displayPerEntityStats) { - // This overrides @net_DebugNetworkEntity_ShowBandwidth value if (ImGui::Begin("Multiplayer Per Entity Stats", &m_displayPerEntityStats, ImGuiWindowFlags_AlwaysAutoResize)) { + if (ImGui::Checkbox("Show Bandwidth over Entities", &m_displayPerEntityBandwidth)) + { + // This overrides @net_DebugNetworkEntity_ShowBandwidth value + if (m_reporter == nullptr) + { + ShowEntityBandwidthDebugOverlay(); + } + } + if (m_reporter) { m_reporter->OnImGuiUpdate(); } - else - { - ShowEntityBandwidthDebugOverlay(); - } } } } diff --git a/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugSystemComponent.h b/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugSystemComponent.h index 87b238c79b..9be53cca02 100644 --- a/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugSystemComponent.h +++ b/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugSystemComponent.h @@ -60,7 +60,9 @@ namespace Multiplayer private: bool m_displayNetworkingStats = false; bool m_displayMultiplayerStats = false; + bool m_displayPerEntityStats = false; + bool m_displayPerEntityBandwidth = false; AZStd::unique_ptr m_reporter; }; From 854e5409505c0a80dc015b67d7455125e1e6d156 Mon Sep 17 00:00:00 2001 From: AMZN-Olex <5432499+AMZN-Olex@users.noreply.github.com> Date: Tue, 3 Aug 2021 12:42:48 -0400 Subject: [PATCH 10/37] Cleanup Signed-off-by: AMZN-Olex <5432499+AMZN-Olex@users.noreply.github.com> --- .../Debug/MultiplayerDebugByteReporter.cpp | 19 +++-- .../Debug/MultiplayerDebugByteReporter.h | 8 +- .../MultiplayerDebugPerEntityReporter.cpp | 76 ++++++++++--------- .../Debug/MultiplayerDebugPerEntityReporter.h | 18 ++--- .../Debug/MultiplayerDebugSystemComponent.cpp | 15 ++-- .../Debug/MultiplayerDebugSystemComponent.h | 4 +- 6 files changed, 74 insertions(+), 66 deletions(-) diff --git a/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugByteReporter.cpp b/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugByteReporter.cpp index 361f9d943d..f69291013a 100644 --- a/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugByteReporter.cpp +++ b/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugByteReporter.cpp @@ -14,6 +14,11 @@ namespace Multiplayer { + MultiplayerDebugByteReporter::MultiplayerDebugByteReporter() + { + MultiplayerDebugByteReporter::Reset(); + } + void MultiplayerDebugByteReporter::ReportBytes(size_t byteSize) { m_count++; @@ -80,9 +85,9 @@ namespace Multiplayer m_lastUpdateTime = now; } - constexpr float k_bitsPerByte = 8.0f; - constexpr int k_bitsPerKilobit = 1024; - return k_bitsPerByte * m_totalBytesLastSecond / k_bitsPerKilobit; + constexpr float bitsPerByte = 8.0f; + constexpr int bitsPerKilobit = 1024; + return bitsPerByte * m_totalBytesLastSecond / bitsPerKilobit; } void MultiplayerDebugByteReporter::Combine(const MultiplayerDebugByteReporter& other) @@ -139,9 +144,9 @@ namespace Multiplayer { MultiplayerDebugByteReporter::Combine(other); - for (const auto& fieldIter : other.m_fieldReports) + for (const auto& fieldIterator : other.m_fieldReports) { - m_fieldReports[fieldIter.first].Combine(fieldIter.second); + m_fieldReports[fieldIterator.first].Combine(fieldIterator.second); } m_componentDirtyBytes.Combine(other.m_componentDirtyBytes); @@ -176,9 +181,9 @@ namespace Multiplayer { MultiplayerDebugByteReporter::Combine(other); - for (const auto& componentIter : other.m_componentReports) + for (const auto& componentIterator : other.m_componentReports) { - m_componentReports[componentIter.first].Combine(componentIter.second); + m_componentReports[componentIterator.first].Combine(componentIterator.second); } SetEntityName(other.GetEntityName()); diff --git a/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugByteReporter.h b/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugByteReporter.h index 279f7fb360..8f7513d5d4 100644 --- a/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugByteReporter.h +++ b/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugByteReporter.h @@ -18,7 +18,7 @@ namespace Multiplayer class MultiplayerDebugByteReporter { public: - MultiplayerDebugByteReporter() { MultiplayerDebugByteReporter::Reset(); } + MultiplayerDebugByteReporter(); virtual ~MultiplayerDebugByteReporter() = default; void ReportBytes(size_t byteSize); @@ -48,7 +48,8 @@ namespace Multiplayer AZStd::chrono::monotonic_clock::time_point m_lastUpdateTime; }; - class MultiplayerDebugComponentReporter : public MultiplayerDebugByteReporter + class MultiplayerDebugComponentReporter final + : public MultiplayerDebugByteReporter { public: MultiplayerDebugComponentReporter() = default; @@ -66,7 +67,8 @@ namespace Multiplayer MultiplayerDebugByteReporter m_componentDirtyBytes; }; - class MultiplayerDebugEntityReporter : public MultiplayerDebugByteReporter + class MultiplayerDebugEntityReporter final + : public MultiplayerDebugByteReporter { public: MultiplayerDebugEntityReporter() = default; diff --git a/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugPerEntityReporter.cpp b/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugPerEntityReporter.cpp index b0bfe097e1..adfef397b6 100644 --- a/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugPerEntityReporter.cpp +++ b/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugPerEntityReporter.cpp @@ -17,16 +17,16 @@ #include #endif -AZ_CVAR(float, net_DebugNetworkEntity_ShowAboveKbps, 1.f, nullptr, AZ::ConsoleFunctorFlags::Null, +AZ_CVAR(float, net_DebugEntities_ShowAboveKbps, 1.f, nullptr, AZ::ConsoleFunctorFlags::Null, "Prints bandwidth on network entities with higher kpbs than this value"); -AZ_CVAR(float, net_DebugNetworkEntity_WarnAboveKbps, 10.f, nullptr, AZ::ConsoleFunctorFlags::Null, +AZ_CVAR(float, net_DebugEntities_WarnAboveKbps, 10.f, nullptr, AZ::ConsoleFunctorFlags::Null, "Prints bandwidth on network entities with higher kpbs than this value"); -AZ_CVAR(AZ::Color, net_DebugNetworkEntity_WarningColor, AZ::Colors::Red, nullptr, AZ::ConsoleFunctorFlags::Null, +AZ_CVAR(AZ::Color, net_DebugEntities_WarningColor, AZ::Colors::Red, nullptr, AZ::ConsoleFunctorFlags::Null, "If true, prints debug text over entities that use a considerable amount of network traffic"); -AZ_CVAR(AZ::Color, net_DebugNetworkEntity_BelowWarningColor, AZ::Colors::Grey, nullptr, AZ::ConsoleFunctorFlags::Null, +AZ_CVAR(AZ::Color, net_DebugEntities_BelowWarningColor, AZ::Colors::Grey, nullptr, AZ::ConsoleFunctorFlags::Null, "If true, prints debug text over entities that use a considerable amount of network traffic"); namespace Multiplayer @@ -133,7 +133,8 @@ namespace Multiplayer { RecordEntitySerializeStart(mode, entityId, entityName); }); - m_eventHandlers.m_componentSerializeEnd = decltype(m_eventHandlers.m_componentSerializeEnd)([this](AzNetworking::SerializerMode mode, Multiplayer::NetComponentId netComponentId) + m_eventHandlers.m_componentSerializeEnd = decltype(m_eventHandlers.m_componentSerializeEnd)([this](AzNetworking::SerializerMode mode, + NetComponentId netComponentId) { RecordComponentSerializeEnd(mode, netComponentId); }); @@ -141,19 +142,25 @@ namespace Multiplayer { RecordEntitySerializeStop(mode, entityId, entityName); }); - m_eventHandlers.m_propertySent = decltype(m_eventHandlers.m_propertySent)([this](Multiplayer::NetComponentId netComponentId, Multiplayer::PropertyIndex propertyId, uint32_t totalBytes) + m_eventHandlers.m_propertySent = decltype(m_eventHandlers.m_propertySent)([this](NetComponentId netComponentId, + PropertyIndex propertyId, uint32_t totalBytes) { RecordPropertySent(netComponentId, propertyId, totalBytes); }); - m_eventHandlers.m_propertyReceived = decltype(m_eventHandlers.m_propertyReceived)([this](Multiplayer::NetComponentId netComponentId, Multiplayer::PropertyIndex propertyId, uint32_t totalBytes) + m_eventHandlers.m_propertyReceived = decltype(m_eventHandlers.m_propertyReceived)([this](NetComponentId netComponentId, + PropertyIndex propertyId, uint32_t totalBytes) { RecordPropertyReceived(netComponentId, propertyId, totalBytes); }); - m_eventHandlers.m_rpcSent = decltype(m_eventHandlers.m_rpcSent)([this](AZ::EntityId entityId, const char* entityName, Multiplayer::NetComponentId netComponentId, Multiplayer::RpcIndex rpcId, uint32_t totalBytes) + m_eventHandlers.m_rpcSent = decltype(m_eventHandlers.m_rpcSent)([this](AZ::EntityId entityId, const char* entityName, + NetComponentId netComponentId, + RpcIndex rpcId, uint32_t totalBytes) { RecordRpcSent(entityId, entityName, netComponentId, rpcId, totalBytes); }); - m_eventHandlers.m_rpcReceived = decltype(m_eventHandlers.m_rpcReceived)([this](AZ::EntityId entityId, const char* entityName, Multiplayer::NetComponentId netComponentId, Multiplayer::RpcIndex rpcId, uint32_t totalBytes) + m_eventHandlers.m_rpcReceived = decltype(m_eventHandlers.m_rpcReceived)([this](AZ::EntityId entityId, const char* entityName, + NetComponentId netComponentId, + RpcIndex rpcId, uint32_t totalBytes) { RecordRpcSent(entityId, entityName, netComponentId, rpcId, totalBytes); }); @@ -161,11 +168,6 @@ namespace Multiplayer GetMultiplayer()->GetStats().ConnectHandlers(m_eventHandlers); } - MultiplayerDebugPerEntityReporter::~MultiplayerDebugPerEntityReporter() - { - m_updateDebugOverlay.RemoveFromQueue(); - } - // -------------------------------------------------------------------------------------------- void MultiplayerDebugPerEntityReporter::OnImGuiUpdate() { @@ -228,7 +230,8 @@ namespace Multiplayer } } - void MultiplayerDebugPerEntityReporter::RecordComponentSerializeEnd(AzNetworking::SerializerMode mode, [[maybe_unused]] Multiplayer::NetComponentId netComponentId) + void MultiplayerDebugPerEntityReporter::RecordComponentSerializeEnd(AzNetworking::SerializerMode mode, [[maybe_unused]] NetComponentId + netComponentId) { switch (mode) { @@ -256,11 +259,11 @@ namespace Multiplayer } void MultiplayerDebugPerEntityReporter::RecordPropertySent( - Multiplayer::NetComponentId netComponentId, - Multiplayer::PropertyIndex propertyId, + NetComponentId netComponentId, + PropertyIndex propertyId, uint32_t totalBytes) { - if (const Multiplayer::MultiplayerComponentRegistry* componentRegistry = Multiplayer::GetMultiplayerComponentRegistry()) + if (const MultiplayerComponentRegistry* componentRegistry = GetMultiplayerComponentRegistry()) { m_currentSendingEntityReport.ReportField(static_cast(netComponentId), componentRegistry->GetComponentName(netComponentId), @@ -269,11 +272,11 @@ namespace Multiplayer } void MultiplayerDebugPerEntityReporter::RecordPropertyReceived( - Multiplayer::NetComponentId netComponentId, - Multiplayer::PropertyIndex propertyId, + NetComponentId netComponentId, + PropertyIndex propertyId, uint32_t totalBytes) { - if (const Multiplayer::MultiplayerComponentRegistry* componentRegistry = Multiplayer::GetMultiplayerComponentRegistry()) + if (const MultiplayerComponentRegistry* componentRegistry = GetMultiplayerComponentRegistry()) { m_currentReceivingEntityReport.ReportField(static_cast(netComponentId), componentRegistry->GetComponentName(netComponentId), @@ -281,9 +284,10 @@ namespace Multiplayer } } - void MultiplayerDebugPerEntityReporter::RecordRpcSent(AZ::EntityId entityId, const char* entityName, Multiplayer::NetComponentId netComponentId, Multiplayer::RpcIndex rpcId, uint32_t totalBytes) + void MultiplayerDebugPerEntityReporter::RecordRpcSent(AZ::EntityId entityId, const char* entityName, NetComponentId netComponentId, + RpcIndex rpcId, uint32_t totalBytes) { - if (const Multiplayer::MultiplayerComponentRegistry* componentRegistry = Multiplayer::GetMultiplayerComponentRegistry()) + if (const MultiplayerComponentRegistry* componentRegistry = GetMultiplayerComponentRegistry()) { // MultiplayerDebugByteReporter requires a RecordEntitySerializeStart(AzNetworking::SerializerMode::ReadFromObject, entityId, entityName); @@ -303,7 +307,7 @@ namespace Multiplayer RpcIndex rpcId, uint32_t totalBytes) { - if (const Multiplayer::MultiplayerComponentRegistry* componentRegistry = Multiplayer::GetMultiplayerComponentRegistry()) + if (const MultiplayerComponentRegistry* componentRegistry = GetMultiplayerComponentRegistry()) { RecordEntitySerializeStart(AzNetworking::SerializerMode::WriteToObject, entityId, entityName); @@ -320,19 +324,18 @@ namespace Multiplayer { m_networkEntitiesTraffic.clear(); + // Merging up and down traffic to provide a unified debug text per entity for (AZStd::pair& entityPair : m_receivingEntityReports) { m_networkEntitiesTraffic[entityPair.first].m_name = entityPair.second.GetEntityName(); m_networkEntitiesTraffic[entityPair.first].m_down = entityPair.second.GetKbitsPerSecond(); } - for (AZStd::pair& entityPair : m_sendingEntityReports) { m_networkEntitiesTraffic[entityPair.first].m_name = entityPair.second.GetEntityName(); m_networkEntitiesTraffic[entityPair.first].m_up = entityPair.second.GetKbitsPerSecond(); } - //get debug display interface for the viewport if (m_debugDisplay == nullptr) { AzFramework::DebugDisplayRequestBus::BusPtr debugDisplayBus; @@ -342,28 +345,28 @@ namespace Multiplayer const AZ::u32 stateBefore = m_debugDisplay->GetState(); - for (AZStd::pair& networkEntity : m_networkEntitiesTraffic) + for (const AZStd::pair& networkEntity : m_networkEntitiesTraffic) { - if (networkEntity.second.m_down < net_DebugNetworkEntity_ShowAboveKbps && networkEntity.second.m_up < net_DebugNetworkEntity_ShowAboveKbps) + if (networkEntity.second.m_down < net_DebugEntities_ShowAboveKbps && networkEntity.second.m_up < net_DebugEntities_ShowAboveKbps) { continue; } - if (networkEntity.second.m_down > net_DebugNetworkEntity_WarnAboveKbps || networkEntity.second.m_up > net_DebugNetworkEntity_WarnAboveKbps) + if (networkEntity.second.m_down > net_DebugEntities_WarnAboveKbps || networkEntity.second.m_up > net_DebugEntities_WarnAboveKbps) { - m_debugDisplay->SetColor(net_DebugNetworkEntity_WarningColor); + m_debugDisplay->SetColor(net_DebugEntities_WarningColor); } else { - m_debugDisplay->SetColor(net_DebugNetworkEntity_BelowWarningColor); + m_debugDisplay->SetColor(net_DebugEntities_BelowWarningColor); } - if (networkEntity.second.m_down > net_DebugNetworkEntity_ShowAboveKbps && networkEntity.second.m_up > net_DebugNetworkEntity_ShowAboveKbps) + if (networkEntity.second.m_down > net_DebugEntities_ShowAboveKbps && networkEntity.second.m_up > net_DebugEntities_ShowAboveKbps) { azsprintf(m_statusBuffer, "[%s] %.0f down / %0.f up (kbps)", networkEntity.second.m_name, networkEntity.second.m_down, networkEntity.second.m_up); } - else if (networkEntity.second.m_down > net_DebugNetworkEntity_ShowAboveKbps) + else if (networkEntity.second.m_down > net_DebugEntities_ShowAboveKbps) { azsprintf(m_statusBuffer, "[%s] %.0f down (kbps)", networkEntity.second.m_name, networkEntity.second.m_down); } @@ -373,9 +376,12 @@ namespace Multiplayer } AZ::Vector3 entityPosition = AZ::Vector3::CreateZero(); - constexpr bool centerText = true; AZ::TransformBus::EventResult(entityPosition, networkEntity.first, &AZ::TransformBus::Events::GetWorldTranslation); - m_debugDisplay->DrawTextLabel(entityPosition, 1.0f, m_statusBuffer, centerText, 0, 0); + if (entityPosition.IsZero() == false) + { + constexpr bool centerText = true; + m_debugDisplay->DrawTextLabel(entityPosition, 1.0f, m_statusBuffer, centerText, 0, 0); + } } m_debugDisplay->SetState(stateBefore); diff --git a/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugPerEntityReporter.h b/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugPerEntityReporter.h index 8a33bc78ee..8a68110b93 100644 --- a/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugPerEntityReporter.h +++ b/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugPerEntityReporter.h @@ -18,34 +18,34 @@ namespace Multiplayer { /** - * \brief GridMate network live analysis tool via ImGui. + * \brief Multiplayer traffic live analysis tool via ImGui. */ class MultiplayerDebugPerEntityReporter { public: MultiplayerDebugPerEntityReporter(); - ~MultiplayerDebugPerEntityReporter(); - // main update loop + //! main update loop void OnImGuiUpdate(); //! Event handlers // @{ void RecordEntitySerializeStart(AzNetworking::SerializerMode mode, AZ::EntityId entityId, const char* entityName); - void RecordComponentSerializeEnd(AzNetworking::SerializerMode mode, Multiplayer::NetComponentId netComponentId); + void RecordComponentSerializeEnd(AzNetworking::SerializerMode mode, NetComponentId netComponentId); void RecordEntitySerializeStop(AzNetworking::SerializerMode mode, AZ::EntityId entityId, const char* entityName); - void RecordPropertySent(Multiplayer::NetComponentId netComponentId, Multiplayer::PropertyIndex propertyId, uint32_t totalBytes); - void RecordPropertyReceived(Multiplayer::NetComponentId netComponentId, Multiplayer::PropertyIndex propertyId, uint32_t totalBytes); - void RecordRpcSent(AZ::EntityId entityId, const char* entityName, Multiplayer::NetComponentId netComponentId, Multiplayer::RpcIndex rpcId, uint32_t totalBytes); - void RecordRpcReceived(AZ::EntityId entityId, const char* entityName, Multiplayer::NetComponentId netComponentId, Multiplayer::RpcIndex rpcId, uint32_t totalBytes); + void RecordPropertySent(NetComponentId netComponentId, PropertyIndex propertyId, uint32_t totalBytes); + void RecordPropertyReceived(NetComponentId netComponentId, PropertyIndex propertyId, uint32_t totalBytes); + void RecordRpcSent(AZ::EntityId entityId, const char* entityName, NetComponentId netComponentId, RpcIndex rpcId, uint32_t totalBytes); + void RecordRpcReceived(AZ::EntityId entityId, const char* entityName, NetComponentId netComponentId, RpcIndex rpcId, uint32_t totalBytes); // }@ + //! Draws bandwidth text over entities void UpdateDebugOverlay(); private: AZ::ScheduledEvent m_updateDebugOverlay; - Multiplayer::MultiplayerStats::EventHandlers m_eventHandlers; + MultiplayerStats::EventHandlers m_eventHandlers; AZStd::map m_sendingEntityReports{}; MultiplayerDebugEntityReporter m_currentSendingEntityReport; diff --git a/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugSystemComponent.cpp b/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugSystemComponent.cpp index 407b6f5f2d..b6dc9573fa 100644 --- a/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugSystemComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugSystemComponent.cpp @@ -13,9 +13,9 @@ #include #include -void OnDebugNetworkEntity_ShowBandwidth_Changed(const bool& showBandwidth); +void OnDebugEntities_ShowBandwidth_Changed(const bool& showBandwidth); -AZ_CVAR(bool, net_DebugNetworkEntity_ShowBandwidth, false, &OnDebugNetworkEntity_ShowBandwidth_Changed, AZ::ConsoleFunctorFlags::Null, +AZ_CVAR(bool, net_DebugEntities_ShowBandwidth, false, &OnDebugEntities_ShowBandwidth_Changed, AZ::ConsoleFunctorFlags::Null, "If true, prints bandwidth values over entities that use a considerable amount of network traffic"); namespace Multiplayer @@ -458,13 +458,10 @@ namespace Multiplayer { if (ImGui::Begin("Multiplayer Per Entity Stats", &m_displayPerEntityStats, ImGuiWindowFlags_AlwaysAutoResize)) { - if (ImGui::Checkbox("Show Bandwidth over Entities", &m_displayPerEntityBandwidth)) + // This overrides @net_DebugNetworkEntity_ShowBandwidth value + if (m_reporter == nullptr) { - // This overrides @net_DebugNetworkEntity_ShowBandwidth value - if (m_reporter == nullptr) - { - ShowEntityBandwidthDebugOverlay(); - } + ShowEntityBandwidthDebugOverlay(); } if (m_reporter) @@ -477,7 +474,7 @@ namespace Multiplayer #endif } -void OnDebugNetworkEntity_ShowBandwidth_Changed(const bool& showBandwidth) +void OnDebugEntities_ShowBandwidth_Changed(const bool& showBandwidth) { if (showBandwidth) { diff --git a/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugSystemComponent.h b/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugSystemComponent.h index 9be53cca02..7555073f10 100644 --- a/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugSystemComponent.h +++ b/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugSystemComponent.h @@ -43,7 +43,7 @@ namespace Multiplayer void Deactivate() override; //! @} - //! IMultiplayerDebugSystem overrides + //! IMultiplayerDebug overrides //! @{ void ShowEntityBandwidthDebugOverlay() override; void HideEntityBandwidthDebugOverlay() override; @@ -62,8 +62,6 @@ namespace Multiplayer bool m_displayMultiplayerStats = false; bool m_displayPerEntityStats = false; - bool m_displayPerEntityBandwidth = false; - AZStd::unique_ptr m_reporter; }; } From 2118ef7a21ad2d5d80979e80d9516e9a6177a443 Mon Sep 17 00:00:00 2001 From: AMZN-Olex <5432499+AMZN-Olex@users.noreply.github.com> Date: Tue, 3 Aug 2021 12:48:36 -0400 Subject: [PATCH 11/37] Preparing for PR Signed-off-by: AMZN-Olex <5432499+AMZN-Olex@users.noreply.github.com> --- Gems/Multiplayer/Code/Include/Multiplayer/IMultiplayerDebug.h | 4 ++-- .../Code/Source/Debug/MultiplayerDebugSystemComponent.cpp | 4 ---- .../Code/Source/Debug/MultiplayerDebugSystemComponent.h | 1 - 3 files changed, 2 insertions(+), 7 deletions(-) diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/IMultiplayerDebug.h b/Gems/Multiplayer/Code/Include/Multiplayer/IMultiplayerDebug.h index 384db66317..e528a44ed9 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/IMultiplayerDebug.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/IMultiplayerDebug.h @@ -19,10 +19,10 @@ namespace Multiplayer virtual ~IMultiplayerDebug() = default; - //! + //! Enables printing of debug text over entities that have significant amount of traffic. virtual void ShowEntityBandwidthDebugOverlay() = 0; - //! + //! Disables printing of debug text over entities that have significant amount of traffic. virtual void HideEntityBandwidthDebugOverlay() = 0; }; } diff --git a/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugSystemComponent.cpp b/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugSystemComponent.cpp index b6dc9573fa..27a4dff485 100644 --- a/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugSystemComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugSystemComponent.cpp @@ -63,10 +63,6 @@ namespace Multiplayer m_reporter.reset(); } - void MultiplayerDebugSystemComponent::OnImGuiInitialize() - { - } - #ifdef IMGUI_ENABLED void MultiplayerDebugSystemComponent::OnImGuiMainMenuUpdate() { diff --git a/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugSystemComponent.h b/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugSystemComponent.h index 7555073f10..7b3c852f58 100644 --- a/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugSystemComponent.h +++ b/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugSystemComponent.h @@ -52,7 +52,6 @@ namespace Multiplayer #ifdef IMGUI_ENABLED //! ImGui::ImGuiUpdateListenerBus overrides //! @{ - void OnImGuiInitialize() override; void OnImGuiMainMenuUpdate() override; void OnImGuiUpdate() override; //! @} From 72aed355cedf9fe28e24e396c64db93a4b1946a2 Mon Sep 17 00:00:00 2001 From: AMZN-Olex <5432499+AMZN-Olex@users.noreply.github.com> Date: Tue, 3 Aug 2021 12:54:32 -0400 Subject: [PATCH 12/37] Cleanup Signed-off-by: AMZN-Olex <5432499+AMZN-Olex@users.noreply.github.com> --- .../Source/NetworkEntity/EntityReplication/EntityReplicator.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicator.cpp b/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicator.cpp index 457ed3eed5..4155f8813b 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicator.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicator.cpp @@ -30,8 +30,6 @@ #include -#pragma optimize("", off) - namespace Multiplayer { EntityReplicator::EntityReplicator From a550827e978d97abb83c1bb4a03aaca4aa5ffdfd Mon Sep 17 00:00:00 2001 From: AMZN-Olex <5432499+AMZN-Olex@users.noreply.github.com> Date: Wed, 4 Aug 2021 14:32:15 -0400 Subject: [PATCH 13/37] Addressing PR Signed-off-by: AMZN-Olex <5432499+AMZN-Olex@users.noreply.github.com> --- .../Code/Source/Debug/MultiplayerDebugByteReporter.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugByteReporter.cpp b/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugByteReporter.cpp index f69291013a..45305e3b39 100644 --- a/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugByteReporter.cpp +++ b/Gems/Multiplayer/Code/Source/Debug/MultiplayerDebugByteReporter.cpp @@ -43,7 +43,6 @@ namespace Multiplayer { if (m_count == 0) { - AZ_Warning("MultiplayerDebugByteReporter", m_totalBytes == 0, "Attempted to average bytes with a zero count."); return 0.0f; } @@ -158,7 +157,7 @@ namespace Multiplayer if (m_currentComponentReport == nullptr) { std::stringstream component; - component << "[" << std::setw(2) << std::setfill('0') << static_cast(index) << "]" << " " << componentName; + component << "[" << std::setw(2) << std::setfill('0') << aznumeric_cast(index) << "]" << " " << componentName; m_currentComponentReport = &m_componentReports[component.str().c_str()]; } From b4bf775647c3d2bc03a94988226e1751644ed9ae Mon Sep 17 00:00:00 2001 From: pappeste Date: Wed, 16 Jun 2021 17:14:27 -0700 Subject: [PATCH 14/37] enable the warning Signed-off-by: Esteban Papp <81431996+amznestebanpapp@users.noreply.github.com> --- cmake/Platform/Common/MSVC/Configurations_msvc.cmake | 1 - 1 file changed, 1 deletion(-) diff --git a/cmake/Platform/Common/MSVC/Configurations_msvc.cmake b/cmake/Platform/Common/MSVC/Configurations_msvc.cmake index 4024e45bad..118a515e30 100644 --- a/cmake/Platform/Common/MSVC/Configurations_msvc.cmake +++ b/cmake/Platform/Common/MSVC/Configurations_msvc.cmake @@ -38,7 +38,6 @@ ly_append_configurations_options( /wd4201 # nonstandard extension used: nameless struct/union. This actually became part of the C++11 std, MS has an open issue: https://developercommunity.visualstudio.com/t/warning-level-4-generates-a-bogus-warning-c4201-no/103064 # Disabling these warnings while they get fixed - /wd4018 # signed/unsigned mismatch /wd4244 # conversion, possible loss of data /wd4245 # conversion, signed/unsigned mismatch /wd4389 # comparison, signed/unsigned mismatch From 97354ce90a9ddd6600271df1e663e21790fc8dc1 Mon Sep 17 00:00:00 2001 From: pappeste Date: Fri, 18 Jun 2021 16:36:30 -0700 Subject: [PATCH 15/37] Sandbox Signed-off-by: Esteban Papp <81431996+amznestebanpapp@users.noreply.github.com> --- Code/Editor/ViewportTitleDlg.cpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/Code/Editor/ViewportTitleDlg.cpp b/Code/Editor/ViewportTitleDlg.cpp index c1e1afb908..cbf2958eea 100644 --- a/Code/Editor/ViewportTitleDlg.cpp +++ b/Code/Editor/ViewportTitleDlg.cpp @@ -451,19 +451,19 @@ void CViewportTitleDlg::AddFOVMenus(QMenu* menu, std::function call { for (size_t i = 0; i < customPresets.size(); ++i) { - if (customPresets[i].isEmpty()) + if (customPresets[static_cast(i)].isEmpty()) { break; } float fov = gSettings.viewports.fDefaultFov; bool ok; - float f = customPresets[i].toDouble(&ok); + float f = customPresets[static_cast(i)].toDouble(&ok); if (ok) { fov = std::max(1.0f, f); fov = std::min(120.0f, f); - QAction* action = menu->addAction(customPresets[i]); + QAction* action = menu->addAction(customPresets[static_cast(i)]); connect(action, &QAction::triggered, action, [fov, callback](){ callback(fov); }); } } @@ -537,13 +537,13 @@ void CViewportTitleDlg::AddAspectRatioMenus(QMenu* menu, std::function(i)].isEmpty()) { break; } static QRegularExpression regex(QStringLiteral("^(\\d+):(\\d+)$")); - QRegularExpressionMatch matches = regex.match(customPresets[i]); + QRegularExpressionMatch matches = regex.match(customPresets[static_cast(i)]); if (matches.hasMatch()) { bool ok; @@ -551,7 +551,7 @@ void CViewportTitleDlg::AddAspectRatioMenus(QMenu* menu, std::functionaddAction(customPresets[i]); + QAction* action = menu->addAction(customPresets[static_cast(i)]); connect(action, &QAction::triggered, action, [width, height, callback]() {callback(width, height); }); } } @@ -668,13 +668,13 @@ void CViewportTitleDlg::AddResolutionMenus(QMenu* menu, std::function(i)].isEmpty()) { break; } static QRegularExpression regex(QStringLiteral("^(\\d+) x (\\d+)$")); - QRegularExpressionMatch matches = regex.match(customPresets[i]); + QRegularExpressionMatch matches = regex.match(customPresets[static_cast(i)]); if (matches.hasMatch()) { bool ok; @@ -682,7 +682,7 @@ void CViewportTitleDlg::AddResolutionMenus(QMenu* menu, std::functionaddAction(customPresets[i]); + QAction* action = menu->addAction(customPresets[static_cast(i)]); connect(action, &QAction::triggered, action, [width, height, callback](){ callback(width, height); }); } } From 10f950b726ca9e6d8fe7f3c776b6094d9027dcce Mon Sep 17 00:00:00 2001 From: pappeste Date: Fri, 18 Jun 2021 17:45:51 -0700 Subject: [PATCH 16/37] fix w4018 Signed-off-by: Esteban Papp <81431996+amznestebanpapp@users.noreply.github.com> --- .../Prefab/Benchmark/PrefabBenchmarkFixture.cpp | 4 ++-- .../Prefab/Benchmark/PrefabCreateBenchmarks.cpp | 6 +++--- .../Benchmark/PrefabInstantiateBenchmarks.cpp | 2 +- .../Prefab/Benchmark/PrefabLoadBenchmarks.cpp | 2 +- Code/Legacy/CrySystem/SystemWin32.cpp | 2 +- .../SDKWrapper/AssImpMaterialWrapper.cpp | 2 +- .../Importers/AssImpAnimationImporter.cpp | 4 ++-- .../Importers/AssImpBitangentStreamImporter.cpp | 4 ++-- .../Importers/AssImpBlendShapeImporter.cpp | 8 ++++---- .../Importers/AssImpColorStreamImporter.cpp | 9 ++++----- .../Importers/AssImpMaterialImporter.cpp | 2 +- .../Importers/AssImpTangentStreamImporter.cpp | 4 ++-- .../Importers/AssImpUvMapImporter.cpp | 6 +++--- .../Utilities/AssImpMeshImporterUtilities.cpp | 8 ++++---- .../Code/Source/AWSCoreSystemComponent.cpp | 2 +- .../Attribution/AWSCoreAttributionManager.cpp | 2 +- Gems/AWSMetrics/Code/Source/MetricsManager.cpp | 4 ++-- Gems/AWSMetrics/Code/Source/MetricsQueue.cpp | 2 +- .../AtomFont/Code/Source/FontRenderer.cpp | 4 ++-- .../Code/Source/Family/BlastFamilyImpl.cpp | 2 +- Gems/Blast/Code/Tests/Mocks/BlastMocks.h | 2 +- Gems/EMotionFX/Code/EMotionFX/Source/Actor.cpp | 2 +- .../Code/Tests/GradientSignalImageTests.cpp | 8 ++++---- Gems/ImGui/Code/Source/ImGuiManager.cpp | 2 +- .../Code/Source/Shape/ShapeGeometryUtil.cpp | 17 ++++++++--------- .../Editor/Animation/UiAnimViewSequence.cpp | 4 ++-- Gems/LyShine/Code/Editor/SpriteBorderEditor.cpp | 4 ++-- .../LyShine/Code/Source/EditorPropertyTypes.cpp | 3 ++- Gems/LyShine/Code/Source/UiImageComponent.cpp | 12 ++++++------ .../LyShine/Code/Source/UiInteractableState.cpp | 2 +- .../Code/Source/UiParticleEmitterComponent.cpp | 4 ++-- Gems/LyShine/Code/Source/UiTextComponent.cpp | 2 +- Gems/PhysXDebug/Code/Source/SystemComponent.cpp | 4 ++-- 33 files changed, 72 insertions(+), 73 deletions(-) diff --git a/Code/Framework/AzToolsFramework/Tests/Prefab/Benchmark/PrefabBenchmarkFixture.cpp b/Code/Framework/AzToolsFramework/Tests/Prefab/Benchmark/PrefabBenchmarkFixture.cpp index 7e47fa9569..9925d5bff3 100644 --- a/Code/Framework/AzToolsFramework/Tests/Prefab/Benchmark/PrefabBenchmarkFixture.cpp +++ b/Code/Framework/AzToolsFramework/Tests/Prefab/Benchmark/PrefabBenchmarkFixture.cpp @@ -85,7 +85,7 @@ namespace Benchmark void BM_Prefab::CreateEntities(const unsigned int entityCount, AZStd::vector& entities) { - for (int entityIndex = 0; entityIndex < entityCount; ++entityIndex) + for (unsigned int entityIndex = 0; entityIndex < entityCount; ++entityIndex) { AZStd::string entityName = "TestEntity"; entityName = entityName + AZStd::to_string(entityIndex); @@ -101,7 +101,7 @@ namespace Benchmark void BM_Prefab::CreateFakePaths(const unsigned int pathCount) { //setup fake paths - for (int number = 0; number < pathCount; ++number) + for (unsigned int number = 0; number < pathCount; ++number) { AZStd::string path = m_pathString; m_paths.push_back(path + AZStd::to_string(number) + "_" + AZStd::to_string(pathCount)); diff --git a/Code/Framework/AzToolsFramework/Tests/Prefab/Benchmark/PrefabCreateBenchmarks.cpp b/Code/Framework/AzToolsFramework/Tests/Prefab/Benchmark/PrefabCreateBenchmarks.cpp index b809a20944..12efea1cc8 100644 --- a/Code/Framework/AzToolsFramework/Tests/Prefab/Benchmark/PrefabCreateBenchmarks.cpp +++ b/Code/Framework/AzToolsFramework/Tests/Prefab/Benchmark/PrefabCreateBenchmarks.cpp @@ -32,7 +32,7 @@ namespace Benchmark state.ResumeTiming(); - for (int instanceCounter = 0; instanceCounter < numInstances; ++instanceCounter) + for (unsigned int instanceCounter = 0; instanceCounter < numInstances; ++instanceCounter) { newInstances.push_back(m_prefabSystemComponent->CreatePrefab( { entities[instanceCounter] }, @@ -110,7 +110,7 @@ namespace Benchmark AZStd::vector> testInstances; testInstances.resize(numInstancesToAdd); - for (int instanceCounter = 0; instanceCounter < numInstancesToAdd; ++instanceCounter) + for (unsigned int instanceCounter = 0; instanceCounter < numInstancesToAdd; ++instanceCounter) { testInstances[instanceCounter] = (m_prefabSystemComponent->CreatePrefab( { entities[instanceCounter] } @@ -161,7 +161,7 @@ namespace Benchmark state.ResumeTiming(); - for (int instanceCounter = 0; instanceCounter < numInstances; ++instanceCounter) + for (unsigned int instanceCounter = 0; instanceCounter < numInstances; ++instanceCounter) { nestedInstanceRoot = m_prefabSystemComponent->CreatePrefab( {}, diff --git a/Code/Framework/AzToolsFramework/Tests/Prefab/Benchmark/PrefabInstantiateBenchmarks.cpp b/Code/Framework/AzToolsFramework/Tests/Prefab/Benchmark/PrefabInstantiateBenchmarks.cpp index 3e9f0ab08e..90ff30a30e 100644 --- a/Code/Framework/AzToolsFramework/Tests/Prefab/Benchmark/PrefabInstantiateBenchmarks.cpp +++ b/Code/Framework/AzToolsFramework/Tests/Prefab/Benchmark/PrefabInstantiateBenchmarks.cpp @@ -33,7 +33,7 @@ namespace Benchmark state.ResumeTiming(); - for (int instanceCounter = 0; instanceCounter < numInstances; ++instanceCounter) + for (unsigned int instanceCounter = 0; instanceCounter < numInstances; ++instanceCounter) { newInstances[instanceCounter] = m_prefabSystemComponent->InstantiatePrefab(templateToInstantiateId); } diff --git a/Code/Framework/AzToolsFramework/Tests/Prefab/Benchmark/PrefabLoadBenchmarks.cpp b/Code/Framework/AzToolsFramework/Tests/Prefab/Benchmark/PrefabLoadBenchmarks.cpp index 7e64fa886c..a6c1e27caf 100644 --- a/Code/Framework/AzToolsFramework/Tests/Prefab/Benchmark/PrefabLoadBenchmarks.cpp +++ b/Code/Framework/AzToolsFramework/Tests/Prefab/Benchmark/PrefabLoadBenchmarks.cpp @@ -29,7 +29,7 @@ namespace Benchmark state.ResumeTiming(); - for (int templateCounter = 0; templateCounter < numTemplates; ++templateCounter) + for (unsigned int templateCounter = 0; templateCounter < numTemplates; ++templateCounter) { m_prefabLoaderInterface->LoadTemplateFromFile(m_paths[templateCounter]); } diff --git a/Code/Legacy/CrySystem/SystemWin32.cpp b/Code/Legacy/CrySystem/SystemWin32.cpp index 2cc41adc08..3e499f9853 100644 --- a/Code/Legacy/CrySystem/SystemWin32.cpp +++ b/Code/Legacy/CrySystem/SystemWin32.cpp @@ -410,7 +410,7 @@ void CSystem::debug_GetCallStack(const char** pFunctions, int& nCount) unsigned int numFrames = StackRecorder::Record(frames, nMaxCount, 1); SymbolStorage::StackLine* textLines = (SymbolStorage::StackLine*)AZ_ALLOCA(sizeof(SymbolStorage::StackLine)*nMaxCount); SymbolStorage::DecodeFrames(frames, numFrames, textLines); - for (int i = 0; i < numFrames; i++) + for (unsigned int i = 0; i < numFrames; i++) { pFunctions[i] = textLines[i]; } diff --git a/Code/Tools/SceneAPI/SDKWrapper/AssImpMaterialWrapper.cpp b/Code/Tools/SceneAPI/SDKWrapper/AssImpMaterialWrapper.cpp index c24577a6de..8ea25a2ff0 100644 --- a/Code/Tools/SceneAPI/SDKWrapper/AssImpMaterialWrapper.cpp +++ b/Code/Tools/SceneAPI/SDKWrapper/AssImpMaterialWrapper.cpp @@ -194,7 +194,7 @@ namespace AZ AZStd::string AssImpMaterialWrapper::GetTextureFileName(MaterialMapType textureType) const { /// Engine currently doesn't support multiple textures. Right now we only use first texture. - int textureIndex = 0; + unsigned int textureIndex = 0; aiString absTexturePath; switch (textureType) { diff --git a/Code/Tools/SceneAPI/SceneBuilder/Importers/AssImpAnimationImporter.cpp b/Code/Tools/SceneAPI/SceneBuilder/Importers/AssImpAnimationImporter.cpp index aadb834aa9..da1db22d5e 100644 --- a/Code/Tools/SceneAPI/SceneBuilder/Importers/AssImpAnimationImporter.cpp +++ b/Code/Tools/SceneAPI/SceneBuilder/Importers/AssImpAnimationImporter.cpp @@ -612,10 +612,10 @@ namespace AZ ValueToKeyDataMap valueToKeyDataMap; // Key time can be less than zero, normalize to have zero be the lowest time. double keyOffset = 0; - for (int keyIdx = 0; keyIdx < meshMorphAnim->mNumKeys; keyIdx++) + for (unsigned int keyIdx = 0; keyIdx < meshMorphAnim->mNumKeys; keyIdx++) { aiMeshMorphKey& key = meshMorphAnim->mKeys[keyIdx]; - for (int valIdx = 0; valIdx < key.mNumValuesAndWeights; ++valIdx) + for (unsigned int valIdx = 0; valIdx < key.mNumValuesAndWeights; ++valIdx) { int currentValue = key.mValues[valIdx]; KeyData thisKey(key.mWeights[valIdx], key.mTime); diff --git a/Code/Tools/SceneAPI/SceneBuilder/Importers/AssImpBitangentStreamImporter.cpp b/Code/Tools/SceneAPI/SceneBuilder/Importers/AssImpBitangentStreamImporter.cpp index 51e0147599..b9b9af1e65 100644 --- a/Code/Tools/SceneAPI/SceneBuilder/Importers/AssImpBitangentStreamImporter.cpp +++ b/Code/Tools/SceneAPI/SceneBuilder/Importers/AssImpBitangentStreamImporter.cpp @@ -89,11 +89,11 @@ namespace AZ bitangentStream->SetGenerationMethod(AZ::SceneAPI::DataTypes::TangentGenerationMethod::FromSourceScene); bitangentStream->ReserveContainerSpace(vertexCount); - for (int sdkMeshIndex = 0; sdkMeshIndex < currentNode->mNumMeshes; ++sdkMeshIndex) + for (unsigned int sdkMeshIndex = 0; sdkMeshIndex < currentNode->mNumMeshes; ++sdkMeshIndex) { const aiMesh* mesh = scene->mMeshes[currentNode->mMeshes[sdkMeshIndex]]; - for (int v = 0; v < mesh->mNumVertices; ++v) + for (unsigned int v = 0; v < mesh->mNumVertices; ++v) { if (!mesh->HasTangentsAndBitangents()) { diff --git a/Code/Tools/SceneAPI/SceneBuilder/Importers/AssImpBlendShapeImporter.cpp b/Code/Tools/SceneAPI/SceneBuilder/Importers/AssImpBlendShapeImporter.cpp index d139a1c86c..e8eb5ecf68 100644 --- a/Code/Tools/SceneAPI/SceneBuilder/Importers/AssImpBlendShapeImporter.cpp +++ b/Code/Tools/SceneAPI/SceneBuilder/Importers/AssImpBlendShapeImporter.cpp @@ -85,7 +85,7 @@ namespace AZ { int sceneMeshIdx = context.m_sourceNode.GetAssImpNode()->mMeshes[nodeMeshIdx]; const aiMesh* aiMesh = context.m_sourceScene.GetAssImpScene()->mMeshes[sceneMeshIdx]; - for (int animIdx = 0; animIdx < aiMesh->mNumAnimMeshes; animIdx++) + for (unsigned int animIdx = 0; animIdx < aiMesh->mNumAnimMeshes; animIdx++) { aiAnimMesh* aiAnimMesh = aiMesh->mAnimMeshes[animIdx]; animToMeshToAnimMeshIndices[aiAnimMesh->mName.C_Str()].emplace_back(nodeMeshIdx, animIdx); @@ -130,7 +130,7 @@ namespace AZ blendShapeData->ReserveData( aiAnimMesh->mNumVertices, aiAnimMesh->HasTangentsAndBitangents(), uvSetUsedFlags, colorSetUsedFlags); - for (int vertIdx = 0; vertIdx < aiAnimMesh->mNumVertices; ++vertIdx) + for (unsigned int vertIdx = 0; vertIdx < aiAnimMesh->mNumVertices; ++vertIdx) { AZ::Vector3 vertex(AssImpSDKWrapper::AssImpTypeConverter::ToVector3(aiAnimMesh->mVertices[vertIdx])); @@ -184,7 +184,7 @@ namespace AZ } // aiAnimMesh just has a list of positions for vertices. The face indices are on the original mesh. - for (int faceIdx = 0; faceIdx < aiMesh->mNumFaces; ++faceIdx) + for (unsigned int faceIdx = 0; faceIdx < aiMesh->mNumFaces; ++faceIdx) { aiFace face = aiMesh->mFaces[faceIdx]; DataTypes::IBlendShapeData::Face blendFace; @@ -199,7 +199,7 @@ namespace AZ face.mNumIndices); continue; } - for (int idx = 0; idx < face.mNumIndices; ++idx) + for (unsigned int idx = 0; idx < face.mNumIndices; ++idx) { blendFace.vertexIndex[idx] = face.mIndices[idx] + vertexOffset; } diff --git a/Code/Tools/SceneAPI/SceneBuilder/Importers/AssImpColorStreamImporter.cpp b/Code/Tools/SceneAPI/SceneBuilder/Importers/AssImpColorStreamImporter.cpp index 438e56e8ad..4043f529df 100644 --- a/Code/Tools/SceneAPI/SceneBuilder/Importers/AssImpColorStreamImporter.cpp +++ b/Code/Tools/SceneAPI/SceneBuilder/Importers/AssImpColorStreamImporter.cpp @@ -56,7 +56,7 @@ namespace AZ const aiScene* scene = context.m_sourceScene.GetAssImpScene(); // This node has at least one mesh, verify that the color channel counts are the same for all meshes. - const int expectedColorChannels = scene->mMeshes[currentNode->mMeshes[0]]->GetNumColorChannels(); + const unsigned int expectedColorChannels = scene->mMeshes[currentNode->mMeshes[0]]->GetNumColorChannels(); const bool allMeshesHaveSameNumberOfColorChannels = AZStd::all_of(currentNode->mMeshes + 1, currentNode->mMeshes + currentNode->mNumMeshes, [scene, expectedColorChannels](const unsigned int meshIndex) { @@ -80,17 +80,16 @@ namespace AZ const uint64_t vertexCount = GetVertexCountForAllMeshesOnNode(*currentNode, *scene); Events::ProcessingResultCombiner combinedVertexColorResults; - for (int colorSetIndex = 0; colorSetIndex < expectedColorChannels; ++colorSetIndex) + for (unsigned int colorSetIndex = 0; colorSetIndex < expectedColorChannels; ++colorSetIndex) { - AZStd::shared_ptr vertexColors = AZStd::make_shared(); vertexColors->ReserveContainerSpace(vertexCount); - for (int sdkMeshIndex = 0; sdkMeshIndex < currentNode->mNumMeshes; ++sdkMeshIndex) + for (unsigned int sdkMeshIndex = 0; sdkMeshIndex < currentNode->mNumMeshes; ++sdkMeshIndex) { const aiMesh* mesh = scene->mMeshes[currentNode->mMeshes[sdkMeshIndex]]; - for (int v = 0; v < mesh->mNumVertices; ++v) + for (unsigned int v = 0; v < mesh->mNumVertices; ++v) { if (colorSetIndex < mesh->GetNumColorChannels()) { diff --git a/Code/Tools/SceneAPI/SceneBuilder/Importers/AssImpMaterialImporter.cpp b/Code/Tools/SceneAPI/SceneBuilder/Importers/AssImpMaterialImporter.cpp index 4880c8d736..8983c76bac 100644 --- a/Code/Tools/SceneAPI/SceneBuilder/Importers/AssImpMaterialImporter.cpp +++ b/Code/Tools/SceneAPI/SceneBuilder/Importers/AssImpMaterialImporter.cpp @@ -56,7 +56,7 @@ namespace AZ Events::ProcessingResultCombiner combinedMaterialImportResults; AZStd::unordered_map> materialMap; - for (int idx = 0; idx < context.m_sourceNode.m_assImpNode->mNumMeshes; ++idx) + for (unsigned int idx = 0; idx < context.m_sourceNode.m_assImpNode->mNumMeshes; ++idx) { int meshIndex = context.m_sourceNode.m_assImpNode->mMeshes[idx]; const aiMesh* assImpMesh = context.m_sourceScene.GetAssImpScene()->mMeshes[meshIndex]; diff --git a/Code/Tools/SceneAPI/SceneBuilder/Importers/AssImpTangentStreamImporter.cpp b/Code/Tools/SceneAPI/SceneBuilder/Importers/AssImpTangentStreamImporter.cpp index 6f1c364399..fbafd3d24f 100644 --- a/Code/Tools/SceneAPI/SceneBuilder/Importers/AssImpTangentStreamImporter.cpp +++ b/Code/Tools/SceneAPI/SceneBuilder/Importers/AssImpTangentStreamImporter.cpp @@ -91,11 +91,11 @@ namespace AZ tangentStream->SetGenerationMethod(AZ::SceneAPI::DataTypes::TangentGenerationMethod::FromSourceScene); tangentStream->ReserveContainerSpace(vertexCount); - for (int sdkMeshIndex = 0; sdkMeshIndex < currentNode->mNumMeshes; ++sdkMeshIndex) + for (unsigned int sdkMeshIndex = 0; sdkMeshIndex < currentNode->mNumMeshes; ++sdkMeshIndex) { const aiMesh* mesh = scene->mMeshes[currentNode->mMeshes[sdkMeshIndex]]; - for (int v = 0; v < mesh->mNumVertices; ++v) + for (unsigned int v = 0; v < mesh->mNumVertices; ++v) { if (!mesh->HasTangentsAndBitangents()) { diff --git a/Code/Tools/SceneAPI/SceneBuilder/Importers/AssImpUvMapImporter.cpp b/Code/Tools/SceneAPI/SceneBuilder/Importers/AssImpUvMapImporter.cpp index 12e13422cf..fc0ac15244 100644 --- a/Code/Tools/SceneAPI/SceneBuilder/Importers/AssImpUvMapImporter.cpp +++ b/Code/Tools/SceneAPI/SceneBuilder/Importers/AssImpUvMapImporter.cpp @@ -62,7 +62,7 @@ namespace AZ // so they can be separated by engine code instead. bool foundTextureCoordinates = false; AZStd::array meshesPerTextureCoordinateIndex = {}; - for (int localMeshIndex = 0; localMeshIndex < currentNode->mNumMeshes; ++localMeshIndex) + for (unsigned int localMeshIndex = 0; localMeshIndex < currentNode->mNumMeshes; ++localMeshIndex) { aiMesh* mesh = scene->mMeshes[currentNode->mMeshes[localMeshIndex]]; for (int texCoordIndex = 0; texCoordIndex < meshesPerTextureCoordinateIndex.size(); ++texCoordIndex) @@ -110,7 +110,7 @@ namespace AZ uvMap->ReserveContainerSpace(vertexCount); bool customNameFound = false; AZStd::string name(AZStd::string::format("%s%d", m_defaultNodeName, texCoordIndex)); - for (int sdkMeshIndex = 0; sdkMeshIndex < currentNode->mNumMeshes; ++sdkMeshIndex) + for (unsigned int sdkMeshIndex = 0; sdkMeshIndex < currentNode->mNumMeshes; ++sdkMeshIndex) { const aiMesh* mesh = scene->mMeshes[currentNode->mMeshes[sdkMeshIndex]]; if(mesh->mTextureCoords[texCoordIndex]) @@ -136,7 +136,7 @@ namespace AZ } } - for (int v = 0; v < mesh->mNumVertices; ++v) + for (unsigned int v = 0; v < mesh->mNumVertices; ++v) { if (mesh->mTextureCoords[texCoordIndex]) { diff --git a/Code/Tools/SceneAPI/SceneBuilder/Importers/Utilities/AssImpMeshImporterUtilities.cpp b/Code/Tools/SceneAPI/SceneBuilder/Importers/Utilities/AssImpMeshImporterUtilities.cpp index a9ab292d25..de94018c9b 100644 --- a/Code/Tools/SceneAPI/SceneBuilder/Importers/Utilities/AssImpMeshImporterUtilities.cpp +++ b/Code/Tools/SceneAPI/SceneBuilder/Importers/Utilities/AssImpMeshImporterUtilities.cpp @@ -40,7 +40,7 @@ namespace AZ::SceneAPI::SceneBuilder // This code re-combines them to match previous FBX SDK behavior, // so they can be separated by engine code instead. int vertOffset = 0; - for (int m = 0; m < currentNode->mNumMeshes; ++m) + for (unsigned int m = 0; m < currentNode->mNumMeshes; ++m) { const aiMesh* mesh = scene->mMeshes[currentNode->mMeshes[m]]; @@ -50,7 +50,7 @@ namespace AZ::SceneAPI::SceneBuilder assImpMatIndexToLYIndex.insert(AZStd::pair(mesh->mMaterialIndex, lyMeshIndex++)); } - for (int vertIdx = 0; vertIdx < mesh->mNumVertices; ++vertIdx) + for (unsigned int vertIdx = 0; vertIdx < mesh->mNumVertices; ++vertIdx) { AZ::Vector3 vertex(mesh->mVertices[vertIdx].x, mesh->mVertices[vertIdx].y, mesh->mVertices[vertIdx].z); @@ -68,7 +68,7 @@ namespace AZ::SceneAPI::SceneBuilder } } - for (int faceIdx = 0; faceIdx < mesh->mNumFaces; ++faceIdx) + for (unsigned int faceIdx = 0; faceIdx < mesh->mNumFaces; ++faceIdx) { aiFace face = mesh->mFaces[faceIdx]; AZ::SceneAPI::DataTypes::IMeshData::Face meshFace; @@ -82,7 +82,7 @@ namespace AZ::SceneAPI::SceneBuilder face.mNumIndices); continue; } - for (int idx = 0; idx < face.mNumIndices; ++idx) + for (unsigned int idx = 0; idx < face.mNumIndices; ++idx) { meshFace.vertexIndex[idx] = face.mIndices[idx] + vertOffset; } diff --git a/Gems/AWSCore/Code/Source/AWSCoreSystemComponent.cpp b/Gems/AWSCore/Code/Source/AWSCoreSystemComponent.cpp index c24518a4d7..92a5aa493f 100644 --- a/Gems/AWSCore/Code/Source/AWSCoreSystemComponent.cpp +++ b/Gems/AWSCore/Code/Source/AWSCoreSystemComponent.cpp @@ -160,7 +160,7 @@ namespace AWSCore // assigned to a specific CPU starting with the specified CPU. AZ::JobManagerDesc jobManagerDesc{}; AZ::JobManagerThreadDesc threadDesc(m_firstThreadCPU, m_threadPriority, m_threadStackSize); - for (unsigned int i = 0; i < m_threadCount; ++i) + for (int i = 0; i < m_threadCount; ++i) { jobManagerDesc.m_workerThreads.push_back(threadDesc); if (threadDesc.m_cpuId > -1) diff --git a/Gems/AWSCore/Code/Source/Editor/Attribution/AWSCoreAttributionManager.cpp b/Gems/AWSCore/Code/Source/Editor/Attribution/AWSCoreAttributionManager.cpp index e0b4db9165..85dd1469cb 100644 --- a/Gems/AWSCore/Code/Source/Editor/Attribution/AWSCoreAttributionManager.cpp +++ b/Gems/AWSCore/Code/Source/Editor/Attribution/AWSCoreAttributionManager.cpp @@ -139,7 +139,7 @@ namespace AWSCore AZStd::chrono::seconds lastSendTimeStamp = AZStd::chrono::seconds(lastSendTimeStampSeconds); AZStd::chrono::seconds secondsSinceLastSend = AZStd::chrono::duration_cast(AZStd::chrono::system_clock::now().time_since_epoch()) - lastSendTimeStamp; - if (secondsSinceLastSend.count() >= delayInSeconds) + if (static_cast(secondsSinceLastSend.count()) >= delayInSeconds) { return true; } diff --git a/Gems/AWSMetrics/Code/Source/MetricsManager.cpp b/Gems/AWSMetrics/Code/Source/MetricsManager.cpp index 5c4d910658..03e31770ee 100644 --- a/Gems/AWSMetrics/Code/Source/MetricsManager.cpp +++ b/Gems/AWSMetrics/Code/Source/MetricsManager.cpp @@ -106,7 +106,7 @@ namespace AWSMetrics AZStd::lock_guard lock(m_metricsMutex); m_metricsQueue.AddMetrics(metricsEvent); - if (m_metricsQueue.GetSizeInBytes() >= m_clientConfiguration->GetMaxQueueSizeInBytes()) + if (m_metricsQueue.GetSizeInBytes() >= static_cast(m_clientConfiguration->GetMaxQueueSizeInBytes())) { // Flush the metrics queue when the accumulated metrics size hits the limit m_waitEvent.release(); @@ -431,7 +431,7 @@ namespace AWSMetrics AZStd::lock_guard lock(m_metricsMutex); m_metricsQueue.AddMetrics(offlineRecords[index]); - if (m_metricsQueue.GetSizeInBytes() >= m_clientConfiguration->GetMaxQueueSizeInBytes()) + if (m_metricsQueue.GetSizeInBytes() >= static_cast(m_clientConfiguration->GetMaxQueueSizeInBytes())) { // Flush the metrics queue when the accumulated metrics size hits the limit m_waitEvent.release(); diff --git a/Gems/AWSMetrics/Code/Source/MetricsQueue.cpp b/Gems/AWSMetrics/Code/Source/MetricsQueue.cpp index ab54fb9e3e..4cc037189a 100644 --- a/Gems/AWSMetrics/Code/Source/MetricsQueue.cpp +++ b/Gems/AWSMetrics/Code/Source/MetricsQueue.cpp @@ -216,7 +216,7 @@ namespace AWSMetrics return false; } - for (int metricsIndex = 0; metricsIndex < doc.Size(); metricsIndex++) + for (rapidjson::SizeType metricsIndex = 0; metricsIndex < doc.Size(); metricsIndex++) { MetricsEvent metrics; if (!metrics.ReadFromJson(doc[metricsIndex])) diff --git a/Gems/AtomLyIntegration/AtomFont/Code/Source/FontRenderer.cpp b/Gems/AtomLyIntegration/AtomFont/Code/Source/FontRenderer.cpp index 101a506ddc..225b6d5ff6 100644 --- a/Gems/AtomLyIntegration/AtomFont/Code/Source/FontRenderer.cpp +++ b/Gems/AtomLyIntegration/AtomFont/Code/Source/FontRenderer.cpp @@ -252,8 +252,8 @@ int AZ::FontRenderer::GetGlyph(GlyphBitmap* glyphBitmap, int* horizontalAdvance, const int textureSlotBufferHeight = glyphBitmap->GetHeight(); // might happen if font characters are too big or cache dimenstions in font.xml is too small "" - const bool charWidthFits = iX + m_glyph->bitmap.width <= textureSlotBufferWidth; - const bool charHeightFits = iY + m_glyph->bitmap.rows <= textureSlotBufferHeight; + const bool charWidthFits = static_cast(iX + m_glyph->bitmap.width) <= textureSlotBufferWidth; + const bool charHeightFits = static_cast(iY + m_glyph->bitmap.rows) <= textureSlotBufferHeight; const bool charFitsInSlot = charWidthFits && charHeightFits; AZ_Error("Font", charFitsInSlot, "Character code %d doesn't fit in font texture; check 'sizeRatio' attribute in font XML or adjust this character's sizing in the font.", characterCode); diff --git a/Gems/Blast/Code/Source/Family/BlastFamilyImpl.cpp b/Gems/Blast/Code/Source/Family/BlastFamilyImpl.cpp index ec4925fe51..2690a12f24 100644 --- a/Gems/Blast/Code/Source/Family/BlastFamilyImpl.cpp +++ b/Gems/Blast/Code/Source/Family/BlastFamilyImpl.cpp @@ -450,7 +450,7 @@ namespace Blast const auto buffer = m_asset.GetAccelerator()->fillDebugRender(-1, mode == DebugRenderAabbTreeSegments); if (buffer.lineCount) { - for (int i = 0; i < buffer.lineCount; ++i) + for (uint32_t i = 0; i < buffer.lineCount; ++i) { auto& line = buffer.lines[i]; AZ::Color color; diff --git a/Gems/Blast/Code/Tests/Mocks/BlastMocks.h b/Gems/Blast/Code/Tests/Mocks/BlastMocks.h index 0733862e25..dddd1e275a 100644 --- a/Gems/Blast/Code/Tests/Mocks/BlastMocks.h +++ b/Gems/Blast/Code/Tests/Mocks/BlastMocks.h @@ -562,7 +562,7 @@ namespace Blast public: FakeEntityProvider(uint32_t entityCount) { - for (int i = 0; i < entityCount; ++i) + for (uint32 i = 0; i < entityCount; ++i) { m_entities.push_back(AZStd::make_shared()); } diff --git a/Gems/EMotionFX/Code/EMotionFX/Source/Actor.cpp b/Gems/EMotionFX/Code/EMotionFX/Source/Actor.cpp index 833f8a14f3..a6368ac088 100644 --- a/Gems/EMotionFX/Code/EMotionFX/Source/Actor.cpp +++ b/Gems/EMotionFX/Code/EMotionFX/Source/Actor.cpp @@ -1555,7 +1555,7 @@ namespace EMotionFX const uint32 geomLODLevel = 0; const uint32 numNodes = mSkeleton->GetNumNodes(); - for (int nodeIndex = 0; nodeIndex < numNodes; nodeIndex++) + for (uint32 nodeIndex = 0; nodeIndex < numNodes; nodeIndex++) { // check if this node has a mesh, if not we can skip it Mesh* mesh = GetMesh(geomLODLevel, nodeIndex); diff --git a/Gems/GradientSignal/Code/Tests/GradientSignalImageTests.cpp b/Gems/GradientSignal/Code/Tests/GradientSignalImageTests.cpp index b1aa74c397..ea9a1d380f 100644 --- a/Gems/GradientSignal/Code/Tests/GradientSignalImageTests.cpp +++ b/Gems/GradientSignal/Code/Tests/GradientSignalImageTests.cpp @@ -116,9 +116,9 @@ namespace UnitTest size_t value = 0; AZStd::hash_combine(value, seed); - for (int x = 0; x < width; ++x) + for (AZ::u32 x = 0; x < width; ++x) { - for (int y = 0; y < height; ++y) + for (AZ::u32 y = 0; y < height; ++y) { AZStd::hash_combine(value, x); AZStd::hash_combine(value, y); @@ -141,9 +141,9 @@ namespace UnitTest const AZ::u8 pixelValue = 255; // Image data should be stored inverted on the y axis relative to our engine, so loop backwards through y. - for (int y = height - 1; y >= 0; --y) + for (int y = static_cast(height) - 1; y >= 0; --y) { - for (int x = 0; x < width; ++x) + for (AZ::u32 x = 0; x < width; ++x) { if ((x == pixelX) && (y == pixelY)) { diff --git a/Gems/ImGui/Code/Source/ImGuiManager.cpp b/Gems/ImGui/Code/Source/ImGuiManager.cpp index ac3247b6a4..427af6d7dc 100644 --- a/Gems/ImGui/Code/Source/ImGuiManager.cpp +++ b/Gems/ImGui/Code/Source/ImGuiManager.cpp @@ -409,7 +409,7 @@ void ImGuiManager::Render() break; case ImGuiResolutionMode::MatchToMaxRenderResolution: - if (backBufferWidth <= static_cast(m_renderResolution.x)) + if (backBufferWidth <= static_cast(m_renderResolution.x)) { renderRes[0] = backBufferWidth; renderRes[1] = backBufferHeight; diff --git a/Gems/LmbrCentral/Code/Source/Shape/ShapeGeometryUtil.cpp b/Gems/LmbrCentral/Code/Source/Shape/ShapeGeometryUtil.cpp index cdb97d2702..3b54447fdd 100644 --- a/Gems/LmbrCentral/Code/Source/Shape/ShapeGeometryUtil.cpp +++ b/Gems/LmbrCentral/Code/Source/Shape/ShapeGeometryUtil.cpp @@ -353,10 +353,10 @@ namespace LmbrCentral const AZ::u32 sides, const AZ::u32 segments, const AZ::u32 capSegments, AZ::u32* indices) { - const auto capSegmentTipVerts = capSegments > 0 ? 1 : 0; - const auto totalSegments = segments + capSegments * 2; - const auto numVerts = sides * (totalSegments + 1) + 2 * capSegmentTipVerts; - const auto hasEnds = capSegments > 0; + const AZ::u32 capSegmentTipVerts = capSegments > 0 ? 1 : 0; + const AZ::u32 totalSegments = segments + capSegments * 2; + const AZ::u32 numVerts = sides * (totalSegments + 1) + 2 * capSegmentTipVerts; + const AZ::u32 hasEnds = capSegments > 0; // Start Faces (start point of tube) // Each starting face shares the same vertex at the beginning of the vertex buffer @@ -365,8 +365,7 @@ namespace LmbrCentral // 1 face per side if (hasEnds) { - - for (auto i = 0; i < sides; ++i) + for (AZ::u32 i = 0; i < sides; ++i) { AZ::u32 a = i + 1; AZ::u32 b = a + 1; @@ -383,9 +382,9 @@ namespace LmbrCentral // Middle Faces // 2 triangles per face. // 1 face per side. - for (auto i = 0; i < totalSegments; ++i) + for (AZ::u32 i = 0; i < totalSegments; ++i) { - for (auto j = 0; j < sides; ++j) + for (AZ::u32 j = 0; j < sides; ++j) { // 4 corners for each face // a ------ d @@ -416,7 +415,7 @@ namespace LmbrCentral // 1 face per side if (hasEnds) { - for (auto i = 0; i < sides; ++i) + for (AZ::u32 i = 0; i < sides; ++i) { AZ::u32 a = totalSegments * sides + i + 1; AZ::u32 b = a + 1; diff --git a/Gems/LyShine/Code/Editor/Animation/UiAnimViewSequence.cpp b/Gems/LyShine/Code/Editor/Animation/UiAnimViewSequence.cpp index 86554c2004..b4c98c39bf 100644 --- a/Gems/LyShine/Code/Editor/Animation/UiAnimViewSequence.cpp +++ b/Gems/LyShine/Code/Editor/Animation/UiAnimViewSequence.cpp @@ -1093,7 +1093,7 @@ void CUiAnimViewSequence::DeselectAllKeys() CUiAnimViewSequenceNotificationContext context(this); CUiAnimViewKeyBundle selectedKeys = GetSelectedKeys(); - for (int i = 0; i < selectedKeys.GetKeyCount(); ++i) + for (unsigned int i = 0; i < selectedKeys.GetKeyCount(); ++i) { CUiAnimViewKeyHandle keyHandle = selectedKeys.GetKey(i); keyHandle.Select(false); @@ -1237,7 +1237,7 @@ float CUiAnimViewSequence::ClipTimeOffsetForSliding(const float timeOffset) for (pTrackIter = tracks.begin(); pTrackIter != tracks.end(); ++pTrackIter) { CUiAnimViewTrack* pTrack = *pTrackIter; - for (int i = 0; i < pTrack->GetKeyCount(); ++i) + for (unsigned int i = 0; i < pTrack->GetKeyCount(); ++i) { CUiAnimViewKeyHandle keyHandle = pTrack->GetKey(i); diff --git a/Gems/LyShine/Code/Editor/SpriteBorderEditor.cpp b/Gems/LyShine/Code/Editor/SpriteBorderEditor.cpp index 1d8de3598e..e46073284b 100644 --- a/Gems/LyShine/Code/Editor/SpriteBorderEditor.cpp +++ b/Gems/LyShine/Code/Editor/SpriteBorderEditor.cpp @@ -191,9 +191,9 @@ void SpriteBorderEditor::UpdateSpriteSheetCellInfo(int newNumRows, int newNumCol // Calculate uniformly sized sprite-sheet cell UVs based on the given // row and column cell configuration. - for (int row = 0; row < m_numRows; ++row) + for (unsigned int row = 0; row < m_numRows; ++row) { - for (int col = 0; col < m_numCols; ++col) + for (unsigned int col = 0; col < m_numCols; ++col) { AZ::Vector2 min(col / floatNumCols, row / floatNumRows); AZ::Vector2 max((col + 1) / floatNumCols, (row + 1) / floatNumRows); diff --git a/Gems/LyShine/Code/Source/EditorPropertyTypes.cpp b/Gems/LyShine/Code/Source/EditorPropertyTypes.cpp index 77a9f59732..695412819d 100644 --- a/Gems/LyShine/Code/Source/EditorPropertyTypes.cpp +++ b/Gems/LyShine/Code/Source/EditorPropertyTypes.cpp @@ -17,8 +17,9 @@ LyShine::AZu32ComboBoxVec LyShine::GetEnumSpriteIndexList(AZ::EntityId entityId, int indexCount = 0; EBUS_EVENT_ID_RESULT(indexCount, entityId, UiIndexableImageBus, GetImageIndexCount); + const AZ::u32 indexCountu32 = static_cast(indexCount); - if (indexCount > 0 && (indexMax <= indexCount - 1) && indexMin <= indexMax) + if (indexCount > 0 && (indexMax <= indexCountu32 - 1) && indexMin <= indexMax) { for (AZ::u32 i = indexMin; i <= indexMax; ++i) { diff --git a/Gems/LyShine/Code/Source/UiImageComponent.cpp b/Gems/LyShine/Code/Source/UiImageComponent.cpp index 1e48bdff51..e56d9957b0 100644 --- a/Gems/LyShine/Code/Source/UiImageComponent.cpp +++ b/Gems/LyShine/Code/Source/UiImageComponent.cpp @@ -224,9 +224,9 @@ namespace IDraw2d::Rounding pixelRounding = isPixelAligned ? IDraw2d::Rounding::Nearest : IDraw2d::Rounding::None; float z = 1.0f; int i = 0; - for (int y = 0; y < numY; ++y) + for (uint32 y = 0; y < numY; ++y) { - for (int x = 0; x < numX; x += 1) + for (uint32 x = 0; x < numX; x += 1) { AZ::Vector3 point3(xValues[x], yValues[y], z); point3 = transform * point3; @@ -2030,7 +2030,7 @@ void UiImageComponent::ClipValuesForSlicedLinearFill(uint32 numValues, float* xV float previousPercentage = 0; int previousIndex = startClip; int clampIndex = -1; // to clamp all values greater than m_fillAmount in specified direction. - for (int arrayPos = 1; arrayPos < numValues; ++arrayPos) + for (uint32 arrayPos = 1; arrayPos < numValues; ++arrayPos) { int currentIndex = startClip + arrayPos * clipInc; float thisPercentage = (clipPosition[currentIndex] - clipPosition[startClip]) / totalLength; @@ -2102,7 +2102,7 @@ void UiImageComponent::ClipAndRenderForSlicedRadialFill(uint32 numVertsPerSide, if (m_fillAmount < 0.5f) { // Clips against first half line and then rotating line and adds results to render list. - for (int currentIndex = 0; currentIndex < totalIndices; currentIndex += 3) + for (uint32 currentIndex = 0; currentIndex < totalIndices; currentIndex += 3) { SVF_P2F_C4B_T2F_F4B intermediateVerts[maxTemporaryVerts]; uint16 intermediateIndices[maxTemporaryIndices]; @@ -2118,7 +2118,7 @@ void UiImageComponent::ClipAndRenderForSlicedRadialFill(uint32 numVertsPerSide, else { // Clips against first half line and adds results to render list then clips against the second half line and rotating line and also adds those results to render list. - for (int currentIndex = 0; currentIndex < totalIndices; currentIndex += 3) + for (uint32 currentIndex = 0; currentIndex < totalIndices; currentIndex += 3) { SVF_P2F_C4B_T2F_F4B intermediateVerts[maxTemporaryVerts]; uint16 intermediateIndices[maxTemporaryIndices]; @@ -2201,7 +2201,7 @@ void UiImageComponent::ClipAndRenderForSlicedRadialCornerOrEdgeFill(uint32 numVe int numIndicesToRender = 0; int vertexOffset = 0; - for (int ix = 0; ix < totalIndices; ix += 3) + for (uint32 ix = 0; ix < totalIndices; ix += 3) { int indicesUsed = ClipToLine(verts, &indices[ix], renderVerts, renderIndices, vertexOffset, numIndicesToRender, lineOrigin, lineEnd); numIndicesToRender += indicesUsed; diff --git a/Gems/LyShine/Code/Source/UiInteractableState.cpp b/Gems/LyShine/Code/Source/UiInteractableState.cpp index d3440a9354..7e87782d00 100644 --- a/Gems/LyShine/Code/Source/UiInteractableState.cpp +++ b/Gems/LyShine/Code/Source/UiInteractableState.cpp @@ -616,7 +616,7 @@ UiInteractableStateFont::FontEffectComboBoxVec UiInteractableStateFont::Populate // NOTE: Curently, in order for this to work, when the font is changed we need to do // "RefreshEntireTree" to get the combo box list refreshed. unsigned int numEffects = m_fontFamily ? m_fontFamily->normal->GetNumEffects() : 0; - for (int i = 0; i < numEffects; ++i) + for (unsigned int i = 0; i < numEffects; ++i) { const char* name = m_fontFamily->normal->GetEffectName(i); result.push_back(AZStd::make_pair(i, name)); diff --git a/Gems/LyShine/Code/Source/UiParticleEmitterComponent.cpp b/Gems/LyShine/Code/Source/UiParticleEmitterComponent.cpp index 9668ebf463..a8b678947b 100644 --- a/Gems/LyShine/Code/Source/UiParticleEmitterComponent.cpp +++ b/Gems/LyShine/Code/Source/UiParticleEmitterComponent.cpp @@ -830,7 +830,7 @@ void UiParticleEmitterComponent::Render(LyShine::IRenderGraph* renderGraph) AZ::u32 totalVerticesInserted = 0; // particlesToRender is the max particles we will render, we could render less if some have zero alpha - for (int i = 0; i < particlesToRender; ++i) + for (AZ::u32 i = 0; i < particlesToRender; ++i) { SVF_P2F_C4B_T2F_F4B* firstVertexOfParticle = &m_cachedPrimitive.m_vertices[totalVerticesInserted]; @@ -1827,7 +1827,7 @@ void UiParticleEmitterComponent::ResetParticleBuffers() const int verticesPerParticle = 4; int baseIndex = 0; - for (int i = 0; i < numIndices; i += indicesPerParticle) + for (AZ::u32 i = 0; i < numIndices; i += indicesPerParticle) { m_cachedPrimitive.m_indices[i + 0] = 0 + baseIndex; m_cachedPrimitive.m_indices[i + 1] = 1 + baseIndex; diff --git a/Gems/LyShine/Code/Source/UiTextComponent.cpp b/Gems/LyShine/Code/Source/UiTextComponent.cpp index d95b15622b..87eb4524a2 100644 --- a/Gems/LyShine/Code/Source/UiTextComponent.cpp +++ b/Gems/LyShine/Code/Source/UiTextComponent.cpp @@ -3527,7 +3527,7 @@ UiTextComponent::FontEffectComboBoxVec UiTextComponent::PopulateFontEffectList() if (m_font) { unsigned int numEffects = m_font->GetNumEffects(); - for (int i = 0; i < numEffects; ++i) + for (unsigned int i = 0; i < numEffects; ++i) { const char* name = m_font->GetEffectName(i); result.push_back(AZStd::make_pair(i, name)); diff --git a/Gems/PhysXDebug/Code/Source/SystemComponent.cpp b/Gems/PhysXDebug/Code/Source/SystemComponent.cpp index 7e1c3630cb..992a2a3697 100644 --- a/Gems/PhysXDebug/Code/Source/SystemComponent.cpp +++ b/Gems/PhysXDebug/Code/Source/SystemComponent.cpp @@ -697,7 +697,7 @@ namespace PhysXDebug if (GetCurrentPxScene()) { // Reserve vector capacity - const int numTriangles = rb.getNbTriangles(); + const physx::PxU32 numTriangles = static_cast(rb.getNbTriangles()); m_trianglePoints.reserve(numTriangles * 3); m_triangleColors.reserve(numTriangles * 3); @@ -731,7 +731,7 @@ namespace PhysXDebug if (GetCurrentPxScene()) { - const int numLines = rb.getNbLines(); + const physx::PxU32 numLines = static_cast(rb.getNbLines()); // Reserve vector capacity m_linePoints.reserve(numLines * 2); From eb3e0b0998463b52e877ab4d2dd63d3c72dfe1b6 Mon Sep 17 00:00:00 2001 From: pappeste Date: Thu, 24 Jun 2021 18:40:11 -0700 Subject: [PATCH 17/37] updates after merging development Signed-off-by: Esteban Papp <81431996+amznestebanpapp@users.noreply.github.com> --- .../Tests/Prefab/Benchmark/SpawnableCreateBenchmarks.cpp | 2 +- .../SceneBuilder/Importers/AssImpAnimationImporter.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Code/Framework/AzToolsFramework/Tests/Prefab/Benchmark/SpawnableCreateBenchmarks.cpp b/Code/Framework/AzToolsFramework/Tests/Prefab/Benchmark/SpawnableCreateBenchmarks.cpp index 9d35081895..54e53a6ebc 100644 --- a/Code/Framework/AzToolsFramework/Tests/Prefab/Benchmark/SpawnableCreateBenchmarks.cpp +++ b/Code/Framework/AzToolsFramework/Tests/Prefab/Benchmark/SpawnableCreateBenchmarks.cpp @@ -33,7 +33,7 @@ namespace Benchmark AZStd::vector> spawnables; spawnables.reserve(numSpawnables); - for (int spwanableCounter = 0; spwanableCounter < numSpawnables; ++spwanableCounter) + for (unsigned int spwanableCounter = 0; spwanableCounter < numSpawnables; ++spwanableCounter) { AZStd::unique_ptr spawnable = AZStd::make_unique(); AzToolsFramework::Prefab::SpawnableUtils::CreateSpawnable(*spawnable, prefabDom); diff --git a/Code/Tools/SceneAPI/SceneBuilder/Importers/AssImpAnimationImporter.cpp b/Code/Tools/SceneAPI/SceneBuilder/Importers/AssImpAnimationImporter.cpp index da1db22d5e..150a50138e 100644 --- a/Code/Tools/SceneAPI/SceneBuilder/Importers/AssImpAnimationImporter.cpp +++ b/Code/Tools/SceneAPI/SceneBuilder/Importers/AssImpAnimationImporter.cpp @@ -445,11 +445,11 @@ namespace AZ AZStd::unordered_set boneList; - for (int meshIndex = 0; meshIndex < scene->mNumMeshes; ++meshIndex) + for (unsigned int meshIndex = 0; meshIndex < scene->mNumMeshes; ++meshIndex) { aiMesh* mesh = scene->mMeshes[meshIndex]; - for (int boneIndex = 0; boneIndex < mesh->mNumBones; ++boneIndex) + for (unsigned int boneIndex = 0; boneIndex < mesh->mNumBones; ++boneIndex) { aiBone* bone = mesh->mBones[boneIndex]; From 9f18b6f1be67a6cfadce79047d8a8d77e8adaa74 Mon Sep 17 00:00:00 2001 From: Esteban Papp <81431996+amznestebanpapp@users.noreply.github.com> Date: Thu, 5 Aug 2021 19:58:46 -0700 Subject: [PATCH 18/37] fixes for new code Signed-off-by: Esteban Papp <81431996+amznestebanpapp@users.noreply.github.com> --- .../SceneBuilder/Importers/AssImpImporterUtilities.cpp | 4 ++-- Gems/LyShine/Code/Editor/Animation/UiAVEventsDialog.cpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Code/Tools/SceneAPI/SceneBuilder/Importers/AssImpImporterUtilities.cpp b/Code/Tools/SceneAPI/SceneBuilder/Importers/AssImpImporterUtilities.cpp index 81feff7d69..861d6dd85c 100644 --- a/Code/Tools/SceneAPI/SceneBuilder/Importers/AssImpImporterUtilities.cpp +++ b/Code/Tools/SceneAPI/SceneBuilder/Importers/AssImpImporterUtilities.cpp @@ -105,7 +105,7 @@ namespace AZ nodesWithNoMesh.emplace(currentNode->mName.C_Str()); } - for (int childIndex = 0; childIndex < currentNode->mNumChildren; ++childIndex) + for (unsigned int childIndex = 0; childIndex < currentNode->mNumChildren; ++childIndex) { queue.push(currentNode->mChildren[childIndex]); } @@ -176,7 +176,7 @@ namespace AZ return true; } - for (int childIndex = 0; childIndex < node->mNumChildren; ++childIndex) + for (unsigned int childIndex = 0; childIndex < node->mNumChildren; ++childIndex) { const aiNode* childNode = node->mChildren[childIndex]; if (RecursiveHasChildBone(childNode, boneByNameMap)) diff --git a/Gems/LyShine/Code/Editor/Animation/UiAVEventsDialog.cpp b/Gems/LyShine/Code/Editor/Animation/UiAVEventsDialog.cpp index e9f5d04314..b45cee6948 100644 --- a/Gems/LyShine/Code/Editor/Animation/UiAVEventsDialog.cpp +++ b/Gems/LyShine/Code/Editor/Animation/UiAVEventsDialog.cpp @@ -352,7 +352,7 @@ int UiAVEventsModel::GetNumberOfUsageAndFirstTimeUsed(const char* eventName, flo { CUiAnimViewTrack* pTrack = tracks.GetTrack(currentTrack); - for (int currentKey = 0; currentKey < pTrack->GetKeyCount(); ++currentKey) + for (unsigned int currentKey = 0; currentKey < pTrack->GetKeyCount(); ++currentKey) { CUiAnimViewKeyHandle keyHandle = pTrack->GetKey(currentKey); From db942e2cf5b70bcf63992ba5631a8a086d534307 Mon Sep 17 00:00:00 2001 From: Esteban Papp <81431996+amznestebanpapp@users.noreply.github.com> Date: Fri, 6 Aug 2021 10:36:53 -0700 Subject: [PATCH 19/37] addresses PR comments Signed-off-by: Esteban Papp <81431996+amznestebanpapp@users.noreply.github.com> --- Code/Editor/ViewportTitleDlg.cpp | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/Code/Editor/ViewportTitleDlg.cpp b/Code/Editor/ViewportTitleDlg.cpp index cbf2958eea..b9c86bf72b 100644 --- a/Code/Editor/ViewportTitleDlg.cpp +++ b/Code/Editor/ViewportTitleDlg.cpp @@ -449,21 +449,21 @@ void CViewportTitleDlg::AddFOVMenus(QMenu* menu, std::function call if (!customPresets.empty()) { - for (size_t i = 0; i < customPresets.size(); ++i) + for (const QString& customPreset : customPresets) { - if (customPresets[static_cast(i)].isEmpty()) + if (customPreset.isEmpty()) { break; } float fov = gSettings.viewports.fDefaultFov; bool ok; - float f = customPresets[static_cast(i)].toDouble(&ok); + float f = customPreset.toDouble(&ok); if (ok) { fov = std::max(1.0f, f); fov = std::min(120.0f, f); - QAction* action = menu->addAction(customPresets[static_cast(i)]); + QAction* action = menu->addAction(customPreset); connect(action, &QAction::triggered, action, [fov, callback](){ callback(fov); }); } } @@ -535,15 +535,15 @@ void CViewportTitleDlg::AddAspectRatioMenus(QMenu* menu, std::functionaddSeparator(); - for (size_t i = 0; i < customPresets.size(); ++i) + for (const QString& customPreset : customPresets) { - if (customPresets[static_cast(i)].isEmpty()) + if (customPreset.isEmpty()) { break; } static QRegularExpression regex(QStringLiteral("^(\\d+):(\\d+)$")); - QRegularExpressionMatch matches = regex.match(customPresets[static_cast(i)]); + QRegularExpressionMatch matches = regex.match(customPreset); if (matches.hasMatch()) { bool ok; @@ -551,7 +551,7 @@ void CViewportTitleDlg::AddAspectRatioMenus(QMenu* menu, std::functionaddAction(customPresets[static_cast(i)]); + QAction* action = menu->addAction(customPreset); connect(action, &QAction::triggered, action, [width, height, callback]() {callback(width, height); }); } } @@ -666,15 +666,15 @@ void CViewportTitleDlg::AddResolutionMenus(QMenu* menu, std::functionaddSeparator(); - for (size_t i = 0; i < customPresets.size(); ++i) + for (const QString& customPreset : customPresets) { - if (customPresets[static_cast(i)].isEmpty()) + if (customPreset.isEmpty()) { break; } static QRegularExpression regex(QStringLiteral("^(\\d+) x (\\d+)$")); - QRegularExpressionMatch matches = regex.match(customPresets[static_cast(i)]); + QRegularExpressionMatch matches = regex.match(customPreset); if (matches.hasMatch()) { bool ok; @@ -682,7 +682,7 @@ void CViewportTitleDlg::AddResolutionMenus(QMenu* menu, std::functionaddAction(customPresets[static_cast(i)]); + QAction* action = menu->addAction(customPreset); connect(action, &QAction::triggered, action, [width, height, callback](){ callback(width, height); }); } } From 5f1e973b3f6953233869544de32bdbc856d9bf63 Mon Sep 17 00:00:00 2001 From: antonmic <56370189+antonmic@users.noreply.github.com> Date: Sun, 8 Aug 2021 23:34:09 -0700 Subject: [PATCH 20/37] Removing condition from View SRG compilation Signed-off-by: antonmic <56370189+antonmic@users.noreply.github.com> --- .../RPI/Code/Include/Atom/RPI.Public/View.h | 6 -- Gems/Atom/RPI/Code/Source/RPI.Public/View.cpp | 94 ++++++++----------- 2 files changed, 39 insertions(+), 61 deletions(-) diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/View.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/View.h index c654952cf4..cdaf59ff75 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/View.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/View.h @@ -110,9 +110,6 @@ namespace AZ //! Value returned is 1.0f when an area equal to the viewport height squared is covered. Useful for accurate LOD decisions. float CalculateSphereAreaInClipSpace(const AZ::Vector3& sphereWorldPosition, float sphereRadius) const; - //! Invalidate the view srg to rebuild the srg. - void InvalidateSrg(); - const AZ::Name& GetName() const { return m_name; } const UsageFlags GetUsageFlags() { return m_usageFlags; } @@ -192,9 +189,6 @@ namespace AZ // Clip space offset for camera jitter with taa Vector2 m_clipSpaceOffset = Vector2(0.0f, 0.0f); - // Flags whether view matrices are dirty which requires rebuild srg - bool m_needBuildSrg = true; - MatrixChangedEvent m_onWorldToClipMatrixChange; MatrixChangedEvent m_onWorldToViewMatrixChange; diff --git a/Gems/Atom/RPI/Code/Source/RPI.Public/View.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/View.cpp index 4984186b4e..1937afc240 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Public/View.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Public/View.cpp @@ -126,8 +126,6 @@ namespace AZ m_onWorldToViewMatrixChange.Signal(m_worldToViewMatrix); m_onWorldToClipMatrixChange.Signal(m_worldToClipMatrix); - - InvalidateSrg(); } AZ::Transform View::GetCameraTransform() const @@ -170,8 +168,6 @@ namespace AZ m_onWorldToViewMatrixChange.Signal(m_worldToViewMatrix); } m_onWorldToClipMatrixChange.Signal(m_worldToClipMatrix); - - InvalidateSrg(); } void View::SetViewToClipMatrix(const AZ::Matrix4x4& viewToClip) @@ -202,14 +198,11 @@ namespace AZ m_unprojectionConstants.SetW(float(tanHalfFovY)); m_onWorldToClipMatrixChange.Signal(m_worldToClipMatrix); - - InvalidateSrg(); } void View::SetClipSpaceOffset(float xOffset, float yOffset) { m_clipSpaceOffset.Set(xOffset, yOffset); - InvalidateSrg(); } const AZ::Matrix4x4& View::GetWorldToViewMatrix() const @@ -362,58 +355,49 @@ namespace AZ return -0.25f * cotHalfFovYSq * AZ::Constants::Pi * radiusSq * sqrt(fabsf((distanceSq - radiusSq)/radiusSqSubDepthSq))/radiusSqSubDepthSq; } - void View::InvalidateSrg() - { - m_needBuildSrg = true; - } - void View::UpdateSrg() { - if (m_needBuildSrg) + if (m_clipSpaceOffset.IsZero()) { - if (m_clipSpaceOffset.IsZero()) - { - Matrix4x4 worldToClipPrevMatrix = m_viewToClipPrevMatrix * m_worldToViewPrevMatrix; - m_shaderResourceGroup->SetConstant(m_worldToClipPrevMatrixConstantIndex, worldToClipPrevMatrix); - m_shaderResourceGroup->SetConstant(m_viewProjectionMatrixConstantIndex, m_worldToClipMatrix); - m_shaderResourceGroup->SetConstant(m_projectionMatrixConstantIndex, m_viewToClipMatrix); - m_shaderResourceGroup->SetConstant(m_clipToWorldMatrixConstantIndex, m_clipToWorldMatrix); - m_shaderResourceGroup->SetConstant(m_projectionMatrixInverseConstantIndex, m_viewToClipMatrix.GetInverseFull()); - } - else - { - // Offset the current and previous frame clip matricies - Matrix4x4 offsetViewToClipMatrix = m_viewToClipMatrix; - offsetViewToClipMatrix.SetElement(0, 2, m_clipSpaceOffset.GetX()); - offsetViewToClipMatrix.SetElement(1, 2, m_clipSpaceOffset.GetY()); - - Matrix4x4 offsetViewToClipPrevMatrix = m_viewToClipPrevMatrix; - offsetViewToClipPrevMatrix.SetElement(0, 2, m_clipSpaceOffset.GetX()); - offsetViewToClipPrevMatrix.SetElement(1, 2, m_clipSpaceOffset.GetY()); - - // Build other matricies dependent on the view to clip matricies - Matrix4x4 offsetWorldToClipMatrix = offsetViewToClipMatrix * m_worldToViewMatrix; - Matrix4x4 offsetWorldToClipPrevMatrix = offsetViewToClipPrevMatrix * m_worldToViewPrevMatrix; - - Matrix4x4 offsetClipToViewMatrix = offsetViewToClipMatrix.GetInverseFull(); - Matrix4x4 offsetClipToWorldMatrix = m_viewToWorldMatrix * offsetClipToViewMatrix; - - m_shaderResourceGroup->SetConstant(m_worldToClipPrevMatrixConstantIndex, offsetWorldToClipPrevMatrix); - m_shaderResourceGroup->SetConstant(m_viewProjectionMatrixConstantIndex, offsetWorldToClipMatrix); - m_shaderResourceGroup->SetConstant(m_projectionMatrixConstantIndex, offsetViewToClipMatrix); - m_shaderResourceGroup->SetConstant(m_clipToWorldMatrixConstantIndex, offsetClipToWorldMatrix); - m_shaderResourceGroup->SetConstant(m_projectionMatrixInverseConstantIndex, offsetViewToClipMatrix.GetInverseFull()); - } - - m_shaderResourceGroup->SetConstant(m_worldPositionConstantIndex, m_position); - m_shaderResourceGroup->SetConstant(m_viewMatrixConstantIndex, m_worldToViewMatrix); - m_shaderResourceGroup->SetConstant(m_viewMatrixInverseConstantIndex, m_worldToViewMatrix.GetInverseFull()); - m_shaderResourceGroup->SetConstant(m_zConstantsConstantIndex, m_nearZ_farZ_farZTimesNearZ_farZMinusNearZ); - m_shaderResourceGroup->SetConstant(m_unprojectionConstantsIndex, m_unprojectionConstants); - - m_shaderResourceGroup->Compile(); - m_needBuildSrg = false; + Matrix4x4 worldToClipPrevMatrix = m_viewToClipPrevMatrix * m_worldToViewPrevMatrix; + m_shaderResourceGroup->SetConstant(m_worldToClipPrevMatrixConstantIndex, worldToClipPrevMatrix); + m_shaderResourceGroup->SetConstant(m_viewProjectionMatrixConstantIndex, m_worldToClipMatrix); + m_shaderResourceGroup->SetConstant(m_projectionMatrixConstantIndex, m_viewToClipMatrix); + m_shaderResourceGroup->SetConstant(m_clipToWorldMatrixConstantIndex, m_clipToWorldMatrix); + m_shaderResourceGroup->SetConstant(m_projectionMatrixInverseConstantIndex, m_viewToClipMatrix.GetInverseFull()); } + else + { + // Offset the current and previous frame clip matricies + Matrix4x4 offsetViewToClipMatrix = m_viewToClipMatrix; + offsetViewToClipMatrix.SetElement(0, 2, m_clipSpaceOffset.GetX()); + offsetViewToClipMatrix.SetElement(1, 2, m_clipSpaceOffset.GetY()); + + Matrix4x4 offsetViewToClipPrevMatrix = m_viewToClipPrevMatrix; + offsetViewToClipPrevMatrix.SetElement(0, 2, m_clipSpaceOffset.GetX()); + offsetViewToClipPrevMatrix.SetElement(1, 2, m_clipSpaceOffset.GetY()); + + // Build other matricies dependent on the view to clip matricies + Matrix4x4 offsetWorldToClipMatrix = offsetViewToClipMatrix * m_worldToViewMatrix; + Matrix4x4 offsetWorldToClipPrevMatrix = offsetViewToClipPrevMatrix * m_worldToViewPrevMatrix; + + Matrix4x4 offsetClipToViewMatrix = offsetViewToClipMatrix.GetInverseFull(); + Matrix4x4 offsetClipToWorldMatrix = m_viewToWorldMatrix * offsetClipToViewMatrix; + + m_shaderResourceGroup->SetConstant(m_worldToClipPrevMatrixConstantIndex, offsetWorldToClipPrevMatrix); + m_shaderResourceGroup->SetConstant(m_viewProjectionMatrixConstantIndex, offsetWorldToClipMatrix); + m_shaderResourceGroup->SetConstant(m_projectionMatrixConstantIndex, offsetViewToClipMatrix); + m_shaderResourceGroup->SetConstant(m_clipToWorldMatrixConstantIndex, offsetClipToWorldMatrix); + m_shaderResourceGroup->SetConstant(m_projectionMatrixInverseConstantIndex, offsetViewToClipMatrix.GetInverseFull()); + } + + m_shaderResourceGroup->SetConstant(m_worldPositionConstantIndex, m_position); + m_shaderResourceGroup->SetConstant(m_viewMatrixConstantIndex, m_worldToViewMatrix); + m_shaderResourceGroup->SetConstant(m_viewMatrixInverseConstantIndex, m_worldToViewMatrix.GetInverseFull()); + m_shaderResourceGroup->SetConstant(m_zConstantsConstantIndex, m_nearZ_farZ_farZTimesNearZ_farZMinusNearZ); + m_shaderResourceGroup->SetConstant(m_unprojectionConstantsIndex, m_unprojectionConstants); + + m_shaderResourceGroup->Compile(); m_viewToClipPrevMatrix = m_viewToClipMatrix; m_worldToViewPrevMatrix = m_worldToViewMatrix; From 12505da6fcf9e3970dc97e4ae43b93bac587908a Mon Sep 17 00:00:00 2001 From: John Jones-Steele Date: Mon, 9 Aug 2021 16:01:18 +0100 Subject: [PATCH 21/37] Changed locale handling and added tests Signed-off-by: John Jones-Steele --- Code/Editor/CryEdit.cpp | 3 -- .../Application/AzQtApplication.cpp | 2 - .../AzQtComponents/Gallery/main.cpp | 2 - .../Tests/FloatToStringConversionTests.cpp | 40 +++++++++++++++++++ .../ProjectManager/Source/Application.cpp | 2 - 5 files changed, 40 insertions(+), 9 deletions(-) diff --git a/Code/Editor/CryEdit.cpp b/Code/Editor/CryEdit.cpp index cd1dd4fe47..47ad7e5381 100644 --- a/Code/Editor/CryEdit.cpp +++ b/Code/Editor/CryEdit.cpp @@ -1626,9 +1626,6 @@ BOOL CCryEditApp::InitInstance() ReflectedVarInit::setupReflection(serializeContext); RegisterReflectedVarHandlers(); - - QLocale::setDefault(QLocale(QLocale::English, QLocale::UnitedStates)); - CreateSplashScreen(); // Register the application's document templates. Document templates diff --git a/Code/Framework/AzQtComponents/AzQtComponents/Application/AzQtApplication.cpp b/Code/Framework/AzQtComponents/AzQtComponents/Application/AzQtApplication.cpp index 594a339448..9dece6e26a 100644 --- a/Code/Framework/AzQtComponents/AzQtComponents/Application/AzQtApplication.cpp +++ b/Code/Framework/AzQtComponents/AzQtComponents/Application/AzQtApplication.cpp @@ -22,8 +22,6 @@ namespace AzQtComponents QApplication::setApplicationName("O3DE Tools Application"); AzQtComponents::PrepareQtPaths(); - - QLocale::setDefault(QLocale(QLocale::English, QLocale::UnitedStates)); } void AzQtApplication::InitializeDpiScaling() diff --git a/Code/Framework/AzQtComponents/AzQtComponents/Gallery/main.cpp b/Code/Framework/AzQtComponents/AzQtComponents/Gallery/main.cpp index 51ca2b349f..fc8af9e8ce 100644 --- a/Code/Framework/AzQtComponents/AzQtComponents/Gallery/main.cpp +++ b/Code/Framework/AzQtComponents/AzQtComponents/Gallery/main.cpp @@ -134,8 +134,6 @@ int main(int argc, char **argv) QApplication::setOrganizationDomain("o3de.org"); QApplication::setApplicationName("O3DEWidgetGallery"); - QLocale::setDefault(QLocale(QLocale::English, QLocale::UnitedStates)); - QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps); QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::PassThrough); diff --git a/Code/Framework/AzQtComponents/AzQtComponents/Tests/FloatToStringConversionTests.cpp b/Code/Framework/AzQtComponents/AzQtComponents/Tests/FloatToStringConversionTests.cpp index da4d8de4cd..a0a8155475 100644 --- a/Code/Framework/AzQtComponents/AzQtComponents/Tests/FloatToStringConversionTests.cpp +++ b/Code/Framework/AzQtComponents/AzQtComponents/Tests/FloatToStringConversionTests.cpp @@ -64,3 +64,43 @@ TEST(AzQtComponents, FloatToString_TruncateShowThousandsSeparatorOnlyOneDecimal) int numDecimalPlaces = 2; EXPECT_EQ(AzQtComponents::toString(1000.000, numDecimalPlaces, testLocal, showThousandsSeparator), "1,000.0"); } + +TEST(AzQtComponents, FloatToString_Truncate2DecimalsWithLocale) +{ + QLocale testLocal{ QLocale() }; + + const bool showThousandsSeparator = false; + const int numDecimalPlaces = 2; + QString testString = "0" + QString(testLocal.decimalPoint()) + "12"; + EXPECT_EQ(AzQtComponents::toString(0.1234, numDecimalPlaces, testLocal, showThousandsSeparator), testString); +} + +TEST(AzQtComponents, FloatToString_AllZerosButOneWithLocale) +{ + QLocale testLocal{ QLocale() }; + + const bool showThousandsSeparator = false; + const int numDecimalPlaces = 2; + QString testString = "1" + QString(testLocal.decimalPoint()) + "0"; + EXPECT_EQ(AzQtComponents::toString(1.0000, numDecimalPlaces, testLocal, showThousandsSeparator), testString); +} + +TEST(AzQtComponents, FloatToString_TruncateShowThousandsSeparatorTruncateNoRoundWithLocale) +{ + QLocale testLocal{ QLocale() }; + + const bool showThousandsSeparator = true; + const int numDecimalPlaces = 3; + QString testString = "1" + QString(testLocal.groupSeparator()) + "000" + QString(testLocal.decimalPoint()) + "123"; + EXPECT_EQ(AzQtComponents::toString(1000.1236, numDecimalPlaces, testLocal, showThousandsSeparator), testString); +} + +TEST(AzQtComponents, FloatToString_TruncateShowThousandsSeparatorOnlyOneDecimalWithLocale) +{ + QLocale testLocal{ QLocale() }; + + const bool showThousandsSeparator = true; + int numDecimalPlaces = 2; + QString testString = "1" + QString(testLocal.groupSeparator()) + "000" + QString(testLocal.decimalPoint()) + "0"; + EXPECT_EQ(AzQtComponents::toString(1000.000, numDecimalPlaces, testLocal, showThousandsSeparator), testString); +} diff --git a/Code/Tools/ProjectManager/Source/Application.cpp b/Code/Tools/ProjectManager/Source/Application.cpp index bdcb59897b..c977698152 100644 --- a/Code/Tools/ProjectManager/Source/Application.cpp +++ b/Code/Tools/ProjectManager/Source/Application.cpp @@ -56,8 +56,6 @@ namespace O3DE::ProjectManager QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps); QCoreApplication::setAttribute(Qt::AA_DontCreateNativeWidgetSiblings); - QLocale::setDefault(QLocale(QLocale::English, QLocale::UnitedStates)); - QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::PassThrough); AzQtComponents::Utilities::HandleDpiAwareness(AzQtComponents::Utilities::SystemDpiAware); From 3b60862237ace01f572fd1d333a28a8e0021070c Mon Sep 17 00:00:00 2001 From: John Jones-Steele Date: Mon, 9 Aug 2021 16:30:49 +0100 Subject: [PATCH 22/37] Fixed order of tests Signed-off-by: John Jones-Steele --- .../AzQtComponents/Tests/FloatToStringConversionTests.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Code/Framework/AzQtComponents/AzQtComponents/Tests/FloatToStringConversionTests.cpp b/Code/Framework/AzQtComponents/AzQtComponents/Tests/FloatToStringConversionTests.cpp index a0a8155475..21db675324 100644 --- a/Code/Framework/AzQtComponents/AzQtComponents/Tests/FloatToStringConversionTests.cpp +++ b/Code/Framework/AzQtComponents/AzQtComponents/Tests/FloatToStringConversionTests.cpp @@ -72,7 +72,7 @@ TEST(AzQtComponents, FloatToString_Truncate2DecimalsWithLocale) const bool showThousandsSeparator = false; const int numDecimalPlaces = 2; QString testString = "0" + QString(testLocal.decimalPoint()) + "12"; - EXPECT_EQ(AzQtComponents::toString(0.1234, numDecimalPlaces, testLocal, showThousandsSeparator), testString); + EXPECT_EQ(testString, AzQtComponents::toString(0.1234, numDecimalPlaces, testLocal, showThousandsSeparator)); } TEST(AzQtComponents, FloatToString_AllZerosButOneWithLocale) @@ -82,7 +82,7 @@ TEST(AzQtComponents, FloatToString_AllZerosButOneWithLocale) const bool showThousandsSeparator = false; const int numDecimalPlaces = 2; QString testString = "1" + QString(testLocal.decimalPoint()) + "0"; - EXPECT_EQ(AzQtComponents::toString(1.0000, numDecimalPlaces, testLocal, showThousandsSeparator), testString); + EXPECT_EQ(testString, AzQtComponents::toString(1.0000, numDecimalPlaces, testLocal, showThousandsSeparator)); } TEST(AzQtComponents, FloatToString_TruncateShowThousandsSeparatorTruncateNoRoundWithLocale) @@ -92,7 +92,7 @@ TEST(AzQtComponents, FloatToString_TruncateShowThousandsSeparatorTruncateNoRound const bool showThousandsSeparator = true; const int numDecimalPlaces = 3; QString testString = "1" + QString(testLocal.groupSeparator()) + "000" + QString(testLocal.decimalPoint()) + "123"; - EXPECT_EQ(AzQtComponents::toString(1000.1236, numDecimalPlaces, testLocal, showThousandsSeparator), testString); + EXPECT_EQ(testString, AzQtComponents::toString(1000.1236, numDecimalPlaces, testLocal, showThousandsSeparator)); } TEST(AzQtComponents, FloatToString_TruncateShowThousandsSeparatorOnlyOneDecimalWithLocale) @@ -102,5 +102,5 @@ TEST(AzQtComponents, FloatToString_TruncateShowThousandsSeparatorOnlyOneDecimalW const bool showThousandsSeparator = true; int numDecimalPlaces = 2; QString testString = "1" + QString(testLocal.groupSeparator()) + "000" + QString(testLocal.decimalPoint()) + "0"; - EXPECT_EQ(AzQtComponents::toString(1000.000, numDecimalPlaces, testLocal, showThousandsSeparator), testString); + EXPECT_EQ(testString, AzQtComponents::toString(1000.000, numDecimalPlaces, testLocal, showThousandsSeparator)); } From 86d0ba7e6b2b16f9136f4e8586e0f0e7f87ba6bb Mon Sep 17 00:00:00 2001 From: John Jones-Steele Date: Mon, 9 Aug 2021 17:00:53 +0100 Subject: [PATCH 23/37] Minor changes to tests Signed-off-by: John Jones-Steele --- .../Tests/FloatToStringConversionTests.cpp | 48 +++++++++---------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/Code/Framework/AzQtComponents/AzQtComponents/Tests/FloatToStringConversionTests.cpp b/Code/Framework/AzQtComponents/AzQtComponents/Tests/FloatToStringConversionTests.cpp index 21db675324..39593a1c58 100644 --- a/Code/Framework/AzQtComponents/AzQtComponents/Tests/FloatToStringConversionTests.cpp +++ b/Code/Framework/AzQtComponents/AzQtComponents/Tests/FloatToStringConversionTests.cpp @@ -13,94 +13,94 @@ TEST(AzQtComponents, FloatToString_Truncate2Decimals) { - QLocale testLocal(QLocale::English, QLocale::UnitedStates); + QLocale testLocale(QLocale::English, QLocale::UnitedStates); const bool showThousandsSeparator = false; const int numDecimalPlaces = 2; - EXPECT_EQ(AzQtComponents::toString(0.1234, numDecimalPlaces, testLocal, showThousandsSeparator), "0.12"); + EXPECT_EQ(AzQtComponents::toString(0.1234, numDecimalPlaces, testLocale, showThousandsSeparator), "0.12"); } TEST(AzQtComponents, FloatToString_AllZerosButOne) { - QLocale testLocal(QLocale::English, QLocale::UnitedStates); + QLocale testLocale(QLocale::English, QLocale::UnitedStates); const bool showThousandsSeparator = false; int numDecimalPlaces = 2; - EXPECT_EQ(AzQtComponents::toString(1.0000, numDecimalPlaces, testLocal, showThousandsSeparator), "1.0"); + EXPECT_EQ(AzQtComponents::toString(1.0000, numDecimalPlaces, testLocale, showThousandsSeparator), "1.0"); } TEST(AzQtComponents, FloatToString_TruncateAllZerosButOne) { - QLocale testLocal(QLocale::English, QLocale::UnitedStates); + QLocale testLocale(QLocale::English, QLocale::UnitedStates); const bool showThousandsSeparator = false; int numDecimalPlaces = 2; - EXPECT_EQ(AzQtComponents::toString(1.0001, numDecimalPlaces, testLocal, showThousandsSeparator), "1.0"); + EXPECT_EQ(AzQtComponents::toString(1.0001, numDecimalPlaces, testLocale, showThousandsSeparator), "1.0"); } TEST(AzQtComponents, FloatToString_TruncateNotRound) { - QLocale testLocal(QLocale::English, QLocale::UnitedStates); + QLocale testLocale(QLocale::English, QLocale::UnitedStates); const bool showThousandsSeparator = false; int numDecimalPlaces = 3; - EXPECT_EQ(AzQtComponents::toString(0.1236, numDecimalPlaces, testLocal, showThousandsSeparator), "0.123"); + EXPECT_EQ(AzQtComponents::toString(0.1236, numDecimalPlaces, testLocale, showThousandsSeparator), "0.123"); } TEST(AzQtComponents, FloatToString_TruncateShowThousandsSeparatorTruncateNoRound) { - QLocale testLocal(QLocale::English, QLocale::UnitedStates); + QLocale testLocale(QLocale::English, QLocale::UnitedStates); const bool showThousandsSeparator = true; int numDecimalPlaces = 3; - EXPECT_EQ(AzQtComponents::toString(1000.1236, numDecimalPlaces, testLocal, showThousandsSeparator), "1,000.123"); + EXPECT_EQ(AzQtComponents::toString(1000.1236, numDecimalPlaces, testLocale, showThousandsSeparator), "1,000.123"); } TEST(AzQtComponents, FloatToString_TruncateShowThousandsSeparatorOnlyOneDecimal) { - QLocale testLocal(QLocale::English, QLocale::UnitedStates); + QLocale testLocale(QLocale::English, QLocale::UnitedStates); const bool showThousandsSeparator = true; int numDecimalPlaces = 2; - EXPECT_EQ(AzQtComponents::toString(1000.000, numDecimalPlaces, testLocal, showThousandsSeparator), "1,000.0"); + EXPECT_EQ(AzQtComponents::toString(1000.000, numDecimalPlaces, testLocale, showThousandsSeparator), "1,000.0"); } TEST(AzQtComponents, FloatToString_Truncate2DecimalsWithLocale) { - QLocale testLocal{ QLocale() }; + QLocale testLocale{ QLocale() }; const bool showThousandsSeparator = false; const int numDecimalPlaces = 2; - QString testString = "0" + QString(testLocal.decimalPoint()) + "12"; - EXPECT_EQ(testString, AzQtComponents::toString(0.1234, numDecimalPlaces, testLocal, showThousandsSeparator)); + QString testString = "0" + QString(testLocale.decimalPoint()) + "12"; + EXPECT_EQ(testString, AzQtComponents::toString(0.1234, numDecimalPlaces, testLocale, showThousandsSeparator)); } TEST(AzQtComponents, FloatToString_AllZerosButOneWithLocale) { - QLocale testLocal{ QLocale() }; + QLocale testLocale{ QLocale() }; const bool showThousandsSeparator = false; const int numDecimalPlaces = 2; - QString testString = "1" + QString(testLocal.decimalPoint()) + "0"; - EXPECT_EQ(testString, AzQtComponents::toString(1.0000, numDecimalPlaces, testLocal, showThousandsSeparator)); + QString testString = "1" + QString(testLocale.decimalPoint()) + "0"; + EXPECT_EQ(testString, AzQtComponents::toString(1.0000, numDecimalPlaces, testLocale, showThousandsSeparator)); } TEST(AzQtComponents, FloatToString_TruncateShowThousandsSeparatorTruncateNoRoundWithLocale) { - QLocale testLocal{ QLocale() }; + QLocale testLocale{ QLocale() }; const bool showThousandsSeparator = true; const int numDecimalPlaces = 3; - QString testString = "1" + QString(testLocal.groupSeparator()) + "000" + QString(testLocal.decimalPoint()) + "123"; - EXPECT_EQ(testString, AzQtComponents::toString(1000.1236, numDecimalPlaces, testLocal, showThousandsSeparator)); + QString testString = "1" + QString(testLocale.groupSeparator()) + "000" + QString(testLocale.decimalPoint()) + "123"; + EXPECT_EQ(testString, AzQtComponents::toString(1000.1236, numDecimalPlaces, testLocale, showThousandsSeparator)); } TEST(AzQtComponents, FloatToString_TruncateShowThousandsSeparatorOnlyOneDecimalWithLocale) { - QLocale testLocal{ QLocale() }; + QLocale testLocale{ QLocale() }; const bool showThousandsSeparator = true; int numDecimalPlaces = 2; - QString testString = "1" + QString(testLocal.groupSeparator()) + "000" + QString(testLocal.decimalPoint()) + "0"; - EXPECT_EQ(testString, AzQtComponents::toString(1000.000, numDecimalPlaces, testLocal, showThousandsSeparator)); + QString testString = "1" + QString(testLocale.groupSeparator()) + "000" + QString(testLocale.decimalPoint()) + "0"; + EXPECT_EQ(testString, AzQtComponents::toString(1000.000, numDecimalPlaces, testLocale, showThousandsSeparator)); } From cb52418a92d083dd3e0de3e2edc538f08760fd15 Mon Sep 17 00:00:00 2001 From: lsemp3d <58790905+lsemp3d@users.noreply.github.com> Date: Mon, 9 Aug 2021 13:05:23 -0700 Subject: [PATCH 24/37] Open the EMFX editor even if no asset is specified Signed-off-by: lsemp3d <58790905+lsemp3d@users.noreply.github.com> --- .../Editor/Components/EditorAnimGraphComponent.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Gems/EMotionFX/Code/Source/Integration/Editor/Components/EditorAnimGraphComponent.cpp b/Gems/EMotionFX/Code/Source/Integration/Editor/Components/EditorAnimGraphComponent.cpp index eef41bc919..8721acbf3d 100644 --- a/Gems/EMotionFX/Code/Source/Integration/Editor/Components/EditorAnimGraphComponent.cpp +++ b/Gems/EMotionFX/Code/Source/Integration/Editor/Components/EditorAnimGraphComponent.cpp @@ -117,16 +117,16 @@ namespace EMotionFX void EditorAnimGraphComponent::LaunchAnimationEditor(const AZ::Data::AssetId& assetId, [[maybe_unused]] const AZ::Data::AssetType& assetType) { + // call to open must be done before LoadCharacter + const char* panelName = EMStudio::MainWindow::GetEMotionFXPaneName(); + EBUS_EVENT(AzToolsFramework::EditorRequests::Bus, OpenViewPane, panelName); + if (assetId.IsValid()) { AZ::Data::AssetId actorAssetId; actorAssetId.SetInvalid(); EditorActorComponentRequestBus::EventResult(actorAssetId, GetEntityId(), &EditorActorComponentRequestBus::Events::GetActorAssetId); - // call to open must be done before LoadCharacter - const char* panelName = EMStudio::MainWindow::GetEMotionFXPaneName(); - EBUS_EVENT(AzToolsFramework::EditorRequests::Bus, OpenViewPane, panelName); - EMStudio::MainWindow* mainWindow = EMStudio::GetMainWindow(); if (mainWindow) { From 8571e71d93f574254a8ead1a24ce4dc09b40414a Mon Sep 17 00:00:00 2001 From: chcurran <82187351+carlitosan@users.noreply.github.com> Date: Mon, 9 Aug 2021 15:42:55 -0700 Subject: [PATCH 25/37] Add JSON serializer support for the Lua component properties Signed-off-by: chcurran <82187351+carlitosan@users.noreply.github.com> --- .gitignore | 1 + .../AzCore/AzCore/Script/ScriptProperty.cpp | 50 ---------- .../AzCore/AzCore/Script/ScriptProperty.h | 28 ------ .../Script/ScriptPropertySerializer.cpp | 99 +++++++++++++++++++ .../AzCore/Script/ScriptPropertySerializer.h | 37 +++++++ .../AzCore/Script/ScriptSystemComponent.cpp | 19 ++-- .../DynamicSerializableField.cpp | 6 +- .../Serialization/DynamicSerializableField.h | 7 +- .../AzCore/AzCore/Slice/SliceComponent.h | 1 - .../AzCore/AzCore/azcore_files.cmake | 2 + .../ToolsComponents/ScriptEditorComponent.cpp | 10 -- .../Internal/VersionedProperty.cpp | 1 - 12 files changed, 160 insertions(+), 101 deletions(-) create mode 100644 Code/Framework/AzCore/AzCore/Script/ScriptPropertySerializer.cpp create mode 100644 Code/Framework/AzCore/AzCore/Script/ScriptPropertySerializer.h diff --git a/.gitignore b/.gitignore index 1b63c7698a..b73c89b1d9 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,4 @@ TestResults/** *.swatches /imgui.ini /scripts/project_manager/logs/ +/AutomatedTesting/Gem/PythonTests/scripting/TestResults diff --git a/Code/Framework/AzCore/AzCore/Script/ScriptProperty.cpp b/Code/Framework/AzCore/AzCore/Script/ScriptProperty.cpp index 8664842893..dbccda4de2 100644 --- a/Code/Framework/AzCore/AzCore/Script/ScriptProperty.cpp +++ b/Code/Framework/AzCore/AzCore/Script/ScriptProperty.cpp @@ -31,7 +31,6 @@ namespace AZ ScriptPropertyGenericClassArray::Reflect(reflection); ScriptPropertyAsset::Reflect(reflection); - ScriptPropertyEntityRef::Reflect(reflection); } template @@ -1358,53 +1357,4 @@ namespace AZ m_value = assetProperty->m_value; } } - - //////////////////////////// - // ScriptPropertyEntityRef - //////////////////////////// - void ScriptPropertyEntityRef::Reflect(AZ::ReflectContext* reflection) - { - AZ::SerializeContext* serializeContext = azrtti_cast(reflection); - - if (serializeContext) - { - serializeContext->Class()-> - Version(1)-> - Field("value", &AZ::ScriptPropertyEntityRef::m_value); - } } - - const AZ::Uuid& ScriptPropertyEntityRef::GetDataTypeUuid() const - { - return AZ::SerializeTypeInfo::GetUuid(); - } - - bool ScriptPropertyEntityRef::DoesTypeMatch(AZ::ScriptDataContext& context, int valueIndex) const - { - return context.IsRegisteredClass(valueIndex); - } - - AZ::ScriptPropertyEntityRef* ScriptPropertyEntityRef::Clone(const char* name) const - { - AZ::ScriptPropertyEntityRef* clonedValue = aznew AZ::ScriptPropertyEntityRef(name ? name : m_name.c_str()); - clonedValue->m_value = m_value; - return clonedValue; - } - - bool ScriptPropertyEntityRef::Write(AZ::ScriptContext& context) - { - AZ::ScriptValue::StackPush(context.NativeContext(), m_value); - return true; - } - - void ScriptPropertyEntityRef::CloneDataFrom(const AZ::ScriptProperty* scriptProperty) - { - const AZ::ScriptPropertyEntityRef* entityProperty = azrtti_cast(scriptProperty); - - AZ_Error("ScriptPropertyEntityRef", entityProperty, "Invalid call to CloneData. Types must match before clone attempt is made.\n"); - if (entityProperty) - { - m_value = entityProperty->m_value; - } - } -} diff --git a/Code/Framework/AzCore/AzCore/Script/ScriptProperty.h b/Code/Framework/AzCore/AzCore/Script/ScriptProperty.h index c12fa3e8f1..f38931127d 100644 --- a/Code/Framework/AzCore/AzCore/Script/ScriptProperty.h +++ b/Code/Framework/AzCore/AzCore/Script/ScriptProperty.h @@ -488,34 +488,6 @@ namespace AZ protected: void CloneDataFrom(const AZ::ScriptProperty* scriptProperty) override; }; - - class ScriptPropertyEntityRef - : public ScriptProperty - { - public: - AZ_CLASS_ALLOCATOR(ScriptPropertyEntityRef, AZ::SystemAllocator, 0); - AZ_RTTI(AZ::ScriptPropertyEntityRef, "{68EDE6C3-0A89-4C50-A86E-06C058C9F862}", ScriptProperty); - - static void Reflect(AZ::ReflectContext* reflection); - - ScriptPropertyEntityRef() {} - ScriptPropertyEntityRef(const char* name) - : ScriptProperty(name) {} - virtual ~ScriptPropertyEntityRef() = default; - const void* GetDataAddress() const override { return &m_value; } - const AZ::Uuid& GetDataTypeUuid() const override; - - bool DoesTypeMatch(AZ::ScriptDataContext& context, int valueIndex) const override; - - ScriptPropertyEntityRef* Clone(const char* name = nullptr) const override; - - bool Write(AZ::ScriptContext& context) override; - - AZ::EntityId m_value; - - protected: - void CloneDataFrom(const AZ::ScriptProperty* scriptProperty) override; - }; } #endif diff --git a/Code/Framework/AzCore/AzCore/Script/ScriptPropertySerializer.cpp b/Code/Framework/AzCore/AzCore/Script/ScriptPropertySerializer.cpp new file mode 100644 index 0000000000..ff889be116 --- /dev/null +++ b/Code/Framework/AzCore/AzCore/Script/ScriptPropertySerializer.cpp @@ -0,0 +1,99 @@ +/* + * 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 +#include +#include + +namespace AZ +{ + AZ_CLASS_ALLOCATOR_IMPL(ScriptPropertySerializer, SystemAllocator, 0); + + JsonSerializationResult::Result ScriptPropertySerializer::Load + ( void* outputValue + , [[maybe_unused]] const Uuid& outputValueTypeId + , const rapidjson::Value& inputValue + , JsonDeserializerContext& context) + { + namespace JSR = JsonSerializationResult; + + AZ_Assert(outputValueTypeId == azrtti_typeid(), "ScriptPropertySerializer Load against output typeID that was not DynamicSerializableField"); + AZ_Assert(outputValue, "ScriptPropertySerializer Load against null output"); + + auto outputVariable = reinterpret_cast(outputValue); + JsonSerializationResult::ResultCode result(JSR::Tasks::ReadField); + AZ::Uuid typeId = AZ::Uuid::CreateNull(); + + auto typeIdMember = inputValue.FindMember(JsonSerialization::TypeIdFieldIdentifier); + if (typeIdMember == inputValue.MemberEnd()) + { + return context.Report(JSR::Tasks::ReadField, JSR::Outcomes::Missing, AZStd::string::format("ScriptPropertySerializer::Load failed to load the %s member", JsonSerialization::TypeIdFieldIdentifier)); + } + + result.Combine(LoadTypeId(typeId, typeIdMember->value, context)); + if (typeId.IsNull()) + { + return context.Report(JSR::Tasks::ReadField, JSR::Outcomes::Catastrophic, "ScriptPropertySerializer::Load failed to load the AZ TypeId of the value"); + } + + AZStd::any storage = context.GetSerializeContext()->CreateAny(typeId); + if (storage.empty() || storage.type() != typeId) + { + return context.Report(result, "ScriptPropertySerializer::Load failed to load a value matched the reported AZ TypeId. The C++ declaration may have been deleted or changed."); + } + + DynamicSerializableField storageField; + storageField.m_data = AZStd::any_cast(&storage); + storageField.m_typeId = typeId; + outputVariable->CopyDataFrom(storageField, context.GetSerializeContext()); + + result.Combine(ContinueLoadingFromJsonObjectField(outputVariable->m_data, typeId, inputValue, "value", context)); + return context.Report(result, result.GetProcessing() != JSR::Processing::Halted + ? "ScriptPropertySerializer Load finished loading DynamicSerializableField" + : "ScriptPropertySerializer Load failed to load DynamicSerializableField"); + } + + JsonSerializationResult::Result ScriptPropertySerializer::Store + ( rapidjson::Value& outputValue + , const void* inputValue + , const void* defaultValue + , [[maybe_unused]] const Uuid& valueTypeId + , JsonSerializerContext& context) + { + namespace JSR = JsonSerializationResult; + + AZ_Assert(valueTypeId == azrtti_typeid(), "DynamicSerializableField Store against value typeID that was not DynamicSerializableField"); + AZ_Assert(inputValue, "DynamicSerializableField Store against null inputValue pointer "); + + auto inputScriptDataPtr = reinterpret_cast(inputValue); + auto inputFieldPtr = inputScriptDataPtr->m_data; + auto defaultScriptDataPtr = reinterpret_cast(defaultValue); + auto defaultFieldPtr = defaultScriptDataPtr ? &defaultScriptDataPtr->m_data : nullptr; + + if (defaultScriptDataPtr && inputScriptDataPtr->IsEqualTo(*defaultScriptDataPtr, context.GetSerializeContext())) + { + return context.Report(JSR::Tasks::WriteValue, JSR::Outcomes::DefaultsUsed, "ScriptPropertySerializer Store used defaults for DynamicSerializableField"); + } + + JSR::ResultCode result(JSR::Tasks::WriteValue); + outputValue.SetObject(); + + { + rapidjson::Value typeValue; + result.Combine(StoreTypeId(typeValue, inputScriptDataPtr->m_typeId, context)); + outputValue.AddMember(rapidjson::StringRef(JsonSerialization::TypeIdFieldIdentifier), AZStd::move(typeValue), context.GetJsonAllocator()); + } + + result.Combine(ContinueStoringToJsonObjectField(outputValue, "value", inputFieldPtr, defaultFieldPtr, inputScriptDataPtr->m_typeId, context)); + + return context.Report(result, result.GetProcessing() != JSR::Processing::Halted + ? "ScriptPropertySerializer Store finished saving DynamicSerializableField" + : "ScriptPropertySerializer Store failed to save DynamicSerializableField"); + } + +} diff --git a/Code/Framework/AzCore/AzCore/Script/ScriptPropertySerializer.h b/Code/Framework/AzCore/AzCore/Script/ScriptPropertySerializer.h new file mode 100644 index 0000000000..a3de3e761d --- /dev/null +++ b/Code/Framework/AzCore/AzCore/Script/ScriptPropertySerializer.h @@ -0,0 +1,37 @@ +/* + * 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 + * + */ + +#pragma once + +#include +#include +#include + +namespace AZ +{ + class ScriptPropertySerializer + : public BaseJsonSerializer + { + public: + AZ_RTTI(ScriptPropertySerializer, "{C7BECA49-84EF-45E6-A89D-052D61766197}", BaseJsonSerializer); + AZ_CLASS_ALLOCATOR_DECL; + + private: + JsonSerializationResult::Result Load + ( void* outputValue + , const Uuid& outputValueTypeId + , const rapidjson::Value& inputValue + , JsonDeserializerContext& context) override; + + JsonSerializationResult::Result Store + ( rapidjson::Value& outputValue + , const void* inputValue + , const void* defaultValue + , const Uuid& valueTypeId, JsonSerializerContext& context) override; + }; +} diff --git a/Code/Framework/AzCore/AzCore/Script/ScriptSystemComponent.cpp b/Code/Framework/AzCore/AzCore/Script/ScriptSystemComponent.cpp index 6706d4f8d8..fa61a225c8 100644 --- a/Code/Framework/AzCore/AzCore/Script/ScriptSystemComponent.cpp +++ b/Code/Framework/AzCore/AzCore/Script/ScriptSystemComponent.cpp @@ -8,10 +8,8 @@ #if !defined(AZCORE_EXCLUDE_LUA) -#include - -#include #include +#include #include #include #include @@ -21,13 +19,16 @@ #include #include #include -#include #include #include #include - -#include +#include +#include #include +#include +#include +#include +#include using namespace AZ; @@ -921,6 +922,12 @@ void ScriptSystemComponent::Reflect(ReflectContext* reflection) } } + if (AZ::JsonRegistrationContext* jsonContext = azrtti_cast(reflection)) + { + jsonContext->Serializer() + ->HandlesType(); + } + if (BehaviorContext* behaviorContext = azrtti_cast(reflection)) { // reflect default entity diff --git a/Code/Framework/AzCore/AzCore/Serialization/DynamicSerializableField.cpp b/Code/Framework/AzCore/AzCore/Serialization/DynamicSerializableField.cpp index 16b6a2c48b..036c2df297 100644 --- a/Code/Framework/AzCore/AzCore/Serialization/DynamicSerializableField.cpp +++ b/Code/Framework/AzCore/AzCore/Serialization/DynamicSerializableField.cpp @@ -98,14 +98,14 @@ namespace AZ return nullptr; } //------------------------------------------------------------------------- - void DynamicSerializableField::CopyDataFrom(const DynamicSerializableField& other) + void DynamicSerializableField::CopyDataFrom(const DynamicSerializableField& other, SerializeContext* useContext) { DestroyData(); m_typeId = other.m_typeId; - m_data = other.CloneData(); + m_data = other.CloneData(useContext); } //------------------------------------------------------------------------- - bool DynamicSerializableField::IsEqualTo(const DynamicSerializableField& other, SerializeContext* useContext) + bool DynamicSerializableField::IsEqualTo(const DynamicSerializableField& other, SerializeContext* useContext) const { if (other.m_typeId != m_typeId) { diff --git a/Code/Framework/AzCore/AzCore/Serialization/DynamicSerializableField.h b/Code/Framework/AzCore/AzCore/Serialization/DynamicSerializableField.h index 70a8924265..4b5a963b26 100644 --- a/Code/Framework/AzCore/AzCore/Serialization/DynamicSerializableField.h +++ b/Code/Framework/AzCore/AzCore/Serialization/DynamicSerializableField.h @@ -9,6 +9,8 @@ #define AZCORE_DYNAMIC_SERIALIZABLE_FIELD_H #include +#include +#include namespace AZ { @@ -24,6 +26,7 @@ namespace AZ { public: AZ_TYPE_INFO(DynamicSerializableField, "{D761E0C2-A098-497C-B8EB-EA62F5ED896B}") + AZ_CLASS_ALLOCATOR(DynamicSerializableField, AZ::SystemAllocator, 0); DynamicSerializableField(); DynamicSerializableField(const DynamicSerializableField& serializableField); @@ -33,8 +36,8 @@ namespace AZ void DestroyData(SerializeContext* useContext = nullptr); void* CloneData(SerializeContext* useContext = nullptr) const; - void CopyDataFrom(const DynamicSerializableField& other); - bool IsEqualTo(const DynamicSerializableField& other, SerializeContext* useContext = nullptr); + void CopyDataFrom(const DynamicSerializableField& other, SerializeContext* useContext = nullptr); + bool IsEqualTo(const DynamicSerializableField& other, SerializeContext* useContext = nullptr) const; template void Set(T* object) diff --git a/Code/Framework/AzCore/AzCore/Slice/SliceComponent.h b/Code/Framework/AzCore/AzCore/Slice/SliceComponent.h index 86f41c6548..7a66167a96 100644 --- a/Code/Framework/AzCore/AzCore/Slice/SliceComponent.h +++ b/Code/Framework/AzCore/AzCore/Slice/SliceComponent.h @@ -13,7 +13,6 @@ #include #include #include -#include #include namespace AZ diff --git a/Code/Framework/AzCore/AzCore/azcore_files.cmake b/Code/Framework/AzCore/AzCore/azcore_files.cmake index 1e2a0b98a0..c33d678730 100644 --- a/Code/Framework/AzCore/AzCore/azcore_files.cmake +++ b/Code/Framework/AzCore/AzCore/azcore_files.cmake @@ -462,6 +462,8 @@ set(FILES Script/ScriptTimePoint.h Script/ScriptProperty.h Script/ScriptProperty.cpp + Script/ScriptPropertySerializer.h + Script/ScriptPropertySerializer.cpp Script/ScriptPropertyTable.h Script/ScriptPropertyTable.cpp Script/ScriptPropertyWatcherBus.h diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/ToolsComponents/ScriptEditorComponent.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/ToolsComponents/ScriptEditorComponent.cpp index 1ae1a3605a..da12a4bd7a 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/ToolsComponents/ScriptEditorComponent.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/ToolsComponents/ScriptEditorComponent.cpp @@ -1107,17 +1107,7 @@ namespace AzToolsFramework ElementAttribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly)-> Attribute(AZ::Edit::Attributes::NameLabelOverride, &AZ::ScriptProperty::m_name); - ec->Class("Script Property Asset(asset)", "A script asset property")-> - ClassElement(AZ::Edit::ClassElements::EditorData, "ScriptPropertyEditorAsset's class attributes.")-> - Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly)-> - DataElement("Asset", &AZ::ScriptPropertyAsset::m_value, "m_value", "An object")-> - Attribute(AZ::Edit::Attributes::NameLabelOverride, &AZ::ScriptProperty::m_name); - ec->Class("Script Property Entity(EntityRef)", "A script entity reference property")-> - ClassElement(AZ::Edit::ClassElements::EditorData, "ScriptPropertyEditorEntityRef's class attributes.")-> - Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly)-> - DataElement("EntityRef", &AZ::ScriptPropertyEntityRef::m_value, "m_entity", "An entity reference")-> - Attribute(AZ::Edit::Attributes::NameLabelOverride, &AZ::ScriptProperty::m_name); } } } diff --git a/Gems/ScriptEvents/Code/Include/ScriptEvents/Internal/VersionedProperty.cpp b/Gems/ScriptEvents/Code/Include/ScriptEvents/Internal/VersionedProperty.cpp index 59f2ffe58c..e09bdc94a9 100644 --- a/Gems/ScriptEvents/Code/Include/ScriptEvents/Internal/VersionedProperty.cpp +++ b/Gems/ScriptEvents/Code/Include/ScriptEvents/Internal/VersionedProperty.cpp @@ -9,7 +9,6 @@ #include "VersionedProperty.h" #include -#include #include #include #include From c6fdb76aedf74a760f314f8074a05743560e7927 Mon Sep 17 00:00:00 2001 From: chcurran <82187351+carlitosan@users.noreply.github.com> Date: Mon, 9 Aug 2021 16:33:54 -0700 Subject: [PATCH 26/37] Bump version number on LuaBuilderComponent Signed-off-by: chcurran <82187351+carlitosan@users.noreply.github.com> --- .../Code/Source/Builders/LuaBuilder/LuaBuilderComponent.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gems/LmbrCentral/Code/Source/Builders/LuaBuilder/LuaBuilderComponent.cpp b/Gems/LmbrCentral/Code/Source/Builders/LuaBuilder/LuaBuilderComponent.cpp index 5a787c480f..0ef02837da 100644 --- a/Gems/LmbrCentral/Code/Source/Builders/LuaBuilder/LuaBuilderComponent.cpp +++ b/Gems/LmbrCentral/Code/Source/Builders/LuaBuilder/LuaBuilderComponent.cpp @@ -25,7 +25,7 @@ void LuaBuilder::BuilderPluginComponent::Activate() { AssetBuilderSDK::AssetBuilderDesc builderDescriptor; builderDescriptor.m_name = "Lua Worker Builder"; - builderDescriptor.m_version = 6; + builderDescriptor.m_version = 7; builderDescriptor.m_analysisFingerprint = AZStd::string::format("%d", static_cast(AZ::ScriptAsset::AssetVersion)); builderDescriptor.m_patterns.push_back(AssetBuilderSDK::AssetBuilderPattern("*.lua", AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard)); builderDescriptor.m_busId = azrtti_typeid(); From c2a2d1c5c79b8c41211e2156dac7a98ba1a9a3d5 Mon Sep 17 00:00:00 2001 From: John Date: Tue, 10 Aug 2021 14:20:46 +0100 Subject: [PATCH 27/37] Add changes from TIF/Feature branch. Signed-off-by: John --- .../Source/TestImpactCommandLineOptions.cpp | 56 +- .../Source/TestImpactCommandLineOptions.h | 24 +- .../Code/Source/TestImpactConsoleMain.cpp | 178 +++-- ...tImpactConsoleTestSequenceEventHandler.cpp | 58 +- ...estImpactConsoleTestSequenceEventHandler.h | 9 +- .../TestImpactRuntimeConfigurationFactory.cpp | 21 +- .../TestImpactClientSequenceReport.h | 500 ++++++++++++--- ...TestImpactClientSequenceReportSerializer.h | 28 + .../TestImpactClientTestRun.h | 145 +++-- .../TestImpactConfiguration.h | 2 +- .../TestImpactFramework/TestImpactPolicy.h | 81 +++ .../TestImpactFramework/TestImpactRuntime.h | 30 +- .../TestImpactSequenceReportException.h | 22 + .../TestImpactTestSequence.h | 114 +--- ...estImpactFileUtils.h => TestImpactUtils.h} | 65 +- .../TestImpactTestTargetMetaMapFactory.cpp | 4 +- .../TestImpactDynamicDependencyMap.cpp | 2 - .../TestImpactDynamicDependencyMap.h | 2 +- .../TestImpactTestSelectorAndPrioritizer.h | 2 +- .../Enumeration/TestImpactTestEnumerator.cpp | 2 +- .../Run/TestImpactInstrumentedTestRunner.cpp | 2 +- .../TestEngine/Run/TestImpactTestRunner.cpp | 2 +- .../TestEngine/TestImpactTestEngine.cpp | 2 +- .../Source/TestImpactClientSequenceReport.cpp | 324 +++++----- ...stImpactClientSequenceReportSerializer.cpp | 606 ++++++++++++++++++ .../Code/Source/TestImpactClientTestRun.cpp | 130 ++-- .../Runtime/Code/Source/TestImpactRuntime.cpp | 496 ++++++++------ .../Code/Source/TestImpactRuntimeUtils.cpp | 2 +- .../Code/Source/TestImpactRuntimeUtils.h | 56 +- .../Runtime/Code/Source/TestImpactUtils.cpp | 244 +++++++ .../testimpactframework_runtime_files.cmake | 7 +- .../ConsoleFrontendConfig.in | 6 +- .../LYTestImpactFramework.cmake | 9 +- .../build/Platform/Windows/build_config.json | 2 +- scripts/build/TestImpactAnalysis/git_utils.py | 77 ++- .../build/TestImpactAnalysis/mars_utils.py | 452 +++++++++++++ scripts/build/TestImpactAnalysis/tiaf.py | 428 +++++++------ .../build/TestImpactAnalysis/tiaf_driver.py | 132 +++- .../build/TestImpactAnalysis/tiaf_logger.py | 20 + .../tiaf_persistent_storage.py | 118 ++++ .../tiaf_persistent_storage_local.py | 56 ++ .../tiaf_persistent_storage_s3.py | 87 +++ 42 files changed, 3534 insertions(+), 1069 deletions(-) create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Include/TestImpactFramework/TestImpactClientSequenceReportSerializer.h create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Include/TestImpactFramework/TestImpactPolicy.h create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Include/TestImpactFramework/TestImpactSequenceReportException.h rename Code/Tools/TestImpactFramework/Runtime/Code/Include/TestImpactFramework/{TestImpactFileUtils.h => TestImpactUtils.h} (51%) create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Source/TestImpactClientSequenceReportSerializer.cpp create mode 100644 Code/Tools/TestImpactFramework/Runtime/Code/Source/TestImpactUtils.cpp create mode 100644 scripts/build/TestImpactAnalysis/mars_utils.py create mode 100644 scripts/build/TestImpactAnalysis/tiaf_logger.py create mode 100644 scripts/build/TestImpactAnalysis/tiaf_persistent_storage.py create mode 100644 scripts/build/TestImpactAnalysis/tiaf_persistent_storage_local.py create mode 100644 scripts/build/TestImpactAnalysis/tiaf_persistent_storage_s3.py diff --git a/Code/Tools/TestImpactFramework/Frontend/Console/Code/Source/TestImpactCommandLineOptions.cpp b/Code/Tools/TestImpactFramework/Frontend/Console/Code/Source/TestImpactCommandLineOptions.cpp index cbe587cf1a..0d1f352f21 100644 --- a/Code/Tools/TestImpactFramework/Frontend/Console/Code/Source/TestImpactCommandLineOptions.cpp +++ b/Code/Tools/TestImpactFramework/Frontend/Console/Code/Source/TestImpactCommandLineOptions.cpp @@ -6,6 +6,8 @@ * */ +#include + #include #include @@ -19,8 +21,9 @@ namespace TestImpact { // Options ConfigKey, + DataFileKey, ChangeListKey, - OutputChangeListKey, + SequenceReportKey, SequenceKey, TestPrioritizationPolicyKey, ExecutionFailurePolicyKey, @@ -55,8 +58,9 @@ namespace TestImpact { // Options "config", + "datafile", "changelist", - "ochangelist", + "report", "sequence", "ppolicy", "epolicy", @@ -92,14 +96,19 @@ namespace TestImpact return ParsePathOption(OptionKeys[ConfigKey], cmd).value_or(LY_TEST_IMPACT_DEFAULT_CONFIG_FILE); } + AZStd::optional ParseDataFile(const AZ::CommandLine& cmd) + { + return ParsePathOption(OptionKeys[DataFileKey], cmd); + } + AZStd::optional ParseChangeListFile(const AZ::CommandLine& cmd) { return ParsePathOption(OptionKeys[ChangeListKey], cmd); } - bool ParseOutputChangeList(const AZ::CommandLine& cmd) + AZStd::optional ParseSequenceReportFile(const AZ::CommandLine& cmd) { - return ParseOnOffOption(OptionKeys[OutputChangeListKey], BinaryStateValue{ false, true }, cmd).value_or(false); + return ParsePathOption(OptionKeys[SequenceReportKey], cmd); } TestSequenceType ParseTestSequenceType(const AZ::CommandLine& cmd) @@ -255,9 +264,9 @@ namespace TestImpact { const AZStd::vector> states = { - {GetSuiteTypeName(SuiteType::Main), SuiteType::Main}, - {GetSuiteTypeName(SuiteType::Periodic), SuiteType::Periodic}, - {GetSuiteTypeName(SuiteType::Sandbox), SuiteType::Sandbox} + { SuiteTypeAsString(SuiteType::Main), SuiteType::Main }, + { SuiteTypeAsString(SuiteType::Periodic), SuiteType::Periodic }, + { SuiteTypeAsString(SuiteType::Sandbox), SuiteType::Sandbox } }; return ParseMultiStateOption(OptionKeys[SuiteFilterKey], states, cmd).value_or(SuiteType::Main); @@ -270,8 +279,9 @@ namespace TestImpact cmd.Parse(argc, argv); m_configurationFile = ParseConfigurationFile(cmd); + m_dataFile = ParseDataFile(cmd); m_changeListFile = ParseChangeListFile(cmd); - m_outputChangeList = ParseOutputChangeList(cmd); + m_sequenceReportFile = ParseSequenceReportFile(cmd); m_testSequenceType = ParseTestSequenceType(cmd); m_testPrioritizationPolicy = ParseTestPrioritizationPolicy(cmd); m_executionFailurePolicy = ParseExecutionFailurePolicy(cmd); @@ -286,28 +296,43 @@ namespace TestImpact m_safeMode = ParseSafeMode(cmd); m_suiteFilter = ParseSuiteFilter(cmd); } + + bool CommandLineOptions::HasDataFilePath() const + { + return m_dataFile.has_value(); + } - bool CommandLineOptions::HasChangeListFile() const + bool CommandLineOptions::HasChangeListFilePath() const { return m_changeListFile.has_value(); } + bool CommandLineOptions::HasSequenceReportFilePath() const + { + return m_sequenceReportFile.has_value(); + } + bool CommandLineOptions::HasSafeMode() const { return m_safeMode; } - const AZStd::optional& CommandLineOptions::GetChangeListFile() const + const AZStd::optional& CommandLineOptions::GetDataFilePath() const + { + return m_dataFile; + } + + const AZStd::optional& CommandLineOptions::GetChangeListFilePath() const { return m_changeListFile; } - bool CommandLineOptions::HasOutputChangeList() const + const AZStd::optional& CommandLineOptions::GetSequenceReportFilePath() const { - return m_outputChangeList; + return m_sequenceReportFile; } - const RepoPath& CommandLineOptions::GetConfigurationFile() const + const RepoPath& CommandLineOptions::GetConfigurationFilePath() const { return m_configurationFile; } @@ -379,8 +404,12 @@ namespace TestImpact " options:\n" " -config= Path to the configuration file for the TIAF runtime (default: \n" " ..json).\n" + " -datafile= Optional path to a test impact data file that will used instead of that\n" + " specified in the config file.\n" " -changelist= Path to the JSON of source file changes to perform test impact \n" " analysis on.\n" + " -report= Path to where the sequence report file will be written (if this option \n" + " is not specified, no report will be written).\n" " -gtimeout= Global timeout value to terminate the entire test sequence should it \n" " be exceeded.\n" " -ttimeout= Timeout value to terminate individual test targets should it be \n" @@ -443,7 +472,6 @@ namespace TestImpact " available, no prioritization will occur).\n" " -maxconcurrency= The maximum number of concurrent test targets/shards to be in flight at \n" " any given moment.\n" - " -ochangelist= Outputs the change list used for test selection.\n" " -suite= The test suite to select from for this test sequence."; return help; diff --git a/Code/Tools/TestImpactFramework/Frontend/Console/Code/Source/TestImpactCommandLineOptions.h b/Code/Tools/TestImpactFramework/Frontend/Console/Code/Source/TestImpactCommandLineOptions.h index ea58305afb..36ca4231b9 100644 --- a/Code/Tools/TestImpactFramework/Frontend/Console/Code/Source/TestImpactCommandLineOptions.h +++ b/Code/Tools/TestImpactFramework/Frontend/Console/Code/Source/TestImpactCommandLineOptions.h @@ -36,20 +36,29 @@ namespace TestImpact CommandLineOptions(int argc, char** argv); static AZStd::string GetCommandLineUsageString(); + //! Returns true if a test impact data file path has been supplied, otherwise false. + bool HasDataFilePath() const; + //! Returns true if a change list file path has been supplied, otherwise false. - bool HasChangeListFile() const; + bool HasChangeListFilePath() const; + + //! Returns true if a sequence report file path has been supplied, otherwise false. + bool HasSequenceReportFilePath() const; //! Returns true if the safe mode option has been enabled, otherwise false. bool HasSafeMode() const; - //! Returns true if the output change list option has been enabled, otherwise false. - bool HasOutputChangeList() const; - //! Returns the path to the runtime configuration file. - const RepoPath& GetConfigurationFile() const; + const RepoPath& GetConfigurationFilePath() const; + + //! Returns the path to the data file (if any). + const AZStd::optional& GetDataFilePath() const; //! Returns the path to the change list file (if any). - const AZStd::optional& GetChangeListFile() const; + const AZStd::optional& GetChangeListFilePath() const; + + //! Returns the path to the sequence report file (if any). + const AZStd::optional& GetSequenceReportFilePath() const; //! Returns the test sequence type to run. TestSequenceType GetTestSequenceType() const; @@ -89,8 +98,9 @@ namespace TestImpact private: RepoPath m_configurationFile; + AZStd::optional m_dataFile; AZStd::optional m_changeListFile; - bool m_outputChangeList = false; + AZStd::optional m_sequenceReportFile; TestSequenceType m_testSequenceType; Policy::TestPrioritization m_testPrioritizationPolicy = Policy::TestPrioritization::None; Policy::ExecutionFailure m_executionFailurePolicy = Policy::ExecutionFailure::Continue; diff --git a/Code/Tools/TestImpactFramework/Frontend/Console/Code/Source/TestImpactConsoleMain.cpp b/Code/Tools/TestImpactFramework/Frontend/Console/Code/Source/TestImpactConsoleMain.cpp index 8027649a44..bbb4765ad4 100644 --- a/Code/Tools/TestImpactFramework/Frontend/Console/Code/Source/TestImpactConsoleMain.cpp +++ b/Code/Tools/TestImpactFramework/Frontend/Console/Code/Source/TestImpactConsoleMain.cpp @@ -9,14 +9,16 @@ #include #include #include +#include #include #include #include #include #include -#include +#include #include #include +#include #include #include @@ -33,31 +35,6 @@ namespace TestImpact { namespace Console { - //! Generates a string to be used for printing to the console for the specified change list. - AZStd::string GenerateChangeListString(const ChangeList& changeList) - { - AZStd::string output; - - const auto& outputFiles = [&output](const AZStd::vector& files) - { - for (const auto& file : files) - { - output += AZStd::string::format("\t%s\n", file.c_str()); - } - }; - - output += AZStd::string::format("Created files (%u):\n", changeList.m_createdFiles.size()); - outputFiles(changeList.m_createdFiles); - - output += AZStd::string::format("Updated files (%u):\n", changeList.m_updatedFiles.size()); - outputFiles(changeList.m_updatedFiles); - - output += AZStd::string::format("Deleted files (%u):\n", changeList.m_deletedFiles.size()); - outputFiles(changeList.m_deletedFiles); - - return output; - } - //! Gets the appropriate console return code for the specified test sequence result. ReturnCode GetReturnCodeForTestSequenceResult(TestSequenceResult result) { @@ -75,6 +52,20 @@ namespace TestImpact } } + //! Wrapper around sequence reports to optionally serialize them and transform the result into a return code. + template + ReturnCode ConsumeSequenceReportAndGetReturnCode(const SequenceReportType& sequenceReport, const CommandLineOptions& options) + { + if (options.HasSequenceReportFilePath()) + { + std::cout << "Exporting sequence report '" << options.GetSequenceReportFilePath().value().c_str() << "'" << std::endl; + const auto sequenceReportJson = SerializeSequenceReport(sequenceReport); + WriteFileContents(sequenceReportJson, options.GetSequenceReportFilePath().value()); + } + + return GetReturnCodeForTestSequenceResult(sequenceReport.GetResult()); + } + //! Wrapper around impact analysis sequences to handle the case where the safe mode option is active. ReturnCode WrappedImpactAnalysisTestSequence( const CommandLineOptions& options, @@ -88,35 +79,34 @@ namespace TestImpact CommandLineOptionsException, "Expected a change list for impact analysis but none was provided"); - TestSequenceResult result = TestSequenceResult::Failure; if (options.HasSafeMode()) { if (options.GetTestSequenceType() == TestSequenceType::ImpactAnalysis) { - auto safeImpactAnalysisSequenceReport = runtime.SafeImpactAnalysisTestSequence( - changeList.value(), - options.GetTestPrioritizationPolicy(), - options.GetTestTargetTimeout(), - options.GetGlobalTimeout(), - SafeImpactAnalysisTestSequenceStartCallback, - SafeImpactAnalysisTestSequenceCompleteCallback, - TestRunCompleteCallback); - - result = safeImpactAnalysisSequenceReport.GetResult(); + return ConsumeSequenceReportAndGetReturnCode( + runtime.SafeImpactAnalysisTestSequence( + changeList.value(), + options.GetTestPrioritizationPolicy(), + options.GetTestTargetTimeout(), + options.GetGlobalTimeout(), + SafeImpactAnalysisTestSequenceStartCallback, + SafeImpactAnalysisTestSequenceCompleteCallback, + TestRunCompleteCallback), + options); } else if (options.GetTestSequenceType() == TestSequenceType::ImpactAnalysisNoWrite) { // A no-write impact analysis sequence with safe mode enabled is functionally identical to a regular sequence type // due to a) the selected tests being run without instrumentation and b) the discarded tests also being run without // instrumentation - auto sequenceReport = runtime.RegularTestSequence( - options.GetTestTargetTimeout(), - options.GetGlobalTimeout(), - TestSequenceStartCallback, - TestSequenceCompleteCallback, - TestRunCompleteCallback); - - result = sequenceReport.GetResult(); + return ConsumeSequenceReportAndGetReturnCode( + runtime.RegularTestSequence( + options.GetTestTargetTimeout(), + options.GetGlobalTimeout(), + TestSequenceStartCallback, + RegularTestSequenceCompleteCallback, + TestRunCompleteCallback), + options); } else { @@ -139,20 +129,18 @@ namespace TestImpact throw(Exception("Unexpected sequence type")); } - auto impactAnalysisSequenceReport = runtime.ImpactAnalysisTestSequence( - changeList.value(), - options.GetTestPrioritizationPolicy(), - dynamicDependencyMapPolicy, - options.GetTestTargetTimeout(), - options.GetGlobalTimeout(), - ImpactAnalysisTestSequenceStartCallback, - ImpactAnalysisTestSequenceCompleteCallback, - TestRunCompleteCallback); - - result = impactAnalysisSequenceReport.GetResult(); + return ConsumeSequenceReportAndGetReturnCode( + runtime.ImpactAnalysisTestSequence( + changeList.value(), + options.GetTestPrioritizationPolicy(), + dynamicDependencyMapPolicy, + options.GetTestTargetTimeout(), + options.GetGlobalTimeout(), + ImpactAnalysisTestSequenceStartCallback, + ImpactAnalysisTestSequenceCompleteCallback, + TestRunCompleteCallback), + options); } - - return GetReturnCodeForTestSequenceResult(result); }; //! Entry point for the test impact analysis framework console front end application. @@ -164,28 +152,22 @@ namespace TestImpact AZStd::optional changeList; // If we have a change list, check to see whether or not the client has requested the printing of said change list - if (options.HasChangeListFile()) + if (options.HasChangeListFilePath()) { - changeList = DeserializeChangeList(ReadFileContents(*options.GetChangeListFile())); - if (options.HasOutputChangeList()) - { - std::cout << "Change List:\n"; - std::cout << GenerateChangeListString(*changeList).c_str(); - - if (options.GetTestSequenceType() == TestSequenceType::None) - { - return ReturnCode::Success; - } - } + changeList = DeserializeChangeList(ReadFileContents(*options.GetChangeListFilePath())); } - // As of now, there are no other non-test operations other than printing a change list so getting this far is considered an error - AZ_TestImpact_Eval(options.GetTestSequenceType() != TestSequenceType::None, CommandLineOptionsException, "No action specified"); + // As of now, there are no non-test operations but leave this door open for the future + if (options.GetTestSequenceType() == TestSequenceType::None) + { + return ReturnCode::Success; + } std::cout << "Constructing in-memory model of source tree and test coverage for test suite "; - std::cout << GetSuiteTypeName(options.GetSuiteFilter()).c_str() << ", this may take a moment...\n"; + std::cout << SuiteTypeAsString(options.GetSuiteFilter()).c_str() << ", this may take a moment...\n"; Runtime runtime( - RuntimeConfigurationFactory(ReadFileContents(options.GetConfigurationFile())), + RuntimeConfigurationFactory(ReadFileContents(options.GetConfigurationFilePath())), + options.GetDataFilePath(), options.GetSuiteFilter(), options.GetExecutionFailurePolicy(), options.GetFailedTestCoveragePolicy(), @@ -208,25 +190,25 @@ namespace TestImpact { case TestSequenceType::Regular: { - const auto sequenceReport = runtime.RegularTestSequence( - options.GetTestTargetTimeout(), - options.GetGlobalTimeout(), - TestSequenceStartCallback, - TestSequenceCompleteCallback, - TestRunCompleteCallback); - - return GetReturnCodeForTestSequenceResult(sequenceReport.GetResult()); + return ConsumeSequenceReportAndGetReturnCode( + runtime.RegularTestSequence( + options.GetTestTargetTimeout(), + options.GetGlobalTimeout(), + TestSequenceStartCallback, + RegularTestSequenceCompleteCallback, + TestRunCompleteCallback), + options); } case TestSequenceType::Seed: { - const auto sequenceReport = runtime.SeededTestSequence( - options.GetTestTargetTimeout(), - options.GetGlobalTimeout(), - TestSequenceStartCallback, - TestSequenceCompleteCallback, - TestRunCompleteCallback); - - return GetReturnCodeForTestSequenceResult(sequenceReport.GetResult()); + return ConsumeSequenceReportAndGetReturnCode( + runtime.SeededTestSequence( + options.GetTestTargetTimeout(), + options.GetGlobalTimeout(), + TestSequenceStartCallback, + SeedTestSequenceCompleteCallback, + TestRunCompleteCallback), + options); } case TestSequenceType::ImpactAnalysisNoWrite: case TestSequenceType::ImpactAnalysis: @@ -241,14 +223,14 @@ namespace TestImpact } else { - const auto sequenceReport = runtime.SeededTestSequence( - options.GetTestTargetTimeout(), - options.GetGlobalTimeout(), - TestSequenceStartCallback, - TestSequenceCompleteCallback, - TestRunCompleteCallback); - - return GetReturnCodeForTestSequenceResult(sequenceReport.GetResult()); + return ConsumeSequenceReportAndGetReturnCode( + runtime.SeededTestSequence( + options.GetTestTargetTimeout(), + options.GetGlobalTimeout(), + TestSequenceStartCallback, + SeedTestSequenceCompleteCallback, + TestRunCompleteCallback), + options); } } default: diff --git a/Code/Tools/TestImpactFramework/Frontend/Console/Code/Source/TestImpactConsoleTestSequenceEventHandler.cpp b/Code/Tools/TestImpactFramework/Frontend/Console/Code/Source/TestImpactConsoleTestSequenceEventHandler.cpp index da7052933c..e7430fe90b 100644 --- a/Code/Tools/TestImpactFramework/Frontend/Console/Code/Source/TestImpactConsoleTestSequenceEventHandler.cpp +++ b/Code/Tools/TestImpactFramework/Frontend/Console/Code/Source/TestImpactConsoleTestSequenceEventHandler.cpp @@ -6,8 +6,9 @@ * */ -#include +#include +#include #include #include @@ -20,7 +21,7 @@ namespace TestImpact { void TestSuiteFilter(SuiteType filter) { - std::cout << "Test suite filter: " << GetSuiteTypeName(filter).c_str() << "\n"; + std::cout << "Test suite filter: " << SuiteTypeAsString(filter).c_str() << "\n"; } void ImpactAnalysisTestSelection(size_t numSelectedTests, size_t numDiscardedTests, size_t numExcludedTests, size_t numDraftedTests) @@ -36,69 +37,66 @@ namespace TestImpact { std::cout << "Sequence completed in " << (testRunReport.GetDuration().count() / 1000.f) << "s with"; - if (!testRunReport.GetExecutionFailureTests().empty() || - !testRunReport.GetFailingTests().empty() || - !testRunReport.GetTimedOutTests().empty() || - !testRunReport.GetUnexecutedTests().empty()) + if (!testRunReport.GetExecutionFailureTestRuns().empty() || + !testRunReport.GetFailingTestRuns().empty() || + !testRunReport.GetTimedOutTestRuns().empty() || + !testRunReport.GetUnexecutedTestRuns().empty()) { std::cout << ":\n"; std::cout << SetColor(Foreground::White, Background::Red).c_str() - << testRunReport.GetFailingTests().size() + << testRunReport.GetFailingTestRuns().size() << ResetColor().c_str() << " test failures\n"; std::cout << SetColor(Foreground::White, Background::Red).c_str() - << testRunReport.GetExecutionFailureTests().size() + << testRunReport.GetExecutionFailureTestRuns().size() << ResetColor().c_str() << " execution failures\n"; std::cout << SetColor(Foreground::White, Background::Red).c_str() - << testRunReport.GetTimedOutTests().size() + << testRunReport.GetTimedOutTestRuns().size() << ResetColor().c_str() << " test timeouts\n"; std::cout << SetColor(Foreground::White, Background::Red).c_str() - << testRunReport.GetUnexecutedTests().size() + << testRunReport.GetUnexecutedTestRuns().size() << ResetColor().c_str() << " unexecuted tests\n"; - if (!testRunReport.GetFailingTests().empty()) + if (!testRunReport.GetFailingTestRuns().empty()) { std::cout << "\nTest failures:\n"; - for (const auto& testRunFailure : testRunReport.GetFailingTests()) + for (const auto& testRunFailure : testRunReport.GetFailingTestRuns()) { - for (const auto& testCaseFailure : testRunFailure.GetTestCaseFailures()) + for (const auto& test : testRunFailure.GetTests()) { - for (const auto& testFailure : testCaseFailure.GetTestFailures()) + if (test.GetResult() == Client::TestResult::Failed) { - std::cout << " " - << testRunFailure.GetTargetName().c_str() - << "." << testCaseFailure.GetName().c_str() - << "." << testFailure.GetName().c_str() << "\n"; + std::cout << " " << test.GetName().c_str() << "\n"; } } } } - if (!testRunReport.GetExecutionFailureTests().empty()) + if (!testRunReport.GetExecutionFailureTestRuns().empty()) { std::cout << "\nExecution failures:\n"; - for (const auto& executionFailure : testRunReport.GetExecutionFailureTests()) + for (const auto& executionFailure : testRunReport.GetExecutionFailureTestRuns()) { std::cout << " " << executionFailure.GetTargetName().c_str() << "\n"; std::cout << executionFailure.GetCommandString().c_str() << "\n"; } } - if (!testRunReport.GetTimedOutTests().empty()) + if (!testRunReport.GetTimedOutTestRuns().empty()) { std::cout << "\nTimed out tests:\n"; - for (const auto& testTimeout : testRunReport.GetTimedOutTests()) + for (const auto& testTimeout : testRunReport.GetTimedOutTestRuns()) { std::cout << " " << testTimeout.GetTargetName().c_str() << "\n"; } } - if (!testRunReport.GetUnexecutedTests().empty()) + if (!testRunReport.GetUnexecutedTestRuns().empty()) { std::cout << "\nUnexecuted tests:\n"; - for (const auto& unexecutedTest : testRunReport.GetUnexecutedTests()) + for (const auto& unexecutedTest : testRunReport.GetUnexecutedTestRuns()) { std::cout << " " << unexecutedTest.GetTargetName().c_str() << "\n"; } @@ -106,7 +104,7 @@ namespace TestImpact } else { - std::cout << SetColor(Foreground::White, Background::Green).c_str() << " \100% passes!\n" << ResetColor().c_str() << "\n"; + std::cout << " " << SetColor(Foreground::White, Background::Green).c_str() << "100% passes!\n" << ResetColor().c_str() << "\n"; } } } @@ -149,13 +147,17 @@ namespace TestImpact draftedTests.size()); } - void TestSequenceCompleteCallback(const Client::SequenceReport& sequenceReport) + void RegularTestSequenceCompleteCallback(const Client::RegularSequenceReport& sequenceReport) { - Output::FailureReport(sequenceReport.GetSelectedTestRunReport()); std::cout << "Updating and serializing the test impact analysis data, this may take a moment...\n"; } + void SeedTestSequenceCompleteCallback(const Client::SeedSequenceReport& sequenceReport) + { + Output::FailureReport(sequenceReport.GetSelectedTestRunReport()); + } + void ImpactAnalysisTestSequenceCompleteCallback(const Client::ImpactAnalysisSequenceReport& sequenceReport) { std::cout << "Selected test run:\n"; @@ -181,7 +183,7 @@ namespace TestImpact std::cout << "Updating and serializing the test impact analysis data, this may take a moment...\n"; } - void TestRunCompleteCallback(const Client::TestRun& testRun, size_t numTestRunsCompleted, size_t totalNumTestRuns) + void TestRunCompleteCallback(const Client::TestRunBase& testRun, size_t numTestRunsCompleted, size_t totalNumTestRuns) { const auto progress = AZStd::string::format("(%03u/%03u)", numTestRunsCompleted, totalNumTestRuns, testRun.GetTargetName().c_str()); diff --git a/Code/Tools/TestImpactFramework/Frontend/Console/Code/Source/TestImpactConsoleTestSequenceEventHandler.h b/Code/Tools/TestImpactFramework/Frontend/Console/Code/Source/TestImpactConsoleTestSequenceEventHandler.h index ff757b2d5a..8f28d60617 100644 --- a/Code/Tools/TestImpactFramework/Frontend/Console/Code/Source/TestImpactConsoleTestSequenceEventHandler.h +++ b/Code/Tools/TestImpactFramework/Frontend/Console/Code/Source/TestImpactConsoleTestSequenceEventHandler.h @@ -38,8 +38,11 @@ namespace TestImpact const Client::TestRunSelection& discardedTests, const AZStd::vector& draftedTests); - //! Handler for TestSequenceCompleteCallback event. - void TestSequenceCompleteCallback(const Client::SequenceReport& sequenceReport); + //! Handler for RegularTestSequenceCompleteCallback event. + void RegularTestSequenceCompleteCallback(const Client::RegularSequenceReport& sequenceReport); + + //! Handler for SeedTestSequenceCompleteCallback event. + void SeedTestSequenceCompleteCallback(const Client::SeedSequenceReport& sequenceReport); //! Handler for ImpactAnalysisTestSequenceCompleteCallback event. void ImpactAnalysisTestSequenceCompleteCallback(const Client::ImpactAnalysisSequenceReport& sequenceReport); @@ -48,6 +51,6 @@ namespace TestImpact void SafeImpactAnalysisTestSequenceCompleteCallback(const Client::SafeImpactAnalysisSequenceReport& sequenceReport); //! Handler for TestRunCompleteCallback event. - void TestRunCompleteCallback(const Client::TestRun& testRun, size_t numTestRunsCompleted, size_t totalNumTestRuns); + void TestRunCompleteCallback(const Client::TestRunBase& testRun, size_t numTestRunsCompleted, size_t totalNumTestRuns); } // namespace Console } // namespace TestImpact diff --git a/Code/Tools/TestImpactFramework/Frontend/Console/Code/Source/TestImpactRuntimeConfigurationFactory.cpp b/Code/Tools/TestImpactFramework/Frontend/Console/Code/Source/TestImpactRuntimeConfigurationFactory.cpp index 0d54cf417d..8ea32d6447 100644 --- a/Code/Tools/TestImpactFramework/Frontend/Console/Code/Source/TestImpactRuntimeConfigurationFactory.cpp +++ b/Code/Tools/TestImpactFramework/Frontend/Console/Code/Source/TestImpactRuntimeConfigurationFactory.cpp @@ -7,6 +7,7 @@ */ #include +#include #include @@ -140,17 +141,17 @@ namespace TestImpact return tempWorkspaceConfig; } - AZStd::array ParseTestImpactAnalysisDataFiles(const RepoPath& root, const rapidjson::Value& sparTIAFile) + AZStd::array ParseTestImpactAnalysisDataFiles(const RepoPath& root, const rapidjson::Value& sparTiaFile) { - AZStd::array sparTIAFiles; - sparTIAFiles[static_cast(SuiteType::Main)] = - GetAbsPathFromRelPath(root, sparTIAFile[GetSuiteTypeName(SuiteType::Main).c_str()].GetString()); - sparTIAFiles[static_cast(SuiteType::Periodic)] = - GetAbsPathFromRelPath(root, sparTIAFile[GetSuiteTypeName(SuiteType::Periodic).c_str()].GetString()); - sparTIAFiles[static_cast(SuiteType::Sandbox)] = - GetAbsPathFromRelPath(root, sparTIAFile[GetSuiteTypeName(SuiteType::Sandbox).c_str()].GetString()); + AZStd::array sparTiaFiles; + sparTiaFiles[static_cast(SuiteType::Main)] = + GetAbsPathFromRelPath(root, sparTiaFile[SuiteTypeAsString(SuiteType::Main).c_str()].GetString()); + sparTiaFiles[static_cast(SuiteType::Periodic)] = + GetAbsPathFromRelPath(root, sparTiaFile[SuiteTypeAsString(SuiteType::Periodic).c_str()].GetString()); + sparTiaFiles[static_cast(SuiteType::Sandbox)] = + GetAbsPathFromRelPath(root, sparTiaFile[SuiteTypeAsString(SuiteType::Sandbox).c_str()].GetString()); - return sparTIAFiles; + return sparTiaFiles; } WorkspaceConfig::Active ParseActiveWorkspaceConfig(const rapidjson::Value& activeWorkspace) @@ -160,7 +161,7 @@ namespace TestImpact activeWorkspaceConfig.m_root = activeWorkspace[Config::Keys[Config::Root]].GetString(); activeWorkspaceConfig.m_enumerationCacheDirectory = GetAbsPathFromRelPath(activeWorkspaceConfig.m_root, relativePaths[Config::Keys[Config::EnumerationCacheDir]].GetString()); - activeWorkspaceConfig.m_sparTIAFiles = + activeWorkspaceConfig.m_sparTiaFiles = ParseTestImpactAnalysisDataFiles(activeWorkspaceConfig.m_root, relativePaths[Config::Keys[Config::TestImpactDataFiles]]); return activeWorkspaceConfig; } diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Include/TestImpactFramework/TestImpactClientSequenceReport.h b/Code/Tools/TestImpactFramework/Runtime/Code/Include/TestImpactFramework/TestImpactClientSequenceReport.h index 73f3827fae..e86123ddc7 100644 --- a/Code/Tools/TestImpactFramework/Runtime/Code/Include/TestImpactFramework/TestImpactClientSequenceReport.h +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Include/TestImpactFramework/TestImpactClientSequenceReport.h @@ -1,6 +1,7 @@ /* - * 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. - * + * 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 * */ @@ -11,10 +12,25 @@ #include #include +#include +#include + namespace TestImpact { namespace Client { + //! The report types generated by each sequence. + enum class SequenceReportType : AZ::u8 + { + RegularSequence, + SeedSequence, + ImpactAnalysisSequence, + SafeImpactAnalysisSequence + }; + + //! Calculates the final sequence result for a composite of multiple sequences. + TestSequenceResult CalculateMultiTestSequenceResult(const AZStd::vector& results); + //! Report detailing the result and duration of a given set of test runs along with the details of each individual test run. class TestRunReport { @@ -23,20 +39,20 @@ namespace TestImpact //! @param result The result of this set of test runs. //! @param startTime The time point his set of test runs started. //! @param duration The duration this set of test runs took to complete. - //! @param passingTests The set of test runs that executed successfully with no failing tests. - //! @param failing tests The set of test runs that executed successfully but had one or more failing tests. - //! @param executionFailureTests The set of test runs that failed to execute. - //! @param timedOutTests The set of test runs that executed successfully but were terminated prematurely due to timing out. - //! @param unexecutedTests The set of test runs that were queued up for execution but did not get the opportunity to execute. + //! @param passingTestRuns The set of test runs that executed successfully with no failing test runs. + //! @param failingTestRuns The set of test runs that executed successfully but had one or more failing tests. + //! @param executionFailureTestRuns The set of test runs that failed to execute. + //! @param timedOutTestRuns The set of test runs that executed successfully but were terminated prematurely due to timing out. + //! @param unexecutedTestRuns The set of test runs that were queued up for execution but did not get the opportunity to execute. TestRunReport( TestSequenceResult result, AZStd::chrono::high_resolution_clock::time_point startTime, AZStd::chrono::milliseconds duration, - AZStd::vector&& passingTests, - AZStd::vector&& failingTests, - AZStd::vector&& executionFailureTests, - AZStd::vector&& timedOutTests, - AZStd::vector&& unexecutedTests); + AZStd::vector&& passingTestRuns, + AZStd::vector&& failingTestRuns, + AZStd::vector&& executionFailureTestRuns, + AZStd::vector&& timedOutTestRuns, + AZStd::vector&& unexecutedTestRuns); //! Returns the result of this sequence of test runs. TestSequenceResult GetResult() const; @@ -50,190 +66,478 @@ namespace TestImpact //! Returns the duration this sequence of test runs took to complete. AZStd::chrono::milliseconds GetDuration() const; + //! Returns the total number of test runs. + size_t GetTotalNumTestRuns() const; + //! Returns the number of passing test runs. - size_t GetNumPassingTests() const; + size_t GetNumPassingTestRuns() const; //! Returns the number of failing test runs. - size_t GetNumFailingTests() const; + size_t GetNumFailingTestRuns() const; + + //! Returns the number of test runs that failed to execute. + size_t GetNumExecutionFailureTestRuns() const; //! Returns the number of timed out test runs. - size_t GetNumTimedOutTests() const; + size_t GetNumTimedOutTestRuns() const; //! Returns the number of unexecuted test runs. - size_t GetNumUnexecutedTests() const; + size_t GetNumUnexecutedTestRuns() const; + + //! Returns the total number of passing tests across all test runs in the report. + size_t GetTotalNumPassingTests() const; + + //! Returns the total number of failing tests across all test runs in the report. + size_t GetTotalNumFailingTests() const; + + //! Returns the total number of disabled tests across all test runs in the report. + size_t GetTotalNumDisabledTests() const; //! Returns the set of test runs that executed successfully with no failing tests. - const AZStd::vector& GetPassingTests() const; + const AZStd::vector& GetPassingTestRuns() const; //! Returns the set of test runs that executed successfully but had one or more failing tests. - const AZStd::vector& GetFailingTests() const; + const AZStd::vector& GetFailingTestRuns() const; //! Returns the set of test runs that failed to execute. - const AZStd::vector& GetExecutionFailureTests() const; + const AZStd::vector& GetExecutionFailureTestRuns() const; //! Returns the set of test runs that executed successfully but were terminated prematurely due to timing out. - const AZStd::vector& GetTimedOutTests() const; + const AZStd::vector& GetTimedOutTestRuns() const; //! Returns the set of test runs that were queued up for execution but did not get the opportunity to execute. - const AZStd::vector& GetUnexecutedTests() const; + const AZStd::vector& GetUnexecutedTestRuns() const; private: - TestSequenceResult m_result; + TestSequenceResult m_result = TestSequenceResult::Success; AZStd::chrono::high_resolution_clock::time_point m_startTime; - AZStd::chrono::milliseconds m_duration; - AZStd::vector m_passingTests; - AZStd::vector m_failingTests; - AZStd::vector m_executionFailureTests; - AZStd::vector m_timedOutTests; - AZStd::vector m_unexecutedTests; + AZStd::chrono::milliseconds m_duration = AZStd::chrono::milliseconds{ 0 }; + AZStd::vector m_passingTestRuns; + AZStd::vector m_failingTestRuns; + AZStd::vector m_executionFailureTestRuns; + AZStd::vector m_timedOutTestRuns; + AZStd::vector m_unexecutedTestRuns; + size_t m_totalNumPassingTests = 0; + size_t m_totalNumFailingTests = 0; + size_t m_totalNumDisabledTests = 0; }; - //! Report detailing a test run sequence of selected tests. - class SequenceReport + //! Base class for all sequence report types. + template + class SequenceReportBase { public: //! Constructs the report for a sequence of selected tests. + //! @param type The type of sequence this report is generated for. + //! @param maxConcurrency The maximum number of concurrent test targets in flight at any given time. + //! @param testTargetTimeout The maximum duration individual test targets may be in flight for (infinite if empty). + //! @param globalTimeout The maximum duration the entire test sequence may run for (infinite if empty). + //! @param policyState The policy state this sequence was executed under. //! @param suiteType The suite from which the tests have been selected from. - //! @param selectedTests The target names of the selected tests. + //! @param selectedTestRuns The target names of the selected test runs. //! @param selectedTestRunReport The report for the set of selected test runs. - SequenceReport(SuiteType suiteType, const TestRunSelection& selectedTests, TestRunReport&& selectedTestRunReport); + SequenceReportBase( + SequenceReportType type, + size_t maxConcurrency, + const AZStd::optional& testTargetTimeout, + const AZStd::optional& globalTimeout, + const PolicyStateType& policyState, + SuiteType suiteType, + const TestRunSelection& selectedTestRuns, + TestRunReport&& selectedTestRunReport) + : m_type(type) + , m_maxConcurrency(maxConcurrency) + , m_testTargetTimeout(testTargetTimeout) + , m_globalTimeout(globalTimeout) + , m_policyState(policyState) + , m_suite(suiteType) + , m_selectedTestRuns(selectedTestRuns) + , m_selectedTestRunReport(AZStd::move(selectedTestRunReport)) + { + } + + virtual ~SequenceReportBase() = default; + + //! Returns the identifying type for this sequence report. + SequenceReportType GetType() const + { + return m_type; + } + + //! Returns the maximum concurrency for this sequence. + size_t GetMaxConcurrency() const + { + return m_maxConcurrency; + } + + //! Returns the global timeout for this sequence. + const AZStd::optional& GetGlobalTimeout() const + { + return m_globalTimeout; + } + + //! Returns the test target timeout for this sequence. + const AZStd::optional& GetTestTargetTimeout() const + { + return m_testTargetTimeout; + } + + //! Returns the policy state for this sequence. + const PolicyStateType& GetPolicyState() const + { + return m_policyState; + } + + //! Returns the suite for this sequence. + SuiteType GetSuite() const + { + return m_suite; + } + + //! Returns the result of the sequence. + virtual TestSequenceResult GetResult() const + { + return m_selectedTestRunReport.GetResult(); + } //! Returns the tests selected for running in the sequence. - TestRunSelection GetSelectedTests() const; + TestRunSelection GetSelectedTestRuns() const + { + return m_selectedTestRuns; + } //! Returns the report for the selected test runs. - TestRunReport GetSelectedTestRunReport() const; + TestRunReport GetSelectedTestRunReport() const + { + return m_selectedTestRunReport; + } //! Returns the start time of the sequence. - AZStd::chrono::high_resolution_clock::time_point GetStartTime() const; + AZStd::chrono::high_resolution_clock::time_point GetStartTime() const + { + return m_selectedTestRunReport.GetStartTime(); + } //! Returns the end time of the sequence. - AZStd::chrono::high_resolution_clock::time_point GetEndTime() const; - - //! Returns the result of the sequence. - virtual TestSequenceResult GetResult() const; + AZStd::chrono::high_resolution_clock::time_point GetEndTime() const + { + return GetStartTime() + GetDuration(); + } //! Returns the entire duration the sequence took from start to finish. - virtual AZStd::chrono::milliseconds GetDuration() const; + virtual AZStd::chrono::milliseconds GetDuration() const + { + return m_selectedTestRunReport.GetDuration(); + } - //! Get the total number of tests in the sequence that passed. - virtual size_t GetTotalNumPassingTests() const; + //! Returns the total number of test runs across all test run reports. + virtual size_t GetTotalNumTestRuns() const + { + return m_selectedTestRunReport.GetTotalNumTestRuns(); + } - //! Get the total number of tests in the sequence that contain one or more test failures. - virtual size_t GetTotalNumFailingTests() const; + //! Returns the total number of passing tests across all test targets in all test run reports. + virtual size_t GetTotalNumPassingTests() const + { + return m_selectedTestRunReport.GetTotalNumPassingTests(); + } - //! Get the total number of tests in the sequence that timed out whilst in flight. - virtual size_t GetTotalNumTimedOutTests() const; + //! Returns the total number of failing tests across all test targets in all test run reports. + virtual size_t GetTotalNumFailingTests() const + { + return m_selectedTestRunReport.GetTotalNumFailingTests(); + } - //! Get the total number of tests in the sequence that were queued for execution but did not get the oppurtunity to execute. - virtual size_t GetTotalNumUnexecutedTests() const; + //! Returns the total number of unexecuted tests across all test targets in all test run reports. + virtual size_t GetTotalNumDisabledTests() const + { + return m_selectedTestRunReport.GetTotalNumDisabledTests(); + } + + //! Get the total number of test runs in the sequence that passed. + virtual size_t GetTotalNumPassingTestRuns() const + { + return m_selectedTestRunReport.GetNumPassingTestRuns(); + } + + //! Get the total number of test runs in the sequence that contain one or more test failures. + virtual size_t GetTotalNumFailingTestRuns() const + { + return m_selectedTestRunReport.GetNumFailingTestRuns(); + } + + //! Returns the total number of test runs that failed to execute. + virtual size_t GetTotalNumExecutionFailureTestRuns() const + { + return m_selectedTestRunReport.GetNumExecutionFailureTestRuns(); + } + + //! Get the total number of test runs in the sequence that timed out whilst in flight. + virtual size_t GetTotalNumTimedOutTestRuns() const + { + return m_selectedTestRunReport.GetNumTimedOutTestRuns(); + } + + //! Get the total number of test runs in the sequence that were queued for execution but did not get the opportunity to execute. + virtual size_t GetTotalNumUnexecutedTestRuns() const + { + return m_selectedTestRunReport.GetNumUnexecutedTestRuns(); + } private: - SuiteType m_suite; - TestRunSelection m_selectedTests; + SequenceReportType m_type; + size_t m_maxConcurrency = 0; + AZStd::optional m_testTargetTimeout; + AZStd::optional m_globalTimeout; + PolicyStateType m_policyState; + SuiteType m_suite = SuiteType::Main; + TestRunSelection m_selectedTestRuns; TestRunReport m_selectedTestRunReport; }; - //! Report detailing a test run sequence of selected and drafted tests. - class DraftingSequenceReport - : public SequenceReport + //! Report type for regular test sequences. + class RegularSequenceReport + : public SequenceReportBase { public: - //! Constructs the report for a sequence of selected and drafted tests. + //! Constructs the report for a regular sequence. + //! @param maxConcurrency The maximum number of concurrent test targets in flight at any given time. + //! @param testTargetTimeout The maximum duration individual test targets may be in flight for (infinite if empty). + //! @param globalTimeout The maximum duration the entire test sequence may run for (infinite if empty). + //! @param policyState The policy state this sequence was executed under. //! @param suiteType The suite from which the tests have been selected from. - //! @param selectedTests The target names of the selected tests. - //! @param draftedTests The target names of the drafted tests. + //! @param selectedTestRuns The target names of the selected test runs. + //! @param selectedTestRunReport The report for the set of selected test runs. + RegularSequenceReport( + size_t maxConcurrency, + const AZStd::optional& testTargetTimeout, + const AZStd::optional& globalTimeout, + const SequencePolicyState& policyState, + SuiteType suiteType, + const TestRunSelection& selectedTestRuns, + TestRunReport&& selectedTestRunReport); + }; + + //! Report type for seed test sequences. + class SeedSequenceReport + : public SequenceReportBase + { + public: + //! Constructs the report for a seed sequence. + //! @param maxConcurrency The maximum number of concurrent test targets in flight at any given time. + //! @param testTargetTimeout The maximum duration individual test targets may be in flight for (infinite if empty). + //! @param globalTimeout The maximum duration the entire test sequence may run for (infinite if empty). + //! @param policyState The policy state this sequence was executed under. + //! @param suiteType The suite from which the tests have been selected from. + //! @param selectedTestRuns The target names of the selected test runs. + //! @param selectedTestRunReport The report for the set of selected test runs. + SeedSequenceReport( + size_t maxConcurrency, + const AZStd::optional& testTargetTimeout, + const AZStd::optional& globalTimeout, + const SequencePolicyState& policyState, + SuiteType suiteType, + const TestRunSelection& selectedTestRuns, + TestRunReport&& selectedTestRunReport); + }; + + //! Report detailing a test run sequence of selected and drafted tests. + template + class DraftingSequenceReportBase + : public SequenceReportBase + { + public: + //! Constructs the report for sequences that draft in previously failed/newly added test targets. + //! @param type The type of sequence this report is generated for. + //! @param maxConcurrency The maximum number of concurrent test targets in flight at any given time. + //! @param testTargetTimeout The maximum duration individual test targets may be in flight for (infinite if empty). + //! @param globalTimeout The maximum duration the entire test sequence may run for (infinite if empty). + //! @param policyState The policy state this sequence was executed under. + //! @param suiteType The suite from which the tests have been selected from. + //! @param selectedTestRuns The target names of the selected test runs. + //! @param draftedTestRuns The target names of the drafted test runs. //! @param selectedTestRunReport The report for the set of selected test runs. //! @param draftedTestRunReport The report for the set of drafted test runs. - DraftingSequenceReport( + DraftingSequenceReportBase( + SequenceReportType type, + size_t maxConcurrency, + const AZStd::optional& testTargetTimeout, + const AZStd::optional& globalTimeout, + const PolicyStateType& policyState, SuiteType suiteType, - const TestRunSelection& selectedTests, - const AZStd::vector& draftedTests, + const TestRunSelection& selectedTestRuns, + const AZStd::vector& draftedTestRuns, TestRunReport&& selectedTestRunReport, - TestRunReport&& draftedTestRunReport); + TestRunReport&& draftedTestRunReport) + : SequenceReportBase( + type, + maxConcurrency, + testTargetTimeout, + globalTimeout, + policyState, + suiteType, + selectedTestRuns, + AZStd::move(selectedTestRunReport)) + , m_draftedTestRuns(draftedTestRuns) + , m_draftedTestRunReport(AZStd::move(draftedTestRunReport)) + { + } - // SequenceReport overrides ... - TestSequenceResult GetResult() const override; - AZStd::chrono::milliseconds GetDuration() const override; - size_t GetTotalNumPassingTests() const override; - size_t GetTotalNumFailingTests() const override; - size_t GetTotalNumTimedOutTests() const override; - size_t GetTotalNumUnexecutedTests() const override; - - //! Returns the tests drafted for running in the sequence. - const AZStd::vector& GetDraftedTests() const; + //! Returns the tests drafted for running in the sequence. + const AZStd::vector& GetDraftedTestRuns() const + { + return m_draftedTestRuns; + } //! Returns the report for the drafted test runs. - TestRunReport GetDraftedTestRunReport() const; + TestRunReport GetDraftedTestRunReport() const + { + return m_draftedTestRunReport; + } + // SequenceReport overrides ... + AZStd::chrono::milliseconds GetDuration() const override + { + return SequenceReportBase::GetDuration() + m_draftedTestRunReport.GetDuration(); + } + + TestSequenceResult GetResult() const override + { + return CalculateMultiTestSequenceResult({ SequenceReportBase::GetResult(), m_draftedTestRunReport.GetResult() }); + } + + size_t GetTotalNumTestRuns() const override + { + return SequenceReportBase::GetTotalNumTestRuns() + m_draftedTestRunReport.GetTotalNumTestRuns(); + } + + size_t GetTotalNumPassingTests() const override + { + return SequenceReportBase::GetTotalNumPassingTests() + m_draftedTestRunReport.GetTotalNumPassingTests(); + } + + size_t GetTotalNumFailingTests() const override + { + return SequenceReportBase::GetTotalNumFailingTests() + m_draftedTestRunReport.GetTotalNumFailingTests(); + } + + size_t GetTotalNumDisabledTests() const override + { + return SequenceReportBase::GetTotalNumDisabledTests() + m_draftedTestRunReport.GetTotalNumDisabledTests(); + } + + size_t GetTotalNumPassingTestRuns() const override + { + return SequenceReportBase::GetTotalNumPassingTestRuns() + m_draftedTestRunReport.GetNumPassingTestRuns(); + } + + size_t GetTotalNumFailingTestRuns() const override + { + return SequenceReportBase::GetTotalNumFailingTestRuns() + m_draftedTestRunReport.GetNumFailingTestRuns(); + } + + size_t GetTotalNumExecutionFailureTestRuns() const override + { + return SequenceReportBase::GetTotalNumExecutionFailureTestRuns() + m_draftedTestRunReport.GetNumExecutionFailureTestRuns(); + } + + size_t GetTotalNumTimedOutTestRuns() const override + { + return SequenceReportBase::GetTotalNumTimedOutTestRuns() + m_draftedTestRunReport.GetNumTimedOutTestRuns(); + } + + size_t GetTotalNumUnexecutedTestRuns() const override + { + return SequenceReportBase::GetTotalNumUnexecutedTestRuns() + m_draftedTestRunReport.GetNumUnexecutedTestRuns(); + } private: - AZStd::vector m_draftedTests; + AZStd::vector m_draftedTestRuns; TestRunReport m_draftedTestRunReport; }; //! Report detailing an impact analysis sequence of selected, discarded and drafted tests. class ImpactAnalysisSequenceReport - : public DraftingSequenceReport + : public DraftingSequenceReportBase { public: - //! Constructs the report for a sequence of selected and drafted tests. + //! Constructs the report for an impact analysis sequence. + //! @param maxConcurrency The maximum number of concurrent test targets in flight at any given time. + //! @param testTargetTimeout The maximum duration individual test targets may be in flight for (infinite if empty). + //! @param globalTimeout The maximum duration the entire test sequence may run for (infinite if empty). + //! @param policyState The policy state this sequence was executed under. //! @param suiteType The suite from which the tests have been selected from. - //! @param selectedTests The target names of the selected tests. - //! @param discardedTests The target names of the discarded tests. - //! @param draftedTests The target names of the drafted tests. + //! @param selectedTestRuns The target names of the selected test runs. + //! @param draftedTestRuns The target names of the drafted test runs. //! @param selectedTestRunReport The report for the set of selected test runs. //! @param draftedTestRunReport The report for the set of drafted test runs. ImpactAnalysisSequenceReport( + size_t maxConcurrency, + const AZStd::optional& testTargetTimeout, + const AZStd::optional& globalTimeout, + const ImpactAnalysisSequencePolicyState& policyState, SuiteType suiteType, - const TestRunSelection& selectedTests, - const AZStd::vector& discardedTests, - const AZStd::vector& draftedTests, + const TestRunSelection& selectedTestRuns, + const AZStd::vector& discardedTestRuns, + const AZStd::vector& draftedTestRuns, TestRunReport&& selectedTestRunReport, TestRunReport&& draftedTestRunReport); - //! Returns the tests discarded from running in the sequence. - const AZStd::vector& GetDiscardedTests() const; + //! Returns the test runs discarded from running in the sequence. + const AZStd::vector& GetDiscardedTestRuns() const; private: - AZStd::vector m_discardedTests; + AZStd::vector m_discardedTestRuns; }; - //! Report detailing an impact analysis sequence of selected, discarded and drafted tests. + //! Report detailing an impact analysis sequence of selected, discarded and drafted test runs. class SafeImpactAnalysisSequenceReport - : public DraftingSequenceReport + : public DraftingSequenceReportBase { public: - //! Constructs the report for a sequence of selected and drafted tests. + //! Constructs the report for a sequence of selected, discarded and drafted test runs. + //! @param maxConcurrency The maximum number of concurrent test targets in flight at any given time. + //! @param testTargetTimeout The maximum duration individual test targets may be in flight for (infinite if empty). + //! @param globalTimeout The maximum duration the entire test sequence may run for (infinite if empty). + //! @param policyState The policy state this sequence was executed under. //! @param suiteType The suite from which the tests have been selected from. - //! @param selectedTests The target names of the selected tests. - //! @param discardedTests The target names of the discarded tests. - //! @param draftedTests The target names of the drafted tests. + //! @param selectedTestRuns The target names of the selected test runs. + //! @param discardedTestRuns The target names of the discarded test runs. + //! @param draftedTestRuns The target names of the drafted test runs. //! @param selectedTestRunReport The report for the set of selected test runs. //! @param discardedTestRunReport The report for the set of discarded test runs. //! @param draftedTestRunReport The report for the set of drafted test runs. SafeImpactAnalysisSequenceReport( + size_t maxConcurrency, + const AZStd::optional& testTargetTimeout, + const AZStd::optional& globalTimeout, + const SafeImpactAnalysisSequencePolicyState& policyState, SuiteType suiteType, - const TestRunSelection& selectedTests, - const TestRunSelection& discardedTests, - const AZStd::vector& draftedTests, + const TestRunSelection& selectedTestRuns, + const TestRunSelection& discardedTestRuns, + const AZStd::vector& draftedTestRuns, TestRunReport&& selectedTestRunReport, TestRunReport&& discardedTestRunReport, TestRunReport&& draftedTestRunReport); - // DraftingSequenceReport overrides ... - TestSequenceResult GetResult() const override; + // SequenceReport overrides ... AZStd::chrono::milliseconds GetDuration() const override; + TestSequenceResult GetResult() const override; + size_t GetTotalNumTestRuns() const override; size_t GetTotalNumPassingTests() const override; size_t GetTotalNumFailingTests() const override; - size_t GetTotalNumTimedOutTests() const override; - size_t GetTotalNumUnexecutedTests() const override; + size_t GetTotalNumDisabledTests() const override; + size_t GetTotalNumPassingTestRuns() const override; + size_t GetTotalNumFailingTestRuns() const override; + size_t GetTotalNumExecutionFailureTestRuns() const override; + size_t GetTotalNumTimedOutTestRuns() const override; + size_t GetTotalNumUnexecutedTestRuns() const override; //! Returns the report for the discarded test runs. - const TestRunSelection GetDiscardedTests() const; + const TestRunSelection GetDiscardedTestRuns() const; //! Returns the report for the discarded test runs. TestRunReport GetDiscardedTestRunReport() const; private: - TestRunSelection m_discardedTests; + TestRunSelection m_discardedTestRuns; TestRunReport m_discardedTestRunReport; }; } // namespace Client diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Include/TestImpactFramework/TestImpactClientSequenceReportSerializer.h b/Code/Tools/TestImpactFramework/Runtime/Code/Include/TestImpactFramework/TestImpactClientSequenceReportSerializer.h new file mode 100644 index 0000000000..fc226588e7 --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Include/TestImpactFramework/TestImpactClientSequenceReportSerializer.h @@ -0,0 +1,28 @@ +/* + * 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 + * + */ + +#pragma once + +#include + +#include + +namespace TestImpact +{ + //! Serializes a regular sequence report to JSON format. + AZStd::string SerializeSequenceReport(const Client::RegularSequenceReport& sequenceReport); + + //! Serializes a seed sequence report to JSON format. + AZStd::string SerializeSequenceReport(const Client::SeedSequenceReport& sequenceReport); + + //! Serializes an impact analysis sequence report to JSON format. + AZStd::string SerializeSequenceReport(const Client::ImpactAnalysisSequenceReport& sequenceReport); + + //! Serializes a safe impact analysis sequence report to JSON format. + AZStd::string SerializeSequenceReport(const Client::SafeImpactAnalysisSequenceReport& sequenceReport); +} // namespace TestImpact diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Include/TestImpactFramework/TestImpactClientTestRun.h b/Code/Tools/TestImpactFramework/Runtime/Code/Include/TestImpactFramework/TestImpactClientTestRun.h index 4b7715bf1d..f22963b5e6 100644 --- a/Code/Tools/TestImpactFramework/Runtime/Code/Include/TestImpactFramework/TestImpactClientTestRun.h +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Include/TestImpactFramework/TestImpactClientTestRun.h @@ -26,8 +26,8 @@ namespace TestImpact AllTestsPass //!< The test run completed its run and all tests passed. }; - //! Representation of a completed test run. - class TestRun + //! Representation of a test run. + class TestRunBase { public: //! Constructs the client facing representation of a given test target's run. @@ -36,13 +36,15 @@ namespace TestImpact //! @param startTime The start time, relative to the sequence start, that this run started. //! @param duration The duration that this test run took to complete. //! @param result The result of the run. - TestRun( + TestRunBase( const AZStd::string& name, const AZStd::string& commandString, AZStd::chrono::high_resolution_clock::time_point startTime, AZStd::chrono::milliseconds duration, TestRunResult result); + virtual ~TestRunBase() = default; + //! Returns the test target name. const AZStd::string& GetTargetName() const; @@ -69,75 +71,120 @@ namespace TestImpact AZStd::chrono::milliseconds m_duration; }; - //! Represents an individual test of a test target that failed. - class TestFailure + //! Representation of a test run that failed to execute. + class TestRunWithExecutionFailure + : public TestRunBase { public: - TestFailure(const AZStd::string& testName, const AZStd::string& errorMessage); + using TestRunBase::TestRunBase; + TestRunWithExecutionFailure(TestRunBase&& testRun); + }; - //! Returns the name of the test that failed. + //! Representation of a test run that was terminated in-flight due to timing out. + class TimedOutTestRun + : public TestRunBase + { + public: + using TestRunBase::TestRunBase; + TimedOutTestRun(TestRunBase&& testRun); + }; + + //! Representation of a test run that was not executed. + class UnexecutedTestRun + : public TestRunBase + { + public: + using TestRunBase::TestRunBase; + UnexecutedTestRun(TestRunBase&& testRun); + }; + + // Result of a test executed during a test run. + enum class TestResult : AZ::u8 + { + Passed, + Failed, + NotRun + }; + + //! Representation of a single test in a test target. + class Test + { + public: + //! Constructs the test with the specified name and result. + Test(const AZStd::string& testName, TestResult result); + + //! Returns the name of this test. const AZStd::string& GetName() const; - //! Returns the error message of the test that failed. - const AZStd::string& GetErrorMessage() const; + //! Returns the result of executing this test. + TestResult GetResult() const; private: AZStd::string m_name; - AZStd::string m_errorMessage; + TestResult m_result; }; - //! Represents a collection of tests that failed. - //! @note Only the failing tests are included in the collection. - class TestCaseFailure + //! Representation of a test run that completed with or without test failures. + class CompletedTestRun + : public TestRunBase { public: - TestCaseFailure(const AZStd::string& testCaseName, AZStd::vector&& testFailures); - - //! Returns the name of the test case containing the failing tests. - const AZStd::string& GetName() const; - - //! Returns the collection of tests in this test case that failed. - const AZStd::vector& GetTestFailures() const; - - private: - AZStd::string m_name; - AZStd::vector m_testFailures; - }; - - //! Representation of a test run's failing tests. - class TestRunWithTestFailures - : public TestRun - { - public: - //! Constructs the client facing representation of a given test target's run. - //! @param name The name of the test target. - //! @param commandString The command string used to execute this test target. - //! @param startTime The start time, relative to the sequence start, that this run started. + //! Constructs the test run from the specified test target executaion data. + //! @param name The name of the test target for this run. + //! @param commandString The command string used to execute the test target for this run. + //! @param startTime The start time, offset from the sequence start time, that this test run started. //! @param duration The duration that this test run took to complete. - //! @param result The result of the run. - //! @param testFailures The failing tests for this test run. - TestRunWithTestFailures( + //! @param result The result of this test run. + //! @param tests The tests contained in the test target for this test run. + CompletedTestRun( const AZStd::string& name, const AZStd::string& commandString, AZStd::chrono::high_resolution_clock::time_point startTime, AZStd::chrono::milliseconds duration, TestRunResult result, - AZStd::vector&& testFailures); + AZStd::vector&& tests); - //! Constructs the client facing representation of a given test target's run. - //! @param testRun The test run this run is to be derived from. - //! @param testFailures The failing tests for this run. - TestRunWithTestFailures(TestRun&& testRun, AZStd::vector&& testFailures); + //! Constructs the test run from the specified test target executaion data. + CompletedTestRun(TestRunBase&& testRun, AZStd::vector&& tests); - //! Returns the total number of failing tests in this run. - size_t GetNumTestFailures() const; + //! Returns the total number of tests in the run. + size_t GetTotalNumTests() const; - //! Returns the test cases in this run containing failing tests. - const AZStd::vector& GetTestCaseFailures() const; + //! Returns the total number of passing tests in the run. + size_t GetTotalNumPassingTests() const; + + //! Returns the total number of failing tests in the run. + size_t GetTotalNumFailingTests() const; + + //! Returns the total number of disabled tests in the run. + size_t GetTotalNumDisabledTests() const; + + //! Returns the tests in the run. + const AZStd::vector& GetTests() const; private: - AZStd::vector m_testCaseFailures; - size_t m_numTestFailures = 0; + AZStd::vector m_tests; + size_t m_totalNumPassingTests = 0; + size_t m_totalNumFailingTests = 0; + size_t m_totalNumDisabledTests = 0; + }; + + //! Representation of a test run that completed with no test failures. + class PassingTestRun + : public CompletedTestRun + { + public: + using CompletedTestRun::CompletedTestRun; + PassingTestRun(TestRunBase&& testRun, AZStd::vector&& tests); + }; + + //! Representation of a test run that completed with one or more test failures. + class FailingTestRun + : public CompletedTestRun + { + public: + using CompletedTestRun::CompletedTestRun; + FailingTestRun(TestRunBase&& testRun, AZStd::vector&& tests); }; } // namespace Client } // namespace TestImpact diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Include/TestImpactFramework/TestImpactConfiguration.h b/Code/Tools/TestImpactFramework/Runtime/Code/Include/TestImpactFramework/TestImpactConfiguration.h index c10c27c643..fcacd90e71 100644 --- a/Code/Tools/TestImpactFramework/Runtime/Code/Include/TestImpactFramework/TestImpactConfiguration.h +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Include/TestImpactFramework/TestImpactConfiguration.h @@ -44,7 +44,7 @@ namespace TestImpact { RepoPath m_root; //!< Path to the persistent workspace tracked by the repository. RepoPath m_enumerationCacheDirectory; //!< Path to the test enumerations cache. - AZStd::array m_sparTIAFiles; //!< Paths to the test impact analysis data files for each test suite. + AZStd::array m_sparTiaFiles; //!< Paths to the test impact analysis data files for each test suite. }; Temp m_temp; diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Include/TestImpactFramework/TestImpactPolicy.h b/Code/Tools/TestImpactFramework/Runtime/Code/Include/TestImpactFramework/TestImpactPolicy.h new file mode 100644 index 0000000000..b400af018a --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Include/TestImpactFramework/TestImpactPolicy.h @@ -0,0 +1,81 @@ +/* + * 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 + * + */ + +#pragma once + +#include + +namespace TestImpact +{ + namespace Policy + { + //! Policy for handling of test targets that fail to execute (e.g. due to the binary not being found). + //! @note Test targets that fail to execute will be tagged such that their execution can be attempted at a later date. This is + //! important as otherwise it would be erroneously assumed that they cover no sources due to having no entries in the dynamic + //! dependency map. + enum class ExecutionFailure : AZ::u8 + { + Abort, //!< Abort the test sequence and report a failure. + Continue, //!< Continue the test sequence but treat the execution failures as test failures after the run. + Ignore //!< Continue the test sequence and ignore the execution failures. + }; + + //! Policy for handling the coverage data of failed tests targets (both tests that failed to execute and tests that ran but failed). + enum class FailedTestCoverage : AZ::u8 + { + Discard, //!< Discard the coverage data produced by the failing tests, causing them to be drafted into future test runs. + Keep //!< Keep any existing coverage data and update the coverage data for failed test targets that produce coverage. + }; + + //! Policy for prioritizing selected tests. + enum class TestPrioritization : AZ::u8 + { + None, //!< Do not attempt any test prioritization. + DependencyLocality //!< Prioritize test targets according to the locality of the production targets they cover in the build + //!< dependency graph. + }; + + //! Policy for handling test targets that report failing tests. + enum class TestFailure : AZ::u8 + { + Abort, //!< Abort the test sequence and report the test failure. + Continue //!< Continue the test sequence and report the test failures after the run. + }; + + //! Policy for handling integrity failures of the dynamic dependency map and the source to target mappings. + enum class IntegrityFailure : AZ::u8 + { + Abort, //!< Abort the test sequence and report the test failure. + Continue //!< Continue the test sequence and report the test failures after the run. + }; + + //! Policy for updating the dynamic dependency map with the coverage data of produced by test sequences. + enum class DynamicDependencyMap : AZ::u8 + { + Discard, //!< Discard the coverage data produced by test sequences. + Update //!< Update the dynamic dependency map with the coverage data produced by test sequences. + }; + + //! Policy for sharding test targets that have been marked for test sharding. + enum class TestSharding : AZ::u8 + { + Never, //!< Do not shard any test targets. + Always //!< Shard all test targets that have been marked for test sharding. + }; + + //! Standard output capture of test target runs. + enum class TargetOutputCapture : AZ::u8 + { + None, //!< Do not capture any output. + StdOut, //!< Send captured output to standard output + File, //!< Write captured output to file. + StdOutAndFile //!< Send captured output to standard output and write to file. + }; + + } // namespace Policy +} // namespace TestImpact diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Include/TestImpactFramework/TestImpactRuntime.h b/Code/Tools/TestImpactFramework/Runtime/Code/Include/TestImpactFramework/TestImpactRuntime.h index 69feb48749..ec8d88eaad 100644 --- a/Code/Tools/TestImpactFramework/Runtime/Code/Include/TestImpactFramework/TestImpactRuntime.h +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Include/TestImpactFramework/TestImpactRuntime.h @@ -78,7 +78,7 @@ namespace TestImpact //! @param testRunMeta The test that has completed. //! @param numTestRunsCompleted The number of test runs that have completed. //! @param totalNumTestRuns The total number of test runs in the sequence. - using TestRunCompleteCallback = AZStd::function; + using TestRunCompleteCallback = AZStd::function; //! The API exposed to the client responsible for all test runs and persistent data management. class Runtime @@ -86,6 +86,7 @@ namespace TestImpact public: //! Constructs a runtime with the specified configuration and policies. //! @param config The configuration used for this runtime instance. + //! @param dataFile The optional data file to be used instead of that specified in the config file. //! @param suiteFilter The test suite for which the coverage data and test selection will draw from. //! @param executionFailurePolicy Determines how to handle test targets that fail to execute. //! @param executionFailureDraftingPolicy Determines how test targets that previously failed to execute are drafted into subsequent test sequences. @@ -94,6 +95,7 @@ namespace TestImpact //! @param testShardingPolicy Determines how to handle test targets that have opted in to test sharding. Runtime( RuntimeConfig&& config, + AZStd::optional dataFile, SuiteType suiteFilter, Policy::ExecutionFailure executionFailurePolicy, Policy::FailedTestCoverage failedTestCoveragePolicy, @@ -112,11 +114,11 @@ namespace TestImpact //! @param testSequenceCompleteCallback The client function to be called after the test sequence has completed. //! @param testRunCompleteCallback The client function to be called after an individual test run has completed. //! @returns The test run and sequence report for the selected test sequence. - Client::SequenceReport RegularTestSequence( + Client::RegularSequenceReport RegularTestSequence( AZStd::optional testTargetTimeout, AZStd::optional globalTimeout, AZStd::optional testSequenceStartCallback, - AZStd::optional> testSequenceCompleteCallback, + AZStd::optional> testSequenceCompleteCallback, AZStd::optional testRunCompleteCallback); //! Runs a test sequence where tests are selected according to test impact analysis so long as they are not on the excluded list. @@ -164,11 +166,11 @@ namespace TestImpact //! @param testSequenceCompleteCallback The client function to be called after the test sequence has completed. //! @param testRunCompleteCallback The client function to be called after an individual test run has completed. //! @returns The test run and sequence report for the selected test sequence. - Client::SequenceReport SeededTestSequence( + Client::SeedSequenceReport SeededTestSequence( AZStd::optional testTargetTimeout, AZStd::optional globalTimeout, AZStd::optional testSequenceStartCallback, - AZStd::optional> testSequenceCompleteCallback, + AZStd::optional> testSequenceCompleteCallback, AZStd::optional testRunCompleteCallback); //! Returns true if the runtime has test impact analysis data (either preexisting or generated). @@ -184,7 +186,7 @@ namespace TestImpact //! @param changeList The change list for which the covering tests and enumeration cache updates will be generated for. //! @param testPrioritizationPolicy The test prioritization strategy to use for the selected test targets. //! @returns The pair of selected test targets and discarded test targets. - AZStd::pair, AZStd::vector> SelectCoveringTestTargetsAndUpdateEnumerationCache( + AZStd::pair, AZStd::vector> SelectCoveringTestTargets( const ChangeList& changeList, Policy::TestPrioritization testPrioritizationPolicy); @@ -204,8 +206,22 @@ namespace TestImpact //! Updates the dynamic dependency map and serializes the entire map to disk. void UpdateAndSerializeDynamicDependencyMap(const AZStd::vector& jobs); + //! Generates a base policy state for the current runtime policy runtime configuration. + PolicyStateBase GeneratePolicyStateBase() const; + + //! Generates a regular/seed sequence policy state for the current runtime policy runtime configuration. + SequencePolicyState GenerateSequencePolicyState() const; + + //! Generates a safe impact analysis sequence policy state for the current runtime policy runtime configuration. + SafeImpactAnalysisSequencePolicyState GenerateSafeImpactAnalysisSequencePolicyState( + Policy::TestPrioritization testPrioritizationPolicy) const; + + //! Generates an impact analysis sequence policy state for the current runtime policy runtime configuration. + ImpactAnalysisSequencePolicyState GenerateImpactAnalysisSequencePolicyState( + Policy::TestPrioritization testPrioritizationPolicy, Policy::DynamicDependencyMap dynamicDependencyMapPolicy) const; + RuntimeConfig m_config; - RepoPath m_sparTIAFile; + RepoPath m_sparTiaFile; SuiteType m_suiteFilter; Policy::ExecutionFailure m_executionFailurePolicy; Policy::FailedTestCoverage m_failedTestCoveragePolicy; diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Include/TestImpactFramework/TestImpactSequenceReportException.h b/Code/Tools/TestImpactFramework/Runtime/Code/Include/TestImpactFramework/TestImpactSequenceReportException.h new file mode 100644 index 0000000000..72c2d0c530 --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Include/TestImpactFramework/TestImpactSequenceReportException.h @@ -0,0 +1,22 @@ +/* + * 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 + * + */ + +#pragma once + +#include + +namespace TestImpact +{ + //! Exception for sequence report operations. + class SequenceReportException + : public Exception + { + public: + using Exception::Exception; + }; +} // namespace TestImpact diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Include/TestImpactFramework/TestImpactTestSequence.h b/Code/Tools/TestImpactFramework/Runtime/Code/Include/TestImpactFramework/TestImpactTestSequence.h index 8841c29644..38b716398c 100644 --- a/Code/Tools/TestImpactFramework/Runtime/Code/Include/TestImpactFramework/TestImpactTestSequence.h +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Include/TestImpactFramework/TestImpactTestSequence.h @@ -9,76 +9,12 @@ #pragma once #include +#include #include namespace TestImpact { - namespace Policy - { - //! Policy for handling of test targets that fail to execute (e.g. due to the binary not being found). - //! @note Test targets that fail to execute will be tagged such that their execution can be attempted at a later date. This is - //! important as otherwise it would be erroneously assumed that they cover no sources due to having no entries in the dynamic - //! dependency map. - enum class ExecutionFailure - { - Abort, //!< Abort the test sequence and report a failure. - Continue, //!< Continue the test sequence but treat the execution failures as test failures after the run. - Ignore //!< Continue the test sequence and ignore the execution failures. - }; - - //! Policy for handling the coverage data of failed tests targets (both test that failed to execute and tests that ran but failed). - enum class FailedTestCoverage - { - Discard, //!< Discard the coverage data produced by the failing tests, causing them to be drafted into future test runs. - Keep //!< Keep any existing coverage data and update the coverage data for failed test targetss that produce coverage. - }; - - //! Policy for prioritizing selected tests. - enum class TestPrioritization - { - None, //!< Do not attempt any test prioritization. - DependencyLocality //!< Prioritize test targets according to the locality of the production targets they cover in the build dependency graph. - }; - - //! Policy for handling test targets that report failing tests. - enum class TestFailure - { - Abort, //!< Abort the test sequence and report the test failure. - Continue //!< Continue the test sequence and report the test failures after the run. - }; - - //! Policy for handling integrity failures of the dynamic dependency map and the source to target mappings. - enum class IntegrityFailure - { - Abort, //!< Abort the test sequence and report the test failure. - Continue //!< Continue the test sequence and report the test failures after the run. - }; - - //! Policy for updating the dynamic dependency map with the coverage data of produced by test sequences. - enum class DynamicDependencyMap - { - Discard, //!< Discard the coverage data produced by test sequences. - Update //!< Update the dynamic dependency map with the coverage data produced by test sequences. - }; - - //! Policy for sharding test targets that have been marked for test sharding. - enum class TestSharding - { - Never, //!< Do not shard any test targets. - Always //!< Shard all test targets that have been marked for test sharding. - }; - - //! Standard output capture of test target runs. - enum class TargetOutputCapture - { - None, //!< Do not capture any output. - StdOut, //!< Send captured output to standard output - File, //!< Write captured output to file. - StdOutAndFile //!< Send captured output to standard output and write to file. - }; - } - //! Configuration for test targets that opt in to test sharding. enum class ShardConfiguration { @@ -97,22 +33,6 @@ namespace TestImpact Sandbox }; - //! User-friendly names for the test suite types. - inline AZStd::string GetSuiteTypeName(SuiteType suiteType) - { - switch (suiteType) - { - case SuiteType::Main: - return "main"; - case SuiteType::Periodic: - return "periodic"; - case SuiteType::Sandbox: - return "sandbox"; - default: - throw(RuntimeException("Unexpected suite type")); - } - } - //! Result of a test sequence that was run. enum class TestSequenceResult { @@ -120,4 +40,36 @@ namespace TestImpact Failure, //!< One or more tests failed and/or timed out and/or failed to launch and/or an integrity failure was encountered. Timeout //!< The global timeout for the sequence was exceeded. }; + + //! Base representation of runtime policies. + struct PolicyStateBase + { + Policy::ExecutionFailure m_executionFailurePolicy = Policy::ExecutionFailure::Continue; + Policy::FailedTestCoverage m_failedTestCoveragePolicy = Policy::FailedTestCoverage::Keep; + Policy::TestFailure m_testFailurePolicy = Policy::TestFailure::Abort; + Policy::IntegrityFailure m_integrityFailurePolicy = Policy::IntegrityFailure::Abort; + Policy::TestSharding m_testShardingPolicy = Policy::TestSharding::Never; + Policy::TargetOutputCapture m_targetOutputCapture = Policy::TargetOutputCapture::None; + }; + + //! Representation of regular and seed sequence policies. + struct SequencePolicyState + { + PolicyStateBase m_basePolicies; + }; + + //! Representation of impact analysis sequence policies. + struct ImpactAnalysisSequencePolicyState + { + PolicyStateBase m_basePolicies; + Policy::TestPrioritization m_testPrioritizationPolicy = Policy::TestPrioritization::None; + Policy::DynamicDependencyMap m_dynamicDependencyMap = Policy::DynamicDependencyMap::Update; + }; + + //! Representation of safe impact analysis sequence policies. + struct SafeImpactAnalysisSequencePolicyState + { + PolicyStateBase m_basePolicies; + Policy::TestPrioritization m_testPrioritizationPolicy = Policy::TestPrioritization::None; + }; } // namespace TestImpact diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Include/TestImpactFramework/TestImpactFileUtils.h b/Code/Tools/TestImpactFramework/Runtime/Code/Include/TestImpactFramework/TestImpactUtils.h similarity index 51% rename from Code/Tools/TestImpactFramework/Runtime/Code/Include/TestImpactFramework/TestImpactFileUtils.h rename to Code/Tools/TestImpactFramework/Runtime/Code/Include/TestImpactFramework/TestImpactUtils.h index f77008ffa7..c4625e9d2c 100644 --- a/Code/Tools/TestImpactFramework/Runtime/Code/Include/TestImpactFramework/TestImpactFileUtils.h +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Include/TestImpactFramework/TestImpactUtils.h @@ -6,12 +6,12 @@ * */ -#include -#include +#include +#include +#include #include #include -#include #pragma once @@ -59,23 +59,48 @@ namespace TestImpact //! Delete the files that match the pattern from the specified directory. //! @param path The path to the directory to pattern match the files for deletion. //! @param pattern The pattern to match files for deletion. - inline void DeleteFiles(const RepoPath& path, const AZStd::string& pattern) - { - AZ::IO::SystemFile::FindFiles(AZStd::string::format("%s/%s", path.c_str(), pattern.c_str()).c_str(), - [&path](const char* file, bool isFile) - { - if (isFile) - { - AZ::IO::SystemFile::Delete(AZStd::string::format("%s/%s", path.c_str(), file).c_str()); - } - - return true; - }); - } + //! @return The number of files that were deleted. + size_t DeleteFiles(const RepoPath& path, const AZStd::string& pattern); //! Deletes the specified file. - inline void DeleteFile(const RepoPath& file) - { - DeleteFiles(file.ParentPath(), file.Filename().Native()); - } + void DeleteFile(const RepoPath& file); + + //! User-friendly names for the test suite types. + AZStd::string SuiteTypeAsString(SuiteType suiteType); + + //! User-friendly names for the sequence report types. + AZStd::string SequenceReportTypeAsString(Client::SequenceReportType type); + + //! User-friendly names for the sequence result types. + AZStd::string TestSequenceResultAsString(TestSequenceResult result); + + //! User-friendly names for the test run result types. + AZStd::string TestRunResultAsString(Client::TestRunResult result); + + //! User-friendly names for the execution failure policy types. + AZStd::string ExecutionFailurePolicyAsString(Policy::ExecutionFailure executionFailurePolicy); + + //! User-friendly names for the failed test coverage policy types. + AZStd::string FailedTestCoveragePolicyAsString(Policy::FailedTestCoverage failedTestCoveragePolicy); + + //! User-friendly names for the test prioritization policy types. + AZStd::string TestPrioritizationPolicyAsString(Policy::TestPrioritization testPrioritizationPolicy); + + //! User-friendly names for the test failure policy types. + AZStd::string TestFailurePolicyAsString(Policy::TestFailure testFailurePolicy); + + //! User-friendly names for the integrity failure policy types. + AZStd::string IntegrityFailurePolicyAsString(Policy::IntegrityFailure integrityFailurePolicy); + + //! User-friendly names for the dynamic dependency map policy types. + AZStd::string DynamicDependencyMapPolicyAsString(Policy::DynamicDependencyMap dynamicDependencyMapPolicy); + + //! User-friendly names for the test sharding policy types. + AZStd::string TestShardingPolicyAsString(Policy::TestSharding testShardingPolicy); + + //! User-friendly names for the target output capture policy types. + AZStd::string TargetOutputCapturePolicyAsString(Policy::TargetOutputCapture targetOutputCapturePolicy); + + //! User-friendly names for the client test result types. + AZStd::string ClientTestResultAsString(Client::TestResult result); } // namespace TestImpact diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Source/Artifact/Factory/TestImpactTestTargetMetaMapFactory.cpp b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Artifact/Factory/TestImpactTestTargetMetaMapFactory.cpp index d69177814f..4c8d71bba1 100644 --- a/Code/Tools/TestImpactFramework/Runtime/Code/Source/Artifact/Factory/TestImpactTestTargetMetaMapFactory.cpp +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Artifact/Factory/TestImpactTestTargetMetaMapFactory.cpp @@ -6,6 +6,8 @@ * */ +#include + #include #include @@ -67,7 +69,7 @@ namespace TestImpact { // Check to see if this test target has the suite we're looking for if (const auto suiteName = suite[Keys[SuiteKey]].GetString(); - strcmp(GetSuiteTypeName(suiteType).c_str(), suiteName) == 0) + strcmp(SuiteTypeAsString(suiteType).c_str(), suiteName) == 0) { testMeta.m_suite = suiteName; testMeta.m_customArgs = suite[Keys[CommandKey]].GetString(); diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Source/Dependency/TestImpactDynamicDependencyMap.cpp b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Dependency/TestImpactDynamicDependencyMap.cpp index 1c79494c30..af32171b2b 100644 --- a/Code/Tools/TestImpactFramework/Runtime/Code/Source/Dependency/TestImpactDynamicDependencyMap.cpp +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Dependency/TestImpactDynamicDependencyMap.cpp @@ -156,8 +156,6 @@ namespace TestImpact { coveringTestTargetIt->second.erase(source); } - - } // 2. diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Source/Dependency/TestImpactDynamicDependencyMap.h b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Dependency/TestImpactDynamicDependencyMap.h index 5309a00894..d63938930f 100644 --- a/Code/Tools/TestImpactFramework/Runtime/Code/Source/Dependency/TestImpactDynamicDependencyMap.h +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Dependency/TestImpactDynamicDependencyMap.h @@ -9,7 +9,7 @@ #pragma once #include -#include +#include #include #include diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Source/Dependency/TestImpactTestSelectorAndPrioritizer.h b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Dependency/TestImpactTestSelectorAndPrioritizer.h index 4f266af8d2..bcfd8d24d5 100644 --- a/Code/Tools/TestImpactFramework/Runtime/Code/Source/Dependency/TestImpactTestSelectorAndPrioritizer.h +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Source/Dependency/TestImpactTestSelectorAndPrioritizer.h @@ -8,7 +8,7 @@ #pragma once -#include +#include #include #include diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Source/TestEngine/Enumeration/TestImpactTestEnumerator.cpp b/Code/Tools/TestImpactFramework/Runtime/Code/Source/TestEngine/Enumeration/TestImpactTestEnumerator.cpp index c2ba00835a..e564b93f6c 100644 --- a/Code/Tools/TestImpactFramework/Runtime/Code/Source/TestEngine/Enumeration/TestImpactTestEnumerator.cpp +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Source/TestEngine/Enumeration/TestImpactTestEnumerator.cpp @@ -6,7 +6,7 @@ * */ -#include +#include #include #include diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Source/TestEngine/Run/TestImpactInstrumentedTestRunner.cpp b/Code/Tools/TestImpactFramework/Runtime/Code/Source/TestEngine/Run/TestImpactInstrumentedTestRunner.cpp index cad8df1449..bf7e4d2c34 100644 --- a/Code/Tools/TestImpactFramework/Runtime/Code/Source/TestEngine/Run/TestImpactInstrumentedTestRunner.cpp +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Source/TestEngine/Run/TestImpactInstrumentedTestRunner.cpp @@ -6,7 +6,7 @@ * */ -#include +#include #include #include diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Source/TestEngine/Run/TestImpactTestRunner.cpp b/Code/Tools/TestImpactFramework/Runtime/Code/Source/TestEngine/Run/TestImpactTestRunner.cpp index a138e329b6..a480f4aa4a 100644 --- a/Code/Tools/TestImpactFramework/Runtime/Code/Source/TestEngine/Run/TestImpactTestRunner.cpp +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Source/TestEngine/Run/TestImpactTestRunner.cpp @@ -6,7 +6,7 @@ * */ -#include +#include #include #include diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Source/TestEngine/TestImpactTestEngine.cpp b/Code/Tools/TestImpactFramework/Runtime/Code/Source/TestEngine/TestImpactTestEngine.cpp index 226301d444..c84700065f 100644 --- a/Code/Tools/TestImpactFramework/Runtime/Code/Source/TestEngine/TestImpactTestEngine.cpp +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Source/TestEngine/TestImpactTestEngine.cpp @@ -6,7 +6,7 @@ * */ -#include +#include #include #include diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Source/TestImpactClientSequenceReport.cpp b/Code/Tools/TestImpactFramework/Runtime/Code/Source/TestImpactClientSequenceReport.cpp index ba3a479fdf..3f4656758a 100644 --- a/Code/Tools/TestImpactFramework/Runtime/Code/Source/TestImpactClientSequenceReport.cpp +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Source/TestImpactClientSequenceReport.cpp @@ -1,6 +1,7 @@ /* - * 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. - * + * 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 * */ @@ -11,7 +12,6 @@ namespace TestImpact { namespace Client { - //! Calculates the final sequence result for a composite of multiple sequences. TestSequenceResult CalculateMultiTestSequenceResult(const AZStd::vector& results) { // Order of precedence: @@ -19,14 +19,12 @@ namespace TestImpact // 2. TestSequenceResult::Timeout // 3. TestSequenceResult::Success - if (const auto it = AZStd::find(results.begin(), results.end(), TestSequenceResult::Failure); - it != results.end()) + if (const auto it = AZStd::find(results.begin(), results.end(), TestSequenceResult::Failure); it != results.end()) { return TestSequenceResult::Failure; } - - if (const auto it = AZStd::find(results.begin(), results.end(), TestSequenceResult::Timeout); - it != results.end()) + + if (const auto it = AZStd::find(results.begin(), results.end(), TestSequenceResult::Timeout); it != results.end()) { return TestSequenceResult::Timeout; } @@ -38,20 +36,32 @@ namespace TestImpact TestSequenceResult result, AZStd::chrono::high_resolution_clock::time_point startTime, AZStd::chrono::milliseconds duration, - AZStd::vector&& passingTests, - AZStd::vector&& failingTests, - AZStd::vector&& executionFailureTests, - AZStd::vector&& timedOutTests, - AZStd::vector&& unexecutedTests) + AZStd::vector&& passingTestRuns, + AZStd::vector&& failingTestRuns, + AZStd::vector&& executionFailureTestRuns, + AZStd::vector&& timedOutTestRuns, + AZStd::vector&& unexecutedTestRuns) : m_startTime(startTime) , m_result(result) , m_duration(duration) - , m_passingTests(AZStd::move(passingTests)) - , m_failingTests(AZStd::move(failingTests)) - , m_executionFailureTests(AZStd::move(executionFailureTests)) - , m_timedOutTests(AZStd::move(timedOutTests)) - , m_unexecutedTests(AZStd::move(unexecutedTests)) + , m_passingTestRuns(AZStd::move(passingTestRuns)) + , m_failingTestRuns(AZStd::move(failingTestRuns)) + , m_executionFailureTestRuns(AZStd::move(executionFailureTestRuns)) + , m_timedOutTestRuns(AZStd::move(timedOutTestRuns)) + , m_unexecutedTestRuns(AZStd::move(unexecutedTestRuns)) { + for (const auto& failingTestRun : m_failingTestRuns) + { + m_totalNumPassingTests += failingTestRun.GetTotalNumPassingTests(); + m_totalNumFailingTests += failingTestRun.GetTotalNumFailingTests(); + m_totalNumDisabledTests += failingTestRun.GetTotalNumDisabledTests(); + } + + for (const auto& passingTestRun : m_passingTestRuns) + { + m_totalNumPassingTests += passingTestRun.GetTotalNumPassingTests(); + m_totalNumDisabledTests += passingTestRun.GetTotalNumDisabledTests(); + } } TestSequenceResult TestRunReport::GetResult() const @@ -74,234 +84,238 @@ namespace TestImpact return m_duration; } - size_t TestRunReport::GetNumPassingTests() const + size_t TestRunReport::GetTotalNumTestRuns() const { - return m_passingTests.size(); + return + GetNumPassingTestRuns() + + GetNumFailingTestRuns() + + GetNumExecutionFailureTestRuns() + + GetNumTimedOutTestRuns() + + GetNumUnexecutedTestRuns(); } - size_t TestRunReport::GetNumFailingTests() const + size_t TestRunReport::GetNumPassingTestRuns() const { - return m_failingTests.size(); + return m_passingTestRuns.size(); } - size_t TestRunReport::GetNumTimedOutTests() const + size_t TestRunReport::GetNumFailingTestRuns() const { - return m_timedOutTests.size(); + return m_failingTestRuns.size(); } - size_t TestRunReport::GetNumUnexecutedTests() const + size_t TestRunReport::GetNumExecutionFailureTestRuns() const { - return m_unexecutedTests.size(); + return m_executionFailureTestRuns.size(); } - const AZStd::vector& TestRunReport::GetPassingTests() const + size_t TestRunReport::TestRunReport::GetNumTimedOutTestRuns() const { - return m_passingTests; + return m_timedOutTestRuns.size(); } - const AZStd::vector& TestRunReport::GetFailingTests() const + size_t TestRunReport::GetNumUnexecutedTestRuns() const { - return m_failingTests; + return m_unexecutedTestRuns.size(); } - const AZStd::vector& TestRunReport::GetExecutionFailureTests() const + const AZStd::vector& TestRunReport::GetPassingTestRuns() const { - return m_executionFailureTests; + return m_passingTestRuns; } - const AZStd::vector& TestRunReport::GetTimedOutTests() const + const AZStd::vector& TestRunReport::GetFailingTestRuns() const { - return m_timedOutTests; + return m_failingTestRuns; } - const AZStd::vector& TestRunReport::GetUnexecutedTests() const + const AZStd::vector& TestRunReport::GetExecutionFailureTestRuns() const { - return m_unexecutedTests; + return m_executionFailureTestRuns; } - SequenceReport::SequenceReport(SuiteType suiteType, const TestRunSelection& selectedTests, TestRunReport&& selectedTestRunReport) - : m_suite(suiteType) - , m_selectedTests(selectedTests) - , m_selectedTestRunReport(AZStd::move(selectedTestRunReport)) + const AZStd::vector& TestRunReport::GetTimedOutTestRuns() const { + return m_timedOutTestRuns; } - TestSequenceResult SequenceReport::GetResult() const + const AZStd::vector& TestRunReport::GetUnexecutedTestRuns() const { - return m_selectedTestRunReport.GetResult(); + return m_unexecutedTestRuns; } - AZStd::chrono::high_resolution_clock::time_point SequenceReport::GetStartTime() const + size_t TestRunReport::GetTotalNumPassingTests() const { - return m_selectedTestRunReport.GetStartTime(); + return m_totalNumPassingTests; } - AZStd::chrono::high_resolution_clock::time_point SequenceReport::GetEndTime() const + size_t TestRunReport::GetTotalNumFailingTests() const { - return GetStartTime() + GetDuration(); + return m_totalNumFailingTests; } - AZStd::chrono::milliseconds SequenceReport::GetDuration() const + size_t TestRunReport::GetTotalNumDisabledTests() const { - return m_selectedTestRunReport.GetDuration(); + return m_totalNumDisabledTests; } - TestRunSelection SequenceReport::GetSelectedTests() const - { - return m_selectedTests; - } - - TestRunReport SequenceReport::GetSelectedTestRunReport() const - { - return m_selectedTestRunReport; - } - - size_t SequenceReport::GetTotalNumPassingTests() const - { - return m_selectedTestRunReport.GetNumPassingTests(); - } - - size_t SequenceReport::GetTotalNumFailingTests() const - { - return m_selectedTestRunReport.GetNumFailingTests(); - } - - size_t SequenceReport::GetTotalNumTimedOutTests() const - { - return m_selectedTestRunReport.GetNumTimedOutTests(); - } - - size_t SequenceReport::GetTotalNumUnexecutedTests() const - { - return m_selectedTestRunReport.GetNumUnexecutedTests(); - } - - DraftingSequenceReport::DraftingSequenceReport( + RegularSequenceReport::RegularSequenceReport( + size_t maxConcurrency, + const AZStd::optional& testTargetTimeout, + const AZStd::optional& globalTimeout, + const SequencePolicyState& policyState, SuiteType suiteType, - const TestRunSelection& selectedTests, - const AZStd::vector& draftedTests, - TestRunReport&& selectedTestRunReport, - TestRunReport&& draftedTestRunReport) - : SequenceReport(suiteType, selectedTests, AZStd::move(selectedTestRunReport)) - , m_draftedTests(draftedTests) - , m_draftedTestRunReport(AZStd::move(draftedTestRunReport)) + const TestRunSelection& selectedTestRuns, + TestRunReport&& selectedTestRunReport) + : SequenceReportBase( + SequenceReportType::RegularSequence, + maxConcurrency, + testTargetTimeout, + globalTimeout, + policyState, + suiteType, + selectedTestRuns, + AZStd::move(selectedTestRunReport)) { } - TestSequenceResult DraftingSequenceReport::GetResult() const + SeedSequenceReport::SeedSequenceReport( + size_t maxConcurrency, + const AZStd::optional& testTargetTimeout, + const AZStd::optional& globalTimeout, + const SequencePolicyState& policyState, + SuiteType suiteType, + const TestRunSelection& selectedTestRuns, + TestRunReport&& selectedTestRunReport) + : SequenceReportBase( + SequenceReportType::SeedSequence, + maxConcurrency, + testTargetTimeout, + globalTimeout, + policyState, + suiteType, + selectedTestRuns, + AZStd::move(selectedTestRunReport)) { - return CalculateMultiTestSequenceResult({SequenceReport::GetResult(), m_draftedTestRunReport.GetResult()}); - } - - AZStd::chrono::milliseconds DraftingSequenceReport::GetDuration() const - { - return SequenceReport::GetDuration() + m_draftedTestRunReport.GetDuration(); - } - - size_t DraftingSequenceReport::GetTotalNumPassingTests() const - { - return SequenceReport::GetTotalNumPassingTests() + m_draftedTestRunReport.GetNumPassingTests(); - } - - size_t DraftingSequenceReport::GetTotalNumFailingTests() const - { - return SequenceReport::GetTotalNumFailingTests() + m_draftedTestRunReport.GetNumFailingTests(); - } - - size_t DraftingSequenceReport::GetTotalNumTimedOutTests() const - { - return SequenceReport::GetTotalNumTimedOutTests() + m_draftedTestRunReport.GetNumTimedOutTests(); - } - - size_t DraftingSequenceReport::GetTotalNumUnexecutedTests() const - { - return SequenceReport::GetTotalNumUnexecutedTests() + m_draftedTestRunReport.GetNumUnexecutedTests(); - } - - const AZStd::vector& DraftingSequenceReport::GetDraftedTests() const - { - return m_draftedTests; - } - - TestRunReport DraftingSequenceReport::GetDraftedTestRunReport() const - { - return m_draftedTestRunReport; } ImpactAnalysisSequenceReport::ImpactAnalysisSequenceReport( + size_t maxConcurrency, + const AZStd::optional& testTargetTimeout, + const AZStd::optional& globalTimeout, + const ImpactAnalysisSequencePolicyState& policyState, SuiteType suiteType, - const TestRunSelection& selectedTests, - const AZStd::vector& discardedTests, - const AZStd::vector& draftedTests, + const TestRunSelection& selectedTestRuns, + const AZStd::vector& discardedTestRuns, + const AZStd::vector& draftedTestRuns, TestRunReport&& selectedTestRunReport, TestRunReport&& draftedTestRunReport) - : DraftingSequenceReport( - suiteType, - selectedTests, - draftedTests, - AZStd::move(selectedTestRunReport), - AZStd::move(draftedTestRunReport)) - , m_discardedTests(discardedTests) + : DraftingSequenceReportBase( + SequenceReportType::ImpactAnalysisSequence, + maxConcurrency, + testTargetTimeout, + globalTimeout, + policyState, + suiteType, + selectedTestRuns, + draftedTestRuns, + AZStd::move(selectedTestRunReport), + AZStd::move(draftedTestRunReport)) + , m_discardedTestRuns(discardedTestRuns) { } - const AZStd::vector& ImpactAnalysisSequenceReport::GetDiscardedTests() const + const AZStd::vector& ImpactAnalysisSequenceReport::GetDiscardedTestRuns() const { - return m_discardedTests; + return m_discardedTestRuns; } SafeImpactAnalysisSequenceReport::SafeImpactAnalysisSequenceReport( + size_t maxConcurrency, + const AZStd::optional& testTargetTimeout, + const AZStd::optional& globalTimeout, + const SafeImpactAnalysisSequencePolicyState& policyState, SuiteType suiteType, - const TestRunSelection& selectedTests, - const TestRunSelection& discardedTests, - const AZStd::vector& draftedTests, + const TestRunSelection& selectedTestRuns, + const TestRunSelection& discardedTestRuns, + const AZStd::vector& draftedTestRuns, TestRunReport&& selectedTestRunReport, TestRunReport&& discardedTestRunReport, TestRunReport&& draftedTestRunReport) - : DraftingSequenceReport( - suiteType, - selectedTests, - draftedTests, - AZStd::move(selectedTestRunReport), - AZStd::move(draftedTestRunReport)) - , m_discardedTests(discardedTests) + : DraftingSequenceReportBase( + SequenceReportType::SafeImpactAnalysisSequence, + maxConcurrency, + testTargetTimeout, + globalTimeout, + policyState, + suiteType, + selectedTestRuns, + draftedTestRuns, + AZStd::move(selectedTestRunReport), + AZStd::move(draftedTestRunReport)) + , m_discardedTestRuns(discardedTestRuns) , m_discardedTestRunReport(AZStd::move(discardedTestRunReport)) { } TestSequenceResult SafeImpactAnalysisSequenceReport::GetResult() const { - return CalculateMultiTestSequenceResult({ DraftingSequenceReport::GetResult(), m_discardedTestRunReport.GetResult() }); + return CalculateMultiTestSequenceResult({ DraftingSequenceReportBase::GetResult(), m_discardedTestRunReport.GetResult() }); } AZStd::chrono::milliseconds SafeImpactAnalysisSequenceReport::GetDuration() const { - return DraftingSequenceReport::GetDuration() + m_discardedTestRunReport.GetDuration(); + return DraftingSequenceReportBase::GetDuration() + m_discardedTestRunReport.GetDuration(); + } + + size_t SafeImpactAnalysisSequenceReport::GetTotalNumTestRuns() const + { + return DraftingSequenceReportBase::GetTotalNumTestRuns() + m_discardedTestRunReport.GetTotalNumTestRuns(); } size_t SafeImpactAnalysisSequenceReport::GetTotalNumPassingTests() const { - return DraftingSequenceReport::GetTotalNumPassingTests() + m_discardedTestRunReport.GetNumPassingTests(); + return DraftingSequenceReportBase::GetTotalNumPassingTests() + m_discardedTestRunReport.GetTotalNumPassingTests(); } size_t SafeImpactAnalysisSequenceReport::GetTotalNumFailingTests() const { - return DraftingSequenceReport::GetTotalNumFailingTests() + m_discardedTestRunReport.GetNumFailingTests(); + return DraftingSequenceReportBase::GetTotalNumFailingTests() + m_discardedTestRunReport.GetTotalNumFailingTests(); } - size_t SafeImpactAnalysisSequenceReport::GetTotalNumTimedOutTests() const + size_t SafeImpactAnalysisSequenceReport::GetTotalNumDisabledTests() const { - return DraftingSequenceReport::GetTotalNumTimedOutTests() + m_discardedTestRunReport.GetNumTimedOutTests(); + return DraftingSequenceReportBase::GetTotalNumDisabledTests() + m_discardedTestRunReport.GetTotalNumDisabledTests(); } - size_t SafeImpactAnalysisSequenceReport::GetTotalNumUnexecutedTests() const + size_t SafeImpactAnalysisSequenceReport::GetTotalNumPassingTestRuns() const { - return DraftingSequenceReport::GetTotalNumUnexecutedTests() + m_discardedTestRunReport.GetNumUnexecutedTests(); + return DraftingSequenceReportBase::GetTotalNumPassingTestRuns() + m_discardedTestRunReport.GetNumPassingTestRuns(); + } + + size_t SafeImpactAnalysisSequenceReport::GetTotalNumFailingTestRuns() const + { + return DraftingSequenceReportBase::GetTotalNumFailingTestRuns() + m_discardedTestRunReport.GetNumFailingTestRuns(); + } + + size_t SafeImpactAnalysisSequenceReport::GetTotalNumExecutionFailureTestRuns() const + { + return DraftingSequenceReportBase::GetTotalNumExecutionFailureTestRuns() + m_discardedTestRunReport.GetNumExecutionFailureTestRuns(); + } + + size_t SafeImpactAnalysisSequenceReport::GetTotalNumTimedOutTestRuns() const + { + return DraftingSequenceReportBase::GetTotalNumTimedOutTestRuns() + m_discardedTestRunReport.GetNumTimedOutTestRuns(); + } + + size_t SafeImpactAnalysisSequenceReport::GetTotalNumUnexecutedTestRuns() const + { + return DraftingSequenceReportBase::GetTotalNumUnexecutedTestRuns() + m_discardedTestRunReport.GetNumUnexecutedTestRuns(); } - const TestRunSelection SafeImpactAnalysisSequenceReport::GetDiscardedTests() const + const TestRunSelection SafeImpactAnalysisSequenceReport::GetDiscardedTestRuns() const { - return m_discardedTests; + return m_discardedTestRuns; } TestRunReport SafeImpactAnalysisSequenceReport::GetDiscardedTestRunReport() const diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Source/TestImpactClientSequenceReportSerializer.cpp b/Code/Tools/TestImpactFramework/Runtime/Code/Source/TestImpactClientSequenceReportSerializer.cpp new file mode 100644 index 0000000000..99a187fe16 --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Source/TestImpactClientSequenceReportSerializer.cpp @@ -0,0 +1,606 @@ +/* + * 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 +#include +#include + +#include +#include +#include +#include + +namespace TestImpact +{ + namespace + { + namespace SequenceReportFields + { + // Keys for pertinent JSON node and attribute names + constexpr const char* Keys[] = + { + "name", + "command_args", + "start_time", + "end_time", + "duration", + "result", + "num_passing_tests", + "num_failing_tests", + "num_disabled_tests", + "tests", + "num_passing_test_runs", + "num_failing_test_runs", + "num_execution_failure_test_runs", + "num_timed_out_test_runs", + "num_unexecuted_test_runs", + "passing_test_runs", + "failing_test_runs", + "execution_failure_test_runs", + "timed_out_test_runs", + "unexecuted_test_runs", + "total_num_passing_tests", + "total_num_failing_tests", + "total_num_disabled_tests", + "total_num_test_runs", + "num_included_test_runs", + "num_excluded_test_runs", + "included_test_runs", + "excluded_test_runs", + "execution_failure", + "coverage_failure", + "test_failure", + "integrity_failure", + "test_sharding", + "target_output_capture", + "test_prioritization", + "dynamic_dependency_map", + "type", + "test_target_timeout", + "global_timeout", + "max_concurrency", + "policy", + "suite", + "selected_test_runs", + "selected_test_run_report", + "total_num_passing_test_runs", + "total_num_failing_test_runs", + "total_num_execution_failure_test_runs", + "total_num_timed_out_test_runs", + "total_num_unexecuted_test_runs", + "drafted_test_runs", + "drafted_test_run_report", + "discarded_test_runs", + "discarded_test_run_report" + }; + + enum + { + Name, + CommandArgs, + StartTime, + EndTime, + Duration, + Result, + NumPassingTests, + NumFailingTests, + NumDisabledTests, + Tests, + NumPassingTestRuns, + NumFailingTestRuns, + NumExecutionFailureTestRuns, + NumTimedOutTestRuns, + NumUnexecutedTestRuns, + PassingTestRuns, + FailingTestRuns, + ExecutionFailureTestRuns, + TimedOutTestRuns, + UnexecutedTestRuns, + TotalNumPassingTests, + TotalNumFailingTests, + TotalNumDisabledTests, + TotalNumTestRuns, + NumIncludedTestRuns, + NumExcludedTestRuns, + IncludedTestRuns, + ExcludedTestRuns, + ExecutionFailure, + CoverageFailure, + TestFailure, + IntegrityFailure, + TestSharding, + TargetOutputCapture, + TestPrioritization, + DynamicDependencyMap, + Type, + TestTargetTimeout, + GlobalTimeout, + MaxConcurrency, + Policy, + Suite, + SelectedTestRuns, + SelectedTestRunReport, + TotalNumPassingTestRuns, + TotalNumFailingTestRuns, + TotalNumExecutionFailureTestRuns, + TotalNumTimedOutTestRuns, + TotalNumUnexecutedTestRuns, + DraftedTestRuns, + DraftedTestRunReport, + DiscardedTestRuns, + DiscardedTestRunReport + }; + } // namespace SequenceReportFields + + AZ::u64 TimePointInMsAsInt64(AZStd::chrono::high_resolution_clock::time_point timePoint) + { + return AZStd::chrono::duration_cast(timePoint.time_since_epoch()).count(); + } + + void SerializeTestRunMembers(const Client::TestRunBase& testRun, rapidjson::PrettyWriter& writer) + { + // Name + writer.Key(SequenceReportFields::Keys[SequenceReportFields::Name]); + writer.String(testRun.GetTargetName().c_str()); + + // Command string + writer.Key(SequenceReportFields::Keys[SequenceReportFields::CommandArgs]); + writer.String(testRun.GetCommandString().c_str()); + + // Start time + writer.Key(SequenceReportFields::Keys[SequenceReportFields::StartTime]); + writer.Int64(TimePointInMsAsInt64(testRun.GetStartTime())); + + // End time + writer.Key(SequenceReportFields::Keys[SequenceReportFields::EndTime]); + writer.Int64(TimePointInMsAsInt64(testRun.GetEndTime())); + + // Duration + writer.Key(SequenceReportFields::Keys[SequenceReportFields::Duration]); + writer.Uint64(testRun.GetDuration().count()); + + // Result + writer.Key(SequenceReportFields::Keys[SequenceReportFields::Result]); + writer.String(TestRunResultAsString(testRun.GetResult()).c_str()); + } + + void SerializeTestRun(const Client::TestRunBase& testRun, rapidjson::PrettyWriter& writer) + { + writer.StartObject(); + { + SerializeTestRunMembers(testRun, writer); + } + writer.EndObject(); + } + + void SerializeCompletedTestRun(const Client::CompletedTestRun& testRun, rapidjson::PrettyWriter& writer) + { + writer.StartObject(); + { + SerializeTestRunMembers(testRun, writer); + + // Number of passing test cases + writer.Key(SequenceReportFields::Keys[SequenceReportFields::NumPassingTests]); + writer.Uint64(testRun.GetTotalNumPassingTests()); + + // Number of failing test cases + writer.Key(SequenceReportFields::Keys[SequenceReportFields::NumFailingTests]); + writer.Uint64(testRun.GetTotalNumFailingTests()); + + // Number of disabled test cases + writer.Key(SequenceReportFields::Keys[SequenceReportFields::NumDisabledTests]); + writer.Uint64(testRun.GetTotalNumDisabledTests()); + + // Tests + writer.Key(SequenceReportFields::Keys[SequenceReportFields::Tests]); + writer.StartArray(); + + for (const auto& test : testRun.GetTests()) + { + // Test + writer.StartObject(); + + // Name + writer.Key(SequenceReportFields::Keys[SequenceReportFields::Name]); + writer.String(test.GetName().c_str()); + + // Result + writer.Key(SequenceReportFields::Keys[SequenceReportFields::Result]); + writer.String(ClientTestResultAsString(test.GetResult()).c_str()); + + writer.EndObject(); // Test + } + + writer.EndArray(); // Tests + } + writer.EndObject(); + } + + void SerializeTestRunReport( + const Client::TestRunReport& testRunReport, rapidjson::PrettyWriter& writer) + { + writer.StartObject(); + { + // Result + writer.Key(SequenceReportFields::Keys[SequenceReportFields::Result]); + writer.String(TestSequenceResultAsString(testRunReport.GetResult()).c_str()); + + // Start time + writer.Key(SequenceReportFields::Keys[SequenceReportFields::StartTime]); + writer.Int64(TimePointInMsAsInt64(testRunReport.GetStartTime())); + + // End time + writer.Key(SequenceReportFields::Keys[SequenceReportFields::EndTime]); + writer.Int64(TimePointInMsAsInt64(testRunReport.GetEndTime())); + + // Duration + writer.Key(SequenceReportFields::Keys[SequenceReportFields::Duration]); + writer.Uint64(testRunReport.GetDuration().count()); + + // Number of passing test runs + writer.Key(SequenceReportFields::Keys[SequenceReportFields::NumPassingTestRuns]); + writer.Uint64(testRunReport.GetNumPassingTestRuns()); + + // Number of failing test runs + writer.Key(SequenceReportFields::Keys[SequenceReportFields::NumFailingTestRuns]); + writer.Uint64(testRunReport.GetNumFailingTestRuns()); + + // Number of test runs that failed to execute + writer.Key(SequenceReportFields::Keys[SequenceReportFields::NumExecutionFailureTestRuns]); + writer.Uint64(testRunReport.GetNumExecutionFailureTestRuns()); + + // Number of timed out test runs + writer.Key(SequenceReportFields::Keys[SequenceReportFields::NumTimedOutTestRuns]); + writer.Uint64(testRunReport.GetNumTimedOutTestRuns()); + + // Number of unexecuted test runs + writer.Key(SequenceReportFields::Keys[SequenceReportFields::NumUnexecutedTestRuns]); + writer.Uint64(testRunReport.GetNumUnexecutedTestRuns()); + + // Passing test runs + writer.Key(SequenceReportFields::Keys[SequenceReportFields::PassingTestRuns]); + writer.StartArray(); + for (const auto& testRun : testRunReport.GetPassingTestRuns()) + { + SerializeCompletedTestRun(testRun, writer); + } + writer.EndArray(); // Passing test runs + + // Failing test runs + writer.Key(SequenceReportFields::Keys[SequenceReportFields::FailingTestRuns]); + writer.StartArray(); + for (const auto& testRun : testRunReport.GetFailingTestRuns()) + { + SerializeCompletedTestRun(testRun, writer); + } + writer.EndArray(); // Failing test runs + + // Execution failures + writer.Key(SequenceReportFields::Keys[SequenceReportFields::ExecutionFailureTestRuns]); + writer.StartArray(); + for (const auto& testRun : testRunReport.GetExecutionFailureTestRuns()) + { + SerializeTestRun(testRun, writer); + } + writer.EndArray(); // Execution failures + + // Timed out test runs + writer.Key(SequenceReportFields::Keys[SequenceReportFields::TimedOutTestRuns]); + writer.StartArray(); + for (const auto& testRun : testRunReport.GetTimedOutTestRuns()) + { + SerializeTestRun(testRun, writer); + } + writer.EndArray(); // Timed out test runs + + // Unexecuted test runs + writer.Key(SequenceReportFields::Keys[SequenceReportFields::UnexecutedTestRuns]); + writer.StartArray(); + for (const auto& testRun : testRunReport.GetUnexecutedTestRuns()) + { + SerializeTestRun(testRun, writer); + } + writer.EndArray(); // Unexecuted test runs + + // Number of passing tests + writer.Key(SequenceReportFields::Keys[SequenceReportFields::TotalNumPassingTests]); + writer.Uint64(testRunReport.GetTotalNumPassingTests()); + + // Number of failing tests + writer.Key(SequenceReportFields::Keys[SequenceReportFields::TotalNumFailingTests]); + writer.Uint64(testRunReport.GetTotalNumFailingTests()); + + // Number of disabled tests + writer.Key(SequenceReportFields::Keys[SequenceReportFields::TotalNumDisabledTests]); + writer.Uint64(testRunReport.GetTotalNumDisabledTests()); + } + writer.EndObject(); + } + + void SerializeTestSelection( + const Client::TestRunSelection& testSelection, rapidjson::PrettyWriter& writer) + { + writer.StartObject(); + { + // Total number of test runs + writer.Key(SequenceReportFields::Keys[SequenceReportFields::TotalNumTestRuns]); + writer.Uint64(testSelection.GetTotalNumTests()); + + // Number of included test runs + writer.Key(SequenceReportFields::Keys[SequenceReportFields::NumIncludedTestRuns]); + writer.Uint64(testSelection.GetNumIncludedTestRuns()); + + // Number of excluded test runs + writer.Key(SequenceReportFields::Keys[SequenceReportFields::NumExcludedTestRuns]); + writer.Uint64(testSelection.GetNumExcludedTestRuns()); + + // Included test runs + writer.Key(SequenceReportFields::Keys[SequenceReportFields::IncludedTestRuns]); + writer.StartArray(); + for (const auto& testRun : testSelection.GetIncludededTestRuns()) + { + writer.String(testRun.c_str()); + } + writer.EndArray(); // Included test runs + + // Excluded test runs + writer.Key(SequenceReportFields::Keys[SequenceReportFields::ExcludedTestRuns]); + writer.StartArray(); + for (const auto& testRun : testSelection.GetExcludedTestRuns()) + { + writer.String(testRun.c_str()); + } + writer.EndArray(); // Excluded test runs + } + writer.EndObject(); + } + + void SerializePolicyStateBaseMembers(const PolicyStateBase& policyState, rapidjson::PrettyWriter& writer) + { + // Execution failure + writer.Key(SequenceReportFields::Keys[SequenceReportFields::ExecutionFailure]); + writer.String(ExecutionFailurePolicyAsString(policyState.m_executionFailurePolicy).c_str()); + + // Failed test coverage + writer.Key(SequenceReportFields::Keys[SequenceReportFields::CoverageFailure]); + writer.String(FailedTestCoveragePolicyAsString(policyState.m_failedTestCoveragePolicy).c_str()); + + // Test failure + writer.Key(SequenceReportFields::Keys[SequenceReportFields::TestFailure]); + writer.String(TestFailurePolicyAsString(policyState.m_testFailurePolicy).c_str()); + + // Integrity failure + writer.Key(SequenceReportFields::Keys[SequenceReportFields::IntegrityFailure]); + writer.String(IntegrityFailurePolicyAsString(policyState.m_integrityFailurePolicy).c_str()); + + // Test sharding + writer.Key(SequenceReportFields::Keys[SequenceReportFields::TestSharding]); + writer.String(TestShardingPolicyAsString(policyState.m_testShardingPolicy).c_str()); + + // Target output capture + writer.Key(SequenceReportFields::Keys[SequenceReportFields::TargetOutputCapture]); + writer.String(TargetOutputCapturePolicyAsString(policyState.m_targetOutputCapture).c_str()); + } + + void SerializePolicyStateMembers( + const SequencePolicyState& policyState, rapidjson::PrettyWriter& writer) + { + SerializePolicyStateBaseMembers(policyState.m_basePolicies, writer); + } + + void SerializePolicyStateMembers( + const SafeImpactAnalysisSequencePolicyState& policyState, rapidjson::PrettyWriter& writer) + { + SerializePolicyStateBaseMembers(policyState.m_basePolicies, writer); + + // Test prioritization + writer.Key(SequenceReportFields::Keys[SequenceReportFields::TestPrioritization]); + writer.String(TestPrioritizationPolicyAsString(policyState.m_testPrioritizationPolicy).c_str()); + } + + void SerializePolicyStateMembers( + const ImpactAnalysisSequencePolicyState& policyState, rapidjson::PrettyWriter& writer) + { + SerializePolicyStateBaseMembers(policyState.m_basePolicies, writer); + + // Test prioritization + writer.Key(SequenceReportFields::Keys[SequenceReportFields::TestPrioritization]); + writer.String(TestPrioritizationPolicyAsString(policyState.m_testPrioritizationPolicy).c_str()); + + // Dynamic dependency map + writer.Key(SequenceReportFields::Keys[SequenceReportFields::DynamicDependencyMap]); + writer.String(DynamicDependencyMapPolicyAsString(policyState.m_dynamicDependencyMap).c_str()); + } + + template + void SerializeSequenceReportBaseMembers( + const Client::SequenceReportBase& sequenceReport, rapidjson::PrettyWriter& writer) + { + // Type + writer.Key(SequenceReportFields::Keys[SequenceReportFields::Type]); + writer.String(SequenceReportTypeAsString(sequenceReport.GetType()).c_str()); + + // Test target timeout + writer.Key(SequenceReportFields::Keys[SequenceReportFields::TestTargetTimeout]); + writer.Uint64(sequenceReport.GetTestTargetTimeout().value_or(AZStd::chrono::milliseconds{0}).count()); + + // Global timeout + writer.Key(SequenceReportFields::Keys[SequenceReportFields::GlobalTimeout]); + writer.Uint64(sequenceReport.GetGlobalTimeout().value_or(AZStd::chrono::milliseconds{ 0 }).count()); + + // Maximum concurrency + writer.Key(SequenceReportFields::Keys[SequenceReportFields::MaxConcurrency]); + writer.Uint64(sequenceReport.GetMaxConcurrency()); + + // Policies + writer.Key(SequenceReportFields::Keys[SequenceReportFields::Policy]); + writer.StartObject(); + { + SerializePolicyStateMembers(sequenceReport.GetPolicyState(), writer); + } + writer.EndObject(); // Policies + + // Suite + writer.Key(SequenceReportFields::Keys[SequenceReportFields::Suite]); + writer.String(SuiteTypeAsString(sequenceReport.GetSuite()).c_str()); + + // Selected test runs + writer.Key(SequenceReportFields::Keys[SequenceReportFields::SelectedTestRuns]); + SerializeTestSelection(sequenceReport.GetSelectedTestRuns(), writer); + + // Selected test run report + writer.Key(SequenceReportFields::Keys[SequenceReportFields::SelectedTestRunReport]); + SerializeTestRunReport(sequenceReport.GetSelectedTestRunReport(), writer); + + // Start time + writer.Key(SequenceReportFields::Keys[SequenceReportFields::StartTime]); + writer.Int64(TimePointInMsAsInt64(sequenceReport.GetStartTime())); + + // End time + writer.Key(SequenceReportFields::Keys[SequenceReportFields::EndTime]); + writer.Int64(TimePointInMsAsInt64(sequenceReport.GetEndTime())); + + // Duration + writer.Key(SequenceReportFields::Keys[SequenceReportFields::Duration]); + writer.Uint64(sequenceReport.GetDuration().count()); + + // Result + writer.Key(SequenceReportFields::Keys[SequenceReportFields::Result]); + writer.String(TestSequenceResultAsString(sequenceReport.GetResult()).c_str()); + + // Total number of test runs + writer.Key(SequenceReportFields::Keys[SequenceReportFields::TotalNumTestRuns]); + writer.Uint64(sequenceReport.GetTotalNumTestRuns()); + + // Total number of passing test runs + writer.Key(SequenceReportFields::Keys[SequenceReportFields::TotalNumPassingTestRuns]); + writer.Uint64(sequenceReport.GetTotalNumPassingTestRuns()); + + // Total number of failing test runs + writer.Key(SequenceReportFields::Keys[SequenceReportFields::TotalNumFailingTestRuns]); + writer.Uint64(sequenceReport.GetTotalNumFailingTestRuns()); + + // Total number of test runs that failed to execute + writer.Key(SequenceReportFields::Keys[SequenceReportFields::TotalNumExecutionFailureTestRuns]); + writer.Uint64(sequenceReport.GetTotalNumExecutionFailureTestRuns()); + + // Total number of timed out test runs + writer.Key(SequenceReportFields::Keys[SequenceReportFields::TotalNumTimedOutTestRuns]); + writer.Uint64(sequenceReport.GetTotalNumTimedOutTestRuns()); + + // Total number of unexecuted test runs + writer.Key(SequenceReportFields::Keys[SequenceReportFields::TotalNumUnexecutedTestRuns]); + writer.Uint64(sequenceReport.GetTotalNumUnexecutedTestRuns()); + + // Total number of passing tests + writer.Key(SequenceReportFields::Keys[SequenceReportFields::TotalNumPassingTests]); + writer.Uint64(sequenceReport.GetTotalNumPassingTests()); + + // Total number of failing tests + writer.Key(SequenceReportFields::Keys[SequenceReportFields::TotalNumFailingTests]); + writer.Uint64(sequenceReport.GetTotalNumFailingTests()); + + // Total number of disabled tests + writer.Key(SequenceReportFields::Keys[SequenceReportFields::TotalNumDisabledTests]); + writer.Uint64(sequenceReport.GetTotalNumDisabledTests()); + } + + template + void SerializeDraftingSequenceReportMembers( + const Client::DraftingSequenceReportBase& sequenceReport, rapidjson::PrettyWriter& writer) + { + SerializeSequenceReportBaseMembers(sequenceReport, writer); + + // Drafted test runs + writer.Key(SequenceReportFields::Keys[SequenceReportFields::DraftedTestRuns]); + writer.StartArray(); + for (const auto& testRun : sequenceReport.GetDraftedTestRuns()) + { + writer.String(testRun.c_str()); + } + writer.EndArray(); // Drafted test runs + + // Drafted test run report + writer.Key(SequenceReportFields::Keys[SequenceReportFields::DraftedTestRunReport]); + SerializeTestRunReport(sequenceReport.GetDraftedTestRunReport(), writer); + } + } // namespace + + AZStd::string SerializeSequenceReport(const Client::RegularSequenceReport& sequenceReport) + { + rapidjson::StringBuffer stringBuffer; + rapidjson::PrettyWriter writer(stringBuffer); + + writer.StartObject(); + { + SerializeSequenceReportBaseMembers(sequenceReport, writer); + } + writer.EndObject(); + + return stringBuffer.GetString(); + } + + AZStd::string SerializeSequenceReport(const Client::SeedSequenceReport& sequenceReport) + { + rapidjson::StringBuffer stringBuffer; + rapidjson::PrettyWriter writer(stringBuffer); + + writer.StartObject(); + { + SerializeSequenceReportBaseMembers(sequenceReport, writer); + } + writer.EndObject(); + + return stringBuffer.GetString(); + } + + AZStd::string SerializeSequenceReport(const Client::ImpactAnalysisSequenceReport& sequenceReport) + { + rapidjson::StringBuffer stringBuffer; + rapidjson::PrettyWriter writer(stringBuffer); + + writer.StartObject(); + { + SerializeDraftingSequenceReportMembers(sequenceReport, writer); + + // Discarded test runs + writer.Key(SequenceReportFields::Keys[SequenceReportFields::DiscardedTestRuns]); + writer.StartArray(); + for (const auto& testRun : sequenceReport.GetDiscardedTestRuns()) + { + writer.String(testRun.c_str()); + } + writer.EndArray(); // Discarded test runs + } + writer.EndObject(); + + return stringBuffer.GetString(); + } + + AZStd::string SerializeSequenceReport(const Client::SafeImpactAnalysisSequenceReport& sequenceReport) + { + rapidjson::StringBuffer stringBuffer; + rapidjson::PrettyWriter writer(stringBuffer); + + writer.StartObject(); + { + SerializeDraftingSequenceReportMembers(sequenceReport, writer); + + // Discarded test runs + writer.Key(SequenceReportFields::Keys[SequenceReportFields::DiscardedTestRuns]); + SerializeTestSelection(sequenceReport.GetDiscardedTestRuns(), writer); + + // Discarded test run report + writer.Key(SequenceReportFields::Keys[SequenceReportFields::DiscardedTestRunReport]); + SerializeTestRunReport(sequenceReport.GetDiscardedTestRunReport(), writer); + } + writer.EndObject(); + + return stringBuffer.GetString(); + } +} // namespace TestImpact diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Source/TestImpactClientTestRun.cpp b/Code/Tools/TestImpactFramework/Runtime/Code/Source/TestImpactClientTestRun.cpp index 0a258c5dac..6f50145716 100644 --- a/Code/Tools/TestImpactFramework/Runtime/Code/Source/TestImpactClientTestRun.cpp +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Source/TestImpactClientTestRun.cpp @@ -8,11 +8,13 @@ #include +#include + namespace TestImpact { namespace Client { - TestRun::TestRun( + TestRunBase::TestRunBase( const AZStd::string& name, const AZStd::string& commandString, AZStd::chrono::high_resolution_clock::time_point startTime, @@ -26,107 +28,145 @@ namespace TestImpact { } - const AZStd::string& TestRun::GetTargetName() const + const AZStd::string& TestRunBase::GetTargetName() const { return m_targetName; } - const AZStd::string& TestRun::GetCommandString() const + const AZStd::string& TestRunBase::GetCommandString() const { return m_commandString; } - AZStd::chrono::high_resolution_clock::time_point TestRun::GetStartTime() const + AZStd::chrono::high_resolution_clock::time_point TestRunBase::GetStartTime() const { return m_startTime; } - AZStd::chrono::high_resolution_clock::time_point TestRun::GetEndTime() const + AZStd::chrono::high_resolution_clock::time_point TestRunBase::GetEndTime() const { return m_startTime + m_duration; } - AZStd::chrono::milliseconds TestRun::GetDuration() const + AZStd::chrono::milliseconds TestRunBase::GetDuration() const { return m_duration; } - TestRunResult TestRun::GetResult() const + TestRunResult TestRunBase::GetResult() const { return m_result; } - TestFailure::TestFailure(const AZStd::string& testName, const AZStd::string& errorMessage) + TestRunWithExecutionFailure::TestRunWithExecutionFailure(TestRunBase&& testRun) + : TestRunBase(AZStd::move(testRun)) + { + } + + TimedOutTestRun::TimedOutTestRun(TestRunBase&& testRun) + : TestRunBase(AZStd::move(testRun)) + { + } + + UnexecutedTestRun::UnexecutedTestRun(TestRunBase&& testRun) + : TestRunBase(AZStd::move(testRun)) + { + } + + Test::Test(const AZStd::string& testName, TestResult result) : m_name(testName) - , m_errorMessage(errorMessage) + , m_result(result) { } - const AZStd::string& TestFailure::GetName() const + const AZStd::string& Test::GetName() const { return m_name; } - const AZStd::string& TestFailure::GetErrorMessage() const + TestResult Test::GetResult() const { - return m_errorMessage; + return m_result; } - TestCaseFailure::TestCaseFailure(const AZStd::string& testCaseName, AZStd::vector&& testFailures) - : m_name(testCaseName) - , m_testFailures(AZStd::move(testFailures)) + AZStd::tuple CalculateTestCaseMetrics(const AZStd::vector& tests) { - } + size_t totalNumPassingTests = 0; + size_t totalNumFailingTests = 0; + size_t totalNumDisabledTests = 0; - const AZStd::string& TestCaseFailure::GetName() const - { - return m_name; - } - - const AZStd::vector& TestCaseFailure::GetTestFailures() const - { - return m_testFailures; - } - - static size_t CalculateNumTestRunFailures(const AZStd::vector& testFailures) - { - size_t numTestFailures = 0; - for (const auto& testCase : testFailures) + for (const auto& test : tests) { - numTestFailures += testCase.GetTestFailures().size(); + if (test.GetResult() == Client::TestResult::Passed) + { + totalNumPassingTests++; + } + else if (test.GetResult() == Client::TestResult::Failed) + { + totalNumFailingTests++; + } + else + { + totalNumDisabledTests++; + } } - return numTestFailures; + return { totalNumPassingTests, totalNumFailingTests, totalNumDisabledTests }; } - TestRunWithTestFailures::TestRunWithTestFailures( + CompletedTestRun::CompletedTestRun( const AZStd::string& name, const AZStd::string& commandString, AZStd::chrono::high_resolution_clock::time_point startTime, AZStd::chrono::milliseconds duration, TestRunResult result, - AZStd::vector&& testFailures) - : TestRun(name, commandString, startTime, duration, result) - , m_testCaseFailures(AZStd::move(testFailures)) + AZStd::vector&& tests) + : TestRunBase(name, commandString, startTime, duration, result) + , m_tests(AZStd::move(tests)) { - m_numTestFailures = CalculateNumTestRunFailures(m_testCaseFailures); + AZStd::tie(m_totalNumPassingTests, m_totalNumFailingTests, m_totalNumDisabledTests) = CalculateTestCaseMetrics(m_tests); } - TestRunWithTestFailures::TestRunWithTestFailures(TestRun&& testRun, AZStd::vector&& testFailures) - : TestRun(AZStd::move(testRun)) - , m_testCaseFailures(AZStd::move(testFailures)) + CompletedTestRun::CompletedTestRun(TestRunBase&& testRun, AZStd::vector&& tests) + : TestRunBase(AZStd::move(testRun)) + , m_tests(AZStd::move(tests)) { - m_numTestFailures = CalculateNumTestRunFailures(m_testCaseFailures); + AZStd::tie(m_totalNumPassingTests, m_totalNumFailingTests, m_totalNumDisabledTests) = CalculateTestCaseMetrics(m_tests); } - size_t TestRunWithTestFailures::GetNumTestFailures() const + size_t CompletedTestRun::GetTotalNumTests() const { - return m_numTestFailures; + return m_tests.size(); } - const AZStd::vector& TestRunWithTestFailures::GetTestCaseFailures() const + size_t CompletedTestRun::GetTotalNumPassingTests() const + { + return m_totalNumPassingTests; + } + + size_t CompletedTestRun::GetTotalNumFailingTests() const + { + return m_totalNumFailingTests; + } + + size_t CompletedTestRun::GetTotalNumDisabledTests() const + { + return m_totalNumDisabledTests; + } + + const AZStd::vector& CompletedTestRun::GetTests() const + { + return m_tests; + } + + PassingTestRun::PassingTestRun(TestRunBase&& testRun, AZStd::vector&& tests) + : CompletedTestRun(AZStd::move(testRun), AZStd::move(tests)) + { + } + + FailingTestRun::FailingTestRun(TestRunBase&& testRun, AZStd::vector&& tests) + : CompletedTestRun(AZStd::move(testRun), AZStd::move(tests)) { - return m_testCaseFailures; } } // namespace Client } // namespace TestImpact diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Source/TestImpactRuntime.cpp b/Code/Tools/TestImpactFramework/Runtime/Code/Source/TestImpactRuntime.cpp index 478b63e8b2..8a9d96bca8 100644 --- a/Code/Tools/TestImpactFramework/Runtime/Code/Source/TestImpactRuntime.cpp +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Source/TestImpactRuntime.cpp @@ -6,7 +6,7 @@ * */ -#include +#include #include #include @@ -81,7 +81,7 @@ namespace TestImpact { if (m_testCompleteCallback.has_value()) { - Client::TestRun testRun( + Client::TestRunBase testRun( testJob.GetTestTarget()->GetName(), testJob.GetCommandString(), testJob.GetStartTime(), @@ -110,8 +110,140 @@ namespace TestImpact return result; } + //! Utility structure for holding the pertinent data for test run reports. + template + struct TestRunData + { + TestSequenceResult m_result = TestSequenceResult::Success; + AZStd::vector m_jobs; + AZStd::chrono::high_resolution_clock::time_point m_relativeStartTime; + AZStd::chrono::milliseconds m_duration = AZStd::chrono::milliseconds{ 0 }; + }; + + //! Wrapper for the impact analysis test sequence to handle both the updating and non-updating policies through a common pathway. + //! @tparam TestRunnerFunctor The functor for running the specified tests. + //! @tparam TestJob The test engine job type returned by the functor. + //! @param maxConcurrency The maximum concurrency being used for this sequence. + //! @param policyState The policy state being used for the sequence. + //! @param suiteType The suite type used for this sequence. + //! @param timer The timer to use for the test run timings. + //! @param testRunner The test runner functor to use for each of the test runs. + //! @param includedSelectedTestTargets The subset of test targets that were selected to run and not also fully excluded from running. + //! @param excludedSelectedTestTargets The subset of test targets that were selected to run but were fully excluded running. + //! @param discardedTestTargets The subset of test targets that were discarded from the test selection and will not be run. + //! @param globalTimeout The maximum duration the entire test sequence may run for (infinite if empty). + //! @param testSequenceStartCallback The client function to be called after the test targets have been selected but prior to running the + //! tests. + //! @param testSequenceCompleteCallback The client function to be called after the test sequence has completed. + //! @param testRunCompleteCallback The client function to be called after an individual test run has completed. + //! @param updateCoverage The function to call to update the dynamic dependency map with test coverage (if any). + template + Client::ImpactAnalysisSequenceReport ImpactAnalysisTestSequenceWrapper( + size_t maxConcurrency, + const ImpactAnalysisSequencePolicyState& policyState, + SuiteType suiteType, + const Timer& sequenceTimer, + const TestRunnerFunctor& testRunner, + const AZStd::vector& includedSelectedTestTargets, + const AZStd::vector& excludedSelectedTestTargets, + const AZStd::vector& discardedTestTargets, + const AZStd::vector& draftedTestTargets, + const AZStd::optional& testTargetTimeout, + const AZStd::optional& globalTimeout, + AZStd::optional testSequenceStartCallback, + AZStd::optional> testSequenceEndCallback, + AZStd::optional testCompleteCallback, + AZStd::optional& jobs)>> updateCoverage) + { + TestRunData selectedTestRunData, draftedTestRunData; + AZStd::optional sequenceTimeout = globalTimeout; + + // Extract the client facing representation of selected, discarded and drafted test targets + const Client::TestRunSelection selectedTests( + ExtractTestTargetNames(includedSelectedTestTargets), ExtractTestTargetNames(excludedSelectedTestTargets)); + const auto discardedTests = ExtractTestTargetNames(discardedTestTargets); + const auto draftedTests = ExtractTestTargetNames(draftedTestTargets); + + // Inform the client that the sequence is about to start + if (testSequenceStartCallback.has_value()) + { + (*testSequenceStartCallback)(suiteType, selectedTests, discardedTests, draftedTests); + } + + // We share the test run complete handler between the selected and drafted test runs as to present them together as one + // continuous test sequence to the client rather than two discrete test runs + const size_t totalNumTestRuns = includedSelectedTestTargets.size() + draftedTestTargets.size(); + TestRunCompleteCallbackHandler testRunCompleteHandler(totalNumTestRuns, testCompleteCallback); + + const auto gatherTestRunData = [&sequenceTimer, &testRunner, &testRunCompleteHandler, &globalTimeout] + (const AZStd::vector& testsTargets, TestRunData& testRunData) + { + const Timer testRunTimer; + testRunData.m_relativeStartTime = testRunTimer.GetStartTimePointRelative(sequenceTimer); + auto [result, jobs] = testRunner(testsTargets, testRunCompleteHandler, globalTimeout); + testRunData.m_result = result; + testRunData.m_jobs = AZStd::move(jobs); + testRunData.m_duration = testRunTimer.GetElapsedMs(); + }; + + if (!includedSelectedTestTargets.empty()) + { + // Run the selected test targets and collect the test run results + gatherTestRunData(includedSelectedTestTargets, selectedTestRunData); + + // Carry the remaining global sequence time over to the drafted test run + if (globalTimeout.has_value()) + { + const auto elapsed = selectedTestRunData.m_duration; + sequenceTimeout = elapsed < globalTimeout.value() ? globalTimeout.value() - elapsed : AZStd::chrono::milliseconds(0); + } + } + + if (!draftedTestTargets.empty()) + { + // Run the drafted test targets and collect the test run results + gatherTestRunData(draftedTestTargets, draftedTestRunData); + } + + // Generate the sequence report for the client + const auto sequenceReport = Client::ImpactAnalysisSequenceReport( + maxConcurrency, + testTargetTimeout, + globalTimeout, + policyState, + suiteType, + selectedTests, + discardedTests, + draftedTests, + GenerateTestRunReport( + selectedTestRunData.m_result, + selectedTestRunData.m_relativeStartTime, + selectedTestRunData.m_duration, + selectedTestRunData.m_jobs), + GenerateTestRunReport( + draftedTestRunData.m_result, + draftedTestRunData.m_relativeStartTime, + draftedTestRunData.m_duration, + draftedTestRunData.m_jobs)); + + // Inform the client that the sequence has ended + if (testSequenceEndCallback.has_value()) + { + (*testSequenceEndCallback)(sequenceReport); + } + + // Update the dynamic dependency map with the latest coverage data (if any) + if (updateCoverage.has_value()) + { + (*updateCoverage)(ConcatenateVectors(selectedTestRunData.m_jobs, draftedTestRunData.m_jobs)); + } + + return sequenceReport; + } + Runtime::Runtime( RuntimeConfig&& config, + AZStd::optional dataFile, SuiteType suiteFilter, Policy::ExecutionFailure executionFailurePolicy, Policy::FailedTestCoverage failedTestCoveragePolicy, @@ -151,27 +283,22 @@ namespace TestImpact try { + if (dataFile.has_value()) + { + m_sparTiaFile = dataFile.value().String(); + } + else + { + m_sparTiaFile = m_config.m_workspace.m_active.m_sparTiaFiles[static_cast(m_suiteFilter)].String(); + } + // Populate the dynamic dependency map with the existing source coverage data (if any) - m_sparTIAFile = m_config.m_workspace.m_active.m_sparTIAFiles[static_cast(m_suiteFilter)].String(); - const auto tiaDataRaw = ReadFileContents(m_sparTIAFile); + const auto tiaDataRaw = ReadFileContents(m_sparTiaFile); const auto tiaData = DeserializeSourceCoveringTestsList(tiaDataRaw); if (tiaData.GetNumSources()) { m_dynamicDependencyMap->ReplaceSourceCoverage(tiaData); m_hasImpactAnalysisData = true; - - // Enumerate new test targets - const auto testTargetsWithNoEnumeration = m_dynamicDependencyMap->GetNotCoveringTests(); - if (!testTargetsWithNoEnumeration.empty()) - { - m_testEngine->UpdateEnumerationCache( - testTargetsWithNoEnumeration, - Policy::ExecutionFailure::Ignore, - Policy::TestFailure::Continue, - AZStd::nullopt, - AZStd::nullopt, - AZStd::nullopt); - } } } catch (const DependencyException& e) @@ -186,7 +313,7 @@ namespace TestImpact AZ_Printf( LogCallSite, AZStd::string::format( - "No test impact analysis data found for suite '%s' at %s\n", GetSuiteTypeName(m_suiteFilter).c_str(), m_sparTIAFile.c_str()).c_str()); + "No test impact analysis data found for suite '%s' at %s\n", SuiteTypeAsString(m_suiteFilter).c_str(), m_sparTiaFile.c_str()).c_str()); } } @@ -230,7 +357,7 @@ namespace TestImpact } } - AZStd::pair, AZStd::vector> Runtime::SelectCoveringTestTargetsAndUpdateEnumerationCache( + AZStd::pair, AZStd::vector> Runtime::SelectCoveringTestTargets( const ChangeList& changeList, Policy::TestPrioritization testPrioritizationPolicy) { @@ -243,9 +370,6 @@ namespace TestImpact // Populate a set with the selected test targets so that we can infer the discarded test target not selected for this change list const AZStd::unordered_set selectedTestTargetSet(selectedTestTargets.begin(), selectedTestTargets.end()); - // Update the enumeration caches of mutated targets regardless of the current sharding policy - EnumerateMutatedTestTargets(changeDependencyList); - // The test targets in the main list not in the selected test target set are the test targets not selected for this change list for (const auto& testTarget : m_dynamicDependencyMap->GetTestTargetList().GetTargets()) { @@ -287,7 +411,7 @@ namespace TestImpact void Runtime::ClearDynamicDependencyMapAndRemoveExistingFile() { m_dynamicDependencyMap->ClearAllSourceCoverage(); - DeleteFile(m_sparTIAFile); + DeleteFile(m_sparTiaFile); } SourceCoveringTestsList Runtime::CreateSourceCoveringTestFromTestCoverages(const AZStd::vector& jobs) @@ -368,9 +492,9 @@ namespace TestImpact } m_dynamicDependencyMap->ReplaceSourceCoverage(sourceCoverageTestsList); - const auto sparTIA = m_dynamicDependencyMap->ExportSourceCoverage(); - const auto sparTIAData = SerializeSourceCoveringTestsList(sparTIA); - WriteFileContents(sparTIAData, m_sparTIAFile); + const auto sparTia = m_dynamicDependencyMap->ExportSourceCoverage(); + const auto sparTiaData = SerializeSourceCoveringTestsList(sparTia); + WriteFileContents(sparTiaData, m_sparTiaFile); m_hasImpactAnalysisData = true; } catch(const RuntimeException& e) @@ -386,11 +510,42 @@ namespace TestImpact } } - Client::SequenceReport Runtime::RegularTestSequence( + PolicyStateBase Runtime::GeneratePolicyStateBase() const + { + PolicyStateBase policyState; + + policyState.m_executionFailurePolicy = m_executionFailurePolicy; + policyState.m_failedTestCoveragePolicy = m_failedTestCoveragePolicy; + policyState.m_integrityFailurePolicy = m_integrationFailurePolicy; + policyState.m_targetOutputCapture = m_targetOutputCapture; + policyState.m_testFailurePolicy = m_testFailurePolicy; + policyState.m_testShardingPolicy = m_testShardingPolicy; + + return policyState; + } + + SequencePolicyState Runtime::GenerateSequencePolicyState() const + { + return { GeneratePolicyStateBase() }; + } + + SafeImpactAnalysisSequencePolicyState Runtime::GenerateSafeImpactAnalysisSequencePolicyState( + Policy::TestPrioritization testPrioritizationPolicy) const + { + return { GeneratePolicyStateBase(), testPrioritizationPolicy }; + } + + ImpactAnalysisSequencePolicyState Runtime::GenerateImpactAnalysisSequencePolicyState( + Policy::TestPrioritization testPrioritizationPolicy, Policy::DynamicDependencyMap dynamicDependencyMapPolicy) const + { + return { GeneratePolicyStateBase(), testPrioritizationPolicy, dynamicDependencyMapPolicy }; + } + + Client::RegularSequenceReport Runtime::RegularTestSequence( AZStd::optional testTargetTimeout, AZStd::optional globalTimeout, AZStd::optional testSequenceStartCallback, - AZStd::optional> testSequenceEndCallback, + AZStd::optional> testSequenceEndCallback, AZStd::optional testCompleteCallback) { const Timer sequenceTimer; @@ -434,7 +589,11 @@ namespace TestImpact const auto testRunDuration = testRunTimer.GetElapsedMs(); // Generate the sequence report for the client - const auto sequenceReport = Client::SequenceReport( + const auto sequenceReport = Client::RegularSequenceReport( + m_maxConcurrency, + testTargetTimeout, + globalTimeout, + GenerateSequencePolicyState(), m_suiteFilter, selectedTests, GenerateTestRunReport(result, testRunTimer.GetStartTimePointRelative(sequenceTimer), testRunDuration, testJobs)); @@ -448,95 +607,6 @@ namespace TestImpact return sequenceReport; } - //! Wrapper for the impact analysis test sequence to handle both the updating and non-updating policies through a common pathway. - //! @tparam TestRunnerFunctor The functor for running the specified tests. - //! @tparam TestJob The test engine job type returned by the functor. - //! @param suiteType The suite type used for this sequence. - //! @param timer The timer to use for the test run timings. - //! @param testRunner The test runner functor to use for each of the test runs. - //! @param includedSelectedTestTargets The subset of test targets that were selected to run and not also fully excluded from running. - //! @param excludedSelectedTestTargets The subset of test targets that were selected to run but were fully excluded running. - //! @param discardedTestTargets The subset of test targets that were discarded from the test selection and will not be run. - //! @param globalTimeout The maximum duration the entire test sequence may run for (infinite if empty). - //! @param testSequenceStartCallback The client function to be called after the test targets have been selected but prior to running the tests. - //! @param testSequenceCompleteCallback The client function to be called after the test sequence has completed. - //! @param testRunCompleteCallback The client function to be called after an individual test run has completed. - //! @param updateCoverage The function to call to update the dynamic dependency map with test coverage (if any). - template - Client::ImpactAnalysisSequenceReport ImpactAnalysisTestSequenceWrapper( - SuiteType suiteType, - const Timer& sequenceTimer, - const TestRunnerFunctor& testRunner, - const AZStd::vector& includedSelectedTestTargets, - const AZStd::vector& excludedSelectedTestTargets, - const AZStd::vector& discardedTestTargets, - const AZStd::vector& draftedTestTargets, - const AZStd::optional globalTimeout, - AZStd::optional testSequenceStartCallback, - AZStd::optional> testSequenceEndCallback, - AZStd::optional testCompleteCallback, - AZStd::optional& jobs)>> updateCoverage) - { - AZStd::optional sequenceTimeout = globalTimeout; - - // Extract the client facing representation of selected, discarded and drafted test targets - const Client::TestRunSelection selectedTests( - ExtractTestTargetNames(includedSelectedTestTargets), ExtractTestTargetNames(excludedSelectedTestTargets)); - const auto discardedTests = ExtractTestTargetNames(discardedTestTargets); - const auto draftedTests = ExtractTestTargetNames(draftedTestTargets); - - // Inform the client that the sequence is about to start - if (testSequenceStartCallback.has_value()) - { - (*testSequenceStartCallback)(suiteType, selectedTests, discardedTests, draftedTests); - } - - // We share the test run complete handler between the selected and drafted test runs as to present them together as one - // continuous test sequence to the client rather than two discrete test runs - const size_t totalNumTestRuns = includedSelectedTestTargets.size() + draftedTestTargets.size(); - TestRunCompleteCallbackHandler testRunCompleteHandler(totalNumTestRuns, testCompleteCallback); - - // Run the selected test targets and collect the test run results - const Timer selectedTestRunTimer; - const auto [selectedResult, selectedTestJobs] = testRunner(includedSelectedTestTargets, testRunCompleteHandler, globalTimeout); - const auto selectedTestRunDuration = selectedTestRunTimer.GetElapsedMs(); - - // Carry the remaining global sequence time over to the drafted test run - if (globalTimeout.has_value()) - { - const auto elapsed = selectedTestRunDuration; - sequenceTimeout = elapsed < globalTimeout.value() ? globalTimeout.value() - elapsed : AZStd::chrono::milliseconds(0); - } - - // Run the drafted test targets and collect the test run results - Timer draftedTestRunTimer; - const auto [draftedResult, draftedTestJobs] = testRunner(draftedTestTargets, testRunCompleteHandler, globalTimeout); - const auto draftedTestRunDuration = draftedTestRunTimer.GetElapsedMs(); - - // Generate the sequence report for the client - const auto sequenceReport = Client::ImpactAnalysisSequenceReport( - suiteType, - selectedTests, - discardedTests, - draftedTests, - GenerateTestRunReport(selectedResult, selectedTestRunTimer.GetStartTimePointRelative(sequenceTimer), selectedTestRunDuration, selectedTestJobs), - GenerateTestRunReport(draftedResult, draftedTestRunTimer.GetStartTimePointRelative(sequenceTimer), draftedTestRunDuration, draftedTestJobs)); - - // Inform the client that the sequence has ended - if (testSequenceEndCallback.has_value()) - { - (*testSequenceEndCallback)(sequenceReport); - } - - // Update the dynamic dependency map with the latest coverage data (if any) - if (updateCoverage.has_value()) - { - (*updateCoverage)(ConcatenateVectors(selectedTestJobs, draftedTestJobs)); - } - - return sequenceReport; - } - Client::ImpactAnalysisSequenceReport Runtime::ImpactAnalysisTestSequence( const ChangeList& changeList, Policy::TestPrioritization testPrioritizationPolicy, @@ -550,10 +620,30 @@ namespace TestImpact const Timer sequenceTimer; // Draft in the test targets that have no coverage entries in the dynamic dependency map - AZStd::vector draftedTestTargets = m_dynamicDependencyMap->GetNotCoveringTests(); + const AZStd::vector draftedTestTargets = m_dynamicDependencyMap->GetNotCoveringTests(); - // The test targets that were selected for the change list by the dynamic dependency map and the test targets that were not - auto [selectedTestTargets, discardedTestTargets] = SelectCoveringTestTargetsAndUpdateEnumerationCache(changeList, testPrioritizationPolicy); + const auto selectCoveringTestTargetsAndPruneDraftedFromDiscarded = + [this, &draftedTestTargets, &changeList, testPrioritizationPolicy]() + { + // The test targets that were selected for the change list by the dynamic dependency map and the test targets that were not + const auto [selectedTestTargets, discardedTestTargets] = + SelectCoveringTestTargets(changeList, testPrioritizationPolicy); + + const AZStd::unordered_set draftedTestTargetsSet(draftedTestTargets.begin(), draftedTestTargets.end()); + + AZStd::vector discardedNotDraftedTestTargets; + for (const auto* testTarget : discardedTestTargets) + { + if (!draftedTestTargetsSet.count(testTarget)) + { + discardedNotDraftedTestTargets.push_back(testTarget); + } + } + + return AZStd::pair{ selectedTestTargets, discardedNotDraftedTestTargets }; + }; + + const auto [selectedTestTargets, discardedTestTargets] = selectCoveringTestTargetsAndPruneDraftedFromDiscarded(); // The subset of selected test targets that are not on the configuration's exclude list and those that are auto [includedSelectedTestTargets, excludedSelectedTestTargets] = SelectTestTargetsByExcludeList(selectedTestTargets); @@ -604,6 +694,8 @@ namespace TestImpact }; return ImpactAnalysisTestSequenceWrapper( + m_maxConcurrency, + GenerateImpactAnalysisSequencePolicyState(testPrioritizationPolicy, dynamicDependencyMapPolicy), m_suiteFilter, sequenceTimer, instrumentedTestRun, @@ -611,6 +703,7 @@ namespace TestImpact excludedSelectedTestTargets, discardedTestTargets, draftedTestTargets, + testTargetTimeout, globalTimeout, testSequenceStartCallback, testSequenceEndCallback, @@ -620,6 +713,8 @@ namespace TestImpact else { return ImpactAnalysisTestSequenceWrapper( + m_maxConcurrency, + GenerateImpactAnalysisSequencePolicyState(testPrioritizationPolicy, dynamicDependencyMapPolicy), m_suiteFilter, sequenceTimer, regularTestRun, @@ -627,6 +722,7 @@ namespace TestImpact excludedSelectedTestTargets, discardedTestTargets, draftedTestTargets, + testTargetTimeout, globalTimeout, testSequenceStartCallback, testSequenceEndCallback, @@ -645,13 +741,15 @@ namespace TestImpact AZStd::optional testCompleteCallback) { const Timer sequenceTimer; - auto sequenceTimeout = globalTimeout; + TestRunData selectedTestRunData, draftedTestRunData; + TestRunData discardedTestRunData; + AZStd::optional sequenceTimeout = globalTimeout; // Draft in the test targets that have no coverage entries in the dynamic dependency map AZStd::vector draftedTestTargets = m_dynamicDependencyMap->GetNotCoveringTests(); // The test targets that were selected for the change list by the dynamic dependency map and the test targets that were not - auto [selectedTestTargets, discardedTestTargets] = SelectCoveringTestTargetsAndUpdateEnumerationCache(changeList, testPrioritizationPolicy); + const auto [selectedTestTargets, discardedTestTargets] = SelectCoveringTestTargets(changeList, testPrioritizationPolicy); // The subset of selected test targets that are not on the configuration's exclude list and those that are auto [includedSelectedTestTargets, excludedSelectedTestTargets] = SelectTestTargetsByExcludeList(selectedTestTargets); @@ -675,71 +773,107 @@ namespace TestImpact // continuous test sequence to the client rather than three discrete test runs const size_t totalNumTestRuns = includedSelectedTestTargets.size() + draftedTestTargets.size() + includedDiscardedTestTargets.size(); TestRunCompleteCallbackHandler testRunCompleteHandler(totalNumTestRuns, testCompleteCallback); - - // Run the selected test targets and collect the test run results - const Timer selectedTestRunTimer; - const auto [selectedResult, selectedTestJobs] = m_testEngine->InstrumentedRun( - includedSelectedTestTargets, - m_testShardingPolicy, - m_executionFailurePolicy, - m_integrationFailurePolicy, - m_testFailurePolicy, - m_targetOutputCapture, - testTargetTimeout, - sequenceTimeout, - AZStd::ref(testRunCompleteHandler)); - const auto selectedTestRunDuration = selectedTestRunTimer.GetElapsedMs(); - - // Carry the remaining global sequence time over to the discarded test run - if (globalTimeout.has_value()) + + // Functor for running instrumented test targets + const auto instrumentedTestRun = + [this, &testTargetTimeout, &sequenceTimeout, &testRunCompleteHandler](const AZStd::vector& testsTargets) { - const auto elapsed = selectedTestRunDuration; - sequenceTimeout = elapsed < globalTimeout.value() ? globalTimeout.value() - elapsed : AZStd::chrono::milliseconds(0); + return m_testEngine->InstrumentedRun( + testsTargets, + m_testShardingPolicy, + m_executionFailurePolicy, + m_integrationFailurePolicy, + m_testFailurePolicy, + m_targetOutputCapture, + testTargetTimeout, + sequenceTimeout, + AZStd::ref(testRunCompleteHandler)); + }; + + // Functor for running uninstrumented test targets + const auto regularTestRun = + [this, &testTargetTimeout, &sequenceTimeout, &testRunCompleteHandler](const AZStd::vector& testsTargets) + { + return m_testEngine->RegularRun( + testsTargets, + m_testShardingPolicy, + m_executionFailurePolicy, + m_testFailurePolicy, + m_targetOutputCapture, + testTargetTimeout, + sequenceTimeout, + AZStd::ref(testRunCompleteHandler)); + }; + + // Functor for running instrumented test targets + const auto gatherTestRunData = [&sequenceTimer] + (const AZStd::vector& testsTargets, const auto& testRunner, auto& testRunData) + { + const Timer testRunTimer; + testRunData.m_relativeStartTime = testRunTimer.GetStartTimePointRelative(sequenceTimer); + auto [result, jobs] = testRunner(testsTargets); + testRunData.m_result = result; + testRunData.m_jobs = AZStd::move(jobs); + testRunData.m_duration = testRunTimer.GetElapsedMs(); + }; + + if (!includedSelectedTestTargets.empty()) + { + // Run the selected test targets and collect the test run results + gatherTestRunData(includedSelectedTestTargets, instrumentedTestRun, selectedTestRunData); + + // Carry the remaining global sequence time over to the discarded test run + if (globalTimeout.has_value()) + { + const auto elapsed = selectedTestRunData.m_duration; + sequenceTimeout = elapsed < globalTimeout.value() ? globalTimeout.value() - elapsed : AZStd::chrono::milliseconds(0); + } } - // Run the discarded test targets and collect the test run results - const Timer discardedTestRunTimer; - const auto [discardedResult, discardedTestJobs] = m_testEngine->RegularRun( - includedDiscardedTestTargets, - m_testShardingPolicy, - m_executionFailurePolicy, - m_testFailurePolicy, - m_targetOutputCapture, - testTargetTimeout, - sequenceTimeout, - AZStd::ref(testRunCompleteHandler)); - const auto discardedTestRunDuration = discardedTestRunTimer.GetElapsedMs(); - - // Carry the remaining global sequence time over to the drafted test run - if (globalTimeout.has_value()) + if (!includedDiscardedTestTargets.empty()) { - const auto elapsed = selectedTestRunDuration + discardedTestRunDuration; - sequenceTimeout = elapsed < globalTimeout.value() ? globalTimeout.value() - elapsed : AZStd::chrono::milliseconds(0); + // Run the discarded test targets and collect the test run results + gatherTestRunData(includedDiscardedTestTargets, regularTestRun, discardedTestRunData); + + // Carry the remaining global sequence time over to the drafted test run + if (globalTimeout.has_value()) + { + const auto elapsed = selectedTestRunData.m_duration + discardedTestRunData.m_duration; + sequenceTimeout = elapsed < globalTimeout.value() ? globalTimeout.value() - elapsed : AZStd::chrono::milliseconds(0); + } } - // Run the drafted test targets and collect the test run results - const Timer draftedTestRunTimer; - const auto [draftedResult, draftedTestJobs] = m_testEngine->InstrumentedRun( - draftedTestTargets, - m_testShardingPolicy, - m_executionFailurePolicy, - m_integrationFailurePolicy, - m_testFailurePolicy, - m_targetOutputCapture, - testTargetTimeout, - sequenceTimeout, - AZStd::ref(testRunCompleteHandler)); - const auto draftedTestRunDuration = draftedTestRunTimer.GetElapsedMs(); + if (!draftedTestTargets.empty()) + { + // Run the drafted test targets and collect the test run results + gatherTestRunData(draftedTestTargets, instrumentedTestRun, draftedTestRunData); + } // Generate the sequence report for the client const auto sequenceReport = Client::SafeImpactAnalysisSequenceReport( + m_maxConcurrency, + testTargetTimeout, + globalTimeout, + GenerateSafeImpactAnalysisSequencePolicyState(testPrioritizationPolicy), m_suiteFilter, selectedTests, discardedTests, draftedTests, - GenerateTestRunReport(selectedResult, selectedTestRunTimer.GetStartTimePointRelative(sequenceTimer), selectedTestRunDuration, selectedTestJobs), - GenerateTestRunReport(discardedResult, discardedTestRunTimer.GetStartTimePointRelative(sequenceTimer), discardedTestRunDuration, discardedTestJobs), - GenerateTestRunReport(draftedResult, draftedTestRunTimer.GetStartTimePointRelative(sequenceTimer), draftedTestRunDuration, draftedTestJobs)); + GenerateTestRunReport( + selectedTestRunData.m_result, + selectedTestRunData.m_relativeStartTime, + selectedTestRunData.m_duration, + selectedTestRunData.m_jobs), + GenerateTestRunReport( + discardedTestRunData.m_result, + discardedTestRunData.m_relativeStartTime, + discardedTestRunData.m_duration, + discardedTestRunData.m_jobs), + GenerateTestRunReport( + draftedTestRunData.m_result, + draftedTestRunData.m_relativeStartTime, + draftedTestRunData.m_duration, + draftedTestRunData.m_jobs)); // Inform the client that the sequence has ended if (testSequenceEndCallback.has_value()) @@ -747,15 +881,15 @@ namespace TestImpact (*testSequenceEndCallback)(sequenceReport); } - UpdateAndSerializeDynamicDependencyMap(ConcatenateVectors(selectedTestJobs, draftedTestJobs)); + UpdateAndSerializeDynamicDependencyMap(ConcatenateVectors(selectedTestRunData.m_jobs, draftedTestRunData.m_jobs)); return sequenceReport; } - Client::SequenceReport Runtime::SeededTestSequence( + Client::SeedSequenceReport Runtime::SeededTestSequence( AZStd::optional testTargetTimeout, AZStd::optional globalTimeout, AZStd::optional testSequenceStartCallback, - AZStd::optional> testSequenceEndCallback, + AZStd::optional> testSequenceEndCallback, AZStd::optional testCompleteCallback) { const Timer sequenceTimer; @@ -799,7 +933,11 @@ namespace TestImpact const auto testRunDuration = testRunTimer.GetElapsedMs(); // Generate the sequence report for the client - const auto sequenceReport = Client::SequenceReport( + const auto sequenceReport = Client::SeedSequenceReport( + m_maxConcurrency, + testTargetTimeout, + globalTimeout, + GenerateSequencePolicyState(), m_suiteFilter, selectedTests, GenerateTestRunReport(result, testRunTimer.GetStartTimePointRelative(sequenceTimer), testRunDuration, testJobs)); diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Source/TestImpactRuntimeUtils.cpp b/Code/Tools/TestImpactFramework/Runtime/Code/Source/TestImpactRuntimeUtils.cpp index 0c61c5f426..fec3d90471 100644 --- a/Code/Tools/TestImpactFramework/Runtime/Code/Source/TestImpactRuntimeUtils.cpp +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Source/TestImpactRuntimeUtils.cpp @@ -6,7 +6,7 @@ * */ -#include +#include #include #include diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Source/TestImpactRuntimeUtils.h b/Code/Tools/TestImpactFramework/Runtime/Code/Source/TestImpactRuntimeUtils.h index 33e15bfd20..23d78ee329 100644 --- a/Code/Tools/TestImpactFramework/Runtime/Code/Source/TestImpactRuntimeUtils.h +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Source/TestImpactRuntimeUtils.h @@ -38,34 +38,44 @@ namespace TestImpact //! Extracts the name information from the specified test targets. AZStd::vector ExtractTestTargetNames(const AZStd::vector& testTargets); - //! Generates a test run failure report from the specified test engine job information. + //! Generates the test suites from the specified test engine job information. //! @tparam TestJob The test engine job type. template - AZStd::vector GenerateTestCaseFailures(const TestJob& testJob) + AZStd::vector GenerateClientTests(const TestJob& testJob) { - AZStd::vector testCaseFailures; + AZStd::vector tests; if (testJob.GetTestRun().has_value()) { for (const auto& testSuite : testJob.GetTestRun()->GetTestSuites()) { - AZStd::vector testFailures; for (const auto& testCase : testSuite.m_tests) { - if (testCase.m_result.value_or(TestRunResult::Passed) == TestRunResult::Failed) + auto result = Client::TestResult::NotRun; + if (testCase.m_result.has_value()) { - testFailures.push_back(Client::TestFailure(testCase.m_name, "No error message retrieved")); + if (testCase.m_result.value() == TestRunResult::Passed) + { + result = Client::TestResult::Passed; + } + else if (testCase.m_result.value() == TestRunResult::Failed) + { + result = Client::TestResult::Failed; + } + else + { + throw RuntimeException(AZStd::string::format( + "Unexpected test run result: %u", aznumeric_cast(testCase.m_result.value()))); + } } - } - - if (!testFailures.empty()) - { - testCaseFailures.push_back(Client::TestCaseFailure(testSuite.m_name, AZStd::move(testFailures))); + + const auto name = AZStd::string::format("%s.%s", testSuite.m_name.c_str(), testCase.m_name.c_str()); + tests.push_back(Client::Test(name, result)); } } } - return testCaseFailures; + return tests; } template @@ -75,11 +85,11 @@ namespace TestImpact AZStd::chrono::milliseconds duration, const AZStd::vector& testJobs) { - AZStd::vector passingTests; - AZStd::vector failingTests; - AZStd::vector executionFailureTests; - AZStd::vector timedOutTests; - AZStd::vector unexecutedTests; + AZStd::vector passingTests; + AZStd::vector failingTests; + AZStd::vector executionFailureTests; + AZStd::vector timedOutTests; + AZStd::vector unexecutedTests; for (const auto& testJob : testJobs) { @@ -88,7 +98,7 @@ namespace TestImpact AZStd::chrono::high_resolution_clock::time_point() + AZStd::chrono::duration_cast(testJob.GetStartTime() - startTime); - Client::TestRun clientTestRun( + Client::TestRunBase clientTestRun( testJob.GetTestTarget()->GetName(), testJob.GetCommandString(), relativeStartTime, testJob.GetDuration(), testJob.GetTestResult()); @@ -96,27 +106,27 @@ namespace TestImpact { case Client::TestRunResult::FailedToExecute: { - executionFailureTests.push_back(clientTestRun); + executionFailureTests.emplace_back(AZStd::move(clientTestRun)); break; } case Client::TestRunResult::NotRun: { - unexecutedTests.push_back(clientTestRun); + unexecutedTests.emplace_back(AZStd::move(clientTestRun)); break; } case Client::TestRunResult::Timeout: { - timedOutTests.push_back(clientTestRun); + timedOutTests.emplace_back(AZStd::move(clientTestRun)); break; } case Client::TestRunResult::AllTestsPass: { - passingTests.push_back(clientTestRun); + passingTests.emplace_back(AZStd::move(clientTestRun), GenerateClientTests(testJob)); break; } case Client::TestRunResult::TestFailures: { - failingTests.emplace_back(AZStd::move(clientTestRun), GenerateTestCaseFailures(testJob)); + failingTests.emplace_back(AZStd::move(clientTestRun), GenerateClientTests(testJob)); break; } default: diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/Source/TestImpactUtils.cpp b/Code/Tools/TestImpactFramework/Runtime/Code/Source/TestImpactUtils.cpp new file mode 100644 index 0000000000..993aee9af5 --- /dev/null +++ b/Code/Tools/TestImpactFramework/Runtime/Code/Source/TestImpactUtils.cpp @@ -0,0 +1,244 @@ +/* + * 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 +#include + +#include + +namespace TestImpact +{ + //! Delete the files that match the pattern from the specified directory. + //! @param path The path to the directory to pattern match the files for deletion. + //! @param pattern The pattern to match files for deletion. + size_t DeleteFiles(const RepoPath& path, const AZStd::string& pattern) + { + size_t numFilesDeleted = 0; + + AZ::IO::SystemFile::FindFiles( + AZStd::string::format("%s/%s", path.c_str(), pattern.c_str()).c_str(), + [&path, &numFilesDeleted](const char* file, bool isFile) + { + if (isFile) + { + AZ::IO::SystemFile::Delete(AZStd::string::format("%s/%s", path.c_str(), file).c_str()); + numFilesDeleted++; + } + + return true; + }); + + return numFilesDeleted; + } + + //! Deletes the specified file. + void DeleteFile(const RepoPath& file) + { + DeleteFiles(file.ParentPath(), file.Filename().Native()); + } + + //! User-friendly names for the test suite types. + AZStd::string SuiteTypeAsString(SuiteType suiteType) + { + switch (suiteType) + { + case SuiteType::Main: + return "main"; + case SuiteType::Periodic: + return "periodic"; + case SuiteType::Sandbox: + return "sandbox"; + default: + throw(Exception("Unexpected suite type")); + } + } + + AZStd::string SequenceReportTypeAsString(Client::SequenceReportType type) + { + switch (type) + { + case Client::SequenceReportType::RegularSequence: + return "regular"; + case Client::SequenceReportType::SeedSequence: + return "seed"; + case Client::SequenceReportType::ImpactAnalysisSequence: + return "impact_analysis"; + case Client::SequenceReportType::SafeImpactAnalysisSequence: + return "safe_impact_analysis"; + default: + throw(Exception(AZStd::string::format("Unexpected sequence report type: %u", aznumeric_cast(type)))); + } + } + + AZStd::string TestSequenceResultAsString(TestSequenceResult result) + { + switch (result) + { + case TestSequenceResult::Failure: + return "failure"; + case TestSequenceResult::Success: + return "success"; + case TestSequenceResult::Timeout: + return "timeout"; + default: + throw(Exception(AZStd::string::format("Unexpected test sequence result: %u", aznumeric_cast(result)))); + } + } + + AZStd::string TestRunResultAsString(Client::TestRunResult result) + { + switch (result) + { + case Client::TestRunResult::AllTestsPass: + return "all_tests_pass"; + case Client::TestRunResult::FailedToExecute: + return "failed_to_execute"; + case Client::TestRunResult::NotRun: + return "not_run"; + case Client::TestRunResult::TestFailures: + return "test_failures"; + case Client::TestRunResult::Timeout: + return "timeout"; + default: + throw(Exception(AZStd::string::format("Unexpected test run result: %u", aznumeric_cast(result)))); + } + } + + AZStd::string ExecutionFailurePolicyAsString(Policy::ExecutionFailure executionFailurePolicy) + { + switch (executionFailurePolicy) + { + case Policy::ExecutionFailure::Abort: + return "abort"; + case Policy::ExecutionFailure::Continue: + return "continue"; + case Policy::ExecutionFailure::Ignore: + return "ignore"; + default: + throw(Exception( + AZStd::string::format("Unexpected execution failure policy: %u", aznumeric_cast(executionFailurePolicy)))); + } + } + + AZStd::string FailedTestCoveragePolicyAsString(Policy::FailedTestCoverage failedTestCoveragePolicy) + { + switch (failedTestCoveragePolicy) + { + case Policy::FailedTestCoverage::Discard: + return "discard"; + case Policy::FailedTestCoverage::Keep: + return "keep"; + default: + throw(Exception( + AZStd::string::format("Unexpected failed test coverage policy: %u", aznumeric_cast(failedTestCoveragePolicy)))); + } + } + + AZStd::string TestPrioritizationPolicyAsString(Policy::TestPrioritization testPrioritizationPolicy) + { + switch (testPrioritizationPolicy) + { + case Policy::TestPrioritization::DependencyLocality: + return "dependency_locality"; + case Policy::TestPrioritization::None: + return "none"; + default: + throw(Exception( + AZStd::string::format("Unexpected test prioritization policy: %u", aznumeric_cast(testPrioritizationPolicy)))); + } + } + + AZStd::string TestFailurePolicyAsString(Policy::TestFailure testFailurePolicy) + { + switch (testFailurePolicy) + { + case Policy::TestFailure::Abort: + return "abort"; + case Policy::TestFailure::Continue: + return "continue"; + default: + throw( + Exception(AZStd::string::format("Unexpected test failure policy: %u", aznumeric_cast(testFailurePolicy)))); + } + } + + AZStd::string IntegrityFailurePolicyAsString(Policy::IntegrityFailure integrityFailurePolicy) + { + switch (integrityFailurePolicy) + { + case Policy::IntegrityFailure::Abort: + return "abort"; + case Policy::IntegrityFailure::Continue: + return "continue"; + default: + throw(Exception( + AZStd::string::format("Unexpected integration failure policy: %u", aznumeric_cast(integrityFailurePolicy)))); + } + } + + AZStd::string DynamicDependencyMapPolicyAsString(Policy::DynamicDependencyMap dynamicDependencyMapPolicy) + { + switch (dynamicDependencyMapPolicy) + { + case Policy::DynamicDependencyMap::Discard: + return "discard"; + case Policy::DynamicDependencyMap::Update: + return "update"; + default: + throw(Exception(AZStd::string::format( + "Unexpected dynamic dependency map policy: %u", aznumeric_cast(dynamicDependencyMapPolicy)))); + } + } + + AZStd::string TestShardingPolicyAsString(Policy::TestSharding testShardingPolicy) + { + switch (testShardingPolicy) + { + case Policy::TestSharding::Always: + return "always"; + case Policy::TestSharding::Never: + return "never"; + default: + throw(Exception( + AZStd::string::format("Unexpected test sharding policy: %u", aznumeric_cast(testShardingPolicy)))); + } + } + + AZStd::string TargetOutputCapturePolicyAsString(Policy::TargetOutputCapture targetOutputCapturePolicy) + { + switch (targetOutputCapturePolicy) + { + case Policy::TargetOutputCapture::File: + return "file"; + case Policy::TargetOutputCapture::None: + return "none"; + case Policy::TargetOutputCapture::StdOut: + return "stdout"; + case Policy::TargetOutputCapture::StdOutAndFile: + return "stdout_file"; + default: + throw(Exception( + AZStd::string::format("Unexpected target output capture policy: %u", aznumeric_cast(targetOutputCapturePolicy)))); + } + } + + AZStd::string ClientTestResultAsString(Client::TestResult result) + { + switch (result) + { + case Client::TestResult::Failed: + return "failed"; + case Client::TestResult::NotRun: + return "not_run"; + case Client::TestResult::Passed: + return "passed"; + default: + throw(Exception(AZStd::string::format("Unexpected client test case result: %u", aznumeric_cast(result)))); + } + } +} // namespace TestImpact diff --git a/Code/Tools/TestImpactFramework/Runtime/Code/testimpactframework_runtime_files.cmake b/Code/Tools/TestImpactFramework/Runtime/Code/testimpactframework_runtime_files.cmake index 76673d9f40..75f253ad27 100644 --- a/Code/Tools/TestImpactFramework/Runtime/Code/testimpactframework_runtime_files.cmake +++ b/Code/Tools/TestImpactFramework/Runtime/Code/testimpactframework_runtime_files.cmake @@ -16,11 +16,14 @@ set(FILES Include/TestImpactFramework/TestImpactChangelist.h Include/TestImpactFramework/TestImpactChangelistSerializer.h Include/TestImpactFramework/TestImpactChangelistException.h + Include/TestImpactFramework/TestImpactPolicy.h Include/TestImpactFramework/TestImpactTestSequence.h Include/TestImpactFramework/TestImpactClientTestSelection.h Include/TestImpactFramework/TestImpactClientTestRun.h Include/TestImpactFramework/TestImpactClientSequenceReport.h - Include/TestImpactFramework/TestImpactFileUtils.h + Include/TestImpactFramework/TestImpactUtils.h + Include/TestImpactFramework/TestImpactClientSequenceReportSerializer.h + Include/TestImpactFramework/TestImpactSequenceReportException.h Source/Artifact/TestImpactArtifactException.h Source/Artifact/Factory/TestImpactBuildTargetDescriptorFactory.cpp Source/Artifact/Factory/TestImpactBuildTargetDescriptorFactory.h @@ -125,5 +128,7 @@ set(FILES Source/TestImpactClientTestRun.cpp Source/TestImpactClientSequenceReport.cpp Source/TestImpactChangeListSerializer.cpp + Source/TestImpactClientSequenceReportSerializer.cpp Source/TestImpactRepoPath.cpp + Source/TestImpactUtils.cpp ) diff --git a/cmake/TestImpactFramework/ConsoleFrontendConfig.in b/cmake/TestImpactFramework/ConsoleFrontendConfig.in index e4111fb9cd..17fbd217d7 100644 --- a/cmake/TestImpactFramework/ConsoleFrontendConfig.in +++ b/cmake/TestImpactFramework/ConsoleFrontendConfig.in @@ -1,7 +1,8 @@ { "meta": { "platform": "${platform}", - "timestamp": "${timestamp}" + "timestamp": "${timestamp}", + "build_config": "${build_config}" }, "jenkins": { "use_test_impact_analysis": ${use_tiaf} @@ -32,8 +33,7 @@ "historic": { "root": "${historic_dir}", "relative_paths": { - "last_run_hash_file": "last_run.hash", - "last_build_target_list_file": "LastRunBuildTargets.json" + "data": "historic_data.json" } } }, diff --git a/cmake/TestImpactFramework/LYTestImpactFramework.cmake b/cmake/TestImpactFramework/LYTestImpactFramework.cmake index 61cc8c200a..7dd2617582 100644 --- a/cmake/TestImpactFramework/LYTestImpactFramework.cmake +++ b/cmake/TestImpactFramework/LYTestImpactFramework.cmake @@ -22,10 +22,10 @@ set(LY_TEST_IMPACT_CONSOLE_TARGET "TestImpact.Frontend.Console") set(LY_TEST_IMPACT_WORKING_DIR "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/TestImpactFramework") # Directory for artifacts generated at runtime -set(LY_TEST_IMPACT_TEMP_DIR "${LY_TEST_IMPACT_WORKING_DIR}/Temp") +set(LY_TEST_IMPACT_TEMP_DIR "${LY_TEST_IMPACT_WORKING_DIR}/$/Temp") # Directory for files that persist between runtime runs -set(LY_TEST_IMPACT_PERSISTENT_DIR "${LY_TEST_IMPACT_WORKING_DIR}/Persistent") +set(LY_TEST_IMPACT_PERSISTENT_DIR "${LY_TEST_IMPACT_WORKING_DIR}/$/Persistent") # Directory for static artifacts produced as part of the build system generation process set(LY_TEST_IMPACT_ARTIFACT_DIR "${LY_TEST_IMPACT_WORKING_DIR}/Artifact") @@ -43,7 +43,7 @@ set(LY_TEST_IMPACT_TEST_TYPE_FILE "${LY_TEST_IMPACT_ARTIFACT_DIR}/TestType/All.t set(LY_TEST_IMPACT_GEM_TARGET_FILE "${LY_TEST_IMPACT_ARTIFACT_DIR}/BuildType/All.gems") # Path to the config file for each build configuration -set(LY_TEST_IMPACT_CONFIG_FILE_PATH "${LY_TEST_IMPACT_PERSISTENT_DIR}/tiaf.$.json") +set(LY_TEST_IMPACT_CONFIG_FILE_PATH "${LY_TEST_IMPACT_PERSISTENT_DIR}/tiaf.json") # Preprocessor directive for the config file path set(LY_TEST_IMPACT_CONFIG_FILE_PATH_DEFINITION "LY_TEST_IMPACT_DEFAULT_CONFIG_FILE=\"${LY_TEST_IMPACT_CONFIG_FILE_PATH}\"") @@ -379,6 +379,9 @@ function(ly_test_impact_write_config_file CONFIG_TEMPLATE_FILE BIN_DIR) # Timestamp this config file was generated at string(TIMESTAMP timestamp "%Y-%m-%d %H:%M:%S") + # Build configuration this config file is being generated for + set(build_config "$") + # Instrumentation binary if(NOT LY_TEST_IMPACT_INSTRUMENTATION_BIN) # No binary specified is not an error, it just means that the test impact analysis part of the framework is disabled diff --git a/scripts/build/Platform/Windows/build_config.json b/scripts/build/Platform/Windows/build_config.json index 235e1406ab..8ac0b5241c 100644 --- a/scripts/build/Platform/Windows/build_config.json +++ b/scripts/build/Platform/Windows/build_config.json @@ -89,7 +89,7 @@ "CONFIGURATION": "profile", "SCRIPT_PATH": "scripts/build/TestImpactAnalysis/tiaf_driver.py", "SCRIPT_PARAMETERS": - "--config=\"!OUTPUT_DIRECTORY!/bin/TestImpactFramework/persistent/tiaf.profile.json\" --suite=main --test-failure-policy=continue --src-branch=!BRANCH_NAME! --dst-branch=!CHANGE_TARGET! --pipeline=!PIPELINE_NAME! --dest-commit=!CHANGE_ID! --seeding-branches=!BUILD_SNAPSHOTS! --seeding-pipelines=default" + "--config=\"!OUTPUT_DIRECTORY!/bin/TestImpactFramework/profile/Persistent/tiaf.json\" --src-branch=!BRANCH_NAME! --dst-branch=!BRANCH_NAME! --commit=!CHANGE_ID! --s3-bucket=!TEST_IMPACT_S3_BUCKET! --mars-index-prefix=jonawals --suite=main --test-failure-policy=continue" } }, "debug_vs2019": { diff --git a/scripts/build/TestImpactAnalysis/git_utils.py b/scripts/build/TestImpactAnalysis/git_utils.py index ec31ab1f53..04d994ba4a 100644 --- a/scripts/build/TestImpactAnalysis/git_utils.py +++ b/scripts/build/TestImpactAnalysis/git_utils.py @@ -6,34 +6,67 @@ # # -import os import subprocess import git +import pathlib -# Returns True if the dst commit descends from the src commit, otherwise False -def is_descendent(src_commit_hash, dst_commit_hash): - if src_commit_hash is None or dst_commit_hash is None: - return False - result = subprocess.run(["git", "merge-base", "--is-ancestor", src_commit_hash, dst_commit_hash]) - return result.returncode == 0 - -# Attempts to create a diff from the src and dst commits and write to the specified output file -def create_diff_file(src_commit_hash, dst_commit_hash, output_path): - if os.path.isfile(output_path): - os.remove(output_path) - os.makedirs(os.path.dirname(output_path), exist_ok=True) - # git diff will only write to the output file if both commit hashes are valid - subprocess.run(["git", "diff", "--name-status", f"--output={output_path}", src_commit_hash, dst_commit_hash]) - if not os.path.isfile(output_path): - raise FileNotFoundError(f"Source commit '{src_commit_hash}' and/or destination commit '{dst_commit_hash}' are invalid") - -# Basic representation of a repository +# Basic representation of a git repository class Repo: - def __init__(self, repo_path): - self.__repo = git.Repo(repo_path) + def __init__(self, repo_path: str): + self._repo = git.Repo(repo_path) # Returns the current branch @property def current_branch(self): - branch = self.__repo.active_branch + branch = self._repo.active_branch return branch.name + + def create_diff_file(self, src_commit_hash: str, dst_commit_hash: str, output_path: pathlib.Path): + """ + Attempts to create a diff from the src and dst commits and write to the specified output file. + + @param src_commit_hash: The hash for the source commit. + @param dst_commit_hash: The hash for the destination commit. + @param output_path: The path to the file to write the diff to. + """ + + try: + # Remove the existing file (if any) and create the parent directory + output_path.unlink(missing_ok=True) + output_path.parent.mkdir(exist_ok=True) + except EnvironmentError as e: + raise RuntimeError(f"Could not create path for output file '{output_path}'") + + # git diff will only write to the output file if both commit hashes are valid + subprocess.run(["git", "diff", "--name-status", f"--output={output_path}", src_commit_hash, dst_commit_hash]) + if not output_path.is_file(): + raise RuntimeError(f"Source commit '{src_commit_hash}' and/or destination commit '{dst_commit_hash}' are invalid") + + def is_descendent(self, src_commit_hash: str, dst_commit_hash: str): + """ + Determines whether or not dst_commit is a descendent of src_commit. + + @param src_commit_hash: The hash for the source commit. + @param dst_commit_hash: The hash for the destination commit. + @return: True if the dst commit descends from the src commit, otherwise False. + """ + + if not src_commit_hash and not dst_commit_hash: + return False + result = subprocess.run(["git", "merge-base", "--is-ancestor", src_commit_hash, dst_commit_hash]) + return result.returncode == 0 + + # Returns the distance between two commits + def commit_distance(self, src_commit_hash: str, dst_commit_hash: str): + """ + Determines the number of commits between src_commit and dst_commit. + + @param src_commit_hash: The hash for the source commit. + @param dst_commit_hash: The hash for the destination commit. + @return: The distance between src_commit and dst_commit (if both are valid commits), otherwise None. + """ + + if not src_commit_hash and not dst_commit_hash: + return None + commits = self._repo.iter_commits(src_commit_hash + '..' + dst_commit_hash) + return len(list(commits)) diff --git a/scripts/build/TestImpactAnalysis/mars_utils.py b/scripts/build/TestImpactAnalysis/mars_utils.py new file mode 100644 index 0000000000..d25aafb664 --- /dev/null +++ b/scripts/build/TestImpactAnalysis/mars_utils.py @@ -0,0 +1,452 @@ +# +# 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 +# +# + +import datetime +import json +import socket +from tiaf_logger import get_logger + +logger = get_logger(__file__) + +MARS_JOB_KEY = "job" +SRC_COMMIT_KEY = "src_commit" +DST_COMMIT_KEY = "src_commit" +COMMIT_DISTANCE_KEY = "commit_distance" +SRC_BRANCH_KEY = "src_branch" +DST_BRANCH_KEY = "dst_branch" +SUITE_KEY = "suite" +SOURCE_OF_TRUTH_BRANCH_KEY = "source_of_truth_branch" +IS_SOURCE_OF_TRUTH_BRANCH_KEY = "is_source_of_truth_branch" +USE_TEST_IMPACT_ANALYSIS_KEY = "use_test_impact_analysis" +HAS_CHANGE_LIST_KEY = "has_change_list" +HAS_HISTORIC_DATA_KEY = "has_historic_data" +S3_BUCKET_KEY = "s3_bucket" +DRIVER_ARGS_KEY = "driver_args" +RUNTIME_ARGS_KEY = "runtime_args" +RUNTIME_RETURN_CODE_KEY = "return_code" +NAME_KEY = "name" +RESULT_KEY = "result" +NUM_PASSING_TESTS_KEY = "num_passing_tests" +NUM_FAILING_TESTS_KEY = "num_failing_tests" +NUM_DISABLED_TESTS_KEY = "num_disabled_tests" +COMMAND_ARGS_STRING = "command_args" +NUM_PASSING_TEST_RUNS_KEY = "num_passing_test_runs" +NUM_FAILING_TEST_RUNS_KEY = "num_failing_test_runs" +NUM_EXECUTION_FAILURE_TEST_RUNS_KEY = "num_execution_failure_test_runs" +NUM_TIMED_OUT_TEST_RUNS_KEY = "num_timed_out_test_runs" +NUM_UNEXECUTED_TEST_RUNS_KEY = "num_unexecuted_test_runs" +TOTAL_NUM_PASSING_TESTS_KEY = "total_num_passing_tests" +TOTAL_NUM_FAILING_TESTS_KEY = "total_num_failing_tests" +TOTAL_NUM_DISABLED_TESTS_KEY = "total_num_disabled_tests" +START_TIME_KEY = "start_time" +END_TIME_KEY = "end_time" +DURATION_KEY = "duration" +INCLUDED_TEST_RUNS_KEY = "included_test_runs" +EXCLUDED_TEST_RUNS_KEY = "excluded_test_runs" +NUM_INCLUDED_TEST_RUNS_KEY = "num_included_test_runs" +NUM_EXCLUDED_TEST_RUNS_KEY = "num_excluded_test_runs" +TOTAL_NUM_TEST_RUNS_KEY = "total_num_test_runs" +PASSING_TEST_RUNS_KEY = "passing_test_runs" +FAILING_TEST_RUNS_KEY = "failing_test_runs" +EXECUTION_FAILURE_TEST_RUNS_KEY = "execution_failure_test_runs" +TIMED_OUT_TEST_RUNS_KEY = "timed_out_test_runs" +UNEXECUTED_TEST_RUNS_KEY = "unexecuted_test_runs" +TOTAL_NUM_PASSING_TEST_RUNS_KEY = "total_num_passing_test_runs" +TOTAL_NUM_FAILING_TEST_RUNS_KEY = "total_num_failing_test_runs" +TOTAL_NUM_EXECUTION_FAILURE_TEST_RUNS_KEY = "total_num_execution_failure_test_runs" +TOTAL_NUM_TIMED_OUT_TEST_RUNS_KEY = "total_num_timed_out_test_runs" +TOTAL_NUM_UNEXECUTED_TEST_RUNS_KEY = "total_num_unexecuted_test_runs" +SEQUENCE_TYPE_KEY = "type" +IMPACT_ANALYSIS_SEQUENCE_TYPE_KEY = "impact_analysis" +SAFE_IMPACT_ANALYSIS_SEQUENCE_TYPE_KEY = "safe_impact_analysis" +SEED_SEQUENCE_TYPE_KEY = "seed" +TEST_TARGET_TIMEOUT_KEY = "test_target_timeout" +GLOBAL_TIMEOUT_KEY = "global_timeout" +MAX_CONCURRENCY_KEY = "max_concurrency" +SELECTED_KEY = "selected" +DRAFTED_KEY = "drafted" +DISCARDED_KEY = "discarded" +SELECTED_TEST_RUN_REPORT_KEY = "selected_test_run_report" +DISCARDED_TEST_RUN_REPORT_KEY = "discarded_test_run_report" +DRAFTED_TEST_RUN_REPORT_KEY = "drafted_test_run_report" +SELECTED_TEST_RUNS_KEY = "selected_test_runs" +DRAFTED_TEST_RUNS_KEY = "drafted_test_runs" +DISCARDED_TEST_RUNS_KEY = "discarded_test_runs" +INSTRUMENTATION_KEY = "instrumentation" +EFFICIENCY_KEY = "efficiency" +CONFIG_KEY = "config" +POLICY_KEY = "policy" +CHANGE_LIST_KEY = "change_list" +TEST_RUN_SELECTION_KEY = "test_run_selection" +DYNAMIC_DEPENDENCY_MAP_POLICY_KEY = "dynamic_dependency_map" +DYNAMIC_DEPENDENCY_MAP_POLICY_UPDATE_KEY = "update" +REPORT_KEY = "report" + +class FilebeatExn(Exception): + pass + +class FilebeatClient(object): + def __init__(self, host="127.0.0.1", port=9000, timeout=20): + self._filebeat_host = host + self._filebeat_port = port + self._socket_timeout = timeout + self._socket = None + + self._open_socket() + + def send_event(self, payload, index, timestamp=None, pipeline="filebeat"): + if timestamp is None: + timestamp = datetime.datetime.utcnow().timestamp() + + event = { + "index": index, + "timestamp": timestamp, + "pipeline": pipeline, + "payload": json.dumps(payload) + } + + # Serialise event, add new line and encode as UTF-8 before sending to Filebeat. + data = json.dumps(event, sort_keys=True) + "\n" + data = data.encode() + + #print(f"-> {data}") + self._send_data(data) + + def _open_socket(self): + logger.info(f"Connecting to Filebeat on {self._filebeat_host}:{self._filebeat_port}") + + self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self._socket.settimeout(self._socket_timeout) + + try: + self._socket.connect((self._filebeat_host, self._filebeat_port)) + except (ConnectionError, socket.timeout): + raise FilebeatExn("Failed to connect to Filebeat") from None + + def _send_data(self, data): + total_sent = 0 + + while total_sent < len(data): + try: + sent = self._socket.send(data[total_sent:]) + except BrokenPipeError: + logging.error("Filebeat socket closed by peer") + self._socket.close() + self._open_socket() + total_sent = 0 + else: + total_sent = total_sent + sent + +def format_timestamp(timestamp: float): + """ + Formats the given floating point timestamp into "yyyy-MM-dd'T'HH:mm:ss.SSSXX" format. + + @param timestamp: The timestamp to format. + @return: The formatted timestamp. + """ + return datetime.datetime.utcfromtimestamp(timestamp).strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3] + "Z" + +def generate_mars_timestamp(t0_offset_milliseconds: int, t0_timestamp: float): + """ + Generates a MARS timestamp in the format "yyyy-MM-dd'T'HH:mm:ss.SSSXX" by offsetting the T0 timestamp + by the specified amount of milliseconds. + + @param t0_offset_milliseconds: The amount of time to offset from T0. + @param t0_timestamp: The T0 timestamp that TIAF timings will be offst from. + @return: The formatted timestamp offset from T0 by the specified amount of milliseconds. + """ + + t0_offset_seconds = get_duration_in_seconds(t0_offset_milliseconds) + t0_offset_timestamp = t0_timestamp + t0_offset_seconds + return format_timestamp(t0_offset_timestamp) + +def get_duration_in_seconds(duration_in_milliseconds: int): + """ + Gets the specified duration in milliseconds (as used by TIAF) in seconds (as used my MARS documents). + + @param duration_in_milliseconds: The millisecond duration to transform into seconds. + @return: The duration in seconds. + """ + + return duration_in_milliseconds * 0.001 + +def generate_mars_job(tiaf_result, driver_args): + """ + Generates a MARS job document using the job meta-data used to drive the TIAF sequence. + + @param tiaf_result: The result object generated by the TIAF script. + @param driver_args: The arguments specified to the driver script. + @return: The MARS job document with the job meta-data. + """ + + mars_job = {key:tiaf_result[key] for key in + [ + SRC_COMMIT_KEY, + DST_COMMIT_KEY, + COMMIT_DISTANCE_KEY, + SRC_BRANCH_KEY, + DST_BRANCH_KEY, + SUITE_KEY, + SOURCE_OF_TRUTH_BRANCH_KEY, + IS_SOURCE_OF_TRUTH_BRANCH_KEY, + USE_TEST_IMPACT_ANALYSIS_KEY, + HAS_CHANGE_LIST_KEY, + HAS_HISTORIC_DATA_KEY, + S3_BUCKET_KEY, + RUNTIME_ARGS_KEY, + RUNTIME_RETURN_CODE_KEY + ]} + + mars_job[DRIVER_ARGS_KEY] = driver_args + return mars_job + +def generate_test_run_list(test_runs): + """ + Generates a list of test run name strings from the list of TIAF test runs. + + @param test_runs: The list of TIAF test runs to generate the name strings from. + @return: The list of test run name strings. + """ + + test_run_list = [] + for test_run in test_runs: + test_run_list.append(test_run[NAME_KEY]) + return test_run_list + +def generate_mars_test_run_selections(test_run_selection, test_run_report, t0_timestamp: float): + """ + Generates a list of MARS test run selections from a TIAF test run selection and report. + + @param test_run_selection: The TIAF test run selection. + @param test_run_report: The TIAF test run report. + @param t0_timestamp: The T0 timestamp that TIAF timings will be offst from. + @return: The list of TIAF test runs. + """ + + mars_test_run_selection = {key:test_run_report[key] for key in + [ + RESULT_KEY, + NUM_PASSING_TEST_RUNS_KEY, + NUM_FAILING_TEST_RUNS_KEY, + NUM_EXECUTION_FAILURE_TEST_RUNS_KEY, + NUM_TIMED_OUT_TEST_RUNS_KEY, + NUM_UNEXECUTED_TEST_RUNS_KEY, + TOTAL_NUM_PASSING_TESTS_KEY, + TOTAL_NUM_FAILING_TESTS_KEY, + TOTAL_NUM_DISABLED_TESTS_KEY + ]} + + mars_test_run_selection[START_TIME_KEY] = generate_mars_timestamp(test_run_report[START_TIME_KEY], t0_timestamp) + mars_test_run_selection[END_TIME_KEY] = generate_mars_timestamp(test_run_report[END_TIME_KEY], t0_timestamp) + mars_test_run_selection[DURATION_KEY] = get_duration_in_seconds(test_run_report[DURATION_KEY]) + + mars_test_run_selection[INCLUDED_TEST_RUNS_KEY] = test_run_selection[INCLUDED_TEST_RUNS_KEY] + mars_test_run_selection[EXCLUDED_TEST_RUNS_KEY] = test_run_selection[EXCLUDED_TEST_RUNS_KEY] + mars_test_run_selection[NUM_INCLUDED_TEST_RUNS_KEY] = test_run_selection[NUM_INCLUDED_TEST_RUNS_KEY] + mars_test_run_selection[NUM_EXCLUDED_TEST_RUNS_KEY] = test_run_selection[NUM_EXCLUDED_TEST_RUNS_KEY] + mars_test_run_selection[TOTAL_NUM_TEST_RUNS_KEY] = test_run_selection[TOTAL_NUM_TEST_RUNS_KEY] + + mars_test_run_selection[PASSING_TEST_RUNS_KEY] = generate_test_run_list(test_run_report[PASSING_TEST_RUNS_KEY]) + mars_test_run_selection[FAILING_TEST_RUNS_KEY] = generate_test_run_list(test_run_report[FAILING_TEST_RUNS_KEY]) + mars_test_run_selection[EXECUTION_FAILURE_TEST_RUNS_KEY] = generate_test_run_list(test_run_report[EXECUTION_FAILURE_TEST_RUNS_KEY]) + mars_test_run_selection[TIMED_OUT_TEST_RUNS_KEY] = generate_test_run_list(test_run_report[TIMED_OUT_TEST_RUNS_KEY]) + mars_test_run_selection[UNEXECUTED_TEST_RUNS_KEY] = generate_test_run_list(test_run_report[UNEXECUTED_TEST_RUNS_KEY]) + + return mars_test_run_selection + +def generate_test_runs_from_list(test_run_list: list): + """ + Generates a list of TIAF test runs from a list of test target name strings. + + @param test_run_list: The list of test target names. + @return: The list of TIAF test runs. + """ + + test_run_list = { + TOTAL_NUM_TEST_RUNS_KEY: len(test_run_list), + NUM_INCLUDED_TEST_RUNS_KEY: len(test_run_list), + NUM_EXCLUDED_TEST_RUNS_KEY: 0, + INCLUDED_TEST_RUNS_KEY: test_run_list, + EXCLUDED_TEST_RUNS_KEY: [] + } + + return test_run_list + +def generate_mars_sequence(sequence_report: dict, mars_job: dict, change_list:dict, t0_timestamp: float): + """ + Generates the MARS sequence document from the specified TIAF sequence report. + + @param sequence_report: The TIAF runtime sequence report. + @param mars_job: The MARS job for this sequence. + @param change_list: The change list for which the TIAF sequence was run. + @param t0_timestamp: The T0 timestamp that TIAF timings will be offst from. + @return: The MARS sequence document for the specified TIAF sequence report. + """ + + mars_sequence = {key:sequence_report[key] for key in + [ + SEQUENCE_TYPE_KEY, + RESULT_KEY, + POLICY_KEY, + TOTAL_NUM_TEST_RUNS_KEY, + TOTAL_NUM_PASSING_TEST_RUNS_KEY, + TOTAL_NUM_FAILING_TEST_RUNS_KEY, + TOTAL_NUM_EXECUTION_FAILURE_TEST_RUNS_KEY, + TOTAL_NUM_TIMED_OUT_TEST_RUNS_KEY, + TOTAL_NUM_UNEXECUTED_TEST_RUNS_KEY, + TOTAL_NUM_PASSING_TESTS_KEY, + TOTAL_NUM_FAILING_TESTS_KEY, + TOTAL_NUM_DISABLED_TESTS_KEY + ]} + + mars_sequence[START_TIME_KEY] = generate_mars_timestamp(sequence_report[START_TIME_KEY], t0_timestamp) + mars_sequence[END_TIME_KEY] = generate_mars_timestamp(sequence_report[END_TIME_KEY], t0_timestamp) + mars_sequence[DURATION_KEY] = get_duration_in_seconds(sequence_report[DURATION_KEY]) + + config = {key:sequence_report[key] for key in + [ + TEST_TARGET_TIMEOUT_KEY, + GLOBAL_TIMEOUT_KEY, + MAX_CONCURRENCY_KEY + ]} + + test_run_selection = {} + test_run_selection[SELECTED_KEY] = generate_mars_test_run_selections(sequence_report[SELECTED_TEST_RUNS_KEY], sequence_report[SELECTED_TEST_RUN_REPORT_KEY], t0_timestamp) + if sequence_report[SEQUENCE_TYPE_KEY] == IMPACT_ANALYSIS_SEQUENCE_TYPE_KEY or sequence_report[SEQUENCE_TYPE_KEY] == SAFE_IMPACT_ANALYSIS_SEQUENCE_TYPE_KEY: + total_test_runs = sequence_report[TOTAL_NUM_TEST_RUNS_KEY] + if total_test_runs > 0: + test_run_selection[SELECTED_KEY][EFFICIENCY_KEY] = (1.0 - (test_run_selection[SELECTED_KEY][TOTAL_NUM_TEST_RUNS_KEY] / total_test_runs)) * 100 + else: + test_run_selection[SELECTED_KEY][EFFICIENCY_KEY] = 100 + test_run_selection[DRAFTED_KEY] = generate_mars_test_run_selections(generate_test_runs_from_list(sequence_report[DRAFTED_TEST_RUNS_KEY]), sequence_report[DRAFTED_TEST_RUN_REPORT_KEY], t0_timestamp) + if sequence_report[SEQUENCE_TYPE_KEY] == SAFE_IMPACT_ANALYSIS_SEQUENCE_TYPE_KEY: + test_run_selection[DISCARDED_KEY] = generate_mars_test_run_selections(sequence_report[DISCARDED_TEST_RUNS_KEY], sequence_report[DISCARDED_TEST_RUN_REPORT_KEY], t0_timestamp) + else: + test_run_selection[SELECTED_KEY][EFFICIENCY_KEY] = 0 + + mars_sequence[MARS_JOB_KEY] = mars_job + mars_sequence[CONFIG_KEY] = config + mars_sequence[TEST_RUN_SELECTION_KEY] = test_run_selection + mars_sequence[CHANGE_LIST_KEY] = change_list + + return mars_sequence + +def extract_mars_test_target(test_run, instrumentation, mars_job, t0_timestamp: float): + """ + Extracts a MARS test target from the specified TIAF test run. + + @param test_run: The TIAF test run. + @param instrumentation: Flag specifying whether or not instrumentation was used for the test targets in this run. + @param mars_job: The MARS job for this test target. + @param t0_timestamp: The T0 timestamp that TIAF timings will be offst from. + @return: The MARS test target documents for the specified TIAF test target. + """ + + mars_test_run = {key:test_run[key] for key in + [ + NAME_KEY, + RESULT_KEY, + NUM_PASSING_TESTS_KEY, + NUM_FAILING_TESTS_KEY, + NUM_DISABLED_TESTS_KEY, + COMMAND_ARGS_STRING + ]} + + mars_test_run[START_TIME_KEY] = generate_mars_timestamp(test_run[START_TIME_KEY], t0_timestamp) + mars_test_run[END_TIME_KEY] = generate_mars_timestamp(test_run[END_TIME_KEY], t0_timestamp) + mars_test_run[DURATION_KEY] = get_duration_in_seconds(test_run[DURATION_KEY]) + + mars_test_run[MARS_JOB_KEY] = mars_job + mars_test_run[INSTRUMENTATION_KEY] = instrumentation + return mars_test_run + +def extract_mars_test_targets_from_report(test_run_report, instrumentation, mars_job, t0_timestamp: float): + """ + Extracts the MARS test targets from the specified TIAF test run report. + + @param test_run_report: The TIAF runtime test run report. + @param instrumentation: Flag specifying whether or not instrumentation was used for the test targets in this run. + @param mars_job: The MARS job for these test targets. + @param t0_timestamp: The T0 timestamp that TIAF timings will be offst from. + @return: The list of all MARS test target documents for the test targets in the TIAF test run report. + """ + + mars_test_targets = [] + + for test_run in test_run_report[PASSING_TEST_RUNS_KEY]: + mars_test_targets.append(extract_mars_test_target(test_run, instrumentation, mars_job, t0_timestamp)) + for test_run in test_run_report[FAILING_TEST_RUNS_KEY]: + mars_test_targets.append(extract_mars_test_target(test_run, instrumentation, mars_job, t0_timestamp)) + for test_run in test_run_report[EXECUTION_FAILURE_TEST_RUNS_KEY]: + mars_test_targets.append(extract_mars_test_target(test_run, instrumentation, mars_job, t0_timestamp)) + for test_run in test_run_report[TIMED_OUT_TEST_RUNS_KEY]: + mars_test_targets.append(extract_mars_test_target(test_run, instrumentation, mars_job, t0_timestamp)) + for test_run in test_run_report[UNEXECUTED_TEST_RUNS_KEY]: + mars_test_targets.append(extract_mars_test_target(test_run, instrumentation, mars_job, t0_timestamp)) + + return mars_test_targets + +def generate_mars_test_targets(sequence_report: dict, mars_job: dict, t0_timestamp: float): + """ + Generates a MARS test target document for each test target in the TIAF sequence report. + + @param sequence_report: The TIAF runtime sequence report. + @param mars_job: The MARS job for this sequence. + @param t0_timestamp: The T0 timestamp that TIAF timings will be offst from. + @return: The list of all MARS test target documents for the test targets in the TIAF sequence report. + """ + + mars_test_targets = [] + + # Determine whether or not the test targets were executed with instrumentation + if sequence_report[SEQUENCE_TYPE_KEY] == SEED_SEQUENCE_TYPE_KEY or sequence_report[SEQUENCE_TYPE_KEY] == SAFE_IMPACT_ANALYSIS_SEQUENCE_TYPE_KEY or (sequence_report[SEQUENCE_TYPE_KEY] == IMPACT_ANALYSIS_SEQUENCE_TYPE_KEY and sequence_report[POLICY_KEY][DYNAMIC_DEPENDENCY_MAP_POLICY_KEY] == DYNAMIC_DEPENDENCY_MAP_POLICY_UPDATE_KEY): + instrumentation = True + else: + instrumentation = False + + # Extract the MARS test target documents from each of the test run reports + mars_test_targets += extract_mars_test_targets_from_report(sequence_report[SELECTED_TEST_RUN_REPORT_KEY], instrumentation, mars_job, t0_timestamp) + if sequence_report[SEQUENCE_TYPE_KEY] == IMPACT_ANALYSIS_SEQUENCE_TYPE_KEY or sequence_report[SEQUENCE_TYPE_KEY] == SAFE_IMPACT_ANALYSIS_SEQUENCE_TYPE_KEY: + mars_test_targets += extract_mars_test_targets_from_report(sequence_report[DRAFTED_TEST_RUN_REPORT_KEY], instrumentation, mars_job, t0_timestamp) + if sequence_report[SEQUENCE_TYPE_KEY] == SAFE_IMPACT_ANALYSIS_SEQUENCE_TYPE_KEY: + mars_test_targets += extract_mars_test_targets_from_report(sequence_report[DISCARDED_TEST_RUN_REPORT_KEY], instrumentation, mars_job, t0_timestamp) + + return mars_test_targets + +def transmit_report_to_mars(mars_index_prefix: str, tiaf_result: dict, driver_args: list): + """ + Transforms the TIAF result into the appropriate MARS documents and transmits them to MARS. + + @param mars_index_prefix: The index prefix to be used for all MARS documents. + @param tiaf_result: The result object from the TIAF script. + @param driver_args: The arguments passed to the TIAF driver script. + """ + + try: + filebeat = FilebeatClient("localhost", 9000, 60) + + # T0 is the current timestamp that the report timings will be offset from + t0_timestamp = datetime.datetime.now().timestamp() + + # Generate and transmit the MARS job document + mars_job = generate_mars_job(tiaf_result, driver_args) + filebeat.send_event(mars_job, f"{mars_index_prefix}.tiaf.job") + + if tiaf_result[REPORT_KEY] is not None: + # Generate and transmit the MARS sequence document + mars_sequence = generate_mars_sequence(tiaf_result[REPORT_KEY], mars_job, tiaf_result[CHANGE_LIST_KEY], t0_timestamp) + filebeat.send_event(mars_sequence, f"{mars_index_prefix}.tiaf.sequence") + + # Generate and transmit the MARS test target documents + mars_test_targets = generate_mars_test_targets(tiaf_result[REPORT_KEY], mars_job, t0_timestamp) + for mars_test_target in mars_test_targets: + filebeat.send_event(mars_test_target, f"{mars_index_prefix}.tiaf.test_target") + except FilebeatExn as e: + logger.error(e) + except KeyError as e: + logger.error(f"The report does not contain the key {str(e)}.") \ No newline at end of file diff --git a/scripts/build/TestImpactAnalysis/tiaf.py b/scripts/build/TestImpactAnalysis/tiaf.py index ce869b975a..c368465ecc 100644 --- a/scripts/build/TestImpactAnalysis/tiaf.py +++ b/scripts/build/TestImpactAnalysis/tiaf.py @@ -6,237 +6,299 @@ # # -import os import json import subprocess import re -import git_utils +import uuid +import pathlib from git_utils import Repo -from enum import Enum +from tiaf_persistent_storage_local import PersistentStorageLocal +from tiaf_persistent_storage_s3 import PersistentStorageS3 +from tiaf_logger import get_logger -# Returns True if the specified child path is a child of the specified parent path, otherwise False -def is_child_path(parent_path, child_path): - parent_path = os.path.abspath(parent_path) - child_path = os.path.abspath(child_path) - return os.path.commonpath([os.path.abspath(parent_path)]) == os.path.commonpath([os.path.abspath(parent_path), os.path.abspath(child_path)]) +logger = get_logger(__file__) class TestImpact: - def __init__(self, config_file, dst_commit, src_branch, dst_branch, pipeline, seeding_branches, seeding_pipelines): - # Commit - self.__dst_commit = dst_commit - print(f"Commit: '{self.__dst_commit}'.") - self.__src_commit = None - self.__has_src_commit = False - # Branch - self.__src_branch = src_branch - print(f"Source branch: '{self.__src_branch}'.") - self.__dst_branch = dst_branch - print(f"Destination branch: '{self.__dst_branch}'.") - print(f"Seeding branches: '{seeding_branches}'.") - if self.__src_branch in seeding_branches: - self.__is_seeding_branch = True - else: - self.__is_seeding_branch = False - print(f"Is seeding branch: '{self.__is_seeding_branch}'.") - # Pipeline - self.__pipeline = pipeline - print(f"Pipeline: '{self.__pipeline}'.") - print(f"Seeding pipelines: '{seeding_pipelines}'.") - if self.__pipeline in seeding_pipelines: - self.__is_seeding_pipeline = True - else: - self.__is_seeding_pipeline = False - print(f"Is seeding pipeline: '{self.__is_seeding_pipeline}'.") - # Config - self.__parse_config_file(config_file) - # Sequence - if self.__is_seeding_branch and self.__is_seeding_pipeline: - self.__is_seeding = True - else: - self.__is_seeding = False - print(f"Is seeding: '{self.__is_seeding}'.") - if self.__use_test_impact_analysis and not self.__is_seeding: - self.__generate_change_list() + def __init__(self, config_file: str): + """ + Initializes the test impact model with the commit, branches as runtime configuration. - # Parse the configuration file and retrieve the data needed for launching the test impact analysis runtime - def __parse_config_file(self, config_file): - print(f"Attempting to parse configuration file '{config_file}'...") - with open(config_file, "r") as config_data: - config = json.load(config_data) - self.__repo_dir = config["repo"]["root"] - self.__repo = Repo(self.__repo_dir) - # TIAF - self.__use_test_impact_analysis = config["jenkins"]["use_test_impact_analysis"] - print(f"Is using test impact analysis: '{self.__use_test_impact_analysis}'.") - self.__tiaf_bin = config["repo"]["tiaf_bin"] - if self.__use_test_impact_analysis and not os.path.isfile(self.__tiaf_bin): - raise FileNotFoundError("Could not find tiaf binary") - # Workspaces - self.__active_workspace = config["workspace"]["active"]["root"] - self.__historic_workspace = config["workspace"]["historic"]["root"] - self.__temp_workspace = config["workspace"]["temp"]["root"] - # Last commit hash - last_commit_hash_path_file = config["workspace"]["historic"]["relative_paths"]["last_run_hash_file"] - self.__last_commit_hash_path = os.path.join(self.__historic_workspace, last_commit_hash_path_file) - print("The configuration file was parsed successfully.") + @param config_file: The runtime config file to obtain the runtime configuration data from. + """ - # Restricts change lists from checking in test impact analysis files - def __check_for_restricted_files(self, file_path): - if is_child_path(self.__active_workspace, file_path) or is_child_path(self.__historic_workspace, file_path) or is_child_path(self.__temp_workspace, file_path): - raise ValueError(f"Checking in test impact analysis framework files is illegal: '{file_path}''.") + self._has_change_list = False + self._parse_config_file(config_file) - def __read_last_run_hash(self): - self.__has_src_commit = False - if os.path.isfile(self.__last_commit_hash_path): - print(f"Previous commit hash found at '{self.__last_commit_hash_path}'.") - with open(self.__last_commit_hash_path) as file: - self.__src_commit = file.read() - self.__has_src_commit = True + def _parse_config_file(self, config_file: str): + """ + Parse the configuration file and retrieve the data needed for launching the test impact analysis runtime. - def __write_last_run_hash(self, last_run_hash): - os.makedirs(self.__historic_workspace, exist_ok=True) - f = open(self.__last_commit_hash_path, "w") - f.write(last_run_hash) - f.close() + @param config_file: The runtime config file to obtain the runtime configuration data from. + """ + + logger.info(f"Attempting to parse configuration file '{config_file}'...") + try: + with open(config_file, "r") as config_data: + self._config = json.load(config_data) + self._repo_dir = self._config["repo"]["root"] + self._repo = Repo(self._repo_dir) + + # TIAF + self._use_test_impact_analysis = self._config["jenkins"]["use_test_impact_analysis"] + self._tiaf_bin = pathlib.Path(self._config["repo"]["tiaf_bin"]) + if self._use_test_impact_analysis and not self._tiaf_bin.is_file(): + logger.warning(f"Could not find TIAF binary at location {self._tiaf_bin}, TIAF will be turned off.") + self._use_test_impact_analysis = False + + # Workspaces + self._active_workspace = self._config["workspace"]["active"]["root"] + self._historic_workspace = self._config["workspace"]["historic"]["root"] + self._temp_workspace = self._config["workspace"]["temp"]["root"] + logger.info("The configuration file was parsed successfully.") + except KeyError as e: + logger.error(f"The config does not contain the key {str(e)}.") + return + + def _attempt_to_generate_change_list(self, last_commit_hash, instance_id: str): + """ + Attempts to determine the change list bewteen now and the last tiaf run (if any). + + @param last_commit_hash: The commit hash of the last TIAF run. + @param instance_id: The unique id to derive the change list file name from. + """ + + self._has_change_list = False + self._change_list_path = None - # Determines the change list bewteen now and the last tiaf run (if any) - def __generate_change_list(self): - self.__has_change_list = False - self.__change_list_path = None # Check whether or not a previous commit hash exists (no hash is not a failure) - self.__read_last_run_hash() - if self.__has_src_commit == True: - if git_utils.is_descendent(self.__src_commit, self.__dst_commit) == False: - print(f"Source commit '{self.__src_commit}' and destination commit '{self.__dst_commit}' are not related.") + self._src_commit = last_commit_hash + if self._src_commit is not None: + if self._repo.is_descendent(self._src_commit, self._dst_commit) == False: + logger.info(f"Source commit '{self._src_commit}' and destination commit '{self._dst_commit}' are not related.") return - diff_path = os.path.join(self.__temp_workspace, "changelist.diff") + self._commit_distance = self._repo.commit_distance(self._src_commit, self._dst_commit) + diff_path = pathlib.Path(pathlib.PurePath(self._temp_workspace).joinpath(f"changelist.{instance_id}.diff")) try: - git_utils.create_diff_file(self.__src_commit, self.__dst_commit, diff_path) - except FileNotFoundError as e: - print(e) + self._repo.create_diff_file(self._src_commit, self._dst_commit, diff_path) + except RuntimeError as e: + logger.error(e) return + # A diff was generated, attempt to parse the diff and construct the change list - print(f"Generated diff between commits '{self.__src_commit}' and '{self.__dst_commit}': '{diff_path}'.") - change_list = {} - change_list["createdFiles"] = [] - change_list["updatedFiles"] = [] - change_list["deletedFiles"] = [] + logger.info(f"Generated diff between commits '{self._src_commit}' and '{self._dst_commit}': '{diff_path}'.") with open(diff_path, "r") as diff_data: lines = diff_data.readlines() for line in lines: match = re.split("^R[0-9]+\\s(\\S+)\\s(\\S+)", line) if len(match) > 1: # File rename - self.__check_for_restricted_files(match[1]) - self.__check_for_restricted_files(match[2]) # Treat renames as a deletion and an addition - change_list["deletedFiles"].append(match[1]) - change_list["createdFiles"].append(match[2]) + self._change_list["deletedFiles"].append(match[1]) + self._change_list["createdFiles"].append(match[2]) else: match = re.split("^[AMD]\\s(\\S+)", line) - self.__check_for_restricted_files(match[1]) if len(match) > 1: if line[0] == 'A': # File addition - change_list["createdFiles"].append(match[1]) + self._change_list["createdFiles"].append(match[1]) elif line[0] == 'M': # File modification - change_list["updatedFiles"].append(match[1]) + self._change_list["updatedFiles"].append(match[1]) elif line[0] == 'D': # File Deletion - change_list["deletedFiles"].append(match[1]) + self._change_list["deletedFiles"].append(match[1]) + # Serialize the change list to the JSON format the test impact analysis runtime expects - change_list_json = json.dumps(change_list, indent = 4) - change_list_path = os.path.join(self.__temp_workspace, "changelist.json") + change_list_json = json.dumps(self._change_list, indent = 4) + change_list_path = pathlib.PurePath(self._temp_workspace).joinpath(f"changelist.{instance_id}.json") f = open(change_list_path, "w") f.write(change_list_json) f.close() - print(f"Change list constructed successfully: '{change_list_path}'.") - print(f"{len(change_list['createdFiles'])} created files, {len(change_list['updatedFiles'])} updated files and {len(change_list['deletedFiles'])} deleted files.") + logger.info(f"Change list constructed successfully: '{change_list_path}'.") + logger.info(f"{len(self._change_list['createdFiles'])} created files, {len(self._change_list['updatedFiles'])} updated files and {len(self._change_list['deletedFiles'])} deleted files.") + # Note: an empty change list generated due to no changes between last and current commit is valid - self.__has_change_list = True - self.__change_list_path = change_list_path + self._has_change_list = True + self._change_list_path = change_list_path else: - print("No previous commit hash found, regular or seeded sequences only will be run.") - self.__has_change_list = False + logger.error("No previous commit hash found, regular or seeded sequences only will be run.") + self._has_change_list = False return - # Runs the specified test sequence - def run(self, suite, test_failure_policy, safe_mode, test_timeout, global_timeout): + def _generate_result(self, s3_bucket: str, suite: str, return_code: int, report: dict, runtime_args: list): + """ + Generates the result object from the pertinent runtime meta-data and sequence report. + + @param The generated result object. + """ + + result = {} + result["src_commit"] = self._src_commit + result["dst_commit"] = self._dst_commit + result["commit_distance"] = self._commit_distance + result["src_branch"] = self._src_branch + result["dst_branch"] = self._dst_branch + result["suite"] = suite + result["use_test_impact_analysis"] = self._use_test_impact_analysis + result["source_of_truth_branch"] = self._source_of_truth_branch + result["is_source_of_truth_branch"] = self._is_source_of_truth_branch + result["has_change_list"] = self._has_change_list + result["has_historic_data"] = self._has_historic_data + result["s3_bucket"] = s3_bucket + result["runtime_args"] = runtime_args + result["return_code"] = return_code + result["report"] = report + result["change_list"] = self._change_list + return result + + def run(self, commit: str, src_branch: str, dst_branch: str, s3_bucket: str, suite: str, test_failure_policy: str, safe_mode: bool, test_timeout: int, global_timeout: int): + """ + Determins the type of sequence to run based on the commit, source branch and test branch before running the + sequence with the specified values. + + @param commit: The commit hash of the changes to run test impact analysis on. + @param src_branch: If not equal to dst_branch, the branch that is being built. + @param dst_branch: If not equal to src_branch, the destination branch for the PR being built. + @param s3_bucket: Location of S3 bucket to use for persistent storage, otherwise local disk storage will be used. + @param suite: Test suite to run. + @param test_failure_policy: Test failure policy for regular and test impact sequences (ignored when seeding). + @param safe_mode: Flag to run impact analysis tests in safe mode (ignored when seeding). + @param test_timeout: Maximum run time (in seconds) of any test target before being terminated (unlimited if None). + @param global_timeout: Maximum run time of the sequence before being terminated (unlimited if None). + """ + args = [] - seed_sequence_test_failure_policy = "continue" - # Suite - args.append(f"--suite={suite}") - print(f"Test suite is set to '{suite}'.") - # Timeouts - if test_timeout != None: - args.append(f"--ttimeout={test_timeout}") - print(f"Test target timeout is set to {test_timeout} seconds.") - if global_timeout != None: - args.append(f"--gtimeout={global_timeout}") - print(f"Global sequence timeout is set to {test_timeout} seconds.") - if self.__use_test_impact_analysis: - print("Test impact analysis is enabled.") - # Seed sequences - if self.__is_seeding: + persistent_storage = None + self._has_historic_data = False + self._change_list = {} + self._change_list["createdFiles"] = [] + self._change_list["updatedFiles"] = [] + self._change_list["deletedFiles"] = [] + + # Branches + self._src_branch = src_branch + self._dst_branch = dst_branch + logger.info(f"Src branch: '{self._src_branch}'.") + logger.info(f"Dst branch: '{self._dst_branch}'.") + + # Source of truth (the branch from which the coverage data will be stored/retrieved from) + if self._dst_branch is None or self._src_branch == self._dst_branch: + # Branch builds are their own source of truth and will update the coverage data for the source of truth after any instrumented sequences complete + self._is_source_of_truth_branch = True + self._source_of_truth_branch = self._src_branch + else: + # PR builds use their destination as the source of truth and never update the coverage data for the source of truth + self._is_source_of_truth_branch = False + self._source_of_truth_branch = self._dst_branch + + logger.info(f"Source of truth branch: '{self._source_of_truth_branch}'.") + logger.info(f"Is source of truth branch: '{self._is_source_of_truth_branch}'.") + + # Commit + self._dst_commit = commit + logger.info(f"Commit: '{self._dst_commit}'.") + self._src_commit = None + self._commit_distance = None + + # Generate a unique ID to be used as part of the file name for required runtime dynamic artifacts. + instance_id = uuid.uuid4().hex + + if self._use_test_impact_analysis: + logger.info("Test impact analysis is enabled.") + try: + # Persistent storage location + if s3_bucket is not None: + persistent_storage = PersistentStorageS3(self._config, suite, s3_bucket, self._source_of_truth_branch) + else: + persistent_storage = PersistentStorageLocal(self._config, suite) + except SystemError as e: + logger.warning(f"The persistent storage encountered an irrecoverable error, test impact analysis will be disabled: '{e}'") + persistent_storage = None + + if persistent_storage is not None: + if persistent_storage.has_historic_data: + logger.info("Historic data found.") + self._attempt_to_generate_change_list(persistent_storage.last_commit_hash, instance_id) + else: + logger.info("No historic data found.") + # Sequence type - args.append("--sequence=seed") - print("Sequence type is set to 'seed'.") - # Test failure policy - args.append(f"--fpolicy={seed_sequence_test_failure_policy}") - print(f"Test failure policy is set to '{seed_sequence_test_failure_policy}'.") - # Impact analysis sequences - else: - if self.__has_change_list: - # Change list - args.append(f"--changelist={self.__change_list_path}") - print(f"Change list is set to '{self.__change_list_path}'.") - # Sequence type - args.append("--sequence=tianowrite") - print("Sequence type is set to 'tianowrite'.") - # Integrity failure policy - args.append("--ipolicy=continue") - print("Integration failure policy is set to 'continue'.") + if self._has_change_list: + if self._is_source_of_truth_branch: + # Use TIA sequence (instrumented subset of tests) for coverage updating branches so we can update the coverage data with the generated coverage + sequence_type = "tia" + else: + # Use TIA no-write sequence (regular subset of tests) for non coverage updating branche + sequence_type = "tianowrite" + # Ignore integrity failures for non coverage updating branches as our confidence in the + args.append("--ipolicy=continue") + logger.info("Integration failure policy is set to 'continue'.") # Safe mode if safe_mode: args.append("--safemode=on") - print("Safe mode set to 'on'.") + logger.info("Safe mode set to 'on'.") else: args.append("--safemode=off") - print("Safe mode set to 'off'.") + logger.info("Safe mode set to 'off'.") + # Change list + args.append(f"--changelist={self._change_list_path}") + logger.info(f"Change list is set to '{self._change_list_path}'.") else: - args.append("--sequence=regular") - print("Sequence type is set to 'regular'.") - # Test failure policy - args.append(f"--fpolicy={test_failure_policy}") - print(f"Test failure policy is set to '{test_failure_policy}'.") - else: - print("Test impact analysis is disabled.") - # Sequence type - args.append("--sequence=regular") - print("Sequence type is set to 'regular'.") - # Seeding job - if self.__is_seeding: - # Test failure policy - args.append(f"--fpolicy={seed_sequence_test_failure_policy}") - print(f"Test failure policy is set to '{seed_sequence_test_failure_policy}'.") - # Non seeding job + if self._is_source_of_truth_branch: + # Use seed sequence (instrumented all tests) for coverage updating branches so we can generate the coverage bed for future sequences + sequence_type = "seed" + # We always continue after test failures when seeding to ensure we capture the coverage for all test targets + test_failure_policy = "continue" + else: + # Use regular sequence (regular all tests) for non coverage updating branches as we have no coverage to use nor coverage to update + sequence_type = "regular" + # Ignore integrity failures for non coverage updating branches as our confidence in the + args.append("--ipolicy=continue") + logger.info("Integration failure policy is set to 'continue'.") else: - # Test failure policy - args.append(f"--fpolicy={test_failure_policy}") - print(f"Test failure policy is set to '{test_failure_policy}'.") - - print("Args: ", end='') - print(*args) - result = subprocess.run([self.__tiaf_bin] + args) - # If the sequence completed (with or without failures) we will update the historical meta-data - if result.returncode == 0 or result.returncode == 7: - print("Test impact analysis runtime returned successfully.") - if self.__is_seeding: - print("Writing historical meta-data...") - self.__write_last_run_hash(self.__dst_commit) - print("Complete!") + # Use regular sequence (regular all tests) when the persistent storage fails to avoid wasting time generating seed data that will not be preserved + sequence_type = "regular" else: - print(f"The test impact analysis runtime returned with error: '{result.returncode}'.") - return result.returncode - \ No newline at end of file + # Use regular sequence (regular all tests) when test impact analysis is disabled + sequence_type = "regular" + args.append(f"--sequence={sequence_type}") + logger.info(f"Sequence type is set to '{sequence_type}'.") + + # Test failure policy + args.append(f"--fpolicy={test_failure_policy}") + logger.info(f"Test failure policy is set to '{test_failure_policy}'.") + + # Sequence report + report_file = pathlib.PurePath(self._temp_workspace).joinpath(f"report.{instance_id}.json") + args.append(f"--report={report_file}") + logger.info(f"Sequence report file is set to '{report_file}'.") + + # Suite + args.append(f"--suite={suite}") + logger.info(f"Test suite is set to '{suite}'.") + + # Timeouts + if test_timeout != None: + args.append(f"--ttimeout={test_timeout}") + logger.info(f"Test target timeout is set to {test_timeout} seconds.") + if global_timeout != None: + args.append(f"--gtimeout={global_timeout}") + logger.info(f"Global sequence timeout is set to {test_timeout} seconds.") + + # Run sequence + unpacked_args = " ".join(args) + logger.info(f"Args: {unpacked_args}") + runtime_result = subprocess.run([self._tiaf_bin] + args) + report = None + + # If the sequence completed (with or without failures) we will update the historical meta-data + if runtime_result.returncode == 0 or runtime_result.returncode == 7: + logger.info("Test impact analysis runtime returned successfully.") + if self._is_source_of_truth_branch and persistent_storage is not None: + persistent_storage.update_and_store_historic_data(self._dst_commit) + with open(report_file) as json_file: + report = json.load(json_file) + else: + logger.error(f"The test impact analysis runtime returned with error: '{runtime_result.returncode}'.") + + return self._generate_result(s3_bucket, suite, runtime_result.returncode, report, args) \ No newline at end of file diff --git a/scripts/build/TestImpactAnalysis/tiaf_driver.py b/scripts/build/TestImpactAnalysis/tiaf_driver.py index 77fc8c1fc2..c44232a38e 100644 --- a/scripts/build/TestImpactAnalysis/tiaf_driver.py +++ b/scripts/build/TestImpactAnalysis/tiaf_driver.py @@ -7,60 +7,136 @@ # import argparse -from tiaf import TestImpact - +import mars_utils import sys -import os -import datetime -import json -import socket +import pathlib +from tiaf import TestImpact +from tiaf_logger import get_logger + +logger = get_logger(__file__) def parse_args(): - def file_path(value): - if os.path.isfile(value): + def valid_file_path(value): + if pathlib.Path(value).is_file(): return value else: raise FileNotFoundError(value) - def timout_type(value): + def valid_timout_type(value): value = int(value) if value <= 0: raise ValueError("Timer values must be positive integers") return value - def test_failure_policy(value): + def valid_test_failure_policy(value): if value == "continue" or value == "abort" or value == "ignore": return value else: raise ValueError("Test failure policy must be 'abort', 'continue' or 'ignore'") parser = argparse.ArgumentParser() - parser.add_argument('--config', dest="config", type=file_path, help="Path to the test impact analysis framework configuration file", required=True) - parser.add_argument('--src-branch', dest="src_branch", help="The branch that is being build", required=True) - parser.add_argument('--dst-branch', dest="dst_branch", help="For PR builds, the destination branch to be merged to, otherwise empty") - parser.add_argument('--seeding-branches', dest="seeding_branches", type=lambda arg: arg.split(','), help="Comma separated branches that seeding will occur on", required=True) - parser.add_argument('--pipeline', dest="pipeline", help="Pipeline the test impact analysis framework is running on", required=True) - parser.add_argument('--seeding-pipelines', dest="seeding_pipelines", type=lambda arg: arg.split(','), help="Comma separated pipeline that seeding will occur on", required=True) - parser.add_argument('--dest-commit', dest="dst_commit", help="Commit to run test impact analysis on (ignored when seeding)", required=True) - parser.add_argument('--suite', dest="suite", help="Test suite to run", required=True) - parser.add_argument('--test-failure-policy', dest="test_failure_policy", type=test_failure_policy, help="Test failure policy for regular and test impact sequences (ignored when seeding)", required=True) - parser.add_argument('--safeMode', dest="safe_mode", action='store_true', help="Run impact analysis tests in safe mode (ignored when seeding)") - parser.add_argument('--testTimeout', dest="test_timeout", type=timout_type, help="Maximum run time (in seconds) of any test target before being terminated", required=False) - parser.add_argument('--globalTimeout', dest="global_timeout", type=timout_type, help="Maximum run time of the sequence before being terminated", required=False) - parser.set_defaults(test_timeout=None) - parser.set_defaults(global_timeout=None) + + # Configuration file path + parser.add_argument( + '--config', + type=valid_file_path, + help="Path to the test impact analysis framework configuration file", + required=True + ) + + # Source branch + parser.add_argument( + '--src-branch', + help="Branch that is being built", + required=True + ) + + # Destination branch + parser.add_argument( + '--dst-branch', + help="For PR builds, the destination branch to be merged to, otherwise empty", + required=False + ) + + # Commit hash + parser.add_argument( + '--commit', + help="Commit that is being built", + required=True + ) + + # S3 bucket + parser.add_argument( + '--s3-bucket', + help="Location of S3 bucket to use for persistent storage, otherwise local disk storage will be used", + required=False + ) + + # MARS index prefix + parser.add_argument( + '--mars-index-prefix', + help="Index prefix to use for MARS, otherwise no data will be tramsmitted to MARS", + required=False + ) + + # Test suite + parser.add_argument( + '--suite', + help="Test suite to run", + required=True + ) + + # Test failure policy + parser.add_argument( + '--test-failure-policy', + type=valid_test_failure_policy, + help="Test failure policy for regular and test impact sequences (ignored when seeding)", + required=True + ) + + # Safe mode + parser.add_argument( + '--safe-mode', + action='store_true', + help="Run impact analysis tests in safe mode (ignored when seeding)", + required=False + ) + + # Test timeout + parser.add_argument( + '--test-timeout', + type=valid_timout_type, + help="Maximum run time (in seconds) of any test target before being terminated", + required=False + ) + + # Global timeout + parser.add_argument( + '--global-timeout', + type=valid_timout_type, + help="Maximum run time of the sequence before being terminated", + required=False + ) + args = parser.parse_args() return args if __name__ == "__main__": + try: args = parse_args() - tiaf = TestImpact(args.config, args.dst_commit, args.src_branch, args.dst_branch, args.pipeline, args.seeding_branches, args.seeding_pipelines) - return_code = tiaf.run(args.suite, args.test_failure_policy, args.safe_mode, args.test_timeout, args.global_timeout) + tiaf = TestImpact(args.config) + tiaf_result = tiaf.run(args.commit, args.src_branch, args.dst_branch, args.s3_bucket, args.suite, args.test_failure_policy, args.safe_mode, args.test_timeout, args.global_timeout) + + if args.mars_index_prefix is not None: + logger.info("Transmitting report to MARS...") + mars_utils.transmit_report_to_mars(args.mars_index_prefix, tiaf_result, sys.argv) + + logger.info("Complete!") # Non-gating will be removed from this script and handled at the job level in SPEC-7413 - #sys.exit(return_code) + #sys.exit(result.return_code) sys.exit(0) except Exception as e: # Non-gating will be removed from this script and handled at the job level in SPEC-7413 - print(f"Exception caught by TIAF driver: {e}") + logger.error(f"Exception caught by TIAF driver: '{e}'.") diff --git a/scripts/build/TestImpactAnalysis/tiaf_logger.py b/scripts/build/TestImpactAnalysis/tiaf_logger.py new file mode 100644 index 0000000000..0acb9349d4 --- /dev/null +++ b/scripts/build/TestImpactAnalysis/tiaf_logger.py @@ -0,0 +1,20 @@ +# +# 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 +# +# + +import logging +import sys + +def get_logger(name: str): + logger = logging.getLogger(name) + logger.setLevel(logging.INFO) + handler = logging.StreamHandler(sys.stdout) + handler.setLevel(logging.DEBUG) + formatter = logging.Formatter('[%(asctime)s][TIAF][%(levelname)s] %(message)s') + handler.setFormatter(formatter) + logger.addHandler(handler) + return logger \ No newline at end of file diff --git a/scripts/build/TestImpactAnalysis/tiaf_persistent_storage.py b/scripts/build/TestImpactAnalysis/tiaf_persistent_storage.py new file mode 100644 index 0000000000..750353651b --- /dev/null +++ b/scripts/build/TestImpactAnalysis/tiaf_persistent_storage.py @@ -0,0 +1,118 @@ +# +# 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 +# +# + +import json +import pathlib +from abc import ABC, abstractmethod +from tiaf_logger import get_logger + +logger = get_logger(__file__) + +# Abstraction for the persistent storage required by TIAF to store and retrieve the branch coverage data and other meta-data +class PersistentStorage(ABC): + def __init__(self, config: dict, suite: str): + """ + Initializes the persistent storage into a state for which there is no historic data available. + + @param config: The runtime configuration to obtain the data file paths from. + @param suite: The test suite for which the historic data will be obtained for. + """ + + # Work on the assumption that there is no historic meta-data (a valid state to be in, should none exist) + self._last_commit_hash = None + self._has_historic_data = False + + try: + # The runtime expects the coverage data to be in the location specified in the config file (unless overridden with + # the --datafile command line argument, which the TIAF scripts do not do) + self._active_workspace = pathlib.Path(config["workspace"]["active"]["root"]) + unpacked_coverage_data_file = config["workspace"]["active"]["relative_paths"]["test_impact_data_files"][suite] + except KeyError as e: + raise SystemError(f"The config does not contain the key {str(e)}.") + + self._unpacked_coverage_data_file = self._active_workspace.joinpath(unpacked_coverage_data_file) + + def _unpack_historic_data(self, historic_data_json: str): + """ + Unpacks the historic data into the appropriate memory and disk locations. + + @param historic_data_json: The historic data in JSON format. + """ + + self._has_historic_data = False + + try: + historic_data = json.loads(historic_data_json) + self._last_commit_hash = historic_data["last_commit_hash"] + + # Create the active workspace directory where the coverage data file will be placed and unpack the coverage data so + # it is accessible by the runtime + self._active_workspace.mkdir(exist_ok=True) + with open(self._unpacked_coverage_data_file, "w", newline='\n') as coverage_data: + coverage_data.write(historic_data["coverage_data"]) + + self._has_historic_data = True + except json.JSONDecodeError: + logger.error("The historic data does not contain valid JSON.") + except KeyError as e: + logger.error(f"The historic data does not contain the key {str(e)}.") + except EnvironmentError as e: + logger.error(f"There was a problem the coverage data file '{self._unpacked_coverage_data_file}': '{e}'.") + + def _pack_historic_data(self, last_commit_hash: str): + """ + Packs the current historic data into a JSON file for serializing. + + @param last_commit_hash: The commit hash to associate the coverage data (and any other meta data) with. + @return: The packed historic data in JSON format. + """ + + try: + # Attempt to read the existing coverage data + if self._unpacked_coverage_data_file.is_file(): + with open(self._unpacked_coverage_data_file, "r") as coverage_data: + historic_data = {"last_commit_hash": last_commit_hash, "coverage_data": coverage_data.read()} + return json.dumps(historic_data) + else: + logger.info(f"No coverage data exists at location '{self._unpacked_coverage_data_file}'.") + except EnvironmentError as e: + logger.error(f"There was a problem the coverage data file '{self._unpacked_coverage_data_file}': '{e}'.") + except TypeError: + logger.error("The historic data could not be serialized to valid JSON.") + + return None + + @abstractmethod + def _store_historic_data(self, historic_data_json: str): + """ + Stores the historic data in the designated persistent storage location. + + @param historic_data_json: The historic data (in JSON format) to be stored in persistent storage. + """ + pass + + def update_and_store_historic_data(self, last_commit_hash: str): + """ + Updates the historic data and stores it in the designated persistent storage location. + + @param last_commit_hash: The commit hash to associate the coverage data (and any other meta data) with. + """ + + historic_data_json = self._pack_historic_data(last_commit_hash) + if historic_data_json is not None: + self._store_historic_data(historic_data_json) + else: + logger.info("The historic data could not be successfully stored.") + + @property + def has_historic_data(self): + return self._has_historic_data + + @property + def last_commit_hash(self): + return self._last_commit_hash \ No newline at end of file diff --git a/scripts/build/TestImpactAnalysis/tiaf_persistent_storage_local.py b/scripts/build/TestImpactAnalysis/tiaf_persistent_storage_local.py new file mode 100644 index 0000000000..ba9b58fbf3 --- /dev/null +++ b/scripts/build/TestImpactAnalysis/tiaf_persistent_storage_local.py @@ -0,0 +1,56 @@ +# +# 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 +# +# + +import pathlib +import logging +from tiaf_persistent_storage import PersistentStorage +from tiaf_logger import get_logger + +logger = get_logger(__file__) + +# Implementation of local persistent storage +class PersistentStorageLocal(PersistentStorage): + def __init__(self, config: str, suite: str): + """ + Initializes the persistent storage with any local historic data available. + + @param config: The runtime config file to obtain the data file paths from. + @param suite: The test suite for which the historic data will be obtained for. + """ + + super().__init__(config, suite) + try: + # Attempt to obtain the local persistent data location specified in the runtime config file + self._historic_workspace = pathlib.Path(config["workspace"]["historic"]["root"]) + historic_data_file = pathlib.Path(config["workspace"]["historic"]["relative_paths"]["data"]) + + # Attempt to unpack the local historic data file + self._historic_data_file = self._historic_workspace.joinpath(historic_data_file) + if self._historic_data_file.is_file(): + with open(self._historic_data_file, "r") as historic_data_raw: + historic_data_json = historic_data_raw.read() + self._unpack_historic_data(historic_data_json) + + except KeyError as e: + raise SystemError(f"The config does not contain the key {str(e)}.") + except EnvironmentError as e: + raise SystemError(f"There was a problem the historic data file '{self._historic_data_file}': '{e}'.") + + def _store_historic_data(self, historic_data_json: str): + """ + Stores then historical data in historic workspace location specified in the runtime config file. + + @param historic_data_json: The historic data (in JSON format) to be stored in persistent storage. + """ + + try: + self._historic_workspace.mkdir(exist_ok=True) + with open(self._historic_data_file, "w") as historic_data_file: + historic_data_file.write(historic_data_json) + except EnvironmentError as e: + logger.error(f"There was a problem the historic data file '{self._historic_data_file}': '{e}'.") \ No newline at end of file diff --git a/scripts/build/TestImpactAnalysis/tiaf_persistent_storage_s3.py b/scripts/build/TestImpactAnalysis/tiaf_persistent_storage_s3.py new file mode 100644 index 0000000000..75c4bc93d5 --- /dev/null +++ b/scripts/build/TestImpactAnalysis/tiaf_persistent_storage_s3.py @@ -0,0 +1,87 @@ +# +# 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 +# +# + +import boto3 +import botocore.exceptions +import zlib +import logging +from io import BytesIO +from tiaf_persistent_storage import PersistentStorage +from tiaf_logger import get_logger + +logger = get_logger(__file__) + +# Implementation of s3 bucket persistent storage +class PersistentStorageS3(PersistentStorage): + def __init__(self, config: dict, suite: str, s3_bucket: str, branch: str): + """ + Initializes the persistent storage with the specified s3 bucket. + + @param config: The runtime config file to obtain the data file paths from. + @param suite: The test suite for which the historic data will be obtained for. + @param s3_bucket: The s3 bucket to use for storing nd retrieving historic data. + """ + + super().__init__(config, suite) + + try: + # We store the historic data as compressed JSON + object_extension = "json.zip" + + # historic_data.json.zip is the file containing the coverage and meta-data of the last TIAF sequence run + historic_data_file = f"historic_data.{object_extension}" + + # The location of the data is in the form / so the build config of each branch gets its own historic data + self._dir = f'{branch}/{config["meta"]["build_config"]}' + self._historic_data_key = f'{self._dir}/{historic_data_file}' + + logger.info(f"Attempting to retrieve historic data for branch '{branch}' at location '{self._historic_data_key}' on bucket '{s3_bucket}'...") + self._s3 = boto3.resource("s3") + self._bucket = self._s3.Bucket(s3_bucket) + + # There is only one historic_data.json.zip in the specified location + for object in self._bucket.objects.filter(Prefix=self._historic_data_key): + logger.info(f"Historic data found for branch '{branch}'.") + + # Archive the existing object with the name of the existing last commit hash + archive_key = f"{self._dir}/archive/{self._last_commit_hash}.{object_extension}" + logger.info(f"Archiving existing historic data to {archive_key}...") + self._bucket.copy({"Bucket": self._bucket.name, "Key": self._historic_data_key}, archive_key) + + # Decode the historic data object into raw bytes + response = object.get() + file_stream = response['Body'] + + # Decompress and unpack the zipped historic data JSON + historic_data_json = zlib.decompress(file_stream.read()).decode('UTF-8') + self._unpack_historic_data(historic_data_json) + + return + except KeyError as e: + raise SystemError(f"The config does not contain the key {str(e)}.") + except botocore.exceptions.BotoCoreError as e: + raise SystemError(f"There was a problem with the s3 bucket: {e}") + except botocore.exceptions.ClientError as e: + raise SystemError(f"There was a problem with the s3 client: {e}") + + def _store_historic_data(self, historic_data_json: str): + """ + Stores then historical data in specified s3 bucket at the location //historical_data.json.zip. + + @param historic_data_json: The historic data (in JSON format) to be stored in persistent storage. + """ + + try: + data = BytesIO(zlib.compress(bytes(historic_data_json, "UTF-8"))) + logger.info(f"Uploading historic data to location '{self._historic_data_key}'...") + self._bucket.upload_fileobj(data, self._historic_data_key) + logger.info("Upload complete.") + except botocore.exceptions.BotoCoreError as e: + logger.error(f"There was a problem with the s3 bucket: {e}") + except botocore.exceptions.ClientError as e: + logger.error(f"There was a problem with the s3 client: {e}") \ No newline at end of file From 2bf64879daa2a3210647a3737613c876775ea022 Mon Sep 17 00:00:00 2001 From: John Jones-Steele Date: Tue, 10 Aug 2021 14:52:09 +0100 Subject: [PATCH 28/37] Fixed the SpinBox tests Signed-off-by: John Jones-Steele --- .../AzToolsFramework/Tests/SpinBoxTests.cpp | 38 +++++++++++++------ 1 file changed, 27 insertions(+), 11 deletions(-) diff --git a/Code/Framework/AzToolsFramework/Tests/SpinBoxTests.cpp b/Code/Framework/AzToolsFramework/Tests/SpinBoxTests.cpp index eb09c68cee..a88cb68638 100644 --- a/Code/Framework/AzToolsFramework/Tests/SpinBoxTests.cpp +++ b/Code/Framework/AzToolsFramework/Tests/SpinBoxTests.cpp @@ -245,12 +245,15 @@ namespace UnitTest { using testing::StrEq; + QLocale testLocale{ QLocale() }; + QString testString = "10" + QString(testLocale.decimalPoint()) + "0"; + m_doubleSpinBox->setSuffix("m"); m_doubleSpinBox->setValue(10.0); // test internal logic (textFromValue() calls private StringValue()) QString value = m_doubleSpinBox->textFromValue(10.0); - EXPECT_THAT(value.toUtf8().constData(), StrEq("10.0")); + EXPECT_THAT(value.toUtf8().constData(), testString); m_doubleSpinBox->setFocus(); EXPECT_THAT(m_doubleSpinBox->suffix().toUtf8().constData(), StrEq("")); @@ -293,31 +296,44 @@ namespace UnitTest TEST_F(SpinBoxFixture, SpinBoxCheckHighValueTruncatesCorrectly) { - QString value = setupTruncationTest("0.9999999"); + QLocale testLocale{ QLocale() }; + QString testString = "0" + QString(testLocale.decimalPoint()) + "9999999"; + QString value = setupTruncationTest(testString); - EXPECT_TRUE(value == "0.999"); + testString = "0" + QString(testLocale.decimalPoint()) + "999"; + EXPECT_TRUE(value == testString); } TEST_F(SpinBoxFixture, SpinBoxCheckLowValueTruncatesCorrectly) { - QString value = setupTruncationTest("0.0000001"); + QLocale testLocale{ QLocale() }; + QString testString = "0" + QString(testLocale.decimalPoint()) + "0000001"; + QString value = setupTruncationTest(testString); - EXPECT_TRUE(value == "0.0"); + testString = "0" + QString(testLocale.decimalPoint()) + "0"; + EXPECT_TRUE(value == testString); } TEST_F(SpinBoxFixture, SpinBoxCheckBugValuesTruncatesCorrectly) { - QString value = setupTruncationTest("0.12395"); + QLocale testLocale{ QLocale() }; + QString testString = "0" + QString(testLocale.decimalPoint()) + "12395"; + QString value = setupTruncationTest(testString); - EXPECT_TRUE(value == "0.123"); + testString = "0" + QString(testLocale.decimalPoint()) + "123"; + EXPECT_TRUE(value == testString); - value = setupTruncationTest("0.94496"); + testString = "0" + QString(testLocale.decimalPoint()) + "94496"; + value = setupTruncationTest(testString); - EXPECT_TRUE(value == "0.944"); + testString = "0" + QString(testLocale.decimalPoint()) + "944"; + EXPECT_TRUE(value == testString); - value = setupTruncationTest("0.0009999"); + testString = "0" + QString(testLocale.decimalPoint()) + "0009999"; + value = setupTruncationTest(testString); - EXPECT_TRUE(value == "0.0"); + testString = "0" + QString(testLocale.decimalPoint()) + "0"; + EXPECT_TRUE(value == testString); } } // namespace UnitTest From 2be043ca96ccd08dbd74d114d6ca98158d381f1d Mon Sep 17 00:00:00 2001 From: John Date: Tue, 10 Aug 2021 15:13:19 +0100 Subject: [PATCH 29/37] Fix None comparisons in TIAF scripts. Signed-off-by: John --- scripts/build/TestImpactAnalysis/mars_utils.py | 4 ++-- scripts/build/TestImpactAnalysis/tiaf.py | 14 ++++++++------ scripts/build/TestImpactAnalysis/tiaf_driver.py | 2 +- .../TestImpactAnalysis/tiaf_persistent_storage.py | 2 +- 4 files changed, 12 insertions(+), 10 deletions(-) diff --git a/scripts/build/TestImpactAnalysis/mars_utils.py b/scripts/build/TestImpactAnalysis/mars_utils.py index d25aafb664..69374512a3 100644 --- a/scripts/build/TestImpactAnalysis/mars_utils.py +++ b/scripts/build/TestImpactAnalysis/mars_utils.py @@ -100,7 +100,7 @@ class FilebeatClient(object): self._open_socket() def send_event(self, payload, index, timestamp=None, pipeline="filebeat"): - if timestamp is None: + if not timestamp: timestamp = datetime.datetime.utcnow().timestamp() event = { @@ -437,7 +437,7 @@ def transmit_report_to_mars(mars_index_prefix: str, tiaf_result: dict, driver_ar mars_job = generate_mars_job(tiaf_result, driver_args) filebeat.send_event(mars_job, f"{mars_index_prefix}.tiaf.job") - if tiaf_result[REPORT_KEY] is not None: + if tiaf_result[REPORT_KEY]: # Generate and transmit the MARS sequence document mars_sequence = generate_mars_sequence(tiaf_result[REPORT_KEY], mars_job, tiaf_result[CHANGE_LIST_KEY], t0_timestamp) filebeat.send_event(mars_sequence, f"{mars_index_prefix}.tiaf.sequence") diff --git a/scripts/build/TestImpactAnalysis/tiaf.py b/scripts/build/TestImpactAnalysis/tiaf.py index c368465ecc..c4d1449dbf 100644 --- a/scripts/build/TestImpactAnalysis/tiaf.py +++ b/scripts/build/TestImpactAnalysis/tiaf.py @@ -49,6 +49,8 @@ class TestImpact: if self._use_test_impact_analysis and not self._tiaf_bin.is_file(): logger.warning(f"Could not find TIAF binary at location {self._tiaf_bin}, TIAF will be turned off.") self._use_test_impact_analysis = False + else: + logger.info(f"Runtime binary found at location {self._tiaf_bin}") # Workspaces self._active_workspace = self._config["workspace"]["active"]["root"] @@ -72,7 +74,7 @@ class TestImpact: # Check whether or not a previous commit hash exists (no hash is not a failure) self._src_commit = last_commit_hash - if self._src_commit is not None: + if self._src_commit: if self._repo.is_descendent(self._src_commit, self._dst_commit) == False: logger.info(f"Source commit '{self._src_commit}' and destination commit '{self._dst_commit}' are not related.") return @@ -182,7 +184,7 @@ class TestImpact: logger.info(f"Dst branch: '{self._dst_branch}'.") # Source of truth (the branch from which the coverage data will be stored/retrieved from) - if self._dst_branch is None or self._src_branch == self._dst_branch: + if not self._dst_branch or self._src_branch == self._dst_branch: # Branch builds are their own source of truth and will update the coverage data for the source of truth after any instrumented sequences complete self._is_source_of_truth_branch = True self._source_of_truth_branch = self._src_branch @@ -207,7 +209,7 @@ class TestImpact: logger.info("Test impact analysis is enabled.") try: # Persistent storage location - if s3_bucket is not None: + if s3_bucket: persistent_storage = PersistentStorageS3(self._config, suite, s3_bucket, self._source_of_truth_branch) else: persistent_storage = PersistentStorageLocal(self._config, suite) @@ -215,7 +217,7 @@ class TestImpact: logger.warning(f"The persistent storage encountered an irrecoverable error, test impact analysis will be disabled: '{e}'") persistent_storage = None - if persistent_storage is not None: + if persistent_storage: if persistent_storage.has_historic_data: logger.info("Historic data found.") self._attempt_to_generate_change_list(persistent_storage.last_commit_hash, instance_id) @@ -278,10 +280,10 @@ class TestImpact: logger.info(f"Test suite is set to '{suite}'.") # Timeouts - if test_timeout != None: + if test_timeout is not None: args.append(f"--ttimeout={test_timeout}") logger.info(f"Test target timeout is set to {test_timeout} seconds.") - if global_timeout != None: + if global_timeout is not None: args.append(f"--gtimeout={global_timeout}") logger.info(f"Global sequence timeout is set to {test_timeout} seconds.") diff --git a/scripts/build/TestImpactAnalysis/tiaf_driver.py b/scripts/build/TestImpactAnalysis/tiaf_driver.py index c44232a38e..99f9de6eb9 100644 --- a/scripts/build/TestImpactAnalysis/tiaf_driver.py +++ b/scripts/build/TestImpactAnalysis/tiaf_driver.py @@ -129,7 +129,7 @@ if __name__ == "__main__": tiaf = TestImpact(args.config) tiaf_result = tiaf.run(args.commit, args.src_branch, args.dst_branch, args.s3_bucket, args.suite, args.test_failure_policy, args.safe_mode, args.test_timeout, args.global_timeout) - if args.mars_index_prefix is not None: + if args.mars_index_prefix: logger.info("Transmitting report to MARS...") mars_utils.transmit_report_to_mars(args.mars_index_prefix, tiaf_result, sys.argv) diff --git a/scripts/build/TestImpactAnalysis/tiaf_persistent_storage.py b/scripts/build/TestImpactAnalysis/tiaf_persistent_storage.py index 750353651b..18ea25091f 100644 --- a/scripts/build/TestImpactAnalysis/tiaf_persistent_storage.py +++ b/scripts/build/TestImpactAnalysis/tiaf_persistent_storage.py @@ -104,7 +104,7 @@ class PersistentStorage(ABC): """ historic_data_json = self._pack_historic_data(last_commit_hash) - if historic_data_json is not None: + if historic_data_json: self._store_historic_data(historic_data_json) else: logger.info("The historic data could not be successfully stored.") From e3221224bef798c5a51fd7d24b7f980173ef56a9 Mon Sep 17 00:00:00 2001 From: John Date: Tue, 10 Aug 2021 15:34:27 +0100 Subject: [PATCH 30/37] Add traceback to tiaf exception handler. Signed-off-by: John --- scripts/build/TestImpactAnalysis/tiaf_driver.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scripts/build/TestImpactAnalysis/tiaf_driver.py b/scripts/build/TestImpactAnalysis/tiaf_driver.py index 99f9de6eb9..5ad16abaa0 100644 --- a/scripts/build/TestImpactAnalysis/tiaf_driver.py +++ b/scripts/build/TestImpactAnalysis/tiaf_driver.py @@ -10,6 +10,7 @@ import argparse import mars_utils import sys import pathlib +import traceback from tiaf import TestImpact from tiaf_logger import get_logger @@ -140,3 +141,4 @@ if __name__ == "__main__": except Exception as e: # Non-gating will be removed from this script and handled at the job level in SPEC-7413 logger.error(f"Exception caught by TIAF driver: '{e}'.") + traceback.print_exc() From 5dbbb93a06b95670e2983f804495d76eb5dfafcb Mon Sep 17 00:00:00 2001 From: John Date: Tue, 10 Aug 2021 15:49:19 +0100 Subject: [PATCH 31/37] Fix path for subprocess Signed-off-by: John --- scripts/build/TestImpactAnalysis/tiaf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/build/TestImpactAnalysis/tiaf.py b/scripts/build/TestImpactAnalysis/tiaf.py index c4d1449dbf..3c94c2b5f4 100644 --- a/scripts/build/TestImpactAnalysis/tiaf.py +++ b/scripts/build/TestImpactAnalysis/tiaf.py @@ -290,7 +290,7 @@ class TestImpact: # Run sequence unpacked_args = " ".join(args) logger.info(f"Args: {unpacked_args}") - runtime_result = subprocess.run([self._tiaf_bin] + args) + runtime_result = subprocess.run([str(self._tiaf_bin)] + args) report = None # If the sequence completed (with or without failures) we will update the historical meta-data From 0953a75a94c59a7bce60cbbe5e2a48397475d35f Mon Sep 17 00:00:00 2001 From: Chris Burel Date: Mon, 9 Aug 2021 12:04:57 -0700 Subject: [PATCH 32/37] Replace MCore::SmallArray usage with AZStd::vector Signed-off-by: Chris Burel --- .../CommandSystem/Source/MetaData.cpp | 8 +- .../Source/NodeGroupCommands.cpp | 8 +- .../ExporterLib/Exporter/NodeExport.cpp | 6 +- .../EMotionFX/Code/EMotionFX/Source/Actor.cpp | 87 ++-- Gems/EMotionFX/Code/EMotionFX/Source/Actor.h | 13 +- .../Code/EMotionFX/Source/ActorInstance.cpp | 4 +- .../Code/EMotionFX/Source/NodeGroup.cpp | 37 +- .../Code/EMotionFX/Source/NodeGroup.h | 18 +- .../NodeGroups/NodeGroupManagementWidget.cpp | 16 +- .../Source/NodeGroups/NodeGroupWidget.cpp | 18 +- .../Source/NodeWindow/ActorInfo.cpp | 4 +- .../Source/NodeWindow/NodeGroupInfo.cpp | 4 +- Gems/EMotionFX/Code/MCore/Source/SmallArray.h | 414 ------------------ Gems/EMotionFX/Code/MCore/mcore_files.cmake | 1 - 14 files changed, 103 insertions(+), 535 deletions(-) delete mode 100644 Gems/EMotionFX/Code/MCore/Source/SmallArray.h diff --git a/Gems/EMotionFX/Code/EMotionFX/CommandSystem/Source/MetaData.cpp b/Gems/EMotionFX/Code/EMotionFX/CommandSystem/Source/MetaData.cpp index 9a0913a5db..f98e88a760 100644 --- a/Gems/EMotionFX/Code/EMotionFX/CommandSystem/Source/MetaData.cpp +++ b/Gems/EMotionFX/Code/EMotionFX/CommandSystem/Source/MetaData.cpp @@ -41,15 +41,15 @@ namespace CommandSystem void MetaData::GenerateNodeGroupMetaData(EMotionFX::Actor* actor, AZStd::string& outMetaDataString) { AZStd::string nodeNameList; - const AZ::u32 numNodeGroups = actor->GetNumNodeGroups(); - for (uint32 i = 0; i < numNodeGroups; ++i) + const size_t numNodeGroups = actor->GetNumNodeGroups(); + for (size_t i = 0; i < numNodeGroups; ++i) { EMotionFX::NodeGroup* nodeGroup = actor->GetNodeGroup(i); outMetaDataString += AZStd::string::format("AddNodeGroup -actorID $(ACTORID) -name \"%s\"\n", nodeGroup->GetName()); nodeNameList.clear(); - const AZ::u16 numNodes = nodeGroup->GetNumNodes(); - for (AZ::u16 n = 0; n < numNodes; ++n) + const size_t numNodes = nodeGroup->GetNumNodes(); + for (size_t n = 0; n < numNodes; ++n) { const AZ::u16 nodeIndex = nodeGroup->GetNode(n); EMotionFX::Node* node = actor->GetSkeleton()->GetNode(nodeIndex); diff --git a/Gems/EMotionFX/Code/EMotionFX/CommandSystem/Source/NodeGroupCommands.cpp b/Gems/EMotionFX/Code/EMotionFX/CommandSystem/Source/NodeGroupCommands.cpp index c65429ab83..adaa9802d9 100644 --- a/Gems/EMotionFX/Code/EMotionFX/CommandSystem/Source/NodeGroupCommands.cpp +++ b/Gems/EMotionFX/Code/EMotionFX/CommandSystem/Source/NodeGroupCommands.cpp @@ -80,7 +80,7 @@ namespace CommandSystem { if (*m_nodeAction == NodeAction::Replace) { - nodeGroup->GetNodeArray().Clear(); + nodeGroup->GetNodeArray().clear(); } for (const AZStd::string& nodeName : *m_nodeNames) { @@ -146,12 +146,12 @@ namespace CommandSystem if (m_nodeNames.has_value()) { // clear previous nodes - nodeGroup->GetNodeArray().Clear(); - const uint16 numNodes = m_oldNodeGroup->GetNumNodes(); + nodeGroup->GetNodeArray().clear(); + const size_t numNodes = m_oldNodeGroup->GetNumNodes(); nodeGroup->SetNumNodes(numNodes); // add all nodes to the group - for (uint16 i = 0; i < numNodes; ++i) + for (size_t i = 0; i < numNodes; ++i) { nodeGroup->SetNode(i, m_oldNodeGroup->GetNode(i)); } diff --git a/Gems/EMotionFX/Code/EMotionFX/Exporters/ExporterLib/Exporter/NodeExport.cpp b/Gems/EMotionFX/Code/EMotionFX/Exporters/ExporterLib/Exporter/NodeExport.cpp index 91727b2113..d8404cc991 100644 --- a/Gems/EMotionFX/Code/EMotionFX/Exporters/ExporterLib/Exporter/NodeExport.cpp +++ b/Gems/EMotionFX/Code/EMotionFX/Exporters/ExporterLib/Exporter/NodeExport.cpp @@ -249,7 +249,7 @@ namespace ExporterLib { chunkHeader.m_sizeInBytes += sizeof(EMotionFX::FileFormat::Actor_NodeGroup); chunkHeader.m_sizeInBytes += GetStringChunkSize(nodeGroup->GetNameString()); - chunkHeader.m_sizeInBytes += sizeof(uint16) * nodeGroup->GetNumNodes(); + chunkHeader.m_sizeInBytes += sizeof(uint16) * aznumeric_cast(nodeGroup->GetNumNodes()); } // endian conversion @@ -277,14 +277,14 @@ namespace ExporterLib MCORE_ASSERT(actor); // get the number of node groups - const uint32 numGroups = actor->GetNumNodeGroups(); + const size_t numGroups = actor->GetNumNodeGroups(); // create the node group array and reserve some elements AZStd::vector nodeGroups; nodeGroups.reserve(numGroups); // iterate through the node groups and add them to the array - for (uint32 i = 0; i < numGroups; ++i) + for (size_t i = 0; i < numGroups; ++i) { nodeGroups.emplace_back(actor->GetNodeGroup(i)); } diff --git a/Gems/EMotionFX/Code/EMotionFX/Source/Actor.cpp b/Gems/EMotionFX/Code/EMotionFX/Source/Actor.cpp index df039b7186..94a56cc0d5 100644 --- a/Gems/EMotionFX/Code/EMotionFX/Source/Actor.cpp +++ b/Gems/EMotionFX/Code/EMotionFX/Source/Actor.cpp @@ -142,9 +142,9 @@ namespace EMotionFX result->RecursiveAddDependencies(this); // clone all nodes groups - for (uint32 i = 0; i < m_nodeGroups.GetLength(); ++i) + for (const NodeGroup* nodeGroup : m_nodeGroups) { - result->AddNodeGroup(aznew NodeGroup(*m_nodeGroups[i])); + result->AddNodeGroup(aznew NodeGroup(*nodeGroup)); } // clone the materials @@ -948,12 +948,11 @@ namespace EMotionFX // remove all node groups void Actor::RemoveAllNodeGroups() { - const uint32 numGroups = m_nodeGroups.GetLength(); - for (uint32 i = 0; i < numGroups; ++i) + for (NodeGroup*& nodeGroup : m_nodeGroups) { - delete m_nodeGroups[i]; + delete nodeGroup; } - m_nodeGroups.Clear(); + m_nodeGroups.clear(); } @@ -1965,13 +1964,13 @@ namespace EMotionFX } - uint32 Actor::GetNumNodeGroups() const + size_t Actor::GetNumNodeGroups() const { - return m_nodeGroups.GetLength(); + return m_nodeGroups.size(); } - NodeGroup* Actor::GetNodeGroup(uint32 index) const + NodeGroup* Actor::GetNodeGroup(size_t index) const { return m_nodeGroups[index]; } @@ -1979,90 +1978,76 @@ namespace EMotionFX void Actor::AddNodeGroup(NodeGroup* newGroup) { - m_nodeGroups.Add(newGroup); + m_nodeGroups.emplace_back(newGroup); } - void Actor::RemoveNodeGroup(uint32 index, bool delFromMem) + void Actor::RemoveNodeGroup(size_t index, bool delFromMem) { if (delFromMem) { delete m_nodeGroups[index]; } - m_nodeGroups.Remove(index); + m_nodeGroups.erase(AZStd::next(begin(m_nodeGroups), index)); } void Actor::RemoveNodeGroup(NodeGroup* group, bool delFromMem) { - m_nodeGroups.RemoveByValue(group); - if (delFromMem) + const auto found = AZStd::find(begin(m_nodeGroups), end(m_nodeGroups), group); + if (found != end(m_nodeGroups)) { - delete group; + m_nodeGroups.erase(found); + if (delFromMem) + { + delete group; + } } } // find a group index by its name - uint32 Actor::FindNodeGroupIndexByName(const char* groupName) const + size_t Actor::FindNodeGroupIndexByName(const char* groupName) const { - const uint32 numGroups = m_nodeGroups.GetLength(); - for (uint32 i = 0; i < numGroups; ++i) + const auto found = AZStd::find_if(begin(m_nodeGroups), end(m_nodeGroups), [groupName](const NodeGroup* nodeGroup) { - if (m_nodeGroups[i]->GetNameString() == groupName) - { - return i; - } - } - - return MCORE_INVALIDINDEX32; + return nodeGroup->GetNameString() == groupName; + }); + return found != end(m_nodeGroups) ? AZStd::distance(begin(m_nodeGroups), found) : InvalidIndex; } // find a group index by its name, but not case sensitive - uint32 Actor::FindNodeGroupIndexByNameNoCase(const char* groupName) const + size_t Actor::FindNodeGroupIndexByNameNoCase(const char* groupName) const { - const uint32 numGroups = m_nodeGroups.GetLength(); - for (uint32 i = 0; i < numGroups; ++i) + const auto found = AZStd::find_if(begin(m_nodeGroups), end(m_nodeGroups), [groupName](const NodeGroup* nodeGroup) { - if (AzFramework::StringFunc::Equal(m_nodeGroups[i]->GetNameString().c_str(), groupName, false /* no case */)) - { - return i; - } - } - - return MCORE_INVALIDINDEX32; + return AzFramework::StringFunc::Equal(nodeGroup->GetNameString(), groupName, false /* no case */); + }); + return found != end(m_nodeGroups) ? AZStd::distance(begin(m_nodeGroups), found) : InvalidIndex; } // find a group by its name NodeGroup* Actor::FindNodeGroupByName(const char* groupName) const { - const uint32 numGroups = m_nodeGroups.GetLength(); - for (uint32 i = 0; i < numGroups; ++i) + const auto found = AZStd::find_if(begin(m_nodeGroups), end(m_nodeGroups), [groupName](const NodeGroup* nodeGroup) { - if (m_nodeGroups[i]->GetNameString() == groupName) - { - return m_nodeGroups[i]; - } - } - return nullptr; + return nodeGroup->GetNameString() == groupName; + }); + return found != end(m_nodeGroups) ? *found : nullptr; } // find a group by its name, but without case sensitivity NodeGroup* Actor::FindNodeGroupByNameNoCase(const char* groupName) const { - const uint32 numGroups = m_nodeGroups.GetLength(); - for (uint32 i = 0; i < numGroups; ++i) + const auto found = AZStd::find_if(begin(m_nodeGroups), end(m_nodeGroups), [groupName](const NodeGroup* nodeGroup) { - if (AzFramework::StringFunc::Equal(m_nodeGroups[i]->GetNameString().c_str(), groupName, false /* no case */)) - { - return m_nodeGroups[i]; - } - } - return nullptr; + return AzFramework::StringFunc::Equal(nodeGroup->GetNameString(), groupName, false /* no case */); + }); + return found != end(m_nodeGroups) ? *found : nullptr; } diff --git a/Gems/EMotionFX/Code/EMotionFX/Source/Actor.h b/Gems/EMotionFX/Code/EMotionFX/Source/Actor.h index 5c4a15e2d8..aa5c5df45c 100644 --- a/Gems/EMotionFX/Code/EMotionFX/Source/Actor.h +++ b/Gems/EMotionFX/Code/EMotionFX/Source/Actor.h @@ -23,7 +23,6 @@ // include MCore related files #include #include -#include #include // include required headers @@ -567,13 +566,13 @@ namespace EMotionFX * Get the number of node groups inside this actor object. * @result The number of node groups. */ - uint32 GetNumNodeGroups() const; + size_t GetNumNodeGroups() const; /** * Get a pointer to a given node group. * @param index The node group index, which must be in range of [0..GetNumNodeGroups()-1]. */ - NodeGroup* GetNodeGroup(uint32 index) const; + NodeGroup* GetNodeGroup(size_t index) const; /** * Add a node group. @@ -586,7 +585,7 @@ namespace EMotionFX * @param index The node group number to remove. This value must be in range of [0..GetNumNodeGroups()-1]. * @param delFromMem Set to true (default) when you wish to also delete the specified group from memory. */ - void RemoveNodeGroup(uint32 index, bool delFromMem = true); + void RemoveNodeGroup(size_t index, bool delFromMem = true); /** * Remove a given node group by its pointer. @@ -601,14 +600,14 @@ namespace EMotionFX * @param groupName The name of the group to search for. This is case sensitive. * @result The group number, or MCORE_INVALIDINDEX32 when it cannot be found. */ - uint32 FindNodeGroupIndexByName(const char* groupName) const; + size_t FindNodeGroupIndexByName(const char* groupName) const; /** * Find a group index by its name, on a non-case sensitive way. * @param groupName The name of the group to search for. This is NOT case sensitive. * @result The group number, or MCORE_INVALIDINDEX32 when it cannot be found. */ - uint32 FindNodeGroupIndexByNameNoCase(const char* groupName) const; + size_t FindNodeGroupIndexByNameNoCase(const char* groupName) const; /** * Find a node group by its name. @@ -925,7 +924,7 @@ namespace EMotionFX AZStd::vector m_nodeMirrorInfos; /**< The array of node mirror info. */ AZStd::vector< AZStd::vector< Material* > > m_materials; /**< A collection of materials (for each lod). */ AZStd::vector< MorphSetup* > m_morphSetups; /**< A morph setup for each geometry LOD. */ - MCore::SmallArray m_nodeGroups; /**< The set of node groups. */ + AZStd::vector m_nodeGroups; /**< The set of node groups. */ AZStd::shared_ptr m_physicsSetup; /**< Hit detection, ragdoll and cloth colliders, joint limits and rigid bodies. */ AZStd::shared_ptr m_simulatedObjectSetup; /**< Setup for simulated objects */ MCore::Distance::EUnitType m_unitType; /**< The unit type used on export. */ diff --git a/Gems/EMotionFX/Code/EMotionFX/Source/ActorInstance.cpp b/Gems/EMotionFX/Code/EMotionFX/Source/ActorInstance.cpp index 6097784e2f..13927f3fd3 100644 --- a/Gems/EMotionFX/Code/EMotionFX/Source/ActorInstance.cpp +++ b/Gems/EMotionFX/Code/EMotionFX/Source/ActorInstance.cpp @@ -84,8 +84,8 @@ namespace EMotionFX EnableAllNodes(); // apply actor node group default states (disable groups of nodes that are disabled on default) - const uint32 numGroups = m_actor->GetNumNodeGroups(); - for (uint32 i = 0; i < numGroups; ++i) + const size_t numGroups = m_actor->GetNumNodeGroups(); + for (size_t i = 0; i < numGroups; ++i) { if (m_actor->GetNodeGroup(i)->GetIsEnabledOnDefault() == false) // if this group is disabled on default { diff --git a/Gems/EMotionFX/Code/EMotionFX/Source/NodeGroup.cpp b/Gems/EMotionFX/Code/EMotionFX/Source/NodeGroup.cpp index 293d64775d..74e59150e0 100644 --- a/Gems/EMotionFX/Code/EMotionFX/Source/NodeGroup.cpp +++ b/Gems/EMotionFX/Code/EMotionFX/Source/NodeGroup.cpp @@ -17,7 +17,7 @@ namespace EMotionFX AZ_CLASS_ALLOCATOR_IMPL(NodeGroup, NodeAllocator, 0) - NodeGroup::NodeGroup(const AZStd::string& groupName, uint16 numNodes, bool enabledOnDefault) + NodeGroup::NodeGroup(const AZStd::string& groupName, size_t numNodes, bool enabledOnDefault) : m_name(groupName) , m_nodes(numNodes) , m_enabledOnDefault(enabledOnDefault) @@ -47,28 +47,28 @@ namespace EMotionFX // set the number of nodes - void NodeGroup::SetNumNodes(const uint16 numNodes) + void NodeGroup::SetNumNodes(const size_t numNodes) { - m_nodes.Resize(numNodes); + m_nodes.resize(numNodes); } // get the number of nodes - uint16 NodeGroup::GetNumNodes() const + size_t NodeGroup::GetNumNodes() const { - return static_cast(m_nodes.GetLength()); + return m_nodes.size(); } // set a given node to a given node number - void NodeGroup::SetNode(uint16 index, uint16 nodeIndex) + void NodeGroup::SetNode(size_t index, uint16 nodeIndex) { m_nodes[index] = nodeIndex; } // get the node number of a given index - uint16 NodeGroup::GetNode(uint16 index) const + uint16 NodeGroup::GetNode(size_t index) const { return m_nodes[index]; } @@ -77,10 +77,9 @@ namespace EMotionFX // enable all nodes in the group inside a given actor instance void NodeGroup::EnableNodes(ActorInstance* targetActorInstance) { - const uint16 numNodes = static_cast(m_nodes.GetLength()); - for (uint16 i = 0; i < numNodes; ++i) + for (uint16 node : m_nodes) { - targetActorInstance->EnableNode(m_nodes[i]); + targetActorInstance->EnableNode(node); } } @@ -88,10 +87,9 @@ namespace EMotionFX // disable all nodes in the group inside a given actor instance void NodeGroup::DisableNodes(ActorInstance* targetActorInstance) { - const uint16 numNodes = static_cast(m_nodes.GetLength()); - for (uint16 i = 0; i < numNodes; ++i) + for (uint16 node : m_nodes) { - targetActorInstance->DisableNode(m_nodes[i]); + targetActorInstance->DisableNode(node); } } @@ -99,26 +97,29 @@ namespace EMotionFX // add a given node to the group (performs a realloc internally) void NodeGroup::AddNode(uint16 nodeIndex) { - m_nodes.Add(nodeIndex); + m_nodes.emplace_back(nodeIndex); } // remove a given node by its node number void NodeGroup::RemoveNodeByNodeIndex(uint16 nodeIndex) { - m_nodes.RemoveByValue(nodeIndex); + if (const auto found = AZStd::find(begin(m_nodes), end(m_nodes), nodeIndex); found) + { + m_nodes.erase(found); + } } // remove a given array element from the list of nodes - void NodeGroup::RemoveNodeByGroupIndex(uint16 index) + void NodeGroup::RemoveNodeByGroupIndex(size_t index) { - m_nodes.Remove(index); + m_nodes.erase(AZStd::next(begin(m_nodes), index)); } // get the node array directly - MCore::SmallArray& NodeGroup::GetNodeArray() + AZStd::vector& NodeGroup::GetNodeArray() { return m_nodes; } diff --git a/Gems/EMotionFX/Code/EMotionFX/Source/NodeGroup.h b/Gems/EMotionFX/Code/EMotionFX/Source/NodeGroup.h index 7ee74415c0..02dc7d70a9 100644 --- a/Gems/EMotionFX/Code/EMotionFX/Source/NodeGroup.h +++ b/Gems/EMotionFX/Code/EMotionFX/Source/NodeGroup.h @@ -11,8 +11,8 @@ // include required files #include "EMotionFXConfig.h" #include "BaseObject.h" -#include #include +#include namespace EMotionFX @@ -34,7 +34,7 @@ namespace EMotionFX public: AZ_CLASS_ALLOCATOR_DECL - NodeGroup(const AZStd::string& groupName = {}, uint16 numNodes = 0, bool enabledOnDefault = true); + NodeGroup(const AZStd::string& groupName = {}, size_t numNodes = 0, bool enabledOnDefault = true); NodeGroup(const NodeGroup& aOther); NodeGroup& operator=(const NodeGroup& aOther); @@ -61,13 +61,13 @@ namespace EMotionFX * This will resize the array of node indices. Don't forget to initialize the node values after increasing the number of nodes though. * @param numNodes The number of nodes that are inside this group. */ - void SetNumNodes(const uint16 numNodes); + void SetNumNodes(size_t numNodes); /** * Get the number of nodes that remain inside this group. * @result The number of nodes inside this group. */ - uint16 GetNumNodes() const; + size_t GetNumNodes() const; /** * Set the value of a given node. @@ -75,14 +75,14 @@ namespace EMotionFX * @param nodeIndex The value for the given node. This is the node index which points inside the Actor object where this group will belong to. * To get access to the actual node object use Actor::GetNode( nodeIndex ). */ - void SetNode(uint16 index, uint16 nodeIndex); + void SetNode(size_t index, uint16 nodeIndex); /** * Get the node index for a given node inside the group. * @param index The node number inside this group, which must be in range of [0..GetNumNodes()-1]. * @result The node number, which points inside the Actor object. Use Actor::GetNode( returnValue ) to get access to the node information. */ - uint16 GetNode(uint16 index) const; + uint16 GetNode(size_t index) const; /** * Enable all nodes that remain inside this group, for a given actor instance. @@ -127,13 +127,13 @@ namespace EMotionFX * @param index The node index in the group. So for example an index value of 5 will remove the sixth node from the group. * The index value must be in range of [0..GetNumNodes() - 1]. */ - void RemoveNodeByGroupIndex(uint16 index); + void RemoveNodeByGroupIndex(size_t index); /** * Get direct access to the array of node indices that are part of this group. * @result A reference to the array of nodes inside this group. Please use this with care. */ - MCore::SmallArray& GetNodeArray(); + AZStd::vector& GetNodeArray(); /** * Check whether this group is enabled after actor instance creation time. @@ -155,7 +155,7 @@ namespace EMotionFX private: AZStd::string m_name; /**< The name of the group. */ - MCore::SmallArray m_nodes; /**< The node index numbers that are inside this group. */ + AZStd::vector m_nodes; /**< The node index numbers that are inside this group. */ bool m_enabledOnDefault; /**< Specifies whether this group is enabled on default (true) or disabled (false). With on default we mean after directly after the actor instance using this group has been created. */ }; } // namespace EMotionFX diff --git a/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/Plugins/StandardPlugins/Source/NodeGroups/NodeGroupManagementWidget.cpp b/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/Plugins/StandardPlugins/Source/NodeGroups/NodeGroupManagementWidget.cpp index 67aee6a5af..bd5a93a0ae 100644 --- a/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/Plugins/StandardPlugins/Source/NodeGroups/NodeGroupManagementWidget.cpp +++ b/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/Plugins/StandardPlugins/Source/NodeGroups/NodeGroupManagementWidget.cpp @@ -262,11 +262,11 @@ namespace EMStudio m_clearButton->setDisabled(disableButtons); // set the row count - m_nodeGroupsTable->setRowCount(m_actor->GetNumNodeGroups()); + m_nodeGroupsTable->setRowCount(aznumeric_caster(m_actor->GetNumNodeGroups())); // fill the table with the existing node groups - const uint32 numNodeGroups = m_actor->GetNumNodeGroups(); - for (uint32 i = 0; i < numNodeGroups; ++i) + const size_t numNodeGroups = m_actor->GetNumNodeGroups(); + for (size_t i = 0; i < numNodeGroups; ++i) { // get the nodegroup EMotionFX::NodeGroup* nodeGroup = m_actor->GetNodeGroup(i); @@ -287,16 +287,16 @@ namespace EMStudio // create table items QTableWidgetItem* tableItemGroupName = new QTableWidgetItem(nodeGroup->GetName()); - AZStd::string numGroupString = AZStd::string::format("%i", nodeGroup->GetNumNodes()); + AZStd::string numGroupString = AZStd::string::format("%zu", nodeGroup->GetNumNodes()); QTableWidgetItem* tableItemNumNodes = new QTableWidgetItem(numGroupString.c_str()); // add items to the table - m_nodeGroupsTable->setCellWidget(i, 0, checkbox); - m_nodeGroupsTable->setItem(i, 1, tableItemGroupName); - m_nodeGroupsTable->setItem(i, 2, tableItemNumNodes); + m_nodeGroupsTable->setCellWidget(aznumeric_caster(i), 0, checkbox); + m_nodeGroupsTable->setItem(aznumeric_caster(i), 1, tableItemGroupName); + m_nodeGroupsTable->setItem(aznumeric_caster(i), 2, tableItemNumNodes); // set the row height - m_nodeGroupsTable->setRowHeight(i, 21); + m_nodeGroupsTable->setRowHeight(aznumeric_caster(i), 21); } // set the old selected row if any one diff --git a/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/Plugins/StandardPlugins/Source/NodeGroups/NodeGroupWidget.cpp b/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/Plugins/StandardPlugins/Source/NodeGroups/NodeGroupWidget.cpp index 7ce09a55d7..8a20070505 100644 --- a/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/Plugins/StandardPlugins/Source/NodeGroups/NodeGroupWidget.cpp +++ b/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/Plugins/StandardPlugins/Source/NodeGroups/NodeGroupWidget.cpp @@ -150,17 +150,17 @@ namespace EMStudio m_removeNodesButton->setEnabled((m_nodeTable->rowCount() != 0) && (m_nodeTable->selectedItems().size() != 0)); // clear the table widget - m_nodeTable->setRowCount(m_nodeGroup->GetNumNodes()); + m_nodeTable->setRowCount(aznumeric_caster(m_nodeGroup->GetNumNodes())); // set header items for the table - AZStd::string headerText = AZStd::string::format("%s Nodes (%i / %zu)", ((m_nodeGroup->GetIsEnabledOnDefault()) ? "Enabled" : "Disabled"), m_nodeGroup->GetNumNodes(), m_actor->GetNumNodes()); + AZStd::string headerText = AZStd::string::format("%s Nodes (%zu / %zu)", ((m_nodeGroup->GetIsEnabledOnDefault()) ? "Enabled" : "Disabled"), m_nodeGroup->GetNumNodes(), m_actor->GetNumNodes()); QTableWidgetItem* nameHeaderItem = new QTableWidgetItem(headerText.c_str()); nameHeaderItem->setTextAlignment(Qt::AlignVCenter | Qt::AlignCenter); m_nodeTable->setHorizontalHeaderItem(0, nameHeaderItem); // fill the table with content - const uint16 numNodes = m_nodeGroup->GetNumNodes(); - for (uint16 i = 0; i < numNodes; ++i) + const size_t numNodes = m_nodeGroup->GetNumNodes(); + for (size_t i = 0; i < numNodes; ++i) { // get the nodegroup EMotionFX::Node* node = m_actor->GetSkeleton()->GetNode(m_nodeGroup->GetNode(i)); @@ -173,10 +173,10 @@ namespace EMStudio // create table items QTableWidgetItem* tableItemNodeName = new QTableWidgetItem(node->GetName()); - m_nodeTable->setItem(i, 0, tableItemNodeName); + m_nodeTable->setItem(aznumeric_caster(i), 0, tableItemNodeName); // set the row height - m_nodeTable->setRowHeight(i, 21); + m_nodeTable->setRowHeight(aznumeric_caster(i), 21); } // resize to contents and adjust header @@ -257,11 +257,9 @@ namespace EMStudio m_nodeSelectionList.Clear(); if (senderWidget == m_selectNodesButton) { - MCore::SmallArray& nodes = m_nodeGroup->GetNodeArray(); - const uint16 numNodes = nodes.GetLength(); - for (uint16 i = 0; i < numNodes; ++i) + for (const uint16 i : m_nodeGroup->GetNodeArray()) { - EMotionFX::Node* node = m_actor->GetSkeleton()->GetNode(nodes[i]); + EMotionFX::Node* node = m_actor->GetSkeleton()->GetNode(i); m_nodeSelectionList.AddNode(node); } } diff --git a/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/Plugins/StandardPlugins/Source/NodeWindow/ActorInfo.cpp b/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/Plugins/StandardPlugins/Source/NodeWindow/ActorInfo.cpp index 6bd00a2b94..9936410231 100644 --- a/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/Plugins/StandardPlugins/Source/NodeWindow/ActorInfo.cpp +++ b/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/Plugins/StandardPlugins/Source/NodeWindow/ActorInfo.cpp @@ -32,9 +32,9 @@ namespace EMStudio m_nodeCount = actor->GetNumNodes(); // node groups - const uint32 numNodeGroups = actor->GetNumNodeGroups(); + const size_t numNodeGroups = actor->GetNumNodeGroups(); m_nodeGroups.reserve(numNodeGroups); - for (uint32 i = 0; i < numNodeGroups; ++i) + for (size_t i = 0; i < numNodeGroups; ++i) { m_nodeGroups.emplace_back(actor, actor->GetNodeGroup(i)); } diff --git a/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/Plugins/StandardPlugins/Source/NodeWindow/NodeGroupInfo.cpp b/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/Plugins/StandardPlugins/Source/NodeWindow/NodeGroupInfo.cpp index 07a31f4402..f25158ae0d 100644 --- a/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/Plugins/StandardPlugins/Source/NodeWindow/NodeGroupInfo.cpp +++ b/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/Plugins/StandardPlugins/Source/NodeWindow/NodeGroupInfo.cpp @@ -23,8 +23,8 @@ namespace EMStudio m_name = nodeGroup->GetNameString(); // iterate over the nodes inside the node group - const uint32 numGroupNodes = nodeGroup->GetNumNodes(); - for (uint32 j = 0; j < numGroupNodes; ++j) + const size_t numGroupNodes = nodeGroup->GetNumNodes(); + for (size_t j = 0; j < numGroupNodes; ++j) { const uint16 nodeIndex = nodeGroup->GetNode(j); const EMotionFX::Node* node = actor->GetSkeleton()->GetNode(nodeIndex); diff --git a/Gems/EMotionFX/Code/MCore/Source/SmallArray.h b/Gems/EMotionFX/Code/MCore/Source/SmallArray.h deleted file mode 100644 index ab94cabbfe..0000000000 --- a/Gems/EMotionFX/Code/MCore/Source/SmallArray.h +++ /dev/null @@ -1,414 +0,0 @@ -/* - * 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 - * - */ - -#pragma once - -#include "StandardHeaders.h" -#include "MCoreSystem.h" -#include "Algorithms.h" -#include "MemoryManager.h" - - -namespace MCore -{ - -/** - * Dynamic array template with a maximum of 65536 items. - * It also doesn't store a memory category and maximum number of elements like the MCore::Array template. - */ -template -class SmallArray -{ - public: - /** - * The memory block ID, used inside the memory manager. - * This will make all arrays remain in the same memory blocks, which is more efficient in a lot of cases. - * However, array data can still remain in other blocks. - */ - enum { MEMORYBLOCK_ID = 2 }; - - /** - * Default constructor. - * Initializes the array so it's empty and has no memory allocated. - */ - MCORE_INLINE SmallArray() : m_data(nullptr), m_length(0) {} - - /** - * Constructor which creates a given number of elements. - * @param elems The element data. - * @param num The number of elements in 'elems'. - */ - MCORE_INLINE explicit SmallArray(T* elems, uint32 num) : m_length(num) { m_data = (T*)MCore::Allocate(m_length * sizeof(T), MCORE_MEMCATEGORY_SMALLARRAY, MEMORYBLOCK_ID, MCORE_FILE, MCORE_LINE); for (uint32 i=0; i 0) { m_data = (T*)MCore::Allocate(m_length * sizeof(T), MCORE_MEMCATEGORY_SMALLARRAY, MEMORYBLOCK_ID, MCORE_FILE, MCORE_LINE); for (uint32 i=0; i& other) : m_data(nullptr), m_length(0) { *this = other; } - - /** - * Move constructor. - * @param other The array to move the data from. - */ - SmallArray(SmallArray&& other) { m_data=other.m_data; m_length=other.m_length; other.m_data=nullptr; other.m_length=0; } - - /** - * Destructor. Deletes all entry data. - * However, if you store pointers to objects, these objects won't be deleted.
- * Example:
- *
-         * SmallArray< Object* > data;
-         * for (uint32 i=0; i<10; i++)
-         *    data.Add( new Object() );
-         * 
- * Now when the array 'data' will be destructed, it will NOT free up the memory of the integers which you allocated by hand, using new. - * In order to free up this memory, you can do this: - *
-         * for (uint32 i=0; i
-         */
-        ~SmallArray()                                                            { for (uint32 i=0; i); return result; }
-
-        /**
-         * Set a given element to a given value.
-         * @param pos The element number.
-         * @param value The value to store at that element number.
-         */
-        MCORE_INLINE void SetElem(uint32 pos, const T& value)                    { m_data[pos] = value; }
-
-        /**
-         * Add a given element to the back of the array.
-         * @param x The element to add.
-         */
-        MCORE_INLINE void Add(const T& x)                                        { Grow(++m_length); Construct(m_length-1, x); }
-
-        /**
-         * Add a given array to the back of this array.
-         * @param a The array to add.
-         */
-        MCORE_INLINE void Add(const SmallArray& a)                            { uint32 l=m_length; Grow(m_length+a.m_length); for (uint32 i=0; i 0) Remove((uint32)0); }
-
-        /**
-         * Remove the last array element.
-         */
-        MCORE_INLINE void RemoveLast()                                            { if (m_length > 0) Destruct(--m_length); }
-
-        /**
-         * Insert an empty element (default constructed) at a given position in the array.
-         * @param pos The position to create the empty element.
-         */
-        MCORE_INLINE void Insert(uint32 pos)                                    { Grow(m_length+1); MoveElements(pos+1, pos, m_length-pos-1); Construct(pos); }
-
-        /**
-         * Insert a given element at a given position in the array.
-         * @param pos The position to insert the empty element.
-         * @param x The element to store at this position.
-         */
-        MCORE_INLINE void Insert(uint32 pos, const T& x)                        { Grow(m_length+1); MoveElements(pos+1, pos, m_length-pos-1); Construct(pos, x); }
-
-        /**
-         * Remove an element at a given position.
-         * @param pos The element number to remove.
-         */
-        MCORE_INLINE void Remove(uint32 pos)                                    { Destruct(pos); MoveElements(pos, pos+1, m_length-pos-1); m_length--; }
-
-        /**
-         * Remove a given number of elements starting at a given position in the array.
-         * @param pos The start element, so to start removing from.
-         * @param num The number of elements to remove from this position.
-         */
-        MCORE_INLINE void Remove(uint32 pos, uint32 num)                        { for (uint32 i=pos; i
-         * And we perform a SwapRemove(2), we will remove element C and place the last element (G) at the empty created position where C was located.
-         * So we will get this:
- * AB.DEFG [where . is empty, after we did the SwapRemove(2)]
- * ABGDEF [this is the result. G has been moved to the empty position]. - */ - MCORE_INLINE void SwapRemove(uint32 pos) { Destruct(pos); if (pos != m_length-1) { Construct(pos, m_data[m_length-1]); Destruct(m_length-1); } m_length--; } // remove element at and place the last element of the array in that position - - /** - * Swap two elements. - * @param pos1 The first element number. - * @param pos2 The second element number. - */ - MCORE_INLINE void Swap(uint32 pos1, uint32 pos2) { if (pos1 != pos2) MCore::Swap(GetItem(pos1), GetItem(pos2)); } - - /** - * Clear the array contents. So GetLength() will return 0 after performing this method. - * @param clearMem If set to true (default) the allocated memory will also be released. If set to false, GetMaxLength() will still return the number of elements - * which the array contained before calling the Clear() method. - */ - MCORE_INLINE void Clear(bool clearMem=true) { for (uint32 i=0; i= newLength) return; uint32 oldLen=m_length; Grow(newLength); for (uint32 i=oldLen; i operators). - * The method will sort all elements between the given 'first' and 'last' element (first and last are also included in the sort). - * @param first The first element to start sorting. - * @param last The last element to sort (when set to MCORE_INVALIDINDEX32, GetLength()-1 will be used). - * @param cmp The compare function. - */ - MCORE_INLINE void Sort(uint32 first=0, uint32 last=MCORE_INVALIDINDEX32, CmpFunc cmp=StdCmp) { if (last==MCORE_INVALIDINDEX32) last=m_length-1; InnerSort(first, last, cmp); } - - /** - * Performs a sort on a given part of the array. - * @param first The first element to start the sorting at. - * @param last The last element to end the sorting. - * @param cmp The compare function. - */ - MCORE_INLINE void InnerSort(int32 first, int32 last, CmpFunc cmp) { if (first >= last) return; int32 split=Partition(first, last, cmp); InnerSort(first, split-1, cmp); InnerSort(split+1, last, cmp); } - - /** - * Resize the array to a given size. - * This does not mean an actual realloc will be made. This will only happen when the new length is bigger than the maxLength of the array. - * @param newLength The new length the array should be. - * @result returns false if the allocation/reallocation of the array failed - */ - bool Resize(uint32 newLength) - { - // check for growing or shrinking array - if (newLength > m_length) - { - // growing array, construct empty elements at end of array - uint32 oldLen = m_length; - GrowExact(newLength); - if (m_data == nullptr) - { - return false; - } - for (uint32 i=oldLen; i 0) - MCore::MemMove(m_data+destIndex, m_data+sourceIndex, numElements * sizeof(T)); - } - - // operators - bool operator==(const SmallArray& other) const { if (m_length != other.m_length) return false; for (uint32 i=0; i& operator= (const SmallArray& other) { if (&other != this) { Clear(); Grow(other.m_length); for (uint32 i=0; i& operator= (SmallArray&& other) { MCORE_ASSERT(&other != this); if (m_data!=nullptr) MCore::Free(m_data); m_data=other.m_data; m_length=other.m_length; other.m_data=nullptr; other.m_length=0; return *this; } - SmallArray& operator+=(const T& other) { Add(other); return *this; } - SmallArray& operator+=(const SmallArray& other) { Add(other); return *this; } - MCORE_INLINE T& operator[](const uint32 index) { MCORE_ASSERT(indexFree(); return; } - if (m_data) - m_data = (T*)MCore::Realloc(m_data, newSize * sizeof(T), MCORE_MEMCATEGORY_SMALLARRAY, MEMORYBLOCK_ID, MCORE_FILE, MCORE_LINE); - else - m_data = (T*)MCore::Allocate(newSize * sizeof(T), MCORE_MEMCATEGORY_SMALLARRAY, MEMORYBLOCK_ID, MCORE_FILE, MCORE_LINE); - } - MCORE_INLINE void Free() { m_length=0; if (m_data) MCore::Free(m_data); m_data=nullptr; } - MCORE_INLINE void Construct(uint32 index, const T& original) { ::new(m_data+index) T(original); } // copy-construct an element at which is a copy of - MCORE_INLINE void Construct(uint32 index) { ::new(m_data+index) T; } // construct an element at place - MCORE_INLINE void Destruct(uint32 index) - { - #if (MCORE_COMPILER == MCORE_COMPILER_MSVC) // work around a compiler bug, marking this index parameter as unused - MCORE_UNUSED(index); - #endif - (m_data+index)->~T(); - } // destruct an element at - - // partition part of array (for sorting) - int32 Partition(int32 left, int32 right, CmpFunc cmp) - { - ::MCore::Swap(m_data[left], m_data[ (left+right)>>1 ]); - - T& target = m_data[right]; - int32 i = left-1; - int32 j = right; - - bool neverQuit = true; // workaround to disable a "warning C4127: conditional expression is constant" - while (neverQuit) - { - while (i < j) { if (cmp(m_data[++i], target) >= 0) break; } - while (j > i) { if (cmp(m_data[--j], target) <= 0) break; } - if (i >= j) break; - ::MCore::Swap(m_data[i], m_data[j]); - } - - ::MCore::Swap(m_data[i], m_data[right]); - return i; - } -}; - -} // namespace MCore diff --git a/Gems/EMotionFX/Code/MCore/mcore_files.cmake b/Gems/EMotionFX/Code/MCore/mcore_files.cmake index 47b35e004b..8b57d0b7f3 100644 --- a/Gems/EMotionFX/Code/MCore/mcore_files.cmake +++ b/Gems/EMotionFX/Code/MCore/mcore_files.cmake @@ -106,7 +106,6 @@ set(FILES Source/StaticAllocator.cpp Source/StaticAllocator.h Source/StaticString.h - Source/SmallArray.h Source/StandardHeaders.h Source/Stream.h Source/StringConversions.cpp From 7c5120b72fadb51ebee07a408e3f62e6b7a63679 Mon Sep 17 00:00:00 2001 From: Jacob Hilliard <64656371+jcbhl@users.noreply.github.com> Date: Tue, 10 Aug 2021 09:57:04 -0700 Subject: [PATCH 33/37] [ATOM-16021] Initial continuous capture support (#2624) * Profiler: Implement continuous capture internals Signed-off-by: Jacob Hilliard * Profiler: extend ProfileCaptureSystemComponent Implements dumping of saved CPU profiling data to a local file, blocking call. Signed-off-by: Jacob Hilliard * Profiler: Working IO thread for serialization Signed-off-by: Jacob Hilliard * Profiler: switch to AZ::JobFunction for IO Signed-off-by: Jacob Hilliard * Profiler: move to a ring buffer for storage Signed-off-by: Jacob Hilliard * Profiler: switch back to IO thread Signed-off-by: Jacob Hilliard * Profiler: add TODO Signed-off-by: Jacob Hilliard * Profiler: correct thread safety issues Signed-off-by: Jacob Hilliard --- .../Atom/Feature/Utils/ProfilingCaptureBus.h | 8 +- .../ProfilingCaptureSystemComponent.cpp | 166 ++++++++++++------ .../Source/ProfilingCaptureSystemComponent.h | 8 + .../RHI/Code/Include/Atom/RHI/CpuProfiler.h | 9 + .../Code/Include/Atom/RHI/CpuProfilerImpl.h | 23 ++- .../RHI/Code/Source/RHI/CpuProfilerImpl.cpp | 63 ++++++- .../Include/Atom/Utils/ImGuiCpuProfiler.inl | 28 +++ 7 files changed, 243 insertions(+), 62 deletions(-) diff --git a/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/Utils/ProfilingCaptureBus.h b/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/Utils/ProfilingCaptureBus.h index 03f522ba44..b22e82f456 100644 --- a/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/Utils/ProfilingCaptureBus.h +++ b/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/Utils/ProfilingCaptureBus.h @@ -28,9 +28,15 @@ namespace AZ //! Dump the PipelineStatistics from passes to a json file. virtual bool CapturePassPipelineStatistics(const AZStd::string& outputFilePath) = 0; - //! Dump the Cpu Profiling Statistics to a json file. + //! Dump a single frame of Cpu profiling data virtual bool CaptureCpuProfilingStatistics(const AZStd::string& outputFilePath) = 0; + //! Start a multiframe capture of CPU profiling data. + virtual bool BeginContinuousCpuProfilingCapture() = 0; + + //! End and dump an in-progress continuous capture. + virtual bool EndContinuousCpuProfilingCapture(const AZStd::string& outputFilePath) = 0; + //! Dump the benchmark metadata to a json file. virtual bool CaptureBenchmarkMetadata(const AZStd::string& benchmarkName, const AZStd::string& outputFilePath) = 0; }; diff --git a/Gems/Atom/Feature/Common/Code/Source/ProfilingCaptureSystemComponent.cpp b/Gems/Atom/Feature/Common/Code/Source/ProfilingCaptureSystemComponent.cpp index 1e2d549e7a..add6d0e098 100644 --- a/Gems/Atom/Feature/Common/Code/Source/ProfilingCaptureSystemComponent.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/ProfilingCaptureSystemComponent.cpp @@ -25,6 +25,7 @@ #include #include #include +#include namespace AZ { @@ -157,14 +158,15 @@ namespace AZ Name m_groupName; Name m_regionName; uint16_t m_stackDepth; - AZStd::sys_time_t m_elapsedInNanoseconds; + AZStd::sys_time_t m_startTick; + AZStd::sys_time_t m_endTick; }; AZ_TYPE_INFO(CpuProfilingStatisticsSerializer, "{D5B02946-0D27-474F-9A44-364C2706DD41}"); static void Reflect(AZ::ReflectContext* context); CpuProfilingStatisticsSerializer() = default; - CpuProfilingStatisticsSerializer(const RHI::CpuProfiler::TimeRegionMap& timeRegionMap); + CpuProfilingStatisticsSerializer(const AZStd::ring_buffer& continuousData); AZStd::vector m_cpuProfilingStatisticsSerializerEntries; }; @@ -327,17 +329,20 @@ namespace AZ // --- CpuProfilingStatisticsSerializer --- - CpuProfilingStatisticsSerializer::CpuProfilingStatisticsSerializer(const RHI::CpuProfiler::TimeRegionMap& timeRegionMap) + CpuProfilingStatisticsSerializer::CpuProfilingStatisticsSerializer(const AZStd::ring_buffer& continuousData) { // Create serializable entries - for (auto& threadEntry : timeRegionMap) + for (const auto& timeRegionMap : continuousData) { - for (auto& cachedRegionEntry : threadEntry.second) + for (const auto& threadEntry : timeRegionMap) { - m_cpuProfilingStatisticsSerializerEntries.insert( - m_cpuProfilingStatisticsSerializerEntries.end(), - cachedRegionEntry.second.begin(), - cachedRegionEntry.second.end()); + for (const auto& cachedRegionEntry : threadEntry.second) + { + m_cpuProfilingStatisticsSerializerEntries.insert( + m_cpuProfilingStatisticsSerializerEntries.end(), + cachedRegionEntry.second.begin(), + cachedRegionEntry.second.end()); + } } } } @@ -359,19 +364,11 @@ namespace AZ CpuProfilingStatisticsSerializer::CpuProfilingStatisticsSerializerEntry::CpuProfilingStatisticsSerializerEntry(const RHI::CachedTimeRegion& cachedTimeRegion) { - // Converts ticks to Nanoseconds - static const auto ticksToNanoSeconds = [](AZStd::sys_time_t elapsedInTicks) -> AZStd::sys_time_t - { - const AZStd::sys_time_t ticksPerSecond = AZStd::GetTimeTicksPerSecond(); - - const AZStd::sys_time_t timeInNanoseconds = (elapsedInTicks * 1000000) / (ticksPerSecond / 1000); - return timeInNanoseconds; - }; - m_groupName = cachedTimeRegion.m_groupRegionName->m_groupName; m_regionName = cachedTimeRegion.m_groupRegionName->m_regionName; m_stackDepth = cachedTimeRegion.m_stackDepth; - m_elapsedInNanoseconds = ticksToNanoSeconds(cachedTimeRegion.m_endTick - cachedTimeRegion.m_startTick); + m_startTick = cachedTimeRegion.m_startTick; + m_endTick = cachedTimeRegion.m_endTick; } void CpuProfilingStatisticsSerializer::CpuProfilingStatisticsSerializerEntry::Reflect(AZ::ReflectContext* context) @@ -383,7 +380,8 @@ namespace AZ ->Field("groupName", &CpuProfilingStatisticsSerializerEntry::m_groupName) ->Field("regionName", &CpuProfilingStatisticsSerializerEntry::m_regionName) ->Field("stackDepth", &CpuProfilingStatisticsSerializerEntry::m_stackDepth) - ->Field("elapsedInNanoseconds", &CpuProfilingStatisticsSerializerEntry::m_elapsedInNanoseconds) + ->Field("startTick", &CpuProfilingStatisticsSerializerEntry::m_startTick) + ->Field("endTick", &CpuProfilingStatisticsSerializerEntry::m_endTick) ; } } @@ -474,6 +472,12 @@ namespace AZ TickBus::Handler::BusDisconnect(); ProfilingCaptureRequestBus::Handler::BusDisconnect(); + + // Block deactivation until the IO thread has finished serializing the CPU data + if (m_cpuDataSerializationThread.joinable()) + { + m_cpuDataSerializationThread.join(); + } } bool ProfilingCaptureSystemComponent::CapturePassTimestamp(const AZStd::string& outputFilePath) @@ -641,6 +645,43 @@ namespace AZ return captureStarted; } + bool SerializeCpuProfilingData(const AZStd::ring_buffer& data, AZStd::string outputFilePath, bool wasEnabled) + { + AZ_TracePrintf("ProfilingCaptureSystemComponent", "Beginning serialization of %zu frames of profiling data\n", data.size()); + JsonSerializerSettings serializationSettings; + serializationSettings.m_keepDefaults = true; + + CpuProfilingStatisticsSerializer serializer(data); + + const auto saveResult = JsonSerializationUtils::SaveObjectToFile(&serializer, + outputFilePath, (CpuProfilingStatisticsSerializer*)nullptr, &serializationSettings); + + AZStd::string captureInfo = outputFilePath; + if (!saveResult.IsSuccess()) + { + captureInfo = AZStd::string::format("Failed to save Cpu Profiling Statistics data to file '%s'. Error: %s", + outputFilePath.c_str(), + saveResult.GetError().c_str()); + AZ_Warning("ProfilingCaptureSystemComponent", false, captureInfo.c_str()); + } + else + { + AZ_Printf("ProfilingCaptureSystemComponent", "Cpu profiling statistics was saved to file [%s]\n", outputFilePath.c_str()); + } + + // Disable the profiler again + if (!wasEnabled) + { + RHI::CpuProfiler::Get()->SetProfilerEnabled(false); + } + + // Notify listeners that the pass' PipelineStatistics queries capture has finished. + ProfilingCaptureNotificationBus::Broadcast(&ProfilingCaptureNotificationBus::Events::OnCaptureCpuProfilingStatisticsFinished, + saveResult.IsSuccess(), + captureInfo); + return saveResult.IsSuccess(); + } + bool ProfilingCaptureSystemComponent::CaptureCpuProfilingStatistics(const AZStd::string& outputFilePath) { // Start the cpu profiling @@ -652,40 +693,10 @@ namespace AZ const bool captureStarted = m_cpuProfilingStatisticsCapture.StartCapture([this, outputFilePath, wasEnabled]() { - JsonSerializerSettings serializationSettings; - serializationSettings.m_keepDefaults = true; - - // Get time Cpu profiled time regions - const RHI::CpuProfiler::TimeRegionMap& timeRegionMap = RHI::CpuProfiler::Get()->GetTimeRegionMap(); - - CpuProfilingStatisticsSerializer serializer(timeRegionMap); - const auto saveResult = JsonSerializationUtils::SaveObjectToFile(&serializer, - outputFilePath, (CpuProfilingStatisticsSerializer*)nullptr, &serializationSettings); - - AZStd::string captureInfo = outputFilePath; - if (!saveResult.IsSuccess()) - { - captureInfo = AZStd::string::format("Failed to save Cpu Profiling Statistics data to file '%s'. Error: %s", - outputFilePath.c_str(), - saveResult.GetError().c_str()); - AZ_Warning("ProfilingCaptureSystemComponent", false, captureInfo.c_str()); - } - else - { - AZ_Printf("ProfilingCaptureSystemComponent", "Cpu profiling statistics was saved to file [%s]\n", outputFilePath.c_str()); - } - - // Disable the profiler again - if (!wasEnabled) - { - RHI::CpuProfiler::Get()->SetProfilerEnabled(false); - } - - // Notify listeners that the pass' PipelineStatistics queries capture has finished. - ProfilingCaptureNotificationBus::Broadcast(&ProfilingCaptureNotificationBus::Events::OnCaptureCpuProfilingStatisticsFinished, - saveResult.IsSuccess(), - captureInfo); - + // Blocking call for a single frame of data, avoid thread overhead + AZStd::ring_buffer singleFrameData; + singleFrameData.push_back(RHI::CpuProfiler::Get()->GetTimeRegionMap()); + SerializeCpuProfilingData(singleFrameData, outputFilePath, wasEnabled); }); // Start the TickBus. @@ -697,6 +708,53 @@ namespace AZ return captureStarted; } + bool ProfilingCaptureSystemComponent::BeginContinuousCpuProfilingCapture() + { + return AZ::RHI::CpuProfiler::Get()->BeginContinuousCapture(); + } + + bool ProfilingCaptureSystemComponent::EndContinuousCpuProfilingCapture(const AZStd::string& outputFilePath) + { + bool expected = false; + if (m_cpuDataSerializationInProgress.compare_exchange_strong(expected, true)) + { + AZStd::ring_buffer captureResult; + const bool captureEnded = AZ::RHI::CpuProfiler::Get()->EndContinuousCapture(captureResult); + if (!captureEnded) + { + AZ_TracePrintf("ProfilingCaptureSystemComponent", "Could not end the continuous capture, is one in progress?\n"); + m_cpuDataSerializationInProgress.store(false); + return false; + } + + // cpuProfilingData could be 1GB+ once saved, so use an IO thread to write it to disk. + auto threadIoFunction = + [data = AZStd::move(captureResult), filePath = AZStd::string(outputFilePath), &flag = m_cpuDataSerializationInProgress]() + { + SerializeCpuProfilingData(data, filePath, true); + flag.store(false); + }; + + // If the thread object already exists (ex. we have already serialized data), join. This will not block since + // m_cpuDataSerializationInProgress was false, meaning the IO thread has already completed execution. + // TODO Use a reusable thread implementation over repeated creation + destruction of threads [ATOM-16214] + if (m_cpuDataSerializationThread.joinable()) + { + m_cpuDataSerializationThread.join(); + } + + auto thread = AZStd::thread(threadIoFunction); + m_cpuDataSerializationThread = AZStd::move(thread); + + return true; + } + + AZ_TracePrintf( + "ProfilingSystemCaptureComponent", + "Cannot end a continuous capture - another serialization is currently in progress\n"); + return false; + } + bool ProfilingCaptureSystemComponent::CaptureBenchmarkMetadata(const AZStd::string& benchmarkName, const AZStd::string& outputFilePath) { const bool captureStarted = m_benchmarkMetadataCapture.StartCapture([this, benchmarkName, outputFilePath]() diff --git a/Gems/Atom/Feature/Common/Code/Source/ProfilingCaptureSystemComponent.h b/Gems/Atom/Feature/Common/Code/Source/ProfilingCaptureSystemComponent.h index c401d27f30..1846767139 100644 --- a/Gems/Atom/Feature/Common/Code/Source/ProfilingCaptureSystemComponent.h +++ b/Gems/Atom/Feature/Common/Code/Source/ProfilingCaptureSystemComponent.h @@ -12,6 +12,7 @@ #include #include +#include namespace AZ { @@ -71,6 +72,8 @@ namespace AZ bool CaptureCpuFrameTime(const AZStd::string& outputFilePath) override; bool CapturePassPipelineStatistics(const AZStd::string& outputFilePath) override; bool CaptureCpuProfilingStatistics(const AZStd::string& outputFilePath) override; + bool BeginContinuousCpuProfilingCapture() override; + bool EndContinuousCpuProfilingCapture(const AZStd::string& outputFilePath) override; bool CaptureBenchmarkMetadata(const AZStd::string& benchmarkName, const AZStd::string& outputFilePath) override; private: @@ -86,6 +89,11 @@ namespace AZ DelayedQueryCaptureHelper m_pipelineStatisticsCapture; DelayedQueryCaptureHelper m_cpuProfilingStatisticsCapture; DelayedQueryCaptureHelper m_benchmarkMetadataCapture; + + // Flag passed by reference to the CPU profiling data serialization job, blocks new continuous capture requests when set. + AZStd::atomic_bool m_cpuDataSerializationInProgress = false; + + AZStd::thread m_cpuDataSerializationThread; }; } } diff --git a/Gems/Atom/RHI/Code/Include/Atom/RHI/CpuProfiler.h b/Gems/Atom/RHI/Code/Include/Atom/RHI/CpuProfiler.h index 1c10b8829f..2248474820 100644 --- a/Gems/Atom/RHI/Code/Include/Atom/RHI/CpuProfiler.h +++ b/Gems/Atom/RHI/Code/Include/Atom/RHI/CpuProfiler.h @@ -10,6 +10,7 @@ #include #include +#include #include #include @@ -82,6 +83,14 @@ namespace AZ //! Get the last frame's TimeRegionMap virtual const TimeRegionMap& GetTimeRegionMap() const = 0; + //! Begin a continuous capture. Blocks the profiler from being toggled off until EndContinuousCapture is called. + [[nodiscard]] virtual bool BeginContinuousCapture() = 0; + + //! Flush the CPU Profiler's saved data into the passed ring buffer . + [[nodiscard]] virtual bool EndContinuousCapture(AZStd::ring_buffer& flushTarget) = 0; + + virtual bool IsContinuousCaptureInProgress() const = 0; + //! Enable/Disable the CpuProfiler virtual void SetProfilerEnabled(bool enabled) = 0; diff --git a/Gems/Atom/RHI/Code/Include/Atom/RHI/CpuProfilerImpl.h b/Gems/Atom/RHI/Code/Include/Atom/RHI/CpuProfilerImpl.h index 640c67858b..7d3b0c5b81 100644 --- a/Gems/Atom/RHI/Code/Include/Atom/RHI/CpuProfilerImpl.h +++ b/Gems/Atom/RHI/Code/Include/Atom/RHI/CpuProfilerImpl.h @@ -106,13 +106,18 @@ namespace AZ void OnSystemTick() final override; //! CpuProfiler overrides... - void BeginTimeRegion(TimeRegion& timeRegion) final; - void EndTimeRegion() final; - const TimeRegionMap& GetTimeRegionMap() const final; - void SetProfilerEnabled(bool enabled) final; - bool IsProfilerEnabled() const final; + void BeginTimeRegion(TimeRegion& timeRegion) final override; + void EndTimeRegion() final override; + const TimeRegionMap& GetTimeRegionMap() const final override; + bool BeginContinuousCapture() final override; + bool EndContinuousCapture(AZStd::ring_buffer& flushTarget) final override; + bool IsContinuousCaptureInProgress() const final override; + void SetProfilerEnabled(bool enabled) final override; + bool IsProfilerEnabled() const final override; private: + static constexpr AZStd::size_t MaxFramesToSave = 2 * 60 * 120; // 2 minutes of 120fps + // Lazily create and register the local thread data void RegisterThreadStorage(); @@ -134,6 +139,14 @@ namespace AZ AZStd::shared_mutex m_shutdownMutex; bool m_initialized = false; + + AZStd::mutex m_continuousCaptureEndingMutex; + + AZStd::atomic_bool m_continuousCaptureInProgress; + + // Stores multiple frames of profiling data, size is controlled by MaxFramesToSave. Flushed when EndContinuousCapture is called. + // Ring buffer so that we can have fast append of new data + removal of old profiling data with good cache locality. + AZStd::ring_buffer m_continuousCaptureData; }; }; // namespace RPI diff --git a/Gems/Atom/RHI/Code/Source/RHI/CpuProfilerImpl.cpp b/Gems/Atom/RHI/Code/Source/RHI/CpuProfilerImpl.cpp index 73a1881ead..cdfd4ac469 100644 --- a/Gems/Atom/RHI/Code/Source/RHI/CpuProfilerImpl.cpp +++ b/Gems/Atom/RHI/Code/Source/RHI/CpuProfilerImpl.cpp @@ -81,6 +81,7 @@ namespace AZ Interface::Register(this); m_initialized = true; SystemTickBus::Handler::BusConnect(); + m_continuousCaptureData.set_capacity(10); } void CpuProfilerImpl::Shutdown() @@ -101,6 +102,8 @@ namespace AZ m_registeredThreads.clear(); m_timeRegionMap.clear(); m_initialized = false; + m_continuousCaptureInProgress.store(false); + m_continuousCaptureData.clear(); SystemTickBus::Handler::BusDisconnect(); } @@ -141,12 +144,54 @@ namespace AZ return m_timeRegionMap; } + bool CpuProfilerImpl::BeginContinuousCapture() + { + bool expected = false; + if (m_continuousCaptureInProgress.compare_exchange_strong(expected, true)) + { + m_enabled = true; + AZ_TracePrintf("Profiler", "Continuous capture started\n"); + return true; + } + + AZ_TracePrintf("Profiler", "Attempting to start a continuous capture while one already in progress"); + return false; + } + + bool CpuProfilerImpl::EndContinuousCapture(AZStd::ring_buffer& flushTarget) + { + if (!m_continuousCaptureInProgress.load()) + { + AZ_TracePrintf("Profiler", "Attempting to end a continuous capture while one not in progress"); + return false; + } + + if (m_continuousCaptureEndingMutex.try_lock()) + { + m_enabled = false; + flushTarget = AZStd::move(m_continuousCaptureData); + m_continuousCaptureData.clear(); + AZ_TracePrintf("Profiler", "Continuous capture ended\n"); + m_continuousCaptureInProgress.store(false); + + m_continuousCaptureEndingMutex.unlock(); + return true; + } + + return false; + } + + bool CpuProfilerImpl::IsContinuousCaptureInProgress() const + { + return m_continuousCaptureInProgress.load(); + } + void CpuProfilerImpl::SetProfilerEnabled(bool enabled) { AZStd::unique_lock lock(m_threadRegisterMutex); - // Early out if the state is already the same - if (m_enabled == enabled) + // Early out if the state is already the same or a continuous capture is in progress + if (m_enabled == enabled || m_continuousCaptureInProgress.load()) { return; } @@ -179,6 +224,20 @@ namespace AZ { return; } + + if (m_continuousCaptureInProgress.load() && m_continuousCaptureEndingMutex.try_lock()) + { + if (m_continuousCaptureData.full() && m_continuousCaptureData.size() != MaxFramesToSave) + { + const AZStd::size_t size = m_continuousCaptureData.size(); + m_continuousCaptureData.set_capacity(AZStd::min(MaxFramesToSave, size + size / 2)); + } + + m_continuousCaptureData.push_back(AZStd::move(m_timeRegionMap)); + m_timeRegionMap.clear(); + m_continuousCaptureEndingMutex.unlock(); + } + AZStd::unique_lock lock(m_threadRegisterMutex); // Iterate through all the threads, and collect the thread's cached time regions diff --git a/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiCpuProfiler.inl b/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiCpuProfiler.inl index ae707a10a7..ffc9a60c00 100644 --- a/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiCpuProfiler.inl +++ b/Gems/Atom/Utils/Code/Include/Atom/Utils/ImGuiCpuProfiler.inl @@ -129,6 +129,34 @@ namespace AZ m_captureToFile = true; } + ImGui::SameLine(); + bool isInProgress = RHI::CpuProfiler::Get()->IsContinuousCaptureInProgress(); + if (ImGui::Button(isInProgress ? "End" : "Begin")) + { + if (isInProgress) + { + AZStd::sys_time_t timeNow = AZStd::GetTimeNowSecond(); + AZStd::string timeString; + AZStd::to_string(timeString, timeNow); + u64 currentTick = AZ::RPI::RPISystemInterface::Get()->GetCurrentTick(); + const AZStd::string frameDataFilePath = AZStd::string::format( + "@user@/CpuProfiler/%s_%llu.json", + timeString.c_str(), + currentTick); + char resolvedPath[AZ::IO::MaxPathLength]; + AZ::IO::FileIOBase::GetInstance()->ResolvePath(frameDataFilePath.c_str(), resolvedPath, AZ::IO::MaxPathLength); + m_lastCapturedFilePath = resolvedPath; + AZ::Render::ProfilingCaptureRequestBus::Broadcast( + &AZ::Render::ProfilingCaptureRequestBus::Events::EndContinuousCpuProfilingCapture, frameDataFilePath); + } + + else + { + AZ::Render::ProfilingCaptureRequestBus::Broadcast( + &AZ::Render::ProfilingCaptureRequestBus::Events::BeginContinuousCpuProfilingCapture); + } + } + if (!m_lastCapturedFilePath.empty()) { ImGui::SameLine(); From 8d0f9f4114630a35ebceb677086f7e16ff5c1df2 Mon Sep 17 00:00:00 2001 From: Scott Romero <24445312+AMZN-ScottR@users.noreply.github.com> Date: Tue, 10 Aug 2021 12:19:31 -0700 Subject: [PATCH 34/37] [development] updated file regex when uploading latest tagged installer (#2987) The "Latest" tagged installer uploads were failing the file filter because the upload script now collects files as full path before performing the regex. Signed-off-by: AMZN-ScottR 24445312+AMZN-ScottR@users.noreply.github.com --- cmake/Platform/Windows/PackagingPostBuild.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/Platform/Windows/PackagingPostBuild.cmake b/cmake/Platform/Windows/PackagingPostBuild.cmake index 3f364b89c5..5e09743373 100644 --- a/cmake/Platform/Windows/PackagingPostBuild.cmake +++ b/cmake/Platform/Windows/PackagingPostBuild.cmake @@ -189,7 +189,7 @@ if(CPACK_AUTO_GEN_TAG) upload_to_s3( ${_latest_upload_url} ${_temp_dir} - "(${_non_versioned_exe}|build_tag.txt)$" + ".*(${_non_versioned_exe}|build_tag.txt)$" ) # cleanup the temp files From ab1b6ff3b40640ce502a340b447ab8939f143189 Mon Sep 17 00:00:00 2001 From: michabr <82236305+michabr@users.noreply.github.com> Date: Tue, 10 Aug 2021 12:41:24 -0700 Subject: [PATCH 35/37] Add LyShine gem runtime dependency to Material Editor in AutomatedTesting project (#2996) Signed-off-by: abrmich --- AutomatedTesting/Gem/Code/CMakeLists.txt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/AutomatedTesting/Gem/Code/CMakeLists.txt b/AutomatedTesting/Gem/Code/CMakeLists.txt index 76c0db3f5c..58ffd957d6 100644 --- a/AutomatedTesting/Gem/Code/CMakeLists.txt +++ b/AutomatedTesting/Gem/Code/CMakeLists.txt @@ -57,6 +57,12 @@ if (PAL_TRAIT_BUILD_HOST_TOOLS) TARGETS Editor VARIANTS Tools) + # The Material Editor needs the Lyshine "Tools" gem variant for the custom LyShine pass + ly_enable_gems( + PROJECT_NAME AutomatedTesting GEMS LyShine + TARGETS MaterialEditor + VARIANTS Tools) + # The pipeline tools use "Builders" gem variants: ly_enable_gems( PROJECT_NAME AutomatedTesting GEM_FILE enabled_gems.cmake From 4450eb4e224c1352addf25db767a61ef3a812e39 Mon Sep 17 00:00:00 2001 From: Esteban Papp <81431996+amznestebanpapp@users.noreply.github.com> Date: Tue, 10 Aug 2021 13:24:20 -0700 Subject: [PATCH 36/37] fix new warning hit Signed-off-by: Esteban Papp <81431996+amznestebanpapp@users.noreply.github.com> --- .../Source/MorphTargetsWindow/PhonemeSelectionWindow.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/Plugins/StandardPlugins/Source/MorphTargetsWindow/PhonemeSelectionWindow.cpp b/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/Plugins/StandardPlugins/Source/MorphTargetsWindow/PhonemeSelectionWindow.cpp index 4f1aa5b42b..a0d0e62abd 100644 --- a/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/Plugins/StandardPlugins/Source/MorphTargetsWindow/PhonemeSelectionWindow.cpp +++ b/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/Plugins/StandardPlugins/Source/MorphTargetsWindow/PhonemeSelectionWindow.cpp @@ -330,7 +330,7 @@ namespace EMStudio const size_t numMorphTargets = m_morphSetup->GetNumMorphTargets(); const uint32 numPhonemeSets = m_morphTarget->GetNumAvailablePhonemeSets(); int insertPosition = 0; - for (int i = 1; i < numPhonemeSets; ++i) + for (uint32 i = 1; i < numPhonemeSets; ++i) { // check if another morph target already has this phoneme set. bool phonemeSetFound = false; From 181998f8107f8776c3bc137732059f0a3b67639e Mon Sep 17 00:00:00 2001 From: Esteban Papp <81431996+amznestebanpapp@users.noreply.github.com> Date: Tue, 10 Aug 2021 14:05:14 -0700 Subject: [PATCH 37/37] Fixes a nightly build and a CMake warning (#2941) * Addressing CMake warning and removing timeouts that are the same as the default Signed-off-by: Esteban Papp <81431996+amznestebanpapp@users.noreply.github.com> * missed this one Signed-off-by: Esteban Papp <81431996+amznestebanpapp@users.noreply.github.com> * Making it an error so developers stops putting timeouts longer than the allowed one since it causes AR issues Signed-off-by: Esteban Papp <81431996+amznestebanpapp@users.noreply.github.com> * fixing warning from the nightly build Signed-off-by: Esteban Papp <81431996+amznestebanpapp@users.noreply.github.com> * disabling test that is triggering a timeout Signed-off-by: Esteban Papp <81431996+amznestebanpapp@users.noreply.github.com> --- .../Gem/PythonTests/Blast/CMakeLists.txt | 1 - .../Gem/PythonTests/NvCloth/CMakeLists.txt | 1 - .../PythonAssetBuilder/CMakeLists.txt | 1 - .../Gem/PythonTests/WhiteBox/CMakeLists.txt | 1 - .../asset_processor_tests/CMakeLists.txt | 24 +++++++++---------- .../Gem/PythonTests/editor/CMakeLists.txt | 4 ---- .../PythonTests/largeworlds/CMakeLists.txt | 11 --------- .../Gem/PythonTests/physics/CMakeLists.txt | 3 --- .../Gem/PythonTests/prefab/CMakeLists.txt | 1 - .../Gem/PythonTests/scripting/CMakeLists.txt | 2 -- .../Gem/PythonTests/smoke/CMakeLists.txt | 1 - Gems/PhysX/Code/CMakeLists.txt | 1 - cmake/LYTestWrappers.cmake | 2 +- 13 files changed, 12 insertions(+), 41 deletions(-) diff --git a/AutomatedTesting/Gem/PythonTests/Blast/CMakeLists.txt b/AutomatedTesting/Gem/PythonTests/Blast/CMakeLists.txt index 7048a6bd46..172eded09a 100644 --- a/AutomatedTesting/Gem/PythonTests/Blast/CMakeLists.txt +++ b/AutomatedTesting/Gem/PythonTests/Blast/CMakeLists.txt @@ -12,7 +12,6 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS) TEST_SUITE main TEST_SERIAL TRUE PATH ${CMAKE_CURRENT_LIST_DIR}/TestSuite_Active.py - TIMEOUT 1500 RUNTIME_DEPENDENCIES Legacy::Editor AZ::AssetProcessor diff --git a/AutomatedTesting/Gem/PythonTests/NvCloth/CMakeLists.txt b/AutomatedTesting/Gem/PythonTests/NvCloth/CMakeLists.txt index a39cdf04cf..3913041f88 100644 --- a/AutomatedTesting/Gem/PythonTests/NvCloth/CMakeLists.txt +++ b/AutomatedTesting/Gem/PythonTests/NvCloth/CMakeLists.txt @@ -13,7 +13,6 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS) TEST_REQUIRES gpu TEST_SERIAL PATH ${CMAKE_CURRENT_LIST_DIR}/TestSuite_Active.py - TIMEOUT 1500 RUNTIME_DEPENDENCIES Legacy::Editor AZ::AssetProcessor diff --git a/AutomatedTesting/Gem/PythonTests/PythonAssetBuilder/CMakeLists.txt b/AutomatedTesting/Gem/PythonTests/PythonAssetBuilder/CMakeLists.txt index 9b6542b3e3..905470e08f 100644 --- a/AutomatedTesting/Gem/PythonTests/PythonAssetBuilder/CMakeLists.txt +++ b/AutomatedTesting/Gem/PythonTests/PythonAssetBuilder/CMakeLists.txt @@ -12,7 +12,6 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS) TEST_SUITE periodic TEST_SERIAL PATH ${CMAKE_CURRENT_LIST_DIR} - TIMEOUT 1500 RUNTIME_DEPENDENCIES Legacy::Editor AZ::AssetProcessor diff --git a/AutomatedTesting/Gem/PythonTests/WhiteBox/CMakeLists.txt b/AutomatedTesting/Gem/PythonTests/WhiteBox/CMakeLists.txt index b5fcfcc44c..1c7a02862d 100644 --- a/AutomatedTesting/Gem/PythonTests/WhiteBox/CMakeLists.txt +++ b/AutomatedTesting/Gem/PythonTests/WhiteBox/CMakeLists.txt @@ -12,7 +12,6 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS) TEST_SUITE main TEST_SERIAL PATH ${CMAKE_CURRENT_LIST_DIR}/TestSuite_Active.py - TIMEOUT 1500 RUNTIME_DEPENDENCIES Legacy::Editor AZ::AssetProcessor diff --git a/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/CMakeLists.txt b/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/CMakeLists.txt index 0170d73af0..3d7a9204e2 100644 --- a/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/CMakeLists.txt +++ b/AutomatedTesting/Gem/PythonTests/assetpipeline/asset_processor_tests/CMakeLists.txt @@ -92,17 +92,17 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS) AZ::AssetProcessor ) - ly_add_pytest( - NAME AssetPipelineTests.AssetBundler - PATH ${CMAKE_CURRENT_LIST_DIR}/asset_bundler_batch_tests.py - EXCLUDE_TEST_RUN_TARGET_FROM_IDE - TEST_SERIAL - TIMEOUT 2400 - TEST_SUITE periodic - RUNTIME_DEPENDENCIES - AZ::AssetProcessor - AZ::AssetBundlerBatch - ) + # Issue #3017 + #ly_add_pytest( + # NAME AssetPipelineTests.AssetBundler + # PATH ${CMAKE_CURRENT_LIST_DIR}/asset_bundler_batch_tests.py + # EXCLUDE_TEST_RUN_TARGET_FROM_IDE + # TEST_SERIAL + # TEST_SUITE periodic + # RUNTIME_DEPENDENCIES + # AZ::AssetProcessor + # AZ::AssetBundlerBatch + #) ly_add_pytest( NAME AssetPipelineTests.AssetBundler_SandBox @@ -111,7 +111,6 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS) PYTEST_MARKS "SUITE_sandbox" # run only sandbox tests in this file EXCLUDE_TEST_RUN_TARGET_FROM_IDE TEST_SERIAL - TIMEOUT 1500 RUNTIME_DEPENDENCIES AZ::AssetProcessor AZ::AssetBundlerBatch @@ -133,7 +132,6 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS) PATH ${CMAKE_CURRENT_LIST_DIR}/missing_dependency_tests.py EXCLUDE_TEST_RUN_TARGET_FROM_IDE TEST_SERIAL - TIMEOUT 1500 TEST_SUITE periodic RUNTIME_DEPENDENCIES AZ::AssetProcessorBatch diff --git a/AutomatedTesting/Gem/PythonTests/editor/CMakeLists.txt b/AutomatedTesting/Gem/PythonTests/editor/CMakeLists.txt index afde0a0d94..fa35bb25c6 100644 --- a/AutomatedTesting/Gem/PythonTests/editor/CMakeLists.txt +++ b/AutomatedTesting/Gem/PythonTests/editor/CMakeLists.txt @@ -13,7 +13,6 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS AND PAL_TRAIT_ TEST_SERIAL PATH ${CMAKE_CURRENT_LIST_DIR} PYTEST_MARKS "SUITE_main and not REQUIRES_gpu" - TIMEOUT 1500 RUNTIME_DEPENDENCIES Legacy::Editor AZ::AssetProcessor @@ -28,7 +27,6 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS AND PAL_TRAIT_ TEST_SERIAL PATH ${CMAKE_CURRENT_LIST_DIR} PYTEST_MARKS "SUITE_periodic and not REQUIRES_gpu" - TIMEOUT 1500 RUNTIME_DEPENDENCIES Legacy::Editor AZ::AssetProcessor @@ -44,7 +42,6 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS AND PAL_TRAIT_ TEST_REQUIRES gpu PATH ${CMAKE_CURRENT_LIST_DIR} PYTEST_MARKS "SUITE_main and REQUIRES_gpu" - TIMEOUT 1500 RUNTIME_DEPENDENCIES Legacy::Editor AZ::AssetProcessor @@ -59,7 +56,6 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS AND PAL_TRAIT_ TEST_SERIAL PATH ${CMAKE_CURRENT_LIST_DIR} PYTEST_MARKS "SUITE_sandbox" - TIMEOUT 1500 RUNTIME_DEPENDENCIES Legacy::Editor AZ::AssetProcessor diff --git a/AutomatedTesting/Gem/PythonTests/largeworlds/CMakeLists.txt b/AutomatedTesting/Gem/PythonTests/largeworlds/CMakeLists.txt index 351ca19031..043485d869 100644 --- a/AutomatedTesting/Gem/PythonTests/largeworlds/CMakeLists.txt +++ b/AutomatedTesting/Gem/PythonTests/largeworlds/CMakeLists.txt @@ -16,7 +16,6 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS AND PAL_TRAIT_ TEST_SUITE main PATH ${CMAKE_CURRENT_LIST_DIR}/dyn_veg PYTEST_MARKS "not SUITE_sandbox and not SUITE_periodic and not SUITE_benchmark" - TIMEOUT 1500 RUNTIME_DEPENDENCIES AZ::AssetProcessor Legacy::Editor @@ -33,7 +32,6 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS AND PAL_TRAIT_ TEST_SUITE sandbox PATH ${CMAKE_CURRENT_LIST_DIR}/dyn_veg PYTEST_MARKS "SUITE_sandbox" - TIMEOUT 1500 RUNTIME_DEPENDENCIES AZ::AssetProcessor Legacy::Editor @@ -49,7 +47,6 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS AND PAL_TRAIT_ TEST_SUITE periodic PATH ${CMAKE_CURRENT_LIST_DIR}/dyn_veg PYTEST_MARKS "SUITE_periodic and dynveg_filter" - TIMEOUT 1500 RUNTIME_DEPENDENCIES AZ::AssetProcessor Legacy::Editor @@ -64,7 +61,6 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS AND PAL_TRAIT_ TEST_SUITE periodic PATH ${CMAKE_CURRENT_LIST_DIR}/dyn_veg PYTEST_MARKS "SUITE_periodic and dynveg_modifier" - TIMEOUT 1500 RUNTIME_DEPENDENCIES AZ::AssetProcessor Legacy::Editor @@ -79,7 +75,6 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS AND PAL_TRAIT_ TEST_SUITE periodic PATH ${CMAKE_CURRENT_LIST_DIR}/dyn_veg PYTEST_MARKS "SUITE_periodic and dynveg_regression" - TIMEOUT 1500 RUNTIME_DEPENDENCIES AZ::AssetProcessor Legacy::Editor @@ -94,7 +89,6 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS AND PAL_TRAIT_ TEST_SUITE periodic PATH ${CMAKE_CURRENT_LIST_DIR}/dyn_veg PYTEST_MARKS "SUITE_periodic and dynveg_area" - TIMEOUT 1500 RUNTIME_DEPENDENCIES AZ::AssetProcessor Legacy::Editor @@ -109,7 +103,6 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS AND PAL_TRAIT_ TEST_SUITE periodic PATH ${CMAKE_CURRENT_LIST_DIR}/dyn_veg PYTEST_MARKS "SUITE_periodic and dynveg_misc" - TIMEOUT 1500 RUNTIME_DEPENDENCIES AZ::AssetProcessor Legacy::Editor @@ -124,7 +117,6 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS AND PAL_TRAIT_ TEST_SUITE periodic PATH ${CMAKE_CURRENT_LIST_DIR}/dyn_veg PYTEST_MARKS "SUITE_periodic and dynveg_surfacetagemitter" - TIMEOUT 1500 RUNTIME_DEPENDENCIES AZ::AssetProcessor Legacy::Editor @@ -140,7 +132,6 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS AND PAL_TRAIT_ TEST_SUITE main PATH ${CMAKE_CURRENT_LIST_DIR}/landscape_canvas PYTEST_MARKS "not SUITE_sandbox and not SUITE_periodic and not SUITE_benchmark" - TIMEOUT 1500 RUNTIME_DEPENDENCIES AZ::AssetProcessor Legacy::Editor @@ -155,7 +146,6 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS AND PAL_TRAIT_ TEST_SUITE periodic PATH ${CMAKE_CURRENT_LIST_DIR}/landscape_canvas PYTEST_MARKS "SUITE_periodic" - TIMEOUT 1500 RUNTIME_DEPENDENCIES AZ::AssetProcessor Legacy::Editor @@ -170,7 +160,6 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS AND PAL_TRAIT_ TEST_SERIAL TEST_SUITE periodic PATH ${CMAKE_CURRENT_LIST_DIR}/gradient_signal - TIMEOUT 1500 RUNTIME_DEPENDENCIES AZ::AssetProcessor Legacy::Editor diff --git a/AutomatedTesting/Gem/PythonTests/physics/CMakeLists.txt b/AutomatedTesting/Gem/PythonTests/physics/CMakeLists.txt index 248bf3fdc5..eb37db1943 100644 --- a/AutomatedTesting/Gem/PythonTests/physics/CMakeLists.txt +++ b/AutomatedTesting/Gem/PythonTests/physics/CMakeLists.txt @@ -12,7 +12,6 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS) TEST_SUITE main TEST_SERIAL PATH ${CMAKE_CURRENT_LIST_DIR}/TestSuite_Main.py - TIMEOUT 1500 RUNTIME_DEPENDENCIES Legacy::Editor AZ::AssetProcessor @@ -25,7 +24,6 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS) TEST_SUITE periodic TEST_SERIAL PATH ${CMAKE_CURRENT_LIST_DIR}/TestSuite_Periodic.py - TIMEOUT 1500 RUNTIME_DEPENDENCIES Legacy::Editor AZ::AssetProcessor @@ -38,7 +36,6 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS) TEST_SUITE sandbox TEST_SERIAL PATH ${CMAKE_CURRENT_LIST_DIR}/TestSuite_Sandbox.py - TIMEOUT 1500 RUNTIME_DEPENDENCIES Legacy::Editor AZ::AssetProcessor diff --git a/AutomatedTesting/Gem/PythonTests/prefab/CMakeLists.txt b/AutomatedTesting/Gem/PythonTests/prefab/CMakeLists.txt index 1d9c54fa40..48c24d1ebf 100644 --- a/AutomatedTesting/Gem/PythonTests/prefab/CMakeLists.txt +++ b/AutomatedTesting/Gem/PythonTests/prefab/CMakeLists.txt @@ -13,7 +13,6 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS) TEST_SUITE main TEST_SERIAL PATH ${CMAKE_CURRENT_LIST_DIR}/TestSuite_Main.py - TIMEOUT 1500 RUNTIME_DEPENDENCIES Legacy::Editor AZ::AssetProcessor diff --git a/AutomatedTesting/Gem/PythonTests/scripting/CMakeLists.txt b/AutomatedTesting/Gem/PythonTests/scripting/CMakeLists.txt index 80b6e9a54e..25988216b2 100644 --- a/AutomatedTesting/Gem/PythonTests/scripting/CMakeLists.txt +++ b/AutomatedTesting/Gem/PythonTests/scripting/CMakeLists.txt @@ -12,7 +12,6 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS) TEST_SUITE periodic TEST_SERIAL PATH ${CMAKE_CURRENT_LIST_DIR}/TestSuite_Periodic.py - TIMEOUT 1500 RUNTIME_DEPENDENCIES Legacy::Editor AZ::AssetProcessor @@ -25,7 +24,6 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS) TEST_SUITE sandbox TEST_SERIAL PATH ${CMAKE_CURRENT_LIST_DIR}/TestSuite_Sandbox.py - TIMEOUT 1500 RUNTIME_DEPENDENCIES Legacy::Editor AZ::AssetProcessor diff --git a/AutomatedTesting/Gem/PythonTests/smoke/CMakeLists.txt b/AutomatedTesting/Gem/PythonTests/smoke/CMakeLists.txt index 2c1d03b5a2..3fc4f3db0e 100644 --- a/AutomatedTesting/Gem/PythonTests/smoke/CMakeLists.txt +++ b/AutomatedTesting/Gem/PythonTests/smoke/CMakeLists.txt @@ -18,7 +18,6 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS) TEST_SERIAL PATH ${CMAKE_CURRENT_LIST_DIR} PYTEST_MARKS "SUITE_smoke" - TIMEOUT 1500 RUNTIME_DEPENDENCIES AZ::AssetProcessor AZ::PythonBindingsExample diff --git a/Gems/PhysX/Code/CMakeLists.txt b/Gems/PhysX/Code/CMakeLists.txt index a69019249f..80e8bee8e3 100644 --- a/Gems/PhysX/Code/CMakeLists.txt +++ b/Gems/PhysX/Code/CMakeLists.txt @@ -186,7 +186,6 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED) ly_add_googlebenchmark( NAME Gem::PhysX.Benchmarks TARGET Gem::PhysX.Tests - TIMEOUT 1500 #25mins ) list(APPEND testTargets PhysX.Tests) diff --git a/cmake/LYTestWrappers.cmake b/cmake/LYTestWrappers.cmake index efb19a20dd..8e71cb3db1 100644 --- a/cmake/LYTestWrappers.cmake +++ b/cmake/LYTestWrappers.cmake @@ -114,7 +114,7 @@ function(ly_add_test) if(NOT ly_add_test_TIMEOUT) set(ly_add_test_TIMEOUT ${LY_TEST_DEFAULT_TIMEOUT}) elseif(ly_add_test_TIMEOUT GREATER LY_TEST_DEFAULT_TIMEOUT) - message(WARNING "TIMEOUT for test ${ly_add_test_NAME} set at ${ly_add_test_TIMEOUT} seconds which is longer than the default of ${LY_TEST_DEFAULT_TIMEOUT}. Allowing a single module to run exceedingly long creates problems in a CI pipeline.") + message(FATAL_ERROR "TIMEOUT for test ${ly_add_test_NAME} set at ${ly_add_test_TIMEOUT} seconds which is longer than the default of ${LY_TEST_DEFAULT_TIMEOUT}. Allowing a single module to run exceedingly long creates problems in a CI pipeline.") endif() if(NOT ly_add_test_TEST_COMMAND)