├── .github ├── dependabot.yml └── workflows │ ├── build.yml │ └── golangci-lint.yml ├── .gitignore ├── .promu.yml ├── LICENSE ├── MAINTAINERS.md ├── Makefile ├── Makefile.common ├── README.md ├── actions.go ├── actions_test.go ├── dyn_stat.go ├── dyn_stat_test.go ├── dynafile_cache.go ├── dynafile_cache_test.go ├── exporter.go ├── exporter_test.go ├── fixtures └── rsyslog-stats.log ├── forward.go ├── forward_test.go ├── go.mod ├── go.sum ├── input_imudp.go ├── input_imudp_test.go ├── inputs.go ├── inputs_test.go ├── kubernetes.go ├── kubernetes_test.go ├── main.go ├── omkafka.go ├── omkafka_test.go ├── point.go ├── point_test.go ├── pointstore.go ├── pointstore_test.go ├── queues.go ├── queues_test.go ├── resources.go ├── resources_test.go └── utils.go /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "gomod" 4 | directory: "/" 5 | schedule: 6 | interval: "monthly" 7 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: 4 | pull_request 5 | 6 | jobs: 7 | 8 | build: 9 | name: Build 10 | runs-on: ubuntu-latest 11 | steps: 12 | 13 | - name: Set up Go 14 | uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2 15 | with: 16 | go-version: 1.23 17 | id: go 18 | 19 | - name: Check out code into the Go module directory 20 | uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 21 | 22 | - name: Build 23 | run: make 24 | -------------------------------------------------------------------------------- /.github/workflows/golangci-lint.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # This action is synced from https://github.com/prometheus/prometheus 3 | name: golangci-lint 4 | on: 5 | push: 6 | paths: 7 | - "go.sum" 8 | - "go.mod" 9 | - "**.go" 10 | - "scripts/errcheck_excludes.txt" 11 | - ".github/workflows/golangci-lint.yml" 12 | - ".golangci.yml" 13 | pull_request: 14 | 15 | permissions: # added using https://github.com/step-security/secure-repo 16 | contents: read 17 | 18 | jobs: 19 | golangci: 20 | permissions: 21 | contents: read # for actions/checkout to fetch code 22 | pull-requests: read # for golangci/golangci-lint-action to fetch pull requests 23 | name: lint 24 | runs-on: ubuntu-latest 25 | steps: 26 | - name: Checkout repository 27 | uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 28 | - name: Install Go 29 | uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2 30 | with: 31 | go-version: 1.23.x 32 | - name: Install snmp_exporter/generator dependencies 33 | run: sudo apt-get update && sudo apt-get -y install libsnmp-dev 34 | if: github.repository == 'prometheus/snmp_exporter' 35 | - name: Lint 36 | uses: golangci/golangci-lint-action@aaa42aa0628b4ae2578232a66b541047968fac86 # v6.1.0 37 | with: 38 | args: --verbose 39 | version: v1.60.2 40 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | *.prof 25 | rsyslog_exporter 26 | 27 | dist 28 | -------------------------------------------------------------------------------- /.promu.yml: -------------------------------------------------------------------------------- 1 | go: 2 | # This must match .github/workflows/build.yml. 3 | version: 1.23 4 | repository: 5 | path: github.com/prometheus-community/rsyslog_exporter 6 | build: 7 | binaries: 8 | - name: rsyslog_exporter 9 | ldflags: | 10 | -X github.com/prometheus/common/version.Version={{.Version}} 11 | -X github.com/prometheus/common/version.Revision={{.Revision}} 12 | -X github.com/prometheus/common/version.Branch={{.Branch}} 13 | -X github.com/prometheus/common/version.BuildUser={{user}}@{{host}} 14 | -X github.com/prometheus/common/version.BuildDate={{date "20060102-15:04:05"}} 15 | tarball: 16 | files: 17 | - LICENSE 18 | - NOTICE 19 | -------------------------------------------------------------------------------- /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 | 203 | -------------------------------------------------------------------------------- /MAINTAINERS.md: -------------------------------------------------------------------------------- 1 | ## Maintainers 2 | 3 | * Antoine Leroyer [@aleroyer](https://github.com/aleroyer) 4 | 5 | ## Sponsor 6 | 7 | * Matthias Rampke 8 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Copyright 2024 The Prometheus Authors 2 | # Licensed under the Apache License, Version 2.0 (the "License"); 3 | # you may not use this file except in compliance with the License. 4 | # You may obtain a copy of the License at 5 | # 6 | # http://www.apache.org/licenses/LICENSE-2.0 7 | # 8 | # Unless required by applicable law or agreed to in writing, software 9 | # distributed under the License is distributed on an "AS IS" BASIS, 10 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # See the License for the specific language governing permissions and 12 | # limitations under the License. 13 | 14 | # Needs to be defined before including Makefile.common to auto-generate targets 15 | DOCKER_ARCHS ?= amd64 armv7 arm64 16 | DOCKER_REPO ?= prometheuscommunity 17 | 18 | include Makefile.common 19 | 20 | STATICCHECK_IGNORE = 21 | 22 | DOCKER_IMAGE_NAME ?= rsyslog-exporter 23 | -------------------------------------------------------------------------------- /Makefile.common: -------------------------------------------------------------------------------- 1 | # Copyright 2018 The Prometheus Authors 2 | # Licensed under the Apache License, Version 2.0 (the "License"); 3 | # you may not use this file except in compliance with the License. 4 | # You may obtain a copy of the License at 5 | # 6 | # http://www.apache.org/licenses/LICENSE-2.0 7 | # 8 | # Unless required by applicable law or agreed to in writing, software 9 | # distributed under the License is distributed on an "AS IS" BASIS, 10 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | # See the License for the specific language governing permissions and 12 | # limitations under the License. 13 | 14 | 15 | # A common Makefile that includes rules to be reused in different prometheus projects. 16 | # !!! Open PRs only against the prometheus/prometheus/Makefile.common repository! 17 | 18 | # Example usage : 19 | # Create the main Makefile in the root project directory. 20 | # include Makefile.common 21 | # customTarget: 22 | # @echo ">> Running customTarget" 23 | # 24 | 25 | # Ensure GOBIN is not set during build so that promu is installed to the correct path 26 | unexport GOBIN 27 | 28 | GO ?= go 29 | GOFMT ?= $(GO)fmt 30 | FIRST_GOPATH := $(firstword $(subst :, ,$(shell $(GO) env GOPATH))) 31 | GOOPTS ?= 32 | GOHOSTOS ?= $(shell $(GO) env GOHOSTOS) 33 | GOHOSTARCH ?= $(shell $(GO) env GOHOSTARCH) 34 | 35 | GO_VERSION ?= $(shell $(GO) version) 36 | GO_VERSION_NUMBER ?= $(word 3, $(GO_VERSION)) 37 | PRE_GO_111 ?= $(shell echo $(GO_VERSION_NUMBER) | grep -E 'go1\.(10|[0-9])\.') 38 | 39 | PROMU := $(FIRST_GOPATH)/bin/promu 40 | pkgs = ./... 41 | 42 | ifeq (arm, $(GOHOSTARCH)) 43 | GOHOSTARM ?= $(shell GOARM= $(GO) env GOARM) 44 | GO_BUILD_PLATFORM ?= $(GOHOSTOS)-$(GOHOSTARCH)v$(GOHOSTARM) 45 | else 46 | GO_BUILD_PLATFORM ?= $(GOHOSTOS)-$(GOHOSTARCH) 47 | endif 48 | 49 | GOTEST := $(GO) test 50 | GOTEST_DIR := 51 | ifneq ($(CIRCLE_JOB),) 52 | ifneq ($(shell command -v gotestsum 2> /dev/null),) 53 | GOTEST_DIR := test-results 54 | GOTEST := gotestsum --junitfile $(GOTEST_DIR)/unit-tests.xml -- 55 | endif 56 | endif 57 | 58 | PROMU_VERSION ?= 0.17.0 59 | PROMU_URL := https://github.com/prometheus/promu/releases/download/v$(PROMU_VERSION)/promu-$(PROMU_VERSION).$(GO_BUILD_PLATFORM).tar.gz 60 | 61 | SKIP_GOLANGCI_LINT := 62 | GOLANGCI_LINT := 63 | GOLANGCI_LINT_OPTS ?= 64 | GOLANGCI_LINT_VERSION ?= v1.60.2 65 | # golangci-lint only supports linux, darwin and windows platforms on i386/amd64/arm64. 66 | # windows isn't included here because of the path separator being different. 67 | ifeq ($(GOHOSTOS),$(filter $(GOHOSTOS),linux darwin)) 68 | ifeq ($(GOHOSTARCH),$(filter $(GOHOSTARCH),amd64 i386 arm64)) 69 | # If we're in CI and there is an Actions file, that means the linter 70 | # is being run in Actions, so we don't need to run it here. 71 | ifneq (,$(SKIP_GOLANGCI_LINT)) 72 | GOLANGCI_LINT := 73 | else ifeq (,$(CIRCLE_JOB)) 74 | GOLANGCI_LINT := $(FIRST_GOPATH)/bin/golangci-lint 75 | else ifeq (,$(wildcard .github/workflows/golangci-lint.yml)) 76 | GOLANGCI_LINT := $(FIRST_GOPATH)/bin/golangci-lint 77 | endif 78 | endif 79 | endif 80 | 81 | PREFIX ?= $(shell pwd) 82 | BIN_DIR ?= $(shell pwd) 83 | DOCKER_IMAGE_TAG ?= $(subst /,-,$(shell git rev-parse --abbrev-ref HEAD)) 84 | DOCKERFILE_PATH ?= ./Dockerfile 85 | DOCKERBUILD_CONTEXT ?= ./ 86 | DOCKER_REPO ?= prom 87 | 88 | DOCKER_ARCHS ?= amd64 89 | 90 | BUILD_DOCKER_ARCHS = $(addprefix common-docker-,$(DOCKER_ARCHS)) 91 | PUBLISH_DOCKER_ARCHS = $(addprefix common-docker-publish-,$(DOCKER_ARCHS)) 92 | TAG_DOCKER_ARCHS = $(addprefix common-docker-tag-latest-,$(DOCKER_ARCHS)) 93 | 94 | SANITIZED_DOCKER_IMAGE_TAG := $(subst +,-,$(DOCKER_IMAGE_TAG)) 95 | 96 | ifeq ($(GOHOSTARCH),amd64) 97 | ifeq ($(GOHOSTOS),$(filter $(GOHOSTOS),linux freebsd darwin windows)) 98 | # Only supported on amd64 99 | test-flags := -race 100 | endif 101 | endif 102 | 103 | # This rule is used to forward a target like "build" to "common-build". This 104 | # allows a new "build" target to be defined in a Makefile which includes this 105 | # one and override "common-build" without override warnings. 106 | %: common-% ; 107 | 108 | .PHONY: common-all 109 | common-all: precheck style check_license lint yamllint unused build test 110 | 111 | .PHONY: common-style 112 | common-style: 113 | @echo ">> checking code style" 114 | @fmtRes=$$($(GOFMT) -d $$(find . -path ./vendor -prune -o -name '*.go' -print)); \ 115 | if [ -n "$${fmtRes}" ]; then \ 116 | echo "gofmt checking failed!"; echo "$${fmtRes}"; echo; \ 117 | echo "Please ensure you are using $$($(GO) version) for formatting code."; \ 118 | exit 1; \ 119 | fi 120 | 121 | .PHONY: common-check_license 122 | common-check_license: 123 | @echo ">> checking license header" 124 | @licRes=$$(for file in $$(find . -type f -iname '*.go' ! -path './vendor/*') ; do \ 125 | awk 'NR<=3' $$file | grep -Eq "(Copyright|generated|GENERATED)" || echo $$file; \ 126 | done); \ 127 | if [ -n "$${licRes}" ]; then \ 128 | echo "license header checking failed:"; echo "$${licRes}"; \ 129 | exit 1; \ 130 | fi 131 | 132 | .PHONY: common-deps 133 | common-deps: 134 | @echo ">> getting dependencies" 135 | $(GO) mod download 136 | 137 | .PHONY: update-go-deps 138 | update-go-deps: 139 | @echo ">> updating Go dependencies" 140 | @for m in $$($(GO) list -mod=readonly -m -f '{{ if and (not .Indirect) (not .Main)}}{{.Path}}{{end}}' all); do \ 141 | $(GO) get -d $$m; \ 142 | done 143 | $(GO) mod tidy 144 | 145 | .PHONY: common-test-short 146 | common-test-short: $(GOTEST_DIR) 147 | @echo ">> running short tests" 148 | $(GOTEST) -short $(GOOPTS) $(pkgs) 149 | 150 | .PHONY: common-test 151 | common-test: $(GOTEST_DIR) 152 | @echo ">> running all tests" 153 | $(GOTEST) $(test-flags) $(GOOPTS) $(pkgs) 154 | 155 | $(GOTEST_DIR): 156 | @mkdir -p $@ 157 | 158 | .PHONY: common-format 159 | common-format: 160 | @echo ">> formatting code" 161 | $(GO) fmt $(pkgs) 162 | 163 | .PHONY: common-vet 164 | common-vet: 165 | @echo ">> vetting code" 166 | $(GO) vet $(GOOPTS) $(pkgs) 167 | 168 | .PHONY: common-lint 169 | common-lint: $(GOLANGCI_LINT) 170 | ifdef GOLANGCI_LINT 171 | @echo ">> running golangci-lint" 172 | $(GOLANGCI_LINT) run $(GOLANGCI_LINT_OPTS) $(pkgs) 173 | endif 174 | 175 | .PHONY: common-lint-fix 176 | common-lint-fix: $(GOLANGCI_LINT) 177 | ifdef GOLANGCI_LINT 178 | @echo ">> running golangci-lint fix" 179 | $(GOLANGCI_LINT) run --fix $(GOLANGCI_LINT_OPTS) $(pkgs) 180 | endif 181 | 182 | .PHONY: common-yamllint 183 | common-yamllint: 184 | @echo ">> running yamllint on all YAML files in the repository" 185 | ifeq (, $(shell command -v yamllint 2> /dev/null)) 186 | @echo "yamllint not installed so skipping" 187 | else 188 | yamllint . 189 | endif 190 | 191 | # For backward-compatibility. 192 | .PHONY: common-staticcheck 193 | common-staticcheck: lint 194 | 195 | .PHONY: common-unused 196 | common-unused: 197 | @echo ">> running check for unused/missing packages in go.mod" 198 | $(GO) mod tidy 199 | @git diff --exit-code -- go.sum go.mod 200 | 201 | .PHONY: common-build 202 | common-build: promu 203 | @echo ">> building binaries" 204 | $(PROMU) build --prefix $(PREFIX) $(PROMU_BINARIES) 205 | 206 | .PHONY: common-tarball 207 | common-tarball: promu 208 | @echo ">> building release tarball" 209 | $(PROMU) tarball --prefix $(PREFIX) $(BIN_DIR) 210 | 211 | .PHONY: common-docker-repo-name 212 | common-docker-repo-name: 213 | @echo "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)" 214 | 215 | .PHONY: common-docker $(BUILD_DOCKER_ARCHS) 216 | common-docker: $(BUILD_DOCKER_ARCHS) 217 | $(BUILD_DOCKER_ARCHS): common-docker-%: 218 | docker build -t "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(SANITIZED_DOCKER_IMAGE_TAG)" \ 219 | -f $(DOCKERFILE_PATH) \ 220 | --build-arg ARCH="$*" \ 221 | --build-arg OS="linux" \ 222 | $(DOCKERBUILD_CONTEXT) 223 | 224 | .PHONY: common-docker-publish $(PUBLISH_DOCKER_ARCHS) 225 | common-docker-publish: $(PUBLISH_DOCKER_ARCHS) 226 | $(PUBLISH_DOCKER_ARCHS): common-docker-publish-%: 227 | docker push "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(SANITIZED_DOCKER_IMAGE_TAG)" 228 | 229 | DOCKER_MAJOR_VERSION_TAG = $(firstword $(subst ., ,$(shell cat VERSION))) 230 | .PHONY: common-docker-tag-latest $(TAG_DOCKER_ARCHS) 231 | common-docker-tag-latest: $(TAG_DOCKER_ARCHS) 232 | $(TAG_DOCKER_ARCHS): common-docker-tag-latest-%: 233 | docker tag "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(SANITIZED_DOCKER_IMAGE_TAG)" "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:latest" 234 | docker tag "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:$(SANITIZED_DOCKER_IMAGE_TAG)" "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$*:v$(DOCKER_MAJOR_VERSION_TAG)" 235 | 236 | .PHONY: common-docker-manifest 237 | common-docker-manifest: 238 | DOCKER_CLI_EXPERIMENTAL=enabled docker manifest create -a "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME):$(SANITIZED_DOCKER_IMAGE_TAG)" $(foreach ARCH,$(DOCKER_ARCHS),$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME)-linux-$(ARCH):$(SANITIZED_DOCKER_IMAGE_TAG)) 239 | DOCKER_CLI_EXPERIMENTAL=enabled docker manifest push "$(DOCKER_REPO)/$(DOCKER_IMAGE_NAME):$(SANITIZED_DOCKER_IMAGE_TAG)" 240 | 241 | .PHONY: promu 242 | promu: $(PROMU) 243 | 244 | $(PROMU): 245 | $(eval PROMU_TMP := $(shell mktemp -d)) 246 | curl -s -L $(PROMU_URL) | tar -xvzf - -C $(PROMU_TMP) 247 | mkdir -p $(FIRST_GOPATH)/bin 248 | cp $(PROMU_TMP)/promu-$(PROMU_VERSION).$(GO_BUILD_PLATFORM)/promu $(FIRST_GOPATH)/bin/promu 249 | rm -r $(PROMU_TMP) 250 | 251 | .PHONY: proto 252 | proto: 253 | @echo ">> generating code from proto files" 254 | @./scripts/genproto.sh 255 | 256 | ifdef GOLANGCI_LINT 257 | $(GOLANGCI_LINT): 258 | mkdir -p $(FIRST_GOPATH)/bin 259 | curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/$(GOLANGCI_LINT_VERSION)/install.sh \ 260 | | sed -e '/install -d/d' \ 261 | | sh -s -- -b $(FIRST_GOPATH)/bin $(GOLANGCI_LINT_VERSION) 262 | endif 263 | 264 | .PHONY: precheck 265 | precheck:: 266 | 267 | define PRECHECK_COMMAND_template = 268 | precheck:: $(1)_precheck 269 | 270 | PRECHECK_COMMAND_$(1) ?= $(1) $$(strip $$(PRECHECK_OPTIONS_$(1))) 271 | .PHONY: $(1)_precheck 272 | $(1)_precheck: 273 | @if ! $$(PRECHECK_COMMAND_$(1)) 1>/dev/null 2>&1; then \ 274 | echo "Execution of '$$(PRECHECK_COMMAND_$(1))' command failed. Is $(1) installed?"; \ 275 | exit 1; \ 276 | fi 277 | endef 278 | 279 | govulncheck: install-govulncheck 280 | govulncheck ./... 281 | 282 | install-govulncheck: 283 | command -v govulncheck > /dev/null || go install golang.org/x/vuln/cmd/govulncheck@latest 284 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rsyslog_exporter [![Build Status](https://travis-ci.org/digitalocean/rsyslog_exporter.svg?branch=master)](https://travis-ci.org/digitalocean/rsyslog_exporter) 2 | 3 | A [prometheus](http://prometheus.io/) exporter for [rsyslog](http://rsyslog.com). It accepts rsyslog [impstats](http://www.rsyslog.com/doc/master/configuration/modules/impstats.html) metrics in JSON format over stdin via the rsyslog [omprog](http://www.rsyslog.com/doc/v8-stable/configuration/modules/omprog.html) plugin and transforms and exposes them for consumption by Prometheus. 4 | 5 | ## Rsyslog Configuration 6 | Configure rsyslog to push JSON formatted stats via omprog: 7 | ``` 8 | module(load="omprog") 9 | 10 | module( 11 | load="impstats" 12 | interval="10" 13 | format="json" 14 | resetCounters="off" 15 | ruleset="process_stats" 16 | ) 17 | 18 | ruleset(name="process_stats") { 19 | action( 20 | type="omprog" 21 | name="to_exporter" 22 | binary="/usr/local/bin/rsyslog_exporter [--tls.server-crt=/path/to/tls.crt --tls.server-key=/path/to/tls.key]" 23 | ) 24 | } 25 | ``` 26 | 27 | The exporter itself logs back via syslog, this cannot be configured at the moment. 28 | 29 | ## Command Line Switches 30 | * `web.listen-address` - default `:9104` - port to listen to (NOTE: the leading 31 | `:` is required for `http.ListenAndServe`) 32 | * `web.telemetry-path` - default `/metrics` - path from which to serve Prometheus metrics 33 | * `tls.server-crt` - default `""` - PEM encoded file containing the server certificate and 34 | the CA certificate for use with `http.ListenAndServeTLS` 35 | * `tls.server-key` - default `""` - PEM encoded file containing the unencrypted 36 | server key for use with `tls.server-crt` 37 | 38 | If you want the exporter to listen for TLS (`https`) you must specify both 39 | `tls.server-crt` and `tls.server-key`. 40 | 41 | ## Provided Metrics 42 | The following metrics provided by the rsyslog [impstats](https://www.rsyslog.com/doc/master/configuration/modules/impstats.html) module are tracked by rsyslog_exporter: 43 | 44 | ### Actions 45 | Action objects describe what is to be done with a message, and are implemented via output modules. 46 | For each action object, the following metrics are provided: 47 | 48 | * processed - messages processed by this action 49 | * failed - number of messages this action failed to process 50 | * suspended - number of times this action was suspended 51 | * suspended_duration - amount of time this action has spent in a suspended state 52 | * resumed - number of times this action has resumed from a suspended state 53 | 54 | ### Inputs 55 | Input objects describe message input sources. 56 | For each input object, the following metrics are provided: 57 | 58 | * submitted - messages submitted to this input 59 | 60 | ### Queues 61 | Queues in rsyslog are used for the main message queue and for actions. Additionally, each ruleset 62 | in an rsyslog configuration may optionally have its own separate main queue. For each queue, 63 | the following metrics are provided: 64 | 65 | * size - messages currently in queue 66 | * enqueued - total messages enqueued during lifetime of queue 67 | * full - number of times the queue was full 68 | * discarded_full - number of times messages were discarded due to the queue being full 69 | * discarded_not_full - number of times messages discarded but queue was not full 70 | * max_queue_size - maximum size the queue reached during its lifetime 71 | 72 | ### Resources 73 | Rsyslog tracks how it uses system resources and provides the following metrics: 74 | 75 | * utime - user time used in microseconds 76 | * stime - system time used in microseconds 77 | * maxrss - maximum resident set size 78 | * minflt - total number of minor faults 79 | * majflt - total number of major faults 80 | * inblock - number of filesystem input operations 81 | * oublock - number of filesystem output operations 82 | * nvcsw - number of voluntary context switches 83 | * nivcsw - number of involuntary context switches 84 | 85 | ### Dynafile Cache 86 | The [omfile](https://www.rsyslog.com/rsyslog-statistic-counter-plugin-omfile/) module can generate 87 | file names from a template. A cache of recent filehandles can be maintained, whose sizing can 88 | impact performance considerably. The module provides the following metrics: 89 | 90 | * requests - number of requests made to obtain a dynafile 91 | * level0 - number of requests for the current active file 92 | * missed - number of cache misses 93 | * evicted - number of times a file needed to be evicted from cache 94 | * maxused - maximum number of cache entries ever used 95 | * closetimeouts - number of times a file was closed due to timeout settings 96 | 97 | ### Dynamic Stats 98 | Rsyslog allows the user to define their own stats namespaces and increment counters within these 99 | buckets using Rainerscript function calls. 100 | 101 | These are exported as counters with the metric name identifying the bucket, and a label value 102 | matching the name of the counter (the label name will always be "counter"). As well as custom 103 | metrics, a "global" dynstats namespace is also published with some additional bookeeping counters. 104 | 105 | See the [dyn_stats](https://www.rsyslog.com/doc/master/configuration/dyn_stats.html) 106 | documentation for more information. 107 | 108 | ### IMUDP Workerthread stats 109 | The [imudp](https://www.rsyslog.com/rsyslog-statistic-counter-plugin-imudp/) module can be configured 110 | to run on multiple worker threads and the following metrics are returned: 111 | 112 | * input_called_recvmmsg - Number of recvmmsg called 113 | * input_called_recvmsg -Number of recvmmsg called 114 | * input_received - Messages received 115 | -------------------------------------------------------------------------------- /actions.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Prometheus Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package main 15 | 16 | import ( 17 | "encoding/json" 18 | "fmt" 19 | ) 20 | 21 | type action struct { 22 | Name string `json:"name"` 23 | Processed int64 `json:"processed"` 24 | Failed int64 `json:"failed"` 25 | Suspended int64 `json:"suspended"` 26 | SuspendedDuration int64 `json:"suspended.duration"` 27 | Resumed int64 `json:"resumed"` 28 | } 29 | 30 | func newActionFromJSON(b []byte) (*action, error) { 31 | var pstat action 32 | err := json.Unmarshal(b, &pstat) 33 | if err != nil { 34 | return nil, fmt.Errorf("failed to decode action stat `%v`: %v", string(b), err) 35 | } 36 | return &pstat, nil 37 | } 38 | 39 | func (a *action) toPoints() []*point { 40 | points := make([]*point, 5) 41 | 42 | points[0] = &point{ 43 | Name: "action_processed", 44 | Type: counter, 45 | Value: a.Processed, 46 | Description: "messages processed", 47 | LabelName: "action", 48 | LabelValue: a.Name, 49 | } 50 | 51 | points[1] = &point{ 52 | Name: "action_failed", 53 | Type: counter, 54 | Value: a.Failed, 55 | Description: "messages failed", 56 | LabelName: "action", 57 | LabelValue: a.Name, 58 | } 59 | 60 | points[2] = &point{ 61 | Name: "action_suspended", 62 | Type: counter, 63 | Value: a.Suspended, 64 | Description: "times suspended", 65 | LabelName: "action", 66 | LabelValue: a.Name, 67 | } 68 | 69 | points[3] = &point{ 70 | Name: "action_suspended_duration", 71 | Type: counter, 72 | Value: a.SuspendedDuration, 73 | Description: "time spent suspended", 74 | LabelName: "action", 75 | LabelValue: a.Name, 76 | } 77 | 78 | points[4] = &point{ 79 | Name: "action_resumed", 80 | Type: counter, 81 | Value: a.Resumed, 82 | Description: "times resumed", 83 | LabelName: "action", 84 | LabelValue: a.Name, 85 | } 86 | 87 | return points 88 | } 89 | -------------------------------------------------------------------------------- /actions_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Prometheus Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package main 15 | 16 | import "testing" 17 | 18 | var ( 19 | actionLog = []byte(`{"name":"test_action","processed":100000,"failed":2,"suspended":1,"suspended.duration":1000,"resumed":1}`) 20 | ) 21 | 22 | func TestNewActionFromJSON(t *testing.T) { 23 | logType := getStatType(actionLog) 24 | if logType != rsyslogAction { 25 | t.Errorf("detected pstat type should be %d but is %d", rsyslogAction, logType) 26 | } 27 | 28 | pstat, err := newActionFromJSON([]byte(actionLog)) 29 | if err != nil { 30 | t.Fatalf("expected parsing action not to fail, got: %v", err) 31 | } 32 | 33 | if want, got := "test_action", pstat.Name; want != got { 34 | t.Errorf("wanted '%s', got '%s'", want, got) 35 | } 36 | 37 | if want, got := int64(100000), pstat.Processed; want != got { 38 | t.Errorf("wanted '%d', got '%d'", want, got) 39 | } 40 | 41 | if want, got := int64(2), pstat.Failed; want != got { 42 | t.Errorf("wanted '%d', got '%d'", want, got) 43 | } 44 | 45 | if want, got := int64(1), pstat.Suspended; want != got { 46 | t.Errorf("wanted '%d', got '%d'", want, got) 47 | } 48 | 49 | if want, got := int64(1000), pstat.SuspendedDuration; want != got { 50 | t.Errorf("wanted '%d', got '%d'", want, got) 51 | } 52 | 53 | if want, got := int64(1), pstat.Resumed; want != got { 54 | t.Errorf("wanted '%d', got '%d'", want, got) 55 | } 56 | } 57 | 58 | func TestActionToPoints(t *testing.T) { 59 | pstat, err := newActionFromJSON([]byte(actionLog)) 60 | if err != nil { 61 | t.Fatalf("expected parsing action not to fail, got: %v", err) 62 | } 63 | points := pstat.toPoints() 64 | 65 | point := points[0] 66 | if want, got := "action_processed", point.Name; want != got { 67 | t.Errorf("wanted '%s', got '%s'", want, got) 68 | } 69 | 70 | if want, got := int64(100000), point.Value; want != got { 71 | t.Errorf("wanted '%d', got '%d'", want, got) 72 | } 73 | 74 | if want, got := counter, point.Type; want != got { 75 | t.Errorf("wanted '%d', got '%d'", want, got) 76 | } 77 | 78 | if want, got := "test_action", point.LabelValue; want != got { 79 | t.Errorf("wanted '%s', got '%s'", want, got) 80 | } 81 | 82 | point = points[1] 83 | if want, got := "action_failed", point.Name; want != got { 84 | t.Errorf("wanted '%s', got '%s'", want, got) 85 | } 86 | 87 | if want, got := int64(2), point.Value; want != got { 88 | t.Errorf("wanted '%d', got '%d'", want, got) 89 | } 90 | 91 | if want, got := counter, point.Type; want != got { 92 | t.Errorf("wanted '%d', got '%d'", want, got) 93 | } 94 | 95 | if want, got := "test_action", point.LabelValue; want != got { 96 | t.Errorf("wanted '%s', got '%s'", want, got) 97 | } 98 | 99 | point = points[2] 100 | if want, got := "action_suspended", point.Name; want != got { 101 | t.Errorf("wanted '%s', got '%s'", want, got) 102 | } 103 | 104 | if want, got := int64(1), point.Value; want != got { 105 | t.Errorf("wanted '%d', got '%d'", want, got) 106 | } 107 | 108 | if want, got := counter, point.Type; want != got { 109 | t.Errorf("wanted '%d', got '%d'", want, got) 110 | } 111 | 112 | if want, got := "test_action", point.LabelValue; want != got { 113 | t.Errorf("wanted '%s', got '%s'", want, got) 114 | } 115 | 116 | point = points[3] 117 | if want, got := "action_suspended_duration", point.Name; want != got { 118 | t.Errorf("wanted '%s', got '%s'", want, got) 119 | } 120 | 121 | if want, got := int64(1000), point.Value; want != got { 122 | t.Errorf("wanted '%d', got '%d'", want, got) 123 | } 124 | 125 | if want, got := counter, point.Type; want != got { 126 | t.Errorf("wanted '%d', got '%d'", want, got) 127 | } 128 | 129 | if want, got := "test_action", point.LabelValue; want != got { 130 | t.Errorf("wanted '%s', got '%s'", want, got) 131 | } 132 | 133 | point = points[4] 134 | if want, got := "action_resumed", point.Name; want != got { 135 | t.Errorf("wanted '%s', got '%s'", want, got) 136 | } 137 | 138 | if want, got := int64(1), point.Value; want != got { 139 | t.Errorf("wanted '%d', got '%d'", want, got) 140 | } 141 | 142 | if want, got := counter, point.Type; want != got { 143 | t.Errorf("wanted '%d', got '%d'", want, got) 144 | } 145 | 146 | if want, got := "test_action", point.LabelValue; want != got { 147 | t.Errorf("wanted '%s', got '%s'", want, got) 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /dyn_stat.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Prometheus Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package main 15 | 16 | import ( 17 | "encoding/json" 18 | "fmt" 19 | ) 20 | 21 | type dynStat struct { 22 | Name string `json:"name"` 23 | Origin string `json:"origin"` 24 | Values map[string]int64 `json:"values"` 25 | } 26 | 27 | func newDynStatFromJSON(b []byte) (*dynStat, error) { 28 | var pstat dynStat 29 | err := json.Unmarshal(b, &pstat) 30 | if err != nil { 31 | return nil, fmt.Errorf("error decoding values stat `%v`: %v", string(b), err) 32 | } 33 | return &pstat, nil 34 | } 35 | 36 | func (i *dynStat) toPoints() []*point { 37 | points := make([]*point, 0, len(i.Values)) 38 | 39 | for name, value := range i.Values { 40 | points = append(points, &point{ 41 | Name: fmt.Sprintf("dynstat_%s", i.Name), 42 | Type: counter, 43 | Value: value, 44 | Description: fmt.Sprintf("dynamic statistic bucket %s", i.Name), 45 | LabelName: "counter", 46 | LabelValue: name, 47 | }) 48 | } 49 | 50 | return points 51 | } 52 | -------------------------------------------------------------------------------- /dyn_stat_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Prometheus Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package main 15 | 16 | import ( 17 | "reflect" 18 | "testing" 19 | ) 20 | 21 | func TestGetDynStat(t *testing.T) { 22 | log := []byte(`{ "name": "global", "origin": "dynstats", "values": { "msg_per_host.ops_overflow": 1, "msg_per_host.new_metric_add": 3, "msg_per_host.no_metric": 0, "msg_per_host.metrics_purged": 0, "msg_per_host.ops_ignored": 0 } }`) 23 | values := map[string]int64{ 24 | "msg_per_host.ops_overflow": 1, 25 | "msg_per_host.new_metric_add": 3, 26 | "msg_per_host.no_metric": 0, 27 | "msg_per_host.metrics_purged": 0, 28 | "msg_per_host.ops_ignored": 0, 29 | } 30 | 31 | if want, got := rsyslogDynStat, getStatType(log); want != got { 32 | t.Errorf("detected pstat type should be %d but is %d", want, got) 33 | } 34 | 35 | pstat, err := newDynStatFromJSON(log) 36 | if err != nil { 37 | t.Fatalf("expected parsing dynamic stat not to fail, got: %v", err) 38 | } 39 | 40 | if want, got := "global", pstat.Name; want != got { 41 | t.Errorf("invalid name, want '%s', got '%s'", want, got) 42 | } 43 | 44 | if want, got := values, pstat.Values; !reflect.DeepEqual(want, got) { 45 | t.Errorf("unexpected values, want: %+v got: %+v", want, got) 46 | } 47 | } 48 | 49 | func TestDynStatToPoints(t *testing.T) { 50 | log := []byte(`{ "name": "global", "origin": "dynstats", "values": { "msg_per_host.ops_overflow": 1, "msg_per_host.new_metric_add": 3, "msg_per_host.no_metric": 0, "msg_per_host.metrics_purged": 0, "msg_per_host.ops_ignored": 0 } }`) 51 | wants := map[string]point{ 52 | "msg_per_host.ops_overflow": point{ 53 | Name: "dynstat_global", 54 | Type: counter, 55 | Value: 1, 56 | Description: "dynamic statistic bucket global", 57 | LabelName: "counter", 58 | LabelValue: "msg_per_host.ops_overflow", 59 | }, 60 | "msg_per_host.new_metric_add": point{ 61 | Name: "dynstat_global", 62 | Type: counter, 63 | Value: 3, 64 | Description: "dynamic statistic bucket global", 65 | LabelName: "counter", 66 | LabelValue: "msg_per_host.new_metric_add", 67 | }, 68 | "msg_per_host.no_metric": point{ 69 | Name: "dynstat_global", 70 | Type: counter, 71 | Value: 0, 72 | Description: "dynamic statistic bucket global", 73 | LabelName: "counter", 74 | LabelValue: "msg_per_host.no_metric", 75 | }, 76 | "msg_per_host.metrics_purged": point{ 77 | Name: "dynstat_global", 78 | Type: counter, 79 | Value: 0, 80 | Description: "dynamic statistic bucket global", 81 | LabelName: "counter", 82 | LabelValue: "msg_per_host.metrics_purged", 83 | }, 84 | "msg_per_host.ops_ignored": point{ 85 | Name: "dynstat_global", 86 | Type: counter, 87 | Value: 0, 88 | Description: "dynamic statistic bucket global", 89 | LabelName: "counter", 90 | LabelValue: "msg_per_host.ops_ignored", 91 | }, 92 | } 93 | 94 | seen := map[string]bool{} 95 | for name := range wants { 96 | seen[name] = false 97 | } 98 | 99 | pstat, err := newDynStatFromJSON(log) 100 | if err != nil { 101 | t.Fatalf("expected parsing dyn stat not to fail, got: %v", err) 102 | } 103 | 104 | points := pstat.toPoints() 105 | for _, got := range points { 106 | key := got.LabelValue 107 | want, ok := wants[key] 108 | if !ok { 109 | t.Errorf("unexpected point, got: %+v", got) 110 | continue 111 | } 112 | 113 | if !reflect.DeepEqual(want, *got) { 114 | t.Errorf("expected point to be %+v, got %+v", want, got) 115 | } 116 | 117 | if seen[key] { 118 | t.Errorf("point seen multiple times: %+v", got) 119 | } 120 | seen[key] = true 121 | } 122 | 123 | for name, ok := range seen { 124 | if !ok { 125 | t.Errorf("expected to see point with key %s, but did not", name) 126 | } 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /dynafile_cache.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Prometheus Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package main 15 | 16 | import ( 17 | "encoding/json" 18 | "fmt" 19 | "strings" 20 | ) 21 | 22 | type dfcStat struct { 23 | Name string `json:"name"` 24 | Origin string `json:"origin"` 25 | Requests int64 `json:"requests"` 26 | Level0 int64 `json:"level0"` 27 | Missed int64 `json:"missed"` 28 | Evicted int64 `json:"evicted"` 29 | MaxUsed int64 `json:"maxused"` 30 | CloseTimeouts int64 `json:"closetimeouts"` 31 | } 32 | 33 | func newDynafileCacheFromJSON(b []byte) (*dfcStat, error) { 34 | var pstat dfcStat 35 | err := json.Unmarshal(b, &pstat) 36 | if err != nil { 37 | return nil, fmt.Errorf("error decoding dynafile cache stat `%v`: %v", string(b), err) 38 | } 39 | pstat.Name = strings.TrimPrefix(pstat.Name, "dynafile cache ") 40 | return &pstat, nil 41 | } 42 | 43 | func (d *dfcStat) toPoints() []*point { 44 | points := make([]*point, 6) 45 | 46 | points[0] = &point{ 47 | Name: "dynafile_cache_requests", 48 | Type: counter, 49 | Value: d.Requests, 50 | Description: "number of requests made to obtain a dynafile", 51 | LabelName: "cache", 52 | LabelValue: d.Name, 53 | } 54 | points[1] = &point{ 55 | Name: "dynafile_cache_level0", 56 | Type: counter, 57 | Value: d.Level0, 58 | Description: "number of requests for the current active file", 59 | LabelName: "cache", 60 | LabelValue: d.Name, 61 | } 62 | points[2] = &point{ 63 | Name: "dynafile_cache_missed", 64 | Type: counter, 65 | Value: d.Missed, 66 | Description: "number of cache misses", 67 | LabelName: "cache", 68 | LabelValue: d.Name, 69 | } 70 | points[3] = &point{ 71 | Name: "dynafile_cache_evicted", 72 | Type: counter, 73 | Value: d.Evicted, 74 | Description: "number of times a file needed to be evicted from cache", 75 | LabelName: "cache", 76 | LabelValue: d.Name, 77 | } 78 | points[4] = &point{ 79 | Name: "dynafile_cache_maxused", 80 | Type: counter, 81 | Value: d.MaxUsed, 82 | Description: "maximum number of cache entries ever used", 83 | LabelName: "cache", 84 | LabelValue: d.Name, 85 | } 86 | points[5] = &point{ 87 | Name: "dynafile_cache_closetimeouts", 88 | Type: counter, 89 | Value: d.CloseTimeouts, 90 | Description: "number of times a file was closed due to timeout settings", 91 | LabelName: "cache", 92 | LabelValue: d.Name, 93 | } 94 | 95 | return points 96 | } 97 | -------------------------------------------------------------------------------- /dynafile_cache_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Prometheus Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package main 15 | 16 | import ( 17 | "reflect" 18 | "testing" 19 | ) 20 | 21 | var ( 22 | dynafileCacheLog = []byte(`{ "name": "dynafile cache cluster", "origin": "omfile", "requests": 1783254, "level0": 1470906, "missed": 2625, "evicted": 2525, "maxused": 100, "closetimeouts": 10 }`) 23 | ) 24 | 25 | func TestNewDynafileCacheFromJSON(t *testing.T) { 26 | logType := getStatType(dynafileCacheLog) 27 | if logType != rsyslogDynafileCache { 28 | t.Errorf("detected pstat type should be %d but is %d", rsyslogDynafileCache, logType) 29 | } 30 | 31 | pstat, err := newDynafileCacheFromJSON([]byte(dynafileCacheLog)) 32 | if err != nil { 33 | t.Fatalf("expected parsing dynafile cache stat not to fail, got: %v", err) 34 | } 35 | 36 | if want, got := "cluster", pstat.Name; want != got { 37 | t.Errorf("want '%s', got '%s'", want, got) 38 | } 39 | 40 | if want, got := int64(1783254), pstat.Requests; want != got { 41 | t.Errorf("want '%d', got '%d'", want, got) 42 | } 43 | 44 | if want, got := int64(1470906), pstat.Level0; want != got { 45 | t.Errorf("want '%d', got '%d'", want, got) 46 | } 47 | 48 | if want, got := int64(2625), pstat.Missed; want != got { 49 | t.Errorf("want '%d', got '%d'", want, got) 50 | } 51 | 52 | if want, got := int64(2525), pstat.Evicted; want != got { 53 | t.Errorf("want '%d', got '%d'", want, got) 54 | } 55 | 56 | if want, got := int64(100), pstat.MaxUsed; want != got { 57 | t.Errorf("want '%d', got '%d'", want, got) 58 | } 59 | 60 | if want, got := int64(10), pstat.CloseTimeouts; want != got { 61 | t.Errorf("want '%d', got '%d'", want, got) 62 | } 63 | } 64 | 65 | func TestDynafileCacheToPoints(t *testing.T) { 66 | 67 | wants := map[string]point{ 68 | "dynafile_cache_requests": point{ 69 | Name: "dynafile_cache_requests", 70 | Type: counter, 71 | Value: 1783254, 72 | Description: "number of requests made to obtain a dynafile", 73 | LabelName: "cache", 74 | LabelValue: "cluster", 75 | }, 76 | "dynafile_cache_level0": point{ 77 | Name: "dynafile_cache_level0", 78 | Type: counter, 79 | Value: 1470906, 80 | Description: "number of requests for the current active file", 81 | LabelName: "cache", 82 | 83 | LabelValue: "cluster", 84 | }, 85 | "dynafile_cache_missed": point{ 86 | Name: "dynafile_cache_missed", 87 | Type: counter, 88 | Value: 2625, 89 | Description: "number of cache misses", 90 | LabelName: "cache", 91 | LabelValue: "cluster", 92 | }, 93 | "dynafile_cache_evicted": point{ 94 | Name: "dynafile_cache_evicted", 95 | Type: counter, 96 | Value: 2525, 97 | Description: "number of times a file needed to be evicted from cache", 98 | LabelName: "cache", 99 | LabelValue: "cluster", 100 | }, 101 | "dynafile_cache_maxused": point{ 102 | Name: "dynafile_cache_maxused", 103 | Type: counter, 104 | Value: 100, 105 | Description: "maximum number of cache entries ever used", 106 | LabelName: "cache", 107 | LabelValue: "cluster", 108 | }, 109 | "dynafile_cache_closetimeouts": point{ 110 | Name: "dynafile_cache_closetimeouts", 111 | Type: counter, 112 | Value: 10, 113 | Description: "number of times a file was closed due to timeout settings", 114 | LabelName: "cache", 115 | LabelValue: "cluster", 116 | }, 117 | } 118 | 119 | seen := map[string]bool{} 120 | for name := range wants { 121 | seen[name] = false 122 | } 123 | 124 | pstat, err := newDynafileCacheFromJSON(dynafileCacheLog) 125 | if err != nil { 126 | t.Fatalf("expected parsing dynafile cache stat not to fail, got: %v", err) 127 | } 128 | 129 | points := pstat.toPoints() 130 | for _, got := range points { 131 | want, ok := wants[got.Name] 132 | if !ok { 133 | t.Errorf("unexpected point, got: %+v", got) 134 | continue 135 | } 136 | 137 | if !reflect.DeepEqual(want, *got) { 138 | t.Errorf("expected point to be %+v, got %+v", want, got) 139 | } 140 | 141 | if seen[got.Name] { 142 | t.Errorf("point seen multiple times: %+v", got) 143 | } 144 | seen[got.Name] = true 145 | } 146 | 147 | for name, ok := range seen { 148 | if !ok { 149 | t.Errorf("expected to see point with key %s, but did not", name) 150 | } 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /exporter.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Prometheus Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package main 15 | 16 | import ( 17 | "bufio" 18 | "bytes" 19 | "fmt" 20 | "log" 21 | "os" 22 | "sync" 23 | 24 | "github.com/prometheus/client_golang/prometheus" 25 | ) 26 | 27 | type rsyslogType int 28 | 29 | const ( 30 | rsyslogUnknown rsyslogType = iota 31 | rsyslogAction 32 | rsyslogInput 33 | rsyslogQueue 34 | rsyslogResource 35 | rsyslogDynStat 36 | rsyslogDynafileCache 37 | rsyslogInputIMDUP 38 | rsyslogForward 39 | rsyslogKubernetes 40 | rsyslogOmkafka 41 | ) 42 | 43 | type rsyslogExporter struct { 44 | started bool 45 | logfile *os.File 46 | scanner *bufio.Scanner 47 | pointStore 48 | } 49 | 50 | func newRsyslogExporter() *rsyslogExporter { 51 | e := &rsyslogExporter{ 52 | scanner: bufio.NewScanner(os.Stdin), 53 | pointStore: pointStore{ 54 | pointMap: make(map[string]*point), 55 | lock: &sync.RWMutex{}, 56 | }, 57 | } 58 | return e 59 | } 60 | 61 | func (re *rsyslogExporter) handleStatLine(rawbuf []byte) error { 62 | s := bytes.SplitN(rawbuf, []byte(" "), 4) 63 | if len(s) != 4 { 64 | return fmt.Errorf("failed to split log line, expected 4 columns, got: %v", len(s)) 65 | } 66 | buf := s[3] 67 | 68 | pstatType := getStatType(buf) 69 | 70 | switch pstatType { 71 | case rsyslogAction: 72 | a, err := newActionFromJSON(buf) 73 | if err != nil { 74 | return err 75 | } 76 | for _, p := range a.toPoints() { 77 | re.set(p) 78 | } 79 | 80 | case rsyslogInput: 81 | i, err := newInputFromJSON(buf) 82 | if err != nil { 83 | return err 84 | } 85 | for _, p := range i.toPoints() { 86 | re.set(p) 87 | } 88 | 89 | case rsyslogInputIMDUP: 90 | u, err := newInputIMUDPFromJSON(buf) 91 | if err != nil { 92 | return err 93 | } 94 | for _, p := range u.toPoints() { 95 | re.set(p) 96 | } 97 | 98 | case rsyslogQueue: 99 | q, err := newQueueFromJSON(buf) 100 | if err != nil { 101 | return err 102 | } 103 | for _, p := range q.toPoints() { 104 | re.set(p) 105 | } 106 | 107 | case rsyslogResource: 108 | r, err := newResourceFromJSON(buf) 109 | if err != nil { 110 | return err 111 | } 112 | for _, p := range r.toPoints() { 113 | re.set(p) 114 | } 115 | case rsyslogDynStat: 116 | s, err := newDynStatFromJSON(buf) 117 | if err != nil { 118 | return err 119 | } 120 | for _, p := range s.toPoints() { 121 | re.set(p) 122 | } 123 | case rsyslogDynafileCache: 124 | d, err := newDynafileCacheFromJSON(buf) 125 | if err != nil { 126 | return err 127 | } 128 | for _, p := range d.toPoints() { 129 | re.set(p) 130 | } 131 | case rsyslogForward: 132 | f, err := newForwardFromJSON(buf) 133 | if err != nil { 134 | return err 135 | } 136 | for _, p := range f.toPoints() { 137 | re.set(p) 138 | } 139 | case rsyslogKubernetes: 140 | k, err := newKubernetesFromJSON(buf) 141 | if err != nil { 142 | return err 143 | } 144 | for _, p := range k.toPoints() { 145 | re.set(p) 146 | } 147 | case rsyslogOmkafka: 148 | o, err := newOmkafkaFromJSON(buf) 149 | if err != nil { 150 | return err 151 | } 152 | for _, p := range o.toPoints() { 153 | re.set(p) 154 | } 155 | 156 | default: 157 | return fmt.Errorf("unknown pstat type: %v", pstatType) 158 | } 159 | return nil 160 | } 161 | 162 | // Describe sends the description of currently known metrics collected 163 | // by this Collector to the provided channel. Note that this implementation 164 | // does not necessarily send the "super-set of all possible descriptors" as 165 | // defined by the Collector interface spec, depending on the timing of when 166 | // it is called. The rsyslog exporter does not know all possible metrics 167 | // it will export until the first full batch of rsyslog impstats messages 168 | // are received via stdin. This is ok for now. 169 | func (re *rsyslogExporter) Describe(ch chan<- *prometheus.Desc) { 170 | ch <- prometheus.NewDesc( 171 | prometheus.BuildFQName("", "rsyslog", "scrapes"), 172 | "times exporter has been scraped", 173 | nil, nil, 174 | ) 175 | 176 | keys := re.keys() 177 | 178 | for _, k := range keys { 179 | p, err := re.get(k) 180 | if err != nil { 181 | ch <- p.promDescription() 182 | } 183 | } 184 | } 185 | 186 | // Collect is called by Prometheus when collecting metrics. 187 | func (re *rsyslogExporter) Collect(ch chan<- prometheus.Metric) { 188 | keys := re.keys() 189 | 190 | for _, k := range keys { 191 | p, err := re.get(k) 192 | if err != nil { 193 | continue 194 | } 195 | 196 | labelValues := []string{} 197 | if p.promLabelValue() != "" { 198 | labelValues = []string{p.promLabelValue()} 199 | } 200 | metric := prometheus.MustNewConstMetric( 201 | p.promDescription(), 202 | p.promType(), 203 | p.promValue(), 204 | labelValues..., 205 | ) 206 | 207 | ch <- metric 208 | } 209 | } 210 | 211 | func (re *rsyslogExporter) run(silent bool) { 212 | errorPoint := &point{ 213 | Name: "stats_line_errors", 214 | Type: counter, 215 | Description: "Counts errors during stats line handling", 216 | } 217 | re.set(errorPoint) 218 | for re.scanner.Scan() { 219 | err := re.handleStatLine(re.scanner.Bytes()) 220 | if err != nil { 221 | errorPoint.Value += 1 222 | if !silent { 223 | log.Printf("error handling stats line: %v, line was: %s", err, re.scanner.Bytes()) 224 | } 225 | } 226 | } 227 | if err := re.scanner.Err(); err != nil { 228 | log.Printf("error reading input: %v", err) 229 | } 230 | log.Print("input ended, exiting normally") 231 | os.Exit(0) 232 | } 233 | -------------------------------------------------------------------------------- /exporter_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Prometheus Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package main 15 | 16 | import ( 17 | "fmt" 18 | "testing" 19 | ) 20 | 21 | func testHelper(t *testing.T, line []byte, testCase []*testUnit) { 22 | exporter := newRsyslogExporter() 23 | exporter.handleStatLine(line) 24 | 25 | for _, k := range exporter.keys() { 26 | t.Logf("have key: '%s'", k) 27 | } 28 | 29 | for _, item := range testCase { 30 | p, err := exporter.get(item.key()) 31 | if err != nil { 32 | t.Error(err) 33 | } 34 | 35 | if want, got := item.Val, p.promValue(); want != got { 36 | t.Errorf("%s: want '%f', got '%f'", item.Name, want, got) 37 | } 38 | } 39 | 40 | exporter.handleStatLine(line) 41 | 42 | for _, item := range testCase { 43 | p, err := exporter.get(item.key()) 44 | if err != nil { 45 | t.Error(err) 46 | } 47 | 48 | var wanted float64 49 | switch p.Type { 50 | case counter: 51 | wanted = item.Val 52 | case gauge: 53 | wanted = item.Val 54 | default: 55 | t.Errorf("%d is not a valid metric type", p.Type) 56 | continue 57 | } 58 | 59 | if want, got := wanted, p.promValue(); want != got { 60 | t.Errorf("%s: want '%f', got '%f'", item.Name, want, got) 61 | } 62 | } 63 | } 64 | 65 | type testUnit struct { 66 | Name string 67 | Val float64 68 | LabelValue string 69 | } 70 | 71 | func (t *testUnit) key() string { 72 | return fmt.Sprintf("%s.%s", t.Name, t.LabelValue) 73 | } 74 | 75 | func TestHandleLineWithAction(t *testing.T) { 76 | tests := []*testUnit{ 77 | &testUnit{ 78 | Name: "action_processed", 79 | Val: 100000, 80 | LabelValue: "test_action", 81 | }, 82 | &testUnit{ 83 | Name: "action_failed", 84 | Val: 2, 85 | LabelValue: "test_action", 86 | }, 87 | &testUnit{ 88 | Name: "action_suspended", 89 | Val: 1, 90 | LabelValue: "test_action", 91 | }, 92 | &testUnit{ 93 | Name: "action_suspended_duration", 94 | Val: 1000, 95 | LabelValue: "test_action", 96 | }, 97 | &testUnit{ 98 | Name: "action_resumed", 99 | Val: 1, 100 | LabelValue: "test_action", 101 | }, 102 | } 103 | 104 | actionLog := []byte(`2017-08-30T08:10:04.786350+00:00 some-node.example.org rsyslogd-pstats: {"name":"test_action","processed":100000,"failed":2,"suspended":1,"suspended.duration":1000,"resumed":1}`) 105 | testHelper(t, actionLog, tests) 106 | } 107 | 108 | func TestHandleLineWithResource(t *testing.T) { 109 | tests := []*testUnit{ 110 | &testUnit{ 111 | Name: "resource_utime", 112 | Val: 10, 113 | LabelValue: "resource-usage", 114 | }, 115 | &testUnit{ 116 | Name: "resource_stime", 117 | Val: 20, 118 | LabelValue: "resource-usage", 119 | }, 120 | &testUnit{ 121 | Name: "resource_maxrss", 122 | Val: 30, 123 | LabelValue: "resource-usage", 124 | }, 125 | &testUnit{ 126 | Name: "resource_minflt", 127 | Val: 40, 128 | LabelValue: "resource-usage", 129 | }, 130 | &testUnit{ 131 | Name: "resource_majflt", 132 | Val: 50, 133 | LabelValue: "resource-usage", 134 | }, 135 | &testUnit{ 136 | Name: "resource_inblock", 137 | Val: 60, 138 | LabelValue: "resource-usage", 139 | }, 140 | &testUnit{ 141 | Name: "resource_oublock", 142 | Val: 70, 143 | LabelValue: "resource-usage", 144 | }, 145 | &testUnit{ 146 | Name: "resource_nvcsw", 147 | Val: 80, 148 | LabelValue: "resource-usage", 149 | }, 150 | &testUnit{ 151 | Name: "resource_nivcsw", 152 | Val: 90, 153 | LabelValue: "resource-usage", 154 | }, 155 | } 156 | 157 | resourceLog := []byte(`2017-08-30T08:10:04.786350+00:00 some-node.example.org rsyslogd-pstats: {"name":"resource-usage","utime":10,"stime":20,"maxrss":30,"minflt":40,"majflt":50,"inblock":60,"oublock":70,"nvcsw":80,"nivcsw":90}`) 158 | testHelper(t, resourceLog, tests) 159 | } 160 | 161 | func TestHandleLineWithInput(t *testing.T) { 162 | tests := []*testUnit{ 163 | &testUnit{ 164 | Name: "input_submitted", 165 | Val: 1000, 166 | LabelValue: "test_input", 167 | }, 168 | } 169 | 170 | inputLog := []byte(`2017-08-30T08:10:04.786350+00:00 some-node.example.org rsyslogd-pstats: {"name":"test_input", "origin":"imuxsock", "submitted":1000}`) 171 | testHelper(t, inputLog, tests) 172 | } 173 | 174 | func TestHandleLineWithQueue(t *testing.T) { 175 | tests := []*testUnit{ 176 | &testUnit{ 177 | Name: "queue_size", 178 | Val: 10, 179 | LabelValue: "main Q", 180 | }, 181 | &testUnit{ 182 | Name: "queue_enqueued", 183 | Val: 20, 184 | LabelValue: "main Q", 185 | }, 186 | &testUnit{ 187 | Name: "queue_full", 188 | Val: 30, 189 | LabelValue: "main Q", 190 | }, 191 | &testUnit{ 192 | Name: "queue_discarded_full", 193 | Val: 40, 194 | LabelValue: "main Q", 195 | }, 196 | &testUnit{ 197 | Name: "queue_discarded_not_full", 198 | Val: 50, 199 | LabelValue: "main Q", 200 | }, 201 | &testUnit{ 202 | Name: "queue_max_size", 203 | Val: 60, 204 | LabelValue: "main Q", 205 | }, 206 | } 207 | 208 | queueLog := []byte(`2017-08-30T08:10:04.786350+00:00 some-node.example.org rsyslogd-pstats: {"name":"main Q","size":10,"enqueued":20,"full":30,"discarded.full":40,"discarded.nf":50,"maxqsize":60}`) 209 | testHelper(t, queueLog, tests) 210 | } 211 | 212 | func TestHandleLineWithGlobal(t *testing.T) { 213 | tests := []*testUnit{ 214 | &testUnit{ 215 | Name: "dynstat_global", 216 | Val: 1, 217 | LabelValue: "msg_per_host.ops_overflow", 218 | }, 219 | &testUnit{ 220 | Name: "dynstat_global", 221 | Val: 3, 222 | LabelValue: "msg_per_host.new_metric_add", 223 | }, 224 | &testUnit{ 225 | Name: "dynstat_global", 226 | Val: 0, 227 | LabelValue: "msg_per_host.no_metric", 228 | }, 229 | &testUnit{ 230 | Name: "dynstat_global", 231 | Val: 0, 232 | LabelValue: "msg_per_host.metrics_purged", 233 | }, 234 | &testUnit{ 235 | Name: "dynstat_global", 236 | Val: 0, 237 | LabelValue: "msg_per_host.ops_ignored", 238 | }, 239 | } 240 | 241 | log := []byte(`2018-01-18T09:39:12.763025+00:00 some-node.example.org rsyslogd-pstats: { "name": "global", "origin": "dynstats", "values": { "msg_per_host.ops_overflow": 1, "msg_per_host.new_metric_add": 3, "msg_per_host.no_metric": 0, "msg_per_host.metrics_purged": 0, "msg_per_host.ops_ignored": 0 } }`) 242 | 243 | testHelper(t, log, tests) 244 | } 245 | 246 | func TestHandleLineWithDynafileCache(t *testing.T) { 247 | tests := []*testUnit{ 248 | &testUnit{ 249 | Name: "dynafile_cache_requests", 250 | Val: 412044, 251 | LabelValue: "cluster", 252 | }, 253 | &testUnit{ 254 | Name: "dynafile_cache_level0", 255 | Val: 294002, 256 | LabelValue: "cluster", 257 | }, 258 | &testUnit{ 259 | Name: "dynafile_cache_missed", 260 | Val: 210, 261 | LabelValue: "cluster", 262 | }, 263 | &testUnit{ 264 | Name: "dynafile_cache_evicted", 265 | Val: 14, 266 | LabelValue: "cluster", 267 | }, 268 | } 269 | 270 | dynafileCacheLog := []byte(`2019-07-03T17:04:01.312432+00:00 some-node.example.org rsyslogd-pstats: { "name": "dynafile cache cluster", "origin": "omfile", "requests": 412044, "level0": 294002, "missed": 210, "evicted": 14, "maxused": 100, "closetimeouts": 0 }`) 271 | testHelper(t, dynafileCacheLog, tests) 272 | } 273 | 274 | func TestHandleUnknown(t *testing.T) { 275 | unknownLog := []byte(`2017-08-30T08:10:04.786350+00:00 some-node.example.org rsyslogd-pstats: {"a":"b"}`) 276 | 277 | exporter := newRsyslogExporter() 278 | exporter.handleStatLine(unknownLog) 279 | 280 | if want, got := 0, len(exporter.keys()); want != got { 281 | t.Errorf("want '%d', got '%d'", want, got) 282 | } 283 | } 284 | -------------------------------------------------------------------------------- /fixtures/rsyslog-stats.log: -------------------------------------------------------------------------------- 1 | 2017-08-30T08:09:54.776051+00:00 some-node.example.org rsyslogd-pstats: { "name": "global", "origin": "dynstats", "values": { } } 2 | 2017-08-30T08:09:54.776052+00:00 some-node.example.org rsyslogd-pstats: { "name": "global", "origin": "percentile", "values": { } } 3 | 2017-08-30T08:09:54.776072+00:00 some-node.example.org rsyslogd-pstats: { "name": "imuxsock", "origin": "imuxsock", "submitted": 9, "ratelimit.discarded": 0, "ratelimit.numratelimiters": 0 } 4 | 2017-08-30T08:09:54.776082+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 0", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 5 | 2017-08-30T08:09:54.776088+00:00 some-node.example.org rsyslogd-pstats: { "name": "to_exporter_2", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 6 | 2017-08-30T08:09:54.776094+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 3", "origin": "core.action", "processed": 1, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 7 | 2017-08-30T08:09:54.776098+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 4", "origin": "core.action", "processed": 1, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 8 | 2017-08-30T08:09:54.776103+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 5", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 9 | 2017-08-30T08:09:54.776109+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 7", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 10 | 2017-08-30T08:09:54.776114+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 10", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 11 | 2017-08-30T08:09:54.776119+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 11", "origin": "core.action", "processed": 6, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 12 | 2017-08-30T08:09:54.776123+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 12", "origin": "core.action", "processed": 2, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 13 | 2017-08-30T08:09:54.776144+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 13", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 14 | 2017-08-30T08:09:54.776151+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 14", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 15 | 2017-08-30T08:09:54.776155+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 15", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 16 | 2017-08-30T08:09:54.776160+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 16", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 17 | 2017-08-30T08:09:54.776164+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 17", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 18 | 2017-08-30T08:09:54.776173+00:00 some-node.example.org rsyslogd-pstats: { "name": "imptcp(*\/\/var\/run\/go-audit.sock\/IPv4)", "origin": "imptcp", "submitted": 1, "sessions.opened": 0, "sessions.openfailed": 0, "sessions.closed": 1, "bytes.received": 1045, "bytes.decompressed": 0 } 19 | 2017-08-30T08:09:54.776181+00:00 some-node.example.org rsyslogd-pstats: { "name": "resource-usage", "origin": "impstats", "utime": 5000, "stime": 4000, "maxrss": 5876, "minflt": 521, "majflt": 0, "inblock": 0, "oublock": 0, "nvcsw": 23, "nivcsw": 15, "openfiles": 16 } 20 | 2017-08-30T08:09:54.776187+00:00 some-node.example.org rsyslogd-pstats: { "name": "main Q", "origin": "core.queue", "size": 18, "enqueued": 28, "full": 0, "discarded.full": 0, "discarded.nf": 0, "maxqsize": 18 } 21 | 2017-08-30T08:09:54.776191+00:00 some-node.example.org rsyslogd-pstats: { "name": "io-work-q", "origin": "imptcp", "enqueued": 0, "maxqsize": 0 } 22 | 2017-08-30T08:10:04.786350+00:00 some-node.example.org rsyslogd-pstats: { "name": "global", "origin": "dynstats", "values": { } } 23 | 2017-08-30T08:10:04.786371+00:00 some-node.example.org rsyslogd-pstats: { "name": "imuxsock", "origin": "imuxsock", "submitted": 12, "ratelimit.discarded": 0, "ratelimit.numratelimiters": 0 } 24 | 2017-08-30T08:10:04.786379+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 0", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 25 | 2017-08-30T08:10:04.786386+00:00 some-node.example.org rsyslogd-pstats: { "name": "to_exporter_2", "origin": "core.action", "processed": 20, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 26 | 2017-08-30T08:10:04.786391+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 3", "origin": "core.action", "processed": 3, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 27 | 2017-08-30T08:10:04.786395+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 4", "origin": "core.action", "processed": 4, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 28 | 2017-08-30T08:10:04.786401+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 5", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 29 | 2017-08-30T08:10:04.786407+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 7", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 30 | 2017-08-30T08:10:04.786413+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 10", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 31 | 2017-08-30T08:10:04.786420+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 11", "origin": "core.action", "processed": 6, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 32 | 2017-08-30T08:10:04.786425+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 12", "origin": "core.action", "processed": 2, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 33 | 2017-08-30T08:10:04.786431+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 13", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 34 | 2017-08-30T08:10:04.786437+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 14", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 35 | 2017-08-30T08:10:04.786443+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 15", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 36 | 2017-08-30T08:10:04.786447+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 16", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 37 | 2017-08-30T08:10:04.786452+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 17", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 38 | 2017-08-30T08:10:04.786459+00:00 some-node.example.org rsyslogd-pstats: { "name": "imptcp(*\/\/var\/run\/go-audit.sock\/IPv4)", "origin": "imptcp", "submitted": 3, "sessions.opened": 0, "sessions.openfailed": 0, "sessions.closed": 1, "bytes.received": 2920, "bytes.decompressed": 0 } 39 | 2017-08-30T08:10:04.786468+00:00 some-node.example.org rsyslogd-pstats: { "name": "resource-usage", "origin": "impstats", "utime": 6000, "stime": 4000, "maxrss": 5876, "minflt": 591, "majflt": 0, "inblock": 0, "oublock": 0, "nvcsw": 35, "nivcsw": 15, "openfiles": 17 } 40 | 2017-08-30T08:10:04.786475+00:00 some-node.example.org rsyslogd-pstats: { "name": "main Q", "origin": "core.queue", "size": 18, "enqueued": 53, "full": 0, "discarded.full": 0, "discarded.nf": 0, "maxqsize": 20 } 41 | 2017-08-30T08:10:04.786479+00:00 some-node.example.org rsyslogd-pstats: { "name": "io-work-q", "origin": "imptcp", "enqueued": 0, "maxqsize": 0 } 42 | 2017-08-30T08:10:14.796202+00:00 some-node.example.org rsyslogd-pstats: { "name": "global", "origin": "dynstats", "values": { } } 43 | 2017-08-30T08:10:14.796219+00:00 some-node.example.org rsyslogd-pstats: { "name": "imuxsock", "origin": "imuxsock", "submitted": 12, "ratelimit.discarded": 0, "ratelimit.numratelimiters": 0 } 44 | 2017-08-30T08:10:14.796227+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 0", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 45 | 2017-08-30T08:10:14.796234+00:00 some-node.example.org rsyslogd-pstats: { "name": "to_exporter_2", "origin": "core.action", "processed": 40, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 46 | 2017-08-30T08:10:14.796239+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 3", "origin": "core.action", "processed": 3, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 47 | 2017-08-30T08:10:14.796243+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 4", "origin": "core.action", "processed": 4, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 48 | 2017-08-30T08:10:14.796247+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 5", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 49 | 2017-08-30T08:10:14.796252+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 7", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 50 | 2017-08-30T08:10:14.796258+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 10", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 51 | 2017-08-30T08:10:14.796262+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 11", "origin": "core.action", "processed": 6, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 52 | 2017-08-30T08:10:14.796267+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 12", "origin": "core.action", "processed": 2, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 53 | 2017-08-30T08:10:14.796271+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 13", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 54 | 2017-08-30T08:10:14.796276+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 14", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 55 | 2017-08-30T08:10:14.796281+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 15", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 56 | 2017-08-30T08:10:14.796285+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 16", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 57 | 2017-08-30T08:10:14.796290+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 17", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 58 | 2017-08-30T08:10:14.796296+00:00 some-node.example.org rsyslogd-pstats: { "name": "imptcp(*\/\/var\/run\/go-audit.sock\/IPv4)", "origin": "imptcp", "submitted": 3, "sessions.opened": 0, "sessions.openfailed": 0, "sessions.closed": 1, "bytes.received": 2920, "bytes.decompressed": 0 } 59 | 2017-08-30T08:10:14.796304+00:00 some-node.example.org rsyslogd-pstats: { "name": "resource-usage", "origin": "impstats", "utime": 6000, "stime": 4000, "maxrss": 5876, "minflt": 604, "majflt": 0, "inblock": 0, "oublock": 0, "nvcsw": 37, "nivcsw": 15, "openfiles": 17 } 60 | 2017-08-30T08:10:14.796310+00:00 some-node.example.org rsyslogd-pstats: { "name": "main Q", "origin": "core.queue", "size": 18, "enqueued": 73, "full": 0, "discarded.full": 0, "discarded.nf": 0, "maxqsize": 20 } 61 | 2017-08-30T08:10:14.796314+00:00 some-node.example.org rsyslogd-pstats: { "name": "io-work-q", "origin": "imptcp", "enqueued": 0, "maxqsize": 0 } 62 | 2017-08-30T08:10:24.806474+00:00 some-node.example.org rsyslogd-pstats: { "name": "global", "origin": "dynstats", "values": { } } 63 | 2017-08-30T08:10:24.806492+00:00 some-node.example.org rsyslogd-pstats: { "name": "imuxsock", "origin": "imuxsock", "submitted": 12, "ratelimit.discarded": 0, "ratelimit.numratelimiters": 0 } 64 | 2017-08-30T08:10:24.806500+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 0", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 65 | 2017-08-30T08:10:24.806506+00:00 some-node.example.org rsyslogd-pstats: { "name": "to_exporter_2", "origin": "core.action", "processed": 60, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 66 | 2017-08-30T08:10:24.806512+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 3", "origin": "core.action", "processed": 3, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 67 | 2017-08-30T08:10:24.806516+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 4", "origin": "core.action", "processed": 4, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 68 | 2017-08-30T08:10:24.806521+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 5", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 69 | 2017-08-30T08:10:24.806526+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 7", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 70 | 2017-08-30T08:10:24.806532+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 10", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 71 | 2017-08-30T08:10:24.806536+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 11", "origin": "core.action", "processed": 6, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 72 | 2017-08-30T08:10:24.806541+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 12", "origin": "core.action", "processed": 2, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 73 | 2017-08-30T08:10:24.806545+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 13", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 74 | 2017-08-30T08:10:24.806551+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 14", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 75 | 2017-08-30T08:10:24.806555+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 15", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 76 | 2017-08-30T08:10:24.806560+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 16", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 77 | 2017-08-30T08:10:24.806564+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 17", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 78 | 2017-08-30T08:10:24.806571+00:00 some-node.example.org rsyslogd-pstats: { "name": "imptcp(*\/\/var\/run\/go-audit.sock\/IPv4)", "origin": "imptcp", "submitted": 3, "sessions.opened": 0, "sessions.openfailed": 0, "sessions.closed": 1, "bytes.received": 2920, "bytes.decompressed": 0 } 79 | 2017-08-30T08:10:24.806580+00:00 some-node.example.org rsyslogd-pstats: { "name": "resource-usage", "origin": "impstats", "utime": 6000, "stime": 4000, "maxrss": 5876, "minflt": 604, "majflt": 0, "inblock": 0, "oublock": 0, "nvcsw": 39, "nivcsw": 15, "openfiles": 17 } 80 | 2017-08-30T08:10:24.806585+00:00 some-node.example.org rsyslogd-pstats: { "name": "main Q", "origin": "core.queue", "size": 18, "enqueued": 93, "full": 0, "discarded.full": 0, "discarded.nf": 0, "maxqsize": 20 } 81 | 2017-08-30T08:10:24.806590+00:00 some-node.example.org rsyslogd-pstats: { "name": "io-work-q", "origin": "imptcp", "enqueued": 0, "maxqsize": 0 } 82 | 2017-08-30T08:10:34.812772+00:00 some-node.example.org rsyslogd-pstats: { "name": "global", "origin": "dynstats", "values": { } } 83 | 2017-08-30T08:10:34.812791+00:00 some-node.example.org rsyslogd-pstats: { "name": "imuxsock", "origin": "imuxsock", "submitted": 33, "ratelimit.discarded": 0, "ratelimit.numratelimiters": 0 } 84 | 2017-08-30T08:10:34.812799+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 0", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 85 | 2017-08-30T08:10:34.812806+00:00 some-node.example.org rsyslogd-pstats: { "name": "to_exporter_2", "origin": "core.action", "processed": 80, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 86 | 2017-08-30T08:10:34.812811+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 3", "origin": "core.action", "processed": 3, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 87 | 2017-08-30T08:10:34.812816+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 4", "origin": "core.action", "processed": 25, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 88 | 2017-08-30T08:10:34.812820+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 5", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 89 | 2017-08-30T08:10:34.812826+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 7", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 90 | 2017-08-30T08:10:34.812831+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 10", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 91 | 2017-08-30T08:10:34.812836+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 11", "origin": "core.action", "processed": 6, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 92 | 2017-08-30T08:10:34.812840+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 12", "origin": "core.action", "processed": 2, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 93 | 2017-08-30T08:10:34.812845+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 13", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 94 | 2017-08-30T08:10:34.812850+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 14", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 95 | 2017-08-30T08:10:34.812855+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 15", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 96 | 2017-08-30T08:10:34.812860+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 16", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 97 | 2017-08-30T08:10:34.812864+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 17", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 98 | 2017-08-30T08:10:34.812871+00:00 some-node.example.org rsyslogd-pstats: { "name": "imptcp(*\/\/var\/run\/go-audit.sock\/IPv4)", "origin": "imptcp", "submitted": 3, "sessions.opened": 0, "sessions.openfailed": 0, "sessions.closed": 1, "bytes.received": 2920, "bytes.decompressed": 0 } 99 | 2017-08-30T08:10:34.812879+00:00 some-node.example.org rsyslogd-pstats: { "name": "resource-usage", "origin": "impstats", "utime": 6000, "stime": 6000, "maxrss": 5876, "minflt": 605, "majflt": 0, "inblock": 0, "oublock": 0, "nvcsw": 83, "nivcsw": 15, "openfiles": 17 } 100 | 2017-08-30T08:10:34.812885+00:00 some-node.example.org rsyslogd-pstats: { "name": "main Q", "origin": "core.queue", "size": 18, "enqueued": 134, "full": 0, "discarded.full": 0, "discarded.nf": 0, "maxqsize": 20 } 101 | 2017-08-30T08:10:34.812890+00:00 some-node.example.org rsyslogd-pstats: { "name": "io-work-q", "origin": "imptcp", "enqueued": 0, "maxqsize": 0 } 102 | 2017-08-30T08:10:44.823040+00:00 some-node.example.org rsyslogd-pstats: { "name": "global", "origin": "dynstats", "values": { } } 103 | 2017-08-30T08:10:44.823060+00:00 some-node.example.org rsyslogd-pstats: { "name": "imuxsock", "origin": "imuxsock", "submitted": 33, "ratelimit.discarded": 0, "ratelimit.numratelimiters": 0 } 104 | 2017-08-30T08:10:44.823067+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 0", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 105 | 2017-08-30T08:10:44.823073+00:00 some-node.example.org rsyslogd-pstats: { "name": "to_exporter_2", "origin": "core.action", "processed": 100, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 106 | 2017-08-30T08:10:44.823079+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 3", "origin": "core.action", "processed": 3, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 107 | 2017-08-30T08:10:44.823083+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 4", "origin": "core.action", "processed": 25, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 108 | 2017-08-30T08:10:44.823088+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 5", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 109 | 2017-08-30T08:10:44.823093+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 7", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 110 | 2017-08-30T08:10:44.823098+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 10", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 111 | 2017-08-30T08:10:44.823103+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 11", "origin": "core.action", "processed": 6, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 112 | 2017-08-30T08:10:44.823107+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 12", "origin": "core.action", "processed": 2, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 113 | 2017-08-30T08:10:44.823111+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 13", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 114 | 2017-08-30T08:10:44.823116+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 14", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 115 | 2017-08-30T08:10:44.823121+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 15", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 116 | 2017-08-30T08:10:44.823125+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 16", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 117 | 2017-08-30T08:10:44.823142+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 17", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 118 | 2017-08-30T08:10:44.823150+00:00 some-node.example.org rsyslogd-pstats: { "name": "imptcp(*\/\/var\/run\/go-audit.sock\/IPv4)", "origin": "imptcp", "submitted": 3, "sessions.opened": 0, "sessions.openfailed": 0, "sessions.closed": 1, "bytes.received": 2920, "bytes.decompressed": 0 } 119 | 2017-08-30T08:10:44.823159+00:00 some-node.example.org rsyslogd-pstats: { "name": "resource-usage", "origin": "impstats", "utime": 6000, "stime": 6000, "maxrss": 5876, "minflt": 605, "majflt": 0, "inblock": 0, "oublock": 0, "nvcsw": 85, "nivcsw": 15, "openfiles": 17 } 120 | 2017-08-30T08:10:44.823165+00:00 some-node.example.org rsyslogd-pstats: { "name": "main Q", "origin": "core.queue", "size": 18, "enqueued": 154, "full": 0, "discarded.full": 0, "discarded.nf": 0, "maxqsize": 20 } 121 | 2017-08-30T08:10:44.823169+00:00 some-node.example.org rsyslogd-pstats: { "name": "io-work-q", "origin": "imptcp", "enqueued": 0, "maxqsize": 0 } 122 | 2017-08-30T08:10:54.833332+00:00 some-node.example.org rsyslogd-pstats: { "name": "global", "origin": "dynstats", "values": { } } 123 | 2017-08-30T08:10:54.833352+00:00 some-node.example.org rsyslogd-pstats: { "name": "imuxsock", "origin": "imuxsock", "submitted": 33, "ratelimit.discarded": 0, "ratelimit.numratelimiters": 0 } 124 | 2017-08-30T08:10:54.833359+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 0", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 125 | 2017-08-30T08:10:54.833365+00:00 some-node.example.org rsyslogd-pstats: { "name": "to_exporter_2", "origin": "core.action", "processed": 120, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 126 | 2017-08-30T08:10:54.833371+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 3", "origin": "core.action", "processed": 3, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 127 | 2017-08-30T08:10:54.833375+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 4", "origin": "core.action", "processed": 25, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 128 | 2017-08-30T08:10:54.833380+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 5", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 129 | 2017-08-30T08:10:54.833385+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 7", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 130 | 2017-08-30T08:10:54.833391+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 10", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 131 | 2017-08-30T08:10:54.833395+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 11", "origin": "core.action", "processed": 6, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 132 | 2017-08-30T08:10:54.833400+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 12", "origin": "core.action", "processed": 2, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 133 | 2017-08-30T08:10:54.833404+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 13", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 134 | 2017-08-30T08:10:54.833409+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 14", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 135 | 2017-08-30T08:10:54.833414+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 15", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 136 | 2017-08-30T08:10:54.833419+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 16", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 137 | 2017-08-30T08:10:54.833423+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 17", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 138 | 2017-08-30T08:10:54.833430+00:00 some-node.example.org rsyslogd-pstats: { "name": "imptcp(*\/\/var\/run\/go-audit.sock\/IPv4)", "origin": "imptcp", "submitted": 3, "sessions.opened": 0, "sessions.openfailed": 0, "sessions.closed": 1, "bytes.received": 2920, "bytes.decompressed": 0 } 139 | 2017-08-30T08:10:54.833439+00:00 some-node.example.org rsyslogd-pstats: { "name": "resource-usage", "origin": "impstats", "utime": 6000, "stime": 6000, "maxrss": 5876, "minflt": 605, "majflt": 0, "inblock": 0, "oublock": 0, "nvcsw": 87, "nivcsw": 15, "openfiles": 17 } 140 | 2017-08-30T08:10:54.833445+00:00 some-node.example.org rsyslogd-pstats: { "name": "main Q", "origin": "core.queue", "size": 18, "enqueued": 174, "full": 0, "discarded.full": 0, "discarded.nf": 0, "maxqsize": 20 } 141 | 2017-08-30T08:10:54.833449+00:00 some-node.example.org rsyslogd-pstats: { "name": "io-work-q", "origin": "imptcp", "enqueued": 0, "maxqsize": 0 } 142 | 2017-08-30T08:11:04.843631+00:00 some-node.example.org rsyslogd-pstats: { "name": "global", "origin": "dynstats", "values": { } } 143 | 2017-08-30T08:11:04.843651+00:00 some-node.example.org rsyslogd-pstats: { "name": "imuxsock", "origin": "imuxsock", "submitted": 36, "ratelimit.discarded": 0, "ratelimit.numratelimiters": 0 } 144 | 2017-08-30T08:11:04.843659+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 0", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 145 | 2017-08-30T08:11:04.843665+00:00 some-node.example.org rsyslogd-pstats: { "name": "to_exporter_2", "origin": "core.action", "processed": 140, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 146 | 2017-08-30T08:11:04.843671+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 3", "origin": "core.action", "processed": 3, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 147 | 2017-08-30T08:11:04.843675+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 4", "origin": "core.action", "processed": 28, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 148 | 2017-08-30T08:11:04.843680+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 5", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 149 | 2017-08-30T08:11:04.843685+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 7", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 150 | 2017-08-30T08:11:04.843690+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 10", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 151 | 2017-08-30T08:11:04.843695+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 11", "origin": "core.action", "processed": 6, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 152 | 2017-08-30T08:11:04.843699+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 12", "origin": "core.action", "processed": 2, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 153 | 2017-08-30T08:11:04.843703+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 13", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 154 | 2017-08-30T08:11:04.843709+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 14", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 155 | 2017-08-30T08:11:04.843713+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 15", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 156 | 2017-08-30T08:11:04.843718+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 16", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 157 | 2017-08-30T08:11:04.843722+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 17", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 158 | 2017-08-30T08:11:04.843729+00:00 some-node.example.org rsyslogd-pstats: { "name": "imptcp(*\/\/var\/run\/go-audit.sock\/IPv4)", "origin": "imptcp", "submitted": 3, "sessions.opened": 0, "sessions.openfailed": 0, "sessions.closed": 1, "bytes.received": 2920, "bytes.decompressed": 0 } 159 | 2017-08-30T08:11:04.843739+00:00 some-node.example.org rsyslogd-pstats: { "name": "resource-usage", "origin": "impstats", "utime": 7000, "stime": 6000, "maxrss": 5876, "minflt": 606, "majflt": 0, "inblock": 0, "oublock": 0, "nvcsw": 95, "nivcsw": 15, "openfiles": 17 } 160 | 2017-08-30T08:11:04.843744+00:00 some-node.example.org rsyslogd-pstats: { "name": "main Q", "origin": "core.queue", "size": 18, "enqueued": 197, "full": 0, "discarded.full": 0, "discarded.nf": 0, "maxqsize": 20 } 161 | 2017-08-30T08:11:04.843749+00:00 some-node.example.org rsyslogd-pstats: { "name": "io-work-q", "origin": "imptcp", "enqueued": 0, "maxqsize": 0 } 162 | 2017-08-30T08:11:14.853927+00:00 some-node.example.org rsyslogd-pstats: { "name": "global", "origin": "dynstats", "values": { } } 163 | 2017-08-30T08:11:14.853946+00:00 some-node.example.org rsyslogd-pstats: { "name": "imuxsock", "origin": "imuxsock", "submitted": 36, "ratelimit.discarded": 0, "ratelimit.numratelimiters": 0 } 164 | 2017-08-30T08:11:14.853954+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 0", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 165 | 2017-08-30T08:11:14.853960+00:00 some-node.example.org rsyslogd-pstats: { "name": "to_exporter_2", "origin": "core.action", "processed": 160, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 166 | 2017-08-30T08:11:14.853965+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 3", "origin": "core.action", "processed": 3, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 167 | 2017-08-30T08:11:14.853970+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 4", "origin": "core.action", "processed": 28, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 168 | 2017-08-30T08:11:14.853974+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 5", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 169 | 2017-08-30T08:11:14.853980+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 7", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 170 | 2017-08-30T08:11:14.853985+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 10", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 171 | 2017-08-30T08:11:14.853990+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 11", "origin": "core.action", "processed": 6, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 172 | 2017-08-30T08:11:14.853994+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 12", "origin": "core.action", "processed": 2, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 173 | 2017-08-30T08:11:14.853998+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 13", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 174 | 2017-08-30T08:11:14.854003+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 14", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 175 | 2017-08-30T08:11:14.854008+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 15", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 176 | 2017-08-30T08:11:14.854012+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 16", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 177 | 2017-08-30T08:11:14.854017+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 17", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 178 | 2017-08-30T08:11:14.854023+00:00 some-node.example.org rsyslogd-pstats: { "name": "imptcp(*\/\/var\/run\/go-audit.sock\/IPv4)", "origin": "imptcp", "submitted": 3, "sessions.opened": 0, "sessions.openfailed": 0, "sessions.closed": 1, "bytes.received": 2920, "bytes.decompressed": 0 } 179 | 2017-08-30T08:11:14.854031+00:00 some-node.example.org rsyslogd-pstats: { "name": "resource-usage", "origin": "impstats", "utime": 7000, "stime": 6000, "maxrss": 5876, "minflt": 606, "majflt": 0, "inblock": 0, "oublock": 0, "nvcsw": 97, "nivcsw": 15, "openfiles": 17 } 180 | 2017-08-30T08:11:14.854037+00:00 some-node.example.org rsyslogd-pstats: { "name": "main Q", "origin": "core.queue", "size": 18, "enqueued": 217, "full": 0, "discarded.full": 0, "discarded.nf": 0, "maxqsize": 20 } 181 | 2017-08-30T08:11:14.854041+00:00 some-node.example.org rsyslogd-pstats: { "name": "io-work-q", "origin": "imptcp", "enqueued": 0, "maxqsize": 0 } 182 | 2017-08-30T08:11:24.862793+00:00 some-node.example.org rsyslogd-pstats: { "name": "global", "origin": "dynstats", "values": { } } 183 | 2017-08-30T08:11:24.862812+00:00 some-node.example.org rsyslogd-pstats: { "name": "imuxsock", "origin": "imuxsock", "submitted": 36, "ratelimit.discarded": 0, "ratelimit.numratelimiters": 0 } 184 | 2017-08-30T08:11:24.862820+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 0", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 185 | 2017-08-30T08:11:24.862826+00:00 some-node.example.org rsyslogd-pstats: { "name": "to_exporter_2", "origin": "core.action", "processed": 180, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 186 | 2017-08-30T08:11:24.862831+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 3", "origin": "core.action", "processed": 3, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 187 | 2017-08-30T08:11:24.862835+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 4", "origin": "core.action", "processed": 28, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 188 | 2017-08-30T08:11:24.862840+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 5", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 189 | 2017-08-30T08:11:24.862845+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 7", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 190 | 2017-08-30T08:11:24.862851+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 10", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 191 | 2017-08-30T08:11:24.862855+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 11", "origin": "core.action", "processed": 6, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 192 | 2017-08-30T08:11:24.862860+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 12", "origin": "core.action", "processed": 2, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 193 | 2017-08-30T08:11:24.862864+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 13", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 194 | 2017-08-30T08:11:24.862869+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 14", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 195 | 2017-08-30T08:11:24.862874+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 15", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 196 | 2017-08-30T08:11:24.862879+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 16", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 197 | 2017-08-30T08:11:24.862883+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 17", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 198 | 2017-08-30T08:11:24.862890+00:00 some-node.example.org rsyslogd-pstats: { "name": "imptcp(*\/\/var\/run\/go-audit.sock\/IPv4)", "origin": "imptcp", "submitted": 3, "sessions.opened": 0, "sessions.openfailed": 0, "sessions.closed": 1, "bytes.received": 2920, "bytes.decompressed": 0 } 199 | 2017-08-30T08:11:24.862899+00:00 some-node.example.org rsyslogd-pstats: { "name": "resource-usage", "origin": "impstats", "utime": 7000, "stime": 6000, "maxrss": 5876, "minflt": 606, "majflt": 0, "inblock": 0, "oublock": 0, "nvcsw": 99, "nivcsw": 15, "openfiles": 17 } 200 | 2017-08-30T08:11:24.862905+00:00 some-node.example.org rsyslogd-pstats: { "name": "main Q", "origin": "core.queue", "size": 18, "enqueued": 237, "full": 0, "discarded.full": 0, "discarded.nf": 0, "maxqsize": 20 } 201 | 2017-08-30T08:11:24.862909+00:00 some-node.example.org rsyslogd-pstats: { "name": "io-work-q", "origin": "imptcp", "enqueued": 0, "maxqsize": 0 } 202 | 2017-08-30T08:11:34.869986+00:00 some-node.example.org rsyslogd-pstats: { "name": "global", "origin": "dynstats", "values": { } } 203 | 2017-08-30T08:11:34.870005+00:00 some-node.example.org rsyslogd-pstats: { "name": "imuxsock", "origin": "imuxsock", "submitted": 39, "ratelimit.discarded": 0, "ratelimit.numratelimiters": 0 } 204 | 2017-08-30T08:11:34.870013+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 0", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 205 | 2017-08-30T08:11:34.870019+00:00 some-node.example.org rsyslogd-pstats: { "name": "to_exporter_2", "origin": "core.action", "processed": 200, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 206 | 2017-08-30T08:11:34.870024+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 3", "origin": "core.action", "processed": 3, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 207 | 2017-08-30T08:11:34.870029+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 4", "origin": "core.action", "processed": 31, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 208 | 2017-08-30T08:11:34.870034+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 5", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 209 | 2017-08-30T08:11:34.870039+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 7", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 210 | 2017-08-30T08:11:34.870045+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 10", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 211 | 2017-08-30T08:11:34.870049+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 11", "origin": "core.action", "processed": 6, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 212 | 2017-08-30T08:11:34.870053+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 12", "origin": "core.action", "processed": 2, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 213 | 2017-08-30T08:11:34.870058+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 13", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 214 | 2017-08-30T08:11:34.870063+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 14", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 215 | 2017-08-30T08:11:34.870068+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 15", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 216 | 2017-08-30T08:11:34.870072+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 16", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 217 | 2017-08-30T08:11:34.870077+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 17", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 218 | 2017-08-30T08:11:34.870084+00:00 some-node.example.org rsyslogd-pstats: { "name": "imptcp(*\/\/var\/run\/go-audit.sock\/IPv4)", "origin": "imptcp", "submitted": 3, "sessions.opened": 0, "sessions.openfailed": 0, "sessions.closed": 1, "bytes.received": 2920, "bytes.decompressed": 0 } 219 | 2017-08-30T08:11:34.870093+00:00 some-node.example.org rsyslogd-pstats: { "name": "resource-usage", "origin": "impstats", "utime": 8000, "stime": 6000, "maxrss": 5876, "minflt": 606, "majflt": 0, "inblock": 0, "oublock": 0, "nvcsw": 107, "nivcsw": 15, "openfiles": 17 } 220 | 2017-08-30T08:11:34.870099+00:00 some-node.example.org rsyslogd-pstats: { "name": "main Q", "origin": "core.queue", "size": 18, "enqueued": 260, "full": 0, "discarded.full": 0, "discarded.nf": 0, "maxqsize": 20 } 221 | 2017-08-30T08:11:34.870103+00:00 some-node.example.org rsyslogd-pstats: { "name": "io-work-q", "origin": "imptcp", "enqueued": 0, "maxqsize": 0 } 222 | 2017-08-30T08:11:44.876344+00:00 some-node.example.org rsyslogd-pstats: { "name": "global", "origin": "dynstats", "values": { } } 223 | 2017-08-30T08:11:44.876364+00:00 some-node.example.org rsyslogd-pstats: { "name": "imuxsock", "origin": "imuxsock", "submitted": 39, "ratelimit.discarded": 0, "ratelimit.numratelimiters": 0 } 224 | 2017-08-30T08:11:44.876372+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 0", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 225 | 2017-08-30T08:11:44.876378+00:00 some-node.example.org rsyslogd-pstats: { "name": "to_exporter_2", "origin": "core.action", "processed": 220, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 226 | 2017-08-30T08:11:44.876383+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 3", "origin": "core.action", "processed": 3, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 227 | 2017-08-30T08:11:44.876388+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 4", "origin": "core.action", "processed": 31, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 228 | 2017-08-30T08:11:44.876392+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 5", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 229 | 2017-08-30T08:11:44.876398+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 7", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 230 | 2017-08-30T08:11:44.876403+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 10", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 231 | 2017-08-30T08:11:44.876408+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 11", "origin": "core.action", "processed": 6, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 232 | 2017-08-30T08:11:44.876412+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 12", "origin": "core.action", "processed": 2, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 233 | 2017-08-30T08:11:44.876417+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 13", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 234 | 2017-08-30T08:11:44.876422+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 14", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 235 | 2017-08-30T08:11:44.876427+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 15", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 236 | 2017-08-30T08:11:44.876432+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 16", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 237 | 2017-08-30T08:11:44.876436+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 17", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 238 | 2017-08-30T08:11:44.876443+00:00 some-node.example.org rsyslogd-pstats: { "name": "imptcp(*\/\/var\/run\/go-audit.sock\/IPv4)", "origin": "imptcp", "submitted": 3, "sessions.opened": 0, "sessions.openfailed": 0, "sessions.closed": 1, "bytes.received": 2920, "bytes.decompressed": 0 } 239 | 2017-08-30T08:11:44.876452+00:00 some-node.example.org rsyslogd-pstats: { "name": "resource-usage", "origin": "impstats", "utime": 8000, "stime": 6000, "maxrss": 5876, "minflt": 606, "majflt": 0, "inblock": 0, "oublock": 0, "nvcsw": 109, "nivcsw": 15, "openfiles": 17 } 240 | 2017-08-30T08:11:44.876458+00:00 some-node.example.org rsyslogd-pstats: { "name": "main Q", "origin": "core.queue", "size": 18, "enqueued": 280, "full": 0, "discarded.full": 0, "discarded.nf": 0, "maxqsize": 20 } 241 | 2017-08-30T08:11:44.876462+00:00 some-node.example.org rsyslogd-pstats: { "name": "io-work-q", "origin": "imptcp", "enqueued": 0, "maxqsize": 0 } 242 | 2017-08-30T08:11:54.883536+00:00 some-node.example.org rsyslogd-pstats: { "name": "global", "origin": "dynstats", "values": { } } 243 | 2017-08-30T08:11:54.883554+00:00 some-node.example.org rsyslogd-pstats: { "name": "imuxsock", "origin": "imuxsock", "submitted": 39, "ratelimit.discarded": 0, "ratelimit.numratelimiters": 0 } 244 | 2017-08-30T08:11:54.883562+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 0", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 245 | 2017-08-30T08:11:54.883568+00:00 some-node.example.org rsyslogd-pstats: { "name": "to_exporter_2", "origin": "core.action", "processed": 240, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 246 | 2017-08-30T08:11:54.883574+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 3", "origin": "core.action", "processed": 4, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 247 | 2017-08-30T08:11:54.883578+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 4", "origin": "core.action", "processed": 31, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 248 | 2017-08-30T08:11:54.883583+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 5", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 249 | 2017-08-30T08:11:54.883588+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 7", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 250 | 2017-08-30T08:11:54.883593+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 10", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 251 | 2017-08-30T08:11:54.883598+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 11", "origin": "core.action", "processed": 6, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 252 | 2017-08-30T08:11:54.883602+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 12", "origin": "core.action", "processed": 2, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 253 | 2017-08-30T08:11:54.883606+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 13", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 254 | 2017-08-30T08:11:54.883612+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 14", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 255 | 2017-08-30T08:11:54.883616+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 15", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 256 | 2017-08-30T08:11:54.883621+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 16", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 257 | 2017-08-30T08:11:54.883625+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 17", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 258 | 2017-08-30T08:11:54.883632+00:00 some-node.example.org rsyslogd-pstats: { "name": "imptcp(*\/\/var\/run\/go-audit.sock\/IPv4)", "origin": "imptcp", "submitted": 4, "sessions.opened": 0, "sessions.openfailed": 0, "sessions.closed": 1, "bytes.received": 3901, "bytes.decompressed": 0 } 259 | 2017-08-30T08:11:54.883641+00:00 some-node.example.org rsyslogd-pstats: { "name": "resource-usage", "origin": "impstats", "utime": 8000, "stime": 6000, "maxrss": 5876, "minflt": 606, "majflt": 0, "inblock": 0, "oublock": 0, "nvcsw": 113, "nivcsw": 15, "openfiles": 17 } 260 | 2017-08-30T08:11:54.883647+00:00 some-node.example.org rsyslogd-pstats: { "name": "main Q", "origin": "core.queue", "size": 18, "enqueued": 301, "full": 0, "discarded.full": 0, "discarded.nf": 0, "maxqsize": 20 } 261 | 2017-08-30T08:11:54.883651+00:00 some-node.example.org rsyslogd-pstats: { "name": "io-work-q", "origin": "imptcp", "enqueued": 0, "maxqsize": 0 } 262 | 2017-08-30T08:12:04.889209+00:00 some-node.example.org rsyslogd-pstats: { "name": "global", "origin": "dynstats", "values": { } } 263 | 2017-08-30T08:12:04.889228+00:00 some-node.example.org rsyslogd-pstats: { "name": "imuxsock", "origin": "imuxsock", "submitted": 42, "ratelimit.discarded": 0, "ratelimit.numratelimiters": 0 } 264 | 2017-08-30T08:12:04.889236+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 0", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 265 | 2017-08-30T08:12:04.889243+00:00 some-node.example.org rsyslogd-pstats: { "name": "to_exporter_2", "origin": "core.action", "processed": 260, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 266 | 2017-08-30T08:12:04.889248+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 3", "origin": "core.action", "processed": 4, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 267 | 2017-08-30T08:12:04.889253+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 4", "origin": "core.action", "processed": 34, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 268 | 2017-08-30T08:12:04.889258+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 5", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 269 | 2017-08-30T08:12:04.889263+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 7", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 270 | 2017-08-30T08:12:04.889268+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 10", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 271 | 2017-08-30T08:12:04.889273+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 11", "origin": "core.action", "processed": 6, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 272 | 2017-08-30T08:12:04.889277+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 12", "origin": "core.action", "processed": 2, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 273 | 2017-08-30T08:12:04.889282+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 13", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 274 | 2017-08-30T08:12:04.889287+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 14", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 275 | 2017-08-30T08:12:04.889292+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 15", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 276 | 2017-08-30T08:12:04.889296+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 16", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 277 | 2017-08-30T08:12:04.889301+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 17", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 278 | 2017-08-30T08:12:04.889308+00:00 some-node.example.org rsyslogd-pstats: { "name": "imptcp(*\/\/var\/run\/go-audit.sock\/IPv4)", "origin": "imptcp", "submitted": 4, "sessions.opened": 0, "sessions.openfailed": 0, "sessions.closed": 1, "bytes.received": 3901, "bytes.decompressed": 0 } 279 | 2017-08-30T08:12:04.889317+00:00 some-node.example.org rsyslogd-pstats: { "name": "resource-usage", "origin": "impstats", "utime": 8000, "stime": 7000, "maxrss": 5876, "minflt": 606, "majflt": 0, "inblock": 0, "oublock": 0, "nvcsw": 121, "nivcsw": 15, "openfiles": 17 } 280 | 2017-08-30T08:12:04.889322+00:00 some-node.example.org rsyslogd-pstats: { "name": "main Q", "origin": "core.queue", "size": 18, "enqueued": 324, "full": 0, "discarded.full": 0, "discarded.nf": 0, "maxqsize": 20 } 281 | 2017-08-30T08:12:04.889327+00:00 some-node.example.org rsyslogd-pstats: { "name": "io-work-q", "origin": "imptcp", "enqueued": 0, "maxqsize": 0 } 282 | 2017-08-30T08:12:14.895862+00:00 some-node.example.org rsyslogd-pstats: { "name": "global", "origin": "dynstats", "values": { } } 283 | 2017-08-30T08:12:14.895881+00:00 some-node.example.org rsyslogd-pstats: { "name": "imuxsock", "origin": "imuxsock", "submitted": 42, "ratelimit.discarded": 0, "ratelimit.numratelimiters": 0 } 284 | 2017-08-30T08:12:14.895889+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 0", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 285 | 2017-08-30T08:12:14.895895+00:00 some-node.example.org rsyslogd-pstats: { "name": "to_exporter_2", "origin": "core.action", "processed": 280, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 286 | 2017-08-30T08:12:14.895900+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 3", "origin": "core.action", "processed": 4, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 287 | 2017-08-30T08:12:14.895905+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 4", "origin": "core.action", "processed": 34, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 288 | 2017-08-30T08:12:14.895909+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 5", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 289 | 2017-08-30T08:12:14.895914+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 7", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 290 | 2017-08-30T08:12:14.895920+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 10", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 291 | 2017-08-30T08:12:14.895924+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 11", "origin": "core.action", "processed": 6, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 292 | 2017-08-30T08:12:14.895929+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 12", "origin": "core.action", "processed": 2, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 293 | 2017-08-30T08:12:14.895933+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 13", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 294 | 2017-08-30T08:12:14.895938+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 14", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 295 | 2017-08-30T08:12:14.895943+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 15", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 296 | 2017-08-30T08:12:14.895948+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 16", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 297 | 2017-08-30T08:12:14.895952+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 17", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 298 | 2017-08-30T08:12:14.895959+00:00 some-node.example.org rsyslogd-pstats: { "name": "imptcp(*\/\/var\/run\/go-audit.sock\/IPv4)", "origin": "imptcp", "submitted": 4, "sessions.opened": 0, "sessions.openfailed": 0, "sessions.closed": 1, "bytes.received": 3901, "bytes.decompressed": 0 } 299 | 2017-08-30T08:12:14.895968+00:00 some-node.example.org rsyslogd-pstats: { "name": "resource-usage", "origin": "impstats", "utime": 8000, "stime": 7000, "maxrss": 5876, "minflt": 606, "majflt": 0, "inblock": 0, "oublock": 0, "nvcsw": 123, "nivcsw": 15, "openfiles": 17 } 300 | 2017-08-30T08:12:14.895974+00:00 some-node.example.org rsyslogd-pstats: { "name": "main Q", "origin": "core.queue", "size": 18, "enqueued": 344, "full": 0, "discarded.full": 0, "discarded.nf": 0, "maxqsize": 20 } 301 | 2017-08-30T08:12:14.895978+00:00 some-node.example.org rsyslogd-pstats: { "name": "io-work-q", "origin": "imptcp", "enqueued": 0, "maxqsize": 0 } 302 | 2017-08-30T08:12:24.903554+00:00 some-node.example.org rsyslogd-pstats: { "name": "global", "origin": "dynstats", "values": { } } 303 | 2017-08-30T08:12:24.903574+00:00 some-node.example.org rsyslogd-pstats: { "name": "imuxsock", "origin": "imuxsock", "submitted": 42, "ratelimit.discarded": 0, "ratelimit.numratelimiters": 0 } 304 | 2017-08-30T08:12:24.903582+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 0", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 305 | 2017-08-30T08:12:24.903588+00:00 some-node.example.org rsyslogd-pstats: { "name": "to_exporter_2", "origin": "core.action", "processed": 300, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 306 | 2017-08-30T08:12:24.903593+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 3", "origin": "core.action", "processed": 4, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 307 | 2017-08-30T08:12:24.903598+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 4", "origin": "core.action", "processed": 34, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 308 | 2017-08-30T08:12:24.903602+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 5", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 309 | 2017-08-30T08:12:24.903608+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 7", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 310 | 2017-08-30T08:12:24.903613+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 10", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 311 | 2017-08-30T08:12:24.903617+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 11", "origin": "core.action", "processed": 6, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 312 | 2017-08-30T08:12:24.903622+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 12", "origin": "core.action", "processed": 2, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 313 | 2017-08-30T08:12:24.903626+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 13", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 314 | 2017-08-30T08:12:24.903631+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 14", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 315 | 2017-08-30T08:12:24.903636+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 15", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 316 | 2017-08-30T08:12:24.903641+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 16", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 317 | 2017-08-30T08:12:24.903645+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 17", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 318 | 2017-08-30T08:12:24.903652+00:00 some-node.example.org rsyslogd-pstats: { "name": "imptcp(*\/\/var\/run\/go-audit.sock\/IPv4)", "origin": "imptcp", "submitted": 4, "sessions.opened": 0, "sessions.openfailed": 0, "sessions.closed": 1, "bytes.received": 3901, "bytes.decompressed": 0 } 319 | 2017-08-30T08:12:24.903661+00:00 some-node.example.org rsyslogd-pstats: { "name": "resource-usage", "origin": "impstats", "utime": 8000, "stime": 7000, "maxrss": 5876, "minflt": 606, "majflt": 0, "inblock": 0, "oublock": 0, "nvcsw": 125, "nivcsw": 15, "openfiles": 17 } 320 | 2017-08-30T08:12:24.903667+00:00 some-node.example.org rsyslogd-pstats: { "name": "main Q", "origin": "core.queue", "size": 18, "enqueued": 364, "full": 0, "discarded.full": 0, "discarded.nf": 0, "maxqsize": 20 } 321 | 2017-08-30T08:12:24.903671+00:00 some-node.example.org rsyslogd-pstats: { "name": "io-work-q", "origin": "imptcp", "enqueued": 0, "maxqsize": 0 } 322 | 2017-08-30T08:12:34.909129+00:00 some-node.example.org rsyslogd-pstats: { "name": "global", "origin": "dynstats", "values": { } } 323 | 2017-08-30T08:12:34.909151+00:00 some-node.example.org rsyslogd-pstats: { "name": "imuxsock", "origin": "imuxsock", "submitted": 45, "ratelimit.discarded": 0, "ratelimit.numratelimiters": 0 } 324 | 2017-08-30T08:12:34.909159+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 0", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 325 | 2017-08-30T08:12:34.909166+00:00 some-node.example.org rsyslogd-pstats: { "name": "to_exporter_2", "origin": "core.action", "processed": 320, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 326 | 2017-08-30T08:12:34.909171+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 3", "origin": "core.action", "processed": 4, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 327 | 2017-08-30T08:12:34.909176+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 4", "origin": "core.action", "processed": 37, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 328 | 2017-08-30T08:12:34.909181+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 5", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 329 | 2017-08-30T08:12:34.909186+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 7", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 330 | 2017-08-30T08:12:34.909192+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 10", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 331 | 2017-08-30T08:12:34.909196+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 11", "origin": "core.action", "processed": 6, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 332 | 2017-08-30T08:12:34.909218+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 12", "origin": "core.action", "processed": 2, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 333 | 2017-08-30T08:12:34.909223+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 13", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 334 | 2017-08-30T08:12:34.909229+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 14", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 335 | 2017-08-30T08:12:34.909233+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 15", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 336 | 2017-08-30T08:12:34.909238+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 16", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 337 | 2017-08-30T08:12:34.909243+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 17", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 338 | 2017-08-30T08:12:34.909250+00:00 some-node.example.org rsyslogd-pstats: { "name": "imptcp(*\/\/var\/run\/go-audit.sock\/IPv4)", "origin": "imptcp", "submitted": 4, "sessions.opened": 0, "sessions.openfailed": 0, "sessions.closed": 1, "bytes.received": 3901, "bytes.decompressed": 0 } 339 | 2017-08-30T08:12:34.909259+00:00 some-node.example.org rsyslogd-pstats: { "name": "resource-usage", "origin": "impstats", "utime": 9000, "stime": 7000, "maxrss": 5876, "minflt": 606, "majflt": 0, "inblock": 0, "oublock": 0, "nvcsw": 133, "nivcsw": 15, "openfiles": 17 } 340 | 2017-08-30T08:12:34.909265+00:00 some-node.example.org rsyslogd-pstats: { "name": "main Q", "origin": "core.queue", "size": 18, "enqueued": 387, "full": 0, "discarded.full": 0, "discarded.nf": 0, "maxqsize": 20 } 341 | 2017-08-30T08:12:34.909269+00:00 some-node.example.org rsyslogd-pstats: { "name": "io-work-q", "origin": "imptcp", "enqueued": 0, "maxqsize": 0 } 342 | 2017-08-30T08:12:44.919198+00:00 some-node.example.org rsyslogd-pstats: { "name": "global", "origin": "dynstats", "values": { } } 343 | 2017-08-30T08:12:44.919216+00:00 some-node.example.org rsyslogd-pstats: { "name": "imuxsock", "origin": "imuxsock", "submitted": 45, "ratelimit.discarded": 0, "ratelimit.numratelimiters": 0 } 344 | 2017-08-30T08:12:44.919224+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 0", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 345 | 2017-08-30T08:12:44.919230+00:00 some-node.example.org rsyslogd-pstats: { "name": "to_exporter_2", "origin": "core.action", "processed": 340, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 346 | 2017-08-30T08:12:44.919235+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 3", "origin": "core.action", "processed": 4, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 347 | 2017-08-30T08:12:44.919239+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 4", "origin": "core.action", "processed": 37, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 348 | 2017-08-30T08:12:44.919244+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 5", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 349 | 2017-08-30T08:12:44.919249+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 7", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 350 | 2017-08-30T08:12:44.919254+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 10", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 351 | 2017-08-30T08:12:44.919259+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 11", "origin": "core.action", "processed": 6, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 352 | 2017-08-30T08:12:44.919263+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 12", "origin": "core.action", "processed": 2, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 353 | 2017-08-30T08:12:44.919267+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 13", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 354 | 2017-08-30T08:12:44.919272+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 14", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 355 | 2017-08-30T08:12:44.919277+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 15", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 356 | 2017-08-30T08:12:44.919281+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 16", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 357 | 2017-08-30T08:12:44.919286+00:00 some-node.example.org rsyslogd-pstats: { "name": "action 17", "origin": "core.action", "processed": 0, "failed": 0, "suspended": 0, "suspended.duration": 0, "resumed": 0 } 358 | 2017-08-30T08:12:44.919293+00:00 some-node.example.org rsyslogd-pstats: { "name": "imptcp(*\/\/var\/run\/go-audit.sock\/IPv4)", "origin": "imptcp", "submitted": 4, "sessions.opened": 0, "sessions.openfailed": 0, "sessions.closed": 1, "bytes.received": 3901, "bytes.decompressed": 0 } 359 | 2017-08-30T08:12:44.919301+00:00 some-node.example.org rsyslogd-pstats: { "name": "resource-usage", "origin": "impstats", "utime": 9000, "stime": 7000, "maxrss": 5876, "minflt": 606, "majflt": 0, "inblock": 0, "oublock": 0, "nvcsw": 135, "nivcsw": 15, "openfiles": 17 } 360 | 2017-08-30T08:12:44.919307+00:00 some-node.example.org rsyslogd-pstats: { "name": "main Q", "origin": "core.queue", "size": 18, "enqueued": 407, "full": 0, "discarded.full": 0, "discarded.nf": 0, "maxqsize": 20 } 361 | 2017-08-30T08:12:44.919311+00:00 some-node.example.org rsyslogd-pstats: { "name": "io-work-q", "origin": "imptcp", "enqueued": 0, "maxqsize": 0 } 362 | -------------------------------------------------------------------------------- /forward.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Prometheus Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package main 15 | 16 | import ( 17 | "encoding/json" 18 | "fmt" 19 | ) 20 | 21 | type forward struct { 22 | Name string `json:"name"` 23 | BytesSent int64 `json:"bytes.sent"` 24 | } 25 | 26 | func newForwardFromJSON(b []byte) (*forward, error) { 27 | var pstat forward 28 | err := json.Unmarshal(b, &pstat) 29 | if err != nil { 30 | return nil, fmt.Errorf("failed to decode forward stat `%v`: %v", string(b), err) 31 | } 32 | return &pstat, nil 33 | } 34 | 35 | func (f *forward) toPoints() []*point { 36 | points := make([]*point, 1) 37 | 38 | points[0] = &point{ 39 | Name: "forward_bytes_total", 40 | Type: counter, 41 | Value: f.BytesSent, 42 | Description: "bytes forwarded to destination", 43 | LabelName: "destination", 44 | LabelValue: f.Name, 45 | } 46 | 47 | return points 48 | } 49 | -------------------------------------------------------------------------------- /forward_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Prometheus Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package main 15 | 16 | import "testing" 17 | 18 | var ( 19 | forwardLog = []byte(`{ "name": "TCP-FQDN-6514", "origin": "omfwd", "bytes.sent": 666 }`) 20 | ) 21 | 22 | func TestNewForwardFromJSON(t *testing.T) { 23 | logType := getStatType(forwardLog) 24 | if logType != rsyslogForward { 25 | t.Errorf("detected pstat type should be %d but is %d", rsyslogForward, logType) 26 | } 27 | 28 | pstat, err := newForwardFromJSON([]byte(forwardLog)) 29 | if err != nil { 30 | t.Fatalf("expected parsing action not to fail, got: %v", err) 31 | } 32 | 33 | if want, got := "TCP-FQDN-6514", pstat.Name; want != got { 34 | t.Errorf("wanted '%s', got '%s'", want, got) 35 | } 36 | 37 | if want, got := int64(666), pstat.BytesSent; want != got { 38 | t.Errorf("wanted '%d', got '%d'", want, got) 39 | } 40 | } 41 | 42 | func TestForwardToPoints(t *testing.T) { 43 | pstat, err := newForwardFromJSON([]byte(forwardLog)) 44 | if err != nil { 45 | t.Fatalf("expected parsing action not to fail, got: %v", err) 46 | } 47 | points := pstat.toPoints() 48 | 49 | point := points[0] 50 | if want, got := "forward_bytes_total", point.Name; want != got { 51 | t.Errorf("wanted '%s', got '%s'", want, got) 52 | } 53 | 54 | if want, got := int64(666), point.Value; want != got { 55 | t.Errorf("wanted '%d', got '%d'", want, got) 56 | } 57 | 58 | if want, got := counter, point.Type; want != got { 59 | t.Errorf("wanted '%d', got '%d'", want, got) 60 | } 61 | 62 | if want, got := "TCP-FQDN-6514", point.LabelValue; want != got { 63 | t.Errorf("wanted '%s', got '%s'", want, got) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/prometheus-community/rsyslog_exporter 2 | 3 | go 1.22 4 | 5 | require github.com/prometheus/client_golang v1.20.5 6 | 7 | require ( 8 | github.com/beorn7/perks v1.0.1 // indirect 9 | github.com/cespare/xxhash/v2 v2.3.0 // indirect 10 | github.com/klauspost/compress v1.17.9 // indirect 11 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 12 | github.com/prometheus/client_model v0.6.1 // indirect 13 | github.com/prometheus/common v0.55.0 // indirect 14 | github.com/prometheus/procfs v0.15.1 // indirect 15 | golang.org/x/sys v0.22.0 // indirect 16 | google.golang.org/protobuf v1.34.2 // indirect 17 | ) 18 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= 2 | github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= 3 | github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= 4 | github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 5 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 6 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 7 | github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= 8 | github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= 9 | github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= 10 | github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= 11 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= 12 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= 13 | github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y= 14 | github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= 15 | github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= 16 | github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= 17 | github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc= 18 | github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8= 19 | github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= 20 | github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= 21 | golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= 22 | golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 23 | google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= 24 | google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= 25 | -------------------------------------------------------------------------------- /input_imudp.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Prometheus Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package main 15 | 16 | import ( 17 | "encoding/json" 18 | "fmt" 19 | ) 20 | 21 | type inputIMUDP struct { 22 | Name string `json:"name"` 23 | Recvmmsg int64 `json:"called.recvmmsg"` 24 | Recvmsg int64 `json:"called.recvmsg"` 25 | Received int64 `json:"msgs.received"` 26 | } 27 | 28 | func newInputIMUDPFromJSON(b []byte) (*inputIMUDP, error) { 29 | var pstat inputIMUDP 30 | err := json.Unmarshal(b, &pstat) 31 | if err != nil { 32 | return nil, fmt.Errorf("error decoding input stat `%v`: %v", string(b), err) 33 | } 34 | return &pstat, nil 35 | } 36 | 37 | func (i *inputIMUDP) toPoints() []*point { 38 | points := make([]*point, 3) 39 | 40 | points[0] = &point{ 41 | Name: "input_called_recvmmsg", 42 | Type: counter, 43 | Value: i.Recvmmsg, 44 | Description: "Number of recvmmsg called", 45 | LabelName: "worker", 46 | LabelValue: i.Name, 47 | } 48 | points[1] = &point{ 49 | Name: "input_called_recvmsg", 50 | Type: counter, 51 | Value: i.Recvmsg, 52 | Description: "Number of recvmmsg called", 53 | LabelName: "worker", 54 | LabelValue: i.Name, 55 | } 56 | 57 | points[2] = &point{ 58 | Name: "input_received", 59 | Type: counter, 60 | Value: i.Received, 61 | Description: "messages received", 62 | LabelName: "worker", 63 | LabelValue: i.Name, 64 | } 65 | 66 | return points 67 | } 68 | -------------------------------------------------------------------------------- /input_imudp_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Prometheus Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package main 15 | 16 | import "testing" 17 | 18 | var ( 19 | inputIMUDPLog = []byte(`{ "name": "test_input_imudp", "origin": "imudp", "called.recvmmsg":1000, "called.recvmsg":2000, "msgs.received":500}`) 20 | ) 21 | 22 | func TestgetInputIMUDP(t *testing.T) { 23 | logType := getStatType(inputIMUDPLog) 24 | if logType != rsyslogInputIMDUP { 25 | t.Errorf("detected pstat type should be %d but is %d", rsyslogInputIMDUP, logType) 26 | } 27 | 28 | pstat, err := newInputIMUDPFromJSON([]byte(inputLog)) 29 | if err != nil { 30 | t.Fatalf("expected parsing input stat not to fail, got: %v", err) 31 | } 32 | 33 | if want, got := "test_input_imudp", pstat.Name; want != got { 34 | t.Errorf("want '%s', got '%s'", want, got) 35 | } 36 | 37 | if want, got := int64(1000), pstat.Recvmsg; want != got { 38 | t.Errorf("want '%d', got '%d'", want, got) 39 | } 40 | 41 | if want, got := int64(2000), pstat.Recvmmsg; want != got { 42 | t.Errorf("want '%d', got '%d'", want, got) 43 | } 44 | 45 | if want, got := int64(500), pstat.Received; want != got { 46 | t.Errorf("want '%d', got '%d'", want, got) 47 | } 48 | } 49 | 50 | func TestInputIMUDPtoPoints(t *testing.T) { 51 | pstat, err := newInputIMUDPFromJSON([]byte(inputIMUDPLog)) 52 | if err != nil { 53 | t.Fatalf("expected parsing input stat not to fail, got: %v", err) 54 | } 55 | 56 | points := pstat.toPoints() 57 | 58 | point := points[0] 59 | if want, got := "input_called_recvmmsg", point.Name; want != got { 60 | t.Errorf("want '%s', got '%s'", want, got) 61 | } 62 | 63 | if want, got := int64(1000), point.Value; want != got { 64 | t.Errorf("want '%d', got '%d'", want, got) 65 | } 66 | 67 | if want, got := "test_input_imudp", point.LabelValue; want != got { 68 | t.Errorf("wanted '%s', got '%s'", want, got) 69 | } 70 | 71 | point = points[1] 72 | if want, got := "input_called_recvmsg", point.Name; want != got { 73 | t.Errorf("want '%s', got '%s'", want, got) 74 | } 75 | 76 | if want, got := int64(2000), point.Value; want != got { 77 | t.Errorf("want '%d', got '%d'", want, got) 78 | } 79 | 80 | if want, got := "test_input_imudp", point.LabelValue; want != got { 81 | t.Errorf("wanted '%s', got '%s'", want, got) 82 | } 83 | 84 | point = points[2] 85 | if want, got := "input_received", point.Name; want != got { 86 | t.Errorf("want '%s', got '%s'", want, got) 87 | } 88 | 89 | if want, got := int64(500), point.Value; want != got { 90 | t.Errorf("want '%d', got '%d'", want, got) 91 | } 92 | 93 | if want, got := "test_input_imudp", point.LabelValue; want != got { 94 | t.Errorf("wanted '%s', got '%s'", want, got) 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /inputs.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Prometheus Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package main 15 | 16 | import ( 17 | "encoding/json" 18 | "fmt" 19 | ) 20 | 21 | type input struct { 22 | Name string `json:"name"` 23 | Submitted int64 `json:"submitted"` 24 | } 25 | 26 | func newInputFromJSON(b []byte) (*input, error) { 27 | var pstat input 28 | err := json.Unmarshal(b, &pstat) 29 | if err != nil { 30 | return nil, fmt.Errorf("error decoding input stat `%v`: %v", string(b), err) 31 | } 32 | return &pstat, nil 33 | } 34 | 35 | func (i *input) toPoints() []*point { 36 | points := make([]*point, 1) 37 | 38 | points[0] = &point{ 39 | Name: "input_submitted", 40 | Type: counter, 41 | Value: i.Submitted, 42 | Description: "messages submitted", 43 | LabelName: "input", 44 | LabelValue: i.Name, 45 | } 46 | 47 | return points 48 | } 49 | -------------------------------------------------------------------------------- /inputs_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Prometheus Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package main 15 | 16 | import "testing" 17 | 18 | var ( 19 | inputLog = []byte(`{"name":"test_input", "origin":"imuxsock", "submitted":1000}`) 20 | ) 21 | 22 | func TestgetInput(t *testing.T) { 23 | logType := getStatType(inputLog) 24 | if logType != rsyslogInput { 25 | t.Errorf("detected pstat type should be %d but is %d", rsyslogInput, logType) 26 | } 27 | 28 | pstat, err := newInputFromJSON([]byte(inputLog)) 29 | if err != nil { 30 | t.Fatalf("expected parsing input stat not to fail, got: %v", err) 31 | } 32 | 33 | if want, got := "test_input", pstat.Name; want != got { 34 | t.Errorf("want '%s', got '%s'", want, got) 35 | } 36 | 37 | if want, got := int64(1000), pstat.Submitted; want != got { 38 | t.Errorf("want '%d', got '%d'", want, got) 39 | } 40 | } 41 | 42 | func TestInputtoPoints(t *testing.T) { 43 | pstat, err := newInputFromJSON([]byte(inputLog)) 44 | if err != nil { 45 | t.Fatalf("expected parsing input stat not to fail, got: %v", err) 46 | } 47 | 48 | points := pstat.toPoints() 49 | 50 | point := points[0] 51 | if want, got := "input_submitted", point.Name; want != got { 52 | t.Errorf("want '%s', got '%s'", want, got) 53 | } 54 | 55 | if want, got := int64(1000), point.Value; want != got { 56 | t.Errorf("want '%d', got '%d'", want, got) 57 | } 58 | 59 | if want, got := "test_input", point.LabelValue; want != got { 60 | t.Errorf("wanted '%s', got '%s'", want, got) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /kubernetes.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Prometheus Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package main 15 | 16 | import ( 17 | "encoding/json" 18 | "fmt" 19 | "regexp" 20 | ) 21 | 22 | var ( 23 | apiNameRegexp = regexp.MustCompile(`mmkubernetes\((\S+)\)`) 24 | ) 25 | 26 | type kubernetes struct { 27 | Name string `json:"name"` 28 | Url string 29 | RecordSeen int64 `json:"recordseen"` 30 | NamespaceMetaSuccess int64 `json:"namespacemetadatasuccess"` 31 | NamespaceMetaNotFound int64 `json:"namespacemetadatanotfound"` 32 | NamespaceMetaBusy int64 `json:"namespacemetadatabusy"` 33 | NamespaceMetaError int64 `json:"namespacemetadataerror"` 34 | PodMetaSuccess int64 `json:"podmetadatasuccess"` 35 | PodMetaNotFound int64 `json:"podmetadatanotfound"` 36 | PodMetaBusy int64 `json:"podmetadatabusy"` 37 | PodMetaError int64 `json:"podmetadataerror"` 38 | } 39 | 40 | func newKubernetesFromJSON(b []byte) (*kubernetes, error) { 41 | var pstat kubernetes 42 | err := json.Unmarshal(b, &pstat) 43 | if err != nil { 44 | return nil, fmt.Errorf("failed to decode kubernetes stat `%v`: %v", string(b), err) 45 | } 46 | matches := apiNameRegexp.FindSubmatch([]byte(pstat.Name)) 47 | if matches != nil { 48 | pstat.Url = string(matches[1]) 49 | } 50 | return &pstat, nil 51 | } 52 | 53 | func (k *kubernetes) toPoints() []*point { 54 | points := make([]*point, 9) 55 | 56 | points[0] = &point{ 57 | Name: "kubernetes_namespace_metadata_success_total", 58 | Type: counter, 59 | Value: k.NamespaceMetaSuccess, 60 | Description: "successful fetches of namespace metadata", 61 | LabelName: "url", 62 | LabelValue: k.Url, 63 | } 64 | 65 | points[1] = &point{ 66 | Name: "kubernetes_namespace_metadata_notfound_total", 67 | Type: counter, 68 | Value: k.NamespaceMetaNotFound, 69 | Description: "notfound fetches of namespace metadata", 70 | LabelName: "url", 71 | LabelValue: k.Url, 72 | } 73 | 74 | points[2] = &point{ 75 | Name: "kubernetes_namespace_metadata_busy_total", 76 | Type: counter, 77 | Value: k.NamespaceMetaBusy, 78 | Description: "busy fetches of namespace metadata", 79 | LabelName: "url", 80 | LabelValue: k.Url, 81 | } 82 | 83 | points[3] = &point{ 84 | Name: "kubernetes_namespace_metadata_error_total", 85 | Type: counter, 86 | Value: k.NamespaceMetaError, 87 | Description: "error fetches of namespace metadata", 88 | LabelName: "url", 89 | LabelValue: k.Url, 90 | } 91 | 92 | points[4] = &point{ 93 | Name: "kubernetes_pod_metadata_success_total", 94 | Type: counter, 95 | Value: k.PodMetaSuccess, 96 | Description: "successful fetches of pod metadata", 97 | LabelName: "url", 98 | LabelValue: k.Url, 99 | } 100 | 101 | points[5] = &point{ 102 | Name: "kubernetes_pod_metadata_notfound_total", 103 | Type: counter, 104 | Value: k.PodMetaNotFound, 105 | Description: "notfound fetches of pod metadata", 106 | LabelName: "url", 107 | LabelValue: k.Url, 108 | } 109 | 110 | points[6] = &point{ 111 | Name: "kubernetes_pod_metadata_busy_total", 112 | Type: counter, 113 | Value: k.PodMetaBusy, 114 | Description: "busy fetches of pod metadata", 115 | LabelName: "url", 116 | LabelValue: k.Url, 117 | } 118 | 119 | points[7] = &point{ 120 | Name: "kubernetes_pod_metadata_error_total", 121 | Type: counter, 122 | Value: k.PodMetaError, 123 | Description: "error fetches of pod metadata", 124 | LabelName: "url", 125 | LabelValue: k.Url, 126 | } 127 | 128 | points[8] = &point{ 129 | Name: "kubernetes_record_seen_total", 130 | Type: counter, 131 | Value: k.RecordSeen, 132 | Description: "records fetched from the api", 133 | LabelName: "url", 134 | LabelValue: k.Url, 135 | } 136 | 137 | return points 138 | } 139 | -------------------------------------------------------------------------------- /kubernetes_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Prometheus Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package main 15 | 16 | import "testing" 17 | 18 | var ( 19 | kubernetesLog = []byte(`{ "name": "mmkubernetes(https://host.domain.tld:6443)", "origin": "mmkubernetes", "recordseen": 477943, "namespacemetadatasuccess": 7, "namespacemetadatanotfound": 0, "namespacemetadatabusy": 0, "namespacemetadataerror": 0, "podmetadatasuccess": 26, "podmetadatanotfound": 0, "podmetadatabusy": 0, "podmetadataerror": 0 }`) 20 | ) 21 | 22 | func TestNewKubernetesFromJSON(t *testing.T) { 23 | logType := getStatType(kubernetesLog) 24 | if logType != rsyslogKubernetes { 25 | t.Errorf("detected pstat type should be %d but is %d", rsyslogKubernetes, logType) 26 | } 27 | 28 | pstat, err := newKubernetesFromJSON([]byte(kubernetesLog)) 29 | if err != nil { 30 | t.Fatalf("expected parsing action not to fail, got: %v", err) 31 | } 32 | 33 | if want, got := "mmkubernetes(https://host.domain.tld:6443)", pstat.Name; want != got { 34 | t.Errorf("wanted '%s', got '%s'", want, got) 35 | } 36 | 37 | if want, got := "https://host.domain.tld:6443", pstat.Url; want != got { 38 | t.Errorf("wanted '%s', got '%s'", want, got) 39 | } 40 | 41 | if want, got := int64(477943), pstat.RecordSeen; want != got { 42 | t.Errorf("wanted '%d', got '%d'", want, got) 43 | } 44 | 45 | if want, got := int64(7), pstat.NamespaceMetaSuccess; want != got { 46 | t.Errorf("wanted '%d', got '%d'", want, got) 47 | } 48 | 49 | if want, got := int64(0), pstat.NamespaceMetaNotFound; want != got { 50 | t.Errorf("wanted '%d', got '%d'", want, got) 51 | } 52 | 53 | if want, got := int64(0), pstat.NamespaceMetaBusy; want != got { 54 | t.Errorf("wanted '%d', got '%d'", want, got) 55 | } 56 | 57 | if want, got := int64(0), pstat.NamespaceMetaError; want != got { 58 | t.Errorf("wanted '%d', got '%d'", want, got) 59 | } 60 | 61 | if want, got := int64(26), pstat.PodMetaSuccess; want != got { 62 | t.Errorf("wanted '%d', got '%d'", want, got) 63 | } 64 | 65 | if want, got := int64(0), pstat.PodMetaNotFound; want != got { 66 | t.Errorf("wanted '%d', got '%d'", want, got) 67 | } 68 | 69 | if want, got := int64(0), pstat.PodMetaBusy; want != got { 70 | t.Errorf("wanted '%d', got '%d'", want, got) 71 | } 72 | 73 | if want, got := int64(0), pstat.PodMetaError; want != got { 74 | t.Errorf("wanted '%d', got '%d'", want, got) 75 | } 76 | 77 | } 78 | 79 | func TestKubernetesToPoints(t *testing.T) { 80 | pstat, err := newKubernetesFromJSON([]byte(kubernetesLog)) 81 | if err != nil { 82 | t.Fatalf("expected parsing action not to fail, got: %v", err) 83 | } 84 | points := pstat.toPoints() 85 | 86 | point := points[0] 87 | if want, got := "kubernetes_namespace_metadata_success_total", point.Name; want != got { 88 | t.Errorf("wanted '%s', got '%s'", want, got) 89 | } 90 | 91 | if want, got := "https://host.domain.tld:6443", point.LabelValue; want != got { 92 | t.Errorf("wanted '%s', got '%s'", want, got) 93 | } 94 | 95 | point = points[1] 96 | if want, got := "kubernetes_namespace_metadata_notfound_total", point.Name; want != got { 97 | t.Errorf("wanted '%s', got '%s'", want, got) 98 | } 99 | 100 | point = points[2] 101 | if want, got := "kubernetes_namespace_metadata_busy_total", point.Name; want != got { 102 | t.Errorf("wanted '%s', got '%s'", want, got) 103 | } 104 | 105 | point = points[3] 106 | if want, got := "kubernetes_namespace_metadata_error_total", point.Name; want != got { 107 | t.Errorf("wanted '%s', got '%s'", want, got) 108 | } 109 | 110 | point = points[4] 111 | if want, got := "kubernetes_pod_metadata_success_total", point.Name; want != got { 112 | t.Errorf("wanted '%s', got '%s'", want, got) 113 | } 114 | 115 | point = points[5] 116 | if want, got := "kubernetes_pod_metadata_notfound_total", point.Name; want != got { 117 | t.Errorf("wanted '%s', got '%s'", want, got) 118 | } 119 | 120 | point = points[6] 121 | if want, got := "kubernetes_pod_metadata_busy_total", point.Name; want != got { 122 | t.Errorf("wanted '%s', got '%s'", want, got) 123 | } 124 | 125 | point = points[7] 126 | if want, got := "kubernetes_pod_metadata_error_total", point.Name; want != got { 127 | t.Errorf("wanted '%s', got '%s'", want, got) 128 | } 129 | 130 | point = points[8] 131 | if want, got := "kubernetes_record_seen_total", point.Name; want != got { 132 | t.Errorf("wanted '%s', got '%s'", want, got) 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Prometheus Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package main 15 | 16 | import ( 17 | "flag" 18 | "log" 19 | "log/syslog" 20 | "net/http" 21 | "os" 22 | "os/signal" 23 | 24 | "github.com/prometheus/client_golang/prometheus" 25 | "github.com/prometheus/client_golang/prometheus/promhttp" 26 | ) 27 | 28 | var ( 29 | listenAddress = flag.String("web.listen-address", ":9104", "Address to listen on for web interface and telemetry.") 30 | metricPath = flag.String("web.telemetry-path", "/metrics", "Path under which to expose metrics.") 31 | certPath = flag.String("tls.server-crt", "", "Path to PEM encoded file containing TLS server cert.") 32 | keyPath = flag.String("tls.server-key", "", "Path to PEM encoded file containing TLS server key (unencyrpted).") 33 | silent = flag.Bool("silent", false, "Disable logging of errors in handling stats lines") 34 | ) 35 | 36 | func main() { 37 | logwriter, e := syslog.New(syslog.LOG_NOTICE|syslog.LOG_SYSLOG, "rsyslog_exporter") 38 | if e == nil { 39 | log.SetOutput(logwriter) 40 | } 41 | 42 | flag.Parse() 43 | exporter := newRsyslogExporter() 44 | 45 | go func() { 46 | c := make(chan os.Signal, 1) 47 | signal.Notify(c, os.Interrupt) 48 | <-c 49 | log.Print("interrupt received, exiting") 50 | os.Exit(0) 51 | }() 52 | 53 | go func() { 54 | exporter.run(*silent) 55 | }() 56 | 57 | prometheus.MustRegister(exporter) 58 | http.Handle(*metricPath, promhttp.Handler()) 59 | http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 60 | w.Write([]byte(` 61 | Rsyslog exporter 62 | 63 |

Rsyslog exporter

64 |

Metrics

65 | 66 | 67 | `)) 68 | }) 69 | 70 | if *certPath == "" && *keyPath == "" { 71 | log.Printf("Listening on %s", *listenAddress) 72 | log.Fatal(http.ListenAndServe(*listenAddress, nil)) 73 | } else if *certPath == "" || *keyPath == "" { 74 | log.Fatal("Both tls.server-crt and tls.server-key must be specified") 75 | } else { 76 | log.Printf("Listening for TLS on %s", *listenAddress) 77 | log.Fatal(http.ListenAndServeTLS(*listenAddress, *certPath, *keyPath, nil)) 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /omkafka.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Prometheus Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package main 15 | 16 | import ( 17 | "encoding/json" 18 | "fmt" 19 | ) 20 | 21 | type omkafka struct { 22 | Name string `json:"name"` 23 | Origin string `json:"origin"` 24 | Submitted int64 `json:"submitted"` 25 | MaxOutQSize int64 `json:"maxoutqsize"` 26 | Failures int64 `json:"failures"` 27 | TopicDynacacheSkipped int64 `json:"topicdynacache.skipped"` 28 | TopicDynacacheMiss int64 `json:"topicdynacache.miss"` 29 | TopicDynacacheEvicted int64 `json:"topicdynacache.evicted"` 30 | Acked int64 `json:"acked"` 31 | FailuresMsgTooLarge int64 `json:"failures_msg_too_large"` 32 | FailuresUnknownTopic int64 `json:"failures_unknown_topic"` 33 | FailuresQueueFull int64 `json:"failures_queue_full"` 34 | FailuresUnknownPartition int64 `json:"failures_unknown_partition"` 35 | FailuresOther int64 `json:"failures_other"` 36 | ErrorsTimedOut int64 `json:"errors_timed_out"` 37 | ErrorsTransport int64 `json:"errors_transport"` 38 | ErrorsBrokerDown int64 `json:"errors_broker_down"` 39 | ErrorsAuth int64 `json:"errors_auth"` 40 | ErrorsSSL int64 `json:"errors_ssl"` 41 | ErrorsOther int64 `json:"errors_other"` 42 | RttAvgUsec int64 `json:"rtt_avg_usec"` 43 | ThrottleAvgMsec int64 `json:"throttle_avg_msec"` 44 | IntLatencyAvgUsec int64 `json:"int_latency_avg_usec"` 45 | } 46 | 47 | const ( 48 | messagesDescription = "number of messages: submitted: messages submitted to omkafka for processing (with both acknowledged deliveries to broker as well as failed or re-submitted from omkafka to librdkafka); failures: messages that librdkafka failed to deliver (broken down into various types in omkafka_failures); acked: messages that were acknowledged by kafka broker. Note that kafka broker provides two levels of delivery acknowledgements depending on topicConfParam: default (acks=1) implies delivery to the leader only while acks=-1 implies delivery to leader as well as replication to all brokers" 49 | topicDynaCacheDescription = "skipped: dynamic topic cache lookups that find an existing topic and skip creating a new one; miss: dynamic topic cache lookups that fail to find an existing topic and end up creating new one; evicted: dynamic topic cache entry evictions" 50 | failuresDescription = "msg_too_large: failed to deliver to the broker because broker considers message to be too large. Note that omkafka may still resubmit to librdkafka depending on resubmitOnFailure option; unknown_topic: failed to deliver to the broker because broker does not recognize the topic; queue_full: dropped by librdkafka when its queue becomes full. Note that default size of librdkafka queue is 100,000 messages; unknown_partition: failed to deliver because broker does not recognize a partition; other: all of the rest of the failures that do not fall in any of the other failure categories" 51 | errorsDescription = "timed_out: messages that librdkafka could not deliver within timeout. These errors will cause action to be suspended but messages can be retried depending on retry options; transport: messages that librdkafka could not deliver due to transport errors. These messages can be retried depending on retry options; broker_down: messages that librdkafka could not deliver because it thinks that broker is not accessible. These messages can be retried depending on options; auth: messages that librdkafka could not deliver due to authentication errors. These messages can be retried depending on the options; ssl: messages that librdkafka could not deliver due to ssl errors. These messages can be retried depending on the options; other: rest of librdkafka errors" 52 | ) 53 | 54 | func newOmkafkaFromJSON(b []byte) (*omkafka, error) { 55 | var pstat omkafka 56 | err := json.Unmarshal(b, &pstat) 57 | if err != nil { 58 | return nil, fmt.Errorf("failed to decode omkafka stat `%v`: %v", string(b), err) 59 | } 60 | return &pstat, nil 61 | } 62 | 63 | func (o *omkafka) toPoints() []*point { 64 | points := make([]*point, 22) 65 | 66 | // A input_submitted metric was always created for omkafka 67 | // as statType filter matched "submitted". Ensure we still 68 | // emit that metric for backwards compatibility. 69 | points[0] = &point{ 70 | Name: "input_submitted", 71 | Type: counter, 72 | Value: o.Submitted, 73 | Description: "messages submitted", 74 | LabelName: "input", 75 | LabelValue: o.Name, 76 | } 77 | points[1] = &point{ 78 | Name: "omkafka_messages", 79 | Type: counter, 80 | Value: o.Submitted, 81 | Description: messagesDescription, 82 | LabelName: "type", 83 | LabelValue: "submitted", 84 | } 85 | points[2] = &point{ 86 | Name: "omkafka_maxoutqsize", 87 | Type: counter, 88 | Value: o.MaxOutQSize, 89 | Description: "high water mark of output queue size", 90 | } 91 | 92 | points[3] = &point{ 93 | Name: "omkafka_messages", 94 | Type: counter, 95 | Value: o.Failures, 96 | Description: messagesDescription, 97 | LabelName: "type", 98 | LabelValue: "failures", 99 | } 100 | 101 | points[4] = &point{ 102 | Name: "omkafka_topicdynacache", 103 | Type: counter, 104 | Value: o.TopicDynacacheSkipped, 105 | Description: topicDynaCacheDescription, 106 | LabelName: "type", 107 | LabelValue: "skipped", 108 | } 109 | 110 | points[5] = &point{ 111 | Name: "omkafka_topicdynacache", 112 | Type: counter, 113 | Value: o.TopicDynacacheMiss, 114 | Description: topicDynaCacheDescription, 115 | LabelName: "type", 116 | LabelValue: "miss", 117 | } 118 | 119 | points[6] = &point{ 120 | Name: "omkafka_topicdynacache", 121 | Type: counter, 122 | Value: o.TopicDynacacheEvicted, 123 | Description: topicDynaCacheDescription, 124 | LabelName: "type", 125 | LabelValue: "evicted", 126 | } 127 | 128 | points[7] = &point{ 129 | Name: "omkafka_messages", 130 | Type: counter, 131 | Value: o.Acked, 132 | Description: messagesDescription, 133 | LabelName: "type", 134 | LabelValue: "acked", 135 | } 136 | 137 | points[8] = &point{ 138 | Name: "omkafka_failures", 139 | Type: counter, 140 | Value: o.FailuresMsgTooLarge, 141 | Description: failuresDescription, 142 | LabelName: "type", 143 | LabelValue: "msg_too_large", 144 | } 145 | 146 | points[9] = &point{ 147 | Name: "omkafka_failures", 148 | Type: counter, 149 | Value: o.FailuresUnknownTopic, 150 | Description: failuresDescription, 151 | LabelName: "type", 152 | LabelValue: "unknown_topic", 153 | } 154 | 155 | points[10] = &point{ 156 | Name: "omkafka_failures", 157 | Type: counter, 158 | Value: o.FailuresQueueFull, 159 | Description: failuresDescription, 160 | LabelName: "type", 161 | LabelValue: "queue_full", 162 | } 163 | 164 | points[11] = &point{ 165 | Name: "omkafka_failures", 166 | Type: counter, 167 | Value: o.FailuresUnknownPartition, 168 | Description: failuresDescription, 169 | LabelName: "type", 170 | LabelValue: "unknown_partition", 171 | } 172 | 173 | points[12] = &point{ 174 | Name: "omkafka_failures", 175 | Type: counter, 176 | Value: o.FailuresOther, 177 | Description: failuresDescription, 178 | LabelName: "type", 179 | LabelValue: "other", 180 | } 181 | 182 | points[13] = &point{ 183 | Name: "omkafka_errors", 184 | Type: counter, 185 | Value: o.ErrorsTimedOut, 186 | Description: errorsDescription, 187 | LabelName: "type", 188 | LabelValue: "timed_out", 189 | } 190 | 191 | points[14] = &point{ 192 | Name: "omkafka_errors", 193 | Type: counter, 194 | Value: o.ErrorsTransport, 195 | Description: errorsDescription, 196 | LabelName: "type", 197 | LabelValue: "transport", 198 | } 199 | 200 | points[15] = &point{ 201 | Name: "omkafka_errors", 202 | Type: counter, 203 | Value: o.ErrorsBrokerDown, 204 | Description: errorsDescription, 205 | LabelName: "type", 206 | LabelValue: "broker_down", 207 | } 208 | 209 | points[16] = &point{ 210 | Name: "omkafka_errors", 211 | Type: counter, 212 | Value: o.ErrorsAuth, 213 | Description: errorsDescription, 214 | LabelName: "type", 215 | LabelValue: "auth", 216 | } 217 | 218 | points[17] = &point{ 219 | Name: "omkafka_errors", 220 | Type: counter, 221 | Value: o.ErrorsSSL, 222 | Description: errorsDescription, 223 | LabelName: "type", 224 | LabelValue: "ssl", 225 | } 226 | 227 | points[18] = &point{ 228 | Name: "omkafka_errors", 229 | Type: counter, 230 | Value: o.ErrorsOther, 231 | Description: errorsDescription, 232 | LabelName: "type", 233 | LabelValue: "other", 234 | } 235 | 236 | points[19] = &point{ 237 | Name: "omkafka_rtt_avg_usec_acg", 238 | Type: gauge, 239 | Value: o.RttAvgUsec, 240 | Description: "broker round trip time in microseconds averaged over all brokers. It is based on the statistics callback window specified through statistics.interval.ms parameter to librdkafka. Average exclude brokers with less than 100 microseconds rtt", 241 | } 242 | 243 | points[20] = &point{ 244 | Name: "omkafka_throttle_avg_msec_avg", 245 | Type: gauge, 246 | Value: o.ThrottleAvgMsec, 247 | Description: "broker throttling time in milliseconds averaged over all brokers. This is also a part of window statistics delivered by librdkakfka. Average excludes brokers with zero throttling time", 248 | } 249 | 250 | points[21] = &point{ 251 | Name: "omkafka_int_latency_avg_usec_avg", 252 | Type: gauge, 253 | Value: o.IntLatencyAvgUsec, 254 | Description: "internal librdkafka producer queue latency in microseconds averaged other all brokers. This is also part of window statistics and average excludes brokers with zero internal latency", 255 | } 256 | 257 | return points 258 | } 259 | -------------------------------------------------------------------------------- /omkafka_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Prometheus Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package main 15 | 16 | import ( 17 | "fmt" 18 | "testing" 19 | ) 20 | 21 | var ( 22 | omkafkaLog = []byte(`{ "name": "omkafka", "origin": "omkafka", "submitted": 59, "maxoutqsize": 9, "failures": 0, "topicdynacache.skipped": 57, "topicdynacache.miss": 2, "topicdynacache.evicted": 0, "acked": 55, "failures_msg_too_large": 0, "failures_unknown_topic": 0, "failures_queue_full": 0, "failures_unknown_partition": 0, "failures_other": 0, "errors_timed_out": 0, "errors_transport": 0, "errors_broker_down": 0, "errors_auth": 0, "errors_ssl": 0, "errors_other": 0, "rtt_avg_usec": 0, "throttle_avg_msec": 0, "int_latency_avg_usec": 0 }`) 23 | ) 24 | 25 | func TestNewOmkafkaFromJSON(t *testing.T) { 26 | logType := getStatType(omkafkaLog) 27 | if logType != rsyslogOmkafka { 28 | t.Errorf("detected pstat type should be %d but is %d", rsyslogOmkafka, logType) 29 | } 30 | 31 | _, err := newOmkafkaFromJSON([]byte(omkafkaLog)) 32 | if err != nil { 33 | t.Fatalf("expected parsing action not to fail, got: %v", err) 34 | } 35 | } 36 | 37 | func TestOmkafkaToPoints(t *testing.T) { 38 | pstat, err := newOmkafkaFromJSON([]byte(omkafkaLog)) 39 | if err != nil { 40 | t.Fatalf("expected parsing action not to fail, got: %v", err) 41 | } 42 | points := pstat.toPoints() 43 | 44 | testCases := []*point{ 45 | { 46 | Name: "input_submitted", 47 | Type: counter, 48 | Value: 59, 49 | LabelValue: "omkafka", 50 | }, 51 | { 52 | Name: "omkafka_messages", 53 | Type: counter, 54 | Value: 59, 55 | LabelValue: "submitted", 56 | }, 57 | { 58 | Name: "omkafka_maxoutqsize", 59 | Type: counter, 60 | Value: 9, 61 | }, 62 | { 63 | Name: "omkafka_messages", 64 | Type: counter, 65 | Value: 0, 66 | LabelValue: "failures", 67 | }, 68 | { 69 | Name: "omkafka_topicdynacache", 70 | Type: counter, 71 | Value: 57, 72 | LabelValue: "skipped", 73 | }, 74 | { 75 | Name: "omkafka_topicdynacache", 76 | Type: counter, 77 | Value: 2, 78 | LabelValue: "miss", 79 | }, 80 | { 81 | Name: "omkafka_topicdynacache", 82 | Type: counter, 83 | Value: 0, 84 | LabelValue: "evicted", 85 | }, 86 | { 87 | Name: "omkafka_messages", 88 | Type: counter, 89 | Value: 55, 90 | LabelValue: "acked", 91 | }, 92 | { 93 | Name: "omkafka_failures", 94 | Type: counter, 95 | Value: 0, 96 | LabelValue: "msg_too_large", 97 | }, 98 | 99 | { 100 | Name: "omkafka_failures", 101 | Type: counter, 102 | Value: 0, 103 | LabelValue: "unknown_topic", 104 | }, 105 | { 106 | Name: "omkafka_failures", 107 | Type: counter, 108 | Value: 0, 109 | LabelValue: "queue_full", 110 | }, 111 | { 112 | Name: "omkafka_failures", 113 | Type: counter, 114 | Value: 0, 115 | LabelValue: "unknown_partition", 116 | }, 117 | { 118 | Name: "omkafka_failures", 119 | Type: counter, 120 | Value: 0, 121 | LabelValue: "other", 122 | }, 123 | { 124 | Name: "omkafka_errors", 125 | Type: counter, 126 | Value: 0, 127 | LabelValue: "timed_out", 128 | }, 129 | { 130 | Name: "omkafka_errors", 131 | Type: counter, 132 | Value: 0, 133 | LabelValue: "transport", 134 | }, 135 | { 136 | Name: "omkafka_errors", 137 | Type: counter, 138 | Value: 0, 139 | LabelValue: "broker_down", 140 | }, 141 | { 142 | Name: "omkafka_errors", 143 | Type: counter, 144 | Value: 0, 145 | LabelValue: "auth", 146 | }, 147 | { 148 | Name: "omkafka_errors", 149 | Type: counter, 150 | Value: 0, 151 | LabelValue: "ssl", 152 | }, 153 | { 154 | Name: "omkafka_errors", 155 | Type: counter, 156 | Value: 0, 157 | LabelValue: "other", 158 | }, 159 | { 160 | Name: "omkafka_rtt_avg_usec_acg", 161 | Type: gauge, 162 | Value: 0, 163 | }, 164 | { 165 | Name: "omkafka_throttle_avg_msec_avg", 166 | Type: gauge, 167 | Value: 0, 168 | }, 169 | { 170 | Name: "omkafka_int_latency_avg_usec_avg", 171 | Type: gauge, 172 | Value: 0, 173 | }, 174 | } 175 | 176 | for idx, tc := range testCases { 177 | t.Run(fmt.Sprintf("point idx %d", idx), func(t *testing.T) { 178 | p := points[idx] 179 | if p.Name != tc.Name { 180 | t.Errorf("got name %s; want %s", p.Name, tc.Name) 181 | } 182 | if p.Type != tc.Type { 183 | t.Errorf("got type %d; %d", p.Type, tc.Type) 184 | } 185 | if p.Value != tc.Value { 186 | t.Errorf("got value %d; %d", p.Value, tc.Value) 187 | } 188 | if p.LabelValue != tc.LabelValue { 189 | t.Errorf("got label value %s; %s", p.LabelValue, tc.LabelValue) 190 | } 191 | }) 192 | } 193 | 194 | } 195 | -------------------------------------------------------------------------------- /point.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Prometheus Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package main 15 | 16 | import ( 17 | "fmt" 18 | 19 | "github.com/prometheus/client_golang/prometheus" 20 | ) 21 | 22 | type pointType int 23 | 24 | const ( 25 | counter pointType = iota 26 | gauge 27 | ) 28 | 29 | type point struct { 30 | Name string 31 | Description string 32 | Type pointType 33 | Value int64 34 | LabelName string 35 | LabelValue string 36 | } 37 | 38 | func (p *point) promDescription() *prometheus.Desc { 39 | variableLabels := []string{} 40 | if p.promLabelName() != "" { 41 | variableLabels = []string{p.promLabelName()} 42 | } 43 | return prometheus.NewDesc( 44 | prometheus.BuildFQName("", "rsyslog", p.Name), 45 | p.Description, 46 | variableLabels, 47 | nil, 48 | ) 49 | } 50 | 51 | func (p *point) promType() prometheus.ValueType { 52 | if p.Type == counter { 53 | return prometheus.CounterValue 54 | } 55 | return prometheus.GaugeValue 56 | } 57 | 58 | func (p *point) promValue() float64 { 59 | return float64(p.Value) 60 | } 61 | 62 | func (p *point) promLabelValue() string { 63 | return p.LabelValue 64 | } 65 | 66 | func (p *point) promLabelName() string { 67 | return p.LabelName 68 | } 69 | 70 | func (p *point) key() string { 71 | return fmt.Sprintf("%s.%s", p.Name, p.LabelValue) 72 | } 73 | -------------------------------------------------------------------------------- /point_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Prometheus Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package main 15 | 16 | import ( 17 | "testing" 18 | 19 | "github.com/prometheus/client_golang/prometheus" 20 | ) 21 | 22 | func TestCounter(t *testing.T) { 23 | p1 := &point{ 24 | Name: "my counter", 25 | Type: counter, 26 | Value: int64(10), 27 | } 28 | 29 | if want, got := float64(10), p1.promValue(); want != got { 30 | t.Errorf("want '%f', got '%f'", want, got) 31 | } 32 | 33 | if want, got := prometheus.ValueType(1), p1.promType(); want != got { 34 | t.Errorf("want '%v', got '%v'", want, got) 35 | } 36 | 37 | wanted := `Desc{fqName: "rsyslog_my counter", help: "", constLabels: {}, variableLabels: []}` 38 | if want, got := wanted, p1.promDescription().String(); want != got { 39 | t.Errorf("want '%s', got '%s'", want, got) 40 | } 41 | } 42 | 43 | func TestGauge(t *testing.T) { 44 | p1 := &point{ 45 | Name: "my gauge", 46 | Type: gauge, 47 | Value: int64(10), 48 | } 49 | 50 | if want, got := float64(10), p1.promValue(); want != got { 51 | t.Errorf("want '%f', got '%f'", want, got) 52 | } 53 | 54 | if want, got := prometheus.ValueType(2), p1.promType(); want != got { 55 | t.Errorf("want '%v', got '%v'", want, got) 56 | } 57 | 58 | wanted := `Desc{fqName: "rsyslog_my gauge", help: "", constLabels: {}, variableLabels: []}` 59 | if want, got := wanted, p1.promDescription().String(); want != got { 60 | t.Errorf("want '%s', got '%s'", want, got) 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /pointstore.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Prometheus Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package main 15 | 16 | import ( 17 | "errors" 18 | "sort" 19 | "sync" 20 | ) 21 | 22 | var ( 23 | errPointNotFound = errors.New("point does not exist") 24 | ) 25 | 26 | type pointStore struct { 27 | pointMap map[string]*point 28 | lock *sync.RWMutex 29 | } 30 | 31 | func newPointStore() *pointStore { 32 | return &pointStore{ 33 | pointMap: make(map[string]*point), 34 | lock: &sync.RWMutex{}, 35 | } 36 | } 37 | 38 | func (ps *pointStore) keys() []string { 39 | ps.lock.Lock() 40 | keys := make([]string, 0) 41 | for k := range ps.pointMap { 42 | keys = append(keys, k) 43 | } 44 | sort.Strings(keys) 45 | ps.lock.Unlock() 46 | return keys 47 | } 48 | 49 | func (ps *pointStore) set(p *point) error { 50 | var err error 51 | ps.lock.Lock() 52 | ps.pointMap[p.key()] = p 53 | ps.lock.Unlock() 54 | return err 55 | } 56 | 57 | func (ps *pointStore) get(name string) (*point, error) { 58 | ps.lock.Lock() 59 | if p, ok := ps.pointMap[name]; ok { 60 | ps.lock.Unlock() 61 | return p, nil 62 | } 63 | ps.lock.Unlock() 64 | return &point{}, errPointNotFound 65 | } 66 | -------------------------------------------------------------------------------- /pointstore_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Prometheus Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package main 15 | 16 | import "testing" 17 | 18 | func TestPointStore(t *testing.T) { 19 | ps := newPointStore() 20 | 21 | s1 := &point{ 22 | Name: "my counter", 23 | Type: counter, 24 | Value: int64(10), 25 | } 26 | 27 | s2 := &point{ 28 | Name: "my counter", 29 | Type: counter, 30 | Value: int64(5), 31 | } 32 | 33 | err := ps.set(s1) 34 | if err != nil { 35 | t.Error(err) 36 | } 37 | 38 | got, err := ps.get(s1.key()) 39 | if err != nil { 40 | t.Error(err) 41 | } 42 | 43 | if want, got := int64(10), got.Value; want != got { 44 | t.Errorf("want '%d', got '%d'", want, got) 45 | } 46 | 47 | err = ps.set(s2) 48 | if err != nil { 49 | t.Error(err) 50 | } 51 | 52 | got, err = ps.get(s2.key()) 53 | if err != nil { 54 | t.Error(err) 55 | } 56 | 57 | if want, got := int64(5), got.Value; want != got { 58 | t.Errorf("want '%d', got '%d'", want, got) 59 | } 60 | 61 | s3 := &point{ 62 | Name: "my gauge", 63 | Type: gauge, 64 | Value: int64(20), 65 | } 66 | 67 | err = ps.set(s3) 68 | if err != nil { 69 | t.Error(err) 70 | } 71 | 72 | got, err = ps.get(s3.key()) 73 | if err != nil { 74 | t.Error(err) 75 | } 76 | 77 | if want, got := int64(20), got.Value; want != got { 78 | t.Errorf("want '%d', got '%d'", want, got) 79 | } 80 | 81 | s4 := &point{ 82 | Name: "my gauge", 83 | Type: gauge, 84 | Value: int64(15), 85 | } 86 | 87 | err = ps.set(s4) 88 | if err != nil { 89 | t.Error(err) 90 | } 91 | 92 | got, err = ps.get(s4.key()) 93 | if err != nil { 94 | t.Error(err) 95 | } 96 | 97 | if want, got := int64(15), got.Value; want != got { 98 | t.Errorf("want '%d', got '%d'", want, got) 99 | } 100 | 101 | _, err = ps.get("no point") 102 | if err != errPointNotFound { 103 | t.Error("getting non existent point should raise error") 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /queues.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Prometheus Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package main 15 | 16 | import ( 17 | "encoding/json" 18 | "fmt" 19 | ) 20 | 21 | type queue struct { 22 | Name string `json:"name"` 23 | Size int64 `json:"size"` 24 | Enqueued int64 `json:"enqueued"` 25 | Full int64 `json:"full"` 26 | DiscardedFull int64 `json:"discarded.full"` 27 | DiscardedNf int64 `json:"discarded.nf"` 28 | MaxQsize int64 `json:"maxqsize"` 29 | } 30 | 31 | func newQueueFromJSON(b []byte) (*queue, error) { 32 | var pstat queue 33 | err := json.Unmarshal(b, &pstat) 34 | if err != nil { 35 | return nil, fmt.Errorf("failed to decode queue stat `%v`: %v", string(b), err) 36 | } 37 | return &pstat, nil 38 | } 39 | 40 | func (q *queue) toPoints() []*point { 41 | points := make([]*point, 6) 42 | 43 | points[0] = &point{ 44 | Name: "queue_size", 45 | Type: gauge, 46 | Value: q.Size, 47 | Description: "messages currently in queue", 48 | LabelName: "queue", 49 | LabelValue: q.Name, 50 | } 51 | 52 | points[1] = &point{ 53 | Name: "queue_enqueued", 54 | Type: counter, 55 | Value: q.Enqueued, 56 | Description: "total messages enqueued", 57 | LabelName: "queue", 58 | LabelValue: q.Name, 59 | } 60 | 61 | points[2] = &point{ 62 | Name: "queue_full", 63 | Type: counter, 64 | Value: q.Full, 65 | Description: "times queue was full", 66 | LabelName: "queue", 67 | LabelValue: q.Name, 68 | } 69 | 70 | points[3] = &point{ 71 | Name: "queue_discarded_full", 72 | Type: counter, 73 | Value: q.DiscardedFull, 74 | Description: "messages discarded due to queue being full", 75 | LabelName: "queue", 76 | LabelValue: q.Name, 77 | } 78 | 79 | points[4] = &point{ 80 | Name: "queue_discarded_not_full", 81 | Type: counter, 82 | Value: q.DiscardedNf, 83 | Description: "messages discarded when queue not full", 84 | LabelName: "queue", 85 | LabelValue: q.Name, 86 | } 87 | 88 | points[5] = &point{ 89 | Name: "queue_max_size", 90 | Type: gauge, 91 | Value: q.MaxQsize, 92 | Description: "maximum size queue has reached", 93 | LabelName: "queue", 94 | LabelValue: q.Name, 95 | } 96 | 97 | return points 98 | } 99 | -------------------------------------------------------------------------------- /queues_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Prometheus Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package main 15 | 16 | import "testing" 17 | 18 | var ( 19 | queueStat = []byte(`{"name":"main Q","size":10,"enqueued":20,"full":30,"discarded.full":40,"discarded.nf":50,"maxqsize":60}`) 20 | ) 21 | 22 | func TestNewQueueFromJSON(t *testing.T) { 23 | logType := getStatType(queueStat) 24 | if logType != rsyslogQueue { 25 | t.Errorf("detected pstat type should be %d but is %d", rsyslogQueue, logType) 26 | } 27 | 28 | pstat, err := newQueueFromJSON([]byte(queueStat)) 29 | if err != nil { 30 | t.Fatalf("expected parsing queue stat not to fail, got: %v", err) 31 | } 32 | 33 | if want, got := "main Q", pstat.Name; want != got { 34 | t.Errorf("want '%s', got '%s'", want, got) 35 | } 36 | 37 | if want, got := int64(10), pstat.Size; want != got { 38 | t.Errorf("want '%d', got '%d'", want, got) 39 | } 40 | 41 | if want, got := int64(20), pstat.Enqueued; want != got { 42 | t.Errorf("want '%d', got '%d'", want, got) 43 | } 44 | 45 | if want, got := int64(30), pstat.Full; want != got { 46 | t.Errorf("want '%d', got '%d'", want, got) 47 | } 48 | 49 | if want, got := int64(40), pstat.DiscardedFull; want != got { 50 | t.Errorf("want '%d', got '%d'", want, got) 51 | } 52 | 53 | if want, got := int64(50), pstat.DiscardedNf; want != got { 54 | t.Errorf("want '%d', got '%d'", want, got) 55 | } 56 | 57 | if want, got := int64(60), pstat.MaxQsize; want != got { 58 | t.Errorf("want '%d', got '%d'", want, got) 59 | } 60 | } 61 | 62 | func TestQueueToPoints(t *testing.T) { 63 | pstat, err := newQueueFromJSON([]byte(queueStat)) 64 | if err != nil { 65 | t.Fatalf("expected parsing queue stat not to fail, got: %v", err) 66 | } 67 | points := pstat.toPoints() 68 | 69 | point := points[0] 70 | if want, got := "queue_size", point.Name; want != got { 71 | t.Errorf("want '%s', got '%s'", want, got) 72 | } 73 | 74 | if want, got := int64(10), point.Value; want != got { 75 | } 76 | 77 | if want, got := gauge, point.Type; want != got { 78 | } 79 | 80 | if want, got := "main Q", point.LabelValue; want != got { 81 | t.Errorf("wanted '%s', got '%s'", want, got) 82 | } 83 | 84 | point = points[1] 85 | if want, got := "queue_enqueued", point.Name; want != got { 86 | t.Errorf("want '%s', got '%s'", want, got) 87 | } 88 | 89 | if want, got := int64(20), point.Value; want != got { 90 | t.Errorf("want '%d', got '%d'", want, got) 91 | } 92 | 93 | if want, got := counter, point.Type; want != got { 94 | t.Errorf("want '%d', got '%d'", want, got) 95 | } 96 | 97 | if want, got := "main Q", point.LabelValue; want != got { 98 | t.Errorf("wanted '%s', got '%s'", want, got) 99 | } 100 | 101 | point = points[2] 102 | if want, got := "queue_full", point.Name; want != got { 103 | t.Errorf("want '%s', got '%s'", want, got) 104 | } 105 | 106 | if want, got := int64(30), point.Value; want != got { 107 | t.Errorf("want '%d', got '%d'", want, got) 108 | } 109 | 110 | if want, got := counter, point.Type; want != got { 111 | t.Errorf("want '%d', got '%d'", want, got) 112 | } 113 | 114 | if want, got := "main Q", point.LabelValue; want != got { 115 | t.Errorf("wanted '%s', got '%s'", want, got) 116 | } 117 | 118 | point = points[3] 119 | if want, got := "queue_discarded_full", point.Name; want != got { 120 | t.Errorf("want '%s', got '%s'", want, got) 121 | } 122 | 123 | if want, got := int64(40), point.Value; want != got { 124 | t.Errorf("want '%d', got '%d'", want, got) 125 | } 126 | 127 | if want, got := counter, point.Type; want != got { 128 | t.Errorf("want '%d', got '%d'", want, got) 129 | } 130 | 131 | if want, got := "main Q", point.LabelValue; want != got { 132 | t.Errorf("wanted '%s', got '%s'", want, got) 133 | } 134 | 135 | point = points[4] 136 | if want, got := "queue_discarded_not_full", point.Name; want != got { 137 | t.Errorf("want '%s', got '%s'", want, got) 138 | } 139 | 140 | if want, got := int64(50), point.Value; want != got { 141 | t.Errorf("want '%d', got '%d'", want, got) 142 | } 143 | 144 | if want, got := counter, point.Type; want != got { 145 | t.Errorf("want '%d', got '%d'", want, got) 146 | } 147 | 148 | if want, got := "main Q", point.LabelValue; want != got { 149 | t.Errorf("wanted '%s', got '%s'", want, got) 150 | } 151 | 152 | point = points[5] 153 | if want, got := "queue_max_size", point.Name; want != got { 154 | t.Errorf("want '%s', got '%s'", want, got) 155 | } 156 | 157 | if want, got := int64(60), point.Value; want != got { 158 | t.Errorf("want '%d', got '%d'", want, got) 159 | } 160 | 161 | if want, got := gauge, point.Type; want != got { 162 | t.Errorf("want '%d', got '%d'", want, got) 163 | } 164 | 165 | if want, got := "main Q", point.LabelValue; want != got { 166 | t.Errorf("wanted '%s', got '%s'", want, got) 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /resources.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Prometheus Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package main 15 | 16 | import ( 17 | "encoding/json" 18 | "fmt" 19 | ) 20 | 21 | type resource struct { 22 | Name string `json:"name"` 23 | Utime int64 `json:"utime"` 24 | Stime int64 `json:"stime"` 25 | Maxrss int64 `json:"maxrss"` 26 | Minflt int64 `json:"minflt"` 27 | Majflt int64 `json:"majflt"` 28 | Inblock int64 `json:"inblock"` 29 | Outblock int64 `json:"oublock"` 30 | Nvcsw int64 `json:"nvcsw"` 31 | Nivcsw int64 `json:"nivcsw"` 32 | } 33 | 34 | func newResourceFromJSON(b []byte) (*resource, error) { 35 | var pstat resource 36 | err := json.Unmarshal(b, &pstat) 37 | if err != nil { 38 | return nil, fmt.Errorf("failed to decode resource stat `%v`: %v", string(b), err) 39 | } 40 | return &pstat, nil 41 | } 42 | 43 | func (r *resource) toPoints() []*point { 44 | points := make([]*point, 9) 45 | 46 | points[0] = &point{ 47 | Name: "resource_utime", 48 | Type: counter, 49 | Value: r.Utime, 50 | Description: "user time used in microseconds", 51 | LabelName: "resource", 52 | LabelValue: r.Name, 53 | } 54 | 55 | points[1] = &point{ 56 | Name: "resource_stime", 57 | Type: counter, 58 | Value: r.Stime, 59 | Description: "system time used in microsends", 60 | LabelName: "resource", 61 | LabelValue: r.Name, 62 | } 63 | 64 | points[2] = &point{ 65 | Name: "resource_maxrss", 66 | Type: gauge, 67 | Value: r.Maxrss, 68 | Description: "maximum resident set size", 69 | LabelName: "resource", 70 | LabelValue: r.Name, 71 | } 72 | 73 | points[3] = &point{ 74 | Name: "resource_minflt", 75 | Type: counter, 76 | Value: r.Minflt, 77 | Description: "total minor faults", 78 | LabelName: "resource", 79 | LabelValue: r.Name, 80 | } 81 | 82 | points[4] = &point{ 83 | Name: "resource_majflt", 84 | Type: counter, 85 | Value: r.Majflt, 86 | Description: "total major faults", 87 | LabelName: "resource", 88 | LabelValue: r.Name, 89 | } 90 | 91 | points[5] = &point{ 92 | Name: "resource_inblock", 93 | Type: counter, 94 | Value: r.Inblock, 95 | Description: "filesystem input operations", 96 | LabelName: "resource", 97 | LabelValue: r.Name, 98 | } 99 | 100 | points[6] = &point{ 101 | Name: "resource_oublock", 102 | Type: counter, 103 | Value: r.Outblock, 104 | Description: "filesystem output operations", 105 | LabelName: "resource", 106 | LabelValue: r.Name, 107 | } 108 | 109 | points[7] = &point{ 110 | Name: "resource_nvcsw", 111 | Type: counter, 112 | Value: r.Nvcsw, 113 | Description: "voluntary context switches", 114 | LabelName: "resource", 115 | LabelValue: r.Name, 116 | } 117 | 118 | points[8] = &point{ 119 | Name: "resource_nivcsw", 120 | Type: counter, 121 | Value: r.Nivcsw, 122 | Description: "involuntary context switches", 123 | LabelName: "resource", 124 | LabelValue: r.Name, 125 | } 126 | 127 | return points 128 | } 129 | -------------------------------------------------------------------------------- /resources_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Prometheus Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package main 15 | 16 | import "testing" 17 | 18 | var ( 19 | resourceLog = []byte(`{"name":"resource-usage","utime":10,"stime":20,"maxrss":30,"minflt":40,"majflt":50,"inblock":60,"oublock":70,"nvcsw":80,"nivcsw":90}`) 20 | ) 21 | 22 | func TestNewResourceFromJSON(t *testing.T) { 23 | logType := getStatType(resourceLog) 24 | if logType != rsyslogResource { 25 | t.Errorf("detected pstat type should be %d but is %d", rsyslogResource, logType) 26 | } 27 | 28 | pstat, err := newResourceFromJSON([]byte(resourceLog)) 29 | if err != nil { 30 | t.Fatalf("expected parsing resource stat not to fail, got: %v", err) 31 | } 32 | 33 | if want, got := "resource-usage", pstat.Name; want != got { 34 | t.Errorf("want '%s', got '%s'", want, got) 35 | } 36 | 37 | if want, got := int64(10), pstat.Utime; want != got { 38 | t.Errorf("want '%d', got '%d'", want, got) 39 | } 40 | 41 | if want, got := int64(20), pstat.Stime; want != got { 42 | t.Errorf("want '%d', got '%d'", want, got) 43 | } 44 | 45 | if want, got := int64(30), pstat.Maxrss; want != got { 46 | t.Errorf("want '%d', got '%d'", want, got) 47 | } 48 | 49 | if want, got := int64(40), pstat.Minflt; want != got { 50 | t.Errorf("want '%d', got '%d'", want, got) 51 | } 52 | 53 | if want, got := int64(50), pstat.Majflt; want != got { 54 | t.Errorf("want '%d', got '%d'", want, got) 55 | } 56 | 57 | if want, got := int64(60), pstat.Inblock; want != got { 58 | t.Errorf("want '%d', got '%d'", want, got) 59 | } 60 | 61 | if want, got := int64(70), pstat.Outblock; want != got { 62 | t.Errorf("want '%d', got '%d'", want, got) 63 | } 64 | 65 | if want, got := int64(80), pstat.Nvcsw; want != got { 66 | t.Errorf("want '%d', got '%d'", want, got) 67 | } 68 | 69 | if want, got := int64(90), pstat.Nivcsw; want != got { 70 | t.Errorf("want '%d', got '%d'", want, got) 71 | } 72 | } 73 | 74 | func TestResourceToPoints(t *testing.T) { 75 | pstat, err := newResourceFromJSON([]byte(resourceLog)) 76 | if err != nil { 77 | t.Fatalf("expected parsing resource stat not to fail, got: %v", err) 78 | } 79 | points := pstat.toPoints() 80 | 81 | point := points[0] 82 | if want, got := "resource_utime", point.Name; want != got { 83 | t.Errorf("want '%s', got '%s'", want, got) 84 | } 85 | 86 | if want, got := int64(10), point.Value; want != got { 87 | t.Errorf("want '%d', got '%d'", want, got) 88 | } 89 | 90 | if want, got := counter, point.Type; want != got { 91 | t.Errorf("want '%d', got '%d'", want, got) 92 | } 93 | 94 | if want, got := "resource-usage", point.LabelValue; want != got { 95 | t.Errorf("wanted '%s', got '%s'", want, got) 96 | } 97 | 98 | point = points[1] 99 | if want, got := "resource_stime", point.Name; want != got { 100 | t.Errorf("want '%s', got '%s'", want, got) 101 | } 102 | 103 | if want, got := int64(20), point.Value; want != got { 104 | t.Errorf("want '%d', got '%d'", want, got) 105 | } 106 | 107 | if want, got := counter, point.Type; want != got { 108 | t.Errorf("want '%d', got '%d'", want, got) 109 | } 110 | 111 | if want, got := "resource-usage", point.LabelValue; want != got { 112 | t.Errorf("wanted '%s', got '%s'", want, got) 113 | } 114 | 115 | point = points[2] 116 | if want, got := "resource_maxrss", point.Name; want != got { 117 | t.Errorf("want '%s', got '%s'", want, got) 118 | } 119 | 120 | if want, got := int64(30), point.Value; want != got { 121 | t.Errorf("want '%d', got '%d'", want, got) 122 | } 123 | 124 | if want, got := gauge, point.Type; want != got { 125 | t.Errorf("want '%d', got '%d'", want, got) 126 | } 127 | 128 | if want, got := "resource-usage", point.LabelValue; want != got { 129 | t.Errorf("wanted '%s', got '%s'", want, got) 130 | } 131 | 132 | point = points[3] 133 | if want, got := "resource_minflt", point.Name; want != got { 134 | t.Errorf("want '%s', got '%s'", want, got) 135 | } 136 | 137 | if want, got := int64(40), point.Value; want != got { 138 | t.Errorf("want '%d', got '%d'", want, got) 139 | } 140 | 141 | if want, got := counter, point.Type; want != got { 142 | t.Errorf("want '%d', got '%d'", want, got) 143 | } 144 | 145 | if want, got := "resource-usage", point.LabelValue; want != got { 146 | t.Errorf("wanted '%s', got '%s'", want, got) 147 | } 148 | 149 | point = points[4] 150 | if want, got := "resource_majflt", point.Name; want != got { 151 | t.Errorf("want '%s', got '%s'", want, got) 152 | } 153 | 154 | if want, got := int64(50), point.Value; want != got { 155 | t.Errorf("want '%d', got '%d'", want, got) 156 | } 157 | 158 | if want, got := counter, point.Type; want != got { 159 | t.Errorf("want '%d', got '%d'", want, got) 160 | } 161 | 162 | if want, got := "resource-usage", point.LabelValue; want != got { 163 | t.Errorf("wanted '%s', got '%s'", want, got) 164 | } 165 | 166 | point = points[5] 167 | if want, got := "resource_inblock", point.Name; want != got { 168 | t.Errorf("want '%s', got '%s'", want, got) 169 | } 170 | 171 | if want, got := int64(60), point.Value; want != got { 172 | t.Errorf("want '%d', got '%d'", want, got) 173 | } 174 | 175 | if want, got := counter, point.Type; want != got { 176 | t.Errorf("want '%d', got '%d'", want, got) 177 | } 178 | 179 | if want, got := "resource-usage", point.LabelValue; want != got { 180 | t.Errorf("wanted '%s', got '%s'", want, got) 181 | } 182 | 183 | point = points[6] 184 | if want, got := "resource_oublock", point.Name; want != got { 185 | t.Errorf("want '%s', got '%s'", want, got) 186 | } 187 | 188 | if want, got := int64(70), point.Value; want != got { 189 | t.Errorf("want '%d', got '%d'", want, got) 190 | } 191 | 192 | if want, got := counter, point.Type; want != got { 193 | t.Errorf("want '%d', got '%d'", want, got) 194 | } 195 | 196 | if want, got := "resource-usage", point.LabelValue; want != got { 197 | t.Errorf("wanted '%s', got '%s'", want, got) 198 | } 199 | 200 | point = points[7] 201 | if want, got := "resource_nvcsw", point.Name; want != got { 202 | t.Errorf("want '%s', got '%s'", want, got) 203 | } 204 | 205 | if want, got := int64(80), point.Value; want != got { 206 | t.Errorf("want '%d', got '%d'", want, got) 207 | } 208 | 209 | if want, got := counter, point.Type; want != got { 210 | t.Errorf("want '%d', got '%d'", want, got) 211 | } 212 | 213 | if want, got := "resource-usage", point.LabelValue; want != got { 214 | t.Errorf("wanted '%s', got '%s'", want, got) 215 | } 216 | 217 | point = points[8] 218 | if want, got := "resource_nivcsw", point.Name; want != got { 219 | t.Errorf("want '%s', got '%s'", want, got) 220 | } 221 | 222 | if want, got := int64(90), point.Value; want != got { 223 | t.Errorf("want '%d', got '%d'", want, got) 224 | } 225 | 226 | if want, got := counter, point.Type; want != got { 227 | t.Errorf("want '%d', got '%d'", want, got) 228 | } 229 | 230 | if want, got := "resource-usage", point.LabelValue; want != got { 231 | t.Errorf("wanted '%s', got '%s'", want, got) 232 | } 233 | } 234 | -------------------------------------------------------------------------------- /utils.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Prometheus Authors 2 | // Licensed under the Apache License, Version 2.0 (the "License"); 3 | // you may not use this file except in compliance with the License. 4 | // You may obtain a copy of the License at 5 | // 6 | // http://www.apache.org/licenses/LICENSE-2.0 7 | // 8 | // Unless required by applicable law or agreed to in writing, software 9 | // distributed under the License is distributed on an "AS IS" BASIS, 10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | // See the License for the specific language governing permissions and 12 | // limitations under the License. 13 | 14 | package main 15 | 16 | import "strings" 17 | 18 | func getStatType(buf []byte) rsyslogType { 19 | line := string(buf) 20 | if strings.Contains(line, "processed") { 21 | return rsyslogAction 22 | } else if strings.Contains(line, "\"name\": \"omkafka\"") { 23 | // Not checking for just omkafka here as multiple actions may/will contain that word. 24 | // omkafka lines have a submitted field, so they need to be filtered before rsyslogInput 25 | return rsyslogOmkafka 26 | } else if strings.Contains(line, "submitted") { 27 | return rsyslogInput 28 | } else if strings.Contains(line, "called.recvmmsg") { 29 | return rsyslogInputIMDUP 30 | } else if strings.Contains(line, "enqueued") { 31 | return rsyslogQueue 32 | } else if strings.Contains(line, "utime") { 33 | return rsyslogResource 34 | } else if strings.Contains(line, "dynstats") { 35 | return rsyslogDynStat 36 | } else if strings.Contains(line, "dynafile cache") { 37 | return rsyslogDynafileCache 38 | } else if strings.Contains(line, "omfwd") { 39 | return rsyslogForward 40 | } else if strings.Contains(line, "mmkubernetes") { 41 | return rsyslogKubernetes 42 | } 43 | return rsyslogUnknown 44 | } 45 | --------------------------------------------------------------------------------