aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorThomas Weißschuh <thomas@t-8ch.de>2023-06-25 16:44:10 +0200
committerThomas Weißschuh <thomas@t-8ch.de>2023-06-30 14:12:48 +0200
commite9ddea79127ebe697dce7cdbc3f042c216b4449a (patch)
tree9d1fc1b2323aa682f7285b1078c9c353ce154780
parentb9abaae31272d984d16a28edb0bcd42e2fd81f5d (diff)
downloadutil-linux-e9ddea79127ebe697dce7cdbc3f042c216b4449a.tar.gz
lsclocks: new util to interact with system clocks
Usecases: * Compare current monotonic time to timestamps reported by systemd * Validate time namespace operations * Inspect clock resolutions Signed-off-by: Thomas Weißschuh <thomas@t-8ch.de>
-rw-r--r--bash-completion/Makemodule.am3
-rw-r--r--bash-completion/lsclocks48
-rw-r--r--configure.ac3
-rw-r--r--meson.build13
-rw-r--r--misc-utils/Makemodule.am8
-rw-r--r--misc-utils/lsclocks.1.adoc81
-rw-r--r--misc-utils/lsclocks.c366
-rw-r--r--misc-utils/meson.build4
-rw-r--r--tests/commands.sh1
-rw-r--r--tests/expected/misc/lsclocks-basic10
-rw-r--r--tests/expected/misc/lsclocks-time1
-rwxr-xr-xtests/ts/misc/lsclocks42
12 files changed, 580 insertions, 0 deletions
diff --git a/bash-completion/Makemodule.am b/bash-completion/Makemodule.am
index ac8926f2d0..2763895e89 100644
--- a/bash-completion/Makemodule.am
+++ b/bash-completion/Makemodule.am
@@ -347,5 +347,8 @@ endif
if BUILD_ENOSYS
dist_bashcompletion_DATA += bash-completion/enosys
endif
+if BUILD_LSCLOCKS
+dist_bashcompletion_DATA += bash-completion/lsclocks
+endif
endif # BUILD_BASH_COMPLETION
diff --git a/bash-completion/lsclocks b/bash-completion/lsclocks
new file mode 100644
index 0000000000..bd81d88bc2
--- /dev/null
+++ b/bash-completion/lsclocks
@@ -0,0 +1,48 @@
+_lsclocks_module()
+{
+ local cur prev OPTS
+ COMPREPLY=()
+ cur="${COMP_WORDS[COMP_CWORD]}"
+ prev="${COMP_WORDS[COMP_CWORD-1]}"
+
+ case $prev in
+ '-J'|'--json')
+ return 0
+ ;;
+ '-n'|'--noheadings')
+ return 0
+ ;;
+ '-o'|'--output')
+ return 0
+ ;;
+ '-r'|'--raw')
+ return 0
+ ;;
+ '-t'|'--time')
+ clocks="$(command "$1" --noheadings --raw --output NAME)"
+ COMPREPLY=( $(compgen -W "$clocks" -- "$cur") )
+ return 0
+ ;;
+ '-h'|'--help'|'-V'|'--version')
+ return 0
+ ;;
+ esac
+ case $cur in
+ -*)
+ OPTS="--json
+ --noheadings
+ --output
+ --raw
+ --time
+ --help
+ --version"
+ COMPREPLY=( $(compgen -W "${OPTS[*]}" -- $cur) )
+ return 0
+ ;;
+ *)
+ return 0
+ ;;
+ esac
+ return 0
+}
+complete -F _lsclocks_module lsclocks
diff --git a/configure.ac b/configure.ac
index 8bd10ee69e..8906e8e7fc 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1894,6 +1894,9 @@ AS_IF([test "x$build_enosys" = xyes], [
])
AM_CONDITIONAL([BUILD_ENOSYS], [test "x$build_enosys" = xyes])
+UL_BUILD_INIT([lsclocks], [yes])
+AM_CONDITIONAL([BUILD_LSCLOCKS], [test "x$build_lsclocks" = xyes])
+
UL_BUILD_INIT([getopt], [yes])
AM_CONDITIONAL([BUILD_GETOPT], [test "x$build_getopt" = xyes])
diff --git a/meson.build b/meson.build
index 37141e7db8..6c6dc527b2 100644
--- a/meson.build
+++ b/meson.build
@@ -2908,6 +2908,19 @@ if cc.compiles(fs.read('include/audit-arch.h'), name : 'has AUDIT_ARCH_NATIVE')
endif
endif
+exe = executable(
+ 'lsclocks',
+ lsclocks_sources,
+ include_directories : includes,
+ link_with : [lib_common, lib_smartcols],
+ install_dir : usrbin_exec_dir,
+ install : true)
+if not is_disabler(exe)
+ exes += exe
+ manadocs += ['misc-utils/lsclocks.1.adoc']
+ bashcompletions += ['lsclocks']
+endif
+
############################################################
opt = not get_option('build-schedutils').disabled()
diff --git a/misc-utils/Makemodule.am b/misc-utils/Makemodule.am
index dcc733b3fd..adee4111fa 100644
--- a/misc-utils/Makemodule.am
+++ b/misc-utils/Makemodule.am
@@ -316,3 +316,11 @@ enosys_SOURCES = misc-utils/enosys.c
enosys_LDADD = $(LDADD) libcommon.la
enosys_CFLAGS = $(AM_CFLAGS)
endif
+
+if BUILD_LSCLOCKS
+usrbin_exec_PROGRAMS += lsclocks
+MANPAGES += misc-utils/lsclocks.1
+lsclocks_SOURCES = misc-utils/lsclocks.c
+lsclocks_LDADD = $(LDADD) libcommon.la libsmartcols.la
+lsclocks_CFLAGS = $(AM_CFLAGS) -I$(ul_libsmartcols_incdir)
+endif
diff --git a/misc-utils/lsclocks.1.adoc b/misc-utils/lsclocks.1.adoc
new file mode 100644
index 0000000000..5c7f5eac43
--- /dev/null
+++ b/misc-utils/lsclocks.1.adoc
@@ -0,0 +1,81 @@
+//po4a: entry man manual
+= lsclocks(1)
+:doctype: manpage
+:man manual: User Commands
+:man source: util-linux {release-version}
+:page-layout: base
+:command: lsclocks
+
+== NAME
+
+lsclocks - display system clocks
+
+== SYNOPSIS
+
+*lsclocks* [option]
+
+== DESCRIPTION
+
+*lsclocks* is a simple command to display system clocks.
+
+It allows to display information like current time and resolutionof clocks like
+CLOCK_MONOTONIC, CLOCK_REALTIME and CLOCK_BOOTTIME.
+
+== OPTIONS
+
+*-J*, *--json*::
+Use JSON output format.
+
+*-n*, *--noheadings*::
+Don't print headings.
+
+*-o*, *--output* _list_::
+Specify which output columns to print. See the *OUTPUT COLUMNS*
+section for details of available columns.
+
+*-r*, *--raw*::
+Use raw output format.
+
+*-r*, *--time* _clock_
+Show current time of one specific clocks.
+
+include::man-common/help-version.adoc[]
+
+== OUTPUT COLUMNS
+
+Each column has a type. Types are surround by < and >.
+
+ID <``number``>::
+Numeric clock ID.
+
+CLOCK <``string``>::
+Name in the form *CLOCK_*
+
+NAME <``string``>::
+Shorter, easier to read name.
+
+TIME <``number``>::
+Current clock timestamp as returned by *clock_gettime()*.
+
+ISO_TIME <``string``>::
+ISO8601 formatted version of *TIME*.
+
+RESOLUTION <``number``>::
+Clock resolution as returned by *clock_getres()*.
+
+
+== AUTHORS
+
+mailto:thomas@t-8ch.de[Thomas Weißschuh]
+
+== SEE ALSO
+
+*clock_getres*(2) *clock_gettime*(2)
+
+include::man-common/bugreports.adoc[]
+
+include::man-common/footer.adoc[]
+
+ifdef::translation[]
+include::man-common/translation.adoc[]
+endif::[]
diff --git a/misc-utils/lsclocks.c b/misc-utils/lsclocks.c
new file mode 100644
index 0000000000..8531e9fe32
--- /dev/null
+++ b/misc-utils/lsclocks.c
@@ -0,0 +1,366 @@
+/*
+ * lsclocks(1) - display system clocks
+ *
+ * Copyright (C) 2023 Thomas Weißschuh <thomas@t-8ch.de>
+ *
+ * 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 to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <stdio.h>
+#include <stdbool.h>
+#include <time.h>
+#include <inttypes.h>
+#include <getopt.h>
+
+#include <libsmartcols.h>
+
+#include "c.h"
+#include "nls.h"
+#include "strutils.h"
+#include "timeutils.h"
+#include "closestream.h"
+#include "xalloc.h"
+
+#define CLOCKFD 3
+#define FD_TO_CLOCKID(fd) ((~(clockid_t) (fd) << 3) | CLOCKFD)
+
+#ifndef CLOCK_REALTIME
+#define CLOCK_REALTIME 0
+#endif
+
+#ifndef CLOCK_MONOTONIC
+#define CLOCK_MONOTONIC 1
+#endif
+
+#ifndef CLOCK_MONOTONIC_RAW
+#define CLOCK_MONOTONIC_RAW 4
+#endif
+
+#ifndef CLOCK_REALTIME_COARSE
+#define CLOCK_REALTIME_COARSE 5
+#endif
+
+#ifndef CLOCK_MONOTONIC_COARSE
+#define CLOCK_MONOTONIC_COARSE 6
+#endif
+
+#ifndef CLOCK_BOOTTIME
+#define CLOCK_BOOTTIME 7
+#endif
+
+#ifndef CLOCK_REALTIME_ALARM
+#define CLOCK_REALTIME_ALARM 8
+#endif
+
+#ifndef CLOCK_BOOTTIME_ALARM
+#define CLOCK_BOOTTIME_ALARM 9
+#endif
+
+#ifndef CLOCK_TAI
+#define CLOCK_TAI 11
+#endif
+
+struct clockinfo {
+ clockid_t id;
+ const char * const id_name;
+ const char * const name;
+};
+
+static const struct clockinfo clocks[] = {
+ { CLOCK_REALTIME, "CLOCK_REALTIME", "realtime" },
+ { CLOCK_MONOTONIC, "CLOCK_MONOTONIC", "monotonic" },
+ { CLOCK_MONOTONIC_RAW, "CLOCK_MONOTONIC_RAW", "monotonic-raw" },
+ { CLOCK_REALTIME_COARSE, "CLOCK_REALTIME_COARSE", "realtime-coarse" },
+ { CLOCK_MONOTONIC_COARSE, "CLOCK_MONOTONIC_COARSE", "monotonic-coarse" },
+ { CLOCK_BOOTTIME, "CLOCK_BOOTTIME", "boottime" },
+ { CLOCK_REALTIME_ALARM, "CLOCK_REALTIME_ALARM", "realtime-alarm" },
+ { CLOCK_BOOTTIME_ALARM, "CLOCK_BOOTTIME_ALARM", "boottime-alarm" },
+ { CLOCK_TAI, "CLOCK_TAI", "tai" },
+};
+
+/* column IDs */
+enum {
+ COL_ID,
+ COL_CLOCK,
+ COL_NAME,
+ COL_TIME,
+ COL_ISO_TIME,
+ COL_RESOLUTION,
+};
+
+/* column names */
+struct colinfo {
+ const char * const name; /* header */
+ double whint; /* width hint (N < 1 is in percent of termwidth) */
+ int flags; /* SCOLS_FL_* */
+ int json_type; /* SCOLS_JSON_* */
+ const char * const help;
+};
+
+/* columns descriptions */
+static const struct colinfo infos[] = {
+ [COL_ID] = { "ID", 1, SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER, N_("numeric id") },
+ [COL_CLOCK] = { "CLOCK", 1, 0, SCOLS_JSON_STRING, N_("symbolic name") },
+ [COL_NAME] = { "NAME", 1, 0, SCOLS_JSON_STRING, N_("readable name") },
+ [COL_TIME] = { "TIME", 1, SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER, N_("numeric time") },
+ [COL_ISO_TIME] = { "ISO_TIME", 1, SCOLS_FL_RIGHT, SCOLS_JSON_STRING, N_("human readable ISO time") },
+ [COL_RESOLUTION] = { "RESOLUTION", 1, SCOLS_FL_RIGHT, SCOLS_JSON_NUMBER, N_("resolution") },
+};
+
+static int column_name_to_id(const char *name, size_t namesz)
+{
+ size_t i;
+
+ for (i = 0; i < ARRAY_SIZE(infos); i++) {
+ const char *cn = infos[i].name;
+
+ if (!strncasecmp(name, cn, namesz) && !*(cn + namesz))
+ return i;
+ }
+ warnx(_("unknown column: %s"), name);
+
+ return -1;
+}
+
+static void __attribute__((__noreturn__)) usage(void)
+{
+ FILE *out = stdout;
+ size_t i;
+
+ fputs(USAGE_HEADER, out);
+ fprintf(out, _(" %s [options]\n"), program_invocation_short_name);
+
+ fputs(USAGE_OPTIONS, out);
+ fputs(_(" -J, --json use JSON output format\n"), out);
+ fputs(_(" -n, --noheadings don't print headings\n"), out);
+ fputs(_(" -o, --output <list> output columns\n"), out);
+ fputs(_(" -r, --raw use raw output format\n"), out);
+ fputs(_(" -t, --time <clock> show current time of single clock\n"), out);
+
+ fputs(USAGE_SEPARATOR, out);
+ printf(USAGE_HELP_OPTIONS(25));
+
+ fprintf(out, USAGE_COLUMNS);
+
+ for (i = 0; i < ARRAY_SIZE(infos); i++)
+ fprintf(out, " %16s %-10s%s\n", infos[i].name,
+ infos[i].json_type == SCOLS_JSON_STRING? "<string>":
+ infos[i].json_type == SCOLS_JSON_ARRAY_STRING? "<string>":
+ infos[i].json_type == SCOLS_JSON_ARRAY_NUMBER? "<string>":
+ infos[i].json_type == SCOLS_JSON_NUMBER? "<number>":
+ "<boolean>",
+ _(infos[i].help));
+
+ printf(USAGE_MAN_TAIL("lslocks(1)"));
+
+ exit(EXIT_SUCCESS);
+}
+
+__attribute__ ((__format__ (__printf__, 3, 4)))
+static void scols_line_asprintf(struct libscols_line *ln, size_t n, const char *format, ...)
+{
+ char *data;
+ va_list args;
+
+ va_start(args, format);
+ xvasprintf(&data, format, args);
+ va_end(args);
+
+ scols_line_refer_data(ln, n, data);
+}
+
+static void scols_line_format_timespec(struct libscols_line *ln, size_t n, const struct timespec *ts)
+{
+ scols_line_asprintf(ln, n, "%ju.%09" PRId32, (uintmax_t) ts->tv_sec, (uint32_t) ts->tv_nsec);
+}
+
+static clockid_t parse_clock(const char *name)
+{
+ size_t i;
+ uint32_t id = -1;
+ int rc;
+
+ rc = ul_strtou32(name, &id, 10);
+
+ for (i = 0; i < ARRAY_SIZE(clocks); i++) {
+ if (!strcmp(name, clocks[i].id_name)
+ || !strcmp(name, clocks[i].name))
+ return clocks[i].id;
+ if (rc == 0 && (clockid_t) id == clocks[i].id)
+ return id;
+ }
+
+ errx(EXIT_FAILURE, _("Unknown clock: %s"), name);
+}
+
+int main(int argc, char **argv)
+{
+ size_t i, j;
+ char c;
+ int rc;
+ const struct colinfo *colinfo;
+ const struct clockinfo *clockinfo;
+
+ struct libscols_table *tb;
+ struct libscols_line *ln;
+ struct libscols_column *col;
+
+ bool noheadings = false, raw = false, json = false;
+ const char *outarg = NULL;
+ int columns[ARRAY_SIZE(infos) * 2];
+ size_t ncolumns = 0;
+ clockid_t clock = -1;
+
+ struct timespec resolution, now;
+ char buf[FORMAT_TIMESTAMP_MAX];
+
+ static const struct option longopts[] = {
+ { "noheadings", no_argument, NULL, 'n' },
+ { "output", required_argument, NULL, 'o' },
+ { "version", no_argument, NULL, 'V' },
+ { "help", no_argument, NULL, 'h' },
+ { "json", no_argument, NULL, 'J' },
+ { "raw", no_argument, NULL, 'r' },
+ { "time", required_argument, NULL, 't' },
+ { 0 }
+ };
+
+ setlocale(LC_ALL, "");
+ bindtextdomain(PACKAGE, LOCALEDIR);
+ textdomain(PACKAGE);
+ close_stdout_atexit();
+
+ while ((c = getopt_long(argc, argv, "no:Jrt:Vh", longopts, NULL)) != -1) {
+ switch (c) {
+ case 'n':
+ noheadings = true;
+ break;
+ case 'o':
+ outarg = optarg;
+ break;
+ case 'J':
+ json = true;
+ break;
+ case 'r':
+ raw = true;
+ break;
+ case 't':
+ clock = parse_clock(optarg);
+ break;
+ case 'V':
+ print_version(EXIT_SUCCESS);
+ case 'h':
+ usage();
+ default:
+ errtryhelp(EXIT_FAILURE);
+ }
+ }
+
+ if (argv[optind])
+ errtryhelp(EXIT_FAILURE);
+
+ if (clock != -1) {
+ rc = clock_gettime(clock, &now);
+ if (rc)
+ err(EXIT_FAILURE, _("failed to get time"));
+ printf("%ju.%09"PRId32"\n", (uintmax_t) now.tv_sec, (uint32_t) now.tv_nsec);
+ return EXIT_SUCCESS;
+ }
+
+ if (!ncolumns) {
+ columns[ncolumns++] = COL_ID;
+ columns[ncolumns++] = COL_CLOCK;
+ columns[ncolumns++] = COL_NAME;
+ columns[ncolumns++] = COL_TIME;
+ columns[ncolumns++] = COL_ISO_TIME;
+ columns[ncolumns++] = COL_RESOLUTION;
+ }
+
+ if (outarg && string_add_to_idarray(outarg, columns, ARRAY_SIZE(columns),
+ &ncolumns, column_name_to_id) < 0)
+ return EXIT_FAILURE;
+
+ scols_init_debug(0);
+
+ tb = scols_new_table();
+ if (!tb)
+ errx(EXIT_FAILURE, _("failed to allocate output table"));
+ scols_table_set_name(tb, "clocks");
+
+ for (i = 0; i < ncolumns; i++) {
+ colinfo = &infos[columns[i]];
+
+ col = scols_table_new_column(tb, colinfo->name, colinfo->whint, colinfo->flags);
+ if (!col)
+ errx(EXIT_FAILURE, _("failed to allocate output column"));
+
+ scols_column_set_json_type(col, colinfo->json_type);
+ }
+
+ for (i = 0; i < ARRAY_SIZE(clocks); i++) {
+ clockinfo = &clocks[i];
+
+ ln = scols_table_new_line(tb, NULL);
+ if (!ln)
+ errx(EXIT_FAILURE, _("failed to allocate output line"));
+
+ /* outside the loop to guarantee consistency between COL_TIME and COL_ISO_TIME */
+ rc = clock_gettime(clockinfo->id, &now);
+ if (rc)
+ now.tv_nsec = -1;
+
+ for (j = 0; j < ncolumns; j++) {
+ switch (columns[j]) {
+ case COL_ID:
+ scols_line_asprintf(ln, j, "%ju", (uintmax_t) clockinfo->id);
+ break;
+ case COL_CLOCK:
+ scols_line_set_data(ln, j, clockinfo->id_name);
+ break;
+ case COL_NAME:
+ scols_line_set_data(ln, j, clockinfo->name);
+ break;
+ case COL_TIME:
+ if (now.tv_nsec == -1)
+ break;
+
+ scols_line_format_timespec(ln, j, &now);
+ break;
+ case COL_ISO_TIME:
+ if (now.tv_nsec == -1)
+ break;
+
+ rc = strtimespec_iso(&now,
+ ISO_GMTIME | ISO_DATE | ISO_TIME | ISO_T | ISO_DOTNSEC | ISO_TIMEZONE,
+ buf, sizeof(buf));
+ if (rc)
+ errx(EXIT_FAILURE, _("failed to format iso time"));
+ scols_line_set_data(ln, j, buf);
+ break;
+ case COL_RESOLUTION:
+ rc = clock_getres(clockinfo->id, &resolution);
+ if (!rc)
+ scols_line_format_timespec(ln, j, &resolution);
+ break;
+ }
+ }
+ }
+
+ scols_table_enable_json(tb, json);
+ scols_table_enable_raw(tb, raw);
+ scols_table_enable_noheadings(tb, noheadings);
+ scols_print_table(tb);
+ scols_unref_table(tb);
+}
diff --git a/misc-utils/meson.build b/misc-utils/meson.build
index 7d21d02c15..a7cc4e4f3f 100644
--- a/misc-utils/meson.build
+++ b/misc-utils/meson.build
@@ -165,3 +165,7 @@ fadvise_sources = files(
waitpid_sources = files(
'waitpid.c',
)
+
+lsclocks_sources = files(
+ 'lsclocks.c',
+)
diff --git a/tests/commands.sh b/tests/commands.sh
index 38b425405b..41c0ee98ce 100644
--- a/tests/commands.sh
+++ b/tests/commands.sh
@@ -87,6 +87,7 @@ TS_CMD_LINE=${TS_CMD_LINE-"${ts_commandsdir}line"}
TS_CMD_LOOK=${TS_CMD_LOOK-"${ts_commandsdir}look"}
TS_CMD_LOSETUP=${TS_CMD_LOSETUP:-"${ts_commandsdir}losetup"}
TS_CMD_LSBLK=${TS_CMD_LSBLK-"${ts_commandsdir}lsblk"}
+TS_CMD_LSCLOCKS=${TS_CMD_LSCPU-"${ts_commandsdir}lsclocks"}
TS_CMD_LSCPU=${TS_CMD_LSCPU-"${ts_commandsdir}lscpu"}
TS_CMD_LSFD=${TS_CMD_LSFD-"${ts_commandsdir}lsfd"}
TS_CMD_LSMEM=${TS_CMD_LSMEM-"${ts_commandsdir}lsmem"}
diff --git a/tests/expected/misc/lsclocks-basic b/tests/expected/misc/lsclocks-basic
new file mode 100644
index 0000000000..b3b25f4a63
--- /dev/null
+++ b/tests/expected/misc/lsclocks-basic
@@ -0,0 +1,10 @@
+ID CLOCK NAME
+ 0 CLOCK_REALTIME realtime
+ 1 CLOCK_MONOTONIC monotonic
+ 4 CLOCK_MONOTONIC_RAW monotonic-raw
+ 5 CLOCK_REALTIME_COARSE realtime-coarse
+ 6 CLOCK_MONOTONIC_COARSE monotonic-coarse
+ 7 CLOCK_BOOTTIME boottime
+ 8 CLOCK_REALTIME_ALARM realtime-alarm
+ 9 CLOCK_BOOTTIME_ALARM boottime-alarm
+11 CLOCK_TAI tai
diff --git a/tests/expected/misc/lsclocks-time b/tests/expected/misc/lsclocks-time
new file mode 100644
index 0000000000..adc652433a
--- /dev/null
+++ b/tests/expected/misc/lsclocks-time
@@ -0,0 +1 @@
+X.X
diff --git a/tests/ts/misc/lsclocks b/tests/ts/misc/lsclocks
new file mode 100755
index 0000000000..55ee4df40e
--- /dev/null
+++ b/tests/ts/misc/lsclocks
@@ -0,0 +1,42 @@
+#!/bin/bash
+
+# Copyright (C) 2023 Thomas Weißschuh <thomas@t-8ch.de>
+#
+# This file is part of util-linux.
+#
+# This file 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 file is distributed in the hope that it will 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.
+
+TS_TOPDIR="${0%/*}/../.."
+TS_DESC="lsclocks"
+
+. "$TS_TOPDIR"/functions.sh
+ts_init "$*"
+
+ts_check_test_command "$TS_CMD_LSCLOCKS"
+ts_check_prog sed
+
+mask_timestamps() {
+ sed 's/[0-9]\+\.[0-9]\+/X.X/g'
+}
+
+ts_init_subtest basic
+
+"$TS_CMD_LSCLOCKS" -o ID,CLOCK,NAME > "$TS_OUTPUT" 2>> "$TS_ERRLOG"
+
+ts_finalize_subtest
+
+ts_init_subtest time
+
+"$TS_CMD_LSCLOCKS" --time monotonic | mask_timestamps > "$TS_OUTPUT" 2>> "$TS_ERRLOG"
+
+ts_finalize_subtest
+
+ts_finalize