diff --git a/src/main.cpp b/src/main.cpp
index 7e03cdc8093c3b4034d24312b98a16a6f606b2d1..bcd10541d725c89b347ea5f62e22b44046cf91f4 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -4,6 +4,7 @@
 #include <mesh/DualMeshManager.hpp>
 #include <mesh/MeshDataManager.hpp>
 #include <mesh/SynchronizerManager.hpp>
+#include <utils/ExecutionStatManager.hpp>
 #include <utils/PugsUtils.hpp>
 #include <utils/RandomEngine.hpp>
 
@@ -18,14 +19,18 @@ main(int argc, char* argv[])
   MeshDataManager::create();
   DualConnectivityManager::create();
   DualMeshManager::create();
+  ExecutionStatManager::create();
 
   parser(filename);
 
+  ExecutionStatManager::printInfo();
+
+  ExecutionStatManager::destroy();
   DualMeshManager::destroy();
   DualConnectivityManager::destroy();
   MeshDataManager::destroy();
-  RandomEngine::destroy();
   QuadratureManager::destroy();
+  RandomEngine::destroy();
   SynchronizerManager::destroy();
 
   finalize();
diff --git a/src/utils/CMakeLists.txt b/src/utils/CMakeLists.txt
index c4070ff64a94711d600db51bf0a614795ed7aca3..5478323a71a879af92a2f43d95d9152c78a4a48d 100644
--- a/src/utils/CMakeLists.txt
+++ b/src/utils/CMakeLists.txt
@@ -8,6 +8,7 @@ add_library(
   ConsoleManager.cpp
   Demangle.cpp
   Exceptions.cpp
+  ExecutionStatManager.cpp
   FPEManager.cpp
   Messenger.cpp
   Partitioner.cpp
diff --git a/src/utils/ExecutionStatManager.cpp b/src/utils/ExecutionStatManager.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..0ea87c550d744cd0e33e11d0a3c71b47a686ffcb
--- /dev/null
+++ b/src/utils/ExecutionStatManager.cpp
@@ -0,0 +1,84 @@
+#include <utils/ExecutionStatManager.hpp>
+
+#include <utils/Exceptions.hpp>
+#include <utils/Messenger.hpp>
+
+#include <cmath>
+#include <rang.hpp>
+#include <sys/resource.h>
+
+ExecutionStatManager* ExecutionStatManager::m_instance = nullptr;
+
+void
+ExecutionStatManager::_printMaxResidentMemory() const
+{
+  const std::vector<std::string> units = {"B", "KB", "MB", "GB", "TB", "PB", "EB"};
+
+  double count = [] {
+    rusage u;
+    getrusage(RUSAGE_SELF, &u);
+    return u.ru_maxrss * 1024;
+  }();
+
+  size_t i_unit = 0;
+  while ((count >= 1024) and (i_unit < units.size())) {
+    ++i_unit;
+    count /= 1024;
+  }
+
+  std::cout << rang::style::bold << "Maximum memory: " << rang::fgB::cyan << count << rang::style::reset
+            << rang::style::bold << units[i_unit] << rang::style::reset << '\n';
+}
+
+void
+ExecutionStatManager::_printElapseTime() const
+{
+  std::cout << rang::style::bold << "Execution time: " << rang::fgB::cyan << m_instance->m_elapse_time.seconds()
+            << rang::style::reset << rang::style::bold << 's' << rang::style::reset << rang::fg::reset << '\n';
+}
+
+void
+ExecutionStatManager::_printTotalCPUTime() const
+{
+  rusage u;
+  getrusage(RUSAGE_SELF, &u);
+
+  double total_cpu_time = u.ru_utime.tv_sec + u.ru_stime.tv_sec + (u.ru_utime.tv_usec + u.ru_stime.tv_usec) * 1E-6;
+
+  std::cout << rang::style::bold << "Total CPU time: " << rang::fgB::cyan << total_cpu_time << rang::style::reset
+            << rang::style::bold << 's' << rang::style::reset << rang::fg::reset << '\n';
+  std::cout << " Using " << parallel::allReduceSum(Kokkos::DefaultHostExecutionSpace::concurrency())
+            << " threads distributed over " << parallel::size() << " processes"
+            << "\n";
+}
+
+void
+ExecutionStatManager::printInfo()
+{
+  std::cout << "----------------- " << rang::fgB::green << "pugs exec stats" << rang::fg::reset
+            << " ---------------------\n";
+
+  ExecutionStatManager::m_instance->_printElapseTime();
+  ExecutionStatManager::m_instance->_printTotalCPUTime();
+  ExecutionStatManager::m_instance->_printMaxResidentMemory();
+}
+
+void
+ExecutionStatManager::create()
+{
+  if (ExecutionStatManager::m_instance == nullptr) {
+    ExecutionStatManager::m_instance = new ExecutionStatManager;
+  } else {
+    throw UnexpectedError("ExecutionStatManager already created");
+  }
+}
+
+void
+ExecutionStatManager::destroy()
+{
+  // One allows multiple destruction to handle unexpected code exit
+  if (ExecutionStatManager::m_instance != nullptr) {
+    delete ExecutionStatManager::m_instance;
+    ExecutionStatManager::m_instance = nullptr;
+  }
+}
diff --git a/src/utils/ExecutionStatManager.hpp b/src/utils/ExecutionStatManager.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..4728366bc225edcb9c7d79989e6b86d24c609db7
--- /dev/null
+++ b/src/utils/ExecutionStatManager.hpp
@@ -0,0 +1,28 @@
+#ifndef EXECUTION_STAT_MANAGER_HPP
+#define EXECUTION_STAT_MANAGER_HPP
+
+#include <utils/Timer.hpp>
+
+class ExecutionStatManager
+{
+ private:
+  static ExecutionStatManager* m_instance;
+
+  Timer m_elapse_time;
+
+  void _printMaxResidentMemory() const;
+  void _printElapseTime() const;
+  void _printTotalCPUTime() const;
+
+  explicit ExecutionStatManager()                   = default;
+  ExecutionStatManager(ExecutionStatManager&&)      = delete;
+  ExecutionStatManager(const ExecutionStatManager&) = delete;
+  ~ExecutionStatManager()                           = default;
+
+ public:
+  static void printInfo();
+  static void create();
+  static void destroy();
+};
+
+#endif   // EXECUTION_STAT_MANAGER_HPP