Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d206f9d20e | |||
| dc8adc1aea | |||
| 35ea262b99 |
@@ -1,4 +1,4 @@
|
|||||||
FROM alpine:3.20.3
|
FROM alpine:3.21.0
|
||||||
|
|
||||||
RUN apk --no-cache upgrade
|
RUN apk --no-cache upgrade
|
||||||
RUN apk --no-cache add ca-certificates tzdata zsh jq tmux curl
|
RUN apk --no-cache add ca-certificates tzdata zsh jq tmux curl
|
||||||
|
|||||||
6
go.mod
6
go.mod
@@ -16,7 +16,7 @@ require (
|
|||||||
github.com/spf13/cobra v1.8.1
|
github.com/spf13/cobra v1.8.1
|
||||||
github.com/stretchr/testify v1.10.0
|
github.com/stretchr/testify v1.10.0
|
||||||
go.ntppool.org/api v0.3.4
|
go.ntppool.org/api v0.3.4
|
||||||
go.ntppool.org/common v0.3.0
|
go.ntppool.org/common v0.3.1
|
||||||
go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho v0.58.0
|
go.opentelemetry.io/contrib/instrumentation/github.com/labstack/echo/otelecho v0.58.0
|
||||||
go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.58.0
|
go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.58.0
|
||||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0
|
||||||
@@ -92,8 +92,8 @@ require (
|
|||||||
golang.org/x/sys v0.28.0 // indirect
|
golang.org/x/sys v0.28.0 // indirect
|
||||||
golang.org/x/text v0.21.0 // indirect
|
golang.org/x/text v0.21.0 // indirect
|
||||||
golang.org/x/time v0.8.0 // indirect
|
golang.org/x/time v0.8.0 // indirect
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20241223144023-3abc09e42ca8 // indirect
|
google.golang.org/genproto/googleapis/api v0.0.0-20241230172942-26aa7a208def // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20241223144023-3abc09e42ca8 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20241230172942-26aa7a208def // indirect
|
||||||
google.golang.org/grpc v1.69.2 // indirect
|
google.golang.org/grpc v1.69.2 // indirect
|
||||||
google.golang.org/protobuf v1.36.1 // indirect
|
google.golang.org/protobuf v1.36.1 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
12
go.sum
12
go.sum
@@ -138,8 +138,8 @@ github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9dec
|
|||||||
go.mongodb.org/mongo-driver v1.11.4/go.mod h1:PTSz5yu21bkT/wXpkS7WR5f0ddqw5quethTUn9WM+2g=
|
go.mongodb.org/mongo-driver v1.11.4/go.mod h1:PTSz5yu21bkT/wXpkS7WR5f0ddqw5quethTUn9WM+2g=
|
||||||
go.ntppool.org/api v0.3.4 h1:KeRyFhIRkjJwZif7hkpqEDEBmukyYGiOi2Fd6j3UzQ0=
|
go.ntppool.org/api v0.3.4 h1:KeRyFhIRkjJwZif7hkpqEDEBmukyYGiOi2Fd6j3UzQ0=
|
||||||
go.ntppool.org/api v0.3.4/go.mod h1:LFLAwnrc/JyjzKnjgf8tCOJhps6oFIjuledS3PCx7xc=
|
go.ntppool.org/api v0.3.4/go.mod h1:LFLAwnrc/JyjzKnjgf8tCOJhps6oFIjuledS3PCx7xc=
|
||||||
go.ntppool.org/common v0.3.0 h1:IuSmyjEhI1F3tr5kc5MqlR4cy5y0o5f3EKvC7Koc6rs=
|
go.ntppool.org/common v0.3.1 h1:JaJpS3m8oAc9jH0yhHYJnjOC+RUzxx/F+EDe0QON4eQ=
|
||||||
go.ntppool.org/common v0.3.0/go.mod h1:25pUt3YUusF1MY0nsljjskcMMeTvKZszVvNsubvWhSM=
|
go.ntppool.org/common v0.3.1/go.mod h1:1SKjFBH9AL7Fj2S0zy41isHzV6dTC+6yIKD5QDtX8aY=
|
||||||
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
||||||
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
||||||
go.opentelemetry.io/contrib/bridges/otelslog v0.8.0 h1:G3sKsNueSdxuACINFxKrQeimAIst0A5ytA2YJH+3e1c=
|
go.opentelemetry.io/contrib/bridges/otelslog v0.8.0 h1:G3sKsNueSdxuACINFxKrQeimAIst0A5ytA2YJH+3e1c=
|
||||||
@@ -246,10 +246,10 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T
|
|||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20241223144023-3abc09e42ca8 h1:st3LcW/BPi75W4q1jJTEor/QWwbNlPlDG0JTn6XhZu0=
|
google.golang.org/genproto/googleapis/api v0.0.0-20241230172942-26aa7a208def h1:0Km0hi+g2KXbXL0+riZzSCKz23f4MmwicuEb00JeonI=
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20241223144023-3abc09e42ca8/go.mod h1:klhJGKFyG8Tn50enBn7gizg4nXGXJ+jqEREdCWaPcV4=
|
google.golang.org/genproto/googleapis/api v0.0.0-20241230172942-26aa7a208def/go.mod h1:u2DoMSpCXjrzqLdobRccQMc9wrnMAJ1DLng0a2yqM2Q=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20241223144023-3abc09e42ca8 h1:TqExAhdPaB60Ux47Cn0oLV07rGnxZzIsaRhQaqS666A=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20241230172942-26aa7a208def h1:4P81qv5JXI/sDNae2ClVx88cgDDA6DPilADkG9tYKz8=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20241223144023-3abc09e42ca8/go.mod h1:lcTa1sDdWEIHMWlITnIczmw5w60CF9ffkb8Z+DVmmjA=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20241230172942-26aa7a208def/go.mod h1:bdAgzvd4kFrpykc5/AC2eLUiegK9T/qxZHD4hXYf/ho=
|
||||||
google.golang.org/grpc v1.69.2 h1:U3S9QEtbXC0bYNvRtcoklF3xGtLViumSYxWykJS+7AU=
|
google.golang.org/grpc v1.69.2 h1:U3S9QEtbXC0bYNvRtcoklF3xGtLViumSYxWykJS+7AU=
|
||||||
google.golang.org/grpc v1.69.2/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4=
|
google.golang.org/grpc v1.69.2/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4=
|
||||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ func (srv *Server) getHistoryParameters(ctx context.Context, c echo.Context) (hi
|
|||||||
|
|
||||||
monitorParam := c.QueryParam("monitor")
|
monitorParam := c.QueryParam("monitor")
|
||||||
|
|
||||||
var monitorID uint32 = 0
|
var monitorID uint32
|
||||||
switch monitorParam {
|
switch monitorParam {
|
||||||
case "":
|
case "":
|
||||||
name := "recentmedian.scores.ntp.dev"
|
name := "recentmedian.scores.ntp.dev"
|
||||||
@@ -145,7 +145,8 @@ func (srv *Server) history(c echo.Context) error {
|
|||||||
ctx, span := tracing.Tracer().Start(c.Request().Context(), "history")
|
ctx, span := tracing.Tracer().Start(c.Request().Context(), "history")
|
||||||
defer span.End()
|
defer span.End()
|
||||||
|
|
||||||
// just cache for a short time by default
|
// set a reasonable default cache time; adjusted later for
|
||||||
|
// happy path common responses
|
||||||
c.Response().Header().Set("Cache-Control", "public,max-age=240")
|
c.Response().Header().Set("Cache-Control", "public,max-age=240")
|
||||||
|
|
||||||
mode := paramHistoryMode(c.Param("mode"))
|
mode := paramHistoryMode(c.Param("mode"))
|
||||||
@@ -223,7 +224,6 @@ func (srv *Server) history(c echo.Context) error {
|
|||||||
default:
|
default:
|
||||||
return c.String(http.StatusNotFound, "not implemented")
|
return c.String(http.StatusNotFound, "not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (srv *Server) historyJSON(ctx context.Context, c echo.Context, server ntpdb.Server, history *logscores.LogScoreHistory) error {
|
func (srv *Server) historyJSON(ctx context.Context, c echo.Context, server ntpdb.Server, history *logscores.LogScoreHistory) error {
|
||||||
@@ -317,21 +317,16 @@ func (srv *Server) historyJSON(ctx context.Context, c echo.Context, server ntpdb
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(history.LogScores) == 0 ||
|
setHistoryCacheControl(c, history)
|
||||||
history.LogScores[len(history.LogScores)-1].Ts.After(time.Now().Add(-8*time.Hour)) {
|
|
||||||
// cache for longer if data hasn't updated for a while
|
|
||||||
c.Request().Header.Set("Cache-Control", "s-maxage=3600,max-age=1800")
|
|
||||||
} else {
|
|
||||||
c.Request().Header.Set("Cache-Control", "s-maxage=300,max-age=240")
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.JSON(http.StatusOK, res)
|
return c.JSON(http.StatusOK, res)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (srv *Server) historyCSV(ctx context.Context, c echo.Context, history *logscores.LogScoreHistory) error {
|
func (srv *Server) historyCSV(ctx context.Context, c echo.Context, history *logscores.LogScoreHistory) error {
|
||||||
log := logger.Setup()
|
log := logger.Setup()
|
||||||
ctx, span := tracing.Tracer().Start(ctx, "history.csv")
|
ctx, span := tracing.Tracer().Start(ctx, "history.csv")
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
b := bytes.NewBuffer([]byte{})
|
b := bytes.NewBuffer([]byte{})
|
||||||
w := csv.NewWriter(b)
|
w := csv.NewWriter(b)
|
||||||
|
|
||||||
@@ -342,7 +337,11 @@ func (srv *Server) historyCSV(ctx context.Context, c echo.Context, history *logs
|
|||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
w.Write([]string{"ts_epoch", "ts", "offset", "step", "score", "monitor_id", "monitor_name", "leap", "error"})
|
err := w.Write([]string{"ts_epoch", "ts", "offset", "step", "score", "monitor_id", "monitor_name", "leap", "error"})
|
||||||
|
if err != nil {
|
||||||
|
log.ErrorContext(ctx, "could not write csv header", "err", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
for _, l := range history.LogScores {
|
for _, l := range history.LogScores {
|
||||||
// log.Debug("csv line", "id", l.ID, "n", i)
|
// log.Debug("csv line", "id", l.ID, "n", i)
|
||||||
|
|
||||||
@@ -381,16 +380,31 @@ func (srv *Server) historyCSV(ctx context.Context, c echo.Context, history *logs
|
|||||||
w.Flush()
|
w.Flush()
|
||||||
if err := w.Error(); err != nil {
|
if err := w.Error(); err != nil {
|
||||||
log.ErrorContext(ctx, "could not flush csv", "err", err)
|
log.ErrorContext(ctx, "could not flush csv", "err", err)
|
||||||
span.End()
|
|
||||||
return c.String(http.StatusInternalServerError, "csv error")
|
return c.String(http.StatusInternalServerError, "csv error")
|
||||||
}
|
}
|
||||||
|
|
||||||
// log.Info("entries", "count", len(history.LogScores), "out_bytes", b.Len())
|
// log.Info("entries", "count", len(history.LogScores), "out_bytes", b.Len())
|
||||||
|
|
||||||
c.Response().Header().Set("Cache-Control", "s-maxage=150,max-age=120")
|
setHistoryCacheControl(c, history)
|
||||||
|
|
||||||
c.Response().Header().Set("Content-Disposition", "inline")
|
c.Response().Header().Set("Content-Disposition", "inline")
|
||||||
// Chrome and Firefox force-download text/csv files, so use text/plain
|
// Chrome and Firefox force-download text/csv files, so use text/plain
|
||||||
// https://bugs.chromium.org/p/chromium/issues/detail?id=152911
|
// https://bugs.chromium.org/p/chromium/issues/detail?id=152911
|
||||||
return c.Blob(http.StatusOK, "text/plain", b.Bytes())
|
return c.Blob(http.StatusOK, "text/plain", b.Bytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
func setHistoryCacheControl(c echo.Context, history *logscores.LogScoreHistory) {
|
||||||
|
hdr := c.Response().Header()
|
||||||
|
if len(history.LogScores) == 0 ||
|
||||||
|
// cache for longer if data hasn't updated for a while; or we didn't
|
||||||
|
// find any.
|
||||||
|
(time.Now().Add(-8 * time.Hour).After(history.LogScores[len(history.LogScores)-1].Ts)) {
|
||||||
|
hdr.Set("Cache-Control", "s-maxage=3600,max-age=1800")
|
||||||
|
} else {
|
||||||
|
if len(history.LogScores) == 1 {
|
||||||
|
hdr.Set("Cache-Control", "s-maxage=60,max-age=35")
|
||||||
|
} else {
|
||||||
|
hdr.Set("Cache-Control", "s-maxage=240,max-age=120")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ func NewServer(ctx context.Context, configFile string) (*Server, error) {
|
|||||||
Environment: conf.DeploymentMode(),
|
Environment: conf.DeploymentMode(),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, fmt.Errorf("tracing init: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
srv.tpShutdown = append(srv.tpShutdown, tpShutdown)
|
srv.tpShutdown = append(srv.tpShutdown, tpShutdown)
|
||||||
@@ -274,22 +274,22 @@ func (srv *Server) userCountryData(c echo.Context) error {
|
|||||||
UserCountry: data,
|
UserCountry: data,
|
||||||
ZoneStats: zoneStats,
|
ZoneStats: zoneStats,
|
||||||
})
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func healthHandler(srv *Server, log *slog.Logger) func(w http.ResponseWriter, req *http.Request) {
|
func healthHandler(srv *Server, log *slog.Logger) func(w http.ResponseWriter, req *http.Request) {
|
||||||
|
|
||||||
return func(w http.ResponseWriter, req *http.Request) {
|
return func(w http.ResponseWriter, req *http.Request) {
|
||||||
|
|
||||||
ctx := req.Context()
|
ctx := req.Context()
|
||||||
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
g, ctx := errgroup.WithContext(ctx)
|
g, ctx := errgroup.WithContext(ctx)
|
||||||
|
|
||||||
stats := srv.db.Stats()
|
stats := srv.db.Stats()
|
||||||
|
if stats.OpenConnections > 5 {
|
||||||
log.InfoContext(ctx, "health requests", "url", req.URL.String(), "stats", stats)
|
log.InfoContext(ctx, "health requests", "url", req.URL.String(), "stats", stats)
|
||||||
|
}
|
||||||
|
|
||||||
reset, err := strconv.ParseBool(req.URL.Query().Get("reset"))
|
if resetParam := req.URL.Query().Get("reset"); resetParam != "" {
|
||||||
|
reset, err := strconv.ParseBool(resetParam)
|
||||||
log.InfoContext(ctx, "db reset request", "err", err, "reset", reset)
|
log.InfoContext(ctx, "db reset request", "err", err, "reset", reset)
|
||||||
|
|
||||||
if err == nil && reset {
|
if err == nil && reset {
|
||||||
@@ -297,6 +297,7 @@ func healthHandler(srv *Server, log *slog.Logger) func(w http.ResponseWriter, re
|
|||||||
srv.db.SetMaxIdleConns(0)
|
srv.db.SetMaxIdleConns(0)
|
||||||
srv.db.SetConnMaxLifetime(5 * time.Second)
|
srv.db.SetConnMaxLifetime(5 * time.Second)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
g.Go(func() error {
|
g.Go(func() error {
|
||||||
err := srv.ch.Scores.Ping(ctx)
|
err := srv.ch.Scores.Ping(ctx)
|
||||||
@@ -316,13 +317,19 @@ func healthHandler(srv *Server, log *slog.Logger) func(w http.ResponseWriter, re
|
|||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
err = g.Wait()
|
err := g.Wait()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
w.WriteHeader(http.StatusServiceUnavailable)
|
w.WriteHeader(http.StatusServiceUnavailable)
|
||||||
w.Write([]byte("db ping err"))
|
_, err = w.Write([]byte("db ping err"))
|
||||||
|
if err != nil {
|
||||||
|
log.ErrorContext(ctx, "could not write response", "err", err)
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
w.Write([]byte("ok"))
|
_, err = w.Write([]byte("ok"))
|
||||||
|
if err != nil {
|
||||||
|
log.ErrorContext(ctx, "could not write response", "err", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user