pax_global_header00006660000000000000000000000064147437200510014515gustar00rootroot0000000000000052 comment=1c1bd4704bfa7254e898025907a7582c6df284e1 prometheus-smokeping-prober-0.9.0/000077500000000000000000000000001474372005100171775ustar00rootroot00000000000000prometheus-smokeping-prober-0.9.0/.circleci/000077500000000000000000000000001474372005100210325ustar00rootroot00000000000000prometheus-smokeping-prober-0.9.0/.circleci/config.yml000066400000000000000000000030211474372005100230160ustar00rootroot00000000000000--- version: 2.1 orbs: prometheus: prometheus/prometheus@0.17.1 executors: # This must match .promu.yml. golang: docker: - image: cimg/go:1.23 jobs: test: executor: golang steps: - checkout - run: go mod download - run: make promu - run: make style lint unused test build - run: rm -v smokeping_prober workflows: version: 2 smokeping_prober: jobs: - test: filters: tags: only: /.*/ - prometheus/build: name: build parallelism: 3 promu_opts: "-p linux/amd64 -p windows/amd64 -p linux/arm64 -p darwin/amd64 -p darwin/arm64 -p linux/386" filters: tags: ignore: /^v[0-9]+(\.[0-9]+){2}(-.+|[^-.]*)$/ branches: ignore: /^(master|release-.*|.*build-all.*)$/ - prometheus/build: name: build_all parallelism: 3 filters: branches: only: /^(master|release-.*|.*build-all.*)$/ tags: only: /^v[0-9]+(\.[0-9]+){2}(-.+|[^-.]*)$/ - prometheus/publish_master: docker_hub_organization: superque quay_io_organization: superq requires: - test - build_all filters: branches: only: master - prometheus/publish_release: docker_hub_organization: superque quay_io_organization: superq requires: - test - build_all filters: tags: only: /^v.*/ branches: ignore: /.*/ prometheus-smokeping-prober-0.9.0/.github/000077500000000000000000000000001474372005100205375ustar00rootroot00000000000000prometheus-smokeping-prober-0.9.0/.github/dependabot.yml000066400000000000000000000001561474372005100233710ustar00rootroot00000000000000version: 2 updates: - package-ecosystem: "gomod" directory: "/" schedule: interval: "monthly" prometheus-smokeping-prober-0.9.0/.github/workflows/000077500000000000000000000000001474372005100225745ustar00rootroot00000000000000prometheus-smokeping-prober-0.9.0/.github/workflows/container_description.yml000066400000000000000000000044671474372005100277170ustar00rootroot00000000000000--- name: Push README to Docker Hub on: push: paths: - "README.md" - "README-containers.md" - ".github/workflows/container_description.yml" branches: [ main, master ] permissions: contents: read jobs: PushDockerHubReadme: runs-on: ubuntu-latest name: Push README to Docker Hub if: github.repository_owner == 'prometheus' || github.repository_owner == 'prometheus-community' # Don't run this workflow on forks. steps: - name: git checkout uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Set docker hub repo name run: echo "DOCKER_REPO_NAME=$(make docker-repo-name)" >> $GITHUB_ENV - name: Push README to Dockerhub uses: christian-korneck/update-container-description-action@d36005551adeaba9698d8d67a296bd16fa91f8e8 # v1 env: DOCKER_USER: ${{ secrets.DOCKER_HUB_LOGIN }} DOCKER_PASS: ${{ secrets.DOCKER_HUB_PASSWORD }} with: destination_container_repo: ${{ env.DOCKER_REPO_NAME }} provider: dockerhub short_description: ${{ env.DOCKER_REPO_NAME }} # Empty string results in README-containers.md being pushed if it # exists. Otherwise, README.md is pushed. readme_file: '' PushQuayIoReadme: runs-on: ubuntu-latest name: Push README to quay.io if: github.repository_owner == 'prometheus' || github.repository_owner == 'prometheus-community' # Don't run this workflow on forks. steps: - name: git checkout uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Set quay.io org name run: echo "DOCKER_REPO=$(echo quay.io/${GITHUB_REPOSITORY_OWNER} | tr -d '-')" >> $GITHUB_ENV - name: Set quay.io repo name run: echo "DOCKER_REPO_NAME=$(make docker-repo-name)" >> $GITHUB_ENV - name: Push README to quay.io uses: christian-korneck/update-container-description-action@d36005551adeaba9698d8d67a296bd16fa91f8e8 # v1 env: DOCKER_APIKEY: ${{ secrets.QUAY_IO_API_TOKEN }} with: destination_container_repo: ${{ env.DOCKER_REPO_NAME }} provider: quay # Empty string results in README-containers.md being pushed if it # exists. Otherwise, README.md is pushed. readme_file: '' prometheus-smokeping-prober-0.9.0/.github/workflows/golangci-lint.yml000066400000000000000000000023451474372005100260520ustar00rootroot00000000000000--- # This action is synced from https://github.com/prometheus/prometheus name: golangci-lint on: push: paths: - "go.sum" - "go.mod" - "**.go" - "scripts/errcheck_excludes.txt" - ".github/workflows/golangci-lint.yml" - ".golangci.yml" pull_request: permissions: # added using https://github.com/step-security/secure-repo contents: read jobs: golangci: permissions: contents: read # for actions/checkout to fetch code pull-requests: read # for golangci/golangci-lint-action to fetch pull requests name: lint runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Install Go uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0 with: go-version: 1.23.x - name: Install snmp_exporter/generator dependencies run: sudo apt-get update && sudo apt-get -y install libsnmp-dev if: github.repository == 'prometheus/snmp_exporter' - name: Lint uses: golangci/golangci-lint-action@971e284b6050e8a5849b72094c50ab08da042db8 # v6.1.1 with: args: --verbose version: v1.63.4 prometheus-smokeping-prober-0.9.0/.gitignore000066400000000000000000000000751474372005100211710ustar00rootroot00000000000000smokeping_prober /.build /.deps /.release /.tarballs /vendor prometheus-smokeping-prober-0.9.0/.golangci.yml000066400000000000000000000007711474372005100215700ustar00rootroot00000000000000linters: enable: - misspell - revive - sloglint issues: exclude-rules: - path: _test.go linters: - errcheck linters-settings: errcheck: exclude-functions: # Used in HTTP handlers, any error is handled by the server itself. - (net/http.ResponseWriter).Write revive: rules: # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#unused-parameter - name: unused-parameter severity: warning disabled: true prometheus-smokeping-prober-0.9.0/.promu.yml000066400000000000000000000010041474372005100211350ustar00rootroot00000000000000go: # This must match .circle/config.yml. version: 1.23 repository: path: github.com/superq/smokeping_prober build: ldflags: | -X github.com/prometheus/common/version.Version={{.Version}} -X github.com/prometheus/common/version.Revision={{.Revision}} -X github.com/prometheus/common/version.Branch={{.Branch}} -X github.com/prometheus/common/version.BuildUser={{user}}@{{host}} -X github.com/prometheus/common/version.BuildDate={{date "20060102-15:04:05"}} tarball: files: - LICENSE prometheus-smokeping-prober-0.9.0/.yamllint000066400000000000000000000007171474372005100210360ustar00rootroot00000000000000--- extends: default ignore: | **/node_modules rules: braces: max-spaces-inside: 1 level: error brackets: max-spaces-inside: 1 level: error commas: disable comments: disable comments-indentation: disable document-start: disable indentation: spaces: consistent indent-sequences: consistent key-duplicates: ignore: | config/testdata/section_key_dup.bad.yml line-length: disable truthy: check-keys: false prometheus-smokeping-prober-0.9.0/CHANGELOG.md000066400000000000000000000034711474372005100210150ustar00rootroot00000000000000## master / unreleased * [CHANGE] * [FEATURE] * [ENHANCEMENT] * [BUGFIX] ## 0.9.0 / 2025-01-21 Note that this version now uses a BPF filter in privileged mode to improve perforamnce. * [CHANGE] Update logging to Go slog library #173 * [ENHANCEMENT] Improve performance #178 ## 0.8.1 / 2024-04-02 * [BUGFIX] Fix no unlock upon error #148 ## 0.8.0 / 2024-04-02 * [FEATURE] Support runtime config reload #121 ## 0.7.3 / 2024-03-10 * [BUGFIX] Fix incorrect label setting #141 #142 ## 0.7.2 / 2024-03-09 * [BUGFIX] Fix config_last_reload_successful description #127 ## 0.7.1 / 2023-07-12 * [BUGFIX] Ignore network read timeout errors #115 ## 0.7.0 / 2023-07-10 * [FEATURE] Make source ip configurable #83 * [FEATURE] Add metrics for send and receive errors #109 * [FEATURE] Enable native histograms #108 ## 0.6.1 / 2022-06-08 * [BUGFIX] Fix RecordRtts memory leak for config file targets #73 ## 0.6.0 / 2022-05-23 * [CHANGE] Exit with a non-zero error code on fatal error #70 * [FEATURE] Add TLS/Auth from exporter-toolkit #69 ## 0.5.2 / 2022-03-17 Re-tag release. ## 0.5.1 / 2022-03-16 * [BUGFIX] Fix histogram buckets #65 ## 0.5.0 / 2021-12-19 * [FEATURE] Support loading targets from a config file #54 ## 0.4.2 / 2021-04-10 * [BUGFIX] Fix sequence number wrapping #53 ## 0.4.1 / 2021-02-02 * [ENHANCEMENT] Smooth out pingers over the interval #48 * [BUGFIX] Fix memory leak #49 ## 0.4.0 / 2021-01-31 * [FEATURE] Add support for duplicate packet detection #47 ## 0.3.1 / 2020-06-04 Rebuild with latest Go / vendoring. ## 0.3.0 / 2019-06-25 * [FEATURE] Enable pprof * [FEATURE] Add TTL monitoring #20 * [ENHANCEMENT] Update to latest upstream go-ping library #20 ## 0.2.0 / 2019-05-14 * [FEATURE] Add example rules file. #12 * [FEATURE] Allow setting buckets from flag. #17 ## 0.1.0 / 2018-11-08 Initial release prometheus-smokeping-prober-0.9.0/Dockerfile000066400000000000000000000004351474372005100211730ustar00rootroot00000000000000ARG ARCH="amd64" ARG OS="linux" FROM quay.io/prometheus/busybox-${OS}-${ARCH}:latest LABEL maintainer="Ben Kochie " ARG ARCH="amd64" ARG OS="linux" COPY .build/${OS}-${ARCH}/smokeping_prober /bin/smokeping_prober EXPOSE 9374 ENTRYPOINT [ "/bin/smokeping_prober" ] prometheus-smokeping-prober-0.9.0/LICENSE000066400000000000000000000261351474372005100202130ustar00rootroot00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. prometheus-smokeping-prober-0.9.0/Makefile000066400000000000000000000013111474372005100206330ustar00rootroot00000000000000# Copyright 2018 The Prometheus Authors # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. DOCKER_ARCHS ?= amd64 armv7 arm64 DOCKER_REPO ?= superque DOCKER_IMAGE_NAME ?= smokeping-prober include Makefile.common prometheus-smokeping-prober-0.9.0/Makefile.common000066400000000000000000000221631474372005100221320ustar00rootroot00000000000000# Copyright 2018 The Prometheus Authors # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # A common Makefile that includes rules to be reused in different prometheus projects. # !!! Open PRs only against the prometheus/prometheus/Makefile.common repository! # Example usage : # Create the main Makefile in the root project directory. # include Makefile.common # customTarget: # @echo ">> Running customTarget" # # Ensure GOBIN is not set during build so that promu is installed to the correct path unexport GOBIN GO ?= go GOFMT ?= $(GO)fmt FIRST_GOPATH := $(firstword $(subst :, ,$(shell $(GO) env GOPATH))) GOOPTS ?= GOHOSTOS ?= $(shell $(GO) env GOHOSTOS) GOHOSTARCH ?= $(shell $(GO) env GOHOSTARCH) GO_VERSION ?= $(shell $(GO) version) GO_VERSION_NUMBER ?= $(word 3, $(GO_VERSION)) PRE_GO_111 ?= $(shell echo $(GO_VERSION_NUMBER) | grep -E 'go1\.(10|[0-9])\.') PROMU := $(FIRST_GOPATH)/bin/promu pkgs = ./... ifeq (arm, $(GOHOSTARCH)) GOHOSTARM ?= $(shell GOARM= $(GO) env GOARM) GO_BUILD_PLATFORM ?= $(GOHOSTOS)-$(GOHOSTARCH)v$(GOHOSTARM) else GO_BUILD_PLATFORM ?= $(GOHOSTOS)-$(GOHOSTARCH) endif GOTEST := $(GO) test GOTEST_DIR := ifneq ($(CIRCLE_JOB),) ifneq ($(shell command -v gotestsum 2> /dev/null),) GOTEST_DIR := test-results GOTEST := gotestsum --junitfile $(GOTEST_DIR)/unit-tests.xml -- endif endif PROMU_VERSION ?= 0.17.0 PROMU_URL := https://github.com/prometheus/promu/releases/download/v$(PROMU_VERSION)/promu-$(PROMU_VERSION).$(GO_BUILD_PLATFORM).tar.gz SKIP_GOLANGCI_LINT := GOLANGCI_LINT := GOLANGCI_LINT_OPTS ?= GOLANGCI_LINT_VERSION ?= v1.63.4 # golangci-lint only supports linux, darwin and windows platforms on i386/amd64/arm64. # windows isn't included here because of the path separator being different. ifeq ($(GOHOSTOS),$(filter $(GOHOSTOS),linux darwin)) ifeq ($(GOHOSTARCH),$(filter $(GOHOSTARCH),amd64 i386 arm64)) # If we're in CI and there is an Actions file, that means the linter # is being run in Actions, so we don't need to run it here. ifneq (,$(SKIP_GOLANGCI_LINT)) GOLANGCI_LINT := else ifeq (,$(CIRCLE_JOB)) GOLANGCI_LINT := $(FIRST_GOPATH)/bin/golangci-lint else ifeq (,$(wildcard .github/workflows/golangci-lint.yml)) GOLANGCI_LINT := $(FIRST_GOPATH)/bin/golangci-lint endif endif endif PREFIX ?= $(shell pwd) BIN_DIR ?= $(shell pwd) DOCKER_IMAGE_TAG ?= $(subst /,-,$(shell git rev-parse --abbrev-ref HEAD)) DOCKERFILE_PATH ?= ./Dockerfile DOCKERBUILD_CONTEXT ?= ./ DOCKER_REPO ?= prom DOCKER_ARCHS ?= amd64 BUILD_DOCKER_ARCHS = $(addprefix common-docker-,$(DOCKER_ARCHS)) PUBLISH_DOCKER_ARCHS = $(addprefix common-docker-publish-,$(DOCKER_ARCHS)) TAG_DOCKER_ARCHS = $(addprefix common-docker-tag-latest-,$(DOCKER_ARCHS)) SANITIZED_DOCKER_IMAGE_TAG := $(subst +,-,$(DOCKER_IMAGE_TAG)) ifeq ($(GOHOSTARCH),amd64) ifeq ($(GOHOSTOS),$(filter $(GOHOSTOS),linux freebsd darwin windows)) # Only supported on amd64 test-flags := -race endif endif # This rule is used to forward a target like "build" to "common-build". This # allows a new "build" target to be defined in a Makefile which includes this # one and override "common-build" without override warnings. %: common-% ; .PHONY: common-all common-all: precheck style check_license lint yamllint unused build test .PHONY: common-style common-style: @echo ">> checking code style" @fmtRes=$$($(GOFMT) -d $$(find . -path ./vendor -prune -o -name '*.go' -print)); \ if [ -n "$${fmtRes}" ]; then \ echo "gofmt checking failed!"; echo "$${fmtRes}"; echo; \ echo "Please ensure you are using $$($(GO) version) for formatting code."; \ exit 1; \ fi .PHONY: common-check_license common-check_license: @echo ">> checking license header" @licRes=$$(for file in $$(find . -type f -iname '*.go' ! -path './vendor/*') ; do \ awk 'NR<=3' $$file | grep -Eq "(Copyright|generated|GENERATED)" || echo $$file; \ done); \ if [ -n "$${licRes}" ]; then \ echo "license header checking failed:"; echo "$${licRes}"; \ exit 1; \ fi .PHONY: common-deps common-deps: @echo ">> getting dependencies" $(GO) mod download .PHONY: update-go-deps update-go-deps: @echo ">> updating Go dependencies" @for m in $$($(GO) list -mod=readonly -m -f '{{ if and (not .Indirect) (not .Main)}}{{.Path}}{{end}}' all); do \ $(GO) get -d $$m; \ done $(GO) mod tidy .PHONY: common-test-short common-test-short: $(GOTEST_DIR) @echo ">> running short tests" $(GOTEST) -short $(GOOPTS) $(pkgs) .PHONY: common-test common-test: $(GOTEST_DIR) @echo ">> running all tests" $(GOTEST) $(test-flags) $(GOOPTS) $(pkgs) $(GOTEST_DIR): @mkdir -p $@ .PHONY: common-format common-format: @echo ">> formatting code" $(GO) fmt $(pkgs) .PHONY: common-vet common-vet: @echo ">> vetting code" $(GO) vet $(GOOPTS) $(pkgs) .PHONY: common-lint common-lint: $(GOLANGCI_LINT) ifdef GOLANGCI_LINT @echo ">> running golangci-lint" $(GOLANGCI_LINT) run $(GOLANGCI_LINT_OPTS) $(pkgs) endif .PHONY: common-lint-fix common-lint-fix: $(GOLANGCI_LINT) ifdef GOLANGCI_LINT @echo ">> running golangci-lint fix" $(GOLANGCI_LINT) run --fix $(GOLANGCI_LINT_OPTS) $(pkgs) endif .PHONY: common-yamllint common-yamllint: @echo ">> running yamllint on all YAML files in the repository" ifeq (, $(shell command -v yamllint 2> /dev/null)) @echo "yamllint not installed so skipping" else yamllint . endif # For backward-compatibility. .PHONY: common-staticcheck common-staticcheck: lint .PHONY: common-unused common-unused: @echo ">> running check for unused/missing packages in go.mod" $(GO) mod tidy @git diff --exit-code -- go.sum go.mod .PHONY: common-build common-build: promu @echo ">> building binaries" $(PROMU) build --prefix $(PREFIX) $(PROMU_BINARIES) .PHONY: common-tarball common-tarball: promu @echo ">> building release tarball" $(PROMU) tarball --prefix $(PREFIX) $(BIN_DIR) .PHONY: common-docker-repo-name common-docker-repo-name: @echo "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)" .PHONY: common-docker $(BUILD_DOCKER_ARCHS) common-docker: $(BUILD_DOCKER_ARCHS) $(BUILD_DOCKER_ARCHS): common-docker-%: docker build -t "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(SANITIZED_DOCKER_IMAGE_TAG)" \ -f $(DOCKERFILE_PATH) \ --build-arg ARCH="$*" \ --build-arg OS="linux" \ $(DOCKERBUILD_CONTEXT) .PHONY: common-docker-publish $(PUBLISH_DOCKER_ARCHS) common-docker-publish: $(PUBLISH_DOCKER_ARCHS) $(PUBLISH_DOCKER_ARCHS): common-docker-publish-%: docker push "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(SANITIZED_DOCKER_IMAGE_TAG)" DOCKER_MAJOR_VERSION_TAG = $(firstword $(subst ., ,$(shell cat VERSION))) .PHONY: common-docker-tag-latest $(TAG_DOCKER_ARCHS) common-docker-tag-latest: $(TAG_DOCKER_ARCHS) $(TAG_DOCKER_ARCHS): common-docker-tag-latest-%: docker tag "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(SANITIZED_DOCKER_IMAGE_TAG)" "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:latest" docker tag "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(SANITIZED_DOCKER_IMAGE_TAG)" "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:v$(DOCKER_MAJOR_VERSION_TAG)" .PHONY: common-docker-manifest common-docker-manifest: DOCKER_CLI_EXPERIMENTAL=enabled docker manifest create -a "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME):$(SANITIZED_DOCKER_IMAGE_TAG)" $(foreach ARCH,$(DOCKER_ARCHS),$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$(ARCH):$(SANITIZED_DOCKER_IMAGE_TAG)) DOCKER_CLI_EXPERIMENTAL=enabled docker manifest push "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME):$(SANITIZED_DOCKER_IMAGE_TAG)" .PHONY: promu promu: $(PROMU) $(PROMU): $(eval PROMU_TMP := $(shell mktemp -d)) curl -s -L $(PROMU_URL) | tar -xvzf - -C $(PROMU_TMP) mkdir -p $(FIRST_GOPATH)/bin cp $(PROMU_TMP)/promu-$(PROMU_VERSION).$(GO_BUILD_PLATFORM)/promu $(FIRST_GOPATH)/bin/promu rm -r $(PROMU_TMP) .PHONY: proto proto: @echo ">> generating code from proto files" @./scripts/genproto.sh ifdef GOLANGCI_LINT $(GOLANGCI_LINT): mkdir -p $(FIRST_GOPATH)/bin curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/$(GOLANGCI_LINT_VERSION)/install.sh \ | sed -e '/install -d/d' \ | sh -s -- -b $(FIRST_GOPATH)/bin $(GOLANGCI_LINT_VERSION) endif .PHONY: precheck precheck:: define PRECHECK_COMMAND_template = precheck:: $(1)_precheck PRECHECK_COMMAND_$(1) ?= $(1) $$(strip $$(PRECHECK_OPTIONS_$(1))) .PHONY: $(1)_precheck $(1)_precheck: @if ! $$(PRECHECK_COMMAND_$(1)) 1>/dev/null 2>&1; then \ echo "Execution of '$$(PRECHECK_COMMAND_$(1))' command failed. Is $(1) installed?"; \ exit 1; \ fi endef govulncheck: install-govulncheck govulncheck ./... install-govulncheck: command -v govulncheck > /dev/null || go install golang.org/x/vuln/cmd/govulncheck@latest prometheus-smokeping-prober-0.9.0/README.md000066400000000000000000000113501474372005100204560ustar00rootroot00000000000000# smokeping_prober [![CircleCI](https://circleci.com/gh/SuperQ/smokeping_prober/tree/master.svg?style=svg)](https://circleci.com/gh/SuperQ/smokeping_prober/tree/master) [![Docker Repository on Quay](https://quay.io/repository/superq/smokeping-prober/status "Docker Repository on Quay")](https://quay.io/repository/superq/smokeping-prober) Prometheus style "smokeping" prober. ![Example Graph](example-graph.png) ## Overview This prober sends a series of ICMP (or UDP) pings to a target and records the responses in Prometheus histogram metrics. ``` usage: smokeping_prober [] [...] Flags: -h, --help Show context-sensitive help (also try --help-long and --help-man). --config.file=CONFIG.FILE Optional smokeping_prober configuration yaml file. --web.telemetry-path="/metrics" Path under which to expose metrics. --web.systemd-socket Use systemd socket activation listeners instead of port listeners (Linux only). --web.listen-address=:9374 ... Addresses on which to expose metrics and web interface. Repeatable for multiple addresses. --web.config.file="" [EXPERIMENTAL] Path to configuration file that can enable TLS or authentication. --buckets="5e-05,0.0001,0.0002,0.0004,0.0008,0.0016,0.0032,0.0064,0.0128,0.0256,0.0512,0.1024,0.2048,0.4096,0.8192,1.6384,3.2768,6.5536,13.1072,26.2144" A comma delimited list of buckets to use -i, --ping.interval=1s Ping interval duration --privileged Run in privileged ICMP mode -s, --ping.size=56 Ping packet size in bytes --log.level=info Only log messages with the given severity or above. One of: [debug, info, warn, error] --log.format=logfmt Output format of log messages. One of: [logfmt, json] --version Show application version. Args: [] List of hosts to ping ``` ## Configuration The prober can take a list of targets and parameters from the command line or from a yaml config file. Example config: ```yaml --- targets: - hosts: - host1 - host2 interval: 1s # Duration, Default 1s. network: ip # One of ip, ip4, ip6. Default: ip (automatic IPv4/IPv6) protocol: icmp # One of icmp, udp. Default: icmp (Requires privileged operation) size: 56 # Packet data size in bytes. Default 56 (Range: 24 - 65535) source: 127.0.1.1 # Souce IP address to use. Default: None (automatic selection) ``` In each host group the `interval`, `network`, and `protocol` are optional. The interval Duration is in [Go time.ParseDuration()](https://golang.org/pkg/time/#ParseDuration) syntax. The config is read on startup, and can be reloaded with the SIGHUP signal, or with an HTTP POST to the URI path `/-/reload`. ## Building and running Requires Go >= 1.22 ```console go install github.com/superq/smokeping_prober@latest sudo setcap cap_net_raw=+ep ${GOPATH}/bin/smokeping_prober ``` On multi-cpu systems it is typically more efficient to limit the prober to one CPU in order to reduce the number of cross-cpu context switches and packet copies from the kernel to the prober. This can be done with the `GOMAXPROCS` environment variable, or by using container (cgroup) limits. ```console export GOMAXPROCS=1 ./smokeping_prober ``` ## Docker ```bash docker run \ -p 9374:9374 \ --privileged \ --env GOMAXPROCS=1 \ quay.io/superq/smokeping-prober:latest \ some-ping-target.example.com ``` ## Metrics Metric Name | Type | Description ----------------------------------------|------------|------------------------------------------- smokeping\_requests\_total | Counter | Counter of pings sent. smokeping\_response\_duration\_seconds | Histogram | Ping response duration. smokeping\_response\_ttl | Gauge | The last response Time To Live (TTL). smokeping\_response\_duplicates\_total | Counter | The number of duplicated response packets. smokeping\_receive\_errors\_total | Counter | The number of errors when Pinger attempts to receive packets. smokeping\_send\_errors\_total | Counter | The number of errors when Pinger attempts to send packets. ### TLS and basic authentication The Smokeping Prober supports TLS and basic authentication. To use TLS and/or basic authentication, you need to pass a configuration file using the `--web.config.file` parameter. The format of the file is described [in the exporter-toolkit repository](https://github.com/prometheus/exporter-toolkit/blob/master/docs/web-configuration.md). ### Health check A health check can be requested in the URI path `/-/healthy`. prometheus-smokeping-prober-0.9.0/VERSION000066400000000000000000000000061474372005100202430ustar00rootroot000000000000000.9.0 prometheus-smokeping-prober-0.9.0/collector.go000066400000000000000000000126031474372005100215160ustar00rootroot00000000000000// Copyright 2018 Ben Kochie // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package main import ( "net" probing "github.com/prometheus-community/pro-bing" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" ) const ( namespace = "smokeping" ) var ( labelNames = []string{"ip", "host", "source"} pingResponseTTL = promauto.NewGaugeVec( prometheus.GaugeOpts{ Namespace: namespace, Name: "response_ttl", Help: "The last response Time To Live (TTL).", }, labelNames, ) pingResponseDuplicates = promauto.NewCounterVec( prometheus.CounterOpts{ Namespace: namespace, Name: "response_duplicates_total", Help: "The number of duplicated response packets.", }, labelNames, ) pingRecvErrors = promauto.NewCounter( prometheus.CounterOpts{ Namespace: namespace, Name: "receive_errors_total", Help: "The number of errors when Pinger attempts to receive packets.", }, ) pingSendErrors = promauto.NewCounterVec( prometheus.CounterOpts{ Namespace: namespace, Name: "send_errors_total", Help: "The number of errors when Pinger attempts to send packets.", }, labelNames, ) ) func newPingResponseHistogram(buckets []float64, factor float64) *prometheus.HistogramVec { return prometheus.NewHistogramVec( prometheus.HistogramOpts{ Namespace: namespace, Name: "response_duration_seconds", Help: "A histogram of latencies for ping responses.", Buckets: buckets, NativeHistogramBucketFactor: factor, }, labelNames, ) } // SmokepingCollector collects metrics from the pinger. type SmokepingCollector struct { pingers *[]*probing.Pinger requestsSent *prometheus.Desc } func NewSmokepingCollector(pingers []*probing.Pinger, pingResponseSeconds prometheus.HistogramVec) *SmokepingCollector { instance := SmokepingCollector{ requestsSent: prometheus.NewDesc( prometheus.BuildFQName(namespace, "", "requests_total"), "Number of ping requests sent", labelNames, nil, ), } instance.updatePingers(pingers, pingResponseSeconds) return &instance } func (s *SmokepingCollector) updatePingers(pingers []*probing.Pinger, pingResponseSeconds prometheus.HistogramVec) { pingResponseDuplicates.Reset() pingResponseSeconds.Reset() pingResponseTTL.Reset() pingSendErrors.Reset() for _, pinger := range pingers { // Init all metrics to 0s. ipAddr := pinger.IPAddr().String() host := pinger.Addr() source := pinger.Source pingResponseDuplicates.WithLabelValues(ipAddr, host, source) pingResponseSeconds.WithLabelValues(ipAddr, host, source) pingResponseTTL.WithLabelValues(ipAddr, host, source) pingSendErrors.WithLabelValues(ipAddr, host, source) // Setup handler functions. pinger.OnRecv = func(pkt *probing.Packet) { pingResponseSeconds.WithLabelValues(pkt.IPAddr.String(), host, source).Observe(pkt.Rtt.Seconds()) pingResponseTTL.WithLabelValues(pkt.IPAddr.String(), host, source).Set(float64(pkt.TTL)) logger.Debug("Echo reply", "ip_addr", pkt.IPAddr, "bytes_received", pkt.Nbytes, "icmp_seq", pkt.Seq, "time", pkt.Rtt, "ttl", pkt.TTL) } pinger.OnDuplicateRecv = func(pkt *probing.Packet) { pingResponseDuplicates.WithLabelValues(pkt.IPAddr.String(), host, source).Inc() logger.Debug("Echo reply (DUP!)", "ip_addr", pkt.IPAddr, "bytes_received", pkt.Nbytes, "icmp_seq", pkt.Seq, "time", pkt.Rtt, "ttl", pkt.TTL) } pinger.OnFinish = func(stats *probing.Statistics) { logger.Debug("Ping statistics", "addr", stats.Addr, "packets_sent", stats.PacketsSent, "packets_received", stats.PacketsRecv, "packet_loss_percent", stats.PacketLoss, "min_rtt", stats.MinRtt, "avg_rtt", stats.AvgRtt, "max_rtt", stats.MaxRtt, "stddev_rtt", stats.StdDevRtt) } pinger.OnRecvError = func(err error) { if neterr, ok := err.(*net.OpError); ok { if neterr.Timeout() { // Ignore read timeout errors, these are handled by the pinger. return } } pingRecvErrors.Inc() // TODO: @tjhop -- should this be logged at error level? logger.Debug("Error receiving packet", "error", err) } pinger.OnSendError = func(pkt *probing.Packet, err error) { pingSendErrors.WithLabelValues(pkt.IPAddr.String(), host, source).Inc() logger.Debug("Error sending packet", "ip_addr", pkt.IPAddr, "bytes_received", pkt.Nbytes, "icmp_seq", pkt.Seq, "time", pkt.Rtt, "ttl", pkt.TTL, "error", err) } } s.pingers = &pingers } func (s *SmokepingCollector) Describe(ch chan<- *prometheus.Desc) { ch <- s.requestsSent } func (s *SmokepingCollector) Collect(ch chan<- prometheus.Metric) { for _, pinger := range *s.pingers { stats := pinger.Statistics() ch <- prometheus.MustNewConstMetric( s.requestsSent, prometheus.CounterValue, float64(stats.PacketsSent), stats.IPAddr.String(), stats.Addr, pinger.Source, ) } } prometheus-smokeping-prober-0.9.0/config/000077500000000000000000000000001474372005100204445ustar00rootroot00000000000000prometheus-smokeping-prober-0.9.0/config/config.go000066400000000000000000000056611474372005100222500ustar00rootroot00000000000000// Copyright 2021 Ben Kochie // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package config import ( "fmt" "os" "sync" "time" yaml "gopkg.in/yaml.v2" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" ) const namespace = "smokeping_prober" var ( configReloadSuccess = promauto.NewGauge(prometheus.GaugeOpts{ Namespace: namespace, Name: "config_last_reload_successful", Help: "smokeping_prober config loaded successfully.", }) configReloadSeconds = promauto.NewGauge(prometheus.GaugeOpts{ Namespace: namespace, Name: "config_last_reload_success_timestamp_seconds", Help: "Timestamp of the last successful configuration reload.", }) // DefaultTargetGroup sets the default configuration for the TargetGroup DefaultTargetGroup = TargetGroup{ Interval: time.Second, Network: "ip", Protocol: "icmp", Size: 56, } ) type Config struct { Targets []TargetGroup `yaml:"targets"` } type SafeConfig struct { sync.RWMutex C *Config } func (sc *SafeConfig) ReloadConfig(confFile string) (err error) { var c = &Config{} defer func() { if err != nil { configReloadSuccess.Set(0) } else { configReloadSuccess.Set(1) configReloadSeconds.SetToCurrentTime() } }() yamlReader, err := os.Open(confFile) if err != nil { return fmt.Errorf("error reading config file: %w", err) } defer yamlReader.Close() decoder := yaml.NewDecoder(yamlReader) if err = decoder.Decode(c); err != nil { return fmt.Errorf("error parsing config file: %w", err) } sc.Lock() sc.C = c sc.Unlock() return nil } type TargetGroup struct { Hosts []string `yaml:"hosts"` Interval time.Duration `yaml:"interval,omitempty"` Network string `yaml:"network,omitempty"` Protocol string `yaml:"protocol,omitempty"` Size int `yaml:"size,omitempty"` Source string `yaml:"source,omitempty"` // TODO: Needs work to fix MetricFamily consistency. // Labels map[string]string `yaml:"labels,omitempty"` } // UnmarshalYAML implements the yaml.Unmarshaler interface. func (s *Config) UnmarshalYAML(unmarshal func(interface{}) error) error { type plain Config return unmarshal((*plain)(s)) } // UnmarshalYAML implements the yaml.Unmarshaler interface. func (s *TargetGroup) UnmarshalYAML(unmarshal func(interface{}) error) error { *s = DefaultTargetGroup type plain TargetGroup return unmarshal((*plain)(s)) } prometheus-smokeping-prober-0.9.0/dashboard.json000066400000000000000000000310631474372005100220240ustar00rootroot00000000000000{ "__inputs": [ { "name": "DS_PROMETHEUS", "label": "Prometheus", "description": "", "type": "datasource", "pluginId": "prometheus", "pluginName": "Prometheus" } ], "__elements": {}, "__requires": [ { "type": "grafana", "id": "grafana", "name": "Grafana", "version": "9.2.4" }, { "type": "panel", "id": "heatmap", "name": "Heatmap", "version": "" }, { "type": "datasource", "id": "prometheus", "name": "Prometheus", "version": "1.0.0" }, { "type": "panel", "id": "timeseries", "name": "Time series", "version": "" } ], "annotations": { "list": [ { "builtIn": 1, "datasource": { "type": "datasource", "uid": "grafana" }, "enable": true, "hide": true, "iconColor": "rgba(0, 211, 255, 1)", "name": "Annotations & Alerts", "target": { "limit": 100, "matchAny": false, "tags": [], "type": "dashboard" }, "type": "dashboard" } ] }, "description": "Smoke Ping using https://github.com/SuperQ/smokeping_prober\r\nwith \r\nlatency heatmap\r\nlatency graph\r\npacket loss gragh\r\n", "editable": true, "fiscalYearStartMonth": 0, "gnetId": 11335, "graphTooltip": 0, "id": null, "links": [], "liveNow": false, "panels": [ { "collapsed": false, "gridPos": { "h": 1, "w": 24, "x": 0, "y": 0 }, "id": 7, "panels": [], "repeat": "target", "repeatDirection": "h", "title": "$target", "type": "row" }, { "cards": {}, "color": { "cardColor": "#FF9830", "colorScale": "sqrt", "colorScheme": "interpolateOranges", "exponent": 0.5, "mode": "opacity" }, "dataFormat": "tsbuckets", "datasource": { "type": "prometheus", "uid": "${DS_PROMETHEUS}" }, "fieldConfig": { "defaults": { "custom": { "hideFrom": { "legend": false, "tooltip": false, "viz": false }, "scaleDistribution": { "type": "linear" } } }, "overrides": [] }, "gridPos": { "h": 10, "w": 12, "x": 0, "y": 1 }, "heatmap": {}, "hideZeroBuckets": false, "highlightCards": true, "id": 2, "legend": { "show": false }, "links": [], "options": { "calculate": false, "calculation": {}, "cellGap": 2, "cellValues": {}, "color": { "exponent": 0.5, "fill": "#FF9830", "mode": "opacity", "reverse": false, "scale": "exponential", "scheme": "Oranges", "steps": 128 }, "exemplars": { "color": "rgba(255,0,255,0.7)" }, "filterValues": { "le": 1e-9 }, "legend": { "show": false }, "rowsFrame": { "layout": "auto" }, "showValue": "never", "tooltip": { "show": true, "yHistogram": false }, "yAxis": { "axisPlacement": "left", "decimals": 0, "min": "0", "reverse": false, "unit": "s" } }, "pluginVersion": "9.2.4", "reverseYBuckets": false, "targets": [ { "datasource": { "type": "prometheus", "uid": "${DS_PROMETHEUS}" }, "editorMode": "code", "expr": "sum(rate(smokeping_response_duration_seconds_bucket{host=\"${target:raw}\"}[1m])) by (le)", "format": "heatmap", "intervalFactor": 1, "legendFormat": "{{le}}", "range": true, "refId": "A" } ], "title": "Smoke Ping - $target", "tooltip": { "show": true, "showHistogram": false }, "type": "heatmap", "xAxis": { "show": true }, "yAxis": { "decimals": 0, "format": "s", "logBase": 1, "min": "0", "show": true }, "yBucketBound": "auto" }, { "datasource": { "type": "prometheus", "uid": "${DS_PROMETHEUS}" }, "fieldConfig": { "defaults": { "color": { "mode": "palette-classic" }, "custom": { "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "Loss %", "axisPlacement": "auto", "barAlignment": 0, "drawStyle": "line", "fillOpacity": 10, "gradientMode": "none", "hideFrom": { "legend": false, "tooltip": false, "viz": false }, "lineInterpolation": "linear", "lineWidth": 1, "pointSize": 5, "scaleDistribution": { "type": "linear" }, "showPoints": "never", "spanNulls": false, "stacking": { "group": "A", "mode": "none" }, "thresholdsStyle": { "mode": "off" } }, "links": [], "mappings": [], "thresholds": { "mode": "absolute", "steps": [ { "color": "green", "value": null }, { "color": "red", "value": 80 } ] }, "unit": "percentunit" }, "overrides": [ { "matcher": { "id": "byName", "options": "Count" }, "properties": [ { "id": "custom.fillOpacity", "value": 0 }, { "id": "unit", "value": "none" }, { "id": "decimals", "value": 0 }, { "id": "custom.axisLabel", "value": "Loss Packet" }, { "id": "custom.lineStyle", "value": { "dash": [ 10, 10 ], "fill": "dash" } } ] } ] }, "gridPos": { "h": 10, "w": 12, "x": 12, "y": 1 }, "id": 4, "options": { "legend": { "calcs": [ "lastNotNull" ], "displayMode": "table", "placement": "bottom", "showLegend": true }, "tooltip": { "mode": "multi", "sort": "none" } }, "pluginVersion": "9.2.4", "targets": [ { "datasource": { "type": "prometheus", "uid": "${DS_PROMETHEUS}" }, "editorMode": "code", "expr": "(smokeping_requests_total{host=\"${target:raw}\"} - smokeping_response_duration_seconds_count{host=\"${target:raw}\"})/smokeping_requests_total{host=\"${target:raw}\"} ", "legendFormat": "Percentage", "range": true, "refId": "A" }, { "datasource": { "type": "prometheus", "uid": "${DS_PROMETHEUS}" }, "editorMode": "code", "expr": "(smokeping_requests_total{host=\"${target:raw}\"} - smokeping_response_duration_seconds_count{host=\"${target:raw}\"})", "legendFormat": "Count", "range": true, "refId": "B" } ], "title": "Packet Loss - $target", "type": "timeseries" }, { "datasource": { "type": "prometheus", "uid": "${DS_PROMETHEUS}" }, "fieldConfig": { "defaults": { "color": { "mode": "palette-classic" }, "custom": { "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, "drawStyle": "line", "fillOpacity": 10, "gradientMode": "none", "hideFrom": { "legend": false, "tooltip": false, "viz": false }, "lineInterpolation": "linear", "lineWidth": 1, "pointSize": 5, "scaleDistribution": { "type": "linear" }, "showPoints": "never", "spanNulls": false, "stacking": { "group": "A", "mode": "none" }, "thresholdsStyle": { "mode": "off" } }, "links": [], "mappings": [], "thresholds": { "mode": "absolute", "steps": [ { "color": "green", "value": null }, { "color": "red", "value": 80 } ] }, "unit": "s" }, "overrides": [ { "matcher": { "id": "byName", "options": "Count" }, "properties": [ { "id": "custom.fillOpacity", "value": 0 }, { "id": "unit", "value": "none" }, { "id": "decimals", "value": 0 }, { "id": "custom.axisPlacement", "value": "hidden" }, { "id": "custom.axisLabel", "value": "Loss Packet" }, { "id": "custom.lineStyle", "value": { "dash": [ 10, 10 ], "fill": "dash" } } ] } ] }, "gridPos": { "h": 10, "w": 24, "x": 0, "y": 11 }, "id": 5, "options": { "legend": { "calcs": [ "mean", "lastNotNull", "max", "min" ], "displayMode": "table", "placement": "bottom", "showLegend": true }, "tooltip": { "mode": "multi", "sort": "none" } }, "pluginVersion": "9.2.4", "targets": [ { "datasource": { "type": "prometheus", "uid": "${DS_PROMETHEUS}" }, "editorMode": "code", "expr": "smokeping_response_duration_seconds_sum{host=\"${target:raw}\"} / smokeping_response_duration_seconds_count{host=\"${target:raw}\"}", "legendFormat": "{{host}}", "range": true, "refId": "A" } ], "title": "Latency - $target", "type": "timeseries" } ], "refresh": "30s", "schemaVersion": 37, "style": "dark", "tags": [], "templating": { "list": [ { "current": {}, "datasource": { "type": "prometheus", "uid": "${DS_PROMETHEUS}" }, "definition": "label_values(smokeping_requests_total, host)", "hide": 0, "includeAll": true, "multi": false, "name": "target", "options": [], "query": { "query": "label_values(smokeping_requests_total, host)", "refId": "Prometheus-target-Variable-Query" }, "refresh": 1, "regex": "", "skipUrlSync": false, "sort": 1, "tagValuesQuery": "", "tagsQuery": "", "type": "query", "useTags": false } ] }, "time": { "from": "now-1h", "to": "now" }, "timepicker": { "refresh_intervals": [ "5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d" ], "time_options": [ "5m", "15m", "1h", "6h", "12h", "24h", "2d", "7d", "30d" ] }, "timezone": "", "title": "Smokeping", "uid": "i5aRaLaik", "version": 12, "weekStart": "" }prometheus-smokeping-prober-0.9.0/example-graph.png000066400000000000000000002471761474372005100224600ustar00rootroot00000000000000PNG  IHDR!AsBITOtEXtSoftwaregnome-screenshot> IDATxyg&3d{]9TPPGGGmT Z+'QUQ<(r)7,,}'I~2;d]d2|'3REť= {aL0&^q2Ld2L& ?EfV|N(c!))L&S B'2[hQQ51XDR) 4z諯no%0*!acʂSN^~i?^Bȼ>- `% aHB_O=[!Z-7rԨ3n/**3w9!O>Ԋ+23jNLQT] @_K&q(P(!>oͧkԬ<묳 v{yEynnn"|xwgpdǎ077ϳ-Nc׮]LBȹG>4xn&aa*`Νb#˲>w;<ۿo63k4M6qBgi89j$!dM{w]}vԩSsee~L&8]@BEQwO~r [6o`ܸ[}k/So&eŇ<Ͽ+.!d997Ο?vxMvsl߶G C!dժU\s9+Vi7--kk./ƦnM|q1b) !\vȑGymcC#!j߾ŋ]|񏗽LOB>w0Cnm'?)>|ǎ#//o„ u*#]U @0&~7gE}˿.ݮ;w~5H?MQ,6~p O2Yp|2ao) -{ C>7t Z)6f;v,!d` -y~ɷnYdSL~.}jP(?'$.^VsR&BȊ+gyȑ#O?rƑe/|AAg)6.y~ɲenwEEּu]7gμXNE|yp 6t̘1q_'S/$S~k$T~A0!kY66eNG$r X5joԩSoOV<ۋ9tBhߘٜ/BN;K/u' ]LD?Op,\3jrj!@N2Ə9%KK&ee*V ti}j-ZXVZٻcǎNqIfǫ=n)מq x}ko.hJ{]`@>|NVE,ßu9FQݞ.Ne>!7 $.f=@˜ {aL0&^d/ Ƅ cB1!@˜ {aL0&^d/ Ƅ cB1!@ʊ1ayE%\0LSVVV?qVV '?wF7\Wu XژU{n߾aU'SmLXXXk'u1byG+**Nߞ{~s3fqEQ=hiiوF<أF:n5 bwayq\uuhGck֬).)v=k֬iiiJ)2&j,Bt:!dCP<OLBxŗ!['sgBNW= SdL8wޜK? ,zo۶].))RT*ՙgh4z{T44jmkkxSCX ô98!PUTTxB@'`2)ZFqb=v@$"c§zFVBFyw̚9Kl:$!=<&I5b|NN!$^ЅFq] MFU"H45LRīLBH0H$FQj}4RUYV'8Npb @@Պ|(Q!$H‘Hl6K //4RST:t T*k\.멬:.677BJ-l6&'ǜk5W:BE|)`Hu܃Iav:]~cbRB`0W,,X{uuZfb1)o˳JM$nnH@ ()):v{Ie*xDjillRŽW[[ćdZ%ln_{<744lVhGZZZ ZHy6gAC]abz (rJD:ww=BZP[#˃ҜHtdfi}{yNӃD9m!$YTQ^}}FR"Qn8=.4f"2l6[,)9˕j1:]Az=.WAxiiWABOrZZZ,|9if;pL-%>z!/ |Ap{fsa1 ]PR7A81,smmpv|DMM4M;,ǣB?9~wzT é6&lllx )}®-_Dp8JY}\+PuB9 Ƅ cB1!@˜ {aL0&^d/ Ƅ cB1!@bd.fq^wmv]lQQرc5Mu[n& Ì?*˭VQve*b\Դo޾ESP +.\@Q%3g/g/{fs` oz׋?y34hys z' ͛;rE/Jp7xG~1!\ ~=wH$BQC?8k/\H$m3Xr%!d1_YYYSS5?Zmg5L4)fԐCֶcc2 y;vF"BH2\ &l6~e?|8M'_;hgV=={fW~أ [)e̙U#G-p7yse0].ڵk|wkBIO12_|ӵ^{7F E?qgÇ9v&Re/ڽٿ<+駟y@^x?{㋾ҳvѣfsE>җu>_{tr7߼l}Qg  GI$<cY5MӄCx<ކzZCQԑ=-WAptP! st(^ftt:4xM)rby$QN'ϛ ANW2$ֳRT r:]ZJw:]zNz=}D1%BIr9_mw: ǝN!}!$8.a!!Aqmz>˭}A\{XL<"P!IEyZ-'+t BBVt(>DNexpXZ IDAT@ t8Ο^1 S}Hyx<.!.Gw@ӕH>[NMwGN˪ B"^o 0 #B $]AiZz}Oʞx᰼<ǝHԜӃe ED=<27y4uzd_i4^"^)ʋŔ9("/tiʜ 2E 0 uru72l tS)z}RNibNr4!//czT,Ry]LAz<)9z3tXD7"DJHRdVtj.=staA5rqEN Iʂi0Sө7-RrINb`#Òat ȿtA}!AH|&ҧ4c_8s#GpbE1{ gn))HwgnG41uX`0DŽP(csGXxݺuv1ydV !m޴fҙ/ywPM}H'.)-v9[|'V"aP :vyZ-Yfn߾r1\jo|g?ٜ/] ӧ0ի?% g߱ k^q]Lo;fܑ06} y{R ]dΜ=aÆ 6L&Vr p Ou|k> tL@˜ {aL0&^d/ Ƅ cB1!@˜ {aL>Ì?JޮRƎSV6([u^̄jǏWXXz6|/BZIccþ}2ju}}M䱾OSHߌ  4w!C^|8g/~’kڻh2ͺUVⲣIAA`YjPY٭[Ezu9LVDŽ3gͼ暫_55͝F-`L8jԨG~jn6N~tMSMٚ:̸cF,5P(D ̞Ç|?r &NfѢ'Z!dСyW_s{+; pjC=֛o%={q@HҞD׍3w("$WV6hС90 sウ!D#s8 $TWW+*qM7St?OzNF}pp-=ч~0BHmm]gRQQ0LuuǷ ! #4iV}m۶o۶]zjsrM-鋸Yg}믽t:9m֬,**(#'4M=KQ4Mhiwi:4h2t,K,/N+FY^E<Ch4-DRuhRV,F"QgŤYN_3EӴbSfӺ`6.F)1TRًcZ;sK[t772wgnU/4ņ'T~A !ܻy-[{˳sG[QQqEXnwo*frE>ЃLuKKp?YoK-e/^IΙ{1ϧx7_/_}s۳7b!`S`(?x`8ZPVVVT<ىlll,gܹKq Ykذ@y S!*~w '_kXvy`W;aBH"8ϋ풉'b><q=BBHuuW^z6n$@ӴjZIJ,]):`0Xh46е !''eY6Ѕ ikQh,`ld22 .P( dǣZnݺuX2eʂ zF"BZ'(p\8Ov啗x\C+..;g=tO,Z|-Ӯnv'?yݕ]PE99&7@h4&fDa|l"L&S"Hq5Nd2cL")Ge옐(f/_~ŕW\|=675B -_R5t\F6l5/o'?=[ // 2d…nŲt)Aصk%%ńH$p8l<# :>󕔔HxG;u8 H----,/K666Z">L$MMM999FQlynt:% ;NͦѴ.**Tz>_?.;u֖L&;---,Zse5z,f9`h//ZV';|4HiAN+kmm%ܬhrs;khh0999 Ñ򢭭mVkVˉ-Prj{bx`I<'󅅅Ƚ_}U"_jjj8.7Yyxbz%Y9N{*((`YHy>חZ^Ǿ*r8J-v]Rj9d2h2L&ӑ{jy #{Zaaô׻ -*mLFc{y!4%HJyAӔ (rZVV-Zu:)]~ZXSAhnnh4]AͽBzNATԫ 480r\Aϖ-߹\byNӃ^^Vc <H$z]Vno!:+SbEE2SNYV!ÒKuX= Z{G? ,(g.:,w0v谎iFK uXn;RtX= b1RrF2D9s6,vCj̭Khj37.JD<12[W$2 Dx\-cxjb$ɤE[ljDBqQT| ]Z9]sdåocAHJA'ki*i+<3JђRbOK//Ӟ,/>8/]7kOj<O۸x\p1Y3G<[AAqB~|wW<#.:~Y#rrLV̙sFy~k_|%y~_QcΧ(O!oq=/\tS^F5͛<_ ϛS~x'^vO|>/>ܴikdRoS{vzq@HӦM+++=tYg(_` uo8&O|ι ZvO8IpY#vޝL&>8ABZ$$nÏ39a:qn|PaaqpmEy׬>p:6kLeshɤ^J24= zU 9h-IoJT_Fb0YV-=i`3L6e`ЫT*Y`STj^S'jcE׫P(OSZkeYAOQKWY _q*U~r$NcYVb04ce-uʸ)kOQ')4M .:[juG E몣E\W]aEyjuƍ(Etި2i-ړMqf*c,Z-H$S[2!}OHyqOS+bp65 ҃ = |ZL9UONDzʽ1}OI[!=4eZN)z!}202 U2rG0XVEy:]ʦ$rk'h4(O҃X{Ե~Ue dX{5q\ZْC.!AH-ʹuAE%9!7J]:NK4ړlh4dRٟZNQa)4autXZ6-f>02PgnZ-37r|ܴZmZzy֓KQ48R2/BFs_W^WUUQYVs,ly+V?^Kr{[T*˲MO=Tު;܊.pŊw[!do6lhẑFB~#bo_|sΨh,w^ߡC^N=p!  t!pBiz3] ly;wR!k 64455t!p=ZuBN~?i-I&͸ݻvoٲE1euuZpacCcIIysy]<%rk-;lQZSQQi_t3!3X7p4/+x=7llllڶm{goAʺx6[lٌ857b[ <+ð EY@'l(ǘD6[`P\Sx<h`$ BFkW]}u]]taGH$lV ?}~B﫮<uں>Vg޽{67?fLee>H$!lРg>T,zÆ999W^uԟOO.jUUU(ɉbXL,B4iZl4QTX,*,(qH$"%'Iy2D„%HD"aҊ!x<(ʋF-/V^Xňɋ˓#'/F,a.^yh4"uߨ Ayb1bh4'$p8b1Mz)3w{Zߖw"WeBtd8^!-XWTH$HT#diʞ)=iiiyNirwސix>Ћ =;D"Q\UB"!֦X1CPTTqv{<Aa!ceZLN;`0ڪȩ>+=fƍӃ i$inQU^^&JwXAhjj`6;K/HN{MV8pC8s;>gnf&}rvTOr v1tPq\QQQs saÆBxa׷*=E4i"!djzĈҳcf!**LVWW>ѣGŻ8V'9s˳X,s7èV2x৞z# !cQϹw!ZϺ6mSxo8.b1so!ٳg IDAT]g3d`^7iҤs=BO˛&V#>ܯѿzG~{ !mmE?j'gX݃w}sH$~<4.񦦶63O?#H >WSTv{_ӧN.j4͛7/y~]-'Bnj;rssF&ܱ˯644̟qf+).YVk4~Txy`08߈lذaÆ &Iպ\.OD@1rdH$ҢYnJ&y@ }>I>!0&^d/ Ƅ cB1!@˜ {aL0&^d/  PTcǎ)+:p`gSj4qx=ޭ[vBN7n\Ļw[;VTWߺu|Ç:aUlҧ ׿cBżx\k޽Fi֝wZOY^Qp...9k__,U6[cK/H$үo$ҿc[oj6vB~~iӦ}C)].9s#EQ=Y3rmss_"O7mDϷ_{cʕc`~eeeMM̈́ /7gϞ=-W^IΊ~}'ٳ!!䛯O6T1&dfM;w/%߬0af tMhre?|M'_;EiYi29\.gIIizM buXRIFAj"z`0\LAh4au}KB'G9źbR h4Y(׻ܬV xAI376d@V,!P/(ZO1}sG[QQqEX{+z?wW|#G|| **jeY-oŊw_k⳿// ccv׿>{׿W_sFEc{fMuQyeee8n!ҲÑHtkBqq˪kkk8!4=tЖ;е jUUUI'N;2 )nSX,<<{!{GE'_kXvy`;*:3~oذQ(֯VB8?͛B&M4wڽe)ŢcTL"TT:}LX]]}ŕWx6666m۶T*UeeeۢјVոFZd{d'0!d9Z`0L&See%Ƅ MNla1&\nݺuX2eʂ zߚq^N&JJJG3=(o{!!>21clٲeĉ?vҬ !Z%1c3Y'-i]7uv՟#!D`0.c>d2ItÇkft8 \cXnEK&999RQTFcǝ.Kza2t;k{7$ mAڢ֊m U!X ģD+Z+_bk[/D ZNA!HwLfg7@χLf;yg>;X,fYi!Hl6Rd2y^uCtyHݦD^$IXjLy6!<өދg\&uEs6myTZnv"D9%Qeiˋ&EWd2gW׳,Nrl6L&òz.JrBVEݻptYY=鋢ǵE )T/GA͆PJ$IKZ'Q7lNy@w*DhԴbx<.D! e2 @p剢=)}l00L=S'h(JM Mx~yOZa0C`d2_@%˭ ^:e$j3L(q\DcePh,JjD8SB 9o!YW^r}>.)- ԁ$N']c1zm_mWAOaw VE=,A۩-y%%^\ ͦRp D"LF.P$=*Ap].WA!J"(Ibxvy$I_.Bh۶|1aċ~[?Эn-_ƛnT>znj9zh?zU+Ws+w3˟QjsI++WVݠ[ҭ[?@r9nD◿ŋ_>d(Dmڸl6]z$I<Ͽşx|jg̘βhܹs窕+{k@һsBWZf˲l*>۷wﴫVhExkС?'$ "T7ϛ$I;Zy]]]]]04M }LQY#Ldw֐L&$3 0p.sB`N `9! \0' 0p.E͌?h4uѣ;,?"bҤIUUU ;x/,\-I\zСç9Vu s… \+kjo[z~dm2j˞^m6x'f]Kmm;_jn3g]?k֬zջsB;tжdK/iW@͝;?ᦍAg=x׏@įwݡ g͚v;:7*+ك?ݲe5׌~ {٨w7q/y5WGʄ!x? Lf>AB(4ibUǎP&C@ `0u-_p]6\da'IR.!lF}Q{A~1'4˯bɒEQKg|y,WpҤ+.bw8%*IH,A$IބD"$I\n% fá$) vu,|$u&m2X %%R^yyD"q\IIB1Y^O~yq0X,v][X,J h^y qCQjyd2YZZFORlLP<y><! l//XV/Ivd2r1({hT+ ( p hsZmNqey=4Mwu'NlnWAPƀŕX,&IAKt:NwB60`i XAbi :`)ώfju8`fh{Zh4dV.$^tWn$WnFqǏhll'Wn X̕ X! B^zIiͽC@'Dž&!&NXq{ccc0qk 裏<#C^8iң>e5k5s֝w[fΚY;jyrK1ʃ cs9FdE\ѭaZXeV0Catr9F[$IAhrU\$m 1^8N² c'lsܳgIӧrna6׿K.njj5"v^_} xcƨm;'WΞ}re7=:1Ng3ֻv~̝{M? !tСeO?`'>0k֬ypJ:dɒ{;Q]]]]]07O٬_|P%b(,#;,h7L&).0' 0p."0 IDATsB`N `9! \0' 0p}m &MTUUŰKÇ߾};˲+/pbnjj!I}6't8O>l޵kb}WV~?(;{vNx5,X8MGghF}?X g͚v;:7".ҟ=-[EQovG$i0qh4bh4bi4;Z !j[ $I/H-F <5-dAyѨ+ ˲3M$پimJ1Z{ шP{y$IPԺ.O0 78޾'tEFW^F$CC/O)FB=RBㄶhVO#ѨhE^ EQžY:8yQ!IT~K lOk;p eO3EQʣ(*oC Ei]=} }3 A08-r BN=;T3nD0h7Z^)׽SnHޣtKWL=M?"tup P BgaXQA(8i {Zׇ2O=MN; N# ɞrՕ@ޕ[ᾂ+7T|A[/J*#vذl%CVcb8qºu]˦<n}S$QZ&MG^v"B讻ꪫ(Nz}K/fMkv VdHU'啔v`&z88k%IJŮa^!t:/8U]=@QRl['xl愋U^^`BBÆ s+BMu[ <,gꕲ,7|s:)|6SGy%e^!ҧng 7[ @gs‰'*BPccc0qbsBPCC \.'r\nΜ[H6q%,ˮYϿPSS33gt꿊%IѣMh4N:!`EEdFQX_PY9X}h;b ~H$2D]s EiAjˉE-MMM645x\Ng볾57())-l.l/!J%#R}^<aê \ jĉTm9zxn+- efsLpdALJx<:D* V[[//o{MMǬVS^ʲ|htZ'N4xmr9&ͦRp d2١C; Bss3Ivru]NBT*VU VEaH.!KRATQYݱcgwia00UUڜ+*;~јW.(;֭ T~04M'A0-X].۝Scb0D رcKANN++_q;h:_^@j: s'N xKA:tA$)oD8ȈPQQn2uX,wUv8:2l(i":0'Җ-[ )0~CԯXAhiir95$2݀eUЖӞ^ /lhh8WngMqtZNˀ B/9$I]bte2'NA8_:e';w $U7iEb6O8+"c۶~sرcB$IΜyܶCj3f1++BF3fȑ#B , 8ֽ2vnFI;z̘G\'\rU.aFD4J/{z{BGS_9LӴG8ٜP!r'NhVL&]'s}}B}0p.sB`N `9! \0' 0p.Eu!zǎz۷ogYV."u<ϟz/wc/XRuN?(;{vN8i҅?{g@o;d2GY+)5>×]vkk(Ewo׏̫j?>S9\oz}m}_{'Cqc=Ϻ]yL&7lU(j?aϮwۼCڵk(=vnMP(?644 L&u]7nXN-[674fW_Ȳ2Mgf8_}7|k >æ&G$z%^?_fJeMWԕ-+O%Sڕ3:d';?K/KǍɎO71.{?o oL?^1ln9T)2Lm0M8N ^ ÚL&oKd0LR]`Fh4 md(JӀLFlo0l6DT ٬)F&l$i6іa`0'dɬ-0DbpD+8N+ bʬ-O 6tfSAyF]yfQS"je "rh&I`Еg2R9j1a&nJ#I*vg_ɺ}araO3M=ͤiʡ=  'fswКS<٬- ,t:A$Aw(I4;\Xd? h0ԝAP .f25AP< ǕAP^ANNuW-ðT*%rajyXg. `iAw({d4v X ԭ ԀeZl6[(,V^O,YNWa[A(ipsړ +7qr}rasz_i)zr;q B BmD!dzi:?~w<[o,}b֭h)gD _Mϳ+~s߽۷_SN%˗?i&е^+]~ЕW^h?q}2|/^ZqcY;x`ȑ5L =6zhCCC.u-_4 G/8>zW[bX__YYپ}` 5jd:niu!JB8Aٳճfzg2 w$II-((E?x?:ڗv#LOzZ<Qe=m ~+WZZ²6z)yv r ð Ţ> 8p8dY9!P,xpp8'9!IK'\NwoWfsEW%IBLK!r\-<}`0au^",\.qD dV2)5}DnSI`eYá٬ C]s&$no7J8aZՖD"a0PfE-&LLFuNfzl<DZc cj9fAԖNmXtԋ]IRdRov+Y,1<ͪ>2,䕗8SvB$I3  dٜfS?P2$v!d)jh8f/X,g6 8.ӕ0 _^SSR)ǬSzhTd$1imA(NA`Y]O+R,P,Ah+Ϥ}zLQWb0]TH3rB9żN W^<7*N! -OǭVkSSS,i z- ˲]{Va =z^: qÇ pVa:]X ހ$(JmyA] +7-pvf4L\4崟,+˒a7s‹/\\^޾;n6;rnW[y>Wl&D:#Rx<Ň}U ARGnպx?qīs9&_oHSp d2١C; Bss3IvۥA!J"hU`=aê Ñ\. B `;A0,:t;A0pa*mN?~h+OQV P 4LvEKÐbAht\nw^N}>:T+A(++X: B"鮃BeegA8vuAGSKJ|_~y X6 !Iê4X_____o1'Lس٥oǎ;v y>7fV2}B5ǏWDNh)^zKq%vmeWa^1c=zo }5|DSO-U_◿@| B,Z|oðL&^ғşx|jg̘βhܹs窕+O]@/愢(Nj徽{/uRÿ~jlh4Z 6o޼yfig/liS}?psd2L40LKp}0p.sB`N `9! \0' 0p.E'NX=Ç:tH#F? vV'Od.yÇPp9l~GkjF߿j.X07V*?a f ={=IGw/[\.jӦM0'97T`'B?k{w>|7xvBG^麺["Vsn;r8gٜʄ!kZ]}pW\6lxWсWlݲj8Ig\ xvżՌ G֮]KG鹵skjB/!d2qlvlٲݍJ*#vי .x{߾}1I~_?[;/}Íee|G,-- #X ͩK]s~'νax8`t,v7SU;!tϒz%~7/*(F !$Iseq!4rdM<8q`0myAey$F8dAbFQe HR^ʲq,A$:W!I y<(${cpֱlcذasB0@:E0 #}q IDAT>6W*<Ҙ1FSRq(wھknu]Xpl'+$!rYRNO.}[n>}Fmmm0ܰ~úu BxD"B,tq7#GKJ|T*NS|"`T]3MӒ$|>%$m[sl6;!n~,!|8ufs Mǽ^z;$v*JqKKKԖh4*-P(]y+)vBW^29$;&jhNQmyGy=^㸏>numN ʀ(U,B(h4/,ˡ~^ߝ\ajiiillѕ-V0 ѕ۩X(gT*%vԌXm>\9X///.Y^Q/ߏJ e5kjjj&_Ǖ/B(m-O s*FiŢ'H \Q#'H=a)-A[^4yza!b4J˲h&B(m4Դ$hZ-zd"WOXʦYSOXJ B_dmF t *cm1(M(JJK&FiɨD"I1ժd*T8N9#`q\$B#$3MڧңQZ%My|4Jc&*0h(JZK(m2ԉD"6Ms)Wʋ B4'\r_0FiI1u}2 o ,3gNie}!aϦIRևC: 6L(˜Ԗ0L~v͵}o۪19G F( Z2sg3*-#-堶<Ðh69E}N1 rkyJHX: B2.y дrAhgS&Q-(5Nbs䁌ضj*fΜ#r8qh邜Jj̙SNQR̙S>qO[_MZ+DG%IR Y$I֌QJL0o &}6'3w(np̘}6oN&YTRs8 .lB4MOr{lS|6Rɮ6Xn[|ٍ7ݨwznj9z{ٳ^x|'os~_{=/2B(_Pccc=>w?i-Bew_qXǗλvƌ,Ɲ;wZlNxfvow{<G"y~gGݎu;vbFE Bh?/iW]ԗuuuuuul64 puPJRiV2L&{* 0p.sB`N `9! \0' 0p. @Ǐ3Muuu#F? v]xDE}9'Z o_xusf0k??0ٳgoѓKT~4uԻ3 QodxhS_8ٜr:m#jj?>|7xvBG^麺[:sM=;j]j孷_>'j}6'4L_;n+.b6LK/U~qܸ,e2BH9>[ny`p ֽ&EaXYYi's:&Iw XFpln$I(0 s8$澮 6pœ(H09a_^^m)(7_|Ϣ/:tcYv͚O9k^juᒢ(y<drB1 DJJ|F !4rdͱc}d2ez=VRw6[kyDžB!׫^rh4ZZZb0NxEE$z"O2 E4DZBBXiio4=N8a۝N򒒍W5CDb\ mZbJ\OD?_3fdl#^_:Q6'~hW&>DmώV}cno&iP[^>O(/݄a r[>sMG9 ]JM^ SecjBoMpO)lm~r>7/ju-1:d}kyNLǷ>&]Moq^6\5Vya؍izj,"ސ~vwGNuӳldsrL-KmY9tGL4]_auV'_<#UZʱo&>ȭ啈CjxsJqTՏ V7h/p V[Xcxם/-]G=7wqTپUd$6ƍf8`0% l troroh05(l-a nƲb-ڮٝ~Ḍؖγg3眝YW:-//43 +Ḹׯ-;.Zrɽ@ٗ5_TwTPwʫ8H__M3:}W]ʦfz߬~T-~~ZSg?^yN`0 a uG>?LI>w5hI |Uw֭ b ~Rpe'Ou+ m7yݞ>^{_V6eYZVUdgOr;w:³g_rxJ+ϼڟߪ;ړ+.{6}z$iN9+gGz{K|F?;oQTU>_oH58"k9+~x߫;΋g͜z|-Iy?/39{f%2UHtfz# XBx<ios1 XʽvEϥMOH|̘$1~_QQ!EҋD"PD]Nh4VZMϗL&3.ۍ6>eRsfZS rdR"<z,kLO_H8 k XjrȞeioo`t8 A.nj1sCS9s;DGGiKO7`w斫Nyt CNsB0_u+rzG*u+(&2VFD\n۶_lP!DĜٳ>3W_}'p}_$IVUWoEh0̙;w_EIRKYIApDQE$I]&EQ} (eFDׄŌ˧ɤ IidKJ&YFdRd$I]U>.=9|$[d2ݵCez$%o6Lfx)X>O+Lfe %j@ R R2ad¢S6.p0i#&HaL =Ɍ/JiH'CaX)m4"KB 0"%)h5"sF2e!dAfj#FB* ,<Bi' Gh^Cem≠}#AӔʲp8|[֕74oB%uf*71,U^uU,"N;::kܮd+B}Usݿ?s!˅3wEQK.Ytݮgcm@{[qK<0͛y|&|_|š~޾yyƷn|ѼSSSSSiSs-[fj0~?1'Tö/yö;kB`M `5!L^& ք0y&/XkB`M `";XayΝ;<ifϞk.}x&,2;|(Op8~AXM5lF5^zҋ/^񦕇=5^kBɄ @$%I~M8{Μ /`CW\権o5k>jjz<5O?ֆbYnfoذaf" fB(Y֞X,wA9$a?n?۸a˖]z> 6\s5w˖#ni3{ywTԕ+o(weA&'4?efxE'ҼAFXeY1L&0(Zql6d$n6Dhلa4m2uiG HFL&M0LA((2M;MSuc˲o e2Ei#faȘ;bXDe~> {Hp#aI5#_]HkݫW[^q.miT!dawn=W_@-Pp+4˜ߝz,hE?ەLȇ~^m7[6}{%[::O'FHԝr56$ٹ`HN/NF0آ-n={z 펾-i#7־ B,e<r}+HDdI`=IO eY?RTFU F]zdB9{T3x(U9vpUbF3 dԞr ?ПF&``aq}!ӲahI4e0 !;=MkRitDi+BKO]e /C+,*V7$1Xaø袋XZ[[+2ao75:x),X;Vˇ>D-^ƛV~hڵ#.\xm\p8 ϭoZk_5k&H޵{ Dޑ_ &\UUq_ &2w"`Bq|ڴc{zzȯ@AA~QQю;uPIkz{;B8AM넑Hx玝MM7Ȳ|΢W\ _z[޵K@ڽgvbE. --==[np(fw Lf,:yH$G~5$IiaNgBfw.`B٬vք@QPo6ujX0 #>G554'd2͛1U,AK$n!{}|GK,}'yv_H$8.(r\L;K&Sv  KIx3&8.Hߜ#2ŒbC$ BANO{kųHFIO6%=m2Jzڻr8Kdދe#T&-($Š0’x\GHDGDQȸ(V)@eܭa:Ih$MI4n s,)k.""4dg#)tƮm˼c<i<32OCf)Is;Ɍ6=6+=F"rF["el=DY2[*&) ,aAkLLФGXFd8V,Vd4G! _U0TYYEϮS]!B թa**X94C]ƲTYY&%mzc;Q(uեهrLBjuGeŠ,[PPFm6{^^1||{ غN9Suo|M=Jwhyϝzx'{9 ڿ+YO)%m2$I2a[\M_|@]/4٫Hn,Q<)qz t^8%S~i)k6HNmis^)73*/s{o?_Bwʓ?ܫ*{zb||_]J,Xw7tG~brWqW}ޛLg~pdATWbOV#}uu0ujA8p;??bIs}}"1uN[h_.:?;:z I[vIT>eJ4sֻ-uPdYt8lv{*d2ݓX,L)2R xee%#:`0Hdꫭ~/3= d===$L&s~~ƈpv`zA7`!"HT}FF$I*)n$#B"lƈ5`X+t$aр1ІԹ={Fcn |#B] _مx<חNKKi:N++G|>$^v!xF!tuu|eS)ݦI/y Dޥ-rԩ uJxQQ:.ɤ`&̍}- A[مxDYYF82en;rv. X>OWD\^xvgU#.!$ɪ( 9s}D3ׄ \vǽZ"/;nժ(?>}Bl"JJͽ{[uWZGb1Z?x L`qM;ƺK5@ #(>!W^qW\qBW_sܷK.ya͛7?c7ׄW^~_#IjsGMMMbӦf/nٲElnnnnnZz#&PG~AȯoQ8<`M `5!L^& ք0y&/XkB`M `5!L^x'06lb,H<+*+B8 G &a 矿xϞo9ϯno憎yh?fWyEŲ~pׯ3ׄ'r}cPmmCw_uе^cۯ_q}0" <qM/ Dz<JMg'r(~G7=g΅^+_QsT͚5~e55S=ikkC,[WW7kL7lذux"Lf!Ba!\.֭[M`Ϯe;9;'"IǛMp8,IRLvҥK**+6yO?N5k-_|qӼy8ƷB7{7>8-[v]$imm*=Y@4B1q!tI,Ia0 S^8˲4MHE4I,KQ!I``I2(e$3#,A&a4M, *)0 3R2.$Kemz$I,aEQ.=e hzYufz.=I?IH~~`T$vb[Gn'(DkXڒq!DӴn:w%Dz$OHH.^#R@I%HRz`{җ@Ix$emz~9R|+@[Sw\'xv.%c۹OHZQg4'%"ʒK O899-:Mzm<mGqrO䰺oKzR4kKzR8l X#HHNĶǂ!)9R%{(K>> mI;]0r(?7(4 }!ujY)b:ڶS +4%̪uuJu }vwf[e; ,A#4;b1~;:q>2i9ڊe uO,/'l:SnY:%tuJQh aҭaܜN( F3`A374B錃˲j=\wN`B88B XJb!d^;o3g/e8я~Tp5k|>II?OWW,XpgVu"/^|M+W?zB .[.]zY8~75/Κ5O$vڝ38LsüL&#;0!S9މ Z _J!t:٣N$7uju4sab9Y$d29H$ j";"IWSN!B8?6q[o֜{.?Bh=M[[[( 4nݺm|&O í0e);0!8v$կ $aX~~>Iyyy fj5!PZv{뉻&4ͿoJJJC] &IЫ8P x|XVeѨ `&I!.6xo$#S=p,Ytnkc/b{{{4: eO&pjt 敿hj_ӧW̜Y '֖:f5rƙ͉ p}\y%wUIҥ~={j\O?u@3MX`EԩSN9RwΘQyq/N邏kfт.Z>M%KNkmٺ5uh_~o^%b=w'|S"3Θ{_Jk_}zݬYG]+;:e1NooV# ~|d2񁁨nWo nuFy>PwGdY!--NP2ʙ `zB02ouǣьbqNS@2t8eYL/HŒ>~aҌH H$")9Q^(Flڞ$Ɍ|>LFM^v!$|$2ƴqLH&H[0 X,ΪD &YpH~5h63 !;\`(5=}!p\,p(j`3YҭG$)NVkt=m:M&1\f 2jB@=M`!X՛VpX3 !Nu@!|#{0/Z4㏿9֏Ϛ)˲vܞ`K^L$'\{w ?=eo[K8m9'մϗJ<ӧ9tܣ^ysAHu9s:6>cmVnQ#?ɬ lXS\t=_n޹sg*=袓7mVW{N?$]?/7{vS+D** N;ط ?(3g?jc_~SuGXL$XyL5b{-7ոB BjDWƶ/{f/r#|q4$8NQeFԩ ޗO;xQzOΚz&m/<\{VG}ͩ[~C4}e˖J^Pݳ'u'mqIu}Cݗ_J=%Mw5O>V…ϟW#gX-;k׮Jv/Sgj27/}uL]Qokp}%RV^[PiӮl7\MygY?͙5k/F.4YBo+?{˖}}'Xqc꺱 }}n3O|-I:FqNMYxIL|H$}?H(3B D_ !Yd1XP_n $N%g$mtۊ ur\WL-`W^o ڻңA+I~rLF4؟,}}næNd_i6㸸e``Ӌx> }b3wtۇ0i|hLEI8!IR}! )&!%Is;v=|D]P 'P]""H$Iu_@0 /@9S$!mzniu>bU[Ož>w^C*d2XsbRX,v{0 ST8$I? O;ѧgeYMO-T UG* ՅD/z$e@yWWQDsJQ)0+BUBxr r[Vl8{啕 u6nLXW^Pkc&c:93fTkK/=MDu/niVhL$ w]nڔLt}C~HMyvxu.o}}0sfU}C?A]vK^| 5%}FwBܰrܰ>0nCoҳٹ3PflkK--N-oï]T[͞]UP]ՠb IDATMxjsƙǝI? /8,]uw`{:WޗoH--ho{olޜnoI mёZZ7M̙'jj.̍5к>HwufgnyY3mڴ4s3¶m5QG7ԵimM+oӝB8_DMӎ>L:ŋsL+ؗ_C.Qag\ߔ^c7ܜ*oͯݱ#US\PkwW[[.okjחZc+ Mg͸y5a$a x7r=Pwok,=|"SVNk$j4̝]? &p…78jj=kK&zKuuunӦ]\w֎qLżjՃ?6}zGG$0M5 'a]w5h,_~=Bn4j(J>쩧ܷK.ya͛7?3`" =Tgu8P˼TԔ{ӦE眫nnٲElnnnnnZ h" Gyu0 ,`M `5!L^& ք0y&/XkB`M `5!L^x'0S~8w? H|ѿvtt{t?q7o`hٲk{O>\s5E-_!t͘1g|GLꪫ'}ݡ$Ie B,˲a8aa(eYy=MF# Fw`YHS#F#@:b6$I7Z,dRjn7b|"!(lhO&S-C$‰4T2 C1 Ԉ@$Ո"tbaAt2V<*=ǬV@\D%B̆1IQz7BCHR#ΠFq@檪ʽ{<׈aD޵<\i Liw-KB2c@E w9$ H KjvrDD0 nPZO2:?9C!$#M.^]ZI0@)6=ǧM;?z#aHoiCRH$IRRN5`uvO$S=K ;ls"M&fN&rzG]\(DKn7q\BMsB8Ak v(Oz<YǪUp݇ŋx^}… oK^M~zfݻvg&ኊr+X0nOj0 R ?B8WWWyp82޹ ז6 LnL,'X,F~ '轣#:E(}om$ewR'#v٣ݴXpuѹ`KsKOO֭ۆz7 Ê f,{0j-(W4a'I2LR0 `fZ%I2 fj-**5!PX6$vPu NPee݌㏻/R^ \,>)#S=p,Ytnkc/Ν߄x<^XXy>9hA߿ Rm;00000PTTC;($Iڋ$I5v jU?X,&IBgX65x ˣiZmp8TPP{7euGP(H? ˲әi/)nhXYVѨDɤϗ^<NgEpaaz#P$bEEEꎂ`2LϏKzi\.ɤ'L/;ðq`tRDh$),,TΑH8t (ifi3f93=јjD"0IԦiھ({qHaYjM'˲֦'BfS' !q\(^v!áx\WAI3 GÑn=òZJz gR K:-//E񫯾ήBЦDhfzDbB(j:=`YvBSm!S>ðT:wu9NWA3Ḿ1 ](x9 A[مSeY޺ur7BC AIoV0$e¡00B+Ft.Ԁ;GMflr:q,y7 A?`q\< k4NO7sCF!tOPf8 !#%]q=.{+.?~u.+d2q&ŕ"9.nj$ugDx3xpu8..ڧdKTE⒤$9. _tхyxG#lMHY?9ɿ=/*ߑ̹ V?z+jY/[f]3mmm!ef͞i6[~ }~*'Gؚe۷] ߟL&_簟|Ivmܰeˮmhhz=|Ɇ 暻~}eB7޴rƌ=;fʕ7P;I4M+0k~J3 a!aAAQ4AMS$IeFh8^ D:a0A%=52+=atɐiZe]2c$ӻV&$C.m= A(I fO(T[0 gDʈd/BaI2#E%I!סԧ$dvO>6MS8Nh# 3-G h:ZQY dhDi2#="$ʌ( 8}zم0bO~EQB(ha]z\(=mMYf&8]c8Xc840TV8hPOѝrsFZҝӲ,]2C40.7W! nE٭/F+|(3{>Tt@Q 7)wtVi${%C _hwOjFԡePk37gnVXTnHb!dÎG?Q}Ýk֬%%^?]]]W.X;Vˇ>D-^ƛV~hڵ#.\xm\p8 ϭoZk_5k&H޵{ D)llkkx&2w"`Bq|ڴc{zzx&;v) >v``o[H!vPY:?89o[s~]~={5.:wQ0linݺuP{q| -7ɉeY3/$$IJő_ & ÜEEz<'˴!OQ#`Y@l<)Ngm{{]EEFQDQ[ZZLө[C(WH*z'H!f3I|B):`0}0=NŢDq=eJ:^oiia0 sھp݂ ^LI^ggdW6eYt8lv{dwwOAlNqq5eL/F,h4VY9\!ȅp! AWH$+//U"`Jݑq\>I,re(<ϷlM!h4^o<//֩ !8ށ DQU!.mQ XtVv!ȅ`4$"݀eR aNRsF}mmmavxfn>GevP,]!"GؚpX4z3&uSԩU(z<!IUUEQlmmmmm5 s=x)u;L&曗,;0w܋.^ڜzt4,U^uUNs3i_`l(Q?쳧zz/_t l޼> u %Iz'yY 8&usӦE眫nnٲElnnnnnZ#q5yu0{AkB`M `5!L^& ք0y&/XkB`M `";(--9klr-<ϏwFpD:^\ǟx8nW>ҲN HGؚxٲe/+uݿ_l,] HGؽ$IKowyԩٯ=g΅^+_QsT͚5~e55S=ikkC,[WW7kL7lذuS8!Lf!BagPhdR$Iʫ~駟^9}ڱK.O?N;}֬_|M?3jD|̛?OALM#I`'MMә f'AaKdpe Plj̶ VmzL 6+=FhC\9="in~$iZKC\5 0յk=Pj[$̃;umarvO3جjzr(uGX4}p `M!TM`0hV꡴٬AÑP$i0 _rvz,f\}덲aMb:tcYӦy!(7b!NuWMðH$"ajzgBҟӔPÌPctШ L&lxܹˀ;0,PtTpt,01gnN3H|b\fnN37M2Xfnf:8B XJb!d^ v(Oz<YǪUp݇ŋx^}… oK^M~zfݻv!ښh4>0UUmmm\P^^Ft[NL8OvlOOo \PP_TTcNLfG];007މH$ wTu+EQZH$yy߂:I$mذqOW]*<+u]OәLT*͟?"b25UphΝc\.,oJ2opBsECCC$wu͵G5kVr̖{BabQ%IdGGr<43g|>/y0g2BXX`l ɲ!F\\\\\\\\\\\.`*),CឞJ2?z>dɒ̟I؜/^֬$/Iؽ~W7\.ڹ}˖9_O'?UW_} CGyDW#?ݻ !ǝwy@ ( / 1|k !$ O_=ީkW/aݍv)_ye顗^|If/Ra\u7,]GdN>ᢋ.z衇ߎFUU4McY뺢 ihQw.?E^4xAQ]Y5[dY&X[$cHRr݌VɲrQ͸\MQ>-oJAh^Ӻ\~{TjzrNq˛rPN MӋwϞ٬003 B[[W_ui a1?`™I'-f?`5,?s;FNb1G]1v欳Zdɿ|Χj5By? ůO=[!PyP(,[lm[n%ڵk=,F7A=,| q.v2-vES؊|>D"jUi)ЊiCi2g h 8VZM^M' Ry3uW:Vs,z=O4}xZz::ۊe= W\JjZ ,+X[#s9k^^^74~fB_z0 Boo97ϽΝ[Zp%IN4oeX,V銢Nw-"6? xVvsɂ ͖t:v#ْL^o(ě( әp8zI B.+r>_loo3/mR4k-Ĺ\N6%j$-CCC <_WѢ(p& qHyj5Q#r\(ԕW(Yc[/Bb<O8 utBR l6j[[!j;vf-/bY6M\^(ěÀQ^46o'E1[{ZT.ʳU d5m<0MSTjPhZL@ 0+UUM9PTB}ybq*ApAAMP*J=-/Iue2B<0$i;wrZ3bT:gUKlB(=B!qj{&QUͼ[ULF BWdۭiR.3XmA~łyD0,hYBj^zWz&#hn)Odj#Tb� #JLF8<l.XNhy9UU!Dr.ms/d2k5XEQ2j�!T*ڂ7B$lv[0>Q̜:5f>_zZ.EZ^.uRӬ30!Z&kNs9{r|ZBr,qՋeYs/E[BP'R^=f)EZm<#n7B!/MI 50NN lo3/9iZL&+#Afy M!4o0`z] 6.2`U2w 4id2`j rJH{NegN -yG ؃ 9`|>YɆsaV 7Afs4 /e2i/DM6Vaj6  晛$lSfgniLr\*sJ* \|+VK7n ٫iZooooo=yC_2@;s=y800}{(SNs=۷g^8U7}SO۞=d2/_jZ%XqWd2H ~SpnP(B::Nazy]]W\q7_ޚwplh9 |MBH[[xa뮿tw&<^r%??w,KkR4o 7\w՚M8F vͭjo5r}/̜9s !˖/nzIBe˖3}+Vx* !D6mꚷh"#mݺtR[{ZgV^ݻz`߾>GNN/EQ7FX;oB%I?w~p(v{{w[%}a,opႅ󍟯ڥK>O2̉']{ 0oǫhG&4M9(B5zMlZfkql-TuշЎb5\$sl1B\.ī(bb-4^17.h(2gw4id{5V_L)͆[\&=moyOk=Yvniڶ+38K&Өנ\{x1Q'_.WÝk =bR4{߹c-.عZ4gy3mo.kbHTtNr<= 4*Oi}IOu^L˞a;ssΝgn r:37gBO#je>5 Šm.s=g"++v~~5k9.y{߻ƛP替v٥'I_=x.;wKFˍ7[pk@O۶uۏ~Բed/oe bѢr2888 ǟ@ `ݻwbuk5޽g @f] xGGǟdfNZT*]!v1-qlX%.\.zTUuݺZ_i*!vnBkgUE1'܏^ye``p]&X X-*d>7b1Ih Pa.ZMӁ@ ǫH$0'n_ DžJ\.We-1}Oj5kB^xW{DQdYbKTDB<|(DQB!8M?0S,|[[Gs0D"aۍ@Q@ ysB0|eqb9R='}ߝUH8io\\̙MVmmqchQvi~[( œ9sC|6cwka$Ia%~o81>88 Ѣ(J*F>hVL[[ˎ̜r65)/ Ů!+IYKӵZ}l>L&E- p8lnD"dYbُ(Lؗ͜J\.7k,N|>W,Νc(dY744D>v%H, CP}y@ 0Z44^g(vc|>?{l92tvv{G_|E]Y/ &8F#㕧j2D"~h$)N1Wb!/ԗ7W MS:::͖T*rRźj>#- wmRmZx,yO4UQAU'灥fiiG&AдAu{ܱWL0b?ʩ((ǣZmi Q:s3z!.N,;BZb]]>}?O=ԗ^|rW\{Q7sgqƓOl2͛?~rt^>H$0̪Uy>ć sE_d|/Yo߾UK\'rg.rS~'0{>EQr6}/7? ֭˲텍o-m/|n|he^5\R$ewرn-1'$<쯟O IDAT{<ϻnAOo}V2#?~x$ c]S !Zi!y+~2'4w'!\.OɲJvu_P\lj>!L1', ׯ.ֽG(=̈33333333{ s<ド~l㧞z*oQ|z?pi|gl67UZ: ?/tYg-+V~[tҢ^y㩎 _E'~?{j 8uEݟf 0]/My?K~p!WjY^smRin57pLi ì[_>2-{_4Be˖3}+Vx* !D6mꚷh"ے#mݺtR[{ZgV^ݻz`߾>G}Nx%h?Ko~sVg8rݽ-OBN>0S7[NXp~~ҥK{'L֮aɲZor?M,r& K` -j5ksf~eYkK g@vS. 5vlY'x^gyn=b(r,-0L ২{zkZg}mTؒm:^/Ap4k3YwnÞfVۖ8o}ӜA0:d`];zlAhS| cΞ6 X3z=^/g/QsÈ+!v8aAJ2!׺'sqIRV9`z zZsrA`# mp׺X8 B}yL4A€e9!9`o89GgOo=zj5x0Z}y>[ybȀei-hj X^3F:Lי̍37&3`3gكpPBQX͒%o}{}w~BY{ivŗ|{oM7ćB7.߹_2Znq݂]BO~ض~G-{$y/Sxu˕)_8 ޽[_ 3@W\dz{.ZMӋw``0Nw-y8X'T* &8n9BbZ:!q7|K?k{vz5MSvcNuqb7{Gs200s^Iy],- J֏ `&8.ʲ$JW nFӴ/X,J ZB(ć cNx ]X.o( |>OȲuח^/!R*!(OQq(\y}dժLR2Z*1+R,7r +JӢX^M$={6~n۷h4j<4mh$ yp0f~aRRCgwB5;l>[p!IZ#5l9pqmmmf޽{Cp41z@,5$U>=ryh(=g,s|>/y̛3X,,X0dY;w.!$W uvmxH$'̓(ܹa>_ L&s6c)M~{ dKj x| (? dsgO4`قZ__݈ @a֬N7l6k B+V\44mr:`5M XsZVI Xw_rX t !KyGurۀ70ө5<ϻ޽{܎Ιap0A䰜dlA8BN(Un|2gl!}DQܵkEQ{gW8eϞ݅BP(|ɯizx9 7fn{{4|y-f焕Jg|g4W\&=Opj{======<{^Al8✰r\.>%r*Bn15x(sZoQsb~BZ1sEg=Uqœ`œ`œ`œ`œ`œ`œ`œ`rOwcO|;kGV3㧞z*oQ|z?pi|gl67ZeNxk>ۢ(^r{ƛ$I"XqWd2H ~SpnP(B::Nazy]]W\q7_ޚwplh9ᩧO|;| !-c63 s׽݄_K.r箖ei5V*Zi~?Lj>T>ēƄ;4Z`>!dٲEMO>iu۶s=1<χ^{kg|=w/^8_cUb;^.q^Ci,K4!$zMUe)2^O4qlqFZ,ts0v^kaX]¹\.!EQq,Me9<e㲵8Z8,(a^[y+fʄU y~…9!Xmm~dY.ʫVrY^,3WT*ED~"7VlosB!s_r%'͕JYŒHl1$[?f 2^/sW_^м^/IRT'bRW^\$z>W,4M ÄWvܥSq}MrN_up$rdKqpE1L $ g6WeJ%(j"ǣHN : B>[gE7K|>A030\.s]֜4-r29$kӪ[sjS-9Z\tɀe "ˍ@ev$gقLxAK$RXU.Wl9g2 `ꃐ:`|>YI#Xd,c 6B8:Veji^o iĂun ٫iZooooo=y#&ZLK|嬳^_.Y}"!==jMn=眳8'd͛7c8nҥy㋈Ng}v$afժ<C {/2>b,ٷozө%/[XjBȽ{ w)*=/d旛B}eYEQ^ƍ6X!y?SUu۶w}|k>Kw\u .X)I˲;vXG'xX,~뛷@ dlg?}< ꄿ]~) W4`c-1'r\.>%˲}ܦn15x(sZ0-fĜX,_~r;z(DQ|_Ow-gF\'0'0'0'0'0'0'0'0']@ p /$Ix<~ꩧ|p```EQ̧8ٙzofsGjcU i^yWfϾnXqWd2H ~SpnP(B::Nazy]]W\q7_ޚp,i{Go[gOmlkg{?]k[Z9wɥ^eYZsU~붫7߼zǪV7_gk_}htӓO –-[4_si~zk¡}u7͢^Oo{w[c;^|%-X8ڵ_ |i K.}џd2O袋zzVs\ZVESEYX, - Ѵ,!4MOТiEQk3ɖq(nVqV^ X`;SJw  IDATvecӺ2 Ψ 򎡞F=p d[ø˞v$kkwttܹUqӎN:iQPQA8&ʛƞvx9BbZ G"|ڒ!H!/EQh$zPXlٶ۶nJٵkמ{$YnJcyaM8^|@ .I{4Pa.ZMӁ@ {/IC6uzrxxv%Jy^獇 Ao2< C7rz!Il6F=RBͼ*m9v"˲fBVbc`nw8hQ%+ZrX,0fyBf^F/J\Q.S<N=&+/L~FNZ`vu49uZ^X,dy0P a79AN 2{N=nN (rUU M{˲fy hN75 XsJtV۹s ay B(zG3Fyfu#1`كP*KAR,k&-  Ȁe;5XG2MS=-Ͳ }j>;bd0V̍D"gn X͙(X!˪j]V!HZʄNBH"H &!H\D2tf_!Ha W]_m/p՚LE1bո ˒(Vuf"UպIպ?XJժ(VUuEkXUձ VŪR)XS{y,Z$jjUVEkyZM 6SUQj!kym%I ,۶hXgg;4]Dɲj BeEQm-έW/(ZVcW:vGߝ| pi9[d%"tҢ%KR==jMn=眳8'd͛7c8nҥy;}ّHaU>|1  .ȸ/Yo߾UK|-_J.2BȽ{ w)*='%~P(nݗXUm/lxkx3UUm{{w׿C.~UW$,cǎ1@Kh9_s>2޳b[߼L&c/?H$_(T'2=wNQT>'\jqyOOOOO^WJ@9ZbN8r\.7|JT*5Xo+bZ(ƻx'i1#ba=݅cC!szh93:!49!̅9!̅9!̅9!̅9!̅9!̅9!<:gL$m6 "WuWTTP,(Z r*x~A9D(rRZڦwӦm@Nڴ@Ky=#dgN_7; /+%ϝ>wa1dKu#b)}YrL&dϞ=B0<@k|fMN p3 ԯ_m[544BN)99Y2VYݻKF=wv$w>gLڽ{M~TYY0uؾXdgx(Z4T&q湬FnqύajqϭPa/NVkn?nwWsٔqܑ#G/[QUU5py^~rS%%%o?O?T<7}(<SB>xq ^zZӧN_CI 8pkQTqqsssM&sG>!66F&@'4ڵ}N~Dߗ\WWW\@e4!4yBu3gNϟj]Ws_;/p$ltnIɴ(''o7CP桢cDzNbLJ U-2x'\lZV[G>!44e9( &vt,5M\\g AӅT*)J 5CǼCyyys1c)Zl6Bd+\)rJ[cO>9GF~/noYY' !:}QXX( ї|]|EbYvݺu듒 i5xt^}yyy9911a0TWW׈UTT444tÒ.),,86<<]RBBBk.]kZCJBIC422G*66}6`sr,>#].u2BL.pB```ppp\  %XS*k(--p_][[[YYթSjC||Y,qxP/rN. kvᙋKHqNb׋WVVqqW^YYj!T*e'B\W^AEBFj.gx(44DV %fI/W(.ʺ(CP]mܹ>ʺΝ;Qiinv3 qe(WuBV\Zz6H Q.g2KJJ""" -&B}}Ce[NV4&DƊh]G&I%%zDP?#b9t$8O=l6ƊWd.^,FI*"}܃p8gorRsaY6)9a˕?vǷiiiMN}ª!C?7*jĈ>޽-[{߽ݻwĸmrRsV8 8NV-_O&CBBRupG׎-\'!e׮mxOՖ̙fþu77)t'vk_yyONƢœby~[7h: !2>uB㏍P(l6<"M'F ZZZښ/+\bZ[[E*_ڏ_ իWсa2m9~q>!BO'_/  }B>!BO'_Rvqw\<7`f6[r9M_o,')rp+7Z2󼸄yL&.lj (g+%E+<ðf (ð 8eYB8<"2qx.7%+QPYV -q0lSJ!8'kq)rxIx / 0 PHVedD2LTUU,+I“dU)\w0EQrd1'JcYmͲrIE<4BE[*%Km1dI5 F) q0-&!Ե%{U2 S[klM"HV%˲ Dpy'\ke"ã(ҚUp:@sG }B>!BO'_/  }B>!b;:ֻw/gffJʻwۺRu34sa_ll'3+GFFvU2l6[mK ;ԩSW|'շo_eerwa1dKugZ\\\BB-U[HHHϞ=O.--#K>QVVކCcYgΜؘ_ZRRϻvzW3EQ}IHLY~$?PsFttL߾}NoAϝi9ԧO8z칳gϊ%&&ݓas9Gkn ^Tp+-a#՚W(ʌiÇ+.Ozjx2ɓ4EvKݲ{s Zf%3OSR6By)M˘^WW׮3;9kVys]ޥKL''';ιs_(*,2={xQQaaz$<Y`h JKK!dJKJ.|]O|ԩk BB~&B\^X?ٌq۶n3̈́-[KL_nR?2erYYُt(۶ut:N=_pͲOdϜ13?{1iSӧO3a*JBH=.ydܼy?!E].צ?R}[ JJ6|h/!_R9W!=> +**>?B(R(*BRRRv;qGYlEUUUG\15X։لF{|xC""M<ٹs3fL jR%yyy\G?29kFߠyc6QFqG?B;-m!71mj~~;$,B/9lB9\Z_E=+.<3kOdc<]ȍhkؙ̯o#rrrM~|zW^yEVKq8;ѣNJSF9`BMK7 /:~DVV֘я^|)11q;`~*i%p\oX(8aophfF\@m)**\|Ŕ)SFibپ}ÿ478q?py/;r#ƷW}}*1˺T*k| 6mzᅹuuҧG <8--mS2.(E+ebS(ڵ+&&z&OI6lܸq„uMR /?w]ջw~;LrrUn/Zh٫׼r~jĉu7zc{ѣ>SҵZ c_}Ғ_~Լdx4їo|@iM1pDѸ`BB1 4hEԗ{C;p\￿qӦT*`P(fl+)Bta:d0DFDFBN}vFdXM&ӤIOUVTx>֓z@BBCuK8ӻk_9WGDDnj(.xPE@W'44t!NnGGGu~Ʋ_~Ԥ+/6iZ.&3MS31=H 1NZZĆ1Æ +,,yb_~_߅?^o6[O4u8dz9*"/ºe*lG~r|^ֱ,Bȑ#'L ?99nN l6[uu2d0EQdsKLraJJ !D_'?&"bccÏa'W(j D!C<ӧϜ8ΒVYp޽߯|kCH:ǘa޽n/~Rvq7?_ ŅΝ=8EEΝt̩oXv$KN3*:ZNTԮz}ɟȑ\9kFddʷޖ<|GnaaaÇ_paa:t:'OoV񆆆GǬ]NxPΝ2eWTT eV=z²]fL\~2 %%9,LҥG'5MUU*bc#o<>/>b=&=i .*U1cF1bΟ?ѳM 2l6Wl6l6lߟw[iӧ۷ ?tfy}=wd2uz3jkk֯t:ƌò{n7U>Vt.z9<9kۖh# l۽rgAQԠA&Lou|CӦM:]hXNcYba=ۺfY6}rz.]/_!\\"!^TpͬV3!*,<]t!j cǍ7np=+ { < :X֊+ܯv玭[ /8/<B+6}={ȩ6Tx Ņ kV9~'|׷;!LdYYVywN1}ۄtbƏ?x)'8rHm6۾ZN:_ YIaii OUĒK}Yofo,ŕ<2j(Ն͛?ݶv)K.ѣp{vi:cZ_r7˲Vu޽kVugՈ>}Z=!N董V*--:tȔgO#!BO؎ڗbv8d/' _pQ '_/ '&BȠAZ32q(#_!Dt\<F!f8yBp;"$.u(H(Bbm3f !348.n2T!f(."[EEїNJEAQE'_x7IENDB`prometheus-smokeping-prober-0.9.0/example-rules.yml000066400000000000000000000012311474372005100225020ustar00rootroot00000000000000--- groups: - name: Smokeping interval: 30s rules: - record: instance:smokeping_probe_success:ratio1m expr: increase(smokeping_response_duration_seconds_count[1m]) / increase(smokeping_requests_total[1m]) - record: instance:smokeping_response_duration_seconds:q50 expr: histogram_quantile(0.50, rate(smokeping_response_duration_seconds_bucket[1m])) - record: instance:smokeping_response_duration_seconds:q90 expr: histogram_quantile(0.90, rate(smokeping_response_duration_seconds_bucket[1m])) - record: instance:smokeping_response_duration_seconds:q99 expr: histogram_quantile(0.99, rate(smokeping_response_duration_seconds_bucket[1m])) prometheus-smokeping-prober-0.9.0/go.mod000066400000000000000000000025731474372005100203140ustar00rootroot00000000000000module github.com/superq/smokeping_prober go 1.22 require ( github.com/alecthomas/kingpin/v2 v2.4.0 github.com/prometheus-community/pro-bing v0.6.0 github.com/prometheus/client_golang v1.20.5 github.com/prometheus/common v0.62.0 github.com/prometheus/exporter-toolkit v0.13.2 go.uber.org/automaxprocs v1.6.0 golang.org/x/sync v0.10.0 gopkg.in/yaml.v2 v2.4.0 ) require ( github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/coreos/go-systemd/v22 v22.5.0 // indirect github.com/google/uuid v1.6.0 // indirect github.com/jpillora/backoff v1.0.0 // indirect github.com/klauspost/compress v1.17.9 // indirect github.com/mdlayher/socket v0.4.1 // indirect github.com/mdlayher/vsock v1.2.1 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f // indirect github.com/prometheus/client_model v0.6.1 // indirect github.com/prometheus/procfs v0.15.1 // indirect github.com/xhit/go-str2duration/v2 v2.1.0 // indirect golang.org/x/crypto v0.32.0 // indirect golang.org/x/net v0.34.0 // indirect golang.org/x/oauth2 v0.24.0 // indirect golang.org/x/sys v0.29.0 // indirect golang.org/x/text v0.21.0 // indirect google.golang.org/protobuf v1.36.1 // indirect ) prometheus-smokeping-prober-0.9.0/go.sum000066400000000000000000000164161474372005100203420ustar00rootroot00000000000000github.com/alecthomas/kingpin/v2 v2.4.0 h1:f48lwail6p8zpO1bC4TxtqACaGqHYA22qkHjHpqDjYY= github.com/alecthomas/kingpin/v2 v2.4.0/go.mod h1:0gyi0zQnjuFk8xrkNKamJoyUo382HRL7ATRpFZCw6tE= github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 h1:s6gZFSlWYmbqAuRjVTiNNhvNRfY2Wxp9nhfyel4rklc= github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U= github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA= github.com/mdlayher/vsock v1.2.1 h1:pC1mTJTvjo1r9n9fbm7S1j04rCgCzhCOS5DY0zqHlnQ= github.com/mdlayher/vsock v1.2.1/go.mod h1:NRfCibel++DgeMD8z/hP+PPTjlNJsdPOmxcnENvE+SE= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g= github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U= github.com/prometheus-community/pro-bing v0.6.0 h1:04SZ/092gONTE1XUFzYFWqgB4mKwcdkqNChLMFedwhg= github.com/prometheus-community/pro-bing v0.6.0/go.mod h1:jNCOI3D7pmTCeaoF41cNS6uaxeFY/Gmc3ffwbuJVzAQ= github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y= github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io= github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I= github.com/prometheus/exporter-toolkit v0.13.2 h1:Z02fYtbqTMy2i/f+xZ+UK5jy/bl1Ex3ndzh06T/Q9DQ= github.com/prometheus/exporter-toolkit v0.13.2/go.mod h1:tCqnfx21q6qN1KA4U3Bfb8uWzXfijIrJz3/kTIqMV7g= github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/xhit/go-str2duration/v2 v2.1.0 h1:lxklc02Drh6ynqX+DdPyp5pCKLUQpRT8bp8Ydu2Bstc= github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU= go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs= go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8= golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= golang.org/x/oauth2 v0.24.0 h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE= golang.org/x/oauth2 v0.24.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk= google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= prometheus-smokeping-prober-0.9.0/main.go000066400000000000000000000243501474372005100204560ustar00rootroot00000000000000// Copyright 2018 Ben Kochie // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package main import ( "errors" "fmt" "log/slog" "net/http" _ "net/http/pprof" "os" "os/signal" "strconv" "strings" "syscall" "time" probing "github.com/prometheus-community/pro-bing" "github.com/superq/smokeping_prober/config" "github.com/alecthomas/kingpin/v2" "github.com/prometheus/client_golang/prometheus" versioncollector "github.com/prometheus/client_golang/prometheus/collectors/version" "github.com/prometheus/client_golang/prometheus/promhttp" "github.com/prometheus/common/promslog" "github.com/prometheus/common/promslog/flag" "github.com/prometheus/common/version" "github.com/prometheus/exporter-toolkit/web" "github.com/prometheus/exporter-toolkit/web/kingpinflag" "go.uber.org/automaxprocs/maxprocs" "golang.org/x/sync/errgroup" ) var ( // Generated with: prometheus.ExponentialBuckets(0.00005, 2, 20) defaultBuckets = "5e-05,0.0001,0.0002,0.0004,0.0008,0.0016,0.0032,0.0064,0.0128,0.0256,0.0512,0.1024,0.2048,0.4096,0.8192,1.6384,3.2768,6.5536,13.1072,26.2144" logger *slog.Logger sc = &config.SafeConfig{ C: &config.Config{}, } ) type hostList []string func (h *hostList) Set(value string) error { if value == "" { return fmt.Errorf("'%s' is not valid hostname", value) } *h = append(*h, value) return nil } func (h *hostList) String() string { return "" } func (h *hostList) IsCumulative() bool { return true } func HostList(s kingpin.Settings) (target *[]string) { target = new([]string) s.SetValue((*hostList)(target)) return } type smokePingers struct { started []*probing.Pinger prepared []*probing.Pinger g *errgroup.Group maxInterval time.Duration } func (s *smokePingers) sizeOfPrepared() int { if s.prepared != nil { return len(s.prepared) } return 0 } func (s *smokePingers) start() { if s.sizeOfPrepared() == 0 { return } if s.g != nil { err := s.stop() if err != nil { logger.Warn("At least one previous pinger failed to run", "err", err) } } s.g = new(errgroup.Group) s.started = s.prepared splay := time.Duration(s.maxInterval.Nanoseconds() / int64(len(s.started))) for _, pinger := range s.started { logger.Info("Starting prober", "address", pinger.Addr(), "interval", pinger.Interval, "size_bytes", pinger.Size, "source_address", pinger.Source) s.g.Go(pinger.Run) time.Sleep(splay) } s.prepared = nil } func (s *smokePingers) stop() error { if s.g == nil { return nil } if s.started == nil { return nil } for _, pinger := range s.started { pinger.Stop() } if err := s.g.Wait(); err != nil { return fmt.Errorf("pingers failed: %v", err) } return nil } func (s *smokePingers) prepare(hosts *[]string, interval *time.Duration, privileged *bool, sizeBytes *int) error { pingers := make([]*probing.Pinger, len(*hosts)) var pinger *probing.Pinger var host string for i, host := range *hosts { pinger = probing.New(host) err := pinger.Resolve() if err != nil { return fmt.Errorf("failed to resolve pinger: %v", err) } logger.Info("Pinger resolved", "host", host, "ip_addr", pinger.IPAddr()) pinger.Interval = *interval pinger.RecordRtts = false pinger.SetPrivileged(*privileged) pinger.Size = *sizeBytes pingers[i] = pinger } maxInterval := *interval sc.Lock() defer sc.Unlock() for _, targetGroup := range sc.C.Targets { packetSize := targetGroup.Size if packetSize < 24 || packetSize > 65535 { return fmt.Errorf("packet size must be in the range 24-65535, but found '%d' bytes", packetSize) } if targetGroup.Interval > maxInterval { maxInterval = targetGroup.Interval } for _, host = range targetGroup.Hosts { pinger = probing.New(host) pinger.Interval = targetGroup.Interval pinger.RecordRtts = false pinger.SetNetwork(targetGroup.Network) pinger.Size = packetSize pinger.Source = targetGroup.Source if targetGroup.Protocol == "icmp" { pinger.SetPrivileged(true) } err := pinger.Resolve() if err != nil { return fmt.Errorf("failed to resolve pinger: %v", err) } pingers = append(pingers, pinger) } } s.prepared = pingers s.maxInterval = maxInterval return nil } func parseBuckets(buckets string) ([]float64, error) { bucketstrings := strings.Split(buckets, ",") bucketlist := make([]float64, len(bucketstrings)) for i := range bucketstrings { value, err := strconv.ParseFloat(bucketstrings[i], 64) if err != nil { return nil, err } bucketlist[i] = value } return bucketlist, nil } func init() { prometheus.MustRegister(versioncollector.NewCollector("smokeping_prober")) } func main() { var ( configFile = kingpin.Flag("config.file", "Optional smokeping_prober configuration yaml file.").String() metricsPath = kingpin.Flag("web.telemetry-path", "Path under which to expose metrics.").Default("/metrics").String() webConfig = kingpinflag.AddFlags(kingpin.CommandLine, ":9374") buckets = kingpin.Flag("buckets", "A comma delimited list of buckets to use").Default(defaultBuckets).String() factor = kingpin.Flag("native-histogram-factor", "The scaling factor for native histogram buckets").Hidden().Default("1.05").Float() interval = kingpin.Flag("ping.interval", "Ping interval duration").Short('i').Default("1s").Duration() privileged = kingpin.Flag("privileged", "Run in privileged ICMP mode").Default("true").Bool() sizeBytes = kingpin.Flag("ping.size", "Ping packet size in bytes").Short('s').Default("56").Int() hosts = HostList(kingpin.Arg("hosts", "List of hosts to ping")) ) var smokePingers smokePingers var smokepingCollector *SmokepingCollector promslogConfig := &promslog.Config{} flag.AddFlags(kingpin.CommandLine, promslogConfig) kingpin.Version(version.Print("smokeping_prober")) kingpin.HelpFlag.Short('h') kingpin.Parse() logger = promslog.New(promslogConfig) logger.Info("Starting smokeping_prober", "version", version.Info()) logger.Info("Build context", "build_context", version.BuildContext()) if *sizeBytes < 24 || *sizeBytes > 65535 { logger.Error("Invalid packet size. (24-65535)", "bytes", *sizeBytes) os.Exit(1) } l := func(format string, a ...interface{}) { logger.Info(fmt.Sprintf(strings.TrimPrefix(format, "maxprocs: "), a...), "component", "automaxprocs") } if _, err := maxprocs.Set(maxprocs.Logger(l)); err != nil { logger.Warn("Failed to set GOMAXPROCS automatically", "component", "automaxprocs", "err", err) } if err := sc.ReloadConfig(*configFile); err != nil { if errors.Is(err, os.ErrNotExist) { logger.Info("ignoring missing config file", "filename", *configFile) } else { logger.Error("error loading config", "filename", err.Error()) os.Exit(1) } } bucketlist, err := parseBuckets(*buckets) if err != nil { logger.Error("Failed to parse buckets", "err", err) os.Exit(1) } pingResponseSeconds := newPingResponseHistogram(bucketlist, *factor) prometheus.MustRegister(pingResponseSeconds) err = smokePingers.prepare(hosts, interval, privileged, sizeBytes) if err != nil { logger.Error("Unable to create ping", "err", err) os.Exit(1) } if smokePingers.sizeOfPrepared() == 0 { logger.Error("no targets specified on command line or in config file") os.Exit(1) } smokePingers.start() smokepingCollector = NewSmokepingCollector(smokePingers.started, *pingResponseSeconds) prometheus.MustRegister(smokepingCollector) hup := make(chan os.Signal, 1) signal.Notify(hup, syscall.SIGHUP) reloadCh := make(chan chan error) go func() { for { var errCallback func(e error) var successCallback func() select { case <-hup: errCallback = func(e error) {} successCallback = func() {} case rc := <-reloadCh: errCallback = func(e error) { rc <- e } successCallback = func() { rc <- nil } } if err := sc.ReloadConfig(*configFile); err != nil { logger.Error("Error reloading config", "err", err) errCallback(err) continue } err = smokePingers.prepare(hosts, interval, privileged, sizeBytes) if err != nil { logger.Error("Unable to create ping from config", "err", err) errCallback(err) continue } if smokePingers.sizeOfPrepared() == 0 { logger.Error("No targets specified on command line or in config file") errCallback(fmt.Errorf("no targets specified")) continue } smokePingers.start() smokepingCollector.updatePingers(smokePingers.started, *pingResponseSeconds) logger.Info("Reloaded config file") successCallback() } }() http.Handle(*metricsPath, promhttp.Handler()) http.HandleFunc("/-/healthy", func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) w.Write([]byte("Healthy")) }) http.HandleFunc("/-/reload", func(w http.ResponseWriter, r *http.Request) { if r.Method != "POST" { w.WriteHeader(http.StatusMethodNotAllowed) fmt.Fprintf(w, "This endpoint requires a POST request.\n") return } rc := make(chan error) reloadCh <- rc if err := <-rc; err != nil { http.Error(w, fmt.Sprintf("Failed to reload config: %s", err), http.StatusInternalServerError) } }) if *metricsPath != "/" && *metricsPath != "" { landingConfig := web.LandingConfig{ Name: "Smokeping Prober", Description: "Smokeping-style packet prober for Prometheus", Version: version.Info(), Links: []web.LandingLinks{ { Address: *metricsPath, Text: "Metrics", }, }, } landingPage, err := web.NewLandingPage(landingConfig) if err != nil { logger.Error("Failed to created landing page", "err", err) os.Exit(1) } http.Handle("/", landingPage) } server := &http.Server{} if err := web.ListenAndServe(server, webConfig, logger); err != nil { logger.Error("Failed to run web server", "err", err) os.Exit(1) } err = smokePingers.stop() if err != nil { logger.Error("Failed to run smoke pingers", "err", err) } } prometheus-smokeping-prober-0.9.0/smokeping_prober.yml000066400000000000000000000005771474372005100233000ustar00rootroot00000000000000--- targets: - hosts: - localhost interval: 1s # Duration, Default 1s. network: ip # One of ip, ip4, ip6. Default: ip (automatic IPv4/IPv6) protocol: icmp # One of icmp, udp. Default: icmp (Requires privileged operation) size: 56 # Packet data size in bytes. Default 56 (Range: 24 - 65535) source: 127.0.1.1 # Souce IP address to use. Default: None (automatic selection)