Skip to content

Commit d9fd463

Browse files
committed
Add -bench-replay
1 parent 560ef1d commit d9fd463

File tree

3 files changed

+156
-0
lines changed

3 files changed

+156
-0
lines changed

cli_flags.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ const (
2929
defaultArgSendErrorFrames = false
3030
defaultArgBenchDataDir = ""
3131
defaultArgBenchProtoDir = ""
32+
defaultArgBenchReplay = false
3233

3334
// This is the X in 2^(n + x) where n is the default hardcoded map size value
3435
defaultArgMapScaleFactor = 0
@@ -70,11 +71,13 @@ var (
7071
sendErrorFramesHelp = "Send error frames (devfiler only, breaks Kibana)"
7172
benchDataDirHelp = "Directory to store data for benchmarking."
7273
benchProtoDirHelp = "Directory to store raw protobuf wire messages."
74+
benchReplayHelp = "Replay data from -bench-data-dir directory."
7375
)
7476

7577
type arguments struct {
7678
benchDataDir string
7779
benchProtoDir string
80+
benchReplay bool
7881
bpfVerifierLogLevel uint
7982
bpfVerifierLogSize int
8083
collAgentAddr string
@@ -154,6 +157,8 @@ func parseArgs() (*arguments, error) {
154157
benchDataDirHelp)
155158
fs.StringVar(&args.benchProtoDir, "bench-proto-dir", defaultArgBenchProtoDir,
156159
benchProtoDirHelp)
160+
fs.BoolVar(&args.benchReplay, "bench-replay", defaultArgBenchReplay,
161+
benchReplayHelp)
157162

158163
fs.Usage = func() {
159164
fs.PrintDefaults()

main.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,12 @@ func mainWithExitCode() exitCode {
188188
}
189189

190190
if args.benchDataDir != "" {
191+
if args.benchReplay {
192+
if err = reporter.Replay(mainCtx, args.benchDataDir, rep); err != nil {
193+
return failure("Failed to replay benchmark data: %v", err)
194+
}
195+
return exitSuccess
196+
}
191197
rep, err = reporter.NewBenchmarkReporter(args.benchDataDir, rep)
192198
if err != nil {
193199
return failure("Failed to create benchmark reporter: %v", err)
@@ -344,6 +350,10 @@ func sanityCheck(args *arguments) exitCode {
344350
}
345351
}
346352

353+
if args.benchReplay && args.benchDataDir == "" {
354+
return failure("Replay requested but no data directory specified")
355+
}
356+
347357
return exitSuccess
348358
}
349359

reporter/replay.go

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
package reporter
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"fmt"
7+
"os"
8+
"path/filepath"
9+
"sort"
10+
"strings"
11+
"time"
12+
13+
log "github.com/sirupsen/logrus"
14+
15+
"github.com/open-telemetry/opentelemetry-ebpf-profiler/libpf"
16+
)
17+
18+
type fileInfo struct {
19+
name string
20+
timestamp int64
21+
id uint64
22+
funcName string
23+
}
24+
25+
// Replay replays the stored data from benchDataDir.
26+
// The argument r is the reporter that will receive the replayed data.
27+
func Replay(ctx context.Context, benchDataDir string, rep Reporter) error {
28+
files, err := os.ReadDir(benchDataDir)
29+
if err != nil {
30+
return fmt.Errorf("failed to read directory %s: %v", benchDataDir, err)
31+
}
32+
33+
fileInfos := make([]fileInfo, 0, len(files))
34+
35+
for _, f := range files {
36+
if !strings.HasSuffix(f.Name(), ".json") {
37+
continue
38+
}
39+
40+
name := f.Name()
41+
// scan name for timestamp, counter and function name
42+
var timestamp int64
43+
var id uint64
44+
var funcName string
45+
if _, err = fmt.Sscanf(name, "%d_%x_%s", &timestamp, &id, &funcName); err != nil {
46+
log.Errorf("Failed to parse file name %s: %v", name, err)
47+
continue
48+
}
49+
funcName = strings.TrimSuffix(funcName, ".json")
50+
51+
fileInfos = append(fileInfos, fileInfo{
52+
name: name,
53+
timestamp: timestamp,
54+
id: id,
55+
funcName: funcName,
56+
})
57+
}
58+
59+
if len(fileInfos) == 0 {
60+
return nil
61+
}
62+
63+
// Sort fileInfos ascending by ID.
64+
sort.Slice(fileInfos, func(i, j int) bool {
65+
return fileInfos[i].id < fileInfos[j].id
66+
})
67+
68+
if fileInfos[0].funcName != "Start" {
69+
return fmt.Errorf("first function name must be \"Start\", instead it is \"%s\"",
70+
fileInfos[0].funcName)
71+
}
72+
73+
curTS := fileInfos[0].timestamp
74+
75+
// Replay the stored data
76+
for _, fi := range fileInfos[1:] {
77+
time.Sleep(time.Duration(fi.timestamp-curTS) * time.Nanosecond)
78+
curTS = fi.timestamp
79+
80+
switch fi.funcName {
81+
case "TraceEvent":
82+
var v traceEvent
83+
if err = dataFromFileInfo(benchDataDir, fi, &v); err == nil {
84+
rep.ReportTraceEvent(v.Trace, v.Meta)
85+
}
86+
case "CountForTrace":
87+
var v countForTrace
88+
if err = dataFromFileInfo(benchDataDir, fi, &v); err == nil {
89+
rep.ReportCountForTrace(v.TraceHash, v.Count, v.Meta)
90+
}
91+
case "FramesForTrace":
92+
var v libpf.Trace
93+
if err = dataFromFileInfo[libpf.Trace](benchDataDir, fi, &v); err == nil {
94+
rep.ReportFramesForTrace(&v)
95+
}
96+
case "FallbackSymbol":
97+
var v fallbackSymbol
98+
if err = dataFromFileInfo(benchDataDir, fi, &v); err == nil {
99+
rep.ReportFallbackSymbol(v.FrameID, v.Symbol)
100+
}
101+
case "FrameMetadata":
102+
var v frameMetadata
103+
if err = dataFromFileInfo(benchDataDir, fi, &v); err == nil {
104+
rep.FrameMetadata(v.FileID, v.AddressOrLine, v.LineNumber, v.FunctionOffset,
105+
v.FunctionName, v.FilePath)
106+
}
107+
case "Metrics":
108+
var v metrics
109+
if err = dataFromFileInfo[metrics](benchDataDir, fi, &v); err == nil {
110+
rep.ReportMetrics(v.Timestamp, v.IDs, v.Values)
111+
}
112+
default:
113+
err = fmt.Errorf("unsupported function name in file %s: %s", fi.name, fi.funcName)
114+
}
115+
116+
if err != nil {
117+
log.Errorf("Failed to replay data from file %s: %v", fi.name, err)
118+
}
119+
120+
if err = ctx.Err(); err != nil {
121+
return err
122+
}
123+
}
124+
125+
return nil
126+
}
127+
128+
func dataFromFileInfo[T any](dir string, fi fileInfo, data *T) error {
129+
pathName := filepath.Join(dir, fi.name)
130+
f, err := os.Open(pathName)
131+
if err != nil {
132+
return fmt.Errorf("failed to open file %s: %v", pathName, err)
133+
}
134+
defer f.Close()
135+
136+
if err = json.NewDecoder(f).Decode(data); err != nil {
137+
return fmt.Errorf("failed to decode JSON from file %s: %v", pathName, err)
138+
}
139+
140+
return nil
141+
}

0 commit comments

Comments
 (0)