blob: 61b9c60a2917818f7c45f433ef9ce1a25983ab6c [file] [log] [blame]
/*
* Copyright (C) 2018 Oracle. All Rights Reserved.
*
* Author: Darrick J. Wong <darrick.wong@oracle.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it would be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "libxfs.h"
#include <stdio.h>
#include <dirent.h>
#include <pthread.h>
#include <sys/statvfs.h>
#include <time.h>
#include "../repair/threads.h"
#include "path.h"
#include "disk.h"
#include "read_verify.h"
#include "xfs_scrub.h"
#include "common.h"
#include "counter.h"
#include "progress.h"
/*
* Progress Tracking
*
* For scrub phases that expect to take a long time, this facility uses
* the threaded counter and some phase/state information to report the
* progress of a particular phase to stdout. Each phase that wants
* progress information needs to set up the tracker with an estimate of
* the work to be done and periodic updates when work items finish. In
* return, the progress tracker will print a pretty progress bar and
* twiddle to a tty, or a raw numeric output compatible with fsck -C.
*/
struct progress_tracker {
FILE *fp;
const char *tag;
struct ptcounter *ptc;
uint64_t max;
unsigned int phase;
int rshift;
int twiddle;
bool isatty;
bool terminate;
pthread_t thread;
/* static state */
pthread_mutex_t lock;
pthread_cond_t wakeup;
};
static struct progress_tracker pt = {
.lock = PTHREAD_MUTEX_INITIALIZER,
.wakeup = PTHREAD_COND_INITIALIZER,
};
/* Add some progress. */
void
progress_add(
uint64_t x)
{
if (pt.fp)
ptcounter_add(pt.ptc, x);
}
static const char twiddles[] = "|/-\\";
static void
progress_report(
uint64_t sum)
{
char buf[81];
int tag_len;
int num_len;
int pbar_len;
int plen;
if (!pt.fp)
return;
if (sum > pt.max)
sum = pt.max;
/* Emulate fsck machine-readable output (phase, cur, max, label) */
if (!pt.isatty) {
snprintf(buf, sizeof(buf), _("%u %"PRIu64" %"PRIu64" %s"),
pt.phase, sum, pt.max, pt.tag);
fprintf(pt.fp, "%s\n", buf);
fflush(pt.fp);
return;
}
/* Interactive twiddle progress bar. */
if (debug) {
num_len = snprintf(buf, sizeof(buf),
"%c %"PRIu64"/%"PRIu64" (%.1f%%)",
twiddles[pt.twiddle],
sum >> pt.rshift,
pt.max >> pt.rshift,
100.0 * sum / pt.max);
} else {
num_len = snprintf(buf, sizeof(buf),
"%c (%.1f%%)",
twiddles[pt.twiddle],
100.0 * sum / pt.max);
}
memmove(buf + sizeof(buf) - (num_len + 1), buf, num_len + 1);
tag_len = snprintf(buf, sizeof(buf), _("Phase %u: |"), pt.phase);
pbar_len = sizeof(buf) - (num_len + 1 + tag_len);
plen = (int)((double)pbar_len * sum / pt.max);
memset(buf + tag_len, '=', plen);
memset(buf + tag_len + plen, ' ', pbar_len - plen);
pt.twiddle = (pt.twiddle + 1) % 4;
fprintf(pt.fp, "%c%s\r%c", START_IGNORE, buf, END_IGNORE);
fflush(pt.fp);
}
#define NSEC_PER_SEC (1000000000)
static void *
progress_report_thread(void *arg)
{
struct timespec abstime;
int ret;
pthread_mutex_lock(&pt.lock);
while (1) {
/* Every half second. */
ret = clock_gettime(CLOCK_REALTIME, &abstime);
if (ret)
break;
abstime.tv_nsec += NSEC_PER_SEC / 2;
if (abstime.tv_nsec > NSEC_PER_SEC) {
abstime.tv_sec++;
abstime.tv_nsec -= NSEC_PER_SEC;
}
pthread_cond_timedwait(&pt.wakeup, &pt.lock, &abstime);
if (pt.terminate)
break;
progress_report(ptcounter_value(pt.ptc));
}
pthread_mutex_unlock(&pt.lock);
return NULL;
}
/* End a phase of progress reporting. */
void
progress_end_phase(void)
{
if (!pt.fp)
return;
pthread_mutex_lock(&pt.lock);
pt.terminate = true;
pthread_mutex_unlock(&pt.lock);
pthread_cond_broadcast(&pt.wakeup);
pthread_join(pt.thread, NULL);
progress_report(pt.max);
ptcounter_free(pt.ptc);
pt.max = 0;
pt.ptc = NULL;
if (pt.fp) {
fprintf(pt.fp, CLEAR_EOL);
fflush(pt.fp);
}
pt.fp = NULL;
}
/* Set ourselves up to report progress. */
bool
progress_init_phase(
struct scrub_ctx *ctx,
FILE *fp,
unsigned int phase,
uint64_t max,
int rshift,
unsigned int nr_threads)
{
int ret;
assert(pt.fp == NULL);
if (fp == NULL || max == 0) {
pt.fp = NULL;
return true;
}
pt.fp = fp;
pt.isatty = isatty(fileno(fp));
pt.tag = ctx->mntpoint;
pt.max = max;
pt.phase = phase;
pt.rshift = rshift;
pt.twiddle = 0;
pt.terminate = false;
pt.ptc = ptcounter_init(nr_threads);
if (!pt.ptc)
goto out_max;
ret = pthread_create(&pt.thread, NULL, progress_report_thread, NULL);
if (ret)
goto out_ptcounter;
return true;
out_ptcounter:
ptcounter_free(pt.ptc);
pt.ptc = NULL;
out_max:
pt.max = 0;
pt.fp = NULL;
return false;
}