github.com/munnerz/test-infra@v0.0.0-20190108210205-ce3d181dc989/boskos/metrics/metrics.go (about)

     1  /*
     2  Copyright 2017 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package main
    18  
    19  import (
    20  	"encoding/json"
    21  	"flag"
    22  	"fmt"
    23  	"net/http"
    24  	"strings"
    25  	"time"
    26  
    27  	"github.com/prometheus/client_golang/prometheus"
    28  	"github.com/prometheus/client_golang/prometheus/promhttp"
    29  	"github.com/sirupsen/logrus"
    30  	"k8s.io/apimachinery/pkg/util/sets"
    31  	"k8s.io/test-infra/boskos/client"
    32  	"k8s.io/test-infra/boskos/common"
    33  )
    34  
    35  type prometheusMetrics struct {
    36  	BoskosState map[string]map[string]prometheus.Gauge
    37  }
    38  
    39  var (
    40  	promMetrics = prometheusMetrics{
    41  		BoskosState: map[string]map[string]prometheus.Gauge{},
    42  	}
    43  	resources, states common.CommaSeparatedStrings
    44  	defaultStates     = []string{
    45  		common.Busy,
    46  		common.Cleaning,
    47  		common.Dirty,
    48  		common.Free,
    49  		common.Leased,
    50  	}
    51  )
    52  
    53  func init() {
    54  	flag.Var(&resources, "resource-type", "comma-separated list of resources need to have metrics collected")
    55  	flag.Var(&states, "resource-state", "comma-separated list of states need to have metrics collected")
    56  }
    57  
    58  func initMetrics() {
    59  	for _, resource := range resources {
    60  		promMetrics.BoskosState[resource] = map[string]prometheus.Gauge{}
    61  		for _, state := range states {
    62  			promMetrics.BoskosState[resource][state] = prometheus.NewGauge(prometheus.GaugeOpts{
    63  				Name: fmt.Sprintf("boskos_%s_%s", strings.Replace(resource, "-", "_", -1), state),
    64  				Help: fmt.Sprintf("Number of %s %s", state, resource),
    65  			})
    66  		}
    67  		// Adding other state for metrics that are not captured with existing state
    68  		promMetrics.BoskosState[resource][common.Other] = prometheus.NewGauge(prometheus.GaugeOpts{
    69  			Name: fmt.Sprintf("boskos_%s_%s", strings.Replace(resource, "-", "_", -1), common.Other),
    70  			Help: fmt.Sprintf("Number of %s %s", common.Other, resource),
    71  		})
    72  	}
    73  
    74  	for _, gauges := range promMetrics.BoskosState {
    75  		for _, gauge := range gauges {
    76  			prometheus.MustRegister(gauge)
    77  		}
    78  	}
    79  }
    80  
    81  func main() {
    82  	logrus.SetFormatter(&logrus.JSONFormatter{})
    83  	boskos := client.NewClient("Metrics", "http://boskos")
    84  	logrus.Infof("Initialzied boskos client!")
    85  
    86  	flag.Parse()
    87  	if states == nil {
    88  		states = defaultStates
    89  	}
    90  
    91  	initMetrics()
    92  
    93  	http.Handle("/prometheus", promhttp.Handler())
    94  	http.Handle("/", handleMetric(boskos))
    95  
    96  	go func() {
    97  		logTick := time.NewTicker(time.Minute).C
    98  		for range logTick {
    99  			if err := update(boskos); err != nil {
   100  				logrus.WithError(err).Warning("[Boskos Metrics]Update failed!")
   101  			}
   102  		}
   103  	}()
   104  
   105  	logrus.Info("Start Service")
   106  	logrus.WithError(http.ListenAndServe(":8080", nil)).Fatal("ListenAndServe returned.")
   107  }
   108  
   109  func filterMetrics(src map[string]int) map[string]int {
   110  	metricStates := sets.NewString(states...)
   111  	dest := map[string]int{}
   112  	// Making sure all metrics are created
   113  	for state := range metricStates {
   114  		dest[state] = 0
   115  	}
   116  	dest[common.Other] = 0
   117  	for state, value := range src {
   118  		if state != common.Other && metricStates.Has(state) {
   119  			dest[state] = value
   120  		} else {
   121  			dest[common.Other] += value
   122  		}
   123  	}
   124  	return dest
   125  }
   126  
   127  func update(boskos *client.Client) error {
   128  	for _, resource := range resources {
   129  		metric, err := boskos.Metric(resource)
   130  		if err != nil {
   131  			return fmt.Errorf("fail to get metric for %s : %v", resource, err)
   132  		}
   133  		// Filtering metrics states
   134  		for state, value := range filterMetrics(metric.Current) {
   135  			promMetrics.BoskosState[resource][state].Set(float64(value))
   136  		}
   137  	}
   138  	return nil
   139  }
   140  
   141  //  handleMetric: Handler for /
   142  //  Method: GET
   143  func handleMetric(boskos *client.Client) http.HandlerFunc {
   144  	return func(res http.ResponseWriter, req *http.Request) {
   145  		log := logrus.WithField("handler", "handleMetric")
   146  		log.Infof("From %v", req.RemoteAddr)
   147  
   148  		if req.Method != "GET" {
   149  			log.Warningf("[BadRequest]method %v, expect GET", req.Method)
   150  			http.Error(res, "only accepts GET request", http.StatusMethodNotAllowed)
   151  			return
   152  		}
   153  
   154  		rtype := req.URL.Query().Get("type")
   155  		if rtype == "" {
   156  			msg := "type must be set in the request."
   157  			log.Warning(msg)
   158  			http.Error(res, msg, http.StatusBadRequest)
   159  			return
   160  		}
   161  
   162  		log.Infof("Request for metric %v", rtype)
   163  
   164  		metric, err := boskos.Metric(rtype)
   165  		if err != nil {
   166  			log.WithError(err).Errorf("Fail to get metic for %v", rtype)
   167  			http.Error(res, err.Error(), http.StatusNotFound)
   168  			return
   169  		}
   170  
   171  		metricJSON, err := json.Marshal(metric)
   172  		if err != nil {
   173  			log.WithError(err).Errorf("json.Marshal failed: %v", metricJSON)
   174  			http.Error(res, err.Error(), http.StatusInternalServerError)
   175  			return
   176  		}
   177  		log.Infof("Metric query for %v: %v", rtype, string(metricJSON))
   178  		fmt.Fprint(res, string(metricJSON))
   179  	}
   180  }