A device detection library that is used natively or by 51Degrees products

Hash/Performance.c

The example illustrates a "clock-time" benchmark for assessing detection speed.

The example illustrates a "clock-time" benchmark for assessing detection speed.Using a YAML formatted evidence file - "20000 Evidence Records.yml" - from the data repository on GitHub, which the example downloads when it is missing.

It's important to understand the trade-offs between performance, memory usage and accuracy, that the 51Degrees pipeline configuration makes available, and this example shows a range of different configurations to illustrate the difference in performance.

Requesting properties from a single component reduces detection time compared with requesting properties from multiple components. If you don't specify any properties to detect, then all properties are detected.

Please review performance options and hash dataset options for more information about adjusting performance.

This example is available in full on GitHub.

/* *********************************************************************
* This Original Work is copyright of 51 Degrees Mobile Experts Limited.
* Copyright 2026 51 Degrees Mobile Experts Limited, Davidson House,
* Forbury Square, Reading, Berkshire, United Kingdom RG1 3EU.
*
* 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.
* ********************************************************************* */
#include <stdio.h>
#include <time.h>
// Include ExmapleBase.h before others as it includes Windows 'crtdbg.h'
// which requires to be included before 'malloc.h'.
#include "ExampleBase.h"
// the default number of threads if one is not provided.
#define DEFAULT_NUMBER_OF_THREADS 2
// the default number of tests to execute.
#define DEFAULT_ITERATIONS 10000
// Optional CLI control. Set before workers start and read-only during a run.
static bool detectionOnly = false;
// Parameters used for allocating memory when reading evidence.
#define SIZE_OF_KEY 500
#define SIZE_OF_VALUE 1000
#define MAX_EVIDENCE 20
// The device detection data folder from the sub module with lite device data.
static const char* dataDir = "data/device-detection";
// In this example, by default, the 51degrees "Lite" file needs to be in the
// data/device-detection folder, and is downloaded there when it is missing,
// or you may specify another file as a command line parameter.
//
// Note that the Lite data file is only used for illustration, and has
// limited accuracy and capabilities.
// Find out about the Enterprise data file on our pricing page:
// /pricing?utm_source=code&utm_medium=example&utm_campaign=51degrees-cxx&utm_content=examples-c-hash-performance.c&utm_term=top
static const char* dataFileName = "51Degrees-LiteV4.1.hash";
// This file contains the 20,000 most commonly seen combinations of header
// values that are relevant to device detection. For example, User-Agent and
// UA-CH headers.
static const char* evidenceFileName = "20000 Evidence Records.yml";
// Returned by main. Set to 1 when the data file cannot be used.
static int exitCode = 0;
// The value of the evidence for User-Agent is not stored in the shared string
// structure as these are almost always unique.
static const char* userAgent = "user-agent";
typedef struct performanceConfig_t {
// Base configuration
ConfigHash *config;
// True if all properties should be initialized and fetched
bool allProperties;
performanceConfig performanceConfigs[] = {
{ &HashInMemoryConfig, false },
{ &HashInMemoryConfig, true },
//{ &HashBalancedConfig, false },
//{ &HashBalancedConfig, true },
//{ &HashLowMemoryConfig, false },
//{ &HashLowMemoryConfig, true }
};
typedef struct evidence_node_t evidenceNode;
typedef struct evidence_node_t {
EvidenceKeyValuePairArray* array; // evidence
evidenceNode* next; // null if the end of the list
typedef struct shared_string_node_t sharedStringNode;
typedef struct shared_string_node_t {
const char* value;
size_t length;
// Next node in the insertion-ordered list, used only to free every node.
sharedStringNode* next;
// Next node in the same hash bucket, used for fast lookup.
sharedStringNode* bucketNext;
typedef struct performanceState_t performanceState;
typedef struct threadState_t {
// The main state containing the dataset.
performanceState* mainState;
// Pointer to the first item of evidence available.
evidenceNode* evidenceFirst;
// Pointer to the last item of evidence available.
evidenceNode* evidenceLast;
// Used to ensure compiler optimiser doesn't optimise out the very
// method that the benchmark is testing.
unsigned long long checkSum;
// Number of device detection iterations.
unsigned long long iterations;
typedef struct performanceState_t {
// Number of concurrent threads to benchmark
uint16_t numberOfThreads;
// Pointer to the first shared string.
sharedStringNode* sharedStringFirst;
// Pointer to the last shared string.
sharedStringNode* sharedStringLast;
// Hash table over the shared strings for O(1) average lookup instead of a
// linear scan of the whole list (which is O(n) per lookup and makes loading
// a large evidence set slow). Bucket array of chain heads, and the number of
// buckets (a power of two so a mask can index it). NULL/0 when not
// initialised, in which case lookup falls back to the linear scan.
sharedStringNode** sharedStringBuckets;
size_t sharedStringBucketCount;
// Number of sets of evidence in the evidence array
int evidenceCount;
// Number of sets of evidence to process
int iterations;
// Location of the 51Degrees data file
const char* dataFileLocation;
// File pointer to write output to, usually stdout
FILE* output;
// File pointer to write results to, usually null
FILE* resultsOutput;
// Manager containing the dataset
ResourceManager manager;
// Running threads
// Time in millis to startup the device detection component.
double startUpMillis;
// Number of property values retrieved for each iteration.
int availableProperties;
// Max number of evidence key values pairs that will be processed.
int maxEvidence;
// The total time taken to run all the threads.
double elapsedMilliSeconds;
// Pointer to an array of size numberOfThreads containing the thread states.
threadState* threadStates;
// Index in threadStates that should be used when preparing evidence.
int threadIndex;
// FNV-1a hash of a NUL-terminated string, reduced to a bucket index. The
// bucket count is a power of two so the modulo is a cheap bitmask.
static size_t sharedStringBucket(
performanceState* perfState,
const char* s) {
uint64_t h = 1469598103934665603ULL; // FNV-1a offset basis
for (const unsigned char* p = (const unsigned char*)s; *p != '\0'; p++) {
h ^= (uint64_t)*p;
h *= 1099511628211ULL; // FNV-1a prime
}
return (size_t)h & (perfState->sharedStringBucketCount - 1);
}
static const char* getOrAddSharedString(
performanceState* perfState,
const char* target) {
// Look the string up. With the hash table this scans only the (short) chain
// in one bucket; without it (table not allocated) it falls back to scanning
// the whole insertion-ordered list as before.
int haveTable = perfState->sharedStringBuckets != NULL;
size_t bucket = 0;
if (haveTable) {
bucket = sharedStringBucket(perfState, target);
for (node = perfState->sharedStringBuckets[bucket];
node != NULL; node = node->bucketNext) {
if (strcmp(target, node->value) == 0) {
return node->value;
}
}
}
else {
for (node = perfState->sharedStringFirst;
node != NULL; node = node->next) {
if (strcmp(target, node->value) == 0) {
return node->value;
}
}
}
// Not present yet, so create a new node.
size_t length = strlen(target) + 1;
if (node == NULL) {
return NULL;
}
node->next = NULL;
node->bucketNext = NULL;
node->length = length;
node->value = (const char*)Malloc(sizeof(char) * length);
if (node->value == NULL) {
return NULL;
}
if (strncpy((char*)node->value, target, length) == NULL) {
return NULL;
}
// Add to the insertion-ordered list, which freeSharedStrings walks to free
// every node.
if (perfState->sharedStringFirst == NULL) {
perfState->sharedStringFirst = node;
perfState->sharedStringLast = node;
}
else {
perfState->sharedStringLast->next = node;
perfState->sharedStringLast = node;
}
// Add to the head of its hash bucket for fast future lookups.
if (haveTable) {
node->bucketNext = perfState->sharedStringBuckets[bucket];
perfState->sharedStringBuckets[bucket] = node;
}
// Return the string from the node.
return node->value;
}
static void storeEvidence(KeyValuePair* pairs, uint16_t size, void* state) {
char* ptr;
performanceState* perfState = (performanceState*)state;
// Get the thread state to store this evidence against.
threadState* threadState = perfState->threadStates + perfState->threadIndex;
// Allocate space for this node and the evidence.
node->next = NULL;
node->array = evidence;
// Add the evidence node to the linked list of evidence nodes.
if (threadState->evidenceFirst == NULL) {
threadState->evidenceFirst = node;
threadState->evidenceLast = node;
}
else {
threadState->evidenceLast->next = node;
threadState->evidenceLast = node;
}
// Get the target pair that was just allocated.
for (uint32_t i = 0; i < size; i++) {
// Set prefix for the key.
EvidencePrefixMap* prefix = EvidenceMapPrefix(pairs[i].key);
if (prefix != NULL) {
evidence->items[i].prefix = prefix->prefixEnum;
// Get a shared string for the field name without the prefix and
// use this. Reduces memory consumption as there are only a limited
// number of keys.
evidence->items[i].item.key = getOrAddSharedString(
perfState,
pairs[i].key + prefix->prefixLength);
if (evidence->items[i].item.key == NULL) {
}
evidence->items[i].item.keyLength =
strlen(evidence->items[i].item.key);
}
else {
evidence->items[i].item.key = NULL;
evidence->items[i].item.keyLength = 0;
}
// If the field is User-Agent or NULL then create new memory for the
// string value, otherwise use shared strings.
if (evidence->items[i].item.key == NULL ||
strcmp(evidence->items[i].item.key, userAgent) == 0) {
// Copy the value to new memory at the original value, and then set
// the parsed value to point to the original value. This memory
// will be freed after the test.
evidence->items[i].item.valueLength =
evidence->items[i].parsedLength =
strlen(pairs[i].value);
evidence->items[i].item.value = (const char*)Malloc(
sizeof(char) * (evidence->items[i].item.valueLength + 1));
ptr = strncpy(
(char*)evidence->items[i].item.value,
pairs[i].value,
evidence->items[i].item.valueLength);
if (ptr == NULL) {
}
*(ptr + evidence->items[i].item.valueLength) = '\0';
evidence->items[i].parsedValue = evidence->items[i].item.value;
}
else {
// Set the parsed value to the shared string value and set the
// original value to NULL to indicate there is no memory to be
// freed after the performance test.
evidence->items[i].parsedValue = getOrAddSharedString(
perfState,
pairs[i].value);
if (evidence->items[i].parsedValue == NULL) {
}
// Set the length of the parsed value.
evidence->items[i].parsedLength = strlen(
(char*)evidence->items[i].parsedValue);
evidence->items[i].item.value = NULL;
}
}
evidence->count = size;
// Set the maximum capacity needed in evidence if higher.
if (size > perfState->maxEvidence) {
perfState->maxEvidence = size;
}
// Increment the total evidence count.
perfState->evidenceCount++;
// Increment the thread state index.
perfState->threadIndex++;
if (perfState->threadIndex == perfState->numberOfThreads) {
perfState->threadIndex = 0;
}
}
void runPerformanceThread(void* state) {
String* value;
threadState *thisState = (threadState*)state;
// Create an instance of results to access the returned values.
&thisState->mainState->manager,
0);
// Reference to the dataset.
DataSetHash* dataSet = (DataSetHash*)results->b.b.dataSet;
// ResultsHashFromEvidence caches a pair->header pointer into the current
// dataset's uniqueHeaders. If we walked node->array directly, that cache
// would persist on the shared node and outlive the dataset — the next
// executeBenchmark iteration reloads the resource manager and frees the
// prior uniqueHeaders, turning the cached pointer into a dangling
// reference (heap-use-after-free). A per-thread items buffer keeps the
// cache scoped to this run, and zeroing header forces re-resolution
// against the current dataset.
thisState->mainState->maxEvidence);
EvidenceKeyValuePair* localItems = evidence->items;
evidenceNode* node = thisState->evidenceFirst;
while(node != NULL) {
*evidence = *node->array;
memcpy(localItems, evidence->items,
sizeof(EvidenceKeyValuePair) * evidence->count);
evidence->items = localItems;
for (uint32_t i = 0; i < evidence->count; i++) {
evidence->items[i].header = NULL;
}
ResultsHashFromEvidence(results, evidence, exception);
// Get the all properties from the results if this is part of the
// performance evaluation.
for (uint32_t j = 0; detectionOnly == false &&
j < PropertiesGetRequiredPropertyCount(dataSet->b.b.available);
j++) {
results,
j,
exception) != NULL && EXCEPTION_OKAY) {
value = (String*)results->values.items[0].data.ptr;
if (results->values.count > 0 && value != NULL) {
// Increase the checksum with the size of the string to
// provide a crude checksum.
thisState->checkSum += value->size;
}
}
}
// Walk any graph the reads did not, which is every graph when only
// detecting, so that the iterations count every walk.
ResultsEvaluateAll(&results->b.b, exception);
// Update the total iterations for the thread.
for (uint32_t i = 0; i < results->count; i++) {
thisState->iterations +=
(unsigned long long)results->items[i].iterations;
}
// Move to the next node.
node = node->next;
}
ResultsHashFree(results);
}
}
double runTests(performanceState *state) {
// Reset the checksums for all the thread states before running the tests.
for (int i = 0; i < state->numberOfThreads; i++) {
state->threadStates[i].checkSum = 0;
state->threadStates[i].iterations = 0;
}
int thread;
TIMER_CREATE;
TIMER_START;
// Create and start the threads.
for (thread = 0; thread < state->numberOfThreads; thread++) {
state->threads[thread],
(THREAD_ROUTINE)&runPerformanceThread,
&state->threadStates[thread]);
}
// Wait for them to finish.
for (thread = 0; thread < state->numberOfThreads; thread++) {
THREAD_JOIN(state->threads[thread]);
THREAD_CLOSE(state->threads[thread]);
}
}
else {
fprintf(state->output, "Example not build with multi threading support.\n");
runPerformanceThread(&state->threadStates[0]);
}
TIMER_END;
return TIMER_ELAPSED;
}
void doReport(performanceState *state) {
// Work out the checksum and iterations from all threads.
unsigned long long checksum = 0;
unsigned long long iterations = 0;
for (int i = 0; i < state->numberOfThreads; i++) {
checksum += state->threadStates[i].checkSum;
iterations += state->threadStates[i].iterations;
}
// output the results from the benchmark to the console
double millisPerTest = state->elapsedMilliSeconds / (double)state->evidenceCount;
fprintf(state->output,
"%d detections, Average ms per detection: %f, Detections per second: %.0lf\n",
state->evidenceCount,
millisPerTest,
round(1000.0 / millisPerTest));
fprintf(state->output,
"Concurrent threads: %d, Checksum: %llu, Iterations: %llu\n",
state->numberOfThreads,
checksum,
iterations);
fprintf(state->output,
"Startup ms %.0lf\n",
state->startUpMillis);
fprintf(state->output,
"Properties retrieved %d\n",
detectionOnly ? 0 : state->availableProperties);
fprintf(state->output, "Detection only: %s\n\n",
detectionOnly ? "true" : "false");
if (state->resultsOutput != NULL) {
fprintf(state->resultsOutput, " \"DetectionsPerSecond\": %.2f,\n", round(1000.0 / millisPerTest));
fprintf(state->resultsOutput, " \"StartupMs\": %.0lf,\n", state->startUpMillis);
fprintf(state->resultsOutput, " \"DetectionOnly\": %s\n",
detectionOnly ? "true" : "false");
}
}
void executeBenchmark(
// Make a local copy of the config as we're going to alter it a bit.
ConfigHash dataSetConfig = *config.config;
// Output the name of the stock configuration before changing parameters.
fprintf(state->output,
"Benchmarking with profile: %s AllProperties: %s\n",
fiftyoneDegreesExampleGetConfigName(dataSetConfig),
config.allProperties ? "True" : "False");
// Ensure that for performance tests the updating of the matched user-agent
// is disabled to reduce processing overhead.
dataSetConfig.b.updateMatchedUserAgent = false;
if (config.allProperties == false) {
properties.string = "IsMobile";
}
// Multi graph operation is being deprecated. There is only one graph.
dataSetConfig.usePerformanceGraph = false;
dataSetConfig.usePredictiveGraph = true;
dataSetConfig.strings.concurrency = state->numberOfThreads;
dataSetConfig.properties.concurrency = state->numberOfThreads;
dataSetConfig.values.concurrency = state->numberOfThreads;
dataSetConfig.profiles.concurrency = state->numberOfThreads;
dataSetConfig.nodes.concurrency = state->numberOfThreads;
dataSetConfig.profileOffsets.concurrency = state->numberOfThreads;
dataSetConfig.maps.concurrency = state->numberOfThreads;
dataSetConfig.components.concurrency = state->numberOfThreads;
state->threads = (FIFTYONE_DEGREES_THREAD*)
Malloc(sizeof(FIFTYONE_DEGREES_THREAD) * state->numberOfThreads);
fprintf(state->output, "Initialize device detection\n");
TIMER_CREATE;
TIMER_START;
&state->manager,
&dataSetConfig,
&properties,
state->dataFileLocation,
exception);
if (status != SUCCESS) {
const char* message = StatusGetMessage(status, state->dataFileLocation);
fprintf(state->output, "%s\n", message);
Free((void*)message);
Free(state->threads);
exitCode = 1;
return;
}
TIMER_END;
state->startUpMillis = TIMER_ELAPSED;
// Check data file
DataSetHash* dataset = DataSetHashGet(&state->manager);
state->availableProperties = PropertiesGetRequiredPropertyCount(
dataset->b.b.available);
fiftyoneDegreesExampleCheckDataFile(dataset);
// Normally the benchmark is run twice, the first pass warming the caches.
// The memory-only build holds the whole dataset in memory with no caches to
// warm, so that warmup pass just doubles the work for no measurable benefit;
// skip it there and only warm up the standard build, whose caches benefit.
fprintf(state->output, "Warming up\n");
runTests(state);
}
fprintf(state->output, "Running\n");
state->elapsedMilliSeconds = runTests(state);
fprintf(state->output,
"Finished - Execution time was %lf ms\n",
state->elapsedMilliSeconds);
ResourceManagerFree(&state->manager);
Free(state->threads);
doReport(state);
}
void freeEvidence(performanceState* state) {
for(int i = 0; i < state->numberOfThreads; i++) {
evidenceNode* node = state->threadStates[i].evidenceFirst;
while (node != NULL) {
for (uint32_t j = 0; j < evidence->count; j++) {
if (evidence->items[j].item.value != NULL) {
Free((void*)evidence->items[j].item.value);
}
}
evidenceNode* next = node->next;
Free(node);
node = next;
}
}
}
void freeSharedStrings(performanceState* state) {
sharedStringNode* node = state->sharedStringFirst;
while (node != NULL) {
Free((void*)node->value);
sharedStringNode* next = node->next;
Free(node);
node = next;
}
// Free the hash table itself (the nodes were freed via the list above).
if (state->sharedStringBuckets != NULL) {
Free((void*)state->sharedStringBuckets);
state->sharedStringBuckets = NULL;
state->sharedStringBucketCount = 0;
}
}
void fiftyoneDegreesHashPerformance(
const char* dataFilePath,
const char* evidenceFilePath,
uint16_t numberOfThreads,
int iterations,
FILE* output,
FILE* resultsOutput) {
// Check that the memory only configuration is being used.
fprintf(output, "Running Performance example - ");
fprintf(output, "optimised build\n");
}
else {
fprintf(output, "standard build\n");
printf("\033[0;33m");
fprintf(
output,
"Use FIFTYONE_DEGREES_MEMORY_ONLY directive for optimum " \
"performance\n");
printf("\033[0m");
}
state.dataFileLocation = dataFilePath;
state.output = output;
state.resultsOutput = resultsOutput;
state.evidenceCount = 0;
state.numberOfThreads = numberOfThreads;
}
else {
state.numberOfThreads = 1;
}
state.threadStates = (threadState*)
Malloc(sizeof(threadState) * numberOfThreads);
for(int i = 0; i < numberOfThreads; i++) {
state.threadStates[i].evidenceFirst = NULL;
state.threadStates[i].evidenceLast = NULL;
state.threadStates[i].mainState = &state;
}
state.iterations = iterations;
// Allocate working memory for iterating over the YAML evidence source.
char buffer[MAX_EVIDENCE * (SIZE_OF_KEY + SIZE_OF_VALUE)];
KeyValuePair pair[MAX_EVIDENCE];
char key[MAX_EVIDENCE][SIZE_OF_KEY];
char value[MAX_EVIDENCE][SIZE_OF_VALUE];
for (int i = 0; i < MAX_EVIDENCE; i++) {
pair[i].key = key[i];
pair[i].keyLength = SIZE_OF_KEY;
pair[i].value = value[i];
pair[i].valueLength = SIZE_OF_VALUE;
}
// Iterate over the YAML evidence source storing each entry in memory as
// evidence.
fprintf(
state.output,
"Reading '%i' evidence records into memory.\n",
state.iterations);
// Set the state to empty default values.
state.evidenceCount = 0;
state.maxEvidence = 0;
state.threadIndex = 0;
state.sharedStringFirst = NULL;
state.sharedStringLast = NULL;
// Allocate the shared-string hash table so evidence loading looks strings up
// in O(1) rather than scanning the whole list. A power-of-two bucket count
// lets the hash be reduced with a bitmask. If allocation fails it is left
// NULL and lookups fall back to a linear scan.
state.sharedStringBucketCount = (size_t)1 << 20; // ~1 million buckets
state.sharedStringBuckets = (sharedStringNode**)Malloc(
state.sharedStringBucketCount * sizeof(sharedStringNode*));
if (state.sharedStringBuckets != NULL) {
memset(state.sharedStringBuckets, 0,
state.sharedStringBucketCount * sizeof(sharedStringNode*));
}
else {
state.sharedStringBucketCount = 0;
}
evidenceFilePath,
buffer,
sizeof(buffer),
pair,
MAX_EVIDENCE,
state.iterations,
&state,
storeEvidence);
// Report the number of evidence records read.
fprintf(
state.output,
"Read '%i' evidence records into memory.\n",
state.evidenceCount);
if (state.resultsOutput != NULL) {
fprintf(state.resultsOutput, "{");
}
// Run the selected benchmarks using the evidence now in memory.
for (int i = 0;
i < (int)(sizeof(performanceConfigs) / sizeof(performanceConfig));
i++) {
// Set the special evidence processing to false to optimize
// evaluation for core device detection.
performanceConfig config = performanceConfigs[i];
config.config->b.processSpecialEvidence = false;
if (CollectionGetIsMemoryOnly() == false ||
config.config->b.b.allInMemory == true) {
if (state.resultsOutput != NULL) {
fprintf(state.resultsOutput, "%s\n\"%s%s\": {\n",
i > 0 ? "," : "",
fiftyoneDegreesExampleGetConfigName(*(config.config)),
config.allProperties ? "_All" : "");
}
executeBenchmark(&state, config);
if (state.resultsOutput != NULL) {
fprintf(state.resultsOutput, "}");
}
}
}
if (state.resultsOutput != NULL) {
fprintf(state.resultsOutput, "}\n");
}
// Free the memory used by the shared strings.
freeSharedStrings(&state);
// Free the memory used for the evidence.
freeEvidence(&state);
// Free the memory used for results and thread state.
Free(state.threadStates);
fprintf(output, "Finished Performance example\n");
}
void fiftyoneDegreesExampleCPerformanceRun(ExampleParameters* params) {
// Call the actual function.
fiftyoneDegreesHashPerformance(
params->dataFilePath,
params->evidenceFilePath,
params->numberOfThreads,
params->iterations,
params->output,
params->resultsOutput);
}
#ifndef TEST
#define DATA_OPTION "--data-file"
#define DATA_OPTION_SHORT "-d"
#define UA_OPTION "--user-agent-file"
#define UA_OPTION_SHORT "-u"
#define THREAD_OPTION "--threads"
#define THREAD_OPTION_SHORT "-t"
#define JSON_OPTION "--json-output"
#define JSON_OPTION_SHORT "-j"
#define ITERATIONS_OPTION "--iterations"
#define ITERATIONS_OPTION_SHORT "-i"
#define DETECTION_ONLY_OPTION "--detection-only"
#define HELP_OPTION "--help"
#define HELP_OPTION_SHORT "-h"
#define OPTION_PADDING(o) ((int)(30 - strlen(o)))
#define OPTION_MESSAGE(m, o, s) printf(" %s, %s%*s: %s\n", o, s, OPTION_PADDING(o), " ", m);
void printHelp() {
printf("Available options are:\n");
printf(" --detection-only: Skip property retrieval, keeping component selection unchanged\n");
OPTION_MESSAGE("Path to a 51Degrees Hash data file", DATA_OPTION, DATA_OPTION_SHORT);
OPTION_MESSAGE("Path to a User-Agents YAML file", UA_OPTION, UA_OPTION_SHORT);
OPTION_MESSAGE("Number of threads to run in parallel", THREAD_OPTION, THREAD_OPTION_SHORT);
OPTION_MESSAGE("Number of iterations", ITERATIONS_OPTION, ITERATIONS_OPTION_SHORT);
OPTION_MESSAGE("Path to a file to output JSON format results to", JSON_OPTION, JSON_OPTION_SHORT);
OPTION_MESSAGE("Print this help", HELP_OPTION, HELP_OPTION_SHORT);
}
int main(int argc, char* argv[]) {
StatusCode status = SUCCESS;
char dataFilePath[FILE_MAX_PATH];
char evidenceFilePath[FILE_MAX_PATH];
uint16_t numberOfThreads = DEFAULT_NUMBER_OF_THREADS;
int iterations = DEFAULT_ITERATIONS;
char *outFile = NULL;
dataFilePath[0] = '\0';
evidenceFilePath[0] = '\0';
for (int i = 0; i < argc; i++) {
if (strcmp(argv[i], DATA_OPTION) == 0 ||
strcmp(argv[i], DATA_OPTION_SHORT) == 0) {
// Set data file path
strcpy(dataFilePath, argv[i + 1]);
}
else if (strcmp(argv[i], UA_OPTION) == 0 ||
strcmp(argv[i], UA_OPTION_SHORT) == 0) {
// Set evidence file path
strcpy(evidenceFilePath, argv[i + 1]);
}
else if (strcmp(argv[i], THREAD_OPTION) == 0 ||
strcmp(argv[i], THREAD_OPTION_SHORT) == 0) {
// Set the number of threads
numberOfThreads = (uint16_t)atoi(argv[i + 1]);
}
else if (strcmp(argv[i], JSON_OPTION) == 0 ||
strcmp(argv[i], JSON_OPTION_SHORT) == 0) {
// Set the JSON results file
outFile = argv[i + 1];
}
else if (strcmp(argv[i], ITERATIONS_OPTION) == 0 ||
strcmp(argv[i], ITERATIONS_OPTION_SHORT) == 0) {
// Set the iterations per thread
iterations = atoi(argv[i + 1]);
}
else if (strcmp(argv[i], DETECTION_ONLY_OPTION) == 0) {
detectionOnly = true;
}
else if (strcmp(argv[i], HELP_OPTION) == 0 ||
strcmp(argv[i], HELP_OPTION_SHORT) == 0) {
// Print the help options
printHelp();
return 0;
}
else if (argv[i][0] == '-') {
// Something invalid was entered, so do not continue
printf(
"The option '%s' is not recognized. Use %s (%s) to list options.",
argv[i],
HELP_OPTION,
HELP_OPTION_SHORT);
return 1;
}
else {
// Do nothing, this is a value.
}
}
if (strlen(dataFilePath) == 0) {
status = fiftyoneDegreesExampleGetFilePath(
dataDir,
dataFileName,
dataFilePath,
sizeof(dataFilePath));
if (status != SUCCESS) {
printf(("Failed to find a device detection "
"data file. Make sure the data submodule is there, "
"by running `git submodule update --init data`, and "
"run this example again to download the file.\n"));
fgetc(stdin);
return 1;
}
}
if (strlen(evidenceFilePath) == 0) {
status = fiftyoneDegreesExampleGetFilePath(
dataDir,
evidenceFileName,
evidenceFilePath,
sizeof(evidenceFilePath));
if (status != SUCCESS) {
printf(("Failed to find a device detection "
"evidence file. Make sure the data submodule is there, "
"by running `git submodule update --init data`, and "
"run this example again to download the file.\n"));
fgetc(stdin);
return 1;
}
}
ExampleParameters params;
params.dataFilePath = dataFilePath;
params.evidenceFilePath = evidenceFilePath;
params.numberOfThreads = numberOfThreads;
params.iterations = iterations;
params.output = stdout;
if (outFile != NULL) {
params.resultsOutput = fopen(outFile, "w");
}
else {
params.resultsOutput = NULL;
}
// Run the example
fiftyoneDegreesExampleMemCheck(
&params,
fiftyoneDegreesExampleCPerformanceRun);
if (outFile != NULL) {
fclose(params.resultsOutput);
}
return exitCode;
}
#endif
@ FIFTYONE_DEGREES_EVIDENCE_IGNORE
The evidence is invalid and should be ignored.
Definition evidence.h:151
#define DataSetHashRelease
Synonym for fiftyoneDegreesDataSetHashRelease function.
Definition dd/hash/fiftyone.h:72
#define ResultsHashFromEvidence
Synonym for fiftyoneDegreesResultsHashFromEvidence function.
Definition dd/hash/fiftyone.h:70
#define ResultsHashGetValues
Synonym for fiftyoneDegreesResultsHashGetValues function.
Definition dd/hash/fiftyone.h:58
fiftyoneDegreesConfigHash ConfigHash
Synonym for fiftyoneDegreesConfigHash type.
Definition dd/hash/fiftyone.h:51
fiftyoneDegreesResultsHash ResultsHash
Synonym for fiftyoneDegreesResultsHash type.
Definition dd/hash/fiftyone.h:50
#define HashInMemoryConfig
Synonym for fiftyoneDegreesHashInMemoryConfig config.
Definition dd/hash/fiftyone.h:86
fiftyoneDegreesDataSetHash DataSetHash
Synonym for fiftyoneDegreesDataSetHash type.
Definition dd/hash/fiftyone.h:48
#define ResultsHashCreate(manager, overridesCapacity)
Two argument convenience form of fiftyoneDegreesResultsHashCreate that injects the deprecated userAge...
Definition dd/hash/fiftyone.h:66
#define ResultsHashFree
Synonym for fiftyoneDegreesResultsHashFree function.
Definition dd/hash/fiftyone.h:67
#define HashInitManagerFromFile
Synonym for fiftyoneDegreesHashInitManagerFromFile function.
Definition dd/hash/fiftyone.h:75
#define DataSetHashGet
Synonym for fiftyoneDegreesDataSetHashGet function.
Definition dd/hash/fiftyone.h:71
#define ThreadingGetIsThreadSafe
Synonym for fiftyoneDegreesThreadingGetIsThreadSafe function.
Definition common/fiftyone.h:339
#define EXCEPTION_THROW
Synonym for FIFTYONE_DEGREES_EXCEPTION_THROW macro.
Definition common/fiftyone.h:446
#define CollectionGetIsMemoryOnly
Synonym for fiftyoneDegreesCollectionGetIsMemoryOnly function.
Definition common/fiftyone.h:342
fiftyoneDegreesEvidenceKeyValuePairArray EvidenceKeyValuePairArray
Synonym for fiftyoneDegreesEvidenceKeyValuePairArray type.
Definition common/fiftyone.h:172
#define PropertiesDefault
Synonym for fiftyoneDegreesPropertiesDefault function.
Definition common/fiftyone.h:268
#define EXCEPTION_CREATE
Synonym for FIFTYONE_DEGREES_EXCEPTION_CREATE macro.
Definition common/fiftyone.h:443
fiftyoneDegreesEvidenceKeyValuePair EvidenceKeyValuePair
Synonym for fiftyoneDegreesEvidenceKeyValuePair type.
Definition common/fiftyone.h:138
#define StatusGetMessage
Synonym for fiftyoneDegreesStatusGetMessage function.
Definition common/fiftyone.h:293
#define EvidenceCreate
Synonym for fiftyoneDegreesEvidenceCreate function.
Definition common/fiftyone.h:240
#define YamlFileIterateWithLimit
Synonym for fiftyoneDegreesYamlFileIterateWithLimit.
Definition common/fiftyone.h:396
#define PropertiesGetRequiredPropertyCount
Synonym for fiftyoneDegreesPropertiesGetRequiredPropertyCount function.
Definition common/fiftyone.h:327
fiftyoneDegreesStatusCode StatusCode
Synonym for fiftyoneDegreesStatusCode type.
Definition common/fiftyone.h:134
#define ResultsEvaluateAll
Synonym for fiftyoneDegreesResultsEvaluateAll function.
Definition common/fiftyone.h:354
#define EXCEPTION_OKAY
Synonym for FIFTYONE_DEGREES_EXCEPTION_OKAY macro.
Definition common/fiftyone.h:444
#define THREAD_JOIN
Synonym for FIFTYONE_DEGREES_THREAD_JOIN macro.
Definition common/fiftyone.h:454
fiftyoneDegreesString String
Synonym for fiftyoneDegreesString type.
Definition common/fiftyone.h:158
fiftyoneDegreesKeyValuePair KeyValuePair
Synonym for fiftyoneDegreesKeyValuePair type.
Definition common/fiftyone.h:193
#define THREAD_CREATE
Synonym for FIFTYONE_DEGREES_THREAD_CREATE macro.
Definition common/fiftyone.h:451
fiftyoneDegreesEvidencePrefixMap EvidencePrefixMap
Synonym for fiftyoneDegreesEvidencePrefixMap type.
Definition common/fiftyone.h:139
#define SUCCESS
Synonym for FIFTYONE_DEGREES_STATUS_SUCCESS status code.
Definition common/fiftyone.h:478
#define EvidenceFree
Synonym for fiftyoneDegreesEvidenceFree function.
Definition common/fiftyone.h:241
#define Malloc
Synonym for fiftyoneDegreesMalloc function.
Definition common/fiftyone.h:263
#define THREAD_ROUTINE
Synonym for FIFTYONE_DEGREES_THREAD_ROUTINE macro.
Definition common/fiftyone.h:455
#define ResourceManagerFree
Synonym for fiftyoneDegreesResourceManagerFree function.
Definition common/fiftyone.h:310
fiftyoneDegreesResourceManager ResourceManager
Synonym for fiftyoneDegreesResourceManager type.
Definition common/fiftyone.h:137
#define THREAD_CLOSE
Synonym for FIFTYONE_DEGREES_THREAD_CLOSE macro.
Definition common/fiftyone.h:452
fiftyoneDegreesPropertiesRequired PropertiesRequired
Synonym for fiftyoneDegreesPropertiesRequired type.
Definition common/fiftyone.h:135
#define Free
Synonym for fiftyoneDegreesFree function.
Definition common/fiftyone.h:265
#define THREAD_EXIT
Synonym for FIFTYONE_DEGREES_THREAD_EXIT macro.
Definition common/fiftyone.h:453
#define EvidenceMapPrefix
Synonym for fiftyoneDegreesEvidenceMapPrefix function.
Definition common/fiftyone.h:321
#define FILE_MAX_PATH
Synonym for FIFTYONE_DEGREES_FILE_MAX_PATH macro.
Definition common/fiftyone.h:450
#define FIFTYONE_DEGREES_THREAD
A thread created with the FIFTYONE_DEGREES_THREAD_CREATE macro.
Definition threading.h:180
Definition GettingStarted.c:67
Definition Performance.c:129
uint16_t concurrency
Expected number of concurrent requests, 1 or greater.
Definition collection.h:299
bool allInMemory
True if the data file should be loaded entirely into continuous memory.
Definition config.h:54
fiftyoneDegreesConfigBase b
Base structure members.
Definition config-dd.h:53
bool processSpecialEvidence
Some evidence requires additional processing that doesn't need to be checked for if being used in an ...
Definition config-dd.h:73
bool updateMatchedUserAgent
True if the detection should record the matched characters from the target User-Agent.
Definition config-dd.h:54
fiftyoneDegreesCollectionConfig maps
Maps collection config.
Definition hash.h:192
bool usePerformanceGraph
True if the performance optimized graph should be used for processing.
Definition hash.h:209
bool usePredictiveGraph
True if the predictive optimized graph should be used for processing.
Definition hash.h:211
fiftyoneDegreesCollectionConfig properties
Properties collection config.
Definition hash.h:193
fiftyoneDegreesCollectionConfig nodes
Nodes collection config.
Definition hash.h:199
fiftyoneDegreesCollectionConfig values
Values collection config.
Definition hash.h:195
fiftyoneDegreesConfigDeviceDetection b
Base configuration.
Definition hash.h:188
fiftyoneDegreesCollectionConfig strings
Strings collection config.
Definition hash.h:189
fiftyoneDegreesCollectionConfig profiles
Profiles collection config.
Definition hash.h:196
fiftyoneDegreesCollectionConfig components
Components collection config.
Definition hash.h:190
fiftyoneDegreesCollectionConfig profileOffsets
Profile offsets collection config.
Definition hash.h:200
fiftyoneDegreesPropertiesAvailable * available
Available properties.
Definition dataset.h:129
fiftyoneDegreesDataSetBase b
Base structure members.
Definition dataset-dd.h:53
fiftyoneDegreesDataSetDeviceDetection b
Base data set.
Definition hash.h:271
fiftyoneDegreesEvidencePrefix prefixEnum
Enum value of prefix name.
Definition evidence.h:159
size_t prefixLength
Length of the prefix string.
Definition evidence.h:158
const char * string
Separated list of required properties or NULL if all properties are required.
Definition properties.h:234
Configuration to use when building the dataset for benchmarking.
Definition Performance.c:96
Single state containing everything needed for running and then reporting the performance tests.
Definition Performance.c:175
Definition Performance.c:140
State specific to a single thread.
Definition Performance.c:157