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); }