Added autotest that calculates costs of TLB misses

BUG=None

TEST=Run on peppy board

Change-Id: I253b1656b29136a67b8cb124f76fc41b18b1cc0e
Reviewed-on: https://chromium-review.googlesource.com/297177
Commit-Ready: Nemanja Vasić <nvasic@google.com>
Tested-by: Nemanja Vasić <nvasic@google.com>
Reviewed-by: David Sharp <dhsharp@chromium.org>
diff --git a/client/site_tests/hardware_TLBMissCost/control b/client/site_tests/hardware_TLBMissCost/control
new file mode 100644
index 0000000..67aab8f
--- /dev/null
+++ b/client/site_tests/hardware_TLBMissCost/control
@@ -0,0 +1,24 @@
+# Copyright 2015 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+AUTHOR = "Nemanja Vasic <nvasic@google.com>"
+NAME = "hardware_TLBMissCost"
+TIME = "SHORT"
+TEST_CATEGORY = "Functional"
+TEST_CLASS = "hardware"
+TEST_TYPE = "client"
+ATTRIBUTES = "suite:experimental"
+SUITE = "experimental"
+
+DOC = """
+Calculate cost of a TLB miss
+
+Arguments:
+  events: Events to pass to perf stat -e
+  program: Benchmark binary
+"""
+
+job.run_test('hardware_TLBMissCost', tag='TLBMissCost',
+             events=('cycles', 'iTLB-misses'),
+             program='iTLB_benchmark')
diff --git a/client/site_tests/hardware_TLBMissCost/hardware_TLBMissCost.py b/client/site_tests/hardware_TLBMissCost/hardware_TLBMissCost.py
new file mode 100644
index 0000000..8558dad
--- /dev/null
+++ b/client/site_tests/hardware_TLBMissCost/hardware_TLBMissCost.py
@@ -0,0 +1,63 @@
+# Copyright 2015 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import os
+import perf_measurement
+import numpy
+
+from autotest_lib.client.bin import test
+from autotest_lib.client.bin import utils
+from autotest_lib.client.common_lib import error
+
+# This event counts cycles when the page miss handler is servicing page walks
+# caused by ITLB misses. Raw event codes for x86 microarchitectures can be
+# found at Intel Open Source technology center website:
+# https://download.01.org/perfmon
+RAW_PAGE_WALK_EVENT_CODES = {
+    'Broadwell': 'r1085',
+    'Haswell': 'r1085',
+    'IvyBridge': 'r0485',
+    'SandyBridge': 'r0485',
+}
+
+class hardware_TLBMissCost(test.test):
+    """Calculates cost of one iTLB miss in
+    terms of cycles spent on page walking.
+    """
+
+    version = 1
+    preserve_srcdir = True
+
+    def initialize(self, events=('cycles', 'iTLB-misses')):
+        self.job.require_gcc()
+        self.events = events
+
+    def setup(self):
+        os.chdir(self.srcdir)
+        utils.make('clean')
+        utils.make()
+
+    def warmup(self):
+        uarch = utils.get_intel_cpu_uarch()
+        if uarch not in RAW_PAGE_WALK_EVENT_CODES:
+            raise error.TestNAError('Unsupported microarchitecture.')
+        self.pw_event = RAW_PAGE_WALK_EVENT_CODES.get(uarch)
+
+    def run_once(self, program):
+        program = os.path.join(self.srcdir, program)
+        self.events = self.events + (self.pw_event,)
+        self.facts = perf_measurement.GatherPerfStats(program,
+                ','.join(self.events))
+
+    def postprocess_iteration(self):
+        results = {}
+        if ('iTLB-misses' in self.events):
+            pw_cycles_per_miss = [x[self.pw_event] * 1.0 / x['iTLB-misses']
+                                  for x in self.facts]
+            results['pw-cycles-per-miss'] = numpy.average(pw_cycles_per_miss)
+        if ('cycles' in self.events):
+            pw_cycle_percent = [x[self.pw_event] * 100.0 / x['cycles']
+                                for x in self.facts]
+            results['pw-cycle-percent'] = numpy.average(pw_cycle_percent)
+        self.write_perf_keyval(results)
diff --git a/client/site_tests/hardware_TLBMissCost/perf_measurement.py b/client/site_tests/hardware_TLBMissCost/perf_measurement.py
new file mode 100755
index 0000000..6511d42
--- /dev/null
+++ b/client/site_tests/hardware_TLBMissCost/perf_measurement.py
@@ -0,0 +1,47 @@
+#!/usr/bin/python2.7
+# Copyright 2015 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import subprocess
+import numpy
+
+class Error(Exception):
+    """Module error class."""
+
+def GatherPerfStats(program, events):
+    """Run perf stat with the given events and given program.
+
+    @param program: path to benchmark binary. It should take one argument
+        (number of loop iterations) and produce no output.
+    @param events: value to pass to '-e' arg of perf stat.
+    @returns: List of dicts.
+    """
+    facts = []
+    for i in xrange(0, 10):
+        loops = (i + 1) * 10
+        out = subprocess.check_output(
+                ('perf', 'stat', '-x', ',',
+                 '-e', events,
+                 program, '%d' % loops),
+                stderr=subprocess.STDOUT)
+        unsupported_events = []
+        f = {}
+        for line in out.splitlines():
+            fields = line.split(',')
+            count, unit, event = None, None, None
+            if len(fields) == 2:
+                count, event = fields
+            elif len(fields) == 3:
+                count, unit, event = fields
+            else:
+                raise Error('Unable to parse perf stat output')
+            if count == '<not supported>':
+                unsupported_events.append(event)
+            else:
+                f[event] = int(count)
+        if unsupported_events:
+            raise Error('These events are not supported: %s'
+                        % unsupported_events)
+        facts.append(f)
+    return facts
diff --git a/client/site_tests/hardware_TLBMissCost/src/Makefile b/client/site_tests/hardware_TLBMissCost/src/Makefile
new file mode 100644
index 0000000..e1a54b4
--- /dev/null
+++ b/client/site_tests/hardware_TLBMissCost/src/Makefile
@@ -0,0 +1,15 @@
+CFLAGS=-O0 -g
+
+BINS=iTLB_benchmark
+OBJS=iTLB_benchmark.o iTLB_benchmark_function.o
+OUTPUTS=$(BINS) $(OBJS) iTLB_benchmark_function.c
+
+all: $(OUTPUTS)
+
+iTLB_benchmark: iTLB_benchmark.o iTLB_benchmark_function.o
+
+iTLB_benchmark_function.c: generateBenchmarkFunction.sh
+	./generateBenchmarkFunction.sh 4096 1024 > iTLB_benchmark_function.c
+
+clean:
+	rm -rf $(OUTPUTS)
diff --git a/client/site_tests/hardware_TLBMissCost/src/generateBenchmarkFunction.sh b/client/site_tests/hardware_TLBMissCost/src/generateBenchmarkFunction.sh
new file mode 100755
index 0000000..eba11fc
--- /dev/null
+++ b/client/site_tests/hardware_TLBMissCost/src/generateBenchmarkFunction.sh
@@ -0,0 +1,24 @@
+#!/bin/bash
+# This script generates an assembly function that jumps over blocks of size
+# determined by its first argument. Number of such blocks is determined
+# by the second argument.
+
+PAGE_SIZE=$(( $1 ))
+TLB_ENTRY_CNT=$(( $2 ))
+
+function instruction_block {
+  for (( c=0; c < PAGE_SIZE ; c++ )) ; do
+    echo '    "nop\n\t"'
+  done
+}
+
+echo 'void iTLB_bechmark_function() {'
+echo '  __asm__ ('
+
+for (( i=0; i < TLB_ENTRY_CNT; i++ )) ; do
+   echo '    "1:jmp 1f\n\t"'
+   instruction_block
+done
+echo '    "1:nop\n\t"'
+echo '  );'
+echo '}'
diff --git a/client/site_tests/hardware_TLBMissCost/src/iTLB_benchmark.c b/client/site_tests/hardware_TLBMissCost/src/iTLB_benchmark.c
new file mode 100644
index 0000000..7809f44
--- /dev/null
+++ b/client/site_tests/hardware_TLBMissCost/src/iTLB_benchmark.c
@@ -0,0 +1,18 @@
+#include <stdlib.h>
+#include "iTLB_benchmark_function.h"
+
+int main(int argc, char *argv[]) {
+  unsigned long loops = 1000;
+  if (argc > 1) {
+    loops = strtoul(argv[1], NULL, 10);
+    if (loops < 1) {
+      loops = 1;
+    }
+  }
+
+  while (--loops) {
+    iTLB_bechmark_function();
+  }
+
+  return 0;
+}
diff --git a/client/site_tests/hardware_TLBMissCost/src/iTLB_benchmark_function.h b/client/site_tests/hardware_TLBMissCost/src/iTLB_benchmark_function.h
new file mode 100644
index 0000000..dd31701
--- /dev/null
+++ b/client/site_tests/hardware_TLBMissCost/src/iTLB_benchmark_function.h
@@ -0,0 +1 @@
+void iTLB_bechmark_function();