3 * @purpose Implementation of data handling functions; saving, loading, displaying, selecting.
7 #include <assert.h> //TODO: Remove asserts
10 * One off initialisation of DataFile
11 * @param df - The DataFile
13 void Data_Init(DataFile * df)
18 df->write_file = NULL;
19 pthread_mutex_init(&(df->mutex), NULL);
23 * Initialise a DataFile from a filename; opens read/write FILE*
24 * @param df - DataFile to initialise
25 * @param filename - Name of file; overwritten if it exists
27 void Data_Open(DataFile * df, const char * filename)
29 assert(filename != NULL);
33 df->filename = strdup(filename);
35 // Set number of DataPoints
39 df->write_file = fopen(filename, "wb+");
40 if (df->write_file == NULL)
42 Fatal("Error opening DataFile %s - %s", filename, strerror(errno));
46 df->read_file = df->write_file;
48 //NOTE: Opening the same file in read mode gives funny results; fread generally reads less than expected
49 // The strerror is: "Transport endpoint is not connected"
51 df->read_file = fopen(filename, "rb");
52 if (df->read_file == NULL)
54 Fatal("Error opening DataFile %s - %s", filename, strerror(errno));
62 * @param df - The DataFile to close
64 void Data_Close(DataFile * df)
68 //TODO: Write data to TSV?
72 df->write_file = NULL;
74 fclose(df->write_file);
82 * Save DataPoints to a DataFile
83 * @param df - The DataFile to save to
84 * @param buffer - Array of DataPoint(s) to save
85 * @param amount - Number of DataPoints in the buffer
87 void Data_Save(DataFile * df, DataPoint * buffer, int amount)
89 pthread_mutex_unlock(&(df->mutex));
91 assert(buffer != NULL);
94 // Go to the end of the file
95 if (fseek(df->write_file, 0, SEEK_END) < 0)
97 Fatal("Error seeking to end of DataFile %s - %s", df->filename, strerror(errno));
100 // Attempt to write the DataPoints
101 int amount_written = fwrite(buffer, sizeof(DataPoint), amount, df->write_file);
103 // Check if the correct number of points were written
104 if (amount_written != amount)
106 Fatal("Wrote %d points instead of %d to DataFile %s - %s", amount_written, amount, df->filename, strerror(errno));
109 // Update number of DataPoints
110 df->num_points += amount_written;
112 pthread_mutex_unlock(&(df->mutex));
117 * Read DataPoints from a DataFile
118 * @param df - The DataFile to read from
119 * @param buffer - Array to fill with DataPoints
120 * @param index - Index to start reading at (inclusive)
121 * @param amount - Maximum number of DataPoints to read
122 * @returns - Actual number of points read (If smaller than amount, the end of the file was reached)
124 int Data_Read(DataFile * df, DataPoint * buffer, int index, int amount)
126 pthread_mutex_lock(&(df->mutex));
129 assert(buffer != NULL);
133 // If we would read past the end of the file, reduce the amount of points to read
135 if (index + amount > df->num_points)
137 Log(LOGDEBUG, "Requested %d points but will only read %d to get to EOF (%d)", amount, df->num_points - index, df->num_points);
138 amount = df->num_points - index;
142 // Go to position in file
143 if (fseek(df->read_file, index*sizeof(DataPoint), SEEK_SET))
145 Fatal("Error seeking to position %d in DataFile %s - %s", index, df->filename, strerror(errno));
148 // Attempt to read the DataPoints
149 int amount_read = fread(buffer, sizeof(DataPoint), amount, df->read_file);
151 // Check if correct number of points were read
152 if (amount_read != amount)
154 Log(LOGERR,"Read %d points instead of %d from DataFile %s - %s", amount_read, amount, df->filename, strerror(errno));
157 pthread_mutex_unlock(&(df->mutex));
162 * Print data points between two indexes using a given format
163 * @param df - DataFile to print
164 * @param start_index - Index to start at (inclusive)
165 * @param end_index - Index to end at (inclusive)
166 * @param format - The format to use
168 void Data_PrintByIndexes(DataFile * df, int start_index, int end_index, DataFormat format)
171 assert(start_index >= 0);
172 assert(end_index >= 0);
173 assert(end_index <= df->num_points-1 || df->num_points == 0);
175 const char * fmt_string; // Format for each data point
176 char seperator; // Character used to seperate successive data points
178 // Determine what format string and seperator character to use
182 fmt_string = "[%f,%f]";
184 // For JSON we need an opening bracket
188 fmt_string = "%f\t%f";
193 DataPoint buffer[DATA_BUFSIZ]; // Buffer
194 // initialise buffer to stop stuff complaining
195 memset(buffer, 0, sizeof(DataPoint)*DATA_BUFSIZ);
197 if (start_index < end_index)
200 int index = start_index;
201 // Repeat until all DataPoints are printed
202 while (index <= end_index)
204 // Fill the buffer from the DataFile
205 int amount_read = Data_Read(df, buffer, index, DATA_BUFSIZ);
207 // Print all points in the buffer
208 for (int i = 0; i < amount_read && index <= end_index; ++i)
210 // Print individual DataPoint
211 FCGI_PrintRaw(fmt_string, buffer[i].time_stamp, buffer[i].value);
213 // Last seperator is not required
214 if (index+1 <= end_index)
215 FCGI_PrintRaw("%c", seperator);
217 // Advance the position in the DataFile
226 // For JSON we need a closing bracket
235 * Print data points between two time stamps using a given format.
236 * Prints nothing if the time stamp
237 * @param df - DataFile to print
238 * @param start_time - Time to start from (inclusive)
239 * @param end_time - Time to end at (inclusive)
240 * @param format - The format to use
242 void Data_PrintByTimes(DataFile * df, double start_time, double end_time, DataFormat format)
245 assert(start_time >= 0);
246 assert(end_time >= 0);
247 assert(end_time >= start_time);
251 // Get starting index
252 int start_index = Data_FindByTime(df, start_time, &closest);
254 // Start time is greater than most recent time stamp
255 if (start_index >= df->num_points-1)
257 if (start_index == 0 || closest.time_stamp < start_time)
259 Data_PrintByIndexes(df, 0, 0, format); // Will print "empty" dataset
264 // Get finishing index
265 int end_index = Data_FindByTime(df, end_time, &closest);
267 // Print data between the indexes
268 Data_PrintByIndexes(df, start_index, end_index, format);
272 * Get the index of the DataPoint closest to a given time stamp
273 * @param df - DataFile to search
274 * @param time_stamp - The time stamp to search for
275 * @param closest - If not NULL, will be filled with the DataPoint chosen
276 * @returns index of DataPoint with the *closest* time stamp to that given
278 int Data_FindByTime(DataFile * df, double time_stamp, DataPoint * closest)
281 assert(time_stamp >= 0);
282 assert(closest != NULL);
284 DataPoint tmp; // Current DataPoint in binary search
286 int lower = 0; // lower index in binary search
287 pthread_mutex_lock(&(df->mutex));
288 int upper = df->num_points - 1; // upper index in binary search
289 pthread_mutex_unlock(&(df->mutex));
290 int index = 0; // current index in binary search
292 // Commence binary search:
293 while (upper - lower > 1)
296 index = lower + ((upper - lower)/2);
299 if (Data_Read(df, &tmp, index, 1) != 1)
301 Fatal("Couldn't read DataFile %s at index %d", df->filename, index);
304 // Change search interval to either half appropriately
305 if (tmp.time_stamp > time_stamp)
309 else if (tmp.time_stamp < time_stamp)
315 // Store closest DataPoint
324 * Helper; handle FCGI response that requires data
325 * Should be called first.
326 * @param df - DataFile to access
327 * @param start - Info about start_time param
328 * @param end - Info about end_time param
329 * @param fmt - Info about format param
330 * @param current_time - Current time
332 void Data_Handler(DataFile * df, FCGIValue * start, FCGIValue * end, DataFormat format, double current_time)
334 double start_time = *(double*)(start->value);
335 double end_time = *(double*)(end->value);
339 FCGI_JSONKey("data");
342 // If a time was specified
343 if (FCGI_RECEIVED(start->flags) || FCGI_RECEIVED(end->flags))
345 // Wrap times relative to the current time
347 start_time += current_time;
349 end_time += current_time;
351 // Print points by time range
352 Data_PrintByTimes(df, start_time, end_time, format);
355 else // No time was specified; just return a recent set of points
357 pthread_mutex_lock(&(df->mutex));
358 int start_index = df->num_points-DATA_BUFSIZ;
359 int end_index = df->num_points-1;
360 pthread_mutex_unlock(&(df->mutex));
368 // Print points by indexes
369 Data_PrintByIndexes(df, start_index, end_index, format);
375 * Helper - Convert human readable format string to DataFormat
376 * @param fmt - FCGIValue to use
378 DataFormat Data_GetFormat(FCGIValue * fmt)
380 char * fmt_str = *(char**)(fmt->value);
381 // Check if format type was specified
382 if (FCGI_RECEIVED(fmt->flags))
384 if (strcmp(fmt_str, "json") == 0)
386 else if (strcmp(fmt_str, "tsv") == 0)
389 Log(LOGERR, "Unknown format type \"%s\"", fmt_str);