aboutsummaryrefslogtreecommitdiff
path: root/afdo_tools/bisection/afdo_prof_analysis.py
diff options
context:
space:
mode:
authorGeorge Burgess IV <gbiv@google.com>2019-10-03 22:58:05 -0700
committerGeorge Burgess <gbiv@chromium.org>2019-10-17 18:35:32 +0000
commit90d3658f7f8170c66f67f021df64bd421cdb7f11 (patch)
tree859c13d9fb9209eb5f8c382ce9050b5cd16742e3 /afdo_tools/bisection/afdo_prof_analysis.py
parentbce57b75d6b4109e0a18974261f8c2820eb3ce44 (diff)
downloadtoolchain-utils-90d3658f7f8170c66f67f021df64bd421cdb7f11.tar.gz
afdo_bisection: remove absl dependency
absl isn't available outside of the chroot, which makes using this script pretty difficult in some cases. The dependency isn't necessary (we only care for flags), and it's probably better if we parameterize functions/objects on the things they depend on, rather than having them reach into FLAGS. BUG=None TEST=Ran the tests Change-Id: Ic535b92ce580071ab5a346cde76f201941a748ee Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/third_party/toolchain-utils/+/1839618 Reviewed-by: Caroline Tice <cmtice@chromium.org> Tested-by: George Burgess <gbiv@chromium.org>
Diffstat (limited to 'afdo_tools/bisection/afdo_prof_analysis.py')
-rwxr-xr-xafdo_tools/bisection/afdo_prof_analysis.py128
1 files changed, 68 insertions, 60 deletions
diff --git a/afdo_tools/bisection/afdo_prof_analysis.py b/afdo_tools/bisection/afdo_prof_analysis.py
index 27ed7110..8455d2b3 100755
--- a/afdo_tools/bisection/afdo_prof_analysis.py
+++ b/afdo_tools/bisection/afdo_prof_analysis.py
@@ -13,14 +13,9 @@ to try and determine what exactly is bad about the bad profile. It does this
with three main techniques: bisecting search, range search, and rough diff-ing.
"""
-from __future__ import print_function
-
-from absl import app
-from absl import flags
-from datetime import date
-from enum import IntEnum
-from tempfile import mkstemp
+from __future__ import division, print_function
+import argparse
import json
# Pylint recommends we use "from chromite.lib import cros_logging as logging".
# Chromite specific policy message, we want to keep using the standard logging
@@ -29,29 +24,10 @@ import logging
import os
import random
import subprocess
-import sys
import time
-
-flags.DEFINE_string('good_prof', None, 'Text-based "Good" profile for'
- 'analysis')
-flags.DEFINE_string('bad_prof', None, 'Text-based "Bad" profile for' 'analysis')
-flags.DEFINE_string(
- 'external_decider', None, 'External script that, given an'
- 'AFDO profile, returns GOOD/BAD/SKIP')
-flags.DEFINE_string('analysis_output_file', None,
- 'File to output JSON results to')
-flags.DEFINE_string(
- 'state_file', '%s/afdo_analysis_state.json' % os.getcwd(),
- 'File path containing state to load from initially, and '
- 'will be overwritten with new state on each iteration')
-flags.DEFINE_boolean(
- 'no_resume', False, 'If enabled, no initial state will be '
- 'loaded and the program will run from the beginning')
-flags.DEFINE_boolean(
- 'remove_state_on_completion', False, 'If enabled, state '
- 'file will be removed once profile analysis is completed')
-flags.DEFINE_float('seed', None, 'Float specifying seed for randomness')
-FLAGS = flags.FLAGS
+from datetime import date
+from enum import IntEnum
+from tempfile import mkstemp
class StatusEnum(IntEnum):
@@ -113,11 +89,12 @@ def prof_to_tmp(prof):
class DeciderState(object):
"""Class for the external decider."""
- def __init__(self, state_file):
+ def __init__(self, state_file, external_decider, seed):
self.accumulated_results = [] # over this run of the script
+ self.external_decider = external_decider
self.saved_results = [] # imported from a previous run of this script
self.state_file = state_file
- self.seed = FLAGS.seed or time.time()
+ self.seed = seed if seed is not None else time.time()
def load_state(self):
if not os.path.exists(self.state_file):
@@ -142,8 +119,7 @@ class DeciderState(object):
def save_state(self):
state = {'seed': self.seed, 'accumulated_results': self.accumulated_results}
- fd, tmp_file = mkstemp()
- os.close(fd)
+ tmp_file = self.state_file + '.new'
with open(tmp_file, 'w') as f:
json.dump(state, f, indent=2)
os.rename(tmp_file, self.state_file)
@@ -160,7 +136,7 @@ class DeciderState(object):
filename = prof_to_tmp(prof)
try:
- return_code = subprocess.call([FLAGS.external_decider, filename])
+ return_code = subprocess.call([self.external_decider, filename])
finally:
os.remove(filename)
@@ -174,7 +150,7 @@ class DeciderState(object):
if save_run:
self.accumulated_results.append(status.value)
logging.info('Run %d of external script %s returned %s',
- len(self.accumulated_results), FLAGS.external_decider,
+ len(self.accumulated_results), self.external_decider,
status.name)
self.save_state()
return status
@@ -209,7 +185,7 @@ def bisect_profiles(decider, good, bad, common_funcs, lo, hi):
results['individuals'].append(common_funcs[lo])
return results
- mid = (lo + hi) / 2
+ mid = (lo + hi) // 2
lo_mid_prof = good.copy() # covers bad from lo:mid
mid_hi_prof = good.copy() # covers bad from mid:hi
for func in common_funcs[lo:mid]:
@@ -248,9 +224,9 @@ def bisect_profiles_wrapper(decider, good, bad, perform_check=True):
# Note that while decider is a random mock, these assertions may fail.
if perform_check:
if decider.run(good, save_run=False) != StatusEnum.GOOD_STATUS:
- raise ValueError("Supplied good profile is not actually GOOD")
+ raise ValueError('Supplied good profile is not actually GOOD')
if decider.run(bad, save_run=False) != StatusEnum.BAD_STATUS:
- raise ValueError("Supplied bad profile is not actually BAD")
+ raise ValueError('Supplied bad profile is not actually BAD')
common_funcs = sorted(func for func in good if func in bad)
if not common_funcs:
@@ -282,7 +258,7 @@ def range_search(decider, good, bad, common_funcs, lo, hi):
of functions in a reasonable timeframe.
"""
- average = lambda x, y: int(round((x + y) / 2.0))
+ average = lambda x, y: int(round((x + y) // 2.0))
def find_upper_border(good_copy, funcs, lo, hi, last_bad_val=None):
"""Finds the upper border of problematic range."""
@@ -329,7 +305,7 @@ def range_search(decider, good, bad, common_funcs, lo, hi):
random.shuffle(lo_mid_funcs)
random.shuffle(mid_hi_funcs)
else: # consider lo-mid and mid-hi separately bc must cross border
- mid = (lo + hi) / 2
+ mid = (lo + hi) // 2
lo_mid_funcs = common_funcs[lo:mid]
mid_hi_funcs = common_funcs[mid:hi]
@@ -380,20 +356,58 @@ def check_bad_not_good(decider, good, bad):
return decider.run(good_copy) == StatusEnum.BAD_STATUS
-def main(_):
-
- if not FLAGS.no_resume and FLAGS.seed: # conflicting seeds
+def parse_args():
+ parser = argparse.ArgumentParser(
+ description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
+ parser.add_argument(
+ '--good_prof',
+ required=True,
+ help='Text-based "Good" profile for analysis')
+ parser.add_argument(
+ '--bad_prof', required=True, help='Text-based "Bad" profile for analysis')
+ parser.add_argument(
+ '--external_decider',
+ required=True,
+ help='External script that, given an AFDO profile, returns '
+ 'GOOD/BAD/SKIP')
+ parser.add_argument(
+ '--analysis_output_file',
+ required=True,
+ help='File to output JSON results to')
+ parser.add_argument(
+ '--state_file',
+ default='%s/afdo_analysis_state.json' % os.getcwd(),
+ help='File path containing state to load from initially, and will be '
+ 'overwritten with new state on each iteration')
+ parser.add_argument(
+ '--no_resume',
+ action='store_true',
+ help='If enabled, no initial state will be loaded and the program will '
+ 'run from the beginning')
+ parser.add_argument(
+ '--remove_state_on_completion',
+ action='store_true',
+ help='If enabled, state file will be removed once profile analysis is '
+ 'completed')
+ parser.add_argument(
+ '--seed', type=float, help='Float specifying seed for randomness')
+ return parser.parse_args()
+
+
+def main(flags):
+ if not flags.no_resume and flags.seed: # conflicting seeds
raise RuntimeError('Ambiguous seed value; do not resume from existing '
'state and also specify seed by command line flag')
- decider = DeciderState(FLAGS.state_file)
- if not FLAGS.no_resume:
+ decider = DeciderState(
+ flags.state_file, flags.external_decider, seed=flags.seed)
+ if not flags.no_resume:
decider.load_state()
random.seed(decider.seed)
- with open(FLAGS.good_prof) as good_f:
+ with open(flags.good_prof) as good_f:
good_items = text_to_json(good_f)
- with open(FLAGS.bad_prof) as bad_f:
+ with open(flags.bad_prof) as bad_f:
bad_items = text_to_json(bad_f)
bisect_results = bisect_profiles_wrapper(decider, good_items, bad_items)
@@ -406,25 +420,19 @@ def main(_):
'good_only_functions': gnb_result,
'bad_only_functions': bng_result
}
- with open(FLAGS.analysis_output_file, 'wb') as f:
+ with open(flags.analysis_output_file, 'wb') as f:
json.dump(results, f, indent=2)
- if FLAGS.remove_state_on_completion:
- os.remove(FLAGS.state_file)
+ if flags.remove_state_on_completion:
+ os.remove(flags.state_file)
logging.info('Removed state file %s following completion of script...',
- FLAGS.state_file)
+ flags.state_file)
else:
- completed_state_file = '%s.completed.%s' % (FLAGS.state_file,
+ completed_state_file = '%s.completed.%s' % (flags.state_file,
str(date.today()))
- os.rename(FLAGS.state_file, completed_state_file)
+ os.rename(flags.state_file, completed_state_file)
logging.info('Stored completed state file as %s...', completed_state_file)
return results
if __name__ == '__main__':
- flags.mark_flag_as_required('good_prof')
- flags.mark_flag_as_required('bad_prof')
- flags.mark_flag_as_required('external_decider')
- flags.mark_flag_as_required('analysis_output_file')
- app.run(main)
-else:
- FLAGS(sys.argv)
+ main(parse_args())