]> git.ipfire.org Git - thirdparty/util-linux.git/commitdiff
lsclocks: new util to interact with system clocks
authorThomas Weißschuh <thomas@t-8ch.de>
Sun, 25 Jun 2023 14:44:10 +0000 (16:44 +0200)
committerThomas Weißschuh <thomas@t-8ch.de>
Fri, 30 Jun 2023 12:12:48 +0000 (14:12 +0200)
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>
12 files changed:
bash-completion/Makemodule.am
bash-completion/lsclocks [new file with mode: 0644]
configure.ac
meson.build
misc-utils/Makemodule.am
misc-utils/lsclocks.1.adoc [new file with mode: 0644]
misc-utils/lsclocks.c [new file with mode: 0644]
misc-utils/meson.build
tests/commands.sh
tests/expected/misc/lsclocks-basic [new file with mode: 0644]
tests/expected/misc/lsclocks-time [new file with mode: 0644]
tests/ts/misc/lsclocks [new file with mode: 0755]

index ac8926f2d0ad90bdbec4eb71fdda5e22ac6bbf58..2763895e898ab725695546e82714fe14413bae9e 100644 (file)
@@ -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 (file)
index 0000000..bd81d88
--- /dev/null
@@ -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
index 8bd10ee69e01d8cbb64f74800c9d7d680cba87a3..8906e8e7fcda29776f1206a35e90fd5e32199d74 100644 (file)
@@ -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])
 
index 37141e7db89a3ee72163ab99b2ce07fb3fc61294..6c6dc527b28f078e10bcd39f7b48702d6b47c417 100644 (file)
@@ -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()
index dcc733b3fd19abd4217dd85397fc792907c2cb29..adee4111fad16a101f88ab18ee264b4af60c59f3 100644 (file)
@@ -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 (file)
index 0000000..5c7f5ea
--- /dev/null
@@ -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 (file)
index 0000000..8531e9f
--- /dev/null
@@ -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);
+}
index 7d21d02c15abee3837834fbf4ba1005d5e082614..a7cc4e4f3f04d51604514d1c03d5475cf1e3ed70 100644 (file)
@@ -165,3 +165,7 @@ fadvise_sources = files(
 waitpid_sources = files(
   'waitpid.c',
 )
+
+lsclocks_sources = files(
+  'lsclocks.c',
+)
index 38b425405b416c50bcc58c610bac95e49b9e0460..41c0ee98ce8ae1be54ad0c6de4576de78fbbdad4 100644 (file)
@@ -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 (file)
index 0000000..b3b25f4
--- /dev/null
@@ -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 (file)
index 0000000..adc6524
--- /dev/null
@@ -0,0 +1 @@
+X.X
diff --git a/tests/ts/misc/lsclocks b/tests/ts/misc/lsclocks
new file mode 100755 (executable)
index 0000000..55ee4df
--- /dev/null
@@ -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