Variable Granularity support for data collection (#6430)
* Variable Granularity support for data collection in the dbengine. * Variable Granularity support for data collection in the daemon. * Added tests to validate the data being queried after having been collected by changing data collection interval * Fix memory corruption * Updated database engine documentation about data collection frequency behaviour
Markos Fountoulakis committed
Aug 28, 2019 at 17:33 UTC
2a06960117fa020b94783c54785fa6e3b8b6ee5c
15 files changed
+1604
-294
backends/backends.c
+2
-1
@@ -131,7 +131,8 @@ calculated_number backend_calculate_value_from_stored_data(
131
}
132
*/
133
for(rd->state->query_ops.init(rd, &handle, after, before) ; !rd->state->query_ops.is_finished(&handle) ; ) {
134
- n = rd->state->query_ops.next_metric(&handle);
134
+ time_t curr_t;
135
+ n = rd->state->query_ops.next_metric(&handle, &curr_t);
136
137
if(unlikely(!does_storage_number_exist(n))) {
138
// not collected
daemon/unit_test.c
+281
-73
@@ -1581,34 +1581,12 @@ static inline void rrddim_set_by_pointer_fake_time(RRDDIM *rd, collected_number
1581
if(unlikely(v > rd->collected_value_max)) rd->collected_value_max = v;
1582
}
1583
1584
-int test_dbengine(void)
1584
+static RRDHOST *dbengine_rrdhost_find_or_create(char *name)
1585
{
1586
- const int CHARTS = 128;
1587
- const int DIMS = 16; /* That gives us 2048 metrics */
1588
- const int POINTS = 16384; /* This produces 128MiB of metric data */
1589
- const int QUERY_BATCH = 4096;
1590
- uint8_t same;
1591
- int i, j, k, c, errors;
1592
- RRDHOST *host = NULL;
1593
- RRDSET *st[CHARTS];
1594
- RRDDIM *rd[CHARTS][DIMS];
1595
- char name[101];
1596
- time_t time_now;
1597
- collected_number last;
1598
- struct rrddim_query_handle handle;
1599
- calculated_number value, expected;
1600
- storage_number n;
1601
-
1602
- error_log_limit_unlimited();
1603
- fprintf(stderr, "\nRunning DB-engine test\n");
1604
-
1605
- default_rrd_memory_mode = RRD_MEMORY_MODE_DBENGINE;
1606
-
1607
- debug(D_RRDHOST, "Initializing localhost with hostname 'unittest-dbengine'");
1608
- host = rrdhost_find_or_create(
1609
- "unittest-dbengine"
1610
- , "unittest-dbengine"
1611
- , "unittest-dbengine"
1586
+ return rrdhost_find_or_create(
1587
+ name
1588
+ , name
1589
+ , name
1590
, os_type
1591
, netdata_configured_timezone
1592
, config_get(CONFIG_SECTION_BACKEND, "host tags", "")
@@ -1624,15 +1602,33 @@ int test_dbengine(void)
1602
, default_rrdpush_send_charts_matching
1603
, NULL
1604
);
1627
- if (NULL == host)
1628
- return 1;
1605
+}
1606
+
1607
+// costants for test_dbengine
1608
+static const int CHARTS = 64;
1609
+static const int DIMS = 16; // That gives us 64 * 16 = 1024 metrics
1610
+#define REGIONS (3) // 3 regions of update_every
1611
+// first region update_every is 2, second is 3, third is 1
1612
+static const int REGION_UPDATE_EVERY[REGIONS] = {2, 3, 1};
1613
+static const int REGION_POINTS[REGIONS] = {
1614
+ 16384, // This produces 64MiB of metric data for the first region: update_every = 2
1615
+ 16384, // This produces 64MiB of metric data for the second region: update_every = 3
1616
+ 16384, // This produces 64MiB of metric data for the third region: update_every = 1
1617
+};
1618
+static const int QUERY_BATCH = 4096;
1619
+
1620
+static void test_dbengine_create_charts(RRDHOST *host, RRDSET *st[CHARTS], RRDDIM *rd[CHARTS][DIMS],
1621
+ int update_every)
1622
+{
1623
+ int i, j;
1624
+ char name[101];
1625
1626
for (i = 0 ; i < CHARTS ; ++i) {
1627
snprintfz(name, 100, "dbengine-chart-%d", i);
1628
1629
// create the chart
1630
st[i] = rrdset_create(host, "netdata", name, name, "netdata", NULL, "Unit Testing", "a value", "unittest",
1635
- NULL, 1, 1, RRDSET_TYPE_LINE);
1631
+ NULL, 1, update_every, RRDSET_TYPE_LINE);
1632
rrdset_flag_set(st[i], RRDSET_FLAG_DEBUG);
1633
rrdset_flag_set(st[i], RRDSET_FLAG_STORE_FIRST);
1634
for (j = 0 ; j < DIMS ; ++j) {
@@ -1642,50 +1638,103 @@ int test_dbengine(void)
1638
}
1639
}
1640
1641
+ // Initialize DB with the very first entries
1642
+ for (i = 0 ; i < CHARTS ; ++i) {
1643
+ for (j = 0 ; j < DIMS ; ++j) {
1644
+ rd[i][j]->last_collected_time.tv_sec =
1645
+ st[i]->last_collected_time.tv_sec = st[i]->last_updated.tv_sec = 2 * API_RELATIVE_TIME_MAX - 1;
1646
+ rd[i][j]->last_collected_time.tv_usec =
1647
+ st[i]->last_collected_time.tv_usec = st[i]->last_updated.tv_usec = 0;
1648
+ }
1649
+ }
1650
+ for (i = 0 ; i < CHARTS ; ++i) {
1651
+ st[i]->usec_since_last_update = USEC_PER_SEC;
1652
+
1653
+ for (j = 0; j < DIMS; ++j) {
1654
+ rrddim_set_by_pointer_fake_time(rd[i][j], 69, 2 * API_RELATIVE_TIME_MAX); // set first value to 69
1655
+ }
1656
+ rrdset_done(st[i]);
1657
+ }
1658
+ // Fluh pages for subsequent real values
1659
+ for (i = 0 ; i < CHARTS ; ++i) {
1660
+ for (j = 0; j < DIMS; ++j) {
1661
+ rrdeng_store_metric_flush_current_page(rd[i][j]);
1662
+ }
1663
+ }
1664
+}
1665
+
1666
+// Feeds the database region with test data, returns last timestamp of region
1667
+static time_t test_dbengine_create_metrics(RRDSET *st[CHARTS], RRDDIM *rd[CHARTS][DIMS],
1668
+ int current_region, time_t time_start)
1669
+{
1670
+ time_t time_now;
1671
+ int i, j, c, update_every;
1672
+ collected_number next;
1673
+
1674
+ update_every = REGION_UPDATE_EVERY[current_region];
1675
+ time_now = time_start + update_every;
1676
// feed it with the test data
1646
- time_now = 1;
1647
- last = 0;
1677
for (i = 0 ; i < CHARTS ; ++i) {
1678
for (j = 0 ; j < DIMS ; ++j) {
1679
rd[i][j]->last_collected_time.tv_sec =
1680
st[i]->last_collected_time.tv_sec = st[i]->last_updated.tv_sec = time_now;
1681
rd[i][j]->last_collected_time.tv_usec =
1653
- st[i]->last_collected_time.tv_usec = st[i]->last_updated.tv_usec = 0;
1682
+ st[i]->last_collected_time.tv_usec = st[i]->last_updated.tv_usec = 0;
1683
}
1684
}
1656
- for(c = 0; c < POINTS ; ++c) {
1657
- ++time_now; // time_now = c + 2
1685
+ for (c = 0; c < REGION_POINTS[current_region] ; ++c) {
1686
+ time_now += update_every; // time_now = start + (c + 2) * update_every
1687
for (i = 0 ; i < CHARTS ; ++i) {
1659
- st[i]->usec_since_last_update = USEC_PER_SEC;
1688
+ st[i]->usec_since_last_update = USEC_PER_SEC * update_every;
1689
1690
for (j = 0; j < DIMS; ++j) {
1662
- last = i * DIMS * POINTS + j * POINTS + c;
1663
- rrddim_set_by_pointer_fake_time(rd[i][j], last, time_now);
1691
+ next = i * DIMS * REGION_POINTS[current_region] + j * REGION_POINTS[current_region] + c;
1692
+ rrddim_set_by_pointer_fake_time(rd[i][j], next, time_now);
1693
}
1694
rrdset_done(st[i]);
1695
}
1696
}
1697
+ return time_now; //time_end
1698
+}
1699
1669
- // check the result
1700
+// Checks the metric data for the given region, returns number of errors
1701
+static int test_dbengine_check_metrics(RRDSET *st[CHARTS], RRDDIM *rd[CHARTS][DIMS],
1702
+ int current_region, time_t time_start)
1703
+{
1704
+ uint8_t same;
1705
+ time_t time_now, time_retrieved;
1706
+ int i, j, k, c, errors, update_every;
1707
+ collected_number last;
1708
+ calculated_number value, expected;
1709
+ storage_number n;
1710
+ struct rrddim_query_handle handle;
1711
+
1712
+ update_every = REGION_UPDATE_EVERY[current_region];
1713
errors = 0;
1714
1672
- for(c = 0; c < POINTS ; c += QUERY_BATCH) {
1673
- time_now = c + 2;
1715
+ // check the result
1716
+ for (c = 0; c < REGION_POINTS[current_region] ; c += QUERY_BATCH) {
1717
+ time_now = time_start + (c + 2) * update_every;
1718
for (i = 0 ; i < CHARTS ; ++i) {
1719
for (j = 0; j < DIMS; ++j) {
1676
- rd[i][j]->state->query_ops.init(rd[i][j], &handle, time_now, time_now + QUERY_BATCH);
1720
+ rd[i][j]->state->query_ops.init(rd[i][j], &handle, time_now, time_now + QUERY_BATCH * update_every);
1721
for (k = 0; k < QUERY_BATCH; ++k) {
1678
- last = i * DIMS * POINTS + j * POINTS + c + k;
1722
+ last = i * DIMS * REGION_POINTS[current_region] + j * REGION_POINTS[current_region] + c + k;
1723
expected = unpack_storage_number(pack_storage_number((calculated_number)last, SN_EXISTS));
1724
1681
- n = rd[i][j]->state->query_ops.next_metric(&handle);
1725
+ n = rd[i][j]->state->query_ops.next_metric(&handle, &time_retrieved);
1726
value = unpack_storage_number(n);
1727
1728
same = (calculated_number_round(value * 10000000.0) == calculated_number_round(expected * 10000000.0)) ? 1 : 0;
1729
if(!same) {
1730
fprintf(stderr, " DB-engine unittest %s/%s: at %lu secs, expecting value "
1731
CALCULATED_NUMBER_FORMAT ", found " CALCULATED_NUMBER_FORMAT ", ### E R R O R ###\n",
1688
- st[i]->name, rd[i][j]->name, (unsigned long)time_now + k, expected, value);
1732
+ st[i]->name, rd[i][j]->name, (unsigned long)time_now + k * update_every, expected, value);
1733
+ errors++;
1734
+ }
1735
+ if(time_retrieved != time_now + k * update_every) {
1736
+ fprintf(stderr, " DB-engine unittest %s/%s: at %lu secs, found timestamp %lu ### E R R O R ###\n",
1737
+ st[i]->name, rd[i][j]->name, (unsigned long)time_now + k * update_every, (unsigned long)time_retrieved);
1738
errors++;
1739
}
1740
}
@@ -1693,7 +1742,184 @@ int test_dbengine(void)
1742
}
1743
}
1744
}
1745
+ return errors;
1746
+}
1747
+
1748
+// Check rrdr transformations
1749
+static int test_dbengine_check_rrdr(RRDSET *st[CHARTS], RRDDIM *rd[CHARTS][DIMS],
1750
+ int current_region, time_t time_start, time_t time_end)
1751
+{
1752
+ uint8_t same;
1753
+ time_t time_now, time_retrieved;
1754
+ int i, j, errors, update_every;
1755
+ long c;
1756
+ collected_number last;
1757
+ calculated_number value, expected;
1758
+
1759
+ errors = 0;
1760
+ update_every = REGION_UPDATE_EVERY[current_region];
1761
+ long points = (time_end - time_start) / update_every - 1;
1762
+ for (i = 0 ; i < CHARTS ; ++i) {
1763
+ RRDR *r = rrd2rrdr(st[i], points, time_start + update_every, time_end, RRDR_GROUPING_AVERAGE, 0, 0, NULL);
1764
+ if (!r) {
1765
+ fprintf(stderr, " DB-engine unittest %s: empty RRDR ### E R R O R ###\n", st[i]->name);
1766
+ return ++errors;
1767
+ } else {
1768
+ assert(r->st == st[i]);
1769
+ for (c = 0; c != rrdr_rows(r) ; ++c) {
1770
+ RRDDIM *d;
1771
+ time_now = time_start + (c + 2) * update_every;
1772
+ time_retrieved = r->t[c];
1773
+
1774
+ // for each dimension
1775
+ for (j = 0, d = r->st->dimensions ; d && j < r->d ; ++j, d = d->next) {
1776
+ calculated_number *cn = &r->v[ c * r->d ];
1777
+ value = cn[j];
1778
+ assert(rd[i][j] == d);
1779
+
1780
+ last = i * DIMS * REGION_POINTS[current_region] + j * REGION_POINTS[current_region] + c;
1781
+ expected = unpack_storage_number(pack_storage_number((calculated_number)last, SN_EXISTS));
1782
+
1783
+ same = (calculated_number_round(value * 10000000.0) == calculated_number_round(expected * 10000000.0)) ? 1 : 0;
1784
+ if(!same) {
1785
+ fprintf(stderr, " DB-engine unittest %s/%s: at %lu secs, expecting value "
1786
+ CALCULATED_NUMBER_FORMAT ", RRDR found " CALCULATED_NUMBER_FORMAT ", ### E R R O R ###\n",
1787
+ st[i]->name, rd[i][j]->name, (unsigned long)time_now, expected, value);
1788
+ errors++;
1789
+ }
1790
+ if(time_retrieved != time_now) {
1791
+ fprintf(stderr, " DB-engine unittest %s/%s: at %lu secs, found RRDR timestamp %lu ### E R R O R ###\n",
1792
+ st[i]->name, rd[i][j]->name, (unsigned long)time_now, (unsigned long)time_retrieved);
1793
+ errors++;
1794
+ }
1795
+ }
1796
+ }
1797
+ rrdr_free(r);
1798
+ }
1799
+ }
1800
+ return errors;
1801
+}
1802
+
1803
+int test_dbengine(void)
1804
+{
1805
+ int i, j, errors, update_every, current_region;
1806
+ RRDHOST *host = NULL;
1807
+ RRDSET *st[CHARTS];
1808
+ RRDDIM *rd[CHARTS][DIMS];
1809
+ time_t time_start[REGIONS], time_end[REGIONS];
1810
+
1811
+ error_log_limit_unlimited();
1812
+ fprintf(stderr, "\nRunning DB-engine test\n");
1813
+
1814
+ default_rrd_memory_mode = RRD_MEMORY_MODE_DBENGINE;
1815
+
1816
+ debug(D_RRDHOST, "Initializing localhost with hostname 'unittest-dbengine'");
1817
+ host = dbengine_rrdhost_find_or_create("unittest-dbengine");
1818
+ if (NULL == host)
1819
+ return 1;
1820
+
1821
+ current_region = 0; // this is the first region of data
1822
+ update_every = REGION_UPDATE_EVERY[current_region]; // set data collection frequency to 2 seconds
1823
+ test_dbengine_create_charts(host, st, rd, update_every);
1824
+
1825
+ time_start[current_region] = 2 * API_RELATIVE_TIME_MAX;
1826
+ time_end[current_region] = test_dbengine_create_metrics(st,rd, current_region, time_start[current_region]);
1827
+
1828
+ errors = test_dbengine_check_metrics(st, rd, current_region, time_start[current_region]);
1829
+ if (errors)
1830
+ goto error_out;
1831
+
1832
+ current_region = 1; //this is the second region of data
1833
+ update_every = REGION_UPDATE_EVERY[current_region]; // set data collection frequency to 3 seconds
1834
+ // Align pages for frequency change
1835
+ for (i = 0 ; i < CHARTS ; ++i) {
1836
+ st[i]->update_every = update_every;
1837
+ for (j = 0; j < DIMS; ++j) {
1838
+ rrdeng_store_metric_flush_current_page(rd[i][j]);
1839
+ }
1840
+ }
1841
+
1842
+ time_start[current_region] = time_end[current_region - 1] + update_every;
1843
+ if (0 != time_start[current_region] % update_every) // align to update_every
1844
+ time_start[current_region] += update_every - time_start[current_region] % update_every;
1845
+ time_end[current_region] = test_dbengine_create_metrics(st,rd, current_region, time_start[current_region]);
1846
+
1847
+ errors = test_dbengine_check_metrics(st, rd, current_region, time_start[current_region]);
1848
+ if (errors)
1849
+ goto error_out;
1850
+
1851
+ current_region = 2; //this is the third region of data
1852
+ update_every = REGION_UPDATE_EVERY[current_region]; // set data collection frequency to 1 seconds
1853
+ // Align pages for frequency change
1854
+ for (i = 0 ; i < CHARTS ; ++i) {
1855
+ st[i]->update_every = update_every;
1856
+ for (j = 0; j < DIMS; ++j) {
1857
+ rrdeng_store_metric_flush_current_page(rd[i][j]);
1858
+ }
1859
+ }
1860
+
1861
+ time_start[current_region] = time_end[current_region - 1] + update_every;
1862
+ if (0 != time_start[current_region] % update_every) // align to update_every
1863
+ time_start[current_region] += update_every - time_start[current_region] % update_every;
1864
+ time_end[current_region] = test_dbengine_create_metrics(st,rd, current_region, time_start[current_region]);
1865
1866
+ errors = test_dbengine_check_metrics(st, rd, current_region, time_start[current_region]);
1867
+ if (errors)
1868
+ goto error_out;
1869
+
1870
+ for (current_region = 0 ; current_region < REGIONS ; ++current_region) {
1871
+ errors = test_dbengine_check_rrdr(st, rd, current_region, time_start[current_region], time_end[current_region]);
1872
+ if (errors)
1873
+ goto error_out;
1874
+ }
1875
+
1876
+ current_region = 1;
1877
+ update_every = REGION_UPDATE_EVERY[current_region]; // use the maximum update_every = 3
1878
+ errors = 0;
1879
+ long points = (time_end[REGIONS - 1] - time_start[0]) / update_every - 1; // cover all time regions with RRDR
1880
+ long point_offset = (time_start[current_region] - time_start[0]) / update_every;
1881
+ for (i = 0 ; i < CHARTS ; ++i) {
1882
+ RRDR *r = rrd2rrdr(st[i], points, time_start[0] + update_every, time_end[REGIONS - 1], RRDR_GROUPING_AVERAGE, 0, 0, NULL);
1883
+ if (!r) {
1884
+ fprintf(stderr, " DB-engine unittest %s: empty RRDR ### E R R O R ###\n", st[i]->name);
1885
+ ++errors;
1886
+ } else {
1887
+ long c;
1888
+
1889
+ assert(r->st == st[i]);
1890
+ // test current region values only, since they must be left unchanged
1891
+ for (c = point_offset ; c < point_offset + rrdr_rows(r) / REGIONS / 2 ; ++c) {
1892
+ RRDDIM *d;
1893
+ time_t time_now = time_start[current_region] + (c - point_offset + 2) * update_every;
1894
+ time_t time_retrieved = r->t[c];
1895
+
1896
+ // for each dimension
1897
+ for(j = 0, d = r->st->dimensions ; d && j < r->d ; ++j, d = d->next) {
1898
+ calculated_number *cn = &r->v[ c * r->d ];
1899
+ calculated_number value = cn[j];
1900
+ assert(rd[i][j] == d);
1901
+
1902
+ collected_number last = i * DIMS * REGION_POINTS[current_region] + j * REGION_POINTS[current_region] + c - point_offset;
1903
+ calculated_number expected = unpack_storage_number(pack_storage_number((calculated_number)last, SN_EXISTS));
1904
+
1905
+ uint8_t same = (calculated_number_round(value * 10000000.0) == calculated_number_round(expected * 10000000.0)) ? 1 : 0;
1906
+ if(!same) {
1907
+ fprintf(stderr, " DB-engine unittest %s/%s: at %lu secs, expecting value "
1908
+ CALCULATED_NUMBER_FORMAT ", RRDR found " CALCULATED_NUMBER_FORMAT ", ### E R R O R ###\n",
1909
+ st[i]->name, rd[i][j]->name, (unsigned long)time_now, expected, value);
1910
+ errors++;
1911
+ }
1912
+ if(time_retrieved != time_now) {
1913
+ fprintf(stderr, " DB-engine unittest %s/%s: at %lu secs, found RRDR timestamp %lu ### E R R O R ###\n",
1914
+ st[i]->name, rd[i][j]->name, (unsigned long)time_now, (unsigned long)time_retrieved);
1915
+ errors++;
1916
+ }
1917
+ }
1918
+ }
1919
+ rrdr_free(r);
1920
+ }
1921
+ }
1922
+error_out:
1923
rrdeng_exit(host->rrdeng_ctx);
1924
rrd_wrlock();
1925
rrdhost_delete_charts(host);
@@ -1704,43 +1930,25 @@ int test_dbengine(void)
1930
1931
void generate_dbengine_dataset(unsigned history_seconds)
1932
{
1707
- const int DIMS = 128;
1933
+ const int DSET_DIMS = 128;
1934
const uint64_t EXPECTED_COMPRESSION_RATIO = 94;
1709
- int j;
1935
+ int j, update_every = 1;
1936
RRDHOST *host = NULL;
1937
RRDSET *st;
1712
- RRDDIM *rd[DIMS];
1938
+ RRDDIM *rd[DSET_DIMS];
1939
char name[101];
1940
time_t time_current, time_present;
1941
1942
default_rrd_memory_mode = RRD_MEMORY_MODE_DBENGINE;
1943
default_rrdeng_page_cache_mb = 128;
1718
- /* Worst case for uncompressible data */
1719
- default_rrdeng_disk_quota_mb = (((uint64_t)DIMS) * sizeof(storage_number) * history_seconds) / (1024 * 1024);
1944
+ // Worst case for uncompressible data
1945
+ default_rrdeng_disk_quota_mb = (((uint64_t)DSET_DIMS) * sizeof(storage_number) * history_seconds) / (1024 * 1024);
1946
default_rrdeng_disk_quota_mb -= default_rrdeng_disk_quota_mb * EXPECTED_COMPRESSION_RATIO / 100;
1947
1948
error_log_limit_unlimited();
1949
debug(D_RRDHOST, "Initializing localhost with hostname 'dbengine-dataset'");
1950
1725
- host = rrdhost_find_or_create(
1726
- "dbengine-dataset"
1727
- , "dbengine-dataset"
1728
- , "dbengine-dataset"
1729
- , os_type
1730
- , netdata_configured_timezone
1731
- , config_get(CONFIG_SECTION_BACKEND, "host tags", "")
1732
- , program_name
1733
- , program_version
1734
- , default_rrd_update_every
1735
- , default_rrd_history_entries
1736
- , RRD_MEMORY_MODE_DBENGINE
1737
- , default_health_enabled
1738
- , default_rrdpush_enabled
1739
- , default_rrdpush_destination
1740
- , default_rrdpush_api_key
1741
- , default_rrdpush_send_charts_matching
1742
- , NULL
1743
- );
1951
+ host = dbengine_rrdhost_find_or_create("dbengine-dataset");
1952
if (NULL == host)
1953
return;
1954
@@ -1748,8 +1956,8 @@ void generate_dbengine_dataset(unsigned history_seconds)
1956
1957
// create the chart
1958
st = rrdset_create(host, "example", "random", "random", "example", NULL, "random", "random", "random",
1751
- NULL, 1, 1, RRDSET_TYPE_LINE);
1752
- for (j = 0 ; j < DIMS ; ++j) {
1959
+ NULL, 1, update_every, RRDSET_TYPE_LINE);
1960
+ for (j = 0 ; j < DSET_DIMS ; ++j) {
1961
snprintfz(name, 100, "random%d", j);
1962
1963
rd[j] = rrddim_add(st, name, NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
@@ -1758,7 +1966,7 @@ void generate_dbengine_dataset(unsigned history_seconds)
1966
time_present = now_realtime_sec();
1967
// feed it with the test data
1968
time_current = time_present - history_seconds;
1761
- for (j = 0 ; j < DIMS ; ++j) {
1969
+ for (j = 0 ; j < DSET_DIMS ; ++j) {
1970
rd[j]->last_collected_time.tv_sec =
1971
st->last_collected_time.tv_sec = st->last_updated.tv_sec = time_current;
1972
rd[j]->last_collected_time.tv_usec =
@@ -1767,7 +1975,7 @@ void generate_dbengine_dataset(unsigned history_seconds)
1975
for( ; time_current < time_present; ++time_current) {
1976
st->usec_since_last_update = USEC_PER_SEC;
1977
1770
- for (j = 0; j < DIMS; ++j) {
1978
+ for (j = 0; j < DSET_DIMS; ++j) {
1979
rrddim_set_by_pointer_fake_time(rd[j], (time_current + j) % 128, time_current);
1980
}
1981
rrdset_done(st);
database/README.md
+2
@@ -47,6 +47,8 @@ Currently Netdata supports 6 memory modes:
47
database. There is some amount of RAM dedicated to data caching and indexing and the rest of
48
the data reside compressed on disk. The number of history entries is not fixed in this case,
49
but depends on the configured disk space and the effective compression ratio of the data stored.
50
+ This is the **only mode** that supports changing the data collection update frequency
51
+ (`update_every`) **without losing** the previously stored metrics.
52
For more details see [here](engine/).
53
54
You can select the memory mode by editing `netdata.conf` and setting:
database/engine/README.md
+2
@@ -4,6 +4,8 @@ The Database Engine works like a traditional
4
database. There is some amount of RAM dedicated to data caching and indexing and the rest of
5
the data reside compressed on disk. The number of history entries is not fixed in this case,
6
but depends on the configured disk space and the effective compression ratio of the data stored.
7
+This is the **only mode** that supports changing the data collection update frequency
8
+(`update_every`) **without losing** the previously stored metrics.
9
10
## Files
11
database/engine/pagecache.c
+245
-39
@@ -419,6 +419,35 @@ static inline int is_point_in_time_in_page(struct rrdeng_page_descr *descr, usec
419
return (point_in_time >= descr->start_time && point_in_time <= descr->end_time);
420
}
421
422
+/* The caller must hold the page index lock */
423
+static inline struct rrdeng_page_descr *
424
+ find_first_page_in_time_range(struct pg_cache_page_index *page_index, usec_t start_time, usec_t end_time)
425
+{
426
+ struct rrdeng_page_descr *descr = NULL;
427
+ Pvoid_t *PValue;
428
+ Word_t Index;
429
+
430
+ Index = (Word_t)(start_time / USEC_PER_SEC);
431
+ PValue = JudyLLast(page_index->JudyL_array, &Index, PJE0);
432
+ if (likely(NULL != PValue)) {
433
+ descr = *PValue;
434
+ if (is_page_in_time_range(descr, start_time, end_time)) {
435
+ return descr;
436
+ }
437
+ }
438
+
439
+ Index = (Word_t)(start_time / USEC_PER_SEC);
440
+ PValue = JudyLFirst(page_index->JudyL_array, &Index, PJE0);
441
+ if (likely(NULL != PValue)) {
442
+ descr = *PValue;
443
+ if (is_page_in_time_range(descr, start_time, end_time)) {
444
+ return descr;
445
+ }
446
+ }
447
+
448
+ return NULL;
449
+}
450
+
451
/* Update metric oldest and latest timestamps efficiently when adding new values */
452
void pg_cache_add_new_metric_time(struct pg_cache_page_index *page_index, struct rrdeng_page_descr *descr)
453
{
@@ -510,70 +539,144 @@ void pg_cache_insert(struct rrdengine_instance *ctx, struct pg_cache_page_index
539
uv_rwlock_wrunlock(&pg_cache->pg_cache_rwlock);
540
}
541
513
-/*
514
- * Searches for a page and triggers disk I/O if necessary and possible.
542
+usec_t pg_cache_oldest_time_in_range(struct rrdengine_instance *ctx, uuid_t *id, usec_t start_time, usec_t end_time)
543
+{
544
+ struct page_cache *pg_cache = &ctx->pg_cache;
545
+ struct rrdeng_page_descr *descr = NULL;
546
+ Pvoid_t *PValue;
547
+ struct pg_cache_page_index *page_index;
548
+
549
+ uv_rwlock_rdlock(&pg_cache->metrics_index.lock);
550
+ PValue = JudyHSGet(pg_cache->metrics_index.JudyHS_array, id, sizeof(uuid_t));
551
+ if (likely(NULL != PValue)) {
552
+ page_index = *PValue;
553
+ }
554
+ uv_rwlock_rdunlock(&pg_cache->metrics_index.lock);
555
+ if (NULL == PValue) {
556
+ return INVALID_TIME;
557
+ }
558
+
559
+ uv_rwlock_rdlock(&page_index->lock);
560
+ descr = find_first_page_in_time_range(page_index, start_time, end_time);
561
+ if (NULL == descr) {
562
+ uv_rwlock_rdunlock(&page_index->lock);
563
+ return INVALID_TIME;
564
+ }
565
+ uv_rwlock_rdunlock(&page_index->lock);
566
+ return descr->start_time;
567
+}
568
+
569
+/**
570
+ * Return page information for the first page before point_in_time that satisfies the filter.
571
+ * @param ctx DB context
572
+ * @param page_index page index of a metric
573
+ * @param point_in_time the pages that are searched must be older than this timestamp
574
+ * @param filter decides if the page satisfies the caller's criteria
575
+ * @param page_info the result of the search is set in this pointer
576
+ */
577
+void pg_cache_get_filtered_info_prev(struct rrdengine_instance *ctx, struct pg_cache_page_index *page_index,
578
+ usec_t point_in_time, pg_cache_page_info_filter_t *filter,
579
+ struct rrdeng_page_info *page_info)
580
+{
581
+ struct page_cache *pg_cache = &ctx->pg_cache;
582
+ struct rrdeng_page_descr *descr = NULL;
583
+ Pvoid_t *PValue;
584
+ Word_t Index;
585
+
586
+ (void)pg_cache;
587
+ assert(NULL != page_index);
588
+
589
+ Index = (Word_t)(point_in_time / USEC_PER_SEC);
590
+ uv_rwlock_rdlock(&page_index->lock);
591
+ do {
592
+ PValue = JudyLPrev(page_index->JudyL_array, &Index, PJE0);
593
+ descr = unlikely(NULL == PValue) ? NULL : *PValue;
594
+ } while (descr != NULL && !filter(descr));
595
+ if (unlikely(NULL == descr)) {
596
+ page_info->page_length = 0;
597
+ page_info->start_time = INVALID_TIME;
598
+ page_info->end_time = INVALID_TIME;
599
+ } else {
600
+ page_info->page_length = descr->page_length;
601
+ page_info->start_time = descr->start_time;
602
+ page_info->end_time = descr->end_time;
603
+ }
604
+ uv_rwlock_rdunlock(&page_index->lock);
605
+}
606
+/**
607
+ * Searches for pages in a time range and triggers disk I/O if necessary and possible.
608
* Does not get a reference.
516
- * Returns page index pointer for given metric UUID.
609
+ * @param ctx DB context
610
+ * @param id UUID
611
+ * @param start_time inclusive starting time in usec
612
+ * @param end_time inclusive ending time in usec
613
+ * @param page_info_arrayp It allocates (*page_arrayp) and populates it with information of pages that overlap
614
+ * with the time range [start_time,end_time]. The caller must free (*page_info_arrayp) with freez().
615
+ * If page_info_arrayp is set to NULL nothing was allocated.
616
+ * @param ret_page_indexp Sets the page index pointer (*ret_page_indexp) for the given UUID.
617
+ * @return the number of pages that overlap with the time range [start_time,end_time].
618
*/
518
-struct pg_cache_page_index *
519
- pg_cache_preload(struct rrdengine_instance *ctx, uuid_t *id, usec_t start_time, usec_t end_time)
619
+unsigned pg_cache_preload(struct rrdengine_instance *ctx, uuid_t *id, usec_t start_time, usec_t end_time,
620
+ struct rrdeng_page_info **page_info_arrayp, struct pg_cache_page_index **ret_page_indexp)
621
{
622
struct page_cache *pg_cache = &ctx->pg_cache;
623
struct rrdeng_page_descr *descr = NULL, *preload_array[PAGE_CACHE_MAX_PRELOAD_PAGES];
624
struct page_cache_descr *pg_cache_descr = NULL;
524
- int i, j, k, count, found;
625
+ unsigned i, j, k, preload_count, count, page_info_array_max_size;
626
unsigned long flags;
627
Pvoid_t *PValue;
628
struct pg_cache_page_index *page_index;
629
Word_t Index;
630
uint8_t failed_to_reserve;
631
632
+ assert(NULL != ret_page_indexp);
633
+
634
uv_rwlock_rdlock(&pg_cache->metrics_index.lock);
635
PValue = JudyHSGet(pg_cache->metrics_index.JudyHS_array, id, sizeof(uuid_t));
636
if (likely(NULL != PValue)) {
534
- page_index = *PValue;
637
+ *ret_page_indexp = page_index = *PValue;
638
}
639
uv_rwlock_rdunlock(&pg_cache->metrics_index.lock);
640
if (NULL == PValue) {
641
debug(D_RRDENGINE, "%s: No page was found to attempt preload.", __func__);
539
- return NULL;
642
+ *ret_page_indexp = NULL;
643
+ return 0;
644
}
645
646
uv_rwlock_rdlock(&page_index->lock);
543
- /* Find first page in range */
544
- found = 0;
545
- Index = (Word_t)(start_time / USEC_PER_SEC);
546
- PValue = JudyLLast(page_index->JudyL_array, &Index, PJE0);
547
- if (likely(NULL != PValue)) {
548
- descr = *PValue;
549
- if (is_page_in_time_range(descr, start_time, end_time)) {
550
- found = 1;
551
- }
552
- }
553
- if (!found) {
554
- Index = (Word_t)(start_time / USEC_PER_SEC);
555
- PValue = JudyLFirst(page_index->JudyL_array, &Index, PJE0);
556
- if (likely(NULL != PValue)) {
557
- descr = *PValue;
558
- if (is_page_in_time_range(descr, start_time, end_time)) {
559
- found = 1;
560
- }
561
- }
562
- }
563
- if (!found) {
647
+ descr = find_first_page_in_time_range(page_index, start_time, end_time);
648
+ if (NULL == descr) {
649
uv_rwlock_rdunlock(&page_index->lock);
650
debug(D_RRDENGINE, "%s: No page was found to attempt preload.", __func__);
566
- return page_index;
651
+ *ret_page_indexp = NULL;
652
+ return 0;
653
+ } else {
654
+ Index = (Word_t)(descr->start_time / USEC_PER_SEC);
655
+ }
656
+ if (page_info_arrayp) {
657
+ page_info_array_max_size = PAGE_CACHE_MAX_PRELOAD_PAGES * sizeof(struct rrdeng_page_info);
658
+ *page_info_arrayp = mallocz(page_info_array_max_size);
659
}
660
569
- for (count = 0 ;
570
- descr != NULL && is_page_in_time_range(descr, start_time, end_time);
661
+ for (count = 0, preload_count = 0 ;
662
+ descr != NULL && is_page_in_time_range(descr, start_time, end_time) ;
663
PValue = JudyLNext(page_index->JudyL_array, &Index, PJE0),
664
descr = unlikely(NULL == PValue) ? NULL : *PValue) {
665
/* Iterate all pages in range */
666
667
if (unlikely(0 == descr->page_length))
668
continue;
669
+ if (page_info_arrayp) {
670
+ if (unlikely(count >= page_info_array_max_size / sizeof(struct rrdeng_page_info))) {
671
+ page_info_array_max_size += PAGE_CACHE_MAX_PRELOAD_PAGES * sizeof(struct rrdeng_page_info);
672
+ *page_info_arrayp = reallocz(*page_info_arrayp, page_info_array_max_size);
673
+ }
674
+ (*page_info_arrayp)[count].start_time = descr->start_time;
675
+ (*page_info_arrayp)[count].end_time = descr->end_time;
676
+ (*page_info_arrayp)[count].page_length = descr->page_length;
677
+ }
678
+ ++count;
679
+
680
rrdeng_page_descr_mutex_lock(ctx, descr);
681
pg_cache_descr = descr->pg_cache_descr;
682
flags = pg_cache_descr->flags;
@@ -586,8 +689,8 @@ struct pg_cache_page_index *
689
}
690
}
691
if (!(flags & RRD_PAGE_POPULATED) && pg_cache_try_get_unsafe(descr, 1)) {
589
- preload_array[count++] = descr;
590
- if (PAGE_CACHE_MAX_PRELOAD_PAGES == count) {
692
+ preload_array[preload_count++] = descr;
693
+ if (PAGE_CACHE_MAX_PRELOAD_PAGES == preload_count) {
694
rrdeng_page_descr_mutex_unlock(ctx, descr);
695
break;
696
}
@@ -598,7 +701,7 @@ struct pg_cache_page_index *
701
uv_rwlock_rdunlock(&page_index->lock);
702
703
failed_to_reserve = 0;
601
- for (i = 0 ; i < count && !failed_to_reserve ; ++i) {
704
+ for (i = 0 ; i < preload_count && !failed_to_reserve ; ++i) {
705
struct rrdeng_cmd cmd;
706
struct rrdeng_page_descr *next;
707
@@ -614,7 +717,7 @@ struct pg_cache_page_index *
717
cmd.read_extent.page_cache_descr[0] = descr;
718
/* don't use this page again */
719
preload_array[i] = NULL;
617
- for (j = 0, k = 1 ; j < count ; ++j) {
720
+ for (j = 0, k = 1 ; j < preload_count ; ++j) {
721
next = preload_array[j];
722
if (NULL == next) {
723
continue;
@@ -635,7 +738,7 @@ struct pg_cache_page_index *
738
}
739
if (failed_to_reserve) {
740
debug(D_RRDENGINE, "%s: Failed to reserve enough memory, canceling I/O.", __func__);
638
- for (i = 0 ; i < count ; ++i) {
741
+ for (i = 0 ; i < preload_count ; ++i) {
742
descr = preload_array[i];
743
if (NULL == descr) {
744
continue;
@@ -643,11 +746,15 @@ struct pg_cache_page_index *
746
pg_cache_put(ctx, descr);
747
}
748
}
646
- if (!count) {
749
+ if (!preload_count) {
750
/* no such page */
751
debug(D_RRDENGINE, "%s: No page was eligible to attempt preload.", __func__);
752
}
650
- return page_index;
753
+ if (unlikely(0 == count && page_info_arrayp)) {
754
+ freez(*page_info_arrayp);
755
+ *page_info_arrayp = NULL;
756
+ }
757
+ return count;
758
}
759
760
/*
@@ -757,6 +864,105 @@ struct rrdeng_page_descr *
864
return descr;
865
}
866
867
+/*
868
+ * Searches for the first page between start_time and end_time and gets a reference.
869
+ * start_time and end_time are inclusive.
870
+ * If index is NULL lookup by UUID (id).
871
+ */
872
+struct rrdeng_page_descr *
873
+pg_cache_lookup_next(struct rrdengine_instance *ctx, struct pg_cache_page_index *index, uuid_t *id,
874
+ usec_t start_time, usec_t end_time)
875
+{
876
+ struct page_cache *pg_cache = &ctx->pg_cache;
877
+ struct rrdeng_page_descr *descr = NULL;
878
+ struct page_cache_descr *pg_cache_descr = NULL;
879
+ unsigned long flags;
880
+ Pvoid_t *PValue;
881
+ struct pg_cache_page_index *page_index;
882
+ uint8_t page_not_in_cache;
883
+
884
+ if (unlikely(NULL == index)) {
885
+ uv_rwlock_rdlock(&pg_cache->metrics_index.lock);
886
+ PValue = JudyHSGet(pg_cache->metrics_index.JudyHS_array, id, sizeof(uuid_t));
887
+ if (likely(NULL != PValue)) {
888
+ page_index = *PValue;
889
+ }
890
+ uv_rwlock_rdunlock(&pg_cache->metrics_index.lock);
891
+ if (NULL == PValue) {
892
+ return NULL;
893
+ }
894
+ } else {
895
+ page_index = index;
896
+ }
897
+ pg_cache_reserve_pages(ctx, 1);
898
+
899
+ page_not_in_cache = 0;
900
+ uv_rwlock_rdlock(&page_index->lock);
901
+ while (1) {
902
+ descr = find_first_page_in_time_range(page_index, start_time, end_time);
903
+ if (NULL == descr || 0 == descr->page_length) {
904
+ /* non-empty page not found */
905
+ uv_rwlock_rdunlock(&page_index->lock);
906
+
907
+ pg_cache_release_pages(ctx, 1);
908
+ return NULL;
909
+ }
910
+ rrdeng_page_descr_mutex_lock(ctx, descr);
911
+ pg_cache_descr = descr->pg_cache_descr;
912
+ flags = pg_cache_descr->flags;
913
+ if ((flags & RRD_PAGE_POPULATED) && pg_cache_try_get_unsafe(descr, 0)) {
914
+ /* success */
915
+ rrdeng_page_descr_mutex_unlock(ctx, descr);
916
+ debug(D_RRDENGINE, "%s: Page was found in memory.", __func__);
917
+ break;
918
+ }
919
+ if (!(flags & RRD_PAGE_POPULATED) && pg_cache_try_get_unsafe(descr, 1)) {
920
+ struct rrdeng_cmd cmd;
921
+
922
+ uv_rwlock_rdunlock(&page_index->lock);
923
+
924
+ cmd.opcode = RRDENG_READ_PAGE;
925
+ cmd.read_page.page_cache_descr = descr;
926
+ rrdeng_enq_cmd(&ctx->worker_config, &cmd);
927
+
928
+ debug(D_RRDENGINE, "%s: Waiting for page to be asynchronously read from disk:", __func__);
929
+ if(unlikely(debug_flags & D_RRDENGINE))
930
+ print_page_cache_descr(descr);
931
+ while (!(pg_cache_descr->flags & RRD_PAGE_POPULATED)) {
932
+ pg_cache_wait_event_unsafe(descr);
933
+ }
934
+ /* success */
935
+ /* Downgrade exclusive reference to allow other readers */
936
+ pg_cache_descr->flags &= ~RRD_PAGE_LOCKED;
937
+ pg_cache_wake_up_waiters_unsafe(descr);
938
+ rrdeng_page_descr_mutex_unlock(ctx, descr);
939
+ rrd_stat_atomic_add(&ctx->stats.pg_cache_misses, 1);
940
+ return descr;
941
+ }
942
+ uv_rwlock_rdunlock(&page_index->lock);
943
+ debug(D_RRDENGINE, "%s: Waiting for page to be unlocked:", __func__);
944
+ if(unlikely(debug_flags & D_RRDENGINE))
945
+ print_page_cache_descr(descr);
946
+ if (!(flags & RRD_PAGE_POPULATED))
947
+ page_not_in_cache = 1;
948
+ pg_cache_wait_event_unsafe(descr);
949
+ rrdeng_page_descr_mutex_unlock(ctx, descr);
950
+
951
+ /* reset scan to find again */
952
+ uv_rwlock_rdlock(&page_index->lock);
953
+ }
954
+ uv_rwlock_rdunlock(&page_index->lock);
955
+
956
+ if (!(flags & RRD_PAGE_DIRTY))
957
+ pg_cache_replaceQ_set_hot(ctx, descr);
958
+ pg_cache_release_pages(ctx, 1);
959
+ if (page_not_in_cache)
960
+ rrd_stat_atomic_add(&ctx->stats.pg_cache_misses, 1);
961
+ else
962
+ rrd_stat_atomic_add(&ctx->stats.pg_cache_hits, 1);
963
+ return descr;
964
+}
965
+
966
struct pg_cache_page_index *create_page_index(uuid_t *id)
967
{
968
struct pg_cache_page_index *page_index;
database/engine/pagecache.h
+28
-5
@@ -48,9 +48,6 @@ struct page_cache_descr {
48
* number of descriptor users | DESTROY | LOCKED | ALLOCATED |
49
*/
50
struct rrdeng_page_descr {
51
- uint32_t page_length;
52
- usec_t start_time;
53
- usec_t end_time;
51
uuid_t *id; /* never changes */
52
struct extent_info *extent;
53
@@ -59,8 +56,25 @@ struct rrdeng_page_descr {
56
57
/* Compare-And-Swap target for page cache descriptor allocation algorithm */
58
volatile unsigned long pg_cache_descr_state;
59
+
60
+ /* page information */
61
+ usec_t start_time;
62
+ usec_t end_time;
63
+ uint32_t page_length;
64
};
65
66
+#define PAGE_INFO_SCRATCH_SZ (8)
67
+struct rrdeng_page_info {
68
+ uint8_t scratch[PAGE_INFO_SCRATCH_SZ]; /* scratch area to be used by page-cache users */
69
+
70
+ usec_t start_time;
71
+ usec_t end_time;
72
+ uint32_t page_length;
73
+};
74
+
75
+/* returns 1 for success, 0 for failure */
76
+typedef int pg_cache_page_info_filter_t(struct rrdeng_page_descr *);
77
+
78
#define PAGE_CACHE_MAX_PRELOAD_PAGES (256)
79
80
/* maps time ranges to pages */
@@ -149,11 +163,20 @@ extern void pg_cache_put(struct rrdengine_instance *ctx, struct rrdeng_page_desc
163
extern void pg_cache_insert(struct rrdengine_instance *ctx, struct pg_cache_page_index *index,
164
struct rrdeng_page_descr *descr);
165
extern void pg_cache_punch_hole(struct rrdengine_instance *ctx, struct rrdeng_page_descr *descr, uint8_t remove_dirty);
152
-extern struct pg_cache_page_index *
153
- pg_cache_preload(struct rrdengine_instance *ctx, uuid_t *id, usec_t start_time, usec_t end_time);
166
+extern usec_t pg_cache_oldest_time_in_range(struct rrdengine_instance *ctx, uuid_t *id,
167
+ usec_t start_time, usec_t end_time);
168
+extern void pg_cache_get_filtered_info_prev(struct rrdengine_instance *ctx, struct pg_cache_page_index *page_index,
169
+ usec_t point_in_time, pg_cache_page_info_filter_t *filter,
170
+ struct rrdeng_page_info *page_info);
171
+extern unsigned
172
+ pg_cache_preload(struct rrdengine_instance *ctx, uuid_t *id, usec_t start_time, usec_t end_time,
173
+ struct rrdeng_page_info **page_info_arrayp, struct pg_cache_page_index **ret_page_indexp);
174
extern struct rrdeng_page_descr *
175
pg_cache_lookup(struct rrdengine_instance *ctx, struct pg_cache_page_index *index, uuid_t *id,
176
usec_t point_in_time);
177
+extern struct rrdeng_page_descr *
178
+ pg_cache_lookup_next(struct rrdengine_instance *ctx, struct pg_cache_page_index *index, uuid_t *id,
179
+ usec_t start_time, usec_t end_time);
180
extern struct pg_cache_page_index *create_page_index(uuid_t *id);
181
extern void init_page_cache(struct rrdengine_instance *ctx);
182
extern void free_page_cache(struct rrdengine_instance *ctx);
database/engine/rrdengine.c
+3
@@ -24,6 +24,9 @@ void sanity_check(void)
24
25
/* page count must fit in 8 bits */
26
BUILD_BUG_ON(MAX_PAGES_PER_EXTENT > 255);
27
+
28
+ /* page info scratch space must be able to hold 2 32-bit integers */
29
+ BUILD_BUG_ON(sizeof(((struct rrdeng_page_info *)0)->scratch) < 2 * sizeof(uint32_t));
30
}
31
32
void read_extent_cb(uv_fs_t* req)
database/engine/rrdengineapi.c
+264
-34
@@ -218,6 +218,208 @@ void rrdeng_store_metric_finalize(RRDDIM *rd)
218
}
219
}
220
221
+/* Returns 1 if the data collection interval is well defined, 0 otherwise */
222
+static int metrics_with_known_interval(struct rrdeng_page_descr *descr)
223
+{
224
+ unsigned page_entries;
225
+
226
+ if (unlikely(INVALID_TIME == descr->start_time || INVALID_TIME == descr->end_time))
227
+ return 0;
228
+ page_entries = descr->page_length / sizeof(storage_number);
229
+ if (likely(page_entries > 1)) {
230
+ return 1;
231
+ }
232
+ return 0;
233
+}
234
+
235
+static inline uint32_t *pginfo_to_dt(struct rrdeng_page_info *page_info)
236
+{
237
+ return (uint32_t *)&page_info->scratch[0];
238
+}
239
+
240
+static inline uint32_t *pginfo_to_points(struct rrdeng_page_info *page_info)
241
+{
242
+ return (uint32_t *)&page_info->scratch[sizeof(uint32_t)];
243
+}
244
+
245
+/**
246
+ * Calculates the regions of different data collection intervals in a netdata chart in the time range
247
+ * [start_time,end_time]. This call takes the netdata chart read lock.
248
+ * @param st the netdata chart whose data collection interval boundaries are calculated.
249
+ * @param start_time inclusive starting time in usec
250
+ * @param end_time inclusive ending time in usec
251
+ * @param region_info_arrayp It allocates (*region_info_arrayp) and populates it with information of regions of a
252
+ * reference dimension that that have different data collection intervals and overlap with the time range
253
+ * [start_time,end_time]. The caller must free (*region_info_arrayp) with freez(). If region_info_arrayp is set
254
+ * to NULL nothing was allocated.
255
+ * @param max_intervalp is derefenced and set to be the largest data collection interval of all regions.
256
+ * @return number of regions with different data collection intervals.
257
+ */
258
+unsigned rrdeng_variable_step_boundaries(RRDSET *st, time_t start_time, time_t end_time,
259
+ struct rrdeng_region_info **region_info_arrayp, unsigned *max_intervalp)
260
+{
261
+ struct pg_cache_page_index *page_index;
262
+ struct rrdengine_instance *ctx;
263
+ unsigned pages_nr;
264
+ RRDDIM *rd_iter, *rd;
265
+ struct rrdeng_page_info *page_info_array, *curr, *prev, *old_prev;
266
+ unsigned i, j, page_entries, region_points, page_points, regions, max_interval;
267
+ time_t now;
268
+ usec_t dt, current_position_time, max_time = 0, min_time, curr_time, first_valid_time_in_page;
269
+ struct rrdeng_region_info *region_info_array;
270
+ uint8_t is_first_region_initialized;
271
+
272
+ ctx = st->rrdhost->rrdeng_ctx;
273
+ regions = 1;
274
+ *max_intervalp = max_interval = 0;
275
+ region_info_array = NULL;
276
+ *region_info_arrayp = NULL;
277
+ page_info_array = NULL;
278
+
279
+ rrdset_rdlock(st);
280
+ for(rd_iter = st->dimensions, rd = NULL, min_time = (usec_t)-1 ; rd_iter ; rd_iter = rd_iter->next) {
281
+ /*
282
+ * Choose oldest dimension as reference. This is not equivalent to the union of all dimensions
283
+ * but it is a best effort approximation with a bias towards older metrics in a chart. It
284
+ * matches netdata behaviour in the sense that dimensions are generally aligned in a chart
285
+ * and older dimensions contain more information about the time range. It does not work well
286
+ * for metrics that have recently stopped being collected.
287
+ */
288
+ curr_time = pg_cache_oldest_time_in_range(ctx, rd_iter->state->rrdeng_uuid,
289
+ start_time * USEC_PER_SEC, end_time * USEC_PER_SEC);
290
+ if (INVALID_TIME != curr_time && curr_time < min_time) {
291
+ rd = rd_iter;
292
+ min_time = curr_time;
293
+ }
294
+ }
295
+ rrdset_unlock(st);
296
+ if (NULL == rd) {
297
+ return 1;
298
+ }
299
+ pages_nr = pg_cache_preload(ctx, rd->state->rrdeng_uuid, start_time * USEC_PER_SEC, end_time * USEC_PER_SEC,
300
+ &page_info_array, &page_index);
301
+ if (pages_nr) {
302
+ /* conservative allocation, will reduce the size later if necessary */
303
+ region_info_array = mallocz(sizeof(*region_info_array) * pages_nr);
304
+ }
305
+ is_first_region_initialized = 0;
306
+ region_points = 0;
307
+
308
+ /* pages loop */
309
+ for (i = 0, curr = NULL, prev = NULL ; i < pages_nr ; ++i) {
310
+ old_prev = prev;
311
+ prev = curr;
312
+ curr = &page_info_array[i];
313
+ *pginfo_to_points(curr) = 0; /* initialize to invalid page */
314
+ *pginfo_to_dt(curr) = 0; /* no known data collection interval yet */
315
+ if (unlikely(INVALID_TIME == curr->start_time || INVALID_TIME == curr->end_time)) {
316
+ info("Ignoring page with invalid timestamp.");
317
+ prev = old_prev;
318
+ continue;
319
+ }
320
+ page_entries = curr->page_length / sizeof(storage_number);
321
+ assert(0 != page_entries);
322
+ if (likely(1 != page_entries)) {
323
+ dt = (curr->end_time - curr->start_time) / (page_entries - 1);
324
+ *pginfo_to_dt(curr) = ROUND_USEC_TO_SEC(dt);
325
+ if (unlikely(0 == *pginfo_to_dt(curr)))
326
+ *pginfo_to_dt(curr) = 1;
327
+ } else {
328
+ dt = 0;
329
+ }
330
+ for (j = 0, page_points = 0 ; j < page_entries ; ++j) {
331
+ uint8_t is_metric_out_of_order, is_metric_earlier_than_range;
332
+
333
+ is_metric_earlier_than_range = 0;
334
+ is_metric_out_of_order = 0;
335
+
336
+ current_position_time = curr->start_time + j * dt;
337
+ now = current_position_time / USEC_PER_SEC;
338
+ if (now > end_time) { /* there will be no more pages in the time range */
339
+ break;
340
+ }
341
+ if (now < start_time)
342
+ is_metric_earlier_than_range = 1;
343
+ if (unlikely(current_position_time < max_time)) /* just went back in time */
344
+ is_metric_out_of_order = 1;
345
+ if (is_metric_earlier_than_range || unlikely(is_metric_out_of_order)) {
346
+ if (unlikely(is_metric_out_of_order))
347
+ info("Ignoring metric with out of order timestamp.");
348
+ continue; /* next entry */
349
+ }
350
+ /* here is a valid metric */
351
+ ++page_points;
352
+ region_info_array[regions - 1].points = ++region_points;
353
+ max_time = current_position_time;
354
+ if (1 == page_points)
355
+ first_valid_time_in_page = current_position_time;
356
+ if (unlikely(!is_first_region_initialized)) {
357
+ assert(1 == regions);
358
+ /* this is the first region */
359
+ region_info_array[0].start_time = current_position_time;
360
+ is_first_region_initialized = 1;
361
+ }
362
+ }
363
+ *pginfo_to_points(curr) = page_points;
364
+ if (0 == page_points) {
365
+ prev = old_prev;
366
+ continue;
367
+ }
368
+
369
+ if (unlikely(0 == dt)) { /* unknown data collection interval */
370
+ assert(1 == page_points);
371
+
372
+ if (likely(NULL != prev)) { /* get interval from previous page */
373
+ *pginfo_to_dt(curr) = *pginfo_to_dt(prev);
374
+ } else { /* there is no previous page in the query */
375
+ struct rrdeng_page_info db_page_info;
376
+
377
+ /* go to database */
378
+ pg_cache_get_filtered_info_prev(ctx, page_index, curr->start_time,
379
+ metrics_with_known_interval, &db_page_info);
380
+ if (unlikely(db_page_info.start_time == INVALID_TIME || db_page_info.end_time == INVALID_TIME ||
381
+ 0 == db_page_info.page_length)) { /* nothing in the database, default to update_every */
382
+ *pginfo_to_dt(curr) = rd->update_every;
383
+ } else {
384
+ unsigned db_entries;
385
+ usec_t db_dt;
386
+
387
+ db_entries = db_page_info.page_length / sizeof(storage_number);
388
+ db_dt = (db_page_info.end_time - db_page_info.start_time) / (db_entries - 1);
389
+ *pginfo_to_dt(curr) = ROUND_USEC_TO_SEC(db_dt);
390
+ if (unlikely(0 == *pginfo_to_dt(curr)))
391
+ *pginfo_to_dt(curr) = 1;
392
+
393
+ }
394
+ }
395
+ }
396
+ if (likely(prev) && unlikely(*pginfo_to_dt(curr) != *pginfo_to_dt(prev))) {
397
+ info("Data collection interval change detected in query: %"PRIu32" -> %"PRIu32,
398
+ *pginfo_to_dt(prev), *pginfo_to_dt(curr));
399
+ region_info_array[regions++ - 1].points -= page_points;
400
+ region_info_array[regions - 1].points = region_points = page_points;
401
+ region_info_array[regions - 1].start_time = first_valid_time_in_page;
402
+ }
403
+ if (*pginfo_to_dt(curr) > max_interval)
404
+ max_interval = *pginfo_to_dt(curr);
405
+ region_info_array[regions - 1].update_every = *pginfo_to_dt(curr);
406
+ }
407
+ if (page_info_array)
408
+ freez(page_info_array);
409
+ if (region_info_array) {
410
+ if (likely(is_first_region_initialized)) {
411
+ /* free unnecessary memory */
412
+ region_info_array = reallocz(region_info_array, sizeof(*region_info_array) * regions);
413
+ *region_info_arrayp = region_info_array;
414
+ *max_intervalp = max_interval;
415
+ } else {
416
+ /* empty result */
417
+ freez(region_info_array);
418
+ }
419
+ }
420
+ return regions;
421
+}
422
+
423
/*
424
* Gets a handle for loading metrics from the database.
425
* The handle must be released with rrdeng_load_metric_final().
@@ -226,80 +428,108 @@ void rrdeng_load_metric_init(RRDDIM *rd, struct rrddim_query_handle *rrdimm_hand
428
{
429
struct rrdeng_query_handle *handle;
430
struct rrdengine_instance *ctx;
431
+ unsigned pages_nr;
432
433
ctx = rd->rrdset->rrdhost->rrdeng_ctx;
434
rrdimm_handle->start_time = start_time;
435
rrdimm_handle->end_time = end_time;
436
handle = &rrdimm_handle->rrdeng;
437
+ handle->next_page_time = start_time;
438
handle->now = start_time;
235
- handle->dt = rd->rrdset->update_every;
439
+ handle->position = 0;
440
handle->ctx = ctx;
441
handle->descr = NULL;
238
- handle->page_index = pg_cache_preload(ctx, rd->state->rrdeng_uuid,
239
- start_time * USEC_PER_SEC, end_time * USEC_PER_SEC);
442
+ pages_nr = pg_cache_preload(ctx, rd->state->rrdeng_uuid, start_time * USEC_PER_SEC, end_time * USEC_PER_SEC,
443
+ NULL, &handle->page_index);
444
+ if (unlikely(NULL == handle->page_index || 0 == pages_nr))
445
+ /* there are no metrics to load */
446
+ handle->next_page_time = INVALID_TIME;
447
}
448
242
-storage_number rrdeng_load_metric_next(struct rrddim_query_handle *rrdimm_handle)
449
+/* Returns the metric and sets its timestamp into current_time */
450
+storage_number rrdeng_load_metric_next(struct rrddim_query_handle *rrdimm_handle, time_t *current_time)
451
{
452
struct rrdeng_query_handle *handle;
453
struct rrdengine_instance *ctx;
454
struct rrdeng_page_descr *descr;
455
storage_number *page, ret;
248
- unsigned position;
249
- usec_t point_in_time;
456
+ unsigned position, entries;
457
+ usec_t next_page_time, current_position_time;
458
459
handle = &rrdimm_handle->rrdeng;
252
- if (unlikely(INVALID_TIME == handle->now)) {
460
+ if (unlikely(INVALID_TIME == handle->next_page_time)) {
461
return SN_EMPTY_SLOT;
462
}
463
ctx = handle->ctx;
256
- point_in_time = handle->now * USEC_PER_SEC;
257
- descr = handle->descr;
258
-
259
- if (unlikely(NULL == handle->page_index)) {
260
- ret = SN_EMPTY_SLOT;
261
- goto out;
464
+ if (unlikely(NULL == (descr = handle->descr))) {
465
+ /* it's the first call */
466
+ next_page_time = handle->next_page_time * USEC_PER_SEC;
467
}
468
+ position = handle->position + 1;
469
+
470
if (unlikely(NULL == descr ||
264
- point_in_time < descr->start_time ||
265
- point_in_time > descr->end_time)) {
471
+ position >= (descr->page_length / sizeof(storage_number)))) {
472
+ /* We need to get a new page */
473
if (descr) {
474
+ /* Drop old page's reference */
475
+ handle->next_page_time = (descr->end_time / USEC_PER_SEC) + 1;
476
+ if (unlikely(handle->next_page_time > rrdimm_handle->end_time)) {
477
+ goto no_more_metrics;
478
+ }
479
+ next_page_time = handle->next_page_time * USEC_PER_SEC;
480
#ifdef NETDATA_INTERNAL_CHECKS
481
rrd_stat_atomic_add(&ctx->stats.metric_API_consumers, -1);
482
#endif
483
pg_cache_put(ctx, descr);
484
handle->descr = NULL;
485
}
273
- descr = pg_cache_lookup(ctx, handle->page_index, &handle->page_index->id, point_in_time);
486
+ descr = pg_cache_lookup_next(ctx, handle->page_index, &handle->page_index->id,
487
+ next_page_time, rrdimm_handle->end_time * USEC_PER_SEC);
488
if (NULL == descr) {
275
- ret = SN_EMPTY_SLOT;
276
- goto out;
489
+ goto no_more_metrics;
490
}
491
#ifdef NETDATA_INTERNAL_CHECKS
492
rrd_stat_atomic_add(&ctx->stats.metric_API_consumers, 1);
493
#endif
494
handle->descr = descr;
282
- }
283
- if (unlikely(INVALID_TIME == descr->start_time ||
284
- INVALID_TIME == descr->end_time)) {
285
- ret = SN_EMPTY_SLOT;
286
- goto out;
495
+ if (unlikely(INVALID_TIME == descr->start_time ||
496
+ INVALID_TIME == descr->end_time)) {
497
+ goto no_more_metrics;
498
+ }
499
+ if (unlikely(descr->start_time != descr->end_time && next_page_time > descr->start_time)) {
500
+ /* we're in the middle of the page somewhere */
501
+ entries = descr->page_length / sizeof(storage_number);
502
+ position = ((uint64_t)(next_page_time - descr->start_time)) * entries /
503
+ (descr->end_time - descr->start_time + 1);
504
+ } else {
505
+ position = 0;
506
+ }
507
}
508
page = descr->pg_cache_descr->page;
289
- if (unlikely(descr->start_time == descr->end_time)) {
290
- ret = page[0];
291
- goto out;
292
- }
293
- position = ((uint64_t)(point_in_time - descr->start_time)) * (descr->page_length / sizeof(storage_number)) /
294
- (descr->end_time - descr->start_time + 1);
509
ret = page[position];
510
+ entries = descr->page_length / sizeof(storage_number);
511
+ if (entries > 1) {
512
+ usec_t dt;
513
297
-out:
298
- handle->now += handle->dt;
299
- if (unlikely(handle->now > rrdimm_handle->end_time)) {
300
- handle->now = INVALID_TIME;
514
+ dt = (descr->end_time - descr->start_time) / (entries - 1);
515
+ current_position_time = descr->start_time + position * dt;
516
+ } else {
517
+ current_position_time = descr->start_time;
518
}
519
+ handle->position = position;
520
+ handle->now = current_position_time / USEC_PER_SEC;
521
+/* assert(handle->now >= rrdimm_handle->start_time && handle->now <= rrdimm_handle->end_time);
522
+ The above assertion is an approximation and needs to take update_every into account */
523
+ if (unlikely(handle->now >= rrdimm_handle->end_time)) {
524
+ /* next calls will not load any more metrics */
525
+ handle->next_page_time = INVALID_TIME;
526
+ }
527
+ *current_time = handle->now;
528
return ret;
529
+
530
+no_more_metrics:
531
+ handle->next_page_time = INVALID_TIME;
532
+ return SN_EMPTY_SLOT;
533
}
534
535
int rrdeng_load_metric_is_finished(struct rrddim_query_handle *rrdimm_handle)
@@ -307,7 +537,7 @@ int rrdeng_load_metric_is_finished(struct rrddim_query_handle *rrdimm_handle)
537
struct rrdeng_query_handle *handle;
538
539
handle = &rrdimm_handle->rrdeng;
310
- return (INVALID_TIME == handle->now);
540
+ return (INVALID_TIME == handle->next_page_time);
541
}
542
543
/*
database/engine/rrdengineapi.h
+10
-1
@@ -15,6 +15,12 @@
15
extern int default_rrdeng_page_cache_mb;
16
extern int default_rrdeng_disk_quota_mb;
17
18
+struct rrdeng_region_info {
19
+ time_t start_time;
20
+ int update_every;
21
+ unsigned points;
22
+};
23
+
24
extern void *rrdeng_create_page(struct rrdengine_instance *ctx, uuid_t *id, struct rrdeng_page_descr **ret_descr);
25
extern void rrdeng_commit_page(struct rrdengine_instance *ctx, struct rrdeng_page_descr *descr,
26
Word_t page_correlation_id);
@@ -25,9 +31,12 @@ extern void rrdeng_store_metric_init(RRDDIM *rd);
31
extern void rrdeng_store_metric_flush_current_page(RRDDIM *rd);
32
extern void rrdeng_store_metric_next(RRDDIM *rd, usec_t point_in_time, storage_number number);
33
extern void rrdeng_store_metric_finalize(RRDDIM *rd);
34
+extern unsigned
35
+ rrdeng_variable_step_boundaries(RRDSET *st, time_t start_time, time_t end_time,
36
+ struct rrdeng_region_info **region_info_arrayp, unsigned *max_intervalp);
37
extern void rrdeng_load_metric_init(RRDDIM *rd, struct rrddim_query_handle *rrdimm_handle,
38
time_t start_time, time_t end_time);
30
-extern storage_number rrdeng_load_metric_next(struct rrddim_query_handle *rrdimm_handle);
39
+extern storage_number rrdeng_load_metric_next(struct rrddim_query_handle *rrdimm_handle, time_t *current_time);
40
extern int rrdeng_load_metric_is_finished(struct rrddim_query_handle *rrdimm_handle);
41
extern void rrdeng_load_metric_finalize(struct rrddim_query_handle *rrdimm_handle);
42
extern time_t rrdeng_metric_latest_time(RRDDIM *rd);
database/engine/rrdenginelib.h
+2
@@ -23,6 +23,8 @@ struct rrdeng_page_descr;
23
#define ALIGN_BYTES_FLOOR(x) (((x) / RRDENG_BLOCK_SIZE) * RRDENG_BLOCK_SIZE)
24
#define ALIGN_BYTES_CEILING(x) ((((x) + RRDENG_BLOCK_SIZE - 1) / RRDENG_BLOCK_SIZE) * RRDENG_BLOCK_SIZE)
25
26
+#define ROUND_USEC_TO_SEC(x) (((x) + USEC_PER_SEC / 2 - 1) / USEC_PER_SEC)
27
+
28
typedef uintptr_t rrdeng_stats_t;
29
30
#ifdef __ATOMIC_RELAXED
database/rrd.h
+4
-3
@@ -273,8 +273,9 @@ struct rrddim_query_handle {
273
struct rrdeng_page_descr *descr;
274
struct rrdengine_instance *ctx;
275
struct pg_cache_page_index *page_index;
276
- time_t now; //TODO: remove now to implement next point iteration
277
- time_t dt; //TODO: remove dt to implement next point iteration
276
+ time_t next_page_time;
277
+ time_t now;
278
+ unsigned position;
279
} rrdeng; // state the database engine uses
280
#endif
281
};
@@ -307,7 +308,7 @@ struct rrddim_volatile {
308
void (*init)(RRDDIM *rd, struct rrddim_query_handle *handle, time_t start_time, time_t end_time);
309
310
// run this to load each metric number from the database
310
- storage_number (*next_metric)(struct rrddim_query_handle *handle);
311
+ storage_number (*next_metric)(struct rrddim_query_handle *handle, time_t *current_time);
312
313
// run this to test if the series of next_metric() database queries is finished
314
int (*is_finished)(struct rrddim_query_handle *handle);
database/rrddim.c
+2
-1
@@ -118,11 +118,12 @@ static void rrddim_query_init(RRDDIM *rd, struct rrddim_query_handle *handle, ti
118
handle->slotted.finished = 0;
119
}
120
121
-static storage_number rrddim_query_next_metric(struct rrddim_query_handle *handle) {
121
+static storage_number rrddim_query_next_metric(struct rrddim_query_handle *handle, time_t *current_time) {
122
RRDDIM *rd = handle->rd;
123
long entries = rd->rrdset->entries;
124
long slot = handle->slotted.slot;
125
126
+ (void)current_time;
127
if (unlikely(handle->slotted.slot == handle->slotted.last_slot))
128
handle->slotted.finished = 1;
129
storage_number n = rd->values[slot++];
web/api/queries/average/average.c
+6
-3
@@ -46,9 +46,12 @@ calculated_number grouping_flush_average(RRDR *r, RRDR_VALUE_FLAGS *rrdr_value_
46
*rrdr_value_options_ptr |= RRDR_VALUE_EMPTY;
47
}
48
else {
49
- if(unlikely(r->internal.resampling_group != 1))
50
- value = g->sum / r->internal.resampling_divisor;
51
- else
49
+ if(unlikely(r->internal.resampling_group != 1)) {
50
+ if (unlikely(r->result_options & RRDR_RESULT_OPTION_VARIABLE_STEP))
51
+ value = g->sum / g->count / r->internal.resampling_divisor;
52
+ else
53
+ value = g->sum / r->internal.resampling_divisor;
54
+ } else
55
value = g->sum / g->count;
56
}
57
web/api/queries/query.c
+746
-130
@@ -376,7 +376,7 @@ static inline void rrdr_done(RRDR *r, long rrdr_line) {
376
// ----------------------------------------------------------------------------
377
// fill RRDR for a single dimension
378
379
-static inline void do_dimension(
379
+static inline void do_dimension_variablestep(
380
RRDR *r
381
, long points_wanted
382
, RRDDIM *rd
@@ -384,16 +384,16 @@ static inline void do_dimension(
384
, time_t after_wanted
385
, time_t before_wanted
386
){
387
- RRDSET *st = r->st;
387
+// RRDSET *st = r->st;
388
389
time_t
390
now = after_wanted,
391
- dt = st->update_every,
391
+ dt = r->update_every,
392
max_date = 0,
393
min_date = 0;
394
395
long
396
- group_size = r->group,
396
+// group_size = r->group,
397
points_added = 0,
398
values_in_group = 0,
399
values_in_group_non_zero = 0,
@@ -403,102 +403,240 @@ static inline void do_dimension(
403
group_value_flags = RRDR_VALUE_NOTHING;
404
405
struct rrddim_query_handle handle;
406
- uint8_t initialized_query;
406
407
calculated_number min = r->min, max = r->max;
408
size_t db_points_read = 0;
409
+ time_t db_now = now;
410
+ storage_number n_curr, n_prev = SN_EMPTY_SLOT;
411
+ calculated_number value;
412
+
413
+ for(rd->state->query_ops.init(rd, &handle, now, before_wanted) ; points_added < points_wanted ; now += dt) {
414
+ // make sure we return data in the proper time range
415
+ if (unlikely(now > before_wanted)) {
416
+#ifdef NETDATA_INTERNAL_CHECKS
417
+ r->internal.log = "stopped, because attempted to access the db after 'wanted before'";
418
+#endif
419
+ break;
420
+ }
421
+ if (unlikely(now < after_wanted)) {
422
+#ifdef NETDATA_INTERNAL_CHECKS
423
+ r->internal.log = "skipped, because attempted to access the db before 'wanted after'";
424
+#endif
425
+ continue;
426
+ }
427
+
428
+ while (now >= db_now && (!rd->state->query_ops.is_finished(&handle) ||
429
+ does_storage_number_exist(n_prev))) {
430
+ value = NAN;
431
+ if (does_storage_number_exist(n_prev)) {
432
+ // use the previously read database value
433
+ n_curr = n_prev;
434
+ } else {
435
+ // read the value from the database
436
+ n_curr = rd->state->query_ops.next_metric(&handle, &db_now);
437
+ }
438
+ n_prev = SN_EMPTY_SLOT;
439
+ // db_now has a different value than above
440
+ if (likely(now >= db_now)) {
441
+ if (likely(does_storage_number_exist(n_curr))) {
442
+ value = unpack_storage_number(n_curr);
443
+ if (likely(value != 0.0))
444
+ values_in_group_non_zero++;
445
+
446
+ if (unlikely(did_storage_number_reset(n_curr)))
447
+ group_value_flags |= RRDR_VALUE_RESET;
448
+ }
449
+ } else {
450
+ // We must postpone processing the value and fill the result with gaps instead
451
+ if (likely(does_storage_number_exist(n_curr))) {
452
+ n_prev = n_curr;
453
+ }
454
+ }
455
+ // add this value to grouping
456
+ r->internal.grouping_add(r, value);
457
+ values_in_group++;
458
+ db_points_read++;
459
+ }
460
+
461
+ if (0 == values_in_group) {
462
+ // add NAN to grouping
463
+ r->internal.grouping_add(r, NAN);
464
+ }
465
+
466
+ rrdr_line = rrdr_line_init(r, now, rrdr_line);
467
+
468
+ if(unlikely(!min_date)) min_date = now;
469
+ max_date = now;
470
+
471
+ // find the place to store our values
472
+ RRDR_VALUE_FLAGS *rrdr_value_options_ptr = &r->o[rrdr_line * r->d + dim_id_in_rrdr];
473
+
474
+ // update the dimension options
475
+ if(likely(values_in_group_non_zero))
476
+ r->od[dim_id_in_rrdr] |= RRDR_DIMENSION_NONZERO;
477
+
478
+ // store the specific point options
479
+ *rrdr_value_options_ptr = group_value_flags;
480
+
481
+ // store the value
482
+ value = r->internal.grouping_flush(r, rrdr_value_options_ptr);
483
+ r->v[rrdr_line * r->d + dim_id_in_rrdr] = value;
484
+
485
+ if(likely(points_added || dim_id_in_rrdr)) {
486
+ // find the min/max across all dimensions
487
+
488
+ if(unlikely(value < min)) min = value;
489
+ if(unlikely(value > max)) max = value;
490
+
491
+ }
492
+ else {
493
+ // runs only when dim_id_in_rrdr == 0 && points_added == 0
494
+ // so, on the first point added for the query.
495
+ min = max = value;
496
+ }
497
+
498
+ points_added++;
499
+ values_in_group = 0;
500
+ group_value_flags = RRDR_VALUE_NOTHING;
501
+ values_in_group_non_zero = 0;
502
+ }
503
+ rd->state->query_ops.finalize(&handle);
504
+
505
+ r->internal.db_points_read += db_points_read;
506
+ r->internal.result_points_generated += points_added;
507
+
508
+ r->min = min;
509
+ r->max = max;
510
+ r->before = max_date;
511
+ r->after = min_date - (r->group - 1) * dt;
512
+ rrdr_done(r, rrdr_line);
513
+
514
+ #ifdef NETDATA_INTERNAL_CHECKS
515
+ if(unlikely(r->rows != points_added))
516
+ error("INTERNAL ERROR: %s.%s added %zu rows, but RRDR says I added %zu.", r->st->name, rd->name, (size_t)points_added, (size_t)r->rows);
517
+ #endif
518
+}
519
411
- for(initialized_query = 0 ; points_added < points_wanted ; now += dt) {
520
+static inline void do_dimension_fixedstep(
521
+ RRDR *r
522
+ , long points_wanted
523
+ , RRDDIM *rd
524
+ , long dim_id_in_rrdr
525
+ , time_t after_wanted
526
+ , time_t before_wanted
527
+){
528
+ RRDSET *st = r->st;
529
+
530
+ time_t
531
+ now = after_wanted,
532
+ dt = r->update_every / r->group, /* usually is st->update_every */
533
+ max_date = 0,
534
+ min_date = 0;
535
536
+ long
537
+ group_size = r->group,
538
+ points_added = 0,
539
+ values_in_group = 0,
540
+ values_in_group_non_zero = 0,
541
+ rrdr_line = -1;
542
+
543
+ RRDR_VALUE_FLAGS
544
+ group_value_flags = RRDR_VALUE_NOTHING;
545
+
546
+ struct rrddim_query_handle handle;
547
+
548
+ calculated_number min = r->min, max = r->max;
549
+ size_t db_points_read = 0;
550
+ time_t db_now = now;
551
+
552
+ for(rd->state->query_ops.init(rd, &handle, now, before_wanted) ; points_added < points_wanted ; now += dt) {
553
// make sure we return data in the proper time range
554
if(unlikely(now > before_wanted)) {
415
- #ifdef NETDATA_INTERNAL_CHECKS
555
+#ifdef NETDATA_INTERNAL_CHECKS
556
r->internal.log = "stopped, because attempted to access the db after 'wanted before'";
417
- #endif
557
+#endif
558
break;
559
}
560
if(unlikely(now < after_wanted)) {
421
- #ifdef NETDATA_INTERNAL_CHECKS
561
+#ifdef NETDATA_INTERNAL_CHECKS
562
r->internal.log = "skipped, because attempted to access the db before 'wanted after'";
423
- #endif
563
+#endif
564
continue;
565
}
426
-
427
- if (unlikely(!initialized_query)) {
428
- rd->state->query_ops.init(rd, &handle, now, before_wanted);
429
- initialized_query = 1;
430
- }
566
// read the value from the database
567
//storage_number n = rd->values[slot];
568
#ifdef NETDATA_INTERNAL_CHECKS
434
- if (rd->rrd_memory_mode == RRD_MEMORY_MODE_DBENGINE) {
435
-#ifdef ENABLE_DBENGINE
436
- if (now != handle.rrdeng.now)
437
- error("INTERNAL CHECK: Unaligned query for %s, database time: %ld, expected time: %ld", rd->id, (long)handle.rrdeng.now, (long)now);
438
-#endif
439
- } else if (rrdset_time2slot(st, now) != (long unsigned)handle.slotted.slot) {
569
+ if ((rd->rrd_memory_mode != RRD_MEMORY_MODE_DBENGINE) &&
570
+ (rrdset_time2slot(st, now) != (long unsigned)handle.slotted.slot)) {
571
error("INTERNAL CHECK: Unaligned query for %s, database slot: %lu, expected slot: %lu", rd->id, (long unsigned)handle.slotted.slot, rrdset_time2slot(st, now));
572
}
573
#endif
443
- storage_number n = rd->state->query_ops.next_metric(&handle);
444
- calculated_number value = NAN;
445
- if(likely(does_storage_number_exist(n))) {
574
+ db_now = now; // this is needed to set db_now in case the next_metric implementation does not set it
575
+ storage_number n = rd->state->query_ops.next_metric(&handle, &db_now);
576
+ for ( ; now <= db_now ; now += dt) {
577
+ calculated_number value = NAN;
578
+ if(likely(now >= db_now && does_storage_number_exist(n))) {
579
+#if defined(NETDATA_INTERNAL_CHECKS) && defined(ENABLE_DBENGINE)
580
+ if ((rd->rrd_memory_mode == RRD_MEMORY_MODE_DBENGINE) && (now != handle.rrdeng.now)) {
581
+ error("INTERNAL CHECK: Unaligned query for %s, database time: %ld, expected time: %ld", rd->id, (long)handle.rrdeng.now, (long)now);
582
+ }
583
+#endif
584
+ value = unpack_storage_number(n);
585
+ if(likely(value != 0.0))
586
+ values_in_group_non_zero++;
587
447
- value = unpack_storage_number(n);
448
- if(likely(value != 0.0))
449
- values_in_group_non_zero++;
588
+ if(unlikely(did_storage_number_reset(n)))
589
+ group_value_flags |= RRDR_VALUE_RESET;
590
451
- if(unlikely(did_storage_number_reset(n)))
452
- group_value_flags |= RRDR_VALUE_RESET;
591
+ }
592
454
- }
593
+ // add this value for grouping
594
+ r->internal.grouping_add(r, value);
595
+ values_in_group++;
596
+ db_points_read++;
597
456
- // add this value for grouping
457
- r->internal.grouping_add(r, value);
458
- values_in_group++;
459
- db_points_read++;
598
+ if(unlikely(values_in_group == group_size)) {
599
+ rrdr_line = rrdr_line_init(r, now, rrdr_line);
600
461
- if(unlikely(values_in_group == group_size)) {
462
- rrdr_line = rrdr_line_init(r, now, rrdr_line);
601
+ if(unlikely(!min_date)) min_date = now;
602
+ max_date = now;
603
464
- if(unlikely(!min_date)) min_date = now;
465
- max_date = now;
604
+ // find the place to store our values
605
+ RRDR_VALUE_FLAGS *rrdr_value_options_ptr = &r->o[rrdr_line * r->d + dim_id_in_rrdr];
606
467
- // find the place to store our values
468
- RRDR_VALUE_FLAGS *rrdr_value_options_ptr = &r->o[rrdr_line * r->d + dim_id_in_rrdr];
607
+ // update the dimension options
608
+ if(likely(values_in_group_non_zero))
609
+ r->od[dim_id_in_rrdr] |= RRDR_DIMENSION_NONZERO;
610
470
- // update the dimension options
471
- if(likely(values_in_group_non_zero))
472
- r->od[dim_id_in_rrdr] |= RRDR_DIMENSION_NONZERO;
611
+ // store the specific point options
612
+ *rrdr_value_options_ptr = group_value_flags;
613
474
- // store the specific point options
475
- *rrdr_value_options_ptr = group_value_flags;
614
+ // store the value
615
+ calculated_number value = r->internal.grouping_flush(r, rrdr_value_options_ptr);
616
+ r->v[rrdr_line * r->d + dim_id_in_rrdr] = value;
617
477
- // store the value
478
- calculated_number value = r->internal.grouping_flush(r, rrdr_value_options_ptr);
479
- r->v[rrdr_line * r->d + dim_id_in_rrdr] = value;
618
+ if(likely(points_added || dim_id_in_rrdr)) {
619
+ // find the min/max across all dimensions
620
481
- if(likely(points_added || dim_id_in_rrdr)) {
482
- // find the min/max across all dimensions
621
+ if(unlikely(value < min)) min = value;
622
+ if(unlikely(value > max)) max = value;
623
484
- if(unlikely(value < min)) min = value;
485
- if(unlikely(value > max)) max = value;
624
+ }
625
+ else {
626
+ // runs only when dim_id_in_rrdr == 0 && points_added == 0
627
+ // so, on the first point added for the query.
628
+ min = max = value;
629
+ }
630
631
+ points_added++;
632
+ values_in_group = 0;
633
+ group_value_flags = RRDR_VALUE_NOTHING;
634
+ values_in_group_non_zero = 0;
635
}
488
- else {
489
- // runs only when dim_id_in_rrdr == 0 && points_added == 0
490
- // so, on the first point added for the query.
491
- min = max = value;
492
- }
493
-
494
- points_added++;
495
- values_in_group = 0;
496
- group_value_flags = RRDR_VALUE_NOTHING;
497
- values_in_group_non_zero = 0;
636
}
637
+ now = db_now;
638
}
500
- if (likely(initialized_query))
501
- rd->state->query_ops.finalize(&handle);
639
+ rd->state->query_ops.finalize(&handle);
640
641
r->internal.db_points_read += db_points_read;
642
r->internal.result_points_generated += points_added;
@@ -506,13 +644,13 @@ static inline void do_dimension(
644
r->min = min;
645
r->max = max;
646
r->before = max_date;
509
- r->after = min_date - (r->group - 1) * r->st->update_every;
647
+ r->after = min_date - (r->group - 1) * dt;
648
rrdr_done(r, rrdr_line);
649
512
- #ifdef NETDATA_INTERNAL_CHECKS
650
+#ifdef NETDATA_INTERNAL_CHECKS
651
if(unlikely(r->rows != points_added))
652
error("INTERNAL ERROR: %s.%s added %zu rows, but RRDR says I added %zu.", r->st->name, rd->name, (size_t)points_added, (size_t)r->rows);
515
- #endif
653
+#endif
654
}
655
656
// ----------------------------------------------------------------------------
@@ -589,22 +727,18 @@ static void rrd2rrdr_log_request_response_metdata(RRDR *r
727
}
728
#endif // NETDATA_INTERNAL_CHECKS
729
592
-RRDR *rrd2rrdr(
593
- RRDSET *st
594
- , long points_requested
595
- , long long after_requested
596
- , long long before_requested
597
- , RRDR_GROUPING group_method
598
- , long resampling_time_requested
599
- , RRDR_OPTIONS options
600
- , const char *dimensions
730
+// Returns 1 if an absolute period was requested or 0 if it was a relative period
731
+static int rrdr_convert_before_after_to_absolute(
732
+ long long *after_requestedp
733
+ , long long *before_requestedp
734
+ , time_t first_entry_t
735
+ , time_t last_entry_t
736
) {
602
- int aligned = !(options & RRDR_OPTION_NOT_ALIGNED);
603
-
737
int absolute_period_requested = -1;
738
+ long long after_requested, before_requested;
739
606
- time_t first_entry_t = rrdset_first_entry_t(st);
607
- time_t last_entry_t = rrdset_last_entry_t(st);
740
+ before_requested = *before_requestedp;
741
+ after_requested = *after_requestedp;
742
743
if(before_requested == 0 && after_requested == 0) {
744
// dump the all the data
@@ -614,25 +748,15 @@ RRDR *rrd2rrdr(
748
}
749
750
// allow relative for before (smaller than API_RELATIVE_TIME_MAX)
617
- if(((before_requested < 0)?-before_requested:before_requested) <= API_RELATIVE_TIME_MAX) {
618
- if(abs(before_requested) % st->update_every) {
619
- // make sure it is multiple of st->update_every
620
- if(before_requested < 0) before_requested = before_requested - st->update_every - before_requested % st->update_every;
621
- else before_requested = before_requested + st->update_every - before_requested % st->update_every;
622
- }
751
+ if(abs(before_requested) <= API_RELATIVE_TIME_MAX) {
752
if(before_requested > 0) before_requested = first_entry_t + before_requested;
624
- else before_requested = last_entry_t + before_requested;
753
+ else before_requested = last_entry_t + before_requested; //last_entry_t is not really now_t
754
+ //TODO: fix before_requested to be relative to now_t
755
absolute_period_requested = 0;
756
}
757
758
// allow relative for after (smaller than API_RELATIVE_TIME_MAX)
629
- if(((after_requested < 0)?-after_requested:after_requested) <= API_RELATIVE_TIME_MAX) {
630
- if(after_requested == 0) after_requested = -st->update_every;
631
- if(abs(after_requested) % st->update_every) {
632
- // make sure it is multiple of st->update_every
633
- if(after_requested < 0) after_requested = after_requested - st->update_every - after_requested % st->update_every;
634
- else after_requested = after_requested + st->update_every - after_requested % st->update_every;
635
- }
759
+ if(abs(after_requested) <= API_RELATIVE_TIME_MAX) {
760
after_requested = before_requested + after_requested;
761
absolute_period_requested = 0;
762
}
@@ -654,9 +778,53 @@ RRDR *rrd2rrdr(
778
after_requested = tmp;
779
}
780
781
+ *before_requestedp = before_requested;
782
+ *after_requestedp = after_requested;
783
+
784
+ return absolute_period_requested;
785
+}
786
+
787
+static RRDR *rrd2rrdr_fixedstep(
788
+ RRDSET *st
789
+ , long points_requested
790
+ , long long after_requested
791
+ , long long before_requested
792
+ , RRDR_GROUPING group_method
793
+ , long resampling_time_requested
794
+ , RRDR_OPTIONS options
795
+ , const char *dimensions
796
+ , int update_every
797
+ , time_t first_entry_t
798
+ , time_t last_entry_t
799
+ , int absolute_period_requested
800
+) {
801
+ int aligned = !(options & RRDR_OPTION_NOT_ALIGNED);
802
+
803
+ if(!absolute_period_requested) {
804
+ if(before_requested % update_every) {
805
+ // make sure it is multiple of update_every
806
+ if(before_requested > 0)
807
+ before_requested = before_requested - update_every + before_requested % update_every;
808
+ #ifdef NETDATA_INTERNAL_CHECKS
809
+ else
810
+ error("INTERNAL ERROR: rrd2rrdr() on %s, negative or zero before_requested", st->name);
811
+ #endif
812
+ }
813
+ if(after_requested % update_every) {
814
+ // make sure it is multiple of update_every
815
+ if(after_requested < 0)
816
+ after_requested = after_requested - update_every + after_requested % update_every;
817
+ #ifdef NETDATA_INTERNAL_CHECKS
818
+ else
819
+ error("INTERNAL ERROR: rrd2rrdr() on %s, negative or zero after_requested", st->name);
820
+ #endif
821
+ }
822
+ if(after_requested == before_requested) after_requested -= update_every;
823
+ }
824
+
825
// the duration of the chart
826
time_t duration = before_requested - after_requested;
659
- long available_points = duration / st->update_every;
827
+ long available_points = duration / update_every;
828
829
if(duration <= 0 || available_points <= 0)
830
return rrdr_create(st, 1);
@@ -674,7 +842,7 @@ RRDR *rrd2rrdr(
842
// resampling_time_requested enforces a certain grouping multiple
843
calculated_number resampling_divisor = 1.0;
844
long resampling_group = 1;
677
- if(unlikely(resampling_time_requested > st->update_every)) {
845
+ if(unlikely(resampling_time_requested > update_every)) {
846
if (unlikely(resampling_time_requested > duration)) {
847
// group_time is above the available duration
848
@@ -684,7 +852,7 @@ RRDR *rrd2rrdr(
852
853
after_requested = before_requested - resampling_time_requested;
854
duration = before_requested - after_requested;
687
- available_points = duration / st->update_every;
855
+ available_points = duration / update_every;
856
group = available_points / points_requested;
857
}
858
@@ -696,16 +864,16 @@ RRDR *rrd2rrdr(
864
if(delta > resampling_time_requested / 10) {
865
after_requested -= resampling_time_requested - delta;
866
duration = before_requested - after_requested;
699
- available_points = duration / st->update_every;
867
+ available_points = duration / update_every;
868
group = available_points / points_requested;
869
}
870
}
871
872
// the points we should group to satisfy gtime
705
- resampling_group = resampling_time_requested / st->update_every;
706
- if(unlikely(resampling_time_requested % st->update_every)) {
873
+ resampling_group = resampling_time_requested / update_every;
874
+ if(unlikely(resampling_time_requested % update_every)) {
875
#ifdef NETDATA_INTERNAL_CHECKS
708
- info("INTERNAL CHECK: %s: requested gtime %ld secs, is not a multiple of the chart's data collection frequency %d secs", st->id, resampling_time_requested, st->update_every);
876
+ info("INTERNAL CHECK: %s: requested gtime %ld secs, is not a multiple of the chart's data collection frequency %d secs", st->id, resampling_time_requested, update_every);
877
#endif
878
879
resampling_group++;
@@ -716,7 +884,7 @@ RRDR *rrd2rrdr(
884
if(unlikely(group % resampling_group)) group += resampling_group - (group % resampling_group); // make sure group is multiple of resampling_group
885
886
//resampling_divisor = group / resampling_group;
719
- resampling_divisor = (calculated_number)(group * st->update_every) / (calculated_number)resampling_time_requested;
887
+ resampling_divisor = (calculated_number)(group * update_every) / (calculated_number)resampling_time_requested;
888
}
889
890
// now that we have group,
@@ -724,8 +892,8 @@ RRDR *rrd2rrdr(
892
893
if(aligned) {
894
// alignement has been requested, so align the values
727
- before_requested -= (before_requested % group);
728
- after_requested -= (after_requested % group);
895
+ before_requested -= before_requested % (group * update_every);
896
+ after_requested -= after_requested % (group * update_every);
897
}
898
899
// we align the request on requested_before
@@ -735,28 +903,28 @@ RRDR *rrd2rrdr(
903
error("INTERNAL ERROR: rrd2rrdr() on %s, before_wanted is after db max", st->name);
904
#endif
905
738
- before_wanted = last_entry_t - (last_entry_t % ( ((aligned)?group:1) * st->update_every ));
906
+ before_wanted = last_entry_t - (last_entry_t % ( ((aligned)?group:1) * update_every ));
907
}
908
//size_t before_slot = rrdset_time2slot(st, before_wanted);
909
910
// we need to estimate the number of points, for having
911
// an integer number of values per point
744
- long points_wanted = (before_wanted - after_requested) / (st->update_every * group);
912
+ long points_wanted = (before_wanted - after_requested) / (update_every * group);
913
746
- time_t after_wanted = before_wanted - (points_wanted * group * st->update_every) + st->update_every;
914
+ time_t after_wanted = before_wanted - (points_wanted * group * update_every) + update_every;
915
if(unlikely(after_wanted < first_entry_t)) {
916
// hm... we go to the past, calculate again points_wanted using all the db from before_wanted to the beginning
917
points_wanted = (before_wanted - first_entry_t) / group;
918
919
// recalculate after wanted with the new number of points
752
- after_wanted = before_wanted - (points_wanted * group * st->update_every) + st->update_every;
920
+ after_wanted = before_wanted - (points_wanted * group * update_every) + update_every;
921
922
if(unlikely(after_wanted < first_entry_t)) {
923
#ifdef NETDATA_INTERNAL_CHECKS
924
error("INTERNAL ERROR: rrd2rrdr() on %s, after_wanted is before db min", st->name);
925
#endif
926
759
- after_wanted = first_entry_t - (first_entry_t % ( ((aligned)?group:1) * st->update_every )) + ( ((aligned)?group:1) * st->update_every );
927
+ after_wanted = first_entry_t - (first_entry_t % ( ((aligned)?group:1) * update_every )) + ( ((aligned)?group:1) * update_every );
928
}
929
}
930
//size_t after_slot = rrdset_time2slot(st, after_wanted);
@@ -772,7 +940,7 @@ RRDR *rrd2rrdr(
940
}
941
942
// recalculate points_wanted using the final time-frame
775
- points_wanted = (before_wanted - after_wanted) / st->update_every / group + 1;
943
+ points_wanted = (before_wanted - after_wanted) / update_every / group + 1;
944
if(unlikely(points_wanted < 0)) {
945
#ifdef NETDATA_INTERNAL_CHECKS
946
error("INTERNAL ERROR: rrd2rrdr() on %s, points_wanted is %ld", st->name, points_wanted);
@@ -803,8 +971,8 @@ RRDR *rrd2rrdr(
971
error("INTERNAL CHECK: after_slot is invalid %zu, expected 0 to %ld", after_slot, st->entries - 1);
972
*/
973
806
- if(points_wanted > (before_wanted - after_wanted) / group / st->update_every + 1)
807
- error("INTERNAL CHECK: points_wanted %ld is more than points %ld", points_wanted, (before_wanted - after_wanted) / group / st->update_every + 1);
974
+ if(points_wanted > (before_wanted - after_wanted) / group / update_every + 1)
975
+ error("INTERNAL CHECK: points_wanted %ld is more than points %ld", points_wanted, (before_wanted - after_wanted) / group / update_every + 1);
976
977
if(group < resampling_group)
978
error("INTERNAL CHECK: group %ld is less than the desired group points %ld", group, resampling_group);
@@ -844,7 +1012,7 @@ RRDR *rrd2rrdr(
1012
// initialize RRDR
1013
1014
r->group = group;
847
- r->update_every = (int)group * st->update_every;
1015
+ r->update_every = (int)group * update_every;
1016
r->before = before_wanted;
1017
r->after = after_wanted;
1018
r->internal.points_wanted = points_wanted;
@@ -913,7 +1081,7 @@ RRDR *rrd2rrdr(
1081
// reset the grouping for the new dimension
1082
r->internal.grouping_reset(r);
1083
916
- do_dimension(
1084
+ do_dimension_fixedstep(
1085
r
1086
, points_wanted
1087
, rd
@@ -961,30 +1129,426 @@ RRDR *rrd2rrdr(
1129
}
1130
1131
#ifdef NETDATA_INTERNAL_CHECKS
1132
+ if (dimensions_used) {
1133
+ if(r->internal.log)
1134
+ rrd2rrdr_log_request_response_metdata(r, group_method, aligned, group, resampling_time_requested, resampling_group, after_wanted, after_requested, before_wanted, before_requested, points_requested, points_wanted, /*after_slot, before_slot,*/ r->internal.log);
1135
+
1136
+ if(r->rows != points_wanted)
1137
+ rrd2rrdr_log_request_response_metdata(r, group_method, aligned, group, resampling_time_requested, resampling_group, after_wanted, after_requested, before_wanted, before_requested, points_requested, points_wanted, /*after_slot, before_slot,*/ "got 'points' is not wanted 'points'");
1138
+
1139
+ if(aligned && (r->before % group) != 0)
1140
+ rrd2rrdr_log_request_response_metdata(r, group_method, aligned, group, resampling_time_requested, resampling_group, after_wanted, after_requested, before_wanted, before_requested, points_requested, points_wanted, /*after_slot, before_slot,*/ "'before' is not aligned but alignment is required");
1141
+
1142
+ // 'after' should not be aligned, since we start inside the first group
1143
+ //if(aligned && (r->after % group) != 0)
1144
+ // rrd2rrdr_log_request_response_metdata(r, group_method, aligned, group, resampling_time_requested, resampling_group, after_wanted, after_requested, before_wanted, before_requested, points_requested, points_wanted, after_slot, before_slot, "'after' is not aligned but alignment is required");
1145
+
1146
+ if(r->before != before_requested)
1147
+ rrd2rrdr_log_request_response_metdata(r, group_method, aligned, group, resampling_time_requested, resampling_group, after_wanted, after_requested, before_wanted, before_requested, points_requested, points_wanted, /*after_slot, before_slot,*/ "chart is not aligned to requested 'before'");
1148
+
1149
+ if(r->before != before_wanted)
1150
+ rrd2rrdr_log_request_response_metdata(r, group_method, aligned, group, resampling_time_requested, resampling_group, after_wanted, after_requested, before_wanted, before_requested, points_requested, points_wanted, /*after_slot, before_slot,*/ "got 'before' is not wanted 'before'");
1151
+
1152
+ // reported 'after' varies, depending on group
1153
+ if(r->after != after_wanted)
1154
+ rrd2rrdr_log_request_response_metdata(r, group_method, aligned, group, resampling_time_requested, resampling_group, after_wanted, after_requested, before_wanted, before_requested, points_requested, points_wanted, /*after_slot, before_slot,*/ "got 'after' is not wanted 'after'");
1155
+ }
1156
+ #endif
1157
+
1158
+ // free all resources used by the grouping method
1159
+ r->internal.grouping_free(r);
1160
+
1161
+ // when all the dimensions are zero, we should return all of them
1162
+ if(unlikely(options & RRDR_OPTION_NONZERO && !dimensions_nonzero)) {
1163
+ // all the dimensions are zero
1164
+ // mark them as NONZERO to send them all
1165
+ for(rd = st->dimensions, c = 0 ; rd && c < dimensions_count ; rd = rd->next, c++) {
1166
+ if(unlikely(r->od[c] & RRDR_DIMENSION_HIDDEN)) continue;
1167
+ r->od[c] |= RRDR_DIMENSION_NONZERO;
1168
+ }
1169
+ }
1170
+
1171
+ rrdr_query_completed(r->internal.db_points_read, r->internal.result_points_generated);
1172
+ return r;
1173
+}
1174
+
1175
+#ifdef ENABLE_DBENGINE
1176
+static RRDR *rrd2rrdr_variablestep(
1177
+ RRDSET *st
1178
+ , long points_requested
1179
+ , long long after_requested
1180
+ , long long before_requested
1181
+ , RRDR_GROUPING group_method
1182
+ , long resampling_time_requested
1183
+ , RRDR_OPTIONS options
1184
+ , const char *dimensions
1185
+ , int update_every
1186
+ , time_t first_entry_t
1187
+ , time_t last_entry_t
1188
+ , int absolute_period_requested
1189
+ , struct rrdeng_region_info *region_info_array
1190
+) {
1191
+ int aligned = !(options & RRDR_OPTION_NOT_ALIGNED);
1192
+
1193
+ if(!absolute_period_requested) {
1194
+ if(before_requested % update_every) {
1195
+ // make sure it is multiple of update_every
1196
+ if(before_requested > 0)
1197
+ before_requested = before_requested - before_requested % update_every;
1198
+ #ifdef NETDATA_INTERNAL_CHECKS
1199
+ else
1200
+ error("INTERNAL ERROR: rrd2rrdr() on %s, negative or zero before_requested", st->name);
1201
+ #endif
1202
+ }
1203
+ if(after_requested % update_every) {
1204
+ // make sure it is multiple of update_every
1205
+ if(after_requested < 0)
1206
+ after_requested = after_requested - after_requested % update_every;
1207
+ #ifdef NETDATA_INTERNAL_CHECKS
1208
+ else
1209
+ error("INTERNAL ERROR: rrd2rrdr() on %s, negative or zero after_requested", st->name);
1210
+ #endif
1211
+ }
1212
+ if(after_requested == before_requested) after_requested -= update_every;
1213
+ }
1214
+
1215
+ // the duration of the chart
1216
+ time_t duration = before_requested - after_requested;
1217
+ long available_points = duration / update_every;
1218
+
1219
+ if(duration <= 0 || available_points <= 0) {
1220
+ freez(region_info_array);
1221
+ return rrdr_create(st, 1);
1222
+ }
1223
+
1224
+ // check the number of wanted points in the result
1225
+ if(unlikely(points_requested < 0)) points_requested = -points_requested;
1226
+ if(unlikely(points_requested > available_points)) points_requested = available_points;
1227
+ if(unlikely(points_requested == 0)) points_requested = available_points;
1228
+
1229
+ // calculate the desired grouping of source data points
1230
+ long group = available_points / points_requested;
1231
+ if(unlikely(group <= 0)) group = 1;
1232
+ if(unlikely(available_points % points_requested > points_requested / 2)) group++; // rounding to the closest integer
1233
+
1234
+ // resampling_time_requested enforces a certain grouping multiple
1235
+ calculated_number resampling_divisor = 1.0;
1236
+ long resampling_group = 1;
1237
+ if(unlikely(resampling_time_requested > update_every)) {
1238
+ if (unlikely(resampling_time_requested > duration)) {
1239
+ // group_time is above the available duration
1240
+
1241
+ #ifdef NETDATA_INTERNAL_CHECKS
1242
+ info("INTERNAL CHECK: %s: requested gtime %ld secs, is greater than the desired duration %ld secs", st->id, resampling_time_requested, duration);
1243
+ #endif
1244
+
1245
+ after_requested = before_requested - resampling_time_requested;
1246
+ duration = before_requested - after_requested;
1247
+ available_points = duration / update_every;
1248
+ group = available_points / points_requested;
1249
+ }
1250
+
1251
+ // if the duration is not aligned to resampling time
1252
+ // extend the duration to the past, to avoid a gap at the chart
1253
+ // only when the missing duration is above 1/10th of a point
1254
+ if(duration % resampling_time_requested) {
1255
+ time_t delta = duration % resampling_time_requested;
1256
+ if(delta > resampling_time_requested / 10) {
1257
+ after_requested -= resampling_time_requested - delta;
1258
+ duration = before_requested - after_requested;
1259
+ available_points = duration / update_every;
1260
+ group = available_points / points_requested;
1261
+ }
1262
+ }
1263
+
1264
+ // the points we should group to satisfy gtime
1265
+ resampling_group = resampling_time_requested / update_every;
1266
+ if(unlikely(resampling_time_requested % update_every)) {
1267
+ #ifdef NETDATA_INTERNAL_CHECKS
1268
+ info("INTERNAL CHECK: %s: requested gtime %ld secs, is not a multiple of the chart's data collection frequency %d secs", st->id, resampling_time_requested, update_every);
1269
+ #endif
1270
+
1271
+ resampling_group++;
1272
+ }
1273
+
1274
+ // adapt group according to resampling_group
1275
+ if(unlikely(group < resampling_group)) group = resampling_group; // do not allow grouping below the desired one
1276
+ if(unlikely(group % resampling_group)) group += resampling_group - (group % resampling_group); // make sure group is multiple of resampling_group
1277
+
1278
+ //resampling_divisor = group / resampling_group;
1279
+ resampling_divisor = (calculated_number)(group * update_every) / (calculated_number)resampling_time_requested;
1280
+ }
1281
+
1282
+ // now that we have group,
1283
+ // align the requested timeframe to fit it.
1284
+
1285
+ if(aligned) {
1286
+ // alignement has been requested, so align the values
1287
+ before_requested -= before_requested % (group * update_every);
1288
+ after_requested -= after_requested % (group * update_every);
1289
+ }
1290
+
1291
+ // we align the request on requested_before
1292
+ time_t before_wanted = before_requested;
1293
+ if(likely(before_wanted > last_entry_t)) {
1294
+ #ifdef NETDATA_INTERNAL_CHECKS
1295
+ error("INTERNAL ERROR: rrd2rrdr() on %s, before_wanted is after db max", st->name);
1296
+ #endif
1297
+
1298
+ before_wanted = last_entry_t - (last_entry_t % ( ((aligned)?group:1) * update_every ));
1299
+ }
1300
+ //size_t before_slot = rrdset_time2slot(st, before_wanted);
1301
+
1302
+ // we need to estimate the number of points, for having
1303
+ // an integer number of values per point
1304
+ long points_wanted = (before_wanted - after_requested) / (update_every * group);
1305
+
1306
+ time_t after_wanted = before_wanted - (points_wanted * group * update_every) + update_every;
1307
+ if(unlikely(after_wanted < first_entry_t)) {
1308
+ // hm... we go to the past, calculate again points_wanted using all the db from before_wanted to the beginning
1309
+ points_wanted = (before_wanted - first_entry_t) / group;
1310
+
1311
+ // recalculate after wanted with the new number of points
1312
+ after_wanted = before_wanted - (points_wanted * group * update_every) + update_every;
1313
+
1314
+ if(unlikely(after_wanted < first_entry_t)) {
1315
+ #ifdef NETDATA_INTERNAL_CHECKS
1316
+ error("INTERNAL ERROR: rrd2rrdr() on %s, after_wanted is before db min", st->name);
1317
+ #endif
1318
+
1319
+ after_wanted = first_entry_t - (first_entry_t % ( ((aligned)?group:1) * update_every )) + ( ((aligned)?group:1) * update_every );
1320
+ }
1321
+ }
1322
+ //size_t after_slot = rrdset_time2slot(st, after_wanted);
1323
+
1324
+ // check if they are reversed
1325
+ if(unlikely(after_wanted > before_wanted)) {
1326
+ #ifdef NETDATA_INTERNAL_CHECKS
1327
+ error("INTERNAL ERROR: rrd2rrdr() on %s, reversed wanted after/before", st->name);
1328
+ #endif
1329
+ time_t tmp = before_wanted;
1330
+ before_wanted = after_wanted;
1331
+ after_wanted = tmp;
1332
+ }
1333
965
- if(r->internal.log)
966
- rrd2rrdr_log_request_response_metdata(r, group_method, aligned, group, resampling_time_requested, resampling_group, after_wanted, after_requested, before_wanted, before_requested, points_requested, points_wanted, /*after_slot, before_slot,*/ r->internal.log);
1334
+ // recalculate points_wanted using the final time-frame
1335
+ points_wanted = (before_wanted - after_wanted) / update_every / group + 1;
1336
+ if(unlikely(points_wanted < 0)) {
1337
+ #ifdef NETDATA_INTERNAL_CHECKS
1338
+ error("INTERNAL ERROR: rrd2rrdr() on %s, points_wanted is %ld", st->name, points_wanted);
1339
+ #endif
1340
+ points_wanted = 0;
1341
+ }
1342
+
1343
+#ifdef NETDATA_INTERNAL_CHECKS
1344
+ duration = before_wanted - after_wanted;
1345
968
- if(r->rows != points_wanted)
969
- rrd2rrdr_log_request_response_metdata(r, group_method, aligned, group, resampling_time_requested, resampling_group, after_wanted, after_requested, before_wanted, before_requested, points_requested, points_wanted, /*after_slot, before_slot,*/ "got 'points' is not wanted 'points'");
1346
+ if(after_wanted < first_entry_t)
1347
+ error("INTERNAL CHECK: after_wanted %u is too small, minimum %u", (uint32_t)after_wanted, (uint32_t)first_entry_t);
1348
971
- if(aligned && (r->before % group) != 0)
972
- rrd2rrdr_log_request_response_metdata(r, group_method, aligned, group, resampling_time_requested, resampling_group, after_wanted, after_requested, before_wanted, before_requested, points_requested, points_wanted, /*after_slot, before_slot,*/ "'before' is not aligned but alignment is required");
1349
+ if(after_wanted > last_entry_t)
1350
+ error("INTERNAL CHECK: after_wanted %u is too big, maximum %u", (uint32_t)after_wanted, (uint32_t)last_entry_t);
1351
974
- // 'after' should not be aligned, since we start inside the first group
975
- //if(aligned && (r->after % group) != 0)
976
- // rrd2rrdr_log_request_response_metdata(r, group_method, aligned, group, resampling_time_requested, resampling_group, after_wanted, after_requested, before_wanted, before_requested, points_requested, points_wanted, after_slot, before_slot, "'after' is not aligned but alignment is required");
1352
+ if(before_wanted < first_entry_t)
1353
+ error("INTERNAL CHECK: before_wanted %u is too small, minimum %u", (uint32_t)before_wanted, (uint32_t)first_entry_t);
1354
978
- if(r->before != before_requested)
979
- rrd2rrdr_log_request_response_metdata(r, group_method, aligned, group, resampling_time_requested, resampling_group, after_wanted, after_requested, before_wanted, before_requested, points_requested, points_wanted, /*after_slot, before_slot,*/ "chart is not aligned to requested 'before'");
1355
+ if(before_wanted > last_entry_t)
1356
+ error("INTERNAL CHECK: before_wanted %u is too big, maximum %u", (uint32_t)before_wanted, (uint32_t)last_entry_t);
1357
981
- if(r->before != before_wanted)
982
- rrd2rrdr_log_request_response_metdata(r, group_method, aligned, group, resampling_time_requested, resampling_group, after_wanted, after_requested, before_wanted, before_requested, points_requested, points_wanted, /*after_slot, before_slot,*/ "got 'before' is not wanted 'before'");
1358
+/*
1359
+ if(before_slot >= (size_t)st->entries)
1360
+ error("INTERNAL CHECK: before_slot is invalid %zu, expected 0 to %ld", before_slot, st->entries - 1);
1361
984
- // reported 'after' varies, depending on group
985
- if(r->after != after_wanted)
986
- rrd2rrdr_log_request_response_metdata(r, group_method, aligned, group, resampling_time_requested, resampling_group, after_wanted, after_requested, before_wanted, before_requested, points_requested, points_wanted, /*after_slot, before_slot,*/ "got 'after' is not wanted 'after'");
1362
+ if(after_slot >= (size_t)st->entries)
1363
+ error("INTERNAL CHECK: after_slot is invalid %zu, expected 0 to %ld", after_slot, st->entries - 1);
1364
+*/
1365
1366
+ if(points_wanted > (before_wanted - after_wanted) / group / update_every + 1)
1367
+ error("INTERNAL CHECK: points_wanted %ld is more than points %ld", points_wanted, (before_wanted - after_wanted) / group / update_every + 1);
1368
+
1369
+ if(group < resampling_group)
1370
+ error("INTERNAL CHECK: group %ld is less than the desired group points %ld", group, resampling_group);
1371
+
1372
+ if(group > resampling_group && group % resampling_group)
1373
+ error("INTERNAL CHECK: group %ld is not a multiple of the desired group points %ld", group, resampling_group);
1374
+#endif
1375
+
1376
+ // -------------------------------------------------------------------------
1377
+ // initialize our result set
1378
+ // this also locks the chart for us
1379
+
1380
+ RRDR *r = rrdr_create(st, points_wanted);
1381
+ if(unlikely(!r)) {
1382
+ #ifdef NETDATA_INTERNAL_CHECKS
1383
+ error("INTERNAL CHECK: Cannot create RRDR for %s, after=%u, before=%u, duration=%u, points=%ld", st->id, (uint32_t)after_wanted, (uint32_t)before_wanted, (uint32_t)duration, points_wanted);
1384
+ #endif
1385
+ freez(region_info_array);
1386
+ return NULL;
1387
+ }
1388
+
1389
+ if(unlikely(!r->d || !points_wanted)) {
1390
+ #ifdef NETDATA_INTERNAL_CHECKS
1391
+ error("INTERNAL CHECK: Returning empty RRDR (no dimensions in RRDSET) for %s, after=%u, before=%u, duration=%zu, points=%ld", st->id, (uint32_t)after_wanted, (uint32_t)before_wanted, (size_t)duration, points_wanted);
1392
+ #endif
1393
+ freez(region_info_array);
1394
+ return r;
1395
+ }
1396
+
1397
+ r->result_options |= RRDR_RESULT_OPTION_VARIABLE_STEP;
1398
+ if(unlikely(absolute_period_requested == 1))
1399
+ r->result_options |= RRDR_RESULT_OPTION_ABSOLUTE;
1400
+ else
1401
+ r->result_options |= RRDR_RESULT_OPTION_RELATIVE;
1402
+
1403
+ // find how many dimensions we have
1404
+ long dimensions_count = r->d;
1405
+
1406
+ // -------------------------------------------------------------------------
1407
+ // initialize RRDR
1408
+
1409
+ r->group = group;
1410
+ r->update_every = (int)group * update_every;
1411
+ r->before = before_wanted;
1412
+ r->after = after_wanted;
1413
+ r->internal.points_wanted = points_wanted;
1414
+ r->internal.resampling_group = resampling_group;
1415
+ r->internal.resampling_divisor = resampling_divisor;
1416
+
1417
+
1418
+ // -------------------------------------------------------------------------
1419
+ // assign the processor functions
1420
+
1421
+ {
1422
+ int i, found = 0;
1423
+ for(i = 0; !found && api_v1_data_groups[i].name ;i++) {
1424
+ if(api_v1_data_groups[i].value == group_method) {
1425
+ r->internal.grouping_create= api_v1_data_groups[i].create;
1426
+ r->internal.grouping_reset = api_v1_data_groups[i].reset;
1427
+ r->internal.grouping_free = api_v1_data_groups[i].free;
1428
+ r->internal.grouping_add = api_v1_data_groups[i].add;
1429
+ r->internal.grouping_flush = api_v1_data_groups[i].flush;
1430
+ found = 1;
1431
+ }
1432
+ }
1433
+ if(!found) {
1434
+ errno = 0;
1435
+ #ifdef NETDATA_INTERNAL_CHECKS
1436
+ error("INTERNAL ERROR: grouping method %u not found for chart '%s'. Using 'average'", (unsigned int)group_method, r->st->name);
1437
+ #endif
1438
+ r->internal.grouping_create= grouping_create_average;
1439
+ r->internal.grouping_reset = grouping_reset_average;
1440
+ r->internal.grouping_free = grouping_free_average;
1441
+ r->internal.grouping_add = grouping_add_average;
1442
+ r->internal.grouping_flush = grouping_flush_average;
1443
+ }
1444
+ }
1445
+
1446
+ // allocate any memory required by the grouping method
1447
+ r->internal.grouping_data = r->internal.grouping_create(r);
1448
+
1449
+
1450
+ // -------------------------------------------------------------------------
1451
+ // disable the not-wanted dimensions
1452
+
1453
+ rrdset_check_rdlock(st);
1454
+
1455
+ if(dimensions)
1456
+ rrdr_disable_not_selected_dimensions(r, options, dimensions);
1457
+
1458
+
1459
+ // -------------------------------------------------------------------------
1460
+ // do the work for each dimension
1461
+
1462
+ time_t max_after = 0, min_before = 0;
1463
+ long max_rows = 0;
1464
+
1465
+ RRDDIM *rd;
1466
+ long c, dimensions_used = 0, dimensions_nonzero = 0;
1467
+ for(rd = st->dimensions, c = 0 ; rd && c < dimensions_count ; rd = rd->next, c++) {
1468
+
1469
+ // if we need a percentage, we need to calculate all dimensions
1470
+ if(unlikely(!(options & RRDR_OPTION_PERCENTAGE) && (r->od[c] & RRDR_DIMENSION_HIDDEN))) {
1471
+ if(unlikely(r->od[c] & RRDR_DIMENSION_SELECTED)) r->od[c] &= ~RRDR_DIMENSION_SELECTED;
1472
+ continue;
1473
+ }
1474
+ r->od[c] |= RRDR_DIMENSION_SELECTED;
1475
+
1476
+ // reset the grouping for the new dimension
1477
+ r->internal.grouping_reset(r);
1478
+
1479
+ do_dimension_variablestep(
1480
+ r
1481
+ , points_wanted
1482
+ , rd
1483
+ , c
1484
+ , after_wanted
1485
+ , before_wanted
1486
+ );
1487
+
1488
+ if(r->od[c] & RRDR_DIMENSION_NONZERO)
1489
+ dimensions_nonzero++;
1490
+
1491
+ // verify all dimensions are aligned
1492
+ if(unlikely(!dimensions_used)) {
1493
+ min_before = r->before;
1494
+ max_after = r->after;
1495
+ max_rows = r->rows;
1496
+ }
1497
+ else {
1498
+ if(r->after != max_after) {
1499
+ #ifdef NETDATA_INTERNAL_CHECKS
1500
+ error("INTERNAL ERROR: 'after' mismatch between dimensions for chart '%s': max is %zu, dimension '%s' has %zu",
1501
+ st->name, (size_t)max_after, rd->name, (size_t)r->after);
1502
+ #endif
1503
+ r->after = (r->after > max_after) ? r->after : max_after;
1504
+ }
1505
+
1506
+ if(r->before != min_before) {
1507
+ #ifdef NETDATA_INTERNAL_CHECKS
1508
+ error("INTERNAL ERROR: 'before' mismatch between dimensions for chart '%s': max is %zu, dimension '%s' has %zu",
1509
+ st->name, (size_t)min_before, rd->name, (size_t)r->before);
1510
+ #endif
1511
+ r->before = (r->before < min_before) ? r->before : min_before;
1512
+ }
1513
+
1514
+ if(r->rows != max_rows) {
1515
+ #ifdef NETDATA_INTERNAL_CHECKS
1516
+ error("INTERNAL ERROR: 'rows' mismatch between dimensions for chart '%s': max is %zu, dimension '%s' has %zu",
1517
+ st->name, (size_t)max_rows, rd->name, (size_t)r->rows);
1518
+ #endif
1519
+ r->rows = (r->rows > max_rows) ? r->rows : max_rows;
1520
+ }
1521
+ }
1522
+
1523
+ dimensions_used++;
1524
+ }
1525
+
1526
+ #ifdef NETDATA_INTERNAL_CHECKS
1527
+
1528
+ if (dimensions_used) {
1529
+ if(r->internal.log)
1530
+ rrd2rrdr_log_request_response_metdata(r, group_method, aligned, group, resampling_time_requested, resampling_group, after_wanted, after_requested, before_wanted, before_requested, points_requested, points_wanted, /*after_slot, before_slot,*/ r->internal.log);
1531
+
1532
+ if(r->rows != points_wanted)
1533
+ rrd2rrdr_log_request_response_metdata(r, group_method, aligned, group, resampling_time_requested, resampling_group, after_wanted, after_requested, before_wanted, before_requested, points_requested, points_wanted, /*after_slot, before_slot,*/ "got 'points' is not wanted 'points'");
1534
+
1535
+ if(aligned && (r->before % group) != 0)
1536
+ rrd2rrdr_log_request_response_metdata(r, group_method, aligned, group, resampling_time_requested, resampling_group, after_wanted, after_requested, before_wanted, before_requested, points_requested, points_wanted, /*after_slot, before_slot,*/ "'before' is not aligned but alignment is required");
1537
+
1538
+ // 'after' should not be aligned, since we start inside the first group
1539
+ //if(aligned && (r->after % group) != 0)
1540
+ // rrd2rrdr_log_request_response_metdata(r, group_method, aligned, group, resampling_time_requested, resampling_group, after_wanted, after_requested, before_wanted, before_requested, points_requested, points_wanted, after_slot, before_slot, "'after' is not aligned but alignment is required");
1541
+
1542
+ if(r->before != before_requested)
1543
+ rrd2rrdr_log_request_response_metdata(r, group_method, aligned, group, resampling_time_requested, resampling_group, after_wanted, after_requested, before_wanted, before_requested, points_requested, points_wanted, /*after_slot, before_slot,*/ "chart is not aligned to requested 'before'");
1544
+
1545
+ if(r->before != before_wanted)
1546
+ rrd2rrdr_log_request_response_metdata(r, group_method, aligned, group, resampling_time_requested, resampling_group, after_wanted, after_requested, before_wanted, before_requested, points_requested, points_wanted, /*after_slot, before_slot,*/ "got 'before' is not wanted 'before'");
1547
+
1548
+ // reported 'after' varies, depending on group
1549
+ if(r->after != after_wanted)
1550
+ rrd2rrdr_log_request_response_metdata(r, group_method, aligned, group, resampling_time_requested, resampling_group, after_wanted, after_requested, before_wanted, before_requested, points_requested, points_wanted, /*after_slot, before_slot,*/ "got 'after' is not wanted 'after'");
1551
+ }
1552
#endif
1553
1554
// free all resources used by the grouping method
@@ -1001,5 +1565,57 @@ RRDR *rrd2rrdr(
1565
}
1566
1567
rrdr_query_completed(r->internal.db_points_read, r->internal.result_points_generated);
1568
+ freez(region_info_array);
1569
return r;
1570
}
1571
+#endif //#ifdef ENABLE_DBENGINE
1572
+
1573
+RRDR *rrd2rrdr(
1574
+ RRDSET *st
1575
+ , long points_requested
1576
+ , long long after_requested
1577
+ , long long before_requested
1578
+ , RRDR_GROUPING group_method
1579
+ , long resampling_time_requested
1580
+ , RRDR_OPTIONS options
1581
+ , const char *dimensions
1582
+) {
1583
+ int rrd_update_every;
1584
+ int absolute_period_requested;
1585
+ time_t first_entry_t = rrdset_first_entry_t(st);
1586
+ time_t last_entry_t = rrdset_last_entry_t(st);
1587
+
1588
+ absolute_period_requested = rrdr_convert_before_after_to_absolute(&after_requested, &before_requested,
1589
+ first_entry_t, last_entry_t);
1590
+
1591
+#ifdef ENABLE_DBENGINE
1592
+ if ((st->rrd_memory_mode == RRD_MEMORY_MODE_DBENGINE)) {
1593
+ struct rrdeng_region_info *region_info_array;
1594
+ unsigned regions, max_interval;
1595
+
1596
+ /* This call takes the chart read-lock */
1597
+ regions = rrdeng_variable_step_boundaries(st, after_requested, before_requested,
1598
+ ®ion_info_array, &max_interval);
1599
+ if (1 == regions) {
1600
+ if (region_info_array)
1601
+ rrd_update_every = region_info_array[0].update_every;
1602
+ else
1603
+ rrd_update_every = st->update_every;
1604
+ if (region_info_array)
1605
+ freez(region_info_array);
1606
+ return rrd2rrdr_fixedstep(st, points_requested, after_requested, before_requested, group_method,
1607
+ resampling_time_requested, options, dimensions, rrd_update_every,
1608
+ first_entry_t, last_entry_t, absolute_period_requested);
1609
+ } else {
1610
+ rrd_update_every = (uint16_t)max_interval;
1611
+ return rrd2rrdr_variablestep(st, points_requested, after_requested, before_requested, group_method,
1612
+ resampling_time_requested, options, dimensions, rrd_update_every,
1613
+ first_entry_t, last_entry_t, absolute_period_requested, region_info_array);
1614
+ }
1615
+ }
1616
+#endif
1617
+ rrd_update_every = st->update_every;
1618
+ return rrd2rrdr_fixedstep(st, points_requested, after_requested, before_requested, group_method,
1619
+ resampling_time_requested, options, dimensions,
1620
+ rrd_update_every, first_entry_t, last_entry_t, absolute_period_requested);
1621
+}
\ No newline at end of file
web/api/queries/rrdr.h
+7
-4
@@ -6,7 +6,7 @@
6
#include "libnetdata/libnetdata.h"
7
8
typedef enum rrdr_options {
9
- RRDR_OPTION_NONZERO = 0x00000001, // don't output dimensions will just zero values
9
+ RRDR_OPTION_NONZERO = 0x00000001, // don't output dimensions with just zero values
10
RRDR_OPTION_REVERSED = 0x00000002, // output the rows in reverse order (oldest to newest)
11
RRDR_OPTION_ABSOLUTE = 0x00000004, // values positive, for DATASOURCE_SSV before summing
12
RRDR_OPTION_MIN2MAX = 0x00000008, // when adding dimensions, use max - min, instead of sum
@@ -18,7 +18,7 @@ typedef enum rrdr_options {
18
RRDR_OPTION_JSON_WRAP = 0x00000200, // wrap the response in a JSON header with info about the result
19
RRDR_OPTION_LABEL_QUOTES = 0x00000400, // in CSV output, wrap header labels in double quotes
20
RRDR_OPTION_PERCENTAGE = 0x00000800, // give values as percentage of total
21
- RRDR_OPTION_NOT_ALIGNED = 0x00001000, // do not align charts for persistant timeframes
21
+ RRDR_OPTION_NOT_ALIGNED = 0x00001000, // do not align charts for persistent timeframes
22
RRDR_OPTION_DISPLAY_ABS = 0x00002000, // for badges, display the absolute value, but calculate colors with sign
23
RRDR_OPTION_MATCH_IDS = 0x00004000, // when filtering dimensions, match only IDs
24
RRDR_OPTION_MATCH_NAMES = 0x00008000, // when filtering dimensions, match only names
@@ -40,8 +40,11 @@ typedef enum rrdr_dimension_flag {
40
41
// RRDR result options
42
typedef enum rrdr_result_flags {
43
- RRDR_RESULT_OPTION_ABSOLUTE = 0x00000001, // the query uses absolute time-frames (can be cached by browsers and proxies)
44
- RRDR_RESULT_OPTION_RELATIVE = 0x00000002, // the query uses relative time-frames (should not to be cached by browsers and proxies)
43
+ RRDR_RESULT_OPTION_ABSOLUTE = 0x00000001, // the query uses absolute time-frames
44
+ // (can be cached by browsers and proxies)
45
+ RRDR_RESULT_OPTION_RELATIVE = 0x00000002, // the query uses relative time-frames
46
+ // (should not to be cached by browsers and proxies)
47
+ RRDR_RESULT_OPTION_VARIABLE_STEP = 0x00000004, // the query uses variable-step time-frames
48
} RRDR_RESULT_FLAGS;
49
50
typedef struct rrdresult {