Getting Started with C++ and InfluxDB

Navigate to:

This article was written by Pravin Kumar Sinha and was originally published by The New Stack. Scroll down for the author’s bio. 

While relational database management systems (RDBMS) are efficient with storing tables, columns, and primary keys in a spreadsheet architecture, they become inefficient when there’s a lot of data input received over a long period of time.

Databases designed specifically to store time series data are known as time series databases (TSDB).

For example, an RDBMS might look like this:

------------------------------------------------------------------
  | Id |   Name         | Age  | Phonenumber     |    email        |
  ------------------------------------------------------------------
  |  0 | Jonn           | 26   | xxxxxx          | [email protected]  |
  |  1 | Susan          | 44   | xxxxxx          | [email protected] |
  |  2 | Alice          | 19   | xxxxxx          | [email protected] |
  |  3 | Jennifer       | 32   | xxxxxx          | [email protected] |
  ---------------------------------------------------------------------

In a TSDB, data is categorized in measurements. A measurement is a container, roughly equivalent to a table in RDBMS, which contains tuples of time and field data. Each entry is a tuple of (time, field data) and is recognized as a point. A point can optionally include a string tag.

A TSDB might look more like this:

----------------------------------------------------
  |       Tag           |   Field     |   Timestamp  |
  |city   device name   | temperature |              |
  ------------------------------------------------------------------
  |HUSTON    deviceX    | 23          | 2021-11-20T12-31-18.865954 |
  |MIAMI     deviceX    | 26          | 2021-11-20T12-31-18.965954 |
  |MIAMI     deviceY    | 23          | 2021-11-20T12-31-19.965954 |
  |SEATTLE   deviceY    | 18          | 2021-11-20T12-31-19.965954 |
  |SEATTLE   deviceZ    | 19          | 2021-11-20T12-31-20.814952 |
  |ATLANTA   deviceA    | 20          | 2021-11-20T12-31-21.665954 |
  |DALLAS    deviceC    | 28          | 2021-11-20T12-31-22.485954 |
  ------------------------------------------------------------------

The sample TSDB shown above stores temperature (measurement) and value (field data) measured across various cities (tag) during a range of time (timestamp), measured through devices (tag) manufactured from more than one company.

But TSDBs can facilitate the continuous monitoring of a variety of data, like IoT sensors, market trading data, and stock exchange data. This article shows you how to set up, configure, connect, write, and query an InfluxDB database using a C++ library.

InfluxDB is an open source time series database built to handle high write and read/query timestamped data loads. In addition to providing a constant stream of data, like server metrics or application monitoring, InfluxDB also provides events generation, real-time analytics, automatic expiration and deletion of backed-up stored data, and easy ways to forecast and analyze time series data.

You’ll learn how to store real IoT sensor data in an InfluxDB database and then analyze the stored data as a block of time period samples. We’ll finish off by plotting the data in a graph.

Setting up the InfluxDB server

This tutorial is set up on Ubuntu 20.04 LTS, but you can find other installation support in InfluxDB’s documentation. You can follow along with the source code for this tutorial here.

sudo apt-get install influxdb
 sudo systemctl start influxdb
 sudo systemctl status influxdb

systemctl status influxdb should show the running status:

influxdb.service - InfluxDB is an open-source, distributed, time series database
 Loaded: loaded (/lib/systemd/system/influxdb.service; enabled; vendor preset: enabled)
 Active: active (running) since Sat 2021-11-20 14:24:56 IST; 39s ago
 Docs: man:influxd(1)
 Main PID: 23892 (influxd)
 Tasks: 13 (limit: 4505)
 Memory: 4.5M
 CGroup: /system.slice/influxdb.service
 ??23892 /usr/bin/influxd -config /etc/influxdb/influxdb.conf

The InfluxDB server version for this tutorial is 1.6.4. You can get more information about server configuration by running $influxd, and tweak your configuration with /etc/influxdb/influxdb.conf.

Setting up the influxdb-cxx C++ client library

This tutorial uses the influxdb-cxx client library, version 0.6.7, to connect, populate, and query the InfluxDB server. The InfluxDB server is listening at IP address 127.0.0.1 and at TCP port 8086.

Prerequisites

As mentioned earlier, this tutorial is set up on Ubuntu 20.04 LTS. The rest of the software dependency list is as follows:

  • C++ 17 compiler. You can install g++ 9.3.0 through sudo apt-get install g++.
  • CMake 3.12+ tool. You can install CMake 3.16.3 through sudo apt-get install cmake.
  • Curl library. You can install curl4-openssl through sudo apt-get install libcurl4-openssl-dev.
  • Boost 1.57+ library (optional). Install Boost 1.71 c through sudo apt-get install libboost1.71-all-dev.

Get the influxdb-cxx client library

Create a new directory and execute the following command:

$git clone https://github.com/offa/influxdb-cxx

Build the influxdb-cxx client library

$cd influxdb-cxx
 $mkdir build && cd build

When the testing suite is not required, you can set the following options to OFF while running cmake.

$cmake -D INFLUXCXX_TESTING:BOOL=OFF ..

Install the influxdb-cxx client library

$sudo make install

The client library builds libInfluxDB.so and installs at /usr/local/lib/libInfluxDB.so. Header files InfluxDBFactory.h, InfluxDB., Point.h, Transport.h are installed at /usr/local/include/.

Header files and library files are required while compiling and linking the source code respectively.

The C++ application uses libInfluxDB.so while linking in order to generate an executable binary.

-----------------
 |libInfluxDB.so |
 -----------------
 ^
 | <<link>>
 |
 --------------- <<compile>>
 | C++ program | ----------> InfluxDBFactory.h
 --------------- | InfluxDB.h
 | | Point.h
 |<<generates +--> Transport.h
 | executable>>
 ------------
 |Executable|
 ------------

The influxdb-cxx client library class design

The influxdb-cxx client is designed to interact with the InfluxDB server through class InfluxDB. All influxdb-cxx classes are defined under the global namespace influxdb.

--------------------
 | InfluxDBFactory |---o get() [protocol]://[username:password@]host:port[?db=database]
 -------------------- \ --------------
 | \<<connects>> ---->| TSDB |
 |<<returns>> v / |Time-Series |
 | ------------------- / | Database |
 | +---------->| InfluxDB server | -- --------------
 | / -------------------
 | +--+
 | |
 ------------
 | | ----o createDatabaseIfNotExists()
 | | ----o write()
 | InfluxDB | ----o query()
 | | --o batchOf()
 | | --o flushBatch()
 ------------
 |
 v
 <<Measurement>>
 ----------------
 | Point | ----o addField()
 |--------------|
 | +measurement | ----o setTimestamp()|
 ----------------
 |
 o
 addTag()

The InfluxDBFactory class provides the get() method, which accepts a URI string with information on IP address, port with optional protocol name, database name, database user, and password. The HTTP URI defaults to tcp. InfluxDBFactory returns the InfluxDB class, which is connected to the InfluxDB server.

The InfluxDB class then provides methods to create the database, write to the database, and query the database. The InfluxDB class writes a point (or list of points) to the database – the point class represents a measurement container.

Since a measurement container contains tag, field, and timestamp, the point class provides methods for adding tag, field, and timestamp to itself. Calling the write() method on the InfluxDB class with point as an argument, first serialize the point class data as per Line protocol rules.

Final line protocol data is then sent to the InfluxDB server. Batch mode is also supported by the InfluxDB class, where the batchOf() method specifies the batch size. The addPointToBatch() method takes the list of points to be added, and the flushBatch() method finally writes to the database.

Making a connection

The InfluxDBFactory.get() method is called with a URI string to create a connection with the InfluxDB server. Specify the URI as: [protocol]://[username:password@]host:port[?db=database]

For example, you can call the get() method as influxdb::InfluxDBFactory::Get("http://localhost:8086?db=temperature_db").

Here, HTTP (TCP) is the protocol, localhost (127.0.0.1) is the IP address, 8086 is the TCP port, and temperature_db is the database name. You can use HTTP (TCP), UDP, and Unix Socket as protocol. The IP address, protocol, username, password, port number, and database name can also be configured through the configuration file /etc/influxdb/influxdb.conf.

In this example, you can make a connection by specifying the database name. Create the database if it doesn’t exist.

auto db = influxdb::InfluxDBFactory::Get("http://localhost:8086?db=temperature_db");
db->createDatabaseIfNotExists();
#include <iostream>
#include <InfluxDBFactory.h>
int main(int argc,char *argv[]) {
 auto db = influxdb::InfluxDBFactory::Get("http://localhost:8086?db=temperature_db");
 db->createDatabaseIfNotExists();
 for (auto i: db->query("SHOW DATABASES")) std::cout<<i.getTags()<<std::endl;
 return 0;
}

Compiling, linking, and creating the executable

In a new directory, create a file called CMakeLists.txt.

$mkdir cxx-influxdb-example && cd cxx-influxdb-example

Add the following lines to CMakeLists.txt.

cmake_minimum_required(VERSION 3.12.0)
project(cxx-influxdb-example)
find_package(InfluxDB)
add_executable(example-influx-cxx main.cpp)
target_link_libraries(example-influx-cxx PRIVATE InfluxData::InfluxDB)

cxx-influxdb-example is the project name, whereas example-influx-cxx is the executable name.

Write your main.cpp code, including the influxdb-cxx client header files.

$mkdir build && cd build
 $cmake ..
 $make
 $./example-influx-cxx

Output:

name=_internal
name=temperature_db

Connecting with SSL/TLS

HTTP is disabled by default. You’ll need to purchase an SSL key and certificate. Modify configuration file

/etc/influxdb/influxdb.conf

and modify or add the following key value pairs:

https-enabled=true
https-certificate=/etc/ssl/<signed-certificate-file>.crt (or to /etc/ssl/<bundled-certificate-file>.pem)
https-private-key=/etc/ssl/<private-key-file>.key (or to /etc/ssl/<bundled-certificate-file>.pem

Next, run the following command:

$sudo chown influxdb:influxdb /etc/ssl/<CA-certificate-file>
$sudo chmod 644 /etc/ssl/<CA-certificate-file>
$sudo chmod 600 /etc/ssl/<private-key-file>
$sudo systemctl restart influxdb

You can specify https instead of http in the InfluxDBFactory.get() URI argument to get TLS(SSL) encryption.

influxdb::InfluxDBFactory::Get("https://localhost:8086?db=temperature_db");

Writing to the InfluxDB database

The InfluxDB database is organized into various measurements. As mentioned earlier, a measurement is like a container and is equivalent to tables in an RDBMS. Each measurement record is a point which is a tuple of (measurement, tag, field value, time).

Measurement - String
 Tag name,value - String
 Field name - String
 Filed value - String, Integer, Float and Boolean
 Timestamp - Unix nanosecond timestamps

You can write a point to the database with InfluxDB.write() method by specifying measurement name, tag name, value pair list (optional), field name, value pair list, and timestamp (optional). A missing timestamp would be added by the InfluxDB database server.

auto db = influxdb::InfluxDBFactory::Get("http://localhost:8086?db=temperature_db");
db->write(influxdb::Point{"temperature"}.addTag("city","DALLAS").addTag("device","companyX").addField("value",28));

Querying data

You can query InfluxDB by calling the InfluxDB.query() method and passing the SQL query string as an argument. Database records in InfluxDB are returned in JSON format, where each record is represented by point class. Point.getName(), Point.getTags(), Point.getFields() and Point.getTimestamp() retun measurement, tag, field and timestamp respectively.

#include <iostream>
#include <InfluxDBFactory.h>
int main(int argc,char *argv[]) {
 auto db = influxdb::InfluxDBFactory::Get("http://localhost:8086?db=temperature_db");
 for (auto i: db->query("select * from temperature")) {
 std::cout<<i.getName()<<":";
 std::cout<<i.getTags()<<":";
 std::cout<<i.getFields()<<":";
 std::cout<<std::to_string(std::chrono::duration_cast<std::chrono::nanoseconds>(i.getTimestamp().time_since_epoch()).count())<<std::endl;
 }
 return 0;
}

Output:

temperature:city=DALLAS,device=companyX:value=28.000000000000000000:1637659246724538477

You can set float type precision by setting influxdb::Point::floatsPrecision=4.

int main(int argc,char *argv[]) {
 auto db = influxdb::InfluxDBFactory::Get("http://localhost:8086?db=temperature_db");
 influxdb::Point::floatsPrecision=4;

Output:

temperature:city=DALLAS,device=companyX:value=28.0000:1637659246724538477

Writing to InfluxDB in a batch

You can write data in a batch after mentioning the batch size. Call the flushBatch() method to flush the data to the database.

#include <iostream>
#include <InfluxDBFactory.h>
int main(int argc,char *argv[]) {
 auto db = influxdb::InfluxDBFactory::Get("http://localhost:8086?db=temperature_db");
 influxdb::Point::floatsPrecision=4;
 db->batchOf(10);
 for (int i=0;i<10;i++)
 db->write(influxdb::Point{"temperature"}.addTag("city","SEATTLE").addTag("device","companyY").addField("value",28+i));
 db->flushBatch();
 return 0;
}

Debugging with the influxdb-client utility

You can also verify data available in InfluxDB through the influxdb-client utility available on Ubuntu 20.04 through $sudo apt-get install influxdb-client.

$ influx
Connected to http://localhost:8086 version 1.6.4
InfluxDB shell version: 1.6.4
> SHOW DATABASES
name: databases
name
----
_internal
temperature_db
> use temperature_db
Using database temperature_db
> SELECT * FROM temperature
name: temperature
time city device value
---- ---- ------ -----
1637661663965328258 SEATTLE companyY 28
1637661663965352861 SEATTLE companyY 29
1637661663965357288 SEATTLE companyY 30
 .....

The finished application

The complete program is as follows:

#include <iostream>
#include <InfluxDBFactory.h>
int main(int argc,char *argv[]) {
 auto db = influxdb::InfluxDBFactory::Get("http://localhost:8086?db=temperature_db");
 influxdb::Point::floatsPrecision=4;
 db->createDatabaseIfNotExists();
 for (auto i: db->query("SHOW DATABASES")) std::cout<<i.getTags()<<std::endl;
 db->write(influxdb::Point{"temperature"}.addTag("city","DALLAS").addTag("device","companyX").addField("value",28));
 db->batchOf(10);
 for (int i=0;i<10;i++)
 db->write(influxdb::Point{"temperature"}.addTag("city","SEATTLE").addTag("device","companyY").addField("value",28+i));
 db->flushBatch();
 for (auto i: db->query("select * from temperature")) {
 std::cout<<i.getName()<<":";
 std::cout<<i.getTags()<<":";
 std::cout<<i.getFields()<<":";
 std::cout<<std::to_string(std::chrono::duration_cast<std::chrono::nanoseconds>(i.getTimestamp().time_since_epoch()).count())<<std::endl;
 }
return 0;

Real-time data from the experimental IoT sensor

The IoT sensor data measuring power, temperature, humidity, light, CO2, and dust is available at IoTSenser. Each data sample is recorded against a timestamp:

"2015-08-01 00:00:28",0,32,40,0,973,27.8
"2015-08-01 00:00:58",0,32,40,0,973,27.09
"2015-08-01 00:01:28",0,32,40,0,973,34.5
"2015-08-01 00:01:58",0,32,40,0,973,28.43
"2015-08-01 00:02:28",0,32,40,0,973,27.58
"2015-08-01 00:02:59",0,32,40,0,971,29.35
"2015-08-01 00:03:29",0,32,40,0,971,26.46
...

In the example code, the database iotsensor_db is created with iotsensor as the measurement field.

The full code for inserting data in your database and plotting a graph is as follows. Also, note that the graph is drawn from the pbPlots utility available in the Cpp directory. The pbPlots.cpp, pbPlots.hpp, supportLib.cpp, and supportLib.hpp files are copied from the cxx-influxdb-example directory and added to the CMakeLists.txt file.

cmake_minimum_required(VERSION 3.12.0)
project(cxx-influxdb-example)
find_package(InfluxDB)
add_executable(example-influx-cxx supportLib.cpp pbPlots.cpp demomain.cpp)
target_link_libraries(example-influx-cxx PRIVATE InfluxData::InfluxDB)
#include <iostream>
#include <string>
#include <fstream>
#include <boost/algorithm/string.hpp>
#include <InfluxDBFactory.h>
#include "pbPlots.h"
#include "supportLib.h"
#include <chrono>
std::chrono::system_clock::time_point createDateTime(int year, int month, int day, int hour, int minute, int second) // these are UTC values
{
 tm timeinfo1 = tm();
 timeinfo1.tm_year = year - 1900;
 timeinfo1.tm_mon = month - 1;
 timeinfo1.tm_mday = day;
 timeinfo1.tm_hour = hour;
 timeinfo1.tm_min = minute;
 timeinfo1.tm_sec = second;
 tm timeinfo = timeinfo1;
 time_t tt = timegm(&timeinfo);
 return std::chrono::system_clock::from_time_t(tt);
}
int main(int argc,char *argv[]) {
 auto db = influxdb::InfluxDBFactory::Get("http://localhost:8086?db=iotsensor_db");
 influxdb::Point::floatsPrecision=4;
 db->createDatabaseIfNotExists();
 for (auto i: db->query("SHOW DATABASES")) std::cout<<i.getTags()<<std::endl;
 std::ifstream file("sensor-data.csv");
 if (!file) {
 std::cout<<"Unable to open file"<<std::endl;
 return -1;
 }
 std::string dataline;
 std::vector<std::string> sensordatavector, linevector, timestampvector;
 while(getline(file,dataline)) {
 dataline.erase(remove( dataline.begin(), dataline.end(), '\"' ),dataline.end());
 sensordatavector.push_back(dataline);
 }
 std::cout<<"number of entries in sensor data "<<sensordatavector.size()<<std::endl;
 auto t_start = std::chrono::high_resolution_clock::now();
 db->batchOf(10000);
 int counter=0;
 for (std::string i : sensordatavector) {
 boost::algorithm::split(linevector, i, boost::is_any_of(","));
 boost::algorithm::split(timestampvector, linevector[0], boost::is_any_of(":- "));
 std::chrono::system_clock::time_point tss=createDateTime(std::stod(timestampvector[0]),std::stod(timestampvector[1]),std::stod(timestampvector[2]),std::stod(timestampvector[3]),std::stod(timestampvector[4]),std::stod(timestampvector[5]));
 db->write(influxdb::Point{"iotsensor"}
 .addField("power",std::stod(linevector[1]))
 .addField("temperature",std::stod(linevector[2]))
 .addField("humidity",std::stod(linevector[3]))
 .addField("light",std::stod(linevector[4]))
 .addField("co2",std::stod(linevector[5]))
 .addField("dust",std::stod(linevector[6]))
 .setTimestamp(tss));
 counter+=1;
 if (counter==10000) {
 db->flushBatch();
 counter=0;
 }
 }
 auto t_end = std::chrono::high_resolution_clock::now();
 std::cout<<"Total time (ms) for write "<<std::chrono::duration<double, std::milli>(t_end-t_start).count()<<std::endl;
 std::cout<<"database record count "<<db->query("select * from iotsensor").size()<<std::endl;
 std::vector<double> doublevector;
 std::vector<double> fieldvector[6];
 std::vector<std::string> fieldnamevector;
 sensordatavector.resize(0);linevector.resize(0);
// for (auto i: db->query("select * from iotsensor")) {
 for (auto i: db->query("select * from iotsensor where time> '2015-08-04 00:00:00' and time<'2015-08-08 01:00:00'")) {
 doublevector.push_back(std::chrono::duration_cast<std::chrono::nanoseconds>(i.getTimestamp().time_since_epoch()).count());
 boost::algorithm::split(sensordatavector, i.getFields(), boost::is_any_of(","));
 for (int j=0;j<6;j++) {
 boost::algorithm::split(linevector, sensordatavector[j], boost::is_any_of("="));
 fieldnamevector.push_back(linevector[0]);
 fieldvector[j].push_back(std::stod(linevector[1]));
 }
 }
 RGBABitmapImageReference *imageref=CreateRGBABitmapImageReference();
 size_t length;
 double *pngdata;
 std::string filename;
 for (int j=0;j<6;j++) {
 DrawScatterPlot(imageref, 600, 400, &doublevector[0], doublevector.size(),&fieldvector[j][0],doublevector.size());
 pngdata=ConvertToPNG(&length,imageref->image);
 filename="plot_partial_"+fieldnamevector[j]+std::to_string(j)+".png";
 WriteToFile(pngdata,length,(char*)filename.c_str());
 }
return 0;
}

Output:

name=_internal
name=temperature_db
name=iotsensor_db
number of entries in sensor data 88688
Total time (ms) for write 5874.81
database record count 79992

It takes 5874.81 milliseconds to write 79992 points in a database where each point has six fields.

plot_co2

plot_dust

plot_humidity

plot_light

plot_power

plot_temperature

Records timestamped between “2015-08-04 00:00:00” and “2015-08-08 01:00:00”.

plot_co2-2

plot_dust-2

plot_humidity-2

plot_light-2

plot_power-2

plot_temperature-2

Conclusion

To recap, time series databases (TSDBs) are different from RDBMSes in the sense that a TSDB is highly specialized in backing up stored time series data. As a result, writing and querying the data happens at lightning speed.

For your own applications, check out InfluxDB, a database that provides metrics, events, and data analytics for time series data.