go.etcd.io/etcd@v3.3.27+incompatible/etcdserver/api/etcdhttp/metrics.go (about)

     1  // Copyright 2017 The etcd Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package etcdhttp
    16  
    17  import (
    18  	"context"
    19  	"encoding/json"
    20  	"net/http"
    21  	"time"
    22  
    23  	"github.com/coreos/etcd/etcdserver"
    24  	"github.com/coreos/etcd/etcdserver/etcdserverpb"
    25  	"github.com/coreos/etcd/raft"
    26  
    27  	"github.com/prometheus/client_golang/prometheus"
    28  	"github.com/prometheus/client_golang/prometheus/promhttp"
    29  )
    30  
    31  const (
    32  	PathMetrics = "/metrics"
    33  	PathHealth  = "/health"
    34  )
    35  
    36  // HandleMetricsHealth registers metrics and health handlers.
    37  func HandleMetricsHealth(mux *http.ServeMux, srv etcdserver.ServerV2) {
    38  	mux.Handle(PathMetrics, promhttp.Handler())
    39  	mux.Handle(PathHealth, NewHealthHandler(func() Health { return checkHealth(srv) }))
    40  }
    41  
    42  // HandlePrometheus registers prometheus handler on '/metrics'.
    43  func HandlePrometheus(mux *http.ServeMux) {
    44  	mux.Handle(PathMetrics, promhttp.Handler())
    45  }
    46  
    47  // NewHealthHandler handles '/health' requests.
    48  func NewHealthHandler(hfunc func() Health) http.HandlerFunc {
    49  	return func(w http.ResponseWriter, r *http.Request) {
    50  		if r.Method != http.MethodGet {
    51  			w.Header().Set("Allow", http.MethodGet)
    52  			http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
    53  			plog.Warningf("/health error (status code %d)", http.StatusMethodNotAllowed)
    54  			return
    55  		}
    56  		h := hfunc()
    57  		d, _ := json.Marshal(h)
    58  		if h.Health != "true" {
    59  			http.Error(w, string(d), http.StatusServiceUnavailable)
    60  			return
    61  		}
    62  		w.WriteHeader(http.StatusOK)
    63  		w.Write(d)
    64  	}
    65  }
    66  
    67  var (
    68  	healthSuccess = prometheus.NewCounter(prometheus.CounterOpts{
    69  		Namespace: "etcd",
    70  		Subsystem: "server",
    71  		Name:      "health_success",
    72  		Help:      "The total number of successful health checks",
    73  	})
    74  	healthFailed = prometheus.NewCounter(prometheus.CounterOpts{
    75  		Namespace: "etcd",
    76  		Subsystem: "server",
    77  		Name:      "health_failures",
    78  		Help:      "The total number of failed health checks",
    79  	})
    80  )
    81  
    82  func init() {
    83  	prometheus.MustRegister(healthSuccess)
    84  	prometheus.MustRegister(healthFailed)
    85  }
    86  
    87  // Health defines etcd server health status.
    88  // TODO: remove manual parsing in etcdctl cluster-health
    89  type Health struct {
    90  	Health string `json:"health"`
    91  }
    92  
    93  // TODO: server NOSPACE, etcdserver.ErrNoLeader in health API
    94  
    95  func checkHealth(srv etcdserver.ServerV2) Health {
    96  	h := Health{Health: "true"}
    97  
    98  	as := srv.Alarms()
    99  	if len(as) > 0 {
   100  		h.Health = "false"
   101  		for _, v := range as {
   102  			plog.Warningf("/health error due to an alarm %s", v.String())
   103  		}
   104  	}
   105  
   106  	if h.Health == "true" {
   107  		if uint64(srv.Leader()) == raft.None {
   108  			h.Health = "false"
   109  			plog.Warningf("/health error; no leader (status code %d)", http.StatusServiceUnavailable)
   110  		}
   111  	}
   112  
   113  	if h.Health == "true" {
   114  		ctx, cancel := context.WithTimeout(context.Background(), time.Second)
   115  		_, err := srv.Do(ctx, etcdserverpb.Request{Method: "QGET"})
   116  		cancel()
   117  		if err != nil {
   118  			h.Health = "false"
   119  			plog.Warningf("/health error; QGET failed %v (status code %d)", err, http.StatusServiceUnavailable)
   120  		}
   121  	}
   122  
   123  	if h.Health == "true" {
   124  		healthSuccess.Inc()
   125  		plog.Infof("/health OK (status code %d)", http.StatusOK)
   126  	} else {
   127  		healthFailed.Inc()
   128  	}
   129  	return h
   130  }