* Some refactoring: put the GC options / results in separate structs.

* The garbage collector now also prints the number of blocks freed.
This commit is contained in:
Eelco Dolstra 2008-06-18 09:34:17 +00:00
parent 934c58aa38
commit a72709afd8
15 changed files with 252 additions and 166 deletions

View file

@ -578,17 +578,17 @@ void getOwnership(const Path & path)
void deletePathWrapped(const Path & path, void deletePathWrapped(const Path & path,
unsigned long long & bytesFreed) unsigned long long & bytesFreed, unsigned long long & blocksFreed)
{ {
try { try {
/* First try to delete it ourselves. */ /* First try to delete it ourselves. */
deletePath(path, bytesFreed); deletePath(path, bytesFreed, blocksFreed);
} catch (SysError & e) { } catch (SysError & e) {
/* If this failed due to a permission error, then try it with /* If this failed due to a permission error, then try it with
the setuid helper. */ the setuid helper. */
if (haveBuildUsers() && !amPrivileged()) { if (haveBuildUsers() && !amPrivileged()) {
getOwnership(path); getOwnership(path);
deletePath(path, bytesFreed); deletePath(path, bytesFreed, blocksFreed);
} else } else
throw; throw;
} }
@ -597,8 +597,8 @@ void deletePathWrapped(const Path & path,
void deletePathWrapped(const Path & path) void deletePathWrapped(const Path & path)
{ {
unsigned long long dummy; unsigned long long dummy1, dummy2;
deletePathWrapped(path, dummy); deletePathWrapped(path, dummy1, dummy2);
} }

View file

@ -439,9 +439,9 @@ Paths topoSortPaths(const PathSet & paths)
} }
void LocalStore::tryToDelete(GCAction action, const PathSet & livePaths, void LocalStore::tryToDelete(const GCOptions & options, GCResults & results,
const PathSet & tempRootsClosed, PathSet & done, PathSet & deleted, const PathSet & livePaths, const PathSet & tempRootsClosed, PathSet & done,
const Path & path, unsigned long long & bytesFreed) const Path & path)
{ {
if (done.find(path) != done.end()) return; if (done.find(path) != done.end()) return;
done.insert(path); done.insert(path);
@ -449,7 +449,7 @@ void LocalStore::tryToDelete(GCAction action, const PathSet & livePaths,
debug(format("considering deletion of `%1%'") % path); debug(format("considering deletion of `%1%'") % path);
if (livePaths.find(path) != livePaths.end()) { if (livePaths.find(path) != livePaths.end()) {
if (action == gcDeleteSpecific) if (options.action == GCOptions::gcDeleteSpecific)
throw Error(format("cannot delete path `%1%' since it is still alive") % path); throw Error(format("cannot delete path `%1%' since it is still alive") % path);
debug(format("live path `%1%'") % path); debug(format("live path `%1%'") % path);
return; return;
@ -470,15 +470,18 @@ void LocalStore::tryToDelete(GCAction action, const PathSet & livePaths,
queryReferrers(path, referrers); queryReferrers(path, referrers);
foreach (PathSet::iterator, i, referrers) foreach (PathSet::iterator, i, referrers)
if (*i != path) if (*i != path)
tryToDelete(action, livePaths, tempRootsClosed, done, deleted, *i, bytesFreed); tryToDelete(options, results, livePaths, tempRootsClosed, done, *i);
debug(format("dead path `%1%'") % path); debug(format("dead path `%1%'") % path);
deleted.insert(path); results.paths.insert(path);
/* If just returning the set of dead paths, we also return the /* If just returning the set of dead paths, we also return the
space that would be freed if we deleted them. */ space that would be freed if we deleted them. */
if (action == gcReturnDead) { if (options.action == GCOptions::gcReturnDead) {
bytesFreed += computePathSize(path); unsigned long long bytesFreed, blocksFreed;
computePathSize(path, bytesFreed, blocksFreed);
results.bytesFreed += bytesFreed;
results.blocksFreed += blocksFreed;
return; return;
} }
@ -504,9 +507,10 @@ void LocalStore::tryToDelete(GCAction action, const PathSet & livePaths,
printMsg(lvlInfo, format("deleting `%1%'") % path); printMsg(lvlInfo, format("deleting `%1%'") % path);
/* Okay, it's safe to delete. */ /* Okay, it's safe to delete. */
unsigned long long freed; unsigned long long bytesFreed, blocksFreed;
deleteFromStore(path, freed); deleteFromStore(path, bytesFreed, blocksFreed);
bytesFreed += freed; results.bytesFreed += bytesFreed;
results.blocksFreed += blocksFreed;
#ifndef __CYGWIN__ #ifndef __CYGWIN__
if (fdLock != -1) if (fdLock != -1)
@ -516,12 +520,8 @@ void LocalStore::tryToDelete(GCAction action, const PathSet & livePaths,
} }
void LocalStore::collectGarbage(GCAction action, const PathSet & pathsToDelete, void LocalStore::collectGarbage(const GCOptions & options, GCResults & results)
bool ignoreLiveness, PathSet & result, unsigned long long & bytesFreed)
{ {
result.clear();
bytesFreed = 0;
bool gcKeepOutputs = bool gcKeepOutputs =
queryBoolSetting("gc-keep-outputs", false); queryBoolSetting("gc-keep-outputs", false);
bool gcKeepDerivations = bool gcKeepDerivations =
@ -537,7 +537,7 @@ void LocalStore::collectGarbage(GCAction action, const PathSet & pathsToDelete,
/* Find the roots. Since we've grabbed the GC lock, the set of /* Find the roots. Since we've grabbed the GC lock, the set of
permanent roots cannot increase now. */ permanent roots cannot increase now. */
printMsg(lvlError, format("finding garbage collector roots...")); printMsg(lvlError, format("finding garbage collector roots..."));
Roots rootMap = ignoreLiveness ? Roots() : nix::findRoots(true); Roots rootMap = options.ignoreLiveness ? Roots() : nix::findRoots(true);
PathSet roots; PathSet roots;
for (Roots::iterator i = rootMap.begin(); i != rootMap.end(); ++i) for (Roots::iterator i = rootMap.begin(); i != rootMap.end(); ++i)
@ -547,11 +547,11 @@ void LocalStore::collectGarbage(GCAction action, const PathSet & pathsToDelete,
NIX_ROOT_FINDER environment variable. This is typically used NIX_ROOT_FINDER environment variable. This is typically used
to add running programs to the set of roots (to prevent them to add running programs to the set of roots (to prevent them
from being garbage collected). */ from being garbage collected). */
if (!ignoreLiveness) if (!options.ignoreLiveness)
addAdditionalRoots(roots); addAdditionalRoots(roots);
if (action == gcReturnRoots) { if (options.action == GCOptions::gcReturnRoots) {
result = roots; results.paths = roots;
return; return;
} }
@ -595,8 +595,8 @@ void LocalStore::collectGarbage(GCAction action, const PathSet & pathsToDelete,
} }
} }
if (action == gcReturnLive) { if (options.action == GCOptions::gcReturnLive) {
result = livePaths; results.paths = livePaths;
return; return;
} }
@ -633,27 +633,25 @@ void LocalStore::collectGarbage(GCAction action, const PathSet & pathsToDelete,
paths. */ paths. */
printMsg(lvlError, format("reading the Nix store...")); printMsg(lvlError, format("reading the Nix store..."));
PathSet storePaths; PathSet storePaths;
if (action != gcDeleteSpecific) { if (options.action != GCOptions::gcDeleteSpecific) {
Paths entries = readDirectory(nixStore); Paths entries = readDirectory(nixStore);
for (Paths::iterator i = entries.begin(); i != entries.end(); ++i) for (Paths::iterator i = entries.begin(); i != entries.end(); ++i)
storePaths.insert(canonPath(nixStore + "/" + *i)); storePaths.insert(canonPath(nixStore + "/" + *i));
} else { } else {
for (PathSet::iterator i = pathsToDelete.begin(); foreach (PathSet::iterator, i, options.pathsToDelete) {
i != pathsToDelete.end(); ++i)
{
assertStorePath(*i); assertStorePath(*i);
storePaths.insert(*i); storePaths.insert(*i);
} }
} }
/* Try to delete store paths in the topologically sorted order. */ /* Try to delete store paths in the topologically sorted order. */
printMsg(lvlError, action == gcReturnDead printMsg(lvlError, options.action == GCOptions::gcReturnDead
? format("looking for garbage...") ? format("looking for garbage...")
: format("deleting garbage...")); : format("deleting garbage..."));
PathSet done; PathSet done;
foreach (PathSet::iterator, i, storePaths) foreach (PathSet::iterator, i, storePaths)
tryToDelete(action, livePaths, tempRootsClosed, done, result, *i, bytesFreed); tryToDelete(options, results, livePaths, tempRootsClosed, done, *i);
} }

View file

@ -851,7 +851,8 @@ Path LocalStore::importPath(bool requireSignature, Source & source)
} }
void LocalStore::deleteFromStore(const Path & path, unsigned long long & bytesFreed) void LocalStore::deleteFromStore(const Path & path, unsigned long long & bytesFreed,
unsigned long long & blocksFreed)
{ {
bytesFreed = 0; bytesFreed = 0;
@ -871,7 +872,7 @@ void LocalStore::deleteFromStore(const Path & path, unsigned long long & bytesFr
invalidatePath(path); invalidatePath(path);
} }
deletePathWrapped(path, bytesFreed); deletePathWrapped(path, bytesFreed, blocksFreed);
} }

View file

@ -25,10 +25,11 @@ struct OptimiseStats
unsigned long sameContents; unsigned long sameContents;
unsigned long filesLinked; unsigned long filesLinked;
unsigned long long bytesFreed; unsigned long long bytesFreed;
unsigned long long blocksFreed;
OptimiseStats() OptimiseStats()
{ {
totalFiles = sameContents = filesLinked = 0; totalFiles = sameContents = filesLinked = 0;
bytesFreed = 0; bytesFreed = blocksFreed = 0;
} }
}; };
@ -89,11 +90,11 @@ public:
Roots findRoots(); Roots findRoots();
void collectGarbage(GCAction action, const PathSet & pathsToDelete, void collectGarbage(const GCOptions & options, GCResults & results);
bool ignoreLiveness, PathSet & result, unsigned long long & bytesFreed);
/* Delete a path from the Nix store. */ /* Delete a path from the Nix store. */
void deleteFromStore(const Path & path, unsigned long long & bytesFreed); void deleteFromStore(const Path & path, unsigned long long & bytesFreed,
unsigned long long & blocksFreed);
/* Optimise the disk space usage of the Nix store by hard-linking /* Optimise the disk space usage of the Nix store by hard-linking
files with the same contents. */ files with the same contents. */
@ -143,10 +144,9 @@ private:
void upgradeStore12(); void upgradeStore12();
void tryToDelete(GCAction action, const PathSet & livePaths, void tryToDelete(const GCOptions & options, GCResults & results,
const PathSet & tempRootsClosed, PathSet & done, PathSet & deleted, const PathSet & livePaths, const PathSet & tempRootsClosed, PathSet & done,
const Path & path, unsigned long long & bytesFreed); const Path & path);
}; };
@ -179,7 +179,7 @@ void getOwnership(const Path & path);
/* Like deletePath(), but changes the ownership of `path' using the /* Like deletePath(), but changes the ownership of `path' using the
setuid wrapper if necessary (and possible). */ setuid wrapper if necessary (and possible). */
void deletePathWrapped(const Path & path, void deletePathWrapped(const Path & path,
unsigned long long & bytesFreed); unsigned long long & bytesFreed, unsigned long long & blocksFreed);
void deletePathWrapped(const Path & path); void deletePathWrapped(const Path & path);

View file

@ -101,6 +101,7 @@ static void hashAndLink(bool dryRun, HashToPath & hashToPath,
stats.filesLinked++; stats.filesLinked++;
stats.bytesFreed += st.st_size; stats.bytesFreed += st.st_size;
stats.blocksFreed += st.st_blocks;
} }
if (S_ISDIR(st.st_mode)) { if (S_ISDIR(st.st_mode)) {

View file

@ -372,24 +372,20 @@ Roots RemoteStore::findRoots()
} }
void RemoteStore::collectGarbage(GCAction action, const PathSet & pathsToDelete, void RemoteStore::collectGarbage(const GCOptions & options, GCResults & results)
bool ignoreLiveness, PathSet & result, unsigned long long & bytesFreed)
{ {
result.clear();
bytesFreed = 0;
writeInt(wopCollectGarbage, to); writeInt(wopCollectGarbage, to);
writeInt(action, to); writeInt(options.action, to);
writeStringSet(pathsToDelete, to); writeStringSet(options.pathsToDelete, to);
writeInt(ignoreLiveness, to); writeInt(options.ignoreLiveness, to);
writeLongLong(options.maxFreed, to);
writeInt(options.maxLinks, to);
processStderr(); processStderr();
result = readStringSet(from); results.paths = readStringSet(from);
results.bytesFreed = readLongLong(from);
/* Ugh - NAR integers are 64 bits, but read/writeInt() aren't. */ results.blocksFreed = readLongLong(from);
unsigned int lo = readInt(from);
unsigned int hi = readInt(from);
bytesFreed = (((unsigned long long) hi) << 32) | lo;
} }

View file

@ -65,8 +65,7 @@ public:
Roots findRoots(); Roots findRoots();
void collectGarbage(GCAction action, const PathSet & pathsToDelete, void collectGarbage(const GCOptions & options, GCResults & results);
bool ignoreLiveness, PathSet & result, unsigned long long & bytesFreed);
private: private:
AutoCloseFD fdSocket; AutoCloseFD fdSocket;

View file

@ -16,7 +16,27 @@ namespace nix {
typedef std::map<Path, Path> Roots; typedef std::map<Path, Path> Roots;
/* Garbage collector operation. */
struct GCOptions
{
/* Garbage collector operation:
- `gcReturnRoots': find and return the set of roots for the
garbage collector. These are the store paths symlinked to in
the `gcroots' directory.
- `gcReturnLive': return the set of paths reachable from
(i.e. in the closure of) the roots.
- `gcReturnDead': return the set of paths not reachable from
the roots.
- `gcDeleteDead': actually delete the latter set.
- `gcDeleteSpecific': delete the paths listed in
`pathsToDelete', insofar as they are not reachable.
*/
typedef enum { typedef enum {
gcReturnRoots, gcReturnRoots,
gcReturnLive, gcReturnLive,
@ -25,6 +45,54 @@ typedef enum {
gcDeleteSpecific, gcDeleteSpecific,
} GCAction; } GCAction;
GCAction action;
/* If `ignoreLiveness' is set, then reachability from the roots is
ignored (dangerous!). However, the paths must still be
unreferenced *within* the store (i.e., there can be no other
store paths that depend on them). */
bool ignoreLiveness;
/* For `gcDeleteSpecific', the paths to delete. */
PathSet pathsToDelete;
/* Stop after at least `maxFreed' bytes have been freed. */
unsigned long long maxFreed;
/* Stop after the number of hard links to the Nix store directory
has dropped to at least `maxLinks'. */
unsigned int maxLinks;
GCOptions()
{
action = gcDeleteDead;
ignoreLiveness = false;
maxFreed = ULLONG_MAX;
maxLinks = UINT_MAX;
}
};
struct GCResults
{
/* Depending on the action, the GC roots, or the paths that would
be or have been deleted. */
PathSet paths;
/* For `gcReturnDead', `gcDeleteDead' and `gcDeleteSpecific', the
number of bytes that would be or was freed. */
unsigned long long bytesFreed;
/* The number of file system blocks that would be or was freed. */
unsigned long long blocksFreed;
GCResults()
{
bytesFreed = 0;
blocksFreed = 0;
}
};
class StoreAPI class StoreAPI
{ {
@ -137,33 +205,8 @@ public:
outside of the Nix store that point to `storePath'. */ outside of the Nix store that point to `storePath'. */
virtual Roots findRoots() = 0; virtual Roots findRoots() = 0;
/* Depending on `action', this function does the following: /* Perform a garbage collection. */
virtual void collectGarbage(const GCOptions & options, GCResults & results) = 0;
- `gcReturnRoots': find and return the set of roots for the
garbage collector. These are the store paths symlinked to in
the `gcroots' directory.
- `gcReturnLive': return the set of paths reachable from
(i.e. in the closure of) the roots.
- `gcReturnDead': return the set of paths not reachable from
the roots.
- `gcDeleteDead': actually delete the latter set.
- `gcDeleteSpecific': delete the paths listed in
`pathsToDelete', insofar as they are not reachable.
If `ignoreLiveness' is set, then reachability from the roots is
ignored (dangerous!). However, the paths must still be
unreferenced *within* the store (i.e., there can be no other
store paths that depend on them).
For `gcReturnDead', `gcDeleteDead' and `gcDeleteSpecific', the
number of bytes that would be or was freed is returned in
`bytesFreed'. */
virtual void collectGarbage(GCAction action, const PathSet & pathsToDelete,
bool ignoreLiveness, PathSet & result, unsigned long long & bytesFreed) = 0;
}; };

View file

@ -15,24 +15,24 @@ namespace nix {
typedef enum { typedef enum {
wopQuit = 0, wopQuit = 0,
wopIsValidPath, wopIsValidPath = 1,
wopHasSubstitutes = 3, wopHasSubstitutes = 3,
wopQueryPathHash, wopQueryPathHash = 4,
wopQueryReferences, wopQueryReferences = 5,
wopQueryReferrers, wopQueryReferrers = 6,
wopAddToStore, wopAddToStore = 7,
wopAddTextToStore, wopAddTextToStore = 8,
wopBuildDerivations, wopBuildDerivations = 9,
wopEnsurePath, wopEnsurePath = 10,
wopAddTempRoot, wopAddTempRoot = 11,
wopAddIndirectRoot, wopAddIndirectRoot = 12,
wopSyncWithGC, wopSyncWithGC = 13,
wopFindRoots, wopFindRoots = 14,
wopCollectGarbage, wopExportPath = 16,
wopExportPath, wopImportPath = 17,
wopImportPath, wopQueryDeriver = 18,
wopQueryDeriver, wopSetOptions = 19,
wopSetOptions, wopCollectGarbage = 20,
} WorkerOp; } WorkerOp;

View file

@ -41,6 +41,21 @@ void writeInt(unsigned int n, Sink & sink)
} }
void writeLongLong(unsigned long long n, Sink & sink)
{
unsigned char buf[8];
buf[0] = n & 0xff;
buf[1] = (n >> 8) & 0xff;
buf[2] = (n >> 16) & 0xff;
buf[3] = (n >> 24) & 0xff;
buf[4] = (n >> 32) & 0xff;
buf[5] = (n >> 40) & 0xff;
buf[6] = (n >> 48) & 0xff;
buf[7] = (n >> 56) & 0xff;
sink(buf, sizeof(buf));
}
void writeString(const string & s, Sink & sink) void writeString(const string & s, Sink & sink)
{ {
unsigned int len = s.length(); unsigned int len = s.length();
@ -84,6 +99,22 @@ unsigned int readInt(Source & source)
} }
unsigned long long readLongLong(Source & source)
{
unsigned char buf[8];
source(buf, sizeof(buf));
return
((unsigned long long) buf[0]) |
((unsigned long long) buf[1] << 8) |
((unsigned long long) buf[2] << 16) |
((unsigned long long) buf[3] << 24) |
((unsigned long long) buf[4] << 32) |
((unsigned long long) buf[5] << 40) |
((unsigned long long) buf[6] << 48) |
((unsigned long long) buf[7] << 56);
}
string readString(Source & source) string readString(Source & source)
{ {
unsigned int len = readInt(source); unsigned int len = readInt(source);

View file

@ -95,11 +95,13 @@ struct StringSource : Source
void writePadding(unsigned int len, Sink & sink); void writePadding(unsigned int len, Sink & sink);
void writeInt(unsigned int n, Sink & sink); void writeInt(unsigned int n, Sink & sink);
void writeLongLong(unsigned long long n, Sink & sink);
void writeString(const string & s, Sink & sink); void writeString(const string & s, Sink & sink);
void writeStringSet(const StringSet & ss, Sink & sink); void writeStringSet(const StringSet & ss, Sink & sink);
void readPadding(unsigned int len, Source & source); void readPadding(unsigned int len, Source & source);
unsigned int readInt(Source & source); unsigned int readInt(Source & source);
unsigned long long readLongLong(Source & source);
string readString(Source & source); string readString(Source & source);
StringSet readStringSet(Source & source); StringSet readStringSet(Source & source);

View file

@ -229,30 +229,38 @@ void writeFile(const Path & path, const string & s)
} }
unsigned long long computePathSize(const Path & path) static void _computePathSize(const Path & path,
unsigned long long & bytes, unsigned long long & blocks)
{ {
unsigned long long size = 0;
checkInterrupt(); checkInterrupt();
struct stat st; struct stat st;
if (lstat(path.c_str(), &st)) if (lstat(path.c_str(), &st))
throw SysError(format("getting attributes of path `%1%'") % path); throw SysError(format("getting attributes of path `%1%'") % path);
size += st.st_size; bytes += st.st_size;
blocks += st.st_blocks;
if (S_ISDIR(st.st_mode)) { if (S_ISDIR(st.st_mode)) {
Strings names = readDirectory(path); Strings names = readDirectory(path);
for (Strings::iterator i = names.begin(); i != names.end(); ++i) for (Strings::iterator i = names.begin(); i != names.end(); ++i)
size += computePathSize(path + "/" + *i); _computePathSize(path + "/" + *i, bytes, blocks);
} }
return size;
} }
static void _deletePath(const Path & path, unsigned long long & bytesFreed) void computePathSize(const Path & path,
unsigned long long & bytes, unsigned long long & blocks)
{
bytes = 0;
blocks = 0;
_computePathSize(path, bytes, blocks);
}
static void _deletePath(const Path & path, unsigned long long & bytesFreed,
unsigned long long & blocksFreed)
{ {
checkInterrupt(); checkInterrupt();
@ -263,6 +271,7 @@ static void _deletePath(const Path & path, unsigned long long & bytesFreed)
throw SysError(format("getting attributes of path `%1%'") % path); throw SysError(format("getting attributes of path `%1%'") % path);
bytesFreed += st.st_size; bytesFreed += st.st_size;
blocksFreed += st.st_blocks;
if (S_ISDIR(st.st_mode)) { if (S_ISDIR(st.st_mode)) {
Strings names = readDirectory(path); Strings names = readDirectory(path);
@ -274,7 +283,7 @@ static void _deletePath(const Path & path, unsigned long long & bytesFreed)
} }
for (Strings::iterator i = names.begin(); i != names.end(); ++i) for (Strings::iterator i = names.begin(); i != names.end(); ++i)
_deletePath(path + "/" + *i, bytesFreed); _deletePath(path + "/" + *i, bytesFreed, blocksFreed);
} }
if (remove(path.c_str()) == -1) if (remove(path.c_str()) == -1)
@ -284,17 +293,19 @@ static void _deletePath(const Path & path, unsigned long long & bytesFreed)
void deletePath(const Path & path) void deletePath(const Path & path)
{ {
unsigned long long dummy; unsigned long long dummy1, dummy2;
deletePath(path, dummy); deletePath(path, dummy1, dummy2);
} }
void deletePath(const Path & path, unsigned long long & bytesFreed) void deletePath(const Path & path, unsigned long long & bytesFreed,
unsigned long long & blocksFreed)
{ {
startNest(nest, lvlDebug, startNest(nest, lvlDebug,
format("recursively deleting path `%1%'") % path); format("recursively deleting path `%1%'") % path);
bytesFreed = 0; bytesFreed = 0;
_deletePath(path, bytesFreed); blocksFreed = 0;
_deletePath(path, bytesFreed, blocksFreed);
} }

View file

@ -61,14 +61,16 @@ string readFile(const Path & path);
void writeFile(const Path & path, const string & s); void writeFile(const Path & path, const string & s);
/* Compute the sum of the sizes of all files in `path'. */ /* Compute the sum of the sizes of all files in `path'. */
unsigned long long computePathSize(const Path & path); void computePathSize(const Path & path,
unsigned long long & bytes, unsigned long long & blocks);
/* Delete a path; i.e., in the case of a directory, it is deleted /* Delete a path; i.e., in the case of a directory, it is deleted
recursively. Don't use this at home, kids. The second variant recursively. Don't use this at home, kids. The second variant
returns the number of bytes freed. */ returns the number of bytes and blocks freed. */
void deletePath(const Path & path); void deletePath(const Path & path);
void deletePath(const Path & path, unsigned long long & bytesFreed); void deletePath(const Path & path, unsigned long long & bytesFreed,
unsigned long long & blocksFreed);
/* Make a path read-only recursively. */ /* Make a path read-only recursively. */
void makePathReadOnly(const Path & path); void makePathReadOnly(const Path & path);

View file

@ -489,19 +489,19 @@ static void opCheckValidity(Strings opFlags, Strings opArgs)
} }
static string showBytes(unsigned long long bytes) static string showBytes(unsigned long long bytes, unsigned long long blocks)
{ {
return (format("%d bytes (%.2f MiB)") return (format("%d bytes (%.2f MiB, %d blocks)")
% bytes % (bytes / (1024.0 * 1024.0))).str(); % bytes % (bytes / (1024.0 * 1024.0)) % blocks).str();
} }
struct PrintFreed struct PrintFreed
{ {
bool show, dryRun; bool show, dryRun;
unsigned long long bytesFreed; const GCResults & results;
PrintFreed(bool show, bool dryRun) PrintFreed(bool show, bool dryRun, const GCResults & results)
: show(show), dryRun(dryRun), bytesFreed(0) { } : show(show), dryRun(dryRun), results(results) { }
~PrintFreed() ~PrintFreed()
{ {
if (show) if (show)
@ -509,34 +509,36 @@ struct PrintFreed
(dryRun (dryRun
? "%1% would be freed\n" ? "%1% would be freed\n"
: "%1% freed\n")) : "%1% freed\n"))
% showBytes(bytesFreed); % showBytes(results.bytesFreed, results.blocksFreed);
} }
}; };
static void opGC(Strings opFlags, Strings opArgs) static void opGC(Strings opFlags, Strings opArgs)
{ {
GCAction action = gcDeleteDead; GCOptions options;
options.action = GCOptions::gcDeleteDead;
GCResults results;
/* Do what? */ /* Do what? */
for (Strings::iterator i = opFlags.begin(); for (Strings::iterator i = opFlags.begin();
i != opFlags.end(); ++i) i != opFlags.end(); ++i)
if (*i == "--print-roots") action = gcReturnRoots; if (*i == "--print-roots") options.action = GCOptions::gcReturnRoots;
else if (*i == "--print-live") action = gcReturnLive; else if (*i == "--print-live") options.action = GCOptions::gcReturnLive;
else if (*i == "--print-dead") action = gcReturnDead; else if (*i == "--print-dead") options.action = GCOptions::gcReturnDead;
else if (*i == "--delete") action = gcDeleteDead; else if (*i == "--delete") options.action = GCOptions::gcDeleteDead;
else throw UsageError(format("bad sub-operation `%1%' in GC") % *i); else throw UsageError(format("bad sub-operation `%1%' in GC") % *i);
PathSet result; PrintFreed freed(
PrintFreed freed(action == gcDeleteDead || action == gcReturnDead, options.action == GCOptions::gcDeleteDead || options.action == GCOptions::gcReturnDead,
action == gcReturnDead); options.action == GCOptions::gcReturnDead, results);
store->collectGarbage(action, PathSet(), false, result, freed.bytesFreed); store->collectGarbage(options, results);
if (action != gcDeleteDead) { if (options.action != GCOptions::gcDeleteDead)
for (PathSet::iterator i = result.begin(); i != result.end(); ++i) foreach (PathSet::iterator, i, results.paths)
cout << *i << std::endl; cout << *i << std::endl;
} }
}
/* Remove paths from the Nix store if possible (i.e., if they do not /* Remove paths from the Nix store if possible (i.e., if they do not
@ -544,22 +546,21 @@ static void opGC(Strings opFlags, Strings opArgs)
roots). */ roots). */
static void opDelete(Strings opFlags, Strings opArgs) static void opDelete(Strings opFlags, Strings opArgs)
{ {
bool ignoreLiveness = false; GCOptions options;
options.action = GCOptions::gcDeleteSpecific;
for (Strings::iterator i = opFlags.begin(); for (Strings::iterator i = opFlags.begin();
i != opFlags.end(); ++i) i != opFlags.end(); ++i)
if (*i == "--ignore-liveness") ignoreLiveness = true; if (*i == "--ignore-liveness") options.ignoreLiveness = true;
else throw UsageError(format("unknown flag `%1%'") % *i); else throw UsageError(format("unknown flag `%1%'") % *i);
PathSet pathsToDelete;
for (Strings::iterator i = opArgs.begin(); for (Strings::iterator i = opArgs.begin();
i != opArgs.end(); ++i) i != opArgs.end(); ++i)
pathsToDelete.insert(followLinksToStorePath(*i)); options.pathsToDelete.insert(followLinksToStorePath(*i));
PathSet dummy; GCResults results;
PrintFreed freed(true, false); PrintFreed freed(true, false, results);
store->collectGarbage(gcDeleteSpecific, pathsToDelete, ignoreLiveness, store->collectGarbage(options, results);
dummy, freed.bytesFreed);
} }
@ -653,7 +654,7 @@ static void showOptimiseStats(OptimiseStats & stats)
{ {
printMsg(lvlError, printMsg(lvlError,
format("%1% freed by hard-linking %2% files; there are %3% files with equal contents out of %4% files in total") format("%1% freed by hard-linking %2% files; there are %3% files with equal contents out of %4% files in total")
% showBytes(stats.bytesFreed) % showBytes(stats.bytesFreed, stats.blocksFreed)
% stats.filesLinked % stats.filesLinked
% stats.sameContents % stats.sameContents
% stats.totalFiles); % stats.totalFiles);

View file

@ -395,23 +395,24 @@ static void performOp(unsigned int clientVersion,
} }
case wopCollectGarbage: { case wopCollectGarbage: {
GCAction action = (GCAction) readInt(from); GCOptions options;
PathSet pathsToDelete = readStorePaths(from); options.action = (GCOptions::GCAction) readInt(from);
bool ignoreLiveness = readInt(from); options.pathsToDelete = readStorePaths(from);
options.ignoreLiveness = readInt(from);
options.maxFreed = readLongLong(from);
options.maxLinks = readInt(from);
PathSet result; GCResults results;
unsigned long long bytesFreed;
startWork(); startWork();
if (ignoreLiveness) if (options.ignoreLiveness)
throw Error("you are not allowed to ignore liveness"); throw Error("you are not allowed to ignore liveness");
store->collectGarbage(action, pathsToDelete, ignoreLiveness, store->collectGarbage(options, results);
result, bytesFreed);
stopWork(); stopWork();
writeStringSet(result, to); writeStringSet(results.paths, to);
writeInt(bytesFreed & 0xffffffff, to); writeLongLong(results.bytesFreed, to);
writeInt(bytesFreed >> 32, to); writeLongLong(results.blocksFreed, to);
break; break;
} }