The example illustrates a "clock-time" benchmark for assessing detection speed.Using a YAML formatted evidence file - "20000 Evidence Records.yml" - supplied with the distribution or can be obtained from the data repository on Github.
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.
#include <stdio.h>
#include <time.h>
#include "ExampleBase.h"
#define DEFAULT_NUMBER_OF_THREADS 2
#define DEFAULT_ITERATIONS 10000
#define SIZE_OF_KEY 500
#define SIZE_OF_VALUE 1000
#define MAX_EVIDENCE 20
static const char* dataDir = "device-detection-data";
static const char* dataFileName = "51Degrees-LiteV4.1.hash";
static const char* evidenceFileName = "20000 Evidence Records.yml";
static const char* userAgent = "user-agent";
typedef struct performanceConfig_t {
bool allProperties;
};
typedef struct evidence_node_t {
typedef struct shared_string_node_t {
const char* value;
size_t length;
typedef struct threadState_t {
unsigned long long checkSum;
unsigned long long iterations;
typedef struct performanceState_t {
uint16_t numberOfThreads;
int evidenceCount;
int iterations;
const char* dataFileLocation;
FILE* output;
FILE* resultsOutput;
double startUpMillis;
int availableProperties;
int maxEvidence;
double elapsedMilliSeconds;
int threadIndex;
static const char* getOrAddSharedString(
const char* target) {
while (node != NULL) {
if (strcmp(target, node->
value) == 0) {
return node->value;
}
}
size_t length = strlen(target) + 1;
if (node == NULL) {
return NULL;
}
node->next = NULL;
node->value = (
const char*)
Malloc(
sizeof(
char) * length);
if (node->value == NULL) {
return NULL;
}
if (strncpy((char*)node->value, target, length) == NULL) {
return NULL;
}
if (perfState->sharedStringFirst == NULL) {
perfState->sharedStringFirst = node;
perfState->
sharedStringLast = node;
}
else {
perfState->sharedStringLast->next = node;
perfState->sharedStringLast = node;
}
return node->value;
}
static void storeEvidence(
KeyValuePair* pairs, uint16_t size,
void* state) {
char* ptr;
}
else {
}
for (uint32_t i = 0; i < size; i++) {
if (prefix != NULL) {
evidence->items[i].item.
key = getOrAddSharedString(
perfState,
if (
evidence->items[i].item.key == NULL) {
}
}
else {
}
if (
evidence->items[i].item.key == NULL ||
strcmp(
evidence->items[i].item.key, userAgent) == 0) {
strlen(pairs[i].value);
sizeof(
char) * (
evidence->items[i].item.valueLength + 1));
ptr = strncpy(
pairs[i].value,
if (ptr == NULL) {
}
*(ptr +
evidence->items[i].item.valueLength) =
'\0';
}
else {
evidence->items[i].parsedValue = getOrAddSharedString(
perfState,
pairs[i].value);
if (
evidence->items[i].parsedValue == NULL) {
}
evidence->items[i].parsedLength = strlen(
}
}
if (size > perfState->
maxEvidence) {
perfState->maxEvidence = size;
}
perfState->
evidenceCount++;
perfState->threadIndex++;
if (perfState->threadIndex == perfState->
numberOfThreads) {
perfState->threadIndex = 0;
}
}
void runPerformanceThread(void* state) {
&thisState->
mainState->
manager,
0);
thisState->mainState->maxEvidence);
for (uint32_t i = 0; i <
evidence->capacity; i++) {
}
while(node != NULL) {
for (uint32_t i = 0; i < results->
count; i++) {
}
results,
j,
thisState->
checkSum += value->
size;
}
}
}
node = node->next;
}
}
}
for (int i = 0; i < state->numberOfThreads; i++) {
state->threadStates[i].checkSum = 0;
state->threadStates[i].iterations = 0;
}
int thread;
TIMER_CREATE;
TIMER_START;
for (thread = 0; thread < state->numberOfThreads; thread++) {
&state->threadStates[thread]);
}
for (thread = 0; thread < state->numberOfThreads; thread++) {
}
}
else {
fprintf(state->
output,
"Example not build with multi threading support.\n");
runPerformanceThread(&state->threadStates[0]);
}
TIMER_END;
return TIMER_ELAPSED;
}
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;
}
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",
fprintf(state->output,
"Properties retrieved %d\n",
state->
availableProperties);
fprintf(state->output, "\n");
if (state->
resultsOutput != NULL) {
fprintf(state->resultsOutput, " \"DetectionsPerSecond\": %.2f,\n", round(1000.0 / millisPerTest));
fprintf(state->resultsOutput, " \"StartupMs\": %.0lf,\n", state->startUpMillis);
}
}
void executeBenchmark(
fprintf(state->output,
"Benchmarking with profile: %s AllProperties: %s\n",
fiftyoneDegreesExampleGetConfigName(dataSetConfig),
config.
allProperties ?
"True" :
"False");
if (config.allProperties == false) {
properties.
string =
"IsMobile";
}
fprintf(state->output, "Initialize device detection\n");
TIMER_CREATE;
TIMER_START;
&state->manager,
&dataSetConfig,
&properties,
exception);
fprintf(state->output, "%s\n", message);
return;
}
TIMER_END;
state->startUpMillis = TIMER_ELAPSED;
fiftyoneDegreesExampleCheckDataFile(dataset);
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);
doReport(state);
}
for(int i = 0; i < state->numberOfThreads; i++) {
while (node != NULL) {
for (uint32_t j = 0; j <
evidence->count; j++) {
if (
evidence->items[j].item.value != NULL) {
}
}
node = next;
}
}
}
while (node != NULL) {
Free((
void*)node->value);
node = next;
}
}
void fiftyoneDegreesHashPerformance(
const char* dataFilePath,
const char* evidenceFilePath,
uint16_t numberOfThreads,
int iterations,
FILE* output,
FILE* resultsOutput) {
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;
}
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;
char buffer[MAX_EVIDENCE * (SIZE_OF_KEY + SIZE_OF_VALUE)];
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;
}
fprintf(
state.output,
"Reading '%i' evidence records into memory.\n",
state.iterations);
state.evidenceCount = 0;
state.maxEvidence = 0;
state.threadIndex = 0;
state.sharedStringFirst = NULL;
state.sharedStringLast = NULL;
evidenceFilePath,
buffer,
sizeof(buffer),
pair,
MAX_EVIDENCE,
state.iterations,
&state,
storeEvidence);
fprintf(
state.output,
"Read '%i' evidence records into memory.\n",
state.evidenceCount);
if (state.resultsOutput != NULL) {
fprintf(state.resultsOutput, "{");
}
for (int i = 0;
i++) {
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");
}
freeSharedStrings(&state);
freeEvidence(&state);
Free(state.threadStates);
fprintf(output, "Finished Performance example\n");
}
void fiftyoneDegreesExampleCPerformanceRun(ExampleParameters* params) {
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 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");
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[]) {
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) {
strcpy(dataFilePath, argv[i + 1]);
}
else if (strcmp(argv[i], UA_OPTION) == 0 ||
strcmp(argv[i], UA_OPTION_SHORT) == 0) {
strcpy(evidenceFilePath, argv[i + 1]);
}
else if (strcmp(argv[i], THREAD_OPTION) == 0 ||
strcmp(argv[i], THREAD_OPTION_SHORT) == 0) {
numberOfThreads = (uint16_t)atoi(argv[i + 1]);
}
else if (strcmp(argv[i], JSON_OPTION) == 0 ||
strcmp(argv[i], JSON_OPTION_SHORT) == 0) {
outFile = argv[i + 1];
}
else if (strcmp(argv[i], ITERATIONS_OPTION) == 0 ||
strcmp(argv[i], ITERATIONS_OPTION_SHORT) == 0) {
iterations = atoi(argv[i + 1]);
}
else if (strcmp(argv[i], HELP_OPTION) == 0 ||
strcmp(argv[i], HELP_OPTION_SHORT) == 0) {
printHelp();
return 0;
}
else if (argv[i][0] == '-') {
printf(
"The option '%s' is not recognized. Use %s (%s) to list options.",
argv[i],
HELP_OPTION,
HELP_OPTION_SHORT);
return 1;
}
else {
}
}
if (strlen(dataFilePath) == 0) {
dataDir,
dataFileName,
dataFilePath,
sizeof(dataFilePath));
printf(("Failed to find a device detection "
"data file. Make sure the device-detection-data "
"submodule has been updated by running "
"`git submodule update --recursive`\n"));
fgetc(stdin);
return 1;
}
}
if (strlen(evidenceFilePath) == 0) {
dataDir,
evidenceFileName,
evidenceFilePath,
sizeof(evidenceFilePath));
printf(("Failed to find a device detection "
"evidence file. Make sure the device-detection-data "
"submodule has been updated by running "
"`git submodule update --recursive`\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;
}
fiftyoneDegreesExampleMemCheck(
¶ms,
fiftyoneDegreesExampleCPerformanceRun);
if (outFile != NULL) {
fclose(params.resultsOutput);
}
return 0;
}
#endif