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_PER_THREAD 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 benchmarkResult_t {
long count;
double elapsedMillis;
unsigned long checkSum;
typedef struct evidence_node_t {
typedef struct shared_string_node_t {
const char* value;
size_t length;
typedef struct performanceState_t {
uint16_t numberOfThreads;
int evidenceCount;
int iterationsPerThread;
const char* dataFileLocation;
FILE* output;
FILE* resultsOutput;
double startUpMillis;
int availableProperties;
int maxEvidence;
typedef struct threadState_t {
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;
if (perfState->
evidenceFirst == NULL) {
perfState->evidenceFirst = node;
perfState->
evidenceLast = node;
}
else {
perfState->evidenceLast->next = node;
perfState->evidenceLast = node;
}
for (uint32_t i = 0; i < size; i++) {
if (prefix != NULL) {
evidence->items[i].field = getOrAddSharedString(
perfState,
}
}
else {
}
strcmp(
evidence->items[i].field, userAgent) == 0) {
sizeof(
char) * (pairs[i].
valueLength + 1));
ptr = strncpy(
(
char*)
evidence->items[i].originalValue,
pairs[i].valueLength);
if (ptr == NULL) {
}
}
else {
evidence->items[i].parsedValue = getOrAddSharedString(
perfState,
pairs[i].value);
if (
evidence->items[i].parsedValue == NULL) {
}
evidence->items[i].originalValue = NULL;
}
}
if (size > perfState->
maxEvidence) {
perfState->maxEvidence = size;
}
perfState->
evidenceCount++;
}
void runPerformanceThread(void* state) {
&thisState->
mainState->
manager,
thisState->mainState->maxEvidence,
thisState->mainState->maxEvidence);
thisState->mainState->maxEvidence);
TIMER_CREATE;
TIMER_START;
while(node != NULL) {
results,
j,
if (value != NULL) {
thisState->
result->
checkSum += value->
size;
}
}
}
thisState->result->
count++;
node = node->next;
}
TIMER_END;
thisState->result->
elapsedMillis = TIMER_ELAPSED;
}
}
for (int i = 0; i < state->numberOfThreads; i++) {
states[i].mainState = state;
states[i].result = &state->
resultList[i];
states[i].result->checkSum = 0;
states[i].result->count = 0;
states[i].result->elapsedMillis = 0;
}
int thread;
TIMER_CREATE;
TIMER_START;
for (thread = 0; thread < state->numberOfThreads; thread++) {
&states[thread]);
}
for (thread = 0; thread < state->numberOfThreads; thread++) {
}
}
else {
fprintf(state->
output,
"Example not build with multi threading support.\n");
runPerformanceThread(&states[0]);
}
TIMER_END;
return TIMER_ELAPSED;
}
double totalMillis = 0;
long totalChecks = 0;
long checksum = 0;
for (int i = 0; i < state->numberOfThreads; i++) {
fprintf(state->output,
"Thread: %ld detections, elapsed %.3f seconds, %.0lf Detections per second\n",
result->count,
result->elapsedMillis / 1000.0,
round(1000.0 * result->count / result->elapsedMillis));
totalMillis += result->elapsedMillis;
totalChecks += result->count;
checksum += result->checkSum;
}
double millisPerTest = ((double)totalMillis / (state->numberOfThreads * totalChecks));
fprintf(state->output,
"Overall: %ld detections, Average ms per detection: %f, Detections per second: %.0lf\n",
totalChecks,
millisPerTest,
round(1000.0 / millisPerTest));
fprintf(state->output,
"Overall: Concurrent threads: %d, Checksum: %lx\n",
state->numberOfThreads,
checksum);
fprintf(state->output,
"Overall: Startup ms %.0lf\n",
fprintf(state->output,
"Overall: 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, "Load from disk\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");
double executionTime = runTests(state);
fprintf(state->output,
"Finished - Execution time was %lf ms\n",
executionTime);
doReport(state);
}
while (node != NULL) {
for (uint32_t j = 0; j <
evidence->count; j++) {
if (
evidence->items[j].originalValue != NULL) {
}
}
node = next;
}
}
while (node != NULL) {
Free((
void*)node->value);
node = next;
}
}
void fiftyoneDegreesHashPerformance(
const char* dataFilePath,
const char* evidenceFilePath,
uint16_t numberOfThreads,
int iterationsPerThread,
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;
}
state.
iterationsPerThread = iterationsPerThread;
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.iterationsPerThread);
state.evidenceCount = 0;
state.maxEvidence = 0;
state.evidenceFirst = NULL;
state.evidenceLast = NULL;
state.sharedStringFirst = NULL;
state.sharedStringLast = NULL;
evidenceFilePath,
buffer,
sizeof(buffer),
pair,
MAX_EVIDENCE,
state.iterationsPerThread,
&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(*(performanceConfigs[i].config)),
performanceConfigs[i].allProperties ? "_All" : "");
}
executeBenchmark(&state, performanceConfigs[i]);
if (state.resultsOutput != NULL) {
fprintf(state.resultsOutput, "}");
}
}
}
if (state.resultsOutput != NULL) {
fprintf(state.resultsOutput, "}\n");
}
freeSharedStrings(&state);
freeEvidence(&state);
fprintf(output, "Finished Performance example\n");
}
void fiftyoneDegreesExampleCPerformanceRun(ExampleParameters* params) {
fiftyoneDegreesHashPerformance(
params->dataFilePath,
params->evidenceFilePath,
params->numberOfThreads,
params->iterationsPerThread,
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", THREAD_OPTION, THREAD_OPTION_SHORT);
OPTION_MESSAGE("Number of iterations per thread", 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 iterationsPerThread = DEFAULT_ITERATIONS_PER_THREAD;
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) {
iterationsPerThread = 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.iterationsPerThread = iterationsPerThread;
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