Blame SOURCES/sos-bz1873185-estimate-only-option.patch

900e1b
From 5b245b1e449c6a05d09034bcb8290bffded79327 Mon Sep 17 00:00:00 2001
900e1b
From: Pavel Moravec <pmoravec@redhat.com>
900e1b
Date: Wed, 8 Sep 2021 17:04:58 +0200
900e1b
Subject: [PATCH] [report] Implement --estimate-only
900e1b
900e1b
Add report option --estimate-only to estimate disk space requirements
900e1b
when running a sos report.
900e1b
900e1b
Resolves: #2673
900e1b
900e1b
Signed-off-by: Pavel Moravec <pmoravec@redhat.com>
900e1b
---
900e1b
 man/en/sos-report.1    | 13 +++++++-
900e1b
 sos/report/__init__.py | 74 ++++++++++++++++++++++++++++++++++++++++--
900e1b
 2 files changed, 84 insertions(+), 3 deletions(-)
900e1b
900e1b
diff --git a/man/en/sos-report.1 b/man/en/sos-report.1
900e1b
index 36b337df..e8efc8f8 100644
900e1b
--- a/man/en/sos-report.1
900e1b
+++ b/man/en/sos-report.1
900e1b
@@ -14,7 +14,7 @@ sos report \- Collect and package diagnostic and support data
900e1b
           [--preset preset] [--add-preset add_preset]\fR
900e1b
           [--del-preset del_preset] [--desc description]\fR
900e1b
           [--batch] [--build] [--debug] [--dry-run]\fR
900e1b
-          [--label label] [--case-id id]\fR
900e1b
+          [--estimate-only] [--label label] [--case-id id]\fR
900e1b
           [--threads threads]\fR
900e1b
           [--plugin-timeout TIMEOUT]\fR
900e1b
           [--cmd-timeout TIMEOUT]\fR
900e1b
@@ -317,6 +317,17 @@ output, or string data from the system. The resulting logs may be used
900e1b
 to understand the actions that sos would have taken without the dry run
900e1b
 option.
900e1b
 .TP
900e1b
+.B \--estimate-only
900e1b
+Estimate disk space requirements when running sos report. This can be valuable
900e1b
+to prevent sosreport working dir to consume all free disk space. No plugin data
900e1b
+is available at the end.
900e1b
+
900e1b
+Plugins will be collected sequentially, size of collected files and commands outputs
900e1b
+will be calculated and the plugin files will be immediatelly deleted prior execution
900e1b
+of the next plugin. This still can consume whole free disk space, though. Please note,
900e1b
+size estimations may not be accurate for highly utilized systems due to changes between
900e1b
+an estimate and a real execution.
900e1b
+.TP
900e1b
 .B \--upload
900e1b
 If specified, attempt to upload the resulting archive to a vendor defined location.
900e1b
 
900e1b
diff --git a/sos/report/__init__.py b/sos/report/__init__.py
900e1b
index 82484f1d..b033f621 100644
900e1b
--- a/sos/report/__init__.py
900e1b
+++ b/sos/report/__init__.py
900e1b
@@ -86,6 +86,7 @@ class SoSReport(SoSComponent):
900e1b
         'desc': '',
900e1b
         'domains': [],
900e1b
         'dry_run': False,
900e1b
+        'estimate_only': False,
900e1b
         'experimental': False,
900e1b
         'enable_plugins': [],
900e1b
         'keywords': [],
900e1b
@@ -137,6 +138,7 @@ class SoSReport(SoSComponent):
900e1b
         self._args = args
900e1b
         self.sysroot = "/"
900e1b
         self.preset = None
900e1b
+        self.estimated_plugsizes = {}
900e1b
 
900e1b
         self.print_header()
900e1b
         self._set_debug()
900e1b
@@ -223,6 +225,11 @@ class SoSReport(SoSComponent):
900e1b
                                 help="Description for a new preset",)
900e1b
         report_grp.add_argument("--dry-run", action="store_true",
900e1b
                                 help="Run plugins but do not collect data")
900e1b
+        report_grp.add_argument("--estimate-only", action="store_true",
900e1b
+                                help="Approximate disk space requirements for "
900e1b
+                                     "a real sos run; disables --clean and "
900e1b
+                                     "--collect, sets --threads=1 and "
900e1b
+                                     "--no-postproc")
900e1b
         report_grp.add_argument("--experimental", action="store_true",
900e1b
                                 dest="experimental", default=False,
900e1b
                                 help="enable experimental plugins")
900e1b
@@ -700,6 +700,33 @@ class SoSReport(SoSComponent):
900e1b
                 self.all_options.append((plugin, plugin_name, optname,
900e1b
                                          optparm))
900e1b
 
900e1b
+    def _set_estimate_only(self):
900e1b
+        # set estimate-only mode by enforcing some options settings
900e1b
+        # and return a corresponding log messages string
900e1b
+        msg = "\nEstimate-only mode enabled"
900e1b
+        ext_msg = []
900e1b
+        if self.opts.threads > 1:
900e1b
+            ext_msg += ["--threads=%s overriden to 1" % self.opts.threads, ]
900e1b
+            self.opts.threads = 1
900e1b
+        if not self.opts.build:
900e1b
+            ext_msg += ["--build enabled", ]
900e1b
+            self.opts.build = True
900e1b
+        if not self.opts.no_postproc:
900e1b
+            ext_msg += ["--no-postproc enabled", ]
900e1b
+            self.opts.no_postproc = True
900e1b
+        if self.opts.clean:
900e1b
+            ext_msg += ["--clean disabled", ]
900e1b
+            self.opts.clean = False
900e1b
+        if self.opts.upload:
900e1b
+            ext_msg += ["--upload* options disabled", ]
900e1b
+            self.opts.upload = False
900e1b
+        if ext_msg:
900e1b
+            msg += ", which overrides some options:\n  " + "\n  ".join(ext_msg)
900e1b
+        else:
900e1b
+            msg += "."
900e1b
+        msg += "\n\n"
900e1b
+        return msg
900e1b
+
900e1b
     def _report_profiles_and_plugins(self):
900e1b
         self.ui_log.info("")
900e1b
         if len(self.loaded_plugins):
900e1b
@@ -875,10 +909,12 @@ class SoSReport(SoSComponent):
900e1b
         return True
900e1b
 
900e1b
     def batch(self):
900e1b
+        msg = self.policy.get_msg()
900e1b
+        if self.opts.estimate_only:
900e1b
+            msg += self._set_estimate_only()
900e1b
         if self.opts.batch:
900e1b
-            self.ui_log.info(self.policy.get_msg())
900e1b
+            self.ui_log.info(msg)
900e1b
         else:
900e1b
-            msg = self.policy.get_msg()
900e1b
             msg += _("Press ENTER to continue, or CTRL-C to quit.\n")
900e1b
             try:
900e1b
                 input(msg)
900e1b
@@ -1011,6 +1047,22 @@ class SoSReport(SoSComponent):
900e1b
                 self.running_plugs.remove(plugin[1])
900e1b
                 self.loaded_plugins[plugin[0]-1][1].set_timeout_hit()
900e1b
                 pool._threads.clear()
900e1b
+        if self.opts.estimate_only:
900e1b
+            from pathlib import Path
900e1b
+            tmpdir_path = Path(self.archive.get_tmp_dir())
900e1b
+            self.estimated_plugsizes[plugin[1]] = sum(
900e1b
+                    [f.stat().st_size for f in tmpdir_path.glob('**/*')
900e1b
+                     if (os.path.isfile(f) and not os.path.islink(f))])
900e1b
+            # remove whole tmp_dir content - including "sos_commands" and
900e1b
+            # similar dirs that will be re-created on demand by next plugin
900e1b
+            # if needed; it is less error-prone approach than skipping
900e1b
+            # deletion of some dirs but deleting their content
900e1b
+            for f in os.listdir(self.archive.get_tmp_dir()):
900e1b
+                f = os.path.join(self.archive.get_tmp_dir(), f)
900e1b
+                if os.path.isdir(f):
900e1b
+                    rmtree(f)
900e1b
+                else:
900e1b
+                    os.unlink(f)
900e1b
         return True
900e1b
 
900e1b
     def collect_plugin(self, plugin):
900e1b
@@ -1330,6 +1382,24 @@ class SoSReport(SoSComponent):
900e1b
             self.policy.display_results(archive, directory, checksum,
900e1b
                                         map_file=map_file)
900e1b
 
900e1b
+        if self.opts.estimate_only:
900e1b
+            from sos.utilities import get_human_readable
900e1b
+            _sum = get_human_readable(sum(self.estimated_plugsizes.values()))
900e1b
+            self.ui_log.info("Estimated disk space requirement for whole "
900e1b
+                             "uncompressed sos report directory: %s" % _sum)
900e1b
+            bigplugins = sorted(self.estimated_plugsizes.items(),
900e1b
+                                key=lambda x: x[1], reverse=True)[:3]
900e1b
+            bp_out = ",  ".join("%s: %s" %
900e1b
+                                (p, get_human_readable(v, precision=0))
900e1b
+                                for p, v in bigplugins)
900e1b
+            self.ui_log.info("Three biggest plugins:  %s" % bp_out)
900e1b
+            self.ui_log.info("")
900e1b
+            self.ui_log.info("Please note the estimation is relevant to the "
900e1b
+                             "current options.")
900e1b
+            self.ui_log.info("Be aware that the real disk space requirements "
900e1b
+                             "might be different.")
900e1b
+            self.ui_log.info("")
900e1b
+
900e1b
         if self.opts.upload or self.opts.upload_url:
900e1b
             if not self.opts.build:
900e1b
                 try:
900e1b
-- 
900e1b
2.31.1
900e1b
900e1b
From 7ae47e6c0717c0b56c3368008dd99a87f7f436d5 Mon Sep 17 00:00:00 2001
900e1b
From: Pavel Moravec <pmoravec@redhat.com>
900e1b
Date: Wed, 13 Oct 2021 20:21:16 +0200
900e1b
Subject: [PATCH] [report] Count with sos_logs and sos_reports in
900e1b
 --estimate-only
900e1b
900e1b
Currently, we estimate just plugins' disk space and ignore sos_logs
900e1b
or sos_reports directories - although they can occupy nontrivial disk
900e1b
space as well.
900e1b
900e1b
Resolves: #2723
900e1b
900e1b
Signed-off-by: Pavel Moravec <pmoravec@redhat.com>
900e1b
---
900e1b
 sos/report/__init__.py | 8 ++++++++
900e1b
 1 file changed, 8 insertions(+)
900e1b
900e1b
diff --git a/sos/report/__init__.py b/sos/report/__init__.py
900e1b
index e35c7e8d..7feb31ee 100644
900e1b
--- a/sos/report/__init__.py
900e1b
+++ b/sos/report/__init__.py
900e1b
@@ -1380,6 +1380,14 @@ class SoSReport(SoSComponent):
900e1b
 
900e1b
         if self.opts.estimate_only:
900e1b
             from sos.utilities import get_human_readable
900e1b
+            from pathlib import Path
900e1b
+            # add sos_logs, sos_reports dirs, etc., basically everything
900e1b
+            # that remained in self.tmpdir after plugins' contents removal
900e1b
+            # that still will be moved to the sos report final directory path
900e1b
+            tmpdir_path = Path(self.tmpdir)
900e1b
+            self.estimated_plugsizes['sos_logs_reports'] = sum(
900e1b
+                    [f.stat().st_size for f in tmpdir_path.glob('**/*')])
900e1b
+
900e1b
             _sum = get_human_readable(sum(self.estimated_plugsizes.values()))
900e1b
             self.ui_log.info("Estimated disk space requirement for whole "
900e1b
                              "uncompressed sos report directory: %s" % _sum)
900e1b
-- 
900e1b
2.31.1
900e1b
900e1b
From 4293f3317505661e8f32ba94ad87310996fa1626 Mon Sep 17 00:00:00 2001
900e1b
From: Eric Desrochers <eric.desrochers@canonical.com>
900e1b
Date: Tue, 19 Oct 2021 12:18:40 -0400
900e1b
Subject: [PATCH] [report] check for symlink before rmtree when opt
900e1b
 estimate-only is use
900e1b
900e1b
Check if the dir is also symlink before performing rmtree()
900e1b
method so that unlink() method can be used instead.
900e1b
900e1b
Traceback (most recent call last):
900e1b
  File "./bin/sos", line 22, in <module>
900e1b
    sos.execute()
900e1b
  File "/tmp/sos/sos/__init__.py", line 186, in execute
900e1b
    self._component.execute()
900e1b
OSError: Cannot call rmtree on a symbolic link
900e1b
900e1b
Closes: #2727
900e1b
900e1b
Signed-off-by: Eric Desrochers <eric.desrochers@canonical.com>
900e1b
---
900e1b
 sos/report/__init__.py | 2 +-
900e1b
 1 file changed, 1 insertion(+), 1 deletion(-)
900e1b
900e1b
diff --git a/sos/report/__init__.py b/sos/report/__init__.py
900e1b
index 7feb31ee..1b5bc97d 100644
900e1b
--- a/sos/report/__init__.py
900e1b
+++ b/sos/report/__init__.py
900e1b
@@ -1059,7 +1059,7 @@ class SoSReport(SoSComponent):
900e1b
             # deletion of some dirs but deleting their content
900e1b
             for f in os.listdir(self.archive.get_tmp_dir()):
900e1b
                 f = os.path.join(self.archive.get_tmp_dir(), f)
900e1b
-                if os.path.isdir(f):
900e1b
+                if os.path.isdir(f) and not os.path.islink(f):
900e1b
                     rmtree(f)
900e1b
                 else:
900e1b
                     os.unlink(f)
900e1b
-- 
900e1b
2.31.1
900e1b
6ebc5b
From 589d47c93257b55bc796ef6ac25b88c974ee3d72 Mon Sep 17 00:00:00 2001
6ebc5b
From: Pavel Moravec <pmoravec@redhat.com>
6ebc5b
Date: Mon, 8 Nov 2021 16:38:24 +0100
6ebc5b
Subject: [PATCH] [report] Calculate sizes of dirs, symlinks and manifest in
6ebc5b
 estimate mode
6ebc5b
6ebc5b
Enhance --estimate-mode to calculate sizes of also:
6ebc5b
- symlinks
6ebc5b
- directories themselves
6ebc5b
- manifest.json file
6ebc5b
6ebc5b
Use os.lstat() method instead of os.stat() to properly calculate the
6ebc5b
sizes (and not destinations of symlinks, e.g.).
6ebc5b
6ebc5b
Print five biggest plugins instead of three as sos logs and reports do
6ebc5b
stand as one "plugin" in the list, often.
6ebc5b
6ebc5b
Resolves: #2752
6ebc5b
6ebc5b
Signed-off-by: Pavel Moravec <pmoravec@redhat.com>
6ebc5b
---
6ebc5b
 sos/report/__init__.py | 56 +++++++++++++++++++++---------------------
6ebc5b
 1 file changed, 28 insertions(+), 28 deletions(-)
6ebc5b
6ebc5b
diff --git a/sos/report/__init__.py b/sos/report/__init__.py
6ebc5b
index 10952566..a4c92acc 100644
6ebc5b
--- a/sos/report/__init__.py
6ebc5b
+++ b/sos/report/__init__.py
6ebc5b
@@ -1050,8 +1050,7 @@ class SoSReport(SoSComponent):
6ebc5b
             from pathlib import Path
6ebc5b
             tmpdir_path = Path(self.archive.get_tmp_dir())
6ebc5b
             self.estimated_plugsizes[plugin[1]] = sum(
6ebc5b
-                    [f.stat().st_size for f in tmpdir_path.glob('**/*')
6ebc5b
-                     if (os.path.isfile(f) and not os.path.islink(f))])
6ebc5b
+                    [f.lstat().st_size for f in tmpdir_path.glob('**/*')])
6ebc5b
             # remove whole tmp_dir content - including "sos_commands" and
6ebc5b
             # similar dirs that will be re-created on demand by next plugin
6ebc5b
             # if needed; it is less error-prone approach than skipping
6ebc5b
@@ -1273,6 +1272,33 @@ class SoSReport(SoSComponent):
6ebc5b
                 short_name='manifest.json'
6ebc5b
             )
6ebc5b
 
6ebc5b
+        # print results in estimate mode (to include also just added manifest)
6ebc5b
+        if self.opts.estimate_only:
6ebc5b
+            from sos.utilities import get_human_readable
6ebc5b
+            from pathlib import Path
6ebc5b
+            # add sos_logs, sos_reports dirs, etc., basically everything
6ebc5b
+            # that remained in self.tmpdir after plugins' contents removal
6ebc5b
+            # that still will be moved to the sos report final directory path
6ebc5b
+            tmpdir_path = Path(self.tmpdir)
6ebc5b
+            self.estimated_plugsizes['sos_logs_reports'] = sum(
6ebc5b
+                    [f.lstat().st_size for f in tmpdir_path.glob('**/*')])
6ebc5b
+
6ebc5b
+            _sum = get_human_readable(sum(self.estimated_plugsizes.values()))
6ebc5b
+            self.ui_log.info("Estimated disk space requirement for whole "
6ebc5b
+                             "uncompressed sos report directory: %s" % _sum)
6ebc5b
+            bigplugins = sorted(self.estimated_plugsizes.items(),
6ebc5b
+                                key=lambda x: x[1], reverse=True)[:5]
6ebc5b
+            bp_out = ",  ".join("%s: %s" %
6ebc5b
+                                (p, get_human_readable(v, precision=0))
6ebc5b
+                                for p, v in bigplugins)
6ebc5b
+            self.ui_log.info("Five biggest plugins:  %s" % bp_out)
6ebc5b
+            self.ui_log.info("")
6ebc5b
+            self.ui_log.info("Please note the estimation is relevant to the "
6ebc5b
+                             "current options.")
6ebc5b
+            self.ui_log.info("Be aware that the real disk space requirements "
6ebc5b
+                             "might be different.")
6ebc5b
+            self.ui_log.info("")
6ebc5b
+
6ebc5b
         # package up and compress the results
6ebc5b
         if not self.opts.build:
6ebc5b
             old_umask = os.umask(0o077)
6ebc5b
@@ -1377,32 +1403,6 @@ class SoSReport(SoSComponent):
6ebc5b
             self.policy.display_results(archive, directory, checksum,
6ebc5b
                                         map_file=map_file)
6ebc5b
 
6ebc5b
-        if self.opts.estimate_only:
6ebc5b
-            from sos.utilities import get_human_readable
6ebc5b
-            from pathlib import Path
6ebc5b
-            # add sos_logs, sos_reports dirs, etc., basically everything
6ebc5b
-            # that remained in self.tmpdir after plugins' contents removal
6ebc5b
-            # that still will be moved to the sos report final directory path
6ebc5b
-            tmpdir_path = Path(self.tmpdir)
6ebc5b
-            self.estimated_plugsizes['sos_logs_reports'] = sum(
6ebc5b
-                    [f.stat().st_size for f in tmpdir_path.glob('**/*')])
6ebc5b
-
6ebc5b
-            _sum = get_human_readable(sum(self.estimated_plugsizes.values()))
6ebc5b
-            self.ui_log.info("Estimated disk space requirement for whole "
6ebc5b
-                             "uncompressed sos report directory: %s" % _sum)
6ebc5b
-            bigplugins = sorted(self.estimated_plugsizes.items(),
6ebc5b
-                                key=lambda x: x[1], reverse=True)[:3]
6ebc5b
-            bp_out = ",  ".join("%s: %s" %
6ebc5b
-                                (p, get_human_readable(v, precision=0))
6ebc5b
-                                for p, v in bigplugins)
6ebc5b
-            self.ui_log.info("Three biggest plugins:  %s" % bp_out)
6ebc5b
-            self.ui_log.info("")
6ebc5b
-            self.ui_log.info("Please note the estimation is relevant to the "
6ebc5b
-                             "current options.")
6ebc5b
-            self.ui_log.info("Be aware that the real disk space requirements "
6ebc5b
-                             "might be different.")
6ebc5b
-            self.ui_log.info("")
6ebc5b
-
6ebc5b
         if self.opts.upload or self.opts.upload_url:
6ebc5b
             if not self.opts.build:
6ebc5b
                 try:
6ebc5b
-- 
6ebc5b
2.31.1
6ebc5b