#!/usr/bin/env expect
############################################################################
# Purpose:  Test of --cpu-freq options
############################################################################
#  Copyright (C) 2014 Bull S. A. S.
#		Bull, Rue Jean Jaures, B.P.68, 78340, Les Clayes-sous-Bois.
#  Written by Rod Schultz <Rod.Schultz@bull.com>
#
# This file is part of Slurm, a resource management program.
# For details, see <https://slurm.schedmd.com/>.
# Please also read the included file: DISCLAIMER.
#
# Slurm is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free
# Software Foundation; either version 2 of the License, or (at your option)
# any later version.
#
# Slurm is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along
# with Slurm; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA.
############################################################################
source ./globals

# Test various --cpu-freq options

# Assumptions
# - the first node selected has frequency scaling, wih ondemand and userspace
# - all cpus on a node have the same scaling options.
# - task affinity either cgroups or cpusets

set nerr        0
set test_prog   "$test_name.prog"
set avail_freq  [split "1000 2000" " "]
set nfreq       0
set have_on_demand 1
set have_user_space 1
set file_out    "$test_dir/output"
set wd          [pwd]

################################################################
# Run a test case.
# "test_case" is a wrapper for "test_freq" due to current Linux
# not reliably changing frequency, so we can repeat a test
#
# Parameters
# - opt		value of --cpu-freq
# - expmin      expected value of scaling_min (if 0, don't check)
# - expcur	expected value of scaling_cur (if 0, don't check)
# - expmax      expected value of scaling_max (if 0, don't check)
# - expgov      expected value of scaling_governor (if 0, don't check)
#
# Returns
# 0 on success, 1 on failure
# 2 if measured frequency differs too much from target frequency
#   ERROR message is sent to user on failure.
################################################################
proc test_freq {opt expmin expcur expmax expgov} {
	global bin_rm srun sbatch test_node test_cpu wd number file_out test_name

	log_user 0
	exec $bin_rm -f $file_out
	set job_id 0
	spawn $sbatch -w $test_node --exclusive -o $file_out -t1 $test_name.batch $test_cpu $opt $wd $srun
	expect {
		-re "Submitted batch job ($number)" {
			set job_id $expect_out(1,string)
			exp_continue
		}
		eof {
			wait
		}
	}
	if {$job_id == 0} {
		fail "Batch submit failure"
	}
	#
	# Wait for job to complete
	#
	wait_for_job -fail $job_id "DONE"
	wait_for_file -fail $file_out
	set line ""
	set start_value ""
	set test_value ""
	set end_value ""
	set fd 0
	set fd [open $file_out "r"]

	# Search for starting values
	while {$fd > 0 && [gets $fd line] != -1} {
		set pos [string first "scaling_values:" $line]
		if {$pos == 0} {
			set start_value [string trim $line "\r"]
			set start_value [string range $start_value 16 end]
			break
		} 
	}
	if {$start_value eq ""} {
		log_error "--cpu-freq=$opt -- can't find starting values"
		close $fd
		log_user 1
		return 1
	}

	# Search for test values
	while {$fd > 0 && [gets $fd line] != -1} {
		set pos [string first "scaling_values:" $line]
		if {$pos == 0} {
			set test_value [string trim $line "\r"]
			set test_value [string range $test_value 16 end]
			break
		} 
	}
	if {$test_value eq ""} {
		log_error "--cpu-freq=$opt -- can't find test values"
		close $fd
		log_user 1
		return 1
	}

	# Search for ending values
	while {$fd > 0 && [gets $fd line] != -1} {
		set pos [string first "scaling_values:" $line]
		if {$pos == 0} {
			set end_value [string trim $line "\r"]
			set end_value [string range $end_value 16 end]
			break
		} 
	}
	if {$end_value eq ""} {
		log_error "--cpu-freq=$opt -- can't find ending values"
		close $fd
		log_user 1
		return 1
	}
	close $fd

	log_debug "--cpu-freq=$opt"
	log_debug "Start_freq: $start_value"
	log_debug "Test_freq:  $test_value"
	log_debug "End_freq:   $end_value"

	set strt_vals [split $start_value " "]
	set sgov [string range [lindex $strt_vals 0] 4 end]
	set smin [string range [lindex $strt_vals 1] 4 end]
	set scur [string range [lindex $strt_vals 2] 4 end]
	set smax [string range [lindex $strt_vals 3] 4 end]

	set tst_vals [split $test_value " "]
	set tgov [string range [lindex $tst_vals 0] 4 end]
	set tmin [string range [lindex $tst_vals 1] 4 end]
	set tcur [string range [lindex $tst_vals 2] 4 end]
	set tmax [string range [lindex $tst_vals 3] 4 end]

	set end_vals [split $end_value " "]
	set egov [string range [lindex $end_vals 0] 4 end]
	set emin [string range [lindex $end_vals 1] 4 end]
	set ecur [string range [lindex $end_vals 2] 4 end]
	set emax [string range [lindex $end_vals 3] 4 end]

	if {$expgov != 0 && $tgov ne $expgov} {
		log_error "--cpu-freq=$opt -- test governor $tgov not expected ($expgov)"
		log_user 1
		return 1
	}
	if {$expmin != 0 && $tmin ne $expmin} {
		log_error "--cpu-freq=$opt -- test min $tmin not expected ($expmin)"
		log_user 1
		return 1
	}
	# Check that actual frequency is within 10 percent of target
	if {$expcur != 0} {
		set percent_error [expr abs($tcur - $expcur) * 100 / $expcur]
	} else {
		set percent_error 0
	}
	if {$percent_error > 10} {
		set real_error 1
		if {$opt eq "High"} {
			if {$tcur > $expcur} {
				log_warn "--cpu-freq=$opt -- test cur $tcur higher than ($expcur).  This is expected as scaling_cur_freq is a kernel estimation."
				set real_error 0
			}
		}

		if {$real_error} {
			log_warn "--cpu-freq=$opt -- test cur $tcur not expected ($expcur, error is $percent_error percent)"
			log_user 1
			return 2
		}
	} elseif {$expcur != 0} {
		log_debug "Difference between requested and actual frequency is $percent_error percent ($tcur vs. $expcur)"
	}
	if {$expmax != 0 && $tmax ne $expmax} {
		log_error "--cpu-freq=$opt -- test max $tmax not expected ($expmax)"
		log_user 1
		return 1
	}
	if {$sgov ne $egov} {
		log_error "--cpu-freq=$opt -- starting governor $sgov not reset ($egov)"
		log_user 1
		return 1
	}
	if {$smin ne $emin} {
		log_error "--cpu-freq=$opt -- starting min $smin not reset ($emin)"
		log_user 1
		return 1
	}
	if {$smax ne $emax} {
		log_error "--cpu-freq=$opt -- starting max $smax not reset ($emax)"
		log_user 1
		return 1
	}

	log_user 1
	return 0
}

proc test_case {opt expmin expcur expmax expgov} {
	set retry_cnt 5
	for {set inx 0} {$inx < $retry_cnt} {incr inx} {
		set rc [test_freq $opt $expmin $expcur $expmax $expgov]
		if {$rc != 2} {
			return $rc
		}
	}
	log_error "--cpu-freq control failing repeatedly"
	return $rc
}

if {[is_running_in_container]} {
	skip "This test will not work in a container"
}

# Check environment
if {![param_contains [get_affinity_types] "affinity"]} {
	skip "This test requires some form of task affinity"
}
if {[param_contains [get_config_param "SlurmdParameters"] "config_overrides"]} {
	log_warn "SlurmdParameters=config_overrides is discouraged. Proceeding assuming that the number of CPUs declared is accurate."
}
log_user 0
spawn $scontrol show config
expect {
	-re "CpuFreqGovernors *= ($re_word_str)" {
		if {[string first "OnDemand" $expect_out(1,string)] == -1} {
			set have_on_demand 0
		}
		if {[string first "UserSpace" $expect_out(1,string)] == -1} {
			set have_user_space 0
		}
		exp_continue
	}
	eof {
		wait
	}
}

# Identify a node to use
set timeout $max_job_delay
set test_node 0
spawn $srun --exclusive -n1 -t1 env
expect {
	-re "SLURMD_NODENAME=($re_word_str)" {
		set test_node $expect_out(1,string)
		exp_continue
	}
	eof {
		wait
	}
}
if {$test_node == 0} {
	fail "Failed to get hostname"
}

# Determine CPU count on that node.
# We will assume the tests will run on the highest numbered CPU
set test_cpu -1
spawn $scontrol show node $test_node
expect {
	-re "CPUTot=($number)" {
		set test_cpu $expect_out(1,string)
		exp_continue
	}
	eof {
		wait
	}
}

log_user 1
if {$test_cpu == -1} {
	fail "Failed to get CPU count on node ($test_node)"
}
incr test_cpu -1
log_debug "Test node is $test_node, test_cpu is $test_cpu"

# Verify that selected CPU on selected node supports cpu_frequency
set have_on_demand 0
set have_user_space 0
set nfreq 0
spawn $srun -w $test_node --exclusive -t1 ./$test_name.bash $test_cpu
expect {
	-re "scaling frequency not supported" {
		skip "Test requires frequency scaling"
	}
	-re "available_governors (\[a-zA-Z \]+)" {
		if {[string first "ondemand" $expect_out(1,string)] >= 0} {
			set have_on_demand 1
		}
		if {[string first "userspace" $expect_out(1,string)] >= 0} {
			set have_user_space 1
		}
		exp_continue
	}
	-re "available_frequencies (\[0-9 \]+)" {
		set tmp [string trimright $expect_out(1,string)]
		set freq_line [string trimright $tmp]
		set freqs [split $freq_line " "]
		set avail_freq [lsort -integer $freqs]
		set nfreq [llength $avail_freq]
		exp_continue
	}
	eof {
		wait
	}
}
if {$nfreq < 2} {
	skip "Test requires at least 2 available frequencies"
}
if {$have_user_space == 0} {
	skip "Test requires UserSpace governor"
}
if {$have_on_demand == 0} {
	skip "Test requires OnDemand governor"
}

set xmx [expr ($nfreq - 1) / 2]
set xlow [lindex $avail_freq 0]
set xhigh [lindex $avail_freq $nfreq-1]
set xhighm1 [lindex $avail_freq $nfreq-2]
set xmed [lindex $avail_freq $xmx]
log_debug "Frequencies: low:$xlow medium:$xmed highm1:$xhighm1 high:$xhigh"

#
# Now run the tests
#
incr nerr [test_case userspace 0 0 0 userspace]
if {$have_on_demand == 1} {
	incr nerr [test_case ondemand 0 0 0 ondemand]
}
incr nerr [test_case Low $xlow $xlow 0 userspace]
incr nerr [test_case High 0 $xhigh $xhigh userspace]
incr nerr [test_case HighM1 0 $xhighm1 0 userspace]
incr nerr [test_case Medium 0 $xmed 0 userspace]
incr nerr [test_case $xmed 0 $xmed 0 userspace]
if {$nfreq > 2} {
	set mxx [expr $nfreq - 1]
	set minfrq [lindex $avail_freq 1]
	set maxfrq [lindex $avail_freq $mxx]
	set opt "$minfrq-$maxfrq:ondemand"
	incr nerr [test_case $opt $minfrq 0 $maxfrq ondemand]
}

if {$nerr != 0} {
	fail "$nerr test cases failed."
}
