Java multi-threading in EspressChart API

If you are using the ERES in a multi-user environment, multiple users can be exporting reports and charts at the same time, utilizing multiple CPU cores.

However, if you are using the EC, ER, EDAB or ERES API to export charts locally, the application typically runs in a single thread.

In the following code block, there’s an example of a single-threaded chart export using the EspressChart API.

// do not connect to the EspressManager server
QbChart.setEspressManagerUsed(false);

// our chart template is parameterized
// it uses a single integer as the argument
Object queryParams[] = new Object[1];

// set the argument to "1"
queryParams[0] = 1;

// create an instance of the QbChart object
QbChart chart = new QbChart((Applet)null, "chartTemplate.pac", queryParams);

// export the chart to a PNG file
chart.export(QbChart.PNG, "exportedChart.png");

If you run the previous code in a loop (even with different parameter values), it can export as many charts as you want, however, it will export one chart at a time using a single CPU thread. Most of the CPU will be idle.

CPU utilization in a single-threaded application

This will negatively impact the overall performance of your API implementation on a modern CPU with many threads. On the other hand, if you are exporting a single chart or just a few charts at the same time, the export should be finished within a couple of seconds maximum.

However, it is possible to utilize multiple CPU cores even via the EspressChart API without connecting to the EC, ER, EDAB or ERES server by utilizing Java multi threading. In this example, we are using EspressChart API but similar code can be used for speeding up EspressReport export too. To use this code with EspressReport, replace the QbChart Java class with QbReport. The rest of the code is the same for both EspressChart and Espress Report.

In this example, we want to export 10,000 charts. All charts will be using the same chart template (although it would be also possible for every chart to be different, with some code changes) but different data.

The chart template is parameterized. The chart takes one parameter – a customer ID (integer). The chart is loading its data from a MySQL 8 database.

This is how the chart template looks in Chart Designer.

We want to export 10,000 of these charts, each with a different customer ID in as little time as possible.

We’ll create a multi-threaded Java application using the EspressChart API.

We’ll create two Java classes:

BenchmarkRunner.java

The BenchmarkRunner.java class will be the executable class and it will control the multi-threaded application.

ChartExporter.java

The ChartExporter.java class will be doing the actual exporting.

project file structure
private List<Integer> paramList, paramListSynchronized;
 
/**
 * launches the multi-threaded benchmark
 *
 * @param args default argument, no arguments needed for this app
 */
public static void main(String[] args) {
new BenchmarkRunner();
}
 
public BenchmarkRunner() {
// set the number of application threads that will be used for export
            // set as many threads as your CPU can handle
            // using more threads than your CPU has is counter-productive
            // for instance for AMD 5950X, we'll use 32 threads
            int threadCount = 32;
 
            // fetch the parameter list
            // a reference of this list will be passed to all threads
            paramList = new ArrayList<>();
            for (int k = 1; k <= 50; k++) {
            	paramList.add(k);
            }
 
            paramListSynchronized = Collections.synchronizedList(paramList);
 
            // we're starting the export, save the current system time in milliseconds
            long starttime = System.currentTimeMillis();
 
            // create a new Fixed Thread Pool in the size of the threadCount
            ExecutorService executor = Executors.newFixedThreadPool(threadCount);
            List<Future<?>> futures = new ArrayList<>();
 
            // start all threads one by one
            for (int i = 1; i <= threadCount; i++) {
            	// create a new thread
                        // initialize the ChartBenchmarkTemplate class using it's only constructor
                        // pass the paramList as a reference
                        Future<?> f = executor
                                                        	.submit(new ChartExporter(i, "templates/StatementChart.pac", paramListSynchronized));
 
                         // add to the futures list so we have a list of all threads we launched
                         futures.add(f);
}
 
            // iterate over the threads
            for (Future<?> f : futures) {
            	try {
                        	// wait for the thread to finish
                                   f.get();
} catch (InterruptedException e) {
                        	// basic error handling, use something better in your project
                                   e.printStackTrace();
} catch (ExecutionException e) {
                        	// basic error handling, use something better in your project
                                   	e.printStackTrace();
}
}
 
// all threads have finished, print out the time difference (in milliseconds)
            // and exit the application
            System.out.println("--- All threads completed in " + (System.currentTimeMillis() - starttime) + " miliseconds");
            // we're done, exit the application
            System.exit(0);
}

  
 /**
  * initialize the chart export thread
  *
  * @param threadid 	the number of the thread. Informative. Only used in a
  *                 	println
  * @param templateName the name of the chart template that will be exported
  * @param params   	the list of all remaining parameters waiting to be
  *                     exported
  */
 public ChartExporter(int threadid, String templateName, List<Integer> params) {
 // do not use EspressManager for the export
 QbChart.setEspressManagerUsed(false);
 	this.threadid = threadid;
            this.template = templateName;
            this.params = params;
}
 
/**
 * Get a parameter out of the params list and export it
 */
public void export() {
// prepare a variable that will store the parameter
            Object queryParams[] = new Object[1];
 
 	// while the params list still contains parameters to be exported, take one
            // parameter and export it
            while (params.size() > 0) {
            	// it's important to handle the params list in a synchronized block to prevent
            // concurrent modification
            // synchronize by the params list only, all other commands can run in parallel
            synchronized (params) {
            // take and remove the last parameter from the params list and save it in this
            // thread
            	queryParams[0] = params.remove(params.size() - 1);
 	}
 
 	// just prints an informative message
            System.out.println("Thread nr. " + threadid + " exporting chart ID " + queryParams[0]);
 
 	// load the chart from the template, apply the parameter
            QbChart chart = new QbChart((Applet) null, template, queryParams);
 
            // export the chart
            chart.export(QbChart.PNG, "dist/StatementChartID_" + queryParams[0] + ".png");
} 
}

This application can use all of our CPU threads to speed-up the exporting.

Please note that this application makes sense only if you want to export many charts as quickly as possible. Export of a single chart will still be single-threaded.

all CPU threads are being utilized for chart exporting

You can download the full source code, including the chart template file here: https://www.quadbase.com/downloads/QuadbaseMultiThreadedExport.zip