Skip to content

Commit

Permalink
timeutils: cache mktime() return values for an hour
Browse files Browse the repository at this point in the history
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 <balazs.scheidler@axoflow.com>
  • Loading branch information
bazsi committed May 17, 2024
1 parent fa3ab9f commit d8aaffe
Showing 1 changed file with 24 additions and 13 deletions.
37 changes: 24 additions & 13 deletions lib/timeutils/cache.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit d8aaffe

Please sign in to comment.