source $srcdir/$subdir/onthefly_common.tcl

set test "uprobes_onthefly"
if {![installtest_p] || ![inode_uprobes_p]} {
   untested "$test"
   return
}

# Goal: Ensure that inode-uprobes properly support on-the-fly arming/disarming.
# The script consists of a process.function and a process.function.return whose
# conditions are toggled by a timer probe. We then analyze the actual debug
# statements printed out to ensure the toggled probes are armed and disarmed as
# expected.

# Runs stap on a script with the given arguments
proc run_stap {subtest args script} {
   global test

   # Now start the application
   set pid [exec ./$test.x &]

   # We always need the DEBUG flags
   set args "$args -DDEBUG_STP_ON_THE_FLY"

   # The script we use uses @entry(), which uses maps underneath. Since we will
   # be stressing the system, we need to ensure we will have enough space for
   # many map items.
   set args "$args -DMAXMAPENTRIES=100000"

   # This test seems especially prone to exceeding the default overhead
   # threshold in the harder subtests. Disable overload checking.
   set args "$args -DSTP_NO_OVERLOAD"

   verbose -log "running stap -e {$script} $args"
   # Run stap and catch any errors (we will rethrow it later)
   set err [catch {eval exec stap -e {$script} $args} out]
   verbose -log "stap rc: $err, output: $out"
    
   # Kill app
   kill -INT $pid 1

   if {$err} {error $out}
   return $out
}

# Returns a script with some variables replaced.
#     SUBTEST    name of subtest
#     ENABLED    set to 1 to start the target probe as enabled, 0 as disabled
#     MAXTOGGLES number of times we toggle the target probe condition
#     TIMER      if a number, then it's the period of the timer probe which
#                toggles the target probes, if set to 'profile' then instead a
#                timer.profile probe is used to toggle the target probe, if set
#                to 'hardcore' then both a timer.profile probe and a
#                kernel.trace("*") probe is used for toggling, if set to 'max'
#                then a lot of various probe points are used for toggling
proc make_script {SUBTEST ENABLED MAXTOGGLES TIMER} {
   global test

   set script {

      # We want these probes to fire as deterministically as possible so that
      # their outputs can easily be predicted and compared. Unfortunately this
      # is complicated by multiple facts, including
      #     (1) the timer might be so fast that the probes don't have time to
      #         fire and print something,
      #     (2) the uprobe might print something after it is re-enabled, but
      #         before the uretprobe is re-enabled,
      #     (3) disabling a uretprobe won't stop an already running handler.
      #
      # To get around these issues, we use a simple state machine. The states
      # are as follow:
      #
      #     0 = cond disabled
      #     1 = cond enabled, but uprobe && uretprobe not yet enabled
      #     2 = cond enabled, uprobe enabled, uretprobe not yet enabled
      #     3 = cond enabled, uprobe && uretprobe enabled, nothing printed yet
      #     4 = cond enabled, 'hit' printed but not 'rethit'
      #     5 = cond enabled, 'rethit' printed

      global state = ENABLED
      global toggles = 0

      probe process("./TEST.x").function("foo") if (state > 0) {
         if (state == 3) {
            println("hit")
            state++;
         } else if (state == 1) {
            state++;
         }
      }

      probe process("./TEST.x").function("foo").return if (state > 0) {
         # ensure that nothing changed during the vfs_read body
         if (state != @entry(state))
            next
         if (state == 4) {
            println("rethit")
            state++;
         } else if (state == 2) {
            state++;
         }
      }

      probe timer.us(TIMER) {
         if (state != 0 && state != 5)
            next # give probes more time to move through the states
         toggles++
         if (toggles > MAXTOGGLES)
            exit()
         else {
            println("toggling")
            state = !state
         }
      }

      probe timer.s(360) {
         println("timed out")
         exit()
      }
   }

   # On ppc64, you can't probe the hcall_* tracepoints (PR17126:
   # tracepoints.exp testcase causing stalls/hang on ppc64). This was
   # worked around by making a tracepoint blocklist. However, this
   # test is using the '-g' (guru) argument to systemtap, which causes
   # systemtap to ignore the tracepoint blocklist. So, on ppc64, we'll
   # probe all the kernel tracepoints *except* for the ones starting
   # with 'h'.
   if {[string match ppc* $::tcl_platform(machine)]} {
      set kernel_trace {kernel.trace("[a-gi-z]*")}
   } else {
      set kernel_trace {kernel.trace("*")}
   }

   # Set in values
   set script [string map "TEST $test \
                           SUBTEST $SUBTEST \
                           ENABLED $ENABLED \
                           MAXTOGGLES $MAXTOGGLES" $script]
   if {[string match $TIMER "profile"]} {
      set script [string map "timer.us(TIMER) timer.profile" $script]
   } elseif {[string match $TIMER "hardcore"]} {
      set script [string map "timer.us(TIMER) \
         {timer.profile, $kernel_trace}" $script]
   } elseif {[string match $TIMER "max"]} {
      set script [string map "timer.us(TIMER) \
         {begin, end, error, kernel.function(\"*@workqueue.c\"), \
          process.begin, process.end, $kernel_trace, \
          netfilter.pf(\"NFPROTO_IPV4\").hook(\"NF_INET_LOCAL_IN\")?, \
          netfilter.pf(\"NFPROTO_IPV4\").hook(\"NF_INET_LOCAL_OUT\")?, \
          perf.sw.cpu_clock.sample(1000000)?, timer.profile.tick?}" $script]
   } else {
      set script [string map "TIMER $TIMER" $script]
   }

   return $script
}

# Creates the pattern which will be used by the is_valid_output proc to
# determine if the output is valid. In other words, given the initial value of
# enabled, the maximum number of toggles, and whether on-the-fly is turned on or
# not, this proc determines what will be the output of stap.
proc make_pattern {subtest start_enabled maxtoggles} {
   set pattern ""

   # If we're not starting as enabled, then the kprobes will be registered as
   # disabled.
   if {!$start_enabled} {
      lappend pattern "* not registering (uretprobe) pidx ?"
      lappend pattern "* not registering (uprobe) pidx ?"
      lappend pattern "* not registering (uprobe) pidx ?"
   }

   # For each toggle:
   #  - if we were enabled, then we would have had 'hit' and 'rethit' lines
   #  - if i == maxtoggles, then we'll exit so nothing more will be printed
   #  - if i < maxtoggles, then the timer probe will output 'toggling' followed
   #    by 'unregistering' or 'registering' lines if on-the-fly is turned on
   for {set i 0} {$i <= $maxtoggles} {incr i} {
      if {$start_enabled} {
         lappend pattern "hit" "rethit"
      }
      if {$i < $maxtoggles} {
         lappend pattern "toggling"
         if {$start_enabled} {
            set start_enabled 0
            lappend pattern "* unregistering (uretprobe) pidx ?"
            lappend pattern "* unregistering (uprobe) pidx ?"
            lappend pattern "* unregistering (uprobe) pidx ?"
         } else {
            set start_enabled 1
            if {$i < $maxtoggles} {
               lappend pattern "* registering (uretprobe) pidx ?"
               lappend pattern "* registering (uprobe) pidx ?"
               lappend pattern "* registering (uprobe) pidx ?"
            }
         }
      }
   }

   return $pattern
}

set c_program {
   int foo(int i) {
      int j = i;
      return j+i*2;
   }
   int main(int argc, char **argv) {
      while (1) {
         argc = foo(argc);
      }
      return argc;
   }
}

# Compile program (feed program through stdin, -x c --> C code)
catch {exec gcc -g -o $test.x -x c - << $c_program} err
if {$err == "" && [file exists $test.x]} {
   pass "$test (compilation)"
} else {
   fail "$test (compilation)"
   verbose -log "GCC output: $err"
   return
}

# Various on-the-fly tests

# Toggle 0, 1, 2, 3, 4, and 5 times. This is important since we test if the
# probes finish well when disabled/enabled.

run_subtest_valid "otf_finish_at_start_disabled" 0 0 500000
run_subtest_valid "otf_finish_at_start_enabled"  1 0 500000
run_subtest_valid "otf_start_disabled_iter_1"    0 1 500000
run_subtest_valid "otf_start_enabled_iter_1"     1 1 500000
run_subtest_valid "otf_start_disabled_iter_2"    0 2 500000
run_subtest_valid "otf_start_enabled_iter_2"     1 2 500000
run_subtest_valid "otf_start_disabled_iter_3"    0 3 500000
run_subtest_valid "otf_start_enabled_iter_3"     1 3 500000
run_subtest_valid "otf_start_disabled_iter_4"    0 4 500000
run_subtest_valid "otf_start_enabled_iter_4"     1 4 500000
run_subtest_valid "otf_start_disabled_iter_5"    0 5 500000
run_subtest_valid "otf_start_enabled_iter_5"     1 5 500000

# Small interval tests

run_subtest_valid "otf_timer_100ms" 1 5 100000
run_subtest_valid "otf_timer_50ms"  1 5 50000

# NB: if we go any shorter, the kernel might not have a chance to run
# systemtap_module_refresh() before the next toggling occurs, throwing off the
# expected output.

# Stress tests

# These tests are not validated, i.e. their outputs are not checked against a
# pattern (see NB above why). Instead, they're just meant to stress the
# on-the-fly mechanism to ensure stap/the kernel doesn't die. So if stap is
# successfully run and terminated, we just default to a pass.

run_subtest_stress "otf_stress_10ms_iter_50"  1 50 10000
run_subtest_stress "otf_stress_5ms_iter_50"   1 50 5000
run_subtest_stress "otf_stress_1ms_iter_50"   1 50 1000
run_subtest_stress "otf_stress_500us_iter_50" 1 50 500
run_subtest_stress "otf_stress_100us_iter_50" 1 50 100

run_subtest_stress "otf_stress_prof_iter_2000" 1 2000 profile

# Use 100 us for interval
run_subtest_stress "otf_stress_hard_iter_2000" 1 2000 hardcore \
   -D STP_ON_THE_FLY_INTERVAL=100000

run_subtest_stress "otf_stress_max_iter_5000" 1 5000 max \
   -D STP_ON_THE_FLY_INTERVAL=100000

if {[file exists $test.x]} {
   file delete $test.x
}
