Allow profile results to be written to a file.
[bluesky.git] / bluesky / util.c
1 /* Blue Sky: File Systems in the Cloud
2  *
3  * Copyright (C) 2009  The Regents of the University of California
4  * Written by Michael Vrable <mvrable@cs.ucsd.edu>
5  *
6  * TODO: Licensing
7  */
8
9 #define _GNU_SOURCE
10 #include <stdio.h>
11 #include <stdint.h>
12 #include <glib.h>
13 #include <string.h>
14 #include <unistd.h>
15 #include <sys/mman.h>
16 #include <sys/syscall.h>
17 #include <sys/types.h>
18
19 #include "bluesky-private.h"
20
21 /* Miscellaneous useful functions that don't really fit anywhere else. */
22
23 bluesky_time_hires bluesky_now_hires()
24 {
25     struct timespec time;
26
27     if (clock_gettime(CLOCK_REALTIME, &time) != 0) {
28         perror("clock_gettime");
29         return 0;
30     }
31
32     return (int64_t)(time.tv_sec) * 1000000000 + time.tv_nsec;
33 }
34
35 /* Convert a UTF-8 string to lowercase.  This can be used to implement
36  * case-insensitive lookups and comparisons, by normalizing all values to
37  * lowercase first.  Returns a newly-allocated string as a result. */
38 gchar *bluesky_lowercase(const gchar *s)
39 {
40     /* TODO: Unicode handling; for now just do ASCII. */
41     return g_ascii_strdown(s, -1);
42 }
43
44 gboolean bluesky_inode_is_ready(BlueSkyInode *inode)
45 {
46     if (inode == NULL)
47         return FALSE;
48
49     g_mutex_lock(inode->lock);
50     gboolean valid = (inode->type != BLUESKY_PENDING
51                       && inode->type != BLUESKY_INVALID);
52
53     g_mutex_unlock(inode->lock);
54
55     return valid;
56 }
57
58 /**** Reference-counted strings. ****/
59
60 /* Create and return a new reference-counted string.  The reference count is
61  * initially one.  The newly-returned string takes ownership of the memory
62  * pointed at by data, and will call g_free on it when the reference count
63  * drops to zero. */
64 BlueSkyRCStr *bluesky_string_new(gpointer data, gsize len)
65 {
66     BlueSkyRCStr *string = g_new(BlueSkyRCStr, 1);
67     string->mmap = NULL;
68     string->data = data;
69     string->len = len;
70     g_atomic_int_set(&string->refcount, 1);
71     return string;
72 }
73
74 /* Create a new BlueSkyRCStr from a GString.  The GString is destroyed. */
75 BlueSkyRCStr *bluesky_string_new_from_gstring(GString *s)
76 {
77     gsize len = s->len;
78     return bluesky_string_new(g_string_free(s, FALSE), len);
79 }
80
81 /* Create a new BlueSkyRCStr from a memory-mapped buffer. */
82 BlueSkyRCStr *bluesky_string_new_from_mmap(BlueSkyCacheFile *mmap,
83                                            int offset, gsize len)
84 {
85     g_assert(offset + len <= mmap->len);
86
87     BlueSkyRCStr *string = g_new(BlueSkyRCStr, 1);
88     string->mmap = mmap;
89     g_atomic_int_inc(&mmap->mapcount);
90     string->data = (char *)mmap->addr + offset;
91     string->len = len;
92     g_atomic_int_set(&string->refcount, 1);
93     return string;
94 }
95
96 void bluesky_string_ref(BlueSkyRCStr *string)
97 {
98     if (string == NULL)
99         return;
100
101     g_atomic_int_inc(&string->refcount);
102 }
103
104 void bluesky_string_unref(BlueSkyRCStr *string)
105 {
106     if (string == NULL)
107         return;
108
109     if (g_atomic_int_dec_and_test(&string->refcount)) {
110         if (string->mmap == NULL) {
111             g_free(string->data);
112         } else {
113             bluesky_mmap_unref(string->mmap);
114         }
115         g_free(string);
116     }
117 }
118
119 /* Duplicate and return a new reference-counted string, containing a copy of
120  * the original data, with a reference count of 1.  As an optimization, if the
121  * passed-in string already has a reference count of 1, the original is
122  * returned.   Can be used to make a mutable copy of a shared string.  For this
123  * to truly be safe, it is probably needed that there be some type of lock
124  * protecting access to the string. */
125 BlueSkyRCStr *bluesky_string_dup(BlueSkyRCStr *string)
126 {
127     if (string == NULL)
128         return NULL;
129
130     if (string->mmap != NULL) {
131         BlueSkyRCStr *s;
132         s = bluesky_string_new(g_memdup(string->data, string->len),
133                                string->len);
134         bluesky_string_unref(string);
135         return s;
136     }
137
138     if (g_atomic_int_dec_and_test(&string->refcount)) {
139         /* There are no other shared copies, so return this one. */
140         g_atomic_int_inc(&string->refcount);
141         return string;
142     } else {
143         return bluesky_string_new(g_memdup(string->data, string->len),
144                                   string->len);
145     }
146 }
147
148 /* Resize the data block used by a BlueSkyRCStr.  The data pointer might change
149  * after making this call, so it should not be cached across calls to this
150  * function.  To avoid confusing any other users, the caller probably ought to
151  * hold the only reference to the string (by calling bluesky_string_dup first
152  * if needed). */
153 void bluesky_string_resize(BlueSkyRCStr *string, gsize len)
154 {
155     g_assert(string->mmap == NULL);
156
157     if (string->len == len)
158         return;
159
160     g_warn_if_fail(string->refcount == 1);
161
162     string->data = g_realloc(string->data, len);
163     string->len = len;
164 }
165
166 /* Cache LRU list management functions.  These manage the doubly-linked list of
167  * inodes sorted by accessed/modified time.  The FS lock should be held while
168  * calling these.
169  *
170  * _remove will unlink an inode from the linked list.
171  *
172  * _prepend and _append insert an inode at the head or tail of the linked list,
173  * and return a pointer to the linked list element (which should be stored in
174  * the inode); the inode should not already be in the list.
175  *
176  * _head and _tail simply return the first or last item inode in the list. */
177 void bluesky_list_unlink(GList *head, GList *item)
178 {
179     if (item == NULL)
180         return;
181
182     if (head->prev == item)
183         head->prev = item->prev;
184     head->next = g_list_delete_link(head->next, item);
185 }
186
187 GList *bluesky_list_prepend(GList *head, BlueSkyInode *inode)
188 {
189     head->next = g_list_prepend(head->next, inode);
190     if (head->prev == NULL)
191         head->prev = g_list_last(head->next);
192     return head->next;
193 }
194
195 GList *bluesky_list_append(GList *head, BlueSkyInode *inode)
196 {
197     if (head->next == NULL)
198         return bluesky_list_prepend(head, inode);
199
200     g_assert(head->prev != NULL && head->prev->next == NULL);
201
202     GList *link = g_list_alloc();
203     link->data = inode;
204     link->next = NULL;
205     link->prev = head->prev;
206     head->prev->next = link;
207     head->prev = link;
208     return link;
209 }
210
211 BlueSkyInode *bluesky_list_head(GList *head)
212 {
213     if (head->next == NULL)
214         return NULL;
215     else
216         return (BlueSkyInode *)head->next->data;
217 }
218
219 BlueSkyInode *bluesky_list_tail(GList *head)
220 {
221     if (head->prev == NULL)
222         return NULL;
223     else
224         return (BlueSkyInode *)head->prev->data;
225 }
226
227 /**** Range sets. ****/
228
229 /* These are a data structure which can track a set of discontiguous integer
230  * ranges--such as the partitioning of the inode number space or the bytes in a
231  * log file into objects.  This current prototype implementation just tracks
232  * the starting offset with a hash table and doesn't track the length, but
233  * should be extended later to track properly. */
234
235 struct BlueSkyRangeset {
236     GSequence *seq;
237 };
238
239 static gint compare64(gconstpointer a, gconstpointer b, gpointer user_data)
240 {
241     uint64_t x = *(uint64_t *)a;
242     uint64_t y = *(uint64_t *)b;
243
244     if (x < y)
245         return -1;
246     else if (x > y)
247         return 1;
248     else
249         return 0;
250 }
251
252 BlueSkyRangeset *bluesky_rangeset_new()
253 {
254     BlueSkyRangeset *rangeset = g_new(BlueSkyRangeset, 1);
255     rangeset->seq = g_sequence_new(g_free);
256     return rangeset;
257 }
258
259 void bluesky_rangeset_free(BlueSkyRangeset *rangeset)
260 {
261     g_sequence_free(rangeset->seq);
262     g_free(rangeset);
263 }
264
265 gboolean bluesky_rangeset_insert(BlueSkyRangeset *rangeset,
266                                  uint64_t start, uint64_t length,
267                                  gpointer data)
268 {
269     GSequenceIter *i;
270     i = g_sequence_search(rangeset->seq, &start, compare64, NULL);
271     i = g_sequence_iter_prev(i);
272
273     /* TODO: Checks that no other item overlaps. */
274
275     BlueSkyRangesetItem *item = g_new(BlueSkyRangesetItem, 1);
276     item->start = start;
277     item->length = length;
278     item->data = data;
279     g_sequence_insert_sorted(rangeset->seq, item, compare64, NULL);
280
281     return TRUE;
282 }
283
284 const BlueSkyRangesetItem *bluesky_rangeset_lookup(BlueSkyRangeset *rangeset,
285                                                    uint64_t offset)
286 {
287     GSequenceIter *i;
288     i = g_sequence_search(rangeset->seq, &offset, compare64, NULL);
289     i = g_sequence_iter_prev(i);
290     if (g_sequence_iter_is_end(i))
291         return NULL;
292
293     BlueSkyRangesetItem *item = (BlueSkyRangesetItem *)g_sequence_get(i);
294     if (offset >= item->start && offset < item->start + item->length)
295         return item;
296     else
297         return NULL;
298 }
299
300 /* Look up the first rangeset item starting at or following the given address.
301  * Can be used to iterate through a rangeset. */
302 const BlueSkyRangesetItem *bluesky_rangeset_lookup_next(BlueSkyRangeset *rangeset, uint64_t offset)
303 {
304     GSequenceIter *i;
305     i = g_sequence_search(rangeset->seq, &offset, compare64, NULL);
306     i = g_sequence_iter_prev(i);
307     if (g_sequence_iter_is_end(i))
308         return NULL;
309     BlueSkyRangesetItem *item = (BlueSkyRangesetItem *)g_sequence_get(i);
310     if (item->start < offset) {
311         i = g_sequence_iter_next(i);
312         if (g_sequence_iter_is_end(i))
313             item = NULL;
314         else
315             item = (BlueSkyRangesetItem *)g_sequence_get(i);
316     }
317     return item;
318 }
319
320 /* Determine the full extent of a rangeset--the smallest offset covered and the
321  * length needed to extend to the end of the last item. */
322 void bluesky_rangeset_get_extents(BlueSkyRangeset *rangeset,
323                                   uint64_t *start, uint64_t *length)
324 {
325     GSequenceIter *i;
326     BlueSkyRangesetItem *item;
327
328     i = g_sequence_get_begin_iter(rangeset->seq);
329     if (g_sequence_iter_is_end(i)) {
330         *start = 0;
331         *length = 0;
332         return;
333     }
334
335     item = (BlueSkyRangesetItem *)g_sequence_get(i);
336     *start = item->start;
337
338     i = g_sequence_get_end_iter(rangeset->seq);
339     i = g_sequence_iter_prev(i);
340     item = (BlueSkyRangesetItem *)g_sequence_get(i);
341     *length = (item->start + item->length) - *start;
342 }
343
344 /**** Request response-time tracking. ****/
345 /* TODO: Locking */
346 typedef struct {
347     int tid;
348     bluesky_time_hires timestamp;
349     char *message;
350 } RTEvent;
351
352 BlueSkyProfile *bluesky_profile_new()
353 {
354     BlueSkyProfile *profile = g_new0(BlueSkyProfile, 1);
355     profile->lock = g_mutex_new();
356     return profile;
357 }
358
359 void bluesky_profile_free(BlueSkyProfile *profile)
360 {
361     while (profile->events != NULL) {
362         RTEvent *event = (RTEvent *)profile->events->data;
363         g_free(event->message);
364         g_free(event);
365         profile->events = g_list_delete_link(profile->events, profile->events);
366     }
367     g_mutex_free(profile->lock);
368     g_free(profile->description);
369     g_free(profile);
370 }
371
372 void bluesky_profile_add_event(BlueSkyProfile *profile, char *message)
373 {
374     g_return_if_fail(profile != NULL);
375
376     g_mutex_lock(profile->lock);
377     RTEvent *event = g_new(RTEvent, 1);
378     event->timestamp = bluesky_now_hires();
379     /* FIXME: Non-portable */
380     event->tid = syscall(SYS_gettid);
381     event->message = message;
382     profile->events = g_list_prepend(profile->events, event);
383     g_mutex_unlock(profile->lock);
384 }
385
386 static FILE *profiling_file = NULL;
387 static GStaticMutex profiling_print_lock = G_STATIC_MUTEX_INIT;
388
389 void bluesky_profile_set_output(FILE *stream)
390 {
391     profiling_file = stream;
392 }
393
394 void bluesky_profile_print(BlueSkyProfile *profile)
395 {
396     FILE *stream = profiling_file ? profiling_file : stdout;
397     g_return_if_fail(profile != NULL);
398
399     g_mutex_lock(profile->lock);
400     g_static_mutex_lock(&profiling_print_lock);
401     fprintf(stream, "Event Timeline: %s\n", profile->description);
402     GList *link = g_list_last(profile->events);
403     bluesky_time_hires last_time = 0;
404     while (link != NULL) {
405         RTEvent *event = (RTEvent *)link->data;
406         fprintf(stream, "  [%d] [%"PRIi64" ns]: %s\n",
407                 event->tid, event->timestamp - last_time, event->message);
408         last_time = event->timestamp;
409         link = link->prev;
410     }
411     g_static_mutex_unlock(&profiling_print_lock);
412     g_mutex_unlock(profile->lock);
413 }
414
415 static GStaticPrivate per_thread_profile = G_STATIC_PRIVATE_INIT;
416
417 BlueSkyProfile *bluesky_profile_get()
418 {
419     return (BlueSkyProfile *)g_static_private_get(&per_thread_profile);
420 }
421
422 void bluesky_profile_set(BlueSkyProfile *profile)
423 {
424     g_static_private_set(&per_thread_profile, profile, NULL);
425 }