# -*- tab-width: 4 -*- ;; Emacs
# vi: set filetype=sh tabstop=8 shiftwidth=8 noexpandtab :: Vi/ViM
############################################################ IDENT(1)
#
# $Title: dwatch(8) JSON module for dtrace_io(4) $
# $Copyright: 2014-2022 Devin Teske. All rights reserved. $
# $FrauBSD: dwatch-json/json-io-top-raw 2022-08-22 15:36:11 -0700 freebsdfrau $
# $Version: 1.0 $
#
############################################################ DESCRIPTION
#
# Produce JSON custom log format for disk I/O
#
############################################################ PROBE

: "${PROBE_SECONDS:=10}"
: ${PROBE:=profile-${PROBE_SECONDS}s}

############################################################ FUNCTIONS

#
# JSON helpers
#
jfmt(){ printf "\\\\\"%s\\\\\":$1" "$2" "$3"; }
jraw(){ jfmt %s "$1" "$2"; }
jstr(){ jraw "$1" "\\\"$2\\\""; }
_jstr(){ printf ,; jstr "$@"; }
_jraw(){ printf ,; jraw "$@"; }

############################################################ OVERRIDES

MAX_ARGS=0		# -B num
MAX_DEPTH=0		# -K num

#
# Unsupported features
#
unset EXECREGEX		# -z regex
unset GROUP		# -g group
unset PID		# -p pid
unset PROBE_COALESCE	# -F
unset PSTREE		# -R
unset USER		# -u user

############################################################ EVENT ACTION

_EVENT_TEST="${EVENT_TEST:+($EVENT_TEST)}"
_EVENT_TEST="${CUSTOM_TEST:+$CUSTOM_TEST${EVENT_TEST:+ && }}$_EVENT_TEST"
if [ "$JID" ]; then
	pr_id="curthread->td_proc->p_ucred->cr_prison->pr_id"
	_EVENT_TEST="$pr_id == $JID${_EVENT_TEST:+ && ($_EVENT_TEST)}"
	unset JID
fi
EVENT_TEST="cpu == 0"
CUSTOM_TEST=

############################################################ ACTIONS

BIO_COMMANDS="
	BIO_CMD0
	BIO_CMD1
	BIO_CMD2
	BIO_DELETE
	BIO_FLUSH
	BIO_GETATTR
	BIO_READ
	BIO_WRITE
	BIO_ZONE
" # END-QUOTE

BIO_CMD_LIST=$( echo "$BIO_COMMANDS" | awk '
	/^[[:space:]]*(#|$)/ { next }
	{
		sub(/#.*/, "")
		sub(/^[[:space:]]*/, "")
		sub(/[[:space:]]*$/, "")
		n = split($0, f, /[[:space:]]+/)
		for (i = 1; i <= n; i++) list = list " " f[i]
	}
	END { print substr(list, 2) }
' )

bio_cmd_list=$( echo "$BIO_CMD_LIST" | awk '{
	sub(/^BIO_/, "")
	gsub(/ BIO_/, " ")
	print tolower($0)
}' )

bytes_list="read_bytes bytes_read write_bytes bytes_written"

stat_list="start done error"

exec 9<<EOF
this struct bio	*bio;
this bufinfo_t	bufinfo;
this uint8_t	b_error;
this int	b_flags;
this int	bio_cmd;
this long	bio_length;

this devinfo_t	devinfo;
this string	device_entry;
this string	device_if;
this string	device_type;

$( IFS=" "
	for bytes in $bytes_list; do
		printf "uint64_t\t%s;\n" "$bytes"
	done

	for cmd in $bio_cmd_list unknown; do
		for stat in $stat_list; do
			printf "uint64_t\t%s_%s;\n" $cmd $stat
		done
	done
	printf "\n"

	printf "inline int set_globals_to[int num] =\n"
	for bytes in $bytes_list; do
		printf "\t%s =\n" "$bytes"
	done
	for cmd in $bio_cmd_list unknown; do
		for stat in $stat_list; do
			printf "\t%s_%s =\n" $cmd $stat
		done
	done
	printf "\tnum;\n\n"
)

#pragma D binding "1.13" device_name
inline string device_name[devinfo_t di] = di.dev_name != "" ?
	strjoin(di.dev_name, lltostr(di.dev_minor)) :
	strjoin("[", strjoin(
		strjoin(lltostr(di.dev_major), ","),
		strjoin(lltostr(di.dev_minor), "]")));

inline int add_bio_cmd_start_bytes[int bio_cmd, uint64_t bytes] =
	bio_cmd == BIO_READ ? read_bytes += bytes :
	bio_cmd == BIO_WRITE ? write_bytes += bytes :
	0;

inline int add_bio_cmd_done_bytes[int bio_cmd, uint64_t bytes] =
	bio_cmd == BIO_READ ? bytes_read += bytes :
	bio_cmd == BIO_WRITE ? bytes_written += bytes :
	0;

$( IFS=" "
	for stat in $stat_list; do
		printf "inline int increment_bio_cmd_%s[int bio_cmd] =\n" $stat
		for cmd in $BIO_CMD_LIST; do
			var=$( echo "${cmd#BIO_}" | awk '{print tolower($0)}' )
			printf "\tbio_cmd == %s ? %s_%s++ :\n" $cmd $var $stat
		done
		printf "\tunknown_%s++;\n\n" $stat
	done
)

BEGIN /* probe ID $ID */
{${TRACE:+
	printf("<$ID>");}
	set_globals_to[0];
}

/****************************** I/O ******************************/

io:::start, io:::done /* probe ID $(( $ID + 1 )) */
{${TRACE:+
	printf("<$(( $ID + 1 ))>");}
	this->bio = (struct bio *)args[0];
}

io:::start, io:::done /this->bio != NULL/ /* probe ID $(( $ID + 2 )) */
{${TRACE:+
	printf("<$(( $ID + 2 ))>");
}
	/*
	 * struct bio *
	 */
	this->bufinfo = xlate <bufinfo_t> ((struct bio *)this->bio);
	this->b_flags = (int)this->bufinfo.b_flags;
	this->b_error = this->b_flags & BIO_ERROR == BIO_ERROR ? 1 : 0;
	this->bio_cmd = (int)this->bufinfo.b_cmd;
	this->bio_length = (long)this->bufinfo.b_bcount;

	/*
	 * struct devstat *
	 */
	this->devinfo = xlate <devinfo_t> ((struct devstat *)args[1]);
	this->device_entry = device_name[this->devinfo];
	this->device_if = device_if[(int)this->devinfo.dev_type];
	this->device_type = device_type[(int)this->devinfo.dev_type];

	/* De-duplicate events */
	this->bio = (this->device_entry == "[0,-1]" ? NULL : this->bio);
}

io:::start /this->bio != NULL${_EVENT_TEST:+ &&
	$_EVENT_TEST}/
	/* probe ID $(( $ID + 3 )) */
{${TRACE:+
	printf("<$(( $ID + 3 ))>");
}
	increment_bio_cmd_start[this->bio_cmd];
	add_bio_cmd_start_bytes[this->bio_cmd, this->bio_length];
}

io:::done /this->bio != NULL${_EVENT_TEST:+ &&
	$_EVENT_TEST}/
	/* probe ID $(( $ID + 4 )) */
{${TRACE:+
	printf("<$(( $ID + 4 ))>");
}
	increment_bio_cmd_done[this->bio_cmd];
	add_bio_cmd_done_bytes[this->bio_cmd, this->bio_length];
}

io:::start, io:::done /this->bio != NULL &&
	this->b_error${_EVENT_TEST:+ &&
	$_EVENT_TEST}/
	/* probe ID $(( $ID + 5 )) */
{${TRACE:+
	printf("<$(( $ID + 5 ))>");
}
	increment_bio_cmd_error[this->bio_cmd];
}
EOF
ACTIONS=$( cat <&9 )
ID=$(( $ID + 6 ))

############################################################ EVENT TAG

# The EVENT_TAG is run inside the print action after the timestamp has been
# printed. By default, `UID.GID CMD[PID]: ' of the process is printed.

EVENT_TAG="printf(\"${PROFILE%-raw}: \")"

############################################################ EVENT DETAILS

: ${HOSTNAME:=$( hostname )}

SECONDS="walltimestamp / 1000000000"

exec 9<<EOF
	/*
	 * Print event details
	 */
	printf("{$( IFS=" "
		 jstr report_type "${PROFILE%-raw}"
		_jstr hostname "$HOSTNAME"
		_jraw epoch %u
		for cmd in $bio_cmd_list unknown; do
			for stat in $stat_list; do
				key=${cmd}_$stat
				_jraw $key %lu
				_jraw ${key}_rate %lu
			done
		done
		for bytes in $bytes_list; do
			_jraw $bytes %lu
			_jraw ${bytes}_rate %lu
		done
		_jraw start_bytes %lu
		_jraw start_bytes_rate %lu
		_jraw bytes_done %lu
		_jraw bytes_done_rate %lu
	)}",
		$SECONDS,
		$( IFS=" "
			for cmd in $bio_cmd_list unknown; do
				for stat in $stat_list; do
					key=${cmd}_$stat
					printf "\n\t\t%s," $key
					printf "\n\t\t%s / %u," \
						$key $PROBE_SECONDS
				done
			done
			for bytes in $bytes_list; do
				printf "\n\t\t%s," $bytes
				printf "\n\t\t%s / %u," $bytes $PROBE_SECONDS
			done
		)
		read_bytes + write_bytes,
		(read_bytes + write_bytes) / $PROBE_SECONDS,
		bytes_read + bytes_written,
		(bytes_read + bytes_written) / $PROBE_SECONDS
	);

	/*
	 * Reset counters
	 */
	set_globals_to[0];
EOF
EVENT_DETAILS=$( cat <&9 )

################################################################################
# END
################################################################################
