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 }