Skip to content

Commit a9ebbee

Browse files
authored
Merge pull request #7 from compspec/add-close
feat: add close and perfetto support
2 parents 9aad887 + 885e5ad commit a9ebbee

File tree

8 files changed

+137
-1
lines changed

8 files changed

+137
-1
lines changed

pkg/fs/loopback.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616
// We need to implement a custom LoopbackNode Open function
1717
var _ = (fs.NodeOpener)((*CompatLoopbackNode)(nil))
1818
var _ = (fs.NodeLookuper)((*CompatLoopbackNode)(nil))
19+
var _ = (fs.NodeFlusher)((*CompatLoopbackNode)(nil))
1920

2021
type CompatLoopbackNode struct {
2122
fs.LoopbackNode
@@ -40,6 +41,14 @@ func (n *CompatLoopbackNode) Lookup(ctx context.Context, name string, out *fuse.
4041
return ch, 0
4142
}
4243

44+
// Flush is called for the close(2) call, could be multiple times. See:
45+
// https://github.com/hanwen/go-fuse/blob/aff07cbd88fef6a2561a87a1e43255516ba7d4b6/fs/api.go#L369
46+
func (n *CompatLoopbackNode) Flush(ctx context.Context, fh fs.FileHandle) syscall.Errno {
47+
p := n.path()
48+
logger.LogEvent("Close", p)
49+
return 0
50+
}
51+
4352
// https://github.com/hanwen/go-fuse/blob/f5b6d1b67f4a4d0f4c3c88b4491185b3685e8383/fs/loopback.go#L48
4453
func idFromStat(rootNode *fs.LoopbackRoot, st *syscall.Stat_t) fs.StableAttr {
4554
swapped := (uint64(st.Dev) << 32) | (uint64(st.Dev) >> 32)

python/compatlib/compatlib/client/__init__.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,13 @@ def get_parser():
8383
description="Build models for fs recordings",
8484
formatter_class=argparse.RawTextHelpFormatter,
8585
)
86+
perfetto = subparsers.add_parser(
87+
"to-perfetto",
88+
description="generate perfetto recording for events",
89+
formatter_class=argparse.RawTextHelpFormatter,
90+
)
8691

87-
for command in analyze_recording, plot_recording, run_models:
92+
for command in analyze_recording, plot_recording, run_models, perfetto:
8893
command.add_argument(
8994
"-d",
9095
"--outdir",
@@ -149,6 +154,8 @@ def help(return_code=0):
149154
from .plot_recording import main
150155
if args.command == "run-models":
151156
from .models import main
157+
if args.command == "to-perfetto":
158+
from .perfetto import main
152159

153160
# Pass on to the correct parser
154161
return_code = 0

python/compatlib/compatlib/client/analyze_recording.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ def main(args, parser, extra, subparser):
2323

2424
# A trace set is a collection of event files
2525
traceset = TraceSet(events)
26+
if not traceset.files:
27+
logger.exit("No event files were found.")
2628

2729
# Define output files and paths
2830
image_outdir = os.path.join(args.outdir, "img")

python/compatlib/compatlib/client/models.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ def main(args, parser, extra, subparser):
2424

2525
# A trace set is a collection of event files
2626
traceset = TraceSet(events)
27+
if not traceset.files:
28+
logger.exit("No event files were found.")
2729
df = traceset.to_dataframe()
2830

2931
# Define output files and paths
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import os
2+
3+
from compatlib.logger import logger
4+
from compatlib.traces import TraceSet
5+
6+
7+
def main(args, parser, extra, subparser):
8+
"""
9+
The "extra" here is the list of events
10+
11+
compatlib to-perfetto $(find ../recording -name *.out)
12+
"""
13+
# Extra events here should be one or more result event files to parse
14+
events = extra
15+
16+
# An output directory is required
17+
if not args.outdir:
18+
logger.exit("Please specify an output directory with -d/--outdir")
19+
20+
# Define output files and paths
21+
outfile = os.path.join(args.outdir, "perfetto-trace.pfw")
22+
logger.info(f"Output will be saved to: {outfile}")
23+
if not os.path.exists(args.outdir):
24+
os.makedirs(args.outdir)
25+
26+
# A trace set is a collection of event files
27+
traceset = TraceSet(events)
28+
if not traceset.files:
29+
logger.exit("No event files were found.")
30+
traceset.to_perfetto(outfile)

python/compatlib/compatlib/client/plot_recording.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ def main(args, parser, extra, subparser):
2424

2525
# A trace set is a collection of event files
2626
traceset = TraceSet(events)
27+
if not traceset.files:
28+
logger.exit("No event files were found.")
2729

2830
# Define output files and paths
2931
image_outdir = os.path.join(args.outdir, "img")

python/compatlib/compatlib/traces/traces.py

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import json
12
import os
23

34
import pandas
@@ -43,6 +44,84 @@ def check(self):
4344
events.append(filename)
4445
self.files = events
4546

47+
def to_perfetto(self, outfile):
48+
"""
49+
Generate perfetto json output file for events.
50+
51+
# See format at:
52+
# https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU/preview?tab=t.0
53+
"""
54+
df = self.to_dataframe()
55+
56+
# Give an arbitrary id to each filename
57+
ids = {}
58+
count = 0
59+
60+
# We will thread the pids as the different runs of lammps,
61+
# and the thread ids as the library ids
62+
63+
# TODO: this can be cleaned up and moved into own perfetto.py
64+
# I won't do this until I've added the close event and tested again
65+
with open(outfile, "w") as fd:
66+
fd.write("[")
67+
68+
for pid, tag in enumerate(df.basename.unique()):
69+
subset = df[df.basename == tag]
70+
71+
# Subtract the minimum timestamp for each run so we always start at 0
72+
start_time = subset.timestamp.min()
73+
subset.loc[:, "timestamp"] = subset.timestamp - start_time
74+
75+
for row in subset.iterrows():
76+
# Get a faux process id
77+
if row[1].normalized_path not in ids:
78+
ids[row[1].normalized_path] = count
79+
count += 1
80+
identifier = ids[row[1].normalized_path]
81+
if row[1].ms_in_state is not None:
82+
fd.write(
83+
json.dumps(
84+
{
85+
"name": row[1].normalized_path,
86+
"pid": pid,
87+
"tid": identifier,
88+
"ts": row[1].timestamp,
89+
"dur": row[1].ms_in_state,
90+
# Beginning of phase event
91+
"ph": "X",
92+
"cat": tag,
93+
"args": {
94+
"name": row[1].normalized_path,
95+
"path": row[1].path,
96+
"result": row[1].basename,
97+
"function": row[1].function,
98+
},
99+
}
100+
)
101+
)
102+
else:
103+
fd.write(
104+
json.dumps(
105+
{
106+
"name": row[1].normalized_path,
107+
"pid": pid,
108+
"tid": identifier,
109+
"ts": row[1].timestamp,
110+
# Beginning of phase event
111+
"ph": "B",
112+
"cat": tag,
113+
"args": {
114+
"name": row[1].normalized_path,
115+
"path": row[1].path,
116+
"result": row[1].basename,
117+
"function": row[1].function,
118+
},
119+
}
120+
)
121+
)
122+
fd.write("\n")
123+
fd.write("]")
124+
46125
def iter_events(self, operation="Open"):
47126
"""
48127
Iterate through files and yield event object

python/compatlib/compatlib/utils.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@ def read_file(filename):
2424
return content
2525

2626

27+
def write_json(content, filename):
28+
with open(filename, "w") as fd:
29+
fd.write(json.dumps(content, indent=4))
30+
31+
2732
def write_file(content, filename, executable=False):
2833
with open(filename, "w") as fd:
2934
fd.write(content)

0 commit comments

Comments
 (0)