Skip to content

Commit 9fb2771

Browse files
tonyhutterbehlendorf
authored andcommitted
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. This also includes a new 'faketty' function in libtest.shlib that is compatible with FreeBSD (code provided by @freqlabs). Reviewed-by: Jorgen Lundman <[email protected]> Reviewed-by: Brian Behlendorf <[email protected]> Reviewed-by: Ryan Moeller <[email protected]> Signed-off-by: Tony Hutter <[email protected]> Closes #9340
1 parent 5e8ac05 commit 9fb2771

File tree

10 files changed

+479
-139
lines changed

10 files changed

+479
-139
lines changed

cmd/zpool/zpool_main.c

Lines changed: 258 additions & 136 deletions
Large diffs are not rendered by default.

include/libzutil.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,18 @@ extern int zpool_history_unpack(char *, uint64_t, uint64_t *, nvlist_t ***,
146146
struct zfs_cmd;
147147
int zfs_ioctl_fd(int fd, unsigned long request, struct zfs_cmd *zc);
148148

149+
/*
150+
* List of colors to use
151+
*/
152+
#define ANSI_RED "\033[0;31m"
153+
#define ANSI_YELLOW "\033[0;33m"
154+
#define ANSI_RESET "\033[0m"
155+
#define ANSI_BOLD "\033[1m"
156+
157+
void color_start(char *color);
158+
void color_end(void);
159+
int printf_color(char *color, char *format, ...);
160+
149161
#ifdef __cplusplus
150162
}
151163
#endif

lib/libzfs/libzfs_util.c

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1886,3 +1886,95 @@ zfs_version_print(void)
18861886

18871887
return (0);
18881888
}
1889+
1890+
/*
1891+
* Return 1 if the user requested ANSI color output, and our terminal supports
1892+
* it. Return 0 for no color.
1893+
*/
1894+
static int
1895+
use_color(void)
1896+
{
1897+
static int use_color = -1;
1898+
char *term;
1899+
1900+
/*
1901+
* Optimization:
1902+
*
1903+
* For each zpool invocation, we do a single check to see if we should
1904+
* be using color or not, and cache that value for the lifetime of the
1905+
* the zpool command. That makes it cheap to call use_color() when
1906+
* we're printing with color. We assume that the settings are not going
1907+
* to change during the invocation of a zpool command (the user isn't
1908+
* going to change the ZFS_COLOR value while zpool is running, for
1909+
* example).
1910+
*/
1911+
if (use_color != -1) {
1912+
/*
1913+
* We've already figured out if we should be using color or
1914+
* not. Return the cached value.
1915+
*/
1916+
return (use_color);
1917+
}
1918+
1919+
term = getenv("TERM");
1920+
/*
1921+
* The user sets the ZFS_COLOR env var set to enable zpool ANSI color
1922+
* output. However if NO_COLOR is set (https://no-color.org/) then
1923+
* don't use it. Also, don't use color if terminal doesn't support
1924+
* it.
1925+
*/
1926+
if (libzfs_envvar_is_set("ZFS_COLOR") &&
1927+
!libzfs_envvar_is_set("NO_COLOR") &&
1928+
isatty(STDOUT_FILENO) && term && strcmp("dumb", term) != 0 &&
1929+
strcmp("unknown", term) != 0) {
1930+
/* Color supported */
1931+
use_color = 1;
1932+
} else {
1933+
use_color = 0;
1934+
}
1935+
1936+
return (use_color);
1937+
}
1938+
1939+
/*
1940+
* color_start() and color_end() are used for when you want to colorize a block
1941+
* of text. For example:
1942+
*
1943+
* color_start(ANSI_RED_FG)
1944+
* printf("hello");
1945+
* printf("world");
1946+
* color_end();
1947+
*/
1948+
void
1949+
color_start(char *color)
1950+
{
1951+
if (use_color())
1952+
printf("%s", color);
1953+
}
1954+
1955+
void
1956+
color_end(void)
1957+
{
1958+
if (use_color())
1959+
printf(ANSI_RESET);
1960+
}
1961+
1962+
/* printf() with a color. If color is NULL, then do a normal printf. */
1963+
int
1964+
printf_color(char *color, char *format, ...)
1965+
{
1966+
va_list aptr;
1967+
int rc;
1968+
1969+
if (color)
1970+
color_start(color);
1971+
1972+
va_start(aptr, format);
1973+
rc = vprintf(format, aptr);
1974+
va_end(aptr);
1975+
1976+
if (color)
1977+
color_end();
1978+
1979+
return (rc);
1980+
}

man/man8/zpool.8

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -453,6 +453,12 @@ Cause
453453
to dump core on exit for the purposes of running
454454
.Sy ::findleaks .
455455
.El
456+
.Bl -tag -width "ZFS_COLOR"
457+
.It Ev ZFS_COLOR
458+
Use ANSI color in
459+
.Nm zpool status
460+
output.
461+
.El
456462
.Bl -tag -width "ZPOOL_IMPORT_PATH"
457463
.It Ev ZPOOL_IMPORT_PATH
458464
The search path for devices or files to use with the pool. This is a colon-separated list of directories in which

tests/runfiles/common.run

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

284284
[tests/functional/cli_root/zpool]
285-
tests = ['zpool_001_neg', 'zpool_002_pos', 'zpool_003_pos']
285+
tests = ['zpool_001_neg', 'zpool_002_pos', 'zpool_003_pos', 'zpool_colors']
286286
tags = ['functional', 'cli_root', 'zpool']
287287

288288
[tests/functional/cli_root/zpool_add]

tests/zfs-tests/include/commands.cfg

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ export SYSTEM_FILES_COMMON='arp
7777
rm
7878
rmdir
7979
scp
80+
script
8081
sed
8182
seq
8283
setfacl

tests/zfs-tests/include/libtest.shlib

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3916,3 +3916,18 @@ function stat_size #<path>
39163916
;;
39173917
esac
39183918
}
3919+
3920+
# Run a command as if it was being run in a TTY.
3921+
#
3922+
# Usage:
3923+
#
3924+
# faketty command
3925+
#
3926+
function faketty
3927+
{
3928+
if is_freebsd; then
3929+
script -q /dev/null "$@"
3930+
else
3931+
script --return --quiet -c "$*" /dev/null
3932+
fi
3933+
}

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: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
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 sync
48+
49+
log_must zpool offline -f $TESTPOOL $DISK3
50+
log_must wait_for_degraded $TESTPOOL
51+
log_must zinject -d $DISK2 -e io -T read -f 20 $TESTPOOL
52+
log_must zinject -d $DISK2 -e io -T write -f 20 $TESTPOOL
53+
54+
55+
log_must zpool scrub -w $TESTPOOL
56+
log_must zinject -c all
57+
58+
59+
# Use 'script' to fake zpool status into thinking it's running in a tty.
60+
# Log the output here in case it's needed for postmortem.
61+
log_note "$(faketty TERM=xterm-256color ZFS_COLOR=1 zpool status)"
62+
63+
# Replace the escape codes with "ESC" so they're easier to grep
64+
out="$(faketty TERM=xterm-256color ZFS_COLOR=1 zpool status | \
65+
grep -E 'pool:|DEGRADED' | \
66+
sed -r 's/\s+//g;'$(echo -e 's/\033/ESC/g'))"
67+
68+
log_note "$(echo $out)"
69+
70+
log_note "Look for 'pool:' in bold"
71+
log_must eval "echo \"$out\" | grep -q 'ESC\[1mpool:ESC\[0m' "
72+
73+
log_note "Look for 'DEGRADED' in yellow"
74+
log_must eval "echo \"$out\" | grep -q 'ESC\[0;33mDEGRADEDESC\[0m'"
75+
76+
#
77+
# The escape code for 'FAULTED' is a little more tricky. The line starts like
78+
# this:
79+
#
80+
# <start red escape code> loop2 FAULTED <end escape code>
81+
#
82+
# Luckily, awk counts the start and end escape codes as separate fields, so
83+
# we can easily remove the vdev field to get what we want.
84+
#
85+
out="$(faketty TERM=xterm-256color ZFS_COLOR=1 zpool status \
86+
| awk '/FAULTED/{print $1$3$4}' | sed -r $(echo -e 's/\033/ESC/g'))"
87+
88+
log_note "Look for 'FAULTED' in red"
89+
log_must eval "echo \"$out\" | grep -q 'ESC\[0;31mFAULTEDESC\[0m'"
90+
91+
log_pass "zpool status displayed colors"

0 commit comments

Comments
 (0)