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