@@ -13,12 +13,11 @@ import (
13
13
"os"
14
14
"runtime"
15
15
"runtime/pprof"
16
+ "sort"
16
17
"strconv"
17
18
"strings"
18
19
"time"
19
20
20
- "github.com/google/pprof/profile"
21
-
22
21
"code.gitea.io/gitea/models"
23
22
"code.gitea.io/gitea/modules/base"
24
23
"code.gitea.io/gitea/modules/context"
@@ -36,6 +35,7 @@ import (
36
35
"code.gitea.io/gitea/services/mailer"
37
36
38
37
"gitea.com/go-chi/session"
38
+ "github.com/google/pprof/profile"
39
39
)
40
40
41
41
const (
@@ -331,7 +331,7 @@ func Monitor(ctx *context.Context) {
331
331
ctx .Data ["Title" ] = ctx .Tr ("admin.monitor" )
332
332
ctx .Data ["PageIsAdmin" ] = true
333
333
ctx .Data ["PageIsAdminMonitor" ] = true
334
- ctx .Data ["Processes" ] = process .GetManager ().Processes (true )
334
+ ctx .Data ["Processes" ] = process .GetManager ().Processes (true , true , nil )
335
335
ctx .Data ["Entries" ] = cron .ListTasks ()
336
336
ctx .Data ["Queues" ] = queue .GetManager ().ManagedQueues ()
337
337
@@ -344,18 +344,176 @@ func GoroutineStacktrace(ctx *context.Context) {
344
344
ctx .Data ["PageIsAdmin" ] = true
345
345
ctx .Data ["PageIsAdminMonitor" ] = true
346
346
347
- reader , writer := io .Pipe ()
348
- defer reader .Close ()
349
- go func () {
350
- err := pprof .Lookup ("goroutine" ).WriteTo (writer , 0 )
351
- _ = writer .CloseWithError (err )
352
- }()
353
- p , err := profile .Parse (reader )
354
- if err != nil {
355
- ctx .ServerError ("GoroutineStacktrace" , err )
347
+ var stacks * profile.Profile
348
+ processes := process .GetManager ().Processes (false , false , func () {
349
+ reader , writer := io .Pipe ()
350
+ defer reader .Close ()
351
+ go func () {
352
+ err := pprof .Lookup ("goroutine" ).WriteTo (writer , 0 )
353
+ _ = writer .CloseWithError (err )
354
+ }()
355
+ var err error
356
+ stacks , err = profile .Parse (reader )
357
+ if err != nil {
358
+ ctx .ServerError ("GoroutineStacktrace" , err )
359
+ return
360
+ }
361
+ })
362
+ if ctx .Written () {
356
363
return
357
364
}
358
- ctx .Data ["Profile" ] = p
365
+
366
+ type StackEntry struct {
367
+ Function string
368
+ File string
369
+ Line int
370
+ }
371
+
372
+ type Label struct {
373
+ Name string
374
+ Value string
375
+ }
376
+
377
+ type Stack struct {
378
+ Count int64
379
+ Description string
380
+ Labels []* Label
381
+ Entry []* StackEntry
382
+ }
383
+
384
+ type ProcessStack struct {
385
+ PID process.IDType
386
+ ParentPID process.IDType
387
+ Description string
388
+ Start time.Time
389
+ Type string
390
+
391
+ Children []* ProcessStack
392
+ Stacks []* Stack
393
+ }
394
+
395
+ // Now earlier we sorted by process time so we know that we should not have children before parents
396
+ pidMap := map [process.IDType ]* ProcessStack {}
397
+ processStacks := make ([]* ProcessStack , 0 , len (processes ))
398
+ for _ , process := range processes {
399
+ pStack := & ProcessStack {
400
+ PID : process .PID ,
401
+ ParentPID : process .ParentPID ,
402
+ Description : process .Description ,
403
+ Start : process .Start ,
404
+ Type : process .Type ,
405
+ }
406
+
407
+ pidMap [process .PID ] = pStack
408
+ if parent , ok := pidMap [process .ParentPID ]; ok {
409
+ parent .Children = append (parent .Children , pStack )
410
+ } else {
411
+ processStacks = append (processStacks , pStack )
412
+ }
413
+ }
414
+
415
+ goroutineCount := int64 (0 )
416
+
417
+ // Now walk through the "Sample" slice in the goroutines stack
418
+ for _ , sample := range stacks .Sample {
419
+ stack := & Stack {}
420
+
421
+ // Add the labels
422
+ for name , value := range sample .Label {
423
+ if name == process .DescriptionPProfLabel || name == process .PIDPProfLabel || name == process .PPIDPProfLabel || name == process .ProcessTypePProfLabel {
424
+ continue
425
+ }
426
+ if len (value ) != 1 {
427
+ // Unexpected...
428
+ log .Error ("Label: %s in goroutine stack with unexpected number of values: %v" , name , value )
429
+ continue
430
+ }
431
+
432
+ stack .Labels = append (stack .Labels , & Label {Name : name , Value : value [0 ]})
433
+ }
434
+
435
+ stack .Count = sample .Value [0 ]
436
+ goroutineCount += stack .Count
437
+
438
+ // Now get the processStack for this goroutine sample
439
+ var processStack * ProcessStack
440
+ if pidvalue , ok := sample .Label [process .PIDPProfLabel ]; ok && len (pidvalue ) == 1 {
441
+ pid := process .IDType (pidvalue [0 ])
442
+ processStack , ok = pidMap [pid ]
443
+ if ! ok && pid != "" {
444
+ ppid := process .IDType ("" )
445
+ if value , ok := sample .Label [process .PPIDPProfLabel ]; ok && len (value ) == 1 {
446
+ ppid = process .IDType (value [0 ])
447
+ }
448
+ description := "(missing process)"
449
+ if value , ok := sample .Label [process .DescriptionPProfLabel ]; ok && len (value ) == 1 {
450
+ description = value [0 ] + " " + description
451
+ }
452
+ ptype := process .SystemProcessType
453
+ if value , ok := sample .Label [process .ProcessTypePProfLabel ]; ok && len (value ) == 1 {
454
+ ptype = value [0 ]
455
+ }
456
+ processStack = & ProcessStack {
457
+ PID : pid ,
458
+ ParentPID : ppid ,
459
+ Description : description ,
460
+ Type : ptype ,
461
+ }
462
+
463
+ pidMap [processStack .PID ] = processStack
464
+ if parent , ok := pidMap [processStack .ParentPID ]; ok {
465
+ parent .Children = append (parent .Children , processStack )
466
+ }
467
+ }
468
+ }
469
+ if processStack == nil {
470
+ var ok bool
471
+ processStack , ok = pidMap ["" ]
472
+ if ! ok {
473
+ processStack = & ProcessStack {
474
+ Description : "(unknown)" ,
475
+ Type : "code" ,
476
+ }
477
+ pidMap [processStack .PID ] = processStack
478
+ processStacks = append (processStacks , processStack )
479
+ }
480
+ }
481
+
482
+ // Now walk through the locations...
483
+ for _ , location := range sample .Location {
484
+ for _ , line := range location .Line {
485
+ entry := & StackEntry {
486
+ Function : line .Function .Name ,
487
+ File : line .Function .Filename ,
488
+ Line : int (line .Line ),
489
+ }
490
+ stack .Entry = append (stack .Entry , entry )
491
+ }
492
+ }
493
+ stack .Description = "(others)"
494
+ if len (stack .Entry ) > 0 {
495
+ stack .Description = stack .Entry [len (stack .Entry )- 1 ].Function
496
+ }
497
+
498
+ processStack .Stacks = append (processStack .Stacks , stack )
499
+ }
500
+
501
+ // Now finally re-sort the processstacks so the newest processes are at the top
502
+ after := func (processStacks []* ProcessStack ) func (i , j int ) bool {
503
+ return func (i , j int ) bool {
504
+ left , right := processStacks [i ], processStacks [j ]
505
+ return left .Start .After (right .Start )
506
+ }
507
+ }
508
+ sort .Slice (processStacks , after (processStacks ))
509
+ for _ , processStack := range processStacks {
510
+ sort .Slice (processStack .Children , after (processStack .Children ))
511
+ }
512
+
513
+ ctx .Data ["ProcessStacks" ] = processStacks
514
+ ctx .Data ["Profile" ] = stacks
515
+
516
+ ctx .Data ["GoroutineCount" ] = goroutineCount
359
517
360
518
ctx .HTML (http .StatusOK , tplStacktrace )
361
519
}
0 commit comments