1// Copyright © 2018 Enrico Stahn <enrico.stahn@gmail.com> 2// Licensed under the Apache License, Version 2.0 (the "License"); 3// you may not use this file except in compliance with the License. 4// You may obtain a copy of the License at 5// 6// http://www.apache.org/licenses/LICENSE-2.0 7// 8// Unless required by applicable law or agreed to in writing, software 9// distributed under the License is distributed on an "AS IS" BASIS, 10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11// See the License for the specific language governing permissions and 12// limitations under the License. 13 14package cmd 15 16import ( 17 "context" 18 "net/http" 19 "os" 20 "os/signal" 21 "syscall" 22 "time" 23 24 "github.com/hipages/php-fpm_exporter/phpfpm" 25 "github.com/prometheus/client_golang/prometheus" 26 "github.com/prometheus/client_golang/prometheus/promhttp" 27 "github.com/spf13/cobra" 28) 29 30// Configuration variables 31var ( 32 listeningAddress string 33 metricsEndpoint string 34 scrapeURIs []string 35 fixProcessCount bool 36) 37 38// serverCmd represents the server command 39var serverCmd = &cobra.Command{ 40 Use: "server", 41 Short: "A brief description of your command", 42 Long: `A longer description that spans multiple lines and likely contains examples 43and usage of using your command. For example: 44 45Cobra is a CLI library for Go that empowers applications. 46This application is a tool to generate the needed files 47to quickly create a Cobra application.`, 48 Run: func(cmd *cobra.Command, args []string) { 49 log.Infof("Starting server on %v with path %v", listeningAddress, metricsEndpoint) 50 51 pm := phpfpm.PoolManager{} 52 53 for _, uri := range scrapeURIs { 54 pm.Add(uri) 55 } 56 57 exporter := phpfpm.NewExporter(pm) 58 59 if fixProcessCount { 60 log.Info("Idle/Active/Total Processes will be calculated by php-fpm_exporter.") 61 exporter.CountProcessState = true 62 } 63 64 prometheus.MustRegister(exporter) 65 66 srv := &http.Server{ 67 Addr: listeningAddress, 68 // Good practice to set timeouts to avoid Slowloris attacks. 69 WriteTimeout: time.Second * 15, 70 ReadTimeout: time.Second * 15, 71 IdleTimeout: time.Second * 60, 72 } 73 74 http.Handle(metricsEndpoint, promhttp.Handler()) 75 http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 76 _, err := w.Write([]byte(`<html> 77 <head><title>php-fpm_exporter</title></head> 78 <body> 79 <h1>php-fpm_exporter</h1> 80 <p><a href='` + metricsEndpoint + `'>Metrics</a></p> 81 </body> 82 </html>`)) 83 84 if err != nil { 85 log.Error() 86 } 87 }) 88 89 // Run our server in a goroutine so that it doesn't block. 90 go func() { 91 if err := srv.ListenAndServe(); err != nil { 92 log.Error(err) 93 } 94 }() 95 96 c := make(chan os.Signal, 1) 97 // We'll accept graceful shutdowns when quit via SIGINT (Ctrl+C) or SIGTERM 98 // SIGKILL, SIGQUIT will not be caught. 99 signal.Notify(c, os.Interrupt, syscall.SIGTERM) 100 101 // Block until we receive our signal. 102 <-c 103 104 // Create a deadline to wait for. 105 var wait time.Duration 106 ctx, cancel := context.WithTimeout(context.Background(), wait) 107 defer cancel() 108 // Doesn't block if no connections, but will otherwise wait 109 // until the timeout deadline. 110 if err := srv.Shutdown(ctx); err != nil { 111 log.Fatal("Error during shutdown", err) 112 } 113 // Optionally, you could run srv.Shutdown in a goroutine and block on 114 // <-ctx.Done() if your application should wait for other services 115 // to finalize based on context cancellation. 116 log.Info("Shutting down") 117 os.Exit(0) 118 }, 119} 120 121func init() { 122 RootCmd.AddCommand(serverCmd) 123 124 serverCmd.Flags().StringVar(&listeningAddress, "web.listen-address", ":9253", "Address on which to expose metrics and web interface.") 125 serverCmd.Flags().StringVar(&metricsEndpoint, "web.telemetry-path", "/metrics", "Path under which to expose metrics.") 126 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") 127 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.") 128 129 // Workaround since vipers BindEnv is currently not working as expected (see https://github.com/spf13/viper/issues/461) 130 131 envs := map[string]string{ 132 "PHP_FPM_WEB_LISTEN_ADDRESS": "web.listen-address", 133 "PHP_FPM_WEB_TELEMETRY_PATH": "web.telemetry-path", 134 "PHP_FPM_SCRAPE_URI": "phpfpm.scrape-uri", 135 "PHP_FPM_FIX_PROCESS_COUNT": "phpfpm.fix-process-count", 136 } 137 138 mapEnvVars(envs, serverCmd) 139} 140