#!/bin/bash
#
# Copyright (C) 2020 Francis Deslauriers <francis.deslauriers@efficios.com>
#
# SPDX-License-Identifier: LGPL-2.1-only

CURDIR=$(dirname "$0")/
TESTDIR=$CURDIR/../../../

TEST_TMPDIR=$(mktemp --tmpdir -d tmp.test_notifier_discarded_count.XXXXXX)

TESTAPP_PATH="$TESTDIR/utils/testapp"
TESTAPP_NAME="gen-ust-events"
TESTAPP_BIN="$TESTAPP_PATH/$TESTAPP_NAME/$TESTAPP_NAME"

TESTPOINT_BASE_PATH=$(readlink -f "$TEST_TMPDIR/lttng.t_p_n")
TESTPOINT_PIPE_PATH=$(mktemp -u --tmpdir="$TEST_TMPDIR" "lttng.t_p_n.XXXXXX")
TESTPOINT=$(readlink -f "${CURDIR}/.libs/libpause_sessiond.so")

SH_TAP=1

# shellcheck source=../../../utils/utils.sh
source "$TESTDIR/utils/utils.sh"
# shellcheck source=./util_event_generator.sh
source "$CURDIR/util_event_generator.sh"

FULL_LTTNG_BIN="${TESTDIR}/../src/bin/lttng/${LTTNG_BIN}"
FULL_LTTNG_SESSIOND_BIN="${TESTDIR}/../src/bin/lttng-sessiond/lttng-sessiond"

UST_NUM_TESTS=15
DESTRUCTIVE_TESTS_NUM=12
KERNEL_NUM_TESTS=$((14 + $DESTRUCTIVE_TESTS_NUM))
NUM_TESTS=$(($UST_NUM_TESTS + $KERNEL_NUM_TESTS))

plan_tests $NUM_TESTS

function trigger_get_discarded_notif_number()
{
	local trigger_name="$1"
	local list_triggers_stdout=$(mktemp --tmpdir="$TEST_TMPDIR" "list_triggers_stdout.XXXXXX")

	"$FULL_LTTNG_BIN" list-triggers > "$list_triggers_stdout"

	cat "$list_triggers_stdout" | grep -A7 "$trigger_name" | grep -A2 "event rule matches" | tail -1 | grep --quiet "errors: none"
	ret=$?

	if [ "$ret" -eq "0" ]; then
		notif_nb="0"
	else
		notif_nb=$(cat "$list_triggers_stdout" | grep -A7 "$trigger_name" | grep "discarded tracer messages" | cut -d' ' -f10)
	fi

	rm -f "$list_triggers_stdout"

	# Printing the value to that the caller can get it back.
	echo "$notif_nb"
}

function test_kernel_notifier_discarded_count
{
	local sessiond_pipe=()
	local trigger_name="my_trigger"
	local list_triggers_stdout=$(mktemp --tmpdir="$TEST_TMPDIR" "list_triggers_stdout.XXXXXX")

	# Used on sessiond launch.
	LTTNG_SESSIOND_ENV_VARS="LTTNG_TESTPOINT_ENABLE=1 \
		NOTIFIER_PAUSE_PIPE_PATH=${TESTPOINT_PIPE_PATH} \
		LD_PRELOAD=${TESTPOINT}"

	diag "Kernel event notifer error counter"

	start_lttng_sessiond_notap

	# This is needed since the testpoint creates a pipe with the sessiond
	# type suffixed.
	for f in "$TESTPOINT_BASE_PATH"*; do
		sessiond_pipe+=("$f")
	done

	lttng_add_trigger_ok "$trigger_name" \
		--condition event-rule-matches --type=kernel --name=lttng_test_filter_event \
		--action notify

	trigger_discarded_nb=$(trigger_get_discarded_notif_number "$trigger_name")
	is $trigger_discarded_nb 0 "No discarded tracer notification"

	# Stop consumption of notifier tracer notifications.
	diag "Pause consumption of tracer messages"
	echo -n 1 > $sessiond_pipe

	# The notifier ring buffer configuration is currently made of 16 4096
	# bytes subbuffers. Each kernel notification is at least 42 bytes long.
	# To fill it, we need to generate (16 * 4096)/42 = 1561 notifications.
	# That number is a bit larger than what we need since some of the space
	# is lost in subbuffer boundaries.
	echo -n "2000" > /proc/lttng-test-filter-event

	# Confirm that the number of tracer notifications discarded is non-zero.
	trigger_discarded_nb=$(trigger_get_discarded_notif_number "$trigger_name")
	isnt $trigger_discarded_nb 0 "Discarded tracer notification number non-zero ($trigger_discarded_nb) as expected"

	lttng_remove_trigger_ok "$trigger_name"

	# Confirm that no notifier is enabled.
	list_triggers_line_count=$("$FULL_LTTNG_BIN" list-triggers | wc -l)
	is "$list_triggers_line_count" "0" "No \`event-rule-matches\` kernel notifier enabled as expected"

	# Enable another notifier and list it to confirm the counter was cleared.
	lttng_add_trigger_ok "$trigger_name" \
		--condition event-rule-matches --type=kernel --name=lttng_test_filter_event \
		--action notify

	trigger_discarded_nb=$(trigger_get_discarded_notif_number "$trigger_name")
	is $trigger_discarded_nb 0 "No discarded tracer notification"

	lttng_remove_trigger_ok "$trigger_name"

	stop_lttng_sessiond_notap

	unset LTTNG_SESSIOND_ENV_VARS

	rm -f "$list_triggers_stdout"
}

function test_kernel_notifier_discarded_count_max_bucket
{
	start_lttng_sessiond "" "--event-notifier-error-buffer-size-kernel=3"

	diag "Kernel event notifer error counter bucket limit"
	for i in $(seq 3); do
		lttng_add_trigger_ok "$i" \
			--condition event-rule-matches --type=kernel --name=my_event_that_doesnt_need_to_really_exist_$i \
			--action notify
	done

	for i in $(seq 4 5); do
		lttng_add_trigger_fail "$i" \
			--condition event-rule-matches --type=kernel --name=my_event_that_doesnt_need_to_really_exist_$i \
			--action notify
	done

	stop_lttng_sessiond_notap
}

function test_ust_notifier_discarded_count
{
	local sessiond_pipe=()
	local trigger_name="my_trigger"
	local NR_USEC_WAIT=0
	local PIPE_SIZE
	local NR_ITER

	diag "UST event notifer error counter"

	PIPE_SIZE=$("$CURDIR"/default_pipe_size_getter)
	if [ $? -ne 0 ]; then
		BAIL_OUT "Failed to get system default pipe size"
	else
		diag "Default system pipe size: $PIPE_SIZE bytes"
	fi

	# Find the number of events needed to overflow the event notification
	# pipe buffer. Each LTTng-UST notification is at least 42 bytes long.
	# Double that number to ensure enough events are created to overflow
	# the buffer.
	NR_ITER=$(( (PIPE_SIZE / 42) * 2 ))
	diag "Test application will emit $NR_ITER events"

	# Used on sessiond launch.
	LTTNG_SESSIOND_ENV_VARS="LTTNG_TESTPOINT_ENABLE=1 \
		NOTIFIER_PAUSE_PIPE_PATH=${TESTPOINT_PIPE_PATH} \
		LD_PRELOAD=${TESTPOINT}"

	start_lttng_sessiond_notap

	# This is needed since the testpoint create a pipe with the sessiond
	# type suffixed.
	for f in "$TESTPOINT_BASE_PATH"*; do
		sessiond_pipe+=("$f")
	done

	lttng_add_trigger_ok "$trigger_name" \
		--condition event-rule-matches --type=user --name=tp:tptest \
		--action notify

	trigger_discarded_nb=$(trigger_get_discarded_notif_number "$trigger_name")
	is $trigger_discarded_nb 0 "No discarded tracer notification"

	# Stop consumption of notifier tracer notifications.
	diag "Pause consumption of tracer messages"
	echo -n 1 > $sessiond_pipe

	$TESTAPP_BIN -i $NR_ITER -w $NR_USEC_WAIT
	ok $? "Generating $NR_ITER tracer notifications"

	# Confirm that the number of tracer notifications discarded is non-zero.
	trigger_discarded_nb=$(trigger_get_discarded_notif_number "$trigger_name")
	isnt $trigger_discarded_nb 0 "Discarded tracer notification number non-zero ($trigger_discarded_nb) as expected"

	# Remove the notifier.
	lttng_remove_trigger_ok "$trigger_name"

	# Confirm that no trigger is enabled.
	list_triggers_line_count=$("$FULL_LTTNG_BIN" list-triggers | wc -l)
	is "$list_triggers_line_count" "0" "No \`event-rule-matches\` userspace notifier enabled as expected"

	# Enable another notifier and list it to confirm the counter was cleared.
	lttng_add_trigger_ok "$trigger_name" \
		--condition event-rule-matches --type=user --name=tp:tptest \
		--action notify

	trigger_discarded_nb=$(trigger_get_discarded_notif_number "$trigger_name")
	is $trigger_discarded_nb 0 "No discarded tracer notification"

	lttng_remove_trigger_ok "$trigger_name"

	stop_lttng_sessiond_notap

	unset LTTNG_SESSIOND_ENV_VARS
}

function test_ust_notifier_discarded_count_max_bucket
{
	start_lttng_sessiond "" "--event-notifier-error-buffer-size-userspace=3"

	diag "UST event notifer error counter bucket limit"
	for i in $(seq 3); do
		lttng_add_trigger_ok "$i" \
			--condition event-rule-matches --type=user --name=my_event_that_doesnt_need_to_really_exist_$i \
			--action notify
	done

	for i in $(seq 4 5); do
		lttng_add_trigger_fail "$i" \
			--condition event-rule-matches --type=user --name=my_event_that_doesnt_need_to_really_exist_$i \
			--action notify
	done

	stop_lttng_sessiond_notap
}

function test_ust_notifier_discarded_count_multi_uid
{
	local sessiond_pipe=()
	local root_trigger_name="root_trigger"
	local user_trigger_name="user_trigger"
	local list_triggers_stdout=$(mktemp --tmpdir="$TEST_TMPDIR" "list_triggers_stdout.XXXXXX")
	local NR_USEC_WAIT=0
	local PIPE_SIZE
	local NR_ITER
	local new_user="dummy_lttng_test_user"

	diag "UST event notifer error counter multiple UIDs"

	# Create a dummy user to run test apps as.
	useradd --no-create-home "$new_user"
	new_uid=$(id -u "$new_user")

	PIPE_SIZE=$("$CURDIR"/default_pipe_size_getter)
	if [ $? -ne 0 ]; then
		BAIL_OUT "Failed to get system default pipe size"
	else
		diag "Default system pipe size: $PIPE_SIZE bytes"
	fi

	# Find the number of events needed to overflow the event notification
	# pipe buffer. Each LTTng-UST notification is at least 42 bytes long.
	# Double that number to ensure enough events are created to overflow
	# the buffer.
	NR_ITER=$(( (PIPE_SIZE / 42) * 2 ))
	diag "Test applications will emit $NR_ITER events"

	# Used on sessiond launch.
	LTTNG_SESSIOND_ENV_VARS="LTTNG_TESTPOINT_ENABLE=1 \
		NOTIFIER_PAUSE_PIPE_PATH=${TESTPOINT_PIPE_PATH} \
		LD_PRELOAD=${TESTPOINT}"

	start_lttng_sessiond_notap

	# This is needed since the testpoint create a pipe with the sessiond
	# type suffixed.
	for f in "$TESTPOINT_BASE_PATH"*; do
		sessiond_pipe+=("$f")
	done

	lttng_add_trigger_ok "$root_trigger_name" \
		--condition event-rule-matches --type=user --name tp:tptest \
		--action notify

	lttng_add_trigger_ok "$user_trigger_name" --owner-uid "$new_uid" \
		--condition event-rule-matches --type=user --name tp:tptest \
		--action notify

	# Stop consumption of notifier tracer notifications.
	echo -n 1 > $sessiond_pipe

	$TESTAPP_BIN -i $NR_ITER -w $NR_USEC_WAIT
	ok $? "Generating $NR_ITER tracer notifications as UID: $(id -u)"

	su "$new_user" -c "$TESTAPP_BIN -i $NR_ITER -w $NR_USEC_WAIT"
	ok $? "Generating $NR_ITER tracer notifications as UID: $new_uid"

	root_trigger_discarded_nb=$(trigger_get_discarded_notif_number "$root_trigger_name")
	user_trigger_discarded_nb=$(trigger_get_discarded_notif_number "$user_trigger_name")

	isnt $root_trigger_discarded_nb 0 \
		"Root trigger discarded notifications number ($root_trigger_discarded_nb) is non-zero"
	isnt $user_trigger_discarded_nb 0 \
		"User trigger discarded notifications number ($user_trigger_discarded_nb) is non-zero"

	lttng_remove_trigger_ok "$root_trigger_name"
	lttng_remove_trigger_ok "$user_trigger_name" --owner-uid "$new_uid"

	stop_lttng_sessiond_notap

	unset LTTNG_SESSIOND_ENV_VARS

	userdel "$new_user"
	rm -f "$list_triggers_stdout"
}

function test_ust_notifier_discarded_regardless_trigger_owner
{
	local sessiond_pipe=()
	local root_trigger_name="root_trigger"
	local user_trigger_name="user_trigger"
	local list_triggers_stdout=$(mktemp --tmpdir="$TEST_TMPDIR" "list_triggers_stdout.XXXXXX")
	local NR_USEC_WAIT=0
	local PIPE_SIZE
	local NR_ITER
	local new_user="dummy_lttng_test_user"

	PIPE_SIZE=$("$CURDIR"/default_pipe_size_getter)
	if [ $? -ne 0 ]; then
		BAIL_OUT "Failed to get system default pipe size"
	else
		diag "Default system pipe size: $PIPE_SIZE bytes"
	fi

	# Find the number of events needed to overflow the event notification
	# pipe buffer. Each LTTng-UST notification is at least 42 bytes long.
	# Double that number to ensure enough events are created to overflow
	# the buffer.
	NR_ITER=$(( (PIPE_SIZE / 42) * 2 ))
	diag "Test applications will emit $NR_ITER events"

	diag "UST event notifer error counter persists when a root trigger is present"

	# Create a dummy user to run test apps as.
	useradd --no-create-home "$new_user"
	new_uid=$(id -u "$new_user")

	# Used on sessiond launch.
	LTTNG_SESSIOND_ENV_VARS="LTTNG_TESTPOINT_ENABLE=1 \
		NOTIFIER_PAUSE_PIPE_PATH=${TESTPOINT_PIPE_PATH} \
		LD_PRELOAD=${TESTPOINT}"

	start_lttng_sessiond_notap

	# This is needed since the testpoint create a pipe with the sessiond
	# type suffixed.
	for f in "$TESTPOINT_BASE_PATH"*; do
		sessiond_pipe+=("$f")
	done

	lttng_add_trigger_ok "$root_trigger_name" \
		--condition event-rule-matches --type=user --name tp:tptest \
		--action notify

	# Stop consumption of notifier tracer notifications.
	echo -n 1 > $sessiond_pipe

	su "$new_user" -c "$TESTAPP_BIN -i $NR_ITER -w $NR_USEC_WAIT"
	ok $? "Generating $NR_ITER tracer notifications as UID: $new_uid"

	root_trigger_discarded_nb=$(trigger_get_discarded_notif_number "$root_trigger_name")

	isnt $root_trigger_discarded_nb 0 \
		"Root trigger discarded notifications number ($root_trigger_discarded_nb) is non-zero"

	lttng_remove_trigger_ok "$root_trigger_name"

	stop_lttng_sessiond_notap

	unset LTTNG_SESSIOND_ENV_VARS

	userdel "$new_user"
	rm -f "$list_triggers_stdout"
}

test_ust_notifier_discarded_count
test_ust_notifier_discarded_count_max_bucket

check_skip_kernel_test "$KERNEL_NUM_TESTS" "Skipping kernel notification tests." ||
{

	validate_lttng_modules_present

	modprobe lttng-test

	test_kernel_notifier_discarded_count
	test_kernel_notifier_discarded_count_max_bucket

	if destructive_tests_enabled ; then
		# Those tests add a new user on the system. Since it's a quite
		# intrusive change to the system, we decide to only run it when
		# the user knows what they are doing.
		test_ust_notifier_discarded_count_multi_uid
		test_ust_notifier_discarded_regardless_trigger_owner
	else
		skip 0 "You need to set the LTTNG_ENABLE_DESTRUCTIVE_TESTS environment variable to \"will-break-my-system\" to run this test" $DESTRUCTIVE_TESTS_NUM
	fi

	modprobe --remove lttng-test

	rm -rf "${sessiond_pipe[@]}" 2> /dev/null

}

rm -rf "$TEST_TMPDIR"
