CERN’s ROOT Primer (visualization of ping responses data)

ROOT is a framework for data processing, born at CERN, at the heart of the research on high-energy physics. While Python-based data processing framewords are quite trendy now, their performance could be lacking, plus ROOT could be used within a C++ application directly. It has other interesting features too, but in this post we’re going to explore the basic usage: drawing a graph and a boxplot for the ping response values (measured directly in C++ code using Boost.Asio).

Pinging code is straightforward and Boost.Asio documentation could explain all unfamiliar functionality. The high level flow is:

  • Create io_context that would wait until we hit limit of requests/responses;

  • Start sending requests and receiving responses;

  • During send operation we create echo ICMP header and send it with the 5 seconds timeout (and start sending again in 1 second);

  • During receive operation we verify identifier and sequence number of the response, and print the results (in same format as usual ping utility). After that, we start receiving again.

  • As soon as we get the sequence number equals limit, we stop sending/receiving.

Now, let’s analyze the results using ROOT. Please install ROOT using their instructions. Usually it’s available in the package manager, e.g. for Arch Linux:

$ sudo pacman -Syu root

You can download and start the code above directly (requires sudo in GNU/Linux):

$ sudo root ping_stats.C

Or you can start ROOT interactive shell (please note that Jupyter has it as a kernel too):

$ sudo root

(Please note that sudo is required just for the ping functionality, usually you don’t need it).

In ROOT’s prompt we can load (but don’t execute) the code:

root [0] .L ping_stats.C

Let’s ping OpenDNS (Cisco) server 10 times:

root [1] auto samples = 10
(int) 10
root [2] auto values = get_stats("208.67.222.222", samples);
20 bytes from 208.67.222.222: icmp_seq=1, ttl=58, time=24
20 bytes from 208.67.222.222: icmp_seq=2, ttl=58, time=24
20 bytes from 208.67.222.222: icmp_seq=3, ttl=58, time=24
20 bytes from 208.67.222.222: icmp_seq=4, ttl=58, time=32
20 bytes from 208.67.222.222: icmp_seq=5, ttl=58, time=24
20 bytes from 208.67.222.222: icmp_seq=6, ttl=58, time=22
20 bytes from 208.67.222.222: icmp_seq=7, ttl=58, time=25
20 bytes from 208.67.222.222: icmp_seq=8, ttl=58, time=24
20 bytes from 208.67.222.222: icmp_seq=9, ttl=58, time=22
20 bytes from 208.67.222.222: icmp_seq=10, ttl=58, time=27

Please note that if you don’t use a semicolon, the result of the expression is printed immediately. You can examine the values in the interactive shell anytime though:

root [3] values
(std::vector<int, std::allocator<int> > &) { 24, 24, 24, 32, 24, 22, 25, 24, 22, 27 }

Next, proceed to the drawing. First, we’ll set up the global style palette (we get the colors from the palette using plc option, and default palette is not always good enough):

root [4] gStyle->SetPalette(kSolar)

Next, we create the canvas we’re going to draw on (ROOT could create the default one too):

root [5] auto c = new TCanvas();

It will immediately show the white empty canvas window. Next, we create the graph (we use std::iota to create the range ticks for the X axis):

root [6] std::vector<Int_t> range(samples)
(std::vector<Int_t> &) { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }
root [7] std::iota(range.begin(), range.end(), 1)
root [8] auto gr = new TGraph(samples, &range[0], &values[0]);

Finally, let’s draw the graph (al options mean to draw axis and polyline for the graph, please see the full reference here):

root [9] gr->Draw("al plc")

You could see how the canvas has changed and has a graph now. Let’s save it to SVG:

root [10] c->Print("graph.svg")
Info in <TCanvas::Print>: SVG file graph.svg has been created

Next, let’s draw a boxplot. It’s a little bit tricky as ROOT consider it as a special shape of the histogram, so the code could be not so obvious. There are two parts of it (at least, in the current code):

  • Calculate the range of the values in the resulted boxplot;

  • Draw the boxplot itself.

The range is calculated as [Q1 - 1.5 * IQR, Q3 + 1.5 * IQR], but we’re going to use another histogram for that, not pure math solution:

root [11] auto total_min = *std::min_element(values.begin(), values.end())
(int) 22
root [12] auto total_max = *std::max_element(values.begin(), values.end())
(int) 32
root [13] auto h2 = new TH1I(nullptr, nullptr, total_max - total_min + 1, total_min, total_max + 1);
root [14] for (auto el: values) { h2->Fill(el); }
root [15] Double_t q[] = {0., 0., 0,}
(Double_t [3]) { 0.0000000, 0.0000000, 0.0000000 }
root [16] Double_t p[] = {0.25, 0.5, 0.75}
(Double_t [3]) { 0.25000000, 0.50000000, 0.75000000 }
root [17] h2->GetQuantiles(3, q, p)
(int) 3
root [18] auto iqr = q[2] - q[0]
(double) 1.4000000
root [19] auto range_min = q[0] - 1.5 * iqr
(double) 22.000000
root [20] auto range_max = q[2] + 1.5 * iqr
(double) 27.600000

Now, we’re ready to put the values into histogram:

root [21] auto hist = new TH2I(nullptr, nullptr, 1, 0, 1, range_max - range_min + 1, range_min, range_max + 1);
root [22] hist->SetBarWidth(0.1)
root [23] for (auto v: values) { hist->Fill(0., v); }

And draw the boxplot (candle means to draw a boxplot, please see the full reference here):

root [24] hist->Draw("candle plc")

You could see how the canvas has changed and has a boxplot now (the graph is deleted, however you could use same option to leave the old graphics in place). Let’s save it to SVG:

root [25] c->Print("boxplot.svg")
Info in <TCanvas::Print>: SVG file boxplot.svg has been created

And that’s all! Surely, it’s only basics, and you could find much more documentation on ROOT’s website. The last item I’d like to show you how to use ROOT in the C++ program. It won’t require much work though.

First, you need to add the ROOT’s headers:

#include "TApplication.h"
#include "TCanvas.h"
#include "TColor.h"
#include "TGraph.h"
#include "TH1.h"
#include "TH2.h"
#include "TMultiGraph.h"
#include "TROOT.h"
#include "TStyle.h"

Second, declare the entry point (start the application and run ROOT’s code):

int main(int argc, char** argv) {
  TApplication app("ROOT Application", &argc, argv);
  ping_stats();
  app.Run();
}

Finally, just compile it:

g++ ping_stats.C `root-config --cflags --libs`

I hope that would give you some information to start working with ROOT. It’s definitely worth looking into, and I hope it will help you in your research and data analysis work. Happy hacking!

Comments

Popular posts from this blog

Web application framework comparison by memory consumption

Trac Ticket Workflow

Python vs JS vs PHP for embedded systems