├── .github ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ └── main.yml ├── .gitignore ├── .goreleaser.yml ├── .promu.yml ├── CONTRIBUTORS.md ├── Dockerfile ├── Dockerfile.testing ├── LICENSE ├── Makefile ├── README.md ├── VERSION ├── examples ├── alerts.yaml ├── dashboard.json ├── dashboard.png └── prometheus.yaml ├── go.mod ├── go.sum ├── internal └── mock │ └── mock.go ├── opensips.cfg ├── opensips ├── jsonrpc │ └── opensips_jsonrpc.go ├── opensips.go └── opensips_test.go ├── opensips_exporter.go ├── processors ├── core_processor.go ├── dialog_processor.go ├── load_processor.go ├── net_processor.go ├── pkmem_processor.go ├── processor.go ├── registrar_processor.go ├── scrape_processor.go ├── shmem_processor.go ├── sl_processor.go ├── tm_processor.go ├── tmx_processor.go ├── uri_processor.go └── usrloc_processor.go └── run.sh /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | Nice having you onboard! 3 | 4 | We love contributions from everyone and here are a few things to keep in mind when contributing. 5 | 6 | ## Issues 7 | Somethings not working like it should? We've got you covered! 8 | 9 | * Use github to create an issue; 10 | * Write a descriptive title; 11 | * Fill out the issue template; 12 | * Wait for a developer to make coffee and get back to you; 13 | 14 | ## Pull requests 15 | You want to help out by contributing code? Here's a checklist for a pull request: 16 | 17 | * Fork the project; 18 | * Create a branch for your code; 19 | * Write your awesome code; 20 | * Make sure all tests pass; 21 | * Don't forget to add yourself to the [CONTRIBUTORS.md](CONTRIBUTORS.md) file; 22 | * Squash commits and provide a sane commit message; 23 | * Create a pull request; 24 | * Write a descriptive title; 25 | * Fill out the pull request template; 26 | * Wait for a developer to make coffee and get back to you; 27 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### Version 2 | 3 | {version or tag here} 4 | 5 | ### File / Feature 6 | 7 | {file or feature containing the issue} 8 | 9 | ### Expected behaviour 10 | 11 | {what should happen} 12 | 13 | ### Actual behaviour 14 | 15 | {what happens} 16 | 17 | ### Stacktrace / Error message 18 | 19 | {paste here} 20 | 21 | ### Other info 22 | 23 | {anything else that might be related/useful} -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### Issue number 2 | 3 | {if exists provide related issue} 4 | 5 | ### Expected behaviour 6 | 7 | {what should have happened} 8 | 9 | ### Actual behaviour 10 | 11 | {what happens} 12 | 13 | ### Description of fix 14 | 15 | {small description of what fixes the issue} 16 | 17 | ### Other info 18 | 19 | {anything else that might be related/useful} -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: goreleaser 2 | 3 | on: 4 | push: 5 | tags: 6 | - '*' 7 | 8 | jobs: 9 | goreleaser: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Login to DockerHub 13 | uses: docker/login-action@v1 14 | with: 15 | username: ${{ secrets.DOCKERHUB_USERNAME }} 16 | password: ${{ secrets.DOCKERHUB_TOKEN }} 17 | - 18 | name: Checkout 19 | uses: actions/checkout@v2 20 | with: 21 | fetch-depth: 0 22 | - 23 | name: Set up Go 24 | uses: actions/setup-go@v2 25 | with: 26 | go-version: 1.15 27 | - 28 | name: Run GoReleaser 29 | uses: goreleaser/goreleaser-action@v2 30 | with: 31 | version: latest 32 | args: release --rm-dist 33 | env: 34 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # IDE specific files 2 | .idea/ 3 | 4 | # Builds 5 | opensips_exporter 6 | dist -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | project_name: opensips_exporter 2 | 3 | builds: 4 | - id: opensips_exporter 5 | goos: 6 | - linux 7 | goarch: 8 | - amd64 9 | - arm64 10 | - 386 11 | 12 | checksum: 13 | name_template: "{{ .ProjectName }}_sha256sums.txt" 14 | algorithm: sha256 15 | 16 | dockers: 17 | - goos: linux 18 | goarch: amd64 19 | binaries: 20 | - opensips_exporter 21 | image_templates: 22 | - "voipgrid/opensips_exporter:{{ .Version }}" 23 | - "voipgrid/opensips_exporter:latest" 24 | skip_push: false 25 | dockerfile: Dockerfile 26 | build_flag_templates: 27 | - "--label=org.label-schema.schema-version=1.0" 28 | - "--label=org.label-schema.version={{.Version}}" 29 | - "--label=org.label-schema.name={{.ProjectName}}" 30 | 31 | nfpms: 32 | - file_name_template: "{{ .ProjectName }}_{{ .Version }}_{{ .Arch }}" 33 | bindir: /usr/bin 34 | vendor: VoIPGRID 35 | homepage: https://www.voipgrid.nl 36 | maintainer: VoIPGRID 37 | license: Apache 2.0 38 | description: Simple server that scrapes OpenSIPS stats and exports them via HTTP for Prometheus consumption 39 | formats: 40 | - deb 41 | 42 | release: 43 | github: 44 | owner: voipgrid 45 | name: opensips_exporter 46 | prerelease: auto 47 | -------------------------------------------------------------------------------- /.promu.yml: -------------------------------------------------------------------------------- 1 | go: 2 | cgo: true 3 | repository: 4 | path: github.com/VoIPGRID/opensips_exporter 5 | build: 6 | binaries: 7 | - name: opensips_exporter 8 | flags: -a -tags netgo 9 | ldflags: | 10 | -X {{repoPath}}/vendor/github.com/prometheus/common/version.Version={{.Version}} 11 | -X {{repoPath}}/vendor/github.com/prometheus/common/version.Revision={{.Revision}} 12 | -X {{repoPath}}/vendor/github.com/prometheus/common/version.Branch={{.Branch}} 13 | -X {{repoPath}}/vendor/github.com/prometheus/common/version.BuildUser={{user}}@{{host}} 14 | -X {{repoPath}}/vendor/github.com/prometheus/common/version.BuildDate={{date "20060102-15:04:05"}} 15 | tarball: 16 | files: 17 | - LICENSE 18 | crossbuild: 19 | platforms: 20 | - linux/amd64 21 | - linux/386 22 | - darwin/amd64 23 | - darwin/386 24 | - netbsd/amd64 25 | - netbsd/386 26 | - linux/arm 27 | - linux/arm64 28 | -------------------------------------------------------------------------------- /CONTRIBUTORS.md: -------------------------------------------------------------------------------- 1 | # Contributors 2 | We would like to thank everyone on this list for contributing to this project! 3 | 4 | Contributors can add themselves to the list by using to following format: 5 | 6 | John Doe {johndoe@nobody.tld} 7 | 8 | Keep the list in alfabetical order please. 9 | 10 | ## Many thanks to 11 | 12 | * Devhouse Spindle {opensource@wearespindle.com} 13 | * Ruben Homs {ruben.homs@wearespindle.com} 14 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM quay.io/prometheus/busybox:latest 2 | MAINTAINER Ruben Homs 3 | 4 | COPY opensips_exporter /bin/opensips_exporter 5 | 6 | ENTRYPOINT ["/bin/opensips_exporter"] 7 | USER nobody 8 | EXPOSE 9434 9 | -------------------------------------------------------------------------------- /Dockerfile.testing: -------------------------------------------------------------------------------- 1 | FROM debian:buster 2 | LABEL maintainer="Ruben Homs " 3 | 4 | USER root 5 | 6 | # Set Environment Variables 7 | ENV DEBIAN_FRONTEND noninteractive 8 | 9 | ARG OPENSIPS_VERSION=3.0 10 | ARG OPENSIPS_BUILD=releases 11 | 12 | #install basic components 13 | RUN apt update -qq && apt install -y gnupg2 ca-certificates 14 | 15 | #add keyserver, repository 16 | RUN apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 049AD65B 17 | RUN echo "deb https://apt.opensips.org buster ${OPENSIPS_VERSION}-${OPENSIPS_BUILD}" >/etc/apt/sources.list.d/opensips.list 18 | 19 | RUN apt update -qq && apt install -y opensips curl net-tools procps 20 | 21 | RUN apt-get -y install opensips-http-modules 22 | 23 | RUN rm -rf /var/lib/apt/lists/* 24 | 25 | EXPOSE 5060/udp 26 | EXPOSE 8888/tcp 27 | 28 | COPY run.sh /run.sh 29 | COPY opensips.cfg /etc/opensips/opensips.cfg 30 | 31 | ENTRYPOINT ["/run.sh"] 32 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Copyright 2015 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 | GO ?= GO15VENDOREXPERIMENT=1 go 15 | GOPATH := $(firstword $(subst :, ,$(shell $(GO) env GOPATH))) 16 | GOARCH := $(shell $(GO) env GOARCH) 17 | GOHOSTARCH := $(shell $(GO) env GOHOSTARCH) 18 | 19 | PROMTOOL ?= $(GOPATH)/bin/promtool 20 | PROMU ?= $(GOPATH)/bin/promu 21 | STATICCHECK ?= $(GOPATH)/bin/staticcheck 22 | pkgs = $(shell $(GO) list ./... | grep -v /vendor/) 23 | 24 | PREFIX ?= $(shell pwd) 25 | BIN_DIR ?= $(shell pwd) 26 | DOCKER_IMAGE_NAME ?= opensips_exporter 27 | DOCKER_IMAGE_TAG ?= $(subst /,-,$(shell git rev-parse --abbrev-ref HEAD)) 28 | MACH ?= $(shell uname -m) 29 | DOCKERFILE ?= Dockerfile 30 | 31 | NAME ?= opensips 32 | OPENSIPS_DOCKER_TAG ?= 2.4.0 33 | 34 | all: format vet staticcheck build 35 | 36 | style: 37 | @echo ">> checking code style" 38 | @! gofmt -d $(shell find . -path ./vendor -prune -o -name '*.go' -print) | grep '^' 39 | 40 | format: 41 | @echo ">> formatting code" 42 | @$(GO) fmt $(pkgs) 43 | 44 | vet: 45 | @echo ">> vetting code" 46 | @$(GO) vet $(pkgs) 47 | 48 | staticcheck: $(STATICCHECK) 49 | @echo ">> running staticcheck" 50 | @$(STATICCHECK) -ignore "$(STATICCHECK_IGNORE)" $(pkgs) 51 | 52 | docker-test-build: 53 | docker build \ 54 | --tag="opensips/opensips:$(OPENSIPS_DOCKER_TAG)" \ 55 | -f Dockerfile.testing . 56 | 57 | docker-test-start: 58 | docker run -p 8888:8888 -d --name $(NAME) opensips/opensips:$(OPENSIPS_DOCKER_TAG) 59 | 60 | $(GOPATH)/bin/staticcheck: 61 | @GOOS= GOARCH= $(GO) get -u honnef.co/go/tools/cmd/staticcheck 62 | 63 | 64 | .PHONY: all style format build test vet tarball docker promtool promu staticcheck 65 | 66 | # Declaring the binaries at their default locations as PHONY targets is a hack 67 | # to ensure the latest version is downloaded on every make execution. 68 | # If this is not desired, copy/symlink these binaries to a different path and 69 | # set the respective environment variables. 70 | .PHONY: $(GOPATH)/bin/promtool $(GOPATH)/bin/promu $(GOPATH)/bin/staticcheck 71 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # opensips_exporter 2 | This exporter exposes OpenSIPS metrics for consumption by Prometheus using the Unix socket 3 | provided by OpenSIPS. It uses the 4 | OpenSIPS [Management Interface](http://www.opensips.org/Documentation/Interface-MI-2-4) to gather 5 | these statistics. It supports two protocols to communicate with the Management Interface `mi_datagram` for OpenSIPS versions up to 2.4.x and for OpenSIPS versions from 3.0 and higher it supports `mi_http` (JSON-RPC) 6 | 7 | Tested and developed against OpenSIPS versions 1.10, 2.4, 3.0, 3.1 though this will probably work with all other versions as well. 8 | 9 | ## Status 10 | 11 | [![Go Report Card](https://goreportcard.com/badge/github.com/VoIPGRID/opensips_exporter)](https://goreportcard.com/report/github.com/VoIPGRID/opensips_exporter) 12 | [![Docker Pulls](https://img.shields.io/docker/pulls/voipgrid/opensips_exporter.svg?maxAge=604800)](https://hub.docker.com/r/voipgrid/opensips_exporter/) 13 | 14 | Active / maintained 15 | 16 | This project is considered stable for use in production environments. 17 | 18 | ## Examples and dashboard 19 | A few examples are provided to give you a clue on how this would be setup on a Prometheus instance: 20 | - [Prometheus scrape config](examples/prometheus.yaml) 21 | - [AlertManager rules](examples/alerts.yaml) 22 | 23 | And because gathering the metrics is only half of the story there's [a dashboard](examples/dashboard.json) you can import into a Grafana installation which works wonders with this exporter. The dashboard is also available on the [Grafana dashboard site](https://grafana.com/dashboards/6935). 24 | 25 | A picture is worth a thousand words. 26 | ![OpenSIPS Dashboard for Grafana](examples/dashboard.png "OpenSIPS dashboard for Grafana") 27 | *Note: the dashboard uses elements from the [node exporter](https://github.com/prometheus/node_exporter/) but can still be used without it.* 28 | 29 | ## Usage 30 | 31 | Make sure `$GOPATH/bin` is in your `$PATH`. 32 | 33 | ```text 34 | Usage of opensips_exporter: 35 | -addr string 36 | Address on which the OpenSIPS exporter listens. (e.g. 127.0.0.1:9434) (default ":9434") 37 | -http_address string 38 | Address to query the management query through HTTP (e.g. http://127.0.0.1:8888/mi/) (default "http://127.0.0.1:8888/mi/") 39 | -path string 40 | The path where metrics will be served. (default "/metrics") 41 | -protocol string (required) 42 | Which protocol to use to get data from the Management Interface (mi_datagram & mi_http currently supported) (default "mi_datagram") 43 | -socket string 44 | Path to the socket file for OpenSIPS.) (default "/var/run/ser-fg/ser.sock") 45 | ``` 46 | 47 | ### OpenSIPS up to version 2.4 48 | Up to OpenSIPS version 2.4 the exporter works with the `mi_datagram` module. You can load it in your OpenSIPS config like so: 49 | ``` 50 | loadmodule "mi_datagram.so" 51 | modparam("mi_datagram", "socket_name", "RUNDIR/ser.sock") 52 | ``` 53 | Then start the exporter with the following params: 54 | ``` 55 | opensips_exporter -protocol mi_datagram -socket RUNDIR/ser.sock 56 | ``` 57 | 58 | ### OpenSIPS version 3.0 and higher 59 | From OpenSIPS version 3.0 and higher the datagram protocol is not supported, instead you can use the `mi_http` module 60 | which uses JSON-RPC to communicate with the Management Interface. For debian you have to install 61 | the `opensips-http-modules` to include the module in your OpenSIPS installation. You can load it in your OpenSIPS config like so: 62 | ``` 63 | loadmodule "httpd.so" 64 | loadmodule "mi_http.so" 65 | modparam("httpd", "ip", "127.0.0.1") 66 | ``` 67 | By default the management interface listens on port 8888 which is the default in the exporter as well. You can start the exporter with the following params: 68 | ``` 69 | opensips_exporter -protocol mi_http 70 | ``` 71 | 72 | ## Exported Metrics 73 | 74 | | Metric | Meaning | Labels | Metric type | 75 | | ------ | ------- | ------ | ------ | 76 | | opensips_up | Whether the opensips exporter could read metrics from the Management Interface socket. (i.e. is OpenSIPS up) | | Gauge | 77 | | opensips_core_bad_URIs_rcvd | Number of URIs that OpenSIPS failed to parse. | | Counter | 78 | | opensips_core_bad_msg_hdr | Number of SIP headers that OpenSIPS failed to parse. | | Counter | 79 | | opensips_core_replies | Number of received replies by OpenSIPS. | kind | Counter | 80 | | opensips_core_replies_total | Total number of received replies by OpenSIPS. | | Counter | 81 | | opensips_core_request | Number of requests by OpenSIPS. | kind | Counter | 82 | | opensips_core_requests_total | Total number of received requests by OpenSIPS. | | Counter | 83 | | opensips_core_unsupported_methods | Number of non-standard methods encountered by OpenSIPS while parsing SIP methods. | | Counter | 84 | | opensips_core_uptime_seconds | Number of seconds elapsed from OpenSIPS starting. | | Counter | 85 | | opensips_dialog_dialogs | Number of dialogs. | status | Gauge | 86 | | opensips_dialog_received | The number of dialog events received from other OpenSIPS instances. | event | Counter | 87 | | opensips_dialog_sent | Number of replicated dialog requests send to other OpenSIPS instances. | event | Counter | 88 | | opensips_load_load | Percentage of UDP children that are awake and processing SIP messages on the specific UDP interface. |ip, port, protocol| Gauge | 89 | | opensips_load_process | The realtime load of the process ID. (**OpenSIPS >= 2.4**) |process| Gauge | 90 | | opensips_load_tcp_load | Percentage of TCP children that are awake and processing SIP messages. | | Gauge | 91 | | opensips_load_core | The realtime load of entire OpenSIPS - this counts all the core processes of OpenSIPS; the additional processes requested by modules are not counted in this load. (**OpenSIPS >= 2.4**) | | Gauge | 92 | | opensips_load_core_1m | The last minute average load of core OpenSIPS (covering only core/SIP processes) (**OpenSIPS >= 2.4**) | | Gauge | 93 | | opensips_load_core_10m | The last 10 minute average load of core OpenSIPS (covering only core/SIP processes) (**OpenSIPS >= 2.4**) | | Gauge | 94 | | opensips_load_all | The realtime load of entire OpenSIPS, counting both core and module processes. (**OpenSIPS >= 2.4**) | | Gauge | 95 | | opensips_load_all_1m | The last minute average load of entire OpenSIPS (covering all processes). (**OpenSIPS >= 2.4**) | | Gauge | 96 | | opensips_load_all_10m | The last 10 minute average load of entire OpenSIPS (covering all processes). (**OpenSIPS >= 2.4**) | | Gauge | 97 | | opensips_load_1m | The last minute average load of the process ID. (**OpenSIPS >= 2.4**) | ip, port, protocol, process | Gauge | 98 | | opensips_load_10m | The last 10 minute average load of the process ID. (**OpenSIPS >= 2.4**) | ip, port, protocol, process | Gauge | 99 | | opensips_load_processes_number | Number of running OpenSIPS processes. (**OpenSIPS >= 3.0**) | | Gauge | 100 | | opensips_net_waiting | Number of bytes waiting to be consumed on an interface that OpenSIPS is listening on. | protocol | Gauge | 101 | | opensips_pkmem_fragments | Currently available number of free fragments in the private memory for OpenSIPS process. | pid | Gauge | 102 | | opensips_pkmem_free_size | Free private memory available for the OpenSIPS process. Computed as total_size - real_used_size. | pid | Gauge | 103 | | opensips_pkmem_max_used_size | The maximum amount of private memory ever used by the OpenSIPS process. | pid | Gauge | 104 | | opensips_pkmem_real_used_size | Amount of private memory requested by the OpenSIPS process, including allocator-specific metadata. | pid | Gauge | 105 | | opensips_pkmem_total_size | Total size of private memory available to the OpenSIPS process. | pid | Gauge | 106 | | opensips_pkmem_used_size | Amount of private memory requested and used by the OpenSIPS process. | pid | Gauge | 107 | | opensips_registrar_default_expire | Value of default_expire parameter. | | Gauge | 108 | | opensips_registrar_max_contacts | Value of max_contacts parameter. | | Gauge | 109 | | opensips_registrar_max_expires | Value of max_expires parameter. | | Gauge | 110 | | opensips_registrar_registrations | Number of registrations. | type | Counter | 111 | | opensips_shmem_fragments | Total number of fragments in the shared memory. | | Gauge | 112 | | opensips_shmem_free_size | Free memory available. Computed as total_size - real_used_size | | Gauge | 113 | | opensips_shmem_max_used_size | Maximum amount of shared memory ever used by OpenSIPS processes. | | Gauge | 114 | | opensips_shmem_real_used_size | Amount of shared memory requested by OpenSIPS processes + malloc overhead | | Gauge | 115 | | opensips_shmem_total_size | Total size of shared memory available to OpenSIPS processes. | | Gauge | 116 | | opensips_shmem_used_size | Amount of shared memory requested and used by OpenSIPS processes. | | Gauge | 117 | | opensips_sl_received_ACKs | The number of received_ACKs. | | Counter | 118 | | opensips_sl_replies | The number of replies. | type | Counter | 119 | | opensips_sl_xxx_replies | The number of replies that don't match any other reply status. | | Counter | 120 | | opensips_sl_failures | The number of failures. | | Counter | 121 | | opensips_sl_sent_err_replies_total | The total number of sent_err_replies. | | Counter | 122 | | opensips_sl_sent_replies_total | The total number of sent_replies. | | Counter | 123 | | opensips_tm_inuse_transactions | Number of transactions existing in memory at current time. | | Counter | 124 | | opensips_tm_local_replies_total | Total number of replies local generated by TM module. | | Counter | 125 | | opensips_tm_received_replies_total | Total number of total replies received by TM module. | | Counter | 126 | | opensips_tm_relayed_replies_total | Total number of replies received and relayed by TM module. | | Counter | 127 | | opensips_tm_transactions_total | Total number of transactions. (TM module)| type | Counter | 128 | | opensips_tmx_transactions_total | Total number of transactions. (TMX module) | type | Counter | 129 | | opensips_tmx_UAS_transactions | Total number of transactions created by received requests. | type | Counter | 130 | | opensips_tmx_UAC_transactions | Total number of transactions created by local generated requests. | | Counter | 131 | | opensips_tmx_inuse_transactions | Number of transactions existing in memory at current time. | | Gauge | 132 | | opensips_tmx_active_transactions | Number of ongoing transactions at current time. | | Gauge | 133 | | opensips_tmx_replies | Total number of replies. | type | Counter | 134 | | opensips_uri_negative_checks | Amount of negative URI checks. | | Counter | 135 | | opensips_uri_positive_checks | Amount of positive URI checks. | | Counter | 136 | | opensips_usrloc_registered_users_total | Total number of AOR existing in the USRLOC memory cache for all domains. | | Counter | 137 | | opensips_usrloc_contacts | Number of contacts existing in the USRLOC memory cache for that domain. | domain | Gauge | 138 | | opensips_usrloc_expires | Total number of expired contacts for that domain. | domain | Gauge | 139 | | opensips_usrloc_users | Number of AOR existing in the USRLOC memory cache for that domain. | domain | Gauge | 140 | 141 | ## Processors 142 | 143 | There are processors available per 'module' of OpenSIPS. The processors take the 144 | statistics from the OpenSIPS socket and turn it into a Prometheus metric. You can 145 | recognise a processor by the naming convention of the metrics. 146 | For example the`opensips_core_replies` metric comes from the core module and its 147 | processor can be found in `./processors/core_processor`. 148 | 149 | You can find out more about the available modules in the OpenSIPS documentation. 150 | 151 | ### Filtering enabled processors 152 | 153 | It is possible to select what processors you want metrics from. You can do this by 154 | appending `collect[]` parameters to your request. If for example you only want to 155 | get metrics about the [core](http://www.opensips.org/Documentation/Interface-CoreStatistics-2-4) 156 | and [usrloc](http://www.opensips.org/html/docs/modules/2.4.x/usrloc.html#exported_statistics) 157 | module you can do this as follows: 158 | 159 | ```bash 160 | curl localhost:9434/metrics?collect[]=core:&collect[]=usrloc: 161 | ``` 162 | 163 | **_Note: You have to append `:` to the module name for this to work._** 164 | 165 | ## Development 166 | 167 | To work on opensips_exporter, get a recent [Go] and 168 | run: 169 | 170 | go get -u github.com/VoIPGRID/opensips_exporter 171 | 172 | The `github.com/VoIPGRID/opensips_exporter/opensips` package contains the 173 | implementation of the interactions with OpenSIPS needed to get statistics from 174 | the mi_datagram Unix socket of a running OpenSIPS. For tests, there is a mock 175 | in the `./internal/mock` package. 176 | 177 | Metrics from different OpenSIPS modules are extracted by processors defined in 178 | the `./processors` package. To extend this exporter with metrics from other modules 179 | create your own processor and implement the `Collector` interface. See the other 180 | processors for inspiration. 181 | 182 | ## Contributing 183 | 184 | See the [CONTRIBUTING.md](.github/CONTRIBUTING.md) file on how to contribute to this project. 185 | 186 | ## Contributors 187 | 188 | See the [CONTRIBUTORS.md](CONTRIBUTORS.md) file for a list of contributors to the project. 189 | 190 | [Go]: https://golang.org/doc/install (Getting Started - The Go Programming Language) 191 | [mod]: https://golang.org/ref/mod (Installation · go mod) 192 | 193 | ## License 194 | 195 | opensips_exporter is made available under the Apache 2.0 license. See the [LICENSE](LICENSE) file for more info. 196 | -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | 2.0.0 -------------------------------------------------------------------------------- /examples/alerts.yaml: -------------------------------------------------------------------------------- 1 | groups: 2 | - name: opensips-alerts 3 | rules: 4 | - alert: OpenSIPSExporterDown 5 | expr: up{job="opensips"} == 0 6 | for: 15m 7 | labels: 8 | severity: warning 9 | annotations: 10 | description: "The OpenSIPS exporter was unable to be scraped for the last 15m." 11 | 12 | - alert: OpenSIPSDown 13 | expr: opensips_up{job="opensips"} == 0 14 | for: 3m 15 | labels: 16 | severity: critical 17 | annotations: 18 | description: "OpenSIPS on {{ $labels.instance }} did not respond to any queries on the Management Interface socket in the last 3m. This could mean the socket has died or the OpenSIPS process has died." 19 | 20 | - alert: OpenSIPSRestarted 21 | expr: opensips_core_uptime_seconds{job="opensips"} < 300 22 | for: 1m 23 | labels: 24 | severity: warning 25 | annotations: 26 | description: "OpenSIPS on {{$labels.instance }} has been up for {{ $value }}s and has therefore likely been restarted." 27 | -------------------------------------------------------------------------------- /examples/dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VoIPGRID/opensips_exporter/0f7e5c139207853d939505ba14672baab871ebfb/examples/dashboard.png -------------------------------------------------------------------------------- /examples/prometheus.yaml: -------------------------------------------------------------------------------- 1 | global: 2 | scrape_interval: 15s # Set the scrape interval to every 15 seconds. Default is every 1 minute. 3 | evaluation_interval: 15s # Evaluate rules every 15 seconds. The default is every 1 minute. 4 | 5 | scrape_configs: 6 | - job_name: 'opensips' 7 | 8 | targets: 9 | - 'opensips.mydomain.com:9434' 10 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/VoIPGRID/opensips_exporter 2 | 3 | go 1.15 4 | 5 | require ( 6 | github.com/KeisukeYamashita/go-jsonrpc v1.0.1 7 | github.com/beorn7/perks v0.0.0-20160804104726-4c0e84591b9a // indirect 8 | github.com/matttproud/golang_protobuf_extensions v1.0.0 // indirect 9 | github.com/onsi/gomega v1.10.3 // indirect 10 | github.com/prometheus/client_golang v0.8.0 11 | github.com/prometheus/client_model v0.0.0-20171117100541-99fa1f4be8e5 // indirect 12 | github.com/prometheus/common v0.0.0-20180110214958-89604d197083 // indirect 13 | github.com/prometheus/procfs v0.0.0-20180212145926-282c8707aa21 // indirect 14 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f 15 | ) 16 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/KeisukeYamashita/go-jsonrpc v1.0.1 h1:mkg8th2I7C1y3tnAsxroQbmQcXEZ22AkQ5pazRd/KDM= 2 | github.com/KeisukeYamashita/go-jsonrpc v1.0.1/go.mod h1:NyiYd1oDwaSsIflCju5dKRvLdG5rZ/I4E07ygRYYmgc= 3 | github.com/VoIPGRID/opensips_exporter v1.1.1 h1:1HH5611ac+vUAupBakbrJps5TAxGbt17/KojQh5Diwk= 4 | github.com/VoIPGRID/opensips_exporter v2.0.0+incompatible h1:mqn1Jss/afNYxA8cF2gTgyccdJDmS4QPPl4WLFtCLEY= 5 | github.com/VoIPGRID/opensips_exporter v2.0.0+incompatible/go.mod h1:3TA23i066+PGhXnewO6SUuQUhmtyipT+Ahzm2bIPjtc= 6 | github.com/beorn7/perks v0.0.0-20160804104726-4c0e84591b9a h1:BtpsbiV638WQZwhA98cEZw2BsbnQJrbd0BI7tsy0W1c= 7 | github.com/beorn7/perks v0.0.0-20160804104726-4c0e84591b9a/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= 8 | github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= 9 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 10 | github.com/golang/protobuf v1.0.0 h1:lsek0oXi8iFE9L+EXARyHIjU5rlWIhhTkjDz3vHhWWQ= 11 | github.com/golang/protobuf v1.0.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 12 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 13 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 14 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 15 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 16 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 17 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 18 | github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= 19 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 20 | github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM= 21 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 22 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 23 | github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= 24 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 25 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= 26 | github.com/matttproud/golang_protobuf_extensions v1.0.0 h1:YNOwxxSJzSUARoD9KRZLzM9Y858MNGCOACTvCW9TSAc= 27 | github.com/matttproud/golang_protobuf_extensions v1.0.0/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= 28 | github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= 29 | github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= 30 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 31 | github.com/onsi/ginkgo v1.12.1 h1:mFwc4LvZ0xpSvDZ3E+k8Yte0hLOMxXUlP+yXtJqkYfQ= 32 | github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= 33 | github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= 34 | github.com/onsi/gomega v1.10.3 h1:gph6h/qe9GSUw1NhH1gp+qb+h8rXD8Cy60Z32Qw3ELA= 35 | github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc= 36 | github.com/prometheus/client_golang v0.8.0 h1:1921Yw9Gc3iSc4VQh3PIoOqgPCZS7G/4xQNVUp8Mda8= 37 | github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= 38 | github.com/prometheus/client_model v0.0.0-20171117100541-99fa1f4be8e5 h1:cLL6NowurKLMfCeQy4tIeph12XNQWgANCNvdyrOYKV4= 39 | github.com/prometheus/client_model v0.0.0-20171117100541-99fa1f4be8e5/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= 40 | github.com/prometheus/common v0.0.0-20180110214958-89604d197083 h1:BVsJT8+ZbyuL3hypz/HmEiM8h2P6hBQGig4el9/MdjA= 41 | github.com/prometheus/common v0.0.0-20180110214958-89604d197083/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= 42 | github.com/prometheus/procfs v0.0.0-20180212145926-282c8707aa21 h1:t1goPR/clmILdOPOVkZytGpQMMTHZ0IkF4+AaZZfMJY= 43 | github.com/prometheus/procfs v0.0.0-20180212145926-282c8707aa21/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= 44 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 45 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 46 | golang.org/x/net v0.0.0-20180320002117-6078986fec03 h1:7AqHAZ7CvA95ugmTHvadCc9K2ltE9f5dYKpce5J1kn8= 47 | golang.org/x/net v0.0.0-20180320002117-6078986fec03/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 48 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 49 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 50 | golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0 h1:wBouT66WTYFXdxfVdz9sVWARVd/2vfGcmI45D2gj45M= 51 | golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 52 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA= 53 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 54 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 55 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 56 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 57 | golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 58 | golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 59 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f h1:+Nyd8tzPX9R7BWHguqsrbFdRx3WQ/1ib8I44HXV5yTA= 60 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 61 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 62 | golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= 63 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 64 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 65 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= 66 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 67 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 68 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 69 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 70 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 71 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 72 | google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM= 73 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 74 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 75 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 76 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= 77 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= 78 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= 79 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 80 | gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= 81 | gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 82 | -------------------------------------------------------------------------------- /internal/mock/mock.go: -------------------------------------------------------------------------------- 1 | package mock 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "net" 7 | "os" 8 | "path" 9 | "time" 10 | 11 | "golang.org/x/sync/errgroup" 12 | ) 13 | 14 | // Mock is a fake OpenSIPS mi_datagram socket 15 | type Mock struct { 16 | response []byte 17 | sleep time.Duration 18 | 19 | dir string 20 | addr *net.UnixAddr 21 | l *net.UnixConn 22 | g errgroup.Group 23 | } 24 | 25 | // New creates a new Mock with given response. Before responding the Mock will 26 | // sleep for a moment. 27 | func New(response []byte, sleep time.Duration) (m *Mock, err error) { 28 | m = new(Mock) 29 | m.response = response 30 | m.sleep = sleep 31 | m.dir, err = ioutil.TempDir(os.TempDir(), "mock-opensips-") 32 | if err != nil { 33 | return 34 | } 35 | m.addr, err = net.ResolveUnixAddr("unixgram", path.Join(m.dir, "mock.sock")) 36 | if err != nil { 37 | return 38 | } 39 | m.l, err = net.ListenUnixgram("unixgram", m.addr) 40 | if err != nil { 41 | return 42 | } 43 | return 44 | } 45 | 46 | // Socket returns the Mock's socket address 47 | func (m *Mock) Socket() string { 48 | return m.addr.Name 49 | } 50 | 51 | // Run handles a given number of requests within the deadline. 52 | func (m *Mock) Run(count int, deadline time.Time) error { 53 | err := m.l.SetReadDeadline(deadline) 54 | if err != nil { 55 | return err 56 | } 57 | for i := 0; i < count; i++ { 58 | buf := make([]byte, 65535) 59 | _, raddr, err := m.l.ReadFromUnix(buf) 60 | if err != nil { 61 | return err 62 | } 63 | if raddr == nil { 64 | return fmt.Errorf("mock.run: got nil raddr") 65 | } 66 | m.g.Go(func() error { 67 | time.Sleep(m.sleep) 68 | // OpenSIPS responds through an anonymous socket too 69 | c, err := net.DialUnix("unixgram", nil, raddr) 70 | if err != nil { 71 | return err 72 | } 73 | _, err = c.Write(m.response) 74 | if err != nil { 75 | return err 76 | } 77 | return c.Close() 78 | }) 79 | } 80 | return m.g.Wait() 81 | } 82 | 83 | // Close removes the resources created for Mock. 84 | func (m *Mock) Close() error { 85 | err := m.l.Close() 86 | if err != nil { 87 | return err 88 | } 89 | err = os.Remove(m.addr.Name) 90 | if err != nil { 91 | return err 92 | } 93 | return os.Remove(m.dir) 94 | } 95 | -------------------------------------------------------------------------------- /opensips.cfg: -------------------------------------------------------------------------------- 1 | # 2 | # OpenSIPS residential configuration script 3 | # by OpenSIPS Solutions 4 | # 5 | # This script was generated via "make menuconfig", from 6 | # the "Residential" scenario. 7 | # You can enable / disable more features / functionalities by 8 | # re-generating the scenario with different options.# 9 | # 10 | # Please refer to the Core CookBook at: 11 | # https://opensips.org/Resources/DocsCookbooks 12 | # for a explanation of possible statements, functions and parameters. 13 | # 14 | 15 | 16 | ####### Global Parameters ######### 17 | 18 | log_level=3 19 | log_stderror=yes 20 | log_facility=LOG_LOCAL0 21 | 22 | children=4 23 | 24 | /* uncomment the following lines to enable debugging */ 25 | #debug_mode=yes 26 | 27 | /* uncomment the next line to enable the auto temporary blacklisting of 28 | not available destinations (default disabled) */ 29 | #disable_dns_blacklist=no 30 | 31 | /* uncomment the next line to enable IPv6 lookup after IPv4 dns 32 | lookup failures (default disabled) */ 33 | #dns_try_ipv6=yes 34 | 35 | /* comment the next line to enable the auto discovery of local aliases 36 | based on reverse DNS on IPs */ 37 | auto_aliases=no 38 | 39 | 40 | listen=udp:127.0.0.1:5060 # CUSTOMIZE ME 41 | 42 | 43 | 44 | ####### Modules Section ######## 45 | 46 | #set module path 47 | mpath="/usr/lib/x86_64-linux-gnu/opensips/modules/" 48 | 49 | #### SIGNALING module 50 | loadmodule "signaling.so" 51 | 52 | #### StateLess module 53 | loadmodule "sl.so" 54 | 55 | #### Transaction Module 56 | loadmodule "tm.so" 57 | modparam("tm", "fr_timeout", 5) 58 | modparam("tm", "fr_inv_timeout", 30) 59 | modparam("tm", "restart_fr_on_each_reply", 0) 60 | modparam("tm", "onreply_avp_mode", 1) 61 | 62 | #### Record Route Module 63 | loadmodule "rr.so" 64 | /* do not append from tag to the RR (no need for this script) */ 65 | modparam("rr", "append_fromtag", 0) 66 | 67 | #### MAX ForWarD module 68 | loadmodule "maxfwd.so" 69 | 70 | #### SIP MSG OPerationS module 71 | loadmodule "sipmsgops.so" 72 | 73 | #### FIFO Management Interface 74 | loadmodule "mi_fifo.so" 75 | modparam("mi_fifo", "fifo_name", "/tmp/opensips_fifo") 76 | modparam("mi_fifo", "fifo_mode", 0666) 77 | 78 | #### USeR LOCation module 79 | loadmodule "usrloc.so" 80 | modparam("usrloc", "nat_bflag", "NAT") 81 | modparam("usrloc", "working_mode_preset", "single-instance-no-db") 82 | 83 | #### REGISTRAR module 84 | loadmodule "registrar.so" 85 | modparam("registrar", "tcp_persistent_flag", "TCP_PERSISTENT") 86 | /* uncomment the next line not to allow more than 10 contacts per AOR */ 87 | #modparam("registrar", "max_contacts", 10) 88 | 89 | #### ACCounting module 90 | loadmodule "acc.so" 91 | /* what special events should be accounted ? */ 92 | modparam("acc", "early_media", 0) 93 | modparam("acc", "report_cancels", 0) 94 | /* by default we do not adjust the direct of the sequential requests. 95 | if you enable this parameter, be sure to enable "append_fromtag" 96 | in "rr" module */ 97 | modparam("acc", "detect_direction", 0) 98 | 99 | loadmodule "proto_udp.so" 100 | 101 | #### mi_http module 102 | loadmodule "httpd.so" 103 | loadmodule "mi_http.so" 104 | modparam("httpd", "ip", "127.0.0.1") 105 | 106 | ####### Routing Logic ######## 107 | 108 | # main request routing logic 109 | 110 | route{ 111 | 112 | if (!mf_process_maxfwd_header(10)) { 113 | send_reply(483,"Too Many Hops"); 114 | exit; 115 | } 116 | 117 | if (has_totag()) { 118 | 119 | # handle hop-by-hop ACK (no routing required) 120 | if ( is_method("ACK") && t_check_trans() ) { 121 | t_relay(); 122 | exit; 123 | } 124 | 125 | # sequential request within a dialog should 126 | # take the path determined by record-routing 127 | if ( !loose_route() ) { 128 | # we do record-routing for all our traffic, so we should not 129 | # receive any sequential requests without Route hdr. 130 | send_reply(404,"Not here"); 131 | exit; 132 | } 133 | 134 | if (is_method("BYE")) { 135 | # do accounting even if the transaction fails 136 | do_accounting("log","failed"); 137 | } 138 | 139 | # route it out to whatever destination was set by loose_route() 140 | # in $du (destination URI). 141 | route(relay); 142 | exit; 143 | } 144 | 145 | # CANCEL processing 146 | if (is_method("CANCEL")) { 147 | if (t_check_trans()) 148 | t_relay(); 149 | exit; 150 | } 151 | 152 | # absorb retransmissions, but do not create transaction 153 | t_check_trans(); 154 | 155 | if ( !(is_method("REGISTER") ) ) { 156 | 157 | if (is_myself("$fd")) { 158 | 159 | } else { 160 | # if caller is not local, then called number must be local 161 | 162 | if (!is_myself("$rd")) { 163 | send_reply(403,"Relay Forbidden"); 164 | exit; 165 | } 166 | } 167 | 168 | } 169 | 170 | # preloaded route checking 171 | if (loose_route()) { 172 | xlog("L_ERR", 173 | "Attempt to route with preloaded Route's [$fu/$tu/$ru/$ci]"); 174 | if (!is_method("ACK")) 175 | send_reply(403,"Preload Route denied"); 176 | exit; 177 | } 178 | 179 | # record routing 180 | if (!is_method("REGISTER|MESSAGE")) 181 | record_route(); 182 | 183 | # account only INVITEs 184 | if (is_method("INVITE")) { 185 | 186 | do_accounting("log"); 187 | } 188 | 189 | 190 | if (!is_myself("$rd")) { 191 | append_hf("P-hint: outbound\r\n"); 192 | 193 | route(relay); 194 | } 195 | 196 | # requests for my domain 197 | 198 | if (is_method("PUBLISH|SUBSCRIBE")) { 199 | send_reply(503, "Service Unavailable"); 200 | exit; 201 | } 202 | 203 | if (is_method("REGISTER")) { 204 | 205 | if (!save("location")) 206 | sl_reply_error(); 207 | 208 | exit; 209 | } 210 | 211 | if ($rU==NULL) { 212 | # request with no Username in RURI 213 | send_reply(484,"Address Incomplete"); 214 | exit; 215 | } 216 | 217 | # do lookup with method filtering 218 | if (!lookup("location","m")) { 219 | t_reply(404, "Not Found"); 220 | exit; 221 | } 222 | 223 | # when routing via usrloc, log the missed calls also 224 | do_accounting("log","missed"); 225 | route(relay); 226 | } 227 | 228 | 229 | route[relay] { 230 | # for INVITEs enable some additional helper routes 231 | if (is_method("INVITE")) { 232 | t_on_branch("per_branch_ops"); 233 | t_on_reply("handle_nat"); 234 | t_on_failure("missed_call"); 235 | } 236 | 237 | if (!t_relay()) { 238 | send_reply(500,"Internal Error"); 239 | } 240 | exit; 241 | } 242 | 243 | 244 | 245 | 246 | branch_route[per_branch_ops] { 247 | xlog("new branch at $ru\n"); 248 | } 249 | 250 | 251 | onreply_route[handle_nat] { 252 | xlog("incoming reply\n"); 253 | } 254 | 255 | 256 | failure_route[missed_call] { 257 | if (t_was_cancelled()) { 258 | exit; 259 | } 260 | 261 | # uncomment the following lines if you want to block client 262 | # redirect based on 3xx replies. 263 | ##if (t_check_status("3[0-9][0-9]")) { 264 | ##t_reply(404,"Not found"); 265 | ## exit; 266 | ##} 267 | 268 | 269 | } 270 | -------------------------------------------------------------------------------- /opensips/jsonrpc/opensips_jsonrpc.go: -------------------------------------------------------------------------------- 1 | package jsonrpc 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | "strings" 7 | 8 | "github.com/KeisukeYamashita/go-jsonrpc" 9 | "github.com/VoIPGRID/opensips_exporter/opensips" 10 | ) 11 | 12 | // JSONRPC holds all the information necessary for handling connections to 13 | // the OpenSIPS Management Interface (targeting version >= 3.0). 14 | type JSONRPC struct { 15 | url string 16 | } 17 | 18 | // New creates a new JSONRPC instance. Pass it the running OpenSIPS' 19 | // HTTP JSON RPC endpoint to connect to. 20 | func New(url string) *JSONRPC { 21 | return &JSONRPC{ 22 | url: url, 23 | } 24 | } 25 | 26 | // GetStatistics calls the JSON-RPC endpoint and returns the 27 | // statistics OpenSIPS sends back. The targets can be "all", "group:" or 28 | // "name" (e.g. "shmem:" or "rcv_requests"). 29 | func (o *JSONRPC) GetStatistics(targets ...string) (map[string]opensips.Statistic, error) { 30 | rpcClient := jsonrpc.NewRPCClient(o.url) 31 | 32 | // request {"jsonrpc":"2.0","method":"get_statistics","params":[["core:","tm:"]],"id":1} 33 | resp, err := rpcClient.Call("get_statistics", targets) 34 | if err != nil { 35 | return nil, fmt.Errorf("error while getting statistics from JSON-RPC endpoint: %w", err) 36 | } 37 | 38 | statistics, err := parseStatistics(resp.Result.(map[string]interface{})) 39 | if err != nil { 40 | return nil, err 41 | } 42 | 43 | return statistics, nil 44 | } 45 | 46 | func parseStatistics(response map[string]interface{}) (map[string]opensips.Statistic, error) { 47 | var res = map[string]opensips.Statistic{} 48 | for key, value := range response { 49 | asString := fmt.Sprintf("%s = %s", key, value) 50 | stat, err := parseStatistic(asString) 51 | if err != nil { 52 | return res, fmt.Errorf("error while parsing stat: %w", err) 53 | } 54 | res[stat.Name] = stat 55 | } 56 | return res, nil 57 | } 58 | 59 | func parseStatistic(metric string) (opensips.Statistic, error) { 60 | var name, module, valueString string 61 | // Check for OpenSIPS >= 2 metric format 62 | // i.e.shmem:total_size:: 2147483648 63 | if metric == "" { 64 | // There's an empty line in the output since OpenSIPS 2.4.5 65 | // ignore and continue 66 | return opensips.Statistic{}, nil 67 | } 68 | if strings.Contains(metric, "=") { 69 | // OpenSIPS < 2 metric format 70 | // i.e. shmem:total_size = 2147483648 71 | metricSplit := strings.Split(metric, ":") 72 | module = metricSplit[0] 73 | name = strings.Split(strings.Join(metricSplit[1:], ":"), " ")[0] 74 | i := strings.LastIndex(metric, " ") 75 | valueString = metric[i+1:] 76 | } else { 77 | return opensips.Statistic{}, fmt.Errorf("unknown metric format encountered for: %s", metric) 78 | } 79 | 80 | value, err := strconv.ParseFloat(valueString, 64) 81 | if err != nil { 82 | return opensips.Statistic{}, err 83 | } 84 | 85 | return opensips.Statistic{ 86 | Module: module, 87 | Name: name, 88 | Value: value, 89 | }, nil 90 | } 91 | -------------------------------------------------------------------------------- /opensips/opensips.go: -------------------------------------------------------------------------------- 1 | package opensips 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "fmt" 7 | "io/ioutil" 8 | "net" 9 | "os" 10 | "path" 11 | "strconv" 12 | "strings" 13 | "sync/atomic" 14 | "time" 15 | ) 16 | 17 | const firstLineOK = "200 OK\n" 18 | 19 | // OpenSIPS holds all the information necessary for handling connections to 20 | // the OpenSIPS Management Interface (targeting version 1.10). 21 | type OpenSIPS struct { 22 | socket string 23 | tmpdir string 24 | 25 | count int64 26 | } 27 | 28 | // Statistic holds the module, name and value of a statistic 29 | // as returned by OpenSIPS. 30 | type Statistic struct { 31 | Module, Name string 32 | Value float64 33 | } 34 | 35 | // New creates a new OpenSIPS instance. Pass it the running OpenSIPS' 36 | // mi_datagram Unix socket string to connect to. The socket should be 37 | // expressed as a full path to the socket, and the current user should have 38 | // permissions to read from and write to this socket, in addition to write 39 | // access to the folder it's located in (for creating the return socket). 40 | func New(socket string) (*OpenSIPS, error) { 41 | tmpdir, err := ioutil.TempDir(path.Dir(socket), "opensips_exporter") 42 | if err != nil { 43 | return nil, err 44 | } 45 | return &OpenSIPS{ 46 | socket: socket, 47 | tmpdir: tmpdir, 48 | }, nil 49 | } 50 | 51 | // GetStatistics calls the get_statistics management function and returns the 52 | // statistics OpenSIPS sends back. The targets can be "all", "group:" or 53 | // "name" (e.g. "shmem:" or "rcv_requests"). 54 | func (o *OpenSIPS) GetStatistics(targets ...string) (map[string]Statistic, error) { 55 | msg := []byte(":get_statistics:\n") 56 | for _, target := range targets { 57 | msg = append(msg, []byte(target)...) 58 | msg = append(msg, '\n') 59 | } 60 | resp, err := o.roundtrip(msg) 61 | if err != nil { 62 | return nil, err 63 | } 64 | buf := bytes.NewBuffer(resp) 65 | line, err := buf.ReadString('\n') 66 | if err != nil { 67 | return nil, err 68 | } 69 | if line != firstLineOK { 70 | return nil, fmt.Errorf("expected %q, got %q", firstLineOK, line) 71 | } 72 | var rv []string 73 | for err == nil { 74 | rv = append(rv, line) 75 | line, err = buf.ReadString('\n') 76 | } 77 | 78 | statistics, err := parseStatistics(rv[1:]) 79 | if err != nil { 80 | return nil, fmt.Errorf("error while parsing statistics: %v", err) 81 | } 82 | 83 | return statistics, nil 84 | } 85 | 86 | func parseStatistics(statistics []string) (map[string]Statistic, error) { 87 | var res = map[string]Statistic{} 88 | for _, s := range statistics { 89 | s = strings.TrimSuffix(s, "\n") 90 | stat, err := parseStatistic(s) 91 | if err != nil { 92 | return res, err 93 | } 94 | res[stat.Name] = stat 95 | } 96 | return res, nil 97 | } 98 | 99 | func parseStatistic(metric string) (Statistic, error) { 100 | var name, module, valueString string 101 | // Check for OpenSIPS >= 2 metric format 102 | // i.e.shmem:total_size:: 2147483648 103 | if metric == "" { 104 | // There's an empty line in the output since OpenSIPS 2.4.5 105 | // ignore and continue 106 | return Statistic{}, nil 107 | } 108 | if strings.Contains(metric, "::") { 109 | valueIndex := strings.LastIndex(metric, "::") 110 | valueString = strings.TrimSpace(metric[valueIndex+2:]) 111 | metricSplit := strings.Split(metric[:valueIndex], ":") 112 | module = metricSplit[0] 113 | name = strings.Split(strings.Join(metricSplit[1:], ":"), " ")[0] 114 | } else if strings.Contains(metric, "=") { 115 | // OpenSIPS < 2 metric format 116 | // i.e. shmem:total_size = 2147483648 117 | metricSplit := strings.Split(metric, ":") 118 | module = metricSplit[0] 119 | name = strings.Split(strings.Join(metricSplit[1:], ":"), " ")[0] 120 | i := strings.LastIndex(metric, " ") 121 | valueString = metric[i+1:] 122 | } else { 123 | return Statistic{}, errors.New("Error: unknown metric format encountered for: " + metric) 124 | } 125 | 126 | value, err := strconv.ParseFloat(valueString, 64) 127 | if err != nil { 128 | return Statistic{}, err 129 | } 130 | 131 | return Statistic{ 132 | Module: module, 133 | Name: name, 134 | Value: value, 135 | }, nil 136 | } 137 | 138 | func (o *OpenSIPS) roundtrip(request []byte) ([]byte, error) { 139 | raddr, err := net.ResolveUnixAddr("unixgram", o.socket) 140 | if err != nil { 141 | return nil, err 142 | } 143 | count := atomic.AddInt64(&o.count, 1) 144 | laddr, err := net.ResolveUnixAddr("unixgram", path.Join(o.tmpdir, fmt.Sprintf("%d.sock", count))) 145 | if err != nil { 146 | return nil, err 147 | } 148 | c, err := net.ListenUnixgram("unixgram", laddr) 149 | if err != nil { 150 | return nil, err 151 | } 152 | defer os.Remove(laddr.Name) 153 | defer c.Close() 154 | _, err = c.WriteToUnix(request, raddr) 155 | if err != nil { 156 | return nil, err 157 | } 158 | err = c.SetReadDeadline(time.Now().Add(time.Second)) 159 | if err != nil { 160 | return nil, err 161 | } 162 | buf := make([]byte, 65535) 163 | n, err := c.Read(buf) 164 | if err != nil { 165 | return nil, err 166 | } 167 | return buf[:n], err 168 | } 169 | 170 | // Close tears down all resources created for this OpenSIPS instance. 171 | func (o *OpenSIPS) Close() error { 172 | err := os.Remove(o.tmpdir) 173 | return err 174 | } 175 | -------------------------------------------------------------------------------- /opensips/opensips_test.go: -------------------------------------------------------------------------------- 1 | package opensips_test 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | "time" 7 | 8 | "github.com/VoIPGRID/opensips_exporter/internal/mock" 9 | "github.com/VoIPGRID/opensips_exporter/opensips" 10 | "golang.org/x/sync/errgroup" 11 | ) 12 | 13 | func TestGetStatistics(t *testing.T) { 14 | const fakeStatistic = "core:fake_statistic = 42\n" 15 | var fakeStatisticObject = opensips.Statistic{ 16 | Name: "fake_statistic", 17 | Module: "core", 18 | Value: 42, 19 | } 20 | m, err := mock.New([]byte("200 OK\n"+fakeStatistic), 0) 21 | if err != nil { 22 | t.Fatal(err) 23 | } 24 | o, err := opensips.New(m.Socket()) 25 | if err != nil { 26 | t.Fatal(err) 27 | } 28 | var g errgroup.Group 29 | g.Go(func() error { 30 | statistics, err := o.GetStatistics("fake_statistic") 31 | if err != nil { 32 | return err 33 | } 34 | if len(statistics) != 1 { 35 | return fmt.Errorf("expected 1 line from GetStatistics, got %d", len(statistics)) 36 | } 37 | if statistics["fake_statistic"] != fakeStatisticObject { 38 | return fmt.Errorf("expected %v, got %v", fakeStatistic, statistics["fake_statistic"]) 39 | } 40 | return nil 41 | }) 42 | if err := m.Run(1, time.Now().Add(10*time.Second)); err != nil { 43 | t.Fatal(err) 44 | } 45 | if err := g.Wait(); err != nil { 46 | t.Fatal(err) 47 | } 48 | if err := o.Close(); err != nil { 49 | t.Fatal(err) 50 | } 51 | if err := m.Close(); err != nil { 52 | t.Fatal(err) 53 | } 54 | } 55 | 56 | func TestConcurrentGetStatistics(t *testing.T) { 57 | const fakeStatistic = "core:fake_statistic = 42\n" 58 | var fakeStatisticObject = opensips.Statistic{ 59 | Name: "fake_statistic", 60 | Module: "core", 61 | Value: 42, 62 | } 63 | m, err := mock.New([]byte("200 OK\n"+fakeStatistic), 100*time.Millisecond) 64 | if err != nil { 65 | t.Fatal(err) 66 | } 67 | o, err := opensips.New(m.Socket()) 68 | if err != nil { 69 | t.Fatal(err) 70 | } 71 | var g errgroup.Group 72 | for i := 0; i < 10; i++ { 73 | g.Go(func() error { 74 | statistics, err := o.GetStatistics("fake_statistic") 75 | if err != nil { 76 | return err 77 | } 78 | if len(statistics) != 1 { 79 | return fmt.Errorf("expected 1 line from GetStatistics, got %d", len(statistics)) 80 | } 81 | if statistics["fake_statistic"] != fakeStatisticObject { 82 | return fmt.Errorf("expected %v, got %v", fakeStatisticObject, statistics["fake_statistic"]) 83 | } 84 | return nil 85 | }) 86 | } 87 | if err := m.Run(10, time.Now().Add(10*time.Second)); err != nil { 88 | t.Fatal(err) 89 | } 90 | if err := g.Wait(); err != nil { 91 | t.Fatal(err) 92 | } 93 | if err := o.Close(); err != nil { 94 | t.Fatal(err) 95 | } 96 | if err := m.Close(); err != nil { 97 | t.Fatal(err) 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /opensips_exporter.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "log" 6 | "net/http" 7 | "os" 8 | "strings" 9 | 10 | "fmt" 11 | 12 | "github.com/VoIPGRID/opensips_exporter/opensips" 13 | "github.com/VoIPGRID/opensips_exporter/opensips/jsonrpc" 14 | "github.com/VoIPGRID/opensips_exporter/processors" 15 | "github.com/prometheus/client_golang/prometheus" 16 | "github.com/prometheus/client_golang/prometheus/promhttp" 17 | ) 18 | 19 | var o *opensips.OpenSIPS 20 | var j *jsonrpc.JSONRPC 21 | var collectAll = []string{"core:", "shmem:", "net:", "uri:", "tm:", "sl:", "usrloc:", "dialog:", "registrar:", "pkmem:", "load:", "tmx:"} 22 | 23 | const envPrefix = "OPENSIPS_EXPORTER" 24 | 25 | func handler(w http.ResponseWriter, r *http.Request) { 26 | collect := r.URL.Query()["collect[]"] 27 | collectors := make(map[prometheus.Collector]bool) 28 | 29 | if len(collect) == 0 { 30 | // Collect everything if nothing is specified 31 | collect = collectAll 32 | } 33 | var scrapeProcessor prometheus.Collector 34 | 35 | var statistics map[string]opensips.Statistic 36 | var err error 37 | switch *protocol { 38 | case "mi_datagram": 39 | o, err = opensips.New(*socketPath) 40 | if err != nil { 41 | log.Fatalf("Could not create datagram socket: %v", err) 42 | } 43 | statistics, err = o.GetStatistics(collect...) 44 | case "mi_http": 45 | j = jsonrpc.New(*httpEndpoint) 46 | statistics, err = j.GetStatistics(collect...) 47 | } 48 | 49 | if err != nil { 50 | scrapeProcessor = processors.NewScrapeProcessor(0) 51 | log.Printf("Error encountered while reading statistics from opensips socket: %v", err) 52 | } else { 53 | scrapeProcessor = processors.NewScrapeProcessor(1) 54 | } 55 | collectors[scrapeProcessor] = true 56 | 57 | var selectedProcessors = map[string]bool{} 58 | for _, processor := range collect { 59 | if p, ok := processors.OpensipsProcessors[processor]; ok { 60 | processorFunc := fmt.Sprintf("%p", p) 61 | if _, ok := selectedProcessors[processorFunc]; !ok { 62 | selectedProcessors[processorFunc] = true 63 | collectors[p(statistics)] = true 64 | } 65 | } 66 | } 67 | 68 | registry := prometheus.NewRegistry() 69 | for collector := range collectors { 70 | err := registry.Register(collector) 71 | if err != nil { 72 | log.Printf("Problems registering the %T processor (could be due to no metrics found for this processor). Error: %v\n", collector, err) 73 | } 74 | } 75 | 76 | gatherers := prometheus.Gatherers{ 77 | prometheus.DefaultGatherer, 78 | registry, 79 | } 80 | 81 | // Delegate http serving to Prometheus client library, which will call collector.Collect. 82 | h := promhttp.HandlerFor(gatherers, 83 | promhttp.HandlerOpts{ 84 | ErrorHandling: promhttp.ContinueOnError, 85 | }) 86 | h.ServeHTTP(w, r) 87 | } 88 | 89 | // strflag is like flag.String, with value overridden by an environment 90 | // variable (when present). e.g. with socket path, the env var used as default 91 | // is OPENSIPS_EXPORTER_SOCKET_PATH, if present in env. 92 | func strflag(name string, value string, usage string) *string { 93 | if v, ok := os.LookupEnv(envPrefix + strings.ToUpper(name)); ok { 94 | return flag.String(name, v, usage) 95 | } 96 | return flag.String(name, value, usage) 97 | } 98 | 99 | var ( 100 | socketPath *string 101 | metricsPath *string 102 | addr *string 103 | protocol *string 104 | httpEndpoint *string 105 | ) 106 | 107 | func main() { 108 | addr = strflag("addr", ":9434", "Address on which the OpenSIPS exporter listens. (e.g. 127.0.0.1:9434)") 109 | metricsPath = strflag("path", "/metrics", "The path where metrics will be served.") 110 | socketPath = strflag("socket", "/var/run/ser-fg/ser.sock", "Path to the socket file for OpenSIPS.)") 111 | httpEndpoint = strflag("http_address", "http://127.0.0.1:8888/mi/", "Address to query the Management Interface through HTTP with (e.g. http://127.0.0.1:8888/mi/)") 112 | protocol = strflag("protocol", "", "Which protocol to use to get data from the Management Interface (mi_datagram & mi_http currently supported)") 113 | flag.Parse() 114 | 115 | switch *protocol { 116 | case "mi_datagram": 117 | if *socketPath == "" { 118 | log.Fatalf("The -protocol flag is set to mi_datagram but the -socket param is not set. Exiting.") 119 | } 120 | case "mi_http": 121 | if *httpEndpoint == "" { 122 | log.Fatalf("The -protocol is set to mi_http but the -http_address flag is not set. Exiting.") 123 | } 124 | default: 125 | log.Fatalf("Please set the -protocol flag to define which protocol the exporter should use to query for metrics. (mi_datagram or mi_http)") 126 | } 127 | 128 | http.HandleFunc(*metricsPath, handler) 129 | http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 130 | w.Write([]byte(` 131 | OpenSIPS Exporter 132 | 133 |

OpenSIPS Exporter

134 |

Metrics

135 | 136 | `)) 137 | }) 138 | log.Printf("Started OpenSIPS exporter, listening on %v", *addr) 139 | log.Fatal(http.ListenAndServe(*addr, nil)) 140 | } 141 | -------------------------------------------------------------------------------- /processors/core_processor.go: -------------------------------------------------------------------------------- 1 | package processors 2 | 3 | import ( 4 | "github.com/VoIPGRID/opensips_exporter/opensips" 5 | "github.com/prometheus/client_golang/prometheus" 6 | ) 7 | 8 | // coreProcessor provides basic metrics, 9 | // doc: http://www.opensips.org/Documentation/Interface-CoreStatistics-1-11#toc1 10 | // src: https://github.com/OpenSIPS/opensips/blob/1.11/core_stats.h 11 | type coreProcessor struct { 12 | statistics map[string]opensips.Statistic 13 | } 14 | 15 | var coreMetrics = map[string]metric{ 16 | "rcv_requests": newMetric("core", "requests_total", "Total number of received requests by OpenSIPS.", []string{}, prometheus.CounterValue), 17 | "rcv_replies": newMetric("core", "replies_total", "Total number of received replies by OpenSIPS.", []string{}, prometheus.CounterValue), 18 | "fwd_requests": newMetric("core", "requests", "Number of requests by OpenSIPS.", []string{"kind"}, prometheus.CounterValue), 19 | "fwd_replies": newMetric("core", "replies", "Number of received replies by OpenSIPS.", []string{"kind"}, prometheus.CounterValue), 20 | "drop_requests": newMetric("core", "requests", "Number of requests by OpenSIPS.", []string{"kind"}, prometheus.CounterValue), 21 | "drop_replies": newMetric("core", "replies", "Number of received replies by OpenSIPS.", []string{"kind"}, prometheus.CounterValue), 22 | "err_requests": newMetric("core", "requests", "Number of requests by OpenSIPS.", []string{"kind"}, prometheus.CounterValue), 23 | "err_replies": newMetric("core", "replies", "Number of received replies by OpenSIPS.", []string{"kind"}, prometheus.CounterValue), 24 | "bad_URIs_rcvd": newMetric("core", "bad_URIs_rcvd", "Number of URIs that OpenSIPS failed to parse.", []string{}, prometheus.CounterValue), 25 | "unsupported_methods": newMetric("core", "unsupported_methods", "Number of non-standard methods encountered by OpenSIPS while parsing SIP methods.", []string{}, prometheus.CounterValue), 26 | "bad_msg_hdr": newMetric("core", "bad_msg_hdr", "Number of SIP headers that OpenSIPS failed to parse.", []string{}, prometheus.CounterValue), 27 | "timestamp": newMetric("core", "uptime_seconds", "Number of seconds elapsed from OpenSIPS starting.", []string{}, prometheus.CounterValue), 28 | } 29 | 30 | func init() { 31 | for metric := range coreMetrics { 32 | OpensipsProcessors[metric] = coreProcessorFunc 33 | } 34 | OpensipsProcessors["core:"] = coreProcessorFunc 35 | } 36 | 37 | func coreProcessorFunc(s map[string]opensips.Statistic) prometheus.Collector { 38 | return &coreProcessor{ 39 | statistics: s, 40 | } 41 | } 42 | 43 | // Describe implements prometheus.Collector. 44 | func (p coreProcessor) Describe(ch chan<- *prometheus.Desc) { 45 | for _, metric := range coreMetrics { 46 | ch <- metric.Desc 47 | } 48 | } 49 | 50 | // Collect implements prometheus.Collector. 51 | func (p coreProcessor) Collect(ch chan<- prometheus.Metric) { 52 | for _, s := range p.statistics { 53 | if s.Module == "core" { 54 | switch s.Name { 55 | case "rcv_requests": 56 | ch <- prometheus.MustNewConstMetric( 57 | coreMetrics["rcv_requests"].Desc, 58 | coreMetrics["rcv_requests"].ValueType, 59 | s.Value, 60 | ) 61 | case "rcv_replies": 62 | ch <- prometheus.MustNewConstMetric( 63 | coreMetrics["rcv_replies"].Desc, 64 | coreMetrics["rcv_replies"].ValueType, 65 | s.Value, 66 | ) 67 | case "fwd_requests": 68 | ch <- prometheus.MustNewConstMetric( 69 | coreMetrics["fwd_requests"].Desc, 70 | coreMetrics["fwd_requests"].ValueType, 71 | s.Value, 72 | "forwarded", 73 | ) 74 | case "fwd_replies": 75 | ch <- prometheus.MustNewConstMetric( 76 | coreMetrics["fwd_replies"].Desc, 77 | coreMetrics["fwd_replies"].ValueType, 78 | s.Value, 79 | "forwarded", 80 | ) 81 | case "drop_requests": 82 | ch <- prometheus.MustNewConstMetric( 83 | coreMetrics["drop_requests"].Desc, 84 | coreMetrics["drop_requests"].ValueType, 85 | s.Value, 86 | "dropped", 87 | ) 88 | case "drop_replies": 89 | ch <- prometheus.MustNewConstMetric( 90 | coreMetrics["drop_replies"].Desc, 91 | coreMetrics["drop_replies"].ValueType, 92 | s.Value, 93 | "dropped", 94 | ) 95 | case "err_requests": 96 | ch <- prometheus.MustNewConstMetric( 97 | coreMetrics["err_requests"].Desc, 98 | coreMetrics["err_requests"].ValueType, 99 | s.Value, 100 | "error", 101 | ) 102 | case "err_replies": 103 | ch <- prometheus.MustNewConstMetric( 104 | coreMetrics["err_replies"].Desc, 105 | coreMetrics["err_replies"].ValueType, 106 | s.Value, 107 | "error", 108 | ) 109 | case "bad_URIs_rcvd": 110 | ch <- prometheus.MustNewConstMetric( 111 | coreMetrics["bad_URIs_rcvd"].Desc, 112 | coreMetrics["bad_URIs_rcvd"].ValueType, 113 | s.Value, 114 | ) 115 | case "unsupported_methods": 116 | ch <- prometheus.MustNewConstMetric( 117 | coreMetrics["unsupported_methods"].Desc, 118 | coreMetrics["unsupported_methods"].ValueType, 119 | s.Value, 120 | ) 121 | case "bad_msg_hdr": 122 | ch <- prometheus.MustNewConstMetric( 123 | coreMetrics["bad_msg_hdr"].Desc, 124 | coreMetrics["bad_msg_hdr"].ValueType, 125 | s.Value, 126 | ) 127 | case "timestamp": 128 | ch <- prometheus.MustNewConstMetric( 129 | coreMetrics["timestamp"].Desc, 130 | coreMetrics["timestamp"].ValueType, 131 | s.Value, 132 | ) 133 | } 134 | } 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /processors/dialog_processor.go: -------------------------------------------------------------------------------- 1 | package processors 2 | 3 | import ( 4 | "github.com/VoIPGRID/opensips_exporter/opensips" 5 | "github.com/prometheus/client_golang/prometheus" 6 | ) 7 | 8 | // dialogProcessor exposes metrics about SIP dialogs. 9 | // doc: http://www.opensips.org/html/docs/modules/1.11.x/dialog.html#idp5859728 10 | // src: https://github.com/OpenSIPS/opensips/blob/1.11/modules/dialog/dialog.c#L283 11 | type dialogProcessor struct { 12 | statistics map[string]opensips.Statistic 13 | } 14 | 15 | var dialogLabelNames = []string{} 16 | var dialogMetrics = map[string]metric{ 17 | "active_dialogs": newMetric("dialog", "dialogs", "Number of dialogs.", []string{"status"}, prometheus.GaugeValue), 18 | "early_dialogs": newMetric("dialog", "dialogs", "Number of dialogs.", []string{"status"}, prometheus.GaugeValue), 19 | "processed_dialogs": newMetric("dialog", "dialogs", "Number of dialogs.", []string{"status"}, prometheus.GaugeValue), 20 | "expired_dialogs": newMetric("dialog", "dialogs", "Number of dialogs.", []string{"status"}, prometheus.GaugeValue), 21 | "failed_dialogs": newMetric("dialog", "dialogs", "Number of dialogs.", []string{"status"}, prometheus.GaugeValue), 22 | "create_sent": newMetric("dialog", "sent", "Number of replicated dialog requests send to other OpenSIPS instances.", []string{"event"}, prometheus.CounterValue), 23 | "update_sent": newMetric("dialog", "sent", "Number of replicated dialog requests send to other OpenSIPS instances.", []string{"event"}, prometheus.CounterValue), 24 | "delete_sent": newMetric("dialog", "sent", "Number of replicated dialog requests send to other OpenSIPS instances.", []string{"event"}, prometheus.CounterValue), 25 | "create_rcv": newMetric("dialog", "received", "The number of dialog events received from other OpenSIPS instances.", []string{"event"}, prometheus.CounterValue), 26 | "update_rcv": newMetric("dialog", "received", "The number of dialog events received from other OpenSIPS instances.", []string{"event"}, prometheus.CounterValue), 27 | "delete_rcv": newMetric("dialog", "received", "The number of dialog events received from other OpenSIPS instances.", []string{"event"}, prometheus.CounterValue), 28 | } 29 | 30 | func init() { 31 | for metric := range dialogMetrics { 32 | OpensipsProcessors[metric] = dialogProcessorFunc 33 | } 34 | OpensipsProcessors["dialog:"] = dialogProcessorFunc 35 | } 36 | 37 | // Describe implements prometheus.Collector. 38 | func (p dialogProcessor) Describe(ch chan<- *prometheus.Desc) { 39 | for _, metric := range dialogMetrics { 40 | ch <- metric.Desc 41 | } 42 | } 43 | 44 | // Collect implements prometheus.Collector. 45 | func (p dialogProcessor) Collect(ch chan<- prometheus.Metric) { 46 | for _, s := range p.statistics { 47 | if s.Module == "dialog" { 48 | switch s.Name { 49 | case "active_dialogs": 50 | ch <- prometheus.MustNewConstMetric( 51 | dialogMetrics["active_dialogs"].Desc, 52 | dialogMetrics["active_dialogs"].ValueType, 53 | s.Value, 54 | "active", 55 | ) 56 | case "early_dialogs": 57 | ch <- prometheus.MustNewConstMetric( 58 | dialogMetrics["early_dialogs"].Desc, 59 | dialogMetrics["early_dialogs"].ValueType, 60 | s.Value, 61 | "early", 62 | ) 63 | case "processed_dialogs": 64 | ch <- prometheus.MustNewConstMetric( 65 | dialogMetrics["processed_dialogs"].Desc, 66 | dialogMetrics["processed_dialogs"].ValueType, 67 | s.Value, 68 | "processed", 69 | ) 70 | case "expired_dialogs": 71 | ch <- prometheus.MustNewConstMetric( 72 | dialogMetrics["expired_dialogs"].Desc, 73 | dialogMetrics["expired_dialogs"].ValueType, 74 | s.Value, 75 | "expired", 76 | ) 77 | case "failed_dialogs": 78 | ch <- prometheus.MustNewConstMetric( 79 | dialogMetrics["failed_dialogs"].Desc, 80 | dialogMetrics["failed_dialogs"].ValueType, 81 | s.Value, 82 | "failed", 83 | ) 84 | case "create_sent": 85 | ch <- prometheus.MustNewConstMetric( 86 | dialogMetrics["create_sent"].Desc, 87 | dialogMetrics["create_sent"].ValueType, 88 | s.Value, 89 | "create", 90 | ) 91 | case "update_sent": 92 | ch <- prometheus.MustNewConstMetric( 93 | dialogMetrics["update_sent"].Desc, 94 | dialogMetrics["update_sent"].ValueType, 95 | s.Value, 96 | "update", 97 | ) 98 | case "delete_sent": 99 | ch <- prometheus.MustNewConstMetric( 100 | dialogMetrics["delete_sent"].Desc, 101 | dialogMetrics["delete_sent"].ValueType, 102 | s.Value, 103 | "delete", 104 | ) 105 | case "create_rcv": 106 | ch <- prometheus.MustNewConstMetric( 107 | dialogMetrics["create_rcv"].Desc, 108 | dialogMetrics["create_rcv"].ValueType, 109 | s.Value, 110 | "create", 111 | ) 112 | case "update_rcv": 113 | ch <- prometheus.MustNewConstMetric( 114 | dialogMetrics["update_rcv"].Desc, 115 | dialogMetrics["update_rcv"].ValueType, 116 | s.Value, 117 | "update", 118 | ) 119 | case "delete_rcv": 120 | ch <- prometheus.MustNewConstMetric( 121 | dialogMetrics["delete_rcv"].Desc, 122 | dialogMetrics["delete_rcv"].ValueType, 123 | s.Value, 124 | "delete", 125 | ) 126 | } 127 | } 128 | } 129 | } 130 | 131 | func dialogProcessorFunc(s map[string]opensips.Statistic) prometheus.Collector { 132 | return &dialogProcessor{ 133 | statistics: s, 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /processors/load_processor.go: -------------------------------------------------------------------------------- 1 | package processors 2 | 3 | import ( 4 | "strings" 5 | 6 | "log" 7 | 8 | "github.com/VoIPGRID/opensips_exporter/opensips" 9 | "github.com/prometheus/client_golang/prometheus" 10 | ) 11 | 12 | // loadProcessor describes busy children 13 | // doc: http://www.opensips.org/Documentation/Interface-CoreStatistics-1-11#toc14 14 | type loadProcessor struct { 15 | statistics map[string]opensips.Statistic 16 | } 17 | 18 | type loadMetric struct { 19 | metric metric 20 | ip string 21 | port string 22 | protocol string 23 | process string 24 | } 25 | 26 | func init() { 27 | OpensipsProcessors["load:"] = loadProcessorFunc 28 | OpensipsProcessors["tcp-load"] = loadProcessorFunc 29 | OpensipsProcessors["load"] = loadProcessorFunc 30 | OpensipsProcessors["load1m"] = loadProcessorFunc 31 | OpensipsProcessors["load10m"] = loadProcessorFunc 32 | OpensipsProcessors["load-all"] = loadProcessorFunc 33 | OpensipsProcessors["load1m-all"] = loadProcessorFunc 34 | OpensipsProcessors["load10m-all"] = loadProcessorFunc 35 | } 36 | 37 | // Describe implements prometheus.Collector. 38 | func (p loadProcessor) Describe(ch chan<- *prometheus.Desc) { 39 | for _, m := range p.loadMetrics() { 40 | ch <- m.metric.Desc 41 | } 42 | } 43 | 44 | // Collect implements prometheus.Collector. 45 | func (p loadProcessor) Collect(ch chan<- prometheus.Metric) { 46 | for key, u := range p.loadMetrics() { 47 | if u.ip != "" { 48 | ch <- prometheus.MustNewConstMetric( 49 | u.metric.Desc, 50 | u.metric.ValueType, 51 | p.statistics[key].Value, 52 | u.ip, u.port, u.protocol, 53 | ) 54 | } else if u.process != "" { 55 | ch <- prometheus.MustNewConstMetric( 56 | u.metric.Desc, 57 | u.metric.ValueType, 58 | p.statistics[key].Value, 59 | u.process, 60 | ) 61 | } else { 62 | ch <- prometheus.MustNewConstMetric( 63 | u.metric.Desc, 64 | u.metric.ValueType, 65 | p.statistics[key].Value, 66 | ) 67 | } 68 | } 69 | } 70 | 71 | func loadProcessorFunc(s map[string]opensips.Statistic) prometheus.Collector { 72 | return &loadProcessor{ 73 | statistics: s, 74 | } 75 | } 76 | 77 | func (p loadProcessor) loadMetrics() map[string]loadMetric { 78 | var metrics = map[string]loadMetric{} 79 | 80 | var stats []opensips.Statistic 81 | for _, s := range p.statistics { 82 | if s.Module == "load" { 83 | stats = append(stats, s) 84 | } 85 | } 86 | 87 | for _, s := range stats { 88 | 89 | if s.Name == "tcp-load" { 90 | // tcp-load is a OpenSIPS < 2 metric. 91 | metrics["tcp-load"] = loadMetric{ 92 | metric: newMetric("load", "tcp_load", "Percentage of TCP children that are awake and processing SIP messages.", []string{}, prometheus.GaugeValue), 93 | ip: "", 94 | protocol: "", 95 | port: "", 96 | process: "", 97 | } 98 | continue 99 | } else if strings.Contains(s.Name, "udp:") || strings.Contains(s.Name, "tcp:") { 100 | // The load metric is an OpenSIPS < 2 metric. The format for it is: 101 | // load:udp:127.0.0.1:5060-load = 0 102 | metrics[s.Name] = parseLegacyLoadFormat(s) 103 | continue 104 | } else if strings.Contains(s.Name, "proc") { 105 | // This metric is an OpenSIPS > 2 metric. The format for it is: 106 | // load:load-proc-1:: 0 107 | // load:load1m-proc-1:: 0 108 | // load:load10m-proc-1:: 0 109 | metrics[s.Name] = parseNewLoadFormat(s) 110 | continue 111 | } else if s.Name == "load" { 112 | metrics["load"] = loadMetric{ 113 | metric: newMetric("load", "core", "The realtime load of entire OpenSIPS - this counts all the core processes of OpenSIPS; the additional processes requested by modules are not counted in this load.", []string{}, prometheus.GaugeValue), 114 | ip: "", 115 | protocol: "", 116 | port: "", 117 | process: "", 118 | } 119 | continue 120 | } else if s.Name == "load1m" { 121 | metrics["load1m"] = loadMetric{ 122 | metric: newMetric("load", "core_1m", "The last minute average load of core OpenSIPS (covering only core/SIP processes)", []string{}, prometheus.GaugeValue), 123 | ip: "", 124 | protocol: "", 125 | port: "", 126 | process: "", 127 | } 128 | continue 129 | } else if s.Name == "load10m" { 130 | metrics["load10m"] = loadMetric{ 131 | metric: newMetric("load", "core_10m", "The last 10 minute average load of core OpenSIPS (covering only core/SIP processes)", []string{}, prometheus.GaugeValue), 132 | ip: "", 133 | protocol: "", 134 | port: "", 135 | process: "", 136 | } 137 | continue 138 | } else if s.Name == "load-all" { 139 | metrics["load-all"] = loadMetric{ 140 | metric: newMetric("load", "all", "The realtime load of entire OpenSIPS, counting both core and module processes.", []string{}, prometheus.GaugeValue), 141 | ip: "", 142 | protocol: "", 143 | port: "", 144 | process: "", 145 | } 146 | continue 147 | } else if s.Name == "load1m-all" { 148 | metrics["load1m-all"] = loadMetric{ 149 | metric: newMetric("load", "all_1m", "The last minute average load of entire OpenSIPS (covering all processes).", []string{}, prometheus.GaugeValue), 150 | ip: "", 151 | protocol: "", 152 | port: "", 153 | process: "", 154 | } 155 | continue 156 | } else if s.Name == "load10m-all" { 157 | metrics["load10m-all"] = loadMetric{ 158 | metric: newMetric("load", "all_10m", "The last 10 minute average load of entire OpenSIPS (covering all processes).", []string{}, prometheus.GaugeValue), 159 | ip: "", 160 | protocol: "", 161 | port: "", 162 | process: "", 163 | } 164 | continue 165 | } else if s.Name == "processes_number" { 166 | metrics["processes_number"] = loadMetric{ 167 | metric: newMetric("load", "processes_number", "Number of running OpenSIPS processes.", []string{}, prometheus.GaugeValue), 168 | ip: "", 169 | protocol: "", 170 | port: "", 171 | process: "", 172 | } 173 | continue 174 | } 175 | } 176 | return metrics 177 | } 178 | 179 | func parseLegacyLoadFormat(statistic opensips.Statistic) loadMetric { 180 | var ret loadMetric 181 | split := strings.Split(statistic.Name, ":") 182 | if len(split) >= 2 { 183 | protocol := split[0] 184 | ip := split[1] 185 | port := split[2] 186 | port = strings.Trim(port, "-load") 187 | 188 | metric := newMetric("load", "load", "The realtime load of entire OpenSIPS - this counts all the core processes of OpenSIPS; the additional processes requested by modules are not counted in this load.", []string{"ip", "port", "protocol"}, prometheus.GaugeValue) 189 | ret = loadMetric{ 190 | metric: metric, 191 | ip: ip, 192 | port: port, 193 | protocol: protocol, 194 | process: "", 195 | } 196 | } else { 197 | log.Printf("Unable to parse metric '%v'in loadProcessor. Reason: Not enough fields received (protocol, ip, port)", statistic.Name) 198 | } 199 | return ret 200 | } 201 | 202 | func parseNewLoadFormat(statistic opensips.Statistic) loadMetric { 203 | split := strings.Split(statistic.Name, "-") 204 | process := split[len(split)-1] 205 | load := split[0] 206 | 207 | switch load { 208 | case "load": 209 | return loadMetric{ 210 | metric: newMetric("load", "process", "The realtime load of the process ID.", []string{"process"}, prometheus.GaugeValue), 211 | ip: "", 212 | port: "", 213 | protocol: "", 214 | process: process, 215 | } 216 | case "load1m": 217 | return loadMetric{ 218 | metric: newMetric("load", "1m", "The last minute average load of the process ID.", []string{"process"}, prometheus.GaugeValue), 219 | ip: "", 220 | port: "", 221 | protocol: "", 222 | process: process, 223 | } 224 | case "load10m": 225 | return loadMetric{ 226 | metric: newMetric("load", "10m", "The last 10 minutes average load of the process ID.", []string{"process"}, prometheus.GaugeValue), 227 | ip: "", 228 | port: "", 229 | protocol: "", 230 | process: process, 231 | } 232 | case "processes_number": 233 | return loadMetric{ 234 | metric: newMetric("load", "processes_number", "Number of running OpenSIPS processes.", []string{}, prometheus.GaugeValue), 235 | ip: "", 236 | protocol: "", 237 | port: "", 238 | process: "", 239 | } 240 | } 241 | return loadMetric{} 242 | } 243 | -------------------------------------------------------------------------------- /processors/net_processor.go: -------------------------------------------------------------------------------- 1 | package processors 2 | 3 | import ( 4 | "github.com/VoIPGRID/opensips_exporter/opensips" 5 | "github.com/prometheus/client_golang/prometheus" 6 | ) 7 | 8 | // netProcessor provides metrics about network packets. 9 | // doc: http://www.opensips.org/Documentation/Interface-CoreStatistics-1-11#toc17 10 | // src: https://github.com/OpenSIPS/opensips/blob/b917c70ba8d5797dc6364aaf702c3415539be52a/core_stats.c#L95 11 | type netProcessor struct { 12 | statistics map[string]opensips.Statistic 13 | } 14 | 15 | var netLabelNames = []string{"protocol"} 16 | var netMetrics = map[string]metric{ 17 | "waiting_udp": newMetric("net", "waiting", "Number of bytes waiting to be consumed on an interface that OpenSIPS is listening on.", netLabelNames, prometheus.GaugeValue), 18 | "waiting_tcp": newMetric("net", "waiting", "Number of bytes waiting to be consumed on an interface that OpenSIPS is listening on.", netLabelNames, prometheus.GaugeValue), 19 | "waiting_tls": newMetric("net", "waiting", "Number of bytes waiting to be consumed on an interface that OpenSIPS is listening on.", netLabelNames, prometheus.GaugeValue), 20 | } 21 | 22 | func init() { 23 | for metric := range netMetrics { 24 | OpensipsProcessors[metric] = netProcessorFunc 25 | } 26 | OpensipsProcessors["net:"] = netProcessorFunc 27 | } 28 | 29 | // Describe implements prometheus.Collector. 30 | func (p netProcessor) Describe(ch chan<- *prometheus.Desc) { 31 | for _, metric := range netMetrics { 32 | ch <- metric.Desc 33 | } 34 | } 35 | 36 | // Collect implements prometheus.Collector. 37 | func (p netProcessor) Collect(ch chan<- prometheus.Metric) { 38 | for _, s := range p.statistics { 39 | if s.Module == "net" { 40 | switch s.Name { 41 | case "waiting_udp": 42 | ch <- prometheus.MustNewConstMetric( 43 | netMetrics["waiting_udp"].Desc, 44 | netMetrics["waiting_udp"].ValueType, 45 | s.Value, 46 | "udp", 47 | ) 48 | case "waiting_tcp": 49 | ch <- prometheus.MustNewConstMetric( 50 | netMetrics["waiting_tcp"].Desc, 51 | netMetrics["waiting_tcp"].ValueType, 52 | s.Value, 53 | "tcp", 54 | ) 55 | case "waiting_tls": 56 | ch <- prometheus.MustNewConstMetric( 57 | netMetrics["waiting_tls"].Desc, 58 | netMetrics["waiting_tls"].ValueType, 59 | s.Value, 60 | "tls", 61 | ) 62 | } 63 | } 64 | } 65 | } 66 | 67 | func netProcessorFunc(s map[string]opensips.Statistic) prometheus.Collector { 68 | return &netProcessor{ 69 | statistics: s, 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /processors/pkmem_processor.go: -------------------------------------------------------------------------------- 1 | package processors 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/VoIPGRID/opensips_exporter/opensips" 7 | "github.com/prometheus/client_golang/prometheus" 8 | ) 9 | 10 | // pkmemProcessor provides metrics about private memory usage and fragments 11 | // doc: http://www.opensips.org/Documentation/Interface-CoreStatistics-1-11#toc28 12 | // src: https://github.com/OpenSIPS/opensips/blob/b917c70ba8d5797dc6364aaf702c3415539be52a/core_stats.c#L165 13 | type pkmemProcessor struct { 14 | statistics map[string]opensips.Statistic 15 | } 16 | 17 | type pkmemMetric struct { 18 | metric metric 19 | pid string 20 | } 21 | 22 | func init() { 23 | OpensipsProcessors["pkmem:"] = pkmemProcessorFunc 24 | OpensipsProcessors["total_size"] = pkmemProcessorFunc 25 | OpensipsProcessors["used_size"] = pkmemProcessorFunc 26 | OpensipsProcessors["real_used_size"] = pkmemProcessorFunc 27 | OpensipsProcessors["max_used_size"] = pkmemProcessorFunc 28 | OpensipsProcessors["free_size"] = pkmemProcessorFunc 29 | OpensipsProcessors["fragments"] = pkmemProcessorFunc 30 | } 31 | 32 | // Describe implements prometheus.Collector. 33 | func (p pkmemProcessor) Describe(ch chan<- *prometheus.Desc) { 34 | for _, m := range p.pkmemMetrics() { 35 | ch <- m.metric.Desc 36 | } 37 | } 38 | 39 | // Collect implements prometheus.Collector. 40 | func (p pkmemProcessor) Collect(ch chan<- prometheus.Metric) { 41 | for key, u := range p.pkmemMetrics() { 42 | ch <- prometheus.MustNewConstMetric( 43 | u.metric.Desc, 44 | u.metric.ValueType, 45 | p.statistics[key].Value, 46 | u.pid, 47 | ) 48 | } 49 | } 50 | 51 | func pkmemProcessorFunc(s map[string]opensips.Statistic) prometheus.Collector { 52 | return &pkmemProcessor{ 53 | statistics: s, 54 | } 55 | } 56 | 57 | func (p pkmemProcessor) pkmemMetrics() map[string]pkmemMetric { 58 | var metrics = map[string]pkmemMetric{} 59 | 60 | // Get all pkmem statistics 61 | var stats []opensips.Statistic 62 | for _, s := range p.statistics { 63 | if s.Module == "pkmem" { 64 | stats = append(stats, s) 65 | } 66 | } 67 | 68 | for _, s := range stats { 69 | split := strings.Index(s.Name, "-") 70 | 71 | if split == -1 { 72 | continue 73 | } 74 | 75 | pid := s.Name[:split] 76 | metricType := s.Name[split+1:] 77 | 78 | switch metricType { 79 | case "total_size": 80 | metric := newMetric("pkmem", metricType, "Total size of private memory available to the OpenSIPS process.", []string{"pid"}, prometheus.GaugeValue) 81 | metrics[s.Name] = pkmemMetric{ 82 | metric: metric, 83 | pid: pid, 84 | } 85 | case "used_size": 86 | metric := newMetric("pkmem", metricType, "Amount of private memory requested and used by the OpenSIPS process.", []string{"pid"}, prometheus.GaugeValue) 87 | metrics[s.Name] = pkmemMetric{ 88 | metric: metric, 89 | pid: pid, 90 | } 91 | case "real_used_size": 92 | metric := newMetric("pkmem", metricType, "Amount of private memory requested by the OpenSIPS process, including allocator-specific metadata.", []string{"pid"}, prometheus.GaugeValue) 93 | metrics[s.Name] = pkmemMetric{ 94 | metric: metric, 95 | pid: pid, 96 | } 97 | case "max_used_size": 98 | metric := newMetric("pkmem", metricType, "The maximum amount of private memory ever used by the OpenSIPS process.", []string{"pid"}, prometheus.GaugeValue) 99 | metrics[s.Name] = pkmemMetric{ 100 | metric: metric, 101 | pid: pid, 102 | } 103 | case "free_size": 104 | metric := newMetric("pkmem", metricType, "Free private memory available for the OpenSIPS process. Computed as total_size - real_used_size.", []string{"pid"}, prometheus.GaugeValue) 105 | metrics[s.Name] = pkmemMetric{ 106 | metric: metric, 107 | pid: pid, 108 | } 109 | case "fragments": 110 | metric := newMetric("pkmem", metricType, "Currently available number of free fragments in the private memory for OpenSIPS process.", []string{"pid"}, prometheus.GaugeValue) 111 | metrics[s.Name] = pkmemMetric{ 112 | metric: metric, 113 | pid: pid, 114 | } 115 | } 116 | } 117 | 118 | return metrics 119 | } 120 | -------------------------------------------------------------------------------- /processors/processor.go: -------------------------------------------------------------------------------- 1 | package processors 2 | 3 | import ( 4 | "github.com/VoIPGRID/opensips_exporter/opensips" 5 | "github.com/prometheus/client_golang/prometheus" 6 | ) 7 | 8 | type metric struct { 9 | Desc *prometheus.Desc 10 | ValueType prometheus.ValueType 11 | } 12 | 13 | type processor func(map[string]opensips.Statistic) prometheus.Collector 14 | 15 | // OpensipsProcessors is a map of processors for each subsystem 16 | var OpensipsProcessors = make(map[string]processor) 17 | 18 | const namespace = "opensips" 19 | 20 | func newMetric(subsystem string, name string, help string, variableLabels []string, t prometheus.ValueType) metric { 21 | return metric{ 22 | prometheus.NewDesc( 23 | prometheus.BuildFQName(namespace, subsystem, name), 24 | help, variableLabels, nil, 25 | ), t} 26 | } 27 | -------------------------------------------------------------------------------- /processors/registrar_processor.go: -------------------------------------------------------------------------------- 1 | package processors 2 | 3 | import ( 4 | "github.com/VoIPGRID/opensips_exporter/opensips" 5 | "github.com/prometheus/client_golang/prometheus" 6 | ) 7 | 8 | // registrarProcessor provides metrcs about SIP registrations 9 | // doc: http://www.opensips.org/html/docs/modules/1.11.x/registrar.html#idp5702944 10 | // src: https://github.com/OpenSIPS/opensips/blob/1.11/modules/registrar/reg_mod.c#L202 11 | type registrarProcessor struct { 12 | statistics map[string]opensips.Statistic 13 | } 14 | 15 | var registrarLabelNames = []string{} 16 | var registrarMetrics = map[string]metric{ 17 | "max_expires": newMetric("registrar", "max_expires", "Value of max_expires parameter.", registrarLabelNames, prometheus.GaugeValue), 18 | "max_contacts": newMetric("registrar", "max_contacts", "Value of max_contacts parameter.", registrarLabelNames, prometheus.GaugeValue), 19 | "default_expire": newMetric("registrar", "default_expire", "Value of default_expire parameter.", registrarLabelNames, prometheus.GaugeValue), 20 | "accepted_regs": newMetric("registrar", "registrations", "Number of registrations.", []string{"type"}, prometheus.CounterValue), 21 | "rejected_regs": newMetric("registrar", "registrations", "Number of registrations.", []string{"type"}, prometheus.CounterValue), 22 | } 23 | 24 | func init() { 25 | for metric := range registrarMetrics { 26 | OpensipsProcessors[metric] = registrarProcessorFunc 27 | } 28 | OpensipsProcessors["registrar:"] = registrarProcessorFunc 29 | } 30 | 31 | // Describe implements prometheus.Collector. 32 | func (p registrarProcessor) Describe(ch chan<- *prometheus.Desc) { 33 | for _, metric := range registrarMetrics { 34 | ch <- metric.Desc 35 | } 36 | } 37 | 38 | // Collect implements prometheus.Collector. 39 | func (p registrarProcessor) Collect(ch chan<- prometheus.Metric) { 40 | for _, s := range p.statistics { 41 | if s.Module == "registrar" { 42 | switch s.Name { 43 | case "max_expires": 44 | ch <- prometheus.MustNewConstMetric( 45 | registrarMetrics["max_expires"].Desc, 46 | registrarMetrics["max_expires"].ValueType, 47 | s.Value, 48 | ) 49 | case "max_contacts": 50 | ch <- prometheus.MustNewConstMetric( 51 | registrarMetrics["max_contacts"].Desc, 52 | registrarMetrics["max_contacts"].ValueType, 53 | s.Value, 54 | ) 55 | case "default_expire": 56 | ch <- prometheus.MustNewConstMetric( 57 | registrarMetrics["default_expire"].Desc, 58 | registrarMetrics["default_expire"].ValueType, 59 | s.Value, 60 | ) 61 | case "accepted_regs": 62 | ch <- prometheus.MustNewConstMetric( 63 | registrarMetrics["accepted_regs"].Desc, 64 | registrarMetrics["accepted_regs"].ValueType, 65 | s.Value, 66 | "accepted", 67 | ) 68 | case "rejected_regs": 69 | ch <- prometheus.MustNewConstMetric( 70 | registrarMetrics["rejected_regs"].Desc, 71 | registrarMetrics["rejected_regs"].ValueType, 72 | s.Value, 73 | "rejected", 74 | ) 75 | } 76 | } 77 | } 78 | } 79 | 80 | func registrarProcessorFunc(s map[string]opensips.Statistic) prometheus.Collector { 81 | return ®istrarProcessor{ 82 | statistics: s, 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /processors/scrape_processor.go: -------------------------------------------------------------------------------- 1 | package processors 2 | 3 | import ( 4 | "github.com/prometheus/client_golang/prometheus" 5 | ) 6 | 7 | type scrapeProcessor struct { 8 | upMetric metric 9 | upStatus float64 10 | } 11 | 12 | // Describe implements prometheus.Collector. 13 | func (p scrapeProcessor) Describe(ch chan<- *prometheus.Desc) { 14 | ch <- p.upMetric.Desc 15 | } 16 | 17 | // Collect implements prometheus.Collector. 18 | func (p scrapeProcessor) Collect(ch chan<- prometheus.Metric) { 19 | ch <- prometheus.MustNewConstMetric( 20 | p.upMetric.Desc, 21 | p.upMetric.ValueType, 22 | p.upStatus, 23 | ) 24 | } 25 | 26 | // NewScrapeProcessor is used to export meta metrics about the exporter/OpenSIPS such as up status, 27 | // time to scrape, metrics processed, scrape count etc. 28 | func NewScrapeProcessor(upStatus float64) prometheus.Collector { 29 | return &scrapeProcessor{ 30 | upMetric: newMetric("", "up", "Whether the opensips exporter could read metrics from the Management Interface socket. (i.e. is OpenSIPS up)", []string{}, prometheus.GaugeValue), 31 | upStatus: upStatus, 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /processors/shmem_processor.go: -------------------------------------------------------------------------------- 1 | package processors 2 | 3 | import ( 4 | "github.com/VoIPGRID/opensips_exporter/opensips" 5 | "github.com/prometheus/client_golang/prometheus" 6 | ) 7 | 8 | // shmemProcessor provices metrics about shared memory 9 | // doc: http://www.opensips.org/Documentation/Interface-CoreStatistics-1-11#toc21 10 | // src: https://github.com/OpenSIPS/opensips/blob/1.11/mem/shm_mem.c#L52 11 | type shmemProcessor struct { 12 | statistics map[string]opensips.Statistic 13 | } 14 | 15 | var shmemLabelNames = []string{} 16 | var shmemMetrics = map[string]metric{ 17 | "total_size": newMetric("shmem", "total_size", "Total size of shared memory available to OpenSIPS processes.", shmemLabelNames, prometheus.GaugeValue), 18 | "used_size": newMetric("shmem", "used_size", "Amount of shared memory requested and used by OpenSIPS processes.", shmemLabelNames, prometheus.GaugeValue), 19 | "real_used_size": newMetric("shmem", "real_used_size", "Amount of shared memory requested by OpenSIPS processes + malloc overhead", shmemLabelNames, prometheus.GaugeValue), 20 | "max_used_size": newMetric("shmem", "max_used_size", "Maximum amount of shared memory ever used by OpenSIPS processes.", shmemLabelNames, prometheus.GaugeValue), 21 | "free_size": newMetric("shmem", "free_size", "Free memory available. Computed as total_size - real_used_size", shmemLabelNames, prometheus.GaugeValue), 22 | "fragments": newMetric("shmem", "fragments", "Total number of fragments in the shared memory.", shmemLabelNames, prometheus.GaugeValue), 23 | } 24 | 25 | func init() { 26 | for metric := range shmemMetrics { 27 | OpensipsProcessors[metric] = shmemProcessorFunc 28 | } 29 | OpensipsProcessors["shmem:"] = shmemProcessorFunc 30 | } 31 | 32 | // Describe implements prometheus.Collector. 33 | func (p shmemProcessor) Describe(ch chan<- *prometheus.Desc) { 34 | for _, metric := range shmemMetrics { 35 | ch <- metric.Desc 36 | } 37 | } 38 | 39 | // Collect implements prometheus.Collector. 40 | func (p shmemProcessor) Collect(ch chan<- prometheus.Metric) { 41 | for _, s := range p.statistics { 42 | if s.Module == "shmem" { 43 | switch s.Name { 44 | case "total_size": 45 | ch <- prometheus.MustNewConstMetric( 46 | shmemMetrics["total_size"].Desc, 47 | shmemMetrics["total_size"].ValueType, 48 | s.Value, 49 | ) 50 | case "used_size": 51 | ch <- prometheus.MustNewConstMetric( 52 | shmemMetrics["used_size"].Desc, 53 | shmemMetrics["used_size"].ValueType, 54 | s.Value, 55 | ) 56 | case "real_used_size": 57 | ch <- prometheus.MustNewConstMetric( 58 | shmemMetrics["real_used_size"].Desc, 59 | shmemMetrics["real_used_size"].ValueType, 60 | s.Value, 61 | ) 62 | case "max_used_size": 63 | ch <- prometheus.MustNewConstMetric( 64 | shmemMetrics["max_used_size"].Desc, 65 | shmemMetrics["max_used_size"].ValueType, 66 | s.Value, 67 | ) 68 | case "free_size": 69 | ch <- prometheus.MustNewConstMetric( 70 | shmemMetrics["free_size"].Desc, 71 | shmemMetrics["free_size"].ValueType, 72 | s.Value, 73 | ) 74 | case "fragments": 75 | ch <- prometheus.MustNewConstMetric( 76 | shmemMetrics["fragments"].Desc, 77 | shmemMetrics["fragments"].ValueType, 78 | s.Value, 79 | ) 80 | } 81 | } 82 | } 83 | } 84 | 85 | func shmemProcessorFunc(s map[string]opensips.Statistic) prometheus.Collector { 86 | return &shmemProcessor{ 87 | statistics: s, 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /processors/sl_processor.go: -------------------------------------------------------------------------------- 1 | package processors 2 | 3 | import ( 4 | "github.com/VoIPGRID/opensips_exporter/opensips" 5 | "github.com/prometheus/client_golang/prometheus" 6 | ) 7 | 8 | // slProcessor provides metrics for its stateless UA 9 | // doc: http://www.opensips.org/html/docs/modules/1.11.x/sl.html#idp158896 10 | // src: https://github.com/OpenSIPS/opensips/blob/1.11/modules/sl/sl.c#L91 11 | type slProcessor struct { 12 | statistics map[string]opensips.Statistic 13 | } 14 | 15 | var slLabelNames = []string{} 16 | var slMetrics = map[string]metric{ 17 | "xxx_replies": newMetric("sl", "xxx_replies", "The number of replies that don't match any other reply status.", slLabelNames, prometheus.CounterValue), 18 | "1xx_replies": newMetric("sl", "replies", "The number of replies.", []string{"type"}, prometheus.CounterValue), 19 | "2xx_replies": newMetric("sl", "replies", "The number of replies.", []string{"type"}, prometheus.CounterValue), 20 | "200_replies": newMetric("sl", "replies", "The number of replies.", []string{"type"}, prometheus.CounterValue), 21 | "202_replies": newMetric("sl", "replies", "The number of replies.", []string{"type"}, prometheus.CounterValue), 22 | "3xx_replies": newMetric("sl", "replies", "The number of replies.", []string{"type"}, prometheus.CounterValue), 23 | "300_replies": newMetric("sl", "replies", "The number of replies.", []string{"type"}, prometheus.CounterValue), 24 | "301_replies": newMetric("sl", "replies", "The number of replies.", []string{"type"}, prometheus.CounterValue), 25 | "302_replies": newMetric("sl", "replies", "The number of replies.", []string{"type"}, prometheus.CounterValue), 26 | "4xx_replies": newMetric("sl", "replies", "The number of replies.", []string{"type"}, prometheus.CounterValue), 27 | "400_replies": newMetric("sl", "replies", "The number of replies.", []string{"type"}, prometheus.CounterValue), 28 | "401_replies": newMetric("sl", "replies", "The number of replies.", []string{"type"}, prometheus.CounterValue), 29 | "403_replies": newMetric("sl", "replies", "The number of replies.", []string{"type"}, prometheus.CounterValue), 30 | "404_replies": newMetric("sl", "replies", "The number of replies.", []string{"type"}, prometheus.CounterValue), 31 | "407_replies": newMetric("sl", "replies", "The number of replies.", []string{"type"}, prometheus.CounterValue), 32 | "408_replies": newMetric("sl", "replies", "The number of replies.", []string{"type"}, prometheus.CounterValue), 33 | "483_replies": newMetric("sl", "replies", "The number of replies.", []string{"type"}, prometheus.CounterValue), 34 | "5xx_replies": newMetric("sl", "replies", "The number of replies.", []string{"type"}, prometheus.CounterValue), 35 | "500_replies": newMetric("sl", "replies", "The number of replies.", []string{"type"}, prometheus.CounterValue), 36 | "6xx_replies": newMetric("sl", "replies", "The number of replies.", []string{"type"}, prometheus.CounterValue), 37 | "sent_replies": newMetric("sl", "sent_replies_total", "The total number of sent_replies.", slLabelNames, prometheus.CounterValue), 38 | "sent_err_replies": newMetric("sl", "sent_err_replies_total", "The total number of sent_err_replies.", slLabelNames, prometheus.CounterValue), 39 | "received_ACKs": newMetric("sl", "received_ACKs", "The number of received_ACKs.", slLabelNames, prometheus.CounterValue), 40 | "failures": newMetric("sl", "failures", "The number of failures.", slLabelNames, prometheus.CounterValue), 41 | } 42 | 43 | func init() { 44 | for metric := range slMetrics { 45 | OpensipsProcessors[metric] = slProcessorFunc 46 | } 47 | OpensipsProcessors["sl:"] = slProcessorFunc 48 | } 49 | 50 | // Describe implements prometheus.Collector. 51 | func (p slProcessor) Describe(ch chan<- *prometheus.Desc) { 52 | for _, metric := range slMetrics { 53 | ch <- metric.Desc 54 | } 55 | } 56 | 57 | // Collect implements prometheus.Collector. 58 | func (p slProcessor) Collect(ch chan<- prometheus.Metric) { 59 | for _, s := range p.statistics { 60 | if s.Module == "sl" { 61 | switch s.Name { 62 | case "xxx_replies": 63 | ch <- prometheus.MustNewConstMetric( 64 | slMetrics["xxx_replies"].Desc, 65 | slMetrics["xxx_replies"].ValueType, 66 | s.Value, 67 | ) 68 | case "1xx_replies": 69 | ch <- prometheus.MustNewConstMetric( 70 | slMetrics["1xx_replies"].Desc, 71 | slMetrics["1xx_replies"].ValueType, 72 | s.Value, 73 | "1xx", 74 | ) 75 | case "2xx_replies": 76 | ch <- prometheus.MustNewConstMetric( 77 | slMetrics["2xx_replies"].Desc, 78 | slMetrics["2xx_replies"].ValueType, 79 | s.Value, 80 | "2xx", 81 | ) 82 | case "200_replies": 83 | ch <- prometheus.MustNewConstMetric( 84 | slMetrics["200_replies"].Desc, 85 | slMetrics["200_replies"].ValueType, 86 | s.Value, 87 | "200", 88 | ) 89 | case "202_replies": 90 | ch <- prometheus.MustNewConstMetric( 91 | slMetrics["202_replies"].Desc, 92 | slMetrics["202_replies"].ValueType, 93 | s.Value, 94 | "202", 95 | ) 96 | case "3xx_replies": 97 | ch <- prometheus.MustNewConstMetric( 98 | slMetrics["3xx_replies"].Desc, 99 | slMetrics["3xx_replies"].ValueType, 100 | s.Value, 101 | "3xx", 102 | ) 103 | case "300_replies": 104 | ch <- prometheus.MustNewConstMetric( 105 | slMetrics["300_replies"].Desc, 106 | slMetrics["300_replies"].ValueType, 107 | s.Value, 108 | "300", 109 | ) 110 | case "301_replies": 111 | ch <- prometheus.MustNewConstMetric( 112 | slMetrics["301_replies"].Desc, 113 | slMetrics["301_replies"].ValueType, 114 | s.Value, 115 | "301", 116 | ) 117 | case "302_replies": 118 | ch <- prometheus.MustNewConstMetric( 119 | slMetrics["302_replies"].Desc, 120 | slMetrics["302_replies"].ValueType, 121 | s.Value, 122 | "302", 123 | ) 124 | case "4xx_replies": 125 | ch <- prometheus.MustNewConstMetric( 126 | slMetrics["4xx_replies"].Desc, 127 | slMetrics["4xx_replies"].ValueType, 128 | s.Value, 129 | "4xx", 130 | ) 131 | case "400_replies": 132 | ch <- prometheus.MustNewConstMetric( 133 | slMetrics["400_replies"].Desc, 134 | slMetrics["400_replies"].ValueType, 135 | s.Value, 136 | "400", 137 | ) 138 | case "401_replies": 139 | ch <- prometheus.MustNewConstMetric( 140 | slMetrics["401_replies"].Desc, 141 | slMetrics["401_replies"].ValueType, 142 | s.Value, 143 | "401", 144 | ) 145 | case "403_replies": 146 | ch <- prometheus.MustNewConstMetric( 147 | slMetrics["403_replies"].Desc, 148 | slMetrics["403_replies"].ValueType, 149 | s.Value, 150 | "403", 151 | ) 152 | case "404_replies": 153 | ch <- prometheus.MustNewConstMetric( 154 | slMetrics["404_replies"].Desc, 155 | slMetrics["404_replies"].ValueType, 156 | s.Value, 157 | "404", 158 | ) 159 | case "407_replies": 160 | ch <- prometheus.MustNewConstMetric( 161 | slMetrics["407_replies"].Desc, 162 | slMetrics["407_replies"].ValueType, 163 | s.Value, 164 | "407", 165 | ) 166 | case "408_replies": 167 | ch <- prometheus.MustNewConstMetric( 168 | slMetrics["408_replies"].Desc, 169 | slMetrics["408_replies"].ValueType, 170 | s.Value, 171 | "408", 172 | ) 173 | case "483_replies": 174 | ch <- prometheus.MustNewConstMetric( 175 | slMetrics["483_replies"].Desc, 176 | slMetrics["483_replies"].ValueType, 177 | s.Value, 178 | "483", 179 | ) 180 | case "5xx_replies": 181 | ch <- prometheus.MustNewConstMetric( 182 | slMetrics["5xx_replies"].Desc, 183 | slMetrics["5xx_replies"].ValueType, 184 | s.Value, 185 | "5xx", 186 | ) 187 | case "500_replies": 188 | ch <- prometheus.MustNewConstMetric( 189 | slMetrics["500_replies"].Desc, 190 | slMetrics["500_replies"].ValueType, 191 | s.Value, 192 | "500", 193 | ) 194 | case "6xx_replies": 195 | ch <- prometheus.MustNewConstMetric( 196 | slMetrics["6xx_replies"].Desc, 197 | slMetrics["6xx_replies"].ValueType, 198 | s.Value, 199 | "6xx", 200 | ) 201 | case "sent_replies": 202 | ch <- prometheus.MustNewConstMetric( 203 | slMetrics["sent_replies"].Desc, 204 | slMetrics["sent_replies"].ValueType, 205 | s.Value, 206 | ) 207 | case "sent_err_replies": 208 | ch <- prometheus.MustNewConstMetric( 209 | slMetrics["sent_err_replies"].Desc, 210 | slMetrics["sent_err_replies"].ValueType, 211 | s.Value, 212 | ) 213 | case "received_ACKs": 214 | ch <- prometheus.MustNewConstMetric( 215 | slMetrics["received_ACKs"].Desc, 216 | slMetrics["received_ACKs"].ValueType, 217 | s.Value, 218 | ) 219 | case "failures": 220 | ch <- prometheus.MustNewConstMetric( 221 | slMetrics["failures"].Desc, 222 | slMetrics["failures"].ValueType, 223 | s.Value, 224 | ) 225 | } 226 | } 227 | } 228 | } 229 | 230 | func slProcessorFunc(s map[string]opensips.Statistic) prometheus.Collector { 231 | return &slProcessor{ 232 | statistics: s, 233 | } 234 | } 235 | -------------------------------------------------------------------------------- /processors/tm_processor.go: -------------------------------------------------------------------------------- 1 | package processors 2 | 3 | import ( 4 | "github.com/VoIPGRID/opensips_exporter/opensips" 5 | "github.com/prometheus/client_golang/prometheus" 6 | ) 7 | 8 | // tmProcessor exposes metrics for stateful processing of SIP transactions. 9 | // doc: http://www.opensips.org/html/docs/modules/1.11.x/tm.html#idp5881664 10 | // src: https://github.com/OpenSIPS/opensips/blob/1.11/modules/tm/tm.c#L283 11 | type tmProcessor struct { 12 | statistics map[string]opensips.Statistic 13 | } 14 | 15 | var tmLabelNames = []string{} 16 | var tmMetrics = map[string]metric{ 17 | "received_replies": newMetric("tm", "received_replies_total", "Total number of total replies received by TM module.", tmLabelNames, prometheus.CounterValue), 18 | "relayed_replies": newMetric("tm", "relayed_replies_total", "Total number of replies received and relayed by TM module.", tmLabelNames, prometheus.CounterValue), 19 | "local_replies": newMetric("tm", "local_replies_total", "Total number of replies local generated by TM module.", tmLabelNames, prometheus.CounterValue), 20 | "UAS_transactions": newMetric("tm", "transactions_total", "Total number of transactions.", []string{"type"}, prometheus.CounterValue), 21 | "UAC_transactions": newMetric("tm", "transactions_total", "Total number of transactions.", []string{"type"}, prometheus.CounterValue), 22 | "2xx_transactions": newMetric("tm", "transactions_total", "Total number of transactions.", []string{"type"}, prometheus.CounterValue), 23 | "3xx_transactions": newMetric("tm", "transactions_total", "Total number of transactions.", []string{"type"}, prometheus.CounterValue), 24 | "4xx_transactions": newMetric("tm", "transactions_total", "Total number of transactions.", []string{"type"}, prometheus.CounterValue), 25 | "5xx_transactions": newMetric("tm", "transactions_total", "Total number of transactions.", []string{"type"}, prometheus.CounterValue), 26 | "6xx_transactions": newMetric("tm", "transactions_total", "Total number of transactions.", []string{"type"}, prometheus.CounterValue), 27 | "inuse_transactions": newMetric("tm", "inuse_transactions", "Number of transactions existing in memory at current time.", tmLabelNames, prometheus.GaugeValue), 28 | } 29 | 30 | func init() { 31 | for metric := range tmMetrics { 32 | OpensipsProcessors[metric] = tmProcessorFunc 33 | } 34 | OpensipsProcessors["tm:"] = tmProcessorFunc 35 | } 36 | 37 | // Describe implements prometheus.Collector. 38 | func (p tmProcessor) Describe(ch chan<- *prometheus.Desc) { 39 | for _, metric := range tmMetrics { 40 | ch <- metric.Desc 41 | } 42 | } 43 | 44 | // Collect implements prometheus.Collector. 45 | func (p tmProcessor) Collect(ch chan<- prometheus.Metric) { 46 | for _, s := range p.statistics { 47 | if s.Module == "tm" { 48 | switch s.Name { 49 | case "received_replies": 50 | ch <- prometheus.MustNewConstMetric( 51 | tmMetrics["received_replies"].Desc, 52 | tmMetrics["received_replies"].ValueType, 53 | s.Value, 54 | ) 55 | case "relayed_replies": 56 | ch <- prometheus.MustNewConstMetric( 57 | tmMetrics["relayed_replies"].Desc, 58 | tmMetrics["relayed_replies"].ValueType, 59 | s.Value, 60 | ) 61 | case "local_replies": 62 | ch <- prometheus.MustNewConstMetric( 63 | tmMetrics["local_replies"].Desc, 64 | tmMetrics["local_replies"].ValueType, 65 | s.Value, 66 | ) 67 | case "UAS_transactions": 68 | ch <- prometheus.MustNewConstMetric( 69 | tmMetrics["UAS_transactions"].Desc, 70 | tmMetrics["UAS_transactions"].ValueType, 71 | s.Value, 72 | "UAS", 73 | ) 74 | case "UAC_transactions": 75 | ch <- prometheus.MustNewConstMetric( 76 | tmMetrics["UAC_transactions"].Desc, 77 | tmMetrics["UAC_transactions"].ValueType, 78 | s.Value, 79 | "UAC", 80 | ) 81 | case "2xx_transactions": 82 | ch <- prometheus.MustNewConstMetric( 83 | tmMetrics["2xx_transactions"].Desc, 84 | tmMetrics["2xx_transactions"].ValueType, 85 | s.Value, 86 | "2xx", 87 | ) 88 | case "3xx_transactions": 89 | ch <- prometheus.MustNewConstMetric( 90 | tmMetrics["3xx_transactions"].Desc, 91 | tmMetrics["3xx_transactions"].ValueType, 92 | s.Value, 93 | "3xx", 94 | ) 95 | case "4xx_transactions": 96 | ch <- prometheus.MustNewConstMetric( 97 | tmMetrics["4xx_transactions"].Desc, 98 | tmMetrics["4xx_transactions"].ValueType, 99 | s.Value, 100 | "4xx", 101 | ) 102 | case "5xx_transactions": 103 | ch <- prometheus.MustNewConstMetric( 104 | tmMetrics["5xx_transactions"].Desc, 105 | tmMetrics["5xx_transactions"].ValueType, 106 | s.Value, 107 | "5xx", 108 | ) 109 | case "6xx_transactions": 110 | ch <- prometheus.MustNewConstMetric( 111 | tmMetrics["6xx_transactions"].Desc, 112 | tmMetrics["6xx_transactions"].ValueType, 113 | s.Value, 114 | "6xx", 115 | ) 116 | case "inuse_transactions": 117 | ch <- prometheus.MustNewConstMetric( 118 | tmMetrics["inuse_transactions"].Desc, 119 | tmMetrics["inuse_transactions"].ValueType, 120 | s.Value, 121 | ) 122 | } 123 | } 124 | } 125 | } 126 | 127 | func tmProcessorFunc(s map[string]opensips.Statistic) prometheus.Collector { 128 | return &tmProcessor{ 129 | statistics: s, 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /processors/tmx_processor.go: -------------------------------------------------------------------------------- 1 | package processors 2 | 3 | import ( 4 | "github.com/VoIPGRID/opensips_exporter/opensips" 5 | "github.com/prometheus/client_golang/prometheus" 6 | ) 7 | 8 | // tmxProcessor exposes metrics for stateful processing of SIP transactions. 9 | // doc: https://kamailio.org/docs/modules/4.4.x/modules/tmx.html#idp23886596 10 | type tmxProcessor struct { 11 | statistics map[string]opensips.Statistic 12 | } 13 | 14 | var tmxLabelNames = []string{} 15 | var tmxMetrics = map[string]metric{ 16 | "UAS_transactions": newMetric("tmx", "UAS_transactions", "Total number of transactions created by received requests.", tmxLabelNames, prometheus.CounterValue), 17 | "UAC_transactions": newMetric("tmx", "UAC_transactions", "Total number of transactions created by local generated requests.", tmxLabelNames, prometheus.CounterValue), 18 | "2xx_transactions": newMetric("tmx", "transactions_total", "Total number of transactions.", []string{"type"}, prometheus.CounterValue), 19 | "3xx_transactions": newMetric("tmx", "transactions_total", "Total number of transactions.", []string{"type"}, prometheus.CounterValue), 20 | "4xx_transactions": newMetric("tmx", "transactions_total", "Total number of transactions.", []string{"type"}, prometheus.CounterValue), 21 | "5xx_transactions": newMetric("tmx", "transactions_total", "Total number of transactions.", []string{"type"}, prometheus.CounterValue), 22 | "6xx_transactions": newMetric("tmx", "transactions_total", "Total number of transactions.", []string{"type"}, prometheus.CounterValue), 23 | "inuse_transactions": newMetric("tmx", "inuse_transactions", "Number of transactions existing in memory at current time.", tmxLabelNames, prometheus.GaugeValue), 24 | "active_transactions": newMetric("tmx", "active_transactions", "Number of ongoing transactions at current time.", tmxLabelNames, prometheus.GaugeValue), 25 | "rpl_received": newMetric("tmx", "replies", "Total number of replies.", []string{"type"}, prometheus.CounterValue), 26 | "rpl_absorbed": newMetric("tmx", "replies", "Total number of replies.", []string{"type"}, prometheus.CounterValue), 27 | "rpl_relayed": newMetric("tmx", "replies", "Total number of replies.", []string{"type"}, prometheus.CounterValue), 28 | "rpl_generated": newMetric("tmx", "replies", "Total number of replies.", []string{"type"}, prometheus.CounterValue), 29 | "rpl_sent": newMetric("tmx", "replies", "Total number of replies.", []string{"type"}, prometheus.CounterValue), 30 | } 31 | 32 | func init() { 33 | for metric := range tmxMetrics { 34 | OpensipsProcessors[metric] = tmxProcessorFunc 35 | } 36 | OpensipsProcessors["tmx:"] = tmxProcessorFunc 37 | } 38 | 39 | // Describe implements prometheus.Collector. 40 | func (p tmxProcessor) Describe(ch chan<- *prometheus.Desc) { 41 | for _, metric := range tmxMetrics { 42 | ch <- metric.Desc 43 | } 44 | } 45 | 46 | // Collect implements prometheus.Collector. 47 | func (p tmxProcessor) Collect(ch chan<- prometheus.Metric) { 48 | for _, s := range p.statistics { 49 | if s.Module == "tmx" { 50 | switch s.Name { 51 | case "UAS_transactions": 52 | ch <- prometheus.MustNewConstMetric( 53 | tmxMetrics["UAS_transactions"].Desc, 54 | tmxMetrics["UAS_transactions"].ValueType, 55 | s.Value, 56 | ) 57 | case "UAC_transactions": 58 | ch <- prometheus.MustNewConstMetric( 59 | tmxMetrics["UAC_transactions"].Desc, 60 | tmxMetrics["UAC_transactions"].ValueType, 61 | s.Value, 62 | ) 63 | case "2xx_transactions": 64 | ch <- prometheus.MustNewConstMetric( 65 | tmxMetrics["2xx_transactions"].Desc, 66 | tmxMetrics["2xx_transactions"].ValueType, 67 | s.Value, 68 | "2xx", 69 | ) 70 | case "3xx_transactions": 71 | ch <- prometheus.MustNewConstMetric( 72 | tmxMetrics["3xx_transactions"].Desc, 73 | tmxMetrics["3xx_transactions"].ValueType, 74 | s.Value, 75 | "3xx", 76 | ) 77 | case "4xx_transactions": 78 | ch <- prometheus.MustNewConstMetric( 79 | tmxMetrics["4xx_transactions"].Desc, 80 | tmxMetrics["4xx_transactions"].ValueType, 81 | s.Value, 82 | "4xx", 83 | ) 84 | case "5xx_transactions": 85 | ch <- prometheus.MustNewConstMetric( 86 | tmxMetrics["5xx_transactions"].Desc, 87 | tmxMetrics["5xx_transactions"].ValueType, 88 | s.Value, 89 | "5xx", 90 | ) 91 | case "6xx_transactions": 92 | ch <- prometheus.MustNewConstMetric( 93 | tmxMetrics["6xx_transactions"].Desc, 94 | tmxMetrics["6xx_transactions"].ValueType, 95 | s.Value, 96 | "6xx", 97 | ) 98 | case "inuse_transactions": 99 | ch <- prometheus.MustNewConstMetric( 100 | tmxMetrics["inuse_transactions"].Desc, 101 | tmxMetrics["inuse_transactions"].ValueType, 102 | s.Value, 103 | ) 104 | case "active_transactions": 105 | ch <- prometheus.MustNewConstMetric( 106 | tmxMetrics["active_transactions"].Desc, 107 | tmxMetrics["active_transactions"].ValueType, 108 | s.Value, 109 | ) 110 | case "rpl_received": 111 | ch <- prometheus.MustNewConstMetric( 112 | tmxMetrics["rpl_received"].Desc, 113 | tmxMetrics["rpl_received"].ValueType, 114 | s.Value, 115 | "received", 116 | ) 117 | case "rpl_absorbed": 118 | ch <- prometheus.MustNewConstMetric( 119 | tmxMetrics["rpl_absorbed"].Desc, 120 | tmxMetrics["rpl_absorbed"].ValueType, 121 | s.Value, 122 | "absorbed", 123 | ) 124 | case "rpl_relayed": 125 | ch <- prometheus.MustNewConstMetric( 126 | tmxMetrics["rpl_relayed"].Desc, 127 | tmxMetrics["rpl_relayed"].ValueType, 128 | s.Value, 129 | "relayed", 130 | ) 131 | case "rpl_generated": 132 | ch <- prometheus.MustNewConstMetric( 133 | tmxMetrics["rpl_generated"].Desc, 134 | tmxMetrics["rpl_generated"].ValueType, 135 | s.Value, 136 | "generated", 137 | ) 138 | case "rpl_sent": 139 | ch <- prometheus.MustNewConstMetric( 140 | tmxMetrics["rpl_sent"].Desc, 141 | tmxMetrics["rpl_sent"].ValueType, 142 | s.Value, 143 | "sent", 144 | ) 145 | } 146 | } 147 | } 148 | } 149 | 150 | func tmxProcessorFunc(s map[string]opensips.Statistic) prometheus.Collector { 151 | return &tmxProcessor{ 152 | statistics: s, 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /processors/uri_processor.go: -------------------------------------------------------------------------------- 1 | package processors 2 | 3 | import ( 4 | "github.com/VoIPGRID/opensips_exporter/opensips" 5 | "github.com/prometheus/client_golang/prometheus" 6 | ) 7 | 8 | // uriProcessor metrics related to SIP URI processing. 9 | // doc: http://www.opensips.org/html/docs/modules/1.11.x/uri.html 10 | // src: https://github.com/OpenSIPS/opensips/blob/1.11/modules/uri/uri_mod.c#L191 11 | type uriProcessor struct { 12 | statistics map[string]opensips.Statistic 13 | } 14 | 15 | var uriLabelNames = []string{} 16 | var uriMetrics = map[string]metric{ 17 | "positive": newMetric("uri", "positive_checks", "Amount of positive URI checks.", uriLabelNames, prometheus.CounterValue), 18 | "negative_checks": newMetric("uri", "negative_checks", "Amount of negative URI checks.", uriLabelNames, prometheus.CounterValue), 19 | } 20 | 21 | func init() { 22 | for metric := range uriMetrics { 23 | OpensipsProcessors[metric] = uriProcessorFunc 24 | } 25 | OpensipsProcessors["uri:"] = uriProcessorFunc 26 | } 27 | 28 | // Describe implements prometheus.Collector. 29 | func (p uriProcessor) Describe(ch chan<- *prometheus.Desc) { 30 | for _, metric := range uriMetrics { 31 | ch <- metric.Desc 32 | } 33 | } 34 | 35 | // Collect implements prometheus.Collector. 36 | func (p uriProcessor) Collect(ch chan<- prometheus.Metric) { 37 | for _, s := range p.statistics { 38 | if s.Module == "uri" { 39 | switch s.Name { 40 | case "positive": 41 | ch <- prometheus.MustNewConstMetric( 42 | uriMetrics["positive"].Desc, 43 | uriMetrics["positive"].ValueType, 44 | s.Value, 45 | ) 46 | case "negative_checks": 47 | ch <- prometheus.MustNewConstMetric( 48 | uriMetrics["negative_checks"].Desc, 49 | uriMetrics["negative_checks"].ValueType, 50 | s.Value, 51 | ) 52 | } 53 | } 54 | } 55 | } 56 | 57 | func uriProcessorFunc(s map[string]opensips.Statistic) prometheus.Collector { 58 | return &uriProcessor{ 59 | statistics: s, 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /processors/usrloc_processor.go: -------------------------------------------------------------------------------- 1 | package processors 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/VoIPGRID/opensips_exporter/opensips" 7 | "github.com/prometheus/client_golang/prometheus" 8 | ) 9 | 10 | // usrlocProcessor provides metrics about User locations via registring. 11 | // doc: http://www.opensips.org/html/docs/modules/1.11.x/usrloc.html#idp5699792 12 | // src: Not clear 13 | type usrlocProcessor struct { 14 | statistics map[string]opensips.Statistic 15 | } 16 | 17 | type usrlocMetric struct { 18 | metric metric 19 | domain string 20 | } 21 | 22 | func init() { 23 | OpensipsProcessors["usrloc:"] = usrlocProcessorFunc 24 | OpensipsProcessors["contacts"] = usrlocProcessorFunc 25 | OpensipsProcessors["users"] = usrlocProcessorFunc 26 | OpensipsProcessors["expires"] = usrlocProcessorFunc 27 | } 28 | 29 | // Describe implements prometheus.Collector. 30 | func (p usrlocProcessor) Describe(ch chan<- *prometheus.Desc) { 31 | for _, m := range p.usrlocMetrics() { 32 | ch <- m.metric.Desc 33 | } 34 | } 35 | 36 | // Collect implements prometheus.Collector. 37 | func (p usrlocProcessor) Collect(ch chan<- prometheus.Metric) { 38 | for key, u := range p.usrlocMetrics() { 39 | if u.domain != "" { 40 | ch <- prometheus.MustNewConstMetric( 41 | u.metric.Desc, 42 | u.metric.ValueType, 43 | p.statistics[key].Value, 44 | u.domain, 45 | ) 46 | } else { 47 | ch <- prometheus.MustNewConstMetric( 48 | u.metric.Desc, 49 | u.metric.ValueType, 50 | p.statistics[key].Value, 51 | ) 52 | } 53 | 54 | } 55 | } 56 | 57 | func usrlocProcessorFunc(s map[string]opensips.Statistic) prometheus.Collector { 58 | return &usrlocProcessor{ 59 | statistics: s, 60 | } 61 | } 62 | 63 | func (p usrlocProcessor) usrlocMetrics() map[string]usrlocMetric { 64 | var metrics = map[string]usrlocMetric{} 65 | 66 | // Get all usrloc statistics 67 | var stats []opensips.Statistic 68 | for _, s := range p.statistics { 69 | if s.Module == "usrloc" { 70 | stats = append(stats, s) 71 | } 72 | } 73 | 74 | for _, s := range stats { 75 | split := strings.LastIndex(s.Name, "-") 76 | 77 | if split == -1 { 78 | continue 79 | } 80 | 81 | metricType := s.Name[split+1:] 82 | domain := s.Name[:split] 83 | 84 | switch metricType { 85 | case "users": 86 | metric := newMetric("usrloc", metricType, "Number of AOR existing in the USRLOC memory cache for that domain.", []string{"domain"}, prometheus.GaugeValue) 87 | metrics[s.Name] = usrlocMetric{ 88 | metric: metric, 89 | domain: domain, 90 | } 91 | case "contacts": 92 | metric := newMetric("usrloc", metricType, "Number of contacts existing in the USRLOC memory cache for that domain.", []string{"domain"}, prometheus.GaugeValue) 93 | metrics[s.Name] = usrlocMetric{ 94 | metric: metric, 95 | domain: domain, 96 | } 97 | case "expires": 98 | metric := newMetric("usrloc", metricType, "Total number of expired contacts for that domain.", []string{"domain"}, prometheus.GaugeValue) 99 | metrics[s.Name] = usrlocMetric{ 100 | metric: metric, 101 | domain: domain, 102 | } 103 | } 104 | } 105 | 106 | metrics["registered_users"] = usrlocMetric{ 107 | metric: newMetric("usrloc", "registered_users_total", " Total number of AOR existing in the USRLOC memory cache for all domains.", []string{}, prometheus.CounterValue), 108 | domain: "", 109 | } 110 | return metrics 111 | } 112 | -------------------------------------------------------------------------------- /run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | sed -i "s/RUN_OPENSIPS=no/RUN_OPENSIPS=yes/g" /etc/default/opensips 4 | sed -i "s/DAEMON=\/sbin\/opensips/DAEMON=\/usr\/sbin\/opensips/g" /etc/init.d/opensips 5 | 6 | HOST_IP=$(ip route get 8.8.8.8 | head -n +1 | tr -s " " | cut -d " " -f 7) 7 | sed -i "s/^listen=udp.*5060/listen=udp:${HOST_IP}:5060/g" /etc/opensips/opensips.cfg 8 | 9 | sed -i "s/^listen=udp.*5060/listen=udp:${HOST_IP}:5060/g" /etc/opensips/opensips.cfg 10 | 11 | sed -i "s/modparam(\"httpd\", \"ip\", \"127.0.0.1\")/modparam(\"httpd\", \"ip\", \"${HOST_IP}\")/g" /etc/opensips/opensips.cfg 12 | 13 | # skip syslog and run opensips at stderr 14 | /usr/sbin/opensips -FE 15 | --------------------------------------------------------------------------------