File: record-hardware-events

package info (click to toggle)
chromium 139.0.7258.138-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 6,120,676 kB
  • sloc: cpp: 35,100,869; ansic: 7,163,530; javascript: 4,103,002; python: 1,436,920; asm: 946,517; xml: 746,709; pascal: 187,653; perl: 88,691; sh: 88,436; objc: 79,953; sql: 51,488; cs: 44,583; fortran: 24,137; makefile: 22,147; tcl: 15,277; php: 13,980; yacc: 8,984; ruby: 7,485; awk: 3,720; lisp: 3,096; lex: 1,327; ada: 727; jsp: 228; sed: 36
file content (332 lines) | stat: -rwxr-xr-x 9,309 bytes parent folder | download | duplicates (7)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
#!/bin/sh
#
# Record hardware counter events using 'simpleperf' on Android.
#
# Usage:
#   $0 [-a APP] [-d DURATION] [-e EVENT]...
#
# The script has two modes of operation:
#
# - In 'timed' mode (with flag '-d' specified), the events specified
#   will be recorded for 'DURATION' seconds.
# - In 'free-running' mode (without '-d' specified), recording will continue
#   till explicitly stopped by pressing 'ENTER'.
#
# Output Format:
#
#   The output is a CSV file, with the following structure:
#
#     THREAD_NAME,CPU,EVENT-COUNT,EVENT-COUNT,...
#
#   where each 'EVENT' is the name of the event specified on invocation.
#
#   Event counts for a given <thread-name, CPU> pair across processes
#   are merged.
#
# Prerequisites (on the host):
#
#   ADB     https://developer.android.com/studio/command-line/adb
#   MILLER  https://miller.readthedocs.io/en/latest/
#
local_tmpdir=$(mktemp --directory) || {
  echo ERROR: Cannot create temporary directory.
  exit 1
}
trap "rm -r ${local_tmpdir}" EXIT INT

default_app="com.android.chrome"
default_event="cpu-cycles"
print_test_time=NO
verbose=NO

debug_dir=""
duration=""
simpleperf_events=""

# Print usage and exit.
usage()
{
  [[ ${#} -gt 0 ]] && {
    echo ERROR: $*
    echo
  }

  echo "Usage: $0 [options]

Measure performance counter events using 'simpleperf'.

Options:
  -D DIR     Save intermediate files to directory DIR for debugging.
  -T         Display the test run time reported by 'simpleperf'.
  -a APP     Measure APP [default: '${default_app}'].
  -d DUR     Run for duration seconds [default: run till asked to exit].
  -e EVENT   Measure perf event 'EVENT' [default: ${default_event}].
  -n         Dry-run.  Implies -v.
  -v         Be verbose.
"
  exit 2
}

# Parse command-line options.
parse_options_and_set_defaults() {
  local OPTIND
  while getopts D:HTa:d:e:nv option; do
    case $option in
    D)    debug_dir="$OPTARG"
          ;;
    T)    print_test_time=YES
          ;;
    a)    app="$OPTARG"
          ;;
    d)    duration="$OPTARG"
          ;;
    e)    simpleperf_events="${simpleperf_events} ${OPTARG}"
          ;;
    n)    dry_run=YES
          verbose=YES
          ;;
    v)    verbose=YES
          ;;
    ?)    usage
          exit 2
          ;;
    esac
  done

  shift $(($OPTIND - 1))

  # Set defaults.
  [[ ${verbose} = YES ]] && echo "[V] TMPDIR: ${local_tmpdir}"

  [[ -n "${simpleperf_events}" ]] || simpleperf_events="${default_event}"

  [[ -z "${app}" ]] && app=${default_app}
}

# Prepares the command line to use on the device.
#
# The prepared command line is displayed to the user if verbose mode (-v)
# is in effect.
prepare_simpleperf_command_line() {
  measurement_command="/system/bin/simpleperf stat --per-core --per-thread --csv "
  measurement_command="${measurement_command} --app ${app}"

  if [[ -n ${duration} ]]; then
    measurement_command="${measurement_command} --duration ${duration}"
  fi

  # Add the simpleperf events specified, while remembering the desired output
  # order of events.
  output_order=""
  for event in ${simpleperf_events}; do
    measurement_command="${measurement_command} -e ${event} "
    if [[ -z "${output_order}" ]]; then
      output_order="${event}"
    else
      output_order="${output_order},${event}"
    fi
  done

  [[ ${verbose} = YES ]] && echo "[V] COMMAND: ${measurement_command}"
}

# Bail early if preconditions are not satisfied.
run_sanity_checks() {
  if ! which mlr > /dev/null; then
    echo ERROR: "'mlr' is not installed."
    echo
    echo Please install it using "'apt install miller'" or equivalent.
    exit 1
  fi

  is_adb_root=$(adb shell id -u) || { \
    echo ERROR: Cannot run 'adb'.
    exit 1
  }

  if [[ "${is_adb_root}" -ne 0 ]]; then
     echo ERROR: Please run 'adb root'.
     exit 1
  fi
}

# Prepare the script for execution on the phone/tablet.
prepare_device_script() {
  device_tmpfile="$(adb shell mktemp)"
  device_errors="$(adb shell mktemp)"
  device_script="$(adb shell mktemp)"

  script_file=${local_tmpdir}/script
  cat > ${script_file} <<EOF
#!/bin/sh -e
#
# Runs the measurement command in the background, returning its PID.
${measurement_command} 2> ${device_errors} > ${device_tmpfile} &
echo \$!
EOF

  # If '-D' is specified, make a copy of the generated script for later
  # debugging.
  if [[ -n ${debug_dir} ]]; then
    mkdir -p ${debug_dir}
    cp ${script_file} ${debug_dir}/script
  fi

  # Copy the generated script to the device.
  adb push ${script_file} ${device_script} > /dev/null
  adb shell "/system/bin/chmod +x ${device_script}"
}

# Runs the prepared script on the device.
run_simpleperf_on_device() {
  # Run the generated script, remembering its PID on the device for later.
  device_pid=$(adb shell /bin/sh ${device_script})

  if [[ -n ${duration} ]]; then
    # If a duration was specified, wait for slightly longer than specified.
    sleep $((${duration} + 1))
  else
    # Otherwise wait for a manual signal terminating the run.
    read -r -p "Press ENTER to stop the measurement:"
  fi

  # Interrupt the measurement script.  This can fail if the script has already
  # exited due to an error.
  adb shell kill -INT ${device_pid} || true

  # Flush in-memory buffers.
  adb shell /system/bin/sync

  # Wait for 'simpleperf' to exit on the device.
  while true; do
    simpleperf_running="$(adb shell pgrep simpleperf)"
    if [[ -z ${simpleperf_is_running} ]]; then
      break;
    else
      sleep 1
    fi
  done

  # Wait a bit before pulling data in.  For some reason it takes a while for
  # simpleperf's output file to get populated.
  sleep 1

  adb shell fsync ${device_tmpfile}
}

# Copies simpleperf's output to the host, for further processing.
copy_simpleperf_output() {
  # Copy over simpleperf's output.
  simpleperf_output="${local_tmpdir}/simpleperf-output"
  adb pull ${device_tmpfile} ${simpleperf_output} > /dev/null

  # Make a copy of simpleperf's output, if '-D' was specified.
  [[ -n ${debug_dir} ]] && {
    cp ${simpleperf_output} ${debug_dir}/simpleperf-output
    adb pull ${device_errors} ${debug_dir}/simpleperf-errors > /dev/null;
  }

  # Clean up temporary files on the device.
  adb shell rm ${device_tmpfile} ${device_script} ${device_errors}
}

# Check for a successful run of simpleperf.
sanity_check_simpleperf_output() {
  # Verify that the output is complete.
  test_time=$(awk -F, '/Total test time/ { print $2 }' ${simpleperf_output})
  if [[ -z "${test_time}" ]]; then
    echo ERROR: truncated test data from simpleperf.
    exit 1
  fi
}

# Removes non-CSV lines from simpleperf's output and fills in missing rows.
preprocess_simpleperf_output() {
  # The output uses the following CSV schema, as of simpleperf version
  # '1.build.9558342':
  #
  #   thread_name,pid,tid,cpu,count,event_name,count/runtime,units,always-empty
  #
  # However, there are also extra lines in the CSV output, namely:
  #
  #  1) "Performance counter statistics,"
  #  2) "Total test time,DURATION,seconds,"

  # The "CSV-like" lines in the output of 'simpleperf' have the following
  # structure:
  #
  #    threadname,pid,tid,cpu,count,eventname,rate,units,<empty-field>
  #
  # Remove the non-CSV lines and ensure that every [thread,cpu,event] combination
  # is present in the processed output.
  #
  # Counts are merged across TIDs and PIDs for a given event.  If a
  # [thread,cpu,event] combination is not present in the input, it is given
  # a count of zero in the processed output.
  #
  # TODO(b/1398262): Use 'mlr' for preprocessing instead of 'awk'.
  processed_output="${local_tmpdir}/processed-output"
  awk -F, '
    $1 ~ /Total test time/ { next }
    $1 ~ /Performance counter/ { next }
    {
      t = $1; c = $4; e = $6;
      thread_name[t] = 1; cpu[c] = 1; event_name[e] = 1;
      event_count[t,c,e] += $5;  # Merge counts across TIDs and PIDs.
    }
    END {
      for (e in event_name) {
        for (t in thread_name) {
          for (c in cpu) {
            printf("%s,%d,%d,%s\n", t, c, event_count[t,c,e], e);
          }
        }
      }
    }
    ' ${simpleperf_output} > ${processed_output}

  [[ -n ${debug_dir} ]] && cp ${processed_output} ${debug_dir}/processed-output
}

# Label, sort, and reshape CSV data.
process_simpleperf_output() {
  # Group event counts by thread and CPU.
  mlr_first_pass=${local_tmpdir}/mlr-first-pass
  mlr --csv --from ${processed_output} --implicit-csv-header \
    label "thread_name,cpu,count,event_name" then \
      sort -f thread_name,cpu then \
      reshape -s event_name,count then \
      reorder -e -f "${output_order}" then \
      gap -g event_name > ${mlr_first_pass}

  # Make a copy of 'mlr's output if '-D' was specified.
  [[ -n ${debug_dir} ]] && cp ${mlr_first_pass} ${debug_dir}/
}

# Show the processed output.
display_processed_output() {
  cat ${mlr_first_pass}
}

maybe_print_test_time() {
  [[ ${print_test_time} = YES ]] && echo "Test time: ${test_time}"
}


# MAIN

parse_options_and_set_defaults "$@"
prepare_simpleperf_command_line

# Exit at this point if a dry run was specified.
[[ ${dry_run} = YES ]] && exit 0

run_sanity_checks
prepare_device_script
run_simpleperf_on_device
copy_simpleperf_output
sanity_check_simpleperf_output
preprocess_simpleperf_output
process_simpleperf_output
display_processed_output
maybe_print_test_time