[ML Agent] Implementation of DBus Model interface
authorSangjung Woo <sangjung.woo@samsung.com>
Wed, 27 Jul 2022 07:11:59 +0000 (16:11 +0900)
committergichan-jang <56856496+gichan-jang@users.noreply.github.com>
Mon, 8 Aug 2022 07:18:01 +0000 (16:18 +0900)
This patch newly adds the DBus Model interface, which handles the neural
network model files. The name of a model file and its file path is
stored to the internal key-value database. To do this, this patch also
adds the IMLServiceDB interface for database operation and
MLServiceLevelDB class for LevelDB support.

Signed-off-by: Sangjung Woo <sangjung.woo@samsung.com>
daemon/includes/dbus-interface.h
daemon/includes/gdbus-util.h
daemon/includes/modules.h
daemon/includes/service-db.hh [new file with mode: 0644]
daemon/meson.build
daemon/model-dbus-impl.cc [new file with mode: 0644]
daemon/service-db.cc [new file with mode: 0644]
dbus/model-dbus.xml [new file with mode: 0644]

index 7403aef..af29ab0 100644 (file)
@@ -22,6 +22,7 @@
 #define DBUS_ML_BUS_NAME                "org.tizen.machinelearning.service"
 #define DBUS_ML_PATH                    "/Org/Tizen/MachineLearning/Service"
 
+/* Pipeline Interface */
 #define DBUS_PIPELINE_INTERFACE          "org.tizen.machinelearning.service.pipeline"
 #define DBUS_PIPELINE_PATH               "/Org/Tizen/MachineLearning/Service/Pipeline"
 
 #define DBUS_PIPELINE_I_GET_STATE_HANDLER       "handle_get_state"
 #define DBUS_PIPELINE_I_GET_DESCRIPTION_HANDLER "handle_get_description"
 
+/* Model Interface */
+#define DBUS_MODEL_INTERFACE            "org.tizen.machinelearning.service.model"
+#define DBUS_MODEL_PATH                 "/Org/Tizen/MachineLearning/Service/Model"
+
+#define DBUS_MODEL_I_HANDLER_SET_PATH     "handle_set_path"
+#define DBUS_MODEL_I_HANDLER_GET_PATH     "handle_get_path"
+#define DBUS_MODEL_I_HANDLER_DELETE       "handle_delete"
+
 #endif /* __GDBUS_INTERFACE_H__ */
index 818b09d..4a7f77d 100644 (file)
 
 #include "pipeline-dbus.h"
 
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
 /**
  * @brief DBus signal handler information to connect
  */
@@ -96,4 +100,8 @@ int gdbus_get_system_connection (gboolean is_session);
  * @brief Disconnect the DBus message bus.
  */
 void gdbus_put_system_connection (void);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
 #endif /* __GDBUS_UTIL_H__ */
index a4b4556..eb8d2b0 100644 (file)
 #ifndef __MODULES_H__
 #define __MODULES_H__
 
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
 /**
  * @brief Data structure contains the name and callback functions for a specific DBus interface.
  */
@@ -65,4 +69,8 @@ void add_module (const struct module_ops *module);
  * @param[in] module DBus interface information.
  */
 void remove_module (const struct module_ops *module);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
 #endif /* __MODULES_H__ */
diff --git a/daemon/includes/service-db.hh b/daemon/includes/service-db.hh
new file mode 100644 (file)
index 0000000..f338f7d
--- /dev/null
@@ -0,0 +1,70 @@
+/* SPDX-License-Identifier: Apache-2.0 */
+/**
+ * Copyright (c) 2022 Samsung Electronics Co., Ltd. All Rights Reserved.
+ *
+ * @file service-db.hh
+ * @date 28 Mar 2022
+ * @brief NNStreamer/Service Database Interface
+ * @see        https://github.com/nnstreamer/api
+ * @author Sangjung Woo <sangjung.woo@samsung.com>
+ * @bug No known bugs except for NYI items
+ */
+
+#ifndef __SERVICE_DB_HH__
+#define __SERVICE_DB_HH__
+
+#include <leveldb/c.h>
+#include <iostream>
+
+/**
+ * @brief Interface for database operation of ML service
+ */
+class IMLServiceDB
+{
+public:
+  /**
+   * @brief Destroy the IMLServiceDB object
+   */
+  virtual ~IMLServiceDB ()
+  {
+  };
+  virtual void connectDB () = 0;
+  virtual void disconnectDB () = 0;
+  virtual void put (const std::string key, const std::string value) = 0;
+  virtual void get (const std::string name,
+      std::string & out_value) = 0;
+  virtual void del (const std::string name) = 0;
+};
+
+/**
+ * @brief Class for implementation of IMLServiceDB
+ */
+class MLServiceLevelDB : public IMLServiceDB
+{
+public:
+  MLServiceLevelDB (const MLServiceLevelDB &) = delete;
+  MLServiceLevelDB (MLServiceLevelDB &&) = delete;
+  MLServiceLevelDB & operator= (const MLServiceLevelDB &) = delete;
+  MLServiceLevelDB & operator= (MLServiceLevelDB &&) = delete;
+
+  virtual void connectDB () override;
+  virtual void disconnectDB () override;
+  virtual void put (const std::string name,
+      const std::string value) override;
+  virtual void get (std::string name,
+      std::string & out_value) override;
+  virtual void del (std::string name);
+
+  static IMLServiceDB & getInstance (void);
+
+private:
+  MLServiceLevelDB (std::string path);
+  virtual ~MLServiceLevelDB ();
+
+  std::string path;
+  leveldb_t *db_obj;
+  leveldb_readoptions_t *db_roptions;
+  leveldb_writeoptions_t *db_woptions;
+};
+
+#endif /* __SERVICE_DB_HH__ */
index df40554..1303061 100644 (file)
@@ -4,6 +4,16 @@ if get_option('enable-machine-learning-agent')
   nns_ml_agent_gen_srcs = []
   nns_ml_agent_incs = include_directories('includes')
 
+  dlog_dep = dependency('dlog')
+  libsystemd_dep = dependency('libsystemd')
+
+  nns_ml_agent_srcs += join_paths('main.c')
+  nns_ml_agent_srcs += join_paths('modules.c')
+  nns_ml_agent_srcs += join_paths('gdbus-util.c')
+  nns_ml_agent_srcs += join_paths('service-db.cc')
+  nns_ml_agent_srcs += join_paths('pipeline-module.c')
+  nns_ml_agent_srcs += join_paths('model-dbus-impl.cc')
+
   # Generate GDbus header and code
   gdbus_prog = find_program('gdbus-codegen', required : true)
   gdbus_gen_src = custom_target('gdbus-gencode',
@@ -13,13 +23,16 @@ if get_option('enable-machine-learning-agent')
               '--generate-c-code', 'pipeline-dbus',
               '--output-directory', meson.current_build_dir(),
               '@INPUT@'])
-
   nns_ml_agent_gen_srcs += gdbus_gen_src
 
-  nns_ml_agent_srcs += join_paths('main.c')
-  nns_ml_agent_srcs += join_paths('modules.c')
-  nns_ml_agent_srcs += join_paths('gdbus-util.c')
-  nns_ml_agent_srcs += join_paths('pipeline-module.c')
+  gdbus_gen_model_src = custom_target('gdbus-model-gencode',
+    input : '../dbus/model-dbus.xml',
+    output : ['model-dbus.h', 'model-dbus.c'],
+    command : [gdbus_prog, '--interface-prefix', 'org.tizen',
+              '--generate-c-code', 'model-dbus',
+              '--output-directory', meson.current_build_dir(),
+              '@INPUT@'])
+  nns_ml_agent_gen_srcs += gdbus_gen_model_src
 
   # Enable ML Agent Test
   gdbus_gen_test_src = custom_target('gdbus-gencode-test',
@@ -32,8 +45,7 @@ if get_option('enable-machine-learning-agent')
 
   gdbus_gen_header_dep = declare_dependency(sources : nns_ml_agent_gen_srcs)
   gdbus_gen_header_test_dep = declare_dependency(sources : [nns_ml_agent_gen_srcs, gdbus_gen_test_src])
-  dlog_dep = dependency('dlog')
-  libsystemd_dep = dependency('libsystemd')
+
   ai_service_daemon_deps = [
     gdbus_gen_header_dep,
     glib_dep,
@@ -41,6 +53,7 @@ if get_option('enable-machine-learning-agent')
     gio_unix_dep,
     gst_dep,
     dlog_dep,
+    leveldb_dep,
     libsystemd_dep
   ]
 
diff --git a/daemon/model-dbus-impl.cc b/daemon/model-dbus-impl.cc
new file mode 100644 (file)
index 0000000..34748eb
--- /dev/null
@@ -0,0 +1,254 @@
+/* SPDX-License-Identifier: Apache-2.0 */
+/**
+ * Copyright (c) 2022 Samsung Electronics Co., Ltd. All Rights Reserved.
+ *
+ * @file model-dbus-impl.cc
+ * @date 29 Jul 2022
+ * @brief DBus implementation for Model Interface
+ * @see        https://github.com/nnstreamer/api
+ * @author Sangjung Woo <sangjung.woo@samsung.com>
+ * @bug No known bugs except for NYI items
+ */
+
+#include <glib.h>
+#include <errno.h>
+
+#include "common.h"
+#include "modules.h"
+#include "gdbus-util.h"
+#include "dbus-interface.h"
+#include "model-dbus.h"
+#include "service-db.hh"
+
+static MachinelearningServiceModel *g_gdbus_instance = NULL;
+
+/**
+ * @brief Utility function to get the DBus proxy of Model interface.
+ */
+static MachinelearningServiceModel *
+gdbus_get_model_instance (void)
+{
+  return machinelearning_service_model_skeleton_new ();
+}
+
+/**
+ * @brief Utility function to release DBus proxy of Model interface.
+ */
+static void
+gdbus_put_model_instance (MachinelearningServiceModel ** instance)
+{
+  g_clear_object (instance);
+}
+
+/**
+ * @brief The callback function of SetPath method.
+ *
+ * @param obj Proxy instance.
+ * @param invoc Method invocation handle.
+ * @param name The name of target model.
+ * @param path the file path of target.
+ * @return @c TRUE if the request is handled. FALSE if the service is not available.
+ */
+static gboolean
+dbus_cb_model_set_path (MachinelearningServiceModel *obj,
+    GDBusMethodInvocation *invoc,
+    const gchar *name,
+    const gchar *path)
+{
+  int ret = 0;
+  IMLServiceDB &db = MLServiceLevelDB::getInstance ();
+
+  try {
+    db.connectDB();
+    db.put (name, std::string (path));
+  }
+  catch (const std::runtime_error & e)
+  {
+    ret = -EIO;
+  }
+  catch (const std::invalid_argument & e)
+  {
+    ret = -EINVAL;
+  }
+  catch (const std::exception & e)
+  {
+    ret = -EIO;
+  }
+
+  db.disconnectDB ();
+  machinelearning_service_model_complete_set_path (obj, invoc, ret);
+
+  return TRUE;
+}
+
+/**
+ * @brief The callback function of GetPath method
+ *
+ * @param obj Proxy instance.
+ * @param invoc Method invocation handle.
+ * @param name The name of target model.
+ * @return @c TRUE if the request is handled. FALSE if the service is not available.
+ */
+static gboolean
+dbus_cb_model_get_path (MachinelearningServiceModel *obj,
+    GDBusMethodInvocation *invoc,
+    const gchar *name)
+{
+  int ret = 0;
+  std::string ret_path;
+  IMLServiceDB & db = MLServiceLevelDB::getInstance ();
+
+  try {
+    db.connectDB ();
+    db.get (name, ret_path);
+  }
+  catch (const std::invalid_argument & e)
+  {
+    ret = -EINVAL;
+  }
+  catch (const std::runtime_error & e)
+  {
+    ret = -EIO;
+  }
+  catch (const std::exception & e)
+  {
+    ret = -EIO;
+  }
+
+  db.disconnectDB ();
+  machinelearning_service_model_complete_get_path (obj, invoc, ret_path.c_str(), ret);
+
+  return TRUE;
+}
+
+/**
+ * @brief The callback function of Delete method
+ *
+ * @param obj Proxy instance.
+ * @param invoc Method invocation handle.
+ * @param name The name of target model.
+ * @return @c TRUE if the request is handled. FALSE if the service is not available.
+ */
+static gboolean
+gdbus_cb_model_delete (MachinelearningServiceModel *obj,
+    GDBusMethodInvocation *invoc,
+    const gchar *name)
+{
+  int ret = 0;
+  IMLServiceDB & db = MLServiceLevelDB::getInstance ();
+
+  try {
+    db.connectDB ();
+    db.del (name);
+  }
+  catch (const std::invalid_argument & e)
+  {
+    ret = -EINVAL;
+  }
+  catch (const std::runtime_error & e)
+  {
+    ret = -EIO;
+  }
+  catch (const std::exception & e)
+  {
+    ret = -EIO;
+  }
+
+  db.disconnectDB ();
+  machinelearning_service_model_complete_delete (obj, invoc, ret);
+
+  return TRUE;
+}
+
+/**
+ * @brief Event handler list of Model interface
+ */
+static struct gdbus_signal_info handler_infos[] = {
+  {
+    .signal_name = DBUS_MODEL_I_HANDLER_SET_PATH,
+    .cb = G_CALLBACK (dbus_cb_model_set_path),
+    .cb_data = NULL,
+    .handler_id = 0,
+  }, {
+    .signal_name = DBUS_MODEL_I_HANDLER_GET_PATH,
+    .cb = G_CALLBACK (dbus_cb_model_get_path),
+    .cb_data = NULL,
+    .handler_id = 0,
+  }, {
+    .signal_name = DBUS_MODEL_I_HANDLER_DELETE,
+    .cb = G_CALLBACK (gdbus_cb_model_delete),
+    .cb_data = NULL,
+    .handler_id = 0,
+  },
+};
+
+/**
+ * @brief The callback function for probing Model Interface module.
+ */
+static int
+probe_model_module (void *data)
+{
+  int ret = 0;
+  g_debug ("probe_model_module");
+
+  g_gdbus_instance = gdbus_get_model_instance ();
+  if (NULL == g_gdbus_instance) {
+    g_critical ("cannot get a dbus instance for the %s interface\n",
+        DBUS_MODEL_INTERFACE);
+    return -ENOSYS;
+  }
+
+  ret = gdbus_connect_signal (g_gdbus_instance,
+      ARRAY_SIZE(handler_infos), handler_infos);
+  if (ret < 0) {
+    g_critical ("cannot register callbacks as the dbus method invocation "
+        "handlers\n ret: %d", ret);
+    ret = -ENOSYS;
+    goto out;
+  }
+
+  ret = gdbus_export_interface (g_gdbus_instance, DBUS_MODEL_PATH);
+  if (ret < 0) {
+    g_critical ("cannot export the dbus interface '%s' "
+        "at the object path '%s'\n", DBUS_MODEL_INTERFACE, DBUS_MODEL_PATH);
+    ret = -ENOSYS;
+    goto out_disconnect;
+  }
+
+  return 0;
+
+out_disconnect:
+  gdbus_disconnect_signal (g_gdbus_instance, 
+    ARRAY_SIZE (handler_infos), handler_infos);
+
+out:
+  gdbus_put_model_instance (&g_gdbus_instance);
+
+  return ret;
+}
+
+/**
+ * @brief The callback function for initializing Model Interface module.
+ */
+static void
+init_model_module (void *data) { }
+
+/**
+ * @brief The callback function for exiting Model Interface module.
+ */
+static void
+exit_model_module (void *data)
+{
+  gdbus_disconnect_signal (g_gdbus_instance, 
+    ARRAY_SIZE (handler_infos), handler_infos);
+  gdbus_put_model_instance (&g_gdbus_instance);
+}
+
+static const struct module_ops model_ops = {
+  .name = "model-interface",
+  .probe = probe_model_module,
+  .init = init_model_module,
+  .exit = exit_model_module,
+};
+
+MODULE_OPS_REGISTER (&model_ops)
diff --git a/daemon/service-db.cc b/daemon/service-db.cc
new file mode 100644 (file)
index 0000000..7a8635e
--- /dev/null
@@ -0,0 +1,190 @@
+/* SPDX-License-Identifier: Apache-2.0 */
+/**
+ * Copyright (c) 2022 Samsung Electronics Co., Ltd. All Rights Reserved.
+ *
+ * @file service-db.cc
+ * @date 29 Jul 2022
+ * @brief Database implementation of NNStreamer/Service C-API
+ * @see https://github.com/nnstreamer/api
+ * @author Sangjung Woo <sangjung.woo@samsung.com>
+ * @bug No known bugs except for NYI items
+ */
+
+#include <glib.h>
+
+#include "service-db.hh"
+
+#define ML_DATABASE_PATH      DB_PATH"/.ml-service-leveldb"
+
+/**
+ * @brief Get an instance of IMLServiceDB, which is created only once at runtime.
+ * @return IMLServiceDB& IMLServiceDB instance
+ */
+IMLServiceDB & MLServiceLevelDB::getInstance (void)
+{
+  static MLServiceLevelDB instance (ML_DATABASE_PATH);
+
+  return instance;
+}
+
+/**
+ * @brief Construct a new MLServiceLevelDB object
+ * @param path database path
+ */
+MLServiceLevelDB::MLServiceLevelDB (std::string path)
+:  path (path), db_obj (nullptr), db_roptions (nullptr), db_woptions (nullptr)
+{
+  db_roptions = leveldb_readoptions_create ();
+  db_woptions = leveldb_writeoptions_create ();
+  leveldb_writeoptions_set_sync (db_woptions, 1);
+}
+
+/**
+ * @brief Destroy the MLServiceLevelDB object
+ */
+MLServiceLevelDB::~MLServiceLevelDB ()
+{
+  leveldb_readoptions_destroy (db_roptions);
+  leveldb_writeoptions_destroy (db_woptions);
+}
+
+/**
+ * @brief Connect the level DB and initialize the private variables.
+ */
+void
+MLServiceLevelDB::connectDB ()
+{
+  char *err = nullptr;
+  leveldb_options_t *db_options;
+
+  if (db_obj)
+    return;
+
+  db_options = leveldb_options_create ();
+  leveldb_options_set_create_if_missing (db_options, 1);
+
+  db_obj = leveldb_open (db_options, path.c_str (), &err);
+  leveldb_options_destroy (db_options);
+  if (err != nullptr) {
+    g_warning ("Error! Failed to open database located at '%s': leveldb_open() has returned an error: %s",
+        path.c_str (), err);
+    leveldb_free (err);
+    throw std::runtime_error ("Failed to connectDB()!");
+  }
+  return;
+}
+
+/**
+ * @brief Disconnect the level DB
+ * @note LevelDB does not support multi-process and it might cause
+ * the IO exception when multiple clients write the key simultaneously.
+ */
+void
+MLServiceLevelDB::disconnectDB ()
+{
+  if (db_obj) {
+    leveldb_close (db_obj);
+    db_obj = nullptr;
+  }
+}
+
+/**
+ * @brief Set the value with the given name.
+ * @note If the name already exists, the pipeline description is overwritten.
+ * @param[in] name Unique name to retrieve the associated pipeline description.
+ * @param[in] value The pipeline description to be stored.
+ */
+void
+MLServiceLevelDB::put (const std::string name,
+    const std::string value)
+{
+  char *err = NULL;
+
+  if (name.empty() || value.empty())
+    throw std::invalid_argument ("Invalid name or value parameters!");
+
+  leveldb_put (db_obj, db_woptions, name.c_str (), name.size (),
+      value.c_str (), value.size (), &err);
+  if (err != nullptr) {
+    g_warning
+        ("Failed to call leveldb_put () for the name, '%s' of the pipeline description (size: %zu bytes / description: '%.40s')",
+        name.c_str (), value.size (),
+        value.c_str ());
+    g_warning ("leveldb_put () has returned an error: %s", err);
+    leveldb_free (err);
+    throw std::runtime_error ("Failed to put()!");
+  }
+}
+
+/**
+ * @brief Get the value with the given name.
+ * @param[in] name The unique name to retrieve.
+ * @param[out] value The pipeline corresponding with the given name.
+ */
+void
+MLServiceLevelDB::get (const std::string name,
+    std::string & out_value)
+{
+  char *err = NULL;
+  char *value = NULL;
+  gsize read_len;
+
+  if (name.empty())
+    throw std::invalid_argument ("Invalid name parameters!");
+
+  value = leveldb_get (db_obj, db_roptions, name.c_str (), name.size (),
+      &read_len, &err);
+  if (err != nullptr) {
+    g_warning
+        ("Failed to call leveldb_get() for the name %s. Error message is %s.",
+        name.c_str (), err);
+    leveldb_free (err);
+    leveldb_free (value);
+    throw std::runtime_error ("Failed to get()!");
+  }
+
+  if (!value) {
+    g_warning
+        ("Failed to find the key %s. The key should be set before reading it",
+        name.c_str ());
+    throw std::invalid_argument ("Fail to find the key");
+  }
+
+  out_value = std::string (value, read_len);
+  leveldb_free (value);
+  return;
+}
+
+/**
+ * @brief Delete the value with a given name.
+ * @param[in] name The unique name to delete
+ */
+void
+MLServiceLevelDB::del (const std::string name)
+{
+  char *err = NULL;
+  char *value = NULL;
+  gsize read_len;
+
+  if (name.empty())
+    throw std::invalid_argument ("Invalid name parameters!");
+
+  /* Check whether the key exists or not. */
+  value = leveldb_get (db_obj, db_roptions, name.c_str (), name.size (),
+      &read_len, &err);
+  if (!value) {
+    g_warning
+        ("Failed to find the key %s. The key should be set before reading it",
+        name.c_str ());
+    throw std::invalid_argument ("Fail to find the key");
+  }
+  leveldb_free (value);
+
+  leveldb_delete (db_obj, db_woptions, name.c_str (), name.size (), &err);
+  if (err != nullptr) {
+    g_warning ("Failed to delete the key %s. Error message is %s", name.c_str (),
+        err);
+    leveldb_free (err);
+    throw std::runtime_error ("Failed to del()!");
+  }
+}
diff --git a/dbus/model-dbus.xml b/dbus/model-dbus.xml
new file mode 100644 (file)
index 0000000..a5388f0
--- /dev/null
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<node name="/Org/Tizen/MachineLearning/Service">
+  <interface name="org.tizen.machinelearning.service.model">
+    <!-- Set the file path of the designated neural network model -->
+    <method name="SetPath">
+      <arg type="s" name="name" direction="in" />
+      <arg type="s" name="path" direction="in" />
+      <arg type="i" name="result" direction="out" />
+    </method>
+    <!-- Get the file path of the designated neural network model -->
+    <method name="GetPath">
+      <arg type="s" name="name" direction="in" />
+      <arg type="s" name="path" direction="out" />
+      <arg type="i" name="result" direction="out" />
+    </method>
+    <!-- Delete the file path of the designated neural network model -->
+    <method name="Delete">
+      <arg type="s" name="name" direction="in" />
+      <arg type="i" name="result" direction="out" />
+    </method>
+  </interface>
+</node>