Initial version

This commit is contained in:
Enrico Stahn 2018-02-17 23:42:58 +11:00
parent 7e195736a9
commit 10db3f27ba
9 changed files with 796 additions and 0 deletions

103
cmd/get.go Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
}