@@ -2,13 +2,19 @@ package bootstrap
2
2
3
3
import (
4
4
"embed"
5
+ "fmt"
5
6
"github.com/diggerhq/digger/backend/config"
6
7
"github.com/diggerhq/digger/backend/segment"
8
+ pprof_gin "github.com/gin-contrib/pprof"
7
9
"html/template"
8
10
"io/fs"
9
11
"log"
10
12
"net/http"
11
13
"os"
14
+ "path/filepath"
15
+ "runtime"
16
+ "runtime/pprof"
17
+
12
18
"time"
13
19
14
20
"github.com/diggerhq/digger/backend/controllers"
@@ -24,6 +30,68 @@ import (
24
30
// based on https://www.digitalocean.com/community/tutorials/using-ldflags-to-set-version-information-for-go-applications
25
31
var Version = "dev"
26
32
33
+ func setupProfiler (r * gin.Engine ) {
34
+ // Enable pprof endpoints
35
+ pprof_gin .Register (r )
36
+
37
+ // Create profiles directory if it doesn't exist
38
+ if err := os .MkdirAll ("/tmp/profiles" , 0755 ); err != nil {
39
+ log .Fatalf ("Failed to create profiles directory: %v" , err )
40
+ }
41
+
42
+ // Start periodic profiling goroutine
43
+ go periodicProfiling ()
44
+ }
45
+
46
+ func periodicProfiling () {
47
+ ticker := time .NewTicker (1 * time .Hour )
48
+ defer ticker .Stop ()
49
+
50
+ for {
51
+ select {
52
+ case <- ticker .C :
53
+ // Trigger GC before taking memory profile
54
+ runtime .GC ()
55
+
56
+ // Create memory profile
57
+ timestamp := time .Now ().Format ("2006-01-02-15-04-05" )
58
+ memProfilePath := filepath .Join ("/tmp/profiles" , fmt .Sprintf ("memory-%s.pprof" , timestamp ))
59
+ f , err := os .Create (memProfilePath )
60
+ if err != nil {
61
+ log .Printf ("Failed to create memory profile: %v" , err )
62
+ continue
63
+ }
64
+
65
+ if err := pprof .WriteHeapProfile (f ); err != nil {
66
+ log .Printf ("Failed to write memory profile: %v" , err )
67
+ }
68
+ f .Close ()
69
+
70
+ // Cleanup old profiles (keep last 24)
71
+ cleanupOldProfiles ("/tmp/profiles" , 168 )
72
+ }
73
+ }
74
+ }
75
+
76
+ func cleanupOldProfiles (dir string , keep int ) {
77
+ files , err := filepath .Glob (filepath .Join (dir , "memory-*.pprof" ))
78
+ if err != nil {
79
+ log .Printf ("Failed to list profile files: %v" , err )
80
+ return
81
+ }
82
+
83
+ if len (files ) <= keep {
84
+ return
85
+ }
86
+
87
+ // Sort files by name (which includes timestamp)
88
+ for i := 0 ; i < len (files )- keep ; i ++ {
89
+ if err := os .Remove (files [i ]); err != nil {
90
+ log .Printf ("Failed to remove old profile %s: %v" , files [i ], err )
91
+ }
92
+ }
93
+ }
94
+
27
95
func Bootstrap (templates embed.FS , diggerController controllers.DiggerController ) * gin.Engine {
28
96
defer segment .CloseClient ()
29
97
initLogging ()
@@ -46,6 +114,11 @@ func Bootstrap(templates embed.FS, diggerController controllers.DiggerController
46
114
models .ConnectDatabase ()
47
115
48
116
r := gin .Default ()
117
+
118
+ if _ , exists := os .LookupEnv ("DIGGER_PPROF_DEBUG_ENABLED" ); exists {
119
+ setupProfiler (r )
120
+ }
121
+
49
122
// TODO: check "secret"
50
123
store := gormsessions .NewStore (models .DB .GormDB , true , []byte ("secret" ))
51
124
0 commit comments