Skip to content

Commit bc92b10

Browse files
authored
[perf] Profile possibly slow functions (#1491)
## Summary Profile a few functions. This shows any function that takes more than 1 ms. Q: does trace have this functionality? It looks like it does time events but we have to explicitly enable it using the `trace` flag? ## How was it tested? ```bash export DEVBOX_PRINT_EXEC_TIME=1 devbox add curl ... Exec times over 1ms: "syncPackagesToProfile" took 343.118292ms "nixEnv" took 128.018333ms "nixEnv" took 128.219083ms "CreateWrappers" took 257.527167ms "ensurePackagesAreInstalled" took 768.378084ms "CreateWrappers" took 2.56ms "devbox add curl" took 1.21434s ```
1 parent 3ed5546 commit bc92b10

File tree

6 files changed

+47
-9
lines changed

6 files changed

+47
-9
lines changed

internal/boxcli/root.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ func Execute(ctx context.Context, args []string) int {
106106
}
107107

108108
func Main() {
109+
timer := debug.Timer(strings.Join(os.Args, " "))
109110
ctx := context.Background()
110111
if strings.HasSuffix(os.Args[0], "ssh") ||
111112
strings.HasSuffix(os.Args[0], "scp") {
@@ -126,7 +127,7 @@ func Main() {
126127
code := Execute(ctx, os.Args[1:])
127128
// Run out here instead of as a middleware so we can capture any time we spend
128129
// in middlewares as well.
129-
debug.PrintExecutionTime()
130+
timer.End()
130131
os.Exit(code)
131132
}
132133

internal/debug/time.go

Lines changed: 40 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,22 +6,54 @@ package debug
66
import (
77
"fmt"
88
"os"
9+
"runtime"
910
"strconv"
1011
"strings"
1112
"time"
1213
)
1314

15+
var timerEnabled, _ = strconv.ParseBool(os.Getenv(devboxPrintExecTime))
16+
1417
const devboxPrintExecTime = "DEVBOX_PRINT_EXEC_TIME"
1518

16-
var start = time.Now()
19+
var headerPrinted = false
20+
21+
type timer struct {
22+
name string
23+
time time.Time
24+
}
25+
26+
func Timer(name string) *timer {
27+
if !timerEnabled {
28+
return nil
29+
}
30+
return &timer{
31+
name: name,
32+
time: time.Now(),
33+
}
34+
}
35+
36+
func FunctionTimer() *timer {
37+
if !timerEnabled {
38+
return nil
39+
}
40+
pc := make([]uintptr, 15)
41+
n := runtime.Callers(2, pc)
42+
frames := runtime.CallersFrames(pc[:n])
43+
frame, _ := frames.Next()
44+
parts := strings.Split(frame.Function, ".")
45+
return Timer(parts[len(parts)-1])
46+
}
1747

18-
func PrintExecutionTime() {
19-
if enabled, _ := strconv.ParseBool(os.Getenv(devboxPrintExecTime)); !enabled {
48+
func (t *timer) End() {
49+
if t == nil {
2050
return
2151
}
22-
fmt.Fprintf(
23-
os.Stderr,
24-
"\"%s\" took %s\n", strings.Join(os.Args, " "),
25-
time.Since(start),
26-
)
52+
if !headerPrinted {
53+
fmt.Fprintln(os.Stderr, "\nExec times over 1ms:")
54+
headerPrinted = true
55+
}
56+
if time.Since(t.time) >= time.Millisecond {
57+
fmt.Fprintf(os.Stderr, "\"%s\" took %s\n", t.name, time.Since(t.time))
58+
}
2759
}

internal/impl/devbox.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -934,6 +934,7 @@ var nixEnvCache map[string]string
934934
// Note that this is in-memory cache of the final environment, and not the same
935935
// as the nix print-dev-env cache which is stored in a file.
936936
func (d *Devbox) nixEnv(ctx context.Context) (map[string]string, error) {
937+
defer debug.FunctionTimer().End()
937938
if nixEnvCache != nil {
938939
return nixEnvCache, nil
939940
}

internal/impl/packages.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,7 @@ const (
203203
// what operations are happening, because this function may take time to execute.
204204
func (d *Devbox) ensurePackagesAreInstalled(ctx context.Context, mode installMode) error {
205205
defer trace.StartRegion(ctx, "ensurePackages").End()
206+
defer debug.FunctionTimer().End()
206207

207208
if upToDate, err := d.lockfile.IsUpToDateAndInstalled(); err != nil || upToDate {
208209
return err
@@ -266,6 +267,7 @@ func (d *Devbox) profilePath() (string, error) {
266267
// syncPackagesToProfile ensures that all packages in devbox.json exist in the nix profile,
267268
// and no more.
268269
func (d *Devbox) syncPackagesToProfile(ctx context.Context, mode installMode) error {
270+
defer debug.FunctionTimer().End()
269271
// TODO: we can probably merge these two operations to be faster and minimize chances of
270272
// the devbox.json and nix profile falling out of sync.
271273
if err := d.addPackagesToProfile(ctx, mode); err != nil {

internal/shellgen/scripts.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ type devboxer interface {
3333
// WriteScriptsToFiles writes scripts defined in devbox.json into files inside .devbox/gen/scripts.
3434
// Scripts (and hooks) are persisted so that we can easily call them from devbox run (inside or outside shell).
3535
func WriteScriptsToFiles(devbox devboxer) error {
36+
defer debug.FunctionTimer().End()
3637
err := os.MkdirAll(filepath.Join(devbox.ProjectDir(), scriptsDir), 0755) // Ensure directory exists.
3738
if err != nil {
3839
return errors.WithStack(err)

internal/wrapnix/wrapper.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ var devboxSymlinkDir = xdg.CacheSubpath(filepath.Join("devbox", "bin", "current"
3838

3939
// CreateWrappers creates wrappers for all the executables in nix paths
4040
func CreateWrappers(ctx context.Context, devbox devboxer) error {
41+
defer debug.FunctionTimer().End()
4142
shellEnvHash, err := devbox.ShellEnvHash(ctx)
4243
if err != nil {
4344
return err

0 commit comments

Comments
 (0)