modify sc lab pod yaml as real configure
[yardstick.git] / yardstick / benchmark / scenarios / compute / cpuload.py
1 ##############################################################################
2 # Copyright (c) 2015 Ericsson AB 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 """Processor statistics and system load."""
11
12 import logging
13 import time
14 import re
15 import yardstick.ssh as ssh
16
17 from yardstick.benchmark.scenarios import base
18
19
20 LOG = logging.getLogger(__name__)
21
22
23 class CPULoad(base.Scenario):
24
25     """Collect processor statistics and system load.
26
27     This scenario reads system load averages and
28     CPU usage statistics on a Linux host.
29
30     CPU usage statistics are read using the utility 'mpstat'.
31
32     If 'mpstat' is not installed on the host usage statistics
33     are instead read directly from '/proc/stat'.
34
35     Load averages are read from the file '/proc/loadavg'
36     on the Linux host.
37
38     Parameters
39           interval - Time interval to measure CPU usage. A value of 0
40                      indicates that processors statistics are to be
41                      reported for the time since system startup (boot)
42
43           type:       [int]
44           unit:       seconds
45           default:    0
46
47     """
48
49     __scenario_type__ = "CPUload"
50
51     MPSTAT_FIELD_SIZE = 10
52
53     def __init__(self, scenario_cfg, context_cfg):
54         """Scenario construction."""
55         self.scenario_cfg = scenario_cfg
56         self.context_cfg = context_cfg
57         self.setup_done = False
58         self.has_mpstat = False
59
60     def setup(self):
61         """Scenario setup."""
62         host = self.context_cfg['host']
63         user = host.get('user', 'ubuntu')
64         ip = host.get('ip', None)
65         key_filename = host.get('key_filename', '~/.ssh/id_rsa')
66
67         LOG.info("user:%s, host:%s", user, ip)
68         self.client = ssh.SSH(user, ip, key_filename=key_filename)
69         self.client.wait(timeout=600)
70
71         # Check if mpstat prog is installed
72         status, _, _ = self.client.execute("mpstat -V >/dev/null 2>&1")
73         if status != 0:
74             LOG.info("MPSTAT is NOT installed")
75             self.has_mpstat = False
76         else:
77             LOG.info("MPSTAT is installed")
78             self.has_mpstat = True
79
80         if 'options' in self.scenario_cfg:
81             self.interval = self.scenario_cfg['options'].get("interval", 0)
82         else:
83             self.interval = 0
84
85         self.setup_done = True
86
87     def _execute_command(self, cmd):
88         """Execute a command on server."""
89         LOG.info("Executing: %s" % cmd)
90         status, stdout, stderr = self.client.execute(cmd)
91         if status != 0:
92             raise RuntimeError("Failed executing command: ",
93                                cmd, stderr)
94         return stdout
95
96     def _get_loadavg(self):
97         """Get system load."""
98         return {'loadavg': self._execute_command("cat /proc/loadavg").split()}
99
100     def _get_cpu_usage_mpstat(self):
101         """Get processor usage using mpstat."""
102         if self.interval > 0:
103             cmd = "mpstat -P ON %s 1" % self.interval
104         else:
105             cmd = "mpstat -P ON"
106
107         result = self._execute_command(cmd)
108
109         fields = []
110         mpstat = {}
111
112         time_marker = re.compile("^([0-9]+):([0-9]+):([0-9]+)$")
113         ampm_marker = re.compile("(AM|PM)$")
114
115         # Parse CPU stats
116         for row in result.split('\n'):
117             line = row.split()
118
119             if line and re.match(time_marker, line[0]):
120
121                 if re.match(ampm_marker, line[1]):
122                     del line[:2]
123                 else:
124                     del line[:1]
125
126                 if line[0] == 'CPU':
127                     # header fields
128                     fields = line[1:]
129                     if len(fields) != CPULoad.MPSTAT_FIELD_SIZE:
130                         raise RuntimeError("mpstat: unexpected field size",
131                                            fields)
132                 else:
133                     # value fields
134                     cpu = 'cpu' if line[0] == 'all' else 'cpu' + line[0]
135                     values = line[1:]
136                     if values and len(values) == len(fields):
137                         mpstat[cpu] = dict(zip(fields, values))
138                     else:
139                         raise RuntimeError("mpstat: parse error", fields, line)
140
141         return {'mpstat': mpstat}
142
143     def _get_cpu_usage(self):
144         """Get processor usage from /proc/stat."""
145         fields = ['%usr', '%nice', '%sys', '%idle', '%iowait',
146                   '%irq', '%soft', '%steal', '%guest', '%gnice']
147
148         cmd = "grep '^cpu[0-9 ].' /proc/stat"
149
150         if self.interval > 0:
151             previous = self._execute_command(cmd).splitlines()
152             time.sleep(self.interval)
153             current = self._execute_command(cmd).splitlines()
154         else:
155             current = self._execute_command(cmd).splitlines()
156             previous = current
157
158         mpstat = {}
159
160         for (prev, cur) in zip(previous, current):
161
162             # Split string to list tokens
163             cur_list = cur.split()
164             prev_list = prev.split()
165
166             cpu = cur_list[0]
167
168             cur_stats = map(int, cur_list[1:])
169             if self.interval > 0:
170                 prev_stats = map(int, prev_list[1:])
171             else:
172                 prev_stats = [0] * len(cur_stats)
173
174             # NB: Don't add 'guest' and 'gnice' as they
175             # are already included in 'usr' and 'nice'.
176             uptime_prev = sum(prev_stats[0:8])
177             uptime_cur = sum(cur_stats[0:8])
178
179             # Remove 'guest' and 'gnice' from 'usr' and 'nice'
180             prev_stats[0] -= prev_stats[8]
181             prev_stats[1] -= prev_stats[9]
182             cur_stats[0] -= cur_stats[8]
183             cur_stats[1] -= cur_stats[9]
184
185             # number of samples (jiffies) in the interval
186             samples = (uptime_cur - uptime_prev) or 1
187
188             def _percent(x, y):
189                 if x < y:
190                     return 0.0
191                 else:
192                     return "%.2f" % (100.0 * (x - y) / samples)
193
194             load = map(_percent, cur_stats, prev_stats)
195
196             mpstat[cpu] = dict(zip(fields, load))
197
198         return {'mpstat': mpstat}
199
200     def run(self, result):
201         """Read processor statistics."""
202         if not self.setup_done:
203             self.setup()
204
205         result.update(self._get_loadavg())
206
207         if self.has_mpstat:
208             result.update(self._get_cpu_usage_mpstat())
209         else:
210             result.update(self._get_cpu_usage())
211
212         # Note: No SLA as this scenario is only collecting statistics
213
214 # def _test():
215 #     """internal test function."""
216 #     import pkg_resources
217 #     key_filename = pkg_resources.resource_filename('yardstick.resources',
218 #                                                    'files/yardstick_key')
219 #     ctx = {
220 #         'host': {
221 #             'ip': '172.16.0.175',
222 #             'user': 'ubuntu',
223 #             'key_filename': key_filename
224 #         }
225 #     }
226
227 #     logger = logging.getLogger('yardstick')
228 #     logger.setLevel(logging.DEBUG)
229
230 #     args = {}
231 #     result = {}
232
233 #     p = CPULoad(args, ctx)
234 #     p.run(result)
235 #     import json
236 #     print json.dumps(result)
237
238 # if __name__ == '__main__':
239 #     _test()