184 lines
4.2 KiB
Go
184 lines
4.2 KiB
Go
package server
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"database/sql"
|
|
"encoding/csv"
|
|
"fmt"
|
|
"net/http"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/labstack/echo/v4"
|
|
"go.ntppool.org/common/logger"
|
|
"go.ntppool.org/common/tracing"
|
|
"go.ntppool.org/data-api/logscores"
|
|
"go.ntppool.org/data-api/ntpdb"
|
|
)
|
|
|
|
type historyMode uint8
|
|
|
|
const (
|
|
historyModeUnknown historyMode = iota
|
|
historyModeLog
|
|
historyModeJSON
|
|
historyModeMonitor
|
|
)
|
|
|
|
func paramHistoryMode(s string) historyMode {
|
|
switch s {
|
|
case "log":
|
|
return historyModeLog
|
|
case "json":
|
|
return historyModeJSON
|
|
case "monitor":
|
|
return historyModeMonitor
|
|
default:
|
|
return historyModeUnknown
|
|
}
|
|
}
|
|
|
|
func (srv *Server) getHistory(ctx context.Context, c echo.Context, server ntpdb.Server) (*logscores.LogScoreHistory, error) {
|
|
log := logger.Setup()
|
|
|
|
limit := 0
|
|
if limitParam, err := strconv.Atoi(c.QueryParam("limit")); err == nil {
|
|
limit = limitParam
|
|
} else {
|
|
limit = 50
|
|
}
|
|
if limit > 4000 {
|
|
limit = 4000
|
|
}
|
|
|
|
since, _ := strconv.ParseInt(c.QueryParam("since"), 10, 64) // defaults to 0 so don't care if it parses
|
|
|
|
monitorParam := c.QueryParam("monitor")
|
|
|
|
if since > 0 {
|
|
c.Request().Header.Set("Cache-Control", "s-maxage=300")
|
|
}
|
|
|
|
q := ntpdb.NewWrappedQuerier(ntpdb.New(srv.db))
|
|
|
|
var monitorID uint32 = 0
|
|
switch monitorParam {
|
|
case "":
|
|
name := "recentmedian.scores.ntp.dev"
|
|
monitor, err := q.GetMonitorByName(ctx, sql.NullString{Valid: true, String: name})
|
|
if err != nil {
|
|
log.Warn("could not find monitor", "name", name, "err", err)
|
|
}
|
|
monitorID = monitor.ID
|
|
case "*":
|
|
monitorID = 0 // don't filter on monitor ID
|
|
}
|
|
|
|
log.Info("monitor param", "monitor", monitorID)
|
|
|
|
sinceTime := time.Unix(since, 0)
|
|
if since > 0 {
|
|
log.Warn("monitor data requested with since parameter, not supported", "since", sinceTime)
|
|
}
|
|
|
|
ls, err := logscores.GetHistory(ctx, srv.db, server.ID, monitorID, sinceTime, limit)
|
|
|
|
return ls, err
|
|
}
|
|
|
|
func (srv *Server) history(c echo.Context) error {
|
|
log := logger.Setup()
|
|
ctx, span := tracing.Tracer().Start(c.Request().Context(), "history")
|
|
defer span.End()
|
|
|
|
// for errors and 404s, a shorter cache time
|
|
c.Response().Header().Set("Cache-Control", "public,max-age=300")
|
|
|
|
mode := paramHistoryMode(c.Param("mode"))
|
|
if mode == historyModeUnknown {
|
|
return c.String(http.StatusNotFound, "invalid mode")
|
|
}
|
|
|
|
server, err := srv.FindServer(ctx, c.Param("server"))
|
|
if err != nil {
|
|
log.Error("find server", "err", err)
|
|
return c.String(http.StatusInternalServerError, "internal error")
|
|
}
|
|
if server.ID == 0 {
|
|
return c.String(http.StatusNotFound, "server not found")
|
|
}
|
|
|
|
history, err := srv.getHistory(ctx, c, server)
|
|
if err != nil {
|
|
log.Error("get history", "err", err)
|
|
return c.String(http.StatusInternalServerError, "internal error")
|
|
}
|
|
|
|
if mode == historyModeLog {
|
|
|
|
ctx, span := tracing.Tracer().Start(ctx, "history.csv")
|
|
b := bytes.NewBuffer([]byte{})
|
|
w := csv.NewWriter(b)
|
|
|
|
ff := func(f float64) string {
|
|
s := fmt.Sprintf("%.9f", f)
|
|
s = strings.TrimRight(s, "0")
|
|
s = strings.TrimRight(s, ".")
|
|
return s
|
|
}
|
|
|
|
w.Write([]string{"ts_epoch", "ts", "offset", "step", "score", "monitor_id", "monitor_name", "leap", "error"})
|
|
for _, l := range history.LogScores {
|
|
// log.Debug("csv line", "id", l.ID, "n", i)
|
|
|
|
var offset string
|
|
if l.Offset.Valid {
|
|
offset = ff(l.Offset.Float64)
|
|
}
|
|
|
|
step := ff(l.Step)
|
|
score := ff(l.Score)
|
|
var monName string
|
|
if l.MonitorID.Valid {
|
|
monName = history.Monitors[int(l.MonitorID.Int32)]
|
|
}
|
|
var leap string
|
|
if l.Attributes.Leap != 0 {
|
|
leap = fmt.Sprintf("%d", l.Attributes.Leap)
|
|
}
|
|
|
|
err := w.Write([]string{
|
|
strconv.Itoa(int(l.Ts.Unix())),
|
|
// l.Ts.Format(time.RFC3339),
|
|
l.Ts.Format("2006-01-02 15:04:05"),
|
|
offset,
|
|
step,
|
|
score,
|
|
fmt.Sprintf("%d", l.MonitorID.Int32),
|
|
monName,
|
|
leap,
|
|
l.Attributes.Error,
|
|
})
|
|
if err != nil {
|
|
log.Warn("csv encoding error", "ls_id", l.ID, "err", err)
|
|
}
|
|
}
|
|
w.Flush()
|
|
if err := w.Error(); err != nil {
|
|
log.ErrorContext(ctx, "could not flush csv", "err", err)
|
|
span.End()
|
|
return c.String(http.StatusInternalServerError, "csv error")
|
|
}
|
|
|
|
log.Info("entries", "count", len(history.LogScores), "out_bytes", b.Len())
|
|
|
|
span.End()
|
|
return c.Blob(http.StatusOK, "text/csv", b.Bytes())
|
|
|
|
}
|
|
|
|
return c.JSON(http.StatusOK, history)
|
|
}
|