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