-
-
Notifications
You must be signed in to change notification settings - Fork 5.9k
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
Changes from all commits
5093d7d
9cfcbb4
f22aa21
b9989b1
6451d01
351149b
7941fbb
0ef07f6
8d0d93e
768a960
82169ca
ba894a1
75d96e8
381f12a
2fc5d72
79756cd
6247e72
d9eac18
7decd93
4d916b4
b9942cb
7c4be9c
6c2017d
d37978e
950c474
2dbab58
2b523f1
5925f47
91bc97a
61755a7
1400ab2
edebd86
1ffe696
92ac295
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
// Copyright 2023 The Gitea Authors. All rights reserved. | ||
// SPDX-License-Identifier: MIT | ||
|
||
package process | ||
|
||
import ( | ||
"bytes" | ||
"fmt" | ||
"io" | ||
) | ||
|
||
// WriteProcesses writes out processes to a provided writer | ||
func WriteProcesses(out io.Writer, processes []*Process, processCount int, goroutineCount int64, indent string, flat bool) error { | ||
if goroutineCount > 0 { | ||
if _, err := fmt.Fprintf(out, "%sTotal Number of Goroutines: %d\n", indent, goroutineCount); err != nil { | ||
return err | ||
} | ||
} | ||
if _, err := fmt.Fprintf(out, "%sTotal Number of Processes: %d\n", indent, processCount); err != nil { | ||
return err | ||
} | ||
if len(processes) > 0 { | ||
if err := WriteProcess(out, processes[0], indent+" ", flat); err != nil { | ||
return err | ||
} | ||
} | ||
if len(processes) > 1 { | ||
for _, process := range processes[1:] { | ||
if _, err := fmt.Fprintf(out, "%s |\n", indent); err != nil { | ||
return err | ||
} | ||
if err := WriteProcess(out, process, indent+" ", flat); err != nil { | ||
return err | ||
} | ||
} | ||
} | ||
return nil | ||
} | ||
|
||
// WriteProcess writes out a process to a provided writer | ||
func WriteProcess(out io.Writer, process *Process, indent string, flat bool) error { | ||
sb := &bytes.Buffer{} | ||
if flat { | ||
if process.ParentPID != "" { | ||
_, _ = fmt.Fprintf(sb, "%s+ PID: %s\t\tType: %s\n", indent, process.PID, process.Type) | ||
} else { | ||
_, _ = fmt.Fprintf(sb, "%s+ PID: %s:%s\tType: %s\n", indent, process.ParentPID, process.PID, process.Type) | ||
} | ||
} else { | ||
_, _ = fmt.Fprintf(sb, "%s+ PID: %s\tType: %s\n", indent, process.PID, process.Type) | ||
} | ||
indent += "| " | ||
|
||
_, _ = fmt.Fprintf(sb, "%sDescription: %s\n", indent, process.Description) | ||
_, _ = fmt.Fprintf(sb, "%sStart: %s\n", indent, process.Start) | ||
|
||
if len(process.Stacks) > 0 { | ||
_, _ = fmt.Fprintf(sb, "%sGoroutines:\n", indent) | ||
for _, stack := range process.Stacks { | ||
indent := indent + " " | ||
_, _ = fmt.Fprintf(sb, "%s+ Description: %s", indent, stack.Description) | ||
if stack.Count > 1 { | ||
_, _ = fmt.Fprintf(sb, "* %d", stack.Count) | ||
} | ||
_, _ = fmt.Fprintf(sb, "\n") | ||
indent += "| " | ||
if len(stack.Labels) > 0 { | ||
_, _ = fmt.Fprintf(sb, "%sLabels: %q:%q", indent, stack.Labels[0].Name, stack.Labels[0].Value) | ||
|
||
if len(stack.Labels) > 1 { | ||
for _, label := range stack.Labels[1:] { | ||
_, _ = fmt.Fprintf(sb, ", %q:%q", label.Name, label.Value) | ||
} | ||
} | ||
_, _ = fmt.Fprintf(sb, "\n") | ||
} | ||
_, _ = fmt.Fprintf(sb, "%sStack:\n", indent) | ||
indent += " " | ||
zeripath marked this conversation as resolved.
Show resolved
Hide resolved
|
||
for _, entry := range stack.Entry { | ||
_, _ = fmt.Fprintf(sb, "%s+ %s\n", indent, entry.Function) | ||
_, _ = fmt.Fprintf(sb, "%s| %s:%d\n", indent, entry.File, entry.Line) | ||
} | ||
} | ||
} | ||
if _, err := out.Write(sb.Bytes()); err != nil { | ||
return err | ||
} | ||
sb.Reset() | ||
if len(process.Children) > 0 { | ||
if _, err := fmt.Fprintf(out, "%sChildren:\n", indent); err != nil { | ||
return err | ||
} | ||
for _, child := range process.Children { | ||
if err := WriteProcess(out, child, indent+" ", flat); err != nil { | ||
return err | ||
} | ||
} | ||
} | ||
return nil | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,11 +4,10 @@ | |
package private | ||
|
||
import ( | ||
"bytes" | ||
"fmt" | ||
"io" | ||
"net/http" | ||
"runtime" | ||
"runtime/pprof" | ||
"time" | ||
|
||
"code.gitea.io/gitea/modules/context" | ||
|
@@ -60,7 +59,7 @@ func Processes(ctx *context.PrivateContext) { | |
ctx.Resp.Header().Set("Content-Type", "text/plain;charset=utf-8") | ||
ctx.Resp.WriteHeader(http.StatusOK) | ||
|
||
if err := writeProcesses(ctx.Resp, processes, processCount, goroutineCount, "", flat); err != nil { | ||
if err := process_module.WriteProcesses(ctx.Resp, processes, processCount, goroutineCount, "", flat); err != nil { | ||
log.Error("Unable to write out process stacktrace: %v", err) | ||
if !ctx.Written() { | ||
ctx.JSON(http.StatusInternalServerError, private.Response{ | ||
|
@@ -71,90 +70,33 @@ func Processes(ctx *context.PrivateContext) { | |
} | ||
} | ||
|
||
func writeProcesses(out io.Writer, processes []*process_module.Process, processCount int, goroutineCount int64, indent string, flat bool) error { | ||
if goroutineCount > 0 { | ||
if _, err := fmt.Fprintf(out, "%sTotal Number of Goroutines: %d\n", indent, goroutineCount); err != nil { | ||
return err | ||
} | ||
} | ||
if _, err := fmt.Fprintf(out, "%sTotal Number of Processes: %d\n", indent, processCount); err != nil { | ||
return err | ||
} | ||
if len(processes) > 0 { | ||
if err := writeProcess(out, processes[0], " ", flat); err != nil { | ||
return err | ||
} | ||
} | ||
if len(processes) > 1 { | ||
for _, process := range processes[1:] { | ||
if _, err := fmt.Fprintf(out, "%s | \n", indent); err != nil { | ||
return err | ||
} | ||
if err := writeProcess(out, process, " ", flat); err != nil { | ||
return err | ||
} | ||
// ListProfiles lists the available named pprof profiles | ||
func ListProfiles(ctx *context.PrivateContext) { | ||
json := ctx.FormBool("json") | ||
profiles := pprof.Profiles() | ||
if json { | ||
names := make([]string, len(profiles)) | ||
for _, profile := range profiles { | ||
names = append(names, profile.Name()) | ||
} | ||
ctx.JSON(http.StatusOK, map[string]interface{}{ | ||
"Names": names, | ||
}) | ||
} | ||
return nil | ||
} | ||
|
||
func writeProcess(out io.Writer, process *process_module.Process, indent string, flat bool) error { | ||
sb := &bytes.Buffer{} | ||
if flat { | ||
if process.ParentPID != "" { | ||
_, _ = fmt.Fprintf(sb, "%s+ PID: %s\t\tType: %s\n", indent, process.PID, process.Type) | ||
} else { | ||
_, _ = fmt.Fprintf(sb, "%s+ PID: %s:%s\tType: %s\n", indent, process.ParentPID, process.PID, process.Type) | ||
ctx.Status(http.StatusOK) | ||
for _, profile := range profiles { | ||
if _, err := ctx.Resp.Write([]byte(profile.Name())); err != nil { | ||
log.Error("Unable to write out profile name: %v", err) | ||
ctx.Error(http.StatusInternalServerError, err.Error()) | ||
return | ||
} | ||
} else { | ||
_, _ = fmt.Fprintf(sb, "%s+ PID: %s\tType: %s\n", indent, process.PID, process.Type) | ||
} | ||
indent += "| " | ||
|
||
_, _ = fmt.Fprintf(sb, "%sDescription: %s\n", indent, process.Description) | ||
_, _ = fmt.Fprintf(sb, "%sStart: %s\n", indent, process.Start) | ||
|
||
if len(process.Stacks) > 0 { | ||
_, _ = fmt.Fprintf(sb, "%sGoroutines:\n", indent) | ||
for _, stack := range process.Stacks { | ||
indent := indent + " " | ||
_, _ = fmt.Fprintf(sb, "%s+ Description: %s", indent, stack.Description) | ||
if stack.Count > 1 { | ||
_, _ = fmt.Fprintf(sb, "* %d", stack.Count) | ||
} | ||
_, _ = fmt.Fprintf(sb, "\n") | ||
indent += "| " | ||
if len(stack.Labels) > 0 { | ||
_, _ = fmt.Fprintf(sb, "%sLabels: %q:%q", indent, stack.Labels[0].Name, stack.Labels[0].Value) | ||
|
||
if len(stack.Labels) > 1 { | ||
for _, label := range stack.Labels[1:] { | ||
_, _ = fmt.Fprintf(sb, ", %q:%q", label.Name, label.Value) | ||
} | ||
} | ||
_, _ = fmt.Fprintf(sb, "\n") | ||
} | ||
_, _ = fmt.Fprintf(sb, "%sStack:\n", indent) | ||
indent += " " | ||
for _, entry := range stack.Entry { | ||
_, _ = fmt.Fprintf(sb, "%s+ %s\n", indent, entry.Function) | ||
_, _ = fmt.Fprintf(sb, "%s| %s:%d\n", indent, entry.File, entry.Line) | ||
} | ||
} | ||
} | ||
if _, err := out.Write(sb.Bytes()); err != nil { | ||
return err | ||
} | ||
sb.Reset() | ||
if len(process.Children) > 0 { | ||
if _, err := fmt.Fprintf(out, "%sChildren:\n", indent); err != nil { | ||
return err | ||
} | ||
for _, child := range process.Children { | ||
if err := writeProcess(out, child, indent+" ", flat); err != nil { | ||
return err | ||
} | ||
if _, err := ctx.Resp.Write([]byte("\n")); err != nil { | ||
log.Error("Unable to write out profile name: %v", err) | ||
ctx.Error(http.StatusInternalServerError, err.Error()) | ||
return | ||
Comment on lines
+79
to
+98
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why are we writing the names twice? |
||
} | ||
} | ||
return nil | ||
ctx.Resp.Flush() | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,187 @@ | ||
// Copyright 2022 The Gitea Authors. All rights reserved. | ||
// SPDX-License-Identifier: MIT | ||
|
||
package admin | ||
|
||
import ( | ||
"fmt" | ||
"net/http" | ||
"runtime/pprof" | ||
"runtime/trace" | ||
"strconv" | ||
"time" | ||
|
||
"code.gitea.io/gitea/modules/context" | ||
"code.gitea.io/gitea/modules/process" | ||
"code.gitea.io/gitea/modules/setting" | ||
|
||
"github.com/felixge/fgprof" | ||
) | ||
|
||
// PProfProcessStacktrace returns the stacktrace similar to GoroutineStacktrace but without rendering it | ||
func PProfProcessStacktrace(ctx *context.Context) { | ||
flat := ctx.FormBool("flat") | ||
noSystem := ctx.FormBool("no-system") | ||
|
||
format := ctx.FormString("format") | ||
jsonFormat := format == "json" | ||
delvh marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
start := time.Now() | ||
filename := "process-stacktrace-" + strconv.FormatInt(start.Unix(), 10) | ||
if jsonFormat { | ||
filename += ".json" | ||
} | ||
|
||
processStacks, processCount, goroutineCount, err := process.GetManager().ProcessStacktraces(flat, noSystem) | ||
if err != nil { | ||
ctx.ServerError("ProcessStacktraces", err) | ||
} | ||
|
||
ctx.SetServeHeaders(&context.ServeHeaderOptions{ | ||
Filename: filename, | ||
LastModified: start, | ||
}) | ||
|
||
if jsonFormat { | ||
ctx.JSON(http.StatusOK, map[string]interface{}{ | ||
"TotalNumberOfGoroutines": goroutineCount, | ||
"TotalNumberOfProcesses": processCount, | ||
"Processes": processStacks, | ||
}) | ||
return | ||
} | ||
|
||
if err := process.WriteProcesses(ctx.Resp, processStacks, processCount, goroutineCount, "", flat); err != nil { | ||
ctx.ServerError("WriteProcesses", err) | ||
} | ||
} | ||
|
||
// PProfFGProfile returns the Full Go Profile from fgprof | ||
func PProfFGProfile(ctx *context.Context) { | ||
durationStr := ctx.FormString("duration") | ||
duration := 30 * time.Second | ||
if durationStr != "" { | ||
var err error | ||
duration, err = time.ParseDuration(durationStr) | ||
if err != nil { | ||
ctx.Flash.Error(ctx.Tr("admin.monitor.pprof.duration_invalid")) | ||
ctx.Redirect(setting.AppSubURL + "/admin/monitor") | ||
return | ||
} | ||
} | ||
|
||
format := fgprof.Format(ctx.FormString("format")) | ||
if format != fgprof.FormatFolded { | ||
format = fgprof.FormatPprof | ||
} | ||
delvh marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
start := time.Now() | ||
|
||
ctx.SetServeHeaders(&context.ServeHeaderOptions{ | ||
Filename: "fgprof-profile-" + strconv.FormatInt(start.Unix(), 10), | ||
LastModified: start, | ||
}) | ||
|
||
fn := fgprof.Start(ctx.Resp, format) | ||
|
||
select { | ||
case <-time.After(duration): | ||
case <-ctx.Done(): | ||
} | ||
|
||
err := fn() | ||
if err != nil { | ||
ctx.ServerError("fgprof.Write", err) | ||
} | ||
} | ||
|
||
// PProfCPUProfile returns the PProf CPU Profile | ||
func PProfCPUProfile(ctx *context.Context) { | ||
durationStr := ctx.FormString("duration") | ||
duration := 30 * time.Second | ||
if durationStr != "" { | ||
var err error | ||
duration, err = time.ParseDuration(durationStr) | ||
if err != nil { | ||
ctx.Flash.Error(ctx.Tr("admin.monitor.pprof.duration_invalid")) | ||
ctx.Redirect(setting.AppSubURL + "/admin/monitor") | ||
return | ||
} | ||
} | ||
|
||
start := time.Now() | ||
|
||
ctx.SetServeHeaders(&context.ServeHeaderOptions{ | ||
Filename: "cpu-profile-" + strconv.FormatInt(start.Unix(), 10), | ||
LastModified: start, | ||
}) | ||
|
||
err := pprof.StartCPUProfile(ctx.Resp) | ||
if err != nil { | ||
ctx.ServerError("StartCPUProfile", err) | ||
return | ||
} | ||
|
||
select { | ||
case <-time.After(duration): | ||
case <-ctx.Done(): | ||
} | ||
pprof.StopCPUProfile() | ||
} | ||
|
||
// PProfNamedProfile returns the PProf Profile | ||
func PProfNamedProfile(ctx *context.Context) { | ||
name := ctx.FormString("name") | ||
profile := pprof.Lookup(name) | ||
if profile == nil { | ||
ctx.ServerError(fmt.Sprintf("pprof.Lookup(%s)", name), fmt.Errorf("missing profile: %s", name)) | ||
return | ||
} | ||
|
||
debug := ctx.FormInt("debug") | ||
|
||
start := time.Now() | ||
|
||
ctx.SetServeHeaders(&context.ServeHeaderOptions{ | ||
Filename: name + "-profile-" + strconv.FormatInt(start.Unix(), 10), | ||
LastModified: start, | ||
}) | ||
if err := profile.WriteTo(ctx.Resp, debug); err != nil { | ||
ctx.ServerError(fmt.Sprintf("PProfNamedProfile(%s).WriteTo", name), err) | ||
return | ||
delvh marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
} | ||
|
||
// Trace returns a trace | ||
func Trace(ctx *context.Context) { | ||
durationStr := ctx.FormString("duration") | ||
duration := 30 * time.Second | ||
if durationStr != "" { | ||
var err error | ||
duration, err = time.ParseDuration(durationStr) | ||
if err != nil { | ||
ctx.Flash.Error(ctx.Tr("admin.monitor.pprof.duration_invalid")) | ||
ctx.Redirect(setting.AppSubURL + "/admin/monitor") | ||
return | ||
} | ||
} | ||
|
||
start := time.Now() | ||
|
||
ctx.SetServeHeaders(&context.ServeHeaderOptions{ | ||
Filename: "trace-" + strconv.FormatInt(start.Unix(), 10), | ||
LastModified: start, | ||
}) | ||
|
||
err := trace.Start(ctx.Resp) | ||
if err != nil { | ||
ctx.ServerError("StartCPUProfile", err) | ||
return | ||
} | ||
|
||
select { | ||
case <-time.After(duration): | ||
case <-ctx.Done(): | ||
} | ||
trace.Stop() | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
<h4 class="ui top attached header"> | ||
{{.locale.Tr "admin.monitor.pprof"}} | ||
</h4> | ||
<div class="ui attached segment"> | ||
<p>{{.locale.Tr "admin.monitor.pprof.description"}}</p> | ||
<p>{{.locale.Tr "admin.monitor.pprof.description_2"}}</p> | ||
delvh marked this conversation as resolved.
Show resolved
Hide resolved
|
||
<h4>{{.locale.Tr "admin.monitor.pprof.cpuprofile"}}</h4> | ||
<form action="{{AppSubUrl}}/admin/monitor/cpu-profile"> | ||
<div class="ui form"> | ||
<div class="help">{{.locale.Tr "admin.monitor.pprof.cpuprofile.description"}}</div> | ||
<div class="field"> | ||
<label for="pprof-cpuprofile-duration">{{.locale.Tr "admin.monitor.pprof.duration"}}</label> | ||
<input id="pprof-cpuprofile-duration" type="text" name="duration" placeholder="{{.locale.Tr "admin.monitor.pprof.duration_placeholder"}}"> | ||
</div> | ||
<input class="ui button basic" type="submit" value="{{.locale.Tr "admin.monitor.pprof.download"}}"> | ||
</div> | ||
</form> | ||
<h4>{{.locale.Tr "admin.monitor.pprof.fgprof"}}</h4> | ||
<form action="{{AppSubUrl}}/admin/monitor/fgprof"> | ||
<div class="ui form"> | ||
<div class="help">{{.locale.Tr "admin.monitor.pprof.fgprof.description"}}</div> | ||
<div class="field"> | ||
<label for="pprof-fgprof-duration">{{.locale.Tr "admin.monitor.pprof.duration"}}</label> | ||
<input id="pprof-fgprof-duration" type="text" name="duration" placeholder="{{.locale.Tr "admin.monitor.pprof.duration_placeholder"}}"> | ||
</div> | ||
<div class="field"> | ||
<label for="pprof-fgprof-format">{{.locale.Tr "admin.monitor.pprof.fgprof.format"}}</label> | ||
<select class="ui search dropdown" id="pprof-fgprof-format" name="format"> | ||
<option value="pprof" selected>pprof</option> | ||
<option value="folded">folded</option> | ||
</select> | ||
</div> | ||
<input class="ui button basic" type="submit" value="{{.locale.Tr "admin.monitor.pprof.download"}}"> | ||
</div> | ||
</form> | ||
<h4>{{.locale.Tr "admin.monitor.stacktrace"}}</h4> | ||
<form action="{{AppSubUrl}}/admin/monitor/stacktrace-profile"> | ||
<div class="ui form"> | ||
<div class="help">{{.locale.Tr "admin.monitor.pprof.stacktrace.description"}}</div> | ||
<div class="inline field"> | ||
<div class="ui checkbox"> | ||
<input id="pprof-stacktrace-flat" type="checkbox" tabindex="0" class="hidden" name="flat"> | ||
zeripath marked this conversation as resolved.
Show resolved
Hide resolved
|
||
<label for="pprof-stacktrace-flat">{{.locale.Tr "admin.monitor.pprof.stacktrace.flat"}}</label> | ||
</div> | ||
</div> | ||
<div class="inline field"> | ||
<div class="ui checkbox"> | ||
<input id="pprof-stacktrace-no-system" type="checkbox" tabindex="0" class="hidden" name="no-system"> | ||
zeripath marked this conversation as resolved.
Show resolved
Hide resolved
|
||
<label for="pprof-stacktrace-no-system">{{.locale.Tr "admin.monitor.pprof.stacktrace.no-system"}}</label> | ||
</div> | ||
</div> | ||
<div class="field"> | ||
<label for="pprof-stacktrace-format">{{.locale.Tr "admin.monitor.pprof.stacktrace.format"}}</label> | ||
<select class="ui search dropdown" id="pprof-stacktrace-format" name="format"> | ||
<option value="text" selected>text</option> | ||
<option value="json">json</option> | ||
</select> | ||
</div> | ||
<input class="ui button basic" type="submit" value="{{.locale.Tr "admin.monitor.pprof.download"}}"> | ||
</div> | ||
</form> | ||
<h4>{{.locale.Tr "admin.monitor.pprof.named_profiles"}}</h4> | ||
<form action="{{AppSubUrl}}/admin/monitor/profile"> | ||
<div class="ui form"> | ||
<div class="help">{{.locale.Tr "admin.monitor.pprof.named_profiles.description"}}</div> | ||
<div class="field"> | ||
<label for="pprof-named_profiles-name">{{.locale.Tr "admin.monitor.pprof.named_profiles.name"}}</label> | ||
<select class="ui search dropdown" id="pprof-named_profiles-name" name="name"> | ||
{{range .Profiles}} | ||
<option value="{{.Name}}">{{.Name}}</option> | ||
{{end}} | ||
</select> | ||
</div> | ||
<div class="field"> | ||
<label for="pprof-named_profiles-debug">{{$.locale.Tr "admin.monitor.pprof.named_profiles.debug"}}</label> | ||
<select class="ui search dropdown" id="pprof-named_profiles-debug" name="format"> | ||
<option value="0" selected>{{.locale.Tr "admin.monitor.pprof.named_profiles.format_pprof"}}</option> | ||
<option value="1">{{.locale.Tr "admin.monitor.pprof.named_profiles.format_text"}}</option> | ||
<option value="2">{{.locale.Tr "admin.monitor.pprof.named_profiles.format_goroutine"}}</option> | ||
zeripath marked this conversation as resolved.
Show resolved
Hide resolved
|
||
</select> | ||
</div> | ||
<input class="ui button basic" type="submit" value="{{$.locale.Tr "admin.monitor.pprof.download"}}"> | ||
</div> | ||
</form> | ||
<h4>{{.locale.Tr "admin.monitor.pprof.trace"}}</h4> | ||
<form action="{{AppSubUrl}}/admin/monitor/trace"> | ||
<div class="ui form"> | ||
<div class="help">{{.locale.Tr "admin.monitor.pprof.trace.description"}}</div> | ||
<div class="field"> | ||
<label for="pprof-trace-duration">{{.locale.Tr "admin.monitor.pprof.duration"}}</label> | ||
<input id="pprof-trace-duration" type="text" name="duration" placeholder="{{.locale.Tr "admin.monitor.pprof.duration_placeholder"}}"> | ||
</div> | ||
<input class="ui button basic" type="submit" value="{{.locale.Tr "admin.monitor.pprof.download"}}"> | ||
</div> | ||
</form> | ||
</div> |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,6 +7,7 @@ | |
{{.locale.Tr "admin.monitor.stacktrace"}}: {{.locale.Tr "admin.monitor.goroutines" .GoroutineCount}} | ||
<div class="ui right"> | ||
<a class="ui primary tiny button" href="{{AppSubUrl}}/admin/monitor">{{.locale.Tr "admin.monitor"}}</a> | ||
<a class="ui tiny button" id="download-stacktrace">{{.locale.Tr "admin.monitor.stacktrace.download_stacktrace"}}</a> | ||
</div> | ||
</h4> | ||
<div class="ui attached segment"> | ||
|
@@ -18,6 +19,16 @@ | |
</div> | ||
</div> | ||
</div> | ||
<div class="ui small basic modal" id="download-stacktrace-modal"> | ||
<div class="ui icon header"> | ||
{{svg "octicon-download" 16 "close inside"}} | ||
{{.locale.Tr "admin.monitor.stacktrace.download_stacktrace"}} | ||
</div> | ||
{{template "base/delete_modal_actions" .}} | ||
<div class="hide" id="stacktrace-to-download"> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why not just:
? Then no need that complex dialog nor the JS code There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No. Then they get a different stacktrace. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I mean , could there be no stacktrace on the UI? Just some links to help to collect problems. The complex UI doesn't help users. As an end user, they should just download and report the stacktrace file. The end users could do nothing even if they see the stacktrace on the UI..... There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. FYI, when I was using GitLab, they have an all-in-one tool. It collects everything into a file, and what I need to do is just using the tool and sending the generated file to them, then they can help to resolve problems. Making the diagnosis system too complex doesn't benefit end users IMO. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I'm going to add a direct download stacktrace profile below to get the stacktraces without seeing them.
The pretty stacktrace helps me and SEVERAL bugs have been solved using it. It has helped me a large number of times already.
Not every user is incapable and whilst the purpose of this UI is to help us to help users we should enable users and developers to help themselves. A pretty UI can be helpful for us to solve issues in a way that the opaque stacktrace format is not. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
If it means more complex, then it's not necessary IMO. My initial idea is about keeping the system simple but complete and useful. While I do not think keeping the downloaded stacktrace file exactly the same as the UI list is meaningful -- everytime you refresh the page, you get a different stacktrace ..... Even if they are different, they are all helpful for resolving problem equally, and maybe sometimes the UI shown list is not helpful but the downloaded is helpful in case the downloaded one catches the problem, everything is possible. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fundamentally it's really important to be able to download the stacktrace that you have seen. Given these are dynamic it's possible that you may actually only see the issue on the stacktrace that you have and if you try get another the issue will be gone. We need the download.
If I knew what we generally needed I would do that - but in general we don't need cpu-profiles etc. We do need a way of reading the logs and I will get to that but ... let's get general routes in and then we can have pared back routes to get the common things. Right now we have no easy way of getting pprof profiles from users and that fundamentally limits our ability to use them for diagnostics. This PR makes that a hell of a lot easier. |
||
{{.StacktraceString}} | ||
</div> | ||
</div> | ||
<div class="ui small basic delete modal"> | ||
<div class="ui icon header"> | ||
{{svg "octicon-x" 16 "close inside"}} | ||
|
Uh oh!
There was an error while loading. Please reload this page.