diff --git a/.gitignore b/.gitignore
index a83c3d75ec8..7dbc1af588d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -357,6 +357,7 @@ tests/libs/zbxtime/zbx_tm_add
tests/libs/zbxtime/zbx_tm_round_down
tests/libs/zbxtime/zbx_tm_round_up
tests/libs/zbxtime/zbx_tm_sub
+tests/libs/zbxtime/zbx_timespec
tests/libs/zbxtrends/zbx_baseline_get_data
tests/libs/zbxtrends/zbx_trends_parse_range
tests/libs/zbxvariant/zbx_variant_compare
diff --git a/build/mingw/Makefile b/build/mingw/Makefile
index fba74625781..f13b19a6a4a 100644
--- a/build/mingw/Makefile
+++ b/build/mingw/Makefile
@@ -26,6 +26,7 @@ OBJS = \
$(OUTPUTDIR)\param.o \
$(OUTPUTDIR)\misc.o \
$(OUTPUTDIR)\time.o \
+ $(OUTPUTDIR)\timespec.o \
$(OUTPUTDIR)\expr.o \
$(OUTPUTDIR)\host.o \
$(OUTPUTDIR)\macro.o \
@@ -146,6 +147,9 @@ $(OUTPUTDIR)\win32_file.o: $(TOPDIR)\src\libs\zbxfile\win32\win32_file.c
$(OUTPUTDIR)\time.o: $(TOPDIR)\src\libs\zbxtime\time.c
$(CC) $(CFLAGS) -DUNICODE -c $^ -o $@
+$(OUTPUTDIR)\timespec.o: $(TOPDIR)\src\libs\zbxtime\timespec.c
+ $(CC) $(CFLAGS) -DUNICODE -c $^ -o $@
+
$(OUTPUTDIR)\expr.o: $(TOPDIR)\src\libs\zbxexpr\expr.c
$(CC) $(CFLAGS) -DUNICODE -c $^ -o $@
diff --git a/build/win32/project/Makefile_agent b/build/win32/project/Makefile_agent
index 29c41bc8208..585403bfd54 100644
--- a/build/win32/project/Makefile_agent
+++ b/build/win32/project/Makefile_agent
@@ -52,6 +52,7 @@ OBJS = \
..\..\..\src\libs\zbxfile\win32\win32_file.o \
..\..\..\src\libs\zbxparam\param.o \
..\..\..\src\libs\zbxtime\time.o \
+ ..\..\..\src\libs\zbxtime\timespec.o \
..\..\..\src\libs\zbxexpr\expr.o \
..\..\..\src\libs\zbxexpr\function.o \
..\..\..\src\libs\zbxexpr\host.o \
diff --git a/build/win32/project/Makefile_get b/build/win32/project/Makefile_get
index bd1a8454dd1..59530e9cd9a 100644
--- a/build/win32/project/Makefile_get
+++ b/build/win32/project/Makefile_get
@@ -41,6 +41,7 @@ OBJS = \
..\..\..\src\libs\zbxcommon\common_str.o \
..\..\..\src\libs\zbxparam\param.o \
..\..\..\src\libs\zbxtime\time.o \
+ ..\..\..\src\libs\zbxtime\timespec.o \
..\..\..\src\libs\zbxexpr\expr.o \
..\..\..\src\libs\zbxexpr\function.o \
..\..\..\src\libs\zbxexpr\host.o \
diff --git a/build/win32/project/Makefile_sender b/build/win32/project/Makefile_sender
index 08f57887e11..4b123b95add 100644
--- a/build/win32/project/Makefile_sender
+++ b/build/win32/project/Makefile_sender
@@ -39,6 +39,7 @@ OBJS = \
..\..\..\src\libs\zbxcommon\common_str.o \
..\..\..\src\libs\zbxcommon\common_log.o \
..\..\..\src\libs\zbxtime\time.o \
+ ..\..\..\src\libs\zbxtime\timespec.o \
..\..\..\src\libs\zbxexpr\expr.o \
..\..\..\src\libs\zbxexpr\function.o \
..\..\..\src\libs\zbxexpr\host.o \
diff --git a/build/win32/project/Makefile_sender_dll b/build/win32/project/Makefile_sender_dll
index 306150ef9c2..01247e33689 100644
--- a/build/win32/project/Makefile_sender_dll
+++ b/build/win32/project/Makefile_sender_dll
@@ -46,6 +46,7 @@ OBJS = \
..\..\..\src\libs\zbxcommon\components_strings_representations.o \
..\..\..\src\libs\zbxcommon\libc_wrappers.o \
..\..\..\src\libs\zbxtime\time.o \
+ ..\..\..\src\libs\zbxtime\timespec.o \
..\..\..\src\libs\zbxexpr\expr.o \
..\..\..\src\libs\zbxexpr\function.o \
..\..\..\src\libs\zbxexpr\host.o \
diff --git a/configure.ac b/configure.ac
index 78cd724c2d8..62318afc60d 100644
--- a/configure.ac
+++ b/configure.ac
@@ -243,7 +243,7 @@ dnl Linux
AC_CHECK_HEADERS([linux/version.h])
dnl MacOS
-AC_CHECK_HEADERS([mach/host_info.h mach/mach_host.h vm/vm_param.h nlist.h])
+AC_CHECK_HEADERS([mach/host_info.h mach/mach_host.h vm/vm_param.h nlist.h mach/mach_time.h])
dnl maybe MacOS
AC_CHECK_HEADERS([kstat.h])
diff --git a/include/zbxtime.h b/include/zbxtime.h
index da48a134987..d3d554149d6 100644
--- a/include/zbxtime.h
+++ b/include/zbxtime.h
@@ -17,6 +17,18 @@
#include "zbxcommon.h"
+#ifndef CLOCK_REALTIME
+#define CLOCK_REALTIME 0
+#endif
+
+#ifndef CLOCK_MONOTONIC
+#define CLOCK_MONOTONIC 1
+#endif
+
+#define ZBX_TIMESPEC_THRESHOLD_NSEC (250 * 1000000) /* max allowed drift */
+
+#define ZBX_TS2DBL(ts) ((double)(ts)->sec + 1.0e-9 * (double)(ts)->ns)
+
typedef struct
{
int sec; /* seconds */
@@ -51,9 +63,13 @@ typedef enum
}
zbx_time_unit_t;
+void zbx_init_library_time(void);
+void zbx_deinit_library_time(void);
+
double zbx_time(void);
void zbx_timespec(zbx_timespec_t *ts);
void zbx_timespec_normalize(zbx_timespec_t *ts);
+void zbx_get_clock_time(int clk_id, zbx_timespec_t *ts);
double zbx_current_time(void);
int zbx_is_leap_year(int year);
void zbx_get_time(struct tm *tm, long *milliseconds, zbx_timezone_t *tz);
diff --git a/src/go/internal/agent/resultcache/diskcache.go b/src/go/internal/agent/resultcache/diskcache.go
index 5da33434a5c..0581559682d 100644
--- a/src/go/internal/agent/resultcache/diskcache.go
+++ b/src/go/internal/agent/resultcache/diskcache.go
@@ -27,6 +27,7 @@ import (
"golang.zabbix.com/agent2/internal/agent"
"golang.zabbix.com/agent2/internal/monitor"
"golang.zabbix.com/agent2/pkg/version"
+ "golang.zabbix.com/agent2/pkg/zbxlib"
"golang.zabbix.com/sdk/log"
"golang.zabbix.com/sdk/plugin"
"golang.zabbix.com/sdk/plugin/itemutil"
@@ -387,10 +388,9 @@ func (c *DiskCache) write(r *plugin.Result) {
}
var ns int
- var clock int64
+ var clock int
if !r.Ts.IsZero() {
- clock = r.Ts.Unix()
- ns = r.Ts.Nanosecond()
+ clock, ns = zbxlib.Timespec()
}
var Mtime int = DbVariableNotSet
@@ -427,7 +427,7 @@ func (c *DiskCache) write(r *plugin.Result) {
if r.Persistent {
if c.oldestLog == 0 {
- c.oldestLog = clock
+ c.oldestLog = int64(clock)
}
if (now - c.oldestLog) > c.storagePeriod {
atomic.StoreUint32(&c.persistFlag, 1)
@@ -441,7 +441,7 @@ func (c *DiskCache) write(r *plugin.Result) {
}
} else {
if c.oldestData == 0 {
- c.oldestData = clock
+ c.oldestData = int64(clock)
}
if (now - c.oldestData) > c.storagePeriod+StorageTolerance {
diff --git a/src/go/internal/agent/resultcache/memorycache.go b/src/go/internal/agent/resultcache/memorycache.go
index 5c5275c0107..9792a44eb5d 100644
--- a/src/go/internal/agent/resultcache/memorycache.go
+++ b/src/go/internal/agent/resultcache/memorycache.go
@@ -23,6 +23,7 @@ import (
"golang.zabbix.com/agent2/internal/agent"
"golang.zabbix.com/agent2/internal/monitor"
"golang.zabbix.com/agent2/pkg/version"
+ "golang.zabbix.com/agent2/pkg/zbxlib"
"golang.zabbix.com/sdk/log"
"golang.zabbix.com/sdk/plugin"
"golang.zabbix.com/sdk/plugin/itemutil"
@@ -193,8 +194,7 @@ func (c *MemoryCache) write(r *plugin.Result) {
var clock, ns int
if !r.Ts.IsZero() {
- clock = int(r.Ts.Unix())
- ns = r.Ts.Nanosecond()
+ clock, ns = zbxlib.Timespec()
}
data := &AgentData{
diff --git a/src/go/pkg/zbxlib/checks_darwin.go b/src/go/pkg/zbxlib/checks_darwin.go
index 063252f9c0b..7c26ddd06f8 100644
--- a/src/go/pkg/zbxlib/checks_darwin.go
+++ b/src/go/pkg/zbxlib/checks_darwin.go
@@ -22,6 +22,7 @@ package zbxlib
#cgo CFLAGS: -I${SRCDIR}/../../../../../include
#include "zbxsysinfo.h"
+#include "zbxtime.h"
int system_localtime(AGENT_REQUEST *request, AGENT_RESULT *result);
int system_boottime(AGENT_REQUEST *request, AGENT_RESULT *result);
@@ -39,6 +40,19 @@ int vfs_fs_size(AGENT_REQUEST *request, AGENT_RESULT *result);
int vfs_fs_get(AGENT_REQUEST *request, AGENT_RESULT *result);
int vm_memory_size(AGENT_REQUEST *request, AGENT_RESULT *result);
+int agent_realtime(AGENT_REQUEST *request, AGENT_RESULT *result)
+{
+ zbx_timespec_t ts = {0};
+
+ ZBX_UNUSED(request);
+
+ zbx_get_clock_time(CLOCK_REALTIME, &ts);
+
+ SET_DBL_RESULT(result, (double)ts.sec + (double)ts.ns / 1000000000.0);
+
+ return SYSINFO_RET_OK;
+}
+
*/
import "C"
@@ -72,7 +86,8 @@ func resolveMetric(key string) (cfunc unsafe.Pointer) {
return unsafe.Pointer(C.vfs_fs_get)
case "vm.memory.size":
return unsafe.Pointer(C.vm_memory_size)
-
+ case "agent.realtime":
+ return unsafe.Pointer(C.agent_realtime)
default:
return
}
diff --git a/src/go/pkg/zbxlib/checks_linux.go b/src/go/pkg/zbxlib/checks_linux.go
index 22d23b2fc89..345d79b8b01 100644
--- a/src/go/pkg/zbxlib/checks_linux.go
+++ b/src/go/pkg/zbxlib/checks_linux.go
@@ -21,6 +21,7 @@ package zbxlib
#include "zbxsysinfo.h"
#include "module.h"
+#include "zbxtime.h"
// vfs_dir_get, system_localtime, system_users_num
#include "zbxsysinfo/common/zbxsysinfo_common.c"
@@ -70,6 +71,20 @@ int vfs_fs_inode(AGENT_REQUEST *request, AGENT_RESULT *result);
int vfs_fs_size(AGENT_REQUEST *request, AGENT_RESULT *result);
int vfs_fs_get(AGENT_REQUEST *request, AGENT_RESULT *result);
int vm_memory_size(AGENT_REQUEST *request, AGENT_RESULT *result);
+
+int agent_realtime(AGENT_REQUEST *request, AGENT_RESULT *result)
+{
+ zbx_timespec_t ts = {0};
+
+ ZBX_UNUSED(request);
+
+ zbx_get_clock_time(CLOCK_REALTIME, &ts);
+
+ SET_DBL_RESULT(result, (double)ts.sec + (double)ts.ns / 1000000000.0);
+
+ return SYSINFO_RET_OK;
+}
+
*/
import "C"
@@ -101,6 +116,8 @@ func resolveMetric(key string) (cfunc unsafe.Pointer) {
cfunc = unsafe.Pointer(C.system_hw_macaddr)
case "vfs.dir.get":
cfunc = unsafe.Pointer(C.vfs_dir_get)
+ case "agent.realtime":
+ return unsafe.Pointer(C.agent_realtime)
}
return
}
diff --git a/src/go/pkg/zbxlib/checks_windows.go b/src/go/pkg/zbxlib/checks_windows.go
index 6718196f0af..2fad7d4c3b8 100644
--- a/src/go/pkg/zbxlib/checks_windows.go
+++ b/src/go/pkg/zbxlib/checks_windows.go
@@ -22,6 +22,7 @@ package zbxlib
#include "zbxsysinfo.h"
#include "module.h"
+#include "zbxtime.h"
// vfs_dir_get, system_localtime
@@ -51,6 +52,20 @@ int system_swap_out(AGENT_REQUEST *request, AGENT_RESULT *result);
int system_swap_size(AGENT_REQUEST *request, AGENT_RESULT *result);
int vfs_fs_inode(AGENT_REQUEST *request, AGENT_RESULT *result);
int vm_memory_size(AGENT_REQUEST *request, AGENT_RESULT *result);
+
+int agent_realtime(AGENT_REQUEST *request, AGENT_RESULT *result)
+{
+ zbx_timespec_t ts = {0};
+
+ ZBX_UNUSED(request);
+
+ zbx_get_clock_time(CLOCK_REALTIME, &ts);
+
+ SET_DBL_RESULT(result, (double)ts.sec + (double)ts.ns / 1000000000.0);
+
+ return SYSINFO_RET_OK;
+}
+
*/
import "C"
@@ -64,6 +79,8 @@ func resolveMetric(key string) (cfunc unsafe.Pointer) {
cfunc = unsafe.Pointer(C.system_localtime)
case "vfs.dir.get":
cfunc = unsafe.Pointer(C.vfs_dir_get)
+ case "agent.realtime":
+ return unsafe.Pointer(C.agent_realtime)
}
return
diff --git a/src/go/pkg/zbxlib/time.go b/src/go/pkg/zbxlib/time.go
new file mode 100644
index 00000000000..fcba0a34b15
--- /dev/null
+++ b/src/go/pkg/zbxlib/time.go
@@ -0,0 +1,38 @@
+/*
+** Copyright (C) 2001-2025 Zabbix SIA
+**
+** This program is free software: you can redistribute it and/or modify it under the terms of
+** the GNU Affero General Public License as published by the Free Software Foundation, version 3.
+**
+** This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+** without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+** See the GNU Affero General Public License for more details.
+**
+** You should have received a copy of the GNU Affero General Public License along with this program.
+** If not, see .
+**/
+package zbxlib
+
+/* cspell:disable */
+
+/*
+#include "zbxtime.h"
+*/
+import "C"
+
+import (
+ "golang.zabbix.com/sdk/log"
+)
+
+func init() {
+ C.zbx_init_library_time()
+}
+
+func Timespec() (int, int) {
+ var timespec C.zbx_timespec_t
+
+ log.Tracef("Calling C function \"zbx_timespec()\"")
+ C.zbx_timespec(×pec)
+
+ return int(timespec.sec), int(timespec.ns)
+}
diff --git a/src/go/plugins/zabbix/async/async_nix.go b/src/go/plugins/zabbix/async/async_nix.go
index 89e18e09989..0a2f6366ec5 100644
--- a/src/go/plugins/zabbix/async/async_nix.go
+++ b/src/go/plugins/zabbix/async/async_nix.go
@@ -28,5 +28,6 @@ func getMetrics() []string {
"system.cpu.intr", "Device interrupts.",
"system.hw.cpu", "CPU information.",
"system.hw.macaddr", "Listing of MAC addresses.",
+ "agent.realtime", "Realtime.",
}
}
diff --git a/src/go/plugins/zabbix/async/async_windows.go b/src/go/plugins/zabbix/async/async_windows.go
index cf4fec90ca0..5b4560175da 100644
--- a/src/go/plugins/zabbix/async/async_windows.go
+++ b/src/go/plugins/zabbix/async/async_windows.go
@@ -17,5 +17,6 @@ package zabbixasync
func getMetrics() []string {
return []string{
"system.localtime", "Returns system local time.",
+ "agent.realtime", "Realtime.",
}
}
diff --git a/src/libs/zbxcalc/func_eval.c b/src/libs/zbxcalc/func_eval.c
index 7d2b989b5ae..a08c6f390e3 100644
--- a/src/libs/zbxcalc/func_eval.c
+++ b/src/libs/zbxcalc/func_eval.c
@@ -1519,7 +1519,15 @@ static int evaluate_LASTCLOCK(zbx_variant_t *value, const zbx_dc_evaluate_item_t
if (SUCCEED == (ret = get_last_n_value(item, parameters, ts, &vc_value, error)))
{
- zbx_variant_set_ui64(value, (zbx_uint64_t)vc_value.timestamp.sec);
+ if (ITEM_VALUE_TYPE_FLOAT == item->value_type)
+ {
+ zbx_variant_set_dbl(value, (double)vc_value.timestamp.sec +
+ (double)vc_value.timestamp.ns / 1000000000.0);
+ }
+ else
+ {
+ zbx_variant_set_ui64(value, (zbx_uint64_t)vc_value.timestamp.sec);
+ }
zbx_history_record_clear(&vc_value, item->value_type);
}
diff --git a/src/libs/zbxpoller/checks_internal.c b/src/libs/zbxpoller/checks_internal.c
index e948852e417..d9b7b035f91 100644
--- a/src/libs/zbxpoller/checks_internal.c
+++ b/src/libs/zbxpoller/checks_internal.c
@@ -380,6 +380,20 @@ int get_value_internal(const zbx_dc_item_t *item, AGENT_RESULT *result, const zb
SET_UI64_RESULT(result, time(NULL) - config_startup_time);
}
+ else if (0 == strcmp(tmp, "realtime"))
+ {
+ zbx_timespec_t ts = {0};
+
+ if (1 != nparams)
+ {
+ SET_MSG_RESULT(result, zbx_strdup(NULL, "Invalid number of parameters."));
+ goto out;
+ }
+
+ zbx_get_clock_time(CLOCK_REALTIME, &ts);
+
+ SET_DBL_RESULT(result, (double)ts.sec + (double)ts.ns / 1000000000.0);
+ }
else if (0 == strcmp(tmp, "boottime")) /* zabbix["boottime"] */
{
if (1 != nparams)
diff --git a/src/libs/zbxsysinfo/agent/agent.c b/src/libs/zbxsysinfo/agent/agent.c
index 0dabd2f7bda..3acb35e704a 100644
--- a/src/libs/zbxsysinfo/agent/agent.c
+++ b/src/libs/zbxsysinfo/agent/agent.c
@@ -15,6 +15,7 @@
#include "zbxsysinfo.h"
#include "../sysinfo.h"
#include "agent.h"
+#include "zbxtime.h"
#include "modbtype.h"
@@ -23,6 +24,7 @@ static int agent_hostmetadata(AGENT_REQUEST *request, AGENT_RESULT *result);
static int agent_ping(AGENT_REQUEST *request, AGENT_RESULT *result);
static int agent_version(AGENT_REQUEST *request, AGENT_RESULT *result);
static int agent_variant(AGENT_REQUEST *request, AGENT_RESULT *result);
+static int agent_realtime(AGENT_REQUEST *request, AGENT_RESULT *result);
static zbx_metric_t parameters_agent[] =
/* KEY FLAG FUNCTION TEST PARAMETERS */
@@ -32,6 +34,7 @@ static zbx_metric_t parameters_agent[] =
{"agent.ping", 0, agent_ping, NULL},
{"agent.variant", 0, agent_variant, NULL},
{"agent.version", 0, agent_version, NULL},
+ {"agent.realtime", 0, agent_realtime, NULL},
{"modbus.get", CF_HAVEPARAMS, modbus_get, "tcp://127.0.0.1"},
{0}
};
@@ -112,3 +115,16 @@ static int agent_variant(AGENT_REQUEST *request, AGENT_RESULT *result)
return SYSINFO_RET_OK;
}
+
+static int agent_realtime(AGENT_REQUEST *request, AGENT_RESULT *result)
+{
+ zbx_timespec_t ts = {0};
+
+ ZBX_UNUSED(request);
+
+ zbx_get_clock_time(CLOCK_REALTIME, &ts);
+
+ SET_DBL_RESULT(result, (double)ts.sec + (double)ts.ns / 1000000000.0);
+
+ return SYSINFO_RET_OK;
+}
diff --git a/src/libs/zbxtime/Makefile.am b/src/libs/zbxtime/Makefile.am
index 60bc8d3952c..ec8deda01f3 100644
--- a/src/libs/zbxtime/Makefile.am
+++ b/src/libs/zbxtime/Makefile.am
@@ -3,4 +3,5 @@
noinst_LIBRARIES = libzbxtime.a
libzbxtime_a_SOURCES = \
- time.c
+ time.c \
+ timespec.c
diff --git a/src/libs/zbxtime/time.c b/src/libs/zbxtime/time.c
index d68ed4dbba6..d2b8441c14b 100644
--- a/src/libs/zbxtime/time.c
+++ b/src/libs/zbxtime/time.c
@@ -16,6 +16,10 @@
#include "zbxnum.h"
+#if HAVE_MACH_MACH_TIME_H
+# include
+#endif
+
/******************************************************************************
* *
* Purpose: Gets the current time. *
@@ -55,133 +59,6 @@ void zbx_timespec_normalize(zbx_timespec_t *ts)
}
}
-/******************************************************************************
- * *
- * Purpose: Gets the current time. *
- * *
- * Comments: Time in seconds since midnight (00:00:00), *
- * January 1, 1970, coordinated universal time (UTC). *
- * *
- ******************************************************************************/
-void zbx_timespec(zbx_timespec_t *ts)
-{
- static ZBX_THREAD_LOCAL zbx_timespec_t last_ts = {0, 0};
- static ZBX_THREAD_LOCAL int corr = 0;
-#if defined(_WINDOWS) || defined(__MINGW32__)
- static ZBX_THREAD_LOCAL LARGE_INTEGER tickPerSecond = {0};
- struct _timeb tb;
- int sec_diff;
-#else
- struct timeval tv;
- int rc = -1;
-# ifdef HAVE_TIME_CLOCK_GETTIME
- struct timespec tp;
-# endif
-#endif
-#if defined(_WINDOWS) || defined(__MINGW32__)
-
- if (0 == tickPerSecond.QuadPart)
- QueryPerformanceFrequency(&tickPerSecond);
-
- _ftime(&tb);
-
- ts->sec = (int)tb.time;
- ts->ns = tb.millitm * 1000000;
-
- if (0 != tickPerSecond.QuadPart)
- {
- LARGE_INTEGER tick;
-
- if (TRUE == QueryPerformanceCounter(&tick))
- {
- static ZBX_THREAD_LOCAL LARGE_INTEGER last_tick = {0};
-
- if (0 < last_tick.QuadPart)
- {
- LARGE_INTEGER qpc_tick = {0}, ntp_tick = {0};
-
- /* _ftime () returns precision in milliseconds, but 'ns' could be increased up to 1ms */
- if (last_ts.sec == ts->sec && last_ts.ns > ts->ns && 1000000 > (last_ts.ns - ts->ns))
- {
- ts->ns = last_ts.ns;
- }
- else
- {
- ntp_tick.QuadPart = tickPerSecond.QuadPart * (ts->sec - last_ts.sec) +
- tickPerSecond.QuadPart * (ts->ns - last_ts.ns) / 1000000000;
- }
-
- /* host system time can shift backwards, then correction is not reasonable */
- if (0 <= ntp_tick.QuadPart)
- qpc_tick.QuadPart = tick.QuadPart - last_tick.QuadPart - ntp_tick.QuadPart;
-
- if (0 < qpc_tick.QuadPart && qpc_tick.QuadPart < tickPerSecond.QuadPart)
- {
- int ns = (int)(1000000000 * qpc_tick.QuadPart / tickPerSecond.QuadPart);
-
- if (1000000 > ns) /* value less than 1 millisecond */
- {
- ts->ns += ns;
-
- while (ts->ns >= 1000000000)
- {
- ts->sec++;
- ts->ns -= 1000000000;
- }
- }
- }
- }
-
- last_tick = tick;
- }
- }
-#else /* not _WINDOWS */
-#ifdef HAVE_TIME_CLOCK_GETTIME
- if (0 == (rc = clock_gettime(CLOCK_REALTIME, &tp)))
- {
- ts->sec = (int)tp.tv_sec;
- ts->ns = (int)tp.tv_nsec;
- }
-#endif /* HAVE_TIME_CLOCK_GETTIME */
-
- if (0 != rc && 0 == (rc = gettimeofday(&tv, NULL)))
- {
- ts->sec = (int)tv.tv_sec;
- ts->ns = (int)tv.tv_usec * 1000;
- }
-
- if (0 != rc)
- {
- ts->sec = (int)time(NULL);
- ts->ns = 0;
- }
-#endif /* not _WINDOWS */
-
-#if defined(_WINDOWS) || defined(__MINGW32__)
- sec_diff = ts->sec - last_ts.sec;
-
- /* correction window is 1 sec before the corrected last _ftime clock reading */
- if ((0 == sec_diff && ts->ns <= last_ts.ns) || (-1 == sec_diff && ts->ns > last_ts.ns))
-#else
- if (last_ts.ns == ts->ns && last_ts.sec == ts->sec)
-#endif
- {
- ts->ns = last_ts.ns + (++corr);
-
- while (ts->ns >= 1000000000)
- {
- ts->sec++;
- ts->ns -= 1000000000;
- }
- }
- else
- {
- last_ts.sec = ts->sec;
- last_ts.ns = ts->ns;
- corr = 0;
- }
-}
-
/******************************************************************************
* *
* Purpose: Gets the current time including UTC offset *
@@ -1293,3 +1170,88 @@ int zbx_ts_check_deadline(const zbx_timespec_t *deadline)
return SUCCEED;
}
+
+#if defined(_WINDOWS) || defined(__MINGW32__)
+void zbx_get_clock_time(int clk_id, zbx_timespec_t *ts)
+{
+ if (CLOCK_REALTIME == clk_id)
+ {
+ FILETIME ft;
+
+#if (_WIN32_WINNT >= 0x0602) // Windows 8+
+ GetSystemTimePreciseAsFileTime(&ft);
+#else
+ GetSystemTimeAsFileTime(&ft);
+#endif
+
+ /* convert FILETIME (100-nanosecond intervals since 1601-01-01) to Unix epoch */
+ ULONGLONG t = ((ULONGLONG)ft.dwHighDateTime << 32) | ft.dwLowDateTime;
+
+ t -= 116444736000000000ULL;
+ ts->sec = t / 10000000ULL;
+ ts->ns = (long)((t % 10000000ULL) * 100);
+ }
+ else
+ {
+ static LARGE_INTEGER frequency;
+ static BOOL initialized = FALSE;
+ LARGE_INTEGER counter;
+
+ if (!initialized) {
+ QueryPerformanceFrequency(&frequency);
+ initialized = TRUE;
+ }
+
+ QueryPerformanceCounter(&counter);
+ ts->sec = (int)(counter.QuadPart / frequency.QuadPart);
+ ts->ns = (int)(((counter.QuadPart % frequency.QuadPart) * 1000000000LL) / frequency.QuadPart);
+ }
+}
+#else
+void zbx_get_clock_time(int clk_id, zbx_timespec_t *ts)
+{
+ int rc = -1;
+#ifdef HAVE_TIME_CLOCK_GETTIME
+ struct timespec tp;
+
+ if (0 == (rc == clock_gettime(clk_id, &tp)))
+ {
+ ts->sec = (int)tp.tv_sec;
+ ts->ns = (int)tp.tv_nsec;
+
+ return;
+ }
+#elif defined(HAVE_MACH_MACH_TIME_H)
+ static mach_timebase_info_data_t timebase;
+ uint64_t mach_time = mach_absolute_time();
+
+ if (timebase.denom == 0) {
+ /* used to convert mach units into nanoseconds */
+ mach_timebase_info(&timebase);
+ }
+
+ uint64_t nanoseconds = mach_time * timebase.numer / timebase.denom;
+
+ ts->sec = nanoseconds / 1000000000;
+ ts->ns = nanoseconds % 1000000000;
+
+ return;
+#else
+#error "System does not have monotonic clock"
+#endif
+ struct timeval tv;
+
+ /* Note. POSIX.1-2008 marks this as obsolete. */
+ if (0 != rc && 0 == (rc = gettimeofday(&tv, NULL)))
+ {
+ ts->sec = (int)tv.tv_sec;
+ ts->ns = (int)tv.tv_usec * 1000;
+ }
+ else
+ {
+ /* just in case another fallback */
+ ts->sec = (int)time(NULL);
+ ts->ns = 0;
+ }
+}
+#endif
diff --git a/src/libs/zbxtime/timespec.c b/src/libs/zbxtime/timespec.c
new file mode 100644
index 00000000000..a4e93f4781f
--- /dev/null
+++ b/src/libs/zbxtime/timespec.c
@@ -0,0 +1,143 @@
+/*
+** Copyright (C) 2001-2025 Zabbix SIA
+**
+** This program is free software: you can redistribute it and/or modify it under the terms of
+** the GNU Affero General Public License as published by the Free Software Foundation, version 3.
+**
+** This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+** without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+** See the GNU Affero General Public License for more details.
+**
+** You should have received a copy of the GNU Affero General Public License along with this program.
+** If not, see .
+**/
+
+#include "zbxtime.h"
+
+/* this is base library - refernce to zbxmutexs is problematic */
+#ifdef HAVE_PTHREAD_H
+static pthread_mutex_t timebase_lock;
+#elif defined(_WINDOWS) || defined(__MINGW32__)
+static HANDLE timebase_lock;
+#endif
+
+static zbx_timespec_t sys_timebase;
+static zbx_timespec_t mono_timebase;
+
+/* ns should always be in range: 0 <= ns <= 1'000'000'000 */
+static void normalize_ns(zbx_timespec_t *ts)
+{
+ while (1000000000 <= ts->ns)
+ {
+ ts->sec++;
+ ts->ns -= 1000000000;
+ }
+
+ while (0 > ts->ns)
+ {
+ ts->sec--;
+ ts->ns += 1000000000;
+ }
+}
+
+void zbx_init_library_time(void)
+{
+ /* init local mutex cause mutexes initialized by zbxmutexs require time before zbx_locks_create */
+#ifdef HAVE_PTHREAD_H
+ if (0 != pthread_mutex_init(&timebase_lock, NULL))
+ {
+ zabbix_log(LOG_LEVEL_CRIT, "failed to init time library");
+ exit(EXIT_FAILURE);
+ }
+#elif defined(_WINDOWS) || defined(__MINGW32__)
+ if (NULL == (timebase_lock = CreateMutex(NULL, FALSE, NULL)))
+ {
+ zabbix_log(LOG_LEVEL_CRIT, "failed to init time library: %s", GetLastError());
+ exit(EXIT_FAILURE);
+ }
+#endif
+
+ zbx_get_clock_time(CLOCK_MONOTONIC, &mono_timebase);
+ zbx_get_clock_time(CLOCK_REALTIME, &sys_timebase);
+}
+
+void zbx_deinit_library_time(void)
+{
+#ifdef HAVE_PTHREAD_H
+ pthread_mutex_destroy(&timebase_lock);
+#elif defined(_WINDOWS) || defined(__MINGW32__)
+ CloseHandle(timebase_lock);
+#endif
+}
+
+/******************************************************************************
+ * *
+ * Purpose: Gets the current time. *
+ * *
+ * Requirements: 1. should be monotonic, i.e, always going forward *
+ * 2. should be close to system clock time *
+ * 3. should never return identical value *
+ * 4. should synchronize between threads *
+ * *
+ * Comments: Time in seconds since midnight (00:00:00), *
+ * January 1, 1970, coordinated universal time (UTC). *
+ * *
+ ******************************************************************************/
+void zbx_timespec(zbx_timespec_t *ts)
+{
+ static int id_corr = 0;
+ zbx_timespec_t mono_now, now_sys, mono_delta, adj_delta;
+
+#ifdef HAVE_PTHREAD_H
+ pthread_mutex_lock(&timebase_lock);
+#elif defined(_WINDOWS) || defined(__MINGW32__)
+ WaitForSingleObject(timebase_lock, INFINITE);
+#endif
+
+ /* calculate monotonic delta ticks forward */
+ zbx_get_clock_time(CLOCK_MONOTONIC, &mono_now);
+
+ mono_delta.sec = mono_now.sec - mono_timebase.sec;
+ mono_delta.ns = mono_now.ns - mono_timebase.ns;
+
+ ts->sec = sys_timebase.sec + mono_delta.sec;
+ ts->ns = sys_timebase.ns + mono_delta.ns;
+
+ normalize_ns(ts);
+
+ /* calculate if adjustment for system time is required*/
+ zbx_get_clock_time(CLOCK_REALTIME, &now_sys);
+
+ adj_delta.sec = now_sys.sec - ts->sec;
+ adj_delta.ns = now_sys.ns - ts->ns;
+
+ normalize_ns(&adj_delta);
+
+ if (0 < adj_delta.sec || ZBX_TIMESPEC_THRESHOLD_NSEC <= adj_delta.ns)
+ {
+ sys_timebase = now_sys;
+ *ts = now_sys;
+ }
+
+ /* Monotonic time functions may return the same value multiple times so */
+ /* make sure that this function will never return two identical values */
+ if (sys_timebase.ns <= ts->ns && sys_timebase.sec <= ts->sec)
+ {
+ ts->ns = sys_timebase.ns + (++id_corr);
+
+ normalize_ns(ts);
+ }
+ else
+ {
+ id_corr = 0;
+ }
+
+ sys_timebase = *ts;
+ mono_timebase = mono_now;
+
+#ifdef HAVE_PTHREAD_H
+ pthread_mutex_unlock(&timebase_lock);
+#elif defined(_WINDOWS) || defined(__MINGW32__)
+ ReleaseMutex(timebase_lock);
+#endif
+}
diff --git a/src/zabbix_agent/zabbix_agentd.c b/src/zabbix_agent/zabbix_agentd.c
index 6aaf1d0c05d..e86df812ba2 100644
--- a/src/zabbix_agent/zabbix_agentd.c
+++ b/src/zabbix_agent/zabbix_agentd.c
@@ -1543,6 +1543,7 @@ void zbx_free_service_resources(void)
#ifndef _WINDOWS
zbx_locks_destroy();
+ zbx_deinit_library_time();
#endif
}
@@ -1556,6 +1557,7 @@ int main(int argc, char **argv)
argv = zbx_setproctitle_init(argc, argv);
zbx_progname = get_program_name(argv[0]);
+ zbx_init_library_time();
zbx_init_library_common(zbx_log_impl, get_zbx_progname, zbx_backtrace);
zbx_init_library_sysinfo(get_zbx_config_timeout, get_zbx_config_enable_remote_commands,
get_zbx_config_log_remote_commands, get_zbx_config_unsafe_user_parameters,
diff --git a/src/zabbix_get/zabbix_get.c b/src/zabbix_get/zabbix_get.c
index 83e21ca65fb..8b84848a3c4 100644
--- a/src/zabbix_get/zabbix_get.c
+++ b/src/zabbix_get/zabbix_get.c
@@ -21,6 +21,7 @@
#include "zbxlog.h"
#include "zbxjson.h"
#include "zbxbincommon.h"
+#include "zbxtime.h"
#ifndef _WINDOWS
# include "zbxnix.h"
@@ -374,6 +375,7 @@ int main(int argc, char **argv)
zbx_progname = get_program_name(argv[0]);
+ zbx_init_library_time();
zbx_init_library_common(zbx_log_impl, get_zbx_progname, zbx_backtrace);
#ifndef _WINDOWS
zbx_init_library_nix(get_zbx_progname, NULL);
@@ -615,6 +617,7 @@ out:
}
#endif
zbx_config_tls_free(zbx_config_tls);
+ zbx_deinit_library_time();
#if defined(_WINDOWS)
while (0 == WSACleanup())
;
diff --git a/src/zabbix_js/zabbix_js.c b/src/zabbix_js/zabbix_js.c
index 7a0bd92dfc3..0e647358059 100644
--- a/src/zabbix_js/zabbix_js.c
+++ b/src/zabbix_js/zabbix_js.c
@@ -20,6 +20,7 @@
#include "zbxnix.h"
#include "zbxnum.h"
#include "zbxbincommon.h"
+#include "zbxtime.h"
ZBX_GET_CONFIG_VAR2(const char *, const char *, zbx_progname, NULL)
static const char title_message[] = "zabbix_js";
@@ -228,6 +229,7 @@ int main(int argc, char **argv)
zbx_progname = get_program_name(argv[0]);
+ zbx_init_library_time();
zbx_init_library_common(zbx_log_impl, get_zbx_progname, zbx_backtrace);
#ifndef _WINDOWS
zbx_init_library_nix(get_zbx_progname, NULL);
@@ -347,6 +349,7 @@ close:
#ifndef _WINDOWS
zbx_locks_destroy();
#endif
+ zbx_deinit_library_time();
clean:
zbx_free(result);
zbx_free(error);
diff --git a/src/zabbix_proxy/proxy.c b/src/zabbix_proxy/proxy.c
index ea079b86e7b..4f8d1174baa 100644
--- a/src/zabbix_proxy/proxy.c
+++ b/src/zabbix_proxy/proxy.c
@@ -1215,6 +1215,7 @@ static void zbx_on_exit(int ret, void *on_exit_args)
zbx_config_tls_free(zbx_config_tls);
zbx_db_config_free(zbx_db_config);
+ zbx_deinit_library_time();
exit(EXIT_SUCCESS);
}
@@ -1249,6 +1250,7 @@ int main(int argc, char **argv)
zbx_db_config = zbx_db_config_create();
/* initialize libraries before using */
+ zbx_init_library_time();
zbx_init_library_common(zbx_log_impl, get_zbx_progname, zbx_backtrace);
zbx_init_library_nix(get_zbx_progname, get_process_info_by_thread);
zbx_init_library_dbupgrade(get_zbx_program_type, get_zbx_config_timeout);
diff --git a/src/zabbix_sender/send_buffer.c b/src/zabbix_sender/send_buffer.c
index 733983f79eb..effa2a02360 100644
--- a/src/zabbix_sender/send_buffer.c
+++ b/src/zabbix_sender/send_buffer.c
@@ -26,6 +26,15 @@ typedef struct
}
zbx_send_batch_t;
+typedef struct
+{
+ char *host;
+ char *key;
+ int clock;
+ int ns;
+}
+zbx_item_timestamp_t;
+
static zbx_hash_t send_batch_hash(const void *d)
{
const zbx_send_batch_t *b = (const zbx_send_batch_t *)d;
@@ -41,6 +50,34 @@ static int send_batch_compare(const void *d1, const void *d2)
return strcmp(b1->host, b2->host);
}
+static zbx_hash_t timestamp_hash(const void *d)
+{
+ zbx_hash_t hash = ZBX_DEFAULT_HASH_SEED;
+ const zbx_item_timestamp_t *b = (const zbx_item_timestamp_t *)d;
+
+ hash = ZBX_DEFAULT_STRING_HASH_ALGO(b->host, strlen(b->host), ZBX_DEFAULT_HASH_SEED);
+
+ return ZBX_DEFAULT_STRING_HASH_ALGO(b->key, strlen(b->key), hash);
+}
+
+static int timestamp_compare(const void *d1, const void *d2)
+{
+ int ret;
+ const zbx_item_timestamp_t *b1 = (const zbx_item_timestamp_t *)d1;
+ const zbx_item_timestamp_t *b2 = (const zbx_item_timestamp_t *)d2;
+
+ if (0 != (ret = strcmp(b1->host, b2->host)))
+ return ret;
+
+ if (0 != (ret = strcmp(b1->key, b2->key)))
+ return ret;
+
+ ZBX_RETURN_IF_NOT_EQUAL(b1->clock, b2->clock);
+ ZBX_RETURN_IF_NOT_EQUAL(b1->ns, b2->ns);
+
+ return ret;
+}
+
/******************************************************************************
* *
* Purpose: initialize send buffer *
@@ -57,6 +94,7 @@ void sb_init(zbx_send_buffer_t *buf, int group_mode, const char *host, int with_
buf->kv_alloc = 0;
zbx_hashset_create(&buf->batches, 100, send_batch_hash, send_batch_compare);
+ zbx_hashset_create(&buf->timestamps, 100, timestamp_hash, timestamp_compare);
}
/******************************************************************************
@@ -68,6 +106,7 @@ void sb_destroy(zbx_send_buffer_t *buf)
{
zbx_hashset_iter_t iter;
zbx_send_batch_t *batch;
+ zbx_item_timestamp_t *timestamp;
zbx_hashset_iter_reset(&buf->batches, &iter);
while (NULL != (batch = (zbx_send_batch_t *)zbx_hashset_iter_next(&iter)))
@@ -83,6 +122,15 @@ void sb_destroy(zbx_send_buffer_t *buf)
zbx_hashset_destroy(&buf->batches);
+ zbx_hashset_iter_reset(&buf->timestamps, &iter);
+ while (NULL != (timestamp = (zbx_item_timestamp_t *)zbx_hashset_iter_next(&iter)))
+ {
+ zbx_free(timestamp->host);
+ zbx_free(timestamp->key);
+ }
+
+ zbx_hashset_destroy(&buf->timestamps);
+
zbx_free(buf->key);
zbx_free(buf->value);
zbx_free(buf->host);
@@ -174,8 +222,8 @@ static zbx_send_batch_t *sb_add_value(zbx_send_buffer_t *buf, const char *host,
* Comments: line format: [] [] *
* *
******************************************************************************/
-int sb_parse_line(zbx_send_buffer_t *buf, const char *line, size_t line_alloc, int send_mode, struct zbx_json **out,
- char **error)
+int sb_parse_line(zbx_send_buffer_t *buf, const char *line, size_t line_alloc, int line_no, int send_mode,
+ struct zbx_json **out, char **error)
{
const char *p = line;
char hostname[MAX_STRING_LEN], tmp[32];
@@ -258,6 +306,23 @@ int sb_parse_line(zbx_send_buffer_t *buf, const char *line, size_t line_alloc, i
return FAIL;
}
+ if (1 == buf->with_clock && 1 == buf->with_ns)
+ {
+ char *host = zbx_strdup(NULL, NULL != buf->host ? buf->host : "");
+ zbx_item_timestamp_t local = {.host = host, .key = buf->key, .clock = clock, .ns = ns}, *ptr;
+
+ if (NULL != (ptr = (zbx_item_timestamp_t *)zbx_hashset_search(&buf->timestamps, &local)))
+ {
+ zabbix_log(LOG_LEVEL_WARNING, "[line %d] duplicate timestamps", line_no);
+ }
+ else
+ {
+ local.key = zbx_strdup(NULL, buf->key);
+
+ zbx_hashset_insert(&buf->timestamps, &local, sizeof(local));
+ }
+ }
+
zbx_send_batch_t *batch;
batch = sb_add_value(buf, hostname, buf->key, buf->value, clock, ns);
diff --git a/src/zabbix_sender/send_buffer.h b/src/zabbix_sender/send_buffer.h
index f8d93824bcc..24b4c8dc7e3 100644
--- a/src/zabbix_sender/send_buffer.h
+++ b/src/zabbix_sender/send_buffer.h
@@ -42,6 +42,7 @@ typedef struct
size_t kv_alloc;
zbx_hashset_t batches;
+ zbx_hashset_t timestamps;
}
zbx_send_buffer_t;
@@ -49,8 +50,8 @@ const char *get_string(const char *p, char *buf, size_t bufsize);
void sb_init(zbx_send_buffer_t *buf, int group_mode, const char *host, int with_clock, int with_ns);
void sb_destroy(zbx_send_buffer_t *buf);
-int sb_parse_line(zbx_send_buffer_t *buf, const char *line, size_t line_alloc, int immediate, struct zbx_json **out,
- char **error);
+int sb_parse_line(zbx_send_buffer_t *buf, const char *line, size_t line_alloc, int line_no, int immediate,
+ struct zbx_json **out,char **error);
struct zbx_json *sb_pop(zbx_send_buffer_t *buf);
#endif
diff --git a/src/zabbix_sender/zabbix_sender.c b/src/zabbix_sender/zabbix_sender.c
index 896bae7bf81..c5650d076fd 100644
--- a/src/zabbix_sender/zabbix_sender.c
+++ b/src/zabbix_sender/zabbix_sender.c
@@ -1447,6 +1447,7 @@ int main(int argc, char **argv)
zbx_progname = get_program_name(argv[0]);
+ zbx_init_library_time();
zbx_init_library_common(zbx_log_impl, get_zbx_progname, zbx_backtrace);
#ifndef _WINDOWS
zbx_init_library_nix(get_zbx_progname, NULL);
@@ -1619,7 +1620,8 @@ int main(int argc, char **argv)
send_mode = ZBX_SEND_IMMEDIATE;
}
- if (FAIL == sb_parse_line(&send_buffer, in_line, in_line_alloc, send_mode, &out, &error))
+ if (FAIL == sb_parse_line(&send_buffer, in_line, in_line_alloc, total_count, send_mode, &out,
+ &error))
{
zabbix_log(LOG_LEVEL_CRIT, "[line %d] %s", total_count, error);
zbx_free(error);
@@ -1720,6 +1722,7 @@ exit:
#ifndef _WINDOWS
zbx_locks_destroy();
#endif
+ zbx_deinit_library_time();
#if defined(_WINDOWS)
while (0 == WSACleanup())
;
diff --git a/src/zabbix_server/server.c b/src/zabbix_server/server.c
index 11c13ea252d..1d5019ed8a5 100644
--- a/src/zabbix_server/server.c
+++ b/src/zabbix_server/server.c
@@ -1293,6 +1293,7 @@ static void zbx_on_exit(int ret, void *on_exit_args)
zbx_config_tls_free(zbx_config_tls);
zbx_db_config_free(zbx_db_config);
zbx_deinit_library_export();
+ zbx_deinit_library_time();
exit(EXIT_SUCCESS);
}
@@ -1327,6 +1328,7 @@ int main(int argc, char **argv)
zbx_db_config = zbx_db_config_create();
/* initialize libraries before using */
+ zbx_init_library_time();
zbx_init_library_common(zbx_log_impl, get_zbx_progname, zbx_backtrace);
zbx_init_library_nix(get_zbx_progname, get_process_info_by_thread);
zbx_init_library_dbupgrade(get_zbx_program_type, get_zbx_config_timeout);
diff --git a/tests/libs/Makefile.include b/tests/libs/Makefile.include
index 11fe393f864..f853f6438bb 100644
--- a/tests/libs/Makefile.include
+++ b/tests/libs/Makefile.include
@@ -248,4 +248,4 @@ MOCK_TEST_DEPS = \
$(top_srcdir)/src/libs/zbxnum/libzbxnum.a \
$(top_srcdir)/src/libs/zbxthreads/libzbxthreads.a \
$(top_srcdir)/src/libs/zbxcomms/libzbxcomms.a \
- $(top_srcdir)/src/libs/zbxcommon/libzbxcommon.a
+ $(top_srcdir)/src/libs/zbxtime/libzbxtime.a
diff --git a/tests/libs/zbxexpr/zbx_calculate_macro_function.c b/tests/libs/zbxexpr/zbx_calculate_macro_function.c
index ab0f10c2b3b..f07ea64d634 100644
--- a/tests/libs/zbxexpr/zbx_calculate_macro_function.c
+++ b/tests/libs/zbxexpr/zbx_calculate_macro_function.c
@@ -20,37 +20,7 @@
#include "zbxexpr.h"
#include "zbxtime.h"
-time_t __wrap_time(time_t *ptr);
-
-static zbx_timespec_t vcmock_ts;
-
-/******************************************************************************
- * *
- * Purpose: sets the current time. The key must be present in input data or *
- * the test case will fail *
- * *
- ******************************************************************************/
-static void vcmock_set_time(zbx_mock_handle_t hitem, const char *key)
-{
- zbx_mock_error_t err;
- const char *data;
-
- data = zbx_mock_get_object_member_string(hitem, key);
-
- if (ZBX_MOCK_SUCCESS != (err = zbx_strtime_to_timespec(data, &vcmock_ts)))
- fail_msg("Cannot read \"%s\" parameter", key);
-}
-
-/*
- * time() emulation
- */
-time_t __wrap_time(time_t *ptr)
-{
- if (NULL != ptr)
- *ptr = vcmock_ts.sec;
-
- return vcmock_ts.sec;
-}
+#include "../../mocks/valuecache/valuecache_mock.h"
void zbx_mock_test_entry(void **state)
{
@@ -69,7 +39,7 @@ void zbx_mock_test_entry(void **state)
fail_msg("Cannot set 'TZ' environment variable: %s", zbx_strerror(errno));
handle = zbx_mock_get_parameter_handle("in");
- vcmock_set_time(handle, "time");
+ zbx_vcmock_set_time(handle, "time");
zbx_snprintf(macro_expr, MAX_STRING_LEN, "{{TIME}.fmttime(%s, %s)}",
zbx_mock_get_parameter_string("in.fmt"), zbx_mock_get_parameter_string("in.period"));
diff --git a/tests/libs/zbxtime/Makefile.am b/tests/libs/zbxtime/Makefile.am
index 441a0fa0a69..586444834aa 100644
--- a/tests/libs/zbxtime/Makefile.am
+++ b/tests/libs/zbxtime/Makefile.am
@@ -6,7 +6,8 @@ BINARIES_tests = \
zbx_tm_add \
zbx_tm_round_down \
zbx_tm_round_up \
- zbx_tm_sub
+ zbx_tm_sub \
+ zbx_timespec
noinst_PROGRAMS = $(BINARIES_tests)
@@ -102,3 +103,17 @@ zbx_tm_sub_LDADD += @SERVER_LIBS@
zbx_tm_sub_LDFLAGS = @SERVER_LDFLAGS@ $(CMOCKA_LDFLAGS)
zbx_tm_sub_CFLAGS = $(TIME_COMPILER_FLAGS)
+
+
+zbx_timespec_SOURCES = \
+ zbx_timespec.c \
+ $(COMMON_SRC_FILES)
+
+zbx_timespec_LDADD = \
+ $(TIME_LIBS)
+
+zbx_timespec_LDADD += @SERVER_LIBS@
+
+zbx_timespec_LDFLAGS = @SERVER_LDFLAGS@ $(CMOCKA_LDFLAGS)
+
+zbx_timespec_CFLAGS = $(TIME_COMPILER_FLAGS)
diff --git a/tests/libs/zbxtime/zbx_timespec.c b/tests/libs/zbxtime/zbx_timespec.c
new file mode 100644
index 00000000000..e6783bae25d
--- /dev/null
+++ b/tests/libs/zbxtime/zbx_timespec.c
@@ -0,0 +1,155 @@
+/*
+** Copyright (C) 2001-2025 Zabbix SIA
+**
+** This program is free software: you can redistribute it and/or modify it under the terms of
+** the GNU Affero General Public License as published by the Free Software Foundation, version 3.
+**
+** This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+** without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+** See the GNU Affero General Public License for more details.
+**
+** You should have received a copy of the GNU Affero General Public License along with this program.
+** If not, see .
+**/
+
+#include "zbxmocktest.h"
+#include "zbxmockdata.h"
+#include "zbxmockassert.h"
+#include "zbxmockutil.h"
+
+void __wrap_zbx_get_clock_time(int clk_id, zbx_timespec_t *ts);
+void __real_zbx_get_clock_time(int clk_id, zbx_timespec_t *ts);
+
+#define zbx_get_clock_time __real_zbx_get_clock_time
+#include "zbxtime.h"
+#include "../../../src/libs/zbxtime/time.c"
+#undef zbx_get_clock_time
+
+#define zbx_get_clock_time __wrap_zbx_get_clock_time
+#include "../../../src/libs/zbxtime/timespec.c"
+#undef zbx_get_clock_time
+
+static zbx_timespec_t *ts_mono = NULL, *ts_real = NULL;
+
+void __wrap_zbx_get_clock_time(int clk_id, zbx_timespec_t *ts)
+{
+ if (CLOCK_REALTIME == clk_id && NULL != ts_real)
+ *ts = *ts_real;
+ else if (CLOCK_MONOTONIC == clk_id && NULL != ts_mono)
+ *ts = *ts_mono;
+ else
+ __real_zbx_get_clock_time(clk_id, ts);
+}
+
+/* zbx_timespec() should never return the same value twice */
+static void test_monotonic(void)
+{
+ zbx_timespec_t ts1 = {0}, ts2 = {0};
+
+ zbx_timespec(&ts1);
+
+ for (int i = 0; i < 10000; i++)
+ {
+ zbx_timespec(&ts2);
+
+ if (ts1.sec == ts2.sec && ts2.ns == ts1.ns)
+ {
+ fail_msg("zbx_timespec() is not monotonic");
+ }
+
+ zbx_timespec(&ts1);
+ }
+}
+
+/* zbx_timespec() forward drift against system clock should be less than 250 ms */
+static void test_drift(void)
+{
+ zbx_timespec_t ts1 = {0}, ts2 = {0};
+
+ for (int i = 0; i < 10; i++)
+ {
+ __real_zbx_get_clock_time(CLOCK_REALTIME, &ts1);
+ zbx_timespec(&ts2);
+
+ int drift = zbx_timespec_compare(&ts2, &ts1);
+
+ if (ZBX_TIMESPEC_THRESHOLD_NSEC < abs(drift))
+ {
+ fail_msg("zbx_timespec() drift too long: %d", drift);
+ }
+
+ usleep(ZBX_TIMESPEC_THRESHOLD_NSEC / 1000);
+ }
+}
+
+/* when monotonic clock gives the same value zbx_timespec() should tick forward */
+static void test_stepping(void)
+{
+ zbx_timespec_t ts = {0};
+
+ __real_zbx_get_clock_time(CLOCK_REALTIME, &ts);
+ ts_mono = ts_real = &ts;
+
+ zbx_init_library_time();
+
+ zbx_timespec_t ts1 = {0}, ts2 = {0};
+
+ zbx_timespec(&ts1);
+ zbx_timespec(&ts2);
+
+ zbx_mock_assert_int_eq("zbx_timespec_t seconds", ts1.sec, ts1.sec);
+ zbx_mock_assert_int_ne("zbx_timespec ns", ts1.ns, ts2.ns);
+}
+
+/* zbx_timespec() forward drift against system clock should be less than 250 ms */
+static void test_manual_drift(void)
+{
+ zbx_timespec_t ts1 = {0}, ts2 = {0};
+
+ __real_zbx_get_clock_time(CLOCK_REALTIME, &ts1);
+ ts_real = &ts1;
+
+ __real_zbx_get_clock_time(CLOCK_MONOTONIC, &ts2);
+ ts_mono = &ts2;
+
+ zbx_init_library_time();
+
+ for (int i = 0; i < 1000; i++)
+ {
+ zbx_timespec_t ts = {0};
+
+ zbx_timespec(&ts);
+
+ int drift = zbx_timespec_compare(&ts, ts_real);
+
+ zabbix_log(LOG_LEVEL_DEBUG, "drift:%d", drift);
+
+ if (ZBX_TIMESPEC_THRESHOLD_NSEC < abs(drift))
+ {
+ fail_msg("zbx_timespec() manual drift too long: %d", drift);
+ }
+
+ /* move realtime clock by 1 ms */
+ ts_real->ns += i * 1000000;
+ }
+}
+
+void zbx_mock_test_entry(void **state)
+{
+ ZBX_UNUSED(state);
+
+ const char *test_name = zbx_mock_get_parameter_string("in.test");
+
+ zbx_mock_assert_ptr_ne("test name", NULL, test_name);
+
+ if (0 == strcmp(test_name, "monotonic"))
+ test_monotonic();
+ else if (0 == strcmp(test_name, "drift"))
+ test_drift();
+ else if (0 == strcmp(test_name, "stepping"))
+ test_stepping();
+ else if (0 == strcmp(test_name, "manual drift"))
+ test_manual_drift();
+ else
+ fail_msg("unknown test: %s", test_name);
+}
diff --git a/tests/libs/zbxtime/zbx_timespec.yaml b/tests/libs/zbxtime/zbx_timespec.yaml
new file mode 100644
index 00000000000..fbe16552515
--- /dev/null
+++ b/tests/libs/zbxtime/zbx_timespec.yaml
@@ -0,0 +1,17 @@
+---
+test case: Test that zbx_timespec is monotonic
+in:
+ test: monotonic
+---
+test case: Test that zbx_timespec drift
+in:
+ test: drift
+---
+test case: Test that zbx_timespec stepping
+in:
+ test: stepping
+---
+test case: Test zbx_timespec manual drift
+in:
+ test: manual drift
+...
diff --git a/tests/zbxmocktest.c b/tests/zbxmocktest.c
index 0181e85ac3a..fb185fce525 100644
--- a/tests/zbxmocktest.c
+++ b/tests/zbxmocktest.c
@@ -18,6 +18,7 @@
#ifndef _WINDOWS
# include "zbxnix.h"
#endif
+#include "zbxtime.h"
/* unresolved symbols needed for linking */
static unsigned char program_type = 0;
@@ -134,9 +135,11 @@ int main (void)
};
zbx_set_log_level(LOG_LEVEL_TRACE);
+ zbx_init_library_time();
zbx_init_library_common(zbx_mock_log_impl, get_zbx_progname, zbx_backtrace);
#ifndef _WINDOWS
zbx_init_library_nix(get_zbx_progname, NULL);
#endif
+
return cmocka_run_group_tests(tests, NULL, NULL);
}