├── .gitignore ├── .github ├── FUNDING.yml ├── dependabot.yml └── workflows │ ├── release.yml │ └── codeql-analysis.yml ├── docs ├── grafana7-dashboard.png ├── grafana8-dashboard.png ├── README.md ├── grafana8.json ├── grafana8_2.json └── grafana7.json ├── Dockerfile ├── Makefile ├── main.go ├── go.mod ├── collector_e2e_test.go ├── collector.go ├── collector_test.go ├── LICENSE └── go.sum /.gitignore: -------------------------------------------------------------------------------- 1 | bin/ 2 | .idea/ -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: 0xERR0R 2 | ko_fi: 0xerr0r 3 | custom: ["paypal.me/spx01"] 4 | -------------------------------------------------------------------------------- /docs/grafana7-dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xERR0R/dex/HEAD/docs/grafana7-dashboard.png -------------------------------------------------------------------------------- /docs/grafana8-dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xERR0R/dex/HEAD/docs/grafana8-dashboard.png -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: gomod 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | time: "04:00" 8 | open-pull-requests-limit: 10 9 | assignees: 10 | - 0xERR0R 11 | - package-ecosystem: github-actions 12 | directory: "/" 13 | schedule: 14 | interval: daily 15 | assignees: 16 | - 0xERR0R 17 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # build stage 2 | FROM golang:alpine AS build-env 3 | RUN apk add --no-cache \ 4 | git \ 5 | make \ 6 | gcc \ 7 | libc-dev \ 8 | tzdata \ 9 | zip \ 10 | ca-certificates 11 | 12 | ENV GO111MODULE=on \ 13 | CGO_ENABLED=0 14 | 15 | WORKDIR /src 16 | 17 | COPY go.mod . 18 | COPY go.sum . 19 | RUN go mod download 20 | 21 | # add source 22 | ADD . . 23 | 24 | RUN make build 25 | 26 | RUN mkdir -p \ 27 | /rootfs/app \ 28 | /rootfs/usr/share \ 29 | /rootfs/etc/ssl/certs \ 30 | && cp -t /rootfs/app /src/bin/dex \ 31 | && : `# the timezone data:` \ 32 | && cp -Rt /rootfs/usr/share /usr/share/zoneinfo \ 33 | && : `# the tls certificates:` \ 34 | && cp -t /rootfs/etc/ssl/certs /etc/ssl/certs/ca-certificates.crt 35 | 36 | # final stage 37 | FROM scratch 38 | COPY --from=build-env /rootfs / 39 | 40 | ENTRYPOINT ["/app/dex"] 41 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: build docker-build docker-buildx-push help test clean 2 | .DEFAULT_GOAL := help 3 | 4 | DOCKER_IMAGE_NAME=spx01/dex 5 | DOCKER_TAG=latest 6 | BINARY_NAME=dex 7 | BIN_OUT_DIR=bin 8 | 9 | 10 | build: ## Build binary 11 | go build -v -ldflags="-w -s" -o $(BIN_OUT_DIR)/$(BINARY_NAME) 12 | 13 | docker-buildx-push: ## Build multi arch docker images and push 14 | docker buildx build \ 15 | --platform linux/386,linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64 \ 16 | --tag $(DOCKER_IMAGE_NAME):$(DOCKER_TAG) \ 17 | --tag $(DOCKER_IMAGE_NAME):latest \ 18 | --push . 19 | 20 | docker-build: ## Build docker image 21 | docker build --network=host --tag $(DOCKER_IMAGE_NAME):$(DOCKER_TAG) . 22 | 23 | help: ## Shows help 24 | @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' 25 | 26 | test: 27 | go test ./... -v 28 | 29 | clean: 30 | rm -f $(BIN_OUT_DIR)/$(BINARY_NAME) 31 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | 6 | jobs: 7 | 8 | build: 9 | name: Build 10 | runs-on: ubuntu-latest 11 | env: 12 | SHOULD_PUSH: ${{ github.repository_owner == '0xERR0R' && github.ref == 'refs/heads/master' }} 13 | 14 | steps: 15 | 16 | - name: Checkout 17 | uses: actions/checkout@v6 18 | with: 19 | fetch-depth: 0 20 | 21 | - name: Set up Go 22 | uses: actions/setup-go@v6 23 | with: 24 | go-version-file: go.mod 25 | 26 | - name: Run tests 27 | run: make test 28 | 29 | - name: Build 30 | run: make build 31 | 32 | - name: Set up Docker Buildx 33 | if: env.SHOULD_PUSH == 'true' 34 | uses: docker/setup-buildx-action@v3 35 | with: 36 | version: latest 37 | 38 | - name: Get next version 39 | id: version 40 | if: env.SHOULD_PUSH == 'true' 41 | uses: reecetech/version-increment@2024.10.1 42 | with: 43 | scheme: semver 44 | increment: patch 45 | 46 | - name: Set Version 47 | if: env.SHOULD_PUSH == 'true' 48 | run: | 49 | git tag ${{ steps.version.outputs.v-version }} 50 | git push origin ${{ steps.version.outputs.v-version }} 51 | 52 | - name: Login to DockerHub 53 | if: env.SHOULD_PUSH == 'true' 54 | uses: docker/login-action@v3 55 | with: 56 | username: ${{ secrets.DOCKER_USERNAME }} 57 | password: ${{ secrets.DOCKER_TOKEN }} 58 | 59 | - name: Build the Docker image and push 60 | if: env.SHOULD_PUSH == 'true' 61 | run: make docker-buildx-push DOCKER_TAG=${{ steps.version.outputs.version }} 62 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "net/http" 7 | "os" 8 | "os/signal" 9 | "strconv" 10 | "strings" 11 | "time" 12 | 13 | "github.com/prometheus/client_golang/prometheus" 14 | "github.com/prometheus/client_golang/prometheus/promhttp" 15 | 16 | log "github.com/sirupsen/logrus" 17 | ) 18 | 19 | func main() { 20 | reg := prometheus.NewRegistry() 21 | 22 | var labels []string 23 | 24 | if rawLabels, isSet := os.LookupEnv("DEX_LABELS"); isSet { 25 | labels = strings.Split(rawLabels, ",") 26 | } 27 | 28 | reg.MustRegister(newDockerCollector(labels)) 29 | 30 | router := http.NewServeMux() 31 | router.Handle("/metrics", promhttp.HandlerFor(reg, promhttp.HandlerOpts{ 32 | Registry: reg, 33 | })) 34 | 35 | serverPort := 8080 36 | 37 | if strPort, isSet := os.LookupEnv("DEX_PORT"); isSet { 38 | if intPort, err := strconv.Atoi(strPort); err == nil { 39 | serverPort = intPort 40 | } 41 | } 42 | 43 | server := &http.Server{ 44 | Addr: fmt.Sprintf(":%v", serverPort), 45 | Handler: router, 46 | ReadTimeout: 5 * time.Second, 47 | WriteTimeout: 120 * time.Second, 48 | IdleTimeout: 15 * time.Second, 49 | } 50 | 51 | done := make(chan bool) 52 | 53 | quit := make(chan os.Signal, 1) 54 | signal.Notify(quit, os.Interrupt) 55 | 56 | go func() { 57 | <-quit 58 | log.Info("Server is shutting down...") 59 | 60 | ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) 61 | defer cancel() 62 | 63 | if err := server.Shutdown(ctx); err != nil { 64 | log.Fatalf("Could not gracefully shutdown the server: %v\n", err) 65 | } 66 | close(done) 67 | }() 68 | 69 | log.Info("Server is ready to handle requests at :", serverPort) 70 | if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed { 71 | log.Fatalf("Could not listen on %d: %v\n", serverPort, err) 72 | } 73 | 74 | <-done 75 | log.Info("Server stopped") 76 | } 77 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ master ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ master ] 20 | schedule: 21 | - cron: '44 10 * * 4' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'go' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 37 | # Learn more about CodeQL language support at https://git.io/codeql-language-support 38 | 39 | steps: 40 | - name: Checkout repository 41 | uses: actions/checkout@v6 42 | 43 | # Initializes the CodeQL tools for scanning. 44 | - name: Initialize CodeQL 45 | uses: github/codeql-action/init@v4 46 | with: 47 | languages: ${{ matrix.language }} 48 | # If you wish to specify custom queries, you can do so here or in a config file. 49 | # By default, queries listed here will override any specified in a config file. 50 | # Prefix the list here with "+" to use these queries and those in the config file. 51 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 52 | 53 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 54 | # If this step fails, then you should remove it and run the build manually (see below) 55 | - name: Autobuild 56 | uses: github/codeql-action/autobuild@v4 57 | 58 | # ℹ️ Command-line programs to run using the OS shell. 59 | # 📚 https://git.io/JvXDl 60 | 61 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 62 | # and modify them (or add more) to build your code if your project 63 | # uses a compiled language 64 | 65 | #- run: | 66 | # make bootstrap 67 | # make release 68 | 69 | - name: Perform CodeQL Analysis 70 | uses: github/codeql-action/analyze@v4 71 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module dex 2 | 3 | go 1.24.4 4 | 5 | require ( 6 | github.com/docker/docker v28.5.2+incompatible 7 | github.com/prometheus/client_golang v1.23.2 8 | github.com/prometheus/client_model v0.6.2 9 | github.com/sirupsen/logrus v1.9.3 10 | github.com/stretchr/testify v1.11.1 11 | github.com/testcontainers/testcontainers-go v0.40.0 12 | ) 13 | 14 | require ( 15 | dario.cat/mergo v1.0.2 // indirect 16 | github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect 17 | github.com/Microsoft/go-winio v0.6.2 // indirect 18 | github.com/beorn7/perks v1.0.1 // indirect 19 | github.com/cenkalti/backoff/v4 v4.3.0 // indirect 20 | github.com/cespare/xxhash/v2 v2.3.0 // indirect 21 | github.com/containerd/errdefs v1.0.0 // indirect 22 | github.com/containerd/errdefs/pkg v0.3.0 // indirect 23 | github.com/containerd/log v0.1.0 // indirect 24 | github.com/containerd/platforms v0.2.1 // indirect 25 | github.com/cpuguy83/dockercfg v0.3.2 // indirect 26 | github.com/davecgh/go-spew v1.1.1 // indirect 27 | github.com/distribution/reference v0.6.0 // indirect 28 | github.com/docker/go-connections v0.6.0 // indirect 29 | github.com/docker/go-units v0.5.0 // indirect 30 | github.com/ebitengine/purego v0.8.4 // indirect 31 | github.com/felixge/httpsnoop v1.0.4 // indirect 32 | github.com/go-logr/logr v1.4.2 // indirect 33 | github.com/go-logr/stdr v1.2.2 // indirect 34 | github.com/go-ole/go-ole v1.2.6 // indirect 35 | github.com/google/uuid v1.6.0 // indirect 36 | github.com/klauspost/compress v1.18.0 // indirect 37 | github.com/kylelemons/godebug v1.1.0 // indirect 38 | github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect 39 | github.com/magiconair/properties v1.8.10 // indirect 40 | github.com/moby/docker-image-spec v1.3.1 // indirect 41 | github.com/moby/go-archive v0.1.0 // indirect 42 | github.com/moby/patternmatcher v0.6.0 // indirect 43 | github.com/moby/sys/sequential v0.6.0 // indirect 44 | github.com/moby/sys/user v0.4.0 // indirect 45 | github.com/moby/sys/userns v0.1.0 // indirect 46 | github.com/moby/term v0.5.0 // indirect 47 | github.com/morikuni/aec v1.0.0 // indirect 48 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 49 | github.com/opencontainers/go-digest v1.0.0 // indirect 50 | github.com/opencontainers/image-spec v1.1.1 // indirect 51 | github.com/pkg/errors v0.9.1 // indirect 52 | github.com/pmezard/go-difflib v1.0.0 // indirect 53 | github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect 54 | github.com/prometheus/common v0.66.1 // indirect 55 | github.com/prometheus/procfs v0.16.1 // indirect 56 | github.com/shirou/gopsutil/v4 v4.25.6 // indirect 57 | github.com/tklauser/go-sysconf v0.3.12 // indirect 58 | github.com/tklauser/numcpus v0.6.1 // indirect 59 | github.com/yusufpapurcu/wmi v1.2.4 // indirect 60 | go.opentelemetry.io/auto/sdk v1.1.0 // indirect 61 | go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 // indirect 62 | go.opentelemetry.io/otel v1.35.0 // indirect 63 | go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.29.0 // indirect 64 | go.opentelemetry.io/otel/metric v1.35.0 // indirect 65 | go.opentelemetry.io/otel/sdk v1.29.0 // indirect 66 | go.opentelemetry.io/otel/trace v1.35.0 // indirect 67 | go.yaml.in/yaml/v2 v2.4.2 // indirect 68 | golang.org/x/crypto v0.45.0 // indirect 69 | golang.org/x/sys v0.38.0 // indirect 70 | golang.org/x/time v0.6.0 // indirect 71 | google.golang.org/genproto/googleapis/api v0.0.0-20240826202546-f6391c0de4c7 // indirect 72 | google.golang.org/protobuf v1.36.8 // indirect 73 | gopkg.in/yaml.v3 v3.0.1 // indirect 74 | ) 75 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | [![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/0xERR0R/dex/release.yml "Release")](https://github.com/0xERR0R/dex/actions/workflows/release.yml) 2 | [![GitHub go.mod Go version](https://img.shields.io/github/go-mod/go-version/0xERR0R/dex "Go version")](#) 3 | [![Donation](https://img.shields.io/badge/buy%20me%20a%20coffee-donate-blueviolet.svg)](https://ko-fi.com/0xerr0r) 4 | 5 | # DEX - Docker EXporter for prometheus 6 | 7 | DEX is a lightweight Prometheus exporter that monitors Docker containers and exports their metrics. It's designed to run as a Docker container and supports all architectures. 8 | 9 | ## Key Features 10 | - Lightweight and efficient 11 | - Supports all architectures 12 | - Real-time container metrics monitoring 13 | - Easy integration with Prometheus and Grafana 14 | - No configuration required for basic usage 15 | 16 | ## Exposed Metrics 17 | 18 | | Metric Name | Type | Description | Labels | 19 | |------------|------|-------------|--------| 20 | | dex_block_io_read_bytes_total | Counter | Total number of bytes read from block devices | `container_name` | 21 | | dex_block_io_write_bytes_total | Counter | Total number of bytes written to block devices | `container_name` | 22 | | dex_container_exited | Gauge | 1 if container has exited, 0 otherwise | `container_name` | 23 | | dex_container_restarting | Gauge | 1 if container is restarting, 0 otherwise | `container_name` | 24 | | dex_container_restarts_total | Counter | Total number of container restarts | `container_name` | 25 | | dex_container_running | Gauge | 1 if container is running, 0 otherwise | `container_name` | 26 | | dex_cpu_utilization_percent | Gauge | Current CPU utilization percentage | `container_name` | 27 | | dex_cpu_utilization_seconds_total | Counter | Cumulative CPU time consumed | `container_name` | 28 | | dex_memory_total_bytes | Gauge | Total memory limit in bytes | `container_name` | 29 | | dex_memory_usage_bytes | Counter | Current memory usage in bytes | `container_name` | 30 | | dex_memory_utilization_percent | Gauge | Current memory utilization percentage | `container_name` | 31 | | dex_network_rx_bytes_total | Counter | Total bytes received over network | `container_name` | 32 | | dex_network_tx_bytes_total | Counter | Total bytes transmitted over network | `container_name` | 33 | | dex_pids_current | Counter | Current number of processes in the container | `container_name` | 34 | 35 | ## Prerequisites 36 | - Docker installed and running 37 | - Prometheus server (for metrics collection) 38 | - Grafana (optional, for visualization) 39 | 40 | ## Configuration 41 | 42 | | Environment Variable | Description | Default | 43 | |---|---|---| 44 | | `DEX_PORT` | The port the exporter will listen on. | `8080` | 45 | | `DEX_LABELS` | Comma-separated list of additional labels to export. | `""` | 46 | 47 | ### Dynamic Labels 48 | 49 | You can add extra labels to all metrics using the `DEX_LABELS` environment variable. This can be useful for adding more context to your metrics, such as the image name or command. 50 | 51 | **Available Labels:** 52 | - `image` 53 | - `image_id` 54 | - `command` 55 | - `created` 56 | 57 | **Example:** 58 | 59 | ```bash 60 | export DEX_LABELS="image,command" 61 | ``` 62 | 63 | This will add the `image` and `command` labels to all exported metrics. 64 | 65 | > [!WARNING] 66 | > **NOTE! High Cardinality Ahead!** 67 | > 68 | > Each unique combination of key-value label pairs represents a new time series in Prometheus. Using labels with high cardinality (many different values), such as `image_id` or `created`, can dramatically increase the amount of data stored and impact Prometheus' performance. 69 | > 70 | > For more information, please see the [Prometheus documentation on labels](https://prometheus.io/docs/practices/naming/#labels). 71 | 72 | ## Run with docker 73 | Start docker container with following `docker-compose.yml`: 74 | ```yml 75 | version: '2.1' 76 | services: 77 | dex: 78 | image: spx01/dex 79 | container_name: dex 80 | volumes: 81 | - /var/run/docker.sock:/var/run/docker.sock 82 | ports: 83 | - 8386:8080 84 | restart: always 85 | ``` 86 | 87 | ## Building from Source 88 | 89 | As an alternative to Docker, you can build and run DEX from source: 90 | 91 | ```bash 92 | # Clone the repository 93 | git clone https://github.com/spx01/dex.git 94 | cd dex 95 | 96 | # Build the binary 97 | go build -o dex . 98 | 99 | # Run the exporter 100 | ./dex 101 | ``` 102 | 103 | ## Test with curl 104 | ```bash 105 | $ curl localhost:8386/metrics 106 | ``` 107 | 108 | Example output: 109 | 110 | ``` 111 | # HELP dex_container_running 1 if docker container is running, 0 otherwise 112 | # TYPE dex_container_running gauge 113 | dex_container_running{container_name="dex"} 1 114 | # HELP dex_cpu_utilization_percent CPU utilization in percent 115 | # TYPE dex_cpu_utilization_percent gauge 116 | dex_cpu_utilization_percent{container_name="dex"} 0.036 117 | ... 118 | ``` 119 | 120 | ## Contributing 121 | 122 | Contributions are welcome! Please feel free to submit a pull request. 123 | 124 | 125 | ## Grafana dashboard 126 | 127 | ### Grafana 7 128 | 129 | Example grafana7 dashboard definition [as JSON](grafana7.json) 130 | ![grafana-dashboard](grafana7-dashboard.png). 131 | 132 | ### Grafana 8 133 | 134 | Another dashboard for Grafana 8 (thanks @scMarkus !!!) [as JSON](grafana8.json) 135 | ![grafana-dashboard](grafana8-dashboard.png) 136 | 137 | Modification (thanks @GitSchorsch) with additional job filter [as JSON](grafana8_2.json) 138 | -------------------------------------------------------------------------------- /collector_e2e_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "strings" 7 | "testing" 8 | "time" 9 | 10 | "github.com/docker/docker/client" 11 | "github.com/prometheus/client_golang/prometheus" 12 | "github.com/prometheus/client_golang/prometheus/testutil" 13 | dto "github.com/prometheus/client_model/go" 14 | "github.com/stretchr/testify/assert" 15 | "github.com/stretchr/testify/require" 16 | "github.com/testcontainers/testcontainers-go" 17 | "github.com/testcontainers/testcontainers-go/wait" 18 | ) 19 | 20 | func TestDockerCollector_E2E_BasicMetrics(t *testing.T) { 21 | ctx := context.Background() 22 | 23 | // Setup: Start a simple container 24 | req := testcontainers.ContainerRequest{ 25 | Image: "alpine:latest", 26 | Cmd: []string{"sleep", "10"}, 27 | WaitingFor: wait.ForExec([]string{"true"}).WithStartupTimeout(1 * time.Minute), 28 | } 29 | genericContainer, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ 30 | ContainerRequest: req, 31 | Started: true, 32 | }) 33 | require.NoError(t, err, "Failed to start container") 34 | defer func() { 35 | if termErr := genericContainer.Terminate(ctx); termErr != nil { 36 | t.Logf("Failed to terminate container: %s", termErr.Error()) 37 | } 38 | }() 39 | 40 | containerID := genericContainer.GetContainerID() 41 | require.NotEmpty(t, containerID, "Container ID should not be empty") 42 | 43 | dockerCli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation()) 44 | require.NoError(t, err, "Failed to create Docker client for inspect") 45 | // No defer dockerCli.Close() here, it might interfere with collector.cli 46 | 47 | inspectedContainer, err := dockerCli.ContainerInspect(ctx, containerID) 48 | require.NoError(t, err, "Failed to inspect container") 49 | actualContainerName := strings.TrimPrefix(inspectedContainer.Name, "/") 50 | dockerCli.Close() // Close this auxiliary client now that we have the name 51 | 52 | collector := newDockerCollector(nil) 53 | require.NotNil(t, collector, "Collector should not be nil") 54 | require.NotNil(t, collector.cli, "Collector Docker client should not be nil") 55 | // The main collector.cli will be closed by the main function or when the collector is GC'd if not explicitly closed. 56 | // For robust testing, ensure collector.cli is closed. Let's assume newDockerCollector could be modified 57 | // or we handle its lifecycle if it were a long-lived object. In a test, explicit close is good. 58 | defer collector.cli.Close() 59 | 60 | registry := prometheus.NewRegistry() 61 | err = registry.Register(collector) 62 | require.NoError(t, err, "Failed to register collector") 63 | 64 | // Give a brief moment for the container to be fully listed and stats to start populating if needed 65 | time.Sleep(3 * time.Second) 66 | 67 | metricChan := make(chan prometheus.Metric, 100) // Buffer to avoid blocking, actual count varies 68 | go func() { 69 | collector.Collect(metricChan) 70 | close(metricChan) 71 | }() 72 | 73 | foundRunningMetric := false 74 | foundRestartsMetric := false 75 | // Basic check for any CPU metric to indicate stats are flowing 76 | foundCPUMetricForTestContainer := false 77 | 78 | for m := range metricChan { 79 | pbMetric := &dto.Metric{} 80 | err = m.Write(pbMetric) 81 | require.NoError(t, err, "Error writing metric to protobuf") 82 | 83 | var metricContainerName string 84 | for _, labelPair := range pbMetric.Label { 85 | if labelPair.GetName() == "container_name" { 86 | metricContainerName = labelPair.GetValue() 87 | break 88 | } 89 | } 90 | 91 | if metricContainerName == actualContainerName { 92 | descString := m.Desc().String() // For easy check of metric name 93 | if strings.Contains(descString, "dex_container_running") { 94 | foundRunningMetric = true 95 | require.NotNil(t, pbMetric.Gauge, "Gauge should not be nil for dex_container_running") 96 | assert.Equal(t, 1.0, *pbMetric.Gauge.Value, "Container should be running") 97 | } 98 | if strings.Contains(descString, "dex_container_restarts_total") { 99 | foundRestartsMetric = true 100 | require.NotNil(t, pbMetric.Counter, "Counter should not be nil for dex_container_restarts_total") 101 | assert.Equal(t, 0.0, *pbMetric.Counter.Value, "Container restarts should be 0") 102 | } 103 | if strings.Contains(descString, "dex_cpu_utilization_percent") { // Check one of the stats-based metrics 104 | foundCPUMetricForTestContainer = true 105 | // Value can be anything, just checking it's produced for the running container 106 | require.NotNil(t, pbMetric.Gauge, "Gauge should not be nil for dex_cpu_utilization_percent") 107 | t.Logf("CPU utilization for %s: %f", actualContainerName, *pbMetric.Gauge.Value) 108 | } 109 | } 110 | } 111 | 112 | assert.True(t, foundRunningMetric, "dex_container_running metric not found for the test container: %s", actualContainerName) 113 | assert.True(t, foundRestartsMetric, "dex_container_restarts_total metric not found for the test container: %s", actualContainerName) 114 | assert.True(t, foundCPUMetricForTestContainer, "dex_cpu_utilization_percent metric not found for the test container: %s. Stats might not be available yet or container too short-lived.", actualContainerName) 115 | 116 | // CollectAndLint is a good general check 117 | problems, err := testutil.CollectAndLint(collector) 118 | require.NoError(t, err, "Error during CollectAndLint") 119 | for _, p := range problems { 120 | // Only log problems related to our test container to avoid noise from other system containers 121 | if strings.Contains(p.Metric, actualContainerName) { 122 | t.Logf("Lint problem for %s: %s - %s", actualContainerName, p.Metric, p.Text) 123 | } 124 | } 125 | 126 | // Using the filteredCollector for CollectAndCompare 127 | expectedMetricsText := fmt.Sprintf("# HELP dex_container_running 1 if docker container is running, 0 otherwise\n# TYPE dex_container_running gauge\ndex_container_running{container_name=\"%s\"} 1\n# HELP dex_container_restarts_total Number of times the container has restarted\n# TYPE dex_container_restarts_total counter\ndex_container_restarts_total{container_name=\"%s\"} 0\n", actualContainerName, actualContainerName) 128 | 129 | filteredRegistry := prometheus.NewRegistry() 130 | // Create a new collector instance for the filtered test, as the original collector might have state or issues if reused across registrations. 131 | // However, newDockerCollector() creates a new Docker client each time. For this test, it's fine. 132 | // If newDockerCollector were expensive, we'd pass the original collector.cli to a new filteredCollector wrapper. 133 | // The provided filteredCollector struct takes *DockerCollector, so we re-use the main one for filtering logic. 134 | fc := newFilteredCollector(collector, actualContainerName) 135 | require.NoError(t, filteredRegistry.Register(fc), "Failed to register filtered collector") 136 | 137 | err = testutil.CollectAndCompare(fc, strings.NewReader(expectedMetricsText), "dex_container_running", "dex_container_restarts_total") 138 | if err != nil { 139 | t.Logf("CollectAndCompare for basic metrics failed. This is sometimes sensitive to exact output. Error: %v", err) 140 | // The loop-based assertions are primary for these basic metrics. 141 | } 142 | } 143 | 144 | // newFilteredCollector wraps a DockerCollector and only exposes metrics for a specific container name. 145 | type filteredCollector struct { 146 | innerCollector *DockerCollector 147 | targetContainerName string 148 | // Store the actual Docker client from the inner collector to ensure it's the same one being used. 149 | // This isn't strictly necessary if innerCollector.cli is public and used directly, 150 | // but good practice if we were to re-implement parts of Collect. 151 | } 152 | 153 | func newFilteredCollector(inner *DockerCollector, targetName string) *filteredCollector { 154 | return &filteredCollector{innerCollector: inner, targetContainerName: targetName} 155 | } 156 | 157 | func (fc *filteredCollector) Describe(ch chan<- *prometheus.Desc) { 158 | // For simplicity, we can let the inner collector describe all, or filter descriptions too. 159 | // Filtering descriptions is more complex if they are not dynamic per metric. 160 | // Prometheus recommends describing all possible metrics. 161 | fc.innerCollector.Describe(ch) 162 | } 163 | 164 | func (fc *filteredCollector) Collect(ch chan<- prometheus.Metric) { 165 | innerMetrics := make(chan prometheus.Metric, 100) // Buffer to avoid blocking 166 | go func() { 167 | fc.innerCollector.Collect(innerMetrics) 168 | close(innerMetrics) 169 | }() 170 | 171 | for metric := range innerMetrics { 172 | pbMetric := &dto.Metric{} 173 | err := metric.Write(pbMetric) 174 | if err != nil { 175 | // In a real test, log this error or fail 176 | fmt.Printf("Error writing metric to protobuf in filteredCollector: %v\n", err) 177 | continue 178 | } 179 | var metricMatchesTarget bool 180 | for _, labelPair := range pbMetric.Label { 181 | if labelPair.GetName() == "container_name" && labelPair.GetValue() == fc.targetContainerName { 182 | metricMatchesTarget = true 183 | break 184 | } 185 | } 186 | if metricMatchesTarget { 187 | ch <- metric 188 | } 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /collector.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "strconv" 7 | "strings" 8 | "sync" 9 | 10 | "github.com/docker/docker/api/types" 11 | "github.com/docker/docker/api/types/container" 12 | "github.com/docker/docker/client" 13 | "github.com/prometheus/client_golang/prometheus" 14 | log "github.com/sirupsen/logrus" 15 | ) 16 | 17 | var labelCname = []string{"container_name"} 18 | 19 | type DockerCollector struct { 20 | cli *client.Client 21 | labels []string 22 | } 23 | 24 | func newDockerCollector(labels []string) *DockerCollector { 25 | cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation()) 26 | if err != nil { 27 | log.Fatalf("can't create docker client: %v", err) 28 | } 29 | 30 | return &DockerCollector{ 31 | cli: cli, 32 | labels: labels, 33 | } 34 | } 35 | 36 | func (c *DockerCollector) Describe(_ chan<- *prometheus.Desc) { 37 | 38 | } 39 | 40 | func (c *DockerCollector) Collect(ch chan<- prometheus.Metric) { 41 | containers, err := c.cli.ContainerList(context.Background(), container.ListOptions{ 42 | All: true, 43 | }) 44 | if err != nil { 45 | log.Error("can't list containers: ", err) 46 | return 47 | } 48 | 49 | var wg sync.WaitGroup 50 | 51 | for _, container := range containers { 52 | wg.Add(1) 53 | 54 | go c.processContainer(container, ch, &wg) 55 | } 56 | wg.Wait() 57 | } 58 | 59 | func (c *DockerCollector) getLabelValues(cont types.Container, cName string) []string { 60 | var labelValues []string 61 | labelValues = append(labelValues, cName) 62 | 63 | for _, label := range c.labels { 64 | switch label { 65 | case "image": 66 | labelValues = append(labelValues, cont.Image) 67 | case "image_id": 68 | labelValues = append(labelValues, cont.ID) 69 | case "command": 70 | labelValues = append(labelValues, cont.Command) 71 | case "created": 72 | labelValues = append(labelValues, strconv.FormatInt(cont.Created, 10)) 73 | default: 74 | labelValues = append(labelValues, "") 75 | log.Warnf("label '%s' doesn't exist in container '%s'", label, cName) 76 | } 77 | } 78 | return labelValues 79 | } 80 | 81 | func (c *DockerCollector) processContainer(cont types.Container, ch chan<- prometheus.Metric, wg *sync.WaitGroup) { 82 | defer wg.Done() 83 | 84 | cName := strings.TrimPrefix(strings.Join(cont.Names, ";"), "/") 85 | 86 | labelNames := append(labelCname, c.labels...) 87 | 88 | labelValues := c.getLabelValues(cont, cName) 89 | 90 | var isRunning, isRestarting, isExited float64 91 | 92 | if cont.State == "running" { 93 | isRunning = 1 94 | } 95 | 96 | if cont.State == "restarting" { 97 | isRestarting = 1 98 | } 99 | 100 | if cont.State == "exited" { 101 | isExited = 1 102 | } 103 | 104 | // container state metric for all containers 105 | ch <- prometheus.MustNewConstMetric(prometheus.NewDesc( 106 | "dex_container_running", 107 | "1 if docker container is running, 0 otherwise", 108 | labelNames, 109 | nil, 110 | ), prometheus.GaugeValue, isRunning, labelValues...) 111 | 112 | ch <- prometheus.MustNewConstMetric(prometheus.NewDesc( 113 | "dex_container_restarting", 114 | "1 if docker container is restarting, 0 otherwise", 115 | labelNames, 116 | nil, 117 | ), prometheus.GaugeValue, isRestarting, labelValues...) 118 | 119 | ch <- prometheus.MustNewConstMetric(prometheus.NewDesc( 120 | "dex_container_exited", 121 | "1 if docker container exited, 0 otherwise", 122 | labelNames, 123 | nil, 124 | ), prometheus.GaugeValue, isExited, labelValues...) 125 | 126 | if inspect, err := c.cli.ContainerInspect(context.Background(), cont.ID); err != nil { 127 | log.Fatal(err) 128 | } else { 129 | ch <- prometheus.MustNewConstMetric(prometheus.NewDesc( 130 | "dex_container_restarts_total", 131 | "Number of times the container has restarted", 132 | labelNames, 133 | nil, 134 | ), prometheus.CounterValue, float64(inspect.RestartCount), labelValues...) 135 | } 136 | 137 | // stats metrics only for running containers 138 | if isRunning == 1 { 139 | 140 | if stats, err := c.cli.ContainerStats(context.Background(), cont.ID, false); err != nil { 141 | log.Fatal(err) 142 | } else { 143 | var containerStats container.StatsResponse 144 | err := json.NewDecoder(stats.Body).Decode(&containerStats) 145 | if err != nil { 146 | log.Error("can't read api stats: ", err) 147 | } 148 | if err := stats.Body.Close(); err != nil { 149 | log.Error("can't close body: ", err) 150 | } 151 | 152 | c.blockIoMetrics(ch, &containerStats, labelNames, labelValues) 153 | 154 | c.memoryMetrics(ch, &containerStats, labelNames, labelValues) 155 | 156 | c.networkMetrics(ch, &containerStats, labelNames, labelValues) 157 | 158 | c.CPUMetrics(ch, &containerStats, labelNames, labelValues) 159 | 160 | c.pidsMetrics(ch, &containerStats, labelNames, labelValues) 161 | } 162 | } 163 | } 164 | 165 | func (c *DockerCollector) CPUMetrics(ch chan<- prometheus.Metric, containerStats *container.StatsResponse, labelNames []string, labelValues []string) { 166 | totalUsage := containerStats.CPUStats.CPUUsage.TotalUsage 167 | cpuDelta := totalUsage - containerStats.PreCPUStats.CPUUsage.TotalUsage 168 | sysemDelta := containerStats.CPUStats.SystemUsage - containerStats.PreCPUStats.SystemUsage 169 | onlineCPUs := containerStats.CPUStats.OnlineCPUs 170 | 171 | cpuUtilization := (float64(cpuDelta) / float64(sysemDelta)) * float64(onlineCPUs) * 100.0 172 | 173 | ch <- prometheus.MustNewConstMetric(prometheus.NewDesc( 174 | "dex_cpu_utilization_percent", 175 | "CPU utilization in percent", 176 | labelNames, 177 | nil, 178 | ), prometheus.GaugeValue, cpuUtilization, labelValues...) 179 | 180 | ch <- prometheus.MustNewConstMetric(prometheus.NewDesc( 181 | "dex_cpu_utilization_seconds_total", 182 | "Cumulative CPU utilization in seconds", 183 | labelNames, 184 | nil, 185 | ), prometheus.CounterValue, float64(totalUsage)/1e9, labelValues...) 186 | } 187 | 188 | func (c *DockerCollector) networkMetrics(ch chan<- prometheus.Metric, containerStats *container.StatsResponse, labelNames []string, labelValues []string) { 189 | ch <- prometheus.MustNewConstMetric(prometheus.NewDesc( 190 | "dex_network_rx_bytes_total", 191 | "Network received bytes total", 192 | labelNames, 193 | nil, 194 | ), prometheus.CounterValue, float64(containerStats.Networks["eth0"].RxBytes), labelValues...) 195 | ch <- prometheus.MustNewConstMetric(prometheus.NewDesc( 196 | "dex_network_tx_bytes_total", 197 | "Network sent bytes total", 198 | labelNames, 199 | nil, 200 | ), prometheus.CounterValue, float64(containerStats.Networks["eth0"].TxBytes), labelValues...) 201 | } 202 | 203 | func (c *DockerCollector) memoryMetrics(ch chan<- prometheus.Metric, containerStats *container.StatsResponse, labelNames []string, labelValues []string) { 204 | // From official documentation 205 | //Note: On Linux, the Docker CLI reports memory usage by subtracting page cache usage from the total memory usage. 206 | //The API does not perform such a calculation but rather provides the total memory usage and the amount from the page cache so that clients can use the data as needed. 207 | //On cgroup v1 hosts, the cache usage is defined as the value of total_inactive_file field in the memory.stat file. 208 | //On cgroup v2 hosts, the cache usage is defined as the value of inactive_file field. 209 | memoryUsage := containerStats.MemoryStats.Usage - getCacheMemory(containerStats.MemoryStats.Stats) 210 | memoryTotal := containerStats.MemoryStats.Limit 211 | 212 | memoryUtilization := float64(memoryUsage) / float64(memoryTotal) * 100.0 213 | ch <- prometheus.MustNewConstMetric(prometheus.NewDesc( 214 | "dex_memory_usage_bytes", 215 | "Total memory usage bytes", 216 | labelNames, 217 | nil, 218 | ), prometheus.CounterValue, float64(memoryUsage), labelValues...) 219 | ch <- prometheus.MustNewConstMetric(prometheus.NewDesc( 220 | "dex_memory_total_bytes", 221 | "Total memory bytes", 222 | labelNames, 223 | nil, 224 | ), prometheus.GaugeValue, float64(memoryTotal), labelValues...) 225 | ch <- prometheus.MustNewConstMetric(prometheus.NewDesc( 226 | "dex_memory_utilization_percent", 227 | "Memory utilization percent", 228 | labelNames, 229 | nil, 230 | ), prometheus.GaugeValue, memoryUtilization, labelValues...) 231 | } 232 | 233 | func (c *DockerCollector) blockIoMetrics(ch chan<- prometheus.Metric, containerStats *container.StatsResponse, labelNames []string, labelValues []string) { 234 | var readTotal, writeTotal uint64 235 | for _, b := range containerStats.BlkioStats.IoServiceBytesRecursive { 236 | if strings.EqualFold(b.Op, "read") { 237 | readTotal += b.Value 238 | } 239 | if strings.EqualFold(b.Op, "write") { 240 | writeTotal += b.Value 241 | } 242 | } 243 | 244 | ch <- prometheus.MustNewConstMetric(prometheus.NewDesc( 245 | "dex_block_io_read_bytes_total", 246 | "Block I/O read bytes", 247 | labelNames, 248 | nil, 249 | ), prometheus.CounterValue, float64(readTotal), labelValues...) 250 | 251 | ch <- prometheus.MustNewConstMetric(prometheus.NewDesc( 252 | "dex_block_io_write_bytes_total", 253 | "Block I/O write bytes", 254 | labelNames, 255 | nil, 256 | ), prometheus.CounterValue, float64(writeTotal), labelValues...) 257 | } 258 | 259 | func (c *DockerCollector) pidsMetrics(ch chan<- prometheus.Metric, containerStats *container.StatsResponse, labelNames []string, labelValues []string) { 260 | ch <- prometheus.MustNewConstMetric(prometheus.NewDesc( 261 | "dex_pids_current", 262 | "Current number of pids in the cgroup", 263 | labelNames, 264 | nil, 265 | ), prometheus.CounterValue, float64(containerStats.PidsStats.Current), labelValues...) 266 | } 267 | 268 | func getCacheMemory(stats map[string]uint64) uint64 { 269 | // On cgroup v2 hosts, the cache usage is defined as the value of inactive_file field. 270 | if val, ok := stats["inactive_file"]; ok { 271 | return val 272 | } 273 | // On cgroup v1 hosts, the cache usage is defined as the value of total_inactive_file field. 274 | if val, ok := stats["total_inactive_file"]; ok { 275 | return val 276 | } 277 | // Fallback for older versions 278 | return stats["cache"] 279 | } 280 | -------------------------------------------------------------------------------- /collector_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | 7 | "github.com/docker/docker/api/types" 8 | "github.com/docker/docker/api/types/container" 9 | "github.com/prometheus/client_golang/prometheus" 10 | dto "github.com/prometheus/client_model/go" 11 | "github.com/stretchr/testify/assert" 12 | "github.com/stretchr/testify/require" 13 | ) 14 | 15 | func TestGetLabelValues(t *testing.T) { 16 | collector := newDockerCollector([]string{"image", "command", "nonexistent"}) 17 | 18 | container := types.Container{ 19 | ID: "test-id", 20 | Names: []string{"/test-container"}, 21 | Image: "test-image", 22 | Command: "test-command", 23 | State: "running", 24 | } 25 | 26 | labelValues := collector.getLabelValues(container, "test-container") 27 | 28 | assert.Equal(t, []string{"test-container", "test-image", "test-command", ""}, labelValues) 29 | } 30 | 31 | func TestCPUMetrics(t *testing.T) { 32 | c := &DockerCollector{} // We don't need a real client for this test 33 | containerName := "test-container" 34 | 35 | stats := &container.StatsResponse{ 36 | CPUStats: container.CPUStats{ 37 | OnlineCPUs: 1, 38 | CPUUsage: container.CPUUsage{ 39 | TotalUsage: 1000000000, // 1 second in nanoseconds 40 | }, 41 | SystemUsage: 60000000000, // Example system usage 42 | }, 43 | PreCPUStats: container.CPUStats{ 44 | CPUUsage: container.CPUUsage{ 45 | TotalUsage: 500000000, // 0.5 seconds in nanoseconds 46 | }, 47 | SystemUsage: 50000000000, // Example previous system usage 48 | }, 49 | } 50 | 51 | ch := make(chan prometheus.Metric, 2) // Expecting 2 metrics 52 | 53 | c.CPUMetrics(ch, stats, []string{"container_name"}, []string{containerName}) 54 | close(ch) 55 | 56 | var metrics []prometheus.Metric 57 | for metric := range ch { 58 | metrics = append(metrics, metric) 59 | } 60 | 61 | assert.Len(t, metrics, 2, "Expected 2 CPU metrics") 62 | 63 | expectedUtilizationPercent := 5.0 64 | expectedUtilizationSecondsTotal := 1.0 65 | 66 | foundUtilizationPercent := false 67 | foundUtilizationSecondsTotal := false 68 | 69 | for _, m := range metrics { 70 | desc := m.Desc().String() 71 | pbMetric := &dto.Metric{} 72 | err := m.Write(pbMetric) 73 | require.NoError(t, err, "Failed to write metric to protobuf") 74 | 75 | if strings.Contains(desc, "dex_cpu_utilization_percent") { 76 | foundUtilizationPercent = true 77 | require.NotNil(t, pbMetric.Gauge, "Gauge should not be nil for dex_cpu_utilization_percent") 78 | val := *pbMetric.Gauge.Value 79 | assert.InDelta(t, expectedUtilizationPercent, val, 0.001, "Unexpected dex_cpu_utilization_percent value") 80 | } 81 | if strings.Contains(desc, "dex_cpu_utilization_seconds_total") { 82 | foundUtilizationSecondsTotal = true 83 | require.NotNil(t, pbMetric.Counter, "Counter should not be nil for dex_cpu_utilization_seconds_total") 84 | val := *pbMetric.Counter.Value 85 | assert.InDelta(t, expectedUtilizationSecondsTotal, val, 0.001, "Unexpected dex_cpu_utilization_seconds_total value") 86 | } 87 | } 88 | 89 | assert.True(t, foundUtilizationPercent, "Metric dex_cpu_utilization_percent not found") 90 | assert.True(t, foundUtilizationSecondsTotal, "Metric dex_cpu_utilization_seconds_total not found") 91 | } 92 | 93 | func TestNetworkMetrics(t *testing.T) { 94 | c := &DockerCollector{} 95 | containerName := "test-network-container" 96 | 97 | stats := &container.StatsResponse{ 98 | Networks: map[string]container.NetworkStats{ 99 | "eth0": { 100 | RxBytes: 1024, 101 | TxBytes: 2048, 102 | }, 103 | }, 104 | } 105 | 106 | ch := make(chan prometheus.Metric, 2) 107 | c.networkMetrics(ch, stats, []string{"container_name"}, []string{containerName}) 108 | close(ch) 109 | 110 | var metrics []prometheus.Metric 111 | for metric := range ch { 112 | metrics = append(metrics, metric) 113 | } 114 | 115 | assert.Len(t, metrics, 2, "Expected 2 network metrics") 116 | 117 | expectedRxBytes := 1024.0 118 | expectedTxBytes := 2048.0 119 | 120 | foundRxBytes := false 121 | foundTxBytes := false 122 | 123 | for _, m := range metrics { 124 | desc := m.Desc().String() 125 | pbMetric := &dto.Metric{} 126 | err := m.Write(pbMetric) 127 | require.NoError(t, err, "Failed to write metric to protobuf") 128 | 129 | if strings.Contains(desc, "dex_network_rx_bytes_total") { 130 | foundRxBytes = true 131 | require.NotNil(t, pbMetric.Counter, "Counter should not be nil for dex_network_rx_bytes_total") 132 | val := *pbMetric.Counter.Value 133 | assert.Equal(t, expectedRxBytes, val, "Unexpected dex_network_rx_bytes_total value") 134 | } 135 | if strings.Contains(desc, "dex_network_tx_bytes_total") { 136 | foundTxBytes = true 137 | require.NotNil(t, pbMetric.Counter, "Counter should not be nil for dex_network_tx_bytes_total") 138 | val := *pbMetric.Counter.Value 139 | assert.Equal(t, expectedTxBytes, val, "Unexpected dex_network_tx_bytes_total value") 140 | } 141 | } 142 | 143 | assert.True(t, foundRxBytes, "Metric dex_network_rx_bytes_total not found") 144 | assert.True(t, foundTxBytes, "Metric dex_network_tx_bytes_total not found") 145 | } 146 | 147 | func TestMemoryMetrics(t *testing.T) { 148 | c := &DockerCollector{} 149 | containerName := "test-memory-container" 150 | 151 | stats := &container.StatsResponse{ 152 | MemoryStats: container.MemoryStats{ 153 | Usage: 800 * 1024 * 1024, // 800 MiB 154 | Limit: 1024 * 1024 * 1024, // 1 GiB 155 | Stats: map[string]uint64{ 156 | "cache": 200 * 1024 * 1024, // 200 MiB 157 | }, 158 | }, 159 | } 160 | 161 | ch := make(chan prometheus.Metric, 3) 162 | c.memoryMetrics(ch, stats, []string{"container_name"}, []string{containerName}) 163 | close(ch) 164 | 165 | var metrics []prometheus.Metric 166 | for metric := range ch { 167 | metrics = append(metrics, metric) 168 | } 169 | 170 | assert.Len(t, metrics, 3, "Expected 3 memory metrics") 171 | 172 | expectedMemoryUsageBytes := float64(600 * 1024 * 1024) 173 | expectedMemoryTotalBytes := float64(1024 * 1024 * 1024) 174 | expectedMemoryUtilizationPercent := (expectedMemoryUsageBytes / expectedMemoryTotalBytes) * 100.0 175 | 176 | foundUsageBytes := false 177 | foundTotalBytes := false 178 | foundUtilizationPercent := false 179 | 180 | for _, m := range metrics { 181 | desc := m.Desc().String() 182 | pbMetric := &dto.Metric{} 183 | err := m.Write(pbMetric) 184 | require.NoError(t, err, "Failed to write metric to protobuf") 185 | 186 | if strings.Contains(desc, "dex_memory_usage_bytes") { 187 | foundUsageBytes = true 188 | require.NotNil(t, pbMetric.Counter, "Counter should not be nil for dex_memory_usage_bytes") 189 | val := *pbMetric.Counter.Value 190 | assert.Equal(t, expectedMemoryUsageBytes, val, "Unexpected dex_memory_usage_bytes value") 191 | } 192 | if strings.Contains(desc, "dex_memory_total_bytes") { 193 | foundTotalBytes = true 194 | require.NotNil(t, pbMetric.Gauge, "Gauge should not be nil for dex_memory_total_bytes") 195 | val := *pbMetric.Gauge.Value 196 | assert.Equal(t, expectedMemoryTotalBytes, val, "Unexpected dex_memory_total_bytes value") 197 | } 198 | if strings.Contains(desc, "dex_memory_utilization_percent") { 199 | foundUtilizationPercent = true 200 | require.NotNil(t, pbMetric.Gauge, "Gauge should not be nil for dex_memory_utilization_percent") 201 | val := *pbMetric.Gauge.Value 202 | assert.InDelta(t, expectedMemoryUtilizationPercent, val, 0.001, "Unexpected dex_memory_utilization_percent value") 203 | } 204 | } 205 | 206 | assert.True(t, foundUsageBytes, "Metric dex_memory_usage_bytes not found") 207 | assert.True(t, foundTotalBytes, "Metric dex_memory_total_bytes not found") 208 | assert.True(t, foundUtilizationPercent, "Metric dex_memory_utilization_percent not found") 209 | } 210 | 211 | func TestBlockIoMetrics(t *testing.T) { 212 | c := &DockerCollector{} 213 | containerName := "test-blockio-container" 214 | 215 | stats := &container.StatsResponse{ 216 | BlkioStats: container.BlkioStats{ 217 | IoServiceBytesRecursive: []container.BlkioStatEntry{ 218 | {Op: "Read", Value: 1000}, 219 | {Op: "Write", Value: 2000}, 220 | {Op: "Read", Value: 500}, 221 | {Op: "Write", Value: 1000}, 222 | {Op: "Total", Value: 4500}, // Should be ignored by current logic 223 | }, 224 | }, 225 | } 226 | 227 | ch := make(chan prometheus.Metric, 2) 228 | c.blockIoMetrics(ch, stats, []string{"container_name"}, []string{containerName}) 229 | close(ch) 230 | 231 | var metrics []prometheus.Metric 232 | for metric := range ch { 233 | metrics = append(metrics, metric) 234 | } 235 | 236 | assert.Len(t, metrics, 2, "Expected 2 block I/O metrics") 237 | 238 | expectedReadBytes := 1500.0 239 | expectedWriteBytes := 3000.0 240 | 241 | foundReadBytes := false 242 | foundWriteBytes := false 243 | 244 | for _, m := range metrics { 245 | desc := m.Desc().String() 246 | pbMetric := &dto.Metric{} 247 | err := m.Write(pbMetric) 248 | require.NoError(t, err, "Failed to write metric to protobuf") 249 | 250 | if strings.Contains(desc, "dex_block_io_read_bytes_total") { 251 | foundReadBytes = true 252 | require.NotNil(t, pbMetric.Counter, "Counter should not be nil for dex_block_io_read_bytes_total") 253 | val := *pbMetric.Counter.Value 254 | assert.Equal(t, expectedReadBytes, val, "Unexpected dex_block_io_read_bytes_total value") 255 | } 256 | if strings.Contains(desc, "dex_block_io_write_bytes_total") { 257 | foundWriteBytes = true 258 | require.NotNil(t, pbMetric.Counter, "Counter should not be nil for dex_block_io_write_bytes_total") 259 | val := *pbMetric.Counter.Value 260 | assert.Equal(t, expectedWriteBytes, val, "Unexpected dex_block_io_write_bytes_total value") 261 | } 262 | } 263 | 264 | assert.True(t, foundReadBytes, "Metric dex_block_io_read_bytes_total not found") 265 | assert.True(t, foundWriteBytes, "Metric dex_block_io_write_bytes_total not found") 266 | } 267 | 268 | func TestPidsMetrics(t *testing.T) { 269 | c := &DockerCollector{} 270 | containerName := "test-pids-container" 271 | 272 | stats := &container.StatsResponse{ 273 | PidsStats: container.PidsStats{ 274 | Current: 42, 275 | }, 276 | } 277 | 278 | ch := make(chan prometheus.Metric, 1) // Expecting 1 metric 279 | c.pidsMetrics(ch, stats, []string{"container_name"}, []string{containerName}) 280 | close(ch) 281 | 282 | var metrics []prometheus.Metric 283 | for metric := range ch { 284 | metrics = append(metrics, metric) 285 | } 286 | 287 | assert.Len(t, metrics, 1, "Expected 1 PID metric") 288 | 289 | expectedPidsCurrent := 42.0 290 | foundPidsCurrent := false 291 | 292 | for _, m := range metrics { 293 | desc := m.Desc().String() 294 | pbMetric := &dto.Metric{} 295 | err := m.Write(pbMetric) 296 | require.NoError(t, err, "Failed to write metric to protobuf") 297 | 298 | if strings.Contains(desc, "dex_pids_current") { 299 | foundPidsCurrent = true 300 | require.NotNil(t, pbMetric.Counter, "Counter should not be nil for dex_pids_current") 301 | val := *pbMetric.Counter.Value 302 | assert.Equal(t, expectedPidsCurrent, val, "Unexpected dex_pids_current value") 303 | } 304 | } 305 | 306 | assert.True(t, foundPidsCurrent, "Metric dex_pids_current not found") 307 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8= 2 | dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA= 3 | github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk= 4 | github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= 5 | github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= 6 | github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= 7 | github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= 8 | github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= 9 | github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= 10 | github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= 11 | github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= 12 | github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= 13 | github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= 14 | github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 15 | github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= 16 | github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= 17 | github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE= 18 | github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk= 19 | github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= 20 | github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= 21 | github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A= 22 | github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw= 23 | github.com/cpuguy83/dockercfg v0.3.2 h1:DlJTyZGBDlXqUZ2Dk2Q3xHs/FtnooJJVaad2S9GKorA= 24 | github.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= 25 | github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= 26 | github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= 27 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 28 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 29 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 30 | github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= 31 | github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= 32 | github.com/docker/docker v28.5.2+incompatible h1:DBX0Y0zAjZbSrm1uzOkdr1onVghKaftjlSWt4AFexzM= 33 | github.com/docker/docker v28.5.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= 34 | github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94= 35 | github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE= 36 | github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= 37 | github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= 38 | github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw= 39 | github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= 40 | github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= 41 | github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= 42 | github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= 43 | github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= 44 | github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= 45 | github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= 46 | github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= 47 | github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= 48 | github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= 49 | github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 50 | github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= 51 | github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= 52 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 53 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 54 | github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 h1:asbCHRVmodnJTuQ3qamDwqVOIjwqUPTYmYuemVOx+Ys= 55 | github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0/go.mod h1:ggCgvZ2r7uOoQjOyu2Y1NhHmEPPzzuhWgcza5M1Ji1I= 56 | github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= 57 | github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= 58 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 59 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 60 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 61 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 62 | github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= 63 | github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= 64 | github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= 65 | github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= 66 | github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE= 67 | github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= 68 | github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= 69 | github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= 70 | github.com/moby/go-archive v0.1.0 h1:Kk/5rdW/g+H8NHdJW2gsXyZ7UnzvJNOy6VKJqueWdcQ= 71 | github.com/moby/go-archive v0.1.0/go.mod h1:G9B+YoujNohJmrIYFBpSd54GTUB4lt9S+xVQvsJyFuo= 72 | github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk= 73 | github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= 74 | github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw= 75 | github.com/moby/sys/atomicwriter v0.1.0/go.mod h1:Ul8oqv2ZMNHOceF643P6FKPXeCmYtlQMvpizfsSoaWs= 76 | github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU= 77 | github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko= 78 | github.com/moby/sys/user v0.4.0 h1:jhcMKit7SA80hivmFJcbB1vqmw//wU61Zdui2eQXuMs= 79 | github.com/moby/sys/user v0.4.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs= 80 | github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g= 81 | github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28= 82 | github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= 83 | github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= 84 | github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= 85 | github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= 86 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= 87 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= 88 | github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= 89 | github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= 90 | github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= 91 | github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= 92 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 93 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 94 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 95 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 96 | github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= 97 | github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= 98 | github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= 99 | github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= 100 | github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= 101 | github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= 102 | github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs= 103 | github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA= 104 | github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= 105 | github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= 106 | github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= 107 | github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= 108 | github.com/shirou/gopsutil/v4 v4.25.6 h1:kLysI2JsKorfaFPcYmcJqbzROzsBWEOAtw6A7dIfqXs= 109 | github.com/shirou/gopsutil/v4 v4.25.6/go.mod h1:PfybzyydfZcN+JMMjkF6Zb8Mq1A/VcogFFg7hj50W9c= 110 | github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= 111 | github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= 112 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 113 | github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= 114 | github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= 115 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 116 | github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= 117 | github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= 118 | github.com/testcontainers/testcontainers-go v0.40.0 h1:pSdJYLOVgLE8YdUY2FHQ1Fxu+aMnb6JfVz1mxk7OeMU= 119 | github.com/testcontainers/testcontainers-go v0.40.0/go.mod h1:FSXV5KQtX2HAMlm7U3APNyLkkap35zNLxukw9oBi/MY= 120 | github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= 121 | github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= 122 | github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= 123 | github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= 124 | github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= 125 | github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= 126 | go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= 127 | go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= 128 | go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 h1:TT4fX+nBOA/+LUkobKGW1ydGcn+G3vRw9+g5HwCphpk= 129 | go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0/go.mod h1:L7UH0GbB0p47T4Rri3uHjbpCFYrVrwc1I25QhNPiGK8= 130 | go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= 131 | go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= 132 | go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.29.0 h1:dIIDULZJpgdiHz5tXrTgKIMLkus6jEFa7x5SOKcyR7E= 133 | go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.29.0/go.mod h1:jlRVBe7+Z1wyxFSUs48L6OBQZ5JwH2Hg/Vbl+t9rAgI= 134 | go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.29.0 h1:JAv0Jwtl01UFiyWZEMiJZBiTlv5A50zNs8lsthXqIio= 135 | go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.29.0/go.mod h1:QNKLmUEAq2QUbPQUfvw4fmv0bgbK7UlOSFCnXyfvSNc= 136 | go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M= 137 | go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= 138 | go.opentelemetry.io/otel/sdk v1.29.0 h1:vkqKjk7gwhS8VaWb0POZKmIEDimRCMsopNYnriHyryo= 139 | go.opentelemetry.io/otel/sdk v1.29.0/go.mod h1:pM8Dx5WKnvxLCb+8lG1PRNIDxu9g9b9g59Qr7hfAAok= 140 | go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= 141 | go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= 142 | go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= 143 | go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= 144 | go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= 145 | go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= 146 | go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= 147 | go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= 148 | golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= 149 | golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= 150 | golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= 151 | golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= 152 | golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 153 | golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 154 | golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 155 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 156 | golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 157 | golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 158 | golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= 159 | golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= 160 | golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU= 161 | golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254= 162 | golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= 163 | golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= 164 | golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U= 165 | golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= 166 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 167 | google.golang.org/genproto/googleapis/api v0.0.0-20240826202546-f6391c0de4c7 h1:YcyjlL1PRr2Q17/I0dPk2JmYS5CDXfcdb2Z3YRioEbw= 168 | google.golang.org/genproto/googleapis/api v0.0.0-20240826202546-f6391c0de4c7/go.mod h1:OCdP9MfskevB/rbYvHTsXTtKC+3bHWajPdoKgjcYkfo= 169 | google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ= 170 | google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= 171 | google.golang.org/grpc v1.67.0 h1:IdH9y6PF5MPSdAntIcpjQ+tXO41pcQsfZV2RxtQgVcw= 172 | google.golang.org/grpc v1.67.0/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= 173 | google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc= 174 | google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= 175 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 176 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 177 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 178 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 179 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 180 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 181 | gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q= 182 | gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA= 183 | -------------------------------------------------------------------------------- /docs/grafana8.json: -------------------------------------------------------------------------------- 1 | { 2 | "annotations": { 3 | "list": [ 4 | { 5 | "builtIn": 1, 6 | "datasource": "-- Grafana --", 7 | "enable": true, 8 | "hide": true, 9 | "iconColor": "rgba(0, 211, 255, 1)", 10 | "limit": 100, 11 | "name": "Annotations & Alerts", 12 | "type": "dashboard" 13 | } 14 | ] 15 | }, 16 | "description": "0xERR0R / dex metrics exporter Dashboard", 17 | "editable": true, 18 | "gnetId": null, 19 | "graphTooltip": 0, 20 | "id": 16, 21 | "iteration": 1627457996017, 22 | "links": [], 23 | "panels": [ 24 | { 25 | "datasource": null, 26 | "fieldConfig": { 27 | "defaults": { 28 | "color": { 29 | "mode": "thresholds" 30 | }, 31 | "custom": { 32 | "fillOpacity": 70, 33 | "lineWidth": 0 34 | }, 35 | "mappings": [ 36 | { 37 | "options": { 38 | "0": { 39 | "color": "red", 40 | "index": 1, 41 | "text": "DOWN" 42 | }, 43 | "1": { 44 | "color": "green", 45 | "index": 0, 46 | "text": "OK" 47 | } 48 | }, 49 | "type": "value" 50 | }, 51 | { 52 | "options": { 53 | "from": 2, 54 | "result": { 55 | "color": "blue", 56 | "index": 2, 57 | "text": "MULTY" 58 | }, 59 | "to": 999 60 | }, 61 | "type": "range" 62 | } 63 | ], 64 | "thresholds": { 65 | "mode": "absolute", 66 | "steps": [ 67 | { 68 | "color": "green", 69 | "value": null 70 | } 71 | ] 72 | }, 73 | "unit": "short" 74 | }, 75 | "overrides": [] 76 | }, 77 | "gridPos": { 78 | "h": 9, 79 | "w": 24, 80 | "x": 0, 81 | "y": 0 82 | }, 83 | "id": 5, 84 | "interval": "10m", 85 | "options": { 86 | "alignValue": "center", 87 | "legend": { 88 | "displayMode": "list", 89 | "placement": "bottom" 90 | }, 91 | "mergeValues": true, 92 | "rowHeight": 0.9, 93 | "showValue": "auto", 94 | "tooltip": { 95 | "mode": "single" 96 | } 97 | }, 98 | "targets": [ 99 | { 100 | "exemplar": true, 101 | "expr": "count(dex_container_running{container_name=~\"$container\"}) by (container_name)", 102 | "interval": "", 103 | "legendFormat": " {{container_name}}", 104 | "refId": "A" 105 | } 106 | ], 107 | "title": "Container alive", 108 | "type": "state-timeline" 109 | }, 110 | { 111 | "datasource": null, 112 | "description": "", 113 | "fieldConfig": { 114 | "defaults": { 115 | "color": { 116 | "mode": "palette-classic" 117 | }, 118 | "custom": { 119 | "hideFrom": { 120 | "legend": false, 121 | "tooltip": false, 122 | "viz": false 123 | } 124 | }, 125 | "decimals": 2, 126 | "mappings": [], 127 | "unit": "short" 128 | }, 129 | "overrides": [] 130 | }, 131 | "gridPos": { 132 | "h": 8, 133 | "w": 8, 134 | "x": 0, 135 | "y": 9 136 | }, 137 | "id": 10, 138 | "options": { 139 | "legend": { 140 | "displayMode": "table", 141 | "placement": "right", 142 | "values": [ 143 | "percent", 144 | "value" 145 | ] 146 | }, 147 | "pieType": "pie", 148 | "reduceOptions": { 149 | "calcs": [ 150 | "lastNotNull" 151 | ], 152 | "fields": "", 153 | "values": false 154 | }, 155 | "tooltip": { 156 | "mode": "single" 157 | } 158 | }, 159 | "targets": [ 160 | { 161 | "exemplar": true, 162 | "expr": "avg_over_time(dex_cpu_utilization_percent{container_name=~\"$container\"}[$range])", 163 | "instant": true, 164 | "interval": "", 165 | "legendFormat": "{{container_name}}", 166 | "refId": "A" 167 | } 168 | ], 169 | "title": "Last $range CPU Usage", 170 | "type": "piechart" 171 | }, 172 | { 173 | "datasource": null, 174 | "fieldConfig": { 175 | "defaults": { 176 | "color": { 177 | "fixedColor": "blue", 178 | "mode": "thresholds" 179 | }, 180 | "mappings": [], 181 | "thresholds": { 182 | "mode": "absolute", 183 | "steps": [ 184 | { 185 | "color": "green", 186 | "value": null 187 | } 188 | ] 189 | } 190 | }, 191 | "overrides": [] 192 | }, 193 | "gridPos": { 194 | "h": 8, 195 | "w": 9, 196 | "x": 8, 197 | "y": 9 198 | }, 199 | "id": 13, 200 | "options": { 201 | "colorMode": "value", 202 | "graphMode": "area", 203 | "justifyMode": "auto", 204 | "orientation": "auto", 205 | "reduceOptions": { 206 | "calcs": [ 207 | "lastNotNull" 208 | ], 209 | "fields": "", 210 | "values": false 211 | }, 212 | "text": {}, 213 | "textMode": "auto" 214 | }, 215 | "pluginVersion": "8.0.3", 216 | "targets": [ 217 | { 218 | "exemplar": true, 219 | "expr": "count(dex_container_running{container_name=~\"$container\"})", 220 | "interval": "", 221 | "legendFormat": "", 222 | "refId": "A" 223 | } 224 | ], 225 | "title": "Running Containers", 226 | "type": "stat" 227 | }, 228 | { 229 | "datasource": null, 230 | "description": "", 231 | "fieldConfig": { 232 | "defaults": { 233 | "color": { 234 | "mode": "palette-classic" 235 | }, 236 | "custom": { 237 | "hideFrom": { 238 | "legend": false, 239 | "tooltip": false, 240 | "viz": false 241 | } 242 | }, 243 | "decimals": 2, 244 | "mappings": [], 245 | "unit": "decbytes" 246 | }, 247 | "overrides": [] 248 | }, 249 | "gridPos": { 250 | "h": 8, 251 | "w": 7, 252 | "x": 17, 253 | "y": 9 254 | }, 255 | "id": 11, 256 | "options": { 257 | "legend": { 258 | "displayMode": "table", 259 | "placement": "right", 260 | "values": [ 261 | "percent", 262 | "value" 263 | ] 264 | }, 265 | "pieType": "pie", 266 | "reduceOptions": { 267 | "calcs": [ 268 | "lastNotNull" 269 | ], 270 | "fields": "", 271 | "values": false 272 | }, 273 | "tooltip": { 274 | "mode": "single" 275 | } 276 | }, 277 | "targets": [ 278 | { 279 | "exemplar": true, 280 | "expr": "avg_over_time(dex_memory_usage_bytes{container_name=~\"$container\"}[$range])", 281 | "instant": true, 282 | "interval": "", 283 | "legendFormat": "{{container_name}}", 284 | "refId": "A" 285 | } 286 | ], 287 | "title": "Last $range Memory Usage", 288 | "type": "piechart" 289 | }, 290 | { 291 | "datasource": null, 292 | "fieldConfig": { 293 | "defaults": { 294 | "color": { 295 | "mode": "palette-classic" 296 | }, 297 | "custom": { 298 | "axisLabel": "", 299 | "axisPlacement": "auto", 300 | "barAlignment": 0, 301 | "drawStyle": "line", 302 | "fillOpacity": 0, 303 | "gradientMode": "none", 304 | "hideFrom": { 305 | "legend": false, 306 | "tooltip": false, 307 | "viz": false 308 | }, 309 | "lineInterpolation": "linear", 310 | "lineWidth": 1, 311 | "pointSize": 5, 312 | "scaleDistribution": { 313 | "type": "linear" 314 | }, 315 | "showPoints": "auto", 316 | "spanNulls": 3600000, 317 | "stacking": { 318 | "group": "A", 319 | "mode": "none" 320 | }, 321 | "thresholdsStyle": { 322 | "mode": "off" 323 | } 324 | }, 325 | "decimals": 1, 326 | "mappings": [], 327 | "thresholds": { 328 | "mode": "absolute", 329 | "steps": [ 330 | { 331 | "color": "green", 332 | "value": null 333 | } 334 | ] 335 | }, 336 | "unit": "percent" 337 | }, 338 | "overrides": [] 339 | }, 340 | "gridPos": { 341 | "h": 8, 342 | "w": 12, 343 | "x": 0, 344 | "y": 17 345 | }, 346 | "id": 6, 347 | "options": { 348 | "legend": { 349 | "calcs": [ 350 | "max", 351 | "mean" 352 | ], 353 | "displayMode": "table", 354 | "placement": "right" 355 | }, 356 | "tooltip": { 357 | "mode": "multi" 358 | } 359 | }, 360 | "targets": [ 361 | { 362 | "exemplar": true, 363 | "expr": "avg_over_time(dex_cpu_utilization_percent{container_name=~\"$container\"}[$range])", 364 | "interval": "", 365 | "legendFormat": "{{container_name}}", 366 | "refId": "A" 367 | } 368 | ], 369 | "title": "CPU usage Percent", 370 | "type": "timeseries" 371 | }, 372 | { 373 | "datasource": null, 374 | "fieldConfig": { 375 | "defaults": { 376 | "color": { 377 | "mode": "palette-classic" 378 | }, 379 | "custom": { 380 | "axisLabel": "", 381 | "axisPlacement": "auto", 382 | "barAlignment": 0, 383 | "drawStyle": "line", 384 | "fillOpacity": 0, 385 | "gradientMode": "none", 386 | "hideFrom": { 387 | "legend": false, 388 | "tooltip": false, 389 | "viz": false 390 | }, 391 | "lineInterpolation": "linear", 392 | "lineWidth": 1, 393 | "pointSize": 5, 394 | "scaleDistribution": { 395 | "type": "linear" 396 | }, 397 | "showPoints": "auto", 398 | "spanNulls": 3600000, 399 | "stacking": { 400 | "group": "A", 401 | "mode": "none" 402 | }, 403 | "thresholdsStyle": { 404 | "mode": "off" 405 | } 406 | }, 407 | "decimals": 1, 408 | "mappings": [], 409 | "thresholds": { 410 | "mode": "absolute", 411 | "steps": [ 412 | { 413 | "color": "green", 414 | "value": null 415 | } 416 | ] 417 | }, 418 | "unit": "decbytes" 419 | }, 420 | "overrides": [] 421 | }, 422 | "gridPos": { 423 | "h": 8, 424 | "w": 12, 425 | "x": 12, 426 | "y": 17 427 | }, 428 | "id": 8, 429 | "options": { 430 | "legend": { 431 | "calcs": [ 432 | "max", 433 | "mean", 434 | "last" 435 | ], 436 | "displayMode": "table", 437 | "placement": "right" 438 | }, 439 | "tooltip": { 440 | "mode": "multi" 441 | } 442 | }, 443 | "targets": [ 444 | { 445 | "exemplar": true, 446 | "expr": "dex_memory_usage_bytes{container_name=~\"$container\"}", 447 | "hide": false, 448 | "interval": "", 449 | "legendFormat": "{{container_name}}", 450 | "refId": "B" 451 | } 452 | ], 453 | "title": "Memory usage", 454 | "type": "timeseries" 455 | }, 456 | { 457 | "datasource": null, 458 | "fieldConfig": { 459 | "defaults": { 460 | "color": { 461 | "mode": "palette-classic" 462 | }, 463 | "custom": { 464 | "axisLabel": "", 465 | "axisPlacement": "auto", 466 | "barAlignment": 0, 467 | "drawStyle": "line", 468 | "fillOpacity": 0, 469 | "gradientMode": "none", 470 | "hideFrom": { 471 | "legend": false, 472 | "tooltip": false, 473 | "viz": false 474 | }, 475 | "lineInterpolation": "linear", 476 | "lineWidth": 1, 477 | "pointSize": 5, 478 | "scaleDistribution": { 479 | "type": "linear" 480 | }, 481 | "showPoints": "auto", 482 | "spanNulls": 3600000, 483 | "stacking": { 484 | "group": "A", 485 | "mode": "none" 486 | }, 487 | "thresholdsStyle": { 488 | "mode": "off" 489 | } 490 | }, 491 | "mappings": [], 492 | "thresholds": { 493 | "mode": "absolute", 494 | "steps": [ 495 | { 496 | "color": "green", 497 | "value": null 498 | } 499 | ] 500 | }, 501 | "unit": "Bps" 502 | }, 503 | "overrides": [] 504 | }, 505 | "gridPos": { 506 | "h": 8, 507 | "w": 12, 508 | "x": 0, 509 | "y": 25 510 | }, 511 | "id": 7, 512 | "options": { 513 | "legend": { 514 | "calcs": [ 515 | "max", 516 | "min", 517 | "mean" 518 | ], 519 | "displayMode": "table", 520 | "placement": "right" 521 | }, 522 | "tooltip": { 523 | "mode": "multi" 524 | } 525 | }, 526 | "targets": [ 527 | { 528 | "exemplar": true, 529 | "expr": "rate(dex_network_rx_bytes_total{container_name=~\"$container\"}[$range])", 530 | "interval": "", 531 | "legendFormat": "rx - {{container_name}}", 532 | "refId": "A" 533 | }, 534 | { 535 | "exemplar": true, 536 | "expr": "-rate(dex_network_tx_bytes_total{container_name=~\"$container\"}[$range])", 537 | "hide": false, 538 | "interval": "", 539 | "legendFormat": "tx- {{container_name}}", 540 | "refId": "B" 541 | } 542 | ], 543 | "title": "Network read / write Bytes", 544 | "type": "timeseries" 545 | }, 546 | { 547 | "datasource": null, 548 | "fieldConfig": { 549 | "defaults": { 550 | "color": { 551 | "mode": "palette-classic" 552 | }, 553 | "custom": { 554 | "axisLabel": "", 555 | "axisPlacement": "auto", 556 | "barAlignment": 0, 557 | "drawStyle": "line", 558 | "fillOpacity": 0, 559 | "gradientMode": "none", 560 | "hideFrom": { 561 | "legend": false, 562 | "tooltip": false, 563 | "viz": false 564 | }, 565 | "lineInterpolation": "linear", 566 | "lineWidth": 1, 567 | "pointSize": 5, 568 | "scaleDistribution": { 569 | "type": "linear" 570 | }, 571 | "showPoints": "auto", 572 | "spanNulls": 3600000, 573 | "stacking": { 574 | "group": "A", 575 | "mode": "none" 576 | }, 577 | "thresholdsStyle": { 578 | "mode": "off" 579 | } 580 | }, 581 | "mappings": [], 582 | "thresholds": { 583 | "mode": "absolute", 584 | "steps": [ 585 | { 586 | "color": "green", 587 | "value": null 588 | } 589 | ] 590 | }, 591 | "unit": "Bps" 592 | }, 593 | "overrides": [] 594 | }, 595 | "gridPos": { 596 | "h": 8, 597 | "w": 12, 598 | "x": 12, 599 | "y": 25 600 | }, 601 | "id": 4, 602 | "options": { 603 | "legend": { 604 | "calcs": [ 605 | "max", 606 | "min", 607 | "mean" 608 | ], 609 | "displayMode": "table", 610 | "placement": "right" 611 | }, 612 | "tooltip": { 613 | "mode": "multi" 614 | } 615 | }, 616 | "targets": [ 617 | { 618 | "exemplar": true, 619 | "expr": "rate(dex_block_io_read_bytes_total{container_name=~\"$container\"}[$range])", 620 | "interval": "", 621 | "legendFormat": "read - {{container_name}}", 622 | "refId": "A" 623 | }, 624 | { 625 | "exemplar": true, 626 | "expr": "-rate(dex_block_io_write_bytes_total{container_name=~\"$container\"}[$range])", 627 | "hide": false, 628 | "interval": "", 629 | "legendFormat": "write - {{container_name}}", 630 | "refId": "B" 631 | } 632 | ], 633 | "title": "Storage read / write Bytes", 634 | "type": "timeseries" 635 | } 636 | ], 637 | "refresh": "1m", 638 | "schemaVersion": 30, 639 | "style": "dark", 640 | "tags": [ 641 | "prometheus", 642 | "dex" 643 | ], 644 | "templating": { 645 | "list": [ 646 | { 647 | "allValue": null, 648 | "current": { 649 | "selected": true, 650 | "text": [ 651 | "All" 652 | ], 653 | "value": [ 654 | "$__all" 655 | ] 656 | }, 657 | "datasource": null, 658 | "definition": "dex_block_io_read_bytes_total", 659 | "description": null, 660 | "error": null, 661 | "hide": 0, 662 | "includeAll": true, 663 | "label": "container", 664 | "multi": true, 665 | "name": "container", 666 | "options": [], 667 | "query": { 668 | "query": "dex_block_io_read_bytes_total", 669 | "refId": "StandardVariableQuery" 670 | }, 671 | "refresh": 1, 672 | "regex": "/.*container_name=\"([^\"]*).*/", 673 | "skipUrlSync": false, 674 | "sort": 1, 675 | "type": "query" 676 | }, 677 | { 678 | "auto": true, 679 | "auto_count": 30, 680 | "auto_min": "1m", 681 | "current": { 682 | "selected": false, 683 | "text": "auto", 684 | "value": "$__auto_interval_range" 685 | }, 686 | "description": null, 687 | "error": null, 688 | "hide": 0, 689 | "label": null, 690 | "name": "range", 691 | "options": [ 692 | { 693 | "selected": true, 694 | "text": "auto", 695 | "value": "$__auto_interval_range" 696 | }, 697 | { 698 | "selected": false, 699 | "text": "1m", 700 | "value": "1m" 701 | }, 702 | { 703 | "selected": false, 704 | "text": "5m", 705 | "value": "5m" 706 | }, 707 | { 708 | "selected": false, 709 | "text": "10m", 710 | "value": "10m" 711 | }, 712 | { 713 | "selected": false, 714 | "text": "30m", 715 | "value": "30m" 716 | }, 717 | { 718 | "selected": false, 719 | "text": "1h", 720 | "value": "1h" 721 | }, 722 | { 723 | "selected": false, 724 | "text": "6h", 725 | "value": "6h" 726 | }, 727 | { 728 | "selected": false, 729 | "text": "12h", 730 | "value": "12h" 731 | }, 732 | { 733 | "selected": false, 734 | "text": "1d", 735 | "value": "1d" 736 | } 737 | ], 738 | "query": "1m,5m,10m,30m,1h,6h,12h,1d", 739 | "queryValue": "", 740 | "refresh": 2, 741 | "skipUrlSync": false, 742 | "type": "interval" 743 | } 744 | ] 745 | }, 746 | "time": { 747 | "from": "now-3h", 748 | "to": "now" 749 | }, 750 | "timepicker": { 751 | "hidden": false, 752 | "refresh_intervals": [ 753 | "30s", 754 | "1m", 755 | "5m", 756 | "15m", 757 | "30m" 758 | ] 759 | }, 760 | "timezone": "", 761 | "title": "Dex Metrics", 762 | "uid": "Sk4Ci0W7k", 763 | "version": 12 764 | } 765 | -------------------------------------------------------------------------------- /docs/grafana8_2.json: -------------------------------------------------------------------------------- 1 | { 2 | "annotations": { 3 | "list": [ 4 | { 5 | "builtIn": 1, 6 | "datasource": "-- Grafana --", 7 | "enable": true, 8 | "hide": true, 9 | "iconColor": "rgba(0, 211, 255, 1)", 10 | "limit": 100, 11 | "name": "Annotations & Alerts", 12 | "target": { 13 | "limit": 100, 14 | "matchAny": false, 15 | "tags": [], 16 | "type": "dashboard" 17 | }, 18 | "type": "dashboard" 19 | } 20 | ] 21 | }, 22 | "description": "0xERR0R / dex metrics exporter Dashboard", 23 | "editable": true, 24 | "gnetId": null, 25 | "graphTooltip": 0, 26 | "id": 23, 27 | "iteration": 1627594283124, 28 | "links": [], 29 | "panels": [ 30 | { 31 | "datasource": null, 32 | "fieldConfig": { 33 | "defaults": { 34 | "color": { 35 | "mode": "thresholds" 36 | }, 37 | "custom": { 38 | "fillOpacity": 70, 39 | "lineWidth": 0 40 | }, 41 | "mappings": [ 42 | { 43 | "options": { 44 | "0": { 45 | "color": "red", 46 | "index": 1, 47 | "text": "DOWN" 48 | }, 49 | "1": { 50 | "color": "green", 51 | "index": 0, 52 | "text": "OK" 53 | } 54 | }, 55 | "type": "value" 56 | }, 57 | { 58 | "options": { 59 | "from": 2, 60 | "result": { 61 | "color": "blue", 62 | "index": 2, 63 | "text": "MULTY" 64 | }, 65 | "to": 999 66 | }, 67 | "type": "range" 68 | } 69 | ], 70 | "thresholds": { 71 | "mode": "absolute", 72 | "steps": [ 73 | { 74 | "color": "green", 75 | "value": null 76 | } 77 | ] 78 | }, 79 | "unit": "short" 80 | }, 81 | "overrides": [] 82 | }, 83 | "gridPos": { 84 | "h": 14, 85 | "w": 24, 86 | "x": 0, 87 | "y": 0 88 | }, 89 | "id": 5, 90 | "interval": "10m", 91 | "options": { 92 | "alignValue": "center", 93 | "legend": { 94 | "displayMode": "list", 95 | "placement": "bottom" 96 | }, 97 | "mergeValues": true, 98 | "rowHeight": 0.9, 99 | "showValue": "always", 100 | "tooltip": { 101 | "mode": "single" 102 | } 103 | }, 104 | "targets": [ 105 | { 106 | "exemplar": true, 107 | "expr": "dex_container_running{container_name=~\"$container\", job=~\"$job.*\"}", 108 | "interval": "", 109 | "legendFormat": "{{container_name}}", 110 | "refId": "A" 111 | } 112 | ], 113 | "title": "Container alive", 114 | "type": "state-timeline" 115 | }, 116 | { 117 | "datasource": null, 118 | "description": "", 119 | "fieldConfig": { 120 | "defaults": { 121 | "color": { 122 | "mode": "palette-classic" 123 | }, 124 | "custom": { 125 | "hideFrom": { 126 | "legend": false, 127 | "tooltip": false, 128 | "viz": false 129 | } 130 | }, 131 | "decimals": 2, 132 | "mappings": [], 133 | "unit": "short" 134 | }, 135 | "overrides": [] 136 | }, 137 | "gridPos": { 138 | "h": 8, 139 | "w": 8, 140 | "x": 0, 141 | "y": 14 142 | }, 143 | "id": 10, 144 | "options": { 145 | "legend": { 146 | "displayMode": "table", 147 | "placement": "right", 148 | "values": [ 149 | "percent", 150 | "value" 151 | ] 152 | }, 153 | "pieType": "pie", 154 | "reduceOptions": { 155 | "calcs": [ 156 | "lastNotNull" 157 | ], 158 | "fields": "", 159 | "values": false 160 | }, 161 | "tooltip": { 162 | "mode": "single" 163 | } 164 | }, 165 | "targets": [ 166 | { 167 | "exemplar": true, 168 | "expr": "avg_over_time(dex_cpu_utilization_percent{container_name=~\"$container\", job=~\"$job.*\"}[$range])", 169 | "instant": true, 170 | "interval": "", 171 | "legendFormat": "{{container_name}}", 172 | "refId": "A" 173 | } 174 | ], 175 | "title": "Last $range CPU Usage", 176 | "type": "piechart" 177 | }, 178 | { 179 | "datasource": null, 180 | "fieldConfig": { 181 | "defaults": { 182 | "color": { 183 | "fixedColor": "blue", 184 | "mode": "thresholds" 185 | }, 186 | "mappings": [], 187 | "thresholds": { 188 | "mode": "absolute", 189 | "steps": [ 190 | { 191 | "color": "green", 192 | "value": null 193 | } 194 | ] 195 | } 196 | }, 197 | "overrides": [] 198 | }, 199 | "gridPos": { 200 | "h": 8, 201 | "w": 5, 202 | "x": 8, 203 | "y": 14 204 | }, 205 | "id": 13, 206 | "options": { 207 | "colorMode": "value", 208 | "graphMode": "area", 209 | "justifyMode": "auto", 210 | "orientation": "auto", 211 | "reduceOptions": { 212 | "calcs": [ 213 | "lastNotNull" 214 | ], 215 | "fields": "", 216 | "values": false 217 | }, 218 | "text": {}, 219 | "textMode": "auto" 220 | }, 221 | "pluginVersion": "8.1.0-28481pre", 222 | "targets": [ 223 | { 224 | "exemplar": true, 225 | "expr": "count(dex_container_running{container_name=~\"$container\", job=~\"$job.*\"} == 1)", 226 | "interval": "", 227 | "legendFormat": "", 228 | "refId": "A" 229 | } 230 | ], 231 | "title": "Running Containers", 232 | "type": "stat" 233 | }, 234 | { 235 | "datasource": null, 236 | "description": "", 237 | "fieldConfig": { 238 | "defaults": { 239 | "color": { 240 | "mode": "thresholds" 241 | }, 242 | "mappings": [], 243 | "thresholds": { 244 | "mode": "absolute", 245 | "steps": [ 246 | { 247 | "color": "green", 248 | "value": null 249 | }, 250 | { 251 | "color": "red", 252 | "value": 80 253 | } 254 | ] 255 | } 256 | }, 257 | "overrides": [] 258 | }, 259 | "gridPos": { 260 | "h": 8, 261 | "w": 4, 262 | "x": 13, 263 | "y": 14 264 | }, 265 | "id": 15, 266 | "options": { 267 | "colorMode": "value", 268 | "graphMode": "area", 269 | "justifyMode": "auto", 270 | "orientation": "auto", 271 | "reduceOptions": { 272 | "calcs": [ 273 | "lastNotNull" 274 | ], 275 | "fields": "", 276 | "values": false 277 | }, 278 | "text": {}, 279 | "textMode": "auto" 280 | }, 281 | "pluginVersion": "8.1.0-28481pre", 282 | "targets": [ 283 | { 284 | "exemplar": true, 285 | "expr": "count(dex_container_running{container_name=~\"$container\", job=~\"$job.*\"} == 0)", 286 | "interval": "", 287 | "legendFormat": "", 288 | "refId": "A" 289 | } 290 | ], 291 | "title": "Containers down", 292 | "type": "stat" 293 | }, 294 | { 295 | "datasource": null, 296 | "description": "", 297 | "fieldConfig": { 298 | "defaults": { 299 | "color": { 300 | "mode": "palette-classic" 301 | }, 302 | "custom": { 303 | "hideFrom": { 304 | "legend": false, 305 | "tooltip": false, 306 | "viz": false 307 | } 308 | }, 309 | "decimals": 2, 310 | "mappings": [], 311 | "unit": "decbytes" 312 | }, 313 | "overrides": [] 314 | }, 315 | "gridPos": { 316 | "h": 8, 317 | "w": 7, 318 | "x": 17, 319 | "y": 14 320 | }, 321 | "id": 11, 322 | "options": { 323 | "legend": { 324 | "displayMode": "table", 325 | "placement": "right", 326 | "values": [ 327 | "percent", 328 | "value" 329 | ] 330 | }, 331 | "pieType": "pie", 332 | "reduceOptions": { 333 | "calcs": [ 334 | "lastNotNull" 335 | ], 336 | "fields": "", 337 | "values": false 338 | }, 339 | "tooltip": { 340 | "mode": "single" 341 | } 342 | }, 343 | "targets": [ 344 | { 345 | "exemplar": true, 346 | "expr": "avg_over_time(dex_memory_usage_bytes{container_name=~\"$container\", job=~\"$job.*\"}[$range])", 347 | "instant": true, 348 | "interval": "", 349 | "legendFormat": "{{container_name}}", 350 | "refId": "A" 351 | } 352 | ], 353 | "title": "Last $range Memory Usage", 354 | "type": "piechart" 355 | }, 356 | { 357 | "datasource": null, 358 | "fieldConfig": { 359 | "defaults": { 360 | "color": { 361 | "mode": "palette-classic" 362 | }, 363 | "custom": { 364 | "axisLabel": "", 365 | "axisPlacement": "auto", 366 | "barAlignment": 0, 367 | "drawStyle": "line", 368 | "fillOpacity": 0, 369 | "gradientMode": "none", 370 | "hideFrom": { 371 | "legend": false, 372 | "tooltip": false, 373 | "viz": false 374 | }, 375 | "lineInterpolation": "linear", 376 | "lineWidth": 1, 377 | "pointSize": 5, 378 | "scaleDistribution": { 379 | "type": "linear" 380 | }, 381 | "showPoints": "auto", 382 | "spanNulls": 3600000, 383 | "stacking": { 384 | "group": "A", 385 | "mode": "none" 386 | }, 387 | "thresholdsStyle": { 388 | "mode": "off" 389 | } 390 | }, 391 | "decimals": 1, 392 | "mappings": [], 393 | "thresholds": { 394 | "mode": "absolute", 395 | "steps": [ 396 | { 397 | "color": "green", 398 | "value": null 399 | } 400 | ] 401 | }, 402 | "unit": "percent" 403 | }, 404 | "overrides": [] 405 | }, 406 | "gridPos": { 407 | "h": 8, 408 | "w": 12, 409 | "x": 0, 410 | "y": 22 411 | }, 412 | "id": 6, 413 | "options": { 414 | "legend": { 415 | "calcs": [ 416 | "max", 417 | "mean" 418 | ], 419 | "displayMode": "table", 420 | "placement": "right" 421 | }, 422 | "tooltip": { 423 | "mode": "multi" 424 | } 425 | }, 426 | "targets": [ 427 | { 428 | "exemplar": true, 429 | "expr": "avg_over_time(dex_cpu_utilization_percent{container_name=~\"$container\", job=~\"$job.*\" }[$range])", 430 | "interval": "", 431 | "legendFormat": "{{container_name}}", 432 | "refId": "A" 433 | } 434 | ], 435 | "title": "CPU usage Percent", 436 | "type": "timeseries" 437 | }, 438 | { 439 | "datasource": null, 440 | "fieldConfig": { 441 | "defaults": { 442 | "color": { 443 | "mode": "palette-classic" 444 | }, 445 | "custom": { 446 | "axisLabel": "", 447 | "axisPlacement": "auto", 448 | "barAlignment": 0, 449 | "drawStyle": "line", 450 | "fillOpacity": 0, 451 | "gradientMode": "none", 452 | "hideFrom": { 453 | "legend": false, 454 | "tooltip": false, 455 | "viz": false 456 | }, 457 | "lineInterpolation": "linear", 458 | "lineWidth": 1, 459 | "pointSize": 5, 460 | "scaleDistribution": { 461 | "type": "linear" 462 | }, 463 | "showPoints": "auto", 464 | "spanNulls": 3600000, 465 | "stacking": { 466 | "group": "A", 467 | "mode": "none" 468 | }, 469 | "thresholdsStyle": { 470 | "mode": "off" 471 | } 472 | }, 473 | "decimals": 1, 474 | "mappings": [], 475 | "thresholds": { 476 | "mode": "absolute", 477 | "steps": [ 478 | { 479 | "color": "green", 480 | "value": null 481 | } 482 | ] 483 | }, 484 | "unit": "decbytes" 485 | }, 486 | "overrides": [] 487 | }, 488 | "gridPos": { 489 | "h": 8, 490 | "w": 12, 491 | "x": 12, 492 | "y": 22 493 | }, 494 | "id": 8, 495 | "options": { 496 | "legend": { 497 | "calcs": [ 498 | "max", 499 | "mean", 500 | "last" 501 | ], 502 | "displayMode": "table", 503 | "placement": "right" 504 | }, 505 | "tooltip": { 506 | "mode": "multi" 507 | } 508 | }, 509 | "targets": [ 510 | { 511 | "exemplar": true, 512 | "expr": "dex_memory_usage_bytes{container_name=~\"$container\", job=~\"$job.*\"}", 513 | "hide": false, 514 | "interval": "", 515 | "legendFormat": "{{container_name}}", 516 | "refId": "B" 517 | } 518 | ], 519 | "title": "Memory usage", 520 | "type": "timeseries" 521 | }, 522 | { 523 | "datasource": null, 524 | "fieldConfig": { 525 | "defaults": { 526 | "color": { 527 | "mode": "palette-classic" 528 | }, 529 | "custom": { 530 | "axisLabel": "", 531 | "axisPlacement": "auto", 532 | "barAlignment": 0, 533 | "drawStyle": "line", 534 | "fillOpacity": 0, 535 | "gradientMode": "none", 536 | "hideFrom": { 537 | "legend": false, 538 | "tooltip": false, 539 | "viz": false 540 | }, 541 | "lineInterpolation": "linear", 542 | "lineWidth": 1, 543 | "pointSize": 5, 544 | "scaleDistribution": { 545 | "type": "linear" 546 | }, 547 | "showPoints": "auto", 548 | "spanNulls": 3600000, 549 | "stacking": { 550 | "group": "A", 551 | "mode": "none" 552 | }, 553 | "thresholdsStyle": { 554 | "mode": "off" 555 | } 556 | }, 557 | "mappings": [], 558 | "thresholds": { 559 | "mode": "absolute", 560 | "steps": [ 561 | { 562 | "color": "green", 563 | "value": null 564 | } 565 | ] 566 | }, 567 | "unit": "Bps" 568 | }, 569 | "overrides": [] 570 | }, 571 | "gridPos": { 572 | "h": 8, 573 | "w": 12, 574 | "x": 0, 575 | "y": 30 576 | }, 577 | "id": 7, 578 | "options": { 579 | "legend": { 580 | "calcs": [ 581 | "max", 582 | "min", 583 | "mean" 584 | ], 585 | "displayMode": "table", 586 | "placement": "right" 587 | }, 588 | "tooltip": { 589 | "mode": "multi" 590 | } 591 | }, 592 | "targets": [ 593 | { 594 | "exemplar": true, 595 | "expr": "rate(dex_network_rx_bytes_total{container_name=~\"$container\", job=~\"$job.*\"}[$range])", 596 | "interval": "", 597 | "legendFormat": "rx - {{container_name}}", 598 | "refId": "A" 599 | }, 600 | { 601 | "exemplar": true, 602 | "expr": "-rate(dex_network_tx_bytes_total{container_name=~\"$container\", job=~\"$job.*\"}[$range])", 603 | "hide": false, 604 | "interval": "", 605 | "legendFormat": "tx- {{container_name}}", 606 | "refId": "B" 607 | } 608 | ], 609 | "title": "Network read / write Bytes", 610 | "type": "timeseries" 611 | }, 612 | { 613 | "datasource": null, 614 | "fieldConfig": { 615 | "defaults": { 616 | "color": { 617 | "mode": "palette-classic" 618 | }, 619 | "custom": { 620 | "axisLabel": "", 621 | "axisPlacement": "auto", 622 | "barAlignment": 0, 623 | "drawStyle": "line", 624 | "fillOpacity": 0, 625 | "gradientMode": "none", 626 | "hideFrom": { 627 | "legend": false, 628 | "tooltip": false, 629 | "viz": false 630 | }, 631 | "lineInterpolation": "linear", 632 | "lineWidth": 1, 633 | "pointSize": 5, 634 | "scaleDistribution": { 635 | "type": "linear" 636 | }, 637 | "showPoints": "auto", 638 | "spanNulls": 3600000, 639 | "stacking": { 640 | "group": "A", 641 | "mode": "none" 642 | }, 643 | "thresholdsStyle": { 644 | "mode": "off" 645 | } 646 | }, 647 | "mappings": [], 648 | "thresholds": { 649 | "mode": "absolute", 650 | "steps": [ 651 | { 652 | "color": "green", 653 | "value": null 654 | } 655 | ] 656 | }, 657 | "unit": "Bps" 658 | }, 659 | "overrides": [] 660 | }, 661 | "gridPos": { 662 | "h": 8, 663 | "w": 12, 664 | "x": 12, 665 | "y": 30 666 | }, 667 | "id": 4, 668 | "options": { 669 | "legend": { 670 | "calcs": [ 671 | "max", 672 | "min", 673 | "mean" 674 | ], 675 | "displayMode": "table", 676 | "placement": "right" 677 | }, 678 | "tooltip": { 679 | "mode": "multi" 680 | } 681 | }, 682 | "targets": [ 683 | { 684 | "exemplar": true, 685 | "expr": "rate(dex_block_io_read_bytes_total{container_name=~\"$container\", job=~\"$job.*\"}[$range])", 686 | "interval": "", 687 | "legendFormat": "read - {{container_name}}", 688 | "refId": "A" 689 | }, 690 | { 691 | "exemplar": true, 692 | "expr": "-rate(dex_block_io_write_bytes_total{container_name=~\"$container\", job=~\"$job.*\"}[$range])", 693 | "hide": false, 694 | "interval": "", 695 | "legendFormat": "write - {{container_name}}", 696 | "refId": "B" 697 | } 698 | ], 699 | "title": "Storage read / write Bytes", 700 | "type": "timeseries" 701 | } 702 | ], 703 | "refresh": "30s", 704 | "schemaVersion": 30, 705 | "style": "dark", 706 | "tags": [ 707 | "prometheus", 708 | "dex" 709 | ], 710 | "templating": { 711 | "list": [ 712 | { 713 | "description": null, 714 | "error": null, 715 | "hide": 2, 716 | "label": "Jobs postfix", 717 | "name": "job_postfix", 718 | "query": "_dex", 719 | "skipUrlSync": false, 720 | "type": "constant" 721 | }, 722 | { 723 | "allValue": ".*", 724 | "current": { 725 | "selected": true, 726 | "text": "monitor", 727 | "value": "monitor" 728 | }, 729 | "datasource": null, 730 | "definition": "label_values(job)", 731 | "description": null, 732 | "error": null, 733 | "hide": 0, 734 | "includeAll": true, 735 | "label": "Job", 736 | "multi": false, 737 | "name": "job", 738 | "options": [], 739 | "query": { 740 | "query": "label_values(job)", 741 | "refId": "StandardVariableQuery" 742 | }, 743 | "refresh": 1, 744 | "regex": "/(?.*)$job_postfix/", 745 | "skipUrlSync": false, 746 | "sort": 0, 747 | "type": "query" 748 | }, 749 | { 750 | "allValue": ".*", 751 | "current": { 752 | "selected": true, 753 | "text": [ 754 | "All" 755 | ], 756 | "value": [ 757 | "$__all" 758 | ] 759 | }, 760 | "datasource": null, 761 | "definition": "dex_container_running", 762 | "description": null, 763 | "error": null, 764 | "hide": 0, 765 | "includeAll": true, 766 | "label": "container", 767 | "multi": true, 768 | "name": "container", 769 | "options": [], 770 | "query": { 771 | "query": "dex_container_running", 772 | "refId": "StandardVariableQuery" 773 | }, 774 | "refresh": 1, 775 | "regex": "/.*container_name=\"(?[^\"]*).*job=\"$job$job_postfix\".*/", 776 | "skipUrlSync": false, 777 | "sort": 1, 778 | "type": "query" 779 | }, 780 | { 781 | "auto": true, 782 | "auto_count": 30, 783 | "auto_min": "1m", 784 | "current": { 785 | "selected": false, 786 | "text": "1m", 787 | "value": "1m" 788 | }, 789 | "description": null, 790 | "error": null, 791 | "hide": 0, 792 | "label": null, 793 | "name": "range", 794 | "options": [ 795 | { 796 | "selected": false, 797 | "text": "auto", 798 | "value": "$__auto_interval_range" 799 | }, 800 | { 801 | "selected": true, 802 | "text": "1m", 803 | "value": "1m" 804 | }, 805 | { 806 | "selected": false, 807 | "text": "5m", 808 | "value": "5m" 809 | }, 810 | { 811 | "selected": false, 812 | "text": "10m", 813 | "value": "10m" 814 | }, 815 | { 816 | "selected": false, 817 | "text": "30m", 818 | "value": "30m" 819 | }, 820 | { 821 | "selected": false, 822 | "text": "1h", 823 | "value": "1h" 824 | }, 825 | { 826 | "selected": false, 827 | "text": "6h", 828 | "value": "6h" 829 | }, 830 | { 831 | "selected": false, 832 | "text": "12h", 833 | "value": "12h" 834 | }, 835 | { 836 | "selected": false, 837 | "text": "1d", 838 | "value": "1d" 839 | } 840 | ], 841 | "query": "1m,5m,10m,30m,1h,6h,12h,1d", 842 | "queryValue": "", 843 | "refresh": 2, 844 | "skipUrlSync": false, 845 | "type": "interval" 846 | } 847 | ] 848 | }, 849 | "time": { 850 | "from": "now-3h", 851 | "to": "now" 852 | }, 853 | "timepicker": { 854 | "hidden": false, 855 | "refresh_intervals": [ 856 | "30s", 857 | "1m", 858 | "5m", 859 | "15m", 860 | "30m" 861 | ] 862 | }, 863 | "timezone": "", 864 | "title": "Dex Metrics", 865 | "uid": "WRJKewWgk", 866 | "version": 6 867 | } 868 | -------------------------------------------------------------------------------- /docs/grafana7.json: -------------------------------------------------------------------------------- 1 | { 2 | "__inputs": [ 3 | { 4 | "name": "DS_PROMETHEUS", 5 | "label": "Prometheus", 6 | "description": "", 7 | "type": "datasource", 8 | "pluginId": "prometheus", 9 | "pluginName": "Prometheus" 10 | } 11 | ], 12 | "__requires": [ 13 | { 14 | "type": "grafana", 15 | "id": "grafana", 16 | "name": "Grafana", 17 | "version": "7.3.1" 18 | }, 19 | { 20 | "type": "panel", 21 | "id": "grafana-piechart-panel", 22 | "name": "Pie Chart", 23 | "version": "1.6.1" 24 | }, 25 | { 26 | "type": "panel", 27 | "id": "graph", 28 | "name": "Graph", 29 | "version": "" 30 | }, 31 | { 32 | "type": "datasource", 33 | "id": "prometheus", 34 | "name": "Prometheus", 35 | "version": "1.0.0" 36 | }, 37 | { 38 | "type": "panel", 39 | "id": "singlestat", 40 | "name": "Singlestat", 41 | "version": "" 42 | } 43 | ], 44 | "annotations": { 45 | "list": [ 46 | { 47 | "builtIn": 1, 48 | "datasource": "-- Grafana --", 49 | "enable": true, 50 | "hide": true, 51 | "iconColor": "rgba(0, 211, 255, 1)", 52 | "name": "Annotations & Alerts", 53 | "type": "dashboard" 54 | } 55 | ] 56 | }, 57 | "editable": true, 58 | "gnetId": null, 59 | "graphTooltip": 0, 60 | "id": null, 61 | "iteration": 1611952185251, 62 | "links": [], 63 | "panels": [ 64 | { 65 | "cacheTimeout": null, 66 | "colorBackground": false, 67 | "colorValue": false, 68 | "colors": [ 69 | "#299c46", 70 | "rgba(237, 129, 40, 0.89)", 71 | "#d44a3a" 72 | ], 73 | "datasource": "${DS_PROMETHEUS}", 74 | "fieldConfig": { 75 | "defaults": { 76 | "custom": {} 77 | }, 78 | "overrides": [] 79 | }, 80 | "format": "none", 81 | "gauge": { 82 | "maxValue": 100, 83 | "minValue": 0, 84 | "show": false, 85 | "thresholdLabels": false, 86 | "thresholdMarkers": true 87 | }, 88 | "id": 9, 89 | "interval": "", 90 | "links": [], 91 | "mappingType": 1, 92 | "mappingTypes": [ 93 | { 94 | "name": "value to text", 95 | "value": 1 96 | }, 97 | { 98 | "name": "range to text", 99 | "value": 2 100 | } 101 | ], 102 | "maxDataPoints": 100, 103 | "nullPointMode": "connected", 104 | "nullText": null, 105 | "postfix": "", 106 | "postfixFontSize": "50%", 107 | "prefix": "", 108 | "prefixFontSize": "50%", 109 | "rangeMaps": [ 110 | { 111 | "from": "null", 112 | "text": "N/A", 113 | "to": "null" 114 | } 115 | ], 116 | "sparkline": { 117 | "fillColor": "rgba(31, 118, 189, 0.18)", 118 | "full": false, 119 | "lineColor": "rgb(31, 120, 193)", 120 | "show": true, 121 | "ymax": null, 122 | "ymin": null 123 | }, 124 | "tableColumn": "", 125 | "targets": [ 126 | { 127 | "expr": "count(dex_container_running{job=~\"$job\"} == 1)", 128 | "instant": false, 129 | "interval": "", 130 | "legendFormat": "", 131 | "refId": "A" 132 | } 133 | ], 134 | "thresholds": "", 135 | "timeFrom": null, 136 | "timeShift": null, 137 | "title": "Containers running", 138 | "type": "singlestat", 139 | "valueFontSize": "80%", 140 | "valueMaps": [ 141 | { 142 | "op": "=", 143 | "text": "N/A", 144 | "value": "null" 145 | } 146 | ], 147 | "valueName": "current" 148 | }, 149 | { 150 | "cacheTimeout": null, 151 | "colorBackground": false, 152 | "colorValue": true, 153 | "colors": [ 154 | "#299c46", 155 | "rgba(237, 129, 40, 0.89)", 156 | "#d44a3a" 157 | ], 158 | "datasource": "${DS_PROMETHEUS}", 159 | "decimals": null, 160 | "fieldConfig": { 161 | "defaults": { 162 | "custom": {} 163 | }, 164 | "overrides": [] 165 | }, 166 | "format": "none", 167 | "gauge": { 168 | "maxValue": 100, 169 | "minValue": 0, 170 | "show": false, 171 | "thresholdLabels": false, 172 | "thresholdMarkers": true 173 | }, 174 | "gridPos": { 175 | "h": 3, 176 | "w": 6, 177 | "x": 6, 178 | "y": 0 179 | }, 180 | "id": 10, 181 | "interval": "", 182 | "links": [], 183 | "mappingType": 1, 184 | "mappingTypes": [ 185 | { 186 | "name": "value to text", 187 | "value": 1 188 | }, 189 | { 190 | "name": "range to text", 191 | "value": 2 192 | } 193 | ], 194 | "maxDataPoints": 100, 195 | "nullPointMode": "connected", 196 | "nullText": null, 197 | "postfix": "", 198 | "postfixFontSize": "50%", 199 | "prefix": "", 200 | "prefixFontSize": "50%", 201 | "rangeMaps": [ 202 | { 203 | "from": "null", 204 | "text": "N/A", 205 | "to": "null" 206 | } 207 | ], 208 | "sparkline": { 209 | "fillColor": "rgba(31, 118, 189, 0.18)", 210 | "full": false, 211 | "lineColor": "rgb(31, 120, 193)", 212 | "show": true, 213 | "ymax": null, 214 | "ymin": null 215 | }, 216 | "tableColumn": "", 217 | "targets": [ 218 | { 219 | "expr": "count(dex_container_running{job=~\"$job\"} == 0)", 220 | "instant": true, 221 | "interval": "", 222 | "legendFormat": "", 223 | "refId": "A" 224 | } 225 | ], 226 | "thresholds": "0,0", 227 | "timeFrom": null, 228 | "timeShift": null, 229 | "title": "Containers not running", 230 | "type": "singlestat", 231 | "valueFontSize": "80%", 232 | "valueMaps": [ 233 | { 234 | "op": "=", 235 | "text": "0", 236 | "value": "null" 237 | } 238 | ], 239 | "valueName": "current" 240 | }, 241 | { 242 | "aliasColors": {}, 243 | "bars": false, 244 | "dashLength": 10, 245 | "dashes": false, 246 | "datasource": "${DS_PROMETHEUS}", 247 | "fieldConfig": { 248 | "defaults": { 249 | "custom": {}, 250 | "links": [] 251 | }, 252 | "overrides": [] 253 | }, 254 | "fill": 1, 255 | "fillGradient": 1, 256 | "gridPos": { 257 | "h": 11, 258 | "w": 12, 259 | "x": 12, 260 | "y": 0 261 | }, 262 | "hiddenSeries": false, 263 | "id": 2, 264 | "interval": "", 265 | "legend": { 266 | "alignAsTable": false, 267 | "avg": false, 268 | "current": false, 269 | "hideZero": false, 270 | "max": false, 271 | "min": false, 272 | "show": true, 273 | "total": false, 274 | "values": false 275 | }, 276 | "lines": true, 277 | "linewidth": 2, 278 | "nullPointMode": "null as zero", 279 | "options": { 280 | "alertThreshold": true 281 | }, 282 | "percentage": false, 283 | "pluginVersion": "7.3.1", 284 | "pointradius": 2, 285 | "points": false, 286 | "renderer": "flot", 287 | "seriesOverrides": [], 288 | "spaceLength": 10, 289 | "stack": false, 290 | "steppedLine": false, 291 | "targets": [ 292 | { 293 | "expr": "dex_cpu_utilization_percent{job=~\"$job\",container_name=~\"$container_name\"}", 294 | "interval": "", 295 | "legendFormat": "{{container_name}}", 296 | "refId": "A" 297 | } 298 | ], 299 | "thresholds": [], 300 | "timeFrom": null, 301 | "timeRegions": [], 302 | "timeShift": null, 303 | "title": "Cpu utilization", 304 | "tooltip": { 305 | "shared": false, 306 | "sort": 2, 307 | "value_type": "individual" 308 | }, 309 | "type": "graph", 310 | "xaxis": { 311 | "buckets": null, 312 | "mode": "time", 313 | "name": null, 314 | "show": true, 315 | "values": [] 316 | }, 317 | "yaxes": [ 318 | { 319 | "decimals": null, 320 | "format": "percent", 321 | "label": "cpu utilization", 322 | "logBase": 1, 323 | "max": null, 324 | "min": "0", 325 | "show": true 326 | }, 327 | { 328 | "format": "short", 329 | "label": null, 330 | "logBase": 1, 331 | "max": null, 332 | "min": null, 333 | "show": true 334 | } 335 | ], 336 | "yaxis": { 337 | "align": false, 338 | "alignLevel": null 339 | } 340 | }, 341 | { 342 | "aliasColors": {}, 343 | "bars": false, 344 | "dashLength": 10, 345 | "dashes": false, 346 | "datasource": "${DS_PROMETHEUS}", 347 | "fieldConfig": { 348 | "defaults": { 349 | "custom": {}, 350 | "links": [] 351 | }, 352 | "overrides": [] 353 | }, 354 | "fill": 1, 355 | "fillGradient": 1, 356 | "gridPos": { 357 | "h": 8, 358 | "w": 12, 359 | "x": 0, 360 | "y": 3 361 | }, 362 | "hiddenSeries": false, 363 | "id": 4, 364 | "legend": { 365 | "avg": false, 366 | "current": false, 367 | "max": false, 368 | "min": false, 369 | "show": true, 370 | "total": false, 371 | "values": false 372 | }, 373 | "lines": true, 374 | "linewidth": 2, 375 | "nullPointMode": "null as zero", 376 | "options": { 377 | "alertThreshold": true 378 | }, 379 | "percentage": false, 380 | "pluginVersion": "7.3.1", 381 | "pointradius": 2, 382 | "points": false, 383 | "renderer": "flot", 384 | "seriesOverrides": [], 385 | "spaceLength": 10, 386 | "stack": false, 387 | "steppedLine": false, 388 | "targets": [ 389 | { 390 | "expr": "dex_memory_usage_bytes{job=~\"$job\",container_name=~\"$container_name\"}", 391 | "interval": "", 392 | "legendFormat": "{{ container_name }}", 393 | "refId": "A" 394 | } 395 | ], 396 | "thresholds": [], 397 | "timeFrom": null, 398 | "timeRegions": [], 399 | "timeShift": null, 400 | "title": "memory usage", 401 | "tooltip": { 402 | "shared": true, 403 | "sort": 2, 404 | "value_type": "individual" 405 | }, 406 | "type": "graph", 407 | "xaxis": { 408 | "buckets": null, 409 | "mode": "time", 410 | "name": null, 411 | "show": true, 412 | "values": [] 413 | }, 414 | "yaxes": [ 415 | { 416 | "format": "decbytes", 417 | "label": "memory usage", 418 | "logBase": 1, 419 | "max": null, 420 | "min": null, 421 | "show": true 422 | }, 423 | { 424 | "format": "short", 425 | "label": "", 426 | "logBase": 1, 427 | "max": null, 428 | "min": null, 429 | "show": true 430 | } 431 | ], 432 | "yaxis": { 433 | "align": false, 434 | "alignLevel": null 435 | } 436 | }, 437 | { 438 | "aliasColors": {}, 439 | "breakPoint": "50%", 440 | "cacheTimeout": null, 441 | "combine": { 442 | "label": "Others", 443 | "threshold": 0 444 | }, 445 | "datasource": "${DS_PROMETHEUS}", 446 | "decimals": 2, 447 | "fieldConfig": { 448 | "defaults": { 449 | "custom": {} 450 | }, 451 | "overrides": [] 452 | }, 453 | "fontSize": "80%", 454 | "format": "decbytes", 455 | "gridPos": { 456 | "h": 8, 457 | "w": 12, 458 | "x": 0, 459 | "y": 11 460 | }, 461 | "id": 12, 462 | "interval": null, 463 | "legend": { 464 | "header": "Memory", 465 | "percentage": false, 466 | "show": true, 467 | "values": true 468 | }, 469 | "legendType": "Right side", 470 | "links": [], 471 | "maxDataPoints": 3, 472 | "nullPointMode": "connected", 473 | "pieType": "pie", 474 | "strokeWidth": 1, 475 | "targets": [ 476 | { 477 | "expr": "sort_desc(dex_memory_usage_bytes{job=~\"$job\",container_name=~\"$container_name\"})", 478 | "instant": true, 479 | "interval": "", 480 | "legendFormat": "{{ container_name }}", 481 | "refId": "A" 482 | } 483 | ], 484 | "timeFrom": null, 485 | "timeShift": null, 486 | "title": "Memory", 487 | "type": "grafana-piechart-panel", 488 | "valueName": "current" 489 | }, 490 | { 491 | "aliasColors": {}, 492 | "bars": false, 493 | "dashLength": 10, 494 | "dashes": false, 495 | "datasource": "${DS_PROMETHEUS}", 496 | "fieldConfig": { 497 | "defaults": { 498 | "custom": {}, 499 | "links": [] 500 | }, 501 | "overrides": [] 502 | }, 503 | "fill": 1, 504 | "fillGradient": 0, 505 | "gridPos": { 506 | "h": 8, 507 | "w": 12, 508 | "x": 12, 509 | "y": 11 510 | }, 511 | "hiddenSeries": false, 512 | "id": 15, 513 | "legend": { 514 | "avg": false, 515 | "current": false, 516 | "max": false, 517 | "min": false, 518 | "show": true, 519 | "total": false, 520 | "values": false 521 | }, 522 | "lines": true, 523 | "linewidth": 1, 524 | "nullPointMode": "null as zero", 525 | "options": { 526 | "alertThreshold": true 527 | }, 528 | "percentage": false, 529 | "pluginVersion": "7.3.1", 530 | "pointradius": 2, 531 | "points": false, 532 | "renderer": "flot", 533 | "seriesOverrides": [], 534 | "spaceLength": 10, 535 | "stack": false, 536 | "steppedLine": false, 537 | "targets": [ 538 | { 539 | "expr": "irate(dex_block_io_write_bytes_total{job=~\"$job\",container_name=~\"$container_name\"}[5m])", 540 | "interval": "", 541 | "legendFormat": "{{container_name}}", 542 | "refId": "A" 543 | } 544 | ], 545 | "thresholds": [], 546 | "timeFrom": null, 547 | "timeRegions": [], 548 | "timeShift": null, 549 | "title": "Block I/O write", 550 | "tooltip": { 551 | "shared": true, 552 | "sort": 0, 553 | "value_type": "individual" 554 | }, 555 | "type": "graph", 556 | "xaxis": { 557 | "buckets": null, 558 | "mode": "time", 559 | "name": null, 560 | "show": true, 561 | "values": [] 562 | }, 563 | "yaxes": [ 564 | { 565 | "format": "Bps", 566 | "label": null, 567 | "logBase": 1, 568 | "max": null, 569 | "min": null, 570 | "show": true 571 | }, 572 | { 573 | "format": "short", 574 | "label": null, 575 | "logBase": 1, 576 | "max": null, 577 | "min": null, 578 | "show": true 579 | } 580 | ], 581 | "yaxis": { 582 | "align": false, 583 | "alignLevel": null 584 | } 585 | }, 586 | { 587 | "aliasColors": {}, 588 | "bars": false, 589 | "dashLength": 10, 590 | "dashes": false, 591 | "datasource": "${DS_PROMETHEUS}", 592 | "fieldConfig": { 593 | "defaults": { 594 | "custom": {}, 595 | "links": [] 596 | }, 597 | "overrides": [] 598 | }, 599 | "fill": 1, 600 | "fillGradient": 1, 601 | "gridPos": { 602 | "h": 8, 603 | "w": 12, 604 | "x": 0, 605 | "y": 19 606 | }, 607 | "hiddenSeries": false, 608 | "id": 6, 609 | "legend": { 610 | "alignAsTable": false, 611 | "avg": false, 612 | "current": false, 613 | "max": false, 614 | "min": false, 615 | "rightSide": false, 616 | "show": true, 617 | "total": false, 618 | "values": false 619 | }, 620 | "lines": true, 621 | "linewidth": 2, 622 | "nullPointMode": "null as zero", 623 | "options": { 624 | "alertThreshold": true 625 | }, 626 | "percentage": false, 627 | "pluginVersion": "7.3.1", 628 | "pointradius": 2, 629 | "points": false, 630 | "renderer": "flot", 631 | "seriesOverrides": [], 632 | "spaceLength": 10, 633 | "stack": false, 634 | "steppedLine": false, 635 | "targets": [ 636 | { 637 | "expr": "irate(dex_network_rx_bytes_total{job=~\"$job\",container_name=~\"$container_name\"}[5m])", 638 | "hide": false, 639 | "interval": "", 640 | "legendFormat": "{{ container_name }}", 641 | "refId": "A" 642 | } 643 | ], 644 | "thresholds": [], 645 | "timeFrom": null, 646 | "timeRegions": [], 647 | "timeShift": null, 648 | "title": "Network Rx", 649 | "tooltip": { 650 | "shared": false, 651 | "sort": 0, 652 | "value_type": "individual" 653 | }, 654 | "type": "graph", 655 | "xaxis": { 656 | "buckets": null, 657 | "mode": "time", 658 | "name": null, 659 | "show": true, 660 | "values": [] 661 | }, 662 | "yaxes": [ 663 | { 664 | "format": "Bps", 665 | "label": null, 666 | "logBase": 1, 667 | "max": null, 668 | "min": null, 669 | "show": true 670 | }, 671 | { 672 | "format": "short", 673 | "label": null, 674 | "logBase": 1, 675 | "max": null, 676 | "min": null, 677 | "show": true 678 | } 679 | ], 680 | "yaxis": { 681 | "align": false, 682 | "alignLevel": null 683 | } 684 | }, 685 | { 686 | "aliasColors": {}, 687 | "bars": false, 688 | "dashLength": 10, 689 | "dashes": false, 690 | "datasource": "${DS_PROMETHEUS}", 691 | "fieldConfig": { 692 | "defaults": { 693 | "custom": {}, 694 | "links": [] 695 | }, 696 | "overrides": [] 697 | }, 698 | "fill": 1, 699 | "fillGradient": 0, 700 | "gridPos": { 701 | "h": 8, 702 | "w": 12, 703 | "x": 12, 704 | "y": 19 705 | }, 706 | "hiddenSeries": false, 707 | "id": 14, 708 | "legend": { 709 | "avg": false, 710 | "current": false, 711 | "max": false, 712 | "min": false, 713 | "show": true, 714 | "total": false, 715 | "values": false 716 | }, 717 | "lines": true, 718 | "linewidth": 1, 719 | "nullPointMode": "null as zero", 720 | "options": { 721 | "alertThreshold": true 722 | }, 723 | "percentage": false, 724 | "pluginVersion": "7.3.1", 725 | "pointradius": 2, 726 | "points": false, 727 | "renderer": "flot", 728 | "seriesOverrides": [], 729 | "spaceLength": 10, 730 | "stack": false, 731 | "steppedLine": false, 732 | "targets": [ 733 | { 734 | "expr": "irate(dex_block_io_read_bytes_total{job=~\"$job\",container_name=~\"$container_name\"}[5m])", 735 | "interval": "", 736 | "legendFormat": "{{container_name}}", 737 | "refId": "A" 738 | } 739 | ], 740 | "thresholds": [], 741 | "timeFrom": null, 742 | "timeRegions": [], 743 | "timeShift": null, 744 | "title": "Block I/O read", 745 | "tooltip": { 746 | "shared": true, 747 | "sort": 0, 748 | "value_type": "individual" 749 | }, 750 | "type": "graph", 751 | "xaxis": { 752 | "buckets": null, 753 | "mode": "time", 754 | "name": null, 755 | "show": true, 756 | "values": [] 757 | }, 758 | "yaxes": [ 759 | { 760 | "format": "Bps", 761 | "label": null, 762 | "logBase": 1, 763 | "max": null, 764 | "min": null, 765 | "show": true 766 | }, 767 | { 768 | "format": "short", 769 | "label": null, 770 | "logBase": 1, 771 | "max": null, 772 | "min": null, 773 | "show": true 774 | } 775 | ], 776 | "yaxis": { 777 | "align": false, 778 | "alignLevel": null 779 | } 780 | }, 781 | { 782 | "aliasColors": {}, 783 | "bars": false, 784 | "dashLength": 10, 785 | "dashes": false, 786 | "datasource": "${DS_PROMETHEUS}", 787 | "fieldConfig": { 788 | "defaults": { 789 | "custom": {}, 790 | "links": [] 791 | }, 792 | "overrides": [] 793 | }, 794 | "fill": 1, 795 | "fillGradient": 1, 796 | "gridPos": { 797 | "h": 8, 798 | "w": 12, 799 | "x": 0, 800 | "y": 27 801 | }, 802 | "hiddenSeries": false, 803 | "id": 7, 804 | "legend": { 805 | "avg": false, 806 | "current": false, 807 | "max": false, 808 | "min": false, 809 | "show": true, 810 | "total": false, 811 | "values": false 812 | }, 813 | "lines": true, 814 | "linewidth": 2, 815 | "nullPointMode": "null as zero", 816 | "options": { 817 | "alertThreshold": true 818 | }, 819 | "percentage": false, 820 | "pluginVersion": "7.3.1", 821 | "pointradius": 2, 822 | "points": false, 823 | "renderer": "flot", 824 | "seriesOverrides": [], 825 | "spaceLength": 10, 826 | "stack": false, 827 | "steppedLine": false, 828 | "targets": [ 829 | { 830 | "expr": "irate(dex_network_tx_bytes_total{job=~\"$job\",container_name=~\"$container_name\"}[5m])", 831 | "interval": "", 832 | "legendFormat": " {{ container_name }}", 833 | "refId": "A" 834 | } 835 | ], 836 | "thresholds": [], 837 | "timeFrom": null, 838 | "timeRegions": [], 839 | "timeShift": null, 840 | "title": "Network Tx", 841 | "tooltip": { 842 | "shared": false, 843 | "sort": 2, 844 | "value_type": "individual" 845 | }, 846 | "type": "graph", 847 | "xaxis": { 848 | "buckets": null, 849 | "mode": "time", 850 | "name": null, 851 | "show": true, 852 | "values": [] 853 | }, 854 | "yaxes": [ 855 | { 856 | "format": "Bps", 857 | "label": null, 858 | "logBase": 1, 859 | "max": null, 860 | "min": null, 861 | "show": true 862 | }, 863 | { 864 | "format": "short", 865 | "label": null, 866 | "logBase": 1, 867 | "max": null, 868 | "min": null, 869 | "show": true 870 | } 871 | ], 872 | "yaxis": { 873 | "align": false, 874 | "alignLevel": null 875 | } 876 | } 877 | ], 878 | "refresh": false, 879 | "schemaVersion": 26, 880 | "style": "dark", 881 | "tags": [], 882 | "templating": { 883 | "list": [ 884 | { 885 | "allValue": null, 886 | "current": {}, 887 | "datasource": "${DS_PROMETHEUS}", 888 | "definition": "label_values(container_name)", 889 | "error": null, 890 | "hide": 0, 891 | "includeAll": true, 892 | "label": "Container", 893 | "multi": false, 894 | "name": "container_name", 895 | "options": [], 896 | "query": "label_values(container_name)", 897 | "refresh": 1, 898 | "regex": "", 899 | "skipUrlSync": false, 900 | "sort": 1, 901 | "tagValuesQuery": "", 902 | "tags": [], 903 | "tagsQuery": "", 904 | "type": "query", 905 | "useTags": false 906 | }, 907 | { 908 | "allValue": null, 909 | "current": {}, 910 | "datasource": "${DS_PROMETHEUS}", 911 | "definition": "label_values(dex_container_running, job)", 912 | "error": null, 913 | "hide": 0, 914 | "includeAll": true, 915 | "label": "Job", 916 | "multi": false, 917 | "name": "job", 918 | "options": [], 919 | "query": "label_values(dex_container_running, job)", 920 | "refresh": 1, 921 | "regex": "", 922 | "skipUrlSync": false, 923 | "sort": 1, 924 | "tagValuesQuery": "", 925 | "tags": [], 926 | "tagsQuery": "", 927 | "type": "query", 928 | "useTags": false 929 | } 930 | ] 931 | }, 932 | "time": { 933 | "from": "now-24h", 934 | "to": "now" 935 | }, 936 | "timepicker": { 937 | "refresh_intervals": [ 938 | "5s", 939 | "10s", 940 | "30s", 941 | "1m", 942 | "5m", 943 | "15m", 944 | "30m", 945 | "1h", 946 | "2h", 947 | "1d" 948 | ] 949 | }, 950 | "timezone": "", 951 | "title": "Docker", 952 | "uid": "pp-hyrkRz", 953 | "version": 3 954 | } 955 | --------------------------------------------------------------------------------