Df 0 1 3_ Inspect Analysis

Use callbacks to update a plot and a progress bar during the event loop.

Showcase registration of callback functions that act on partial results while the event-loop is running using OnPartialResult and OnPartialResultSlot. This tutorial is not meant to run in batch mode.

Author: Enrico Guiraud (CERN)
This notebook tutorial was automatically generated with ROOTBOOK-izer from the macro found in the ROOT repository on Tuesday, June 15, 2021 at 07:17 AM.

In [1]:
using namespace ROOT; // RDataFrame lives in here
In [2]:
ROOT::EnableImplicitMT();
const auto poolSize = ROOT::GetThreadPoolSize();
const auto nSlots = 0 == poolSize ? 1 : poolSize;

Setup a simple RDataFrame

We start by creating a RDataFrame with a good number of empty events

In [3]:
const auto nEvents = nSlots * 10000ull;
RDataFrame d(nEvents);

heavywork is a lambda that fakes some interesting computation and just returns a normally distributed double

In [4]:
TRandom r;
auto heavyWork = [&r]() {
   for (volatile int i = 0; i < 1000000; ++i)
      ;
   return r.Gaus();
};

Let's define a column "x" produced by invoking heavywork for each event df stores a modified data-frame that contains "x"

In [5]:
auto df = d.Define("x", heavyWork);

Now we register a histogram-filling action with the rdataframe. h can be used just like a pointer to TH1D but it is actually a TResultProxy, a smart object that triggers an event-loop to fill the pointee histogram if needed.

In [6]:
auto h = df.Histo1D<double>({"browserHisto", "", 100, -2., 2.}, "x");

Use the callback mechanism to draw the histogram on a TBrowser while it is being filled

So far we have registered a column "x" to a data-frame with nEvents events and we registered the filling of a histogram with the values of column "x". In the following we will register three functions for execution during the event-loop:

  • one is to be executed once just before the loop and adds a partially-filled histogram to a TBrowser
  • the next is executed every 50 events and draws the partial histogram on the TBrowser's TPad
  • another callback is responsible of updating a simple progress bar from multiple threads

First off we create a tbrowser that contains a "rdfresults" directory

In [7]:
auto dfDirectory = new TMemFile("RDFResults", "RECREATE");
auto browser = new TBrowser("b", dfDirectory);
Warning in <TBrowser::TBrowser>: The ROOT browser cannot run in batch mode

The global pad should now be set to the tbrowser's canvas, let's store its value in a local variable

In [8]:
auto browserPad = gPad;

A useful feature of tresultproxy is its onpartialresult method: it allows us to register a callback that is executed once per specified number of events during the event-loop, on "partial" versions of the result objects contained in the TResultProxy. In this case, the partial result is going to be a histogram filled with an increasing number of events. Instead of requesting the callback to be executed every N entries, this time we use the special value kOnce to request that it is executed once right before starting the event-loop. The callback is a C++11 lambda that registers the partial result object in dfDirectory.

In [9]:
h.OnPartialResult(h.kOnce, [dfDirectory](TH1D &h_) { dfDirectory->Add(&h_); });
input_line_56:2:30: error: 'dfDirectory' cannot be captured because it does not have automatic storage duration
 h.OnPartialResult(h.kOnce, [dfDirectory](TH1D &h_) { dfDirectory->Add(&h_); });
                             ^
input_line_52:2:7: note: 'dfDirectory' declared here
 auto dfDirectory = new TMemFile("RDFResults", "RECREATE");
      ^

Note that we called onpartialresult with a dot, ., since this is a method of tresultproxy itself. We do not want to call OnPartialResult on the pointee histogram!)

Multiple callbacks can be registered on the same tresultproxy (they are executed one after the other in the same order as they were registered). We now request that the partial result is drawn and the TBrowser's TPad is updated every 50 events.

In [10]:
h.OnPartialResult(50, [&browserPad](TH1D &hist) {
   if (!browserPad)
      return; // in case root -b was invoked
   browserPad->cd();
   hist.Draw();
   browserPad->Update();
   // This call tells ROOT to process all pending GUI events
   // It allows users to use the TBrowser as usual while the event-loop is running
   gSystem->ProcessEvents();
});
input_line_57:2:26: error: 'browserPad' cannot be captured because it does not have automatic storage duration
 h.OnPartialResult(50, [&browserPad](TH1D &hist) {
                         ^
input_line_55:2:7: note: 'browserPad' declared here
 auto browserPad = gPad;
      ^

Finally, we would like to print a progress bar on the terminal to show how the event-loop is progressing. To take into account all events we use OnPartialResultSlot: when Implicit Multi-Threading is enabled, in fact, OnPartialResult invokes the callback only in one of the worker threads, and always returns that worker threads' partial result. This is useful because it means we don't have to worry about concurrent execution and thread-safety of the callbacks if we are happy with just one threads' partial result. OnPartialResultSlot, on the other hand, invokes the callback in each one of the worker threads, every time a thread finishes processing a batch of everyN events. This is what we want for the progress bar, but we need to take care that two threads will not print to terminal at the same time: we need a std::mutex for synchronization.

In [11]:
std::string progressBar;
std::mutex barMutex; // Only one thread at a time can lock a mutex. Let's use this to avoid concurrent printing.

Magic numbers that yield good progress bars for nslots = 1,2,4,8

In [12]:
const auto everyN = nSlots == 8 ? 1000 : 100ull * nSlots;
const auto barWidth = nEvents / everyN;
h.OnPartialResultSlot(everyN, [&barWidth, &progressBar, &barMutex](unsigned int /*slot*/, TH1D & /*partialHist*/) {
   std::lock_guard<std::mutex> l(barMutex); // lock_guard locks the mutex at construction, releases it at destruction
   progressBar.push_back('#');
   // re-print the line with the progress bar
   std::cout << "\r[" << std::left << std::setw(barWidth) << progressBar << ']' << std::flush;
});
input_line_59:4:44: error: 'progressBar' cannot be captured because it does not have automatic storage duration
h.OnPartialResultSlot(everyN, [&barWidth, &progressBar, &barMutex](unsigned int /*slot*/, TH1D & /*partialHist*/) {
                                           ^
input_line_58:2:14: note: 'progressBar' declared here
 std::string progressBar;
             ^
input_line_59:4:58: error: 'barMutex' cannot be captured because it does not have automatic storage duration
h.OnPartialResultSlot(everyN, [&barWidth, &progressBar, &barMutex](unsigned int /*slot*/, TH1D & /*partialHist*/) {
                                                         ^
input_line_58:3:12: note: 'barMutex' declared here
std::mutex barMutex; // Only one thread at a time can lock a mutex. Let's use this to avoid concurrent printing.
           ^

Running the analysis

So far we told RDataFrame what we want to happen during the event-loop, but we have not actually run any of those actions: the TBrowser is still empty, the progress bar has not been printed even once, and we haven't produced a single data-point! As usual with RDataFrame, the event-loop is triggered by accessing the contents of a TResultProxy for the first time. Let's run!

In [13]:
std::cout << "Analysis running..." << std::endl;
h->Draw(); // the final, complete result will be drawn after the event-loop has completed.
std::cout << "\nDone!" << std::endl;
IncrementalExecutor::executeFunction: symbol '_ZSteqI4TH1DEbRKSt10shared_ptrIT_EDn' unresolved while linking [cling interface function]!
You are probably missing the definition of bool std::operator==<TH1D>(std::shared_ptr<TH1D> const&, decltype(nullptr))
Maybe you need to load the corresponding shared library?

Finally, some book-keeping: in the tmemfile that we are using as tbrowser directory, we substitute the partial result with a clone of the final result (the "original" final result will be deleted at the end of the macro).

In [14]:
dfDirectory->Clear();
auto clone = static_cast<TH1D *>(h->Clone());
clone->SetDirectory(nullptr);
dfDirectory->Add(clone);
if (!browserPad)
   return; // in case root -b was invoked
browserPad->cd();
clone->Draw();
browserPad->Update();
IncrementalExecutor::executeFunction: symbol '_ZSteqI4TH1DEbRKSt10shared_ptrIT_EDn' unresolved while linking function '_GLOBAL__sub_I_cling_module_256'!
You are probably missing the definition of bool std::operator==<TH1D>(std::shared_ptr<TH1D> const&, decltype(nullptr))
Maybe you need to load the corresponding shared library?

Thread 9 (Thread 0x7f09d57fa700 (LWP 3488147)):
#0  0x00007f09f5aa912b in __GI___select (nfds=0, readfds=0x0, writefds=0x0, exceptfds=0x0, timeout=0x7f09d57f98c0) at ../sysdeps/unix/sysv/linux/select.c:41
#1  0x000000000061d8be in ?? ()
#2  0x00000000005c4730 in ?? ()
#3  0x0000000000570296 in _PyEval_EvalFrameDefault ()
#4  0x00000000005f5ed6 in _PyFunction_Vectorcall ()
#5  0x000000000056b3fe in _PyEval_EvalFrameDefault ()
#6  0x00000000005f5ed6 in _PyFunction_Vectorcall ()
#7  0x000000000056b3fe in _PyEval_EvalFrameDefault ()
#8  0x00000000005f5ed6 in _PyFunction_Vectorcall ()
#9  0x000000000050a5cc in ?? ()
#10 0x00000000005f54e7 in PyObject_Call ()
#11 0x0000000000654f8c in ?? ()
#12 0x0000000000674ac8 in ?? ()
#13 0x00007f09f5977609 in start_thread (arg=<optimized out>) at pthread_create.c:477
#14 0x00007f09f5ab3293 in clone () at ../sysdeps/unix/sysv/linux/x86_64/clone.S:95

Thread 8 (Thread 0x7f09d5ffb700 (LWP 3488146)):
#0  0x00007f09f5a76dff in __GI___wait4 (pid=3488161, stat_loc=stat_loc
entry=0x7f09d5ff7668, options=options
entry=0, usage=usage
entry=0x0) at ../sysdeps/unix/sysv/linux/wait4.c:27
#1  0x00007f09f5a76d7b in __GI___waitpid (pid=<optimized out>, stat_loc=stat_loc
entry=0x7f09d5ff7668, options=options
entry=0) at waitpid.c:38
#2  0x00007f09f59e60e7 in do_system (line=<optimized out>) at ../sysdeps/posix/system.c:172
#3  0x00007f09f38bfede in TUnixSystem::StackTrace() () from /home/sftnight/build/workspace/root-makedoc-master/rootspi/rdoc/src/master.build/lib/libCore.so
#4  0x00007f09ef204c00 in cling::MultiplexInterpreterCallbacks::PrintStackTrace() () from /home/sftnight/build/workspace/root-makedoc-master/rootspi/rdoc/src/master.build/lib/libCling.so
#5  0x00007f09ef1f8762 in cling_runtime_internal_throwIfInvalidPointer () from /home/sftnight/build/workspace/root-makedoc-master/rootspi/rdoc/src/master.build/lib/libCling.so
#6  0x00007f09dc00b121 in ?? ()
#7  0x00000000027f1740 in ?? ()
#8  0x00007f09d5ff9d00 in ?? ()
#9  0x0000000000000000 in ?? ()

Thread 7 (Thread 0x7f09d67fc700 (LWP 3488137)):
#0  0x00007f09f5ab35ce in epoll_wait (epfd=27, events=0x7f09d67fb200, maxevents=256, timeout=-1) at ../sysdeps/unix/sysv/linux/epoll_wait.c:30
#1  0x00007f09e4d5c899 in ?? () from /lib/x86_64-linux-gnu/libzmq.so.5
#2  0x00007f09e4d9ee5f in ?? () from /lib/x86_64-linux-gnu/libzmq.so.5
#3  0x00007f09f5977609 in start_thread (arg=<optimized out>) at pthread_create.c:477
#4  0x00007f09f5ab3293 in clone () at ../sysdeps/unix/sysv/linux/x86_64/clone.S:95

Thread 6 (Thread 0x7f09d6ffd700 (LWP 3488136)):
#0  0x00007f09f5ab35ce in epoll_wait (epfd=25, events=0x7f09d6ffc200, maxevents=256, timeout=-1) at ../sysdeps/unix/sysv/linux/epoll_wait.c:30
#1  0x00007f09e4d5c899 in ?? () from /lib/x86_64-linux-gnu/libzmq.so.5
#2  0x00007f09e4d9ee5f in ?? () from /lib/x86_64-linux-gnu/libzmq.so.5
#3  0x00007f09f5977609 in start_thread (arg=<optimized out>) at pthread_create.c:477
#4  0x00007f09f5ab3293 in clone () at ../sysdeps/unix/sysv/linux/x86_64/clone.S:95

Thread 5 (Thread 0x7f09d77fe700 (LWP 3488135)):
#0  0x00007f09f5aa6aff in __GI___poll (fds=0x7f09d77fd590, nfds=2, timeout=-1) at ../sysdeps/unix/sysv/linux/poll.c:29
#1  0x00007f09e4dac339 in zmq_poll () from /lib/x86_64-linux-gnu/libzmq.so.5
#2  0x00007f09e4d7e5ce in ?? () from /lib/x86_64-linux-gnu/libzmq.so.5
#3  0x00007f09e49420f7 in ?? () from /usr/lib/python3/dist-packages/zmq/backend/cython/_device.cpython-38-x86_64-linux-gnu.so
#4  0x00007f09e49413f6 in ?? () from /usr/lib/python3/dist-packages/zmq/backend/cython/_device.cpython-38-x86_64-linux-gnu.so
#5  0x00000000005f66f6 in _PyObject_MakeTpCall ()
#6  0x000000000057094b in _PyEval_EvalFrameDefault ()
#7  0x00000000005f5ed6 in _PyFunction_Vectorcall ()
#8  0x000000000056b3fe in _PyEval_EvalFrameDefault ()
#9  0x00000000005f5ed6 in _PyFunction_Vectorcall ()
#10 0x000000000056b3fe in _PyEval_EvalFrameDefault ()
#11 0x00000000005f5ed6 in _PyFunction_Vectorcall ()
#12 0x000000000050a5cc in ?? ()
#13 0x00000000005f54e7 in PyObject_Call ()
#14 0x0000000000654f8c in ?? ()
#15 0x0000000000674ac8 in ?? ()
#16 0x00007f09f5977609 in start_thread (arg=<optimized out>) at pthread_create.c:477
#17 0x00007f09f5ab3293 in clone () at ../sysdeps/unix/sysv/linux/x86_64/clone.S:95

Thread 4 (Thread 0x7f09d7fff700 (LWP 3488134)):
#0  0x00007f09f5ab35ce in epoll_wait (epfd=17, events=0x7f09dc142f60, maxevents=3, timeout=-1) at ../sysdeps/unix/sysv/linux/epoll_wait.c:30
#1  0x0000000000635c7c in ?? ()
#2  0x00000000005047ae in ?? ()
#3  0x000000000056b3fe in _PyEval_EvalFrameDefault ()
#4  0x000000000056951a in _PyEval_EvalCodeWithName ()
#5  0x00000000005f60b3 in _PyFunction_Vectorcall ()
#6  0x000000000056b3fe in _PyEval_EvalFrameDefault ()
#7  0x00000000005f5ed6 in _PyFunction_Vectorcall ()
#8  0x000000000056b3fe in _PyEval_EvalFrameDefault ()
#9  0x00000000005f5ed6 in _PyFunction_Vectorcall ()
#10 0x000000000056b3fe in _PyEval_EvalFrameDefault ()
#11 0x00000000005f5ed6 in _PyFunction_Vectorcall ()
#12 0x000000000056b3fe in _PyEval_EvalFrameDefault ()
#13 0x00000000005f5ed6 in _PyFunction_Vectorcall ()
#14 0x000000000050a5cc in ?? ()
#15 0x00000000005f54e7 in PyObject_Call ()
#16 0x000000000056ca9e in _PyEval_EvalFrameDefault ()
#17 0x00000000005f5ed6 in _PyFunction_Vectorcall ()
#18 0x000000000056b3fe in _PyEval_EvalFrameDefault ()
#19 0x00000000005f5ed6 in _PyFunction_Vectorcall ()
#20 0x000000000056b3fe in _PyEval_EvalFrameDefault ()
#21 0x00000000005f5ed6 in _PyFunction_Vectorcall ()
#22 0x000000000050a5cc in ?? ()
#23 0x00000000005f54e7 in PyObject_Call ()
#24 0x0000000000654f8c in ?? ()
#25 0x0000000000674ac8 in ?? ()
#26 0x00007f09f5977609 in start_thread (arg=<optimized out>) at pthread_create.c:477
#27 0x00007f09f5ab3293 in clone () at ../sysdeps/unix/sysv/linux/x86_64/clone.S:95

Thread 3 (Thread 0x7f09dcdd6700 (LWP 3488133)):
#0  0x00007f09f5ab35ce in epoll_wait (epfd=8, events=0x7f09dcdd5200, maxevents=256, timeout=-1) at ../sysdeps/unix/sysv/linux/epoll_wait.c:30
#1  0x00007f09e4d5c899 in ?? () from /lib/x86_64-linux-gnu/libzmq.so.5
#2  0x00007f09e4d9ee5f in ?? () from /lib/x86_64-linux-gnu/libzmq.so.5
#3  0x00007f09f5977609 in start_thread (arg=<optimized out>) at pthread_create.c:477
#4  0x00007f09f5ab3293 in clone () at ../sysdeps/unix/sysv/linux/x86_64/clone.S:95

Thread 2 (Thread 0x7f09dd5d7700 (LWP 3488132)):
#0  0x00007f09f5ab35ce in epoll_wait (epfd=6, events=0x7f09dd5d6200, maxevents=256, timeout=-1) at ../sysdeps/unix/sysv/linux/epoll_wait.c:30
#1  0x00007f09e4d5c899 in ?? () from /lib/x86_64-linux-gnu/libzmq.so.5
#2  0x00007f09e4d9ee5f in ?? () from /lib/x86_64-linux-gnu/libzmq.so.5
#3  0x00007f09f5977609 in start_thread (arg=<optimized out>) at pthread_create.c:477
#4  0x00007f09f5ab3293 in clone () at ../sysdeps/unix/sysv/linux/x86_64/clone.S:95

Thread 1 (Thread 0x7f09f57c5740 (LWP 3488109)):
#0  futex_abstimed_wait_cancelable (private=<optimized out>, abstime=0x7ffd34c11af0, clockid=<optimized out>, expected=0, futex_word=0x93bb4c <_PyRuntime+1228>) at ../sysdeps/nptl/futex-internal.h:320
#1  __pthread_cond_wait_common (abstime=0x7ffd34c11af0, clockid=<optimized out>, mutex=0x93bb50 <_PyRuntime+1232>, cond=0x93bb20 <_PyRuntime+1184>) at pthread_cond_wait.c:520
#2  __pthread_cond_timedwait (cond=0x93bb20 <_PyRuntime+1184>, mutex=0x93bb50 <_PyRuntime+1232>, abstime=0x7ffd34c11af0) at pthread_cond_wait.c:665
#3  0x000000000057449d in PyEval_RestoreThread ()
#4  0x000000000061d8c8 in ?? ()
#5  0x00000000005c4730 in ?? ()
#6  0x000000000056b21e in _PyEval_EvalFrameDefault ()
#7  0x000000000056951a in _PyEval_EvalCodeWithName ()
#8  0x00000000005f60b3 in _PyFunction_Vectorcall ()
#9  0x000000000056b21e in _PyEval_EvalFrameDefault ()
#10 0x000000000056951a in _PyEval_EvalCodeWithName ()
#11 0x00000000005f60b3 in _PyFunction_Vectorcall ()
#12 0x000000000056b3fe in _PyEval_EvalFrameDefault ()
#13 0x000000000056951a in _PyEval_EvalCodeWithName ()
#14 0x00000000005f60b3 in _PyFunction_Vectorcall ()
#15 0x000000000056b3fe in _PyEval_EvalFrameDefault ()
#16 0x00000000005009d3 in ?? ()
#17 0x00000000004f51f3 in ?? ()
#18 0x00000000005c49a0 in ?? ()
#19 0x000000000056b21e in _PyEval_EvalFrameDefault ()
#20 0x000000000056951a in _PyEval_EvalCodeWithName ()
#21 0x000000000050a640 in ?? ()
#22 0x000000000056b21e in _PyEval_EvalFrameDefault ()
#23 0x00000000005009d3 in ?? ()
#24 0x00000000004f51f3 in ?? ()
#25 0x00000000005c49a0 in ?? ()
#26 0x000000000056b21e in _PyEval_EvalFrameDefault ()
#27 0x000000000056951a in _PyEval_EvalCodeWithName ()
#28 0x00000000005f60b3 in _PyFunction_Vectorcall ()
#29 0x000000000050a5cc in ?? ()
#30 0x00000000005f54e7 in PyObject_Call ()
#31 0x000000000056ca9e in _PyEval_EvalFrameDefault ()
#32 0x0000000000500528 in ?? ()
#33 0x0000000000504186 in ?? ()
#34 0x000000000056b3fe in _PyEval_EvalFrameDefault ()
#35 0x00000000005f5ed6 in _PyFunction_Vectorcall ()
#36 0x000000000056b3fe in _PyEval_EvalFrameDefault ()
#37 0x000000000056951a in _PyEval_EvalCodeWithName ()
#38 0x00000000005f60b3 in _PyFunction_Vectorcall ()
#39 0x00000000005f54e7 in PyObject_Call ()
#40 0x000000000056ca9e in _PyEval_EvalFrameDefault ()
#41 0x000000000056951a in _PyEval_EvalCodeWithName ()
#42 0x00000000005f60b3 in _PyFunction_Vectorcall ()
#43 0x00000000005f6b6b in _PyObject_FastCallDict ()
#44 0x0000000000664e8d in ?? ()
#45 0x00000000005f66f6 in _PyObject_MakeTpCall ()
#46 0x00000000005704c3 in _PyEval_EvalFrameDefault ()
#47 0x00000000005f5ed6 in _PyFunction_Vectorcall ()
#48 0x000000000050a5cc in ?? ()
#49 0x00000000004f2d9e in ?? ()
#50 0x00000000005fe883 in ?? ()
#51 0x00000000005c4887 in ?? ()
#52 0x00000000005f598d in PyVectorcall_Call ()
#53 0x00000000005714d6 in _PyEval_EvalFrameDefault ()
#54 0x00000000005f5ed6 in _PyFunction_Vectorcall ()
#55 0x000000000056b3fe in _PyEval_EvalFrameDefault ()
#56 0x00000000005f5ed6 in _PyFunction_Vectorcall ()
#57 0x000000000056b3fe in _PyEval_EvalFrameDefault ()
#58 0x00000000005f5ed6 in _PyFunction_Vectorcall ()
#59 0x000000000056b3fe in _PyEval_EvalFrameDefault ()
#60 0x00000000005f5ed6 in _PyFunction_Vectorcall ()
#61 0x000000000056b3fe in _PyEval_EvalFrameDefault ()
#62 0x00000000005f5ed6 in _PyFunction_Vectorcall ()
#63 0x000000000056b3fe in _PyEval_EvalFrameDefault ()
#64 0x000000000056951a in _PyEval_EvalCodeWithName ()
#65 0x000000000050a640 in ?? ()
#66 0x000000000056c3f8 in _PyEval_EvalFrameDefault ()
#67 0x00000000005f5ed6 in _PyFunction_Vectorcall ()
#68 0x000000000056b21e in _PyEval_EvalFrameDefault ()
#69 0x000000000056951a in _PyEval_EvalCodeWithName ()
#70 0x000000000068be87 in PyEval_EvalCode ()
#71 0x00000000005ffde4 in ?? ()
#72 0x00000000005c49a0 in ?? ()
#73 0x000000000056b21e in _PyEval_EvalFrameDefault ()
#74 0x000000000056951a in _PyEval_EvalCodeWithName ()
#75 0x00000000005f60b3 in _PyFunction_Vectorcall ()
#76 0x000000000056b21e in _PyEval_EvalFrameDefault ()
#77 0x000000000056951a in _PyEval_EvalCodeWithName ()
#78 0x00000000005f60b3 in _PyFunction_Vectorcall ()
#79 0x00000000005f54e7 in PyObject_Call ()
#80 0x00000000006b53f8 in ?? ()
#81 0x00000000006b57e9 in Py_RunMain ()
#82 0x00000000006b5a0d in Py_BytesMain ()
#83 0x00007f09f59b80b3 in __libc_start_main (main=0x4eee60 <main>, argc=5, argv=0x7ffd34c150f8, init=<optimized out>, fini=<optimized out>, rtld_fini=<optimized out>, stack_end=0x7ffd34c150e8) at ../csu/libc-start.c:308
#84 0x00000000005f9ece in _start ()
Error in <HandleInterpreterException>: Trying to dereference null pointer or trying to call routine taking non-null arguments.
Execution of your code was aborted.
input_line_61:4:1: warning: null passed to a callee that requires a non-null argument [-Wnonnull]
clone->SetDirectory(nullptr);
^~~~~

Draw all canvases

In [15]:
gROOT->GetListOfCanvases()->Draw()