Skip to content

Commit

Permalink
Transfer - Allow thread dumping
Browse files Browse the repository at this point in the history
  • Loading branch information
yahavi committed Dec 14, 2023
1 parent 95b17f1 commit 5d4da87
Show file tree
Hide file tree
Showing 2 changed files with 144 additions and 0 deletions.
87 changes: 87 additions & 0 deletions utils/coreutils/profiler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package coreutils

import (
"errors"
"fmt"
"os"
"runtime/pprof"
"time"

"github.com/jfrog/jfrog-client-go/utils/io/fileutils"
)

const (
defaultInterval = time.Second
defaultRepetitions = 3
)

type Profiler struct {
interval time.Duration
repetitions uint
}

type ProfilerOption func(*Profiler)

func NewProfiler(opts ...ProfilerOption) *Profiler {
profiler := &Profiler{
interval: defaultInterval,
repetitions: defaultRepetitions,
}
for _, opt := range opts {
opt(profiler)
}
return profiler
}

func WithInterval(interval time.Duration) ProfilerOption {
return func(p *Profiler) {
p.interval = interval
}
}

func WithRepetitions(repetitions uint) ProfilerOption {
return func(p *Profiler) {
p.repetitions = repetitions
}
}

func (p *Profiler) ThreadDump() (output string, err error) {
var outputFilePath string
if outputFilePath, err = p.threadDumpToFile(); err != nil {
return
}
defer func() {
err = errors.Join(err, os.Remove(outputFilePath))
}()
return p.convertFileToString(outputFilePath)
}

func (p *Profiler) threadDumpToFile() (outputFilePath string, err error) {
outputFile, err := fileutils.CreateTempFile()
if err != nil {
return
}
defer func() {
err = errors.Join(err, outputFile.Close())
}()

for i := 0; i < int(p.repetitions); i++ {
fmt.Fprintf(outputFile, "=== Thread dump #%d ===\n", i)
prof := pprof.Lookup("goroutine")
if err = prof.WriteTo(outputFile, 1); err != nil {
return
}
time.Sleep(p.interval)
}
return outputFile.Name(), nil
}

func (p *Profiler) convertFileToString(outputFilePath string) (output string, err error) {
var outputBytes []byte
outputBytes, err = os.ReadFile(outputFilePath)
if err != nil {
return
}
output = string(outputBytes)
return
}
57 changes: 57 additions & 0 deletions utils/coreutils/profiler_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package coreutils

import (
"strconv"
"testing"
"time"

"github.com/stretchr/testify/assert"
)

func TestThreadDump(t *testing.T) {
// Create default profiler
profiler := NewProfiler()

// Start a thread that sleeps
go func() {
dummyZzzz()
}()

// Run thread dump
output, err := profiler.ThreadDump()
assert.NoError(t, err)

// Check results
assert.Contains(t, output, "Thread dump #0")
assert.Contains(t, output, "Thread dump #1")
assert.Contains(t, output, "Thread dump #2")
assert.Contains(t, output, "dummyZzzz")
}

func TestThreadInterval(t *testing.T) {
// Create profiler with 10 repetitions and 10ms intervals
var expectedRepetitions uint = 10
var expectedInterval = 10 * time.Millisecond
profiler := NewProfiler(WithInterval(expectedInterval), WithRepetitions(expectedRepetitions))

// Check that the required values are set
assert.Equal(t, profiler.interval, expectedInterval)
assert.Equal(t, profiler.repetitions, expectedRepetitions)

// start measure the time
start := time.Now()

// Run thread dump
output, err := profiler.ThreadDump()
assert.NoError(t, err)

// Ensure duration less than 1 second
assert.WithinDuration(t, start, time.Now(), time.Second)

// Ensure 10 repetitions
assert.Contains(t, output, "Thread dump #"+strconv.FormatUint(uint64(expectedRepetitions)-1, 10))
}

func dummyZzzz() {
time.Sleep(time.Second)
}

0 comments on commit 5d4da87

Please sign in to comment.