s390-tools/s390-tools-01-Add-zpwr-tool.patch

567 lines
14 KiB
Diff
Raw Permalink Normal View History

From 6004a7029cbd839eb5eaeff2276f81c57e068b74 Mon Sep 17 00:00:00 2001
From: Sumanth Korikkar <sumanthk@linux.ibm.com>
Date: Wed, 8 Jan 2025 08:57:02 +0100
Subject: [PATCH] s390-tools: Add zpwr tool
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
zpwr displays power readings of a partition and central processing
complex (CPC) from power information block (pib). pib is retrieved by
issuing diag324 ioctl to /dev/diag device.
Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com>
Signed-off-by: Sumanth Korikkar <sumanthk@linux.ibm.com>
Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
---
.gitignore | 1 +
Makefile | 3 +-
zpwr/Makefile | 23 +++
zpwr/zpwr.c | 430 ++++++++++++++++++++++++++++++++++++++++++++++++++
zpwr/zpwr.h | 46 ++++++
5 files changed, 502 insertions(+), 1 deletion(-)
create mode 100644 zpwr/Makefile
create mode 100644 zpwr/zpwr.c
create mode 100644 zpwr/zpwr.h
diff --git a/.gitignore b/.gitignore
index a0a01d93..23a8c5df 100644
--- a/.gitignore
+++ b/.gitignore
@@ -138,3 +138,4 @@ zkey/kmip/zkey-kmip.so
zkey/zkey
zkey/zkey-cryptsetup
zpcictl/zpcictl
+zpwr/zpwr
diff --git a/Makefile b/Makefile
index e1a23058..3fa6eabd 100644
--- a/Makefile
+++ b/Makefile
@@ -15,7 +15,8 @@ TOOL_DIRS = zipl zdump fdasd dasdfmt dasdview tunedasd \
vmcp man mon_tools dasdinfo vmur cpuplugd ipl_tools \
ziomon iucvterm hyptop cmsfs-fuse qethqoat zfcpdump zdsfs cpumf \
systemd hmcdrvfs cpacfstats zdev dump2tar zkey netboot etc zpcictl \
- lsstp hsci hsavmcore chreipl-fcp-mpath ap_tools rust opticsmon
+ lsstp hsci hsavmcore chreipl-fcp-mpath ap_tools rust opticsmon \
+ zpwr
else
BASELIB_DIRS =
diff --git a/zpwr/Makefile b/zpwr/Makefile
new file mode 100644
index 00000000..02581a68
--- /dev/null
+++ b/zpwr/Makefile
@@ -0,0 +1,23 @@
+#
+# Copyright IBM Corp. 2025
+#
+# s390-tools is free software; you can redistribute it and/or modify
+# it under the terms of the MIT license. See LICENSE for details.
+#
+
+include ../common.mak
+
+all: zpwr
+
+OBJECTS = zpwr.o
+
+zpwr: $(OBJECTS) $(rootdir)/libutil/libutil.a -lm
+
+install: all
+ $(INSTALL) -d -m 755 $(DESTDIR)$(USRBINDIR)
+ $(INSTALL) -g $(GROUP) -o $(OWNER) -m 755 zpwr $(DESTDIR)$(USRBINDIR)
+
+clean:
+ rm -f *.o *~ zpwr core
+
+.PHONY: all install clean
diff --git a/zpwr/zpwr.c b/zpwr/zpwr.c
new file mode 100644
index 00000000..40d19dbf
--- /dev/null
+++ b/zpwr/zpwr.c
@@ -0,0 +1,430 @@
+/*
+ * zpwr - Display power readings of s390 computing environment.
+ *
+ * Display power readings for resources in s390 computing environment from
+ * power information block (pib).
+ *
+ * Copyright IBM Corp. 2025
+ *
+ * s390-tools is free software; you can redistribute it and/or modify
+ * it under the terms of the MIT license. See LICENSE for details.
+ */
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <iconv.h>
+#include <math.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <time.h>
+
+#include "lib/util_base.h"
+#include "lib/util_fmt.h"
+#include "lib/util_opt.h"
+#include "lib/util_prg.h"
+#include "zpwr.h"
+
+#define DIAG "/dev/diag"
+#define NANO 1000000000ULL
+#define NUMUNIT 5
+#define NAMELEN 8
+#define COMPWIDTH 27
+#define OPT_FORMAT 256
+
+enum part_power {
+ CPU,
+ STORAGE,
+ IO,
+ MAX_PM_PARTITION,
+};
+
+enum cpc_power {
+ TOTAL,
+ UNASSIGNED,
+ INFRA,
+ MAX_PM_CPC,
+};
+
+struct zpwrinfo {
+ u64 part[MAX_PM_PARTITION];
+ u64 cpc[MAX_PM_CPC];
+ bool pvalid;
+ bool cvalid;
+};
+
+static const char *simplefmt_part[MAX_PM_PARTITION] = {
+ "LPAR CPU:",
+ "LPAR Storage:",
+ "LPAR I/O:",
+};
+
+static const char *simplefmt_cpc[MAX_PM_CPC] = {
+ "CPC Total:",
+ "CPC Unassigned Resources:",
+ "CPC Infrastructure:",
+};
+
+static const char *complexfmt_part[MAX_PM_PARTITION] = {
+ "cpu",
+ "storage",
+ "io",
+};
+
+static const char *complexfmt_cpc[MAX_PM_CPC] = {
+ "total",
+ "unassigned_resources",
+ "infrastructure",
+};
+
+static const struct util_prg prg = {
+ .desc = "Power readings of s390 computing environment",
+ .copyright_vec = {
+ {
+ .owner = "IBM Corp.",
+ .pub_first = 2025,
+ .pub_last = 2025,
+ },
+ UTIL_PRG_COPYRIGHT_END
+ }
+};
+
+static struct util_opt opt_vec[] = {
+ UTIL_OPT_SECTION("OPTIONS"),
+ {
+ .option = { "format", required_argument, NULL, OPT_FORMAT },
+ .argument = "FORMAT",
+ .flags = UTIL_OPT_FLAG_NOSHORT,
+ .desc = "List data in specified FORMAT (" FMT_TYPE_NAMES ")",
+ },
+ {
+ .option = { "delay", required_argument, NULL, 'd' },
+ .argument = "NUMBER",
+ .desc = "Power readings after delay (seconds)",
+ },
+ {
+ .option = { "count", required_argument, NULL, 'c' },
+ .argument = "NUMBER",
+ .desc = "Number of power readings",
+ },
+ {
+ .option = { "stream", no_argument, NULL, 's' },
+ .desc = "Power readings in stream mode",
+ },
+ UTIL_OPT_HELP,
+ UTIL_OPT_VERSION,
+ UTIL_OPT_END
+};
+
+static int get_max_column_width(struct zpwrinfo *pinfo)
+{
+ u64 max = 0;
+ int i, col;
+
+ for (i = 0; i < MAX_PM_PARTITION; i++) {
+ if (pinfo->part[i] > max)
+ max = pinfo->part[i];
+ }
+ for (i = 0; i < MAX_PM_CPC; i++) {
+ if (pinfo->cpc[i] > max)
+ max = pinfo->cpc[i];
+ }
+ col = (int)log10((double)max) + 1;
+ /* power unit and space consideration */
+ col += 3;
+ return col;
+}
+
+static char *get_human_readable_unit(u64 val)
+{
+ const char *unitstr[NUMUNIT] = { "uW", "mW", " W", "kW", "MW" };
+ int exponent[NUMUNIT] = { 1, 3, 6, 9, 12 }, unitindex = 0, i;
+ double res, smallestres = (double)val;
+ char *buf;
+
+ for (i = 1; i < NUMUNIT; i++) {
+ res = (double)val / pow(10, exponent[i]);
+ if ((u64)res && res < smallestres) {
+ smallestres = res;
+ unitindex = i;
+ }
+ }
+ util_asprintf(&buf, "%.2f %s", smallestres, unitstr[unitindex]);
+ return buf;
+}
+
+static double get_human_readable_interval(u64 val, bool *seconds)
+{
+ double res;
+
+ res = (double)val / pow(10, 9);
+ if ((u64)res)
+ *seconds = true;
+ else
+ *seconds = false;
+ return *seconds ? res : (double)val;
+}
+
+/* From linux arch/s390/include/asm/timex.h */
+static unsigned long tod_to_ns(unsigned long todval)
+{
+ return ((todval >> 9) * 125) + (((todval & 0x1ff) * 125) >> 9);
+}
+
+static void reset_zpwrinfo(struct zpwrinfo *pinfo, struct pib *pib,
+ unsigned long buffersize)
+{
+ memset(pinfo, 0, sizeof(*pinfo));
+ memset(pib, 0, buffersize);
+}
+
+static void print_zpwrinfo(struct zpwrinfo *pinfo, u64 iteration,
+ int fmt_specified, u64 interval)
+{
+ enum util_fmt_mflags_t fmt_mflags = FMT_DEFAULT;
+ bool secondflag = false;
+ struct timespec ts;
+ char timestr[30];
+ char *simplestr;
+ int i, colwidth;
+ struct tm *tm;
+
+ if (!fmt_specified) {
+ colwidth = get_max_column_width(pinfo);
+ for (i = 0; i < MAX_PM_PARTITION; i++) {
+ if (!pinfo->pvalid)
+ break;
+ printf("%-*s", COMPWIDTH, simplefmt_part[i]);
+ simplestr = get_human_readable_unit(pinfo->part[i]);
+ printf("%*s\n", colwidth, simplestr);
+ free(simplestr);
+ }
+ printf("\n");
+ for (i = 0; i < MAX_PM_CPC; i++) {
+ if (!pinfo->cvalid)
+ break;
+ printf("%-*s", COMPWIDTH, simplefmt_cpc[i]);
+ simplestr = get_human_readable_unit(pinfo->cpc[i]);
+ printf("%*s\n", colwidth, simplestr);
+ free(simplestr);
+ }
+ printf("\n");
+ printf("Update interval: %.2f %s\n",
+ get_human_readable_interval(interval, &secondflag),
+ secondflag ? "s" : "ns");
+ return;
+ }
+ clock_gettime(CLOCK_REALTIME, &ts);
+ tm = localtime(&ts.tv_sec);
+ strftime(timestr, sizeof(timestr), "%F %T%z", tm);
+ util_fmt_obj_start(FMT_ROW, "iteration");
+ util_fmt_pair(fmt_mflags, "iteration", "%llu", iteration);
+ util_fmt_pair(fmt_mflags, "time", "%s", timestr);
+ util_fmt_pair(fmt_mflags, "time_epoch_sec", "%lld", ts.tv_sec);
+ util_fmt_pair(fmt_mflags, "time_epoch_nsec", "%ld", ts.tv_nsec);
+ util_fmt_pair(fmt_mflags, "update_interval", "%llu", interval);
+ util_fmt_obj_start(FMT_LIST, "lpar");
+ for (i = 0; i < MAX_PM_PARTITION; i++)
+ util_fmt_pair(pinfo->pvalid ? fmt_mflags : fmt_mflags | FMT_INVAL,
+ complexfmt_part[i], "%llu", pinfo->part[i]);
+ util_fmt_obj_end(); /* End of lpar list */
+ util_fmt_obj_start(FMT_LIST, "cpc");
+ for (i = 0; i < MAX_PM_CPC; i++)
+ util_fmt_pair(pinfo->cvalid ? fmt_mflags : fmt_mflags | FMT_INVAL,
+ complexfmt_cpc[i], "%llu", pinfo->cpc[i]);
+ util_fmt_obj_end(); /* End of cpc list */
+ util_fmt_obj_end(); /* End of iteration row */
+}
+
+static int read_zpwrinfo(struct zpwrinfo *pinfo, struct pib *pib)
+{
+ struct pib_prologue *prologue;
+ int i, comp, max = 0, rc = 0;
+ u64 *curr_zpwrinfo;
+ u8 *metrics;
+ void *ptr;
+
+ ptr = (u8 *)pib + pib->hlen;
+ prologue = ptr;
+ for (i = 0; i < pib->num; i++) {
+ metrics = (u8 *)prologue + sizeof(*prologue);
+ if (prologue->format == 0) {
+ curr_zpwrinfo = pinfo->part;
+ max = MAX_PM_PARTITION;
+ pinfo->pvalid = true;
+ } else if (prologue->format == 1) {
+ curr_zpwrinfo = pinfo->cpc;
+ max = MAX_PM_CPC;
+ pinfo->cvalid = true;
+ } else {
+ rc = -EINVAL;
+ warnx("Unknown format detected:%d\n", prologue->format);
+ break;
+ }
+ metrics += NAMELEN;
+ for (comp = 0; comp < max; comp++) {
+ memcpy(&curr_zpwrinfo[comp], metrics, sizeof(u64));
+ metrics += sizeof(u64);
+ }
+ ptr = (u8 *)prologue + prologue->len;
+ prologue = ptr;
+ }
+ return rc;
+}
+
+static void fmt_start(enum util_fmt_t fmt, unsigned int fmt_flags,
+ int fmt_specified)
+{
+ if (!fmt_specified)
+ return;
+ util_fmt_init(stdout, fmt, fmt_flags, 1);
+ if (fmt != FMT_JSONSEQ)
+ util_fmt_obj_start(FMT_LIST, "zpwr");
+}
+
+static void fmt_end(enum util_fmt_t fmt, int fmt_specified)
+{
+ if (!fmt_specified)
+ return;
+ if (fmt != FMT_JSONSEQ)
+ util_fmt_obj_end(); /* zpwr[] */
+ util_fmt_exit();
+}
+
+int main(int argc, char *argv[])
+{
+ enum util_fmt_flags_t fmt_flags = FMT_HANDLEINT | FMT_QUOTEALL | FMT_KEEPINVAL;
+ int ch, fd, rc = EXIT_FAILURE, fmt_specified = 0;
+ bool stream = false, init = true;
+ enum util_fmt_t fmt = FMT_JSON;
+ u64 init_seq, interval = 0;
+ long count = 0, delay = 0;
+ struct diag324_pib data;
+ struct zpwrinfo *pinfo;
+ struct timespec ts;
+ size_t buffersize;
+ struct pib *pib;
+ struct stat st;
+
+ util_prg_init(&prg);
+ util_opt_init(opt_vec, NULL);
+ while (1) {
+ ch = util_opt_getopt_long(argc, argv);
+ if (ch == -1)
+ break;
+ switch (ch) {
+ case 'c':
+ errno = 0;
+ count = strtoul(optarg, NULL, 0);
+ if (errno || count <= 0)
+ errx(EXIT_FAILURE, "Positive number expected for option -%c", ch);
+ break;
+ case 'd':
+ errno = 0;
+ delay = strtoul(optarg, NULL, 0);
+ if (errno || delay <= 0)
+ errx(EXIT_FAILURE, "Positive number expected for option -%c", ch);
+ break;
+ case 's':
+ stream = true;
+ break;
+ case OPT_FORMAT:
+ if (!util_fmt_name_to_type(optarg, &fmt)) {
+ errx(EXIT_FAILURE, "Supported formats: %s", FMT_TYPE_NAMES);
+ } else {
+ if (fmt == FMT_CSV)
+ fmt_flags |= FMT_NOMETA;
+ else
+ fmt_flags |= FMT_DEFAULT;
+ fmt_specified = 1;
+ }
+ break;
+ case 'h':
+ util_prg_print_help();
+ util_opt_print_help();
+ return EXIT_SUCCESS;
+ case 'v':
+ util_prg_print_version();
+ return EXIT_SUCCESS;
+ default:
+ util_opt_print_parse_error(ch, argv);
+ return EXIT_FAILURE;
+ }
+ }
+ if (stream && delay)
+ errx(EXIT_FAILURE, "-s and -d option are mutually exclusive");
+ if (count && !delay && !stream)
+ errx(EXIT_FAILURE, "-c option can only be used in conjunction with -d or -s");
+ if (stat(DIAG, &st) == -1)
+ errx(EXIT_FAILURE, "Missing kernel support to retrieve power readings");
+ fd = open(DIAG, O_RDONLY);
+ if (fd < 0)
+ err(EXIT_FAILURE, "Open failed: %s", DIAG);
+ rc = ioctl(fd, DIAG324_GET_PIBLEN, &buffersize);
+ if (rc && errno == EOPNOTSUPP) {
+ warnx("The machine does not support retrieving power readings");
+ goto out;
+ } else if (rc) {
+ warn("Ioctl (DIAG324_GET_PIBLEN) failed");
+ goto out;
+ }
+ pinfo = calloc(1, sizeof(*pinfo));
+ if (!pinfo) {
+ warnx("Allocation of pinfo failed");
+ goto out;
+ }
+ pib = calloc(1, buffersize);
+ if (!pib) {
+ free(pinfo);
+ warnx("Allocation of pib failed");
+ goto out;
+ }
+ data.address = (u64)pib;
+ fmt_start(fmt, fmt_flags, fmt_specified);
+ while (true) {
+ rc = ioctl(fd, DIAG324_GET_PIBBUF, &data);
+ if (rc != 0 && errno != EBUSY) {
+ warn("Ioctl (DIAG324_GET_PIBBUF) failed");
+ goto out_free;
+ }
+ rc = read_zpwrinfo(pinfo, pib);
+ if (rc)
+ goto out_free;
+ if (init) {
+ init_seq = data.sequence;
+ init = false;
+ }
+ interval = tod_to_ns(pib->intv);
+ print_zpwrinfo(pinfo, data.sequence - init_seq, fmt_specified, interval);
+ reset_zpwrinfo(pinfo, pib, buffersize);
+ if (stream) {
+ ts.tv_sec = interval / NANO;
+ ts.tv_nsec = interval % NANO;
+ } else {
+ ts.tv_sec = delay;
+ ts.tv_nsec = 0;
+ }
+ if ((stream || delay) && !count) {
+ nanosleep(&ts, NULL);
+ continue;
+ } else if (--count > 0) {
+ nanosleep(&ts, NULL);
+ continue;
+ } else {
+ break;
+ }
+ }
+ fmt_end(fmt, fmt_specified);
+out_free:
+ free(pinfo);
+ free(pib);
+out:
+ close(fd);
+ return rc ? EXIT_FAILURE : EXIT_SUCCESS;
+}
diff --git a/zpwr/zpwr.h b/zpwr/zpwr.h
new file mode 100644
index 00000000..00052723
--- /dev/null
+++ b/zpwr/zpwr.h
@@ -0,0 +1,46 @@
+/*
+ * zpwr - display power readings of s390 computing environment.
+ *
+ * ioctls for diag324 and structures definitions.
+ *
+ * Copyright IBM Corp. 2025
+ *
+ * s390-tools is free software; you can redistribute it and/or modify
+ * it under the terms of the MIT license. See LICENSE for details.
+ */
+
+#ifndef ZPWR_H
+#define ZPWR_H
+
+#include <linux/types.h>
+
+#define DIAG_MAGIC_STR 'D'
+
+struct pib {
+ __u32 : 8;
+ __u32 num : 8;
+ __u32 len : 16;
+ __u32 : 24;
+ __u32 hlen : 8;
+ __u64 : 64;
+ __u64 intv;
+ __u8 r[];
+} __packed;
+
+struct pib_prologue {
+ __u64 format : 4;
+ __u64 : 20;
+ __u64 len : 8;
+ __u64 : 32;
+};
+
+struct diag324_pib {
+ __u64 address;
+ __u64 sequence;
+};
+
+/* Diag ioctl definitions */
+#define DIAG324_GET_PIBBUF _IOWR(DIAG_MAGIC_STR, 0x77, struct diag324_pib)
+#define DIAG324_GET_PIBLEN _IOR(DIAG_MAGIC_STR, 0x78, size_t)
+
+#endif /* ZPWR_H */