Skip to content

Add PProf to admin pages and to gitea manager #22742

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 34 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
5093d7d
Add PProf to admin pages
zeripath Feb 3, 2023
9cfcbb4
Update pprof.tmpl
zeripath Feb 4, 2023
f22aa21
Update locale_en-US.ini
zeripath Feb 4, 2023
b9989b1
Add FGProf handler
zeripath Feb 4, 2023
6451d01
wire in PProfFGProfile
zeripath Feb 4, 2023
351149b
Wire in FGProf
zeripath Feb 4, 2023
7941fbb
Update locale_en-US.ini
zeripath Feb 4, 2023
0ef07f6
Update pprof.tmpl
zeripath Feb 4, 2023
8d0d93e
Update pprof.go
zeripath Feb 4, 2023
768a960
Update pprof.go
zeripath Feb 4, 2023
82169ca
Update web.go
zeripath Feb 4, 2023
ba894a1
Update pprof.go
zeripath Feb 4, 2023
75d96e8
Update pprof.tmpl
zeripath Feb 4, 2023
381f12a
Update pprof.tmpl
zeripath Feb 4, 2023
2fc5d72
Update locale_en-US.ini
zeripath Feb 4, 2023
79756cd
placate lint
zeripath Feb 4, 2023
6247e72
Add pprof endpoints to manager too
zeripath Feb 4, 2023
d9eac18
add documentation
zeripath Feb 4, 2023
7decd93
Add output option
zeripath Feb 4, 2023
4d916b4
fix missing name
zeripath Feb 4, 2023
b9942cb
add trace and fix format
zeripath Feb 4, 2023
7c4be9c
fix format on stacktraces
zeripath Feb 4, 2023
6c2017d
fix trace
zeripath Feb 4, 2023
d37978e
Apply suggestions from code review
zeripath Feb 5, 2023
950c474
Merge remote-tracking branch 'origin/main' into add-pprof-to-admin-pages
zeripath Feb 5, 2023
2dbab58
as per delvh
zeripath Feb 5, 2023
2b523f1
include indent in WriteProcess
zeripath Feb 5, 2023
5925f47
as per delvh
zeripath Feb 5, 2023
91bc97a
remove space
zeripath Feb 5, 2023
61755a7
as per delvh
zeripath Feb 5, 2023
1400ab2
as per delvh
zeripath Feb 5, 2023
edebd86
add some comments
zeripath Feb 5, 2023
1ffe696
Merge remote-tracking branch 'origin/main' into add-pprof-to-admin-pages
zeripath Feb 19, 2023
92ac295
move trace help in to the ui form
zeripath Feb 19, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
185 changes: 175 additions & 10 deletions cmd/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
package cmd

import (
"context"
"fmt"
"io"
"net/http"
"os"
"time"
Expand All @@ -26,6 +28,11 @@ var (
subcmdFlushQueues,
subcmdLogging,
subCmdProcesses,
subCmdCPUProfile,
subCmdFGProfile,
subCmdListNamedProfiles,
subCmdNamedProfile,
subCmdTrace,
},
}
subcmdShutdown = cli.Command{
Expand Down Expand Up @@ -95,15 +102,117 @@ var (
Name: "cancel",
Usage: "Process PID to cancel. (Only available for non-system processes.)",
},
cli.StringFlag{
Name: "output,o",
Usage: "File to output to (set to \"-\" for stdout)",
Value: "-",
},
},
}
subCmdCPUProfile = cli.Command{
Name: "cpu-profile",
Usage: "Return PProf CPU profile",
Action: runCPUProfile,
Flags: []cli.Flag{
cli.DurationFlag{
Name: "duration",
Usage: "Duration to collect CPU Profile over",
Value: 30 * time.Second,
},
cli.StringFlag{
Name: "output,o",
Usage: "File to output to (set to \"-\" for stdout)",
Value: "cpu-profile",
},
},
}
subCmdFGProfile = cli.Command{
Name: "fg-profile",
Usage: "Return PProf Full Go profile",
Action: runFGProfile,
Flags: []cli.Flag{
cli.DurationFlag{
Name: "duration",
Usage: "Duration to collect CPU Profile over",
Value: 30 * time.Second,
},
cli.StringFlag{
Name: "format",
Usage: "Format to return the profile in: pprof, folded",
Value: "pprof",
},
cli.StringFlag{
Name: "output,o",
Usage: "File to output to (set to \"-\" for stdout)",
Value: "fg-profile",
},
},
}
subCmdNamedProfile = cli.Command{
Name: "named-profile",
Usage: "Return PProf named profile",
Action: runNamedProfile,
Flags: []cli.Flag{
cli.StringFlag{
Name: "name",
Usage: "Name of profile to run",
},
cli.IntFlag{
Name: "debug-level",
Usage: "Debug level for the profile",
},
cli.StringFlag{
Name: "output,o",
Usage: "File to output to (set to \"-\" for stdout)",
},
},
}
subCmdListNamedProfiles = cli.Command{
Name: "list-named-profiles",
Usage: "Return PProf list of named profiles",
Action: runListNamedProfile,
Flags: []cli.Flag{
cli.BoolFlag{
Name: "json",
Usage: "Output as json",
},
cli.StringFlag{
Name: "output,o",
Usage: "File to output to (set to \"-\" for stdout)",
Value: "-",
},
},
}
subCmdTrace = cli.Command{
Name: "trace",
Usage: "Return PProf trace",
Action: runTrace,
Flags: []cli.Flag{
cli.DurationFlag{
Name: "duration",
Usage: "Duration to collect CPU Profile over",
Value: 30 * time.Second,
},
cli.StringFlag{
Name: "output,o",
Usage: "File to output to (set to \"-\" for stdout)",
Value: "trace",
},
},
}
)

func runShutdown(c *cli.Context) error {
func setupManager(c *cli.Context) (context.Context, context.CancelFunc) {
ctx, cancel := installSignals()
defer cancel()

setup("manager", c.Bool("debug"))
return ctx, cancel
}

func runShutdown(c *cli.Context) error {
ctx, cancel := setupManager(c)
defer cancel()

statusCode, msg := private.Shutdown(ctx)
switch statusCode {
case http.StatusInternalServerError:
Expand All @@ -115,10 +224,9 @@ func runShutdown(c *cli.Context) error {
}

func runRestart(c *cli.Context) error {
ctx, cancel := installSignals()
ctx, cancel := setupManager(c)
defer cancel()

setup("manager", c.Bool("debug"))
statusCode, msg := private.Restart(ctx)
switch statusCode {
case http.StatusInternalServerError:
Expand All @@ -130,10 +238,9 @@ func runRestart(c *cli.Context) error {
}

func runFlushQueues(c *cli.Context) error {
ctx, cancel := installSignals()
ctx, cancel := setupManager(c)
defer cancel()

setup("manager", c.Bool("debug"))
statusCode, msg := private.FlushQueues(ctx, c.Duration("timeout"), c.Bool("non-blocking"))
switch statusCode {
case http.StatusInternalServerError:
Expand All @@ -144,16 +251,74 @@ func runFlushQueues(c *cli.Context) error {
return nil
}

func runProcesses(c *cli.Context) error {
ctx, cancel := installSignals()
func determineOutput(c *cli.Context, defaultFilename string) (io.WriteCloser, error) {
out := os.Stdout
filename := c.String("output")
if filename == "" {
filename = defaultFilename
}
if filename != "-" {
var err error
out, err = os.OpenFile(filename, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0o644)
if err != nil {
return nil, fail("Unable to open "+filename, err.Error())
}
fmt.Printf("Writing to %s\n", filename)
}
return out, nil
}

func wrapManagerPrivateFunc(c *cli.Context, defaultOutput string, fn func(ctx context.Context, out io.Writer) (int, string)) error {
ctx, cancel := setupManager(c)
defer cancel()

setup("manager", c.Bool("debug"))
statusCode, msg := private.Processes(ctx, os.Stdout, c.Bool("flat"), c.Bool("no-system"), c.Bool("stacktraces"), c.Bool("json"), c.String("cancel"))
out, err := determineOutput(c, defaultOutput)
if err != nil {
return err
}
defer out.Close()

statusCode, msg := fn(ctx, out)
switch statusCode {
case http.StatusInternalServerError:
return fail("InternalServerError", msg)
}

return nil
}

func runProcesses(c *cli.Context) error {
return wrapManagerPrivateFunc(c, "-", func(ctx context.Context, out io.Writer) (int, string) {
return private.Processes(ctx, out, c.Bool("flat"), c.Bool("no-system"), c.Bool("stacktraces"), c.Bool("json"), c.String("cancel"))
})
}

func runCPUProfile(c *cli.Context) error {
return wrapManagerPrivateFunc(c, "cpu-profile", func(ctx context.Context, out io.Writer) (int, string) {
return private.CPUProfile(ctx, out, c.Duration("duration"))
})
}

func runFGProfile(c *cli.Context) error {
return wrapManagerPrivateFunc(c, "fg-profile", func(ctx context.Context, out io.Writer) (int, string) {
return private.FGProfile(ctx, out, c.Duration("duration"), c.String("format"))
})
}

func runNamedProfile(c *cli.Context) error {
return wrapManagerPrivateFunc(c, c.String("name")+"-profile", func(ctx context.Context, out io.Writer) (int, string) {
return private.NamedProfile(ctx, out, c.String("name"), c.Int("debug-level"))
})
}

func runListNamedProfile(c *cli.Context) error {
return wrapManagerPrivateFunc(c, "-", func(ctx context.Context, out io.Writer) (int, string) {
return private.ListNamedProfiles(ctx, out, c.Bool("json"))
})
}

func runTrace(c *cli.Context) error {
return wrapManagerPrivateFunc(c, "trace", func(ctx context.Context, out io.Writer) (int, string) {
return private.Trace(ctx, out, c.Duration("duration"))
})
}
23 changes: 23 additions & 0 deletions docs/content/doc/usage/command-line.en-us.md
Original file line number Diff line number Diff line change
Expand Up @@ -526,6 +526,29 @@ Manage running server operations:
- `--stacktraces`: Show stacktraces for goroutines associated with processes
- `--json`: Output as json
- `--cancel PID`: Send cancel to process with PID. (Only for non-system processes.)
- `--output filename`, `-o filename`: Filename to output to. (Set to `-` to use stdout.)
- `cpu-profile`: Return the PProf CPU profile
- Options:
- `--duration`: Duration of time to run profile (default: 30s)
- `--output filename`, `-o filename`: Filename to output to. (Set to `-` to use stdout.)
- `fg-profile`: Returns the PProf Full Go profile
- Options:
- `--duration`: Duration of time to run profile (default: 30s)
- `--format`: Format of profile (default: pprof)
- `--output filename`, `-o filename`: Filename to output to. (Set to `-` to use stdout.)
- `list-named-profiles`: Returns a list of named profiles
- Options:
- `--json`: Set to true to return a json output
- `--output filename`, `-o filename`: Filename to output to. (Set to `-` to use stdout.)
- `named-profile`: Returns the output of a named profile
- Options:
- `--name`: Name of the profile
- `--debug-level`: Debug level for the profile
- `--output filename`, `-o filename`: Filename to output to. (Set to `-` to use stdout.)
- `trace`: Return the PProf trace
- Options:
- `--duration`: Duration of time to run profile (default: 30s)
- `--output filename`, `-o filename`: Filename to output to. (Set to `-` to use stdout.)

### dump-repo

Expand Down
2 changes: 1 addition & 1 deletion modules/context/private.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ var privateContextKey interface{} = "default_private_context"

// WithPrivateContext set up private context in request
func WithPrivateContext(req *http.Request, ctx *PrivateContext) *http.Request {
return req.WithContext(context.WithValue(req.Context(), privateContextKey, ctx))
return req.WithContext(context.WithValue(context.WithValue(req.Context(), privateContextKey, ctx), contextKey, ctx.Context))
}

// GetPrivateContext returns a context for Private routes
Expand Down
44 changes: 44 additions & 0 deletions modules/private/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -230,3 +230,47 @@ func Processes(ctx context.Context, out io.Writer, flat, noSystem, stacktraces,
}
return http.StatusOK, ""
}

// CPUProfile returns a cpu profile from Gitea
func CPUProfile(ctx context.Context, out io.Writer, duration time.Duration) (int, string) {
reqURL := setting.LocalURL + fmt.Sprintf("api/internal/manager/cpu-profile?duration=%s", url.QueryEscape(duration.String()))
return commonGet(ctx, out, reqURL)
}

// FGProfile returns the full go profile from Gitea
func FGProfile(ctx context.Context, out io.Writer, duration time.Duration, format string) (int, string) {
reqURL := setting.LocalURL + fmt.Sprintf("api/internal/manager/fgprof?duration=%s&format=%s", url.QueryEscape(duration.String()), url.QueryEscape(format))
return commonGet(ctx, out, reqURL)
}

// NamedProfile returns the named profile from Gitea
func NamedProfile(ctx context.Context, out io.Writer, name string, debugLevel int) (int, string) {
reqURL := setting.LocalURL + fmt.Sprintf("api/internal/manager/profile?name=%s&debug=%d", url.QueryEscape(name), debugLevel)
return commonGet(ctx, out, reqURL)
}

// ListNamedProfiles returns a list of named profiles
func ListNamedProfiles(ctx context.Context, out io.Writer, json bool) (int, string) {
reqURL := setting.LocalURL + fmt.Sprintf("api/internal/manager/list-profiles?json=%t", json)
return commonGet(ctx, out, reqURL)
}

// Trace returns a trace from Gitea
func Trace(ctx context.Context, out io.Writer, duration time.Duration) (int, string) {
reqURL := setting.LocalURL + fmt.Sprintf("api/internal/manager/trace?duration=%s", url.QueryEscape(duration.String()))
return commonGet(ctx, out, reqURL)
}

func commonGet(ctx context.Context, out io.Writer, reqURL string) (int, string) {
req := newInternalRequest(ctx, reqURL, "GET")
resp, err := req.Response()
if err != nil {
return http.StatusInternalServerError, fmt.Sprintf("Unable to contact gitea: %v", err.Error())
}
defer resp.Body.Close()
_, err = io.Copy(out, resp.Body)
if err != nil {
return http.StatusInternalServerError, err.Error()
}
return resp.StatusCode, ""
}
Loading