mirror of
				https://github.com/ARM-software/devlib.git
				synced 2025-10-31 14:01:20 +00:00 
			
		
		
		
	trace: Add DmesgCollector
Allows collecting dmesg output and parses it for easy filtering.
This commit is contained in:
		
				
					committed by
					
						 Marc Bonnici
						Marc Bonnici
					
				
			
			
				
	
			
			
			
						parent
						
							be8b87d559
						
					
				
				
					commit
					8512f116fc
				
			
							
								
								
									
										187
									
								
								devlib/trace/dmesg.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										187
									
								
								devlib/trace/dmesg.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,187 @@ | ||||
| #    Copyright 2019 ARM Limited | ||||
| # | ||||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| # you may not use this file except in compliance with the License. | ||||
| # You may obtain a copy of the License at | ||||
| # | ||||
| #     http://www.apache.org/licenses/LICENSE-2.0 | ||||
| # | ||||
| # Unless required by applicable law or agreed to in writing, software | ||||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| # See the License for the specific language governing permissions and | ||||
| # limitations under the License. | ||||
| # | ||||
|  | ||||
| from __future__ import division | ||||
| import re | ||||
| from itertools import takewhile | ||||
| from datetime import timedelta | ||||
|  | ||||
| from devlib.trace import TraceCollector | ||||
|  | ||||
|  | ||||
| class KernelLogEntry(object): | ||||
|     """ | ||||
|     Entry of the kernel ring buffer. | ||||
|  | ||||
|     :param facility: facility the entry comes from | ||||
|     :type facility: str | ||||
|  | ||||
|     :param level: log level | ||||
|     :type level: str | ||||
|  | ||||
|     :param timestamp: Timestamp of the entry | ||||
|     :type timestamp: datetime.timedelta | ||||
|  | ||||
|     :param msg: Content of the entry | ||||
|     :type msg: str | ||||
|     """ | ||||
|  | ||||
|     _TIMESTAMP_MSG_REGEX = re.compile(r'\[(.*?)\] (.*)') | ||||
|  | ||||
|     def __init__(self, facility, level, timestamp, msg): | ||||
|         self.facility = facility | ||||
|         self.level = level | ||||
|         self.timestamp = timestamp | ||||
|         self.msg = msg | ||||
|  | ||||
|     @classmethod | ||||
|     def from_str(cls, line): | ||||
|         """ | ||||
|         Parses a "dmesg --decode" output line, formatted as following: | ||||
|         kern  :err   : [3618282.310743] nouveau 0000:01:00.0: systemd-logind[988]: nv50cal_space: -16 | ||||
|  | ||||
|         Or the more basic output: | ||||
|         [3618282.310743] nouveau 0000:01:00.0: systemd-logind[988]: nv50cal_space: -16 | ||||
|  | ||||
|         """ | ||||
|  | ||||
|         def parse_timestamp_msg(line): | ||||
|             match = cls._TIMESTAMP_MSG_REGEX.match(line.strip()) | ||||
|             if not match: | ||||
|                 raise ValueError('dmesg entry format not recognized: {}'.format(line)) | ||||
|             timestamp, msg = match.groups() | ||||
|             return timestamp, msg | ||||
|  | ||||
|         # If we can parse the timestamp directly, that is a basic line | ||||
|         try: | ||||
|             timestamp, msg = parse_timestamp_msg(line) | ||||
|         except ValueError: | ||||
|             facility, level, remainder = line.split(':', 2) | ||||
|             timestamp, msg = parse_timestamp_msg(remainder) | ||||
|             facility = facility.strip() | ||||
|             level = level.strip() | ||||
|         else: | ||||
|             facility = None | ||||
|             level = None | ||||
|  | ||||
|         return cls( | ||||
|             facility=facility, | ||||
|             level=level, | ||||
|             timestamp=timedelta(seconds=float(timestamp.strip())), | ||||
|             msg=msg.strip(), | ||||
|         ) | ||||
|  | ||||
|     def __str__(self): | ||||
|         if self.facility and self.level: | ||||
|             prefix = '{facility}:{level}:'.format( | ||||
|                 facility=self.facility, | ||||
|                 level=self.level, | ||||
|             ) | ||||
|         else: | ||||
|             prefix = '' | ||||
|  | ||||
|         return '{prefix}[{timestamp}] {msg}'.format( | ||||
|             timestamp=self.timestamp.total_seconds(), | ||||
|             msg=self.msg, | ||||
|             prefix=prefix, | ||||
|         ) | ||||
|  | ||||
|  | ||||
| class DmesgCollector(TraceCollector): | ||||
|     """ | ||||
|     Dmesg output collector. | ||||
|  | ||||
|     :param level: Minimum log level to enable. All levels that are more | ||||
|         critical will be collected as well. | ||||
|     :type level: str | ||||
|  | ||||
|     :param facility: Facility to record, see dmesg --help for the list. | ||||
|     :type level: str | ||||
|  | ||||
|     .. warning:: If BusyBox dmesg is used, facility and level will be ignored, | ||||
|         and the parsed entries will also lack that information. | ||||
|     """ | ||||
|  | ||||
|     # taken from "dmesg --help" | ||||
|     # This list needs to be ordered by priority | ||||
|     LOG_LEVELS = [ | ||||
|         "emerg",        # system is unusable | ||||
|         "alert",        # action must be taken immediately | ||||
|         "crit",         # critical conditions | ||||
|         "err",          # error conditions | ||||
|         "warn",         # warning conditions | ||||
|         "notice",       # normal but significant condition | ||||
|         "info",         # informational | ||||
|         "debug",        # debug-level messages | ||||
|     ] | ||||
|  | ||||
|     def __init__(self, target, level=LOG_LEVELS[-1], facility='kern'): | ||||
|         super(DmesgCollector, self).__init__(target) | ||||
|  | ||||
|         if level not in self.LOG_LEVELS: | ||||
|             raise ValueError('level needs to be one of: {}'.format( | ||||
|                 ', '.join(self.LOG_LEVELS) | ||||
|             )) | ||||
|         self.level = level | ||||
|  | ||||
|         # Check if dmesg is the BusyBox one, or the one from util-linux. | ||||
|         # Note: BusyBox dmesg does not support -h, but will still print the | ||||
|         # help with an exit code of 1 | ||||
|         self.basic_dmesg = 'BusyBox' in self.target.execute('dmesg -h', | ||||
|                                                         check_exit_code=False) | ||||
|         self.reset() | ||||
|  | ||||
|     @property | ||||
|     def entries(self): | ||||
|         return self._parse_entries(self.dmesg_out) | ||||
|  | ||||
|     @classmethod | ||||
|     def _parse_entries(cls, dmesg_out): | ||||
|         if not dmesg_out: | ||||
|             return [] | ||||
|         else: | ||||
|             return [ | ||||
|                 KernelLogEntry.from_str(line) | ||||
|                 for line in dmesg_out.splitlines() | ||||
|             ] | ||||
|  | ||||
|     def reset(self): | ||||
|         self.dmesg_out = None | ||||
|  | ||||
|     def start(self): | ||||
|         self.reset() | ||||
|         # Empty the dmesg ring buffer | ||||
|         self.target.execute('dmesg -c') | ||||
|  | ||||
|     def stop(self): | ||||
|         levels_list = list(takewhile( | ||||
|             lambda level: level != self.level, | ||||
|             self.LOG_LEVELS | ||||
|         )) | ||||
|         levels_list.append(self.level) | ||||
|         if self.basic_dmesg: | ||||
|             cmd = 'dmesg' | ||||
|         else: | ||||
|             cmd = 'dmesg --facility={facility} --force-prefix --decode --level={levels}'.format( | ||||
|                 levels=','.join(levels_list), | ||||
|                 facility=self.facility, | ||||
|             ) | ||||
|  | ||||
|         self.dmesg_out = self.target.execute(cmd) | ||||
|  | ||||
|     def get_trace(self, outfile): | ||||
|         with open(outfile, 'wt') as f: | ||||
|             f.write(self.dmesg_out + '\n') | ||||
|  | ||||
		Reference in New Issue
	
	Block a user