2023-07-09 02:37:41 +02:00
|
|
|
package api
|
2021-08-21 23:38:23 +02:00
|
|
|
|
|
|
|
import (
|
2024-04-09 03:00:40 +02:00
|
|
|
"errors"
|
2021-08-22 03:25:49 +02:00
|
|
|
"log"
|
2021-08-21 23:38:23 +02:00
|
|
|
"math"
|
|
|
|
"net/http"
|
|
|
|
"sort"
|
|
|
|
"time"
|
|
|
|
|
2022-12-06 07:41:09 +01:00
|
|
|
"github.com/TwiN/gatus/v5/storage/store"
|
|
|
|
"github.com/TwiN/gatus/v5/storage/store/common"
|
2023-07-09 02:37:41 +02:00
|
|
|
"github.com/gofiber/fiber/v2"
|
2021-08-21 23:38:23 +02:00
|
|
|
"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},
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
2023-07-09 02:37:41 +02:00
|
|
|
func ResponseTimeChart(c *fiber.Ctx) error {
|
|
|
|
duration := c.Params("duration")
|
2024-08-12 04:40:19 +02:00
|
|
|
chartTimestampFormatter := chart.TimeValueFormatterWithFormat(timeFormat)
|
2021-08-21 23:38:23 +02:00
|
|
|
var from time.Time
|
|
|
|
switch duration {
|
2024-08-12 04:40:19 +02:00
|
|
|
case "30d":
|
|
|
|
from = time.Now().Truncate(time.Hour).Add(-30 * 24 * time.Hour)
|
|
|
|
chartTimestampFormatter = chart.TimeDateValueFormatter
|
2021-08-21 23:38:23 +02:00
|
|
|
case "7d":
|
2024-08-12 04:40:19 +02:00
|
|
|
from = time.Now().Truncate(time.Hour).Add(-7 * 24 * time.Hour)
|
2021-08-21 23:38:23 +02:00
|
|
|
case "24h":
|
2021-08-22 03:25:49 +02:00
|
|
|
from = time.Now().Truncate(time.Hour).Add(-24 * time.Hour)
|
2021-08-21 23:38:23 +02:00
|
|
|
default:
|
2024-08-12 04:40:19 +02:00
|
|
|
return c.Status(400).SendString("Durations supported: 30d, 7d, 24h")
|
2021-08-21 23:38:23 +02:00
|
|
|
}
|
2023-07-09 02:37:41 +02:00
|
|
|
hourlyAverageResponseTime, err := store.Get().GetHourlyAverageResponseTimeByKey(c.Params("key"), from, time.Now())
|
2021-08-21 23:38:23 +02:00
|
|
|
if err != nil {
|
2024-04-09 03:00:40 +02:00
|
|
|
if errors.Is(err, common.ErrEndpointNotFound) {
|
2023-07-09 02:37:41 +02:00
|
|
|
return c.Status(404).SendString(err.Error())
|
2024-04-09 03:00:40 +02:00
|
|
|
} else if errors.Is(err, common.ErrInvalidTimeRange) {
|
2023-07-09 02:37:41 +02:00
|
|
|
return c.Status(400).SendString(err.Error())
|
2021-08-21 23:38:23 +02:00
|
|
|
}
|
2023-07-09 02:37:41 +02:00
|
|
|
return c.Status(500).SendString(err.Error())
|
2021-08-21 23:38:23 +02:00
|
|
|
}
|
2021-08-22 03:25:49 +02:00
|
|
|
if len(hourlyAverageResponseTime) == 0 {
|
2023-07-09 02:37:41 +02:00
|
|
|
return c.Status(204).SendString("")
|
2021-08-22 03:25:49 +02:00
|
|
|
}
|
2021-08-21 23:38:23 +02:00
|
|
|
series := chart.TimeSeries{
|
|
|
|
Name: "Average response time per hour",
|
|
|
|
Style: chart.Style{
|
|
|
|
StrokeWidth: 1.5,
|
|
|
|
DotWidth: 2.0,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
keys := make([]int, 0, len(hourlyAverageResponseTime))
|
2021-08-22 03:25:49 +02:00
|
|
|
earliestTimestamp := int64(0)
|
2021-08-21 23:38:23 +02:00
|
|
|
for hourlyTimestamp := range hourlyAverageResponseTime {
|
|
|
|
keys = append(keys, int(hourlyTimestamp))
|
2021-08-22 03:25:49 +02:00
|
|
|
if earliestTimestamp == 0 || hourlyTimestamp < earliestTimestamp {
|
|
|
|
earliestTimestamp = hourlyTimestamp
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for earliestTimestamp > from.Unix() {
|
|
|
|
earliestTimestamp -= int64(time.Hour.Seconds())
|
|
|
|
keys = append(keys, int(earliestTimestamp))
|
2021-08-21 23:38:23 +02:00
|
|
|
}
|
|
|
|
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{
|
2024-08-12 04:40:19 +02:00
|
|
|
ValueFormatter: chartTimestampFormatter,
|
2021-08-21 23:38:23 +02:00
|
|
|
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},
|
|
|
|
}
|
2023-07-09 02:37:41 +02:00
|
|
|
c.Set("Content-Type", "image/svg+xml")
|
|
|
|
c.Set("Cache-Control", "no-cache, no-store")
|
|
|
|
c.Set("Expires", "0")
|
|
|
|
c.Status(http.StatusOK)
|
|
|
|
if err := graph.Render(chart.SVG, c); err != nil {
|
2024-04-02 03:47:14 +02:00
|
|
|
log.Println("[api.ResponseTimeChart] Failed to render response time chart:", err.Error())
|
2023-07-09 02:37:41 +02:00
|
|
|
return c.Status(500).SendString(err.Error())
|
2021-08-21 23:38:23 +02:00
|
|
|
}
|
2023-07-09 02:37:41 +02:00
|
|
|
return nil
|
2021-08-21 23:38:23 +02:00
|
|
|
}
|