Skip to content

Glaber ValueCache Programmer's Guide

Architecture Overview

The ValueCache is a sophisticated three-layer caching system implemented in C++ that provides intelligent storage and retrieval of time-series monitoring data.

Component Hierarchy

items_valuecache (glb_state_valuecache.cpp)
├── glb_valuecache_raw (raw cache layer)
│   └── glb_tsbuff_t history_cache (time-series circular buffer)
├── glb_valuecache_downsampled (aggregated cache layer)
│   └── glb_tsbuff_t* trends_cache (downsampled time-series buffer)
└── item_demand_t (demand tracking and prediction)

Core Components

1. items_valuecache Class

File: src/libs/glb_state/glb_state_valuecache.cpp/hpp

Main interface for per-item value caching. Each monitored item has its own items_valuecache instance.

Key Members

class items_valuecache {
public:
    glb_valuecache_raw raw_cache;              // Recent high-resolution data
    glb_valuecache_downsampled downsampled_cache;  // Aggregated historical data
    item_demand_t demand;                       // Usage pattern tracking

    unsigned char value_type;                   // ITEM_VALUE_TYPE_* constant
    int last_accessed;                          // Last access timestamp
    mem_funcs_t *memf;                         // Memory allocation functions
};

Main Operations

Method Purpose Cache Interaction
fetch_by_time() Retrieve values for time period Reads from both caches, may trigger DB fetch
fetch_by_count() Retrieve N most recent values Primarily raw cache
add_value() Insert new monitoring value Adds to raw cache, triggers downsampling
add_value_lld() Insert LLD discovery value Raw cache only (text data)
clean_old_values() Remove obsolete data Both caches, demand-aware
fetch_trends() On-demand aggregation Computes from cached data

2. glb_valuecache_raw Class

File: src/libs/glb_state/glb_valuecache_raw.cpp/hpp

Manages the raw, high-resolution value cache using a circular time-series buffer.

Data Structure

Uses glb_tsbuff_t - a time-sorted circular buffer with: - Automatic growth when demand increases - Head/tail pointers for efficient FIFO operations - Time-based indexing for fast lookups

class glb_valuecache_raw {
private:
    glb_tsbuff_t history_cache;  // Circular buffer of glb_state_item_value_t

public:
    int add_value(u_int64_t itemid, ZBX_DC_HISTORY *h, int now, 
                  mem_funcs_t *memf, unsigned char value_type, 
                  item_demand_t &demand);

    int fetch_by_time(u_int64_t itemid, unsigned char value_type, 
                      int seconds, int time_shift, int now,
                      zbx_vector_history_record_t *values, 
                      item_demand_t &demand, mem_funcs_t *memf);
};

Growth Algorithm

When cache is full and demand is not met:

int calc_grow_buffer_size(int old_size) {
    int new_size = old_size * 120 / 100;  // Grow by 20%

    if ((new_size - old_size) < 8)        // Minimum growth: 8 elements
        new_size = old_size + 8;

    if ((new_size - old_size) > 64)       // Maximum growth: 64 elements
        new_size = old_size + 64;

    return new_size;
}

Database Fetch Strategy

When requested data is not in cache:

  1. Determine fetch range: Based on requested time/count and current cache state
  2. Check demand: Avoid fetching less than what demand indicates
  3. Fetch from DB: Using DCget_history_by_*() functions
  4. Populate cache: Insert fetched values maintaining time order
  5. Update demand: Record what was fetched for future optimization
// Fetch decision logic
if (request_time_from < oldest_cached_time) {
    // Need to fetch older data from DB
    fetch_from_db_by_time(itemid, request_time_from, oldest_cached_time, ...);
}

if (requested_count > cached_count) {
    // Need more values
    fetch_from_db_by_count(itemid, requested_count, ...);
}

3. glb_valuecache_downsampled Class

File: src/libs/glb_state/glb_valuecache_downsampled.cpp/hpp

Manages aggregated data for long-term history with reduced memory footprint.

Downsampling Process

void add_data_from_raw_cache(u_int64_t itemid, 
                              glb_valuecache_raw &raw_cache,
                              mem_funcs_t *memf, 
                              unsigned char value_type,
                              item_demand_t &demand)

Algorithm: 1. Check if raw cache has old data beyond GLB_CACHE_ITEMS_MAX_DURATION 2. Extract values in the "exceeding" period (configurable, e.g., 1 hour) 3. Aggregate extracted values (average for numeric types) 4. Create glb_downsample_item with aggregated value 5. Add to downsampled cache 6. Values remain in raw cache until cleanup

Downsample Item Structure

class glb_downsample_item {
public:
    unsigned int timestamp;  // Composite: time + count
    union {
        u_int64_t ui64;
        double dbl;
    } value;

    unsigned int get_count() const;      // Extract count from timestamp
    int get_timestamp() const;           // Extract actual timestamp
};

Timestamp Encoding: - Encodes both time and sample count in one field - timestamp / 3600 = hour-based time - timestamp % 3600 = count (max 3599 samples)

Lazy Initialization

Downsampled cache is created on-demand:

if (NULL == trends_cache) {
    // Allocate only when first downsampled data is needed
    trends_cache = allocate_new_tsbuff(...);
}

4. item_demand_t Class

File: include/objects/valuecache/item_demand.cpp/hpp

Tracks and predicts data access patterns to optimize cache allocation.

Demand Metrics

class item_demand_t {
public:
    // Current active demand
    int period;              // Time-based demand (seconds)
    int count;               // Count-based demand (number of values)
    int timeshift;           // Time offset from current time

    // Peak daily demand (learning mechanism)
    int daily_period;
    int daily_count;
    int daily_timeshift;

    // Last change timestamps
    int period_change;
    int count_change;
    int timeshift_change;

    // Database fetch tracking
    int db_fetched_time_from;     // Oldest time fetched from DB
    int db_fetched_count;         // Number of values fetched
    int db_fetched_timeshift;     // Timeshift used in fetch
};

Demand Update Algorithm

int update_demand(u_int64_t itemid, 
                  unsigned int new_count,
                  unsigned int new_period, 
                  unsigned int new_timeshift,
                  unsigned int now)

Logic: 1. Immediate Update: If new demand exceeds current, update immediately 2. Daily Learning: Track peak demands over 24 hours 3. Periodic Application: Every 24 hours, set demand to daily peak 4. Reset on Change: When demand increases, reset DB fetch tracking

Learning Period: GLB_CACHE_ITEM_DEMAND_UPDATE = 86400 seconds (24 hours)

// Example: Count demand update
if (count < new_count) {
    count = new_count;              // Immediate update
    daily_count = 0;                // Reset daily tracker
    count_change = now;             // Record change time
    reset_db_fetch_time_and_count(); // Will need to refetch
}

// After 24 hours, apply learned demand
if (now - count_change > 86400) {
    count = daily_count;            // Apply learned demand
    daily_count = 0;                // Start new learning cycle
}

Demand Checking

Two types of demand validation:

1. Count-Based Demand:

int ensure_cache_demand_by_count_met(u_int64_t itemid, glb_tsbuff_t &tsbuf)
- Without timeshift: cache_count - 1 >= demand.count - With timeshift: Count values between now - timeshift and oldest

2. Time-Based Demand:

int check_cache_demand_by_time_is_met(u_int64_t itemid, glb_tsbuff_t &tsbuf)
- Check: oldest_cached_time <= (now - period - timeshift)

Both must be satisfied for demand to be met.

Data Structures

glb_state_item_value_t

Core value storage structure:

struct glb_state_item_value_t {
    int time_sec;        // Timestamp (seconds since epoch)
    union {
        double dbl;      // Float value (ITEM_VALUE_TYPE_FLOAT)
        u_int64_t ui64;  // Integer value (ITEM_VALUE_TYPE_UINT64)
        char *str;       // String value (TEXT/STR/LOG types)
    } value;

    void clear(mem_funcs_t *memf, unsigned char value_type);
    void set_from_dc_history_record(mem_funcs_t *memf, 
                                     ZBX_DC_HISTORY *record, 
                                     unsigned char value_type);
    void save_to_history_record(zbx_history_record_t *record, 
                                 unsigned char value_type);
};

Memory Management: - String types: Dynamically allocated via memf->malloc_func() - Numeric types: Stored directly in union - Cleanup: clear() method frees string memory

glb_tsbuff_t

Time-series circular buffer (defined elsewhere in codebase):

Properties: - Fixed-size, can be resized dynamically - Head/tail pointers for FIFO operations - Time-ordered storage - Fast time-based lookups

Key Operations: - glb_tsbuff_add_to_head(): Add newest value - glb_tsbuff_get_value_tail(): Get oldest value - glb_tsbuff_free_tail(): Remove oldest value - glb_tsbuff_find_time_idx(): Binary search by timestamp - glb_tsbuff_resize(): Grow/shrink buffer

Key Algorithms

Fetch by Time Algorithm

int items_valuecache::fetch_by_time(u_int64_t itemid, int value_type, 
                                     int seconds, int time_shift, int now,
                                     zbx_vector_history_record_t *values, 
                                     mem_funcs_t *memf)

Flow:

1. Update Demand
   └── demand.update_demand(itemid, 0, seconds, time_shift, now)

2. Fetch from Raw Cache
   └── May trigger DB fetch if data missing
   └── Populates both raw and downsampled caches

3. Decision Point: Is request within MAX_DURATION?

   YES (recent data only):
   ├── Return raw cache values only
   └── Return SUCCEED/FAIL

   NO (extends beyond MAX_DURATION):
   ├── Fetch older data from downsampled cache
   │   └── Calculate: downsampled_seconds = max_threshold - fetch_from
   │   └── downsampled_cache.fetch_by_time(...)
   ├── Append raw cache values (newer data)
   └── Return combined result

Example: - now = 1000000 - GLB_CACHE_ITEMS_MAX_DURATION = 172800 (2 days) - Request: seconds = 259200 (3 days), time_shift = 0

Request range: [740800 to 1000000]
Raw threshold: [827200 to 1000000]  (within 2 days)
Downsampled:   [740800 to 827200]   (beyond 2 days)

Actions:
1. Fetch 172800 seconds from raw cache → [827200, 1000000]
2. Fetch 86400 seconds from downsampled → [740800, 827200]
3. Merge: downsampled values + raw values

Add Value with Downsampling

int items_valuecache::add_value(u_int64_t itemid, ZBX_DC_HISTORY *h, 
                                 int now, mem_funcs_t *memf)

Flow:

1. Value Type Check
   ├── If value_type changed → reset cache
   └── Update value_type

2. Add to Raw Cache
   └── raw_cache.add_value(itemid, h, now, memf, value_type, demand)
       ├── Ensure space (demand-aware)
       │   ├── If full AND demand met → rotate (free tail)
       │   └── If full AND demand NOT met → grow buffer
       └── Insert value at head

3. Trigger Downsampling
   └── downsampled_cache.add_data_from_raw_cache(...)
       ├── Check if raw cache has old data (> MAX_DURATION)
       ├── Extract values from last downsampling period
       ├── Aggregate (average for numeric types)
       ├── Create downsampled item
       └── Add to downsampled cache

4. Update Access Time
   └── last_accessed = now

Cleanup Algorithm

void items_valuecache::clean_old_values(u_int64_t itemid)

Two-Stage Cleanup:

Stage 1: Raw Cache Cleanup

int clean_time = now - GLB_CACHE_ITEMS_MAX_DURATION;
int clean_min_count = GLB_CACHE_ITEMS_MIN_CLEAN_COUNT;

raw_cache.clean_old_values(clean_time, clean_min_count, ...);
- Remove values older than clean_time - Keep at least clean_min_count values

Stage 2: Downsampled Cache Cleanup

downsampled_cache.clean_old_values(itemid, demand);
- Remove oldest values while demand is still met - Check both count and time demands - Stop when removing more would violate demand

Pseudo-code:

while (cache_has_values) {
    if (demand_met_without_oldest_value) {
        remove_oldest_value();
    } else {
        break;  // Stop cleanup
    }
}

Trends/Aggregation Algorithm

int items_valuecache::fetch_trends(u_int64_t itemid, int value_type,
                                    int time_from, int time_to,
                                    int aggregation_hours, int trend_function,
                                    zbx_vector_history_record_t *values,
                                    std::string &error, mem_funcs_t *memf)

Purpose: Compute on-demand aggregations (hourly, daily, etc.) from cached data.

Flow:

1. Validation
   ├── Check value_type (only FLOAT/UINT64 supported)
   ├── Validate aggregation_hours > 0
   └── Validate time_from < time_to

2. Exclude Current Incomplete Period
   └── Adjust time_to to last complete aggregation boundary
       Example: If aggregation_hours=1 and now=10:30
                time_to adjusted to 10:00

3. Fetch Raw Values
   └── Call fetch_by_time() to get all values in [time_from, time_to]
       (May come from both raw and downsampled caches)

4. Aggregate into Buckets
   ├── Calculate bucket boundaries
   │   first_bucket = (time_from / aggregation_seconds) * aggregation_seconds
   │   num_buckets = (time_to - first_bucket) / aggregation_seconds
   │
   └── For each bucket:
       ├── Collect all values in [bucket_start, bucket_end)
       ├── Calculate statistics: sum, min, max, count
       └── Apply trend_function:
           ├── AVG: sum / count
           ├── MIN: min_val
           ├── MAX: max_val
           ├── SUM: sum
           └── COUNT: count

5. Create Result Records
   └── For each non-empty bucket:
       ├── timestamp = bucket_start
       └── value = aggregated_value

Example:

Request: Hourly averages from 2024-01-01 00:00 to 2024-01-01 05:00
aggregation_hours = 1 (3600 seconds)

Buckets:
  [00:00, 01:00) → avg of values in this hour → result[0]
  [01:00, 02:00) → avg of values in this hour → result[1]
  [02:00, 03:00) → avg of values in this hour → result[2]
  [03:00, 04:00) → avg of values in this hour → result[3]
  [04:00, 05:00) → avg of values in this hour → result[4]

Serialization (Dump/Load)

Dump Format

JSON Structure:

{
  "value_type": 0,
  "last_accessed": 1234567890,
  "demand": {
    "count": 100,
    "period": 3600,
    "timeshift": 0
  },
  "raw_values": [
    {"clock": 1234567890, "value": 42.5},
    {"clock": 1234567891, "value": 43.2},
    ...
  ],
  "downsampled_values": [
    {"clock": 1234560000, "value": 40.1, "count": 60},
    {"clock": 1234563600, "value": 41.3, "count": 58},
    ...
  ]
}

Dump Operation

void items_valuecache::dump_to_json(struct zbx_json *json, 
                                     unsigned char value_type)

Process: 1. Serialize metadata (value_type, last_accessed) 2. Serialize demand object 3. Serialize raw cache values (array) 4. Serialize downsampled cache values (array)

Load Operation

int items_valuecache::load_values_from_json(u_int64_t itemid,
                                             struct zbx_json_parse *jp_valuecache,
                                             mem_funcs_t *memf)

Process: 1. Load metadata (load_metadata()) 2. Parse and load raw values - Convert JSON to ZBX_DC_HISTORY format - Insert into raw cache using add_value() 3. Parse and load downsampled values - Convert JSON to downsampled items - Insert into downsampled cache 4. Reconstruct demand state

Error Handling: - Failures in loading raw values logged but not fatal - Failures in loading downsampled values logged but not fatal - Metadata load failure is fatal

Memory Management

Memory Function Pointers

All allocations use custom memory functions:

typedef struct {
    void* (*malloc_func)(void*, size_t);
    void (*free_func)(void*);
    void* (*realloc_func)(void*, size_t);
} mem_funcs_t;

Why?: Allows use of shared memory allocators for cache data.

String Handling

For text-based value types:

// Allocation
value.str = (char*)memf->malloc_func(NULL, strlen(source) + 1);
strcpy(value.str, source);

// Deallocation
if (value_type == ITEM_VALUE_TYPE_TEXT || ...) {
    memf->free_func(value.str);
}

Important: Always check value_type before freeing/accessing string pointers.

Buffer Resizing

Raw cache growth:

int glb_tsbuff_resize(glb_tsbuff_t *tsbuf, int new_size,
                      void* (*malloc_func)(void*, size_t),
                      void (*free_func)(void*),
                      void *context);
  • Allocates new buffer of new_size
  • Copies existing values
  • Frees old buffer
  • Updates pointers

Configuration Constants

External Configuration (from zabbix_server.conf)

extern u_int64_t CONFIG_VALUE_CACHE_DEFAULT_ELEMENTS; // Initial buffer size
extern u_int64_t GLB_CACHE_ITEMS_MAX_DURATION;       // Raw cache max age
extern u_int64_t GLB_CACHE_ITEMS_MIN_CLEAN_COUNT;    // Min values to keep
extern u_int64_t GLB_DOWNSAMPLE_PERIOD;              // Downsampling interval

Internal Constants

#define CACHE_RESERVE_PERCENT 10        // Buffer reserve capacity
#define CACHE_RESERVE_COUNT 32          // Minimum reserve slots
#define GLB_CACHE_ITEM_DEMAND_UPDATE 86400  // Demand learning period (24h)

Trend Functions

#define TREND_FUNCTION_AVG   0
#define TREND_FUNCTION_MIN   1
#define TREND_FUNCTION_MAX   2
#define TREND_FUNCTION_SUM   3
#define TREND_FUNCTION_COUNT 4

Threading and Concurrency

Important: The code shown does not include explicit locking. Concurrency control is expected to be handled at a higher level (e.g., in the item management layer).

Assumptions: - Per-item operations are serialized by caller - Shared memory access is protected externally - mem_funcs_t operations are thread-safe

Recommendation: When integrating, ensure: 1. Each item's valuecache accessed by single thread at a time 2. Or, add mutex protection around all public methods 3. Memory allocator (memf) is thread-safe

Error Handling

Return Codes

  • SUCCEED (0): Operation successful
  • FAIL (typically -1): Operation failed

Error Scenarios

Scenario Return Action
Cache miss (no data) FAIL Trigger DB fetch
DB fetch fails FAIL Log error, return empty result
Value type mismatch FAIL Reset cache with new type
Invalid parameters FAIL Log error, return
Memory allocation fails FAIL Log critical error

Logging

Uses Glaber debug macros:

DEBUG_ITEM(itemid, "Message format %d", value);
LOG_INF("Information message");

Enable detailed logging for specific items by setting debug level in configuration.

Performance Considerations

Optimization Techniques

  1. Circular Buffers: O(1) add/remove operations
  2. Time-Based Indexing: Binary search for time lookups
  3. Lazy Downsampling: Only aggregate when beneficial
  4. Demand-Based Allocation: Memory used only where needed
  5. Database Fetch Coalescing: Fetch once, cache for multiple queries

Cache Hit Optimization

To maximize cache hits: 1. Set ValueCacheMaxDuration to cover typical query ranges 2. Allow 24-hour learning period for demand tracking 3. Pre-warm cache by querying historical data 4. Monitor and tune based on access patterns

Memory Efficiency

Downsampling reduces memory by: - Aggregating multiple values into one - Storing only timestamp + aggregated value + count - Typically 10-100x compression for old data

Example: - Raw: 3600 values × 16 bytes = 57.6 KB per hour - Downsampled: 1 value × 16 bytes = 16 bytes per hour - Compression: ~3600:1

Integration Points

Item Management Layer

The valuecache integrates with the item management system:

// In item lifecycle
item_init()  valuecache.init(memf)
item_add_value()  valuecache.add_value(...)
item_fetch_history()  valuecache.fetch_by_time(...)
item_destroy()  valuecache.destroy(memf)

Database Backend

Cache misses trigger database fetches via: - DCget_history_by_time(): Fetch values in time range - DCget_history_by_count(): Fetch N most recent values

These functions must be implemented in the database layer.

Configuration System

Read configuration during initialization:

GLB_CACHE_ITEMS_MAX_DURATION = read_config("ValueCacheMaxDuration", default);
// etc.

Testing Strategies

Unit Testing

Test individual components in isolation:

  1. Demand Tracking:
  2. Test demand updates with various patterns
  3. Verify 24-hour learning cycle
  4. Check demand satisfaction logic

  5. Raw Cache:

  6. Test buffer growth/shrink
  7. Verify cleanup with demand
  8. Test time/count-based fetches

  9. Downsampled Cache:

  10. Test aggregation correctness
  11. Verify composite timestamp encoding
  12. Test demand-based cleanup

Integration Testing

Test component interactions:

  1. Add → Downsample → Fetch:
  2. Add values over time
  3. Verify automatic downsampling
  4. Fetch mixed raw+downsampled data

  5. Demand-Based Growth:

  6. Simulate increasing demand
  7. Verify buffer grows appropriately
  8. Check cleanup doesn't violate demand

  9. Dump → Restart → Load:

  10. Dump cache state to JSON
  11. Clear cache
  12. Load from JSON
  13. Verify identical state

Performance Testing

Benchmark key operations:

// Example benchmark
for (int i = 0; i < 10000; i++) {
    valuecache.add_value(itemid, &history, now + i, memf);
}
// Measure: throughput, memory usage, cache hit rate

for (int i = 0; i < 1000; i++) {
    valuecache.fetch_by_time(itemid, value_type, 3600, 0, now, &values, memf);
}
// Measure: latency, cache hits vs DB fetches

Future Enhancements

Potential Improvements

  1. Compression: Compress old data before downsampling
  2. Adaptive Downsampling: Variable aggregation periods based on data characteristics
  3. Predictive Fetching: Prefetch likely-needed data from DB
  4. Lock-Free Structures: Improve concurrency with lock-free data structures
  5. Tiered Downsampling: Multiple downsampling levels (hourly → daily → weekly)

Extensibility Points

To extend the valuecache:

  1. New Aggregation Functions:
  2. Add to TREND_FUNCTION_* constants
  3. Implement in fetch_trends() switch statement

  4. Custom Value Types:

  5. Extend glb_state_item_value_t union
  6. Update clear(), set_from_*(), save_to_*() methods

  7. Alternative Storage:

  8. Replace glb_tsbuff_t with custom structure
  9. Implement same interface (add, get, resize, etc.)

Common Pitfalls

1. Memory Leaks with Strings

Problem: Forgetting to free string values

// BAD
glb_tsbuff_free_tail(&cache);  // Leaks string memory!

// GOOD
glb_state_item_value_t *val = (glb_state_item_value_t*)glb_tsbuff_get_value_tail(&cache);
val->clear(memf, value_type);  // Frees string first
glb_tsbuff_free_tail(&cache);

2. Value Type Mismatches

Problem: Accessing wrong union member

// BAD
if (value_type == ITEM_VALUE_TYPE_FLOAT) {
    printf("%llu", val->value.ui64);  // Wrong member!
}

// GOOD
if (value_type == ITEM_VALUE_TYPE_FLOAT) {
    printf("%f", val->value.dbl);
}

3. Ignoring Demand Updates

Problem: Not updating demand on fetches

// BAD
int fetch_by_time(...) {
    // Forgot to update demand!
    return raw_cache.fetch_by_time(...);
}

// GOOD
int fetch_by_time(...) {
    demand.update_demand(itemid, 0, seconds, time_shift, now);
    return raw_cache.fetch_by_time(...);
}

4. Incomplete Cleanup

Problem: Cleaning raw cache but not downsampled

// BAD
void clean_old_values() {
    raw_cache.clean_old_values(...);
    // Forgot downsampled cache!
}

// GOOD
void clean_old_values() {
    raw_cache.clean_old_values(...);
    downsampled_cache.clean_old_values(...);
}

Code Examples

Complete Fetch Example

// Fetch last hour of data for an item
u_int64_t itemid = 12345;
int value_type = ITEM_VALUE_TYPE_FLOAT;
int seconds = 3600;        // 1 hour
int time_shift = 0;        // No time shift
int now = glb_time(NULL);

zbx_vector_history_record_t values;
zbx_history_record_vector_create(&values);

// Fetch from cache (may trigger DB fetch)
int ret = valuecache.fetch_by_time(itemid, value_type, seconds, 
                                    time_shift, now, &values, memf);

if (ret == SUCCEED) {
    // Process values
    for (int i = 0; i < values.values_num; i++) {
        printf("Time: %d, Value: %f\n",
               values.values[i].timestamp.sec,
               values.values[i].value.dbl);
    }
}

// Cleanup
zbx_history_record_vector_destroy(&values, value_type);

Complete Add Example

// Add a new monitoring value
ZBX_DC_HISTORY history;
history.itemid = 12345;
history.metric.ts.sec = glb_time(NULL);
history.metric.ts.ns = 0;
history.hist_value_type = ITEM_VALUE_TYPE_FLOAT;
history.metric.value.data.dbl = 42.5;

int now = glb_time(NULL);

// Add to cache (triggers downsampling if needed)
int ret = valuecache.add_value(itemid, &history, now, memf);

if (ret == SUCCEED) {
    printf("Value added to cache\n");
} else {
    printf("Failed to add value\n");
}
// Get hourly averages for last 24 hours
u_int64_t itemid = 12345;
int value_type = ITEM_VALUE_TYPE_FLOAT;
int now = glb_time(NULL);
int time_from = now - 86400;  // 24 hours ago
int time_to = now;
int aggregation_hours = 1;     // 1-hour buckets
int trend_function = TREND_FUNCTION_AVG;

zbx_vector_history_record_t trends;
zbx_history_record_vector_create(&trends);
std::string error;

// Compute trends from cached data
int ret = valuecache.fetch_trends(itemid, value_type, time_from, time_to,
                                   aggregation_hours, trend_function,
                                   &trends, error, memf);

if (ret == SUCCEED) {
    printf("Generated %d hourly averages\n", trends.values_num);
    for (int i = 0; i < trends.values_num; i++) {
        printf("Hour starting %d: avg = %f\n",
               trends.values[i].timestamp.sec,
               trends.values[i].value.dbl);
    }
} else {
    printf("Failed to compute trends: %s\n", error.c_str());
}

zbx_history_record_vector_destroy(&trends, value_type);

Debugging

Enable Debug Logging

In configuration:

DebugLevel=4

Or programmatically:

#define DEBUG_ITEM_ENABLED

Key Debug Messages

Look for these patterns:

"Fetching %d seconds from raw cache"
"Cache demand IS MET/IS NOT MET without last item"
"Downsampled cache added %d values"
"Resizing cache %d->%d"
"DB fetch completed: %d values"

Memory Inspection

Check cache statistics:

int count = valuecache.get_cache_count();
int size = valuecache.get_cache_size();
printf("Cache: %d values, %d bytes\n", count, size);

// Inspect demand
printf("Demand: count=%d, period=%d, timeshift=%d\n",
       valuecache.demand.count,
       valuecache.demand.period,
       valuecache.demand.timeshift);

Dump Cache State

struct zbx_json json;
zbx_json_init(&json, 1024);
valuecache.dump_to_json(&json, value_type);
printf("%s\n", json.buffer);
zbx_json_free(&json);

Summary

The Glaber ValueCache is a sophisticated, adaptive caching system that:

  • Intelligently manages memory based on actual usage patterns
  • Optimizes performance through two-tier caching (raw + downsampled)
  • Minimizes database load by learning and predicting data needs
  • Provides persistence through JSON serialization
  • Supports trends with on-demand aggregation

The modular design allows easy extension and testing, while the demand-based approach ensures efficient resource utilization in production environments.