51Degrees Device Detection Java  4.2

Device detection services for 51Degrees Pipeline


This example shows how to change the performance profile when creating a 51Degrees device detection engine. It also includes a simple method of benchmarking the engine that can illustrate the performance differences. Note that benchmarking is a complex area and this is not a sophisticated solution. It is simply intended to demonstrate the approximate, relative performance of the pre-configured profiles.

This example is available in full on GitHub.

This example requires a local data file. Free data files can be acquired by pulling the submodules under this repository or from the device-detection-data GitHub repository.

/* *********************************************************************
* This Original Work is copyright of 51 Degrees Mobile Experts Limited.
* Copyright 2019 51 Degrees Mobile Experts Limited, 5 Charlotte Close,
* Caversham, Reading, Berkshire, United Kingdom RG4 7BY.
* This Original Work is licensed under the European Union Public Licence (EUPL)
* v.1.2 and is subject to its terms as set out below.
* If a copy of the EUPL was not distributed with this file, You can obtain
* one at https://opensource.org/licenses/EUPL-1.2.
* The 'Compatible Licences' set out in the Appendix to the EUPL (as may be
* amended by the European Commission) shall be deemed incompatible for
* the purposes of the Work and the provisions of the compatibility
* clause in Article 5 of the EUPL shall not apply.
* If using the Work as, or as part of, a network application, by
* including the attribution notice(s) required under Article 5 of the EUPL
* in the end user terms of the application under an appropriate heading,
* such notice(s) shall fulfill the requirements of that article.
* ********************************************************************* */
package fiftyone.devicedetection.examples.hash;
import fiftyone.devicedetection.DeviceDetectionPipelineBuilder;
import fiftyone.devicedetection.examples.ExampleBase;
import fiftyone.devicedetection.examples.ProgramBase;
import fiftyone.devicedetection.shared.DeviceData;
import fiftyone.pipeline.core.data.FlowData;
import fiftyone.pipeline.core.flowelements.Pipeline;
import fiftyone.pipeline.engines.Constants;
import fiftyone.pipeline.engines.data.AspectPropertyValue;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
public class Performance extends ProgramBase {
public static void main(String[] args) throws Exception {
String dataFile = args.length > 0 ? args[0] :
String uaFile = args.length > 1 ? args[1] :
getDefaultFilePath("20000 User Agents.csv").getAbsolutePath();
new Example(true).run(dataFile, uaFile, 10000);
System.out.println("Complete. Press enter to exit.");
public static class Example extends ExampleBase {
public Example(boolean printOutput) {
public void run(String dataFile, String uaFile, int count)
throws Exception {
println("Constructing pipeline with engine " +
"from file " + dataFile);
// Build and run a new on-premise Hash engine with the max
// performance profile which loads all the available device data
// into memory.
run(uaFile, count, new DeviceDetectionPipelineBuilder()
.useOnPremise(dataFile, false)
// Prefer low memory profile where all data streamed
// from disk on-demand. Experiment with other profiles.
private String uaFile;
private int count;
private Pipeline pipeline;
private AtomicInteger isMobileTrue;
private AtomicInteger isMobileFalse;
private AtomicInteger isMobileUnknown;
private final int maxDistinctUAs = 10000;
private final int threadCount = 4;
private void run(String uaFile, int count, Pipeline pipeline)
throws Exception {
this.uaFile = uaFile;
this.count = count;
this.pipeline = pipeline;
println("Processing " + count + " User-Agents from " + uaFile);
println("The " + count + " process calls will use a " +
"maximum of " + maxDistinctUAs + " distinct User-Agents");
long calibrationTime = runThreads(true);
long time = runThreads(false);
// Output the average time to process a single User-Agent.
double detectionsPerSecond = (double)(count * threadCount) /
(double)((time - calibrationTime) / (double)1000);
printf("Average %.2f detections per second using %d threads " +
"(%2f per thread)\n",
detectionsPerSecond / threadCount);
printf("%4f ms per User-Agent effective (%4f actual)\n",
1000 / detectionsPerSecond,
(1000 * threadCount) / detectionsPerSecond);
println("IsMobile = True : " + isMobileTrue.get());
println("IsMobile = False : " + isMobileFalse.get());
println("IsMobile = Unknown : " + isMobileUnknown.get());
private long runThreads(boolean calibration)
throws IOException, InterruptedException, ExecutionException {
isMobileTrue = new AtomicInteger(0);
isMobileFalse = new AtomicInteger(0);
isMobileUnknown = new AtomicInteger(0);
// Start multiple threads to process a set of User-Agents, making a
// note of the time at which processing was started.
List<Callable<Void>> callables = new ArrayList<>();
for (int i = 0; i < threadCount; i++) {
callables.add(new PerformanceCallable(
new ReportIterable(
getUserAgents(uaFile, count).iterator(),
maxDistinctUAs, 40 / threadCount),
ExecutorService service = Executors.newFixedThreadPool(threadCount);
long start = System.currentTimeMillis();
// Wait for all processing to finish, and make a note of the time
// elapsed since the processing was started.
List<Future<Void>> results = service.invokeAll(callables);
for (Future<Void> result : results) {
return System.currentTimeMillis() - start;
private static class PerformanceCallable implements Callable<Void> {
private final Iterable<String> userAgents;
private final Pipeline pipeline;
final AtomicInteger isMobileTrue;
final AtomicInteger isMobileFalse;
final AtomicInteger isMobileUnknown;
private final boolean calibration;
public PerformanceCallable(
Iterable<String> userAgents,
Pipeline pipeline,
AtomicInteger isMobileTrue,
AtomicInteger isMobileFalse,
AtomicInteger isMobileUnknown,
boolean calibration) {
this.userAgents = userAgents;
this.pipeline = pipeline;
this.isMobileTrue = isMobileTrue;
this.isMobileFalse = isMobileFalse;
this.isMobileUnknown = isMobileUnknown;
this.calibration = calibration;
public Void call() throws Exception {
// Create a new flow data to add evidence to and get
// device data back again.
Iterator<String> userAgentsIterator = userAgents.iterator();
while (userAgentsIterator.hasNext()) {
String userAgent = userAgentsIterator.next();
if (calibration) {
else {
// A try-with-resource block MUST be used for the
// FlowData instance. This ensures that native resources
// created by the device detection engine are freed.
try (FlowData data = pipeline.createFlowData()) {
// Add the User-Agent as evidence to the flow data.
data.addEvidence("header.User-Agent", userAgent)
// Get the device from the engine.
DeviceData device =
// Update the counters depending on the IsMobile
// result.
AspectPropertyValue<Boolean> isMobile =
if (isMobile.hasValue()) {
if (isMobile.getValue()) {
} else {
} else {
return null;