Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Purge kernel KMPs #591

Draft
wants to merge 6 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion tools/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ FILE( GLOB ALLCC RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "*.cc" )
STRING( REPLACE ".cc" ";" APLLPROG ${ALLCC} )

# make sure not to statically linked installed tools
SET( LINKALLSYM CalculateReusableBlocks DownloadFiles )
SET( LINKALLSYM CalculateReusableBlocks DownloadFiles zypp-runpurge)

FOREACH( loop_var ${APLLPROG} )
ADD_EXECUTABLE( ${loop_var}
Expand Down
51 changes: 43 additions & 8 deletions tools/zypp-runpurge.cc
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ int usage( const argparse::Options & options_r, int return_r = 0 )

int main ( int argc, char *argv[] )
{
auto z = getZYpp();

appname = Pathname::basename( argv[0] );
argparse::Options options;
Expand All @@ -34,7 +35,7 @@ int main ( int argc, char *argv[] )

auto result = options.parse( argc, argv );

if ( result.count( "help" ) || !result.count("uname") || !result.positionals().size() )
if ( result.count( "help" ) || !result.positionals().size() )
return usage( options, 1 );

const std::string &testcaseDir = result.positionals().front();
Expand All @@ -51,31 +52,65 @@ int main ( int argc, char *argv[] )

TestSetup t;
try {
t.LoadSystemAt( result.positionals().front() );
t.LoadSystemAt( "." );
} catch ( const zypp::Exception &e ) {
std::cerr << "Failed to load the testcase at " << result.positionals().front() << std::endl;
std::cerr << "Got exception: " << e << std::endl;
return 1;
}

std::string unameR;
if ( result.count("uname") ) {
unameR = result["uname"].arg();
} else {
std::cout << "No --uname provided. Guessing it from the latest kernel-default installed...." << std::endl;
const PoolItem & running( ui::Selectable::get("kernel-default")->theObj() );
if ( not running ) {
std::cerr << "Oops: No installed kernel-default." << std::endl;
return usage( options, 1 );
}
std::cout << "Guess running: " << running.asString() << endl;
const Capability unameRProvides { "kernel-uname-r" }; // indicator provides
for ( const auto & cap : running.provides() ) {
if ( cap.matches( unameRProvides ) == CapMatch::yes ) {
unameR = cap.detail().ed().asString();
break;
}
}
if ( unameR.empty() ) {
std::cerr << "Oops: Guessed kernel does not provide " << unameRProvides << std::endl;
return usage( options, 1 );
}
std::cout << "Guess --uname: " << unameR << endl;
}

std::string keepSpec = "oldest,running,latest";
if ( result.count("keepSpec") ) {
keepSpec = result["keepSpec"].arg();
}


PurgeKernels krnls;
krnls.setUnameR( result["uname"].arg() );
krnls.setUnameR( unameR );
krnls.setKeepSpec( keepSpec );
krnls.markObsoleteKernels();

const auto &makeNVRA = []( const PoolItem &pck ) -> std::string {
return pck.name() + "-" + pck.edition().asString() + "." + pck.arch().asString();
};

unsigned count = 0;
auto pool = ResPool::instance();
const filter::ByStatus toBeUninstalledFilter( &ResStatus::isToBeUninstalled );
for ( auto it = pool.byStatusBegin( toBeUninstalledFilter ); it != pool.byStatusEnd( toBeUninstalledFilter ); it++ ) {
std::cout << "Removing " << makeNVRA(*it) + (it->status().isByUser() ? " (by user)" : " (autoremoved)") << std::endl;
std::cout << "Removing " << it->asString() + (it->status().isByUser() ? " (by user)" : " (autoremoved)") << std::endl;
++count;
}
std::cout << "Purged kernels: " << count << std::endl;

const auto & clusterResult { krnls.clusterKmps() };
std::cout << clusterResult.first << endl;

const auto & purgableKmps { clusterResult.second };
std::cout << "Purgable Kmps: " << purgableKmps.size() << std::endl;
for ( const auto & kmp : purgableKmps ) {
std::cout << " purge: " << kmp << std::endl;
}

return 0;
Expand Down
204 changes: 196 additions & 8 deletions zypp/PurgeKernels.cc
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,18 @@ namespace zypp {
};
using GroupMap = std::unordered_map<std::string, GroupInfo>;

std::ostream & operator<<( std::ostream & str, const GroupInfo::GroupType & obj ) {
switch ( obj ) {
#define OUTS(t) case GroupInfo::t: return str << #t
OUTS( None );
OUTS( Kernels );
OUTS( RelatedBinaries );
OUTS( Sources );
#undef OUTS
};
return str << "[UNKNOWN_GroupType]";
}

struct PurgeKernels::Impl {

Impl() {
Expand Down Expand Up @@ -505,7 +517,8 @@ namespace zypp {
const filter::ByStatus toBeUninstalledFilter( &ResStatus::isToBeUninstalled );

// kernel flavour regex
const str::regex kernelFlavourRegex("^kernel-(.*)$");
// XXX: No dashes in flavor names
const str::regex kernelFlavourRegex("^kernel-([^-]+)(-.*)?$");

// the map of all installed kernel packages, grouped by Flavour -> Arch -> Version -> (List of all packages in that category)
// devel and source packages are grouped together
Expand Down Expand Up @@ -594,12 +607,6 @@ namespace zypp {

std::string flavour = what[1];

// XXX: No dashes in flavor names
const auto dash = flavour.find_first_of('-');
if ( dash != std::string::npos ) {
flavour = flavour.substr( 0, dash );
}

// the ident for kernels is the flavour, to also handle cases like kernel-base and kernel which should be in the same group handled together
addPackageToMap( GroupInfo::Kernels, flavour, flavour, installedKrnlPck );

Expand Down Expand Up @@ -694,4 +701,185 @@ namespace zypp {
return _pimpl->_keepSpec;
}

}
std::pair<PurgeKernels::KernelKmps,PurgeKernels::PurgableKmps> PurgeKernels::clusterKmps( const bool checkSystem )
{
using SolvableSet =std::unordered_set<sat::Solvable>;

// Whether slv is in container
auto contains = []( const SolvableSet & container_r, const sat::Solvable & slv_r ) -> bool {
return container_r.find( slv_r ) != container_r.end();
};

// Remove items from fitting which do not occur in matching
auto intersect = [&contains]( SolvableSet & fitting_r, const SolvableSet & matching_r ) {
for ( auto it = fitting_r.begin(); it != fitting_r.end(); ) {
if ( contains( matching_r, *it ) )
++it;
else
it = fitting_r.erase( it );
}
};

SolvableSet kernels; // multiversion kernels, if checkSystem is true only the ones to keep.
SolvableSet purged; // If checkSystem is true, kernels to be purged are kept here.
SolvableSet kmps; // multiversion kmps
{
const Capability idxcap { "kernel-uname-r" };
sat::WhatProvides idx { idxcap };
for ( const auto & i : idx ) {
if ( i.isSystem() != checkSystem )
continue;
if ( not checkSystem || PoolItem(i).status().staysInstalled() )
kernels.insert( i );
else
purged.insert( i );
}
}
{
const Capability idxcap { "multiversion(kernel)" };
sat::WhatProvides idx { idxcap };

StrMatcher matchMod { "kmod(*)", Match::GLOB };
auto isKmp = [&matchMod]( const sat::Solvable & slv_r ) -> bool {
for ( const auto & prov : slv_r.provides() ) {
if ( matchMod.doMatch( prov.detail().name().c_str() ) )
return true;
}
return false;
};

for ( const auto & i : idx ) {
if ( i.isSystem() != checkSystem )
continue;
if ( contains( kernels, i ) || contains( purged, i ) )
continue;
if ( isKmp( i ) ) kmps.insert( i );
// else: multiversion others
}
}
MIL << "!Kernel " << kernels.size() << ", purged " << purged.size() << ", KMPs " << kmps.size() << std::endl;

// Caching which KMP requirement is provided by what kernel (kept ones only).
std::unordered_map<Capability,SolvableSet> whatKernelProvidesCache;
auto whatKernelProvides = [&]( const Capability & req ) -> const SolvableSet & {
auto iter = whatKernelProvidesCache.find( req );
if ( iter != whatKernelProvidesCache.end() )
return iter->second; // hit
// miss:
SolvableSet & providingKernels = whatKernelProvidesCache[req];
sat::WhatProvides idx { req };
for ( const auto & i : idx ) {
if ( contains( kernels, i ) )
providingKernels.insert( i );
}
return providingKernels;
};

// using Bucket = std::set<sat::Solvable,byNVRA>; ///< fitting KMP versions
// using KmpBuckets = std::map<IdString,Bucket>; ///< KMP name : fitting KMP versions
// using KernelKmps = std::map<sat::Solvable,KmpBuckets,byNVRA>; ///< kernel : KMP name : fitting KMP versions
// using PurgableKmps = std::unordered_set<sat::Solvable>; ///< KMPs found to be superfluous

// Cluster the KMPs....
KernelKmps kernelKmps;
SolvableSet protectedKmps;

for ( const auto & kmp : kmps ) {
// Kernels satisfying all kernel related requirements
std::optional<SolvableSet> fittingKernels;
for ( const auto & req : kmp.requires() ) {
const SolvableSet & providingKernels { whatKernelProvides( req ) };
if ( providingKernels.size() == 0 )
continue; // not a kernel related requirement
if ( not fittingKernels ) {
fittingKernels = providingKernels; // initial set
} else {
intersect( *fittingKernels, providingKernels );
}
}
if ( fittingKernels ) {
for ( const auto & kernel : *fittingKernels ) {
kernelKmps[kernel][kmp.ident()].insert( kmp );
}
}
else {
// The Solvable::noSolvable entry for KMPs with no kernel requirements
kernelKmps[sat::Solvable::noSolvable][kmp.ident()].insert( kmp );
DBG << "PROTECT (no kernel requirements)" << kmp << std::endl;
protectedKmps.insert( kmp );
}
}

// Now protect the highest KMP versions per kernel.
// ==== kernel-default 5.14.21-150400.24.136.1 (i)
// drbd-kmp-default fit 3 versions(s)
// - drbd-kmp-default 9.0.30~1+git.10bee2d5_k5.14.21_150400.22-150400.1.75 (i)
// - drbd-kmp-default 9.0.30~1+git.10bee2d5_k5.14.21_150400.24.11-150400.3.2.9 (i)
// * drbd-kmp-default 9.0.30~1+git.10bee2d5_k5.14.21_150400.24.46-150400.3.4.1 (i)
for ( const auto & [kernel,bucket] : kernelKmps ) {
if ( not kernel ) // The Solvable::noSolvable entry for KMPs with no kernel requirements
continue; // (already in protectedKmps)

for ( const auto & el : bucket ) {
const auto & versions { el.second };
sat::Solvable highest { *versions.rbegin() };
DBG << "PROTECT (best)" << highest << " for " << kernel << std::endl;
protectedKmps.insert( highest );
}
}

// Build result
SolvableSet purgableKmps { kmps };
for ( const auto & kmp : protectedKmps ) {
purgableKmps.erase( kmp );
}
MIL << "PROTECTED: " << protectedKmps.size() << std::endl;
MIL << "PURGABLE: " << purgableKmps.size() << std::endl;
for ( const auto & kmp : purgableKmps ) {
DBG << "purge: " << kmp << std::endl;
}
return { kernelKmps, purgableKmps };
}

std::ostream & operator<<( std::ostream & str, const PurgeKernels::KernelKmps & obj )
{
using SolvableSet = PurgeKernels::PurgableKmps;
SolvableSet kmps;
SolvableSet protectedKmps;

for ( const auto & [kernel,bucket] : obj ) {
if ( not kernel )
str << std::endl << "==== Does not require a kept kernel" << std::endl;
else
str << std::endl << "==== " << kernel << std::endl;

for ( const auto & [kmp,versions] : bucket ) {
sat::Solvable highest { *versions.rbegin() };
if ( kernel )
protectedKmps.insert( highest );
str << " " << kmp << " fit " << versions.size() << " versions(s)" << std::endl;
for ( const auto & version : versions ) {
str << " "<<(not kernel||version==highest?"*":"-")<<" " << version << std::endl;
kmps.insert( version );
if ( not kernel )
protectedKmps.insert( version );
}
}
}

SolvableSet purgableKmps { kmps };
for ( const auto & kmp : protectedKmps ) {
purgableKmps.erase( kmp );
}

str << "KMPS: " << kmps.size() << std::endl;
str << "PROTECTED: " << protectedKmps.size() << std::endl;
str << "PURGABLE: " << purgableKmps.size() << std::endl;
for ( const auto & kmp : purgableKmps ) {
str << " purge: " << kmp << std::endl;
}

return str;
}

} // namespace zypp
Loading