#!/bin/bash
#
# SPDX-FileCopyrightText: 2013 Christian Babeux <christian.babeux@efficios.com>
# SPDX-FileCopyrightText: 2020 Jérémie Galarneau <jeremie.galarneau@efficios.com>
#
# SPDX-License-Identifier: LGPL-2.1-only

TEST_DESC="Tracefile count limits"

CURDIR=$(dirname "$0")/
TESTDIR=$CURDIR/../../..
BT2_PLUGINS_DIR="${TESTDIR}/utils/bt2_plugins"

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

NUM_TESTS=74

PAGE_SIZE=$(getconf PAGE_SIZE)
TRACEFILE_SIZE=$PAGE_SIZE

source "$TESTDIR"/utils/utils.sh

if [ ! -x "$TESTAPP_BIN" ]; then
	BAIL_OUT "No UST events binary detected."
fi

function pick_random_cpuid ()
{
	local cpuids
	read -r -a cpuids <<< "$(get_online_cpus)"
	echo "${cpuids[ $RANDOM % ${#cpuids[@]} ]}"
}

function enable_lttng_channel_count_limit ()
{
	sess_name="$1"
	channel_name="$2"
	tracefile_count_limit="$3"

	test_name="Enable channel \`$channel_name\` "
	test_name+="for session \`$sess_name\`: "
	test_name+="$tracefile_count_limit tracefiles"

	"$TESTDIR"/../src/bin/lttng/"$LTTNG_BIN" enable-channel \
	    -u "$channel_name" -s "$sess_name" \
	    --subbuf-size "$PAGE_SIZE" \
	    --tracefile-size "$TRACEFILE_SIZE" \
	    --tracefile-count "$tracefile_count_limit" >/dev/null 2>&1

	ok $? "$test_name"
}

function validate_min_max ()
{
	stats="$1"
	field="$2"
	expected_min="$3"
	expected_max="$4"

	echo $stats | grep -q -E "$field $expected_min $expected_max"
	return $?
}

function get_total_stream_file_size ()
{
	local trace_path="$1"
	local stream_name_pattern="$2"
	local size

	size=$(find "$trace_path" -type f -regex "$stream_name_pattern" -exec du -b -c {} + | tail -n1 | cut -f 1)
	# Set a default is no files were found. This avoids integer comparison errors with an empty value.
	if [ -z "${size}" ]; then
		size=0
	fi
	echo "$size"
}

function get_stream_file_count ()
{
	local trace_path="$1"
	local stream_name_pattern="$2"
	local count

	count=$(find "$trace_path" -type f -regex "$stream_name_pattern" | wc -l)
	echo "$count"
}

function test_tracefile_count_limit ()
{
	local count_limit="$1"

	local channel_name="channel"
	local cpuno=$(pick_random_cpuid)
	local event_name="tp:tptest"
	local expected_size=$((count_limit * TRACEFILE_SIZE))
	local num_iter=100000
	local previous_stream_size=-1
	local session_name
	local stream_pattern=".*${channel_name}_${cpuno}_[0-9]*"
	local stream_size=0
	local trace_path=$(mktemp -d -t "tmp.${FUNCNAME[0]}_trace_path.XXXXXX")

	session_name=$(randstring 16 0)

	diag "Test tracefile count limit : CPU $cpuno, $count_limit tracefiles, expecting a maximum of $expected_size bytes per CPU"

	create_lttng_session_ok "$session_name" "$trace_path"

	enable_lttng_channel_count_limit \
	    "$session_name" "$channel_name" "$count_limit"

	enable_ust_lttng_event_ok \
	    "$session_name" "$event_name" "$channel_name"

	# Run the test app until the total stream size stops changing the
	# expected size is exceeded (error).
	#
	# The `$stream_size` will not stabilize until the trace file count
	# limit is reached. This is guaranteed by the use of start/produce/stop
	# cycles forcing the consumption of buffers, preventing unwanted stall
	# in stream size.
	while [ "$stream_size" -ne "$previous_stream_size" ]; do
		start_lttng_tracing_notap "$session_name"
		if ! taskset -c "$cpuno" "$TESTAPP_BIN" -i "$num_iter" >/dev/null 2>&1 ; then
			diag "Taskset failed for CPU ${cpuno}"
			# This is a case that can happen in the CI cluster which has the CPU
			# affinities of containers changed at runtime when other colocated
			# containers are started or stopped and the cluster manager attempts
			# to rebalance the load across the host's physical resources.
			#
			# When this is the first iteration, skip the remaining tests as
			# most likely all future calls to taskset with this cpu will fail.
			if [[ "${previous_stream_size}" == "-1" ]]; then
				stop_lttng_tracing_notap "${session_name}"
				destroy_lttng_session_ok "${session_name}"
				skip 0 "Taskset failed on first iteration" 5
				rm -rf "${trace_path}"
				return
			fi
		fi
		stop_lttng_tracing_notap "$session_name"

		previous_stream_size="$stream_size"
		stream_size=$(get_total_stream_file_size "$trace_path" "$stream_pattern")
		diag "Completed an iteration: previous size = $previous_stream_size bytes, new size = $stream_size bytes"

		if [ "$stream_size" -gt "$expected_size" ]; then
			diag "Total size for CPU $cpuno exceeds expected size: stream size = $stream_size bytes, expected size = $expected_size"
			break
		fi
	done

	destroy_lttng_session_ok "$session_name"

	[ "$expected_size" -eq "$stream_size" ]
	ok $? "Total stream size of CPU $cpuno is $expected_size"

	[ "$(get_stream_file_count "$trace_path" "$stream_pattern")" -eq "$count_limit" ]
	ok $? "Stream meets the trace file limit of $count_limit"

	stats=$("_run_babeltrace_cmd" --plugin-path "${BT2_PLUGINS_DIR}" convert $trace_path -c filter.lttngtest.event_name -p "names=[\"${event_name}\"]" -c sink.lttngtest.field_stats)

	validate_min_max "$stats" "intfield" "[0-9]+" "$expected_max"
	ok $? "Trace validation - intfield"

	validate_min_max "$stats" "netintfield" "[0-9]+" "$expected_max"
	ok $? "Trace validation - netintfield"

	validate_min_max "$stats" "longfield" "[0-9]+" "$expected_max"
	ok $? "Trace validation - longfield"

	rm -rf "$trace_path"
}

LIMITS=("1" "2" "4" "8" "10" "16" "32" "64")

plan_tests $NUM_TESTS

print_test_banner "$TEST_DESC"

bail_out_if_no_babeltrace

start_lttng_sessiond

for limit in "${LIMITS[@]}";
do
	test_tracefile_count_limit "$limit"
done

stop_lttng_sessiond
