blob: cbc3c059d7c807117ecd87546604196fff57eb92 [file] [log] [blame]
Hung-Te Lin8fa29062015-11-25 18:00:59 +08001# Copyright 2015 The Chromium OS Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5"""Module to retrieve system information and status."""
6
7from __future__ import print_function
8
9import collections
10import glob
11import logging
12import netifaces
13import os
14import re
15import subprocess
16
17import factory_common # pylint: disable=W0611
18from cros.factory import hwid
Hung-Te Lin8fa29062015-11-25 18:00:59 +080019from cros.factory.system import GetBoard
20from cros.factory.system import partitions
21from cros.factory import test
Hung-Te Linb3a09642015-12-14 18:57:23 +080022from cros.factory.test import dut
Hung-Te Lin6a72c642015-12-13 22:09:09 +080023from cros.factory.test.dut import power
Hung-Te Lin8fa29062015-11-25 18:00:59 +080024from cros.factory.test import factory
25from cros.factory.utils.process_utils import Spawn
26from cros.factory.utils.sys_utils import MountDeviceAndReadFile
27
28
29class SystemInfo(object):
30 """Static information about the system.
31
32 This is mostly static information that changes rarely if ever
33 (e.g., version numbers, serial numbers, etc.).
34 """
35 # If not None, an update that is available from the update server.
36 update_md5sum = None
37
38 # The cached release image version and channel.
39 release_image_version = None
40 release_image_channel = None
41 allowed_release_channels = ['dev', 'beta', 'stable']
42
Hung-Te Linb3a09642015-12-14 18:57:23 +080043 def __init__(self, dut_instance=None):
44 self.dut = dut.Create() if dut_instance is None else dut_instance
45
Hung-Te Lin8fa29062015-11-25 18:00:59 +080046 self.mlb_serial_number = None
47 try:
48 self.mlb_serial_number = test.shopfloor.GetDeviceData()[
49 'mlb_serial_number']
50 except:
51 pass
52
53 self.serial_number = None
54 try:
55 self.serial_number = test.shopfloor.get_serial_number()
56 if self.serial_number is not None:
57 self.serial_number = str(self.serial_number)
58 except:
59 pass
60
61 self.stage = None
62 try:
63 self.stage = test.shopfloor.GetDeviceData()['stage']
64 except:
65 pass
66
67 self.factory_image_version = None
68 try:
69 lsb_release = open('/etc/lsb-release').read()
70 match = re.search('^GOOGLE_RELEASE=(.+)$', lsb_release,
71 re.MULTILINE)
72 if match:
73 self.factory_image_version = match.group(1)
74 except:
75 pass
76
77 self.toolkit_version = None
78 try:
79 with open('/usr/local/factory/TOOLKIT_VERSION') as f:
80 self.toolkit_version = f.read().strip()
81 except:
82 pass
83
84 def _GetReleaseLSBValue(lsb_key):
85 """Gets the value from the lsb-release file on release image."""
86 if not _GetReleaseLSBValue.lsb_content:
87 try:
88 release_rootfs = partitions.RELEASE_ROOTFS.path
89 _GetReleaseLSBValue.lsb_content = (
90 MountDeviceAndReadFile(release_rootfs, '/etc/lsb-release'))
91 except:
92 pass
93
94 match = re.search('^%s=(.+)$' % lsb_key,
95 _GetReleaseLSBValue.lsb_content,
96 re.MULTILINE)
97 if match:
98 return match.group(1)
99 else:
100 return None
101 # The cached content of lsb-release file.
102 _GetReleaseLSBValue.lsb_content = ""
103
104 self.release_image_version = None
105 if SystemInfo.release_image_version:
106 self.release_image_version = SystemInfo.release_image_version
107 logging.debug('Obtained release image version from SystemInfo: %r',
108 self.release_image_version)
109 else:
110 logging.debug('Release image version does not exist in SystemInfo. '
111 'Try to get it from lsb-release from release partition.')
112
113 self.release_image_version = _GetReleaseLSBValue('GOOGLE_RELEASE')
114 if self.release_image_version:
115 logging.debug('Release image version: %s', self.release_image_version)
116 logging.debug('Cache release image version to SystemInfo.')
117 SystemInfo.release_image_version = self.release_image_version
118 else:
119 logging.debug('Can not read release image version from lsb-release.')
120
121 self.release_image_channel = None
122 if SystemInfo.release_image_channel:
123 self.release_image_channel = SystemInfo.release_image_channel
124 logging.debug('Obtained release image channel from SystemInfo: %r',
125 self.release_image_channel)
126 else:
127 logging.debug('Release image channel does not exist in SystemInfo. '
128 'Try to get it from lsb-release from release partition.')
129
130 self.release_image_channel = _GetReleaseLSBValue('CHROMEOS_RELEASE_TRACK')
131 if self.release_image_channel:
132 logging.debug('Release image channel: %s', self.release_image_channel)
133 logging.debug('Cache release image channel to SystemInfo.')
134 SystemInfo.release_image_channel = self.release_image_channel
135 else:
136 logging.debug('Can not read release image channel from lsb-release.')
137
138 self.wlan0_mac = None
139 try:
140 for wlan_interface in ['mlan0', 'wlan0']:
141 address_path = os.path.join('/sys/class/net/',
142 wlan_interface, 'address')
143 if os.path.exists(address_path):
144 self.wlan0_mac = open(address_path).read().strip()
145 except:
146 pass
147
148 self.eth_macs = dict()
149 try:
150 eth_paths = glob.glob('/sys/class/net/eth*')
151 for eth_path in eth_paths:
152 address_path = os.path.join(eth_path, 'address')
153 if os.path.exists(address_path):
154 self.eth_macs[os.path.basename(eth_path)] = open(
155 address_path).read().strip()
156 except:
157 self.eth_macs = None
158
159 self.kernel_version = None
160 try:
161 uname = subprocess.Popen(['uname', '-r'], stdout=subprocess.PIPE)
162 stdout, _ = uname.communicate()
163 self.kernel_version = stdout.strip()
164 except:
165 pass
166
167 self.architecture = None
168 try:
169 self.architecture = Spawn(['uname', '-m'],
170 check_output=True).stdout_data.strip()
171 except:
172 pass
173
174 self.ec_version = None
175 try:
Hung-Te Linb3a09642015-12-14 18:57:23 +0800176 self.ec_version = self.dut.ec.GetECVersion()
Hung-Te Lin8fa29062015-11-25 18:00:59 +0800177 except:
178 pass
179
180 self.pd_version = None
181 try:
Hung-Te Linb3a09642015-12-14 18:57:23 +0800182 self.pd_version = self.dut.ec.GetPDVersion()
Hung-Te Lin8fa29062015-11-25 18:00:59 +0800183 except:
184 pass
185
186 self.firmware_version = None
187 try:
188 crossystem = subprocess.Popen(['crossystem', 'fwid'],
189 stdout=subprocess.PIPE)
190 stdout, _ = crossystem.communicate()
191 self.firmware_version = stdout.strip() or None
192 except:
193 pass
194
195 self.mainfw_type = None
196 try:
197 crossystem = subprocess.Popen(['crossystem', 'mainfw_type'],
198 stdout=subprocess.PIPE)
199 stdout, _ = crossystem.communicate()
200 self.mainfw_type = stdout.strip() or None
201 except:
202 pass
203
204 self.root_device = None
205 try:
206 rootdev = Spawn(['rootdev', '-s'],
207 stdout=subprocess.PIPE, ignore_stderr=True)
208 stdout, _ = rootdev.communicate()
209 self.root_device = stdout.strip()
210 except:
211 pass
212
213 self.factory_md5sum = factory.get_current_md5sum()
214
215 # Uses checksum of hwid file as hwid database version.
216 self.hwid_database_version = None
217 try:
218 hwid_file_path = os.path.join(hwid.common.DEFAULT_HWID_DATA_PATH,
219 hwid.common.ProbeBoard().upper())
220 if os.path.exists(hwid_file_path):
221 self.hwid_database_version = hwid.hwid_utils.ComputeDatabaseChecksum(
222 hwid_file_path)
223 except:
224 pass
225
226 # update_md5sum is currently in SystemInfo's __dict__ but not this
227 # object's. Copy it from SystemInfo into this object's __dict__.
228 self.update_md5sum = SystemInfo.update_md5sum
229
230
231# TODO(hungte) Move these functions to network module.
232
233def GetIPv4Interfaces():
234 """Returns a list of IPv4 interfaces."""
235 interfaces = sorted(netifaces.interfaces())
236 return [x for x in interfaces if not x.startswith('lo')]
237
238
239def GetIPv4InterfaceAddresses(interface):
240 """Returns a list of ips of an interface"""
241 try:
242 addresses = netifaces.ifaddresses(interface).get(netifaces.AF_INET, [])
243 except ValueError:
244 pass
245 ips = [x.get('addr') for x in addresses
246 if 'addr' in x] or ['none']
247 return ips
248
249
250def IsInterfaceConnected(prefix):
251 """Returns whether any interface starting with prefix is connected"""
252 ips = []
253 for interface in GetIPv4Interfaces():
254 if interface.startswith(prefix):
255 ips += [x for x in GetIPv4InterfaceAddresses(interface) if x != 'none']
256
257 return ips != []
258
259
260def GetIPv4Addresses():
261 """Returns a string describing interfaces' IPv4 addresses.
262
263 The returned string is of the format
264
265 eth0=192.168.1.10, wlan0=192.168.16.14
266 """
267 ret = []
268 interfaces = GetIPv4Interfaces()
269 for interface in interfaces:
270 ips = GetIPv4InterfaceAddresses(interface)
271 ret.append('%s=%s' % (interface, '+'.join(ips)))
272
273 return ', '.join(ret)
274
275
276_SysfsAttribute = collections.namedtuple('SysfsAttribute',
277 ['name', 'type', 'optional'])
278_SysfsBatteryAttributes = [
279 _SysfsAttribute('charge_full', int, False),
280 _SysfsAttribute('charge_full_design', int, False),
281 _SysfsAttribute('charge_now', int, False),
282 _SysfsAttribute('current_now', int, False),
283 _SysfsAttribute('present', bool, False),
284 _SysfsAttribute('status', str, False),
285 _SysfsAttribute('voltage_now', int, False),
286 _SysfsAttribute('voltage_min_design', int, True),
287 _SysfsAttribute('energy_full', int, True),
288 _SysfsAttribute('energy_full_design', int, True),
289 _SysfsAttribute('energy_now', int, True),
290]
291
292
293class SystemStatus(object):
294 """Information about the current system status.
295
296 This is information that changes frequently, e.g., load average
297 or battery information.
298
299 We log a bunch of system status here.
300 """
301 # Class variable: a charge_manager instance for checking force
302 # charge status.
303 charge_manager = None
304
Hung-Te Linb3a09642015-12-14 18:57:23 +0800305 def __init__(self, dut_instance=None):
Hung-Te Lin8fa29062015-11-25 18:00:59 +0800306 def _CalculateBatteryFractionFull(battery):
307 for t in ['charge', 'energy']:
308 now = battery['%s_now' % t]
309 full = battery['%s_full' % t]
310 if (now is not None and full is not None and full > 0 and now >= 0):
311 return float(now) / full
312 return None
313
Hung-Te Linb3a09642015-12-14 18:57:23 +0800314 self.dut = dut.Create() if dut_instance is None else dut_instance
Hung-Te Lin8fa29062015-11-25 18:00:59 +0800315 self.battery = {}
316 self.battery_sysfs_path = None
317 path_list = glob.glob('/sys/class/power_supply/*/type')
318 for p in path_list:
319 try:
320 if open(p).read().strip() == 'Battery':
321 self.battery_sysfs_path = os.path.dirname(p)
322 break
323 except:
324 logging.warning('sysfs path %s is unavailable', p)
325
326 for k, item_type, optional in _SysfsBatteryAttributes:
327 self.battery[k] = None
328 try:
329 if self.battery_sysfs_path:
330 self.battery[k] = item_type(
331 open(os.path.join(self.battery_sysfs_path, k)).read().strip())
332 except:
333 log_func = logging.error
334 if optional:
335 log_func = logging.debug
336 log_func('sysfs path %s is unavailable',
337 os.path.join(self.battery_sysfs_path, k))
338
339 self.battery['fraction_full'] = _CalculateBatteryFractionFull(self.battery)
340
341 self.battery['force'] = False
342 if self.charge_manager:
343 force_status = {
Hung-Te Lin6a72c642015-12-13 22:09:09 +0800344 power.Power.ChargeState.DISCHARGE: 'Discharging',
345 power.Power.ChargeState.CHARGE: 'Charging',
346 power.Power.ChargeState.IDLE: 'Idle'}.get(
Hung-Te Lin8fa29062015-11-25 18:00:59 +0800347 self.charge_manager.state)
348 if force_status:
349 self.battery['status'] = force_status
350 self.battery['force'] = True
351
352 # Get fan speed
353 try:
Hung-Te Lin0326f852015-11-26 12:48:07 +0800354 self.fan_rpm = self.dut.thermal.GetFanRPM()
Hung-Te Lin8fa29062015-11-25 18:00:59 +0800355 except:
356 self.fan_rpm = None
357
358 # Get temperatures from sensors
359 try:
Hung-Te Lin0326f852015-11-26 12:48:07 +0800360 self.temperatures = self.dut.thermal.GetTemperatures()
Hung-Te Lin8fa29062015-11-25 18:00:59 +0800361 except:
362 self.temperatures = []
363
364 try:
Hung-Te Lin0326f852015-11-26 12:48:07 +0800365 self.main_temperature_index = self.dut.thermal.GetMainTemperatureIndex()
Hung-Te Lin8fa29062015-11-25 18:00:59 +0800366 except:
367 self.main_temperature_index = None
368
369 try:
370 self.load_avg = map(
371 float, open('/proc/loadavg').read().split()[0:3])
372 except:
373 self.load_avg = None
374
375 try:
376 self.cpu = map(int, open('/proc/stat').readline().split()[1:])
377 except:
378 self.cpu = None
379
380 try:
381 self.ips = GetIPv4Addresses()
382 except:
383 self.ips = None
384
385 try:
386 self.eth_on = IsInterfaceConnected('eth')
387 except:
388 self.eth_on = None
389
390 try:
391 self.wlan_on = (IsInterfaceConnected('mlan') or
392 IsInterfaceConnected('wlan'))
393 except:
394 self.wlan_on = None
395
396
397if __name__ == '__main__':
398 import yaml
399 print(yaml.dump(dict(system_info=SystemInfo(None, None).__dict__,
400 system_status=SystemStatus().__dict__),
401 default_flow_style=False))