TimeChecker filter implement for debug time duration 67/320667/5
authorEunki, Hong <eunkiki.hong@samsung.com>
Wed, 20 Nov 2024 12:57:56 +0000 (21:57 +0900)
committerEunki, Hong <eunkiki.hong@samsung.com>
Mon, 25 Nov 2024 01:58:53 +0000 (10:58 +0900)
Let we define new filter to check time duration and threshold to print.
It will be useful if we want to check duration of some API

Change-Id: Id1cb5878cb5daac6cf972f5f48f99d9b1695cf26
Signed-off-by: Eunki, Hong <eunkiki.hong@samsung.com>
automated-tests/src/dali-internal/CMakeLists.txt
automated-tests/src/dali-internal/utc-Dali-Internal-Debug.cpp [new file with mode: 0644]
dali/integration-api/debug.cpp
dali/integration-api/debug.h

index 9578d2f0a64f8c7a516f4eb79ec285cc1afa040b..5ae002753c9d9e11edb88aed97f7ebe6924b935c 100644 (file)
@@ -11,6 +11,7 @@ SET(TC_SOURCES
   utc-Dali-Internal-ActorRelayout.cpp
   utc-Dali-Internal-ConstString.cpp
   utc-Dali-Internal-Core.cpp
+  utc-Dali-Internal-Debug.cpp
   utc-Dali-Internal-DummyMemoryPool.cpp
   utc-Dali-Internal-FixedSizeMemoryPool.cpp
   utc-Dali-Internal-FrustumCulling.cpp
diff --git a/automated-tests/src/dali-internal/utc-Dali-Internal-Debug.cpp b/automated-tests/src/dali-internal/utc-Dali-Internal-Debug.cpp
new file mode 100644 (file)
index 0000000..e7f9380
--- /dev/null
@@ -0,0 +1,211 @@
+/*
+ * Copyright (c) 2024 Samsung Electronics Co., Ltd.
+ *
+ * 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.
+ *
+ */
+
+// Enable debug log for test coverage
+#define DEBUG_ENABLED 1
+
+#include <dali-test-suite-utils.h>
+#include <dali/public-api/dali-core.h>
+#include <stdlib.h>
+
+#include <iostream>
+
+// Internal headers are allowed here
+#include <dali/integration-api/debug.h>
+
+using namespace Dali;
+
+namespace
+{
+static Debug::DebugPriority gCapturedLogPriority;
+static std::string          gCapturedLog;
+
+void LogCaptureFunction(Debug::DebugPriority priority, std::string& string)
+{
+  gCapturedLogPriority = priority;
+  gCapturedLog         = string;
+}
+
+bool CheckCapturedLogContainString(Debug::DebugPriority priority, std::string string)
+{
+  bool ret = false;
+  if(gCapturedLogPriority == priority)
+  {
+    ret = (gCapturedLog.find(string) != std::string::npos);
+  }
+  return ret;
+}
+
+// Define static dummy filter, for line coverage.
+DALI_INIT_TIME_CHECKER_FILTER(gDummyTimeCheckerFilter, SOME_ENVORINMENT);
+} // namespace
+
+void utc_dali_internal_debug_startup()
+{
+  test_return_value = TET_UNDEF;
+
+  gCapturedLog.clear();
+}
+
+void utc_dali_internal_debug_cleanup()
+{
+  gCapturedLog.clear();
+
+  test_return_value = TET_PASS;
+}
+
+int UtcDaliDebugLogPrintP(void)
+{
+  TestApplication application;
+
+  tet_infoline("UtcDaliDebugLogPrintP() Test dali default log macros");
+
+  Debug::InstallLogFunction(LogCaptureFunction);
+
+  std::string expectLogString = "some expect logs";
+
+  DALI_LOG_ERROR("%s", expectLogString.c_str());
+  DALI_TEST_EQUALS(CheckCapturedLogContainString(Debug::DebugPriority::ERROR, expectLogString), true, TEST_LOCATION);
+
+  DALI_LOG_WARNING("%s", expectLogString.c_str());
+  DALI_TEST_EQUALS(CheckCapturedLogContainString(Debug::DebugPriority::WARNING, expectLogString), true, TEST_LOCATION);
+
+  DALI_LOG_RELEASE_INFO("%s", expectLogString.c_str());
+  DALI_TEST_EQUALS(CheckCapturedLogContainString(Debug::DebugPriority::INFO, expectLogString), true, TEST_LOCATION);
+
+  DALI_LOG_DEBUG_INFO("%s", expectLogString.c_str());
+  DALI_TEST_EQUALS(CheckCapturedLogContainString(Debug::DebugPriority::DEBUG, expectLogString), true, TEST_LOCATION);
+
+  Debug::UninstallLogFunction();
+
+  END_TEST;
+}
+
+int UtcDaliDebugTimeCheckerP(void)
+{
+  TestApplication application;
+
+  tet_infoline("UtcDaliDebugTimeCheckerP() Test time checker without environment");
+
+  Debug::InstallLogFunction(LogCaptureFunction);
+
+  auto* enabledFilter  = Dali::Integration::TimeChecker::ThresholdFilter::New(0, "SOME_ENVORINMENT");
+  auto* disabledFilter = Dali::Integration::TimeChecker::ThresholdFilter::New(std::numeric_limits<uint32_t>::max(), "SOME_ENVORINMENT");
+
+  DALI_TEST_EQUALS(enabledFilter->IsEnabled(), true, TEST_LOCATION);
+  DALI_TEST_EQUALS(disabledFilter->IsEnabled(), false, TEST_LOCATION);
+  DALI_TEST_EQUALS(gDummyTimeCheckerFilter->IsEnabled(), false, TEST_LOCATION);
+
+  std::string expectLogString = "some expect logs";
+
+  tet_printf("Use function directly.\n");
+  tet_printf("Check enabled filter print log\n");
+  enabledFilter->BeginTimeCheck();
+  enabledFilter->EndTimeCheck(expectLogString.c_str(), "", "", 0);
+  DALI_TEST_EQUALS(CheckCapturedLogContainString(Debug::DebugPriority::INFO, expectLogString), true, TEST_LOCATION);
+
+  tet_printf("Check disabled filter don't print log\n");
+  // Clean up captured log first
+  gCapturedLog.clear();
+  disabledFilter->BeginTimeCheck();
+  disabledFilter->EndTimeCheck(expectLogString.c_str(), "", "", 0);
+  DALI_TEST_EQUALS(gCapturedLog.empty(), true, TEST_LOCATION);
+
+  tet_printf("Use function by macro.\n");
+  tet_printf("Check enabled filter print log\n");
+  DALI_TIME_CHECKER_BEGIN(enabledFilter);
+  DALI_TIME_CHECKER_BEGIN(enabledFilter);
+  DALI_TIME_CHECKER_BEGIN(enabledFilter);
+
+  DALI_TIME_CHECKER_END(enabledFilter);
+  DALI_TEST_EQUALS(CheckCapturedLogContainString(Debug::DebugPriority::INFO, "ms"), true, TEST_LOCATION);
+
+  DALI_TIME_CHECKER_END_WITH_MESSAGE(enabledFilter, "simple message");
+  DALI_TEST_EQUALS(CheckCapturedLogContainString(Debug::DebugPriority::INFO, "simple message"), true, TEST_LOCATION);
+
+  bool messageGeneratorExcuted = false;
+
+  DALI_TIME_CHECKER_END_WITH_MESSAGE_GENERATOR(enabledFilter, [&](std::ostringstream& oss) {
+    messageGeneratorExcuted = true;
+    oss << "complex message";
+  });
+  DALI_TEST_EQUALS(messageGeneratorExcuted, true, TEST_LOCATION);
+  DALI_TEST_EQUALS(CheckCapturedLogContainString(Debug::DebugPriority::INFO, "complex message"), true, TEST_LOCATION);
+
+  tet_printf("Check disabled filter don't print log\n");
+  // Clean up captured log first
+  gCapturedLog.clear();
+  DALI_TIME_CHECKER_BEGIN(gDummyTimeCheckerFilter);
+  DALI_TIME_CHECKER_BEGIN(gDummyTimeCheckerFilter);
+  DALI_TIME_CHECKER_BEGIN(gDummyTimeCheckerFilter);
+
+  DALI_TIME_CHECKER_END(gDummyTimeCheckerFilter);
+  DALI_TEST_EQUALS(gCapturedLog.empty(), true, TEST_LOCATION);
+
+  DALI_TIME_CHECKER_END_WITH_MESSAGE(gDummyTimeCheckerFilter, "simple message");
+  DALI_TEST_EQUALS(gCapturedLog.empty(), true, TEST_LOCATION);
+
+  messageGeneratorExcuted = false;
+  DALI_TIME_CHECKER_END_WITH_MESSAGE_GENERATOR(gDummyTimeCheckerFilter, [&](std::ostringstream& oss) {
+    messageGeneratorExcuted = true;
+    oss << "complex message";
+  });
+  DALI_TEST_EQUALS(messageGeneratorExcuted, false, TEST_LOCATION);
+  DALI_TEST_EQUALS(gCapturedLog.empty(), true, TEST_LOCATION);
+
+  Debug::UninstallLogFunction();
+
+  END_TEST;
+}
+
+int UtcDaliDebugTimeCheckerScopeTracerP(void)
+{
+  TestApplication application;
+
+  tet_infoline("UtcDaliDebugTimeCheckerScopeTracerP() Test time checker without environment");
+
+  Debug::InstallLogFunction(LogCaptureFunction);
+
+  auto* enabledFilter = Dali::Integration::TimeChecker::ThresholdFilter::New(0, "SOME_ENVORINMENT");
+
+  DALI_TEST_EQUALS(enabledFilter->IsEnabled(), true, TEST_LOCATION);
+
+  std::string expectLogString = "some expect logs";
+  {
+    DALI_TIME_CHECKER_SCOPE(enabledFilter, expectLogString.c_str());
+    DALI_TEST_EQUALS(gCapturedLog.empty(), true, TEST_LOCATION);
+    std::string expectLogString2 = "another expect logs";
+    {
+      DALI_TIME_CHECKER_SCOPE(enabledFilter, expectLogString2.c_str());
+      DALI_TEST_EQUALS(gCapturedLog.empty(), true, TEST_LOCATION);
+    }
+    DALI_TEST_EQUALS(CheckCapturedLogContainString(Debug::DebugPriority::INFO, expectLogString2), true, TEST_LOCATION);
+
+    // Clean up captured log first
+    gCapturedLog.clear();
+    {
+      DALI_TIME_CHECKER_SCOPE(enabledFilter, expectLogString2.c_str());
+      DALI_TEST_EQUALS(gCapturedLog.empty(), true, TEST_LOCATION);
+    }
+    DALI_TEST_EQUALS(CheckCapturedLogContainString(Debug::DebugPriority::INFO, expectLogString2), true, TEST_LOCATION);
+  }
+  DALI_TEST_EQUALS(CheckCapturedLogContainString(Debug::DebugPriority::INFO, expectLogString), true, TEST_LOCATION);
+
+  Debug::UninstallLogFunction();
+
+  END_TEST;
+}
index 7647c224f1578a837bd044b3d6d96f276522fcaa..f6eee8b11ee28338537c9b8772920ba1a4c46608 100644 (file)
 #include <cstdarg>
 #include <memory>
 
+// INTERNAL INCLUDES
+#include <dali/public-api/common/list-wrapper.h>
+#include <dali/public-api/common/vector-wrapper.h>
+
 namespace Dali
 {
 #ifdef DEBUG_ENABLED
@@ -278,8 +282,6 @@ std::string FormatToString(const char* format, ...)
   return s;
 }
 
-#ifdef DEBUG_ENABLED
-
 void GetNanoseconds(uint64_t& timeInNanoseconds)
 {
   // Get the time of a monotonic clock since its epoch.
@@ -290,10 +292,105 @@ void GetNanoseconds(uint64_t& timeInNanoseconds)
   timeInNanoseconds = static_cast<uint64_t>(duration.count());
 }
 
-#endif // DEBUG_ENABLED
-
 } // namespace Log
 
+namespace TimeChecker
+{
+typedef std::list<std::unique_ptr<ThresholdFilter>> FilterList;
+
+namespace
+{
+constexpr uint64_t MILLISECONDS_TO_NANOSECONDS = 1'000'000;
+
+static FilterList& GetActiveFilters()
+{
+  thread_local static FilterList activeFilters;
+  return activeFilters;
+}
+} // namespace
+
+struct ThresholdFilter::Impl
+{
+  std::vector<uint64_t> mTimeStamps;
+};
+
+ThresholdFilter* ThresholdFilter::New(ThresholdFilter::Milliseconds thresholdMilliSeconds, const char* environmentVariableName)
+{
+  try
+  {
+    char* environmentVariableValue = getenv(environmentVariableName);
+    if(environmentVariableValue)
+    {
+      thresholdMilliSeconds = static_cast<Milliseconds>(std::atoi(environmentVariableValue));
+    }
+  }
+  catch(...)
+  {
+    // Do nothing, just use default value.
+  }
+
+  ThresholdFilter* thresholdFilter = new ThresholdFilter(thresholdMilliSeconds);
+
+  GetActiveFilters().push_back(std::unique_ptr<ThresholdFilter>(thresholdFilter));
+  return thresholdFilter;
+}
+
+void ThresholdFilter::BeginTimeCheck()
+{
+  if(DALI_LIKELY(IsEnabled()))
+  {
+    uint64_t currentTime;
+    Log::GetNanoseconds(currentTime);
+    mImpl->mTimeStamps.push_back(currentTime);
+  }
+}
+
+void ThresholdFilter::EndTimeCheck(const char* messagePrefix, const char* module, const char* function, const int line)
+{
+  if(DALI_LIKELY(IsEnabled() && !mImpl->mTimeStamps.empty()))
+  {
+    uint64_t currentTime;
+    Log::GetNanoseconds(currentTime);
+
+    uint64_t duration = currentTime - mImpl->mTimeStamps.back();
+    mImpl->mTimeStamps.pop_back();
+
+    if(duration >= static_cast<uint64_t>(mThresholdMilliSeconds) * MILLISECONDS_TO_NANOSECONDS)
+    {
+      // Use LogMessage function directly, to print correct module / function and line.
+      Dali::Integration::Log::LogMessage(Dali::Integration::Log::INFO, DALI_LOG_FORMAT_PREFIX "%s takes [%.6lf ms]\n", module, function, line, messagePrefix ? messagePrefix : "", static_cast<double>(duration) / static_cast<double>(MILLISECONDS_TO_NANOSECONDS));
+    }
+  }
+}
+
+ThresholdFilter::ThresholdFilter(ThresholdFilter::Milliseconds thresholdMilliSeconds)
+: mThresholdMilliSeconds(thresholdMilliSeconds),
+  mImpl(new Impl())
+{
+}
+
+ScopeTracer::ScopeTracer(ThresholdFilter* filter, const char* messagePrefix, const char* module, const char* function, const int line)
+: mMessagePrefix(messagePrefix),
+  mModule(module),
+  mFunction(function),
+  mLine(line),
+  mFilter(filter)
+{
+  if(mFilter && mFilter->IsEnabled())
+  {
+    mFilter->BeginTimeCheck();
+  }
+}
+
+ScopeTracer::~ScopeTracer()
+{
+  if(mFilter && mFilter->IsEnabled())
+  {
+    mFilter->EndTimeCheck(mMessagePrefix, mModule, mFunction, mLine);
+  }
+}
+} // namespace TimeChecker
+
 } // namespace Integration
 
 } // namespace Dali
index 5d812586662858caca574bd723cc58c5a64464ea..687c4f559f5050194fde7e53664f9a0cba324547 100644 (file)
 #include <stdint.h>
 #include <cstring>
 #include <iostream>
-#include <list>
+#include <memory>
 #include <sstream>
 #include <string>
 
+#include <dali/public-api/common/list-wrapper.h>
 #include <dali/public-api/common/vector-wrapper.h>
 #include <dali/public-api/object/property-map.h>
 
@@ -504,7 +505,7 @@ public:                                    \
   *
   * @note The maximum value timeInNanoseconds can hold is 0xFFFFFFFFFFFFFFFF which is 1.844674407e+19. Therefore, this can overflow after approximately 584 years.
   */
-void GetNanoseconds(uint64_t& timeInNanoseconds);
+DALI_CORE_API void GetNanoseconds(uint64_t& timeInNanoseconds);
 
 #define DALI_LOG_TIMER_START(timeVariable) \
   uint64_t timeVariable##1;                \
@@ -523,6 +524,174 @@ void GetNanoseconds(uint64_t& timeInNanoseconds);
 #endif
 
 } // namespace Log
+
+namespace TimeChecker
+{
+/**
+ * @brief Print as release info if elapsed time greater or equal than threshold. (milliseconds)
+ * You can also set threshold by environment value.
+ *
+ * threshold = -1 (==std::numeric_limits<uint32_t>::max()) means always not print.
+ * threshold = 0 mean always print.
+ *
+ * To check duration, call BeginTimeCheck() and EndTimeCheck() pairwise.
+ * The filter print duration at EndTimeCheck() by DALI_LOG_RELEASE_INFO.
+ * We can call it recursively.
+ *
+ * @code
+ * auto* filter = ThresholdFilter::New(20u, "specific_environment_value");
+ *
+ * filter->BeginTimeCheck();
+ * for(;;)
+ * {
+ *   filter->BeginTimeCheck();
+ *   // Some codes
+ *   filter->EndTimeCheck("Phase 1");
+ *   filter->BeginTimeCheck();
+ *   // Some codes
+ *   filter->EndTimeCheck("Phase 2");
+ * }
+ * filter->EndTimeCheck("Total");
+ * @endcode
+ */
+class DALI_CORE_API ThresholdFilter
+{
+public:
+  using Milliseconds = uint32_t;
+
+  /**
+   * @brief Create new filter object.
+   * The ownership of memory hold in global static list.
+   * It will be released when thread joined.
+   * @return New allocated filter with threshold and environment.
+   */
+  static ThresholdFilter* New(ThresholdFilter::Milliseconds thresholdMilliSeconds, const char* environmentVariableName);
+
+  /**
+   * @brief Check whether this filter is enabled or not.
+   * @return True if threshold is valid value. False if threshold is -1 (==std::numeric_limits<uint32_t>::max())
+   */
+  inline bool IsEnabled()
+  {
+    return mThresholdMilliSeconds != std::numeric_limits<Milliseconds>::max();
+  }
+
+  /**
+   * @brief Begin time checker.
+   * @post EndTimeCheck() should be called after.
+   */
+  void BeginTimeCheck();
+
+  /**
+   * @brief End time checker.
+   * Print log if duration is greater of equal than threshold.
+   * @param[in] messagePrefix Prefix of message.
+   * @param[in] module The name of module who call this checker
+   * @param[in] fuction The name of function who call this checker
+   * @param[in] line The line number of function who call this checker.
+   * @pre BeginTimeCheck() should be called before.
+   */
+  void EndTimeCheck(const char* messagePrefix, const char* module, const char* function, const int line);
+
+private:
+  ThresholdFilter(Milliseconds thresholdMilliSeconds);
+
+private:
+  Milliseconds mThresholdMilliSeconds;
+
+  struct Impl;
+  std::unique_ptr<Impl> mImpl;
+};
+
+/**
+ * @brief The ScopeTracer object is used by the DALI_TIME_CHECKER_SCOPE macros.
+ */
+class DALI_CORE_API ScopeTracer final
+{
+public:
+  ScopeTracer(ThresholdFilter* filter, const char* messagePrefix, const char* module, const char* function, const int line);
+  ~ScopeTracer();
+
+public:
+  const char* mMessagePrefix;
+  const char* mModule;
+  const char* mFunction;
+  const int   mLine;
+
+  ThresholdFilter* mFilter;
+};
+
+/**
+ * @brief Initialization of time checker filter
+ * Note that filter must be thread_local scoped
+ * @ref ThresholdFilter::New
+ */
+#define DALI_INIT_TIME_CHECKER_FILTER_WITH_DEFAULT_THRESHOLD(name, environmentVariableName, threshold)                                                            \
+  namespace                                                                                                                                                       \
+  {                                                                                                                                                               \
+  thread_local Dali::Integration::TimeChecker::ThresholdFilter* name = Dali::Integration::TimeChecker::ThresholdFilter::New(threshold, #environmentVariableName); \
+  }
+
+/**
+ * @brief Shorten filter macro without default threshold value.
+ * It will make filter disabled as default.
+ * @ref ThresholdFilter::New
+ */
+#define DALI_INIT_TIME_CHECKER_FILTER(name, environmentVariableName) \
+  DALI_INIT_TIME_CHECKER_FILTER_WITH_DEFAULT_THRESHOLD(name, environmentVariableName, std::numeric_limits<Dali::Integration::TimeChecker::ThresholdFilter::Milliseconds>::max())
+
+/**
+ * @brief Begin of time checker
+ * @ref ThresholdFilter::BeginTimeCheck
+ */
+#define DALI_TIME_CHECKER_BEGIN(filter) \
+  if(filter && filter->IsEnabled())     \
+  {                                     \
+    filter->BeginTimeCheck();           \
+  }
+
+/**
+ * @brief End of time checker without any prefix message
+ * @ref ThresholdFilter::EndTimeCheck
+ */
+#define DALI_TIME_CHECKER_END(filter)                  \
+  if(filter && filter->IsEnabled())                    \
+  {                                                    \
+    filter->EndTimeCheck("",                           \
+                         DALI_LOG_FORMAT_PREFIX_ARGS); \
+  }
+
+/**
+ * @brief End of time checker with simple prefix message
+ * @ref ThresholdFilter::EndTimeCheck
+ */
+#define DALI_TIME_CHECKER_END_WITH_MESSAGE(filter, message) \
+  if(filter && filter->IsEnabled())                         \
+  {                                                         \
+    filter->EndTimeCheck(message,                           \
+                         DALI_LOG_FORMAT_PREFIX_ARGS);      \
+  }
+
+/**
+ * @brief End of time checker with prefix message generator
+ * @ref ThresholdFilter::EndTimeCheck
+ */
+#define DALI_TIME_CHECKER_END_WITH_MESSAGE_GENERATOR(filter, messageGenerator) \
+  if(filter && filter->IsEnabled())                                            \
+  {                                                                            \
+    std::ostringstream oss;                                                    \
+    messageGenerator(oss);                                                     \
+    filter->EndTimeCheck(oss.str().c_str(),                                    \
+                         DALI_LOG_FORMAT_PREFIX_ARGS);                         \
+  }
+
+/**
+ * @brief Used for scope time check. It check time around a scope.
+ */
+#define DALI_TIME_CHECKER_SCOPE(filter, message) \
+  Dali::Integration::TimeChecker::ScopeTracer timeCheckerScopeTracer(filter, message, DALI_LOG_FORMAT_PREFIX_ARGS);
+
+} // namespace TimeChecker
 } // namespace Integration
 } // namespace Dali