blob: ceaa6273e17f9771d54ad6a6491238ab6d41f66a [file] [log] [blame]
Brendan Jackman3cfbad12017-04-11 15:42:06 +01001import csv
2import os
3import signal
4from subprocess import Popen, PIPE
5from tempfile import NamedTemporaryFile
6from devlib.instrument import Instrument, CONTINUOUS, MeasurementsCsv
7from devlib.exception import HostError
8from devlib.host import PACKAGE_BIN_DIRECTORY
9from devlib.utils.misc import which
10
11INSTALL_INSTRUCTIONS="""
12MonsoonInstrument requires the monsoon.py tool, available from AOSP:
13
14https://android.googlesource.com/platform/cts/+/master/tools/utils/monsoon.py
15
16Download this script and put it in your $PATH (or pass it as the monsoon_bin
Brendan Jackman3dbd3f72017-05-22 13:48:47 +010017parameter to MonsoonInstrument). `pip install python-gflags pyserial` to install
18the dependencies.
Brendan Jackman3cfbad12017-04-11 15:42:06 +010019"""
20
21class MonsoonInstrument(Instrument):
22 """Instrument for Monsoon Solutions power monitor
23
24 To use this instrument, you need to install the monsoon.py script available
25 from the Android Open Source Project. As of May 2017 this is under the CTS
26 repository:
27
28 https://android.googlesource.com/platform/cts/+/master/tools/utils/monsoon.py
29
30 Collects power measurements only, from a selection of two channels, the USB
31 passthrough channel and the main output channel.
32
33 :param target: Ignored
34 :param monsoon_bin: Path to monsoon.py executable. If not provided,
35 ``$PATH`` is searched.
36 :param tty_device: TTY device to use to communicate with the Power
37 Monitor. If not provided, a sane default is used.
38 """
39
40 mode = CONTINUOUS
41
42 def __init__(self, target, monsoon_bin=None, tty_device=None):
43 super(MonsoonInstrument, self).__init__(target)
44 self.monsoon_bin = monsoon_bin or which('monsoon.py')
45 if not self.monsoon_bin:
46 raise HostError(INSTALL_INSTRUCTIONS)
47
48 self.tty_device = tty_device
49
50 self.process = None
51 self.output = None
52
53 self.sample_rate_hz = 500
54 self.add_channel('output', 'power')
55 self.add_channel('USB', 'power')
56
57 def reset(self, sites=None, kinds=None, channels=None):
58 super(MonsoonInstrument, self).reset(sites, kinds)
59
60 def start(self):
61 if self.process:
62 self.process.kill()
63
Joel Fernandes4568bcb2017-05-06 10:32:43 -070064 os.system(self.monsoon_bin + ' --usbpassthrough off')
65
Brendan Jackman3cfbad12017-04-11 15:42:06 +010066 cmd = [self.monsoon_bin,
67 '--hz', str(self.sample_rate_hz),
68 '--samples', '-1', # -1 means sample indefinitely
69 '--includeusb']
70 if self.tty_device:
71 cmd += ['--device', self.tty_device]
72
73 self.logger.debug(' '.join(cmd))
74 self.buffer_file = NamedTemporaryFile(prefix='monsoon', delete=False)
75 self.process = Popen(cmd, stdout=self.buffer_file, stderr=PIPE)
76
77 def stop(self):
78 process = self.process
79 self.process = None
80 if not process:
81 raise RuntimeError('Monsoon script not started')
82
83 process.poll()
84 if process.returncode is not None:
85 stdout, stderr = process.communicate()
86 raise HostError(
87 'Monsoon script exited unexpectedly with exit code {}.\n'
88 'stdout:\n{}\nstderr:\n{}'.format(process.returncode,
89 stdout, stderr))
90
91 process.send_signal(signal.SIGINT)
92
93 stderr = process.stderr.read()
94
95 self.buffer_file.close()
96 with open(self.buffer_file.name) as f:
97 stdout = f.read()
98 os.remove(self.buffer_file.name)
99 self.buffer_file = None
100
101 self.output = (stdout, stderr)
Joel Fernandes4568bcb2017-05-06 10:32:43 -0700102 os.system(self.monsoon_bin + ' --usbpassthrough on')
103
104 # Wait for USB connection to be restored
105 print ('waiting for usb connection to be back')
106 os.system('adb wait-for-device')
Brendan Jackman3cfbad12017-04-11 15:42:06 +0100107
108 def get_data(self, outfile):
109 if self.process:
110 raise RuntimeError('`get_data` called before `stop`')
111
112 stdout, stderr = self.output
113
114 with open(outfile, 'wb') as f:
115 writer = csv.writer(f)
116 active_sites = [c.site for c in self.active_channels]
117
118 # Write column headers
119 row = []
120 if 'output' in active_sites:
121 row.append('output_power')
122 if 'USB' in active_sites:
123 row.append('USB_power')
124 writer.writerow(row)
125
126 # Write data
127 for line in stdout.splitlines():
128 # Each output line is a main_output, usb_output measurement pair.
129 # (If our user only requested one channel we still collect both,
130 # and just ignore one of them)
131 output, usb = line.split()
132 row = []
133 if 'output' in active_sites:
134 row.append(output)
135 if 'USB' in active_sites:
136 row.append(usb)
137 writer.writerow(row)
138
Marc Bonnici049b2752017-08-03 16:43:32 +0100139 return MeasurementsCsv(outfile, self.active_channels, self.sample_rate_hz)