Skip to content

Commit 660994a

Browse files
committed
---
yaml --- r: 345151 b: refs/heads/master c: 957076f h: refs/heads/master i: 345149: 5648666 345147: 425269e 345143: 3c2d063 345135: 5b204e3 345119: d0a53df 345087: 88749b1
1 parent 4908a56 commit 660994a

File tree

4 files changed

+152
-12
lines changed

4 files changed

+152
-12
lines changed

[refs]

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
---
2-
refs/heads/master: 76376696451bb1cfc6fb9170000192f2867f7657
2+
refs/heads/master: 957076fa0e559e17422afa9fdd17003638112831
33
refs/heads/master-next: 203b3026584ecad859eb328b2e12490099409cd5
44
refs/tags/osx-passed: b6b74147ef8a386f532cf9357a1bde006e552c54
55
refs/tags/swift-2.2-SNAPSHOT-2015-12-01-a: 6bb18e013c2284f2b45f5f84f2df2887dc0f7dea

trunk/utils/jobstats/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,4 @@
2020
__versioninfo__ = (0, 1, 0)
2121
__version__ = '.'.join(str(v) for v in __versioninfo__)
2222

23-
from .jobstats import JobStats, load_stats_dir, merge_all_jobstats # noqa
23+
from .jobstats import JobStats, JobProfs, load_stats_dir, merge_all_jobstats, list_stats_dir_profiles # noqa

trunk/utils/jobstats/jobstats.py

Lines changed: 89 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,20 +23,14 @@
2323
import re
2424

2525

26-
class JobStats(object):
27-
"""Object holding the stats of a single job run during a compilation,
28-
corresponding to a single JSON file produced by a single job process
29-
passed -stats-output-dir."""
26+
class JobData(object):
3027

31-
def __init__(self, jobkind, jobid, module, start_usec, dur_usec,
32-
jobargs, stats):
28+
def __init__(self, jobkind, jobid, module, jobargs):
3329
self.jobkind = jobkind
3430
self.jobid = jobid
3531
self.module = module
36-
self.start_usec = start_usec
37-
self.dur_usec = dur_usec
3832
self.jobargs = jobargs
39-
self.stats = stats
33+
(self.input, self.triple, self.out, self.opt) = jobargs[0:4]
4034

4135
def is_driver_job(self):
4236
"""Return true iff self measures a driver job"""
@@ -46,6 +40,29 @@ def is_frontend_job(self):
4640
"""Return true iff self measures a frontend job"""
4741
return self.jobkind == 'frontend'
4842

43+
44+
class JobProfs(JobData):
45+
"""Object denoting the profile of a single job run during a compilation,
46+
corresponding to a single directory of profiles produced by a single
47+
job process passed -stats-output-dir."""
48+
49+
def __init__(self, jobkind, jobid, module, jobargs, profiles):
50+
self.profiles = profiles
51+
super(JobProfs, self).__init__(jobkind, jobid, module, jobargs)
52+
53+
54+
class JobStats(JobData):
55+
"""Object holding the stats of a single job run during a compilation,
56+
corresponding to a single JSON file produced by a single job process
57+
passed -stats-output-dir."""
58+
59+
def __init__(self, jobkind, jobid, module, start_usec, dur_usec,
60+
jobargs, stats):
61+
self.start_usec = start_usec
62+
self.dur_usec = dur_usec
63+
self.stats = stats
64+
super(JobStats, self).__init__(jobkind, jobid, module, jobargs)
65+
4966
def driver_jobs_ran(self):
5067
"""Return the count of a driver job's ran sub-jobs"""
5168
assert(self.is_driver_job())
@@ -190,6 +207,11 @@ def to_lnt_test_obj(self, args):
190207
r"-(?P<pid>\d+)(-.*)?.json$")
191208
FILEPAT = re.compile(FILEPATSTR)
192209

210+
PROFILEPATSTR = (r"^profile-(?P<start>\d+)-swift-(?P<kind>\w+)-" +
211+
AUXPATSTR +
212+
r"-(?P<pid>\d+)(-.*)?.dir$")
213+
PROFILEPAT = re.compile(PROFILEPATSTR)
214+
193215

194216
def match_auxpat(s):
195217
m = AUXPAT.match(s)
@@ -215,6 +237,64 @@ def match_filepat(s):
215237
return None
216238

217239

240+
def match_profilepat(s):
241+
m = PROFILEPAT.match(s)
242+
if m is not None:
243+
return m.groupdict()
244+
else:
245+
return None
246+
247+
248+
def find_profiles_in(profiledir, select_stat=[]):
249+
sre = re.compile('.*' if len(select_stat) == 0 else
250+
'|'.join(select_stat))
251+
profiles = None
252+
for profile in os.listdir(profiledir):
253+
if profile.endswith(".svg"):
254+
continue
255+
if sre.search(profile) is None:
256+
continue
257+
fullpath = os.path.join(profiledir, profile)
258+
s = os.stat(fullpath)
259+
if s.st_size != 0:
260+
if profiles is None:
261+
profiles = dict()
262+
try:
263+
(counter, profiletype) = os.path.splitext(profile)
264+
# drop leading period from extension
265+
profiletype = profiletype[1:]
266+
if profiletype not in profiles:
267+
profiles[profiletype] = dict()
268+
profiles[profiletype][counter] = fullpath
269+
except:
270+
pass
271+
return profiles
272+
273+
274+
def list_stats_dir_profiles(path, select_module=[], select_stat=[], **kwargs):
275+
"""Finds all stats-profiles in path, returning list of JobProfs objects"""
276+
jobprofs = []
277+
for root, dirs, files in os.walk(path):
278+
for d in dirs:
279+
mg = match_profilepat(d)
280+
if not mg:
281+
continue
282+
# NB: "pid" in fpat is a random number, not unix pid.
283+
jobkind = mg['kind']
284+
jobid = int(mg['pid'])
285+
module = mg["module"]
286+
if len(select_module) != 0 and module not in select_module:
287+
continue
288+
jobargs = [mg["input"], mg["triple"], mg["out"], mg["opt"]]
289+
290+
e = JobProfs(jobkind=jobkind, jobid=jobid,
291+
module=module, jobargs=jobargs,
292+
profiles=find_profiles_in(os.path.join(root, d),
293+
select_stat))
294+
jobprofs.append(e)
295+
return jobprofs
296+
297+
218298
def load_stats_dir(path, select_module=[], select_stat=[],
219299
exclude_timers=False, merge_timers=False, **kwargs):
220300
"""Loads all stats-files found in path into a list of JobStats objects"""

trunk/utils/process-stats-dir.py

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,9 @@
2828
import urllib2
2929
from collections import namedtuple
3030
from operator import attrgetter
31-
from jobstats import load_stats_dir, merge_all_jobstats
31+
32+
from jobstats import (list_stats_dir_profiles,
33+
load_stats_dir, merge_all_jobstats)
3234

3335

3436
MODULE_PAT = re.compile('^(\w+)\.')
@@ -492,6 +494,56 @@ def evaluate_delta(args):
492494
return 1
493495

494496

497+
def render_profiles(args):
498+
flamegraph_pl = args.flamegraph_script
499+
if flamegraph_pl is None:
500+
import distutils.spawn
501+
flamegraph_pl = distutils.spawn.find_executable("flamegraph.pl")
502+
if flamegraph_pl is None:
503+
print("Need flamegraph.pl in $PATH, or pass --flamegraph-script")
504+
505+
vargs = vars_of_args(args)
506+
for statsdir in args.remainder:
507+
jobprofs = list_stats_dir_profiles(statsdir, **vargs)
508+
index_path = os.path.join(statsdir, "profile-index.html")
509+
all_profile_types = set([k for keys in [j.profiles.keys()
510+
for j in jobprofs
511+
if j.profiles is not None]
512+
for k in keys])
513+
with open(index_path, "wb") as index:
514+
for ptype in all_profile_types:
515+
index.write("<h2>Profile type: " + ptype + "</h2>\n")
516+
index.write("<ul>\n")
517+
for j in jobprofs:
518+
if j.is_frontend_job():
519+
index.write(" <li>" +
520+
("Module %s :: %s" %
521+
(j.module, " ".join(j.jobargs))) + "\n")
522+
index.write(" <ul>\n")
523+
profiles = sorted(j.profiles.get(ptype, {}).items())
524+
for counter, path in profiles:
525+
title = ("Module: %s, File: %s, "
526+
"Counter: %s, Profile: %s" %
527+
(j.module, j.input, counter, ptype))
528+
subtitle = j.triple + ", -" + j.opt
529+
svg = os.path.abspath(path + ".svg")
530+
with open(path) as p, open(svg, "wb") as g:
531+
import subprocess
532+
print("Building flamegraph " + svg)
533+
subprocess.check_call([flamegraph_pl,
534+
"--title", title,
535+
"--subtitle", subtitle],
536+
stdin=p, stdout=g)
537+
link = ("<tt><a href=\"file://%s\">%s</a></tt>" %
538+
(svg, counter))
539+
index.write(" <li>" + link + "\n")
540+
index.write(" </ul>\n")
541+
index.write(" </li>\n")
542+
if args.browse_profiles:
543+
import webbrowser
544+
webbrowser.open_new_tab("file://" + os.path.abspath(index_path))
545+
546+
495547
def main():
496548
parser = argparse.ArgumentParser()
497549
parser.add_argument("--verbose", action="store_true",
@@ -596,6 +648,12 @@ def main():
596648
help="evaluate an expression of stat-names")
597649
modes.add_argument("--evaluate-delta", type=str, default=None,
598650
help="evaluate an expression of stat-deltas")
651+
modes.add_argument("--render-profiles", action="store_true",
652+
help="render any profiles to SVG flamegraphs")
653+
parser.add_argument("--flamegraph-script", type=str, default=None,
654+
help="path to flamegraph.pl")
655+
parser.add_argument("--browse-profiles", action="store_true",
656+
help="open web browser tabs with rendered profiles")
599657
parser.add_argument('remainder', nargs=argparse.REMAINDER,
600658
help="stats-dirs to process")
601659

@@ -622,6 +680,8 @@ def main():
622680
return evaluate(args)
623681
elif args.evaluate_delta:
624682
return evaluate_delta(args)
683+
elif args.render_profiles:
684+
return render_profiles(args)
625685
return None
626686

627687

0 commit comments

Comments
 (0)