#include "svn_pools.h"
#include "svn_string.h"
#include "svn_io.h"
#include "private/svn_string_private.h"
typedef struct file_stats_t
{
const char *name;
apr_int64_t size;
apr_int64_t rev_num;
apr_int64_t open_count;
apr_int64_t seek_count;
apr_int64_t uncached_seek_count;
apr_int64_t unnecessary_seeks;
apr_int64_t read_count;
apr_int64_t empty_reads;
apr_int64_t read_size;
apr_int64_t clusters_read;
apr_int64_t unique_clusters_read;
apr_array_header_t *read_map;
} file_stats_t;
typedef struct handle_info_t
{
file_stats_t *file;
apr_int64_t last_read_start;
apr_int64_t last_read_size;
apr_int64_t read_count;
} handle_info_t;
typedef unsigned char byte;
typedef unsigned short word;
typedef byte color_t[3];
static apr_hash_t *files = NULL;
static apr_hash_t *handles = NULL;
static apr_int64_t cluster_size = 64 * 1024;
static void
store_read_info(handle_info_t *handle_info)
{
if (handle_info->last_read_size)
{
apr_size_t i;
apr_size_t first_cluster
= (apr_size_t)(handle_info->last_read_start / cluster_size);
apr_size_t last_cluster
= (apr_size_t)(( handle_info->last_read_start
+ handle_info->last_read_size
- 1) / cluster_size);
while (handle_info->file->read_map->nelts <= last_cluster)
APR_ARRAY_PUSH(handle_info->file->read_map, word) = 0;
handle_info->file->clusters_read += last_cluster - first_cluster + 1;
for (i = first_cluster; i <= last_cluster; ++i)
{
word *count = &APR_ARRAY_IDX(handle_info->file->read_map, i, word);
if (*count == 0)
handle_info->file->unique_clusters_read++;
if (*count < 0xffff)
++*count;
}
}
else if (handle_info->read_count == 0)
{
handle_info->file->unnecessary_seeks++;
}
}
static void
open_file(const char *name, int handle)
{
file_stats_t *file = apr_hash_get(files, name, APR_HASH_KEY_STRING);
handle_info_t *handle_info = apr_hash_get(handles, &handle, sizeof(handle));
if (!file)
{
apr_pool_t *pool = apr_hash_pool_get(files);
apr_pool_t *subpool = svn_pool_create(pool);
apr_file_t *apr_file = NULL;
apr_finfo_t finfo = { 0 };
int cluster_count = 0;
apr_file_open(&apr_file, name,
APR_READ | APR_BUFFERED, APR_OS_DEFAULT, subpool);
if (apr_file)
apr_file_info_get(&finfo, APR_FINFO_SIZE, apr_file);
svn_pool_destroy(subpool);
file = apr_pcalloc(pool, sizeof(*file));
file->name = apr_pstrdup(pool, name);
file->size = finfo.size;
cluster_count = (int)(1 + (file->size - 1) / cluster_size);
file->read_map = apr_array_make(pool, file->size
? cluster_count
: 1, sizeof(word));
while (file->read_map->nelts < cluster_count)
APR_ARRAY_PUSH(file->read_map, byte) = 0;
if (strstr(name, "/db/revs/") != NULL && strstr(name, "manifest") == NULL)
if (strstr(name, ".pack/pack") != NULL)
file->rev_num = SVN_STR_TO_REV(strstr(name, "/db/revs/") + 9);
else
file->rev_num = SVN_STR_TO_REV(strrchr(name, '/') + 1);
else
file->rev_num = -1;
if (file->rev_num >= 0)
{
const char *suffix = name + strlen(name) - 4;
if (strcmp(suffix, ".l2p") == 0 || strcmp(suffix, ".p2l") == 0)
file->rev_num = -1;
}
apr_hash_set(files, file->name, APR_HASH_KEY_STRING, file);
}
file->open_count++;
if (!handle_info)
{
apr_pool_t *pool = apr_hash_pool_get(handles);
int *key = apr_palloc(pool, sizeof(*key));
*key = handle;
handle_info = apr_pcalloc(pool, sizeof(*handle_info));
apr_hash_set(handles, key, sizeof(*key), handle_info);
}
handle_info->file = file;
handle_info->last_read_start = 0;
handle_info->last_read_size = 0;
}
static void
read_file(int handle, apr_int64_t count)
{
handle_info_t *handle_info = apr_hash_get(handles, &handle, sizeof(handle));
if (handle_info)
{
handle_info->read_count++;
handle_info->last_read_size += count;
handle_info->file->read_count++;
handle_info->file->read_size += count;
if (count == 0)
handle_info->file->empty_reads++;
}
}
static void
seek_file(int handle, apr_int64_t location)
{
handle_info_t *handle_info = apr_hash_get(handles, &handle, sizeof(handle));
if (handle_info)
{
apr_size_t cluster = (apr_size_t)(location / cluster_size);
store_read_info(handle_info);
handle_info->last_read_size = 0;
handle_info->last_read_start = location;
handle_info->read_count = 0;
handle_info->file->seek_count++;
if ( handle_info->file->read_map->nelts <= cluster
|| APR_ARRAY_IDX(handle_info->file->read_map, cluster, word) == 0)
handle_info->file->uncached_seek_count++;
}
}
static void
close_file(int handle)
{
handle_info_t *handle_info = apr_hash_get(handles, &handle, sizeof(handle));
if (handle_info)
store_read_info(handle_info);
}
static void
parse_line(svn_stringbuf_t *line)
{
char *func_end = strchr(line->data, '(');
char *return_value = strrchr(line->data, ' ');
char *first_param_end;
apr_int64_t func_return = 0;
char *func_start = strchr(line->data, ' ');
if (func_end == NULL || return_value == NULL)
return;
if (func_start == NULL || func_start > func_end)
func_start = line->data;
else
while(*func_start == ' ')
func_start++;
first_param_end = strchr(func_end, ',');
if (first_param_end == NULL)
first_param_end = strchr(func_end, ')');
if (first_param_end == NULL)
return;
*func_end++ = 0;
*first_param_end = 0;
++return_value;
svn_error_clear(svn_cstring_atoi64(&func_return, return_value));
if (strcmp(func_start, "open") == 0)
{
*func_end++ = 0;
*--first_param_end = 0;
open_file(func_end, (int)func_return);
}
else if (strcmp(func_start, "read") == 0)
read_file(atoi(func_end), func_return);
else if (strcmp(func_start, "lseek") == 0)
seek_file(atoi(func_end), func_return);
else if (strcmp(func_start, "close") == 0)
close_file(atoi(func_end));
}
static void
parse_file(apr_file_t *file)
{
apr_pool_t *pool = svn_pool_create(NULL);
apr_pool_t *iterpool = svn_pool_create(pool);
svn_stringbuf_t *line = svn_stringbuf_create_ensure(4096, pool);
do
{
svn_error_t *err = NULL;
line->len = line->blocksize-1;
err = svn_io_read_length_line(file, line->data, &line->len, iterpool);
svn_error_clear(err);
if (err)
break;
parse_line(line);
svn_pool_clear(iterpool);
}
while (line->len > 0);
}
static int
compare_files(file_stats_t **lhs, file_stats_t **rhs)
{
return (*lhs)->rev_num < (*rhs)->rev_num;
}
static apr_array_header_t *
get_rev_files(apr_pool_t *pool)
{
apr_hash_index_t *hi;
apr_array_header_t *result = apr_array_make(pool,
apr_hash_count(files),
sizeof(file_stats_t *));
for (hi = apr_hash_first(pool, files); hi; hi = apr_hash_next(hi))
{
const char *name = NULL;
apr_ssize_t len = 0;
file_stats_t *file = NULL;
apr_hash_this(hi, (const void **)&name, &len, (void**)&file);
if (file->rev_num >= 0)
APR_ARRAY_PUSH(result, file_stats_t *) = file;
}
qsort(result->elts, result->nelts, result->elt_size,
(int (*)(const void *, const void *))compare_files);
return result;
}
static void
write_number(byte *dest, int value)
{
while (value)
{
*dest = (byte)(value % 256);
value /= 256;
++dest;
}
}
static int
interpolate(int y0, int x0, int y1, int x1, int x)
{
return y0 + ((y1 - y0) * (x - x0)) / (x1 - x0);
}
static void
select_color(byte color[3], word value)
{
enum { COLOR_COUNT = 10 };
word table[COLOR_COUNT][4] =
{
{ 0, 255, 255, 255 },
{ 1, 64, 128, 0 },
{ 2, 0, 128, 0 },
{ 8, 0, 192, 192 },
{ 64, 0, 0, 192 },
{ 256, 64, 32, 230 },
{ 512, 192, 0, 128 },
{ 1024, 96, 32, 96 },
{ 4096, 32, 16, 32 },
{ 65535, 0, 0, 0 }
};
int i;
for (i = 0; i < COLOR_COUNT; ++i)
if (table[i][0] >= value)
break;
if (table[i][0] == value)
{
color[0] = (byte)table[i][1];
color[1] = (byte)table[i][2];
color[2] = (byte)table[i][3];
}
else
{
color[0] = (byte)interpolate(table[i-1][1], table[i-1][0],
table[i][1], table[i][0],
value);
color[1] = (byte)interpolate(table[i-1][2], table[i-1][0],
table[i][2], table[i][0],
value);
color[2] = (byte)interpolate(table[i-1][3], table[i-1][0],
table[i][3], table[i][0],
value);
}
}
static void
write_bitmap_header(apr_file_t *file, int xsize, int ysize)
{
byte header[54] =
{
'B', 'M',
0, 0, 0, 0,
0, 0, 0, 0,
54, 0, 0, 0,
40, 0, 0, 0,
0, 0, 0, 0,
0, 0, 0, 0,
1, 0,
24, 0,
0, 0, 0, 0,
0, 0, 0, 0,
0xe8, 3, 0, 0,
0xe8, 3, 0, 0,
0, 0, 0, 0,
0, 0, 0, 0
};
apr_size_t written;
int row_size = APR_ALIGN(xsize * 3, 4);
write_number(header + 2, ysize * row_size + 54);
write_number(header + 18, xsize);
write_number(header + 22, ysize);
write_number(header + 38, ysize * row_size);
written = sizeof(header);
apr_file_write(file, header, &written);
}
static void
add_sample(color_t color,
color_t *source,
double source_start,
double source_end,
double scaling_factor)
{
double factor = (source_end - source_start) / scaling_factor;
apr_size_t i;
for (i = 0; i < sizeof(color_t) / sizeof(*color); ++i)
color[i] += (source_end - source_start < 0.5) && source_start > 1.0
? factor * source[(apr_size_t)source_start - 1][i]
: factor * source[(apr_size_t)source_start][i];
}
static void
scale_line(color_t* out,
int out_len,
color_t *in,
int in_len)
{
double scaling_factor = (double)(in_len) / (double)(out_len);
apr_size_t i;
memset(out, 0, out_len * sizeof(color_t));
for (i = 0; i < out_len; ++i)
{
color_t color = { 0 };
double source_start = i * scaling_factor;
double source_end = (i + 1) * scaling_factor;
if ((apr_size_t)source_start == (apr_size_t)source_end)
{
add_sample(color, in, source_start, source_end, scaling_factor);
}
else
{
apr_size_t k;
apr_size_t first_sample_end = (apr_size_t)source_start + 1;
apr_size_t last_sample_start = (apr_size_t)source_end;
add_sample(color, in, source_start, first_sample_end, scaling_factor);
for (k = first_sample_end; k < last_sample_start; ++k)
add_sample(color, in, k, k + 1, scaling_factor);
add_sample(color, in, last_sample_start, source_end, scaling_factor);
}
memcpy(out[i], color, sizeof(color));
}
}
static void
write_bitmap(apr_array_header_t *info,
int max_x,
apr_file_t *file,
apr_pool_t *pool)
{
int ysize = info->nelts;
int xsize = 0;
int x, y;
apr_size_t row_size;
apr_size_t written;
color_t *line, *scaled_line;
svn_boolean_t do_scale = max_x > 0;
for (y = 0; y < ysize; ++y)
if (xsize < APR_ARRAY_IDX(info, y, file_stats_t *)->read_map->nelts)
xsize = APR_ARRAY_IDX(info, y, file_stats_t *)->read_map->nelts;
if (xsize >= 0x4000)
xsize = 0x3fff;
if (ysize >= 0x4000)
ysize = 0x3fff;
if (max_x == 0)
max_x = xsize;
row_size = APR_ALIGN(max_x * sizeof(color_t), 4);
line = apr_pcalloc(pool, xsize * sizeof(color_t));
scaled_line = apr_pcalloc(pool, row_size);
write_bitmap_header(file, max_x, ysize);
for (y = 0; y < ysize; ++y)
{
file_stats_t *file_info = APR_ARRAY_IDX(info, y, file_stats_t *);
int block_count = file_info->read_map->nelts;
for (x = 0; x < xsize; ++x)
{
color_t color = { 128, 128, 128 };
if (x < block_count)
{
word count = APR_ARRAY_IDX(file_info->read_map, x, word);
select_color(color, count);
}
memcpy(line[x], color, sizeof(color));
}
scale_line(scaled_line, max_x, line, block_count ? block_count : 1);
written = row_size;
apr_file_write(file, do_scale ? scaled_line : line, &written);
}
}
static void
write_scale(apr_file_t *file)
{
int x;
word value = 0, inc = 1;
write_bitmap_header(file, 64, 1);
for (x = 0; x < 64; ++x)
{
apr_size_t written;
byte color[3] = { 128, 128, 128 };
select_color(color, value);
if (value + (int)inc < 0x10000)
{
value += inc;
if (value >= 8 * inc)
inc *= 2;
}
written = sizeof(color);
apr_file_write(file, color, &written);
}
}
static void
print_stats(apr_pool_t *pool)
{
apr_int64_t open_count = 0;
apr_int64_t seek_count = 0;
apr_int64_t read_count = 0;
apr_int64_t read_size = 0;
apr_int64_t clusters_read = 0;
apr_int64_t unique_clusters_read = 0;
apr_int64_t uncached_seek_count = 0;
apr_int64_t unnecessary_seek_count = 0;
apr_int64_t empty_read_count = 0;
apr_hash_index_t *hi;
for (hi = apr_hash_first(pool, files); hi; hi = apr_hash_next(hi))
{
const char *name = NULL;
apr_ssize_t len = 0;
file_stats_t *file = NULL;
apr_hash_this(hi, (const void **)&name, &len, (void**)&file);
open_count += file->open_count;
seek_count += file->seek_count;
read_count += file->read_count;
read_size += file->read_size;
clusters_read += file->clusters_read;
unique_clusters_read += file->unique_clusters_read;
uncached_seek_count += file->uncached_seek_count;
unnecessary_seek_count += file->unnecessary_seeks;
empty_read_count += file->empty_reads;
}
printf("%20s files\n", svn__i64toa_sep(apr_hash_count(files), ',', pool));
printf("%20s files opened\n", svn__i64toa_sep(open_count, ',', pool));
printf("%20s seeks\n", svn__i64toa_sep(seek_count, ',', pool));
printf("%20s unnecessary seeks\n", svn__i64toa_sep(unnecessary_seek_count, ',', pool));
printf("%20s uncached seeks\n", svn__i64toa_sep(uncached_seek_count, ',', pool));
printf("%20s reads\n", svn__i64toa_sep(read_count, ',', pool));
printf("%20s empty reads\n", svn__i64toa_sep(empty_read_count, ',', pool));
printf("%20s unique clusters read\n", svn__i64toa_sep(unique_clusters_read, ',', pool));
printf("%20s clusters read\n", svn__i64toa_sep(clusters_read, ',', pool));
printf("%20s bytes read\n", svn__i64toa_sep(read_size, ',', pool));
}
static void
print_usage(void)
{
printf("fsfs-access-map <file>\n\n");
printf("Reads strace of some FSFS-based tool from <file>, prints some stats\n");
printf("and writes a cluster access map to 'access.bmp' the current folder.\n");
printf("Each pixel corresponds to one 64kB cluster and every line to a rev\n");
printf("or packed rev file in the repository. Turquoise and green indicate\n");
printf("1 and 2 hits, yellow to read-ish colors for up to 20, shares of\n");
printf("for up to 100 and black for > 200 hits.\n\n");
printf("A typical strace invocation looks like this:\n");
printf("strace -e trace=open,close,read,lseek -o strace.txt svn log ...\n");
}
int main(int argc, const char *argv[])
{
apr_pool_t *pool = NULL;
apr_file_t *file = NULL;
apr_initialize();
atexit(apr_terminate);
pool = svn_pool_create(NULL);
files = apr_hash_make(pool);
handles = apr_hash_make(pool);
if (argc == 2)
apr_file_open(&file, argv[1], APR_READ | APR_BUFFERED, APR_OS_DEFAULT,
pool);
if (file == NULL)
{
print_usage();
return 0;
}
parse_file(file);
apr_file_close(file);
print_stats(pool);
apr_file_open(&file, "access.bmp",
APR_WRITE | APR_CREATE | APR_TRUNCATE | APR_BUFFERED,
APR_OS_DEFAULT, pool);
write_bitmap(get_rev_files(pool), 0, file, pool);
apr_file_close(file);
apr_file_open(&file, "access_scaled.bmp",
APR_WRITE | APR_CREATE | APR_TRUNCATE | APR_BUFFERED,
APR_OS_DEFAULT, pool);
write_bitmap(get_rev_files(pool), 1024, file, pool);
apr_file_close(file);
apr_file_open(&file, "scale.bmp",
APR_WRITE | APR_CREATE | APR_TRUNCATE | APR_BUFFERED,
APR_OS_DEFAULT, pool);
write_scale(file);
apr_file_close(file);
return 0;
}