## Sumary When dealing with multiple hosts, it can be hard for customers filter through and figure out which host to query metric data from. This PR aims to make this easier by adding support for host tags so that there is another layer of filtering / grouping applied for hosts. ## Detailed explanation - Adds new UI components to allow adding one or more host tag filter, and a switch to choose between `AND/OR` and `OR` operators when using more than one filter following Zabbix's UI: https://github.com/user-attachments/assets/c971f5eb-7e93-4238-bd6b-902cc657c014 https://github.com/user-attachments/assets/5f8996de-684e-4ffa-b98e-8e205c4fc1df - Modifies the existing `getHosts` function to make a call to the backend with a few additional parameters to `extend` (essentially extract) the host tags for a given selected group. No backend changes were required for this. ## Why To make it easier for customers to query metric data when dealing with multiple hosts. ## How to test - Go to explore or a dashboard and create a Zabbix query where the query type is `Metrics` - The easiest way to test is by selecting `/.*/` for Groups, checking the returned `Hosts` they should all be there - Add a host tag filter and change the keys and operators as well as switching from `AND/OR` to `OR` you should see how the values returned for `Host` changes ## Future work Adding variable support for host tags once this is completed. Fixes: https://github.com/orgs/grafana/projects/457/views/40?pane=issue&itemId=3609900134&issue=grafana%7Coss-big-tent-squad%7C126 and https://github.com/grafana/grafana-zabbix/issues/927 --------- Co-authored-by: ismail simsek <ismailsimsek09@gmail.com>
552 lines
13 KiB
Go
552 lines
13 KiB
Go
package zabbix
|
|
|
|
import (
|
|
"context"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
|
)
|
|
|
|
func (ds *Zabbix) GetHistory(ctx context.Context, items []*Item, timeRange backend.TimeRange) (History, error) {
|
|
history := History{}
|
|
// Zabbix stores history in different tables and `history` param required for query. So in one query it's only
|
|
// possible to get history for items with one type. In order to get history for items with multiple types (numeric unsigned and numeric float),
|
|
// items should be grouped by the `value_type` field.
|
|
groupedItemids := make(map[int][]string, 0)
|
|
for _, item := range items {
|
|
groupedItemids[item.ValueType] = append(groupedItemids[item.ValueType], item.ID)
|
|
}
|
|
|
|
for historyType, itemids := range groupedItemids {
|
|
result, err := ds.getHistory(ctx, itemids, historyType, timeRange)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
history = append(history, result...)
|
|
}
|
|
|
|
return history, nil
|
|
}
|
|
|
|
func (ds *Zabbix) getHistory(ctx context.Context, itemids []string, historyType int, timeRange backend.TimeRange) (History, error) {
|
|
params := ZabbixAPIParams{
|
|
"output": "extend",
|
|
"itemids": itemids,
|
|
"history": historyType,
|
|
"time_from": timeRange.From.Unix(),
|
|
"time_till": timeRange.To.Unix(),
|
|
"sortfield": "clock",
|
|
"sortorder": "ASC",
|
|
}
|
|
|
|
result, err := ds.Request(ctx, &ZabbixAPIRequest{Method: "history.get", Params: params})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var history History
|
|
err = convertTo(result, &history)
|
|
return history, err
|
|
}
|
|
|
|
func (ds *Zabbix) GetTrend(ctx context.Context, items []*Item, timeRange backend.TimeRange) (Trend, error) {
|
|
itemids := make([]string, 0)
|
|
for _, item := range items {
|
|
itemids = append(itemids, item.ID)
|
|
}
|
|
|
|
return ds.getTrend(ctx, itemids, timeRange)
|
|
}
|
|
|
|
func (ds *Zabbix) getTrend(ctx context.Context, itemids []string, timeRange backend.TimeRange) (Trend, error) {
|
|
params := ZabbixAPIParams{
|
|
"output": "extend",
|
|
"itemids": itemids,
|
|
"time_from": timeRange.From.Unix(),
|
|
"time_till": timeRange.To.Unix(),
|
|
"sortfield": "clock",
|
|
"sortorder": "ASC",
|
|
}
|
|
|
|
result, err := ds.Request(ctx, &ZabbixAPIRequest{Method: "trend.get", Params: params})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var trend Trend
|
|
err = convertTo(result, &trend)
|
|
return trend, err
|
|
}
|
|
|
|
func (ds *Zabbix) GetItems(
|
|
ctx context.Context,
|
|
groupFilter string,
|
|
hostFilter string,
|
|
itemTagFilter string,
|
|
itemFilter string,
|
|
itemType string,
|
|
showDisabled bool,
|
|
) ([]*Item, error) {
|
|
var allItems []*Item
|
|
hosts, err := ds.GetHosts(ctx, groupFilter, hostFilter)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(hosts) == 0 {
|
|
return allItems, nil
|
|
}
|
|
|
|
hostids := make([]string, 0)
|
|
for _, host := range hosts {
|
|
hostids = append(hostids, host.ID)
|
|
}
|
|
|
|
if isRegex(itemTagFilter) {
|
|
tags, err := ds.GetItemTags(ctx, groupFilter, hostFilter, itemTagFilter)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var tagStrs []string
|
|
for _, t := range tags {
|
|
tagStrs = append(tagStrs, itemTagToString(t))
|
|
}
|
|
itemTagFilter = strings.Join(tagStrs, ",")
|
|
}
|
|
allItems, err = ds.GetAllItems(ctx, hostids, nil, itemType, showDisabled, itemTagFilter)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return filterItemsByQuery(allItems, itemFilter)
|
|
}
|
|
|
|
func (ds *Zabbix) GetItemsBefore54(
|
|
ctx context.Context,
|
|
groupFilter string,
|
|
hostFilter string,
|
|
appFilter string,
|
|
itemFilter string,
|
|
itemType string,
|
|
showDisabled bool,
|
|
) ([]*Item, error) {
|
|
hosts, err := ds.GetHosts(ctx, groupFilter, hostFilter)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
hostids := make([]string, 0)
|
|
for _, host := range hosts {
|
|
hostids = append(hostids, host.ID)
|
|
}
|
|
|
|
apps, err := ds.GetApps(ctx, groupFilter, hostFilter, appFilter)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
appids := make([]string, 0)
|
|
for _, app := range apps {
|
|
appids = append(appids, app.ID)
|
|
}
|
|
|
|
var allItems []*Item
|
|
if len(appids) > 0 {
|
|
allItems, err = ds.GetAllItems(ctx, nil, appids, itemType, showDisabled, "")
|
|
} else if appFilter == "" && len(hostids) > 0 {
|
|
allItems, err = ds.GetAllItems(ctx, hostids, nil, itemType, showDisabled, "")
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return filterItemsByQuery(allItems, itemFilter)
|
|
}
|
|
|
|
func filterItemsByQuery(items []*Item, filter string) ([]*Item, error) {
|
|
re, err := parseFilter(filter)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
filteredItems := make([]*Item, 0)
|
|
for _, i := range items {
|
|
name := i.Name
|
|
if re != nil {
|
|
match, err := re.MatchString(name)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if match {
|
|
filteredItems = append(filteredItems, i)
|
|
}
|
|
} else if name == filter {
|
|
filteredItems = append(filteredItems, i)
|
|
}
|
|
|
|
}
|
|
|
|
return filteredItems, nil
|
|
}
|
|
|
|
func (ds *Zabbix) GetApps(ctx context.Context, groupFilter string, hostFilter string, appFilter string) ([]Application, error) {
|
|
hosts, err := ds.GetHosts(ctx, groupFilter, hostFilter)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
hostids := make([]string, 0)
|
|
for _, host := range hosts {
|
|
hostids = append(hostids, host.ID)
|
|
}
|
|
allApps, err := ds.GetAllApps(ctx, hostids)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return filterAppsByQuery(allApps, appFilter)
|
|
}
|
|
|
|
func filterAppsByQuery(items []Application, filter string) ([]Application, error) {
|
|
re, err := parseFilter(filter)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
filteredItems := make([]Application, 0)
|
|
for _, i := range items {
|
|
name := i.Name
|
|
if re != nil {
|
|
match, err := re.MatchString(name)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if match {
|
|
filteredItems = append(filteredItems, i)
|
|
}
|
|
} else if name == filter {
|
|
filteredItems = append(filteredItems, i)
|
|
}
|
|
|
|
}
|
|
|
|
return filteredItems, nil
|
|
}
|
|
|
|
func (ds *Zabbix) GetItemTags(ctx context.Context, groupFilter string, hostFilter string, tagFilter string) ([]Tag, error) {
|
|
hosts, err := ds.GetHosts(ctx, groupFilter, hostFilter)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
hostids := make([]string, 0)
|
|
for _, host := range hosts {
|
|
hostids = append(hostids, host.ID)
|
|
}
|
|
|
|
var allItems []*Item
|
|
itemType := "num"
|
|
showDisabled := false
|
|
|
|
allItems, err = ds.GetAllItems(ctx, hostids, nil, itemType, showDisabled, "")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var allTags []Tag
|
|
tagsMap := make(map[string]Tag)
|
|
for _, item := range allItems {
|
|
for _, itemTag := range item.Tags {
|
|
tagStr := itemTagToString(itemTag)
|
|
tagsMap[tagStr] = itemTag
|
|
}
|
|
}
|
|
for _, t := range tagsMap {
|
|
allTags = append(allTags, t)
|
|
}
|
|
|
|
return filterTags(allTags, tagFilter)
|
|
}
|
|
|
|
func filterTags(items []Tag, filter string) ([]Tag, error) {
|
|
re, err := parseFilter(filter)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
filteredItems := make([]Tag, 0)
|
|
for _, i := range items {
|
|
tagStr := itemTagToString(i)
|
|
if re != nil {
|
|
match, err := re.MatchString(tagStr)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if match {
|
|
filteredItems = append(filteredItems, i)
|
|
}
|
|
} else if tagStr == filter {
|
|
filteredItems = append(filteredItems, i)
|
|
}
|
|
|
|
}
|
|
|
|
return filteredItems, nil
|
|
}
|
|
|
|
func (ds *Zabbix) GetHosts(ctx context.Context, groupFilter string, hostFilter string) ([]Host, error) {
|
|
groups, err := ds.GetGroups(ctx, groupFilter)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
groupids := make([]string, 0)
|
|
for _, group := range groups {
|
|
groupids = append(groupids, group.ID)
|
|
}
|
|
allHosts, err := ds.GetAllHosts(ctx, groupids)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return filterHostsByQuery(allHosts, hostFilter)
|
|
}
|
|
|
|
func filterHostsByQuery(items []Host, filter string) ([]Host, error) {
|
|
re, err := parseFilter(filter)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
filteredItems := make([]Host, 0)
|
|
for _, i := range items {
|
|
name := i.Name
|
|
if re != nil {
|
|
match, err := re.MatchString(name)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if match {
|
|
filteredItems = append(filteredItems, i)
|
|
}
|
|
} else if name == filter {
|
|
filteredItems = append(filteredItems, i)
|
|
}
|
|
|
|
}
|
|
|
|
return filteredItems, nil
|
|
}
|
|
|
|
func (ds *Zabbix) GetGroups(ctx context.Context, groupFilter string) ([]Group, error) {
|
|
allGroups, err := ds.GetAllGroups(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return filterGroupsByQuery(allGroups, groupFilter)
|
|
}
|
|
|
|
func filterGroupsByQuery(items []Group, filter string) ([]Group, error) {
|
|
re, err := parseFilter(filter)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
filteredItems := make([]Group, 0)
|
|
for _, i := range items {
|
|
name := i.Name
|
|
if re != nil {
|
|
match, err := re.MatchString(name)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if match {
|
|
filteredItems = append(filteredItems, i)
|
|
}
|
|
} else if name == filter {
|
|
filteredItems = append(filteredItems, i)
|
|
}
|
|
|
|
}
|
|
|
|
return filteredItems, nil
|
|
}
|
|
|
|
func (ds *Zabbix) GetAllItems(ctx context.Context, hostids []string, appids []string, itemtype string, showDisabled bool, itemTagFilter string) ([]*Item, error) {
|
|
params := ZabbixAPIParams{
|
|
"output": []string{"itemid", "name", "key_", "value_type", "hostid", "status", "state", "units", "valuemapid", "delay"},
|
|
"sortfield": "name",
|
|
"webitems": true,
|
|
"filter": map[string]interface{}{},
|
|
"selectHosts": []string{"hostid", "name"},
|
|
"hostids": hostids,
|
|
"applicationids": appids,
|
|
}
|
|
|
|
filter := params["filter"].(map[string]interface{})
|
|
switch itemtype {
|
|
case "num":
|
|
filter["value_type"] = []int{0, 3}
|
|
case "text":
|
|
filter["value_type"] = []int{1, 2, 4}
|
|
}
|
|
|
|
if ds.version >= 54 {
|
|
params["selectTags"] = "extend"
|
|
if len(itemTagFilter) > 0 {
|
|
allTags := strings.Split(itemTagFilter, ",")
|
|
tagsParams := make([]map[string]string, 0)
|
|
for _, tagStr := range allTags {
|
|
tag := parseItemTag(tagStr)
|
|
tagParam := map[string]string{
|
|
"tag": tag.Tag,
|
|
"value": tag.Value,
|
|
"operator": "1",
|
|
}
|
|
tagsParams = append(tagsParams, tagParam)
|
|
}
|
|
// tags order should be handled for higher cache hit ratio
|
|
sort.Slice(tagsParams, func(i, j int) bool {
|
|
if tagsParams[i]["tag"] != tagsParams[j]["tag"] {
|
|
return tagsParams[i]["tag"] < tagsParams[j]["tag"]
|
|
}
|
|
return tagsParams[i]["value"] < tagsParams[j]["value"]
|
|
})
|
|
params["tags"] = tagsParams
|
|
params["evaltype"] = 2
|
|
}
|
|
}
|
|
|
|
if !showDisabled {
|
|
params["monitored"] = true
|
|
}
|
|
|
|
result, err := ds.Request(ctx, &ZabbixAPIRequest{Method: "item.get", Params: params})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var items []*Item
|
|
err = convertTo(result, &items)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
items = expandItems(items)
|
|
return items, err
|
|
}
|
|
|
|
func (ds *Zabbix) GetItemsByIDs(ctx context.Context, itemids []string) ([]*Item, error) {
|
|
params := ZabbixAPIParams{
|
|
"itemids": itemids,
|
|
"output": []string{"itemid", "name", "key_", "value_type", "hostid", "status", "state", "units", "valuemapid", "delay"},
|
|
"webitems": true,
|
|
"selectHosts": []string{"hostid", "name"},
|
|
}
|
|
|
|
result, err := ds.Request(ctx, &ZabbixAPIRequest{Method: "item.get", Params: params})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var items []*Item
|
|
err = convertTo(result, &items)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
items = expandItems(items)
|
|
return items, err
|
|
}
|
|
|
|
func (ds *Zabbix) GetAllApps(ctx context.Context, hostids []string) ([]Application, error) {
|
|
params := ZabbixAPIParams{
|
|
"output": "extend",
|
|
"hostids": hostids,
|
|
}
|
|
|
|
result, err := ds.Request(ctx, &ZabbixAPIRequest{Method: "application.get", Params: params})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var apps []Application
|
|
err = convertTo(result, &apps)
|
|
return apps, err
|
|
}
|
|
|
|
func (ds *Zabbix) GetAllHosts(ctx context.Context, groupids []string) ([]Host, error) {
|
|
params := ZabbixAPIParams{
|
|
"output": []string{"hostid", "name", "host"},
|
|
"sortfield": "name",
|
|
"groupids": groupids,
|
|
}
|
|
|
|
result, err := ds.Request(ctx, &ZabbixAPIRequest{Method: "host.get", Params: params})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var hosts []Host
|
|
err = convertTo(result, &hosts)
|
|
return hosts, err
|
|
}
|
|
|
|
func (ds *Zabbix) GetAllGroups(ctx context.Context) ([]Group, error) {
|
|
params := ZabbixAPIParams{
|
|
"output": []string{"name", "groupid"},
|
|
"sortfield": "name",
|
|
"with_hosts": true,
|
|
}
|
|
|
|
result, err := ds.Request(ctx, &ZabbixAPIRequest{Method: "hostgroup.get", Params: params})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var groups []Group
|
|
err = convertTo(result, &groups)
|
|
return groups, err
|
|
}
|
|
|
|
func (ds *Zabbix) GetValueMappings(ctx context.Context) ([]ValueMap, error) {
|
|
params := ZabbixAPIParams{
|
|
"output": "extend",
|
|
"selectMappings": "extend",
|
|
}
|
|
|
|
result, err := ds.Request(ctx, &ZabbixAPIRequest{Method: "valuemap.get", Params: params})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var valuemaps []ValueMap
|
|
err = convertTo(result, &valuemaps)
|
|
return valuemaps, err
|
|
}
|
|
|
|
func (ds *Zabbix) GetFullVersion(ctx context.Context) (string, error) {
|
|
result, err := ds.request(ctx, "apiinfo.version", ZabbixAPIParams{})
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
var version string
|
|
err = convertTo(result, &version)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return version, nil
|
|
}
|
|
|
|
func (ds *Zabbix) GetVersion(ctx context.Context) (int, error) {
|
|
fullStringVersion, err := ds.GetFullVersion(ctx)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
version := strings.Replace(fullStringVersion[0:3], ".", "", 1)
|
|
versionNum, err := strconv.Atoi(version)
|
|
return versionNum, err
|
|
}
|