From 3e2d805d088ce0ab79aea44c0e0603f50ba1390d Mon Sep 17 00:00:00 2001 From: Andrea Mazzoleni Date: Mon, 13 Oct 2014 23:47:25 +0200 Subject: [PATCH] Extends the status command to show free space. --- HISTORY | 2 + TODO | 4 -- cmdline/mingw.c | 62 +++++++++++++++++++++++-- cmdline/portable.h | 2 +- cmdline/scan.c | 2 +- cmdline/status.c | 111 ++++++++++++++++++++++++++++++++++++++++----- cmdline/unix.c | 57 +++++++++++++++++------ 7 files changed, 205 insertions(+), 35 deletions(-) diff --git a/HISTORY b/HISTORY index 5ff92dc0..3c31f6a9 100644 --- a/HISTORY +++ b/HISTORY @@ -5,6 +5,8 @@ SnapRAID HISTORY =========== * Adds AVX2 support to improve parity generation speed. * A lot faster directory listing for Windows. + * More detailed 'status' report with single disk stats and + free space available. * Prints the time spent waiting for each disk also in 'scrub'. * The CPU usage, speed and ETA estimations are now based on the last 100 seconds rather than from the start. diff --git a/TODO b/TODO index bae8a204..abd4ff0f 100644 --- a/TODO +++ b/TODO @@ -5,10 +5,6 @@ This is the list of TODO items for SnapRAID. - Next -* In status prints the allocated parity in each data disk. -In case, also takes into account the extra available space -in the parity disks. - * Avoid to do optimized move operation if more files with same timestamp/size are present. diff --git a/cmdline/mingw.c b/cmdline/mingw.c index 266454d2..fd53db34 100644 --- a/cmdline/mingw.c +++ b/cmdline/mingw.c @@ -1421,12 +1421,66 @@ int filephy(const char* file, uint64_t size, uint64_t* physical) return 0; } -int fsinfo(const char* path, int* has_persistent_inode) +int fsinfo(const char* path, int* has_persistent_inode, uint64_t* free_space) { - (void)path; - /* all FAT/exFAT/NTFS when managed from Windows have persistent inodes */ - *has_persistent_inode = 1; + if (has_persistent_inode) + *has_persistent_inode = 1; + + if (free_space) { + ULARGE_INTEGER total_free_bytes; + DWORD attr; + char dir[PATH_MAX]; + + if (strlen(path) + 1 > sizeof(dir)) { + windows_errno(ERROR_BUFFER_OVERFLOW); + return -1; + } + + strcpy(dir, path); + + /* get the file attributes */ + attr = GetFileAttributesW(convert(dir)); + if (attr == INVALID_FILE_ATTRIBUTES) { + DWORD error = GetLastError(); + + if (error != ERROR_FILE_NOT_FOUND) { + windows_errno(error); + return -1; + } + + /* if it doesn't exist, we assume a file */ + /* and we check for the containing dir */ + attr = 0; + } + + /* if it's not a directory, truncate the file name */ + if ((attr & FILE_ATTRIBUTE_DIRECTORY) == 0) { + char* slash = strrchr(dir, '/'); + + /** + * Cut the file name, but leave the last slash. + * + * This is done because a MSDN comment about using of UNC paths. + * + * MSDN 'GetDiskFreeSpaceEx function' + * http://msdn.microsoft.com/en-us/library/windows/desktop/aa364937%28v=vs.85%29.aspx + * If this parameter is a UNC name, it must include a trailing backslash, + * for example, "\\MyServer\MyShare\". + */ + if (slash) + slash[1] = 0; + } + + /* get the free space of the directory */ + /* note that it must be a directory */ + if (!GetDiskFreeSpaceExW(convert(dir), 0, 0, &total_free_bytes)) { + windows_errno(GetLastError()); + return -1; + } + + *free_space = total_free_bytes.QuadPart; + } return 0; } diff --git a/cmdline/portable.h b/cmdline/portable.h index bb17ccee..35388903 100644 --- a/cmdline/portable.h +++ b/cmdline/portable.h @@ -243,7 +243,7 @@ int filephy(const char* path, uint64_t size, uint64_t* physical); * Checks if the underline filesystem support persistent inodes. * Returns -1 on error, 0 on success. */ -int fsinfo(const char* path, int* has_persistent_inode); +int fsinfo(const char* path, int* has_persistent_inode, uint64_t* free_space); /** * Get the tick counter value. diff --git a/cmdline/scan.c b/cmdline/scan.c index cba0daaa..c63d9bab 100644 --- a/cmdline/scan.c +++ b/cmdline/scan.c @@ -1352,7 +1352,7 @@ void state_scan(struct snapraid_state* state, int output) printf("Scanning disk %s...\n", disk->name); /* check if the disk supports persistent inodes */ - ret = fsinfo(disk->dir, &has_persistent_inode); + ret = fsinfo(disk->dir, &has_persistent_inode, 0); if (ret < 0) { /* LCOV_EXCL_START */ fprintf(stderr, "Error accessing disk '%s' to get filesystem info. %s.\n", disk->dir, strerror(errno)); diff --git a/cmdline/status.c b/cmdline/status.c index 9a36f391..45e12a4c 100644 --- a/cmdline/status.c +++ b/cmdline/status.c @@ -35,6 +35,14 @@ unsigned day_ago(time_t time, time_t now) #define GRAPH_COLUMN 70 #define GRAPH_ROW 15 +static unsigned perc(uint64_t part, uint64_t total) +{ + if (!total) + return 0; + + return (unsigned)(part * 100 / total); +} + int state_status(struct snapraid_state* state) { block_off_t blockmax; @@ -55,25 +63,60 @@ int state_status(struct snapraid_state* state) unsigned file_fragmented; unsigned extra_fragment; uint64_t file_size; + uint64_t file_block_count; + uint64_t file_block_free; + uint64_t base = 1024 * 1024 * (uint64_t)1024; + block_off_t parity_block_free; unsigned unsynced_blocks; + uint64_t free_space; + uint64_t wasted; + int ret; /* get the present time */ now = time(0); blockmax = parity_size(state); + fprintf(stdlog, "summary:block_size:%u\n", state->block_size); + fprintf(stdlog, "summary:parity_block_count:%u\n", blockmax); + + /* get the free space on the first parity disk */ + ret = fsinfo(state->parity_path[0], 0, &free_space); + if (ret < 0) { + /* LCOV_EXCL_START */ + fprintf(stderr, "Error accessing file '%s' to get filesystem info. %s.\n", state->parity_path[0], strerror(errno)); + exit(EXIT_FAILURE); + /* LCOV_EXCL_STOP */ + } + + parity_block_free = free_space / state->block_size; + + fprintf(stdlog, "summary:parity_block_free:%s:%u\n", lev_config_name(0), parity_block_free); + + printf("\n"); + printf(" Files Fragmented Excess Wasted Used Free Use Name\n"); + printf(" Files Fragments GiB GiB GiB \n"); + /* count fragments */ file_count = 0; file_size = 0; + file_block_count = 0; + file_block_free = 0; file_fragmented = 0; extra_fragment = 0; for (node_disk = state->disklist; node_disk != 0; node_disk = node_disk->next) { struct snapraid_disk* disk = node_disk->data; tommy_node* node; + block_off_t i; unsigned disk_file_count = 0; unsigned disk_file_fragmented = 0; unsigned disk_extra_fragment = 0; + block_off_t disk_block_count = 0; uint64_t disk_file_size = 0; + block_off_t disk_block_free; + block_off_t disk_block_max_by_space; + block_off_t disk_block_max_by_parity; + block_off_t disk_block_max; /* for each file in the disk */ node = disk->filelist; @@ -104,32 +147,76 @@ int state_status(struct snapraid_state* state) ++file_fragmented; ++disk_file_fragmented; } + + disk_block_count += file->blockmax; } /* count files */ ++file_count; ++disk_file_count; file_size += file->size; + file_block_count += file->blockmax; disk_file_size += file->size; } + /* get the free space on the disk */ + ret = fsinfo(disk->dir, 0, &free_space); + if (ret < 0) { + /* LCOV_EXCL_START */ + fprintf(stderr, "Error accessing disk '%s' to get filesystem info. %s.\n", disk->dir, strerror(errno)); + exit(EXIT_FAILURE); + /* LCOV_EXCL_STOP */ + } + + disk_block_free = free_space / state->block_size; + + disk_block_max_by_space = disk_block_count + disk_block_free; + disk_block_max_by_parity = blockmax + parity_block_free; + + /* the maximum usable space in a disk is limited by both */ + /* the disk size and by the parity size */ + disk_block_max = disk_block_max_by_space < disk_block_max_by_parity + ? disk_block_max_by_space : disk_block_max_by_parity; + + file_block_free += disk_block_max - disk_block_count; + + printf("%8u", disk_file_count); + printf("%8u", disk_file_fragmented); + printf("%8u", disk_extra_fragment); + + wasted = disk_block_count * (uint64_t)state->block_size - disk_file_size; + printf("%6" PRIu64 ".%01" PRIu64, wasted / base, (wasted * 10 / base) % 10); + printf("%8" PRIu64, disk_file_size / base); + printf("%8" PRIu64, (disk_block_max - disk_block_count) * (uint64_t)state->block_size / base); + printf(" %3u%%", perc(disk_block_count, disk_block_max)); + printf(" %s\n", disk->name); + fprintf(stdlog, "summary:disk_file_count:%s:%u\n", disk->name, disk_file_count); + fprintf(stdlog, "summary:disk_block_count:%s:%u\n", disk->name, disk_block_count); fprintf(stdlog, "summary:disk_fragmented_file_count:%s:%u\n", disk->name, disk_file_fragmented); fprintf(stdlog, "summary:disk_excess_fragment_count:%s:%u\n", disk->name, disk_extra_fragment); fprintf(stdlog, "summary:disk_file_size:%s:%" PRIu64 "\n", disk->name, disk_file_size); + fprintf(stdlog, "summary:disk_block_free:%s:%u\n", disk->name, disk_block_free); + fprintf(stdlog, "summary:disk_block_max_by_space:%s:%u\n", disk->name, disk_block_max_by_space); + fprintf(stdlog, "summary:disk_block_max_by_parity:%s:%u\n", disk->name, disk_block_max_by_parity); + fprintf(stdlog, "summary:disk_block_max:%s:%u\n", disk->name, disk_block_max); } - printf("\n"); - - printf("Files: %u\n", file_count); - printf("Fragmented files: %u\n", file_fragmented); - printf("Excess fragments: %u\n", extra_fragment); - printf("Files size: %" PRIu64 " GiB\n", file_size / (1024 * 1024 * 1024) ); - printf("Parity size: %" PRIu64 " GiB\n", blockmax * (uint64_t)state->block_size / (1024 * 1024 * 1024) ); + /* totals */ + printf(" --------------------------------------------------------------------------\n"); + printf("%8u", file_count); + printf("%8u", file_fragmented); + printf("%8u", extra_fragment); + wasted = file_block_count * state->block_size - file_size; + printf("%6" PRIu64 ".%01" PRIu64, wasted / base, (wasted * 10 / base) % 10); + printf("%8" PRIu64, file_size / base); + printf("%8" PRIu64, file_block_free * state->block_size / base); + printf(" %3u%%", perc(file_block_count, file_block_count + file_block_free)); printf("\n"); fprintf(stdlog, "summary:file_count:%u\n", file_count); + fprintf(stdlog, "summary:file_block_count:%" PRIu64 "\n", file_block_count); fprintf(stdlog, "summary:fragmented_file_count:%u\n", file_fragmented); fprintf(stdlog, "summary:excess_fragment_count:%u\n", extra_fragment); fprintf(stdlog, "summary:file_size:%" PRIu64 "\n", file_size); @@ -242,16 +329,18 @@ int state_status(struct snapraid_state* state) bar[i] = step; } + printf("\n\n"); + /* print the graph */ for (y = 0; y < GRAPH_ROW; ++y) { if (y == 0) - printf("%3u%% ", barmax * 100 / count); + printf("%3u%%|", barmax * 100 / count); else if (y == GRAPH_ROW - 1) - printf(" 0%% "); + printf(" 0%%|"); else if (y == GRAPH_ROW / 2) - printf("%3u%% ", barmax * 50 / count); + printf("%3u%%|", barmax * 50 / count); else - printf(" "); + printf(" |"); for (x = 0; x < GRAPH_COLUMN; ++x) { unsigned pivot = barmax * (GRAPH_ROW - y) / GRAPH_ROW; /* if it's the baseline */ diff --git a/cmdline/unix.c b/cmdline/unix.c index 7ac6b628..8bfe42ed 100644 --- a/cmdline/unix.c +++ b/cmdline/unix.c @@ -205,31 +205,60 @@ int filephy(const char* path, uint64_t size, uint64_t* physical) return 0; } -int fsinfo(const char* path, int* has_persistent_inode) +int fsinfo(const char* path, int* has_persistent_inode, uint64_t* free_space) { #if HAVE_STATFS && HAVE_STRUCT_STATFS_F_TYPE struct statfs st; - if (statfs(path, &st) != 0) - return -1; + if (statfs(path, &st) != 0) { + char dir[PATH_MAX]; + char* slash; + + if (errno != ENOENT) { + return -1; + } + + /* if it doesn't exist, we assume a file */ + /* and we check for the containing dir */ + if (strlen(path) + 1 > sizeof(dir)) { + errno = ENAMETOOLONG; + return -1; + } + + strcpy(dir, path); + + slash = strrchr(dir, '/'); + if (!slash) + return -1; + + *slash = 0; + if (statfs(dir, &st) != 0) + return -1; + } /* to get the fs type check "man stat" or "stat -f -t FILE" */ + if (has_persistent_inode) + switch (st.f_type) { + case 0x65735546 : /* FUSE, "fuseblk" in the stat command */ + case 0x4d44 : /* VFAT, "msdos" in the stat command */ + *has_persistent_inode = 0; + break; + default : + /* by default assume yes */ + *has_persistent_inode = 1; + break; + } - switch (st.f_type) { - case 0x65735546 : /* FUSE, "fuseblk" in the stat command */ - case 0x4d44 : /* VFAT, "msdos" in the stat command */ - *has_persistent_inode = 0; - break; - default : - /* by default assume yes */ - *has_persistent_inode = 1; - break; - } + if (free_space) + *free_space = st.f_bsize * (uint64_t)st.f_bfree; #else (void)path; /* by default assume yes */ - *has_persistent_inode = 1; + if (has_persistent_inode) + *has_persistent_inode = 1; + if (free_space) + *free_space = 0; #endif return 0;