Skip to content

Commit 303cbfe

Browse files
committed
adding efficient polling to waitForStepsToFinish
The current waitForStepsToFinish implementation is a classic busy-wait. It checks for file existence without any sleep, resulting in a high CPU usage. Adding a profile with a unit test to show that almost all time is spent in system calls with a high total sample count. This led to execssive CPU usage by the sidecar even when just waiting. The function now sleeps 100ms between checks, drastically reducing the frequency. The sidecar now uses minimal CPU while waiting. Signed-off-by: Priti Desai <pdesai@us.ibm.com>
1 parent d504890 commit 303cbfe

File tree

2 files changed

+56
-0
lines changed

2 files changed

+56
-0
lines changed

internal/sidecarlogresults/sidecarlogresults.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import (
2626
"os"
2727
"path/filepath"
2828
"strings"
29+
"time"
2930

3031
"github.com/tektoncd/pipeline/pkg/apis/config"
3132
"github.com/tektoncd/pipeline/pkg/apis/pipeline"
@@ -103,6 +104,7 @@ func waitForStepsToFinish(runDir string) error {
103104
return err
104105
}
105106
}
107+
time.Sleep(100 * time.Millisecond)
106108
}
107109
return nil
108110
}

internal/sidecarlogresults/sidecarlogresults_test.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,11 @@ import (
2323
"fmt"
2424
"os"
2525
"path/filepath"
26+
"runtime/pprof"
2627
"sort"
2728
"strings"
2829
"testing"
30+
"time"
2931

3032
"github.com/google/go-cmp/cmp"
3133
v1 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1"
@@ -608,6 +610,58 @@ func TestExtractStepAndResultFromSidecarResultName_Error(t *testing.T) {
608610
}
609611
}
610612

613+
// TestWaitForStepsToFinish_Profile ensures that waitForStepsToFinish correctly waits for all step output files to appear before returning
614+
// The test creates a file called cpu.prof and starts Go's CPU profiler
615+
// A temporary directory is created to simulate the Tekton step run directory.
616+
// The test creates a large number of subdirectories e.g. step0, step1, ..., each representing a step in a TaskRun
617+
// A goroutine is started that, one by one, writes an out file in each step directory, with a small delay between each
618+
// The test calls the function and waits for it to complete and the profile is saved for later analysis
619+
// This is helpful to compare the impact of code changes, provides a reproducible way to profile and optimize the function waitForStepsToFinish
620+
func TestWaitForStepsToFinish_Profile(t *testing.T) {
621+
f, err := os.Create("cpu.prof")
622+
if err != nil {
623+
t.Fatalf("could not create CPU profile: %v", err)
624+
}
625+
defer func(f *os.File) {
626+
err := f.Close()
627+
if err != nil {
628+
return
629+
}
630+
}(f)
631+
err = pprof.StartCPUProfile(f)
632+
if err != nil {
633+
return
634+
}
635+
defer pprof.StopCPUProfile()
636+
637+
// Setup: create a temp runDir with many fake step files
638+
runDir := t.TempDir()
639+
stepCount := 100
640+
for i := 0; i < stepCount; i++ {
641+
dir := filepath.Join(runDir, fmt.Sprintf("step%d", i))
642+
err := os.MkdirAll(dir, 0755)
643+
if err != nil {
644+
return
645+
}
646+
}
647+
648+
// Simulate steps finishing one by one with a delay
649+
go func() {
650+
for i := 0; i < stepCount; i++ {
651+
file := filepath.Join(runDir, fmt.Sprintf("step%d", i), "out")
652+
err := os.WriteFile(file, []byte("done"), 0644)
653+
if err != nil {
654+
return
655+
}
656+
time.Sleep(10 * time.Millisecond)
657+
}
658+
}()
659+
660+
if err := waitForStepsToFinish(runDir); err != nil {
661+
t.Fatalf("waitForStepsToFinish failed: %v", err)
662+
}
663+
}
664+
611665
func TestLookForArtifacts(t *testing.T) {
612666
base := basicArtifacts()
613667
modified := base.DeepCopy()

0 commit comments

Comments
 (0)