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