From 005930749ea634fbdfb92317075968cc5292aef1 Mon Sep 17 00:00:00 2001 From: Guilhem Lettron Date: Fri, 10 Mar 2023 16:09:02 +0100 Subject: [PATCH 1/2] feat: add script parsing for custom metrics --- cmd/server.go | 29 +++++++++- contrib/php_opcache_exporter.php | 49 ++++++++++++++++ go.mod | 4 +- scripts/gatherer.go | 95 ++++++++++++++++++++++++++++++++ 4 files changed, 174 insertions(+), 3 deletions(-) create mode 100644 contrib/php_opcache_exporter.php create mode 100644 scripts/gatherer.go diff --git a/cmd/server.go b/cmd/server.go index d1d82028..a900ce93 100644 --- a/cmd/server.go +++ b/cmd/server.go @@ -18,10 +18,12 @@ import ( "net/http" "os" "os/signal" + "strings" "syscall" "time" "github.com/hipages/php-fpm_exporter/phpfpm" + "github.com/hipages/php-fpm_exporter/scripts" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promhttp" "github.com/spf13/cobra" @@ -33,6 +35,7 @@ var ( metricsEndpoint string scrapeURIs []string fixProcessCount bool + scriptsPaths []string ) // serverCmd represents the server command @@ -71,7 +74,30 @@ to quickly create a Cobra application.`, IdleTimeout: time.Second * 60, } - http.Handle(metricsEndpoint, promhttp.Handler()) + gatherers := prometheus.Gatherers{ + prometheus.DefaultGatherer, + } + + // add scripts gatherer if scripts are defined + if len(scriptsPaths) > 0 { + + addresses := make([]string, len(scrapeURIs)) + for i, uri := range scrapeURIs { + addresses[i] = strings.SplitN(uri, ";", 2)[0] + } + + log.Debugf("Scripts to execute: %v", scriptsPaths) + log.Debugf("Scrape URIs: %v", addresses) + + sGather := &scripts.Gathered{ + Scripts: scriptsPaths, + Sockets: addresses, + } + + gatherers = append(gatherers, sGather) + } + + http.Handle(metricsEndpoint, promhttp.HandlerFor(gatherers, promhttp.HandlerOpts{})) http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { _, err := w.Write([]byte(` php-fpm_exporter @@ -125,6 +151,7 @@ func init() { serverCmd.Flags().StringVar(&metricsEndpoint, "web.telemetry-path", "/metrics", "Path under which to expose metrics.") serverCmd.Flags().StringSliceVar(&scrapeURIs, "phpfpm.scrape-uri", []string{"tcp://127.0.0.1:9000/status"}, "FastCGI address, e.g. unix:///tmp/php.sock;/status or tcp://127.0.0.1:9000/status") serverCmd.Flags().BoolVar(&fixProcessCount, "phpfpm.fix-process-count", false, "Enable to calculate process numbers via php-fpm_exporter since PHP-FPM sporadically reports wrong active/idle/total process numbers.") + serverCmd.Flags().StringSliceVar(&scriptsPaths, "scripts.path", []string{}, "PHP scripts to execute to get additional metrics.") // Workaround since vipers BindEnv is currently not working as expected (see https://github.com/spf13/viper/issues/461) diff --git a/contrib/php_opcache_exporter.php b/contrib/php_opcache_exporter.php new file mode 100644 index 00000000..56e0e88e --- /dev/null +++ b/contrib/php_opcache_exporter.php @@ -0,0 +1,49 @@ + $value) { + dump_as_prometheus_metric($value, $prefix.'_'.str_replace('.', '_', $key)); + } + } else { + var_dump($root); + die('Encountered unsupported value type'); + } +} + +// Report status & statistics +dump_as_prometheus_metric(opcache_get_status(false), 'opcache_status'); + +// Report configuration directives +dump_as_prometheus_metric(opcache_get_configuration(), 'opcache_configuration'); diff --git a/go.mod b/go.mod index 64c31def..fd3ed53a 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,8 @@ require ( github.com/gosuri/uitable v0.0.4 github.com/mitchellh/go-homedir v1.1.0 github.com/prometheus/client_golang v1.23.0 + github.com/prometheus/client_model v0.6.2 + github.com/prometheus/common v0.65.0 github.com/sirupsen/logrus v1.9.3 github.com/spf13/cobra v1.9.1 github.com/spf13/viper v1.19.0 @@ -27,8 +29,6 @@ require ( github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/pelletier/go-toml/v2 v2.2.2 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/prometheus/client_model v0.6.2 // indirect - github.com/prometheus/common v0.65.0 // indirect github.com/prometheus/procfs v0.16.1 // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect diff --git a/scripts/gatherer.go b/scripts/gatherer.go new file mode 100644 index 00000000..cf2ab9c3 --- /dev/null +++ b/scripts/gatherer.go @@ -0,0 +1,95 @@ +// Copyright 2017 Kumina, https://kumina.nl/ +// Copyright 2023 Guilhem Lettron, guilhem@barpilot.io +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License.package scripts + +package scripts + +import ( + "fmt" + "net/url" + "path" + + dto "github.com/prometheus/client_model/go" + "github.com/prometheus/common/expfmt" + fcgiclient "github.com/tomasen/fcgi_client" +) + +var ( + phpfpmSocketPathLabel = "socket_path" + phpfpmScriptPathLabel = "script_path" +) + +// Gathered collects metrics from scripts via PHP-FPM. +type Gathered struct { + Sockets []string + Scripts []string +} + +// Gather collects metrics from scripts via PHP-FPM. +func (g *Gathered) Gather() ([]*dto.MetricFamily, error) { + var result []*dto.MetricFamily + + for _, socketPath := range g.Sockets { + parsedURL, err := url.Parse(socketPath) + if err != nil { + return result, fmt.Errorf("failed to parse socket path %q: %s", socketPath, err) + } + + for _, scriptPath := range g.Scripts { + fcgi, err := fcgiclient.Dial(parsedURL.Scheme, path.Join(parsedURL.Host, parsedURL.Path)) + if err != nil { + return result, fmt.Errorf("failed to connect to %q PHP-FPM socket %q: %w", parsedURL.Scheme, parsedURL.Host, err) + } + defer fcgi.Close() + + env := make(map[string]string) + env["DOCUMENT_ROOT"] = path.Dir(scriptPath) + env["SCRIPT_FILENAME"] = scriptPath + env["SCRIPT_NAME"] = path.Base(scriptPath) + env["REQUEST_METHOD"] = "GET" + + resp, err := fcgi.Get(env) + if err != nil { + return result, fmt.Errorf("failed to get metrics from PHP-FPM socket %q: %w", socketPath, err) + } + + var parser expfmt.TextParser + parsed, err := parser.TextToMetricFamilies(resp.Body) + if err != nil { + return result, fmt.Errorf("failed to parse metrics from PHP-FPM socket %q: %w", socketPath, err) + } + + for _, metricFamily := range parsed { + for _, metric := range metricFamily.Metric { + socketPathCopy := parsedURL.String() + socketLabel := &dto.LabelPair{ + Name: &phpfpmSocketPathLabel, + Value: &socketPathCopy, + } + + scriptPathCopy := scriptPath + scriptLabel := &dto.LabelPair{ + Name: &phpfpmScriptPathLabel, + Value: &scriptPathCopy, + } + + metric.Label = append(metric.Label, socketLabel, scriptLabel) + } + result = append(result, metricFamily) + } + } + } + + return result, nil +} From e987b7b99b63a087f48ba343a2439db4b87966f7 Mon Sep 17 00:00:00 2001 From: Guilhem Lettron Date: Tue, 21 Oct 2025 10:51:31 +0200 Subject: [PATCH 2/2] test: fix golang version --- .github/workflows/release.yml | 2 +- .github/workflows/test_pr.yml | 6 +++--- .github/workflows/test_push.yml | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3e5f01e3..72e7d403 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -41,7 +41,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v5.5.0 with: - go-version: 1.17 + go-version: 1.23 id: go - name: Login to DockerHub diff --git a/.github/workflows/test_pr.yml b/.github/workflows/test_pr.yml index 40964f52..efa7f353 100644 --- a/.github/workflows/test_pr.yml +++ b/.github/workflows/test_pr.yml @@ -13,7 +13,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v5.5.0 with: - go-version: 1.17 + go-version: 1.23 id: go - name: Checkout @@ -34,7 +34,7 @@ jobs: - name: Setup Go uses: actions/setup-go@v5.5.0 with: - go-version: 1.17 + go-version: 1.23 id: go - name: Checkout @@ -64,7 +64,7 @@ jobs: - name: Setup Go uses: actions/setup-go@v5.5.0 with: - go-version: 1.17 + go-version: 1.23 id: go - name: Run GoReleaser diff --git a/.github/workflows/test_push.yml b/.github/workflows/test_push.yml index b475f8e5..805d4ba0 100644 --- a/.github/workflows/test_push.yml +++ b/.github/workflows/test_push.yml @@ -16,7 +16,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v5.5.0 with: - go-version: 1.17 + go-version: 1.23 id: go - name: Checkout @@ -37,7 +37,7 @@ jobs: - name: Setup Go uses: actions/setup-go@v5.5.0 with: - go-version: 1.17 + go-version: 1.23 id: go - name: Checkout @@ -64,7 +64,7 @@ jobs: - name: Setup Go uses: actions/setup-go@v5.5.0 with: - go-version: 1.17 + go-version: 1.23 id: go - name: Run GoReleaser