diff --git a/src/native/clr/host/assembly-store.cc b/src/native/clr/host/assembly-store.cc index 2586b6c6af9..d0ea1684845 100644 --- a/src/native/clr/host/assembly-store.cc +++ b/src/native/clr/host/assembly-store.cc @@ -791,7 +791,7 @@ auto AssemblyStore::open_assembly (std::string_view const& name, int64_t &size) return assembly_data; } -void AssemblyStore::configure_from_payload (const void *payload_start, const std::function& get_full_store_path) noexcept +void AssemblyStore::configure_from_payload (const void *payload_start, const char *store_path) noexcept { auto header = static_cast(payload_start); @@ -800,7 +800,7 @@ void AssemblyStore::configure_from_payload (const void *payload_start, const std LOG_ASSEMBLY, std::source_location::current (), "Assembly store '%s' is not a valid .NET for Android assembly store file", - get_full_store_path ().c_str () + optional_string (store_path) ); } @@ -809,7 +809,7 @@ void AssemblyStore::configure_from_payload (const void *payload_start, const std LOG_ASSEMBLY, std::source_location::current (), "Assembly store '%s' uses format version %x, instead of the expected %x", - get_full_store_path ().c_str (), + optional_string (store_path), header->version, ASSEMBLY_STORE_FORMAT_VERSION ); @@ -841,5 +841,5 @@ void AssemblyStore::configure_from_payload (const void *payload_start, const std names_cursor += name_length; } - log_debugf (LOG_ASSEMBLY, "Mapped assembly store %s; content ID 0x%" PRIx64, get_full_store_path ().c_str (), assembly_store_content_id); + log_debugf (LOG_ASSEMBLY, "Mapped assembly store %s; content ID 0x%" PRIx64, optional_string (store_path), assembly_store_content_id); } diff --git a/src/native/clr/host/host.cc b/src/native/clr/host/host.cc index a7d12ed8ab8..e0abcbde019 100644 --- a/src/native/clr/host/host.cc +++ b/src/native/clr/host/host.cc @@ -197,7 +197,7 @@ void Host::map_assembly_store_via_dlopen (const char *store_path) noexcept } log_debugf (LOG_ASSEMBLY, "Assembly store payload via dynamic symbol: %p (%s)", payload, optional_string (store_path)); - AssemblyStore::configure_from_payload (payload, [store_path]() -> std::string { return std::string { store_path }; }); + AssemblyStore::configure_from_payload (payload, store_path); found_assembly_store = true; } @@ -316,7 +316,6 @@ void Host::Java_mono_android_Runtime_initInternal ( FastTiming::initialize ((Logger::log_timing_categories() & LogTimingCategories::FastBare) != LogTimingCategories::FastBare); if (FastTiming::enabled ()) [[unlikely]] { - _timing = std::make_shared (); internal_timing.start_event (TimingEventKind::TotalRuntimeInit); } diff --git a/src/native/clr/host/internal-pinvokes-clr.cc b/src/native/clr/host/internal-pinvokes-clr.cc index 844f9b748f0..7c978f9ee08 100644 --- a/src/native/clr/host/internal-pinvokes-clr.cc +++ b/src/native/clr/host/internal-pinvokes-clr.cc @@ -41,15 +41,11 @@ _monodroid_lookup_replacement_method_info (const char *jniSourceType, const char managed_timing_sequence* monodroid_timing_start (const char *message) { - // Technically a reference here is against the idea of shared pointers, but - // in this instance it's fine since we know we won't be storing the pointer - // and this way things are slightly faster. - std::shared_ptr const &timing = Host::get_timing (); - if (!timing) { + if (!FastTiming::enabled ()) [[likely]] { return nullptr; } - managed_timing_sequence *ret = timing->get_available_sequence (); + managed_timing_sequence *ret = Host::get_timing ().get_available_sequence (); if (message != nullptr) { log_write (LOG_TIMING, LogLevel::Info, message); } @@ -64,12 +60,7 @@ void monodroid_timing_stop (managed_timing_sequence *sequence, const char *messa return; } - std::shared_ptr const &timing = Host::get_timing (); - if (!timing) [[unlikely]] { - return; - } - sequence->end = FastTiming::get_time (); Timing::info (sequence, message == nullptr ? DEFAULT_MESSAGE.data () : message); - timing->release_sequence (sequence); + Host::get_timing ().release_sequence (sequence); } diff --git a/src/native/clr/include/host/assembly-store.hh b/src/native/clr/include/host/assembly-store.hh index 99b6dfce2f4..811a2f4be0a 100644 --- a/src/native/clr/include/host/assembly-store.hh +++ b/src/native/clr/include/host/assembly-store.hh @@ -1,7 +1,6 @@ #pragma once #include -#include #include #include #include @@ -19,9 +18,9 @@ namespace xamarin::android { // Configure the store directly from an in-memory payload pointer (obtained via // dlopen()+dlsym() of the `_assembly_store` dynamic symbol). The payload is mapped // read-only and is never modified, so it (and every pointer derived from it) is `const`. - // `get_full_store_path` is invoked only to build diagnostics if the payload turns out - // to be invalid. - static void configure_from_payload (const void *payload_start, const std::function& get_full_store_path) noexcept; + // `store_path` is used only in diagnostic messages and may be `nullptr` - every use of it + // goes through `optional_string ()`. + static void configure_from_payload (const void *payload_start, const char *store_path) noexcept; private: static void set_assembly_data_and_size (uint8_t* source_assembly_data, uint32_t source_assembly_data_size, uint8_t*& dest_assembly_data, uint32_t& dest_assembly_data_size) noexcept; diff --git a/src/native/clr/include/host/host.hh b/src/native/clr/include/host/host.hh index a492ecf21b6..c9cd3f38523 100644 --- a/src/native/clr/include/host/host.hh +++ b/src/native/clr/include/host/host.hh @@ -23,7 +23,7 @@ namespace xamarin::android { static void Java_mono_android_Runtime_registerNatives (JNIEnv *env, jclass nativeClass) noexcept; static void propagate_uncaught_exception (JNIEnv *env, jobject javaThread, jthrowable javaException) noexcept; - static auto get_timing () -> std::shared_ptr + static auto get_timing () noexcept -> Timing& { return _timing; } @@ -54,7 +54,10 @@ namespace xamarin::android { private: static inline void *clr_host = nullptr; static inline unsigned int domain_id = 0; - static inline std::shared_ptr _timing{}; + // Constant-initialized and live for the whole lifetime of the process, so there is + // nothing to allocate or free. Only used when fast timing is enabled, which callers + // check with `FastTiming::enabled ()`. + static inline Timing _timing {}; static inline bool found_assembly_store = false; static inline jnienv_register_jni_natives_fn jnienv_register_jni_natives = nullptr; static inline jnienv_propagate_uncaught_exception_fn jnienv_propagate_uncaught_exception = nullptr; diff --git a/src/native/common/include/runtime-base/mainthread-dso-loader.hh b/src/native/common/include/runtime-base/mainthread-dso-loader.hh index e1c16c347e6..eaa7c12526d 100644 --- a/src/native/common/include/runtime-base/mainthread-dso-loader.hh +++ b/src/native/common/include/runtime-base/mainthread-dso-loader.hh @@ -5,7 +5,6 @@ #include #include -#include #include #include diff --git a/src/native/common/include/runtime-base/timing-internal.hh b/src/native/common/include/runtime-base/timing-internal.hh index a02f23992df..93e409860fb 100644 --- a/src/native/common/include/runtime-base/timing-internal.hh +++ b/src/native/common/include/runtime-base/timing-internal.hh @@ -2,15 +2,11 @@ #include #include -#include #include #include +#include #include -#include #include -#include -#include -#include #include #include @@ -27,12 +23,34 @@ using namespace xamarin::android::internal; #include #include #include +#include #include namespace xamarin::android { - namespace chrono = std::chrono; - - using time_point = chrono::time_point; + inline constexpr uint64_t NANOSECONDS_PER_MILLISECOND = 1000000ull; + inline constexpr uint64_t NANOSECONDS_PER_SECOND = 1000000000ull; + + // A monotonic point in time, or an interval between two such points, in nanoseconds. + using time_point = uint64_t; + + // Splits an interval into the components used by the timing output format. Note that `seconds` + // and `milliseconds` are both totals for the *entire* interval rather than a breakdown of it: + // an interval of 1.5s has `seconds == 1` and `milliseconds == 1500`, and both are printed. This + // is what `duration_cast` and `duration_cast` used to return and it must + // not be "corrected" to milliseconds-within-the-second, because the output format is consumed by + // performance measuring utilities. Only `nanoseconds` is a remainder, within the last millisecond. + struct time_interval + { + unsigned long long seconds; + unsigned long long milliseconds; + unsigned long long nanoseconds; + + explicit constexpr time_interval (time_point interval) noexcept + : seconds { interval / NANOSECONDS_PER_SECOND }, + milliseconds { interval / NANOSECONDS_PER_MILLISECOND }, + nanoseconds { interval % NANOSECONDS_PER_MILLISECOND } + {} + }; // Events should never change their assigned values and no values should be reused. // Values are used by the test runner to determine what measurement was taken. @@ -64,7 +82,7 @@ namespace xamarin::android { time_point start; time_point end; TimingEventKind kind; - std::string *more_info = nullptr; + char *more_info = nullptr; bool complete = false; }; @@ -84,6 +102,14 @@ namespace xamarin::android { TimingEventChunk *next = nullptr; }; + // A single entry on the per-thread stack of timing events which have been started but + // not yet ended. + struct OpenSequence + { + TimingEvent *event; + OpenSequence *next; + }; + // defaults static constexpr bool default_fast_timing_enabled = false; static constexpr bool default_log_to_file = false; @@ -95,10 +121,13 @@ namespace xamarin::android { static constexpr std::string_view OPT_FILE_NAME { "filename=" }; static constexpr std::string_view OPT_TO_FILE { "to-file" }; + // Enough to hold any value the `debug.mono.timing` property can carry, `PROP_VALUE_MAX` is 92. + static constexpr size_t MAX_TIMING_FILE_NAME_SIZE = 128uz; + protected: void configure_for_use () noexcept { - first_event_chunk = new TimingEventChunk; + first_event_chunk = allocate_event_chunk (); } public: @@ -111,9 +140,9 @@ namespace xamarin::android { while (chunk != nullptr) { TimingEventChunk *next = chunk->next; for (TimingEvent &event : chunk->events) { - delete event.more_info; + std::free (event.more_info); } - delete chunk; + std::free (chunk); chunk = next; } } @@ -189,15 +218,13 @@ namespace xamarin::android { [[gnu::always_inline]] static auto event_duration_ns (TimingEvent const& event) noexcept -> uint64_t { - return static_cast((event.end - event.start).count ()); + return event.end - event.start; } // Returns the message length excluding NUL, or the negative required capacity including NUL. static auto format_message (TimingEvent const& event, char *buffer, size_t buffer_size, bool indent) noexcept -> ssize_t { - using namespace std::literals; - - auto interval = event.end - event.start; // nanoseconds + time_interval interval { event.end - event.start }; int length = snprintf ( buffer, buffer_size, @@ -206,10 +233,10 @@ namespace xamarin::android { event.before_managed ? "[0/" : "[1/", static_cast(event.kind), event_kind_description (event.kind), - event.more_info == nullptr ? "" : event.more_info->c_str (), - static_cast(chrono::duration_cast(interval).count ()), - static_cast(chrono::duration_cast(interval).count ()), - static_cast((interval % 1ms).count ()) + event.more_info == nullptr ? "" : event.more_info, + interval.seconds, + interval.milliseconds, + interval.nanoseconds ); if (length < 0) { if (buffer != nullptr && buffer_size > 0uz) { @@ -267,7 +294,7 @@ namespace xamarin::android { return; } - if (skip_log_if_more_info_missing && (event.more_info == nullptr || event.more_info->empty ())) { + if (skip_log_if_more_info_missing && (event.more_info == nullptr || event.more_info[0] == '\0')) { return; } @@ -282,7 +309,7 @@ namespace xamarin::android { ev.start = get_time (); ev.kind = kind; ev.before_managed = MonodroidState::is_startup_in_progress (); - open_sequences.push (&ev); + push_sequence_event (&ev); } // If `uses_more_info` is `true`, the caller **MUST** call `add_more_info`, since the @@ -308,7 +335,7 @@ namespace xamarin::android { [[gnu::always_inline]] void add_more_info (const char *str, size_t length) noexcept { - store_more_info (new std::string (str, length)); + store_more_info (duplicate_more_info (std::string_view { str, length }, {})); } // Builds the message from two parts, so that its exact length is known up front and the @@ -316,9 +343,7 @@ namespace xamarin::android { [[gnu::always_inline]] void add_more_info (std::string_view const& first, std::string_view const& second) noexcept { - auto *more_info = new std::string (first.data (), first.length ()); - more_info->append (second); - store_more_info (more_info); + store_more_info (duplicate_more_info (first, second)); } [[gnu::always_inline]] @@ -375,26 +400,57 @@ namespace xamarin::android { static auto get_time () noexcept -> time_point { struct timespec t; - if (clock_gettime (CLOCK_MONOTONIC_RAW, &t) != 0) [[unlikely]] { - log_warnf (LOG_TIMING, "clock_gettime failed for CLOCK_MONOTONIC_RAW: %s", optional_string (strerror (errno))); + if (clock_gettime (CLOCK_MONOTONIC, &t) != 0) [[unlikely]] { + log_warnf (LOG_TIMING, "clock_gettime failed for CLOCK_MONOTONIC: %s", optional_string (strerror (errno))); 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)); + return (static_cast(t.tv_sec) * NANOSECONDS_PER_SECOND) + static_cast(t.tv_nsec); } private: + // Writes a single output line. `output` is the file passed to `dump`, or `nullptr` when + // the caller doesn't write to a file. + using LineWriter = void (*) (FILE *output, std::string_view const& line); + bool no_events_logged (size_t entries) noexcept; void dump_to_logcat (size_t entries) noexcept; void dump_to_file (size_t entries) noexcept; - void dump (size_t entries, bool indent, std::function line_writer) noexcept; + void dump (size_t entries, bool indent, LineWriter line_writer, FILE *output) noexcept; + + // Returns a NUL-terminated copy of `first` and `second` concatenated, or `nullptr` if it + // cannot be allocated. Timing is a diagnostic facility, so a failure here only costs us the + // extra information attached to a single event and must not bring the application down. + [[gnu::always_inline]] + static auto duplicate_more_info (std::string_view const& first, std::string_view const& second) noexcept -> char* + { + size_t length = Helpers::add_with_overflow_check (first.length (), second.length ()); + auto *more_info = static_cast (std::malloc (Helpers::add_with_overflow_check (length, 1uz))); + if (more_info == nullptr) [[unlikely]] { + return nullptr; + } + + // `memcpy` must not be called with a `nullptr` source, not even for a zero length, and an + // empty `std::string_view` is allowed to have a `nullptr` data pointer. + if (!first.empty ()) { + std::memcpy (more_info, first.data (), first.length ()); + } + + if (!second.empty ()) { + std::memcpy (more_info + first.length (), second.data (), second.length ()); + } + + more_info[length] = '\0'; + + return more_info; + } // Takes ownership of `more_info`. [[gnu::always_inline]] - void store_more_info (std::string *more_info) noexcept + void store_more_info (char *more_info) noexcept { TimingEvent *event = pop_sequence_event (); if (event == nullptr) [[unlikely]] { - delete more_info; + std::free (more_info); log_warnf (LOG_TIMING, "FastTiming::add_more_info called without prior FastTiming::start_event called"); return; } @@ -405,23 +461,41 @@ namespace xamarin::android { } [[gnu::always_inline]] - auto get_sequence_event () noexcept -> TimingEvent* + static void push_sequence_event (TimingEvent *event) noexcept { - if (open_sequences.empty ()) [[unlikely]] { + auto *entry = static_cast (std::malloc (sizeof (OpenSequence))); + if (entry == nullptr) [[unlikely]] { + Helpers::abort_application (LOG_TIMING, "Unable to allocate memory for an open timing sequence"); + } + + entry->event = event; + entry->next = open_sequences; + open_sequences = entry; + } + + [[gnu::always_inline]] + static auto get_sequence_event () noexcept -> TimingEvent* + { + OpenSequence *entry = open_sequences; + if (entry == nullptr) [[unlikely]] { return nullptr; } - return open_sequences.top (); + return entry->event; } [[gnu::always_inline]] - auto pop_sequence_event () noexcept -> TimingEvent* + static auto pop_sequence_event () noexcept -> TimingEvent* { - TimingEvent *event = get_sequence_event (); - if (event != nullptr) [[likely]] { - open_sequences.pop (); + OpenSequence *entry = open_sequences; + if (entry == nullptr) [[unlikely]] { + return nullptr; } + TimingEvent *event = entry->event; + open_sequences = entry->next; + std::free (entry); + return event; } @@ -487,6 +561,20 @@ namespace xamarin::android { } private: + // Event chunks are chained together and the events in them are handed out as references that + // stay valid until the process exits, so a chunk must never move. Allocating them with + // `calloc` avoids `operator new` and, with it, a dependency on `libc++`; zero-filling matches + // the default member initializers of `TimingEvent`. + static auto allocate_event_chunk () noexcept -> TimingEventChunk* + { + auto *chunk = static_cast (std::calloc (1uz, sizeof (TimingEventChunk))); + if (chunk == nullptr) [[unlikely]] { + Helpers::abort_application (LOG_TIMING, "Unable to allocate memory for timing events"); + } + + return chunk; + } + void parse_options (const char *options) noexcept; static void really_initialize (bool log_immediately) noexcept; @@ -504,7 +592,7 @@ namespace xamarin::android { for (size_t i = current_chunk_index; i < chunk_index; ++i) { TimingEventChunk *next = __atomic_load_n (&chunk->next, __ATOMIC_ACQUIRE); if (next == nullptr) [[unlikely]] { - TimingEventChunk *new_chunk = new TimingEventChunk; + TimingEventChunk *new_chunk = allocate_event_chunk (); if (__atomic_compare_exchange_n ( &chunk->next, &next, @@ -521,7 +609,7 @@ namespace xamarin::android { (i + 2uz) * EVENT_CHUNK_SIZE ); } else { - delete new_chunk; + std::free (new_chunk); } } chunk = next; @@ -535,9 +623,12 @@ namespace xamarin::android { private: std::atomic_size_t next_event_index = 0uz; TimingEventChunk *first_event_chunk = nullptr; - std::unique_ptr output_file_name{}; + // The name is read from the `debug.mono.timing` system property, whose whole value is limited + // to `PROP_VALUE_MAX` (92) bytes, so a fixed buffer is always large enough. Keeping it inline + // also keeps `FastTiming` constant-initialized, so the global instance needs no guard variable. + char output_file_name[MAX_TIMING_FILE_NAME_SIZE] = {}; - static inline thread_local std::stack open_sequences; + static inline thread_local OpenSequence *open_sequences = nullptr; static inline thread_local TimingEventChunk *cached_event_chunk = nullptr; static inline thread_local size_t cached_event_chunk_index = 0uz; static inline bool is_enabled = false; diff --git a/src/native/common/include/runtime-base/timing.hh b/src/native/common/include/runtime-base/timing.hh index 5decc37823f..5019ee09f9a 100644 --- a/src/native/common/include/runtime-base/timing.hh +++ b/src/native/common/include/runtime-base/timing.hh @@ -3,12 +3,13 @@ #include #include -#include -#include +#include #include #include +#include + #include "timing-internal.hh" namespace xamarin::android @@ -24,14 +25,7 @@ namespace xamarin::android // well, but the overhead it has (out of necessity) might not be desirable in native code. class Timing { - static constexpr size_t DEFAULT_POOL_SIZE = 16uz; - public: - explicit Timing (size_t initial_pool_size = DEFAULT_POOL_SIZE) noexcept - { - sequence_pool.resize (initial_pool_size); - } - static void info (managed_timing_sequence const *seq, const char *message) { do_log (LogLevel::Info, seq, message); @@ -46,19 +40,13 @@ namespace xamarin::android { pthread_mutex_lock (&sequence_lock); - managed_timing_sequence *ret; - for (size_t i = 0uz; i < sequence_pool.size (); i++) { - if (sequence_pool[i].in_use) { - continue; - } - - ret = &sequence_pool[i]; - ret->in_use = true; - - pthread_mutex_unlock (&sequence_lock); - return ret; + managed_timing_sequence *ret = find_unused_sequence (); + if (ret == nullptr) { + ret = allocate_chunk (); } - ret = &sequence_pool.emplace_back (); + + ret->start = 0; + ret->end = 0; ret->in_use = true; pthread_mutex_unlock (&sequence_lock); @@ -72,13 +60,52 @@ namespace xamarin::android } pthread_mutex_lock (&sequence_lock); - sequence->start = time_point::min (); - sequence->end = time_point::min (); sequence->in_use = false; pthread_mutex_unlock (&sequence_lock); } private: + // Sequences are handed out to managed code, which holds on to them until it stops the + // measurement, so they must never move. They are allocated in chunks that are chained + // together and never freed, and are recycled through `in_use`, so that every address + // handed out stays valid for the lifetime of the process. + static inline constexpr size_t SEQUENCE_CHUNK_SIZE = 16uz; + + struct sequence_chunk + { + sequence_chunk *next; + managed_timing_sequence sequences[SEQUENCE_CHUNK_SIZE]; + }; + + // Must be called with `sequence_lock` held. + auto find_unused_sequence () noexcept -> managed_timing_sequence* + { + for (sequence_chunk *chunk = sequence_chunks; chunk != nullptr; chunk = chunk->next) { + for (size_t i = 0uz; i < SEQUENCE_CHUNK_SIZE; i++) { + if (!chunk->sequences[i].in_use) { + return &chunk->sequences[i]; + } + } + } + + return nullptr; + } + + // Must be called with `sequence_lock` held. `calloc` clears `in_use` for every entry in + // the new chunk, so all of them start out available. + auto allocate_chunk () noexcept -> managed_timing_sequence* + { + auto *chunk = static_cast (std::calloc (1uz, sizeof (sequence_chunk))); + if (chunk == nullptr) [[unlikely]] { + Helpers::abort_application (LOG_TIMING, "Unable to allocate memory for timing sequences"); + } + + chunk->next = sequence_chunks; + sequence_chunks = chunk; + + return &chunk->sequences[0uz]; + } + [[gnu::always_inline]] static void do_log (LogLevel level, managed_timing_sequence const *seq, const char *message) { @@ -86,21 +113,20 @@ namespace xamarin::android return; } - using namespace std::literals; - auto interval = seq->end - seq->start; // nanoseconds + time_interval interval { seq->end - seq->start }; 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 ()) + interval.seconds, + interval.milliseconds, + interval.nanoseconds ); } private: - std::vector sequence_pool; - pthread_mutex_t sequence_lock = PTHREAD_MUTEX_INITIALIZER; + sequence_chunk *sequence_chunks = nullptr; + pthread_mutex_t sequence_lock = PTHREAD_MUTEX_INITIALIZER; }; } diff --git a/src/native/common/runtime-base/timing-internal.cc b/src/native/common/runtime-base/timing-internal.cc index ee64739753d..53ed4ccdbad 100644 --- a/src/native/common/runtime-base/timing-internal.cc +++ b/src/native/common/runtime-base/timing-internal.cc @@ -1,4 +1,3 @@ -#include #include #include @@ -12,7 +11,26 @@ namespace xamarin::android { using namespace xamarin::android; using namespace std::literals; -namespace chrono = std::chrono; +namespace { + void write_line_to_logcat ([[maybe_unused]] FILE *output, std::string_view const& line) noexcept + { + // Don't add empty messages to the logcat, waste of time + if (line.empty ()) { + return; + } + + log_writef (LOG_TIMING, LogLevel::Info, "%.*s", static_cast(line.length ()), line.data ()); + } + + void write_line_to_file (FILE *output, std::string_view const& line) noexcept + { + if (!line.empty ()) { + fwrite (line.data (), line.size (), 1, output); + } + + fwrite (Constants::NEWLINE.data (), Constants::NEWLINE.size (), 1, output); + } +} void FastTiming::really_initialize (bool log_immediately) noexcept { @@ -21,8 +39,8 @@ void FastTiming::really_initialize (bool log_immediately) noexcept // TLS variables are initialized on first use, do it here so that we can have // the overhead out of mind later, at least for the main thread. - open_sequences.push (0); - open_sequences.pop (); + push_sequence_event (nullptr); + pop_sequence_event (); // Options in `debug.mono.timing` are relevant only when immediate logging is disabled if (immediate_logging) { @@ -54,7 +72,14 @@ void FastTiming::parse_options (const char *options) noexcept if (param_length == OPT_TO_FILE.length () && strncmp (param, OPT_TO_FILE.data (), param_length) == 0) { log_to_file = true; } else if (param_length >= OPT_FILE_NAME.length () && strncmp (param, OPT_FILE_NAME.data (), OPT_FILE_NAME.length ()) == 0) { - output_file_name = std::make_unique (param + OPT_FILE_NAME.length (), param_length - OPT_FILE_NAME.length ()); + const char *name = param + OPT_FILE_NAME.length (); + size_t name_length = param_length - OPT_FILE_NAME.length (); + if (name_length >= sizeof (output_file_name)) [[unlikely]] { + log_warnf (LOG_TIMING, "Timing file name '%.*s' is too long, will use the default one", static_cast(name_length), name); + } else { + memcpy (output_file_name, name, name_length); + output_file_name[name_length] = '\0'; + } } else if (param_length >= OPT_DURATION.length () && strncmp (param, OPT_DURATION.data (), OPT_DURATION.length ()) == 0) { const char *duration = param + OPT_DURATION.length (); char *end; @@ -71,7 +96,7 @@ void FastTiming::parse_options (const char *options) noexcept param = separator == nullptr ? nullptr : separator + 1; } - if (output_file_name) { + if (output_file_name[0] != '\0') { log_to_file = true; } @@ -91,15 +116,15 @@ bool FastTiming::no_events_logged (size_t entries) noexcept return true; } -void FastTiming::dump (size_t entries, bool indent, std::function line_writer) noexcept +void FastTiming::dump (size_t entries, bool indent, LineWriter line_writer, FILE *output) noexcept { char stack_buffer [Constants::MAX_LOGCAT_MESSAGE_LENGTH]; - line_writer ("Startup costs:"sv); + line_writer (output, "Startup costs:"sv); auto log = [&] (TimingEvent const& event) -> uint64_t { size_t message_length; char *message = build_message (event, stack_buffer, sizeof (stack_buffer), &message_length, indent); - line_writer (std::string_view { message, message_length }); + line_writer (output, std::string_view { message, message_length }); if (message != stack_buffer) { std::free (message); } @@ -108,7 +133,7 @@ void FastTiming::dump (size_t entries, bool indent, std::function int { return snprintf ( buffer, buffer_size, - " %.*s: %lld:%lld::%lld", + " %.*s: %llu:%llu::%llu", static_cast(msg.length ()), msg.data (), - static_cast(chrono::duration_cast (time_ns).count ()), - static_cast(chrono::duration_cast (time_ns).count ()), - static_cast((time_ns % 1ms).count ()) + interval.seconds, + interval.milliseconds, + interval.nanoseconds ); }; @@ -186,7 +211,7 @@ void FastTiming::dump (size_t entries, bool indent, std::function(msg.length ()), msg.data ()); - }; - dump (entries, true /* indent */, line_writer); + dump (entries, true /* indent */, write_line_to_logcat, nullptr); } void FastTiming::dump_to_file (size_t entries) noexcept @@ -232,7 +250,7 @@ void FastTiming::dump_to_file (size_t entries) noexcept return; } - std::string_view file_name = output_file_name == nullptr ? default_timing_file_name : *output_file_name; + std::string_view file_name = output_file_name[0] == '\0' ? default_timing_file_name : std::string_view { output_file_name }; char stack_buffer [Util::LocalPathBufferSize]; char *timing_log_path = Util::join_paths (stack_buffer, sizeof (stack_buffer), temporary_directory, file_name); @@ -256,14 +274,7 @@ void FastTiming::dump_to_file (size_t entries) noexcept log_infof (LOG_TIMING, "[2/2] Performance measurement results logged to file: %s", timing_log_path); - auto line_writer = [=](std::string_view const& msg) { - if (!msg.empty ()) { - fwrite (msg.data (), msg.size (), 1, timing_log); - } - fwrite (Constants::NEWLINE.data (), Constants::NEWLINE.size (), 1, timing_log); - }; - - dump (entries, true /* indent */, line_writer); + dump (entries, true /* indent */, write_line_to_file, timing_log); fflush (timing_log); fclose (timing_log); if (timing_log_path != stack_buffer) { diff --git a/src/native/mono/monodroid/monodroid-glue.cc b/src/native/mono/monodroid/monodroid-glue.cc index ed29d2be6f9..4a5f9e38c40 100644 --- a/src/native/mono/monodroid/monodroid-glue.cc +++ b/src/native/mono/monodroid/monodroid-glue.cc @@ -116,15 +116,15 @@ MonodroidRuntime::log_jit_event (MonoMethod *method, const char *event_name) noe char* name = mono_method_full_name (method, 1); - auto interval = jit_time_end - jit_time_start; // nanoseconds + time_interval interval { jit_time_end - jit_time_start }; fprintf ( jit_log, "JIT method %6s: %s elapsed: %zus:%zu::%zu\n", event_name, name, - static_cast((chrono::duration_cast(interval).count ())), - static_cast((chrono::duration_cast(interval)).count ()), - static_cast((interval % 1ms).count ()) + static_cast(interval.seconds), + static_cast(interval.milliseconds), + static_cast(interval.nanoseconds) ); free (name);