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