1 /* nbdkit
2  * Copyright (C) 2019 Red Hat Inc.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions are
6  * met:
7  *
8  * * Redistributions of source code must retain the above copyright
9  * notice, this list of conditions and the following disclaimer.
10  *
11  * * Redistributions in binary form must reproduce the above copyright
12  * notice, this list of conditions and the following disclaimer in the
13  * documentation and/or other materials provided with the distribution.
14  *
15  * * Neither the name of Red Hat nor the names of its contributors may be
16  * used to endorse or promote products derived from this software without
17  * specific prior written permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY RED HAT AND CONTRIBUTORS ''AS IS'' AND
20  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
21  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
22  * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL RED HAT OR
23  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
26  * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
27  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
29  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30  * SUCH DAMAGE.
31  */
32 
33 #include <config.h>
34 
35 #include <stdio.h>
36 #include <stdlib.h>
37 #include <stdint.h>
38 #include <stdbool.h>
39 #include <inttypes.h>
40 #include <string.h>
41 #include <sys/time.h>
42 #include <fcntl.h>
43 #include <unistd.h>
44 
45 #include <pthread.h>
46 
47 #include <nbdkit-filter.h>
48 
49 #include "cleanup.h"
50 #include "tvdiff.h"
51 
52 static char *filename;
53 static bool append;
54 static FILE *fp;
55 static struct timeval start_t;
56 
57 typedef struct {
58   const char *name;
59   uint64_t ops;
60   uint64_t bytes;
61   uint64_t usecs;
62 } nbdstat;
63 
64 /* This lock protects all the stats. */
65 static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
66 static nbdstat pread_st   = { "read" };
67 static nbdstat pwrite_st  = { "write" };
68 static nbdstat trim_st    = { "trim" };
69 static nbdstat zero_st    = { "zero" };
70 static nbdstat extents_st = { "extents" };
71 static nbdstat cache_st   = { "cache" };
72 static nbdstat flush_st   = { "flush" };
73 
74 #define KiB 1024
75 #define MiB 1048576
76 #define GiB 1073741824
77 
78 static char *
humansize(uint64_t bytes)79 humansize (uint64_t bytes)
80 {
81   int r;
82   char *ret;
83 
84   if (bytes < KiB)
85     r = asprintf (&ret, "%" PRIu64 " bytes", bytes);
86   else if (bytes < MiB)
87     r = asprintf (&ret, "%.2f KiB", bytes / (double)KiB);
88   else if (bytes < GiB)
89     r = asprintf (&ret, "%.2f MiB", bytes / (double)MiB);
90   else
91     r = asprintf (&ret, "%.2f GiB", bytes / (double)GiB);
92   if (r == -1)
93     ret = NULL;
94   return ret;
95 }
96 
97 static char *
humanrate(uint64_t bytes,uint64_t usecs)98 humanrate (uint64_t bytes, uint64_t usecs)
99 {
100   double secs = usecs / 1000000.0;
101   return secs != 0.0 ? humansize (bytes / secs) : NULL;
102 }
103 
104 static inline const char *
maybe(char * s)105 maybe (char *s)
106 {
107   return s ? s : "(n/a)";
108 }
109 
110 static void
print_stat(const nbdstat * st,int64_t usecs)111 print_stat (const nbdstat *st, int64_t usecs)
112 {
113   if (st->ops > 0) {
114     char *size = humansize (st->bytes);
115     char *op_rate = humanrate (st->bytes, st->usecs);
116     char *total_rate = humanrate (st->bytes, usecs);
117 
118     fprintf (fp, "%s: %" PRIu64 " ops, %.6f s, %s, %s/s op, %s/s total\n",
119              st->name, st->ops, st->usecs / 1000000.0, maybe (size),
120              maybe (op_rate), maybe (total_rate));
121 
122     free (size);
123     free (op_rate);
124     free (total_rate);
125   }
126 }
127 
128 static void
print_totals(uint64_t usecs)129 print_totals (uint64_t usecs)
130 {
131   uint64_t ops = pread_st.ops + pwrite_st.ops + trim_st.ops + zero_st.ops +
132     extents_st.ops + flush_st.ops;
133   uint64_t bytes = pread_st.bytes + pwrite_st.bytes + trim_st.bytes +
134     zero_st.bytes;
135   char *size = humansize (bytes);
136   char *rate = humanrate (bytes, usecs);
137 
138   fprintf (fp, "total: %" PRIu64 " ops, %.6f s, %s, %s/s\n",
139            ops, usecs / 1000000.0, maybe (size), maybe (rate));
140 
141   free (size);
142   free (rate);
143 }
144 
145 static inline void
print_stats(int64_t usecs)146 print_stats (int64_t usecs)
147 {
148   print_totals (usecs);
149   print_stat (&pread_st,   usecs);
150   print_stat (&pwrite_st,  usecs);
151   print_stat (&trim_st,    usecs);
152   print_stat (&zero_st,    usecs);
153   print_stat (&extents_st, usecs);
154   print_stat (&cache_st,   usecs);
155   print_stat (&flush_st,   usecs);
156   fflush (fp);
157 }
158 
159 static void
stats_unload(void)160 stats_unload (void)
161 {
162   struct timeval now;
163   int64_t usecs;
164 
165   gettimeofday (&now, NULL);
166   usecs = tvdiff_usec (&start_t, &now);
167   if (fp && usecs > 0) {
168     ACQUIRE_LOCK_FOR_CURRENT_SCOPE (&lock);
169     print_stats (usecs);
170   }
171 
172   if (fp)
173     fclose (fp);
174   free (filename);
175 }
176 
177 static int
stats_config(nbdkit_next_config * next,void * nxdata,const char * key,const char * value)178 stats_config (nbdkit_next_config *next, void *nxdata,
179               const char *key, const char *value)
180 {
181   int r;
182 
183   if (strcmp (key, "statsfile") == 0) {
184     free (filename);
185     filename = nbdkit_absolute_path (value);
186     if (filename == NULL)
187       return -1;
188     return 0;
189   }
190   else if (strcmp (key, "statsappend") == 0) {
191     r = nbdkit_parse_bool (value);
192     if (r == -1)
193       return -1;
194     append = r;
195     return 0;
196   }
197 
198   return next (nxdata, key, value);
199 }
200 
201 static int
stats_config_complete(nbdkit_next_config_complete * next,void * nxdata)202 stats_config_complete (nbdkit_next_config_complete *next, void *nxdata)
203 {
204   if (filename == NULL) {
205     nbdkit_error ("stats filter requires statsfile parameter");
206     return -1;
207   }
208 
209   return next (nxdata);
210 }
211 
212 static int
stats_get_ready(nbdkit_next_get_ready * next,void * nxdata)213 stats_get_ready (nbdkit_next_get_ready *next, void *nxdata)
214 {
215   int fd;
216 
217   /* Using fopen("ae"/"we") would be more convenient, but as Haiku
218    * still lacks that, use this instead. Atomicity is not essential
219    * here since .config completes before threads that might fork, if
220    * we have to later add yet another fallback to fcntl(fileno()) for
221    * systems without O_CLOEXEC.
222    */
223   fd = open (filename,
224              O_CLOEXEC | O_WRONLY | O_CREAT | (append ? O_APPEND : O_TRUNC),
225              0666);
226   if (fd < 0) {
227     nbdkit_error ("open: %s: %m", filename);
228     return -1;
229   }
230   fp = fdopen (fd, append ? "a" : "w");
231   if (fp == NULL) {
232     nbdkit_error ("fdopen: %s: %m", filename);
233     return -1;
234   }
235 
236   gettimeofday (&start_t, NULL);
237 
238   return next (nxdata);
239 }
240 
241 #define stats_config_help \
242   "statsfile=<FILE>    (required) The file to place the log in.\n" \
243   "statsappend=<BOOL>  True to append to the log (default false).\n"
244 
245 static inline void
record_stat(nbdstat * st,uint32_t count,const struct timeval * start)246 record_stat (nbdstat *st, uint32_t count, const struct timeval *start)
247 {
248   struct timeval end;
249   uint64_t usecs;
250 
251   gettimeofday (&end, NULL);
252   usecs = tvdiff_usec (start, &end);
253 
254   ACQUIRE_LOCK_FOR_CURRENT_SCOPE (&lock);
255   st->ops++;
256   st->bytes += count;
257   st->usecs += usecs;
258 }
259 
260 /* Read. */
261 static int
stats_pread(struct nbdkit_next_ops * next_ops,void * nxdata,void * handle,void * buf,uint32_t count,uint64_t offset,uint32_t flags,int * err)262 stats_pread (struct nbdkit_next_ops *next_ops, void *nxdata,
263              void *handle, void *buf, uint32_t count, uint64_t offset,
264              uint32_t flags, int *err)
265 {
266   struct timeval start;
267   int r;
268 
269   gettimeofday (&start, NULL);
270   r = next_ops->pread (nxdata, buf, count, offset, flags, err);
271   if (r == 0) record_stat (&pread_st, count, &start);
272   return r;
273 }
274 
275 /* Write. */
276 static int
stats_pwrite(struct nbdkit_next_ops * next_ops,void * nxdata,void * handle,const void * buf,uint32_t count,uint64_t offset,uint32_t flags,int * err)277 stats_pwrite (struct nbdkit_next_ops *next_ops, void *nxdata,
278               void *handle,
279               const void *buf, uint32_t count, uint64_t offset,
280               uint32_t flags, int *err)
281 {
282   struct timeval start;
283   int r;
284 
285   gettimeofday (&start, NULL);
286   r = next_ops->pwrite (nxdata, buf, count, offset, flags, err);
287   if (r == 0) record_stat (&pwrite_st, count, &start);
288   return r;
289 }
290 
291 /* Trim. */
292 static int
stats_trim(struct nbdkit_next_ops * next_ops,void * nxdata,void * handle,uint32_t count,uint64_t offset,uint32_t flags,int * err)293 stats_trim (struct nbdkit_next_ops *next_ops, void *nxdata,
294             void *handle,
295             uint32_t count, uint64_t offset, uint32_t flags,
296             int *err)
297 {
298   struct timeval start;
299   int r;
300 
301   gettimeofday (&start, NULL);
302   r = next_ops->trim (nxdata, count, offset, flags, err);
303   if (r == 0) record_stat (&trim_st, count, &start);
304   return r;
305 }
306 
307 /* Flush. */
308 static int
stats_flush(struct nbdkit_next_ops * next_ops,void * nxdata,void * handle,uint32_t flags,int * err)309 stats_flush (struct nbdkit_next_ops *next_ops, void *nxdata,
310              void *handle, uint32_t flags,
311              int *err)
312 {
313   struct timeval start;
314   int r;
315 
316   gettimeofday (&start, NULL);
317   r = next_ops->flush (nxdata, flags, err);
318   if (r == 0) record_stat (&flush_st, 0, &start);
319   return r;
320 }
321 
322 /* Zero. */
323 static int
stats_zero(struct nbdkit_next_ops * next_ops,void * nxdata,void * handle,uint32_t count,uint64_t offset,uint32_t flags,int * err)324 stats_zero (struct nbdkit_next_ops *next_ops, void *nxdata,
325             void *handle,
326             uint32_t count, uint64_t offset, uint32_t flags,
327             int *err)
328 {
329   struct timeval start;
330   int r;
331 
332   gettimeofday (&start, NULL);
333   r = next_ops->zero (nxdata, count, offset, flags, err);
334   if (r == 0) record_stat (&zero_st, count, &start);
335   return r;
336 }
337 
338 /* Extents. */
339 static int
stats_extents(struct nbdkit_next_ops * next_ops,void * nxdata,void * handle,uint32_t count,uint64_t offset,uint32_t flags,struct nbdkit_extents * extents,int * err)340 stats_extents (struct nbdkit_next_ops *next_ops, void *nxdata,
341                void *handle,
342                uint32_t count, uint64_t offset, uint32_t flags,
343                struct nbdkit_extents *extents, int *err)
344 {
345   struct timeval start;
346   int r;
347 
348   gettimeofday (&start, NULL);
349   r = next_ops->extents (nxdata, count, offset, flags, extents, err);
350   /* XXX There's a case for trying to determine how long the extents
351    * will be that are returned to the client (instead of simply using
352    * count), given the flags and the complex rules in the protocol.
353    */
354   if (r == 0) record_stat (&extents_st, count, &start);
355   return r;
356 }
357 
358 /* Cache. */
359 static int
stats_cache(struct nbdkit_next_ops * next_ops,void * nxdata,void * handle,uint32_t count,uint64_t offset,uint32_t flags,int * err)360 stats_cache (struct nbdkit_next_ops *next_ops, void *nxdata,
361              void *handle,
362              uint32_t count, uint64_t offset, uint32_t flags,
363              int *err)
364 {
365   struct timeval start;
366   int r;
367 
368   gettimeofday (&start, NULL);
369   r = next_ops->cache (nxdata, count, offset, flags, err);
370   if (r == 0) record_stat (&cache_st, count, &start);
371   return r;
372 }
373 
374 static struct nbdkit_filter filter = {
375   .name              = "stats",
376   .longname          = "nbdkit stats filter",
377   .unload            = stats_unload,
378   .config            = stats_config,
379   .config_complete   = stats_config_complete,
380   .config_help       = stats_config_help,
381   .get_ready         = stats_get_ready,
382   .pread             = stats_pread,
383   .pwrite            = stats_pwrite,
384   .trim              = stats_trim,
385   .flush             = stats_flush,
386   .zero              = stats_zero,
387   .extents           = stats_extents,
388   .cache             = stats_cache,
389 };
390 
391 NBDKIT_REGISTER_FILTER(filter)
392