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>
#define __NPU_HOST_LIBNPUHOST_H__
#include <stdint.h>
+#include <stddef.h>
#include <stdbool.h>
#include <typedef.h>
#include <npubinfmt.h>
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,
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);
/**
}
/**
+ * @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
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 */
}
cb (&output, req->getID(), cb_data);
+
+ delete buffer;
}
/** @brief implementation of TRIV2's setModel (): WIP */
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);
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 */
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; }
/** @brief emulation element */
class EmulElement;
+class EmulStat;
/** @brief Driver APIs for emulation */
class TrinityEmulAPI : public DriverAPI {
public:
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;
/**< 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
#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:
/** @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 */
/** 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;
}
if (status != 0)
return status;
+ EmulStat *stat = stat_map_.find (dev_fd_);
+ assert (stat != nullptr); /* always exist */
+ stat->addMemoryAlloc (size);
+
return elem->getDmabuf();
}
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;
}
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)
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;
}
}
/**
+ * @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