IVGCVSW-4595 Add IFileOnlyPacketHandlers to file only profiling connection
authorJim Flynn <jim.flynn@arm.com>
Sun, 29 Mar 2020 16:48:26 +0000 (17:48 +0100)
committerfinn.williams <finn.williams@arm.com>
Mon, 27 Apr 2020 16:38:55 +0000 (16:38 +0000)
Change-Id: Ib49a8cbbf323da4109cdab9750e6c4d276e484b7
Signed-off-by: Jim Flynn <jim.flynn@arm.com>
21 files changed:
Android.mk
CMakeLists.txt
include/armnn/IRuntime.hpp
include/armnn/profiling/ILocalPacketHandler.hpp [new file with mode: 0644]
include/armnn/profiling/ITimelineDecoder.hpp
profiling/common/include/ProfilingException.hpp [new file with mode: 0644]
src/profiling/FileOnlyProfilingConnection.cpp
src/profiling/FileOnlyProfilingConnection.hpp
src/profiling/IProfilingConnection.hpp
src/profiling/NullProfilingConnection.hpp [new file with mode: 0644]
src/profiling/ProfilingConnectionFactory.cpp
src/profiling/ProfilingService.hpp
src/profiling/ProfilingUtils.cpp
src/profiling/ProfilingUtils.hpp
src/profiling/test/FileOnlyProfilingDecoratorTests.cpp
src/profiling/test/PrintPacketHeaderHandler.cpp [new file with mode: 0644]
src/profiling/test/PrintPacketHeaderHandler.hpp [new file with mode: 0644]
src/profiling/test/TestTimelinePacketHandler.cpp [new file with mode: 0644]
src/profiling/test/TestTimelinePacketHandler.hpp [new file with mode: 0644]
src/profiling/test/TimelineModel.cpp [new file with mode: 0644]
src/profiling/test/TimelineModel.hpp [new file with mode: 0644]

index 2cd6e50..9ae4a99 100644 (file)
@@ -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
 
index 8f3aa03..54376b6 100644 (file)
@@ -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)
index 06d249e..48ad7c4 100644 (file)
@@ -10,6 +10,7 @@
 #include "Tensor.hpp"
 #include "Types.hpp"
 #include "TypesUtils.hpp"
+#include "profiling/ILocalPacketHandler.hpp"
 
 #include <memory>
 
@@ -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<armnn::profiling::ILocalPacketHandlerSharedPtr> m_LocalPacketHandlers;
         };
         ExternalProfilingOptions m_ProfilingOptions;
 
diff --git a/include/armnn/profiling/ILocalPacketHandler.hpp b/include/armnn/profiling/ILocalPacketHandler.hpp
new file mode 100644 (file)
index 0000000..a2b9d5f
--- /dev/null
@@ -0,0 +1,48 @@
+//
+// Copyright © 2020 Arm Ltd. All rights reserved.
+// SPDX-License-Identifier: MIT
+//
+
+#pragma once
+
+
+#include <armnn/utility/IgnoreUnused.hpp>
+
+#include <cstdint>
+#include <memory>
+#include <vector>
+
+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<uint32_t> 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<ILocalPacketHandler>;
+using ILocalPacketHandlerSharedPtr = std::shared_ptr<ILocalPacketHandler>;
+
+} // namespace profiling
+
+} // namespace armnn
\ No newline at end of file
index 3a02bb5..5d22ad8 100644 (file)
@@ -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 (file)
index 0000000..532c2d4
--- /dev/null
@@ -0,0 +1,29 @@
+//
+// Copyright © 2020 Arm Ltd. All rights reserved.
+// SPDX-License-Identifier: MIT
+//
+#pragma once
+
+#include <stdexcept>
+#include <string>
+
+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
index f9bdde9..5947d2c 100644 (file)
@@ -8,6 +8,7 @@
 
 #include <armnn/Exceptions.hpp>
 
+#include <algorithm>
 #include <boost/numeric/conversion/cast.hpp>
 #include <iostream>
 #include <thread>
@@ -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<ILocalPacketHandlerSharedPtr> 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<std::mutex> 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<std::mutex> 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
index d4477b6..12ac273 100644 (file)
@@ -5,15 +5,19 @@
 
 #pragma once
 
-#include "CounterDirectory.hpp"
+#include <armnn/profiling/ILocalPacketHandler.hpp>
 #include "DirectoryCaptureCommandHandler.hpp"
 #include "IProfilingConnection.hpp"
+#include "Packet.hpp"
 #include "ProfilingUtils.hpp"
 #include "Runtime.hpp"
 
+#include <atomic>
 #include <condition_variable>
 #include <fstream>
+#include <mutex>
 #include <queue>
+#include <thread>
 
 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<ILocalPacketHandlerSharedPtr> m_PacketHandlers;
+    std::map<uint32_t, std::vector<ILocalPacketHandlerSharedPtr>> m_IndexedHandlers;
+    std::vector<ILocalPacketHandlerSharedPtr> m_UniversalHandlers;
+
+    // List of readable packets for the local packet handlers
+    std::queue<Packet> 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<bool> m_IsRunning;
+    std::atomic<bool> m_KeepRunning;
+    int m_Timeout;
 };
 
 }    // namespace profiling
index 5d6a352..2a1c35f 100644 (file)
@@ -6,6 +6,7 @@
 #pragma once
 
 #include "Packet.hpp"
+#include <armnn/profiling/ILocalPacketHandler.hpp>
 
 #include <cstdint>
 
diff --git a/src/profiling/NullProfilingConnection.hpp b/src/profiling/NullProfilingConnection.hpp
new file mode 100644 (file)
index 0000000..a72d7bf
--- /dev/null
@@ -0,0 +1,41 @@
+//
+// Copyright © 2020 Arm Ltd. All rights reserved.
+// SPDX-License-Identifier: MIT
+//
+
+#pragma once
+
+#include "IProfilingConnection.hpp"
+
+#include <armnn/utility/IgnoreUnused.hpp>
+
+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
index 1d264de..7849b7e 100644 (file)
@@ -30,6 +30,8 @@ std::unique_ptr<IProfilingConnection> 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<IProfilingConnection> ProfilingConnectionFactory::GetProfilingCo
         return std::make_unique<ProfilingConnectionDumpToFileDecorator>(
             std::make_unique<FileOnlyProfilingConnection>(options), options);
     }
+    else if (options.m_FileOnly && !options.m_LocalPacketHandlers.empty())
+    {
+        // This is the type 4.
+        return std::make_unique<FileOnlyProfilingConnection>(options);
+    }
     else
     {
         // This is type 1.
index f3d10e7..ba5da7d 100644 (file)
@@ -14,6 +14,7 @@
 #include "DeactivateTimelineReportingCommandHandler.hpp"
 #include "ICounterRegistry.hpp"
 #include "ICounterValues.hpp"
+#include <armnn/profiling/ILocalPacketHandler.hpp>
 #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;
index fc70856..6d2565c 100644 (file)
@@ -5,6 +5,8 @@
 
 #include "ProfilingUtils.hpp"
 
+#include "common/include/ProfilingException.hpp"
+
 #include <armnn/Version.hpp>
 
 #include <WallClockTimer.hpp>
@@ -1052,6 +1054,33 @@ uint64_t GetTimestamp()
     return static_cast<uint64_t>(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<unsigned char[]> packetData;
+    if (dataLength > 0)
+    {
+        packetData = std::make_unique<unsigned char[]>(dataLength);
+        std::memcpy(packetData.get(), buffer + 8u, dataLength);
+    }
+
+    return Packet(metadataIdentifier, dataLength, packetData);
+}
+
 } // namespace profiling
 
 } // namespace armnn
index 5888ef0..e2ffb24 100644 (file)
@@ -10,6 +10,7 @@
 
 #include "ICounterDirectory.hpp"
 #include "IPacketBuffer.hpp"
+#include "Packet.hpp"
 
 #include <boost/numeric/conversion/cast.hpp>
 
@@ -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
index baadb85..9644894 100644 (file)
@@ -3,12 +3,14 @@
 // SPDX-License-Identifier: MIT
 //
 
-#include "../FileOnlyProfilingConnection.hpp"
-
+#include <armnn/utility/IgnoreUnused.hpp>
+#include <FileOnlyProfilingConnection.hpp>
+#include <Filesystem.hpp>
+#include <NullProfilingConnection.hpp>
 #include <ProfilingService.hpp>
 #include <Runtime.hpp>
-#include <Filesystem.hpp>
-#include <armnn/utility/IgnoreUnused.hpp>
+#include "PrintPacketHeaderHandler.hpp"
+#include "TestTimelinePacketHandler.hpp"
 
 #include <boost/filesystem.hpp>
 #include <boost/numeric/conversion/cast.hpp>
@@ -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<TestTimelinePacketHandler>();
+    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<armnn::BackendId> 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<TestTimelinePacketHandler*>(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 (file)
index 0000000..24095d8
--- /dev/null
@@ -0,0 +1,31 @@
+//
+// Copyright © 2020 Arm Ltd. All rights reserved.
+// SPDX-License-Identifier: MIT
+//
+
+#include "PrintPacketHeaderHandler.hpp"
+
+#include <iostream>
+
+namespace armnn
+{
+
+namespace profiling
+{
+
+std::vector<uint32_t> PrintPacketHeaderHandler::GetHeadersAccepted()
+{
+    return std::vector<uint32_t>();
+}
+
+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 (file)
index 0000000..3cd5921
--- /dev/null
@@ -0,0 +1,26 @@
+//
+// Copyright © 2020 Arm Ltd. All rights reserved.
+// SPDX-License-Identifier: MIT
+//
+
+#pragma once
+
+#include <armnn/profiling/ILocalPacketHandler.hpp>
+#include "Packet.hpp"
+
+namespace armnn
+{
+
+namespace profiling
+{
+
+class PrintPacketHeaderHandler : public ILocalPacketHandler
+{
+    virtual std::vector<uint32_t> 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 (file)
index 0000000..93fb4b4
--- /dev/null
@@ -0,0 +1,132 @@
+//
+// Copyright © 2020 Arm Ltd. All rights reserved.
+// SPDX-License-Identifier: MIT
+//
+
+#include "TestTimelinePacketHandler.hpp"
+#include "IProfilingConnection.hpp"
+
+#include <armnn/utility/IgnoreUnused.hpp>
+
+#include <chrono>
+#include <iostream>
+#include <sstream>
+
+namespace armnn
+{
+
+namespace profiling
+{
+
+std::vector<uint32_t> TestTimelinePacketHandler::GetHeadersAccepted()
+{
+    std::vector<uint32_t> 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<std::mutex> 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<double, std::milli> 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<std::mutex> 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 (file)
index 0000000..2902e5f
--- /dev/null
@@ -0,0 +1,90 @@
+//
+// Copyright © 2020 Arm Ltd. All rights reserved.
+// SPDX-License-Identifier: MIT
+//
+
+#pragma once
+
+#include <armnn/profiling/ILocalPacketHandler.hpp>
+#include <armnn/profiling/ITimelineDecoder.hpp>
+#include "Packet.hpp"
+#include "ProfilingUtils.hpp"
+#include "TimelineCaptureCommandHandler.hpp"
+#include "TimelineDirectoryCaptureCommandHandler.hpp"
+#include "TimelineModel.hpp"
+
+#include <condition_variable>
+#include <map>
+#include <mutex>
+#include <vector>
+
+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<uint32_t> 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 (file)
index 0000000..73aa0c5
--- /dev/null
@@ -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 (file)
index 0000000..7b88d5f
--- /dev/null
@@ -0,0 +1,67 @@
+//
+// Copyright © 2020 Arm Ltd. All rights reserved.
+// SPDX-License-Identifier: MIT
+//
+
+#pragma once
+
+#include <armnn/profiling/ITimelineDecoder.hpp>
+
+#include <map>
+#include <vector>
+
+namespace armnn
+{
+
+namespace profiling
+{
+using LabelMap = std::map<uint64_t, ITimelineDecoder::Label>;
+using Attribute = std::pair<std::string, std::string>;
+using Attributes = std::map<std::string, Attribute>;
+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<Entity*> m_Children;
+};
+using Entities = std::map<uint64_t, Entity>;
+struct ModelRelationship
+{
+    ModelRelationship(const ITimelineDecoder::Relationship& relationship) : m_Relationship(relationship) {}
+    ITimelineDecoder::Relationship m_Relationship;
+    std::vector<Entity*> m_RelatedEntities;
+};
+using Relationships = std::map<uint64_t, ModelRelationship>;
+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