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).
The full source in available as https://gitlab.com/nuald/nuald-blogspot-com/-/blob/master/site/adoc/ping_stats.C
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 hitlimit
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
Post a Comment