mirror of
				https://github.com/smaeul/u-boot.git
				synced 2025-10-26 01:28:14 +00:00 
			
		
		
		
	The relocation offset can change in some initcall sequences. Handle this and make sure it is used for all debugging statements in init_run_list() Update the trace test to match. Signed-off-by: Simon Glass <sjg@chromium.org> Reviewed-by: Caleb Connolly <caleb.connolly@linaro.org>
		
			
				
	
	
		
			307 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			307 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| # SPDX-License-Identifier: GPL-2.0
 | |
| # Copyright 2022 Google LLC
 | |
| # Written by Simon Glass <sjg@chromium.org>
 | |
| 
 | |
| import os
 | |
| import pytest
 | |
| import re
 | |
| 
 | |
| import u_boot_utils as util
 | |
| 
 | |
| # This is needed for Azure, since the default '..' directory is not writeable
 | |
| TMPDIR = '/tmp/test_trace'
 | |
| 
 | |
| # Decode a function-graph line
 | |
| RE_LINE = re.compile(r'.*0\.\.\.\.\.? \s*([0-9.]*): func.*[|](\s*)(\S.*)?([{};])$')
 | |
| 
 | |
| 
 | |
| def collect_trace(cons):
 | |
|     """Build U-Boot and run it to collect a trace
 | |
| 
 | |
|     Args:
 | |
|         cons (ConsoleBase): U-Boot console
 | |
| 
 | |
|     Returns:
 | |
|         tuple:
 | |
|             str: Filename of the output trace file
 | |
|             int: Microseconds taken for initf_dm according to bootstage
 | |
|     """
 | |
|     cons.run_command('trace pause')
 | |
|     out = cons.run_command('trace stats')
 | |
| 
 | |
|     # The output is something like this:
 | |
|     #    251,003 function sites
 | |
|     #  1,160,283 function calls
 | |
|     #          0 untracked function calls
 | |
|     #  1,230,758 traced function calls (341538 dropped due to overflow)
 | |
|     #         33 maximum observed call depth
 | |
|     #         15 call depth limit
 | |
|     #    748,268 calls not traced due to depth
 | |
|     #  1,230,758 max function calls
 | |
| 
 | |
|     # Get a dict of values from the output
 | |
|     lines = [line.split(maxsplit=1) for line in out.splitlines() if line]
 | |
|     vals = {key: val.replace(',', '') for val, key in lines}
 | |
| 
 | |
|     assert int(vals['function sites']) > 100000
 | |
|     assert int(vals['function calls']) > 200000
 | |
|     assert int(vals['untracked function calls']) == 0
 | |
|     assert int(vals['maximum observed call depth']) > 30
 | |
|     assert (vals['call depth limit'] ==
 | |
|             cons.config.buildconfig.get('config_trace_call_depth_limit'))
 | |
|     assert int(vals['calls not traced due to depth']) > 100000
 | |
| 
 | |
|     out = cons.run_command('bootstage report')
 | |
|     # Accumulated time:
 | |
|     #           19,104  dm_r
 | |
|     #           23,078  of_live
 | |
|     #           46,280  dm_f
 | |
|     dm_f_time = [line.split()[0] for line in out.replace(',', '').splitlines()
 | |
|                  if 'dm_f' in line]
 | |
| 
 | |
|     # Read out the trace data
 | |
|     addr = 0x02000000
 | |
|     size = 0x02000000
 | |
|     out = cons.run_command(f'trace calls {addr:x} {size:x}')
 | |
|     print(out)
 | |
|     fname = os.path.join(TMPDIR, 'trace')
 | |
|     out = cons.run_command(
 | |
|         'host save hostfs - %x %s ${profoffset}' % (addr, fname))
 | |
|     return fname, int(dm_f_time[0])
 | |
| 
 | |
| 
 | |
| def check_function(cons, fname, proftool, map_fname, trace_dat):
 | |
|     """Check that the 'function' output works
 | |
| 
 | |
|     Args:
 | |
|         cons (ConsoleBase): U-Boot console
 | |
|         fname (str): Filename of trace file
 | |
|         proftool (str): Filename of proftool
 | |
|         map_fname (str): Filename of System.map
 | |
|         trace_dat (str): Filename of output file
 | |
|     """
 | |
|     out = util.run_and_log(
 | |
|         cons, [proftool, '-t', fname, '-o', trace_dat, '-m', map_fname,
 | |
|                'dump-ftrace'])
 | |
| 
 | |
|     # Check that trace-cmd can read it
 | |
|     out = util.run_and_log(cons, ['trace-cmd', 'dump', trace_dat])
 | |
| 
 | |
|     # Tracing meta data in file /tmp/test_trace/trace.dat:
 | |
|     #    [Initial format]
 | |
|     #            6        [Version]
 | |
|     #            0        [Little endian]
 | |
|     #            4        [Bytes in a long]
 | |
|     #            4096        [Page size, bytes]
 | |
|     #    [Header page, 205 bytes]
 | |
|     #    [Header event, 205 bytes]
 | |
|     #    [Ftrace format, 3 events]
 | |
|     #    [Events format, 0 systems]
 | |
|     #    [Kallsyms, 342244 bytes]
 | |
|     #    [Trace printk, 0 bytes]
 | |
|     #    [Saved command lines, 9 bytes]
 | |
|     #    1 [CPUs with tracing data]
 | |
|     #    [6 options]
 | |
|     #    [Flyrecord tracing data]
 | |
|     #    [Tracing clock]
 | |
|     #            [local] global counter uptime perf mono mono_raw boot x86-tsc
 | |
|     assert '[Flyrecord tracing data]' in out
 | |
|     assert '4096	[Page size, bytes]' in out
 | |
|     kallsyms = [line.split() for line in out.splitlines() if 'Kallsyms' in line]
 | |
|     # [['[Kallsyms,', '342244', 'bytes]']]
 | |
|     val = int(kallsyms[0][1])
 | |
|     assert val > 50000  # Should be at least 50KB of symbols
 | |
| 
 | |
|     # Check that the trace has something useful
 | |
|     cmd = f"trace-cmd report -l {trace_dat} |grep -E '(initf_|initr_)'"
 | |
|     out = util.run_and_log(cons, ['sh', '-c', cmd])
 | |
| 
 | |
|     # Format:
 | |
|     #      u-boot-1     0.....    60.805596: function:             initf_malloc
 | |
|     #      u-boot-1     0.....    60.805597: function:             initf_malloc
 | |
|     #      u-boot-1     0.....    60.805601: function:             initf_bootstage
 | |
|     #      u-boot-1     0.....    60.805607: function:             initf_bootstage
 | |
| 
 | |
|     lines = [line.replace(':', '').split() for line in out.splitlines()]
 | |
|     vals = {items[4]: float(items[2]) for items in lines if len(items) == 5}
 | |
|     base = None
 | |
|     max_delta = 0
 | |
|     for timestamp in vals.values():
 | |
|         if base:
 | |
|             max_delta = max(max_delta, timestamp - base)
 | |
|         else:
 | |
|             base = timestamp
 | |
| 
 | |
|     # Check for some expected functions
 | |
|     assert 'initf_malloc' in vals.keys()
 | |
|     assert 'initr_watchdog' in vals.keys()
 | |
|     assert 'initr_dm' in vals.keys()
 | |
| 
 | |
|     # All the functions should be executed within five seconds at most
 | |
|     assert max_delta < 5
 | |
| 
 | |
| 
 | |
| def check_funcgraph(cons, fname, proftool, map_fname, trace_dat):
 | |
|     """Check that the 'funcgraph' output works
 | |
| 
 | |
|     Args:
 | |
|         cons (ConsoleBase): U-Boot console
 | |
|         fname (str): Filename of trace file
 | |
|         proftool (str): Filename of proftool
 | |
|         map_fname (str): Filename of System.map
 | |
|         trace_dat (str): Filename of output file
 | |
| 
 | |
|     Returns:
 | |
|         int: Time taken by the first part of the initf_dm() function, in us
 | |
|     """
 | |
| 
 | |
|     # Generate the funcgraph format
 | |
|     out = util.run_and_log(
 | |
|         cons, [proftool, '-t', fname, '-o', trace_dat, '-m', map_fname,
 | |
|                'dump-ftrace', '-f', 'funcgraph'])
 | |
| 
 | |
|     # Check that the trace has what we expect
 | |
|     cmd = f'trace-cmd report -l {trace_dat} |head -n 70'
 | |
|     out = util.run_and_log(cons, ['sh', '-c', cmd])
 | |
| 
 | |
|     # First look for this:
 | |
|     #  u-boot-1     0.....   282.101360: funcgraph_entry:        0.004 us   |    initf_malloc();
 | |
|     # ...
 | |
|     #  u-boot-1     0.....   282.101369: funcgraph_entry:                   |    initf_bootstage() {
 | |
|     #  u-boot-1     0.....   282.101369: funcgraph_entry:                   |      bootstage_init() {
 | |
|     #  u-boot-1     0.....   282.101369: funcgraph_entry:                   |        dlmalloc() {
 | |
|     # ...
 | |
|     #  u-boot-1     0.....   282.101375: funcgraph_exit:         0.001 us   |        }
 | |
|     # Then look for this:
 | |
|     #  u-boot-1     0.....   282.101375: funcgraph_exit:         0.006 us   |      }
 | |
|     # Then check for this:
 | |
|     #  u-boot-1     0.....   282.101375: funcgraph_entry:        0.000 us   |    calc_reloc_ofs();
 | |
| 
 | |
|     expected_indent = None
 | |
|     found_start = False
 | |
|     found_end = False
 | |
|     upto = None
 | |
| 
 | |
|     # Look for initf_bootstage() entry and make sure we see the exit
 | |
|     # Collect the time for initf_dm()
 | |
|     for line in out.splitlines():
 | |
|         m = RE_LINE.match(line)
 | |
|         if m:
 | |
|             timestamp, indent, func, brace = m.groups()
 | |
|             if found_end:
 | |
|                 upto = func
 | |
|                 break
 | |
|             elif func == 'initf_bootstage() ':
 | |
|                 found_start = True
 | |
|                 expected_indent = indent + '  '
 | |
|             elif found_start and indent == expected_indent and brace == '}':
 | |
|                 found_end = True
 | |
| 
 | |
|     # The next function after initf_bootstage() exits should be
 | |
|     # initcall_is_event()
 | |
|     assert upto == 'calc_reloc_ofs()'
 | |
| 
 | |
|     # Now look for initf_dm() and dm_timer_init() so we can check the bootstage
 | |
|     # time
 | |
|     cmd = f"trace-cmd report -l {trace_dat} |grep -E '(initf_dm|dm_timer_init)'"
 | |
|     out = util.run_and_log(cons, ['sh', '-c', cmd])
 | |
| 
 | |
|     start_timestamp = None
 | |
|     end_timestamp = None
 | |
|     for line in out.splitlines():
 | |
|         m = RE_LINE.match(line)
 | |
|         if m:
 | |
|             timestamp, indent, func, brace = m.groups()
 | |
|             if func == 'initf_dm() ':
 | |
|                 start_timestamp = timestamp
 | |
|             elif func == 'dm_timer_init() ':
 | |
|                 end_timestamp = timestamp
 | |
|                 break
 | |
|     assert start_timestamp and end_timestamp
 | |
| 
 | |
|     # Convert the time to microseconds
 | |
|     return int((float(end_timestamp) - float(start_timestamp)) * 1000000)
 | |
| 
 | |
| 
 | |
| def check_flamegraph(cons, fname, proftool, map_fname, trace_fg):
 | |
|     """Check that the 'flamegraph' output works
 | |
| 
 | |
|     This spot checks a few call counts and estimates the time taken by the
 | |
|     initf_dm() function
 | |
| 
 | |
|     Args:
 | |
|         cons (ConsoleBase): U-Boot console
 | |
|         fname (str): Filename of trace file
 | |
|         proftool (str): Filename of proftool
 | |
|         map_fname (str): Filename of System.map
 | |
|         trace_fg (str): Filename of output file
 | |
| 
 | |
|     Returns:
 | |
|         int: Approximate number of microseconds used by the initf_dm() function
 | |
|     """
 | |
| 
 | |
|     # Generate the flamegraph format
 | |
|     out = util.run_and_log(
 | |
|         cons, [proftool, '-t', fname, '-o', trace_fg, '-m', map_fname,
 | |
|                'dump-flamegraph'])
 | |
| 
 | |
|     # We expect dm_timer_init() to be called twice: once before relocation and
 | |
|     # once after
 | |
|     look1 = 'initf_dm;dm_timer_init 1'
 | |
|     look2 = 'board_init_r;initcall_run_list;initr_dm_devices;dm_timer_init 1'
 | |
|     found = 0
 | |
|     with open(trace_fg, 'r') as fd:
 | |
|         for line in fd:
 | |
|             line = line.strip()
 | |
|             if line == look1 or line == look2:
 | |
|                 found += 1
 | |
|     assert found == 2
 | |
| 
 | |
|     # Generate the timing graph
 | |
|     out = util.run_and_log(
 | |
|         cons, [proftool, '-t', fname, '-o', trace_fg, '-m', map_fname,
 | |
|                'dump-flamegraph', '-f', 'timing'])
 | |
| 
 | |
|     # Add up all the time spend in initf_dm() and its children
 | |
|     total = 0
 | |
|     with open(trace_fg, 'r') as fd:
 | |
|         for line in fd:
 | |
|             line = line.strip()
 | |
|             if line.startswith('initf_dm'):
 | |
|                 func, val = line.split()
 | |
|                 count = int(val)
 | |
|                 total += count
 | |
|     return total
 | |
| 
 | |
| check_flamegraph
 | |
| @pytest.mark.slow
 | |
| @pytest.mark.boardspec('sandbox')
 | |
| @pytest.mark.buildconfigspec('trace')
 | |
| def test_trace(u_boot_console):
 | |
|     """Test we can build sandbox with trace, collect and process a trace"""
 | |
|     cons = u_boot_console
 | |
| 
 | |
|     if not os.path.exists(TMPDIR):
 | |
|         os.mkdir(TMPDIR)
 | |
|     proftool = os.path.join(cons.config.build_dir, 'tools', 'proftool')
 | |
|     map_fname = os.path.join(cons.config.build_dir, 'System.map')
 | |
|     trace_dat = os.path.join(TMPDIR, 'trace.dat')
 | |
|     trace_fg = os.path.join(TMPDIR, 'trace.fg')
 | |
| 
 | |
|     fname, dm_f_time = collect_trace(cons)
 | |
| 
 | |
|     check_function(cons, fname, proftool, map_fname, trace_dat)
 | |
|     trace_time = check_funcgraph(cons, fname, proftool, map_fname, trace_dat)
 | |
| 
 | |
|     # Check that bootstage and funcgraph agree to within 10 microseconds
 | |
|     diff = abs(trace_time - dm_f_time)
 | |
|     print(f'trace_time {trace_time}, dm_f_time {dm_f_time}')
 | |
|     assert diff / dm_f_time < 0.01
 | |
| 
 | |
|     fg_time = check_flamegraph(cons, fname, proftool, map_fname, trace_fg)
 | |
| 
 | |
|     # Check that bootstage and flamegraph agree to within 30%
 | |
|     # This allows for CI being slow to run
 | |
|     diff = abs(fg_time - dm_f_time)
 | |
|     assert diff / dm_f_time < 0.3
 |