All checks were successful
continuous-integration/drone/push Build is passing
Replace MySQL driver with pgx/v5 and pgxpool: - Update sqlc to use postgresql engine - Convert query.sql to PostgreSQL syntax ($1 params, CASE WHEN, ANY() arrays) - Replace sql.DB with pgxpool.Pool throughout - Change nullable types from sql.Null* to pgtype.* - Update ID types from uint32 to int64 for PostgreSQL compatibility - Delete MySQL-specific dynamic_connect.go - Add opentelemetry.gowrap template for tracing
147 lines
3.7 KiB
Go
147 lines
3.7 KiB
Go
package server
|
|
|
|
import (
|
|
"errors"
|
|
"net/http"
|
|
"strconv"
|
|
"time"
|
|
|
|
"github.com/jackc/pgx/v5"
|
|
"github.com/labstack/echo/v4"
|
|
"go.ntppool.org/common/logger"
|
|
"go.ntppool.org/common/tracing"
|
|
"go.ntppool.org/data-api/ntpdb"
|
|
)
|
|
|
|
func (srv *Server) zoneCounts(c echo.Context) error {
|
|
log := logger.Setup()
|
|
ctx, span := tracing.Tracer().Start(c.Request().Context(), "zoneCounts")
|
|
defer span.End()
|
|
|
|
// just cache for a short time by default
|
|
c.Response().Header().Set("Cache-Control", "public,max-age=240")
|
|
c.Response().Header().Set("Access-Control-Allow-Origin", "*")
|
|
c.Response().Header().Del("Vary")
|
|
|
|
q := ntpdb.NewWrappedQuerier(ntpdb.New(srv.db))
|
|
|
|
zone, err := q.GetZoneByName(ctx, c.Param("zone_name"))
|
|
if err != nil || zone.ID == 0 {
|
|
if errors.Is(err, pgx.ErrNoRows) {
|
|
return c.String(http.StatusNotFound, "Not found")
|
|
}
|
|
log.ErrorContext(ctx, "could not query for zone", "err", err)
|
|
span.RecordError(err)
|
|
return echo.NewHTTPError(http.StatusInternalServerError, "internal error")
|
|
}
|
|
|
|
counts, err := q.GetZoneCounts(ctx, zone.ID)
|
|
if err != nil {
|
|
if !errors.Is(err, pgx.ErrNoRows) {
|
|
log.ErrorContext(ctx, "get counts", "err", err)
|
|
span.RecordError(err)
|
|
return c.String(http.StatusInternalServerError, "internal error")
|
|
}
|
|
}
|
|
|
|
type historyEntry struct {
|
|
D string `json:"d"` // date
|
|
Ts int `json:"ts"` // epoch timestamp
|
|
Rc int `json:"rc"` // count registered
|
|
Ac int `json:"ac"` // count active
|
|
W int `json:"w"` // netspeed active
|
|
Iv string `json:"iv"` // ip version
|
|
}
|
|
|
|
rv := struct {
|
|
History []historyEntry `json:"history"`
|
|
}{}
|
|
|
|
skipCount := 0.0
|
|
limit := 0
|
|
|
|
if limitParam := c.QueryParam("limit"); len(limitParam) > 0 {
|
|
if limitInt, err := strconv.Atoi(limitParam); err == nil && limitInt > 0 {
|
|
limit = limitInt
|
|
}
|
|
}
|
|
|
|
var mostRecentDate int64 = -1
|
|
if limit > 0 {
|
|
count := 0
|
|
dates := map[int64]bool{}
|
|
for _, c := range counts {
|
|
ep := c.Date.Time.Unix()
|
|
if _, ok := dates[ep]; !ok {
|
|
count++
|
|
dates[ep] = true
|
|
mostRecentDate = ep
|
|
}
|
|
}
|
|
if limit < count {
|
|
if limit > 1 {
|
|
skipCount = float64(count) / float64(limit-1)
|
|
} else {
|
|
// skip everything and use the special logic that we always include the most recent date
|
|
skipCount = float64(count) + 1
|
|
}
|
|
}
|
|
|
|
log.DebugContext(ctx, "mod", "count", count, "limit", limit, "mod", count%limit, "skipCount", skipCount)
|
|
// log.Info("limit plan", "date count", count, "limit", limit, "skipCount", skipCount)
|
|
}
|
|
|
|
toSkip := 0.0
|
|
if limit == 1 {
|
|
toSkip = skipCount // we just want to look for the last entry
|
|
}
|
|
lastDate := int64(0)
|
|
lastSkip := int64(0)
|
|
skipThreshold := 0.5
|
|
for _, c := range counts {
|
|
cDate := c.Date.Time.Unix()
|
|
if (toSkip <= skipThreshold && cDate != lastSkip) ||
|
|
lastDate == cDate ||
|
|
mostRecentDate == cDate {
|
|
// log.Info("adding date", "date", c.Date.Time.Format(time.DateOnly))
|
|
rv.History = append(rv.History, historyEntry{
|
|
D: c.Date.Time.Format(time.DateOnly),
|
|
Ts: int(cDate),
|
|
Ac: int(c.CountActive),
|
|
Rc: int(c.CountRegistered),
|
|
W: int(c.NetspeedActive),
|
|
Iv: string(c.IpVersion),
|
|
})
|
|
lastDate = cDate
|
|
} else {
|
|
// log.Info("skipping date", "date", c.Date.Format(time.DateOnly))
|
|
if lastSkip == cDate {
|
|
continue
|
|
}
|
|
toSkip--
|
|
lastSkip = cDate
|
|
continue
|
|
}
|
|
if toSkip <= skipThreshold && skipCount > 0 {
|
|
toSkip += skipCount
|
|
}
|
|
|
|
}
|
|
|
|
if limit > 0 {
|
|
count := 0
|
|
dates := map[int]bool{}
|
|
for _, c := range rv.History {
|
|
ep := c.Ts
|
|
if _, ok := dates[ep]; !ok {
|
|
count++
|
|
dates[ep] = true
|
|
}
|
|
}
|
|
log.DebugContext(ctx, "result counts", "skipCount", skipCount, "limit", limit, "got", count)
|
|
}
|
|
|
|
c.Response().Header().Set("Cache-Control", "s-maxage=28800, max-age=7200")
|
|
return c.JSON(http.StatusOK, rv)
|
|
}
|