From 259624013535f3c93315868efb5f6f611ba88470 Mon Sep 17 00:00:00 2001 From: Sam Moore Date: Fri, 16 Aug 2013 01:28:47 +0800 Subject: [PATCH] Progress on Framework for Server software We have: - Threaded stuff for sensors that stores data in a buffer and periodically dumps it to a binary file - The use of the buffer reduces the number of times the mutex must be acquired (mutex = expensive) - Will do some tests to see if the buffer improves performance - Dummy function for querying sensors - Sensor Handler function (QuerySensor) gets data from binary file, dumps to a buffer and then prints There is something not quite right; QuerySensor appears to get the most recent dump that the sensor thread made. eg: In the current version, dumps are made twice as often as queries, so you miss half the data. Should be able to fix this after more sleep... --- server/Makefile | 4 +- server/common.h | 20 ++++++++ server/log.h | 1 + server/main.c | 27 +++++++---- server/query.c | 82 ++++++++++++++++++++++++++++++++ server/query.h | 14 ++++++ server/sensor.c | 123 ++++++++++++++++++++++++++++++++++++++++++++++-- server/sensor.h | 47 +++++++++++++++++- 8 files changed, 303 insertions(+), 15 deletions(-) create mode 100644 server/common.h create mode 100644 server/query.c create mode 100644 server/query.h diff --git a/server/Makefile b/server/Makefile index 196dfa1..95cd03e 100644 --- a/server/Makefile +++ b/server/Makefile @@ -2,13 +2,13 @@ CXX = gcc FLAGS = -std=c99 -Wall -Werror -pedantic -g LIB = -lpthread -OBJ = log.o main.o +OBJ = log.o sensor.o test_request.o main.o RM = rm -f BIN = server $(BIN) : $(OBJ) - $(CXX) $(FLAGS) -o $(BIN) $(OBJ) + $(CXX) $(FLAGS) -o $(BIN) $(OBJ) $(LIB) %.o : %.c diff --git a/server/common.h b/server/common.h new file mode 100644 index 0000000..2a6184a --- /dev/null +++ b/server/common.h @@ -0,0 +1,20 @@ +/** + * @file common.h + * @purpose Common header includes + */ + +#ifndef _COMMON_H +#define _COMMON_H + +#define _POSIX_C_SOURCE 200809L +#define _BSD_SOURCE + +#include +#include +#include +#include +#include +#include +#include + +#endif //_COMMON_H diff --git a/server/log.h b/server/log.h index 6db52f6..005059c 100644 --- a/server/log.h +++ b/server/log.h @@ -10,6 +10,7 @@ #include #include #include +#include "common.h" //To get around a 'pedantic' C99 rule that you must have at least 1 variadic arg, combine fmt into that. #define Log(level, ...) LogEx(level, __func__, __VA_ARGS__) diff --git a/server/main.c b/server/main.c index 61ba76d..524f550 100644 --- a/server/main.c +++ b/server/main.c @@ -2,22 +2,21 @@ * @file main.c * @purpose main and its helper functions, signal handling and cleanup functions */ +#include "common.h" -#define _POSIX_C_SOURCE 200809L // For strsignal to work // --- Standard headers --- // -#include -#include #include // for signal handling -#include // string functions -#include // --- Custom headers --- // + #include "log.h" #include "options.h" +#include "sensor.h" // --- Variable definitions --- // Options g_options; // options passed to program through command line arguments +Sensor g_sensors[NUMSENSORS]; // sensors array // --- Function definitions --- // @@ -37,12 +36,14 @@ void ParseArguments(int argc, char ** argv) * Handle a signal * @param signal - The signal number */ +//TODO: Something that gets massively annoying with threads is that you can't predict which one gets the signal +// There are ways to deal with this, but I can't remember them void SignalHandler(int signal) { // At the moment just always exit. // Call `exit` so that Cleanup will be called to... clean up. - Log(LOGWARN, "Got signal %d (%s). Exiting.", sig, strsignal(sig)); - exit(sig); + Log(LOGWARN, "Got signal %d (%s). Exiting.", signal, strsignal(signal)); + exit(signal); } /** @@ -63,7 +64,17 @@ void Cleanup() */ int main(int argc, char ** argv) { - ParseArguments(argc, argv, &g_options); + ParseArguments(argc, argv); + + // start sensor threads + for (int i = 0; i < NUMSENSORS; ++i) + { + Sensor_Init(g_sensors+i, i); + pthread_create(&(g_sensors[i].thread), NULL, Sensor_Main, (void*)(g_sensors+i)); + } + + // run request thread in the main thread + Query_Request(NULL); //TODO: Replace with FastCGI code return 0; } diff --git a/server/query.c b/server/query.c new file mode 100644 index 0000000..7a9ab16 --- /dev/null +++ b/server/query.c @@ -0,0 +1,82 @@ +/** + * @file query.c + * @purpose Temporary file to run a test thread that will query a sensors thread + * Code will probably be combined with Jeremy's FastCGI API + */ + + + + + +#include "sensor.h" +#include "log.h" + +static DataPoint buffer[QUERY_BUFSIZ]; + +/** + * Query sensor with id + * @param id - The index of the sensor in g_sensors + */ +void QuerySensor(int id) //TODO: This code will form the SensorHandler FastCGI function (I think?) +{ + Sensor * s = g_sensors+id; + + int amount_read = 0; + //CRITICAL SECTION (Don't access file while sensor thread is writing to it!) + pthread_mutex_lock(&(s->mutex)); + + FILE * file = fopen(s->filename, "rb"); + if (file == NULL) + { + Log(LOGWARN, "Couldn't open file \"%s\" mode rb - %s", s->filename, strerror(errno)); + } + else + { + fseek(file, 0, SEEK_SET); + rewind(file); + amount_read = fread(&buffer, sizeof(DataPoint), QUERY_BUFSIZ, file); + s->read_offset += amount_read; + Log(LOGDEBUG, "Read %d data points; offset now at %d", amount_read, s->read_offset); + + fclose(file); + } + + pthread_mutex_unlock(&(s->mutex)); + //End critical section + + // So... we have a buffer + // I guess we'll want to JSON it or something? + // Just print it out for now + for (int i = 0; i < amount_read; ++i) + { + printf("%f\t%f\n", buffer[i].time, buffer[i].value); + } + + // Will want to handle case where there actually wasn't anything new to respond with + // (In case we have a sensor that is slower than the rate of jQuery requests) + if (amount_read == 0) + { + Log(LOGWARN, "No data points read from sensor%s file"); + printf("# No data\n"); + } +} + +/** + * Test function to simulate responding to HTTP requests + * @param args - IGNORED (void* required to pass function to pthread_create) + * @returns NULL (void* required to pass function to pthread_create) + */ +void * Query_Main(void * args) +{ + while (true) //TODO: Exit condition + { + + for (int i = 0; i < NUMSENSORS; ++i) + { + printf("# Sensor %d\n", i); + QuerySensor(i); + printf("\n"); + } + usleep(REQUEST_RATE); + } +} diff --git a/server/query.h b/server/query.h new file mode 100644 index 0000000..55a4991 --- /dev/null +++ b/server/query.h @@ -0,0 +1,14 @@ +/** + * @file query.h + * @purpose Header for query stuff + */ + +#ifndef _QUERY_H +#define _QUERY_H + +#define REQUEST_RATE 2000000 // rate to make requests +#define QUERY_BUFSIZ 10 // size of query buffer + +extern void * Query_Main(void * args); + +#endif //QUERY_H diff --git a/server/sensor.c b/server/sensor.c index d1d8b9f..f1ec5dc 100644 --- a/server/sensor.c +++ b/server/sensor.c @@ -4,14 +4,131 @@ * TODO: Finalise implementation */ + + #include "sensor.h" +#include "log.h" +#include + +/** + * Read a data value from a sensor; block until value is read + * @param sensor_id - The ID of the sensor + * @returns The current value of the sensor + */ +DataPoint GetData(int sensor_id) +{ + // switch based on the sensor_id at the moment for testing; + // might be able to just directly access ADC from sensor_id? + //TODO: Implement for real sensors + + DataPoint d; + //TODO: Deal with time stamps properly + static int count = 0; + d.time = count++; + switch (sensor_id) + { + case SENSOR_TEST0: + d.value = count; + break; + case SENSOR_TEST1: + d.value = (float)(rand() % 100) / 100; + break; + default: + Fatal("Unknown sensor id: %d", sensor_id); + break; + } + usleep(100000); // simulate delay in sensor polling + return d; +} + + +/** + * Destroy a sensor + * @param s - Sensor to destroy + */ +void Destroy(Sensor * s) +{ + //TODO: Surely we'll need to do something here? + // Maybe move the binary file into long term file storage? +} + + + +/** + * Initialise a sensor + * @param s - Sensor to initialise + */ +void Sensor_Init(Sensor * s, int id) +{ + s->write_index = 0; + s->read_offset = 0; + s->id = id; + + if (s->id >= pow(10, FILENAMESIZE)) + { + Fatal("Too many sensors! FILENAMESIZE is %d; increase it and recompile.", FILENAMESIZE); + } + sprintf(s->filename, "%d", s->id); + unlink(s->filename); //TODO: Move old files somewhere + + Log(LOGDEBUG, "Initialised sensor %d; binary file is \"%s\"", id, s->filename); +} + /** * Run the main sensor polling loop - * @param args - IGNORED (void* required to use the function with pthreads) + * @param arg - Cast to Sensor* - Sensor that the thread will handle * @returns NULL (void* required to use the function with pthreads) */ -void * Sensor_Main(void * args) +void * Sensor_Main(void * arg) { - + Sensor * s = (Sensor*)(arg); + + while (true) //TODO: Exit condition + { + // The sensor will write data to a buffer until it is full + // Then it will open a file and dump the buffer to the end of it. + // Rinse and repeat + + // The reason I've added the buffer is because locks are expensive + // But maybe it's better to just write data straight to the file + // I'd like to do some tests by changing SENSOR_DATABUFSIZ + + while (s->write_index < SENSOR_DATABUFSIZ) + { + s->buffer[s->write_index] = GetData(s->id); + s->write_index += 1; + } + + //Log(LOGDEBUG, "Filled buffer"); + + // CRITICAL SECTION (no threads should be able to read/write the file at the same time) + pthread_mutex_lock(&(s->mutex)); + + // Open binary file and dump buffer into it + FILE * file = fopen(s->filename, "wb"); + if (file == NULL) + { + Fatal("Couldn't open file \"%s\" mode wb - %s", s->filename, strerror(errno)); + } + fseek(file, 0, SEEK_END); + int amount_written = fwrite(s->buffer, sizeof(DataPoint), SENSOR_DATABUFSIZ, file); + if (amount_written != SENSOR_DATABUFSIZ) + { + Fatal("Wrote %d data points and expected to write %d to \"%s\" - %s", amount_written, SENSOR_DATABUFSIZ, strerror(errno)); + } + + Log(LOGDEBUG, "Wrote %d data points for sensor %d", amount_written, s->id); + + fclose(file); + + pthread_mutex_unlock(&(s->mutex)); + // End of critical section + + s->write_index = 0; // reset position in buffer + + } + return NULL; } + + diff --git a/server/sensor.h b/server/sensor.h index b9a5aad..e51ab0c 100644 --- a/server/sensor.h +++ b/server/sensor.h @@ -9,9 +9,52 @@ #ifndef _SENSOR_H #define _SENSOR_H +#include "common.h" + +/** Number of data points to keep in sensor buffers **/ +#define SENSOR_DATABUFSIZ 10 + +/** Number of sensors **/ +#define NUMSENSORS 1 + +#define FILENAMESIZE 10 + +/** Structure to represent data recorded by a sensor at an instant in time **/ +typedef struct +{ + /** Time at which data was taken **/ + float time; + /** Value of data **/ + float value; +} DataPoint; + +/** Structure to represent a sensor **/ +typedef struct +{ + /** ID number of the sensor **/ + enum {SENSOR_TEST0=0, SENSOR_TEST1=1} id; + /** Buffer to store data from the sensor **/ + DataPoint buffer[SENSOR_DATABUFSIZ]; + /** Index of last point written in the data buffer **/ + int write_index; + /** Offset position in binary file for query thread to read from**/ + int read_offset; + /** File to write data into when buffer is full **/ + char filename[FILENAMESIZE]; + /** Thread running the sensor **/ + pthread_t thread; + /** Mutex to protect access to stuff **/ + pthread_mutex_t mutex; + + +} Sensor; + +/** Array of Sensors **/ +extern Sensor g_sensors[]; + +extern void Sensor_Init(Sensor * s, int id); // Initialise sensor +extern void * Sensor_Main(void * args); // main loop for sensor thread; pass a Sensor* cast to void* -extern void * Sensor_Main(void * args); // main loop for sensor thread - #endif //_SENSOR_H -- 2.20.1