✨ Initial version
This commit is contained in:
parent
7e195736a9
commit
10db3f27ba
9 changed files with 796 additions and 0 deletions
103
cmd/get.go
Normal file
103
cmd/get.go
Normal file
|
@ -0,0 +1,103 @@
|
|||
// Copyright © 2018 Enrico Stahn <enrico.stahn@gmail.com>
|
||||
// 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 cmd
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/gosuri/uitable"
|
||||
"fmt"
|
||||
"time"
|
||||
"php-fpm_exporter/phpfpm"
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
// Configuration variables
|
||||
var (
|
||||
output string
|
||||
)
|
||||
|
||||
// getCmd represents the get command
|
||||
var getCmd = &cobra.Command{
|
||||
Use: "get",
|
||||
Short: "Returns metrics without running as a server",
|
||||
Long: `"get" fetches metrics from php-fpm. Multiple addresses can be specified as follows:
|
||||
|
||||
* php-fpm_exporter get --phpfpm.scrape-uri 127.0.0.1:9000 --phpfpm.scrape-uri 127.0.0.1:9001 [...]
|
||||
* php-fpm_exporter get --phpfpm.scrape-uri 127.0.0.1:9000,127.0.0.1:9001,[...]
|
||||
`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
pm := phpfpm.PoolManager{}
|
||||
|
||||
for _, uri := range scrapeURIs {
|
||||
pm.Add(uri)
|
||||
}
|
||||
|
||||
pm.Update()
|
||||
|
||||
switch output {
|
||||
case "json":
|
||||
content, err := json.Marshal(pm)
|
||||
if err != nil {
|
||||
log.Fatal("Cannot encode to JSON ", err)
|
||||
}
|
||||
fmt.Print(string(content))
|
||||
case "text":
|
||||
table := uitable.New()
|
||||
table.MaxColWidth = 80
|
||||
table.Wrap = true
|
||||
|
||||
pools := pm.Pools()
|
||||
|
||||
for _, pool := range pools {
|
||||
table.AddRow("Address:", pool.Address)
|
||||
table.AddRow("Pool:", pool.Name)
|
||||
table.AddRow("Start time:", time.Time(pool.StartTime).Format(time.RFC1123Z))
|
||||
table.AddRow("Start since:", pool.StartSince)
|
||||
table.AddRow("Accepted connections:", pool.AcceptedConnections)
|
||||
table.AddRow("Listen Queue:", pool.ListenQueue)
|
||||
table.AddRow("Max Listen Queue:", pool.MaxListenQueue)
|
||||
table.AddRow("Listen Queue Length:", pool.ListenQueueLength)
|
||||
table.AddRow("Idle Processes:", pool.IdleProcesses)
|
||||
table.AddRow("Active Processes:", pool.ActiveProcesses)
|
||||
table.AddRow("Total Processes:", pool.TotalProcesses)
|
||||
table.AddRow("Max active processes:", pool.MaxActiveProcesses)
|
||||
table.AddRow("Max children reached:", pool.MaxChildrenReached)
|
||||
table.AddRow("Slow requests:", pool.SlowRequests)
|
||||
table.AddRow("")
|
||||
}
|
||||
|
||||
fmt.Println(table)
|
||||
case "spew":
|
||||
spew.Dump(pm)
|
||||
default:
|
||||
log.Error("Output format not valid.")
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
RootCmd.AddCommand(getCmd)
|
||||
|
||||
// Here you will define your flags and configuration settings.
|
||||
|
||||
// Cobra supports Persistent Flags which will work for this command
|
||||
// and all subcommands, e.g.:
|
||||
// getCmd.PersistentFlags().String("foo", "", "A help for foo")
|
||||
|
||||
// Cobra supports local flags which will only run when this command
|
||||
// is called directly, e.g.:
|
||||
getCmd.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")
|
||||
getCmd.Flags().StringVar(&output, "out", "text","Output format. One of: text, json, spew")
|
||||
}
|
104
cmd/root.go
Normal file
104
cmd/root.go
Normal file
|
@ -0,0 +1,104 @@
|
|||
// Copyright © 2018 Enrico Stahn <enrico.stahn@gmail.com>
|
||||
// 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 cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/mitchellh/go-homedir"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
"github.com/sirupsen/logrus"
|
||||
"php-fpm_exporter/phpfpm"
|
||||
)
|
||||
|
||||
var log = logrus.New()
|
||||
|
||||
var (
|
||||
cfgFile string
|
||||
logLevel string
|
||||
)
|
||||
|
||||
// RootCmd represents the base command when called without any subcommands
|
||||
var RootCmd = &cobra.Command{
|
||||
Use: "php-fpm_exporter",
|
||||
Short: "Exports php-fpm metrics for prometheus",
|
||||
Long: `php-fpm_exporter exports prometheus compatible metrics from php-fpm.`,
|
||||
// Uncomment the following line if your bare application
|
||||
// has an action associated with it:
|
||||
// Run: func(cmd *cobra.Command, args []string) { },
|
||||
}
|
||||
|
||||
// Execute adds all child commands to the root command sets flags appropriately.
|
||||
// This is called by main.main(). It only needs to happen once to the rootCmd.
|
||||
func Execute() {
|
||||
if err := RootCmd.Execute(); err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
cobra.OnInitialize(initConfig, initLogger)
|
||||
|
||||
// Here you will define your flags and configuration settings.
|
||||
// Cobra supports persistent flags, which, if defined here,
|
||||
// will be global for your application.
|
||||
RootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.php-fpm_exporter.yaml)")
|
||||
RootCmd.PersistentFlags().StringVar(&logLevel, "log.level", "error", "Only log messages with the given severity or above. Valid levels: [debug, info, warn, error, fatal]")
|
||||
|
||||
// Cobra also supports local flags, which will only run
|
||||
// when this action is called directly.
|
||||
RootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
|
||||
}
|
||||
|
||||
// initConfig reads in config file and ENV variables if set.
|
||||
func initConfig() {
|
||||
if cfgFile != "" {
|
||||
// Use config file from the flag.
|
||||
viper.SetConfigFile(cfgFile)
|
||||
} else {
|
||||
// Find home directory.
|
||||
home, err := homedir.Dir()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Search config in home directory with name ".php-fpm_exporter" (without extension).
|
||||
viper.AddConfigPath(home)
|
||||
viper.SetConfigName(".php-fpm_exporter")
|
||||
}
|
||||
|
||||
viper.AutomaticEnv() // read in environment variables that match
|
||||
|
||||
// If a config file is found, read it in.
|
||||
if err := viper.ReadInConfig(); err == nil {
|
||||
fmt.Println("Using config file:", viper.ConfigFileUsed())
|
||||
}
|
||||
}
|
||||
|
||||
// initLogger configures the log level
|
||||
func initLogger() {
|
||||
phpfpm.SetLogger(log)
|
||||
|
||||
lvl, err := logrus.ParseLevel(logLevel)
|
||||
if err != nil {
|
||||
lvl = logrus.InfoLevel
|
||||
log.Fatalf("Could not set log level to '%v'.", logLevel)
|
||||
}
|
||||
|
||||
log.SetLevel(lvl)
|
||||
}
|
85
cmd/server.go
Normal file
85
cmd/server.go
Normal file
|
@ -0,0 +1,85 @@
|
|||
// Copyright © 2018 Enrico Stahn <enrico.stahn@gmail.com>
|
||||
// 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 cmd
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"net/http"
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
"php-fpm_exporter/phpfpm"
|
||||
)
|
||||
|
||||
// Configuration variables
|
||||
var (
|
||||
listeningAddress string
|
||||
metricsEndpoint string
|
||||
scrapeURIs []string
|
||||
customLabelNames []string
|
||||
customLabelValues []string
|
||||
)
|
||||
|
||||
// serverCmd represents the server command
|
||||
var serverCmd = &cobra.Command{
|
||||
Use: "server",
|
||||
Short: "A brief description of your command",
|
||||
Long: `A longer description that spans multiple lines and likely contains examples
|
||||
and usage of using your command. For example:
|
||||
|
||||
Cobra is a CLI library for Go that empowers applications.
|
||||
This application is a tool to generate the needed files
|
||||
to quickly create a Cobra application.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
log.Infof("Starting server on %v with path %v", listeningAddress, metricsEndpoint)
|
||||
|
||||
pm := phpfpm.PoolManager{}
|
||||
|
||||
for _, uri := range scrapeURIs {
|
||||
pm.Add(uri)
|
||||
}
|
||||
|
||||
exporter := phpfpm.NewExporter(pm)
|
||||
prometheus.MustRegister(exporter)
|
||||
|
||||
http.Handle(metricsEndpoint, promhttp.Handler())
|
||||
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte(`<html>
|
||||
<head><title>Apache Exporter</title></head>
|
||||
<body>
|
||||
<h1>Apache Exporter</h1>
|
||||
<p><a href='` + metricsEndpoint + `'>Metrics</a></p>
|
||||
</body>
|
||||
</html>`))
|
||||
})
|
||||
log.Fatal(http.ListenAndServe(listeningAddress, nil))
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
RootCmd.AddCommand(serverCmd)
|
||||
|
||||
// Here you will define your flags and configuration settings.
|
||||
|
||||
// Cobra supports Persistent Flags which will work for this command
|
||||
// and all subcommands, e.g.:
|
||||
// serverCmd.PersistentFlags().String("foo", "", "A help for foo")
|
||||
|
||||
// Cobra supports local flags which will only run when this command
|
||||
// is called directly, e.g.:
|
||||
serverCmd.Flags().StringVar(&listeningAddress, "web.listen-address", ":9253", "Address on which to expose metrics and web interface.")
|
||||
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().StringSliceVar(&customLabelNames, "phpfpm.label-name", []string{},"Name of the custom label that will be inserted.")
|
||||
serverCmd.Flags().StringSliceVar(&customLabelValues, "phpfpm.label-value", []string{},"Value of the custom label that will be inserted.")
|
||||
}
|
44
cmd/version.go
Normal file
44
cmd/version.go
Normal file
|
@ -0,0 +1,44 @@
|
|||
// Copyright © 2018 Enrico Stahn <enrico.stahn@gmail.com>
|
||||
// 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 cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// versionCmd represents the version command
|
||||
var versionCmd = &cobra.Command{
|
||||
Use: "version",
|
||||
Short: "Print the version number of php-fpm_exporter",
|
||||
Long: `All software has versions. This is php-fpm_exporter's'`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
fmt.Println("php-fpm_exporter v0.9 -- HEAD")
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
RootCmd.AddCommand(versionCmd)
|
||||
|
||||
// Here you will define your flags and configuration settings.
|
||||
|
||||
// Cobra supports Persistent Flags which will work for this command
|
||||
// and all subcommands, e.g.:
|
||||
// versionCmd.PersistentFlags().String("foo", "", "A help for foo")
|
||||
|
||||
// Cobra supports local flags which will only run when this command
|
||||
// is called directly, e.g.:
|
||||
// versionCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
|
||||
}
|
111
glide.lock
generated
Normal file
111
glide.lock
generated
Normal file
|
@ -0,0 +1,111 @@
|
|||
hash: 41dcf6f131327ca4b18788f48b530080ea79b432b982a1a8d3114b3a2d765c09
|
||||
updated: 2018-02-15T13:47:57.155132+11:00
|
||||
imports:
|
||||
- name: github.com/aws/aws-sdk-go
|
||||
version: 31d8a71e731ad4df5221f6ba3372c461b56a5eda
|
||||
- name: github.com/beorn7/perks
|
||||
version: 4c0e84591b9aa9e6dcfdf3e020114cd81f89d5f9
|
||||
subpackages:
|
||||
- quantile
|
||||
- name: github.com/bmuller/arrow
|
||||
version: 57b28416c629f234bd2291d3ba7b19b9a064fd37
|
||||
- name: github.com/davecgh/go-spew
|
||||
version: 87df7c60d5820d0f8ae11afede5aa52325c09717
|
||||
subpackages:
|
||||
- spew
|
||||
- name: github.com/fsnotify/fsnotify
|
||||
version: 4da3e2cfbabc9f751898f250b49f2439785783a1
|
||||
- name: github.com/golang/protobuf
|
||||
version: bbd03ef6da3a115852eaf24c8a1c46aeb39aa175
|
||||
subpackages:
|
||||
- proto
|
||||
- name: github.com/gorilla/mux
|
||||
version: c0091a029979286890368b4c7b301261e448e242
|
||||
- name: github.com/gosuri/uitable
|
||||
version: 36ee7e946282a3fb1cfecd476ddc9b35d8847e42
|
||||
- name: github.com/hashicorp/hcl
|
||||
version: 392dba7d905ed5d04a5794ba89f558b27e2ba1ca
|
||||
subpackages:
|
||||
- hcl/ast
|
||||
- hcl/parser
|
||||
- hcl/scanner
|
||||
- hcl/strconv
|
||||
- hcl/token
|
||||
- json/parser
|
||||
- json/scanner
|
||||
- json/token
|
||||
- name: github.com/inconshreveable/mousetrap
|
||||
version: 76626ae9c91c4f2a10f34cad8ce83ea42c93bb75
|
||||
- name: github.com/magiconair/properties
|
||||
version: 51463bfca2576e06c62a8504b5c0f06d61312647
|
||||
- name: github.com/matttproud/golang_protobuf_extensions
|
||||
version: c12348ce28de40eed0136aa2b644d0ee0650e56c
|
||||
subpackages:
|
||||
- pbutil
|
||||
- name: github.com/mitchellh/go-homedir
|
||||
version: b8bc1bf767474819792c23f32d8286a45736f1c6
|
||||
- name: github.com/mitchellh/mapstructure
|
||||
version: d0303fe809921458f417bcf828397a65db30a7e4
|
||||
- name: github.com/newrelic/go-agent
|
||||
version: f5bce3387232559bcbe6a5f8227c4bf508dac1ba
|
||||
- name: github.com/pelletier/go-buffruneio
|
||||
version: c37440a7cf42ac63b919c752ca73a85067e05992
|
||||
- name: github.com/pelletier/go-toml
|
||||
version: fe7536c3dee2596cdd23ee9976a17c22bdaae286
|
||||
- name: github.com/prometheus/client_golang
|
||||
version: a40133b69fbd73ee655606a9bf5f8b9b9bf758dd
|
||||
subpackages:
|
||||
- prometheus
|
||||
- name: github.com/prometheus/client_model
|
||||
version: 99fa1f4be8e564e8a6b613da7fa6f46c9edafc6c
|
||||
subpackages:
|
||||
- go
|
||||
- name: github.com/prometheus/common
|
||||
version: 89604d197083d4781071d3c65855d24ecfb0a563
|
||||
subpackages:
|
||||
- expfmt
|
||||
- internal/bitbucket.org/ww/goautoneg
|
||||
- model
|
||||
- name: github.com/prometheus/procfs
|
||||
version: 282c8707aa210456a825798969cc27edda34992a
|
||||
subpackages:
|
||||
- internal/util
|
||||
- nfs
|
||||
- xfs
|
||||
- name: github.com/sirupsen/logrus
|
||||
version: 8c0189d9f6bbf301e5d055d34268156b317016af
|
||||
- name: github.com/speps/go-hashids
|
||||
version: c6ced3330f133cec79e144fb11b2e4c5154059ae
|
||||
- name: github.com/spf13/afero
|
||||
version: 9be650865eab0c12963d8753212f4f9c66cdcf12
|
||||
subpackages:
|
||||
- mem
|
||||
- name: github.com/spf13/cast
|
||||
version: acbeb36b902d72a7a4c18e8f3241075e7ab763e4
|
||||
- name: github.com/spf13/cobra
|
||||
version: 8f5946caaeeff40a98d67f60c25e89c3525038a3
|
||||
- name: github.com/spf13/jwalterweatherman
|
||||
version: 0efa5202c04663c757d84f90f5219c1250baf94f
|
||||
- name: github.com/spf13/pflag
|
||||
version: e57e3eeb33f795204c1ca35f56c44f83227c6e66
|
||||
- name: github.com/spf13/viper
|
||||
version: a1ecfa6a20bd4ef9e9caded262ee1b1b26847675
|
||||
- name: github.com/tomasen/fcgi_client
|
||||
version: d32b71631a94113ee01855724d1038485a396b6f
|
||||
- name: golang.org/x/crypto
|
||||
version: 650f4a345ab4e5b245a3034b110ebc7299e68186
|
||||
subpackages:
|
||||
- ssh/terminal
|
||||
- name: golang.org/x/sys
|
||||
version: 37707fdb30a5b38865cfb95e5aab41707daec7fd
|
||||
subpackages:
|
||||
- unix
|
||||
- windows
|
||||
- name: golang.org/x/text
|
||||
version: 9e2f80a6ba7ed4ba13e0cd4b1f094bf916875735
|
||||
subpackages:
|
||||
- transform
|
||||
- unicode/norm
|
||||
- name: gopkg.in/yaml.v2
|
||||
version: a83829b6f1293c91addabc89d0571c246397bbf4
|
||||
testImports: []
|
7
glide.yaml
Normal file
7
glide.yaml
Normal file
|
@ -0,0 +1,7 @@
|
|||
package: php-fpm_exporter
|
||||
import:
|
||||
- package: github.com/gosuri/uitable
|
||||
- package: github.com/sirupsen/logrus
|
||||
- package: github.com/mitchellh/go-homedir
|
||||
- package: github.com/spf13/cobra
|
||||
- package: github.com/spf13/viper
|
20
main.go
Normal file
20
main.go
Normal file
|
@ -0,0 +1,20 @@
|
|||
// Copyright © 2018 Enrico Stahn <enrico.stahn@gmail.com>
|
||||
// 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 main
|
||||
|
||||
import "php-fpm_exporter/cmd"
|
||||
|
||||
func main() {
|
||||
cmd.Execute()
|
||||
}
|
143
phpfpm/exporter.go
Normal file
143
phpfpm/exporter.go
Normal file
|
@ -0,0 +1,143 @@
|
|||
package phpfpm
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
const (
|
||||
namespace = "phpfpm"
|
||||
)
|
||||
|
||||
type Exporter struct {
|
||||
PoolManager PoolManager
|
||||
mutex sync.Mutex
|
||||
client *http.Client
|
||||
|
||||
startSince *prometheus.Desc
|
||||
acceptedConnections *prometheus.Desc
|
||||
listenQueue *prometheus.Desc
|
||||
maxListenQueue *prometheus.Desc
|
||||
listenQueueLength *prometheus.Desc
|
||||
idleProcesses *prometheus.Desc
|
||||
activeProcesses *prometheus.Desc
|
||||
totalProcesses *prometheus.Desc
|
||||
maxActiveProcesses *prometheus.Desc
|
||||
maxChildrenReached *prometheus.Desc
|
||||
slowRequests *prometheus.Desc
|
||||
}
|
||||
|
||||
func NewExporter(pm PoolManager) *Exporter {
|
||||
return &Exporter{
|
||||
PoolManager: pm,
|
||||
|
||||
startSince: prometheus.NewDesc(
|
||||
prometheus.BuildFQName(namespace, "", "start_since"),
|
||||
"The number of seconds since FPM has started.",
|
||||
[]string{"pool"},
|
||||
nil),
|
||||
|
||||
acceptedConnections: prometheus.NewDesc(
|
||||
prometheus.BuildFQName(namespace, "", "accepted_connections"),
|
||||
"The number of requests accepted by the pool.",
|
||||
[]string{"pool"},
|
||||
nil),
|
||||
|
||||
listenQueue: prometheus.NewDesc(
|
||||
prometheus.BuildFQName(namespace, "", "listen_queue"),
|
||||
"The number of requests in the queue of pending connections.",
|
||||
[]string{"pool"},
|
||||
nil),
|
||||
|
||||
maxListenQueue: prometheus.NewDesc(
|
||||
prometheus.BuildFQName(namespace, "", "max_listen_queue"),
|
||||
"The maximum number of requests in the queue of pending connections since FPM has started.",
|
||||
[]string{"pool"},
|
||||
nil),
|
||||
|
||||
listenQueueLength: prometheus.NewDesc(
|
||||
prometheus.BuildFQName(namespace, "", "listen_queue_length"),
|
||||
"The size of the socket queue of pending connections.",
|
||||
[]string{"pool"},
|
||||
nil),
|
||||
|
||||
idleProcesses: prometheus.NewDesc(
|
||||
prometheus.BuildFQName(namespace, "", "idle_processes"),
|
||||
"The number of idle processes.",
|
||||
[]string{"pool"},
|
||||
nil),
|
||||
|
||||
activeProcesses: prometheus.NewDesc(
|
||||
prometheus.BuildFQName(namespace, "", "active_processes"),
|
||||
"The number of active processes.",
|
||||
[]string{"pool"},
|
||||
nil),
|
||||
|
||||
totalProcesses: prometheus.NewDesc(
|
||||
prometheus.BuildFQName(namespace, "", "total_processes"),
|
||||
"The number of idle + active processes.",
|
||||
[]string{"pool"},
|
||||
nil),
|
||||
|
||||
maxActiveProcesses: prometheus.NewDesc(
|
||||
prometheus.BuildFQName(namespace, "", "max_active_processes"),
|
||||
"The maximum number of active processes since FPM has started.",
|
||||
[]string{"pool"},
|
||||
nil),
|
||||
|
||||
maxChildrenReached: prometheus.NewDesc(
|
||||
prometheus.BuildFQName(namespace, "", "max_children_reached"),
|
||||
"The number of times, the process limit has been reached, when pm tries to start more children (works only for pm 'dynamic' and 'ondemand').",
|
||||
[]string{"pool"},
|
||||
nil),
|
||||
|
||||
slowRequests: prometheus.NewDesc(
|
||||
prometheus.BuildFQName(namespace, "", "slow_requests"),
|
||||
"The number of requests that exceeded your 'request_slowlog_timeout' value.",
|
||||
[]string{"pool"},
|
||||
nil),
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Exporter) Collect(ch chan<- prometheus.Metric) {
|
||||
e.mutex.Lock()
|
||||
defer e.mutex.Unlock()
|
||||
|
||||
e.PoolManager.Update()
|
||||
|
||||
for _, pool := range e.PoolManager.Pools() {
|
||||
ch <- prometheus.MustNewConstMetric(e.startSince, prometheus.CounterValue, float64(pool.AcceptedConnections), pool.Name)
|
||||
ch <- prometheus.MustNewConstMetric(e.acceptedConnections, prometheus.CounterValue, float64(pool.StartSince), pool.Name)
|
||||
ch <- prometheus.MustNewConstMetric(e.listenQueue, prometheus.GaugeValue, float64(pool.ListenQueue), pool.Name)
|
||||
ch <- prometheus.MustNewConstMetric(e.maxListenQueue, prometheus.CounterValue, float64(pool.MaxListenQueue), pool.Name)
|
||||
ch <- prometheus.MustNewConstMetric(e.listenQueueLength, prometheus.GaugeValue, float64(pool.ListenQueueLength), pool.Name)
|
||||
ch <- prometheus.MustNewConstMetric(e.idleProcesses, prometheus.GaugeValue, float64(pool.IdleProcesses), pool.Name)
|
||||
ch <- prometheus.MustNewConstMetric(e.activeProcesses, prometheus.GaugeValue, float64(pool.ActiveProcesses), pool.Name)
|
||||
ch <- prometheus.MustNewConstMetric(e.totalProcesses, prometheus.GaugeValue, float64(pool.TotalProcesses), pool.Name)
|
||||
ch <- prometheus.MustNewConstMetric(e.maxActiveProcesses, prometheus.CounterValue, float64(pool.MaxActiveProcesses), pool.Name)
|
||||
ch <- prometheus.MustNewConstMetric(e.maxChildrenReached, prometheus.CounterValue, float64(pool.MaxChildrenReached), pool.Name)
|
||||
ch <- prometheus.MustNewConstMetric(e.slowRequests, prometheus.CounterValue, float64(pool.SlowRequests), pool.Name)
|
||||
}
|
||||
|
||||
//if err := e.collect(ch); err != nil {
|
||||
// log.Errorf("Error scraping apache: %s", err)
|
||||
// e.scrapeFailures.Inc()
|
||||
// e.scrapeFailures.Collect(ch)
|
||||
//}
|
||||
return
|
||||
}
|
||||
|
||||
func (e *Exporter) Describe(ch chan<- *prometheus.Desc) {
|
||||
ch <- e.startSince
|
||||
ch <- e.acceptedConnections
|
||||
ch <- e.listenQueue
|
||||
ch <- e.maxListenQueue
|
||||
ch <- e.listenQueueLength
|
||||
ch <- e.idleProcesses
|
||||
ch <- e.activeProcesses
|
||||
ch <- e.totalProcesses
|
||||
ch <- e.maxActiveProcesses
|
||||
ch <- e.maxChildrenReached
|
||||
ch <- e.slowRequests
|
||||
}
|
179
phpfpm/phpfpm.go
Normal file
179
phpfpm/phpfpm.go
Normal file
|
@ -0,0 +1,179 @@
|
|||
// Copyright © 2018 Enrico Stahn <enrico.stahn@gmail.com>
|
||||
// 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 phpfpm
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"github.com/tomasen/fcgi_client"
|
||||
"io/ioutil"
|
||||
"encoding/json"
|
||||
"time"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var log logger
|
||||
|
||||
type logger interface {
|
||||
Debugf(string, ...interface{})
|
||||
Error(ar ...interface{})
|
||||
}
|
||||
|
||||
type PoolManager struct {
|
||||
pools []Pool `json:"pools"`
|
||||
}
|
||||
|
||||
type Pool struct {
|
||||
// The address of the pool, e.g. tcp://127.0.0.1:9000 or unix:///tmp/php-fpm.sock
|
||||
Address string
|
||||
CollectionError error
|
||||
|
||||
Name string `json:"pool"`
|
||||
ProcessManager string `json:"process manager"`
|
||||
StartTime Timestamp `json:"start time"`
|
||||
StartSince int `json:"start since"`
|
||||
AcceptedConnections int `json:"accepted conn"`
|
||||
ListenQueue int `json:"listen queue"`
|
||||
MaxListenQueue int `json:"max listen queue"`
|
||||
ListenQueueLength int `json:"listen queue len"`
|
||||
IdleProcesses int `json:"idle processes"`
|
||||
ActiveProcesses int `json:"active processes"`
|
||||
TotalProcesses int `json:"total processes"`
|
||||
MaxActiveProcesses int `json:"max active processes"`
|
||||
MaxChildrenReached int `json:"max children reached"`
|
||||
SlowRequests int `json:"slow requests"`
|
||||
Processes []PoolProcess `json:"processes"`
|
||||
}
|
||||
|
||||
type PoolProcess struct {
|
||||
PID int `json:"pid"`
|
||||
State string `json:"state"`
|
||||
StartTime int `json:"start time"`
|
||||
StartSince int `json:"start since"`
|
||||
Requests int `json:"requests"`
|
||||
RequestDuration int `json:"request duration"`
|
||||
RequestMethod string `json:"request method"`
|
||||
RequestURI string `json:"request uri"`
|
||||
ContentLength int `json:"content length"`
|
||||
User string `json:"user"`
|
||||
Script string `json:"script"`
|
||||
LastRequestCPU float32 `json:"last request cpu"`
|
||||
LastRequestMemory int `json:"last request memory"`
|
||||
}
|
||||
|
||||
func (pm *PoolManager) Add(uri string) Pool {
|
||||
p := Pool{Address: uri}
|
||||
pm.pools = append(pm.pools, p)
|
||||
return p
|
||||
}
|
||||
|
||||
func (pm *PoolManager) Update() (err error) {
|
||||
wg := &sync.WaitGroup{}
|
||||
|
||||
started := time.Now()
|
||||
|
||||
for idx, _ := range pm.pools {
|
||||
wg.Add(1)
|
||||
go func(p *Pool) {
|
||||
defer wg.Done()
|
||||
p.Update()
|
||||
}(&pm.pools[idx])
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
ended := time.Now()
|
||||
|
||||
log.Debugf("Updated %v pool(s) in %v", len(pm.pools), ended.Sub(started))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pm *PoolManager) Pools() []Pool {
|
||||
return pm.pools
|
||||
}
|
||||
|
||||
// Implement custom Marshaler due to "pools" being unexported
|
||||
func (pm PoolManager) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(struct{Pools []Pool `json:"pools"` }{Pools: pm.pools})
|
||||
}
|
||||
|
||||
func (p *Pool) Update() (err error) {
|
||||
p.CollectionError = nil
|
||||
|
||||
env := make(map[string]string)
|
||||
env["SCRIPT_FILENAME"] = "/status"
|
||||
env["SCRIPT_NAME"] = "/status"
|
||||
env["SERVER_SOFTWARE"] = "go / php-fpm_exporter "
|
||||
env["REMOTE_ADDR"] = "127.0.0.1"
|
||||
env["QUERY_STRING"] = "json&full"
|
||||
|
||||
uri, err := url.Parse(p.Address)
|
||||
if err != nil {
|
||||
return p.error(err)
|
||||
}
|
||||
|
||||
fcgi, err := fcgiclient.DialTimeout(uri.Scheme, uri.Hostname()+":"+uri.Port(), time.Duration(3)*time.Second)
|
||||
if err != nil {
|
||||
return p.error(err)
|
||||
}
|
||||
|
||||
resp, err := fcgi.Get(env)
|
||||
if err != nil {
|
||||
return p.error(err)
|
||||
}
|
||||
|
||||
content, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return p.error(err)
|
||||
}
|
||||
|
||||
fcgi.Close()
|
||||
|
||||
log.Debugf("Pool[", p.Address, "]:", string(content))
|
||||
|
||||
if err = json.Unmarshal(content, &p); err != nil {
|
||||
return p.error(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Pool) error(err error) (error) {
|
||||
p.CollectionError = err
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
|
||||
type Timestamp time.Time
|
||||
|
||||
func (t *Timestamp) MarshalJSON() ([]byte, error) {
|
||||
ts := time.Time(*t).Unix()
|
||||
stamp := fmt.Sprint(ts)
|
||||
return []byte(stamp), nil
|
||||
}
|
||||
|
||||
func (t *Timestamp) UnmarshalJSON(b []byte) error {
|
||||
ts, err := strconv.Atoi(string(b))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*t = Timestamp(time.Unix(int64(ts), 0))
|
||||
return nil
|
||||
}
|
||||
|
||||
func SetLogger(logger logger){
|
||||
log = logger
|
||||
}
|
Loading…
Reference in a new issue