diff --git a/cmd/repo-updater/shared/main.go b/cmd/repo-updater/shared/main.go index 749f27226c8..aaa75985c5d 100644 --- a/cmd/repo-updater/shared/main.go +++ b/cmd/repo-updater/shared/main.go @@ -13,7 +13,6 @@ import ( "golang.org/x/time/rate" - "github.com/golang/gddo/httputil" "github.com/graph-gophers/graphql-go/relay" "github.com/opentracing/opentracing-go" "github.com/prometheus/client_golang/prometheus" @@ -59,7 +58,7 @@ var stateHTMLTemplate string // EnterpriseInit is a function that allows enterprise code to be triggered when dependencies // created in Main are ready for use. -type EnterpriseInit func(logger log.Logger, db database.DB, store repos.Store, keyring keyring.Ring, cf *httpcli.Factory, server *repoupdater.Server) []debugserver.Dumper +type EnterpriseInit func(logger log.Logger, db database.DB, store repos.Store, keyring keyring.Ring, cf *httpcli.Factory, server *repoupdater.Server) map[string]debugserver.Dumper type LazyDebugserverEndpoint struct { repoUpdaterStateEndpoint http.HandlerFunc @@ -159,7 +158,7 @@ func Main(enterpriseInit EnterpriseInit) { } // All dependencies ready - var debugDumpers []debugserver.Dumper + debugDumpers := make(map[string]debugserver.Dumper) if enterpriseInit != nil { debugDumpers = enterpriseInit(logger, db, store, keyring.Default(), cf, server) } @@ -230,7 +229,8 @@ func Main(enterpriseInit EnterpriseInit) { globals.WatchExternalURL(nil) - debugserverEndpoints.repoUpdaterStateEndpoint = repoUpdaterStatsHandler(db, updateScheduler, debugDumpers) + debugDumpers["repos"] = updateScheduler + debugserverEndpoints.repoUpdaterStateEndpoint = repoUpdaterStatsHandler(db, debugDumpers) debugserverEndpoints.listAuthzProvidersEndpoint = listAuthzProvidersHandler() debugserverEndpoints.gitserverReposStatusEndpoint = gitserverReposStatusHandler(db) debugserverEndpoints.rateLimiterStateEndpoint = rateLimiterStateHandler @@ -413,44 +413,20 @@ func listAuthzProvidersHandler() http.HandlerFunc { } } -func repoUpdaterStatsHandler(db database.DB, scheduler *repos.UpdateScheduler, debugDumpers []debugserver.Dumper) http.HandlerFunc { +func repoUpdaterStatsHandler(db database.DB, debugDumpers map[string]debugserver.Dumper) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - dumps := []any{ - scheduler.DebugDump(r.Context(), db), - } - for _, dumper := range debugDumpers { - dumps = append(dumps, dumper.DebugDump()) - } + wantDumper := r.URL.Query().Get("dumper") + wantFormat := r.URL.Query().Get("format") - const ( - textPlain = "text/plain" - applicationJson = "application/json" - ) - - // Negotiate the content type. - contentTypeOffers := []string{textPlain, applicationJson} - defaultOffer := textPlain - contentType := httputil.NegotiateContentType(r, contentTypeOffers, defaultOffer) - - // Allow users to override the negotiated content type so that e.g. browser - // users can easily request json by adding ?format=json to - // the URL. - switch r.URL.Query().Get("format") { - case "json": - contentType = applicationJson - } - - switch contentType { - case applicationJson: - p, err := json.MarshalIndent(dumps, "", " ") - if err != nil { - http.Error(w, "failed to marshal snapshot: "+err.Error(), http.StatusInternalServerError) + // Showing the HTML version of repository syncing schedule as the default, + // also the only dumper that supports rendering the HTML version. + if (wantDumper == "" || wantDumper == "repos") && wantFormat != "json" { + reposDumper, ok := debugDumpers["repos"].(*repos.UpdateScheduler) + if !ok { + http.Error(w, "No debug dumper for repos found", http.StatusInternalServerError) return } - w.Header().Set("Content-Type", "application/json") - _, _ = w.Write(p) - default: // This case also applies for defaultOffer. Note that this is preferred // over e.g. a 406 status code, according to the MDN: // https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/406 @@ -460,12 +436,29 @@ func repoUpdaterStatsHandler(db database.DB, scheduler *repos.UpdateScheduler, d }, }) template.Must(tmpl.Parse(stateHTMLTemplate)) - err := tmpl.Execute(w, dumps) + err := tmpl.Execute(w, reposDumper.DebugDump(r.Context(), db.ExternalServices())) if err != nil { - http.Error(w, "failed to render template: "+err.Error(), http.StatusInternalServerError) + http.Error(w, "Failed to render template: "+err.Error(), http.StatusInternalServerError) return } + return } + + var dumps []any + for name, dumper := range debugDumpers { + if wantDumper != "" && wantDumper != name { + continue + } + dumps = append(dumps, dumper.DebugDump(r.Context(), db.ExternalServices())) + } + + p, err := json.MarshalIndent(dumps, "", " ") + if err != nil { + http.Error(w, "Failed to marshal dumps: "+err.Error(), http.StatusInternalServerError) + return + } + w.Header().Set("Content-Type", "application/json") + _, _ = w.Write(p) } } diff --git a/cmd/repo-updater/shared/state.html.tmpl b/cmd/repo-updater/shared/state.html.tmpl index f36ed77ae2d..4dd739b9983 100644 --- a/cmd/repo-updater/shared/state.html.tmpl +++ b/cmd/repo-updater/shared/state.html.tmpl @@ -14,13 +14,11 @@
- {{$schedulerDump := index . 0}} - {{if gt (len .) 1}} -
- To view additional debug dumps, please request a JSON response - -
- {{end}} + {{$schedulerDump := .}} +
+ To view additional debug dumps, please request a JSON response. + +
  • Schedule
  • diff --git a/enterprise/cmd/repo-updater/internal/authz/perms_syncer.go b/enterprise/cmd/repo-updater/internal/authz/perms_syncer.go index 7760fb5c4b6..ba2415c4e8e 100644 --- a/enterprise/cmd/repo-updater/internal/authz/perms_syncer.go +++ b/enterprise/cmd/repo-updater/internal/authz/perms_syncer.go @@ -24,6 +24,7 @@ import ( "github.com/sourcegraph/sourcegraph/internal/authz" "github.com/sourcegraph/sourcegraph/internal/conf" "github.com/sourcegraph/sourcegraph/internal/database" + "github.com/sourcegraph/sourcegraph/internal/debugserver" "github.com/sourcegraph/sourcegraph/internal/errcode" "github.com/sourcegraph/sourcegraph/internal/extsvc" "github.com/sourcegraph/sourcegraph/internal/extsvc/github" @@ -1323,7 +1324,7 @@ func (s *PermsSyncer) runSchedule(ctx context.Context) { } // DebugDump returns the state of the permissions syncer for debugging. -func (s *PermsSyncer) DebugDump() any { +func (s *PermsSyncer) DebugDump(_ context.Context, _ debugserver.ExternalServicesStore) any { type requestInfo struct { Meta *requestMeta Acquired bool diff --git a/enterprise/cmd/repo-updater/main.go b/enterprise/cmd/repo-updater/main.go index f35fddf2f66..be0e1a9f0d9 100644 --- a/enterprise/cmd/repo-updater/main.go +++ b/enterprise/cmd/repo-updater/main.go @@ -39,7 +39,7 @@ func enterpriseInit( keyring keyring.Ring, cf *httpcli.Factory, server *repoupdater.Server, -) (debugDumpers []debugserver.Dumper) { +) (debugDumpers map[string]debugserver.Dumper) { debug, _ := strconv.ParseBool(os.Getenv("DEBUG")) if debug { logger.Info("enterprise edition") @@ -60,12 +60,13 @@ func enterpriseInit( permsStore := edb.Perms(logger, db, timeutil.Now) permsSyncer := authz.NewPermsSyncer(logger.Scoped("PermsSyncer", "repository and user permissions syncer"), db, repoStore, permsStore, timeutil.Now, ratelimit.DefaultRegistry) go startBackgroundPermsSync(ctx, permsSyncer, db) - debugDumpers = append(debugDumpers, permsSyncer) if server != nil { server.PermsSyncer = permsSyncer } - return debugDumpers + return map[string]debugserver.Dumper{ + "repoPerms": permsSyncer, + } } // startBackgroundPermsSync sets up background permissions syncing. diff --git a/internal/debugserver/debug.go b/internal/debugserver/debug.go index 431620289ed..86240c0013f 100644 --- a/internal/debugserver/debug.go +++ b/internal/debugserver/debug.go @@ -1,6 +1,7 @@ package debugserver import ( + "context" "encoding/json" "fmt" "net/http" @@ -17,6 +18,7 @@ import ( "github.com/sourcegraph/sourcegraph/internal/env" "github.com/sourcegraph/sourcegraph/internal/goroutine" "github.com/sourcegraph/sourcegraph/internal/httpserver" + "github.com/sourcegraph/sourcegraph/internal/types" ) var addr = env.Get("SRC_PROF_HTTP", ":6060", "net/http/pprof http bind address.") @@ -70,10 +72,14 @@ type Service struct { DefaultPath string } +type ExternalServicesStore interface { + GetSyncJobs(ctx context.Context) ([]*types.ExternalServiceSyncJob, error) +} + // Dumper is a service which can dump its state for debugging. type Dumper interface { // DebugDump returns a snapshot of the current state. - DebugDump() any + DebugDump(ctx context.Context, esStore ExternalServicesStore) any } // NewServerRoutine returns a background routine that exposes pprof and metrics endpoints. diff --git a/internal/repos/scheduler.go b/internal/repos/scheduler.go index 42af498975f..6ff0119d1d6 100644 --- a/internal/repos/scheduler.go +++ b/internal/repos/scheduler.go @@ -15,6 +15,7 @@ import ( "github.com/sourcegraph/sourcegraph/internal/api" "github.com/sourcegraph/sourcegraph/internal/conf" "github.com/sourcegraph/sourcegraph/internal/database" + "github.com/sourcegraph/sourcegraph/internal/debugserver" "github.com/sourcegraph/sourcegraph/internal/gitserver" gitserverprotocol "github.com/sourcegraph/sourcegraph/internal/gitserver/protocol" "github.com/sourcegraph/sourcegraph/internal/mutablelimiter" @@ -397,7 +398,7 @@ func (s *UpdateScheduler) UpdateOnce(id api.RepoID, name api.RepoName) { } // DebugDump returns the state of the update scheduler for debugging. -func (s *UpdateScheduler) DebugDump(ctx context.Context, db database.DB) any { +func (s *UpdateScheduler) DebugDump(ctx context.Context, esStore debugserver.ExternalServicesStore) any { data := struct { Name string UpdateQueue []*repoUpdate @@ -446,7 +447,7 @@ func (s *UpdateScheduler) DebugDump(ctx context.Context, db database.DB) any { } var err error - data.SyncJobs, err = db.ExternalServices().GetSyncJobs(ctx) + data.SyncJobs, err = esStore.GetSyncJobs(ctx) if err != nil { s.logger.Warn("getting external service sync jobs for debug page", log.Error(err)) }