1 /*
2 ** Zabbix
3 ** Copyright (C) 2001-2021 Zabbix SIA
4 **
5 ** This program is free software; you can redistribute it and/or modify
6 ** it under the terms of the GNU General Public License as published by
7 ** the Free Software Foundation; either version 2 of the License, or
8 ** (at your option) any later version.
9 **
10 ** This program is distributed in the hope that it will be useful,
11 ** but WITHOUT ANY WARRANTY; without even the implied warranty of
12 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 ** GNU General Public License for more details.
14 **
15 ** You should have received a copy of the GNU General Public License
16 ** along with this program; if not, write to the Free Software
17 ** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
18 **/
19 
20 #include "common.h"
21 #include "log.h"
22 #include "memalloc.h"
23 #include "ipc.h"
24 #include "dbcache.h"
25 #include "zbxhistory.h"
26 
27 #include "valuecache.h"
28 
29 #include "vectorimpl.h"
30 
31 /*
32  * The cache (zbx_vc_cache_t) is organized as a hashset of item records (zbx_vc_item_t).
33  *
34  * Each record holds item data (itemid, value_type), statistics (hits, last access time,...)
35  * and the historical data (timestamp,value pairs in ascending order).
36  *
37  * The historical data are stored from largest request (+timeshift) range to the
38  * current time. The data is automatically fetched from DB whenever a request
39  * exceeds cached value range.
40  *
41  * In addition to active range value cache tracks item range for last 24 hours. Once
42  * per day the active range is updated with daily range and the daily range is reset.
43  *
44  * If an item is already being cached the new values are automatically added to the cache
45  * after being written into database.
46  *
47  * When cache runs out of memory to store new items it enters in low memory mode.
48  * In low memory mode cache continues to function as before with few restrictions:
49  *   1) items that weren't accessed during the last day are removed from cache.
50  *   2) items with worst hits/values ratio might be removed from cache to free the space.
51  *   3) no new items are added to the cache.
52  *
53  * The low memory mode can't be turned off - it will persist until server is rebooted.
54  * In low memory mode a warning message is written into log every 5 minutes.
55  */
56 
57 /* the period of low memory warning messages */
58 #define ZBX_VC_LOW_MEMORY_WARNING_PERIOD	(5 * SEC_PER_MIN)
59 
60 /* time period after which value cache will switch back to normal mode */
61 #define ZBX_VC_LOW_MEMORY_RESET_PERIOD		SEC_PER_DAY
62 
63 #define ZBX_VC_LOW_MEMORY_ITEM_PRINT_LIMIT	25
64 
65 static zbx_mem_info_t	*vc_mem = NULL;
66 
67 static zbx_mutex_t	vc_lock = ZBX_MUTEX_NULL;
68 
69 /* flag indicating that the cache was explicitly locked by this process */
70 static int	vc_locked = 0;
71 
72 /* value cache enable/disable flags */
73 #define ZBX_VC_DISABLED		0
74 #define ZBX_VC_ENABLED		1
75 
76 /* value cache state, after initialization value cache is always disabled */
77 static int	vc_state = ZBX_VC_DISABLED;
78 
79 /* the value cache size */
80 extern zbx_uint64_t	CONFIG_VALUE_CACHE_SIZE;
81 
82 ZBX_MEM_FUNC_IMPL(__vc, vc_mem)
83 
84 #define VC_STRPOOL_INIT_SIZE	(1000)
85 #define VC_ITEMS_INIT_SIZE	(1000)
86 
87 #define VC_MAX_NANOSECONDS	999999999
88 
89 #define VC_MIN_RANGE			SEC_PER_MIN
90 
91 /* the range synchronization period in hours */
92 #define ZBX_VC_RANGE_SYNC_PERIOD	24
93 
94 #define ZBX_VC_ITEM_EXPIRE_PERIOD	SEC_PER_DAY
95 
96 /* the data chunk used to store data fragment */
97 typedef struct zbx_vc_chunk
98 {
99 	/* a pointer to the previous chunk or NULL if this is the tail chunk */
100 	struct zbx_vc_chunk	*prev;
101 
102 	/* a pointer to the next chunk or NULL if this is the head chunk */
103 	struct zbx_vc_chunk	*next;
104 
105 	/* the index of first (oldest) value in chunk */
106 	int			first_value;
107 
108 	/* the index of last (newest) value in chunk */
109 	int			last_value;
110 
111 	/* the number of item value slots in chunk */
112 	int			slots_num;
113 
114 	/* the item value data */
115 	zbx_history_record_t	slots[1];
116 }
117 zbx_vc_chunk_t;
118 
119 /* min/max number number of item history values to store in chunk */
120 
121 #define ZBX_VC_MIN_CHUNK_RECORDS	2
122 
123 /* the maximum number is calculated so that the chunk size does not exceed 64KB */
124 #define ZBX_VC_MAX_CHUNK_RECORDS	((64 * ZBX_KIBIBYTE - sizeof(zbx_vc_chunk_t)) / \
125 		sizeof(zbx_history_record_t) + 1)
126 
127 /* the item operational state flags */
128 #define ZBX_ITEM_STATE_CLEAN_PENDING	1
129 #define ZBX_ITEM_STATE_REMOVE_PENDING	2
130 
131 /* the value cache item data */
132 typedef struct
133 {
134 	/* the item id */
135 	zbx_uint64_t	itemid;
136 
137 	/* the item value type */
138 	unsigned char	value_type;
139 
140 	/* the item operational state flags (ZBX_ITEM_STATE_*)        */
141 	unsigned char	state;
142 
143 	/* the item status flags (ZBX_ITEM_STATUS_*)                  */
144 	unsigned char	status;
145 
146 	/* the hour when the current/global range sync was done       */
147 	unsigned char	range_sync_hour;
148 
149 	/* The total number of item values in cache.                  */
150 	/* Used to evaluate if the item must be dropped from cache    */
151 	/* in low memory situation.                                   */
152 	int		values_total;
153 
154 	/* The last time when item cache was accessed.                */
155 	/* Used to evaluate if the item must be dropped from cache    */
156 	/* in low memory situation.                                   */
157 	int		last_accessed;
158 
159 	/* reference counter indicating number of processes           */
160 	/* accessing item                                             */
161 	int		refcount;
162 
163 	/* The range of the largest request in seconds.               */
164 	/* Used to determine if data can be removed from cache.       */
165 	int		active_range;
166 
167 	/* The range for last 24 hours since active_range update.     */
168 	/* Once per day the active_range is synchronized (updated)    */
169 	/* with daily_range and the daily range is reset.             */
170 	int		daily_range;
171 
172 	/* The timestamp marking the oldest value that is guaranteed  */
173 	/* to be cached.                                              */
174 	/* The db_cached_from value is based on actual requests made  */
175 	/* to database and is used to check if the requested time     */
176 	/* interval should be cached.                                 */
177 	int		db_cached_from;
178 
179 	/* The number of cache hits for this item.                    */
180 	/* Used to evaluate if the item must be dropped from cache    */
181 	/* in low memory situation.                                   */
182 	zbx_uint64_t	hits;
183 
184 	/* the last (newest) chunk of item history data               */
185 	zbx_vc_chunk_t	*head;
186 
187 	/* the first (oldest) chunk of item history data              */
188 	zbx_vc_chunk_t	*tail;
189 }
190 zbx_vc_item_t;
191 
192 /* the value cache data  */
193 typedef struct
194 {
195 	/* the number of cache hits, used for statistics */
196 	zbx_uint64_t	hits;
197 
198 	/* the number of cache misses, used for statistics */
199 	zbx_uint64_t	misses;
200 
201 	/* value cache operating mode - see ZBX_VC_MODE_* defines */
202 	int		mode;
203 
204 	/* time when cache operating mode was changed */
205 	int		mode_time;
206 
207 	/* timestamp of the last low memory warning message */
208 	int		last_warning_time;
209 
210 	/* the minimum number of bytes to be freed when cache runs out of space */
211 	size_t		min_free_request;
212 
213 	/* the cached items */
214 	zbx_hashset_t	items;
215 
216 	/* the string pool for str, text and log item values */
217 	zbx_hashset_t	strpool;
218 }
219 zbx_vc_cache_t;
220 
221 /* the item weight data, used to determine if item can be removed from cache */
222 typedef struct
223 {
224 	/* a pointer to the value cache item */
225 	zbx_vc_item_t	*item;
226 
227 	/* the item 'weight' - <number of hits> / <number of cache records> */
228 	double		weight;
229 }
230 zbx_vc_item_weight_t;
231 
232 ZBX_VECTOR_DECL(vc_itemweight, zbx_vc_item_weight_t)
233 ZBX_VECTOR_IMPL(vc_itemweight, zbx_vc_item_weight_t)
234 
235 /* the value cache */
236 static zbx_vc_cache_t	*vc_cache = NULL;
237 
238 /* function prototypes */
239 static void	vc_history_record_copy(zbx_history_record_t *dst, const zbx_history_record_t *src, int value_type);
240 static void	vc_history_record_vector_clean(zbx_vector_history_record_t *vector, int value_type);
241 
242 static size_t	vch_item_free_cache(zbx_vc_item_t *item);
243 static size_t	vch_item_free_chunk(zbx_vc_item_t *item, zbx_vc_chunk_t *chunk);
244 static int	vch_item_add_values_at_tail(zbx_vc_item_t *item, const zbx_history_record_t *values, int values_num);
245 static void	vch_item_clean_cache(zbx_vc_item_t *item);
246 
247 /******************************************************************************
248  *                                                                            *
249  * Function: vc_try_lock                                                      *
250  *                                                                            *
251  * Purpose: locks the cache unless it was explicitly locked externally with   *
252  *          zbx_vc_lock() call.                                               *
253  *                                                                            *
254  ******************************************************************************/
vc_try_lock(void)255 static void	vc_try_lock(void)
256 {
257 	if (ZBX_VC_ENABLED == vc_state && 0 == vc_locked)
258 		zbx_mutex_lock(vc_lock);
259 }
260 
261 /******************************************************************************
262  *                                                                            *
263  * Function: vc_try_unlock                                                    *
264  *                                                                            *
265  * Purpose: unlocks the cache locked by vc_try_lock() function unless it was  *
266  *          explicitly locked externally with zbx_vc_lock() call.             *
267  *                                                                            *
268  ******************************************************************************/
vc_try_unlock(void)269 static void	vc_try_unlock(void)
270 {
271 	if (ZBX_VC_ENABLED == vc_state && 0 == vc_locked)
272 		zbx_mutex_unlock(vc_lock);
273 }
274 
275 /*********************************************************************************
276  *                                                                               *
277  * Function: vc_db_read_values_by_time                                           *
278  *                                                                               *
279  * Purpose: reads item history data from database                                *
280  *                                                                               *
281  * Parameters:  itemid        - [IN] the itemid                                  *
282  *              value_type    - [IN] the value type (see ITEM_VALUE_TYPE_* defs) *
283  *              values        - [OUT] the item history data values               *
284  *              range_start   - [IN] the interval start time                     *
285  *              range_end     - [IN] the interval end time                       *
286  *                                                                               *
287  * Return value: SUCCEED - the history data were read successfully               *
288  *               FAIL - otherwise                                                *
289  *                                                                               *
290  * Comments: This function reads all values from the specified range             *
291  *           [range_start, range_end]                                            *
292  *                                                                               *
293  *********************************************************************************/
vc_db_read_values_by_time(zbx_uint64_t itemid,int value_type,zbx_vector_history_record_t * values,int range_start,int range_end)294 static int	vc_db_read_values_by_time(zbx_uint64_t itemid, int value_type, zbx_vector_history_record_t *values,
295 		int range_start, int range_end)
296 {
297 	/* decrement interval start point because interval starting point is excluded by history backend */
298 	if (0 != range_start)
299 		range_start--;
300 
301 	return zbx_history_get_values(itemid, value_type, range_start, 0, range_end, values);
302 }
303 
304 /************************************************************************************
305  *                                                                                  *
306  * Function: vc_db_read_values_by_time_and_count                                    *
307  *                                                                                  *
308  * Purpose: reads item history data from database                                   *
309  *                                                                                  *
310  * Parameters:  itemid      - [IN] the itemid                                       *
311  *              value_type  - [IN] the value type (see ITEM_VALUE_TYPE_* defs)      *
312  *              values      - [OUT] the item history data values                    *
313  *              range_start - [IN] the interval start time                          *
314  *              count       - [IN] the number of values to read                     *
315  *              range_end   - [IN] the interval end time                            *
316  *              ts          - [IN] the requested timestamp                          *
317  *                                                                                  *
318  * Return value: SUCCEED - the history data were read successfully                  *
319  *               FAIL - otherwise                                                   *
320  *                                                                                  *
321  * Comments: This function will return the smallest data interval on seconds scale  *
322  *           that includes the requested values.                                    *
323  *                                                                                  *
324  ************************************************************************************/
vc_db_read_values_by_time_and_count(zbx_uint64_t itemid,int value_type,zbx_vector_history_record_t * values,int range_start,int count,int range_end,const zbx_timespec_t * ts)325 static int	vc_db_read_values_by_time_and_count(zbx_uint64_t itemid, int value_type,
326 		zbx_vector_history_record_t *values, int range_start, int count, int range_end,
327 		const zbx_timespec_t *ts)
328 {
329 	int	first_timestamp, last_timestamp, i, left = 0, values_start;
330 
331 	/* remember the number of values already stored in values vector */
332 	values_start = values->values_num;
333 
334 	if (0 != range_start)
335 		range_start--;
336 
337 	/* Count based requests can 'split' the data of oldest second. For example if we have    */
338 	/* values with timestamps Ta.0 Tb.0, Tb.5, Tc.0 then requesting 2 values from [0, Tc]    */
339 	/* range will return Tb.5, Tc.0,leaving Tb.0 value in database. However because          */
340 	/* second is the smallest time unit history backends can work with, data must be cached  */
341 	/* by second intervals - it cannot have some values from Tb cached and some not.         */
342 	/* This is achieved by two means:                                                        */
343 	/*   1) request more (by one) values than we need. In most cases there will be no        */
344 	/*      multiple values per second (exceptions are logs and trapper items) - for example */
345 	/*      Ta.0, Tb.0, Tc.0. We need 2 values from Tc. Requesting 3 values gets us          */
346 	/*      Ta.0, Tb.0, Tc.0. As Ta != Tb we can be sure that all values from the last       */
347 	/*      timestamp (Tb) have been cached. So we can drop Ta.0 and return Tb.0, Tc.0.      */
348 	/*   2) Re-read the last second. For example if we have values with timestamps           */
349 	/*      Ta.0 Tb.0, Tb.5, Tc.0, then requesting 3 values from Tc gets us Tb.0, Tb.5, Tc.0.*/
350 	/*      Now we cannot be sure that there are no more values with Tb.* timestamp. So the  */
351 	/*      only thing we can do is to:                                                      */
352 	/*        a) remove values with Tb.* timestamp from result,                              */
353 	/*        b) read all values with Tb.* timestamp from database,                          */
354 	/*        c) add read values to the result.                                              */
355 	if (FAIL == zbx_history_get_values(itemid, value_type, range_start, count + 1, range_end, values))
356 		return FAIL;
357 
358 	/* returned less than requested - all values are read */
359 	if (count > values->values_num - values_start)
360 		return SUCCEED;
361 
362 	/* Check if some of values aren't past the required range. For example we have values    */
363 	/* with timestamps Ta.0, Tb.0, Tb.5. As history backends work on seconds interval we     */
364 	/* can only request values from Tb which would include Tb.5, even if the requested       */
365 	/* end timestamp was less (for example Tb.0);                                            */
366 
367 	/* values from history backend are sorted in descending order by timestamp seconds */
368 	first_timestamp = values->values[values->values_num - 1].timestamp.sec;
369 	last_timestamp = values->values[values_start].timestamp.sec;
370 
371 	for (i = values_start; i < values->values_num && values->values[i].timestamp.sec == last_timestamp; i++)
372 	{
373 		if (0 > zbx_timespec_compare(ts, &values->values[i].timestamp))
374 			left++;
375 	}
376 
377 	/* read missing data */
378 	if (0 != left)
379 	{
380 		int	offset;
381 
382 		/* drop the first (oldest) second to ensure range cutoff at full second */
383 		while (0 < values->values_num && values->values[values->values_num - 1].timestamp.sec == first_timestamp)
384 		{
385 			values->values_num--;
386 			zbx_history_record_clear(&values->values[values->values_num], value_type);
387 			left++;
388 		}
389 
390 		offset = values->values_num;
391 
392 		if (FAIL == zbx_history_get_values(itemid, value_type, range_start, left, first_timestamp, values))
393 			return FAIL;
394 
395 		/* returned less than requested - all values are read */
396 		if (left > values->values_num - offset)
397 			return SUCCEED;
398 
399 		first_timestamp = values->values[values->values_num - 1].timestamp.sec;
400 	}
401 
402 	/* drop the first (oldest) second to ensure range cutoff at full second */
403 	while (0 < values->values_num && values->values[values->values_num - 1].timestamp.sec == first_timestamp)
404 	{
405 		values->values_num--;
406 		zbx_history_record_clear(&values->values[values->values_num], value_type);
407 	}
408 
409 	/* check if there are enough values matching the request range */
410 
411 	for (i = values_start; i < values->values_num; i++)
412 	{
413 		if (0 <= zbx_timespec_compare(ts, &values->values[i].timestamp))
414 			count--;
415 	}
416 
417 	if (0 >= count)
418 		return SUCCEED;
419 
420 	/* re-read the first (oldest) second */
421 	return zbx_history_get_values(itemid, value_type, first_timestamp - 1, 0, first_timestamp, values);
422 }
423 
424 /******************************************************************************
425  *                                                                            *
426  * Function: vc_db_get_values                                                 *
427  *                                                                            *
428  * Purpose: get item history data for the specified time period directly from *
429  *          database                                                          *
430  *                                                                            *
431  * Parameters: itemid     - [IN] the item id                                  *
432  *             value_type - [IN] the item value type                          *
433  *             values     - [OUT] the item history data stored time/value     *
434  *                          pairs in descending order                         *
435  *             seconds    - [IN] the time period to retrieve data for         *
436  *             count      - [IN] the number of history values to retrieve     *
437  *             ts         - [IN] the period end timestamp                     *
438  *                                                                            *
439  * Return value:  SUCCEED - the item history data was retrieved successfully  *
440  *                FAIL    - the item history data was not retrieved           *
441  *                                                                            *
442  ******************************************************************************/
vc_db_get_values(zbx_uint64_t itemid,int value_type,zbx_vector_history_record_t * values,int seconds,int count,const zbx_timespec_t * ts)443 static int	vc_db_get_values(zbx_uint64_t itemid, int value_type, zbx_vector_history_record_t *values, int seconds,
444 		int count, const zbx_timespec_t *ts)
445 {
446 	int	ret = FAIL, i, j, range_start;
447 
448 	if (0 == count)
449 	{
450 		/* read one more second to cover for possible nanosecond shift */
451 		ret = vc_db_read_values_by_time(itemid, value_type, values, ts->sec - seconds, ts->sec);
452 	}
453 	else
454 	{
455 		/* read one more second to cover for possible nanosecond shift */
456 		range_start = (0 == seconds ? 0 : ts->sec - seconds);
457 		ret = vc_db_read_values_by_time_and_count(itemid, value_type, values, range_start, count, ts->sec, ts);
458 	}
459 
460 	if (SUCCEED != ret)
461 		return ret;
462 
463 	zbx_vector_history_record_sort(values, (zbx_compare_func_t)zbx_history_record_compare_desc_func);
464 
465 	/* History backend returns values by full second intervals. With nanosecond resolution */
466 	/* some of returned values might be outside the requested range, for example:          */
467 	/*   returned values: |.o...o..o.|.o...o..o.|.o...o..o.|.o...o..o.|                    */
468 	/*   request range:        \_______________________________/                           */
469 	/* Check if there are any values before and past the requested range and remove them.  */
470 
471 	/* find the last value with timestamp less or equal to the requested range end point */
472 	for (i = 0; i < values->values_num; i++)
473 	{
474 		if (0 >= zbx_timespec_compare(&values->values[i].timestamp, ts))
475 			break;
476 	}
477 
478 	/* all values are past requested range (timestamp greater than requested), return empty vector */
479 	if (i == values->values_num)
480 	{
481 		vc_history_record_vector_clean(values, value_type);
482 		return SUCCEED;
483 	}
484 
485 	/* remove values with timestamp greater than the requested */
486 	if (0 != i)
487 	{
488 		for (j = 0; j < i; j++)
489 			zbx_history_record_clear(&values->values[j], value_type);
490 
491 		for (j = 0; i < values->values_num; i++, j++)
492 			values->values[j] = values->values[i];
493 
494 		values->values_num = j;
495 	}
496 
497 	/* for count based requests remove values exceeding requested count */
498 	if (0 != count)
499 	{
500 		while (count < values->values_num)
501 			zbx_history_record_clear(&values->values[--values->values_num], value_type);
502 	}
503 
504 	/* for time based requests remove values with timestamp outside requested range */
505 	if (0 != seconds)
506 	{
507 		zbx_timespec_t	start = {ts->sec - seconds, ts->ns};
508 
509 		while (0 < values->values_num &&
510 				0 >= zbx_timespec_compare(&values->values[values->values_num - 1].timestamp, &start))
511 		{
512 			zbx_history_record_clear(&values->values[--values->values_num], value_type);
513 		}
514 	}
515 
516 	return SUCCEED;
517 }
518 
519 /******************************************************************************************************************
520  *                                                                                                                *
521  * Common API                                                                                                     *
522  *                                                                                                                *
523  ******************************************************************************************************************/
524 
525 /******************************************************************************
526  *                                                                            *
527  * String pool definitions & functions                                        *
528  *                                                                            *
529  ******************************************************************************/
530 
531 #define REFCOUNT_FIELD_SIZE	sizeof(zbx_uint32_t)
532 
vc_strpool_hash_func(const void * data)533 static zbx_hash_t	vc_strpool_hash_func(const void *data)
534 {
535 	return ZBX_DEFAULT_STRING_HASH_FUNC((char *)data + REFCOUNT_FIELD_SIZE);
536 }
537 
vc_strpool_compare_func(const void * d1,const void * d2)538 static int	vc_strpool_compare_func(const void *d1, const void *d2)
539 {
540 	return strcmp((char *)d1 + REFCOUNT_FIELD_SIZE, (char *)d2 + REFCOUNT_FIELD_SIZE);
541 }
542 
543 /******************************************************************************
544  *                                                                            *
545  * Function: vc_item_weight_compare_func                                      *
546  *                                                                            *
547  * Purpose: compares two item weight data structures by their 'weight'        *
548  *                                                                            *
549  * Parameters: d1   - [IN] the first item weight data structure               *
550  *             d2   - [IN] the second item weight data structure              *
551  *                                                                            *
552  ******************************************************************************/
vc_item_weight_compare_func(const zbx_vc_item_weight_t * d1,const zbx_vc_item_weight_t * d2)553 static int	vc_item_weight_compare_func(const zbx_vc_item_weight_t *d1, const zbx_vc_item_weight_t *d2)
554 {
555 	ZBX_RETURN_IF_NOT_EQUAL(d1->weight, d2->weight);
556 
557 	return 0;
558 }
559 
560 /******************************************************************************
561  *                                                                            *
562  * Function: vc_history_logfree                                               *
563  *                                                                            *
564  * Purpose: frees history log and all resources allocated for it              *
565  *                                                                            *
566  * Parameters: log   - [IN] the history log to free                           *
567  *                                                                            *
568  ******************************************************************************/
vc_history_logfree(zbx_log_value_t * log)569 static void	vc_history_logfree(zbx_log_value_t *log)
570 {
571 	zbx_free(log->source);
572 	zbx_free(log->value);
573 	zbx_free(log);
574 }
575 
576 /******************************************************************************
577  *                                                                            *
578  * Function: vc_history_logdup                                                *
579  *                                                                            *
580  * Purpose: duplicates history log by allocating necessary resources and      *
581  *          copying the target log values.                                    *
582  *                                                                            *
583  * Parameters: log   - [IN] the history log to duplicate                      *
584  *                                                                            *
585  * Return value: the duplicated history log                                   *
586  *                                                                            *
587  ******************************************************************************/
vc_history_logdup(const zbx_log_value_t * log)588 static zbx_log_value_t	*vc_history_logdup(const zbx_log_value_t *log)
589 {
590 	zbx_log_value_t	*plog;
591 
592 	plog = (zbx_log_value_t *)zbx_malloc(NULL, sizeof(zbx_log_value_t));
593 
594 	plog->timestamp = log->timestamp;
595 	plog->logeventid = log->logeventid;
596 	plog->severity = log->severity;
597 	plog->source = (NULL == log->source ? NULL : zbx_strdup(NULL, log->source));
598 	plog->value = zbx_strdup(NULL, log->value);
599 
600 	return plog;
601 }
602 
603 /******************************************************************************
604  *                                                                            *
605  * Function: vc_history_record_vector_clean                                   *
606  *                                                                            *
607  * Purpose: releases resources allocated to store history records             *
608  *                                                                            *
609  * Parameters: vector      - [IN] the history record vector                   *
610  *             value_type  - [IN] the type of vector values                   *
611  *                                                                            *
612  ******************************************************************************/
vc_history_record_vector_clean(zbx_vector_history_record_t * vector,int value_type)613 static void	vc_history_record_vector_clean(zbx_vector_history_record_t *vector, int value_type)
614 {
615 	int	i;
616 
617 	switch (value_type)
618 	{
619 		case ITEM_VALUE_TYPE_STR:
620 		case ITEM_VALUE_TYPE_TEXT:
621 			for (i = 0; i < vector->values_num; i++)
622 				zbx_free(vector->values[i].value.str);
623 
624 			break;
625 		case ITEM_VALUE_TYPE_LOG:
626 			for (i = 0; i < vector->values_num; i++)
627 				vc_history_logfree(vector->values[i].value.log);
628 	}
629 
630 	zbx_vector_history_record_clear(vector);
631 }
632 
633 /******************************************************************************
634  *                                                                            *
635  * Function: vc_update_statistics                                             *
636  *                                                                            *
637  * Purpose: updates cache and item statistics                                 *
638  *                                                                            *
639  * Parameters: item    - [IN] the item (optional)                             *
640  *             hits    - [IN] the number of hits to add                       *
641  *             misses  - [IN] the number of misses to add                     *
642  *                                                                            *
643  * Comments: The misses are added only to cache statistics, while hits are    *
644  *           added to both - item and cache statistics.                       *
645  *                                                                            *
646  ******************************************************************************/
vc_update_statistics(zbx_vc_item_t * item,int hits,int misses)647 static void	vc_update_statistics(zbx_vc_item_t *item, int hits, int misses)
648 {
649 	if (NULL != item)
650 	{
651 		item->hits += hits;
652 		item->last_accessed = time(NULL);
653 	}
654 
655 	if (ZBX_VC_ENABLED == vc_state)
656 	{
657 		vc_cache->hits += hits;
658 		vc_cache->misses += misses;
659 	}
660 }
661 
662 /******************************************************************************
663  *                                                                            *
664  * Function: vc_compare_items_by_total_values                                 *
665  *                                                                            *
666  * Purpose: is used to sort items by value count in descending order          *
667  *                                                                            *
668  ******************************************************************************/
vc_compare_items_by_total_values(const void * d1,const void * d2)669 static int	vc_compare_items_by_total_values(const void *d1, const void *d2)
670 {
671 	zbx_vc_item_t	*c1 = *(zbx_vc_item_t **)d1;
672 	zbx_vc_item_t	*c2 = *(zbx_vc_item_t **)d2;
673 
674 	ZBX_RETURN_IF_NOT_EQUAL(c2->values_total, c1->values_total);
675 
676 	return 0;
677 }
678 
679 /******************************************************************************
680  *                                                                            *
681  * Function: vc_dump_items_statistics                                         *
682  *                                                                            *
683  * Purpose: find out items responsible for low memory                         *
684  *                                                                            *
685  ******************************************************************************/
vc_dump_items_statistics(void)686 static void	vc_dump_items_statistics(void)
687 {
688 	zbx_vc_item_t		*item;
689 	zbx_hashset_iter_t	iter;
690 	int			i, total = 0, limit;
691 	zbx_vector_ptr_t	items;
692 
693 	zabbix_log(LOG_LEVEL_WARNING, "=== most used items statistics for value cache ===");
694 
695 	zbx_vector_ptr_create(&items);
696 
697 	zbx_hashset_iter_reset(&vc_cache->items, &iter);
698 
699 	while (NULL != (item = (zbx_vc_item_t *)zbx_hashset_iter_next(&iter)))
700 	{
701 		zbx_vector_ptr_append(&items, item);
702 		total += item->values_total;
703 	}
704 
705 	zbx_vector_ptr_sort(&items, vc_compare_items_by_total_values);
706 
707 	for (i = 0, limit = MIN(items.values_num, ZBX_VC_LOW_MEMORY_ITEM_PRINT_LIMIT); i < limit; i++)
708 	{
709 		item = (zbx_vc_item_t *)items.values[i];
710 
711 		zabbix_log(LOG_LEVEL_WARNING, "itemid:" ZBX_FS_UI64 " active range:%d hits:" ZBX_FS_UI64 " count:%d"
712 				" perc:" ZBX_FS_DBL "%%", item->itemid, item->active_range, item->hits,
713 				item->values_total, 100 * (double)item->values_total / total);
714 	}
715 
716 	zbx_vector_ptr_destroy(&items);
717 
718 	zabbix_log(LOG_LEVEL_WARNING, "==================================================");
719 }
720 
721 /******************************************************************************
722  *                                                                            *
723  * Function: vc_warn_low_memory                                               *
724  *                                                                            *
725  * Purpose: logs low memory warning                                           *
726  *                                                                            *
727  * Comments: The low memory warning is written to log every 5 minutes when    *
728  *           cache is working in the low memory mode.                         *
729  *                                                                            *
730  ******************************************************************************/
vc_warn_low_memory(void)731 static void	vc_warn_low_memory(void)
732 {
733 	int	now;
734 
735 	now = time(NULL);
736 
737 	if (now - vc_cache->mode_time > ZBX_VC_LOW_MEMORY_RESET_PERIOD)
738 	{
739 		vc_cache->mode = ZBX_VC_MODE_NORMAL;
740 		vc_cache->mode_time = now;
741 
742 		zabbix_log(LOG_LEVEL_WARNING, "value cache has been switched from low memory to normal operation mode");
743 	}
744 	else if (now - vc_cache->last_warning_time > ZBX_VC_LOW_MEMORY_WARNING_PERIOD)
745 	{
746 		vc_cache->last_warning_time = now;
747 		vc_dump_items_statistics();
748 		zbx_mem_dump_stats(LOG_LEVEL_WARNING, vc_mem);
749 
750 		zabbix_log(LOG_LEVEL_WARNING, "value cache is fully used: please increase ValueCacheSize"
751 				" configuration parameter");
752 	}
753 }
754 
755 /******************************************************************************
756  *                                                                            *
757  * Function: vc_release_unused_items                                          *
758  *                                                                            *
759  * Purpose: frees space in cache by dropping items not accessed for more than *
760  *          24 hours                                                          *
761  *                                                                            *
762  * Parameters: source_item - [IN] the item requesting more space to store its *
763  *                                data                                        *
764  *                                                                            *
765  * Return value:  number of bytes freed                                       *
766  *                                                                            *
767  ******************************************************************************/
vc_release_unused_items(const zbx_vc_item_t * source_item)768 static size_t	vc_release_unused_items(const zbx_vc_item_t *source_item)
769 {
770 	int			timestamp;
771 	zbx_hashset_iter_t	iter;
772 	zbx_vc_item_t		*item;
773 	size_t			freed = 0;
774 
775 	if (NULL == vc_cache)
776 		return freed;
777 
778 	timestamp = time(NULL) - ZBX_VC_ITEM_EXPIRE_PERIOD;
779 
780 	zbx_hashset_iter_reset(&vc_cache->items, &iter);
781 
782 	while (NULL != (item = (zbx_vc_item_t *)zbx_hashset_iter_next(&iter)))
783 	{
784 		if (item->last_accessed < timestamp && 0 == item->refcount && source_item != item)
785 		{
786 			freed += vch_item_free_cache(item) + sizeof(zbx_vc_item_t);
787 			zbx_hashset_iter_remove(&iter);
788 		}
789 	}
790 
791 	return freed;
792 }
793 
794 /******************************************************************************
795  *                                                                            *
796  * Function: zbx_vc_housekeeping_value_cache                                  *
797  *                                                                            *
798  * Purpose: release unused items from value cache                             *
799  *                                                                            *
800  * Comments: If unused items are not cleared from value cache periodically    *
801  *           then they will only be cleared when value cache is full, see     *
802  *           vc_release_space().                                              *
803  *                                                                            *
804  ******************************************************************************/
zbx_vc_housekeeping_value_cache(void)805 void	zbx_vc_housekeeping_value_cache(void)
806 {
807 	vc_try_lock();
808 	vc_release_unused_items(NULL);
809 	vc_try_unlock();
810 }
811 
812 /******************************************************************************
813  *                                                                            *
814  * Function: vc_release_space                                                 *
815  *                                                                            *
816  * Purpose: frees space in cache to store the specified number of bytes by    *
817  *          dropping the least accessed items                                 *
818  *                                                                            *
819  * Parameters: item  - [IN] the item requesting more space to store its data  *
820  *             space - [IN] the number of bytes to free                       *
821  *                                                                            *
822  * Comments: The caller item must not be removed from cache to avoid          *
823  *           complications (ie - checking if item still is in cache every     *
824  *           time after calling vc_free_space() function).                    *
825  *           vc_free_space() attempts to free at least min_free_request       *
826  *           bytes of space to reduce number of space release requests.       *
827  *                                                                            *
828  ******************************************************************************/
vc_release_space(zbx_vc_item_t * source_item,size_t space)829 static void	vc_release_space(zbx_vc_item_t *source_item, size_t space)
830 {
831 	zbx_hashset_iter_t		iter;
832 	zbx_vc_item_t			*item;
833 	int				i;
834 	size_t				freed;
835 	zbx_vector_vc_itemweight_t	items;
836 
837 	/* reserve at least min_free_request bytes to avoid spamming with free space requests */
838 	if (space < vc_cache->min_free_request)
839 		space = vc_cache->min_free_request;
840 
841 	/* first remove items with the last accessed time older than a day */
842 	if ((freed = vc_release_unused_items(source_item)) >= space)
843 		return;
844 
845 	/* failed to free enough space by removing old items, entering low memory mode */
846 	vc_cache->mode = ZBX_VC_MODE_LOWMEM;
847 	vc_cache->mode_time = time(NULL);
848 
849 	vc_warn_low_memory();
850 
851 	/* remove items with least hits/size ratio */
852 	zbx_vector_vc_itemweight_create(&items);
853 
854 	zbx_hashset_iter_reset(&vc_cache->items, &iter);
855 
856 	while (NULL != (item = (zbx_vc_item_t *)zbx_hashset_iter_next(&iter)))
857 	{
858 		/* don't remove the item that requested the space and also keep */
859 		/* items currently being accessed                               */
860 		if (0 == item->refcount)
861 		{
862 			zbx_vc_item_weight_t	weight = {.item = item};
863 
864 			if (0 < item->values_total)
865 				weight.weight = (double)item->hits / item->values_total;
866 
867 			zbx_vector_vc_itemweight_append_ptr(&items, &weight);
868 		}
869 	}
870 
871 	zbx_vector_vc_itemweight_sort(&items, (zbx_compare_func_t)vc_item_weight_compare_func);
872 
873 	for (i = 0; i < items.values_num && freed < space; i++)
874 	{
875 		item = items.values[i].item;
876 
877 		freed += vch_item_free_cache(item) + sizeof(zbx_vc_item_t);
878 		zbx_hashset_remove_direct(&vc_cache->items, item);
879 	}
880 	zbx_vector_vc_itemweight_destroy(&items);
881 }
882 
883 /******************************************************************************
884  *                                                                            *
885  * Function: vc_history_record_copy                                           *
886  *                                                                            *
887  * Purpose: copies history value                                              *
888  *                                                                            *
889  * Parameters: dst        - [OUT] a pointer to the destination value          *
890  *             src        - [IN] a pointer to the source value                *
891  *             value_type - [IN] the value type (see ITEM_VALUE_TYPE_* defs)  *
892  *                                                                            *
893  * Comments: Additional memory is allocated to store string, text and log     *
894  *           value contents. This memory must be freed by the caller.         *
895  *                                                                            *
896  ******************************************************************************/
vc_history_record_copy(zbx_history_record_t * dst,const zbx_history_record_t * src,int value_type)897 static void	vc_history_record_copy(zbx_history_record_t *dst, const zbx_history_record_t *src, int value_type)
898 {
899 	dst->timestamp = src->timestamp;
900 
901 	switch (value_type)
902 	{
903 		case ITEM_VALUE_TYPE_STR:
904 		case ITEM_VALUE_TYPE_TEXT:
905 			dst->value.str = zbx_strdup(NULL, src->value.str);
906 			break;
907 		case ITEM_VALUE_TYPE_LOG:
908 			dst->value.log = vc_history_logdup(src->value.log);
909 			break;
910 		default:
911 			dst->value = src->value;
912 	}
913 }
914 
915 /******************************************************************************
916  *                                                                            *
917  * Function: vc_history_record_vector_append                                  *
918  *                                                                            *
919  * Purpose: appends the specified value to value vector                       *
920  *                                                                            *
921  * Parameters: vector     - [IN/OUT] the value vector                         *
922  *             value_type - [IN] the type of value to append                  *
923  *             value      - [IN] the value to append                          *
924  *                                                                            *
925  * Comments: Additional memory is allocated to store string, text and log     *
926  *           value contents. This memory must be freed by the caller.         *
927  *                                                                            *
928  ******************************************************************************/
vc_history_record_vector_append(zbx_vector_history_record_t * vector,int value_type,zbx_history_record_t * value)929 static void	vc_history_record_vector_append(zbx_vector_history_record_t *vector, int value_type,
930 		zbx_history_record_t *value)
931 {
932 	zbx_history_record_t	record;
933 
934 	vc_history_record_copy(&record, value, value_type);
935 	zbx_vector_history_record_append_ptr(vector, &record);
936 }
937 
938 /******************************************************************************
939  *                                                                            *
940  * Function: vc_item_malloc                                                   *
941  *                                                                            *
942  * Purpose: allocate cache memory to store item's resources                   *
943  *                                                                            *
944  * Parameters: item   - [IN] the item                                         *
945  *             size   - [IN] the number of bytes to allocate                  *
946  *                                                                            *
947  * Return value:  The pointer to allocated memory or NULL if there is not     *
948  *                enough shared memory.                                       *
949  *                                                                            *
950  * Comments: If allocation fails this function attempts to free the required  *
951  *           space in cache by calling vc_free_space() and tries again. If it *
952  *           still fails a NULL value is returned.                            *
953  *                                                                            *
954  ******************************************************************************/
vc_item_malloc(zbx_vc_item_t * item,size_t size)955 static void	*vc_item_malloc(zbx_vc_item_t *item, size_t size)
956 {
957 	char	*ptr;
958 
959 	if (NULL == (ptr = (char *)__vc_mem_malloc_func(NULL, size)))
960 	{
961 		/* If failed to allocate required memory, try to free space in      */
962 		/* cache and allocate again. If there still is not enough space -   */
963 		/* return NULL as failure.                                          */
964 		vc_release_space(item, size);
965 		ptr = (char *)__vc_mem_malloc_func(NULL, size);
966 	}
967 
968 	return ptr;
969 }
970 
971 /******************************************************************************
972  *                                                                            *
973  * Function: vc_item_strdup                                                   *
974  *                                                                            *
975  * Purpose: copies string to the cache memory                                 *
976  *                                                                            *
977  * Parameters: item  - [IN] the item                                          *
978  *             str   - [IN] the string to copy                                *
979  *                                                                            *
980  * Return value:  The pointer to the copied string or NULL if there was not   *
981  *                enough space in cache.                                      *
982  *                                                                            *
983  * Comments: If the string pool already contains matching string, then its    *
984  *           reference counter is incremented and the string returned.        *
985  *                                                                            *
986  *           Otherwise cache memory is allocated to store the specified       *
987  *           string. If the allocation fails this function attempts to free   *
988  *           the required space in cache by calling vc_release_space() and    *
989  *           tries again. If it still fails then a NULL value is returned.    *
990  *                                                                            *
991  ******************************************************************************/
vc_item_strdup(zbx_vc_item_t * item,const char * str)992 static char	*vc_item_strdup(zbx_vc_item_t *item, const char *str)
993 {
994 	void	*ptr;
995 
996 	ptr = zbx_hashset_search(&vc_cache->strpool, str - REFCOUNT_FIELD_SIZE);
997 
998 	if (NULL == ptr)
999 	{
1000 		int	tries = 0;
1001 		size_t	len;
1002 
1003 		len = strlen(str) + 1;
1004 
1005 		while (NULL == (ptr = zbx_hashset_insert_ext(&vc_cache->strpool, str - REFCOUNT_FIELD_SIZE,
1006 				REFCOUNT_FIELD_SIZE + len, REFCOUNT_FIELD_SIZE)))
1007 		{
1008 			/* If there is not enough space - free enough to store string + hashset entry overhead */
1009 			/* and try inserting one more time. If it fails again, then fail the function.         */
1010 			if (0 == tries++)
1011 				vc_release_space(item, len + REFCOUNT_FIELD_SIZE + sizeof(ZBX_HASHSET_ENTRY_T));
1012 			else
1013 				return NULL;
1014 		}
1015 
1016 		*(zbx_uint32_t *)ptr = 0;
1017 	}
1018 
1019 	(*(zbx_uint32_t *)ptr)++;
1020 
1021 	return (char *)ptr + REFCOUNT_FIELD_SIZE;
1022 }
1023 
1024 /******************************************************************************
1025  *                                                                            *
1026  * Function: vc_item_strfree                                                  *
1027  *                                                                            *
1028  * Purpose: removes string from cache string pool                             *
1029  *                                                                            *
1030  * Parameters: str   - [IN] the string to remove                              *
1031  *                                                                            *
1032  * Return value: the number of bytes freed                                    *
1033  *                                                                            *
1034  * Comments: This function decrements the string reference counter and        *
1035  *           removes it from the string pool when counter becomes zero.       *
1036  *                                                                            *
1037  *           Note - only strings created with vc_item_strdup() function must  *
1038  *           be freed with vc_item_strfree().                                 *
1039  *                                                                            *
1040  ******************************************************************************/
vc_item_strfree(char * str)1041 static size_t	vc_item_strfree(char *str)
1042 {
1043 	size_t	freed = 0;
1044 
1045 	if (NULL != str)
1046 	{
1047 		void	*ptr = str - REFCOUNT_FIELD_SIZE;
1048 
1049 		if (0 == --(*(zbx_uint32_t *)ptr))
1050 		{
1051 			freed = strlen(str) + REFCOUNT_FIELD_SIZE + 1;
1052 			zbx_hashset_remove_direct(&vc_cache->strpool, ptr);
1053 		}
1054 	}
1055 
1056 	return freed;
1057 }
1058 
1059 /******************************************************************************
1060  *                                                                            *
1061  * Function: vc_item_logdup                                                   *
1062  *                                                                            *
1063  * Purpose: copies log value to the cache memory                              *
1064  *                                                                            *
1065  * Parameters: item  - [IN] the item                                          *
1066  *             log   - [IN] the log value to copy                             *
1067  *                                                                            *
1068  * Return value:  The pointer to the copied log value or NULL if there was    *
1069  *                not enough space in cache.                                  *
1070  *                                                                            *
1071  * Comments: Cache memory is allocated to store the log value. If the         *
1072  *           allocation fails this function attempts to free the required     *
1073  *           space in cache by calling vc_release_space() and tries again.    *
1074  *           If it still fails then a NULL value is returned.                 *
1075  *                                                                            *
1076  ******************************************************************************/
vc_item_logdup(zbx_vc_item_t * item,const zbx_log_value_t * log)1077 static zbx_log_value_t	*vc_item_logdup(zbx_vc_item_t *item, const zbx_log_value_t *log)
1078 {
1079 	zbx_log_value_t	*plog = NULL;
1080 
1081 	if (NULL == (plog = (zbx_log_value_t *)vc_item_malloc(item, sizeof(zbx_log_value_t))))
1082 		return NULL;
1083 
1084 	plog->timestamp = log->timestamp;
1085 	plog->logeventid = log->logeventid;
1086 	plog->severity = log->severity;
1087 
1088 	if (NULL != log->source)
1089 	{
1090 		if (NULL == (plog->source = vc_item_strdup(item, log->source)))
1091 			goto fail;
1092 	}
1093 	else
1094 		plog->source = NULL;
1095 
1096 	if (NULL == (plog->value = vc_item_strdup(item, log->value)))
1097 		goto fail;
1098 
1099 	return plog;
1100 fail:
1101 	vc_item_strfree(plog->source);
1102 
1103 	__vc_mem_free_func(plog);
1104 
1105 	return NULL;
1106 }
1107 
1108 /******************************************************************************
1109  *                                                                            *
1110  * Function: vc_item_logfree                                                  *
1111  *                                                                            *
1112  * Purpose: removes log resource from cache memory                            *
1113  *                                                                            *
1114  * Parameters: str   - [IN] the log to remove                                 *
1115  *                                                                            *
1116  * Return value: the number of bytes freed                                    *
1117  *                                                                            *
1118  * Comments: Note - only logs created with vc_item_logdup() function must     *
1119  *           be freed with vc_item_logfree().                                 *
1120  *                                                                            *
1121  ******************************************************************************/
vc_item_logfree(zbx_log_value_t * log)1122 static size_t	vc_item_logfree(zbx_log_value_t *log)
1123 {
1124 	size_t	freed = 0;
1125 
1126 	if (NULL != log)
1127 	{
1128 		freed += vc_item_strfree(log->source);
1129 		freed += vc_item_strfree(log->value);
1130 
1131 		__vc_mem_free_func(log);
1132 		freed += sizeof(zbx_log_value_t);
1133 	}
1134 
1135 	return freed;
1136 }
1137 
1138 /******************************************************************************
1139  *                                                                            *
1140  * Function: vc_item_free_values                                              *
1141  *                                                                            *
1142  * Purpose: frees cache resources of the specified item value range           *
1143  *                                                                            *
1144  * Parameters: item    - [IN] the item                                        *
1145  *             values  - [IN] the target value array                          *
1146  *             first   - [IN] the first value to free                         *
1147  *             last    - [IN] the last value to free                          *
1148  *                                                                            *
1149  * Return value: the number of bytes freed                                    *
1150  *                                                                            *
1151  ******************************************************************************/
vc_item_free_values(zbx_vc_item_t * item,zbx_history_record_t * values,int first,int last)1152 static size_t	vc_item_free_values(zbx_vc_item_t *item, zbx_history_record_t *values, int first, int last)
1153 {
1154 	size_t	freed = 0;
1155 	int 	i;
1156 
1157 	switch (item->value_type)
1158 	{
1159 		case ITEM_VALUE_TYPE_STR:
1160 		case ITEM_VALUE_TYPE_TEXT:
1161 			for (i = first; i <= last; i++)
1162 				freed += vc_item_strfree(values[i].value.str);
1163 			break;
1164 		case ITEM_VALUE_TYPE_LOG:
1165 			for (i = first; i <= last; i++)
1166 				freed += vc_item_logfree(values[i].value.log);
1167 			break;
1168 	}
1169 
1170 	item->values_total -= (last - first + 1);
1171 
1172 	return freed;
1173 }
1174 
1175 /******************************************************************************
1176  *                                                                            *
1177  * Function: vc_remove_item                                                   *
1178  *                                                                            *
1179  * Purpose: removes item from cache and frees resources allocated for it      *
1180  *                                                                            *
1181  * Parameters: item    - [IN] the item                                        *
1182  *                                                                            *
1183  ******************************************************************************/
vc_remove_item(zbx_vc_item_t * item)1184 static void	vc_remove_item(zbx_vc_item_t *item)
1185 {
1186 	vch_item_free_cache(item);
1187 	zbx_hashset_remove_direct(&vc_cache->items, item);
1188 }
1189 
1190 /******************************************************************************
1191  *                                                                            *
1192  * Function: vc_item_addref                                                   *
1193  *                                                                            *
1194  * Purpose: increment item reference counter                                  *
1195  *                                                                            *
1196  * Parameters: item     - [IN] the item                                       *
1197  *                                                                            *
1198  ******************************************************************************/
vc_item_addref(zbx_vc_item_t * item)1199 static void	vc_item_addref(zbx_vc_item_t *item)
1200 {
1201 	item->refcount++;
1202 }
1203 
1204 /******************************************************************************
1205  *                                                                            *
1206  * Function: vc_item_release                                                  *
1207  *                                                                            *
1208  * Purpose: decrement item reference counter                                  *
1209  *                                                                            *
1210  * Parameters: item     - [IN] the item                                       *
1211  *                                                                            *
1212  * Comments: if the resulting reference counter is 0, then this function      *
1213  *           processes pending item operations                                *
1214  *                                                                            *
1215  ******************************************************************************/
vc_item_release(zbx_vc_item_t * item)1216 static void	vc_item_release(zbx_vc_item_t *item)
1217 {
1218 	if (0 == (--item->refcount))
1219 	{
1220 		if (0 != (item->state & ZBX_ITEM_STATE_REMOVE_PENDING))
1221 		{
1222 			vc_remove_item(item);
1223 			return;
1224 		}
1225 
1226 		if (0 != (item->state & ZBX_ITEM_STATE_CLEAN_PENDING))
1227 			vch_item_clean_cache(item);
1228 
1229 		item->state = 0;
1230 	}
1231 }
1232 
1233 /******************************************************************************
1234  *                                                                            *
1235  * Function: vc_item_update_db_cached_from                                    *
1236  *                                                                            *
1237  * Purpose: updates the timestamp from which the item is being cached         *
1238  *                                                                            *
1239  * Parameters: item      - [IN] the item                                      *
1240  *             timestamp - [IN] the timestamp from which all item values are  *
1241  *                              guaranteed to be cached                       *
1242  *                                                                            *
1243  ******************************************************************************/
vc_item_update_db_cached_from(zbx_vc_item_t * item,int timestamp)1244 static void	vc_item_update_db_cached_from(zbx_vc_item_t *item, int timestamp)
1245 {
1246 	if (0 == item->db_cached_from || timestamp < item->db_cached_from)
1247 		item->db_cached_from = timestamp;
1248 }
1249 
1250 /******************************************************************************************************************
1251  *                                                                                                                *
1252  * History storage API                                                                                            *
1253  *                                                                                                                *
1254  ******************************************************************************************************************/
1255 /*
1256  * The value cache caches all values from the largest request range to
1257  * the current time. The history data are stored in variable size chunks
1258  * as illustrated in the following diagram:
1259  *
1260  *  .----------------.
1261  *  | zbx_vc_cache_t |
1262  *  |----------------|      .---------------.
1263  *  | items          |----->| zbx_vc_item_t |-.
1264  *  '----------------'      |---------------| |-.
1265  *  .-----------------------| tail          | | |
1266  *  |                   .---| head          | | |
1267  *  |                   |   '---------------' | |
1268  *  |                   |     '---------------' |
1269  *  |                   |       '---------------'
1270  *  |                   |
1271  *  |                   '-------------------------------------------------.
1272  *  |                                                                     |
1273  *  |  .----------------.                                                 |
1274  *  '->| zbx_vc_chunk_t |<-.                                              |
1275  *     |----------------|  |  .----------------.                          |
1276  *     | next           |---->| zbx_vc_chunk_t |<-.                       |
1277  *     | prev           |  |  |----------------|  |  .----------------.   |
1278  *     '----------------'  |  | next           |---->| zbx_vc_chunk_t |<--'
1279  *                         '--| prev           |  |  |----------------|
1280  *                            '----------------'  |  | next           |
1281  *                                                '--| prev           |
1282  *                                                   '----------------'
1283  *
1284  * The history values are stored in a double linked list of data chunks, holding
1285  * variable number of records (depending on largest request size).
1286  *
1287  * After adding a new chunk, the older chunks (outside the largest request
1288  * range) are automatically removed from cache.
1289  */
1290 
1291 /******************************************************************************
1292  *                                                                            *
1293  * Function: vch_item_update_range                                            *
1294  *                                                                            *
1295  * Purpose: updates item range with current request range                     *
1296  *                                                                            *
1297  * Parameters: item   - [IN] the item                                         *
1298  *             range  - [IN] the request range                                *
1299  *             now    - [IN] the current timestamp                            *
1300  *                                                                            *
1301  ******************************************************************************/
vch_item_update_range(zbx_vc_item_t * item,int range,int now)1302 static void	vch_item_update_range(zbx_vc_item_t *item, int range, int now)
1303 {
1304 	int	hour, diff;
1305 
1306 	if (VC_MIN_RANGE > range)
1307 		range = VC_MIN_RANGE;
1308 
1309 	if (item->daily_range < range)
1310 		item->daily_range = range;
1311 
1312 	hour = (now / SEC_PER_HOUR) & 0xff;
1313 
1314 	if (0 > (diff = hour - item->range_sync_hour))
1315 		diff += 0xff;
1316 
1317 	if (item->active_range < item->daily_range || ZBX_VC_RANGE_SYNC_PERIOD < diff)
1318 	{
1319 		item->active_range = item->daily_range;
1320 		item->daily_range = range;
1321 		item->range_sync_hour = hour;
1322 	}
1323 }
1324 
1325 /******************************************************************************
1326  *                                                                            *
1327  * Function: vch_item_chunk_slot_count                                        *
1328  *                                                                            *
1329  * Purpose: calculates optimal number of slots for an item data chunk         *
1330  *                                                                            *
1331  * Parameters:  item        - [IN] the item                                   *
1332  *              values_new  - [IN] the number of values to be added           *
1333  *                                                                            *
1334  * Return value: the number of slots for a new item data chunk                *
1335  *                                                                            *
1336  * Comments: From size perspective the optimal slot count per chunk is        *
1337  *           approximately square root of the number of cached values.        *
1338  *           Still creating too many chunks might affect timeshift request    *
1339  *           performance, so don't try creating more than 32 chunks unless    *
1340  *           the calculated slot count exceeds the maximum limit.             *
1341  *                                                                            *
1342  ******************************************************************************/
vch_item_chunk_slot_count(zbx_vc_item_t * item,int values_new)1343 static int	vch_item_chunk_slot_count(zbx_vc_item_t *item, int values_new)
1344 {
1345 	int	nslots, values;
1346 
1347 	values = item->values_total + values_new;
1348 
1349 	nslots = zbx_isqrt32(values);
1350 
1351 	if ((values + nslots - 1) / nslots + 1 > 32)
1352 		nslots = values / 32;
1353 
1354 	if (nslots > (int)ZBX_VC_MAX_CHUNK_RECORDS)
1355 		nslots = ZBX_VC_MAX_CHUNK_RECORDS;
1356 	if (nslots < (int)ZBX_VC_MIN_CHUNK_RECORDS)
1357 		nslots = ZBX_VC_MIN_CHUNK_RECORDS;
1358 
1359 	return nslots;
1360 }
1361 
1362 /******************************************************************************
1363  *                                                                            *
1364  * Function: vch_item_add_chunk                                               *
1365  *                                                                            *
1366  * Purpose: adds a new data chunk at the end of item's history data list      *
1367  *                                                                            *
1368  * Parameters: item          - [IN/OUT] the item to add chunk to              *
1369  *             nslots        - [IN] the number of slots in the new chunk      *
1370  *             insert_before - [IN] the target chunk before which the new     *
1371  *                             chunk must be inserted. If this value is NULL  *
1372  *                             then the new chunk is appended at the end of   *
1373  *                             chunk list (making it the newest chunk).       *
1374  *                                                                            *
1375  * Return value:  SUCCEED - the chunk was added successfully                  *
1376  *                FAIL - failed to create a new chunk (not enough memory)     *
1377  *                                                                            *
1378  ******************************************************************************/
vch_item_add_chunk(zbx_vc_item_t * item,int nslots,zbx_vc_chunk_t * insert_before)1379 static int	vch_item_add_chunk(zbx_vc_item_t *item, int nslots, zbx_vc_chunk_t *insert_before)
1380 {
1381 	zbx_vc_chunk_t	*chunk;
1382 	int		chunk_size;
1383 
1384 	chunk_size = sizeof(zbx_vc_chunk_t) + sizeof(zbx_history_record_t) * (nslots - 1);
1385 
1386 	if (NULL == (chunk = (zbx_vc_chunk_t *)vc_item_malloc(item, chunk_size)))
1387 		return FAIL;
1388 
1389 	memset(chunk, 0, sizeof(zbx_vc_chunk_t));
1390 	chunk->slots_num = nslots;
1391 
1392 	chunk->next = insert_before;
1393 
1394 	if (NULL == insert_before)
1395 	{
1396 		chunk->prev = item->head;
1397 
1398 		if (NULL != item->head)
1399 			item->head->next = chunk;
1400 		else
1401 			item->tail = chunk;
1402 
1403 		item->head = chunk;
1404 	}
1405 	else
1406 	{
1407 		chunk->prev = insert_before->prev;
1408 		insert_before->prev = chunk;
1409 
1410 		if (item->tail == insert_before)
1411 			item->tail = chunk;
1412 		else
1413 			chunk->prev->next = chunk;
1414 	}
1415 
1416 	return SUCCEED;
1417 }
1418 
1419 /******************************************************************************
1420  *                                                                            *
1421  * Function: vch_chunk_find_last_value_before                                 *
1422  *                                                                            *
1423  * Purpose: find the index of the last value in chunk with timestamp less or  *
1424  *          equal to the specified timestamp.                                 *
1425  *                                                                            *
1426  * Parameters:  chunk - [IN] the chunk                                        *
1427  *              ts    - [IN] the target timestamp                             *
1428  *                                                                            *
1429  * Return value: The index of the last value in chunk with timestamp less or  *
1430  *               equal to the specified timestamp.                            *
1431  *               -1 is returned in the case of failure (meaning that all      *
1432  *               values have timestamps greater than the target timestamp).   *
1433  *                                                                            *
1434  ******************************************************************************/
vch_chunk_find_last_value_before(const zbx_vc_chunk_t * chunk,const zbx_timespec_t * ts)1435 static int	vch_chunk_find_last_value_before(const zbx_vc_chunk_t *chunk, const zbx_timespec_t *ts)
1436 {
1437 	int	start = chunk->first_value, end = chunk->last_value, middle;
1438 
1439 	/* check if the last value timestamp is already greater or equal to the specified timestamp */
1440 	if (0 >= zbx_timespec_compare(&chunk->slots[end].timestamp, ts))
1441 		return end;
1442 
1443 	/* chunk contains only one value, which did not pass the above check, return failure */
1444 	if (start == end)
1445 		return -1;
1446 
1447 	/* perform value lookup using binary search */
1448 	while (start != end)
1449 	{
1450 		middle = start + (end - start) / 2;
1451 
1452 		if (0 < zbx_timespec_compare(&chunk->slots[middle].timestamp, ts))
1453 		{
1454 			end = middle;
1455 			continue;
1456 		}
1457 
1458 		if (0 >= zbx_timespec_compare(&chunk->slots[middle + 1].timestamp, ts))
1459 		{
1460 			start = middle;
1461 			continue;
1462 		}
1463 
1464 		return middle;
1465 	}
1466 
1467 	return -1;
1468 }
1469 
1470 /******************************************************************************
1471  *                                                                            *
1472  * Function: vch_item_get_last_value                                          *
1473  *                                                                            *
1474  * Purpose: gets the chunk and index of the last value with a timestamp less  *
1475  *          or equal to the specified timestamp                               *
1476  *                                                                            *
1477  * Parameters:  item          - [IN] the item                                 *
1478  *              ts            - [IN] the target timestamp                     *
1479  *                                   (NULL - current time)                    *
1480  *              pchunk        - [OUT] the chunk containing the target value   *
1481  *              pindex        - [OUT] the index of the target value           *
1482  *                                                                            *
1483  * Return value: SUCCEED - the last value was found successfully              *
1484  *               FAIL - all values in cache have timestamps greater than the  *
1485  *                      target (timeshift) timestamp.                         *
1486  *                                                                            *
1487  * Comments: If end_timestamp value is 0, then simply the last item value in  *
1488  *           cache is returned.                                               *
1489  *                                                                            *
1490  ******************************************************************************/
vch_item_get_last_value(const zbx_vc_item_t * item,const zbx_timespec_t * ts,zbx_vc_chunk_t ** pchunk,int * pindex)1491 static int	vch_item_get_last_value(const zbx_vc_item_t *item, const zbx_timespec_t *ts, zbx_vc_chunk_t **pchunk,
1492 		int *pindex)
1493 {
1494 	zbx_vc_chunk_t	*chunk = item->head;
1495 	int		index;
1496 
1497 	if (NULL == chunk)
1498 		return FAIL;
1499 
1500 	index = chunk->last_value;
1501 
1502 	if (0 < zbx_timespec_compare(&chunk->slots[index].timestamp, ts))
1503 	{
1504 		while (0 < zbx_timespec_compare(&chunk->slots[chunk->first_value].timestamp, ts))
1505 		{
1506 			chunk = chunk->prev;
1507 			/* there are no values for requested range, return failure */
1508 			if (NULL == chunk)
1509 				return FAIL;
1510 		}
1511 		index = vch_chunk_find_last_value_before(chunk, ts);
1512 	}
1513 
1514 	*pchunk = chunk;
1515 	*pindex = index;
1516 
1517 	return SUCCEED;
1518 }
1519 
1520 /******************************************************************************
1521  *                                                                            *
1522  * Function: vch_item_copy_value                                              *
1523  *                                                                            *
1524  * Purpose: copies value in the specified item's chunk slot                   *
1525  *                                                                            *
1526  * Parameters: chunk        - [IN/OUT] the target chunk                       *
1527  *             index        - [IN] the target slot                            *
1528  *             source_value - [IN] the value to copy                          *
1529  *                                                                            *
1530  * Return value: SUCCEED - the value was copied successfully                  *
1531  *               FAIL    - the value copying failed (not enough space for     *
1532  *                         string, text or log type data)                     *
1533  *                                                                            *
1534  * Comments: This function is used to copy data to cache. The contents of     *
1535  *           str, text and log type values are stored in cache string pool.   *
1536  *                                                                            *
1537  ******************************************************************************/
vch_item_copy_value(zbx_vc_item_t * item,zbx_vc_chunk_t * chunk,int index,const zbx_history_record_t * source_value)1538 static int	vch_item_copy_value(zbx_vc_item_t *item, zbx_vc_chunk_t *chunk, int index,
1539 		const zbx_history_record_t *source_value)
1540 {
1541 	zbx_history_record_t	*value;
1542 	int			ret = FAIL;
1543 
1544 	value = &chunk->slots[index];
1545 
1546 	switch (item->value_type)
1547 	{
1548 		case ITEM_VALUE_TYPE_STR:
1549 		case ITEM_VALUE_TYPE_TEXT:
1550 			if (NULL == (value->value.str = vc_item_strdup(item, source_value->value.str)))
1551 				goto out;
1552 			break;
1553 		case ITEM_VALUE_TYPE_LOG:
1554 			if (NULL == (value->value.log = vc_item_logdup(item, source_value->value.log)))
1555 				goto out;
1556 			break;
1557 		default:
1558 			value->value = source_value->value;
1559 	}
1560 	value->timestamp = source_value->timestamp;
1561 
1562 	ret = SUCCEED;
1563 out:
1564 	return ret;
1565 }
1566 
1567 /******************************************************************************
1568  *                                                                            *
1569  * Function: vch_item_copy_values_at_tail                                     *
1570  *                                                                            *
1571  * Purpose: copies values at the beginning of item tail chunk                 *
1572  *                                                                            *
1573  * Parameters: item       - [IN/OUT] the target item                          *
1574  *             values     - [IN] the values to copy                           *
1575  *             values_num - [IN] the number of values to copy                 *
1576  *                                                                            *
1577  * Return value: SUCCEED - the values were copied successfully                *
1578  *               FAIL    - the value copying failed (not enough space for     *
1579  *                         string, text or log type data)                     *
1580  *                                                                            *
1581  * Comments: This function is used to copy data to cache. The contents of     *
1582  *           str, text and log type values are stored in cache string pool.   *
1583  *                                                                            *
1584  ******************************************************************************/
vch_item_copy_values_at_tail(zbx_vc_item_t * item,const zbx_history_record_t * values,int values_num)1585 static int	vch_item_copy_values_at_tail(zbx_vc_item_t *item, const zbx_history_record_t *values, int values_num)
1586 {
1587 	int	i, ret = FAIL, first_value = item->tail->first_value;
1588 
1589 	switch (item->value_type)
1590 	{
1591 		case ITEM_VALUE_TYPE_STR:
1592 		case ITEM_VALUE_TYPE_TEXT:
1593 			for (i = values_num - 1; i >= 0; i--)
1594 			{
1595 				zbx_history_record_t	*value = &item->tail->slots[item->tail->first_value - 1];
1596 
1597 				if (NULL == (value->value.str = vc_item_strdup(item, values[i].value.str)))
1598 					goto out;
1599 
1600 				value->timestamp = values[i].timestamp;
1601 				item->tail->first_value--;
1602 			}
1603 			ret = SUCCEED;
1604 
1605 			break;
1606 		case ITEM_VALUE_TYPE_LOG:
1607 			for (i = values_num - 1; i >= 0; i--)
1608 			{
1609 				zbx_history_record_t	*value = &item->tail->slots[item->tail->first_value - 1];
1610 
1611 				if (NULL == (value->value.log = vc_item_logdup(item, values[i].value.log)))
1612 					goto out;
1613 
1614 				value->timestamp = values[i].timestamp;
1615 				item->tail->first_value--;
1616 			}
1617 			ret = SUCCEED;
1618 
1619 			break;
1620 		default:
1621 			memcpy(&item->tail->slots[item->tail->first_value - values_num], values,
1622 					values_num * sizeof(zbx_history_record_t));
1623 			item->tail->first_value -= values_num;
1624 			ret = SUCCEED;
1625 	}
1626 out:
1627 	item->values_total += first_value - item->tail->first_value;
1628 
1629 	return ret;
1630 }
1631 
1632 /******************************************************************************
1633  *                                                                            *
1634  * Function: vch_item_free_chunk                                              *
1635  *                                                                            *
1636  * Purpose: frees chunk and all resources allocated to store its values       *
1637  *                                                                            *
1638  * Parameters: item    - [IN] the chunk owner item                            *
1639  *             chunk   - [IN] the chunk to free                               *
1640  *                                                                            *
1641  * Return value: the number of bytes freed                                    *
1642  *                                                                            *
1643  ******************************************************************************/
vch_item_free_chunk(zbx_vc_item_t * item,zbx_vc_chunk_t * chunk)1644 static size_t	vch_item_free_chunk(zbx_vc_item_t *item, zbx_vc_chunk_t *chunk)
1645 {
1646 	size_t	freed;
1647 
1648 	freed = sizeof(zbx_vc_chunk_t) + (chunk->slots_num - 1) * sizeof(zbx_history_record_t);
1649 	freed += vc_item_free_values(item, chunk->slots, chunk->first_value, chunk->last_value);
1650 
1651 	__vc_mem_free_func(chunk);
1652 
1653 	return freed;
1654 }
1655 
1656 /******************************************************************************
1657  *                                                                            *
1658  * Function: vch_item_remove_chunk                                            *
1659  *                                                                            *
1660  * Purpose: removes item history data chunk                                   *
1661  *                                                                            *
1662  * Parameters: item    - [IN ] the chunk owner item                           *
1663  *             chunk   - [IN] the chunk to remove                             *
1664  *                                                                            *
1665  ******************************************************************************/
vch_item_remove_chunk(zbx_vc_item_t * item,zbx_vc_chunk_t * chunk)1666 static void	vch_item_remove_chunk(zbx_vc_item_t *item, zbx_vc_chunk_t *chunk)
1667 {
1668 	if (NULL != chunk->next)
1669 		chunk->next->prev = chunk->prev;
1670 
1671 	if (NULL != chunk->prev)
1672 		chunk->prev->next = chunk->next;
1673 
1674 	if (chunk == item->head)
1675 		item->head = chunk->prev;
1676 
1677 	if (chunk == item->tail)
1678 		item->tail = chunk->next;
1679 
1680 	vch_item_free_chunk(item, chunk);
1681 }
1682 
1683 /******************************************************************************
1684  *                                                                            *
1685  * Function: vch_item_clean_cache                                             *
1686  *                                                                            *
1687  * Purpose: removes item history data that are outside (older) the maximum    *
1688  *          request range                                                     *
1689  *                                                                            *
1690  * Parameters:  item   - [IN] the target item                                 *
1691  *                                                                            *
1692  ******************************************************************************/
vch_item_clean_cache(zbx_vc_item_t * item)1693 static void	vch_item_clean_cache(zbx_vc_item_t *item)
1694 {
1695 	zbx_vc_chunk_t	*next;
1696 
1697 	if (0 != item->active_range)
1698 	{
1699 		zbx_vc_chunk_t	*tail = item->tail;
1700 		zbx_vc_chunk_t	*chunk = tail;
1701 		int		timestamp;
1702 
1703 		timestamp = time(NULL) - item->active_range;
1704 
1705 		/* try to remove chunks with all history values older than maximum request range */
1706 		while (NULL != chunk && chunk->slots[chunk->last_value].timestamp.sec < timestamp &&
1707 				chunk->slots[chunk->last_value].timestamp.sec !=
1708 						item->head->slots[item->head->last_value].timestamp.sec)
1709 		{
1710 			/* don't remove the head chunk */
1711 			if (NULL == (next = chunk->next))
1712 				break;
1713 
1714 			/* Values with the same timestamps (seconds resolution) always should be either   */
1715 			/* kept in cache or removed together. There should not be a case when one of them */
1716 			/* is in cache and the second is dropped.                                         */
1717 			/* Here we are handling rare case, when the last value of first chunk has the     */
1718 			/* same timestamp (seconds resolution) as the first value in the second chunk.    */
1719 			/* In this case increase the first value index of the next chunk until the first  */
1720 			/* value timestamp is greater.                                                    */
1721 
1722 			if (next->slots[next->first_value].timestamp.sec != next->slots[next->last_value].timestamp.sec)
1723 			{
1724 				while (next->slots[next->first_value].timestamp.sec ==
1725 						chunk->slots[chunk->last_value].timestamp.sec)
1726 				{
1727 					vc_item_free_values(item, next->slots, next->first_value, next->first_value);
1728 					next->first_value++;
1729 				}
1730 			}
1731 
1732 			/* set the database cached from timestamp to the last (oldest) removed value timestamp + 1 */
1733 			item->db_cached_from = chunk->slots[chunk->last_value].timestamp.sec + 1;
1734 
1735 			vch_item_remove_chunk(item, chunk);
1736 
1737 			chunk = next;
1738 		}
1739 
1740 		/* reset the status flags if data was removed from cache */
1741 		if (tail != item->tail)
1742 			item->status = 0;
1743 	}
1744 }
1745 
1746 /******************************************************************************
1747  *                                                                            *
1748  * Function: vch_item_remove_values                                           *
1749  *                                                                            *
1750  * Purpose: removes item history data that are older than the specified       *
1751  *          timestamp                                                         *
1752  *                                                                            *
1753  * Parameters:  item      - [IN] the target item                              *
1754  *              timestamp - [IN] the timestamp (number of seconds since the   *
1755  *                               Epoch)                                       *
1756  *                                                                            *
1757  ******************************************************************************/
vch_item_remove_values(zbx_vc_item_t * item,int timestamp)1758 static void	vch_item_remove_values(zbx_vc_item_t *item, int timestamp)
1759 {
1760 	zbx_vc_chunk_t	*chunk = item->tail;
1761 
1762 	if (ZBX_ITEM_STATUS_CACHED_ALL == item->status)
1763 		item->status = 0;
1764 
1765 	/* try to remove chunks with all history values older than the timestamp */
1766 	while (chunk->slots[chunk->first_value].timestamp.sec < timestamp)
1767 	{
1768 		zbx_vc_chunk_t	*next;
1769 
1770 		/* If chunk contains values with timestamp greater or equal - remove */
1771 		/* only the values with less timestamp. Otherwise remove the while   */
1772 		/* chunk and check next one.                                         */
1773 		if (chunk->slots[chunk->last_value].timestamp.sec >= timestamp)
1774 		{
1775 			while (chunk->slots[chunk->first_value].timestamp.sec < timestamp)
1776 			{
1777 				vc_item_free_values(item, chunk->slots, chunk->first_value, chunk->first_value);
1778 				chunk->first_value++;
1779 			}
1780 
1781 			break;
1782 		}
1783 
1784 		next = chunk->next;
1785 		vch_item_remove_chunk(item, chunk);
1786 
1787 		/* empty items must be removed to avoid situation when a new value is added to cache */
1788 		/* while other values with matching timestamp seconds are not cached                 */
1789 		if (NULL == next)
1790 		{
1791 			item->state |= ZBX_ITEM_STATE_REMOVE_PENDING;
1792 			break;
1793 		}
1794 
1795 		chunk = next;
1796 	}
1797 }
1798 
1799 /******************************************************************************
1800  *                                                                            *
1801  * Function: vch_item_add_value_at_head                                       *
1802  *                                                                            *
1803  * Purpose: adds one item history value at the end of current item's history  *
1804  *          data                                                              *
1805  *                                                                            *
1806  * Parameters:  item   - [IN] the item to add history data to                 *
1807  *              value  - [IN] the item history data value                     *
1808  *                                                                            *
1809  * Return value: SUCCEED - the history data value was added successfully      *
1810  *               FAIL - failed to add history data value (not enough memory)  *
1811  *                                                                            *
1812  * Comments: In the case of failure the item will be removed from cache       *
1813  *           later.                                                           *
1814  *                                                                            *
1815  ******************************************************************************/
vch_item_add_value_at_head(zbx_vc_item_t * item,const zbx_history_record_t * value)1816 static int	vch_item_add_value_at_head(zbx_vc_item_t *item, const zbx_history_record_t *value)
1817 {
1818 	int		ret = FAIL, index, sindex, nslots = 0;
1819 	zbx_vc_chunk_t	*head = item->head, *chunk, *schunk;
1820 
1821 	if (NULL != item->head &&
1822 			0 < zbx_history_record_compare_asc_func(&item->head->slots[item->head->last_value], value))
1823 	{
1824 		if (0 < zbx_history_record_compare_asc_func(&item->tail->slots[item->tail->first_value], value))
1825 		{
1826 			/* If the added value has the same or older timestamp as the first value in cache */
1827 			/* we can't add it to keep cache consistency. Additionally we must make sure no   */
1828 			/* values with matching timestamp seconds are kept in cache.                      */
1829 			vch_item_remove_values(item, value->timestamp.sec + 1);
1830 
1831 			/* if the value is newer than the database cached from timestamp we must */
1832 			/* adjust the cached from timestamp to exclude this value                */
1833 			if (item->db_cached_from <= value->timestamp.sec)
1834 				item->db_cached_from = value->timestamp.sec + 1;
1835 
1836 			ret = SUCCEED;
1837 			goto out;
1838 		}
1839 
1840 		sindex = item->head->last_value;
1841 		schunk = item->head;
1842 
1843 		if (0 == item->head->slots_num - item->head->last_value - 1)
1844 		{
1845 			if (FAIL == vch_item_add_chunk(item, vch_item_chunk_slot_count(item, 1), NULL))
1846 				goto out;
1847 		}
1848 		else
1849 			item->head->last_value++;
1850 
1851 		item->values_total++;
1852 
1853 		chunk = item->head;
1854 		index = item->head->last_value;
1855 
1856 		do
1857 		{
1858 			chunk->slots[index] = schunk->slots[sindex];
1859 
1860 			chunk = schunk;
1861 			index = sindex;
1862 
1863 			if (--sindex < schunk->first_value)
1864 			{
1865 				if (NULL == (schunk = schunk->prev))
1866 				{
1867 					memset(&chunk->slots[index], 0, sizeof(zbx_vc_chunk_t));
1868 					THIS_SHOULD_NEVER_HAPPEN;
1869 
1870 					goto out;
1871 				}
1872 
1873 				sindex = schunk->last_value;
1874 			}
1875 		}
1876 		while (0 < zbx_timespec_compare(&schunk->slots[sindex].timestamp, &value->timestamp));
1877 	}
1878 	else
1879 	{
1880 		/* find the number of free slots on the right side in last (head) chunk */
1881 		if (NULL != item->head)
1882 			nslots = item->head->slots_num - item->head->last_value - 1;
1883 
1884 		if (0 == nslots)
1885 		{
1886 			if (FAIL == vch_item_add_chunk(item, vch_item_chunk_slot_count(item, 1), NULL))
1887 				goto out;
1888 		}
1889 		else
1890 			item->head->last_value++;
1891 
1892 		item->values_total++;
1893 
1894 		chunk = item->head;
1895 		index = item->head->last_value;
1896 	}
1897 
1898 	if (SUCCEED != vch_item_copy_value(item, chunk, index, value))
1899 		goto out;
1900 
1901 	/* try to remove old (unused) chunks if a new chunk was added */
1902 	if (head != item->head)
1903 		item->state |= ZBX_ITEM_STATE_CLEAN_PENDING;
1904 
1905 	ret = SUCCEED;
1906 out:
1907 	return ret;
1908 }
1909 
1910 /******************************************************************************
1911  *                                                                            *
1912  * Function: vch_item_add_values_at_tail                                      *
1913  *                                                                            *
1914  * Purpose: adds item history values at the beginning of current item's       *
1915  *          history data                                                      *
1916  *                                                                            *
1917  * Parameters:  item   - [IN] the item to add history data to                 *
1918  *              values - [IN] the item history data values                    *
1919  *              num    - [IN] the number of history data values to add        *
1920  *                                                                            *
1921  * Return value: SUCCEED - the history data values were added successfully    *
1922  *               FAIL - failed to add history data values (not enough memory) *
1923  *                                                                            *
1924  * Comments: In the case of failure the item is removed from cache.           *
1925  *           Overlapping values (by timestamp seconds) are ignored.           *
1926  *                                                                            *
1927  ******************************************************************************/
vch_item_add_values_at_tail(zbx_vc_item_t * item,const zbx_history_record_t * values,int values_num)1928 static int	vch_item_add_values_at_tail(zbx_vc_item_t *item, const zbx_history_record_t *values, int values_num)
1929 {
1930 	int 	count = values_num, ret = FAIL;
1931 
1932 	/* skip values already added to the item cache by another process */
1933 	if (NULL != item->tail)
1934 	{
1935 		int	sec = item->tail->slots[item->tail->first_value].timestamp.sec;
1936 
1937 		while (--count >= 0 && values[count].timestamp.sec >= sec)
1938 			;
1939 		++count;
1940 	}
1941 
1942 	while (0 != count)
1943 	{
1944 		int	copy_slots, nslots = 0;
1945 
1946 		/* find the number of free slots on the left side in first (tail) chunk */
1947 		if (NULL != item->tail)
1948 			nslots = item->tail->first_value;
1949 
1950 		if (0 == nslots)
1951 		{
1952 			nslots = vch_item_chunk_slot_count(item, count);
1953 
1954 			if (FAIL == vch_item_add_chunk(item, nslots, item->tail))
1955 				goto out;
1956 
1957 			item->tail->last_value = nslots - 1;
1958 			item->tail->first_value = nslots;
1959 		}
1960 
1961 		/* copy values to chunk */
1962 		copy_slots = MIN(nslots, count);
1963 		count -= copy_slots;
1964 
1965 		if (FAIL == vch_item_copy_values_at_tail(item, values + count, copy_slots))
1966 			goto out;
1967 	}
1968 
1969 	ret = SUCCEED;
1970 out:
1971 	return ret;
1972 }
1973 
1974 /******************************************************************************
1975  *                                                                            *
1976  * Function: vch_item_cache_values_by_time                                    *
1977  *                                                                            *
1978  * Purpose: cache item history data for the specified time period             *
1979  *                                                                            *
1980  * Parameters: item        - [IN] the item                                    *
1981  *             range_start - [IN] the interval start time                     *
1982  *                                                                            *
1983  * Return value:  >=0    - the number of values read from database            *
1984  *                FAIL   - an error occurred while trying to cache values     *
1985  *                                                                            *
1986  * Comments: This function checks if the requested value range is cached and  *
1987  *           updates cache from database if necessary.                        *
1988  *                                                                            *
1989  ******************************************************************************/
vch_item_cache_values_by_time(zbx_vc_item_t * item,int range_start)1990 static int	vch_item_cache_values_by_time(zbx_vc_item_t *item, int range_start)
1991 {
1992 	int	ret = SUCCEED, range_end;
1993 
1994 	if (ZBX_ITEM_STATUS_CACHED_ALL == item->status)
1995 		return SUCCEED;
1996 
1997 	/* check if the requested period is in the cached range */
1998 	if (0 != item->db_cached_from && range_start >= item->db_cached_from)
1999 		return SUCCEED;
2000 
2001 	/* find if the cache should be updated to cover the required range */
2002 	if (NULL != item->tail)
2003 	{
2004 		/* we need to get item values before the first cached value, but not including it */
2005 		range_end = item->tail->slots[item->tail->first_value].timestamp.sec - 1;
2006 	}
2007 	else
2008 		range_end = ZBX_JAN_2038;
2009 
2010 	/* update cache if necessary */
2011 	if (range_start < range_end)
2012 	{
2013 		zbx_vector_history_record_t	records;
2014 
2015 		zbx_vector_history_record_create(&records);
2016 
2017 		vc_try_unlock();
2018 
2019 		if (SUCCEED == (ret = vc_db_read_values_by_time(item->itemid, item->value_type, &records,
2020 				range_start, range_end)))
2021 		{
2022 			zbx_vector_history_record_sort(&records,
2023 					(zbx_compare_func_t)zbx_history_record_compare_asc_func);
2024 		}
2025 
2026 		vc_try_lock();
2027 
2028 		if (SUCCEED == ret)
2029 		{
2030 			if (0 < records.values_num)
2031 				ret = vch_item_add_values_at_tail(item, records.values, records.values_num);
2032 
2033 			/* when updating cache with time based request we can always reset status flags */
2034 			/* flag even if the requested period contains no data                           */
2035 			item->status = 0;
2036 
2037 			if (SUCCEED == ret)
2038 			{
2039 				ret = records.values_num;
2040 				vc_item_update_db_cached_from(item, range_start);
2041 			}
2042 
2043 		}
2044 		zbx_history_record_vector_destroy(&records, item->value_type);
2045 	}
2046 
2047 	return ret;
2048 }
2049 
2050 /******************************************************************************
2051  *                                                                            *
2052  * Function: vch_item_cache_values_by_time_and_count                          *
2053  *                                                                            *
2054  * Purpose: cache the specified number of history data values for time period *
2055  *          since timestamp                                                   *
2056  *                                                                            *
2057  * Parameters: item        - [IN] the item                                    *
2058  *             range_start - [IN] the interval start time                     *
2059  *             count       - [IN] the number of history values to retrieve    *
2060  *             ts          - [IN] the target timestamp                        *
2061  *                                                                            *
2062  * Return value:  >=0    - the number of values read from database            *
2063  *                FAIL   - an error occurred while trying to cache values     *
2064  *                                                                            *
2065  * Comments: This function checks if the requested number of values is cached *
2066  *           and updates cache from database if necessary.                    *
2067  *                                                                            *
2068  ******************************************************************************/
vch_item_cache_values_by_time_and_count(zbx_vc_item_t * item,int range_start,int count,const zbx_timespec_t * ts)2069 static int	vch_item_cache_values_by_time_and_count(zbx_vc_item_t *item, int range_start, int count,
2070 		const zbx_timespec_t *ts)
2071 {
2072 	int	ret = SUCCEED, cached_records = 0, range_end;
2073 
2074 	if (ZBX_ITEM_STATUS_CACHED_ALL == item->status)
2075 		return SUCCEED;
2076 
2077 	/* check if the requested period is in the cached range */
2078 	if (0 != item->db_cached_from && range_start >= item->db_cached_from)
2079 		return SUCCEED;
2080 
2081 	/* find if the cache should be updated to cover the required count */
2082 	if (NULL != item->head)
2083 	{
2084 		zbx_vc_chunk_t	*chunk;
2085 		int		index;
2086 
2087 		if (SUCCEED == vch_item_get_last_value(item, ts, &chunk, &index))
2088 		{
2089 			cached_records = index - chunk->first_value + 1;
2090 
2091 			while (NULL != (chunk = chunk->prev) && cached_records < count)
2092 				cached_records += chunk->last_value - chunk->first_value + 1;
2093 		}
2094 	}
2095 
2096 	/* update cache if necessary */
2097 	if (cached_records < count)
2098 	{
2099 		zbx_vector_history_record_t	records;
2100 
2101 		/* get the end timestamp to which (including) the values should be cached */
2102 		if (NULL != item->head)
2103 			range_end = item->tail->slots[item->tail->first_value].timestamp.sec - 1;
2104 		else
2105 			range_end = ZBX_JAN_2038;
2106 
2107 		vc_try_unlock();
2108 
2109 		zbx_vector_history_record_create(&records);
2110 
2111 		if (range_end > ts->sec)
2112 		{
2113 			ret = vc_db_read_values_by_time(item->itemid, item->value_type, &records, ts->sec + 1,
2114 					range_end);
2115 
2116 			range_end = ts->sec;
2117 		}
2118 
2119 		if (SUCCEED == ret && SUCCEED == (ret = vc_db_read_values_by_time_and_count(item->itemid,
2120 				item->value_type, &records, range_start, count - cached_records, range_end, ts)))
2121 		{
2122 			zbx_vector_history_record_sort(&records,
2123 					(zbx_compare_func_t)zbx_history_record_compare_asc_func);
2124 		}
2125 
2126 		vc_try_lock();
2127 
2128 		if (SUCCEED == ret)
2129 		{
2130 			if (0 < records.values_num)
2131 				ret = vch_item_add_values_at_tail(item, records.values, records.values_num);
2132 
2133 			if (SUCCEED == ret)
2134 			{
2135 				ret = records.values_num;
2136 				if ((count <= records.values_num || 0 == range_start) && 0 != records.values_num)
2137 				{
2138 					vc_item_update_db_cached_from(item,
2139 							item->tail->slots[item->tail->first_value].timestamp.sec);
2140 				}
2141 				else if (0 != range_start)
2142 					vc_item_update_db_cached_from(item, range_start);
2143 			}
2144 		}
2145 
2146 		zbx_history_record_vector_destroy(&records, item->value_type);
2147 	}
2148 
2149 	return ret;
2150 }
2151 
2152 /******************************************************************************
2153  *                                                                            *
2154  * Function: vch_item_get_values_by_time                                      *
2155  *                                                                            *
2156  * Purpose: retrieves item history data from cache                            *
2157  *                                                                            *
2158  * Parameters: item      - [IN] the item                                      *
2159  *             values    - [OUT] the item history data stored time/value      *
2160  *                         pairs in undefined order                           *
2161  *             seconds   - [IN] the time period to retrieve data for          *
2162  *             ts        - [IN] the requested period end timestamp            *
2163  *                                                                            *
2164  ******************************************************************************/
vch_item_get_values_by_time(zbx_vc_item_t * item,zbx_vector_history_record_t * values,int seconds,const zbx_timespec_t * ts)2165 static void	vch_item_get_values_by_time(zbx_vc_item_t *item, zbx_vector_history_record_t *values, int seconds,
2166 		const zbx_timespec_t *ts)
2167 {
2168 	int		index, now;
2169 	zbx_timespec_t	start = {ts->sec - seconds, ts->ns};
2170 	zbx_vc_chunk_t	*chunk;
2171 
2172 	/* Check if maximum request range is not set and all data are cached.  */
2173 	/* Because that indicates there was a count based request with unknown */
2174 	/* range which might be greater than the current request range.        */
2175 	if (0 != item->active_range || ZBX_ITEM_STATUS_CACHED_ALL != item->status)
2176 	{
2177 		now = time(NULL);
2178 		/* add another second to include nanosecond shifts */
2179 		vch_item_update_range(item, seconds + now - ts->sec + 1, now);
2180 	}
2181 
2182 	if (FAIL == vch_item_get_last_value(item, ts, &chunk, &index))
2183 	{
2184 		/* Cache does not contain records for the specified timeshift & seconds range. */
2185 		/* Return empty vector with success.                                           */
2186 		return;
2187 	}
2188 
2189 	/* fill the values vector with item history values until the start timestamp is reached */
2190 	while (0 < zbx_timespec_compare(&chunk->slots[chunk->last_value].timestamp, &start))
2191 	{
2192 		while (index >= chunk->first_value && 0 < zbx_timespec_compare(&chunk->slots[index].timestamp, &start))
2193 			vc_history_record_vector_append(values, item->value_type, &chunk->slots[index--]);
2194 
2195 		if (NULL == (chunk = chunk->prev))
2196 			break;
2197 
2198 		index = chunk->last_value;
2199 	}
2200 }
2201 
2202 /******************************************************************************
2203  *                                                                            *
2204  * Function: vch_item_get_values_by_time_and_count                            *
2205  *                                                                            *
2206  * Purpose: retrieves item history data from cache                            *
2207  *                                                                            *
2208  * Parameters: item      - [IN] the item                                      *
2209  *             values    - [OUT] the item history data stored time/value      *
2210  *                         pairs in undefined order, optional                 *
2211  *                         If null then cache is updated if necessary, but no *
2212  *                         values are returned. Used to ensure that cache     *
2213  *                         contains a value of the specified timestamp.       *
2214  *             seconds   - [IN] the time period                               *
2215  *             count     - [IN] the number of history values to retrieve      *
2216  *             timestamp - [IN] the target timestamp                          *
2217  *                                                                            *
2218  ******************************************************************************/
vch_item_get_values_by_time_and_count(zbx_vc_item_t * item,zbx_vector_history_record_t * values,int seconds,int count,const zbx_timespec_t * ts)2219 static void	vch_item_get_values_by_time_and_count(zbx_vc_item_t *item, zbx_vector_history_record_t *values,
2220 		int seconds, int count, const zbx_timespec_t *ts)
2221 {
2222 	int		index, now, range_timestamp;
2223 	zbx_vc_chunk_t	*chunk;
2224 	zbx_timespec_t	start;
2225 
2226 	/* set start timestamp of the requested time period */
2227 	if (0 != seconds)
2228 	{
2229 		start.sec = ts->sec - seconds;
2230 		start.ns = ts->ns;
2231 	}
2232 	else
2233 	{
2234 		start.sec = 0;
2235 		start.ns = 0;
2236 	}
2237 
2238 	if (FAIL == vch_item_get_last_value(item, ts, &chunk, &index))
2239 	{
2240 		/* return empty vector with success */
2241 		goto out;
2242 	}
2243 
2244 	/* fill the values vector with item history values until the <count> values are read    */
2245 	/* or no more values within specified time period                                       */
2246 	/* fill the values vector with item history values until the start timestamp is reached */
2247 	while (0 < zbx_timespec_compare(&chunk->slots[chunk->last_value].timestamp, &start))
2248 	{
2249 		while (index >= chunk->first_value && 0 < zbx_timespec_compare(&chunk->slots[index].timestamp, &start))
2250 		{
2251 			vc_history_record_vector_append(values, item->value_type, &chunk->slots[index--]);
2252 
2253 			if (values->values_num == count)
2254 				goto out;
2255 		}
2256 
2257 		if (NULL == (chunk = chunk->prev))
2258 			break;
2259 
2260 		index = chunk->last_value;
2261 	}
2262 out:
2263 	if (count > values->values_num)
2264 	{
2265 		if (0 == seconds)
2266 		{
2267 			/* not enough data in db to fulfill a count based request request */
2268 			item->active_range = 0;
2269 			item->daily_range = 0;
2270 			item->status = ZBX_ITEM_STATUS_CACHED_ALL;
2271 			return;
2272 		}
2273 		/* not enough data in the requested period, set the range equal to the period plus */
2274 		/* one second to include nanosecond shifts                                         */
2275 		range_timestamp = ts->sec - seconds;
2276 	}
2277 	else
2278 	{
2279 		/* the requested number of values was retrieved, set the range to the oldest value timestamp */
2280 		range_timestamp = values->values[values->values_num - 1].timestamp.sec - 1;
2281 	}
2282 
2283 	now = time(NULL);
2284 	vch_item_update_range(item, now - range_timestamp, now);
2285 }
2286 
2287 /******************************************************************************
2288  *                                                                            *
2289  * Function: vch_item_get_value_range                                         *
2290  *                                                                            *
2291  * Purpose: get item values for the specified range                           *
2292  *                                                                            *
2293  * Parameters: item      - [IN] the item                                      *
2294  *             values    - [OUT] the item history data stored time/value      *
2295  *                         pairs in undefined order, optional                 *
2296  *                         If null then cache is updated if necessary, but no *
2297  *                         values are returned. Used to ensure that cache     *
2298  *                         contains a value of the specified timestamp.       *
2299  *             seconds   - [IN] the time period to retrieve data for          *
2300  *             count     - [IN] the number of history values to retrieve      *
2301  *             ts        - [IN] the target timestamp                          *
2302  *                                                                            *
2303  * Return value:  SUCCEED - the item history data was retrieved successfully  *
2304  *                FAIL    - the item history data was not retrieved           *
2305  *                                                                            *
2306  * Comments: This function returns data from cache if necessary updating it   *
2307  *           from DB. If cache update was required and failed (not enough     *
2308  *           memory to cache DB values), then this function also fails.       *
2309  *                                                                            *
2310  *           If <count> is set then value range is defined as <count> values  *
2311  *           before <timestamp>. Otherwise the range is defined as <seconds>  *
2312  *           seconds before <timestamp>.                                      *
2313  *                                                                            *
2314  ******************************************************************************/
vch_item_get_values(zbx_vc_item_t * item,zbx_vector_history_record_t * values,int seconds,int count,const zbx_timespec_t * ts)2315 static int	vch_item_get_values(zbx_vc_item_t *item, zbx_vector_history_record_t *values, int seconds,
2316 		int count, const zbx_timespec_t *ts)
2317 {
2318 	int	ret, records_read, hits, misses, range_start;
2319 
2320 	zbx_vector_history_record_clear(values);
2321 
2322 	if (0 == count)
2323 	{
2324 		if (0 > (range_start = ts->sec - seconds))
2325 			range_start = 0;
2326 
2327 		if (FAIL == (ret = vch_item_cache_values_by_time(item, range_start)))
2328 			goto out;
2329 
2330 		records_read = ret;
2331 
2332 		vch_item_get_values_by_time(item, values, seconds, ts);
2333 
2334 		if (records_read > values->values_num)
2335 			records_read = values->values_num;
2336 	}
2337 	else
2338 	{
2339 		range_start = (0 == seconds ? 0 : ts->sec - seconds);
2340 
2341 		if (FAIL == (ret = vch_item_cache_values_by_time_and_count(item, range_start, count, ts)))
2342 			goto out;
2343 
2344 		records_read = ret;
2345 
2346 		vch_item_get_values_by_time_and_count(item, values, seconds, count, ts);
2347 
2348 		if (records_read > values->values_num)
2349 			records_read = values->values_num;
2350 	}
2351 
2352 	hits = values->values_num - records_read;
2353 	misses = records_read;
2354 
2355 	vc_update_statistics(item, hits, misses);
2356 
2357 	ret = SUCCEED;
2358 out:
2359 	return ret;
2360 }
2361 
2362 /******************************************************************************
2363  *                                                                            *
2364  * Function: vch_item_free_cache                                              *
2365  *                                                                            *
2366  * Purpose: frees resources allocated for item history data                   *
2367  *                                                                            *
2368  * Parameters: item    - [IN] the item                                        *
2369  *                                                                            *
2370  * Return value: the size of freed memory (bytes)                             *
2371  *                                                                            *
2372  ******************************************************************************/
vch_item_free_cache(zbx_vc_item_t * item)2373 static size_t	vch_item_free_cache(zbx_vc_item_t *item)
2374 {
2375 	size_t	freed = 0;
2376 
2377 	zbx_vc_chunk_t	*chunk = item->tail;
2378 
2379 	while (NULL != chunk)
2380 	{
2381 		zbx_vc_chunk_t	*next = chunk->next;
2382 
2383 		freed += vch_item_free_chunk(item, chunk);
2384 		chunk = next;
2385 	}
2386 	item->values_total = 0;
2387 	item->head = NULL;
2388 	item->tail = NULL;
2389 
2390 	return freed;
2391 }
2392 
2393 /******************************************************************************************************************
2394  *                                                                                                                *
2395  * Public API                                                                                                     *
2396  *                                                                                                                *
2397  ******************************************************************************************************************/
2398 
2399 /******************************************************************************
2400  *                                                                            *
2401  * Function: zbx_vc_init                                                      *
2402  *                                                                            *
2403  * Purpose: initializes value cache                                           *
2404  *                                                                            *
2405  ******************************************************************************/
zbx_vc_init(char ** error)2406 int	zbx_vc_init(char **error)
2407 {
2408 	const char	*__function_name = "zbx_vc_init";
2409 	zbx_uint64_t	size_reserved;
2410 	int		ret = FAIL;
2411 
2412 	if (0 == CONFIG_VALUE_CACHE_SIZE)
2413 		return SUCCEED;
2414 
2415 	zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __function_name);
2416 
2417 	if (SUCCEED != zbx_mutex_create(&vc_lock, ZBX_MUTEX_VALUECACHE, error))
2418 		goto out;
2419 
2420 	size_reserved = zbx_mem_required_size(1, "value cache size", "ValueCacheSize");
2421 
2422 	if (SUCCEED != zbx_mem_create(&vc_mem, CONFIG_VALUE_CACHE_SIZE, "value cache size", "ValueCacheSize", 1, error))
2423 		goto out;
2424 
2425 	CONFIG_VALUE_CACHE_SIZE -= size_reserved;
2426 
2427 	vc_cache = (zbx_vc_cache_t *)__vc_mem_malloc_func(vc_cache, sizeof(zbx_vc_cache_t));
2428 
2429 	if (NULL == vc_cache)
2430 	{
2431 		*error = zbx_strdup(*error, "cannot allocate value cache header");
2432 		goto out;
2433 	}
2434 	memset(vc_cache, 0, sizeof(zbx_vc_cache_t));
2435 
2436 	zbx_hashset_create_ext(&vc_cache->items, VC_ITEMS_INIT_SIZE,
2437 			ZBX_DEFAULT_UINT64_HASH_FUNC, ZBX_DEFAULT_UINT64_COMPARE_FUNC, NULL,
2438 			__vc_mem_malloc_func, __vc_mem_realloc_func, __vc_mem_free_func);
2439 
2440 	if (NULL == vc_cache->items.slots)
2441 	{
2442 		*error = zbx_strdup(*error, "cannot allocate value cache data storage");
2443 		goto out;
2444 	}
2445 
2446 	zbx_hashset_create_ext(&vc_cache->strpool, VC_STRPOOL_INIT_SIZE,
2447 			vc_strpool_hash_func, vc_strpool_compare_func, NULL,
2448 			__vc_mem_malloc_func, __vc_mem_realloc_func, __vc_mem_free_func);
2449 
2450 	if (NULL == vc_cache->strpool.slots)
2451 	{
2452 		*error = zbx_strdup(*error, "cannot allocate string pool for value cache data storage");
2453 		goto out;
2454 	}
2455 
2456 	/* the free space request should be 5% of cache size, but no more than 128KB */
2457 	vc_cache->min_free_request = (CONFIG_VALUE_CACHE_SIZE / 100) * 5;
2458 	if (vc_cache->min_free_request > 128 * ZBX_KIBIBYTE)
2459 		vc_cache->min_free_request = 128 * ZBX_KIBIBYTE;
2460 
2461 	ret = SUCCEED;
2462 out:
2463 	zbx_vc_disable();
2464 
2465 	zabbix_log(LOG_LEVEL_DEBUG, "End of %s()", __function_name);
2466 
2467 	return ret;
2468 }
2469 
2470 /******************************************************************************
2471  *                                                                            *
2472  * Function: zbx_vc_destroy                                                   *
2473  *                                                                            *
2474  * Purpose: destroys value cache                                              *
2475  *                                                                            *
2476  ******************************************************************************/
zbx_vc_destroy(void)2477 void	zbx_vc_destroy(void)
2478 {
2479 	const char	*__function_name = "zbx_vc_destroy";
2480 
2481 	zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __function_name);
2482 
2483 	if (NULL != vc_cache)
2484 	{
2485 		zbx_mutex_destroy(&vc_lock);
2486 
2487 		zbx_hashset_destroy(&vc_cache->items);
2488 		zbx_hashset_destroy(&vc_cache->strpool);
2489 
2490 		__vc_mem_free_func(vc_cache);
2491 		vc_cache = NULL;
2492 	}
2493 
2494 	zabbix_log(LOG_LEVEL_DEBUG, "End of %s()", __function_name);
2495 }
2496 
2497 /******************************************************************************
2498  *                                                                            *
2499  * Function: zbx_vc_reset                                                     *
2500  *                                                                            *
2501  * Purpose: resets value cache                                                *
2502  *                                                                            *
2503  * Comments: All items and their historical data are removed,                 *
2504  *           cache working mode, statistics reset.                            *
2505  *                                                                            *
2506  ******************************************************************************/
zbx_vc_reset(void)2507 void	zbx_vc_reset(void)
2508 {
2509 	const char	*__function_name = "zbx_vc_reset";
2510 
2511 	zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __function_name);
2512 
2513 	if (NULL != vc_cache)
2514 	{
2515 		zbx_vc_item_t		*item;
2516 		zbx_hashset_iter_t	iter;
2517 
2518 		vc_try_lock();
2519 
2520 		zbx_hashset_iter_reset(&vc_cache->items, &iter);
2521 		while (NULL != (item = (zbx_vc_item_t *)zbx_hashset_iter_next(&iter)))
2522 		{
2523 			vch_item_free_cache(item);
2524 			zbx_hashset_iter_remove(&iter);
2525 		}
2526 
2527 		vc_cache->hits = 0;
2528 		vc_cache->misses = 0;
2529 		vc_cache->min_free_request = 0;
2530 		vc_cache->mode = ZBX_VC_MODE_NORMAL;
2531 		vc_cache->mode_time = 0;
2532 		vc_cache->last_warning_time = 0;
2533 
2534 		vc_try_unlock();
2535 	}
2536 
2537 	zabbix_log(LOG_LEVEL_DEBUG, "End of %s()", __function_name);
2538 }
2539 
2540 /******************************************************************************
2541  *                                                                            *
2542  * Function: zbx_vc_add_values                                                *
2543  *                                                                            *
2544  * Purpose: adds item values to the history and value cache                   *
2545  *                                                                            *
2546  * Parameters: history - [IN] item history values                             *
2547  *                                                                            *
2548  * Return value: SUCCEED - the values were added successfully                 *
2549  *               FAIL    - otherwise                                          *
2550  *                                                                            *
2551  ******************************************************************************/
zbx_vc_add_values(zbx_vector_ptr_t * history)2552 int	zbx_vc_add_values(zbx_vector_ptr_t *history)
2553 {
2554 	zbx_vc_item_t		*item;
2555 	int 			i;
2556 	ZBX_DC_HISTORY		*h;
2557 	time_t			expire_timestamp;
2558 
2559 	if (FAIL == zbx_history_add_values(history))
2560 		return FAIL;
2561 
2562 	if (ZBX_VC_DISABLED == vc_state)
2563 		return SUCCEED;
2564 
2565 	expire_timestamp = time(NULL) - ZBX_VC_ITEM_EXPIRE_PERIOD;
2566 
2567 	vc_try_lock();
2568 
2569 	for (i = 0; i < history->values_num; i++)
2570 	{
2571 		h = (ZBX_DC_HISTORY *)history->values[i];
2572 
2573 		if (NULL != (item = (zbx_vc_item_t *)zbx_hashset_search(&vc_cache->items, &h->itemid)))
2574 		{
2575 			zbx_history_record_t	record = {h->ts, h->value};
2576 
2577 			if (0 == (item->state & ZBX_ITEM_STATE_REMOVE_PENDING))
2578 			{
2579 				vc_item_addref(item);
2580 
2581 				/* If the new value type does not match the item's type in cache we can't  */
2582 				/* change the cache because other processes might still be accessing it    */
2583 				/* at the same time. The only thing that can be done - mark it for removal */
2584 				/* so it could be added later with new type.                               */
2585 				/* Also mark it for removal if the value adding failed. In this case we    */
2586 				/* won't have the latest data in cache - so the requests must go directly  */
2587 				/* to the database.                                                        */
2588 				if (item->value_type != h->value_type || item->last_accessed < expire_timestamp ||
2589 						FAIL == vch_item_add_value_at_head(item, &record))
2590 				{
2591 					item->state |= ZBX_ITEM_STATE_REMOVE_PENDING;
2592 				}
2593 
2594 				vc_item_release(item);
2595 			}
2596 		}
2597 	}
2598 
2599 	vc_try_unlock();
2600 
2601 	return SUCCEED;
2602 }
2603 
2604 /******************************************************************************
2605  *                                                                            *
2606  * Function: zbx_vc_get_values                                                *
2607  *                                                                            *
2608  * Purpose: get item history data for the specified time period               *
2609  *                                                                            *
2610  * Parameters: itemid     - [IN] the item id                                  *
2611  *             value_type - [IN] the item value type                          *
2612  *             values     - [OUT] the item history data stored time/value     *
2613  *                          pairs in descending order                         *
2614  *             seconds    - [IN] the time period to retrieve data for         *
2615  *             count      - [IN] the number of history values to retrieve     *
2616  *             ts         - [IN] the period end timestamp                     *
2617  *                                                                            *
2618  * Return value:  SUCCEED - the item history data was retrieved successfully  *
2619  *                FAIL    - the item history data was not retrieved           *
2620  *                                                                            *
2621  * Comments: If the data is not in cache, it's read from DB, so this function *
2622  *           will always return the requested data, unless some error occurs. *
2623  *                                                                            *
2624  *           If <count> is set then value range is defined as <count> values  *
2625  *           before <timestamp>. Otherwise the range is defined as <seconds>  *
2626  *           seconds before <timestamp>.                                      *
2627  *                                                                            *
2628  ******************************************************************************/
zbx_vc_get_values(zbx_uint64_t itemid,int value_type,zbx_vector_history_record_t * values,int seconds,int count,const zbx_timespec_t * ts)2629 int	zbx_vc_get_values(zbx_uint64_t itemid, int value_type, zbx_vector_history_record_t *values, int seconds,
2630 		int count, const zbx_timespec_t *ts)
2631 {
2632 	const char	*__function_name = "zbx_vc_get_values";
2633 	zbx_vc_item_t	*item = NULL;
2634 	int 		ret = FAIL, cache_used = 1;
2635 
2636 	zabbix_log(LOG_LEVEL_DEBUG, "In %s() itemid:" ZBX_FS_UI64 " value_type:%d seconds:%d count:%d sec:%d ns:%d",
2637 			__function_name, itemid, value_type, seconds, count, ts->sec, ts->ns);
2638 
2639 	vc_try_lock();
2640 
2641 	if (ZBX_VC_DISABLED == vc_state)
2642 		goto out;
2643 
2644 	if (ZBX_VC_MODE_LOWMEM == vc_cache->mode)
2645 		vc_warn_low_memory();
2646 
2647 	if (NULL == (item = (zbx_vc_item_t *)zbx_hashset_search(&vc_cache->items, &itemid)))
2648 	{
2649 		if (ZBX_VC_MODE_NORMAL == vc_cache->mode)
2650 		{
2651 			zbx_vc_item_t   new_item = {.itemid = itemid, .value_type = value_type};
2652 
2653 			if (NULL == (item = (zbx_vc_item_t *)zbx_hashset_insert(&vc_cache->items, &new_item, sizeof(zbx_vc_item_t))))
2654 				goto out;
2655 		}
2656 		else
2657 			goto out;
2658 	}
2659 
2660 	vc_item_addref(item);
2661 
2662 	if (0 != (item->state & ZBX_ITEM_STATE_REMOVE_PENDING) || item->value_type != value_type)
2663 		goto out;
2664 
2665 	ret = vch_item_get_values(item, values, seconds, count, ts);
2666 out:
2667 	if (FAIL == ret)
2668 	{
2669 		if (NULL != item)
2670 			item->state |= ZBX_ITEM_STATE_REMOVE_PENDING;
2671 
2672 		cache_used = 0;
2673 
2674 		vc_try_unlock();
2675 
2676 		ret = vc_db_get_values(itemid, value_type, values, seconds, count, ts);
2677 
2678 		vc_try_lock();
2679 
2680 		if (SUCCEED == ret)
2681 			vc_update_statistics(NULL, 0, values->values_num);
2682 	}
2683 
2684 	if (NULL != item)
2685 		vc_item_release(item);
2686 
2687 	vc_try_unlock();
2688 
2689 	zabbix_log(LOG_LEVEL_DEBUG, "End of %s():%s count:%d cached:%d",
2690 			__function_name, zbx_result_string(ret), values->values_num, cache_used);
2691 
2692 	return ret;
2693 }
2694 
2695 /******************************************************************************
2696  *                                                                            *
2697  * Function: zbx_vc_get_value                                                 *
2698  *                                                                            *
2699  * Purpose: get the last history value with a timestamp less or equal to the  *
2700  *          target timestamp                                                  *
2701  *                                                                            *
2702  * Parameters: itemid     - [IN] the item id                                  *
2703  *             value_type - [IN] the item value type                          *
2704  *             ts         - [IN] the target timestamp                         *
2705  *             value      - [OUT] the value found                             *
2706  *                                                                            *
2707  * Return Value: SUCCEED - the item was retrieved                             *
2708  *               FAIL    - otherwise                                          *
2709  *                                                                            *
2710  * Comments: Depending on the value type this function might allocate memory  *
2711  *           to store value data. To free it use zbx_vc_history_value_clear() *
2712  *           function.                                                        *
2713  *                                                                            *
2714  ******************************************************************************/
zbx_vc_get_value(zbx_uint64_t itemid,int value_type,const zbx_timespec_t * ts,zbx_history_record_t * value)2715 int	zbx_vc_get_value(zbx_uint64_t itemid, int value_type, const zbx_timespec_t *ts, zbx_history_record_t *value)
2716 {
2717 	zbx_vector_history_record_t	values;
2718 	int				ret = FAIL;
2719 
2720 	zbx_history_record_vector_create(&values);
2721 
2722 	if (SUCCEED != zbx_vc_get_values(itemid, value_type, &values, ts->sec, 1, ts) || 0 == values.values_num)
2723 		goto out;
2724 
2725 	*value = values.values[0];
2726 
2727 	/* reset values vector size so the returned value is not cleared when destroying the vector */
2728 	values.values_num = 0;
2729 
2730 	ret = SUCCEED;
2731 out:
2732 	zbx_history_record_vector_destroy(&values, value_type);
2733 
2734 	return ret;
2735 }
2736 
2737 /******************************************************************************
2738  *                                                                            *
2739  * Function: zbx_vc_get_statistics                                            *
2740  *                                                                            *
2741  * Purpose: retrieves usage cache statistics                                  *
2742  *                                                                            *
2743  * Parameters: stats     - [OUT] the cache usage statistics                   *
2744  *                                                                            *
2745  * Return value:  SUCCEED - the cache statistics were retrieved successfully  *
2746  *                FAIL    - failed to retrieve cache statistics               *
2747  *                          (cache was not initialized)                       *
2748  *                                                                            *
2749  ******************************************************************************/
zbx_vc_get_statistics(zbx_vc_stats_t * stats)2750 int	zbx_vc_get_statistics(zbx_vc_stats_t *stats)
2751 {
2752 	if (ZBX_VC_DISABLED == vc_state)
2753 		return FAIL;
2754 
2755 	vc_try_lock();
2756 
2757 	stats->hits = vc_cache->hits;
2758 	stats->misses = vc_cache->misses;
2759 	stats->mode = vc_cache->mode;
2760 
2761 	stats->total_size = vc_mem->total_size;
2762 	stats->free_size = vc_mem->free_size;
2763 
2764 	vc_try_unlock();
2765 
2766 	return SUCCEED;
2767 }
2768 
2769 /******************************************************************************
2770  *                                                                            *
2771  * Function: zbx_vc_lock                                                      *
2772  *                                                                            *
2773  * Purpose: locks the cache for batch usage                                   *
2774  *                                                                            *
2775  * Comments: Use zbx_vc_lock()/zbx_vc_unlock to explicitly lock/unlock cache  *
2776  *           for batch usage. The cache is automatically locked during every  *
2777  *           API call using the cache unless it was explicitly locked with    *
2778  *           zbx_vc_lock() function by the same process.                      *
2779  *                                                                            *
2780  ******************************************************************************/
zbx_vc_lock(void)2781 void	zbx_vc_lock(void)
2782 {
2783 	zbx_mutex_lock(vc_lock);
2784 	vc_locked = 1;
2785 }
2786 
2787 /******************************************************************************
2788  *                                                                            *
2789  * Function: zbx_vc_unlock                                                    *
2790  *                                                                            *
2791  * Purpose: unlocks cache after it has been locked with zbx_vc_lock()         *
2792  *                                                                            *
2793  * Comments: See zbx_vc_lock() function.                                      *
2794  *                                                                            *
2795  ******************************************************************************/
zbx_vc_unlock(void)2796 void	zbx_vc_unlock(void)
2797 {
2798 	vc_locked = 0;
2799 	zbx_mutex_unlock(vc_lock);
2800 }
2801 
2802 /******************************************************************************
2803  *                                                                            *
2804  * Function: zbx_vc_enable                                                    *
2805  *                                                                            *
2806  * Purpose: enables value caching for current process                         *
2807  *                                                                            *
2808  ******************************************************************************/
zbx_vc_enable(void)2809 void	zbx_vc_enable(void)
2810 {
2811 	if (NULL != vc_cache)
2812 		vc_state = ZBX_VC_ENABLED;
2813 }
2814 
2815 /******************************************************************************
2816  *                                                                            *
2817  * Function: zbx_vc_disable                                                   *
2818  *                                                                            *
2819  * Purpose: disables value caching for current process                        *
2820  *                                                                            *
2821  ******************************************************************************/
zbx_vc_disable(void)2822 void	zbx_vc_disable(void)
2823 {
2824 	vc_state = ZBX_VC_DISABLED;
2825 }
2826 
2827 #ifdef HAVE_TESTS
2828 #	include "../../../tests/libs/zbxdbcache/valuecache_test.c"
2829 #endif
2830