blob: 0a45a0d803518aa2732fa6ee3c0b3bfa28287138 [file] [log] [blame]
Sergei Trofimov4e6afe92015-10-09 09:30:04 +01001import os
2import re
3import csv
4import tempfile
5from datetime import datetime
6from collections import defaultdict
7from itertools import izip_longest
8
9from devlib.instrument import Instrument, MeasurementsCsv, CONTINUOUS
10from devlib.exception import TargetError, HostError
11from devlib.utils.android import ApkInfo
12
13
14THIS_DIR = os.path.dirname(__file__)
15
16NETSTAT_REGEX = re.compile(r'I/(?P<tag>netstats-\d+)\(\s*\d*\): (?P<ts>\d+) '
17 r'"(?P<package>[^"]+)" TX: (?P<tx>\S+) RX: (?P<rx>\S+)')
18
19
20def extract_netstats(filepath, tag=None):
21 netstats = []
22 with open(filepath) as fh:
23 for line in fh:
24 match = NETSTAT_REGEX.search(line)
25 if not match:
26 continue
27 if tag and match.group('tag') != tag:
28 continue
29 netstats.append((match.group('tag'),
30 match.group('ts'),
31 match.group('package'),
32 match.group('tx'),
33 match.group('rx')))
34 return netstats
35
36
37def netstats_to_measurements(netstats):
38 measurements = defaultdict(list)
39 for row in netstats:
40 tag, ts, package, tx, rx = row # pylint: disable=unused-variable
41 measurements[package + '_tx'].append(tx)
42 measurements[package + '_rx'].append(rx)
43 return measurements
44
45
46def write_measurements_csv(measurements, filepath):
47 headers = sorted(measurements.keys())
48 columns = [measurements[h] for h in headers]
49 with open(filepath, 'wb') as wfh:
50 writer = csv.writer(wfh)
51 writer.writerow(headers)
52 writer.writerows(izip_longest(*columns))
53
54
55class NetstatsInstrument(Instrument):
56
57 mode = CONTINUOUS
58
59 def __init__(self, target, apk=None, service='.TrafficMetricsService'):
60 """
61 Additional paramerter:
62
63 :apk: Path to the APK file that contains ``com.arm.devlab.netstats``
64 package. If not specified, it will be assumed that an APK with
65 name "netstats.apk" is located in the same directory as the
66 Python module for the instrument.
67 :service: Name of the service to be launched. This service must be
68 present in the APK.
69
70 """
71 if target.os != 'android':
72 raise TargetError('netstats insturment only supports Android targets')
73 if apk is None:
74 apk = os.path.join(THIS_DIR, 'netstats.apk')
75 if not os.path.isfile(apk):
76 raise HostError('APK for netstats instrument does not exist ({})'.format(apk))
77 super(NetstatsInstrument, self).__init__(target)
78 self.apk = apk
79 self.package = ApkInfo(self.apk).package
80 self.service = service
81 self.tag = None
82 self.command = None
83 self.stop_command = 'am kill {}'.format(self.package)
84
85 for package in self.target.list_packages():
86 self.add_channel(package, 'tx')
87 self.add_channel(package, 'rx')
88
89 def setup(self, force=False, *args, **kwargs):
90 if self.target.package_is_installed(self.package):
91 if force:
92 self.logger.debug('Re-installing {} (forced)'.format(self.package))
93 self.target.uninstall_package(self.package)
94 self.target.install(self.apk)
95 else:
96 self.logger.debug('{} already present on target'.format(self.package))
97 else:
98 self.logger.debug('Deploying {} to target'.format(self.package))
99 self.target.install(self.apk)
100
101 def reset(self, sites=None, kinds=None, period=None): # pylint: disable=arguments-differ
102 super(NetstatsInstrument, self).reset(sites, kinds)
103 period_arg, packages_arg = '', ''
104 self.tag = 'netstats-{}'.format(datetime.now().strftime('%Y%m%d%H%M%s'))
105 tag_arg = ' --es tag {}'.format(self.tag)
106 if sites:
107 packages_arg = ' --es packages {}'.format(','.join(sites))
108 if period:
109 period_arg = ' --ei period {}'.format(period)
110 self.command = 'am startservice{}{}{} {}/{}'.format(tag_arg,
111 period_arg,
112 packages_arg,
113 self.package,
114 self.service)
115 self.target.execute(self.stop_command) # ensure the service is not running.
116
117 def start(self):
118 if self.command is None:
119 raise RuntimeError('reset() must be called before start()')
120 self.target.execute(self.command)
121
122 def stop(self):
123 self.target.execute(self.stop_command)
124
125 def get_data(self, outfile):
126 raw_log_file = tempfile.mktemp()
127 self.target.dump_logcat(raw_log_file)
128 data = extract_netstats(raw_log_file)
129 measurements = netstats_to_measurements(data)
130 write_measurements_csv(measurements, outfile)
131 os.remove(raw_log_file)
132 return MeasurementsCsv(outfile, self.active_channels)
133
134 def teardown(self):
135 self.target.uninstall_package(self.package)