Repository: ManimCommunity/manim
Pull request: Build the docs - Logging 
## Changelog / Overview
<!--changelog-start-->

<!--changelog-end-->

## Motivation
The logging while building the docs is currently hardly usable.
closes #1456


## Explanation for Changes
 ...I tried to fix that by disabling manims info logs while rendering and summarizing relevant metrics after the build is finished. 

Have a look at how the docs log now:
https://readthedocs.org/api/v2/build/13697172.txt

## Further Comments
The rendering times are saved as .csv file and can be used to easiely access rendering duration data for various purposes.

## Checklist
- [x] I have read the [Contributing Guidelines](https://docs.manim.community/en/latest/contributing.html)
- [x] I have written a descriptive PR title (see top of PR template for examples)examples in the docs
- [x] My new documentation builds, looks correctly formatted, and adds no additional build warnings
<!-- Once again, thanks for contributing to ManimCommunity! -->


<!-- Do not modify the lines below. These are for the reviewers of your PR -->
## Reviewer Checklist
- [ ] The PR title is descriptive enough
- [ ] The PR is labeled correctly
- [ ] The changelog entry is completed if necessary
- [ ] Newly added functions/classes either have a docstring or are private
- [ ] Newly added functions/classes have [tests](https://github.com/ManimCommunity/manim/wiki/Testing) added and (optional) examples in the docs
- [ ] Newly added documentation builds, looks correctly formatted, and adds no additional build warnings


Problem statement:
Improving the "Build the docs" logging
## Description of proposed feature
The "Build the docs" log is barely usable because it is messy.

Some things I'd like to see changed:

- Reduce the manim rendering log.
- Log the time each example rendering takes.
- Log the time rendering all examples takes.
- (Log what amount of that rendering time is required for LaTeX typesetting)  


## How can the new feature be used?
When working with examples in the docs this could be used to make decisions about how complex they should be to keep the CI time reasonable.

Hints from issue discussion:
...I am working on this.

Commit message: use csv module

Patch to review:
diff --git a/.gitignore b/.gitignore
index e1490a2b56..7f73a68744 100644
--- a/.gitignore
+++ b/.gitignore
@@ -59,6 +59,7 @@ docs/build/
 docs/source/_autosummary/
 docs/source/reference/
 docs/source/_build/
+docs/rendering_times.csv
 
 # PyBuilder
 target/
diff --git a/docs/Makefile b/docs/Makefile
index 846802ddae..f503fce4ec 100644
--- a/docs/Makefile
+++ b/docs/Makefile
@@ -19,8 +19,9 @@ help:
 # Like make clean but also remove files generated by autosummary and
 # rendered videos.
 cleanall: clean
-	rm source/reference/*
-	rm -rf source/media
+	@rm source/reference/*
+	@rm -rf source/media
+	@rm -f rendering_times.csv
 
 # Catch-all target: route all unknown targets to Sphinx using the new
 # "make mode" option.  $(O) is meant as a shortcut for $(SPHINXOPTS).
diff --git a/docs/source/manim_directive.py b/docs/source/manim_directive.py
index af68296267..03be57261c 100644
--- a/docs/source/manim_directive.py
+++ b/docs/source/manim_directive.py
@@ -72,9 +72,15 @@ def construct(self):
         that is rendered in a reference block after the source code.
 
 """
+import csv
+import itertools as it
+import os
+import re
 import shutil
+import sys
 from pathlib import Path
-from typing import List
+from timeit import timeit
+from typing import Callable, List
 
 import jinja2
 from docutils import nodes
@@ -205,6 +211,8 @@ def run(self):
         config.video_dir = "{media_dir}/videos/{quality}"
         output_file = f"{clsname}-{classnamedict[clsname]}"
         config.assets_dir = Path("_static")
+        config.progress_bar = "none"
+        config.verbosity = "WARNING"
 
         config_code = [
             f'config["frame_rate"] = {frame_rate}',
@@ -231,7 +239,13 @@ def run(self):
             *user_code,
             f"{clsname}().render()",
         ]
-        exec("\n".join(code), globals())
+        run_time = timeit(lambda: exec("\n".join(code), globals()), number=1)
+
+        _write_rendering_stats(
+            clsname,
+            run_time,
+            self.state.document.settings.env.docname,
+        )
 
         # copy video file to output directory
         if not (save_as_gif or save_last_frame):
@@ -265,6 +279,55 @@ def run(self):
         return []
 
 
+rendering_times_file_path = "../rendering_times.csv"
+
+
+def _write_rendering_stats(scene_name, run_time, file_name):
+    with open(rendering_times_file_path, "a") as file:
+        csv.writer(file).writerow(
+            [
+                re.sub("^(reference\/)|(manim\.)", "", file_name),
+                scene_name,
+                "%.3f" % run_time,
+            ]
+        )
+
+
+def _log_rendering_times(*args):
+    try:
+        with open(rendering_times_file_path) as file:
+            data = list(csv.reader(file))
+            if len(data) == 0:
+                sys.exit()
+
+            print("\nRendering Summary\n-----------------\n")
+
+            max_file_length = max([len(row[0]) for row in data])
+            for key, group in it.groupby(data, key=lambda row: row[0]):
+                key = key.ljust(max_file_length + 1, ".")
+                group = list(group)
+                if len(group) == 1:
+                    row = group[0]
+                    print(f"{key}{row[2].rjust(7, '.')}s {row[1]}")
+                    continue
+                time_sum = sum([float(row[2]) for row in group])
+                print(
+                    f"{key}{f'{time_sum:.3f}'.rjust(7, '.')}s  => {len(group)} EXAMPLES"
+                )
+                for row in group:
+                    print(f"{' '*(max_file_length)} {row[2].rjust(7)}s {row[1]}")
+        print("")
+    except IOError:
+        pass
+
+
+def _delete_rendering_times(*args):
+    try:
+        os.remove(rendering_times_file_path)
+    except OSError:
+        pass
+
+
 def setup(app):
     import manim
 
@@ -276,6 +339,9 @@ def setup(app):
 
     app.add_directive("manim", ManimDirective)
 
+    app.connect("builder-inited", _delete_rendering_times)
+    app.connect("build-finished", _log_rendering_times)
+
     metadata = {"parallel_read_safe": False, "parallel_write_safe": True}
     return metadata
 
diff --git a/manim/mobject/mobject.py b/manim/mobject/mobject.py
index d6320df9b0..99b5472ad6 100644
--- a/manim/mobject/mobject.py
+++ b/manim/mobject/mobject.py
@@ -2337,9 +2337,9 @@ def shuffle_submobjects(self, *args, **kwargs):
         Examples
         --------
 
-        .. manim:: SuffleSumobjectsExample
+        .. manim:: ShuffleSumobjectsExample
 
-            class SuffleSumobjectsExample(Scene):
+            class ShuffleSumobjectsExample(Scene):
                 def construct(self):
                     s= VGroup(*[Dot().shift(i*0.1*RIGHT) for i in range(-20,20)])
                     s2= s.copy()


Code review comments:
diff_hunk: @@ -265,6 +279,55 @@ def run(self):
         return []
 
 
+rendering_times_file_path = "../rendering_times.csv"
path: docs/source/manim_directive.py
text: @user1:
Try using pathlib wherever possible, rather than os module.