1#!/bin/bash
2#
3# Runs a CUJ and collects PSI measurement.
4# Supported CUJs:
5#   - Bootup
6#   - User switch
7#   - App startup
8#   - Bootup with app launch
9#   - Resume (Work in progress)
10set -E
11function trap_error() {
12    local return_value=$?
13    local line_no=$1
14    echo "Error at line ${line_no}: \"${BASH_COMMAND}\". Return code ${return_value}" \
15          | tee -a ${OUTPUT_DIR}/fatal_log.txt
16    pkill -P $$
17    aae_device_recover
18}
19trap 'trap_error $LINENO' ERR
20
21function clean_exit() {
22  execute_on_adb_connect "adb logcat -G 64K -b all -c"
23}
24trap 'clean_exit' EXIT
25
26readonly PSI_MONITOR_REPO_DIR="packages/services/Car/tools/psi_monitor"
27readonly ADB_WAIT_FOR_DEVICE_SCRIPT="adb_wait_for_device.sh"
28readonly PSI_MONITOR_SCRIPT="psi_monitor.sh"
29readonly PARSE_SCRIPT="parse_psi_events.py"
30readonly PLOT_SCRIPT="psi_plot.py"
31readonly GENERATE_KPIS_SCRIPT="generate_kpis.py"
32function setup_local_dir() {
33  if [[ -z ${ANDROID_BUILD_TOP} ]]; then
34    readonly LOCAL_SCRIPT_DIR=$(dirname ${0})
35  else
36    readonly LOCAL_SCRIPT_DIR=${ANDROID_BUILD_TOP}/${PSI_MONITOR_REPO_DIR}
37  fi
38  if [[ ! -f ${LOCAL_SCRIPT_DIR}/${ADB_WAIT_FOR_DEVICE_SCRIPT} ]]; then
39    echo -e "${ADB_WAIT_FOR_DEVICE_SCRIPT} script not found in ${LOCAL_SCRIPT_DIR}" >&2
40    exit 1
41  fi
42  if [[ ! -f ${LOCAL_SCRIPT_DIR}/${PSI_MONITOR_SCRIPT} ]]; then
43    echo -e "PSI monitor script ${PSI_MONITOR_SCRIPT} not found in ${LOCAL_SCRIPT_DIR}" >&2
44    exit 1
45  fi
46  if [[ ! -f ${LOCAL_SCRIPT_DIR}/${PARSE_SCRIPT} ]]; then
47    echo -e "PSI parse script ${PARSE_SCRIPT} not found in ${LOCAL_SCRIPT_DIR}" >&2
48    exit
49  fi
50  if [[ ! -f ${LOCAL_SCRIPT_DIR}/${PLOT_SCRIPT} ]]; then
51    echo -e "${PLOT_SCRIPT} script not found in ${LOCAL_SCRIPT_DIR}" >&2
52    exit 1
53  fi
54  if [[ ! -f ${LOCAL_SCRIPT_DIR}/${GENERATE_KPIS_SCRIPT} ]]; then
55    echo -e "${GENERATE_KPIS_SCRIPT} script not found in ${LOCAL_SCRIPT_DIR}" >&2
56    exit 1
57  fi
58}
59
60setup_local_dir
61source ${LOCAL_SCRIPT_DIR}/${ADB_WAIT_FOR_DEVICE_SCRIPT}
62
63readonly DEVICE_OUT_DIR="/data/local/tmp/psi_monitor"
64readonly DEVICE_SCRIPT="/data/local/tmp/psi_monitor.sh"
65readonly MIN_BOOTUP_DURATION_SECONDS=60
66readonly MIN_USER_SWITCH_DURATION_SECONDS=30
67readonly POST_APP_STARTUP_MONITOR_DURATION_SECONDS=30
68readonly POST_BOOT_APPS_LAUNCH_TIMES=5
69readonly MIN_BOOT_WITH_APP_LAUNCH_DURATION_SECONDS=90
70readonly TOTAL_PSI_ENTRIES_TO_MONITOR_BASELINE=10
71
72OUTPUT_DIR=${PWD}/out
73PSI_AVG10_THRESHOLD=80
74MAX_PSI_MONITOR_DURATION_SECONDS=120
75PSI_MONITOR_GRACE_DURATION_SECONDS=20
76CUJ=""
77USER_SWITCH_ARGS=""
78APP_STARTUP_PACKAGE=""
79APP_STARTUP_TIMES=1
80POST_BOOT_APP_LAUNCH_STATE="threshold_met"
81MAX_WAIT_FOR_FIRST_CAR_LAUNCHER_DISPLAYED_LOG_MILLISECONDS=1500
82SHOW_PLOTS=true
83PRINT_CUJ_DESC_AND_EXIT=false
84
85function usage() {
86  echo "Runs a CUJ, collects PSI measurement, and generates PSI plots / KPIs ."
87  echo "Usage: run_cuj.sh [-o|--out_dir <output_dir>]"\
88       "[-d|--max_duration_seconds <max_duration_seconds>]"\
89       "[-g|--grace_duration_seconds <grace_duration_seconds>]"\
90       "[-b|--bootup] [-u|--user_switch [<to_user_id> | <from_user_id>-<to_user_id>]]"\
91       "[-a|--app_startup <app_package_name>] [--app_startup_times <app_startup_times>]"\
92       "[-r|--resume]"\
93       "[--bootup_with_app_launch <post_boot_app_launch_state>]"
94
95  echo "-o|--out_dir: Location to output the psi dump and logs"
96  echo "-t|--psi_avg10_threshold: PSI threshold level for peak CPU activity during post boot"
97  echo "-d|--max_duration_seconds: Maximum duration to monitor for PSI changes"
98  echo "-g|--grace_duration_seconds: Grace duration to wait before terminating the monitor"
99  echo "-b|--bootup: Run bootup CUJ"
100  echo "-u|--user_switch: Run user switch CUJ"
101  echo "-r|--resume: Run resume from suspend CUJ"
102  echo "-a|--app_startup: Run app startup CUJ"
103  echo "--app_startup_times: Number of times to launch the app during app startup CUJ"
104  echo "--bootup_with_app_launch: Run bootup with app launch CUJ. Must provide post boot app"\
105       "launch state, which is the post boot state to launch apps. Following states are supported:"
106  echo "\t- threshold_met: Launch apps when PSI threshold is met"
107  echo "\t- baseline_met: Launch apps when PSI baseline is met"
108  echo "\t- immediate: Launch apps immediately after launcher is displayed"
109  echo "\--no_show_plots: Don't show plots at the end of the run. Useful when running this script "\
110       "in a loop"
111  echo "--print_cuj_desc: Prints the CUJ description and exits"
112  echo "-h|--help: Show this help message"
113
114  echo "Example commands:"
115  echo -e "\t1. ./run_cuj.sh -d 90 -g 20 -t 20 --bootup_with_app_launch threshold_met"
116  echo -e "\t2. ./run_cuj.sh -d 90 -g 20 -t 20 --bootup_with_app_launch baseline_met"
117  echo -e "\t3. ./run_cuj.sh -d 90 -g 20 -a com.android.contacts --app_startup_times 10"
118  echo -e "\t4. ./run_cuj.sh -d 90 -g 20 -u 10-11"
119  echo -e "\t5. ./run_cuj.sh -d 90 -g 20 -t 20 -b"
120}
121
122function usage_err() {
123  err "$@\n"
124  usage
125}
126
127function check_and_set_cuj() {
128  if [[ ! -z ${CUJ} ]]; then
129    usage_err "Multiple CUJs specified on the command line: ${CUJ} and ${1}"
130    exit 1
131  fi
132  CUJ=$1
133}
134
135function parse_arguments() {
136  while [[ $# > 0 ]]; do
137    key="$1"
138    case $key in
139    -h|--help)
140        usage
141        exit 1;;
142    -o|--out_dir)
143      OUTPUT_DIR=${2}
144      shift;;
145    -t|--psi_avg10_threshold)
146      PSI_AVG10_THRESHOLD=${2}
147      shift;;
148    -d|--max_duration_seconds)
149      MAX_PSI_MONITOR_DURATION_SECONDS=${2}
150      shift;;
151    -g|--grace_duration_seconds)
152      PSI_MONITOR_GRACE_DURATION_SECONDS=${2}
153      shift;;
154    -b|--bootup)
155      check_and_set_cuj "bootup"
156      ;;
157    -u|--user_switch)
158      check_and_set_cuj "user-switch"
159      USER_SWITCH_ARGS=${2}
160      shift;;
161    -a|--app_startup)
162      check_and_set_cuj "app-startup"
163      APP_STARTUP_PACKAGE=${2}
164      shift;;
165    --app_startup_times)
166      APP_STARTUP_TIMES=${2}
167      shift;;
168    -r|--resume)
169      check_and_set_cuj "resume"
170      ;;
171    --bootup_with_app_launch)
172      check_and_set_cuj "bootup-with-app-launch"
173      POST_BOOT_APP_LAUNCH_STATE=${2}
174      shift;;
175    --no_show_plots)
176      SHOW_PLOTS=false
177      ;;
178    --print_cuj_desc)
179      PRINT_CUJ_DESC_AND_EXIT=true
180      ;;
181    *)
182      echo "${0}: Invalid option '${1}'"
183      usage
184      exit 1;;
185    esac
186    shift # past argument or value
187  done
188}
189
190function print_log() {
191  if [[ ${PRINT_CUJ_DESC_AND_EXIT} == true ]]; then
192    return
193  fi
194  echo -e "[$(date +'%Y-%m-%d %H:%M:%S%z')] ${@}" | tee -a ${OUTPUT_DIR}/run_cuj_log.txt
195}
196
197function err() {
198  echo -e "[$(date +'%Y-%m-%d %H:%M:%S%z')] $@" >&2 | tee -a ${OUTPUT_DIR}/run_cuj_err.txt
199}
200
201function check_arguments() {
202  readonly OUTPUT_DIR
203  mkdir -p ${OUTPUT_DIR}
204  if [[ ! -d ${OUTPUT_DIR} ]]; then
205    usage_err "Out dir ${OUTPUT_DIR} does not exist"
206    exit 1
207  fi
208
209  readonly PSI_AVG10_THRESHOLD
210  if [[ ${PSI_AVG10_THRESHOLD} != +([[:digit:]]) || ${PSI_AVG10_THRESHOLD} -le 0 \
211        || ${PSI_AVG10_THRESHOLD} -ge 100 ]]; then
212    usage_err "PSI Avg10 threshold ${PSI_AVG10_THRESHOLD} is not a valid number. The value should"\
213        "be between 1 and 99"
214    exit 1
215  fi
216
217  if [[ ${MAX_PSI_MONITOR_DURATION_SECONDS} != +([[:digit:]]) \
218        || ${MAX_PSI_MONITOR_DURATION_SECONDS} -lt 10 \
219        || ${MAX_PSI_MONITOR_DURATION_SECONDS} -gt 3600 ]]; then
220    usage_err "Max psi monitor duration seconds ${MAX_PSI_MONITOR_DURATION_SECONDS} is not"\
221        "a valid number. The value should be between 10 and 3600"
222    exit 1
223  fi
224
225  readonly PSI_MONITOR_GRACE_DURATION_SECONDS
226  if [[ ${PSI_MONITOR_GRACE_DURATION_SECONDS} != +([[:digit:]]) \
227        || ${PSI_MONITOR_GRACE_DURATION_SECONDS} -lt 10 \
228        || ${PSI_MONITOR_GRACE_DURATION_SECONDS} -gt 3600 ]]; then
229    usage_err "Psi monitor grace duration seconds ${PSI_MONITOR_GRACE_DURATION_SECONDS} is not"\
230        "a valid number. The value should be between 10 and 3600"
231    exit 1
232  fi
233
234  if [[ -z ${CUJ} ]]; then
235    CUJ="bootup"
236    print_log "CUJ not specified, defaulting to bootup"
237  fi
238
239  readonly CUJ
240}
241
242ALL_USER_IDS=()
243##
244#  Reads user ids from the device and sets the global variable ALL_USER_IDS.
245#
246# Parses the output of "adb shell pm list users", which is in the format of
247# """
248# Users:
249#       UserInfo{0:Driver:813} running
250#       UserInfo{10:Driver:412} running
251# """
252function read_all_user_ids() {
253  IFS=' ' read -r ALL_USER_IDS <<< $(adb shell pm list users | grep "{" | cut -d'{' -f2 \
254                                     | cut -d':' -f1 | tr '\n' ' ')
255}
256
257FROM_USER_ID=""
258TO_USER_ID=""
259function check_cuj_args() {
260  case ${CUJ} in
261    bootup)
262      if [[ ${MAX_PSI_MONITOR_DURATION_SECONDS} -lt ${MIN_BOOTUP_DURATION_SECONDS} ]]; then
263        MAX_PSI_MONITOR_DURATION_SECONDS=${MIN_BOOTUP_DURATION_SECONDS}
264        print_log "Max psi monitor duration seconds is set to ${MAX_PSI_MONITOR_DURATION_SECONDS}"\
265             "to accommodate bootup CUJ"
266      fi
267      ;;
268    user-switch)
269      user_switch_args=()
270      IFS='-' read -r -a user_switch_args <<< "${USER_SWITCH_ARGS}"
271      if [[ ${#user_switch_args[@]} -lt 1 ||${#user_switch_args[@]} -gt 2 ]]; then
272        usage_err "Invalid user switch args: ${USER_SWITCH_ARGS}. It should be in the format of" \
273            "<to_user_id> or <from_user_id>-<to_user_id>"
274        exit 1
275      elif [[ ${#user_switch_args[@]} -eq 2 ]]; then
276        FROM_USER_ID=${user_switch_args[0]}
277        TO_USER_ID=${user_switch_args[1]}
278      else
279        FROM_USER_ID=$(adb shell am get-current-user)
280        TO_USER_ID=${user_switch_args[0]}
281      fi
282
283      if [[ ${FROM_USER_ID} -eq 0 || ${TO_USER_ID} -eq 0 ]]; then
284        err "From user id and to user id should be non-zero or non system users"
285        exit 1
286      elif [[ ${FROM_USER_ID} -eq ${TO_USER_ID} ]]; then
287        err "From user id and to user id should be different"
288        exit 1
289      fi
290
291      read_all_user_ids
292      if [[ ! " ${ALL_USER_IDS[@]} " =~ " ${FROM_USER_ID} " ]]; then
293        err "From user id ${FROM_USER_ID} is not a valid user id."\
294            "Valid user ids are: ${ALL_USER_IDS[@]}"
295        exit 1
296      elif [[ ! " ${ALL_USER_IDS[@]} " =~ " ${TO_USER_ID} " ]]; then
297        err "To user id ${TO_USER_ID} is not a valid user id."\
298            "Valid user ids are: ${ALL_USER_IDS[@]}"
299        exit 1
300      fi
301      if [[ ${MAX_PSI_MONITOR_DURATION_SECONDS} -lt ${MIN_USER_SWITCH_DURATION_SECONDS} ]]; then
302        MAX_PSI_MONITOR_DURATION_SECONDS=${MIN_USER_SWITCH_DURATION_SECONDS}
303        print_log "Max psi monitor duration seconds is set to ${MAX_PSI_MONITOR_DURATION_SECONDS}"\
304             "to accommodate user switch CUJ"
305      fi
306      ;;
307    app-startup)
308      if [[ -z ${APP_STARTUP_PACKAGE} ]]; then
309        usage_err "App package name is not specified for app startup CUJ"
310        exit 1
311      fi
312      if [[ ${APP_STARTUP_TIMES} != +([[:digit:]]) || ${APP_STARTUP_TIMES} -le 0
313          || ${APP_STARTUP_TIMES} -gt 100 ]]; then
314        usage_err "App startup times should be positive integer between 1 and 100"
315        exit 1
316      fi
317      min_duration=$(echo "${APP_STARTUP_TIMES}
318                      + ${POST_APP_STARTUP_MONITOR_DURATION_SECONDS}" | bc)
319      if [[ ${MAX_PSI_MONITOR_DURATION_SECONDS} -lt ${min_duration} ]]; then
320        MAX_PSI_MONITOR_DURATION_SECONDS=${min_duration}
321        print_log "Max psi monitor duration seconds is set to ${MAX_PSI_MONITOR_DURATION_SECONDS}"\
322             "to accommodate app startup CUJ"
323      fi
324      ;;
325    bootup-with-app-launch)
326      if [[ ${POST_BOOT_APP_LAUNCH_STATE} != "threshold_met" && \
327            ${POST_BOOT_APP_LAUNCH_STATE} != "baseline_met" && \
328            ${POST_BOOT_APP_LAUNCH_STATE} != "immediate" ]]; then
329        usage_err "Post boot app launch state should be either threshold_met, baseline_met or"\
330                  "immediate"
331        exit 1
332      fi
333      if [[ ${MAX_PSI_MONITOR_DURATION_SECONDS} -lt ${MIN_BOOT_WITH_APP_LAUNCH_DURATION_SECONDS} ]]
334      then
335        MAX_PSI_MONITOR_DURATION_SECONDS=${MIN_BOOT_WITH_APP_LAUNCH_DURATION_SECONDS}
336        print_log "Max psi monitor duration seconds is set to ${MAX_PSI_MONITOR_DURATION_SECONDS}"\
337             "to accommodate bootup with app launch CUJ"
338      fi
339  esac
340}
341
342function setup() {
343  adb_wait_with_recovery
344  fetch_device_info
345  adb shell rm -rf ${DEVICE_SCRIPT} ${DEVICE_OUT_DIR}
346  adb push ${LOCAL_SCRIPT_DIR}/${PSI_MONITOR_SCRIPT} ${DEVICE_SCRIPT}
347  adb shell chmod 755 ${DEVICE_SCRIPT}
348  adb shell mkdir -p ${DEVICE_OUT_DIR}
349  adb shell setprop persist.debug.psi_monitor.cuj_completed false
350  adb logcat -G 16M -b all -c
351}
352
353FROM_USER_STOPPED_COUNT=0
354function get_from_user_stopped_count() {
355  echo $(adb logcat -d -b events | grep "ssm_user_stopped: ${FROM_USER_ID}" | wc -l)
356}
357
358# This function is called before starting the CUJ. It performs any setup required for the CUJ.
359# This function should leave the device in a state where the adb connection is active.
360function pre_start_cuj() {
361  case ${CUJ} in
362    bootup | bootup-with-app-launch)
363      print_log "Rebooting adb device"; adb reboot
364      adb_wait_with_recovery
365      adb logcat -G 16M
366      ;;
367    user-switch)
368      current_user_id=$(adb shell am get-current-user)
369      if [[ ${current_user_id} -eq ${FROM_USER_ID} ]]; then
370        return
371      else
372        print_log "Switching user to from user ${FROM_USER_ID}, which is different from current user"\
373             "${current_user_id}"
374        adb shell am switch-user ${FROM_USER_ID}
375        print_log "Waiting for user switch to complete by sleeping for 60 seconds"
376        sleep 60
377        # User switch events from previous switches should be ignored. So, get the number of
378        # user switch events from the previous run. Then use this to track the current user switch
379        # event.
380        FROM_USER_STOPPED_COUNT=$(get_from_user_stopped_count)
381      fi
382      ;;
383    resume)
384      err "Resume is not yet supported. Device needs an extra board to support this."\
385          "Refer to http://go/seahawk-str"; exit 1
386      adb shell cmd car_service hibernate --auto
387      adb_wait_with_recovery
388      ;;
389    app-startup)
390      res=$(adb shell pm list packages ${APP_STARTUP_PACKAGE})
391      if [[ -z ${res} ]]; then
392        err "App package ${APP_STARTUP_PACKAGE} is not installed on the device"
393        exit 1
394      elif [[ ! ${res} =~ ^package:${APP_STARTUP_PACKAGE}.* ]]; then
395        err "Multiple app packages with prefix ${APP_STARTUP_PACKAGE} are installed on the device"
396        exit 1
397      else
398        APP_STARTUP_PACKAGE=$(echo ${res} | cut -d':' -f2)
399      fi
400      # Reset the device to the home screen before starting the CUJ.
401      adb shell am start com.android.car.carlauncher/.CarLauncher
402      ;;
403    *)
404      ;;
405  esac
406}
407
408function fetch_logs() {
409  rm -f ${OUTPUT_DIR}/filtered_logcat.txt
410  while true; do
411    adb_wait_with_recovery
412    adb logcat -v year -s ActivityTaskManager,SystemServerTimingAsync \
413      >> ${OUTPUT_DIR}/filtered_logcat.txt
414  done
415}
416
417function run_psi_monitor() {
418  set +e
419  readonly TERM_SIGNAL_TIMEOUT=$(echo "${MAX_PSI_MONITOR_DURATION_SECONDS} \
420                        + ${PSI_MONITOR_GRACE_DURATION_SECONDS}" | bc)
421  readonly KILL_SIGNAL_DURATION=${PSI_MONITOR_GRACE_DURATION_SECONDS}
422  print_log "Starting PSI monitoring with timeout ${TERM_SIGNAL_TIMEOUT}s and kill duration"\
423            "${KILL_SIGNAL_DURATION}s"
424  timeout --preserve-status -k ${KILL_SIGNAL_DURATION} ${TERM_SIGNAL_TIMEOUT} \
425    adb shell ${DEVICE_SCRIPT} --out_dir ${DEVICE_OUT_DIR} \
426      --psi_avg10_threshold ${PSI_AVG10_THRESHOLD} \
427      --max_duration_seconds ${MAX_PSI_MONITOR_DURATION_SECONDS} \
428      --total_psi_entries_to_monitor_baseline ${TOTAL_PSI_ENTRIES_TO_MONITOR_BASELINE} \
429        > ${OUTPUT_DIR}/psi_monitor_log.txt 2> ${OUTPUT_DIR}/psi_monitor_log.txt
430  exit_status=$?
431  print_log "psi_monitor.sh timeout command exit status is '${exit_status}'"
432  set -e
433  return ${exit_status}
434}
435
436function start_activity() {
437  if [ $# -eq 1 ]; then
438    package=$1
439  elif [ $# -eq 2 ]; then
440    action=$1
441    component=$2
442  else
443    action=$1
444    category=$2
445    flag=$3
446    component=$4
447  fi
448
449  if [ $# -eq 1 ]; then
450    print_log "Starting activity for ${package}"
451    print_log $(adb shell am start -W --user current ${package})
452    sleep 1 # Wait to allow the package to perform some init tasks.
453    return
454  fi
455
456  print_log "Starting activity ${component}"
457  if [ $# -eq 2 ]; then
458    print_log $(adb shell am start -W --user current -a ${action} -n ${component})
459  else
460    print_log $(adb shell am start -W --user current -a ${action} -c ${category} -f ${flag} \
461                -n ${component})
462  fi
463  sleep 1 # Wait to allow the package to perform some init tasks.
464}
465
466function start_cuj() {
467  case ${CUJ} in
468    bootup | bootup-with-app-launch)
469      # Boot doesn't need to be started by the script as it is already done by the bootloader
470      # as part of the reboot.
471      ;;
472    user-switch)
473      print_log "Switching user to user ${TO_USER_ID}"
474      adb shell am switch-user ${TO_USER_ID}
475      ;;
476    resume)
477      # Device will be automatically woken up by the hibernate wakeup event. So, no action is
478      # needed.
479      ;;
480    app-startup)
481      print_log "Starting app startup CUJ for ${APP_STARTUP_PACKAGE}"
482      for i in $(seq 1 ${APP_STARTUP_TIMES}); do
483        adb shell am force-stop --user current ${APP_STARTUP_PACKAGE}
484        print_log "Starting app startup CUJ for ${APP_STARTUP_PACKAGE} for the ${i} time"
485        start_activity ${APP_STARTUP_PACKAGE}
486      done
487      ;;
488  esac
489}
490
491function wait_for_cuj_to_complete() {
492  print_log "Waiting for CUJ to complete"
493  case ${CUJ} in
494    bootup | bootup-with-app-launch)
495      while [[ $(adb shell getprop sys.boot_completed) != 1 ]]; do
496        sleep 0.5
497        # Device connection will be lost intermittently during bootup. So, wait for the device
498        # connection after every 0.5 seconds.
499        adb_wait_with_recovery
500      done
501      # PSI monitor script will detect threshold and baseline changes only after the CUJ is
502      # completed. So, for boot-with-app-launch CUJ, mark the CUJ as completed on boot completed.
503      # This will allow the CUJ to progress forward based on the threshold_met or baseline_met
504      # conditions.
505      ;;
506    user-switch)
507      while [[ $(get_from_user_stopped_count) -eq ${FROM_USER_STOPPED_COUNT} ]]; do
508        sleep 0.1
509      done
510      ;;
511    resume)
512      # When adb is available after the device is woken up, the CUJ is considered as completed. So,
513      # no need to wait for any other event.
514      ;;
515    app-startup)
516      # App startup CUJ is considered as completed when all the apps are started. So, no need to
517      # wait for any other event.
518      ;;
519  esac
520  print_log "CUJ completed, marking CUJ as completed"
521  adb shell setprop persist.debug.psi_monitor.cuj_completed true
522}
523
524function wait_for_threshold_met() {
525  print_log "Waiting for threshold met"
526  while [[ $(adb shell getprop persist.debug.psi_monitor.threshold_met) != true ]]; do
527    # The PSI values are read only every 1 seconds by the PSI monitor script. So, wait for 50% of
528    # this duration to avoid waiting for too long and polling the device too often.
529    sleep 0.5
530  done
531}
532
533function wait_for_baseline_met() {
534  print_log "Waiting for baseline met"
535  while [[ $(adb shell getprop persist.debug.psi_monitor.baseline_met) != true ]]; do
536    # The PSI values are read only every 1 seconds by the PSI monitor script. So, wait for 50% of
537    # this duration to avoid waiting for too long and polling the device too often.
538    sleep 0.5
539  done
540}
541
542function start_launcher_activity() {
543  start_activity android.intent.action.MAIN android.intent.category.HOME 0x14000000 \
544    com.android.car.carlauncher/.CarLauncher
545}
546
547function start_app_grid_activity() {
548  start_activity com.android.car.carlauncher.ACTION_APP_GRID android.intent.category.HOME \
549    0x24000000 com.android.car.carlauncher/.GASAppGridActivity
550}
551
552function start_assistant_activity() {
553  start_activity com.google.android.carassistant
554}
555
556function stop_assistant_activity() {
557  adb shell am force-stop --user current com.google.android.carassistant
558}
559
560function start_maps_activity() {
561  start_activity android.intent.action.VIEW \
562    com.google.android.apps.maps/com.google.android.maps.MapsActivity
563}
564
565function stop_maps_activity() {
566  adb shell am force-stop --user current com.google.android.apps.maps
567}
568
569function start_play_store_activity() {
570  start_activity com.android.vending
571}
572
573function stop_play_store_activity() {
574  adb shell am force-stop --user current com.android.vending
575}
576
577function start_contacts_activity() {
578  start_activity com.android.contacts
579}
580
581function stop_contacts_activity() {
582  adb shell am force-stop --user current com.android.contacts
583}
584
585function get_component_displayed_count() {
586  echo $(grep "ActivityTaskManager: Displayed " ${OUTPUT_DIR}/filtered_logcat.txt | grep ${1} \
587         | wc -l)
588}
589
590function wait_for_activity_displayed() {
591  max_wait_millis=$2
592  print_log "Waiting for activity ${1} to be displayed"
593  slept_millis=0
594  while [[ $(get_component_displayed_count ${1}) -eq 0
595           && ${slept_millis} -lt ${max_wait_millis} ]]; do
596    sleep 0.1
597    slept_millis=$(($slept_millis + 100))
598  done
599  print_log "Activity ${1} completed"
600}
601
602function launch_app_on_post_boot() {
603  # Limited maps activity is shown in the car launcher UI. So, force stop maps while in app grid
604  # activity to ensure that the maps activity is not shown and the app performs a warm start during
605  # the test.
606  start_app_grid_activity
607  stop_maps_activity
608  for i in $(seq 1 ${POST_BOOT_APPS_LAUNCH_TIMES}); do
609    print_log "Launching apps on post boot for the ${i}th time"
610    # Start apps
611    start_assistant_activity
612    start_maps_activity
613    start_play_store_activity
614    start_contacts_activity
615
616    if [[ ${i} -lt ${POST_BOOT_APPS_LAUNCH_TIMES} ]]; then
617      # Switch to app grid and force stop apps, so these app can perform warm / cold start during
618      # next run instead of bringing the previous activity to the foreground.
619      start_app_grid_activity
620      stop_assistant_activity
621      stop_maps_activity
622      stop_play_store_activity
623      stop_contacts_activity
624    fi
625  done
626}
627
628function post_cuj_events() {
629  case ${CUJ} in
630    bootup-with-app-launch)
631      if [[ ${POST_BOOT_APP_LAUNCH_STATE} == "threshold_met" ]]; then
632        wait_for_threshold_met
633      elif [[ ${POST_BOOT_APP_LAUNCH_STATE} == "baseline_met" ]]; then
634        wait_for_baseline_met
635      else
636        # On immediate, wait for CarLauncher to be displayed and sleep for 2 seconds to ensure
637        # the device has performed some init for the CarLauncher.
638        wait_for_activity_displayed com.android.car.carlauncher/.CarLauncher \
639          ${MAX_WAIT_FOR_FIRST_CAR_LAUNCHER_DISPLAYED_LOG_MILLISECONDS}
640        sleep 2
641      fi
642      launch_app_on_post_boot
643      ;;
644    *)
645      ;;
646  esac
647}
648
649function get_cuj_desc() {
650  desc=${CUJ}
651  case ${CUJ} in
652    bootup)
653      desc="Post boot"
654      ;;
655    bootup-with-app-launch)
656      desc="Post boot with app launch after "
657      case ${POST_BOOT_APP_LAUNCH_STATE} in
658        threshold_met)
659          desc+="PSI threshold ${PSI_AVG10_THRESHOLD}% is met)"
660          ;;
661        baseline_met)
662          desc+="PSI reaches baseline across ${TOTAL_PSI_ENTRIES_TO_MONITOR_BASELINE} entries"
663          ;;
664        immediate)
665          desc+="car launcher is displayed"
666          ;;
667        esac
668      ;;
669    user-switch)
670      desc="Post user switch from ${FROM_USER_ID} to ${TO_USER_ID}"
671      ;;
672    app-startup)
673      desc="App startup for ${APP_STARTUP_PACKAGE}"
674      ;;
675    resume)
676      desc="Post resume"
677      ;;
678  esac
679  echo ${desc}
680}
681
682function main() {
683  set -e
684  parse_arguments "$@"
685  check_arguments
686  check_cuj_args
687  if [[ ${PRINT_CUJ_DESC_AND_EXIT} == true ]]; then
688    echo $(get_cuj_desc)
689    exit
690  fi
691  setup
692
693  pre_start_cuj
694  # Disable SELinux enforcement to allow the PSI monitor script to read the interfaces at
695  # /proc/pressure. During pre_start_cuj the device may reboot, so perform these actions only after
696  # the device is up.
697  adb root; adb_wait_with_recovery
698  adb shell setenforce 0
699
700  fetch_logs &
701  fetch_logs_pid=$!
702
703  run_psi_monitor &
704  psi_monitor_pid=$!
705
706  start_cuj
707
708  wait_for_cuj_to_complete
709
710  post_cuj_events &
711  post_cuj_events_pid=$!
712  print_log "Triggered post CUJ events in the background at pid ${post_cuj_events_pid}"
713
714  print_log "Waiting for PSI monitoring to complete at pid ${psi_monitor_pid}"
715  set +e
716  wait ${psi_monitor_pid}
717  set -e
718
719  if [[ $(ps -p ${post_cuj_events_pid} > /dev/null) ]]; then
720    # If the post CUJ events are still running, kill them because the PSI monitor has completed.
721    print_log "Killing post CUJ events at pid ${post_cuj_events_pid}"
722    kill ${post_cuj_events_pid}
723  fi
724
725  print_log "Killing logcat at pid ${fetch_logs_pid}"
726  kill ${fetch_logs_pid}
727
728  adb_wait_with_recovery
729  adb pull ${DEVICE_OUT_DIR} ${OUTPUT_DIR}
730  psi_monitor_out=${OUTPUT_DIR}/$(basename ${DEVICE_OUT_DIR})
731  processed_out=${OUTPUT_DIR}/processed; mkdir -p ${processed_out}
732
733  ${LOCAL_SCRIPT_DIR}/${PARSE_SCRIPT} --psi_dump ${psi_monitor_out}/psi_dump.txt \
734    --psi_csv ${processed_out}/psi.csv --logcat ${OUTPUT_DIR}/filtered_logcat.txt \
735    --events_csv ${processed_out}/events.csv
736  if [[ ${SHOW_PLOTS} == true ]]; then
737    ${LOCAL_SCRIPT_DIR}/${PLOT_SCRIPT} --psi_csv ${processed_out}/psi.csv \
738      --events_csv ${processed_out}/events.csv --cuj_name "$(get_cuj_desc)" \
739      --out_file ${processed_out}/psi_plot.html --show_plot
740  else
741    ${LOCAL_SCRIPT_DIR}/${PLOT_SCRIPT} --psi_csv ${processed_out}/psi.csv \
742      --events_csv ${processed_out}/events.csv --cuj_name "$(get_cuj_desc)" \
743      --out_file ${processed_out}/psi_plot.html
744  fi
745  ${LOCAL_SCRIPT_DIR}/${GENERATE_KPIS_SCRIPT} --psi_csv ${processed_out}/psi.csv \
746    --events_csv ${processed_out}/events.csv --out_kpi_csv ${processed_out}/kpis.csv
747
748  # Capture a bugreport for investigation purposes.
749  adb bugreport ${OUTPUT_DIR}
750}
751
752main "$@"
753