mirror of
				https://git.launchpad.net/~ubuntu-release/britney/+git/britney2-ubuntu
				synced 2025-11-04 02:24:24 +00:00 
			
		
		
		
	Collect more statistics from the installability tester
Signed-off-by: Niels Thykier <niels@thykier.net>
This commit is contained in:
		
							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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user