diff options
Diffstat (limited to 'extras/hs-test/infra')
-rw-r--r-- | extras/hs-test/infra/container.go | 7 | ||||
-rw-r--r-- | extras/hs-test/infra/hst_suite.go | 6 | ||||
-rw-r--r-- | extras/hs-test/infra/vppinstance.go | 88 |
3 files changed, 100 insertions, 1 deletions
diff --git a/extras/hs-test/infra/container.go b/extras/hs-test/infra/container.go index 5093398ae56..44f141a9102 100644 --- a/extras/hs-test/infra/container.go +++ b/extras/hs-test/infra/container.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "fmt" + "github.com/docker/go-units" "os" "os/exec" "slices" @@ -15,7 +16,6 @@ import ( containerTypes "github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/image" "github.com/docker/docker/pkg/stdcopy" - "github.com/docker/go-units" "github.com/edwarnicke/exechelper" . "github.com/onsi/ginkgo/v2" ) @@ -382,6 +382,11 @@ func (c *Container) CreateFile(destFileName string, content string) error { return nil } +func (c *Container) GetFile(sourceFileName, targetFileName string) error { + cmd := exec.Command("docker", "cp", c.Name+":"+sourceFileName, targetFileName) + return cmd.Run() +} + /* * Executes in detached mode so that the started application can continue to run * without blocking execution of test diff --git a/extras/hs-test/infra/hst_suite.go b/extras/hs-test/infra/hst_suite.go index 975e01d5b8e..2cf241afa64 100644 --- a/extras/hs-test/infra/hst_suite.go +++ b/extras/hs-test/infra/hst_suite.go @@ -35,6 +35,7 @@ var NConfiguredCpus = flag.Int("cpus", 1, "number of CPUs assigned to vpp") var VppSourceFileDir = flag.String("vppsrc", "", "vpp source file directory") var IsDebugBuild = flag.Bool("debug_build", false, "some paths are different with debug build") var UseCpu0 = flag.Bool("cpu0", false, "use cpu0") +var IsLeakCheck = flag.Bool("leak_check", false, "run leak-check tests") var NumaAwareCpuAlloc bool var SuiteTimeout time.Duration @@ -285,6 +286,11 @@ func (s *HstSuite) SkipUnlessExtendedTestsBuilt() { } } +func (s *HstSuite) SkipUnlessLeakCheck() { + if !*IsLeakCheck { + s.Skip("leak-check tests excluded") + } +} func (s *HstSuite) ResetContainers() { for _, container := range s.StartedContainers { container.stop() diff --git a/extras/hs-test/infra/vppinstance.go b/extras/hs-test/infra/vppinstance.go index d4f570046c7..dfb236b6725 100644 --- a/extras/hs-test/infra/vppinstance.go +++ b/extras/hs-test/infra/vppinstance.go @@ -2,6 +2,7 @@ package hst import ( "context" + "encoding/json" "fmt" "go.fd.io/govpp/binapi/ethernet_types" "io" @@ -97,6 +98,13 @@ type VppCpuConfig struct { SkipCores int } +type VppMemTrace struct { + Count int `json:"count"` + Size int `json:"bytes"` + Sample string `json:"sample"` + Traceback []string `json:"traceback"` +} + func (vpp *VppInstance) getSuite() *HstSuite { return vpp.Container.Suite } @@ -535,3 +543,83 @@ func (vpp *VppInstance) generateVPPCpuConfig() string { return c.Close().ToString() } + +// EnableMemoryTrace enables memory traces of VPP main-heap +func (vpp *VppInstance) EnableMemoryTrace() { + vpp.getSuite().Log(vpp.Vppctl("memory-trace on main-heap")) +} + +// GetMemoryTrace dumps memory traces for analysis +func (vpp *VppInstance) GetMemoryTrace() ([]VppMemTrace, error) { + var trace []VppMemTrace + vpp.getSuite().Log(vpp.Vppctl("save memory-trace trace.json")) + err := vpp.Container.GetFile("/tmp/trace.json", "/tmp/trace.json") + if err != nil { + return nil, err + } + fileBytes, err := os.ReadFile("/tmp/trace.json") + if err != nil { + return nil, err + } + err = json.Unmarshal(fileBytes, &trace) + if err != nil { + return nil, err + } + return trace, nil +} + +// memTracesSuppressCli filter out CLI related samples +func memTracesSuppressCli(traces []VppMemTrace) []VppMemTrace { + var filtered []VppMemTrace + for i := 0; i < len(traces); i++ { + isCli := false + for j := 0; j < len(traces[i].Traceback); j++ { + if strings.Contains(traces[i].Traceback[j], "unix_cli") { + isCli = true + break + } + } + if !isCli { + filtered = append(filtered, traces[i]) + } + } + return filtered +} + +// MemLeakCheck compares memory traces at different point in time, analyzes if memory leaks happen and produces report +func (vpp *VppInstance) MemLeakCheck(first, second []VppMemTrace) { + totalBytes := 0 + totalCounts := 0 + trace1 := memTracesSuppressCli(first) + trace2 := memTracesSuppressCli(second) + report := "" + for i := 0; i < len(trace2); i++ { + match := false + for j := 0; j < len(trace1); j++ { + if trace1[j].Sample == trace2[i].Sample { + if trace2[i].Size > trace1[j].Size { + deltaBytes := trace2[i].Size - trace1[j].Size + deltaCounts := trace2[i].Count - trace1[j].Count + report += fmt.Sprintf("grow %d byte(s) in %d allocation(s) from:\n", deltaBytes, deltaCounts) + for j := 0; j < len(trace2[i].Traceback); j++ { + report += fmt.Sprintf("\t#%d %s\n", j, trace2[i].Traceback[j]) + } + totalBytes += deltaBytes + totalCounts += deltaCounts + } + match = true + break + } + } + if !match { + report += fmt.Sprintf("\nleak of %d byte(s) in %d allocation(s) from:\n", trace2[i].Size, trace2[i].Count) + for j := 0; j < len(trace2[i].Traceback); j++ { + report += fmt.Sprintf("\t#%d %s\n", j, trace2[i].Traceback[j]) + } + totalBytes += trace2[i].Size + totalCounts += trace2[i].Count + } + } + summary := fmt.Sprintf("\nSUMMARY: %d byte(s) leaked in %d allocation(s)\n", totalBytes, totalCounts) + AddReportEntry(summary, report) +} |