From 557fb52579a4cfbc32aaa441ab7047e5f254118d Mon Sep 17 00:00:00 2001 From: peekjef72 Date: Sun, 24 Sep 2023 18:57:10 +0200 Subject: [PATCH] first commit --- .gitignore | 11 + .promu.yml | 23 + LICENSE | 21 + Makefile | 61 + README.md | 11 + VERSION | 1 + actions_action.go | 169 + client.go | 1155 +++ cmd/passwd_crypt/main.go | 85 + collector.go | 261 + config.go | 958 +++ content.go | 237 + .../CITRIX ADC Netscaler Stats.json | 6410 +++++++++++++++++ contribs/dashboards/HP3PAR System.json | 3093 ++++++++ .../dashboards/Veeam Enterprise Manager.json | 4834 +++++++++++++ contribs/prometheus/hp3par_node_example.yml | 8 + contribs/prometheus/prometheus_jobs.yml | 16 + .../systemd/system/hp3par_exporter.service | 23 + .../systemd/system/veeam_exporter.service | 23 + debug_action.go | 182 + doc/session.drawio | 269 + doc/session.png | Bin 0 -> 76031 bytes encrypt/encrypt.go | 79 + exporter.go | 198 + field.go | 340 + go.mod | 42 + go.sum | 165 + httpapi_exporter.go | 137 + metric.go | 490 ++ metric_action.go | 164 + metrics_action.go | 230 + play_script_action.go | 141 + promhttp.go | 154 + query_action.go | 462 ++ set_fact_action.go | 257 + target.go | 524 ++ template.go | 250 + veeam_exporter | 1 + yaml_script.go | 993 +++ 39 files changed, 22478 insertions(+) create mode 100644 .gitignore create mode 100644 .promu.yml create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 README.md create mode 100644 VERSION create mode 100644 actions_action.go create mode 100644 client.go create mode 100644 cmd/passwd_crypt/main.go create mode 100644 collector.go create mode 100644 config.go create mode 100644 content.go create mode 100644 contribs/dashboards/CITRIX ADC Netscaler Stats.json create mode 100644 contribs/dashboards/HP3PAR System.json create mode 100644 contribs/dashboards/Veeam Enterprise Manager.json create mode 100644 contribs/prometheus/hp3par_node_example.yml create mode 100644 contribs/prometheus/prometheus_jobs.yml create mode 100644 contribs/systemd/system/hp3par_exporter.service create mode 100644 contribs/systemd/system/veeam_exporter.service create mode 100644 debug_action.go create mode 100644 doc/session.drawio create mode 100644 doc/session.png create mode 100644 encrypt/encrypt.go create mode 100644 exporter.go create mode 100644 field.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 httpapi_exporter.go create mode 100644 metric.go create mode 100644 metric_action.go create mode 100644 metrics_action.go create mode 100644 play_script_action.go create mode 100644 promhttp.go create mode 100644 query_action.go create mode 100644 set_fact_action.go create mode 100644 target.go create mode 100644 template.go create mode 120000 veeam_exporter create mode 100644 yaml_script.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4335ea5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +/.project +/.settings +/my_tests +/*.tar.gz +.vscode/ +config/ +bin/ +./httpapi_exporter +./passwd_crypt +httpapi_exporter +passwd_encrypt diff --git a/.promu.yml b/.promu.yml new file mode 100644 index 0000000..b4a8b36 --- /dev/null +++ b/.promu.yml @@ -0,0 +1,23 @@ +go: + cgo: false +repository: + path: github.com/peekjef72/httpapi_exporter +build: + binaries: + - name: httpapi_exporter + # path: httpapi_exporter + - name: passwd_encrypt + path: ./cmd/passwd_crypt + flags: -a -tags netgo,static + ldflags: | + -X github.com/prometheus/common/version.Version={{.Version}} + -X github.com/prometheus/common/version.Revision={{.Revision}} + -X github.com/prometheus/common/version.Branch={{.Branch}} + -X github.com/prometheus/common/version.BuildDate={{date "20060102-15:04:05"}} + -X github.com/prometheus/common/version.BuildUser={{user}}@{{host}} +tarball: + prefix: . + files: + - LICENSE + - README.md + - contribs/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..89ad7d9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 Alin Sinpalean + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..913f5ed --- /dev/null +++ b/Makefile @@ -0,0 +1,61 @@ +# Copyright 2015 The Prometheus Authors +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +GO := go +GOPATH := $(firstword $(subst :, ,$(shell $(GO) env GOPATH))) +PROMU := $(GOPATH)/bin/promu +pkgs = $(shell $(GO) list ./... | grep -v /vendor/) + +PREFIX ?= $(shell pwd) +BIN_DIR ?= $(shell pwd) +DOCKER_IMAGE_NAME ?= httpapi_exporter +DOCKER_IMAGE_TAG ?= $(subst /,-,$(shell git rev-parse --abbrev-ref HEAD)) + + +all: promu build test + +style: + @echo ">> checking code style" + @! gofmt -d $(shell find . -path ./vendor -prune -o -name '*.go' -print) | grep '^' + +test: + @echo ">> running tests" + @$(GO) test -short -race $(pkgs) + +format: + @echo ">> formatting code" + @$(GO) fmt $(pkgs) + +vet: + @echo ">> vetting code" + @$(GO) vet $(pkgs) + +build: promu + @echo ">> building binaries" + @$(PROMU) build --prefix $(PREFIX) + +tarball: promu + @echo ">> building release tarball" + @$(PROMU) tarball --prefix $(PREFIX) $(BIN_DIR) + +docker: + @echo ">> building docker image" + @docker build -t "$(DOCKER_IMAGE_NAME):$(DOCKER_IMAGE_TAG)" . + +promu: + @GOOS=$(shell uname -s | tr A-Z a-z) \ + GOARCH=$(subst x86_64,amd64,$(patsubst i%86,386,$(shell uname -m))) \ + $(GO) install github.com/prometheus/promu@latest + + +.PHONY: all style format build test vet tarball docker promu diff --git a/README.md b/README.md new file mode 100644 index 0000000..b73e093 --- /dev/null +++ b/README.md @@ -0,0 +1,11 @@ +# Prometheus HTTPAPI Exporter + +This exporter wants to be a generic JSON REST API exporter. That's mean it can login, then makes requests to collect metrics and performs transformations on values and finally returns metrics in prometheus format. + +Nothing is hard coded in the exporter. That why it is a generic exporter. + +As examples 3 configurations for exporters are provided (see contribs): +- hp3par_exporter +- veeam_exporter +- netscaler_exporter + diff --git a/VERSION b/VERSION new file mode 100644 index 0000000..9325c3c --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +0.3.0 \ No newline at end of file diff --git a/actions_action.go b/actions_action.go new file mode 100644 index 0000000..b077a18 --- /dev/null +++ b/actions_action.go @@ -0,0 +1,169 @@ +package main + +import ( + //"bytes" + "fmt" + + "github.com/go-kit/log" + "github.com/go-kit/log/level" +) + +// *************************************************************************************** +// *************************************************************************************** +// actions (block / loop ) +// *************************************************************************************** +// *************************************************************************************** + +type ActionsAction struct { + Name *Field `yaml:"name,omitempty"` + With []any `yaml:"with,omitempty"` + When []*exporterTemplate `yaml:"when,omitempty"` + LoopVar string `yaml:"loop_var,omitempty"` + Vars map[string]any `yaml:"vars,omitempty"` + Until []*exporterTemplate `yaml:"until,omitempty"` + + Actions []Action `yaml:"actions"` + + // Catches all undefined fields and must be empty after parsing. + XXX map[string]interface{} `yaml:",inline" json:"-"` +} + +func (a *ActionsAction) Type() int { + return actions_action +} + +func (a *ActionsAction) GetName(symtab map[string]any, logger log.Logger) string { + str, err := a.Name.GetValueString(symtab, nil, false) + if err != nil { + level.Warn(logger).Log("msg", fmt.Sprintf("invalid action name: %v", err)) + return "" + } + return str +} + +func (a *ActionsAction) GetNameField() *Field { + return a.Name +} +func (a *ActionsAction) SetNameField(name *Field) { + a.Name = name +} + +func (a *ActionsAction) GetWidh() []any { + return a.With +} +func (a *ActionsAction) SetWidth(with []any) { + a.With = with +} + +func (a *ActionsAction) GetWhen() []*exporterTemplate { + return a.When + +} +func (a *ActionsAction) SetWhen(when []*exporterTemplate) { + a.When = when +} + +func (a *ActionsAction) GetLoopVar() string { + return a.LoopVar +} +func (a *ActionsAction) SetLoopVar(loopvar string) { + a.LoopVar = loopvar +} + +func (a *ActionsAction) GetVars() map[string]any { + return a.Vars +} +func (a *ActionsAction) SetVars(vars map[string]any) { + a.Vars = vars +} + +func (a *ActionsAction) GetUntil() []*exporterTemplate { + return a.Until +} +func (a *ActionsAction) SetUntil(until []*exporterTemplate) { + a.Until = until +} + +// func (a *ActionsAction) GetBaseAction() *BaseAction { +// return nil +// } + +func (a *ActionsAction) setBasicElement( + nameField *Field, + vars map[string]any, + with []any, + loopVar string, + when []*exporterTemplate, + until []*exporterTemplate) error { + return setBasicElement(a, nameField, vars, with, loopVar, when, until) +} + +func (a *ActionsAction) PlayAction(script *YAMLScript, symtab map[string]any, logger log.Logger) error { + return PlayBaseAction(script, symtab, logger, a, a.CustomAction) +} + +// WARNING: only the first MetricPrefix in actionsTree if supported +func (a *ActionsAction) GetMetrics() []*GetMetricsRes { + var ( + final_res []*GetMetricsRes + ) + for _, cur_act := range a.Actions { + res := cur_act.GetMetrics() + if len(res) > 0 { + final_res = append(final_res, res...) + } + } + return final_res +} + +// only for MetricAction +func (a *ActionsAction) GetMetric() *MetricConfig { + return nil +} +func (a *ActionsAction) SetMetricFamily(*MetricFamily) { +} + +// only for PlayAction +func (a *ActionsAction) SetPlayAction(script map[string]*YAMLScript) error { + for _, a := range a.Actions { + if a.Type() == play_script_action || a.Type() == actions_action { + if err := a.SetPlayAction(script); err != nil { + return err + } + } + } + return nil +} + +// specific behavior for the ActionsAction +func (a *ActionsAction) CustomAction(script *YAMLScript, symtab map[string]any, logger log.Logger) error { + level.Debug(logger).Log( + "script", ScriptName(symtab, logger), + "msg", fmt.Sprintf("[Type: ActionsAction] Name: %s - %d Actions to play", a.GetName(symtab, logger), len(a.Actions))) + for _, cur_act := range a.Actions { + // fmt.Printf("\tadd to symbols table: %s = %v\n", key, val) + if err := PlayBaseAction(script, symtab, logger, cur_act, cur_act.CustomAction); err != nil { + return err + } + + } + return nil +} + +func (a *ActionsAction) AddCustomTemplate(customTemplate *exporterTemplate) error { + + if err := AddCustomTemplate(a, customTemplate); err != nil { + return err + } + + for _, cur_act := range a.Actions { + err := cur_act.AddCustomTemplate(customTemplate) + if err != nil { + return err + } + } + + return nil +} + +// *************************************************************************************** diff --git a/client.go b/client.go new file mode 100644 index 0000000..09d2ec5 --- /dev/null +++ b/client.go @@ -0,0 +1,1155 @@ +package main + +import ( + "encoding/json" + "fmt" + "net" + "strconv" + "strings" + + // "sync" + "time" + + "crypto/tls" + "net/http" + + "github.com/go-kit/log" + "github.com/go-kit/log/level" + "github.com/go-resty/resty/v2" + + "github.com/imdario/mergo" + "github.com/mitchellh/copystructure" + "github.com/peekjef72/httpapi_exporter/encrypt" + "golang.org/x/exp/slices" +) + +var ErrInvalidLogin = fmt.Errorf("invalid_login") + +// Query wraps a sql.Stmt and all the metrics populated from it. It helps extract keys and values from result rows. +type Client struct { + client *resty.Client + + logContext []interface{} + logger log.Logger + sc map[string]*YAMLScript + + // maybe better to use target symtab with a mutex.lock + symtab map[string]any + invalid_auth_code []int + + // mutex to hold condition for global client to try to login() + // wait_mutex sync.Mutex + // wake_cond sync.Cond + + // // to protect the data during exchange + // content_mutex sync.Mutex + // msg string +} + +func newClient(target *TargetConfig, sc map[string]*YAMLScript, logger log.Logger, gc *GlobalConfig) *Client { + + cl := &Client{ + logContext: []interface{}{}, + logger: logger, + sc: sc, + symtab: map[string]any{}, + invalid_auth_code: gc.invalid_auth_code, + } + + params := &ClientInitParams{ + Scheme: target.Scheme, + Host: target.Host, + Port: target.Port, + BaseUrl: target.BaseUrl, + AuthConfig: target.AuthConfig, + ProxyUrl: target.ProxyUrl, + VerifySSL: bool(target.VerifySSL), + ConnectionTimeout: time.Duration(target.ConnectionTimeout), + QueryRetry: target.QueryRetry, + } + + cl.Init(params) + + return cl +} + +// ********************* +func TimeoutDialer(cTimeout time.Duration) func(net, addr string) (c net.Conn, err error) { + return func(netw, addr string) (net.Conn, error) { + conn, err := net.DialTimeout(netw, addr, cTimeout) + if err != nil { + return nil, err + } + return conn, nil + } +} + +// *********************** +func (c *Client) Clone() *Client { + //sync.Mutex{} + cl := &Client{ + logContext: []interface{}{}, + logger: c.logger, + sc: c.sc, + // wait_mutex: sync.Mutex{}, + // content_mutex: sync.Mutex{}, + invalid_auth_code: c.invalid_auth_code, + } + + var err error + var tmp any + + tmp = c.symtab + if tmp, err = copystructure.Copy(c.symtab); err != nil { + level.Error(c.logger).Log("msg", "can't clone symbols table for new client") + return nil + } + if val, ok := tmp.(map[string]any); ok { + cl.symtab = val + } else { + cl.symtab = make(map[string]any) + } + + // c.wake_cond = *sync.NewCond(&c.wait_mutex) + + verifySSL, _ := GetMapValueBool(cl.symtab, "verifySSL") + timeout := time.Duration(cl.symtab["connectionTimeout"].(time.Duration)) + query_retry, _ := GetMapValueInt(cl.symtab, "queryRetry") + + params := &ClientInitParams{ + Scheme: GetMapValueString(cl.symtab, "scheme"), + Host: GetMapValueString(cl.symtab, "host"), + Port: GetMapValueString(cl.symtab, "port"), + BaseUrl: GetMapValueString(cl.symtab, "base_url"), + AuthConfig: AuthConfig{ + Mode: GetMapValueString(cl.symtab, "auth_mode"), + Username: GetMapValueString(cl.symtab, "user"), + Password: Secret(GetMapValueString(cl.symtab, "password")), + Token: Secret(GetMapValueString(cl.symtab, "auth_token")), + authKey: GetMapValueString(cl.symtab, "auth_key"), + }, + // BasicAuth: auth_mode, + // Username: GetMapValueString(cl.symtab, "user"), + // Password: Secret(GetMapValueString(cl.symtab, "password")), + ProxyUrl: GetMapValueString(cl.symtab, "proxyUrl"), + VerifySSL: verifySSL, + ConnectionTimeout: timeout, + QueryRetry: query_retry, + } + cl.Init(params) + + for header, values := range c.client.Header { + cl.client.SetHeader(header, values[0]) + } + + auth_set, _ := GetMapValueBool(c.symtab, "auth_set") + if auth_set { + cl.client.UserInfo = &resty.User{ + Username: c.client.UserInfo.Username, + Password: c.client.UserInfo.Password, + } + + cl.symtab["auth_set"] = true + } + return cl +} + +// set the url for client +func (c *Client) SetUrl(url string) string { + if _, ok := c.symtab["APIEndPoint"]; !ok { + err := fmt.Errorf("http base uri not found") + level.Error(c.logger).Log("errmsg", err) + return "" + } + base := c.symtab["APIEndPoint"].(string) + + uri := fmt.Sprintf("%s/%s", base, strings.TrimPrefix(url, "/")) + c.symtab["uri"] = uri + + level.Debug(c.logger).Log("uri", uri) + return uri +} + +// func (c *Client) Synchronise(src *Client) error { +// c.content_mutex.Lock() +// src.content_mutex.Lock() + +// defer func(){ +// c.content_mutex.Unlock() +// src.content_mutex.Unlock() +// }() + +// return nil +// } +// HTTP GET encapsulation +// func (c *Client) Get( +// uri string, +// params map[string]string, +// with_retry bool) ( +// *resty.Response, +// any, +// error) { +// if c.auth_token == "" { +// err := c.Login() +// if err != nil { +// return nil, nil, err +// } +// } +// return c.Execute("GET", uri, params, nil, with_retry) +// } + +// // Post PowerMax HTTP POST encapsulation +// func (c *Client) Post( +// uri string, +// body interface{}, +// with_retry bool) ( +// *resty.Response, +// any, +// error) { + +// if c.auth_token == "" { +// err := c.Login() +// if err != nil { +// return nil, nil, err +// } +// } +// return c.Execute("POST", uri, nil, body, with_retry) +// } + +// parse a response to a json map[string]interface{} +func (c *Client) getJSONResponse(resp *resty.Response) any { + var err error + // var data map[string]interface{} + // var data_map map[string]interface{} + var data any + + body := resp.Body() + if len(body) > 0 { + content_type := resp.Header().Get("content-type") + if strings.Contains(content_type, "application/json") { + // tmp := make([]byte, len(body)) + // copy(tmp, body) + err = json.Unmarshal(body, &data) + if err != nil { + level.Error(c.logger).Log("errmsg", fmt.Sprintf("Fail to decode json results %v", err)) + } + } + } + return data +} + +// sent HTTP Method to uri with params or body and get the reponse and the json obj +func (c *Client) Execute( + method, uri string, + params map[string]string, + body interface{}) ( + // check_invalid_auth bool) ( + *resty.Response, + any, + error) { + + var err error + var data any + var query_retry int + var ok bool + + // lock client until current request is performed + // c.mutex.Lock() + // defer c.mutex.Unlock() + + url := c.SetUrl(uri) + level.Debug(c.logger).Log("msg", "querying httpapi", "method", method, "url", url) + if body != nil { + level.Debug(c.logger).Log("msg", "querying httpapi", "method", method, "url", url, "body", fmt.Sprintf("%+v", body)) + } + if len(params) > 0 { + level.Debug(c.logger).Log("msg", "querying httpapi", "method", method, "url", url, "params", params) + } + + if query_retry, ok = GetMapValueInt(c.symtab, "queryRetry"); !ok { + query_retry = 1 + } + var resp *resty.Response + + req := c.client.NewRequest() + if body != nil { + req.SetBody(body) + } + if len(params) > 0 { + req.SetQueryParams(params) + } + + for i := 0; i <= query_retry; i++ { + resp, err = req.Execute(method, url) + if err == nil { + // check if retry and invalid auth to replat Ping() script + code := resp.StatusCode() + // if (i+1 < query_retry) && check_invalid_auth && slices.Contains(c.invalid_auth_code, code) { + if (i+1 < query_retry) && slices.Contains(c.invalid_auth_code, code) { + level.Debug(c.logger).Log("msg", "received invalid auth. start Ping()/Login()") + if r_val, ok := c.symtab["__coll_channel"]; ok { + if coll_channel, ok := r_val.(chan<- int); ok { + coll_channel <- MsgLogin + c.symtab["logged"] = false + } + } + // if wake_cond, ok := c.symtab["__wake_cond"].(*sync.Cond); !ok { + // wake_cond.Signal() + // } + + return resp, data, ErrInvalidLogin + + // c.Clear() + // // c.auth_token = "" + // // c.client.Header.Del("x-hp3par-wsapi-sessionkey") + // status := false + // var err error + // var tmp any + // original_symtab := c.symtab + // if tmp, err = copystructure.Copy(c.symtab); err != nil { + // err = fmt.Errorf("can't clone symbols table for new client: %s", err) + // return resp, data, err + // } + // if val, ok := tmp.(map[string]any); ok { + // c.symtab = val + // } else { + // c.symtab = make(map[string]any) + // } + // // ** launch a login sequence with temporary symtab + // status, err = c.Login() + + // resp = nil + // // ** reset original symtab for client + // c.symtab = original_symtab + // // ping/login is unsuccessfull : leave the loop + // if err != nil || !status { + // i = query_retry + 1 + // if err == nil { + // err = fmt.Errorf("Ping() is unsuccessful: query stopped") + // return resp, data, err + // } + // } else { + // level.Debug(c.logger).Log("msg", fmt.Sprintf("Ping()/Login() successfull: retrying (%d)", i+1)) + // } + } else { + data = c.getJSONResponse(resp) + i = query_retry + 1 + } + c.symtab["response_headers"] = resp.Header() + } else { + level.Debug(c.logger).Log("msg", fmt.Sprintf("query unsuccessfull: retrying (%d)", i+1)) + delete(c.symtab, "response_headers") + } + } + // something wrong with retry... + if resp == nil { + err = fmt.Errorf("empty response") + } + if r_val, ok := c.symtab["__coll_channel"]; ok { + if coll_channel, ok := r_val.(chan<- int); ok { + coll_channel <- MsgDone + level.Debug(c.logger).Log("msg", "MsgDone sent to channel.") + } + } + return resp, data, err +} + +// add headers to client +func (cl *Client) proceedHeaders() error { + + if r_headers, ok := cl.symtab["headers"]; ok { + // format: "header" "value" + var head_name, head_value, action string + var headers map[any]any + var err error + var ok bool + if headers, ok = r_headers.(map[any]any); ok { + for r_header, r_value := range headers { + // ** get header name + switch header := r_header.(type) { + case *Field: + if head_name, err = header.GetValueString(cl.symtab, nil, false); err != nil { + return err + } + case string: + head_name = header + } + + switch value := r_value.(type) { + case *Field: + if head_value, err = value.GetValueString(cl.symtab, nil, false); err != nil { + return err + } + case string: + head_value = value + } + if head_value == "__delete__" || head_value == "__remove__" { + cl.client.Header.Del(head_name) + } else { + cl.client.SetHeader(head_name, head_value) + } + } + } else if headers_list, ok := r_headers.([]any); ok { + // format: "- name:header"\n value: "header_value" mode: add|delete + for _, map_header := range headers_list { + if headers, ok = map_header.(map[any]any); ok { + action = "add" + head_name = "" + head_value = "" + for r_key, r_value := range headers { + var key_name string + switch key_val := r_key.(type) { + case *Field: + if key_name, err = key_val.GetValueString(cl.symtab, nil, false); err != nil { + return err + } + case string: + key_name = key_val + } + if key_name == "name" { + switch value := r_value.(type) { + case *Field: + if head_name, err = value.GetValueString(cl.symtab, nil, false); err != nil { + return err + } + case string: + head_name = value + } + + } + // get value + if key_name == "value" { + switch value := r_value.(type) { + case *Field: + if head_value, err = value.GetValueString(cl.symtab, nil, false); err != nil { + return err + } + case string: + head_value = value + } + + } + + if key_name == "action" { + switch value := r_value.(type) { + case *Field: + if action, err = value.GetValueString(cl.symtab, nil, false); err != nil { + return err + } + case string: + action = value + } + } + } + if head_name != "" && head_value != "" { + if action == "add" { + cl.client.SetHeader(head_name, head_value) + } else if action == "delete" || action == "remove" { + cl.client.Header.Del(head_name) + } + } + } + } + } + } + return nil +} + +func DeleteCookie(cookies []*http.Cookie, cookie_name string) []*http.Cookie { + for index, http_cookie := range cookies { + if http_cookie.Name == cookie_name { + cookies = append(cookies[:index], cookies[index+1:]...) + } + } + return cookies +} + +// add cookie to client +func (cl *Client) proceedCookies() error { + + if r_cookies, ok := cl.symtab["cookies"]; ok { + // format: name: "header" value: "value" path: + var ( + cookie_name, cookie_value, cookie_path, cookie_domain, action string + cookie_max_age int + headers map[any]any + err error + ok bool + ) + if headers, ok = r_cookies.(map[any]any); ok { + for r_header, r_value := range headers { + // ** get header name + switch header := r_header.(type) { + case *Field: + if cookie_name, err = header.GetValueString(cl.symtab, nil, false); err != nil { + return err + } + case string: + cookie_name = header + } + + switch value := r_value.(type) { + case *Field: + if cookie_value, err = value.GetValueString(cl.symtab, nil, false); err != nil { + return err + } + case string: + cookie_value = value + } + if cookie_value == "__delete__" || cookie_value == "__remove__" { + cl.client.Cookies = DeleteCookie(cl.client.Cookies, cookie_name) + } else { + cookie := &http.Cookie{ + Name: cookie_name, + Value: cookie_value, + } + cl.client.SetCookie(cookie) + } + } + } else if cookies_list, ok := r_cookies.([]any); ok { + // format: "- name:header"\n value: "header_value" mode: add|delete + for _, map_cookie := range cookies_list { + if headers, ok = map_cookie.(map[any]any); ok { + action = "add" + cookie_name = "" + cookie_value = "" + cookie_path = "" + cookie_domain = "" + cookie_max_age = -1 + for r_key, r_value := range headers { + var key_name string + switch key_val := r_key.(type) { + case *Field: + if key_name, err = key_val.GetValueString(cl.symtab, nil, false); err != nil { + return err + } + case string: + key_name = key_val + } + if key_name == "name" { + switch value := r_value.(type) { + case *Field: + if cookie_name, err = value.GetValueString(cl.symtab, nil, false); err != nil { + return err + } + case string: + cookie_name = value + } + + } + // get value + if key_name == "value" { + switch value := r_value.(type) { + case *Field: + if cookie_value, err = value.GetValueString(cl.symtab, nil, false); err != nil { + return err + } + case string: + cookie_value = value + } + + } + // get domain + if key_name == "domain" { + switch value := r_value.(type) { + case *Field: + if cookie_domain, err = value.GetValueString(cl.symtab, nil, false); err != nil { + return err + } + case string: + cookie_domain = value + } + } + // get path + if key_name == "path" { + switch value := r_value.(type) { + case *Field: + if cookie_path, err = value.GetValueString(cl.symtab, nil, false); err != nil { + return err + } + case string: + cookie_path = value + } + } + + if key_name == "action" { + switch value := r_value.(type) { + case *Field: + if action, err = value.GetValueString(cl.symtab, nil, false); err != nil { + return err + } + case string: + action = value + } + } + } + if cookie_name != "" && cookie_value != "" { + if action == "add" { + cookie := &http.Cookie{ + Name: cookie_name, + Value: cookie_value, + } + if cookie_path != "" { + cookie.Path = cookie_path + } + if cookie_domain != "" { + cookie.Domain = cookie_domain + } + if cookie_max_age != -1 { + cookie.MaxAge = cookie_max_age + } + cl.client.SetCookie(cookie) + } else if action == "delete" || action == "remove" { + cl.client.Cookies = DeleteCookie(cl.client.Cookies, cookie_name) + } + } + } + } + } + } + return nil +} + +type CallClientExecuteParams struct { + Payload string + Method string + Url string + Debug bool + VarName string + OkStatus []int + AuthMode string + Username string + Password string + Token string + Timeout time.Duration + // Check_invalid_Auth bool +} + +func (c *Client) callClientExecute(params *CallClientExecuteParams, symtab map[string]any) error { + // payload := fmt.Sprintf("{ \"user\":\"%s\",\"password\":\"%s\", \"sessionType\": 1}", c.user, c.password) + + // if _, ok := symtab["data"]; !ok { + // err := fmt.Errorf("http data not found") + // level.Error(c.logger).Log("errmsg", err) + // return err + // } + var ( + payload any + ) + // payload_raw := symtab["data"].(string) + // if payload_raw == "" { + // payload = nil + // } else { + // payload = payload_raw + // } + if params.Payload == "" { + payload = nil + } else { + payload = params.Payload + } + + // if _, ok := symtab["method"]; !ok { + // err := fmt.Errorf("http method not found") + // level.Error(c.logger).Log("errmsg", err) + // return err + // } + if params.Method == "" { + err := fmt.Errorf("http method not found") + level.Error(c.logger).Log("errmsg", err) + return err + } + method := strings.ToUpper(params.Method) + + // if _, ok := symtab["url"]; !ok { + // err := fmt.Errorf("http url not found") + // level.Error(c.logger).Log("errmsg", err) + // return err + // } + // url := symtab["url"].(string) + if params.Url == "" { + err := fmt.Errorf("http url not found") + level.Error(c.logger).Log("errmsg", err) + return err + } + url := params.Url + old_values := make(map[string]string, 4) + + auth_mode := GetMapValueString(symtab, "auth_mode") + if params.AuthMode != "" { + old_values["auth_mode"] = auth_mode + auth_mode = params.AuthMode + symtab["auth_mode"] = auth_mode + } + + if params.Timeout != 0 { + old_values["timeout"] = fmt.Sprintf("%d", c.client.GetClient().Timeout) + c.client.SetTimeout(params.Timeout) + } + + auth_set, _ := GetMapValueBool(symtab, "auth_set") + if !auth_set { + if auth_mode == "basic" { + passwd := GetMapValueString(symtab, "password") + if params.Password != "" { + old_values["password"] = passwd + passwd = params.Password + symtab["password"] = passwd + } + if strings.Contains(passwd, "/encrypted/") { + ciphertext := passwd[len("/encrypted/"):] + level.Debug(c.logger).Log("ciphertext", ciphertext) + + user := GetMapValueString(symtab, "user") + if params.Username != "" { + old_values["user"] = user + user = params.Username + symtab["user"] = user + } + auth_key := GetMapValueString(symtab, "auth_key") + level.Debug(c.logger).Log("auth_key", auth_key) + cipher, err := encrypt.NewAESCipher(auth_key) + if err != nil { + err := fmt.Errorf("can't obtain cipher to decrypt") + // level.Error(c.logger).Log("errmsg", err) + return err + } + passwd, err = cipher.Decrypt(ciphertext, true) + if err != nil { + err := fmt.Errorf("invalid key provided to decrypt") + // level.Error(c.logger).Log("errmsg", err) + return err + } + c.client.SetBasicAuth(user, passwd) + passwd = "" + symtab["auth_set"] = true + delete(symtab, "auth_key") + } + } else if auth_mode == "token" { + auth_token := GetMapValueString(symtab, "auth_token") + if params.Token != "" { + old_values["auth_token"] = auth_token + auth_token = params.Token + symtab["auth_token"] = auth_token + } + if auth_token != "" { + c.client.SetAuthToken(auth_token) + } + } + } + + // * check if returned status is valid or not: present in valid_status list + // if _, ok := symtab["ok_status"]; !ok { + // err := fmt.Errorf("ok_status not found") + // level.Error(c.logger).Log("errmsg", err) + // return err + // } + if len(params.OkStatus) <= 0 { + err := fmt.Errorf("ok_status not found") + level.Error(c.logger).Log("errmsg", err) + return err + } + valid_status := params.OkStatus + + var_name := params.VarName + + //****************** + //* play the request + // resp, data, err := c.Execute(method, url, nil, payload, params.Check_invalid_Auth) + resp, data, err := c.Execute(method, url, nil, payload) + if err != nil { + level.Error(c.logger).Log("errmsg", err) + return err + } + if params.Debug { + level.Info(c.logger).Log("msg", "launch query debug", + "url", symtab["uri"].(string), "results", string(resp.Body())) + } + // * get returned status + code := resp.StatusCode() + // * set it to symbols table so user can access it + symtab["results_status"] = code + + if !slices.Contains(valid_status, code) { + symtab["query_status"] = false + level.Info(c.logger).Log("msg", fmt.Sprintf("invalid response status: (%d not in %v)", code, valid_status)) + } else { + if data == nil { + err = fmt.Errorf("fail to decode json results: %s", err) + // level.Error(c.logger).Log("errmsg", err) + return err + } else { + if var_name != "" && var_name != "_" { + symtab[var_name] = data + } else if var_name == "_root" { + opts := mergo.WithOverride + if err := mergo.Merge(&symtab, data, opts); err != nil { + level.Error(c.logger).Log("msg", "merging results into symbols table", "errmsg", err) + return err + } + } + symtab["query_status"] = true + + err = nil + } + } + // reset local auth param from client + if auth_mode, ok := old_values["auth_mode"]; ok { + symtab["auth_mode"] = auth_mode + if params.AuthMode == "basic" && params.AuthMode != auth_mode { + c.client.UserInfo = nil + } + } + + if user, ok := old_values["user"]; ok { + symtab["user"] = user + } + + if passwd, ok := old_values["passwd"]; ok { + symtab["passwd"] = passwd + } + + if auth_token, ok := old_values["auth_token"]; ok { + symtab["auth_token"] = auth_token + if params.Token != auth_token { + c.client.Token = "" + } + } + + if timeout_str, ok := old_values["timeout"]; ok { + var i_value int64 + if i_value, err = strconv.ParseInt(timeout_str, 10, 0); err != nil { + i_value = 0 + } + c.client.SetTimeout(time.Duration(i_value)) + } + + return err +} + +func GetMapValueString(symtab map[string]any, key string) string { + var value string + if value_raw, ok := symtab[key]; ok { + switch value_val := value_raw.(type) { + case string: + value = value_val + case int: + value = fmt.Sprintf("%d", value_val) + default: + value = "" + } + } + return value +} + +func GetMapValueInt(symtab map[string]any, key string) (int, bool) { + var value int + found := false + if value_raw, ok := symtab[key]; ok { + found = true + switch value_val := value_raw.(type) { + case string: + var i_value int64 + var err error + if i_value, err = strconv.ParseInt(value_val, 10, 0); err != nil { + i_value = 0 + } + value = int(i_value) + case int: + value = value_val + default: + value = 0 + found = false + } + } + return value, found +} + +func GetMapValueBool(symtab map[string]any, key string) (bool, bool) { + var value bool + + found := false + if value_raw, ok := symtab[key]; ok { + found = true + switch value_val := value_raw.(type) { + case bool: + value = value_val + case string: + asString := strings.ToLower(value_val) + if asString == "1" || asString == "true" || asString == "yes" || asString == "on" { + value = true + } else if asString == "0" || asString == "false" || asString == "no" || asString == "off" { + value = false + } + default: + value = false + found = false + } + } + return value, found +} + +// **************************************************************** +// user HTTP connections script steps +// init(): to initialize http request +// login(): to login to the http API and proceed result (token bearer) +// logout(): to logout and reset parameters +// ping(): to check the auth/cnx is still active +type ClientInitParams struct { + Scheme string + Host string + Port string + BaseUrl string + AuthConfig AuthConfig + // BasicAuth bool + // Username string + // Password Secret + ProxyUrl string + VerifySSL bool + ConnectionTimeout time.Duration + QueryRetry int +} + +func (cl *Client) Init(params *ClientInitParams) error { + + // ** get the init script definition from config if one is defined + // ** set default config for all targets + if script, ok := cl.sc["init"]; ok && script != nil { + // cl.symtab["__client"] = cl.client + cl.symtab["__method"] = cl.callClientExecute + err := script.Play(cl.symtab, false, cl.logger) + delete(cl.symtab, "__method") + + if err != nil { + return err + } + } + var base_url, scheme, port string + var verifySSL bool + var query_retry int + + base_url = GetMapValueString(cl.symtab, "base_url") + scheme = GetMapValueString(cl.symtab, "scheme") + port = GetMapValueString(cl.symtab, "port") + verifySSL, _ = GetMapValueBool(cl.symtab, "verifySSL") + query_retry, _ = GetMapValueInt(cl.symtab, "queryRetry") + + // ** update default parameters with target parameters + if params.BaseUrl != "" { + base_url = params.BaseUrl + } + if params.Scheme != "" { + scheme = params.Scheme + } + if params.Port != "" { + port = params.Port + } + if verifySSL != params.VerifySSL { + verifySSL = params.VerifySSL + } + if query_retry != params.QueryRetry { + query_retry = params.QueryRetry + } + apiendpoint := fmt.Sprintf("%s://%s:%s", scheme, params.Host, port) + baseurl := strings.TrimPrefix(base_url, "/") + if baseurl != "" { + apiendpoint += "/" + baseurl + } + + cl.symtab["APIEndPoint"] = apiendpoint + cl.symtab["scheme"] = scheme + cl.symtab["host"] = params.Host + cl.symtab["port"] = port + cl.symtab["base_url"] = base_url + cl.symtab["auth_mode"] = params.AuthConfig.Mode + cl.symtab["user"] = params.AuthConfig.Username + cl.symtab["password"] = string(params.AuthConfig.Password) + cl.symtab["auth_token"] = string(params.AuthConfig.Token) + cl.symtab["auth_key"] = string(params.AuthConfig.authKey) + cl.symtab["auth_set"] = false + cl.symtab["verifySSL"] = verifySSL + cl.symtab["proxyUrl"] = params.ProxyUrl + cl.symtab["connectionTimeout"] = params.ConnectionTimeout + cl.symtab["queryRetry"] = query_retry + + if scheme == "https" { + cl.client = resty.New().SetTLSClientConfig(&tls.Config{InsecureSkipVerify: !verifySSL}) + } else if scheme == "http" { + cl.client = resty.New() + } else { + level.Error(cl.logger).Log("msg", fmt.Sprintf("invalid scheme for url '%s'", scheme)) + return nil + } + timeout := time.Duration(cl.symtab["connectionTimeout"].(time.Duration)) + // cl.client.SetTransport( + // &http.Transport{ + // DialContext: (&net.Dialer{ + // Timeout: timeout, + // }).DialContext, + // }, + // ) + cl.client.SetTimeout(timeout) + + if err := cl.proceedHeaders(); err != nil { + return err + } + if err := cl.proceedCookies(); err != nil { + return err + } + + if params.AuthConfig.Mode == "basic" { + passwd := string(params.AuthConfig.Password) + if params.AuthConfig.Username != "" && passwd != "" && + !strings.Contains(passwd, "/encrypted/") { + cl.client.SetBasicAuth(params.AuthConfig.Username, passwd) + } + } else if params.AuthConfig.Mode == "token" && params.AuthConfig.Token != "" { + token := GetMapValueString(cl.symtab, "auth_token") + cl.client.SetAuthToken(token) + } + if params.ProxyUrl != "" { + cl.client.SetProxy(params.ProxyUrl) + } + + return nil +} + +// login to target +func (cl *Client) Login() (bool, error) { + + // ** init the connection status func and symbol table + status := false + cl.symtab["logged"] = false + + // ** get the login script definition from config if one is defined + if script, ok := cl.sc["login"]; ok && script != nil { + // cl.symtab["__client"] = cl.client + cl.symtab["__method"] = cl.callClientExecute + err := script.Play(cl.symtab, false, cl.logger) + delete(cl.symtab, "__method") + + if err != nil { + return false, err + } + if err := cl.proceedHeaders(); err != nil { + return false, err + } + if err := cl.proceedCookies(); err != nil { + return false, err + } + // check is user has set a token + token := GetMapValueString(cl.symtab, "auth_token") + if cl.client.Token != token { + cl.client.SetAuthToken(token) + } + } else { + // * no user script has been defined: logged is equivalent to "ping()" query_status + cl.symtab["logged"] = cl.symtab["query_status"] + } + + if logged, ok := GetMapValueBool(cl.symtab, "logged"); ok { + status = logged + } + + return status, nil +} + +// logout from target +func (cl *Client) Logout() error { + // ** get the login script definition from config if one is defined + if script, ok := cl.sc["logout"]; ok && script != nil { + // cl.symtab["__client"] = cl.client + cl.symtab["__method"] = cl.callClientExecute + err := script.Play(cl.symtab, false, cl.logger) + delete(cl.symtab, "__method") + + if err != nil { + return err + } + + if err := cl.proceedHeaders(); err != nil { + return err + } + if err := cl.proceedCookies(); err != nil { + return err + } + } else { + // ** no user script found: equivalent to Clear() + return cl.Clear() + } + return nil +} + +// clear auth info for target +func (cl *Client) Clear() error { + // ** get the clear script definition from config if one is defined + if script, ok := cl.sc["clear"]; ok && script != nil { + // cl.symtab["__client"] = cl.client + cl.symtab["__method"] = cl.callClientExecute + err := script.Play(cl.symtab, false, cl.logger) + delete(cl.symtab, "__method") + + if err != nil { + return err + } + } else { + cl.symtab["logged"] = false + cl.symtab["auth_set"] = false + delete(cl.symtab, "auth_token") + cl.client.SetAuthToken("") + } + + if err := cl.proceedHeaders(); err != nil { + return err + } + if err := cl.proceedCookies(); err != nil { + return err + } + return nil +} + +// ping the target +func (cl *Client) Ping() (bool, error) { + + // ** init the connection status func and symbol table + status := false + cl.symtab["query_status"] = false + + // ** get the ping script definition from config if one is defined + if script, ok := cl.sc["ping"]; ok && script != nil { + level.Debug(cl.logger).Log("msg", fmt.Sprintf("starting script '%s'", script.name)) + // cl.symtab["__client"] = cl.client + cl.symtab["__method"] = cl.callClientExecute + // cl.symtab["check_invalid_auth"] = false + err := script.Play(cl.symtab, false, cl.logger) + delete(cl.symtab, "__method") + // delete(cl.symtab, "check_invalid_auth") + + if err != nil { + return false, err + } + } else { + err := fmt.Errorf("no ping script found... can't connect") + level.Error(cl.logger).Log("msg", err) + return false, err + } + + if query_status, ok := GetMapValueBool(cl.symtab, "query_status"); ok { + status = query_status + } + + if err := cl.proceedHeaders(); err != nil { + return status, err + } + if err := cl.proceedCookies(); err != nil { + return status, err + } + + // check is user has set a token + token := GetMapValueString(cl.symtab, "auth_token") + if cl.client.Token != token { + cl.client.SetAuthToken(token) + } + + return status, nil + +} diff --git a/cmd/passwd_crypt/main.go b/cmd/passwd_crypt/main.go new file mode 100644 index 0000000..dcdec33 --- /dev/null +++ b/cmd/passwd_crypt/main.go @@ -0,0 +1,85 @@ +package main + +import ( + /* rsa + "crypto" + "crypto/rand" + "crypto/rsa" + "crypto/sha256" + "encoding/base64" + "fmt" + + // "crypto/ed25519" + "crypto/x509" + */ + /* cipher + */ + "bufio" + "fmt" + "os" + "strings" + + // "golang.org/x/crypto/ssh/terminal" + kingpin "github.com/alecthomas/kingpin/v2" + "github.com/peekjef72/httpapi_exporter/encrypt" + "github.com/prometheus/common/version" +) + +func main() { + + app := kingpin.New("passwd_crypt", "encrypt password with a shared key.") + var ( + decrypt = app.Flag("decrypt", "Decrypt the provided password with key.").Short('d').Default("false").Bool() + hexa = app.Flag("hexa", "Encode password in hexastring.(default base64).").Short('x').Default("false").Bool() + ) + app.HelpFlag.Short('h') + app.Version(version.Print("passwd_crypt")).VersionFlag.Short('V') + kingpin.MustParse(app.Parse(os.Args[1:])) + // kingpin.Parse() + + fmt.Println("give the key: must be 16 24 or 32 bytes long") + key := credentials("enter key: ") + + cipher, err := encrypt.NewAESCipher(key) + if err != nil { + fmt.Printf("%s\n", err) + os.Exit(1) + } + + if !*decrypt { + passwd := credentials("enter password: ") + + fmt.Println("Encrypting...") + msg := []byte(passwd) + ciphertext := cipher.Encrypt(msg, !*hexa) + fmt.Printf("Encrypted message hex: %s\n", ciphertext) + } else { + passwd := credentials("enter encrypted password: ") + + fmt.Println("Decrypting...") + plaintext, err := cipher.Decrypt(passwd, !*hexa) + if err != nil { + // Don't display this message to the end-user, as it could potentially + // give an attacker useful information. Just tell them something like "Failed to decrypt." + fmt.Printf("Error decryping message: %s\n", err.Error()) + os.Exit(1) + } + fmt.Printf("Decrypted message: %s\n", string(plaintext)) + } +} + +func credentials(prompt string) string { + reader := bufio.NewReader(os.Stdin) + + fmt.Print(prompt) + res, _ := reader.ReadString('\n') + + // fmt.Print("Enter Password: ") + // bytePassword, err := terminal.ReadPassword(0) + // if err == nil { + // fmt.Println("\nPassword typed: " + string(bytePassword)) + // } + // password := string(bytePassword) + + return strings.TrimSpace(res) +} diff --git a/collector.go b/collector.go new file mode 100644 index 0000000..281a95f --- /dev/null +++ b/collector.go @@ -0,0 +1,261 @@ +package main + +import ( + "context" + "fmt" + + // "sync" + "time" + + "github.com/go-kit/log" + "github.com/go-kit/log/level" + dto "github.com/prometheus/client_model/go" +) + +// Collector is a self-contained group of http queries and metric families to collect from results. It is +// conceptually similar to a prometheus.Collector. +type Collector interface { + // Collect is the equivalent of prometheus.Collector.Collect() but takes a context to run in and a database to run on. + Collect(context.Context, chan<- Metric, chan<- int) + SetClient(*Client) + GetClient() *Client +} + +// collector implements Collector. It wraps a collection of queries, metrics and the database to collect them from. +type collector struct { + config *CollectorConfig + client *Client + // queries []*Query + logContext []interface{} + collect_script []*YAMLScript + // metricFamilies []*MetricFamily + logger log.Logger +} + +// NewCollector returns a new Collector with the given configuration and database. The metrics it creates will all have +// the provided const labels applied. +func NewCollector( + logContext []interface{}, + logger log.Logger, + cc *CollectorConfig, + constLabels []*dto.LabelPair, + collect_script []*YAMLScript) (Collector, error) { + + // var mfs []*MetricFamily + + logContext = append(logContext, "collector", cc.Name) + // mfs := make([]*MetricFamily,) + for _, scr := range collect_script { + for _, ma := range scr.metricsActions { + for _, act := range ma.Actions { + if act.Type() == metric_action { + mc := act.GetMetric() + if mc == nil { + return nil, fmt.Errorf("MetricAction nil received") + } + mf, err := NewMetricFamily(logContext, mc, constLabels, cc.customTemplate) + if err != nil { + return nil, err + } + // ma.metricFamilies = append(ma.metricFamilies, mf) + // mfs = append(mfs, mf) + act.SetMetricFamily(mf) + } + } + // for _, mc := range ma.GetMetrics() { + // } + } + } + // Instantiate metric families. + // for _, mc := range cc.Metrics { + // mf, err := NewMetricFamily(logContext, mc, constLabels, cc.customTemplate) + // if err != nil { + // return nil, err + // } + // mfs, found := queryMFs[mc.Query()] + // if !found { + // mfs = make([]*MetricFamily, 0, 2) + // } + // queryMFs[mc.Query()] = append(mfs, mf) + // } + + // Instantiate queries. + // queries := make([]*Query, 0, len(cc.Metrics)) + // for qc, mfs := range queryMFs { + // q, err := NewQuery(logContext, logger, qc, mfs...) + // if err != nil { + // return nil, err + // } + // queries = append(queries, q) + // } + + c := collector{ + config: cc, + // queries: queries, + logContext: logContext, + logger: logger, + // metricFamilies: mfs, + collect_script: collect_script, + } + + if c.config.MinInterval > 0 { + var logCtx []interface{} + + logCtx = append(logCtx, logContext...) + logCtx = append(logCtx, "msg", fmt.Sprintf("NewCollector(): Non-zero min_interval (%s), using cached collector.", c.config.MinInterval)) + level.Debug(logger).Log(logCtx...) + return newCachingCollector(&c), nil + } + return &c, nil +} + +// SetClient implement SetClient for Client +func (c *collector) GetClient() (client *Client) { + return c.client +} + +// SetClient implement SetClient for Client +func (c *collector) SetClient(client *Client) { + c.client = client +} + +// type CollectContext struct { +// method func(*CallClientExecuteParams, map[string]any) error +// // ctx context.Context +// metric_ch chan<- Metric +// metricfamilies []*MetricFamily +// wake_cond *sync.Cond +// // logcontext []any +// } + +// Collect implements Collector. +func (c *collector) Collect(ctx context.Context, ch chan<- Metric, coll_ch chan<- int) { + // var wg sync.WaitGroup + // wg.Add(len(c.queries)) + // for _, q := range c.queries { + // go func(q *Query) { + // defer wg.Done() + // q.Collect(ctx, client, ch) + // }(q) + // } + // // Only return once all queries have been processed + // wg.Wait() + // use a collect context object + + // cctx := &CollectContext{ + // method: c.client.callClientExecute, + // // ctx: ctx, + // metric_ch: ch, + // metricfamilies: c.metricFamilies, + // wake_cond: wake_cond, + // // logcontext: c.logContext, + // } + + c.client.symtab["__method"] = c.client.callClientExecute + // c.client.symtab["__context"] = ctx + c.client.symtab["__channel"] = ch + c.client.symtab["__coll_channel"] = coll_ch + // c.client.symtab["__metricfamilies"] = c.metricFamilies + // c.client.symtab["__wake_cond"] = wake_cond + // c.client.symtab["__logcontext"] = c.logContext + + // c.client.symtab["__collect_context"] = cctx + + for _, scr := range c.collect_script { + level.Debug(c.logger).Log("msg", fmt.Sprintf("starting script '%s'", scr.name)) + if err := scr.Play(c.client.symtab, false, c.logger); err != nil { + if err != ErrInvalidLogin { + level.Warn(c.logger).Log("script", scr.name, "errmsg", err) + coll_ch <- MsgQuit + } + } + } + delete(c.client.symtab, "__channel") + delete(c.client.symtab, "__coll_channel") + // delete(c.client.symtab, "__metricfamilies") +} + +// newCachingCollector returns a new Collector wrapping the provided raw Collector. +func newCachingCollector(rawColl *collector) Collector { + cc := &cachingCollector{ + rawColl: rawColl, + minInterval: time.Duration(rawColl.config.MinInterval), + cacheSem: make(chan time.Time, 1), + } + cc.cacheSem <- time.Time{} + return cc +} + +// Collector with a cache for collected metrics. Only used when min_interval is non-zero. +type cachingCollector struct { + // Underlying collector, which is being cached. + rawColl *collector + // Convenience copy of rawColl.config.MinInterval. + minInterval time.Duration + + // Used as a non=blocking semaphore protecting the cache. The value in the channel is the time of the cached metrics. + cacheSem chan time.Time + // Metrics saved from the last Collect() call. + cache []Metric +} + +// SetClient implement SetClient()for Client +func (cc *cachingCollector) SetClient(client *Client) { + cc.rawColl.client = client +} + +// SetClient implement SetClient for Client +func (cc *cachingCollector) GetClient() (client *Client) { + return cc.rawColl.client +} + +// Collect implements Collector. +func (cc *cachingCollector) Collect(ctx context.Context, ch chan<- Metric, coll_ch chan<- int) { + if ctx.Err() != nil { + ch <- NewInvalidMetric(cc.rawColl.logContext, ctx.Err()) + return + } + + collTime := time.Now() + select { + case cacheTime := <-cc.cacheSem: + // Have the lock. + if age := collTime.Sub(cacheTime); age > cc.minInterval { + // Cache contents are older than minInterval, collect fresh metrics, cache them and pipe them through. + var logCtx []interface{} + + logCtx = append(logCtx, cc.rawColl.logContext...) + logCtx = append(logCtx, "msg", fmt.Sprintf("Collecting fresh metrics: min_interval=%.3fs cache_age=%.3fs", + cc.minInterval.Seconds(), age.Seconds())) + level.Debug(cc.rawColl.logger).Log(logCtx...) + cacheChan := make(chan Metric, capMetricChan) + cc.cache = make([]Metric, 0, len(cc.cache)) + go func() { + cc.rawColl.Collect(ctx, cacheChan, coll_ch) + close(cacheChan) + }() + for metric := range cacheChan { + cc.cache = append(cc.cache, metric) + ch <- metric + } + cacheTime = collTime + } else { + var logCtx []interface{} + + logCtx = append(logCtx, cc.rawColl.logContext...) + logCtx = append(logCtx, "msg", fmt.Sprintf("Returning cached metrics: min_interval=%.3fs cache_age=%.3fs", + cc.minInterval.Seconds(), age.Seconds())) + level.Debug(cc.rawColl.logger).Log(logCtx...) + for _, metric := range cc.cache { + ch <- metric + } + } + // Always replace the value in the semaphore channel. + cc.cacheSem <- cacheTime + + case <-ctx.Done(): + // Context closed, record an error and return + // TODO: increment an error counter + ch <- NewInvalidMetric(cc.rawColl.logContext, ctx.Err()) + } +} diff --git a/config.go b/config.go new file mode 100644 index 0000000..f2d3210 --- /dev/null +++ b/config.go @@ -0,0 +1,958 @@ +package main + +import ( + "fmt" + "reflect" + "regexp" + + // "html/template" + "os" + "path/filepath" + "strings" + "text/template" + "time" + + // "github.com/Masterminds/sprig/v3" + "github.com/go-kit/log" + "github.com/go-kit/log/level" + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/common/model" + "gopkg.in/yaml.v3" +) + +// Load attempts to parse the given config file and return a Config object. +func Load(configFile string, logger log.Logger, collectorName string) (*Config, error) { + level.Info(logger).Log("msg", fmt.Sprintf("Loading configuration from %s", configFile)) + buf, err := os.ReadFile(configFile) + if err != nil { + return nil, err + } + + c := Config{ + configFile: configFile, + logger: logger, + collectorName: collectorName, + } + + err = yaml.Unmarshal(buf, &c) + if err != nil { + return nil, err + } + + return &c, nil +} + +// +// Top-level config +// + +// Config is a collection of targets and collectors. +type Config struct { + Globals *GlobalConfig `yaml:"global"` + CollectorFiles []string `yaml:"collector_files,omitempty"` + Targets []*TargetConfig `yaml:"targets,omitempty"` + Collectors []*CollectorConfig `yaml:"collectors,omitempty"` + HttpAPIConfig map[string]*YAMLScript `yaml:"httpapi_config"` + + configFile string + logger log.Logger + collectorName string + + // Catches all undefined fields and must be empty after parsing. + XXX map[string]interface{} `yaml:",inline" json:"-"` +} + +// UnmarshalYAML implements the yaml.Unmarshaler interface for Config. +func (c *Config) UnmarshalYAML(unmarshal func(interface{}) error) error { + type plain Config + if err := unmarshal((*plain)(c)); err != nil { + return err + } + + if len(c.Targets) == 0 { + return fmt.Errorf("at least one target in `targets` must be defined") + } + + // Load any externally defined collectors. + if err := c.loadCollectorFiles(); err != nil { + return err + } + + if len(c.Collectors) == 0 { + return fmt.Errorf("at least one collector in `collectors` must be defined") + } + + // Populate collector references for the target/jobs. + colls := make(map[string]*CollectorConfig) + for _, coll := range c.Collectors { + // Set the min interval to the global default if not explicitly set. + if coll.MinInterval < 0 { + coll.MinInterval = c.Globals.MinInterval + } + if _, found := colls[coll.Name]; found { + return fmt.Errorf("duplicate collector name: %s", coll.Name) + } + colls[coll.Name] = coll + // set metric prefix + var prefix string + for _, cs := range coll.CollectScripts { + for _, a := range cs.Actions { + reslist := a.GetMetrics() + for _, res := range reslist { + for _, metric := range res.mc { + // for _, metric := range coll.Metrics { + // add metric prefix to all metrics name + + if res.maprefix != "" { + prefix = res.maprefix + } else if coll.MetricPrefix != "" { + prefix = coll.MetricPrefix + } else if c.Globals.MetricPrefix != "" { + prefix = c.Globals.MetricPrefix + } + metric.Name = prefix + "_" + metric.Name + } + } + } + } + } + + // read the target config with a TargetsFiles specfied + for _, t := range c.Targets { + if len(t.TargetsFiles) > 0 { + err := c.loadTargetsFiles(t.TargetsFiles) + if err != nil { + return err + } + } + } + targets := c.Targets + c.Targets = nil + // remove pseudo targets with a TargetsFiles + for _, t := range targets { + if len(t.TargetsFiles) == 0 { + c.Targets = append(c.Targets, t) + } + } + + if len(c.Targets) == 0 { + return fmt.Errorf("at least one target in `targets` must be defined") + } + + for _, t := range c.Targets { + // substitute the collector names list set in config by the value forced in command line argument + if c.collectorName != "" { + t.CollectorRefs = nil + t.CollectorRefs = append(t.CollectorRefs, c.collectorName) + } + cs, err := resolveCollectorRefs(t.CollectorRefs, colls, fmt.Sprintf("target %q", t.Name)) + if err != nil { + return err + } + t.collectors = cs + } + + // Check for empty/duplicate target names/data source names + tnames := make(map[string]interface{}) + for _, t := range c.Targets { + if len(t.TargetsFiles) > 0 { + continue + } + if t.Name == "" { + return fmt.Errorf("empty target name in static config %+v", t) + } + if _, ok := tnames[t.Name]; ok { + return fmt.Errorf("duplicate target name %q in target %+v", t.Name, t) + } + tnames[t.Name] = nil + + if t.ConnectionTimeout == 0 { + t.ConnectionTimeout = c.Globals.ConnectionTimeout + } + + if t.QueryRetry == -1 { + t.QueryRetry = c.Globals.QueryRetry + } + } + + // check HttpAPIConfig script: + for name, sc := range c.HttpAPIConfig { + if sc != nil { + sc.name = name + // have to set the action to play for play_script_action + for _, a := range sc.Actions { + if a.Type() == play_script_action || a.Type() == actions_action { + if err := a.SetPlayAction(c.HttpAPIConfig); err != nil { + return err + } + } + } + } + } + + return checkOverflow(c.XXX, "config") +} + +func GetScriptsDef(map_src map[string]*YAMLScript) map[string]ActionsList { + var val ActionsList + scdef := make(map[string]ActionsList, len(map_src)+1) + for name, scr := range map_src { + val = (ActionsList)(nil) + if scr != nil { + val = (ActionsList)(scr.Actions) + } + scdef[name] = val + } + return scdef +} + +type dumpConfig struct { + Globals *GlobalConfig `yaml:"global"` + CollectorFiles []string `yaml:"collector_files,omitempty"` + Collectors []*dumpCollectorConfig `yaml:"collectors,omitempty"` + // HttpAPIConfig map[string]*ActionsList `yaml:"httpapi_config"` + HttpAPIConfig map[string]ActionsList `yaml:"httpapi_config"` +} + +// YAML marshals the config into YAML format. +func (c *Config) YAML() ([]byte, error) { + dc := &dumpConfig{ + Globals: c.Globals, + CollectorFiles: c.CollectorFiles, + Collectors: GetCollectorsDef(c.Collectors), + HttpAPIConfig: GetScriptsDef(c.HttpAPIConfig), + } + return yaml.Marshal(dc) +} + +// loadCollectorFiles resolves all collector file globs to files and loads the collectors they define. +func (c *Config) loadCollectorFiles() error { + baseDir := filepath.Dir(c.configFile) + for _, cfglob := range c.CollectorFiles { + // Resolve relative paths by joining them to the configuration file's directory. + if len(cfglob) > 0 && !filepath.IsAbs(cfglob) { + cfglob = filepath.Join(baseDir, cfglob) + } + + // Resolve the glob to actual filenames. + cfs, err := filepath.Glob(cfglob) + level.Debug(c.logger).Log("msg", fmt.Sprintf("Checking collectors from %s", cfglob)) + if err != nil { + // The only error can be a bad pattern. + return fmt.Errorf("error parsing collector files for %s: %s", cfglob, err) + } + + // And load the CollectorConfig defined in each file. + for _, cf := range cfs { + level.Debug(c.logger).Log("msg", fmt.Sprintf("Loading collectors from %s", cf)) + buf, err := os.ReadFile(cf) + if err != nil { + return fmt.Errorf("reading collectors file %s: %s", cf, err) + } + + cc := CollectorConfig{ + symtab: map[string]any{}, + } + err = yaml.Unmarshal(buf, &cc) + if err != nil { + return fmt.Errorf("reading %s: %s", cf, err) + } + c.Collectors = append(c.Collectors, &cc) + level.Info(c.logger).Log("msg", fmt.Sprintf("Loaded collector %s from %s", cc.Name, cf)) + } + } + + return nil +} + +// loadTargetsFiles resolves all targets file globs to files and loads the targets they define. +func (c *Config) loadTargetsFiles(targetFilepath []string) error { + baseDir := filepath.Dir(c.configFile) + for _, tfglob := range targetFilepath { + // Resolve relative paths by joining them to the configuration file's directory. + if len(tfglob) > 0 && !filepath.IsAbs(tfglob) { + tfglob = filepath.Join(baseDir, tfglob) + } + + // Resolve the glob to actual filenames. + tfs, err := filepath.Glob(tfglob) + level.Debug(c.logger).Log("msg", fmt.Sprintf("Checking targets from %s", tfglob)) + if err != nil { + // The only error can be a bad pattern. + return fmt.Errorf("error resolving targets_files files for %s: %s", tfglob, err) + } + + // And load the CollectorConfig defined in each file. + for _, tf := range tfs { + level.Debug(c.logger).Log("msg", fmt.Sprintf("Loading targets from %s", tf)) + buf, err := os.ReadFile(tf) + if err != nil { + return fmt.Errorf("reading targets_files for %s: %s", tf, err) + } + + target := TargetConfig{} + err = yaml.Unmarshal(buf, &target) + if err != nil { + return fmt.Errorf("parsing targets_files for %s: %s", tf, err) + } + target.setFromFile(tf) + c.Targets = append(c.Targets, &target) + level.Info(c.logger).Log("msg", fmt.Sprintf("Loaded target '%q' from %s", target.Name, tf)) + } + } + + return nil +} + +// GlobalConfig contains globally applicable defaults. +type GlobalConfig struct { + MinInterval model.Duration `yaml:"min_interval"` // minimum interval between query executions, default is 0 + ConnectionTimeout model.Duration `yaml:"connection_timeout"` // connection timeout, target + ScrapeTimeout model.Duration `yaml:"scrape_timeout"` // per-scrape timeout, global + TimeoutOffset model.Duration `yaml:"scrape_timeout_offset"` // offset to subtract from timeout in seconds + MetricPrefix string `yaml:"metric_prefix"` // a prefix to ad dto all metric name; may be redefined in collector files + QueryRetry int `yaml:"query_retry,omitempty"` // target specific number of times to retry a query + InvalidHttpCode any `yaml:"invalid_auth_code,omitempty"` + ExporterName string `yaml:"exporter_name,omitempty"` + + invalid_auth_code []int + // query_retry int + // Catches all undefined fields and must be empty after parsing. + XXX map[string]interface{} `yaml:",inline" json:"-"` +} + +// UnmarshalYAML implements the yaml.Unmarshaler interface for GlobalConfig. +func (g *GlobalConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { + // Default to running the queries on every scrape. + g.MinInterval = model.Duration(0) + // Default to 2 seconds, to connect to a target. + g.ConnectionTimeout = model.Duration(2 * time.Second) + // Default to 10 seconds, since Prometheus has a 10 second scrape timeout default. + g.ScrapeTimeout = model.Duration(10 * time.Second) + // Default to .5 seconds. + g.TimeoutOffset = model.Duration(500 * time.Millisecond) + g.ExporterName = exporter_name + + // Default tp 3 + g.QueryRetry = 3 + + // Default to hp3par + g.MetricPrefix = "hp3par" + + type plain GlobalConfig + if err := unmarshal((*plain)(g)); err != nil { + return err + } + + if g.TimeoutOffset <= 0 { + return fmt.Errorf("global.scrape_timeout_offset must be strictly positive, have %s", g.TimeoutOffset) + } + if g.ConnectionTimeout <= 0 { + return fmt.Errorf("global.connection_timeout must be strictly positive, have %s", g.ConnectionTimeout) + } + + if g.InvalidHttpCode == nil { + g.invalid_auth_code = []int{401, 403} + } else { + g.invalid_auth_code = buildStatus(g.InvalidHttpCode) + } + + return checkOverflow(g.XXX, "global") +} + +// +// Targets +// + +// TargetConfig defines a url and a set of collectors to be executed on it. +type TargetConfig struct { + Name string `yaml:"name"` // target name to connect to from prometheus + Scheme string `yaml:"scheme"` + Host string `yaml:"host"` + Port string `yaml:"port,omitempty"` + BaseUrl string `yaml:"baseUrl,omitempty"` + AuthConfig AuthConfig `yaml:"auth_mode,omitempty"` + // Username string `yaml:"user,omitempty"` + // Password Secret `yaml:"password,omitempty"` // data source definition to connect to + // BasicAuth ConvertibleBoolean `yaml:"basicAuth"` + ProxyUrl string `yaml:"proxy,omitempty"` + VerifySSL ConvertibleBoolean `yaml:"verifySSL,omitempty"` + ConnectionTimeout model.Duration `yaml:"connection_timeout,omitempty"` // connection timeout, per-target + Labels map[string]string `yaml:"labels,omitempty"` // labels to apply to all metrics collected from the targets + CollectorRefs []string `yaml:"collectors"` // names of collectors to execute on the target + TargetsFiles []string `yaml:"targets_files,omitempty"` // slice of path and pattern for files that contains targets + QueryRetry int `yaml:"query_retry,omitempty"` // target specific number of times to retry a query + + collectors []*CollectorConfig // resolved collector references + fromFile string // filepath if loaded from targets_files pattern + // basicAuth bool + // verifySSL bool + + // Catches all undefined fields and must be empty after parsing. + XXX map[string]interface{} `yaml:",inline" json:"-"` +} + +// Collectors returns the collectors referenced by the target, resolved. +func (t *TargetConfig) Collectors() []*CollectorConfig { + return t.collectors +} + +// set fromFile for target when read from targets_files directive +func (t *TargetConfig) setFromFile(file_path string) { + t.fromFile = file_path +} + +// UnmarshalYAML implements the yaml.Unmarshaler interface for TargetConfig. +func (t *TargetConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { + type plain TargetConfig + // set default value for target + t.QueryRetry = -1 + t.VerifySSL = true + + if err := unmarshal((*plain)(t)); err != nil { + return err + } + + // Check required fields + if len(t.TargetsFiles) == 0 { + if t.Name == "" { + return fmt.Errorf("empty target name in target %+v", t) + } + + if t.Scheme == "" { + t.Scheme = "https" + } + if t.Port == "" { + t.Port = "443" + } + if t.BaseUrl != "" { + t.BaseUrl = strings.Trim(t.BaseUrl, "/") + } + + if t.Host == "" { + return fmt.Errorf("missing data_source_name for target %+v", t) + } + checkCollectorRefs(t.CollectorRefs, t.Name) + + if len(t.Labels) > 0 { + err := t.checkLabelCollisions() + if err != nil { + return err + } + } + } else { + for _, file := range t.TargetsFiles { + if file == "" { + return fmt.Errorf("missing targets_files pattern") + } + } + } + if t.AuthConfig.Mode == "" { + t.AuthConfig.Mode = "basic" + } + + return checkOverflow(t.XXX, "target") +} + +// checkLabelCollisions checks for label collisions between StaticConfig labels and Metric labels. +func (t *TargetConfig) checkLabelCollisions() error { + sclabels := make(map[string]interface{}) + for _, l := range t.Labels { + sclabels[l] = nil + } + + for _, c := range t.collectors { + // for _, m := range c.Metrics { + for _, cs := range c.CollectScripts { + for _, a := range cs.Actions { + fmt.Printf("action type: %s", reflect.TypeOf(a)) + reslist := a.GetMetrics() + for _, res := range reslist { + for _, m := range res.mc { + if keymap, ok := m.KeyLabels.(map[string]string); ok { + for _, l := range keymap { + if _, ok := sclabels[l]; ok { + return fmt.Errorf( + "label collision in target %q: label %q is defined both by a static_config and by metric %q of collector %q", + t.Name, l, m.Name, c.Name) + } + } + } + } + } + } + } + } + return nil +} + +// +// Collectors +// + +// type mapScript map[string]*YAMLScript + +// func (m mapScript) MarshalText() (text []byte, err error) { +// var res []byte +// for name, sc := range m { +// b, err := yaml.Marshal(sc) +// if err != nil { +// return nil, err +// } +// res = append(res, []byte(name)...) +// res = append(res, []byte(":\n")...) +// b = bytes.Replace(b, []byte("|\n"), []byte(""), 1) +// res = append(res, b...) +// } +// return res, nil +// } + +// CollectorConfig defines a set of metrics and how they are collected. +type CollectorConfig struct { + Name string `yaml:"collector_name"` // name of this collector + MetricPrefix string `yaml:"metric_prefix,omitempty"` // a prefix to ad dto all metric name; may be redefined in collector files + MinInterval model.Duration `yaml:"min_interval,omitempty"` // minimum interval between query executions + // Metrics []*MetricConfig `yaml:"metrics"` // metrics/queries defined by this collector + Templates map[string]string `yaml:"templates,omitempty"` // share custom templates/funcs for results templating + CollectScripts map[string]*YAMLScript `yaml:"scripts,omitempty"` // map of all independent scripts to collect metrics - each script can run in parallem + // CollectScripts mapScript `yaml:"scripts,omitempty"` // map of all independent scripts to collect metrics - each script can run in parallem + symtab map[string]any + + customTemplate *exporterTemplate // to store the custom Templates used by this collector + // Metrics []*MetricConfig // metrics defined by this collector + + // Catches all undefined fields and must be empty after parsing. + XXX map[string]interface{} `yaml:",inline" json:"-"` +} + +// UnmarshalYAML implements the yaml.Unmarshaler interface for CollectorConfig. +func (c *CollectorConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { + // Default to undefined (a negative value) so it can be overridden by the global default when not explicitly set. + c.MinInterval = -1 + c.MetricPrefix = "" + + type plain CollectorConfig + if err := unmarshal((*plain)(c)); err != nil { + return err + } + + // if len(c.Metrics) == 0 { + // return fmt.Errorf("no metrics defined for collector %q", c.Name) + // } + + // build the default templates/funcs that my be used by all templates + if len(c.Templates) > 0 { + // c.customTemplate = template.New("default").Funcs(sprig.FuncMap()) + c.customTemplate = (*exporterTemplate)(template.New("default").Funcs(mymap())) + if c.customTemplate == nil { + return fmt.Errorf("for collector %s template is invalid", c.Name) + } + for name, tmpl := range c.Templates { + def := "{{- define \"" + name + "\" }}" + strings.ReplaceAll(tmpl, "\n", "") + "{{ end -}}" + ptr := (*template.Template)(c.customTemplate) + if tmp_tpl, err := ptr.Parse(def); err != nil { + return fmt.Errorf("for collector %s template %s is invalid: %s", c.Name, def, err) + } else { + c.customTemplate = (*exporterTemplate)(tmp_tpl) + } + } + } + + if c.CollectScripts != nil { + for collect_script_name, c_script := range c.CollectScripts { + if c_script.name == "" { + c_script.name = collect_script_name + } + if err := c_script.AddCustomTemplate(c.customTemplate); err != nil { + err = fmt.Errorf("script %s: error with custom template: %s", c_script.name, err) + return err + } + } + } + + return checkOverflow(c.XXX, "collector") +} + +type dumpCollectorConfig struct { + Name string `yaml:"collector_name"` // name of this collector + MetricPrefix string `yaml:"metric_prefix,omitempty"` // a prefix to ad dto all metric name; may be redefined in collector files + MinInterval model.Duration `yaml:"min_interval,omitempty"` // minimum interval between query executions + Templates map[string]string `yaml:"templates,omitempty"` // share custom templates/funcs for results templating + CollectScripts map[string]ActionsList `yaml:"scripts,omitempty"` // map of all independent scripts to collect metrics - each script can run in parallem +} + +func GetCollectorsDef(src_colls []*CollectorConfig) []*dumpCollectorConfig { + colls := make([]*dumpCollectorConfig, len(src_colls)) + for idx, coll := range src_colls { + colls[idx] = &dumpCollectorConfig{ + Name: coll.Name, + MetricPrefix: coll.MetricPrefix, + MinInterval: coll.MinInterval, + Templates: coll.Templates, + CollectScripts: GetScriptsDef(coll.CollectScripts), + } + } + return colls +} + +// UnmarshalYAML implements the yaml.Unmarshaler interface for CollectorConfig. +// func (c *CollectorConfig) MarshalText() (text []byte, err error) { +// var res []byte +// // res = append(res, []byte(name)...) +// // res = append(res, []byte(":\n")...) +// // b = bytes.Replace(b, []byte("|\n"), []byte(""), 1) +// // res = append(res, b...) +// if b, err := yaml.Marshal(c.Name); err != nil { +// return nil, err +// } else { +// res = append(res, []byte("collector_name: ")...) +// res = append(res, b...) +// } + +// if b, err := yaml.Marshal(c.MetricPrefix); err != nil { +// return nil, err +// } else { +// // res = append(res, []byte(name)...) +// res = append(res, []byte("metric_prefix: ")...) +// res = append(res, b...) +// } + +// if b, err := yaml.Marshal(c.MinInterval); err != nil { +// return nil, err +// } else { +// // res = append(res, []byte(name)...) +// res = append(res, []byte("min_interval: ")...) +// res = append(res, b...) +// } + +// if len(c.Templates) > 0 { +// if b, err := yaml.Marshal(c.Templates); err != nil { +// return nil, err +// } else { +// res = append(res, []byte("templates:\n")...) +// res = append(res, b...) +// } +// } +// if b, err := yaml.Marshal(c.CollectScripts); err != nil { +// return nil, err +// } else { +// res = append(res, []byte("scripts:\n")...) +// b = bytes.Replace(b, []byte("|\n"), []byte(""), 1) +// res = append(res, b...) +// } +// return res, nil +// } + +// MetricConfig defines a Prometheus metric, the SQL query to populate it and the mapping of columns to metric +// keys/values. +type MetricConfig struct { + Name string `yaml:"metric_name"` // the Prometheus metric name + TypeString string `yaml:"type"` // the Prometheus metric type + Help string `yaml:"help"` // the Prometheus metric help text + // KeyLabels map[string]string `yaml:"key_labels,omitempty"` // expose these atributes as labels from JSON object: format name: value with name and value that should be template + KeyLabels any `yaml:"key_labels,omitempty"` // expose these atributes as labels from JSON object: format name: value with name and value that should be template + // Labels string `yaml:"labels,omitempty"` // expose these atributes as labels like key_labels but should be a variable template: format name: value with name and value that should be template + StaticLabels map[string]string `yaml:"static_labels,omitempty"` // fixed key/value pairs as static labels + ValueLabel string `yaml:"value_label,omitempty"` // with multiple value columns, map their names under this label + Values map[string]string `yaml:"values"` // expose each of these columns as a value, keyed by column name + // ResultFields []string `yaml:"results,omitempty"` // field name in JSON where to find a list of results + Scope string `yaml:"scope,omitempty"` // var path where to collect data: shortcut for {{ .scope.path.var }} + + valueType prometheus.ValueType // TypeString converted to prometheus.ValueType + key_labels_map map[string]string + key_labels *Field + // name *Field + // help *Field + // metric_type *Field + // labels *Field + // Catches all undefined fields and must be empty after parsing. + // XXX map[string]interface{} `yaml:",inline" json:"-"` +} + +// ValueType returns the metric type, converted to a prometheus.ValueType. +func (m *MetricConfig) ValueType() prometheus.ValueType { + return m.valueType +} + +// UnmarshalYAML implements the yaml.Unmarshaler interface for MetricConfig. +func (m *MetricConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { + type plain MetricConfig + if err := unmarshal((*plain)(m)); err != nil { + return err + } + + // Check required fields + if m.Name == "" { + return fmt.Errorf("missing name for metric %+v", m) + } + // if val, err := NewField(m.Name, nil); err == nil { + // m.name = val + // } else { + // return err + // } + + if m.TypeString == "" { + return fmt.Errorf("missing type for metric %q", m.Name) + } + // if val, err := NewField(m.TypeString, nil); err == nil { + // m.metric_type = val + // } else { + // return err + // } + + // help is not mandatory: so empty is valid + // if m.Help == "" { + // return fmt.Errorf("missing help for metric %q", m.Name) + // } + + // if val, err := NewField(m.Help, nil); err == nil { + // m.help = val + // } else { + // return err + // } + // if m.Labels != "" { + // if val, err := NewField(m.Help, nil); err == nil { + // m.help = val + // } else { + // return err + // } + // } + + switch strings.ToLower(m.TypeString) { + case "counter": + m.valueType = prometheus.CounterValue + case "gauge": + m.valueType = prometheus.GaugeValue + default: + return fmt.Errorf("unsupported metric type: %s", m.TypeString) + } + + // m.keyLabels := make(map[Label]*Label,0 len(m.KeyLabels)) + // Check for duplicate key labels + if m.KeyLabels != nil { + switch ktype := m.KeyLabels.(type) { + case map[string]string: + for key := range ktype { + checkLabel(key, "metric", m.Name) + } + m.key_labels_map = ktype + case map[string]any: + m.key_labels_map = make(map[string]string, len(ktype)) + for key, val_raw := range ktype { + checkLabel(key, "metric", m.Name) + if val, ok := val_raw.(string); ok { + m.key_labels_map[key] = val + } + } + case string: + if ktype != "" { + if val, err := NewField(ktype, nil); err == nil { + m.key_labels = val + } else { + return err + } + } + default: + return fmt.Errorf("key_labels should be a map[string][string] or Template(string) that will contain a map[string][string] for metric %q", m.Name) + } + } + + if len(m.Values) == 0 { + return fmt.Errorf("no values defined for metric %q", m.Name) + } + + if len(m.Values) > 1 { + // Multiple value columns but no value label to identify them + if m.ValueLabel == "" { + return fmt.Errorf("value_label must be defined for metric with multiple values %q", m.Name) + } + checkLabel(m.ValueLabel, "value_label for metric", m.Name) + } + + // return checkOverflow(m.XXX, "metric") + return nil +} + +// Secret special type for storing secrets. +type Secret string + +// UnmarshalYAML implements the yaml.Unmarshaler interface for Secrets. +func (s *Secret) UnmarshalYAML(unmarshal func(interface{}) error) error { + type plain Secret + return unmarshal((*plain)(s)) +} + +// MarshalYAML implements the yaml.Marshaler interface for Secrets. +func (s Secret) MarshalYAML() (interface{}, error) { + if s != "" { + return "", nil + } + return nil, nil +} + +// ConvertibleBoolean special type to retrive 1 yes true to boolean true +type ConvertibleBoolean bool + +func (bit *ConvertibleBoolean) UnmarshalJSON(data []byte) error { + asString := strings.ToLower(string(data)) + if asString == "1" || asString == "true" || asString == "yes" || asString == "on" { + *bit = true + } else if asString == "0" || asString == "false" || asString == "no" || asString == "off" { + *bit = false + } else { + return fmt.Errorf("boolean unmarshal error: invalid input %s", asString) + } + return nil +} + +type AuthConfig struct { + Mode string `yaml:"mode,omitempty"` // basic, encrypted, bearer + Username string `yaml:"user,omitempty"` + Password Secret `yaml:"password,omitempty"` + Token Secret `yaml:"token,omitempty"` + authKey string +} + +// UnmarshalYAML implements the yaml.Unmarshaler interface for authConfig +func (auth *AuthConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { + type plain AuthConfig + if err := unmarshal((*plain)(auth)); err != nil { + return err + } + + // Check required fields + if auth.Mode == "" { + auth.Mode = "basic" + } else { + auth.Mode = strings.ToLower(auth.Mode) + mode := make(map[string]int) + for _, val := range []string{"basic", "token", "script"} { + mode[val] = 1 + } + if _, err := mode[auth.Mode]; !err { + return fmt.Errorf("invalid mode auth %s", auth.Mode) + } + } + if auth.Mode == "token" && auth.Token == "" { + return fmt.Errorf("token not set with auth mode 'token'") + } + + return nil +} + +// MarshalYAML implements the yaml.Marshaler interface for AuthConfig. +// func (auth AuthConfig) MarshalYAML() (interface{}, error) { +// var ( +// ymlname string +// val string +// buffer bytes.Buffer +// ) +// nptype := reflect.TypeOf(auth) +// values := reflect.ValueOf(auth) +// for i := 0; i < nptype.NumField(); i++ { +// // prints empty line if there is no json tag for the field +// field := nptype.Field(i) +// val = "" +// t_name := field.Type.Name() +// if t_name == "string" || t_name == "Secret" { +// val = values.Field(i).String() +// if val == "" { +// continue +// } +// } +// tags := field.Tag.Get("yaml") +// elmts := strings.Split(tags, ",") +// if len(elmts) > 0 { +// ymlname = elmts[0] +// } else { +// ymlname = "" +// } +// if field.Type.Name() == "string" { +// // val = values.Field(i) +// buffer.WriteString(ymlname) +// buffer.WriteString(": ") +// buffer.WriteString(val) +// buffer.WriteString("\n") +// } else { +// indent := " " +// buffer.WriteString(ymlname) +// buffer.WriteString(":\n") +// value := values.Field(i) +// b, err := yaml.Marshal(value.Interface()) +// if err == nil { +// b = bytes.TrimRight(b, "\n") +// res := bytes.SplitAfter(b, []byte("\n")) +// if len(res) > 1 { +// for _, val := range res { +// buffer.WriteString(indent) +// buffer.Write(val) +// // buffer.WriteString("\n") +// } +// } +// } else { +// return nil, err +// } +// } +// } +// return buffer.String(), nil +// } + +// ************************************************************************************************* +func checkCollectorRefs(collectorRefs []string, ctx string) error { + // At least one collector, no duplicates + if len(collectorRefs) == 0 { + return fmt.Errorf("no collectors defined for %s", ctx) + } + for i, ci := range collectorRefs { + for _, cj := range collectorRefs[i+1:] { + if ci == cj { + return fmt.Errorf("duplicate collector reference %q in %s", ci, ctx) + } + } + } + return nil +} + +func resolveCollectorRefs( + collectorRefs []string, collectors map[string]*CollectorConfig, ctx string) ([]*CollectorConfig, error) { + resolved := make([]*CollectorConfig, 0, len(collectorRefs)) + for _, cref := range collectorRefs { + // check if cref(a collector name) is a pattern or not + if strings.HasPrefix(cref, "~") { + pat := regexp.MustCompile(cref[1:]) + for c_name, c := range collectors { + if pat.MatchString(c_name) { + resolved = append(resolved, c) + } + } + } else { + c, found := collectors[cref] + if !found { + return nil, fmt.Errorf("unknown collector %q referenced in %s", cref, ctx) + } + resolved = append(resolved, c) + } + } + return resolved, nil +} + +func checkLabel(label string, ctx ...string) error { + if label == "" { + return fmt.Errorf("empty label defined in %s", strings.Join(ctx, " ")) + } + if label == "job" || label == "instance" { + return fmt.Errorf("reserved label %q redefined in %s", label, strings.Join(ctx, " ")) + } + return nil +} + +func checkOverflow(m map[string]interface{}, ctx string) error { + if len(m) > 0 { + var keys []string + for k := range m { + keys = append(keys, k) + } + return fmt.Errorf("unknown fields '%s' in '%s'", strings.Join(keys, ", "), ctx) + } + return nil +} diff --git a/content.go b/content.go new file mode 100644 index 0000000..fc9ed63 --- /dev/null +++ b/content.go @@ -0,0 +1,237 @@ +package main + +import ( + "fmt" + "html/template" + "net/http" + "runtime" + + "github.com/prometheus/common/version" + "gopkg.in/yaml.v3" +) + +const ( + docsUrl = "https://github.com/peekjef72/httpapi_exporter#readme" + templates = ` + {{ define "page" -}} + + + Prometheus {{ .ExporterName }} + + + + + {{template "content" .}} + + + {{- end }} + + {{ define "content.home" -}} +

This is a Prometheus {{ .ExporterName }} instance. + You are probably looking for its metrics handler.

+ {{- end }} + + {{ define "content.config" -}} +

Configuration

+
{{ .Config }}
+ {{- end }} + + {{ define "content.targets" -}} +

Targets

+
{{ .Targets }}
+ {{- end }} + + {{ define "content.status" -}} +

Build Information

+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
Version{{ .Version.Version }}
Revision{{ .Version.Revision }}
Branch{{ .Version.Branch }}
BuildUser{{ .Version.BuildUser }}
BuildDate{{ .Version.BuildDate }}
GoVersion{{ .Version.GoVersion }}
+ {{- end }} + + {{ define "content.error" -}} +

Error

+
{{ .Err }}
+ {{- end }} + ` +) + +type versionInfo struct { + Version string + Revision string + Branch string + BuildUser string + BuildDate string + GoVersion string +} +type tdata struct { + ExporterName string + MetricsPath string + DocsUrl string + + // `/config` only + Config string + + // `:targets` only + Targets string + + // status + Version versionInfo + // `/error` only + Err error +} + +var ( + allTemplates = template.Must(template.New("").Parse(templates)) + homeTemplate = pageTemplate("home") + configTemplate = pageTemplate("config") + targetsTemplate = pageTemplate("targets") + statusTemplate = pageTemplate("status") + errorTemplate = pageTemplate("error") +) + +func pageTemplate(name string) *template.Template { + pageTemplate := fmt.Sprintf(`{{define "content"}}{{template "content.%s" .}}{{end}}{{template "page" .}}`, name) + return template.Must(template.Must(allTemplates.Clone()).Parse(pageTemplate)) +} + +// HomeHandlerFunc is the HTTP handler for the home page (`/`). +func HomeHandlerFunc(metricsPath string, exporter Exporter) func(http.ResponseWriter, *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + homeTemplate.Execute(w, &tdata{ + ExporterName: exporter.Config().Globals.ExporterName, + MetricsPath: metricsPath, + DocsUrl: docsUrl, + }) + } +} + +// ConfigHandlerFunc is the HTTP handler for the `/config` page. It outputs the configuration marshaled in YAML format. +func ConfigHandlerFunc(metricsPath string, exporter Exporter) func(http.ResponseWriter, *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + config, err := exporter.Config().YAML() + if err != nil { + HandleError(0, err, metricsPath, exporter, w, r) + return + } + configTemplate.Execute(w, &tdata{ + ExporterName: exporter.Config().Globals.ExporterName, + MetricsPath: metricsPath, + DocsUrl: docsUrl, + Config: string(config), + }) + } +} + +// ConfigHandlerFunc is the HTTP handler for the `/config` page. It outputs the configuration marshaled in YAML format. +func StatusHandlerFunc(metricsPath string, exporter Exporter) func(http.ResponseWriter, *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + + vinfos := versionInfo{ + Version: version.Version, + Revision: version.Revision, + Branch: version.Branch, + BuildUser: version.BuildUser, + BuildDate: version.BuildDate, + GoVersion: runtime.Version(), + } + + statusTemplate.Execute(w, &tdata{ + ExporterName: exporter.Config().Globals.ExporterName, + MetricsPath: metricsPath, + DocsUrl: docsUrl, + Version: vinfos, + }) + } +} + +// TargetsHandlerFunc is the HTTP handler for the `/target` page. It outputs the targets configuration marshaled in YAML format. +func TargetsHandlerFunc(metricsPath string, exporter Exporter) func(http.ResponseWriter, *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + var targets_cfg []byte + var err error + c := exporter.Config() + // for _, t := range c.Targets { + targets_cfg, err = yaml.Marshal(c.Targets) + if err != nil { + // content = nil + HandleError(0, err, metricsPath, exporter, w, r) + return + } + // targets_cfg = append(targets_cfg, content...) + // } + targetsTemplate.Execute(w, &tdata{ + ExporterName: exporter.Config().Globals.ExporterName, + MetricsPath: metricsPath, + DocsUrl: docsUrl, + Targets: string(targets_cfg), + }) + } +} + +// HandleError is an error handler that other handlers defer to in case of error. It is important to not have written +// anything to w before calling HandleError(), or the 500 status code won't be set (and the content might be mixed up). +func HandleError(status int, err error, metricsPath string, exporter Exporter, w http.ResponseWriter, r *http.Request) { + if status == 0 { + status = http.StatusInternalServerError + } + w.WriteHeader(status) + errorTemplate.Execute(w, &tdata{ + ExporterName: exporter.Config().Globals.ExporterName, + MetricsPath: metricsPath, + DocsUrl: docsUrl, + Err: err, + }) +} diff --git a/contribs/dashboards/CITRIX ADC Netscaler Stats.json b/contribs/dashboards/CITRIX ADC Netscaler Stats.json new file mode 100644 index 0000000..6a4000c --- /dev/null +++ b/contribs/dashboards/CITRIX ADC Netscaler Stats.json @@ -0,0 +1,6410 @@ +{ + "__inputs": [ + { + "name": "DS_PROMETHEUS", + "label": "Prometheus", + "description": "", + "type": "datasource", + "pluginId": "prometheus", + "pluginName": "Prometheus" + } + ], + "__requires": [ + { + "type": "grafana", + "id": "grafana", + "name": "Grafana", + "version": "7.5.16" + }, + { + "type": "panel", + "id": "graph", + "name": "Graph", + "version": "" + }, + { + "type": "datasource", + "id": "prometheus", + "name": "Prometheus", + "version": "1.0.0" + }, + { + "type": "panel", + "id": "stat", + "name": "Stat", + "version": "" + }, + { + "type": "panel", + "id": "table", + "name": "Table", + "version": "" + } + ], + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": "-- Grafana --", + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "gnetId": null, + "graphTooltip": 1, + "id": null, + "iteration": 1695549945022, + "links": [], + "panels": [ + { + "collapsed": true, + "datasource": null, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 59, + "panels": [ + { + "datasource": null, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [ + { + "from": "", + "id": 1, + "text": "Down", + "to": "", + "type": 1, + "value": "0" + }, + { + "from": "", + "id": 2, + "text": "Up", + "to": "", + "type": 1, + "value": "1" + } + ], + "noValue": "N/A", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "blue", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 9, + "x": 0, + "y": 1 + }, + "id": 57, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "/^edition$/", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "7.5.11", + "targets": [ + { + "exemplar": true, + "expr": "citrixadc_bandwidth_licensed{instance=~\"$instance\"}", + "format": "table", + "interval": "", + "legendFormat": "", + "refId": "A" + } + ], + "title": "Edition", + "type": "stat" + }, + { + "datasource": null, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [ + { + "from": "", + "id": 1, + "text": "Down", + "to": "", + "type": 1, + "value": "0" + }, + { + "from": "", + "id": 2, + "text": "Up", + "to": "", + "type": 1, + "value": "1" + } + ], + "noValue": "N/A", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "blue", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 15, + "x": 9, + "y": 1 + }, + "id": 56, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "/^version$/", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "7.5.11", + "targets": [ + { + "exemplar": true, + "expr": "citrixadc_bandwidth_licensed{instance=~\"$instance\"}", + "format": "table", + "interval": "", + "legendFormat": "", + "refId": "A" + } + ], + "title": "version", + "type": "stat" + }, + { + "datasource": null, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [ + { + "from": "", + "id": 1, + "text": "Down", + "to": "", + "type": 1, + "value": "0" + }, + { + "from": "", + "id": 2, + "text": "Up", + "to": "", + "type": 1, + "value": "1" + } + ], + "noValue": "N/A", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "blue", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 6, + "x": 6, + "y": 4 + }, + "id": 62, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "/^status$/", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "7.5.11", + "targets": [ + { + "exemplar": true, + "expr": "citrixadc_ha_available{instance=~\"$instance\"}", + "format": "table", + "interval": "", + "legendFormat": "", + "refId": "A" + } + ], + "title": "HA Available", + "type": "stat" + }, + { + "datasource": null, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [ + { + "from": "", + "id": 1, + "text": "Down", + "to": "", + "type": 1, + "value": "0" + }, + { + "from": "", + "id": 2, + "text": "Up", + "to": "", + "type": 1, + "value": "1" + } + ], + "noValue": "N/A", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "blue", + "value": null + } + ] + }, + "unit": "Mbits" + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 6, + "x": 12, + "y": 4 + }, + "id": 730, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "7.5.11", + "targets": [ + { + "exemplar": true, + "expr": "citrixadc_bandwidth_licensed{instance=~\"$instance\"}", + "format": "table", + "interval": "", + "legendFormat": "", + "refId": "A" + } + ], + "title": "Licensed Bandwidth", + "type": "stat" + } + ], + "title": "Configuration", + "type": "row" + }, + { + "collapsed": false, + "datasource": null, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 1 + }, + "id": 61, + "panels": [], + "title": "High Availability", + "type": "row" + }, + { + "datasource": null, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [ + { + "from": "", + "id": 1, + "text": "Down", + "to": "", + "type": 1, + "value": "0" + }, + { + "from": "", + "id": 2, + "text": "Up", + "to": "", + "type": 1, + "value": "1" + } + ], + "noValue": "Down (n/a)", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "dark-red", + "value": null + }, + { + "color": "dark-green", + "value": 1 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 2 + }, + "id": 63, + "options": { + "colorMode": "background", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "last" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "7.5.16", + "targets": [ + { + "exemplar": true, + "expr": "citrixadc_ha_state{instance=~\"$instance\"}", + "format": "time_series", + "interval": "", + "legendFormat": "", + "refId": "A" + } + ], + "title": "State", + "type": "stat" + }, + { + "datasource": null, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [ + { + "from": "", + "id": 1, + "text": "Other", + "to": "", + "type": 1, + "value": "0" + }, + { + "from": "", + "id": 2, + "text": "Primary", + "to": "", + "type": 1, + "value": "1" + }, + { + "from": "", + "id": 3, + "text": "Secondary", + "to": "", + "type": 1, + "value": "2" + } + ], + "noValue": "N/A", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "semi-dark-red", + "value": null + }, + { + "color": "semi-dark-green", + "value": 1 + }, + { + "color": "#EAB839", + "value": 2 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 6, + "x": 6, + "y": 2 + }, + "id": 64, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "last" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "7.5.16", + "targets": [ + { + "exemplar": true, + "expr": "citrixadc_ha_node_state{instance=~\"$instance\"}", + "format": "time_series", + "instant": true, + "interval": "", + "legendFormat": "", + "refId": "A" + } + ], + "title": "Node Status", + "type": "stat" + }, + { + "datasource": null, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "noValue": "N/A", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "blue", + "value": null + } + ] + }, + "unit": "dateTimeAsIso" + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 12, + "x": 12, + "y": 2 + }, + "id": 65, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "7.5.16", + "targets": [ + { + "exemplar": true, + "expr": "citrixadc_ha_node_start_timestamp{instance=~\"$instance\"}*1000", + "format": "time_series", + "instant": true, + "interval": "", + "legendFormat": "", + "refId": "A" + } + ], + "title": "Last transition", + "type": "stat" + }, + { + "datasource": null, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "noValue": "N/A", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "#EAB839", + "value": 5 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 6, + "x": 6, + "y": 5 + }, + "id": 66, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "7.5.16", + "targets": [ + { + "exemplar": true, + "expr": "citrixadc_ha_node_propagation_timeout_total{instance=~\"$instance\"}", + "format": "time_series", + "interval": "", + "legendFormat": "count", + "refId": "A" + } + ], + "title": "Propagation timeout Count", + "type": "stat" + }, + { + "datasource": null, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "noValue": "N/A", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "#EAB839", + "value": 5 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 6, + "x": 12, + "y": 5 + }, + "id": 67, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "last" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "7.5.16", + "targets": [ + { + "exemplar": true, + "expr": "citrixadc_ha_node_sync_failure_total{instance=~\"$instance\"}", + "format": "time_series", + "interval": "", + "legendFormat": "count", + "refId": "A" + } + ], + "title": "Sync Failure", + "type": "stat" + }, + { + "collapsed": false, + "datasource": null, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 8 + }, + "id": 18, + "panels": [], + "title": "System", + "type": "row" + }, + { + "datasource": null, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [ + { + "from": "", + "id": 1, + "text": "Down", + "to": "", + "type": 1, + "value": "0" + }, + { + "from": "", + "id": 2, + "text": "Up", + "to": "", + "type": 1, + "value": "1" + } + ], + "noValue": "Down", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "dark-red", + "value": null + }, + { + "color": "dark-green", + "value": 1 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 9, + "x": 0, + "y": 9 + }, + "id": 16, + "options": { + "colorMode": "background", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "last" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "7.5.16", + "targets": [ + { + "exemplar": true, + "expr": "citrixadc_probe_success{instance=~\"$instance\"}", + "format": "time_series", + "interval": "", + "legendFormat": "", + "refId": "A" + } + ], + "title": "State", + "type": "stat" + }, + { + "datasource": null, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "noValue": "N/A", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "blue", + "value": null + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 8, + "x": 9, + "y": 9 + }, + "id": 48, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "7.5.16", + "targets": [ + { + "exemplar": true, + "expr": "citrixadc_cpu_number_total{instance=~\"$instance\"}", + "format": "table", + "interval": "", + "legendFormat": "", + "refId": "A" + } + ], + "title": "CPU ", + "type": "stat" + }, + { + "datasource": null, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [ + { + "from": "", + "id": 1, + "text": "Down", + "to": "", + "type": 1, + "value": "0" + }, + { + "from": "", + "id": 2, + "text": "Up", + "to": "", + "type": 1, + "value": "1" + } + ], + "noValue": "N/A", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "blue", + "value": null + } + ] + }, + "unit": "decbytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 7, + "x": 17, + "y": 9 + }, + "id": 173, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "7.5.16", + "targets": [ + { + "exemplar": true, + "expr": "citrixadc_memory_total_available_bytes{instance=~\"$instance\"}", + "format": "table", + "interval": "", + "legendFormat": "", + "refId": "A" + } + ], + "title": "Memory", + "type": "stat" + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": null, + "fieldConfig": { + "defaults": { + "unit": "percentunit" + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 11, + "w": 12, + "x": 0, + "y": 12 + }, + "hiddenSeries": false, + "id": 13, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": true, + "show": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.16", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [ + { + "$$hashKey": "object:7745", + "alias": "Total", + "hiddenSeries": true + } + ], + "spaceLength": 10, + "stack": true, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "( citrixadc_ns_rx_mbits_rate {instance=~\"$instance\"} / sum(citrixadc_bandwidth_licensed{instance=~\"$instance\"}) without(edition,version) )", + "format": "time_series", + "interval": "", + "intervalFactor": 1, + "legendFormat": "received", + "refId": "A" + }, + { + "exemplar": true, + "expr": "( citrixadc_ns_tx_mbits_rate {instance=~\"$instance\"} / sum(citrixadc_bandwidth_licensed{instance=~\"$instance\"}) without(edition,version) )", + "format": "time_series", + "hide": false, + "interval": "", + "intervalFactor": 1, + "legendFormat": "transmitted", + "refId": "B" + }, + { + "exemplar": true, + "expr": "( (citrixadc_ns_rx_mbits_rate {instance=~\"$instance\"} + citrixadc_ns_tx_mbits_rate {instance=~\"$instance\"}) / sum(citrixadc_bandwidth_licensed{instance=~\"$instance\"}) without(edition,version) )", + "format": "time_series", + "hide": false, + "interval": "", + "intervalFactor": 1, + "legendFormat": "Total", + "refId": "C" + } + ], + "thresholds": [ + { + "$$hashKey": "object:7758", + "colorMode": "critical", + "fill": true, + "line": true, + "op": "gt", + "value": 1, + "yaxis": "left" + } + ], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Licensed Throughput percent (stacked)", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:82", + "format": "percentunit", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "$$hashKey": "object:83", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": null, + "fieldConfig": { + "defaults": { + "unit": "Mbits" + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 11, + "w": 12, + "x": 12, + "y": 12 + }, + "hiddenSeries": false, + "id": 1179, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": true, + "show": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.16", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [ + { + "$$hashKey": "object:5767", + "alias": "transmitted", + "stack": "B" + }, + { + "$$hashKey": "object:5774", + "alias": "received", + "stack": "B" + }, + { + "$$hashKey": "object:5871", + "alias": "Licensed Bandwidth", + "color": "#C4162A", + "fill": 0 + } + ], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "citrixadc_ns_rx_mbits_rate {instance=~\"$instance\"}", + "format": "time_series", + "interval": "", + "intervalFactor": 1, + "legendFormat": "received", + "refId": "A" + }, + { + "exemplar": true, + "expr": "citrixadc_ns_tx_mbits_rate {instance=~\"$instance\"}", + "format": "time_series", + "hide": false, + "interval": "", + "intervalFactor": 1, + "legendFormat": "transmitted", + "refId": "B" + }, + { + "exemplar": true, + "expr": "citrixadc_bandwidth_licensed{instance=~\"$instance\"}", + "format": "time_series", + "hide": false, + "interval": "", + "intervalFactor": 1, + "legendFormat": "Licensed Bandwidth", + "refId": "C" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Licensed Throughput rate (stacked) ", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:82", + "format": "Mbits", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "$$hashKey": "object:83", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": null, + "fieldConfig": { + "defaults": { + "unit": "percent" + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 9, + "w": 12, + "x": 0, + "y": 23 + }, + "hiddenSeries": false, + "id": 3, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.16", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "citrixadc_packet_cpu_usage_percent{instance=~\"$instance\"}", + "format": "time_series", + "interval": "", + "intervalFactor": 1, + "legendFormat": "cpu usage", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "CPU Utilization Percentage", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:113", + "format": "percent", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "$$hashKey": "object:114", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": null, + "fieldConfig": { + "defaults": { + "unit": "percent" + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 9, + "w": 12, + "x": 12, + "y": 23 + }, + "hiddenSeries": false, + "id": 11, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.16", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "citrixadc_memory_usage_percent{instance=~\"$instance\"}", + "format": "time_series", + "interval": "", + "intervalFactor": 1, + "legendFormat": "mem % used", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Memory Utilization Percentage", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:144", + "format": "percent", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "$$hashKey": "object:145", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": null, + "fieldConfig": { + "defaults": { + "unit": "percent" + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 9, + "w": 12, + "x": 6, + "y": 32 + }, + "hiddenSeries": false, + "id": 14, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.16", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "citrixadc_var_partition_used_percent{instance=~\"$instance\"}", + "format": "time_series", + "interval": "", + "intervalFactor": 1, + "legendFormat": "/var", + "refId": "A" + }, + { + "exemplar": true, + "expr": "citrixadc_flash_partition_used_percent{instance=~\"$instance\"}", + "format": "time_series", + "hide": false, + "interval": "", + "intervalFactor": 1, + "legendFormat": "/flash", + "refId": "B" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Disk Usage", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:144", + "format": "percent", + "label": null, + "logBase": 1, + "max": "100", + "min": "0", + "show": true + }, + { + "$$hashKey": "object:145", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "collapsed": false, + "datasource": null, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 41 + }, + "id": 35, + "panels": [], + "title": "Interfaces Stats", + "type": "row" + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": null, + "description": "", + "fieldConfig": { + "defaults": { + "unit": "bps" + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 10, + "w": 12, + "x": 0, + "y": 42 + }, + "hiddenSeries": false, + "id": 36, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": true, + "show": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.16", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [ + { + "$$hashKey": "object:369", + "alias": "/^Out.*/", + "transform": "negative-Y" + } + ], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "rate(citrixadc_interface_tot_rx_bytes_total{instance=~\"$instance\"}[2m]) * 8", + "interval": "", + "legendFormat": "In {{ citrixadc_interface_id }}", + "refId": "A" + }, + { + "exemplar": true, + "expr": "rate(citrixadc_interface_tot_tx_bytes_total{instance=~\"$instance\"}[2m]) * 8", + "hide": false, + "interval": "", + "legendFormat": "Out {{ citrixadc_interface_id }}", + "refId": "B" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Average In/out Bits by second by interface", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:305", + "format": "bps", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "$$hashKey": "object:306", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": null, + "fieldConfig": { + "defaults": { + "unit": "bps" + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 10, + "w": 12, + "x": 12, + "y": 42 + }, + "hiddenSeries": false, + "id": 1453, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": true, + "show": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.16", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [ + { + "$$hashKey": "object:369", + "alias": "/^Out.*/", + "transform": "negative-Y" + } + ], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "citrixadc_interface_rx_bytes_rate{instance=~\"$instance\"} * 8", + "hide": false, + "interval": "", + "legendFormat": "In {{ citrixadc_interface_id }}", + "refId": "C" + }, + { + "exemplar": true, + "expr": "citrixadc_interface_tx_bytes_rate{instance=~\"$instance\"} * 8", + "hide": false, + "interval": "", + "legendFormat": "Out {{ citrixadc_interface_id }}", + "refId": "D" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Instant In/out Bits by second by interface", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:305", + "format": "bps", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "$$hashKey": "object:306", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": null, + "fieldConfig": { + "defaults": { + "unit": "pps" + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 52 + }, + "hiddenSeries": false, + "id": 38, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.16", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "rate(citrixadc_interface_tot_multicast_packets_total{instance=~\"$instance\"}[2m])", + "interval": "", + "legendFormat": "Multicast pkt {{ citrixadc_interface_id }}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Multicast Packet by second by interface", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:305", + "format": "pps", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "$$hashKey": "object:306", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": null, + "fieldConfig": { + "defaults": { + "unit": "pps" + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 52 + }, + "hiddenSeries": false, + "id": 37, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.16", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [ + { + "$$hashKey": "object:635", + "alias": "/^Out.*/", + "transform": "negative-Y" + } + ], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "rate(citrixadc_interface_err_dropped_rx_packets_total{instance=~\"$instance\"}[2m])", + "interval": "", + "legendFormat": "In {{ citrixadc_interface_id }}", + "refId": "A" + }, + { + "exemplar": true, + "expr": "rate(citrixadc_interface_err_dropped_tx_packets_total{instance=~\"$instance\"}[2m])", + "hide": false, + "interval": "", + "legendFormat": "Out {{ citrixadc_interface_id }}", + "refId": "B" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "In/out Dropped Pkt by second by Interface", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:305", + "format": "pps", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "$$hashKey": "object:306", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": false + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "collapsed": false, + "datasource": null, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 60 + }, + "id": 29, + "panels": [], + "title": "IP stats", + "type": "row" + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": null, + "fieldConfig": { + "defaults": { + "unit": "bps" + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 61 + }, + "hiddenSeries": false, + "id": 32, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.16", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [ + { + "$$hashKey": "object:583", + "alias": "Out", + "transform": "negative-Y" + } + ], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "rate(citrixadc_ip_tot_rx_bytes_total{instance=~\"$instance\"}[2m]) * 8", + "interval": "", + "legendFormat": "In", + "refId": "A" + }, + { + "exemplar": true, + "expr": "rate(citrixadc_ip_tx_bytes_total{instance=~\"$instance\"}[2m]) *8 ", + "hide": false, + "interval": "", + "legendFormat": "Out", + "refId": "B" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "In/out Bits by second", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:305", + "format": "bps", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "$$hashKey": "object:306", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": null, + "fieldConfig": { + "defaults": { + "unit": "pps" + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 61 + }, + "hiddenSeries": false, + "id": 30, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.16", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [ + { + "$$hashKey": "object:547", + "alias": "Out", + "transform": "negative-Y" + } + ], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "rate(citrixadc_ip_tot_rx_bytes_total{instance=~\"$instance\"}[2m])", + "interval": "", + "legendFormat": "In", + "refId": "A" + }, + { + "exemplar": true, + "expr": "rate(citrixadc_ip_tx_bytes_total{instance=~\"$instance\"}[2m])", + "hide": false, + "interval": "", + "legendFormat": "Out", + "refId": "B" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "In/out Packets by second", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:305", + "format": "pps", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "$$hashKey": "object:306", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": null, + "fieldConfig": { + "defaults": { + "unit": "cps" + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 69 + }, + "hiddenSeries": false, + "id": 31, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.16", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "rate(citrixadc_ip_tot_routed_packets_total{instance=~\"$instance\"}[2m])", + "interval": "", + "legendFormat": "routed", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Packet routed by second", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:305", + "format": "cps", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "$$hashKey": "object:306", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": null, + "fieldConfig": { + "defaults": { + "unit": "cps" + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 69 + }, + "hiddenSeries": false, + "id": 33, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.16", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "rate(citrixadc_ip_tot_fragments_total{instance=~\"$instance\"}[2m])", + "interval": "", + "legendFormat": "fragmented", + "refId": "A" + }, + { + "exemplar": true, + "expr": "rate(citrixadc_ip_tot_successful_assembly_total{instance=~\"$instance\"}[2m])", + "hide": false, + "interval": "", + "legendFormat": "assembly", + "refId": "B" + }, + { + "exemplar": true, + "expr": "rate(citrixadc_ip_tot_address_lookup_total{instance=~\"$instance\"}[2m])", + "hide": false, + "interval": "", + "legendFormat": "lookup", + "refId": "C" + }, + { + "exemplar": true, + "expr": "rate(citrixadc_ip_tot_address_lookup_fail_total{instance=~\"$instance\"}[2m])", + "hide": false, + "interval": "", + "legendFormat": "Failed lookup", + "refId": "D" + }, + { + "exemplar": true, + "expr": "rate(citrixadc_ip_tot_udp_fragments_forwarded_total{instance=~\"$instance\"}[2m])", + "hide": false, + "interval": "", + "legendFormat": "udp fragments fwd", + "refId": "E" + }, + { + "exemplar": true, + "expr": "rate(citrixadc_ip_tot_tcp_fragments_forwarded_total{instance=~\"$instance\"}[2m])", + "hide": false, + "interval": "", + "legendFormat": "tcp fragments fwd", + "refId": "F" + }, + { + "exemplar": true, + "expr": "rate(citrixadc_ip_tot_bad_checksums_total{instance=~\"$instance\"}[2m])", + "hide": false, + "interval": "", + "legendFormat": "bad checksum", + "refId": "G" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "packet stats", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:305", + "format": "cps", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "$$hashKey": "object:306", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "collapsed": false, + "datasource": null, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 77 + }, + "id": 313, + "panels": [], + "title": "UDP stats", + "type": "row" + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": null, + "fieldConfig": { + "defaults": { + "unit": "decbits" + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 78 + }, + "hiddenSeries": false, + "id": 447, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.16", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [ + { + "$$hashKey": "object:547", + "alias": "/^Out .*/", + "transform": "negative-Y" + } + ], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "rate(citrixadc_udp_tot_rx_bytes_total {instance=~\"$instance\"}[2m])", + "interval": "", + "legendFormat": "In {{ instance }}", + "refId": "A" + }, + { + "exemplar": true, + "expr": "rate(citrixadc_udp_tot_tx_bytes_total {instance=~\"$instance\"}[2m])", + "hide": false, + "interval": "", + "legendFormat": "Out {{ instance }}", + "refId": "B" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "In/out bits by second", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:305", + "format": "decbits", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "$$hashKey": "object:306", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": null, + "fieldConfig": { + "defaults": { + "unit": "pps" + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 78 + }, + "hiddenSeries": false, + "id": 453, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.16", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [ + { + "$$hashKey": "object:1966", + "alias": "/^Out /", + "transform": "negative-Y" + } + ], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "rate(citrixadc_udp_tot_rx_packets_total{instance=~\"$instance\"}[2m]) * 8", + "interval": "", + "legendFormat": "In {{ instance }}", + "refId": "A" + }, + { + "exemplar": true, + "expr": "rate(citrixadc_udp_tot_tx_packets_total{instance=~\"$instance\"}[2m]) *8 ", + "hide": false, + "interval": "", + "legendFormat": "Out {{ instance }}", + "refId": "B" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "In / Out packets by second", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:305", + "format": "pps", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "$$hashKey": "object:306", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": null, + "fieldConfig": { + "defaults": { + "unit": "bps" + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 12, + "x": 6, + "y": 86 + }, + "hiddenSeries": false, + "id": 448, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": false, + "min": false, + "show": true, + "total": true, + "values": true + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.16", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "rate(citrixadc_udp_tot_unknown_service_packets_total {instance=~\"$instance\"}[2m]) * 8", + "interval": "", + "legendFormat": "Unknown {{ instance }}", + "refId": "A" + }, + { + "exemplar": true, + "expr": "rate(citrixadc_udp_tot_bad_checksum_packets_total {instance=~\"$instance\"}[2m]) *8 ", + "hide": false, + "interval": "", + "legendFormat": "bad checksum {{ instance }}", + "refId": "B" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Unknown - bad checksum packets by second", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:305", + "format": "bps", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "$$hashKey": "object:306", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "collapsed": false, + "datasource": null, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 94 + }, + "id": 451, + "panels": [], + "title": "TCP Stats", + "type": "row" + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": null, + "fieldConfig": { + "defaults": { + "unit": "bps" + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 95 + }, + "hiddenSeries": false, + "id": 449, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.16", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [ + { + "$$hashKey": "object:547", + "alias": "/^Out .*/", + "transform": "negative-Y" + } + ], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "rate(citrixadc_tcp_tot_rx_bytes_total {instance=~\"$instance\"}[2m]) * 8 ", + "interval": "", + "legendFormat": "In {{ instance }}", + "refId": "A" + }, + { + "exemplar": true, + "expr": "rate(citrixadc_tcp_tot_tx_bytes_total{instance=~\"$instance\"}[2m]) * 8", + "hide": false, + "interval": "", + "legendFormat": "Out {{ instance }}", + "refId": "B" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "In/out bits by second", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:305", + "format": "bps", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "$$hashKey": "object:306", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": null, + "fieldConfig": { + "defaults": { + "unit": "pps" + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 95 + }, + "hiddenSeries": false, + "id": 724, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.16", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [ + { + "$$hashKey": "object:547", + "alias": "/^Out .*/", + "transform": "negative-Y" + } + ], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "rate(citrixadc_tcp_tot_rx_packets_total{instance=~\"$instance\"}[2m])", + "interval": "", + "legendFormat": "In {{ instance }}", + "refId": "A" + }, + { + "exemplar": true, + "expr": "rate(citrixadc_tcp_tot_tx_packets_total{instance=~\"$instance\"}[2m])", + "hide": false, + "interval": "", + "legendFormat": "Out {{ instance }}", + "refId": "B" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "In/out packets by second", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:305", + "format": "pps", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "$$hashKey": "object:306", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": null, + "fieldConfig": { + "defaults": { + "unit": "pps" + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 11, + "w": 12, + "x": 0, + "y": 103 + }, + "hiddenSeries": false, + "id": 455, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": true, + "rightSide": false, + "show": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.16", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "rate(citrixadc_tcp_tot_client_connections_opened_total {instance=~\"$instance\"}[2m])", + "interval": "", + "legendFormat": "client opened cnx {{ instance }}", + "refId": "A" + }, + { + "exemplar": true, + "expr": "rate(citrixadc_tcp_tot_server_connections_opened_total {instance=~\"$instance\"}[2m])", + "hide": false, + "interval": "", + "legendFormat": "server opened cnx {{ instance }}", + "refId": "B" + }, + { + "exemplar": true, + "expr": "rate(citrixadc_tcp_tot_syn_total {instance=~\"$instance\"}[2m])", + "hide": false, + "interval": "", + "legendFormat": "SYN received {{ instance }}", + "refId": "C" + }, + { + "exemplar": true, + "expr": "rate(citrixadc_tcp_tot_client_fin_total{instance=~\"$instance\"}[2m])", + "hide": false, + "interval": "", + "legendFormat": "client FIN received {{ instance }}", + "refId": "D" + }, + { + "exemplar": true, + "expr": "rate(citrixadc_tcp_tot_server_fin_total{instance=~\"$instance\"}[2m])", + "hide": false, + "interval": "", + "legendFormat": "server FIN received {{ instance }}", + "refId": "E" + }, + { + "exemplar": true, + "expr": "rate(citrixadc_tcp_tot_syn_probe_total{instance=~\"$instance\"}[2m])", + "hide": false, + "interval": "", + "legendFormat": "PROBE {{ instance }}", + "refId": "F" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "TCP metrics by second", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:305", + "format": "pps", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "$$hashKey": "object:306", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": null, + "fieldConfig": { + "defaults": { + "unit": "pps" + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 11, + "w": 12, + "x": 12, + "y": 103 + }, + "hiddenSeries": false, + "id": 1727, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": true, + "rightSide": false, + "show": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.16", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "rate(citrixadc_tcp_err_full_retransmit_total{instance=~\"$instance\"}[2m])", + "hide": false, + "interval": "", + "legendFormat": "full retransmitted {{ instance }}", + "refId": "A" + }, + { + "exemplar": true, + "expr": "rate(citrixadc_tcp_err_badchecksum_total{instance=~\"$instance\"}[2m])", + "hide": false, + "interval": "", + "legendFormat": "bad chksum {{ instance }}", + "refId": "B" + }, + { + "exemplar": true, + "expr": "rate(citrixadc_tcp_err_reset_threshold_total {instance=~\"$instance\"}[2m])", + "hide": false, + "interval": "", + "legendFormat": "RESET {{ instance }}", + "refId": "C" + }, + { + "exemplar": true, + "expr": "rate(citrixadc_tcp_err_bad_connection_state_total {instance=~\"$instance\"}[2m])", + "hide": false, + "interval": "", + "legendFormat": "BAD state {{ instance }}", + "refId": "D" + }, + { + "exemplar": true, + "expr": "rate(citrixadc_tcp_err_out_of_window_total{instance=~\"$instance\"}[2m])", + "hide": false, + "interval": "", + "legendFormat": "out of window {{ instance }}", + "refId": "E" + }, + { + "exemplar": true, + "expr": "rate(citrixadc_tcp_err_syn_dropped_congestion_total{instance=~\"$instance\"}[2m])", + "hide": false, + "interval": "", + "legendFormat": "SYN dropped congestion {{ instance }}", + "refId": "F" + }, + { + "exemplar": true, + "expr": "rate(citrixadc_tcp_err_any_port_fail_total{instance=~\"$instance\"}[2m])", + "hide": false, + "interval": "", + "legendFormat": "any port fail {{ instance }}", + "refId": "G" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "TCP \"errors\" packets by second", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:305", + "format": "pps", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "$$hashKey": "object:306", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "collapsed": false, + "datasource": null, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 114 + }, + "id": 590, + "panels": [], + "title": "HTTP Stats", + "type": "row" + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": null, + "fieldConfig": { + "defaults": { + "unit": "reqps" + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 115 + }, + "hiddenSeries": false, + "id": 454, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.16", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [ + { + "$$hashKey": "object:547", + "alias": "/^Responses /", + "transform": "negative-Y" + } + ], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "rate(citrixadc_http_tot_requests_total{instance=~\"$instance\"}[2m])", + "interval": "", + "legendFormat": "Requests {{ instance }}", + "refId": "A" + }, + { + "exemplar": true, + "expr": "rate(citrixadc_http_tot_responses_total{instance=~\"$instance\"}[2m])", + "hide": false, + "interval": "", + "legendFormat": "Responses {{ instance }}", + "refId": "B" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Request, Response by second", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:305", + "format": "reqps", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "$$hashKey": "object:306", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": null, + "fieldConfig": { + "defaults": { + "unit": "reqps" + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 115 + }, + "hiddenSeries": false, + "id": 728, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.16", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [ + { + "$$hashKey": "object:547", + "alias": "/^Responses /", + "transform": "negative-Y" + } + ], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "citrixadc_http_requests_rate{instance=~\"$instance\"}", + "interval": "", + "legendFormat": "Requests {{ instance }}", + "refId": "A" + }, + { + "exemplar": true, + "expr": "citrixadc_http_responses_rate{instance=~\"$instance\"}", + "hide": false, + "interval": "", + "legendFormat": "Responses {{ instance }}", + "refId": "B" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Request, Response instant value by second", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:305", + "format": "reqps", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "$$hashKey": "object:306", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": null, + "fieldConfig": { + "defaults": { + "unit": "reqps" + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 123 + }, + "hiddenSeries": false, + "id": 726, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.16", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [ + { + "$$hashKey": "object:2495", + "alias": "/.*response.*/", + "transform": "negative-Y" + } + ], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "rate(citrixadc_http_tot_10_requests_total{instance=~\"$instance\"}[2m])", + "interval": "", + "legendFormat": "HTTP 1.0 req {{ instance }}", + "refId": "A" + }, + { + "exemplar": true, + "expr": "rate(citrixadc_http_tot_11_requests_total{instance=~\"$instance\"}[2m])", + "hide": false, + "interval": "", + "legendFormat": "HTTP 1.1 req {{ instance }}", + "refId": "B" + }, + { + "exemplar": true, + "expr": "rate(citrixadc_http_tot_10_responses_total {instance=~\"$instance\"}[2m])", + "hide": false, + "interval": "", + "legendFormat": "HTTP 1.0 response {{ instance }}", + "refId": "C" + }, + { + "exemplar": true, + "expr": "rate(citrixadc_http_tot_10_responses_total{instance=~\"$instance\"}[2m])", + "hide": false, + "interval": "", + "legendFormat": "HTTP 1.1 response {{ instance }}", + "refId": "D" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "HTTP request/response by version by second", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:305", + "format": "reqps", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "$$hashKey": "object:306", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": null, + "fieldConfig": { + "defaults": { + "unit": "reqps" + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 123 + }, + "hiddenSeries": false, + "id": 725, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.16", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "rate(citrixadc_http_tot_gets_total{instance=~\"$instance\"}[2m])", + "interval": "", + "legendFormat": "GET {{ instance }}", + "refId": "A" + }, + { + "exemplar": true, + "expr": "rate(citrixadc_http_tot_posts_total{instance=~\"$instance\"}[2m])", + "hide": false, + "interval": "", + "legendFormat": "POST {{ instance }}", + "refId": "B" + }, + { + "exemplar": true, + "expr": "rate(citrixadc_http_tot_others_total {instance=~\"$instance\"}[2m])", + "hide": false, + "interval": "", + "legendFormat": "others {{ instance }}", + "refId": "C" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "HTTP methods by second", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:305", + "format": "reqps", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "$$hashKey": "object:306", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": null, + "fieldConfig": { + "defaults": { + "unit": "reqps" + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 131 + }, + "hiddenSeries": false, + "id": 727, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.16", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "rate(citrixadc_http_tot_chunked_requests_total{instance=~\"$instance\"}[2m])", + "interval": "", + "legendFormat": "req {{ instance }}", + "refId": "A" + }, + { + "exemplar": true, + "expr": "rate(citrixadc_http_tot_chunked_responses_total{instance=~\"$instance\"}[2m])", + "hide": false, + "interval": "", + "legendFormat": "response {{ instance }}", + "refId": "B" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "HTTP chunked by second", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:305", + "format": "reqps", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "$$hashKey": "object:306", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": null, + "fieldConfig": { + "defaults": { + "unit": "reqps" + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 131 + }, + "hiddenSeries": false, + "id": 729, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.16", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "rate(citrixadc_http_err_tot_incomplete_header_packets_total {instance=~\"$instance\"}[2m])", + "interval": "", + "legendFormat": "incomplete header {{ instance }}", + "refId": "A" + }, + { + "exemplar": true, + "expr": "rate(citrixadc_http_err_tot_server_responses_total{instance=~\"$instance\"}[2m])", + "hide": false, + "interval": "", + "legendFormat": "error response {{ instance }}", + "refId": "B" + }, + { + "exemplar": true, + "expr": "rate(citrixadc_http_err_tot_large_body_packets_total {instance=~\"$instance\"}[2m])", + "hide": false, + "interval": "", + "legendFormat": "too large body {{ instance }}", + "refId": "C" + }, + { + "exemplar": true, + "expr": "rate(citrixadc_http_err_tot_large_chunk_requests_total {instance=~\"$instance\"}[2m])", + "hide": false, + "interval": "", + "legendFormat": "too large chunked size {{ instance }}", + "refId": "D" + }, + { + "exemplar": true, + "expr": "rate(citrixadc_http_err_tot_large_content_requests_total {instance=~\"$instance\"}[2m])", + "hide": false, + "interval": "", + "legendFormat": "too large content size {{ instance }}", + "refId": "E" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "HTTP \"errors\" by second", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:305", + "format": "reqps", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "$$hashKey": "object:306", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "collapsed": true, + "datasource": null, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 139 + }, + "id": 40, + "panels": [ + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": null, + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 14, + "w": 24, + "x": 0, + "y": 140 + }, + "hiddenSeries": false, + "id": 42, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.5", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "rate(citrixadc_lb_hits_total{instance=~\"$instance\", citrixadc_lb_state=\"UP\"}[2m])", + "interval": "", + "legendFormat": "{{ citrixadc_lb_name }} {{ citrixadc_lb_type }} In", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Total Hits/s by LB Service", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:1198", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "$$hashKey": "object:1199", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": null, + "description": "", + "fieldConfig": { + "defaults": { + "unit": "percent" + }, + "overrides": [] + }, + "fill": 0, + "fillGradient": 0, + "gridPos": { + "h": 9, + "w": 24, + "x": 0, + "y": 154 + }, + "hiddenSeries": false, + "id": 896, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.5", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "sum by(citrixadc_lb_name) (citrixadc_lb_members_up_total{instance=~\"$instance\"})", + "interval": "", + "legendFormat": "client connection {{ citrixadc_lb_name }}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "percent of members UP for service", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:1198", + "format": "percent", + "label": null, + "logBase": 1, + "max": "100", + "min": null, + "show": true + }, + { + "$$hashKey": "object:1199", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + } + ], + "title": "Load Balacing", + "type": "row" + }, + { + "collapsed": false, + "datasource": null, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 140 + }, + "id": 46, + "panels": [], + "repeat": "service", + "title": "LB service Stats $service", + "type": "row" + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": null, + "fieldConfig": { + "defaults": { + "unit": "reqps" + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 9, + "w": 12, + "x": 0, + "y": 141 + }, + "hiddenSeries": false, + "id": 893, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.16", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [ + { + "$$hashKey": "object:2923", + "alias": "/Response/", + "transform": "negative-Y" + } + ], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "sum( rate(citrixadc_lb_requests_total{instance=~\"$instance\", citrixadc_lb_name=~\"$service\"}[2m]) ) without( citrixadc_lb_state )", + "interval": "", + "legendFormat": "requests {{ citrixadc_lb_name}}", + "refId": "A" + }, + { + "exemplar": true, + "expr": "sum( rate(citrixadc_lb_responses_total{instance=~\"$instance\", citrixadc_lb_name=~\"$service\"}[2m]) ) without( citrixadc_lb_state )", + "hide": false, + "interval": "", + "legendFormat": "Responses {{ citrixadc_lb_name}}", + "refId": "B" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "requests & responses by second for $service", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:1198", + "format": "reqps", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "$$hashKey": "object:1199", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": null, + "fieldConfig": { + "defaults": { + "unit": "cps" + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 9, + "w": 12, + "x": 12, + "y": 141 + }, + "hiddenSeries": false, + "id": 44, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.16", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "sum(rate(citrixadc_lb_hits_total{instance=~\"$instance\", citrixadc_lb_name=~\"$service\"}[2m])) without( citrixadc_lb_state )", + "interval": "", + "legendFormat": "Hits", + "refId": "A" + }, + { + "exemplar": true, + "expr": "sum( rate(citrixadc_lb_responses_total{instance=~\"$instance\", citrixadc_lb_name=~\"$service\"}[2m])) without( citrixadc_lb_state )", + "hide": true, + "interval": "", + "legendFormat": "Responses", + "refId": "B" + }, + { + "exemplar": true, + "expr": "sum(rate(citrixadc_lb_surge_count_total{instance=~\"$instance\", citrixadc_lb_name=~\"$service\"}[2m]) ) without( citrixadc_lb_state )", + "hide": false, + "interval": "", + "legendFormat": "Surge", + "refId": "C" + }, + { + "exemplar": true, + "expr": "sum( rate(citrixadc_lb_tolerable_transactions_count_total{instance=~\"$instance\", citrixadc_lb_name=~\"$service\"}[2m])) without( citrixadc_lb_state )", + "hide": false, + "interval": "", + "legendFormat": "Tolerable Transactions", + "refId": "D" + }, + { + "exemplar": true, + "expr": "sum( rate(citrixadc_lb_ttlb_calculated_transactions_total{instance=~\"$instance\", citrixadc_lb_name=~\"$service\"}[2m]) ) without( citrixadc_lb_state )", + "hide": false, + "interval": "", + "legendFormat": "Calculated Transactions", + "refId": "E" + }, + { + "exemplar": true, + "expr": "sum( rate(citrixadc_lb_deffered_requests_total{instance=~\"$instance\", citrixadc_lb_name=~\"$service\"}[2m]) ) without( citrixadc_lb_state )", + "hide": false, + "interval": "", + "legendFormat": "deffered req", + "refId": "F" + }, + { + "exemplar": true, + "expr": "sum( rate(citrixadc_lb_spillover_count_total{instance=~\"$instance\", citrixadc_lb_name=~\"$service\"}[2m]) ) without( citrixadc_lb_state )", + "hide": false, + "interval": "", + "legendFormat": "Spillover", + "refId": "G" + }, + { + "exemplar": true, + "expr": "sum( rate(citrixadc_lb_invalid_response_request_total{instance=~\"$instance\", citrixadc_lb_name=~\"$service\"}[2m]) ) without( citrixadc_lb_state )", + "hide": false, + "interval": "", + "legendFormat": "invalid req", + "refId": "H" + }, + { + "exemplar": true, + "expr": "sum( rate(citrixadc_lb_invalid_response_request_dropped_total{instance=~\"$instance\", citrixadc_lb_name=~\"$service\"}[2m])) without( citrixadc_lb_state )", + "hide": false, + "interval": "", + "legendFormat": "dropped req", + "refId": "I" + }, + { + "exemplar": true, + "expr": "sum( rate(citrixadc_lb_busy_error_total{instance=~\"$instance\", citrixadc_lb_name=~\"$service\"}[2m]) ) without( citrixadc_lb_state )", + "hide": false, + "interval": "", + "legendFormat": "busy error", + "refId": "J" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Stats by second for $service", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:1198", + "format": "cps", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "$$hashKey": "object:1199", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": null, + "description": "", + "fieldConfig": { + "defaults": { + "unit": "Bps" + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 9, + "w": 12, + "x": 0, + "y": 150 + }, + "hiddenSeries": false, + "id": 864, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.16", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [ + { + "$$hashKey": "object:3086", + "alias": "/^Out/", + "transform": "negative-Y" + } + ], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "sum(rate(citrixadc_lb_request_bytes_received_total{instance=~\"$instance\", citrixadc_lb_name=~\"$service\"}[2m])) without( citrixadc_lb_state )", + "interval": "", + "legendFormat": "In", + "refId": "A" + }, + { + "exemplar": true, + "expr": "sum( rate(citrixadc_lb_response_bytes_received_total{instance=~\"$instance\", citrixadc_lb_name=~\"$service\"}[2m])) without( citrixadc_lb_state )", + "hide": false, + "interval": "", + "legendFormat": "Out", + "refId": "B" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Bytes In/Out by sec for $service", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:1198", + "format": "Bps", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "$$hashKey": "object:1199", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": null, + "description": "", + "fieldConfig": { + "defaults": { + "unit": "pps" + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 9, + "w": 12, + "x": 12, + "y": 150 + }, + "hiddenSeries": false, + "id": 43, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.16", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [ + { + "$$hashKey": "object:3048", + "alias": "/^Out/", + "transform": "negative-Y" + } + ], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "sum( rate(citrixadc_lb_packets_received_total{instance=~\"$instance\", citrixadc_lb_name=~\"$service\"}[2m]) ) without( citrixadc_lb_state )", + "interval": "", + "legendFormat": "In", + "refId": "A" + }, + { + "exemplar": true, + "expr": "sum(rate(citrixadc_lb_packets_sent_total{instance=~\"$instance\", citrixadc_lb_name=~\"$service\"}[2m])) without( citrixadc_lb_state )", + "hide": false, + "interval": "", + "legendFormat": "Out", + "refId": "B" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Packet In/Out by sec for $service", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:1198", + "format": "pps", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "$$hashKey": "object:1199", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": null, + "description": "", + "fieldConfig": { + "defaults": { + "unit": "short" + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 9, + "w": 12, + "x": 0, + "y": 159 + }, + "hiddenSeries": false, + "id": 894, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.16", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "sum(citrixadc_lb_current_client_connection_count{instance=~\"$instance\", citrixadc_lb_name=~\"$service\"}) without(citrixadc_lb_state)", + "interval": "", + "legendFormat": "client connection {{ citrixadc_lb_name }}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "current client connection for $service", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:1198", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "$$hashKey": "object:1199", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": null, + "description": "", + "fieldConfig": { + "defaults": { + "unit": "percent" + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 9, + "w": 12, + "x": 12, + "y": 159 + }, + "hiddenSeries": false, + "id": 895, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.16", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "sum(citrixadc_lb_members_up_total{instance=~\"$instance\", citrixadc_lb_name=~\"$service\"}) without(citrixadc_lb_state)", + "interval": "", + "legendFormat": "client connection {{ citrixadc_lb_name }}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "percent of members UP for $service", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:1198", + "format": "percent", + "label": null, + "logBase": 1, + "max": "100", + "min": null, + "show": true + }, + { + "$$hashKey": "object:1199", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "collapsed": false, + "datasource": null, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 168 + }, + "id": 20, + "panels": [], + "title": "SSL", + "type": "row" + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": null, + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 169 + }, + "hiddenSeries": false, + "id": 22, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.16", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "rate(citrixadc_ssl_tot_tlsv11_sessions_total{instance=~\"$instance\"}[2m])", + "interval": "", + "legendFormat": "TLSv1 session", + "refId": "A" + }, + { + "exemplar": true, + "expr": "rate(citrixadc_ssl_tot_v2_sessions_total{instance=~\"$instance\"}[2m])", + "hide": false, + "interval": "", + "legendFormat": "SSLv2 session", + "refId": "B" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "SSL V2 session by second", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:305", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "$$hashKey": "object:306", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": null, + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 169 + }, + "hiddenSeries": false, + "id": 23, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.16", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "rate(citrixadc_ssl_tot_sessions_total{instance=~\"$instance\"}[2m])", + "interval": "", + "legendFormat": "num", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Total SSL session by second", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:305", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "$$hashKey": "object:306", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": null, + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 177 + }, + "hiddenSeries": false, + "id": 24, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.16", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "rate(citrixadc_ssl_tot_encode_total{instance=~\"$instance\"}[2m])", + "hide": false, + "interval": "", + "legendFormat": "num", + "refId": "B" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Total SSL encode by second", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:305", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "$$hashKey": "object:306", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": null, + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 177 + }, + "hiddenSeries": false, + "id": 25, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.16", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "rate(citrixadc_ssl_crypto_utilization_stat_total{instance=~\"$instance\"}[2m])", + "hide": false, + "interval": "", + "legendFormat": "num", + "refId": "B" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Total SSL crypto usage by second", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:305", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "$$hashKey": "object:306", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "datasource": null, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": null, + "filterable": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "none" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Expiration in Days" + }, + "properties": [ + { + "id": "unit", + "value": "d" + }, + { + "id": "thresholds", + "value": { + "mode": "absolute", + "steps": [ + { + "color": "red", + "value": null + }, + { + "color": "#EAB839", + "value": 45 + }, + { + "color": "green", + "value": 80 + } + ] + } + }, + { + "id": "custom.displayMode", + "value": "color-background" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Expiration Date" + }, + "properties": [ + { + "id": "unit", + "value": "dateTimeAsLocal" + } + ] + } + ] + }, + "gridPos": { + "h": 7, + "w": 24, + "x": 0, + "y": 185 + }, + "id": 27, + "options": { + "showHeader": true + }, + "pluginVersion": "7.5.16", + "targets": [ + { + "exemplar": true, + "expr": "citrixadc_ssl_cert_days_to_expire{instance=~\"$instance\"}", + "format": "table", + "instant": true, + "interval": "", + "legendFormat": "", + "refId": "A" + }, + { + "exemplar": true, + "expr": "(time() + (citrixadc_ssl_cert_days_to_expire{instance=~\"$instance\"} * 86400)) * 1000", + "format": "table", + "hide": false, + "instant": true, + "interval": "", + "legendFormat": "", + "refId": "B" + } + ], + "title": "Certificate expiration", + "transformations": [ + { + "id": "merge", + "options": {} + }, + { + "id": "filterFieldsByName", + "options": { + "include": { + "names": [ + "citrixadc_cert_key", + "Value #A", + "Value #B" + ] + } + } + }, + { + "id": "organize", + "options": { + "excludeByName": {}, + "indexByName": {}, + "renameByName": { + "Value #A": "Expiration in Days", + "Value #B": "Expiration Date" + } + } + } + ], + "type": "table" + }, + { + "collapsed": true, + "datasource": null, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 192 + }, + "id": 175, + "panels": [ + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": null, + "fieldConfig": { + "defaults": { + "unit": "Bps" + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 9, + "w": 12, + "x": 0, + "y": 224 + }, + "hiddenSeries": false, + "id": 176, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.5", + "pointradius": 5, + "points": false, + "renderer": "flot", + "scopedVars": { + "ssl_service": { + "selected": false, + "text": "svctxsgdh.exterieur.d-a.com", + "value": "svctxsgdh.exterieur.d-a.com" + } + }, + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "rate(citrixadc_sslvserver_decrypt_bytes_total{instance=~\"$instance\", citrixadc_sslvserver_name=~\"$ssl_service\"}[2m])", + "format": "time_series", + "interval": "", + "intervalFactor": 1, + "legendFormat": "decrypt {{ citrixadc_sslvserver_name }}", + "refId": "A" + }, + { + "exemplar": true, + "expr": "rate(citrixadc_sslvserver_encrypt_bytes_total{instance=~\"$instance\", citrixadc_sslvserver_name=~\"$ssl_service\"}[2m])", + "format": "time_series", + "hide": false, + "interval": "", + "intervalFactor": 1, + "legendFormat": "encrypt {{ citrixadc_sslvserver_name }}", + "refId": "B" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "SSL Encrypt/Decrypt bytes/s $ssl_service", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:144", + "format": "Bps", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "$$hashKey": "object:145", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": null, + "fieldConfig": { + "defaults": { + "unit": "cps" + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 9, + "w": 12, + "x": 12, + "y": 224 + }, + "hiddenSeries": false, + "id": 177, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.5", + "pointradius": 5, + "points": false, + "renderer": "flot", + "scopedVars": { + "ssl_service": { + "selected": false, + "text": "svctxsgdh.exterieur.d-a.com", + "value": "svctxsgdh.exterieur.d-a.com" + } + }, + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "rate(citrixadc_sslvserver_session_new_total{instance=~\"$instance\", citrixadc_sslvserver_name=~\"$ssl_service\"}[2m])", + "format": "time_series", + "hide": false, + "interval": "", + "intervalFactor": 1, + "legendFormat": "new session {{ citrixadc_sslvserver_name }}", + "refId": "A" + }, + { + "exemplar": true, + "expr": "rate(citrixadc_sslvserver_session_hits_total{instance=~\"$instance\", citrixadc_sslvserver_name=~\"$ssl_service\"}[2m])", + "format": "time_series", + "hide": false, + "interval": "", + "intervalFactor": 1, + "legendFormat": "hits {{ citrixadc_sslvserver_name }}", + "refId": "B" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "SSL new session and hits /s for $ssl_service", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:144", + "format": "cps", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "$$hashKey": "object:145", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": null, + "fieldConfig": { + "defaults": { + "unit": "cps" + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 9, + "w": 12, + "x": 0, + "y": 233 + }, + "hiddenSeries": false, + "id": 178, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.5", + "pointradius": 5, + "points": false, + "renderer": "flot", + "scopedVars": { + "ssl_service": { + "selected": false, + "text": "svctxsgdh.exterieur.d-a.com", + "value": "svctxsgdh.exterieur.d-a.com" + } + }, + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "rate(citrixadc_sslvserver_auth_success_total{instance=~\"$instance\", citrixadc_sslvserver_name=~\"$ssl_service\"}[2m])", + "format": "time_series", + "hide": false, + "interval": "", + "intervalFactor": 1, + "legendFormat": "auth success {{ citrixadc_sslvserver_name }}", + "refId": "A" + }, + { + "exemplar": true, + "expr": "rate(citrixadc_sslvserver_auth_failure_total{instance=~\"$instance\", citrixadc_sslvserver_name=~\"$ssl_service\"}[2m])", + "format": "time_series", + "hide": false, + "interval": "", + "intervalFactor": 1, + "legendFormat": "auth failure {{ citrixadc_sslvserver_name }}", + "refId": "B" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "SSL Encrypt/Decrypt bytes/s $ssl_service", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:144", + "format": "cps", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "$$hashKey": "object:145", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + } + ], + "repeat": "ssl_service", + "title": "SSL service Stats $ssl_service", + "type": "row" + }, + { + "collapsed": true, + "datasource": null, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 197 + }, + "id": 52, + "panels": [ + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": null, + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 9, + "w": 12, + "x": 0, + "y": 209 + }, + "hiddenSeries": false, + "id": 49, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.5", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "rate(citrixadc_aaa_auth_success_total{instance=~\"$instance\"}[2m])", + "format": "time_series", + "interval": "", + "intervalFactor": 1, + "legendFormat": "success auth", + "refId": "A" + }, + { + "exemplar": true, + "expr": "rate(citrixadc_aaa_auth_fail_total {instance=~\"$instance\"}[2m])", + "format": "time_series", + "hide": false, + "interval": "", + "intervalFactor": 1, + "legendFormat": "failed auth", + "refId": "B" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Auth", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:144", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "$$hashKey": "object:145", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": null, + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 9, + "w": 12, + "x": 12, + "y": 209 + }, + "hiddenSeries": false, + "id": 53, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.5", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "rate(citrixadc_aaa_tot_sessions_total{instance=~\"$instance\"}[2m])", + "format": "time_series", + "interval": "", + "intervalFactor": 1, + "legendFormat": "Total session", + "refId": "A" + }, + { + "exemplar": true, + "expr": "rate(citrixadc_aaa_tot_sessiontimeout_total{instance=~\"$instance\"}[2m])", + "format": "time_series", + "hide": false, + "interval": "", + "intervalFactor": 1, + "legendFormat": "Timeout session", + "refId": "B" + }, + { + "exemplar": true, + "expr": "rate(citrixadc_aaa_tot_tm_sessions_total {instance=~\"$instance\"}[2m])", + "format": "time_series", + "hide": false, + "interval": "", + "intervalFactor": 1, + "legendFormat": "TM? session", + "refId": "C" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Session", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:144", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "$$hashKey": "object:145", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": null, + "description": "Count of current Basic ICA only connections.\nCount of current SmartAccess ICA connections.", + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 9, + "w": 12, + "x": 0, + "y": 218 + }, + "hiddenSeries": false, + "id": 1170, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.5", + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "citrixadc_aaa_cur_ica_conn {instance=~\"$instance\"}", + "format": "time_series", + "interval": "", + "intervalFactor": 1, + "legendFormat": "SmartAccess ICA", + "refId": "A" + }, + { + "exemplar": true, + "expr": "citrixadc_aaa_cur_ica_only_conn {instance=~\"$instance\"}", + "format": "time_series", + "hide": false, + "interval": "", + "intervalFactor": 1, + "legendFormat": "Basic ICA only conn", + "refId": "B" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "ICA", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:144", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "$$hashKey": "object:145", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + } + ], + "title": "AAA Stats", + "type": "row" + } + ], + "refresh": "1m", + "schemaVersion": 27, + "style": "dark", + "tags": [], + "templating": { + "list": [ + { + "allValue": null, + "current": {}, + "datasource": "${DS_PROMETHEUS}", + "definition": "label_values(citrixadc_probe_success, instance)", + "description": null, + "error": null, + "hide": 0, + "includeAll": true, + "label": "Hostname", + "multi": false, + "name": "instance", + "options": [], + "query": { + "query": "label_values(citrixadc_probe_success, instance)", + "refId": "StandardVariableQuery" + }, + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "tagValuesQuery": "", + "tags": [], + "tagsQuery": "", + "type": "query", + "useTags": false + }, + { + "allValue": null, + "current": {}, + "datasource": "${DS_PROMETHEUS}", + "definition": "label_values(citrixadc_lb_packets_received_total{instance=~\"$instance\",citrixadc_lb_state=\"UP\"}, citrixadc_lb_name)", + "description": null, + "error": null, + "hide": 0, + "includeAll": true, + "label": "LBC service", + "multi": true, + "name": "service", + "options": [], + "query": { + "query": "label_values(citrixadc_lb_packets_received_total{instance=~\"$instance\",citrixadc_lb_state=\"UP\"}, citrixadc_lb_name)", + "refId": "StandardVariableQuery" + }, + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "tagValuesQuery": "", + "tags": [], + "tagsQuery": "", + "type": "query", + "useTags": false + }, + { + "allValue": null, + "current": {}, + "datasource": "${DS_PROMETHEUS}", + "definition": "label_values(citrixadc_sslvserver_health{instance=~\"$instance\"}, citrixadc_sslvserver_name)", + "description": null, + "error": null, + "hide": 0, + "includeAll": true, + "label": "SSL service", + "multi": true, + "name": "ssl_service", + "options": [], + "query": { + "query": "label_values(citrixadc_sslvserver_health{instance=~\"$instance\"}, citrixadc_sslvserver_name)", + "refId": "StandardVariableQuery" + }, + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "tagValuesQuery": "", + "tags": [], + "tagsQuery": "", + "type": "query", + "useTags": false + } + ] + }, + "time": { + "from": "now-5m", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "1s", + "2s", + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "time_options": [ + "5m", + "15m", + "1h", + "6h", + "12h", + "24h", + "2d", + "7d", + "30d" + ] + }, + "timezone": "", + "title": "CITRIX ADC Netscaler Stats", + "uid": "NT3Z0MJkk", + "version": 56 +} \ No newline at end of file diff --git a/contribs/dashboards/HP3PAR System.json b/contribs/dashboards/HP3PAR System.json new file mode 100644 index 0000000..a903c93 --- /dev/null +++ b/contribs/dashboards/HP3PAR System.json @@ -0,0 +1,3093 @@ +{ + "__inputs": [ + { + "name": "DS_PROMETHEUS", + "label": "Prometheus", + "description": "", + "type": "datasource", + "pluginId": "prometheus", + "pluginName": "Prometheus" + } + ], + "__requires": [ + { + "type": "grafana", + "id": "grafana", + "name": "Grafana", + "version": "7.5.16" + }, + { + "type": "panel", + "id": "graph", + "name": "Graph", + "version": "" + }, + { + "type": "datasource", + "id": "prometheus", + "name": "Prometheus", + "version": "1.0.0" + }, + { + "type": "panel", + "id": "stat", + "name": "Stat", + "version": "" + }, + { + "type": "panel", + "id": "table", + "name": "Table", + "version": "" + }, + { + "type": "panel", + "id": "timeseries", + "name": "Time series", + "version": "" + } + ], + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": "-- Grafana --", + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "gnetId": null, + "graphTooltip": 0, + "id": null, + "iteration": 1695551204099, + "links": [], + "panels": [ + { + "collapsed": false, + "datasource": null, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 2, + "panels": [], + "title": "system", + "type": "row" + }, + { + "datasource": null, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 2, + "w": 4, + "x": 0, + "y": 1 + }, + "id": 6, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "/^model$/", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "7.5.16", + "targets": [ + { + "exemplar": true, + "expr": "hp3par_system_id{host=~\"$host\"} ", + "format": "table", + "instant": true, + "interval": "", + "legendFormat": "", + "refId": "A" + } + ], + "title": "Model", + "type": "stat" + }, + { + "datasource": null, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 2, + "w": 4, + "x": 4, + "y": 1 + }, + "id": 7, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "/^version$/", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "7.5.16", + "targets": [ + { + "exemplar": true, + "expr": "hp3par_system_id{host=~\"$host\"} ", + "format": "table", + "instant": true, + "interval": "", + "legendFormat": "", + "refId": "A" + } + ], + "title": "Version", + "type": "stat" + }, + { + "datasource": null, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 2, + "w": 4, + "x": 8, + "y": 1 + }, + "id": 8, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "/^patches$/", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "7.5.16", + "targets": [ + { + "exemplar": true, + "expr": "hp3par_system_id{host=~\"$host\"} ", + "format": "table", + "instant": true, + "interval": "", + "legendFormat": "", + "refId": "A" + } + ], + "title": "Patches", + "type": "stat" + }, + { + "datasource": null, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 2, + "w": 4, + "x": 12, + "y": 1 + }, + "id": 9, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "/^serial$/", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "7.5.16", + "targets": [ + { + "exemplar": true, + "expr": "hp3par_system_id{host=~\"$host\"} ", + "format": "table", + "instant": true, + "interval": "", + "legendFormat": "", + "refId": "A" + } + ], + "title": "Serial", + "type": "stat" + }, + { + "datasource": null, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 2, + "w": 4, + "x": 16, + "y": 1 + }, + "id": 10, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "7.5.16", + "targets": [ + { + "exemplar": true, + "expr": "hp3par_system_nodes_total{host=~\"$host\"} ", + "format": "time_series", + "instant": true, + "interval": "", + "legendFormat": "", + "refId": "A" + } + ], + "title": "Total Nodes", + "type": "stat" + }, + { + "datasource": null, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "red", + "value": null + }, + { + "color": "green", + "value": 2 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 2, + "w": 4, + "x": 20, + "y": 1 + }, + "id": 18, + "options": { + "colorMode": "background", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "7.5.16", + "targets": [ + { + "exemplar": true, + "expr": "hp3par_system_nodes_active{host=~\"$host\"} ", + "format": "time_series", + "instant": true, + "interval": "", + "legendFormat": "", + "refId": "A" + } + ], + "title": "Active Nodes", + "type": "stat" + }, + { + "datasource": null, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "blue", + "value": null + } + ] + }, + "unit": "decbytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 9, + "x": 0, + "y": 3 + }, + "id": 19, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "center", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "7.5.16", + "targets": [ + { + "exemplar": true, + "expr": "hp3par_capacity_total_bytes{host=~\"$host\"} ", + "format": "time_series", + "instant": true, + "interval": "", + "legendFormat": "{{ type }}", + "refId": "A" + } + ], + "title": "Total Capacity by Disk Type", + "type": "stat" + }, + { + "datasource": null, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "graph": false, + "legend": false, + "tooltip": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "decbytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 15, + "x": 9, + "y": 3 + }, + "id": 20, + "options": { + "graph": {}, + "legend": { + "calcs": [ + "min", + "max", + "lastNotNull", + "mean" + ], + "displayMode": "table", + "placement": "bottom" + }, + "tooltipOptions": { + "mode": "single" + } + }, + "pluginVersion": "7.5.16", + "targets": [ + { + "exemplar": true, + "expr": "hp3par_capacity_free_bytes{host=~\"$host\"} ", + "interval": "", + "legendFormat": "{{ type}}", + "refId": "A" + } + ], + "title": "Total free Capacity by disk type", + "type": "timeseries" + }, + { + "datasource": null, + "description": "average cluster cpu usage ( over all cpu and all cluster's nodes)", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "graph": false, + "legend": false, + "tooltip": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 10 + }, + "id": 4, + "options": { + "graph": {}, + "legend": { + "calcs": [ + "min", + "max", + "lastNotNull", + "mean" + ], + "displayMode": "table", + "placement": "bottom" + }, + "tooltipOptions": { + "mode": "single" + } + }, + "pluginVersion": "7.5.16", + "targets": [ + { + "exemplar": true, + "expr": "avg by(mode) (hp3par_cpu_usage_percent{ mode != \"idle\"})", + "interval": "", + "legendFormat": "{{ mode }}", + "refId": "A" + } + ], + "title": "cpu usage", + "type": "timeseries" + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": null, + "description": "Hits divided accesses displayed in percentage. (hitIO / accessIO percent) over 5 minutes", + "fieldConfig": { + "defaults": { + "unit": "percent" + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 10 + }, + "hiddenSeries": false, + "id": 45, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.16", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [ + { + "$$hashKey": "object:1090", + "alias": "/.*read$/", + "transform": "negative-Y" + } + ], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "hp3par_memory_hit_percent{host=~\"$host\"}", + "interval": "", + "legendFormat": "node: {{ node }} - {{ mode }}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Memory IO cache hit percent", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:1012", + "format": "percent", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "$$hashKey": "object:1013", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "datasource": null, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "graph": false, + "legend": false, + "tooltip": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 24, + "x": 0, + "y": 18 + }, + "id": 46, + "options": { + "graph": {}, + "legend": { + "calcs": [ + "min", + "max", + "lastNotNull", + "mean" + ], + "displayMode": "table", + "placement": "bottom" + }, + "tooltipOptions": { + "mode": "single" + } + }, + "pluginVersion": "7.5.16", + "targets": [ + { + "exemplar": true, + "expr": "hp3par_memory_page_stats{host=~\"$host\"}", + "interval": "", + "legendFormat": "node: {{ node }} - pages {{ mode }}", + "refId": "A" + } + ], + "title": "Memory Page details", + "type": "timeseries" + }, + { + "collapsed": true, + "datasource": null, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 27 + }, + "id": 32, + "panels": [ + { + "datasource": null, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": null, + "filterable": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "decbytes" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "% Used" + }, + "properties": [ + { + "id": "unit", + "value": "percentunit" + }, + { + "id": "custom.displayMode", + "value": "gradient-gauge" + }, + { + "id": "thresholds", + "value": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "#EAB839", + "value": 0.85 + }, + { + "color": "red", + "value": 0.97 + } + ] + } + }, + { + "id": "custom.width", + "value": 234 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Avail Size" + }, + "properties": [ + { + "id": "custom.width", + "value": 314 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "name" + }, + "properties": [ + { + "id": "custom.width", + "value": 521 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "host" + }, + "properties": [ + { + "id": "custom.width", + "value": 237 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Snapshots Size" + }, + "properties": [ + { + "id": "custom.width", + "value": 261 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Total Size" + }, + "properties": [ + { + "id": "custom.width", + "value": 261 + } + ] + } + ] + }, + "gridPos": { + "h": 4, + "w": 24, + "x": 0, + "y": 28 + }, + "id": 33, + "options": { + "showHeader": true, + "sortBy": [] + }, + "pluginVersion": "7.5.16", + "targets": [ + { + "exemplar": true, + "expr": "sum by(host, name) (hp3par_cpg_total_bytes{host=~\"$host\"}) != 0", + "format": "table", + "instant": true, + "interval": "", + "legendFormat": "", + "refId": "A" + }, + { + "exemplar": true, + "expr": "sum by(host, name) (hp3par_cpg_available_bytes{host=~\"$host\"}) != 0", + "format": "table", + "hide": false, + "instant": true, + "interval": "", + "legendFormat": "", + "refId": "B" + }, + { + "exemplar": true, + "expr": "( sum by(host, name) (hp3par_cpg_total_bytes{host=~\"$host\"}) - sum by(host, name) (hp3par_cpg_available_bytes{host=~\"$host\"}) )/ (sum by(host, name) (hp3par_cpg_total_bytes{host=~\"$host\"}) +1 ) != 0", + "format": "table", + "hide": false, + "instant": true, + "interval": "", + "legendFormat": "", + "refId": "C" + }, + { + "exemplar": true, + "expr": "sum by(host, name) (hp3par_cpg_snapshot_used_bytes{host=~\"$host\"}) != 0", + "format": "table", + "hide": false, + "instant": true, + "interval": "", + "legendFormat": "", + "refId": "D" + } + ], + "timeFrom": null, + "timeShift": null, + "title": "Common Provisionning Groups", + "transformations": [ + { + "id": "merge", + "options": {} + }, + { + "id": "organize", + "options": { + "excludeByName": { + "Time": true, + "host": true + }, + "indexByName": { + "Time": 0, + "Value #A": 4, + "Value #B": 3, + "Value #C": 5, + "host": 1, + "name": 2 + }, + "renameByName": { + "Value #A": "Total Size", + "Value #B": "Avail Size", + "Value #C": "% Used", + "Value #D": "Snapshots Size" + } + } + } + ], + "type": "table" + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": null, + "fieldConfig": { + "defaults": { + "unit": "percentunit" + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 32 + }, + "hiddenSeries": false, + "id": 35, + "legend": { + "avg": false, + "current": false, + "hideZero": true, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.16", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "( sum by(host, name) (hp3par_cpg_total_bytes{host=~\"$host\"}) - sum by(host, name) (hp3par_cpg_available_bytes{host=~\"$host\"}) )/ (sum by(host, name) (hp3par_cpg_total_bytes{host=~\"$host\"}) +1 ) ", + "interval": "", + "legendFormat": "{{ name }}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "CPG Used %", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:368", + "format": "percentunit", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "$$hashKey": "object:369", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": null, + "fieldConfig": { + "defaults": { + "unit": "decbytes" + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 32 + }, + "hiddenSeries": false, + "id": 36, + "legend": { + "avg": false, + "current": false, + "hideZero": true, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.16", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "( sum by(host, name) (hp3par_cpg_snapshot_used_bytes{host=~\"$host\"})) != 0", + "interval": "", + "legendFormat": "{{ name }}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "CPG Snapshots Used Bytes", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:368", + "format": "decbytes", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "$$hashKey": "object:369", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + } + ], + "title": "CPG", + "type": "row" + }, + { + "collapsed": true, + "datasource": null, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 28 + }, + "id": 38, + "panels": [ + { + "datasource": null, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": null, + "filterable": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "decbytes" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Used %" + }, + "properties": [ + { + "id": "unit", + "value": "percentunit" + }, + { + "id": "custom.displayMode", + "value": "gradient-gauge" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Compaction Ratio" + }, + "properties": [ + { + "id": "unit", + "value": "short" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Deduplication Ration" + }, + "properties": [ + { + "id": "unit", + "value": "short" + } + ] + } + ] + }, + "gridPos": { + "h": 9, + "w": 24, + "x": 0, + "y": 29 + }, + "id": 40, + "options": { + "showHeader": true, + "sortBy": [ + { + "desc": true, + "displayName": "Deduplication Ration" + } + ] + }, + "pluginVersion": "7.5.16", + "targets": [ + { + "exemplar": true, + "expr": "sum by(host, name, provisionningtype) (hp3par_volume_total_bytes{host=~\"$host\"})", + "format": "table", + "hide": false, + "instant": true, + "interval": "", + "legendFormat": "", + "refId": "A" + }, + { + "exemplar": true, + "expr": "sum by(host, name, provisionningtype) (hp3par_volume_used_bytes{host=~\"$host\"})", + "format": "table", + "hide": false, + "instant": true, + "interval": "", + "legendFormat": "", + "refId": "B" + }, + { + "exemplar": true, + "expr": "sum by(host, name, provisionningtype) (hp3par_volume_used_bytes{host=~\"$host\"}) / sum by(host, name, provisionningtype) (hp3par_volume_total_bytes{host=~\"$host\"})", + "format": "table", + "hide": false, + "instant": true, + "interval": "", + "legendFormat": "", + "refId": "C" + }, + { + "exemplar": true, + "expr": "sum by(host, name, provisionningtype) (hp3par_volume_compaction_ratio{host=~\"$host\"})", + "format": "table", + "hide": false, + "instant": true, + "interval": "", + "legendFormat": "", + "refId": "D" + }, + { + "exemplar": true, + "expr": "sum by(host, name, provisionningtype) (hp3par_volume_deduplication_ratio{host=~\"$host\"})", + "format": "table", + "hide": false, + "instant": true, + "interval": "", + "legendFormat": "", + "refId": "E" + } + ], + "title": "Volumes Infos", + "transformations": [ + { + "id": "merge", + "options": {} + }, + { + "id": "organize", + "options": { + "excludeByName": { + "Time": true, + "host": true + }, + "indexByName": {}, + "renameByName": { + "Value #A": "Total Size", + "Value #B": "Used Size", + "Value #C": "Used %", + "Value #D": "Compaction Ratio", + "Value #E": "Deduplication Ration", + "Value #Total Size": "Total Size", + "Value #Used Percent": "Used Percent", + "Value #Used Size": "Used Size" + } + } + } + ], + "type": "table" + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": null, + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 38 + }, + "hiddenSeries": false, + "id": 41, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": true, + "show": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.16", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [ + { + "$$hashKey": "object:76", + "alias": "/read$/", + "transform": "negative-Y" + } + ], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "hp3par_volume_read_io_per_second{host=~\"$host\"}", + "interval": "", + "legendFormat": "{{ name }}: read", + "refId": "A" + }, + { + "exemplar": true, + "expr": "hp3par_volume_write_io_per_second{host=~\"$host\"}", + "hide": false, + "interval": "", + "legendFormat": "{{ name }}: write", + "refId": "B" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Volumes IO operations by sec over 5 min", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:734", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "$$hashKey": "object:735", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": null, + "fieldConfig": { + "defaults": { + "unit": "decbytes" + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 38 + }, + "hiddenSeries": false, + "id": 42, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": true, + "show": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.16", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [ + { + "$$hashKey": "object:76", + "alias": "/read$/", + "transform": "negative-Y" + } + ], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "hp3par_volume_read_bytes_per_second{host=~\"$host\"}", + "interval": "", + "legendFormat": "{{ name }}: read", + "refId": "A" + }, + { + "exemplar": true, + "expr": "hp3par_volume_write_bytes_per_second{host=~\"$host\"}", + "hide": false, + "interval": "", + "legendFormat": "{{ name }}: write", + "refId": "B" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Volumes IO Bytes by sec over 5 min", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:790", + "format": "decbytes", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "$$hashKey": "object:791", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": null, + "fieldConfig": { + "defaults": { + "unit": "s" + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 46 + }, + "hiddenSeries": false, + "id": 43, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": true, + "show": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.16", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [ + { + "$$hashKey": "object:76", + "alias": "/read$/", + "transform": "negative-Y" + } + ], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "hp3par_volume_read_latency_second{host=~\"$host\"}", + "interval": "", + "legendFormat": "{{ name }}: read", + "refId": "A" + }, + { + "exemplar": true, + "expr": "hp3par_volume_write_latency_second{host=~\"$host\"}", + "hide": false, + "interval": "", + "legendFormat": "{{ name }}: write", + "refId": "B" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Volumes IO latency over 5 min", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:896", + "format": "s", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "$$hashKey": "object:897", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "datasource": null, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "graph": false, + "legend": false, + "tooltip": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 46 + }, + "id": 44, + "options": { + "graph": {}, + "legend": { + "calcs": [ + "min", + "max", + "mean", + "last" + ], + "displayMode": "table", + "placement": "bottom" + }, + "tooltipOptions": { + "mode": "single" + } + }, + "pluginVersion": "7.5.16", + "targets": [ + { + "exemplar": true, + "expr": "hp3par_volume_busy_percent{host=~\"$host\"}", + "interval": "", + "legendFormat": "{{ name }}", + "refId": "A" + } + ], + "title": "Volumes Busy % over 5 min", + "type": "timeseries" + } + ], + "title": "Volumes", + "type": "row" + }, + { + "collapsed": true, + "datasource": null, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 29 + }, + "id": 12, + "panels": [ + { + "datasource": null, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": null, + "filterable": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byRegexp", + "options": ".* Size" + }, + "properties": [ + { + "id": "unit", + "value": "decbytes" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Free %" + }, + "properties": [ + { + "id": "custom.displayMode", + "value": "gradient-gauge" + }, + { + "id": "unit", + "value": "percent" + }, + { + "id": "custom.width", + "value": 291 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "cageID" + }, + "properties": [ + { + "id": "custom.width", + "value": 63 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "cageSide" + }, + "properties": [ + { + "id": "custom.width", + "value": 76 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "magazine" + }, + "properties": [ + { + "id": "custom.width", + "value": 81 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "diskPos" + }, + "properties": [ + { + "id": "custom.width", + "value": 70 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "id" + }, + "properties": [ + { + "id": "custom.width", + "value": 33 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "type" + }, + "properties": [ + { + "id": "custom.width", + "value": 51 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Total Size" + }, + "properties": [ + { + "id": "custom.width", + "value": 167 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Left Life %" + }, + "properties": [ + { + "id": "custom.width", + "value": 251 + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 30 + }, + "id": 14, + "options": { + "showHeader": true, + "sortBy": [] + }, + "pluginVersion": "7.5.16", + "targets": [ + { + "exemplar": true, + "expr": "avg by( cageID, cageSide,magazine,diskPos) (hp3par_physical_disk_total_bytes{host=~\"$host\"})", + "format": "table", + "instant": true, + "interval": "", + "legendFormat": "{{ id }}", + "refId": "A" + }, + { + "exemplar": true, + "expr": "avg by( cageID, cageSide,magazine,diskPos) (hp3par_physical_disk_failed_bytes{host=~\"$host\"})", + "format": "table", + "hide": false, + "instant": true, + "interval": "", + "legendFormat": "{{ id }}", + "refId": "C" + }, + { + "exemplar": true, + "expr": "avg by( cageID, cageSide,magazine,diskPos) (hp3par_physical_disk_free_bytes{host=~\"$host\"} )", + "format": "table", + "hide": false, + "instant": true, + "interval": "", + "legendFormat": "{{ id }}", + "refId": "D" + }, + { + "exemplar": true, + "expr": "hp3par_physical_disk_free_bytes{host=~\"$host\"} / hp3par_physical_disk_total_bytes{host=~\"$host\"} * 100", + "format": "table", + "hide": false, + "instant": true, + "interval": "", + "legendFormat": "", + "refId": "E" + }, + { + "exemplar": true, + "expr": "avg by( cageID, cageSide,magazine,diskPos) (hp3par_physical_disk_life_left_percent{host=~\"$host\"})", + "format": "table", + "hide": false, + "instant": true, + "interval": "", + "legendFormat": "", + "refId": "B" + } + ], + "title": "Physical disks", + "transformations": [ + { + "id": "merge", + "options": {} + }, + { + "id": "organize", + "options": { + "excludeByName": { + "Time": true, + "__name__": true, + "daapplication": true, + "dacomposant": true, + "daversion": true, + "environment": true, + "host": true, + "instance": true, + "job": true, + "segment": true + }, + "indexByName": { + "Time": 0, + "Value #A": 15, + "Value #B": 18, + "Value #C": 19, + "Value #D": 16, + "Value #E": 17, + "cageID": 1, + "cageSide": 2, + "daapplication": 3, + "dacomposant": 4, + "daversion": 5, + "diskPos": 7, + "environment": 8, + "host": 9, + "id": 10, + "instance": 11, + "job": 12, + "magazine": 6, + "segment": 13, + "type": 14 + }, + "renameByName": { + "Value #A": "Total Size", + "Value #B": "Left Life %", + "Value #C": "Failed Size", + "Value #D": "Free Size", + "Value #E": "Free %" + } + } + } + ], + "type": "table" + }, + { + "datasource": null, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "graph": false, + "legend": false, + "tooltip": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "celsius" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 38 + }, + "id": 16, + "options": { + "graph": {}, + "legend": { + "calcs": [ + "min", + "max", + "mean", + "last" + ], + "displayMode": "table", + "placement": "bottom" + }, + "tooltipOptions": { + "mode": "single" + } + }, + "pluginVersion": "7.5.16", + "targets": [ + { + "exemplar": true, + "expr": "hp3par_physical_disk_temperature{host=~\"$host\"}", + "interval": "", + "legendFormat": "{{ id }}", + "refId": "A" + } + ], + "title": "Disk Temperature in Celsius", + "type": "timeseries" + }, + { + "datasource": null, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "graph": false, + "legend": false, + "tooltip": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "decbytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 38 + }, + "id": 17, + "options": { + "graph": {}, + "legend": { + "calcs": [ + "min", + "max", + "mean", + "last" + ], + "displayMode": "table", + "placement": "bottom" + }, + "tooltipOptions": { + "mode": "single" + } + }, + "pluginVersion": "7.5.16", + "targets": [ + { + "exemplar": true, + "expr": "hp3par_physical_disk_total_bytes{host=~\"$host\"} - hp3par_physical_disk_free_bytes{host=~\"$host\"}", + "interval": "", + "legendFormat": "{{ id }}", + "refId": "A" + } + ], + "title": "Disk usage", + "type": "timeseries" + } + ], + "title": "Physical disks", + "type": "row" + }, + { + "collapsed": true, + "datasource": null, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 30 + }, + "id": 22, + "panels": [ + { + "datasource": null, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": null, + "filterable": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Status" + }, + "properties": [ + { + "id": "mappings", + "value": [ + { + "from": "", + "id": 1, + "text": "CONFIG_WAIT", + "to": "", + "type": 1, + "value": "1" + }, + { + "from": "", + "id": 2, + "text": "ALPA_WAIT", + "to": "", + "type": 1, + "value": "2" + }, + { + "from": "", + "id": 3, + "text": "LOGIN_WAIT", + "to": "", + "type": 1, + "value": "3" + }, + { + "from": "", + "id": 4, + "text": "READY", + "to": "", + "type": 1, + "value": "4" + }, + { + "from": "", + "id": 5, + "text": "LOSS_SYNC", + "to": "", + "type": 1, + "value": "5" + }, + { + "from": "", + "id": 6, + "text": "ERROR_STATE", + "to": "", + "type": 1, + "value": "6" + }, + { + "from": "", + "id": 7, + "text": "XXX", + "to": "", + "type": 1, + "value": "7" + }, + { + "from": "", + "id": 8, + "text": "NONPARTICIPATE", + "to": "", + "type": 1, + "value": "8" + }, + { + "from": "", + "id": 9, + "text": "COREDUMP", + "to": "", + "type": 1, + "value": "9" + }, + { + "from": "", + "id": 10, + "text": "OFFLINE", + "to": "", + "type": 1, + "value": "10" + } + ] + }, + { + "id": "thresholds", + "value": { + "mode": "absolute", + "steps": [ + { + "color": "orange", + "value": null + }, + { + "color": "green", + "value": 4 + }, + { + "color": "red", + "value": 5 + } + ] + } + }, + { + "id": "custom.displayMode", + "value": "color-background" + }, + { + "id": "custom.align", + "value": "center" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "FailOver State" + }, + "properties": [ + { + "id": "mappings", + "value": [ + { + "from": "", + "id": 1, + "text": "On", + "to": "", + "type": 1, + "value": "1" + }, + { + "from": "", + "id": 2, + "text": "Off", + "to": "", + "type": 1, + "value": "0" + } + ] + }, + { + "id": "thresholds", + "value": { + "mode": "absolute", + "steps": [ + { + "color": "rgb(130, 135, 130)", + "value": null + }, + { + "color": "dark-green", + "value": 1 + } + ] + } + }, + { + "id": "custom.displayMode", + "value": "color-background" + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 31 + }, + "id": 24, + "options": { + "showHeader": true, + "sortBy": [ + { + "desc": false, + "displayName": "source" + } + ] + }, + "pluginVersion": "7.5.16", + "targets": [ + { + "exemplar": true, + "expr": "sum by(source, target, label, porttype, protocol) (hp3par_port_status{host=~\"$host\"})", + "format": "table", + "instant": true, + "interval": "", + "legendFormat": "", + "refId": "A" + }, + { + "exemplar": true, + "expr": "sum by(source, target, label, porttype, protocol) (hp3par_port_failover_state{host=~\"$host\"})", + "format": "table", + "hide": false, + "instant": true, + "interval": "", + "legendFormat": "", + "refId": "B" + } + ], + "title": "Port Status", + "transformations": [ + { + "id": "merge", + "options": {} + }, + { + "id": "organize", + "options": { + "excludeByName": { + "Time": true + }, + "indexByName": { + "Time": 0, + "Value #A": 6, + "Value #B": 7, + "label": 3, + "porttype": 4, + "protocol": 5, + "source": 1, + "target": 2 + }, + "renameByName": { + "Value #A": "Status", + "Value #B": "FailOver State" + } + } + } + ], + "type": "table" + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": null, + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 39 + }, + "hiddenSeries": false, + "id": 26, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": true, + "show": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.16", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [ + { + "$$hashKey": "object:76", + "alias": "/read$/", + "transform": "negative-Y" + } + ], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "hp3par_port_read_io_per_second{host=~\"$host\"}", + "interval": "", + "legendFormat": "{{ source }} {{ porttype}}: read", + "refId": "A" + }, + { + "exemplar": true, + "expr": "hp3par_port_write_io_per_second{host=~\"$host\"}", + "hide": false, + "interval": "", + "legendFormat": "{{ source }} {{ porttype}}: write", + "refId": "B" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Port IO operations by sec over 5 min", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": null, + "fieldConfig": { + "defaults": { + "unit": "decbytes" + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 39 + }, + "hiddenSeries": false, + "id": 27, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": true, + "show": true, + "sort": "max", + "sortDesc": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.16", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [ + { + "$$hashKey": "object:76", + "alias": "/read$/", + "transform": "negative-Y" + } + ], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "hp3par_port_read_bytes_per_second{host=~\"$host\"}", + "interval": "", + "legendFormat": "{{ source }} {{ porttype}}: read", + "refId": "A" + }, + { + "exemplar": true, + "expr": "hp3par_port_write_bytes_per_second{host=~\"$host\"}", + "hide": false, + "interval": "", + "legendFormat": "{{ source }} {{ porttype}}: write", + "refId": "B" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Port IO Bytes by sec over 5 min", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "decbytes", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": null, + "fieldConfig": { + "defaults": { + "unit": "s" + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 47 + }, + "hiddenSeries": false, + "id": 28, + "legend": { + "alignAsTable": true, + "avg": true, + "current": true, + "max": true, + "min": true, + "show": true, + "sort": "current", + "sortDesc": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.16", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [ + { + "$$hashKey": "object:76", + "alias": "/read$/", + "transform": "negative-Y" + } + ], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "hp3par_port_read_latency_second{host=~\"$host\"}", + "interval": "", + "legendFormat": "{{ source }} {{ porttype}}: read", + "refId": "A" + }, + { + "exemplar": true, + "expr": "hp3par_port_write_latency_second{host=~\"$host\"}", + "hide": false, + "interval": "", + "legendFormat": "{{ source }} {{ porttype}}: write", + "refId": "B" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Port IO latency over 5 min", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "s", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "datasource": null, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "graph": false, + "legend": false, + "tooltip": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 47 + }, + "id": 30, + "options": { + "graph": {}, + "legend": { + "calcs": [ + "min", + "max", + "mean", + "last" + ], + "displayMode": "table", + "placement": "bottom" + }, + "tooltipOptions": { + "mode": "single" + } + }, + "pluginVersion": "7.5.16", + "targets": [ + { + "exemplar": true, + "expr": "hp3par_port_busy_percent{host=~\"$host\"}", + "interval": "", + "legendFormat": "{{ source }} {{ porttype }}", + "refId": "A" + } + ], + "title": "Port Busy % over 5 min", + "type": "timeseries" + } + ], + "title": "Physical ports", + "type": "row" + } + ], + "refresh": false, + "schemaVersion": 27, + "style": "dark", + "tags": [], + "templating": { + "list": [ + { + "allValue": null, + "current": {}, + "datasource": "${DS_PROMETHEUS}", + "definition": "label_values(hp3par_up, host)", + "description": "", + "error": null, + "hide": 0, + "includeAll": false, + "label": null, + "multi": false, + "name": "host", + "options": [], + "query": { + "query": "label_values(hp3par_up, host)", + "refId": "StandardVariableQuery" + }, + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "tagValuesQuery": "", + "tags": [], + "tagsQuery": "", + "type": "query", + "useTags": false + } + ] + }, + "time": { + "from": "now-1h", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "HP3PAR System", + "uid": "fK_lN3D4z", + "version": 17 +} \ No newline at end of file diff --git a/contribs/dashboards/Veeam Enterprise Manager.json b/contribs/dashboards/Veeam Enterprise Manager.json new file mode 100644 index 0000000..df641f6 --- /dev/null +++ b/contribs/dashboards/Veeam Enterprise Manager.json @@ -0,0 +1,4834 @@ +{ + "__inputs": [ + { + "name": "DS_PROMETHEUS", + "label": "Prometheus", + "description": "", + "type": "datasource", + "pluginId": "prometheus", + "pluginName": "Prometheus" + } + ], + "__requires": [ + { + "type": "panel", + "id": "bargauge", + "name": "Bar gauge", + "version": "" + }, + { + "type": "grafana", + "id": "grafana", + "name": "Grafana", + "version": "7.5.16" + }, + { + "type": "panel", + "id": "graph", + "name": "Graph", + "version": "" + }, + { + "type": "panel", + "id": "piechart", + "name": "Pie chart v2", + "version": "" + }, + { + "type": "datasource", + "id": "prometheus", + "name": "Prometheus", + "version": "1.0.0" + }, + { + "type": "panel", + "id": "stat", + "name": "Stat", + "version": "" + }, + { + "type": "panel", + "id": "table", + "name": "Table", + "version": "" + } + ], + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": "-- Grafana --", + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "description": "", + "editable": true, + "gnetId": null, + "graphTooltip": 0, + "id": null, + "iteration": 1695551238041, + "links": [], + "panels": [ + { + "collapsed": false, + "datasource": null, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 2, + "panels": [], + "title": "Enterprise Manager - Summary", + "type": "row" + }, + { + "datasource": null, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "rgb(228, 232, 228)", + "value": null + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "backup" + }, + "properties": [ + { + "id": "displayName", + "value": "Backup Servers" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "proxy" + }, + "properties": [ + { + "id": "displayName", + "value": "Proxy Servers" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "repository" + }, + "properties": [ + { + "id": "displayName", + "value": "Repository Servers" + } + ] + } + ] + }, + "gridPos": { + "h": 5, + "w": 4, + "x": 0, + "y": 1 + }, + "id": 4, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "center", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "last" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "value" + }, + "pluginVersion": "7.5.16", + "targets": [ + { + "exemplar": true, + "expr": "veeam_em_overview_element_count{instance=~\"$enterprisemanager\", count_type=\"server\", type=\"backup\"}", + "interval": "", + "legendFormat": "{{ type }}", + "refId": "A" + } + ], + "title": "Backup Servers", + "type": "stat" + }, + { + "datasource": null, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "rgb(228, 232, 228)", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 4, + "x": 4, + "y": 1 + }, + "id": 5, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "center", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "last" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "value" + }, + "pluginVersion": "7.5.16", + "targets": [ + { + "exemplar": true, + "expr": "veeam_em_overview_element_count{instance=~\"$enterprisemanager\", count_type=\"server\", type=\"repository\"}", + "interval": "", + "legendFormat": "{{ type }}", + "refId": "A" + } + ], + "title": "Repository Servers", + "type": "stat" + }, + { + "datasource": null, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "rgb(228, 232, 228)", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 4, + "x": 8, + "y": 1 + }, + "id": 6, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "center", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "last" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "value" + }, + "pluginVersion": "7.5.16", + "targets": [ + { + "exemplar": true, + "expr": "veeam_em_overview_element_count{instance=~\"$enterprisemanager\", count_type=\"server\", type=\"proxy\"}", + "interval": "", + "legendFormat": "{{ type }}", + "refId": "A" + } + ], + "title": "Proxy Servers", + "type": "stat" + }, + { + "datasource": null, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "rgb(228, 232, 228)", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 4, + "x": 12, + "y": 1 + }, + "id": 7, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "center", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "last" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "value" + }, + "pluginVersion": "7.5.16", + "targets": [ + { + "exemplar": true, + "expr": "veeam_em_overview_element_count{instance=~\"$enterprisemanager\", count_type=\"tasks\", type=\"scheduled_jobs\"}", + "interval": "", + "legendFormat": "{{ type }}", + "refId": "A" + } + ], + "title": "Scueduled Jobs", + "type": "stat" + }, + { + "datasource": null, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "rgb(228, 232, 228)", + "value": null + } + ] + }, + "unit": "decbytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 4, + "x": 16, + "y": 1 + }, + "id": 12, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "center", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "last" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "value" + }, + "pluginVersion": "7.5.16", + "targets": [ + { + "exemplar": true, + "expr": "veeam_em_overview_vms_total_bytes{instance=~\"$enterprisemanager\", type=\"source_vms\"}", + "interval": "", + "legendFormat": "{{ type }}", + "refId": "A" + } + ], + "title": "Total Source VMs", + "type": "stat" + }, + { + "datasource": null, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "rgb(228, 232, 228)", + "value": null + } + ] + }, + "unit": "decbytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 4, + "x": 20, + "y": 1 + }, + "id": 31, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "center", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "last" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "value" + }, + "pluginVersion": "7.5.16", + "targets": [ + { + "exemplar": true, + "expr": "veeam_em_overview_vms_total_bytes{instance=~\"$enterprisemanager\", type=\"full_backup_points\"}", + "interval": "", + "legendFormat": "{{ type }}", + "refId": "A" + } + ], + "title": "Full backup VMs", + "type": "stat" + }, + { + "datasource": null, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "dark-green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 4, + "x": 0, + "y": 6 + }, + "id": 8, + "options": { + "colorMode": "background", + "graphMode": "area", + "justifyMode": "center", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "last" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "value" + }, + "pluginVersion": "7.5.16", + "targets": [ + { + "exemplar": true, + "expr": "veeam_em_overview_element_count{instance=~\"$enterprisemanager\", count_type=\"tasks\", type=\"successful_vms\"}", + "interval": "", + "legendFormat": "{{ type }}", + "refId": "A" + } + ], + "title": "Successful VMs", + "type": "stat" + }, + { + "datasource": null, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "dark-orange", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 4, + "x": 4, + "y": 6 + }, + "id": 10, + "options": { + "colorMode": "background", + "graphMode": "area", + "justifyMode": "center", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "last" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "value" + }, + "pluginVersion": "7.5.16", + "targets": [ + { + "exemplar": true, + "expr": "veeam_em_overview_element_count{instance=~\"$enterprisemanager\", count_type=\"tasks\", type=\"warning_vms\"}", + "interval": "", + "legendFormat": "{{ type }}", + "refId": "A" + } + ], + "title": "Warning VMs", + "type": "stat" + }, + { + "datasource": null, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "dark-red", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 4, + "x": 8, + "y": 6 + }, + "id": 9, + "options": { + "colorMode": "background", + "graphMode": "area", + "justifyMode": "center", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "last" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "value" + }, + "pluginVersion": "7.5.16", + "targets": [ + { + "exemplar": true, + "expr": "veeam_em_overview_element_count{instance=~\"$enterprisemanager\", count_type=\"tasks\", type=\"failed_vms\"}", + "interval": "", + "legendFormat": "{{ type }}", + "refId": "A" + } + ], + "title": "Failed VMs", + "type": "stat" + }, + { + "datasource": null, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "rgb(228, 232, 228)", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 4, + "x": 12, + "y": 6 + }, + "id": 11, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "center", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "last" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "value" + }, + "pluginVersion": "7.5.16", + "targets": [ + { + "exemplar": true, + "expr": "veeam_em_overview_vms_count{instance=~\"$enterprisemanager\", type=\"protected\"}", + "interval": "", + "legendFormat": "{{ type }}", + "refId": "A" + } + ], + "title": "Protected VMs", + "type": "stat" + }, + { + "datasource": null, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "rgb(228, 232, 228)", + "value": null + } + ] + }, + "unit": "decbytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 4, + "x": 16, + "y": 6 + }, + "id": 30, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "center", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "last" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "value" + }, + "pluginVersion": "7.5.16", + "targets": [ + { + "exemplar": true, + "expr": "veeam_em_overview_vms_total_bytes{instance=~\"$enterprisemanager\", type=\"incremental_backup_points\"}", + "interval": "", + "legendFormat": "{{ type }}", + "refId": "A" + } + ], + "title": "Incremental backup VMs", + "type": "stat" + }, + { + "datasource": null, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "rgb(228, 232, 228)", + "value": null + } + ] + }, + "unit": "decbytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 4, + "x": 20, + "y": 6 + }, + "id": 32, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "center", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "last" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "value" + }, + "pluginVersion": "7.5.16", + "targets": [ + { + "exemplar": true, + "expr": "veeam_em_overview_vms_total_bytes{instance=~\"$enterprisemanager\", type=\"replica_restore_points\"}", + "interval": "", + "legendFormat": "{{ type }}", + "refId": "A" + } + ], + "title": "Replica Restore VMs", + "type": "stat" + }, + { + "collapsed": true, + "datasource": null, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 11 + }, + "id": 157, + "panels": [ + { + "datasource": null, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": null, + "filterable": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "#EAB839", + "value": 70 + }, + { + "color": "red", + "value": 90 + } + ] + }, + "unit": "percentunit" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "% Used" + }, + "properties": [ + { + "id": "custom.displayMode", + "value": "lcd-gauge" + }, + { + "id": "thresholds", + "value": { + "mode": "percentage", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "#EAB839", + "value": 70 + }, + { + "color": "red", + "value": 90 + } + ] + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Total bytes" + }, + "properties": [ + { + "id": "unit", + "value": "decbytes" + }, + { + "id": "custom.width", + "value": 138 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Free Bytes" + }, + "properties": [ + { + "id": "unit", + "value": "decbytes" + }, + { + "id": "custom.width", + "value": 140 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "type" + }, + "properties": [ + { + "id": "custom.width", + "value": 181 + } + ] + } + ] + }, + "gridPos": { + "h": 12, + "w": 24, + "x": 0, + "y": 12 + }, + "id": 14, + "options": { + "showHeader": true, + "sortBy": [ + { + "desc": true, + "displayName": "% Used" + } + ] + }, + "pluginVersion": "7.5.5", + "targets": [ + { + "exemplar": true, + "expr": "1 - (sum(veeam_em_overview_repositories_capacity_free_bytes{}) by (name, type) / sum(veeam_em_overview_repositories_capacity_total_bytes{}) by (name, type) )", + "format": "table", + "instant": true, + "interval": "", + "legendFormat": "{{ name }}", + "refId": "A" + }, + { + "exemplar": true, + "expr": "sum(veeam_em_overview_repositories_capacity_total_bytes{}) by (name, type)", + "format": "table", + "hide": false, + "instant": true, + "interval": "", + "legendFormat": "", + "refId": "B" + }, + { + "exemplar": true, + "expr": "sum(veeam_em_overview_repositories_capacity_free_bytes{}) by (name, type)", + "format": "table", + "hide": false, + "instant": true, + "interval": "", + "legendFormat": "", + "refId": "C" + } + ], + "title": "Backup Repository - Usage Capacity", + "transformations": [ + { + "id": "filterFieldsByName", + "options": { + "include": { + "names": [ + "name", + "type", + "Value #A", + "Value #B", + "Value #C" + ] + } + } + }, + { + "id": "merge", + "options": {} + }, + { + "id": "organize", + "options": { + "excludeByName": {}, + "indexByName": { + "Value #A": 4, + "Value #B": 2, + "Value #C": 3, + "name": 0, + "type": 1 + }, + "renameByName": { + "Value": "Usage", + "Value #A": "% Used", + "Value #B": "Total bytes", + "Value #C": "Free Bytes" + } + } + } + ], + "type": "table" + } + ], + "title": "Repositories", + "type": "row" + }, + { + "collapsed": false, + "datasource": null, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 12 + }, + "id": 36, + "panels": [], + "title": "Job Historical Performance and Duration", + "type": "row" + }, + { + "datasource": null, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "short" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Warning" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Success" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Failed" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Idle" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "rgba(224, 218, 224, 0.58)", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byRegexp", + "options": "/(Idle)|(Working)/" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "super-light-purple", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 9, + "w": 12, + "x": 0, + "y": 13 + }, + "id": 161, + "options": { + "displayLabels": [ + "value", + "name" + ], + "legend": { + "displayMode": "table", + "placement": "right", + "values": [ + "value" + ] + }, + "pieType": "donut", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {} + }, + "pluginVersion": "7.5.5", + "targets": [ + { + "exemplar": true, + "expr": "count(veeam_em_jobs_sessions_state{instance=~\"$enterprisemanager\"} == 1) or vector(0)", + "instant": true, + "interval": "", + "legendFormat": "Success", + "refId": "A" + }, + { + "exemplar": true, + "expr": "count(veeam_em_jobs_sessions_state{instance=~\"$enterprisemanager\"} == 2) or vector(0)", + "hide": false, + "instant": true, + "interval": "", + "legendFormat": "Warning", + "refId": "B" + }, + { + "exemplar": true, + "expr": "count(veeam_em_jobs_sessions_state{instance=~\"$enterprisemanager\"} == 3) or vector(0)", + "hide": false, + "instant": true, + "interval": "", + "legendFormat": "Failed", + "refId": "C" + }, + { + "exemplar": true, + "expr": "count(veeam_em_jobs_sessions_state{instance=~\"$enterprisemanager\"} == 4) or vector(0)", + "hide": false, + "instant": true, + "interval": "", + "legendFormat": "Idle", + "refId": "D" + }, + { + "exemplar": true, + "expr": "count(veeam_em_jobs_sessions_state{instance=~\"$enterprisemanager\"} == 5) or vector(0)", + "hide": false, + "interval": "", + "legendFormat": "Working", + "refId": "E" + } + ], + "timeFrom": null, + "timeShift": null, + "title": "Backup Job State (last 24 hours)", + "type": "piechart" + }, + { + "datasource": null, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "short" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Warning" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Success" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Failed" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Idle" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "rgba(224, 218, 224, 0.58)", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byRegexp", + "options": "/(Idle)|(Working)/" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "super-light-purple", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 9, + "w": 12, + "x": 12, + "y": 13 + }, + "id": 162, + "options": { + "displayLabels": [ + "value", + "name" + ], + "legend": { + "displayMode": "table", + "placement": "right", + "values": [ + "value" + ] + }, + "pieType": "donut", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {} + }, + "pluginVersion": "7.5.5", + "targets": [ + { + "exemplar": true, + "expr": "count(veeam_em_vmjobs_sessions_state{instance=~\"$enterprisemanager\"} == 1) or vector(0)", + "instant": false, + "interval": "", + "legendFormat": "Success", + "refId": "A" + }, + { + "exemplar": true, + "expr": "count(veeam_em_vmjobs_sessions_state{instance=~\"$enterprisemanager\"} == 2) or vector(0)", + "hide": false, + "interval": "", + "legendFormat": "Warning", + "refId": "B" + }, + { + "exemplar": true, + "expr": "count(veeam_em_vmjobs_sessions_state{instance=~\"$enterprisemanager\"} == 3) or vector(0)", + "hide": false, + "interval": "", + "legendFormat": "Failed", + "refId": "C" + }, + { + "exemplar": true, + "expr": "count(veeam_em_jobs_sessions_state{instance=~\"$enterprisemanager\"} == 4) or vector(0)", + "hide": false, + "interval": "", + "legendFormat": "Idle", + "refId": "D" + }, + { + "exemplar": true, + "expr": "count(veeam_em_jobs_sessions_state{instance=~\"$enterprisemanager\"} == 5) or vector(0)", + "hide": false, + "interval": "", + "legendFormat": "Working", + "refId": "E" + } + ], + "timeFrom": null, + "timeShift": null, + "title": "Backup VM Jobs State (last 24 hours)", + "type": "piechart" + }, + { + "datasource": null, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": null, + "filterable": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Status" + }, + "properties": [ + { + "id": "custom.displayMode", + "value": "color-background" + }, + { + "id": "mappings", + "value": [ + { + "from": "", + "id": 1, + "text": "Success", + "to": "", + "type": 1, + "value": "1" + }, + { + "from": "", + "id": 2, + "text": "Warning", + "to": "", + "type": 1, + "value": "2" + }, + { + "from": "", + "id": 3, + "text": "Failed", + "to": "", + "type": 1, + "value": "3" + }, + { + "from": "", + "id": 4, + "text": "Idle", + "to": "", + "type": 1, + "value": "4" + }, + { + "from": "", + "id": 6, + "text": "Working", + "to": "", + "type": 1, + "value": "5" + }, + { + "from": "", + "id": 7, + "text": "!! undefined !!", + "to": "", + "type": 1, + "value": "0" + } + ] + }, + { + "id": "thresholds", + "value": { + "mode": "absolute", + "steps": [ + { + "color": "rgb(106, 107, 105)", + "value": null + }, + { + "color": "green", + "value": 1 + }, + { + "color": "#EAB839", + "value": 2 + }, + { + "color": "red", + "value": 3 + }, + { + "color": "rgb(164, 168, 168)", + "value": 4 + } + ] + } + }, + { + "id": "custom.width", + "value": 86 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Duration" + }, + "properties": [ + { + "id": "custom.width", + "value": 99 + }, + { + "id": "unit", + "value": "s" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "type" + }, + "properties": [ + { + "id": "custom.width", + "value": 146 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "startdate" + }, + "properties": [ + { + "id": "custom.width", + "value": 193 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "backupserver" + }, + "properties": [ + { + "id": "custom.width", + "value": 197 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "jobname" + }, + "properties": [ + { + "id": "custom.width", + "value": 308 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "jobretries" + }, + "properties": [ + { + "id": "custom.width", + "value": 75 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "name" + }, + "properties": [ + { + "id": "custom.width", + "value": 602 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Retries" + }, + "properties": [ + { + "id": "custom.width", + "value": 64 + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 22 + }, + "id": 87, + "options": { + "showHeader": true, + "sortBy": [ + { + "desc": true, + "displayName": "Status" + } + ] + }, + "pluginVersion": "7.5.16", + "targets": [ + { + "exemplar": true, + "expr": "veeam_em_jobs_sessions_state{instance=~\"$enterprisemanager\"}", + "format": "table", + "hide": false, + "instant": true, + "interval": "", + "legendFormat": "", + "refId": "A" + }, + { + "exemplar": true, + "expr": "veeam_em_jobs_sessions_duration{instance=~\"$enterprisemanager\"}", + "format": "table", + "hide": false, + "instant": true, + "interval": "", + "legendFormat": "", + "refId": "B" + }, + { + "exemplar": true, + "expr": "veeam_em_jobs_sessions_retries{instance=~\"$enterprisemanager\"}", + "format": "table", + "hide": false, + "instant": true, + "interval": "", + "legendFormat": "", + "refId": "C" + } + ], + "title": "Backup Job Status", + "transformations": [ + { + "id": "filterFieldsByName", + "options": { + "include": { + "names": [ + "backupserver", + "jobname", + "name", + "Value #A", + "Value #B", + "Value #C" + ] + } + } + }, + { + "id": "merge", + "options": {} + }, + { + "id": "organize", + "options": { + "excludeByName": {}, + "indexByName": { + "Value #A": 3, + "Value #B": 4, + "backupserver": 0, + "jobname": 1, + "jobretries": 5, + "name": 2 + }, + "renameByName": { + "Value #A": "Status", + "Value #B": "Duration", + "Value #C": "Retries" + } + } + } + ], + "type": "table" + }, + { + "datasource": null, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": null, + "filterable": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Status" + }, + "properties": [ + { + "id": "custom.displayMode", + "value": "color-background" + }, + { + "id": "mappings", + "value": [ + { + "from": "", + "id": 1, + "text": "Success", + "to": "", + "type": 1, + "value": "1" + }, + { + "from": "", + "id": 2, + "text": "Warning", + "to": "", + "type": 1, + "value": "2" + }, + { + "from": "", + "id": 3, + "text": "Failed", + "to": "", + "type": 1, + "value": "3" + }, + { + "from": "", + "id": 4, + "text": "Idle", + "to": "", + "type": 1, + "value": "4" + }, + { + "from": "", + "id": 5, + "text": "!! Undefined !!", + "to": "", + "type": 1, + "value": "0" + }, + { + "from": "", + "id": 6, + "text": "Working", + "to": "", + "type": 1, + "value": "5" + } + ] + }, + { + "id": "thresholds", + "value": { + "mode": "absolute", + "steps": [ + { + "color": "rgb(106, 107, 105)", + "value": null + }, + { + "color": "green", + "value": 1 + }, + { + "color": "#EAB839", + "value": 2 + }, + { + "color": "red", + "value": 3 + }, + { + "color": "rgb(164, 168, 168)", + "value": 4 + } + ] + } + }, + { + "id": "custom.width", + "value": 89 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Duration" + }, + "properties": [ + { + "id": "custom.width", + "value": 99 + }, + { + "id": "unit", + "value": "s" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "backupserver" + }, + "properties": [ + { + "id": "custom.width", + "value": 197 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "jobname" + }, + "properties": [ + { + "id": "custom.width", + "value": 443 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Retries" + }, + "properties": [ + { + "id": "custom.width", + "value": 65 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "vmname" + }, + "properties": [ + { + "id": "custom.width", + "value": 148 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "VM Size" + }, + "properties": [ + { + "id": "unit", + "value": "decbytes" + }, + { + "id": "custom.width", + "value": 91 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "taskname" + }, + "properties": [ + { + "id": "custom.width", + "value": 311 + } + ] + } + ] + }, + "gridPos": { + "h": 9, + "w": 24, + "x": 0, + "y": 30 + }, + "id": 160, + "options": { + "showHeader": true, + "sortBy": [ + { + "desc": false, + "displayName": "vmname" + } + ] + }, + "pluginVersion": "7.5.16", + "targets": [ + { + "exemplar": true, + "expr": "veeam_em_vmjobs_sessions_state{instance=~\"$enterprisemanager\"}", + "format": "table", + "hide": false, + "instant": true, + "interval": "", + "legendFormat": "", + "refId": "A" + }, + { + "exemplar": true, + "expr": "veeam_em_vmjobs_sessions_duration{instance=~\"$enterprisemanager\"}", + "format": "table", + "hide": false, + "instant": true, + "interval": "", + "legendFormat": "", + "refId": "B" + }, + { + "exemplar": true, + "expr": "veeam_em_vmjobs_sessions_retries{instance=~\"$enterprisemanager\"}", + "format": "table", + "hide": true, + "instant": true, + "interval": "", + "legendFormat": "", + "refId": "C" + }, + { + "exemplar": true, + "expr": "veeam_em_vmjobs_sessions_total_bytes{instance=~\"$enterprisemanager\"}", + "format": "table", + "hide": false, + "instant": true, + "interval": "", + "legendFormat": "", + "refId": "D" + } + ], + "title": "Backup VMJob Status", + "transformations": [ + { + "id": "filterFieldsByName", + "options": { + "include": { + "names": [ + "backupserver", + "jobname", + "taskname", + "vmname", + "Value #A", + "Value #B", + "Value #D" + ] + } + } + }, + { + "id": "merge", + "options": {} + }, + { + "id": "organize", + "options": { + "excludeByName": {}, + "indexByName": { + "Value #A": 4, + "Value #B": 5, + "Value #D": 6, + "backupserver": 0, + "jobname": 1, + "taskname": 3, + "vmname": 2 + }, + "renameByName": { + "Value #A": "Status", + "Value #B": "Duration", + "Value #C": "Retries", + "Value #D": "VM Size" + } + } + } + ], + "type": "table" + }, + { + "datasource": null, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": null, + "filterable": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Status" + }, + "properties": [ + { + "id": "mappings", + "value": [ + { + "from": "", + "id": 1, + "text": "Undefined", + "to": "", + "type": 1, + "value": "0" + }, + { + "from": "", + "id": 2, + "text": "Failed", + "to": "", + "type": 1, + "value": "3" + }, + { + "from": "", + "id": 3, + "text": "Pending", + "to": "", + "type": 1, + "value": "4" + }, + { + "from": "", + "id": 4, + "text": "Working", + "to": "", + "type": 1, + "value": "5" + }, + { + "from": "", + "id": 5, + "text": "Warning", + "to": "", + "type": 1, + "value": "2" + } + ] + }, + { + "id": "custom.displayMode", + "value": "color-background" + }, + { + "id": "thresholds", + "value": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "rgb(131, 135, 135)", + "value": 1 + }, + { + "color": "#EAB839", + "value": 2 + }, + { + "color": "red", + "value": 3 + }, + { + "color": "dark-purple", + "value": 4 + } + ] + } + }, + { + "id": "custom.width", + "value": 108 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "vmname" + }, + "properties": [ + { + "id": "custom.width", + "value": 108 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "message" + }, + "properties": [ + { + "id": "custom.width", + "value": 507 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "taskname" + }, + "properties": [ + { + "id": "custom.width", + "value": 283 + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 39 + }, + "id": 169, + "options": { + "showHeader": true, + "sortBy": [] + }, + "pluginVersion": "7.5.16", + "targets": [ + { + "exemplar": true, + "expr": "veeam_em_vmjobs_sessions_error{instance=~\"$enterprisemanager\"}", + "format": "table", + "instant": true, + "interval": "", + "legendFormat": "", + "refId": "A" + } + ], + "title": "Backup VMJob Errors", + "transformations": [ + { + "id": "filterFieldsByName", + "options": { + "include": { + "names": [ + "backupserver", + "jobname", + "message", + "taskname", + "vmname", + "Value" + ] + } + } + }, + { + "id": "organize", + "options": { + "excludeByName": {}, + "indexByName": { + "Value #A": 4, + "backupserver": 0, + "jobname": 1, + "message": 5, + "taskname": 3, + "vmname": 2 + }, + "renameByName": { + "Value": "Status", + "Value #A": "Status" + } + } + } + ], + "type": "table" + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": null, + "fieldConfig": { + "defaults": { + "unit": "s" + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 47 + }, + "hiddenSeries": false, + "id": 34, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.16", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "veeam_em_overview_jobs_max_duration{instance=~\"$enterprisemanager\"}", + "interval": "", + "legendFormat": "MAX {{ jobname }} ( {{ type }} )", + "refId": "A" + }, + { + "exemplar": true, + "expr": "avg(veeam_em_jobs_sessions_duration{instance=~\"$enterprisemanager\"}) by(backupserver)", + "hide": false, + "interval": "", + "legendFormat": "avg {{ backupserver }} ", + "refId": "B" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Job Duration (All type)", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:341", + "format": "s", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "$$hashKey": "object:342", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": null, + "fieldConfig": { + "defaults": { + "unit": "percentunit" + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 47 + }, + "hiddenSeries": false, + "id": 133, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.16", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "1 - (count (veeam_em_jobs_sessions_retries{instance=~\"$enterprisemanager\"} != 0 ) by(instance, backupserver)/ count(veeam_em_jobs_sessions_retries{instance=~\"$enterprisemanager\"} ) by(instance, backupserver) )", + "interval": "", + "legendFormat": "{{ backupserver }}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Job Reries percent (All type)", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:341", + "format": "percentunit", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "$$hashKey": "object:342", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "collapsed": false, + "datasource": null, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 55 + }, + "id": 135, + "panels": [], + "repeat": "backupserver", + "title": "backup Jobs for [$backupserver]", + "type": "row" + }, + { + "datasource": null, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "short" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Warning" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Success" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Failed" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Idle" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-purple", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Working" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "super-light-purple", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 9, + "w": 8, + "x": 0, + "y": 56 + }, + "id": 136, + "options": { + "displayLabels": [ + "value", + "name" + ], + "legend": { + "displayMode": "table", + "placement": "right", + "values": [ + "value" + ] + }, + "pieType": "donut", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {} + }, + "pluginVersion": "7.5.5", + "targets": [ + { + "exemplar": true, + "expr": "count(veeam_em_jobs_sessions_state{instance=~\"$enterprisemanager\", backupserver=~\"$backupserver\"} == 1) or vector(0)", + "instant": false, + "interval": "", + "legendFormat": "Success", + "refId": "A" + }, + { + "exemplar": true, + "expr": "count(veeam_em_jobs_sessions_state{instance=~\"$enterprisemanager\", backupserver=~\"$backupserver\"} == 2) or vector(0)", + "hide": false, + "interval": "", + "legendFormat": "Warning", + "refId": "B" + }, + { + "exemplar": true, + "expr": "count(veeam_em_jobs_sessions_state{instance=~\"$enterprisemanager\", backupserver=~\"$backupserver\"} == 3) or vector(0)", + "hide": false, + "interval": "", + "legendFormat": "Failed", + "refId": "C" + }, + { + "exemplar": true, + "expr": "count(veeam_em_jobs_sessions_state{instance=~\"$enterprisemanager\", backupserver=~\"$backupserver\"} == 4) or vector(0)", + "hide": false, + "interval": "", + "legendFormat": "Idle", + "refId": "D" + }, + { + "exemplar": true, + "expr": "count(veeam_em_jobs_sessions_state{instance=~\"$enterprisemanager\", backupserver=~\"$backupserver\"} == 5) or vector(0)", + "hide": false, + "interval": "", + "legendFormat": "Working", + "refId": "E" + } + ], + "timeFrom": null, + "timeShift": null, + "title": "Job State", + "type": "piechart" + }, + { + "datasource": null, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": null, + "filterable": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Status" + }, + "properties": [ + { + "id": "custom.displayMode", + "value": "color-background" + }, + { + "id": "mappings", + "value": [ + { + "from": "", + "id": 1, + "text": "Success", + "to": "", + "type": 1, + "value": "1" + }, + { + "from": "", + "id": 2, + "text": "Warning", + "to": "", + "type": 1, + "value": "2" + }, + { + "from": "", + "id": 3, + "text": "Failed", + "to": "", + "type": 1, + "value": "3" + }, + { + "from": "", + "id": 4, + "text": "Idle", + "to": "", + "type": 1, + "value": "4" + }, + { + "from": "", + "id": 5, + "text": "!! Undefined !!", + "to": "", + "type": 1, + "value": "0" + }, + { + "from": "", + "id": 6, + "text": "Working", + "to": "", + "type": 1, + "value": "5" + } + ] + }, + { + "id": "thresholds", + "value": { + "mode": "absolute", + "steps": [ + { + "color": "rgb(106, 107, 105)", + "value": null + }, + { + "color": "green", + "value": 1 + }, + { + "color": "#EAB839", + "value": 2 + }, + { + "color": "red", + "value": 3 + }, + { + "color": "rgb(164, 168, 168)", + "value": 4 + } + ] + } + }, + { + "id": "custom.width", + "value": 86 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Duration" + }, + "properties": [ + { + "id": "custom.width", + "value": 99 + }, + { + "id": "unit", + "value": "s" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "type" + }, + "properties": [ + { + "id": "custom.width", + "value": 146 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "startdate" + }, + "properties": [ + { + "id": "custom.width", + "value": 193 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "backupserver" + }, + "properties": [ + { + "id": "custom.width", + "value": 197 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "jobname" + }, + "properties": [ + { + "id": "custom.width", + "value": 256 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "jobretries" + }, + "properties": [ + { + "id": "custom.width", + "value": 75 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "name" + }, + "properties": [ + { + "id": "custom.width", + "value": 369 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Retries" + }, + "properties": [ + { + "id": "custom.width", + "value": 54 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "jobtype" + }, + "properties": [ + { + "id": "custom.width", + "value": 86 + } + ] + } + ] + }, + "gridPos": { + "h": 9, + "w": 16, + "x": 8, + "y": 56 + }, + "id": 155, + "options": { + "showHeader": true, + "sortBy": [ + { + "desc": false, + "displayName": "Status" + } + ] + }, + "pluginVersion": "7.5.16", + "targets": [ + { + "exemplar": true, + "expr": "veeam_em_jobs_sessions_state{instance=~\"$enterprisemanager\", backupserver=~\"$backupserver\"}", + "format": "table", + "hide": false, + "instant": true, + "interval": "", + "legendFormat": "", + "refId": "A" + }, + { + "exemplar": true, + "expr": "veeam_em_jobs_sessions_duration{instance=~\"$enterprisemanager\", backupserver=~\"$backupserver\"}", + "format": "table", + "hide": false, + "instant": true, + "interval": "", + "legendFormat": "", + "refId": "B" + }, + { + "exemplar": true, + "expr": "veeam_em_jobs_sessions_retries{instance=~\"$enterprisemanager\", backupserver=~\"$backupserver\"}", + "format": "table", + "hide": false, + "instant": true, + "interval": "", + "legendFormat": "", + "refId": "C" + } + ], + "title": "Backup Job Status", + "transformations": [ + { + "id": "filterFieldsByName", + "options": { + "include": { + "names": [ + "jobname", + "name", + "Value #A", + "Value #B", + "Value #C", + "jobtype" + ] + } + } + }, + { + "id": "merge", + "options": {} + }, + { + "id": "organize", + "options": { + "excludeByName": {}, + "indexByName": { + "Value #A": 1, + "Value #B": 3, + "Value #C": 4, + "jobname": 0, + "name": 2 + }, + "renameByName": { + "Value #A": "Status", + "Value #B": "Duration", + "Value #C": "Retries" + } + } + } + ], + "type": "table" + }, + { + "datasource": null, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "short" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Warning" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Success" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Failed" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "red", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Idle" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-purple", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Working" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "super-light-purple", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 9, + "w": 8, + "x": 0, + "y": 65 + }, + "id": 158, + "options": { + "displayLabels": [ + "value", + "name" + ], + "legend": { + "displayMode": "table", + "placement": "right", + "values": [ + "value" + ] + }, + "pieType": "donut", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": {} + }, + "pluginVersion": "7.5.5", + "targets": [ + { + "exemplar": true, + "expr": "count(veeam_em_vmjobs_sessions_state{instance=~\"$enterprisemanager\", backupserver=~\"$backupserver\"} == 1) or vector(0)", + "instant": false, + "interval": "", + "legendFormat": "Success", + "refId": "A" + }, + { + "exemplar": true, + "expr": "count(veeam_em_vmjobs_sessions_state{instance=~\"$enterprisemanager\", backupserver=~\"$backupserver\"} == 2) or vector(0)", + "hide": false, + "interval": "", + "legendFormat": "Warning", + "refId": "B" + }, + { + "exemplar": true, + "expr": "count(veeam_em_vmjobs_sessions_state{instance=~\"$enterprisemanager\", backupserver=~\"$backupserver\"} == 3) or vector(0)", + "hide": false, + "interval": "", + "legendFormat": "Failed", + "refId": "C" + }, + { + "exemplar": true, + "expr": "count(veeam_em_vmjobs_sessions_state{instance=~\"$enterprisemanager\", backupserver=~\"$backupserver\"} == 4) or vector(0)", + "hide": false, + "interval": "", + "legendFormat": "Idle", + "refId": "D" + }, + { + "exemplar": true, + "expr": "count(veeam_em_vmjobs_sessions_state{instance=~\"$enterprisemanager\", backupserver=~\"$backupserver\"} == 5) or vector(0)", + "hide": false, + "interval": "", + "legendFormat": "Working", + "refId": "E" + } + ], + "timeFrom": null, + "timeShift": null, + "title": "VM Jobs State", + "type": "piechart" + }, + { + "datasource": null, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": null, + "filterable": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Status" + }, + "properties": [ + { + "id": "custom.displayMode", + "value": "color-background" + }, + { + "id": "mappings", + "value": [ + { + "from": "", + "id": 1, + "text": "Success", + "to": "", + "type": 1, + "value": "1" + }, + { + "from": "", + "id": 2, + "text": "Warning", + "to": "", + "type": 1, + "value": "2" + }, + { + "from": "", + "id": 3, + "text": "Failed", + "to": "", + "type": 1, + "value": "3" + }, + { + "from": "", + "id": 4, + "text": "Idle", + "to": "", + "type": 1, + "value": "4" + }, + { + "from": "", + "id": 5, + "text": "!! Undefined !!", + "to": "", + "type": 1, + "value": "0" + }, + { + "from": "", + "id": 6, + "text": "Working", + "to": "", + "type": 1, + "value": "5" + } + ] + }, + { + "id": "thresholds", + "value": { + "mode": "absolute", + "steps": [ + { + "color": "rgb(106, 107, 105)", + "value": null + }, + { + "color": "green", + "value": 1 + }, + { + "color": "#EAB839", + "value": 2 + }, + { + "color": "red", + "value": 3 + }, + { + "color": "rgb(164, 168, 168)", + "value": 4 + } + ] + } + }, + { + "id": "custom.width", + "value": 86 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Duration" + }, + "properties": [ + { + "id": "custom.width", + "value": 99 + }, + { + "id": "unit", + "value": "s" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "jobname" + }, + "properties": [ + { + "id": "custom.width", + "value": 240 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Retries" + }, + "properties": [ + { + "id": "custom.width", + "value": 65 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "vmname" + }, + "properties": [ + { + "id": "custom.width", + "value": 131 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "VM Size" + }, + "properties": [ + { + "id": "unit", + "value": "decbytes" + }, + { + "id": "custom.width", + "value": 91 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "taskname" + }, + "properties": [ + { + "id": "custom.width", + "value": 271 + } + ] + } + ] + }, + "gridPos": { + "h": 9, + "w": 16, + "x": 8, + "y": 65 + }, + "id": 159, + "options": { + "showHeader": true, + "sortBy": [ + { + "desc": true, + "displayName": "Status" + } + ] + }, + "pluginVersion": "7.5.16", + "targets": [ + { + "exemplar": true, + "expr": "veeam_em_vmjobs_sessions_state{instance=~\"$enterprisemanager\", backupserver=~\"$backupserver\"}", + "format": "table", + "hide": false, + "instant": true, + "interval": "", + "legendFormat": "", + "refId": "A" + }, + { + "exemplar": true, + "expr": "veeam_em_vmjobs_sessions_duration{instance=~\"$enterprisemanager\", backupserver=~\"$backupserver\"}", + "format": "table", + "hide": false, + "instant": true, + "interval": "", + "legendFormat": "", + "refId": "B" + }, + { + "exemplar": true, + "expr": "veeam_em_vmjobs_sessions_retries{instance=~\"$enterprisemanager\", backupserver=~\"$backupserver\"}", + "format": "table", + "hide": false, + "instant": true, + "interval": "", + "legendFormat": "", + "refId": "C" + }, + { + "exemplar": true, + "expr": "veeam_em_vmjobs_sessions_total_bytes{instance=~\"$enterprisemanager\", backupserver=~\"$backupserver\"}", + "format": "table", + "hide": false, + "instant": true, + "interval": "", + "legendFormat": "", + "refId": "D" + } + ], + "title": "Backup VMJob Status", + "transformations": [ + { + "id": "filterFieldsByName", + "options": { + "include": { + "names": [ + "jobname", + "taskname", + "vmname", + "Value #A", + "Value #B", + "Value #D" + ] + } + } + }, + { + "id": "merge", + "options": {} + }, + { + "id": "organize", + "options": { + "excludeByName": {}, + "indexByName": { + "Value #A": 3, + "Value #B": 4, + "Value #C": 5, + "jobname": 1, + "taskname": 2, + "vmname": 0 + }, + "renameByName": { + "Value #A": "Status", + "Value #B": "Duration", + "Value #C": "Retries", + "Value #D": "VM Size" + } + } + } + ], + "type": "table" + }, + { + "datasource": null, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": null, + "filterable": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Status" + }, + "properties": [ + { + "id": "mappings", + "value": [ + { + "from": "", + "id": 1, + "text": "Undefined", + "to": "", + "type": 1, + "value": "0" + }, + { + "from": "", + "id": 2, + "text": "Failed", + "to": "", + "type": 1, + "value": "3" + }, + { + "from": "", + "id": 3, + "text": "Pending", + "to": "", + "type": 1, + "value": "4" + }, + { + "from": "", + "id": 4, + "text": "Working", + "to": "", + "type": 1, + "value": "5" + }, + { + "from": "", + "id": 5, + "text": "Warning", + "to": "", + "type": 1, + "value": "2" + } + ] + }, + { + "id": "custom.displayMode", + "value": "color-background" + }, + { + "id": "thresholds", + "value": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "rgb(131, 135, 135)", + "value": 1 + }, + { + "color": "#EAB839", + "value": 2 + }, + { + "color": "red", + "value": 3 + }, + { + "color": "dark-purple", + "value": 4 + } + ] + } + }, + { + "id": "custom.width", + "value": 108 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "vmname" + }, + "properties": [ + { + "id": "custom.width", + "value": 108 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "message" + }, + "properties": [ + { + "id": "custom.width", + "value": 507 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "taskname" + }, + "properties": [ + { + "id": "custom.width", + "value": 283 + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 74 + }, + "id": 170, + "options": { + "showHeader": true, + "sortBy": [ + { + "desc": true, + "displayName": "Status" + } + ] + }, + "pluginVersion": "7.5.16", + "targets": [ + { + "exemplar": true, + "expr": "veeam_em_vmjobs_sessions_error{instance=~\"$enterprisemanager\", backupserver=~\"$backupserver\"}", + "format": "table", + "instant": true, + "interval": "", + "legendFormat": "", + "refId": "A" + } + ], + "title": "VM Backup Errors", + "transformations": [ + { + "id": "filterFieldsByName", + "options": { + "include": { + "names": [ + "backupserver", + "jobname", + "message", + "taskname", + "vmname", + "Value" + ] + } + } + }, + { + "id": "organize", + "options": { + "excludeByName": {}, + "indexByName": { + "Value #A": 4, + "backupserver": 0, + "jobname": 1, + "message": 5, + "taskname": 3, + "vmname": 2 + }, + "renameByName": { + "Value": "Status", + "Value #A": "Status" + } + } + } + ], + "type": "table" + }, + { + "collapsed": true, + "datasource": null, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 325 + }, + "id": 38, + "panels": [ + { + "datasource": null, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": null, + "filterable": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Status" + }, + "properties": [ + { + "id": "mappings", + "value": [ + { + "from": "", + "id": 1, + "text": "Unknown", + "to": "", + "type": 1, + "value": "0" + }, + { + "from": "", + "id": 2, + "text": "Online", + "to": "", + "type": 1, + "value": "1" + }, + { + "from": "", + "id": 3, + "text": "Offline", + "to": "", + "type": 1, + "value": "2" + } + ] + }, + { + "id": "custom.displayMode", + "value": "color-background" + }, + { + "id": "thresholds", + "value": { + "mode": "absolute", + "steps": [ + { + "color": "rgb(142, 143, 142)", + "value": null + }, + { + "color": "green", + "value": 1 + }, + { + "color": "red", + "value": 2 + } + ] + } + }, + { + "id": "custom.width", + "value": 103 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Agent OS Version" + }, + "properties": [ + { + "id": "custom.width", + "value": 310 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Agent Version" + }, + "properties": [ + { + "id": "custom.width", + "value": 126 + } + ] + } + ] + }, + "gridPos": { + "h": 9, + "w": 24, + "x": 0, + "y": 24 + }, + "id": 40, + "options": { + "showHeader": true, + "sortBy": [ + { + "desc": true, + "displayName": "Status" + } + ] + }, + "pluginVersion": "7.5.5", + "targets": [ + { + "exemplar": true, + "expr": "veeam_em_agents_status{instance=~\"$enterprisemanager\"}", + "format": "table", + "instant": true, + "interval": "", + "legendFormat": "", + "refId": "A" + } + ], + "title": "Backup Agent Status", + "transformations": [ + { + "id": "filterFieldsByName", + "options": { + "include": { + "names": [ + "name", + "osversion", + "version", + "Value", + "backupserver" + ] + } + } + }, + { + "id": "organize", + "options": { + "excludeByName": {}, + "indexByName": {}, + "renameByName": { + "Value": "Status", + "name": "Agent Host", + "osversion": "Agent OS Version", + "version": "Agent Version" + } + } + } + ], + "type": "table" + } + ], + "repeat": null, + "title": "Veeam Agent Overview", + "type": "row" + }, + { + "collapsed": true, + "datasource": null, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 326 + }, + "id": 16, + "panels": [ + { + "datasource": null, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "blue", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 7, + "x": 0, + "y": 149 + }, + "id": 164, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "/^description$/", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "7.5.5", + "targets": [ + { + "exemplar": true, + "expr": "veeam_em_backup_servers_config{instance=~\"$enterprisemanager\", name=~\"$backupserver\"}", + "format": "table", + "instant": true, + "interval": "", + "legendFormat": "", + "refId": "A" + } + ], + "title": "Description", + "type": "stat" + }, + { + "datasource": null, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "blue", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 7, + "x": 7, + "y": 149 + }, + "id": 166, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "/^version$/", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "7.5.5", + "targets": [ + { + "exemplar": true, + "expr": "veeam_em_backup_servers_config{instance=~\"$enterprisemanager\", name=~\"$backupserver\"}", + "format": "table", + "instant": true, + "interval": "", + "legendFormat": "", + "refId": "A" + } + ], + "title": "Description", + "type": "stat" + }, + { + "datasource": null, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "blue", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 7, + "x": 14, + "y": 149 + }, + "id": 165, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "/^port$/", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "7.5.5", + "targets": [ + { + "exemplar": true, + "expr": "veeam_em_backup_servers_config{instance=~\"$enterprisemanager\", name=~\"$backupserver\"}", + "format": "table", + "instant": true, + "interval": "", + "legendFormat": "", + "refId": "A" + } + ], + "title": "Port", + "type": "stat" + }, + { + "aliasColors": { + "idle": "#0A50A1" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${DS_PROMETHEUS}", + "fieldConfig": { + "defaults": { + "unit": "percentunit" + }, + "overrides": [] + }, + "fill": 10, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 152 + }, + "hiddenSeries": false, + "hideTimeOverride": false, + "id": 25, + "legend": { + "alignAsTable": true, + "avg": false, + "current": true, + "max": true, + "min": false, + "rightSide": false, + "show": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 0, + "links": [], + "nullPointMode": "null as zero", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.5", + "pointradius": 5, + "points": false, + "renderer": "flot", + "repeat": "backupserver", + "scopedVars": { + "backupserver": { + "selected": true, + "text": "", + "value": "" + } + }, + "seriesOverrides": [], + "spaceLength": 10, + "stack": true, + "steppedLine": false, + "targets": [ + { + "application": { + "filter": "" + }, + "exemplar": true, + "expr": "1 - (sum by (host) (rate(windows_cpu_time_total{host=\"$backupserver\", mode=\"idle\"}[5m])) / count by (host) (windows_cpu_core_frequency_mhz{host=\"$backupserver\"}) )", + "format": "time_series", + "functions": [], + "group": { + "filter": "" + }, + "hide": false, + "host": { + "filter": "" + }, + "interval": "", + "intervalFactor": 1, + "item": { + "filter": "" + }, + "legendFormat": "", + "metric": "mysql_global_status_questions", + "mode": 0, + "options": { + "showDisabledItems": false + }, + "refId": "A", + "step": 20 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "CPU Usage / $backupserver", + "tooltip": { + "shared": false, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:104", + "format": "percentunit", + "label": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "$$hashKey": "object:105", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${DS_PROMETHEUS}", + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 168 + }, + "hiddenSeries": false, + "hideTimeOverride": false, + "id": 23, + "legend": { + "alignAsTable": true, + "avg": false, + "current": true, + "max": true, + "min": false, + "rightSide": false, + "show": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null as zero", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.5", + "pointradius": 5, + "points": false, + "renderer": "flot", + "repeat": "backupserver", + "scopedVars": { + "backupserver": { + "selected": true, + "text": "", + "value": "" + } + }, + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "application": { + "filter": "" + }, + "exemplar": true, + "expr": "windows_os_virtual_memory_bytes{host=~\"$backupserver\"}", + "format": "time_series", + "functions": [], + "group": { + "filter": "" + }, + "hide": false, + "host": { + "filter": "" + }, + "interval": "", + "intervalFactor": 1, + "item": { + "filter": "" + }, + "legendFormat": "Virtual memory {{ host }}", + "metric": "mysql_global_status_questions", + "mode": 0, + "options": { + "showDisabledItems": false + }, + "refId": "A", + "step": 5 + }, + { + "application": { + "filter": "" + }, + "exemplar": true, + "expr": "windows_cs_physical_memory_bytes{host=~\"$backupserver\"}", + "format": "time_series", + "functions": [], + "group": { + "filter": "" + }, + "hide": false, + "host": { + "filter": "" + }, + "interval": "", + "intervalFactor": 1, + "item": { + "filter": "" + }, + "legendFormat": "Physical memory {{ host }}", + "metric": "mysql_global_status_questions", + "mode": 0, + "options": { + "showDisabledItems": false + }, + "refId": "B", + "step": 5 + }, + { + "application": { + "filter": "" + }, + "exemplar": true, + "expr": "windows_os_physical_memory_free_bytes{host=~\"$backupserver\"}", + "format": "time_series", + "functions": [], + "group": { + "filter": "" + }, + "hide": false, + "host": { + "filter": "" + }, + "interval": "", + "intervalFactor": 1, + "item": { + "filter": "" + }, + "legendFormat": "Free physical memory {{ host }}", + "metric": "mysql_global_status_questions", + "mode": 0, + "options": { + "showDisabledItems": false + }, + "refId": "C", + "step": 5 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Memory / $backupserver", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:271", + "format": "decbytes", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "$$hashKey": "object:272", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "datasource": null, + "fieldConfig": { + "defaults": { + "mappings": [ + { + "from": "", + "id": 0, + "text": "Up", + "to": "", + "type": 1, + "value": "1" + }, + { + "from": "", + "id": 1, + "text": "Down", + "to": "", + "type": 1, + "value": "0" + }, + { + "from": "", + "id": 2, + "text": "No Data", + "to": "", + "type": 1, + "value": "null" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "semi-dark-red", + "value": null + }, + { + "color": "semi-dark-green", + "value": 0 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 24, + "x": 0, + "y": 184 + }, + "id": 21, + "options": { + "colorMode": "background", + "graphMode": "none", + "justifyMode": "center", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "mean" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "7.5.5", + "repeat": "backupserver", + "scopedVars": { + "backupserver": { + "selected": true, + "text": "", + "value": "" + } + }, + "targets": [ + { + "exemplar": true, + "expr": "up{host=~\"$backupserver\"}", + "format": "time_series", + "instant": true, + "interval": "", + "legendFormat": "{{ host }}", + "refId": "A" + } + ], + "timeFrom": null, + "timeShift": null, + "title": "Satus $backupserver", + "type": "stat" + }, + { + "datasource": null, + "fieldConfig": { + "defaults": { + "mappings": [], + "thresholds": { + "mode": "percentage", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 24, + "x": 0, + "y": 192 + }, + "id": 27, + "options": { + "displayMode": "lcd", + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "last" + ], + "fields": "", + "values": false + }, + "showUnfilled": true, + "text": {} + }, + "pluginVersion": "7.5.5", + "repeat": "backupserver", + "scopedVars": { + "backupserver": { + "selected": true, + "text": "", + "value": "" + } + }, + "targets": [ + { + "exemplar": true, + "expr": "100 - (windows_logical_disk_free_bytes{host=~\"$backupserver\"} / windows_logical_disk_size_bytes{host=~\"$backupserver\"})*100", + "interval": "", + "legendFormat": "{{ volume }}", + "refId": "A" + } + ], + "timeFrom": null, + "timeShift": null, + "title": "Disks $backupserver", + "type": "bargauge" + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${DS_PROMETHEUS}", + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 7, + "w": 24, + "x": 0, + "y": 206 + }, + "hiddenSeries": false, + "hideTimeOverride": false, + "id": 29, + "legend": { + "alignAsTable": true, + "avg": false, + "current": true, + "max": true, + "min": false, + "rightSide": false, + "show": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null as zero", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.5", + "pointradius": 5, + "points": false, + "renderer": "flot", + "repeat": "backupserver", + "scopedVars": { + "backupserver": { + "selected": true, + "text": "", + "value": "" + } + }, + "seriesOverrides": [ + { + "$$hashKey": "object:305", + "alias": "A", + "transform": "negative-Y" + } + ], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "application": { + "filter": "" + }, + "exemplar": true, + "expr": "rate(windows_net_bytes_sent_total{host=~\"$backupserver\"}[5m]) >0", + "format": "time_series", + "functions": [], + "group": { + "filter": "" + }, + "hide": false, + "host": { + "filter": "" + }, + "interval": "", + "intervalFactor": 1, + "item": { + "filter": "" + }, + "legendFormat": "Sent {{nic}} {{ host }}", + "metric": "mysql_global_status_questions", + "mode": 0, + "options": { + "showDisabledItems": false + }, + "refId": "B", + "step": 10 + }, + { + "application": { + "filter": "" + }, + "exemplar": true, + "expr": "rate(windows_net_bytes_received_total{host=~\"$backupserver\"}[5m]) <0", + "format": "time_series", + "functions": [], + "group": { + "filter": "" + }, + "hide": false, + "host": { + "filter": "" + }, + "interval": "", + "intervalFactor": 1, + "item": { + "filter": "" + }, + "legendFormat": "Received {{nic}} {{ host }}", + "metric": "mysql_global_status_questions", + "mode": 0, + "options": { + "showDisabledItems": false + }, + "refId": "A", + "step": 10 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Network $backupserver", + "tooltip": { + "shared": true, + "sort": 2, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "$$hashKey": "object:439", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "$$hashKey": "object:440", + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + } + ], + "repeat": null, + "title": "Backup Server Performance [ $backupserver ( $backupserver_version )] (node_exporter)", + "type": "row" + } + ], + "refresh": "5m", + "schemaVersion": 27, + "style": "dark", + "tags": [], + "templating": { + "list": [ + { + "allValue": null, + "current": {}, + "datasource": "${DS_PROMETHEUS}", + "definition": "label_values(veeam_em_up, instance)", + "description": null, + "error": null, + "hide": 0, + "includeAll": true, + "label": "Enterprise Manager", + "multi": true, + "name": "enterprisemanager", + "options": [], + "query": { + "query": "label_values(veeam_em_up, instance)", + "refId": "StandardVariableQuery" + }, + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "tagValuesQuery": "", + "tags": [], + "tagsQuery": "", + "type": "query", + "useTags": false + }, + { + "allValue": null, + "current": {}, + "datasource": "${DS_PROMETHEUS}", + "definition": "label_values(veeam_em_backup_servers_config, name)", + "description": null, + "error": null, + "hide": 0, + "includeAll": true, + "label": null, + "multi": true, + "name": "backupserver", + "options": [], + "query": { + "query": "label_values(veeam_em_backup_servers_config, name)", + "refId": "StandardVariableQuery" + }, + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "tagValuesQuery": "", + "tags": [], + "tagsQuery": "", + "type": "query", + "useTags": false + }, + { + "allValue": null, + "current": {}, + "datasource": "${DS_PROMETHEUS}", + "definition": "label_values(veeam_em_backup_servers_config{name=~\"$backupserver\"},version)", + "description": null, + "error": null, + "hide": 2, + "includeAll": false, + "label": "BackupServer Version", + "multi": false, + "name": "backupserver_version", + "options": [], + "query": { + "query": "label_values(veeam_em_backup_servers_config{name=~\"$backupserver\"},version)", + "refId": "StandardVariableQuery" + }, + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "tagValuesQuery": "", + "tags": [], + "tagsQuery": "", + "type": "query", + "useTags": false + }, + { + "allValue": null, + "current": { + "selected": false, + "text": "Warning", + "value": "2" + }, + "description": null, + "error": null, + "hide": 0, + "includeAll": false, + "label": "VM BackupJob Status", + "multi": false, + "name": "vm_status", + "options": [ + { + "selected": false, + "text": "Undefined", + "value": "0" + }, + { + "selected": false, + "text": "Success", + "value": "1" + }, + { + "selected": true, + "text": "Warning", + "value": "2" + }, + { + "selected": false, + "text": "Failed", + "value": "3" + }, + { + "selected": false, + "text": "Pending -Idle", + "value": "4" + }, + { + "selected": false, + "text": "Working-In Progress", + "value": "5" + } + ], + "query": "Undefined : 0, Success : 1, Warning : 2, Failed : 3, Pending -Idle : 4 , Working-In Progress : 5", + "queryValue": "", + "skipUrlSync": false, + "type": "custom" + } + ] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "Veeam Enterprise Manager", + "uid": "bpHLHK6Mk", + "version": 28 +} \ No newline at end of file diff --git a/contribs/prometheus/hp3par_node_example.yml b/contribs/prometheus/hp3par_node_example.yml new file mode 100644 index 0000000..f0fdc79 --- /dev/null +++ b/contribs/prometheus/hp3par_node_example.yml @@ -0,0 +1,8 @@ +- targets: [ "hp3par_node_1" ] + labels: + __tmp_source_host: "hp3par_exporter_host.domain.name:9321" + # if you have activated password encrypted passphrass + __auth_key: __shared__auth_passphrase__ + host: " hp3par_node_1_fullqualified.domain.name" + #custom labels… + environment: "DEV" diff --git a/contribs/prometheus/prometheus_jobs.yml b/contribs/prometheus/prometheus_jobs.yml new file mode 100644 index 0000000..ee9ca98 --- /dev/null +++ b/contribs/prometheus/prometheus_jobs.yml @@ -0,0 +1,16 @@ +#--------- Start prometheus hp3par exporter ---------# + - job_name: "hp3par" + metrics_path: /metrics + file_sd_configs: + - files: [ "/etc/prometheus/hp3par_nodes/*.yml" ] + relabel_configs: + - source_labels: [__address__] + target_label: __param_target + # if you use a shared passphrase between prometheus and exporter + - source_labels: [__auth_key] + target_label: __param_auth_key + # use the value set in __tmp_source_host as exporter host name + - source_labels: [__tmp_source_host] + target_label: __address__ + +#--------- End prometheus hp3par exporter ---------# diff --git a/contribs/systemd/system/hp3par_exporter.service b/contribs/systemd/system/hp3par_exporter.service new file mode 100644 index 0000000..1120422 --- /dev/null +++ b/contribs/systemd/system/hp3par_exporter.service @@ -0,0 +1,23 @@ +[Unit] +Description=hp3par_exporter (httpapi_exporter) for prometheus +Wants=network-online.target +After=network-online.target +StartLimitBurst=4 +StartLimitIntervalSec=30 + +[Service] +User=node_exporter +Group=node_exporter +WorkingDirectory=/etc/httpapi_exporter/hp3par/ +Restart=always +RestartSec=2 +Type=simple + +ExecStart=/opt/httpapi_exporter/hp3par_exporter \ + --config.file=/etc/httpapi_exporter/hp3par/config.yml \ + --log.level=warn \ + --web.listen-address=dal-v-survdadc.dassault-avion.val:9321 +ExecReload=/bin/kill -HUP $MAINPID + +[Install] +WantedBy=multi-user.target diff --git a/contribs/systemd/system/veeam_exporter.service b/contribs/systemd/system/veeam_exporter.service new file mode 100644 index 0000000..d1d1efd --- /dev/null +++ b/contribs/systemd/system/veeam_exporter.service @@ -0,0 +1,23 @@ +[Unit] +Description= veeam_exporter (httpapi_exporter) for prometheus +Wants=network-online.target +After=network-online.target +StartLimitBurst=4 +StartLimitIntervalSec=30 + +[Service] +User=node_exporter +Group=node_exporter +WorkingDirectory=/etc/httpapi_exporter/veeam/ +Restart=always +RestartSec=2 +Type=simple + +ExecStart=/opt/httpapi_exporter/veeam_exporter \ + --config.file=/etc/httpapi_exporter/veeam/config.yml \ + --log.level=warn \ + --web.listen-address=dal-v-survdadc.dassault-avion.val:9247 +ExecReload=/bin/kill -HUP $MAINPID + +[Install] +WantedBy=multi-user.target diff --git a/debug_action.go b/debug_action.go new file mode 100644 index 0000000..72265e0 --- /dev/null +++ b/debug_action.go @@ -0,0 +1,182 @@ +package main + +import ( + //"bytes" + "fmt" + + "github.com/go-kit/log" + "github.com/go-kit/log/level" +) + +// *************************************************************************************** +// *************************************************************************************** +// debug action +// *************************************************************************************** +// *************************************************************************************** + +// **************************** + +type DebugActionConfig struct { + MsgVal string `yaml:"msg"` + + msg *Field + + // Catches all undefined fields and must be empty after parsing. + XXX map[string]interface{} `yaml:",inline" json:"-"` +} + +// UnmarshalYAML implements the yaml.Unmarshaler interface for DebugActionConfig. +func (dc *DebugActionConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { + type plain DebugActionConfig + var err error + if err := unmarshal((*plain)(dc)); err != nil { + return err + } + // Check required fields + dc.msg, err = NewField(dc.MsgVal, nil) + if err != nil { + return fmt.Errorf("invalid template for debug message %q: %s", dc.MsgVal, err) + } + + return checkOverflow(dc.XXX, "debug action") +} + +// **************************** +type DebugAction struct { + Name *Field `yaml:"name,omitempty"` + With []any `yaml:"with,omitempty"` + When []*exporterTemplate `yaml:"when,omitempty"` + LoopVar string `yaml:"loop_var,omitempty"` + Vars map[string]any `yaml:"vars,omitempty"` + Until []*exporterTemplate `yaml:"until,omitempty"` + + Debug *DebugActionConfig `yaml:"debug"` +} + +func (a *DebugAction) Type() int { + return debug_action +} + +func (a *DebugAction) GetName(symtab map[string]any, logger log.Logger) string { + str, err := a.Name.GetValueString(symtab, nil, false) + if err != nil { + level.Warn(logger).Log("msg", fmt.Sprintf("invalid action name: %v", err)) + return "" + } + return str +} + +func (a *DebugAction) GetNameField() *Field { + return a.Name +} +func (a *DebugAction) SetNameField(name *Field) { + a.Name = name +} + +func (a *DebugAction) GetWidh() []any { + return a.With +} +func (a *DebugAction) SetWidth(with []any) { + a.With = with +} + +func (a *DebugAction) GetWhen() []*exporterTemplate { + return a.When + +} +func (a *DebugAction) SetWhen(when []*exporterTemplate) { + a.When = when +} + +func (a *DebugAction) GetLoopVar() string { + return a.LoopVar +} +func (a *DebugAction) SetLoopVar(loopvar string) { + a.LoopVar = loopvar +} + +func (a *DebugAction) GetVars() map[string]any { + return a.Vars +} +func (a *DebugAction) SetVars(vars map[string]any) { + a.Vars = vars +} + +func (a *DebugAction) GetUntil() []*exporterTemplate { + return a.Until +} +func (a *DebugAction) SetUntil(until []*exporterTemplate) { + a.Until = until +} + +// func (a *DebugAction) GetBaseAction() *BaseAction { +// return nil +// } + +func (a *DebugAction) setBasicElement( + nameField *Field, + vars map[string]any, + with []any, + loopVar string, + when []*exporterTemplate, + until []*exporterTemplate) error { + return setBasicElement(a, nameField, vars, with, loopVar, when, until) +} + +func (a *DebugAction) PlayAction(script *YAMLScript, symtab map[string]any, logger log.Logger) error { + return PlayBaseAction(script, symtab, logger, a, a.CustomAction) +} + +// only for MetricsAction +func (a *DebugAction) GetMetrics() []*GetMetricsRes { + return nil +} + +// only for MetricAction +func (a *DebugAction) GetMetric() *MetricConfig { + return nil +} +func (a *DebugAction) SetMetricFamily(*MetricFamily) { +} + +// only for PlayAction +func (a *DebugAction) SetPlayAction(scripts map[string]*YAMLScript) error { + return nil +} + +// specific behavior for the DebugAction +func (a *DebugAction) CustomAction(script *YAMLScript, symtab map[string]any, logger log.Logger) error { + level.Debug(logger).Log( + "script", ScriptName(symtab, logger), + "msg", fmt.Sprintf("[Type: DebugAction] Name: %s", Name(a.Name, symtab, logger))) + + str, err := a.Debug.msg.GetValueString(symtab, nil, false) + if err != nil { + str = a.Debug.MsgVal + level.Warn(logger).Log( + "script", ScriptName(symtab, logger), + "msg", fmt.Sprintf("invalid template for debug message '%s': %v", str, err)) + } + + level.Debug(logger).Log( + "script", ScriptName(symtab, logger), + "msg", fmt.Sprintf(" message: %s", str)) + + return nil +} + +func (a *DebugAction) AddCustomTemplate(customTemplate *exporterTemplate) error { + + if err := AddCustomTemplate(a, customTemplate); err != nil { + return err + } + if a.Debug.msg != nil { + if err := a.Debug.msg.AddDefaultTemplate(customTemplate); err != nil { + return err + } + } + + return nil +} + +// *************************************************************************************** diff --git a/doc/session.drawio b/doc/session.drawio new file mode 100644 index 0000000..e3fe808 --- /dev/null +++ b/doc/session.drawio @@ -0,0 +1,269 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/doc/session.png b/doc/session.png new file mode 100644 index 0000000000000000000000000000000000000000..3b5343c76484dedd89ac70b742e25200550614a0 GIT binary patch literal 76031 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 W+tNWgwYI^nA=H(%l`8PpL;eq^TS+Vc literal 0 HcmV?d00001 diff --git a/encrypt/encrypt.go b/encrypt/encrypt.go new file mode 100644 index 0000000..ffcb375 --- /dev/null +++ b/encrypt/encrypt.go @@ -0,0 +1,79 @@ +package encrypt + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "encoding/base64" + "encoding/hex" + "fmt" +) + +type AESCipher struct { + GCM cipher.AEAD + nonceSize int +} + +// Initilze AES/GCM for both encrypting and decrypting. +func NewAESCipher(key_str string) (*AESCipher, error) { + + block, err := aes.NewCipher([]byte(key_str)) + if err != nil { + return nil, fmt.Errorf("error reading key: %s", err.Error()) + } + + gcm, err := cipher.NewGCM(block) + if err != nil { + return nil, fmt.Errorf("error initializing AEAD: %s", err.Error()) + } + + res := AESCipher{ + GCM: gcm, + nonceSize: gcm.NonceSize(), + } + return &res, nil +} + +func randBytes(length int) []byte { + b := make([]byte, length) + rand.Read(b) + return b +} + +func (ci *AESCipher) Encrypt(plaintext []byte, base64_encoded bool) (cipherstring string) { + nonce := randBytes(ci.nonceSize) + ciphertext := ci.GCM.Seal(nonce, nonce, plaintext, nil) + + if base64_encoded { + cipherstring = base64.StdEncoding.EncodeToString(ciphertext) + } else { + cipherstring = hex.EncodeToString(ciphertext) + } + return cipherstring +} + +func (ci *AESCipher) Decrypt(cipherstring string, base64_encoded bool) (plainstring string, err error) { + var ciphertext, plaintext []byte + + if base64_encoded { + ciphertext, err = base64.StdEncoding.DecodeString(cipherstring) + } else { + ciphertext, err = hex.DecodeString(cipherstring) + } + if err != nil { + return "", err + } + + if len(ciphertext) < ci.nonceSize { + return "", fmt.Errorf("ciphertext too short") + } + nonce := ciphertext[0:ci.nonceSize] + msg := ciphertext[ci.nonceSize:] + plaintext, err = ci.GCM.Open(nil, nonce, msg, nil) + if err != nil { + return "", err + } + plainstring = string(plaintext) + + return plainstring, nil +} diff --git a/exporter.go b/exporter.go new file mode 100644 index 0000000..8815baf --- /dev/null +++ b/exporter.go @@ -0,0 +1,198 @@ +package main + +import ( + "context" + "fmt" + "sync" + + "google.golang.org/protobuf/proto" + + "github.com/go-kit/log" + "github.com/go-kit/log/level" + "github.com/prometheus/client_golang/prometheus" + dto "github.com/prometheus/client_model/go" +) + +// Exporter is a prometheus.Gatherer that gathers SQL metrics from targets and merges them with the default registry. +type Exporter interface { + prometheus.Gatherer + + // WithContext returns a (single use) copy of the Exporter, which will use the provided context for Gather() calls. + WithContext(context.Context, Target) Exporter + // Config returns the Exporter's underlying Config object. + Config() *Config + Targets() []Target + Logger() log.Logger + FindTarget(string) (Target, error) + GetFirstTarget() (Target, error) +} + +type exporter struct { + config *Config + targets []Target + + cur_target Target + ctx context.Context + logger log.Logger +} + +// NewExporter returns a new Exporter with the provided config. +func NewExporter(configFile string, logger log.Logger, collectorName string) (Exporter, error) { + c, err := Load(configFile, logger, collectorName) + if err != nil { + return nil, err + } + + var targets []Target + var logContext []interface{} + if len(c.Targets) > 1 { + targets = make([]Target, 0, len(c.Targets)*3) + } + for _, t := range c.Targets { + if len(t.TargetsFiles) > 0 { + continue + } + target, err := NewTarget(logContext, t, t.Collectors(), nil, c.Globals, c.HttpAPIConfig, logger) + if err != nil { + return nil, err + } + if len(c.Targets) > 1 { + targets = append(targets, target) + } else { + targets = []Target{target} + } + } + + return &exporter{ + config: c, + targets: targets, + ctx: context.Background(), + logger: logger, + }, nil +} + +func (e *exporter) WithContext(ctx context.Context, t Target) Exporter { + return &exporter{ + config: e.config, + targets: e.targets, + cur_target: t, + ctx: ctx, + logger: e.logger, + } +} + +// Gather implements prometheus.Gatherer. +func (e *exporter) Gather() ([]*dto.MetricFamily, error) { + var ( + metricChan = make(chan Metric, capMetricChan) + errs prometheus.MultiError + ) + + var wg sync.WaitGroup + // wg.Add(len(e.targets)) + // for _, t := range e.targets { + // go func(target Target) { + // defer wg.Done() + // target.Collect(e.ctx, metricChan) + // }(t) + // } + // add only cur target + wg.Add(1) + go func(target Target) { + defer wg.Done() + target.Collect(e.ctx, metricChan) + }(e.cur_target) + + // Wait for all collectors to complete, then close the channel. + go func() { + wg.Wait() + close(metricChan) + }() + + // Drain metricChan in case of premature return. + defer func() { + for range metricChan { + } + }() + + level.Debug(e.logger).Log("msg", fmt.Sprintf("exporter.Gather(): **** Target launch is OVER :'%s' ****", e.cur_target.Name())) + // Gather. + dtoMetricFamilies := make(map[string]*dto.MetricFamily, 10) + // level.Debug(e.logger).Log("msg", "exporter.Gather(): just before for chan") + for metric := range metricChan { + // level.Debug(e.logger).Log("msg", "exporter.Gather(): in for chan") + dtoMetric := &dto.Metric{} + if err := metric.Write(dtoMetric); err != nil { + errs = append(errs, err) + continue + } + metricDesc := metric.Desc() + dtoMetricFamily, ok := dtoMetricFamilies[metricDesc.Name()] + if !ok { + dtoMetricFamily = &dto.MetricFamily{} + dtoMetricFamily.Name = proto.String(metricDesc.Name()) + dtoMetricFamily.Help = proto.String(metricDesc.Help()) + switch { + case dtoMetric.Gauge != nil: + dtoMetricFamily.Type = dto.MetricType_GAUGE.Enum() + case dtoMetric.Counter != nil: + dtoMetricFamily.Type = dto.MetricType_COUNTER.Enum() + default: + errs = append(errs, fmt.Errorf("don't know how to handle metric %v", dtoMetric)) + continue + } + dtoMetricFamilies[metricDesc.Name()] = dtoMetricFamily + } + dtoMetricFamily.Metric = append(dtoMetricFamily.Metric, dtoMetric) + } + level.Debug(e.logger).Log("msg", "exporter.Gather(): **** Target channel analysis is OVER ****") + + // No need to sort metric families, prometheus.Gatherers will do that for us when merging. + result := make([]*dto.MetricFamily, 0, len(dtoMetricFamilies)) + for _, mf := range dtoMetricFamilies { + result = append(result, mf) + } + return result, errs +} + +// Config implements Exporter. +func (e *exporter) Config() *Config { + return e.config +} + +// Targets implements Exporter. +func (e *exporter) Targets() []Target { + return e.targets +} + +// Logger implements Exporter. +func (e *exporter) Logger() log.Logger { + return e.logger +} + +// FindTarget implements Exporter. +func (e *exporter) FindTarget(tname string) (Target, error) { + var t_found Target + found := false + for _, t := range e.targets { + if tname == t.Name() { + t_found = t + found = true + } + } + if !found { + return t_found, fmt.Errorf("target '%s' not found", tname) + } + return t_found, nil +} + +// GetFirstTarget implements Exporter. +func (e *exporter) GetFirstTarget() (Target, error) { + var t_found Target + if len(e.targets) == 0 { + return t_found, fmt.Errorf("no target found") + } else { + t_found = e.targets[0] + } + return t_found, nil +} diff --git a/field.go b/field.go new file mode 100644 index 0000000..49e5769 --- /dev/null +++ b/field.go @@ -0,0 +1,340 @@ +package main + +import ( + "encoding/json" + "fmt" + "html" + + // "regexp" + + "strconv" + ttemplate "text/template" + + "strings" +) + +// var base60float = regexp.MustCompile(`^[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+(?:\.[0-9_]*)?$`) + +type exporterTemplate ttemplate.Template + +func (tmpl *exporterTemplate) MarshalText() (text []byte, err error) { + + return []byte(tmpl.Tree.Root.String()), nil +} + +type Field struct { + raw string + tmpl *exporterTemplate +} + +// create a new key or value Field that can be a GO template +func NewField(name string, customTemplate *exporterTemplate) (*Field, error) { + var tmpl *ttemplate.Template + var err error + + if strings.Contains(name, "{") { + if customTemplate != nil { + ptr := (*ttemplate.Template)(customTemplate) + tmpl, err = ptr.Clone() + if err != nil { + return nil, fmt.Errorf("field template clone for %s is invalid: %s", name, err) + } + } else { + // tmpl = template.New("field").Funcs(sprig.FuncMap()) + tmpl = ttemplate.New("field").Funcs(mymap()) + } + tmpl, err = tmpl.Parse(name) + if err != nil { + return nil, fmt.Errorf("field template %s is invalid: %s", name, err) + } + } + return &Field{ + raw: name, + tmpl: (*exporterTemplate)(tmpl), + }, nil +} + +// obtain float64 from a var of any type +func RawGetValueFloat(curval any) float64 { + var f_value float64 + var err error + if curval == nil { + return 0.0 + } + + // it is a raw value not a template look in "item" + switch curval := curval.(type) { + case int64: + f_value = float64(curval) + case float64: + f_value = curval + case string: + if f_value, err = strconv.ParseFloat(strings.Trim(curval, "\r\n "), 64); err != nil { + f_value = 0 + } + default: + f_value = 0 + // value := row[v].(float64) + } + return f_value +} + +// obtain string from a var of any type +func RawGetValueString(curval any) string { + res := "" + if curval == nil { + return res + } + + switch curval := curval.(type) { + case string: + res = curval + // case map[any]any: + // bytes_res, err := json.MarshalIndent(curval, "", "") + // if err != nil { + // fmt.Printf("error: %s\n", string(err.Error())) + // } + // res = string(bytes_res) + default: + res = fmt.Sprintf("%v", curval) + } + return strings.Trim(res, "\r\n ") +} + +// obtain a final string value from Field +// use template if one is defined using item to symbols table +// else +// check if value must be sustituted using provided sub map +// if check_item set to true, check if the resulting value exists in item symbols table +// else return raw value (simple string) +func (f *Field) GetValueString( + item map[string]interface{}, + sub map[string]string, + check_item bool) (string, error) { + if f == nil { + return "", nil + } + + if f.tmpl != nil { + tmp_res := new(strings.Builder) + err := ((*ttemplate.Template)(f.tmpl)).Execute(tmp_res, &item) + if err != nil { + return "", err + } + // obtain final string from builder + tmp := tmp_res.String() + // remove before and after blank chars + tmp = strings.Trim(tmp, "\r\n ") + // unescape string + return html.UnescapeString(tmp), nil + } else { + val := f.raw + // check if there is a transformation value in sub[stitution] map + if sub != nil { + if _, ok := sub[val]; ok { + val = sub[val] + } + } + if check_item { + if curval, ok := item[val]; ok { + return RawGetValueString(curval), nil + } + } + return RawGetValueString(val), nil + } +} + +// obtain a final float64 value from Field +// use template if one is defined using item to symbols table +// else if the resulting value exists in item symbols table return it +// else return raw value (simple float64 constant) +func (f *Field) GetValueFloat( + item map[string]interface{}) (float64, error) { + var str_value any + + if f == nil { + return 0, nil + } + + if f.tmpl != nil { + tmp_res := new(strings.Builder) + // tmpl, err := template.New("field").Funcs(sprig.FuncMap()).Parse("{{ mulf .result.totalMiB 1024 1024 }}") + // if err == nil { + // tmpl.Execute(tmp_res, &item) + // if err != nil { + // return 0, err + // } + // str_value = tmp_res.String() + // str_value = html.UnescapeString(str_value.(string)) + // fmt.Printf("tmp: %s\n", str_value) + // var data any + // err = json.Unmarshal([]byte(str_value.(string)), &data) + // if err == nil { + // res, err := json.MarshalIndent(data, "", " ") + // if err == nil { + // fmt.Printf("res: %s\n", string(res)) + // } + // } + // } + // tmp_res = new(strings.Builder) + err := ((*ttemplate.Template)(f.tmpl)).Execute(tmp_res, &item) + if err != nil { + return 0, err + } + str_value = html.UnescapeString(tmp_res.String()) + } else { + val := f.raw + // check if value exists in symbol table + if curval, ok := item[val]; ok { + str_value = curval + } else { + str_value = val + } + } + return RawGetValueFloat(str_value), nil +} + +func (f *Field) GetValueObject( + // item map[string]interface{}) ([]any, error) { + item any) (any, error) { + res_slice := make([]any, 0) + + if f == nil { + return res_slice, nil + } + + if f.tmpl != nil { + tmp_res := new(strings.Builder) + err := ((*ttemplate.Template)(f.tmpl)).Execute(tmp_res, &item) + if err != nil { + return res_slice, err + } + var data any + json_obj := tmp_res.String() + if json_obj != "" { + // json_obj = strings.ReplaceAll(json_obj, """, "\"") + json_obj = html.UnescapeString(json_obj) + // json_obj = strings.TrimSuffix(json_obj, "\n") + // fmt.Println(json_obj) + err = json.Unmarshal([]byte(json_obj), &data) + if err != nil { + // if _, ok := err.(*json.UnmarshalTypeError); ok { + + // } + return res_slice, err + } + } + return data, nil + } else { + datas := &ResultElement{ + raw: item, + } + if data, found := datas.GetSlice(f.raw); found { + return data, nil + } else { + return nil, nil + } + } +} + +func (f *Field) String() string { + if f == nil { + return "" + } + + if f.tmpl != nil { + // f.tmpl. + return f.tmpl.Tree.Root.String() + } else { + return f.raw + } +} + +func (f *Field) MarshalText() (text []byte, err error) { + + return []byte(f.String()), nil +} + +func (f *Field) AddDefaultTemplate(customTemplate *exporterTemplate) error { + if f.tmpl != nil && customTemplate != nil { + if tmpl, err := AddDefaultTemplate(f.tmpl, customTemplate); err != nil { + f.tmpl = tmpl + } else { + return err + } + } + return nil +} + +func AddDefaultTemplate(dest_tmpl *exporterTemplate, customTemplate *exporterTemplate) (*exporterTemplate, error) { + if dest_tmpl != nil && customTemplate != nil { + cc_tmpl, err := ((*ttemplate.Template)(customTemplate)).Clone() + if err != nil { + return nil, fmt.Errorf("field template clone for %s is invalid: %s", ((*ttemplate.Template)(customTemplate)).Name(), err) + } + for _, tmpl := range cc_tmpl.Templates() { + name := tmpl.Name() + if name == "default" { + continue + } + + _, err = ((*ttemplate.Template)(dest_tmpl)).AddParseTree(tmpl.Name(), tmpl.Tree) + if err != nil { + return nil, fmt.Errorf("field template %s is invalid: %s", tmpl.Name(), err) + } + } + } + return dest_tmpl, nil +} + +type ResultElement struct { + raw any +} + +func (r *ResultElement) GetSlice(field string) ([]any, bool) { + var myslice []any + var found bool + if r_type, ok := r.raw.(map[string]any); ok { + + if myvar, ok := r_type[field]; ok { + switch curval := myvar.(type) { + case []any: + myslice = curval + found = true + case map[string]any: + n_slice := make([]any, 1) + n_slice[0] = myvar + myslice = n_slice + found = true + + } + // myvart := reflect.ValueOf(myvar).Kind() + // if myvart == reflect.Slice { + // myslice = myvar.([]interface{}) + // // for i, myvar := range myslice { + // // myvart := reflect.ValueOf(myvar).Kind() + // // if myvart == reflect.Map { + // // mymap := make(map[string]interface{}) + // // for k, v := range myvar.(map[string]interface{}) { + // // fmt.Printf("k: %s, v %+v\n", k, v) + // // } + // // } + // // } + // } else if myvart == reflect.Map { + // n_slice := make([]interface{}, 1) + // n_slice[0] = myvar + // myslice = n_slice + // } + } + } + return myslice, found +} + +// func (r *ResultElement) GetMap(field string) map[string]interface{} { +// var mymap map[string]interface{} +// myvart := reflect.ValueOf(r.raw).Kind() +// if myvart != reflect.Map { +// mymap = nil +// } +// return mymap +// } diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..22a9774 --- /dev/null +++ b/go.mod @@ -0,0 +1,42 @@ +module github.com/peekjef72/httpapi_exporter + +go 1.18 + +require ( + github.com/go-kit/log v0.2.1 + github.com/mitchellh/copystructure v1.2.0 + github.com/prometheus/client_golang v1.16.0 + github.com/prometheus/client_model v0.4.0 + github.com/prometheus/common v0.44.0 + google.golang.org/protobuf v1.31.0 +) + +require ( + github.com/Masterminds/goutils v1.1.1 // indirect + github.com/Masterminds/semver/v3 v3.2.1 // indirect + github.com/google/uuid v1.3.1 // indirect + github.com/huandu/xstrings v1.4.0 // indirect + github.com/imdario/mergo v0.3.15 + github.com/mitchellh/reflectwalk v1.0.2 // indirect + github.com/shopspring/decimal v1.3.1 // indirect + github.com/spf13/cast v1.5.1 // indirect + github.com/xhit/go-str2duration/v2 v2.1.0 // indirect + golang.org/x/crypto v0.13.0 // indirect + golang.org/x/net v0.15.0 // indirect +) + +require ( + github.com/Masterminds/sprig/v3 v3.2.3 + github.com/alecthomas/kingpin/v2 v2.3.2 + github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/go-logfmt/logfmt v0.6.0 // indirect + github.com/go-resty/resty/v2 v2.8.0 + github.com/golang/protobuf v1.5.3 // indirect + github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect + github.com/prometheus/procfs v0.12.0 // indirect + golang.org/x/exp v0.0.0-20230905200255-921286631fa9 + golang.org/x/sys v0.12.0 // indirect + gopkg.in/yaml.v3 v3.0.1 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..cb8ef90 --- /dev/null +++ b/go.sum @@ -0,0 +1,165 @@ +dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= +dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= +github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= +github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= +github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= +github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0= +github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= +github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA= +github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM= +github.com/alecthomas/kingpin/v2 v2.3.2 h1:H0aULhgmSzN8xQ3nX1uxtdlTHYoPLu5AhHxWrKI6ocU= +github.com/alecthomas/kingpin/v2 v2.3.2/go.mod h1:0gyi0zQnjuFk8xrkNKamJoyUo382HRL7ATRpFZCw6tE= +github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 h1:s6gZFSlWYmbqAuRjVTiNNhvNRfY2Wxp9nhfyel4rklc= +github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= +github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= +github.com/go-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU= +github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= +github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4= +github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= +github.com/go-resty/resty/v2 v2.7.0 h1:me+K9p3uhSmXtrBZ4k9jcEAfJmuC8IivWHwaLZwPrFY= +github.com/go-resty/resty/v2 v2.7.0/go.mod h1:9PWDzw47qPphMRFfhsyk0NnSgvluHcljSMVIq3w7q0I= +github.com/go-resty/resty/v2 v2.8.0 h1:J29d0JFWwSWrDCysnOK/YjsPMLQTx0TvgJEHVGvf2L8= +github.com/go-resty/resty/v2 v2.8.0/go.mod h1:UCui0cMHekLrSntoMyofdSTaPpinlRHFtPpizuyDW2w= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= +github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU= +github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/imdario/mergo v0.3.11 h1:3tnifQM4i+fbajXKBHXWEH+KvNHqojZ778UH75j3bGA= +github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/imdario/mergo v0.3.15 h1:M8XP7IuFNsqUx6VPK2P9OSmsYsI/YFaGil0uD21V3dM= +github.com/imdario/mergo v0.3.15/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= +github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= +github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= +github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= +github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= +github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= +github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= +github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v1.15.1 h1:8tXpTmJbyH5lydzFPoxSIJ0J46jdh3tylbvM1xCv0LI= +github.com/prometheus/client_golang v1.15.1/go.mod h1:e9yaBhRPU2pPNsZwE+JdQl0KEt1N9XgF6zxWmaC0xOk= +github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8= +github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc= +github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY= +github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU= +github.com/prometheus/common v0.43.0 h1:iq+BVjvYLei5f27wiuNiB1DN6DYQkp1c8Bx0Vykh5us= +github.com/prometheus/common v0.43.0/go.mod h1:NCvr5cQIh3Y/gy73/RdVtC9r8xxrxwJnB+2lB3BxrFc= +github.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdOOfY= +github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY= +github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI= +github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY= +github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= +github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= +github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= +github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= +github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= +github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= +github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA= +github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= +github.com/xhit/go-str2duration/v2 v2.1.0 h1:lxklc02Drh6ynqX+DdPyp5pCKLUQpRT8bp8Ydu2Bstc= +github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= +golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= +golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= +golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck= +golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= +golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53 h1:5llv2sWeaMSnA3w2kS57ouQQ4pudlXrR0dCgw51QK9o= +golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w= +golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= +golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20211029224645-99673261e6eb/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8= +golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= +google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= +google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/httpapi_exporter.go b/httpapi_exporter.go new file mode 100644 index 0000000..ddf9bbc --- /dev/null +++ b/httpapi_exporter.go @@ -0,0 +1,137 @@ +package main + +import ( + "context" + "fmt" + "net/http" + "os" + "time" + + _ "net/http/pprof" + + kingpin "github.com/alecthomas/kingpin/v2" + "github.com/go-kit/log/level" + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promhttp" + "github.com/prometheus/common/expfmt" + "github.com/prometheus/common/promlog" + "github.com/prometheus/common/promlog/flag" + "github.com/prometheus/common/version" +) + +const ( + // Constant values + metricsPublishingPort = ":9321" + exporter_name = "httpapi_exporter" +) + +var ( + listenAddress = kingpin.Flag("web.listen-address", "The address to listen on for HTTP requests.").Default(metricsPublishingPort).String() + metricsPath = kingpin.Flag("web.telemetry-path", "Path under which to expose collector's internal metrics.").Default("/metrics").String() + configFile = kingpin.Flag("config.file", "Exporter configuration file.").Short('c').Default("config/config.yml").String() + //debug_flag = kingpin.Flag("debug", "debug connection checks.").Short('d').Default("false").Bool() + dry_run = kingpin.Flag("dry-run", "Only check exporter configuration file and exit.").Short('n').Default("false").Bool() + // alsologtostderr = kingpin.Flag("alsologtostderr", "log to standard error as well as files.").Default("true").Bool() + target_name = kingpin.Flag("target", "In dry-run mode specify the target name, else ignored.").Short('t').String() + collector_name = kingpin.Flag("metric", "Specify the collector name restriction to collect, replace the collector_names set for each target.").Short('m').String() +) + +func init() { + prometheus.MustRegister(version.NewCollector(exporter_name)) +} + +func main() { + + logConfig := promlog.Config{} + flag.AddFlags(kingpin.CommandLine, &logConfig) + kingpin.Version(version.Print(exporter_name)).VersionFlag.Short('V') + kingpin.HelpFlag.Short('h') + kingpin.Parse() + + logger := promlog.New(&logConfig) + level.Info(logger).Log("msg", fmt.Sprintf("Starting %s", exporter_name), "version", version.Info()) + level.Info(logger).Log("msg", "Build context", "build_context", version.BuildContext()) + + exporter, err := NewExporter(*configFile, logger, *collector_name) + if err != nil { + level.Error(logger).Log("msg", fmt.Sprintf("Error creating exporter: %s", err)) + os.Exit(1) + } + + if *dry_run { + level.Info(logger).Log("msg", "configuration OK.") + // get the target if defined + var t Target + var err error + if *target_name != "" { + t, err = exporter.FindTarget(*target_name) + } else { + t, err = exporter.GetFirstTarget() + } + if err != nil { + level.Error(logger).Log("errmsg", err) + os.Exit(1) + } + level.Info(logger).Log("msg", fmt.Sprintf("try to collect target %s.", t.Name())) + timeout := time.Duration(0) + configTimeout := time.Duration(exporter.Config().Globals.ScrapeTimeout) + + // If the configured scrape timeout is more restrictive, use that instead. + if configTimeout > 0 && (timeout <= 0 || configTimeout < timeout) { + timeout = configTimeout + } + var ctx context.Context + var cancel context.CancelFunc + if timeout <= 0 { + ctx = context.Background() + cancel = func() {} + } else { + ctx, cancel = context.WithTimeout(context.Background(), timeout) + } + defer cancel() + + gatherer := prometheus.Gatherers{exporter.WithContext(ctx, t)} + mfs, err := gatherer.Gather() + if err != nil { + level.Error(logger).Log("errmsg", fmt.Sprintf("Error gathering metrics: %v", err)) + if len(mfs) == 0 { + os.Exit(1) + } + } else { + level.Info(logger).Log("msg", "collect is OK. Dumping result to stdout.") + } + + //dump metric to stdout + enc := expfmt.NewEncoder(os.Stdout, expfmt.FmtText) + + for _, mf := range mfs { + err := enc.Encode(mf) + if err != nil { + level.Error(logger).Log("Errmsg", err) + break + } + } + if closer, ok := enc.(expfmt.Closer); ok { + // This in particular takes care of the final "# EOF\n" line for OpenMetrics. + closer.Close() + } + level.Info(logger).Log("msg", "dry-run is over. Exiting.") + os.Exit(0) + } + + // Setup and start webserver. + http.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) { http.Error(w, "OK", http.StatusOK) }) + http.HandleFunc("/", HomeHandlerFunc(*metricsPath, exporter)) + http.HandleFunc("/config", ConfigHandlerFunc(*metricsPath, exporter)) + http.HandleFunc("/status", StatusHandlerFunc(*metricsPath, exporter)) + http.HandleFunc("/targets", TargetsHandlerFunc(*metricsPath, exporter)) + http.Handle(*metricsPath, ExporterHandlerFor(exporter)) + // Expose exporter metrics separately, for debugging purposes. + http.Handle("/httpapi_exporter_metrics", promhttp.Handler()) + + level.Info(logger).Log("msg", "Listening on address", "address", *listenAddress) + if err := http.ListenAndServe(*listenAddress, nil); err != nil { + level.Error(logger).Log("msg", "Error starting HTTP server", "errmsg", err) + os.Exit(1) + } +} diff --git a/metric.go b/metric.go new file mode 100644 index 0000000..f201648 --- /dev/null +++ b/metric.go @@ -0,0 +1,490 @@ +package main + +import ( + "fmt" + "sort" + + "github.com/go-kit/log" + "github.com/go-kit/log/level" + "github.com/prometheus/client_golang/prometheus" + dto "github.com/prometheus/client_model/go" + "google.golang.org/protobuf/proto" +) + +// MetricDesc is a descriptor for a family of metrics, sharing the same name, help, labes, type. +type MetricDesc interface { + Name() string + Help() string + ValueType() prometheus.ValueType + ConstLabels() []*dto.LabelPair + // Labels() []string + LogContext() []interface{} +} + +// +// MetricFamily +// + +// MetricFamily implements MetricDesc for SQL metrics, with logic for populating its labels and values from sql.Rows. +type MetricFamily struct { + config *MetricConfig + constLabels []*dto.LabelPair + // labels []string + labels []*Label // raw string or template + valueslabels []*Label // raw string or template for key and value + // resultsFields []*Field // raw string or template too + logContext []interface{} +} + +// NewMetricFamily creates a new MetricFamily with the given metric config and const labels (e.g. job and instance). +func NewMetricFamily( + logContext []interface{}, + mc *MetricConfig, + constLabels []*dto.LabelPair, + customTemplate *exporterTemplate) (*MetricFamily, error) { + + var ( + err error + labels []*Label + ) + + logContext = append(logContext, "metric", mc.Name) + + if len(mc.Values) == 0 { + logContext = append(logContext, "errmsg", "NewMetricFamily(): multiple values but no value label") + return nil, fmt.Errorf("%s", logContext...) + } + if len(mc.Values) > 1 && len(mc.ValueLabel) == 0 { + logContext = append(logContext, "errmsg", "NewMetricFamily(): multiple values but no value label") + } + + // labels := make(map[Label]*Label, len(mc.KeyLabels)+1) + // valueslabels := make(map[Label]*Label, len(mc.Values)+1) + + // all labels are stored in variable 'labels': size of slice if size of KeyLabels + 1 if size of Values is greater than 1 + if len(mc.key_labels_map) > 0 { + label_len := len(mc.key_labels_map) + if len(mc.Values) > 1 { + label_len++ + } + labels = make([]*Label, label_len) + + // labels = append(labels, mc.KeyLabels...) + + i := 0 + for key, val := range mc.key_labels_map { + labels[i], err = NewLabel(key, val, mc.Name, "key_label", customTemplate) + if err != nil { + return nil, err + } + i++ + } + // add an element in labels for value_label (the name of the label); value will be set later + if len(mc.Values) > 1 { + labels[i], err = NewLabel(mc.ValueLabel, "", mc.Name, "value_label", customTemplate) + if err != nil { + return nil, err + } + } + } + // values are stored in variable 'valueslabels': it is meanfull only when values are greater than 1. + // original config struct is a map of [value label] : [value template]; + valueslabels := make([]*Label, len(mc.Values)) + + i := 0 + for key, val := range mc.Values { + valueslabels[i], err = NewLabel(key, val, mc.Name, "'values'", customTemplate) + if err != nil { + return nil, err + } + i++ + // valueslabels[*keylabel] = valuelabel + } + + // Create a copy of original slice to avoid modifying constLabels + sortedLabels := append(constLabels[:0:0], constLabels...) + + for k, v := range mc.StaticLabels { + sortedLabels = append(sortedLabels, &dto.LabelPair{ + Name: proto.String(k), + Value: proto.String(v), + }) + } + sort.Sort(labelPairSorter(sortedLabels)) + + // results := make([]*Field, len(mc.ResultFields)) + // for i, res_field := range mc.ResultFields { + // res, err := NewField(res_field, customTemplate) + // if err != nil { + // return nil, fmt.Errorf("NewMetricFamily(): result field invalid metric{name:'%s', result: '%s'}: %s", mc.Name, res_field, err) + // } + // results[i] = res + // } + + return &MetricFamily{ + config: mc, + constLabels: sortedLabels, + labels: labels, + valueslabels: valueslabels, + // resultsFields: results, + logContext: logContext, + }, nil +} + +// Collect is the equivalent of prometheus.Collector.Collect() but takes a Query output map to populate values from. +func (mf MetricFamily) Collect(rawdatas any, logger log.Logger, ch chan<- Metric) { + var set_root bool = false + // reset logcontxt for MetricFamily: remove previous errors if any + mf.logContext = make([]any, 2) + mf.logContext[0] = "metric" + mf.logContext[1] = mf.config.Name + + item, ok := rawdatas.(map[string]any) + if !ok { + ch <- NewInvalidMetric(mf.logContext, fmt.Errorf("metrtic %s invalid type", mf.config.Name)) + } + // set default scope to "loop_var" entry: item[item["loop_var"]] + if mf.config.Scope == "" { + if loop_var, ok := item["loop_var"].(string); ok { + if loop_var == "empty_item" { + loop_var = "item" + } + if datas, ok := item[loop_var].(map[string]any); ok { + item = datas + item["root"] = rawdatas + set_root = true + } + } + } else if mf.config.Scope != "none" { + var err error + item, err = SetScope(mf.config.Scope, item) + if err != nil { + ch <- NewInvalidMetric(mf.logContext, err) + } + item["root"] = rawdatas + set_root = true + } + + // build the labels family with the content of the var(*Field) + if len(mf.labels) == 0 && mf.config.key_labels != nil { + if labelsmap_raw, err := ValorizeValue(item, mf.config.key_labels, logger, 0); err == nil { + if key_labels_map, ok := labelsmap_raw.(map[string]any); ok { + label_len := len(key_labels_map) + if len(mf.config.Values) > 1 { + label_len++ + } + mf.labels = make([]*Label, label_len) + + i := 0 + for key, val_raw := range key_labels_map { + if val, ok := val_raw.(string); ok { + mf.labels[i], err = NewLabel(key, val, mf.config.Name, "key_label", nil) + if err != nil { + level.Warn(logger).Log("errmsg", fmt.Sprintf("invalid template for key_values for metric %s: %s (maybe use |toRawJson.)", mf.config.Name, err)) + continue + } + } + i++ + } + // add an element in labels for value_label (the name of the label); value will be set later + if len(mf.config.Values) > 1 { + mf.labels[i], _ = NewLabel(mf.config.ValueLabel, "", mf.config.Name, "value_label", nil) + if err != nil { + level.Warn(logger).Log("errmsg", fmt.Sprintf("invalid templatefor value_label for metric %s: %s (maybe use |toRawJson.)", mf.config.Name, err)) + // return nil, err + } + } + } + } else { + level.Warn(logger).Log("errmsg", fmt.Sprintf("invalid template for key_values for metric %s: %s (maybe use |toRawJson.)", mf.config.Name, err)) + } + } + + labelNames := make([]string, len(mf.labels)) + labelValues := make([]string, len(mf.labels)) + + for i, label := range mf.labels { + var err error + // last label may be null if metric has no value label, because it has only one value + if label == nil { + continue + } + if item != nil { + if label.Key != nil { + labelNames[i], err = label.Key.GetValueString(item, nil, false) + if err != nil { + ch <- NewInvalidMetric(mf.logContext, fmt.Errorf("invalid template label name metric{ name: %s, key: %s} : %s", mf.config.Name, label.Key.tmpl.Tree.Root.String(), err)) + } + } + + if label.Value != nil { + sub := make(map[string]string) + sub["_"] = labelNames[i] + labelValues[i], err = label.Value.GetValueString(item, sub, true) + if err != nil { + ch <- NewInvalidMetric(mf.logContext, fmt.Errorf("invalid template label value metric{ name: %s, value: %s} : %s", mf.config.Name, label.Value.tmpl.Tree.Root.String(), err)) + continue + } + } + } else { + labelNames[i] = fmt.Sprintf("undef_%d", i) + labelValues[i] = "" + } + i++ + } + + for _, label := range mf.valueslabels { + var f_value float64 + var err error + + // fill the label value for value_label with the current name of the value if there is a label ! + if len(mf.config.Values) > 1 { + labelValues[len(labelValues)-1], err = label.Key.GetValueString(item, nil, false) + if err != nil { + ch <- NewInvalidMetric(mf.logContext, fmt.Errorf("invalid template label name metric{ name: %s, key: %s} : %s", mf.config.Name, label.Key.tmpl.Tree.Root.String(), err)) + continue + } + } + f_value, err = label.Value.GetValueFloat(item) + if err != nil { + ch <- NewInvalidMetric(mf.logContext, fmt.Errorf("invalid template label value metric{ name: %s, value: %s} : %s", mf.config.Name, label.Value.tmpl.Tree.Root.String(), err)) + } + + ch <- NewMetric(&mf, f_value, labelNames, labelValues) + } + if set_root { + delete(item, "root") + } +} + +// Name implements MetricDesc. +func (mf MetricFamily) Name() string { + return mf.config.Name +} + +// Help implements MetricDesc. +func (mf MetricFamily) Help() string { + return mf.config.Help +} + +// ValueType implements MetricDesc. +func (mf MetricFamily) ValueType() prometheus.ValueType { + return mf.config.ValueType() +} + +// ConstLabels implements MetricDesc. +func (mf MetricFamily) ConstLabels() []*dto.LabelPair { + return mf.constLabels +} + +// Labels implements MetricDesc. +// func (mf MetricFamily) Labels() []string { +// return mf.labels +// } + +// LogContext implements MetricDesc. +func (mf MetricFamily) LogContext() []interface{} { + return mf.logContext +} + +// +// automaticMetricDesc +// + +// automaticMetric is a MetricDesc for automatically generated metrics (e.g. `up` and `scrape_duration`). +type automaticMetricDesc struct { + name string + help string + valueType prometheus.ValueType + labels []string + constLabels []*dto.LabelPair + logContext []interface{} +} + +// NewAutomaticMetricDesc creates a MetricDesc for automatically generated metrics. +func NewAutomaticMetricDesc( + logContext []interface{}, name, help string, valueType prometheus.ValueType, constLabels []*dto.LabelPair, labels ...string) MetricDesc { + return &automaticMetricDesc{ + name: name, + help: help, + valueType: valueType, + constLabels: constLabels, + labels: labels, + logContext: logContext, + } +} + +// Name implements MetricDesc. +func (a automaticMetricDesc) Name() string { + return a.name +} + +// Help implements MetricDesc. +func (a automaticMetricDesc) Help() string { + return a.help +} + +// ValueType implements MetricDesc. +func (a automaticMetricDesc) ValueType() prometheus.ValueType { + return a.valueType +} + +// ConstLabels implements MetricDesc. +func (a automaticMetricDesc) ConstLabels() []*dto.LabelPair { + return a.constLabels +} + +// Labels implements MetricDesc. +func (a automaticMetricDesc) Labels() []string { + return a.labels +} + +// LogContext implements MetricDesc. +func (a automaticMetricDesc) LogContext() []interface{} { + return a.logContext +} + +// +// Metric +// + +// A Metric models a single sample value with its meta data being exported to Prometheus. +type Metric interface { + Desc() MetricDesc + Write(out *dto.Metric) error +} + +// NewMetric returns a metric with one fixed value that cannot be changed. +// +// NewMetric panics if the length of labelValues is not consistent with desc.labels(). +func NewMetric(desc MetricDesc, value float64, labelNames []string, labelValues []string) Metric { + if len(labelNames) != len(labelValues) { + panic(fmt.Sprintf("[%s] expected %d labels, got %d", desc.LogContext(), len(labelNames), len(labelValues))) + } + return &constMetric{ + desc: desc, + val: value, + labelPairs: makeLabelPairs(desc, labelNames, labelValues), + } + // fmt.Printf("met: %+v\n", tmp) + // fmt.Printf("\tmet: %s k[%d]:%v v[%d]: %v\n", tmp.desc.Name(), len(labelNames), labelNames, len(labelValues), labelValues) + // return tmp +} + +// constMetric is a metric with one fixed value that cannot be changed. +type constMetric struct { + desc MetricDesc + val float64 + labelPairs []*dto.LabelPair +} + +// Desc implements Metric. +func (m *constMetric) Desc() MetricDesc { + return m.desc +} + +// Write implements Metric. +func (m *constMetric) Write(out *dto.Metric) error { + out.Label = m.labelPairs + switch t := m.desc.ValueType(); t { + case prometheus.CounterValue: + out.Counter = &dto.Counter{Value: proto.Float64(m.val)} + case prometheus.GaugeValue: + out.Gauge = &dto.Gauge{Value: proto.Float64(m.val)} + default: + var logContext []interface{} + logContext = append(logContext, m.desc.LogContext()...) + logContext = append(logContext, "errmsg", fmt.Sprintf("encountered unknown type %v", t)) + return fmt.Errorf("%s", logContext...) + } + return nil +} + +func makeLabelPairs(desc MetricDesc, labelNames []string, labelValues []string) []*dto.LabelPair { + labels := labelNames + constLabels := desc.ConstLabels() + + totalLen := len(labels) + len(constLabels) + if totalLen == 0 { + // Super fast path. + return nil + } + if len(labels) == 0 { + // Moderately fast path. + return constLabels + } + labelPairs := make([]*dto.LabelPair, 0, totalLen) + for i, label := range labels { + labelPairs = append(labelPairs, &dto.LabelPair{ + Name: proto.String(label), + Value: proto.String(labelValues[i]), + }) + } + labelPairs = append(labelPairs, constLabels...) + sort.Sort(labelPairSorter(labelPairs)) + return labelPairs +} + +// labelPairSorter implements sort.Interface. +// It provides a sortable version of a slice of dto.LabelPair pointers. + +type labelPairSorter []*dto.LabelPair + +func (s labelPairSorter) Len() int { + return len(s) +} + +func (s labelPairSorter) Swap(i, j int) { + s[i], s[j] = s[j], s[i] +} + +func (s labelPairSorter) Less(i, j int) bool { + return s[i].GetName() < s[j].GetName() +} + +type invalidMetric struct { + logContext []interface{} + err error +} + +// NewInvalidMetric returns a metric whose Write method always returns the provided error. +func NewInvalidMetric(logContext []interface{}, err error) Metric { + return invalidMetric{ + logContext: logContext, + err: err, + } +} + +func (m invalidMetric) Desc() MetricDesc { return nil } + +func (m invalidMetric) Write(*dto.Metric) error { + return m.err +} + +type Label struct { + Key *Field + Value *Field +} + +func NewLabel(key string, value string, mName string, errStr string, customTemplate *exporterTemplate) (*Label, error) { + var ( + keyField, valueField *Field + err error + ) + + keyField, err = NewField(key, customTemplate) + if err != nil { + return nil, fmt.Errorf("NewMetricFamily(): name of %s for metric %q: %s", errStr, mName, err) + } + if value != "" { + valueField, err = NewField(value, customTemplate) + if err != nil { + return nil, fmt.Errorf("NewMetricFamily(): value of %s for metric %q: %s", errStr, mName, err) + } + } + + return &Label{ + Key: keyField, + Value: valueField, + }, nil +} diff --git a/metric_action.go b/metric_action.go new file mode 100644 index 0000000..8c31d44 --- /dev/null +++ b/metric_action.go @@ -0,0 +1,164 @@ +package main + +import ( + //"bytes" + "fmt" + + "github.com/go-kit/log" + "github.com/go-kit/log/level" +) + +// *************************************************************************************** +// *************************************************************************************** +// metric fact +// *************************************************************************************** +// *************************************************************************************** + +type MetricAction struct { + Name *Field `yaml:"name,omitempty"` + With []any `yaml:"with,omitempty"` + When []*exporterTemplate `yaml:"when,omitempty"` + LoopVar string `yaml:"loop_var,omitempty"` + Vars map[string]any `yaml:"vars,omitempty"` + Until []*exporterTemplate `yaml:"until,omitempty"` + + mc *MetricConfig + + metricFamily *MetricFamily + + // Catches all undefined fields and must be empty after parsing. + XXX map[string]interface{} `yaml:",inline" json:"-"` +} + +func (a *MetricAction) Type() int { + return metric_action +} + +func (a *MetricAction) GetName(symtab map[string]any, logger log.Logger) string { + str, err := a.Name.GetValueString(symtab, nil, false) + if err != nil { + level.Warn(logger).Log("msg", fmt.Sprintf("invalid action name: %v", err)) + return "" + } + return str +} + +func (a *MetricAction) GetNameField() *Field { + return a.Name +} +func (a *MetricAction) SetNameField(name *Field) { + a.Name = name +} + +func (a *MetricAction) GetWidh() []any { + return a.With +} +func (a *MetricAction) SetWidth(with []any) { + a.With = with +} + +func (a *MetricAction) GetWhen() []*exporterTemplate { + return a.When + +} +func (a *MetricAction) SetWhen(when []*exporterTemplate) { + a.When = when +} + +func (a *MetricAction) GetLoopVar() string { + return a.LoopVar +} +func (a *MetricAction) SetLoopVar(loopvar string) { + a.LoopVar = loopvar +} + +func (a *MetricAction) GetVars() map[string]any { + return a.Vars +} +func (a *MetricAction) SetVars(vars map[string]any) { + a.Vars = vars +} + +func (a *MetricAction) GetUntil() []*exporterTemplate { + return a.Until +} +func (a *MetricAction) SetUntil(until []*exporterTemplate) { + a.Until = until +} + +func (a *MetricAction) setBasicElement( + nameField *Field, + vars map[string]any, + with []any, + loopVar string, + when []*exporterTemplate, + until []*exporterTemplate) error { + return setBasicElement(a, nameField, vars, with, loopVar, when, until) +} + +func (a *MetricAction) PlayAction(script *YAMLScript, symtab map[string]any, logger log.Logger) error { + return PlayBaseAction(script, symtab, logger, a, a.CustomAction) +} + +// only for MetricsAction +func (a *MetricAction) GetMetrics() []*GetMetricsRes { + return nil +} + +// only for MetricAction +func (a *MetricAction) GetMetric() *MetricConfig { + return a.mc +} + +func (a *MetricAction) SetMetricFamily(mf *MetricFamily) { + a.metricFamily = mf +} + +// only for PlayAction +func (a *MetricAction) SetPlayAction(scripts map[string]*YAMLScript) error { + return nil +} + +// specific behavior for the MetricAction + +func (a *MetricAction) CustomAction(script *YAMLScript, symtab map[string]any, logger log.Logger) error { + var ( + metric_channel chan<- Metric + // mfs []*MetricFamily + ) + loop_var_idx := "" + if raw_loop_var_idx, ok := symtab["loop_var_idx"].(int); ok { + if raw_loop_var_idx > 0 { + loop_var_idx = fmt.Sprintf(" %d", raw_loop_var_idx) + } + } + level.Debug(logger).Log( + "script", ScriptName(symtab, logger), + "msg", fmt.Sprintf("[Type: MetricAction] Name: %s%s", a.GetName(symtab, logger), loop_var_idx)) + + if r_val, ok := symtab["__channel"]; ok { + if metric_channel, ok = r_val.(chan<- Metric); !ok { + panic("invalid context (channel)") + } + } else { + panic("invalid context (channel)") + } + + // for _, mf := range mfs { + level.Debug(logger).Log( + "script", ScriptName(symtab, logger), + "msg", fmt.Sprintf(" metric_name: %s", a.metricFamily.Name())) + a.metricFamily.Collect(symtab, logger, metric_channel) + // } + + return nil +} + +func (a *MetricAction) AddCustomTemplate(customTemplate *exporterTemplate) error { + + if err := AddCustomTemplate(a, customTemplate); err != nil { + return err + } + + return nil +} diff --git a/metrics_action.go b/metrics_action.go new file mode 100644 index 0000000..1b14272 --- /dev/null +++ b/metrics_action.go @@ -0,0 +1,230 @@ +package main + +import ( + //"bytes" + "fmt" + "strings" + + "github.com/go-kit/log" + "github.com/go-kit/log/level" +) + +// *************************************************************************************** +// *************************************************************************************** +// metrics action (block / loop ) +// *************************************************************************************** +// *************************************************************************************** + +type MetricsAction struct { + // BaseAction + Name *Field `yaml:"name,omitempty"` + With []any `yaml:"with,omitempty"` + When []*exporterTemplate `yaml:"when,omitempty"` + LoopVar string `yaml:"loop_var,omitempty"` + Vars map[string]any `yaml:"vars,omitempty"` + Until []*exporterTemplate `yaml:"until,omitempty"` + + Metrics []*MetricConfig `yaml:"metrics"` // metrics defined by this collector + Scope string `yaml:"scope,omitempty"` // var path where to collect data: shortcut for {{ .scope.path.var }} + MetricPrefix string `yaml:"metric_prefix,omitempty"` // var to alert metric name + + // Catches all undefined fields and must be empty after parsing. + XXX map[string]interface{} `yaml:",inline" json:"-"` + Actions []Action `yaml:"-"` +} + +func (a *MetricsAction) Type() int { + return metrics_action +} + +func (a *MetricsAction) GetName(symtab map[string]any, logger log.Logger) string { + str, err := a.Name.GetValueString(symtab, nil, false) + if err != nil { + level.Warn(logger).Log("msg", fmt.Sprintf("invalid action name: %v", err)) + return "" + } + return str +} + +func (a *MetricsAction) GetNameField() *Field { + return a.Name +} +func (a *MetricsAction) SetNameField(name *Field) { + a.Name = name +} + +func (a *MetricsAction) GetWidh() []any { + return a.With +} +func (a *MetricsAction) SetWidth(with []any) { + a.With = with +} + +func (a *MetricsAction) GetWhen() []*exporterTemplate { + return a.When + +} +func (a *MetricsAction) SetWhen(when []*exporterTemplate) { + a.When = when +} + +func (a *MetricsAction) GetLoopVar() string { + return a.LoopVar +} +func (a *MetricsAction) SetLoopVar(loopvar string) { + a.LoopVar = loopvar +} + +func (a *MetricsAction) GetVars() map[string]any { + return a.Vars +} +func (a *MetricsAction) SetVars(vars map[string]any) { + a.Vars = vars +} + +func (a *MetricsAction) GetUntil() []*exporterTemplate { + return a.Until +} +func (a *MetricsAction) SetUntil(until []*exporterTemplate) { + a.Until = until +} + +func (a *MetricsAction) setBasicElement( + nameField *Field, + vars map[string]any, + with []any, + loopVar string, + when []*exporterTemplate, + until []*exporterTemplate) error { + return setBasicElement(a, nameField, vars, with, loopVar, when, until) +} + +func (a *MetricsAction) PlayAction(script *YAMLScript, symtab map[string]any, logger log.Logger) error { + return PlayBaseAction(script, symtab, logger, a, a.CustomAction) +} + +// only for MetricsAction +func (a *MetricsAction) GetMetrics() []*GetMetricsRes { + res := make([]*GetMetricsRes, 1) + res[0] = &GetMetricsRes{ + mc: a.Metrics, + maprefix: a.MetricPrefix, + } + return res +} + +// only for MetricAction +func (a *MetricsAction) GetMetric() *MetricConfig { + return nil +} +func (a *MetricsAction) SetMetricFamily(*MetricFamily) { +} + +// only for PlayAction +func (a *MetricsAction) SetPlayAction(scripts map[string]*YAMLScript) error { + return nil +} + +// *************************************************************************************** +// specific behavior for the MetricsAction +func SetScope(scope string, symtab map[string]any) (map[string]any, error) { + var err error + + tmp_symtab := symtab + // split the scope string into parts: attr1.attr[0].attr + if scope[0] == '.' { + scope = scope[1:] + } + vars := strings.Split(scope, ".") + // last_elmt := len(vars) -1 + for _, var_name := range vars { + if raw_value, ok := tmp_symtab[var_name]; ok { + switch cur_value := raw_value.(type) { + case map[string]any: + tmp_symtab = cur_value + default: + err = fmt.Errorf("can't set scope: '%s' has invalid type", var_name) + } + // } + } else { + err = fmt.Errorf("can't set scope: '%s' not found", var_name) + } + } + return tmp_symtab, err +} + +// *************************************************************************************** +func (a *MetricsAction) CustomAction(script *YAMLScript, symtab map[string]any, logger log.Logger) error { + + var ( + metric_channel chan<- Metric + // mfs []*MetricFamily + ) + // var logContext []any + + level.Debug(logger).Log( + "script", ScriptName(symtab, logger), + "msg", fmt.Sprintf("[Type: MetricsAction] Name: %s - %d metrics_name to set", a.GetName(symtab, logger), len(a.Metrics))) + + query_status, ok := GetMapValueBool(symtab, "query_status") + if !ok || (ok && !query_status) { + level.Debug(logger).Log( + "script", ScriptName(symtab, logger), + "msg", fmt.Sprintf("[Type: MetricsAction] Name: %s - previous query has invalid status skipping", a.GetName(symtab, logger))) + return nil + } + + if r_val, ok := symtab["__channel"]; ok { + if metric_channel, ok = r_val.(chan<- Metric); !ok { + panic("invalid context (channel)") + } + } else { + panic("invalid context (channel)") + } + + // check if user has specified a + tmp_symtab := symtab + // check if user has specified a scope for result : change the symtab access to that scope + if a.Scope != "" && a.Scope != "none" { + var err error + tmp_symtab, err = SetScope(a.Scope, tmp_symtab) + if err != nil { + level.Warn(logger).Log("errmsg", err) + } + + tmp_symtab["__name__"] = symtab["__name__"] + } + for _, cur_act := range a.Actions { + tmp_symtab["__channel"] = metric_channel + // tmp_symtab["__metricfamilies"] = mfs + // fmt.Printf("\tadd to symbols table: %s = %v\n", key, val) + if err := PlayBaseAction(script, tmp_symtab, logger, cur_act, cur_act.CustomAction); err != nil { + return err + } + + } + if a.Scope != "" && a.Scope != "none" && tmp_symtab != nil { + delete(tmp_symtab, "__name__") + } + + return nil +} + +// *************************************************************************************** +func (a *MetricsAction) AddCustomTemplate(customTemplate *exporterTemplate) error { + + if err := AddCustomTemplate(a, customTemplate); err != nil { + return err + } + + for _, cur_act := range a.Actions { + err := cur_act.AddCustomTemplate(customTemplate) + if err != nil { + return err + } + } + + return nil +} + +// *************************************************************************************** diff --git a/play_script_action.go b/play_script_action.go new file mode 100644 index 0000000..dad11f4 --- /dev/null +++ b/play_script_action.go @@ -0,0 +1,141 @@ +package main + +import ( + //"bytes" + "fmt" + + "github.com/go-kit/log" + "github.com/go-kit/log/level" +) + +// *************************************************************************************** +// *************************************************************************************** +// play_script_fact +// *************************************************************************************** +// *************************************************************************************** + +type PlayScriptAction struct { + Name *Field `yaml:"name,omitempty"` + With []any `yaml:"with,omitempty"` + When []*exporterTemplate `yaml:"when,omitempty"` + LoopVar string `yaml:"loop_var,omitempty"` + Vars map[string]any `yaml:"vars,omitempty"` + Until []*exporterTemplate `yaml:"until,omitempty"` + PlayScriptActionName string `yaml:"play_script"` + + playScriptAction *YAMLScript + + // Catches all undefined fields and must be empty after parsing. + XXX map[string]interface{} `yaml:",inline" json:"-"` +} + +func (a *PlayScriptAction) Type() int { + return play_script_action +} + +func (a *PlayScriptAction) GetName(symtab map[string]any, logger log.Logger) string { + str, err := a.Name.GetValueString(symtab, nil, false) + if err != nil { + level.Warn(logger).Log("msg", fmt.Sprintf("invalid action name: %v", err)) + return "" + } + return str +} + +func (a *PlayScriptAction) GetNameField() *Field { + return a.Name +} +func (a *PlayScriptAction) SetNameField(name *Field) { + a.Name = name +} + +func (a *PlayScriptAction) GetWidh() []any { + return a.With +} +func (a *PlayScriptAction) SetWidth(with []any) { + a.With = with +} + +func (a *PlayScriptAction) GetWhen() []*exporterTemplate { + return a.When + +} +func (a *PlayScriptAction) SetWhen(when []*exporterTemplate) { + a.When = when +} + +func (a *PlayScriptAction) GetLoopVar() string { + return a.LoopVar +} +func (a *PlayScriptAction) SetLoopVar(loopvar string) { + a.LoopVar = loopvar +} + +func (a *PlayScriptAction) GetVars() map[string]any { + return a.Vars +} +func (a *PlayScriptAction) SetVars(vars map[string]any) { + a.Vars = vars +} + +func (a *PlayScriptAction) GetUntil() []*exporterTemplate { + return a.Until +} +func (a *PlayScriptAction) SetUntil(until []*exporterTemplate) { + a.Until = until +} + +func (a *PlayScriptAction) setBasicElement( + nameField *Field, + vars map[string]any, + with []any, + loopVar string, + when []*exporterTemplate, + until []*exporterTemplate) error { + return setBasicElement(a, nameField, vars, with, loopVar, when, until) +} + +func (a *PlayScriptAction) PlayAction(script *YAMLScript, symtab map[string]any, logger log.Logger) error { + return PlayBaseAction(script, symtab, logger, a, a.CustomAction) +} + +// only for MetricsAction +func (a *PlayScriptAction) GetMetrics() []*GetMetricsRes { + return nil +} + +// only for MetricAction +func (a *PlayScriptAction) GetMetric() *MetricConfig { + return nil +} +func (a *PlayScriptAction) SetMetricFamily(*MetricFamily) { +} + +// only for PlayAction +func (a *PlayScriptAction) SetPlayAction(scripts map[string]*YAMLScript) error { + if script, ok := scripts[a.PlayScriptActionName]; ok && script != nil { + a.playScriptAction = script + return nil + } + return fmt.Errorf("scriptname not found in play_script action %s", a.PlayScriptActionName) +} + +// specific behavior for the PlayScriptAction +func (a *PlayScriptAction) CustomAction(script *YAMLScript, symtab map[string]any, logger log.Logger) error { + // var err error + level.Debug(logger).Log( + "script", ScriptName(symtab, logger), + "msg", fmt.Sprintf("[Type: PlayScriptAction] Name: %s", a.GetName(symtab, logger))) + + return a.playScriptAction.Play(symtab, false, logger) +} + +func (a *PlayScriptAction) AddCustomTemplate(customTemplate *exporterTemplate) error { + + if err := AddCustomTemplate(a, customTemplate); err != nil { + return err + } + return a.playScriptAction.AddCustomTemplate(customTemplate) +} + +// *************************************************************************************** diff --git a/promhttp.go b/promhttp.go new file mode 100644 index 0000000..46521bb --- /dev/null +++ b/promhttp.go @@ -0,0 +1,154 @@ +package main + +import ( + "bytes" + "compress/gzip" + "context" + "fmt" + "io" + "net/http" + "strconv" + "strings" + "sync" + "time" + + "github.com/go-kit/log/level" + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/common/expfmt" +) + +const ( + contentTypeHeader = "Content-Type" + contentLengthHeader = "Content-Length" + contentEncodingHeader = "Content-Encoding" + acceptEncodingHeader = "Accept-Encoding" +) + +// ExporterHandlerFor returns an http.Handler for the provided Exporter. +func ExporterHandlerFor(exporter Exporter) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + ctx, cancel := contextFor(req, exporter) + defer cancel() + + params := req.URL.Query() + tname := params.Get("target") + if tname == "" { + err := fmt.Errorf("Target parameter is missing") + HandleError(http.StatusBadRequest, err, *metricsPath, exporter, w, req) + return + } + + t, err := exporter.FindTarget(tname) + if err != nil { + HandleError(http.StatusNotFound, err, *metricsPath, exporter, w, req) + return + } + + auth_key := params.Get("auth_key") + if auth_key != "" { + t.SetSymbol("auth_key", auth_key) + } + + // Go through prometheus.Gatherers to sanitize and sort metrics. + gatherer := prometheus.Gatherers{exporter.WithContext(ctx, t)} + mfs, err := gatherer.Gather() + if err != nil { + level.Error(exporter.Logger()).Log("msg", fmt.Sprintf("Error gathering metrics for '%s': %s", tname, err)) + if len(mfs) == 0 { + http.Error(w, "No metrics gathered, "+err.Error(), http.StatusInternalServerError) + return + } + } + + contentType := expfmt.Negotiate(req.Header) + buf := getBuf() + defer giveBuf(buf) + writer, encoding := decorateWriter(req, buf) + enc := expfmt.NewEncoder(writer, contentType) + var errs prometheus.MultiError + for _, mf := range mfs { + if err := enc.Encode(mf); err != nil { + errs = append(errs, err) + level.Info(exporter.Logger()).Log("msg", fmt.Sprintf("Error encoding metric family %q: %s", mf.GetName(), err)) + } + } + if closer, ok := writer.(io.Closer); ok { + closer.Close() + } + if errs.MaybeUnwrap() != nil && buf.Len() == 0 { + err = fmt.Errorf("no metrics encoded: %s, ", errs.Error()) + HandleError(http.StatusInternalServerError, err, *metricsPath, exporter, w, req) + return + } + header := w.Header() + header.Set(contentTypeHeader, string(contentType)) + header.Set(contentLengthHeader, fmt.Sprint(buf.Len())) + if encoding != "" { + header.Set(contentEncodingHeader, encoding) + } + w.Write(buf.Bytes()) + }) +} + +func contextFor(req *http.Request, exporter Exporter) (context.Context, context.CancelFunc) { + timeout := time.Duration(0) + configTimeout := time.Duration(exporter.Config().Globals.ScrapeTimeout) + // If a timeout is provided in the Prometheus header, use it. + if v := req.Header.Get("X-Prometheus-Scrape-Timeout-Seconds"); v != "" { + timeoutSeconds, err := strconv.ParseFloat(v, 64) + if err != nil { + level.Error(exporter.Logger()).Log("msg", fmt.Sprintf("Failed to parse timeout (`%s`) from Prometheus header: %s", v, err)) + } else { + timeout = time.Duration(timeoutSeconds * float64(time.Second)) + + // Subtract the timeout offset, unless the result would be negative or zero. + timeoutOffset := time.Duration(exporter.Config().Globals.TimeoutOffset) + if timeoutOffset > timeout { + level.Error(exporter.Logger()).Log("msg", fmt.Sprintf("global.scrape_timeout_offset (`%s`) is greater than Prometheus' scraping timeout (`%s`), ignoring", + timeoutOffset, timeout)) + } else { + timeout -= timeoutOffset + } + } + } + + // If the configured scrape timeout is more restrictive, use that instead. + if configTimeout > 0 && (timeout <= 0 || configTimeout < timeout) { + timeout = configTimeout + } + + if timeout <= 0 { + return context.Background(), func() {} + } + return context.WithTimeout(context.Background(), timeout) +} + +var bufPool sync.Pool + +func getBuf() *bytes.Buffer { + buf := bufPool.Get() + if buf == nil { + return &bytes.Buffer{} + } + return buf.(*bytes.Buffer) +} + +func giveBuf(buf *bytes.Buffer) { + buf.Reset() + bufPool.Put(buf) +} + +// decorateWriter wraps a writer to handle gzip compression if requested. It +// returns the decorated writer and the appropriate "Content-Encoding" header +// (which is empty if no compression is enabled). +func decorateWriter(request *http.Request, writer io.Writer) (w io.Writer, encoding string) { + header := request.Header.Get(acceptEncodingHeader) + parts := strings.Split(header, ",") + for _, part := range parts { + part := strings.TrimSpace(part) + if part == "gzip" || strings.HasPrefix(part, "gzip;") { + return gzip.NewWriter(writer), "gzip" + } + } + return writer, "" +} diff --git a/query_action.go b/query_action.go new file mode 100644 index 0000000..d4f2811 --- /dev/null +++ b/query_action.go @@ -0,0 +1,462 @@ +package main + +import ( + //"bytes" + "fmt" + "strconv" + "strings" + "time" + + "github.com/go-kit/log" + "github.com/go-kit/log/level" +) + +// *************************************************************************************** +// *************************************************************************************** +// query +// *************************************************************************************** +// *************************************************************************************** +type QueryActionConfig struct { + Query string `yaml:"url"` + Method string `yaml:"method,omitempty"` + Data string `yaml:"data,omitempty"` + Debug ConvertibleBoolean `yaml:"debug,omitempty"` + VarName string `yaml:"var_name,omitempty"` + OkStatus any `yaml:"ok_status,omitempty"` + AuthConfig *AuthConfig `yaml:"auth_mode,omitempty"` + Timeout int `yaml:"timeout,omitempty"` + + query *Field + method *Field + data *Field + var_name *Field + + auth_mode *Field + user *Field + passwd *Field + token *Field + + ok_status []int + + // Catches all undefined fields and must be empty after parsing. + XXX map[string]interface{} `yaml:",inline" json:"-"` +} + +// UnmarshalYAML implements the yaml.Unmarshaler interface for QueryActionConfig. +func (qc *QueryActionConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { + type plain QueryActionConfig + var err error + if err := unmarshal((*plain)(qc)); err != nil { + return err + } + // Check required fields + qc.query, err = NewField(qc.Query, nil) + if err != nil { + return fmt.Errorf("invalid template for query %q: %s", qc.Query, err) + } + + if qc.Method == "" { + qc.Method = "GET" + } + qc.method, err = NewField(qc.Method, nil) + if err != nil { + return fmt.Errorf("invalid template for method %q: %s", qc.Method, err) + } + if qc.Data != "" { + qc.data, err = NewField(qc.Data, nil) + if err != nil { + return fmt.Errorf("invalid template for data %q: %s", qc.Data, err) + } + } else { + qc.data = nil + } + + if qc.VarName == "" { + qc.VarName = "_root" + } + qc.var_name, err = NewField(qc.VarName, nil) + if err != nil { + return fmt.Errorf("invalid template for var_name %q: %s", qc.VarName, err) + } + + if qc.OkStatus != nil { + qc.ok_status = buildStatus(qc.OkStatus) + } + // set default if something was wrong or not set + if qc.ok_status == nil { + qc.ok_status = []int{200} + } + + if qc.AuthConfig != (*AuthConfig)(nil) { + if qc.AuthConfig.Mode != "" { + qc.auth_mode, err = NewField(qc.AuthConfig.Mode, nil) + if err != nil { + return fmt.Errorf("invalid template for query auth_mode %q: %s", qc.AuthConfig.Mode, err) + } + } + if qc.AuthConfig.Username != "" { + qc.user, err = NewField(qc.AuthConfig.Username, nil) + if err != nil { + return fmt.Errorf("invalid template for query username %q: %s", qc.AuthConfig.Username, err) + } + } + if qc.AuthConfig.Password != "" { + qc.passwd, err = NewField(string(qc.AuthConfig.Password), nil) + if err != nil { + return fmt.Errorf("invalid template for query password %q: %s", qc.AuthConfig.Password, err) + } + } + if qc.AuthConfig.Token != "" { + qc.token, err = NewField(string(qc.AuthConfig.Token), nil) + if err != nil { + return fmt.Errorf("invalid template for query auth_token %q: %s", qc.AuthConfig.Token, err) + } + } + } + + // switch curval := qc.OkStatus.(type) { + // case int: + // qc.ok_status = make([]int, 1) + // qc.ok_status[0] = curval + // case []int: + // qc.ok_status = curval + // case []string: + // var i_value int64 + // var err error + // qc.ok_status = make([]int, len(curval)) + // for idx, status := range curval { + // if i_value, err = strconv.ParseInt(strings.Trim(status, "\r\n "), 10, 0); err != nil { + // i_value = 0 + // } + // qc.ok_status[idx] = int(i_value) + // } + + // } + + return checkOverflow(qc.XXX, "query action") +} + +func buildStatus(raw_status any) []int { + var status []int + switch curval := raw_status.(type) { + case int: + status = make([]int, 1) + status[0] = curval + case []any: + status = make([]int, len(curval)) + for idx, subval := range curval { + switch sub_val := subval.(type) { + case int: + status[idx] = sub_val + case string: + var i_value int64 + var err error + if i_value, err = strconv.ParseInt(strings.Trim(sub_val, "\r\n "), 10, 0); err != nil { + i_value = 0 + } + status[idx] = int(i_value) + } + } + } + return status +} + +type QueryAction struct { + Name *Field `yaml:"name,omitempty"` + With []any `yaml:"with,omitempty"` + When []*exporterTemplate `yaml:"when,omitempty"` + LoopVar string `yaml:"loop_var,omitempty"` + Vars map[string]any `yaml:"vars,omitempty"` + Until []*exporterTemplate `yaml:"until,omitempty"` + Query *QueryActionConfig + + // Catches all undefined fields and must be empty after parsing. + XXX map[string]interface{} `yaml:",inline" json:"-"` +} + +// ***************** +func (a *QueryAction) Type() int { + return query_action +} + +func (a *QueryAction) GetName(symtab map[string]any, logger log.Logger) string { + str, err := a.Name.GetValueString(symtab, nil, false) + if err != nil { + level.Warn(logger).Log("msg", fmt.Sprintf("invalid action name: %v", err)) + return "" + } + return str +} +func (a *QueryAction) GetNameField() *Field { + return a.Name +} +func (a *QueryAction) SetNameField(name *Field) { + a.Name = name +} + +func (a *QueryAction) GetWidh() []any { + return a.With +} +func (a *QueryAction) SetWidth(with []any) { + a.With = with +} + +func (a *QueryAction) GetWhen() []*exporterTemplate { + return a.When + +} +func (a *QueryAction) SetWhen(when []*exporterTemplate) { + a.When = when +} + +func (a *QueryAction) GetLoopVar() string { + return a.LoopVar +} +func (a *QueryAction) SetLoopVar(loopvar string) { + a.LoopVar = loopvar +} + +func (a *QueryAction) GetVars() map[string]any { + return a.Vars +} +func (a *QueryAction) SetVars(vars map[string]any) { + a.Vars = vars +} + +func (a *QueryAction) GetUntil() []*exporterTemplate { + return a.Until +} +func (a *QueryAction) SetUntil(until []*exporterTemplate) { + a.Until = until +} + +func (a *QueryAction) setBasicElement( + nameField *Field, + vars map[string]any, + with []any, + loopVar string, + when []*exporterTemplate, + until []*exporterTemplate) error { + return setBasicElement(a, nameField, vars, with, loopVar, when, until) +} + +func (a *QueryAction) PlayAction(script *YAMLScript, symtab map[string]any, logger log.Logger) error { + return PlayBaseAction(script, symtab, logger, a, a.CustomAction) +} + +// only for MetricsAction +func (a *QueryAction) GetMetrics() []*GetMetricsRes { + return nil +} + +// only for MetricAction +func (a *QueryAction) GetMetric() *MetricConfig { + return nil +} +func (a *QueryAction) SetMetricFamily(*MetricFamily) { +} + +// only for PlayAction +func (a *QueryAction) SetPlayAction(scripts map[string]*YAMLScript) error { + return nil +} + +func (a *QueryAction) AddCustomTemplate(customTemplate *exporterTemplate) error { + + if err := AddCustomTemplate(a, customTemplate); err != nil { + return err + } + + if a.Query.query != nil { + if err := a.Query.query.AddDefaultTemplate(customTemplate); err != nil { + return err + } + } + + if a.Query.method != nil { + if err := a.Query.method.AddDefaultTemplate(customTemplate); err != nil { + return err + } + } + + if a.Query.data != nil { + if err := a.Query.data.AddDefaultTemplate(customTemplate); err != nil { + return err + } + } + if a.Query.var_name != nil { + if err := a.Query.var_name.AddDefaultTemplate(customTemplate); err != nil { + return err + } + } + + if a.Query.auth_mode != nil { + if err := a.Query.auth_mode.AddDefaultTemplate(customTemplate); err != nil { + return err + } + } + + if a.Query.user != nil { + if err := a.Query.user.AddDefaultTemplate(customTemplate); err != nil { + return err + } + } + + if a.Query.passwd != nil { + if err := a.Query.passwd.AddDefaultTemplate(customTemplate); err != nil { + return err + } + } + + if a.Query.token != nil { + if err := a.Query.token.AddDefaultTemplate(customTemplate); err != nil { + return err + } + } + + return nil +} + +func (a *QueryAction) CustomAction(script *YAMLScript, symtab map[string]any, logger log.Logger) error { + var ( + err error + payload, query, method, var_name string + auth_mode, user, passwd, auth_token string + ) + + level.Debug(logger).Log( + "script", ScriptName(symtab, logger), + "msg", fmt.Sprintf("[Type: QueryAction] Name: %s", a.GetName(symtab, logger))) + + query, err = a.Query.query.GetValueString(symtab, nil, false) + if err != nil { + query = a.Query.Query + level.Warn(logger).Log( + "script", ScriptName(symtab, logger), + "msg", fmt.Sprintf("invalid template for query '%s': %v", a.Query.Query, err)) + } + // symtab["url"] = query + + if a.Query.data != nil { + // auth_key, ok := symtab["auth_key"] + // if ok { + // level.Debug(logger).Log("authkey", auth_key) + // } + + payload, err = a.Query.data.GetValueString(symtab, nil, false) + if err != nil { + payload = a.Query.Data + level.Warn(logger).Log( + "script", ScriptName(symtab, logger), + "msg", fmt.Sprintf("invalid template for data '%s': %v", a.Query.Data, err)) + } + } else { + payload = "" + } + // symtab["data"] = payload + + method, err = a.Query.method.GetValueString(symtab, nil, false) + if err != nil { + method = strings.ToUpper(a.Query.Method) + level.Warn(logger).Log( + "script", ScriptName(symtab, logger), + "msg", fmt.Sprintf("invalid template for method '%s': %v", a.Query.Method, err)) + } + // symtab["method"] = method + + // maybe a good idea to check template var in ok_status too + // ok_status, err = a.Query.method.GetValueString(symtab, nil, false) + // if err != nil { + // method = strings.ToUpper(method) + // level.Warn(logger).Log( + // "script", a.ScriptName(symtab, logger), + // "msg", fmt.Sprintf("invalid template for method '%s': %v", method, err)) + // } + // symtab["ok_status"] = a.Query.ok_status + + var_name, err = a.Query.var_name.GetValueString(symtab, nil, false) + if err != nil { + level.Warn(logger).Log( + "script", ScriptName(symtab, logger), + "msg", fmt.Sprintf("invalid template for var_name '%s': %v", a.Query.VarName, err)) + } + // check_invalid_auth, found := GetMapValueBool(symtab, "check_invalid_auth") + // if !found { + // check_invalid_auth = true + // } + // symtab["var_name"] = var_name + + auth_mode, err = a.Query.auth_mode.GetValueString(symtab, nil, false) + if err != nil { + level.Warn(logger).Log( + "script", ScriptName(symtab, logger), + "msg", fmt.Sprintf("invalid template for auth_mode.mode '%s': %v", a.Query.AuthConfig.Mode, err)) + } + + user, err = a.Query.user.GetValueString(symtab, nil, false) + if err != nil { + level.Warn(logger).Log( + "script", ScriptName(symtab, logger), + "msg", fmt.Sprintf("invalid template for auth_mode.user '%s': %v", a.Query.AuthConfig.Username, err)) + } + + passwd, err = a.Query.passwd.GetValueString(symtab, nil, false) + if err != nil { + level.Warn(logger).Log( + "script", ScriptName(symtab, logger), + "msg", fmt.Sprintf("invalid template for auth_mode.user '%s': %v", a.Query.AuthConfig.Password, err)) + } + + auth_token, err = a.Query.token.GetValueString(symtab, nil, false) + if err != nil { + level.Warn(logger).Log( + "script", ScriptName(symtab, logger), + "msg", fmt.Sprintf("invalid template for auth_mode.token '%s': %v", a.Query.AuthConfig.Token, err)) + } + + // reset special var headers from th symbols table + delete(symtab, "headers") + params := &CallClientExecuteParams{ + Payload: payload, + Method: method, + Url: query, + Debug: bool(a.Query.Debug), + VarName: var_name, + OkStatus: a.Query.ok_status, + AuthMode: auth_mode, + Username: user, + Password: passwd, + Token: auth_token, + Timeout: time.Duration(a.Query.Timeout) * time.Second, + // Check_invalid_Auth: check_invalid_auth, + } + + level.Debug(logger).Log( + "script", ScriptName(symtab, logger), + "msg", fmt.Sprintf(" query: '%s' - method: '%s' - target_var: '%s'", query, method, a.Query.VarName)) + + if raw_func, ok := symtab["__method"]; ok { + if Func, ok := raw_func.(func(*CallClientExecuteParams, map[string]any) error); ok { + if err = Func(params, symtab); err != nil { + if err != ErrInvalidLogin { + level.Warn(logger).Log( + "script", ScriptName(symtab, logger), + "msg", fmt.Sprintf("internal method returns error: '%v'", err)) + } + } + } + // delete(symtab, "url") + // delete(symtab, "data") + // delete(symtab, "method") + // delete(symtab, "ok_status") + // delete(symtab, "var_name") + + } else { + level.Warn(logger).Log( + "script", ScriptName(symtab, logger), + "msg", "internal method to play not found") + } + return err +} + +// *************************************************************************************** diff --git a/set_fact_action.go b/set_fact_action.go new file mode 100644 index 0000000..882c013 --- /dev/null +++ b/set_fact_action.go @@ -0,0 +1,257 @@ +package main + +import ( + //"bytes" + "fmt" + + "github.com/go-kit/log" + "github.com/go-kit/log/level" +) + +// *************************************************************************************** +// *************************************************************************************** +// set_fact +// *************************************************************************************** +// *************************************************************************************** + +type SetFactAction struct { + Name *Field `yaml:"name,omitempty"` + With []any `yaml:"with,omitempty"` + When []*exporterTemplate `yaml:"when,omitempty"` + LoopVar string `yaml:"loop_var,omitempty"` + Vars map[string]any `yaml:"vars,omitempty"` + Until []*exporterTemplate `yaml:"until,omitempty"` + + SetFact map[string]any `yaml:"set_fact"` + + setFact [][]any +} + +func (a *SetFactAction) Type() int { + return set_fact_action +} + +func (a *SetFactAction) GetName(symtab map[string]any, logger log.Logger) string { + str, err := a.Name.GetValueString(symtab, nil, false) + if err != nil { + level.Warn(logger).Log("msg", fmt.Sprintf("invalid action name: %v", err)) + return "" + } + return str +} + +func (a *SetFactAction) GetNameField() *Field { + return a.Name +} +func (a *SetFactAction) SetNameField(name *Field) { + a.Name = name +} + +func (a *SetFactAction) GetWidh() []any { + return a.With +} +func (a *SetFactAction) SetWidth(with []any) { + a.With = with +} + +func (a *SetFactAction) GetWhen() []*exporterTemplate { + return a.When + +} +func (a *SetFactAction) SetWhen(when []*exporterTemplate) { + a.When = when +} + +func (a *SetFactAction) GetLoopVar() string { + return a.LoopVar +} +func (a *SetFactAction) SetLoopVar(loopvar string) { + a.LoopVar = loopvar +} + +func (a *SetFactAction) GetVars() map[string]any { + return a.Vars +} +func (a *SetFactAction) SetVars(vars map[string]any) { + a.Vars = vars +} +func (a *SetFactAction) GetUntil() []*exporterTemplate { + return a.Until +} +func (a *SetFactAction) SetUntil(until []*exporterTemplate) { + a.Until = until +} + +func (a *SetFactAction) setBasicElement( + nameField *Field, + vars map[string]any, + with []any, + loopVar string, + when []*exporterTemplate, + until []*exporterTemplate) error { + return setBasicElement(a, nameField, vars, with, loopVar, when, until) +} + +func (a *SetFactAction) PlayAction(script *YAMLScript, symtab map[string]any, logger log.Logger) error { + + return PlayBaseAction(script, symtab, logger, a, a.CustomAction) +} + +// only for MetricsAction +func (a *SetFactAction) GetMetrics() []*GetMetricsRes { + return nil +} + +// only for MetricAction +func (a *SetFactAction) GetMetric() *MetricConfig { + return nil +} +func (a *SetFactAction) SetMetricFamily(*MetricFamily) { +} + +// only for PlayAction +func (a *SetFactAction) SetPlayAction(scripts map[string]*YAMLScript) error { + return nil +} + +// specific behavior for the DebugAction +func (a *SetFactAction) CustomAction(script *YAMLScript, symtab map[string]any, logger log.Logger) error { + var key_name string + var err error + var value_name any + level.Debug(logger).Log( + "script", ScriptName(symtab, logger), + "msg", fmt.Sprintf("[Type: SetFactAction] Name: %s", Name(a.Name, symtab, logger))) + for _, pair := range a.setFact { + // tmp_map := map[string]any{} + if pair == nil { + return fmt.Errorf("set_fact: invalid key value") + } + if key, ok := pair[0].(*Field); ok { + key_name, err = key.GetValueString(symtab, nil, false) + if err == nil { + if value_name, err = getValue(symtab, pair[1]); err != nil { + return err + } + // switch value := pair[1].(type) { + // case *Field: + // if value_name, err = value.GetValueString(symtab, nil, false); err != nil { + // return err + // } + // default: + // // need to call a func to obtain a value from any type but with all content valorized + // // => list: try to valorize each element + // // => map: try to valorize key and value + // value_name = value + // } + if value_name == nil { + level.Debug(logger).Log( + "script", ScriptName(symtab, logger), + "msg", fmt.Sprintf(" remove from symbols table: %s", key_name)) + delete(symtab, key_name) + } else { + if key_name != "_" { + level.Debug(logger).Log( + "script", ScriptName(symtab, logger), + "msg", fmt.Sprintf(" add to symbols table: %s = '%v'", key_name, value_name)) + symtab[key_name] = value_name + } else { + level.Debug(logger).Log( + "script", ScriptName(symtab, logger), + "msg", " result discard (key >'_')") + + } + } + } + } + } + + return nil +} + +func (a *SetFactAction) AddCustomTemplate(customTemplate *exporterTemplate) error { + + if err := AddCustomTemplate(a, customTemplate); err != nil { + return err + } + + for _, pair := range a.setFact { + // tmp_map := map[string]any{} + if pair == nil { + return fmt.Errorf("set_fact: invalid key value") + } + if key, ok := pair[0].(*Field); ok { + if key != nil { + if err := key.AddDefaultTemplate(customTemplate); err != nil { + return err + } + } + if pair[1] != nil { + if err := AddCustomTemplateElement(pair[1], customTemplate); err != nil { + return fmt.Errorf("error in set_fact value: %s", err) + } + } + } + } + + return nil +} + +// *************************************************************************************** +// *************************************************************************************** + +func getMapValue(symtab map[string]any, raw_maps map[any]any) (map[any]any, error) { + var err error + final_res := make(map[any]any) + for raw_key, raw_value := range raw_maps { + key, err := getValue(symtab, raw_key) + if err != nil { + return nil, fmt.Errorf("invalid template for var key %s: %s", raw_key, err) + } + value, err := getValue(symtab, raw_value) + if err != nil { + return nil, fmt.Errorf("invalid template for var value %s: %s", raw_value, err) + } + final_res[key] = value + } + return final_res, err +} + +func getSliceValue(symtab map[string]any, raw_slice []any) (any, error) { + var err error + final_res := make([]any, len(raw_slice)) + for idx, r_value := range raw_slice { + res, err := getValue(symtab, r_value) + if err != nil { + return nil, fmt.Errorf("invalid template for var key %q: %s", res, err) + } + final_res[idx] = res + + } + return final_res, err +} + +func getValue(symtab map[string]any, raw_value any) (value_name any, err error) { + switch value := raw_value.(type) { + case *Field: + if value_name, err = value.GetValueString(symtab, nil, false); err != nil { + return nil, err + } + case []any: + if value_name, err = getSliceValue(symtab, value); err != nil { + return nil, err + } + case map[any]any: + if value_name, err = getMapValue(symtab, value); err != nil { + return nil, err + } + default: + // need to call a func to obtain a value from any type but with all content valorized + // => list: try to valorize each element + // => map: try to valorize key and value + value_name = value + } + return value_name, err +} + +// *************************************************************************************** diff --git a/target.go b/target.go new file mode 100644 index 0000000..7d8c94c --- /dev/null +++ b/target.go @@ -0,0 +1,524 @@ +package main + +import ( + "context" + "fmt" + "sort" + "sync" + "time" + + "github.com/go-kit/log" + "github.com/go-kit/log/level" + "github.com/prometheus/client_golang/prometheus" + dto "github.com/prometheus/client_model/go" + "google.golang.org/protobuf/proto" +) + +const ( + // Capacity for the channel to collect metrics. + capMetricChan = 1000 + + // Capacity for the channel to collect control message from collectors. + capCollectChan = 100 + + upMetricName = "up" + upMetricHelp = "1 if the target is reachable, or 0 if the scrape failed" + scrapeDurationName = "scrape_duration_seconds" + scrapeDurationHelp = "How long it took to scrape the target in seconds" +) + +// Target collects SQL metrics from a single sql.DB instance. It aggregates one or more Collectors and it looks much +// like a prometheus.Collector, except its Collect() method takes a Context to run in. +type Target interface { + // Collect is the equivalent of prometheus.Collector.Collect(), but takes a context to run in. + Collect(ctx context.Context, ch chan<- Metric) + Name() string + SetSymbol(string, any) error + // YAML() ([]byte, error) +} + +// target implements Target. It wraps a httpAPI, which is initially nil but never changes once instantianted. +type target struct { + name string + client *Client + collectors []Collector + constLabels prometheus.Labels + globalConfig *GlobalConfig + httpAPIScript map[string]*YAMLScript + upDesc MetricDesc + scrapeDurationDesc MetricDesc + logContext []interface{} + + logger log.Logger + + // symbols table + // tsymtab map[string]any + + // mutex to hold condition for global client to try to login() + // wait_mutex sync.Mutex + // wake_cond sync.Cond + + // to protect the data during exchange + // content_mutex sync.Mutex + // msg int +} + +const ( + // one collector needs a ping action + MsgPing = iota + // one collector needs a login action + MsgLogin = iota + // start-restart collect for all collectors + MsgCollect = iota + // wait for all collectors to reply + MsgWait = iota + // collecting is over + MsgQuit = iota + // one collect is over + MsgDone = iota +) + +// NewTarget returns a new Target with the given instance name, data source name, collectors and constant labels. +// An empty target name means the exporter is running in single target mode: no synthetic metrics will be exported. +func NewTarget( + logContext []interface{}, + tpar *TargetConfig, + ccs []*CollectorConfig, + constLabels prometheus.Labels, + gc *GlobalConfig, + http_script map[string]*YAMLScript, + logger log.Logger) (Target, error) { + + if tpar.Name != "" { + logContext = append(logContext, "target", tpar.Name) + } + + constLabelPairs := make([]*dto.LabelPair, 0, len(constLabels)) + for n, v := range constLabels { + constLabelPairs = append(constLabelPairs, &dto.LabelPair{ + Name: proto.String(n), + Value: proto.String(v), + }) + } + sort.Sort(labelPairSorter(constLabelPairs)) + + collectors := make([]Collector, 0, len(ccs)) + for _, cc := range ccs { + cscrl := make([]*YAMLScript, len(cc.CollectScripts)) + i := 0 + for _, cs := range cc.CollectScripts { + cscrl[i] = cs + i++ + } + c, err := NewCollector(logContext, logger, cc, constLabelPairs, cscrl) + if err != nil { + return nil, err + } + collectors = append(collectors, c) + } + + upDesc := NewAutomaticMetricDesc(logContext, gc.MetricPrefix+"_"+upMetricName, upMetricHelp, prometheus.GaugeValue, constLabelPairs) + scrapeDurationDesc := + NewAutomaticMetricDesc(logContext, gc.MetricPrefix+"_"+scrapeDurationName, scrapeDurationHelp, prometheus.GaugeValue, constLabelPairs) + + t := target{ + name: tpar.Name, + client: newClient(tpar, http_script, logger, gc), + collectors: collectors, + constLabels: constLabels, + globalConfig: gc, + httpAPIScript: http_script, + upDesc: upDesc, + scrapeDurationDesc: scrapeDurationDesc, + logContext: logContext, + logger: logger, + // wait_mutex: sync.Mutex{}, + // content_mutex: sync.Mutex{}, + } + if t.client == nil { + return nil, fmt.Errorf("internal http client undefined") + } + // t.wake_cond = *sync.NewCond(&t.wait_mutex) + + // init the symbols tab + // t.symtab = make(map[string]any) + + return &t, nil +} + +// Name implement Target.Name +// to obtain target name from interface +func (t *target) Name() string { + return t.name +} + +// SetSymbol implement Target.SetSymbol +func (t *target) SetSymbol(key string, value any) error { + + t.client.symtab[key] = value + return nil +} + +// func (t *target) startCollect(wg *sync.WaitGroup, ctx context.Context, ch chan<- Metric) { +// wg.Add(len(t.collectors)) +// for _, c := range t.collectors { +// // have to build a new client copy to allow multi connection to target +// c_client := t.client.Clone() +// c.SetClient(c_client) + +// // If using a single target connection, collectors will likely run sequentially anyway. But we might have more. +// go func(collector Collector) { +// defer wg.Done() +// collector.Collect(ctx, ch) +// }(c) +// } +// } + +// Collect implements Target. +func (t *target) Collect(ctx context.Context, ch chan<- Metric) { + + // chan to receive order from collector if something wrong with authentication + collectChan := make(chan int, capCollectChan) + + var ( + scrapeStart = time.Now() + wg_coll sync.WaitGroup + targetUp bool + err error + ) + + // wait for all collectors are over + defer func() { + wg_coll.Wait() + }() + + // to store is we already have tried to login + has_logged := false + + // t.client.client.httpClient.Transport.TLSClientConfig.InsecureSkipVerify + // try to connect to target + collectChan <- MsgPing + + leave_loop := false + for msg := range collectChan { + // t.content_mutex.Lock() + // msg := t.msg + // t.content_mutex.Unlock() + switch msg { + + case MsgLogin: + level.Debug(t.logger).Log("msg", "target: received MsgLogin") + if msg == MsgLogin && !has_logged { + t.client.Clear() + if status, err := t.client.Login(); err != nil { + collectChan <- MsgQuit + } else { + if status { + has_logged = true + collectChan <- MsgPing + } else { + collectChan <- MsgQuit + } + } + } + case MsgPing: + level.Debug(t.logger).Log("msg", "target: received MsgPing") + // If using a single target connection, collectors will likely run sequentially anyway. But we might have more. + wg_coll.Add(1) + go func(t *target, ch chan<- Metric, coll_ch chan<- int) { + defer wg_coll.Done() + // collector.Collect(ctx, met_ch, &t.wake_cond) + targetUp, err = t.ping(ctx, ch, collectChan) + }(t, ch, collectChan) + level.Debug(t.logger).Log("msg", "target: ping send MsgWait") + collectChan <- MsgWait + + case MsgQuit: + level.Debug(t.logger).Log("msg", "target: ping received MsgQuit") + leave_loop = true + + case MsgWait: + level.Debug(t.logger).Log("msg", "start waiting for ping is over") + wg_coll.Wait() + level.Debug(t.logger).Log("msg", "after waiting for ping is over") + need_login := false + submsg := <-collectChan + + switch submsg { + case MsgLogin: + level.Debug(t.logger).Log("msg", "target ping wait: received MsgLogin") + need_login = true + case MsgDone: + level.Debug(t.logger).Log("msg", "target ping wait: received MsgDone") + default: + level.Debug(t.logger).Log("msg", fmt.Sprintf("target ping wait: received msg [%d]", submsg)) + } + if need_login { + collectChan <- MsgLogin + } else { + collectChan <- MsgQuit + } + } + // leave collectChan loop + if leave_loop { + break + } + } + + // targetUp, err := t.ping(ctx) + if err != nil { + ch <- NewInvalidMetric(t.logContext, err) + targetUp = false + } + if t.name != "" { + // Export the target's `up` metric as early as we know what it should be. + ch <- NewMetric(t.upDesc, boolToFloat64(targetUp), nil, nil) + } + + // Don't bother with the collectors if target is down. + if targetUp { + // var wg sync.WaitGroup + + // mutex to hold condition for global client to try to login() + // wait_mutex := sync.Mutex{} + // wake_cond := *sync.NewCond(&t.wait_mutex) + + // wg.Add(1) + // go func(t *target, ctx context.Context, met_ch chan<- Metric) { + + // ok := true + has_logged := false + // check if we have already logged to target ? + // if not then set msg to directly try to login + if logged, ok := GetMapValueBool(t.client.symtab, "logged"); ok && logged { + has_logged = logged + } + + if has_logged { + // if already logged only start to collect metrics + collectChan <- MsgCollect + } else { + // else not already logged so start to login in + collectChan <- MsgLogin + } + + for msg := range collectChan { + // t.content_mutex.Lock() + // msg := t.msg + // t.content_mutex.Unlock() + switch msg { + + case MsgLogin: + level.Debug(t.logger).Log("msg", "target: received MsgLogin") + // check if we have already logged to target ? + // if not then set msg to directly try to login + // if logged, ok := GetMapValueBool(t.client.symtab, "logged"); ok && logged { + // // t.content_mutex.Lock() + // // t.msg = MsgLogin + // // t.content_mutex.Unlock() + // has_logged = logged + // } + level.Debug(t.logger).Log("msg", fmt.Sprintf("target: MsgLogin check has_logged: %v", has_logged)) + if msg == MsgLogin && !has_logged { + // + // wait until all collectors have finished + wg_coll.Wait() + t.client.Clear() + t.client.symtab["__coll_channel"] = collectChan + + if status, err := t.client.Login(); err != nil { + collectChan <- MsgQuit + } else { + if status { + has_logged = true + collectChan <- MsgCollect + // t.msg = MsgEmpty + // msg = MsgEmpty + } else { + collectChan <- MsgQuit + + // t.msg = MsgQuit + // msg = MsgQuit + } + } + delete(t.client.symtab, "__coll_channel") + } + case MsgCollect: + level.Debug(t.logger).Log("msg", "target: received MsgCollect") + wg_coll.Add(len(t.collectors)) + level.Debug(t.logger).Log("msg", fmt.Sprintf("target: send %d collector(s)", len(t.collectors))) + for _, c := range t.collectors { + // have to build a new client copy to allow multi connection to target + c_client := t.client.Clone() + c.SetClient(c_client) + // c.SetClient(t.client) + + // If using a single target connection, collectors will likely run sequentially anyway. But we might have more. + go func(collector Collector) { + defer wg_coll.Done() + // collector.Collect(ctx, met_ch, &t.wake_cond) + collector.Collect(ctx, ch, collectChan) + }(c) + } + level.Debug(t.logger).Log("msg", "target: send MsgWait") + collectChan <- MsgWait + + case MsgQuit: + level.Debug(t.logger).Log("msg", "target: received MsgQuit") + close(collectChan) + + case MsgWait: + level.Debug(t.logger).Log("msg", "start waiting for all collectors are over") + + wg_coll.Wait() + level.Debug(t.logger).Log("msg", "after waiting for all collectors is over") + need_login := false + // we have received len(t.collectors) msgs from collectors + for i := 0; i < len(t.collectors); i++ { + level.Debug(t.logger).Log("msg", fmt.Sprintf("target wait: read msg from collector %d", i)) + submsg := <-collectChan + // for submsg := range collectChan { + + switch submsg { + case MsgLogin: + level.Debug(t.logger).Log("msg", "target wait: received MsgLogin") + need_login = true + default: + level.Debug(t.logger).Log("msg", fmt.Sprintf("target wait: received msg [%d] for collector %d", submsg, i)) + } + } + if need_login { + collectChan <- MsgLogin + t.client.symtab["logged"] = false + has_logged = false + if logged, ok := GetMapValueBool(t.client.symtab, "logged"); ok && logged { + level.Debug(t.logger).Log("msg", fmt.Sprintf("target: MsgLogin check has_logged: %v", logged)) + } + } else { + collectChan <- MsgQuit + } + } + // level.Debug(t.logger).Log("msg", "goroutine login() is waiting") + // t.wait_mutex.Lock() + // t.wake_cond.Wait() + } + level.Debug(t.logger).Log("msg", "goroutine target collector controler is over") + // }(t, ctx, ch) + + // wait_mutex.Lock() + // wake_cond.Wait() + // Wait for all collectors to complete, then close the channel. + // go func() { + // wg.Wait() + // close(collectChan) + // }() + + // Drain collectChan in case of premature return. + defer func() { + for range collectChan { + } + }() + } + + // Wait for all collectors (if any) to complete. + // wg.Wait() + level.Debug(t.logger).Log("msg", "collectors have stopped") + // tell the "waiting login" func to stop + // t.content_mutex.Lock() + // t.msg = MsgQuit + // t.content_mutex.Unlock() + // t.wake_cond.Signal() + + if t.name != "" { + // And export a `scrape duration` metric once we're done scraping. + ch <- NewMetric(t.scrapeDurationDesc, float64(time.Since(scrapeStart))*1e-9, nil, nil) + } +} + +// YAML marshals the config into YAML format. +// type tconfig struct { +// Name string `yaml:"name"` // target name to connect to from prometheus +// Scheme string `yaml:"scheme"` +// Host string `yaml:"host"` +// Port string `yaml:"port"` +// BaseUrl string `yaml:"baseUrl"` +// AuthConfig AuthConfig `yaml:"auth_mode,omitempty"` +// ProxyUrl string `yaml:"proxy"` +// VerifySSL ConvertibleBoolean `yaml:"verifySSL"` +// ConnectionTimeout model.Duration `yaml:"connection_timeout"` // connection timeout, per-target +// Labels map[string]string `yaml:"labels,omitempty"` // labels to apply to all metrics collected from the targets +// CollectorRefs []string `yaml:"collectors"` // names of collectors to execute on the target +// TargetsFiles []string `yaml:"targets_files,omitempty"` // slice of path and pattern for files that contains targets +// QueryRetry string `yaml:"query_retry,omitempty"` // target specific number of times to retry a query +// } + +// func (t *target) YAML() ([]byte, error) { +// tcfg := &tconfig{ +// Name: t.Name(), +// Scheme: t.Sc, +// Host: "", +// Port: "", +// BaseUrl: "", +// AuthConfig: AuthConfig{}, +// ProxyUrl: "", +// VerifySSL: false, +// ConnectionTimeout: t, +// Labels: map[string]string{}, +// CollectorRefs: []string{}, +// TargetsFiles: []string{}, +// QueryRetry: "", +// } +// return yaml.Marshal(tcfg) +// } + +// ping implement ping for target +func (t *target) ping(ctx context.Context, ch chan<- Metric, coll_ch chan<- int) (bool, error) { + t.client.symtab["__coll_channel"] = coll_ch + status, err := t.client.Ping() + delete(t.client.symtab, "__coll_channel") + return status, err +} + +// boolToFloat64 converts a boolean flag to a float64 value (0.0 or 1.0). +func boolToFloat64(value bool) float64 { + if value { + return 1.0 + } + return 0.0 +} + +// type targets []target + +// func (ts targets) YAML() ([]byte, error) { +// var full, content []byte +// var err error + +// for t := range ts { +// content, err = yaml.Marshal(t) +// if err != nil { +// content = nil +// return content, err +// } +// full = append(full, content...) +// } +// return full, err +// } + +// type Targets interface { +// YAML() ([]byte, error) +// } + +// func (ts *Targets) YAML() ([]byte, error) { +// var full, content []byte +// var err error + +// for t := range *ts { +// content, err = yaml.Marshal(t) +// if err != nil { +// content = nil +// return content, err +// } +// full = append(full, content...) +// } +// return full, err +// } diff --git a/template.go b/template.go new file mode 100644 index 0000000..0498cb0 --- /dev/null +++ b/template.go @@ -0,0 +1,250 @@ +package main + +import ( + "bytes" + "encoding/json" + "fmt" + "net/http" + "strconv" + + // "strconv" + "strings" + ttemplate "text/template" + + "github.com/Masterminds/sprig/v3" + "github.com/peekjef72/httpapi_exporter/encrypt" +) + +func convertToBytes(curval any, unit string) (int64, error) { + var i_value int64 + var err error + if curval == nil { + return 0, nil + } + + // it is a raw value not a template look in "item" + switch curval := curval.(type) { + case int: + i_value = int64(curval) + case int64: + i_value = curval + case float32: + i_value = int64(curval) + case float64: + i_value = int64(curval) + case string: + if i_value, err = strconv.ParseInt(strings.Trim(curval, "\r\n "), 10, 64); err != nil { + i_value = 0 + } + default: + i_value = 0 + // value := row[v].(float64) + } + switch unit { + case "kilobyte", "Kb": + i_value = i_value * 1024 + case "megabyte", "Mb": + i_value = i_value * 1024 * 1024 + case "gigabyte", "Gb": + i_value = i_value * 1024 * 1024 * 1024 + + } + return i_value, nil +} + +// allow to retrive string header from response's headers +func getHeader(headers http.Header, header string) (string, error) { + return headers.Get(header), nil +} + +// function for template: custom dict hasKey() key that allow to query key from dict of map[any]any type instead of map[string]any +func exporterHasKey(dict any, lookup_key string) (bool, error) { + res := false + + switch maptype := dict.(type) { + case map[string]any: + if _, ok := maptype[lookup_key]; ok { + res = true + } + case map[any]any: + if _, ok := maptype[lookup_key]; ok { + res = true + } + } + + return res, nil +} + +// function for template: custom dict get() key that allow to query key from dict of map[any]any type instead of map[string]any +func exporterGet(dict any, lookup_key string) (any, error) { + var val any + + switch maptype := dict.(type) { + case map[string]any: + if raw_val, ok := maptype[lookup_key]; ok { + val = raw_val + } + case map[any]any: + if raw_val, ok := maptype[lookup_key]; ok { + val = raw_val + } + default: + val = "" + } + return val, nil +} + +// function for template: custom dict set() key with value that allow to set key of dict map[any]any type instead of map[string]any +func exporterSet(dict any, lookup_key string, val any) (any, error) { + + switch maptype := dict.(type) { + case map[string]any: + maptype[lookup_key] = val + case map[any]any: + maptype[lookup_key] = val + } + + return dict, nil +} + +// function for template: custom dict keys() that allow to obtain keys slide from dict map[any]any type instead of map[string]any +func exporterKeys(dict any) ([]any, error) { + var res []any + + switch maptype := dict.(type) { + case map[string]any: + res = make([]any, len(maptype)) + i := 0 + for raw_key := range maptype { + res[i] = raw_key + i++ + } + case map[any]any: + res = make([]any, len(maptype)) + i := 0 + for raw_key := range maptype { + res[i] = raw_key + i++ + } + } + + return res, nil +} + +// function for template: custom dict values() that allow to obtain values slide from map[any]any type instead of map[string]any +func exporterValues(dict any) ([]any, error) { + var res []any + + switch maptype := dict.(type) { + case map[string]any: + res = make([]any, len(maptype)) + i := 0 + for _, raw_value := range maptype { + res[i] = raw_value + i++ + } + case map[any]any: + res = make([]any, len(maptype)) + i := 0 + for _, raw_value := range maptype { + res[i] = raw_value + i++ + } + } + + return res, nil +} + +// function for template: obtain json marshal representation of obj +func exporterToRawJson(in any) (string, error) { + var ( + err error + ) + buf := new(bytes.Buffer) + enc := json.NewEncoder(buf) + enc.SetEscapeHTML(false) + + switch raw_v := in.(type) { + case []any: + buf.WriteString("[") + llen := len(raw_v) + for i, raw_v2 := range raw_v { + err = enc.Encode(&raw_v2) + if i+1 < llen { + buf.WriteString(",") + } + } + buf.WriteString("]") + case map[any]any: + buf.WriteString("{") + mlen := len(raw_v) + i := 0 + for raw_k, raw_v2 := range raw_v { + str, err2 := exporterToRawJson(raw_k) + if err2 != nil { + return "", err2 + } + buf.WriteString(str) + buf.WriteString(":") + str, err2 = exporterToRawJson(raw_v2) + if err2 != nil { + return "", err2 + } + buf.WriteString(str) + i++ + if i < mlen { + buf.WriteString(",") + } + } + buf.WriteString("}") + // case string: + default: + err = enc.Encode(&raw_v) + } + + if err != nil { + return "", err + } + return strings.TrimSuffix(buf.String(), "\n"), nil + +} + +// function to decrypt password from shared key sent by caller +func exporterDecryptPass(passwd string, auth_key string) (string, error) { + if strings.Contains(passwd, "/encrypted/") { + ciphertext := passwd[len("/encrypted/"):] + cipher, err := encrypt.NewAESCipher(auth_key) + if err != nil { + err := fmt.Errorf("can't obtain cipher to decrypt: %s", err) + // level.Error(c.logger).Log("errmsg", err) + return passwd, err + } + passwd, err = cipher.Decrypt(ciphertext, true) + if err != nil { + err := fmt.Errorf("invalid key provided to decrypt: %s", err) + // level.Error(c.logger).Log("errmsg", err) + return passwd, err + } + } + + return passwd, nil +} +func mymap() ttemplate.FuncMap { + sprig_map := sprig.FuncMap() + // my_map := make(map[string]interface{}, len(sprig_map)+1) + // for k, v := range sprig_map { + // my_map[k] = v + // } + // my_map["convertToBytes"] = convertToBytes + sprig_map["convertToBytes"] = convertToBytes + sprig_map["getHeader"] = getHeader + sprig_map["exporterDecryptPass"] = exporterDecryptPass + sprig_map["exporterHasKey"] = exporterHasKey + sprig_map["exporterGet"] = exporterGet + sprig_map["exporterSet"] = exporterSet + sprig_map["exporterKeys"] = exporterKeys + sprig_map["exporterValues"] = exporterValues + sprig_map["exporterToRawJson"] = exporterToRawJson + + return sprig_map +} diff --git a/veeam_exporter b/veeam_exporter new file mode 120000 index 0000000..b13eec0 --- /dev/null +++ b/veeam_exporter @@ -0,0 +1 @@ +httpapi_exporter \ No newline at end of file diff --git a/yaml_script.go b/yaml_script.go new file mode 100644 index 0000000..384401f --- /dev/null +++ b/yaml_script.go @@ -0,0 +1,993 @@ +package main + +import ( + //"bytes" + "fmt" + "reflect" + "strings" + "text/template" + + "github.com/go-kit/log" + "github.com/go-kit/log/level" + "gopkg.in/yaml.v3" +) + +type YAMLScript struct { + // name string `yaml:"name"` + Actions ActionsList `yaml:"actions"` + UntilLimit int `yaml:"until_limit"` + + name string + customTemplate *template.Template + metricsActions []*MetricsAction +} + +type DumpYAMLScript struct { + Actions ActionsList `yaml:"actions"` + UntilLimit int `yaml:"until_limit"` +} + +//****************************************************************** +//** +//****************************************************************** + +const ( + base_action = iota + set_fact_action = iota + actions_action = iota + debug_action = iota + query_action = iota + metrics_action = iota + metric_action = iota + play_script_action = iota +) + +type GetMetricsRes struct { + mc []*MetricConfig + maprefix string +} + +type Action interface { + BaseAction + // GetName(symtab map[string]any, logger log.Logger) string + Type() int + // GetBaseAction() *BaseAction + setBasicElement(nameField *Field, vars map[string]any, with []any, loopVar string, when []*exporterTemplate, until []*exporterTemplate) error + PlayAction(script *YAMLScript, symtab map[string]any, logger log.Logger) error + CustomAction(script *YAMLScript, symtab map[string]any, logger log.Logger) error + // to dump config + // String() string + + // only for MetricsAction + GetMetrics() []*GetMetricsRes + // only for MetricAction + GetMetric() *MetricConfig + SetMetricFamily(*MetricFamily) + // only for PlayAction + SetPlayAction(scripts map[string]*YAMLScript) error + AddCustomTemplate(customTemplate *exporterTemplate) error +} + +type ActionsList []Action + +func (sc *YAMLScript) Play(symtab map[string]any, ignore_errors bool, logger log.Logger) error { + symtab["__name__"] = sc.name + for _, ac := range sc.Actions { + err := ac.PlayAction(sc, symtab, logger) + if !ignore_errors && err != nil { + return err + } + } + return nil +} + +func (sc *YAMLScript) AddCustomTemplate(customTemplate *exporterTemplate) error { + for _, ac := range sc.Actions { + err := ac.AddCustomTemplate(customTemplate) + if err != nil { + return err + } + } + return nil +} + +func ScriptName(symtab map[string]any, logger log.Logger) string { + raw_name, ok := symtab["__name__"] + if !ok { + level.Warn(logger).Log("msg", "invalid script name") + return "" + } + str, ok := raw_name.(string) + if !ok { + level.Warn(logger).Log("msg", "invalid script name type") + return "" + + } + return str +} + +func Name(name *Field, symtab map[string]any, logger log.Logger) string { + str, err := name.GetValueString(symtab, nil, false) + if err != nil { + level.Warn(logger).Log("msg", fmt.Sprintf("invalid action name: %v", err)) + return "" + } + return str +} + +type BaseAction interface { + GetName(symtab map[string]any, logger log.Logger) string + + GetNameField() *Field + SetNameField(*Field) + GetWidh() []any + SetWidth([]any) + GetWhen() []*exporterTemplate + SetWhen([]*exporterTemplate) + GetLoopVar() string + SetLoopVar(string) + GetVars() map[string]any + SetVars(map[string]any) + GetUntil() []*exporterTemplate + SetUntil([]*exporterTemplate) +} + +func setBasicElement( + ba BaseAction, + nameField *Field, + vars map[string]any, + with []any, + loopVar string, + when []*exporterTemplate, + until []*exporterTemplate) error { + + if nameField != nil { + ba.SetNameField(nameField) + } + if len(vars) > 0 { + ba.SetVars(vars) + } + if len(with) > 0 { + // have to check every element in the slide + // build a Field for each. + baWith := make([]any, len(with)) + for idx, elmt := range with { + switch curval := elmt.(type) { + case string: + tt := strings.LastIndex(curval, "}}") + if tt != -1 { + curval = curval[:tt] + " | toRawJson " + curval[tt:] + } + field, err := NewField(curval, nil) + if err != nil { + return err + } + baWith[idx] = field + case map[any]any: + baWith[idx] = elmt + case int: + tmp := fmt.Sprintf("%d", curval) + field, err := NewField(tmp, nil) + if err != nil { + return err + } + baWith[idx] = field + + case *Field: + baWith[idx] = curval + + default: + return fmt.Errorf("with_items: invalid type: '%s'", reflect.TypeOf(elmt)) + } + } + ba.SetWidth(baWith) + if loopVar != "" { + ba.SetLoopVar(loopVar) + } + // ba.With = with + } + if len(when) > 0 { + ba.SetWhen(when) + } + if len(until) > 0 { + ba.SetUntil(until) + } + return nil +} + +func AddCustomTemplateElement(item any, customTemplate *exporterTemplate) error { + switch curval := item.(type) { + case *Field: + if curval != nil { + if err := curval.AddDefaultTemplate(customTemplate); err != nil { + return err + } + } + case map[any]any: + if curval == nil { + return nil + } + for r_key, r_value := range curval { + if err := AddCustomTemplateElement(r_key, customTemplate); err != nil { + return err + } + if err := AddCustomTemplateElement(r_value, customTemplate); err != nil { + return err + } + } + case []any: + if curval == nil { + return nil + } + for _, r_value := range curval { + if err := AddCustomTemplateElement(r_value, customTemplate); err != nil { + return err + } + } + default: + return fmt.Errorf("invalid type for with_items: %v", item) + } + return nil +} + +func AddCustomTemplate(ba BaseAction, customTemplate *exporterTemplate) error { + name := ba.GetNameField() + if name != nil { + if err := name.AddDefaultTemplate(customTemplate); err != nil { + return err + } + } + baWith := ba.GetWidh() + for idx, with := range baWith { + if err := AddCustomTemplateElement(with, customTemplate); err != nil { + return fmt.Errorf("error in with[%d]: %s", idx, err) + } + } + baUntil := ba.GetUntil() + for idx, until_tmpl := range baUntil { + if tmpl, err := AddDefaultTemplate(until_tmpl, customTemplate); err != nil { + return fmt.Errorf("error in until[%d]: %s", idx, err) + } else { + baUntil[idx] = tmpl + } + } + + baWhen := ba.GetWhen() + for idx, when_tmpl := range baWhen { + if tmpl, err := AddDefaultTemplate(when_tmpl, customTemplate); err != nil { + return fmt.Errorf("error in when[%d]: %s", idx, err) + } else { + baWhen[idx] = tmpl + } + } + + return nil +} + +// ******************************************************************************** +func ValorizeValue(symtab map[string]any, item any, logger log.Logger, cur_level int) (any, error) { + var data any + var err error + // fmt.Println("item = ", reflect.TypeOf(item)) + switch curval := item.(type) { + case *Field: + check_value := false + if cur_level == 0 { + // this is a template populate list with var from symtab + data, err = curval.GetValueObject(symtab) + if err != nil { + return data, err + } + if r_data, ok := data.([]any); ok { + if r_data == nil { + check_value = true + } + } + // if r_data, ok := data.([]any); ok { + // if len(r_data) == 0 { + // check_value = true + // } + // } + } else { + check_value = true + } + if check_value { + data, err = curval.GetValueString(symtab, nil, false) + } + return data, err + case map[any]any: + ldata := make(map[string]any, len(curval)) + + for r_key, r_value := range curval { + key, err := ValorizeValue(symtab, r_key, logger, cur_level+1) + if err != nil { + level.Warn(logger).Log("msg", fmt.Sprintf("error building map key: %v", r_key), "errmsg", err) + continue + } + key_val := "" + if r_key_val, ok := key.(string); ok { + key_val = r_key_val + } else { + continue + } + value, err := ValorizeValue(symtab, r_value, logger, cur_level+1) + if err != nil { + level.Warn(logger).Log("msg", fmt.Sprintf("error building map value for key '%s'", key_val), "errmsg", err) + continue + } + ldata[key_val] = value + } + if len(ldata) > 0 { + data = ldata + } + case []any: + ldata := make([]any, len(curval)) + for idx, r_value := range curval { + values, err := ValorizeValue(symtab, r_value, logger, cur_level+1) + if err != nil { + level.Warn(logger).Log("msg", fmt.Sprintf("error building list value for index: %d", idx), "errmsg", err) + continue + } + ldata[idx] = values + } + if len(ldata) > 0 { + data = ldata + } + default: + level.Warn(logger).Log("msg", fmt.Sprintf("invalid type for with_items: %s", item)) + } + return data, nil +} + +func preserve_sym_tab(symtab map[string]any, old_values map[string]any, key string, val any) { + if old_val, ok := symtab[key]; ok { + old_values[key] = old_val + } else { + old_values[key] = "_" + } + symtab[key] = val +} + +func PlayBaseAction(script *YAMLScript, symtab map[string]any, logger log.Logger, ba Action, customAction func(*YAMLScript, map[string]any, log.Logger) error) error { + + // to preverse values from symtab + old_values := make(map[string]any) + + defer func() { + for key, val := range old_values { + if val == "_" { + delete(symtab, key) + } else { + symtab[key] = val + } + } + }() + + // add the local vars from the action to symbols table + baVars := ba.GetVars() + if len(baVars) > 0 { + for key, val := range baVars { + preserve_sym_tab(symtab, old_values, key, val) + } + } + var items []any + var loop_var string + do_loop := false + set_loops_var := false + + final_items := make([]any, 0) + baWith := ba.GetWidh() + if len(baWith) > 0 { + // build a list of element from ba.With list of Field + items = baWith + for _, item := range items { + + data, err := ValorizeValue(symtab, item, logger, 0) + if err == nil { + if l_data, ok := data.([]any); ok { + final_items = append(final_items, l_data...) + } else { + final_items = append(final_items, data) + } + } else { + level.Warn(logger).Log("msg", fmt.Sprintf("no data found for with_items: %s", err)) + } + } + items = final_items + baLoopVar := ba.GetLoopVar() + if baLoopVar != "" { + loop_var = baLoopVar + } else { + loop_var = "item" + } + set_loops_var = true + } else if len(ba.GetUntil()) > 0 { + do_loop = true + } else { + items = make([]any, 1) + items[0] = 0 + } + + if !do_loop { + // loop on items + for idx, item := range items { + if set_loops_var { + preserve_sym_tab(symtab, old_values, loop_var, item) + preserve_sym_tab(symtab, old_values, "loop_var_idx", idx) + preserve_sym_tab(symtab, old_values, "loop_var", loop_var) + } + // check if there are condition on the "item" loop; + // if one is false break item the loop on next. + baWhen := ba.GetWhen() + if len(baWhen) > 0 { + + valid_value := true + + for i, condTpl := range baWhen { + tmp_res := new(strings.Builder) + // ptr := (*template.Template)(condTpl) + err := ((*template.Template)(condTpl)).Execute(tmp_res, &symtab) + if err != nil { + return fmt.Errorf("invalid template value for 'when' %s: %s", baWhen[i].Tree.Root.String(), err) + } + if tmp_res.String() != "true" { + level.Debug(logger).Log( + "script", ScriptName(symtab, logger), + "msg", fmt.Sprintf("Name: '%s' skipped : when condition false: '%s'", ba.GetName(symtab, logger), baWhen[i].Tree.Root.String())) + + valid_value = false + break + } + } + if !valid_value { + continue + } + } + err := customAction(script, symtab, logger) + if err != nil { + return err + } + } + } else { + idx := 0 + for { + if set_loops_var { + preserve_sym_tab(symtab, old_values, "loop_var_idx", idx) + // symtab["loop_var_idx"] = idx + } + valid_value := true + + baUntil := ba.GetUntil() + for i, condTpl := range baUntil { + tmp_res := new(strings.Builder) + err := ((*template.Template)(condTpl)).Execute(tmp_res, &symtab) + if err != nil { + err := fmt.Errorf("invalid template value for 'until' %s: %s", baUntil[i].Tree.Root.String(), err) + level.Warn(logger).Log("msg", err) + return err + } + if tmp_res.String() != "true" { + level.Debug(logger).Log( + "script", ScriptName(symtab, logger), + "msg", fmt.Sprintf("Name: '%s' until limit cond reached : '%s'", ba.GetName(symtab, logger), baUntil[i].Tree.Root.String())) + + valid_value = false + break + } + } + if !valid_value || idx >= script.UntilLimit { + if idx >= script.UntilLimit { + level.Warn(logger).Log("msg", fmt.Sprintf("max iteration reached for until action (%d)", script.UntilLimit)) + } + break + } + idx += 1 + + err := customAction(script, symtab, logger) + if err != nil { + return err + } + } + } + return nil +} + +// *************************************************************************************** +// *************************************************************************************** +// yaml script parser +// *************************************************************************************** +// *************************************************************************************** + +// parse all possible actions in a yaml script. +// +// * debug: msg: test{{ go template}} +// +// * set_fact: vars to set in symbols table +// +// * query: play url to the target and set resulting json obj the sybols table +// +// * metrics: define a list of metric (metric_name) to generate for the exporter +// +// * actions: define a list of subaction to play: query, metrics... +// +// * play_script: play the script +// +// * metric_name: a metric definition +type tmpActions []map[string]yaml.Node + +func build_WithItems(raw yaml.Node) ([]any, error) { + var listElmt []any + if raw.Tag == "!!str" { + with_field, err := NewField(raw.Value, nil) + if err != nil { + return nil, fmt.Errorf("invalid template for var name (set_fact) %q: %s", raw.Value, err) + } + listElmt = make([]any, 1) + listElmt[0] = with_field + } else if raw.Tag == "!!map" { + var ( + raw_mapElmt map[string]any + mapElmt map[any]any + err error + ) + + if err = raw.Decode(&raw_mapElmt); err != nil { + return nil, err + } + if mapElmt, err = buildMapField(raw_mapElmt); err != nil { + return nil, err + } + listElmt = make([]any, 1) + listElmt[0] = mapElmt + } else if raw.Tag == "!!seq" { + var ( + str_listElmt []any + err error + ) + + if err = raw.Decode(&str_listElmt); err != nil { + return nil, err + } + if listElmt, err = buildSliceField(str_listElmt); err != nil { + return nil, err + } + } else { + listElmt = make([]any, 0) + } + return listElmt, nil +} + +func build_Cond(script *YAMLScript, raw yaml.Node) ([]*exporterTemplate, error) { + var listElmt []string + var when []*exporterTemplate + + if raw.Tag == "!!str" { + listElmt = make([]string, 1) + listElmt[0] = raw.Value + } else if raw.Tag == "!!seq" { + if err := raw.Decode(&listElmt); err != nil { + return nil, err + } + } else { + listElmt = make([]string, 0) + } + if len(listElmt) > 0 { + when = make([]*exporterTemplate, len(listElmt)) + for i, cond := range listElmt { + var tmpl *template.Template + var err error + + if !strings.Contains(cond, "{{") { + cond = "{{ " + cond + " }}" + } + // alter the when conditions to add custom templates/funcs if exist + if script.customTemplate != nil { + tmpl, err = script.customTemplate.Clone() + if err != nil { + return nil, fmt.Errorf("for script %s template clone error: %s", script.name, err) + } + } else { + tmpl = template.New("cond").Funcs(mymap()) + } + tmpl, err = tmpl.Parse(cond) + if err != nil { + return nil, fmt.Errorf("for script %s template %s is invalid: %s", script.name, cond, err) + } + when[i] = (*exporterTemplate)(tmpl) + } + } + return when, nil +} + +func buildMapField(raw_maps map[string]any) (map[any]any, error) { + var err error + final_res := make(map[any]any) + for key, r_value := range raw_maps { + res, err := buildFields(key, r_value) + if err != nil { + return nil, fmt.Errorf("invalid template for var key %s: %s", key, err) + } + for key, val := range res { + final_res[key] = val + } + } + return final_res, err +} + +func buildSliceField(raw_slice []any) ([]any, error) { + var err error + final_res := make([]any, len(raw_slice)) + for idx, r_value := range raw_slice { + res, err := buildValueField(r_value) + if err != nil { + return nil, fmt.Errorf("invalid template for var key %q: %s", res, err) + } + final_res[idx] = res + + } + return final_res, err +} + +func buildFields(key string, val any) (map[any]any, error) { + var err error + var key_field *Field + var value_field any + // Check required fields + res := make(map[any]any) + + key_field, err = NewField(key, nil) + if err != nil { + return nil, fmt.Errorf("invalid template for var name (set_fact) %q: %s", key, err) + } + value_field, err = buildValueField(val) + if err != nil { + return nil, fmt.Errorf("invalid template for var name (set_fact) %q: %s", key, err) + } + res[key_field] = value_field + + return res, nil +} + +func buildValueField(val any) (any, error) { + + switch curval := val.(type) { + case string: + value_field, err := NewField(curval, nil) + if err != nil { + return nil, fmt.Errorf("invalid template for var value (set_fact) %q: %s", curval, err) + } + return value_field, nil + + // value is a map + case map[string]any: + tmp, err := buildMapField(curval) + if err != nil { + return nil, fmt.Errorf("invalid template for map value (set_fact) %q: %s", curval, err) + } + return tmp, nil + // value is a slice + case []any: + tmp, err := buildSliceField(curval) + if err != nil { + return nil, fmt.Errorf("invalid template for map value (set_fact) %q: %s", curval, err) + } + return tmp, nil + + // a value int float... what else ? + default: + return curval, nil + } +} + +// UnmarshalYAML implements the yaml.Unmarshaler interface for YAMLScript. +func (script *YAMLScript) UnmarshalYAML(value *yaml.Node) error { + var tmp tmpActions + if err := value.Decode(&tmp); err != nil { + return err + } + actions, err := ActionsListDecode(script, make(ActionsList, 0, len(tmp)), tmp, value) + if err != nil { + return err + } + script.Actions = actions + script.UntilLimit = 20 + return nil +} + +func ActionsListDecode(script *YAMLScript, actions ActionsList, tmp tmpActions, parentNode *yaml.Node) (ActionsList, error) { + main_checker := map[string]bool{ + "name": true, + "loop": true, + "loop_var": true, + "vars": true, + "until": true, + "when": true, + "with_items": true, + } + + for i := range tmp { + var name *Field + var nameVal, loopVar string + var with_items []any + var until, when []*exporterTemplate + var vars map[string]any + var err error + checker := main_checker + skip_checker := false + cur_act := tmp[i] + + // parse name + if raw, ok := cur_act["name"]; ok { + nameVal = raw.Value + name, err = NewField(nameVal, nil) + if err != nil { + return nil, fmt.Errorf("name for action invalid %q: %s", raw.Value, err) + } + } + // parse vars + if raw, ok := cur_act["vars"]; ok { + if raw.Tag == "!!map" { + if err := raw.Decode(&vars); err != nil { + return nil, err + } + } + } + + // parse with_items + if raw, ok := cur_act["with_items"]; ok { + listElmt, err := build_WithItems(raw) + if err != nil { + return nil, err + } + with_items = listElmt + } else if raw, ok := cur_act["loop"]; ok { + listElmt, err := build_WithItems(raw) + if err != nil { + return nil, err + } + with_items = listElmt + } + if with_items != nil { + // parse loop_var + if raw, ok := cur_act["loop_var"]; ok { + loopVar = raw.Value + } + } + + // parse when + if raw, ok := cur_act["when"]; ok { + cond, err := build_Cond(script, raw) + if err != nil { + return nil, err + } + when = cond + } + + // parse when + if raw, ok := cur_act["until"]; ok { + cond, err := build_Cond(script, raw) + if err != nil { + return nil, err + } + until = cond + } + + // *********************************************** + // *********************************************** + // ** parse the action keyword + // *********************************************** + // *********************************************** + + // *********************************************** + // debug + if raw, ok := cur_act["debug"]; ok { + checker["debug"] = true + da := &DebugActionConfig{} + if err := raw.Decode(da); err != nil { + err = fmt.Errorf("%v: for action '%s'", err, name.String()) + return nil, err + } + a := &DebugAction{} + a.Debug = da + if err = a.setBasicElement(name, vars, with_items, loopVar, when, until); err != nil { + return nil, err + } + actions = append(actions, a) + } else if raw, ok := cur_act["set_fact"]; ok { + // *********************************************** + // set_fact + checker["set_fact"] = true + sfa := make(map[string]interface{}) + if err := raw.Decode(&sfa); err != nil { + err = fmt.Errorf("%v: for action '%s'", err, name.String()) + return nil, err + } + a := &SetFactAction{} + if len(sfa) > 0 { + a.SetFact = sfa + a.setFact = make([][]any, len(a.SetFact)) + + idx := 0 + for key, val := range a.SetFact { + // Check required fields + a.setFact[idx] = make([]any, 2) + if new_map, err := buildFields(key, val); err != nil { + return nil, err + } else { + for key, val := range new_map { + a.setFact[idx][0] = key + a.setFact[idx][1] = val + } + } + idx++ + } + } + if err = a.setBasicElement(name, vars, with_items, loopVar, when, until); err != nil { + return nil, err + } + actions = append(actions, a) + } else if raw, ok := cur_act["query"]; ok { + // *********************************************** + // url/query + checker["query"] = true + qa := &QueryActionConfig{} + if err := raw.Decode(qa); err != nil { + err = fmt.Errorf("%v: for action '%s'", err, name.String()) + return nil, err + } + a := &QueryAction{} + a.Query = qa + if err = a.setBasicElement(name, vars, with_items, loopVar, when, until); err != nil { + return nil, err + } + actions = append(actions, a) + } else if raw, ok := cur_act["play_script"]; ok { + // *********************************************** + // play_script + checker["play_script"] = true + script_name := "" + if err := raw.Decode(&script_name); err != nil { + err = fmt.Errorf("%v: for action '%s'", err, name.String()) + return nil, err + } + a := &PlayScriptAction{ + PlayScriptActionName: script_name, + } + if err = a.setBasicElement(name, vars, with_items, loopVar, when, until); err != nil { + return nil, err + } + actions = append(actions, a) + } else if _, ok := cur_act["metric_name"]; ok { + // *********************************************** + // play_script + checker["metric_name"] = true + mc := &MetricConfig{} + if err := parentNode.Content[i].Decode(mc); err != nil { + return nil, err + } + // MAYBE mc.Name should be a Field so that the name could be a template !! + a := &MetricAction{ + mc: mc, + } + name, err = NewField(mc.Name, nil) + if err != nil { + return nil, fmt.Errorf("name for action invalid %q: %s", mc.Name, err) + } + + if err = a.setBasicElement(name, vars, with_items, loopVar, when, until); err != nil { + return nil, err + } + actions = append(actions, a) + skip_checker = true + } else if raw, ok := cur_act["actions"]; ok { + // *********************************************** + // actions + checker["actions"] = true + if raw.Tag == "!!seq" { + var tmp_sub tmpActions + if err := raw.Decode(&tmp_sub); err != nil { + err = fmt.Errorf("%v: for action '%s'", err, name.String()) + return nil, err + } + + acta, err := ActionsListDecode(script, make(ActionsList, 0, len(tmp_sub)), tmp_sub, &raw) + if err != nil { + return nil, err + } + a := &ActionsAction{} + a.Actions = acta + if err = a.setBasicElement(name, vars, with_items, loopVar, when, until); err != nil { + return nil, err + } + actions = append(actions, a) + } + } else if raw, ok := cur_act["metrics"]; ok { + // *********************************************** + // metric name + checker["metrics"] = true + checker["scope"] = true + checker["metric_prefix"] = true + + if raw.Tag == "!!seq" { + var tmp_sub tmpActions + if err := raw.Decode(&tmp_sub); err != nil { + err = fmt.Errorf("%v: for action '%s'", err, name.String()) + return nil, err + } + + acta, err := ActionsListDecode(script, make(ActionsList, 0, len(tmp_sub)), tmp_sub, &raw) + if err != nil { + return nil, err + } + a := &MetricsAction{} + a.Actions = acta + if err = a.setBasicElement(name, vars, with_items, loopVar, when, until); err != nil { + return nil, err + } + actions = append(actions, a) + + //*** append current metrics list to the global list + script.metricsActions = append(script.metricsActions, a) + + mcl := make([]*MetricConfig, len(raw.Content)) + idx := 0 + for _, act := range acta { + if act.Type() == metric_action { + mcl[idx] = act.GetMetric() + idx++ + } + } + a.Metrics = mcl + + if raw, ok := cur_act["scope"]; ok { + if raw.Tag == "!!str" { + scope := "" + if err := raw.Decode(&scope); err != nil { + return nil, err + } + a.Scope = scope + } + // # propagate scope == "none" to all metrics + if a.Scope == "none" { + for _, mcl := range a.Metrics { + if mcl.Scope == "" { + mcl.Scope = a.Scope + } + } + } + } + + if raw, ok := cur_act["metric_prefix"]; ok { + if raw.Tag == "!!str" { + metric_prefix := "" + if err := raw.Decode(&metric_prefix); err != nil { + return nil, err + } + a.MetricPrefix = metric_prefix + } + } + } + } else { + // we haven't found any label in action that we should understand + // display first key of the map and context (line, column) + for key, val := range cur_act { + err := fmt.Errorf("unknown action type: '%s': '%v', arround line %d column: %d", key, val.Value, val.Line, val.Column) + return nil, err + } + // *********************************************** + // return nil, fmt.Errorf("unknown action type: +%v", cur_act) + } + + if !skip_checker { + for name, raw := range cur_act { + if _, ok := checker[name]; !ok { + return nil, fmt.Errorf("unknown attribute '%s' for action '%v' on line: %d column: %d", name, reflect.TypeOf(actions[len(actions)-1]), raw.Line, raw.Column) + + } + } + } + } + return actions, nil +} + +// ***************************************************************************************