From 241061c767d37bfa4120173e134f27b5fb333bc6 Mon Sep 17 00:00:00 2001 From: raghu0891 Date: Thu, 12 Dec 2024 11:30:43 +0100 Subject: [PATCH] base 3 commit --- .github/workflows/observability.yml | 4 +- observability-lib/Makefile | 6 +- .../dashboards/atlas-don/component.go | 3 + .../dashboards/atlas-don/component_test.go | 47 +- .../dashboards/atlas-don/platform.go | 1 + .../dashboards/atlas-don/test-output.json | 130 +++-- .../dashboards/capabilities/component_test.go | 44 +- .../dashboards/capabilities/test-output.json | 21 +- .../core-node-components/component.go | 3 + .../core-node-components/component_test.go | 47 +- .../core-node-components/platform.go | 1 + .../core-node-components/test-output.json | 29 +- .../dashboards/core-node/component.go | 422 ++++++++------ .../dashboards/core-node/component_test.go | 47 +- .../dashboards/core-node/platform.go | 1 + .../dashboards/core-node/test-output.json | 530 +++++++++--------- .../k8s-resources/component_test.go | 43 +- .../dashboards/k8s-resources/test-output.json | 59 +- .../dashboards/nop-ocr/component_test.go | 44 +- .../dashboards/nop-ocr/test-output.json | 41 +- observability-lib/go.mod | 10 +- observability-lib/go.sum | 80 +-- observability-lib/grafana/alerts.go | 74 +-- observability-lib/grafana/builder.go | 9 +- observability-lib/grafana/dashboard.go | 6 +- observability-lib/grafana/dashboard_test.go | 53 +- observability-lib/grafana/panels.go | 160 ++++-- observability-lib/grafana/variables.go | 103 +++- pkg/assets/link_test.go | 38 +- pkg/codec/byte_string_modifier.go | 256 +++++++++ pkg/codec/byte_string_modifier_test.go | 341 +++++++++++ pkg/codec/config.go | 23 + pkg/codec/encodings/type_codec_test.go | 7 + pkg/codec/modifier_base.go | 20 +- pkg/custmsg/custom_message.go | 83 +++ pkg/custmsg/custom_message_test.go | 16 + .../contractreader/helper_test.go | 5 + pkg/types/ccipocr3/rmn_types.go | 4 +- .../codec_interface_fuzz_tests.go | 1 + .../interfacetests/codec_interface_tests.go | 4 + pkg/types/interfacetests/utils.go | 5 + pkg/types/llo/types.go | 19 +- pkg/types/provider_llo.go | 12 +- pkg/workflows/exec/interpolation.go | 27 +- pkg/workflows/exec/interpolation_test.go | 44 ++ 45 files changed, 2113 insertions(+), 810 deletions(-) create mode 100644 pkg/codec/byte_string_modifier.go create mode 100644 pkg/codec/byte_string_modifier_test.go create mode 100644 pkg/custmsg/custom_message.go create mode 100644 pkg/custmsg/custom_message_test.go diff --git a/.github/workflows/observability.yml b/.github/workflows/observability.yml index 7c9653f..f701241 100644 --- a/.github/workflows/observability.yml +++ b/.github/workflows/observability.yml @@ -21,7 +21,7 @@ jobs: go-version-file: "go.mod" - name: Build - run: go build -v ./... + run: make build - name: Unit Tests - run: go test -v ./... + run: make test diff --git a/observability-lib/Makefile b/observability-lib/Makefile index ad7795d..be7def0 100644 --- a/observability-lib/Makefile +++ b/observability-lib/Makefile @@ -12,4 +12,8 @@ lint: .PHONY: test test: - go test -v ./... \ No newline at end of file + go test ./... + +.PHONY: update +update: + go test ./dashboards/... -update=1 diff --git a/observability-lib/dashboards/atlas-don/component.go b/observability-lib/dashboards/atlas-don/component.go index 878a0d4..a7f09fe 100644 --- a/observability-lib/dashboards/atlas-don/component.go +++ b/observability-lib/dashboards/atlas-don/component.go @@ -31,6 +31,9 @@ func NewDashboard(props *Props) (*grafana.Dashboard, error) { } props.platformOpts = platformPanelOpts(props.OCRVersion) + if props.Tested { + props.platformOpts.LabelQuery = "" + } builder := grafana.NewBuilder(&grafana.BuilderOptions{ Name: props.Name, diff --git a/observability-lib/dashboards/atlas-don/component_test.go b/observability-lib/dashboards/atlas-don/component_test.go index 6961c13..c6fe59e 100644 --- a/observability-lib/dashboards/atlas-don/component_test.go +++ b/observability-lib/dashboards/atlas-don/component_test.go @@ -1,6 +1,7 @@ package atlasdon_test import ( + "flag" "os" "testing" @@ -11,12 +12,54 @@ import ( atlasdon "github.com/goplugin/plugin-common/observability-lib/dashboards/atlas-don" ) +var update = flag.Bool("update", false, "update golden test files") + +const fileOutput = "test-output.json" + +func TestGenerateFile(t *testing.T) { + if *update == false { + t.Skip("skipping test") + } + + testDashboard, err := atlasdon.NewDashboard(&atlasdon.Props{ + Name: "DON OCR Dashboard", + MetricsDataSource: grafana.NewDataSource("Prometheus", "1"), + OCRVersion: "ocr2", + Tested: true, + }) + if err != nil { + t.Errorf("Error creating dashboard: %v", err) + } + json, errJSON := testDashboard.GenerateJSON() + if errJSON != nil { + t.Errorf("Error generating JSON: %v", errJSON) + } + if _, errExists := os.Stat(fileOutput); errExists == nil { + errRemove := os.Remove(fileOutput) + if errRemove != nil { + t.Errorf("Error removing file: %v", errRemove) + } + } + file, errFile := os.Create(fileOutput) + if errFile != nil { + panic(errFile) + } + writeString, err := file.WriteString(string(json)) + if err != nil { + t.Errorf("Error writing to file: %v", writeString) + } + t.Cleanup(func() { + file.Close() + }) +} + func TestNewDashboard(t *testing.T) { t.Run("NewDashboard creates a dashboard", func(t *testing.T) { testDashboard, err := atlasdon.NewDashboard(&atlasdon.Props{ Name: "DON OCR Dashboard", MetricsDataSource: grafana.NewDataSource("Prometheus", "1"), OCRVersion: "ocr2", + Tested: true, }) if err != nil { t.Errorf("Error creating dashboard: %v", err) @@ -28,11 +71,11 @@ func TestNewDashboard(t *testing.T) { t.Errorf("Error generating JSON: %v", errJSON) } - jsonCompared, errCompared := os.ReadFile("test-output.json") + jsonCompared, errCompared := os.ReadFile(fileOutput) if errCompared != nil { t.Errorf("Error reading file: %v", errCompared) } - require.ElementsMatch(t, jsonCompared, json) + require.JSONEq(t, string(jsonCompared), string(json)) }) } diff --git a/observability-lib/dashboards/atlas-don/platform.go b/observability-lib/dashboards/atlas-don/platform.go index 3a25bd5..f58c855 100644 --- a/observability-lib/dashboards/atlas-don/platform.go +++ b/observability-lib/dashboards/atlas-don/platform.go @@ -16,6 +16,7 @@ type Props struct { MetricsDataSource *grafana.DataSource // MetricsDataSource is the datasource for querying metrics OCRVersion string // OCRVersion is the version of the OCR (ocr, ocr2, ocr3) platformOpts platformOpts + Tested bool } // PlatformPanelOpts generate different queries depending on params diff --git a/observability-lib/dashboards/atlas-don/test-output.json b/observability-lib/dashboards/atlas-don/test-output.json index 3f28f58..b3cfa12 100644 --- a/observability-lib/dashboards/atlas-don/test-output.json +++ b/observability-lib/dashboards/atlas-don/test-output.json @@ -6,6 +6,7 @@ "ocr2" ], "timezone": "browser", + "editable": true, "graphTooltip": 0, "time": { "from": "now-30m", @@ -13,7 +14,7 @@ }, "fiscalYearStartMonth": 0, "refresh": "30s", - "schemaVersion": 0, + "schemaVersion": 39, "panels": [ { "type": "row", @@ -30,10 +31,10 @@ }, { "type": "stat", - "id": 0, + "id": 1, "targets": [ { - "expr": "bool:ocr2_telemetry_down{contract=~\"${contract}\", feed_id=~\"${feed_id}\", namespace=\"otpe2\", job=~\"${job}\", } == 1", + "expr": "bool:ocr2_telemetry_down{} == 1", "format": "", "legendFormat": "{{job}} | {{report_type}}", "refId": "" @@ -57,6 +58,7 @@ "justifyMode": "auto", "textMode": "name", "wideLayout": true, + "showPercentChange": false, "reduceOptions": { "calcs": [ "last" @@ -66,7 +68,7 @@ "titleSize": 10, "valueSize": 18 }, - "showPercentChange": false, + "percentChangeColorMode": "standard", "orientation": "horizontal" }, "fieldConfig": { @@ -97,7 +99,7 @@ }, { "type": "stat", - "id": 1, + "id": 2, "targets": [ { "expr": "bool:ocr2_oracle_telemetry_down_except_telemetry_down{job=~\"${job}\", oracle!=\"csa_unknown\"} == 1", @@ -133,6 +135,7 @@ "justifyMode": "auto", "textMode": "name", "wideLayout": true, + "showPercentChange": false, "reduceOptions": { "calcs": [ "last" @@ -142,7 +145,7 @@ "titleSize": 10, "valueSize": 18 }, - "showPercentChange": false, + "percentChangeColorMode": "standard", "orientation": "horizontal" }, "fieldConfig": { @@ -173,7 +176,7 @@ }, { "type": "stat", - "id": 2, + "id": 3, "targets": [ { "expr": "bool:ocr2_feed_reporting_failure_except_feed_telemetry_down{job=~\"${job}\", oracle!=\"csa_unknown\"} == 1", @@ -200,6 +203,7 @@ "justifyMode": "auto", "textMode": "name", "wideLayout": true, + "showPercentChange": false, "reduceOptions": { "calcs": [ "last" @@ -209,7 +213,7 @@ "titleSize": 10, "valueSize": 18 }, - "showPercentChange": false, + "percentChangeColorMode": "standard", "orientation": "horizontal" }, "fieldConfig": { @@ -240,7 +244,7 @@ }, { "type": "stat", - "id": 3, + "id": 4, "targets": [ { "expr": "bool:ocr2_feed_telemetry_down_except_telemetry_down{job=~\"${job}\"} == 1", @@ -267,6 +271,7 @@ "justifyMode": "auto", "textMode": "name", "wideLayout": true, + "showPercentChange": false, "reduceOptions": { "calcs": [ "last" @@ -276,7 +281,7 @@ "titleSize": 10, "valueSize": 18 }, - "showPercentChange": false, + "percentChangeColorMode": "standard", "orientation": "horizontal" }, "fieldConfig": { @@ -307,7 +312,7 @@ }, { "type": "stat", - "id": 4, + "id": 5, "targets": [ { "expr": "bool:ocr2_oracle_blind_except_telemetry_down{job=~\"${job}\"} == 1", @@ -343,6 +348,7 @@ "justifyMode": "auto", "textMode": "name", "wideLayout": true, + "showPercentChange": false, "reduceOptions": { "calcs": [ "last" @@ -352,7 +358,7 @@ "titleSize": 10, "valueSize": 18 }, - "showPercentChange": false, + "percentChangeColorMode": "standard", "orientation": "horizontal" }, "fieldConfig": { @@ -383,7 +389,7 @@ }, { "type": "stat", - "id": 5, + "id": 6, "targets": [ { "expr": "bool:ocr2_oracle_feed_no_observations_except_oracle_blind_except_feed_reporting_failure_except_feed_telemetry_down{job=~\"${job}\"} == 1", @@ -419,6 +425,7 @@ "justifyMode": "auto", "textMode": "name", "wideLayout": true, + "showPercentChange": false, "reduceOptions": { "calcs": [ "last" @@ -428,7 +435,7 @@ "titleSize": 10, "valueSize": 18 }, - "showPercentChange": false, + "percentChangeColorMode": "standard", "orientation": "horizontal" }, "fieldConfig": { @@ -472,10 +479,10 @@ }, { "type": "stat", - "id": 6, + "id": 7, "targets": [ { - "expr": "sum(ocr2_contract_oracle_active{contract=~\"${contract}\", feed_id=~\"${feed_id}\", namespace=\"otpe2\", job=~\"${job}\", }) by (contract, oracle)", + "expr": "sum(ocr2_contract_oracle_active{}) by (contract, oracle)", "format": "", "legendFormat": "{{oracle}}", "refId": "" @@ -508,6 +515,7 @@ "justifyMode": "auto", "textMode": "name", "wideLayout": true, + "showPercentChange": false, "reduceOptions": { "calcs": [ "last" @@ -517,7 +525,7 @@ "titleSize": 10, "valueSize": 18 }, - "showPercentChange": false, + "percentChangeColorMode": "standard", "orientation": "horizontal" }, "fieldConfig": { @@ -561,22 +569,22 @@ }, { "type": "timeseries", - "id": 7, + "id": 8, "targets": [ { - "expr": "ocr2_contract_config_n{contract=~\"${contract}\", feed_id=~\"${feed_id}\", namespace=\"otpe2\", job=~\"${job}\", }", + "expr": "ocr2_contract_config_n{}", "format": "", "legendFormat": "{{feed_id}}", "refId": "" }, { - "expr": "ocr2_contract_config_r_max{contract=~\"${contract}\", feed_id=~\"${feed_id}\", namespace=\"otpe2\", job=~\"${job}\", }", + "expr": "ocr2_contract_config_r_max{}", "format": "", "legendFormat": "Max nodes", "refId": "" }, { - "expr": "avg(2 * ocr2_contract_config_f{contract=~\"${contract}\", feed_id=~\"${feed_id}\", namespace=\"otpe2\", job=~\"${job}\", } + 1)", + "expr": "avg(2 * ocr2_contract_config_f{} + 1)", "format": "", "legendFormat": "Min nodes", "refId": "" @@ -637,7 +645,7 @@ }, { "type": "timeseries", - "id": 8, + "id": 9, "targets": [ { "expr": "sum by (sender, receiver) (increase(ocr2_telemetry_p2p_received_total{job=~\"${job}\"}[5m]))", @@ -687,7 +695,7 @@ }, { "type": "timeseries", - "id": 9, + "id": 10, "targets": [ { "expr": "sum by (sender, receiver) (rate(ocr2_telemetry_p2p_received_total{job=~\"${job}\"}[5m]))", @@ -737,10 +745,10 @@ }, { "type": "timeseries", - "id": 10, + "id": 11, "targets": [ { - "expr": "ocr2_telemetry_observation{contract=~\"${contract}\", feed_id=~\"${feed_id}\", namespace=\"otpe2\", job=~\"${job}\", }", + "expr": "ocr2_telemetry_observation{}", "format": "", "legendFormat": "{{oracle}}", "refId": "" @@ -787,10 +795,10 @@ }, { "type": "timeseries", - "id": 11, + "id": 12, "targets": [ { - "expr": "rate(ocr2_telemetry_message_observe_total{contract=~\"${contract}\", feed_id=~\"${feed_id}\", namespace=\"otpe2\", job=~\"${job}\", }[5m])", + "expr": "rate(ocr2_telemetry_message_observe_total{}[5m])", "format": "", "legendFormat": "{{oracle}}", "refId": "" @@ -850,10 +858,10 @@ }, { "type": "timeseries", - "id": 12, + "id": 13, "targets": [ { - "expr": "ocr2_telemetry_feed_agreed_epoch{contract=~\"${contract}\", feed_id=~\"${feed_id}\", namespace=\"otpe2\", job=~\"${job}\", }", + "expr": "ocr2_telemetry_feed_agreed_epoch{}", "format": "", "legendFormat": "{{feed_id}}", "refId": "" @@ -900,10 +908,10 @@ }, { "type": "timeseries", - "id": 13, + "id": 14, "targets": [ { - "expr": "ocr2_telemetry_epoch_round{contract=~\"${contract}\", feed_id=~\"${feed_id}\", namespace=\"otpe2\", job=~\"${job}\", }", + "expr": "ocr2_telemetry_epoch_round{}", "format": "", "legendFormat": "{{oracle}}", "refId": "" @@ -950,10 +958,10 @@ }, { "type": "timeseries", - "id": 14, + "id": 15, "targets": [ { - "expr": "rate(ocr2_telemetry_round_started_total{contract=~\"${contract}\", feed_id=~\"${feed_id}\", namespace=\"otpe2\", job=~\"${job}\", }[1m])", + "expr": "rate(ocr2_telemetry_round_started_total{}[1m])", "format": "", "legendFormat": "{{oracle}}", "refId": "" @@ -1000,10 +1008,10 @@ }, { "type": "timeseries", - "id": 15, + "id": 16, "targets": [ { - "expr": "rate(ocr2_telemetry_ingested_total{contract=~\"${contract}\", feed_id=~\"${feed_id}\", namespace=\"otpe2\", job=~\"${job}\", }[1m])", + "expr": "rate(ocr2_telemetry_ingested_total{}[1m])", "format": "", "legendFormat": "{{oracle}}", "refId": "" @@ -1063,10 +1071,10 @@ }, { "type": "stat", - "id": 16, + "id": 17, "targets": [ { - "expr": "ocr2_contract_config_alpha{contract=~\"${contract}\", feed_id=~\"${feed_id}\", namespace=\"otpe2\", job=~\"${job}\", }", + "expr": "ocr2_contract_config_alpha{}", "format": "", "legendFormat": "{{contract}}", "refId": "" @@ -1090,6 +1098,7 @@ "justifyMode": "auto", "textMode": "value_and_name", "wideLayout": true, + "showPercentChange": false, "reduceOptions": { "calcs": [ "last" @@ -1099,7 +1108,7 @@ "titleSize": 10, "valueSize": 18 }, - "showPercentChange": false, + "percentChangeColorMode": "standard", "orientation": "horizontal" }, "fieldConfig": { @@ -1113,10 +1122,10 @@ }, { "type": "stat", - "id": 17, + "id": 18, "targets": [ { - "expr": "ocr2_contract_config_delta_c_seconds{contract=~\"${contract}\", feed_id=~\"${feed_id}\", namespace=\"otpe2\", job=~\"${job}\", }", + "expr": "ocr2_contract_config_delta_c_seconds{}", "format": "", "legendFormat": "{{contract}}", "refId": "" @@ -1140,6 +1149,7 @@ "justifyMode": "auto", "textMode": "value_and_name", "wideLayout": true, + "showPercentChange": false, "reduceOptions": { "calcs": [ "last" @@ -1149,7 +1159,7 @@ "titleSize": 10, "valueSize": 18 }, - "showPercentChange": false, + "percentChangeColorMode": "standard", "orientation": "horizontal" }, "fieldConfig": { @@ -1163,10 +1173,10 @@ }, { "type": "stat", - "id": 18, + "id": 19, "targets": [ { - "expr": "ocr2_contract_config_delta_grace_seconds{contract=~\"${contract}\", feed_id=~\"${feed_id}\", namespace=\"otpe2\", job=~\"${job}\", }", + "expr": "ocr2_contract_config_delta_grace_seconds{}", "format": "", "legendFormat": "{{contract}}", "refId": "" @@ -1190,6 +1200,7 @@ "justifyMode": "auto", "textMode": "value_and_name", "wideLayout": true, + "showPercentChange": false, "reduceOptions": { "calcs": [ "last" @@ -1199,7 +1210,7 @@ "titleSize": 10, "valueSize": 18 }, - "showPercentChange": false, + "percentChangeColorMode": "standard", "orientation": "horizontal" }, "fieldConfig": { @@ -1213,10 +1224,10 @@ }, { "type": "stat", - "id": 19, + "id": 20, "targets": [ { - "expr": "ocr2_contract_config_delta_progress_seconds{contract=~\"${contract}\", feed_id=~\"${feed_id}\", namespace=\"otpe2\", job=~\"${job}\", }", + "expr": "ocr2_contract_config_delta_progress_seconds{}", "format": "", "legendFormat": "{{contract}}", "refId": "" @@ -1240,6 +1251,7 @@ "justifyMode": "auto", "textMode": "value_and_name", "wideLayout": true, + "showPercentChange": false, "reduceOptions": { "calcs": [ "last" @@ -1249,7 +1261,7 @@ "titleSize": 10, "valueSize": 18 }, - "showPercentChange": false, + "percentChangeColorMode": "standard", "orientation": "horizontal" }, "fieldConfig": { @@ -1263,10 +1275,10 @@ }, { "type": "stat", - "id": 20, + "id": 21, "targets": [ { - "expr": "ocr2_contract_config_delta_resend_seconds{contract=~\"${contract}\", feed_id=~\"${feed_id}\", namespace=\"otpe2\", job=~\"${job}\", }", + "expr": "ocr2_contract_config_delta_resend_seconds{}", "format": "", "legendFormat": "{{contract}}", "refId": "" @@ -1290,6 +1302,7 @@ "justifyMode": "auto", "textMode": "value_and_name", "wideLayout": true, + "showPercentChange": false, "reduceOptions": { "calcs": [ "last" @@ -1299,7 +1312,7 @@ "titleSize": 10, "valueSize": 18 }, - "showPercentChange": false, + "percentChangeColorMode": "standard", "orientation": "horizontal" }, "fieldConfig": { @@ -1313,10 +1326,10 @@ }, { "type": "stat", - "id": 21, + "id": 22, "targets": [ { - "expr": "ocr2_contract_config_delta_round_seconds{contract=~\"${contract}\", feed_id=~\"${feed_id}\", namespace=\"otpe2\", job=~\"${job}\", }", + "expr": "ocr2_contract_config_delta_round_seconds{}", "format": "", "legendFormat": "{{contract}}", "refId": "" @@ -1340,6 +1353,7 @@ "justifyMode": "auto", "textMode": "value_and_name", "wideLayout": true, + "showPercentChange": false, "reduceOptions": { "calcs": [ "last" @@ -1349,7 +1363,7 @@ "titleSize": 10, "valueSize": 18 }, - "showPercentChange": false, + "percentChangeColorMode": "standard", "orientation": "horizontal" }, "fieldConfig": { @@ -1363,10 +1377,10 @@ }, { "type": "stat", - "id": 22, + "id": 23, "targets": [ { - "expr": "ocr2_contract_config_delta_stage_seconds{contract=~\"${contract}\", feed_id=~\"${feed_id}\", namespace=\"otpe2\", job=~\"${job}\", }", + "expr": "ocr2_contract_config_delta_stage_seconds{}", "format": "", "legendFormat": "{{contract}}", "refId": "" @@ -1390,6 +1404,7 @@ "justifyMode": "auto", "textMode": "value_and_name", "wideLayout": true, + "showPercentChange": false, "reduceOptions": { "calcs": [ "last" @@ -1399,7 +1414,7 @@ "titleSize": 10, "valueSize": 18 }, - "showPercentChange": false, + "percentChangeColorMode": "standard", "orientation": "horizontal" }, "fieldConfig": { @@ -1418,6 +1433,7 @@ "type": "query", "name": "job", "label": "Job", + "description": "", "query": "label_values(up{namespace=\"otpe2\"}, job)", "datasource": { "uid": "Prometheus" @@ -1438,6 +1454,7 @@ "type": "query", "name": "contract", "label": "Contract", + "description": "", "query": "label_values(ocr2_contract_config_f{job=\"$job\"}, contract)", "datasource": { "uid": "Prometheus" @@ -1458,6 +1475,7 @@ "type": "query", "name": "feed_id", "label": "Feed ID", + "description": "", "query": "label_values(ocr2_contract_config_f{job=\"$job\", contract=\"$contract\"}, feed_id)", "datasource": { "uid": "Prometheus" diff --git a/observability-lib/dashboards/capabilities/component_test.go b/observability-lib/dashboards/capabilities/component_test.go index 973695c..0fa3148 100644 --- a/observability-lib/dashboards/capabilities/component_test.go +++ b/observability-lib/dashboards/capabilities/component_test.go @@ -1,6 +1,7 @@ package capabilities_test import ( + "flag" "os" "testing" @@ -11,6 +12,45 @@ import ( "github.com/goplugin/plugin-common/observability-lib/dashboards/capabilities" ) +var update = flag.Bool("update", false, "update golden test files") + +const fileOutput = "test-output.json" + +func TestGenerateFile(t *testing.T) { + if *update == false { + t.Skip("skipping test") + } + + testDashboard, err := capabilities.NewDashboard(&capabilities.Props{ + Name: "Capabilities Dashboard", + MetricsDataSource: grafana.NewDataSource("Prometheus", ""), + }) + if err != nil { + t.Errorf("Error creating dashboard: %v", err) + } + json, errJSON := testDashboard.GenerateJSON() + if errJSON != nil { + t.Errorf("Error generating JSON: %v", errJSON) + } + if _, errExists := os.Stat(fileOutput); errExists == nil { + errRemove := os.Remove(fileOutput) + if errRemove != nil { + t.Errorf("Error removing file: %v", errRemove) + } + } + file, errFile := os.Create(fileOutput) + if errFile != nil { + panic(errFile) + } + writeString, err := file.WriteString(string(json)) + if err != nil { + t.Errorf("Error writing to file: %v", writeString) + } + t.Cleanup(func() { + file.Close() + }) +} + func TestNewDashboard(t *testing.T) { t.Run("NewDashboard creates a dashboard", func(t *testing.T) { testDashboard, err := capabilities.NewDashboard(&capabilities.Props{ @@ -27,11 +67,11 @@ func TestNewDashboard(t *testing.T) { t.Errorf("Error generating JSON: %v", errJSON) } - jsonCompared, errCompared := os.ReadFile("test-output.json") + jsonCompared, errCompared := os.ReadFile(fileOutput) if errCompared != nil { t.Errorf("Error reading file: %v", errCompared) } - require.ElementsMatch(t, jsonCompared, json) + require.JSONEq(t, string(jsonCompared), string(json)) }) } diff --git a/observability-lib/dashboards/capabilities/test-output.json b/observability-lib/dashboards/capabilities/test-output.json index 192b6fe..ee8828d 100644 --- a/observability-lib/dashboards/capabilities/test-output.json +++ b/observability-lib/dashboards/capabilities/test-output.json @@ -5,6 +5,7 @@ "Capabilities" ], "timezone": "browser", + "editable": true, "graphTooltip": 0, "time": { "from": "now-7d", @@ -12,7 +13,7 @@ }, "fiscalYearStartMonth": 0, "refresh": "30s", - "schemaVersion": 0, + "schemaVersion": 39, "panels": [ { "type": "row", @@ -29,7 +30,7 @@ }, { "type": "timeseries", - "id": 0, + "id": 1, "targets": [ { "expr": "capability_execution_time_ms", @@ -79,7 +80,7 @@ }, { "type": "timeseries", - "id": 1, + "id": 2, "targets": [ { "expr": "capability_runs_count", @@ -129,7 +130,7 @@ }, { "type": "timeseries", - "id": 2, + "id": 3, "targets": [ { "expr": "capability_runs_fault_count", @@ -179,7 +180,7 @@ }, { "type": "timeseries", - "id": 3, + "id": 4, "targets": [ { "expr": "capability_runs_invalid_count", @@ -229,7 +230,7 @@ }, { "type": "timeseries", - "id": 4, + "id": 5, "targets": [ { "expr": "capability_runs_unauthorized_count", @@ -279,7 +280,7 @@ }, { "type": "timeseries", - "id": 5, + "id": 6, "targets": [ { "expr": "capability_runs_no_resource_count", @@ -334,6 +335,7 @@ "type": "query", "name": "env", "label": "Environment", + "description": "", "query": "label_values(up, env)", "datasource": { "uid": "Prometheus" @@ -354,6 +356,7 @@ "type": "query", "name": "cluster", "label": "Cluster", + "description": "", "query": "label_values(up{env=\"$env\"}, cluster)", "datasource": { "uid": "Prometheus" @@ -374,6 +377,7 @@ "type": "query", "name": "namespace", "label": "Namespace", + "description": "", "query": "label_values(up{env=\"$env\", cluster=\"$cluster\"}, namespace)", "datasource": { "uid": "Prometheus" @@ -394,6 +398,7 @@ "type": "query", "name": "job", "label": "Job", + "description": "", "query": "label_values(up{env=\"$env\", cluster=\"$cluster\", namespace=\"$namespace\"}, job)", "datasource": { "uid": "Prometheus" @@ -414,6 +419,7 @@ "type": "query", "name": "pod", "label": "Pod", + "description": "", "query": "label_values(up{env=\"$env\", cluster=\"$cluster\", namespace=\"$namespace\", job=\"$job\"}, pod)", "datasource": { "uid": "Prometheus" @@ -434,6 +440,7 @@ "type": "query", "name": "capability", "label": "Capability", + "description": "", "query": "label_values(up{env=\"$env\", cluster=\"$cluster\", namespace=\"$namespace\", job=\"$job\"}, pod)", "datasource": { "uid": "Prometheus" diff --git a/observability-lib/dashboards/core-node-components/component.go b/observability-lib/dashboards/core-node-components/component.go index 43d4c46..a75d460 100644 --- a/observability-lib/dashboards/core-node-components/component.go +++ b/observability-lib/dashboards/core-node-components/component.go @@ -15,6 +15,9 @@ func NewDashboard(props *Props) (*grafana.Dashboard, error) { } props.platformOpts = platformPanelOpts() + if props.Tested { + props.platformOpts.LabelQuery = "" + } builder := grafana.NewBuilder(&grafana.BuilderOptions{ Name: props.Name, diff --git a/observability-lib/dashboards/core-node-components/component_test.go b/observability-lib/dashboards/core-node-components/component_test.go index 3776102..b33b7c7 100644 --- a/observability-lib/dashboards/core-node-components/component_test.go +++ b/observability-lib/dashboards/core-node-components/component_test.go @@ -1,6 +1,7 @@ package corenodecomponents_test import ( + "flag" "os" "testing" @@ -11,12 +12,54 @@ import ( corenodecomponents "github.com/goplugin/plugin-common/observability-lib/dashboards/core-node-components" ) +var update = flag.Bool("update", false, "update golden test files") + +const fileOutput = "test-output.json" + +func TestGenerateFile(t *testing.T) { + if *update == false { + t.Skip("skipping test") + } + + testDashboard, err := corenodecomponents.NewDashboard(&corenodecomponents.Props{ + Name: "Core Node Components Dashboard", + MetricsDataSource: grafana.NewDataSource("Prometheus", ""), + LogsDataSource: grafana.NewDataSource("Loki", ""), + Tested: true, + }) + if err != nil { + t.Errorf("Error creating dashboard: %v", err) + } + json, errJSON := testDashboard.GenerateJSON() + if errJSON != nil { + t.Errorf("Error generating JSON: %v", errJSON) + } + if _, errExists := os.Stat(fileOutput); errExists == nil { + errRemove := os.Remove(fileOutput) + if errRemove != nil { + t.Errorf("Error removing file: %v", errRemove) + } + } + file, errFile := os.Create(fileOutput) + if errFile != nil { + panic(errFile) + } + writeString, err := file.WriteString(string(json)) + if err != nil { + t.Errorf("Error writing to file: %v", writeString) + } + t.Cleanup(func() { + file.Close() + }) +} + func TestNewDashboard(t *testing.T) { t.Run("NewDashboard creates a dashboard", func(t *testing.T) { testDashboard, err := corenodecomponents.NewDashboard(&corenodecomponents.Props{ Name: "Core Node Components Dashboard", MetricsDataSource: grafana.NewDataSource("Prometheus", ""), LogsDataSource: grafana.NewDataSource("Loki", ""), + Tested: true, }) if err != nil { t.Errorf("Error creating dashboard: %v", err) @@ -28,11 +71,11 @@ func TestNewDashboard(t *testing.T) { t.Errorf("Error generating JSON: %v", errJSON) } - jsonCompared, errCompared := os.ReadFile("test-output.json") + jsonCompared, errCompared := os.ReadFile(fileOutput) if errCompared != nil { t.Errorf("Error reading file: %v", errCompared) } - require.ElementsMatch(t, jsonCompared, json) + require.JSONEq(t, string(jsonCompared), string(json)) }) } diff --git a/observability-lib/dashboards/core-node-components/platform.go b/observability-lib/dashboards/core-node-components/platform.go index 3112599..f3b91fa 100644 --- a/observability-lib/dashboards/core-node-components/platform.go +++ b/observability-lib/dashboards/core-node-components/platform.go @@ -17,6 +17,7 @@ type Props struct { MetricsDataSource *grafana.DataSource // MetricsDataSource is the datasource for querying metrics LogsDataSource *grafana.DataSource // LogsDataSource is the datasource for querying logs platformOpts platformOpts + Tested bool } // PlatformPanelOpts generate different queries for "docker" and "k8s" deployment platforms diff --git a/observability-lib/dashboards/core-node-components/test-output.json b/observability-lib/dashboards/core-node-components/test-output.json index f722b93..feda677 100644 --- a/observability-lib/dashboards/core-node-components/test-output.json +++ b/observability-lib/dashboards/core-node-components/test-output.json @@ -7,6 +7,7 @@ "Components" ], "timezone": "browser", + "editable": true, "graphTooltip": 0, "time": { "from": "now-30m", @@ -14,14 +15,14 @@ }, "fiscalYearStartMonth": 0, "refresh": "30s", - "schemaVersion": 0, + "schemaVersion": 39, "panels": [ { "type": "stat", - "id": 0, + "id": 1, "targets": [ { - "expr": "100 * avg(avg_over_time(health{blockchain=~\"${blockchain}\", product=~\"${product}\", network_type=~\"${network_type}\", component=~\"${component}\", service=~\"${service}\", env=~\"${env}\", cluster=~\"${cluster}\", service_id=~\"${service_id}\"}[$interval])) by (service_id, version, service, cluster, env)", + "expr": "100 * avg(avg_over_time(health{service_id=~\"${service_id}\"}[$interval])) by (service_id, version, service, cluster, env)", "format": "", "legendFormat": "{{service_id}}", "refId": "" @@ -45,6 +46,7 @@ "justifyMode": "auto", "textMode": "value_and_name", "wideLayout": true, + "showPercentChange": false, "reduceOptions": { "calcs": [ "last" @@ -54,7 +56,7 @@ "titleSize": 10, "valueSize": 18 }, - "showPercentChange": false, + "percentChangeColorMode": "standard", "orientation": "vertical" }, "fieldConfig": { @@ -89,10 +91,10 @@ }, { "type": "timeseries", - "id": 1, + "id": 2, "targets": [ { - "expr": "100 * (health{blockchain=~\"${blockchain}\", product=~\"${product}\", network_type=~\"${network_type}\", component=~\"${component}\", service=~\"${service}\", env=~\"${env}\", cluster=~\"${cluster}\", service_id=~\"${service_id}\"})", + "expr": "100 * (health{service_id=~\"${service_id}\"})", "format": "", "legendFormat": "{{service_id}}", "refId": "" @@ -141,10 +143,10 @@ }, { "type": "timeseries", - "id": 2, + "id": 3, "targets": [ { - "expr": "100 * (avg(avg_over_time(health{blockchain=~\"${blockchain}\", product=~\"${product}\", network_type=~\"${network_type}\", component=~\"${component}\", service=~\"${service}\", env=~\"${env}\", cluster=~\"${cluster}\", service_id=~\"${service_id}\"}[$interval])) by (service_id, version, service, cluster, env))", + "expr": "100 * (avg(avg_over_time(health{service_id=~\"${service_id}\"}[$interval])) by (service_id, version, service, cluster, env))", "format": "", "legendFormat": "{{service_id}}", "refId": "" @@ -193,7 +195,7 @@ }, { "type": "logs", - "id": 3, + "id": 4, "targets": [ { "expr": "{env=\"${env}\", cluster=\"${cluster}\", product=\"${product}\", network_type=\"${network_type}\", instance=~\"${service}\"} | json | level=~\"(error|panic|fatal|crit)\"", @@ -228,6 +230,7 @@ "type": "interval", "name": "interval", "label": "Interval", + "description": "", "query": "30s,1m,5m,15m,30m,1h,6h,12h", "current": { "selected": true, @@ -243,6 +246,7 @@ "type": "query", "name": "env", "label": "Environment", + "description": "", "query": "label_values(up, env)", "datasource": { "uid": "Prometheus" @@ -263,6 +267,7 @@ "type": "query", "name": "cluster", "label": "Cluster", + "description": "", "query": "label_values(up{env=\"$env\"}, cluster)", "datasource": { "uid": "Prometheus" @@ -283,6 +288,7 @@ "type": "query", "name": "blockchain", "label": "Blockchain", + "description": "", "query": "label_values(up{env=\"$env\", cluster=\"$cluster\"}, blockchain)", "datasource": { "uid": "Prometheus" @@ -303,6 +309,7 @@ "type": "query", "name": "product", "label": "Product", + "description": "", "query": "label_values(up{env=\"$env\", cluster=\"$cluster\", blockchain=\"$blockchain\"}, product)", "datasource": { "uid": "Prometheus" @@ -323,6 +330,7 @@ "type": "query", "name": "network_type", "label": "Network Type", + "description": "", "query": "label_values(up{env=\"$env\", cluster=\"$cluster\", blockchain=\"$blockchain\", product=\"$product\"}, network_type)", "datasource": { "uid": "Prometheus" @@ -343,6 +351,7 @@ "type": "query", "name": "component", "label": "Component", + "description": "", "query": "label_values(up{env=\"$env\", cluster=\"$cluster\", blockchain=\"$blockchain\", network_type=\"$network_type\"}, component)", "datasource": { "uid": "Prometheus" @@ -363,6 +372,7 @@ "type": "query", "name": "service", "label": "Service", + "description": "", "query": "label_values(up{env=\"$env\", cluster=\"$cluster\", blockchain=\"$blockchain\", network_type=\"$network_type\", component=\"$component\"}, service)", "datasource": { "uid": "Prometheus" @@ -383,6 +393,7 @@ "type": "query", "name": "service_id", "label": "Service ID", + "description": "", "query": "label_values(health{cluster=\"$cluster\", blockchain=\"$blockchain\", network_type=\"$network_type\", component=\"$component\", service=\"$service\"}, service_id)", "datasource": { "uid": "Prometheus" diff --git a/observability-lib/dashboards/core-node/component.go b/observability-lib/dashboards/core-node/component.go index f9b1b17..e165397 100644 --- a/observability-lib/dashboards/core-node/component.go +++ b/observability-lib/dashboards/core-node/component.go @@ -34,6 +34,9 @@ func NewDashboard(props *Props) (*grafana.Dashboard, error) { } props.platformOpts = platformPanelOpts(props.Platform) + if props.Tested { + props.platformOpts.LabelQuery = "" + } builder := grafana.NewBuilder(&grafana.BuilderOptions{ Name: props.Name, @@ -345,55 +348,53 @@ func headlines(p *Props) []*grafana.Panel { }, Min: grafana.Pointer[float64](0), Max: grafana.Pointer[float64](100), - AlertOptions: &grafana.AlertOptions{ - Summary: `Uptime less than 90% over last 15 minutes on one component in a Node`, - Description: `Component {{ index $labels "service_id" }} uptime in the last 15m is {{ index $values "A" }}%`, - RunbookURL: "https://github.com/goplugin/plugin-common/tree/main/observability-lib", - For: "15m", - Tags: map[string]string{ - "severity": "warning", - }, - Query: []grafana.RuleQuery{ - { - Expr: `health{` + p.AlertsFilters + `}`, - RefID: "A", - Datasource: p.MetricsDataSource.UID, - }, + }, + LegendOptions: &grafana.LegendOptions{ + DisplayMode: common.LegendDisplayModeList, + Placement: common.LegendPlacementRight, + }, + AlertOptions: &grafana.AlertOptions{ + Summary: `Uptime less than 90% over last 15 minutes on one component in a Node`, + Description: `Component {{ index $labels "service_id" }} uptime in the last 15m is {{ index $values "A" }}%`, + RunbookURL: "https://github.com/goplugin/plugin-common/tree/main/observability-lib", + For: "15m", + Tags: map[string]string{ + "severity": "warning", + }, + Query: []grafana.RuleQuery{ + { + Expr: `health{` + p.AlertsFilters + `}`, + RefID: "A", + Datasource: p.MetricsDataSource.UID, }, - QueryRefCondition: "D", - Condition: []grafana.ConditionQuery{ - { - RefID: "B", - ReduceExpression: &grafana.ReduceExpression{ - Expression: "A", - Reducer: expr.TypeReduceReducerMean, - }, + }, + QueryRefCondition: "D", + Condition: []grafana.ConditionQuery{ + { + RefID: "B", + ReduceExpression: &grafana.ReduceExpression{ + Expression: "A", + Reducer: expr.TypeReduceReducerMean, }, - { - RefID: "C", - MathExpression: &grafana.MathExpression{ - Expression: "$B * 100", - }, + }, + { + RefID: "C", + MathExpression: &grafana.MathExpression{ + Expression: "$B * 100", }, - { - RefID: "D", - ThresholdExpression: &grafana.ThresholdExpression{ - Expression: "C", - ThresholdConditionsOptions: []grafana.ThresholdConditionsOption{ - { - Params: []float64{90, 0}, - Type: expr.TypeThresholdTypeLt, - }, - }, + }, + { + RefID: "D", + ThresholdExpression: &grafana.ThresholdExpression{ + Expression: "C", + ThresholdConditionsOptions: grafana.ThresholdConditionsOption{ + Params: []float64{1}, + Type: grafana.TypeThresholdTypeLt, }, }, }, }, }, - LegendOptions: &grafana.LegendOptions{ - DisplayMode: common.LegendDisplayModeList, - Placement: common.LegendPlacementRight, - }, })) panels = append(panels, grafana.NewStatPanel(&grafana.StatPanelOptions{ @@ -440,35 +441,33 @@ func headlines(p *Props) []*grafana.Panel { Legend: `{{` + p.platformOpts.LabelFilter + `}} - {{account}}`, }, }, - AlertOptions: &grafana.AlertOptions{ - Summary: `ETH Balance is lower than threshold`, - Description: `ETH Balance critically low at {{ index $values "A" }} on {{ index $labels "` + p.platformOpts.LabelFilter + `" }}`, - RunbookURL: "https://github.com/goplugin/plugin-common/tree/main/observability-lib", - For: "15m", - NoDataState: alerting.RuleNoDataStateOK, - Tags: map[string]string{ - "severity": "critical", - }, - Query: []grafana.RuleQuery{ - { - Expr: `eth_balance{` + p.AlertsFilters + `}`, - Instant: true, - RefID: "A", - Datasource: p.MetricsDataSource.UID, - }, + }, + AlertOptions: &grafana.AlertOptions{ + Summary: `ETH Balance is lower than threshold`, + Description: `ETH Balance critically low at {{ index $values "A" }} on {{ index $labels "` + p.platformOpts.LabelFilter + `" }}`, + RunbookURL: "https://github.com/goplugin/plugin-common/tree/main/observability-lib", + For: "15m", + NoDataState: alerting.RuleNoDataStateOK, + Tags: map[string]string{ + "severity": "critical", + }, + Query: []grafana.RuleQuery{ + { + Expr: `eth_balance{` + p.AlertsFilters + `}`, + Instant: true, + RefID: "A", + Datasource: p.MetricsDataSource.UID, }, - QueryRefCondition: "B", - Condition: []grafana.ConditionQuery{ - { - RefID: "B", - ThresholdExpression: &grafana.ThresholdExpression{ - Expression: "A", - ThresholdConditionsOptions: []grafana.ThresholdConditionsOption{ - { - Params: []float64{1, 0}, - Type: expr.TypeThresholdTypeLt, - }, - }, + }, + QueryRefCondition: "B", + Condition: []grafana.ConditionQuery{ + { + RefID: "B", + ThresholdExpression: &grafana.ThresholdExpression{ + Expression: "A", + ThresholdConditionsOptions: grafana.ThresholdConditionsOption{ + Params: []float64{1}, + Type: grafana.TypeThresholdTypeLt, }, }, }, @@ -489,35 +488,33 @@ func headlines(p *Props) []*grafana.Panel { Legend: `{{` + p.platformOpts.LabelFilter + `}} - {{account}}`, }, }, - AlertOptions: &grafana.AlertOptions{ - Summary: `Solana Balance is lower than threshold`, - Description: `Solana Balance critically low at {{ index $values "A" }} on {{ index $labels "` + p.platformOpts.LabelFilter + `" }}`, - RunbookURL: "https://github.com/goplugin/plugin-common/tree/main/observability-lib", - For: "15m", - NoDataState: alerting.RuleNoDataStateOK, - Tags: map[string]string{ - "severity": "critical", - }, - Query: []grafana.RuleQuery{ - { - Expr: `solana_balance{` + p.AlertsFilters + `}`, - Instant: true, - RefID: "A", - Datasource: p.MetricsDataSource.UID, - }, + }, + AlertOptions: &grafana.AlertOptions{ + Summary: `Solana Balance is lower than threshold`, + Description: `Solana Balance critically low at {{ index $values "A" }} on {{ index $labels "` + p.platformOpts.LabelFilter + `" }}`, + RunbookURL: "https://github.com/goplugin/plugin-common/tree/main/observability-lib", + For: "15m", + NoDataState: alerting.RuleNoDataStateOK, + Tags: map[string]string{ + "severity": "critical", + }, + Query: []grafana.RuleQuery{ + { + Expr: `solana_balance{` + p.AlertsFilters + `}`, + Instant: true, + RefID: "A", + Datasource: p.MetricsDataSource.UID, }, - QueryRefCondition: "B", - Condition: []grafana.ConditionQuery{ - { - RefID: "B", - ThresholdExpression: &grafana.ThresholdExpression{ - Expression: "A", - ThresholdConditionsOptions: []grafana.ThresholdConditionsOption{ - { - Params: []float64{1, 0}, - Type: expr.TypeThresholdTypeLt, - }, - }, + }, + QueryRefCondition: "B", + Condition: []grafana.ConditionQuery{ + { + RefID: "B", + ThresholdExpression: &grafana.ThresholdExpression{ + Expression: "A", + ThresholdConditionsOptions: grafana.ThresholdConditionsOption{ + Params: []float64{1}, + Type: grafana.TypeThresholdTypeLt, }, }, }, @@ -842,35 +839,33 @@ func headTracker(p *Props) []*grafana.Panel { Legend: `{{` + p.platformOpts.LabelFilter + `}}`, }, }, - AlertOptions: &grafana.AlertOptions{ - Summary: `No Headers Received`, - Description: `{{ index $labels "` + p.platformOpts.LabelFilter + `" }} on ChainID {{ index $labels "ChainID" }} has received {{ index $values "A" }} heads over 10 minutes.`, - RunbookURL: "https://github.com/goplugin/plugin-common/tree/main/observability-lib", - For: "10m", - NoDataState: alerting.RuleNoDataStateOK, - Tags: map[string]string{ - "severity": "critical", - }, - Query: []grafana.RuleQuery{ - { - Expr: `increase(head_tracker_heads_received{` + p.AlertsFilters + `}[10m])`, - Instant: true, - RefID: "A", - Datasource: p.MetricsDataSource.UID, - }, + }, + AlertOptions: &grafana.AlertOptions{ + Summary: `No Headers Received`, + Description: `{{ index $labels "` + p.platformOpts.LabelFilter + `" }} on ChainID {{ index $labels "ChainID" }} has received {{ index $values "A" }} heads over 10 minutes.`, + RunbookURL: "https://github.com/goplugin/plugin-common/tree/main/observability-lib", + For: "10m", + NoDataState: alerting.RuleNoDataStateOK, + Tags: map[string]string{ + "severity": "critical", + }, + Query: []grafana.RuleQuery{ + { + Expr: `increase(head_tracker_heads_received{` + p.AlertsFilters + `}[10m])`, + Instant: true, + RefID: "A", + Datasource: p.MetricsDataSource.UID, }, - QueryRefCondition: "B", - Condition: []grafana.ConditionQuery{ - { - RefID: "B", - ThresholdExpression: &grafana.ThresholdExpression{ - Expression: "A", - ThresholdConditionsOptions: []grafana.ThresholdConditionsOption{ - { - Params: []float64{1, 0}, - Type: expr.TypeThresholdTypeLt, - }, - }, + }, + QueryRefCondition: "B", + Condition: []grafana.ConditionQuery{ + { + RefID: "B", + ThresholdExpression: &grafana.ThresholdExpression{ + Expression: "A", + ThresholdConditionsOptions: grafana.ThresholdConditionsOption{ + Params: []float64{1}, + Type: grafana.TypeThresholdTypeLt, }, }, }, @@ -970,57 +965,146 @@ func headReporter(p *Props) []*grafana.Panel { func txManager(p *Props) []*grafana.Panel { var panels []*grafana.Panel - txStatus := map[string]string{ - "num_confirmed_transactions": "Confirmed", - "num_successful_transactions": "Successful", - "num_tx_reverted": "Reverted", - "num_gas_bumps": "Gas Bumps", - "fwd_tx_count": "Forwarded", - "tx_attempt_count": "Attempts", - "gas_bump_exceeds_limit": "Gas Bump Exceeds Limit", - } + panels = append(panels, grafana.NewTimeSeriesPanel(&grafana.TimeSeriesPanelOptions{ + PanelOptions: &grafana.PanelOptions{ + Datasource: p.MetricsDataSource.Name, + Title: "TX Manager Confirmed", + Span: 6, + Height: 6, + Query: []grafana.Query{ + { + Expr: `sum(tx_manager_num_confirmed_transactions{` + p.platformOpts.LabelQuery + `}) by (blockchain, chainID, ` + p.platformOpts.LabelFilter + `)`, + Legend: `{{` + p.platformOpts.LabelFilter + `}} - {{blockchain}} - {{chainID}}`, + }, + }, + }, + })) - for status, title := range txStatus { - panels = append(panels, grafana.NewTimeSeriesPanel(&grafana.TimeSeriesPanelOptions{ - PanelOptions: &grafana.PanelOptions{ - Datasource: p.MetricsDataSource.Name, - Title: "TX Manager " + title, - Span: 6, - Height: 6, - Query: []grafana.Query{ - { - Expr: `sum(tx_manager_` + status + `{` + p.platformOpts.LabelQuery + `}) by (blockchain, chainID, ` + p.platformOpts.LabelFilter + `)`, - Legend: `{{` + p.platformOpts.LabelFilter + `}} - {{blockchain}} - {{chainID}}`, - }, + panels = append(panels, grafana.NewTimeSeriesPanel(&grafana.TimeSeriesPanelOptions{ + PanelOptions: &grafana.PanelOptions{ + Datasource: p.MetricsDataSource.Name, + Title: "TX Manager Successful", + Span: 6, + Height: 6, + Query: []grafana.Query{ + { + Expr: `sum(tx_manager_num_successful_transactions{` + p.platformOpts.LabelQuery + `}) by (blockchain, chainID, ` + p.platformOpts.LabelFilter + `)`, + Legend: `{{` + p.platformOpts.LabelFilter + `}} - {{blockchain}} - {{chainID}}`, }, }, - })) - } + }, + })) - txUntilStatus := map[string]string{ - "broadcast": "The amount of time elapsed from when a transaction is enqueued to until it is broadcast", - "confirmed": "The amount of time elapsed from a transaction being broadcast to being included in a block", - } + panels = append(panels, grafana.NewTimeSeriesPanel(&grafana.TimeSeriesPanelOptions{ + PanelOptions: &grafana.PanelOptions{ + Datasource: p.MetricsDataSource.Name, + Title: "TX Manager Reverted", + Span: 6, + Height: 6, + Query: []grafana.Query{ + { + Expr: `sum(tx_manager_num_tx_reverted{` + p.platformOpts.LabelQuery + `}) by (blockchain, chainID, ` + p.platformOpts.LabelFilter + `)`, + Legend: `{{` + p.platformOpts.LabelFilter + `}} - {{blockchain}} - {{chainID}}`, + }, + }, + }, + })) - for status, description := range txUntilStatus { - panels = append(panels, grafana.NewTimeSeriesPanel(&grafana.TimeSeriesPanelOptions{ - PanelOptions: &grafana.PanelOptions{ - Datasource: p.MetricsDataSource.Name, - Title: "TX Manager Time Until " + status, - Description: description, - Span: 6, - Height: 6, - Decimals: 1, - Unit: "ms", - Query: []grafana.Query{ - { - Expr: `histogram_quantile(0.9, sum(rate(tx_manager_time_until_tx_` + status + `_bucket{` + p.platformOpts.LabelQuery + `}[$__rate_interval])) by (le, ` + p.platformOpts.LabelFilter + `, blockchain, chainID)) / 1e6`, - Legend: `{{` + p.platformOpts.LabelFilter + `}} - {{blockchain}} - {{chainID}}`, - }, + panels = append(panels, grafana.NewTimeSeriesPanel(&grafana.TimeSeriesPanelOptions{ + PanelOptions: &grafana.PanelOptions{ + Datasource: p.MetricsDataSource.Name, + Title: "TX Manager Gas Bumps", + Span: 6, + Height: 6, + Query: []grafana.Query{ + { + Expr: `sum(tx_manager_num_gas_bumps{` + p.platformOpts.LabelQuery + `}) by (blockchain, chainID, ` + p.platformOpts.LabelFilter + `)`, + Legend: `{{` + p.platformOpts.LabelFilter + `}} - {{blockchain}} - {{chainID}}`, }, }, - })) - } + }, + })) + + panels = append(panels, grafana.NewTimeSeriesPanel(&grafana.TimeSeriesPanelOptions{ + PanelOptions: &grafana.PanelOptions{ + Datasource: p.MetricsDataSource.Name, + Title: "TX Manager Forwarded", + Span: 6, + Height: 6, + Query: []grafana.Query{ + { + Expr: `sum(tx_manager_fwd_tx_count{` + p.platformOpts.LabelQuery + `}) by (blockchain, chainID, ` + p.platformOpts.LabelFilter + `)`, + Legend: `{{` + p.platformOpts.LabelFilter + `}} - {{blockchain}} - {{chainID}}`, + }, + }, + }, + })) + + panels = append(panels, grafana.NewTimeSeriesPanel(&grafana.TimeSeriesPanelOptions{ + PanelOptions: &grafana.PanelOptions{ + Datasource: p.MetricsDataSource.Name, + Title: "TX Manager Attempts", + Span: 6, + Height: 6, + Query: []grafana.Query{ + { + Expr: `sum(tx_manager_tx_attempt_count{` + p.platformOpts.LabelQuery + `}) by (blockchain, chainID, ` + p.platformOpts.LabelFilter + `)`, + Legend: `{{` + p.platformOpts.LabelFilter + `}} - {{blockchain}} - {{chainID}}`, + }, + }, + }, + })) + + panels = append(panels, grafana.NewTimeSeriesPanel(&grafana.TimeSeriesPanelOptions{ + PanelOptions: &grafana.PanelOptions{ + Datasource: p.MetricsDataSource.Name, + Title: "TX Manager Gas Bump Exceeds Limit", + Span: 6, + Height: 6, + Query: []grafana.Query{ + { + Expr: `sum(tx_manager_gas_bump_exceeds_limit{` + p.platformOpts.LabelQuery + `}) by (blockchain, chainID, ` + p.platformOpts.LabelFilter + `)`, + Legend: `{{` + p.platformOpts.LabelFilter + `}} - {{blockchain}} - {{chainID}}`, + }, + }, + }, + })) + + panels = append(panels, grafana.NewTimeSeriesPanel(&grafana.TimeSeriesPanelOptions{ + PanelOptions: &grafana.PanelOptions{ + Datasource: p.MetricsDataSource.Name, + Title: "TX Manager Time Until Broadcast", + Description: "The amount of time elapsed from when a transaction is enqueued to until it is broadcast", + Span: 6, + Height: 6, + Decimals: 1, + Unit: "ms", + Query: []grafana.Query{ + { + Expr: `histogram_quantile(0.9, sum(rate(tx_manager_time_until_tx_broadcast_bucket{` + p.platformOpts.LabelQuery + `}[$__rate_interval])) by (le, ` + p.platformOpts.LabelFilter + `, blockchain, chainID)) / 1e6`, + Legend: `{{` + p.platformOpts.LabelFilter + `}} - {{blockchain}} - {{chainID}}`, + }, + }, + }, + })) + + panels = append(panels, grafana.NewTimeSeriesPanel(&grafana.TimeSeriesPanelOptions{ + PanelOptions: &grafana.PanelOptions{ + Datasource: p.MetricsDataSource.Name, + Title: "TX Manager Time Until Confirmed", + Description: "The amount of time elapsed from a transaction being broadcast to being included in a block", + Span: 6, + Height: 6, + Decimals: 1, + Unit: "ms", + Query: []grafana.Query{ + { + Expr: `histogram_quantile(0.9, sum(rate(tx_manager_time_until_tx_confirmed_bucket{` + p.platformOpts.LabelQuery + `}[$__rate_interval])) by (le, ` + p.platformOpts.LabelFilter + `, blockchain, chainID)) / 1e6`, + Legend: `{{` + p.platformOpts.LabelFilter + `}} - {{blockchain}} - {{chainID}}`, + }, + }, + }, + })) return panels } diff --git a/observability-lib/dashboards/core-node/component_test.go b/observability-lib/dashboards/core-node/component_test.go index a2cef09..daf5ff4 100644 --- a/observability-lib/dashboards/core-node/component_test.go +++ b/observability-lib/dashboards/core-node/component_test.go @@ -1,6 +1,7 @@ package corenode_test import ( + "flag" "os" "testing" @@ -10,12 +11,54 @@ import ( "github.com/stretchr/testify/require" ) +var update = flag.Bool("update", false, "update golden test files") + +const fileOutput = "test-output.json" + +func TestGenerateFile(t *testing.T) { + if *update == false { + t.Skip("skipping test") + } + + testDashboard, err := corenode.NewDashboard(&corenode.Props{ + Name: "Core Node Dashboard", + Platform: grafana.TypePlatformDocker, + MetricsDataSource: grafana.NewDataSource("Prometheus", "1"), + Tested: true, + }) + if err != nil { + t.Errorf("Error creating dashboard: %v", err) + } + json, errJSON := testDashboard.GenerateJSON() + if errJSON != nil { + t.Errorf("Error generating JSON: %v", errJSON) + } + if _, errExists := os.Stat(fileOutput); errExists == nil { + errRemove := os.Remove(fileOutput) + if errRemove != nil { + t.Errorf("Error removing file: %v", errRemove) + } + } + file, errFile := os.Create(fileOutput) + if errFile != nil { + panic(errFile) + } + writeString, err := file.WriteString(string(json)) + if err != nil { + t.Errorf("Error writing to file: %v", writeString) + } + t.Cleanup(func() { + file.Close() + }) +} + func TestNewDashboard(t *testing.T) { t.Run("NewDashboard creates a dashboard", func(t *testing.T) { testDashboard, err := corenode.NewDashboard(&corenode.Props{ Name: "Core Node Dashboard", Platform: grafana.TypePlatformDocker, MetricsDataSource: grafana.NewDataSource("Prometheus", "1"), + Tested: true, }) if err != nil { t.Errorf("Error creating dashboard: %v", err) @@ -27,11 +70,11 @@ func TestNewDashboard(t *testing.T) { t.Errorf("Error generating JSON: %v", errJSON) } - jsonCompared, errCompared := os.ReadFile("test-output.json") + jsonCompared, errCompared := os.ReadFile(fileOutput) if errCompared != nil { t.Errorf("Error reading file: %v", errCompared) } - require.ElementsMatch(t, jsonCompared, json) + require.JSONEq(t, string(jsonCompared), string(json)) }) } diff --git a/observability-lib/dashboards/core-node/platform.go b/observability-lib/dashboards/core-node/platform.go index 3a9647d..424663c 100644 --- a/observability-lib/dashboards/core-node/platform.go +++ b/observability-lib/dashboards/core-node/platform.go @@ -24,6 +24,7 @@ type Props struct { AlertsTags map[string]string // AlertsTags is the tags to map with notification policy AlertsFilters string // AlertsFilters is the filters to apply to alerts platformOpts platformOpts + Tested bool } // PlatformPanelOpts generate different queries for "docker" and "k8s" deployment platforms diff --git a/observability-lib/dashboards/core-node/test-output.json b/observability-lib/dashboards/core-node/test-output.json index 0c887fe..83e0824 100644 --- a/observability-lib/dashboards/core-node/test-output.json +++ b/observability-lib/dashboards/core-node/test-output.json @@ -6,6 +6,7 @@ "Node" ], "timezone": "browser", + "editable": true, "graphTooltip": 0, "time": { "from": "now-30m", @@ -13,7 +14,7 @@ }, "fiscalYearStartMonth": 0, "refresh": "30s", - "schemaVersion": 0, + "schemaVersion": 39, "panels": [ { "type": "row", @@ -30,10 +31,10 @@ }, { "type": "stat", - "id": 0, + "id": 1, "targets": [ { - "expr": "version{instance=~\"${instance}\", }", + "expr": "version{}", "instant": true, "range": false, "format": "", @@ -59,6 +60,7 @@ "justifyMode": "auto", "textMode": "name", "wideLayout": true, + "showPercentChange": false, "reduceOptions": { "calcs": [ "last" @@ -68,7 +70,7 @@ "titleSize": 10, "valueSize": 18 }, - "showPercentChange": false, + "percentChangeColorMode": "standard", "orientation": "horizontal" }, "fieldConfig": { @@ -82,10 +84,10 @@ }, { "type": "stat", - "id": 1, + "id": 2, "targets": [ { - "expr": "uptime_seconds{instance=~\"${instance}\", }", + "expr": "uptime_seconds{}", "format": "", "legendFormat": "{{instance}}", "refId": "" @@ -109,6 +111,7 @@ "justifyMode": "auto", "textMode": "value_and_name", "wideLayout": true, + "showPercentChange": false, "reduceOptions": { "calcs": [ "last" @@ -118,7 +121,7 @@ "titleSize": 10, "valueSize": 18 }, - "showPercentChange": false, + "percentChangeColorMode": "standard", "orientation": "horizontal" }, "fieldConfig": { @@ -132,10 +135,10 @@ }, { "type": "stat", - "id": 2, + "id": 3, "targets": [ { - "expr": "sum(eth_balance{instance=~\"${instance}\", }) by (instance, account)", + "expr": "sum(eth_balance{}) by (instance, account)", "instant": true, "range": false, "format": "", @@ -161,6 +164,7 @@ "justifyMode": "auto", "textMode": "value_and_name", "wideLayout": true, + "showPercentChange": false, "reduceOptions": { "calcs": [ "last" @@ -170,7 +174,7 @@ "titleSize": 10, "valueSize": 18 }, - "showPercentChange": false, + "percentChangeColorMode": "standard", "orientation": "horizontal" }, "fieldConfig": { @@ -201,10 +205,10 @@ }, { "type": "stat", - "id": 3, + "id": 4, "targets": [ { - "expr": "sum(solana_balance{instance=~\"${instance}\", }) by (instance, account)", + "expr": "sum(solana_balance{}) by (instance, account)", "instant": true, "range": false, "format": "", @@ -230,6 +234,7 @@ "justifyMode": "auto", "textMode": "value_and_name", "wideLayout": true, + "showPercentChange": false, "reduceOptions": { "calcs": [ "last" @@ -239,7 +244,7 @@ "titleSize": 10, "valueSize": 18 }, - "showPercentChange": false, + "percentChangeColorMode": "standard", "orientation": "horizontal" }, "fieldConfig": { @@ -270,10 +275,10 @@ }, { "type": "timeseries", - "id": 4, + "id": 5, "targets": [ { - "expr": "100 * (avg(avg_over_time(health{instance=~\"${instance}\", }[15m])) by (instance, service_id, version, service, cluster, env))", + "expr": "100 * (avg(avg_over_time(health{}[15m])) by (instance, service_id, version, service, cluster, env))", "format": "", "legendFormat": "{{instance}} - {{service_id}}", "refId": "" @@ -322,10 +327,10 @@ }, { "type": "stat", - "id": 5, + "id": 6, "targets": [ { - "expr": "100 * avg(avg_over_time(health{instance=~\"${instance}\", }[15m])) by (instance, service_id, version, service, cluster, env) \u003c 90", + "expr": "100 * avg(avg_over_time(health{}[15m])) by (instance, service_id, version, service, cluster, env) \u003c 90", "format": "", "legendFormat": "{{instance}} - {{service_id}}", "refId": "" @@ -349,6 +354,7 @@ "justifyMode": "auto", "textMode": "value_and_name", "wideLayout": true, + "showPercentChange": false, "reduceOptions": { "calcs": [ "last" @@ -358,7 +364,7 @@ "titleSize": 10, "valueSize": 18 }, - "showPercentChange": false, + "percentChangeColorMode": "standard", "orientation": "horizontal" }, "fieldConfig": { @@ -393,10 +399,10 @@ }, { "type": "timeseries", - "id": 6, + "id": 7, "targets": [ { - "expr": "sum(eth_balance{instance=~\"${instance}\", }) by (instance, account)", + "expr": "sum(eth_balance{}) by (instance, account)", "format": "", "legendFormat": "{{instance}} - {{account}}", "refId": "" @@ -443,10 +449,10 @@ }, { "type": "timeseries", - "id": 7, + "id": 8, "targets": [ { - "expr": "sum(solana_balance{instance=~\"${instance}\", }) by (instance, account)", + "expr": "sum(solana_balance{}) by (instance, account)", "format": "", "legendFormat": "{{instance}} - {{account}}", "refId": "" @@ -493,10 +499,10 @@ }, { "type": "stat", - "id": 8, + "id": 9, "targets": [ { - "expr": "process_open_fds{instance=~\"${instance}\", }", + "expr": "process_open_fds{}", "format": "", "legendFormat": "{{instance}}", "refId": "" @@ -520,6 +526,7 @@ "justifyMode": "auto", "textMode": "value", "wideLayout": true, + "showPercentChange": false, "reduceOptions": { "calcs": [ "last" @@ -529,7 +536,7 @@ "titleSize": 10, "valueSize": 18 }, - "showPercentChange": false, + "percentChangeColorMode": "standard", "orientation": "auto" }, "fieldConfig": { @@ -543,10 +550,10 @@ }, { "type": "stat", - "id": 9, + "id": 10, "targets": [ { - "expr": "go_info{instance=~\"${instance}\", }", + "expr": "go_info{}", "instant": true, "range": false, "format": "", @@ -572,6 +579,7 @@ "justifyMode": "auto", "textMode": "name", "wideLayout": true, + "showPercentChange": false, "reduceOptions": { "calcs": [ "last" @@ -581,7 +589,7 @@ "titleSize": 10, "valueSize": 18 }, - "showPercentChange": false, + "percentChangeColorMode": "standard", "orientation": "auto" }, "fieldConfig": { @@ -608,28 +616,28 @@ }, { "type": "timeseries", - "id": 10, + "id": 11, "targets": [ { - "expr": "sum(db_conns_max{instance=~\"${instance}\", }) by (instance)", + "expr": "sum(db_conns_max{}) by (instance)", "format": "", "legendFormat": "{{instance}} - Max", "refId": "" }, { - "expr": "sum(db_conns_open{instance=~\"${instance}\", }) by (instance)", + "expr": "sum(db_conns_open{}) by (instance)", "format": "", "legendFormat": "{{instance}} - Open", "refId": "" }, { - "expr": "sum(db_conns_used{instance=~\"${instance}\", }) by (instance)", + "expr": "sum(db_conns_used{}) by (instance)", "format": "", "legendFormat": "{{instance}} - Used", "refId": "" }, { - "expr": "sum(db_conns_wait{instance=~\"${instance}\", }) by (instance)", + "expr": "sum(db_conns_wait{}) by (instance)", "format": "", "legendFormat": "{{instance}} - Wait", "refId": "" @@ -676,10 +684,10 @@ }, { "type": "timeseries", - "id": 11, + "id": 12, "targets": [ { - "expr": "sum(db_wait_count{instance=~\"${instance}\", }) by (instance)", + "expr": "sum(db_wait_count{}) by (instance)", "format": "", "legendFormat": "{{instance}}", "refId": "" @@ -726,10 +734,10 @@ }, { "type": "timeseries", - "id": 12, + "id": 13, "targets": [ { - "expr": "sum(db_wait_time_seconds{instance=~\"${instance}\", }) by (instance)", + "expr": "sum(db_wait_time_seconds{}) by (instance)", "format": "", "legendFormat": "{{instance}}", "refId": "" @@ -789,22 +797,22 @@ }, { "type": "timeseries", - "id": 13, + "id": 14, "targets": [ { - "expr": "histogram_quantile(0.9, sum(rate(sql_query_timeout_percent_bucket{instance=~\"${instance}\", }[$__rate_interval])) by (le))", + "expr": "histogram_quantile(0.9, sum(rate(sql_query_timeout_percent_bucket{}[$__rate_interval])) by (le))", "format": "", "legendFormat": "p90", "refId": "" }, { - "expr": "histogram_quantile(0.95, sum(rate(sql_query_timeout_percent_bucket{instance=~\"${instance}\", }[$__rate_interval])) by (le))", + "expr": "histogram_quantile(0.95, sum(rate(sql_query_timeout_percent_bucket{}[$__rate_interval])) by (le))", "format": "", "legendFormat": "p95", "refId": "" }, { - "expr": "histogram_quantile(0.99, sum(rate(sql_query_timeout_percent_bucket{instance=~\"${instance}\", }[$__rate_interval])) by (le))", + "expr": "histogram_quantile(0.99, sum(rate(sql_query_timeout_percent_bucket{}[$__rate_interval])) by (le))", "format": "", "legendFormat": "p99", "refId": "" @@ -864,10 +872,10 @@ }, { "type": "timeseries", - "id": 14, + "id": 15, "targets": [ { - "expr": "sum(head_tracker_current_head{instance=~\"${instance}\", }) by (instance)", + "expr": "sum(head_tracker_current_head{}) by (instance)", "format": "", "legendFormat": "{{instance}}", "refId": "" @@ -914,10 +922,10 @@ }, { "type": "stat", - "id": 15, + "id": 16, "targets": [ { - "expr": "head_tracker_current_head{instance=~\"${instance}\", }", + "expr": "head_tracker_current_head{}", "instant": true, "range": false, "format": "", @@ -943,6 +951,7 @@ "justifyMode": "auto", "textMode": "value", "wideLayout": true, + "showPercentChange": false, "reduceOptions": { "calcs": [ "last" @@ -952,7 +961,7 @@ "titleSize": 10, "valueSize": 18 }, - "showPercentChange": false, + "percentChangeColorMode": "standard", "orientation": "auto" }, "fieldConfig": { @@ -966,10 +975,10 @@ }, { "type": "timeseries", - "id": 16, + "id": 17, "targets": [ { - "expr": "rate(head_tracker_heads_received{instance=~\"${instance}\", }[1m])", + "expr": "rate(head_tracker_heads_received{}[1m])", "format": "", "legendFormat": "{{instance}}", "refId": "" @@ -1016,10 +1025,10 @@ }, { "type": "timeseries", - "id": 17, + "id": 18, "targets": [ { - "expr": "head_tracker_very_old_head{instance=~\"${instance}\", }", + "expr": "head_tracker_very_old_head{}", "format": "", "legendFormat": "{{instance}}", "refId": "" @@ -1066,10 +1075,10 @@ }, { "type": "timeseries", - "id": 18, + "id": 19, "targets": [ { - "expr": "rate(head_tracker_connection_errors{instance=~\"${instance}\", }[1m])", + "expr": "rate(head_tracker_connection_errors{}[1m])", "format": "", "legendFormat": "{{instance}}", "refId": "" @@ -1129,10 +1138,10 @@ }, { "type": "timeseries", - "id": 19, + "id": 20, "targets": [ { - "expr": "sum(unconfirmed_transactions{instance=~\"${instance}\", }) by (instance)", + "expr": "sum(unconfirmed_transactions{}) by (instance)", "format": "", "legendFormat": "{{instance}}", "refId": "" @@ -1179,10 +1188,10 @@ }, { "type": "timeseries", - "id": 20, + "id": 21, "targets": [ { - "expr": "sum(max_unconfirmed_tx_age{instance=~\"${instance}\", }) by (instance)", + "expr": "sum(max_unconfirmed_tx_age{}) by (instance)", "format": "", "legendFormat": "{{instance}}", "refId": "" @@ -1229,10 +1238,10 @@ }, { "type": "timeseries", - "id": 21, + "id": 22, "targets": [ { - "expr": "sum(max_unconfirmed_blocks{instance=~\"${instance}\", }) by (instance)", + "expr": "sum(max_unconfirmed_blocks{}) by (instance)", "format": "", "legendFormat": "{{instance}}", "refId": "" @@ -1292,10 +1301,10 @@ }, { "type": "timeseries", - "id": 22, + "id": 23, "targets": [ { - "expr": "sum(tx_manager_num_confirmed_transactions{instance=~\"${instance}\", }) by (blockchain, chainID, instance)", + "expr": "sum(tx_manager_num_confirmed_transactions{}) by (blockchain, chainID, instance)", "format": "", "legendFormat": "{{instance}} - {{blockchain}} - {{chainID}}", "refId": "" @@ -1342,10 +1351,10 @@ }, { "type": "timeseries", - "id": 23, + "id": 24, "targets": [ { - "expr": "sum(tx_manager_num_successful_transactions{instance=~\"${instance}\", }) by (blockchain, chainID, instance)", + "expr": "sum(tx_manager_num_successful_transactions{}) by (blockchain, chainID, instance)", "format": "", "legendFormat": "{{instance}} - {{blockchain}} - {{chainID}}", "refId": "" @@ -1392,10 +1401,10 @@ }, { "type": "timeseries", - "id": 24, + "id": 25, "targets": [ { - "expr": "sum(tx_manager_num_tx_reverted{instance=~\"${instance}\", }) by (blockchain, chainID, instance)", + "expr": "sum(tx_manager_num_tx_reverted{}) by (blockchain, chainID, instance)", "format": "", "legendFormat": "{{instance}} - {{blockchain}} - {{chainID}}", "refId": "" @@ -1442,10 +1451,10 @@ }, { "type": "timeseries", - "id": 25, + "id": 26, "targets": [ { - "expr": "sum(tx_manager_num_gas_bumps{instance=~\"${instance}\", }) by (blockchain, chainID, instance)", + "expr": "sum(tx_manager_num_gas_bumps{}) by (blockchain, chainID, instance)", "format": "", "legendFormat": "{{instance}} - {{blockchain}} - {{chainID}}", "refId": "" @@ -1492,10 +1501,10 @@ }, { "type": "timeseries", - "id": 26, + "id": 27, "targets": [ { - "expr": "sum(tx_manager_fwd_tx_count{instance=~\"${instance}\", }) by (blockchain, chainID, instance)", + "expr": "sum(tx_manager_fwd_tx_count{}) by (blockchain, chainID, instance)", "format": "", "legendFormat": "{{instance}} - {{blockchain}} - {{chainID}}", "refId": "" @@ -1542,10 +1551,10 @@ }, { "type": "timeseries", - "id": 27, + "id": 28, "targets": [ { - "expr": "sum(tx_manager_tx_attempt_count{instance=~\"${instance}\", }) by (blockchain, chainID, instance)", + "expr": "sum(tx_manager_tx_attempt_count{}) by (blockchain, chainID, instance)", "format": "", "legendFormat": "{{instance}} - {{blockchain}} - {{chainID}}", "refId": "" @@ -1592,10 +1601,10 @@ }, { "type": "timeseries", - "id": 28, + "id": 29, "targets": [ { - "expr": "sum(tx_manager_gas_bump_exceeds_limit{instance=~\"${instance}\", }) by (blockchain, chainID, instance)", + "expr": "sum(tx_manager_gas_bump_exceeds_limit{}) by (blockchain, chainID, instance)", "format": "", "legendFormat": "{{instance}} - {{blockchain}} - {{chainID}}", "refId": "" @@ -1642,16 +1651,16 @@ }, { "type": "timeseries", - "id": 29, + "id": 30, "targets": [ { - "expr": "histogram_quantile(0.9, sum(rate(tx_manager_time_until_tx_broadcast_bucket{instance=~\"${instance}\", }[$__rate_interval])) by (le, instance, blockchain, chainID)) / 1e6", + "expr": "histogram_quantile(0.9, sum(rate(tx_manager_time_until_tx_broadcast_bucket{}[$__rate_interval])) by (le, instance, blockchain, chainID)) / 1e6", "format": "", "legendFormat": "{{instance}} - {{blockchain}} - {{chainID}}", "refId": "" } ], - "title": "TX Manager Time Until broadcast", + "title": "TX Manager Time Until Broadcast", "description": "The amount of time elapsed from when a transaction is enqueued to until it is broadcast", "transparent": false, "datasource": { @@ -1692,16 +1701,16 @@ }, { "type": "timeseries", - "id": 30, + "id": 31, "targets": [ { - "expr": "histogram_quantile(0.9, sum(rate(tx_manager_time_until_tx_confirmed_bucket{instance=~\"${instance}\", }[$__rate_interval])) by (le, instance, blockchain, chainID)) / 1e6", + "expr": "histogram_quantile(0.9, sum(rate(tx_manager_time_until_tx_confirmed_bucket{}[$__rate_interval])) by (le, instance, blockchain, chainID)) / 1e6", "format": "", "legendFormat": "{{instance}} - {{blockchain}} - {{chainID}}", "refId": "" } ], - "title": "TX Manager Time Until confirmed", + "title": "TX Manager Time Until Confirmed", "description": "The amount of time elapsed from a transaction being broadcast to being included in a block", "transparent": false, "datasource": { @@ -1755,10 +1764,10 @@ }, { "type": "stat", - "id": 31, + "id": 32, "targets": [ { - "expr": "count(log_poller_query_duration_sum{instance=~\"${instance}\", }) by (evmChainID)", + "expr": "count(log_poller_query_duration_sum{}) by (evmChainID)", "format": "", "legendFormat": "chainId: {{evmChainID}}", "refId": "" @@ -1782,6 +1791,7 @@ "justifyMode": "auto", "textMode": "value_and_name", "wideLayout": true, + "showPercentChange": false, "reduceOptions": { "calcs": [ "last" @@ -1791,7 +1801,7 @@ "titleSize": 10, "valueSize": 18 }, - "showPercentChange": false, + "percentChangeColorMode": "standard", "orientation": "horizontal" }, "fieldConfig": { @@ -1805,16 +1815,16 @@ }, { "type": "timeseries", - "id": 32, + "id": 33, "targets": [ { - "expr": "avg by (query, instance) (sum by (query, job) (rate(log_poller_query_duration_count{instance=~\"${instance}\", }[$__rate_interval])))", + "expr": "avg by (query, instance) (sum by (query, job) (rate(log_poller_query_duration_count{}[$__rate_interval])))", "format": "", "legendFormat": "{{instance}} - {{query}}", "refId": "" }, { - "expr": "avg (sum by(instance) (rate(log_poller_query_duration_count{instance=~\"${instance}\", }[$__rate_interval])))", + "expr": "avg (sum by(instance) (rate(log_poller_query_duration_count{}[$__rate_interval])))", "format": "", "legendFormat": "Total", "refId": "" @@ -1861,10 +1871,10 @@ }, { "type": "timeseries", - "id": 33, + "id": 34, "targets": [ { - "expr": "avg by (instance, type) (sum by (type, instance) (rate(log_poller_query_duration_count{instance=~\"${instance}\", }[$__rate_interval])))", + "expr": "avg by (instance, type) (sum by (type, instance) (rate(log_poller_query_duration_count{}[$__rate_interval])))", "format": "", "legendFormat": "{{instance}} - {{type}}", "refId": "" @@ -1911,10 +1921,10 @@ }, { "type": "timeseries", - "id": 34, + "id": 35, "targets": [ { - "expr": "avg by (instance, query) (log_poller_query_dataset_size{instance=~\"${instance}\", })", + "expr": "avg by (instance, query) (log_poller_query_dataset_size{})", "format": "", "legendFormat": "{{instance}} - {{query}}", "refId": "" @@ -1961,10 +1971,10 @@ }, { "type": "timeseries", - "id": 35, + "id": 36, "targets": [ { - "expr": "max by (instance, query) (log_poller_query_dataset_size{instance=~\"${instance}\", })", + "expr": "max by (instance, query) (log_poller_query_dataset_size{})", "format": "", "legendFormat": "{{instance}} - {{query}}", "refId": "" @@ -2011,10 +2021,10 @@ }, { "type": "timeseries", - "id": 36, + "id": 37, "targets": [ { - "expr": "max by (evmChainID) (log_poller_query_dataset_size{instance=~\"${instance}\", })", + "expr": "max by (evmChainID) (log_poller_query_dataset_size{})", "format": "", "legendFormat": "{{evmChainID}}", "refId": "" @@ -2061,10 +2071,10 @@ }, { "type": "timeseries", - "id": 37, + "id": 38, "targets": [ { - "expr": "histogram_quantile(0.5, sum(rate(log_poller_query_duration_bucket{instance=~\"${instance}\", }[$__rate_interval])) by (le, instance, query)) / 1e6", + "expr": "histogram_quantile(0.5, sum(rate(log_poller_query_duration_bucket{}[$__rate_interval])) by (le, instance, query)) / 1e6", "format": "", "legendFormat": "{{instance}} - {{query}}", "refId": "" @@ -2111,10 +2121,10 @@ }, { "type": "timeseries", - "id": 38, + "id": 39, "targets": [ { - "expr": "histogram_quantile(0.9, sum(rate(log_poller_query_duration_bucket{instance=~\"${instance}\", }[$__rate_interval])) by (le, instance, query)) / 1e6", + "expr": "histogram_quantile(0.9, sum(rate(log_poller_query_duration_bucket{}[$__rate_interval])) by (le, instance, query)) / 1e6", "format": "", "legendFormat": "{{instance}} - {{query}}", "refId": "" @@ -2161,10 +2171,10 @@ }, { "type": "timeseries", - "id": 39, + "id": 40, "targets": [ { - "expr": "histogram_quantile(0.99, sum(rate(log_poller_query_duration_bucket{instance=~\"${instance}\", }[$__rate_interval])) by (le, instance, query)) / 1e6", + "expr": "histogram_quantile(0.99, sum(rate(log_poller_query_duration_bucket{}[$__rate_interval])) by (le, instance, query)) / 1e6", "format": "", "legendFormat": "{{instance}} - {{query}}", "refId": "" @@ -2211,10 +2221,10 @@ }, { "type": "timeseries", - "id": 40, + "id": 41, "targets": [ { - "expr": "avg by (evmChainID) (log_poller_logs_inserted{instance=~\"${instance}\", })", + "expr": "avg by (evmChainID) (log_poller_logs_inserted{})", "format": "", "legendFormat": "{{evmChainID}}", "refId": "" @@ -2261,10 +2271,10 @@ }, { "type": "timeseries", - "id": 41, + "id": 42, "targets": [ { - "expr": "avg by (evmChainID) (rate(log_poller_logs_inserted{instance=~\"${instance}\", }[$__rate_interval]))", + "expr": "avg by (evmChainID) (rate(log_poller_logs_inserted{}[$__rate_interval]))", "format": "", "legendFormat": "{{evmChainID}}", "refId": "" @@ -2311,10 +2321,10 @@ }, { "type": "timeseries", - "id": 42, + "id": 43, "targets": [ { - "expr": "avg by (evmChainID) (log_poller_blocks_inserted{instance=~\"${instance}\", })", + "expr": "avg by (evmChainID) (log_poller_blocks_inserted{})", "format": "", "legendFormat": "{{evmChainID}}", "refId": "" @@ -2361,10 +2371,10 @@ }, { "type": "timeseries", - "id": 43, + "id": 44, "targets": [ { - "expr": "avg by (evmChainID) (rate(log_poller_blocks_inserted{instance=~\"${instance}\", }[$__rate_interval]))", + "expr": "avg by (evmChainID) (rate(log_poller_blocks_inserted{}[$__rate_interval]))", "format": "", "legendFormat": "{{evmChainID}}", "refId": "" @@ -2424,10 +2434,10 @@ }, { "type": "timeseries", - "id": 44, + "id": 45, "targets": [ { - "expr": "sum(feeds_job_proposal_requests{instance=~\"${instance}\", }) by (instance)", + "expr": "sum(feeds_job_proposal_requests{}) by (instance)", "format": "", "legendFormat": "{{instance}}", "refId": "" @@ -2474,10 +2484,10 @@ }, { "type": "timeseries", - "id": 45, + "id": 46, "targets": [ { - "expr": "sum(feeds_job_proposal_count{instance=~\"${instance}\", }) by (instance)", + "expr": "sum(feeds_job_proposal_count{}) by (instance)", "format": "", "legendFormat": "{{instance}}", "refId": "" @@ -2537,10 +2547,10 @@ }, { "type": "timeseries", - "id": 46, + "id": 47, "targets": [ { - "expr": "sum(mailbox_load_percent{instance=~\"${instance}\", }) by (capacity, name, instance)", + "expr": "sum(mailbox_load_percent{}) by (capacity, name, instance)", "format": "", "legendFormat": "{{instance}} - Capacity: {{capacity}} - {{name}}", "refId": "" @@ -2600,10 +2610,10 @@ }, { "type": "timeseries", - "id": 47, + "id": 48, "targets": [ { - "expr": "sum(log_panic_count{instance=~\"${instance}\", }) by (instance)", + "expr": "sum(log_panic_count{}) by (instance)", "format": "", "legendFormat": "{{instance}} - panic", "refId": "" @@ -2650,10 +2660,10 @@ }, { "type": "timeseries", - "id": 48, + "id": 49, "targets": [ { - "expr": "sum(log_fatal_count{instance=~\"${instance}\", }) by (instance)", + "expr": "sum(log_fatal_count{}) by (instance)", "format": "", "legendFormat": "{{instance}} - fatal", "refId": "" @@ -2700,10 +2710,10 @@ }, { "type": "timeseries", - "id": 49, + "id": 50, "targets": [ { - "expr": "sum(log_critical_count{instance=~\"${instance}\", }) by (instance)", + "expr": "sum(log_critical_count{}) by (instance)", "format": "", "legendFormat": "{{instance}} - critical", "refId": "" @@ -2750,10 +2760,10 @@ }, { "type": "timeseries", - "id": 50, + "id": 51, "targets": [ { - "expr": "sum(log_warn_count{instance=~\"${instance}\", }) by (instance)", + "expr": "sum(log_warn_count{}) by (instance)", "format": "", "legendFormat": "{{instance}} - warn", "refId": "" @@ -2800,10 +2810,10 @@ }, { "type": "timeseries", - "id": 51, + "id": 52, "targets": [ { - "expr": "sum(log_error_count{instance=~\"${instance}\", }) by (instance)", + "expr": "sum(log_error_count{}) by (instance)", "format": "", "legendFormat": "{{instance}} - error", "refId": "" @@ -2863,10 +2873,10 @@ }, { "type": "timeseries", - "id": 52, + "id": 53, "targets": [ { - "expr": "sum(rate(log_panic_count{instance=~\"${instance}\", }[$__rate_interval])) by (instance)", + "expr": "sum(rate(log_panic_count{}[$__rate_interval])) by (instance)", "format": "", "legendFormat": "{{instance}} - error", "refId": "" @@ -2913,10 +2923,10 @@ }, { "type": "timeseries", - "id": 53, + "id": 54, "targets": [ { - "expr": "sum(rate(log_fatal_count{instance=~\"${instance}\", }[$__rate_interval])) by (instance)", + "expr": "sum(rate(log_fatal_count{}[$__rate_interval])) by (instance)", "format": "", "legendFormat": "{{instance}} - error", "refId": "" @@ -2963,10 +2973,10 @@ }, { "type": "timeseries", - "id": 54, + "id": 55, "targets": [ { - "expr": "sum(rate(log_critical_count{instance=~\"${instance}\", }[$__rate_interval])) by (instance)", + "expr": "sum(rate(log_critical_count{}[$__rate_interval])) by (instance)", "format": "", "legendFormat": "{{instance}} - error", "refId": "" @@ -3013,10 +3023,10 @@ }, { "type": "timeseries", - "id": 55, + "id": 56, "targets": [ { - "expr": "sum(rate(log_warn_count{instance=~\"${instance}\", }[$__rate_interval])) by (instance)", + "expr": "sum(rate(log_warn_count{}[$__rate_interval])) by (instance)", "format": "", "legendFormat": "{{instance}} - error", "refId": "" @@ -3063,10 +3073,10 @@ }, { "type": "timeseries", - "id": 56, + "id": 57, "targets": [ { - "expr": "sum(rate(log_error_count{instance=~\"${instance}\", }[$__rate_interval])) by (instance)", + "expr": "sum(rate(log_error_count{}[$__rate_interval])) by (instance)", "format": "", "legendFormat": "{{instance}} - error", "refId": "" @@ -3126,10 +3136,10 @@ }, { "type": "timeseries", - "id": 57, + "id": 58, "targets": [ { - "expr": "evm_pool_rpc_node_highest_seen_block{instance=~\"${instance}\", }", + "expr": "evm_pool_rpc_node_highest_seen_block{}", "format": "", "legendFormat": "{{instance}}", "refId": "" @@ -3176,10 +3186,10 @@ }, { "type": "timeseries", - "id": 58, + "id": 59, "targets": [ { - "expr": "evm_pool_rpc_node_num_seen_blocks{instance=~\"${instance}\", }", + "expr": "evm_pool_rpc_node_num_seen_blocks{}", "format": "", "legendFormat": "{{instance}}", "refId": "" @@ -3226,10 +3236,10 @@ }, { "type": "timeseries", - "id": 59, + "id": 60, "targets": [ { - "expr": "evm_pool_rpc_node_polls_total{instance=~\"${instance}\", }", + "expr": "evm_pool_rpc_node_polls_total{}", "format": "", "legendFormat": "{{instance}}", "refId": "" @@ -3276,10 +3286,10 @@ }, { "type": "timeseries", - "id": 60, + "id": 61, "targets": [ { - "expr": "evm_pool_rpc_node_polls_failed{instance=~\"${instance}\", }", + "expr": "evm_pool_rpc_node_polls_failed{}", "format": "", "legendFormat": "{{instance}}", "refId": "" @@ -3326,10 +3336,10 @@ }, { "type": "timeseries", - "id": 61, + "id": 62, "targets": [ { - "expr": "evm_pool_rpc_node_polls_success{instance=~\"${instance}\", }", + "expr": "evm_pool_rpc_node_polls_success{}", "format": "", "legendFormat": "{{instance}}", "refId": "" @@ -3389,10 +3399,10 @@ }, { "type": "stat", - "id": 62, + "id": 63, "targets": [ { - "expr": "sum(multi_node_states{instance=~\"${instance}\", state=\"Alive\"}) by (instance, chainId)", + "expr": "sum(multi_node_states{state=\"Alive\"}) by (instance, chainId)", "format": "", "legendFormat": "{{instance}} - {{chainId}}", "refId": "" @@ -3416,6 +3426,7 @@ "justifyMode": "auto", "textMode": "value_and_name", "wideLayout": true, + "showPercentChange": false, "reduceOptions": { "calcs": [ "last" @@ -3425,7 +3436,7 @@ "titleSize": 10, "valueSize": 18 }, - "showPercentChange": false, + "percentChangeColorMode": "standard", "orientation": "horizontal" }, "fieldConfig": { @@ -3439,10 +3450,10 @@ }, { "type": "stat", - "id": 63, + "id": 64, "targets": [ { - "expr": "sum(multi_node_states{instance=~\"${instance}\", state=\"Closed\"}) by (instance, chainId)", + "expr": "sum(multi_node_states{state=\"Closed\"}) by (instance, chainId)", "format": "", "legendFormat": "{{instance}} - {{chainId}}", "refId": "" @@ -3466,6 +3477,7 @@ "justifyMode": "auto", "textMode": "value_and_name", "wideLayout": true, + "showPercentChange": false, "reduceOptions": { "calcs": [ "last" @@ -3475,7 +3487,7 @@ "titleSize": 10, "valueSize": 18 }, - "showPercentChange": false, + "percentChangeColorMode": "standard", "orientation": "horizontal" }, "fieldConfig": { @@ -3489,10 +3501,10 @@ }, { "type": "stat", - "id": 64, + "id": 65, "targets": [ { - "expr": "sum(multi_node_states{instance=~\"${instance}\", state=\"Dialed\"}) by (instance, chainId)", + "expr": "sum(multi_node_states{state=\"Dialed\"}) by (instance, chainId)", "format": "", "legendFormat": "{{instance}} - {{chainId}}", "refId": "" @@ -3516,6 +3528,7 @@ "justifyMode": "auto", "textMode": "value_and_name", "wideLayout": true, + "showPercentChange": false, "reduceOptions": { "calcs": [ "last" @@ -3525,7 +3538,7 @@ "titleSize": 10, "valueSize": 18 }, - "showPercentChange": false, + "percentChangeColorMode": "standard", "orientation": "horizontal" }, "fieldConfig": { @@ -3539,10 +3552,10 @@ }, { "type": "stat", - "id": 65, + "id": 66, "targets": [ { - "expr": "sum(multi_node_states{instance=~\"${instance}\", state=\"InvalidChainID\"}) by (instance, chainId)", + "expr": "sum(multi_node_states{state=\"InvalidChainID\"}) by (instance, chainId)", "format": "", "legendFormat": "{{instance}} - {{chainId}}", "refId": "" @@ -3566,6 +3579,7 @@ "justifyMode": "auto", "textMode": "value_and_name", "wideLayout": true, + "showPercentChange": false, "reduceOptions": { "calcs": [ "last" @@ -3575,7 +3589,7 @@ "titleSize": 10, "valueSize": 18 }, - "showPercentChange": false, + "percentChangeColorMode": "standard", "orientation": "horizontal" }, "fieldConfig": { @@ -3589,10 +3603,10 @@ }, { "type": "stat", - "id": 66, + "id": 67, "targets": [ { - "expr": "sum(multi_node_states{instance=~\"${instance}\", state=\"OutOfSync\"}) by (instance, chainId)", + "expr": "sum(multi_node_states{state=\"OutOfSync\"}) by (instance, chainId)", "format": "", "legendFormat": "{{instance}} - {{chainId}}", "refId": "" @@ -3616,6 +3630,7 @@ "justifyMode": "auto", "textMode": "value_and_name", "wideLayout": true, + "showPercentChange": false, "reduceOptions": { "calcs": [ "last" @@ -3625,7 +3640,7 @@ "titleSize": 10, "valueSize": 18 }, - "showPercentChange": false, + "percentChangeColorMode": "standard", "orientation": "horizontal" }, "fieldConfig": { @@ -3639,10 +3654,10 @@ }, { "type": "stat", - "id": 67, + "id": 68, "targets": [ { - "expr": "sum(multi_node_states{instance=~\"${instance}\", state=\"Undialed\"}) by (instance, chainId)", + "expr": "sum(multi_node_states{state=\"Undialed\"}) by (instance, chainId)", "format": "", "legendFormat": "{{instance}} - {{chainId}}", "refId": "" @@ -3666,6 +3681,7 @@ "justifyMode": "auto", "textMode": "value_and_name", "wideLayout": true, + "showPercentChange": false, "reduceOptions": { "calcs": [ "last" @@ -3675,7 +3691,7 @@ "titleSize": 10, "valueSize": 18 }, - "showPercentChange": false, + "percentChangeColorMode": "standard", "orientation": "horizontal" }, "fieldConfig": { @@ -3689,10 +3705,10 @@ }, { "type": "stat", - "id": 68, + "id": 69, "targets": [ { - "expr": "sum(multi_node_states{instance=~\"${instance}\", state=\"Unreachable\"}) by (instance, chainId)", + "expr": "sum(multi_node_states{state=\"Unreachable\"}) by (instance, chainId)", "format": "", "legendFormat": "{{instance}} - {{chainId}}", "refId": "" @@ -3716,6 +3732,7 @@ "justifyMode": "auto", "textMode": "value_and_name", "wideLayout": true, + "showPercentChange": false, "reduceOptions": { "calcs": [ "last" @@ -3725,7 +3742,7 @@ "titleSize": 10, "valueSize": 18 }, - "showPercentChange": false, + "percentChangeColorMode": "standard", "orientation": "horizontal" }, "fieldConfig": { @@ -3739,10 +3756,10 @@ }, { "type": "stat", - "id": 69, + "id": 70, "targets": [ { - "expr": "sum(multi_node_states{instance=~\"${instance}\", state=\"Unusable\"}) by (instance, chainId)", + "expr": "sum(multi_node_states{state=\"Unusable\"}) by (instance, chainId)", "format": "", "legendFormat": "{{instance}} - {{chainId}}", "refId": "" @@ -3766,6 +3783,7 @@ "justifyMode": "auto", "textMode": "value_and_name", "wideLayout": true, + "showPercentChange": false, "reduceOptions": { "calcs": [ "last" @@ -3775,7 +3793,7 @@ "titleSize": 10, "valueSize": 18 }, - "showPercentChange": false, + "percentChangeColorMode": "standard", "orientation": "horizontal" }, "fieldConfig": { @@ -3802,10 +3820,10 @@ }, { "type": "timeseries", - "id": 70, + "id": 71, "targets": [ { - "expr": "sum(increase(evm_pool_rpc_node_calls_success{instance=~\"${instance}\", }[$__rate_interval])) by (instance, evmChainID, nodeName) / sum(increase(evm_pool_rpc_node_calls_total{instance=~\"${instance}\", }[$__rate_interval])) by (instance, evmChainID, nodeName)", + "expr": "sum(increase(evm_pool_rpc_node_calls_success{}[$__rate_interval])) by (instance, evmChainID, nodeName) / sum(increase(evm_pool_rpc_node_calls_total{}[$__rate_interval])) by (instance, evmChainID, nodeName)", "format": "", "legendFormat": "{{instance}} - {{nodeName}}", "refId": "" @@ -3874,10 +3892,10 @@ }, { "type": "timeseries", - "id": 71, + "id": 72, "targets": [ { - "expr": "sum(increase(evm_pool_rpc_node_dials_failed{instance=~\"${instance}\", }[$__rate_interval])) by (instance, evmChainID, nodeName) / sum(increase(evm_pool_rpc_node_calls_total{instance=~\"${instance}\", }[$__rate_interval])) by (instance, evmChainID, nodeName)", + "expr": "sum(increase(evm_pool_rpc_node_dials_failed{}[$__rate_interval])) by (instance, evmChainID, nodeName) / sum(increase(evm_pool_rpc_node_calls_total{}[$__rate_interval])) by (instance, evmChainID, nodeName)", "format": "", "legendFormat": "{{instance}} - {{evmChainID}} - {{nodeName}}", "refId": "" @@ -3946,40 +3964,40 @@ }, { "type": "timeseries", - "id": 72, + "id": 73, "targets": [ { - "expr": "evm_pool_rpc_node_num_transitions_to_alive{instance=~\"${instance}\", }", + "expr": "evm_pool_rpc_node_num_transitions_to_alive{}", "format": "", "legendFormat": "Alive", "refId": "" }, { - "expr": "evm_pool_rpc_node_num_transitions_to_in_sync{instance=~\"${instance}\", }", + "expr": "evm_pool_rpc_node_num_transitions_to_in_sync{}", "format": "", "legendFormat": "InSync", "refId": "" }, { - "expr": "evm_pool_rpc_node_num_transitions_to_out_of_sync{instance=~\"${instance}\", }", + "expr": "evm_pool_rpc_node_num_transitions_to_out_of_sync{}", "format": "", "legendFormat": "OutOfSync", "refId": "" }, { - "expr": "evm_pool_rpc_node_num_transitions_to_unreachable{instance=~\"${instance}\", }", + "expr": "evm_pool_rpc_node_num_transitions_to_unreachable{}", "format": "", "legendFormat": "UnReachable", "refId": "" }, { - "expr": "evm_pool_rpc_node_num_transitions_to_invalid_chain_id{instance=~\"${instance}\", }", + "expr": "evm_pool_rpc_node_num_transitions_to_invalid_chain_id{}", "format": "", "legendFormat": "InvalidChainID", "refId": "" }, { - "expr": "evm_pool_rpc_node_num_transitions_to_unusable{instance=~\"${instance}\", }", + "expr": "evm_pool_rpc_node_num_transitions_to_unusable{}", "format": "", "legendFormat": "TransitionToUnusable", "refId": "" @@ -4026,10 +4044,10 @@ }, { "type": "timeseries", - "id": 73, + "id": 74, "targets": [ { - "expr": "evm_pool_rpc_node_states{instance=~\"${instance}\", }", + "expr": "evm_pool_rpc_node_states{}", "format": "", "legendFormat": "{{instance}} - {{evmChainID}} - {{state}}", "refId": "" @@ -4076,10 +4094,10 @@ }, { "type": "timeseries", - "id": 74, + "id": 75, "targets": [ { - "expr": "sum(increase(evm_pool_rpc_node_verifies_success{instance=~\"${instance}\", }[$__rate_interval])) by (instance, evmChainID, nodeName) / sum(increase(evm_pool_rpc_node_verifies{instance=~\"${instance}\", }[$__rate_interval])) by (instance, evmChainID, nodeName) * 100", + "expr": "sum(increase(evm_pool_rpc_node_verifies_success{}[$__rate_interval])) by (instance, evmChainID, nodeName) / sum(increase(evm_pool_rpc_node_verifies{}[$__rate_interval])) by (instance, evmChainID, nodeName) * 100", "format": "", "legendFormat": "{{instance}} - {{evmChainID}} - {{nodeName}}", "refId": "" @@ -4126,10 +4144,10 @@ }, { "type": "timeseries", - "id": 75, + "id": 76, "targets": [ { - "expr": "sum(increase(evm_pool_rpc_node_verifies_failed{instance=~\"${instance}\", }[$__rate_interval])) by (instance, evmChainID, nodeName) / sum(increase(evm_pool_rpc_node_verifies{instance=~\"${instance}\", }[$__rate_interval])) by (instance, evmChainID, nodeName) * 100", + "expr": "sum(increase(evm_pool_rpc_node_verifies_failed{}[$__rate_interval])) by (instance, evmChainID, nodeName) / sum(increase(evm_pool_rpc_node_verifies{}[$__rate_interval])) by (instance, evmChainID, nodeName) * 100", "format": "", "legendFormat": "{{instance}} - {{evmChainID}} - {{nodeName}}", "refId": "" @@ -4189,10 +4207,10 @@ }, { "type": "timeseries", - "id": 76, + "id": 77, "targets": [ { - "expr": "histogram_quantile(0.90, sum(rate(evm_pool_rpc_node_rpc_call_time_bucket{instance=~\"${instance}\", }[$__rate_interval])) by (instance, le, rpcCallName)) / 1e6", + "expr": "histogram_quantile(0.90, sum(rate(evm_pool_rpc_node_rpc_call_time_bucket{}[$__rate_interval])) by (instance, le, rpcCallName)) / 1e6", "format": "", "legendFormat": "{{instance}} - {{rpcCallName}}", "refId": "" @@ -4239,10 +4257,10 @@ }, { "type": "timeseries", - "id": 77, + "id": 78, "targets": [ { - "expr": "histogram_quantile(0.95, sum(rate(evm_pool_rpc_node_rpc_call_time_bucket{instance=~\"${instance}\", }[$__rate_interval])) by (instance, le, rpcCallName)) / 1e6", + "expr": "histogram_quantile(0.95, sum(rate(evm_pool_rpc_node_rpc_call_time_bucket{}[$__rate_interval])) by (instance, le, rpcCallName)) / 1e6", "format": "", "legendFormat": "{{instance}} - {{rpcCallName}}", "refId": "" @@ -4289,10 +4307,10 @@ }, { "type": "timeseries", - "id": 78, + "id": 79, "targets": [ { - "expr": "histogram_quantile(0.99, sum(rate(evm_pool_rpc_node_rpc_call_time_bucket{instance=~\"${instance}\", }[$__rate_interval])) by (instance, le, rpcCallName)) / 1e6", + "expr": "histogram_quantile(0.99, sum(rate(evm_pool_rpc_node_rpc_call_time_bucket{}[$__rate_interval])) by (instance, le, rpcCallName)) / 1e6", "format": "", "legendFormat": "{{instance}} - {{rpcCallName}}", "refId": "" @@ -4352,10 +4370,10 @@ }, { "type": "timeseries", - "id": 79, + "id": 80, "targets": [ { - "expr": "sum(gas_updater_all_gas_price_percentiles{instance=~\"${instance}\", }) by (instance, evmChainID, percentile)", + "expr": "sum(gas_updater_all_gas_price_percentiles{}) by (instance, evmChainID, percentile)", "format": "", "legendFormat": "{{instance}} - {{evmChainID}} - {{percentile}}", "refId": "" @@ -4402,10 +4420,10 @@ }, { "type": "timeseries", - "id": 80, + "id": 81, "targets": [ { - "expr": "sum(gas_updater_all_tip_cap_percentiles{instance=~\"${instance}\", }) by (instance, evmChainID, percentile)", + "expr": "sum(gas_updater_all_tip_cap_percentiles{}) by (instance, evmChainID, percentile)", "format": "", "legendFormat": "{{instance}} - {{evmChainID}} - {{percentile}}", "refId": "" @@ -4452,10 +4470,10 @@ }, { "type": "timeseries", - "id": 81, + "id": 82, "targets": [ { - "expr": "sum(gas_updater_set_gas_price{instance=~\"${instance}\", }) by (instance)", + "expr": "sum(gas_updater_set_gas_price{}) by (instance)", "format": "", "legendFormat": "{{instance}}", "refId": "" @@ -4502,10 +4520,10 @@ }, { "type": "timeseries", - "id": 82, + "id": 83, "targets": [ { - "expr": "sum(gas_updater_set_tip_cap{instance=~\"${instance}\", }) by (instance)", + "expr": "sum(gas_updater_set_tip_cap{}) by (instance)", "format": "", "legendFormat": "{{instance}}", "refId": "" @@ -4552,10 +4570,10 @@ }, { "type": "timeseries", - "id": 83, + "id": 84, "targets": [ { - "expr": "sum(gas_updater_current_base_fee{instance=~\"${instance}\", }) by (instance)", + "expr": "sum(gas_updater_current_base_fee{}) by (instance)", "format": "", "legendFormat": "{{instance}}", "refId": "" @@ -4602,10 +4620,10 @@ }, { "type": "timeseries", - "id": 84, + "id": 85, "targets": [ { - "expr": "sum(block_history_estimator_connectivity_failure_count{instance=~\"${instance}\", }) by (instance)", + "expr": "sum(block_history_estimator_connectivity_failure_count{}) by (instance)", "format": "", "legendFormat": "{{instance}}", "refId": "" @@ -4665,10 +4683,10 @@ }, { "type": "timeseries", - "id": 85, + "id": 86, "targets": [ { - "expr": "pipeline_task_execution_time{instance=~\"${instance}\", } / 1e6", + "expr": "pipeline_task_execution_time{} / 1e6", "format": "", "legendFormat": "{{instance}} JobID: {{job_id}}", "refId": "" @@ -4715,10 +4733,10 @@ }, { "type": "timeseries", - "id": 86, + "id": 87, "targets": [ { - "expr": "pipeline_run_errors{instance=~\"${instance}\", }", + "expr": "pipeline_run_errors{}", "format": "", "legendFormat": "{{instance}} JobID: {{job_id}}", "refId": "" @@ -4765,10 +4783,10 @@ }, { "type": "timeseries", - "id": 87, + "id": 88, "targets": [ { - "expr": "pipeline_run_total_time_to_completion{instance=~\"${instance}\", } / 1e6", + "expr": "pipeline_run_total_time_to_completion{} / 1e6", "format": "", "legendFormat": "{{instance}} JobID: {{job_id}}", "refId": "" @@ -4815,10 +4833,10 @@ }, { "type": "timeseries", - "id": 88, + "id": 89, "targets": [ { - "expr": "pipeline_tasks_total_finished{instance=~\"${instance}\", }", + "expr": "pipeline_tasks_total_finished{}", "format": "", "legendFormat": "{{instance}} JobID: {{job_id}}", "refId": "" @@ -4878,10 +4896,10 @@ }, { "type": "timeseries", - "id": 89, + "id": 90, "targets": [ { - "expr": "histogram_quantile(0.95, sum(rate(service_gonic_request_duration_bucket{instance=~\"${instance}\", }[$__rate_interval])) by (instance, le, path, method))", + "expr": "histogram_quantile(0.95, sum(rate(service_gonic_request_duration_bucket{}[$__rate_interval])) by (instance, le, path, method))", "format": "", "legendFormat": "{{instance}} - {{method}} - {{path}}", "refId": "" @@ -4928,10 +4946,10 @@ }, { "type": "timeseries", - "id": 90, + "id": 91, "targets": [ { - "expr": "sum(rate(service_gonic_requests_total{instance=~\"${instance}\", }[$__rate_interval])) by (instance, path, method, code)", + "expr": "sum(rate(service_gonic_requests_total{}[$__rate_interval])) by (instance, path, method, code)", "format": "", "legendFormat": "{{instance}} - {{method}} - {{path}} - {{code}}", "refId": "" @@ -4978,10 +4996,10 @@ }, { "type": "timeseries", - "id": 91, + "id": 92, "targets": [ { - "expr": "avg(rate(service_gonic_request_size_bytes_sum{instance=~\"${instance}\", }[$__rate_interval])) by (instance)/avg(rate(service_gonic_request_size_bytes_count{instance=~\"${instance}\", }[$__rate_interval])) by (instance)", + "expr": "avg(rate(service_gonic_request_size_bytes_sum{}[$__rate_interval])) by (instance)/avg(rate(service_gonic_request_size_bytes_count{}[$__rate_interval])) by (instance)", "format": "", "legendFormat": "{{instance}}", "refId": "" @@ -5028,10 +5046,10 @@ }, { "type": "timeseries", - "id": 92, + "id": 93, "targets": [ { - "expr": "avg(rate(service_gonic_response_size_bytes_sum{instance=~\"${instance}\", }[$__rate_interval])) by (instance)/avg(rate(service_gonic_response_size_bytes_count{instance=~\"${instance}\", }[$__rate_interval])) by (instance)", + "expr": "avg(rate(service_gonic_response_size_bytes_sum{}[$__rate_interval])) by (instance)/avg(rate(service_gonic_response_size_bytes_count{}[$__rate_interval])) by (instance)", "format": "", "legendFormat": "{{instance}}", "refId": "" @@ -5091,10 +5109,10 @@ }, { "type": "timeseries", - "id": 93, + "id": 94, "targets": [ { - "expr": "sum(rate(promhttp_metric_handler_requests_total{instance=~\"${instance}\", }[$__rate_interval])) by (instance, code)", + "expr": "sum(rate(promhttp_metric_handler_requests_total{}[$__rate_interval])) by (instance, code)", "format": "", "legendFormat": "{{instance}} - {{code}}", "refId": "" @@ -5154,10 +5172,10 @@ }, { "type": "timeseries", - "id": 94, + "id": 95, "targets": [ { - "expr": "sum(go_threads{instance=~\"${instance}\", }) by (instance)", + "expr": "sum(go_threads{}) by (instance)", "format": "", "legendFormat": "{{instance}}", "refId": "" @@ -5204,10 +5222,10 @@ }, { "type": "stat", - "id": 95, + "id": 96, "targets": [ { - "expr": "sum(go_memstats_heap_alloc_bytes{instance=~\"${instance}\", }) by (instance)", + "expr": "sum(go_memstats_heap_alloc_bytes{}) by (instance)", "format": "", "legendFormat": "", "refId": "" @@ -5231,6 +5249,7 @@ "justifyMode": "auto", "textMode": "value", "wideLayout": true, + "showPercentChange": false, "reduceOptions": { "calcs": [ "last" @@ -5240,7 +5259,7 @@ "titleSize": 10, "valueSize": 18 }, - "showPercentChange": false, + "percentChangeColorMode": "standard", "orientation": "horizontal" }, "fieldConfig": { @@ -5254,10 +5273,10 @@ }, { "type": "timeseries", - "id": 96, + "id": 97, "targets": [ { - "expr": "sum(go_memstats_heap_alloc_bytes{instance=~\"${instance}\", }) by (instance)", + "expr": "sum(go_memstats_heap_alloc_bytes{}) by (instance)", "format": "", "legendFormat": "{{instance}}", "refId": "" @@ -5304,34 +5323,34 @@ }, { "type": "timeseries", - "id": 97, + "id": 98, "targets": [ { - "expr": "go_memstats_heap_alloc_bytes{instance=~\"${instance}\", }", + "expr": "go_memstats_heap_alloc_bytes{}", "format": "", "legendFormat": "{{instance}} - Alloc", "refId": "" }, { - "expr": "go_memstats_heap_sys_bytes{instance=~\"${instance}\", }", + "expr": "go_memstats_heap_sys_bytes{}", "format": "", "legendFormat": "{{instance}} - Sys", "refId": "" }, { - "expr": "go_memstats_heap_idle_bytes{instance=~\"${instance}\", }", + "expr": "go_memstats_heap_idle_bytes{}", "format": "", "legendFormat": "{{instance}} - Idle", "refId": "" }, { - "expr": "go_memstats_heap_inuse_bytes{instance=~\"${instance}\", }", + "expr": "go_memstats_heap_inuse_bytes{}", "format": "", "legendFormat": "{{instance}} - InUse", "refId": "" }, { - "expr": "go_memstats_heap_released_bytes{instance=~\"${instance}\", }", + "expr": "go_memstats_heap_released_bytes{}", "format": "", "legendFormat": "{{instance}} - Released", "refId": "" @@ -5378,52 +5397,52 @@ }, { "type": "timeseries", - "id": 98, + "id": 99, "targets": [ { - "expr": "go_memstats_mspan_inuse_bytes{instance=~\"${instance}\", }", + "expr": "go_memstats_mspan_inuse_bytes{}", "format": "", "legendFormat": "{{instance}} - Total InUse", "refId": "" }, { - "expr": "go_memstats_mspan_sys_bytes{instance=~\"${instance}\", }", + "expr": "go_memstats_mspan_sys_bytes{}", "format": "", "legendFormat": "{{instance}} - Total Sys", "refId": "" }, { - "expr": "go_memstats_mcache_inuse_bytes{instance=~\"${instance}\", }", + "expr": "go_memstats_mcache_inuse_bytes{}", "format": "", "legendFormat": "{{instance}} - Cache InUse", "refId": "" }, { - "expr": "go_memstats_mcache_sys_bytes{instance=~\"${instance}\", }", + "expr": "go_memstats_mcache_sys_bytes{}", "format": "", "legendFormat": "{{instance}} - Cache Sys", "refId": "" }, { - "expr": "go_memstats_buck_hash_sys_bytes{instance=~\"${instance}\", }", + "expr": "go_memstats_buck_hash_sys_bytes{}", "format": "", "legendFormat": "{{instance}} - Hash Sys", "refId": "" }, { - "expr": "go_memstats_gc_sys_bytes{instance=~\"${instance}\", }", + "expr": "go_memstats_gc_sys_bytes{}", "format": "", "legendFormat": "{{instance}} - GC Sys", "refId": "" }, { - "expr": "go_memstats_other_sys_bytes{instance=~\"${instance}\", }", + "expr": "go_memstats_other_sys_bytes{}", "format": "", "legendFormat": "{{instance}} - bytes of memory are used for other runtime allocations", "refId": "" }, { - "expr": "go_memstats_next_gc_bytes{instance=~\"${instance}\", }", + "expr": "go_memstats_next_gc_bytes{}", "format": "", "legendFormat": "{{instance}} - Next GC", "refId": "" @@ -5470,16 +5489,16 @@ }, { "type": "timeseries", - "id": 99, + "id": 100, "targets": [ { - "expr": "go_memstats_stack_inuse_bytes{instance=~\"${instance}\", }", + "expr": "go_memstats_stack_inuse_bytes{}", "format": "", "legendFormat": "{{instance}} - InUse", "refId": "" }, { - "expr": "go_memstats_stack_sys_bytes{instance=~\"${instance}\", }", + "expr": "go_memstats_stack_sys_bytes{}", "format": "", "legendFormat": "{{instance}} - Sys", "refId": "" @@ -5526,10 +5545,10 @@ }, { "type": "timeseries", - "id": 100, + "id": 101, "targets": [ { - "expr": "go_memstats_sys_bytes{instance=~\"${instance}\", }", + "expr": "go_memstats_sys_bytes{}", "format": "", "legendFormat": "{{instance}}", "refId": "" @@ -5576,10 +5595,10 @@ }, { "type": "timeseries", - "id": 101, + "id": 102, "targets": [ { - "expr": "go_memstats_mallocs_total{instance=~\"${instance}\", } - go_memstats_frees_total{instance=~\"${instance}\", }", + "expr": "go_memstats_mallocs_total{} - go_memstats_frees_total{}", "format": "", "legendFormat": "{{instance}}", "refId": "" @@ -5626,10 +5645,10 @@ }, { "type": "timeseries", - "id": 102, + "id": 103, "targets": [ { - "expr": "rate(go_memstats_mallocs_total{instance=~\"${instance}\", }[1m])", + "expr": "rate(go_memstats_mallocs_total{}[1m])", "format": "", "legendFormat": "{{instance}}", "refId": "" @@ -5676,10 +5695,10 @@ }, { "type": "timeseries", - "id": 103, + "id": 104, "targets": [ { - "expr": "rate(go_memstats_lookups_total{instance=~\"${instance}\", }[1m])", + "expr": "rate(go_memstats_lookups_total{}[1m])", "format": "", "legendFormat": "{{instance}}", "refId": "" @@ -5726,10 +5745,10 @@ }, { "type": "timeseries", - "id": 104, + "id": 105, "targets": [ { - "expr": "go_goroutines{instance=~\"${instance}\", }", + "expr": "go_goroutines{}", "format": "", "legendFormat": "{{instance}}", "refId": "" @@ -5781,6 +5800,7 @@ "type": "query", "name": "instance", "label": "Instance", + "description": "", "query": "label_values(instance)", "datasource": { "uid": "Prometheus" @@ -5862,7 +5882,7 @@ { "evaluator": { "params": [ - 90, + 1, 0 ], "type": "lt" diff --git a/observability-lib/dashboards/k8s-resources/component_test.go b/observability-lib/dashboards/k8s-resources/component_test.go index c69af4a..c6b6b02 100644 --- a/observability-lib/dashboards/k8s-resources/component_test.go +++ b/observability-lib/dashboards/k8s-resources/component_test.go @@ -1,6 +1,7 @@ package k8sresources_test import ( + "flag" "os" "testing" @@ -10,6 +11,44 @@ import ( "github.com/stretchr/testify/require" ) +var update = flag.Bool("update", false, "update golden test files") +var fileOutput = "test-output.json" + +func TestGenerateFile(t *testing.T) { + if *update == false { + t.Skip("skipping test") + } + + testDashboard, err := k8sresources.NewDashboard(&k8sresources.Props{ + Name: "K8s resources", + MetricsDataSource: grafana.NewDataSource("Prometheus", ""), + }) + if err != nil { + t.Errorf("Error creating dashboard: %v", err) + } + json, errJSON := testDashboard.GenerateJSON() + if errJSON != nil { + t.Errorf("Error generating JSON: %v", errJSON) + } + if _, errExists := os.Stat(fileOutput); errExists == nil { + errRemove := os.Remove(fileOutput) + if errRemove != nil { + t.Errorf("Error removing file: %v", errRemove) + } + } + file, errFile := os.Create(fileOutput) + if errFile != nil { + panic(errFile) + } + writeString, err := file.WriteString(string(json)) + if err != nil { + t.Errorf("Error writing to file: %v", writeString) + } + t.Cleanup(func() { + file.Close() + }) +} + func TestNewDashboard(t *testing.T) { t.Run("NewDashboard creates a dashboard", func(t *testing.T) { testDashboard, err := k8sresources.NewDashboard(&k8sresources.Props{ @@ -26,11 +65,11 @@ func TestNewDashboard(t *testing.T) { t.Errorf("Error generating JSON: %v", errJSON) } - jsonCompared, errCompared := os.ReadFile("test-output.json") + jsonCompared, errCompared := os.ReadFile(fileOutput) if errCompared != nil { t.Errorf("Error reading file: %v", errCompared) } - require.ElementsMatch(t, jsonCompared, json) + require.JSONEq(t, string(jsonCompared), string(json)) }) } diff --git a/observability-lib/dashboards/k8s-resources/test-output.json b/observability-lib/dashboards/k8s-resources/test-output.json index 4667b4d..34712d4 100644 --- a/observability-lib/dashboards/k8s-resources/test-output.json +++ b/observability-lib/dashboards/k8s-resources/test-output.json @@ -8,6 +8,7 @@ "Resources" ], "timezone": "browser", + "editable": true, "graphTooltip": 0, "time": { "from": "now-30m", @@ -15,7 +16,7 @@ }, "fiscalYearStartMonth": 0, "refresh": "30s", - "schemaVersion": 0, + "schemaVersion": 39, "panels": [ { "type": "row", @@ -32,7 +33,7 @@ }, { "type": "stat", - "id": 0, + "id": 1, "targets": [ { "expr": "100 * sum(node_namespace_pod_container:container_cpu_usage_seconds_total:sum_irate{cluster=\"$cluster\", namespace=\"$namespace\", pod=\"$pod\"}) by (container) / sum(cluster:namespace:pod_cpu:active:kube_pod_container_resource_requests{cluster=\"$cluster\", namespace=\"$namespace\", pod=\"$pod\"}) by (container)", @@ -61,6 +62,7 @@ "justifyMode": "auto", "textMode": "value", "wideLayout": true, + "showPercentChange": false, "reduceOptions": { "calcs": [ "last" @@ -70,7 +72,7 @@ "titleSize": 10, "valueSize": 18 }, - "showPercentChange": false, + "percentChangeColorMode": "standard", "orientation": "horizontal" }, "fieldConfig": { @@ -84,7 +86,7 @@ }, { "type": "stat", - "id": 1, + "id": 2, "targets": [ { "expr": "100 * sum(node_namespace_pod_container:container_cpu_usage_seconds_total:sum_irate{cluster=\"$cluster\", namespace=\"$namespace\", pod=\"$pod\"}) by (container) / sum(cluster:namespace:pod_cpu:active:kube_pod_container_resource_limits{cluster=\"$cluster\", namespace=\"$namespace\", pod=\"$pod\"}) by (container)", @@ -113,6 +115,7 @@ "justifyMode": "auto", "textMode": "value", "wideLayout": true, + "showPercentChange": false, "reduceOptions": { "calcs": [ "last" @@ -122,7 +125,7 @@ "titleSize": 10, "valueSize": 18 }, - "showPercentChange": false, + "percentChangeColorMode": "standard", "orientation": "horizontal" }, "fieldConfig": { @@ -136,7 +139,7 @@ }, { "type": "stat", - "id": 2, + "id": 3, "targets": [ { "expr": "100 * sum(container_memory_working_set_bytes{job=\"kubelet\", metrics_path=\"/metrics/cadvisor\", cluster=\"$cluster\", namespace=\"$namespace\", pod=\"$pod\", image!=\"\"}) by (container) / sum(cluster:namespace:pod_memory:active:kube_pod_container_resource_requests{cluster=\"$cluster\", namespace=\"$namespace\", pod=\"$pod\"}) by (container)", @@ -165,6 +168,7 @@ "justifyMode": "auto", "textMode": "value", "wideLayout": true, + "showPercentChange": false, "reduceOptions": { "calcs": [ "last" @@ -174,7 +178,7 @@ "titleSize": 10, "valueSize": 18 }, - "showPercentChange": false, + "percentChangeColorMode": "standard", "orientation": "horizontal" }, "fieldConfig": { @@ -188,7 +192,7 @@ }, { "type": "stat", - "id": 3, + "id": 4, "targets": [ { "expr": "100 * sum(container_memory_working_set_bytes{job=\"kubelet\", metrics_path=\"/metrics/cadvisor\", cluster=\"$cluster\", namespace=\"$namespace\", pod=\"$pod\", container!=\"\", image!=\"\"}) by (container) / sum(cluster:namespace:pod_memory:active:kube_pod_container_resource_limits{cluster=\"$cluster\", namespace=\"$namespace\", pod=\"$pod\"}) by (container)", @@ -217,6 +221,7 @@ "justifyMode": "auto", "textMode": "value", "wideLayout": true, + "showPercentChange": false, "reduceOptions": { "calcs": [ "last" @@ -226,7 +231,7 @@ "titleSize": 10, "valueSize": 18 }, - "showPercentChange": false, + "percentChangeColorMode": "standard", "orientation": "horizontal" }, "fieldConfig": { @@ -253,7 +258,7 @@ }, { "type": "stat", - "id": 4, + "id": 5, "targets": [ { "expr": "sum(increase(kube_pod_container_status_restarts_total{pod=~\"$pod\", namespace=~\"${namespace}\"}[$__rate_interval])) by (pod)", @@ -280,6 +285,7 @@ "justifyMode": "auto", "textMode": "value_and_name", "wideLayout": true, + "showPercentChange": false, "reduceOptions": { "calcs": [ "last" @@ -289,7 +295,7 @@ "titleSize": 10, "valueSize": 18 }, - "showPercentChange": false, + "percentChangeColorMode": "standard", "orientation": "horizontal" }, "fieldConfig": { @@ -303,7 +309,7 @@ }, { "type": "stat", - "id": 5, + "id": 6, "targets": [ { "expr": "sum(container_oom_events_total{pod=~\"$pod\", namespace=~\"${namespace}\"}) by (pod)", @@ -330,6 +336,7 @@ "justifyMode": "auto", "textMode": "value_and_name", "wideLayout": true, + "showPercentChange": false, "reduceOptions": { "calcs": [ "last" @@ -339,7 +346,7 @@ "titleSize": 10, "valueSize": 18 }, - "showPercentChange": false, + "percentChangeColorMode": "standard", "orientation": "horizontal" }, "fieldConfig": { @@ -353,7 +360,7 @@ }, { "type": "stat", - "id": 6, + "id": 7, "targets": [ { "expr": "kube_pod_container_status_last_terminated_reason{reason=\"OOMKilled\", pod=~\"$pod\", namespace=~\"${namespace}\"}", @@ -380,6 +387,7 @@ "justifyMode": "auto", "textMode": "value_and_name", "wideLayout": true, + "showPercentChange": false, "reduceOptions": { "calcs": [ "last" @@ -389,7 +397,7 @@ "titleSize": 10, "valueSize": 18 }, - "showPercentChange": false, + "percentChangeColorMode": "standard", "orientation": "horizontal" }, "fieldConfig": { @@ -416,7 +424,7 @@ }, { "type": "timeseries", - "id": 7, + "id": 8, "targets": [ { "expr": "sum(node_namespace_pod_container:container_cpu_usage_seconds_total:sum_irate{pod=~\"$pod\", namespace=~\"${namespace}\"}) by (pod)", @@ -478,7 +486,7 @@ }, { "type": "timeseries", - "id": 8, + "id": 9, "targets": [ { "expr": "sum(container_memory_rss{pod=~\"$pod\", namespace=~\"${namespace}\", container!=\"\"}) by (pod)", @@ -553,7 +561,7 @@ }, { "type": "timeseries", - "id": 9, + "id": 10, "targets": [ { "expr": "sum(irate(container_network_receive_bytes_total{pod=~\"$pod\", namespace=~\"${namespace}\"}[$__rate_interval])) by (pod)", @@ -603,7 +611,7 @@ }, { "type": "timeseries", - "id": 10, + "id": 11, "targets": [ { "expr": "sum(irate(container_network_transmit_bytes_total{pod=~\"$pod\", namespace=~\"${namespace}\"}[$__rate_interval])) by (pod)", @@ -653,7 +661,7 @@ }, { "type": "timeseries", - "id": 11, + "id": 12, "targets": [ { "expr": "avg(irate(container_network_receive_bytes_total{pod=~\"$pod\", namespace=~\"${namespace}\"}[$__rate_interval])) by (pod)", @@ -703,7 +711,7 @@ }, { "type": "timeseries", - "id": 12, + "id": 13, "targets": [ { "expr": "avg(irate(container_network_transmit_bytes_total{pod=~\"$pod\", namespace=~\"${namespace}\"}[$__rate_interval])) by (pod)", @@ -766,7 +774,7 @@ }, { "type": "timeseries", - "id": 13, + "id": 14, "targets": [ { "expr": "ceil(sum by(container, pod) (rate(container_fs_reads_total{job=\"kubelet\", metrics_path=\"/metrics/cadvisor\", container!=\"\", cluster=\"$cluster\", namespace=\"$namespace\", pod=\"$pod\"}[$__rate_interval]) + rate(container_fs_writes_total{job=\"kubelet\", metrics_path=\"/metrics/cadvisor\", container!=\"\", cluster=\"$cluster\", namespace=\"$namespace\", pod=\"$pod\"}[$__rate_interval])))", @@ -816,7 +824,7 @@ }, { "type": "timeseries", - "id": 14, + "id": 15, "targets": [ { "expr": "sum by(container, pod) (rate(container_fs_reads_bytes_total{job=\"kubelet\", metrics_path=\"/metrics/cadvisor\", container!=\"\", cluster=\"$cluster\", namespace=\"$namespace\", pod=\"$pod\"}[$__rate_interval]) + rate(container_fs_writes_bytes_total{job=\"kubelet\", metrics_path=\"/metrics/cadvisor\", container!=\"\", cluster=\"$cluster\", namespace=\"$namespace\", pod=\"$pod\"}[$__rate_interval]))", @@ -871,6 +879,7 @@ "type": "query", "name": "env", "label": "Environment", + "description": "", "query": "label_values(up, env)", "datasource": { "uid": "Prometheus" @@ -891,6 +900,7 @@ "type": "query", "name": "cluster", "label": "Cluster", + "description": "", "query": "label_values(up{env=\"$env\"}, cluster)", "datasource": { "uid": "Prometheus" @@ -911,6 +921,7 @@ "type": "query", "name": "namespace", "label": "Namespace", + "description": "", "query": "label_values(up{env=\"$env\", cluster=\"$cluster\"}, namespace)", "datasource": { "uid": "Prometheus" @@ -931,6 +942,7 @@ "type": "query", "name": "job", "label": "Job", + "description": "", "query": "label_values(up{env=\"$env\", cluster=\"$cluster\", namespace=\"$namespace\"}, job)", "datasource": { "uid": "Prometheus" @@ -951,6 +963,7 @@ "type": "query", "name": "pod", "label": "Pod", + "description": "", "query": "label_values(up{env=\"$env\", cluster=\"$cluster\", namespace=\"$namespace\", job=\"$job\"}, pod)", "datasource": { "uid": "Prometheus" diff --git a/observability-lib/dashboards/nop-ocr/component_test.go b/observability-lib/dashboards/nop-ocr/component_test.go index c4fe49e..1526896 100644 --- a/observability-lib/dashboards/nop-ocr/component_test.go +++ b/observability-lib/dashboards/nop-ocr/component_test.go @@ -1,6 +1,7 @@ package nopocr_test import ( + "flag" "os" "testing" @@ -10,6 +11,45 @@ import ( "github.com/stretchr/testify/require" ) +var update = flag.Bool("update", false, "update golden test files") + +const fileOutput = "test-output.json" + +func TestGenerateFile(t *testing.T) { + if *update == false { + t.Skip("skipping test") + } + + testDashboard, err := nopocr.NewDashboard(&nopocr.Props{ + Name: "NOP OCR Dashboard", + MetricsDataSource: grafana.NewDataSource("Prometheus", ""), + }) + if err != nil { + t.Errorf("Error creating dashboard: %v", err) + } + json, errJSON := testDashboard.GenerateJSON() + if errJSON != nil { + t.Errorf("Error generating JSON: %v", errJSON) + } + if _, errExists := os.Stat(fileOutput); errExists == nil { + errRemove := os.Remove(fileOutput) + if errRemove != nil { + t.Errorf("Error removing file: %v", errRemove) + } + } + file, errFile := os.Create(fileOutput) + if errFile != nil { + panic(errFile) + } + writeString, err := file.WriteString(string(json)) + if err != nil { + t.Errorf("Error writing to file: %v", writeString) + } + t.Cleanup(func() { + file.Close() + }) +} + func TestNewDashboard(t *testing.T) { t.Run("NewDashboard creates a dashboard", func(t *testing.T) { testDashboard, err := nopocr.NewDashboard(&nopocr.Props{ @@ -26,11 +66,11 @@ func TestNewDashboard(t *testing.T) { t.Errorf("Error generating JSON: %v", errJSON) } - jsonCompared, errCompared := os.ReadFile("test-output.json") + jsonCompared, errCompared := os.ReadFile(fileOutput) if errCompared != nil { t.Errorf("Error reading file: %v", errCompared) } - require.ElementsMatch(t, jsonCompared, json) + require.JSONEq(t, string(jsonCompared), string(json)) }) } diff --git a/observability-lib/dashboards/nop-ocr/test-output.json b/observability-lib/dashboards/nop-ocr/test-output.json index 5cbabe1..006074e 100644 --- a/observability-lib/dashboards/nop-ocr/test-output.json +++ b/observability-lib/dashboards/nop-ocr/test-output.json @@ -7,6 +7,7 @@ "" ], "timezone": "browser", + "editable": true, "graphTooltip": 0, "time": { "from": "now-1d", @@ -14,7 +15,7 @@ }, "fiscalYearStartMonth": 0, "refresh": "30s", - "schemaVersion": 0, + "schemaVersion": 39, "panels": [ { "type": "row", @@ -31,7 +32,7 @@ }, { "type": "stat", - "id": 0, + "id": 1, "targets": [ { "expr": "avg_over_time((sum(changes(_telemetry_epoch_round{env=~\"${env}\", contract=~\"${contract}\"}[90s])) by (env, contract, feed_id, network_name, oracle) \u003ebool 0)[$__range:])", @@ -67,6 +68,7 @@ "justifyMode": "auto", "textMode": "value_and_name", "wideLayout": true, + "showPercentChange": false, "reduceOptions": { "calcs": [ "last" @@ -76,7 +78,7 @@ "titleSize": 10, "valueSize": 18 }, - "showPercentChange": false, + "percentChangeColorMode": "standard", "orientation": "auto" }, "fieldConfig": { @@ -111,7 +113,7 @@ }, { "type": "stat", - "id": 1, + "id": 2, "targets": [ { "expr": "avg_over_time((sum(changes(_telemetry_message_observe_total{env=~\"${env}\", contract=~\"${contract}\"}[3m])) by (env, contract, feed_id, network_name, oracle) \u003ebool 0)[$__range:])", @@ -147,6 +149,7 @@ "justifyMode": "auto", "textMode": "value_and_name", "wideLayout": true, + "showPercentChange": false, "reduceOptions": { "calcs": [ "last" @@ -156,7 +159,7 @@ "titleSize": 10, "valueSize": 18 }, - "showPercentChange": false, + "percentChangeColorMode": "standard", "orientation": "auto" }, "fieldConfig": { @@ -191,7 +194,7 @@ }, { "type": "stat", - "id": 2, + "id": 3, "targets": [ { "expr": "avg_over_time((sum(changes(_telemetry_message_report_req_observation_total{env=~\"${env}\", contract=~\"${contract}\"}[3m])) by (env, contract, feed_id, network_name, oracle) \u003ebool 0)[$__range:])", @@ -227,6 +230,7 @@ "justifyMode": "auto", "textMode": "value_and_name", "wideLayout": true, + "showPercentChange": false, "reduceOptions": { "calcs": [ "last" @@ -236,7 +240,7 @@ "titleSize": 10, "valueSize": 18 }, - "showPercentChange": false, + "percentChangeColorMode": "standard", "orientation": "auto" }, "fieldConfig": { @@ -284,7 +288,7 @@ }, { "type": "stat", - "id": 3, + "id": 4, "targets": [ { "expr": "avg_over_time((sum(changes(_telemetry_epoch_round{env=~\"${env}\", oracle=~\"${oracle}\"}[90s])) by (env, contract, feed_id, network_name, oracle) \u003ebool 0)[$__range:])", @@ -320,6 +324,7 @@ "justifyMode": "auto", "textMode": "value_and_name", "wideLayout": true, + "showPercentChange": false, "reduceOptions": { "calcs": [ "last" @@ -329,7 +334,7 @@ "titleSize": 10, "valueSize": 18 }, - "showPercentChange": false, + "percentChangeColorMode": "standard", "orientation": "auto" }, "fieldConfig": { @@ -364,7 +369,7 @@ }, { "type": "stat", - "id": 4, + "id": 5, "targets": [ { "expr": "avg_over_time((sum(changes(_telemetry_message_observe_total{env=~\"${env}\", oracle=~\"${oracle}\"}[3m])) by (env, contract, feed_id, network_name, oracle) \u003ebool 0)[$__range:])", @@ -400,6 +405,7 @@ "justifyMode": "auto", "textMode": "value_and_name", "wideLayout": true, + "showPercentChange": false, "reduceOptions": { "calcs": [ "last" @@ -409,7 +415,7 @@ "titleSize": 10, "valueSize": 18 }, - "showPercentChange": false, + "percentChangeColorMode": "standard", "orientation": "auto" }, "fieldConfig": { @@ -444,7 +450,7 @@ }, { "type": "stat", - "id": 5, + "id": 6, "targets": [ { "expr": "avg_over_time((sum(changes(_telemetry_message_report_req_observation_total{env=~\"${env}\", oracle=~\"${oracle}\"}[3m])) by (env, contract, feed_id, network_name, oracle) \u003ebool 0)[$__range:])", @@ -480,6 +486,7 @@ "justifyMode": "auto", "textMode": "value_and_name", "wideLayout": true, + "showPercentChange": false, "reduceOptions": { "calcs": [ "last" @@ -489,7 +496,7 @@ "titleSize": 10, "valueSize": 18 }, - "showPercentChange": false, + "percentChangeColorMode": "standard", "orientation": "auto" }, "fieldConfig": { @@ -524,7 +531,7 @@ }, { "type": "stat", - "id": 6, + "id": 7, "targets": [ { "expr": "avg_over_time((sum(changes(_telemetry_p2p_received_total{env=~\"${env}\", receiver=~\"${oracle}\"}[3m])) by (sender, receiver) \u003ebool 0)[$__range:])", @@ -560,6 +567,7 @@ "justifyMode": "auto", "textMode": "value_and_name", "wideLayout": true, + "showPercentChange": false, "reduceOptions": { "calcs": [ "last" @@ -569,7 +577,7 @@ "titleSize": 10, "valueSize": 18 }, - "showPercentChange": false, + "percentChangeColorMode": "standard", "orientation": "auto" }, "fieldConfig": { @@ -609,6 +617,7 @@ "type": "query", "name": "env", "label": "Environment", + "description": "", "query": "label_values(_contract_config_f{}, env)", "datasource": { "uid": "Prometheus" @@ -629,6 +638,7 @@ "type": "query", "name": "contract", "label": "Contract", + "description": "", "query": "label_values(_contract_oracle_active{env=\"$env\"}, contract)", "datasource": { "uid": "Prometheus" @@ -649,6 +659,7 @@ "type": "query", "name": "oracle", "label": "NOP", + "description": "", "query": "label_values(_contract_oracle_active{env=\"$env\"}, oracle)", "datasource": { "uid": "Prometheus" diff --git a/observability-lib/go.mod b/observability-lib/go.mod index de353df..3ad992b 100644 --- a/observability-lib/go.mod +++ b/observability-lib/go.mod @@ -3,8 +3,8 @@ module github.com/goplugin/plugin-common/observability-lib go 1.21.4 require ( - github.com/go-resty/resty/v2 v2.14.0 - github.com/grafana/grafana-foundation-sdk/go v0.0.0-20240717180137-18b7def9b008 + github.com/go-resty/resty/v2 v2.15.3 + github.com/grafana/grafana-foundation-sdk/go v0.0.0-20241009194022-923b32e3e69b github.com/rs/zerolog v1.33.0 github.com/spf13/cobra v1.8.1 github.com/stretchr/testify v1.9.0 @@ -16,11 +16,11 @@ require ( github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.19 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rogpeppe/go-internal v1.10.0 // indirect github.com/spf13/pflag v1.0.5 // indirect - golang.org/x/net v0.27.0 // indirect - golang.org/x/sys v0.22.0 // indirect + golang.org/x/net v0.30.0 // indirect + golang.org/x/sys v0.26.0 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect ) diff --git a/observability-lib/go.sum b/observability-lib/go.sum index 040c177..f59de2c 100644 --- a/observability-lib/go.sum +++ b/observability-lib/go.sum @@ -3,12 +3,11 @@ github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46t github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 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/go-resty/resty/v2 v2.14.0 h1:/rhkzsAqGQkozwfKS5aFAbb6TyKd3zyFRWcdRXLPCAU= -github.com/go-resty/resty/v2 v2.14.0/go.mod h1:IW6mekUOsElt9C7oWr0XRt9BNSD6D5rr9mhk6NjmNHg= +github.com/go-resty/resty/v2 v2.15.3 h1:bqff+hcqAflpiF591hhJzNdkRsFhlB96CYfBwSFvql8= +github.com/go-resty/resty/v2 v2.15.3/go.mod h1:0fHAoK7JoBy/Ch36N8VFeMsK7xQOHhvWaC3iOktwmIU= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/grafana/grafana-foundation-sdk/go v0.0.0-20240717180137-18b7def9b008 h1:QEqDMW+20VJTkqU892tb9FbvCtI1uxxGvyXwulRhpAU= -github.com/grafana/grafana-foundation-sdk/go v0.0.0-20240717180137-18b7def9b008/go.mod h1:WtWosval1KCZP9BGa42b8aVoJmVXSg0EvQXi9LDSVZQ= +github.com/grafana/grafana-foundation-sdk/go v0.0.0-20241009194022-923b32e3e69b h1:YxlugK0wL5hh86wT0hZSGw9cPTvacOUmHxjP15fsIlE= +github.com/grafana/grafana-foundation-sdk/go v0.0.0-20241009194022-923b32e3e69b/go.mod h1:WtWosval1KCZP9BGa42b8aVoJmVXSg0EvQXi9LDSVZQ= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= @@ -21,8 +20,9 @@ github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -40,75 +40,15 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -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.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= -golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= -golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= -golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= -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/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -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-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= -golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= -golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= -golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= -golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= -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/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= -golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -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-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/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= +golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= -golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= -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.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/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= -golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= -golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4= -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.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -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/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U= golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= -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/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= -golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 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/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/observability-lib/grafana/alerts.go b/observability-lib/grafana/alerts.go index 2c42637..f050b89 100644 --- a/observability-lib/grafana/alerts.go +++ b/observability-lib/grafana/alerts.go @@ -2,6 +2,7 @@ package grafana import ( "github.com/grafana/grafana-foundation-sdk/go/alerting" + "github.com/grafana/grafana-foundation-sdk/go/cog" "github.com/grafana/grafana-foundation-sdk/go/expr" "github.com/grafana/grafana-foundation-sdk/go/prometheus" ) @@ -62,57 +63,41 @@ type ResampleExpression struct { type ThresholdExpression struct { Expression string - ThresholdConditionsOptions []ThresholdConditionsOption + ThresholdConditionsOptions ThresholdConditionsOption } +type TypeThresholdType string + +const ( + TypeThresholdTypeGt TypeThresholdType = "gt" + TypeThresholdTypeLt TypeThresholdType = "lt" + TypeThresholdTypeWithinRange TypeThresholdType = "within_range" + TypeThresholdTypeOutsideRange TypeThresholdType = "outside_range" +) + type ThresholdConditionsOption struct { Params []float64 - Type expr.TypeThresholdType + Type TypeThresholdType } -func newThresholdConditionsOptions(options []ThresholdConditionsOption) []struct { - Evaluator struct { - Params []float64 `json:"params"` - Type expr.TypeThresholdType `json:"type"` - } `json:"evaluator"` - LoadedDimensions any `json:"loadedDimensions,omitempty"` - UnloadEvaluator *struct { - Params []float64 `json:"params"` - Type expr.TypeThresholdType `json:"type"` - } `json:"unloadEvaluator,omitempty"` -} { - var conditions []struct { - Evaluator struct { - Params []float64 `json:"params"` - Type expr.TypeThresholdType `json:"type"` - } `json:"evaluator"` - LoadedDimensions any `json:"loadedDimensions,omitempty"` - UnloadEvaluator *struct { - Params []float64 `json:"params"` - Type expr.TypeThresholdType `json:"type"` - } `json:"unloadEvaluator,omitempty"` - } - for _, option := range options { - conditions = append(conditions, struct { - Evaluator struct { - Params []float64 `json:"params"` - Type expr.TypeThresholdType `json:"type"` - } `json:"evaluator"` - LoadedDimensions any `json:"loadedDimensions,omitempty"` - UnloadEvaluator *struct { - Params []float64 `json:"params"` - Type expr.TypeThresholdType `json:"type"` - } `json:"unloadEvaluator,omitempty"` - }{ - Evaluator: struct { - Params []float64 `json:"params"` - Type expr.TypeThresholdType `json:"type"` - }{ - Params: option.Params, - Type: option.Type, - }, - }) +func newThresholdConditionsOptions(options ThresholdConditionsOption) []cog.Builder[expr.ExprTypeThresholdConditions] { + var conditions []cog.Builder[expr.ExprTypeThresholdConditions] + + var params []float64 + params = append(params, options.Params...) + + if len(options.Params) == 1 { + params = append(params, 0) } + + conditions = append(conditions, expr.NewExprTypeThresholdConditionsBuilder(). + Evaluator( + expr.NewExprTypeThresholdConditionsEvaluatorBuilder(). + Params(params). + Type(expr.TypeThresholdType(options.Type)), + ), + ) + return conditions } @@ -174,7 +159,6 @@ func newConditionQuery(options ConditionQuery) *alerting.QueryBuilder { type AlertOptions struct { Name string - Datasource string Summary string Description string RunbookURL string diff --git a/observability-lib/grafana/builder.go b/observability-lib/grafana/builder.go index 11fa130..90319dd 100644 --- a/observability-lib/grafana/builder.go +++ b/observability-lib/grafana/builder.go @@ -59,8 +59,8 @@ func (b *Builder) AddRow(title string) { } func (b *Builder) getPanelCounter() uint32 { - res := b.panelCounter b.panelCounter = inc(&b.panelCounter) + res := b.panelCounter return res } @@ -82,6 +82,9 @@ func (b *Builder) AddPanel(panel ...*Panel) { } else if item.logPanelBuilder != nil { item.logPanelBuilder.Id(panelID) b.dashboardBuilder.WithPanel(item.logPanelBuilder) + } else if item.heatmapBuilder != nil { + item.heatmapBuilder.Id(panelID) + b.dashboardBuilder.WithPanel(item.heatmapBuilder) } if item.alertBuilder != nil { b.alertsBuilder = append(b.alertsBuilder, item.alertBuilder) @@ -89,6 +92,10 @@ func (b *Builder) AddPanel(panel ...*Panel) { } } +func (b *Builder) AddAlert(alerts ...*alerting.RuleBuilder) { + b.alertsBuilder = append(b.alertsBuilder, alerts...) +} + func (b *Builder) AddContactPoint(contactPoints ...*alerting.ContactPointBuilder) { b.contactPointsBuilder = append(b.contactPointsBuilder, contactPoints...) } diff --git a/observability-lib/grafana/dashboard.go b/observability-lib/grafana/dashboard.go index 4c711d4..4a0d0e8 100644 --- a/observability-lib/grafana/dashboard.go +++ b/observability-lib/grafana/dashboard.go @@ -81,7 +81,11 @@ func (db *Dashboard) DeployToGrafana(options *DeployOptions) error { alert.RuleGroup = *db.Dashboard.Title alert.FolderUID = folder.UID alert.Annotations["__dashboardUid__"] = *newDashboard.UID - alert.Annotations["__panelId__"] = panelIDByTitle(db.Dashboard, alert.Title) + + panelId := panelIDByTitle(db.Dashboard, alert.Title) + if panelId != "" { + alert.Annotations["__panelId__"] = panelIDByTitle(db.Dashboard, alert.Title) + } _, _, errPostAlertRule := grafanaClient.PostAlertRule(alert) if errPostAlertRule != nil { diff --git a/observability-lib/grafana/dashboard_test.go b/observability-lib/grafana/dashboard_test.go index 2ef9922..f49682d 100644 --- a/observability-lib/grafana/dashboard_test.go +++ b/observability-lib/grafana/dashboard_test.go @@ -3,7 +3,6 @@ package grafana_test import ( "testing" - "github.com/grafana/grafana-foundation-sdk/go/expr" "github.com/goplugin/plugin-common/observability-lib/grafana" "github.com/stretchr/testify/require" ) @@ -32,34 +31,32 @@ func TestGenerateJSON(t *testing.T) { Legend: `{{account}}`, }, }, - AlertOptions: &grafana.AlertOptions{ - Summary: `ETH Balance is lower than threshold`, - Description: `ETH Balance critically low at {{ index $values "A" }}`, - RunbookURL: "https://github.com/goplugin/plugin-common/tree/main/observability-lib", - For: "1m", - Tags: map[string]string{ - "severity": "warning", - }, - Query: []grafana.RuleQuery{ - { - Expr: `eth_balance`, - Instant: true, - RefID: "A", - Datasource: "datasource-uid", - }, + }, + AlertOptions: &grafana.AlertOptions{ + Summary: `ETH Balance is lower than threshold`, + Description: `ETH Balance critically low at {{ index $values "A" }}`, + RunbookURL: "https://github.com/goplugin/plugin-common/tree/main/observability-lib", + For: "1m", + Tags: map[string]string{ + "severity": "warning", + }, + Query: []grafana.RuleQuery{ + { + Expr: `eth_balance`, + Instant: true, + RefID: "A", + Datasource: "datasource-uid", }, - QueryRefCondition: "B", - Condition: []grafana.ConditionQuery{ - { - RefID: "B", - ThresholdExpression: &grafana.ThresholdExpression{ - Expression: "A", - ThresholdConditionsOptions: []grafana.ThresholdConditionsOption{ - { - Params: []float64{2, 0}, - Type: expr.TypeThresholdTypeLt, - }, - }, + }, + QueryRefCondition: "B", + Condition: []grafana.ConditionQuery{ + { + RefID: "B", + ThresholdExpression: &grafana.ThresholdExpression{ + Expression: "A", + ThresholdConditionsOptions: grafana.ThresholdConditionsOption{ + Params: []float64{2}, + Type: grafana.TypeThresholdTypeLt, }, }, }, diff --git a/observability-lib/grafana/panels.go b/observability-lib/grafana/panels.go index 4466a10..110dc2e 100644 --- a/observability-lib/grafana/panels.go +++ b/observability-lib/grafana/panels.go @@ -5,6 +5,7 @@ import ( "github.com/grafana/grafana-foundation-sdk/go/common" "github.com/grafana/grafana-foundation-sdk/go/dashboard" "github.com/grafana/grafana-foundation-sdk/go/gauge" + "github.com/grafana/grafana-foundation-sdk/go/heatmap" "github.com/grafana/grafana-foundation-sdk/go/logs" "github.com/grafana/grafana-foundation-sdk/go/prometheus" "github.com/grafana/grafana-foundation-sdk/go/stat" @@ -36,6 +37,7 @@ func newQuery(query Query) *prometheus.DataqueryBuilder { type LegendOptions struct { Placement common.LegendPlacement DisplayMode common.LegendDisplayMode + Calcs []string } func newLegend(options *LegendOptions) *common.VizLegendOptionsBuilder { @@ -49,8 +51,14 @@ func newLegend(options *LegendOptions) *common.VizLegendOptionsBuilder { builder := common.NewVizLegendOptionsBuilder(). ShowLegend(true). - Placement(options.Placement). - DisplayMode(options.DisplayMode) + Placement(options.Placement) + + if len(options.Calcs) > 0 { + options.DisplayMode = common.LegendDisplayModeTable + builder.Calcs(options.Calcs) + } + + builder.DisplayMode(options.DisplayMode) return builder } @@ -81,20 +89,30 @@ func newTransform(options *TransformOptions) dashboard.DataTransformerConfig { } type PanelOptions struct { - Datasource string - Title string - Description string - Span uint32 - Height uint32 - Decimals float64 - Unit string - NoValue string - Min *float64 - Max *float64 - Query []Query - Threshold *ThresholdOptions - Transform *TransformOptions - AlertOptions *AlertOptions + Datasource string + Title string + Description string + Span uint32 + Height uint32 + Decimals float64 + Unit string + NoValue string + Min *float64 + Max *float64 + Query []Query + Threshold *ThresholdOptions + Transform *TransformOptions + ColorScheme dashboard.FieldColorModeId +} + +type Panel struct { + statPanelBuilder *stat.PanelBuilder + timeSeriesPanelBuilder *timeseries.PanelBuilder + gaugePanelBuilder *gauge.PanelBuilder + tablePanelBuilder *table.PanelBuilder + logPanelBuilder *logs.PanelBuilder + heatmapBuilder *heatmap.PanelBuilder + alertBuilder *alerting.RuleBuilder } // panel defaults @@ -125,15 +143,7 @@ type StatPanelOptions struct { GraphMode common.BigValueGraphMode TextMode common.BigValueTextMode Orientation common.VizOrientation -} - -type Panel struct { - statPanelBuilder *stat.PanelBuilder - timeSeriesPanelBuilder *timeseries.PanelBuilder - gaugePanelBuilder *gauge.PanelBuilder - tablePanelBuilder *table.PanelBuilder - logPanelBuilder *logs.PanelBuilder - alertBuilder *alerting.RuleBuilder + Mappings []dashboard.ValueMapping } func NewStatPanel(options *StatPanelOptions) *Panel { @@ -170,6 +180,7 @@ func NewStatPanel(options *StatPanelOptions) *Panel { TextMode(options.TextMode). Orientation(options.Orientation). JustifyMode(options.JustifyMode). + Mappings(options.Mappings). ReduceOptions(common.NewReduceDataOptionsBuilder().Calcs([]string{"last"})) if options.Min != nil { @@ -204,13 +215,8 @@ func NewStatPanel(options *StatPanelOptions) *Panel { newPanel.WithTransformation(newTransform(options.Transform)) } - if options.AlertOptions != nil { - options.AlertOptions.Name = options.Title - - return &Panel{ - statPanelBuilder: newPanel, - alertBuilder: NewAlertRule(options.AlertOptions), - } + if options.ColorScheme != "" { + newPanel.ColorScheme(dashboard.NewFieldColorBuilder().Mode(options.ColorScheme)) } return &Panel{ @@ -220,9 +226,11 @@ func NewStatPanel(options *StatPanelOptions) *Panel { type TimeSeriesPanelOptions struct { *PanelOptions + AlertOptions *AlertOptions FillOpacity float64 ScaleDistribution common.ScaleDistribution LegendOptions *LegendOptions + ThresholdStyle common.GraphThresholdsStyleMode } func NewTimeSeriesPanel(options *TimeSeriesPanelOptions) *Panel { @@ -269,12 +277,20 @@ func NewTimeSeriesPanel(options *TimeSeriesPanelOptions) *Panel { if options.Threshold != nil { newPanel.Thresholds(newThresholds(options.Threshold)) + + if options.ThresholdStyle != "" { + newPanel.ThresholdsStyle(common.NewGraphThresholdsStyleConfigBuilder().Mode(options.ThresholdStyle)) + } } if options.Transform != nil { newPanel.WithTransformation(newTransform(options.Transform)) } + if options.ColorScheme != "" { + newPanel.ColorScheme(dashboard.NewFieldColorBuilder().Mode(options.ColorScheme)) + } + if options.AlertOptions != nil { options.AlertOptions.Name = options.Title @@ -303,7 +319,11 @@ func NewGaugePanel(options *GaugePanelOptions) *Panel { Span(options.Span). Height(options.Height). Decimals(options.Decimals). - Unit(options.Unit) + Unit(options.Unit). + ReduceOptions( + common.NewReduceDataOptionsBuilder(). + Calcs([]string{"lastNotNull"}).Values(false), + ) if options.Min != nil { newPanel.Min(*options.Min) @@ -325,15 +345,6 @@ func NewGaugePanel(options *GaugePanelOptions) *Panel { newPanel.WithTransformation(newTransform(options.Transform)) } - if options.AlertOptions != nil { - options.AlertOptions.Name = options.Title - - return &Panel{ - gaugePanelBuilder: newPanel, - alertBuilder: NewAlertRule(options.AlertOptions), - } - } - return &Panel{ gaugePanelBuilder: newPanel, } @@ -376,13 +387,8 @@ func NewTablePanel(options *TablePanelOptions) *Panel { newPanel.WithTransformation(newTransform(options.Transform)) } - if options.AlertOptions != nil { - options.AlertOptions.Name = options.Title - - return &Panel{ - tablePanelBuilder: newPanel, - alertBuilder: NewAlertRule(options.AlertOptions), - } + if options.ColorScheme != "" { + newPanel.ColorScheme(dashboard.NewFieldColorBuilder().Mode(options.ColorScheme)) } return &Panel{ @@ -425,16 +431,58 @@ func NewLogPanel(options *LogPanelOptions) *Panel { newPanel.WithTransformation(newTransform(options.Transform)) } - if options.AlertOptions != nil { - options.AlertOptions.Name = options.Title - - return &Panel{ - logPanelBuilder: newPanel, - alertBuilder: NewAlertRule(options.AlertOptions), - } + if options.ColorScheme != "" { + newPanel.ColorScheme(dashboard.NewFieldColorBuilder().Mode(options.ColorScheme)) } return &Panel{ logPanelBuilder: newPanel, } } + +type HeatmapPanelOptions struct { + *PanelOptions +} + +func NewHeatmapPanel(options *HeatmapPanelOptions) *Panel { + setDefaults(options.PanelOptions) + + newPanel := heatmap.NewPanelBuilder(). + Datasource(datasourceRef(options.Datasource)). + Title(options.Title). + Description(options.Description). + Span(options.Span). + Height(options.Height). + Decimals(options.Decimals). + Unit(options.Unit). + NoValue(options.NoValue) + + if options.Min != nil { + newPanel.Min(*options.Min) + } + + if options.Max != nil { + newPanel.Max(*options.Max) + } + + for _, q := range options.Query { + q.Format = prometheus.PromQueryFormatHeatmap + newPanel.WithTarget(newQuery(q)) + } + + if options.Threshold != nil { + newPanel.Thresholds(newThresholds(options.Threshold)) + } + + if options.Transform != nil { + newPanel.WithTransformation(newTransform(options.Transform)) + } + + if options.ColorScheme != "" { + newPanel.ColorScheme(dashboard.NewFieldColorBuilder().Mode(options.ColorScheme)) + } + + return &Panel{ + heatmapBuilder: newPanel, + } +} diff --git a/observability-lib/grafana/variables.go b/observability-lib/grafana/variables.go index 5ad2633..445a071 100644 --- a/observability-lib/grafana/variables.go +++ b/observability-lib/grafana/variables.go @@ -1,38 +1,93 @@ package grafana import ( + "strings" + "github.com/grafana/grafana-foundation-sdk/go/cog" "github.com/grafana/grafana-foundation-sdk/go/dashboard" ) -type VariableOption struct { - Name string - Label string +type VariableOptionValues struct { } -type QueryVariableOptions struct { - *VariableOption - Datasource string - Query string - Multi bool - Regex string +type VariableOption struct { + Name string + Label string + Description string CurrentText string CurrentValue string - IncludeAll bool } -func NewQueryVariable(options *QueryVariableOptions) *dashboard.QueryVariableBuilder { - if options.CurrentText == "" { +type CustomVariableOptions struct { + *VariableOption + Values map[string]any +} + +func NewCustomVariable(options *CustomVariableOptions) *dashboard.CustomVariableBuilder { + if options.CurrentText == "" && options.CurrentValue == "" { options.CurrentText = "All" + options.CurrentValue = "$__all" + } + + variable := dashboard.NewCustomVariableBuilder(options.Name). + Label(options.Label). + Description(options.Description). + Current(dashboard.VariableOption{ + Selected: cog.ToPtr[bool](true), + Text: dashboard.StringOrArrayOfString{String: cog.ToPtr(options.CurrentText)}, + Value: dashboard.StringOrArrayOfString{String: cog.ToPtr(options.CurrentValue)}, + }) + + optionsList := []dashboard.VariableOption{ + { + Selected: cog.ToPtr[bool](true), + Text: dashboard.StringOrArrayOfString{String: cog.ToPtr(options.CurrentText)}, + Value: dashboard.StringOrArrayOfString{String: cog.ToPtr(options.CurrentValue)}, + }, + } + for key, value := range options.Values { + if key != options.CurrentText { + option := dashboard.VariableOption{ + Text: dashboard.StringOrArrayOfString{String: cog.ToPtr(key)}, + Value: dashboard.StringOrArrayOfString{String: cog.ToPtr(value.(string))}, + } + optionsList = append(optionsList, option) + } + } + variable.Options(optionsList) + + valuesString := "" + for key, value := range options.Values { + // Escape commas and colons in the value which are reserved characters for values string + cleanValue := strings.ReplaceAll(value.(string), ",", "\\,") + cleanValue = strings.ReplaceAll(cleanValue, ":", "\\:") + valuesString += key + " : " + cleanValue + " , " } + variable.Values(dashboard.StringOrMap{String: cog.ToPtr(strings.TrimSuffix(valuesString, ", "))}) + + return variable +} + +type QueryVariableOptions struct { + *VariableOption + Datasource string + Query string + Multi bool + Regex string + IncludeAll bool + QueryWithType map[string]any + Hide *dashboard.VariableHide +} - if options.CurrentValue == "" { +func NewQueryVariable(options *QueryVariableOptions) *dashboard.QueryVariableBuilder { + if options.CurrentText == "" && options.CurrentValue == "" { + options.CurrentText = "All" options.CurrentValue = "$__all" } variable := dashboard.NewQueryVariableBuilder(options.Name). Label(options.Label). - Query(dashboard.StringOrMap{String: cog.ToPtr[string](options.Query)}). + Description(options.Description). Datasource(datasourceRef(options.Datasource)). Current(dashboard.VariableOption{ Selected: cog.ToPtr[bool](true), @@ -42,6 +97,12 @@ func NewQueryVariable(options *QueryVariableOptions) *dashboard.QueryVariableBui Sort(dashboard.VariableSortAlphabeticalAsc). Multi(options.Multi) + if options.Query != "" { + variable.Query(dashboard.StringOrMap{String: cog.ToPtr[string](options.Query)}) + } else if options.QueryWithType != nil { + variable.Query(dashboard.StringOrMap{Map: options.QueryWithType}) + } + if options.Regex != "" { variable.Regex(options.Regex) } @@ -50,6 +111,10 @@ func NewQueryVariable(options *QueryVariableOptions) *dashboard.QueryVariableBui variable.IncludeAll(options.IncludeAll) } + if options.Hide != nil { + variable.Hide(*options.Hide) + } + return variable } @@ -59,12 +124,18 @@ type IntervalVariableOptions struct { } func NewIntervalVariable(options *IntervalVariableOptions) *dashboard.IntervalVariableBuilder { + if options.CurrentText == "" && options.CurrentValue == "" { + options.CurrentText = "All" + options.CurrentValue = "$__all" + } + return dashboard.NewIntervalVariableBuilder(options.Name). Label(options.Label). + Description(options.Description). Values(dashboard.StringOrMap{String: cog.ToPtr[string](options.Interval)}). Current(dashboard.VariableOption{ Selected: cog.ToPtr[bool](true), - Text: dashboard.StringOrArrayOfString{ArrayOfString: []string{"All"}}, - Value: dashboard.StringOrArrayOfString{ArrayOfString: []string{"$__all"}}, + Text: dashboard.StringOrArrayOfString{ArrayOfString: []string{options.CurrentText}}, + Value: dashboard.StringOrArrayOfString{ArrayOfString: []string{options.CurrentValue}}, }) } diff --git a/pkg/assets/link_test.go b/pkg/assets/link_test.go index b5a85fc..baed234 100644 --- a/pkg/assets/link_test.go +++ b/pkg/assets/link_test.go @@ -187,20 +187,20 @@ func TestLink(t *testing.T) { {"1", "1"}, {"1 juels", "1"}, {"100000000000", "100000000000"}, - {"0.0000001 pli", "100000000000"}, - {"1000000000000", "0.000001 pli"}, - {"100000000000000", "0.0001 pli"}, - {"0.0001 pli", "0.0001 pli"}, - {"10000000000000000", "0.01 pli"}, - {"0.01 pli", "0.01 pli"}, - {"100000000000000000", "0.1 pli"}, - {"0.1 pli", "0.1 pli"}, - {"1.0 pli", "1 pli"}, - {"1000000000000000000", "1 pli"}, - {"1000000000000000000 juels", "1 pli"}, - {"1100000000000000000", "1.1 pli"}, - {"1.1pli", "1.1 pli"}, - {"1.1 pli", "1.1 pli"}, + {"0.0000001 link", "100000000000"}, + {"1000000000000", "0.000001 link"}, + {"100000000000000", "0.0001 link"}, + {"0.0001 link", "0.0001 link"}, + {"10000000000000000", "0.01 link"}, + {"0.01 link", "0.01 link"}, + {"100000000000000000", "0.1 link"}, + {"0.1 link", "0.1 link"}, + {"1.0 link", "1 link"}, + {"1000000000000000000", "1 link"}, + {"1000000000000000000 juels", "1 link"}, + {"1100000000000000000", "1.1 link"}, + {"1.1link", "1.1 link"}, + {"1.1 link", "1.1 link"}, } { t.Run(tt.input, func(t *testing.T) { var l assets.Link @@ -215,12 +215,12 @@ func TestLink(t *testing.T) { func FuzzLink(f *testing.F) { f.Add("1") - f.Add("1 pli") - f.Add("1.1pli") + f.Add("1 link") + f.Add("1.1link") f.Add("2.3") - f.Add("2.3 pli") - f.Add("00005 pli") - f.Add("0.0005pli") + f.Add("2.3 link") + f.Add("00005 link") + f.Add("0.0005link") f.Add("1100000000000000000000000000000") f.Add("1100000000000000000000000000000 juels") f.Fuzz(func(t *testing.T, v string) { diff --git a/pkg/codec/byte_string_modifier.go b/pkg/codec/byte_string_modifier.go new file mode 100644 index 0000000..6170674 --- /dev/null +++ b/pkg/codec/byte_string_modifier.go @@ -0,0 +1,256 @@ +package codec + +import ( + "fmt" + "reflect" + + "github.com/goplugin/plugin-common/pkg/types" +) + +// AddressModifier defines the interface for encoding, decoding, and handling addresses. +// This interface allows for chain-specific logic to be injected into the modifier without +// modifying the common repository. +type AddressModifier interface { + // EncodeAddress converts byte array representing an address into its string form using chain-specific logic. + EncodeAddress([]byte) (string, error) + // DecodeAddress converts a string representation of an address back into its byte array form using chain-specific logic. + DecodeAddress(string) ([]byte, error) + // Length returns the expected byte length of the address for the specific chain. + Length() int +} + +// NewAddressBytesToStringModifier creates and returns a new modifier that transforms address byte +// arrays to their corresponding string representation (or vice versa) based on the provided +// AddressModifier. +// +// The fields parameter specifies which fields within a struct should be modified. The AddressModifier +// is injected into the modifier to handle chain-specific logic during the contractReader relayer configuration. +func NewAddressBytesToStringModifier(fields []string, modifier AddressModifier) Modifier { + // bool is a placeholder value + fieldMap := map[string]bool{} + for _, field := range fields { + fieldMap[field] = true + } + + m := &bytesToStringModifier{ + modifier: modifier, + modifierBase: modifierBase[bool]{ + fields: fieldMap, + onToOffChainType: map[reflect.Type]reflect.Type{}, + offToOnChainType: map[reflect.Type]reflect.Type{}, + }, + } + + // Modify field for input using the modifier to convert the byte array to string + m.modifyFieldForInput = func(_ string, field *reflect.StructField, _ string, _ bool) error { + t, err := createStringTypeForBytes(field.Type, field.Name, modifier.Length()) + if err != nil { + return err + } + field.Type = t + return nil + } + + return m +} + +type bytesToStringModifier struct { + // Injected modifier that contains chain-specific logic + modifier AddressModifier + modifierBase[bool] +} + +func (t *bytesToStringModifier) RetypeToOffChain(onChainType reflect.Type, _ string) (tpe reflect.Type, err error) { + defer func() { + // StructOf can panic if the fields are not valid + if r := recover(); r != nil { + tpe = nil + err = fmt.Errorf("%w: %v", types.ErrInvalidType, r) + } + }() + + // Attempt to retype using the shared functionality in modifierBase + offChainType, err := t.modifierBase.RetypeToOffChain(onChainType, "") + if err != nil { + // Handle additional cases specific to bytesToStringModifier + if onChainType.Kind() == reflect.Array { + addrType := reflect.ArrayOf(t.modifier.Length(), reflect.TypeOf(byte(0))) + // Check for nested byte arrays (e.g., [n][20]byte) + if onChainType.Elem() == addrType.Elem() { + return reflect.ArrayOf(onChainType.Len(), reflect.TypeOf("")), nil + } + } + } + + return offChainType, err +} + +// TransformToOnChain uses the AddressModifier for string-to-address conversion. +func (t *bytesToStringModifier) TransformToOnChain(offChainValue any, _ string) (any, error) { + return transformWithMaps(offChainValue, t.offToOnChainType, t.fields, noop, stringToAddressHookForOnChain(t.modifier)) +} + +// TransformToOffChain uses the AddressModifier for address-to-string conversion. +func (t *bytesToStringModifier) TransformToOffChain(onChainValue any, _ string) (any, error) { + return transformWithMaps(onChainValue, t.onToOffChainType, t.fields, + addressTransformationAction(t.modifier.Length()), + addressToStringHookForOffChain(t.modifier), + ) +} + +// addressTransformationAction performs conversions over the fields we want to modify. +// It handles byte arrays, ensuring they are convertible to the expected length. +// It then replaces the field in the map with the transformed value. +func addressTransformationAction(length int) func(extractMap map[string]any, key string, _ bool) error { + return func(em map[string]any, fieldName string, _ bool) error { + if val, ok := em[fieldName]; ok { + rVal := reflect.ValueOf(val) + + if !rVal.IsValid() { + return fmt.Errorf("invalid value for field %s", fieldName) + } + + if rVal.Kind() == reflect.Ptr && !rVal.IsNil() { + rVal = reflect.Indirect(rVal) + } + + expectedType := reflect.ArrayOf(length, reflect.TypeOf(byte(0))) + if rVal.Type().ConvertibleTo(expectedType) { + if !rVal.CanConvert(expectedType) { + return fmt.Errorf("cannot convert type %v to expected type %v for field %s", rVal.Type(), expectedType, fieldName) + } + rVal = rVal.Convert(expectedType) + } + + switch rVal.Kind() { + case reflect.Array: + // Handle outer arrays (e.g., [n][length]byte) + if rVal.Type().Elem().Kind() == reflect.Array && rVal.Type().Elem().Len() == length { + addressArray := reflect.New(reflect.ArrayOf(rVal.Len(), expectedType)).Elem() + for i := 0; i < rVal.Len(); i++ { + elem := rVal.Index(i) + if elem.Len() != length { + return fmt.Errorf("expected [%d]byte but got length %d for element %d in field %s", length, elem.Len(), i, fieldName) + } + reflect.Copy(addressArray.Index(i), elem) + } + em[fieldName] = addressArray.Interface() + } else if rVal.Type() == expectedType { + // Handle a single array (e.g., [length]byte) + addressVal := reflect.New(expectedType).Elem() + reflect.Copy(addressVal, rVal) + em[fieldName] = addressVal.Interface() + } else { + return fmt.Errorf("expected [%d]byte but got %v for field %s", length, rVal.Type(), fieldName) + } + case reflect.Slice: + // Handle slices of byte arrays (e.g., [][length]byte) + if rVal.Len() > 0 && rVal.Index(0).Type() == expectedType { + addressSlice := reflect.MakeSlice(reflect.SliceOf(expectedType), rVal.Len(), rVal.Len()) + for i := 0; i < rVal.Len(); i++ { + elem := rVal.Index(i) + if elem.Len() != length { + return fmt.Errorf("expected element of [%d]byte but got length %d at index %d for field %s", length, elem.Len(), i, fieldName) + } + reflect.Copy(addressSlice.Index(i), elem) + } + em[fieldName] = addressSlice.Interface() + } else { + return fmt.Errorf("expected slice of [%d]byte but got %v for field %s", length, rVal.Type(), fieldName) + } + default: + return fmt.Errorf("unexpected type %v for field %s", rVal.Kind(), fieldName) + } + } + return nil + } +} + +// createStringTypeForBytes converts a byte array, pointer, or slice type to a string type for a given field. +// This function inspects the kind of the input type (array, pointer, slice) and performs the conversion +// if the element type matches the specified byte array length. Returns an error if the conversion is not possible. +func createStringTypeForBytes(t reflect.Type, field string, length int) (reflect.Type, error) { + switch t.Kind() { + case reflect.Pointer: + return createStringTypeForBytes(t.Elem(), field, length) + + case reflect.Array: + // Handle arrays, convert array of bytes to array of strings + if t.Elem().Kind() == reflect.Uint8 && t.Len() == length { + return reflect.TypeOf(""), nil + } else if t.Elem().Kind() == reflect.Array && t.Elem().Len() == length { + // Handle nested arrays (e.g., [2][20]byte to [2]string) + return reflect.ArrayOf(t.Len(), reflect.TypeOf("")), nil + } + return nil, fmt.Errorf("%w: cannot convert bytes for field %s", types.ErrInvalidType, field) + + case reflect.Slice: + // Handle slices of byte arrays, convert to slice of strings + if t.Elem().Kind() == reflect.Array && t.Elem().Len() == length { + return reflect.SliceOf(reflect.TypeOf("")), nil + } + return nil, fmt.Errorf("%w: cannot convert bytes for field %s", types.ErrInvalidType, field) + + default: + return nil, fmt.Errorf("%w: cannot convert bytes for field %s", types.ErrInvalidType, field) + } +} + +// stringToAddressHookForOnChain converts a string representation of an address back into a byte array for on-chain use. +func stringToAddressHookForOnChain(modifier AddressModifier) func(from reflect.Type, to reflect.Type, data any) (any, error) { + return func(from reflect.Type, to reflect.Type, data any) (any, error) { + byteArrTyp := reflect.ArrayOf(modifier.Length(), reflect.TypeOf(byte(0))) + strTyp := reflect.TypeOf("") + + // Convert from string to byte array (e.g., string -> [20]byte) + if from == strTyp && (to == byteArrTyp || to.ConvertibleTo(byteArrTyp)) { + addr, ok := data.(string) + if !ok { + return nil, fmt.Errorf("invalid type: expected string but got %T", data) + } + + bts, err := modifier.DecodeAddress(addr) + if err != nil { + return nil, err + } + + if len(bts) != modifier.Length() { + return nil, fmt.Errorf("length mismatch: expected %d bytes, got %d", modifier.Length(), len(bts)) + } + + val := reflect.New(byteArrTyp).Elem() + reflect.Copy(val, reflect.ValueOf(bts)) + return val.Interface(), nil + } + return data, nil + } +} + +// addressToStringHookForOffChain converts byte arrays to their string representation for off-chain use. +func addressToStringHookForOffChain(modifier AddressModifier) func(from reflect.Type, to reflect.Type, data any) (any, error) { + return func(from reflect.Type, to reflect.Type, data any) (any, error) { + byteArrTyp := reflect.ArrayOf(modifier.Length(), reflect.TypeOf(byte(0))) + strTyp := reflect.TypeOf("") + rVal := reflect.ValueOf(data) + + if !reflect.ValueOf(data).IsValid() { + return nil, fmt.Errorf("invalid value for conversion: got %T", data) + } + + // Convert from byte array to string (e.g., [20]byte -> string) + if from.ConvertibleTo(byteArrTyp) && to == strTyp { + bts := make([]byte, rVal.Len()) + for i := 0; i < rVal.Len(); i++ { + bts[i] = byte(rVal.Index(i).Uint()) + } + + encoded, err := modifier.EncodeAddress(bts) + if err != nil { + return nil, fmt.Errorf("failed to encode address: %w", err) + } + + return encoded, nil + } + return data, nil + } +} diff --git a/pkg/codec/byte_string_modifier_test.go b/pkg/codec/byte_string_modifier_test.go new file mode 100644 index 0000000..192473c --- /dev/null +++ b/pkg/codec/byte_string_modifier_test.go @@ -0,0 +1,341 @@ +package codec_test + +import ( + "encoding/hex" + "errors" + "reflect" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/goplugin/plugin-common/pkg/codec" +) + +// MockAddressModifier is a mock implementation of the AddressModifier interface. +type MockAddressModifier struct { + length int +} + +func (m MockAddressModifier) EncodeAddress(bytes []byte) (string, error) { + return "0x" + hex.EncodeToString(bytes), nil +} + +func (m MockAddressModifier) DecodeAddress(str string) ([]byte, error) { + if len(str) == 0 { + return nil, errors.New("empty address") + } + return hex.DecodeString(str[2:]) // Skip the "0x" prefix for hex encoding +} + +func (m MockAddressModifier) Length() int { + return m.length +} + +func TestAddressBytesToString(t *testing.T) { + // Mocking AddressModifier for 20-byte addresses + mockModifier := MockAddressModifier{length: 20} + + type concreteStruct struct { + A string + T [20]byte + } + + type concreteStructWithLargeAddress struct { + A string + T [20]byte + } + + type pointerStruct struct { + A string + T *[20]byte + } + + type arrayStruct struct { + A string + T [2][20]byte + } + + type sliceStruct struct { + A string + T [][20]byte + } + + concretest := reflect.TypeOf(&concreteStruct{}) + concreteLargest := reflect.TypeOf(&concreteStructWithLargeAddress{}) + pointertst := reflect.TypeOf(&pointerStruct{}) + arrayst := reflect.TypeOf(&arrayStruct{}) + slicest := reflect.TypeOf(&sliceStruct{}) + + type Bytes20AddressType [20]byte + + type otherIntegerType struct { + A string + T Bytes20AddressType + } + + type pointerOtherIntegerType struct { + A string + T *Bytes20AddressType + } + oit := reflect.TypeOf(&otherIntegerType{}) + oitpt := reflect.TypeOf(&pointerOtherIntegerType{}) + + testAddrBytes := [20]byte{} + testAddrStr := "0x" + hex.EncodeToString(testAddrBytes[:]) + anyString := "test" + + t.Run("RetypeToOffChain converts fixed length bytes to string", func(t *testing.T) { + for _, test := range []struct { + name string + tp reflect.Type + }{ + {"[20]byte", concretest}, + {"typed address", oit}, + {"[20]byte pointer", pointertst}, + {"*typed address", oitpt}, + } { + t.Run(test.name, func(t *testing.T) { + converter := codec.NewAddressBytesToStringModifier([]string{"T"}, mockModifier) + convertedType, err := converter.RetypeToOffChain(test.tp, "") + + require.NoError(t, err) + assert.Equal(t, reflect.Pointer, convertedType.Kind()) + convertedType = convertedType.Elem() + + require.Equal(t, 2, convertedType.NumField()) + assert.Equal(t, test.tp.Elem().Field(0), convertedType.Field(0)) + assert.Equal(t, test.tp.Elem().Field(1).Name, convertedType.Field(1).Name) + assert.Equal(t, reflect.TypeOf(""), convertedType.Field(1).Type) + }) + } + }) + + t.Run("RetypeToOffChain converts arrays of fixed length bytes to array of string", func(t *testing.T) { + converter := codec.NewAddressBytesToStringModifier([]string{"T"}, mockModifier) + + convertedType, err := converter.RetypeToOffChain(arrayst, "") + require.NoError(t, err) + assert.Equal(t, reflect.Pointer, convertedType.Kind()) + convertedType = convertedType.Elem() + + require.Equal(t, 2, convertedType.NumField()) + assert.Equal(t, arrayst.Elem().Field(0), convertedType.Field(0)) + assert.Equal(t, reflect.TypeOf([2]string{}), convertedType.Field(1).Type) + }) + + t.Run("RetypeToOffChain converts slices of fixed length bytes to slices of string", func(t *testing.T) { + converter := codec.NewAddressBytesToStringModifier([]string{"T"}, mockModifier) + + convertedType, err := converter.RetypeToOffChain(slicest, "") + require.NoError(t, err) + assert.Equal(t, reflect.Pointer, convertedType.Kind()) + convertedType = convertedType.Elem() + + require.Equal(t, 2, convertedType.NumField()) + assert.Equal(t, slicest.Elem().Field(0), convertedType.Field(0)) + assert.Equal(t, reflect.TypeOf([]string{}), convertedType.Field(1).Type) + }) + + t.Run("TransformToOnChain converts string to bytes", func(t *testing.T) { + for _, test := range []struct { + name string + t reflect.Type + expected any + }{ + {"[20]byte", concretest, &concreteStruct{A: anyString, T: [20]byte{}}}, + {"*[20]byte", pointertst, &pointerStruct{A: anyString, T: &[20]byte{}}}, + {"typed address", oit, &otherIntegerType{A: anyString, T: Bytes20AddressType{}}}, + {"*typed address", oitpt, &pointerOtherIntegerType{A: anyString, T: &Bytes20AddressType{}}}, + } { + t.Run(test.name, func(t *testing.T) { + converter := codec.NewAddressBytesToStringModifier([]string{"T"}, mockModifier) + convertedType, err := converter.RetypeToOffChain(test.t, "") + require.NoError(t, err) + + rOffchain := reflect.New(convertedType.Elem()) + iOffChain := reflect.Indirect(rOffchain) + iOffChain.FieldByName("A").SetString(anyString) + iOffChain.FieldByName("T").Set(reflect.ValueOf(testAddrStr)) + + actual, err := converter.TransformToOnChain(rOffchain.Interface(), "") + require.NoError(t, err) + + assert.Equal(t, test.expected, actual) + }) + } + }) + + t.Run("TransformToOnChain converts string array to array of fixed length bytes", func(t *testing.T) { + converter := codec.NewAddressBytesToStringModifier([]string{"T"}, mockModifier) + + convertedType, err := converter.RetypeToOffChain(arrayst, "") + require.NoError(t, err) + + rOffchain := reflect.New(convertedType.Elem()) + iOffChain := reflect.Indirect(rOffchain) + + arrayValue := [2]string{testAddrStr, testAddrStr} + + iOffChain.FieldByName("T").Set(reflect.ValueOf(arrayValue)) + + actual, err := converter.TransformToOnChain(rOffchain.Interface(), "") + require.NoError(t, err) + + expected := &arrayStruct{A: "", T: [2][20]byte{}} + assert.Equal(t, expected, actual) + }) + + t.Run("TransformToOnChain converts string slice to slice of [length]byte", func(t *testing.T) { + converter := codec.NewAddressBytesToStringModifier([]string{"T"}, mockModifier) + + convertedType, err := converter.RetypeToOffChain(slicest, "") + require.NoError(t, err) + + rOffchain := reflect.New(convertedType.Elem()) + iOffChain := reflect.Indirect(rOffchain) + + iOffChain.FieldByName("T").Set(reflect.ValueOf([]string{testAddrStr, testAddrStr})) + + actual, err := converter.TransformToOnChain(rOffchain.Interface(), "") + require.NoError(t, err) + + expected := &sliceStruct{ + A: "", + T: [][20]byte{ + testAddrBytes, + testAddrBytes, + }, + } + + assert.Equal(t, expected, actual) + }) + + t.Run("TransformToOnChain returns error on invalid inputs", func(t *testing.T) { + converter := codec.NewAddressBytesToStringModifier([]string{"T"}, mockModifier) + + tests := []struct { + name string + addrStr string + structType reflect.Type + }{ + { + name: "Invalid length input", + addrStr: "0x" + hex.EncodeToString([]byte{1, 2, 3}), + structType: concretest, + }, + { + name: "Larger than expected input", + addrStr: "0x" + hex.EncodeToString(make([]byte, 40)), + structType: concreteLargest, + }, + { + name: "Empty string input", + addrStr: "", + structType: concretest, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + convertedType, err := converter.RetypeToOffChain(tt.structType, "") + require.NoError(t, err) + + rOffchain := reflect.New(convertedType.Elem()) + iOffChain := reflect.Indirect(rOffchain) + iOffChain.FieldByName("A").SetString(anyString) + iOffChain.FieldByName("T").Set(reflect.ValueOf(tt.addrStr)) + + _, err = converter.TransformToOnChain(rOffchain.Interface(), "") + require.Error(t, err) + }) + } + }) + + t.Run("TransformToOffChain converts bytes to string", func(t *testing.T) { + for _, test := range []struct { + name string + t reflect.Type + offChain any + }{ + {"[20]byte", concretest, &concreteStruct{A: anyString, T: [20]byte{}}}, + {"*[20]byte", pointertst, &pointerStruct{A: anyString, T: &[20]byte{}}}, + {"typed address", oit, &otherIntegerType{A: anyString, T: Bytes20AddressType{}}}, + {"*typed address", oitpt, &pointerOtherIntegerType{A: anyString, T: &Bytes20AddressType{}}}, + } { + t.Run(test.name, func(t *testing.T) { + converter := codec.NewAddressBytesToStringModifier([]string{"T"}, mockModifier) + convertedType, err := converter.RetypeToOffChain(test.t, "") + require.NoError(t, err) + + actual, err := converter.TransformToOffChain(test.offChain, "") + require.NoError(t, err) + + expected := reflect.New(convertedType.Elem()) + iOffChain := reflect.Indirect(expected) + iOffChain.FieldByName("A").SetString(anyString) + iOffChain.FieldByName("T").Set(reflect.ValueOf(testAddrStr)) + assert.Equal(t, expected.Interface(), actual) + }) + } + }) + + t.Run("TransformToOffChain converts array of bytes to string array", func(t *testing.T) { + converter := codec.NewAddressBytesToStringModifier([]string{"T"}, mockModifier) + + convertedType, err := converter.RetypeToOffChain(arrayst, "") + require.NoError(t, err) + + rOffchain := reflect.New(convertedType.Elem()) + iOffChain := reflect.Indirect(rOffchain) + expectedAddrs := [2]string{testAddrStr, testAddrStr} + iOffChain.FieldByName("T").Set(reflect.ValueOf(expectedAddrs)) + + actual, err := converter.TransformToOffChain(&arrayStruct{A: anyString, T: [2][20]byte{}}, "") + require.NoError(t, err) + + expected := reflect.New(convertedType.Elem()) + iExpected := reflect.Indirect(expected) + iExpected.FieldByName("A").SetString(anyString) + iExpected.FieldByName("T").Set(reflect.ValueOf(expectedAddrs)) + assert.Equal(t, expected.Interface(), actual) + }) + + t.Run("TransformToOffChain converts slice bytes to string slice", func(t *testing.T) { + converter := codec.NewAddressBytesToStringModifier([]string{"T"}, mockModifier) + + convertedType, err := converter.RetypeToOffChain(slicest, "") + require.NoError(t, err) + + rOffchain := reflect.New(convertedType.Elem()) + iOffChain := reflect.Indirect(rOffchain) + expectedAddrs := []string{testAddrStr, testAddrStr} + iOffChain.FieldByName("T").Set(reflect.ValueOf(expectedAddrs)) + + actual, err := converter.TransformToOffChain(&sliceStruct{ + A: anyString, + T: [][20]byte{testAddrBytes, testAddrBytes}, + }, "") + require.NoError(t, err) + + expected := reflect.New(convertedType.Elem()) + iExpected := reflect.Indirect(expected) + iExpected.FieldByName("A").SetString(anyString) + iExpected.FieldByName("T").Set(reflect.ValueOf(expectedAddrs)) + assert.Equal(t, expected.Interface(), actual) + }) + + t.Run("Unsupported field type returns error", func(t *testing.T) { + converter := codec.NewAddressBytesToStringModifier([]string{"T"}, mockModifier) + + unsupportedStruct := struct { + A string + T int // Unsupported type + }{} + + // We expect RetypeToOffChain to return an error because 'T' is not a supported type. + _, err := converter.RetypeToOffChain(reflect.TypeOf(&unsupportedStruct), "") + require.Error(t, err) + assert.Contains(t, err.Error(), "cannot convert bytes for field T") + }) +} diff --git a/pkg/codec/config.go b/pkg/codec/config.go index ec56a39..8a82518 100644 --- a/pkg/codec/config.go +++ b/pkg/codec/config.go @@ -22,6 +22,7 @@ import ( // - hard code -> [HardCodeModifierConfig] // - extract element -> [ElementExtractorModifierConfig] // - epoch to time -> [EpochToTimeModifierConfig] +// - address to string -> [AddressBytesToStringModifierConfig] type ModifiersConfig []ModifierConfig func (m *ModifiersConfig) UnmarshalJSON(data []byte) error { @@ -52,6 +53,8 @@ func (m *ModifiersConfig) UnmarshalJSON(data []byte) error { (*m)[i] = &EpochToTimeModifierConfig{} case ModifierExtractProperty: (*m)[i] = &PropertyExtractorConfig{} + case ModifierAddressToString: + (*m)[i] = &AddressBytesToStringModifierConfig{} default: return fmt.Errorf("%w: unknown modifier type: %s", types.ErrInvalidConfig, mType) } @@ -84,6 +87,7 @@ const ( ModifierExtractElement ModifierType = "extract element" ModifierEpochToTime ModifierType = "epoch to time" ModifierExtractProperty ModifierType = "extract property" + ModifierAddressToString ModifierType = "address to string" ) type ModifierConfig interface { @@ -225,6 +229,25 @@ func (c *PropertyExtractorConfig) MarshalJSON() ([]byte, error) { }) } +// AddressBytesToStringModifierConfig is used to transform address byte fields into string fields. +// It holds the list of fields that should be modified and the chain-specific logic to do the modifications. +type AddressBytesToStringModifierConfig struct { + Fields []string + // Modifier is skipped in JSON serialization, will be injected later. + Modifier AddressModifier `json:"-"` +} + +func (c *AddressBytesToStringModifierConfig) ToModifier(_ ...mapstructure.DecodeHookFunc) (Modifier, error) { + return NewAddressBytesToStringModifier(c.Fields, c.Modifier), nil +} + +func (c *AddressBytesToStringModifierConfig) MarshalJSON() ([]byte, error) { + return json.Marshal(&modifierMarshaller[AddressBytesToStringModifierConfig]{ + Type: ModifierAddressToString, + T: c, + }) +} + type typer struct { Type string } diff --git a/pkg/codec/encodings/type_codec_test.go b/pkg/codec/encodings/type_codec_test.go index f14e87e..545d713 100644 --- a/pkg/codec/encodings/type_codec_test.go +++ b/pkg/codec/encodings/type_codec_test.go @@ -133,6 +133,10 @@ func (*interfaceTesterBase) GetAccountBytes(i int) []byte { return []byte{ib, ib + 1, ib + 2, ib + 3, ib + 4, ib + 5, ib + 6, ib + 7} } +func (t *interfaceTesterBase) GetAccountString(i int) string { + return string(t.GetAccountBytes(i)) +} + type bigEndianInterfaceTester struct { interfaceTesterBase lenient bool @@ -170,6 +174,8 @@ func (b *bigEndianInterfaceTester) encode(t *testing.T, bytes []byte, ts TestStr } bytes = append(bytes, byte(len(ts.Account))) bytes = append(bytes, ts.Account...) + bytes = rawbin.BigEndian.AppendUint32(bytes, uint32(len(ts.AccountStr))) + bytes = append(bytes, []byte(ts.AccountStr)...) bytes = append(bytes, byte(len(ts.Accounts))) for _, account := range ts.Accounts { bytes = append(bytes, byte(len(account))) @@ -249,6 +255,7 @@ func newTestStructCodec(t *testing.T, builder encodings.Builder) encodings.TypeC {Name: "OracleID", Codec: builder.OracleID()}, {Name: "OracleIDs", Codec: oIDs}, {Name: "Account", Codec: acc}, + {Name: "AccountStr", Codec: sCodec}, {Name: "Accounts", Codec: accs}, {Name: "BigField", Codec: bi}, {Name: "NestedDynamicStruct", Codec: midDynamicCodec}, diff --git a/pkg/codec/modifier_base.go b/pkg/codec/modifier_base.go index 261c5df..5f21d21 100644 --- a/pkg/codec/modifier_base.go +++ b/pkg/codec/modifier_base.go @@ -37,6 +37,7 @@ func (m *modifierBase[T]) RetypeToOffChain(onChainType reflect.Type, itemType st return cached, nil } + var offChainType reflect.Type switch onChainType.Kind() { case reflect.Pointer: elm, err := m.RetypeToOffChain(onChainType.Elem(), "") @@ -44,35 +45,30 @@ func (m *modifierBase[T]) RetypeToOffChain(onChainType reflect.Type, itemType st return nil, err } - ptr := reflect.PointerTo(elm) - m.onToOffChainType[onChainType] = ptr - m.offToOnChainType[ptr] = onChainType - return ptr, nil + offChainType = reflect.PointerTo(elm) case reflect.Slice: elm, err := m.RetypeToOffChain(onChainType.Elem(), "") if err != nil { return nil, err } - sliceType := reflect.SliceOf(elm) - m.onToOffChainType[onChainType] = sliceType - m.offToOnChainType[sliceType] = onChainType - return sliceType, nil + offChainType = reflect.SliceOf(elm) case reflect.Array: elm, err := m.RetypeToOffChain(onChainType.Elem(), "") if err != nil { return nil, err } - arrayType := reflect.ArrayOf(onChainType.Len(), elm) - m.onToOffChainType[onChainType] = arrayType - m.offToOnChainType[arrayType] = onChainType - return arrayType, nil + offChainType = reflect.ArrayOf(onChainType.Len(), elm) case reflect.Struct: return m.getStructType(onChainType) default: return nil, fmt.Errorf("%w: cannot retype the kind %v", types.ErrInvalidType, onChainType.Kind()) } + + m.onToOffChainType[onChainType] = offChainType + m.offToOnChainType[offChainType] = onChainType + return offChainType, nil } func (m *modifierBase[T]) getStructType(outputType reflect.Type) (reflect.Type, error) { diff --git a/pkg/custmsg/custom_message.go b/pkg/custmsg/custom_message.go new file mode 100644 index 0000000..54e1e2d --- /dev/null +++ b/pkg/custmsg/custom_message.go @@ -0,0 +1,83 @@ +package custmsg + +import ( + "context" + "fmt" + + "google.golang.org/protobuf/proto" + + "github.com/goplugin/plugin-common/pkg/beholder" + "github.com/goplugin/plugin-common/pkg/beholder/pb" + "github.com/goplugin/plugin-common/pkg/values" +) + +type Labeler struct { + labels map[string]string +} + +func NewLabeler() Labeler { + return Labeler{labels: make(map[string]string)} +} + +// With adds multiple key-value pairs to the CustomMessageLabeler for transmission With SendLogAsCustomMessage +func (c Labeler) With(keyValues ...string) Labeler { + newCustomMessageLabeler := NewLabeler() + + if len(keyValues)%2 != 0 { + // If an odd number of key-value arguments is passed, return the original CustomMessageLabeler unchanged + return c + } + + // Copy existing labels from the current agent + for k, v := range c.labels { + newCustomMessageLabeler.labels[k] = v + } + + // Add new key-value pairs + for i := 0; i < len(keyValues); i += 2 { + key := keyValues[i] + value := keyValues[i+1] + newCustomMessageLabeler.labels[key] = value + } + + return newCustomMessageLabeler +} + +// SendLogAsCustomMessage emits a BaseMessage With msg and labels as data. +// any key in labels that is not part of orderedLabelKeys will not be transmitted +func (c Labeler) SendLogAsCustomMessage(msg string) error { + return sendLogAsCustomMessageW(msg, c.labels) +} + +func sendLogAsCustomMessageW(msg string, labels map[string]string) error { + // cast to map[string]any + newLabels := map[string]any{} + for k, v := range labels { + newLabels[k] = v + } + + m, err := values.NewMap(newLabels) + if err != nil { + return fmt.Errorf("could not wrap labels to map: %w", err) + } + + // Define a custom protobuf payload to emit + payload := &pb.BaseMessage{ + Msg: msg, + Labels: values.ProtoMap(m), + } + payloadBytes, err := proto.Marshal(payload) + if err != nil { + return fmt.Errorf("sending custom message failed to marshal protobuf: %w", err) + } + + err = beholder.GetEmitter().Emit(context.Background(), payloadBytes, + "beholder_data_schema", "/beholder-base-message/versions/1", // required + "beholder_data_type", "custom_message", + ) + if err != nil { + return fmt.Errorf("sending custom message failed on emit: %w", err) + } + + return nil +} diff --git a/pkg/custmsg/custom_message_test.go b/pkg/custmsg/custom_message_test.go new file mode 100644 index 0000000..4d41408 --- /dev/null +++ b/pkg/custmsg/custom_message_test.go @@ -0,0 +1,16 @@ +package custmsg + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +// tests CustomMessageAgent does not share state across new instances created by `With` +func Test_CustomMessageAgent(t *testing.T) { + cma := NewLabeler() + cma1 := cma.With("key1", "value1") + cma2 := cma1.With("key2", "value2") + + assert.NotEqual(t, cma1.labels, cma2.labels) +} diff --git a/pkg/loop/internal/relayer/pluginprovider/contractreader/helper_test.go b/pkg/loop/internal/relayer/pluginprovider/contractreader/helper_test.go index 493d408..68deab1 100644 --- a/pkg/loop/internal/relayer/pluginprovider/contractreader/helper_test.go +++ b/pkg/loop/internal/relayer/pluginprovider/contractreader/helper_test.go @@ -44,11 +44,16 @@ func (*cannotEncode) UnmarshalText() error { type interfaceTesterBase struct{} var anyAccountBytes = []byte{1, 2, 3} +var anyAccountString = string(anyAccountBytes) func (it *interfaceTesterBase) GetAccountBytes(_ int) []byte { return anyAccountBytes } +func (it *interfaceTesterBase) GetAccountString(_ int) string { + return anyAccountString +} + func (it *interfaceTesterBase) Name() string { return "relay client" } diff --git a/pkg/types/ccipocr3/rmn_types.go b/pkg/types/ccipocr3/rmn_types.go index 73f6d1f..8d88394 100644 --- a/pkg/types/ccipocr3/rmn_types.go +++ b/pkg/types/ccipocr3/rmn_types.go @@ -2,8 +2,8 @@ package ccipocr3 // RMNReport is the payload that is signed by the RMN nodes, transmitted and verified onchain. type RMNReport struct { - ReportVersionDigest Bytes32 // e.g. keccak256("RMN_V1_6_ANY2EVM_REPORT") - DestChainID BigInt // If applies, a chain specific id, e.g. evm chain id otherwise empty. + ReportVersion string // e.g. "RMN_V1_6_ANY2EVM_REPORT". + DestChainID BigInt // If applies, a chain specific id, e.g. evm chain id otherwise empty. DestChainSelector ChainSelector RmnRemoteContractAddress Bytes OfframpAddress Bytes diff --git a/pkg/types/interfacetests/codec_interface_fuzz_tests.go b/pkg/types/interfacetests/codec_interface_fuzz_tests.go index 8aba6f3..47d756b 100644 --- a/pkg/types/interfacetests/codec_interface_fuzz_tests.go +++ b/pkg/types/interfacetests/codec_interface_fuzz_tests.go @@ -38,6 +38,7 @@ func RunCodecInterfaceFuzzTests(f *testing.F, tester CodecInterfaceTester) { OracleID: commontypes.OracleID(oracleId), OracleIDs: oids, Account: tester.GetAccountBytes(accountSeed), + AccountStr: tester.GetAccountString(accountSeed), Accounts: [][]byte{tester.GetAccountBytes(accountsSeed + 1), tester.GetAccountBytes(accountsSeed + 2)}, BigField: big.NewInt(bigField), NestedDynamicStruct: MidLevelDynamicTestStruct{ diff --git a/pkg/types/interfacetests/codec_interface_tests.go b/pkg/types/interfacetests/codec_interface_tests.go index 1b8c303..808cd23 100644 --- a/pkg/types/interfacetests/codec_interface_tests.go +++ b/pkg/types/interfacetests/codec_interface_tests.go @@ -66,6 +66,7 @@ func RunCodecInterfaceTests(t *testing.T, tester CodecInterfaceTester) { resp := tester.EncodeFields(t, req) compatibleItem := compatibleTestStruct{ Account: item.Account, + AccountStr: item.AccountStr, Accounts: item.Accounts, BigField: item.BigField, DifferentField: item.DifferentField, @@ -95,6 +96,7 @@ func RunCodecInterfaceTests(t *testing.T, tester CodecInterfaceTester) { resp := tester.EncodeFields(t, req) compatibleMap := map[string]any{ "Account": item.Account, + "AccountStr": item.AccountStr, "Accounts": item.Accounts, "BigField": item.BigField, "DifferentField": item.DifferentField, @@ -139,6 +141,7 @@ func RunCodecInterfaceTests(t *testing.T, tester CodecInterfaceTester) { OracleID: ts.OracleID, OracleIDs: ts.OracleIDs, Account: ts.Account, + AccountStr: ts.AccountStr, Accounts: ts.Accounts, BigField: ts.BigField, NestedDynamicStruct: ts.NestedDynamicStruct, @@ -353,6 +356,7 @@ func RunCodecInterfaceTests(t *testing.T, tester CodecInterfaceTester) { OracleID: 0, OracleIDs: [32]commontypes.OracleID{}, Account: nil, + AccountStr: "", Accounts: nil, BigField: nil, NestedDynamicStruct: MidLevelDynamicTestStruct{}, diff --git a/pkg/types/interfacetests/utils.go b/pkg/types/interfacetests/utils.go index 6df1832..809e45a 100644 --- a/pkg/types/interfacetests/utils.go +++ b/pkg/types/interfacetests/utils.go @@ -20,6 +20,7 @@ type BasicTester[T any] interface { Setup(t T) Name() string GetAccountBytes(i int) []byte + GetAccountString(i int) string } type testcase[T any] struct { @@ -157,6 +158,7 @@ type TestStruct struct { OracleID commontypes.OracleID OracleIDs [32]commontypes.OracleID Account []byte + AccountStr string Accounts [][]byte DifferentField string BigField *big.Int @@ -174,6 +176,7 @@ type TestStructMissingField struct { OracleID commontypes.OracleID OracleIDs [32]commontypes.OracleID Account []byte + AccountStr string Accounts [][]byte BigField *big.Int NestedDynamicStruct MidLevelDynamicTestStruct @@ -183,6 +186,7 @@ type TestStructMissingField struct { // compatibleTestStruct has fields in a different order type compatibleTestStruct struct { Account []byte + AccountStr string Accounts [][]byte BigField *big.Int DifferentField string @@ -217,6 +221,7 @@ func CreateTestStruct[T any](i int, tester BasicTester[T]) TestStruct { OracleID: commontypes.OracleID(i + 1), OracleIDs: [32]commontypes.OracleID{commontypes.OracleID(i + 2), commontypes.OracleID(i + 3)}, Account: tester.GetAccountBytes(i + 3), + AccountStr: tester.GetAccountString(i + 3), Accounts: [][]byte{tester.GetAccountBytes(i + 4), tester.GetAccountBytes(i + 5)}, DifferentField: s, BigField: big.NewInt(int64((i + 1) * (i + 2))), diff --git a/pkg/types/llo/types.go b/pkg/types/llo/types.go index e17d32e..22fa1ed 100644 --- a/pkg/types/llo/types.go +++ b/pkg/types/llo/types.go @@ -8,6 +8,7 @@ import ( "math" "github.com/goplugin/plugin-libocr/offchainreporting2plus/ocr3types" + ocr2types "github.com/goplugin/plugin-libocr/offchainreporting2plus/types" "github.com/goplugin/plugin-common/pkg/services" ) @@ -35,8 +36,15 @@ const ( // NOTE: Only add something here if you actually need it, because it has to // be supported forever and can't be changed + + // ReportFormatEVMPremiumLegacy maintains compatibility with the legacy + // Mercury v0.3 report format ReportFormatEVMPremiumLegacy ReportFormat = 1 - ReportFormatJSON ReportFormat = 2 + // ReportFormatJSON is a simple JSON format for reference and debugging + ReportFormatJSON ReportFormat = 2 + // ReportFormatRetirement is a special "capstone" report format to indicate + // a retired OCR instance, and handover crucial information to a new one + ReportFormatRetirement ReportFormat = 3 _ ReportFormat = math.MaxUint32 // reserved ) @@ -44,6 +52,7 @@ const ( var ReportFormats = []ReportFormat{ ReportFormatEVMPremiumLegacy, ReportFormatJSON, + ReportFormatRetirement, } func (rf ReportFormat) String() string { @@ -52,6 +61,8 @@ func (rf ReportFormat) String() string { return "evm_premium_legacy" case ReportFormatJSON: return "json" + case ReportFormatRetirement: + return "retirement" default: return fmt.Sprintf("unknown(%d)", rf) } @@ -63,6 +74,8 @@ func ReportFormatFromString(s string) (ReportFormat, error) { return ReportFormatEVMPremiumLegacy, nil case "json": return ReportFormatJSON, nil + case "retirement": + return ReportFormatRetirement, nil default: return 0, fmt.Errorf("unknown report format: %q", s) } @@ -302,3 +315,7 @@ type ChannelDefinitionCache interface { Definitions() ChannelDefinitions services.Service } + +type ShouldRetireCache interface { + ShouldRetire(digest ocr2types.ConfigDigest) (bool, error) +} diff --git a/pkg/types/provider_llo.go b/pkg/types/provider_llo.go index 855679a..527003b 100644 --- a/pkg/types/provider_llo.go +++ b/pkg/types/provider_llo.go @@ -1,11 +1,21 @@ package types import ( + ocrtypes "github.com/goplugin/plugin-libocr/offchainreporting2plus/types" + "github.com/goplugin/plugin-common/pkg/types/llo" ) +type LLOConfigProvider interface { + OffchainConfigDigester() ocrtypes.OffchainConfigDigester + // One instance will be run per config tracker + ContractConfigTrackers() []ocrtypes.ContractConfigTracker +} + type LLOProvider interface { - ConfigProvider + Service + LLOConfigProvider + ShouldRetireCache() llo.ShouldRetireCache ContractTransmitter() llo.Transmitter ChannelDefinitionCache() llo.ChannelDefinitionCache } diff --git a/pkg/workflows/exec/interpolation.go b/pkg/workflows/exec/interpolation.go index 7c6b4e1..992673d 100644 --- a/pkg/workflows/exec/interpolation.go +++ b/pkg/workflows/exec/interpolation.go @@ -104,8 +104,9 @@ func FindAndInterpolateAllKeys(input any, state Results) (any, error) { } type Env struct { - Binary []byte - Config []byte + Binary []byte + Config []byte + Secrets map[string]string } // FindAndInterpolateEnv takes a `config` any value, and recursively @@ -126,7 +127,7 @@ func FindAndInterpolateEnvVars(input any, env Env) (any, error) { } splitToken := strings.Split(matches[1], ".") - if len(splitToken) != 2 { + if len(splitToken) < 2 { return el, nil } @@ -139,8 +140,26 @@ func FindAndInterpolateEnvVars(input any, env Env) (any, error) { return env.Config, nil case "binary": return env.Binary, nil + case "secrets": + switch len(splitToken) { + // A token of the form: + // ENV.secrets. + case 3: + got, ok := env.Secrets[splitToken[2]] + if !ok { + return "", fmt.Errorf("invalid env token: could not find %q in ENV.secrets", splitToken[2]) + } + + return got, nil + // A token of the form: + // ENV.secrets + case 2: + return env.Secrets, nil + } + + return nil, fmt.Errorf("invalid env token: must contain two or three elements, got %q", el.(string)) default: - return "", fmt.Errorf("invalid env token: must be of the form $(ENV.): got %s", el) + return "", fmt.Errorf("invalid env token: must be of the form $(ENV.): got %s", el) } }, ) diff --git a/pkg/workflows/exec/interpolation_test.go b/pkg/workflows/exec/interpolation_test.go index a0c92ea..bde54e6 100644 --- a/pkg/workflows/exec/interpolation_test.go +++ b/pkg/workflows/exec/interpolation_test.go @@ -247,6 +247,50 @@ func TestInterpolateEnv(t *testing.T) { assert.NoError(t, err) } +func TestInterpolateEnv_Secrets(t *testing.T) { + c := map[string]any{ + "fidelityAPIKey": "$(ENV.secrets.fidelity)", + } + _, err := exec.FindAndInterpolateEnvVars(c, exec.Env{}) + assert.ErrorContains(t, err, `invalid env token: could not find "fidelity" in ENV.secrets`) + + c = map[string]any{ + "fidelityAPIKey": "$(ENV.secrets.fidelity.foo)", + } + _, err = exec.FindAndInterpolateEnvVars(c, exec.Env{}) + assert.ErrorContains(t, err, `invalid env token: must contain two or three elements`) + + c = map[string]any{ + "secrets": "$(ENV.secrets)", + } + secrets := map[string]string{ + "foo": "fooSecret", + "bar": "barSecret", + } + got, err := exec.FindAndInterpolateEnvVars( + c, + exec.Env{Secrets: secrets}) + require.NoError(t, err) + assert.Equal(t, got, map[string]any{ + "secrets": secrets, + }) + + c = map[string]any{ + "secrets": "$(ENV.secrets.foo)", + } + secrets = map[string]string{ + "foo": "fooSecret", + "bar": "barSecret", + } + got, err = exec.FindAndInterpolateEnvVars( + c, + exec.Env{Secrets: secrets}) + require.NoError(t, err) + assert.Equal(t, got, map[string]any{ + "secrets": "fooSecret", + }) +} + type fakeResults map[string]*exec.Result func (f fakeResults) ResultForStep(s string) (*exec.Result, bool) {