Private
Public Access
1
0

Add png graph handler
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
2023-12-09 13:53:56 -08:00
parent 69cc4b4e80
commit e824274998
13 changed files with 400 additions and 135 deletions

View File

@@ -24,9 +24,7 @@ const pointSymbol = "‱"
func (srv *Server) dnsAnswers(c echo.Context) error {
log := logger.Setup()
ctx := c.Request().Context()
ctx, span := tracing.Tracer().Start(ctx, "dnsanswers")
ctx, span := tracing.Tracer().Start(c.Request().Context(), "dnsanswers")
defer span.End()
// for errors and 404s, a shorter cache time

161
server/graph_image.go Normal file
View File

@@ -0,0 +1,161 @@
package server
import (
"context"
"database/sql"
"errors"
"fmt"
"io"
"net/http"
"net/http/httptrace"
"net/url"
"os"
"strconv"
"time"
"github.com/hashicorp/go-retryablehttp"
"github.com/labstack/echo/v4"
"go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace"
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/trace"
"go.ntppool.org/common/logger"
"go.ntppool.org/common/tracing"
"go.ntppool.org/data-api/ntpdb"
)
func (srv *Server) graphImage(c echo.Context) error {
log := logger.Setup()
ctx, span := tracing.Tracer().Start(c.Request().Context(), "graphImage")
defer span.End()
// cache errors briefly
c.Response().Header().Set("Cache-Control", "public,max-age=240")
serverID := c.Param("server")
imageType := c.Param("type")
log = log.With("serverID", serverID).With("type", imageType)
log.Info("graph parameters")
if imageType != "offset.png" {
return c.String(http.StatusNotFound, "invalid image name")
}
if len(c.QueryString()) > 0 {
// people breaking the varnish cache by adding query parameters
redirectURL := c.Request().URL
redirectURL.RawQuery = ""
log.InfoContext(ctx, "redirecting", "url", redirectURL.String())
return c.Redirect(308, redirectURL.String())
}
q := ntpdb.NewWrappedQuerier(ntpdb.New(srv.db))
var serverData ntpdb.Server
var dberr error
if id, err := strconv.Atoi(serverID); id > 0 && err == nil {
serverData, dberr = q.GetServerByID(ctx, uint32(id))
} else {
serverData, dberr = q.GetServerByIP(ctx, serverID)
}
if dberr != nil {
if !errors.Is(dberr, sql.ErrNoRows) {
log.Error("could not query server id", "err", dberr)
return c.String(http.StatusInternalServerError, "server error")
}
return c.String(http.StatusNotFound, "not found")
}
if serverData.ID == 0 || (serverData.DeletionOn.Valid && serverData.DeletionOn.Time.Before(time.Now())) {
return c.String(http.StatusNotFound, "not found")
}
if serverData.Ip != serverID {
return c.Redirect(308, fmt.Sprintf("/graph/%s/offset.png", serverData.Ip))
}
contentType, data, err := srv.fetchGraph(ctx, serverData.Ip)
if err != nil {
span.RecordError(err)
return c.String(http.StatusInternalServerError, "server error")
}
if len(data) == 0 {
span.RecordError(fmt.Errorf("no data"))
return c.String(http.StatusInternalServerError, "server error")
}
ttl := 1800
c.Response().Header().Set("Cache-Control",
fmt.Sprintf("public,max-age=%d,s-maxage=%.0f",
ttl, float64(ttl)*0.75,
),
)
return c.Blob(http.StatusOK, contentType, data)
}
func (srv *Server) fetchGraph(ctx context.Context, serverIP string) (string, []byte, error) {
log := logger.Setup()
ctx, span := tracing.Tracer().Start(ctx, "fetchGraph")
defer span.End()
// q := url.Values{}
// q.Set("graph_only", "1")
// pagePath := srv.config.WebURL("/scores/" + serverIP, q)
serviceHost := os.Getenv("screensnap_service")
if len(serviceHost) == 0 {
serviceHost = "screensnap"
}
reqURL := url.URL{
Scheme: "http",
Host: serviceHost,
Path: fmt.Sprintf("/image/offset/%s", serverIP),
}
client := retryablehttp.NewClient()
client.Logger = log
client.HTTPClient.Transport = otelhttp.NewTransport(
client.HTTPClient.Transport,
otelhttp.WithClientTrace(func(ctx context.Context) *httptrace.ClientTrace {
return otelhttptrace.NewClientTrace(ctx)
}),
)
req, err := retryablehttp.NewRequestWithContext(ctx, "GET", reqURL.String(), nil)
if err != nil {
return "", nil, err
}
resp, err := client.Do(req)
if err != nil {
return "", nil, err
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
span.AddEvent("unexpected status code", trace.WithAttributes(attribute.Int64("http.status", int64(resp.StatusCode))))
return "text/plain", nil, fmt.Errorf("upstream error %d", resp.StatusCode)
}
b, err := io.ReadAll(resp.Body)
if err != nil {
return "", nil, err
}
return resp.Header.Get("Content-Type"), b, nil
}
// # my $data = JSON::encode_json(
// # { url => $url->as_string(),
// # timeout => 10,
// # viewport => "501x233",
// # height => 233,
// # resource_timeout => 5,
// # wait => 0.5,
// # scale_method => "vector",
// # }
// # );

View File

@@ -25,13 +25,16 @@ import (
"go.ntppool.org/common/version"
"go.ntppool.org/common/xff/fastlyxff"
"go.ntppool.org/api/config"
chdb "go.ntppool.org/data-api/chdb"
"go.ntppool.org/data-api/ntpdb"
)
type Server struct {
db *sql.DB
ch *chdb.ClickHouse
db *sql.DB
ch *chdb.ClickHouse
config *config.Config
ctx context.Context
@@ -40,6 +43,8 @@ type Server struct {
}
func NewServer(ctx context.Context, configFile string) (*Server, error) {
log := logger.Setup()
ch, err := chdb.New(ctx, configFile)
if err != nil {
return nil, fmt.Errorf("clickhouse open: %w", err)
@@ -49,10 +54,16 @@ func NewServer(ctx context.Context, configFile string) (*Server, error) {
return nil, fmt.Errorf("mysql open: %w", err)
}
conf := config.New()
if !conf.Valid() {
log.Error("invalid ntppool config")
}
srv := &Server{
ch: ch,
db: db,
ctx: ctx,
config: conf,
metrics: metricsserver.New(),
}
@@ -171,6 +182,8 @@ func (srv *Server) Run() error {
e.GET("/api/usercc", srv.userCountryData)
e.GET("/api/server/dns/answers/:server", srv.dnsAnswers)
e.GET("/graph/:server/:type", srv.graphImage)
// e.GET("/api/server/scores/:server/:type", srv.logScores)
g.Go(func() error {