Extend /stats API to handle two routes with the same path in different apps (#735)

* Extend deprecated /stats API to handle apps and paths correctly

* More changes (bugfixes) to the JSON structure returned by the stats API call
This commit is contained in:
Nigel Deakin
2018-02-05 15:51:53 +00:00
committed by GitHub
parent ac4dfa6077
commit 5089dd6119

View File

@@ -10,62 +10,85 @@ import (
// * hot containers active // * hot containers active
// * memory used / available // * memory used / available
// global statistics // stats is the top-level struct containing composite and individial statistics all routes in all apps
// an instance of this struc is maintained in memory to keep a record of the current stats since the server was started
// access must be synchronized using the Mutex
type stats struct { type stats struct {
mu sync.Mutex mu sync.Mutex
// statistics for all functions combined // composite statistics for all routes in all apps
queue uint64 queue uint64
running uint64 running uint64
complete uint64 complete uint64
failed uint64 failed uint64
// statistics for individual functions, keyed by function path // statistics for individual apps, keyed by appname
functionStatsMap map[string]*functionStats apps map[string]appStats
} }
// statistics for an individual function // appStats holds statistics for the routes in an individual app, keyed by the path of the route
type functionStats struct { // instances of this struc are used to maintain an in-memory record of the current stats
// access must be synchronized using the Mutex on the parent stats
type appStats struct {
routes map[string]*routeStats
}
// routeStats holds statistics for an individual route
// instances of this struc are used to maintain an in-memory record of the current stats
// access must be synchronized using the Mutex on the parent stats
type routeStats struct {
queue uint64 queue uint64
running uint64 running uint64
complete uint64 complete uint64
failed uint64 failed uint64
} }
// Stats hold the statistics for all functions combined // Stats is the top-level struct containing composite and individial statistics all routes in all apps
// and the statistics for each individual function // an instance of this struc is created when converting the current stats to JSON
type Stats struct { type Stats struct {
Queue uint64 Queue uint64
Running uint64 Running uint64
Complete uint64 Complete uint64
Failed uint64 Failed uint64
// statistics for individual functions, keyed by function path // statistics for individual apps, keyed by appname
FunctionStatsMap map[string]*FunctionStats Apps map[string]AppStats
} }
// FunctionStats holds the statistics for an individual function // AppStats holds statistics for the routes in an individual app, keyed by the path of the route
type FunctionStats struct { // instances of this struc are used when converting the current stats to JSON
type AppStats struct {
Routes map[string]*RouteStats
}
// RouteStats holds statistics for an individual route
// instances of this struc are used when converting the current stats to JSON
type RouteStats struct {
Queue uint64 Queue uint64
Running uint64 Running uint64
Complete uint64 Complete uint64
Failed uint64 Failed uint64
} }
func (s *stats) getStatsForFunction(path string) *functionStats { // return the stats corresponding to the specified app name and route path, creating a new stats if one does not already exist
if s.functionStatsMap == nil { func (s *stats) getStatsForRoute(app string, path string) *routeStats {
s.functionStatsMap = make(map[string]*functionStats) if s.apps == nil {
s.apps = make(map[string]appStats)
} }
thisFunctionStats, found := s.functionStatsMap[path] thisAppStats, appFound := s.apps[app]
if !found { if !appFound {
thisFunctionStats = &functionStats{} thisAppStats = appStats{routes: make(map[string]*routeStats)}
s.functionStatsMap[path] = thisFunctionStats s.apps[app] = thisAppStats
} }
thisRouteStats, pathFound := thisAppStats.routes[path]
return thisFunctionStats if !pathFound {
thisRouteStats = &routeStats{}
thisAppStats.routes[path] = thisRouteStats
}
return thisRouteStats
} }
func (s *stats) Enqueue(ctx context.Context, app string, path string) { func (s *stats) Enqueue(ctx context.Context, app string, path string) {
s.mu.Lock() s.mu.Lock()
fstats := s.getStatsForFunction(path) fstats := s.getStatsForRoute(app, path)
s.queue++ s.queue++
fstats.queue++ fstats.queue++
@@ -79,7 +102,7 @@ func (s *stats) Enqueue(ctx context.Context, app string, path string) {
func (s *stats) Dequeue(ctx context.Context, app string, path string) { func (s *stats) Dequeue(ctx context.Context, app string, path string) {
s.mu.Lock() s.mu.Lock()
fstats := s.getStatsForFunction(path) fstats := s.getStatsForRoute(app, path)
s.queue-- s.queue--
fstats.queue-- fstats.queue--
@@ -91,7 +114,7 @@ func (s *stats) Dequeue(ctx context.Context, app string, path string) {
func (s *stats) DequeueAndStart(ctx context.Context, app string, path string) { func (s *stats) DequeueAndStart(ctx context.Context, app string, path string) {
s.mu.Lock() s.mu.Lock()
fstats := s.getStatsForFunction(path) fstats := s.getStatsForRoute(app, path)
s.queue-- s.queue--
s.running++ s.running++
fstats.queue-- fstats.queue--
@@ -106,7 +129,7 @@ func (s *stats) DequeueAndStart(ctx context.Context, app string, path string) {
func (s *stats) Complete(ctx context.Context, app string, path string) { func (s *stats) Complete(ctx context.Context, app string, path string) {
s.mu.Lock() s.mu.Lock()
fstats := s.getStatsForFunction(path) fstats := s.getStatsForRoute(app, path)
s.running-- s.running--
s.complete++ s.complete++
fstats.running-- fstats.running--
@@ -121,7 +144,7 @@ func (s *stats) Complete(ctx context.Context, app string, path string) {
func (s *stats) Failed(ctx context.Context, app string, path string) { func (s *stats) Failed(ctx context.Context, app string, path string) {
s.mu.Lock() s.mu.Lock()
fstats := s.getStatsForFunction(path) fstats := s.getStatsForRoute(app, path)
s.running-- s.running--
s.failed++ s.failed++
fstats.running-- fstats.running--
@@ -136,7 +159,7 @@ func (s *stats) Failed(ctx context.Context, app string, path string) {
func (s *stats) DequeueAndFail(ctx context.Context, app string, path string) { func (s *stats) DequeueAndFail(ctx context.Context, app string, path string) {
s.mu.Lock() s.mu.Lock()
fstats := s.getStatsForFunction(path) fstats := s.getStatsForRoute(app, path)
s.queue-- s.queue--
s.failed++ s.failed++
fstats.queue-- fstats.queue--
@@ -161,16 +184,22 @@ func IncrementTooBusy(ctx context.Context) {
} }
func (s *stats) Stats() Stats { func (s *stats) Stats() Stats {
// this creates a Stats from a stats
// stats is the internal struc which is continuously updated, and access is controlled using its Mutex
// Stats is a deep copy for external use, and can be converted to JSON
var stats Stats var stats Stats
s.mu.Lock() s.mu.Lock()
stats.Running = s.running stats.Running = s.running
stats.Complete = s.complete stats.Complete = s.complete
stats.Queue = s.queue stats.Queue = s.queue
stats.Failed = s.failed stats.Failed = s.failed
stats.FunctionStatsMap = make(map[string]*FunctionStats) stats.Apps = make(map[string]AppStats)
for key, value := range s.functionStatsMap { for appname, thisAppStats := range s.apps {
thisFunctionStats := &FunctionStats{Queue: value.queue, Running: value.running, Complete: value.complete, Failed: value.failed} newAppStats := AppStats{Routes: make(map[string]*RouteStats)}
stats.FunctionStatsMap[key] = thisFunctionStats stats.Apps[appname] = newAppStats
for path, thisRouteStats := range thisAppStats.routes {
newAppStats.Routes[path] = &RouteStats{Queue: thisRouteStats.queue, Running: thisRouteStats.running, Complete: thisRouteStats.complete, Failed: thisRouteStats.failed}
}
} }
s.mu.Unlock() s.mu.Unlock()
return stats return stats