1 /* fsfs-access-map.c -- convert strace output into FSFS access bitmap
2  *
3  * ====================================================================
4  *    Licensed to the Apache Software Foundation (ASF) under one
5  *    or more contributor license agreements.  See the NOTICE file
6  *    distributed with this work for additional information
7  *    regarding copyright ownership.  The ASF licenses this file
8  *    to you under the Apache License, Version 2.0 (the
9  *    "License"); you may not use this file except in compliance
10  *    with the License.  You may obtain a copy of the License at
11  *
12  *      http://www.apache.org/licenses/LICENSE-2.0
13  *
14  *    Unless required by applicable law or agreed to in writing,
15  *    software distributed under the License is distributed on an
16  *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17  *    KIND, either express or implied.  See the License for the
18  *    specific language governing permissions and limitations
19  *    under the License.
20  * ====================================================================
21  */
22 
23 #include "svn_pools.h"
24 #include "svn_string.h"
25 #include "svn_io.h"
26 
27 #include "private/svn_string_private.h"
28 
29 /* The information we gather for each file.  There will be one instance
30  * per file name - even if the file got deleted and re-created.
31  */
32 typedef struct file_stats_t
33 {
34   /* file name as found in the open() call */
35   const char *name;
36 
37   /* file size as determined during the tool run.  Will be 0 for
38    * files that no longer exist.  However, there may still be entries
39    * in the read_map. */
40   apr_int64_t size;
41 
42   /* for rev files (packed or non-packed), this will be the first revision
43    * that file. -1 for non-rev files. */
44   apr_int64_t rev_num;
45 
46   /* number of times this file got opened */
47   apr_int64_t open_count;
48 
49   /* number of lseek counts */
50   apr_int64_t seek_count;
51 
52   /* number of lseek calls to clusters not previously read */
53   apr_int64_t uncached_seek_count;
54 
55   /* number of lseek counts not followed by a read */
56   apr_int64_t unnecessary_seeks;
57 
58   /* number of read() calls */
59   apr_int64_t read_count;
60 
61   /* number of read() calls that returned 0 bytes */
62   apr_int64_t empty_reads;
63 
64   /* total number of bytes returned by those reads */
65   apr_int64_t read_size;
66 
67   /* number of clusters read */
68   apr_int64_t clusters_read;
69 
70   /* number of different clusters read
71    * (i.e. number of non-zero entries in read_map). */
72   apr_int64_t unique_clusters_read;
73 
74   /* cluster -> read count mapping (1 word per cluster, saturated at 64k) */
75   apr_array_header_t *read_map;
76 
77 } file_stats_t;
78 
79 /* Represents an open file handle.  It refers to a file and concatenates
80  * consecutive reads such that we don't artificially hit the same cluster
81  * multiple times.  Instances of this type will be reused to limit the
82  * allocation load on the lookup map.
83  */
84 typedef struct handle_info_t
85 {
86   /* the open file */
87   file_stats_t *file;
88 
89   /* file offset at which the current series of reads started (default: 0) */
90   apr_int64_t last_read_start;
91 
92   /* bytes read so far in the current series of reads started (default: 0) */
93   apr_int64_t last_read_size;
94 
95   /* number of read() calls in this series */
96   apr_int64_t read_count;
97 } handle_info_t;
98 
99 /* useful typedef */
100 typedef unsigned char byte;
101 typedef unsigned short word;
102 
103 /* an RGB color */
104 typedef byte color_t[3];
105 
106 /* global const char * file name -> *file_info_t map */
107 static apr_hash_t *files = NULL;
108 
109 /* global int handle -> *handle_info_t map.  Entries don't get removed
110  * by close().  Instead, we simply recycle (and re-initilize) existing
111  * instances. */
112 static apr_hash_t *handles = NULL;
113 
114 /* assume cluster size.  64 and 128kB are typical values for RAIDs. */
115 static apr_int64_t cluster_size = 64 * 1024;
116 
117 /* Call this after a sequence of reads has been ended by either close()
118  * or lseek() for this HANDLE_INFO.  This will update the read_map and
119  * unique_clusters_read members of the underlying file_info_t structure.
120  */
121 static void
store_read_info(handle_info_t * handle_info)122 store_read_info(handle_info_t *handle_info)
123 {
124   if (handle_info->last_read_size)
125     {
126       apr_size_t i;
127       apr_size_t first_cluster
128          = (apr_size_t)(handle_info->last_read_start / cluster_size);
129       apr_size_t last_cluster
130          = (apr_size_t)((  handle_info->last_read_start
131                          + handle_info->last_read_size
132                          - 1) / cluster_size);
133 
134       /* auto-expand access map in case the file later shrunk or got deleted */
135       while (handle_info->file->read_map->nelts <= last_cluster)
136         APR_ARRAY_PUSH(handle_info->file->read_map, word) = 0;
137 
138       /* accumulate the accesses per cluster. Saturate and count first
139        * (i.e. disjoint) accesses clusters */
140       handle_info->file->clusters_read += last_cluster - first_cluster + 1;
141       for (i = first_cluster; i <= last_cluster; ++i)
142         {
143           word *count = &APR_ARRAY_IDX(handle_info->file->read_map, i, word);
144           if (*count == 0)
145             handle_info->file->unique_clusters_read++;
146           if (*count < 0xffff)
147             ++*count;
148         }
149     }
150   else if (handle_info->read_count == 0)
151     {
152       /* two consecutive seeks */
153       handle_info->file->unnecessary_seeks++;
154     }
155 }
156 
157 /* Handle a open() call.  Ensures that a file_info_t for the given NAME
158  * exists.  Auto-create and initialize a handle_info_t for it linked to
159  * HANDLE.
160  */
161 static void
open_file(const char * name,int handle)162 open_file(const char *name, int handle)
163 {
164   file_stats_t *file = apr_hash_get(files, name, APR_HASH_KEY_STRING);
165   handle_info_t *handle_info = apr_hash_get(handles, &handle, sizeof(handle));
166 
167   /* auto-create file info */
168   if (!file)
169     {
170       apr_pool_t *pool = apr_hash_pool_get(files);
171       apr_pool_t *subpool = svn_pool_create(pool);
172 
173       apr_file_t *apr_file = NULL;
174       apr_finfo_t finfo = { 0 };
175       int cluster_count = 0;
176 
177       /* determine file size (if file still exists) */
178       apr_file_open(&apr_file, name,
179                     APR_READ | APR_BUFFERED, APR_OS_DEFAULT, subpool);
180       if (apr_file)
181         apr_file_info_get(&finfo, APR_FINFO_SIZE, apr_file);
182       svn_pool_destroy(subpool);
183 
184       file = apr_pcalloc(pool, sizeof(*file));
185       file->name = apr_pstrdup(pool, name);
186       file->size = finfo.size;
187 
188       /* pre-allocate cluster map accordingly
189        * (will be auto-expanded later if necessary) */
190       cluster_count = (int)(1 + (file->size - 1) / cluster_size);
191       file->read_map = apr_array_make(pool, file->size
192                                           ? cluster_count
193                                           : 1, sizeof(word));
194 
195       while (file->read_map->nelts < cluster_count)
196         APR_ARRAY_PUSH(file->read_map, byte) = 0;
197 
198       /* determine first revision of rev / packed rev files */
199       if (strstr(name, "/db/revs/") != NULL && strstr(name, "manifest") == NULL)
200         if (strstr(name, ".pack/pack") != NULL)
201           file->rev_num = SVN_STR_TO_REV(strstr(name, "/db/revs/") + 9);
202         else
203           file->rev_num = SVN_STR_TO_REV(strrchr(name, '/') + 1);
204       else
205         file->rev_num = -1;
206 
207       /* filter out log/phys index files */
208       if (file->rev_num >= 0)
209         {
210           const char *suffix = name + strlen(name) - 4;
211           if (strcmp(suffix, ".l2p") == 0 || strcmp(suffix, ".p2l") == 0)
212             file->rev_num = -1;
213         }
214 
215       apr_hash_set(files, file->name, APR_HASH_KEY_STRING, file);
216     }
217 
218   file->open_count++;
219 
220   /* auto-create handle instance */
221   if (!handle_info)
222     {
223       apr_pool_t *pool = apr_hash_pool_get(handles);
224       int *key = apr_palloc(pool, sizeof(*key));
225       *key = handle;
226 
227       handle_info = apr_pcalloc(pool, sizeof(*handle_info));
228       apr_hash_set(handles, key, sizeof(*key), handle_info);
229     }
230 
231   /* link handle to file */
232   handle_info->file = file;
233   handle_info->last_read_start = 0;
234   handle_info->last_read_size = 0;
235 }
236 
237 /* COUNT bytes have been read from file with the given HANDLE.
238  */
239 static void
read_file(int handle,apr_int64_t count)240 read_file(int handle, apr_int64_t count)
241 {
242   handle_info_t *handle_info = apr_hash_get(handles, &handle, sizeof(handle));
243   if (handle_info)
244     {
245       /* known file handle -> expand current read sequence */
246 
247       handle_info->read_count++;
248       handle_info->last_read_size += count;
249       handle_info->file->read_count++;
250       handle_info->file->read_size += count;
251 
252       if (count == 0)
253         handle_info->file->empty_reads++;
254     }
255 }
256 
257 /* Seek to offset LOCATION in file given by HANDLE.
258  */
259 static void
seek_file(int handle,apr_int64_t location)260 seek_file(int handle, apr_int64_t location)
261 {
262   handle_info_t *handle_info = apr_hash_get(handles, &handle, sizeof(handle));
263   if (handle_info)
264     {
265       /* known file handle -> end current read sequence and start a new one */
266 
267       apr_size_t cluster = (apr_size_t)(location / cluster_size);
268 
269       store_read_info(handle_info);
270 
271       handle_info->last_read_size = 0;
272       handle_info->last_read_start = location;
273       handle_info->read_count = 0;
274       handle_info->file->seek_count++;
275 
276       /* if we seek to a location that had not been read from before,
277        * there will probably be a real I/O seek on the following read.
278        */
279       if (   handle_info->file->read_map->nelts <= cluster
280           || APR_ARRAY_IDX(handle_info->file->read_map, cluster, word) == 0)
281         handle_info->file->uncached_seek_count++;
282     }
283 }
284 
285 /* The given file HANDLE has been closed.
286  */
287 static void
close_file(int handle)288 close_file(int handle)
289 {
290   /* for known file handles, end current read sequence */
291 
292   handle_info_t *handle_info = apr_hash_get(handles, &handle, sizeof(handle));
293   if (handle_info)
294     store_read_info(handle_info);
295 }
296 
297 /* Parse / process non-empty the LINE from an strace output.
298  */
299 static void
parse_line(svn_stringbuf_t * line)300 parse_line(svn_stringbuf_t *line)
301 {
302   /* determine function name, first parameter and return value */
303   char *func_end = strchr(line->data, '(');
304   char *return_value = strrchr(line->data, ' ');
305   char *first_param_end;
306   apr_int64_t func_return = 0;
307   char *func_start = strchr(line->data, ' ');
308 
309   if (func_end == NULL || return_value == NULL)
310     return;
311 
312   if (func_start == NULL || func_start > func_end)
313     func_start = line->data;
314   else
315     while(*func_start == ' ')
316       func_start++;
317 
318   first_param_end = strchr(func_end, ',');
319   if (first_param_end == NULL)
320     first_param_end = strchr(func_end, ')');
321 
322   if (first_param_end == NULL)
323     return;
324 
325   *func_end++ = 0;
326   *first_param_end = 0;
327   ++return_value;
328 
329   /* (try to) convert the return value into an integer.
330    * If that fails, continue anyway as defaulting to 0 will be safe for us. */
331   svn_error_clear(svn_cstring_atoi64(&func_return, return_value));
332 
333   /* process those operations that we care about */
334   if (strcmp(func_start, "open") == 0)
335     {
336       /* remove double quotes from file name parameter */
337       *func_end++ = 0;
338       *--first_param_end = 0;
339 
340       open_file(func_end, (int)func_return);
341     }
342   else if (strcmp(func_start, "read") == 0)
343     read_file(atoi(func_end), func_return);
344   else if (strcmp(func_start, "lseek") == 0)
345     seek_file(atoi(func_end), func_return);
346   else if (strcmp(func_start, "close") == 0)
347     close_file(atoi(func_end));
348 }
349 
350 /* Process the strace output stored in FILE.
351  */
352 static void
parse_file(apr_file_t * file)353 parse_file(apr_file_t *file)
354 {
355   apr_pool_t *pool = svn_pool_create(NULL);
356   apr_pool_t *iterpool = svn_pool_create(pool);
357 
358   /* limit lines to 4k (usually, we need less than 200 bytes) */
359   svn_stringbuf_t *line = svn_stringbuf_create_ensure(4096, pool);
360 
361   do
362     {
363       svn_error_t *err = NULL;
364 
365       line->len = line->blocksize-1;
366       err = svn_io_read_length_line(file, line->data, &line->len, iterpool);
367       svn_error_clear(err);
368       if (err)
369         break;
370 
371       parse_line(line);
372       svn_pool_clear(iterpool);
373     }
374   while (line->len > 0);
375 }
376 
377 /* qsort() callback.  Sort files by revision number.
378  */
379 static int
compare_files(file_stats_t ** lhs,file_stats_t ** rhs)380 compare_files(file_stats_t **lhs, file_stats_t **rhs)
381 {
382   return (*lhs)->rev_num < (*rhs)->rev_num;
383 }
384 
385 /* Return all rev (and packed rev) files sorted by revision number.
386  * Allocate the result in POOL.
387  */
388 static apr_array_header_t *
get_rev_files(apr_pool_t * pool)389 get_rev_files(apr_pool_t *pool)
390 {
391   apr_hash_index_t *hi;
392   apr_array_header_t *result = apr_array_make(pool,
393                                               apr_hash_count(files),
394                                               sizeof(file_stats_t *));
395 
396   /* select all files that have a rev number */
397   for (hi = apr_hash_first(pool, files); hi; hi = apr_hash_next(hi))
398     {
399       const char *name = NULL;
400       apr_ssize_t len = 0;
401       file_stats_t *file = NULL;
402 
403       apr_hash_this(hi, (const void **)&name, &len, (void**)&file);
404       if (file->rev_num >= 0)
405         APR_ARRAY_PUSH(result, file_stats_t *) = file;
406     }
407 
408   /* sort them */
409   qsort(result->elts, result->nelts, result->elt_size,
410         (int (*)(const void *, const void *))compare_files);
411 
412   /* return the result */
413   return result;
414 }
415 
416 /* store VALUE to DEST in little-endian format.  Assume that the target
417  * buffer is filled with 0.
418  */
419 static void
write_number(byte * dest,int value)420 write_number(byte *dest, int value)
421 {
422   while (value)
423     {
424       *dest = (byte)(value % 256);
425       value /= 256;
426       ++dest;
427     }
428 }
429 
430 /* Return a linearly interpolated y value for X with X0 <= X <= X1 and
431  * the corresponding Y0 and Y1 values.
432  */
433 static int
interpolate(int y0,int x0,int y1,int x1,int x)434 interpolate(int y0, int x0, int y1, int x1, int x)
435 {
436   return y0 + ((y1 - y0) * (x - x0)) / (x1 - x0);
437 }
438 
439 /* Return the BMP-encoded 24 bit COLOR for the given value.
440  */
441 static void
select_color(byte color[3],word value)442 select_color(byte color[3], word value)
443 {
444   enum { COLOR_COUNT = 10 };
445 
446   /* value -> color table. Missing values get interpolated.
447    * { count, B - G - R } */
448   word table[COLOR_COUNT][4] =
449     {
450       {     0, 255, 255, 255 },   /* unread -> white */
451       {     1,  64, 128,   0 },   /* read once -> turquoise  */
452       {     2,   0, 128,   0 },   /* twice  -> green */
453       {     8,   0, 192, 192 },   /*    8x  -> yellow */
454       {    64,   0,   0, 192 },   /*   64x  -> red */
455       {   256,  64,  32, 230 },   /*  256x  -> bright red */
456       {   512, 192,   0, 128 },   /*  512x  -> purple */
457       {  1024,  96,  32,  96 },   /* 1024x  -> UV purple */
458       {  4096,  32,  16,  32 },   /* 4096x  -> EUV purple */
459       { 65535,   0,   0,   0 }    /*   max  -> black */
460     };
461 
462   /* find upper limit entry for value */
463   int i;
464   for (i = 0; i < COLOR_COUNT; ++i)
465     if (table[i][0] >= value)
466       break;
467 
468   /* exact match? */
469   if (table[i][0] == value)
470     {
471       color[0] = (byte)table[i][1];
472       color[1] = (byte)table[i][2];
473       color[2] = (byte)table[i][3];
474     }
475   else
476     {
477       /* interpolate */
478       color[0] = (byte)interpolate(table[i-1][1], table[i-1][0],
479                                    table[i][1], table[i][0],
480                                    value);
481       color[1] = (byte)interpolate(table[i-1][2], table[i-1][0],
482                                    table[i][2], table[i][0],
483                                    value);
484       color[2] = (byte)interpolate(table[i-1][3], table[i-1][0],
485                                    table[i][3], table[i][0],
486                                    value);
487     }
488 }
489 
490 /* Writes a BMP image header to FILE for a 24-bit color picture of the
491  * given XSIZE and YSIZE dimension.
492  */
493 static void
write_bitmap_header(apr_file_t * file,int xsize,int ysize)494 write_bitmap_header(apr_file_t *file, int xsize, int ysize)
495 {
496   /* BMP file header (some values need to filled in later)*/
497   byte header[54] =
498     {
499       'B', 'M',        /* magic */
500       0, 0, 0, 0,      /* file size (to be written later) */
501       0, 0, 0, 0,      /* reserved, unused */
502       54, 0, 0, 0,     /* pixel map starts at offset 54dec */
503 
504       40, 0, 0, 0,     /* DIB header has 40 bytes */
505       0, 0, 0, 0,      /* x size in pixel */
506       0, 0, 0, 0,      /* y size in pixel */
507       1, 0,            /* 1 color plane */
508       24, 0,           /* 24 bits / pixel */
509       0, 0, 0, 0,      /* no pixel compression used */
510       0, 0, 0, 0,      /* size of pixel array (to be written later) */
511       0xe8, 3, 0, 0,   /* 1 pixel / mm */
512       0xe8, 3, 0, 0,   /* 1 pixel / mm */
513       0, 0, 0, 0,      /* no colors in palette */
514       0, 0, 0, 0       /* no colors to import */
515     };
516 
517   apr_size_t written;
518 
519   /* rows in BMP files must be aligned to 4 bytes */
520   int row_size = APR_ALIGN(xsize * 3, 4);
521 
522   /* write numbers to header */
523   write_number(header + 2, ysize * row_size + 54);
524   write_number(header + 18, xsize);
525   write_number(header + 22, ysize);
526   write_number(header + 38, ysize * row_size);
527 
528   /* write header to file */
529   written = sizeof(header);
530   apr_file_write(file, header, &written);
531 }
532 
533 /* To COLOR, add the fractional value of SOURCE from fractional indexes
534  * SOURCE_START to SOURCE_END and apply the SCALING_FACTOR.
535  */
536 static void
add_sample(color_t color,color_t * source,double source_start,double source_end,double scaling_factor)537 add_sample(color_t color,
538            color_t *source,
539            double source_start,
540            double source_end,
541            double scaling_factor)
542 {
543   double factor = (source_end - source_start) / scaling_factor;
544 
545   apr_size_t i;
546   for (i = 0; i < sizeof(color_t) / sizeof(*color); ++i)
547     color[i] += (source_end - source_start < 0.5) && source_start > 1.0
548               ? factor * source[(apr_size_t)source_start - 1][i]
549               : factor * source[(apr_size_t)source_start][i];
550 }
551 
552 /* Scale the IN_LEN RGB values from IN to OUT_LEN RGB values in OUT.
553  */
554 static void
scale_line(color_t * out,int out_len,color_t * in,int in_len)555 scale_line(color_t* out,
556            int out_len,
557            color_t *in,
558            int in_len)
559 {
560   double scaling_factor = (double)(in_len) / (double)(out_len);
561 
562   apr_size_t i;
563   memset(out, 0, out_len * sizeof(color_t));
564   for (i = 0; i < out_len; ++i)
565     {
566       color_t color = { 0 };
567 
568       double source_start = i * scaling_factor;
569       double source_end = (i + 1) * scaling_factor;
570 
571       if ((apr_size_t)source_start == (apr_size_t)source_end)
572         {
573           add_sample(color, in, source_start, source_end, scaling_factor);
574         }
575       else
576         {
577           apr_size_t k;
578           apr_size_t first_sample_end = (apr_size_t)source_start + 1;
579           apr_size_t last_sample_start = (apr_size_t)source_end;
580 
581           add_sample(color, in, source_start, first_sample_end, scaling_factor);
582           for (k = first_sample_end; k < last_sample_start; ++k)
583             add_sample(color, in, k, k + 1, scaling_factor);
584 
585           add_sample(color, in, last_sample_start, source_end, scaling_factor);
586         }
587 
588       memcpy(out[i], color, sizeof(color));
589     }
590 }
591 
592 /* Write the cluster read map for all files in INFO as BMP image to FILE.
593  * If MAX_X is not 0, scale all lines to MAX_X pixels.  Use POOL for
594  * allocations.
595  */
596 static void
write_bitmap(apr_array_header_t * info,int max_x,apr_file_t * file,apr_pool_t * pool)597 write_bitmap(apr_array_header_t *info,
598              int max_x,
599              apr_file_t *file,
600              apr_pool_t *pool)
601 {
602   int ysize = info->nelts;
603   int xsize = 0;
604   int x, y;
605   apr_size_t row_size;
606   apr_size_t written;
607   color_t *line, *scaled_line;
608   svn_boolean_t do_scale = max_x > 0;
609 
610   /* xsize = max cluster number */
611   for (y = 0; y < ysize; ++y)
612     if (xsize < APR_ARRAY_IDX(info, y, file_stats_t *)->read_map->nelts)
613       xsize = APR_ARRAY_IDX(info, y, file_stats_t *)->read_map->nelts;
614 
615   /* limit picture dimensions (16k pixels in each direction) */
616   if (xsize >= 0x4000)
617     xsize = 0x3fff;
618   if (ysize >= 0x4000)
619     ysize = 0x3fff;
620   if (max_x == 0)
621     max_x = xsize;
622 
623   /* rows in BMP files must be aligned to 4 bytes */
624   row_size = APR_ALIGN(max_x * sizeof(color_t), 4);
625 
626   /**/
627   line = apr_pcalloc(pool, xsize * sizeof(color_t));
628   scaled_line = apr_pcalloc(pool, row_size);
629 
630   /* write header to file */
631   write_bitmap_header(file, max_x, ysize);
632 
633   /* write all rows */
634   for (y = 0; y < ysize; ++y)
635     {
636       file_stats_t *file_info = APR_ARRAY_IDX(info, y, file_stats_t *);
637       int block_count = file_info->read_map->nelts;
638       for (x = 0; x < xsize; ++x)
639         {
640           color_t color = { 128, 128, 128 };
641           if (x < block_count)
642             {
643               word count = APR_ARRAY_IDX(file_info->read_map, x, word);
644               select_color(color, count);
645             }
646 
647           memcpy(line[x], color, sizeof(color));
648         }
649 
650       scale_line(scaled_line, max_x, line, block_count ? block_count : 1);
651 
652       written = row_size;
653       apr_file_write(file, do_scale ? scaled_line : line, &written);
654     }
655 }
656 
657 /* write a color bar with (roughly) logarithmic scale as BMP image to FILE.
658  */
659 static void
write_scale(apr_file_t * file)660 write_scale(apr_file_t *file)
661 {
662   int x;
663   word value = 0, inc = 1;
664 
665   /* write header to file */
666   write_bitmap_header(file, 64, 1);
667 
668   for (x = 0; x < 64; ++x)
669     {
670       apr_size_t written;
671       byte color[3] = { 128, 128, 128 };
672 
673       select_color(color, value);
674       if (value + (int)inc < 0x10000)
675         {
676           value += inc;
677           if (value >= 8 * inc)
678             inc *= 2;
679         }
680 
681       written = sizeof(color);
682       apr_file_write(file, color, &written);
683     }
684 }
685 
686 /* Write a summary of the I/O ops to stdout.
687  * Use POOL for temporaries.
688  */
689 static void
print_stats(apr_pool_t * pool)690 print_stats(apr_pool_t *pool)
691 {
692   apr_int64_t open_count = 0;
693   apr_int64_t seek_count = 0;
694   apr_int64_t read_count = 0;
695   apr_int64_t read_size = 0;
696   apr_int64_t clusters_read = 0;
697   apr_int64_t unique_clusters_read = 0;
698   apr_int64_t uncached_seek_count = 0;
699   apr_int64_t unnecessary_seek_count = 0;
700   apr_int64_t empty_read_count = 0;
701 
702   apr_hash_index_t *hi;
703   for (hi = apr_hash_first(pool, files); hi; hi = apr_hash_next(hi))
704     {
705       const char *name = NULL;
706       apr_ssize_t len = 0;
707       file_stats_t *file = NULL;
708 
709       apr_hash_this(hi, (const void **)&name, &len, (void**)&file);
710 
711       open_count += file->open_count;
712       seek_count += file->seek_count;
713       read_count += file->read_count;
714       read_size += file->read_size;
715       clusters_read += file->clusters_read;
716       unique_clusters_read += file->unique_clusters_read;
717       uncached_seek_count += file->uncached_seek_count;
718       unnecessary_seek_count += file->unnecessary_seeks;
719       empty_read_count += file->empty_reads;
720     }
721 
722   printf("%20s files\n", svn__i64toa_sep(apr_hash_count(files), ',', pool));
723   printf("%20s files opened\n", svn__i64toa_sep(open_count, ',', pool));
724   printf("%20s seeks\n", svn__i64toa_sep(seek_count, ',', pool));
725   printf("%20s unnecessary seeks\n", svn__i64toa_sep(unnecessary_seek_count, ',', pool));
726   printf("%20s uncached seeks\n", svn__i64toa_sep(uncached_seek_count, ',', pool));
727   printf("%20s reads\n", svn__i64toa_sep(read_count, ',', pool));
728   printf("%20s empty reads\n", svn__i64toa_sep(empty_read_count, ',', pool));
729   printf("%20s unique clusters read\n", svn__i64toa_sep(unique_clusters_read, ',', pool));
730   printf("%20s clusters read\n", svn__i64toa_sep(clusters_read, ',', pool));
731   printf("%20s bytes read\n", svn__i64toa_sep(read_size, ',', pool));
732 }
733 
734 /* Some help output. */
735 static void
print_usage(void)736 print_usage(void)
737 {
738   printf("fsfs-access-map <file>\n\n");
739   printf("Reads strace of some FSFS-based tool from <file>, prints some stats\n");
740   printf("and writes a cluster access map to 'access.bmp' the current folder.\n");
741   printf("Each pixel corresponds to one 64kB cluster and every line to a rev\n");
742   printf("or packed rev file in the repository.  Turquoise and green indicate\n");
743   printf("1 and 2 hits, yellow to read-ish colors for up to 20, shares of\n");
744   printf("for up to 100 and black for > 200 hits.\n\n");
745   printf("A typical strace invocation looks like this:\n");
746   printf("strace -e trace=open,close,read,lseek -o strace.txt svn log ...\n");
747 }
748 
749 /* linear control flow */
main(int argc,const char * argv[])750 int main(int argc, const char *argv[])
751 {
752   apr_pool_t *pool = NULL;
753   apr_file_t *file = NULL;
754 
755   apr_initialize();
756   atexit(apr_terminate);
757 
758   pool = svn_pool_create(NULL);
759   files = apr_hash_make(pool);
760   handles = apr_hash_make(pool);
761 
762   if (argc == 2)
763     apr_file_open(&file, argv[1], APR_READ | APR_BUFFERED, APR_OS_DEFAULT,
764                   pool);
765   if (file == NULL)
766     {
767       print_usage();
768       return 0;
769     }
770   parse_file(file);
771   apr_file_close(file);
772 
773   print_stats(pool);
774 
775   apr_file_open(&file, "access.bmp",
776                 APR_WRITE | APR_CREATE | APR_TRUNCATE | APR_BUFFERED,
777                 APR_OS_DEFAULT, pool);
778   write_bitmap(get_rev_files(pool), 0, file, pool);
779   apr_file_close(file);
780 
781   apr_file_open(&file, "access_scaled.bmp",
782                 APR_WRITE | APR_CREATE | APR_TRUNCATE | APR_BUFFERED,
783                 APR_OS_DEFAULT, pool);
784   write_bitmap(get_rev_files(pool), 1024, file, pool);
785   apr_file_close(file);
786 
787   apr_file_open(&file, "scale.bmp",
788                 APR_WRITE | APR_CREATE | APR_TRUNCATE | APR_BUFFERED,
789                 APR_OS_DEFAULT, pool);
790   write_scale(file);
791   apr_file_close(file);
792 
793   return 0;
794 }
795