1 /*
2 ** Zabbix
3 ** Copyright (C) 2001-2021 Zabbix SIA
4 **
5 ** This program is free software; you can redistribute it and/or modify
6 ** it under the terms of the GNU General Public License as published by
7 ** the Free Software Foundation; either version 2 of the License, or
8 ** (at your option) any later version.
9 **
10 ** This program is distributed in the hope that it will be useful,
11 ** but WITHOUT ANY WARRANTY; without even the implied warranty of
12 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 ** GNU General Public License for more details.
14 **
15 ** You should have received a copy of the GNU General Public License
16 ** along with this program; if not, write to the Free Software
17 ** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 **/
19
20 #include "common.h"
21 #include "log.h"
22 #include "valuecache.h"
23 #include "dbcache.h"
24
25 #include "checks_aggregate.h"
26
27 #define ZBX_VALUE_FUNC_MIN 0
28 #define ZBX_VALUE_FUNC_AVG 1
29 #define ZBX_VALUE_FUNC_MAX 2
30 #define ZBX_VALUE_FUNC_SUM 3
31 #define ZBX_VALUE_FUNC_COUNT 4
32 #define ZBX_VALUE_FUNC_LAST 5
33
34 /******************************************************************************
35 * *
36 * Function: evaluate_history_func_min *
37 * *
38 * Purpose: calculate minimum value from the history value vector *
39 * *
40 * Parameters: values - [IN] a vector containing history values *
41 * value_type - [IN] the type of values. Only float/uint64 *
42 * values are supported. *
43 * result - [OUT] the resulting value *
44 * *
45 ******************************************************************************/
evaluate_history_func_min(zbx_vector_history_record_t * values,int value_type,history_value_t * result)46 static void evaluate_history_func_min(zbx_vector_history_record_t *values, int value_type, history_value_t *result)
47 {
48 int i;
49
50 *result = values->values[0].value;
51
52 if (ITEM_VALUE_TYPE_UINT64 == value_type)
53 {
54 for (i = 1; i < values->values_num; i++)
55 if (values->values[i].value.ui64 < result->ui64)
56 result->ui64 = values->values[i].value.ui64;
57 }
58 else
59 {
60 for (i = 1; i < values->values_num; i++)
61 if (values->values[i].value.dbl < result->dbl)
62 result->dbl = values->values[i].value.dbl;
63 }
64 }
65
66 /******************************************************************************
67 * *
68 * Function: evaluate_history_func_max *
69 * *
70 * Purpose: calculate maximum value from the history value vector *
71 * *
72 * Parameters: values - [IN] a vector containing history values *
73 * value_type - [IN] the type of values. Only float/uint64 *
74 * values are supported. *
75 * result - [OUT] the resulting value *
76 * *
77 ******************************************************************************/
evaluate_history_func_max(zbx_vector_history_record_t * values,int value_type,history_value_t * result)78 static void evaluate_history_func_max(zbx_vector_history_record_t *values, int value_type, history_value_t *result)
79 {
80 int i;
81
82 *result = values->values[0].value;
83
84 if (ITEM_VALUE_TYPE_UINT64 == value_type)
85 {
86 for (i = 1; i < values->values_num; i++)
87 if (values->values[i].value.ui64 > result->ui64)
88 result->ui64 = values->values[i].value.ui64;
89 }
90 else
91 {
92 for (i = 1; i < values->values_num; i++)
93 if (values->values[i].value.dbl > result->dbl)
94 result->dbl = values->values[i].value.dbl;
95 }
96 }
97
98 /******************************************************************************
99 * *
100 * Function: evaluate_history_func_sum *
101 * *
102 * Purpose: calculate sum of values from the history value vector *
103 * *
104 * Parameters: values - [IN] a vector containing history values *
105 * value_type - [IN] the type of values. Only float/uint64 *
106 * values are supported. *
107 * result - [OUT] the resulting value *
108 * *
109 ******************************************************************************/
evaluate_history_func_sum(zbx_vector_history_record_t * values,int value_type,history_value_t * result)110 static void evaluate_history_func_sum(zbx_vector_history_record_t *values, int value_type, history_value_t *result)
111 {
112 int i;
113
114 if (ITEM_VALUE_TYPE_UINT64 == value_type)
115 {
116 result->ui64 = 0;
117 for (i = 0; i < values->values_num; i++)
118 result->ui64 += values->values[i].value.ui64;
119 }
120 else
121 {
122 result->dbl = 0;
123 for (i = 0; i < values->values_num; i++)
124 result->dbl += values->values[i].value.dbl;
125 }
126 }
127
128 /******************************************************************************
129 * *
130 * Function: evaluate_history_func_avg *
131 * *
132 * Purpose: calculate average value of values from the history value vector *
133 * *
134 * Parameters: values - [IN] a vector containing history values *
135 * value_type - [IN] the type of values. Only float/uint64 *
136 * values are supported. *
137 * result - [OUT] the resulting value *
138 * *
139 ******************************************************************************/
evaluate_history_func_avg(zbx_vector_history_record_t * values,int value_type,history_value_t * result)140 static void evaluate_history_func_avg(zbx_vector_history_record_t *values, int value_type, history_value_t *result)
141 {
142 evaluate_history_func_sum(values, value_type, result);
143
144 if (ITEM_VALUE_TYPE_UINT64 == value_type)
145 result->ui64 /= values->values_num;
146 else
147 result->dbl /= values->values_num;
148 }
149
150 /******************************************************************************
151 * *
152 * Function: evaluate_history_func_count *
153 * *
154 * Purpose: calculate number of values in value vector *
155 * *
156 * Parameters: values - [IN] a vector containing history values *
157 * value_type - [IN] the type of values. Only float/uint64 *
158 * values are supported. *
159 * result - [OUT] the resulting value *
160 * *
161 ******************************************************************************/
evaluate_history_func_count(zbx_vector_history_record_t * values,int value_type,history_value_t * result)162 static void evaluate_history_func_count(zbx_vector_history_record_t *values, int value_type,
163 history_value_t *result)
164 {
165 if (ITEM_VALUE_TYPE_UINT64 == value_type)
166 result->ui64 = values->values_num;
167 else
168 result->dbl = values->values_num;
169 }
170
171 /******************************************************************************
172 * *
173 * Function: evaluate_history_func_last *
174 * *
175 * Purpose: calculate the last (newest) value in value vector *
176 * *
177 * Parameters: values - [IN] a vector containing history values *
178 * result - [OUT] the resulting value *
179 * *
180 ******************************************************************************/
evaluate_history_func_last(zbx_vector_history_record_t * values,history_value_t * result)181 static void evaluate_history_func_last(zbx_vector_history_record_t *values, history_value_t *result)
182 {
183 *result = values->values[0].value;
184 }
185
186 /******************************************************************************
187 * *
188 * Function: evaluate_history_func *
189 * *
190 * Purpose: calculate function with values from value vector *
191 * *
192 * Parameters: values - [IN] a vector containing history values *
193 * value_type - [IN] the type of values. Only float/uint64 *
194 * values are supported. *
195 * func - [IN] the function to calculate. Only *
196 * ZBX_VALUE_FUNC_MIN, ZBX_VALUE_FUNC_AVG, *
197 * ZBX_VALUE_FUNC_MAX, ZBX_VALUE_FUNC_SUM, *
198 * ZBX_VALUE_FUNC_COUNT, ZBX_VALUE_FUNC_LAST *
199 * functions are supported. *
200 * result - [OUT] the resulting value *
201 * *
202 ******************************************************************************/
evaluate_history_func(zbx_vector_history_record_t * values,int value_type,int func,history_value_t * result)203 static void evaluate_history_func(zbx_vector_history_record_t *values, int value_type, int func,
204 history_value_t *result)
205 {
206 switch (func)
207 {
208 case ZBX_VALUE_FUNC_MIN:
209 evaluate_history_func_min(values, value_type, result);
210 break;
211 case ZBX_VALUE_FUNC_AVG:
212 evaluate_history_func_avg(values, value_type, result);
213 break;
214 case ZBX_VALUE_FUNC_MAX:
215 evaluate_history_func_max(values, value_type, result);
216 break;
217 case ZBX_VALUE_FUNC_SUM:
218 evaluate_history_func_sum(values, value_type, result);
219 break;
220 case ZBX_VALUE_FUNC_COUNT:
221 evaluate_history_func_count(values, value_type, result);
222 break;
223 case ZBX_VALUE_FUNC_LAST:
224 evaluate_history_func_last(values, result);
225 break;
226 }
227 }
228
229 /******************************************************************************
230 * *
231 * Function: quote_string *
232 * *
233 * Purpose: quotes string by enclosing it in double quotes and escaping *
234 * double quotes inside string with '\'. *
235 * *
236 * Parameters: str - [IN/OUT] the string to quote *
237 * sz_str - [IN] the string length *
238 * *
239 * Comments: The '\' character itself is not quoted. As the result if string *
240 * ends with '\' it can be quoted (for example for error messages), *
241 * but it's impossible to unquote it. *
242 * *
243 ******************************************************************************/
quote_string(char ** str,size_t sz_src)244 static void quote_string(char **str, size_t sz_src)
245 {
246 size_t sz_dst;
247
248 sz_dst = zbx_get_escape_string_len(*str, "\"") + 3;
249
250 *str = (char *)zbx_realloc(*str, sz_dst);
251
252 (*str)[--sz_dst] = '\0';
253 (*str)[--sz_dst] = '"';
254
255 while (0 < sz_src)
256 {
257 (*str)[--sz_dst] = (*str)[--sz_src];
258
259 if ('"' == (*str)[sz_src])
260 (*str)[--sz_dst] = '\\';
261 }
262 (*str)[--sz_dst] = '"';
263 }
264
265 /******************************************************************************
266 * *
267 * Function: aggregate_quote_groups *
268 * *
269 * Purpose: quotes the individual groups in the list if necessary *
270 * *
271 ******************************************************************************/
aggregate_quote_groups(char ** str,size_t * str_alloc,size_t * str_offset,zbx_vector_str_t * groups)272 static void aggregate_quote_groups(char **str, size_t *str_alloc, size_t *str_offset, zbx_vector_str_t *groups)
273 {
274 int i;
275 char *group, *separator = "";
276
277 for (i = 1; i <= groups->values_num; i++)
278 {
279 group = zbx_strdup(NULL, groups->values[i - 1]);
280 zbx_strcpy_alloc(str, str_alloc, str_offset, separator);
281 separator = (char *)", ";
282
283 quote_string(&group, strlen(group));
284 zbx_strcpy_alloc(str, str_alloc, str_offset, group);
285 zbx_free(group);
286 }
287 }
288
289 /******************************************************************************
290 * *
291 * Function: aggregate_get_items *
292 * *
293 * Purpose: get array of items specified by key for selected groups *
294 * (including nested groups) *
295 * *
296 * Parameters: itemids - [OUT] list of item ids *
297 * groups - [IN] list of host groups *
298 * itemkey - [IN] item key to aggregate *
299 * error - [OUT] the error message *
300 * *
301 * Return value: SUCCEED - item identifier(s) were retrieved successfully *
302 * FAIL - no items matching the specified groups or keys *
303 * *
304 ******************************************************************************/
aggregate_get_items(zbx_vector_uint64_t * itemids,zbx_vector_str_t * groups,const char * itemkey,char ** error)305 static int aggregate_get_items(zbx_vector_uint64_t *itemids, zbx_vector_str_t *groups, const char *itemkey,
306 char **error)
307 {
308 const char *__function_name = "aggregate_get_items";
309
310 char *esc;
311 DB_RESULT result;
312 DB_ROW row;
313 zbx_uint64_t itemid;
314 char *sql = NULL;
315 size_t sql_alloc = ZBX_KIBIBYTE, sql_offset = 0, error_alloc = 0, error_offset = 0;
316 int ret = FAIL;
317 zbx_vector_uint64_t groupids;
318
319 zabbix_log(LOG_LEVEL_DEBUG, "In %s() itemkey:'%s'", __function_name, itemkey);
320
321 zbx_vector_uint64_create(&groupids);
322 zbx_dc_get_nested_hostgroupids_by_names(groups, &groupids);
323
324 if (0 == groupids.values_num)
325 {
326 zbx_strcpy_alloc(error, &error_alloc, &error_offset, "None of the groups in list ");
327 aggregate_quote_groups(error, &error_alloc, &error_offset, groups);
328 zbx_strcpy_alloc(error, &error_alloc, &error_offset, " is correct.");
329 goto out;
330 }
331
332 sql = (char *)zbx_malloc(sql, sql_alloc);
333 esc = DBdyn_escape_string(itemkey);
334
335 zbx_snprintf_alloc(&sql, &sql_alloc, &sql_offset,
336 "select distinct i.itemid"
337 " from items i,hosts h,hosts_groups hg"
338 " where i.hostid=h.hostid"
339 " and h.hostid=hg.hostid"
340 " and i.key_='%s'"
341 " and i.status=%d"
342 " and i.state=%d"
343 " and h.status=%d"
344 " and",
345 esc, ITEM_STATUS_ACTIVE, ITEM_STATE_NORMAL, HOST_STATUS_MONITORED);
346
347 zbx_free(esc);
348
349 DBadd_condition_alloc(&sql, &sql_alloc, &sql_offset, "hg.groupid", groupids.values, groupids.values_num);
350 result = DBselect("%s", sql);
351 zbx_free(sql);
352
353 while (NULL != (row = DBfetch(result)))
354 {
355 ZBX_STR2UINT64(itemid, row[0]);
356 zbx_vector_uint64_append(itemids, itemid);
357 }
358 DBfree_result(result);
359
360 if (0 == itemids->values_num)
361 {
362 zbx_snprintf_alloc(error, &error_alloc, &error_offset, "No items for key \"%s\" in group(s) ", itemkey);
363 aggregate_quote_groups(error, &error_alloc, &error_offset, groups);
364 zbx_chrcpy_alloc(error, &error_alloc, &error_offset, '.');
365 goto out;
366 }
367
368 zbx_vector_uint64_sort(itemids, ZBX_DEFAULT_UINT64_COMPARE_FUNC);
369
370 ret = SUCCEED;
371
372 out:
373 zbx_vector_uint64_destroy(&groupids);
374
375 zabbix_log(LOG_LEVEL_DEBUG, "End of %s()", __function_name);
376
377 return ret;
378 }
379
380 /******************************************************************************
381 * *
382 * Function: evaluate_aggregate *
383 * *
384 * Parameters: item - [IN] aggregated item *
385 * grp_func - [IN] one of ZBX_GRP_FUNC_* *
386 * groups - [IN] list of host groups *
387 * itemkey - [IN] item key to aggregate *
388 * item_func - [IN] one of ZBX_VALUE_FUNC_* *
389 * param - [IN] item_func parameter (optional) *
390 * *
391 * Return value: SUCCEED - aggregate item evaluated successfully *
392 * FAIL - otherwise *
393 * *
394 ******************************************************************************/
evaluate_aggregate(DC_ITEM * item,AGENT_RESULT * res,int grp_func,zbx_vector_str_t * groups,const char * itemkey,int item_func,const char * param)395 static int evaluate_aggregate(DC_ITEM *item, AGENT_RESULT *res, int grp_func, zbx_vector_str_t *groups,
396 const char *itemkey, int item_func, const char *param)
397 {
398 const char *__function_name = "evaluate_aggregate";
399 zbx_vector_uint64_t itemids;
400 history_value_t value, item_result;
401 zbx_history_record_t group_value;
402 int ret = FAIL, *errcodes = NULL, i, count, seconds;
403 DC_ITEM *items = NULL;
404 zbx_vector_history_record_t values, group_values;
405 char *error = NULL;
406 zbx_timespec_t ts;
407
408 zabbix_log(LOG_LEVEL_DEBUG, "In %s() grp_func:%d itemkey:'%s' item_func:%d param:'%s'",
409 __function_name, grp_func, itemkey, item_func, ZBX_NULL2STR(param));
410
411 zbx_timespec(&ts);
412 zbx_vector_uint64_create(&itemids);
413
414 if (FAIL == aggregate_get_items(&itemids, groups, itemkey, &error))
415 {
416 SET_MSG_RESULT(res, error);
417 goto clean1;
418 }
419
420 memset(&value, 0, sizeof(value));
421 zbx_history_record_vector_create(&group_values);
422
423 items = (DC_ITEM *)zbx_malloc(items, sizeof(DC_ITEM) * itemids.values_num);
424 errcodes = (int *)zbx_malloc(errcodes, sizeof(int) * itemids.values_num);
425
426 DCconfig_get_items_by_itemids(items, itemids.values, errcodes, itemids.values_num);
427
428 if (ZBX_VALUE_FUNC_LAST == item_func)
429 {
430 count = 1;
431 seconds = 0;
432 }
433 else
434 {
435 if (FAIL == is_time_suffix(param, &seconds, ZBX_LENGTH_UNLIMITED))
436 {
437 SET_MSG_RESULT(res, zbx_strdup(NULL, "Invalid fourth parameter."));
438 goto clean2;
439 }
440 count = 0;
441 }
442
443 for (i = 0; i < itemids.values_num; i++)
444 {
445 if (SUCCEED != errcodes[i])
446 continue;
447
448 if (ITEM_STATUS_ACTIVE != items[i].status)
449 continue;
450
451 if (HOST_STATUS_MONITORED != items[i].host.status)
452 continue;
453
454 if (ITEM_VALUE_TYPE_FLOAT != items[i].value_type && ITEM_VALUE_TYPE_UINT64 != items[i].value_type)
455 continue;
456
457 zbx_history_record_vector_create(&values);
458
459 if (SUCCEED == zbx_vc_get_values(items[i].itemid, items[i].value_type, &values, seconds, count, &ts) &&
460 0 < values.values_num)
461 {
462 evaluate_history_func(&values, items[i].value_type, item_func, &item_result);
463
464 if (item->value_type == items[i].value_type)
465 group_value.value = item_result;
466 else
467 {
468 if (ITEM_VALUE_TYPE_UINT64 == item->value_type)
469 group_value.value.ui64 = (zbx_uint64_t)item_result.dbl;
470 else
471 group_value.value.dbl = (double)item_result.ui64;
472 }
473
474 zbx_vector_history_record_append_ptr(&group_values, &group_value);
475 }
476
477 zbx_history_record_vector_destroy(&values, items[i].value_type);
478 }
479
480 if (0 == group_values.values_num)
481 {
482 char *tmp = NULL;
483 size_t tmp_alloc = 0, tmp_offset = 0;
484
485 aggregate_quote_groups(&tmp, &tmp_alloc, &tmp_offset, groups);
486 SET_MSG_RESULT(res, zbx_dsprintf(NULL, "No values for key \"%s\" in group(s) %s.", itemkey, tmp));
487 zbx_free(tmp);
488
489 goto clean2;
490 }
491
492 evaluate_history_func(&group_values, item->value_type, grp_func, &value);
493
494 if (ITEM_VALUE_TYPE_FLOAT == item->value_type)
495 SET_DBL_RESULT(res, value.dbl);
496 else
497 SET_UI64_RESULT(res, value.ui64);
498
499 ret = SUCCEED;
500 clean2:
501 DCconfig_clean_items(items, errcodes, itemids.values_num);
502
503 zbx_free(errcodes);
504 zbx_free(items);
505 zbx_history_record_vector_destroy(&group_values, item->value_type);
506 clean1:
507 zbx_vector_uint64_destroy(&itemids);
508
509 zabbix_log(LOG_LEVEL_DEBUG, "End of %s():%s", __function_name, zbx_result_string(ret));
510
511 return ret;
512 }
513
514 /******************************************************************************
515 * *
516 * Function: get_value_aggregate *
517 * *
518 * Purpose: retrieve data from Zabbix server (aggregate items) *
519 * *
520 * Parameters: item - item we are interested in *
521 * *
522 * Return value: SUCCEED - data successfully retrieved and stored in result *
523 * and result_str (as string) *
524 * NOTSUPPORTED - requested item is not supported *
525 * *
526 * Author: Alexei Vladishev *
527 * *
528 ******************************************************************************/
get_value_aggregate(DC_ITEM * item,AGENT_RESULT * result)529 int get_value_aggregate(DC_ITEM *item, AGENT_RESULT *result)
530 {
531 const char *__function_name = "get_value_aggregate";
532
533 AGENT_REQUEST request;
534 int ret = NOTSUPPORTED;
535 const char *tmp, *groups, *itemkey, *funcp = NULL;
536 int grp_func, item_func, params_num;
537 zbx_vector_str_t group_names;
538
539 zabbix_log(LOG_LEVEL_DEBUG, "In %s() key:'%s'", __function_name, item->key_orig);
540
541 init_request(&request);
542 zbx_vector_str_create(&group_names);
543
544 if (ITEM_VALUE_TYPE_FLOAT != item->value_type && ITEM_VALUE_TYPE_UINT64 != item->value_type)
545 {
546 SET_MSG_RESULT(result, zbx_strdup(NULL, "Value type must be Numeric for aggregate items"));
547 goto out;
548 }
549
550 if (SUCCEED != parse_item_key(item->key, &request))
551 {
552 SET_MSG_RESULT(result, zbx_strdup(NULL, "Invalid item key format."));
553 goto out;
554 }
555
556 if (0 == strcmp(get_rkey(&request), "grpmin"))
557 {
558 grp_func = ZBX_VALUE_FUNC_MIN;
559 }
560 else if (0 == strcmp(get_rkey(&request), "grpavg"))
561 {
562 grp_func = ZBX_VALUE_FUNC_AVG;
563 }
564 else if (0 == strcmp(get_rkey(&request), "grpmax"))
565 {
566 grp_func = ZBX_VALUE_FUNC_MAX;
567 }
568 else if (0 == strcmp(get_rkey(&request), "grpsum"))
569 {
570 grp_func = ZBX_VALUE_FUNC_SUM;
571 }
572 else
573 {
574 SET_MSG_RESULT(result, zbx_strdup(NULL, "Invalid item key."));
575 goto out;
576 }
577
578 params_num = get_rparams_num(&request);
579
580 if (3 > params_num || params_num > 4)
581 {
582 SET_MSG_RESULT(result, zbx_strdup(NULL, "Invalid number of parameters."));
583 goto out;
584 }
585
586 groups = get_rparam(&request, 0);
587 itemkey = get_rparam(&request, 1);
588 tmp = get_rparam(&request, 2);
589
590 if (REQUEST_PARAMETER_TYPE_ARRAY == get_rparam_type(&request, 0))
591 {
592 int i, groups_num;
593 char *group;
594 zbx_request_parameter_type_t type;
595
596 groups_num = num_param(groups);
597
598 for (i = 1; i <= groups_num; i++)
599 {
600 if (NULL == (group = get_param_dyn(groups, i, &type)))
601 continue;
602
603 zbx_vector_str_append(&group_names, group);
604
605 if (REQUEST_PARAMETER_TYPE_STRING != type)
606 {
607 SET_MSG_RESULT(result, zbx_dsprintf(NULL, "Invalid host group list: %s", groups));
608 goto out;
609 }
610 }
611 }
612 else
613 zbx_vector_str_append(&group_names, zbx_strdup(NULL, groups));
614
615 zabbix_log(LOG_LEVEL_DEBUG, "Host groups: '%s', Item key: '%s', Item function: '%s'", groups, itemkey, tmp);
616
617 if (0 == strcmp(tmp, "min"))
618 item_func = ZBX_VALUE_FUNC_MIN;
619 else if (0 == strcmp(tmp, "avg"))
620 item_func = ZBX_VALUE_FUNC_AVG;
621 else if (0 == strcmp(tmp, "max"))
622 item_func = ZBX_VALUE_FUNC_MAX;
623 else if (0 == strcmp(tmp, "sum"))
624 item_func = ZBX_VALUE_FUNC_SUM;
625 else if (0 == strcmp(tmp, "count"))
626 item_func = ZBX_VALUE_FUNC_COUNT;
627 else if (0 == strcmp(tmp, "last"))
628 item_func = ZBX_VALUE_FUNC_LAST;
629 else
630 {
631 SET_MSG_RESULT(result, zbx_strdup(NULL, "Invalid third parameter."));
632 goto out;
633 }
634
635 if (4 == params_num)
636 {
637 funcp = get_rparam(&request, 3);
638 }
639 else if (3 == params_num && ZBX_VALUE_FUNC_LAST != item_func)
640 {
641 SET_MSG_RESULT(result, zbx_strdup(NULL, "Invalid number of parameters."));
642 goto out;
643 }
644
645 if (SUCCEED != evaluate_aggregate(item, result, grp_func, &group_names, itemkey, item_func, funcp))
646 goto out;
647
648 ret = SUCCEED;
649 out:
650 zbx_vector_str_clear_ext(&group_names, zbx_str_free);
651 zbx_vector_str_destroy(&group_names);
652 free_request(&request);
653
654 zabbix_log(LOG_LEVEL_DEBUG, "End of %s():%s", __function_name, zbx_result_string(ret));
655
656 return ret;
657 }
658