Update Titlepage with links to individual sections
[matches/MCTX3420.git] / server / data.c
1 /**
2  * @file data.c
3  * @brief Implementation of data handling functions; saving, loading, displaying, selecting.
4  */
5
6 #include "data.h"
7 #include <assert.h> //TODO: Remove asserts
8
9 /**
10  * One off initialisation of DataFile
11  * @param df - The DataFile
12  */
13 void Data_Init(DataFile * df)
14 {
15         // Everything is NULL
16         memset(df, 0, sizeof(DataFile));
17         pthread_mutex_init(&(df->mutex), NULL);
18 }
19
20 /**
21  * Initialise a DataFile from a filename; opens read/write FILE*
22  * @param df - DataFile to initialise
23  * @param filename - Name of file; overwritten if it exists
24  */
25 void Data_Open(DataFile * df, const char * filename)
26 {
27         assert(filename != NULL);
28         assert(df != NULL);
29
30         // Set the filename
31         df->filename = strdup(filename);
32
33         // Set number of DataPoints
34         df->num_points = 0; 
35
36         // Set file pointer
37         df->file = fopen(filename, "wb+");
38         if (df->file == NULL)
39         {
40                 Fatal("Error opening DataFile %s - %s", filename, strerror(errno));
41         }
42 }
43
44 /**
45  * Close a DataFile
46  * @param df - The DataFile to close
47  */
48 void Data_Close(DataFile * df)
49 {
50         assert(df != NULL);
51
52         //TODO: Write data to TSV?
53
54         fclose(df->file);
55
56         // Clear the FILE*s
57         df->file = NULL;
58
59         // Clear the filename
60         free(df->filename);
61         df->filename = NULL;
62 }
63
64 /**
65  * Save DataPoints to a DataFile
66  * @param df - The DataFile to save to
67  * @param buffer - Array of DataPoint(s) to save
68  * @param amount - Number of DataPoints in the buffer
69  */
70 void Data_Save(DataFile * df, DataPoint * buffer, int amount)
71 {
72         pthread_mutex_lock(&(df->mutex));
73         assert(df != NULL);
74         assert(buffer != NULL);
75         assert(amount >= 0);
76
77         // Go to the end of the file
78         if (fseek(df->file, 0, SEEK_END) < 0)
79         {
80                 Fatal("Error seeking to end of DataFile %s - %s", df->filename, strerror(errno));
81         }
82
83         // Attempt to write the DataPoints
84         int amount_written = fwrite(buffer, sizeof(DataPoint), amount, df->file);
85         
86         // Check if the correct number of points were written
87         if (amount_written != amount)
88         {
89                 Fatal("Wrote %d points instead of %d to DataFile %s - %s", amount_written, amount, df->filename, strerror(errno));
90         }
91
92         // Update number of DataPoints
93         df->num_points += amount_written;
94
95         pthread_mutex_unlock(&(df->mutex));
96 }
97
98 /**
99  * Read DataPoints from a DataFile
100  * @param df - The DataFile to read from
101  * @param buffer - Array to fill with DataPoints
102  * @param index - Index to start reading at (inclusive)
103  * @param amount - Maximum number of DataPoints to read
104  * @returns - Actual number of points read (If smaller than amount, the end of the file was reached)
105  */
106 int Data_Read(DataFile * df, DataPoint * buffer, int index, int amount)
107 {
108         pthread_mutex_lock(&(df->mutex));
109
110         assert(df != NULL);
111         assert(buffer != NULL);
112         assert(index >= 0);
113         assert(amount > 0);
114         
115         // If we would read past the end of the file, reduce the amount of points to read
116         
117         if (index + amount > df->num_points)
118         {
119                 Log(LOGDEBUG, "Requested %d points but will only read %d to get to EOF (%d)", amount, df->num_points - index, df->num_points);
120                 amount = df->num_points - index;
121         }
122         
123
124         // Go to position in file
125         if (fseek(df->file, index*sizeof(DataPoint), SEEK_SET))
126         {
127                 Fatal("Error seeking to position %d in DataFile %s - %s", index, df->filename, strerror(errno));
128         }
129
130         // Attempt to read the DataPoints
131         int amount_read = fread(buffer, sizeof(DataPoint), amount, df->file);
132
133         // Check if correct number of points were read
134         if (amount_read != amount)
135         {
136                 Log(LOGERR,"Read %d points instead of %d from DataFile %s - %s", amount_read, amount, df->filename, strerror(errno));
137         }
138
139         pthread_mutex_unlock(&(df->mutex));
140         return amount_read;
141 }
142
143 /**
144  * Print data points between two indexes using a given format
145  * @param df - DataFile to print
146  * @param start_index - Index to start at (inclusive)
147  * @param end_index - Index to end at (exclusive)
148  * @param format - The format to use
149  */
150 void Data_PrintByIndexes(DataFile * df, int start_index, int end_index, DataFormat format)
151 {
152         assert(df != NULL);
153         assert(start_index >= 0);
154         assert(end_index >= 0);
155         assert(end_index <= df->num_points || df->num_points == 0);
156
157         const char * fmt_string; // Format for each data point
158         char separator; // Character used to seperate successive data points
159         
160         // Determine what format string and separator character to use
161         switch (format)
162         {
163                 case JSON:
164                         fmt_string = "[%.9f,%f]";
165                         separator = ',';
166                         // For JSON we need an opening bracket
167                         FCGI_PrintRaw("["); 
168                         break;
169                 case TSV:
170                         fmt_string = "%.9f\t%f";
171                         separator = '\n';
172                         break;
173         }
174
175         DataPoint buffer[DATA_BUFSIZ] = {{0}}; // Buffer
176         int index = start_index;
177
178         // Repeat until all DataPoints are printed
179         while (index < end_index)
180         {
181                 // Fill the buffer from the DataFile
182                 int amount_read = Data_Read(df, buffer, index, DATA_BUFSIZ);
183
184                 // Print all points in the buffer
185                 for (int i = 0; i < amount_read && index < end_index; ++i)
186                 {
187                         // Print individual DataPoint
188                         FCGI_PrintRaw(fmt_string, buffer[i].time_stamp, buffer[i].value);
189                         
190                         // Last separator is not required
191                         if (index+1 < end_index)
192                                 FCGI_PrintRaw("%c", separator);
193
194                         // Advance the position in the DataFile
195                         ++index;
196                 }
197         }
198         
199         switch (format)
200         {
201                 case JSON:
202                         // For JSON we need a closing bracket
203                         FCGI_PrintRaw("]"); 
204                         break;
205                 default:
206                         break;
207         }
208 }
209
210 /**
211  * Print data points between two time stamps using a given format.
212  * Prints nothing if the time stamp
213  * @param df - DataFile to print
214  * @param start_time - Time to start from (inclusive)
215  * @param end_time - Time to end at (exclusive)
216  * @param format - The format to use
217  */
218 void Data_PrintByTimes(DataFile * df, double start_time, double end_time, DataFormat format)
219 {
220         assert(df != NULL);
221         //Clamp boundaries
222         if (start_time < 0)
223                 start_time = 0;
224         if (end_time < 0)
225                 end_time = 0;
226
227         int start_index = 0, end_index = 0;
228         if (start_time < end_time)
229         {
230                 start_index = Data_FindByTime(df, start_time, NULL);
231                 end_index = Data_FindByTime(df, end_time, NULL);
232         }
233
234         Data_PrintByIndexes(df, start_index, end_index, format);
235 }
236
237 /**
238  * Get the index of the DataPoint closest to a given time stamp
239  * @param df - DataFile to search
240  * @param time_stamp - The time stamp to search for
241  * @param closest - If not NULL, will be filled with the DataPoint chosen
242  * @returns index of DataPoint with the *closest* time stamp to that given
243  */
244 int Data_FindByTime(DataFile * df, double time_stamp, DataPoint * closest)
245 {
246         assert(df != NULL);
247         assert(time_stamp >= 0);
248         //assert(closest != NULL);
249
250         DataPoint tmp; // Current DataPoint in binary search
251
252         int lower = 0; // lower index in binary search
253         pthread_mutex_lock(&(df->mutex));
254                 int upper = df->num_points - 1; // upper index in binary search
255         pthread_mutex_unlock(&(df->mutex));
256         int index = 0; // current index in binary search
257
258         // Commence binary search:
259         while (upper - lower > 1)
260         {
261                 // Pick midpoint
262                 index = lower + ((upper - lower)/2);
263
264                 // Look at DataPoint
265                 if (Data_Read(df, &tmp, index, 1) != 1)
266                 {
267                         Fatal("Couldn't read DataFile %s at index %d", df->filename, index);
268                 }
269
270                 // Change search interval to either half appropriately
271                 if (tmp.time_stamp > time_stamp)
272                 {
273                         upper = index;
274                 }
275                 else if (tmp.time_stamp < time_stamp)
276                 {
277                         lower = index;
278                 }
279         }
280
281         // Store closest DataPoint
282         if (closest != NULL)
283                 *closest = tmp;
284         
285         return index;
286         
287 }
288
289 /**
290  * Helper; handle FCGI response that requires data
291  * Should be called first.
292  * @param df - DataFile to access
293  * @param start - Info about start_time param 
294  * @param end - Info about end_time param
295  * @param fmt - Info about format param
296  * @param current_time - Current time
297  */
298 void Data_Handler(DataFile * df, FCGIValue * start, FCGIValue * end, DataFormat format, double current_time)
299 {
300         double start_time = *(double*)(start->value);
301         double end_time = *(double*)(end->value);
302
303         if (format == JSON)
304         {
305                 FCGI_JSONKey("data");
306         }
307
308         // If a time was specified
309         if (FCGI_RECEIVED(start->flags) || FCGI_RECEIVED(end->flags))
310         {
311                 // Wrap times relative to the current time
312                 if (start_time < 0)
313                         start_time += current_time;
314                 if (end_time < 0)
315                         end_time += current_time;
316
317                 // Print points by time range
318                 Data_PrintByTimes(df, start_time, end_time, format);
319
320         }
321         else // No time was specified; just return a recent set of points
322         {
323                 pthread_mutex_lock(&(df->mutex));
324                         int start_index = df->num_points-DATA_BUFSIZ;
325                         int end_index = df->num_points-1;
326                 pthread_mutex_unlock(&(df->mutex));
327
328                 // Bounds check
329                 if (start_index < 0)
330                         start_index = 0;
331                 if (end_index < 0)
332                         end_index = 0;
333
334                 // Print points by indexes
335                 Data_PrintByIndexes(df, start_index, end_index, format);
336         }
337
338 }
339
340 /**
341  * Helper - Convert human readable format string to DataFormat
342  * @param fmt - FCGIValue to use
343  */
344 DataFormat Data_GetFormat(FCGIValue * fmt)
345 {
346         const char * fmt_str = *(const char**)(fmt->value);
347         // Check if format type was specified
348         if (FCGI_RECEIVED(fmt->flags))
349         {
350                 if (strcmp(fmt_str, "json") == 0)
351                         return JSON;
352                 else if (strcmp(fmt_str, "tsv") == 0)
353                         return TSV;
354                 else
355                         Log(LOGERR, "Unknown format type \"%s\"", fmt_str);
356         }
357         return JSON;
358 }
359
360 /**
361  * Binary search for index of a double in an array
362  * @param value - The value
363  * @param x - The array
364  * @param size - Sizeof the array
365  */
366 int FindClosest(double value, double x[], int size)
367 {
368         int upper = size-1;
369         int lower = 0;
370         int index = 0;
371         while (upper - lower > 1)
372         {
373                 index = lower + ((upper - lower)/2);
374                 double look = x[index];
375                 if (look > value)
376                         upper = index;
377                 else if (look < value)
378                         lower = index;
379                 else
380                         return index;
381         }
382
383         if (x[index] > value && index > 0)
384                 --index;
385         return index;
386
387 }
388
389 /**
390  * Get calibrated value by interpolation in array y
391  * @param value - Raw measured value
392  * @param x - x values (raw values) of the data
393  * @param y - calibrated values
394  * @param size - Number of values in the arrays
395  * @returns interpolated calibrated value
396  */
397 double Data_Calibrate(double value, double x[], double y[], int size)
398 {
399         int i = FindClosest(value, x, size);
400         if (i >= size-1)
401         {
402                 i = size-2;     
403         }
404         double dist = (value - x[i])/(x[i+1] - x[i]);
405         return y[i] + dist*(y[i+1]-y[i]);
406 }

UCC git Repository :: git.ucc.asn.au