3 * @brief 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)
17 pthread_mutex_init(&(df->mutex), NULL);
22 * Initialise a DataFile from a filename; opens read/write FILE*
23 * @param df - DataFile to initialise
24 * @param filename - Name of file; overwritten if it exists
26 void Data_Open(DataFile * df, const char * filename)
28 assert(filename != NULL);
32 df->filename = strdup(filename);
34 // Set number of DataPoints
38 df->file = fopen(filename, "wb+");
41 Fatal("Error opening DataFile %s - %s", filename, strerror(errno));
47 * @param df - The DataFile to close
49 void Data_Close(DataFile * df)
53 //TODO: Write data to TSV?
66 * Save DataPoints to a DataFile
67 * @param df - The DataFile to save to
68 * @param buffer - Array of DataPoint(s) to save
69 * @param amount - Number of DataPoints in the buffer
71 void Data_Save(DataFile * df, DataPoint * buffer, int amount)
73 pthread_mutex_lock(&(df->mutex));
75 assert(buffer != NULL);
78 // Go to the end of the file
79 if (fseek(df->file, 0, SEEK_END) < 0)
81 Fatal("Error seeking to end of DataFile %s - %s", df->filename, strerror(errno));
84 // Attempt to write the DataPoints
85 int amount_written = fwrite(buffer, sizeof(DataPoint), amount, df->file);
87 // Check if the correct number of points were written
88 if (amount_written != amount)
90 Fatal("Wrote %d points instead of %d to DataFile %s - %s", amount_written, amount, df->filename, strerror(errno));
93 // Update number of DataPoints
94 df->num_points += amount_written;
96 pthread_mutex_unlock(&(df->mutex));
100 * Read DataPoints from a DataFile
101 * @param df - The DataFile to read from
102 * @param buffer - Array to fill with DataPoints
103 * @param index - Index to start reading at (inclusive)
104 * @param amount - Maximum number of DataPoints to read
105 * @returns - Actual number of points read (If smaller than amount, the end of the file was reached)
107 int Data_Read(DataFile * df, DataPoint * buffer, int index, int amount)
109 pthread_mutex_lock(&(df->mutex));
112 assert(buffer != NULL);
116 // If we would read past the end of the file, reduce the amount of points to read
118 if (index + amount > df->num_points)
120 Log(LOGDEBUG, "Requested %d points but will only read %d to get to EOF (%d)", amount, df->num_points - index, df->num_points);
121 amount = df->num_points - index;
125 // Go to position in file
126 if (fseek(df->file, index*sizeof(DataPoint), SEEK_SET))
128 Fatal("Error seeking to position %d in DataFile %s - %s", index, df->filename, strerror(errno));
131 // Attempt to read the DataPoints
132 int amount_read = fread(buffer, sizeof(DataPoint), amount, df->file);
134 // Check if correct number of points were read
135 if (amount_read != amount)
137 Log(LOGERR,"Read %d points instead of %d from DataFile %s - %s", amount_read, amount, df->filename, strerror(errno));
140 pthread_mutex_unlock(&(df->mutex));
145 * Print data points between two indexes using a given format
146 * @param df - DataFile to print
147 * @param start_index - Index to start at (inclusive)
148 * @param end_index - Index to end at (exclusive)
149 * @param format - The format to use
151 void Data_PrintByIndexes(DataFile * df, int start_index, int end_index, DataFormat format)
154 assert(start_index >= 0);
155 assert(end_index >= 0);
156 assert(end_index <= df->num_points || df->num_points == 0);
158 const char * fmt_string; // Format for each data point
159 char separator; // Character used to seperate successive data points
161 // Determine what format string and separator character to use
165 fmt_string = "[%f,%f]";
167 // For JSON we need an opening bracket
171 fmt_string = "%f\t%f";
176 DataPoint buffer[DATA_BUFSIZ] = {{0}}; // Buffer
177 int index = start_index;
179 // Repeat until all DataPoints are printed
180 while (index < end_index)
182 // Fill the buffer from the DataFile
183 int amount_read = Data_Read(df, buffer, index, DATA_BUFSIZ);
185 // Print all points in the buffer
186 for (int i = 0; i < amount_read && index < end_index; ++i)
188 // Print individual DataPoint
189 FCGI_PrintRaw(fmt_string, buffer[i].time_stamp, buffer[i].value);
191 // Last separator is not required
192 if (index+1 < end_index)
193 FCGI_PrintRaw("%c", separator);
195 // Advance the position in the DataFile
203 // For JSON we need a closing bracket
212 * Print data points between two time stamps using a given format.
213 * Prints nothing if the time stamp
214 * @param df - DataFile to print
215 * @param start_time - Time to start from (inclusive)
216 * @param end_time - Time to end at (exclusive)
217 * @param format - The format to use
219 void Data_PrintByTimes(DataFile * df, double start_time, double end_time, DataFormat format)
228 int start_index = 0, end_index = 0;
229 if (start_time < end_time)
231 start_index = Data_FindByTime(df, start_time, NULL);
232 end_index = Data_FindByTime(df, end_time, NULL);
235 Data_PrintByIndexes(df, start_index, end_index, format);
239 * Get the index of the DataPoint closest to a given time stamp
240 * @param df - DataFile to search
241 * @param time_stamp - The time stamp to search for
242 * @param closest - If not NULL, will be filled with the DataPoint chosen
243 * @returns index of DataPoint with the *closest* time stamp to that given
245 int Data_FindByTime(DataFile * df, double time_stamp, DataPoint * closest)
248 assert(time_stamp >= 0);
249 //assert(closest != NULL);
251 DataPoint tmp; // Current DataPoint in binary search
253 int lower = 0; // lower index in binary search
254 pthread_mutex_lock(&(df->mutex));
255 int upper = df->num_points - 1; // upper index in binary search
256 pthread_mutex_unlock(&(df->mutex));
257 int index = 0; // current index in binary search
259 // Commence binary search:
260 while (upper - lower > 1)
263 index = lower + ((upper - lower)/2);
266 if (Data_Read(df, &tmp, index, 1) != 1)
268 Fatal("Couldn't read DataFile %s at index %d", df->filename, index);
271 // Change search interval to either half appropriately
272 if (tmp.time_stamp > time_stamp)
276 else if (tmp.time_stamp < time_stamp)
282 // Store closest DataPoint
291 * Helper; handle FCGI response that requires data
292 * Should be called first.
293 * @param df - DataFile to access
294 * @param start - Info about start_time param
295 * @param end - Info about end_time param
296 * @param fmt - Info about format param
297 * @param current_time - Current time
299 void Data_Handler(DataFile * df, FCGIValue * start, FCGIValue * end, DataFormat format, double current_time)
301 double start_time = *(double*)(start->value);
302 double end_time = *(double*)(end->value);
306 FCGI_JSONKey("data");
309 // If a time was specified
310 if (FCGI_RECEIVED(start->flags) || FCGI_RECEIVED(end->flags))
312 // Wrap times relative to the current time
314 start_time += current_time;
316 end_time += current_time;
318 // Print points by time range
319 Data_PrintByTimes(df, start_time, end_time, format);
322 else // No time was specified; just return a recent set of points
324 pthread_mutex_lock(&(df->mutex));
325 int start_index = df->num_points-DATA_BUFSIZ;
326 int end_index = df->num_points-1;
327 pthread_mutex_unlock(&(df->mutex));
335 // Print points by indexes
336 Data_PrintByIndexes(df, start_index, end_index, format);
342 * Helper - Convert human readable format string to DataFormat
343 * @param fmt - FCGIValue to use
345 DataFormat Data_GetFormat(FCGIValue * fmt)
347 const char * fmt_str = *(const char**)(fmt->value);
348 // Check if format type was specified
349 if (FCGI_RECEIVED(fmt->flags))
351 if (strcmp(fmt_str, "json") == 0)
353 else if (strcmp(fmt_str, "tsv") == 0)
356 Log(LOGERR, "Unknown format type \"%s\"", fmt_str);