From: Sam Moore Date: Thu, 12 Sep 2013 15:50:44 +0000 (+0800) Subject: Merge branch 'refactor' X-Git-Url: https://git.ucc.asn.au/?a=commitdiff_plain;h=bbd64b3c32d79d247c7535693aafa20a3baba6b8;p=matches%2FMCTX3420.git Merge branch 'refactor' Conflicts: server/sensor.c server/sensor.h Also made the resulting code compile. BUT... There are memory errors in the FCGI code :S --- bbd64b3c32d79d247c7535693aafa20a3baba6b8 diff --cc server/common.h index c2edd3d,5b6be66..a690149 --- a/server/common.h +++ b/server/common.h @@@ -33,4 -32,4 +32,5 @@@ /**Takes the tv1-tv2 between two timevals and returns the result as a double*/ #define TIMEVAL_DIFF(tv1, tv2) ((tv1).tv_sec - (tv2).tv_sec + 1e-6 * ((tv1).tv_usec - (tv2).tv_usec)) ++ #endif //_COMMON_H diff --cc server/data.c index 0000000,254b7bc..57b51ff mode 000000,100644..100644 --- a/server/data.c +++ b/server/data.c @@@ -1,0 -1,297 +1,299 @@@ + /** + * @file data.c + * @purpose Implementation of data handling functions; saving, loading, displaying, selecting. + */ + + #include "data.h" + #include //TODO: Remove asserts + + /** + * One off initialisation of DataFile + * @param df - The DataFile + */ + void Data_Init(DataFile * df) + { + // Everything is NULL + df->filename = NULL; + df->read_file = NULL; + df->write_file = NULL; + } + + /** + * Initialise a DataFile from a filename; opens read/write FILE* + * @param df - DataFile to initialise + * @param filename - Name of file; overwritten if it exists + */ + void Data_Open(DataFile * df, const char * filename) + { + assert(filename != NULL); + assert(df != NULL); + + // Set the filename + df->filename = filename; + + // Set number of DataPoints + df->num_points = 0; ++ ++ // Set write FILE* ++ df->write_file = fopen(filename, "w"); ++ if (df->write_file == NULL) ++ { ++ Fatal("Error opening DataFile %s - %s", filename, strerror(errno)); ++ } + + // Set read FILE* + df->read_file = fopen(filename, "r"); + if (df->read_file == NULL) + { + Fatal("Error opening DataFile %s - %s", filename, strerror(errno)); + } + - // Set write FILE* - df->write_file = fopen(filename, "w"); - if (df->write_file == NULL) - { - Fatal("Error opening DataFile %s - %s", filename, strerror(errno)); - } ++ + } + + /** + * Close a DataFile + * @param df - The DataFile to close + */ + void Data_Close(DataFile * df) + { + assert(df != NULL); + + //TODO: Write data to TSV? + + // Clear the FILE*s + df->read_file = NULL; + df->write_file = NULL; + + // Clear the filename + df->filename = NULL; + } + + /** + * Save DataPoints to a DataFile + * @param df - The DataFile to save to + * @param buffer - Array of DataPoint(s) to save + * @param amount - Number of DataPoints in the buffer + */ + void Data_Save(DataFile * df, DataPoint * buffer, int amount) + { + assert(df != NULL); + assert(buffer != NULL); + assert(amount >= 0); + + // Go to the end of the file + if (fseek(df->write_file, 0, SEEK_END) < 0) + { + Fatal("Error seeking to end of DataFile %s - %s", df->filename, strerror(errno)); + } + + // Attempt to write the DataPoints + int amount_written = fwrite(buffer, sizeof(DataPoint), amount, df->write_file); + + // Check if the correct number of points were written + if (amount_written != amount) + { + Fatal("Wrote %d points instead of %d to DataFile %s - %s", amount_written, amount, df->filename, strerror(errno)); + } + + // Update number of DataPoints + pthread_mutex_lock(&(df->mutex)); + df->num_points += amount_written; + pthread_mutex_unlock(&(df->mutex)); + + } + + /** + * Read DataPoints from a DataFile + * @param df - The DataFile to read from + * @param buffer - Array to fill with DataPoints + * @param index - Index to start reading at (inclusive) + * @param amount - Maximum number of DataPoints to read + * @returns - Actual number of points read (If smaller than amount, the end of the file was reached) + */ + int Data_Read(DataFile * df, DataPoint * buffer, int index, int amount) + { + assert(df != NULL); + assert(buffer != NULL); + assert(index >= 0); + assert(amount > 0); + + // If we would read past the end of the file, reduce the amount of points to read + pthread_mutex_lock(&(df->mutex)); + if (index + amount >= df->num_points) + { + Log(LOGDEBUG, "Requested %d points but will only read %d to get to EOF", amount, df->num_points - index); + amount = df->num_points - index; + } + pthread_mutex_unlock(&(df->mutex)); + + // Go to position in file + if (fseek(df->read_file, index*sizeof(DataPoint), SEEK_SET)) + { + Fatal("Error seeking to position %d in DataFile %s - %s", index, df->filename, strerror(errno)); + } + + // Attempt to read the DataPoints + int amount_read = fread(buffer, sizeof(DataPoint), amount, df->read_file); + + // Check if correct number of points were read + if (amount_read != amount) + { + Fatal("Read %d points instead of %d from DataFile %s - %s", amount_read, amount, df->filename, strerror(errno)); + } + + return amount; + } + + /** + * Print data points between two indexes using a given format + * @param df - DataFile to print + * @param start_index - Index to start at (inclusive) + * @param end_index - Index to end at (inclusive) + * @param format - The format to use + */ -void Data_Print(DataFile * df, int start_index, int end_index, DataFormat format) ++void Data_PrintByIndexes(DataFile * df, int start_index, int end_index, DataFormat format) + { + assert(df != NULL); + assert(start_index >= 0); + assert(end_index <= df->num_points-1); + + const char * fmt_string; // Format for each data point + char seperator; // Character used to seperate successive data points + + // Determine what format string and seperator character to use + switch (format) + { + case JSON: + fmt_string = "[%f,%f]"; + seperator = ','; + // For JSON we need an opening bracket + FCGI_PrintRaw("["); + break; + case TSV: + fmt_string = "%f\t%f"; + seperator = '\n'; + break; + } + + DataPoint buffer[DATA_BUFSIZ]; // Buffer + + int index = start_index; + + // Repeat until all DataPoints are printed + while (index <= end_index) + { + // Fill the buffer from the DataFile + int amount_read = Data_Read(df, buffer, index, DATA_BUFSIZ); + + // Print all points in the buffer + for (int i = 0; i < amount_read; ++i) + { + // Print individual DataPoint + FCGI_PrintRaw(fmt_string, buffer[i].time_stamp, buffer[i].value); + + // Last seperator is not required + if (index+1 < end_index) + FCGI_PrintRaw("%c", seperator); + + // Advance the position in the DataFile + ++index; + } + } + + switch (format) + { + case JSON: + // For JSON we need a closing bracket + FCGI_PrintRaw("]"); + break; + default: + break; + } + } + + /** + * Print data points between two time stamps using a given format. + * Prints nothing if the time stamp + * @param df - DataFile to print + * @param start_time - Time to start from (inclusive) + * @param end_time - Time to end at (inclusive) + * @param format - The format to use + */ -void Data_PrintTimes(DataFile * df, double start_time, double end_time, DataFormat format) ++void Data_PrintByTimes(DataFile * df, double start_time, double end_time, DataFormat format) + { + assert(df != NULL); + assert(start_time > 0); + assert(end_time > 0); + assert(end_time > start_time); + + DataPoint closest; + + // Get starting index + int start_index = Data_FindByTime(df, start_time, &closest); + + // Start time is greater than most recent time stamp + if (start_index >= df->num_points-1 && closest.time_stamp < start_time) + { - Data_Print(df, 0, 0, format); // Will print "empty" dataset ++ Data_PrintByIndexes(df, 0, 0, format); // Will print "empty" dataset + return; + } + + // Get finishing index + int end_index = Data_FindByTime(df, end_time, &closest); + + // Print data between the indexes - Data_Print(df, start_index, end_index, format); ++ Data_PrintByIndexes(df, start_index, end_index, format); + } + + /** + * Get the index of the DataPoint closest to a given time stamp + * @param df - DataFile to search + * @param time_stamp - The time stamp to search for + * @param closest - If not NULL, will be filled with the DataPoint chosen + * @returns index of DataPoint with the *closest* time stamp to that given + */ + int Data_FindByTime(DataFile * df, double time_stamp, DataPoint * closest) + { + assert(df != NULL); + assert(time_stamp >= 0); + assert(closest != NULL); + + DataPoint tmp; // Current DataPoint in binary search + + int lower = 0; // lower index in binary search + pthread_mutex_lock(&(df->mutex)); + int upper = df->num_points - 1; // upper index in binary search + pthread_mutex_unlock(&(df->mutex)); + int index = 0; // current index in binary search + + // Commence binary search: + while (upper - lower > 1) + { + // Pick midpoint + index = lower + ((upper - lower)/2); + + // Look at DataPoint + if (Data_Read(df, &tmp, index, 1) != 1) + { + Fatal("Couldn't read DataFile %s at index %d", df->filename, index); + } + + // Change search interval to either half appropriately + if (tmp.time_stamp > time_stamp) + { + upper = index; + } + else if (tmp.time_stamp < time_stamp) + { + lower = index; + } + } + + // Store closest DataPoint + if (closest != NULL) + *closest = tmp; + + return index; + + } diff --cc server/fastcgi.c index 8f4ab32,4f47584..b58ba79 --- a/server/fastcgi.c +++ b/server/fastcgi.c @@@ -38,24 -37,17 +38,24 @@@ struct FCGIContext * @param params User specified paramters: [actuators, sensors] */ static void IdentifyHandler(FCGIContext *context, char *params) { - bool identSensors = false, identActuators = false; - const char *key, *value; + bool ident_sensors = false, ident_actuators = false; + //const char *key, *value; - struct timeval now; ++ int i; - while ((params = FCGI_KeyPair(params, &key, &value))) { + FCGIValue values[2] = {{"sensors", &ident_sensors, FCGI_BOOL_T}, + {"actuators", &ident_actuators, FCGI_BOOL_T}}; + + if (!FCGI_ParseRequest(context, params, values, 2)) + return; + + /*while ((params = FCGI_KeyPair(params, &key, &value))) { if (!strcmp(key, "sensors")) { - identSensors = !identSensors; + ident_sensors = !ident_sensors; } else if (!strcmp(key, "actuators")) { - identActuators = !identActuators; + ident_actuators = !ident_actuators; } - } + }*/ FCGI_BeginJSON(context, STATUS_OK); FCGI_JSONPair("description", "MCTX3420 Server API (2013)"); @@@ -291,11 -200,15 +291,12 @@@ void FCGI_BeginJSON(FCGIContext *contex printf("{\r\n"); printf("\t\"module\" : \"%s\"", context->current_module); FCGI_JSONLong("status", status_code); - - // Jeremy: Should we include a timestamp in the JSON; something like this? - double start_time = g_options.start_time.tv_sec + 1e-6*(g_options.start_time.tv_usec); + //Time and running statistics + struct timeval now; gettimeofday(&now, NULL); - double current_time = now.tv_sec + 1e-6*(now.tv_usec); - FCGI_JSONDouble("start_time", start_time); - FCGI_JSONDouble("current_time", current_time); - FCGI_JSONDouble("running_time", current_time - start_time); + FCGI_JSONDouble("start_time", TIMEVAL_TO_DOUBLE(g_options.start_time)); + FCGI_JSONDouble("current_time", TIMEVAL_TO_DOUBLE(now)); + FCGI_JSONDouble("running_time", TIMEVAL_DIFF(now, g_options.start_time)); } /** diff --cc server/run.sh index aafc5d1,aafc5d1..a172c45 --- a/server/run.sh +++ b/server/run.sh @@@ -1,6 -1,6 +1,6 @@@ #!/bin/bash # Use this to quickly test run the server in valgrind --#spawn-fcgi -p9005 -n ./valgrind.sh ++spawn-fcgi -p9005 -n ./valgrind.sh # Use this to run the server normally #./stream & --spawn-fcgi -p9005 -n ./server ++#spawn-fcgi -p9005 -n ./server diff --cc server/sensor.c index f2c73c5,2e08a30..8914817 --- a/server/sensor.c +++ b/server/sensor.c @@@ -369,218 -254,87 +254,90 @@@ void Sensor_EndResponse(FCGIContext * c */ void Sensor_Handler(FCGIContext *context, char * params) { - StatusCodes status = STATUS_OK; - - OutputType output_type = JSON; - - - - const char * key; const char * value; - - Sensor * sensor = NULL; - struct timeval now; gettimeofday(&now, NULL); - - double start_time = -1; - double end_time = -1; - double current_time = TIMEVAL_DIFF(now, g_options.start_time) - bool seek_time = false; - bool points_specified = false; - int query_size = SENSOR_QUERYBUFSIZ; - int start_index = -1; - int end_index = -1; - - /* //Possible use case? - FCGIValue values[5] = { - {"id", &id, FCGI_REQUIRED(FCGI_INT_T)}, - {"format", &format, FCGI_STRING_T}, - {"points", &points, FCGI_STRING_T}, - {"start_time", &start_time, FCGI_DOUBLE_T}, - {"end_time", &end_time, FCGI_DOUBLE_T} + double current_time = TIMEVAL_DIFF(now, g_options.start_time); + ++ int id = 0; + double start_time = 0; + double end_time = current_time; + char * fmt_str; + + // key/value pairs + FCGIValue values[] = { - {"id", &id, FCGI_REQUIRED(FCGI_INT_T)}, ++ {"id", &id, FCGI_REQUIRED(FCGI_LONG_T)}, + {"format", &fmt_str, FCGI_STRING_T}, + {"start_time", &start_time, FCGI_DOUBLE_T}, + {"end_time", &end_time, FCGI_DOUBLE_T}, }; - if (!FCGI_ParseRequest(context, params, values, 5)) - return;*/ - while ((params = FCGI_KeyPair(params, &key, &value)) != NULL) + // enum to avoid the use of magic numbers + typedef enum { + ID, + FORMAT, + START_TIME, + END_TIME, + } SensorParams; + + // Fill values appropriately + if (!FCGI_ParseRequest(context, params, values, sizeof(values))) { - Log(LOGDEBUG, "Got key=%s and value=%s", key, value); - if (strcmp(key, "id") == 0) - { - if (sensor != NULL) - { - Log(LOGERR, "Only one sensor id should be specified"); - status = STATUS_ERROR; - break; - } - if (*value == '\0') - { - Log(LOGERR, "No id specified."); - status = STATUS_ERROR; - break; - } - - sensor = Sensor_Identify(value); - if (sensor == NULL) - { - Log(LOGERR, "Invalid sensor id: %s", value); - status = STATUS_ERROR; - break; - } - } - else if (strcmp(key, "format") == 0) - { - if (strcmp(value, "json") == 0) - output_type = JSON; - else if (strcmp(value, "csv") == 0) - output_type = CSV; - else if (strcmp(value, "tsv") == 0) - output_type = TSV; - } - else if (strcmp(key, "points") == 0) - { - points_specified = true; - if (strcmp(value, "all") == 0) - { - query_size = sensor->points_written; - } - else - { - char * end; - query_size = strtol(value, &end, 10); - if (*end != '\0') - { - Log(LOGERR, "Require \"all\" or an integer value: %s = %s", key, value); - status = STATUS_ERROR; - break; - } - } - - } - else if (strcmp(key, "start_time") == 0) - { - seek_time = true; - char * end; - start_time = strtod(value, &end); - if (*end != '\0') - { - Log(LOGERR, "Require a double: %s = %s", key, value); - status = STATUS_ERROR; - break; - } - - // Treat negative values as being relative to the current time - if (start_time < 0) - { - start_time = current_time + start_time; - } - start_time = floor(start_time); - } - else if (strcmp(key, "end_time") == 0) - { - seek_time = true; - char * end; - end_time = strtod(value, &end); - if (*end != '\0') - { - Log(LOGERR, "Require a double: %s = %s", key, value); - status = STATUS_ERROR; - break; - } - - // Treat negative values as being relative to the current time - if (end_time < 0) - { - end_time = current_time + end_time; - } - end_time = ceil(end_time); - } - // For backward compatability: - else if (strcmp(key, "dump") == 0) - { - output_type = TSV; - query_size = sensor->points_written+1; - } - else - { - Log(LOGERR, "Unknown key \"%s\" (value = %s)", key, value); - status = STATUS_ERROR; - break; - } + // Error occured; FCGI_RejectJSON already called + return; } - if (status != STATUS_ERROR && sensor == NULL) + // Error checking on sensor id + if (id < 0 || id >= NUMSENSORS) { - Log(LOGERR, "No valid sensor id given"); - status = STATUS_ERROR; + Log(LOGERR, "Invalid id %d", id); - FCGI_RejectJSON(); // Whoops, I do still need this! ++ FCGI_RejectJSON(context, "Invalid id"); // Whoops, I do still need this! } ++ ++ DataFormat format = JSON; - if (status == STATUS_ERROR) + // Check if format type was specified + if (FCGI_RECEIVED(values[FORMAT].flags)) { - FCGI_RejectJSON(context, "Invalid input parameters"); - return; + if (strcmp(fmt_str, "json") == 0) + format = JSON; + else if (strcmp(fmt_str, "tsv") == 0) + format = TSV; + else + Log(LOGERR, "Unknown format type \"%s\"", fmt_str); } - - if (seek_time) + // Get Sensor - Sensor * s = g_sensors[id]; ++ Sensor * s = g_sensors+id; + + // Begin response + Sensor_BeginResponse(context, id, format); + + // If a time was specified + if (FCGI_RECEIVED(values[START_TIME].flags) || FCGI_RECEIVED(values[END_TIME].flags)) { - if (end_time < 0 && !points_specified) - end_index = sensor->points_written; - else - { - int count = 0; DataPoint d; - end_index = FindTime(sensor, end_time, &count, &d); - Log(LOGDEBUG, "FindTime - Looked for %f; found [%f,%f] after %d iterations; sensor %d, position %d", end_time, d.time_stamp, d.value, count, sensor->id, end_index); - } + // Wrap times relative to the current time if (start_time < 0) - start_time = 0; - else - { - int count = 0; DataPoint d; - start_index = FindTime(sensor, start_time, &count, &d); - Log(LOGDEBUG, "FindTime - Looked for %f; found [%f,%f] after %d iterations; sensor %d, position %d", start_time, d.time_stamp, d.value, count, sensor->id, start_index); - } + start_time += current_time; + if (end_time < 0) + end_time += current_time; + + // Print points by time range + Data_PrintByTimes(&(s->data_file), start_time, end_time, format); - if (points_specified) - end_index = start_index + query_size; - } - else - { - start_index = sensor->points_written - query_size; - - end_index = sensor->points_written; - } - - if (start_index < 0) - { - Log(LOGNOTE, "start_index = %d => Clamped to 0", start_index); - start_index = 0; } - if (end_index > sensor->points_written) + else // No time was specified; just return a recent set of points { - Log(LOGNOTE, "end_index = %d => Clamped to %d", end_index, sensor->points_written); - end_index = sensor->points_written; + pthread_mutex_lock(&(s->data_file.mutex)); + int start_index = s->data_file.num_points-DATA_BUFSIZ; + int end_index = s->data_file.num_points-1; + pthread_mutex_unlock(&(s->data_file.mutex)); + + // Print points by indexes + Data_PrintByIndexes(&(s->data_file), start_index, end_index, format); } - switch (output_type) - { - case JSON: - FCGI_BeginJSON(context, status); - FCGI_JSONLong("id", sensor->id); - FCGI_JSONKey("data"); - PrintData(sensor, start_index, end_index, output_type); - FCGI_EndJSON(); - break; - default: - FCGI_PrintRaw("Content-type: text/plain\r\n\r\n"); - PrintData(sensor, start_index, end_index, output_type); - //Force download with content-disposition - // Sam: This is cool, but I don't think we should do it - // - letting the user view it in the browser and then save with their own filename is more flexible - //"Content-disposition: attachment;filename=%d.csv\r\n\r\n", sensor->id); - break; - } + // Finish response + Sensor_EndResponse(context, id, format); } diff --cc server/sensor.h index 89dae33,a499b77..55b7d89 --- a/server/sensor.h +++ b/server/sensor.h @@@ -52,24 -38,16 +38,15 @@@ typedef struc { /** ID number of the sensor **/ SensorId id; - /** Buffer to store data from the sensor **/ - DataPoint buffer[SENSOR_DATABUFSIZ]; - /** Index of last point written in the data buffer **/ - int write_index; - /** Number of points read **/ - long points_read; - /** Number of points written to file **/ - long points_written; - /** Binary file to write data into when buffer is full **/ - FILE * file; - /** Number of data points stored in file **/ - long points_stored; - /** Thread running the sensor **/ + /** DataFile to store sensor values in **/ + DataFile data_file; + /** Indicates whether the Sensor should record data **/ + bool record_data; + /** Thread the Sensor is running in **/ pthread_t thread; - /** Mutex to protect access to stuff **/ - pthread_mutex_t mutex; + /** Most recently recorded data **/ + DataPoint newest_data; -- } Sensor;