Skip to content

Commit

Permalink
Colorize zpool status output
Browse files Browse the repository at this point in the history
If the ZFS_COLOR env variable is set, then use ANSI color
output in zpool status:

- Column headers are bold
- Degraded or offline pools/vdevs are yellow
- Non-zero error counters and faulted vdevs/pools are red
- The 'status:' and 'action:' sections are yellow if they're
  displaying a warning.

Signed-off-by: Tony Hutter <[email protected]>
  • Loading branch information
tonyhutter committed Sep 18, 2019
1 parent 94bcf6f commit be9e1b6
Show file tree
Hide file tree
Showing 11 changed files with 1,079 additions and 136 deletions.
386 changes: 253 additions & 133 deletions cmd/zpool/zpool_main.c

Large diffs are not rendered by default.

37 changes: 37 additions & 0 deletions config/ax_require_defined.m4
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# ===========================================================================
# https://www.gnu.org/software/autoconf-archive/ax_require_defined.html
# ===========================================================================
#
# SYNOPSIS
#
# AX_REQUIRE_DEFINED(MACRO)
#
# DESCRIPTION
#
# AX_REQUIRE_DEFINED is a simple helper for making sure other macros have
# been defined and thus are available for use. This avoids random issues
# where a macro isn't expanded. Instead the configure script emits a
# non-fatal:
#
# ./configure: line 1673: AX_CFLAGS_WARN_ALL: command not found
#
# It's like AC_REQUIRE except it doesn't expand the required macro.
#
# Here's an example:
#
# AX_REQUIRE_DEFINED([AX_CHECK_LINK_FLAG])
#
# LICENSE
#
# Copyright (c) 2014 Mike Frysinger <[email protected]>
#
# Copying and distribution of this file, with or without modification, are
# permitted in any medium without royalty provided the copyright notice
# and this notice are preserved. This file is offered as-is, without any
# warranty.

#serial 2

AC_DEFUN([AX_REQUIRE_DEFINED], [dnl
m4_ifndef([$1], [m4_fatal([macro ]$1[ is not defined; is a m4 file missing?])])
])dnl AX_REQUIRE_DEFINED
582 changes: 582 additions & 0 deletions config/ax_with_curses.m4

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions config/user.m4
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ dnl #
dnl # Default ZFS user configuration
dnl #
AC_DEFUN([ZFS_AC_CONFIG_USER], [
AX_WITH_CURSES
ZFS_AC_CONFIG_USER_GETTEXT
ZFS_AC_CONFIG_USER_MOUNT_HELPER
ZFS_AC_CONFIG_USER_UDEV
Expand Down
12 changes: 12 additions & 0 deletions include/libzutil.h
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,18 @@ extern void zpool_dump_ddt(const ddt_stat_t *, const ddt_histogram_t *);
extern int zpool_history_unpack(char *, uint64_t, uint64_t *, nvlist_t ***,
uint_t *);

/*
* List of colors to use ("FG" for forground color, "BG" for background)
*/
#define ANSI_RED "\033[0;31m"
#define ANSI_YELLOW "\033[0;33m"
#define ANSI_RESET "\033[0m"
#define ANSI_BOLD "\033[1m"

void color_start(char *color);
void color_end(void);
int printf_color(char *color, char *format, ...);

#ifdef __cplusplus
}
#endif
Expand Down
2 changes: 1 addition & 1 deletion lib/libzfs/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ endif

libzfs_la_LDFLAGS = -version-info 2:0:0

libzfs_la_LIBADD += -lm $(LIBSSL)
libzfs_la_LIBADD += -lm $(LIBSSL) $(CURSES_LIBS)

EXTRA_DIST = $(libzfs_pc_DATA) $(USER_C)

Expand Down
106 changes: 106 additions & 0 deletions lib/libzfs/libzfs_util.c
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,14 @@
#include <sys/types.h>
#include <sys/wait.h>

#ifdef HAVE_CURSES_COLOR
#include <curses.h>
#include <term.h>
#ifdef lines
#undef lines
#endif
#endif

#include <libzfs.h>
#include <libzfs_core.h>

Expand Down Expand Up @@ -1886,3 +1894,101 @@ zfs_version_print(void)

return (0);
}

/*
* Return 1 if the user request ANSI color output, and our terminal supports
* it. Return 0 for no color.
*/
static int
use_color(void)
{
#ifdef HAVE_CURSES_COLOR
static int use_color = -1;

/*
* Optimization:
*
* For each zpool invocation, we do a single check to see if we should
* be using color or not, and cache that value for the lifetime of the
* the zpool command. That makes it cheap to call use_color() when
* we're printing with color. We assume that the settings are not going
* to change during the invocation of a zpool command (the user isn't
* going to change the ZFS_COLOR value while zpool is running, for
* example).
*/
if (use_color != -1) {
/*
* We've already figured out if we should be using color or
* not. Return the cached value.
*/
return (use_color);
}

/*
* The user sets the ZFS_COLOR env var set to enable zpool ANSI color
* output. However if NO_COLOR is set (https://no-color.org/) then
* don't use it.
*/
if (libzfs_envvar_is_set("ZFS_COLOR") &&
!libzfs_envvar_is_set("NO_COLOR")) {
/*
* User has requested color, check if our terminal supports it.
* If it's not supported, then silently don't use it.
*/
if (setupterm(NULL, fileno(stdout), (int *)0) == 0 &&
tigetnum("colors") >= 16) {
/* Color supported */
use_color = 1;
} else {
use_color = 0;
}
return (use_color);
}
#endif
return (0);
}

/*
* color_start() and color_end() are used for when you want to colorize a block
* of text. For example:
*
* color_start(ANSI_RED_FG)
* printf("hello");
* printf("world");
* color_end();
*/
void
color_start(char *color)
{
if (use_color())
printf(color);
}

void
color_end(void)
{
if (use_color())
printf(ANSI_RESET);
}

/* printf() with a color. If color is NULL, then do a normal printf. */
int
printf_color(char *color, char *format, ...)
{
char buf[4090];
va_list aptr;
int rc;

if (color)
color_start(color);

va_start(aptr, format);
vsprintf(buf, format, aptr);
va_end(aptr);
rc = printf(buf);

if (color)
color_end();

return (rc);
}
6 changes: 6 additions & 0 deletions man/man8/zpool.8
Original file line number Diff line number Diff line change
Expand Up @@ -2861,6 +2861,12 @@ or
by setting
.Sy ZFS_VDEV_DEVID_OPT_OUT .
.El
.Bl -tag -width "ZFS_COLOR"
.It Ev ZFS_COLOR
Use ANSI color in
.Nm zpool status
output.
.El
.Bl -tag -width "ZPOOL_SCRIPTS_AS_ROOT"
.It Ev ZPOOL_SCRIPTS_AS_ROOT
Allow a privileged user to run the
Expand Down
3 changes: 2 additions & 1 deletion tests/zfs-tests/tests/functional/cli_root/zpool/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ dist_pkgdata_SCRIPTS = \
cleanup.ksh \
zpool_001_neg.ksh \
zpool_002_pos.ksh \
zpool_003_pos.ksh
zpool_003_pos.ksh \
zpool_colors.ksh
2 changes: 1 addition & 1 deletion tests/zfs-tests/tests/functional/cli_root/zpool/setup.ksh
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,4 @@

DISK=${DISKS%% *}

default_setup $DISK
default_mirror_setup $DISKS
78 changes: 78 additions & 0 deletions tests/zfs-tests/tests/functional/cli_root/zpool/zpool_colors.ksh
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
#!/bin/ksh -p
#
# CDDL HEADER START
#
# The contents of this file are subject to the terms of the
# Common Development and Distribution License (the "License").
# You may not use this file except in compliance with the License.
#
# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
# or http://www.opensolaris.org/os/licensing.
# See the License for the specific language governing permissions
# and limitations under the License.
#
# When distributing Covered Code, include this CDDL HEADER in each
# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
# If applicable, add the following below this CDDL HEADER, with the
# fields enclosed by brackets "[]" replaced with your own identifying
# information: Portions Copyright [yyyy] [name of copyright owner]
#
# CDDL HEADER END
#

#
# Copyright 2007 Sun Microsystems, Inc. All rights reserved.
# Use is subject to license terms.
#

#
# Copyright (c) 2012, 2016 by Delphix. All rights reserved.
#

. $STF_SUITE/include/libtest.shlib

#
# DESCRIPTION:
# Verify debugging features of zpool such as ABORT and freeze/unfreeze
# should run successfully.
#
# STRATEGY:
# 1. Create an array containing each zpool options.
# 2. For each element, execute the zpool command.
# 3. Verify it run successfully.
#

verify_runnable "both"

function cleanup
{
zinject -c all
}

log_onexit cleanup

log_assert "Test colorized zpool status output"

DISK2="$(echo $DISKS | cut -d' ' -f2)"
DISK3="$(echo $DISKS | cut -d' ' -f3)"

log_must dd if=/dev/urandom of=/$TESTDIR/testfile bs=10M count=1

log_must zpool export $TESTPOOL
log_must zpool import $TESTPOOL

log_note "$(ZFS_COLOR=1 zpool status)"
log_must zpool offline -f $TESTPOOL $DISK3
log_must wait_for_degraded $TESTPOOL
log_must zinject -d $DISK2 -e io -T read -f 20 $TESTPOOL
log_must zinject -d $DISK2 -e io -T write -f 20 $TESTPOOL


log_must zpool scrub $TESTPOOL
log_must wait_scrubbed $TESTPOOL
log_must zinject -c all

out="$(ZFS_COLOR=1 zpool status)"


log_pass "zpool status displayed colors"

0 comments on commit be9e1b6

Please sign in to comment.