From c14b3ef890a714c1de8c074eb7fa0084c025f9e0 Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Fri, 17 Jul 2026 00:56:16 +0200 Subject: [PATCH 01/10] [NativeAOT] Add printf-style native logging Introduce printf-style native logging and abort helpers while preserving the existing std::format APIs for MonoVM and CoreCLR. Migrate the NativeAOT-specific formatted call sites to the new primitives. Refs #12139 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 187b207a-083b-461e-9071-e9aab61c9d07 --- src/native/clr/include/shared/log_types.hh | 8 ++ src/native/clr/shared/helpers.cc | 20 ++++- src/native/clr/shared/log_functions.cc | 85 +++++++++++++++++-- src/native/common/include/shared/helpers.hh | 3 + .../nativeaot/host/bridge-processing.cc | 16 ++-- src/native/nativeaot/host/host.cc | 4 +- 6 files changed, 113 insertions(+), 23 deletions(-) diff --git a/src/native/clr/include/shared/log_types.hh b/src/native/clr/include/shared/log_types.hh index 7aef6e20456..8e8d0811e45 100644 --- a/src/native/clr/include/shared/log_types.hh +++ b/src/native/clr/include/shared/log_types.hh @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -47,6 +48,13 @@ namespace xamarin::android { // A slightly faster alternative to other log functions as it doesn't parse the message // for format placeholders nor it uses variable arguments void log_write (LogCategories category, LogLevel level, const char *message) noexcept; + void log_writev (LogCategories category, LogLevel level, const char *format, va_list args) noexcept; + void log_writef (LogCategories category, LogLevel level, const char *format, ...) noexcept __attribute__ ((format (printf, 3, 4))); + void log_debugf (LogCategories category, const char *format, ...) noexcept __attribute__ ((format (printf, 2, 3))); + void log_infof (LogCategories category, const char *format, ...) noexcept __attribute__ ((format (printf, 2, 3))); + void log_warnf (LogCategories category, const char *format, ...) noexcept __attribute__ ((format (printf, 2, 3))); + void log_errorf (LogCategories category, const char *format, ...) noexcept __attribute__ ((format (printf, 2, 3))); + void log_fatalf (LogCategories category, const char *format, ...) noexcept __attribute__ ((format (printf, 2, 3))); [[gnu::always_inline]] static inline void log_write (LogCategories category, LogLevel level, std::string_view const& message) noexcept diff --git a/src/native/clr/shared/helpers.cc b/src/native/clr/shared/helpers.cc index 7850592af5a..da509e5fb73 100644 --- a/src/native/clr/shared/helpers.cc +++ b/src/native/clr/shared/helpers.cc @@ -7,11 +7,24 @@ using namespace xamarin::android; +[[noreturn]] void +Helpers::abort_applicationf (LogCategories category, std::source_location sloc, const char *format, ...) noexcept +{ + char *message = nullptr; + const char *safe_format = format == nullptr ? "" : format; + va_list args; + va_start (args, format); + int ret = vasprintf (&message, safe_format, args); + va_end (args); + + abort_application (category, ret < 0 ? safe_format : message, true, sloc); +} + [[noreturn]] void Helpers::abort_application (LogCategories category, const char *message, bool log_location, std::source_location sloc) noexcept { // Log it, but also... - log_fatal (category, "{}", message); + log_write (category, LogLevel::Fatal, message); // ...let android include it in the tombstone, debuggerd output, stack trace etc android_set_abort_message (message); @@ -33,9 +46,10 @@ Helpers::abort_application (LogCategories category, const char *message, bool lo } } - log_fatal ( + log_writef ( category, - "Abort at {}:{}:{} ('{}')", + LogLevel::Fatal, + "Abort at %s:%u:%u ('%s')", file_name, sloc.line (), sloc.column (), diff --git a/src/native/clr/shared/log_functions.cc b/src/native/clr/shared/log_functions.cc index acd5e705c06..0c097edaed7 100644 --- a/src/native/clr/shared/log_functions.cc +++ b/src/native/clr/shared/log_functions.cc @@ -55,6 +55,17 @@ namespace { }; constexpr size_t loglevel_map_max_index = (sizeof(loglevel_map) / sizeof(android_LogPriority)) - 1; + + [[gnu::always_inline]] + auto priority_for_level (LogLevel level) noexcept -> android_LogPriority + { + size_t map_index = static_cast(level); + if (map_index > loglevel_map_max_index) { + return DEFAULT_PRIORITY; + } + + return loglevel_map[map_index]; + } } unsigned int log_categories = LOG_NONE; @@ -117,15 +128,75 @@ namespace xamarin::android { void log_write (LogCategories category, LogLevel level, const char *message) noexcept { - size_t map_index = static_cast(level); - android_LogPriority priority; + __android_log_write (priority_for_level (level), category_name (category), message); + } - if (map_index > loglevel_map_max_index) { - priority = DEFAULT_PRIORITY; - } else { - priority = loglevel_map[map_index]; + void + log_writev (LogCategories category, LogLevel level, const char *format, va_list args) noexcept + { + const char *safe_format = format == nullptr ? "" : format; + __android_log_vprint (priority_for_level (level), category_name (category), safe_format, args); + } + + void + log_writef (LogCategories category, LogLevel level, const char *format, ...) noexcept + { + va_list args; + va_start (args, format); + log_writev (category, level, format, args); + va_end (args); + } + + void + log_debugf (LogCategories category, const char *format, ...) noexcept + { + if ((log_categories & category) == 0) { + return; + } + + va_list args; + va_start (args, format); + log_writev (category, LogLevel::Debug, format, args); + va_end (args); + } + + void + log_infof (LogCategories category, const char *format, ...) noexcept + { + if ((log_categories & category) == 0) { + return; } - __android_log_write (priority, category_name (category), message); + va_list args; + va_start (args, format); + log_writev (category, LogLevel::Info, format, args); + va_end (args); + } + + void + log_warnf (LogCategories category, const char *format, ...) noexcept + { + va_list args; + va_start (args, format); + log_writev (category, LogLevel::Warn, format, args); + va_end (args); + } + + void + log_errorf (LogCategories category, const char *format, ...) noexcept + { + va_list args; + va_start (args, format); + log_writev (category, LogLevel::Error, format, args); + va_end (args); + } + + void + log_fatalf (LogCategories category, const char *format, ...) noexcept + { + va_list args; + va_start (args, format); + log_writev (category, LogLevel::Fatal, format, args); + va_end (args); } } diff --git a/src/native/common/include/shared/helpers.hh b/src/native/common/include/shared/helpers.hh index 22e29b40f25..f1ec1fb6ab4 100644 --- a/src/native/common/include/shared/helpers.hh +++ b/src/native/common/include/shared/helpers.hh @@ -56,6 +56,9 @@ namespace xamarin::android [[noreturn]] static void abort_application (LogCategories category, const char *message, bool log_location = true, std::source_location sloc = std::source_location::current ()) noexcept; + [[noreturn]] + static void abort_applicationf (LogCategories category, std::source_location sloc, const char *format, ...) noexcept __attribute__ ((format (printf, 3, 4))); + [[noreturn]] static void abort_application (LogCategories category, std::string const& message, bool log_location = true, std::source_location sloc = std::source_location::current ()) noexcept { diff --git a/src/native/nativeaot/host/bridge-processing.cc b/src/native/nativeaot/host/bridge-processing.cc index b87a49b431b..eb636515d6f 100644 --- a/src/native/nativeaot/host/bridge-processing.cc +++ b/src/native/nativeaot/host/bridge-processing.cc @@ -1,5 +1,3 @@ -#include - #include #include #include @@ -21,16 +19,12 @@ void BridgeProcessing::naot_initialize_on_runtime_init (JNIEnv *env) noexcept GCUserPeerable_jiClearManagedReferences = env->GetMethodID (GCUserPeerable_class, "jiClearManagedReferences", "()V"); if (GCUserPeerable_jiAddManagedReference == nullptr || GCUserPeerable_jiClearManagedReferences == nullptr) [[unlikely]] { - constexpr auto ABSENT = "absent"sv; - constexpr auto PRESENT = "present"sv; - - Helpers::abort_application ( + Helpers::abort_applicationf ( LOG_DEFAULT, - std::format ( - "Failed to find GCUserPeerable method(s): jiAddManagedReference ({}); jiClearManagedReferences ({})"sv, - GCUserPeerable_jiAddManagedReference == nullptr ? ABSENT : PRESENT, - GCUserPeerable_jiClearManagedReferences == nullptr ? ABSENT : PRESENT - ) + std::source_location::current (), + "Failed to find GCUserPeerable method(s): jiAddManagedReference (%s); jiClearManagedReferences (%s)", + GCUserPeerable_jiAddManagedReference == nullptr ? "absent" : "present", + GCUserPeerable_jiClearManagedReferences == nullptr ? "absent" : "present" ); } } diff --git a/src/native/nativeaot/host/host.cc b/src/native/nativeaot/host/host.cc index b6d87483c78..7258f963785 100644 --- a/src/native/nativeaot/host/host.cc +++ b/src/native/nativeaot/host/host.cc @@ -33,9 +33,9 @@ auto HostCommon::Java_JNI_OnLoad (JavaVM *vm, void *reserved) noexcept -> jint if (__jni_on_load_handler_count > 0) { for (uint32_t i = 0; i < __jni_on_load_handler_count; i++) { - log_debug ( + log_debugf ( LOG_ASSEMBLY, - "Calling JNI on-load init func '{}' ({:p})", + "Calling JNI on-load init func '%s' (%p)", optional_string (__jni_on_load_handler_names[i]), reinterpret_cast(__jni_on_load_handlers[i]) ); From 90e6fb67de6dab87b6cf755e5fb48f7d17ddaafa Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Fri, 17 Jul 2026 10:18:55 +0200 Subject: [PATCH 02/10] [native] Test printf-style logging Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 70f63eb7-6599-414c-a947-d860705aa0fa --- .../NativeLoggingTests.cs | 50 +++++++++++ .../NativeLogging/include/android/log.h | 26 ++++++ .../NativeLogging/include/constants.hh | 22 +++++ .../NativeLogging/log-functions-tests.cc | 83 +++++++++++++++++++ 4 files changed, 181 insertions(+) create mode 100644 src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/NativeLoggingTests.cs create mode 100644 src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Resources/NativeLogging/include/android/log.h create mode 100644 src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Resources/NativeLogging/include/constants.hh create mode 100644 src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Resources/NativeLogging/log-functions-tests.cc diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/NativeLoggingTests.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/NativeLoggingTests.cs new file mode 100644 index 00000000000..0454820a571 --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/NativeLoggingTests.cs @@ -0,0 +1,50 @@ +using NUnit.Framework; +using System; +using System.IO; +using Xamarin.ProjectTools; + +namespace Xamarin.Android.Build.Tests +{ + [TestFixture] + public class NativeLoggingTests : BaseTest + { + [Test] + public void PrintfLoggingForwardsArgumentsAndSkipsDisabledCategories () + { + if (IsWindows) { + Assert.Ignore ("The native logging test requires a host C++ compiler."); + } + + var testDirectory = Path.Combine (XABuildPaths.TopDirectory, "src", "Xamarin.Android.Build.Tasks", "Tests", "Xamarin.Android.Build.Tests", "Resources", "NativeLogging"); + var nativeDirectory = Path.Combine (XABuildPaths.TopDirectory, "src", "native"); + var outputDirectory = Path.Combine (Root, TestName); + var output = Path.Combine (outputDirectory, "native-logging-tests"); + Directory.CreateDirectory (outputDirectory); + + var compiler = Environment.GetEnvironmentVariable ("CXX"); + if (string.IsNullOrEmpty (compiler)) { + compiler = "clang++"; + } + + var arguments = string.Join (" ", new [] { + "-std=c++23", + "-Wall", + "-Wextra", + "-Werror", + $"-I\"{Path.Combine (testDirectory, "include")}\"", + $"-I\"{Path.Combine (nativeDirectory, "common", "include")}\"", + $"-I\"{Path.Combine (nativeDirectory, "clr", "include")}\"", + $"-I\"{Path.Combine (XABuildPaths.TopDirectory, "external", "Java.Interop", "src", "java-interop")}\"", + $"\"{Path.Combine (testDirectory, "log-functions-tests.cc")}\"", + $"\"{Path.Combine (nativeDirectory, "clr", "shared", "log_functions.cc")}\"", + $"-o \"{output}\"", + }); + + var (compileExitCode, compileOutput, compileError) = RunProcessWithExitCode (compiler, arguments); + Assert.That (compileExitCode, Is.EqualTo (0), $"{compileOutput}{Environment.NewLine}{compileError}"); + + var (testExitCode, testOutput, testError) = RunProcessWithExitCode (output, ""); + Assert.That (testExitCode, Is.EqualTo (0), $"{testOutput}{Environment.NewLine}{testError}"); + } + } +} diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Resources/NativeLogging/include/android/log.h b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Resources/NativeLogging/include/android/log.h new file mode 100644 index 00000000000..61d3d0a79e7 --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Resources/NativeLogging/include/android/log.h @@ -0,0 +1,26 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum android_LogPriority { + ANDROID_LOG_UNKNOWN = 0, + ANDROID_LOG_DEFAULT, + ANDROID_LOG_VERBOSE, + ANDROID_LOG_DEBUG, + ANDROID_LOG_INFO, + ANDROID_LOG_WARN, + ANDROID_LOG_ERROR, + ANDROID_LOG_FATAL, + ANDROID_LOG_SILENT, +} android_LogPriority; + +int __android_log_write (int priority, const char *tag, const char *text); +int __android_log_vprint (int priority, const char *tag, const char *format, va_list args); + +#ifdef __cplusplus +} +#endif diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Resources/NativeLogging/include/constants.hh b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Resources/NativeLogging/include/constants.hh new file mode 100644 index 00000000000..cb5bbb465da --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Resources/NativeLogging/include/constants.hh @@ -0,0 +1,22 @@ +#pragma once + +#include + +namespace xamarin::android { + class Constants + { + public: + static constexpr std::string_view LOG_CATEGORY_NAME_NONE { "*none*" }; + static constexpr std::string_view LOG_CATEGORY_NAME_MONODROID { "monodroid" }; + static constexpr std::string_view LOG_CATEGORY_NAME_MONODROID_ASSEMBLY { "monodroid-assembly" }; + static constexpr std::string_view LOG_CATEGORY_NAME_MONODROID_DEBUG { "monodroid-debug" }; + static constexpr std::string_view LOG_CATEGORY_NAME_MONODROID_GC { "monodroid-gc" }; + static constexpr std::string_view LOG_CATEGORY_NAME_MONODROID_GREF { "monodroid-gref" }; + static constexpr std::string_view LOG_CATEGORY_NAME_MONODROID_LREF { "monodroid-lref" }; + static constexpr std::string_view LOG_CATEGORY_NAME_MONODROID_TIMING { "monodroid-timing" }; + static constexpr std::string_view LOG_CATEGORY_NAME_MONODROID_BUNDLE { "monodroid-bundle" }; + static constexpr std::string_view LOG_CATEGORY_NAME_MONODROID_NETWORK { "monodroid-network" }; + static constexpr std::string_view LOG_CATEGORY_NAME_MONODROID_NETLINK { "monodroid-netlink" }; + static constexpr std::string_view LOG_CATEGORY_NAME_ERROR { "*error*" }; + }; +} diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Resources/NativeLogging/log-functions-tests.cc b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Resources/NativeLogging/log-functions-tests.cc new file mode 100644 index 00000000000..a39da50ac20 --- /dev/null +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Resources/NativeLogging/log-functions-tests.cc @@ -0,0 +1,83 @@ +#include +#include +#include + +#include +#include + +namespace { + constexpr size_t MESSAGE_SIZE = 256; + + char last_message [MESSAGE_SIZE]; + int last_priority; + int log_count; + + auto fail (const char *message) noexcept -> int + { + std::fprintf (stderr, "%s\n", message); + return 1; + } + + void reset_log () noexcept + { + std::memset (last_message, 0, sizeof (last_message)); + last_priority = -1; + log_count = 0; + } +} + +extern "C" int +__android_log_write (int priority, const char*, const char *text) +{ + last_priority = priority; + log_count++; + return std::snprintf (last_message, sizeof (last_message), "%s", text); +} + +extern "C" int +__android_log_vprint (int priority, const char*, const char *format, va_list args) +{ + last_priority = priority; + log_count++; + return std::vsnprintf (last_message, sizeof (last_message), format, args); +} + +int +main () +{ + using namespace xamarin::android; + + const char *text = "forwarded"; + const void *pointer = &log_count; + char expected [MESSAGE_SIZE]; + std::snprintf (expected, sizeof (expected), "%s %p %d", text, pointer, -42); + + reset_log (); + log_writef (LOG_ASSEMBLY, LogLevel::Info, "%s %p %d", text, pointer, -42); + if (log_count != 1 || last_priority != ANDROID_LOG_INFO || std::strcmp (last_message, expected) != 0) { + return fail ("printf arguments were not forwarded to the Android logging API"); + } + + log_categories = LOG_NONE; + reset_log (); + log_debugf (LOG_ASSEMBLY, "%s", "disabled debug"); + log_infof (LOG_ASSEMBLY, "%s", "disabled info"); + if (log_count != 0 || last_message[0] != '\0') { + return fail ("disabled debug or info logging invoked formatting"); + } + + log_categories = LOG_ASSEMBLY; + reset_log (); + log_debugf (LOG_ASSEMBLY, "%d", 7); + if (log_count != 1 || last_priority != ANDROID_LOG_DEBUG || std::strcmp (last_message, "7") != 0) { + return fail ("enabled debug logging did not format its message"); + } + + reset_log (); + log_infof (LOG_ASSEMBLY, "%s", "enabled info"); + if (log_count != 1 || last_priority != ANDROID_LOG_INFO || std::strcmp (last_message, "enabled info") != 0) { + return fail ("enabled info logging did not format its message"); + } + + return 0; +} From 2cb0c4e946266989db2be6dc0291c0a368e8c4ee Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Fri, 17 Jul 2026 10:20:53 +0200 Subject: [PATCH 03/10] [Tests] Follow Android string helper convention Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 70f63eb7-6599-414c-a947-d860705aa0fa --- .../Tests/Xamarin.Android.Build.Tests/NativeLoggingTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/NativeLoggingTests.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/NativeLoggingTests.cs index 0454820a571..508320d35ee 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/NativeLoggingTests.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/NativeLoggingTests.cs @@ -22,7 +22,7 @@ public void PrintfLoggingForwardsArgumentsAndSkipsDisabledCategories () Directory.CreateDirectory (outputDirectory); var compiler = Environment.GetEnvironmentVariable ("CXX"); - if (string.IsNullOrEmpty (compiler)) { + if (compiler.IsNullOrEmpty ()) { compiler = "clang++"; } From 4a27f681ccbb2cbf3720f3f4895d379248d0f8a2 Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Fri, 17 Jul 2026 10:24:44 +0200 Subject: [PATCH 04/10] [Tests] Import Android string helpers Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 70f63eb7-6599-414c-a947-d860705aa0fa --- .../Tests/Xamarin.Android.Build.Tests/NativeLoggingTests.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/NativeLoggingTests.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/NativeLoggingTests.cs index 508320d35ee..eed0d6905b3 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/NativeLoggingTests.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/NativeLoggingTests.cs @@ -1,6 +1,7 @@ using NUnit.Framework; using System; using System.IO; +using Xamarin.Android.Tools; using Xamarin.ProjectTools; namespace Xamarin.Android.Build.Tests From ee07100fec749d4a5a4a05e89723601a531f58bb Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Fri, 17 Jul 2026 10:48:28 +0200 Subject: [PATCH 05/10] [Tests] Remove native logging test harness Keep the printf logging PR focused on the runtime implementation instead of introducing new host-native logging test infrastructure. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 70f63eb7-6599-414c-a947-d860705aa0fa --- .../NativeLoggingTests.cs | 51 ------------ .../NativeLogging/include/android/log.h | 26 ------ .../NativeLogging/include/constants.hh | 22 ----- .../NativeLogging/log-functions-tests.cc | 83 ------------------- 4 files changed, 182 deletions(-) delete mode 100644 src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/NativeLoggingTests.cs delete mode 100644 src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Resources/NativeLogging/include/android/log.h delete mode 100644 src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Resources/NativeLogging/include/constants.hh delete mode 100644 src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Resources/NativeLogging/log-functions-tests.cc diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/NativeLoggingTests.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/NativeLoggingTests.cs deleted file mode 100644 index eed0d6905b3..00000000000 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/NativeLoggingTests.cs +++ /dev/null @@ -1,51 +0,0 @@ -using NUnit.Framework; -using System; -using System.IO; -using Xamarin.Android.Tools; -using Xamarin.ProjectTools; - -namespace Xamarin.Android.Build.Tests -{ - [TestFixture] - public class NativeLoggingTests : BaseTest - { - [Test] - public void PrintfLoggingForwardsArgumentsAndSkipsDisabledCategories () - { - if (IsWindows) { - Assert.Ignore ("The native logging test requires a host C++ compiler."); - } - - var testDirectory = Path.Combine (XABuildPaths.TopDirectory, "src", "Xamarin.Android.Build.Tasks", "Tests", "Xamarin.Android.Build.Tests", "Resources", "NativeLogging"); - var nativeDirectory = Path.Combine (XABuildPaths.TopDirectory, "src", "native"); - var outputDirectory = Path.Combine (Root, TestName); - var output = Path.Combine (outputDirectory, "native-logging-tests"); - Directory.CreateDirectory (outputDirectory); - - var compiler = Environment.GetEnvironmentVariable ("CXX"); - if (compiler.IsNullOrEmpty ()) { - compiler = "clang++"; - } - - var arguments = string.Join (" ", new [] { - "-std=c++23", - "-Wall", - "-Wextra", - "-Werror", - $"-I\"{Path.Combine (testDirectory, "include")}\"", - $"-I\"{Path.Combine (nativeDirectory, "common", "include")}\"", - $"-I\"{Path.Combine (nativeDirectory, "clr", "include")}\"", - $"-I\"{Path.Combine (XABuildPaths.TopDirectory, "external", "Java.Interop", "src", "java-interop")}\"", - $"\"{Path.Combine (testDirectory, "log-functions-tests.cc")}\"", - $"\"{Path.Combine (nativeDirectory, "clr", "shared", "log_functions.cc")}\"", - $"-o \"{output}\"", - }); - - var (compileExitCode, compileOutput, compileError) = RunProcessWithExitCode (compiler, arguments); - Assert.That (compileExitCode, Is.EqualTo (0), $"{compileOutput}{Environment.NewLine}{compileError}"); - - var (testExitCode, testOutput, testError) = RunProcessWithExitCode (output, ""); - Assert.That (testExitCode, Is.EqualTo (0), $"{testOutput}{Environment.NewLine}{testError}"); - } - } -} diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Resources/NativeLogging/include/android/log.h b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Resources/NativeLogging/include/android/log.h deleted file mode 100644 index 61d3d0a79e7..00000000000 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Resources/NativeLogging/include/android/log.h +++ /dev/null @@ -1,26 +0,0 @@ -#pragma once - -#include - -#ifdef __cplusplus -extern "C" { -#endif - -typedef enum android_LogPriority { - ANDROID_LOG_UNKNOWN = 0, - ANDROID_LOG_DEFAULT, - ANDROID_LOG_VERBOSE, - ANDROID_LOG_DEBUG, - ANDROID_LOG_INFO, - ANDROID_LOG_WARN, - ANDROID_LOG_ERROR, - ANDROID_LOG_FATAL, - ANDROID_LOG_SILENT, -} android_LogPriority; - -int __android_log_write (int priority, const char *tag, const char *text); -int __android_log_vprint (int priority, const char *tag, const char *format, va_list args); - -#ifdef __cplusplus -} -#endif diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Resources/NativeLogging/include/constants.hh b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Resources/NativeLogging/include/constants.hh deleted file mode 100644 index cb5bbb465da..00000000000 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Resources/NativeLogging/include/constants.hh +++ /dev/null @@ -1,22 +0,0 @@ -#pragma once - -#include - -namespace xamarin::android { - class Constants - { - public: - static constexpr std::string_view LOG_CATEGORY_NAME_NONE { "*none*" }; - static constexpr std::string_view LOG_CATEGORY_NAME_MONODROID { "monodroid" }; - static constexpr std::string_view LOG_CATEGORY_NAME_MONODROID_ASSEMBLY { "monodroid-assembly" }; - static constexpr std::string_view LOG_CATEGORY_NAME_MONODROID_DEBUG { "monodroid-debug" }; - static constexpr std::string_view LOG_CATEGORY_NAME_MONODROID_GC { "monodroid-gc" }; - static constexpr std::string_view LOG_CATEGORY_NAME_MONODROID_GREF { "monodroid-gref" }; - static constexpr std::string_view LOG_CATEGORY_NAME_MONODROID_LREF { "monodroid-lref" }; - static constexpr std::string_view LOG_CATEGORY_NAME_MONODROID_TIMING { "monodroid-timing" }; - static constexpr std::string_view LOG_CATEGORY_NAME_MONODROID_BUNDLE { "monodroid-bundle" }; - static constexpr std::string_view LOG_CATEGORY_NAME_MONODROID_NETWORK { "monodroid-network" }; - static constexpr std::string_view LOG_CATEGORY_NAME_MONODROID_NETLINK { "monodroid-netlink" }; - static constexpr std::string_view LOG_CATEGORY_NAME_ERROR { "*error*" }; - }; -} diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Resources/NativeLogging/log-functions-tests.cc b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Resources/NativeLogging/log-functions-tests.cc deleted file mode 100644 index a39da50ac20..00000000000 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Resources/NativeLogging/log-functions-tests.cc +++ /dev/null @@ -1,83 +0,0 @@ -#include -#include -#include - -#include -#include - -namespace { - constexpr size_t MESSAGE_SIZE = 256; - - char last_message [MESSAGE_SIZE]; - int last_priority; - int log_count; - - auto fail (const char *message) noexcept -> int - { - std::fprintf (stderr, "%s\n", message); - return 1; - } - - void reset_log () noexcept - { - std::memset (last_message, 0, sizeof (last_message)); - last_priority = -1; - log_count = 0; - } -} - -extern "C" int -__android_log_write (int priority, const char*, const char *text) -{ - last_priority = priority; - log_count++; - return std::snprintf (last_message, sizeof (last_message), "%s", text); -} - -extern "C" int -__android_log_vprint (int priority, const char*, const char *format, va_list args) -{ - last_priority = priority; - log_count++; - return std::vsnprintf (last_message, sizeof (last_message), format, args); -} - -int -main () -{ - using namespace xamarin::android; - - const char *text = "forwarded"; - const void *pointer = &log_count; - char expected [MESSAGE_SIZE]; - std::snprintf (expected, sizeof (expected), "%s %p %d", text, pointer, -42); - - reset_log (); - log_writef (LOG_ASSEMBLY, LogLevel::Info, "%s %p %d", text, pointer, -42); - if (log_count != 1 || last_priority != ANDROID_LOG_INFO || std::strcmp (last_message, expected) != 0) { - return fail ("printf arguments were not forwarded to the Android logging API"); - } - - log_categories = LOG_NONE; - reset_log (); - log_debugf (LOG_ASSEMBLY, "%s", "disabled debug"); - log_infof (LOG_ASSEMBLY, "%s", "disabled info"); - if (log_count != 0 || last_message[0] != '\0') { - return fail ("disabled debug or info logging invoked formatting"); - } - - log_categories = LOG_ASSEMBLY; - reset_log (); - log_debugf (LOG_ASSEMBLY, "%d", 7); - if (log_count != 1 || last_priority != ANDROID_LOG_DEBUG || std::strcmp (last_message, "7") != 0) { - return fail ("enabled debug logging did not format its message"); - } - - reset_log (); - log_infof (LOG_ASSEMBLY, "%s", "enabled info"); - if (log_count != 1 || last_priority != ANDROID_LOG_INFO || std::strcmp (last_message, "enabled info") != 0) { - return fail ("enabled info logging did not format its message"); - } - - return 0; -} From 85b56174e00c30c39675a985e13693bb4cccbdf8 Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Fri, 17 Jul 2026 12:51:11 +0200 Subject: [PATCH 06/10] [CoreCLR/NativeAOT] Migrate timing logging to printf Use #12140 printf helpers for shared timing diagnostics while preserving MonoVM std::format behavior. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 70f63eb7-6599-414c-a947-d860705aa0fa --- .../include/runtime-base/timing-internal.hh | 20 +++++++++++++++++++ .../common/include/runtime-base/timing.hh | 12 +++++++++++ 2 files changed, 32 insertions(+) diff --git a/src/native/common/include/runtime-base/timing-internal.hh b/src/native/common/include/runtime-base/timing-internal.hh index 5bd95f11af4..cb7847ceec1 100644 --- a/src/native/common/include/runtime-base/timing-internal.hh +++ b/src/native/common/include/runtime-base/timing-internal.hh @@ -260,7 +260,11 @@ namespace xamarin::android { // likely we'll run out of memory way, way, way before that happens size_t old_size = events.capacity (); events.reserve (old_size << 1); +#if defined(XA_HOST_MONOVM) log_warn (LOG_TIMING, "Reallocated timing event buffer from {} to {}"sv, old_size, events.size ()); +#else + log_warnf (LOG_TIMING, "Reallocated timing event buffer from %zu to %zu", old_size, events.size ()); +#endif } } @@ -378,7 +382,11 @@ namespace xamarin::android { { struct timespec t; if (clock_gettime (CLOCK_MONOTONIC_RAW, &t) != 0) [[unlikely]] { +#if defined(XA_HOST_MONOVM) log_warn (LOG_TIMING, "clock_gettime failed for CLOCK_MONOTONIC_RAW: {}"sv, optional_string (strerror (errno))); +#else + log_warnf (LOG_TIMING, "clock_gettime failed for CLOCK_MONOTONIC_RAW: %s", optional_string (strerror (errno))); +#endif return {}; // Results will be nonsensical, but no point in aborting the app } return time_point (chrono::seconds (t.tv_sec) + chrono::nanoseconds (t.tv_nsec)); @@ -494,11 +502,19 @@ namespace xamarin::android { return; } +#if defined(XA_HOST_MONOVM) log_warn ( LOG_TIMING, "Unknown event kind '{}' logged"sv, static_cast>(kind) ); +#else + log_warnf ( + LOG_TIMING, + "Unknown event kind '%u' logged", + static_cast(kind) + ); +#endif append_desc ("unknown event kind"sv); } @@ -510,7 +526,11 @@ namespace xamarin::android { auto is_valid_event_index (size_t index, std::source_location sloc = std::source_location::current ()) const noexcept -> bool { if (index >= events.capacity ()) [[unlikely]] { +#if defined(XA_HOST_MONOVM) log_warn (LOG_TIMING, "Invalid event index passed to method '{}'"sv, sloc.function_name ()); +#else + log_warnf (LOG_TIMING, "Invalid event index passed to method '%s'", optional_string (sloc.function_name ())); +#endif return false; } diff --git a/src/native/common/include/runtime-base/timing.hh b/src/native/common/include/runtime-base/timing.hh index 6e883c240f8..a26ecd629bd 100644 --- a/src/native/common/include/runtime-base/timing.hh +++ b/src/native/common/include/runtime-base/timing.hh @@ -85,6 +85,7 @@ namespace xamarin::android using namespace std::literals; auto interval = seq->end - seq->start; // nanoseconds +#if defined(XA_HOST_MONOVM) auto text = std::format ( "{}; elapsed: {}:{}::{}"sv, message == nullptr ? ""sv : message, @@ -94,6 +95,17 @@ namespace xamarin::android ); log_write (LOG_TIMING, level, text.c_str ()); +#else + log_writef ( + LOG_TIMING, + level, + "%s; elapsed: %llu:%llu::%llu", + optional_string (message, ""), + static_cast(std::chrono::duration_cast(interval).count ()), + static_cast(std::chrono::duration_cast(interval).count ()), + static_cast((interval % 1ms).count ()) + ); +#endif } private: From 5776138b983393c40e5bd30a6c530be35c9b300b Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Fri, 17 Jul 2026 12:59:05 +0200 Subject: [PATCH 07/10] [Native] Report timing buffer capacity after reserve Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 70f63eb7-6599-414c-a947-d860705aa0fa --- src/native/common/include/runtime-base/timing-internal.hh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/native/common/include/runtime-base/timing-internal.hh b/src/native/common/include/runtime-base/timing-internal.hh index cb7847ceec1..3dab3b3048f 100644 --- a/src/native/common/include/runtime-base/timing-internal.hh +++ b/src/native/common/include/runtime-base/timing-internal.hh @@ -261,9 +261,9 @@ namespace xamarin::android { size_t old_size = events.capacity (); events.reserve (old_size << 1); #if defined(XA_HOST_MONOVM) - log_warn (LOG_TIMING, "Reallocated timing event buffer from {} to {}"sv, old_size, events.size ()); + log_warn (LOG_TIMING, "Reallocated timing event buffer from {} to {}"sv, old_size, events.capacity ()); #else - log_warnf (LOG_TIMING, "Reallocated timing event buffer from %zu to %zu", old_size, events.size ()); + log_warnf (LOG_TIMING, "Reallocated timing event buffer from %zu to %zu", old_size, events.capacity ()); #endif } } From a1bf4b7511b762a97174ecc1a83c14db409e00b4 Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Fri, 17 Jul 2026 13:45:40 +0200 Subject: [PATCH 08/10] Address printf logging review feedback Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 70f63eb7-6599-414c-a947-d860705aa0fa --- src/native/clr/include/shared/log_types.hh | 1 - src/native/clr/shared/helpers.cc | 1 + src/native/clr/shared/log_functions.cc | 8 -------- 3 files changed, 1 insertion(+), 9 deletions(-) diff --git a/src/native/clr/include/shared/log_types.hh b/src/native/clr/include/shared/log_types.hh index 8e8d0811e45..b87347ad0a1 100644 --- a/src/native/clr/include/shared/log_types.hh +++ b/src/native/clr/include/shared/log_types.hh @@ -54,7 +54,6 @@ namespace xamarin::android { void log_infof (LogCategories category, const char *format, ...) noexcept __attribute__ ((format (printf, 2, 3))); void log_warnf (LogCategories category, const char *format, ...) noexcept __attribute__ ((format (printf, 2, 3))); void log_errorf (LogCategories category, const char *format, ...) noexcept __attribute__ ((format (printf, 2, 3))); - void log_fatalf (LogCategories category, const char *format, ...) noexcept __attribute__ ((format (printf, 2, 3))); [[gnu::always_inline]] static inline void log_write (LogCategories category, LogLevel level, std::string_view const& message) noexcept diff --git a/src/native/clr/shared/helpers.cc b/src/native/clr/shared/helpers.cc index da509e5fb73..5c95342b1af 100644 --- a/src/native/clr/shared/helpers.cc +++ b/src/native/clr/shared/helpers.cc @@ -1,4 +1,5 @@ #include +#include #include #include diff --git a/src/native/clr/shared/log_functions.cc b/src/native/clr/shared/log_functions.cc index 0c097edaed7..2f3db78abe7 100644 --- a/src/native/clr/shared/log_functions.cc +++ b/src/native/clr/shared/log_functions.cc @@ -191,12 +191,4 @@ namespace xamarin::android { va_end (args); } - void - log_fatalf (LogCategories category, const char *format, ...) noexcept - { - va_list args; - va_start (args, format); - log_writev (category, LogLevel::Fatal, format, args); - va_end (args); - } } From 09f0690daaec89f8a061b4bb3fee5ed58e150893 Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Fri, 17 Jul 2026 14:00:38 +0200 Subject: [PATCH 09/10] Support printf logging helpers on MonoVM Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 70f63eb7-6599-414c-a947-d860705aa0fa --- src/native/mono/shared/log_functions.cc | 77 ++++++++++++++++++++++--- src/native/mono/shared/log_types.hh | 7 +++ 2 files changed, 77 insertions(+), 7 deletions(-) diff --git a/src/native/mono/shared/log_functions.cc b/src/native/mono/shared/log_functions.cc index 394ba2a1927..16e8eda7525 100644 --- a/src/native/mono/shared/log_functions.cc +++ b/src/native/mono/shared/log_functions.cc @@ -1,4 +1,5 @@ #include +#include #include #include @@ -102,19 +103,81 @@ static constexpr android_LogPriority loglevel_map[] = { static constexpr size_t loglevel_map_max_index = (sizeof(loglevel_map) / sizeof(android_LogPriority)) - 1; +static auto +priority_for_level (xamarin::android::LogLevel level) noexcept -> android_LogPriority +{ + size_t map_index = static_cast(level); + if (map_index > loglevel_map_max_index) { + return DEFAULT_PRIORITY; + } + + return loglevel_map[map_index]; +} + namespace xamarin::android { void log_write (LogCategories category, LogLevel level, const char *message) noexcept { - size_t map_index = static_cast(level); - android_LogPriority priority; + __android_log_write (priority_for_level (level), CATEGORY_NAME (category), message); + } + + void + log_writev (LogCategories category, LogLevel level, const char *format, va_list args) noexcept + { + const char *safe_format = format == nullptr ? "" : format; + __android_log_vprint (priority_for_level (level), CATEGORY_NAME (category), safe_format, args); + } + + void + log_writef (LogCategories category, LogLevel level, const char *format, ...) noexcept + { + va_list args; + va_start (args, format); + log_writev (category, level, format, args); + va_end (args); + } + + void + log_debugf (LogCategories category, const char *format, ...) noexcept + { + if ((log_categories & category) == 0) { + return; + } + + va_list args; + va_start (args, format); + log_writev (category, LogLevel::Debug, format, args); + va_end (args); + } - if (map_index > loglevel_map_max_index) { - priority = DEFAULT_PRIORITY; - } else { - priority = loglevel_map[map_index]; + void + log_infof (LogCategories category, const char *format, ...) noexcept + { + if ((log_categories & category) == 0) { + return; } - __android_log_write (priority, CATEGORY_NAME (category), message); + va_list args; + va_start (args, format); + log_writev (category, LogLevel::Info, format, args); + va_end (args); + } + + void + log_warnf (LogCategories category, const char *format, ...) noexcept + { + va_list args; + va_start (args, format); + log_writev (category, LogLevel::Warn, format, args); + va_end (args); + } + + void + log_errorf (LogCategories category, const char *format, ...) noexcept + { + va_list args; + va_start (args, format); + log_writev (category, LogLevel::Error, format, args); + va_end (args); } } diff --git a/src/native/mono/shared/log_types.hh b/src/native/mono/shared/log_types.hh index 0821ef59077..e476b82b1cd 100644 --- a/src/native/mono/shared/log_types.hh +++ b/src/native/mono/shared/log_types.hh @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -47,6 +48,12 @@ namespace xamarin::android { // A slightly faster alternative to other log functions as it doesn't parse the message // for format placeholders nor it uses variable arguments void log_write (LogCategories category, LogLevel level, const char *message) noexcept; + void log_writev (LogCategories category, LogLevel level, const char *format, va_list args) noexcept; + void log_writef (LogCategories category, LogLevel level, const char *format, ...) noexcept __attribute__ ((format (printf, 3, 4))); + void log_debugf (LogCategories category, const char *format, ...) noexcept __attribute__ ((format (printf, 2, 3))); + void log_infof (LogCategories category, const char *format, ...) noexcept __attribute__ ((format (printf, 2, 3))); + void log_warnf (LogCategories category, const char *format, ...) noexcept __attribute__ ((format (printf, 2, 3))); + void log_errorf (LogCategories category, const char *format, ...) noexcept __attribute__ ((format (printf, 2, 3))); [[gnu::always_inline]] static inline void log_write (LogCategories category, LogLevel level, std::string_view const& message) noexcept From 21ff66b26ef2b7f9b994740739b2a50ec7d2b263 Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Fri, 17 Jul 2026 14:07:39 +0200 Subject: [PATCH 10/10] Use printf timing logging on MonoVM Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 70f63eb7-6599-414c-a947-d860705aa0fa --- .../include/runtime-base/timing-internal.hh | 20 ------------------- .../common/include/runtime-base/timing.hh | 12 ----------- 2 files changed, 32 deletions(-) diff --git a/src/native/common/include/runtime-base/timing-internal.hh b/src/native/common/include/runtime-base/timing-internal.hh index 3dab3b3048f..25fdc20b3cd 100644 --- a/src/native/common/include/runtime-base/timing-internal.hh +++ b/src/native/common/include/runtime-base/timing-internal.hh @@ -260,11 +260,7 @@ namespace xamarin::android { // likely we'll run out of memory way, way, way before that happens size_t old_size = events.capacity (); events.reserve (old_size << 1); -#if defined(XA_HOST_MONOVM) - log_warn (LOG_TIMING, "Reallocated timing event buffer from {} to {}"sv, old_size, events.capacity ()); -#else log_warnf (LOG_TIMING, "Reallocated timing event buffer from %zu to %zu", old_size, events.capacity ()); -#endif } } @@ -382,11 +378,7 @@ namespace xamarin::android { { struct timespec t; if (clock_gettime (CLOCK_MONOTONIC_RAW, &t) != 0) [[unlikely]] { -#if defined(XA_HOST_MONOVM) - log_warn (LOG_TIMING, "clock_gettime failed for CLOCK_MONOTONIC_RAW: {}"sv, optional_string (strerror (errno))); -#else log_warnf (LOG_TIMING, "clock_gettime failed for CLOCK_MONOTONIC_RAW: %s", optional_string (strerror (errno))); -#endif return {}; // Results will be nonsensical, but no point in aborting the app } return time_point (chrono::seconds (t.tv_sec) + chrono::nanoseconds (t.tv_nsec)); @@ -502,19 +494,11 @@ namespace xamarin::android { return; } -#if defined(XA_HOST_MONOVM) - log_warn ( - LOG_TIMING, - "Unknown event kind '{}' logged"sv, - static_cast>(kind) - ); -#else log_warnf ( LOG_TIMING, "Unknown event kind '%u' logged", static_cast(kind) ); -#endif append_desc ("unknown event kind"sv); } @@ -526,11 +510,7 @@ namespace xamarin::android { auto is_valid_event_index (size_t index, std::source_location sloc = std::source_location::current ()) const noexcept -> bool { if (index >= events.capacity ()) [[unlikely]] { -#if defined(XA_HOST_MONOVM) - log_warn (LOG_TIMING, "Invalid event index passed to method '{}'"sv, sloc.function_name ()); -#else log_warnf (LOG_TIMING, "Invalid event index passed to method '%s'", optional_string (sloc.function_name ())); -#endif return false; } diff --git a/src/native/common/include/runtime-base/timing.hh b/src/native/common/include/runtime-base/timing.hh index a26ecd629bd..944acbc2049 100644 --- a/src/native/common/include/runtime-base/timing.hh +++ b/src/native/common/include/runtime-base/timing.hh @@ -85,17 +85,6 @@ namespace xamarin::android using namespace std::literals; auto interval = seq->end - seq->start; // nanoseconds -#if defined(XA_HOST_MONOVM) - auto text = std::format ( - "{}; elapsed: {}:{}::{}"sv, - message == nullptr ? ""sv : message, - static_cast((std::chrono::duration_cast(interval).count ())), - static_cast((std::chrono::duration_cast(interval)).count ()), - static_cast((interval % 1ms).count ()) - ); - - log_write (LOG_TIMING, level, text.c_str ()); -#else log_writef ( LOG_TIMING, level, @@ -105,7 +94,6 @@ namespace xamarin::android static_cast(std::chrono::duration_cast(interval).count ()), static_cast((interval % 1ms).count ()) ); -#endif } private: