| 1 | // SPDX-License-Identifier: GPL-3.0-or-later |
| 2 | |
| 3 | #include "query-internal.h" |
| 4 | |
| 5 | static inline bool group_by_is_separator(char c) { |
| 6 | return c == ',' || c == '|' || c == ' '; |
| 7 | } |
| 8 | |
| 9 | static inline bool group_by_token_matches(const char *token, size_t len, const char *name, size_t name_len) { |
| 10 | if(!name) |
| 11 | return false; |
| 12 | |
| 13 | return name_len == len && !strncmp(token, name, len); |
| 14 | } |
| 15 | |
| 16 | RRDR_GROUP_BY group_by_parse(const char *group_by_txt) { |
| 17 | RRDR_GROUP_BY group_by = RRDR_GROUP_BY_NONE; |
| 18 | |
| 19 | if(!group_by_txt || !*group_by_txt) |
| 20 | return group_by; |
| 21 | |
| 22 | const char *s = group_by_txt; |
| 23 | while(*s) { |
| 24 | while(*s && group_by_is_separator(*s)) |
| 25 | s++; |
| 26 | |
| 27 | const char *key = s; |
| 28 | while(*s && !group_by_is_separator(*s)) |
| 29 | s++; |
| 30 | |
| 31 | size_t len = (size_t)(s - key); |
| 32 | if(!len) continue; |
| 33 | |
| 34 | if (group_by_token_matches(key, len, "selected", sizeof("selected") - 1)) |
| 35 | group_by |= RRDR_GROUP_BY_SELECTED; |
| 36 | |
| 37 | if (group_by_token_matches(key, len, "dimension", sizeof("dimension") - 1)) |
| 38 | group_by |= RRDR_GROUP_BY_DIMENSION; |
| 39 | |
| 40 | if (group_by_token_matches(key, len, "instance", sizeof("instance") - 1)) |
| 41 | group_by |= RRDR_GROUP_BY_INSTANCE; |
| 42 | |
| 43 | if (group_by_token_matches(key, len, "percentage-of-instance", sizeof("percentage-of-instance") - 1)) |
| 44 | group_by |= RRDR_GROUP_BY_PERCENTAGE_OF_INSTANCE; |
| 45 | |
| 46 | if (group_by_token_matches(key, len, "label", sizeof("label") - 1)) |
| 47 | group_by |= RRDR_GROUP_BY_LABEL; |
| 48 | |
| 49 | if (group_by_token_matches(key, len, "node", sizeof("node") - 1)) |
| 50 | group_by |= RRDR_GROUP_BY_NODE; |
| 51 | |
| 52 | if (group_by_token_matches(key, len, "context", sizeof("context") - 1)) |
| 53 | group_by |= RRDR_GROUP_BY_CONTEXT; |
| 54 | |
| 55 | if (group_by_token_matches(key, len, "units", sizeof("units") - 1)) |
| 56 | group_by |= RRDR_GROUP_BY_UNITS; |
| 57 | } |
| 58 | |
| 59 | if((group_by & RRDR_GROUP_BY_SELECTED) && (group_by & ~RRDR_GROUP_BY_SELECTED)) { |
| 60 | internal_error(true, "group-by given by query has 'selected' together with more groupings"); |
| 61 | group_by = RRDR_GROUP_BY_SELECTED; // remove all other groupings |
| 62 | } |
| 63 | |
| 64 | if(group_by & RRDR_GROUP_BY_PERCENTAGE_OF_INSTANCE) |
| 65 | group_by = RRDR_GROUP_BY_PERCENTAGE_OF_INSTANCE; // remove all other groupings |
| 66 | |
| 67 | return group_by; |
| 68 | } |
| 69 | |
| 70 | void buffer_json_group_by_to_array(BUFFER *wb, RRDR_GROUP_BY group_by) { |
| 71 | if(group_by == RRDR_GROUP_BY_NONE) |
| 72 | buffer_json_add_array_item_string(wb, "none"); |
| 73 | else { |
| 74 | if (group_by & RRDR_GROUP_BY_DIMENSION) |
| 75 | buffer_json_add_array_item_string(wb, "dimension"); |
| 76 | |
| 77 | if (group_by & RRDR_GROUP_BY_INSTANCE) |
| 78 | buffer_json_add_array_item_string(wb, "instance"); |
| 79 | |
| 80 | if (group_by & RRDR_GROUP_BY_PERCENTAGE_OF_INSTANCE) |
| 81 | buffer_json_add_array_item_string(wb, "percentage-of-instance"); |
| 82 | |
| 83 | if (group_by & RRDR_GROUP_BY_LABEL) |
| 84 | buffer_json_add_array_item_string(wb, "label"); |
| 85 | |
| 86 | if (group_by & RRDR_GROUP_BY_NODE) |
| 87 | buffer_json_add_array_item_string(wb, "node"); |
| 88 | |
| 89 | if (group_by & RRDR_GROUP_BY_CONTEXT) |
| 90 | buffer_json_add_array_item_string(wb, "context"); |
| 91 | |
| 92 | if (group_by & RRDR_GROUP_BY_UNITS) |
| 93 | buffer_json_add_array_item_string(wb, "units"); |
| 94 | |
| 95 | if (group_by & RRDR_GROUP_BY_SELECTED) |
| 96 | buffer_json_add_array_item_string(wb, "selected"); |
| 97 | } |
| 98 | } |
| 99 | |
| 100 | RRDR_GROUP_BY_FUNCTION group_by_aggregate_function_parse(const char *s) { |
| 101 | if(strcmp(s, "average") == 0) |
| 102 | return RRDR_GROUP_BY_FUNCTION_AVERAGE; |
| 103 | |
| 104 | if(strcmp(s, "avg") == 0) |
| 105 | return RRDR_GROUP_BY_FUNCTION_AVERAGE; |
| 106 | |
| 107 | if(strcmp(s, "min") == 0) |
| 108 | return RRDR_GROUP_BY_FUNCTION_MIN; |
| 109 | |
| 110 | if(strcmp(s, "max") == 0) |
| 111 | return RRDR_GROUP_BY_FUNCTION_MAX; |
| 112 | |
| 113 | if(strcmp(s, "sum") == 0) |
| 114 | return RRDR_GROUP_BY_FUNCTION_SUM; |
| 115 | |
| 116 | if(strcmp(s, "percentage") == 0) |
| 117 | return RRDR_GROUP_BY_FUNCTION_PERCENTAGE; |
| 118 | |
| 119 | if(strcmp(s, "extremes") == 0) |
| 120 | return RRDR_GROUP_BY_FUNCTION_EXTREMES; |
| 121 | |
| 122 | return RRDR_GROUP_BY_FUNCTION_AVERAGE; |
| 123 | } |
| 124 | |
| 125 | const char *group_by_aggregate_function_to_string(RRDR_GROUP_BY_FUNCTION group_by_function) { |
| 126 | switch(group_by_function) { |
| 127 | default: |
| 128 | case RRDR_GROUP_BY_FUNCTION_AVERAGE: |
| 129 | return "average"; |
| 130 | |
| 131 | case RRDR_GROUP_BY_FUNCTION_MIN: |
| 132 | return "min"; |
| 133 | |
| 134 | case RRDR_GROUP_BY_FUNCTION_MAX: |
| 135 | return "max"; |
| 136 | |
| 137 | case RRDR_GROUP_BY_FUNCTION_SUM: |
| 138 | return "sum"; |
| 139 | |
| 140 | case RRDR_GROUP_BY_FUNCTION_PERCENTAGE: |
| 141 | return "percentage"; |
| 142 | |
| 143 | case RRDR_GROUP_BY_FUNCTION_EXTREMES: |
| 144 | return "extremes"; |
| 145 | } |
| 146 | } |
| 147 | |
| 148 | // ---------------------------------------------------------------------------- |
| 149 | // group by |
| 150 | |
| 151 | void group_by_label_key_insert_cb(const DICTIONARY_ITEM *item __maybe_unused, void *value, void *data) { |
| 152 | // add the key to our r->label_keys global keys dictionary |
| 153 | DICTIONARY *label_keys = data; |
| 154 | dictionary_set(label_keys, dictionary_acquired_item_name(item), NULL, 0); |
| 155 | |
| 156 | // create a dictionary for the values of this key |
| 157 | struct group_by_label_key *k = value; |
| 158 | k->values = dictionary_create_advanced(DICT_OPTION_SINGLE_THREADED | DICT_OPTION_DONT_OVERWRITE_VALUE, NULL, 0); |
| 159 | } |
| 160 | |
| 161 | void group_by_label_key_delete_cb(const DICTIONARY_ITEM *item __maybe_unused, void *value, void *data __maybe_unused) { |
| 162 | struct group_by_label_key *k = value; |
| 163 | dictionary_destroy(k->values); |
| 164 | } |
| 165 | |
| 166 | int rrdlabels_traversal_cb_to_group_by_label_key(const char *name, const char *value, RRDLABEL_SRC ls __maybe_unused, void *data) { |
| 167 | DICTIONARY *dl = data; |
| 168 | struct group_by_label_key *k = dictionary_set(dl, name, NULL, sizeof(struct group_by_label_key)); |
| 169 | dictionary_set(k->values, value, NULL, 0); |
| 170 | return 1; |
| 171 | } |
| 172 | |
| 173 | void rrdr_json_group_by_labels(BUFFER *wb, const char *key, RRDR *r, RRDR_OPTIONS options) { |
| 174 | if(!r->label_keys || !r->dl) |
| 175 | return; |
| 176 | |
| 177 | buffer_json_member_add_object(wb, key); |
| 178 | |
| 179 | void *t; |
| 180 | dfe_start_read(r->label_keys, t) { |
| 181 | buffer_json_member_add_array(wb, t_dfe.name); |
| 182 | |
| 183 | for(size_t d = 0; d < r->d ;d++) { |
| 184 | if(!rrdr_dimension_should_be_exposed(r->od[d], options)) |
| 185 | continue; |
| 186 | |
| 187 | struct group_by_label_key *k = dictionary_get(r->dl[d], t_dfe.name); |
| 188 | if(k) { |
| 189 | buffer_json_add_array_item_array(wb); |
| 190 | void *tt; |
| 191 | dfe_start_read(k->values, tt) { |
| 192 | buffer_json_add_array_item_string(wb, tt_dfe.name); |
| 193 | } |
| 194 | dfe_done(tt); |
| 195 | buffer_json_array_close(wb); |
| 196 | } |
| 197 | else |
| 198 | buffer_json_add_array_item_string(wb, NULL); |
| 199 | } |
| 200 | |
| 201 | buffer_json_array_close(wb); |
| 202 | } |
| 203 | dfe_done(t); |
| 204 | |
| 205 | buffer_json_object_close(wb); // key |
| 206 | } |
| 207 | |
| 208 | void rrd2rrdr_set_timestamps(RRDR *r) { |
| 209 | QUERY_TARGET *qt = r->internal.qt; |
| 210 | |
| 211 | internal_fatal(qt->window.points != r->n, "QUERY: mismatch to the number of points in qt and r"); |
| 212 | |
| 213 | r->view.group = qt->window.group; |
| 214 | r->view.update_every = (int) query_view_update_every(qt); |
| 215 | r->view.before = qt->window.before; |
| 216 | r->view.after = qt->window.after; |
| 217 | |
| 218 | r->time_grouping.points_wanted = qt->window.points; |
| 219 | r->time_grouping.resampling_group = qt->window.resampling_group; |
| 220 | r->time_grouping.resampling_divisor = qt->window.resampling_divisor; |
| 221 | |
| 222 | r->rows = qt->window.points; |
| 223 | |
| 224 | size_t points_wanted = qt->window.points; |
| 225 | time_t after_wanted = qt->window.after; |
| 226 | time_t before_wanted = qt->window.before; (void)before_wanted; |
| 227 | |
| 228 | time_t view_update_every = r->view.update_every; |
| 229 | time_t query_granularity = (time_t)(r->view.update_every / r->view.group); |
| 230 | |
| 231 | size_t rrdr_line = 0; |
| 232 | time_t first_point_end_time = after_wanted + view_update_every - query_granularity; |
| 233 | time_t now_end_time = first_point_end_time; |
| 234 | |
| 235 | while (rrdr_line < points_wanted) { |
| 236 | r->t[rrdr_line++] = now_end_time; |
| 237 | now_end_time += view_update_every; |
| 238 | } |
| 239 | |
| 240 | internal_fatal(r->t[0] != first_point_end_time, "QUERY: wrong first timestamp in the query"); |
| 241 | internal_error(r->t[points_wanted - 1] != before_wanted, |
| 242 | "QUERY: wrong last timestamp in the query, expected %ld, found %ld", |
| 243 | before_wanted, r->t[points_wanted - 1]); |
| 244 | } |