Implement watchdog manager
authorJihoi Kim <jihoi.kim@samsung.com>
Mon, 28 Apr 2025 12:20:33 +0000 (21:20 +0900)
committerJihoi Kim <jihoi.kim@samsung.com>
Mon, 28 Apr 2025 12:40:38 +0000 (21:40 +0900)
Signed-off-by: Jihoi Kim <jihoi.kim@samsung.com>
19 files changed:
CMakeLists.txt
packaging/united-service.spec
src/CMakeLists.txt
src/service.cc
src/service.hh
src/service_loader.cc
src/service_loader.hh
src/watchdog/cpu_time.cc [new file with mode: 0644]
src/watchdog/cpu_time.hh [new file with mode: 0644]
src/watchdog/heartbeat.cc [new file with mode: 0644]
src/watchdog/heartbeat.hh [new file with mode: 0644]
src/watchdog/inspector.cc [new file with mode: 0644]
src/watchdog/inspector.hh [new file with mode: 0644]
src/watchdog/watchdog_conf.cc [new file with mode: 0644]
src/watchdog/watchdog_conf.hh [new file with mode: 0644]
src/watchdog/watchdog_context.cc [new file with mode: 0644]
src/watchdog/watchdog_context.hh [new file with mode: 0644]
src/watchdog/watchdog_manager.cc [new file with mode: 0644]
src/watchdog/watchdog_manager.hh [new file with mode: 0644]

index 2f4187653a1271545d4dfded63db3363d54320bd..80c510b8e8b6094e0cf5a31d99db57c42b99809f 100644 (file)
@@ -31,6 +31,7 @@ PKG_CHECK_MODULES(GLIB_DEPS REQUIRED glib-2.0)
 PKG_CHECK_MODULES(TIZEN_CORE_DEPS REQUIRED tizen-core)
 PKG_CHECK_MODULES(TIZEN_LIBOPENER_DEPS REQUIRED tizen-libopener)
 PKG_CHECK_MODULES(INIPARSER_DEPS REQUIRED iniparser)
+PKG_CHECK_MODULES(AUL_DEPS REQUIRED aul)
 
 ADD_SUBDIRECTORY(src)
 
index 4da13e284399ee4319a8368aeb2dda08092c0f9a..72d0f79dbddae6b490636a26eda78d8795f14018 100644 (file)
@@ -14,6 +14,7 @@ BuildRequires:  pkgconfig(tizen-core)
 BuildRequires:  pkgconfig(tizen-libopener)
 BuildRequires:  pkgconfig(glib-2.0)
 BuildRequires:  pkgconfig(iniparser)
+BuildRequires:  pkgconfig(aul)
 
 Requires(post): /sbin/ldconfig
 Requires(postun): /sbin/ldconfig
index fe289255d1f37755e68221bde81eb73e387427de..b27f00b06d6301e0e481e0f2bc2e303cbc31729e 100644 (file)
@@ -1,6 +1,7 @@
 AUX_SOURCE_DIRECTORY(${CMAKE_CURRENT_SOURCE_DIR} SRCS)
+AUX_SOURCE_DIRECTORY(${CMAKE_CURRENT_SOURCE_DIR}/watchdog WATCHDOG_SRCS)
 
-ADD_LIBRARY(${TARGET_UNITED_SERVICE} SHARED ${SRCS})
+ADD_LIBRARY(${TARGET_UNITED_SERVICE} SHARED ${SRCS} ${WATCHDOG_SRCS})
 
 SET_TARGET_PROPERTIES(${TARGET_UNITED_SERVICE} PROPERTIES SOVERSION ${MAJORVER})
 SET_TARGET_PROPERTIES(${TARGET_UNITED_SERVICE} PROPERTIES VERSION ${FULLVER})
@@ -18,6 +19,7 @@ APPLY_PKG_CONFIG(${TARGET_UNITED_SERVICE} PUBLIC
   TIZEN_CORE_DEPS
   TIZEN_LIBOPENER_DEPS
   INIPARSER_DEPS
+  AUL_DEPS
 )
 
 TARGET_LINK_LIBRARIES(${TARGET_UNITED_SERVICE} PUBLIC "-lpthread -ldl")
index 1dade7f114dbb32d7e59e2f5c446683f961093a8..a474bceb578670a150606ebc7a44d93f2d6942c2 100644 (file)
@@ -25,6 +25,7 @@
 #include "exception.hh"
 #include "log_private.hh"
 #include "service_manager.hh"
+#include "watchdog/watchdog_manager.hh"
 
 namespace tizen_base {
 namespace {
@@ -116,6 +117,8 @@ const std::shared_ptr<ServiceInfo>& Service::GetServiceInfo() const {
 
 const std::string& Service::GetName() const { return info_->GetName(); }
 
+pid_t Service::GetTid() const { return tid_; }
+
 Service::State Service::GetState() const { return state_; }
 
 tizen_core_h Service::GetCore() const { return core_; }
@@ -139,7 +142,11 @@ void Service::Run() {
         service->tid_ = gettid();
         service->OnBaseCreate();
         service->state_ = Service::State::Running;
+
         service->NotifyStateChanged();
+        WatchdogManager::GetInst().RegisterService(service->GetTid(),
+          service->GetName(), service->GetCore());
+
         return false;
       }, this, &source);
 
@@ -152,6 +159,7 @@ void Service::Run() {
 }
 
 void Service::QuitSelf() {
+  WatchdogManager::GetInst().UnregisterService(tid_);
   auto self = shared_from_this();
   tizen_core_task_quit(task_);
   OnBaseDestroy();
index b06288ecb567d80f4dae6066ecdb3ff5d4277b2a..39bc3ec51645704a20e94b42d56ce53a7c410f78 100644 (file)
@@ -62,6 +62,7 @@ class Service : public std::enable_shared_from_this<Service> {
   bool IsRunning() const;
   const std::shared_ptr<ServiceInfo>& GetServiceInfo() const;
   const std::string& GetName() const;
+  pid_t GetTid() const;
   State GetState() const;
   tizen_core_h GetCore() const;
   tizen_core_channel_sender_h GetChannelSender() const;
index a361766b0f34fcbec528fbd5a90759224f9807b3..e6543c945d6069a81471494ce0af6abc8c723c41 100644 (file)
@@ -32,6 +32,13 @@ namespace {
 constexpr const char kPathUnitedService[] = "/usr/share/united-service/";
 constexpr const char kConf[] = "/conf/";
 
+static const std::string kTagServiceLoader = "service-loader";
+static const std::string kWatchdogSec = kTagServiceLoader + ":watchdogsec";
+static const std::string kCpuTimeSec = kTagServiceLoader + ":cputimesec";
+static const std::string kMemoryMonitor = kTagServiceLoader + ":memorymonitor";
+static const std::string kCpuMonitor = kTagServiceLoader + ":cpumonitor";
+static const std::string kBacktrace = kTagServiceLoader + ":backtrace";
+
 }  // namespace
 
 namespace tizen_base {
@@ -43,6 +50,11 @@ ServiceLoader::ServiceLoader(int argc, char** argv, std::string name)
     Shutdown();
     THROW(SERVICE_ERROR_INVALID_CONTEXT);
   }
+
+  if (!WatchdogInit()) {
+    Shutdown();
+    throw new std::runtime_error("Failed to initialize watchdog manager");
+  }
 }
 
 ServiceLoader::~ServiceLoader() {
@@ -77,6 +89,34 @@ bool ServiceLoader::Init() {
   return true;
 }
 
+bool ServiceLoader::WatchdogInit() {
+  const fs::path loader_conf(kPathUnitedService + name_ + "/" +
+      name_ + ".loader");
+  if (!fs::is_regular_file(loader_conf)) [[unlikely]] {
+    LOGE("Cannot find config file (%s)", loader_conf.c_str());
+    return false;
+  }
+
+  auto dictionary = IniParser::Parse(loader_conf.string());
+  auto builder = WatchdogConf::Builder();
+
+  builder.SetLoaderName(name_);
+  builder.SetWatchdogSec(std::stoi(dictionary->Get(kWatchdogSec)));
+  builder.SetMemoryMonitorFlag(dictionary->Get(kMemoryMonitor) == "yes");
+  builder.SetCpuMonitorFlag(dictionary->Get(kCpuMonitor) == "yes");
+  builder.SetBacktraceFlag(dictionary->Get(kBacktrace) == "yes");
+
+  auto config = builder.Build();
+  if (!config) [[unlikely]]
+    return false;
+
+  if (!WatchdogManager::GetInst().Init(std::move(config))) {
+    return false;
+  }
+
+  return true;
+}
+
 void ServiceLoader::Shutdown() {
   if (source_) {
     tizen_core_source_destroy(source_);
index 12a74223d84d99dacd41b20978dceeb1d36a0ef9..3971fb4a7bba4b4acb8f4a8f720a38da62763567 100644 (file)
 #include "service.hh"
 #include "service_info.hh"
 
+#include "watchdog/watchdog_manager.hh"
+#include "watchdog/watchdog_conf.hh"
+#include "watchdog/heartbeat.hh"
+
 namespace tizen_base {
 
 class ServiceLoader {
@@ -58,6 +62,7 @@ class ServiceLoader {
   void ServiceStateChangedCb(const Service* service, Service::State state);
 
   bool Init();
+  bool WatchdogInit();
   void Shutdown();
   void LoadServices();
   static void ChannelReceiveCb(tizen_core_channel_object_h object,
diff --git a/src/watchdog/cpu_time.cc b/src/watchdog/cpu_time.cc
new file mode 100644 (file)
index 0000000..c32560b
--- /dev/null
@@ -0,0 +1,97 @@
+/*
+ * Copyright (c) 2025 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "cpu_time.hh"
+
+namespace tizen_base {
+
+pid_t CpuTime::GetTid() const {
+  return tid_;
+}
+
+bool CpuTime::Update() {
+  LOGD("CPUtime Update [TID %d]", tid_);
+  bool ret = UpdateTotalCpuTime();
+  if (ret)
+    return UpdateProcessCpuTime();
+  return false;
+}
+
+bool CpuTime::UpdateTotalCpuTime() {
+  FILE* fp = fopen("/proc/stat", "r");
+  if (fp == nullptr) {
+    _E("Failed to open /proc/stat");
+    return false;
+  }
+
+  unsigned long long user = 0;
+  unsigned long long nice = 0;
+  unsigned long long system = 0;
+  unsigned long long idle_time = 0;
+  unsigned long long iowait = 0;
+  unsigned long long irq = 0;
+  unsigned long long softirq = 0;
+  unsigned long long steal = 0;
+  int ret =
+      fscanf(fp, "cpu %llu %llu %llu %llu %llu %llu %llu %llu", &user, &nice,
+              &system, &idle_time, &iowait, &irq, &softirq, &steal);
+  fclose(fp);
+  if (ret != 8) {
+    _E("Failed to scan /proc/stat: %d", ret);
+    return false;
+  }
+
+  previous_total_time_ = current_total_time_;
+  current_total_time_ =
+      user + nice + system + idle_time + iowait + irq + softirq + steal;
+  return true;
+}
+
+bool CpuTime::UpdateProcessCpuTime() {
+  std::string path = "/proc/" + std::to_string(getpid()) + "/task/"
+      + std::to_string(tid_) + "/stat";
+
+  FILE* fp = fopen(path.c_str(), "r");
+  if (fp == nullptr) {
+    _E("Failed to open %s", path.c_str());
+    return false;
+  }
+
+  unsigned long long utime = 0;
+  unsigned long long stime = 0;
+  int ret = fscanf(
+      fp, "%*d %*s %*c %*d %*d %*d %*d %*d %*u %*u %*u %*u %*u %llu %llu",
+      &utime, &stime);
+  fclose(fp);
+  if (ret != 2) {
+    _E("Failed to scan %s: %d", path.c_str(), ret);
+    return false;
+  }
+
+  previous_process_time_ = current_process_time_;
+  current_process_time_ = utime + stime;
+  return true;
+}
+
+
+double CpuTime::CalculateCpuUsage() {
+  auto total_diff = current_total_time_ - previous_total_time_;
+  if (total_diff == 0) return 0.0f;
+  auto process_diff = current_process_time_ - previous_process_time_;
+  return (static_cast<double>(process_diff) / total_diff) * 100.0;
+}
+
+} // namespace tizen_base
diff --git a/src/watchdog/cpu_time.hh b/src/watchdog/cpu_time.hh
new file mode 100644 (file)
index 0000000..4ca5e40
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2025 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef CPU_TIME_HH_
+#define CPU_TIME_HH_
+
+#include <tizen_core.h>
+
+#include <memory>
+#include <string>
+#include <unordered_map>
+
+#include "log_private.hh"
+
+namespace tizen_base {
+
+class CpuTime {
+  public:
+    CpuTime(pid_t tid) : tid_(tid) {}
+    pid_t GetTid() const;
+    bool Update();
+    double CalculateCpuUsage();
+
+  private:
+    bool UpdateTotalCpuTime();
+    bool UpdateProcessCpuTime();
+    pid_t tid_;
+
+    unsigned long long current_total_time_ = 0;
+    unsigned long long current_process_time_ = 0;
+    unsigned long long previous_total_time_ = 0;
+    unsigned long long previous_process_time_ = 0;
+};
+
+} // namespace tizen_base
+
+#endif  // CPU_TIME_HH_
diff --git a/src/watchdog/heartbeat.cc b/src/watchdog/heartbeat.cc
new file mode 100644 (file)
index 0000000..c49e652
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2025 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "heartbeat.hh"
+
+namespace tizen_base {
+
+pid_t Heartbeat::GetTid() const {
+  return tid_;
+}
+
+void Heartbeat::SendPing() {
+  tizen_core_source_h source = nullptr;
+  tizen_core_add_idle_job(
+    core_, [](void* user_data) -> bool {
+      auto* heartbeat = static_cast<Heartbeat*>(user_data);
+      heartbeat->SetPingResult();
+      return false;
+    }, this, &source);
+  return;
+}
+
+void Heartbeat::SetPingResult() {
+  LOGD("Heartbeat Update [TID %d]", gettid());
+  ping_ = true;
+}
+
+bool Heartbeat::CheckPingResult() {
+  bool result = ping_;
+  ping_ = false;
+  return result;
+}
+
+} // namespace tizen_base
diff --git a/src/watchdog/heartbeat.hh b/src/watchdog/heartbeat.hh
new file mode 100644 (file)
index 0000000..4f72be4
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2025 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef HEARTBEAT_HH_
+#define HEARTBEAT_HH_
+
+#include <tizen_core.h>
+
+#include <memory>
+#include <string>
+#include <unordered_map>
+
+#include "log_private.hh"
+
+namespace tizen_base {
+
+class Heartbeat {
+ public:
+  Heartbeat(pid_t tid, tizen_core_h core) :
+    tid_(tid), core_(core), ping_(true) {};
+  pid_t GetTid() const;
+  void SendPing();
+  void SetPingResult();
+  bool CheckPingResult();
+
+ private:
+  pid_t tid_;
+  tizen_core_h core_;
+  bool ping_;
+};
+
+} // namespace tizen_base
+
+#endif  // HEARTBEAT_HH_
diff --git a/src/watchdog/inspector.cc b/src/watchdog/inspector.cc
new file mode 100644 (file)
index 0000000..5508054
--- /dev/null
@@ -0,0 +1,106 @@
+/*
+ * Copyright (c) 2025 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "inspector.hh"
+#include "cpu_time.hh"
+
+#include <filesystem>
+#include <string>
+#include <iostream>
+#include <fstream>
+
+#include <aul_backtrace.h>
+
+#include <sys/ptrace.h>
+#include <sys/wait.h>
+
+// build amd and aul to remove below
+namespace {
+  int aul_send_backtrace_request_mock(int tid) {
+    _I("MOCK: Send BT request to amd");
+    return AUL_R_OK;
+  }
+}
+
+namespace tizen_base::Inspector {
+  void PrintBacktrace(pid_t tid, std::string name) {
+    int ret = aul_send_backtrace_request_mock(tid);
+    if (ret != AUL_R_OK) {
+      _E("Fail to print bt for %d (%d)", tid, ret);
+    }
+  }
+
+  void MemoryMonitor(std::string loader_name) {
+    if (loader_name.empty()) {
+      _E("Invalid parameter");
+      return;
+    }
+
+    std::filesystem::path smaps_path = "/proc/" + std::to_string(getpid()) +
+        "/smaps";
+    if (!std::filesystem::exists(smaps_path)) {
+      _E("%s does not exist", smaps_path.c_str());
+      return;
+    }
+
+    std::ifstream file(smaps_path);
+    if (!file) {
+      _E("Failed to open %s", smaps_path.c_str());
+      return;
+    }
+
+    LOGW("[Loader %d(%s)] Memory monitor", getpid(), loader_name.c_str());
+
+    uint64_t total_pss = 0;
+    std::string line;
+    std::string libinfo;
+    while (std::getline(file, line)) {
+      std::stringstream stream(line);
+      std::string tag;
+
+      if (stream >> tag) {
+        if (!tag.ends_with(":")) {
+          std::string area = std::move(tag);
+          std::string perm;
+          std::string dummy;
+          std::string filename;
+          stream >> perm;
+          stream >> dummy >> dummy >> dummy;
+          stream >> filename;
+
+          libinfo = area + " " + perm + " " +  filename;
+
+        } else if (tag == "Pss:") {
+          uint64_t pss = 0;
+          stream >> pss;
+          total_pss += pss;
+          if (pss > 0) {
+            LOGW("(PSS %-4ld kb) %s\n", pss, libinfo.c_str());
+          }
+        }
+      }
+    }
+
+    LOGW("[%d] Total PSS %-4ld kB", getpid(), total_pss);
+    return;
+  }
+
+  void CpuMonitor(std::shared_ptr<CpuTime> cpu_time) {
+    double cpu_usage = cpu_time->CalculateCpuUsage();
+    LOGW("[%d:%d] CPU usage = %.4f%%", getpid(), cpu_time->GetTid(), cpu_usage);
+    return;
+  }
+} // namespace tizen_base
diff --git a/src/watchdog/inspector.hh b/src/watchdog/inspector.hh
new file mode 100644 (file)
index 0000000..607dfab
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2025 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef INSPECTOR_HH_
+#define INSPECTOR_HH_
+
+#include <tizen_core.h>
+#include "cpu_time.hh"
+
+#include <memory>
+#include <string>
+#include <unordered_map>
+
+#include "log_private.hh"
+
+namespace tizen_base::Inspector {
+
+  void PrintBacktrace(pid_t tid, std::string name);
+  void MemoryMonitor(std::string loader_name);
+  void CpuMonitor(std::shared_ptr<CpuTime> cpu_time);
+
+} // namespace tizen_base::Inspector
+
+#endif  // INSPECTOR_HH_
diff --git a/src/watchdog/watchdog_conf.cc b/src/watchdog/watchdog_conf.cc
new file mode 100644 (file)
index 0000000..9a7f386
--- /dev/null
@@ -0,0 +1,85 @@
+/*
+ * Copyright (c) 2025 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "watchdog_conf.hh"
+#include "log_private.hh"
+
+namespace tizen_base {
+
+WatchdogConf::Builder& WatchdogConf::Builder::SetLoaderName(std::string name) {
+  loader_name_ = name;
+  return *this;
+}
+
+WatchdogConf::Builder& WatchdogConf::Builder::SetWatchdogSec(uint32_t sec) {
+  watchdog_sec_ = sec;
+  return *this;
+}
+
+WatchdogConf::Builder& WatchdogConf::Builder::SetMemoryMonitorFlag(bool flag) {
+  memory_monitor_ = flag;
+  return *this;
+}
+
+WatchdogConf::Builder& WatchdogConf::Builder::SetCpuMonitorFlag(bool flag) {
+  cpu_monitor_ = flag;
+  return *this;
+}
+
+WatchdogConf::Builder& WatchdogConf::Builder::SetBacktraceFlag(bool flag) {
+  backtrace_ = flag;
+  return *this;
+}
+
+std::shared_ptr<WatchdogConf> WatchdogConf::Builder::Build() {
+  auto conf = std::shared_ptr<WatchdogConf>(
+      new (std::nothrow) WatchdogConf(std::move(loader_name_), watchdog_sec_,
+          memory_monitor_, cpu_monitor_, backtrace_));
+
+  if (conf == nullptr) [[unlikely]] {
+    LOGE("Out of memory");
+    return nullptr;
+  }
+  return conf;
+}
+
+WatchdogConf::WatchdogConf(std::string loader_name, uint32_t watchdog_sec,
+    bool memory_monitor, bool cpu_monitor, bool backtrace)
+    : loader_name_(loader_name), watchdog_sec_(watchdog_sec),
+      memory_monitor_(memory_monitor), cpu_monitor_(cpu_monitor),
+      backtrace_(backtrace) {}
+
+std::string WatchdogConf::GetLoaderName() const {
+  return loader_name_;
+}
+
+uint32_t WatchdogConf::GetWatchdogSec() const {
+  return watchdog_sec_;
+}
+
+bool WatchdogConf::IsMemoryMonitor() const {
+  return memory_monitor_;
+}
+
+bool WatchdogConf::IsCpuMonitor() const {
+  return cpu_monitor_;
+}
+
+bool WatchdogConf::IsBacktrace() const {
+  return cpu_monitor_;
+}
+
+} // namespace tizen_base
diff --git a/src/watchdog/watchdog_conf.hh b/src/watchdog/watchdog_conf.hh
new file mode 100644 (file)
index 0000000..b179da6
--- /dev/null
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2025 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef WATCHDOG_CONF_HH_
+#define WATCHDOG_CONF_HH_
+
+#include <memory>
+#include <string>
+#include <unordered_map>
+
+#include "watchdog_conf.hh"
+
+namespace tizen_base {
+
+class WatchdogConf {
+  public :
+    class Builder {
+      public:
+        Builder(){};
+        Builder& SetLoaderName(std::string name);
+        Builder& SetWatchdogSec(uint32_t sec);
+        Builder& SetMemoryMonitorFlag(bool flag);
+        Builder& SetCpuMonitorFlag(bool flag);
+        Builder& SetBacktraceFlag(bool flag);
+        std::shared_ptr<WatchdogConf> Build();
+
+      private:
+        std::string loader_name_;
+        uint32_t watchdog_sec_;
+        bool memory_monitor_;
+        bool cpu_monitor_;
+        bool backtrace_;
+    };
+
+    std::string GetLoaderName() const;
+    uint32_t GetWatchdogSec() const;
+    bool IsCpuMonitor() const;
+    bool IsMemoryMonitor() const;
+    bool IsBacktrace() const;
+
+  private :
+    WatchdogConf(std::string loader_name, uint32_t watchdog_sec,
+        bool memory_monitor, bool cpu_monitor, bool backtrace);
+
+    std::string loader_name_;
+    uint32_t watchdog_sec_;
+    bool memory_monitor_;
+    bool cpu_monitor_;
+    bool backtrace_;
+};
+
+} // namespace tizen_base
+
+#endif  // WATCHDOG_CONF_HH_
diff --git a/src/watchdog/watchdog_context.cc b/src/watchdog/watchdog_context.cc
new file mode 100644 (file)
index 0000000..21b0804
--- /dev/null
@@ -0,0 +1,151 @@
+/*
+ * Copyright (c) 2025 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "watchdog_context.hh"
+#include <mutex>
+
+namespace tizen_base {
+
+WatchdogContext::Builder& WatchdogContext::Builder::SetTid(pid_t tid) {
+  tid_ = tid;
+  return *this;
+}
+
+WatchdogContext::Builder& WatchdogContext::Builder::SetLoaderName(std::string name) {
+  loader_name_ = std::move(name);
+  return *this;
+}
+
+WatchdogContext::Builder& WatchdogContext::Builder::SetServiceName(std::string name) {
+  service_name_ = std::move(name);
+  return *this;
+}
+
+WatchdogContext::Builder& WatchdogContext::Builder::SetMemoryMonitorFlag(bool flag) {
+  memory_monitor_ = flag;
+  return *this;
+}
+
+WatchdogContext::Builder& WatchdogContext::Builder::SetCpuMonitorFlag(bool flag) {
+  cpu_monitor_ = flag;
+  return *this;
+}
+
+WatchdogContext::Builder& WatchdogContext::Builder::SetBacktraceFlag(bool flag) {
+  backtrace_ = flag;
+  return *this;
+}
+
+WatchdogContext::Builder& WatchdogContext::Builder::SetHeartbeat(
+    std::shared_ptr<Heartbeat> heartbeat) {
+  heartbeat_ = std::move(heartbeat);
+  return *this;
+}
+
+WatchdogContext::Builder& WatchdogContext::Builder::SetCpuTime(
+    std::shared_ptr<CpuTime> cpu_time) {
+  cpu_time_ = std::move(cpu_time);
+  return *this;
+}
+
+std::shared_ptr<WatchdogContext> WatchdogContext::Builder::Build() {
+  auto context = std::shared_ptr<WatchdogContext>(
+      new (std::nothrow) WatchdogContext(tid_, std::move(loader_name_),
+          std::move(service_name_), memory_monitor_, cpu_monitor_,
+          backtrace_, heartbeat_, cpu_time_));
+
+  if (context == nullptr) [[unlikely]] {
+    LOGE("Out of memory");
+    return nullptr;
+  }
+  return context;
+}
+
+pid_t WatchdogContext::GetTid() const {
+  return tid_;
+}
+
+std::string WatchdogContext::GetLoaderName() const {
+  return loader_name_;
+}
+
+std::string WatchdogContext::GetServiceName() const {
+  return service_name_;
+}
+
+std::shared_ptr<Heartbeat> WatchdogContext::GetHeartbeat() {
+  return heartbeat_;
+}
+
+std::shared_ptr<CpuTime> WatchdogContext::GetCpuTime() {
+  return cpu_time_;
+}
+
+bool WatchdogContext::CpuTimeInterval() {
+  return cpu_time_->Update();
+}
+
+bool WatchdogContext::HeartbeatInterval() {
+  bool ret = heartbeat_->CheckPingResult();
+  heartbeat_->SendPing();
+  return ret;
+}
+
+void WatchdogContext::OnWatchdogTimeout() {
+  auto self_ = shared_from_this();
+  LOGE("Loader(%s) catch tid %d(%s) timeout", GetLoaderName().c_str(),
+      GetTid(), GetServiceName().c_str());
+
+  if (IsBacktrace()) {
+    Inspector::PrintBacktrace(GetTid(), GetServiceName());
+  }
+
+  std::thread inspect(WatchdogContext::Inspect, std::move(self_));
+  inspect.detach();
+  return;
+}
+
+bool WatchdogContext::IsMemoryMonitor() {
+  return memory_monitor_;
+}
+
+bool WatchdogContext::IsCpuMonitor() {
+  return cpu_monitor_;
+}
+
+bool WatchdogContext::IsBacktrace() {
+  return backtrace_;
+}
+
+void WatchdogContext::Inspect(std::shared_ptr<WatchdogContext> context) {
+  static std::mutex inspect_print_mutex_;
+  std::unique_lock<std::mutex> lock(inspect_print_mutex_);
+
+  LOGD("Inspect Start, tid %d(%s)",
+      context->GetTid(), context->GetServiceName().c_str());
+
+  if (context->IsMemoryMonitor()) {
+    Inspector::MemoryMonitor(context->GetLoaderName());
+  }
+  if (context->IsCpuMonitor()) {
+    Inspector::CpuMonitor(context->GetCpuTime());
+  }
+
+  // ad-hoc, print bt as last step of inspect.
+
+}
+
+} // namespace tizen_base
diff --git a/src/watchdog/watchdog_context.hh b/src/watchdog/watchdog_context.hh
new file mode 100644 (file)
index 0000000..439f5b1
--- /dev/null
@@ -0,0 +1,102 @@
+/*
+ * Copyright (c) 2025 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef WATCHDOG_CONTEXT_HH_
+#define WATCHDOG_CONTEXT_HH_
+
+#include "heartbeat.hh"
+#include "cpu_time.hh"
+#include "inspector.hh"
+
+#include <tizen_core.h>
+
+#include <memory>
+#include <string>
+#include <thread>
+#include <unordered_map>
+
+#include "log_private.hh"
+
+namespace tizen_base {
+
+class WatchdogContext : public std::enable_shared_from_this<WatchdogContext> {
+ public:
+  class Builder {
+    public:
+      Builder(){};
+      Builder& SetTid(pid_t tid);
+      Builder& SetLoaderName(std::string name);
+      Builder& SetServiceName(std::string name);
+      Builder& SetMemoryMonitorFlag(bool flag);
+      Builder& SetCpuMonitorFlag(bool flag);
+      Builder& SetBacktraceFlag(bool flag);
+      Builder& SetHeartbeat(std::shared_ptr<Heartbeat> heartbeat);
+      Builder& SetCpuTime(std::shared_ptr<CpuTime> cpu_time);
+      std::shared_ptr<WatchdogContext> Build();
+
+    private:
+      pid_t tid_;
+      std::string loader_name_;
+      std::string service_name_;
+      bool memory_monitor_ = false;
+      bool cpu_monitor_ = false;
+      bool backtrace_ = false;
+      std::shared_ptr<Heartbeat> heartbeat_ = nullptr;
+      std::shared_ptr<CpuTime> cpu_time_ = nullptr;
+  };
+
+  pid_t GetTid() const;
+  std::string GetLoaderName() const;
+  std::string GetServiceName() const;
+
+  bool HeartbeatInterval();
+  bool CpuTimeInterval();
+  void OnWatchdogTimeout();
+
+  bool IsMemoryMonitor();
+  bool IsCpuMonitor();
+  bool IsBacktrace();
+
+ private:
+  WatchdogContext(pid_t tid, std::string loader_name, std::string service_name,
+      bool memory_monitor, bool cpu_monitor, bool backtrace,
+      std::shared_ptr<Heartbeat> heartbeat, std::shared_ptr<CpuTime> cpu_time)
+      : tid_(tid),
+        loader_name_(std::move(loader_name)),
+        service_name_(std::move(service_name)),
+        memory_monitor_(memory_monitor),
+        cpu_monitor_(cpu_monitor),
+        backtrace_(backtrace),
+        heartbeat_(std::move(heartbeat)),
+        cpu_time_(std::move(cpu_time)) {}
+
+  static void Inspect(std::shared_ptr<WatchdogContext> context);
+  std::shared_ptr<Heartbeat> GetHeartbeat();
+  std::shared_ptr<CpuTime> GetCpuTime();
+
+  pid_t tid_;
+  std::string loader_name_;
+  std::string service_name_;
+  bool memory_monitor_;
+  bool cpu_monitor_;
+  bool backtrace_;
+  std::shared_ptr<Heartbeat> heartbeat_;
+  std::shared_ptr<CpuTime> cpu_time_;
+};
+
+} // namespace tizen_base
+
+#endif  // WATCHDOG_CONTEXT_HH_
diff --git a/src/watchdog/watchdog_manager.cc b/src/watchdog/watchdog_manager.cc
new file mode 100644 (file)
index 0000000..d112732
--- /dev/null
@@ -0,0 +1,167 @@
+/*
+ * Copyright (c) 2025 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "watchdog_manager.hh"
+#include "log_private.hh"
+
+namespace tizen_base {
+
+WatchdogManager& WatchdogManager::GetInst() {
+  static WatchdogManager inst;
+  return inst;
+}
+
+WatchdogManager::~WatchdogManager() {
+  int ret = tizen_core_task_destroy(task_);
+  if (ret != TIZEN_CORE_ERROR_NONE) {
+    LOGE("Cannot destroy watchdog task");
+  }
+}
+
+bool WatchdogManager::Init(std::shared_ptr<WatchdogConf> conf) {
+  if (init_) {
+    LOGW("Reinitialize manager instance");
+  }
+
+  conf_ = std::move(conf);
+  int ret = tizen_core_task_create("watchdog", true, &task_);
+  if (ret != TIZEN_CORE_ERROR_NONE) {
+    LOGE("Cannot create watchdog task(%d)", ret);
+    return false;
+  }
+
+  ret = tizen_core_task_get_tizen_core(task_, &core_);
+  if (ret != TIZEN_CORE_ERROR_NONE) {
+    LOGE("Cannot find core from watchdog task(%d)", ret);
+    return false;
+  }
+
+  if (!StartWatchdog()) {
+    return false;
+  }
+
+  LOGD("Manager instance initialied done");
+  init_ = true;
+  return true;
+}
+
+void WatchdogManager::RegisterService(pid_t tid, std::string name,
+    tizen_core_h core) {
+  LOGI("Loader(%s) register tid %d(%s)", conf_->GetLoaderName().c_str(), tid, name.c_str());
+
+  auto builder = WatchdogContext::Builder();
+  builder.SetLoaderName(conf_->GetLoaderName());
+  builder.SetServiceName(std::move(name));
+  builder.SetTid(std::move(tid));
+  builder.SetMemoryMonitorFlag(conf_->IsMemoryMonitor());
+  builder.SetBacktraceFlag(conf_->IsBacktrace());
+
+  if (conf_->IsCpuMonitor()) {
+    builder.SetCpuMonitorFlag(true);
+    auto cpu_time = std::make_shared<CpuTime>(tid);
+    builder.SetCpuTime(cpu_time);
+  }
+
+  if (conf_->GetWatchdogSec() > 0) {
+    auto heartbeat = std::make_shared<Heartbeat>(tid, core);
+    builder.SetHeartbeat(heartbeat);
+  }
+
+  std::unique_lock<std::mutex> lock(context_list_mutex_);
+  contexts_.emplace_back(std::move(builder.Build()));
+}
+
+void WatchdogManager::UnregisterService(pid_t tid) {
+  std::unique_lock<std::mutex> lock(context_list_mutex_);
+  int ret = contexts_.remove_if([tid](auto context) {
+      return context->GetTid() == tid;
+    });
+
+  if (ret == 1) [[likely]]
+    LOGI("Loader(%s) unregister tid %d", conf_->GetLoaderName().c_str(), tid);
+  else
+    LOGE("Loader(%s) unregister tid %d return %d", conf_->GetLoaderName().c_str(), tid, ret);
+}
+
+void WatchdogManager::Interval() {
+  std::unique_lock<std::mutex> lock(context_list_mutex_);
+  int count = 1;
+  for (auto& context : contexts_) {
+    LOGI("[%d/%ld] %s Interval", count++, contexts_.size(), context->GetServiceName().c_str());
+    if (!context->CpuTimeInterval()) {
+      LOGW("Failed to update CpuTime of tid %d", context->GetTid());
+    }
+    if (!context->HeartbeatInterval()) {
+      LOGE("Failed to verify Heartbeat of tid %d", context->GetTid());
+      context->OnWatchdogTimeout();
+    }
+  }
+}
+
+bool WatchdogManager::StartWatchdog() {
+  int ret = 0;
+
+  if (conf_->GetWatchdogSec() > 0) {
+    tizen_core_source_h oneshot_source;
+
+    // Logging Purpose
+    ret = tizen_core_add_idle_job(core_, [](void* user_data) -> bool {
+      LOGI("Watchdog Manager Job Thread %d", gettid());
+      return false;
+    }, nullptr, &oneshot_source);
+    if (ret != TIZEN_CORE_ERROR_NONE) {
+      LOGE("Cannot add idle job to watchdog %d", ret);
+      return false;
+    }
+
+    // Interval
+    ret = tizen_core_add_timer(core_, conf_->GetWatchdogSec() * 1000,
+      [](void* user_data) -> bool {
+        WatchdogManager::GetInst().Interval();
+        return true;
+      }, nullptr, &source_);
+    if (ret != TIZEN_CORE_ERROR_NONE) {
+      LOGE("Cannot add watchdog interval %d", ret);
+      return false;
+    }
+
+    // Run
+    ret = tizen_core_task_run(task_);
+    if (ret != TIZEN_CORE_ERROR_NONE) {
+      LOGE("Cannot run watchdog task %d", ret);
+      return false;
+    }
+  } else {
+    LOGW("Watchdog is disabled (WatchdogSec 0)");
+  }
+
+  return true;
+}
+
+bool WatchdogManager::StopWatchdog() {
+  int ret = 0;
+  int result = true;
+
+  ret = tizen_core_remove_source(core_, source_);
+  if (ret != TIZEN_CORE_ERROR_NONE) {
+    LOGE("Cannot Stop Watchdog Monitor %d", ret);
+    result = false;
+  }
+
+  return result;
+}
+
+} // namespace tizen_base
diff --git a/src/watchdog/watchdog_manager.hh b/src/watchdog/watchdog_manager.hh
new file mode 100644 (file)
index 0000000..8b97400
--- /dev/null
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2025 Samsung Electronics Co., Ltd All Rights Reserved
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef WATCHDOG_MANAGER_HH_
+#define WATCHDOG_MANAGER_HH_
+
+#include "watchdog_conf.hh"
+#include "watchdog_context.hh"
+
+#include <tizen_core.h>
+
+#include <memory>
+#include <algorithm>
+#include <string>
+#include <thread>
+#include <mutex>
+#include <list>
+
+namespace tizen_base {
+
+class WatchdogManager {
+ public:
+  static WatchdogManager& GetInst();
+  bool Init(std::shared_ptr<WatchdogConf> conf);
+  void RegisterService(pid_t tid, std::string name, tizen_core_h core);
+  void UnregisterService(pid_t tid);
+
+ private:
+  bool StartWatchdog();
+  bool StopWatchdog();
+  void Interval();
+
+  WatchdogManager() : init_(false), conf_(nullptr) {};
+  ~WatchdogManager();
+
+ private:
+  bool init_;
+  tizen_core_h core_;
+  tizen_core_task_h task_;
+  tizen_core_source_h source_;
+
+  std::shared_ptr<WatchdogConf> conf_;
+  std::mutex context_list_mutex_;
+  std::list<std::shared_ptr<WatchdogContext>> contexts_;
+};
+
+} // namespace tizen_base
+
+#endif  // WATCHDOG_MANAGER_HH_