Memory leak from TCanvas::DrawClonePad and help work around

Hi guys, how are you?

I was using valgrind to search for memory leaks in my code and spotted that DrawClonePad indirectly calls TDirectory::CloneObject for which the cloned objected created here is not deleted.

My usage of DrawClonePad is as follows: I have, let’s say, 4 canvas each one one with the same number of histograms inside (3 for instance); I want to have another canvas (called canvasmemleak, e.g) that has as pads each of the earlier canvases, that is, canvasmemleak has 4 pads and each one is one of the canvases with the histograms, exemplified by this code:

TCanvas* canvasmemleak = new TCanvas(/*...*/);//..
vector<TCanvas*> canvases; //...
int nImages = (int)canvases.size();
for (int i = 0; i < nImages; i++) {
      canvasmemleak->cd(i + 1);
      canvases[i]->DrawClonePad();
}

Unfortunately the memory leak caused by DrawClonePad is the biggest one I have and the programme crashes after consuming a certain amount of memory which was previously set and which should not be increased for the running processes.

So I tried a work around which was to have a canvas with each pad being one of histograms of the original 4 canvases, that is, a canvas with 12 histograms (12 pads), exemplified by this code:

TCanvas* multicnvs = new TCanvas(/*...*/);
for (int i = 0; i < nImages; i++) {
     for (int j = 0; j < nPads; j++) {
       TPad* clone = (TPad*)canvases[i]->GetPad(j + 1);
       multicnvs->cd(i * nPads + j + 1);
       clone->DrawClone();
    }
}

Actually I prefer this is solution because it gives control of what to plot (I get the 4 canvases from “outside”) and do so if the histograms are meaningful. But my problem is that I wasn’t able to reproduce the same final visual result as DrawClonePad allows.

I have the 2 macros that I used for testing:

//testCanvasVec.C
#include <vector>
#include <iostream>
#include <TCanvas.h>
#include <TMath.h>
#include <TString.h>
#include <TH1D.h>

std::vector<TCanvas*> testCanvasVec(int nCvs = 2, int nPds = 3) {

   std::vector<TCanvas*> canvases(nCvs);
   int nx = (int) TMath::Sqrt(nPds);
   int ny = nx;
   if(nx < TMath::Sqrt(nPds) && TMath::Nint(TMath::Sqrt(nPds)) == TMath::CeilNint(TMath::Sqrt(nPds)))
      nx++;
   if (ny < TMath::Sqrt(nPds))
      ny++;
   std::cout << "nx: " << nx << std::endl;
   std::cout << "ny: " << ny << std::endl;
   for (int i = 0; i < nCvs; ++i) {
      TString cnv_name("cnv_");
      cnv_name += i;
      TString cnv_title("rnd histos ");
      cnv_title += i;
      canvases[i] = new TCanvas(cnv_name.Data(), cnv_title);
      canvases[i]->Divide(nx, ny);
      TH1D* h;
      for (int j = 0; j < nPds; ++j) {
         TString h_name("h1d_");
         (h_name += i) += j;
         TString h_title("rnd histo ");
         (h_title += i) += j;
         h = new TH1D(h_name.Data(), h_title, 100 * (j + 1), -3 - j, 3 + j);
         h->FillRandom("gaus", 1000 * (j + 1));
         canvases[i]->cd(j + 1);
         h->Draw();
      }
      //canvases[i]->Update();
      //canvases[i]->Draw();
   }
   return canvases;
}
// End of testCanvasVec.C

//testMultiCanvas.C
#include "testCanvasVec.C"

void testMultiCanvas(int nCvs = 2, int nPds = 3) {

   std::vector<TCanvas*> canvases = testCanvasVec(nCvs, nPds);
   int nImages = canvases.size();

/*
   int nx = (int) TMath::Sqrt(nImages);
   int ny = nx;
   if (nx < TMath::Sqrt(nImages) && TMath::Nint(TMath::Sqrt(nImages)) == TMath::CeilNint(TMath::Sqrt(nImages)))
     nx++;
   if (ny < TMath::Sqrt(nImages))
     ny++;

   TCanvas* canvasmemleak = new TCanvas("canvasmemleak", "Canvas Mem Leak", 1000, 1000);
   canvasmemleak->Divide(nx, ny);
   for (int i = 0; i < nImages; i++) {
      canvasmemleak->cd(i + 1);// because canvas nb 0 is the parent
      canvases[i]->DrawClonePad();
   }
   canvasmemleak->Update();
   canvasmemleak->Draw(); // The intended final result
*/
   TCanvas* multicnvs = new TCanvas("multicnvs", "Multi Canvas", 1000, 1000);
   int nPads = 0;
   TPad* pad;
   while((pad = (TPad*)canvases[0]->GetPad(nPads + 1)) != (TPad*)0) {
      std::cout << "# pads: " << ++nPads << std::endl;
      std::cout << "pad: " << pad << std::endl;
   }
   std::cout << "# of pads: " << nPads << std::endl;
   int nxpad = (int) TMath::Sqrt(nPads * nImages);
   int nypad = nxpad;
   if (nxpad < TMath::Sqrt(nPads* nImages) && TMath::Nint(TMath::Sqrt(nPads* nImages)) == TMath::CeilNint(TMath::Sqrt(nPads* nImages)))
     nxpad++;
   if (nypad < TMath::Sqrt(nPads* nImages))
     nypad++;
   std::cout << "nxpad: " << nxpad << std::endl;
   std::cout << "nypad: " << nypad << std::endl;
   multicnvs->Divide(nxpad,nypad);
//    TCanvas* cnv = (TCanvas*)((TCanvas*)multicnvs->cd(1)->Clone());//multicnvs->cd(0)
//    uint ww = cnv->GetWindowWidth(), wh = cnv->GetWindowHeight();
//    std::cout << "ww: " << ww << std::endl;
//    std::cout << "wh: " << wh << std::endl;
   for (int i = 0; i < nImages; i++) { //TODO: reorder drawing to match canvasmemleak looks
     for (int j = 0; j < nPads; j++) {
       TPad* clone = (TPad*)canvases[i]->GetPad(j + 1);
       multicnvs->cd(i * nPads + j + 1);
//        clone->ResizePad();
//        clone->Update();
       clone->DrawClone();
//        clone->SetWindowSize(w, 1);
     }
   }
   multicnvs->Update();
   multicnvs->Draw();
}
//End of  testMultiCanvas.C

So to summarize:

  1. Is the memory leak a bug or can I do something that prevents this leakage?

  2. How can I draw the histograms at least to be visible?

Many thanks for your help!!

Cheers,

Bart

Follow this thread here: savannah.cern.ch/bugs/?82439