├── VERSION ├── MAINTAINERS.md ├── .github ├── dependabot.yml └── workflows │ ├── golangci-lint.yml │ └── container_description.yml ├── CODE_OF_CONDUCT.md ├── SECURITY.md ├── NOTICE ├── .golangci.yml ├── Dockerfile ├── .gitignore ├── .yamllint ├── .promu.yml ├── Makefile ├── go.mod ├── CONTRIBUTING.md ├── .circleci └── config.yml ├── CHANGELOG.md ├── cmd └── prom2json │ └── main.go ├── go.sum ├── histogram └── prometheus_model.go ├── README.md ├── prom2json.go ├── Makefile.common ├── prom2json_test.go └── LICENSE /VERSION: -------------------------------------------------------------------------------- 1 | 1.5.0 2 | -------------------------------------------------------------------------------- /MAINTAINERS.md: -------------------------------------------------------------------------------- 1 | * Björn Rabenstein @beorn7 2 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "gomod" 4 | directory: "/" 5 | schedule: 6 | interval: "monthly" 7 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Prometheus Community Code of Conduct 2 | 3 | Prometheus follows the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/main/code-of-conduct.md). 4 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Reporting a security issue 2 | 3 | The Prometheus security policy, including how to report vulnerabilities, can be 4 | found here: 5 | 6 | 7 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | A tool to scrape a Prometheus client and dump the result as JSON. 2 | Copyright 2014-2015 The Prometheus Authors 3 | 4 | This product includes software developed at 5 | SoundCloud Ltd. (http://soundcloud.com/). 6 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | linters: 3 | exclusions: 4 | rules: 5 | - linters: 6 | - errcheck 7 | # Taken from the default exclusions in v1. 8 | text: Error return value of .((os\.)?std(out|err)\..*|.*Close|.*Flush|os\.Remove(All)?|.*print(f|ln)?|os\.(Un)?Setenv). is not checked 9 | 10 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | ARG ARCH="amd64" 2 | ARG OS="linux" 3 | FROM quay.io/prometheus/busybox-${OS}-${ARCH}:latest 4 | LABEL maintainer="The Prometheus Authors " 5 | 6 | ARG ARCH="amd64" 7 | ARG OS="linux" 8 | COPY .build/${OS}-${ARCH}/prom2json /bin/prom2json 9 | 10 | USER nobody 11 | ENTRYPOINT [ "/bin/prom2json" ] 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | 25 | # Build artifacts 26 | .build 27 | /prom2json 28 | *-stamp 29 | -------------------------------------------------------------------------------- /.yamllint: -------------------------------------------------------------------------------- 1 | --- 2 | extends: default 3 | ignore: | 4 | **/node_modules 5 | 6 | rules: 7 | braces: 8 | max-spaces-inside: 1 9 | level: error 10 | brackets: 11 | max-spaces-inside: 1 12 | level: error 13 | commas: disable 14 | comments: disable 15 | comments-indentation: disable 16 | document-start: disable 17 | indentation: 18 | spaces: consistent 19 | indent-sequences: consistent 20 | key-duplicates: 21 | ignore: | 22 | config/testdata/section_key_dup.bad.yml 23 | line-length: disable 24 | truthy: 25 | check-keys: false 26 | -------------------------------------------------------------------------------- /.promu.yml: -------------------------------------------------------------------------------- 1 | --- 2 | go: 3 | # Whenever the Go version is updated here 4 | # .circle/config.yml should also be updated. 5 | version: 1.25 6 | repository: 7 | path: github.com/prometheus/prom2json 8 | build: 9 | binaries: 10 | - name: prom2json 11 | path: ./cmd/prom2json 12 | flags: -a -tags netgo 13 | ldflags: | 14 | -X github.com/prometheus/common/version.Version={{.Version}} 15 | -X github.com/prometheus/common/version.Revision={{.Revision}} 16 | -X github.com/prometheus/common/version.Branch={{.Branch}} 17 | -X github.com/prometheus/common/version.BuildUser={{user}}@{{host}} 18 | -X github.com/prometheus/common/version.BuildDate={{date "20060102-15:04:05"}} 19 | tarball: 20 | files: 21 | - LICENSE 22 | - NOTICE 23 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Copyright 2018 The Prometheus Authors 2 | # Licensed under the Apache License, Version 2.0 (the "License"); 3 | # you may not use this file except in compliance with the License. 4 | # You may obtain a copy of the License at 5 | # 6 | # http://www.apache.org/licenses/LICENSE-2.0 7 | # 8 | # Unless required by applicable law or agreed to in writing, software 9 | # distributed under the License is distributed on an "AS IS" BASIS, 10 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # See the License for the specific language governing permissions and 12 | # limitations under the License. 13 | 14 | # Needs to be defined before including Makefile.common to auto-generate targets 15 | DOCKER_ARCHS ?= amd64 armv7 arm64 16 | 17 | include Makefile.common 18 | 19 | STATICCHECK_IGNORE = 20 | 21 | DOCKER_IMAGE_NAME ?= prom2json 22 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/prometheus/prom2json 2 | 3 | go 1.24.0 4 | 5 | toolchain go1.24.4 6 | 7 | require ( 8 | github.com/alecthomas/kingpin/v2 v2.4.0 9 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc 10 | github.com/matttproud/golang_protobuf_extensions v1.0.4 11 | github.com/prometheus/client_model v0.6.2 12 | github.com/prometheus/common v0.67.4 13 | github.com/prometheus/prometheus v0.307.3 14 | ) 15 | 16 | require ( 17 | github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b // indirect 18 | github.com/golang/protobuf v1.5.4 // indirect 19 | github.com/kr/pretty v0.3.1 // indirect 20 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 21 | github.com/xhit/go-str2duration/v2 v2.1.0 // indirect 22 | go.yaml.in/yaml/v2 v2.4.3 // indirect 23 | google.golang.org/protobuf v1.36.10 // indirect 24 | ) 25 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Prometheus uses GitHub to manage reviews of pull requests. 4 | 5 | * If you have a trivial fix or improvement, go ahead and create a pull request, 6 | addressing (with `@...`) the maintainer of this repository (see 7 | [MAINTAINERS.md](MAINTAINERS.md)) in the description of the pull request. 8 | 9 | * If you plan to do something more involved, first discuss your ideas 10 | on our [mailing list](https://groups.google.com/forum/?fromgroups#!forum/prometheus-developers). 11 | This will avoid unnecessary work and surely give you and us a good deal 12 | of inspiration. 13 | 14 | * Relevant coding style guidelines are the [Go Code Review 15 | Comments](https://code.google.com/p/go-wiki/wiki/CodeReviewComments) 16 | and the _Formatting and style_ section of Peter Bourgon's [Go: Best 17 | Practices for Production 18 | Environments](http://peter.bourgon.org/go-in-production/#formatting-and-style). 19 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | --- 2 | version: 2.1 3 | orbs: 4 | prometheus: "prometheus/prometheus@0.17.1" 5 | executors: 6 | # Whenever the Go version is updated here, .promu.yml should also be updated. 7 | golang: 8 | docker: 9 | - image: cimg/go:1.25 10 | jobs: 11 | test: 12 | executor: golang 13 | steps: 14 | - prometheus/setup_environment 15 | - run: make 16 | - prometheus/store_artifact: 17 | file: prom2json 18 | workflows: 19 | version: 2 20 | prom2json: 21 | jobs: 22 | - test: 23 | filters: 24 | tags: 25 | only: /.*/ 26 | - prometheus/build: 27 | name: build 28 | filters: 29 | tags: 30 | only: /.*/ 31 | - prometheus/publish_master: 32 | context: org-context 33 | requires: 34 | - test 35 | - build 36 | filters: 37 | branches: 38 | only: master 39 | - prometheus/publish_release: 40 | context: org-context 41 | requires: 42 | - test 43 | - build 44 | filters: 45 | tags: 46 | only: /^v[0-9]+(\.[0-9]+){2}(-.+|[^-.]*)$/ 47 | branches: 48 | ignore: /.*/ 49 | -------------------------------------------------------------------------------- /.github/workflows/golangci-lint.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # This action is synced from https://github.com/prometheus/prometheus 3 | name: golangci-lint 4 | on: 5 | push: 6 | paths: 7 | - "go.sum" 8 | - "go.mod" 9 | - "**.go" 10 | - "scripts/errcheck_excludes.txt" 11 | - ".github/workflows/golangci-lint.yml" 12 | - ".golangci.yml" 13 | pull_request: 14 | 15 | permissions: # added using https://github.com/step-security/secure-repo 16 | contents: read 17 | 18 | jobs: 19 | golangci: 20 | permissions: 21 | contents: read # for actions/checkout to fetch code 22 | pull-requests: read # for golangci/golangci-lint-action to fetch pull requests 23 | name: lint 24 | runs-on: ubuntu-latest 25 | steps: 26 | - name: Checkout repository 27 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 28 | with: 29 | persist-credentials: false 30 | - name: Install Go 31 | uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0 32 | with: 33 | go-version: 1.25.x 34 | - name: Install snmp_exporter/generator dependencies 35 | run: sudo apt-get update && sudo apt-get -y install libsnmp-dev 36 | if: github.repository == 'prometheus/snmp_exporter' 37 | - name: Get golangci-lint version 38 | id: golangci-lint-version 39 | run: echo "version=$(make print-golangci-lint-version)" >> $GITHUB_OUTPUT 40 | - name: Lint 41 | uses: golangci/golangci-lint-action@4afd733a84b1f43292c63897423277bb7f4313a9 # v8.0.0 42 | with: 43 | args: --verbose 44 | version: ${{ steps.golangci-lint-version.outputs.version }} 45 | -------------------------------------------------------------------------------- /.github/workflows/container_description.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Push README to Docker Hub 3 | on: 4 | push: 5 | paths: 6 | - "README.md" 7 | - "README-containers.md" 8 | - ".github/workflows/container_description.yml" 9 | branches: [ main, master ] 10 | 11 | permissions: 12 | contents: read 13 | 14 | jobs: 15 | PushDockerHubReadme: 16 | runs-on: ubuntu-latest 17 | name: Push README to Docker Hub 18 | if: github.repository_owner == 'prometheus' || github.repository_owner == 'prometheus-community' # Don't run this workflow on forks. 19 | steps: 20 | - name: git checkout 21 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 22 | with: 23 | persist-credentials: false 24 | - name: Set docker hub repo name 25 | run: echo "DOCKER_REPO_NAME=$(make docker-repo-name)" >> $GITHUB_ENV 26 | - name: Push README to Dockerhub 27 | uses: christian-korneck/update-container-description-action@d36005551adeaba9698d8d67a296bd16fa91f8e8 # v1 28 | env: 29 | DOCKER_USER: ${{ secrets.DOCKER_HUB_LOGIN }} 30 | DOCKER_PASS: ${{ secrets.DOCKER_HUB_PASSWORD }} 31 | with: 32 | destination_container_repo: ${{ env.DOCKER_REPO_NAME }} 33 | provider: dockerhub 34 | short_description: ${{ env.DOCKER_REPO_NAME }} 35 | # Empty string results in README-containers.md being pushed if it 36 | # exists. Otherwise, README.md is pushed. 37 | readme_file: '' 38 | 39 | PushQuayIoReadme: 40 | runs-on: ubuntu-latest 41 | name: Push README to quay.io 42 | if: github.repository_owner == 'prometheus' || github.repository_owner == 'prometheus-community' # Don't run this workflow on forks. 43 | steps: 44 | - name: git checkout 45 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 46 | with: 47 | persist-credentials: false 48 | - name: Set quay.io org name 49 | run: echo "DOCKER_REPO=$(echo quay.io/${GITHUB_REPOSITORY_OWNER} | tr -d '-')" >> $GITHUB_ENV 50 | - name: Set quay.io repo name 51 | run: echo "DOCKER_REPO_NAME=$(make docker-repo-name)" >> $GITHUB_ENV 52 | - name: Push README to quay.io 53 | uses: christian-korneck/update-container-description-action@d36005551adeaba9698d8d67a296bd16fa91f8e8 # v1 54 | env: 55 | DOCKER_APIKEY: ${{ secrets.QUAY_IO_API_TOKEN }} 56 | with: 57 | destination_container_repo: ${{ env.DOCKER_REPO_NAME }} 58 | provider: quay 59 | # Empty string results in README-containers.md being pushed if it 60 | # exists. Otherwise, README.md is pushed. 61 | readme_file: '' 62 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## master / unreleased 2 | 3 | ### **Breaking changes** 4 | 5 | ### Changes 6 | 7 | * [CHANGE] 8 | * [FEATURE] 9 | * [ENHANCEMENT] 10 | * [BUGFIX] 11 | 12 | ## 1.5.0 / 2025-10-14 13 | 14 | * [FEATURE] Add `--escaping` command line flag to negotiate escaping schemes including full UTF-8 support for metric and label names. #240 15 | * [ENHANCEMENT] Add `--help` and `--version` command line flags. #219 16 | 17 | ## 1.4.2 / 2025-04-09 18 | 19 | * [BUGFIX] Fix formatting of error messages. #190 20 | 21 | _Besides the small fix above, this release comes with updated dependencies, and the pre-built binaries are using Go1.24.1, both to avoid potential security issues. (Note that this is just precaution. We do not know of any relevant vulnerabilities in v1.4.1.)_ 22 | 23 | ## 1.4.1 / 2024-09-19 24 | 25 | _There are no code changes in this release. It merely comes with updated dependencies, and the pre-built binaries are using Go1.23.1., both to avoid potential security issues. (Note that this is just precaution. We do not know of any relevant vulnerabilities in v1.4.0.)_ 26 | 27 | ## 1.4.0 / 2024-07-11 28 | 29 | * [FEATURE] Support native histograms. #169 30 | * [FEATURE] Support float histograms (classic and native). #176 31 | 32 | _This release also comes with updated dependencies, and the pre-built binaries are using Go1.22.5., both to avoid potential security issues. (Note that this is just precaution. We do not know of any relevant vulnerabilities in v1.3.3.)_ 33 | 34 | ## 1.3.3 / 2023-05-25 35 | 36 | _There are no code changes in this release. It merely comes with updated 37 | dependencies, and the pre-built binaries are using Go1.20.4., both to avoid potential security issues. (Note that this is just precaution. We do not know of any relevant vulnerabilities in v1.3.2.)_ 38 | 39 | ## 1.3.2 / 2022-10-07 40 | 41 | _There are no code changes in this release. It merely comes with updated 42 | dependencies, and the pre-built binaries are using Go1.19.2., both to avoid potential security issues. (Note that this is just precaution. We do not know of any relevant vulnerabilities in v1.3.1.)_ 43 | 44 | ## 1.3.1 / 2022-04-19 45 | 46 | _There are no code changes in this release. It merely comes with updated 47 | dependencies, and the pre-built binaries are using Go1.18.1., both to avoid potential security issues. (Note that this is just precaution. We do not know of any relevant vulnerabilities in v1.3.0.)_ 48 | 49 | ## 1.3.0 / 2019-12-21 50 | 51 | * [ENHANCEMENT] Saner settings for the HTTP transport, based on the usual 52 | defaults, but with a ResponseHeaderTimeout of one minute. #72 53 | * [BUGFIX] Close metric family channel in case of errors to prevent leaking a 54 | goroutine. #70 55 | 56 | ## 1.2.2 / 2019-07-23 57 | 58 | * [FEATURE] Add ARM container images. #61 59 | * [BUGFIX] Properly set the sum in a histogram. #65 60 | 61 | ## 1.2.1 / 2019-05-20 62 | 63 | _No actual code changes. Only a fix of the CircleCI config to make Docker 64 | images available again._ 65 | 66 | * [BUGFIX] Fix image upload to Docker Hub and Quay. 67 | 68 | ## 1.2.0 / 2019-05-17 69 | 70 | ### **Breaking changes** 71 | 72 | Users of the `prom2json` package have to take into account that the interface 73 | of `FetchMetricFamilies` has changed (to allow the bugfix below). For users of 74 | the command-line tool `prom2json`, this is just an internal change without any 75 | external visibility. 76 | 77 | ### Changes 78 | 79 | * [FEATURE] Support timestamps. 80 | * [BUGFIX] Do not create a new Transport for each call of `FetchMetricFamilies`. 81 | 82 | ## 1.1.0 / 2018-12-09 83 | 84 | * [FEATURE] Allow reading from STDIN and file (in addition to URL). 85 | * [ENHANCEMENT] Support Go modules for dependency management. 86 | * [ENHANCEMENT] Update dependencies. 87 | 88 | ## 1.0.0 / 2018-10-20 89 | 90 | Initial release 91 | -------------------------------------------------------------------------------- /cmd/prom2json/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 Prometheus Team 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package main 15 | 16 | import ( 17 | "crypto/tls" 18 | "encoding/json" 19 | "fmt" 20 | "io" 21 | "net/http" 22 | "net/url" 23 | "os" 24 | "time" 25 | 26 | "github.com/alecthomas/kingpin/v2" 27 | dto "github.com/prometheus/client_model/go" 28 | "github.com/prometheus/common/version" 29 | 30 | "github.com/prometheus/prom2json" 31 | ) 32 | 33 | var usage = `The path or URL to metrics to convert, if omitted, defaults to read from STDIN. 34 | 35 | Examples: 36 | 37 | $ prom2json http://my-prometheus-server:9000/metrics 38 | 39 | $ curl http://my-prometheus-server:9000/metrics | prom2json 40 | 41 | ` 42 | 43 | func main() { 44 | cert := kingpin.Flag("cert", "client certificate file").PlaceHolder("FILE").String() 45 | key := kingpin.Flag("key", "client certificate's key file").PlaceHolder("FILE").String() 46 | skipServerCertCheck := kingpin.Flag("accept-invalid-cert", "Accept any certificate during TLS handshake. Insecure, use only for testing.").Bool() 47 | escapingScheme := kingpin.Flag("escaping", "Sets an escaping scheme in content negotiation. Use 'allow-utf-8' for full UTF-8 character support."). 48 | PlaceHolder("SCHEME"). 49 | Enum( 50 | "allow-utf-8", 51 | "underscores", 52 | "dots", 53 | "values", 54 | ) 55 | 56 | kingpin.CommandLine.UsageWriter(os.Stderr) 57 | kingpin.Version(version.Print("prom2json")) 58 | kingpin.HelpFlag.Short('h') 59 | 60 | var input io.Reader 61 | var err error 62 | arg := kingpin.Arg("METRICS_PATH | METRICS_URL", usage).String() 63 | 64 | kingpin.Parse() 65 | 66 | if *arg == "" { 67 | // Use stdin on empty argument. 68 | input = os.Stdin 69 | } else if url, urlErr := url.Parse(*arg); urlErr != nil || url.Scheme == "" { 70 | // `url, err := url.Parse("/some/path.txt")` results in: `err == nil && url.Scheme == ""` 71 | // Open file since arg appears not to be a valid URL (parsing error occurred or the scheme is missing). 72 | if input, err = os.Open(*arg); err != nil { 73 | fmt.Fprintln(os.Stderr, "error opening file:", err) 74 | os.Exit(1) 75 | } 76 | } else { 77 | // Validate Client SSL arguments since arg appears to be a valid URL. 78 | if (*cert != "" && *key == "") || (*cert == "" && *key != "") { 79 | fmt.Fprintf(os.Stderr, "%s\n with TLS client authentication: %s --cert /path/to/certificate --key /path/to/key METRICS_URL", usage, os.Args[0]) 80 | os.Exit(1) 81 | } 82 | } 83 | 84 | mfChan := make(chan *dto.MetricFamily, 1024) 85 | // Missing input means we are reading from an URL. 86 | if input != nil { 87 | go func() { 88 | if err := prom2json.ParseReader(input, mfChan); err != nil { 89 | fmt.Fprintln(os.Stderr, "error reading metrics:", err) 90 | os.Exit(1) 91 | } 92 | }() 93 | } else { 94 | transport, err := makeTransport(*cert, *key, *skipServerCertCheck) 95 | if err != nil { 96 | fmt.Fprintln(os.Stderr, err) 97 | os.Exit(1) 98 | } 99 | go func() { 100 | if err := prom2json.FetchMetricFamiliesWithEscapingScheme(*arg, mfChan, transport, *escapingScheme); err != nil { 101 | fmt.Fprintln(os.Stderr, err) 102 | os.Exit(1) 103 | } 104 | }() 105 | } 106 | 107 | result := []*prom2json.Family{} 108 | for mf := range mfChan { 109 | result = append(result, prom2json.NewFamily(mf)) 110 | } 111 | jsonText, err := json.Marshal(result) 112 | if err != nil { 113 | fmt.Fprintln(os.Stderr, "error marshaling JSON:", err) 114 | os.Exit(1) 115 | } 116 | if _, err := os.Stdout.Write(jsonText); err != nil { 117 | fmt.Fprintln(os.Stderr, "error writing to stdout:", err) 118 | os.Exit(1) 119 | } 120 | fmt.Println() 121 | } 122 | 123 | func makeTransport( 124 | certificate string, key string, 125 | skipServerCertCheck bool, 126 | ) (*http.Transport, error) { 127 | // Start with the DefaultTransport for sane defaults. 128 | transport := http.DefaultTransport.(*http.Transport).Clone() 129 | // Conservatively disable HTTP keep-alives as this program will only 130 | // ever need a single HTTP request. 131 | transport.DisableKeepAlives = true 132 | // Timeout early if the server doesn't even return the headers. 133 | transport.ResponseHeaderTimeout = time.Minute 134 | tlsConfig := &tls.Config{InsecureSkipVerify: skipServerCertCheck} 135 | if certificate != "" && key != "" { 136 | cert, err := tls.LoadX509KeyPair(certificate, key) 137 | if err != nil { 138 | return nil, err 139 | } 140 | tlsConfig.Certificates = []tls.Certificate{cert} 141 | } 142 | transport.TLSClientConfig = tlsConfig 143 | return transport, nil 144 | } 145 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/alecthomas/kingpin/v2 v2.4.0 h1:f48lwail6p8zpO1bC4TxtqACaGqHYA22qkHjHpqDjYY= 2 | github.com/alecthomas/kingpin/v2 v2.4.0/go.mod h1:0gyi0zQnjuFk8xrkNKamJoyUo382HRL7ATRpFZCw6tE= 3 | github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b h1:mimo19zliBX/vSQ6PWWSL9lK8qwHozUj03+zLoEB8O0= 4 | github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b/go.mod h1:fvzegU4vN3H1qMT+8wDmzjAcDONcgo2/SZ/TyfdUOFs= 5 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 6 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 7 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 8 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= 9 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 10 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 11 | github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= 12 | github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= 13 | github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= 14 | github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= 15 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 16 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 17 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 18 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 19 | github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= 20 | github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= 21 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= 22 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= 23 | github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= 24 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 25 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= 26 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 27 | github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= 28 | github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= 29 | github.com/prometheus/common v0.67.4 h1:yR3NqWO1/UyO1w2PhUvXlGQs/PtFmoveVO0KZ4+Lvsc= 30 | github.com/prometheus/common v0.67.4/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI= 31 | github.com/prometheus/prometheus v0.307.3 h1:zGIN3EpiKacbMatcUL2i6wC26eRWXdoXfNPjoBc2l34= 32 | github.com/prometheus/prometheus v0.307.3/go.mod h1:sPbNW+KTS7WmzFIafC3Inzb6oZVaGLnSvwqTdz2jxRQ= 33 | github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= 34 | github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= 35 | github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= 36 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 37 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 38 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 39 | github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= 40 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 41 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 42 | github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 43 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 44 | github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= 45 | github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= 46 | github.com/xhit/go-str2duration/v2 v2.1.0 h1:lxklc02Drh6ynqX+DdPyp5pCKLUQpRT8bp8Ydu2Bstc= 47 | github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU= 48 | go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= 49 | go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= 50 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 51 | google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= 52 | google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= 53 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 54 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 55 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 56 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 57 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 58 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 59 | -------------------------------------------------------------------------------- /histogram/prometheus_model.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The Prometheus Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | package histogram 14 | 15 | import ( 16 | "fmt" 17 | 18 | dto "github.com/prometheus/client_model/go" 19 | model "github.com/prometheus/prometheus/model/histogram" 20 | ) 21 | 22 | type APIBucket[BC model.BucketCount] struct { 23 | Boundaries uint64 24 | Lower, Upper float64 25 | Count BC 26 | } 27 | 28 | func NewModelHistogram(ch *dto.Histogram) (*model.Histogram, *model.FloatHistogram) { 29 | if ch.GetSampleCountFloat() > 0 || ch.GetZeroCountFloat() > 0 { 30 | // It is a float histogram. 31 | fh := model.FloatHistogram{ 32 | Count: ch.GetSampleCountFloat(), 33 | Sum: ch.GetSampleSum(), 34 | ZeroThreshold: ch.GetZeroThreshold(), 35 | ZeroCount: ch.GetZeroCountFloat(), 36 | Schema: ch.GetSchema(), 37 | PositiveSpans: make([]model.Span, len(ch.GetPositiveSpan())), 38 | PositiveBuckets: ch.GetPositiveCount(), 39 | NegativeSpans: make([]model.Span, len(ch.GetNegativeSpan())), 40 | NegativeBuckets: ch.GetNegativeCount(), 41 | } 42 | for i, span := range ch.GetPositiveSpan() { 43 | fh.PositiveSpans[i].Offset = span.GetOffset() 44 | fh.PositiveSpans[i].Length = span.GetLength() 45 | } 46 | for i, span := range ch.GetNegativeSpan() { 47 | fh.NegativeSpans[i].Offset = span.GetOffset() 48 | fh.NegativeSpans[i].Length = span.GetLength() 49 | } 50 | return nil, &fh 51 | } 52 | h := model.Histogram{ 53 | Count: ch.GetSampleCount(), 54 | Sum: ch.GetSampleSum(), 55 | ZeroThreshold: ch.GetZeroThreshold(), 56 | ZeroCount: ch.GetZeroCount(), 57 | Schema: ch.GetSchema(), 58 | PositiveSpans: make([]model.Span, len(ch.GetPositiveSpan())), 59 | PositiveBuckets: ch.GetPositiveDelta(), 60 | NegativeSpans: make([]model.Span, len(ch.GetNegativeSpan())), 61 | NegativeBuckets: ch.GetNegativeDelta(), 62 | } 63 | for i, span := range ch.GetPositiveSpan() { 64 | h.PositiveSpans[i].Offset = span.GetOffset() 65 | h.PositiveSpans[i].Length = span.GetLength() 66 | } 67 | for i, span := range ch.GetNegativeSpan() { 68 | h.NegativeSpans[i].Offset = span.GetOffset() 69 | h.NegativeSpans[i].Length = span.GetLength() 70 | } 71 | return &h, nil 72 | } 73 | 74 | func BucketsAsJson[BC model.BucketCount](buckets []APIBucket[BC]) [][]any { 75 | ret := make([][]any, len(buckets)) 76 | for i, b := range buckets { 77 | ret[i] = []any{b.Boundaries, fmt.Sprintf("%v", b.Lower), fmt.Sprintf("%v", b.Upper), fmt.Sprintf("%v", b.Count)} 78 | } 79 | return ret 80 | } 81 | 82 | func GetAPIBuckets(h *model.Histogram) []APIBucket[uint64] { 83 | var apiBuckets []APIBucket[uint64] 84 | var nBuckets []model.Bucket[uint64] 85 | for it := h.NegativeBucketIterator(); it.Next(); { 86 | bucket := it.At() 87 | if bucket.Count != 0 { 88 | nBuckets = append(nBuckets, it.At()) 89 | } 90 | } 91 | for i := len(nBuckets) - 1; i >= 0; i-- { 92 | apiBuckets = append(apiBuckets, makeBucket[uint64](nBuckets[i])) 93 | } 94 | 95 | if h.ZeroCount != 0 { 96 | apiBuckets = append(apiBuckets, makeBucket[uint64](h.ZeroBucket())) 97 | } 98 | 99 | for it := h.PositiveBucketIterator(); it.Next(); { 100 | bucket := it.At() 101 | if bucket.Count != 0 { 102 | apiBuckets = append(apiBuckets, makeBucket[uint64](bucket)) 103 | } 104 | } 105 | return apiBuckets 106 | } 107 | 108 | func GetAPIFloatBuckets(h *model.FloatHistogram) []APIBucket[float64] { 109 | var apiBuckets []APIBucket[float64] 110 | var nBuckets []model.Bucket[float64] 111 | for it := h.NegativeBucketIterator(); it.Next(); { 112 | bucket := it.At() 113 | if bucket.Count != 0 { 114 | nBuckets = append(nBuckets, it.At()) 115 | } 116 | } 117 | for i := len(nBuckets) - 1; i >= 0; i-- { 118 | apiBuckets = append(apiBuckets, makeBucket[float64](nBuckets[i])) 119 | } 120 | 121 | if h.ZeroCount != 0 { 122 | apiBuckets = append(apiBuckets, makeBucket[float64](h.ZeroBucket())) 123 | } 124 | 125 | for it := h.PositiveBucketIterator(); it.Next(); { 126 | bucket := it.At() 127 | if bucket.Count != 0 { 128 | apiBuckets = append(apiBuckets, makeBucket[float64](bucket)) 129 | } 130 | } 131 | return apiBuckets 132 | } 133 | 134 | func makeBucket[BC model.BucketCount](bucket model.Bucket[BC]) APIBucket[BC] { 135 | boundaries := uint64(2) // () Exclusive on both sides AKA open interval. 136 | if bucket.LowerInclusive { 137 | if bucket.UpperInclusive { 138 | boundaries = 3 // [] Inclusive on both sides AKA closed interval. 139 | } else { 140 | boundaries = 1 // [) Inclusive only on lower end AKA right open. 141 | } 142 | } else { 143 | if bucket.UpperInclusive { 144 | boundaries = 0 // (] Inclusive only on upper end AKA left open. 145 | } 146 | } 147 | return APIBucket[BC]{ 148 | Boundaries: boundaries, 149 | Lower: bucket.Lower, 150 | Upper: bucket.Upper, 151 | Count: bucket.Count, 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | prom2json 2 | ========= 3 | 4 | A tool to scrape a Prometheus client and dump the result as JSON. 5 | 6 | # Background 7 | 8 | (Pre-)historically, Prometheus clients were able to expose metrics as 9 | JSON. For various reasons, the JSON exposition format was deprecated. 10 | 11 | Usually, scraping of a Prometheus client is done by the Prometheus 12 | server, which preferably happens with the protocol buffer 13 | format. Sometimes, a human being needs to inspect what a Prometheus 14 | clients exposes. In that case, the text format is used (which is 15 | otherwise meant to allow simplistic _clients_ like shell scripts to 16 | expose metrics to a Prometheus _server_). 17 | 18 | However, some users wish to scrape Prometheus clients with programs 19 | other than the Prometheus server. Those programs would usually use the 20 | protocol buffer format, but for small _ad hoc_ programs, that is too 21 | much of an (programming) overhead. JSON comes in handy for these 22 | use-cases, as many languages offer tooling for JSON parsing. 23 | 24 | To avoid maintaining a JSON format in all client libraries, the 25 | `prom2json` tool has been created, which scrapes a Prometheus client 26 | in protocol buffer or text format and dumps the result as JSON to 27 | `stdout`. 28 | 29 | # Usage 30 | 31 | Installing and building: 32 | 33 | $ GO111MODULE=on go install github.com/prometheus/prom2json/cmd/prom2json@latest 34 | 35 | Running: 36 | 37 | $ prom2json http://my-prometheus-client.example.org:8080/metrics 38 | $ curl http://my-prometheus-client.example.org:8080/metrics | prom2json 39 | $ prom2json /tmp/metrics.prom 40 | 41 | Running with TLS client authentication: 42 | 43 | $ prom2json --cert=/path/to/certificate --key=/path/to/key http://my-prometheus-client.example.org:8080/metrics 44 | 45 | Running without TLS validation (insecure, do not use in production!): 46 | 47 | $ prom2json --accept-invalid-cert https://my-prometheus-client.example.org:8080/metrics 48 | 49 | Advanced HTTP through `curl`: 50 | 51 | $ curl -XPOST -H 'X-CSRFToken: 1234567890abcdef' --connect-timeout 60 'https://username:password@my-prometheus-client.example.org:8080/metrics' | prom2json 52 | 53 | This will dump the JSON to `stdout`. Note that the dumped JSON is 54 | _not_ using the deprecated JSON format as specified in the 55 | [Prometheus exposition format 56 | reference](https://docs.google.com/document/d/1ZjyKiKxZV83VI9ZKAXRGKaUKK2BIWCT7oiGBKDBpjEY/edit?usp=sharing). The 57 | created JSON uses a format much closer in structure to the protocol 58 | buffer format. It is only used by the `prom2json` tool and has no 59 | significance elsewhere. See below for a description. 60 | 61 | A typical use-case is to pipe the JSON format into a tool like `jq` to 62 | run a query over it. That looked like the following when the clients 63 | still supported the deprecated JSON format: 64 | 65 | $ curl http://my-prometheus-client.example.org:8080/metrics | jq . 66 | 67 | Now simply use `prom2json` instead of `curl` (and change the query 68 | syntax according to the changed JSON format generated by `prom2json`): 69 | 70 | $ prom2json http://my-prometheus-client.example.org:8080/metrics | jq . 71 | 72 | Example query to retrieve the number of metrics in the `http_requests_total` metric family (only works with the new format): 73 | 74 | $ prom2json http://my-prometheus-client.example.org:8080/metrics | jq '.[]|select(.name=="http_requests_total")|.metrics|length' 75 | 76 | Example input from stdin: 77 | 78 | $ curl http://my-prometheus-client.example.org:8080/metrics | grep http_requests_total | prom2json 79 | 80 | # JSON format 81 | 82 | Note that all numbers are encoded as strings. Some parsers want it 83 | that way. Also, Prometheus allows sample values like `NaN` or `+Inf`, 84 | which cannot be encoded as JSON numbers. 85 | 86 | A histogram is formatted as a native histogram if it has at least one span. It 87 | is then formatted in a similar way as [the Prometehus query 88 | API](https://prometheus.io/docs/prometheus/latest/querying/api/#native-histograms) 89 | does it. 90 | 91 | ```json 92 | [ 93 | { 94 | "name": "http_request_duration_microseconds", 95 | "help": "The HTTP request latencies in microseconds.", 96 | "type": "SUMMARY", 97 | "metrics": [ 98 | { 99 | "labels": { 100 | "method": "get", 101 | "handler": "prometheus", 102 | "code": "200" 103 | }, 104 | "quantiles": { 105 | "0.99": "67542.292", 106 | "0.9": "23902.678", 107 | "0.5": "6865.718" 108 | }, 109 | "count": "743", 110 | "sum": "6936936.447000001" 111 | }, 112 | { 113 | "labels": { 114 | "method": "get", 115 | "handler": "prometheus", 116 | "code": "400" 117 | }, 118 | "quantiles": { 119 | "0.99": "3542.9", 120 | "0.9": "1202.3", 121 | "0.5": "1002.8" 122 | }, 123 | "count": "4", 124 | "sum": "345.01" 125 | } 126 | ] 127 | }, 128 | { 129 | "name": "roshi_select_call_count", 130 | "help": "How many select calls have been made.", 131 | "type": "COUNTER", 132 | "metrics": [ 133 | { 134 | "value": "1063110" 135 | } 136 | ] 137 | }, 138 | { 139 | "name": "http_request_duration_seconds", 140 | "type": "HISTOGRAM", 141 | "help": "This is a native histogram.", 142 | "metrics": [ 143 | { 144 | "labels": { 145 | "method": "GET", 146 | }, 147 | "buckets": [ 148 | [ 149 | 0, 150 | "17.448123722644123", 151 | "19.027313840043536", 152 | "139" 153 | ], 154 | [ 155 | 0, 156 | "19.027313840043536", 157 | "20.749432874416154", 158 | "85" 159 | ], 160 | [ 161 | 0, 162 | "20.749432874416154", 163 | "22.62741699796952", 164 | "70" 165 | ], 166 | ], 167 | "count": "1000", 168 | "sum": "29969.50000000001" 169 | } 170 | ] 171 | }, 172 | { 173 | "name": "some_weird_normal_distribution", 174 | "type": "HISTOGRAM", 175 | "help": "This is a classic histogram.", 176 | "metrics": [ 177 | { 178 | "buckets": { 179 | "-0.0001899999999999998": "17", 180 | "-0.0002899999999999998": "6", 181 | "-0.0003899999999999998": "2", 182 | "-0.0004899999999999998": "2", 183 | "-0.0005899999999999998": "0", 184 | "-0.0006899999999999999": "0", 185 | "-0.0007899999999999999": "0", 186 | "-0.00089": "0", 187 | "-0.00099": "0", 188 | "-8.999999999999979e-05": "33", 189 | "0.00011000000000000022": "75", 190 | "0.00021000000000000023": "92", 191 | "0.0003100000000000002": "100", 192 | "0.0004100000000000002": "103", 193 | "0.0005100000000000003": "105", 194 | "0.0006100000000000003": "106", 195 | "0.0007100000000000003": "107", 196 | "0.0008100000000000004": "107", 197 | "0.0009100000000000004": "107", 198 | "1.0000000000000216e-05": "50" 199 | }, 200 | "count": "107", 201 | "sum": "0.001792103516591124" 202 | } 203 | ] 204 | } 205 | ] 206 | ``` 207 | 208 | ## Using Docker 209 | 210 | You can deploy this tool using the [prom/prom2json](https://registry.hub.docker.com/r/prom/prom2json/) Docker image. 211 | 212 | For example: 213 | 214 | ```bash 215 | docker pull prom/prom2json 216 | 217 | docker run --rm -ti prom/prom2json http://my-prometheus-client.example.org:8080/metrics 218 | ``` 219 | -------------------------------------------------------------------------------- /prom2json.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Prometheus Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package prom2json 15 | 16 | import ( 17 | "fmt" 18 | "io" 19 | "mime" 20 | "net/http" 21 | 22 | "github.com/matttproud/golang_protobuf_extensions/pbutil" 23 | "github.com/prometheus/common/expfmt" 24 | "github.com/prometheus/common/model" 25 | 26 | dto "github.com/prometheus/client_model/go" 27 | "github.com/prometheus/prom2json/histogram" 28 | ) 29 | 30 | const ( 31 | acceptHeader = `application/vnd.google.protobuf;proto=io.prometheus.client.MetricFamily;encoding=delimited;q=0.7,text/plain;version=1.0.0;q=0.2,text/plain;version=0.0.4;q=0.1` 32 | // acceptHeaderTemplate takes the escaping scheme as its single 33 | // parameter. Note that we even add the parameter to 34 | // text/plain;version=0.0.4. This version officially does not support 35 | // escaping scheme selection, but some targets implement it anyway, so 36 | // no harm in trying. 37 | acceptHeaderTemplate = `application/vnd.google.protobuf;proto=io.prometheus.client.MetricFamily;encoding=delimited;escaping=%[1]s;q=0.7,text/plain;version=1.0.0;escaping=%[1]s;q=0.2,text/plain;version=0.0.4;escaping=%[1]s;q=0.1` 38 | ) 39 | 40 | // Family mirrors the MetricFamily proto message. 41 | type Family struct { 42 | //Time time.Time 43 | Name string `json:"name"` 44 | Help string `json:"help"` 45 | Type string `json:"type"` 46 | Metrics []any `json:"metrics,omitempty"` // Either metric or summary. 47 | } 48 | 49 | // Metric is for all "single value" metrics, i.e. Counter, Gauge, and Untyped. 50 | type Metric struct { 51 | Labels map[string]string `json:"labels,omitempty"` 52 | TimestampMs string `json:"timestamp_ms,omitempty"` 53 | Value string `json:"value"` 54 | } 55 | 56 | // Summary mirrors the Summary proto message. 57 | type Summary struct { 58 | Labels map[string]string `json:"labels,omitempty"` 59 | TimestampMs string `json:"timestamp_ms,omitempty"` 60 | Quantiles map[string]string `json:"quantiles,omitempty"` 61 | Count string `json:"count"` 62 | Sum string `json:"sum"` 63 | } 64 | 65 | // Histogram mirrors the Histogram proto message. 66 | type Histogram struct { 67 | Labels map[string]string `json:"labels,omitempty"` 68 | TimestampMs string `json:"timestamp_ms,omitempty"` 69 | Buckets any `json:"buckets,omitempty"` 70 | Count string `json:"count"` 71 | Sum string `json:"sum"` 72 | } 73 | 74 | // NewFamily consumes a MetricFamily and transforms it to the local Family type. 75 | func NewFamily(dtoMF *dto.MetricFamily) *Family { 76 | mf := &Family{ 77 | //Time: time.Now(), 78 | Name: dtoMF.GetName(), 79 | Help: dtoMF.GetHelp(), 80 | Type: dtoMF.GetType().String(), 81 | Metrics: make([]any, len(dtoMF.Metric)), 82 | } 83 | for i, m := range dtoMF.Metric { 84 | switch dtoMF.GetType() { 85 | case dto.MetricType_SUMMARY: 86 | mf.Metrics[i] = Summary{ 87 | Labels: makeLabels(m), 88 | TimestampMs: makeTimestamp(m), 89 | Quantiles: makeQuantiles(m), 90 | Count: fmt.Sprint(m.GetSummary().GetSampleCount()), 91 | Sum: fmt.Sprint(m.GetSummary().GetSampleSum()), 92 | } 93 | case dto.MetricType_HISTOGRAM: 94 | mf.Metrics[i] = makeHistogram(m) 95 | default: 96 | mf.Metrics[i] = Metric{ 97 | Labels: makeLabels(m), 98 | TimestampMs: makeTimestamp(m), 99 | Value: fmt.Sprint(getValue(m)), 100 | } 101 | } 102 | } 103 | return mf 104 | } 105 | 106 | func getValue(m *dto.Metric) float64 { 107 | switch { 108 | case m.Gauge != nil: 109 | return m.GetGauge().GetValue() 110 | case m.Counter != nil: 111 | return m.GetCounter().GetValue() 112 | case m.Untyped != nil: 113 | return m.GetUntyped().GetValue() 114 | default: 115 | return 0. 116 | } 117 | } 118 | 119 | func makeHistogram(m *dto.Metric) Histogram { 120 | dtoH := m.GetHistogram() 121 | hist := Histogram{ 122 | Labels: makeLabels(m), 123 | TimestampMs: makeTimestamp(m), 124 | Sum: fmt.Sprint(dtoH.GetSampleSum()), 125 | } 126 | // A native histogram is marked by at least one span. 127 | if len(dtoH.GetNegativeSpan())+len(dtoH.GetPositiveSpan()) > 0 { 128 | h, fh := histogram.NewModelHistogram(dtoH) 129 | if h == nil { 130 | // float histogram 131 | hist.Buckets = histogram.BucketsAsJson[float64](histogram.GetAPIFloatBuckets(fh)) 132 | hist.Count = fmt.Sprint(fh.Count) 133 | } else { 134 | hist.Buckets = histogram.BucketsAsJson[uint64](histogram.GetAPIBuckets(h)) 135 | hist.Count = fmt.Sprint(h.Count) 136 | } 137 | } else { 138 | hist.Buckets = makeBuckets(m) 139 | if count := dtoH.GetSampleCountFloat(); count > 0 { 140 | hist.Count = fmt.Sprint(count) 141 | } else { 142 | hist.Count = fmt.Sprint(dtoH.GetSampleCount()) 143 | } 144 | } 145 | return hist 146 | } 147 | 148 | func makeLabels(m *dto.Metric) map[string]string { 149 | result := map[string]string{} 150 | for _, lp := range m.Label { 151 | result[lp.GetName()] = lp.GetValue() 152 | } 153 | return result 154 | } 155 | 156 | func makeTimestamp(m *dto.Metric) string { 157 | if m.TimestampMs == nil { 158 | return "" 159 | } 160 | return fmt.Sprint(m.GetTimestampMs()) 161 | } 162 | 163 | func makeQuantiles(m *dto.Metric) map[string]string { 164 | result := map[string]string{} 165 | for _, q := range m.GetSummary().Quantile { 166 | result[fmt.Sprint(q.GetQuantile())] = fmt.Sprint(q.GetValue()) 167 | } 168 | return result 169 | } 170 | 171 | func makeBuckets(m *dto.Metric) map[string]string { 172 | result := map[string]string{} 173 | for _, b := range m.GetHistogram().Bucket { 174 | if count := b.GetCumulativeCountFloat(); count > 0 { 175 | result[fmt.Sprint(b.GetUpperBound())] = fmt.Sprint(count) 176 | } else { 177 | result[fmt.Sprint(b.GetUpperBound())] = fmt.Sprint(b.GetCumulativeCount()) 178 | } 179 | } 180 | return result 181 | } 182 | 183 | // FetchMetricFamilies retrieves metrics from the provided URL, decodes them 184 | // into MetricFamily proto messages, and sends them to the provided channel. It 185 | // returns after all MetricFamilies have been sent. The provided transport 186 | // may be nil (in which case the default Transport is used). 187 | func FetchMetricFamilies(url string, ch chan<- *dto.MetricFamily, transport http.RoundTripper) error { 188 | return FetchMetricFamiliesWithEscapingScheme(url, ch, transport, "") 189 | } 190 | 191 | // FetchMetricFamiliesWithEscapingScheme works like FetchMetricFamilies but adds 192 | // the provided string as the value of the additional 'escaping' parameter in 193 | // the accept header. 194 | func FetchMetricFamiliesWithEscapingScheme(url string, ch chan<- *dto.MetricFamily, transport http.RoundTripper, escapingScheme string) error { 195 | req, err := http.NewRequest("GET", url, nil) 196 | if err != nil { 197 | close(ch) 198 | return fmt.Errorf("creating GET request for URL %q failed: %w", url, err) 199 | } 200 | if escapingScheme != "" { 201 | req.Header.Add("Accept", fmt.Sprintf(acceptHeaderTemplate, escapingScheme)) 202 | } else { 203 | req.Header.Add("Accept", acceptHeader) 204 | } 205 | client := http.Client{Transport: transport} 206 | resp, err := client.Do(req) 207 | if err != nil { 208 | close(ch) 209 | return fmt.Errorf("executing GET request for URL %q failed: %w", url, err) 210 | } 211 | defer resp.Body.Close() 212 | if resp.StatusCode != http.StatusOK { 213 | close(ch) 214 | return fmt.Errorf("GET request for URL %q returned HTTP status %s", url, resp.Status) 215 | } 216 | return ParseResponse(resp, ch) 217 | } 218 | 219 | // ParseResponse consumes an http.Response and pushes it to the MetricFamily 220 | // channel. It returns when all MetricFamilies are parsed and put on the 221 | // channel. 222 | func ParseResponse(resp *http.Response, ch chan<- *dto.MetricFamily) error { 223 | mediatype, params, err := mime.ParseMediaType(resp.Header.Get("Content-Type")) 224 | if err == nil && mediatype == "application/vnd.google.protobuf" && 225 | params["encoding"] == "delimited" && 226 | params["proto"] == "io.prometheus.client.MetricFamily" { 227 | defer close(ch) 228 | for { 229 | mf := &dto.MetricFamily{} 230 | if _, err = pbutil.ReadDelimited(resp.Body, mf); err != nil { 231 | if err == io.EOF { 232 | break 233 | } 234 | return fmt.Errorf("reading metric family protocol buffer failed: %w", err) 235 | } 236 | ch <- mf 237 | } 238 | } else { 239 | if err := ParseReader(resp.Body, ch); err != nil { 240 | return err 241 | } 242 | } 243 | return nil 244 | } 245 | 246 | // ParseReader consumes an io.Reader and pushes it to the MetricFamily 247 | // channel. It returns when all MetricFamilies are parsed and put on the 248 | // channel. 249 | func ParseReader(in io.Reader, ch chan<- *dto.MetricFamily) error { 250 | defer close(ch) 251 | // We could do further content-type checks here, but the 252 | // fallback for now will anyway be the text format 253 | // version 0.0.4, so just go for it and see if it works. 254 | parser := expfmt.NewTextParser(model.UTF8Validation) 255 | metricFamilies, err := parser.TextToMetricFamilies(in) 256 | if err != nil { 257 | return fmt.Errorf("reading text format failed: %v", err) 258 | } 259 | for _, mf := range metricFamilies { 260 | ch <- mf 261 | } 262 | return nil 263 | } 264 | 265 | // AddLabel allows to add key/value labels to an already existing Family. 266 | func (f *Family) AddLabel(key, val string) { 267 | for i, item := range f.Metrics { 268 | switch m := item.(type) { 269 | case Metric: 270 | m.Labels[key] = val 271 | f.Metrics[i] = m 272 | } 273 | } 274 | } 275 | -------------------------------------------------------------------------------- /Makefile.common: -------------------------------------------------------------------------------- 1 | # Copyright 2018 The Prometheus Authors 2 | # Licensed under the Apache License, Version 2.0 (the "License"); 3 | # you may not use this file except in compliance with the License. 4 | # You may obtain a copy of the License at 5 | # 6 | # http://www.apache.org/licenses/LICENSE-2.0 7 | # 8 | # Unless required by applicable law or agreed to in writing, software 9 | # distributed under the License is distributed on an "AS IS" BASIS, 10 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # See the License for the specific language governing permissions and 12 | # limitations under the License. 13 | 14 | 15 | # A common Makefile that includes rules to be reused in different prometheus projects. 16 | # !!! Open PRs only against the prometheus/prometheus/Makefile.common repository! 17 | 18 | # Example usage : 19 | # Create the main Makefile in the root project directory. 20 | # include Makefile.common 21 | # customTarget: 22 | # @echo ">> Running customTarget" 23 | # 24 | 25 | # Ensure GOBIN is not set during build so that promu is installed to the correct path 26 | unexport GOBIN 27 | 28 | GO ?= go 29 | GOFMT ?= $(GO)fmt 30 | FIRST_GOPATH := $(firstword $(subst :, ,$(shell $(GO) env GOPATH))) 31 | GOOPTS ?= 32 | GOHOSTOS ?= $(shell $(GO) env GOHOSTOS) 33 | GOHOSTARCH ?= $(shell $(GO) env GOHOSTARCH) 34 | 35 | GO_VERSION ?= $(shell $(GO) version) 36 | GO_VERSION_NUMBER ?= $(word 3, $(GO_VERSION)) 37 | PRE_GO_111 ?= $(shell echo $(GO_VERSION_NUMBER) | grep -E 'go1\.(10|[0-9])\.') 38 | 39 | PROMU := $(FIRST_GOPATH)/bin/promu 40 | pkgs = ./... 41 | 42 | ifeq (arm, $(GOHOSTARCH)) 43 | GOHOSTARM ?= $(shell GOARM= $(GO) env GOARM) 44 | GO_BUILD_PLATFORM ?= $(GOHOSTOS)-$(GOHOSTARCH)v$(GOHOSTARM) 45 | else 46 | GO_BUILD_PLATFORM ?= $(GOHOSTOS)-$(GOHOSTARCH) 47 | endif 48 | 49 | GOTEST := $(GO) test 50 | GOTEST_DIR := 51 | ifneq ($(CIRCLE_JOB),) 52 | ifneq ($(shell command -v gotestsum 2> /dev/null),) 53 | GOTEST_DIR := test-results 54 | GOTEST := gotestsum --junitfile $(GOTEST_DIR)/unit-tests.xml -- 55 | endif 56 | endif 57 | 58 | PROMU_VERSION ?= 0.17.0 59 | PROMU_URL := https://github.com/prometheus/promu/releases/download/v$(PROMU_VERSION)/promu-$(PROMU_VERSION).$(GO_BUILD_PLATFORM).tar.gz 60 | 61 | SKIP_GOLANGCI_LINT := 62 | GOLANGCI_LINT := 63 | GOLANGCI_LINT_OPTS ?= 64 | GOLANGCI_LINT_VERSION ?= v2.6.2 65 | GOLANGCI_FMT_OPTS ?= 66 | # golangci-lint only supports linux, darwin and windows platforms on i386/amd64/arm64. 67 | # windows isn't included here because of the path separator being different. 68 | ifeq ($(GOHOSTOS),$(filter $(GOHOSTOS),linux darwin)) 69 | ifeq ($(GOHOSTARCH),$(filter $(GOHOSTARCH),amd64 i386 arm64)) 70 | # If we're in CI and there is an Actions file, that means the linter 71 | # is being run in Actions, so we don't need to run it here. 72 | ifneq (,$(SKIP_GOLANGCI_LINT)) 73 | GOLANGCI_LINT := 74 | else ifeq (,$(CIRCLE_JOB)) 75 | GOLANGCI_LINT := $(FIRST_GOPATH)/bin/golangci-lint 76 | else ifeq (,$(wildcard .github/workflows/golangci-lint.yml)) 77 | GOLANGCI_LINT := $(FIRST_GOPATH)/bin/golangci-lint 78 | endif 79 | endif 80 | endif 81 | 82 | PREFIX ?= $(shell pwd) 83 | BIN_DIR ?= $(shell pwd) 84 | DOCKER_IMAGE_TAG ?= $(subst /,-,$(shell git rev-parse --abbrev-ref HEAD)) 85 | DOCKERFILE_PATH ?= ./Dockerfile 86 | DOCKERBUILD_CONTEXT ?= ./ 87 | DOCKER_REPO ?= prom 88 | 89 | DOCKER_ARCHS ?= amd64 90 | 91 | BUILD_DOCKER_ARCHS = $(addprefix common-docker-,$(DOCKER_ARCHS)) 92 | PUBLISH_DOCKER_ARCHS = $(addprefix common-docker-publish-,$(DOCKER_ARCHS)) 93 | TAG_DOCKER_ARCHS = $(addprefix common-docker-tag-latest-,$(DOCKER_ARCHS)) 94 | 95 | SANITIZED_DOCKER_IMAGE_TAG := $(subst +,-,$(DOCKER_IMAGE_TAG)) 96 | 97 | ifeq ($(GOHOSTARCH),amd64) 98 | ifeq ($(GOHOSTOS),$(filter $(GOHOSTOS),linux freebsd darwin windows)) 99 | # Only supported on amd64 100 | test-flags := -race 101 | endif 102 | endif 103 | 104 | # This rule is used to forward a target like "build" to "common-build". This 105 | # allows a new "build" target to be defined in a Makefile which includes this 106 | # one and override "common-build" without override warnings. 107 | %: common-% ; 108 | 109 | .PHONY: common-all 110 | common-all: precheck style check_license lint yamllint unused build test 111 | 112 | .PHONY: common-style 113 | common-style: 114 | @echo ">> checking code style" 115 | @fmtRes=$$($(GOFMT) -d $$(git ls-files '*.go' ':!:vendor/*' || find . -path ./vendor -prune -o -name '*.go' -print)); \ 116 | if [ -n "$${fmtRes}" ]; then \ 117 | echo "gofmt checking failed!"; echo "$${fmtRes}"; echo; \ 118 | echo "Please ensure you are using $$($(GO) version) for formatting code."; \ 119 | exit 1; \ 120 | fi 121 | 122 | .PHONY: common-check_license 123 | common-check_license: 124 | @echo ">> checking license header" 125 | @licRes=$$(for file in $$(git ls-files '*.go' ':!:vendor/*' || find . -path ./vendor -prune -o -type f -iname '*.go' -print) ; do \ 126 | awk 'NR<=3' $$file | grep -Eq "(Copyright|generated|GENERATED)" || echo $$file; \ 127 | done); \ 128 | if [ -n "$${licRes}" ]; then \ 129 | echo "license header checking failed:"; echo "$${licRes}"; \ 130 | exit 1; \ 131 | fi 132 | 133 | .PHONY: common-deps 134 | common-deps: 135 | @echo ">> getting dependencies" 136 | $(GO) mod download 137 | 138 | .PHONY: update-go-deps 139 | update-go-deps: 140 | @echo ">> updating Go dependencies" 141 | @for m in $$($(GO) list -mod=readonly -m -f '{{ if and (not .Indirect) (not .Main)}}{{.Path}}{{end}}' all); do \ 142 | $(GO) get $$m; \ 143 | done 144 | $(GO) mod tidy 145 | 146 | .PHONY: common-test-short 147 | common-test-short: $(GOTEST_DIR) 148 | @echo ">> running short tests" 149 | $(GOTEST) -short $(GOOPTS) $(pkgs) 150 | 151 | .PHONY: common-test 152 | common-test: $(GOTEST_DIR) 153 | @echo ">> running all tests" 154 | $(GOTEST) $(test-flags) $(GOOPTS) $(pkgs) 155 | 156 | $(GOTEST_DIR): 157 | @mkdir -p $@ 158 | 159 | .PHONY: common-format 160 | common-format: $(GOLANGCI_LINT) 161 | @echo ">> formatting code" 162 | $(GO) fmt $(pkgs) 163 | ifdef GOLANGCI_LINT 164 | @echo ">> formatting code with golangci-lint" 165 | $(GOLANGCI_LINT) fmt $(GOLANGCI_FMT_OPTS) 166 | endif 167 | 168 | .PHONY: common-vet 169 | common-vet: 170 | @echo ">> vetting code" 171 | $(GO) vet $(GOOPTS) $(pkgs) 172 | 173 | .PHONY: common-lint 174 | common-lint: $(GOLANGCI_LINT) 175 | ifdef GOLANGCI_LINT 176 | @echo ">> running golangci-lint" 177 | $(GOLANGCI_LINT) run $(GOLANGCI_LINT_OPTS) $(pkgs) 178 | endif 179 | 180 | .PHONY: common-lint-fix 181 | common-lint-fix: $(GOLANGCI_LINT) 182 | ifdef GOLANGCI_LINT 183 | @echo ">> running golangci-lint fix" 184 | $(GOLANGCI_LINT) run --fix $(GOLANGCI_LINT_OPTS) $(pkgs) 185 | endif 186 | 187 | .PHONY: common-yamllint 188 | common-yamllint: 189 | @echo ">> running yamllint on all YAML files in the repository" 190 | ifeq (, $(shell command -v yamllint 2> /dev/null)) 191 | @echo "yamllint not installed so skipping" 192 | else 193 | yamllint . 194 | endif 195 | 196 | # For backward-compatibility. 197 | .PHONY: common-staticcheck 198 | common-staticcheck: lint 199 | 200 | .PHONY: common-unused 201 | common-unused: 202 | @echo ">> running check for unused/missing packages in go.mod" 203 | $(GO) mod tidy 204 | @git diff --exit-code -- go.sum go.mod 205 | 206 | .PHONY: common-build 207 | common-build: promu 208 | @echo ">> building binaries" 209 | $(PROMU) build --prefix $(PREFIX) $(PROMU_BINARIES) 210 | 211 | .PHONY: common-tarball 212 | common-tarball: promu 213 | @echo ">> building release tarball" 214 | $(PROMU) tarball --prefix $(PREFIX) $(BIN_DIR) 215 | 216 | .PHONY: common-docker-repo-name 217 | common-docker-repo-name: 218 | @echo "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)" 219 | 220 | .PHONY: common-docker $(BUILD_DOCKER_ARCHS) 221 | common-docker: $(BUILD_DOCKER_ARCHS) 222 | $(BUILD_DOCKER_ARCHS): common-docker-%: 223 | docker build -t "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(SANITIZED_DOCKER_IMAGE_TAG)" \ 224 | -f $(DOCKERFILE_PATH) \ 225 | --build-arg ARCH="$*" \ 226 | --build-arg OS="linux" \ 227 | $(DOCKERBUILD_CONTEXT) 228 | 229 | .PHONY: common-docker-publish $(PUBLISH_DOCKER_ARCHS) 230 | common-docker-publish: $(PUBLISH_DOCKER_ARCHS) 231 | $(PUBLISH_DOCKER_ARCHS): common-docker-publish-%: 232 | docker push "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(SANITIZED_DOCKER_IMAGE_TAG)" 233 | 234 | DOCKER_MAJOR_VERSION_TAG = $(firstword $(subst ., ,$(shell cat VERSION))) 235 | .PHONY: common-docker-tag-latest $(TAG_DOCKER_ARCHS) 236 | common-docker-tag-latest: $(TAG_DOCKER_ARCHS) 237 | $(TAG_DOCKER_ARCHS): common-docker-tag-latest-%: 238 | docker tag "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(SANITIZED_DOCKER_IMAGE_TAG)" "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:latest" 239 | docker tag "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(SANITIZED_DOCKER_IMAGE_TAG)" "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:v$(DOCKER_MAJOR_VERSION_TAG)" 240 | 241 | .PHONY: common-docker-manifest 242 | common-docker-manifest: 243 | DOCKER_CLI_EXPERIMENTAL=enabled docker manifest create -a "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME):$(SANITIZED_DOCKER_IMAGE_TAG)" $(foreach ARCH,$(DOCKER_ARCHS),$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$(ARCH):$(SANITIZED_DOCKER_IMAGE_TAG)) 244 | DOCKER_CLI_EXPERIMENTAL=enabled docker manifest push "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME):$(SANITIZED_DOCKER_IMAGE_TAG)" 245 | 246 | .PHONY: promu 247 | promu: $(PROMU) 248 | 249 | $(PROMU): 250 | $(eval PROMU_TMP := $(shell mktemp -d)) 251 | curl -s -L $(PROMU_URL) | tar -xvzf - -C $(PROMU_TMP) 252 | mkdir -p $(FIRST_GOPATH)/bin 253 | cp $(PROMU_TMP)/promu-$(PROMU_VERSION).$(GO_BUILD_PLATFORM)/promu $(FIRST_GOPATH)/bin/promu 254 | rm -r $(PROMU_TMP) 255 | 256 | .PHONY: common-proto 257 | common-proto: 258 | @echo ">> generating code from proto files" 259 | @./scripts/genproto.sh 260 | 261 | ifdef GOLANGCI_LINT 262 | $(GOLANGCI_LINT): 263 | mkdir -p $(FIRST_GOPATH)/bin 264 | curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/$(GOLANGCI_LINT_VERSION)/install.sh \ 265 | | sed -e '/install -d/d' \ 266 | | sh -s -- -b $(FIRST_GOPATH)/bin $(GOLANGCI_LINT_VERSION) 267 | endif 268 | 269 | .PHONY: common-print-golangci-lint-version 270 | common-print-golangci-lint-version: 271 | @echo $(GOLANGCI_LINT_VERSION) 272 | 273 | .PHONY: precheck 274 | precheck:: 275 | 276 | define PRECHECK_COMMAND_template = 277 | precheck:: $(1)_precheck 278 | 279 | PRECHECK_COMMAND_$(1) ?= $(1) $$(strip $$(PRECHECK_OPTIONS_$(1))) 280 | .PHONY: $(1)_precheck 281 | $(1)_precheck: 282 | @if ! $$(PRECHECK_COMMAND_$(1)) 1>/dev/null 2>&1; then \ 283 | echo "Execution of '$$(PRECHECK_COMMAND_$(1))' command failed. Is $(1) installed?"; \ 284 | exit 1; \ 285 | fi 286 | endef 287 | 288 | govulncheck: install-govulncheck 289 | govulncheck ./... 290 | 291 | install-govulncheck: 292 | command -v govulncheck > /dev/null || go install golang.org/x/vuln/cmd/govulncheck@latest 293 | -------------------------------------------------------------------------------- /prom2json_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Prometheus Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package prom2json 15 | 16 | import ( 17 | "math" 18 | "reflect" 19 | "testing" 20 | 21 | "github.com/davecgh/go-spew/spew" 22 | dto "github.com/prometheus/client_model/go" 23 | ) 24 | 25 | type testCase struct { 26 | name string 27 | mFamily *dto.MetricFamily 28 | output *Family 29 | } 30 | 31 | var tcs = []testCase{ 32 | testCase{ 33 | name: "test counter", 34 | mFamily: &dto.MetricFamily{ 35 | Name: strPtr("counter1"), 36 | Type: metricTypePtr(dto.MetricType_COUNTER), 37 | Metric: []*dto.Metric{ 38 | // Test metric with tags 39 | &dto.Metric{ 40 | Label: []*dto.LabelPair{ 41 | createLabelPair("tag1", "abc"), 42 | createLabelPair("tag2", "def"), 43 | }, 44 | Counter: &dto.Counter{ 45 | Value: floatPtr(1), 46 | }, 47 | }, 48 | // Test metric with tags and timestamp 49 | &dto.Metric{ 50 | Label: []*dto.LabelPair{ 51 | createLabelPair("tag1", "foo"), 52 | createLabelPair("tag2", "bar"), 53 | }, 54 | TimestampMs: int64Ptr(123456), 55 | Counter: &dto.Counter{ 56 | Value: floatPtr(42), 57 | }, 58 | }, 59 | // Test metric without tags 60 | &dto.Metric{ 61 | Label: []*dto.LabelPair{}, 62 | Counter: &dto.Counter{ 63 | Value: floatPtr(2), 64 | }, 65 | }, 66 | // Test metric with -Inf 67 | &dto.Metric{ 68 | Label: []*dto.LabelPair{ 69 | createLabelPair("inf", "neg"), 70 | }, 71 | Counter: &dto.Counter{ 72 | Value: floatPtr(math.Inf(-1)), 73 | }, 74 | }, 75 | // Test metric with +Inf 76 | &dto.Metric{ 77 | Label: []*dto.LabelPair{ 78 | createLabelPair("inf", "pos"), 79 | }, 80 | Counter: &dto.Counter{ 81 | Value: floatPtr(math.Inf(1)), 82 | }, 83 | }, 84 | }, 85 | }, 86 | output: &Family{ 87 | Name: "counter1", 88 | Help: "", 89 | Type: "COUNTER", 90 | Metrics: []any{ 91 | Metric{ 92 | Labels: map[string]string{ 93 | "tag2": "def", 94 | "tag1": "abc", 95 | }, 96 | Value: "1", 97 | }, 98 | Metric{ 99 | Labels: map[string]string{ 100 | "tag2": "bar", 101 | "tag1": "foo", 102 | }, 103 | TimestampMs: "123456", 104 | Value: "42", 105 | }, 106 | Metric{ 107 | Labels: map[string]string{}, 108 | Value: "2", 109 | }, 110 | Metric{ 111 | Labels: map[string]string{ 112 | "inf": "neg", 113 | }, 114 | Value: "-Inf", 115 | }, 116 | Metric{ 117 | Labels: map[string]string{ 118 | "inf": "pos", 119 | }, 120 | Value: "+Inf", 121 | }, 122 | }, 123 | }, 124 | }, 125 | testCase{ 126 | name: "test summaries", 127 | mFamily: &dto.MetricFamily{ 128 | Name: strPtr("summary1"), 129 | Type: metricTypePtr(dto.MetricType_SUMMARY), 130 | Metric: []*dto.Metric{ 131 | &dto.Metric{ 132 | // Test summary with NaN 133 | Label: []*dto.LabelPair{ 134 | createLabelPair("tag1", "abc"), 135 | createLabelPair("tag2", "def"), 136 | }, 137 | Summary: &dto.Summary{ 138 | SampleCount: uintPtr(1), 139 | SampleSum: floatPtr(2), 140 | Quantile: []*dto.Quantile{ 141 | createQuantile(0.5, 3), 142 | createQuantile(0.9, 4), 143 | createQuantile(0.99, math.NaN()), 144 | }, 145 | }, 146 | }, 147 | }, 148 | }, 149 | output: &Family{ 150 | Name: "summary1", 151 | Help: "", 152 | Type: "SUMMARY", 153 | Metrics: []any{ 154 | Summary{ 155 | Labels: map[string]string{ 156 | "tag1": "abc", 157 | "tag2": "def", 158 | }, 159 | Quantiles: map[string]string{ 160 | "0.5": "3", 161 | "0.9": "4", 162 | "0.99": "NaN", 163 | }, 164 | Count: "1", 165 | Sum: "2", 166 | }, 167 | }, 168 | }, 169 | }, 170 | testCase{ 171 | name: "test histograms", 172 | mFamily: &dto.MetricFamily{ 173 | Name: strPtr("histogram1"), 174 | Type: metricTypePtr(dto.MetricType_HISTOGRAM), 175 | Metric: []*dto.Metric{ 176 | &dto.Metric{ 177 | // Test summary with NaN 178 | Label: []*dto.LabelPair{ 179 | createLabelPair("tag1", "abc"), 180 | createLabelPair("tag2", "def"), 181 | }, 182 | Histogram: &dto.Histogram{ 183 | SampleCount: uintPtr(1), 184 | SampleSum: floatPtr(2), 185 | Bucket: []*dto.Bucket{ 186 | createBucket(250000, 3), 187 | createBucket(500000, 4), 188 | createBucket(1e+06, 5), 189 | }, 190 | }, 191 | }, 192 | }, 193 | }, 194 | output: &Family{ 195 | Name: "histogram1", 196 | Help: "", 197 | Type: "HISTOGRAM", 198 | Metrics: []any{ 199 | Histogram{ 200 | Labels: map[string]string{ 201 | "tag1": "abc", 202 | "tag2": "def", 203 | }, 204 | Buckets: map[string]string{ 205 | "250000": "3", 206 | "500000": "4", 207 | "1e+06": "5", 208 | }, 209 | Count: "1", 210 | Sum: "2", 211 | }, 212 | }, 213 | }, 214 | }, 215 | testCase{ 216 | name: "test float histograms", 217 | mFamily: &dto.MetricFamily{ 218 | Name: strPtr("histogram1"), 219 | Type: metricTypePtr(dto.MetricType_HISTOGRAM), 220 | Metric: []*dto.Metric{ 221 | &dto.Metric{ 222 | // Test summary with NaN 223 | Label: []*dto.LabelPair{ 224 | createLabelPair("tag1", "abc"), 225 | createLabelPair("tag2", "def"), 226 | }, 227 | Histogram: &dto.Histogram{ 228 | SampleCountFloat: floatPtr(1), 229 | SampleSum: floatPtr(2), 230 | Bucket: []*dto.Bucket{ 231 | createFloatBucket(250000, 3), 232 | createFloatBucket(500000, 4), 233 | createFloatBucket(1e+06, 5), 234 | }, 235 | }, 236 | }, 237 | }, 238 | }, 239 | output: &Family{ 240 | Name: "histogram1", 241 | Help: "", 242 | Type: "HISTOGRAM", 243 | Metrics: []any{ 244 | Histogram{ 245 | Labels: map[string]string{ 246 | "tag1": "abc", 247 | "tag2": "def", 248 | }, 249 | Buckets: map[string]string{ 250 | "250000": "3", 251 | "500000": "4", 252 | "1e+06": "5", 253 | }, 254 | Count: "1", 255 | Sum: "2", 256 | }, 257 | }, 258 | }, 259 | }, 260 | testCase{ 261 | name: "test native histograms", 262 | mFamily: &dto.MetricFamily{ 263 | Name: strPtr("histogram2"), 264 | Type: metricTypePtr(dto.MetricType_HISTOGRAM), 265 | Metric: []*dto.Metric{ 266 | &dto.Metric{ 267 | // Test summary with NaN 268 | Label: []*dto.LabelPair{ 269 | createLabelPair("tag1", "abc"), 270 | createLabelPair("tag2", "def"), 271 | }, 272 | Histogram: &dto.Histogram{ 273 | SampleCount: uintPtr(10), 274 | SampleSum: floatPtr(123.45), 275 | Schema: int32Ptr(1), 276 | PositiveSpan: []*dto.BucketSpan{ 277 | createBucketSpan(0, 3), 278 | createBucketSpan(1, 1), 279 | }, 280 | PositiveDelta: []int64{1, 2, 3, 4}, 281 | }, 282 | }, 283 | }, 284 | }, 285 | output: &Family{ 286 | Name: "histogram2", 287 | Help: "", 288 | Type: "HISTOGRAM", 289 | Metrics: []any{ 290 | Histogram{ 291 | Labels: map[string]string{ 292 | "tag1": "abc", 293 | "tag2": "def", 294 | }, 295 | Buckets: [][]any{ 296 | { 297 | uint64(0), 298 | "0.7071067811865475", 299 | "1", 300 | "1", 301 | }, 302 | { 303 | uint64(0), 304 | "1", 305 | "1.414213562373095", 306 | "3", 307 | }, 308 | { 309 | uint64(0), 310 | "1.414213562373095", 311 | "2", 312 | "6", 313 | }, 314 | { 315 | uint64(0), 316 | "2.82842712474619", 317 | "4", 318 | "10", 319 | }, 320 | }, 321 | Count: "10", 322 | Sum: "123.45", 323 | }, 324 | }, 325 | }, 326 | }, 327 | testCase{ 328 | name: "test native float histograms", 329 | mFamily: &dto.MetricFamily{ 330 | Name: strPtr("histogram2"), 331 | Type: metricTypePtr(dto.MetricType_HISTOGRAM), 332 | Metric: []*dto.Metric{ 333 | &dto.Metric{ 334 | // Test summary with NaN 335 | Label: []*dto.LabelPair{ 336 | createLabelPair("tag1", "abc"), 337 | createLabelPair("tag2", "def"), 338 | }, 339 | Histogram: &dto.Histogram{ 340 | SampleCountFloat: floatPtr(10), 341 | SampleSum: floatPtr(123.45), 342 | Schema: int32Ptr(1), 343 | PositiveSpan: []*dto.BucketSpan{ 344 | createBucketSpan(0, 3), 345 | createBucketSpan(1, 1), 346 | }, 347 | PositiveCount: []float64{1, 3, 6, 10}, 348 | }, 349 | }, 350 | }, 351 | }, 352 | output: &Family{ 353 | Name: "histogram2", 354 | Help: "", 355 | Type: "HISTOGRAM", 356 | Metrics: []any{ 357 | Histogram{ 358 | Labels: map[string]string{ 359 | "tag1": "abc", 360 | "tag2": "def", 361 | }, 362 | Buckets: [][]any{ 363 | { 364 | uint64(0), 365 | "0.7071067811865475", 366 | "1", 367 | "1", 368 | }, 369 | { 370 | uint64(0), 371 | "1", 372 | "1.414213562373095", 373 | "3", 374 | }, 375 | { 376 | uint64(0), 377 | "1.414213562373095", 378 | "2", 379 | "6", 380 | }, 381 | { 382 | uint64(0), 383 | "2.82842712474619", 384 | "4", 385 | "10", 386 | }, 387 | }, 388 | Count: "10", 389 | Sum: "123.45", 390 | }, 391 | }, 392 | }, 393 | }, 394 | } 395 | 396 | func TestConvertToMetricFamily(t *testing.T) { 397 | for _, tc := range tcs { 398 | output := NewFamily(tc.mFamily) 399 | if !reflect.DeepEqual(tc.output, output) { 400 | t.Errorf("test case %s: conversion to metricFamily format failed:\nexpected:\n%s\n\nactual:\n%s", 401 | tc.name, spew.Sdump(tc.output), spew.Sdump(output)) 402 | } 403 | } 404 | } 405 | 406 | func strPtr(s string) *string { 407 | return &s 408 | } 409 | 410 | func floatPtr(f float64) *float64 { 411 | return &f 412 | } 413 | 414 | func metricTypePtr(mt dto.MetricType) *dto.MetricType { 415 | return &mt 416 | } 417 | 418 | func uintPtr(u uint64) *uint64 { 419 | return &u 420 | } 421 | 422 | func int32Ptr(i int32) *int32 { 423 | return &i 424 | } 425 | 426 | func int64Ptr(i int64) *int64 { 427 | return &i 428 | } 429 | 430 | func createLabelPair(name string, value string) *dto.LabelPair { 431 | return &dto.LabelPair{ 432 | Name: &name, 433 | Value: &value, 434 | } 435 | } 436 | 437 | func createQuantile(q float64, v float64) *dto.Quantile { 438 | return &dto.Quantile{ 439 | Quantile: &q, 440 | Value: &v, 441 | } 442 | } 443 | 444 | func createBucket(bound float64, count uint64) *dto.Bucket { 445 | return &dto.Bucket{ 446 | UpperBound: &bound, 447 | CumulativeCount: &count, 448 | } 449 | } 450 | 451 | func createFloatBucket(bound float64, count float64) *dto.Bucket { 452 | return &dto.Bucket{ 453 | UpperBound: &bound, 454 | CumulativeCountFloat: &count, 455 | } 456 | } 457 | 458 | func createBucketSpan(offset int32, length uint32) *dto.BucketSpan { 459 | return &dto.BucketSpan{ 460 | Offset: &offset, 461 | Length: &length, 462 | } 463 | } 464 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | --------------------------------------------------------------------------------