diff --git a/pkg/datasource.go b/pkg/datasource.go index 2ab7845..ee2e0c8 100644 --- a/pkg/datasource.go +++ b/pkg/datasource.go @@ -2,10 +2,16 @@ package main import ( "context" + "crypto/tls" "encoding/json" "errors" "fmt" + "net" + "net/http" + "net/url" + "time" + "github.com/alexanderzobnin/grafana-zabbix/pkg/gtime" simplejson "github.com/bitly/go-simplejson" "github.com/grafana/grafana_plugin_model/go/datasource" hclog "github.com/hashicorp/go-hclog" @@ -28,6 +34,18 @@ type ZabbixDatasource struct { logger log.Logger } +// ZabbixDatasourceInstance stores state about a specific datasource and provides methods to make +// requests to the Zabbix API +type ZabbixDatasourceInstance struct { + url *url.URL + authToken string + dsInfo *backend.DataSourceInstanceSettings + Settings *ZabbixDatasourceSettings + queryCache *Cache + httpClient *http.Client + logger log.Logger +} + // CheckHealth checks if the plugin is running properly func (ds *ZabbixDatasource) CheckHealth(ctx context.Context, req *backend.CheckHealthRequest) (*backend.CheckHealthResult, error) { res := &backend.CheckHealthResult{} @@ -92,6 +110,81 @@ func (ds *ZabbixDatasource) NewZabbixDatasource(dsInfo *backend.DataSourceInstan return dsInstance, nil } +// newZabbixDatasource returns an initialized ZabbixDatasource +func newZabbixDatasource(dsInfo *backend.DataSourceInstanceSettings) (*ZabbixDatasourceInstance, error) { + zabbixURLStr := dsInfo.URL + zabbixURL, err := url.Parse(zabbixURLStr) + if err != nil { + return nil, err + } + + zabbixSettings, err := readZabbixSettings(dsInfo) + if err != nil { + return nil, err + } + + return &ZabbixDatasourceInstance{ + url: zabbixURL, + dsInfo: dsInfo, + Settings: zabbixSettings, + queryCache: NewCache(10*time.Minute, 10*time.Minute), + httpClient: &http.Client{ + Transport: &http.Transport{ + TLSClientConfig: &tls.Config{ + Renegotiation: tls.RenegotiateFreelyAsClient, + }, + Proxy: http.ProxyFromEnvironment, + Dial: (&net.Dialer{ + Timeout: 30 * time.Second, + KeepAlive: 30 * time.Second, + }).Dial, + TLSHandshakeTimeout: 10 * time.Second, + ExpectContinueTimeout: 1 * time.Second, + MaxIdleConns: 100, + IdleConnTimeout: 90 * time.Second, + }, + Timeout: time.Duration(time.Second * 30), + }, + }, nil +} + +func readZabbixSettings(dsInstanceSettings *backend.DataSourceInstanceSettings) (*ZabbixDatasourceSettings, error) { + zabbixSettingsDTO := &ZabbixDatasourceSettingsDTO{ + TrendsFrom: "7d", + TrendsRange: "4d", + CacheTTL: "1h", + } + + err := json.Unmarshal(dsInstanceSettings.JSONData, &zabbixSettingsDTO) + if err != nil { + return nil, err + } + + trendsFrom, err := gtime.ParseInterval(zabbixSettingsDTO.TrendsFrom) + if err != nil { + return nil, err + } + + trendsRange, err := gtime.ParseInterval(zabbixSettingsDTO.TrendsRange) + if err != nil { + return nil, err + } + + cacheTTL, err := gtime.ParseInterval(zabbixSettingsDTO.CacheTTL) + if err != nil { + return nil, err + } + + zabbixSettings := &ZabbixDatasourceSettings{ + Trends: zabbixSettingsDTO.Trends, + TrendsFrom: trendsFrom, + TrendsRange: trendsRange, + CacheTTL: cacheTTL, + } + + return zabbixSettings, nil +} + // Query receives requests from the Grafana backend. Requests are filtered by query type and sent to the // applicable ZabbixDatasource. // func (p *ZabbixPlugin) Query(ctx context.Context, tsdbReq *datasource.DatasourceRequest) (resp *datasource.DatasourceResponse, err error) { @@ -135,6 +228,7 @@ func (ds *ZabbixDatasource) GetDatasource(pluginContext backend.PluginContext) ( dsInstance, err := ds.NewZabbixDatasource(pluginContext.DataSourceInstanceSettings) if err != nil { + ds.logger.Error("Error initializing datasource", "error", err) return nil, err } diff --git a/pkg/gtime/gtime.go b/pkg/gtime/gtime.go new file mode 100644 index 0000000..e1e00d9 --- /dev/null +++ b/pkg/gtime/gtime.go @@ -0,0 +1,36 @@ +package gtime + +import ( + "fmt" + "regexp" + "strconv" + "time" +) + +var dateUnitPattern = regexp.MustCompile(`^(\d+)([dwMy])$`) + +// ParseInterval parses an interval with support for all units that Grafana uses. +func ParseInterval(interval string) (time.Duration, error) { + result := dateUnitPattern.FindSubmatch([]byte(interval)) + + if len(result) != 3 { + return time.ParseDuration(interval) + } + + num, _ := strconv.Atoi(string(result[1])) + period := string(result[2]) + now := time.Now() + + switch period { + case "d": + return now.Sub(now.AddDate(0, 0, -num)), nil + case "w": + return now.Sub(now.AddDate(0, 0, -num*7)), nil + case "M": + return now.Sub(now.AddDate(0, -num, 0)), nil + case "y": + return now.Sub(now.AddDate(-num, 0, 0)), nil + } + + return 0, fmt.Errorf("ParseInterval: invalid duration %q", interval) +} diff --git a/pkg/models.go b/pkg/models.go index ed79132..f8b7aa1 100644 --- a/pkg/models.go +++ b/pkg/models.go @@ -3,10 +3,31 @@ package main import ( "encoding/json" "fmt" + "time" "github.com/grafana/grafana-plugin-sdk-go/backend" ) +// ZabbixDatasourceSettingsDTO model +type ZabbixDatasourceSettingsDTO struct { + Trends bool `json:"trends"` + TrendsFrom string `json:"trendsFrom"` + TrendsRange string `json:"trendsRange"` + CacheTTL string `json:"cacheTTL"` + + DisableReadOnlyUsersAck bool `json:"disableReadOnlyUsersAck"` +} + +// ZabbixDatasourceSettings model +type ZabbixDatasourceSettings struct { + Trends bool + TrendsFrom time.Duration + TrendsRange time.Duration + CacheTTL time.Duration + + DisableReadOnlyUsersAck bool `json:"disableReadOnlyUsersAck"` +} + type ZabbixAPIResourceRequest struct { DatasourceId int64 `json:"datasourceId"` Method string `json:"method"` diff --git a/pkg/zabbix_api.go b/pkg/zabbix_api.go index d89650c..cf05ce7 100644 --- a/pkg/zabbix_api.go +++ b/pkg/zabbix_api.go @@ -34,7 +34,7 @@ func (dsInstance *ZabbixDatasourceInstance) ZabbixAPIQuery(ctx context.Context, if !queryExistInCache { result, err = dsInstance.ZabbixRequest(ctx, apiReq.Method, apiReq.Params) - dsInstance.logger.Debug("ZabbixAPIQuery", "result", result) + // dsInstance.logger.Debug("ZabbixAPIQuery", "result", result) dsInstance.queryCache.Set(HashString(apiReq.String()), result) if err != nil { dsInstance.logger.Debug("ZabbixAPIQuery", "error", err) @@ -386,7 +386,8 @@ func (ds *ZabbixDatasourceInstance) getConsolidateBy(query *QueryModel) string { func (ds *ZabbixDatasourceInstance) getHistotyOrTrend(ctx context.Context, query *QueryModel, items zabbix.Items) (zabbix.History, error) { timeRange := query.TimeRange - useTrend := isUseTrend(timeRange) + useTrend := ds.isUseTrend(timeRange) + ds.logger.Debug("Use trends", "use", useTrend) allHistory := zabbix.History{} groupedItems := map[int]zabbix.Items{} @@ -439,11 +440,18 @@ func (ds *ZabbixDatasourceInstance) getHistotyOrTrend(ctx context.Context, query return allHistory, nil } -func isUseTrend(timeRange backend.TimeRange) bool { +func (ds *ZabbixDatasourceInstance) isUseTrend(timeRange backend.TimeRange) bool { + if !ds.Settings.Trends { + return false + } + + trendsFrom := ds.Settings.TrendsFrom + trendsRange := ds.Settings.TrendsRange fromSec := timeRange.From.Unix() toSec := timeRange.To.Unix() - if (fromSec < time.Now().Add(time.Hour*-7*24).Unix()) || - (toSec-fromSec > (4 * 24 * time.Hour).Milliseconds()) { + rangeSec := float64(toSec - fromSec) + + if (fromSec < time.Now().Add(-trendsFrom).Unix()) || (rangeSec > trendsRange.Seconds()) { return true } return false diff --git a/pkg/zabbix_api_core.go b/pkg/zabbix_api_core.go index aafcb54..259f491 100644 --- a/pkg/zabbix_api_core.go +++ b/pkg/zabbix_api_core.go @@ -3,66 +3,18 @@ package main import ( "bytes" "context" - "crypto/tls" "encoding/json" "errors" "fmt" "io" "io/ioutil" - "net" "net/http" - "net/url" "time" simplejson "github.com/bitly/go-simplejson" - "github.com/grafana/grafana-plugin-sdk-go/backend" - "github.com/grafana/grafana-plugin-sdk-go/backend/log" "golang.org/x/net/context/ctxhttp" ) -// ZabbixDatasourceInstance stores state about a specific datasource and provides methods to make -// requests to the Zabbix API -type ZabbixDatasourceInstance struct { - url *url.URL - authToken string - dsInfo *backend.DataSourceInstanceSettings - queryCache *Cache - httpClient *http.Client - logger log.Logger -} - -// newZabbixDatasource returns an initialized ZabbixDatasource -func newZabbixDatasource(dsInfo *backend.DataSourceInstanceSettings) (*ZabbixDatasourceInstance, error) { - zabbixURLStr := dsInfo.URL - zabbixURL, err := url.Parse(zabbixURLStr) - if err != nil { - return nil, err - } - - return &ZabbixDatasourceInstance{ - url: zabbixURL, - dsInfo: dsInfo, - queryCache: NewCache(10*time.Minute, 10*time.Minute), - httpClient: &http.Client{ - Transport: &http.Transport{ - TLSClientConfig: &tls.Config{ - Renegotiation: tls.RenegotiateFreelyAsClient, - }, - Proxy: http.ProxyFromEnvironment, - Dial: (&net.Dialer{ - Timeout: 30 * time.Second, - KeepAlive: 30 * time.Second, - }).Dial, - TLSHandshakeTimeout: 10 * time.Second, - ExpectContinueTimeout: 1 * time.Second, - MaxIdleConns: 100, - IdleConnTimeout: 90 * time.Second, - }, - Timeout: time.Duration(time.Second * 30), - }, - }, nil -} - // ZabbixRequest checks authentication and makes a request to the Zabbix API func (ds *ZabbixDatasourceInstance) ZabbixRequest(ctx context.Context, method string, params ZabbixAPIParams) (*simplejson.Json, error) { var result *simplejson.Json