blob: f3e40055c545597fda2b0d537edac0463574307c [file] [log] [blame]
# Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import collections
import logging
import os
import re
import StringIO
IW_REMOTE_EVENT_LOG_FILE_NAME = 'iw_event.log'
LogEntry = collections.namedtuple('LogEntry', ['timestamp',
'interface',
'message'])
class IwEventLogger(object):
"""Context enclosing the use of iw event logger."""
def __init__(self, host, command_iw, local_file):
self._host = host
self._command_iw = command_iw
self._iw_event_log_path = os.path.join(self._host.get_tmp_dir(),
IW_REMOTE_EVENT_LOG_FILE_NAME)
self._local_file = local_file
self._pid = None
self._start_time = 0
def __enter__(self):
return self
def __exit__(self, exception, value, traceback):
self.stop()
def _check_message_for_disconnect(self, message):
"""Check log message for disconnect event.
This function checks log messages for signs the connection was
terminated.
@param: message String message to check for disconnect event.
@returns True if the log message is a disconnect event, false otherwise.
"""
return (message.startswith('disconnected') or
message.startswith('Deauthenticated') or
message == 'Previous authentication no longer valid')
@property
def local_file(self):
"""@return string local host path for log file."""
return self._local_file
def start(self):
"""Start event logger.
This function will start iw event process in remote host, and redirect
output to a temporary file in remote host.
"""
command = 'nohup %s event -t </dev/null >%s 2>&1 & echo $!' % (
self._command_iw, self._iw_event_log_path)
command += ';date +%s'
out_lines = self._host.run(command).stdout.splitlines()
self._pid = int(out_lines[0])
self._start_time = float(out_lines[1])
def stop(self):
"""Stop event logger.
This function will kill iw event process, and copy the log file from
remote to local.
"""
if self._pid is None:
return
# Kill iw event process
self._host.run('kill %d' % self._pid, ignore_status=True)
self._pid = None
# Copy iw event log file from remote host
self._host.get_file(self._iw_event_log_path, self._local_file)
logging.info('iw event log saved to %s', self._local_file)
def get_log_entries(self):
"""Parse local log file and yield LogEntry named tuples.
This function will parse the iw event log and return individual
LogEntry tuples for each parsed line.
Here are example of lines to be parsed:
1393961008.058711: wlan0 (phy #0): scan started
1393961019.758599: wlan0 (phy #0): connected to 04:f0:21:03:7d:bd
@yields LogEntry tuples for each log entry.
"""
iw_log = self._host.run('cat %s' % self._iw_event_log_path).stdout
iw_log_file = StringIO.StringIO(iw_log)
for line in iw_log_file.readlines():
parse_line = re.match('\s*(\d+).(\d+): (\w.*): (\w.*)', line)
if parse_line:
time_integer = parse_line.group(1)
time_decimal = parse_line.group(2)
timestamp = float('%s.%s' % (time_integer, time_decimal))
yield LogEntry(timestamp=timestamp,
interface=parse_line.group(3),
message=parse_line.group(4))
def get_reassociation_time(self):
"""Return reassociation time.
This function will search the iw event log to determine the time it
takes from start of reassociation request to being connected. Start of
reassociation request could be either an attempt to scan or to
disconnect. Assume the one that appeared in the log first is the start
of the reassociation request.
@returns float number of seconds it take from start of reassociation
request to being connected. Return None if unable to determine
the time based on the log.
"""
start_time = None
end_time = None
# Figure out the time when reassociation process started and the time
# when client is connected.
for entry in self.get_log_entries():
if (entry.message.startswith('scan started') and
start_time is None):
start_time = entry.timestamp
# Newer wpa_supplicant would attempt to disconnect then reconnect
# without scanning. So if no scan event is detected before the
# disconnect attempt, we'll assume the disconnect attempt is the
# beginning of the reassociate attempt.
if (self._check_message_for_disconnect(entry.message) and
start_time is None):
start_time = entry.timestamp
if entry.message.startswith('connected'):
if start_time is None:
return None
end_time = entry.timestamp
break;
else:
return None
return end_time - start_time
def get_disconnect_count(self):
"""Return number of times the system disconnected during the log.
This function will search the iw event log to determine how many
times the "disconnect" and "Deauthenticated" messages appear.
@returns int number of times the system disconnected in the logs.
"""
count = 0
for entry in self.get_log_entries():
if self._check_message_for_disconnect(entry.message):
count += 1
return count
def get_time_to_disconnected(self):
"""Return disconnect time.
This function will search the iw event log to determine the number of
seconds between the time iw event logger is started to the time the
first "disconnected" or "Deauthenticated" event is received.
@return float number of seconds between the time iw event logger is
started to the time "disconnected" or "Deauthenticated" event
is received. Return None if no "disconnected" or
"Deauthenticated" event is detected in the iw event log.
"""
for entry in self.get_log_entries():
if self._check_message_for_disconnect(entry.message):
return entry.timestamp - self._start_time
return None