IVGCVSW-3721 Add support for startup sequence (Mock Gatord service).
authorColm Donelan <Colm.Donelan@arm.com>
Fri, 11 Oct 2019 12:09:49 +0000 (13:09 +0100)
committerColm Donelan <Colm.Donelan@arm.com>
Fri, 11 Oct 2019 12:12:37 +0000 (13:12 +0100)
* Receive and process the stream metadata from the client.
* Send the connection ack packet.
* Wait in a receiving thread and print the packets.
* GatordMockTest and Impl for PeriodicCounterCapture CommandHandler
* CaptureData class to retain packet data
* MockUtils
* Update SocketProfilingConnection to fix non blocking receipt of packets.
* Restructure directory layout following review comments.
* Extract the mock service into a static library in the cmake files.

Signed-off-by: Colm Donelan <Colm.Donelan@arm.com>
Signed-off-by: Keith Davis <keith.davis@arm.com>
Signed-off-by: Mike Kelly <mike.kelly@arm.com>
Signed-off-by: Finn Williams <Finn.Williams@arm.com>
Signed-off-by: Kevin May <kevin.may@arm.com>
Change-Id: I33c1c9f93976708c9315f71290d42cff53b8c075

19 files changed:
CMakeLists.txt
cmake/GlobalConfig.cmake
src/profiling/SocketProfilingConnection.cpp
src/profiling/SocketProfilingConnection.hpp
tests/profiling/CommandLineProcessor.hpp [deleted file]
tests/profiling/GatordMockMain.cpp [deleted file]
tests/profiling/GatordMockService.cpp [deleted file]
tests/profiling/GatordMockService.hpp [deleted file]
tests/profiling/gatordmock/CommandFileParser.cpp [new file with mode: 0644]
tests/profiling/gatordmock/CommandFileParser.hpp [new file with mode: 0644]
tests/profiling/gatordmock/CommandLineProcessor.cpp [moved from tests/profiling/CommandLineProcessor.cpp with 68% similarity]
tests/profiling/gatordmock/CommandLineProcessor.hpp [new file with mode: 0644]
tests/profiling/gatordmock/GatordMockMain.cpp [new file with mode: 0644]
tests/profiling/gatordmock/GatordMockService.cpp [new file with mode: 0644]
tests/profiling/gatordmock/GatordMockService.hpp [new file with mode: 0644]
tests/profiling/gatordmock/MockUtils.hpp [new file with mode: 0644]
tests/profiling/gatordmock/PeriodicCounterCaptureCommandHandler.cpp [new file with mode: 0644]
tests/profiling/gatordmock/PeriodicCounterCaptureCommandHandler.hpp [new file with mode: 0644]
tests/profiling/gatordmock/tests/GatordMockTests.cpp [new file with mode: 0644]

index 3e415d0..0430643 100644 (file)
@@ -782,6 +782,12 @@ if(BUILD_UNIT_TESTS)
         set_source_files_properties(src/armnnDeserializer/test/SchemaSerialize.s PROPERTIES COMPILE_FLAGS "-x assembler-with-cpp")
     endif()
 
+    if(BUILD_GATORD_MOCK)
+        list(APPEND unittest_sources
+            tests/profiling/gatordmock/tests/GatordMockTests.cpp
+            )
+    endif()
+
     foreach(lib ${armnnUnitTestLibraries})
         message("Adding object library dependency to UnitTests: ${lib}")
         list(APPEND unittest_sources $<TARGET_OBJECTS:${lib}>)
@@ -812,6 +818,10 @@ if(BUILD_UNIT_TESTS)
         target_link_libraries(UnitTests armnnTfParser)
     endif()
 
+    if(BUILD_GATORD_MOCK)
+        target_link_libraries(UnitTests gatordMockService)
+    endif()
+
     if(BUILD_TF_LITE_PARSER)
         target_include_directories(UnitTests SYSTEM PRIVATE "${TF_LITE_SCHEMA_INCLUDE_PATH}")
         target_include_directories(UnitTests SYSTEM PRIVATE "${FLATBUFFERS_INCLUDE_PATH}")
@@ -879,19 +889,30 @@ endif()
 if(BUILD_GATORD_MOCK)
     set(gatord_mock_sources)
     list(APPEND gatord_mock_sources
-        tests/profiling/GatordMockMain.cpp
-        tests/profiling/CommandLineProcessor.hpp
-        tests/profiling/CommandLineProcessor.cpp
-        tests/profiling/GatordMockService.hpp
-        tests/profiling/GatordMockService.cpp
+        tests/profiling/gatordmock/CommandFileParser.hpp
+        tests/profiling/gatordmock/CommandFileParser.cpp
+        tests/profiling/gatordmock/CommandLineProcessor.hpp
+        tests/profiling/gatordmock/CommandLineProcessor.cpp
+        tests/profiling/gatordmock/GatordMockService.hpp
+        tests/profiling/gatordmock/GatordMockService.cpp
+        tests/profiling/gatordmock/MockUtils.hpp
+        tests/profiling/gatordmock/PeriodicCounterCaptureCommandHandler.cpp
+        tests/profiling/gatordmock/PeriodicCounterCaptureCommandHandler.hpp
         )
 
-    include_directories( ${Boost_INCLUDE_DIRS} )
+    include_directories( ${Boost_INCLUDE_DIRS} src/profiling)
 
-    add_executable_ex(GartordMock ${gatord_mock_sources})
+    add_library_ex(gatordMockService STATIC ${gatord_mock_sources})
 
+    add_executable_ex(GartordMock tests/profiling/gatordmock/GatordMockMain.cpp)
+
+    if(Threads_FOUND AND (NOT ("${CMAKE_SYSTEM_NAME}" STREQUAL Android)))
+        target_link_libraries(GartordMock pthread)
+    endif()
+
+    target_link_libraries(GartordMock armnn gatordMockService)
     target_link_libraries(GartordMock
-        ${Boost_PROGRAM_OPTIONS_LIBRARY}
+        ${Boost_PROGRAM_OPTIONS_LIBRARY} ${Boost_SYSTEM_LIBRARY}
         )
 
-endif()
+endif()
\ No newline at end of file
index 4880d59..2c1989e 100644 (file)
@@ -21,7 +21,7 @@ option(FLATC_DIR "Path to Flatbuffers compiler" OFF)
 option(TF_LITE_GENERATED_PATH "Tensorflow lite generated C++ schema location" OFF)
 option(FLATBUFFERS_ROOT "Location where the flatbuffers 'include' and 'lib' folders to be found" Off)
 option(DYNAMIC_BACKEND_PATHS "Colon seperated list of paths where to load the dynamic backends from" "")
-option(BUILD_GATORD_MOCK "Build the Gatord simulator for external profiling testing." OFF)
+option(BUILD_GATORD_MOCK "Build the Gatord simulator for external profiling testing." ON)
 
 include(SelectLibraryConfigurations)
 
index ad62992..6f7101b 100644 (file)
@@ -5,10 +5,11 @@
 
 #include "SocketProfilingConnection.hpp"
 
+#include <cerrno>
 #include <fcntl.h>
+#include <sys/ioctl.h>
 #include <sys/socket.h>
 #include <sys/un.h>
-#include <cerrno>
 #include <string>
 
 namespace armnn
@@ -23,7 +24,7 @@ SocketProfilingConnection::SocketProfilingConnection()
     m_Socket[0].fd = socket(PF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);
     if (m_Socket[0].fd == -1)
     {
-        throw RuntimeException(std::string("Socket construction failed: ") + strerror(errno));
+        throw armnn::RuntimeException(std::string("Socket construction failed: ") + strerror(errno));
     }
 
     // Connect to the named unix domain socket.
@@ -35,7 +36,7 @@ SocketProfilingConnection::SocketProfilingConnection()
     if (0 != connect(m_Socket[0].fd, reinterpret_cast<const sockaddr*>(&server), sizeof(sockaddr_un)))
     {
         close(m_Socket[0].fd);
-        throw RuntimeException(std::string("Cannot connect to stream socket: ") + strerror(errno));
+        throw armnn::RuntimeException(std::string("Cannot connect to stream socket: ") + strerror(errno));
     }
 
     // Our socket will only be interested in polling reads.
@@ -46,7 +47,7 @@ SocketProfilingConnection::SocketProfilingConnection()
     if (0 != fcntl(m_Socket[0].fd, F_SETFL, currentFlags | O_NONBLOCK))
     {
         close(m_Socket[0].fd);
-        throw RuntimeException(std::string("Failed to set socket as non blocking: ") + strerror(errno));
+        throw armnn::RuntimeException(std::string("Failed to set socket as non blocking: ") + strerror(errno));
     }
 }
 
@@ -59,7 +60,7 @@ void SocketProfilingConnection::Close()
 {
     if (close(m_Socket[0].fd) != 0)
     {
-        throw RuntimeException(std::string("Cannot close stream socket: ") + strerror(errno));
+        throw armnn::RuntimeException(std::string("Cannot close stream socket: ") + strerror(errno));
     }
 
     memset(m_Socket, 0, sizeof(m_Socket));
@@ -77,54 +78,67 @@ bool SocketProfilingConnection::WritePacket(const unsigned char* buffer, uint32_
 
 Packet SocketProfilingConnection::ReadPacket(uint32_t timeout)
 {
-    // Poll for data on the socket or until timeout occurs
-    int pollResult = poll(m_Socket, 1, static_cast<int>(timeout));
-
-    switch (pollResult)
+    // Is there currently at least a headers worth of data waiting to be read?
+    int bytes_available;
+    ioctl(m_Socket[0].fd, FIONREAD, &bytes_available);
+    if (bytes_available >= 8)
     {
-    case -1: // Error
-        throw RuntimeException(std::string("Read failure from socket: ") + strerror(errno));
+        // Yes there is. Read it:
+        return ReceivePacket();
+    }
+    else
+    {
+        // Poll for data on the socket or until timeout occurs
+        int pollResult = poll(m_Socket, 1, static_cast<int>(timeout));
+
+        switch (pollResult)
+        {
+            case -1: // Error
+                throw armnn::RuntimeException(std::string("Read failure from socket: ") + strerror(errno));
 
-    case 0: // Timeout
-        throw RuntimeException("Timeout while reading from socket");
+            case 0: // Timeout
+                throw TimeoutException("Timeout while reading from socket");
 
-    default: // Normal poll return but it could still contain an error signal
+            default: // Normal poll return but it could still contain an error signal
 
-        // Check if the socket reported an error
-        if (m_Socket[0].revents & (POLLNVAL | POLLERR | POLLHUP))
-        {
-            throw Exception(std::string("Socket 0 reported an error: ") + strerror(errno));
-        }
+                // Check if the socket reported an error
+                if (m_Socket[0].revents & (POLLNVAL | POLLERR | POLLHUP))
+                {
+                    throw armnn::RuntimeException(std::string("Socket reported an error: ") + strerror(errno));
+                }
 
-        // Check if there is data to read
-        if (!(m_Socket[0].revents & (POLLIN)))
-        {
-            // No data to read from the socket. Silently ignore and continue
-            return Packet();
-        }
+                // Check if there is data to read
+                if (!(m_Socket[0].revents & (POLLIN)))
+                {
+                    // This is a very odd case. The file descriptor was woken up but no data was written.
+                    throw armnn::RuntimeException("Poll resulted in : no data to read");
+                }
 
-        // There is data to read, read the header first
-        char header[8] = {};
-        if (8 != recv(m_Socket[0].fd, &header, sizeof(header), 0))
-        {
-            // What do we do here if there's not a valid 8 byte header to read?
-            throw RuntimeException("The received packet did not contains a valid MIPE header");
+                return ReceivePacket();
         }
+    }
+}
 
-        // stream_metadata_identifier is the first 4 bytes
-        uint32_t metadataIdentifier = 0;
-        std::memcpy(&metadataIdentifier, header, sizeof(metadataIdentifier));
+Packet SocketProfilingConnection::ReceivePacket()
+{
+    char header[8] = {};
+    if (8 != recv(m_Socket[0].fd, &header, sizeof(header), 0))
+    {
+        // What do we do here if there's not a valid 8 byte header to read?
+        throw armnn::RuntimeException("The received packet did not contains a valid MIPE header");
+    }
+    // stream_metadata_identifier is the first 4 bytes
+    uint32_t metadataIdentifier = 0;
+    std::memcpy(&metadataIdentifier, header, sizeof(metadataIdentifier));
 
-        // data_length is the next 4 bytes
-        uint32_t dataLength = 0;
-        std::memcpy(&dataLength, header + 4u, sizeof(dataLength));
+    // data_length is the next 4 bytes
+    uint32_t dataLength = 0;
+    std::memcpy(&dataLength, header + 4u, sizeof(dataLength));
 
         std::unique_ptr<unsigned char[]> packetData;
-        if (dataLength > 0)
-        {
+    if (dataLength > 0)
+    {
             packetData = std::make_unique<unsigned char[]>(dataLength);
-        }
-
         ssize_t receivedLength = recv(m_Socket[0].fd, packetData.get(), dataLength, 0);
         if (receivedLength < 0)
         {
@@ -133,11 +147,11 @@ Packet SocketProfilingConnection::ReadPacket(uint32_t timeout)
         if (dataLength != static_cast<uint32_t>(receivedLength))
         {
             // What do we do here if we can't read in a full packet?
-            throw RuntimeException("Invalid MIPE packet");
+            throw armnn::RuntimeException("Invalid MIPE packet");
         }
-
-        return Packet(metadataIdentifier, dataLength, packetData);
     }
+
+    return Packet(metadataIdentifier, dataLength, packetData);
 }
 
 } // namespace profiling
index 7c77a8b..5fb02bb 100644 (file)
@@ -25,6 +25,10 @@ public:
     Packet ReadPacket(uint32_t timeout) final;
 
 private:
+
+    // Read a full packet from the socket.
+    Packet ReceivePacket();
+
     // To indicate we want to use an abstract UDS ensure the first character of the address is 0.
     const char* m_GatorNamespace = "\0gatord_namespace";
     struct pollfd m_Socket[1]{};
diff --git a/tests/profiling/CommandLineProcessor.hpp b/tests/profiling/CommandLineProcessor.hpp
deleted file mode 100644 (file)
index a7e43ad..0000000
+++ /dev/null
@@ -1,37 +0,0 @@
-//
-// Copyright © 2019 Arm Ltd. All rights reserved.
-// SPDX-License-Identifier: MIT
-//
-#pragma once
-
-#include <string>
-
-namespace armnn
-{
-
-namespace gatordmock
-{
-
-// Parses the command line to extract:
-//
-//
-
-/**
- * Use Boost program options to process the command line.
- * -h or --help to print the options.
- * -n or --namespace to specify the UDS namespace that the server will be listening on.
- */
-class CommandLineProcessor
-{
-public:
-    bool ProcessCommandLine(int argc, char *argv[]);
-
-    std::string GetUdsNamespace() { return m_UdsNamespace; }
-
-private:
-    std::string m_UdsNamespace;
-};
-
-} // namespace gatordmock
-
-} // namespace armnn
diff --git a/tests/profiling/GatordMockMain.cpp b/tests/profiling/GatordMockMain.cpp
deleted file mode 100644 (file)
index b8edeb2..0000000
+++ /dev/null
@@ -1,27 +0,0 @@
-//
-// Copyright © 2019 Arm Ltd. All rights reserved.
-// SPDX-License-Identifier: MIT
-//
-
-#include "CommandLineProcessor.hpp"
-#include "GatordMockService.hpp"
-
-int main(int argc, char *argv[])
-{
-    armnn::gatordmock::CommandLineProcessor cmdline;
-    if (!cmdline.ProcessCommandLine(argc, argv))
-    {
-        return EXIT_FAILURE;
-    }
-    armnn::gatordmock::GatordMockService mockService;
-    if (!mockService.OpenListeningSocket(cmdline.GetUdsNamespace()))
-    {
-        return EXIT_FAILURE;
-    }
-    int clientFd = mockService.BlockForOneClient();
-    if (-1 == clientFd)
-    {
-        return EXIT_FAILURE;
-    }
-    return EXIT_SUCCESS;
-}
diff --git a/tests/profiling/GatordMockService.cpp b/tests/profiling/GatordMockService.cpp
deleted file mode 100644 (file)
index c774ab0..0000000
+++ /dev/null
@@ -1,71 +0,0 @@
-//
-// Copyright © 2019 Arm Ltd. All rights reserved.
-// SPDX-License-Identifier: MIT
-//
-
-#include "GatordMockService.hpp"
-
-#include <cerrno>
-#include <fcntl.h>
-#include <iostream>
-#include <string>
-#include <sys/socket.h>
-#include <sys/un.h>
-
-namespace armnn
-{
-
-namespace gatordmock
-{
-
-
-bool GatordMockService::OpenListeningSocket(std::string udsNamespace)
-{
-    m_ListeningSocket = socket(PF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);
-    if (-1 == m_ListeningSocket)
-    {
-        std::cerr << ": Socket construction failed: " << strerror(errno) << std::endl;
-        return false;
-    }
-
-    sockaddr_un udsAddress;
-    memset(&udsAddress, 0, sizeof(sockaddr_un));
-    // We've set the first element of sun_path to be 0, skip over it and copy the namespace after it.
-    memcpy(udsAddress.sun_path + 1, udsNamespace.c_str(), strlen(udsNamespace.c_str()));
-    udsAddress.sun_family = AF_UNIX;
-
-    // Bind the socket to the UDS namespace.
-    if (-1 == bind(m_ListeningSocket, reinterpret_cast<const sockaddr *>(&udsAddress), sizeof(sockaddr_un)))
-    {
-        std::cerr << ": Binding on socket failed: " << strerror(errno) << std::endl;
-        return false;
-    }
-    // Listen for 1 connection.
-    if (-1 == listen(m_ListeningSocket, 1))
-    {
-        std::cerr << ": Listen call on socket failed: " << strerror(errno) << std::endl;
-        return false;
-    }
-    std::cout << "Bound to UDS namespace: \\0" << udsNamespace << std::endl;
-    return true;
-}
-
-int GatordMockService::BlockForOneClient()
-{
-    std::cout << "Waiting for client connection." << std::endl;
-
-    int accepted = accept4(m_ListeningSocket, nullptr, nullptr, SOCK_CLOEXEC);
-    if (-1 == accepted)
-    {
-        std::cerr << ": Failure when waiting for a client connection: " << strerror(errno) << std::endl;
-        return -1;
-    }
-
-    std::cout << "Client connection established." << std::endl;
-    return accepted;
-}
-
-
-} // namespace gatordmock
-
-} // namespace armnn
diff --git a/tests/profiling/GatordMockService.hpp b/tests/profiling/GatordMockService.hpp
deleted file mode 100644 (file)
index c19e710..0000000
+++ /dev/null
@@ -1,50 +0,0 @@
-//
-// Copyright © 2019 Arm Ltd. All rights reserved.
-// SPDX-License-Identifier: MIT
-//
-
-#pragma once
-
-#include <string>
-
-namespace armnn
-{
-
-namespace gatordmock
-{
-
-
-/**
- * A class that implements a Mock Gatord server. It will listen on a specified Unix domain socket (UDS)
- * namespace for client connections.
- */
-class GatordMockService
-{
-public:
-
-    /**
-     * Establish the Unix domain socket and set it to listen for connections.
-     *
-     * @param udsNamespace the namespace (socket address) associated with the listener.
-     * @return true only if the socket has been correctly setup.
-     */
-    bool OpenListeningSocket(std::string udsNamespace);
-
-    /**
-     * Block waiting to accept one client to connect to the UDS.
-     *
-     * @return the file descriptor of the client connection.
-     */
-    int BlockForOneClient();
-
-private:
-
-    int m_ListeningSocket;
-};
-
-
-} // namespace gatordmock
-
-} // namespace armnn
-
-
diff --git a/tests/profiling/gatordmock/CommandFileParser.cpp b/tests/profiling/gatordmock/CommandFileParser.cpp
new file mode 100644 (file)
index 0000000..e86763b
--- /dev/null
@@ -0,0 +1,76 @@
+//
+// Copyright © 2019 Arm Ltd. All rights reserved.
+// SPDX-License-Identifier: MIT
+//
+
+#include "CommandFileParser.hpp"
+
+#include <algorithm>
+#include <fstream>
+#include <iostream>
+#include <iterator>
+
+namespace armnn
+{
+
+namespace gatordmock
+{
+
+void CommandFileParser::ParseFile(std::string CommandFile, GatordMockService& mockService)
+{
+    std::ifstream infile(CommandFile);
+    std::string line;
+
+    std::cout << "Parsing command file: " << CommandFile << std::endl;
+
+    while (std::getline(infile, line))
+    {
+        std::istringstream iss(line);
+
+        std::vector<std::string> tokens;
+
+        std::copy(std::istream_iterator<std::string>(iss), std::istream_iterator<std::string>(),
+                  std::back_inserter(tokens));
+
+        std::string command = tokens[0];
+
+        if (command == "SET")
+        {
+            // Expected format for the SET command
+            //
+            //      SET 500000 1 2 5 10
+            //
+            // This breaks down to:
+            // SET          command
+            // 500000       polling period in micro seconds
+            // 1 2 5 10     counter list
+
+            uint period = static_cast<uint>(std::stoul(tokens[1]));
+
+            std::vector<uint16_t> counters;
+
+            std::transform(tokens.begin() + 2, tokens.end(), std::back_inserter(counters),
+                           [](const std::string& str) { return static_cast<uint16_t>(std::stoul(str)); });
+
+            mockService.SendPeriodicCounterSelectionList(period, counters);
+        }
+        else if (command == "WAIT")
+        {
+            // Expected format for the SET command
+            //
+            //      WAIT 11000000
+            //
+            // This breaks down to:
+            // WAIT         command
+            // 11000000     timeout period in micro seconds
+
+            uint timeout = static_cast<uint>(std::stoul(tokens[1]));
+
+            mockService.WaitCommand(timeout);
+        }
+    }
+}
+
+}    // namespace gatordmock
+
+}    // namespace armnn
diff --git a/tests/profiling/gatordmock/CommandFileParser.hpp b/tests/profiling/gatordmock/CommandFileParser.hpp
new file mode 100644 (file)
index 0000000..e95395d
--- /dev/null
@@ -0,0 +1,31 @@
+//
+// Copyright © 2019 Arm Ltd. All rights reserved.
+// SPDX-License-Identifier: MIT
+//
+
+#pragma once
+
+#include <string>
+#include "GatordMockService.hpp"
+
+namespace armnn
+{
+
+namespace gatordmock
+{
+
+/// This class parses a command file for the GatordMockService. The file contains one command per line.
+/// Valid commands are: SET and WAIT.
+///
+///  SET: Will construct and send a PeriodicCounterSelection packet to enable a set of counters.
+///  WAIT: Will pause for a set period of time to allow for data to be received.
+class CommandFileParser
+{
+public:
+    void ParseFile(std::string CommandFile, GatordMockService& mockService);
+};
+
+} // namespace gatordmock
+} // namespace armnn
+
+
@@ -6,7 +6,6 @@
 #include "CommandLineProcessor.hpp"
 
 #include <boost/program_options.hpp>
-
 #include <iostream>
 
 namespace armnn
@@ -17,15 +16,18 @@ namespace gatordmock
 bool CommandLineProcessor::ProcessCommandLine(int argc, char *argv[])
 {
     namespace po = boost::program_options;
-
     po::options_description desc("Options");
     try
     {
         desc.add_options()
                 ("help,h", "Display help messages")
+                ("file,f",  po::value<std::string>(&m_File),
+                                 "The path to the file that contains instructions for the mock gatord")
                 ("namespace,n", po::value<std::string>(&m_UdsNamespace)->default_value("gatord_namespace"),
                                 "The Unix domain socket namespace this server will bind to.\n"
-                                "This will always be prepended with \\0 to use the abstract namespace");
+                                "This will always be prepended with \\0 to use the abstract namespace")
+                ("echo,e", po::bool_switch(&m_Echo)->default_value(false),
+                                "Echo packets sent and received to stdout. Disabled by default.\n");
     }
     catch (const std::exception& e)
     {
@@ -45,7 +47,14 @@ bool CommandLineProcessor::ProcessCommandLine(int argc, char *argv[])
             std::cout << desc << std::endl;
             return false;
         }
-
+        // Currently the file parameter is mandatory.
+        if (!vm.count("file"))
+        {
+            std::cout << std::endl << "*** Expected --file or -f parameter." << std::endl;
+            std::cout << std::endl;
+            std::cout << desc << std::endl;
+            return false;
+        }
         po::notify(vm);
     }
     catch (const po::error& e)
diff --git a/tests/profiling/gatordmock/CommandLineProcessor.hpp b/tests/profiling/gatordmock/CommandLineProcessor.hpp
new file mode 100644 (file)
index 0000000..0eed23a
--- /dev/null
@@ -0,0 +1,39 @@
+//
+// Copyright © 2019 Arm Ltd. All rights reserved.
+// SPDX-License-Identifier: MIT
+//
+#pragma once
+
+#include <string>
+
+namespace armnn
+{
+
+namespace gatordmock
+{
+
+
+/// Use Boost program options to process the command line.
+/// -h or --help to print the options.
+/// -n or --namespace to specify the UDS namespace that the server will be listening on.
+/// -e or --echo print all sent and received packets to stdout.
+/// -f or --file The path to the file that contains instructions for the mock gatord.
+class CommandLineProcessor
+{
+public:
+    bool ProcessCommandLine(int argc, char *argv[]);
+    bool IsEchoEnabled() { return m_Echo; }
+
+    std::string GetUdsNamespace() { return m_UdsNamespace; }
+    std::string GetCommandFile() { return m_File; }
+
+private:
+    std::string m_UdsNamespace;
+    std::string m_File;
+
+    bool m_Echo;
+};
+
+} // namespace gatordmock
+
+} // namespace armnn
diff --git a/tests/profiling/gatordmock/GatordMockMain.cpp b/tests/profiling/gatordmock/GatordMockMain.cpp
new file mode 100644 (file)
index 0000000..500b016
--- /dev/null
@@ -0,0 +1,71 @@
+//
+// Copyright © 2019 Arm Ltd. All rights reserved.
+// SPDX-License-Identifier: MIT
+//
+
+#include "CommandFileParser.hpp"
+#include "CommandLineProcessor.hpp"
+#include "GatordMockService.hpp"
+#include "MockUtils.hpp"
+#include "PeriodicCounterCaptureCommandHandler.hpp"
+
+#include <string>
+
+int main(int argc, char *argv[])
+{
+    // Process command line arguments
+    armnn::gatordmock::CommandLineProcessor cmdLine;
+    if (!cmdLine.ProcessCommandLine(argc, argv))
+    {
+        return EXIT_FAILURE;
+    }
+
+    // Initialise functors and register into the CommandHandlerRegistry
+    uint32_t version = 1;
+
+    // Create headers
+    uint32_t counterCaptureCommandHeader = armnn::gatordmock::ConstructHeader(1,0,0);
+
+    // Create the Command Handler Registry
+    armnn::profiling::CommandHandlerRegistry registry;
+
+    // Update with derived functors
+    armnn::gatordmock::PeriodicCounterCaptureCommandHandler counterCaptureCommandHandler(counterCaptureCommandHeader,
+                                                                                         version,
+                                                                                         cmdLine.IsEchoEnabled());
+
+    // Register different derived functors
+    registry.RegisterFunctor(&counterCaptureCommandHandler);
+
+    armnn::gatordmock::GatordMockService mockService(registry, cmdLine.IsEchoEnabled());
+
+    if (!mockService.OpenListeningSocket(cmdLine.GetUdsNamespace()))
+    {
+        return EXIT_FAILURE;
+    }
+
+    // Wait for a single connection.
+    if (-1 == mockService.BlockForOneClient())
+    {
+        return EXIT_FAILURE;
+    }
+
+    // Send receive the strweam metadata and send connection ack.
+    if (!mockService.WaitForStreamMetaData())
+    {
+        return EXIT_FAILURE;
+    }
+    mockService.SendConnectionAck();
+
+    // Prepare to receive data.
+    mockService.LaunchReceivingThread();
+
+    // Process the SET and WAIT command from the file.
+    armnn::gatordmock::CommandFileParser commandLineParser;
+    commandLineParser.ParseFile(cmdLine.GetCommandFile(), mockService);
+
+    // Once we've finished processing the file wait for the receiving thread to close.
+    mockService.WaitForReceivingThread();
+
+    return EXIT_SUCCESS;
+}
diff --git a/tests/profiling/gatordmock/GatordMockService.cpp b/tests/profiling/gatordmock/GatordMockService.cpp
new file mode 100644 (file)
index 0000000..f4146c2
--- /dev/null
@@ -0,0 +1,431 @@
+//
+// Copyright © 2019 Arm Ltd. All rights reserved.
+// SPDX-License-Identifier: MIT
+//
+#include "GatordMockService.hpp"
+
+#include "../../src/profiling/CommandHandlerRegistry.hpp"
+
+#include <cerrno>
+#include <fcntl.h>
+#include <iostream>
+#include <poll.h>
+#include <string>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <unistd.h>
+
+
+namespace armnn
+{
+
+namespace gatordmock
+{
+
+bool GatordMockService::OpenListeningSocket(std::string udsNamespace)
+{
+    m_ListeningSocket = socket(PF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);
+    if (-1 == m_ListeningSocket)
+    {
+        std::cerr << ": Socket construction failed: " << strerror(errno) << std::endl;
+        return false;
+    }
+
+    sockaddr_un udsAddress;
+    memset(&udsAddress, 0, sizeof(sockaddr_un));
+    // We've set the first element of sun_path to be 0, skip over it and copy the namespace after it.
+    memcpy(udsAddress.sun_path + 1, udsNamespace.c_str(), strlen(udsNamespace.c_str()));
+    udsAddress.sun_family = AF_UNIX;
+
+    // Bind the socket to the UDS namespace.
+    if (-1 == bind(m_ListeningSocket, reinterpret_cast<const sockaddr *>(&udsAddress), sizeof(sockaddr_un)))
+    {
+        std::cerr << ": Binding on socket failed: " << strerror(errno) << std::endl;
+        return false;
+    }
+    // Listen for 1 connection.
+    if (-1 == listen(m_ListeningSocket, 1))
+    {
+        std::cerr << ": Listen call on socket failed: " << strerror(errno) << std::endl;
+        return false;
+    }
+    if (m_EchoPackets)
+    {
+        std::cout << "Bound to UDS namespace: \\0" << udsNamespace << std::endl;
+    }
+    return true;
+}
+
+int GatordMockService::BlockForOneClient()
+{
+    if (m_EchoPackets)
+    {
+        std::cout << "Waiting for client connection." << std::endl;
+    }
+    m_ClientConnection = accept4(m_ListeningSocket, nullptr, nullptr, SOCK_CLOEXEC);
+    if (-1 == m_ClientConnection)
+    {
+        std::cerr << ": Failure when waiting for a client connection: " << strerror(errno) << std::endl;
+        return -1;
+    }
+
+    if (m_EchoPackets)
+    {
+        std::cout << "Client connection established." << std::endl;
+    }
+    return m_ClientConnection;
+}
+
+bool GatordMockService::WaitForStreamMetaData()
+{
+    if (m_EchoPackets)
+    {
+        std::cout << "Waiting for stream meta data..." << std::endl;
+    }
+    // The start of the stream metadata is 2x32bit words, 0 and packet length.
+    u_char header[8];
+    if (!ReadFromSocket(header, 8))
+    {
+        return false;
+    }
+    EchoPacket(PacketDirection::Received, header, 8);
+    // The first word, stream_metadata_identifer, should always be 0.
+    if (ToUint32(&header[0], TargetEndianness::BeWire) != 0)
+    {
+        std::cerr << ": Protocol error. The stream_metadata_identifer was not 0." << std::endl;
+        return false;
+    }
+
+    u_char pipeMagic[4];
+    if (!ReadFromSocket(pipeMagic, 4))
+    {
+        return false;
+    }
+    EchoPacket(PacketDirection::Received, pipeMagic, 4);
+
+    // Before we interpret the length we need to read the pipe_magic word to determine endianness.
+    if (ToUint32(&pipeMagic[0], TargetEndianness::BeWire) == PIPE_MAGIC)
+    {
+        m_Endianness = TargetEndianness::BeWire;
+    }
+    else if (ToUint32(&pipeMagic[0], TargetEndianness::LeWire) == PIPE_MAGIC)
+    {
+        m_Endianness = TargetEndianness::LeWire;
+    }
+    else
+    {
+        std::cerr << ": Protocol read error. Unable to read PIPE_MAGIC value." << std::endl;
+        return false;
+    }
+    // Now we know the endianness we can get the length from the header.
+    // Remember we already read the pipe magic 4 bytes.
+    uint32_t metaDataLength = ToUint32(&header[4], m_Endianness) - 4;
+    // Read the entire packet.
+    u_char packetData[metaDataLength];
+    if (metaDataLength != read(m_ClientConnection, &packetData, metaDataLength))
+    {
+        std::cerr << ": Protocol read error. Data length mismatch." << std::endl;
+        return false;
+    }
+    EchoPacket(PacketDirection::Received, packetData, metaDataLength);
+    m_StreamMetaDataVersion = ToUint32(&packetData[0], m_Endianness);
+    m_StreamMetaDataMaxDataLen = ToUint32(&packetData[4], m_Endianness);
+    m_StreamMetaDataPid = ToUint32(&packetData[8], m_Endianness);
+
+    return true;
+}
+
+void GatordMockService::SendConnectionAck()
+{
+    if (m_EchoPackets)
+    {
+        std::cout << "Sending connection acknowledgement." << std::endl;
+    }
+    // The connection ack packet is an empty data packet with packetId == 1.
+    SendPacket(0, 1, nullptr, 0);
+}
+
+bool GatordMockService::LaunchReceivingThread()
+{
+    if (m_EchoPackets)
+    {
+        std::cout << "Launching receiving thread." << std::endl;
+    }
+    // At this point we want to make the socket non blocking.
+    const int currentFlags = fcntl(m_ClientConnection, F_GETFL);
+    if (0 != fcntl(m_ClientConnection, F_SETFL, currentFlags | O_NONBLOCK))
+    {
+        close(m_ClientConnection);
+        std::cerr << "Failed to set socket as non blocking: " << strerror(errno) << std::endl;
+        return false;
+    }
+    m_ListeningThread = std::thread(&GatordMockService::ReceiveLoop, this, std::ref(*this));
+    return true;
+}
+
+void GatordMockService::WaitForReceivingThread()
+{
+    m_CloseReceivingThread.store(true);
+    // Check that the receiving thread is running
+    if (m_ListeningThread.joinable())
+    {
+        // Wait for the receiving thread to complete operations
+        m_ListeningThread.join();
+    }
+}
+
+
+void GatordMockService::SendPeriodicCounterSelectionList(uint period, std::vector<uint16_t> counters)
+{
+    //get the datalength in bytes
+    uint32_t datalength = static_cast<uint32_t>(4 + counters.size() * 2);
+
+    u_char data[datalength];
+
+    *data = static_cast<u_char>(period >> 24);
+    *(data + 1) = static_cast<u_char>(period >> 16 & 0xFF);
+    *(data + 2) = static_cast<u_char>(period >> 8 & 0xFF);
+    *(data + 3) = static_cast<u_char>(period & 0xFF);
+
+    for (unsigned long i = 0; i < counters.size(); ++i)
+    {
+        *(data + 4 + i * 2) = static_cast<u_char>(counters[i] >> 8);
+        *(data + 5 + i * 2) = static_cast<u_char>(counters[i] & 0xFF);
+    }
+
+    // create packet send packet
+    SendPacket(0, 4, data, datalength);
+}
+
+void GatordMockService::WaitCommand(uint timeout)
+{
+    std::this_thread::sleep_for(std::chrono::microseconds(timeout));
+
+    if (m_EchoPackets)
+    {
+        std::cout << std::dec << "Wait command with timeout of " << timeout << " microseconds completed. " << std::endl;
+    }
+}
+
+void GatordMockService::ReceiveLoop(GatordMockService& mockService)
+{
+    m_CloseReceivingThread.store(false);
+    while (!m_CloseReceivingThread.load())
+    {
+        try
+        {
+            armnn::profiling::Packet packet = mockService.WaitForPacket(500);
+        }
+        catch(armnn::TimeoutException)
+        {
+            // In this case we ignore timeouts and and keep trying to receive.
+        }
+    }
+}
+
+armnn::profiling::Packet GatordMockService::WaitForPacket(uint32_t timeoutMs)
+{
+    // Is there currently more than a headers worth of data waiting to be read?
+    int bytes_available;
+    ioctl(m_ClientConnection, FIONREAD, &bytes_available);
+    if (bytes_available > 8)
+    {
+        // Yes there is. Read it:
+        return ReceivePacket();
+    }
+    else
+    {
+        // No there's not. Poll for more data.
+        struct pollfd pollingFd[1]{};
+        pollingFd[0].fd = m_ClientConnection;
+        int pollResult = poll(pollingFd, 1, static_cast<int>(timeoutMs));
+
+        switch (pollResult)
+        {
+            // Error
+            case -1:
+                throw armnn::RuntimeException(std::string("File descriptor reported an error during polling: ") +
+                                              strerror(errno));
+
+            // Timeout
+            case 0:
+                throw armnn::TimeoutException("Timeout while waiting to receive packet.");
+
+            // Normal poll return. It could still contain an error signal
+            default:
+
+                // Check if the socket reported an error
+                if (pollingFd[0].revents & (POLLNVAL | POLLERR | POLLHUP))
+                {
+                    std::cout << "Error while polling receiving socket." << std::endl;
+                    throw armnn::RuntimeException(std::string("File descriptor reported an error during polling: ") +
+                                                  strerror(errno));
+                }
+
+                // Check if there is data to read
+                if (!(pollingFd[0].revents & (POLLIN)))
+                {
+                    // This is a corner case. The socket as been woken up but not with any data.
+                    // We'll throw a timeout exception to loop around again.
+                    throw armnn::TimeoutException("File descriptor was polled but no data was available to receive.");
+                }
+                return ReceivePacket();
+        }
+    }
+}
+
+
+armnn::profiling::Packet GatordMockService::ReceivePacket()
+{
+    uint32_t header[2];
+    if (!ReadHeader(header))
+    {
+        return armnn::profiling::Packet();
+    }
+    // Read data_length bytes from the socket.
+    std::unique_ptr<unsigned char[]> uniquePacketData = std::make_unique<unsigned char[]>(header[1]);
+    unsigned char *packetData = reinterpret_cast<unsigned char *>(uniquePacketData.get());
+
+    if (!ReadFromSocket(packetData, header[1]))
+    {
+        return armnn::profiling::Packet();
+    }
+
+    // Construct received packet
+    armnn::profiling::Packet packetRx = armnn::profiling::Packet(header[0], header[1], uniquePacketData);
+
+    // Pass packet into the handler registry
+    if (packetRx.GetHeader()!= 0)
+    {
+        m_PacketsReceivedCount.operator++(std::memory_order::memory_order_release);
+        m_HandlerRegistry.GetFunctor(header[0],1)->operator()(packetRx);
+    }
+
+    EchoPacket(PacketDirection::Received, packetData, sizeof(packetData));
+    return packetRx;
+}
+
+bool GatordMockService::SendPacket(uint32_t packetFamily, uint32_t packetId, const u_char* data, uint32_t dataLength)
+{
+    // Construct a packet from the id and data given and send it to the client.
+    // Encode the header.
+    uint32_t header[2];
+    header[0] = packetFamily << 26 | packetId << 16;
+    header[1] = dataLength;
+    // Add the header to the packet.
+    u_char packet[8 + dataLength ];
+    InsertU32(header[0], packet, m_Endianness);
+    InsertU32(header[1], packet + 4, m_Endianness);
+    // And the rest of the data if there is any.
+    if (dataLength > 0)
+    {
+        memcpy((packet + 8), data, dataLength);
+    }
+    EchoPacket(PacketDirection::Sending, packet, sizeof(packet));
+    if (-1 == write(m_ClientConnection, packet, sizeof(packet)))
+    {
+        std::cerr << ": Failure when writing to client socket: " << strerror(errno) << std::endl;
+        return false;
+    }
+    return true;
+}
+
+bool GatordMockService::ReadHeader(uint32_t headerAsWords[2])
+{
+    // The herader will always be 2x32bit words.
+    u_char header[8];
+    if (!ReadFromSocket(header, 8))
+    {
+        return false;
+    }
+    headerAsWords[0] = ToUint32(&header[0], m_Endianness);
+    headerAsWords[1] = ToUint32(&header[4], m_Endianness);
+    return true;
+}
+
+bool GatordMockService::ReadFromSocket(u_char* packetData, uint32_t expectedLength)
+{
+    // This is a blocking read until either expectedLength has been received or an error is detected.
+    ssize_t totalBytesRead = 0;
+    while (totalBytesRead < expectedLength)
+    {
+        ssize_t bytesRead = recv(m_ClientConnection, packetData, expectedLength, 0);
+        if (bytesRead < 0)
+        {
+            std::cerr << ": Failure when reading from client socket: " << strerror(errno) << std::endl;
+            return false;
+        }
+        if (bytesRead == 0)
+        {
+            std::cerr << ": EOF while reading from client socket." << std::endl;
+            return false;
+        }
+        totalBytesRead += bytesRead;
+    }
+    return true;
+};
+
+void GatordMockService::EchoPacket(PacketDirection direction, u_char* packet, size_t lengthInBytes)
+{
+    // If enabled print the contents of the data packet to the console.
+    if (m_EchoPackets)
+    {
+        if (direction == PacketDirection::Sending)
+        {
+            std::cout << "Sending " << std::dec << lengthInBytes << " bytes : ";
+        } else
+        {
+            std::cout << "Received " << std::dec << lengthInBytes << " bytes : ";
+        }
+        for (unsigned int i = 0; i < lengthInBytes; i++)
+        {
+            if ((i % 10) == 0)
+            {
+                std::cout << std::endl;
+            }
+            std::cout << std::hex << "0x" << static_cast<unsigned int>(packet[i]) << " ";
+        }
+        std::cout << std::endl;
+    }
+}
+
+uint32_t GatordMockService::ToUint32(u_char* data, TargetEndianness endianness)
+{
+    // Extract the first 4 bytes starting at data and push them into a 32bit integer based on the
+    // specified endianness.
+    if (endianness == TargetEndianness::BeWire)
+    {
+        return static_cast<uint32_t>(data[0]) << 24 | static_cast<uint32_t>(data[1]) << 16 |
+               static_cast<uint32_t>(data[2]) << 8 | static_cast<uint32_t>(data[3]);
+    }
+    else
+    {
+        return static_cast<uint32_t>(data[3]) << 24 | static_cast<uint32_t>(data[2]) << 16 |
+               static_cast<uint32_t>(data[1]) << 8 | static_cast<uint32_t>(data[0]);
+    }
+}
+
+void GatordMockService::InsertU32(uint32_t value, u_char* data, TargetEndianness endianness)
+{
+    // Take the bytes of a 32bit integer and copy them into char array starting at data considering
+    // the endianness value.
+    if (endianness == TargetEndianness::BeWire)
+    {
+        *data = static_cast<u_char>((value >> 24) & 0xFF);
+        *(data + 1) = static_cast<u_char>((value >> 16) & 0xFF);
+        *(data + 2) = static_cast<u_char>((value >> 8) & 0xFF);
+        *(data + 3) = static_cast<u_char>(value & 0xFF);
+    }
+    else
+    {
+        *(data + 3) = static_cast<u_char>((value >> 24) & 0xFF);
+        *(data + 2) = static_cast<u_char>((value >> 16) & 0xFF);
+        *(data + 1) = static_cast<u_char>((value >> 8) & 0xFF);
+        *data = static_cast<u_char>(value & 0xFF);
+    }
+}
+
+} // namespace gatordmock
+
+} // namespace armnn
diff --git a/tests/profiling/gatordmock/GatordMockService.hpp b/tests/profiling/gatordmock/GatordMockService.hpp
new file mode 100644 (file)
index 0000000..1aba2e8
--- /dev/null
@@ -0,0 +1,149 @@
+//
+// Copyright © 2019 Arm Ltd. All rights reserved.
+// SPDX-License-Identifier: MIT
+//
+
+#pragma once
+
+#include <string>
+#include <thread>
+#include <atomic>
+
+#include "../../src/profiling/CommandHandlerRegistry.hpp"
+#include "../../src/profiling/Packet.hpp"
+
+namespace armnn
+{
+
+namespace gatordmock
+{
+
+enum class TargetEndianness
+{
+    BeWire, LeWire
+};
+
+enum class PacketDirection
+{
+    Sending, Received
+};
+
+///  A class that implements a Mock Gatord server. It will listen on a specified Unix domain socket (UDS)
+///  namespace for client connections. It will then allow opertaions to manage coutners while receiving counter data.
+class GatordMockService
+{
+public:
+
+
+    /// @param registry reference to a command handler registry.
+    /// @param echoPackets if true the raw packets will be printed to stdout.
+    GatordMockService(armnn::profiling::CommandHandlerRegistry& registry, bool echoPackets)
+        : m_HandlerRegistry(registry)
+        , m_EchoPackets(echoPackets)
+    {
+        m_PacketsReceivedCount.store(0, std::memory_order_relaxed);
+    }
+
+    ~GatordMockService()
+    {
+        // We have set SOCK_CLOEXEC on these sockets but we'll close them to be good citizens.
+        close(m_ClientConnection);
+        close(m_ListeningSocket);
+    }
+
+    /// Establish the Unix domain socket and set it to listen for connections.
+    /// @param udsNamespace the namespace (socket address) associated with the listener.
+    /// @return true only if the socket has been correctly setup.
+    bool OpenListeningSocket(std::string udsNamespace);
+
+    /// Block waiting to accept one client to connect to the UDS.
+    /// @return the file descriptor of the client connection.
+    int BlockForOneClient();
+
+    /// Once the connection is open wait to receive the stream meta data packet from the client. Reading this
+    /// packet differs from others as we need to determine endianness.
+    /// @return true only if a valid stream met data packet has been received.
+    bool WaitForStreamMetaData();
+
+    /// Send a connection acknowledged packet back to the client.
+    void SendConnectionAck();
+
+    /// Start the thread that will receive all packets and print them nicely to stdout.
+    bool LaunchReceivingThread();
+
+    /// Return the total number of periodic counter capture packets received since the receive thread started.
+    /// @return number of periodic counter capture packets received.
+    uint32_t GetPacketsReceivedCount()
+    {
+        return m_PacketsReceivedCount.load(std::memory_order_acquire);
+    }
+
+    /// This is a placeholder method to prevent main exiting. It can be removed once the
+    /// command handling code is added.
+    void WaitForReceivingThread();
+
+    /// Send the counter list to ArmNN.
+    void SendPeriodicCounterSelectionList(uint period, std::vector<uint16_t> counters);
+
+    /// Execute the WAIT command from the comamnd file.
+    void WaitCommand(uint timeout);
+
+    uint32_t GetStreamMetadataVersion()
+    {
+        return m_StreamMetaDataVersion;
+    }
+
+    uint32_t GetStreamMetadataMaxDataLen()
+    {
+        return m_StreamMetaDataMaxDataLen;
+    }
+
+    uint32_t GetStreamMetadataPid()
+    {
+        return m_StreamMetaDataPid;
+    }
+
+private:
+
+    void ReceiveLoop(GatordMockService& mockService);
+
+    /// Block on the client connection until a complete packet has been received. This is a placeholder function to
+    /// enable early testing of the tool.
+    /// @return true if a valid packet has been received.
+    armnn::profiling::Packet WaitForPacket(uint32_t timeoutMs);
+
+    armnn::profiling::Packet ReceivePacket();
+
+    bool SendPacket(uint32_t packetFamily, uint32_t packetId, const u_char* data, uint32_t dataLength);
+
+    void EchoPacket(PacketDirection direction, u_char* packet, size_t lengthInBytes);
+
+    bool ReadHeader(uint32_t headerAsWords[2]);
+
+    bool ReadFromSocket(u_char* packetData, uint32_t expectedLength);
+
+    uint32_t ToUint32(u_char* data, TargetEndianness endianness);
+
+    void InsertU32(uint32_t value, u_char* data, TargetEndianness endianness);
+
+    static const uint32_t PIPE_MAGIC = 0x45495434;
+
+    std::atomic<uint32_t> m_PacketsReceivedCount;
+    TargetEndianness      m_Endianness;
+    uint32_t              m_StreamMetaDataVersion;
+    uint32_t              m_StreamMetaDataMaxDataLen;
+    uint32_t              m_StreamMetaDataPid;
+
+    armnn::profiling::CommandHandlerRegistry& m_HandlerRegistry;
+
+    bool m_EchoPackets;
+    int m_ListeningSocket;
+    int m_ClientConnection;
+    std::thread m_ListeningThread;
+    std::atomic<bool> m_CloseReceivingThread;
+};
+} // namespace gatordmock
+
+} // namespace armnn
+
+
diff --git a/tests/profiling/gatordmock/MockUtils.hpp b/tests/profiling/gatordmock/MockUtils.hpp
new file mode 100644 (file)
index 0000000..93fc408
--- /dev/null
@@ -0,0 +1,35 @@
+//
+// Copyright © 2017 Arm Ltd. All rights reserved.
+// SPDX-License-Identifier: MIT
+//
+
+#pragma once
+
+#include <EncodeVersion.hpp>
+
+namespace armnn
+{
+
+namespace gatordmock
+{
+
+
+uint32_t ConstructHeader(uint32_t packetFamily,
+                         uint32_t packetClass,
+                         uint32_t packetType)
+{
+    return ((packetFamily & 0x3F) << 26)|
+           ((packetClass & 0x3FF) << 19)|
+           ((packetType & 0x3FFF) << 16);
+}
+
+uint32_t ConstructHeader(uint32_t packetFamily,
+                         uint32_t packetId)
+{
+    return ((packetFamily & 0x3F) << 26)|
+           ((packetId & 0x3FF) << 16);
+}
+
+} // gatordmock
+
+} // armnn
diff --git a/tests/profiling/gatordmock/PeriodicCounterCaptureCommandHandler.cpp b/tests/profiling/gatordmock/PeriodicCounterCaptureCommandHandler.cpp
new file mode 100644 (file)
index 0000000..5a70f68
--- /dev/null
@@ -0,0 +1,147 @@
+//
+// Copyright © 2019 Arm Ltd. All rights reserved.
+// SPDX-License-Identifier: MIT
+//
+
+#include <iostream>
+
+#include "../../../src/profiling/ProfilingUtils.hpp"
+#include "PeriodicCounterCaptureCommandHandler.hpp"
+
+#include <boost/numeric/conversion/cast.hpp>
+
+namespace armnn
+{
+
+namespace gatordmock
+{
+
+using boost::numeric_cast;
+
+std::string CentreAlignFormatting(const std::string stringToPass, const int spacingWidth)
+{
+    std::stringstream outputStream, centrePadding;
+    int padding = spacingWidth - static_cast<int>(stringToPass.size());
+
+    for (int i = 0; i < padding / 2; ++i)
+    {
+        centrePadding << " ";
+    }
+
+    outputStream << centrePadding.str() << stringToPass << centrePadding.str();
+
+    if (padding > 0 && padding % 2 != 0)
+    {
+        outputStream << " ";
+    }
+
+    return outputStream.str();
+}
+
+void PeriodicCounterCaptureCommandHandler::ParseData(const armnn::profiling::Packet& packet)
+{
+    std::vector<uint16_t> counterIds;
+    std::vector<uint32_t> counterValues;
+
+    uint32_t sizeOfUint64 = numeric_cast<uint32_t>(sizeof(uint64_t));
+    uint32_t sizeOfUint32 = numeric_cast<uint32_t>(sizeof(uint32_t));
+    uint32_t sizeOfUint16 = numeric_cast<uint32_t>(sizeof(uint16_t));
+
+    uint32_t offset = 0;
+
+    if (packet.GetLength() >= 8)
+    {
+        offset = 0;
+
+        uint64_t timestamp = profiling::ReadUint64(reinterpret_cast<const unsigned char*>(packet.GetData()), offset);
+
+        if (m_FirstTimestamp == 0)    // detect the first timestamp we receive.
+        {
+            m_FirstTimestamp = timestamp;
+        }
+        else
+        {
+            m_SecondTimestamp    = timestamp;
+            m_CurrentPeriodValue = m_SecondTimestamp - m_FirstTimestamp;
+            m_FirstTimestamp     = m_SecondTimestamp;
+        }
+
+        // Length minus timestamp and header divided by the length of an indexPair
+        unsigned int counters = (packet.GetLength() - 8) / 6;
+
+        if (counters > 0)
+        {
+            counterIds.reserve(counters);
+            counterValues.reserve(counters);
+            // Move offset over timestamp area
+            offset += sizeOfUint64;
+            for (unsigned int pos = 0; pos < counters; ++pos)
+            {
+                counterIds.emplace_back(
+                    profiling::ReadUint16(reinterpret_cast<const unsigned char*>(packet.GetData()), offset));
+                offset += sizeOfUint16;
+
+                counterValues.emplace_back(
+                    profiling::ReadUint32(reinterpret_cast<const unsigned char*>(packet.GetData()), offset));
+                offset += sizeOfUint32;
+            }
+        }
+
+        m_CounterCaptureValues.m_Timestamp = timestamp;
+        m_CounterCaptureValues.m_Uids      = counterIds;
+        m_CounterCaptureValues.m_Values    = counterValues;
+    }
+}
+
+void PeriodicCounterCaptureCommandHandler::operator()(const profiling::Packet& packet)
+{
+    ParseData(packet);
+    if (m_EchoPackets)
+    {
+        std::string header, body, uidString, valueString;
+
+        for (uint16_t uid : m_CounterCaptureValues.m_Uids)
+        {
+            uidString.append(std::to_string(uid));
+            uidString.append(", ");
+        }
+
+        for (uint32_t val : m_CounterCaptureValues.m_Values)
+        {
+            valueString.append(std::to_string(val));
+            valueString.append(", ");
+        }
+
+        body.append(gatordmock::CentreAlignFormatting(std::to_string(m_CounterCaptureValues.m_Timestamp), 10));
+        body.append(" | ");
+        body.append(gatordmock::CentreAlignFormatting(std::to_string(m_CurrentPeriodValue), 13));
+        body.append(" | ");
+        body.append(gatordmock::CentreAlignFormatting(uidString, 10));
+        body.append(" | ");
+        body.append(gatordmock::CentreAlignFormatting(valueString, 10));
+        body.append("\n");
+
+        if (!m_HeaderPrinted)
+        {
+            header.append(gatordmock::CentreAlignFormatting(" Timestamp", 11));
+            header.append(" | ");
+            header.append(gatordmock::CentreAlignFormatting("Period (us)", 13));
+            header.append(" | ");
+            header.append(gatordmock::CentreAlignFormatting("UID's", static_cast<int>(uidString.size())));
+            header.append(" | ");
+            header.append(gatordmock::CentreAlignFormatting("Values", 10));
+            header.append("\n");
+
+            std::cout << header;
+            m_HeaderPrinted = true;
+        }
+
+        std::cout << std::string(body.size(), '-') << "\n";
+
+        std::cout << body;
+    }
+}
+
+}    // namespace gatordmock
+
+}    // namespace armnn
\ No newline at end of file
diff --git a/tests/profiling/gatordmock/PeriodicCounterCaptureCommandHandler.hpp b/tests/profiling/gatordmock/PeriodicCounterCaptureCommandHandler.hpp
new file mode 100644 (file)
index 0000000..7f46d8e
--- /dev/null
@@ -0,0 +1,54 @@
+//
+// Copyright © 2019 Arm Ltd. All rights reserved.
+// SPDX-License-Identifier: MIT
+//
+
+#pragma once
+
+#include "../../armnn/src/profiling/Packet.hpp"
+#include "../../armnn/src/profiling/CommandHandlerFunctor.hpp"
+
+#include <vector>
+
+namespace armnn
+{
+
+namespace gatordmock
+{
+
+struct CounterCaptureValues
+{
+    uint64_t m_Timestamp;
+    std::vector<uint16_t> m_Uids;
+    std::vector<uint32_t> m_Values;
+};
+
+class PeriodicCounterCaptureCommandHandler : public profiling::CommandHandlerFunctor
+{
+
+public:
+    PeriodicCounterCaptureCommandHandler(uint32_t packetId,
+                                         uint32_t version,
+                                         bool echoPackets)
+        : CommandHandlerFunctor(packetId, version)
+        , m_EchoPackets(echoPackets)
+    {}
+
+    void operator()(const armnn::profiling::Packet& packet) override;
+
+    CounterCaptureValues m_CounterCaptureValues;
+
+    uint64_t m_CurrentPeriodValue = 0;
+
+private:
+    void ParseData(const armnn::profiling::Packet& packet);
+
+    uint64_t m_FirstTimestamp = 0, m_SecondTimestamp = 0;
+
+    bool m_HeaderPrinted = false;
+    bool m_EchoPackets;
+};
+
+} // namespace gatordmock
+
+} // namespace armnn
\ No newline at end of file
diff --git a/tests/profiling/gatordmock/tests/GatordMockTests.cpp b/tests/profiling/gatordmock/tests/GatordMockTests.cpp
new file mode 100644 (file)
index 0000000..100b7b8
--- /dev/null
@@ -0,0 +1,178 @@
+//
+// Copyright © 2017 Arm Ltd. All rights reserved.
+// SPDX-License-Identifier: MIT
+//
+
+#include "../MockUtils.hpp"
+#include "../PeriodicCounterCaptureCommandHandler.hpp"
+
+#include "../../src/profiling/CommandHandlerRegistry.hpp"
+#include "../GatordMockService.hpp"
+
+#include "../../src/profiling/ProfilingService.hpp"
+#include "../../src/profiling/test/SendCounterPacketTests.hpp"
+
+#include <boost/cast.hpp>
+#include <boost/test/test_tools.hpp>
+#include <boost/test/unit_test_suite.hpp>
+
+BOOST_AUTO_TEST_SUITE(GatordMockTests)
+
+using namespace armnn;
+using namespace std::this_thread;    // sleep_for, sleep_until
+using namespace std::chrono_literals;
+
+// Required so build succeeds when local variable used only in assert
+#define _unused(x) ((void)(x))
+
+BOOST_AUTO_TEST_CASE(CounterCaptureHandlingTest)
+{
+    using boost::numeric_cast;
+
+    // Initialise functors and register into the CommandHandlerRegistry
+    uint32_t headerWord1 = gatordmock::ConstructHeader(1, 0, 0);
+
+    // Create the Command Handler Registry
+    profiling::CommandHandlerRegistry registry;
+
+    // Data with timestamp, counter idx & counter values
+    std::vector<std::pair<uint16_t, uint32_t>> indexValuePairs;
+    indexValuePairs.reserve(5);
+    indexValuePairs.emplace_back(std::make_pair<uint16_t, uint32_t>(0, 100));
+    indexValuePairs.emplace_back(std::make_pair<uint16_t, uint32_t>(1, 200));
+    indexValuePairs.emplace_back(std::make_pair<uint16_t, uint32_t>(2, 300));
+    indexValuePairs.emplace_back(std::make_pair<uint16_t, uint32_t>(3, 400));
+    indexValuePairs.emplace_back(std::make_pair<uint16_t, uint32_t>(4, 500));
+
+    // ((uint16_t (2 bytes) + uint32_t (4 bytes)) * 5) + word1 + word2
+    uint32_t dataLength = 38;
+
+    // Simulate two different packets incoming 500 ms apart
+    uint64_t time = static_cast<uint64_t>(
+        std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now().time_since_epoch())
+            .count());
+
+    sleep_for(5000us);
+
+    uint64_t time2 = static_cast<uint64_t>(
+        std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now().time_since_epoch())
+            .count());
+
+    // UniqueData required for Packet class
+    std::unique_ptr<unsigned char[]> uniqueData1 = std::make_unique<unsigned char[]>(dataLength);
+    unsigned char* data1                = reinterpret_cast<unsigned char*>(uniqueData1.get());
+
+    std::unique_ptr<unsigned char[]> uniqueData2 = std::make_unique<unsigned char[]>(dataLength);
+    unsigned char* data2                = reinterpret_cast<unsigned char*>(uniqueData2.get());
+
+    uint32_t sizeOfUint64 = numeric_cast<uint32_t>(sizeof(uint64_t));
+    uint32_t sizeOfUint32 = numeric_cast<uint32_t>(sizeof(uint32_t));
+    uint32_t sizeOfUint16 = numeric_cast<uint32_t>(sizeof(uint16_t));
+    // Offset index to point to mem address
+    uint32_t offset = 0;
+
+    profiling::WriteUint64(data1, offset, time);
+    offset += sizeOfUint64;
+    for (const auto& pair : indexValuePairs)
+    {
+        profiling::WriteUint16(data1, offset, pair.first);
+        offset += sizeOfUint16;
+        profiling::WriteUint32(data1, offset, pair.second);
+        offset += sizeOfUint32;
+    }
+
+    offset = 0;
+
+    profiling::WriteUint64(data2, offset, time2);
+    offset += sizeOfUint64;
+    for (const auto& pair : indexValuePairs)
+    {
+        profiling::WriteUint16(data2, offset, pair.first);
+        offset += sizeOfUint16;
+        profiling::WriteUint32(data2, offset, pair.second);
+        offset += sizeOfUint32;
+    }
+
+    // Create packet to send through to the command functor
+    profiling::Packet packet1(headerWord1, dataLength, uniqueData1);
+    profiling::Packet packet2(headerWord1, dataLength, uniqueData2);
+
+    uint32_t version = 1;
+    gatordmock::PeriodicCounterCaptureCommandHandler commandHandler(headerWord1, version, false);
+
+    // Simulate two separate packets coming in to calculate period
+    commandHandler(packet1);
+    commandHandler(packet2);
+
+    BOOST_ASSERT(4500 < commandHandler.m_CurrentPeriodValue && 5500 > commandHandler.m_CurrentPeriodValue);
+
+    for (size_t i = 0; i < commandHandler.m_CounterCaptureValues.m_Uids.size(); ++i)
+    {
+        BOOST_ASSERT(commandHandler.m_CounterCaptureValues.m_Uids[i] == i);
+    }
+}
+
+BOOST_AUTO_TEST_CASE(GatorDMockEndToEnd)
+{
+    // The purpose of this test is to setup both sides of the profiling service and get to the point of receiving
+    // performance data.
+
+    // Initialise functors and register into the CommandHandlerRegistry
+    uint32_t counterCaptureHeader = gatordmock::ConstructHeader(1, 0);
+    uint32_t version              = 1;
+
+    // Create the Command Handler Registry
+    profiling::CommandHandlerRegistry registry;
+
+    // Update with derived functors
+    gatordmock::PeriodicCounterCaptureCommandHandler counterCaptureCommandHandler(counterCaptureHeader, version, false);
+    // Register different derived functors
+    registry.RegisterFunctor(&counterCaptureCommandHandler);
+
+    // Setup the mock service to bind to the UDS.
+    std::string udsNamespace = "gatord_namespace";
+    gatordmock::GatordMockService mockService(registry, false);
+    mockService.OpenListeningSocket(udsNamespace);
+
+    // Enable the profiling service.
+    armnn::Runtime::CreationOptions::ExternalProfilingOptions options;
+    options.m_EnableProfiling                     = true;
+    profiling::ProfilingService& profilingService = profiling::ProfilingService::Instance();
+    profilingService.ResetExternalProfilingOptions(options, true);
+
+    // Bring the profiling service to the "WaitingForAck" state
+    BOOST_CHECK(profilingService.GetCurrentState() == profiling::ProfilingState::Uninitialised);
+    profilingService.Update();
+    BOOST_CHECK(profilingService.GetCurrentState() == profiling::ProfilingState::NotConnected);
+    profilingService.Update();
+
+    // Connect the profiling service to the mock Gatord.
+    int clientFd = mockService.BlockForOneClient();
+    if (-1 == clientFd)
+    {
+        BOOST_FAIL("Failed to connect client");
+    }
+    // Give the profiling service sending thread time start executing and send the stream metadata.
+    std::this_thread::sleep_for(std::chrono::milliseconds(1000));
+    // We should now be in WaitingForAck state.
+    BOOST_CHECK(profilingService.GetCurrentState() == profiling::ProfilingState::WaitingForAck);
+    profilingService.Update();
+    // Read the stream metadata on the mock side.
+    if (!mockService.WaitForStreamMetaData())
+    {
+        BOOST_FAIL("Failed to receive StreamMetaData");
+    }
+    // Send Ack from GatorD
+    mockService.SendConnectionAck();
+    std::this_thread::sleep_for(std::chrono::milliseconds(1000));
+    // At this point the service should be in active state.
+    BOOST_ASSERT(profilingService.GetCurrentState() == profiling::ProfilingState::Active);
+
+    // Future tests here will add counters to the ProfilingService, increment values and examine
+    // PeriodicCounterCapture data received. These are yet to be integrated.
+
+    options.m_EnableProfiling = false;
+    profilingService.ResetExternalProfilingOptions(options, true);
+}
+
+BOOST_AUTO_TEST_SUITE_END()