From 4e755a50e35a1f5ac1b011dc4baf89e6d97f116e Mon Sep 17 00:00:00 2001 From: Jim Flynn Date: Sun, 29 Mar 2020 17:48:26 +0100 Subject: IVGCVSW-4595 Add IFileOnlyPacketHandlers to file only profiling connection Change-Id: Ib49a8cbbf323da4109cdab9750e6c4d276e484b7 Signed-off-by: Jim Flynn --- Android.mk | 14 +- CMakeLists.txt | 12 +- include/armnn/IRuntime.hpp | 7 +- include/armnn/profiling/ILocalPacketHandler.hpp | 48 ++++++ include/armnn/profiling/ITimelineDecoder.hpp | 12 ++ profiling/common/include/ProfilingException.hpp | 29 ++++ src/profiling/FileOnlyProfilingConnection.cpp | 162 ++++++++++++++++++++- src/profiling/FileOnlyProfilingConnection.hpp | 48 +++++- src/profiling/IProfilingConnection.hpp | 1 + src/profiling/NullProfilingConnection.hpp | 41 ++++++ src/profiling/ProfilingConnectionFactory.cpp | 7 + src/profiling/ProfilingService.hpp | 2 + src/profiling/ProfilingUtils.cpp | 29 ++++ src/profiling/ProfilingUtils.hpp | 3 + .../test/FileOnlyProfilingDecoratorTests.cpp | 68 ++++++++- src/profiling/test/PrintPacketHeaderHandler.cpp | 31 ++++ src/profiling/test/PrintPacketHeaderHandler.hpp | 26 ++++ src/profiling/test/TestTimelinePacketHandler.cpp | 132 +++++++++++++++++ src/profiling/test/TestTimelinePacketHandler.hpp | 90 ++++++++++++ src/profiling/test/TimelineModel.cpp | 57 ++++++++ src/profiling/test/TimelineModel.hpp | 67 +++++++++ 21 files changed, 867 insertions(+), 19 deletions(-) create mode 100644 include/armnn/profiling/ILocalPacketHandler.hpp create mode 100644 profiling/common/include/ProfilingException.hpp create mode 100644 src/profiling/NullProfilingConnection.hpp create mode 100644 src/profiling/test/PrintPacketHeaderHandler.cpp create mode 100644 src/profiling/test/PrintPacketHeaderHandler.hpp create mode 100644 src/profiling/test/TestTimelinePacketHandler.cpp create mode 100644 src/profiling/test/TestTimelinePacketHandler.hpp create mode 100644 src/profiling/test/TimelineModel.cpp create mode 100644 src/profiling/test/TimelineModel.hpp diff --git a/Android.mk b/Android.mk index 2cd6e50c3b..9ae4a993b9 100644 --- a/Android.mk +++ b/Android.mk @@ -10,6 +10,7 @@ OPENCL_HEADER_PATH := $(LOCAL_PATH)/../clframework/include NN_HEADER_PATH := $(LOCAL_PATH)/../../../../frameworks/ml/nn/runtime/include ARMNN_HEADER_PATH := $(LOCAL_PATH)/include ARMNN_PROFILING_INCLUDE_PATH := $(LOCAL_PATH)/profiling +ARMNN_TIMELINE_DECODER_INCLUDE_PATH := $(LOCAL_PATH)/src/timelineDecoder ARMNN_MAIN_HEADER_PATH := $(LOCAL_PATH)/src ARMNN_SOURCE_HEADER_PATH := $(LOCAL_PATH)/src/armnn ARMNN_SOURCE_UTILS_HEADER_PATH := $(LOCAL_PATH)/src/armnnUtils @@ -66,6 +67,7 @@ LOCAL_EXPORT_C_INCLUDES := \ $(ARMNN_MAIN_HEADER_PATH) \ $(ARMNN_SOURCE_HEADER_PATH) \ $(ARMNN_PROFILING_INCLUDE_PATH) \ + $(ARMNN_TIMELINE_DECODER_INCLUDE_PATH) \ $(ARMNN_SOURCE_UTILS_HEADER_PATH) \ $(ARMNN_PROFILING_HEADER_PATH) \ $(ARMNN_BACKENDS_HEADER_PATH) @@ -75,6 +77,7 @@ LOCAL_C_INCLUDES := \ $(NN_HEADER_PATH) \ $(ARMNN_HEADER_PATH) \ $(ARMNN_PROFILING_INCLUDE_PATH) \ + $(ARMNN_TIMELINE_DECODER_INCLUDE_PATH) \ $(ARMNN_MAIN_HEADER_PATH) \ $(ARMNN_SOURCE_HEADER_PATH) \ $(ARMNN_SOURCE_UTILS_HEADER_PATH) \ @@ -193,8 +196,8 @@ LOCAL_SRC_FILES := \ src/profiling/ConnectionAcknowledgedCommandHandler.cpp \ src/profiling/CounterDirectory.cpp \ src/profiling/CounterIdMap.cpp \ - src/profiling/DirectoryCaptureCommandHandler.cpp \ src/profiling/DeactivateTimelineReportingCommandHandler.cpp \ + src/profiling/DirectoryCaptureCommandHandler.cpp \ src/profiling/FileOnlyProfilingConnection.cpp \ src/profiling/Holder.cpp \ src/profiling/LabelsAndEventClasses.cpp \ @@ -216,7 +219,10 @@ LOCAL_SRC_FILES := \ src/profiling/SocketProfilingConnection.cpp \ src/profiling/TimelinePacketWriterFactory.cpp \ src/profiling/TimelineUtilityMethods.cpp \ - src/profiling/backends/BackendProfiling.cpp + src/profiling/backends/BackendProfiling.cpp \ + src/timelineDecoder/TimelineCaptureCommandHandler.cpp \ + src/timelineDecoder/TimelineDecoder.cpp \ + src/timelineDecoder/TimelineDirectoryCaptureCommandHandler.cpp LOCAL_STATIC_LIBRARIES := \ arm_compute_library \ @@ -299,6 +305,7 @@ LOCAL_C_INCLUDES := \ $(NN_HEADER_PATH) \ $(ARMNN_HEADER_PATH) \ $(ARMNN_PROFILING_INCLUDE_PATH) \ + $(ARMNN_TIMELINE_DECODER_INCLUDE_PATH) \ $(ARMNN_MAIN_HEADER_PATH) \ $(ARMNN_SOURCE_HEADER_PATH) \ $(ARMNN_SOURCE_UTILS_HEADER_PATH) \ @@ -384,12 +391,15 @@ LOCAL_SRC_FILES := \ src/armnnUtils/test/TensorUtilsTest.cpp \ src/profiling/test/BufferTests.cpp \ src/profiling/test/FileOnlyProfilingDecoratorTests.cpp \ + src/profiling/test/PrintPacketHeaderHandler.cpp \ src/profiling/test/ProfilingConnectionDumpToFileDecoratorTests.cpp \ src/profiling/test/ProfilingGuidTest.cpp \ src/profiling/test/ProfilingTests.cpp \ src/profiling/test/ProfilingTestUtils.cpp \ src/profiling/test/SendCounterPacketTests.cpp \ src/profiling/test/SendTimelinePacketTests.cpp \ + src/profiling/test/TestTimelinePacketHandler.cpp \ + src/profiling/test/TimelineModel.cpp \ src/profiling/test/TimelinePacketTests.cpp \ src/profiling/test/TimelineUtilityMethodsTests.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 8f3aa0368a..54376b6109 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -252,6 +252,7 @@ list(APPEND armnn_sources include/armnn/utility/NumericCast.hpp include/armnn/utility/PolymorphicDowncast.hpp include/armnn/utility/StringUtils.hpp + profiling/common/include/ProfilingException.hpp profiling/common/include/SocketConnectionException.hpp src/armnn/layers/LayerCloneBase.hpp src/armnn/layers/LayerWithParameters.hpp @@ -505,6 +506,7 @@ list(APPEND armnn_sources src/profiling/IProfilingConnectionFactory.hpp src/profiling/LabelsAndEventClasses.cpp src/profiling/LabelsAndEventClasses.hpp + src/profiling/NullProfilingConnection.hpp src/profiling/Packet.hpp src/profiling/PacketBuffer.cpp src/profiling/PacketBuffer.hpp @@ -575,7 +577,7 @@ target_include_directories(armnn PRIVATE src/profiling) target_link_libraries(armnn armnnUtils) target_link_libraries(armnn ${CMAKE_DL_LIBS}) -if ("${CMAKE_SYSTEM_NAME}" STREQUAL Windows) +if ("${CMAKE_SYSTEM_NAME}" STREQUAL Windows) target_link_libraries(armnn Ws2_32.lib) endif() @@ -676,6 +678,8 @@ if(BUILD_UNIT_TESTS) src/armnnUtils/test/TensorUtilsTest.cpp src/profiling/test/BufferTests.cpp src/profiling/test/FileOnlyProfilingDecoratorTests.cpp + src/profiling/test/PrintPacketHeaderHandler.cpp + src/profiling/test/PrintPacketHeaderHandler.hpp src/profiling/test/ProfilingConnectionDumpToFileDecoratorTests.cpp src/profiling/test/ProfilingGuidTest.cpp src/profiling/test/ProfilingMocks.hpp @@ -685,6 +689,10 @@ if(BUILD_UNIT_TESTS) src/profiling/test/ProfilingTestUtils.hpp src/profiling/test/SendCounterPacketTests.cpp src/profiling/test/SendCounterPacketTests.hpp + src/profiling/test/TestTimelinePacketHandler.cpp + src/profiling/test/TestTimelinePacketHandler.hpp + src/profiling/test/TimelineModel.cpp + src/profiling/test/TimelineModel.hpp src/profiling/test/SendTimelinePacketTests.cpp src/profiling/test/TimelinePacketTests.cpp src/profiling/test/TimelineUtilityMethodsTests.cpp @@ -998,7 +1006,7 @@ if (BUILD_ARMNN_SERIALIZER AND (BUILD_TF_PARSER OR BUILD_TF_LITE_PARSER OR BUILD ${Boost_FILESYSTEM_LIBRARY} ${Boost_PROGRAM_OPTIONS_LIBRARY}) addDllCopyCommands(ArmnnConverter) -endif() + endif() if(BUILD_TIMELINE_DECODER) add_subdirectory(src/timelineDecoder) diff --git a/include/armnn/IRuntime.hpp b/include/armnn/IRuntime.hpp index 06d249ea8c..48ad7c494d 100644 --- a/include/armnn/IRuntime.hpp +++ b/include/armnn/IRuntime.hpp @@ -10,6 +10,7 @@ #include "Tensor.hpp" #include "Types.hpp" #include "TypesUtils.hpp" +#include "profiling/ILocalPacketHandler.hpp" #include @@ -61,21 +62,23 @@ public: { ExternalProfilingOptions() : m_EnableProfiling(false) + , m_TimelineEnabled(false) , m_OutgoingCaptureFile("") , m_IncomingCaptureFile("") , m_FileOnly(false) , m_CapturePeriod(LOWEST_CAPTURE_PERIOD) , m_FileFormat("binary") - , m_TimelineEnabled(false) + , m_LocalPacketHandlers() {} bool m_EnableProfiling; + bool m_TimelineEnabled; std::string m_OutgoingCaptureFile; std::string m_IncomingCaptureFile; bool m_FileOnly; uint32_t m_CapturePeriod; std::string m_FileFormat; - bool m_TimelineEnabled; + std::vector m_LocalPacketHandlers; }; ExternalProfilingOptions m_ProfilingOptions; diff --git a/include/armnn/profiling/ILocalPacketHandler.hpp b/include/armnn/profiling/ILocalPacketHandler.hpp new file mode 100644 index 0000000000..a2b9d5fd56 --- /dev/null +++ b/include/armnn/profiling/ILocalPacketHandler.hpp @@ -0,0 +1,48 @@ +// +// Copyright © 2020 Arm Ltd. All rights reserved. +// SPDX-License-Identifier: MIT +// + +#pragma once + + +#include + +#include +#include +#include + +namespace armnn +{ + +namespace profiling +{ +// forward declare to prevent a circular dependency +class Packet; +class IProfilingConnection; + +class ILocalPacketHandler +{ +public: + virtual ~ILocalPacketHandler() {}; + + /// @return lists the headers of the packets that this handler accepts + /// only these packets will get sent to this handler. + /// If this function returns an empty list then ALL packets + /// will be sent to the PacketHandler i.e. a universal handler. + virtual std::vector GetHeadersAccepted() = 0; + + /// process the packet + virtual void HandlePacket(const Packet& packet) = 0; + + /// Set a profiling connection on the handler. Only need to implement this + /// function if the handler will be writing data back to the profiled application. + virtual void SetConnection(IProfilingConnection* profilingConnection) {armnn::IgnoreUnused(profilingConnection);} +}; + +using ILocalPacketHandlerPtr = std::unique_ptr; +using ILocalPacketHandlerSharedPtr = std::shared_ptr; + +} // namespace profiling + +} // namespace armnn \ No newline at end of file diff --git a/include/armnn/profiling/ITimelineDecoder.hpp b/include/armnn/profiling/ITimelineDecoder.hpp index 3a02bb5599..5d22ad8e3d 100644 --- a/include/armnn/profiling/ITimelineDecoder.hpp +++ b/include/armnn/profiling/ITimelineDecoder.hpp @@ -27,6 +27,18 @@ public: LabelLink /// Head uses label Tail (Tail MUST be a guid of a label). }; + static char const* GetRelationshipAsCString(RelationshipType rType) + { + switch (rType) + { + case RelationshipType::RetentionLink: return "RetentionLink"; + case RelationshipType::ExecutionLink: return "ExecutionLink"; + case RelationshipType::DataLink: return "DataLink"; + case RelationshipType::LabelLink: return "LabelLink"; + default: return "Unknown"; + } + } + struct Entity { uint64_t m_Guid; diff --git a/profiling/common/include/ProfilingException.hpp b/profiling/common/include/ProfilingException.hpp new file mode 100644 index 0000000000..532c2d49f7 --- /dev/null +++ b/profiling/common/include/ProfilingException.hpp @@ -0,0 +1,29 @@ +// +// Copyright © 2020 Arm Ltd. All rights reserved. +// SPDX-License-Identifier: MIT +// +#pragma once + +#include +#include + +namespace armnnProfiling +{ + +/// General Exception class for Profiling code +class ProfilingException : public std::exception +{ +public: + explicit ProfilingException(const std::string& message) : m_Message(message) {}; + + /// @return - Error message of ProfilingException + virtual const char* what() const noexcept override + { + return m_Message.c_str(); + } + +private: + std::string m_Message; +}; + +} // namespace armnnProfiling diff --git a/src/profiling/FileOnlyProfilingConnection.cpp b/src/profiling/FileOnlyProfilingConnection.cpp index f9bdde961f..5947d2c081 100644 --- a/src/profiling/FileOnlyProfilingConnection.cpp +++ b/src/profiling/FileOnlyProfilingConnection.cpp @@ -8,6 +8,7 @@ #include +#include #include #include #include @@ -32,10 +33,19 @@ bool FileOnlyProfilingConnection::IsOpen() const void FileOnlyProfilingConnection::Close() { // Dump any unread packets out of the queue. - for (unsigned int i = 0; i < m_PacketQueue.size(); i++) + size_t initialSize = m_PacketQueue.size(); + for (size_t i = 0; i < initialSize; ++i) { m_PacketQueue.pop(); } + // dispose of the processing thread + m_KeepRunning.store(false); + if (m_LocalHandlersThread.joinable()) + { + // make sure the thread wakes up and sees it has to stop + m_ConditionPacketReadable.notify_one(); + m_LocalHandlersThread.join(); + } } bool FileOnlyProfilingConnection::WaitForStreamMeta(const unsigned char* buffer, uint32_t length) @@ -112,10 +122,11 @@ bool FileOnlyProfilingConnection::SendCounterSelectionPacket() bool FileOnlyProfilingConnection::WritePacket(const unsigned char* buffer, uint32_t length) { ARMNN_ASSERT(buffer); + Packet packet = ReceivePacket(buffer, length); // Read Header and determine case uint32_t outgoingHeaderAsWords[2]; - PackageActivity packageActivity = GetPackageActivity(buffer, outgoingHeaderAsWords); + PackageActivity packageActivity = GetPackageActivity(packet, outgoingHeaderAsWords); switch (packageActivity) { @@ -160,6 +171,7 @@ bool FileOnlyProfilingConnection::WritePacket(const unsigned char* buffer, uint3 break; } } + ForwardPacketToHandlers(packet); return true; } @@ -181,10 +193,10 @@ Packet FileOnlyProfilingConnection::ReadPacket(uint32_t timeout) return returnedPacket; } -PackageActivity FileOnlyProfilingConnection::GetPackageActivity(const unsigned char* buffer, uint32_t headerAsWords[2]) +PackageActivity FileOnlyProfilingConnection::GetPackageActivity(const Packet& packet, uint32_t headerAsWords[2]) { - headerAsWords[0] = ToUint32(buffer, m_Endianness); - headerAsWords[1] = ToUint32(buffer + 4, m_Endianness); + headerAsWords[0] = packet.GetHeader(); + headerAsWords[1] = packet.GetLength(); if (headerAsWords[0] == 0x20000) // Packet family = 0 Packet Id = 2 { return PackageActivity::CounterDirectory; @@ -221,6 +233,146 @@ void FileOnlyProfilingConnection::Fail(const std::string& errorMessage) throw RuntimeException(errorMessage); } +/// Adds a local packet handler to the FileOnlyProfilingConnection. Invoking this will start +/// a processing thread that will ensure that processing of packets will happen on a separate +/// thread from the profiling services send thread and will therefore protect against the +/// profiling message buffer becoming exhausted because packet handling slows the dispatch. +void FileOnlyProfilingConnection::AddLocalPacketHandler(ILocalPacketHandlerSharedPtr localPacketHandler) +{ + m_PacketHandlers.push_back(std::move(localPacketHandler)); + ILocalPacketHandlerSharedPtr localCopy = m_PacketHandlers.back(); + localCopy->SetConnection(this); + if (localCopy->GetHeadersAccepted().empty()) + { + //this is a universal handler + m_UniversalHandlers.push_back(localCopy); + } + else + { + for (uint32_t header : localCopy->GetHeadersAccepted()) + { + auto iter = m_IndexedHandlers.find(header); + if (iter == m_IndexedHandlers.end()) + { + std::vector handlers; + handlers.push_back(localCopy); + m_IndexedHandlers.emplace(std::make_pair(header, handlers)); + } + else + { + iter->second.push_back(localCopy); + } + } + } +} + +void FileOnlyProfilingConnection::StartProcessingThread() +{ + // check if the thread has already started + if (m_IsRunning.load()) + { + return; + } + // make sure if there was one running before it is joined + if (m_LocalHandlersThread.joinable()) + { + m_LocalHandlersThread.join(); + } + m_IsRunning.store(true); + m_KeepRunning.store(true); + m_LocalHandlersThread = std::thread(&FileOnlyProfilingConnection::ServiceLocalHandlers, this); +} + +void FileOnlyProfilingConnection::ForwardPacketToHandlers(Packet& packet) +{ + if (m_PacketHandlers.empty()) + { + return; + } + if (m_KeepRunning.load() == false) + { + return; + } + { + std::unique_lock readableListLock(m_ReadableMutex); + if (m_KeepRunning.load() == false) + { + return; + } + m_ReadableList.push(std::move(packet)); + } + m_ConditionPacketReadable.notify_one(); +} + +void FileOnlyProfilingConnection::ServiceLocalHandlers() +{ + do + { + Packet returnedPacket; + bool readPacket = false; + { // only lock while we are taking the packet off the incoming list + std::unique_lock lck(m_ReadableMutex); + if (m_Timeout < 0) + { + m_ConditionPacketReadable.wait(lck, + [&] { return !m_ReadableList.empty(); }); + } + else + { + m_ConditionPacketReadable.wait_for(lck, + std::chrono::milliseconds(std::max(m_Timeout, 1000)), + [&] { return !m_ReadableList.empty(); }); + } + if (m_KeepRunning.load()) + { + if (!m_ReadableList.empty()) + { + returnedPacket = std::move(m_ReadableList.front()); + m_ReadableList.pop(); + readPacket = true; + } + } + else + { + ClearReadableList(); + } + } + if (m_KeepRunning.load() && readPacket) + { + DispatchPacketToHandlers(returnedPacket); + } + } while (m_KeepRunning.load()); + // make sure the readable list is cleared + ClearReadableList(); + m_IsRunning.store(false); +} + +void FileOnlyProfilingConnection::ClearReadableList() +{ + // make sure the incoming packet queue gets emptied + size_t initialSize = m_ReadableList.size(); + for (size_t i = 0; i < initialSize; ++i) + { + m_ReadableList.pop(); + } +} + +void FileOnlyProfilingConnection::DispatchPacketToHandlers(const Packet& packet) +{ + for (auto& delegate : m_UniversalHandlers) + { + delegate->HandlePacket(packet); + } + auto iter = m_IndexedHandlers.find(packet.GetHeader()); + if (iter != m_IndexedHandlers.end()) + { + for (auto &delegate : iter->second) + { + delegate->HandlePacket(packet); + } + } +} + } // namespace profiling } // namespace armnn diff --git a/src/profiling/FileOnlyProfilingConnection.hpp b/src/profiling/FileOnlyProfilingConnection.hpp index d4477b6883..12ac27333d 100644 --- a/src/profiling/FileOnlyProfilingConnection.hpp +++ b/src/profiling/FileOnlyProfilingConnection.hpp @@ -5,15 +5,19 @@ #pragma once -#include "CounterDirectory.hpp" +#include #include "DirectoryCaptureCommandHandler.hpp" #include "IProfilingConnection.hpp" +#include "Packet.hpp" #include "ProfilingUtils.hpp" #include "Runtime.hpp" +#include #include #include +#include #include +#include namespace armnn { @@ -42,7 +46,20 @@ public: : m_Options(options) , m_QuietOp(quietOp) , m_Endianness(TargetEndianness::LeWire) // Set a sensible default. WaitForStreamMeta will set a real value. - {}; + , m_IsRunning(false) + , m_KeepRunning(false) + , m_Timeout(1000) + { + for (ILocalPacketHandlerSharedPtr localPacketHandler : options.m_LocalPacketHandlers) + { + AddLocalPacketHandler(localPacketHandler); + } + if (!options.m_LocalPacketHandlers.empty()) + { + StartProcessingThread(); + } + // NOTE: could add timeout to the external profiling options + }; ~FileOnlyProfilingConnection(); @@ -57,6 +74,11 @@ public: Packet ReadPacket(uint32_t timeout) override; private: + void AddLocalPacketHandler(ILocalPacketHandlerSharedPtr localPacketHandler); + void StartProcessingThread(); + void ClearReadableList(); + void DispatchPacketToHandlers(const Packet& packet); + bool WaitForStreamMeta(const unsigned char* buffer, uint32_t length); uint32_t ToUint32(const unsigned char* data, TargetEndianness endianness); @@ -65,10 +87,13 @@ private: bool SendCounterSelectionPacket(); - PackageActivity GetPackageActivity(const unsigned char* buffer, uint32_t headerAsWords[2]); + PackageActivity GetPackageActivity(const Packet& packet, uint32_t headerAsWords[2]); void Fail(const std::string& errorMessage); + void ForwardPacketToHandlers(Packet& packet); + void ServiceLocalHandlers(); + static const uint32_t PIPE_MAGIC = 0x45495434; Runtime::CreationOptions::ExternalProfilingOptions m_Options; @@ -79,6 +104,23 @@ private: std::mutex m_PacketAvailableMutex; std::condition_variable m_ConditionPacketAvailable; + + std::vector m_PacketHandlers; + std::map> m_IndexedHandlers; + std::vector m_UniversalHandlers; + + // List of readable packets for the local packet handlers + std::queue m_ReadableList; + // Mutex and condition variable for the readable packet list + std::mutex m_ReadableMutex; + std::condition_variable m_ConditionPacketReadable; + // thread that takes items from the readable list and dispatches them + // to the handlers. + std::thread m_LocalHandlersThread; + // atomic booleans that control the operation of the local handlers thread + std::atomic m_IsRunning; + std::atomic m_KeepRunning; + int m_Timeout; }; } // namespace profiling diff --git a/src/profiling/IProfilingConnection.hpp b/src/profiling/IProfilingConnection.hpp index 5d6a352f1d..2a1c35f57e 100644 --- a/src/profiling/IProfilingConnection.hpp +++ b/src/profiling/IProfilingConnection.hpp @@ -6,6 +6,7 @@ #pragma once #include "Packet.hpp" +#include #include diff --git a/src/profiling/NullProfilingConnection.hpp b/src/profiling/NullProfilingConnection.hpp new file mode 100644 index 0000000000..a72d7bfb82 --- /dev/null +++ b/src/profiling/NullProfilingConnection.hpp @@ -0,0 +1,41 @@ +// +// Copyright © 2020 Arm Ltd. All rights reserved. +// SPDX-License-Identifier: MIT +// + +#pragma once + +#include "IProfilingConnection.hpp" + +#include + +namespace armnn +{ + +namespace profiling +{ + +class NullProfilingConnection : public IProfilingConnection +{ + virtual bool IsOpen() const override { return true; }; + + virtual void Close() override {}; + + virtual bool WritePacket(const unsigned char* buffer, uint32_t length) override + { + armnn::IgnoreUnused(buffer); + armnn::IgnoreUnused(length); + return true; + }; + + virtual Packet ReadPacket(uint32_t timeout) override + { + armnn::IgnoreUnused(timeout); + return Packet(0); + } + +}; + +} // namespace profiling + +} // namespace armnn \ No newline at end of file diff --git a/src/profiling/ProfilingConnectionFactory.cpp b/src/profiling/ProfilingConnectionFactory.cpp index 1d264def57..7849b7e21e 100644 --- a/src/profiling/ProfilingConnectionFactory.cpp +++ b/src/profiling/ProfilingConnectionFactory.cpp @@ -30,6 +30,8 @@ std::unique_ptr ProfilingConnectionFactory::GetProfilingCo // ProfilingConnectionDumpToFileDecorator is returned. // 3: If both incoming and outgoing capture files are specified and "file only" then a FileOnlyProfilingConnection // decorated by a ProfilingConnectionDumpToFileDecorator is returned. + // 4. There is now another option if m_FileOnly == true and there are ILocalPacketHandlers specified + // we can create a FileOnlyProfilingConnection without a file dump if ((!options.m_IncomingCaptureFile.empty() || !options.m_OutgoingCaptureFile.empty()) && !options.m_FileOnly) { // This is type 2. @@ -42,6 +44,11 @@ std::unique_ptr ProfilingConnectionFactory::GetProfilingCo return std::make_unique( std::make_unique(options), options); } + else if (options.m_FileOnly && !options.m_LocalPacketHandlers.empty()) + { + // This is the type 4. + return std::make_unique(options); + } else { // This is type 1. diff --git a/src/profiling/ProfilingService.hpp b/src/profiling/ProfilingService.hpp index f3d10e711b..ba5da7d741 100644 --- a/src/profiling/ProfilingService.hpp +++ b/src/profiling/ProfilingService.hpp @@ -14,6 +14,7 @@ #include "DeactivateTimelineReportingCommandHandler.hpp" #include "ICounterRegistry.hpp" #include "ICounterValues.hpp" +#include #include "IProfilingService.hpp" #include "IReportStructure.hpp" #include "PeriodicCounterCapture.hpp" @@ -207,6 +208,7 @@ public: return m_TimelineReporting; } + void AddLocalPacketHandler(ILocalPacketHandlerSharedPtr localPacketHandler); private: //Copy/move constructors/destructors and copy/move assignment operators are deleted ProfilingService(const ProfilingService&) = delete; diff --git a/src/profiling/ProfilingUtils.cpp b/src/profiling/ProfilingUtils.cpp index fc70856e5c..6d2565c610 100644 --- a/src/profiling/ProfilingUtils.cpp +++ b/src/profiling/ProfilingUtils.cpp @@ -5,6 +5,8 @@ #include "ProfilingUtils.hpp" +#include "common/include/ProfilingException.hpp" + #include #include @@ -1052,6 +1054,33 @@ uint64_t GetTimestamp() return static_cast(timestamp.count()); } +Packet ReceivePacket(const unsigned char* buffer, uint32_t length) +{ + if (buffer == nullptr) + { + throw armnnProfiling::ProfilingException("data buffer is nullptr"); + } + if (length < 8) + { + throw armnnProfiling::ProfilingException("length of data buffer is less than 8"); + } + + uint32_t metadataIdentifier = 0; + std::memcpy(&metadataIdentifier, buffer, sizeof(metadataIdentifier)); + + uint32_t dataLength = 0; + std::memcpy(&dataLength, buffer + 4u, sizeof(dataLength)); + + std::unique_ptr packetData; + if (dataLength > 0) + { + packetData = std::make_unique(dataLength); + std::memcpy(packetData.get(), buffer + 8u, dataLength); + } + + return Packet(metadataIdentifier, dataLength, packetData); +} + } // namespace profiling } // namespace armnn diff --git a/src/profiling/ProfilingUtils.hpp b/src/profiling/ProfilingUtils.hpp index 5888ef0b8c..e2ffb24bf9 100644 --- a/src/profiling/ProfilingUtils.hpp +++ b/src/profiling/ProfilingUtils.hpp @@ -10,6 +10,7 @@ #include "ICounterDirectory.hpp" #include "IPacketBuffer.hpp" +#include "Packet.hpp" #include @@ -256,6 +257,8 @@ class BufferExhaustion : public armnn::Exception uint64_t GetTimestamp(); +Packet ReceivePacket(const unsigned char* buffer, uint32_t length); + } // namespace profiling } // namespace armnn diff --git a/src/profiling/test/FileOnlyProfilingDecoratorTests.cpp b/src/profiling/test/FileOnlyProfilingDecoratorTests.cpp index baadb8555d..964489478e 100644 --- a/src/profiling/test/FileOnlyProfilingDecoratorTests.cpp +++ b/src/profiling/test/FileOnlyProfilingDecoratorTests.cpp @@ -3,12 +3,14 @@ // SPDX-License-Identifier: MIT // -#include "../FileOnlyProfilingConnection.hpp" - +#include +#include +#include +#include #include #include -#include -#include +#include "PrintPacketHeaderHandler.hpp" +#include "TestTimelinePacketHandler.hpp" #include #include @@ -37,11 +39,67 @@ class FileOnlyHelperService : public ProfilingService BOOST_AUTO_TEST_SUITE(FileOnlyProfilingDecoratorTests) +std::string UniqueFileName() +{ + std::time_t t = std::time(nullptr); + char mbstr[100]; + std::strftime(mbstr, sizeof(mbstr), "%Y_%m_%d_%H_%M_%S_", std::localtime(&t)); + std::stringstream ss; + ss << mbstr; + ss << t; + ss << ".bin"; + return ss.str(); +} + +BOOST_AUTO_TEST_CASE(TestFileOnlyProfiling) +{ + // Create a temporary file name. + boost::filesystem::path tempPath = boost::filesystem::temp_directory_path(); + boost::filesystem::path tempFile = UniqueFileName(); + tempPath = tempPath / tempFile; + armnn::Runtime::CreationOptions creationOptions; + creationOptions.m_ProfilingOptions.m_EnableProfiling = true; + creationOptions.m_ProfilingOptions.m_FileOnly = true; + creationOptions.m_ProfilingOptions.m_CapturePeriod = 100; + creationOptions.m_ProfilingOptions.m_TimelineEnabled = true; + ILocalPacketHandlerSharedPtr localPacketHandlerPtr = std::make_shared(); + creationOptions.m_ProfilingOptions.m_LocalPacketHandlers.push_back(localPacketHandlerPtr); + + armnn::Runtime runtime(creationOptions); + + // Load a simple network + // build up the structure of the network + INetworkPtr net(INetwork::Create()); + + IConnectableLayer* input = net->AddInputLayer(0, "input"); + + NormalizationDescriptor descriptor; + IConnectableLayer* normalize = net->AddNormalizationLayer(descriptor, "normalization"); + + IConnectableLayer* output = net->AddOutputLayer(0, "output"); + + input->GetOutputSlot(0).Connect(normalize->GetInputSlot(0)); + normalize->GetOutputSlot(0).Connect(output->GetInputSlot(0)); + + input->GetOutputSlot(0).SetTensorInfo(TensorInfo({ 1, 1, 4, 4 }, DataType::Float32)); + normalize->GetOutputSlot(0).SetTensorInfo(TensorInfo({ 1, 1, 4, 4 }, DataType::Float32)); + + // optimize the network + std::vector backends = { armnn::Compute::CpuRef }; + IOptimizedNetworkPtr optNet = Optimize(*net, backends, runtime.GetDeviceSpec()); + + // Load it into the runtime. It should succeed. + armnn::NetworkId netId; + BOOST_TEST(runtime.LoadNetwork(netId, std::move(optNet)) == Status::Success); + + static_cast(localPacketHandlerPtr.get())->WaitOnInferenceCompletion(3000); +} + BOOST_AUTO_TEST_CASE(DumpOutgoingValidFileEndToEnd, * boost::unit_test::disabled()) { // Create a temporary file name. boost::filesystem::path tempPath = boost::filesystem::temp_directory_path(); - boost::filesystem::path tempFile = boost::filesystem::unique_path(); + boost::filesystem::path tempFile = UniqueFileName(); tempPath = tempPath / tempFile; armnn::Runtime::CreationOptions::ExternalProfilingOptions options; options.m_EnableProfiling = true; diff --git a/src/profiling/test/PrintPacketHeaderHandler.cpp b/src/profiling/test/PrintPacketHeaderHandler.cpp new file mode 100644 index 0000000000..24095d8250 --- /dev/null +++ b/src/profiling/test/PrintPacketHeaderHandler.cpp @@ -0,0 +1,31 @@ +// +// Copyright © 2020 Arm Ltd. All rights reserved. +// SPDX-License-Identifier: MIT +// + +#include "PrintPacketHeaderHandler.hpp" + +#include + +namespace armnn +{ + +namespace profiling +{ + +std::vector PrintPacketHeaderHandler::GetHeadersAccepted() +{ + return std::vector(); +} + +void PrintPacketHeaderHandler::HandlePacket(const Packet& packet) +{ + std::stringstream ss; + ss << "Handler Received Outgoing Packet [" << packet.GetPacketFamily() << ":" << packet.GetPacketId() << "]"; + ss << " Length [" << packet.GetLength() << "]" << std::endl; + std::cout << ss.str() << std::endl; +}; + +} // namespace profiling + +} // namespace armnn \ No newline at end of file diff --git a/src/profiling/test/PrintPacketHeaderHandler.hpp b/src/profiling/test/PrintPacketHeaderHandler.hpp new file mode 100644 index 0000000000..3cd5921e51 --- /dev/null +++ b/src/profiling/test/PrintPacketHeaderHandler.hpp @@ -0,0 +1,26 @@ +// +// Copyright © 2020 Arm Ltd. All rights reserved. +// SPDX-License-Identifier: MIT +// + +#pragma once + +#include +#include "Packet.hpp" + +namespace armnn +{ + +namespace profiling +{ + +class PrintPacketHeaderHandler : public ILocalPacketHandler +{ + virtual std::vector GetHeadersAccepted(); + + virtual void HandlePacket(const Packet& packet); +}; + +} // namespace profiling + +} // namespace armnn diff --git a/src/profiling/test/TestTimelinePacketHandler.cpp b/src/profiling/test/TestTimelinePacketHandler.cpp new file mode 100644 index 0000000000..93fb4b433c --- /dev/null +++ b/src/profiling/test/TestTimelinePacketHandler.cpp @@ -0,0 +1,132 @@ +// +// Copyright © 2020 Arm Ltd. All rights reserved. +// SPDX-License-Identifier: MIT +// + +#include "TestTimelinePacketHandler.hpp" +#include "IProfilingConnection.hpp" + +#include + +#include +#include +#include + +namespace armnn +{ + +namespace profiling +{ + +std::vector TestTimelinePacketHandler::GetHeadersAccepted() +{ + std::vector headers; + headers.push_back(m_DirectoryHeader); // message directory + headers.push_back(m_MessageHeader); // message + return headers; +} + +void TestTimelinePacketHandler::HandlePacket(const Packet& packet) +{ + if (packet.GetHeader() == m_DirectoryHeader) + { + ProcessDirectoryPacket(packet); + } + else if (packet.GetHeader() == m_MessageHeader) + { + ProcessMessagePacket(packet); + } + else + { + std::stringstream ss; + ss << "Received a packet with unknown header [" << packet.GetHeader() << "]"; + throw armnn::Exception(ss.str()); + } +} + +void TestTimelinePacketHandler::Stop() +{ + m_Connection->Close(); +} + +void TestTimelinePacketHandler::WaitOnInferenceCompletion(unsigned int timeout) +{ + std::unique_lock lck(m_InferenceCompletedMutex); + + auto start = std::chrono::high_resolution_clock::now(); + // Here we we will go back to sleep after a spurious wake up if + // m_InferenceCompleted is not yet true. + if (!m_InferenceCompletedConditionVariable.wait_for(lck, + std::chrono::milliseconds(timeout), + [&]{return m_InferenceCompleted == true;})) + { + auto finish = std::chrono::high_resolution_clock::now(); + std::chrono::duration elapsed = finish - start; + std::stringstream ss; + ss << "Timed out waiting on inference completion for " << elapsed.count() << " ms"; + throw armnn::TimeoutException(ss.str()); + } + return; +} + +void TestTimelinePacketHandler::SetInferenceComplete() +{ + { // only lock when we are updating the inference completed variable + std::unique_lock lck(m_InferenceCompletedMutex); + m_InferenceCompleted = true; + } + m_InferenceCompletedConditionVariable.notify_one(); +} + +void TestTimelinePacketHandler::ProcessDirectoryPacket(const Packet& packet) +{ + m_DirectoryDecoder(packet); +} + +void TestTimelinePacketHandler::ProcessMessagePacket(const Packet& packet) +{ + m_Decoder(packet); +} + +// TimelineMessageDecoder functions +ITimelineDecoder::TimelineStatus TimelineMessageDecoder::CreateEntity(const Entity& entity) +{ + m_TimelineModel.AddEntity(entity.m_Guid); + return ITimelineDecoder::TimelineStatus::TimelineStatus_Success; +} + +ITimelineDecoder::TimelineStatus TimelineMessageDecoder::CreateEventClass( + const ITimelineDecoder::EventClass& eventClass) +{ + // for the moment terminate the run here so we can get this code + // onto master prior to a major re-organisation + if (m_PacketHandler != nullptr) + { + m_PacketHandler->SetInferenceComplete(); + } + IgnoreUnused(eventClass); + return ITimelineDecoder::TimelineStatus::TimelineStatus_Success; +} + +ITimelineDecoder::TimelineStatus TimelineMessageDecoder::CreateEvent(const ITimelineDecoder::Event& event) +{ + IgnoreUnused(event); + return ITimelineDecoder::TimelineStatus::TimelineStatus_Success; +} + +ITimelineDecoder::TimelineStatus TimelineMessageDecoder::CreateLabel(const ITimelineDecoder::Label& label) +{ + m_TimelineModel.AddLabel(label); + return ITimelineDecoder::TimelineStatus::TimelineStatus_Success; +} + +ITimelineDecoder::TimelineStatus TimelineMessageDecoder::CreateRelationship( + const ITimelineDecoder::Relationship& relationship) +{ + m_TimelineModel.AddRelationship(relationship); + return ITimelineDecoder::TimelineStatus::TimelineStatus_Success; +} + +} // namespace profiling + +} // namespace armnn \ No newline at end of file diff --git a/src/profiling/test/TestTimelinePacketHandler.hpp b/src/profiling/test/TestTimelinePacketHandler.hpp new file mode 100644 index 0000000000..2902e5f89c --- /dev/null +++ b/src/profiling/test/TestTimelinePacketHandler.hpp @@ -0,0 +1,90 @@ +// +// Copyright © 2020 Arm Ltd. All rights reserved. +// SPDX-License-Identifier: MIT +// + +#pragma once + +#include +#include +#include "Packet.hpp" +#include "ProfilingUtils.hpp" +#include "TimelineCaptureCommandHandler.hpp" +#include "TimelineDirectoryCaptureCommandHandler.hpp" +#include "TimelineModel.hpp" + +#include +#include +#include +#include + +namespace armnn +{ + +namespace profiling +{ + +// forward declaration of class +class TestTimelinePacketHandler; +class TimelineMessageDecoder : public ITimelineDecoder +{ +public: + TimelineMessageDecoder(TimelineModel& model) : m_PacketHandler(nullptr), m_TimelineModel(model) {} + virtual TimelineStatus CreateEntity(const Entity&) override; + virtual TimelineStatus CreateEventClass(const EventClass&) override; + virtual TimelineStatus CreateEvent(const Event&) override; + virtual TimelineStatus CreateLabel(const Label&) override; + virtual TimelineStatus CreateRelationship(const Relationship&) override; + void SetPacketHandler(TestTimelinePacketHandler* packetHandler) {m_PacketHandler = packetHandler;}; +private: + TestTimelinePacketHandler* m_PacketHandler; + TimelineModel& m_TimelineModel; +}; + +class TestTimelinePacketHandler : public ILocalPacketHandler +{ +public: + TestTimelinePacketHandler() : + m_Connection(nullptr), + m_InferenceCompleted(false), + m_DirectoryHeader(CreateTimelinePacketHeader(1, 0, 0, 0, 0, 0).first), + m_MessageHeader(CreateTimelinePacketHeader(1, 0, 1, 0, 0, 0).first), + m_MessageDecoder(m_TimelineModel), + m_Decoder(1, 1, 0, m_MessageDecoder), + m_DirectoryDecoder(1, 0, 0, m_Decoder, true) + { m_MessageDecoder.SetPacketHandler(this); } + + virtual std::vector GetHeadersAccepted() override; // ILocalPacketHandler + + virtual void HandlePacket(const Packet& packet) override; // ILocalPacketHandler + + void Stop(); + + void WaitOnInferenceCompletion(unsigned int timeout); + void SetInferenceComplete(); + + const TimelineModel& GetTimelineModel() const {return m_TimelineModel;} + + virtual void SetConnection(IProfilingConnection* profilingConnection) override // ILocalPacketHandler + { + m_Connection = profilingConnection; + } + +private: + void ProcessDirectoryPacket(const Packet& packet); + void ProcessMessagePacket(const Packet& packet); + IProfilingConnection* m_Connection; + std::mutex m_InferenceCompletedMutex; + std::condition_variable m_InferenceCompletedConditionVariable; + bool m_InferenceCompleted; + TimelineModel m_TimelineModel; + uint32_t m_DirectoryHeader; + uint32_t m_MessageHeader; + TimelineMessageDecoder m_MessageDecoder; + timelinedecoder::TimelineCaptureCommandHandler m_Decoder; + timelinedecoder::TimelineDirectoryCaptureCommandHandler m_DirectoryDecoder; +}; + +} // namespace profiling + +} // namespace armnn \ No newline at end of file diff --git a/src/profiling/test/TimelineModel.cpp b/src/profiling/test/TimelineModel.cpp new file mode 100644 index 0000000000..73aa0c5580 --- /dev/null +++ b/src/profiling/test/TimelineModel.cpp @@ -0,0 +1,57 @@ +// +// Copyright © 2020 Arm Ltd. All rights reserved. +// SPDX-License-Identifier: MIT +// + +#include "TimelineModel.hpp" + +namespace armnn +{ + +namespace profiling +{ + +void TimelineModel::AddLabel(const ITimelineDecoder::Label& label) +{ + m_LabelMap.emplace(label.m_Guid, label); +} + +void TimelineModel::AddEntity(uint64_t guid) +{ + m_Entities.emplace(guid, guid); +} + +Entity* TimelineModel::findEntity(uint64_t id) +{ + auto iter = m_Entities.find(id); + if (iter != m_Entities.end()) + { + return &(iter->second); + } + else + { + return nullptr; + } +} + +void TimelineModel::AddRelationship(const ITimelineDecoder::Relationship& relationship) +{ + m_Relationships.emplace(relationship.m_Guid, relationship); +} + +ModelRelationship* TimelineModel::findRelationship(uint64_t id) +{ + auto iter = m_Relationships.find(id); + if (iter != m_Relationships.end()) + { + return &(iter->second); + } + else + { + return nullptr; + } +} + +} // namespace profiling + +} // namespace armnn \ No newline at end of file diff --git a/src/profiling/test/TimelineModel.hpp b/src/profiling/test/TimelineModel.hpp new file mode 100644 index 0000000000..7b88d5fa2c --- /dev/null +++ b/src/profiling/test/TimelineModel.hpp @@ -0,0 +1,67 @@ +// +// Copyright © 2020 Arm Ltd. All rights reserved. +// SPDX-License-Identifier: MIT +// + +#pragma once + +#include + +#include +#include + +namespace armnn +{ + +namespace profiling +{ +using LabelMap = std::map; +using Attribute = std::pair; +using Attributes = std::map; +class Entity +{ +public: + Entity(uint64_t guid) : m_Guid(guid) {} + uint64_t GetGuid() {return m_Guid;} + void AddChild(Entity* child) + { + if (child != nullptr) + { + m_Children.push_back(child); + } + } + void AddAttribute(const std::string& type, const std::string& value) + { + Attribute attr(type, value); + m_Attributes.emplace(type, attr); + } +private: + uint64_t m_Guid; + Attributes m_Attributes; + std::vector m_Children; +}; +using Entities = std::map; +struct ModelRelationship +{ + ModelRelationship(const ITimelineDecoder::Relationship& relationship) : m_Relationship(relationship) {} + ITimelineDecoder::Relationship m_Relationship; + std::vector m_RelatedEntities; +}; +using Relationships = std::map; +class TimelineModel +{ +public: + void AddLabel(const ITimelineDecoder::Label& label); + void AddEntity(uint64_t guid); + Entity* findEntity(uint64_t id); + void AddRelationship(const ITimelineDecoder::Relationship& relationship); + ModelRelationship* findRelationship(uint64_t id); +private: + LabelMap m_LabelMap; + Entities m_Entities; + Relationships m_Relationships; +}; + +} // namespace profiling + +} // namespace armnn \ No newline at end of file -- cgit v1.2.1