Merge "Add NUMA pinning support for node context"
[yardstick.git] / yardstick / benchmark / scenarios / compute / cachestat.py
1 ##############################################################################
2 # Copyright (c) 2016 Huawei Technologies Co.,Ltd and others.
3 #
4 # All rights reserved. This program and the accompanying materials
5 # are made available under the terms of the Apache License, Version 2.0
6 # which accompanies this distribution, and is available at
7 # http://www.apache.org/licenses/LICENSE-2.0
8 ##############################################################################
9
10 """cache hit/miss ratio and usage statistics"""
11
12 from __future__ import absolute_import
13 import pkg_resources
14 import logging
15 import re
16 import yardstick.ssh as ssh
17
18 from yardstick.benchmark.scenarios import base
19 from six.moves import zip
20
21 LOG = logging.getLogger(__name__)
22
23
24 class CACHEstat(base.Scenario):
25     """Collect cache statistics.
26
27     This scenario reads system cache hit/miss ratio and other statistics on
28     a Linux host.
29
30     cache statistics are read using 'cachestat'.
31     cachestat - show Linux page cache hit/miss statistics.
32                 Uses Linux ftrace.
33
34     This is a proof of concept using Linux ftrace capabilities on older
35     kernels, and works by using function profiling for in-kernel counters.
36     Specifically, four kernel functions are traced:
37
38     mark_page_accessed() for measuring cache accesses
39     mark_buffer_dirty() for measuring cache writes
40     add_to_page_cache_lru() for measuring page additions
41     account_page_dirtied() for measuring page dirties
42
43     It is possible that these functions have been renamed (or are different
44     logically) for your kernel version, and this script will not work as-is.
45     This script was written on Linux 3.13. This script is a sandcastle: the
46     kernel may wash some away, and you'll need to rebuild.
47
48     USAGE: cachestat [-Dht] [interval]
49        eg,
50          cachestat 5    # show stats every 5 seconds
51
52     Run "cachestat -h" for full usage.
53
54     WARNING: This uses dynamic tracing of kernel functions, and could cause
55     kernel panics or freezes. Test, and know what you are doing, before use.
56     It also traces cache activity, which can be frequent, and cost some
57     overhead. The statistics should be treated as best-effort: there may be
58     some error margin depending on unusual workload types.
59
60     REQUIREMENTS: CONFIG_FUNCTION_PROFILER, awk.
61     """
62     __scenario_type__ = "CACHEstat"
63
64     TARGET_SCRIPT = "cache_stat.bash"
65
66     def __init__(self, scenario_cfg, context_cfg):
67         """Scenario construction."""
68         self.scenario_cfg = scenario_cfg
69         self.context_cfg = context_cfg
70         self.setup_done = False
71
72     def setup(self):
73         """Scenario setup."""
74         self.target_script = pkg_resources.resource_filename(
75             "yardstick.benchmark.scenarios.compute",
76             CACHEstat.TARGET_SCRIPT)
77
78         host = self.context_cfg['host']
79         user = host.get('user', 'ubuntu')
80         ssh_port = host.get("ssh_port", ssh.DEFAULT_PORT)
81         ip = host.get('ip', None)
82         key_filename = host.get('key_filename', '~/.ssh/id_rsa')
83
84         LOG.info("user:%s, host:%s", user, ip)
85         self.client = ssh.SSH(user, ip, key_filename=key_filename,
86                               port=ssh_port)
87         self.client.wait(timeout=600)
88
89         # copy scripts to host
90         self.client._put_file_shell(self.target_script, '~/cache_stat.sh')
91
92         self.setup_done = True
93
94     def _execute_command(self, cmd):
95         """Execute a command on server."""
96         LOG.info("Executing: %s", cmd)
97         status, stdout, stderr = self.client.execute(cmd)
98         if status:
99             raise RuntimeError("Failed executing command: ",
100                                cmd, stderr)
101         return stdout
102
103     def _filtrate_result(self, result):
104         fields = []
105         cachestat = {}
106         data_marker = re.compile("\d+")
107         ite = 0
108         average = {'HITS': 0, 'MISSES': 0, 'DIRTIES': 0, 'RATIO': 0,
109                    'BUFFERS_MB': 0, 'CACHE_MB': 0}
110         maximum = {'HITS': 0, 'MISSES': 0, 'DIRTIES': 0, 'RATIO': 0,
111                    'BUFFERS_MB': 0, 'CACHE_MB': 0}
112
113         # Parse cache stats
114         for row in result.split('\n'):
115             line = row.split()
116
117             if line and line[0] == 'HITS':
118                 # header fields
119                 fields = line[:]
120             elif line and re.match(data_marker, line[0]):
121                 cache = 'cache' + str(ite)
122                 ite += 1
123                 values = line[:]
124                 if values and len(values) == len(fields):
125                     cachestat[cache] = dict(list(zip(fields, values)))
126
127         for entry in cachestat:
128             for item in average:
129                 if item != 'RATIO':
130                     average[item] += int(cachestat[entry][item])
131                 else:
132                     average[item] += float(cachestat[entry][item][:-1])
133
134             for item in maximum:
135                 if item != 'RATIO':
136                     if int(cachestat[entry][item]) > maximum[item]:
137                         maximum[item] = int(cachestat[entry][item])
138                 else:
139                     if float(cachestat[entry][item][:-1]) > maximum[item]:
140                         maximum[item] = float(cachestat[entry][item][:-1])
141
142         for item in average:
143             if item != 'RATIO':
144                 average[item] = average[item] / len(cachestat)
145             else:
146                 average[item] = str(average[item] / len(cachestat)) + '%'
147
148         return {'cachestat': cachestat, 'average': average, 'max': maximum}
149
150     def _get_cache_usage(self):
151         """Get cache statistics."""
152         options = self.scenario_cfg['options']
153         interval = options.get("interval", 1)
154
155         cmd = "sudo bash cache_stat.sh %s" % (interval)
156
157         result = self._execute_command(cmd)
158         filtrated_result = self._filtrate_result(result)
159
160         return filtrated_result
161
162     def run(self, result):
163         if not self.setup_done:
164             self.setup()
165
166         result.update(self._get_cache_usage())