Collect more statistics from the installability tester

Signed-off-by: Niels Thykier <niels@thykier.net>
master
Niels Thykier 9 years ago
parent 2a34970304
commit b4e4ba4681

@ -2888,6 +2888,10 @@ class Britney(object):
else:
self.upgrade_testing()
self.__log('> Stats from the installability tester', type="I")
for stat in self._inst_tester.stats.stats():
self.__log('> %s' % stat, type="I")
def _installability_test(self, pkg_name, pkg_version, pkg_arch, broken, to_check, nuninst_arch):
"""Test for installability of a package on an architecture

@ -18,6 +18,7 @@ from itertools import chain, filterfalse
from britney_util import iter_except
class InstallabilityTester(object):
def __init__(self, universe, revuniverse, testing, broken, essentials,
@ -53,6 +54,7 @@ class InstallabilityTester(object):
self._revuniverse = revuniverse
self._safe_set = safe_set
self._eqv_table = eqv_table
self._stats = InstallabilityStats()
# Cache of packages known to be broken - we deliberately do not
# include "broken" in it. See _optimize for more info.
@ -98,13 +100,16 @@ class InstallabilityTester(object):
testing -= eqv_set
cbroken |= eqv_set
@property
def stats(self):
return self._stats
def are_equivalent(self, p1, p2):
"""Test if p1 and p2 are equivalent
Returns True if p1 and p2 have the same "signature" in
the package dependency graph (i.e. relations can not tell
them appart sematically except for their name)
them apart semantically except for their name)
"""
eqv_table = self._eqv_table
return p1 in eqv_table and p2 in eqv_table[p1]
@ -114,7 +119,7 @@ class InstallabilityTester(object):
"""Add a binary package to "testing"
If the package is not known, this method will throw an
Keyrror.
KeyError.
"""
t = (pkg_name, pkg_version, pkg_arch)
@ -126,6 +131,8 @@ class InstallabilityTester(object):
self._testing.add(t)
elif t not in self._testing:
self._testing.add(t)
if self._cache_inst:
self._stats.cache_drops += 1
self._cache_inst = set()
if self._cache_broken:
# Re-add broken packages as some of them may now be installable
@ -164,6 +171,7 @@ class InstallabilityTester(object):
if t not in self._broken and t in self._cache_inst:
# It is in our cache (and not guaranteed to be broken) - throw out the cache
self._cache_inst = set()
self._stats.cache_drops += 1
return True
@ -177,17 +185,21 @@ class InstallabilityTester(object):
Returns False otherwise.
"""
self._stats.is_installable_calls += 1
t = (pkg_name, pkg_version, pkg_arch)
if t not in self._universe:
raise KeyError(str(t))
if t not in self._testing or t in self._broken:
self._stats.cache_hits += 1
return False
if t in self._cache_inst:
self._stats.cache_hits += 1
return True
self._stats.cache_misses += 1
return self._check_inst(t)
@ -195,8 +207,9 @@ class InstallabilityTester(object):
# See the explanation of musts, never and choices below.
cache_inst = self._cache_inst
stats = self._stats
if t in cache_inst and not never:
if musts and t in cache_inst and not never:
# use the inst cache only for direct queries/simple queries.
cache = True
if choices:
@ -214,7 +227,6 @@ class InstallabilityTester(object):
if cache:
return True
universe = self._universe
testing = self._testing
cbroken = self._cache_broken
@ -255,16 +267,16 @@ class InstallabilityTester(object):
# set conflicts with t - either way, t is f***ed
cbroken.add(t)
testing.remove(t)
stats.conflicts_essential += 1
return False
musts.update(start)
never.update(ess_never)
# curry check_loop
check_loop = partial(self._check_loop, universe, testing,
eqv_table, musts, never, choices,
eqv_table, stats, musts, never, choices,
cbroken)
# Useful things to remember:
#
# * musts and never are disjointed at all times
@ -278,7 +290,7 @@ class InstallabilityTester(object):
#
# * check never includes choices (these are always in choices)
#
# * A package is installable if never and musts are disjoined
# * A package is installable if never and musts are disjointed
# and both check and choices are empty.
# - exception: _pick_choice may determine the installability
# of t via recursion (calls _check_inst). In this case
@ -318,6 +330,7 @@ class InstallabilityTester(object):
if first:
musts.add(first)
check.add(first)
stats.choice_resolved_using_safe_set += 1
continue
# None of the safe set choices are installable, so drop them
remain -= safe_set
@ -326,11 +339,13 @@ class InstallabilityTester(object):
# the choice was reduced to one package we haven't checked - check that
check.update(remain)
musts.update(remain)
stats.choice_presolved += 1
continue
if not remain:
# all alternatives would violate the conflicts or are uninstallable
# => package is not installable
stats.choice_presolved += 1
return None
# The choice is still deferred
@ -347,7 +362,7 @@ class InstallabilityTester(object):
choices_tmp = set()
check_tmp = set([p])
if not self._check_loop(universe, testing, eqv_table,
musts_copy, never_tmp,
stats, musts_copy, never_tmp,
choices_tmp, cbroken,
check_tmp):
# p cannot be chosen/is broken (unlikely, but ...)
@ -364,6 +379,7 @@ class InstallabilityTester(object):
# routine, but to conserve stack-space, we return
# and expect to be called again later.
musts.update(musts_copy)
stats.choice_resolved_without_restore_point += 1
return False
if not musts.isdisjoint(never_tmp):
@ -371,6 +387,7 @@ class InstallabilityTester(object):
# t uninstallable, so p is a no-go.
continue
stats.backtrace_restore_point_created += 1
# We are not sure that p is safe, setup a backtrack
# point and recurse.
never_tmp |= never
@ -387,6 +404,7 @@ class InstallabilityTester(object):
# to satisfy the dependencies, so pretend to conflict
# with it - hopefully it will reduce future choices.
never.add(p)
stats.backtrace_restore_point_used += 1
# Optimization for the last case; avoid the recursive call
# and just assume the last will lead to a solution. If it
@ -394,6 +412,7 @@ class InstallabilityTester(object):
# have to back-track anyway.
check.add(last)
musts.add(last)
stats.backtrace_last_option += 1
return False
# END _pick_choice
@ -418,11 +437,13 @@ class InstallabilityTester(object):
if verdict:
# if t is installable, then so are all packages in musts
self._cache_inst.update(musts)
stats.solved_installable += 1
else:
stats.solved_uninstallable += 1
return verdict
def _check_loop(self, universe, testing, eqv_table, musts, never,
def _check_loop(self, universe, testing, eqv_table, stats, musts, never,
choices, cbroken, check, len=len,
frozenset=frozenset):
"""Finds all guaranteed dependencies via "check".
@ -493,13 +514,19 @@ class InstallabilityTester(object):
# _build_eqv_packages_table method for more
# information on how this works.
new_cand = set(x for x in candidates if x not in possible_eqv)
stats.eqv_table_times_used += 1
for chosen in iter_except(possible_eqv.pop, KeyError):
new_cand.add(chosen)
possible_eqv -= eqv_table[chosen]
stats.eqv_table_total_number_of_alternatives_eliminated += len(candidates) - len(new_cand)
if len(new_cand) == 1:
check.update(new_cand)
musts.update(new_cand)
stats.eqv_table_reduced_to_one += 1
continue
elif len(candidates) == len(new_cand):
stats.eqv_table_reduced_by_zero += 1
candidates = frozenset(new_cand)
# defer this choice till later
choices.add(candidates)
@ -514,6 +541,7 @@ class InstallabilityTester(object):
eqv_table = self._eqv_table
cbroken = self._cache_broken
universe = self._universe
stats = self._stats
safe_set = self._safe_set
ess_base = set(x for x in self._essentials if x[2] == arch and x in testing)
@ -523,7 +551,7 @@ class InstallabilityTester(object):
not_satisified = partial(filter, start.isdisjoint)
while ess_base:
self._check_loop(universe, testing, eqv_table,
self._check_loop(universe, testing, eqv_table, stats,
start, ess_never, ess_choices,
cbroken, ess_base)
if ess_choices:
@ -575,6 +603,39 @@ class InstallabilityTester(object):
return graph_stats
class InstallabilityStats(object):
def __init__(self):
self.cache_hits = 0
self.cache_misses = 0
self.cache_drops = 0
self.backtrace_restore_point_created = 0
self.backtrace_restore_point_used = 0
self.backtrace_last_option = 0
self.choice_presolved = 0
self.choice_resolved_using_safe_set = 0
self.choice_resolved_without_restore_point = 0
self.is_installable_calls = 0
self.solved_installable = 0
self.solved_uninstallable = 0
self.conflicts_essential = 0
self.eqv_table_times_used = 0
self.eqv_table_reduced_to_one = 0
self.eqv_table_reduced_by_zero = 0
self.eqv_table_total_number_of_alternatives_eliminated = 0
def stats(self):
formats = [
"Requests - is_installable: {is_installable_calls}",
"Cache - hits: {cache_hits}, misses: {cache_misses}, drops: {cache_drops}",
"Choices - pre-solved: {choice_presolved}, safe-set: {choice_resolved_using_safe_set}, No RP: {choice_resolved_without_restore_point}",
"Backtrace - RP created: {backtrace_restore_point_created}, RP used: {backtrace_restore_point_used}, reached last option: {backtrace_last_option}",
"Solved - installable: {solved_installable}, uninstallable: {solved_uninstallable}, conflicts essential: {conflicts_essential}",
"Eqv - times used: {eqv_table_times_used}, perfect reductions: {eqv_table_reduced_to_one}, failed reductions: {eqv_table_reduced_by_zero}, total no. of alternatives pruned: {eqv_table_total_number_of_alternatives_eliminated}",
]
return [x.format(**self.__dict__) for x in formats]
class ArchStats(object):
def __init__(self):

Loading…
Cancel
Save