tools/fake-webserver/server.go (147 lines of code) (raw):

// Copyright 2019 The Prometheus 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 main import ( "fmt" "math/rand" "net/http" _ "net/http/pprof" "time" "github.com/prometheus/client_golang/prometheus" ) var ( registry = prometheus.NewRegistry() namespace = "codelab" subsystem = "api" requestHistogram = prometheus.NewHistogramVec( prometheus.HistogramOpts{ Namespace: namespace, Subsystem: subsystem, Name: "request_duration_seconds", Help: "A histogram of the API HTTP request durations in seconds.", Buckets: prometheus.ExponentialBuckets(0.0001, 1.5, 25), }, []string{"method", "path", "status"}, ) requestsInProgress = prometheus.NewGauge( prometheus.GaugeOpts{ Namespace: namespace, Subsystem: subsystem, Name: "http_requests_in_progress", Help: "The current number of API HTTP requests in progress.", }) requestsTotal = prometheus.NewCounterVec( prometheus.CounterOpts{ Namespace: namespace, Subsystem: subsystem, Name: "requests_total", Help: "Total number of requests", }, []string{"method", "path", "status"}, ) requestErrorsTotal = prometheus.NewCounterVec( prometheus.CounterOpts{ Namespace: namespace, Subsystem: subsystem, Name: "request_errors_total", Help: "Total number of request errors", }, []string{"method", "path", "status"}, ) ) func init() { registry.MustRegister( requestsTotal, requestErrorsTotal, requestHistogram, requestsInProgress, ) } type responseOpts struct { baseLatency time.Duration errorRatio float64 // Whenever 10*outageDuration has passed, an outage will be simulated // that lasts for outageDuration. During the outage, errorRatio is // increased by a factor of 10, and baseLatency by a factor of 3. At // start-up time, an outage is simulated, too (so that you can see the // effects right ahead and don't have to wait for 10*outageDuration). outageDuration time.Duration } var opts = map[string]map[string]responseOpts{ "/api/foo": map[string]responseOpts{ "GET": responseOpts{ baseLatency: 10 * time.Millisecond, errorRatio: 0.005, outageDuration: 23 * time.Second, }, "POST": responseOpts{ baseLatency: 20 * time.Millisecond, errorRatio: 0.02, outageDuration: time.Minute, }, }, "/api/bar": map[string]responseOpts{ "GET": responseOpts{ baseLatency: 15 * time.Millisecond, errorRatio: 0.0025, outageDuration: 13 * time.Second, }, "POST": responseOpts{ baseLatency: 50 * time.Millisecond, errorRatio: 0.01, outageDuration: 47 * time.Second, }, }, "/api/baz": map[string]responseOpts{ "GET": responseOpts{ baseLatency: 2 * time.Millisecond, errorRatio: 0.01, outageDuration: 1 * time.Second, }, "POST": responseOpts{ baseLatency: 4 * time.Millisecond, errorRatio: 0.02, outageDuration: 2 * time.Second, }, }, "/api/boom": map[string]responseOpts{ "GET": responseOpts{ baseLatency: 5 * time.Millisecond, errorRatio: 0.01, outageDuration: 1 * time.Second, }, "POST": responseOpts{ baseLatency: 14 * time.Millisecond, errorRatio: 0.02, outageDuration: 2 * time.Second, }, }, } func handleAPI(method, path string) { requestsInProgress.Inc() status := http.StatusOK duration := time.Millisecond defer func() { requestsInProgress.Dec() requestHistogram.With(prometheus.Labels{ "method": method, "path": path, "status": fmt.Sprint(status), }).Observe(duration.Seconds()) requestsTotal.WithLabelValues(method, path, fmt.Sprint(status)).Inc() }() pathOpts, ok := opts[path] if !ok { status = http.StatusNotFound return } methodOpts, ok := pathOpts[method] if !ok { status = http.StatusMethodNotAllowed return } latencyFactor := time.Duration(1) errorFactor := 1. if time.Since(start)%(10*methodOpts.outageDuration) < methodOpts.outageDuration { latencyFactor *= 3 errorFactor *= 10 } duration = (methodOpts.baseLatency + time.Duration(rand.NormFloat64()*float64(methodOpts.baseLatency)/10)) * latencyFactor if rand.Float64() <= methodOpts.errorRatio*errorFactor { status = http.StatusInternalServerError requestErrorsTotal.WithLabelValues(method, path, fmt.Sprint(status)).Inc() } }