mirror of
https://github.com/TwiN/gatus.git
synced 2025-03-04 10:01:41 +01:00
feat(api): Expose uptime data as text via API (#758)
* Expose Raw Uptime Data via API Signed-off-by: James Hillyard <james.hillyard@payara.fish> * Add Test for Raw Uptime Data API Endpoint Signed-off-by: James Hillyard <james.hillyard@payara.fish> * Document Raw Uptime Data API Endpoint Signed-off-by: James Hillyard <james.hillyard@payara.fish> * Fix Test after #759 Core Refactor Signed-off-by: James Hillyard <james.hillyard@payara.fish> * Update Raw Data Content Type Signed-off-by: James Hillyard <james.hillyard@payara.fish> * Support 30d Data from Raw Uptime Endpoint Signed-off-by: James Hillyard <james.hillyard@payara.fish> * Update README.md * Update README.md --------- Signed-off-by: James Hillyard <james.hillyard@payara.fish> Co-authored-by: TwiN <twin@linux.com>
This commit is contained in:
parent
e88f47f0f4
commit
c44d998fb3
18
README.md
18
README.md
@ -119,6 +119,7 @@ Have any feedback or questions? [Create a discussion](https://github.com/TwiN/ga
|
|||||||
- [Response time](#response-time)
|
- [Response time](#response-time)
|
||||||
- [How to change the color thresholds of the response time badge](#how-to-change-the-color-thresholds-of-the-response-time-badge)
|
- [How to change the color thresholds of the response time badge](#how-to-change-the-color-thresholds-of-the-response-time-badge)
|
||||||
- [API](#api)
|
- [API](#api)
|
||||||
|
- [Raw Data](#raw-data)
|
||||||
- [Installing as binary](#installing-as-binary)
|
- [Installing as binary](#installing-as-binary)
|
||||||
- [High level design overview](#high-level-design-overview)
|
- [High level design overview](#high-level-design-overview)
|
||||||
|
|
||||||
@ -2404,6 +2405,23 @@ Gzip compression will be used if the `Accept-Encoding` HTTP header contains `gzi
|
|||||||
The API will return a JSON payload with the `Content-Type` response header set to `application/json`.
|
The API will return a JSON payload with the `Content-Type` response header set to `application/json`.
|
||||||
No such header is required to query the API.
|
No such header is required to query the API.
|
||||||
|
|
||||||
|
#### Raw Data
|
||||||
|
Gatus exposes the raw data for one of your monitored endpoints.
|
||||||
|
This allows you to track and aggregate data in your own applications for monitored endpoints. For instance if you want to track uptime for a period longer than 7 days.
|
||||||
|
|
||||||
|
##### Uptime
|
||||||
|
The path to get raw uptime data for an endpoint is:
|
||||||
|
```
|
||||||
|
/api/v1/endpoints/{key}/uptimes/{duration}
|
||||||
|
```
|
||||||
|
Where:
|
||||||
|
- `{duration}` is `30d` (alpha), `7d`, `24h` or `1h`
|
||||||
|
- `{key}` has the pattern `<GROUP_NAME>_<ENDPOINT_NAME>` in which both variables have ` `, `/`, `_`, `,` and `.` replaced by `-`.
|
||||||
|
|
||||||
|
For instance, if you want the raw uptime data for the last 24 hours from the endpoint `frontend` in the group `core`, the URL would look like this:
|
||||||
|
```
|
||||||
|
https://example.com/api/v1/endpoints/core_frontend/uptimes/24h
|
||||||
|
```
|
||||||
|
|
||||||
### Installing as binary
|
### Installing as binary
|
||||||
You can download Gatus as a binary using the following command:
|
You can download Gatus as a binary using the following command:
|
||||||
|
@ -78,6 +78,7 @@ func (a *API) createRouter(cfg *config.Config) *fiber.App {
|
|||||||
unprotectedAPIRouter.Get("/v1/config", ConfigHandler{securityConfig: cfg.Security}.GetConfig)
|
unprotectedAPIRouter.Get("/v1/config", ConfigHandler{securityConfig: cfg.Security}.GetConfig)
|
||||||
unprotectedAPIRouter.Get("/v1/endpoints/:key/health/badge.svg", HealthBadge)
|
unprotectedAPIRouter.Get("/v1/endpoints/:key/health/badge.svg", HealthBadge)
|
||||||
unprotectedAPIRouter.Get("/v1/endpoints/:key/health/badge.shields", HealthBadgeShields)
|
unprotectedAPIRouter.Get("/v1/endpoints/:key/health/badge.shields", HealthBadgeShields)
|
||||||
|
unprotectedAPIRouter.Get("/v1/endpoints/:key/uptimes/:duration", UptimeRaw)
|
||||||
unprotectedAPIRouter.Get("/v1/endpoints/:key/uptimes/:duration/badge.svg", UptimeBadge)
|
unprotectedAPIRouter.Get("/v1/endpoints/:key/uptimes/:duration/badge.svg", UptimeBadge)
|
||||||
unprotectedAPIRouter.Get("/v1/endpoints/:key/response-times/:duration/badge.svg", ResponseTimeBadge(cfg))
|
unprotectedAPIRouter.Get("/v1/endpoints/:key/response-times/:duration/badge.svg", ResponseTimeBadge(cfg))
|
||||||
unprotectedAPIRouter.Get("/v1/endpoints/:key/response-times/:duration/chart.svg", ResponseTimeChart)
|
unprotectedAPIRouter.Get("/v1/endpoints/:key/response-times/:duration/chart.svg", ResponseTimeChart)
|
||||||
|
43
api/raw.go
Normal file
43
api/raw.go
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/TwiN/gatus/v5/storage/store"
|
||||||
|
"github.com/TwiN/gatus/v5/storage/store/common"
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func UptimeRaw(c *fiber.Ctx) error {
|
||||||
|
duration := c.Params("duration")
|
||||||
|
var from time.Time
|
||||||
|
switch duration {
|
||||||
|
case "30d":
|
||||||
|
from = time.Now().Add(-30 * 24 * time.Hour)
|
||||||
|
case "7d":
|
||||||
|
from = time.Now().Add(-7 * 24 * time.Hour)
|
||||||
|
case "24h":
|
||||||
|
from = time.Now().Add(-24 * time.Hour)
|
||||||
|
case "1h":
|
||||||
|
from = time.Now().Add(-2 * time.Hour) // Because uptime metrics are stored by hour, we have to cheat a little
|
||||||
|
default:
|
||||||
|
return c.Status(400).SendString("Durations supported: 30d,7d, 24h, 1h")
|
||||||
|
}
|
||||||
|
key := c.Params("key")
|
||||||
|
uptime, err := store.Get().GetUptimeByKey(key, from, time.Now())
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, common.ErrEndpointNotFound) {
|
||||||
|
return c.Status(404).SendString(err.Error())
|
||||||
|
} else if errors.Is(err, common.ErrInvalidTimeRange) {
|
||||||
|
return c.Status(400).SendString(err.Error())
|
||||||
|
}
|
||||||
|
return c.Status(500).SendString(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Set("Content-Type", "text/plain")
|
||||||
|
c.Set("Cache-Control", "no-cache, no-store, must-revalidate")
|
||||||
|
c.Set("Expires", "0")
|
||||||
|
return c.Status(200).Send([]byte(fmt.Sprintf("%f", uptime)))
|
||||||
|
}
|
93
api/raw_test.go
Normal file
93
api/raw_test.go
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/TwiN/gatus/v5/config"
|
||||||
|
"github.com/TwiN/gatus/v5/config/endpoint"
|
||||||
|
"github.com/TwiN/gatus/v5/config/endpoint/ui"
|
||||||
|
"github.com/TwiN/gatus/v5/storage/store"
|
||||||
|
"github.com/TwiN/gatus/v5/watchdog"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRawDataEndpoint(t *testing.T) {
|
||||||
|
defer store.Get().Clear()
|
||||||
|
defer cache.Clear()
|
||||||
|
cfg := &config.Config{
|
||||||
|
Metrics: true,
|
||||||
|
Endpoints: []*endpoint.Endpoint{
|
||||||
|
{
|
||||||
|
Name: "frontend",
|
||||||
|
Group: "core",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "backend",
|
||||||
|
Group: "core",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg.Endpoints[0].UIConfig = ui.GetDefaultConfig()
|
||||||
|
cfg.Endpoints[1].UIConfig = ui.GetDefaultConfig()
|
||||||
|
|
||||||
|
watchdog.UpdateEndpointStatuses(cfg.Endpoints[0], &endpoint.Result{Success: true, Connected: true, Duration: time.Millisecond, Timestamp: time.Now()})
|
||||||
|
watchdog.UpdateEndpointStatuses(cfg.Endpoints[1], &endpoint.Result{Success: false, Connected: false, Duration: time.Second, Timestamp: time.Now()})
|
||||||
|
api := New(cfg)
|
||||||
|
router := api.Router()
|
||||||
|
type Scenario struct {
|
||||||
|
Name string
|
||||||
|
Path string
|
||||||
|
ExpectedCode int
|
||||||
|
Gzip bool
|
||||||
|
}
|
||||||
|
scenarios := []Scenario{
|
||||||
|
{
|
||||||
|
Name: "raw-uptime-1h",
|
||||||
|
Path: "/api/v1/endpoints/core_frontend/uptimes/1h",
|
||||||
|
ExpectedCode: http.StatusOK,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "raw-uptime-24h",
|
||||||
|
Path: "/api/v1/endpoints/core_backend/uptimes/24h",
|
||||||
|
ExpectedCode: http.StatusOK,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "raw-uptime-7d",
|
||||||
|
Path: "/api/v1/endpoints/core_frontend/uptimes/7d",
|
||||||
|
ExpectedCode: http.StatusOK,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "raw-uptime-30d",
|
||||||
|
Path: "/api/v1/endpoints/core_frontend/uptimes/30d",
|
||||||
|
ExpectedCode: http.StatusOK,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "raw-uptime-with-invalid-duration",
|
||||||
|
Path: "/api/v1/endpoints/core_backend/uptimes/3d",
|
||||||
|
ExpectedCode: http.StatusBadRequest,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "raw-uptime-for-invalid-key",
|
||||||
|
Path: "/api/v1/endpoints/invalid_key/uptimes/7d",
|
||||||
|
ExpectedCode: http.StatusNotFound,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, scenario := range scenarios {
|
||||||
|
t.Run(scenario.Name, func(t *testing.T) {
|
||||||
|
request := httptest.NewRequest("GET", scenario.Path, http.NoBody)
|
||||||
|
if scenario.Gzip {
|
||||||
|
request.Header.Set("Accept-Encoding", "gzip")
|
||||||
|
}
|
||||||
|
response, err := router.Test(request)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if response.StatusCode != scenario.ExpectedCode {
|
||||||
|
t.Errorf("%s %s should have returned %d, but returned %d instead", request.Method, request.URL, scenario.ExpectedCode, response.StatusCode)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user