From a1b01e6d5f66ddb6edc8d80d4463359fbf0ef0e7 Mon Sep 17 00:00:00 2001 From: Poorna Date: Fri, 8 Apr 2022 12:44:35 -0700 Subject: [PATCH] Combine profiling start/stop APIs into one (#14662) Take profile duration as a query parameter for profile API --- cmd/admin-handlers.go | 84 +++++++++++++++++++++++++++++++++++++++++-- cmd/admin-router.go | 4 ++- 2 files changed, 85 insertions(+), 3 deletions(-) diff --git a/cmd/admin-handlers.go b/cmd/admin-handlers.go index 91d1c145d..f9c17b7ce 100644 --- a/cmd/admin-handlers.go +++ b/cmd/admin-handlers.go @@ -27,6 +27,7 @@ import ( "fmt" "hash/crc32" "io" + "io/ioutil" "math/rand" "net/http" "net/url" @@ -500,7 +501,7 @@ func (a adminAPIHandlers) TopLocksHandler(w http.ResponseWriter, r *http.Request } // StartProfilingResult contains the status of the starting -// profiling action in a given server +// profiling action in a given server - deprecated API type StartProfilingResult struct { NodeName string `json:"nodeName"` Success bool `json:"success"` @@ -594,6 +595,85 @@ func (a adminAPIHandlers) StartProfilingHandler(w http.ResponseWriter, r *http.R writeSuccessResponseJSON(w, startProfilingResultInBytes) } +// ProfileHandler - POST /minio/admin/v3/profile/?profilerType={profilerType} +// ---------- +// Enable server profiling +func (a adminAPIHandlers) ProfileHandler(w http.ResponseWriter, r *http.Request) { + ctx := newContext(r, w, "Profile") + + defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r)) + + // Validate request signature. + _, adminAPIErr := checkAdminRequestAuth(ctx, r, iampolicy.ProfilingAdminAction, "") + if adminAPIErr != ErrNone { + writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(adminAPIErr), r.URL) + return + } + + if globalNotificationSys == nil { + writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL) + return + } + profileStr := r.Form.Get("profilerType") + profiles := strings.Split(profileStr, ",") + duration := time.Minute + if dstr := r.Form.Get("duration"); dstr != "" { + var err error + duration, err = time.ParseDuration(dstr) + if err != nil { + writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL) + return + } + } + // read request body + io.CopyN(ioutil.Discard, r.Body, 1) + + globalProfilerMu.Lock() + + if globalProfiler == nil { + globalProfiler = make(map[string]minioProfiler, 10) + } + + // Stop profiler of all types if already running + for k, v := range globalProfiler { + v.Stop() + delete(globalProfiler, k) + } + + // Start profiling on remote servers. + for _, profiler := range profiles { + globalNotificationSys.StartProfiling(profiler) + + // Start profiling locally as well. + prof, err := startProfiler(profiler) + if err == nil { + globalProfiler[profiler] = prof + } + } + globalProfilerMu.Unlock() + + timer := time.NewTimer(duration) + for { + select { + case <-ctx.Done(): + if !timer.Stop() { + <-timer.C + } + for k, v := range globalProfiler { + v.Stop() + delete(globalProfiler, k) + } + return + case <-timer.C: + if !globalNotificationSys.DownloadProfilingData(ctx, w) { + writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminProfilerNotEnabled), r.URL) + return + } + return + } + } +} + // dummyFileInfo represents a dummy representation of a profile data file // present only in memory, it helps to generate the zip stream. type dummyFileInfo struct { @@ -614,7 +694,7 @@ func (f dummyFileInfo) Sys() interface{} { return f.sys } // DownloadProfilingHandler - POST /minio/admin/v3/profiling/download // ---------- -// Download profiling information of all nodes in a zip format +// Download profiling information of all nodes in a zip format - deprecated API func (a adminAPIHandlers) DownloadProfilingHandler(w http.ResponseWriter, r *http.Request) { ctx := newContext(r, w, "DownloadProfiling") diff --git a/cmd/admin-router.go b/cmd/admin-router.go index f4b49a70f..f30414e67 100644 --- a/cmd/admin-router.go +++ b/cmd/admin-router.go @@ -84,10 +84,12 @@ func registerAdminRouter(router *mux.Router, enableConfigOps bool) { adminRouter.Methods(http.MethodPost).Path(adminVersion+"/pools/cancel").HandlerFunc(gz(httpTraceAll(adminAPI.CancelDecommission))).Queries("pool", "{pool:.*}") } - // Profiling operations + // Profiling operations - deprecated API adminRouter.Methods(http.MethodPost).Path(adminVersion+"/profiling/start").HandlerFunc(gz(httpTraceAll(adminAPI.StartProfilingHandler))). Queries("profilerType", "{profilerType:.*}") adminRouter.Methods(http.MethodGet).Path(adminVersion + "/profiling/download").HandlerFunc(gz(httpTraceAll(adminAPI.DownloadProfilingHandler))) + // Profiling operations + adminRouter.Methods(http.MethodPost).Path(adminVersion + "/profile").HandlerFunc(gz(httpTraceAll(adminAPI.ProfileHandler))) // Config KV operations. if enableConfigOps {