From d8aaffe4a831961e43c23107189b4add8af4287d Mon Sep 17 00:00:00 2001 From: Balazs Scheidler Date: Sun, 12 May 2024 19:52:44 +0200 Subject: [PATCH] timeutils: cache mktime() return values for an hour Without this patch, cached_mktime() produces a cached value for every second, and would call mktime() again as the second changes. This can be problematic when timestamps are parsed from different seconds in succession (e.g. when the syslog timestamp is 1 second away from a timestamp being parsed using strptime or date-parser). In these cases the cache was invalidated and mktime() was called again. mktime() is _slow_ as it even calls tzset() and validates that /etc/localtime is still pointing to the same timezone. This patch changes the caching strategy: - instead of specific seconds we calculate the cached value for the top of the hour (e.g. minutes==seconds==0) - timezones never change within the same hour, so as long as we are trying to use the cached value, we will do so as long as the year/month/day/hour value matches (plus isdst) - with this, mktime() will be called once every hour. If there's some fluctuation between subsequent timestamps at the turn of the hour, we can still exhibit limited caching (e.g. subsequent timestamps from the last hour and this one), but this is a lot better than the existing behaviour where we do that for every second. Signed-off-by: Balazs Scheidler --- lib/timeutils/cache.c | 37 ++++++++++++++++++++++++------------- 1 file changed, 24 insertions(+), 13 deletions(-) diff --git a/lib/timeutils/cache.c b/lib/timeutils/cache.c index 9b7a3ce4ca..187db84cb2 100644 --- a/lib/timeutils/cache.c +++ b/lib/timeutils/cache.c @@ -305,31 +305,42 @@ get_cached_realtime_sec(void) return (time_t) now.tv_sec; } +static time_t +_calculate_and_adjust_mktime_result_based_on_cache(struct tm *tm) +{ + /* our cached value is valid for 1 hour, as we cache the min==sec==0 value + * for every second */ + int sec = tm->tm_sec; + int min = tm->tm_min; + *tm = cache.mktime.mutated_key; + tm->tm_sec = sec; + tm->tm_min = min; + return cache.mktime.value + tm->tm_min * 60 + tm->tm_sec; +} + time_t cached_mktime(struct tm *tm) { _validate_timeutils_cache(); - if (G_LIKELY(tm->tm_sec == cache.mktime.key.tm_sec && - tm->tm_min == cache.mktime.key.tm_min && - tm->tm_hour == cache.mktime.key.tm_hour && + if (G_LIKELY(tm->tm_hour == cache.mktime.key.tm_hour && tm->tm_mday == cache.mktime.key.tm_mday && tm->tm_mon == cache.mktime.key.tm_mon && tm->tm_year == cache.mktime.key.tm_year && tm->tm_isdst == cache.mktime.key.tm_isdst)) { - *tm = cache.mktime.mutated_key; - return cache.mktime.value; + return _calculate_and_adjust_mktime_result_based_on_cache(tm); } - /* we need to store the incoming value first, as mktime() might change the - * fields in *tm, for instance in the daylight saving transition hour */ - cache.mktime.key = *tm; - cache.mktime.value = mktime(tm); + /* we need to store both the incoming value (key) and the one mutated by + * mktime(), as mktime() might change the fields in *tm for instance in + * the daylight saving transition hour */ + cache.mktime.key = cache.mktime.mutated_key = *tm; - /* the result we yield consists of both the return value and the mutated - * key, so we need to save both */ - cache.mktime.mutated_key = *tm; - return cache.mktime.value; + /* let's cache the top of the hour */ + cache.mktime.mutated_key.tm_sec = 0; + cache.mktime.mutated_key.tm_min = 0; + cache.mktime.value = mktime(&cache.mktime.mutated_key); + return _calculate_and_adjust_mktime_result_based_on_cache(tm); } void