return ltrim(s, c);
}
-/* This function converts the time_t value <now> into a broken out struct tm
- * which must be allocated by the caller. It is highly recommended to use this
- * function instead of localtime() because that one requires a time_t* which
- * is not always compatible with tv_sec depending on OS/hardware combinations.
- */
-static inline void get_localtime(const time_t now, struct tm *tm)
-{
- localtime_r(&now, tm);
-}
-
-/* This function converts the time_t value <now> into a broken out struct tm
- * which must be allocated by the caller. It is highly recommended to use this
- * function instead of gmtime() because that one requires a time_t* which
- * is not always compatible with tv_sec depending on OS/hardware combinations.
- */
-static inline void get_gmtime(const time_t now, struct tm *tm)
-{
- gmtime_r(&now, tm);
-}
+void get_localtime(const time_t now, struct tm *tm);
+void get_gmtime(const time_t now, struct tm *tm);
/* Counts a number of elapsed days since 01/01/0000 based solely on elapsed
* years and assuming the regular rule for leap years applies. It's fake but
return p - buf;
}
+/* cache of conversions from time_t to struct tm via localtime() and gmtime(). */
+static THREAD_LOCAL struct {
+ time_t sec;
+ struct tm tm;
+} localtime_cache[TIME_CACHE_SLOTS], gmtime_cache[TIME_CACHE_SLOTS];
+
+/* slot where most recent value was stored */
+static THREAD_LOCAL uint localtime_cache_slot, gmtime_cache_slot;
+
+/* This function converts the time_t value <now> into a broken out struct tm
+ * which must be allocated by the caller. It is highly recommended to use this
+ * function instead of localtime() because that one requires a time_t* which
+ * is not always compatible with tv_sec depending on OS/hardware combinations.
+ * Also it implements a cache that avoids internal libc contention on tz_lock
+ * when entering __tz_convert().
+ */
+void get_localtime(const time_t now, struct tm *tm)
+{
+ uint32_t idx;
+
+ /* visit recent entries in age order */
+ idx = localtime_cache_slot;
+ do {
+ if (likely(localtime_cache[idx].sec == now)) {
+ /* that's a hit, return the cached entry */
+ *tm = localtime_cache[idx].tm;
+ return;
+ }
+ idx = (idx - 1) % TIME_CACHE_SLOTS;
+ } while (idx != localtime_cache_slot);
+
+ /* that's a miss, convert the time and cache it */
+ localtime_r(&now, tm);
+ localtime_cache_slot = (localtime_cache_slot + 1) % TIME_CACHE_SLOTS;
+ localtime_cache[localtime_cache_slot].sec = now;
+ localtime_cache[localtime_cache_slot].tm = *tm;
+}
+
+/* This function converts the time_t value <now> into a broken out struct tm
+ * which must be allocated by the caller. It is highly recommended to use this
+ * function instead of gmtime() because that one requires a time_t* which
+ * is not always compatible with tv_sec depending on OS/hardware combinations.
+ * Also it implements a cache that avoids internal libc contention on tz_lock
+ * when entering __tz_convert().
+ */
+void get_gmtime(const time_t now, struct tm *tm)
+{
+ uint32_t idx;
+
+ /* visit recent entries in age order */
+ idx = gmtime_cache_slot;
+ do {
+ if (likely(gmtime_cache[idx].sec == now)) {
+ /* that's a hit, return the cached entry */
+ *tm = gmtime_cache[idx].tm;
+ return;
+ }
+ idx = (idx - 1) % TIME_CACHE_SLOTS;
+ } while (idx != gmtime_cache_slot);
+
+ /* that's a miss, convert the time and cache it */
+ gmtime_r(&now, tm);
+ gmtime_cache_slot = (gmtime_cache_slot + 1) % TIME_CACHE_SLOTS;
+ gmtime_cache[gmtime_cache_slot].sec = now;
+ gmtime_cache[gmtime_cache_slot].tm = *tm;
+}
char *human_time(int t, short hz_div) {
static char rv[sizeof("24855d23h")+1]; // longest of "23h59m" and "59m59s"