Configurable Repository Timeout (#25)

* feat: implement correct prometheus metrics

* feat: remove nodeID from metrics due to high cardinality

* feat: add configurable timeouts to contentserver
This commit is contained in:
Stefan Martinov 2021-09-22 11:52:53 +02:00 committed by GitHub
parent 93fcca1a5f
commit ac85c31b77
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 35 additions and 20 deletions

View File

@ -63,6 +63,7 @@ func initTestServer(t testing.TB) (socketAddr, webserverAddr string) {
webserverAddr, webserverAddr,
pathContentserver, pathContentserver,
varDir, varDir,
server.DefaultRepositoryTimeout,
) )
if err != nil { if err != nil {
t.Fatal("test server crashed: ", err) t.Fatal("test server crashed: ", err)

View File

@ -25,14 +25,16 @@ const (
ServiceName = "Content Server" ServiceName = "Content Server"
DefaultHealthzHandlerAddress = ":8080" DefaultHealthzHandlerAddress = ":8080"
DefaultPrometheusListener = "127.0.0.1:9200" DefaultPrometheusListener = ":9200"
) )
var ( var (
flagAddress = flag.String("address", "", "address to bind socket server host:port") flagAddress = flag.String("address", "", "address to bind socket server host:port")
flagWebserverAddress = flag.String("webserver-address", "", "address to bind web server host:port, when empty no webserver will be spawned") flagWebserverAddress = flag.String("webserver-address", "", "address to bind web server host:port, when empty no webserver will be spawned")
flagWebserverPath = flag.String("webserver-path", "/contentserver", "path to export the webserver on - useful when behind a proxy") flagWebserverPath = flag.String("webserver-path", "/contentserver", "path to export the webserver on - useful when behind a proxy")
flagVarDir = flag.String("var-dir", "/var/lib/contentserver", "where to put my data") flagVarDir = flag.String("var-dir", "/var/lib/contentserver", "where to put my data")
flagPrometheusListener = flag.String("prometheus-listener", getenv("PROMETHEUS_LISTENER", DefaultPrometheusListener), "address for the prometheus listener")
flagRepositoryTimeoutDuration = flag.Duration("repository-timeout-duration", server.DefaultRepositoryTimeout, "timeout duration for the contentserver")
// debugging / profiling // debugging / profiling
flagDebug = flag.Bool("debug", false, "toggle debug mode") flagDebug = flag.Bool("debug", false, "toggle debug mode")
@ -40,6 +42,13 @@ var (
flagHeapDump = flag.Int("heap-dump", 0, "dump heap every X minutes") flagHeapDump = flag.Int("heap-dump", 0, "dump heap every X minutes")
) )
func getenv(env, fallback string) string {
if value, ok := os.LookupEnv(env); ok {
return value
}
return fallback
}
func exitUsage(code int) { func exitUsage(code int) {
fmt.Println("Usage:", os.Args[0], "http(s)://your-content-server/path/to/content.json") fmt.Println("Usage:", os.Args[0], "http(s)://your-content-server/path/to/content.json")
flag.PrintDefaults() flag.PrintDefaults()
@ -89,10 +98,10 @@ func main() {
fmt.Println(*flagAddress, flag.Arg(0)) fmt.Println(*flagAddress, flag.Arg(0))
// kickoff metric handlers // kickoff metric handlers
go metrics.RunPrometheusHandler(DefaultPrometheusListener) go metrics.RunPrometheusHandler(*flagPrometheusListener)
go status.RunHealthzHandlerListener(DefaultHealthzHandlerAddress, ServiceName) go status.RunHealthzHandlerListener(DefaultHealthzHandlerAddress, ServiceName)
err := server.RunServerSocketAndWebServer(flag.Arg(0), *flagAddress, *flagWebserverAddress, *flagWebserverPath, *flagVarDir) err := server.RunServerSocketAndWebServer(flag.Arg(0), *flagAddress, *flagWebserverAddress, *flagWebserverPath, *flagVarDir, *flagRepositoryTimeoutDuration)
if err != nil { if err != nil {
fmt.Println("exiting with error", err) fmt.Println("exiting with error", err)
os.Exit(1) os.Exit(1)

View File

@ -54,7 +54,7 @@ type repoDimension struct {
} }
// NewRepo constructor // NewRepo constructor
func NewRepo(server string, varDir string) *Repo { func NewRepo(server string, varDir string, repositoryTimeout time.Duration) *Repo {
logger.Log.Info("creating new repo", logger.Log.Info("creating new repo",
zap.String("server", server), zap.String("server", server),
@ -67,7 +67,7 @@ func NewRepo(server string, varDir string) *Repo {
history: newHistory(varDir), history: newHistory(varDir),
dimensionUpdateChannel: make(chan *repoDimension), dimensionUpdateChannel: make(chan *repoDimension),
dimensionUpdateDoneChannel: make(chan error), dimensionUpdateDoneChannel: make(chan error),
httpClient: getDefaultHTTPClient(2 * time.Minute), httpClient: getDefaultHTTPClient(repositoryTimeout),
updateInProgressChannel: make(chan chan updateResponse, 0), updateInProgressChannel: make(chan chan updateResponse, 0),
} }
@ -160,7 +160,7 @@ func (repo *Repo) getNodes(nodeRequests map[string]*requests.Node, env *requests
zap.String("nodeName", nodeName), zap.String("nodeName", nodeName),
zap.String("nodeID", nodeRequest.ID), zap.String("nodeID", nodeRequest.ID),
) )
status.M.InvalidNodeTreeRequests.WithLabelValues(nodeRequest.ID).Inc() status.M.InvalidNodeTreeRequests.WithLabelValues().Inc()
continue continue
} }
nodes[nodeName] = repo.getNode(treeNode, nodeRequest.Expand, nodeRequest.MimeTypes, path, 0, groups, nodeRequest.DataFields, nodeRequest.ExposeHiddenNodes) nodes[nodeName] = repo.getNode(treeNode, nodeRequest.Expand, nodeRequest.MimeTypes, path, 0, groups, nodeRequest.DataFields, nodeRequest.ExposeHiddenNodes)
@ -249,12 +249,12 @@ func (repo *Repo) WriteRepoBytes(w io.Writer) {
logger.Log.Error("Failed to serve Repo JSON", zap.Error(err)) logger.Log.Error("Failed to serve Repo JSON", zap.Error(err))
} }
w.Write([]byte("{\"reply\":")) _, _ = w.Write([]byte("{\"reply\":"))
_, err = io.Copy(w, f) _, err = io.Copy(w, f)
if err != nil { if err != nil {
logger.Log.Error("Failed to serve Repo JSON", zap.Error(err)) logger.Log.Error("Failed to serve Repo JSON", zap.Error(err))
} }
w.Write([]byte("}")) _, _ = w.Write([]byte("}"))
} }
// Update - reload contents of repository with json from repo.server // Update - reload contents of repository with json from repo.server

View File

@ -18,7 +18,7 @@ func init() {
func NewTestRepo(server, varDir string) *Repo { func NewTestRepo(server, varDir string) *Repo {
r := NewRepo(server, varDir) r := NewRepo(server, varDir, 2*time.Minute)
// because the travis CI VMs are very slow, // because the travis CI VMs are very slow,
// we need to add some delay to allow the server to startup // we need to add some delay to allow the server to startup

View File

@ -3,13 +3,15 @@ package server
import ( import (
"errors" "errors"
"fmt" "fmt"
"net"
"net/http"
"os"
"time"
. "github.com/foomo/contentserver/logger" . "github.com/foomo/contentserver/logger"
"github.com/foomo/contentserver/repo" "github.com/foomo/contentserver/repo"
jsoniter "github.com/json-iterator/go" jsoniter "github.com/json-iterator/go"
"go.uber.org/zap" "go.uber.org/zap"
"net"
"net/http"
"os"
) )
var ( var (
@ -30,11 +32,14 @@ const (
HandlerUpdate = "update" HandlerUpdate = "update"
// HandlerGetRepo get the whole repo // HandlerGetRepo get the whole repo
HandlerGetRepo = "getRepo" HandlerGetRepo = "getRepo"
// DefaultRepositoryTimeout for the HTTP client towards the repo
DefaultRepositoryTimeout = 2 * time.Minute
) )
// Run - let it run and enjoy on a socket near you // Run - let it run and enjoy on a socket near you
func Run(server string, address string, varDir string) error { func Run(server string, address string, varDir string) error {
return RunServerSocketAndWebServer(server, address, "", "", varDir) return RunServerSocketAndWebServer(server, address, "", "", varDir, DefaultRepositoryTimeout)
} }
func RunServerSocketAndWebServer( func RunServerSocketAndWebServer(
@ -43,13 +48,14 @@ func RunServerSocketAndWebServer(
webserverAddress string, webserverAddress string,
webserverPath string, webserverPath string,
varDir string, varDir string,
repositoryTimeout time.Duration,
) error { ) error {
if address == "" && webserverAddress == "" { if address == "" && webserverAddress == "" {
return errors.New("one of the addresses needs to be set") return errors.New("one of the addresses needs to be set")
} }
Log.Info("building repo with content", zap.String("server", server)) Log.Info("building repo with content", zap.String("server", server))
r := repo.NewRepo(server, varDir) r := repo.NewRepo(server, varDir, repositoryTimeout)
// start initial update and handle error // start initial update and handle error
go func() { go func() {

View File

@ -38,8 +38,7 @@ type Metrics struct {
func newMetrics() *Metrics { func newMetrics() *Metrics {
return &Metrics{ return &Metrics{
InvalidNodeTreeRequests: newCounterVec("invalid_node_tree_request_count", InvalidNodeTreeRequests: newCounterVec("invalid_node_tree_request_count",
"Counts the number of invalid tree nodes for a specific node ID", "Counts the number of invalid tree nodes for a specific node"),
"nodeID"),
ServiceRequestCounter: newCounterVec( ServiceRequestCounter: newCounterVec(
"service_request_count", "service_request_count",
"Count of requests for each handler", "Count of requests for each handler",