OSDN Git Service

Added main with command line args to plot the data.
authorNicolas Catania <niko@google.com>
Wed, 3 Jun 2009 18:08:47 +0000 (11:08 -0700)
committerNicolas Catania <niko@google.com>
Wed, 10 Jun 2009 16:11:59 +0000 (09:11 -0700)
New shell script to generate scalability data.

Fixed copyright notice.
Fixed incomplete metadata issue when debugfs was not mounted.

tests/sdcard/plot_sdcard.py
tests/sdcard/profile_sdcard.sh [new file with mode: 0755]
tests/sdcard/stopwatch.cpp

index 10ee00b..19b83c3 100755 (executable)
@@ -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 (executable)
index 0000000..4629c91
--- /dev/null
@@ -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
+
+
index 12fe8f1..8207430 100644 (file)
@@ -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);