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
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
+}