2017-03-10 13:01:26 +00:00
/ *
Copyright 2017 The Kubernetes Authors .
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 collector
import (
2018-06-14 00:55:07 +00:00
"fmt"
"io/ioutil"
"net/http"
"regexp"
"strconv"
2017-03-10 13:01:26 +00:00
"github.com/golang/glog"
2017-10-06 22:47:46 +00:00
2017-03-10 13:01:26 +00:00
"github.com/prometheus/client_golang/prometheus"
)
2018-06-14 00:55:07 +00:00
var (
ac = regexp . MustCompile ( ` Active connections: (\d+) ` )
sahr = regexp . MustCompile ( ` (\d+)\s(\d+)\s(\d+) ` )
reading = regexp . MustCompile ( ` Reading: (\d+) ` )
writing = regexp . MustCompile ( ` Writing: (\d+) ` )
waiting = regexp . MustCompile ( ` Waiting: (\d+) ` )
)
2017-03-10 13:01:26 +00:00
type (
nginxStatusCollector struct {
2017-04-04 12:32:08 +00:00
scrapeChan chan scrapeRequest
ngxHealthPort int
2018-06-14 00:55:07 +00:00
ngxStatusPath string
2017-04-04 12:32:08 +00:00
data * nginxStatusData
watchNamespace string
ingressClass string
2017-03-12 15:27:05 +00:00
}
nginxStatusData struct {
2018-01-19 11:32:19 +00:00
connectionsTotal * prometheus . Desc
requestsTotal * prometheus . Desc
connections * prometheus . Desc
2017-03-10 13:01:26 +00:00
}
2018-06-14 00:55:07 +00:00
basicStatus struct {
// Active total number of active connections
Active int
// Accepted total number of accepted client connections
Accepted int
// Handled total number of handled connections. Generally, the parameter value is the same as accepts unless some resource limits have been reached (for example, the worker_connections limit).
Handled int
// Requests total number of client requests.
Requests int
// Reading current number of connections where nginx is reading the request header.
Reading int
// Writing current number of connections where nginx is writing the response back to the client.
Writing int
// Waiting current number of idle client connections waiting for a request.
Waiting int
}
)
2017-04-04 12:32:08 +00:00
2018-06-14 00:55:07 +00:00
// InitNGINXStatusCollector returns a new prometheus collector the default nginx status module
func InitNGINXStatusCollector ( watchNamespace , ingressClass string , ngxHealthPort int ) error {
const ns string = "nginx"
const ngxStatusPath = "/nginx_status"
2017-03-10 13:01:26 +00:00
p := nginxStatusCollector {
2017-04-04 12:32:08 +00:00
scrapeChan : make ( chan scrapeRequest ) ,
ngxHealthPort : ngxHealthPort ,
2018-06-14 00:55:07 +00:00
ngxStatusPath : ngxStatusPath ,
2017-04-04 12:32:08 +00:00
watchNamespace : watchNamespace ,
ingressClass : ingressClass ,
2017-03-12 15:27:05 +00:00
}
p . data = & nginxStatusData {
2018-01-19 11:32:19 +00:00
connectionsTotal : prometheus . NewDesc (
prometheus . BuildFQName ( ns , "" , "connections_total" ) ,
"total number of connections with state {active, accepted, handled}" ,
[ ] string { "ingress_class" , "namespace" , "state" } , nil ) ,
2017-03-12 15:27:05 +00:00
2018-01-19 11:32:19 +00:00
requestsTotal : prometheus . NewDesc (
2018-01-17 11:56:53 +00:00
prometheus . BuildFQName ( ns , "" , "requests_total" ) ,
2017-03-12 15:27:05 +00:00
"total number of client requests" ,
2017-04-04 12:32:08 +00:00
[ ] string { "ingress_class" , "namespace" } , nil ) ,
2017-03-12 15:27:05 +00:00
2018-01-19 11:32:19 +00:00
connections : prometheus . NewDesc (
2018-06-14 00:55:07 +00:00
prometheus . BuildFQName ( ns , "" , "connections" ) ,
2018-01-19 11:32:19 +00:00
"current number of client connections with state {reading, writing, waiting}" ,
[ ] string { "ingress_class" , "namespace" , "state" } , nil ) ,
2017-03-10 13:01:26 +00:00
}
2018-06-14 00:55:07 +00:00
err := prometheus . Register ( p )
if err != nil {
return fmt . Errorf ( "error while registering nginx status collector : %v" , err )
}
2017-03-10 13:01:26 +00:00
2018-06-14 00:55:07 +00:00
go p . Run ( )
return nil
2017-03-10 13:01:26 +00:00
}
// Describe implements prometheus.Collector.
func ( p nginxStatusCollector ) Describe ( ch chan <- * prometheus . Desc ) {
2018-01-19 11:32:19 +00:00
ch <- p . data . connectionsTotal
ch <- p . data . requestsTotal
ch <- p . data . connections
2017-03-10 13:01:26 +00:00
}
// Collect implements prometheus.Collector.
func ( p nginxStatusCollector ) Collect ( ch chan <- prometheus . Metric ) {
req := scrapeRequest { results : ch , done : make ( chan struct { } ) }
p . scrapeChan <- req
<- req . done
}
2018-06-14 00:55:07 +00:00
func ( p nginxStatusCollector ) Run ( ) {
2017-03-10 13:01:26 +00:00
for req := range p . scrapeChan {
ch := req . results
p . scrape ( ch )
req . done <- struct { } { }
}
}
func ( p nginxStatusCollector ) Stop ( ) {
close ( p . scrapeChan )
}
2018-06-14 00:55:07 +00:00
func httpBody ( url string ) ( [ ] byte , error ) {
resp , err := http . DefaultClient . Get ( url )
if err != nil {
return nil , fmt . Errorf ( "unexpected error scraping nginx : %v" , err )
}
data , err := ioutil . ReadAll ( resp . Body )
if err != nil {
return nil , fmt . Errorf ( "unexpected error scraping nginx (%v)" , err )
}
defer resp . Body . Close ( )
if resp . StatusCode < 200 || resp . StatusCode >= 400 {
return nil , fmt . Errorf ( "unexpected error scraping nginx (status %v)" , resp . StatusCode )
}
return data , nil
}
func toInt ( data [ ] string , pos int ) int {
if len ( data ) == 0 {
return 0
}
if pos > len ( data ) {
return 0
}
if v , err := strconv . Atoi ( data [ pos ] ) ; err == nil {
return v
}
return 0
}
func parse ( data string ) * basicStatus {
acr := ac . FindStringSubmatch ( data )
sahrr := sahr . FindStringSubmatch ( data )
readingr := reading . FindStringSubmatch ( data )
writingr := writing . FindStringSubmatch ( data )
waitingr := waiting . FindStringSubmatch ( data )
return & basicStatus {
toInt ( acr , 1 ) ,
toInt ( sahrr , 1 ) ,
toInt ( sahrr , 2 ) ,
toInt ( sahrr , 3 ) ,
toInt ( readingr , 1 ) ,
toInt ( writingr , 1 ) ,
toInt ( waitingr , 1 ) ,
}
}
func getNginxStatus ( port int , path string ) ( * basicStatus , error ) {
url := fmt . Sprintf ( "http://0.0.0.0:%v%v" , port , path )
glog . V ( 3 ) . Infof ( "start scraping url: %v" , url )
data , err := httpBody ( url )
if err != nil {
return nil , fmt . Errorf ( "unexpected error scraping nginx status page: %v" , err )
}
return parse ( string ( data ) ) , nil
}
2017-09-14 19:35:52 +00:00
// nginxStatusCollector scrape the nginx status
2017-03-10 13:01:26 +00:00
func ( p nginxStatusCollector ) scrape ( ch chan <- prometheus . Metric ) {
2018-06-14 00:55:07 +00:00
s , err := getNginxStatus ( p . ngxHealthPort , p . ngxStatusPath )
2017-03-10 13:01:26 +00:00
if err != nil {
glog . Warningf ( "unexpected error obtaining nginx status info: %v" , err )
return
}
2018-01-19 11:32:19 +00:00
ch <- prometheus . MustNewConstMetric ( p . data . connectionsTotal ,
prometheus . CounterValue , float64 ( s . Active ) , p . ingressClass , p . watchNamespace , "active" )
ch <- prometheus . MustNewConstMetric ( p . data . connectionsTotal ,
prometheus . CounterValue , float64 ( s . Accepted ) , p . ingressClass , p . watchNamespace , "accepted" )
ch <- prometheus . MustNewConstMetric ( p . data . connectionsTotal ,
prometheus . CounterValue , float64 ( s . Handled ) , p . ingressClass , p . watchNamespace , "handled" )
ch <- prometheus . MustNewConstMetric ( p . data . requestsTotal ,
prometheus . CounterValue , float64 ( s . Requests ) , p . ingressClass , p . watchNamespace )
ch <- prometheus . MustNewConstMetric ( p . data . connections ,
prometheus . GaugeValue , float64 ( s . Reading ) , p . ingressClass , p . watchNamespace , "reading" )
ch <- prometheus . MustNewConstMetric ( p . data . connections ,
prometheus . GaugeValue , float64 ( s . Writing ) , p . ingressClass , p . watchNamespace , "writing" )
ch <- prometheus . MustNewConstMetric ( p . data . connections ,
prometheus . GaugeValue , float64 ( s . Waiting ) , p . ingressClass , p . watchNamespace , "waiting" )
2017-03-10 13:01:26 +00:00
}