Skip to content

Commit 51e98a4

Browse files
committed
Colorize zpool status output
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]>
1 parent 4313a5b commit 51e98a4

File tree

13 files changed

+1093
-137
lines changed

13 files changed

+1093
-137
lines changed

cmd/zpool/zpool_main.c

Lines changed: 253 additions & 133 deletions
Large diffs are not rendered by default.

config/ax_require_defined.m4

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# ===========================================================================
2+
# https://www.gnu.org/software/autoconf-archive/ax_require_defined.html
3+
# ===========================================================================
4+
#
5+
# SYNOPSIS
6+
#
7+
# AX_REQUIRE_DEFINED(MACRO)
8+
#
9+
# DESCRIPTION
10+
#
11+
# AX_REQUIRE_DEFINED is a simple helper for making sure other macros have
12+
# been defined and thus are available for use. This avoids random issues
13+
# where a macro isn't expanded. Instead the configure script emits a
14+
# non-fatal:
15+
#
16+
# ./configure: line 1673: AX_CFLAGS_WARN_ALL: command not found
17+
#
18+
# It's like AC_REQUIRE except it doesn't expand the required macro.
19+
#
20+
# Here's an example:
21+
#
22+
# AX_REQUIRE_DEFINED([AX_CHECK_LINK_FLAG])
23+
#
24+
# LICENSE
25+
#
26+
# Copyright (c) 2014 Mike Frysinger <[email protected]>
27+
#
28+
# Copying and distribution of this file, with or without modification, are
29+
# permitted in any medium without royalty provided the copyright notice
30+
# and this notice are preserved. This file is offered as-is, without any
31+
# warranty.
32+
33+
#serial 2
34+
35+
AC_DEFUN([AX_REQUIRE_DEFINED], [dnl
36+
m4_ifndef([$1], [m4_fatal([macro ]$1[ is not defined; is a m4 file missing?])])
37+
])dnl AX_REQUIRE_DEFINED

config/ax_with_curses.m4

Lines changed: 582 additions & 0 deletions
Large diffs are not rendered by default.

config/user.m4

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ dnl #
22
dnl # Default ZFS user configuration
33
dnl #
44
AC_DEFUN([ZFS_AC_CONFIG_USER], [
5+
AX_WITH_CURSES
56
ZFS_AC_CONFIG_USER_GETTEXT
67
ZFS_AC_CONFIG_USER_MOUNT_HELPER
78
ZFS_AC_CONFIG_USER_UDEV

include/libzutil.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,18 @@ extern void zpool_dump_ddt(const ddt_stat_t *, const ddt_histogram_t *);
142142
extern int zpool_history_unpack(char *, uint64_t, uint64_t *, nvlist_t ***,
143143
uint_t *);
144144

145+
/*
146+
* List of colors to use ("FG" for forground color, "BG" for background)
147+
*/
148+
#define ANSI_RED "\033[0;31m"
149+
#define ANSI_YELLOW "\033[0;33m"
150+
#define ANSI_RESET "\033[0m"
151+
#define ANSI_BOLD "\033[1m"
152+
153+
void color_start(char *color);
154+
void color_end(void);
155+
int printf_color(char *color, char *format, ...);
156+
145157
#ifdef __cplusplus
146158
}
147159
#endif

lib/libzfs/Makefile.am

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ endif
7171

7272
libzfs_la_LDFLAGS = -version-info 2:0:0
7373

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

7676
EXTRA_DIST = $(libzfs_pc_DATA) $(USER_C)
7777

lib/libzfs/libzfs_util.c

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,18 @@
4646
#include <sys/types.h>
4747
#include <sys/wait.h>
4848

49+
#ifdef HAVE_CURSES_COLOR
50+
#include <curses.h>
51+
#include <term.h>
52+
/*
53+
* For some reason, term.h #defines 'lines' (in lower case) as a macro.
54+
* Since we also have a variable named 'lines', undefine the macro here.
55+
*/
56+
#ifdef lines
57+
#undef lines
58+
#endif
59+
#endif
60+
4961
#include <libzfs.h>
5062
#include <libzfs_core.h>
5163

@@ -1886,3 +1898,101 @@ zfs_version_print(void)
18861898

18871899
return (0);
18881900
}
1901+
1902+
/*
1903+
* Return 1 if the user request ANSI color output, and our terminal supports
1904+
* it. Return 0 for no color.
1905+
*/
1906+
static int
1907+
use_color(void)
1908+
{
1909+
#ifdef HAVE_CURSES_COLOR
1910+
static int use_color = -1;
1911+
1912+
/*
1913+
* Optimization:
1914+
*
1915+
* For each zpool invocation, we do a single check to see if we should
1916+
* be using color or not, and cache that value for the lifetime of the
1917+
* the zpool command. That makes it cheap to call use_color() when
1918+
* we're printing with color. We assume that the settings are not going
1919+
* to change during the invocation of a zpool command (the user isn't
1920+
* going to change the ZFS_COLOR value while zpool is running, for
1921+
* example).
1922+
*/
1923+
if (use_color != -1) {
1924+
/*
1925+
* We've already figured out if we should be using color or
1926+
* not. Return the cached value.
1927+
*/
1928+
return (use_color);
1929+
}
1930+
1931+
/*
1932+
* The user sets the ZFS_COLOR env var set to enable zpool ANSI color
1933+
* output. However if NO_COLOR is set (https://no-color.org/) then
1934+
* don't use it.
1935+
*/
1936+
if (libzfs_envvar_is_set("ZFS_COLOR") &&
1937+
!libzfs_envvar_is_set("NO_COLOR")) {
1938+
/*
1939+
* User has requested color, check if our terminal supports it.
1940+
* If it's not supported, then silently don't use it.
1941+
*/
1942+
if (setupterm(NULL, fileno(stdout), (int *)0) == 0 &&
1943+
tigetnum("colors") >= 16) {
1944+
/* Color supported */
1945+
use_color = 1;
1946+
} else {
1947+
use_color = 0;
1948+
}
1949+
return (use_color);
1950+
}
1951+
#endif
1952+
return (0);
1953+
}
1954+
1955+
/*
1956+
* color_start() and color_end() are used for when you want to colorize a block
1957+
* of text. For example:
1958+
*
1959+
* color_start(ANSI_RED_FG)
1960+
* printf("hello");
1961+
* printf("world");
1962+
* color_end();
1963+
*/
1964+
void
1965+
color_start(char *color)
1966+
{
1967+
if (use_color())
1968+
printf("%s", color);
1969+
}
1970+
1971+
void
1972+
color_end(void)
1973+
{
1974+
if (use_color())
1975+
printf(ANSI_RESET);
1976+
}
1977+
1978+
/* printf() with a color. If color is NULL, then do a normal printf. */
1979+
int
1980+
printf_color(char *color, char *format, ...)
1981+
{
1982+
char buf[4090];
1983+
va_list aptr;
1984+
int rc;
1985+
1986+
if (color)
1987+
color_start(color);
1988+
1989+
va_start(aptr, format);
1990+
vsprintf(buf, format, aptr);
1991+
va_end(aptr);
1992+
rc = printf("%s", buf);
1993+
1994+
if (color)
1995+
color_end();
1996+
1997+
return (rc);
1998+
}

man/man8/zpool.8

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2867,6 +2867,12 @@ or
28672867
by setting
28682868
.Sy ZFS_VDEV_DEVID_OPT_OUT .
28692869
.El
2870+
.Bl -tag -width "ZFS_COLOR"
2871+
.It Ev ZFS_COLOR
2872+
Use ANSI color in
2873+
.Nm zpool status
2874+
output.
2875+
.El
28702876
.Bl -tag -width "ZPOOL_SCRIPTS_AS_ROOT"
28712877
.It Ev ZPOOL_SCRIPTS_AS_ROOT
28722878
Allow a privileged user to run the

rpm/generic/zfs.spec.in

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ BuildRequires: libblkid-devel
135135
BuildRequires: libudev-devel
136136
BuildRequires: libattr-devel
137137
BuildRequires: openssl-devel
138+
BuildRequires: ncurses-devel
138139
%if 0%{?fedora} >= 28 || 0%{?rhel} >= 8 || 0%{?centos} >= 8
139140
BuildRequires: libtirpc-devel
140141
%endif
@@ -153,6 +154,7 @@ Requires(postun): systemd
153154
# The zpool iostat/status -c scripts call some utilities like lsblk and iostat
154155
Requires: util-linux
155156
Requires: sysstat
157+
Requires: ncurses
156158

157159
%description
158160
This package contains the core ZFS command line utilities.

tests/runfiles/common.run

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -283,7 +283,7 @@ tests = ['zfs_upgrade_001_pos', 'zfs_upgrade_002_pos', 'zfs_upgrade_003_pos',
283283
tags = ['functional', 'cli_root', 'zfs_upgrade']
284284

285285
[tests/functional/cli_root/zpool]
286-
tests = ['zpool_001_neg', 'zpool_002_pos', 'zpool_003_pos']
286+
tests = ['zpool_001_neg', 'zpool_002_pos', 'zpool_003_pos', 'zpool_colors']
287287
tags = ['functional', 'cli_root', 'zpool']
288288

289289
[tests/functional/cli_root/zpool_add]

tests/zfs-tests/tests/functional/cli_root/zpool/Makefile.am

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,5 @@ dist_pkgdata_SCRIPTS = \
44
cleanup.ksh \
55
zpool_001_neg.ksh \
66
zpool_002_pos.ksh \
7-
zpool_003_pos.ksh
7+
zpool_003_pos.ksh \
8+
zpool_colors.ksh

tests/zfs-tests/tests/functional/cli_root/zpool/setup.ksh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,4 @@
2929

3030
DISK=${DISKS%% *}
3131

32-
default_setup $DISK
32+
default_mirror_setup $DISKS
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
#!/bin/ksh -p
2+
#
3+
# CDDL HEADER START
4+
#
5+
# This file and its contents are supplied under the terms of the
6+
# Common Development and Distribution License ("CDDL"), version 1.0.
7+
# You may only use this file in accordance with the terms of version
8+
# 1.0 of the CDDL.
9+
#
10+
# A full copy of the text of the CDDL should have accompanied this
11+
# source. A copy of the CDDL is also available via the Internet at
12+
# http://www.illumos.org/license/CDDL.
13+
#
14+
# CDDL HEADER END
15+
#
16+
# Copyright (c) 2019 Lawrence Livermore National Security, LLC.
17+
18+
. $STF_SUITE/include/libtest.shlib
19+
20+
#
21+
# DESCRIPTION:
22+
# Test that zpool status colored output works.
23+
#
24+
# STRATEGY:
25+
# 1. Create a pool with a bunch of errors and force fault one of the vdevs.
26+
# 2. Look for 'pool:' in bold.
27+
# 3. Look for 'DEGRADED' in yellow
28+
# 3. Look for 'FAULTED' in red
29+
#
30+
31+
verify_runnable "both"
32+
33+
function cleanup
34+
{
35+
zinject -c all
36+
}
37+
38+
log_onexit cleanup
39+
40+
log_assert "Test colorized zpool status output"
41+
42+
DISK2="$(echo $DISKS | cut -d' ' -f2)"
43+
DISK3="$(echo $DISKS | cut -d' ' -f3)"
44+
45+
log_must dd if=/dev/urandom of=/$TESTDIR/testfile bs=10M count=1
46+
47+
log_must zpool export $TESTPOOL
48+
log_must zpool import $TESTPOOL
49+
50+
log_note "$(ZFS_COLOR=1 zpool status)"
51+
log_must zpool offline -f $TESTPOOL $DISK3
52+
log_must wait_for_degraded $TESTPOOL
53+
log_must zinject -d $DISK2 -e io -T read -f 20 $TESTPOOL
54+
log_must zinject -d $DISK2 -e io -T write -f 20 $TESTPOOL
55+
56+
57+
log_must zpool scrub $TESTPOOL
58+
log_must wait_scrubbed $TESTPOOL
59+
log_must zinject -c all
60+
61+
62+
# Replace the escape codes with "ESC" so they're easier to grep
63+
out="$(ZFS_COLOR=1 zpool status | grep -E 'pool:|DEGRADED' | \
64+
sed -r 's/\s+//g;'$(echo -e 's/\033/ESC/g'))"
65+
66+
log_note "Look for 'pool:' in bold"
67+
log_must eval "echo \"$out\" | grep -q 'ESC\[1mpool:ESC\[0m' "
68+
69+
log_note "Look for 'DEGRADED' in yellow"
70+
log_must eval "echo \"$out\" | grep -q 'ESC\[0;33mDEGRADEDESC\[0m'"
71+
72+
#
73+
# The escape code for 'FAULTED' is a little more tricky. The line starts like
74+
# this:
75+
#
76+
# <start red escape code> loop2 FAULTED <end escape code>
77+
#
78+
# Luckily, awk counts the start and end escape codes as separate fields, so
79+
# we can easily remove the vdev field to get what we want.
80+
#
81+
out="$(ZFS_COLOR=1 zpool status | awk '/FAULTED/{print $1$3$4}' | sed -r $(echo -e 's/\033/ESC/g'))"
82+
log_note "Look for 'FAULTED' in red"
83+
log_must eval "echo \"$out\" | grep -q 'ESC\[0;31mFAULTEDESC\[0m'"
84+
85+
log_pass "zpool status displayed colors"

0 commit comments

Comments
 (0)