File s390-tools-01-Add-zpwr-tool.patch of Package s390-tools

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 */
openSUSE Build Service is sponsored by