Merge "Add HA Test Cases in Test Suite"
[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         ip = host.get('ip', None)
79         key_filename = host.get('key_filename', '~/.ssh/id_rsa')
80
81         LOG.info("user:%s, host:%s", user, ip)
82         self.client = ssh.SSH(user, ip, key_filename=key_filename)
83         self.client.wait(timeout=600)
84
85         # copy scripts to host
86         self.client.run("cat > ~/cache_stat.sh",
87                         stdin=open(self.target_script, 'rb'))
88
89         self.setup_done = True
90
91     def _execute_command(self, cmd):
92         """Execute a command on server."""
93         LOG.info("Executing: %s" % cmd)
94         status, stdout, stderr = self.client.execute(cmd)
95         if status:
96             raise RuntimeError("Failed executing command: ",
97                                cmd, stderr)
98         return stdout
99
100     def _filtrate_result(self, result):
101         fields = []
102         cachestat = {}
103         data_marker = re.compile("\d+")
104         ite = 0
105         average = {'HITS': 0, 'MISSES': 0, 'DIRTIES': 0, 'RATIO': 0,
106                    'BUFFERS_MB': 0, 'CACHE_MB': 0}
107         maximum = {'HITS': 0, 'MISSES': 0, 'DIRTIES': 0, 'RATIO': 0,
108                    'BUFFERS_MB': 0, 'CACHE_MB': 0}
109
110         # Parse cache stats
111         for row in result.split('\n'):
112             line = row.split()
113
114             if line and line[0] == 'HITS':
115                 # header fields
116                 fields = line[:]
117             elif line and re.match(data_marker, line[0]):
118                 cache = 'cache' + str(ite)
119                 ite += 1
120                 values = line[:]
121                 if values and len(values) == len(fields):
122                     cachestat[cache] = dict(zip(fields, values))
123
124         for entry in cachestat:
125             for item in average:
126                 if item != 'RATIO':
127                     average[item] += int(cachestat[entry][item])
128                 else:
129                     average[item] += float(cachestat[entry][item][:-1])
130
131             for item in maximum:
132                 if item != 'RATIO':
133                     if int(cachestat[entry][item]) > maximum[item]:
134                         maximum[item] = int(cachestat[entry][item])
135                 else:
136                     if float(cachestat[entry][item][:-1]) > maximum[item]:
137                         maximum[item] = float(cachestat[entry][item][:-1])
138
139         for item in average:
140             if item != 'RATIO':
141                 average[item] = average[item] / len(cachestat)
142             else:
143                 average[item] = str(average[item] / len(cachestat)) + '%'
144
145         return {'cachestat': cachestat, 'average': average, 'max': maximum}
146
147     def _get_cache_usage(self):
148         """Get cache statistics."""
149         options = self.scenario_cfg['options']
150         interval = options.get("interval", 1)
151
152         cmd = "sudo bash cache_stat.sh %s" % (interval)
153
154         result = self._execute_command(cmd)
155         filtrated_result = self._filtrate_result(result)
156
157         return filtrated_result
158
159     def run(self, result):
160         if not self.setup_done:
161             self.setup()
162
163         result.update(self._get_cache_usage())