From: Arvin Schnell Date: Tue, 2 Oct 2018 10:08:09 +0000 (+0200) Subject: - show used space for each snapshot X-Git-Tag: v0.6.0~2^2~9 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=f8fc027498336ca445b23b2831b9fdc9b475f0c8;p=thirdparty%2Fsnapper.git - show used space for each snapshot --- diff --git a/VERSION b/VERSION index b49b2533..a918a2aa 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.5.6 +0.6.0 diff --git a/client/commands.cc b/client/commands.cc index 41c4dc12..3745a535 100644 --- a/client/commands.cc +++ b/client/commands.cc @@ -1,6 +1,6 @@ /* * Copyright (c) [2012-2015] Novell, Inc. - * Copyright (c) 2016 SUSE LLC + * Copyright (c) [2016,2018] SUSE LLC * * All Rights Reserved. * @@ -293,6 +293,49 @@ command_delete_snapshots(DBus::Connection& conn, const string& config_name, } +void +command_calculate_used_space(DBus::Connection& conn, const string& config_name) +{ + DBus::MessageMethodCall call(SERVICE, OBJECT, INTERFACE, "CalculateUsedSpace"); + + DBus::Hoho hoho(call); + hoho << config_name; + + try + { + conn.send_with_reply_and_block(call); + } + catch (const DBus::ErrorException& e) + { + SN_CAUGHT(e); + + if (strcmp(e.name(), "error.quota") == 0) + SN_THROW(QuotaException(e.message())); + + SN_RETHROW(e); + } +} + + +uint64_t +command_get_used_space(DBus::Connection& conn, const string& config_name, unsigned int num) +{ + DBus::MessageMethodCall call(SERVICE, OBJECT, INTERFACE, "GetUsedSpace"); + + DBus::Hoho hoho(call); + hoho << config_name << num; + + DBus::Message reply = conn.send_with_reply_and_block(call); + + uint64_t used_space; + + DBus::Hihi hihi(reply); + hihi >> used_space; + + return used_space; +} + + string command_mount_snapshot(DBus::Connection& conn, const string& config_name, unsigned int num, bool user_request) diff --git a/client/commands.h b/client/commands.h index b70aa957..b1a27e7b 100644 --- a/client/commands.h +++ b/client/commands.h @@ -1,6 +1,6 @@ /* * Copyright (c) [2012-2015] Novell, Inc. - * Copyright (c) 2016 SUSE LLC + * Copyright (c) [2016,2018] SUSE LLC * * All Rights Reserved. * @@ -90,6 +90,12 @@ void command_delete_snapshots(DBus::Connection& conn, const string& config_name, const vector& nums, bool verbose); +void +command_calculate_used_space(DBus::Connection& conn, const string& config_name); + +uint64_t +command_get_used_space(DBus::Connection& conn, const string& config_name, unsigned int num); + string command_mount_snapshot(DBus::Connection& conn, const string& config_name, unsigned int num, bool user_request); diff --git a/client/proxy-dbus.cc b/client/proxy-dbus.cc index 262fc064..38cd26c2 100644 --- a/client/proxy-dbus.cc +++ b/client/proxy-dbus.cc @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016 SUSE LLC + * Copyright (c) [2016,2018] SUSE LLC * * All Rights Reserved. * @@ -53,6 +53,20 @@ ProxySnapshotDbus::ProxySnapshotDbus(ProxySnapshotsDbus* backref, SnapshotType t } +void +ProxySnapperDbus::calculateUsedSpace() const +{ + command_calculate_used_space(conn(), config_name); +} + + +uint64_t +ProxySnapshotDbus::getUsedSpace() const +{ + return command_get_used_space(conn(), configName(), num); +} + + string ProxySnapshotDbus::mountFilesystemSnapshot(bool user_request) const { diff --git a/client/proxy-dbus.h b/client/proxy-dbus.h index 0c49771e..ff901493 100644 --- a/client/proxy-dbus.h +++ b/client/proxy-dbus.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016 SUSE LLC + * Copyright (c) [2016,2018] SUSE LLC * * All Rights Reserved. * @@ -62,6 +62,8 @@ public: virtual bool isCurrent() const override { return num == 0; } + virtual uint64_t getUsedSpace() const override; + virtual string mountFilesystemSnapshot(bool user_request) const override; virtual void umountFilesystemSnapshot(bool user_request) const override; @@ -141,6 +143,8 @@ public: virtual QuotaData queryQuotaData() const override; + virtual void calculateUsedSpace() const override; + DBus::Connection& conn() const; private: diff --git a/client/proxy-lib.h b/client/proxy-lib.h index 8c852825..90ae1f72 100644 --- a/client/proxy-lib.h +++ b/client/proxy-lib.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016 SUSE LLC + * Copyright (c) [2016,2018] SUSE LLC * * All Rights Reserved. * @@ -50,6 +50,11 @@ public: virtual bool isCurrent() const override { return it->isCurrent(); } + virtual uint64_t getUsedSpace() const override + { + return it->getUsedSpace(); + } + virtual string mountFilesystemSnapshot(bool user_request) const override { it->mountFilesystemSnapshot(user_request); @@ -121,6 +126,8 @@ public: virtual QuotaData queryQuotaData() const override { return snapper->queryQuotaData(); } + virtual void calculateUsedSpace() const override { snapper->calculateUsedSpace(); } + std::unique_ptr snapper; private: diff --git a/client/proxy.h b/client/proxy.h index ae15a2c5..b9f1981f 100644 --- a/client/proxy.h +++ b/client/proxy.h @@ -1,5 +1,5 @@ /* - * Copyright (c) [2016-2017] SUSE LLC + * Copyright (c) [2016-2018] SUSE LLC * * All Rights Reserved. * @@ -107,6 +107,8 @@ public: bool isCurrent() const { return impl->isCurrent(); } + uint64_t getUsedSpace() const { return impl->getUsedSpace(); } + void mountFilesystemSnapshot(bool user_request) const { impl->mountFilesystemSnapshot(user_request); } @@ -133,6 +135,8 @@ public: virtual bool isCurrent() const = 0; + virtual uint64_t getUsedSpace() const = 0; + virtual string mountFilesystemSnapshot(bool user_request) const = 0; virtual void umountFilesystemSnapshot(bool user_request) const = 0; @@ -233,6 +237,8 @@ public: virtual QuotaData queryQuotaData() const = 0; + virtual void calculateUsedSpace() const = 0; + }; diff --git a/client/snapper.cc b/client/snapper.cc index d38138b5..d42cbebc 100644 --- a/client/snapper.cc +++ b/client/snapper.cc @@ -45,6 +45,7 @@ #include "utils/text.h" #include "utils/Table.h" #include "utils/GetOpts.h" +#include "utils/HumanString.h" #include "cleanup.h" #include "errors.h" @@ -354,6 +355,7 @@ help_list() << endl << _(" Options for 'list' command:") << endl << _("\t--type, -t \t\tType of snapshots to list.") << endl + << _("\t--disable-used-space\t\tDisable showing used space.") << endl << _("\t--all-configs, -a\t\tList snapshots from all accessible configs.") << endl << endl; } @@ -362,7 +364,7 @@ enum ListMode { LM_ALL, LM_SINGLE, LM_PRE_POST }; void -list_from_one_config(ProxySnapper* snapper, ListMode list_mode); +list_from_one_config(ProxySnapper* snapper, ListMode list_mode, bool show_used_space); void @@ -370,6 +372,7 @@ command_list(ProxySnappers* snappers, ProxySnapper*) { const struct option options[] = { { "type", required_argument, 0, 't' }, + { "disable-used-space", no_argument, 0, 0 }, { "all-configs", no_argument, 0, 'a' }, { 0, 0, 0, 0 } }; @@ -382,6 +385,7 @@ command_list(ProxySnappers* snappers, ProxySnapper*) } ListMode list_mode = LM_ALL; + bool show_used_space = true; GetOpts::parsed_opts::const_iterator opt; @@ -400,6 +404,11 @@ command_list(ProxySnappers* snappers, ProxySnapper*) } } + if ((opt = opts.find("disable-used-space")) != opts.end()) + { + show_used_space = false; + } + vector tmp; if ((opt = opts.find("all-configs")) == opts.end()) @@ -426,14 +435,40 @@ command_list(ProxySnappers* snappers, ProxySnapper*) << snapper->getConfig().getSubvolume() << endl; } - list_from_one_config(snapper, list_mode); + list_from_one_config(snapper, list_mode, show_used_space); } } void -list_from_one_config(ProxySnapper* snapper, ListMode list_mode) +list_from_one_config(ProxySnapper* snapper, ListMode list_mode, bool show_used_space) { + if (list_mode != LM_ALL && list_mode != LM_SINGLE) + show_used_space = false; + + if (show_used_space) + { + try + { + snapper->calculateUsedSpace(); + } + catch (const QuotaException& e) + { + SN_CAUGHT(e); + + show_used_space = false; + } + } + + auto add_date = [](TableRow& row, const ProxySnapshot& snapshot) { + row.add(snapshot.isCurrent() ? "" : datetime(snapshot.getDate(), utc, iso)); + }; + + auto add_used_space = [show_used_space](TableRow& row, const ProxySnapshot& snapshot) { + if (show_used_space) + row.add(snapshot.isCurrent() ? "" : byte_to_humanstring(snapshot.getUsedSpace(), 2)); + }; + Table table; switch (list_mode) @@ -446,6 +481,8 @@ list_from_one_config(ProxySnapper* snapper, ListMode list_mode) header.add(_("Pre #")); header.add(_("Date")); header.add(_("User")); + if (show_used_space) + header.add(_("Used Space")); header.add(_("Cleanup")); header.add(_("Description")); header.add(_("Userdata")); @@ -458,8 +495,9 @@ list_from_one_config(ProxySnapper* snapper, ListMode list_mode) row.add(toString(snapshot.getType())); row.add(decString(snapshot.getNum())); row.add(snapshot.getType() == POST ? decString(snapshot.getPreNum()) : ""); - row.add(snapshot.isCurrent() ? "" : datetime(snapshot.getDate(), utc, iso)); + add_date(row, snapshot); row.add(username(snapshot.getUid())); + add_used_space(row, snapshot); row.add(snapshot.getCleanup()); row.add(snapshot.getDescription()); row.add(show_userdata(snapshot.getUserdata())); @@ -474,6 +512,8 @@ list_from_one_config(ProxySnapper* snapper, ListMode list_mode) header.add(_("#")); header.add(_("Date")); header.add(_("User")); + if (show_used_space) + header.add(_("Used Space")); header.add(_("Description")); header.add(_("Userdata")); table.setHeader(header); @@ -486,8 +526,9 @@ list_from_one_config(ProxySnapper* snapper, ListMode list_mode) TableRow row; row.add(decString(snapshot.getNum())); - row.add(snapshot.isCurrent() ? "" : datetime(snapshot.getDate(), utc, iso)); + add_date(row, snapshot); row.add(username(snapshot.getUid())); + add_used_space(row, snapshot); row.add(snapshot.getDescription()); row.add(show_userdata(snapshot.getUserdata())); table.add(row); diff --git a/client/utils/HumanString.cc b/client/utils/HumanString.cc new file mode 100644 index 00000000..6abd3ce2 --- /dev/null +++ b/client/utils/HumanString.cc @@ -0,0 +1,139 @@ +/* + * Copyright (c) [2004-2014] Novell, Inc. + * Copyright (c) [2016-2018] SUSE LLC + * + * All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, contact Novell, Inc. + * + * To contact Novell about this file by physical or electronic mail, you may + * find current contact information at www.novell.com. + */ + + +#include +#include +#include +#include + +#include "HumanString.h" +#include "text.h" + + +namespace snapper +{ + using std::string; + + + /* + * These are simplified versions of the functions in libstorage-ng. + */ + + + int + num_suffixes() + { + return 7; + } + + + string + get_suffix(int i) + { + switch (i) + { + case 0: + // TRANSLATORS: symbol for "bytes" (best keep untranslated) + return _("B"); + + case 1: + // TRANSLATORS: symbol for "kibi bytes" (best keep untranslated) + return _("KiB"); + + case 2: + // TRANSLATORS: symbol for "mebi bytes" (best keep untranslated) + return _("MiB"); + + case 3: + // TRANSLATORS: symbol for "gibi bytes" (best keep untranslated) + return _("GiB"); + + case 4: + // TRANSLATORS: symbol for "tebi bytes" (best keep untranslated) + return _("TiB"); + + case 5: + // TRANSLATORS: symbol for "pebi bytes" (best keep untranslated) + return _("PiB"); + + case 6: + // TRANSLATORS: symbol for "exbi bytes" (best keep untranslated) + return _("EiB"); + + default: + throw std::logic_error("invalid prefix"); + } + } + + + bool + is_multiple_of(unsigned long long i, unsigned long long j) + { + return i % j == 0; + } + + + int + clz(unsigned long long i) + { + return __builtin_clzll(i); + } + + + // byte_to_humanstring uses integer arithmetic as long as possible + // to provide exact results for those cases. + + + string + byte_to_humanstring(unsigned long long size, int precision) + { + const std::locale loc = std::locale(); + + // Calculate the index of the suffix to use. The index increases by 1 + // when the number of leading zeros decreases by 10. + + int i = size > 0 ? (64 - (clz(size) + 1)) / 10 : 0; + + if (i == 0) + { + unsigned long long v = size >> 10 * i; + + std::ostringstream s; + s.imbue(loc); + s << v << ' ' << get_suffix(i); + return s.str(); + } + else + { + long double v = std::ldexp((long double)(size), - 10 * i); + + std::ostringstream s; + s.imbue(loc); + s.setf(std::ios::fixed); + s.precision(precision); + s << v << ' ' << get_suffix(i); + return s.str(); + } + } + +} diff --git a/client/utils/HumanString.h b/client/utils/HumanString.h new file mode 100644 index 00000000..1d3a75ba --- /dev/null +++ b/client/utils/HumanString.h @@ -0,0 +1,68 @@ +/* + * Copyright (c) [2004-2014] Novell, Inc. + * Copyright (c) [2016,2018] SUSE LLC + * + * All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, contact Novell, Inc. + * + * To contact Novell about this file by physical or electronic mail, you may + * find current contact information at www.novell.com. + */ + + +#include + + +namespace snapper +{ + + const unsigned long long B = 1; + const unsigned long long KiB = 1024 * B; + const unsigned long long MiB = 1024 * KiB; + const unsigned long long GiB = 1024 * MiB; + const unsigned long long TiB = 1024 * GiB; + const unsigned long long PiB = 1024 * TiB; + const unsigned long long EiB = 1024 * PiB; + + + /** + * Return number of suffixes. + * + * @return number of suffixes + */ + int num_suffixes(); + + + /** + * Return a suffix. + * + * @param i index of suffix + * @param classic use classic locale instead of global C++ locale + * @return suffix + */ + std::string get_suffix(int i); + + + /** + * Return a pretty description of a size with required precision and using + * B, KiB, MiB, GiB, TiB, PiB or EiB as unit as appropriate. Supported + * range is 0 B to (16 EiB - 1 B). + * + * @param size size in bytes + * @param precision number of fraction digits in output + * @return formatted string + */ + std::string byte_to_humanstring(unsigned long long size, int precision); + +} diff --git a/client/utils/Makefile.am b/client/utils/Makefile.am index 910beda7..7ebc87b5 100644 --- a/client/utils/Makefile.am +++ b/client/utils/Makefile.am @@ -12,7 +12,8 @@ libutils_la_SOURCES = \ console.cc console.h \ equal-date.cc equal-date.h \ GetOpts.cc GetOpts.h \ - Range.cc Range.h + Range.cc Range.h \ + HumanString.cc HumanString.h libutils_la_LIBADD = ../../snapper/libsnapper.la diff --git a/doc/snapper.xml.in b/doc/snapper.xml.in index cdbd7977..2c005777 100644 --- a/doc/snapper.xml.in +++ b/doc/snapper.xml.in @@ -340,6 +340,16 @@ all, single and pre-post. + + + + Disable display of used space. + Calculating the used space needs some time. Thus + this option can speedup the listing. + Display of used space is automatically disable + is not available, e.g. quota not enabled on btrfs. + + diff --git a/package/snapper.changes b/package/snapper.changes index aaf41e4a..bf50440f 100644 --- a/package/snapper.changes +++ b/package/snapper.changes @@ -1,3 +1,9 @@ +------------------------------------------------------------------- +Tue Oct 02 09:56:59 CEST 2018 - aschnell@suse.com + +- show used space (exclusive space of btrfs qgroup) for each + snapshot (fate#323843) + ------------------------------------------------------------------- Fri Sep 14 12:13:05 CEST 2018 - aschnell@suse.com diff --git a/testsuite/Makefile.am b/testsuite/Makefile.am index 8b3fbd6f..3058449e 100644 --- a/testsuite/Makefile.am +++ b/testsuite/Makefile.am @@ -7,7 +7,7 @@ AM_CPPFLAGS = -I$(top_srcdir) $(DBUS_CFLAGS) LDADD = ../snapper/libsnapper.la ../dbus/libdbus.la -lboost_unit_test_framework check_PROGRAMS = sysconfig-get1.test dirname1.test basename1.test \ - equal-date.test dbus-escape.test cmp-lt.test + equal-date.test dbus-escape.test cmp-lt.test humanstring.test if ENABLE_BTRFS_QUOTA check_PROGRAMS += qgroup1.test @@ -21,3 +21,5 @@ EXTRA_DIST = $(noinst_SCRIPTS) sysconfig-get1.txt equal_date_test_LDADD = -lboost_unit_test_framework ../client/utils/libutils.la +humanstring_test_LDADD = -lboost_unit_test_framework ../client/utils/libutils.la + diff --git a/testsuite/humanstring.cc b/testsuite/humanstring.cc new file mode 100644 index 00000000..239b77b0 --- /dev/null +++ b/testsuite/humanstring.cc @@ -0,0 +1,67 @@ + +#define BOOST_TEST_DYN_LINK +#define BOOST_TEST_MODULE libstorage + +#include + +#include +#include +#include + +#include "../client/utils/HumanString.h" + + +using namespace std; +using namespace snapper; + + +// Tests here must work when using double instead of long double in +// HumanString.cc. + + +string +test(const char* loc, unsigned long long size, int precision) +{ + locale::global(locale(loc)); + + return byte_to_humanstring(size, precision); +} + + + +BOOST_AUTO_TEST_CASE(test_byte_to_humanstring) +{ + BOOST_CHECK_EQUAL(test("en_GB.UTF-8", 0, 2), "0 B"); + + BOOST_CHECK_EQUAL(test("en_GB.UTF-8", 1023, 2), "1,023 B"); + + BOOST_CHECK_EQUAL(test("en_GB.UTF-8", 1024, 2), "1.00 KiB"); + + BOOST_CHECK_EQUAL(test("en_GB.UTF-8", 1025, 2), "1.00 KiB"); + + BOOST_CHECK_EQUAL(test("de_DE.UTF-8", 123456789, 4), "117,7376 MiB"); + + BOOST_CHECK_EQUAL(test("en_GB.UTF-8", 1000 * KiB, 2), "1,000.00 KiB"); + BOOST_CHECK_EQUAL(test("de_DE.UTF-8", 1000 * KiB, 2), "1.000,00 KiB"); + BOOST_CHECK_EQUAL(test("de_CH.UTF-8", 1000 * KiB, 2), "1'000.00 KiB"); + + BOOST_CHECK_EQUAL(test("en_GB.UTF-8", 50 * MiB, 2), "50.00 MiB"); + BOOST_CHECK_EQUAL(test("de_DE.UTF-8", 50 * MiB, 2), "50,00 MiB"); + BOOST_CHECK_EQUAL(test("de_CH.UTF-8", 50 * MiB, 2), "50.00 MiB"); +} + + +BOOST_AUTO_TEST_CASE(test_big_numbers) +{ + // 1 EiB - 1 B + BOOST_CHECK_EQUAL(test("en_GB.UTF-8", 1 * EiB - 1 * B, 2), "1,024.00 PiB"); + + // 1 EiB + BOOST_CHECK_EQUAL(test("en_GB.UTF-8", 1 * EiB, 2), "1.00 EiB"); + + // 1 EiB + 1 B + BOOST_CHECK_EQUAL(test("en_GB.UTF-8", 1 * EiB + 1 * B, 2), "1.00 EiB"); + + // 16 EiB - 1 B + BOOST_CHECK_EQUAL(test("en_GB.UTF-8", 16 * EiB - 1 * B, 2), "16.00 EiB"); +}