Skip to content

Commit 8032d67

Browse files
ethercflowAstroProfundis
authored andcommitted
Add ability to collect direct reclaim latency trace with ftrace (#15)
* Add ability to collect direct reclaim latency trace with ftrace * Fix various issues in utility functions
1 parent 02e982c commit 8032d67

File tree

7 files changed

+169
-12
lines changed

7 files changed

+169
-12
lines changed

insight.py

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,11 @@
3131
from measurement.files import logfiles
3232
from measurement.process import meta as proc_meta
3333
from measurement.tidb import pdctl
34+
from measurement.ftrace import ftrace
3435

3536

3637
class Insight():
38+
cwd = util.cwd()
3739
# data output dir
3840
outdir = "data"
3941
full_outdir = ""
@@ -43,14 +45,18 @@ class Insight():
4345
insight_logfiles = None
4446
insight_configfiles = None
4547
insight_pdctl = None
48+
insight_trace = None
4649

4750
def __init__(self, args):
48-
if args.output:
49-
self.outdir = args.output
5051
if not args.alias:
5152
self.alias = util.get_hostname()
52-
self.full_outdir = fileutils.create_dir(
53-
os.path.join(self.outdir, self.alias))
53+
if args.output and util.is_abs_path(args.output):
54+
self.outdir = args.output
55+
self.full_outdir = fileutils.create_dir(
56+
os.path.join(self.outdir, self.alias))
57+
else:
58+
self.full_outdir = fileutils.create_dir(
59+
os.path.join(self.cwd, self.outdir, self.alias))
5460
logging.debug("Output directory is: %s" % self.full_outdir)
5561

5662
# data collected by `collector`
@@ -126,6 +132,22 @@ def run_perf(self, args):
126132
self.insight_perf = perf.InsightPerf(options=args)
127133
self.insight_perf.run(self.full_outdir)
128134

135+
def run_ftrace(self, args):
136+
if not args.ftrace:
137+
logging.debug("Ignoring collecting of ftrace data.")
138+
return
139+
# perf requires root priviledge
140+
if not util.is_root_privilege():
141+
logging.fatal("It's required to run ftrace with root priviledge.")
142+
return
143+
144+
if args.ftracepoint:
145+
self.insight_ftrace = ftrace.InsightFtrace(self.cwd, args)
146+
self.insight_ftrace.run(self.full_outdir)
147+
else:
148+
logging.debug("Ignoring collecting of ftrace data, no tracepoint is chose.")
149+
150+
129151
def get_datadir_size(self):
130152
# du requires root priviledge to check data-dir
131153
if not util.is_root_privilege():
@@ -242,6 +264,9 @@ def read_pdctl(self, args):
242264
# read and save `pd-ctl` info
243265
insight.read_pdctl(args)
244266

267+
# save ftrace data
268+
insight.run_ftrace(args)
269+
245270
# compress all output to tarball
246271
if args.compress:
247272
fileutils.compress_tarball(insight.full_outdir, insight.alias)

measurement/files/fileutils.py

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,9 @@
33

44
import logging
55
import os
6-
import sys
76

87
from measurement import util
98

10-
# get a numeric Python version
11-
python_version = sys.version_info[0] + sys.version_info[1] / 10
12-
13-
149
# read data from file
1510
def read_file(filename):
1611
data = None
@@ -54,7 +49,7 @@ def create_dir(path):
5449
def list_dir(path):
5550
file_list = []
5651
try:
57-
if python_version >= 3.5:
52+
if util.python_version() >= 3.5:
5853
for entry in os.scandir(path):
5954
file_list.append("%s/%s" % (path, entry.name))
6055
else:

measurement/ftrace/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# -*- coding: utf-8 -*-

measurement/ftrace/ftrace.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# -*- coding: utf-8 -*-
2+
# Collect stack trace with `ftrace`
3+
4+
from measurement.ftrace.mem import drtracer
5+
from measurement.files import fileutils
6+
7+
8+
class InsightFtrace():
9+
# options about tracer
10+
ftrace_options = {}
11+
cwd = ""
12+
13+
# output dir
14+
data_dir = "ftracedata"
15+
16+
def __init__(self, cwd, options):
17+
self.cwd = cwd
18+
self.ftrace_options = vars(options)
19+
20+
def run(self, outputdir=None):
21+
ftrace_outputdir = fileutils.build_full_output_dir(
22+
basedir=outputdir, subdir=self.data_dir)
23+
24+
if not ftrace_outputdir:
25+
return
26+
27+
tracepoint = self.ftrace_options["ftracepoint"]
28+
if tracepoint == "dr":
29+
tracer = drtracer.DirectReclaimTracer(self.ftrace_options)
30+
tracer.save_trace(self.cwd, ftrace_outputdir)
31+
else:
32+
logging.debug("Tracepiont %s is not supported." % tracepoint)

measurement/ftrace/mem/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# -*- coding: utf-8 -*-

measurement/ftrace/mem/drtracer.py

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
# -*- coding: utf-8 -*-
2+
# Trace direct reclaim latency.
3+
4+
import os
5+
import logging
6+
7+
from measurement import util
8+
from measurement.files import fileutils
9+
10+
11+
class DirectReclaimTracer():
12+
# options about tracer
13+
ftrace_options = {}
14+
15+
# output dir
16+
data_file = "mem/drtrace"
17+
18+
# tracefs mount point
19+
tracefs = "/sys/kernel/debug/tracing"
20+
direct_reclaim_begin = "events/vmscan/mm_vmscan_direct_reclaim_begin/enable"
21+
direct_reclaim_end = "events/vmscan/mm_vmscan_direct_reclaim_end/enable"
22+
23+
def __init__(self, options={}):
24+
self.ftrace_options = options
25+
26+
def save_trace(self, cwd, outputdir=None):
27+
_, stderr = util.run_cmd(["cd", self.tracefs])
28+
if stderr:
29+
logging.fatal("""ERROR: accessing tracing. Root user? Kernel has FTRACE?
30+
debugfs mounted? (mount -t debugfs debugfs /sys/kernel/debug)""")
31+
return
32+
33+
if not outputdir:
34+
logging.fatal("ERROR: please give a dir to save trace data")
35+
return
36+
37+
util.chdir(self.tracefs)
38+
39+
# setup trace, set opt
40+
_, stderr = util.run_cmd(["echo nop > current_tracer"], shell=True)
41+
if stderr:
42+
logging.fatal("ERROR: reset current_tracer failed")
43+
os.chdir(cwd)
44+
return
45+
46+
bufsize_kb = self.ftrace_options["ftrace_bufsize"] if "ftrace_bufsize" in \
47+
self.ftrace_options and self.ftrace_options["ftrace_bufsize"] else "4096"
48+
_, stderr = util.run_cmd(["echo %s > buffer_size_kb" % bufsize_kb], shell=True)
49+
if stderr:
50+
logging.fatal("ERROR: set bufsize_kb failed")
51+
os.chdir(cwd)
52+
return
53+
54+
# begin tracing
55+
for event in [self.direct_reclaim_begin, self.direct_reclaim_end]:
56+
_, stderr = util.run_cmd(["echo 1 > %s" % event], shell=True)
57+
if stderr:
58+
logging.fatal("ERROR: enable %s tracepoint failed" % event)
59+
os.chdir(cwd)
60+
return
61+
62+
# collect trace
63+
time = self.ftrace_options["ftrace_time"] if "ftrace_time" in \
64+
self.ftrace_options and self.ftrace_options["ftrace_time"] else 60
65+
util.run_cmd_for_a_while(["cat trace_pipe > %s/%s" %(outputdir, self.data_file)], time, shell=True)
66+
67+
# End tracing
68+
for event in [self.direct_reclaim_begin, self.direct_reclaim_end]:
69+
_, stderr = util.run_cmd(["echo 0 > %s" % event], shell=True)
70+
if stderr:
71+
logging.fatal("ERROR: disable %s tracepoint failed" % event)
72+
os.chdir(cwd)
73+
return
74+
75+
os.chdir(cwd)

measurement/util.py

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
import argparse
55
import logging
66
import os
7+
import sys
8+
import time
79

810
from subprocess import Popen, PIPE
911
try:
@@ -30,11 +32,24 @@ def cwd():
3032
return os.getcwd()
3133

3234

33-
def run_cmd(cmd):
34-
p = Popen(cmd, stdout=PIPE, stderr=PIPE)
35+
def chdir(nwd):
36+
return os.chdir(nwd)
37+
38+
39+
def is_abs_path(path):
40+
return os.path.isabs(path)
41+
42+
def run_cmd(cmd, shell=False):
43+
p = Popen(cmd, shell=shell, stdout=PIPE, stderr=PIPE)
3544
return p.communicate()
3645

3746

47+
def run_cmd_for_a_while(cmd, duration, shell=False):
48+
p = Popen(cmd, shell=shell)
49+
time.sleep(duration)
50+
p.kill()
51+
52+
3853
def parse_cmdline(cmdline):
3954
result = {}
4055
try:
@@ -114,6 +129,15 @@ def parse_insight_opts():
114129
parser.add_argument("-v", "--verbose", action="store_true", default=False,
115130
help="Print verbose output.")
116131

132+
parser.add_argument("-f", "--ftrace", action="store_true", default=False,
133+
help="Collect trace info using ftrace. Disabled by default.")
134+
parser.add_argument("--ftracepoint", action="store", default=None,
135+
help="Tracepoint to be traced (only support to trace direct reclaim latency).")
136+
parser.add_argument("--ftrace-time", type=int, action="store", default=None,
137+
help="Time period of ftrace recording, in seconds (default 60s).")
138+
parser.add_argument("--ftrace-bufsize", action="store", default=None,
139+
help="Ftrace ring buffer size in kb (default 4096 kb).")
140+
117141
return parser.parse_args()
118142

119143

@@ -146,3 +170,7 @@ def get_hostname():
146170
# This function is merely used, so only import socket package when necessary
147171
import socket
148172
return socket.gethostname()
173+
174+
def python_version():
175+
# get a numeric Python version
176+
return sys.version_info[0] + sys.version_info[1] * 0.1

0 commit comments

Comments
 (0)