From d6079c62819b4c022fe23e5101ca5382fbedb10f Mon Sep 17 00:00:00 2001 From: Nicolas Catania Date: Wed, 3 Jun 2009 11:08:47 -0700 Subject: Added main with command line args to plot the data. New shell script to generate scalability data. Fixed copyright notice. Fixed incomplete metadata issue when debugfs was not mounted. --- tests/sdcard/plot_sdcard.py | 295 +++++++++++++++++++++++++++++------------ tests/sdcard/profile_sdcard.sh | 64 +++++++++ tests/sdcard/stopwatch.cpp | 2 +- 3 files changed, 278 insertions(+), 83 deletions(-) create mode 100755 tests/sdcard/profile_sdcard.sh diff --git a/tests/sdcard/plot_sdcard.py b/tests/sdcard/plot_sdcard.py index 10ee00ba..19b83c3c 100755 --- a/tests/sdcard/plot_sdcard.py +++ b/tests/sdcard/plot_sdcard.py @@ -1,49 +1,70 @@ #!/usr/bin/python2.5 # -# Copyright 2009 Google Inc. All Rights Reserved. +# Copyright 2009, The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + """plot_sdcard: A module to plot the results of an sdcard perf test. Requires Gnuplot python v 1.8 Typical usage: + -t x axis is time + -i x axis is iteration + -p profile data generated by profile_sdcard.sh + +./plot_sdcard.py -t /tmp/data.txt +./plot_sdcard.py -i /tmp/data.txt +./plot_sdcard.py -p -python +python interpreter >>> import plot_sdcard as p ->>> (metadata, data) = p.parse('/tmp/data.txt') ->>> p.plotIterations(metadata, data) ->>> p.plotTimes(metadata, data) +>>> (metadata, data) = p.Parse('/tmp/data.txt') +>>> p.PlotIterations(metadata, data) +>>> p.PlotTimes(metadata, data) """ -#TODO: provide a main so we can pipe the result from the run -#TODO: more comments... - -import Gnuplot -from numpy import * -import sys -import re +import getopt from itertools import izip +import re +import sys +import Gnuplot +import numpy + class DataSet(object): + """Dataset holds the summary and data (time,value pairs).""" + def __init__(self, line): - res = re.search('# StopWatch ([\w]+) total/cumulative duration ([0-9.]+)\. Samples: ([0-9]+)', line) + res = re.search(('# StopWatch ([\w]+) total/cumulative ' + 'duration ([0-9.]+). Samples: ([0-9]+)'), line) self.time = [] self.data = [] self.name = res.group(1) self.duration = float(res.group(2)) self.iteration = int(res.group(3)) - print "Name: %s Duration: %f Iterations: %d" % (self.name, self.duration, self.iteration) self.summary = re.match('([a-z_]+)_total', self.name) def __repr__(self): return str(zip(self.time, self.data)) - def add(self, time, value): + def Add(self, time, value): self.time.append(time) self.data.append(value) - def rescaleTo(self, length): + def RescaleTo(self, length): factor = len(self.data) / length if factor > 1: @@ -51,7 +72,7 @@ class DataSet(object): new_data = [] accum = 0.0 idx = 1 - for t,d in izip(self.time, self.data): + for t, d in izip(self.time, self.data): accum += d if idx % factor == 0: new_time.append(t) @@ -73,7 +94,7 @@ class Metadata(object): self.duration = 0.0 self.complete = False - def parse(self, line): + def Parse(self, line): if line.startswith('# Kernel:'): self.kernel = re.search('Linux version ([0-9.]+-[0-9]+)', line).group(1) elif line.startswith('# Command:'): @@ -84,74 +105,29 @@ class Metadata(object): elif line.startswith('# Iterations'): self.iterations = int(re.search('# Iterations: ([0-9]+)', line).group(1)) elif line.startswith('# Fadvise'): - self.fadvise = int(re.search('# Fadvise: ([\w]+)', line).group(1)) - elif line.startswith("# Sched"): + self.fadvise = re.search('# Fadvise: ([\w]+)', line).group(1) + elif line.startswith('# Sched'): self.sched = re.search('# Sched features: ([\w]+)', line).group(1) self.complete = True - def asTitle(self): - return "%s-duration:%f\\n-%s\\n%s" % (self.kernel, self.duration, self.command_line, self.sched) + def AsTitle(self): + return '%s-duration:%f\\n-%s\\n%s' % ( + self.kernel, self.duration, self.command_line, self.sched) - def updateWith(self, dataset): + def UpdateWith(self, dataset): self.duration = max(self.duration, dataset.duration) self.name = dataset.name -def plotIterations(metadata, data): - gp = Gnuplot.Gnuplot(persist = 1) - gp('set data style lines') - gp.clear() - gp.xlabel("iterations") - gp.ylabel("duration in second") - gp.title(metadata.asTitle()) - styles = {} - line_style = 1 - - for dataset in data: - dataset.rescaleTo(metadata.iterations) - x = arange(len(dataset.data), dtype='int_') - if not dataset.name in styles: - styles[dataset.name] = line_style - line_style += 1 - d = Gnuplot.Data(x, dataset.data, - title=dataset.name, - with_='lines ls %d' % styles[dataset.name]) - else: # no need to repeat a title that exists already. - d = Gnuplot.Data(x, dataset.data, - with_='lines ls %d' % styles[dataset.name]) - - gp.replot(d) - gp.hardcopy('/tmp/%s-%s-%f.png' % (metadata.name, metadata.kernel, metadata.duration), terminal='png') - -def plotTimes(metadata, data): - gp = Gnuplot.Gnuplot(persist = 1) - gp('set data style impulses') - gp('set xtics 1') - gp.clear() - gp.xlabel("seconds") - gp.ylabel("duration in second") - gp.title(metadata.asTitle()) - styles = {} - line_style = 1 - - for dataset in data: - #dataset.rescaleTo(metadata.iterations) - x = array(dataset.time, dtype='float_') - if not dataset.name in styles: - styles[dataset.name] = line_style - line_style += 1 - d = Gnuplot.Data(x, dataset.data, - title=dataset.name, - with_='impulses ls %d' % styles[dataset.name]) - else: # no need to repeat a title that exists already. - d = Gnuplot.Data(x, dataset.data, - with_='impulses ls %d' % styles[dataset.name]) +def Parse(filename): + """Parse a file with the collected data. - gp.replot(d) - gp.hardcopy('/tmp/%s-%s-%f.png' % (metadata.name, metadata.kernel, metadata.duration), terminal='png') + The data must be in 2 rows (x,y). + Args: + filename: Full path to the file. + """ -def parse(filename): f = open(filename, 'r') metadata = Metadata() @@ -164,16 +140,16 @@ def parse(filename): if not line: continue if not metadata.complete: - metadata.parse(line) + metadata.Parse(line) continue if re.match('[a-z_]', line): continue - if line.startswith('# StopWatch'): # Start of a new dataset + if line.startswith('# StopWatch'): # Start of a new dataset if dataset: if dataset.summary: - metadata.updateWith(dataset) + metadata.UpdateWith(dataset) else: data.append(dataset) @@ -187,14 +163,169 @@ def parse(filename): try: (time, value) = line.split(None, 1) except ValueError: - print "skipping line %d: %s" % (num, line) + print 'skipping line %d: %s' % (num, line) continue if dataset and not dataset.summary: - dataset.add(float(time), float(value)) + dataset.Add(float(time), float(value)) - except Exception, e: - print "Error parsing line %d" % num, sys.exc_info()[0] + except Exception: + print 'Error parsing line %d' % num, sys.exc_info()[0] raise data.append(dataset) + if not metadata.complete: + print """Error missing metadata. Did you mount debugfs? + [adb shell mount -t debugfs none /sys/kernel/debug]""" + sys.exit(1) return (metadata, data) + + +def PlotIterations(metadata, data): + """Plot the duration of the ops against iteration. + + If you are plotting data with widely different runtimes you probably want to + use PlotTimes instead. + + For instance when readers and writers are in the same mix, the + readers will go thru 100 iterations much faster than the + writers. The load test tries to be smart about that but the final + iterations of the writers will likely be done w/o any influence from + the readers. + + Args: + metadata: For the graph's title. + data: pair of to be plotted. + """ + + gp = Gnuplot.Gnuplot(persist=1) + gp('set data style lines') + gp.clear() + gp.xlabel('iterations') + gp.ylabel('duration in second') + gp.title(metadata.AsTitle()) + styles = {} + line_style = 1 + + for dataset in data: + dataset.RescaleTo(metadata.iterations) + x = numpy.arange(len(dataset.data), dtype='int_') + if not dataset.name in styles: + styles[dataset.name] = line_style + line_style += 1 + d = Gnuplot.Data(x, dataset.data, + title=dataset.name, + with_='lines ls %d' % styles[dataset.name]) + else: # no need to repeat a title that exists already. + d = Gnuplot.Data(x, dataset.data, + with_='lines ls %d' % styles[dataset.name]) + + gp.replot(d) + gp.hardcopy('/tmp/%s-%s-%f.png' % + (metadata.name, metadata.kernel, metadata.duration), + terminal='png') + + +def PlotTimes(metadata, data): + """Plot the duration of the ops against time elapsed. + + Args: + metadata: For the graph's title. + data: pair of to be plotted. + """ + + gp = Gnuplot.Gnuplot(persist=1) + gp('set data style impulses') + gp('set xtics 1') + gp.clear() + gp.xlabel('seconds') + gp.ylabel('duration in second') + gp.title(metadata.AsTitle()) + styles = {} + line_style = 1 + + for dataset in data: + x = numpy.array(dataset.time, dtype='float_') + if not dataset.name in styles: + styles[dataset.name] = line_style + line_style += 1 + d = Gnuplot.Data(x, dataset.data, + title=dataset.name, + with_='impulses ls %d' % styles[dataset.name]) + else: # no need to repeat a title that exists already. + d = Gnuplot.Data(x, dataset.data, + with_='impulses ls %d' % styles[dataset.name]) + + gp.replot(d) + gp.hardcopy('/tmp/%s-%s-%f.png' % + (metadata.name, metadata.kernel, metadata.duration), + terminal='png') + + +def PlotProfile(): + """Plot the time of a run against the number of processes.""" + (metadata, data) = Parse('/tmp/sdcard-scalability.txt') + gp = Gnuplot.Gnuplot(persist=1) + gp('set data style impulses') + gp('set xtics 1') + gp('set pointsize 2') + gp.clear() + gp.xlabel('writer process') + gp.ylabel('duration in second') + gp.title(metadata.AsTitle()) + + dataset = data[0] + x = numpy.array(dataset.time, dtype='int_') + d = Gnuplot.Data(x, dataset.data, + title=dataset.name, + with_='linespoints') + gp.replot(d) + gp.hardcopy('/tmp/%s-%s-%f.png' % + (metadata.name, metadata.kernel, metadata.duration), + terminal='png') + + +def Usage(): + """Print this module's usage.""" + print """ + To plot the result using the iter number of the x axis: + + plot_sdcard.py -i /tmp/data.txt + + To plot the result using time for the x axis: + + plot_sdcard.py -t /tmp/data.txt + + To plot the result from the profiler: + + profile_sdcard.sh + plot_sdcard.py -p + + """ + sys.exit(2) + + +def main(argv): + try: + (optlist, args) = getopt.getopt(argv[1:], + 'itp', ['iteration', 'time', 'profile']) + except getopt.GetoptError, err: + print str(err) + Usage() + + for flag, val in optlist: + if flag in ('-i', '--iteration'): + (metadata, data) = Parse(args[0]) + PlotIterations(metadata, data) + sys.exit(0) + elif flag in ('-t', '--time'): + (metadata, data) = Parse(args[0]) + PlotTimes(metadata, data) + sys.exit(0) + elif flag in ('-p', '--profile'): + PlotProfile() + sys.exit(0) + Usage() + + +if __name__ == '__main__': + main(sys.argv) diff --git a/tests/sdcard/profile_sdcard.sh b/tests/sdcard/profile_sdcard.sh new file mode 100755 index 00000000..4629c910 --- /dev/null +++ b/tests/sdcard/profile_sdcard.sh @@ -0,0 +1,64 @@ +#!/bin/bash +# Copyright 2009, The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Run a bunch of test on the sdcard to establish a performance profile. + +print_kernel() { + adb shell cat /proc/version +} +print_sched_features() { + adb shell cat /sys/kernel/debug/sched_features +} + +# Use dd to get the raw speed of the card +block_level() { + true +} + +# Time to run a test vs number of processes +scalability() { + local file="/tmp/sdcard-scalability.txt" + rm -f ${file} + echo "# Scalability tests" | tee -a ${file} + echo "# Kernel: $(print_kernel)" | tee -a ${file} + echo "# Sched features: $(print_sched_features)" | tee -a ${file} + echo "# StopWatch scalability total/cumulative duration 0.0 Samples: 1" | tee -a ${file} + echo "# Process Time" | tee -a ${file} + for p in $(seq 1 8); do + adb shell sdcard_perf_test --test=write --procnb=${p} --size=1000 --chunk-size=100 --iterations=50 >/tmp/tmp-sdcard.txt + local t=$(grep 'write_total' /tmp/tmp-sdcard.txt | tail -n 1 | cut -f 6 -d ' ') + echo "$p $t" | tee -a ${file} + done + +} + +# Readers and writers should not starve each others. +fairness() { + # Check readers finished before writers. + # Find the time of the last read op. + # Count how many writes and how many read happend + # during that period, do the ratio. + true +} + +####################################################################### +# MAIN + +echo "Make sure debugfs is mounted on the device." +block_level +scalability +fairness + + diff --git a/tests/sdcard/stopwatch.cpp b/tests/sdcard/stopwatch.cpp index 12fe8f1f..8207430d 100644 --- a/tests/sdcard/stopwatch.cpp +++ b/tests/sdcard/stopwatch.cpp @@ -117,7 +117,7 @@ void StopWatch::sprint(char **str, size_t *size) if (kVerbose) SNPRINTF_OR_RETURN(*str, *size, "# Got %d samples for %s\n", mDataLen, mName); processSamples(); - SNPRINTF_OR_RETURN(*str, *size, "# StopWatch %s total/cumulative duration %f. Samples: %d\n", + SNPRINTF_OR_RETURN(*str, *size, "# StopWatch %s total/cumulative duration %f Samples: %d\n", mName, mDuration, mNum); printThroughput(str, size); printAverageMinMax(str, size); -- cgit v1.2.3