├── .github └── workflows │ ├── main.yml │ └── pr.yml ├── .gitignore ├── .golangci.goheader.license ├── .golangci.yml ├── LICENSE ├── Makefile ├── NOTICE ├── README.md ├── VERSION ├── cmd ├── oy-csv-to-targets │ ├── README.md │ ├── main.go │ ├── main_test.go │ └── testdata │ │ ├── expected_targets.json │ │ └── targets.csv ├── oy-expose │ ├── README.md │ ├── main.go │ └── main_test.go ├── oy-periodic-queries │ ├── README.md │ ├── cache.go │ ├── collector.go │ ├── main.go │ └── model.go └── oy-scrape-jitter │ ├── README.md │ ├── main.go │ ├── main_test.go │ └── query_result.json ├── default.nix ├── docs ├── archetypes │ └── default.md ├── config.toml ├── content │ ├── _index.md │ ├── httpclient.md │ ├── metricslint.md │ ├── mimircalc.md │ ├── promqlparser.md │ └── pwgen.md ├── data │ └── menu │ │ └── extra.yaml ├── layouts │ └── shortcodes │ │ ├── tools.html │ │ └── unsafe.html ├── static │ ├── brand.svg │ └── custom.css └── tools-top.md ├── flake.lock ├── flake.nix ├── go.mod ├── go.nix ├── go.sum ├── metrics ├── packages.nix ├── scripts └── errcheck_excludes.txt ├── shell.nix ├── tool-documentation.nix ├── util ├── client │ └── client.go ├── cmd │ └── cmd.go ├── collectors │ ├── fixtures │ │ └── textfile │ │ │ ├── client_side_timestamp.out │ │ │ ├── client_side_timestamp │ │ │ └── metrics.prom │ │ │ ├── different_metric_types.out │ │ │ ├── different_metric_types │ │ │ └── metrics.prom │ │ │ ├── glob_extra_dimension.out │ │ │ ├── histogram.out │ │ │ ├── histogram │ │ │ └── metrics.prom │ │ │ ├── histogram_extra_dimension.out │ │ │ ├── histogram_extra_dimension │ │ │ └── metrics.prom │ │ │ ├── inconsistent_metrics.out │ │ │ ├── inconsistent_metrics │ │ │ └── metrics.prom │ │ │ ├── nonexistent_path.out │ │ │ ├── summary.out │ │ │ ├── summary │ │ │ └── metrics.prom │ │ │ ├── summary_extra_dimension.out │ │ │ ├── summary_extra_dimension │ │ │ └── metrics.prom │ │ │ └── two_metric_files.out │ ├── textfile.go │ └── textfile_test.go ├── http │ └── http.go └── period │ ├── period.go │ └── period_test.go └── wasm ├── metricslint └── main.go ├── promqlparser └── main.go └── pwgen └── main.go /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: "Deploy" 2 | on: 3 | push: 4 | branches: 5 | - 'main' 6 | jobs: 7 | deploy: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v2.4.0 11 | - uses: cachix/install-nix-action@v15 12 | with: 13 | install_url: https://releases.nixos.org/nix/nix-2.13.3/install 14 | extra_nix_config: | 15 | access-tokens = github.com=${{ secrets.GITHUB_TOKEN }} 16 | - uses: cachix/cachix-action@v12 17 | with: 18 | name: oy-toolkit 19 | authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}' 20 | - run: make lint build 21 | name: Lint check 22 | - run: make build 23 | name: Testing and building binaries 24 | - name: Upload binaries to release 25 | uses: svenstaro/upload-release-action@v2 26 | with: 27 | repo_token: ${{ secrets.GITHUB_TOKEN }} 28 | file: result/bin/* 29 | prerelease: true 30 | release_name: latest 31 | tag: ${{ github.ref }} 32 | overwrite: true 33 | file_glob: true 34 | body: "Latest artefacts, built from the main branch." 35 | - name: Building packages 36 | run: make packages 37 | - name: Upload packages to release 38 | uses: svenstaro/upload-release-action@v2 39 | with: 40 | repo_token: ${{ secrets.GITHUB_TOKEN }} 41 | file: result/* 42 | prerelease: true 43 | release_name: latest 44 | tag: ${{ github.ref }} 45 | overwrite: true 46 | file_glob: true 47 | body: "Latest artefacts, built from the main branch." 48 | - run: make publish DOCKER_USERNAME=${{ secrets.DOCKER_USERNAME }} DOCKER_PASSWORD=${{ secrets.DOCKER_PASSWORD }} 49 | name: Building and pushing containers 50 | - run: make publish documentation DOCKER_USERNAME=${{ secrets.DOCKER_USERNAME }} DOCKER_PASSWORD=${{ secrets.DOCKER_PASSWORD }} 51 | name: Building documentation 52 | - name: Deploying documentation 53 | uses: peaceiris/actions-gh-pages@v3 54 | with: 55 | github_token: ${{ secrets.GITHUB_TOKEN }} 56 | publish_dir: ./documentation 57 | -------------------------------------------------------------------------------- /.github/workflows/pr.yml: -------------------------------------------------------------------------------- 1 | name: "Test" 2 | on: 3 | pull_request: 4 | jobs: 5 | deploy: 6 | runs-on: ubuntu-latest 7 | steps: 8 | - uses: actions/checkout@v2.4.0 9 | - uses: cachix/install-nix-action@v15 10 | with: 11 | install_url: https://releases.nixos.org/nix/nix-2.13.3/install 12 | - uses: cachix/cachix-action@v12 13 | with: 14 | name: oy-toolkit 15 | - run: nix show-derivation 16 | - run: make lint build packages publish-script documentation 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /result 2 | /result-* 3 | /oy-* 4 | /publish.sh 5 | -------------------------------------------------------------------------------- /.golangci.goheader.license: -------------------------------------------------------------------------------- 1 | Copyright The o11y toolkit Authors 2 | spdx-license-identifier: apache-2.0 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at: 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | run: 2 | deadline: 5m 3 | 4 | output: 5 | sort-results: true 6 | 7 | linters: 8 | enable: 9 | - gofumpt 10 | - goimports 11 | - revive 12 | - depguard 13 | - goheader 14 | 15 | linters-settings: 16 | depguard: 17 | rules: 18 | main: 19 | deny: 20 | - pkg: sync/atomic 21 | desc: "Use go.uber.org/atomic instead of sync/atomic" 22 | - pkg: github.com/go-kit/kit/log 23 | desc: "Use github.com/go-kit/log instead of github.com/go-kit/kit/log" 24 | goimports: 25 | local-prefixes: github.com/o11ydev/oy-toolkit 26 | gofumpt: 27 | extra-rules: true 28 | goheader: 29 | template-path: .golangci.goheader.license 30 | regexp: 31 | YEAR: 202[2-9] 32 | errcheck: 33 | exclude: scripts/errcheck_excludes.txt 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # This variable is overriden by `nix develop` 2 | O11Y_NIX_SHELL_ENABLED ?= 0 3 | 4 | # Command used to run inside a `nix develop` shell. 5 | # HOME is needed for `go build`. 6 | NIX_DEVELOP = nix --extra-experimental-features nix-command develop --extra-experimental-features flakes -i --keep HOME --keep DOCKER_USERNAME --keep DOCKER_PASSWORD --keep DOCKER_REGISTRY --keep DOCKER_REPOSITORY --keep DOCKER_TAG_SUFFIX --keep DOCKER_ORG 7 | 8 | # Docker settings 9 | export DOCKER_ORG ?= o11y 10 | export DOCKER_PASSWORD ?= none 11 | export DOCKER_REGISTRY ?= quay.io 12 | export DOCKER_REPOSITORY ?= oy-toolkit 13 | export DOCKER_TAG_SUFFIX ?= 14 | export DOCKER_USERNAME ?= none 15 | 16 | # This is true if we are in `nix develop` shell. 17 | ifeq ($(O11Y_NIX_SHELL_ENABLED),1) 18 | all: lint build 19 | 20 | # Human friendly way of running tests. 21 | .PHONY: test 22 | test: lint 23 | @gotestsum ./... 24 | 25 | # Build runs tests, we do not need to explicitely add test as a dependency. 26 | .PHONY: build 27 | build: oy-toolkit 28 | 29 | .PHONY: fmt 30 | fmt: 31 | @gofumpt -l -w --extra . 32 | @goimports -w -local github.com/o11ydev/oy-toolkit . 33 | @alejandra -q *.nix 34 | 35 | .PHONY: lint 36 | lint: 37 | @golangci-lint run 38 | @alejandra -q --check *.nix 39 | 40 | oy-%: rebuild 41 | @echo ">> Building oy-$*" 42 | @nix build ".#oy-$*" 43 | 44 | .PHONY: tidy 45 | tidy: 46 | @go mod tidy -compat=1.17 -go=1.17 47 | 48 | # Shortcut to force running go build each time. 49 | .PHONY: rebuild 50 | rebuild: 51 | 52 | .PHONY: publish-script 53 | publish-script: 54 | @echo ">> Creating publishing script" 55 | @nix build ".#publish-script" -o ./publish.sh 56 | 57 | .PHONY: publish 58 | publish: publish-script 59 | @echo ">> Running publishing script" 60 | @bash -eu ./publish.sh 61 | 62 | .PHONY: documentation 63 | documentation: 64 | @echo ">> Generating documentation" 65 | @nix build ".#documentation" -o ./documentation 66 | 67 | .PHONY: packages 68 | packages: 69 | @echo ">> Generating packages" 70 | @nix build ".#nfpmPackages" 71 | 72 | .PHONY: vendorhash 73 | vendorhash: 74 | @go mod tidy -compat=1.21 -go=1.21 75 | @sed -i '/vendorSha256/s@".*"@"$(shell go mod vendor && nix hash path vendor)"@' packages.nix 76 | @rm -r vendor 77 | 78 | # If we are not in a `nix develop` shell, automatically run into it. 79 | else 80 | default: 81 | @$(NIX_DEVELOP) -c $(MAKE) 82 | 83 | %: 84 | @$(NIX_DEVELOP) -c $(MAKE) $* 85 | 86 | shell: 87 | @$(NIX_DEVELOP) 88 | endif 89 | 90 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | This repository contains code from third party products, including: 2 | 3 | - Prometheus 4 | https://github.com/prometheus 5 | License: apache-2.0 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # The o11y toolkit 2 | 3 | The o11y toolkit is a collection of tools to work around open source 4 | observability products, such as Prometheus, Cortex, Loki, Jaeger and 5 | OpenTelemetry. 6 | -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | 0.0.1 2 | -------------------------------------------------------------------------------- /cmd/oy-csv-to-targets/README.md: -------------------------------------------------------------------------------- 1 | # oy-csv-to-targets 2 | 3 | *oy-csv-to-targets* takes a list of Prometheus targets as a CSV file as input 4 | and produces a JSON file. 5 | 6 | The resulting JSON file can then be used by the `file_sd` discovery mechanism to 7 | dynamically discover and scrape the Prometheus targets. This allows the targets 8 | to be easily managed and updated without having to manually update the 9 | Prometheus configuration file, if your team is not fluent with JSON. 10 | 11 | If no output file is specified, the output goes to stdout. 12 | 13 | ## Example usage 14 | 15 | With the following CSV as an input: 16 | 17 | ``` 18 | ,datacenter,availability_zone 19 | prometheus1:9090,dc1,az1 20 | prometheus2:9090,dc1,az2 21 | prometheus3:9090,dc2,az1 22 | ``` 23 | 24 | The following command: 25 | 26 | ``` 27 | ./oy-csv-to-targets --input.file targets.csv --output.file targets.json 28 | ``` 29 | 30 | Would produce the following `targets.json` file: 31 | 32 | ```json 33 | [ 34 | { 35 | "labels": { 36 | "availability_zone": "az1", 37 | "datacenter": "dc1" 38 | }, 39 | "targets": [ 40 | "prometheus1:9090" 41 | ] 42 | }, 43 | { 44 | "labels": { 45 | "availability_zone": "az2", 46 | "datacenter": "dc1" 47 | }, 48 | "targets": [ 49 | "prometheus2:9090" 50 | ] 51 | }, 52 | { 53 | "labels": { 54 | "availability_zone": "az1", 55 | "datacenter": "dc2" 56 | }, 57 | "targets": [ 58 | "prometheus3:9090" 59 | ] 60 | } 61 | ] 62 | ``` 63 | 64 | You can then configure your Prometheus as follows 65 | 66 | ``` 67 | scrape_configs: 68 | - job_name: prometheus 69 | file_sd_configs: 70 | - files: [targets.json] 71 | ``` 72 | -------------------------------------------------------------------------------- /cmd/oy-csv-to-targets/main.go: -------------------------------------------------------------------------------- 1 | // Copyright The o11y toolkit Authors 2 | // spdx-license-identifier: apache-2.0 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at: 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package main 17 | 18 | import ( 19 | "bytes" 20 | "encoding/csv" 21 | "encoding/json" 22 | "fmt" 23 | "io" 24 | "os" 25 | "path/filepath" 26 | 27 | "github.com/alecthomas/kingpin/v2" 28 | 29 | promlabels "github.com/prometheus/prometheus/model/labels" 30 | 31 | "github.com/o11ydev/oy-toolkit/util/cmd" 32 | ) 33 | 34 | var ( 35 | input = kingpin.Flag("input.file", "Path to a CSV file to use as an input.").PlaceHolder("input.csv").Required().String() 36 | output = kingpin.Flag("output.file", "Path to a json file to use as an output.").PlaceHolder("targets.json").String() 37 | ) 38 | 39 | type target struct { 40 | Labels promlabels.Labels `json:"labels"` 41 | Targets []string `json:"targets"` 42 | } 43 | 44 | func main() { 45 | logger := cmd.InitCmd("oy-csv-to-targets") 46 | 47 | err := csvToJSON(*input, *output) 48 | if err != nil { 49 | logger.Log("error", err.Error()) 50 | } 51 | } 52 | 53 | // Convert a CSV file to a JSON with Prometheus targets. 54 | func csvToJSON(csvFile, jsonFile string) error { 55 | f, err := os.Open(csvFile) 56 | if err != nil { 57 | return fmt.Errorf("failed to open CSV file: %w", err) 58 | } 59 | defer f.Close() 60 | 61 | r := csv.NewReader(f) 62 | 63 | // Read the first line of the CSV to get the headers (i.e. label names). 64 | headers, err := r.Read() 65 | if err != nil { 66 | return fmt.Errorf("failed to read headers from CSV: %w", err) 67 | } 68 | 69 | targets := []target{} 70 | 71 | // Read the rest of the CSV to get the target values 72 | for { 73 | record, err := r.Read() 74 | if err == io.EOF { 75 | break 76 | } 77 | if err != nil { 78 | return fmt.Errorf("failed to read record from CSV: %w", err) 79 | } 80 | 81 | // Create a map to hold the label values 82 | labels := map[string]string{} 83 | 84 | // Loop through the headers and record values to create the label 85 | // values. 86 | // Ignore the first one. 87 | for i, header := range headers { 88 | if i > 0 { 89 | labels[header] = record[i] 90 | } 91 | } 92 | 93 | // Create a new Prometheus target using the label values and the first 94 | // record value as the target address. 95 | t := target{ 96 | Labels: promlabels.FromMap(labels), 97 | Targets: []string{record[0]}, 98 | } 99 | 100 | targets = append(targets, t) 101 | } 102 | 103 | b, err := json.Marshal(targets) 104 | if err != nil { 105 | return fmt.Errorf("failed to marshal targets into JSON: %w", err) 106 | } 107 | 108 | // If there is no file, pretty print to stdout. 109 | if jsonFile == "" { 110 | var out bytes.Buffer 111 | err = json.Indent(&out, b, "", " ") 112 | if err != nil { 113 | return fmt.Errorf("failed to pretty print JSON: %w", err) 114 | } 115 | 116 | fmt.Println(out.String()) 117 | return nil 118 | } 119 | 120 | // Otherwise, output to the file using a temp file. 121 | // Create a temp file in the same directory as the destination file. 122 | tmpFile, err := os.CreateTemp(filepath.Dir(jsonFile), "tmp") 123 | if err != nil { 124 | return fmt.Errorf("failed to create temp file: %w", err) 125 | } 126 | 127 | _, err = tmpFile.Write(b) 128 | if err != nil { 129 | tmpFile.Close() 130 | os.Remove(tmpFile.Name()) 131 | return fmt.Errorf("failed to write to temp file: %w", err) 132 | } 133 | 134 | err = tmpFile.Close() 135 | if err != nil { 136 | os.Remove(tmpFile.Name()) 137 | return fmt.Errorf("failed to close temp file: %w", err) 138 | } 139 | 140 | // Rename the temp file to the destination file. 141 | err = os.Rename(tmpFile.Name(), jsonFile) 142 | if err != nil { 143 | os.Remove(tmpFile.Name()) 144 | return fmt.Errorf("failed to rename temp file: %w", err) 145 | } 146 | 147 | return nil 148 | } 149 | -------------------------------------------------------------------------------- /cmd/oy-csv-to-targets/main_test.go: -------------------------------------------------------------------------------- 1 | // Copyright The o11y toolkit Authors 2 | // spdx-license-identifier: apache-2.0 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at: 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package main 17 | 18 | import ( 19 | "encoding/json" 20 | "io" 21 | "os" 22 | "os/exec" 23 | "testing" 24 | 25 | "github.com/stretchr/testify/require" 26 | ) 27 | 28 | var mainPath = os.Args[0] 29 | 30 | func TestMain(m *testing.M) { 31 | for i, arg := range os.Args { 32 | if arg == "-test.main" { 33 | os.Args = append(os.Args[:i], os.Args[i+1:]...) 34 | main() 35 | return 36 | } 37 | } 38 | 39 | exitCode := m.Run() 40 | os.Exit(exitCode) 41 | } 42 | 43 | func TestCSVToTargets(t *testing.T) { 44 | if testing.Short() { 45 | t.Skip("skipping test in short mode.") 46 | } 47 | 48 | run := exec.Command(mainPath, "-test.main", "--input.file=testdata/targets.csv", "--output.file=testdata/targets.json") 49 | 50 | // Log stderr in case of failure. 51 | stderr, err := run.StderrPipe() 52 | require.NoError(t, err) 53 | go func() { 54 | slurp, _ := io.ReadAll(stderr) 55 | t.Log(string(slurp)) 56 | }() 57 | 58 | err = run.Run() 59 | require.NoError(t, err) 60 | 61 | compareJSON(t, "testdata/expected_targets.json", "testdata/targets.json") 62 | } 63 | 64 | func compareJSON(t *testing.T, file1, file2 string) { 65 | data1, err := os.ReadFile(file1) 66 | require.NoError(t, err) 67 | 68 | data2, err := os.ReadFile(file2) 69 | require.NoError(t, err) 70 | 71 | var obj1 interface{} 72 | err = json.Unmarshal(data1, &obj1) 73 | require.NoError(t, err) 74 | 75 | var obj2 interface{} 76 | err = json.Unmarshal(data2, &obj2) 77 | require.NoError(t, err) 78 | 79 | require.Equal(t, obj1, obj2) 80 | } 81 | -------------------------------------------------------------------------------- /cmd/oy-csv-to-targets/testdata/expected_targets.json: -------------------------------------------------------------------------------- 1 | [{"labels":{"availability_zone":"az1","datacenter":"dc1"},"targets":["prometheus1:9090"]},{"labels":{"availability_zone":"az2","datacenter":"dc1"},"targets":["prometheus2:9090"]},{"labels":{"availability_zone":"az1","datacenter":"dc2"},"targets":["prometheus3:9090"]}] 2 | -------------------------------------------------------------------------------- /cmd/oy-csv-to-targets/testdata/targets.csv: -------------------------------------------------------------------------------- 1 | ,datacenter,availability_zone 2 | prometheus1:9090,dc1,az1 3 | prometheus2:9090,dc1,az2 4 | prometheus3:9090,dc2,az1 5 | -------------------------------------------------------------------------------- /cmd/oy-expose/README.md: -------------------------------------------------------------------------------- 1 | # oy-expose 2 | 3 | *oy-expose* reads a metrics file and exposes its content to be scraped by a 4 | Prometheus server. 5 | 6 | This is similar to the [Node Exporter Textfile Collector](https://github.com/prometheus/node_exporter#textfile-collector), 7 | with a few differences: 8 | - oy-expose only exposes on file. 9 | - oy-expose does not embed other collectors. 10 | 11 | ## Example usage 12 | 13 | Let's create a file called "metrics" with the following content: 14 | 15 | ``` 16 | maintenance_script_run_timestamp_seconds 1647524557 17 | maintenance_script_return_code 0 18 | ``` 19 | 20 | We can run `oy-expose`: 21 | 22 | ``` 23 | $ oy-expose --web.disable-exporter-metrics 24 | ``` 25 | 26 | And query the metrics: 27 | 28 | ``` 29 | $ curl localhost:9099/metrics 30 | # HELP maintenance_script_return_code Metric read from metrics 31 | # TYPE maintenance_script_return_code untyped 32 | maintenance_script_return_code 0 33 | # HELP maintenance_script_run_timestamp_seconds Metric read from metrics 34 | # TYPE maintenance_script_run_timestamp_seconds untyped 35 | maintenance_script_run_timestamp_seconds 1.647524557e+09 36 | # HELP node_textfile_mtime_seconds Unixtime mtime of textfiles successfully read. 37 | # TYPE node_textfile_mtime_seconds gauge 38 | node_textfile_mtime_seconds{file="metrics"} 1.647530635e+09 39 | # HELP node_textfile_scrape_error 1 if there was an error opening or reading a file, 0 otherwise 40 | # TYPE node_textfile_scrape_error gauge 41 | node_textfile_scrape_error 0 42 | ``` 43 | -------------------------------------------------------------------------------- /cmd/oy-expose/main.go: -------------------------------------------------------------------------------- 1 | // Copyright The o11y toolkit Authors 2 | // spdx-license-identifier: apache-2.0 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at: 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package main 17 | 18 | import ( 19 | "context" 20 | "os" 21 | 22 | "github.com/go-kit/log/level" 23 | "github.com/prometheus/client_golang/prometheus" 24 | 25 | "github.com/o11ydev/oy-toolkit/util/cmd" 26 | "github.com/o11ydev/oy-toolkit/util/collectors" 27 | "github.com/o11ydev/oy-toolkit/util/http" 28 | 29 | kingpin "github.com/alecthomas/kingpin/v2" 30 | ) 31 | 32 | func main() { 33 | textFile := kingpin.Arg("metrics-file", "File to read metrics from.").Default("metrics").String() 34 | logger := cmd.InitCmd("oy-expose") 35 | 36 | collector, err := collectors.NewTextFileCollector(logger, *textFile) 37 | if err != nil { 38 | level.Error(logger).Log("msg", "Error creating file collector", "err", err) 39 | os.Exit(1) 40 | } 41 | 42 | r := prometheus.NewRegistry() 43 | r.MustRegister(collector) 44 | err = http.Serve(context.Background(), logger, r) 45 | if err != nil { 46 | level.Error(logger).Log("err", err) 47 | os.Exit(1) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /cmd/oy-expose/main_test.go: -------------------------------------------------------------------------------- 1 | // Copyright The o11y toolkit Authors 2 | // spdx-license-identifier: apache-2.0 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at: 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package main 17 | 18 | import ( 19 | "io" 20 | "net/http" 21 | "net/http/httptest" 22 | "net/url" 23 | "os" 24 | "os/exec" 25 | "testing" 26 | "time" 27 | 28 | "github.com/stretchr/testify/require" 29 | ) 30 | 31 | var mainPath = os.Args[0] 32 | 33 | func TestMain(m *testing.M) { 34 | for i, arg := range os.Args { 35 | if arg == "-test.main" { 36 | os.Args = append(os.Args[:i], os.Args[i+1:]...) 37 | main() 38 | return 39 | } 40 | } 41 | 42 | exitCode := m.Run() 43 | os.Exit(exitCode) 44 | } 45 | 46 | func TestExpose(t *testing.T) { 47 | if testing.Short() { 48 | t.Skip("skipping test in short mode.") 49 | } 50 | 51 | testServer := httptest.NewServer(nil) 52 | testURL := testServer.URL 53 | testServer.Close() 54 | u, err := url.Parse(testURL) 55 | require.NoError(t, err) 56 | 57 | tmpfile, err := os.CreateTemp("", "metrics") 58 | require.NoError(t, err) 59 | defer os.Remove(tmpfile.Name()) 60 | 61 | run := exec.Command(mainPath, "-test.main", "--web.listen-address="+u.Host, tmpfile.Name()) 62 | 63 | // Log stderr in case of failure. 64 | stderr, err := run.StderrPipe() 65 | require.NoError(t, err) 66 | go func() { 67 | slurp, _ := io.ReadAll(stderr) 68 | t.Log(string(slurp)) 69 | }() 70 | 71 | err = run.Start() 72 | require.NoError(t, err) 73 | 74 | done := make(chan error, 1) 75 | go func() { done <- run.Wait() }() 76 | select { 77 | case err := <-done: 78 | t.Errorf("oy-expose should be still running: %v", err) 79 | case <-time.After(1 * time.Second): 80 | } 81 | 82 | resp, err := http.Get(testURL + "/metrics") 83 | require.NoError(t, err) 84 | body, err := io.ReadAll(resp.Body) 85 | require.NoError(t, err) 86 | require.Contains(t, string(body), "node_textfile_scrape_error 0") 87 | 88 | os.Remove(tmpfile.Name()) 89 | 90 | resp, err = http.Get(testURL + "/metrics") 91 | require.NoError(t, err) 92 | body, err = io.ReadAll(resp.Body) 93 | require.NoError(t, err) 94 | require.Contains(t, string(body), "node_textfile_scrape_error 1") 95 | 96 | select { 97 | case err := <-done: 98 | t.Errorf("oy-expose should be still running: %v", err) 99 | case <-time.After(5 * time.Second): 100 | require.NoError(t, run.Process.Kill()) 101 | <-done 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /cmd/oy-periodic-queries/README.md: -------------------------------------------------------------------------------- 1 | # oy-periodic-queries 2 | 3 | *oy-periodic-queries* is a tool that allows you to evaluate Prometheus 4 | recording rules and export the results as metrics, with defined boundaries such 5 | as monthly or weekly (*Only monthly is implemented at the moment*). 6 | 7 | It uses calendar months. 8 | 9 | With this exporter, you can easily calculate monthly resource usage, or any 10 | other metric that requires periodic evaluation. Simply specify the recording 11 | rule and time boundaries, and the exporter will run the rule and provide you 12 | with accurate, timely metrics. 13 | 14 | In the recording rule and labels, you have access to the following variables, that will be 15 | replaced: 16 | 17 | | Variable | Meaning | 18 | |----------|---------| 19 | | $RANGE | The range of the query, in duration | 20 | | $0_PC_TIMESTAMP | The start time of the query, in unix timestamp | 21 | | $x_PC_TIMESTAMP | The time located a x % of the range, in unix timestamp, x 22 | between 0 and 100 | 23 | | $100_PC_TIMESTAMP | The end time of the query, in unix timestamp | 24 | | $x_PC_RANGE | A duration that represents x % of the current range, x between 0 25 | and 100 | 26 | 27 | Other values in `${}` are formatted with [Go's 28 | Time.Format](https://pkg.go.dev/time#Time.Format), e.g. `"${2006-01}"` is turned 29 | into the month and day of the end of the range. Prepend `_` to have it relative 30 | to the start of the range: `"${_2006-01}"`. 31 | 32 | ## Configuration 33 | 34 | The syntax of a rule file is: 35 | 36 | ``` 37 | groups: 38 | [ - ] 39 | ``` 40 | 41 | ### `` 42 | 43 | ``` 44 | # The name of the group. Must be unique within a file. 45 | name: 46 | 47 | # The time period covered by the metrics. 48 | time_period: [ | default = "monthly" ] 49 | 50 | # The maximum time to look back. 51 | lookback: [ | default = "12w" ] 52 | 53 | rules: 54 | [ - ... ] 55 | ``` 56 | 57 | ### `` 58 | 59 | The syntax for recording rules is: 60 | 61 | ``` 62 | # The name of the time series to output to. Must be a valid metric name. 63 | record: 64 | 65 | # The PromQL expression to evaluate. Every evaluation cycle this is 66 | # evaluated at the current time, and the result recorded as a new set of 67 | # time series with the metric name as given by 'record'. 68 | expr: 69 | 70 | # Labels to add or overwrite before storing the result. 71 | labels: 72 | [ : ] 73 | ``` 74 | 75 | ## Example rule file 76 | 77 | A simple example rules file would be: 78 | 79 | ``` 80 | groups: 81 | - name: prometheus network usage 82 | time_period: monthly 83 | lookback: 365d 84 | include_incomplete_ranges: true 85 | rules: 86 | - expr: | 87 | increase(node_network_receive_bytes_total{instance=~"prometheus.*"}[$RANGE]) 88 | and last_over_time(node_network_receive_bytes_total{instance=~"prometheus.*"}[${1_PC_RANGE}] @ ${1_PC_TIMESTAMP}) 89 | and last_over_time(node_network_receive_bytes_total{instance=~"prometheus.*"}[${1_PC_RANGE}] @ ${100_PC_TIMESTAMP}) 90 | record: prometheus:node_network_receive_bytes_total:monthly 91 | labels: 92 | month: "${2006-01}" 93 | - expr: | 94 | increase(node_network_transmit_bytes_total{instance=~"prometheus.*"}[$RANGE]) 95 | and last_over_time(node_network_transmit_bytes_total{instance=~"prometheus.*"}[${1_PC_RANGE}] @ ${1_PC_TIMESTAMP}) 96 | and last_over_time(node_network_transmit_bytes_total{instance=~"prometheus.*"}[${1_PC_RANGE}] @ ${100_PC_TIMESTAMP}) 97 | record: prometheus:node_network_transmit_bytes_total:monthly 98 | labels: 99 | month: "${2006-01}" 100 | ``` 101 | 102 | 103 | In the queries, the following part is used to only select the metrics that 104 | existed in the beginning and the end of the different months. 105 | ``` 106 | and last_over_time(node_network_transmit_bytes_total{instance=~"prometheus.*"}[${1_PC_RANGE}] @ ${1_PC_TIMESTAMP}) 107 | and last_over_time(node_network_transmit_bytes_total{instance=~"prometheus.*"}[${1_PC_RANGE}] @ ${100_PC_TIMESTAMP}) 108 | ``` 109 | 110 | Output of /metrics, when launched with `--web.disable-exporter-metrics`: 111 | 112 | 113 | ``` 114 | # HELP periodic_rule_queries_success 115 | # TYPE periodic_rule_queries_success gauge 116 | periodic_rule_queries_success 1 117 | # HELP prometheus:node_network_receive_bytes_total:monthly 118 | # TYPE prometheus:node_network_receive_bytes_total:monthly untyped 119 | prometheus:node_network_receive_bytes_total:monthly{device="ens3",environment="o11ylab",instance="prometheus02.example.com",job="node",month="2023-01"} 2.2096172024710458e+11 120 | prometheus:node_network_receive_bytes_total:monthly{device="ens3",environment="o11ylab",instance="prometheus11.example.com",job="node",month="2023-01"} 1.4455463702554254e+11 121 | prometheus:node_network_receive_bytes_total:monthly{device="lo",environment="o11ylab",instance="prometheus02.example.com",job="node",month="2023-01"} 9.635369117702129e+08 122 | prometheus:node_network_receive_bytes_total:monthly{device="lo",environment="o11ylab",instance="prometheus11.example.com",job="node",month="2023-01"} 2.4574080228794675e+09 123 | # HELP prometheus:node_network_transmit_bytes_total:monthly 124 | # TYPE prometheus:node_network_transmit_bytes_total:monthly untyped 125 | prometheus:node_network_transmit_bytes_total:monthly{device="ens3",environment="o11ylab",instance="prometheus02.example.com",job="node",month="2023-01"} 1.8763866363794147e+11 126 | prometheus:node_network_transmit_bytes_total:monthly{device="ens3",environment="o11ylab",instance="prometheus11.example.com",job="node",month="2023-01"} 3.813239344424051e+11 127 | prometheus:node_network_transmit_bytes_total:monthly{device="lo",environment="o11ylab",instance="prometheus02.example.com",job="node",month="2023-01"} 9.635369117702129e+08 128 | prometheus:node_network_transmit_bytes_total:monthly{device="lo",environment="o11ylab",instance="prometheus11.example.com",job="node",month="2023-01"} 2.4574080228794675e+09 129 | ``` 130 | -------------------------------------------------------------------------------- /cmd/oy-periodic-queries/cache.go: -------------------------------------------------------------------------------- 1 | // Copyright The o11y toolkit Authors 2 | // spdx-license-identifier: apache-2.0 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at: 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package main 17 | 18 | import ( 19 | "sync" 20 | "time" 21 | 22 | "github.com/prometheus/common/model" 23 | ) 24 | 25 | // Cache represents a simple cache with key-value pairs. 26 | type Cache struct { 27 | mu sync.Mutex 28 | items map[string]cacheItem 29 | timeout time.Duration 30 | } 31 | 32 | type cacheItem struct { 33 | value model.Value 34 | expiry time.Time 35 | } 36 | 37 | // NewCache creates a new cache with the specified timeout duration. 38 | func NewCache(timeout time.Duration) *Cache { 39 | c := &Cache{ 40 | items: make(map[string]cacheItem), 41 | timeout: timeout, 42 | } 43 | go c.startCleanup() 44 | return c 45 | } 46 | 47 | // Get retrieves the value associated with the specified key. 48 | func (c *Cache) Get(key string) (model.Value, bool) { 49 | c.mu.Lock() 50 | defer c.mu.Unlock() 51 | item, ok := c.items[key] 52 | if !ok { 53 | return nil, false 54 | } 55 | if time.Now().After(item.expiry) { 56 | return nil, false 57 | } 58 | return item.value, true 59 | } 60 | 61 | // Set adds or updates the value associated with the specified key. 62 | func (c *Cache) Set(key string, value model.Value) { 63 | c.mu.Lock() 64 | defer c.mu.Unlock() 65 | item := cacheItem{ 66 | value: value, 67 | expiry: time.Now().Add(c.timeout), 68 | } 69 | c.items[key] = item 70 | } 71 | 72 | // startCleanup periodically removes expired items from the cache. 73 | func (c *Cache) startCleanup() { 74 | ticker := time.NewTicker(c.timeout) 75 | defer ticker.Stop() 76 | for { 77 | <-ticker.C 78 | c.mu.Lock() 79 | maxDelete := len(c.items) / 20 80 | deleted := 0 81 | for key, item := range c.items { 82 | if time.Now().After(item.expiry) { 83 | delete(c.items, key) 84 | deleted++ 85 | if deleted > maxDelete { 86 | break 87 | } 88 | } 89 | } 90 | c.mu.Unlock() 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /cmd/oy-periodic-queries/collector.go: -------------------------------------------------------------------------------- 1 | // Copyright The o11y toolkit Authors 2 | // spdx-license-identifier: apache-2.0 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at: 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package main 17 | 18 | import ( 19 | "context" 20 | "fmt" 21 | "os" 22 | "strconv" 23 | "strings" 24 | "time" 25 | 26 | "github.com/go-kit/log" 27 | "github.com/go-kit/log/level" 28 | 29 | "github.com/prometheus/client_golang/api" 30 | v1 "github.com/prometheus/client_golang/api/prometheus/v1" 31 | "github.com/prometheus/client_golang/prometheus" 32 | "github.com/prometheus/common/model" 33 | 34 | "github.com/o11ydev/oy-toolkit/util/period" 35 | ) 36 | 37 | // newGroupCollector returns a new Collector exposing metrics from rules 38 | // evaluated at periodic intervals. 39 | func newGroupsCollector(logger log.Logger, client api.Client, groups []Group) (prometheus.Collector, error) { 40 | c := &groupsCollector{ 41 | groups: groups, 42 | logger: logger, 43 | client: client, 44 | cache: NewCache(24 * time.Hour), 45 | } 46 | return c, nil 47 | } 48 | 49 | type groupsCollector struct { 50 | groups []Group 51 | logger log.Logger 52 | client api.Client 53 | cache *Cache 54 | } 55 | 56 | // Collect implements the Collector interface. 57 | func (c *groupsCollector) Collect(ch chan<- prometheus.Metric) { 58 | var lastErr error 59 | 60 | defer func() { 61 | var val float64 = 1 62 | if lastErr != nil { 63 | level.Error(c.logger).Log("err", lastErr) 64 | val = 0 65 | } 66 | ch <- prometheus.MustNewConstMetric( 67 | prometheus.NewDesc("periodic_queries_success", "", nil, nil), 68 | prometheus.GaugeValue, 69 | val, 70 | ) 71 | }() 72 | 73 | metrics := []prometheus.Metric{} 74 | now := time.Now().Local() 75 | for _, g := range c.groups { 76 | for _, p := range period.CalculatePeriods(now, g.TimePeriod, time.Duration(g.Lookback), g.IncludeIncompleteRanges) { 77 | for _, r := range g.Rules { 78 | m, err := QueryRangeToMetric(c.logger, c.cache, p, r.Expr, c.client, r.Record, r.Labels) 79 | if err != nil { 80 | lastErr = err 81 | return 82 | } 83 | metrics = append(metrics, m...) 84 | } 85 | } 86 | } 87 | for _, metric := range metrics { 88 | ch <- metric 89 | } 90 | } 91 | 92 | func (c *groupsCollector) Describe(chan<- *prometheus.Desc) { 93 | } 94 | 95 | func QueryRangeToMetric(logger log.Logger, c *Cache, p period.Period, query string, client api.Client, metricName string, additionalLabels map[string]string) ([]prometheus.Metric, error) { 96 | queryAPI := v1.NewAPI(client) 97 | 98 | pq := replaceForPeriod(p, query) 99 | 100 | var ( 101 | result model.Value 102 | err error 103 | ok bool 104 | ) 105 | 106 | var found bool 107 | key := fmt.Sprintf("%d/%s", p.End.Unix(), pq) 108 | if p.Complete { 109 | if result, ok = c.Get(key); ok { 110 | found = true 111 | } 112 | } 113 | if !found { 114 | if pq != query { 115 | level.Debug(logger).Log("msg", "replaced query", "period_start", p.Start, "period_end", p.End, "period_complete", p.Complete, "query", query, "new_query", pq) 116 | } 117 | result, _, err = queryAPI.Query(context.Background(), pq, p.End) 118 | if err != nil { 119 | return nil, err 120 | } 121 | if p.Complete { 122 | c.Set(key, result) 123 | } 124 | } 125 | 126 | var metrics []prometheus.Metric 127 | metricLabels := make(prometheus.Labels, len(additionalLabels)) 128 | for k, v := range additionalLabels { 129 | metricLabels[k] = replaceForPeriod(p, v) 130 | } 131 | 132 | for _, vec := range result.(model.Vector) { 133 | labels := make(prometheus.Labels, len(vec.Metric)+len(metricLabels)) 134 | for k, v := range vec.Metric { 135 | if k == model.MetricNameLabel { 136 | continue 137 | } 138 | labels[string(k)] = string(v) 139 | } 140 | for k, v := range metricLabels { 141 | if k == model.MetricNameLabel { 142 | continue 143 | } 144 | labels[k] = v 145 | } 146 | 147 | sample := prometheus.MustNewConstMetric( 148 | prometheus.NewDesc(metricName, "", nil, labels), 149 | prometheus.UntypedValue, 150 | float64(vec.Value), 151 | ) 152 | 153 | metrics = append(metrics, sample) 154 | } 155 | 156 | return metrics, nil 157 | } 158 | 159 | func replaceForPeriod(p period.Period, v string) string { 160 | d := p.End.Sub(p.Start) 161 | replacements := map[string]string{ 162 | "RANGE": fmt.Sprintf("%.0fs", d.Seconds()), 163 | } 164 | for i := 0; i <= 100; i++ { 165 | replacements[strconv.Itoa(i)+"_PC_TIMESTAMP"] = fmt.Sprintf("%d", p.Start.Add(time.Duration(i/100)*d).Unix()) 166 | replacements[strconv.Itoa(i)+"_PC_RANGE"] = fmt.Sprintf("%.0fs", float64(i)*d.Seconds()/100) 167 | } 168 | 169 | return os.Expand(v, func(s string) string { 170 | if s == "$" { 171 | return "$" 172 | } 173 | if v, ok := replacements[s]; ok { 174 | return v 175 | } 176 | if strings.HasPrefix(s, "_") { 177 | return p.Start.Format(strings.TrimPrefix(s, "-")) 178 | } 179 | return p.End.Format(s) 180 | }) 181 | } 182 | -------------------------------------------------------------------------------- /cmd/oy-periodic-queries/main.go: -------------------------------------------------------------------------------- 1 | // Copyright The o11y toolkit Authors 2 | // spdx-license-identifier: apache-2.0 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at: 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package main 17 | 18 | import ( 19 | "context" 20 | "os" 21 | 22 | "github.com/go-kit/log/level" 23 | "github.com/prometheus/client_golang/prometheus" 24 | 25 | "github.com/o11ydev/oy-toolkit/util/client" 26 | "github.com/o11ydev/oy-toolkit/util/cmd" 27 | "github.com/o11ydev/oy-toolkit/util/http" 28 | 29 | kingpin "github.com/alecthomas/kingpin/v2" 30 | ) 31 | 32 | func main() { 33 | ruleFiles := kingpin.Arg("rule-file", "File to read periodic recording rules from.").Required().Strings() 34 | prefetch := kingpin.Flag("prefetch", "Cache metrics before exposing them. Avoid scrape timeout.").Default("true").Bool() 35 | c := client.InitCliFlags() 36 | logger := cmd.InitCmd("oy-periodic-queries") 37 | 38 | promClient, err := client.NewClient(c) 39 | if err != nil { 40 | level.Error(logger).Log("msg", "Can't create Prometheus client", "err", err) 41 | os.Exit(1) 42 | } 43 | 44 | groups, err := loadFiles(*ruleFiles) 45 | if err != nil { 46 | level.Error(logger).Log("err", err) 47 | os.Exit(1) 48 | } 49 | collector, err := newGroupsCollector(logger, promClient, groups) 50 | if err != nil { 51 | level.Error(logger).Log("err", err) 52 | os.Exit(1) 53 | } 54 | 55 | r := prometheus.NewRegistry() 56 | if !*prefetch { 57 | r.MustRegister(collector) 58 | } else { 59 | periodicQueriesReady := prometheus.NewGauge(prometheus.GaugeOpts{ 60 | Name: "periodic_queries_ready", 61 | Help: "Wheter periodic queries have been pre-fetched.", 62 | }) 63 | periodicQueriesReady.Set(0) 64 | r.MustRegister(periodicQueriesReady) 65 | ch := make(chan prometheus.Metric) 66 | go func(ch chan prometheus.Metric) { 67 | for i := range ch { 68 | _ = i 69 | } 70 | }(ch) 71 | go func() { 72 | collector.Collect(ch) 73 | close(ch) 74 | r.MustRegister(collector) 75 | periodicQueriesReady.Set(1) 76 | }() 77 | } 78 | err = http.Serve(context.Background(), logger, r) 79 | if err != nil { 80 | level.Error(logger).Log("err", err) 81 | os.Exit(1) 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /cmd/oy-periodic-queries/model.go: -------------------------------------------------------------------------------- 1 | // Copyright The o11y toolkit Authors 2 | // spdx-license-identifier: apache-2.0 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at: 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package main 17 | 18 | import ( 19 | "fmt" 20 | "os" 21 | 22 | "github.com/prometheus/common/model" 23 | "gopkg.in/yaml.v2" 24 | ) 25 | 26 | type groupfile struct { 27 | Groups []Group `yaml:"groups"` 28 | } 29 | 30 | type Group struct { 31 | Name string `yaml:"name"` 32 | TimePeriod string `yaml:"time_period"` 33 | Lookback model.Duration `yaml:"lookback"` 34 | Rules []Rule `yaml:"rules"` 35 | IncludeIncompleteRanges bool `yaml:"include_incomplete_ranges"` 36 | } 37 | 38 | type Rule struct { 39 | Record string `yaml:"record"` 40 | Expr string `yaml:"expr"` 41 | Labels map[string]string `yaml:"labels"` 42 | } 43 | 44 | func (g *Group) Validate() error { 45 | if g.TimePeriod != "monthly" { 46 | return fmt.Errorf("Only monthly period is supported, got %q", g.TimePeriod) 47 | } 48 | return nil 49 | } 50 | 51 | func (g *Group) UnmarshalYAML(unmarshal func(interface{}) error) error { 52 | *g = Group{ 53 | IncludeIncompleteRanges: true, 54 | } 55 | type plain Group 56 | if err := unmarshal((*plain)(g)); err != nil { 57 | return err 58 | } 59 | 60 | return g.Validate() 61 | } 62 | 63 | func loadFiles(files []string) ([]Group, error) { 64 | g := make([]Group, 0) 65 | for _, file := range files { 66 | data, err := os.ReadFile(file) 67 | if err != nil { 68 | return nil, fmt.Errorf("error reading rule file %s: %w", file, err) 69 | } 70 | gf := groupfile{} 71 | err = yaml.UnmarshalStrict(data, &gf) 72 | if err != nil { 73 | return nil, fmt.Errorf("error unmarshaling rule file %s: %w", file, err) 74 | } 75 | for _, group := range gf.Groups { 76 | if err := group.Validate(); err != nil { 77 | return nil, err 78 | } 79 | g = append(g, group) 80 | } 81 | } 82 | return g, nil 83 | } 84 | -------------------------------------------------------------------------------- /cmd/oy-scrape-jitter/README.md: -------------------------------------------------------------------------------- 1 | # oy-scrape-jitter 2 | 3 | *oy-scrape-jitter* queries a Prometheus server to see how regular scrapes are. 4 | Perfect scrape alignment happens when the distance between all the scrapes are 5 | exactly the same. It enables Prometheus [delta-of-delta](https://github.com/prometheus/prometheus/blob/main/tsdb/docs/bstream.md) 6 | encoding to reduce significantly the size of the blocks. 7 | 8 | ## Example usage 9 | 10 | ```shell 11 | $ ./oy-scrape-jitter --prometheus.url=https://prometheus.demo.do.prometheus.io/ --log.results-only 12 | ts=2022-04-26T12:11:34.659Z caller=main.go:117 level=info msg="overall results" aligned_targets=0 unaligned_targets=10 max_ms=25 13 | ``` 14 | 15 | This means that the maximum deviation seen in your scrape jobs is 25ms. You 16 | could set `--scrape.timestamp-tolerance=25ms` to reduce your disk usage over 17 | time, by enabling Prometheus to correct timestamps up to 25ms. 18 | 19 | 20 | ## Prometheus limits 21 | 22 | Prometheus will only apply timestamp tolerance up to 1%. If your scrape interval 23 | is 30s, you can only adjust timestamps up to 300ms. Setting a 500ms tolerance 24 | will have no effects on jobs with a scrape interval lower than 500s, even if the 25 | deviation is tiny. 26 | 27 | ## Plotting the output 28 | 29 | By using `--plot.file=scrape.png`, you can generate a PNG file which shows the 30 | scrape timestamps jitter with an histogram. 31 | -------------------------------------------------------------------------------- /cmd/oy-scrape-jitter/main.go: -------------------------------------------------------------------------------- 1 | // Copyright The o11y toolkit Authors 2 | // spdx-license-identifier: apache-2.0 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at: 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package main 17 | 18 | import ( 19 | "context" 20 | "fmt" 21 | "image/color" 22 | "os" 23 | "time" 24 | 25 | "github.com/alecthomas/kingpin/v2" 26 | "github.com/go-kit/log" 27 | "github.com/go-kit/log/level" 28 | "github.com/prometheus/client_golang/api" 29 | apiv1 "github.com/prometheus/client_golang/api/prometheus/v1" 30 | "github.com/prometheus/common/model" 31 | "gonum.org/v1/plot" 32 | "gonum.org/v1/plot/plotter" 33 | "gonum.org/v1/plot/vg" 34 | 35 | "github.com/o11ydev/oy-toolkit/util/client" 36 | "github.com/o11ydev/oy-toolkit/util/cmd" 37 | ) 38 | 39 | var ( 40 | metric = kingpin.Flag("metric", "Metric to use to determine jitter.").Default("up").String() 41 | qtime = kingpin.Flag("query.timestamp", "Timestamp of the query.").Int64() 42 | png = kingpin.Flag("plot.file", "Path to a file to write an image of the results.").PlaceHolder("file.png").String() 43 | plotLog = kingpin.Flag("plot.log-y", "Use logarithmic Y axis.").Bool() 44 | lookback = kingpin.Flag("lookback", "How much time to look in the past for scrapes.").Default("1h").Duration() 45 | divisor = kingpin.Flag("divisor", "Divisor to use to determine if a scrape is aligned.").Default("1s").Duration() 46 | unalignedOnly = kingpin.Flag("log.unaligned-only", "Only take unaligned targets in logging.").Bool() 47 | plotUnalignedOnly = kingpin.Flag("plot.unaligned-only", "Only take unaligned targets in plot.").Default("true").Bool() 48 | quiet = kingpin.Flag("log.results-only", "Only log final result.").Bool() 49 | ) 50 | 51 | func main() { 52 | c := client.InitCliFlags() 53 | logger := cmd.InitCmd("oy-scrape-jitter") 54 | 55 | promClient, err := client.NewClient(c) 56 | if err != nil { 57 | level.Error(logger).Log("msg", "Can't create Prometheus client", "err", err) 58 | os.Exit(1) 59 | } 60 | 61 | analyzeScrapeAlignment(logger, promClient) 62 | } 63 | 64 | func analyzeScrapeAlignment(logger log.Logger, promClient api.Client) { 65 | var plotValues plotter.Values 66 | 67 | tm := time.Now() 68 | if *qtime != 0 { 69 | tm = time.Unix(*qtime, 0) 70 | } 71 | 72 | api := apiv1.NewAPI(promClient) 73 | v, warnings, err := api.Query(context.Background(), fmt.Sprintf("%s[%dms]", *metric, lookback.Milliseconds()), tm) 74 | if err != nil { 75 | level.Error(logger).Log("msg", "Can't query up metrics", "err", err) 76 | os.Exit(1) 77 | } 78 | for w := range warnings { 79 | if err != nil { 80 | level.Warn(logger).Log("msg", w) 81 | } 82 | } 83 | 84 | if v.Type() != model.ValMatrix { 85 | if err != nil { 86 | level.Error(logger).Log("msg", "Wrong return type", "expected", model.ValMatrix, "got", v.Type()) 87 | os.Exit(1) 88 | } 89 | } 90 | 91 | result, _ := v.(model.Matrix) 92 | var goodTargets, badTargets int 93 | var maxTarget int64 94 | for _, r := range result { 95 | var good, bad float64 96 | var max int64 97 | var lastTs time.Time 98 | for _, s := range r.Values { 99 | if lastTs.IsZero() { 100 | lastTs = s.Timestamp.Time() 101 | continue 102 | } 103 | diff := s.Timestamp.Time().Sub(lastTs).Milliseconds() 104 | ok := diff%divisor.Milliseconds() == 0 105 | level.Debug(logger).Log("metric", r.Metric.String(), "prev", lastTs, "current", s.Timestamp.Time(), "difference", diff, "aligned", ok) 106 | if ok { 107 | good++ 108 | } else { 109 | bad++ 110 | if diff%divisor.Milliseconds() > divisor.Milliseconds()/2 { 111 | diff = divisor.Milliseconds() - diff%divisor.Milliseconds() 112 | } 113 | if diff > max { 114 | max = diff % divisor.Milliseconds() 115 | if max > maxTarget { 116 | maxTarget = max 117 | } 118 | } 119 | } 120 | if *png != "" { 121 | if !ok || !*plotUnalignedOnly { 122 | plotValues = append(plotValues, float64(diff%divisor.Milliseconds())) 123 | } 124 | } 125 | lastTs = s.Timestamp.Time() 126 | } 127 | 128 | if (bad != 0 || !*unalignedOnly) && !*quiet { 129 | level.Info(logger).Log("metric", r.Metric.String(), "aligned", good, "unaligned", bad, "max_ms", max, "pc", fmt.Sprintf("%.2f%%", 100*good/(bad+good))) 130 | } 131 | 132 | if bad == 0 { 133 | goodTargets++ 134 | } else { 135 | badTargets++ 136 | } 137 | } 138 | level.Info(logger).Log("aligned_targets", goodTargets, "unaligned_targets", badTargets, "max_ms", maxTarget) 139 | if *png != "" { 140 | err := makePlot(plotValues, *png) 141 | if err != nil { 142 | level.Error(logger).Log("msg", "Unable to plot data.", "err", err) 143 | } 144 | } 145 | } 146 | 147 | func makePlot(values plotter.Values, out string) error { 148 | p := plot.New() 149 | p.Title.Text = "Scrape timestamps jitter" 150 | p.X.Label.Text = "Timestamp difference (ms)" 151 | p.Y.Label.Text = "Scrapes" 152 | l := plot.NewLegend() 153 | l.Add("https://o11y.tools") 154 | l.Top = true 155 | l.TextStyle.Color = color.RGBA{0, 0, 0, 100} 156 | 157 | hist, err := plotter.NewHist(values, 25) 158 | if err != nil { 159 | return err 160 | } 161 | hist.FillColor = color.RGBA{255, 0, 74, 255} 162 | hist.LogY = *plotLog 163 | p.Add(hist) 164 | p.Legend = l 165 | 166 | if err := p.Save(4*vg.Inch, 3*vg.Inch, out); err != nil { 167 | return err 168 | } 169 | return nil 170 | } 171 | -------------------------------------------------------------------------------- /cmd/oy-scrape-jitter/main_test.go: -------------------------------------------------------------------------------- 1 | // Copyright The o11y toolkit Authors 2 | // spdx-license-identifier: apache-2.0 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at: 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package main 17 | 18 | import ( 19 | _ "embed" 20 | "io" 21 | "net/http" 22 | "net/http/httptest" 23 | "os" 24 | "os/exec" 25 | "testing" 26 | 27 | "github.com/stretchr/testify/require" 28 | ) 29 | 30 | //go:embed query_result.json 31 | var queryResult string 32 | 33 | var mainPath = os.Args[0] 34 | 35 | func TestMain(m *testing.M) { 36 | for i, arg := range os.Args { 37 | if arg == "-test.main" { 38 | os.Args = append(os.Args[:i], os.Args[i+1:]...) 39 | main() 40 | return 41 | } 42 | } 43 | 44 | exitCode := m.Run() 45 | os.Exit(exitCode) 46 | } 47 | 48 | func TestExpose(t *testing.T) { 49 | if testing.Short() { 50 | t.Skip("skipping test in short mode.") 51 | } 52 | 53 | testServer := httptest.NewServer( 54 | http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 55 | require.Equal(t, "/api/v1/query", r.RequestURI) 56 | _, err := io.WriteString(w, queryResult) 57 | require.NoError(t, err) 58 | }), 59 | ) 60 | t.Cleanup(testServer.Close) 61 | 62 | run := exec.Command(mainPath, "-test.main", "--prometheus.url="+testServer.URL) 63 | out, err := run.Output() 64 | t.Log(string(out)) 65 | require.NoError(t, err) 66 | 67 | require.Contains(t, string(out), "level=info aligned_targets=4 unaligned_targets=6 max_ms=27") 68 | } 69 | -------------------------------------------------------------------------------- /default.nix: -------------------------------------------------------------------------------- 1 | (import (let 2 | lock = builtins.fromJSON (builtins.readFile ./flake.lock); 3 | in 4 | fetchTarball { 5 | url = "https://github.com/edolstra/flake-compat/archive/${lock.nodes.flake-compat.locked.rev}.tar.gz"; 6 | sha256 = lock.nodes.flake-compat.locked.narHash; 7 | }) {src = ./.;}) 8 | .defaultNix 9 | -------------------------------------------------------------------------------- /docs/archetypes/default.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "{{ replace .Name "-" " " | title }}" 3 | date: {{ .Date }} 4 | draft: true 5 | --- 6 | 7 | -------------------------------------------------------------------------------- /docs/config.toml: -------------------------------------------------------------------------------- 1 | baseURL = '/' 2 | languageCode = 'en-us' 3 | title = 'O11y toolkit' 4 | 5 | [params] 6 | 7 | geekdocMenuBundle = true 8 | geekdocRepo = "https://github.com/o11ydev/oy-toolkit" 9 | 10 | [params.geekdocContentLicense] 11 | name = "CC BY-SA 4.0" 12 | link = "https://creativecommons.org/licenses/by-sa/4.0/" 13 | -------------------------------------------------------------------------------- /docs/content/_index.md: -------------------------------------------------------------------------------- 1 | --- 2 | geekdocDescription: Welcome 3 | title: "O11y toolkit" 4 | 5 | --- 6 | 7 | The **O11y toolkit** is a set of utilities that help you debug, augment, 8 | and manage your open source observability stack. 9 | 10 | The toolkit is licensed under the Apache 2 license. You can use it for free. 11 | 12 | ### Available tools 13 | 14 | {{< tools >}} 15 | 16 | ### Sponsor 17 | 18 | This toolkit is sponsored by [O11y](https://o11y.eu). O11y provides 19 | professional [support](https://o11y.eu/support/) and [services](https://o11y.eu/services/) 20 | for your Open Source observability stack. 21 | -------------------------------------------------------------------------------- /docs/content/httpclient.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Prometheus client configuration 3 | --- 4 | 5 | Command line tools that connect to a Prometheus server can take a file path in 6 | a `--client.config` parameter. This is useful to connect to a Prometheus server 7 | with authentication or self-signed certificates. 8 | 9 | The file is written in [YAML format](https://en.wikipedia.org/wiki/YAML), 10 | defined by the scheme described below. 11 | Brackets indicate that a parameter is optional. For non-list parameters the 12 | value is set to the specified default. 13 | 14 | Generic placeholders are defined as follows: 15 | 16 | * ``: a boolean that can take the values `true` or `false` 17 | * ``: a valid path in the current working directory 18 | * ``: a regular string that is a secret, such as a password 19 | * ``: a regular string 20 | 21 | The other placeholders are specified separately. 22 | 23 | ``` 24 | # Sets the `Authorization` header on every request with the 25 | # configured username an. 26 | # password and password_file are mutually exclusive. 27 | basic_auth: 28 | [ username: ] 29 | [ password: ] 30 | [ password_file: ] 31 | 32 | # Sets the `Authorization` header on every request with 33 | # the configured credentials. 34 | authorization: 35 | # Sets the authentication type of the request. 36 | [ type: | default: Bearer ] 37 | # Sets the credentials of the request. It is mutually exclusive with 38 | # `credentials_file`. 39 | [ credentials: ] 40 | # Sets the credentials of the request with the credentials read from the 41 | # configured file. It is mutually exclusive with `credentials`. 42 | [ credentials_file: ] 43 | 44 | # Optional OAuth 2.0 configuration. 45 | # Cannot be used at the same time as basic_auth or authorization. 46 | oauth2: 47 | [ ] 48 | 49 | # Configure whether requests follow HTTP 3xx redirects. 50 | [ follow_redirects: | default = true ] 51 | 52 | # Whether to enable HTTP2. 53 | [ enable_http2: | default: true ] 54 | 55 | # Configures the request's TLS settings. 56 | tls_config: 57 | [ ] 58 | 59 | # Optional proxy URL. 60 | [ proxy_url: ] 61 | ``` 62 | 63 | ### `` 64 | 65 | A `tls_config` allows configuring TLS connections. 66 | 67 | ```yaml 68 | # CA certificate to validate API server certificate with. 69 | [ ca_file: ] 70 | 71 | # Certificate and key files for client cert authentication to the server. 72 | [ cert_file: ] 73 | [ key_file: ] 74 | 75 | # ServerName extension to indicate the name of the server. 76 | # https://tools.ietf.org/html/rfc4366#section-3.1 77 | [ server_name: ] 78 | 79 | # Disable validation of the server certificate. 80 | [ insecure_skip_verify: ] 81 | 82 | # Minimum acceptable TLS version. Accepted values: TLS10 (TLS 1.0), TLS11 (TLS 83 | # 1.1), TLS12 (TLS 1.2), TLS13 (TLS 1.3). 84 | # If unset, Prometheus will use Go default minimum version, which is TLS 1.2. 85 | # See MinVersion in https://pkg.go.dev/crypto/tls#Config. 86 | [ min_version: ] 87 | ``` 88 | 89 | ### `` 90 | 91 | OAuth 2.0 authentication using the client credentials grant type. 92 | Prometheus fetches an access token from the specified endpoint with 93 | the given client access and secret keys. 94 | 95 | ```yaml 96 | client_id: 97 | [ client_secret: ] 98 | 99 | # Read the client secret from a file. 100 | # It is mutually exclusive with `client_secret`. 101 | [ client_secret_file: ] 102 | 103 | # Scopes for the token request. 104 | scopes: 105 | [ - ... ] 106 | 107 | # The URL to fetch the token from. 108 | token_url: 109 | 110 | # Optional parameters to append to the token URL. 111 | endpoint_params: 112 | [ : ... ] 113 | 114 | # Configures the token request's TLS settings. 115 | tls_config: 116 | [ ] 117 | 118 | # Optional proxy URL. 119 | [ proxy_url: ] 120 | ``` 121 | 122 | --- 123 | 124 | This page is derived from the [Prometheus documentation](https://prometheus.io/docs) which is licensed under [CC-BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/) © Prometheus Authors. 125 | -------------------------------------------------------------------------------- /docs/content/metricslint.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: /metrics lint 3 | --- 4 | 5 | This tool enables you to validate the format of Prometheus metrics, and make 6 | sure they can be scraped by a Prometheus server. 7 | 8 | Prometheus supports two exposition formats: the Prometheus text-based exposition 9 | format and [OpenMetrics](https://openmetrics.io). The text-based exposition 10 | format is widespread, and many applications and client libraries supports it. 11 | Additionally, it can be used from scripts, to push metrics to the 12 | [Pushgateway](https://github.com/prometheus/pushgateway) or written to `*.prom` files, to 13 | be collected by the [textfile collectors](https://github.com/prometheus/node_exporter#textfile-collector) 14 | (available in both the [Node Exporter](https://github.com/prometheus/node_exporter) and the 15 | [Windows Exporter](https://github.com/prometheus-community/windows_exporter)). 16 | 17 | Our toolkit also provides [oy-expose](/oy-expose), a standalone tool that can 18 | expose the metrics of a file to be consumed by Prometheus. 19 | 20 | ## Usage 21 | 22 | To use this tool, simply paste the content of your `*.prom` file, the body of 23 | your Pushgateway request, or the output of a `/metrics` HTTP endpoint in the 24 | following text area. 25 | Then, click on the "Lint" button. 26 | 27 | You can click on the following button to load a few metrics: 28 | 29 | {{< unsafe >}} 30 | 31 | {{< /unsafe >}} 32 | 33 | ## Security and privacy 34 | 35 | The input is parsed in your browser and is not sent to our servers. This tool is 36 | based on the official 37 | [client_golang](https://github.com/prometheus/client_golang) library and is 38 | cross compiled to [WASM](https://webassembly.org/), so that it runs natively in 39 | your browser. 40 | 41 | ## Format specification 42 | 43 | This tool uses the [Prometheus text-based exposition 44 | format](https://prometheus.io/docs/instrumenting/exposition_formats/#exposition-formats). 45 | OpenMetrics is not supported yet. 46 | 47 | Everything is run locally from your browser, we do not receive or collect your 48 | metrics. 49 | 50 | ## Command line tool 51 | 52 | This utility behaves like the `promtool check metrics` command, which is 53 | downloadable with [Prometheus](https://prometheus.io/download). 54 | 55 | ## Metrics validation 56 | 57 | {{< unsafe >}} 58 |
59 | {{< /unsafe >}} 60 | 61 | {{< hint type=caution title=Loading icon=gdoc_timer >}} 62 | The application is loading. If this warning does not disappear, please make sure 63 | that [your browser supports WASM](https://caniuse.com/wasm) and that javascript 64 | is enabled. 65 | {{< /hint >}} 66 | 67 | {{< unsafe >}} 68 |
69 | {{< /unsafe >}} 70 | 71 | {{< unsafe >}} 72 | 73 | 74 | 90 | 91 | 92 |
93 | {{< /unsafe >}} 94 | 95 | -------------------------------------------------------------------------------- /docs/content/mimircalc.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Mimir resources 3 | --- 4 | 5 | This tool helps estimate the necessary CPU, memory, and disk space for Grafana 6 | Mimir setups. Input details like the number of active series and queries per 7 | second, and the calculator provides resource requirements for each system 8 | component. It is based on [Grafana's Planning Grafana Mimir capacity 9 | requirements](https://grafana.com/docs/mimir/latest/manage/run-production-environment/planning-capacity). 10 | 11 | {{< unsafe >}} 12 | 133 |
134 | 135 |

136 | 137 | 138 |

139 | 140 | 141 |

142 | 143 | 144 |

145 | 146 | 147 |

148 | 149 | 150 |

151 |
152 | 153 |

Total Resource Requirements

154 |
155 |

Total Resources

156 |
CPU: N/A cores
157 |
Memory: N/A GB
158 |
Disk: N/A GB
159 |
160 | 161 | 162 |

Calculated Requirements

163 |
164 |

Distributor

165 |
CPU: N/A cores
166 |
Memory: N/A GB
167 | 168 |

Ingester

169 |
CPU: N/A cores
170 |
Memory: N/A GB
171 |
Disk: N/A GB
172 | 173 |

Query-frontend

174 |
CPU: N/A cores
175 |
Memory: N/A GB
176 | 177 |

Query-scheduler

178 |
CPU: N/A cores
179 |
Memory: N/A GB
180 | 181 |

Querier

182 |
CPU: N/A cores
183 |
Memory: N/A GB
184 | 185 |

Store-gateway

186 |
CPU: N/A cores
187 |
Memory: N/A GB
188 |
Disk: N/A GB
189 | 190 |

Ruler

191 |
CPU: N/A cores
192 |
Memory: N/A GB
193 | 194 |

Compactor

195 |
CPU: N/A cores
196 |
Memory: N/A GB
197 |
Disk: N/A GB
198 | 199 |

Alertmanager

200 |
CPU: N/A cores
201 |
Memory: N/A GB
202 |
203 | {{< /unsafe >}} 204 | 205 | -------------------------------------------------------------------------------- /docs/content/promqlparser.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: PromQL Parser 3 | --- 4 | 5 | This tool enables you to validate the format of PromQL queries. It also produces 6 | a prettified query. 7 | 8 | ## Usage 9 | 10 | To use this tool, simply paste the content of your query in the 11 | following text area. 12 | Then, click on the "Parse" button. 13 | 14 | You can click on the following button to load a PromQL query: 15 | 16 | {{< unsafe >}} 17 | 18 | {{< /unsafe >}} 19 | 20 | ## Security and privacy 21 | 22 | The input is parsed in your browser and is not sent to our servers. This tool is 23 | based on the official 24 | [prometheus](https://pkg.go.dev/github.com/prometheus/prometheus@main/promql/parser#Prettify) library and is 25 | cross compiled to [WASM](https://webassembly.org/), so that it runs natively in 26 | your browser. 27 | 28 | ## PromQL input 29 | 30 | {{< unsafe >}} 31 |
32 | {{< /unsafe >}} 33 | 34 | {{< hint type=caution title=Loading icon=gdoc_timer >}} 35 | The application is loading. If this warning does not disappear, please make sure 36 | that [your browser supports WASM](https://caniuse.com/wasm) and that javascript 37 | is enabled. 38 | {{< /hint >}} 39 | 40 | {{< unsafe >}} 41 |
42 | {{< /unsafe >}} 43 | 44 | {{< unsafe >}} 45 | 46 | 47 | 63 | 64 | 65 |
66 | {{< /unsafe >}} 67 | 68 | -------------------------------------------------------------------------------- /docs/content/pwgen.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Password file generator for Prometheus 3 | --- 4 | 5 | This form enables you to generate a [Prometheus web.yml 6 | file](https://prometheus.io/docs/prometheus/latest/configuration/https/) to 7 | secure your Prometheus endpoints with basic authentication. 8 | 9 | Prometheus needs passwords hashed with [bcrypt](https://en.wikipedia.org/wiki/Bcrypt). 10 | This tool hashes the passwords directly in your browser, in such a way that we 11 | do not receive the passwords you are generating. 12 | 13 | Once the file is generated, you can optionally append your TLS server 14 | configuration to the file, then start Prometheus with `--web.config.file` 15 | pointing to your newly created file. 16 | 17 | This file is also compatible with Alertmanager, Pushgateway, Node Exporter and 18 | other official exporters. 19 | 20 | ## How to 21 | 22 | Enter the usernames and the passwords, then press the `Generate` button to 23 | compute the file. 24 | 25 | You can add and remove users with the `Remove` and `Add user` buttons. 26 | 27 | 28 | ## Security and privacy 29 | 30 | The input is parsed in your browser and is not sent to our servers. This tool is 31 | cross compiled to [WASM](https://webassembly.org/), so that it runs natively in 32 | your browser. 33 | 34 | ## Users list 35 | 36 | {{< unsafe >}} 37 |
38 | {{< /unsafe >}} 39 | 40 | {{< hint type=caution title=Loading icon=gdoc_timer >}} 41 | The application is loading. If this warning does not disappear, please make sure 42 | that [your browser supports WASM](https://caniuse.com/wasm) and that javascript 43 | is enabled. 44 | {{< /hint >}} 45 | 46 | {{< unsafe >}} 47 |
48 | {{< /unsafe >}} 49 | 50 | {{< unsafe >}} 51 | 52 | 53 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 |
UsernamePassword
114 | 115 |
116 |

Cost:
Increasing cost will increase Prometheus server CPU usage and time 118 | to authenticate users.

119 |
120 | 121 | 122 | 123 | 124 |
125 | {{< /unsafe >}} 126 | 127 | -------------------------------------------------------------------------------- /docs/data/menu/extra.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | header: 3 | - name: GitHub 4 | ref: https://github.com/o11ydev/oy-toolkit 5 | icon: gdoc_github 6 | external: true 7 | - name: Support 8 | ref: https://o11y.eu/prometheus-support 9 | icon: gdoc_fire 10 | external: true 11 | -------------------------------------------------------------------------------- /docs/layouts/shortcodes/tools.html: -------------------------------------------------------------------------------- 1 |
    2 | {{ range where .Site.RegularPages "Params.Tool" "!=" nil }} 3 |
  • {{ .Params.Tool }} {{ .Params.Description | markdownify }}
  • 4 | {{ end }} 5 |
6 | -------------------------------------------------------------------------------- /docs/layouts/shortcodes/unsafe.html: -------------------------------------------------------------------------------- 1 | {{ .Inner }} 2 | -------------------------------------------------------------------------------- /docs/static/brand.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 39 | 41 | 46 | 50 | 54 | 58 | 62 | 66 | 70 | 74 | 78 | 82 | 86 | 90 | 94 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /docs/static/custom.css: -------------------------------------------------------------------------------- 1 | /* Light mode theming */ 2 | :root, 3 | :root[color-mode="light"] { 4 | --header-background: #ff004b; 5 | --header-font-color: #ffffff; 6 | 7 | --body-background: #ffffff; 8 | --body-font-color: #343a40; 9 | 10 | --mark-color: #ffab00; 11 | 12 | --button-background: #62cb97; 13 | --button-border-color: #4ec58a; 14 | 15 | --link-color: #518169; 16 | --link-color-visited: #c54e8a; 17 | 18 | --code-background: #f5f6f8; 19 | --code-accent-color: #e3e7eb; 20 | --code-accent-color-lite: #eff1f3; 21 | 22 | --code-copy-font-color: #6b7784; 23 | --code-copy-border-color: #adb4bc; 24 | --code-copy-success-color: #00c853; 25 | 26 | --accent-color: #e9ecef; 27 | --accent-color-lite: #f8f9fa; 28 | 29 | --control-icons: #b2bac1; 30 | 31 | --footer-background: #2f333e; 32 | --footer-font-color: #ffffff; 33 | --footer-link-color: #ffcc5c; 34 | --footer-link-color-visited: #ffcc5c; 35 | } 36 | @media (prefers-color-scheme: light) { 37 | :root { 38 | --header-background: #ff004b; 39 | --header-font-color: #ffffff; 40 | 41 | --body-background: #ffffff; 42 | --body-font-color: #343a40; 43 | 44 | --mark-color: #ffab00; 45 | 46 | --button-background: #62cb97; 47 | --button-border-color: #4ec58a; 48 | 49 | --link-color: #518169; 50 | --link-color-visited: #c54e8a; 51 | 52 | --code-background: #f5f6f8; 53 | --code-accent-color: #e3e7eb; 54 | --code-accent-color-lite: #eff1f3; 55 | 56 | --code-copy-font-color: #6b7784; 57 | --code-copy-border-color: #adb4bc; 58 | --code-copy-success-color: #00c853; 59 | 60 | --accent-color: #e9ecef; 61 | --accent-color-lite: #f8f9fa; 62 | 63 | --control-icons: #b2bac1; 64 | 65 | --footer-background: #2f333e; 66 | --footer-font-color: #ffffff; 67 | --footer-link-color: #ffcc5c; 68 | --footer-link-color-visited: #ffcc5c; 69 | } 70 | } 71 | 72 | /* Dark mode theming */ 73 | :root[color-mode="dark"] { 74 | --header-background: #ff004b; 75 | --header-font-color: #ffffff; 76 | 77 | --body-background: #343a40; 78 | --body-font-color: #ced3d8; 79 | 80 | --mark-color: #ffab00; 81 | 82 | --button-background: #62cb97; 83 | --button-border-color: #4ec58a; 84 | 85 | --link-color: #7ac29e; 86 | --link-color-visited: #c27a9e; 87 | 88 | --code-background: #2f353a; 89 | --code-accent-color: #262b2f; 90 | --code-accent-color-lite: #2b3035; 91 | 92 | --code-copy-font-color: #adb4bc; 93 | --code-copy-border-color: #808c98; 94 | --code-copy-success-color: #00c853; 95 | 96 | --accent-color: #2b3035; 97 | --accent-color-lite: #2f353a; 98 | 99 | --control-icons: #b2bac1; 100 | 101 | --footer-background: #2f333e; 102 | --footer-font-color: #ffffff; 103 | --footer-link-color: #ffcc5c; 104 | --footer-link-color-visited: #ffcc5c; 105 | } 106 | @media (prefers-color-scheme: dark) { 107 | :root { 108 | --header-background: #ff004b; 109 | --header-font-color: #ffffff; 110 | 111 | --body-background: #343a40; 112 | --body-font-color: #ced3d8; 113 | 114 | --mark-color: #ffab00; 115 | 116 | --button-background: #62cb97; 117 | --button-border-color: #4ec58a; 118 | 119 | --link-color: #7ac29e; 120 | --link-color-visited: #c27a9e; 121 | 122 | --code-background: #2f353a; 123 | --code-accent-color: #262b2f; 124 | --code-accent-color-lite: #2b3035; 125 | 126 | --code-copy-font-color: #adb4bc; 127 | --code-copy-border-color: #808c98; 128 | --code-copy-success-color: #00c853; 129 | 130 | --accent-color: #2b3035; 131 | --accent-color-lite: #2f353a; 132 | 133 | --control-icons: #b2bac1; 134 | 135 | --footer-background: #2f333e; 136 | --footer-font-color: #ffffff; 137 | --footer-link-color: #ffcc5c; 138 | --footer-link-color-visited: #ffcc5c; 139 | } 140 | } 141 | 142 | .gdoc-brand img { 143 | width: 15rem; 144 | height: 3rem; 145 | } 146 | 147 | .gdoc-brand > span > span { 148 | display: none; 149 | } 150 | 151 | .gdoc-header > .container { 152 | padding-top: 0.5rem; 153 | padding-bottom: 0.5rem; 154 | } 155 | 156 | @media screen and (max-width: 600px) { 157 | table.flext thead { 158 | display: none; 159 | } 160 | table.flext td { 161 | display: flex; 162 | } 163 | 164 | table.flext td.flext::before { 165 | content: attr(label); 166 | font-weight: bold; 167 | width: 120px; 168 | min-width: 120px; 169 | } 170 | } 171 | 172 | pre code { 173 | overflow-x:auto; 174 | } 175 | -------------------------------------------------------------------------------- /docs/tools-top.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/o11ydev/oy-toolkit/32f54f7af054290c7a9e3f438ad34324b590671c/docs/tools-top.md -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "flake-compat": { 4 | "flake": false, 5 | "locked": { 6 | "lastModified": 1696426674, 7 | "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=", 8 | "owner": "edolstra", 9 | "repo": "flake-compat", 10 | "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33", 11 | "type": "github" 12 | }, 13 | "original": { 14 | "owner": "edolstra", 15 | "repo": "flake-compat", 16 | "type": "github" 17 | } 18 | }, 19 | "flake-utils": { 20 | "inputs": { 21 | "systems": "systems" 22 | }, 23 | "locked": { 24 | "lastModified": 1701680307, 25 | "narHash": "sha256-kAuep2h5ajznlPMD9rnQyffWG8EM/C73lejGofXvdM8=", 26 | "owner": "numtide", 27 | "repo": "flake-utils", 28 | "rev": "4022d587cbbfd70fe950c1e2083a02621806a725", 29 | "type": "github" 30 | }, 31 | "original": { 32 | "owner": "numtide", 33 | "repo": "flake-utils", 34 | "type": "github" 35 | } 36 | }, 37 | "nixpkgs": { 38 | "locked": { 39 | "lastModified": 1702272962, 40 | "narHash": "sha256-D+zHwkwPc6oYQ4G3A1HuadopqRwUY/JkMwHz1YF7j4Q=", 41 | "owner": "NixOS", 42 | "repo": "nixpkgs", 43 | "rev": "e97b3e4186bcadf0ef1b6be22b8558eab1cdeb5d", 44 | "type": "github" 45 | }, 46 | "original": { 47 | "owner": "NixOS", 48 | "ref": "nixpkgs-unstable", 49 | "repo": "nixpkgs", 50 | "type": "github" 51 | } 52 | }, 53 | "root": { 54 | "inputs": { 55 | "flake-compat": "flake-compat", 56 | "flake-utils": "flake-utils", 57 | "nixpkgs": "nixpkgs" 58 | } 59 | }, 60 | "systems": { 61 | "locked": { 62 | "lastModified": 1681028828, 63 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 64 | "owner": "nix-systems", 65 | "repo": "default", 66 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 67 | "type": "github" 68 | }, 69 | "original": { 70 | "owner": "nix-systems", 71 | "repo": "default", 72 | "type": "github" 73 | } 74 | } 75 | }, 76 | "root": "root", 77 | "version": 7 78 | } 79 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "O11ytools"; 3 | 4 | inputs = { 5 | nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; 6 | flake-utils.url = "github:numtide/flake-utils"; 7 | flake-compat = { 8 | url = "github:edolstra/flake-compat"; 9 | flake = false; 10 | }; 11 | }; 12 | 13 | outputs = { 14 | self, 15 | nixpkgs, 16 | flake-utils, 17 | ... 18 | } @ inputs: 19 | flake-utils.lib.eachDefaultSystem (system: let 20 | pkgs = import nixpkgs { 21 | inherit system; 22 | config = import ./go.nix; 23 | }; 24 | in rec { 25 | packages = import ./packages.nix {inherit pkgs;}; 26 | defaultPackage = packages.oy-toolkit; 27 | devShell = pkgs.mkShell rec { 28 | buildInputs = [ 29 | pkgs.go 30 | pkgs.gofumpt 31 | pkgs.gotestsum 32 | pkgs.gotools 33 | pkgs.golangci-lint 34 | pkgs.git 35 | pkgs.nix 36 | pkgs.skopeo 37 | pkgs.hugo 38 | pkgs.alejandra 39 | pkgs.nfpm 40 | ]; 41 | # This variable is needed in our Makefile. 42 | O11Y_NIX_SHELL_ENABLED = "1"; 43 | }; 44 | }); 45 | nixConfig.bash-prompt = "\[nix-develop\]$ "; 46 | } 47 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/o11ydev/oy-toolkit 2 | 3 | go 1.21 4 | 5 | require ( 6 | github.com/alecthomas/kingpin/v2 v2.4.0 7 | github.com/go-kit/log v0.2.1 8 | github.com/prometheus/client_golang v1.17.0 9 | github.com/prometheus/client_model v0.5.0 10 | github.com/prometheus/common v0.45.0 11 | github.com/prometheus/exporter-toolkit v0.11.0 12 | github.com/prometheus/prometheus v0.48.1 13 | github.com/stretchr/testify v1.8.4 14 | golang.org/x/crypto v0.16.0 15 | gonum.org/v1/plot v0.14.0 16 | gopkg.in/yaml.v2 v2.4.0 17 | ) 18 | 19 | require ( 20 | git.sr.ht/~sbinet/gg v0.5.0 // indirect 21 | github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b // indirect 22 | github.com/alecthomas/units v0.0.0-20231202071711-9a357b53e9c9 // indirect 23 | github.com/beorn7/perks v1.0.1 // indirect 24 | github.com/campoy/embedmd v1.0.0 // indirect 25 | github.com/cespare/xxhash/v2 v2.2.0 // indirect 26 | github.com/coreos/go-systemd/v22 v22.5.0 // indirect 27 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect 28 | github.com/dennwc/varint v1.0.0 // indirect 29 | github.com/go-fonts/liberation v0.3.2 // indirect 30 | github.com/go-latex/latex v0.0.0-20231108140139-5c1ce85aa4ea // indirect 31 | github.com/go-logfmt/logfmt v0.6.0 // indirect 32 | github.com/go-pdf/fpdf v0.9.0 // indirect 33 | github.com/gogo/protobuf v1.3.2 // indirect 34 | github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect 35 | github.com/golang/protobuf v1.5.3 // indirect 36 | github.com/grafana/regexp v0.0.0-20221122212121-6b5c0a4cb7fd // indirect 37 | github.com/jpillora/backoff v1.0.0 // indirect 38 | github.com/json-iterator/go v1.1.12 // indirect 39 | github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect 40 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 41 | github.com/modern-go/reflect2 v1.0.2 // indirect 42 | github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f // indirect 43 | github.com/pkg/errors v0.9.1 // indirect 44 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect 45 | github.com/prometheus/procfs v0.12.0 // indirect 46 | github.com/xhit/go-str2duration/v2 v2.1.0 // indirect 47 | go.uber.org/atomic v1.11.0 // indirect 48 | golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb // indirect 49 | golang.org/x/image v0.14.0 // indirect 50 | golang.org/x/net v0.19.0 // indirect 51 | golang.org/x/oauth2 v0.15.0 // indirect 52 | golang.org/x/sync v0.5.0 // indirect 53 | golang.org/x/sys v0.15.0 // indirect 54 | golang.org/x/text v0.14.0 // indirect 55 | google.golang.org/appengine v1.6.8 // indirect 56 | google.golang.org/protobuf v1.31.0 // indirect 57 | gopkg.in/yaml.v3 v3.0.1 // indirect 58 | ) 59 | -------------------------------------------------------------------------------- /go.nix: -------------------------------------------------------------------------------- 1 | {pkgs, ...}: let 2 | go = pkgs.go_1_21; 3 | in { 4 | packageOverrides = pkgs: { 5 | buildGoModule = pkgs.buildGoModule.override {go = go;}; 6 | go = go; 7 | }; 8 | } 9 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | git.sr.ht/~sbinet/cmpimg v0.1.0 h1:E0zPRk2muWuCqSKSVZIWsgtU9pjsw3eKHi8VmQeScxo= 2 | git.sr.ht/~sbinet/cmpimg v0.1.0/go.mod h1:FU12psLbF4TfNXkKH2ZZQ29crIqoiqTZmeQ7dkp/pxE= 3 | git.sr.ht/~sbinet/gg v0.5.0 h1:6V43j30HM623V329xA9Ntq+WJrMjDxRjuAB1LFWF5m8= 4 | git.sr.ht/~sbinet/gg v0.5.0/go.mod h1:G2C0eRESqlKhS7ErsNey6HHrqU1PwsnCQlekFi9Q2Oo= 5 | github.com/Azure/azure-sdk-for-go/sdk/azcore v1.8.0 h1:9kDVnTz3vbfweTqAUmk/a/pH5pWFCHtvRpHYC0G/dcA= 6 | github.com/Azure/azure-sdk-for-go/sdk/azcore v1.8.0/go.mod h1:3Ug6Qzto9anB6mGlEdgYMDF5zHQ+wwhEaYR4s17PHMw= 7 | github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.4.0 h1:BMAjVKJM0U/CYF27gA0ZMmXGkOcvfFtD0oHVZ1TIPRI= 8 | github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.4.0/go.mod h1:1fXstnBMas5kzG+S3q8UoJcmyU6nUeunJcMDHcRYHhs= 9 | github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0 h1:sXr+ck84g/ZlZUOZiNELInmMgOsuGwdjjVkEIde0OtY= 10 | github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0/go.mod h1:okt5dMMTOFjX/aovMlrjvvXoPMBVSPzk9185BT0+eZM= 11 | github.com/AzureAD/microsoft-authentication-library-for-go v1.1.1 h1:WpB/QDNLpMw72xHJc34BNNykqSOeEJDAWkhf0u12/Jk= 12 | github.com/AzureAD/microsoft-authentication-library-for-go v1.1.1/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= 13 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 14 | github.com/ajstarks/deck v0.0.0-20200831202436-30c9fc6549a9/go.mod h1:JynElWSGnm/4RlzPXRlREEwqTHAN3T56Bv2ITsFT3gY= 15 | github.com/ajstarks/deck/generate v0.0.0-20210309230005-c3f852c02e19/go.mod h1:T13YZdzov6OU0A1+RfKZiZN9ca6VeKdBdyDV+BY97Tk= 16 | github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b h1:slYM766cy2nI3BwyRiyQj/Ud48djTMtMebDqepE95rw= 17 | github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b/go.mod h1:1KcenG0jGWcpt8ov532z81sp/kMMUG485J2InIOyADM= 18 | github.com/alecthomas/kingpin/v2 v2.4.0 h1:f48lwail6p8zpO1bC4TxtqACaGqHYA22qkHjHpqDjYY= 19 | github.com/alecthomas/kingpin/v2 v2.4.0/go.mod h1:0gyi0zQnjuFk8xrkNKamJoyUo382HRL7ATRpFZCw6tE= 20 | github.com/alecthomas/units v0.0.0-20231202071711-9a357b53e9c9 h1:ez/4by2iGztzR4L0zgAOR8lTQK9VlyBVVd7G4omaOQs= 21 | github.com/alecthomas/units v0.0.0-20231202071711-9a357b53e9c9/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE= 22 | github.com/aws/aws-sdk-go v1.45.25 h1:c4fLlh5sLdK2DCRTY1z0hyuJZU4ygxX8m1FswL6/nF4= 23 | github.com/aws/aws-sdk-go v1.45.25/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= 24 | github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= 25 | github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= 26 | github.com/campoy/embedmd v1.0.0 h1:V4kI2qTJJLf4J29RzI/MAt2c3Bl4dQSYPuflzwFH2hY= 27 | github.com/campoy/embedmd v1.0.0/go.mod h1:oxyr9RCiSXg0M3VJ3ks0UGfp98BpSSGr0kpiX3MzVl8= 28 | github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= 29 | github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 30 | github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= 31 | github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= 32 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 33 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 34 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= 35 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 36 | github.com/dennwc/varint v1.0.0 h1:kGNFFSSw8ToIy3obO/kKr8U9GZYUAxQEVuix4zfDWzE= 37 | github.com/dennwc/varint v1.0.0/go.mod h1:hnItb35rvZvJrbTALZtY/iQfDs48JKRG1RPpgziApxA= 38 | github.com/go-fonts/dejavu v0.3.2 h1:3XlHi0JBYX+Cp8n98c6qSoHrxPa4AUKDMKdrh/0sUdk= 39 | github.com/go-fonts/dejavu v0.3.2/go.mod h1:m+TzKY7ZEl09/a17t1593E4VYW8L1VaBXHzFZOIjGEY= 40 | github.com/go-fonts/latin-modern v0.3.2 h1:M+Sq24Dp0ZRPf3TctPnG1MZxRblqyWC/cRUL9WmdaFc= 41 | github.com/go-fonts/latin-modern v0.3.2/go.mod h1:9odJt4NbRrbdj4UAMuLVd4zEukf6aAEKnDaQga0whqQ= 42 | github.com/go-fonts/liberation v0.3.2 h1:XuwG0vGHFBPRRI8Qwbi5tIvR3cku9LUfZGq/Ar16wlQ= 43 | github.com/go-fonts/liberation v0.3.2/go.mod h1:N0QsDLVUQPy3UYg9XAc3Uh3UDMp2Z7M1o4+X98dXkmI= 44 | github.com/go-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU= 45 | github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= 46 | github.com/go-latex/latex v0.0.0-20231108140139-5c1ce85aa4ea h1:DfZQkvEbdmOe+JK2TMtBM+0I9GSdzE2y/L1/AmD8xKc= 47 | github.com/go-latex/latex v0.0.0-20231108140139-5c1ce85aa4ea/go.mod h1:Y7Vld91/HRbTBm7JwoI7HejdDB0u+e9AUBO9MB7yuZk= 48 | github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4= 49 | github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= 50 | github.com/go-pdf/fpdf v0.9.0 h1:PPvSaUuo1iMi9KkaAn90NuKi+P4gwMedWPHhj8YlJQw= 51 | github.com/go-pdf/fpdf v0.9.0/go.mod h1:oO8N111TkmKb9D7VvWGLvLJlaZUQVPM+6V42pp3iV4Y= 52 | github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= 53 | github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= 54 | github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= 55 | github.com/golang-jwt/jwt/v5 v5.0.0 h1:1n1XNM9hk7O9mnQoNBGolZvzebBQ7p93ULHRc28XJUE= 56 | github.com/golang-jwt/jwt/v5 v5.0.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= 57 | github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= 58 | github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= 59 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 60 | github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 61 | github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= 62 | github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 63 | github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= 64 | github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 65 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 66 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 67 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 68 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 69 | github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= 70 | github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 71 | github.com/grafana/regexp v0.0.0-20221122212121-6b5c0a4cb7fd h1:PpuIBO5P3e9hpqBD0O/HjhShYuM6XE0i/lbE6J94kww= 72 | github.com/grafana/regexp v0.0.0-20221122212121-6b5c0a4cb7fd/go.mod h1:M5qHK+eWfAv8VR/265dIuEpL3fNfeC21tXXp9itM24A= 73 | github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= 74 | github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= 75 | github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA= 76 | github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= 77 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 78 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 79 | github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= 80 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 81 | github.com/klauspost/compress v1.17.1 h1:NE3C767s2ak2bweCZo3+rdP4U/HoyVXLv/X9f2gPS5g= 82 | github.com/klauspost/compress v1.17.1/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= 83 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 84 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 85 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 86 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 87 | github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= 88 | github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= 89 | github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg= 90 | github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k= 91 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 92 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 93 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 94 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= 95 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 96 | github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU= 97 | github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= 98 | github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= 99 | github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= 100 | github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU= 101 | github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI= 102 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 103 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 104 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 105 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= 106 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 107 | github.com/prometheus/client_golang v1.17.0 h1:rl2sfwZMtSthVU752MqfjQozy7blglC+1SOtjMAMh+Q= 108 | github.com/prometheus/client_golang v1.17.0/go.mod h1:VeL+gMmOAxkS2IqfCq0ZmHSL+LjWfWDUmp1mBz9JgUY= 109 | github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= 110 | github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= 111 | github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lneoxM= 112 | github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY= 113 | github.com/prometheus/common/sigv4 v0.1.0 h1:qoVebwtwwEhS85Czm2dSROY5fTo2PAPEVdDeppTwGX4= 114 | github.com/prometheus/common/sigv4 v0.1.0/go.mod h1:2Jkxxk9yYvCkE5G1sQT7GuEXm57JrvHu9k5YwTjsNtI= 115 | github.com/prometheus/exporter-toolkit v0.11.0 h1:yNTsuZ0aNCNFQ3aFTD2uhPOvr4iD7fdBvKPAEGkNf+g= 116 | github.com/prometheus/exporter-toolkit v0.11.0/go.mod h1:BVnENhnNecpwoTLiABx7mrPB/OLRIgN74qlQbV+FK1Q= 117 | github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= 118 | github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= 119 | github.com/prometheus/prometheus v0.48.1 h1:CTszphSNTXkuCG6O0IfpKdHcJkvvnAAE1GbELKS+NFk= 120 | github.com/prometheus/prometheus v0.48.1/go.mod h1:SRw624aMAxTfryAcP8rOjg4S/sHHaetx2lyJJ2nM83g= 121 | github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= 122 | github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= 123 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 124 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 125 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 126 | github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= 127 | github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 128 | github.com/xhit/go-str2duration/v2 v2.1.0 h1:lxklc02Drh6ynqX+DdPyp5pCKLUQpRT8bp8Ydu2Bstc= 129 | github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU= 130 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 131 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 132 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 133 | go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= 134 | go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= 135 | go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A= 136 | go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4= 137 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 138 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 139 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 140 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 141 | golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY= 142 | golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= 143 | golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb h1:c0vyKkb6yr3KR7jEfJaOSv4lG7xPkbN6r52aJz1d8a8= 144 | golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI= 145 | golang.org/x/image v0.14.0 h1:tNgSxAFe3jC4uYqvZdTr84SZoM1KfwdC9SKIFrLjFn4= 146 | golang.org/x/image v0.14.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE= 147 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 148 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 149 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 150 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 151 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 152 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 153 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 154 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 155 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 156 | golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= 157 | golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= 158 | golang.org/x/oauth2 v0.15.0 h1:s8pnnxNVzjWyrvYdFUQq5llS1PX2zhPXmccZv99h7uQ= 159 | golang.org/x/oauth2 v0.15.0/go.mod h1:q48ptWNTY5XWf+JNten23lcvHpLJ0ZSxF5ttTHKVCAM= 160 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 161 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 162 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 163 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 164 | golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= 165 | golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 166 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 167 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 168 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 169 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 170 | golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 171 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 172 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 173 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 174 | golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= 175 | golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 176 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 177 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 178 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 179 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 180 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 181 | golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= 182 | golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= 183 | golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= 184 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 185 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 186 | golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 187 | golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 188 | golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= 189 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 190 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 191 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 192 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 193 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 194 | gonum.org/v1/gonum v0.14.0 h1:2NiG67LD1tEH0D7kM+ps2V+fXmsAnpUeec7n8tcr4S0= 195 | gonum.org/v1/gonum v0.14.0/go.mod h1:AoWeoz0becf9QMWtE8iWXNXc27fK4fNeHNf/oMejGfU= 196 | gonum.org/v1/plot v0.14.0 h1:+LBDVFYwFe4LHhdP8coW6296MBEY4nQ+Y4vuUpJopcE= 197 | gonum.org/v1/plot v0.14.0/go.mod h1:MLdR9424SJed+5VqC6MsouEpig9pZX2VZ57H9ko2bXU= 198 | google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= 199 | google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= 200 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 201 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 202 | google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= 203 | google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= 204 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 205 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 206 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 207 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 208 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 209 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 210 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 211 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 212 | honnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las= 213 | rsc.io/pdf v0.1.1 h1:k1MczvYDUvJBe93bYd7wrZLLUEcLZAuF824/I4e5Xr4= 214 | rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= 215 | -------------------------------------------------------------------------------- /metrics: -------------------------------------------------------------------------------- 1 | ok 1 2 | -------------------------------------------------------------------------------- /packages.nix: -------------------------------------------------------------------------------- 1 | {pkgs, ...}: 2 | with pkgs; let 3 | basepkg = name: 4 | buildGoModule { 5 | name = name; 6 | src = stdenv.mkDerivation { 7 | name = "gosrc"; 8 | srcs = [./go.mod ./go.sum ./cmd ./util ./wasm]; 9 | phases = "installPhase"; 10 | installPhase = '' 11 | mkdir $out 12 | for src in $srcs; do 13 | for srcFile in $src; do 14 | cp -r $srcFile $out/$(stripHash $srcFile) 15 | done 16 | done 17 | ''; 18 | }; 19 | CGO_ENABLED = 0; 20 | vendorSha256 = "sha256-LprSLtEiRffPdgnmAzCkh8SMNzua2Z3uuLw5F1S0M9c="; 21 | #vendorSha256 = pkgs.lib.fakeSha256; 22 | subPackages = 23 | if name == "oy-toolkit" 24 | then [] 25 | else ["./cmd/${name}"]; 26 | 27 | ldflags = [ 28 | "-X github.com/prometheus/common/version.Version=${pkgs.lib.removeSuffix "\n" (builtins.readFile ./VERSION)}" 29 | "-X github.com/prometheus/common/version.Branch=n/a" 30 | "-X github.com/prometheus/common/version.Revision=n/a" 31 | "-X github.com/prometheus/common/version.BuildUser=n/a" 32 | "-X github.com/prometheus/common/version.BuildDate=n/a" 33 | ]; 34 | }; 35 | packageList = 36 | builtins.mapAttrs 37 | ( 38 | name: value: 39 | basepkg name 40 | ) 41 | (builtins.readDir ./cmd); 42 | wasmpkg = name: 43 | buildGoModule { 44 | name = name; 45 | src = stdenv.mkDerivation { 46 | name = "gosrc"; 47 | srcs = [./go.mod ./go.sum ./cmd ./util ./wasm]; 48 | phases = "installPhase"; 49 | installPhase = '' 50 | mkdir $out 51 | for src in $srcs; do 52 | for srcFile in $src; do 53 | cp -r $srcFile $out/$(stripHash $srcFile) 54 | done 55 | done 56 | ''; 57 | }; 58 | CGO_ENABLED = 0; 59 | vendorSha256 = "sha256-LprSLtEiRffPdgnmAzCkh8SMNzua2Z3uuLw5F1S0M9c="; 60 | #vendorSha256 = pkgs.lib.fakeSha256; 61 | subPackages = ["wasm/${name}"]; 62 | preBuild = '' 63 | export GOOS=js 64 | export GOARCH=wasm 65 | ''; 66 | }; 67 | wasmList = 68 | builtins.mapAttrs 69 | ( 70 | name: value: 71 | wasmpkg name 72 | ) 73 | (builtins.readDir ./wasm); 74 | dockerPackageList = 75 | lib.mapAttrs' 76 | (name: value: 77 | lib.nameValuePair 78 | "docker-${name}" 79 | (pkgs.dockerTools.buildImage { 80 | name = name; 81 | tag = "latest"; 82 | contents = [pkgs.bashInteractive (builtins.getAttr name packageList)]; 83 | config = { 84 | Entrypoint = ["/bin/${name}"]; 85 | }; 86 | })) 87 | (builtins.readDir ./cmd); 88 | in 89 | lib.recursiveUpdate 90 | (lib.recursiveUpdate packageList dockerPackageList) 91 | rec { 92 | oy-toolkit = basepkg "oy-toolkit"; 93 | publish-script = ( 94 | stdenv.mkDerivation { 95 | name = "release-script"; 96 | phases = "buildPhase"; 97 | buildPhase = 98 | pkgs.writeShellScript "publish" '' 99 | '' 100 | + ( 101 | pkgs.lib.concatMapStrings (x: "\n" + x) 102 | ( 103 | builtins.attrValues ( 104 | builtins.mapAttrs 105 | (name: value: '' 106 | echo -e "\n\n## ${name} ##\n" >> $out 107 | echo 'echo ">> ${name}"' >> $out 108 | echo 'skopeo --insecure-policy copy --dest-username "$DOCKER_USERNAME" --dest-password "$DOCKER_PASSWORD" docker-archive://${builtins.getAttr name dockerPackageList} docker://$DOCKER_REGISTRY/$DOCKER_ORG/$DOCKER_REPOSITORY:${pkgs.lib.removePrefix "docker-" name}$DOCKER_TAG_SUFFIX' >> $out 109 | '') 110 | dockerPackageList 111 | ) 112 | ) 113 | ); 114 | } 115 | ); 116 | documentation = ( 117 | let 118 | theme = pkgs.fetchzip { 119 | url = "https://github.com/thegeeklab/hugo-geekdoc/releases/download/v0.29.4/hugo-geekdoc.tar.gz"; 120 | sha256 = "sha256-Sypg9jBNbW4IeHTqDAq9ZpxgweW1BmFRFjDF51NSg/M="; 121 | stripRoot = false; 122 | }; 123 | menu = { 124 | main = [ 125 | { 126 | name = "tools"; 127 | sub = builtins.map (x: { 128 | name = x; 129 | ref = "/" + x; 130 | }) (builtins.attrNames packageList); 131 | } 132 | { 133 | name = "Configuration"; 134 | ref = "/httpclient"; 135 | } 136 | { 137 | name = "Web tools"; 138 | sub = [ 139 | { 140 | name = "/metrics lint"; 141 | ref = "/metricslint"; 142 | } 143 | { 144 | name = "Password generator"; 145 | ref = "/pwgen"; 146 | } 147 | { 148 | name = "PromQL parser"; 149 | ref = "/promqlparser"; 150 | } 151 | { 152 | name = "Mimir resources"; 153 | ref = "/mimircalc"; 154 | } 155 | ]; 156 | } 157 | ]; 158 | }; 159 | menuFile = pkgs.writeTextFile { 160 | name = "menu"; 161 | text = builtins.toJSON menu; 162 | }; 163 | wasmFiles = stdenv.mkDerivation { 164 | name = "wasmFiles"; 165 | phases = "buildPhase"; 166 | buildPhase = 167 | pkgs.writeShellScript "wasmFiles" '' 168 | mkdir $out 169 | '' 170 | + ( 171 | pkgs.lib.concatMapStrings (x: "\n" + x) 172 | ( 173 | builtins.attrValues ( 174 | builtins.mapAttrs 175 | (name: value: '' 176 | cp ${builtins.getAttr name wasmList}/bin/js_wasm/${name} $out/${name}.wasm 177 | '') 178 | wasmList 179 | ) 180 | ) 181 | ); 182 | }; 183 | commandDocs = stdenv.mkDerivation { 184 | name = "commandDocs"; 185 | phases = "buildPhase"; 186 | buildPhase = 187 | pkgs.writeShellScript "commandDocs" '' 188 | mkdir $out 189 | '' 190 | + ( 191 | pkgs.lib.concatMapStrings (x: "\n" + x) 192 | ( 193 | builtins.attrValues ( 194 | builtins.mapAttrs 195 | (name: value: let 196 | description = pkgs.writeTextFile { 197 | name = "description"; 198 | text = pkgs.lib.removePrefix "# ${name}\n" (builtins.readFile (./cmd + "/${name}/README.md")); 199 | }; 200 | in (import ./tool-documentation.nix { 201 | tool = builtins.getAttr name packageList; 202 | name = name; 203 | description = description; 204 | pkgs = pkgs; 205 | })) 206 | packageList 207 | ) 208 | ) 209 | ); 210 | }; 211 | in 212 | stdenv.mkDerivation { 213 | name = "documentation"; 214 | src = ./docs; 215 | buildInputs = [pkgs.hugo]; 216 | buildPhase = pkgs.writeShellScript "hugo" '' 217 | set -e 218 | cp ${wasmFiles}/* static/ 219 | cp ${pkgs.go_1_21}/share/go/misc/wasm/wasm_exec.js static 220 | 221 | mkdir -p data/menu 222 | cp ${menuFile} data/menu/main.yml 223 | cp -r ${commandDocs}/* content 224 | cat data/menu/main.yml 225 | hugo --theme=${theme} -d $out 226 | echo o11y.tools > $out/CNAME 227 | ''; 228 | installPhase = "true"; 229 | } 230 | ); 231 | nfpmPackages = let 232 | npmConfigurations = ( 233 | builtins.mapAttrs (name: value: 234 | pkgs.writeTextFile { 235 | name = "npm-config-${name}"; 236 | text = builtins.toJSON { 237 | name = name; 238 | arch = "amd64"; 239 | version = pkgs.lib.removeSuffix "\n" (builtins.readFile ./VERSION); 240 | maintainer = "Julien Pivotto "; 241 | description = "The o11y toolkit is a collection of tools that are useful to manage and run an observability stack."; 242 | vendor = "o11y"; 243 | contents = [ 244 | { 245 | src = (builtins.getAttr name packageList) + "/bin/${name}"; 246 | dst = "/bin/${name}"; 247 | } 248 | ]; 249 | }; 250 | }) 251 | (builtins.readDir ./cmd) 252 | ); 253 | in 254 | stdenv.mkDerivation { 255 | name = "gosrc"; 256 | buildInputs = [pkgs.nfpm oy-toolkit]; 257 | phases = "installPhase"; 258 | installPhase = 259 | '' 260 | mkdir $out 261 | '' 262 | + pkgs.lib.concatMapStrings (x: "\n" + x) 263 | ( 264 | builtins.attrValues ( 265 | builtins.mapAttrs 266 | (name: value: '' 267 | nfpm package --config ${(builtins.getAttr name npmConfigurations)} -p rpm -t $out/${name}.rpm 268 | nfpm package --config ${(builtins.getAttr name npmConfigurations)} -p deb -t $out/${name}.deb 269 | '') 270 | (builtins.readDir ./cmd) 271 | ) 272 | ); 273 | }; 274 | } 275 | -------------------------------------------------------------------------------- /scripts/errcheck_excludes.txt: -------------------------------------------------------------------------------- 1 | // Used in HTTP handlers, any error is handled by the server itself. 2 | (net/http.ResponseWriter).Write 3 | 4 | // Never check for logger errors. 5 | (github.com/go-kit/log.Logger).Log 6 | 7 | // No need to check for errors on server's shutdown. 8 | (*net/http.Server).Shutdown 9 | -------------------------------------------------------------------------------- /shell.nix: -------------------------------------------------------------------------------- 1 | (import (let 2 | lock = builtins.fromJSON (builtins.readFile ./flake.lock); 3 | in 4 | fetchTarball { 5 | url = "https://github.com/edolstra/flake-compat/archive/${lock.nodes.flake-compat.locked.rev}.tar.gz"; 6 | sha256 = lock.nodes.flake-compat.locked.narHash; 7 | }) {src = ./.;}) 8 | .shellNix 9 | -------------------------------------------------------------------------------- /tool-documentation.nix: -------------------------------------------------------------------------------- 1 | { 2 | pkgs, 3 | tool, 4 | name, 5 | description, 6 | }: let 7 | usage = pkgs.runCommand "${name} --help" {} "${tool}/bin/${name} --help &> $out"; 8 | documentation = pkgs.writeTextFile { 9 | name = "${name}-doc.md"; 10 | text = let 11 | firstParagraphDescription = builtins.elemAt (builtins.split "\n\n" (builtins.readFile description)) 0; 12 | frontmatterDescription = pkgs.lib.removePrefix "\n*${name}*" firstParagraphDescription; 13 | in '' 14 | --- 15 | title: ${name} 16 | geekdocRepo: "https://github.com/o11ydev/oy-toolkit" 17 | geekdocEditPath: "edit/main/cmd/${name}" 18 | geekdocFilePath: "README.md" 19 | tool: ${name} 20 | description: ${assert frontmatterDescription != firstParagraphDescription; builtins.toJSON frontmatterDescription} 21 | --- 22 | 23 | ${builtins.readFile ./docs/tools-top.md} 24 | 25 | ## Usage 26 | 27 | ``` 28 | ${builtins.readFile usage} 29 | ``` 30 | 31 | ## Description 32 | ${builtins.readFile description} 33 | 34 | ## Downloading 35 | 36 | {{< tabs "usage" >}} 37 | 38 | {{< tab "linux (wget)" >}} 39 | To execute **${name}** within Linux, run: 40 | ``` 41 | wget https://github.com/o11ydev/oy-toolkit/releases/download/main/${name} -O ${name} && chmod +x ${name} && ./${name} --help 42 | ``` 43 | {{< /tab >}} 44 | 45 | {{< tab "linux (deb)" >}} 46 | **${name}** is available as a `.deb` package: 47 | https://github.com/o11ydev/oy-toolkit/releases/download/main/${name}.deb 48 | {{< /tab >}} 49 | 50 | {{< tab "linux (yum)" >}} 51 | **${name}** is available as a `.rpm` package: 52 | https://github.com/o11ydev/oy-toolkit/releases/download/main/${name}.rpm 53 | {{< /tab >}} 54 | 55 | {{< tab "docker" >}} 56 | To execute **${name}** with docker, run: 57 | ``` 58 | docker run quay.io/o11y/oy-toolkit:${name} --help 59 | ``` 60 | {{< /tab >}} 61 | 62 | {{< tab "nix" >}} 63 | To execute **${name}** with nix, run: 64 | ``` 65 | nix run github:o11ydev/oy-toolkit#${name} -- --help 66 | ``` 67 | {{< /tab >}} 68 | 69 | {{< /tabs >}} 70 | ''; 71 | }; 72 | in "cat ${documentation} >> $out/${name}.md" 73 | -------------------------------------------------------------------------------- /util/client/client.go: -------------------------------------------------------------------------------- 1 | // Copyright The o11y toolkit Authors 2 | // spdx-license-identifier: apache-2.0 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at: 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package client 17 | 18 | import ( 19 | "fmt" 20 | "os" 21 | 22 | "github.com/alecthomas/kingpin/v2" 23 | "github.com/prometheus/client_golang/api" 24 | "github.com/prometheus/common/config" 25 | "gopkg.in/yaml.v2" 26 | ) 27 | 28 | type Config struct { 29 | url string 30 | file string 31 | } 32 | 33 | func InitCliFlags() *Config { 34 | var c Config 35 | kingpin.Flag("prometheus.url", "URL of the Prometheus server.").Default("http://127.0.0.1:9090").StringVar(&c.url) 36 | kingpin.Flag("client.config", "Path to a HTTP client configuration.").StringVar(&c.file) 37 | return &c 38 | } 39 | 40 | func NewClient(c *Config) (api.Client, error) { 41 | cfg := config.DefaultHTTPClientConfig 42 | if c.file != "" { 43 | dat, err := os.ReadFile(c.file) 44 | if err != nil { 45 | return nil, fmt.Errorf("error reading client config %s: %w", c.file, err) 46 | } 47 | err = yaml.UnmarshalStrict(dat, &cfg) 48 | if err != nil { 49 | return nil, fmt.Errorf("error unmarshaling client config %s: %w", c.file, err) 50 | } 51 | } 52 | rt, err := config.NewRoundTripperFromConfig(cfg, "oy-toolkit") 53 | if err != nil { 54 | return nil, fmt.Errorf("error creating roundtripper: %w", err) 55 | } 56 | return api.NewClient(api.Config{ 57 | Address: c.url, 58 | RoundTripper: rt, 59 | }) 60 | } 61 | -------------------------------------------------------------------------------- /util/cmd/cmd.go: -------------------------------------------------------------------------------- 1 | // Copyright The o11y toolkit Authors 2 | // spdx-license-identifier: apache-2.0 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at: 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package cmd 17 | 18 | import ( 19 | "os" 20 | 21 | kingpin "github.com/alecthomas/kingpin/v2" 22 | "github.com/go-kit/log" 23 | "github.com/go-kit/log/level" 24 | "github.com/prometheus/common/promlog" 25 | "github.com/prometheus/common/promlog/flag" 26 | "github.com/prometheus/common/version" 27 | ) 28 | 29 | func InitCmd(name string) log.Logger { 30 | promlogConfig := &promlog.Config{} 31 | flag.AddFlags(kingpin.CommandLine, promlogConfig) 32 | kingpin.Version(version.Print(name)) 33 | kingpin.CommandLine.UsageWriter(os.Stdout) 34 | kingpin.HelpFlag.Short('h') 35 | kingpin.Parse() 36 | return newLogger(promlogConfig) 37 | } 38 | 39 | func newLogger(config *promlog.Config) log.Logger { 40 | var l log.Logger 41 | if config.Format != nil && config.Format.String() == "json" { 42 | l = log.NewJSONLogger(log.NewSyncWriter(os.Stdout)) 43 | } else { 44 | l = log.NewLogfmtLogger(log.NewSyncWriter(os.Stdout)) 45 | } 46 | 47 | if config.Level != nil { 48 | var lvl level.Option 49 | switch config.Level.String() { 50 | case "debug": 51 | lvl = level.AllowDebug() 52 | case "info": 53 | lvl = level.AllowInfo() 54 | case "warn": 55 | lvl = level.AllowWarn() 56 | case "error": 57 | lvl = level.AllowError() 58 | default: 59 | lvl = level.AllowDebug() 60 | } 61 | l = level.NewFilter(l, lvl) 62 | } 63 | return l 64 | } 65 | -------------------------------------------------------------------------------- /util/collectors/fixtures/textfile/client_side_timestamp.out: -------------------------------------------------------------------------------- 1 | # HELP node_textfile_scrape_error 1 if there was an error opening or reading a file, 0 otherwise 2 | # TYPE node_textfile_scrape_error gauge 3 | node_textfile_scrape_error 1 4 | -------------------------------------------------------------------------------- /util/collectors/fixtures/textfile/client_side_timestamp/metrics.prom: -------------------------------------------------------------------------------- 1 | metric_with_custom_timestamp 1 1441205977284 2 | normal_metric 2 3 | -------------------------------------------------------------------------------- /util/collectors/fixtures/textfile/different_metric_types.out: -------------------------------------------------------------------------------- 1 | # HELP event_duration_seconds_total Query timings 2 | # TYPE event_duration_seconds_total summary 3 | event_duration_seconds_total{baz="inner_eval",quantile="0.5"} 1.073e-06 4 | event_duration_seconds_total{baz="inner_eval",quantile="0.9"} 1.928e-06 5 | event_duration_seconds_total{baz="inner_eval",quantile="0.99"} 4.35e-06 6 | event_duration_seconds_total_sum{baz="inner_eval"} 1.8652166505091474e+06 7 | event_duration_seconds_total_count{baz="inner_eval"} 1.492355615e+09 8 | event_duration_seconds_total{baz="prepare_time",quantile="0.5"} 4.283e-06 9 | event_duration_seconds_total{baz="prepare_time",quantile="0.9"} 7.796e-06 10 | event_duration_seconds_total{baz="prepare_time",quantile="0.99"} 2.2083e-05 11 | event_duration_seconds_total_sum{baz="prepare_time"} 840923.7919437207 12 | event_duration_seconds_total_count{baz="prepare_time"} 1.492355814e+09 13 | event_duration_seconds_total{baz="result_append",quantile="0.5"} 1.566e-06 14 | event_duration_seconds_total{baz="result_append",quantile="0.9"} 3.223e-06 15 | event_duration_seconds_total{baz="result_append",quantile="0.99"} 6.53e-06 16 | event_duration_seconds_total_sum{baz="result_append"} 4.404109951000078 17 | event_duration_seconds_total_count{baz="result_append"} 1.427647e+06 18 | event_duration_seconds_total{baz="result_sort",quantile="0.5"} 1.847e-06 19 | event_duration_seconds_total{baz="result_sort",quantile="0.9"} 2.975e-06 20 | event_duration_seconds_total{baz="result_sort",quantile="0.99"} 4.08e-06 21 | event_duration_seconds_total_sum{baz="result_sort"} 3.4123187829998307 22 | event_duration_seconds_total_count{baz="result_sort"} 1.427647e+06 23 | # HELP events_total this is a test metric 24 | # TYPE events_total counter 25 | events_total{foo="bar"} 10 26 | events_total{foo="baz"} 20 27 | # HELP node_textfile_mtime_seconds Unixtime mtime of textfiles successfully read. 28 | # TYPE node_textfile_mtime_seconds gauge 29 | node_textfile_mtime_seconds{file="fixtures/textfile/different_metric_types/metrics.prom"} 1 30 | # HELP node_textfile_scrape_error 1 if there was an error opening or reading a file, 0 otherwise 31 | # TYPE node_textfile_scrape_error gauge 32 | node_textfile_scrape_error 0 33 | -------------------------------------------------------------------------------- /util/collectors/fixtures/textfile/different_metric_types/metrics.prom: -------------------------------------------------------------------------------- 1 | # HELP events_total this is a test metric 2 | # TYPE events_total counter 3 | events_total{foo="bar"} 10 4 | events_total{foo="baz"} 20 5 | 6 | # HELP event_duration_seconds_total Query timings 7 | # TYPE event_duration_seconds_total summary 8 | event_duration_seconds_total{baz="inner_eval",quantile="0.5"} 1.073e-06 9 | event_duration_seconds_total{baz="inner_eval",quantile="0.9"} 1.928e-06 10 | event_duration_seconds_total{baz="inner_eval",quantile="0.99"} 4.35e-06 11 | event_duration_seconds_total_sum{baz="inner_eval"} 1.8652166505091474e+06 12 | event_duration_seconds_total_count{baz="inner_eval"} 1.492355615e+09 13 | event_duration_seconds_total{baz="prepare_time",quantile="0.5"} 4.283e-06 14 | event_duration_seconds_total{baz="prepare_time",quantile="0.9"} 7.796e-06 15 | event_duration_seconds_total{baz="prepare_time",quantile="0.99"} 2.2083e-05 16 | event_duration_seconds_total_sum{baz="prepare_time"} 840923.7919437207 17 | event_duration_seconds_total_count{baz="prepare_time"} 1.492355814e+09 18 | event_duration_seconds_total{baz="result_append",quantile="0.5"} 1.566e-06 19 | event_duration_seconds_total{baz="result_append",quantile="0.9"} 3.223e-06 20 | event_duration_seconds_total{baz="result_append",quantile="0.99"} 6.53e-06 21 | event_duration_seconds_total_sum{baz="result_append"} 4.404109951000078 22 | event_duration_seconds_total_count{baz="result_append"} 1.427647e+06 23 | event_duration_seconds_total{baz="result_sort",quantile="0.5"} 1.847e-06 24 | event_duration_seconds_total{baz="result_sort",quantile="0.9"} 2.975e-06 25 | event_duration_seconds_total{baz="result_sort",quantile="0.99"} 4.08e-06 26 | event_duration_seconds_total_sum{baz="result_sort"} 3.4123187829998307 27 | event_duration_seconds_total_count{baz="result_sort"} 1.427647e+06 28 | 29 | -------------------------------------------------------------------------------- /util/collectors/fixtures/textfile/glob_extra_dimension.out: -------------------------------------------------------------------------------- 1 | # HELP node_textfile_mtime_seconds Unixtime mtime of textfiles successfully read. 2 | # TYPE node_textfile_mtime_seconds gauge 3 | node_textfile_mtime_seconds{file="fixtures/textfile/histogram_extra_dimension/metrics.prom"} 1 4 | node_textfile_mtime_seconds{file="fixtures/textfile/summary_extra_dimension/metrics.prom"} 1 5 | # HELP node_textfile_scrape_error 1 if there was an error opening or reading a file, 0 otherwise 6 | # TYPE node_textfile_scrape_error gauge 7 | node_textfile_scrape_error 0 8 | # HELP prometheus_rule_evaluation_duration_seconds The duration for a rule to execute. 9 | # TYPE prometheus_rule_evaluation_duration_seconds summary 10 | prometheus_rule_evaluation_duration_seconds{handler="",rule_type="alerting",quantile="0.9"} 0.001765451 11 | prometheus_rule_evaluation_duration_seconds{handler="",rule_type="alerting",quantile="0.99"} 0.018672076 12 | prometheus_rule_evaluation_duration_seconds_sum{handler="",rule_type="alerting"} 214.85081044700146 13 | prometheus_rule_evaluation_duration_seconds_count{handler="",rule_type="alerting"} 185209 14 | prometheus_rule_evaluation_duration_seconds{handler="",rule_type="recording",quantile="0.5"} 4.3132e-05 15 | prometheus_rule_evaluation_duration_seconds{handler="",rule_type="recording",quantile="0.9"} 8.9295e-05 16 | prometheus_rule_evaluation_duration_seconds{handler="",rule_type="recording",quantile="0.99"} 0.000193657 17 | prometheus_rule_evaluation_duration_seconds_sum{handler="",rule_type="recording"} 185091.01317759082 18 | prometheus_rule_evaluation_duration_seconds_count{handler="",rule_type="recording"} 1.0020195e+08 19 | prometheus_rule_evaluation_duration_seconds{handler="foo",rule_type="alerting",quantile="0.5"} 0.000571464 20 | prometheus_rule_evaluation_duration_seconds_sum{handler="foo",rule_type="alerting"} 0 21 | prometheus_rule_evaluation_duration_seconds_count{handler="foo",rule_type="alerting"} 0 22 | # HELP prometheus_tsdb_compaction_chunk_range Final time range of chunks on their first compaction 23 | # TYPE prometheus_tsdb_compaction_chunk_range histogram 24 | prometheus_tsdb_compaction_chunk_range_bucket{foo="bar",le="100"} 0 25 | prometheus_tsdb_compaction_chunk_range_bucket{foo="bar",le="400"} 0 26 | prometheus_tsdb_compaction_chunk_range_bucket{foo="bar",le="1600"} 0 27 | prometheus_tsdb_compaction_chunk_range_bucket{foo="bar",le="6400"} 0 28 | prometheus_tsdb_compaction_chunk_range_bucket{foo="bar",le="25600"} 7 29 | prometheus_tsdb_compaction_chunk_range_bucket{foo="bar",le="102400"} 7 30 | prometheus_tsdb_compaction_chunk_range_bucket{foo="bar",le="409600"} 1.412839e+06 31 | prometheus_tsdb_compaction_chunk_range_bucket{foo="bar",le="1.6384e+06"} 1.69185e+06 32 | prometheus_tsdb_compaction_chunk_range_bucket{foo="bar",le="6.5536e+06"} 1.691853e+06 33 | prometheus_tsdb_compaction_chunk_range_bucket{foo="bar",le="2.62144e+07"} 1.691853e+06 34 | prometheus_tsdb_compaction_chunk_range_bucket{foo="bar",le="+Inf"} 1.691853e+06 35 | prometheus_tsdb_compaction_chunk_range_sum{foo="bar"} 6.71393432189e+11 36 | prometheus_tsdb_compaction_chunk_range_count{foo="bar"} 1.691853e+06 37 | prometheus_tsdb_compaction_chunk_range_bucket{foo="baz",le="100"} 0 38 | prometheus_tsdb_compaction_chunk_range_bucket{foo="baz",le="400"} 0 39 | prometheus_tsdb_compaction_chunk_range_bucket{foo="baz",le="1600"} 0 40 | prometheus_tsdb_compaction_chunk_range_bucket{foo="baz",le="6400"} 0 41 | prometheus_tsdb_compaction_chunk_range_bucket{foo="baz",le="25600"} 7 42 | prometheus_tsdb_compaction_chunk_range_bucket{foo="baz",le="102400"} 7 43 | prometheus_tsdb_compaction_chunk_range_bucket{foo="baz",le="409600"} 1.412839e+06 44 | prometheus_tsdb_compaction_chunk_range_bucket{foo="baz",le="1.6384e+06"} 1.69185e+06 45 | prometheus_tsdb_compaction_chunk_range_bucket{foo="baz",le="6.5536e+06"} 1.691853e+06 46 | prometheus_tsdb_compaction_chunk_range_bucket{foo="baz",le="2.62144e+07"} 1.691853e+06 47 | prometheus_tsdb_compaction_chunk_range_bucket{foo="baz",le="+Inf"} 1.691853e+06 48 | prometheus_tsdb_compaction_chunk_range_sum{foo="baz"} 6.71393432189e+11 49 | prometheus_tsdb_compaction_chunk_range_count{foo="baz"} 1.691853e+06 50 | -------------------------------------------------------------------------------- /util/collectors/fixtures/textfile/histogram.out: -------------------------------------------------------------------------------- 1 | # HELP node_textfile_mtime_seconds Unixtime mtime of textfiles successfully read. 2 | # TYPE node_textfile_mtime_seconds gauge 3 | node_textfile_mtime_seconds{file="fixtures/textfile/histogram/metrics.prom"} 1 4 | # HELP node_textfile_scrape_error 1 if there was an error opening or reading a file, 0 otherwise 5 | # TYPE node_textfile_scrape_error gauge 6 | node_textfile_scrape_error 0 7 | # HELP prometheus_tsdb_compaction_chunk_range Final time range of chunks on their first compaction 8 | # TYPE prometheus_tsdb_compaction_chunk_range histogram 9 | prometheus_tsdb_compaction_chunk_range_bucket{le="100"} 0 10 | prometheus_tsdb_compaction_chunk_range_bucket{le="400"} 0 11 | prometheus_tsdb_compaction_chunk_range_bucket{le="1600"} 0 12 | prometheus_tsdb_compaction_chunk_range_bucket{le="6400"} 0 13 | prometheus_tsdb_compaction_chunk_range_bucket{le="25600"} 7 14 | prometheus_tsdb_compaction_chunk_range_bucket{le="102400"} 7 15 | prometheus_tsdb_compaction_chunk_range_bucket{le="409600"} 1.412839e+06 16 | prometheus_tsdb_compaction_chunk_range_bucket{le="1.6384e+06"} 1.69185e+06 17 | prometheus_tsdb_compaction_chunk_range_bucket{le="6.5536e+06"} 1.691853e+06 18 | prometheus_tsdb_compaction_chunk_range_bucket{le="2.62144e+07"} 1.691853e+06 19 | prometheus_tsdb_compaction_chunk_range_bucket{le="+Inf"} 1.691853e+06 20 | prometheus_tsdb_compaction_chunk_range_sum 6.71393432189e+11 21 | prometheus_tsdb_compaction_chunk_range_count 1.691853e+06 22 | -------------------------------------------------------------------------------- /util/collectors/fixtures/textfile/histogram/metrics.prom: -------------------------------------------------------------------------------- 1 | # HELP prometheus_tsdb_compaction_chunk_range Final time range of chunks on their first compaction 2 | # TYPE prometheus_tsdb_compaction_chunk_range histogram 3 | prometheus_tsdb_compaction_chunk_range_bucket{le="100"} 0 4 | prometheus_tsdb_compaction_chunk_range_bucket{le="400"} 0 5 | prometheus_tsdb_compaction_chunk_range_bucket{le="1600"} 0 6 | prometheus_tsdb_compaction_chunk_range_bucket{le="6400"} 0 7 | prometheus_tsdb_compaction_chunk_range_bucket{le="25600"} 7 8 | prometheus_tsdb_compaction_chunk_range_bucket{le="102400"} 7 9 | prometheus_tsdb_compaction_chunk_range_bucket{le="409600"} 1.412839e+06 10 | prometheus_tsdb_compaction_chunk_range_bucket{le="1.6384e+06"} 1.69185e+06 11 | prometheus_tsdb_compaction_chunk_range_bucket{le="6.5536e+06"} 1.691853e+06 12 | prometheus_tsdb_compaction_chunk_range_bucket{le="2.62144e+07"} 1.691853e+06 13 | prometheus_tsdb_compaction_chunk_range_bucket{le="+Inf"} 1.691853e+06 14 | prometheus_tsdb_compaction_chunk_range_sum 6.71393432189e+11 15 | prometheus_tsdb_compaction_chunk_range_count 1.691853e+06 16 | -------------------------------------------------------------------------------- /util/collectors/fixtures/textfile/histogram_extra_dimension.out: -------------------------------------------------------------------------------- 1 | # HELP node_textfile_mtime_seconds Unixtime mtime of textfiles successfully read. 2 | # TYPE node_textfile_mtime_seconds gauge 3 | node_textfile_mtime_seconds{file="fixtures/textfile/histogram_extra_dimension/metrics.prom"} 1 4 | # HELP node_textfile_scrape_error 1 if there was an error opening or reading a file, 0 otherwise 5 | # TYPE node_textfile_scrape_error gauge 6 | node_textfile_scrape_error 0 7 | # HELP prometheus_tsdb_compaction_chunk_range Final time range of chunks on their first compaction 8 | # TYPE prometheus_tsdb_compaction_chunk_range histogram 9 | prometheus_tsdb_compaction_chunk_range_bucket{foo="bar",le="100"} 0 10 | prometheus_tsdb_compaction_chunk_range_bucket{foo="bar",le="400"} 0 11 | prometheus_tsdb_compaction_chunk_range_bucket{foo="bar",le="1600"} 0 12 | prometheus_tsdb_compaction_chunk_range_bucket{foo="bar",le="6400"} 0 13 | prometheus_tsdb_compaction_chunk_range_bucket{foo="bar",le="25600"} 7 14 | prometheus_tsdb_compaction_chunk_range_bucket{foo="bar",le="102400"} 7 15 | prometheus_tsdb_compaction_chunk_range_bucket{foo="bar",le="409600"} 1.412839e+06 16 | prometheus_tsdb_compaction_chunk_range_bucket{foo="bar",le="1.6384e+06"} 1.69185e+06 17 | prometheus_tsdb_compaction_chunk_range_bucket{foo="bar",le="6.5536e+06"} 1.691853e+06 18 | prometheus_tsdb_compaction_chunk_range_bucket{foo="bar",le="2.62144e+07"} 1.691853e+06 19 | prometheus_tsdb_compaction_chunk_range_bucket{foo="bar",le="+Inf"} 1.691853e+06 20 | prometheus_tsdb_compaction_chunk_range_sum{foo="bar"} 6.71393432189e+11 21 | prometheus_tsdb_compaction_chunk_range_count{foo="bar"} 1.691853e+06 22 | prometheus_tsdb_compaction_chunk_range_bucket{foo="baz",le="100"} 0 23 | prometheus_tsdb_compaction_chunk_range_bucket{foo="baz",le="400"} 0 24 | prometheus_tsdb_compaction_chunk_range_bucket{foo="baz",le="1600"} 0 25 | prometheus_tsdb_compaction_chunk_range_bucket{foo="baz",le="6400"} 0 26 | prometheus_tsdb_compaction_chunk_range_bucket{foo="baz",le="25600"} 7 27 | prometheus_tsdb_compaction_chunk_range_bucket{foo="baz",le="102400"} 7 28 | prometheus_tsdb_compaction_chunk_range_bucket{foo="baz",le="409600"} 1.412839e+06 29 | prometheus_tsdb_compaction_chunk_range_bucket{foo="baz",le="1.6384e+06"} 1.69185e+06 30 | prometheus_tsdb_compaction_chunk_range_bucket{foo="baz",le="6.5536e+06"} 1.691853e+06 31 | prometheus_tsdb_compaction_chunk_range_bucket{foo="baz",le="2.62144e+07"} 1.691853e+06 32 | prometheus_tsdb_compaction_chunk_range_bucket{foo="baz",le="+Inf"} 1.691853e+06 33 | prometheus_tsdb_compaction_chunk_range_sum{foo="baz"} 6.71393432189e+11 34 | prometheus_tsdb_compaction_chunk_range_count{foo="baz"} 1.691853e+06 35 | -------------------------------------------------------------------------------- /util/collectors/fixtures/textfile/histogram_extra_dimension/metrics.prom: -------------------------------------------------------------------------------- 1 | # HELP prometheus_tsdb_compaction_chunk_range Final time range of chunks on their first compaction 2 | # TYPE prometheus_tsdb_compaction_chunk_range histogram 3 | prometheus_tsdb_compaction_chunk_range_bucket{foo="bar",le="100"} 0 4 | prometheus_tsdb_compaction_chunk_range_bucket{foo="bar",le="400"} 0 5 | prometheus_tsdb_compaction_chunk_range_bucket{foo="bar",le="1600"} 0 6 | prometheus_tsdb_compaction_chunk_range_bucket{foo="bar",le="6400"} 0 7 | prometheus_tsdb_compaction_chunk_range_bucket{foo="bar",le="25600"} 7 8 | prometheus_tsdb_compaction_chunk_range_bucket{foo="bar",le="102400"} 7 9 | prometheus_tsdb_compaction_chunk_range_bucket{foo="bar",le="409600"} 1.412839e+06 10 | prometheus_tsdb_compaction_chunk_range_bucket{foo="bar",le="1.6384e+06"} 1.69185e+06 11 | prometheus_tsdb_compaction_chunk_range_bucket{foo="bar",le="6.5536e+06"} 1.691853e+06 12 | prometheus_tsdb_compaction_chunk_range_bucket{foo="bar",le="2.62144e+07"} 1.691853e+06 13 | prometheus_tsdb_compaction_chunk_range_bucket{foo="bar",le="+Inf"} 1.691853e+06 14 | prometheus_tsdb_compaction_chunk_range_sum{foo="bar"} 6.71393432189e+11 15 | prometheus_tsdb_compaction_chunk_range_count{foo="bar"} 1.691853e+06 16 | prometheus_tsdb_compaction_chunk_range_bucket{foo="baz",le="100"} 0 17 | prometheus_tsdb_compaction_chunk_range_bucket{foo="baz",le="400"} 0 18 | prometheus_tsdb_compaction_chunk_range_bucket{foo="baz",le="1600"} 0 19 | prometheus_tsdb_compaction_chunk_range_bucket{foo="baz",le="6400"} 0 20 | prometheus_tsdb_compaction_chunk_range_bucket{foo="baz",le="25600"} 7 21 | prometheus_tsdb_compaction_chunk_range_bucket{foo="baz",le="102400"} 7 22 | prometheus_tsdb_compaction_chunk_range_bucket{foo="baz",le="409600"} 1.412839e+06 23 | prometheus_tsdb_compaction_chunk_range_bucket{foo="baz",le="1.6384e+06"} 1.69185e+06 24 | prometheus_tsdb_compaction_chunk_range_bucket{foo="baz",le="6.5536e+06"} 1.691853e+06 25 | prometheus_tsdb_compaction_chunk_range_bucket{foo="baz",le="2.62144e+07"} 1.691853e+06 26 | prometheus_tsdb_compaction_chunk_range_bucket{foo="baz",le="+Inf"} 1.691853e+06 27 | prometheus_tsdb_compaction_chunk_range_sum{foo="baz"} 6.71393432189e+11 28 | prometheus_tsdb_compaction_chunk_range_count{foo="baz"} 1.691853e+06 29 | -------------------------------------------------------------------------------- /util/collectors/fixtures/textfile/inconsistent_metrics.out: -------------------------------------------------------------------------------- 1 | # HELP go_goroutines Number of goroutines that currently exist. 2 | # TYPE go_goroutines gauge 3 | go_goroutines{foo=""} 20 4 | go_goroutines{foo="bar"} 229 5 | # HELP http_requests_total Total number of HTTP requests made. 6 | # TYPE http_requests_total counter 7 | http_requests_total{baz="",code="200",foo="",handler="",method="get"} 11 8 | http_requests_total{baz="",code="200",foo="",handler="alerts",method="get"} 35 9 | http_requests_total{baz="",code="200",foo="",handler="config",method="get"} 8 10 | http_requests_total{baz="",code="200",foo="",handler="flags",method="get"} 18 11 | http_requests_total{baz="",code="200",foo="",handler="graph",method="get"} 89 12 | http_requests_total{baz="",code="200",foo="",handler="prometheus",method="get"} 17051 13 | http_requests_total{baz="",code="200",foo="",handler="query",method="get"} 401 14 | http_requests_total{baz="",code="200",foo="",handler="query_range",method="get"} 15663 15 | http_requests_total{baz="",code="200",foo="",handler="rules",method="get"} 7 16 | http_requests_total{baz="",code="200",foo="",handler="series",method="get"} 221 17 | http_requests_total{baz="",code="200",foo="",handler="static",method="get"} 1647 18 | http_requests_total{baz="",code="200",foo="",handler="status",method="get"} 12 19 | http_requests_total{baz="",code="200",foo="bar",handler="",method="get"} 325 20 | http_requests_total{baz="",code="206",foo="",handler="static",method="get"} 2 21 | http_requests_total{baz="",code="400",foo="",handler="query_range",method="get"} 40 22 | http_requests_total{baz="",code="503",foo="",handler="query_range",method="get"} 3 23 | http_requests_total{baz="bar",code="200",foo="",handler="",method="get"} 93 24 | # HELP node_textfile_mtime_seconds Unixtime mtime of textfiles successfully read. 25 | # TYPE node_textfile_mtime_seconds gauge 26 | node_textfile_mtime_seconds{file="fixtures/textfile/inconsistent_metrics/metrics.prom"} 1 27 | # HELP node_textfile_scrape_error 1 if there was an error opening or reading a file, 0 otherwise 28 | # TYPE node_textfile_scrape_error gauge 29 | node_textfile_scrape_error 0 30 | -------------------------------------------------------------------------------- /util/collectors/fixtures/textfile/inconsistent_metrics/metrics.prom: -------------------------------------------------------------------------------- 1 | # HELP http_requests_total Total number of HTTP requests made. 2 | # TYPE http_requests_total counter 3 | http_requests_total{code="200",handler="alerts",method="get"} 35 4 | http_requests_total{code="200",handler="config",method="get"} 8 5 | http_requests_total{code="200",method="get", foo="bar"} 325 6 | http_requests_total{code="200",handler="flags",method="get"} 18 7 | http_requests_total{code="200",handler="graph",method="get"} 89 8 | http_requests_total{code="200",method="get", baz="bar"} 93 9 | http_requests_total{code="200",handler="prometheus",method="get"} 17051 10 | http_requests_total{code="200",handler="query",method="get"} 401 11 | http_requests_total{code="200",handler="query_range",method="get"} 15663 12 | http_requests_total{code="200",handler="rules",method="get"} 7 13 | http_requests_total{code="200",handler="series",method="get"} 221 14 | http_requests_total{code="200",handler="static",method="get"} 1647 15 | http_requests_total{code="200",handler="status",method="get"} 12 16 | http_requests_total{code="200",method="get"} 11 17 | http_requests_total{code="206",handler="static",method="get"} 2 18 | http_requests_total{code="400",handler="query_range",method="get"} 40 19 | http_requests_total{code="503",handler="query_range",method="get"} 3 20 | 21 | # HELP go_goroutines Number of goroutines that currently exist. 22 | # TYPE go_goroutines gauge 23 | go_goroutines{foo="bar"} 229 24 | go_goroutines 20 25 | -------------------------------------------------------------------------------- /util/collectors/fixtures/textfile/nonexistent_path.out: -------------------------------------------------------------------------------- 1 | # HELP node_textfile_scrape_error 1 if there was an error opening or reading a file, 0 otherwise 2 | # TYPE node_textfile_scrape_error gauge 3 | node_textfile_scrape_error 1 4 | -------------------------------------------------------------------------------- /util/collectors/fixtures/textfile/summary.out: -------------------------------------------------------------------------------- 1 | # HELP event_duration_seconds_total Query timings 2 | # TYPE event_duration_seconds_total summary 3 | event_duration_seconds_total{baz="inner_eval",quantile="0.5"} 1.073e-06 4 | event_duration_seconds_total{baz="inner_eval",quantile="0.9"} 1.928e-06 5 | event_duration_seconds_total{baz="inner_eval",quantile="0.99"} 4.35e-06 6 | event_duration_seconds_total_sum{baz="inner_eval"} 1.8652166505091474e+06 7 | event_duration_seconds_total_count{baz="inner_eval"} 1.492355615e+09 8 | event_duration_seconds_total{baz="prepare_time",quantile="0.5"} 4.283e-06 9 | event_duration_seconds_total{baz="prepare_time",quantile="0.9"} 7.796e-06 10 | event_duration_seconds_total{baz="prepare_time",quantile="0.99"} 2.2083e-05 11 | event_duration_seconds_total_sum{baz="prepare_time"} 840923.7919437207 12 | event_duration_seconds_total_count{baz="prepare_time"} 1.492355814e+09 13 | event_duration_seconds_total{baz="result_append",quantile="0.5"} 1.566e-06 14 | event_duration_seconds_total{baz="result_append",quantile="0.9"} 3.223e-06 15 | event_duration_seconds_total{baz="result_append",quantile="0.99"} 6.53e-06 16 | event_duration_seconds_total_sum{baz="result_append"} 4.404109951000078 17 | event_duration_seconds_total_count{baz="result_append"} 1.427647e+06 18 | event_duration_seconds_total{baz="result_sort",quantile="0.5"} 1.847e-06 19 | event_duration_seconds_total{baz="result_sort",quantile="0.9"} 2.975e-06 20 | event_duration_seconds_total{baz="result_sort",quantile="0.99"} 4.08e-06 21 | event_duration_seconds_total_sum{baz="result_sort"} 3.4123187829998307 22 | event_duration_seconds_total_count{baz="result_sort"} 1.427647e+06 23 | # HELP node_textfile_mtime_seconds Unixtime mtime of textfiles successfully read. 24 | # TYPE node_textfile_mtime_seconds gauge 25 | node_textfile_mtime_seconds{file="fixtures/textfile/summary/metrics.prom"} 1 26 | # HELP node_textfile_scrape_error 1 if there was an error opening or reading a file, 0 otherwise 27 | # TYPE node_textfile_scrape_error gauge 28 | node_textfile_scrape_error 0 29 | -------------------------------------------------------------------------------- /util/collectors/fixtures/textfile/summary/metrics.prom: -------------------------------------------------------------------------------- 1 | # HELP event_duration_seconds_total Query timings 2 | # TYPE event_duration_seconds_total summary 3 | event_duration_seconds_total{baz="inner_eval",quantile="0.5"} 1.073e-06 4 | event_duration_seconds_total{baz="inner_eval",quantile="0.9"} 1.928e-06 5 | event_duration_seconds_total{baz="inner_eval",quantile="0.99"} 4.35e-06 6 | event_duration_seconds_total_sum{baz="inner_eval"} 1.8652166505091474e+06 7 | event_duration_seconds_total_count{baz="inner_eval"} 1.492355615e+09 8 | event_duration_seconds_total{baz="prepare_time",quantile="0.5"} 4.283e-06 9 | event_duration_seconds_total{baz="prepare_time",quantile="0.9"} 7.796e-06 10 | event_duration_seconds_total{baz="prepare_time",quantile="0.99"} 2.2083e-05 11 | event_duration_seconds_total_sum{baz="prepare_time"} 840923.7919437207 12 | event_duration_seconds_total_count{baz="prepare_time"} 1.492355814e+09 13 | event_duration_seconds_total{baz="result_append",quantile="0.5"} 1.566e-06 14 | event_duration_seconds_total{baz="result_append",quantile="0.9"} 3.223e-06 15 | event_duration_seconds_total{baz="result_append",quantile="0.99"} 6.53e-06 16 | event_duration_seconds_total_sum{baz="result_append"} 4.404109951000078 17 | event_duration_seconds_total_count{baz="result_append"} 1.427647e+06 18 | event_duration_seconds_total{baz="result_sort",quantile="0.5"} 1.847e-06 19 | event_duration_seconds_total{baz="result_sort",quantile="0.9"} 2.975e-06 20 | event_duration_seconds_total{baz="result_sort",quantile="0.99"} 4.08e-06 21 | event_duration_seconds_total_sum{baz="result_sort"} 3.4123187829998307 22 | event_duration_seconds_total_count{baz="result_sort"} 1.427647e+06 23 | -------------------------------------------------------------------------------- /util/collectors/fixtures/textfile/summary_extra_dimension.out: -------------------------------------------------------------------------------- 1 | # HELP node_textfile_mtime_seconds Unixtime mtime of textfiles successfully read. 2 | # TYPE node_textfile_mtime_seconds gauge 3 | node_textfile_mtime_seconds{file="fixtures/textfile/summary_extra_dimension/metrics.prom"} 1 4 | # HELP node_textfile_scrape_error 1 if there was an error opening or reading a file, 0 otherwise 5 | # TYPE node_textfile_scrape_error gauge 6 | node_textfile_scrape_error 0 7 | # HELP prometheus_rule_evaluation_duration_seconds The duration for a rule to execute. 8 | # TYPE prometheus_rule_evaluation_duration_seconds summary 9 | prometheus_rule_evaluation_duration_seconds{handler="",rule_type="alerting",quantile="0.9"} 0.001765451 10 | prometheus_rule_evaluation_duration_seconds{handler="",rule_type="alerting",quantile="0.99"} 0.018672076 11 | prometheus_rule_evaluation_duration_seconds_sum{handler="",rule_type="alerting"} 214.85081044700146 12 | prometheus_rule_evaluation_duration_seconds_count{handler="",rule_type="alerting"} 185209 13 | prometheus_rule_evaluation_duration_seconds{handler="",rule_type="recording",quantile="0.5"} 4.3132e-05 14 | prometheus_rule_evaluation_duration_seconds{handler="",rule_type="recording",quantile="0.9"} 8.9295e-05 15 | prometheus_rule_evaluation_duration_seconds{handler="",rule_type="recording",quantile="0.99"} 0.000193657 16 | prometheus_rule_evaluation_duration_seconds_sum{handler="",rule_type="recording"} 185091.01317759082 17 | prometheus_rule_evaluation_duration_seconds_count{handler="",rule_type="recording"} 1.0020195e+08 18 | prometheus_rule_evaluation_duration_seconds{handler="foo",rule_type="alerting",quantile="0.5"} 0.000571464 19 | prometheus_rule_evaluation_duration_seconds_sum{handler="foo",rule_type="alerting"} 0 20 | prometheus_rule_evaluation_duration_seconds_count{handler="foo",rule_type="alerting"} 0 21 | -------------------------------------------------------------------------------- /util/collectors/fixtures/textfile/summary_extra_dimension/metrics.prom: -------------------------------------------------------------------------------- 1 | # HELP prometheus_rule_evaluation_duration_seconds The duration for a rule to execute. 2 | # TYPE prometheus_rule_evaluation_duration_seconds summary 3 | prometheus_rule_evaluation_duration_seconds{rule_type="alerting",quantile="0.5", handler="foo"} 0.000571464 4 | prometheus_rule_evaluation_duration_seconds{rule_type="alerting",quantile="0.9"} 0.001765451 5 | prometheus_rule_evaluation_duration_seconds{rule_type="alerting",quantile="0.99"} 0.018672076 6 | prometheus_rule_evaluation_duration_seconds_sum{rule_type="alerting"} 214.85081044700146 7 | prometheus_rule_evaluation_duration_seconds_count{rule_type="alerting"} 185209 8 | prometheus_rule_evaluation_duration_seconds{rule_type="recording",quantile="0.5"} 4.3132e-05 9 | prometheus_rule_evaluation_duration_seconds{rule_type="recording",quantile="0.9"} 8.9295e-05 10 | prometheus_rule_evaluation_duration_seconds{rule_type="recording",quantile="0.99"} 0.000193657 11 | prometheus_rule_evaluation_duration_seconds_sum{rule_type="recording"} 185091.01317759082 12 | prometheus_rule_evaluation_duration_seconds_count{rule_type="recording"} 1.0020195e+08 13 | -------------------------------------------------------------------------------- /util/collectors/fixtures/textfile/two_metric_files.out: -------------------------------------------------------------------------------- 1 | # HELP node_textfile_mtime_seconds Unixtime mtime of textfiles successfully read. 2 | # TYPE node_textfile_mtime_seconds gauge 3 | node_textfile_mtime_seconds{file="fixtures/textfile/two_metric_files/metrics1.prom"} 1 4 | node_textfile_mtime_seconds{file="fixtures/textfile/two_metric_files/metrics2.prom"} 1 5 | # HELP node_textfile_scrape_error 1 if there was an error opening or reading a file, 0 otherwise 6 | # TYPE node_textfile_scrape_error gauge 7 | node_textfile_scrape_error 0 8 | # HELP testmetric1_1 Metric read from fixtures/textfile/two_metric_files/metrics1.prom 9 | # TYPE testmetric1_1 untyped 10 | testmetric1_1{foo="bar"} 10 11 | # HELP testmetric1_2 Metric read from fixtures/textfile/two_metric_files/metrics1.prom 12 | # TYPE testmetric1_2 untyped 13 | testmetric1_2{foo="baz"} 20 14 | # HELP testmetric2_1 Metric read from fixtures/textfile/two_metric_files/metrics2.prom 15 | # TYPE testmetric2_1 untyped 16 | testmetric2_1{foo="bar"} 30 17 | # HELP testmetric2_2 Metric read from fixtures/textfile/two_metric_files/metrics2.prom 18 | # TYPE testmetric2_2 untyped 19 | testmetric2_2{foo="baz"} 40 20 | -------------------------------------------------------------------------------- /util/collectors/textfile.go: -------------------------------------------------------------------------------- 1 | // Copyright The o11y toolkit Authors 2 | // spdx-license-identifier: apache-2.0 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at: 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package collectors 17 | 18 | import ( 19 | "fmt" 20 | "os" 21 | "sort" 22 | "time" 23 | 24 | "github.com/go-kit/log" 25 | "github.com/go-kit/log/level" 26 | "github.com/prometheus/client_golang/prometheus" 27 | dto "github.com/prometheus/client_model/go" 28 | "github.com/prometheus/common/expfmt" 29 | ) 30 | 31 | var mtimeDesc = prometheus.NewDesc( 32 | "node_textfile_mtime_seconds", 33 | "Unixtime mtime of textfiles successfully read.", 34 | []string{"file"}, 35 | nil, 36 | ) 37 | 38 | type textFileCollector struct { 39 | path string 40 | // Only set for testing to get predictable output. 41 | mtime *float64 42 | logger log.Logger 43 | } 44 | 45 | // NewTextFileCollector returns a new Collector exposing metrics read from files 46 | // in the given textfile directory. 47 | func NewTextFileCollector(logger log.Logger, path string) (prometheus.Collector, error) { 48 | c := &textFileCollector{ 49 | path: path, 50 | logger: logger, 51 | } 52 | return c, nil 53 | } 54 | 55 | func convertMetricFamily(metricFamily *dto.MetricFamily, ch chan<- prometheus.Metric, logger log.Logger) { 56 | var valType prometheus.ValueType 57 | var val float64 58 | 59 | allLabelNames := map[string]struct{}{} 60 | for _, metric := range metricFamily.Metric { 61 | labels := metric.GetLabel() 62 | for _, label := range labels { 63 | if _, ok := allLabelNames[label.GetName()]; !ok { 64 | allLabelNames[label.GetName()] = struct{}{} 65 | } 66 | } 67 | } 68 | 69 | for _, metric := range metricFamily.Metric { 70 | if metric.TimestampMs != nil { 71 | level.Warn(logger).Log("msg", "Ignoring unsupported custom timestamp on textfile collector metric", "metric", metric) 72 | } 73 | 74 | labels := metric.GetLabel() 75 | var names []string 76 | var values []string 77 | for _, label := range labels { 78 | names = append(names, label.GetName()) 79 | values = append(values, label.GetValue()) 80 | } 81 | 82 | for k := range allLabelNames { 83 | present := false 84 | for _, name := range names { 85 | if k == name { 86 | present = true 87 | break 88 | } 89 | } 90 | if !present { 91 | names = append(names, k) 92 | values = append(values, "") 93 | } 94 | } 95 | 96 | metricType := metricFamily.GetType() 97 | switch metricType { 98 | case dto.MetricType_COUNTER: 99 | valType = prometheus.CounterValue 100 | val = metric.Counter.GetValue() 101 | 102 | case dto.MetricType_GAUGE: 103 | valType = prometheus.GaugeValue 104 | val = metric.Gauge.GetValue() 105 | 106 | case dto.MetricType_UNTYPED: 107 | valType = prometheus.UntypedValue 108 | val = metric.Untyped.GetValue() 109 | 110 | case dto.MetricType_SUMMARY: 111 | quantiles := map[float64]float64{} 112 | for _, q := range metric.Summary.Quantile { 113 | quantiles[q.GetQuantile()] = q.GetValue() 114 | } 115 | ch <- prometheus.MustNewConstSummary( 116 | prometheus.NewDesc( 117 | *metricFamily.Name, 118 | metricFamily.GetHelp(), 119 | names, nil, 120 | ), 121 | metric.Summary.GetSampleCount(), 122 | metric.Summary.GetSampleSum(), 123 | quantiles, values..., 124 | ) 125 | case dto.MetricType_HISTOGRAM: 126 | buckets := map[float64]uint64{} 127 | for _, b := range metric.Histogram.Bucket { 128 | buckets[b.GetUpperBound()] = b.GetCumulativeCount() 129 | } 130 | ch <- prometheus.MustNewConstHistogram( 131 | prometheus.NewDesc( 132 | *metricFamily.Name, 133 | metricFamily.GetHelp(), 134 | names, nil, 135 | ), 136 | metric.Histogram.GetSampleCount(), 137 | metric.Histogram.GetSampleSum(), 138 | buckets, values..., 139 | ) 140 | default: 141 | panic("unknown metric type") 142 | } 143 | if metricType == dto.MetricType_GAUGE || metricType == dto.MetricType_COUNTER || metricType == dto.MetricType_UNTYPED { 144 | ch <- prometheus.MustNewConstMetric( 145 | prometheus.NewDesc( 146 | *metricFamily.Name, 147 | metricFamily.GetHelp(), 148 | names, nil, 149 | ), 150 | valType, val, values..., 151 | ) 152 | } 153 | } 154 | } 155 | 156 | func (c *textFileCollector) exportMTimes(mtimes map[string]time.Time, ch chan<- prometheus.Metric) { 157 | if len(mtimes) == 0 { 158 | return 159 | } 160 | 161 | // Export the mtimes of the successful files. 162 | // Sorting is needed for predictable output comparison in tests. 163 | filepaths := make([]string, 0, len(mtimes)) 164 | for path := range mtimes { 165 | filepaths = append(filepaths, path) 166 | } 167 | sort.Strings(filepaths) 168 | 169 | for _, path := range filepaths { 170 | mtime := float64(mtimes[path].UnixNano() / 1e9) 171 | if c.mtime != nil { 172 | mtime = *c.mtime 173 | } 174 | ch <- prometheus.MustNewConstMetric(mtimeDesc, prometheus.GaugeValue, mtime, path) 175 | } 176 | } 177 | 178 | func (c *textFileCollector) Describe(chan<- *prometheus.Desc) { 179 | } 180 | 181 | // Collect implements the Collector interface. 182 | func (c *textFileCollector) Collect(ch chan<- prometheus.Metric) { 183 | // Iterate over files and accumulate their metrics, but also track any 184 | // parsing errors so an error metric can be reported. 185 | var errored bool 186 | defer func() { 187 | var errVal float64 188 | if errored { 189 | errVal = 1.0 190 | } 191 | ch <- prometheus.MustNewConstMetric( 192 | prometheus.NewDesc( 193 | "node_textfile_scrape_error", 194 | "1 if there was an error opening or reading a file, 0 otherwise", 195 | nil, nil, 196 | ), 197 | prometheus.GaugeValue, errVal, 198 | ) 199 | }() 200 | 201 | mtimes := make(map[string]time.Time) 202 | //f, err := os.Stat(c.path) 203 | //if err != nil && c.path != "" { 204 | // errored = true 205 | // level.Error(c.logger).Log("msg", "failed to read textfile", "path", c.path, "err", err) 206 | // return 207 | //} 208 | 209 | mtime, err := c.processFile(c.path, ch) 210 | if err != nil { 211 | errored = true 212 | level.Error(c.logger).Log("msg", "failed to collect textfile data", "file", c.path, "err", err) 213 | return 214 | } 215 | 216 | mtimes[c.path] = *mtime 217 | c.exportMTimes(mtimes, ch) 218 | } 219 | 220 | // processFile processes a single file, returning its modification time on success. 221 | func (c *textFileCollector) processFile(path string, ch chan<- prometheus.Metric) (*time.Time, error) { 222 | f, err := os.Open(path) 223 | if err != nil { 224 | return nil, fmt.Errorf("failed to open textfile data file %q: %w", path, err) 225 | } 226 | defer f.Close() 227 | 228 | var parser expfmt.TextParser 229 | families, err := parser.TextToMetricFamilies(f) 230 | if err != nil { 231 | return nil, fmt.Errorf("failed to parse textfile data from %q: %w", path, err) 232 | } 233 | 234 | if hasTimestamps(families) { 235 | return nil, fmt.Errorf("textfile %q contains unsupported client-side timestamps, skipping entire file", path) 236 | } 237 | 238 | for _, mf := range families { 239 | if mf.Help == nil { 240 | help := fmt.Sprintf("Metric read from %s", path) 241 | mf.Help = &help 242 | } 243 | } 244 | 245 | for _, mf := range families { 246 | convertMetricFamily(mf, ch, c.logger) 247 | } 248 | 249 | // Only stat the file once it has been parsed and validated, so that 250 | // a failure does not appear fresh. 251 | stat, err := f.Stat() 252 | if err != nil { 253 | return nil, fmt.Errorf("failed to stat %q: %w", path, err) 254 | } 255 | 256 | t := stat.ModTime() 257 | return &t, nil 258 | } 259 | 260 | // hasTimestamps returns true when metrics contain unsupported timestamps. 261 | func hasTimestamps(parsedFamilies map[string]*dto.MetricFamily) bool { 262 | for _, mf := range parsedFamilies { 263 | for _, m := range mf.Metric { 264 | if m.TimestampMs != nil { 265 | return true 266 | } 267 | } 268 | } 269 | return false 270 | } 271 | -------------------------------------------------------------------------------- /util/collectors/textfile_test.go: -------------------------------------------------------------------------------- 1 | // Copyright The o11y toolkit Authors 2 | // spdx-license-identifier: apache-2.0 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at: 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package collectors 17 | 18 | import ( 19 | "net/http" 20 | "net/http/httptest" 21 | "os" 22 | "testing" 23 | 24 | "github.com/alecthomas/kingpin/v2" 25 | "github.com/go-kit/log" 26 | "github.com/prometheus/client_golang/prometheus" 27 | "github.com/prometheus/client_golang/prometheus/promhttp" 28 | "github.com/prometheus/common/promlog" 29 | "github.com/prometheus/common/promlog/flag" 30 | ) 31 | 32 | type collectorAdapter struct { 33 | prometheus.Collector 34 | } 35 | 36 | // Describe implements the prometheus.Collector interface. 37 | func (a collectorAdapter) Describe(ch chan<- *prometheus.Desc) { 38 | // We have to send *some* metric in Describe, but we don't know which ones 39 | // we're going to get, so just send a dummy metric. 40 | ch <- prometheus.NewDesc("dummy_metric", "Dummy metric.", nil, nil) 41 | } 42 | 43 | func TestTextfileCollector(t *testing.T) { 44 | tests := []struct { 45 | path string 46 | out string 47 | }{ 48 | { 49 | path: "fixtures/textfile/nonexistent_path", 50 | out: "fixtures/textfile/nonexistent_path.out", 51 | }, 52 | { 53 | path: "fixtures/textfile/client_side_timestamp", 54 | out: "fixtures/textfile/client_side_timestamp.out", 55 | }, 56 | { 57 | path: "fixtures/textfile/different_metric_types", 58 | out: "fixtures/textfile/different_metric_types.out", 59 | }, 60 | { 61 | path: "fixtures/textfile/inconsistent_metrics", 62 | out: "fixtures/textfile/inconsistent_metrics.out", 63 | }, 64 | { 65 | path: "fixtures/textfile/histogram", 66 | out: "fixtures/textfile/histogram.out", 67 | }, 68 | { 69 | path: "fixtures/textfile/histogram_extra_dimension", 70 | out: "fixtures/textfile/histogram_extra_dimension.out", 71 | }, 72 | { 73 | path: "fixtures/textfile/summary", 74 | out: "fixtures/textfile/summary.out", 75 | }, 76 | { 77 | path: "fixtures/textfile/summary_extra_dimension", 78 | out: "fixtures/textfile/summary_extra_dimension.out", 79 | }, 80 | } 81 | 82 | for i, test := range tests { 83 | mtime := 1.0 84 | c := &textFileCollector{ 85 | path: test.path + "/metrics.prom", 86 | mtime: &mtime, 87 | logger: log.NewNopLogger(), 88 | } 89 | 90 | // Suppress a log message about `nonexistent_path` not existing, this is 91 | // expected and clutters the test output. 92 | promlogConfig := &promlog.Config{} 93 | flag.AddFlags(kingpin.CommandLine, promlogConfig) 94 | if _, err := kingpin.CommandLine.Parse([]string{"--log.level", "debug"}); err != nil { 95 | t.Fatal(err) 96 | } 97 | 98 | registry := prometheus.NewRegistry() 99 | registry.MustRegister(collectorAdapter{c}) 100 | 101 | rw := httptest.NewRecorder() 102 | promhttp.HandlerFor(registry, promhttp.HandlerOpts{}).ServeHTTP(rw, &http.Request{}) 103 | got := string(rw.Body.String()) 104 | 105 | want, err := os.ReadFile(test.out) 106 | if err != nil { 107 | t.Fatalf("%d. error reading fixture file %s: %s", i, test.out, err) 108 | } 109 | 110 | if string(want) != got { 111 | t.Fatalf("%d.%q want:\n\n%s\n\ngot:\n\n%s", i, test.path, string(want), got) 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /util/http/http.go: -------------------------------------------------------------------------------- 1 | // Copyright The o11y toolkit Authors 2 | // spdx-license-identifier: apache-2.0 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at: 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package http 17 | 18 | import ( 19 | "context" 20 | "net/http" 21 | 22 | "github.com/alecthomas/kingpin/v2" 23 | "github.com/go-kit/log" 24 | "github.com/prometheus/client_golang/prometheus" 25 | "github.com/prometheus/client_golang/prometheus/collectors" 26 | "github.com/prometheus/client_golang/prometheus/promhttp" 27 | "github.com/prometheus/exporter-toolkit/web" 28 | webflag "github.com/prometheus/exporter-toolkit/web/kingpinflag" 29 | ) 30 | 31 | var ( 32 | webConfig = webflag.AddFlags(kingpin.CommandLine, ":9099") 33 | metricsPath = kingpin.Flag("web.telemetry-path", "Path under which to expose metrics.").Default("/metrics").String() 34 | disableExporterMetrics = kingpin.Flag("web.disable-exporter-metrics", "Exclude metrics about the exporter itself (promhttp_*, process_*, go_*).").Bool() 35 | ) 36 | 37 | func Serve(ctx context.Context, logger log.Logger, r *prometheus.Registry) error { 38 | if !*disableExporterMetrics { 39 | r.MustRegister( 40 | collectors.NewProcessCollector(collectors.ProcessCollectorOpts{}), 41 | collectors.NewGoCollector(), 42 | ) 43 | } 44 | 45 | http.Handle(*metricsPath, promhttp.HandlerFor(r, promhttp.HandlerOpts{})) 46 | srv := &http.Server{} 47 | errCh := make(chan error, 1) 48 | go func() { 49 | errCh <- web.ListenAndServe(srv, webConfig, logger) 50 | }() 51 | 52 | select { 53 | case e := <-errCh: 54 | return e 55 | case <-ctx.Done(): 56 | srv.Shutdown(ctx) 57 | return nil 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /util/period/period.go: -------------------------------------------------------------------------------- 1 | // Copyright The o11y toolkit Authors 2 | // spdx-license-identifier: apache-2.0 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at: 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package period 17 | 18 | import ( 19 | "time" 20 | ) 21 | 22 | type Period struct { 23 | Start time.Time 24 | End time.Time 25 | Complete bool 26 | } 27 | 28 | func CalculatePeriods(now time.Time, period string, lookbackTime time.Duration, includeIncompleteRanges bool) []Period { 29 | periods := []Period{} 30 | 31 | // Calculate the start and end times for each period based 32 | // on the specified period string 33 | switch period { 34 | case "monthly": 35 | firstDate := monthStart(now.Add(-lookbackTime), false) 36 | currentMonth := firstDate 37 | for d := 0; currentMonth.Before(now); d++ { 38 | complete := true 39 | end := monthStart(currentMonth, true).Add(-1 * time.Second) 40 | if end.After(now) { 41 | if !includeIncompleteRanges { 42 | break 43 | } 44 | complete = false 45 | end = now 46 | } 47 | 48 | periods = append(periods, Period{ 49 | Start: currentMonth, 50 | End: end, 51 | Complete: complete, 52 | }) 53 | 54 | currentMonth = monthStart(currentMonth, true) 55 | } 56 | } 57 | return periods 58 | } 59 | 60 | func monthStart(t time.Time, next bool) time.Time { 61 | year := t.Year() 62 | month := int(t.Month()) 63 | if next { 64 | if month == 12 { 65 | month = 1 66 | year++ 67 | } else { 68 | month++ 69 | } 70 | } 71 | return time.Date(year, time.Month(month), 1, 0, 0, 0, 0, t.Location()) 72 | } 73 | -------------------------------------------------------------------------------- /util/period/period_test.go: -------------------------------------------------------------------------------- 1 | // Copyright The o11y toolkit Authors 2 | // spdx-license-identifier: apache-2.0 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at: 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | package period 17 | 18 | import ( 19 | "testing" 20 | "time" 21 | 22 | "github.com/stretchr/testify/require" 23 | ) 24 | 25 | func TestCalculatePeriods(t *testing.T) { 26 | testCases := []struct { 27 | name string 28 | period string 29 | now time.Time 30 | lookbackTime time.Duration 31 | includeIncompleteRanges bool 32 | expectedPeriods []Period 33 | }{ 34 | { 35 | name: "monthly periods, include incomplete ranges", 36 | period: "monthly", 37 | now: time.Date(2023, 2, 16, 12, 34, 56, 789, time.UTC), 38 | lookbackTime: 3 * 30 * 24 * time.Hour, 39 | includeIncompleteRanges: true, 40 | expectedPeriods: []Period{ 41 | { 42 | Start: time.Date(2022, 11, 1, 0, 0, 0, 0, time.UTC), 43 | End: time.Date(2022, 12, 1, 0, 0, 0, 0, time.UTC).Add(-time.Second), 44 | }, 45 | { 46 | Start: time.Date(2022, 12, 1, 0, 0, 0, 0, time.UTC), 47 | End: time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC).Add(-time.Second), 48 | }, 49 | { 50 | Start: time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC), 51 | End: time.Date(2023, 2, 1, 0, 0, 0, 0, time.UTC).Add(-time.Second), 52 | }, 53 | { 54 | Start: time.Date(2023, 2, 1, 0, 0, 0, 0, time.UTC), 55 | End: time.Date(2023, 2, 16, 12, 34, 56, 789, time.UTC), 56 | }, 57 | }, 58 | }, 59 | { 60 | name: "monthly periods, include incomplete ranges", 61 | period: "monthly", 62 | now: time.Date(2023, 2, 16, 12, 34, 56, 789, time.UTC), 63 | lookbackTime: 2 * 30 * 24 * time.Hour, 64 | includeIncompleteRanges: false, 65 | expectedPeriods: []Period{ 66 | { 67 | Start: time.Date(2022, 12, 1, 0, 0, 0, 0, time.UTC), 68 | End: time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC).Add(-time.Second), 69 | }, 70 | { 71 | Start: time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC), 72 | End: time.Date(2023, 2, 1, 0, 0, 0, 0, time.UTC).Add(-time.Second), 73 | }, 74 | }, 75 | }, 76 | } 77 | 78 | for _, tc := range testCases { 79 | t.Run(tc.name, func(t *testing.T) { 80 | periods := CalculatePeriods(tc.now, tc.period, tc.lookbackTime, tc.includeIncompleteRanges) 81 | 82 | require.Equal(t, len(tc.expectedPeriods), len(periods), "number of periods") 83 | 84 | for i := range periods { 85 | for _, p := range periods { 86 | t.Logf("%+v", p) 87 | } 88 | require.Equal(t, tc.expectedPeriods[i].Start, periods[i].Start, "period %d start", i) 89 | require.Equal(t, tc.expectedPeriods[i].End, periods[i].End, "period %d end", i) 90 | } 91 | }) 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /wasm/metricslint/main.go: -------------------------------------------------------------------------------- 1 | // Copyright The o11y toolkit Authors 2 | // spdx-license-identifier: apache-2.0 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at: 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | //go:build js && wasm 17 | // +build js,wasm 18 | 19 | package main 20 | 21 | import ( 22 | "fmt" 23 | "strings" 24 | "syscall/js" 25 | 26 | "github.com/prometheus/client_golang/prometheus/testutil/promlint" 27 | ) 28 | 29 | func main() { 30 | c := make(chan struct{}, 0) 31 | 32 | js.Global().Set("metricslint", js.FuncOf(metriclint)) 33 | js.Global().Set("loadexample", js.FuncOf(loadexample)) 34 | jsDoc := js.Global().Get("document") 35 | jsDoc.Call("getElementById", "runButton").Set("disabled", false) 36 | jsDoc.Call("getElementById", "exampleButton").Set("disabled", false) 37 | jsDoc.Call("getElementById", "loadingWarning").Get("style").Set("display", "none") 38 | 39 | <-c 40 | } 41 | 42 | func loadexample(this js.Value, args []js.Value) interface{} { 43 | jsDoc := js.Global().Get("document") 44 | res := jsDoc.Call("getElementById", "metricInput") 45 | res.Set("value", `# HELP process_cpu_seconds_total Total user and system CPU time spent in seconds. 46 | # TYPE process_cpu_seconds_total counter 47 | process_cpu_seconds_total 203674.05 48 | # HELP process_max_fds Maximum number of open file descriptors. 49 | # TYPE process_max_fds gauge 50 | process_max_fds 1024 51 | # HELP process_open_fds Number of open file descriptors. 52 | # TYPE process_open_fds gauge 53 | process_open_fds 10 54 | `) 55 | return nil 56 | } 57 | 58 | func metriclint(this js.Value, args []js.Value) interface{} { 59 | jsDoc := js.Global().Get("document") 60 | res := jsDoc.Call("getElementById", "resultDiv") 61 | 62 | metrics := strings.NewReader(args[0].String() + "\n") 63 | l := promlint.New(metrics) 64 | problems, err := l.Lint() 65 | if err != nil { 66 | res.Set("innerHTML", fmt.Sprintf(` 67 |
68 |
69 | 70 | Parsing error 71 |
72 |
73 | %s 74 |
75 |
76 | `, err.Error())) 77 | return nil 78 | } 79 | 80 | if strings.TrimSpace(args[0].String()) == "" { 81 | res.Set("innerHTML", ` 82 |
83 |
84 | 85 | No input 86 |
87 |
88 | The input provided is empty. Please paste metrics into the text area. 89 |
90 |
91 | `) 92 | return nil 93 | } 94 | 95 | if len(problems) == 0 { 96 | res.Set("innerHTML", ` 97 |
98 |
99 | 100 | Success 101 |
102 |
103 | Input has been parsed successfully. 104 |
105 |
106 | `) 107 | return nil 108 | } 109 | 110 | var pbs string 111 | 112 | for _, p := range problems { 113 | pbs += fmt.Sprintf("
  • %s: %s
  • ", p.Metric, p.Text) 114 | } 115 | 116 | res.Set("innerHTML", fmt.Sprintf(` 117 |
    118 |
    119 | 120 | Issues found 121 |
    122 |
    123 | The input can be parsed but there are linting issues: 124 |
      %s
    125 |
    126 |
    127 | `, pbs)) 128 | 129 | return nil 130 | } 131 | -------------------------------------------------------------------------------- /wasm/promqlparser/main.go: -------------------------------------------------------------------------------- 1 | // Copyright The o11y toolkit Authors 2 | // spdx-license-identifier: apache-2.0 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at: 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | //go:build js && wasm 17 | // +build js,wasm 18 | 19 | package main 20 | 21 | import ( 22 | "fmt" 23 | "syscall/js" 24 | 25 | "github.com/prometheus/prometheus/promql/parser" 26 | ) 27 | 28 | func main() { 29 | c := make(chan struct{}, 0) 30 | 31 | js.Global().Set("parsepromql", js.FuncOf(parsePromQL)) 32 | js.Global().Set("loadexample", js.FuncOf(loadExample)) 33 | jsDoc := js.Global().Get("document") 34 | jsDoc.Call("getElementById", "runButton").Set("disabled", false) 35 | jsDoc.Call("getElementById", "exampleButton").Set("disabled", false) 36 | jsDoc.Call("getElementById", "loadingWarning").Get("style").Set("display", "none") 37 | 38 | <-c 39 | } 40 | 41 | func loadExample(this js.Value, args []js.Value) interface{} { 42 | jsDoc := js.Global().Get("document") 43 | res := jsDoc.Call("getElementById", "promqlInput") 44 | res.Set("value", `100 * sum(rate(jaeger_agent_http_server_errors_total[1m])) by (instance, job, namespace) / sum(rate(jaeger_agent_http_server_total[1m])) by (instance, job, namespace)>1`) 45 | return nil 46 | } 47 | 48 | func parsePromQL(this js.Value, args []js.Value) interface{} { 49 | jsDoc := js.Global().Get("document") 50 | res := jsDoc.Call("getElementById", "resultDiv") 51 | 52 | promql := args[0].String() 53 | expr, err := parser.ParseExpr(promql) 54 | if err != nil { 55 | res.Set("innerHTML", fmt.Sprintf(` 56 |
    57 |
    58 | 59 | Parsing error 60 |
    61 |
    62 | %s 63 |
    64 |
    65 | `, err.Error())) 66 | return nil 67 | } 68 | 69 | res.Set("innerHTML", fmt.Sprintf(` 70 |
    71 |
    72 | 73 | Success 74 |
    75 |
    76 | Input has been parsed successfully. 77 |
    78 |
    79 |

    Prettified PromQL

    80 |
    %s
    81 | `, expr.Pretty(0))) 82 | return nil 83 | } 84 | -------------------------------------------------------------------------------- /wasm/pwgen/main.go: -------------------------------------------------------------------------------- 1 | // Copyright The o11y toolkit Authors 2 | // spdx-license-identifier: apache-2.0 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at: 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | //go:build js && wasm 17 | // +build js,wasm 18 | 19 | package main 20 | 21 | import ( 22 | "errors" 23 | "fmt" 24 | "strconv" 25 | "syscall/js" 26 | 27 | "golang.org/x/crypto/bcrypt" 28 | "gopkg.in/yaml.v2" 29 | ) 30 | 31 | func main() { 32 | c := make(chan struct{}, 0) 33 | 34 | js.Global().Set("generateUsers", js.FuncOf(pwgen)) 35 | jsDoc := js.Global().Get("document") 36 | jsDoc.Call("getElementById", "runButton").Set("disabled", false) 37 | jsDoc.Call("getElementById", "loadingWarning").Get("style").Set("display", "none") 38 | js.Global().Call("addUser") 39 | 40 | <-c 41 | } 42 | 43 | func pwgen(this js.Value, args []js.Value) interface{} { 44 | jsDoc := js.Global().Get("document") 45 | res := jsDoc.Call("getElementById", "resultDiv") 46 | users := jsDoc.Call("querySelectorAll", `[name="username"]`) 47 | passwords := jsDoc.Call("querySelectorAll", `[name="password"]`) 48 | cost := jsDoc.Call("querySelectorAll", `[name="cost"]`) 49 | c, err := strconv.ParseInt(cost.Index(0).Get("value").String(), 10, 32) 50 | if err != nil { 51 | mkErr(err) 52 | return nil 53 | } 54 | userspw := make(map[string]string, users.Length()) 55 | for i := 0; i < users.Length(); i++ { 56 | user := users.Index(i).Get("value").String() 57 | pw := passwords.Index(i).Get("value").String() 58 | if user == "" { 59 | mkErr(errors.New("username can't be empty")) 60 | return nil 61 | } 62 | if pw == "" { 63 | mkErr(fmt.Errorf("password for %q can't be empty", user)) 64 | return nil 65 | } 66 | if _, ok := userspw[user]; ok { 67 | mkErr(fmt.Errorf("duplicate user %q", user)) 68 | return nil 69 | } 70 | gpw, err := bcrypt.GenerateFromPassword([]byte(pw), int(c)) 71 | if err != nil { 72 | mkErr(err) 73 | return nil 74 | } 75 | userspw[user] = string(gpw) 76 | } 77 | 78 | out, err := yaml.Marshal(struct { 79 | Users map[string]string `yaml:"basic_auth_users"` 80 | }{Users: userspw}) 81 | if err != nil { 82 | mkErr(err) 83 | return nil 84 | } 85 | 86 | res.Set("innerHTML", fmt.Sprintf(` 87 |

    Web configuration file

    88 |

    Write the following content as web.yml file and start Prometheus with --web.config.file=web.yml

    89 |
    %s
    90 | `, out)) 91 | return nil 92 | } 93 | 94 | func mkErr(err error) { 95 | jsDoc := js.Global().Get("document") 96 | res := jsDoc.Call("getElementById", "resultDiv") 97 | res.Set("innerHTML", fmt.Sprintf(` 98 |
    99 |
    100 | 101 | Error 102 |
    103 |
    104 | %s 105 |
    106 |
    107 | `, err.Error())) 108 | } 109 | --------------------------------------------------------------------------------