Merge "integration: Support of PVP and PVVP integration TCs"
[vswitchperf.git] / tools / collectors / sysmetrics / pidstat.py
1 # Copyright 2015 Intel Corporation.
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 #   http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14
15 """module for statistics collection by pidstat
16
17 Provides system statistics collected between calls of start() and stop()
18 by command line tool pidstat (part of sysstat package)
19
20 This requires the following setting in your config:
21
22 * PIDSTAT_MONITOR = ['ovs-vswitchd', 'ovsdb-server', 'kvm']
23     processes to be monitorred by pidstat
24
25 * PIDSTAT_OPTIONS = '-dur'
26     options which will be passed to pidstat, i.e. what
27     statistics should be collected by pidstat
28
29 * LOG_FILE_PIDSTAT = 'pidstat.log'
30     log file for pidstat; it defines suffix, which will be added
31     to testcase name. Pidstat detailed statistics will be stored separately
32     for every testcase.
33
34 If this doesn't exist, the application will raise an exception
35 (EAFP).
36 """
37
38 import os
39 import logging
40 import subprocess
41 import time
42 from collections import OrderedDict
43 from tools import tasks
44 from tools import systeminfo
45 from conf import settings
46 from tools.collectors.collector import collector
47
48 _ROOT_DIR = os.path.dirname(os.path.realpath(__file__))
49
50 class Pidstat(collector.ICollector):
51     """A logger of system statistics based on pidstat
52
53     It collects statistics based on configuration
54     """
55     _logger = logging.getLogger(__name__)
56
57     def __init__(self, results_dir, test_name):
58         """
59         Initialize collection of statistics
60         """
61         self._log = os.path.join(results_dir,
62                                  settings.getValue('LOG_FILE_PIDSTAT') +
63                                  '_' + test_name + '.log')
64         self._results = OrderedDict()
65         self._pid = 0
66
67     def start(self):
68         """
69         Starts collection of statistics by pidstat and stores them
70         into the file in directory with test results
71         """
72         monitor = settings.getValue('PIDSTAT_MONITOR')
73         self._logger.info('Statistics are requested for: ' + ', '.join(monitor))
74         pids = systeminfo.get_pids(monitor)
75         if pids:
76             with open(self._log, 'w') as logfile:
77                 cmd = ['sudo', 'LC_ALL=' + settings.getValue('DEFAULT_CMD_LOCALE'),
78                        'pidstat', settings.getValue('PIDSTAT_OPTIONS'),
79                        '-p', ','.join(pids),
80                        str(settings.getValue('PIDSTAT_SAMPLE_INTERVAL'))]
81                 self._logger.debug('%s', ' '.join(cmd))
82                 self._pid = subprocess.Popen(cmd, stdout=logfile, bufsize=0).pid
83
84     def stop(self):
85         """
86         Stops collection of statistics by pidstat and stores statistic summary
87         for each monitored process into self._results dictionary
88         """
89         if self._pid:
90             self._pid = 0
91             # in python3.4 it's not possible to send signal through pid of sudo
92             # process, so all pidstat processes are interupted instead
93             # as a workaround
94             tasks.run_task(['sudo', 'pkill', '--signal', '2', 'pidstat'],
95                            self._logger)
96
97         self._logger.info(
98             'Pidstat log available at %s', self._log)
99
100         # let's give pidstat some time to write down average summary
101         time.sleep(2)
102
103         # parse average values from log file and store them to _results dict
104         self._results = OrderedDict()
105         logfile = open(self._log, 'r')
106         with logfile:
107             line = logfile.readline()
108             while line:
109                 line = line.strip()
110                 # process only lines with summary
111                 if line[0:7] == 'Average':
112                     if line[-7:] == 'Command':
113                         # store header fields if detected
114                         tmp_header = line[8:].split()
115                     else:
116                         # combine stored header fields with actual values
117                         tmp_res = OrderedDict(zip(tmp_header,
118                                                   line[8:].split()))
119                         # use process's name and its  pid as unique key
120                         key = tmp_res.pop('Command') + '_' + tmp_res['PID']
121                         # store values for given command into results dict
122                         if key in self._results:
123                             self._results[key].update(tmp_res)
124                         else:
125                             self._results[key] = tmp_res
126
127                 line = logfile.readline()
128
129     def get_results(self):
130         """Returns collected statistics.
131         """
132         return self._results
133
134     def print_results(self):
135         """Logs collected statistics.
136         """
137         for process in self._results:
138             logging.info("Process: " + '_'.join(process.split('_')[:-1]))
139             for(key, value) in self._results[process].items():
140                 logging.info("         Statistic: " + str(key) +
141                              ", Value: " + str(value))