[API/Status] Implement the memory status API and add its testcases
authorDongju Chae <dongju.chae@samsung.com>
Wed, 6 May 2020 09:56:37 +0000 (18:56 +0900)
committer송욱/On-Device Lab(SR)/Staff Engineer/삼성전자 <wook16.song@samsung.com>
Thu, 7 May 2020 05:51:29 +0000 (14:51 +0900)
This patch implements the memory status API and add its testcases.
It's one of VD requirements (17th).

Signed-off-by: Dongju Chae <dongju.chae@samsung.com>
include/host/libnpuhost.h
src/core/ne-handler.cc
src/core/ne-handler.h
src/core/npu/NPUdrvAPI.h
src/core/npu/NPUdrvAPI_emul.cc
tests/apptests/example_visa.c
tests/unittests/ne_core_handler_test.cc

index be123cd..666d499 100644 (file)
@@ -22,6 +22,7 @@
 #define __NPU_HOST_LIBNPUHOST_H__
 
 #include <stdint.h>
+#include <stddef.h>
 #include <stdbool.h>
 #include <typedef.h>
 #include <npubinfmt.h>
@@ -322,6 +323,15 @@ int runNPU_async(npudev_h dev, uint32_t modelid, const input_buffers *input,
     npuOutputNotify cb, uint64_t *sequence, void *data,
     npu_async_mode mode);
 
+/**
+ * @brief get the current memory status for the given device
+ * @param[in] dev The NPU device handle
+ * @param[out] alloc_total The size of allocated memory until now
+ * @param[out] free_total The size of freed memory until now
+ * @return @c 0 if no error. otherwise a negatice error value
+ */
+int getNPU_memoryStatus(npudev_h dev, size_t *alloc_total, size_t *free_total);
+
 /** Optional Features. May be left unimplemented until later versions */
 /** @todo func prototype TBD. This does not use callback. */
 int runNPU_async_noalloc(npudev_h dev, uint32_t modelid, const input_buffers *input,
@@ -337,10 +347,6 @@ int waitNPUrun(npudev_h dev);
 int getNPUrunStatus(npudev_h dev, uint64_t *status);
 
 /** @todo func prototype TBD */
-int getNPUmemoryStatus(npudev_h dev, uint64_t *total, uint64_t *resvtotal,
-    uint64_t *resvfree, uint64_t *resvmodels, uint64_t *resvdata);
-
-/** @todo func prototype TBD */
 int getNPUstatus(npudev_h dev, uint64_t *status);
 
 /**
index a10a985..7ee9f9b 100644 (file)
@@ -289,6 +289,20 @@ int cleanNPU_inputBuffers (npudev_h dev, input_buffers * input)
 }
 
 /**
+ * @brief get the current memory status for the given device
+ * @param[in] dev The NPU device handle
+ * @param[out] alloc_total The size of allocated memory until now
+ * @param[out] free_total The size of freed memory until now
+ * @return @c 0 if no error. otherwise a negatice error value
+ */
+int getNPU_memoryStatus(npudev_h dev, size_t *alloc_total, size_t *free_total)
+{
+  INIT_HOST_HANDLER (host_handler, dev);
+
+  return host_handler->getMemoryStatus (alloc_total, free_total);
+}
+
+/**
  * @brief Get metadata for NPU model
  * @param[in] model The path of model binary file
  * @param[in] need_extra whether you want to extract the extra data in metadata
@@ -833,6 +847,22 @@ HostHandler::deallocGenericBuffer (generic_buffers *buffers)
   return deallocGenericBuffer (&buffers->bufs[0]);
 }
 
+/**
+ * @brief get the current memory status
+ * @param[out] alloc_total The size of allocated memory until now
+ * @param[out] free_total The size of freed memory until now
+ * @return 0 if no error. otherwise a negatice error value
+ */
+int
+HostHandler::getMemoryStatus (size_t *alloc_total, size_t *free_total)
+{
+  /** API is always set in initialize () */
+  const DriverAPI * api = device_->getDriverAPI ();
+  assert (api != nullptr);
+
+  return api->getMemoryStatus (alloc_total, free_total);
+}
+
 /** implement methods of Device class */
 
 /** @brief constructor of device */
@@ -1171,6 +1201,8 @@ TrinityVision::callback (Request *req, npuOutputNotify cb, void *cb_data)
   }
 
   cb (&output, req->getID(), cb_data);
+
+  delete buffer;
 }
 
 /** @brief implementation of TRIV2's setModel (): WIP */
index 0b34e4c..c192702 100644 (file)
@@ -52,6 +52,9 @@ class HostHandler {
         npuOutputNotify cb = nullptr, void *cb_data = nullptr,
         npu_async_mode mode = NPUASYNC_WAIT, uint64_t *sequence = nullptr);
 
+    /** @brief get statistics */
+    int getMemoryStatus (size_t *alloc_total, size_t *free_total);
+
     static int getNumDevices (dev_type type);
     static int getDevice (npudev_h *dev, dev_type type, uint32_t id);
 
@@ -89,6 +92,7 @@ class Device {
     void setAsyncMode (npu_async_mode mode) { mode_ = mode; }
     HostHandler *getHostHandler () { return handler_.get(); }
     bool initialized () { return initialized_; }
+    const DriverAPI *getDriverAPI () { return api_.get(); }
 
     /** the below requires initialized variables */
 
index 88ed332..8f3a359 100644 (file)
@@ -70,6 +70,8 @@ class DriverAPI {
     virtual int alloc (size_t size) const { return -EPERM; }
     /** @brief deallocate memory with the corresponding dmabuf fd */
     virtual int dealloc (int dmabuf) const { return -EPERM; }
+    /** @brief get memory status */
+    virtual int getMemoryStatus (size_t *alloc, size_t *free) const { return -EPERM; }
 
     /** @brief do mmap() for the dmabuf fd */
     virtual void *mmap (int dmabuf, size_t size) const { return nullptr; }
@@ -125,6 +127,7 @@ class TrinityAsrAPI : public DriverAPI {
 
 /** @brief emulation element */
 class EmulElement;
+class EmulStat;
 /** @brief Driver APIs for emulation */
 class TrinityEmulAPI : public DriverAPI {
   public:
@@ -138,6 +141,7 @@ class TrinityEmulAPI : public DriverAPI {
 
     int alloc (size_t size) const;
     int dealloc (int dmabuf) const;
+    int getMemoryStatus (size_t *alloc_total, size_t *free_total) const;
 
     void *mmap (int dmabuf, size_t size) const;
     int munmap (void *addr, size_t size) const;
@@ -150,6 +154,8 @@ class TrinityEmulAPI : public DriverAPI {
       /**< global api fd */
     static ThreadSafeMap<int, EmulElement> elem_map_;
       /**< dmabuf-to-element map. to track memory allocation */
+    static ThreadSafeMap<int, EmulStat> stat_map_;
+      /**< devfd-to-stat map. to track memory statistics */
 };
 
 #endif
index 02575ee..0f49167 100644 (file)
 
 #define MAX_EMUL_DEVICES (100)
 
+/**
+ * @brief memory statistics. Because driver API methods require 'const' instances,
+ * we use a static variable for std::map to update statistics for each device emulation.
+ */
+class EmulStat {
+  public:
+    EmulStat () : alloc_total_ (0), free_total_ (0) {}
+
+    void addMemoryAlloc (size_t size) {
+      std::unique_lock<std::mutex> lock(lock_);
+      alloc_total_ += size;
+    }
+
+    void addMemoryFree (size_t size) {
+      std::unique_lock<std::mutex> lock(lock_);
+      free_total_ += size;
+    }
+
+    void getMemoryStatus (size_t * alloc_total, size_t * free_total) {
+      std::unique_lock<std::mutex> lock(lock_);
+      *alloc_total = alloc_total_;
+      *free_total = free_total_;
+    }
+
+  private:
+    std::mutex lock_;
+    size_t alloc_total_;
+    size_t free_total_;
+    /** @todo add more */
+};
+
 /** @brief emulation element */
 class EmulElement {
   public:
@@ -56,6 +87,8 @@ EmulElement::EmulElement (size_t size)
 
 /** @brief dmabuf-to-element map */
 ThreadSafeMap<int, EmulElement> TrinityEmulAPI::elem_map_;
+/** @brief devfd-to-stat map */
+ThreadSafeMap<int, EmulStat> TrinityEmulAPI::stat_map_;
 /** @brief element's global id */
 std::atomic<int> EmulElement::global_id_ (0);
 /** @brief element's global id */
@@ -87,6 +120,11 @@ TrinityEmulAPI::open ()
 
   /** this should be different for every instance allocation */
   dev_fd_ = global_fd_.fetch_add (1);
+
+  int status = stat_map_.insert (dev_fd_, new EmulStat);
+  if (status != 0)
+    return status;
+
   return 0;
 }
 
@@ -132,6 +170,10 @@ TrinityEmulAPI::alloc (size_t size) const
   if (status != 0)
     return status;
 
+  EmulStat *stat = stat_map_.find (dev_fd_);
+  assert (stat != nullptr); /* always exist */
+  stat->addMemoryAlloc (size);
+
   return elem->getDmabuf();
 }
 
@@ -150,7 +192,34 @@ TrinityEmulAPI::dealloc (int dmabuf) const
   if (elem == nullptr)
     return -ENOENT;
 
+  EmulStat *stat = stat_map_.find (dev_fd_);
+  assert (stat != nullptr); /* always exist */
+  stat->addMemoryFree (elem->getSize());
+
   elem_map_.remove (elem->getDmabuf());
+
+  return 0;
+}
+
+/**
+ * @brief get the current memory status
+ * @param[out] alloc_total The size of allocated memory until now
+ * @param[out] free_total The size of freed memory until now
+ * @return 0 if no error. otherwise a negatice error value
+ */
+int
+TrinityEmulAPI::getMemoryStatus (size_t *alloc_total, size_t *free_total) const
+{
+  if (!initialized())
+    return -EPERM;
+
+  if (alloc_total == nullptr || free_total == nullptr)
+    return -EINVAL;
+
+  EmulStat *stat = stat_map_.find (dev_fd_);
+  assert (stat != nullptr); /* always exist */
+  stat->getMemoryStatus (alloc_total, free_total);
+
   return 0;
 }
 
index 5ba746a..6c7151a 100644 (file)
@@ -126,6 +126,22 @@ compare_output_buffers (const npubin_meta *meta, const char *base_path,
   return err;
 }
 
+static void
+check_memory_leak (npudev_h dev)
+{
+  size_t alloc_total = 0;
+  size_t free_total = 0;
+  int status;
+
+  status = getNPU_memoryStatus (dev, &alloc_total, &free_total);
+  if (status != 0)
+    return;
+
+  if (alloc_total != free_total)
+    fprintf (stderr, "memory leak check failed (0x%zx != 0x%zx)\n",
+        free_total, alloc_total);
+}
+
 /** @brief run inference for each target visa binary (on sync mode) */
 static int
 run_inference_each (npudev_h dev, const char *base_path, const char *target)
@@ -265,6 +281,10 @@ out_clean:
   cleanNPU_inputBuffers (dev, &input);
 out_free_meta:
   free (meta);
+
+  /** ensure that all memory (allocated by libnpuhost APIs) are successfully freed */
+  check_memory_leak (dev);
+
   return err;
 }
 
index 4f4aa68..189ac97 100644 (file)
@@ -1364,6 +1364,80 @@ TEST (ne_core_handler_test, handler_triv_run_async_n)
 }
 
 /**
+ * @brief test HostHandler's getMemoryStatus ()
+ */
+TEST (ne_core_handler_test, handler_triv_get_memory_status)
+{
+  std::unique_ptr<Device> device (Device::createInstance (NPUCOND_TRIV_CONN_SOCIP, 0));
+  ASSERT_NE (device.get (), nullptr);
+
+  HostHandler * handler = device->getHostHandler ();
+  ASSERT_NE (handler, nullptr);
+
+  const size_t size = 4096;
+  size_t alloc_total;
+  size_t free_total;
+
+  EXPECT_EQ (handler->getMemoryStatus (&alloc_total, &free_total), 0);
+  EXPECT_EQ (alloc_total, 0);
+  EXPECT_EQ (free_total, 0);
+
+  /** buffer */
+  input_buffers input;
+  input.num_buffers = 1;
+  input.bufs[0].type = BUFFER_MAPPED;
+  input.bufs[0].size = size;
+
+  ASSERT_EQ (handler->allocGenericBuffer (&input), 0);
+
+  EXPECT_EQ (handler->getMemoryStatus (&alloc_total, &free_total), 0);
+  EXPECT_EQ (alloc_total, size);
+  EXPECT_EQ (free_total, 0);
+
+  ASSERT_EQ (handler->deallocGenericBuffer (&input), 0);
+
+  EXPECT_EQ (handler->getMemoryStatus (&alloc_total, &free_total), 0);
+  EXPECT_EQ (alloc_total, size);
+  EXPECT_EQ (free_total, size);
+
+  /** model */
+  uint32_t modelid;
+  generic_buffer model_buf;
+  create_model_buffer (model_buf);
+
+  ASSERT_EQ (handler->registerModel (&model_buf, &modelid), 0);
+
+  EXPECT_EQ (handler->getMemoryStatus (&alloc_total, &free_total), 0);
+  EXPECT_EQ (alloc_total, size * 2);
+  EXPECT_EQ (free_total, size);
+
+  ASSERT_EQ (handler->unregisterModels (), 0);
+
+  EXPECT_EQ (handler->getMemoryStatus (&alloc_total, &free_total), 0);
+  EXPECT_EQ (alloc_total, size * 2);
+  EXPECT_EQ (free_total, size * 2);
+}
+
+/**
+ * @brief test HostHandler's getMemoryStatus () with error handling
+ */
+TEST (ne_core_handler_test, handler_triv_get_memory_status_n)
+{
+  std::unique_ptr<Device> device (Device::createInstance (NPUCOND_TRIV_CONN_SOCIP, 0));
+  ASSERT_NE (device.get (), nullptr);
+
+  HostHandler * handler = device->getHostHandler ();
+  ASSERT_NE (handler, nullptr);
+
+  size_t alloc_total;
+  size_t free_total;
+
+  EXPECT_NE (handler->getMemoryStatus (nullptr, nullptr), 0);
+  EXPECT_NE (handler->getMemoryStatus (&alloc_total, nullptr), 0);
+  EXPECT_NE (handler->getMemoryStatus (nullptr, &free_total), 0);
+}
+
+/**
  * @brief main function for unit test
  */
 int