summaryrefslogtreecommitdiff
path: root/shared/nm-glib-aux/nm-time-utils.c
blob: c35230536c921a2901833286b909cfe602279e7c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
// SPDX-License-Identifier: LGPL-2.1+
/* NetworkManager -- Network link manager
 *
 * (C) Copyright 2018 Red Hat, Inc.
 */

#include "nm-default.h"

#include "nm-time-utils.h"

#include "nm-logging-fwd.h"

/*****************************************************************************/

typedef struct {
	/* the offset to the native clock, in seconds. */
	gint64 offset_sec;
	clockid_t clk_id;
} GlobalState;

static const GlobalState *volatile p_global_state;

static const GlobalState *
_t_init_global_state (void)
{
	static GlobalState global_state = { };
	static gsize init_once = 0;
	const GlobalState *p;
	clockid_t clk_id;
	struct timespec tp;
	gint64 offset_sec;
	int r;

	clk_id = CLOCK_BOOTTIME;
	r = clock_gettime (clk_id, &tp);
	if (r == -1 && errno == EINVAL) {
		clk_id = CLOCK_MONOTONIC;
		r = clock_gettime (clk_id, &tp);
	}

	/* The only failure we tolerate is that CLOCK_BOOTTIME is not supported.
	 * Other than that, we rely on kernel to not fail on this. */
	g_assert (r == 0);
	g_assert (tp.tv_nsec >= 0 && tp.tv_nsec < NM_UTILS_NS_PER_SECOND);

	/* Calculate an offset for the time stamp.
	 *
	 * We always want positive values, because then we can initialize
	 * a timestamp with 0 and be sure, that it will be less then any
	 * value nm_utils_get_monotonic_timestamp_*() might return.
	 * For this to be true also for nm_utils_get_monotonic_timestamp_s() at
	 * early boot, we have to shift the timestamp to start counting at
	 * least from 1 second onward.
	 *
	 * Another advantage of shifting is, that this way we make use of the whole 31 bit
	 * range of signed int, before the time stamp for nm_utils_get_monotonic_timestamp_s()
	 * wraps (~68 years).
	 **/
	offset_sec = (- ((gint64) tp.tv_sec)) + 1;

	if (!g_once_init_enter (&init_once)) {
		/* there was a race. We expect the pointer to be fully initialized now. */
		p = g_atomic_pointer_get (&p_global_state);
		g_assert (p);
		return p;
	}

	global_state.offset_sec = offset_sec;
	global_state.clk_id = clk_id;
	p = &global_state;
	g_atomic_pointer_set (&p_global_state, p);
	g_once_init_leave (&init_once, 1);

	_nm_utils_monotonic_timestamp_initialized (&tp,
	                                           p->offset_sec,
	                                           p->clk_id == CLOCK_BOOTTIME);

	return p;
}

#define _t_get_global_state() \
	({ \
		const GlobalState *_p; \
		\
		_p = g_atomic_pointer_get (&p_global_state); \
		(G_LIKELY (_p) ? _p : _t_init_global_state ()); \
	})

#define _t_clock_gettime_eval(p, tp) \
	({ \
		struct timespec *const _tp = (tp); \
		const GlobalState *const _p2 = (p); \
		int _r; \
		\
		nm_assert (_tp); \
		\
		_r = clock_gettime (_p2->clk_id, _tp); \
		\
		nm_assert (_r == 0); \
		nm_assert (_tp->tv_nsec >= 0 && _tp->tv_nsec < NM_UTILS_NS_PER_SECOND); \
		\
		_p2; \
	})

#define _t_clock_gettime(tp) \
	_t_clock_gettime_eval (_t_get_global_state (), tp);

/*****************************************************************************/

/**
 * nm_utils_get_monotonic_timestamp_ns:
 *
 * Returns: a monotonically increasing time stamp in nanoseconds,
 * starting at an unspecified offset. See clock_gettime(), %CLOCK_BOOTTIME.
 *
 * The returned value will start counting at an undefined point
 * in the past and will always be positive.
 *
 * All the nm_utils_get_monotonic_timestamp_*s functions return the same
 * timestamp but in different scales (nsec, usec, msec, sec).
 **/
gint64
nm_utils_get_monotonic_timestamp_ns (void)
{
	const GlobalState *p;
	struct timespec tp;

	p = _t_clock_gettime (&tp);

	/* Although the result will always be positive, we return a signed
	 * integer, which makes it easier to calculate time differences (when
	 * you want to subtract signed values).
	 **/
	return (((gint64) tp.tv_sec) + p->offset_sec) * NM_UTILS_NS_PER_SECOND +
	       tp.tv_nsec;
}

/**
 * nm_utils_get_monotonic_timestamp_us:
 *
 * Returns: a monotonically increasing time stamp in microseconds,
 * starting at an unspecified offset. See clock_gettime(), %CLOCK_BOOTTIME.
 *
 * The returned value will start counting at an undefined point
 * in the past and will always be positive.
 *
 * All the nm_utils_get_monotonic_timestamp_*s functions return the same
 * timestamp but in different scales (nsec, usec, msec, sec).
 **/
gint64
nm_utils_get_monotonic_timestamp_us (void)
{
	const GlobalState *p;
	struct timespec tp;

	p = _t_clock_gettime (&tp);

	/* Although the result will always be positive, we return a signed
	 * integer, which makes it easier to calculate time differences (when
	 * you want to subtract signed values).
	 **/
	return (((gint64) tp.tv_sec) + p->offset_sec) * ((gint64) G_USEC_PER_SEC) +
	       (tp.tv_nsec / (NM_UTILS_NS_PER_SECOND/G_USEC_PER_SEC));
}

/**
 * nm_utils_get_monotonic_timestamp_ms:
 *
 * Returns: a monotonically increasing time stamp in milliseconds,
 * starting at an unspecified offset. See clock_gettime(), %CLOCK_BOOTTIME.
 *
 * The returned value will start counting at an undefined point
 * in the past and will always be positive.
 *
 * All the nm_utils_get_monotonic_timestamp_*s functions return the same
 * timestamp but in different scales (nsec, usec, msec, sec).
 **/
gint64
nm_utils_get_monotonic_timestamp_ms (void)
{
	const GlobalState *p;
	struct timespec tp;

	p = _t_clock_gettime (&tp);

	/* Although the result will always be positive, we return a signed
	 * integer, which makes it easier to calculate time differences (when
	 * you want to subtract signed values).
	 **/
	return (((gint64) tp.tv_sec) + p->offset_sec) * ((gint64) 1000) +
	       (tp.tv_nsec / (NM_UTILS_NS_PER_SECOND/1000));
}

/**
 * nm_utils_get_monotonic_timestamp_s:
 *
 * Returns: nm_utils_get_monotonic_timestamp_ms() in seconds (throwing
 * away sub second parts). The returned value will always be positive.
 *
 * This value wraps after roughly 68 years which should be fine for any
 * practical purpose.
 *
 * All the nm_utils_get_monotonic_timestamp_*s functions return the same
 * timestamp but in different scales (nsec, usec, msec, sec).
 **/
gint32
nm_utils_get_monotonic_timestamp_s (void)
{
	const GlobalState *p;
	struct timespec tp;

	p = _t_clock_gettime (&tp);

	return (((gint64) tp.tv_sec) + p->offset_sec);
}

/**
 * nm_utils_monotonic_timestamp_as_boottime:
 * @timestamp: the monotonic-timestamp that should be converted into CLOCK_BOOTTIME.
 * @timestamp_ns_per_tick: How many nanoseconds make one unit of @timestamp? E.g. if
 *   @timestamp is in unit seconds, pass %NM_UTILS_NS_PER_SECOND; if @timestamp is
 *   in nanoseconds, pass 1; if @timestamp is in milliseconds, pass %NM_UTILS_NS_PER_SECOND/1000.
 *   This must be a multiple of 10, and between 1 and %NM_UTILS_NS_PER_SECOND.
 *
 * Returns: the monotonic-timestamp as CLOCK_BOOTTIME, as returned by clock_gettime().
 *   The unit is the same as the passed in @timestamp based on @timestamp_ns_per_tick.
 *   E.g. if you passed @timestamp in as seconds, it will return boottime in seconds.
 *
 *   Note that valid monotonic-timestamps are always positive numbers (counting roughly since
 *   the application is running). However, it might make sense to calculate a timestamp from
 *   before the application was running, hence negative @timestamp is allowed. The result
 *   in that case might also be a negative timestamp (in CLOCK_BOOTTIME), which would indicate
 *   that the timestamp lies in the past before the machine was booted.
 *
 * On older kernels that don't support CLOCK_BOOTTIME, the returned time is instead CLOCK_MONOTONIC.
 **/
gint64
nm_utils_monotonic_timestamp_as_boottime (gint64 timestamp, gint64 timestamp_ns_per_tick)
{
	const GlobalState *p;
	gint64 offset;

	/* only support ns-per-tick being a multiple of 10. */
	g_return_val_if_fail (timestamp_ns_per_tick == 1
	                      || (timestamp_ns_per_tick > 0 &&
	                          timestamp_ns_per_tick <= NM_UTILS_NS_PER_SECOND &&
	                          timestamp_ns_per_tick % 10 == 0),
	                      -1);

	/* if the caller didn't yet ever fetch a monotonic-timestamp, he cannot pass any meaningful
	 * value (because he has no idea what these timestamps would be). That would be a bug. */
	nm_assert (g_atomic_pointer_get (&p_global_state));

	p = _t_get_global_state ();

	nm_assert (p->offset_sec <= 0);

	/* calculate the offset of monotonic-timestamp to boottime. offset_s is <= 1. */
	offset = p->offset_sec * (NM_UTILS_NS_PER_SECOND / timestamp_ns_per_tick);

	nm_assert (offset <= 0 && offset > G_MININT64);

	/* check for overflow (note that offset is non-positive). */
	g_return_val_if_fail (timestamp < G_MAXINT64 + offset, G_MAXINT64);

	return timestamp - offset;
}

gint64
nm_utils_clock_gettime_ns (clockid_t clockid)
{
	struct timespec tp;

	if (clock_gettime (clockid, &tp) != 0)
		return -NM_ERRNO_NATIVE (errno);
	return nm_utils_timespec_to_ns (&tp);
}

gint64
nm_utils_clock_gettime_ms (clockid_t clockid)
{
	struct timespec tp;

	if (clock_gettime (clockid, &tp) != 0)
		return -NM_ERRNO_NATIVE (errno);
	return nm_utils_timespec_to_ms (&tp);
}