Updated control stuff
[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         df->filename = NULL;
17         pthread_mutex_init(&(df->mutex), NULL);
18         df->file = NULL;
19 }
20
21 /**
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
25  */
26 void Data_Open(DataFile * df, const char * filename)
27 {
28         assert(filename != NULL);
29         assert(df != NULL);
30
31         // Set the filename
32         df->filename = strdup(filename);
33
34         // Set number of DataPoints
35         df->num_points = 0; 
36
37         // Set file pointer
38         df->file = fopen(filename, "wb+");
39         if (df->file == NULL)
40         {
41                 Fatal("Error opening DataFile %s - %s", filename, strerror(errno));
42         }
43 }
44
45 /**
46  * Close a DataFile
47  * @param df - The DataFile to close
48  */
49 void Data_Close(DataFile * df)
50 {
51         assert(df != NULL);
52
53         //TODO: Write data to TSV?
54
55         fclose(df->file);
56
57         // Clear the FILE*s
58         df->file = NULL;
59
60         // Clear the filename
61         free(df->filename);
62         df->filename = NULL;
63 }
64
65 /**
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
70  */
71 void Data_Save(DataFile * df, DataPoint * buffer, int amount)
72 {
73         pthread_mutex_lock(&(df->mutex));
74         assert(df != NULL);
75         assert(buffer != NULL);
76         assert(amount >= 0);
77
78         // Go to the end of the file
79         if (fseek(df->file, 0, SEEK_END) < 0)
80         {
81                 Fatal("Error seeking to end of DataFile %s - %s", df->filename, strerror(errno));
82         }
83
84         // Attempt to write the DataPoints
85         int amount_written = fwrite(buffer, sizeof(DataPoint), amount, df->file);
86         
87         // Check if the correct number of points were written
88         if (amount_written != amount)
89         {
90                 Fatal("Wrote %d points instead of %d to DataFile %s - %s", amount_written, amount, df->filename, strerror(errno));
91         }
92
93         // Update number of DataPoints
94         df->num_points += amount_written;
95
96         pthread_mutex_unlock(&(df->mutex));
97 }
98
99 /**
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)
106  */
107 int Data_Read(DataFile * df, DataPoint * buffer, int index, int amount)
108 {
109         pthread_mutex_lock(&(df->mutex));
110
111         assert(df != NULL);
112         assert(buffer != NULL);
113         assert(index >= 0);
114         assert(amount > 0);
115         
116         // If we would read past the end of the file, reduce the amount of points to read
117         
118         if (index + amount > df->num_points)
119         {
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;
122         }
123         
124
125         // Go to position in file
126         if (fseek(df->file, index*sizeof(DataPoint), SEEK_SET))
127         {
128                 Fatal("Error seeking to position %d in DataFile %s - %s", index, df->filename, strerror(errno));
129         }
130
131         // Attempt to read the DataPoints
132         int amount_read = fread(buffer, sizeof(DataPoint), amount, df->file);
133
134         // Check if correct number of points were read
135         if (amount_read != amount)
136         {
137                 Log(LOGERR,"Read %d points instead of %d from DataFile %s - %s", amount_read, amount, df->filename, strerror(errno));
138         }
139
140         pthread_mutex_unlock(&(df->mutex));
141         return amount_read;
142 }
143
144 /**
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
150  */
151 void Data_PrintByIndexes(DataFile * df, int start_index, int end_index, DataFormat format)
152 {
153         assert(df != NULL);
154         assert(start_index >= 0);
155         assert(end_index >= 0);
156         assert(end_index <= df->num_points || df->num_points == 0);
157
158         const char * fmt_string; // Format for each data point
159         char separator; // Character used to seperate successive data points
160         
161         // Determine what format string and separator character to use
162         switch (format)
163         {
164                 case JSON:
165                         fmt_string = "[%f,%f]";
166                         separator = ',';
167                         // For JSON we need an opening bracket
168                         FCGI_PrintRaw("["); 
169                         break;
170                 case TSV:
171                         fmt_string = "%f\t%f";
172                         separator = '\n';
173                         break;
174         }
175
176         DataPoint buffer[DATA_BUFSIZ] = {{0}}; // Buffer
177         int index = start_index;
178
179         // Repeat until all DataPoints are printed
180         while (index < end_index)
181         {
182                 // Fill the buffer from the DataFile
183                 int amount_read = Data_Read(df, buffer, index, DATA_BUFSIZ);
184
185                 // Print all points in the buffer
186                 for (int i = 0; i < amount_read && index < end_index; ++i)
187                 {
188                         // Print individual DataPoint
189                         FCGI_PrintRaw(fmt_string, buffer[i].time_stamp, buffer[i].value);
190                         
191                         // Last separator is not required
192                         if (index+1 < end_index)
193                                 FCGI_PrintRaw("%c", separator);
194
195                         // Advance the position in the DataFile
196                         ++index;
197                 }
198         }
199         
200         switch (format)
201         {
202                 case JSON:
203                         // For JSON we need a closing bracket
204                         FCGI_PrintRaw("]"); 
205                         break;
206                 default:
207                         break;
208         }
209 }
210
211 /**
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
218  */
219 void Data_PrintByTimes(DataFile * df, double start_time, double end_time, DataFormat format)
220 {
221         assert(df != NULL);
222         //Clamp boundaries
223         if (start_time < 0)
224                 start_time = 0;
225         if (end_time < 0)
226                 end_time = 0;
227
228         int start_index = 0, end_index = 0;
229         if (start_time < end_time)
230         {
231                 start_index = Data_FindByTime(df, start_time, NULL);
232                 end_index = Data_FindByTime(df, end_time, NULL);
233         }
234
235         Data_PrintByIndexes(df, start_index, end_index, format);
236 }
237
238 /**
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
244  */
245 int Data_FindByTime(DataFile * df, double time_stamp, DataPoint * closest)
246 {
247         assert(df != NULL);
248         assert(time_stamp >= 0);
249         //assert(closest != NULL);
250
251         DataPoint tmp; // Current DataPoint in binary search
252
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
258
259         // Commence binary search:
260         while (upper - lower > 1)
261         {
262                 // Pick midpoint
263                 index = lower + ((upper - lower)/2);
264
265                 // Look at DataPoint
266                 if (Data_Read(df, &tmp, index, 1) != 1)
267                 {
268                         Fatal("Couldn't read DataFile %s at index %d", df->filename, index);
269                 }
270
271                 // Change search interval to either half appropriately
272                 if (tmp.time_stamp > time_stamp)
273                 {
274                         upper = index;
275                 }
276                 else if (tmp.time_stamp < time_stamp)
277                 {
278                         lower = index;
279                 }
280         }
281
282         // Store closest DataPoint
283         if (closest != NULL)
284                 *closest = tmp;
285         
286         return index;
287         
288 }
289
290 /**
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
298  */
299 void Data_Handler(DataFile * df, FCGIValue * start, FCGIValue * end, DataFormat format, double current_time)
300 {
301         double start_time = *(double*)(start->value);
302         double end_time = *(double*)(end->value);
303
304         if (format == JSON)
305         {
306                 FCGI_JSONKey("data");
307         }
308
309         // If a time was specified
310         if (FCGI_RECEIVED(start->flags) || FCGI_RECEIVED(end->flags))
311         {
312                 // Wrap times relative to the current time
313                 if (start_time < 0)
314                         start_time += current_time;
315                 if (end_time < 0)
316                         end_time += current_time;
317
318                 // Print points by time range
319                 Data_PrintByTimes(df, start_time, end_time, format);
320
321         }
322         else // No time was specified; just return a recent set of points
323         {
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));
328
329                 // Bounds check
330                 if (start_index < 0)
331                         start_index = 0;
332                 if (end_index < 0)
333                         end_index = 0;
334
335                 // Print points by indexes
336                 Data_PrintByIndexes(df, start_index, end_index, format);
337         }
338
339 }
340
341 /**
342  * Helper - Convert human readable format string to DataFormat
343  * @param fmt - FCGIValue to use
344  */
345 DataFormat Data_GetFormat(FCGIValue * fmt)
346 {
347         const char * fmt_str = *(const char**)(fmt->value);
348         // Check if format type was specified
349         if (FCGI_RECEIVED(fmt->flags))
350         {
351                 if (strcmp(fmt_str, "json") == 0)
352                         return JSON;
353                 else if (strcmp(fmt_str, "tsv") == 0)
354                         return TSV;
355                 else
356                         Log(LOGERR, "Unknown format type \"%s\"", fmt_str);
357         }
358         return JSON;
359 }

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