xref: /spdk/scripts/perf/pm/collect-bmc-pm (revision fdea5c6daceeed2a89d8b82f67a9276d73db3526)
1b43a0112SMichal Berger#!/usr/bin/env bash
2eb53c232Spaul luse#  SPDX-License-Identifier: BSD-3-Clause
3eb53c232Spaul luse#  Copyright (C) 2022 Intel Corporation
4eb53c232Spaul luse#  All rights reserved.
5eb53c232Spaul luse
6b43a0112SMichal Bergerset -e
7b43a0112SMichal Berger
8d5fe62b2SMichal Bergerpmdir=$(readlink -f "$(dirname "$0")")
9d5fe62b2SMichal Bergerrootdir=$(readlink -f "$pmdir/../../../")
10d5fe62b2SMichal Bergersource "$pmdir/common"
11d5fe62b2SMichal Berger
12b43a0112SMichal Bergerhex() { printf '0x%02x\n' "$@"; }
13b43a0112SMichal Berger
147ee11c56SMichal Bergercalc() { bc <<< "scale=2; $*"; }
157ee11c56SMichal Berger
16b43a0112SMichal Bergeris_root() {
17b43a0112SMichal Berger	# Talking to local BMC device requires root privileges
18b43a0112SMichal Berger	if ((UID)); then
19b43a0112SMichal Berger		printf '%s, you need to be root to run this script\n' "$USER" >&2
20b43a0112SMichal Berger		return 1
21b43a0112SMichal Berger	fi
22b43a0112SMichal Berger
23b43a0112SMichal Berger}
24b43a0112SMichal Berger
25b43a0112SMichal Bergeris_ipmitool() {
26b43a0112SMichal Berger	if ! type -P ipmitool; then
27b43a0112SMichal Berger		printf 'ipmitool not detected, cannot run commands against the BMC\n' >&2
28b43a0112SMichal Berger		return 1
29b43a0112SMichal Berger	fi
30b43a0112SMichal Berger}
31b43a0112SMichal Berger
32b43a0112SMichal Bergeripmi_load() {
33b43a0112SMichal Berger	# Silently attempt to load core ipmi drivers - we will pick up the device later on.
34b43a0112SMichal Berger	modprobe -qa ipmi_si ipmi_devintf ipmi_msghandler || return 0
35b43a0112SMichal Berger}
36b43a0112SMichal Berger
37b43a0112SMichal Bergeripmi_supported() {
38b43a0112SMichal Berger	# Verify if kernel detected and registered at least one BMC under
39b43a0112SMichal Berger	# the ipmi platform. Look for KCS specifically as this the type
40b43a0112SMichal Berger	# of the interface the script was tested against.
41b43a0112SMichal Berger
42b43a0112SMichal Berger	local ipmi=/sys/class/ipmi/ipmi0
43b43a0112SMichal Berger
44b43a0112SMichal Berger	# Keep these details global for easy access if needed.
45b43a0112SMichal Berger	local -g man_id prod_id dev_id ipmi_ver platform board ipmitool
46b43a0112SMichal Berger
47b43a0112SMichal Berger	ipmi_load
48b43a0112SMichal Berger
49b43a0112SMichal Berger	if [[ ! -e $ipmi ]]; then
50b43a0112SMichal Berger		printf 'BMC not detected. Please, make sure your platform is IPMI-compatible\n'
51b43a0112SMichal Berger		return 1
52b43a0112SMichal Berger	fi >&2
53b43a0112SMichal Berger
54b43a0112SMichal Berger	type=$(< "$ipmi/device/type")
55b43a0112SMichal Berger
56b43a0112SMichal Berger	if [[ $type != kcs ]]; then
57b43a0112SMichal Berger		printf 'No supported BMC interface detected (%s) - only KCS is supported\n' "$type"
58b43a0112SMichal Berger		return 1
59b43a0112SMichal Berger	fi >&2
60b43a0112SMichal Berger
61b43a0112SMichal Berger	man_id=$(< "$ipmi/device/bmc/manufacturer_id")
62b43a0112SMichal Berger	prod_id=$(< "$ipmi/device/bmc/product_id")
63b43a0112SMichal Berger	dev_id=$(hex "$(< "$ipmi/device/bmc/device_id")")
64b43a0112SMichal Berger	ipmi_ver=$(< "$ipmi/device/bmc/ipmi_version")
65b43a0112SMichal Berger
66b43a0112SMichal Berger	if [[ -e /sys/class/dmi/id/board_vendor ]]; then
67b43a0112SMichal Berger		platform=$(< /sys/class/dmi/id/board_vendor)
68b43a0112SMichal Berger	fi
69b43a0112SMichal Berger
70b43a0112SMichal Berger	if [[ -e /sys/class/dmi/id/board_name ]]; then
71b43a0112SMichal Berger		board=$(< /sys/class/dmi/id/board_name)
72b43a0112SMichal Berger	fi
73b43a0112SMichal Berger
74b43a0112SMichal Berger	# Keep output similar to ipmi_si's
75b43a0112SMichal Berger	cat <<- BMC_DEV >&2
76b43a0112SMichal Berger
77b43a0112SMichal Berger		BMC detected, details below:
78b43a0112SMichal Berger		Manufacturer ID: $man_id
79b43a0112SMichal Berger		Product ID: $prod_id
80b43a0112SMichal Berger		Device ID: $dev_id
81b43a0112SMichal Berger		IPMI Version: $ipmi_ver
82b43a0112SMichal Berger		Platform: ${platform:-unknown}
83b43a0112SMichal Berger		Board: ${board:-unknown}
84b43a0112SMichal Berger
85b43a0112SMichal Berger	BMC_DEV
86b43a0112SMichal Berger
87b43a0112SMichal Berger	# Verify if we have proper tools to work with
88b43a0112SMichal Berger	ipmitool=$(is_ipmitool)
89b43a0112SMichal Berger}
90b43a0112SMichal Berger
91b43a0112SMichal Bergeripmiraw() {
92b43a0112SMichal Berger	# For the majority of commands we use raw payload to not depend on specific ipmitool version
93b43a0112SMichal Berger	# and the way how it interprets/parses the returned data. This also allows us to inspect the
94b43a0112SMichal Berger	# integrity of data more closely to make sure we don't report nonsensical values to the user.
95b43a0112SMichal Berger
96b43a0112SMichal Berger	local rsp
97b43a0112SMichal Berger
98b43a0112SMichal Berger	rsp=($("$ipmitool" raw "$@" 2> /dev/null))
99b43a0112SMichal Berger	# Slap hex prefix to work with proper base
100b43a0112SMichal Berger	rsp=("${rsp[@]/#/0x}")
101b43a0112SMichal Berger
102b43a0112SMichal Berger	hex "${rsp[@]}"
103b43a0112SMichal Berger}
104b43a0112SMichal Berger
105b43a0112SMichal Bergerdcmiraw() {
106b43a0112SMichal Berger	local cmd=$1 data=("${@:2}")
107b43a0112SMichal Berger
108b43a0112SMichal Berger	ipmiraw 0x2c "$cmd" 0xdc "${data[@]}"
109b43a0112SMichal Berger}
110b43a0112SMichal Berger
111b43a0112SMichal Bergerprint_dcmi_available_time_periods() {
112b43a0112SMichal Berger	local time_periods=${enhanced_power_attr[4]}
113b43a0112SMichal Berger	local -g available_time_periods=()
114b43a0112SMichal Berger	local -g available_time_periods_in_seconds=()
115b43a0112SMichal Berger
116b43a0112SMichal Berger	available_time_periods[0]="NOW"
117b43a0112SMichal Berger
118b43a0112SMichal Berger	if ((time_periods > 0)); then
119b43a0112SMichal Berger		local time_idx=5
120b43a0112SMichal Berger		local offset=$time_idx
121b43a0112SMichal Berger		local units unit time time_s units_mask=0xc0 to_sec
122b43a0112SMichal Berger
123b43a0112SMichal Berger		units[0x0]=seconds
124b43a0112SMichal Berger		units[0x1]=minutes
125b43a0112SMichal Berger		units[0x2]=hours
126b43a0112SMichal Berger		units[0x3]=days
127b43a0112SMichal Berger
128b43a0112SMichal Berger		to_sec[0x0]=1
129b43a0112SMichal Berger		to_sec[0x1]=60
130b43a0112SMichal Berger		to_sec[0x2]=3600
131b43a0112SMichal Berger		to_sec[0x3]=86400
132b43a0112SMichal Berger
133b43a0112SMichal Berger		while ((offset < time_idx + time_periods)); do
134b43a0112SMichal Berger			time=$((enhanced_power_attr[offset] & ~units_mask))
135b43a0112SMichal Berger			unit=${units[enhanced_power_attr[offset] >> 6]:-unknown}
136b43a0112SMichal Berger			time_s=$((time * to_sec[enhanced_power_attr[offset] >> 6]))
137b43a0112SMichal Berger			if ((time != 0)); then
138b43a0112SMichal Berger				available_time_periods[offset]="$time $unit"
139b43a0112SMichal Berger				available_time_periods_in_seconds[time_s]=${enhanced_power_attr[offset]}
140b43a0112SMichal Berger			fi
141b43a0112SMichal Berger			((++offset))
142b43a0112SMichal Berger		done
143b43a0112SMichal Berger	fi
144b43a0112SMichal Berger	cat <<- TIME_PERIODS >&2
145b43a0112SMichal Berger
146b43a0112SMichal Berger		Available averaging time periods to request:
147b43a0112SMichal Berger		$(printf '  - %s\n' "${available_time_periods[@]}")
148b43a0112SMichal Berger
149b43a0112SMichal Berger	TIME_PERIODS
150b43a0112SMichal Berger}
151b43a0112SMichal Berger
152b43a0112SMichal Bergerdcmi_power_support() {
153b43a0112SMichal Berger	# Verify if the BMC conforms to the DCMI spec
154b43a0112SMichal Berger	local rsp
155b43a0112SMichal Berger
156b43a0112SMichal Berger	# Table 6-2, Get DCMI Capabilities Command Format
157b43a0112SMichal Berger	if ! rsp=($(dcmiraw 0x1 0x1)); then
158b43a0112SMichal Berger		printf 'Cannot determine if BMC supports DCMI Power Management capability\n' >&2
159b43a0112SMichal Berger		return 1
160b43a0112SMichal Berger	fi
161b43a0112SMichal Berger
162b43a0112SMichal Berger	# Table 6-3, DCMI Capabilities Parameters:
163b43a0112SMichal Berger	#  - Supported DCMI Capabilities:
164b43a0112SMichal Berger	#    - Byte 2 Platform capabilities: [0] Power management
165b43a0112SMichal Berger	if ((!(rsp[5] & (1 << 0)))); then
166b43a0112SMichal Berger		printf 'BMC does not provide DCMI Power Mangament capability\n' >&2
167b43a0112SMichal Berger		return 1
168b43a0112SMichal Berger	fi
169b43a0112SMichal Berger
170b43a0112SMichal Berger	# Check if BMC provides Enhanced System Power Statistics attributes - this allows to issue
171b43a0112SMichal Berger	# requests for power readings at averaging time period, .e.g. from last 5 seconds, 30 minutes,
172b43a0112SMichal Berger	# 1 hour and so on. With this we can provide more detailed view on power usage within a
173b43a0112SMichal Berger	# specific period of time. Without it, we need to depend only on current reading that should
174b43a0112SMichal Berger	# be always available (the "NOW" reading).
175b43a0112SMichal Berger
176b43a0112SMichal Berger	local -g enhanced_power_attr=()
177b43a0112SMichal Berger
178b43a0112SMichal Berger	# Table 6-3, DCMI Capabilities Parameters:
179b43a0112SMichal Berger	#  - Enhanced System Power Statistics attributes
180b43a0112SMichal Berger	if enhanced_power_attr=($(dcmiraw 0x1 0x5)); then
181b43a0112SMichal Berger		print_dcmi_available_time_periods
182b43a0112SMichal Berger	fi
183b43a0112SMichal Berger
184b43a0112SMichal Berger	printf 'Using DCMI Power Management\n' >&2
185b43a0112SMichal Berger}
186b43a0112SMichal Berger
187b43a0112SMichal Bergersdr_power_support() {
188b43a0112SMichal Berger	# This is a fallback which only some platforms may provide (confirmed PowerEdge and CYP).
189b43a0112SMichal Berger	# We are looking for a full, threshold sensor which reports overall power usage in Watts.
190b43a0112SMichal Berger	# Different BMCs may have SDRs which describe such sensor(s) differently so this is not
191b43a0112SMichal Berger	# 100% reliable. To make sure we pick up a proper sensor we also narrow it down to a
192b43a0112SMichal Berger	# specific entity (System Board or Power Supply). Readings from the sensor should be
193b43a0112SMichal Berger	# considered as "NOW" readings (without access to min, max readings).
194b43a0112SMichal Berger
195b43a0112SMichal Berger	local -g power_sensors=()
196b43a0112SMichal Berger	local sensor entity unit status
197b43a0112SMichal Berger
198b43a0112SMichal Berger	# Cache SDR to speed up sensor readings
199b43a0112SMichal Berger	if [[ ! -f $sdr_cache ]]; then
200b43a0112SMichal Berger		printf 'Saving SDR cache at %s\n' "$sdr_cache" >&2
201b43a0112SMichal Berger		"$ipmitool" sdr dump "$sdr_cache" > /dev/null
202b43a0112SMichal Berger	fi
203b43a0112SMichal Berger
204b43a0112SMichal Berger	if ((${#extra_power_sensors[@]} > 0)); then
205b43a0112SMichal Berger		power_sensors+=("${extra_power_sensors[@]}")
206b43a0112SMichal Berger	fi
207b43a0112SMichal Berger
208b43a0112SMichal Berger	while IFS="," read -r sensor _ unit status _ entity _; do
209b43a0112SMichal Berger		[[ $unit == Watts && $status == ok ]] || continue
210b43a0112SMichal Berger		[[ $entity == "System Board" || $entity == "Power Supply" ]] || continue
211b43a0112SMichal Berger		power_sensors+=("$sensor")
212b43a0112SMichal Berger	done < <("$ipmitool" -S "$sdr_cache" -vc sdr list full 2>&1)
213b43a0112SMichal Berger
214b43a0112SMichal Berger	if ((${#power_sensors[@]} > 0)); then
215b43a0112SMichal Berger		printf 'Using SDR (Power sensors: %s)\n' "${power_sensors[*]}"
216b43a0112SMichal Berger	else
217b43a0112SMichal Berger		printf 'Cannot locate power sensors\n'
218b43a0112SMichal Berger		return 1
219b43a0112SMichal Berger	fi >&2
220b43a0112SMichal Berger}
221b43a0112SMichal Berger
222b43a0112SMichal Bergerpower_support() {
2236fb5eae6SMichal Berger	local -g support cpu_support=0
2246fb5eae6SMichal Berger
2256fb5eae6SMichal Berger	if ((include_cpu == 1)) && rapl_supported; then
2266fb5eae6SMichal Berger		cpu_support=1
2276fb5eae6SMichal Berger	fi
228b43a0112SMichal Berger
229b43a0112SMichal Berger	if [[ $interface == dcmi || $interface == sdr ]]; then
230b43a0112SMichal Berger		# override
231b43a0112SMichal Berger		"${interface}_power_support"
232b43a0112SMichal Berger		support=$interface
233b43a0112SMichal Berger	elif dcmi_power_support; then
234b43a0112SMichal Berger		support=dcmi
235b43a0112SMichal Berger	elif sdr_power_support; then
236b43a0112SMichal Berger		support=sdr
237b43a0112SMichal Berger	else
2386fb5eae6SMichal Berger		printf 'BMC does not provide Power Management support, cannot gather system-wide power measurements\n' >&2
2396fb5eae6SMichal Berger		if ((cpu_support)); then
2406fb5eae6SMichal Berger			printf 'Only CPU measurements will be provided\n' >&2
2416fb5eae6SMichal Berger			return 0
2426fb5eae6SMichal Berger		fi
243b43a0112SMichal Berger		return 1
244b43a0112SMichal Berger	fi
245b43a0112SMichal Berger}
246b43a0112SMichal Berger
247b43a0112SMichal Bergerget_dcmi_now_reading() {
248b43a0112SMichal Berger	local rsp reading=0 max min avg ts timeframe mode=01h
249b43a0112SMichal Berger	local get_cmd get_avg=0 print
250b43a0112SMichal Berger
251b43a0112SMichal Berger	# Table 6-16, Get Power Reading Command:
252b43a0112SMichal Berger	get_cmd=(0x2 0x1 0x0 0x0)
253b43a0112SMichal Berger
254f83a2d3aSMichal Berger	if [[ $interval =~ ^[0-9]+$ && -n ${available_time_periods_in_seconds[interval]} ]]; then
255b43a0112SMichal Berger		get_cmd=(0x2 0x2 "${available_time_periods_in_seconds[interval]}" 0x0)
256b43a0112SMichal Berger		get_avg=1
257b43a0112SMichal Berger		mode=02h
258b43a0112SMichal Berger	fi
259b43a0112SMichal Berger
260b43a0112SMichal Berger	# We use System Power Statistics mode to get the "NOW" reading by default. In case
261b43a0112SMichal Berger	# interval matches one supported by Enhanced System Power Statistics we use that
262b43a0112SMichal Berger	# mode to obtain extra min, max, avg statistics.
263b43a0112SMichal Berger
264b43a0112SMichal Berger	if ! rsp=($(dcmiraw "${get_cmd[@]}")); then
265b43a0112SMichal Berger		printf 'DCMI reading: error\n'
266b43a0112SMichal Berger	else
267b43a0112SMichal Berger		# Note that the BMC timestamp depends on the hwclock setup which we then attempt
268b43a0112SMichal Berger		# to represent in UTC.
269b43a0112SMichal Berger		ts=$((rsp[12] << 24 | rsp[11] << 16 | rsp[10] << 8 | rsp[9]))
270b43a0112SMichal Berger		# This is interpreted differently by different BMCs so for now we make a note of
271b43a0112SMichal Berger		# it but don't present it to the user.
272b43a0112SMichal Berger		timeframe=$((rsp[16] << 24 | rsp[15] << 16 | rsp[14] << 8 | rsp[13]))
273b43a0112SMichal Berger		reading=$((rsp[2] << 8 | rsp[1]))
274b43a0112SMichal Berger		if ((get_avg == 1)); then
275b43a0112SMichal Berger			min=$((rsp[4] << 8 | rsp[3]))
276b43a0112SMichal Berger			max=$((rsp[6] << 8 | rsp[5]))
277b43a0112SMichal Berger			avg=$((rsp[8] << 8 | rsp[7]))
278b43a0112SMichal Berger			_DCMI_min+=("$min")
279b43a0112SMichal Berger			_DCMI_max+=("$max")
280b43a0112SMichal Berger			_DCMI_avg+=("$avg")
281b43a0112SMichal Berger			power_readings["DCMI_MIN"]="_DCMI_min[@]"
282b43a0112SMichal Berger			power_readings["DCMI_MAX"]="_DCMI_max[@]"
283b43a0112SMichal Berger			power_readings["DCMI_AVG"]="_DCMI_avg[@]"
284b43a0112SMichal Berger		fi
285b43a0112SMichal Berger		_DCMI+=("$reading")
286b43a0112SMichal Berger		power_readings["DCMI"]="_DCMI[@]"
287b43a0112SMichal Berger
288b43a0112SMichal Berger		for print in min max avg reading; do
289b43a0112SMichal Berger			[[ -n ${!print} ]] || continue
290*fdea5c6dSMichal Berger			printf '(%s) DCMI %s (mode: %s): %u Watts (interval: %ss, test: %s)\n' \
291b43a0112SMichal Berger				"$(utc "$ts")" \
292b43a0112SMichal Berger				"$print" \
293b43a0112SMichal Berger				"$mode" \
294b43a0112SMichal Berger				"${!print}" \
295*fdea5c6dSMichal Berger				"$interval" \
296*fdea5c6dSMichal Berger				"$TEST_TAG" >&2
297b43a0112SMichal Berger		done
298b43a0112SMichal Berger	fi >&2
299b43a0112SMichal Berger}
300b43a0112SMichal Berger
301b43a0112SMichal Bergerget_sdr_now_reading() {
302b43a0112SMichal Berger	local sensor reading=0 ts unit
303b43a0112SMichal Berger
304b43a0112SMichal Berger	if ((${#power_sensors[@]} == 0)); then
305b43a0112SMichal Berger		printf 'No power sensors were provided\n' >&2
306b43a0112SMichal Berger		return 1
307b43a0112SMichal Berger	fi
308b43a0112SMichal Berger
309b43a0112SMichal Berger	for sensor in "${!power_sensors[@]}"; do
310b43a0112SMichal Berger		ts=$(utc)
311b43a0112SMichal Berger		if ! IFS="," read -r _ reading unit _; then
312b43a0112SMichal Berger			reading=error
313b43a0112SMichal Berger		else
314b43a0112SMichal Berger			eval "_sensor${sensor}_readings+=($reading)"
315b43a0112SMichal Berger			power_readings["${power_sensors[sensor]}"]="_sensor${sensor}_readings[@]"
316b43a0112SMichal Berger			reading+=" $unit"
317b43a0112SMichal Berger		fi < <("$ipmitool" -c -S "$sdr_cache" sdr get "${power_sensors[sensor]}") 2> /dev/null
318*fdea5c6dSMichal Berger		printf '(%s) Sensor %s reading: %s (interval: %ss, test: %s)\n' \
319b43a0112SMichal Berger			"$ts" \
320b43a0112SMichal Berger			"${power_sensors[sensor]}" \
321b43a0112SMichal Berger			"$reading" \
322*fdea5c6dSMichal Berger			"$interval" \
323*fdea5c6dSMichal Berger			"$TEST_TAG" >&2
324b43a0112SMichal Berger	done
325b43a0112SMichal Berger}
326b43a0112SMichal Berger
3276fb5eae6SMichal Bergerrapl_supported() {
3286fb5eae6SMichal Berger	[[ -e /sys/class/powercap/intel-rapl ]]
3296fb5eae6SMichal Berger}
3306fb5eae6SMichal Berger
3316fb5eae6SMichal Bergerget_cpu_socket_reading() {
3326fb5eae6SMichal Berger	local rapl=/sys/class/powercap
3336fb5eae6SMichal Berger	local socket socket_idx _socket_idx socket_name
3346fb5eae6SMichal Berger	local ts reading
3356fb5eae6SMichal Berger
3366fb5eae6SMichal Berger	# power_uw is usually not available so we need to relay on energy_uj. It's also rarely
3376fb5eae6SMichal Berger	# rw so we can't zero it out, hence we need to keep track of the initial counter. For
3386fb5eae6SMichal Berger	# details see kernel documentation (powercap.rst).
3396fb5eae6SMichal Berger	ts=$(utc)
3406fb5eae6SMichal Berger	for socket in /sys/class/powercap/intel-rapl:*; do
3416fb5eae6SMichal Berger		[[ -e $socket ]] || continue
3426fb5eae6SMichal Berger
3436fb5eae6SMichal Berger		socket_idx=${socket#*:} socket_name=$(< "$socket/name")
3446fb5eae6SMichal Berger		# Adjust for different domains, see linux/intel_rapl.h
3456fb5eae6SMichal Berger		case "$socket_name" in
3466fb5eae6SMichal Berger			dram | core | uncore) _socket_idx=${socket_idx//:/_} socket_idx=${socket_idx%:*} ;;
3476fb5eae6SMichal Berger			package-*) _socket_idx=$socket_idx socket_name=socket ;;
3486fb5eae6SMichal Berger			psys*) _socket_idx=$socket_idx socket_name=platform ;;
3496fb5eae6SMichal Berger		esac
3506fb5eae6SMichal Berger
3516fb5eae6SMichal Berger		local -n socket_uj=socket_${_socket_idx}_uj
3526fb5eae6SMichal Berger		socket_uj+=("$(< "$socket/energy_uj")")
3536fb5eae6SMichal Berger		# We need at least two readings for comparison
3546fb5eae6SMichal Berger		((${#socket_uj[@]} > 1)) || continue
3556fb5eae6SMichal Berger
3566fb5eae6SMichal Berger		# Convert to Watts - use bc since $interval can be an actual float
3577ee11c56SMichal Berger		reading=$(calc "(${socket_uj[-1]} - ${socket_uj[-2]}) / 1000000 / $interval")
358df2283c5SMichal Berger		if [[ $reading == "-"* ]]; then
35966ed26e0SMichal Berger			# Somehow this may happen, probably when the counter wraps over. Consider
36066ed26e0SMichal Berger			# this as a faulty reading and don't include it since it may impact overall
36166ed26e0SMichal Berger			# avg.
362*fdea5c6dSMichal Berger			printf '(%s) CPU %s %s reading: error(%s) (interval: %ss, test: %s)\n' \
36366ed26e0SMichal Berger				"$ts" \
36466ed26e0SMichal Berger				"$socket_name" \
36566ed26e0SMichal Berger				"$socket_idx" \
36666ed26e0SMichal Berger				"$reading" \
367*fdea5c6dSMichal Berger				"$interval" \
368*fdea5c6dSMichal Berger				"$TEST_TAG" >&2
36966ed26e0SMichal Berger			return 0
37066ed26e0SMichal Berger		fi
3716fb5eae6SMichal Berger		eval "_socket${_socket_idx}_readings+=($reading)"
3726fb5eae6SMichal Berger		power_readings["$socket_name-$socket_idx"]="_socket${_socket_idx}_readings[@]"
3736fb5eae6SMichal Berger
374*fdea5c6dSMichal Berger		printf '(%s) CPU %s %s reading: %s Watts (interval: %ss, test: %s)\n' \
3756fb5eae6SMichal Berger			"$ts" \
3766fb5eae6SMichal Berger			"$socket_name" \
3776fb5eae6SMichal Berger			"$socket_idx" \
3786fb5eae6SMichal Berger			"$reading" \
379*fdea5c6dSMichal Berger			"$interval" \
380*fdea5c6dSMichal Berger			"$TEST_TAG" >&2
3816fb5eae6SMichal Berger	done
3826fb5eae6SMichal Berger}
3836fb5eae6SMichal Berger
384b43a0112SMichal Bergerget_now_reading() {
385b43a0112SMichal Berger	case "$support" in
386b43a0112SMichal Berger		dcmi) get_dcmi_now_reading ;;
387b43a0112SMichal Berger		sdr) get_sdr_now_reading ;;
388b43a0112SMichal Berger		*) ;;
389b43a0112SMichal Berger	esac
390b43a0112SMichal Berger}
391b43a0112SMichal Berger
392b43a0112SMichal Bergerdump_readings() {
393b43a0112SMichal Berger	local sensor reading readings avg total
394b43a0112SMichal Berger
395b43a0112SMichal Berger	((${#power_readings[@]} > 0)) || return 1
396b43a0112SMichal Berger	printf 'Dumping average sensors reading from %s\n' "${!power_readings[*]}" >&2
397b43a0112SMichal Berger
398b43a0112SMichal Berger	for sensor in "${!power_readings[@]}"; do
399b43a0112SMichal Berger		readings=("${!power_readings["$sensor"]}")
400b43a0112SMichal Berger		if ((${#readings[@]} == 0)); then
401b43a0112SMichal Berger			printf 'No readings available for %s sensor\n' "$sensor" >&2
402b43a0112SMichal Berger			continue
403b43a0112SMichal Berger		fi
404b43a0112SMichal Berger		total=0
405b43a0112SMichal Berger		for reading in "${readings[@]}"; do
4067ee11c56SMichal Berger			total=$(calc "$total + $reading")
407b43a0112SMichal Berger		done
4087ee11c56SMichal Berger		avg=$(calc "$total / ${#readings[@]}")
409daeadb17SMichal Berger
410daeadb17SMichal Berger		readings+=("Total: ${#readings[@]}")
411a86fa6d1SKarol Latecki		sensor="${sensor//[[:space:]]/_}"
412d5fe62b2SMichal Berger		printf '%s\n' "$avg" > "$PM_OUTPUTDIR/${prefix:+${prefix}_}avg_${sensor}.bmc.pm.txt"
413d5fe62b2SMichal Berger		printf '%s\n' "${readings[@]}" > "$PM_OUTPUTDIR/${prefix:+${prefix}_}all_${sensor}.bmc.pm.txt"
414d5fe62b2SMichal Berger		printf 'Dumped avg to %s\n' "$PM_OUTPUTDIR/${prefix:+${prefix}_}avg_${sensor}.bmc.pm.txt" >&2
415d5fe62b2SMichal Berger		printf 'Dumped all to %s\n' "$PM_OUTPUTDIR/${prefix:+${prefix}_}all_${sensor}.bmc.pm.txt" >&2
416b43a0112SMichal Berger	done
417b43a0112SMichal Berger}
418b43a0112SMichal Berger
419b43a0112SMichal Bergerutc() {
420b43a0112SMichal Berger	date --utc ${1:+-"d@$1"}
421b43a0112SMichal Berger}
422b43a0112SMichal Berger
423b43a0112SMichal Bergercleanup() {
424d5fe62b2SMichal Berger	rm_pm_pid
425b43a0112SMichal Berger	[[ -f $sdr_cache && $remove_sdr_cache == yes ]] && rm "$sdr_cache"
426b43a0112SMichal Berger	dump_readings
427b43a0112SMichal Berger}
428b43a0112SMichal Berger
429b43a0112SMichal Bergercollect_readings() {
430b43a0112SMichal Berger	local _count=$count
4316fb5eae6SMichal Berger	if ((_count == 1 && cpu_support)); then
4326fb5eae6SMichal Berger		# We need at least two readings to get a meaningful data
4336fb5eae6SMichal Berger		((_count += 1))
4346fb5eae6SMichal Berger	fi
435b43a0112SMichal Berger	while ((count <= 0 ? 1 : _count--)); do
436b43a0112SMichal Berger		get_now_reading
4376fb5eae6SMichal Berger		((cpu_support)) && get_cpu_socket_reading
438b43a0112SMichal Berger		sleep "${interval}s"
439b43a0112SMichal Berger	done
440b43a0112SMichal Berger}
441b43a0112SMichal Berger
442b43a0112SMichal Bergerhelp() {
443b43a0112SMichal Berger	cat <<- HELP
444b43a0112SMichal Berger
445ad80b46eSMichal Berger		Usage: $0 [-h] [-d dir] [-i sdr|dcmi] [-s SENSOR_NAME] [-t interval] [-l] [-p prefix] [-c count] [-r]
446b43a0112SMichal Berger
447b43a0112SMichal Berger		  -h - Print this message.
448b43a0112SMichal Berger		  -d - Directory where the results should be saved. Default is /tmp.
449b43a0112SMichal Berger		  -i - Type of interface to use for requesting power usage. "sdr" or "dcmi".
450b43a0112SMichal Berger		       If not set, available interface is used ("dcmi" has priority).
451b43a0112SMichal Berger		  -t - How long to wait before each get power command in seconds. In case
452b43a0112SMichal Berger		       this value matches one of supported averaging time periods special
453b43a0112SMichal Berger		       variant of the command will be used to obtain the reading - this
454b43a0112SMichal Berger		       variant is used only with the "dcmi" interface. Default is 1s.
455b43a0112SMichal Berger		  -s - In case "sdr" interface is in use, try to read data from SENSOR_NAME.
456b43a0112SMichal Berger		  -x - In case "sdr" interface is in use, don't remove SDR cache. This can
457b43a0112SMichal Berger		       speed up subsequent runs of the script.
458b43a0112SMichal Berger		  -l - Save output of the script to a log file (dir/${0##*/}.bmc.pm.log).
459b43a0112SMichal Berger		  -p - Add prefix to saved files.
460b43a0112SMichal Berger		  -c - Read power usage count times. 0 is the default and it means to run
461b43a0112SMichal Berger		       indefinitely.
4626fb5eae6SMichal Berger		  -r - Include readings from CPU sockets (RAPL-dependent)
463b43a0112SMichal Berger
464b43a0112SMichal Berger		When started, ${0##*/} will enter loop to continuously read power usage from either
465b43a0112SMichal Berger		DCMI interface or dedicated Watts sensors every interval. Each reading will be
466b43a0112SMichal Berger		logged to stderr. Upon termination, average power usage will be dumped to /tmp or
467b43a0112SMichal Berger		directory set by -d.
468b43a0112SMichal Berger
469b43a0112SMichal Berger	HELP
470b43a0112SMichal Berger}
471b43a0112SMichal Berger
472b43a0112SMichal Bergeris_root
473b43a0112SMichal Berger
474b43a0112SMichal Bergerinterval=1
475b43a0112SMichal Bergerremove_sdr_cache=yes
476b43a0112SMichal Bergerlog_to_file=no
477b43a0112SMichal Bergerprefix=""
478b43a0112SMichal Bergercount=0
4796fb5eae6SMichal Bergerinclude_cpu=0
480b43a0112SMichal Berger
481b43a0112SMichal Bergerdeclare -A power_readings=()
482b43a0112SMichal Bergerdeclare -a extra_power_sensors=()
483b43a0112SMichal Berger
4846fb5eae6SMichal Bergerwhile getopts :hi:s:d:t:xlp:c:r arg; do
485b43a0112SMichal Berger	case "$arg" in
486b43a0112SMichal Berger		h)
487b43a0112SMichal Berger			help
488b43a0112SMichal Berger			exit 0
489b43a0112SMichal Berger			;;
490d5fe62b2SMichal Berger		d) PM_OUTPUTDIR=$OPTARG ;;
491b43a0112SMichal Berger		s) extra_power_sensors+=("$OPTARG") ;;
492b43a0112SMichal Berger		i) interface=${OPTARG,,} ;;
493b43a0112SMichal Berger		t) interval=$OPTARG ;;
494b43a0112SMichal Berger		x) remove_sdr_cache=no ;;
495b43a0112SMichal Berger		l) log_to_file=yes ;;
496b43a0112SMichal Berger		p) prefix=$OPTARG ;;
497b43a0112SMichal Berger		c) count=$OPTARG ;;
4986fb5eae6SMichal Berger		r) include_cpu=1 ;;
499b43a0112SMichal Berger		*) ;;
500b43a0112SMichal Berger	esac
501b43a0112SMichal Bergerdone
502b43a0112SMichal Berger
503d5fe62b2SMichal Bergerdeclare -r sdr_cache=$PM_OUTPUTDIR/sdr.cache
504b43a0112SMichal Bergerdeclare -r log_file=${prefix:+${prefix}_}${0##*/}.bmc.pm.log
505b43a0112SMichal Berger
506d5fe62b2SMichal Bergermkdir -p "$PM_OUTPUTDIR"
507b43a0112SMichal Bergerif [[ $log_to_file == yes ]]; then
508d5fe62b2SMichal Berger	printf 'Redirecting to %s\n' "$PM_OUTPUTDIR/$log_file" >&2
509d5fe62b2SMichal Berger	exec > "$PM_OUTPUTDIR/$log_file" 2>&1
510b43a0112SMichal Bergerfi
511b43a0112SMichal Berger
512d5fe62b2SMichal Bergersave_pm_pid
513b43a0112SMichal Bergertrap 'cleanup' EXIT
514d5fe62b2SMichal Bergertrap 'retag' USR1
515b43a0112SMichal Berger
516b43a0112SMichal Bergeripmi_supported
517b43a0112SMichal Bergerpower_support
518b43a0112SMichal Berger
519b43a0112SMichal Bergercollect_readings
520