123 lines
		
	
	
		
			3.5 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			123 lines
		
	
	
		
			3.5 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package handler
 | |
| 
 | |
| import (
 | |
| 	"log"
 | |
| 	"math"
 | |
| 	"net/http"
 | |
| 	"sort"
 | |
| 	"time"
 | |
| 
 | |
| 	"github.com/TwiN/gatus/v5/storage/store"
 | |
| 	"github.com/TwiN/gatus/v5/storage/store/common"
 | |
| 	"github.com/gorilla/mux"
 | |
| 	"github.com/wcharczuk/go-chart/v2"
 | |
| 	"github.com/wcharczuk/go-chart/v2/drawing"
 | |
| )
 | |
| 
 | |
| const timeFormat = "3:04PM"
 | |
| 
 | |
| var (
 | |
| 	gridStyle = chart.Style{
 | |
| 		StrokeColor: drawing.Color{R: 119, G: 119, B: 119, A: 40},
 | |
| 		StrokeWidth: 1.0,
 | |
| 	}
 | |
| 	axisStyle = chart.Style{
 | |
| 		FontColor: drawing.Color{R: 119, G: 119, B: 119, A: 255},
 | |
| 	}
 | |
| 	transparentStyle = chart.Style{
 | |
| 		FillColor: drawing.Color{R: 255, G: 255, B: 255, A: 0},
 | |
| 	}
 | |
| )
 | |
| 
 | |
| func ResponseTimeChart(writer http.ResponseWriter, r *http.Request) {
 | |
| 	vars := mux.Vars(r)
 | |
| 	duration := vars["duration"]
 | |
| 	var from time.Time
 | |
| 	switch duration {
 | |
| 	case "7d":
 | |
| 		from = time.Now().Truncate(time.Hour).Add(-24 * 7 * time.Hour)
 | |
| 	case "24h":
 | |
| 		from = time.Now().Truncate(time.Hour).Add(-24 * time.Hour)
 | |
| 	default:
 | |
| 		http.Error(writer, "Durations supported: 7d, 24h", http.StatusBadRequest)
 | |
| 		return
 | |
| 	}
 | |
| 	hourlyAverageResponseTime, err := store.Get().GetHourlyAverageResponseTimeByKey(vars["key"], from, time.Now())
 | |
| 	if err != nil {
 | |
| 		if err == common.ErrEndpointNotFound {
 | |
| 			http.Error(writer, err.Error(), http.StatusNotFound)
 | |
| 		} else if err == common.ErrInvalidTimeRange {
 | |
| 			http.Error(writer, err.Error(), http.StatusBadRequest)
 | |
| 		} else {
 | |
| 			http.Error(writer, err.Error(), http.StatusInternalServerError)
 | |
| 		}
 | |
| 		return
 | |
| 	}
 | |
| 	if len(hourlyAverageResponseTime) == 0 {
 | |
| 		http.Error(writer, "", http.StatusNoContent)
 | |
| 		return
 | |
| 	}
 | |
| 	series := chart.TimeSeries{
 | |
| 		Name: "Average response time per hour",
 | |
| 		Style: chart.Style{
 | |
| 			StrokeWidth: 1.5,
 | |
| 			DotWidth:    2.0,
 | |
| 		},
 | |
| 	}
 | |
| 	keys := make([]int, 0, len(hourlyAverageResponseTime))
 | |
| 	earliestTimestamp := int64(0)
 | |
| 	for hourlyTimestamp := range hourlyAverageResponseTime {
 | |
| 		keys = append(keys, int(hourlyTimestamp))
 | |
| 		if earliestTimestamp == 0 || hourlyTimestamp < earliestTimestamp {
 | |
| 			earliestTimestamp = hourlyTimestamp
 | |
| 		}
 | |
| 	}
 | |
| 	for earliestTimestamp > from.Unix() {
 | |
| 		earliestTimestamp -= int64(time.Hour.Seconds())
 | |
| 		keys = append(keys, int(earliestTimestamp))
 | |
| 	}
 | |
| 	sort.Ints(keys)
 | |
| 	var maxAverageResponseTime float64
 | |
| 	for _, key := range keys {
 | |
| 		averageResponseTime := float64(hourlyAverageResponseTime[int64(key)])
 | |
| 		if maxAverageResponseTime < averageResponseTime {
 | |
| 			maxAverageResponseTime = averageResponseTime
 | |
| 		}
 | |
| 		series.XValues = append(series.XValues, time.Unix(int64(key), 0))
 | |
| 		series.YValues = append(series.YValues, averageResponseTime)
 | |
| 	}
 | |
| 	graph := chart.Chart{
 | |
| 		Canvas:     transparentStyle,
 | |
| 		Background: transparentStyle,
 | |
| 		Width:      1280,
 | |
| 		Height:     300,
 | |
| 		XAxis: chart.XAxis{
 | |
| 			ValueFormatter: chart.TimeValueFormatterWithFormat(timeFormat),
 | |
| 			GridMajorStyle: gridStyle,
 | |
| 			GridMinorStyle: gridStyle,
 | |
| 			Style:          axisStyle,
 | |
| 			NameStyle:      axisStyle,
 | |
| 		},
 | |
| 		YAxis: chart.YAxis{
 | |
| 			Name:           "Average response time",
 | |
| 			GridMajorStyle: gridStyle,
 | |
| 			GridMinorStyle: gridStyle,
 | |
| 			Style:          axisStyle,
 | |
| 			NameStyle:      axisStyle,
 | |
| 			Range: &chart.ContinuousRange{
 | |
| 				Min: 0,
 | |
| 				Max: math.Ceil(maxAverageResponseTime * 1.25),
 | |
| 			},
 | |
| 		},
 | |
| 		Series: []chart.Series{series},
 | |
| 	}
 | |
| 	writer.Header().Set("Content-Type", "image/svg+xml")
 | |
| 	writer.Header().Set("Cache-Control", "no-cache, no-store")
 | |
| 	writer.Header().Set("Expires", "0")
 | |
| 	writer.WriteHeader(http.StatusOK)
 | |
| 	if err := graph.Render(chart.SVG, writer); err != nil {
 | |
| 		log.Println("[handler][ResponseTimeChart] Failed to render response time chart:", err.Error())
 | |
| 		return
 | |
| 	}
 | |
| }
 |