| #!/bin/bash |
| # Copyright 2019 The Fuchsia Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| # Common methods for metrics collection. |
| # |
| # Note: For non-shell programs, use metrics_custom_report.sh instead. |
| # |
| # Report events to the metrics collector from `fx`, the `fx metrics`, and the |
| # rare subcommand that has special metrics needs. |
| # |
| # How to use it: This file is sourced in //scripts/fx for tracking command |
| # execution and in //tools/devshell/metrics for managing the metrics collection |
| # settings. Developers of shell-based subcommands can source this file in their |
| # subcommand if they need custom event tracking, but only the method |
| # track-subcommand-custom-event can be used in this context. |
| # |
| # This script assumes that vars.sh has already been sourced, since it |
| # depends on FUCHSIA_DIR being defined correctly. |
| |
| # Increase the metrics version by 1 when analytics is updated |
| _METRICS_VERSION="7" |
| _METRICS_ALLOWS_CUSTOM_REPORTING=( "test" "g-review" ) |
| # If args match the below, then track capture group 1 |
| _METRICS_TRACK_REGEX=( |
| "^run (fuchsia-pkg:\/\/[[:graph:]]*)" |
| "^shell (run fuchsia-pkg:\/\/[[:graph:]]*)" |
| ) |
| # We collect metrics when these operations happen without capturing all of |
| # their args. |
| _METRICS_TRACK_COMMAND_OPS=( |
| "publish cache" |
| "shell activity" |
| "shell basename" |
| "shell bssl" |
| "shell bt-avdtp-tool" |
| "shell bt-avrcp-controller" |
| "shell bt-cli" |
| "shell bt-hci-emulator" |
| "shell bt-hci-tool" |
| "shell bt-le-central" |
| "shell bt-le-peripheral" |
| "shell bt-pairing-tool" |
| "shell bt-snoop-cli" |
| "shell bugreport" |
| "shell cal" |
| "shell cat" |
| "shell catapult_converter" |
| "shell cksum" |
| "shell cmp" |
| "shell cols" |
| "shell comm" |
| "shell cowsay" |
| "shell cp" |
| "shell crashpad_database_util" |
| "shell cs" |
| "shell curl" |
| "shell cut" |
| "shell date" |
| "shell dirname" |
| "shell du" |
| "shell echo" |
| "shell ed" |
| "shell env" |
| "shell expand" |
| "shell expr" |
| "shell false" |
| "shell far" |
| "shell fdio_spawn_helper" |
| "shell fdr" |
| "shell find" |
| "shell fold" |
| "shell fuchsia_benchmarks" |
| "shell gltf_export" |
| "shell grep" |
| "shell head" |
| "shell hostname" |
| "shell input" |
| "shell iperf3" |
| "shell iquery" |
| "shell join" |
| "shell limbo_client" |
| "shell link" |
| "shell locate" |
| "shell log_listener" |
| "shell ls" |
| "shell md5sum" |
| "shell mediasession_cli_tool" |
| "shell mkdir" |
| "shell mktemp" |
| "shell mv" |
| "shell net" |
| "shell netdump" |
| "shell nl" |
| "shell od" |
| "shell onet" |
| "shell paste" |
| "shell pathchk" |
| "shell pkgctl" |
| "shell present_view" |
| "shell print_input" |
| "shell printenv" |
| "shell printf" |
| "shell process_input_latency_trace" |
| "shell pwd" |
| "shell readlink" |
| "shell rev" |
| "shell rm" |
| "shell rmdir" |
| "shell run" |
| "shell run_simplest_app_benchmark.sh" |
| "shell run-test-suite" |
| "shell scp" |
| "shell screencap" |
| "shell sed" |
| "shell seq" |
| "shell set_renderer_params" |
| "shell sh" |
| "shell sha1sum" |
| "shell sha224sum" |
| "shell sha256sum" |
| "shell sha384sum" |
| "shell sha512-224sum" |
| "shell sha512-256sum" |
| "shell sha512sum" |
| "shell signal_generator" |
| "shell sleep" |
| "shell snapshot" |
| "shell sort" |
| "shell split" |
| "shell sponge" |
| "shell ssh" |
| "shell ssh-keygen" |
| "shell stash_ctl" |
| "shell strings" |
| "shell sync" |
| "shell system-update-checker" |
| "shell tail" |
| "shell tar" |
| "shell tee" |
| "shell test" |
| "shell tftp" |
| "shell time" |
| "shell touch" |
| "shell tr" |
| "shell trace" |
| "shell true" |
| "shell tsort" |
| "shell tty" |
| "shell uname" |
| "shell unexpand" |
| "shell uniq" |
| "shell unlink" |
| "shell update" |
| "shell uudecode" |
| "shell uuencode" |
| "shell vim" |
| "shell virtual_audio" |
| "shell vol" |
| "shell wav_recorder" |
| "shell wc" |
| "shell which" |
| "shell whoami" |
| "shell wlan" |
| "shell xargs" |
| "shell xinstall" |
| "shell yes" |
| ) |
| |
| _METRICS_TRACK_UNKNOWN_OPS=( "shell" ) |
| |
| # These variables need to be global, but readonly (or declare -r) declares new |
| # variables as local when they are source'd inside a function. |
| # "declare -g -r" is the right way to handle it, but it is not supported in |
| # old versions of Bash, particularly in the one in MacOS. The alternative is to |
| # make them global first via the assignments above and marking they readonly |
| # later. |
| readonly _METRICS_VERSION _METRICS_ALLOWS_CUSTOM_REPORTING _METRICS_TRACK_REGEX _METRICS_TRACK_COMMAND_OPS _METRICS_TRACK_UNKNOWN_OPS |
| |
| # To properly enable unit testing, METRICS_CONFIG is not read-only |
| METRICS_CONFIG="${FUCHSIA_DIR}/.fx/config/metrics" |
| # Old location of this file. Will move to new location automatically. |
| readonly METRICS_CONFIG_OLD="${FUCHSIA_DIR}/.fx-metrics-config" |
| |
| _METRICS_DEBUG=0 |
| _METRICS_DEBUG_LOG_FILE="" |
| _METRICS_USE_VALIDATION_SERVER=0 |
| |
| INIT_WARNING=$'Please opt in or out of fx metrics collection.\n' |
| INIT_WARNING+=$'You will receive this warning until an option is selected.\n' |
| INIT_WARNING+=$'To check what data we collect, run `fx metrics`\n' |
| INIT_WARNING+=$'To opt in or out, run `fx metrics <enable|disable>\n' |
| |
| INIT_WARNING_INTERNAL=$( |
| cat <<'EOF' |
| Enable enhanced analytics to help us improve Fuchsia tools! |
| |
| You are identified as a Googler since your hostname ends with corp.google.com |
| or c.googlers.com. To better understand how Fuchsia tools are used, and help |
| improve these tools and your workflow, Google already has an option, as you |
| know, to collect basic, very redacted, analytics listed in |
| https://fuchsia.dev/fuchsia-src/contribute/governance/policy/analytics_collected_fuchsia_tools. |
| As a Googler, you can help us even more by opting in to enhanced analytics: |
| |
| ffx config analytics enable-enhanced |
| |
| You may need to first run `fx build` to get the latest ffx if the above |
| does not work. |
| |
| Enabling enhanced analytics may collect the following additional information, |
| in accordance with Google's employee privacy policy |
| (go/employee-privacy-policy): |
| |
| • Full command line arguments |
| • Product name (e.g. core, minimal, etc.) |
| • Board type (e.g. vim3, x64, etc.) |
| • Environment variables that affect behavior of the tools |
| • User/Host/Target environment (e.g. tool/system versions, connection type |
| such as Network/USB/Remote, etc.) |
| • Full stacktrace |
| • Logs |
| • Content of configuration files (e.g. args.gn) |
| |
| Before any data is sent, we will replace the value of $USER with the literal |
| string "$USER" and also redact $HOSTNAME in a similar way. |
| |
| To collect basic analytics only, enter |
| ffx config analytics enable |
| If you want to disable all analytics, enter |
| ffx config analytics disable |
| To display the current setting and what is collected, type |
| ffx config analytics show |
| |
| You will continue to receive this notice until you select an option. |
| |
| See Google's employee privacy policy: |
| go/employee-privacy-policy |
| EOF |
| ) |
| |
| # Each Analytics batch call can send at most this many hits. |
| declare -r BATCH_SIZE=25 |
| # Keep track of how many hits have accumulated. |
| hit_count=0 |
| # Holds events for the current batch |
| events=() |
| |
| function __is_in { |
| local v="$1" |
| shift |
| while [[ $# -gt 0 ]]; do |
| if [[ "$1" == "$v" ]]; then |
| return 0 |
| fi |
| shift |
| done |
| return 1 |
| } |
| |
| function __is_in_regex { |
| local v="$1" |
| shift |
| while [[ $# -gt 0 ]]; do |
| if [[ $v =~ $1 ]]; then |
| return 0 |
| fi |
| shift |
| done |
| return 1 |
| } |
| |
| function _get-metrics-config-dir { |
| local base_dir |
| case "${HOST_OS}" in |
| linux) |
| base_dir="${XDG_DATA_HOME}" |
| if [[ -z "${base_dir}" ]]; then |
| base_dir="${HOME}/.local/share" |
| fi |
| ;; |
| mac) |
| base_dir="${HOME}/Library/Application Support" |
| ;; |
| esac |
| echo "${base_dir}/Fuchsia/metrics" |
| } |
| |
| function _read-other-tools-analytics-uuid { |
| local base_dir="$(_get-metrics-config-dir)" |
| echo "$(cat "${base_dir}/uuid" 2>/dev/null)" |
| } |
| |
| function metrics-is-internal-user { |
| # Due to old bash versions on mac, we don't support internal analytics on mac. |
| [[ "$HOSTNAME" =~ (".c.googlers.com"$)|(".corp.google.com"$) && "${HOST_OS}" != "mac" ]] |
| } |
| |
| function metrics-sanitize-string { |
| local sanitized="${1//$FUCHSIA_DIR/\$FUCHSIA_DIR}" |
| sanitized="${sanitized//$USER/\$USER}" |
| sanitized="${sanitized//$HOSTNAME/\$HOSTNAME}" |
| echo "$sanitized" |
| } |
| |
| function metrics-read-config-external { |
| METRICS_UUID="" |
| METRICS_ENABLED=0 |
| _METRICS_DEBUG_LOG_FILE="" |
| if [[ ! -f "${METRICS_CONFIG}" ]]; then |
| return 1 |
| fi |
| source "${METRICS_CONFIG}" |
| if [[ $METRICS_ENABLED == 1 && -z "$METRICS_UUID" ]]; then |
| METRICS_ENABLED=0 |
| return 1 |
| fi |
| OTHER_TOOLS_ANALYTICS_UUID="$(_read-other-tools-analytics-uuid)" |
| return 0 |
| } |
| |
| function metrics-read-config-internal { |
| local base_dir="$(_get-metrics-config-dir)" |
| local metrics_config_internal="${base_dir}/analytics-status-internal" |
| METRICS_LEVEL=0 |
| |
| # Non-migrated user |
| if [[ ! -f "${metrics_config_internal}" ]]; then |
| if [[ -f "${METRICS_CONFIG}" ]]; then |
| metrics-read-config-external |
| metrics-set-level-from-enabled |
| fi |
| return |
| fi |
| |
| # Migrated user |
| METRICS_UUID="" |
| _METRICS_DEBUG_LOG_FILE="" |
| if [[ -f "${METRICS_CONFIG}" ]]; then |
| source "${METRICS_CONFIG}" |
| fi |
| |
| METRICS_LEVEL="$(cat "${metrics_config_internal}" 2>/dev/null)" |
| if [[ "$METRICS_LEVEL" -ne 1 && "$METRICS_LEVEL" -ne 2 ]]; then |
| METRICS_LEVEL=0 |
| fi |
| |
| if [[ "${METRICS_LEVEL}" -gt 0 ]]; then |
| OTHER_TOOLS_ANALYTICS_UUID="$(_read-other-tools-analytics-uuid)" |
| if [[ -z "${METRICS_UUID}" ]]; then |
| uuidgen_cmd=uuidgen |
| if ! command -v "$uuidgen_cmd" >/dev/null 2>&1 ; then |
| fx-error "Command '$uuidgen_cmd' cannot be found, please add it to your PATH."\ |
| "(On Ubuntu/Debian systems, try \`sudo apt install uuid-runtime\`.)" |
| exit 1 |
| fi |
| METRICS_UUID="$($uuidgen_cmd)" |
| metrics-write-config-internal "${METRICS_UUID}" "${_METRICS_DEBUG_LOG_FILE}" |
| return |
| fi |
| |
| if [[ -n "${ENABLED}" ]]; then |
| metrics-write-config-internal "${METRICS_UUID}" "${_METRICS_DEBUG_LOG_FILE}" |
| fi |
| fi |
| |
| } |
| |
| |
| |
| function metrics-read-config { |
| if metrics-is-internal-user; then |
| metrics-read-config-internal |
| else |
| metrics-read-config-external |
| metrics-set-level-from-enabled |
| fi |
| } |
| |
| function metrics-write-config { |
| local enabled=$1 |
| if [[ "$enabled" -eq "1" ]]; then |
| local uuid="$2" |
| if [[ $# -gt 2 ]]; then |
| local debug_logfile="$3" |
| fi |
| fi |
| local -r tempfile="$(mktemp)" |
| |
| # Exit trap to clean up temp file |
| trap "[[ -f \"${tempfile}\" ]] && rm -f \"${tempfile}\"" EXIT |
| |
| { |
| echo "# Autogenerated config file for fx metrics. Run 'fx help metrics' for more information." |
| echo "METRICS_ENABLED=${enabled}" |
| echo "METRICS_UUID=\"${uuid}\"" |
| if [[ -n "${debug_logfile}" ]]; then |
| echo "_METRICS_DEBUG_LOG_FILE=${debug_logfile}" |
| fi |
| } >> "${tempfile}" |
| # Only rewrite the config file if content has changed |
| if ! cmp --silent "${tempfile}" "${METRICS_CONFIG}" ; then |
| mv -f "${tempfile}" "${METRICS_CONFIG}" |
| fi |
| } |
| |
| function metrics-write-config-internal { |
| local uuid="$1" |
| if [[ $# -gt 1 ]]; then |
| local debug_logfile="$2" |
| fi |
| |
| local -r tempfile="$(mktemp)" |
| |
| # Exit trap to clean up temp file |
| trap "[[ -f \"${tempfile}\" ]] && rm -f \"${tempfile}\"" EXIT |
| |
| { |
| echo "# Autogenerated config file for fx metrics." |
| echo "METRICS_UUID=\"${uuid}\"" |
| if [[ -n "${debug_logfile}" ]]; then |
| echo "_METRICS_DEBUG_LOG_FILE=${debug_logfile}" |
| fi |
| } >> "${tempfile}" |
| # Only rewrite the config file if content has changed |
| if ! cmp --silent "${tempfile}" "${METRICS_CONFIG}" ; then |
| mv -f "${tempfile}" "${METRICS_CONFIG}" |
| fi |
| } |
| |
| function metrics-set-level-from-enabled { |
| if [[ "${METRICS_ENABLED}" -eq 1 ]]; then |
| METRICS_LEVEL=1 |
| else |
| METRICS_LEVEL=0 |
| fi |
| } |
| |
| function metrics-read-and-validate-external { |
| local hide_init_warning=$1 |
| if ! metrics-read-config-external; then |
| if [[ "${hide_init_warning}" -ne 1 ]]; then |
| fx-warn "${INIT_WARNING}" |
| fi |
| fi |
| metrics-set-level-from-enabled |
| } |
| |
| function metrics-migrate-internal-users { |
| local base_dir="$(_get-metrics-config-dir)" |
| local metrics_config_internal="${base_dir}/analytics-status-internal" |
| local hide_init_warning="$1" |
| |
| if [[ ! -f "${metrics_config_internal}" ]]; then |
| local metrics_config_external="${base_dir}/analytics-status" |
| if [[ ! -f "${metrics_config_external}" ]]; then |
| # New user |
| if [[ "${hide_init_warning}" -ne 1 ]]; then |
| fx-warn "$INIT_WARNING_INTERNAL" |
| fi |
| return |
| fi |
| |
| # Existing user |
| METRICS_LEVEL_EXISTING=$(cat "${metrics_config_external}" 2>/dev/null) |
| if [[ "${METRICS_LEVEL_EXISTING}" -eq 1 ]]; then |
| # Existing user opted-in |
| if [[ "${hide_init_warning}" -ne 1 ]]; then |
| fx-warn "$INIT_WARNING_INTERNAL" |
| fi |
| else |
| # Existing user opted-out |
| mkdir -p "${base_dir}" |
| echo 0 >"${metrics_config_internal}" |
| fi |
| return |
| fi |
| } |
| |
| function metrics-read-and-validate { |
| if [[ -f "${METRICS_CONFIG_OLD}" ]]; then |
| fx-info "Moving ${METRICS_CONFIG_OLD} to new location ${METRICS_CONFIG}. No further action is needed." |
| mv "${METRICS_CONFIG_OLD}" "${METRICS_CONFIG}" |
| fi |
| if metrics-is-internal-user; then |
| metrics-migrate-internal-users "$1" |
| metrics-read-config-internal |
| else |
| metrics-read-and-validate-external "$1" |
| fi |
| } |
| |
| function metrics-set-debug-logfile { |
| _METRICS_DEBUG_LOG_FILE="$1" |
| return 0 |
| } |
| |
| function metrics-get-debug-logfile { |
| if [[ -n "$_METRICS_DEBUG_LOG_FILE" ]]; then |
| echo "$_METRICS_DEBUG_LOG_FILE" |
| fi |
| } |
| |
| function metrics-maybe-log { |
| local filename="$(metrics-get-debug-logfile)" |
| if [[ -n "$filename" ]]; then |
| if [[ ! -f "$filename" && -w $(dirname "$filename") ]]; then |
| touch "$filename" |
| fi |
| if [[ -w "$filename" ]]; then |
| TIMESTAMP="$(date +%Y%m%d_%H%M%S)" |
| echo -n "${TIMESTAMP}:" >> "$filename" |
| for i in "$@"; do |
| if [[ "$i" =~ ^"--" ]]; then |
| continue # Skip switches. |
| fi |
| # Space before $i is intentional. |
| echo -n " $i" >> "$filename" |
| done |
| # Add a newline at the end. |
| echo >> "$filename" |
| fi |
| fi |
| } |
| |
| # Arguments: |
| # - the name of the fx subcommand |
| # - event action |
| # - (optional) event label |
| function track-subcommand-custom-event { |
| exec 1>/dev/null |
| exec 2>/dev/null |
| local subcommand="$1" |
| local event_action="$2" |
| shift 2 |
| local event_label="$*" |
| |
| # Only allow custom arguments to subcommands defined in # $_METRICS_ALLOWS_CUSTOM_REPORTING |
| if ! __is_in "$subcommand" "${_METRICS_ALLOWS_CUSTOM_REPORTING[@]}"; then |
| return 1 |
| fi |
| |
| # Limit to the first 100 characters |
| # The Analytics API supports up to 500 bytes, but it is likely that |
| # anything larger than 100 characters is an invalid execution and/or not |
| # what we want to track. |
| event_label=${event_label:0:100} |
| |
| metrics-read-config |
| if [[ "${METRICS_LEVEL}" -eq 0 ]]; then |
| return 0 |
| fi |
| |
| event_params=$(fx-command-run jq -c -n \ |
| --arg subcommand "${subcommand}" \ |
| --arg action "${event_action}" \ |
| --arg label "${event_label}" \ |
| '$ARGS.named') |
| _add-to-analytics-batch "custom" "${event_params}" |
| # Send any remaining hits. |
| _send-analytics-batch |
| return 0 |
| } |
| |
| # Arguments: |
| # - the name of the fx subcommand |
| # - args of the subcommand |
| function track-command-execution { |
| exec 1>/dev/null |
| exec 2>/dev/null |
| local subcommand="$1" |
| shift |
| local args="$*" |
| local subcommand_op; |
| if [[ $# -gt 0 ]]; then |
| local subcommand_arr=( $args ) |
| subcommand_op=${subcommand_arr[0]} |
| fi |
| |
| metrics-read-config |
| if [[ "${METRICS_LEVEL}" -eq 0 ]]; then |
| return 0 |
| fi |
| |
| if [[ "$subcommand" == "set" ]]; then |
| # Add separate fx_set hits for packages |
| _process-fx-set-command "$@" |
| fi |
| |
| if [[ "${METRICS_LEVEL}" -eq 2 ]]; then |
| args="$(metrics-sanitize-string "${args}")" |
| elif __is_in_regex "${subcommand} ${args}" "${_METRICS_TRACK_REGEX[@]}"; then |
| args="${BASH_REMATCH[1]}" |
| elif [ -n "${subcommand_op}" ]; then |
| if __is_in "${subcommand} ${subcommand_op}" \ |
| "${_METRICS_TRACK_COMMAND_OPS[@]}"; then |
| # Track specific subcommand arguments (instead of all of them) |
| args="${subcommand_op}" |
| elif __is_in "${subcommand}" "${_METRICS_TRACK_UNKNOWN_OPS[@]}"; then |
| # We care about the fact there was a subcommand_op, but we haven't |
| # explicitly opted it into metrics collection. |
| args="\$unknown_subcommand" |
| else |
| args="" |
| fi |
| else |
| # Track no arguments |
| args="" |
| fi |
| |
| # Limit to the first 500 characters of arguments. |
| # The GA4 360 supports up to 500 characters for parameter values |
| local args_truncated=0 |
| local args1="${args:0:500}" |
| local args2="${args:500:500}" |
| if [[ "${#args}" -gt 1000 ]]; then |
| args_truncated=1 |
| fi |
| |
| event_params=$(fx-command-run jq -c -n \ |
| --arg subcommand "${subcommand}" \ |
| --arg args "${args1}" \ |
| --arg args2 "${args2}" \ |
| --arg args_truncated "${args_truncated}" \ |
| --arg invoker "${FUCHSIA_FX_INVOKER}" \ |
| '$ARGS.named') |
| |
| _add-to-analytics-batch "invoke" "${event_params}" |
| # Send any remaining hits. |
| _send-analytics-batch |
| return 0 |
| } |
| |
| # Arguments: |
| # - args of `fx set` |
| function _process-fx-set-command { |
| while [[ $# -ne 0 ]]; do |
| case $1 in |
| --with) |
| shift # remove "--with" |
| _add-fx-set-hit "fx-with" "$1" |
| ;; |
| --with-base) |
| shift # remove "--with-base" |
| _add-fx-set-hit "fx-with-base" "$1" |
| ;; |
| *) |
| ;; |
| esac |
| shift |
| done |
| } |
| |
| # Arguments: |
| # - category name, either "fx-with" or "fx-with-base" |
| # - package(s) following "--with" or "--with-base" switch |
| function _add-fx-set-hit { |
| category="$1" |
| packages="$2" |
| # Packages argument can be a comma-separated list. |
| IFS=',' read -ra packages_parts <<< "$packages" |
| for p in "${packages_parts[@]}"; do |
| event_params=$(fx-command-run jq -c -n \ |
| --arg with_type "${category}" \ |
| --arg package_name "${p}" \ |
| '$ARGS.named') |
| |
| _add-to-analytics-batch "set" "${event_params}" |
| done |
| } |
| |
| # Arguments: |
| # - time taken to complete (milliseconds) |
| # - exit status |
| # - the name of the fx subcommand |
| # - args of the subcommand |
| function track-command-finished { |
| exec 1>/dev/null |
| exec 2>/dev/null |
| local start_time="$1" |
| local end_time="$2" |
| local exit_status="$3" |
| local subcommand="$4" |
| local is_remote="$5" |
| shift 5 |
| local args="$*" |
| |
| metrics-read-config |
| if [[ "${METRICS_LEVEL}" -eq 0 ]]; then |
| return 0 |
| fi |
| |
| # Only track arguments to the subcommands in $_METRICS_TRACK_ALL_ARGS |
| if [[ "${METRICS_LEVEL}" -eq 2 ]]; then |
| args="$(metrics-sanitize-string "${args}")" |
| elif ! __is_in "$subcommand" "${_METRICS_TRACK_ALL_ARGS[@]}"; then |
| args="" |
| fi |
| |
| local args_truncated=0 |
| local args1="${args:0:500}" |
| local args2="${args:500:500}" |
| if [[ "${#args}" -gt 1000 ]]; then |
| args_truncated=1 |
| fi |
| |
| local timing=$(( (end_time - start_time)/1000 )) |
| |
| event_params=$(fx-command-run jq -c -n \ |
| --arg subcommand "${subcommand}" \ |
| --arg args "${args1}" \ |
| --arg args2 "${args2}" \ |
| --arg args_truncated "${args_truncated}" \ |
| --arg exit_status "${exit_status}" \ |
| --arg is_remote "${is_remote}" \ |
| --arg invoker "${FUCHSIA_FX_INVOKER}" \ |
| --argjson timing "${timing}" \ |
| --argjson start_time_micros "${start_time}" \ |
| --argjson end_time_micros "${end_time}" \ |
| '$ARGS.named') |
| |
| _add-to-analytics-batch "finish" "${event_params}" |
| |
| |
| # Send any remaining hits. |
| _send-analytics-batch |
| return 0 |
| } |
| |
| # Arguments: |
| # - feature name |
| # - 0 for enabled, 1 for disabled |
| function track-feature-status { |
| if [[ "${HOST_OS}" == "mac" ]]; then |
| return |
| fi |
| |
| exec 1>/dev/null |
| exec 2>/dev/null |
| |
| metrics-read-config |
| if [[ "${METRICS_LEVEL}" -eq 0 ]]; then |
| return 0 |
| fi |
| |
| local feature=$1 |
| local is_disabled=$2 |
| local status |
| |
| if [[ "${is_disabled}" -eq 0 ]]; then |
| status="enabled" |
| else |
| status="disabled" |
| fi |
| |
| event_params=$(fx-command-run jq -c -n \ |
| --arg feature "${feature}" \ |
| --arg status "${status}" \ |
| '$ARGS.named') |
| |
| _add-to-analytics-batch "feature" "${event_params}" |
| } |
| |
| function track-build-event { |
| exec 1>/dev/null |
| exec 2>/dev/null |
| local start_time="$1" |
| local end_time="$2" |
| local exit_status="$3" |
| local ninja_switches="$4" |
| local fuchsia_targets="$5" |
| local build_dir="$6" |
| local is_no_op="$7" |
| local is_clean_build="$8" |
| local quiet="$9" |
| |
| local args_json="" |
| local env_flags="" |
| local main_pb_label="" |
| |
| metrics-read-config |
| if [[ "${METRICS_LEVEL}" -eq 0 ]]; then |
| return 0 |
| fi |
| |
| if [[ "${METRICS_LEVEL}" -eq 2 ]]; then |
| ninja_switches="$(metrics-sanitize-string "${ninja_switches}")" |
| fuchsia_targets="$(metrics-sanitize-string "${fuchsia_targets}")" |
| args_json=$(fx-command-run jq -c '{ b: .build_info_board, p: .build_info_product, r: .rbe_mode, c: .compilation_mode, o: .optimize, sv: .select_variant, tc: .target_cpu, ri: .rust_incremental }' "${build_dir}"/args.json) |
| main_pb_label=$(fx-command-run jq -r '.main_pb_label // empty' "${build_dir}"/args.json) |
| else |
| ninja_switches="" |
| fuchsia_targets="" |
| fi |
| |
| ninja_switches="${ninja_switches:0:500}" |
| fuchsia_targets="${fuchsia_targets:0:500}" |
| |
| local args_json1="${args_json:0:500}" |
| local args_json2="${args_json:500:500}" |
| |
| if [[ "${FUCHSIA_FX_ITERATIVE}" -eq 1 ]]; then |
| env_flags="${env_flags}+iterative" |
| else |
| env_flags="${env_flags}-iterative" |
| fi |
| |
| if [[ "${FUCHSIA_FX_TEST_RUN}" -eq 1 ]]; then |
| env_flags="${env_flags}+test" |
| else |
| env_flags="${env_flags}-test" |
| fi |
| |
| if [[ "${FUCHSIA_FX_MULTI_RUN}" -eq 1 ]]; then |
| env_flags="${env_flags}+multi" |
| else |
| env_flags="${env_flags}-multi" |
| fi |
| |
| if [[ "${quiet}" == true ]]; then |
| env_flags="${env_flags}+quiet" |
| else |
| env_flags="${env_flags}-quiet" |
| fi |
| |
| local -i args_json_size=$(wc -c < "${build_dir}"/args.json) |
| local -i target_count=0 |
| if [[ "${is_no_op}" -eq 0 ]]; then |
| target_count=$("${PREBUILT_PYTHON3}" "$FUCHSIA_DIR/tools/devshell/contrib/lib/count-ninja-actions.py") |
| fi |
| |
| event_params=$(fx-command-run jq -c -n \ |
| --arg args_json1 "${args_json1}" \ |
| --arg args_json2 "${args_json2}" \ |
| --arg main_pb_label "${main_pb_label}" \ |
| --arg ninja_switches "${ninja_switches}" \ |
| --arg fuchsia_targets "${fuchsia_targets}" \ |
| --arg exit_status "${exit_status}" \ |
| --argjson start_time_micros "${start_time}" \ |
| --argjson end_time_micros "${end_time}" \ |
| --argjson target_count "${target_count}" \ |
| --argjson is_clean_build "${is_clean_build}" \ |
| --arg env_flags "${env_flags}" \ |
| --argjson args_json_size "${args_json_size}" \ |
| --arg invoker "${FUCHSIA_FX_INVOKER}" \ |
| '$ARGS.named') |
| |
| _add-to-analytics-batch "build" "${event_params}" |
| |
| |
| # Send any remaining hits. |
| _send-analytics-batch |
| return 0 |
| } |
| |
| # Add an analytics hit with the given args to the batch of hits. This will trigger |
| # sending a batch when the batch size limit is hit. |
| # |
| # Arguments: |
| # - analytics arguments, e.g. "t=event" "ec=fx" etc. |
| function __add-to-analytics-batch { |
| if [[ $# -eq 0 ]]; then |
| return 0 |
| fi |
| |
| event_name=$1 |
| shift |
| |
| timestamp_micros=$("${PREBUILT_PYTHON3}" \ |
| -c 'import time; print(int(time.time() * 1000000))') |
| if [[ $# -eq 0 ]]; then |
| event=$(fx-command-run jq -c -n \ |
| --arg name "${event_name}" \ |
| --argjson timestamp_micros "${timestamp_micros}" \ |
| '$ARGS.named') |
| else |
| event=$(fx-command-run jq -c -n \ |
| --arg name "${event_name}" \ |
| --argjson params "$*" \ |
| --argjson timestamp_micros "${timestamp_micros}" \ |
| '$ARGS.named') |
| fi |
| events+=("$event") |
| |
| (( hit_count += 1 )) |
| if ((hit_count == BATCH_SIZE)); then |
| __send-analytics-batch |
| fi |
| } |
| |
| # The following list of allowed ninja persistent modes comes from |
| # https://fuchsia.googlesource.com/third_party/github.com/ninja-build/ninja/+/2005473679afc095025bd6db7d461590f8701e65/src/persistent_mode.cc#338 |
| _ALLOWED_NINJA_PERSISTENT_MODE=( "" "0" "1" "on" "off" "client" "server" ) |
| function _get_ninja_persistent_mode { |
| if __is_in "${NINJA_PERSISTENT_MODE}" \ |
| "${_ALLOWED_NINJA_PERSISTENT_MODE[@]}"; then |
| echo "${NINJA_PERSISTENT_MODE}" |
| else |
| echo unsupported |
| fi |
| } |
| |
| # Sends the current batch of hits to the Analytics server. As a side effect, clears |
| # the hit count and batch data. |
| function __send-analytics-batch { |
| if [[ $hit_count -eq 0 ]]; then |
| return 0 |
| fi |
| |
| local internal |
| if metrics-is-internal-user; then |
| internal=1 |
| else |
| internal=0 |
| fi |
| |
| # Construct the measuremnt data |
| local user_properties="{\"os\":{\"value\":\"$(uname -s)\"},\ |
| \"arch\":{\"value\":\"$(uname -m)\"},\ |
| \"shell\":{\"value\":\"$(_app_name)\"},\ |
| \"shell_version\":{\"value\":\"$(_app_version)\"},\ |
| \"kernel_release\":{\"value\":\"$(uname -rs)\"},\ |
| \"ninja_persistent\":{\"value\":\"$(_get_ninja_persistent_mode)\"},\ |
| \"other_uuid\":{\"value\":\"${OTHER_TOOLS_ANALYTICS_UUID}\"},\ |
| \"internal\":{\"value\":${internal}},\ |
| \"metrics_level\":{\"value\":${METRICS_LEVEL}},\ |
| \"metrics_version\":{\"value\":${_METRICS_VERSION}},\ |
| \"nproc\":{\"value\":$(_get_nproc)}\ |
| }" |
| local events_json=$(fx-command-run jq -n -c '$ARGS.positional' \ |
| --jsonargs "${events[@]}") |
| local measurement=$(fx-command-run jq -n -c \ |
| --arg client_id "${METRICS_UUID}" \ |
| --argjson events "${events_json}" \ |
| --argjson user_properties "${user_properties}" \ |
| '$ARGS.named') |
| |
| local url_path="mp/collect" |
| local url_parameters=$(printf "\x61\x70\x69\x5f\x73\x65\x63\x72\x65\x74=xjiXbh8eSGiExMthvAXd6w&measurement_id=G-L2YHSDD8ZF") |
| local result="" |
| if [[ $_METRICS_DEBUG == 1 && $_METRICS_USE_VALIDATION_SERVER == 1 ]]; then |
| url_path="debug/mp/collect" |
| fi |
| if [[ $_METRICS_DEBUG == 1 && $_METRICS_USE_VALIDATION_SERVER == 0 ]]; then |
| # if testing and not using the validation server, always return 202 |
| result="202" |
| elif [[ $_METRICS_DEBUG == 0 || $_METRICS_USE_VALIDATION_SERVER == 1 ]]; then |
| result=$(curl -s -o /dev/null -w "%{http_code}" -L -X POST \ |
| -H "Content-Type: application/json" \ |
| --data-raw "${measurement}" \ |
| "https://www.google-analytics.com/${url_path}?${url_parameters}") |
| fi |
| metrics-maybe-log "${measurement}" "RESULT=${result}" |
| |
| # Clear batch. |
| hit_count=0 |
| events=() |
| } |
| |
| # Metrics/analytics are processed asynchronously by the following function. |
| function _metrics-service { |
| # redirect stdout and stderr away from parent process so that parent |
| # stdout and stderr can be safely closed. Otherwise, dart will |
| # wait for stdout and stderr being closed, defeating the purpose of |
| # async analytics processing. |
| exec 1>/dev/null |
| exec 2>/dev/null |
| metrics-read-config |
| while read -r -d $'\0' args; do |
| local IFS=$'\n' |
| read -r -d $'\0' -a request <<< "$args" |
| if [[ ${#request[@]} -eq 0 ]]; then |
| __send-analytics-batch |
| else |
| __add-to-analytics-batch "${request[@]}" |
| fi |
| done <&0 |
| __send-analytics-batch |
| } |
| |
| # Init metrics service by redirecting file descriptor 10 to the process |
| # substitution of metrics service. |
| function metrics-init { |
| if [[ -z "${HOME}" ]]; then |
| METRICS_LEVEL=0 |
| exec 10> /dev/null |
| return |
| fi |
| |
| local subcommand="$1" |
| local args="$2" |
| |
| # We will not show user notices for metrics in the following cases: |
| # - the subcommand is metrics |
| # - the subcommand is ffx and there's no fuchsia.analytics.ffx_invoker=fx argument |
| # This is because when ffx has the --config fuchsia.analytics.ffx_invoker=fx argument |
| # it will not display the user notices, so we will need to display it. When ffx |
| # does not have the --config fuchsia.analytics.ffx_invoker=fx argument, we need |
| # to hide the user notices since ffx will display one. |
| # - the subcommand with args is `ffx config analytics ...`. This is to make sure |
| # ffx config analytics <anything> will not show the user notices. |
| local hide_init_warning=0 |
| if [[ "$subcommand" == "metrics" \ |
| || ( "$subcommand" == "ffx" && ! "$args" =~ "fuchsia.analytics.ffx_invoker=fx" ) \ |
| || ( "$subcommand" == "ffx" && "$args" =~ "config analytics" ) \ |
| ]]; then |
| hide_init_warning=1 |
| fi |
| metrics-read-and-validate "${hide_init_warning}" |
| |
| if [[ "${METRICS_LEVEL}" -eq 0 ]]; then |
| exec 10> /dev/null |
| else |
| exec 10> >(_metrics-service) |
| fi |
| } |
| |
| # Calls __add-to-analytics-batch asynchronously, via metrics service |
| function _add-to-analytics-batch { |
| if [[ $# -eq 0 ]]; then |
| return 0 |
| fi |
| |
| local IFS=$'\n' |
| # printf will output \" as ". To avoid that, we use echo to send "$*" |
| echo "$*" >&10 |
| printf "\0" >&10 |
| } |
| |
| # Calls __send-analytics-batch asynchronously, via metrics service |
| function _send-analytics-batch { |
| printf "\0" >&10 |
| } |
| |
| |
| |
| function _os_data { |
| if command -v uname >/dev/null 2>&1 ; then |
| uname -rs |
| else |
| echo "Unknown" |
| fi |
| } |
| |
| function _app_name { |
| if [[ -n "${BASH_VERSION}" ]]; then |
| echo "bash" |
| elif [[ -n "${ZSH_VERSION}" ]]; then |
| echo "zsh" |
| else |
| echo "Unknown" |
| fi |
| } |
| |
| function _app_version { |
| if [[ -n "${BASH_VERSION}" ]]; then |
| echo "${BASH_VERSION}" |
| elif [[ -n "${ZSH_VERSION}" ]]; then |
| echo "${ZSH_VERSION}" |
| else |
| echo "Unknown" |
| fi |
| } |
| |
| function _get_nproc { |
| if [[ "${METRICS_LEVEL}" -eq 2 ]]; then |
| nproc |
| else |
| echo "-1" |
| fi |
| } |
| |
| # Args: |
| # debug_log_file: string with a filename to save logs |
| # use_validation_hit_server: |
| # 0 do not hit any Analytics server (for local tests) |
| # 1 use the Analytics validation Hit server (for integration tests) |
| # config_file: string with a filename to save the config file. Defaults to |
| # METRICS_CONFIG |
| function _enable_testing { |
| _METRICS_DEBUG_LOG_FILE="$1" |
| _METRICS_USE_VALIDATION_SERVER=$2 |
| if [[ $# -gt 2 ]]; then |
| METRICS_CONFIG="$3" |
| fi |
| _METRICS_DEBUG=1 |
| METRICS_UUID="TEST" |
| } |