diff options
author | dtu@chromium.org <dtu@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-03-15 00:55:42 +0000 |
---|---|---|
committer | dtu@chromium.org <dtu@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-03-15 00:55:42 +0000 |
commit | fbcf7ab20679679f9c4f589a34df250a01f12cdf (patch) | |
tree | c329deb4d293c8b69d63e29f6a37fda48ca842c9 /tools/perf | |
parent | 433f7d8c6904ea68aa8774b2305b865e4cac4048 (diff) | |
download | chromium_src-fbcf7ab20679679f9c4f589a34df250a01f12cdf.zip chromium_src-fbcf7ab20679679f9c4f589a34df250a01f12cdf.tar.gz chromium_src-fbcf7ab20679679f9c4f589a34df250a01f12cdf.tar.bz2 |
[telemetry] Add command_line.ArgumentHandlerMixIn class.
The beginnings of a new way to handle command-line options. In the future, all classes that add command-line options must implement ArgumentHandlerMixIn.
Usage: Subclass ArgumentHandlerMixIn. In AddCommandLineArgs, set the command-line options. In ProcessCommandLineArgs, store them in private static class variables. The class can retrieve its own arguments at any time - yes, it's global state, but command-line arguments are always global state anyway, and it's contained within that class.
- Keeps declaration and usage of options close to each other, by encapsulating them in the same class.
- Enforces that all arguments must be available at parse time without depending on other arguments, by making all methods @classmethods.
- The ProcessCommandLineArgs() method gives Handlers a place to validate their command-line options before performing more expensive setup work.
- Removes the need to pass the options parameter around everywhere.
- Allows us to gradually break down Finder/BrowserOptions. Smooths the transition to argparse.
BUG=163294, 350833
TEST=Unit tests, ran cloud_storage, run_benchmark, run_measurement, and run_gpu_tests with a bunch of different argument types.
Review URL: https://codereview.chromium.org/187413003
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@257276 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'tools/perf')
-rw-r--r-- | tools/perf/measurements/endure.py | 45 | ||||
-rw-r--r-- | tools/perf/measurements/loading_profile.py | 9 | ||||
-rw-r--r-- | tools/perf/measurements/measurement_unittest.py | 11 | ||||
-rw-r--r-- | tools/perf/measurements/page_cycler.py | 26 | ||||
-rw-r--r-- | tools/perf/measurements/page_cycler_unittest.py | 33 | ||||
-rw-r--r-- | tools/perf/measurements/rasterize_and_record.py | 26 | ||||
-rw-r--r-- | tools/perf/measurements/rasterize_and_record_micro.py | 21 | ||||
-rw-r--r-- | tools/perf/measurements/record_per_area.py | 8 | ||||
-rw-r--r-- | tools/perf/measurements/skpicture_printer.py | 17 | ||||
-rw-r--r-- | tools/perf/measurements/startup.py | 13 | ||||
-rw-r--r-- | tools/perf/measurements/thread_times.py | 3 | ||||
-rw-r--r-- | tools/perf/measurements/timeline_based_measurement.py | 3 |
12 files changed, 115 insertions, 100 deletions
diff --git a/tools/perf/measurements/endure.py b/tools/perf/measurements/endure.py index e4996eb..80c2506 100644 --- a/tools/perf/measurements/endure.py +++ b/tools/perf/measurements/endure.py @@ -53,14 +53,8 @@ class Endure(page_measurement.PageMeasurement): self._iterations = 0 self._last_sample_iterations = 0 - # Interval between stats sampling. One of these attributes will be set when - # the perf stats interval option is parsed. The other shall remain as None. - self._interval_seconds = None - self._interval_iterations = None - - def AddCommandLineOptions(self, parser): - # TODO(tdu): When ProcessCommandLine is added to replace this method, - # move the logic in _ParseIntervalOption there to ProcessCommandLine. + @classmethod + def AddCommandLineArgs(cls, parser): group = optparse.OptionGroup(parser, 'Endure options') group.add_option('--perf-stats-interval', dest='perf_stats_interval', @@ -71,6 +65,23 @@ class Endure(page_measurement.PageMeasurement): 'of iterations') parser.add_option_group(group) + @classmethod + def ProcessCommandLineArgs(cls, parser, args): + """Parses the --perf-stats-interval option that was passed in.""" + cls._interval_seconds = None + cls._interval_iterations = None + + match = re.match('([0-9]+)([sS]?)$', args.perf_stats_interval) + if not match: + parser.error('Invalid value for --perf-stats-interval: %s' % + args.perf_stats_interval) + + if match.group(2): + cls._interval_seconds = int(match.group(1)) + else: + cls._interval_iterations = int(match.group(1)) + assert cls._interval_seconds or cls._interval_iterations + def DidStartBrowser(self, browser): """Saves the Browser object. Called after the browser is started.""" self._browser = browser @@ -95,11 +106,6 @@ class Endure(page_measurement.PageMeasurement): def MeasurePage(self, page, tab, results): """Takes a sample and adds a result if enough time has passed.""" - # Parse the interval option, setting either or seconds or iterations. - # This is done here because self.options is not set when any of the above - # methods are run. - self._ParseIntervalOption() - # Check whether the sample interval is specified in seconds or iterations, # and take a sample if it's time. self._iterations += 1 @@ -118,19 +124,6 @@ class Endure(page_measurement.PageMeasurement): self._SampleStats(tab, results, iterations=self._iterations) self._last_sample_iterations = self._iterations - def _ParseIntervalOption(self): - """Parses the --perf-stats-interval option that was passed in.""" - if self._interval_seconds or self._interval_iterations: - return - interval = self.options.perf_stats_interval - match = re.match('([0-9]+)([sS]?)$', interval) - assert match, ('Invalid value for --perf-stats-interval: %s' % interval) - if match.group(2): - self._interval_seconds = int(match.group(1)) - else: - self._interval_iterations = int(match.group(1)) - assert self._interval_seconds or self._interval_iterations - def _SampleStats(self, tab, results, seconds=None, iterations=None): """Records information and add it to the results.""" diff --git a/tools/perf/measurements/loading_profile.py b/tools/perf/measurements/loading_profile.py index 1e9db77..06ddc5e 100644 --- a/tools/perf/measurements/loading_profile.py +++ b/tools/perf/measurements/loading_profile.py @@ -17,12 +17,9 @@ class LoadingProfile(page_measurement.PageMeasurement): def results_are_the_same_on_every_page(self): return False - def AddCommandLineOptions(self, parser): - # In order to change the default of an option, we must remove and re-add it. - page_repeat_option = parser.get_option('--page-repeat') - page_repeat_option.default = 2 - parser.remove_option('--page-repeat') - parser.add_option(page_repeat_option) + @classmethod + def AddCommandLineArgs(cls, parser): + parser.set_default('page_repeat', 2) def CustomizeBrowserOptions(self, options): if not perf_profiler.PerfProfiler.is_supported(browser_type='any'): diff --git a/tools/perf/measurements/measurement_unittest.py b/tools/perf/measurements/measurement_unittest.py index 0e839b0..fe51d32 100644 --- a/tools/perf/measurements/measurement_unittest.py +++ b/tools/perf/measurements/measurement_unittest.py @@ -56,7 +56,18 @@ class MeasurementUnitTest(unittest.TestCase): return ps logging.info('running: %s', benchmark) + + # Set the benchmark's default arguments. options = options_for_unittests.GetCopy() options.output_format = 'none' + parser = options.CreateParser() + + benchmark.AddCommandLineArgs(parser) + test.AddCommandLineArgs(parser) + options.MergeDefaultValues(parser.get_default_values()) + + benchmark.ProcessCommandLineArgs(None, options) + test.ProcessCommandLineArgs(None, options) + self.assertEqual(0, SinglePageBenchmark().Run(options), msg='Failed: %s' % benchmark) diff --git a/tools/perf/measurements/page_cycler.py b/tools/perf/measurements/page_cycler.py index 4757df8..01a4628 100644 --- a/tools/perf/measurements/page_cycler.py +++ b/tools/perf/measurements/page_cycler.py @@ -35,8 +35,6 @@ class PageCycler(page_measurement.PageMeasurement): 'page_cycler.js'), 'r') as f: self._page_cycler_js = f.read() - self._record_v8_object_stats = False - self._report_speed_index = False self._speedindex_metric = speedindex.SpeedIndexMetric() self._memory_metric = None self._power_metric = power.PowerMetric() @@ -45,14 +43,9 @@ class PageCycler(page_measurement.PageMeasurement): self._cold_run_start_index = None self._has_loaded_page = collections.defaultdict(int) - def AddCommandLineOptions(self, parser): - # The page cyclers should default to 10 iterations. In order to change the - # default of an option, we must remove and re-add it. - # TODO: Remove this after transition to run_benchmark. - pageset_repeat_option = parser.get_option('--pageset-repeat') - pageset_repeat_option.default = 10 - parser.remove_option('--pageset-repeat') - parser.add_option(pageset_repeat_option) + @classmethod + def AddCommandLineArgs(cls, parser): + parser.set_default('pageset_repeat', 10) parser.add_option('--v8-object-stats', action='store_true', @@ -65,6 +58,11 @@ class PageCycler(page_measurement.PageMeasurement): parser.add_option('--cold-load-percent', type='int', help='%d of page visits for which a cold load is forced') + @classmethod + def ProcessCommandLineArgs(cls, parser, args): + cls._record_v8_object_stats = args.v8_object_stats + cls._report_speed_index = args.report_speed_index + def DidStartBrowser(self, browser): """Initialize metrics once right after the browser has been launched.""" self._memory_metric = memory.MemoryMetric(browser) @@ -101,14 +99,12 @@ class PageCycler(page_measurement.PageMeasurement): iometric.IOMetric.CustomizeBrowserOptions(options) options.AppendExtraBrowserArgs('--js-flags=--expose_gc') - if options.v8_object_stats: - self._record_v8_object_stats = True + if self._record_v8_object_stats: v8_object_stats.V8ObjectStatsMetric.CustomizeBrowserOptions(options) - - if options.report_speed_index: - self._report_speed_index = True + if self._report_speed_index: self._speedindex_metric.CustomizeBrowserOptions(options) + # TODO: Move the rest of this method to ProcessCommandLineArgs. cold_runs_percent_set = (options.cold_load_percent != None) # Handle requests for cold cache runs if (cold_runs_percent_set and diff --git a/tools/perf/measurements/page_cycler_unittest.py b/tools/perf/measurements/page_cycler_unittest.py index af0df94..37f8dc7 100644 --- a/tools/perf/measurements/page_cycler_unittest.py +++ b/tools/perf/measurements/page_cycler_unittest.py @@ -1,19 +1,22 @@ # Copyright 2013 The Chromium 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 unittest -from measurements import page_cycler from telemetry.core import browser_options from telemetry.page import page_measurement_results from telemetry.unittest import simple_mock +from measurements import page_cycler + + # Allow testing protected members in the unit test. # pylint: disable=W0212 -# Used instead of simple_mock.MockObject so that the precise order and -# number of calls need not be specified. class MockMemoryMetric(object): + """Used instead of simple_mock.MockObject so that the precise order and + number of calls need not be specified.""" def __init__(self): pass @@ -29,13 +32,15 @@ class MockMemoryMetric(object): def AddSummaryResults(self, tab, results): pass -# Used to mock loading a page. + class FakePage(object): + """Used to mock loading a page.""" def __init__(self, url): self.url = url -# Used to mock a browser tab. + class FakeTab(object): + """Used to mock a browser tab.""" def __init__(self): self.clear_cache_calls = 0 def ClearCache(self, force=False): @@ -46,14 +51,16 @@ class FakeTab(object): def WaitForJavaScriptExpression(self, _, __): pass + class PageCyclerUnitTest(unittest.TestCase): - def setupCycler(self, args, setup_memory_module=False): + def SetUpCycler(self, args, setup_memory_module=False): cycler = page_cycler.PageCycler() options = browser_options.BrowserFinderOptions() parser = options.CreateParser() - cycler.AddCommandLineOptions(parser) + cycler.AddCommandLineArgs(parser) parser.parse_args(args) + cycler.ProcessCommandLineArgs(parser, options) cycler.CustomizeBrowserOptions(options) if setup_memory_module: @@ -75,17 +82,17 @@ class PageCyclerUnitTest(unittest.TestCase): return cycler def testOptionsColdLoadNoArgs(self): - cycler = self.setupCycler([]) + cycler = self.SetUpCycler([]) self.assertEquals(cycler._cold_run_start_index, 10) def testOptionsColdLoadPagesetRepeat(self): - cycler = self.setupCycler(['--pageset-repeat=20', '--page-repeat=2']) + cycler = self.SetUpCycler(['--pageset-repeat=20', '--page-repeat=2']) self.assertEquals(cycler._cold_run_start_index, 40) def testOptionsColdLoadRequested(self): - cycler = self.setupCycler(['--pageset-repeat=21', '--page-repeat=2', + cycler = self.SetUpCycler(['--pageset-repeat=21', '--page-repeat=2', '--cold-load-percent=40']) self.assertEquals(cycler._cold_run_start_index, 26) @@ -93,7 +100,7 @@ class PageCyclerUnitTest(unittest.TestCase): def testIncompatibleOptions(self): exception_seen = False try: - self.setupCycler(['--pageset-repeat=20s', '--page-repeat=2s', + self.SetUpCycler(['--pageset-repeat=20s', '--page-repeat=2s', '--cold-load-percent=40']) except Exception as e: exception_seen = True @@ -103,7 +110,7 @@ class PageCyclerUnitTest(unittest.TestCase): self.assertTrue(exception_seen) def testCacheHandled(self): - cycler = self.setupCycler(['--pageset-repeat=5', + cycler = self.SetUpCycler(['--pageset-repeat=5', '--cold-load-percent=50'], True) @@ -133,7 +140,7 @@ class PageCyclerUnitTest(unittest.TestCase): cycler.DidNavigateToPage(page, tab) def testColdWarm(self): - cycler = self.setupCycler(['--pageset-repeat=3'], True) + cycler = self.SetUpCycler(['--pageset-repeat=3'], True) pages = [FakePage("http://fakepage1.com"), FakePage("http://fakepage2.com")] tab = FakeTab() results = page_measurement_results.PageMeasurementResults() diff --git a/tools/perf/measurements/rasterize_and_record.py b/tools/perf/measurements/rasterize_and_record.py index 5e4bd75..b621ead 100644 --- a/tools/perf/measurements/rasterize_and_record.py +++ b/tools/perf/measurements/rasterize_and_record.py @@ -22,19 +22,20 @@ class RasterizeAndRecord(page_measurement.PageMeasurement): self._metrics = None self._compositing_features_enabled = False - def AddCommandLineOptions(self, parser): - parser.add_option('--raster-record-repeat', dest='raster_record_repeat', + @classmethod + def AddCommandLineArgs(cls, parser): + parser.add_option('--raster-record-repeat', type='int', default=20, - help='Repetitions in raster and record loops.' + - 'Higher values reduce variance, but can cause' + + help='Repetitions in raster and record loops.' + 'Higher values reduce variance, but can cause' 'instability (timeouts, event buffer overflows, etc.).') - parser.add_option('--start-wait-time', dest='start_wait_time', + parser.add_option('--start-wait-time', type='float', default=5, - help='Wait time before the benchmark is started ' + + help='Wait time before the benchmark is started ' '(must be long enought to load all content)') - parser.add_option('--stop-wait-time', dest='stop_wait_time', + parser.add_option('--stop-wait-time', type='float', default=15, - help='Wait time before measurement is taken ' + + help='Wait time before measurement is taken ' '(must be long enough to render one frame)') def CustomizeBrowserOptions(self, options): @@ -43,8 +44,7 @@ class RasterizeAndRecord(page_measurement.PageMeasurement): # de-scheduled. options.AppendExtraBrowserArgs([ '--enable-gpu-benchmarking', - '--slow-down-raster-scale-factor=%d' % int( - options.raster_record_repeat), + '--slow-down-raster-scale-factor=%d' % options.raster_record_repeat, # Enable impl-side-painting. Current version of benchmark only works for # this mode. '--enable-impl-side-painting', @@ -72,7 +72,7 @@ class RasterizeAndRecord(page_measurement.PageMeasurement): # Wait until the page has loaded and come to a somewhat steady state. # Needs to be adjusted for every device (~2 seconds for workstation). - time.sleep(float(self.options.start_wait_time)) + time.sleep(self.options.start_wait_time) # Render one frame before we start gathering a trace. On some pages, the # first frame requested has more variance in the number of pixels @@ -84,7 +84,7 @@ class RasterizeAndRecord(page_measurement.PageMeasurement): 'window.__rafFired = true;' '});') - time.sleep(float(self.options.stop_wait_time)) + time.sleep(self.options.stop_wait_time) tab.browser.StartTracing('webkit.console,benchmark', 60) tab.ExecuteJavaScript( @@ -98,7 +98,7 @@ class RasterizeAndRecord(page_measurement.PageMeasurement): # Needs to be adjusted for every device and for different # raster_record_repeat counts. # TODO(ernstm): replace by call-back. - time.sleep(float(self.options.stop_wait_time)) + time.sleep(self.options.stop_wait_time) tab.ExecuteJavaScript( 'console.timeEnd("' + TIMELINE_MARKER + '")') diff --git a/tools/perf/measurements/rasterize_and_record_micro.py b/tools/perf/measurements/rasterize_and_record_micro.py index 34e36f4..cd9832f6 100644 --- a/tools/perf/measurements/rasterize_and_record_micro.py +++ b/tools/perf/measurements/rasterize_and_record_micro.py @@ -14,22 +14,23 @@ class RasterizeAndRecordMicro(page_measurement.PageMeasurement): super(RasterizeAndRecordMicro, self).__init__('', True) self._compositing_features_enabled = False - def AddCommandLineOptions(self, parser): - parser.add_option('--start-wait-time', dest='start_wait_time', + @classmethod + def AddCommandLineArgs(cls, parser): + parser.add_option('--start-wait-time', type='float', default=2, - help='Wait time before the benchmark is started ' + + help='Wait time before the benchmark is started ' '(must be long enought to load all content)') - parser.add_option('--rasterize-repeat', dest='rasterize_repeat', + parser.add_option('--rasterize-repeat', type='int', default=100, - help='Repeat each raster this many times. Increase ' + + help='Repeat each raster this many times. Increase ' 'this value to reduce variance.') - parser.add_option('--record-repeat', dest='record_repeat', + parser.add_option('--record-repeat', type='int', default=100, - help='Repeat each record this many times. Increase ' + + help='Repeat each record this many times. Increase ' 'this value to reduce variance.') - parser.add_option('--timeout', dest='timeout', + parser.add_option('--timeout', type='int', default=120, - help='The length of time to wait for the micro ' + + help='The length of time to wait for the micro ' 'benchmark to finish, expressed in seconds.') parser.add_option('--report-detailed-results', action='store_true', @@ -68,7 +69,7 @@ class RasterizeAndRecordMicro(page_measurement.PageMeasurement): tab.WaitForJavaScriptExpression("document.readyState == 'complete'", 10) except TimeoutException: pass - time.sleep(float(self.options.start_wait_time)) + time.sleep(self.options.start_wait_time) record_repeat = self.options.record_repeat rasterize_repeat = self.options.rasterize_repeat diff --git a/tools/perf/measurements/record_per_area.py b/tools/perf/measurements/record_per_area.py index 894f8fb..5eccaf8 100644 --- a/tools/perf/measurements/record_per_area.py +++ b/tools/perf/measurements/record_per_area.py @@ -11,10 +11,10 @@ class RecordPerArea(page_measurement.PageMeasurement): def __init__(self): super(RecordPerArea, self).__init__('', True) - def AddCommandLineOptions(self, parser): - parser.add_option('--start-wait-time', dest='start_wait_time', + def AddCommandLineArgs(self, parser): + parser.add_option('--start-wait-time', type='float', default=2, - help='Wait time before the benchmark is started ' + + help='Wait time before the benchmark is started ' '(must be long enought to load all content)') def CustomizeBrowserOptions(self, options): @@ -29,7 +29,7 @@ class RecordPerArea(page_measurement.PageMeasurement): def MeasurePage(self, page, tab, results): # Wait until the page has loaded and come to a somewhat steady state. # Needs to be adjusted for every device (~2 seconds for workstation). - time.sleep(float(self.options.start_wait_time)) + time.sleep(self.options.start_wait_time) # Enqueue benchmark tab.ExecuteJavaScript(""" diff --git a/tools/perf/measurements/skpicture_printer.py b/tools/perf/measurements/skpicture_printer.py index e039867..152d3ec 100644 --- a/tools/perf/measurements/skpicture_printer.py +++ b/tools/perf/measurements/skpicture_printer.py @@ -11,10 +11,17 @@ _JS = 'chrome.gpuBenchmarking.printToSkPicture("{0}");' class SkpicturePrinter(page_measurement.PageMeasurement): - def AddCommandLineOptions(self, parser): + @classmethod + def AddCommandLineArgs(cls, parser): parser.add_option('-s', '--skp-outdir', help='Output directory for the SKP files') + @classmethod + def ProcessCommandLineArgs(cls, parser, args): + if not args.skp_outdir: + parser.error('Please specify --skp-outdir') + cls._skp_outdir = args.skp_outdir + def CustomizeBrowserOptions(self, options): options.AppendExtraBrowserArgs(['--enable-gpu-benchmarking', '--no-sandbox', @@ -26,13 +33,9 @@ class SkpicturePrinter(page_measurement.PageMeasurement): raise page_measurement.MeasurementFailure( 'SkPicture printing not supported on this platform') - skp_outdir = self.options.skp_outdir - if not skp_outdir: - raise Exception('Please specify --skp-outdir') - outpath = os.path.abspath( - os.path.join(skp_outdir, - page.file_safe_name)) # Replace win32 path separator char '\' with '\\'. + outpath = os.path.abspath( + os.path.join(self._skp_outdir, page.file_safe_name)) js = _JS.format(outpath.replace('\\', '\\\\')) tab.EvaluateJavaScript(js) pictures = glob.glob(os.path.join(outpath, '*.skp')) diff --git a/tools/perf/measurements/startup.py b/tools/perf/measurements/startup.py index 98a1ded..6f15b20 100644 --- a/tools/perf/measurements/startup.py +++ b/tools/perf/measurements/startup.py @@ -19,17 +19,22 @@ class Startup(page_measurement.PageMeasurement): super(Startup, self).__init__(needs_browser_restart_after_each_page=True, action_name_to_run=action_name_to_run) - def AddCommandLineOptions(self, parser): + @classmethod + def AddCommandLineArgs(cls, parser): parser.add_option('--cold', action='store_true', help='Clear the OS disk cache before performing the test') parser.add_option('--warm', action='store_true', help='Start up with everything already cached') - def CustomizeBrowserOptions(self, options): + @classmethod + def ProcessCommandLineArgs(cls, parser, args): + pass # TODO: Once the bots start running benchmarks, enforce that either --warm # or --cold is explicitly specified. - # assert options.warm != options.cold, \ - # "You must specify either --warm or --cold" + # if args.warm == args.cold: + # parser.error('You must specify either --warm or --cold') + + def CustomizeBrowserOptions(self, options): if options.cold: browser_options = options.browser_options browser_options.clear_sytem_cache_for_browser_and_profile_on_start = True diff --git a/tools/perf/measurements/thread_times.py b/tools/perf/measurements/thread_times.py index 34d3495..2ec6530 100644 --- a/tools/perf/measurements/thread_times.py +++ b/tools/perf/measurements/thread_times.py @@ -10,7 +10,8 @@ class ThreadTimes(page_measurement.PageMeasurement): super(ThreadTimes, self).__init__('smoothness') self._metric = None - def AddCommandLineOptions(self, parser): + @classmethod + def AddCommandLineArgs(cls, parser): parser.add_option('--report-silk-results', action='store_true', help='Report results relevant to silk.') parser.add_option('--report-silk-details', action='store_true', diff --git a/tools/perf/measurements/timeline_based_measurement.py b/tools/perf/measurements/timeline_based_measurement.py index a9e1ecc..1edd934 100644 --- a/tools/perf/measurements/timeline_based_measurement.py +++ b/tools/perf/measurements/timeline_based_measurement.py @@ -78,7 +78,8 @@ class TimelineBasedMeasurement(page_measurement.PageMeasurement): def __init__(self): super(TimelineBasedMeasurement, self).__init__('smoothness') - def AddCommandLineOptions(self, parser): + @classmethod + def AddCommandLineArgs(cls, parser): parser.add_option( '--overhead-level', type='choice', choices=ALL_OVERHEAD_LEVELS, |