blob: 65157ffa8c1f1076e9140830438709e15a64336a [file] [log] [blame]
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.tradefed.invoker.logger;
import com.android.tradefed.log.LogUtil.CLog;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
/** A utility class for an invocation to log some metrics. */
public class InvocationMetricLogger {
/** Some special named key that we will always populate for the invocation. */
public enum InvocationMetricKey {
WIFI_AP_NAME("wifi_ap_name", false),
WIFI_CONNECT_TIME("wifi_connect_time", true),
WIFI_CONNECT_COUNT("wifi_connect_count", true),
WIFI_CONNECT_RETRY_COUNT("wifi_connect_retry_count", true),
// Bugreport time and count
BUGREPORT_TIME("bugreport_time", true),
BUGREPORT_COUNT("bugreport_count", true),
ANR_TIME("anr_time", true),
ANR_COUNT("anr_count", true),
// Logcat dump time and count
LOGCAT_DUMP_TIME("logcat_dump_time", true),
LOGCAT_DUMP_COUNT("logcat_dump_count", true),
CLEARED_RUN_ERROR("cleared_run_error", true),
FETCH_BUILD("fetch_build_time_ms", true),
SETUP("setup_time_ms", true),
SHARDING_DEVICE_SETUP_TIME("remote_device_sharding_setup_ms", true),
AUTO_RETRY_TIME("auto_retry_time_ms", true),
BACKFILL_BUILD_INFO("backfill_build_info", false),
STAGE_TESTS_TIME("stage_tests_time_ms", true),
STAGE_REMOTE_TIME("stage_remote_time_ms", true),
STAGE_TESTS_BYTES("stage_tests_bytes", true),
STAGE_TESTS_INDIVIDUAL_DOWNLOADS("stage_tests_individual_downloads", true),
STAGE_UNDEFINED_DEPENDENCY("stage_undefined_dependency", true),
SERVER_REFERENCE("server_reference", false),
INSTRUMENTATION_RERUN_FROM_FILE("instrumentation_rerun_from_file", true),
INSTRUMENTATION_RERUN_SERIAL("instrumentation_rerun_serial", true),
DOWNLOAD_RETRY_COUNT("download_retry_count", true),
METADATA_RETRY_COUNT("metadata_retry_count", true),
XTS_STAGE_TESTS_TIME("xts_stage_tests_time_ms", true),
XTS_STAGE_TESTS_BYTES("xts_stage_tests_bytes", true),
XTS_PARTIAL_DOWNLOAD_SUCCESS_COUNT("xts_partial_download_success_count", true),
XTS_PARTIAL_DOWNLOAD_UNSUPPORTED_FILTER_FALLBACK_COUNT(
"xts_partial_download_unsupported_filter_fallback_count", true),
XTS_PARTIAL_DOWNLOAD_FALLBACK_COUNT("xts_partial_download_fallback_count", true),
XTS_PARTIAL_DOWNLOAD_UNFOUND_MODULES("xts_partial_download_unfound_modules", true),
XTS_PARTIAL_DOWNLOAD_TOTAL_COUNT("xts_partial_download_total_count", true),
SANDBOX_JAR_STAGING_PARTIAL_DOWNLOAD_FEATURE_COUNT(
"sandbox_jar_staging_partial_download_FEATURE_count", true),
SANDBOX_JAR_STAGING_PARTIAL_DOWNLOAD_SUCCESS_COUNT(
"sandbox_jar_staging_partial_download_SUCCESS_count", true),
TEST_DISCOVERY_MODULE_COUNT("test_discovery_module_count", true),
// -- Disk memory usage --
// Approximate peak disk space usage of the invocation
// Represent files that would usually live for the full invocation (min usage)
TEAR_DOWN_DISK_USAGE("teardown_disk_usage_bytes", false),
// Recovery Mode
AUTO_RECOVERY_MODE_COUNT("recovery_mode_count", true),
ATTEMPT_RECOVERY_LOG_COUNT("attempt_pull_recovery_log", true),
// Represents the time we spend attempting to recover a device.
RECOVERY_TIME("recovery_time", true),
// Represents how often we enter the recover device routine.
RECOVERY_ROUTINE_COUNT("recovery_routine_count", true),
// Represents the time we spend attempting to "adb root" a device.
ADB_ROOT_TIME("adb_root_time", true),
// Represents how often we enter the "adb root" device routine.
ADB_ROOT_ROUTINE_COUNT("adb_root_routine_count", true),
// Represents the time we spend attempting to reboot a device.
ADB_REBOOT_TIME("adb_reboot_time", true),
// Represents how often we attempt to reboot the device.
ADB_REBOOT_ROUTINE_COUNT("adb_reboot_routine_count", true),
// Represents the time attempting to reboot a device into bootloader
BOOTLOADER_REBOOT_TIME("bootloader_reboot_time", true),
// Represents how often we attempt to reboot the device into bootloader
BOOTLOADER_REBOOT_COUNT("bootloader_reboot_count", true),
// Represents the time attempting to reboot a device into fastbootd
FASTBOOTD_REBOOT_TIME("fastbootd_reboot_time", true),
// Represents how often we attempt to reboot the device into fastbootd
FASTBOOTD_REBOOT_COUNT("fastbootd_reboot_count", true),
// Represents how often we reboot a device already in bootloader
BOOTLOADER_SAME_STATE_REBOOT("bootloader_same_state_reboot", true),
// Represents the time we spend during postboot setup
POSTBOOT_SETUP_TIME("postboot_setup_time", true),
// Represents how often we go through postboot setup
POSTBOOT_SETUP_COUNT("postboot_setup_count", true),
// Represents the time we spend during postboot wifi setup
POSTBOOT_WIFI_SETUP_TIME("postboot_wifi_setup_time", true),
// Represents how often we go through postboot wifi setup
POSTBOOT_WIFI_SETUP_COUNT("postboot_wifi_setup_count", true),
// Represents the time we spend during md5 calculation
MD5_CALCULATION_TIME("md5_calculation_time", true),
// Represents how often we go through md5 calculation
MD5_CALCULATION_COUNT("md5_calculation_count", true),
// Represents the time we spend pulling file from device.
PULL_FILE_TIME("pull_file_time_ms", true),
// Represents how many times we pulled file from the device.
PULL_FILE_COUNT("pull_file_count", true),
// Represents the time we spend pulling dir from device.
PULL_DIR_TIME("pull_dir_time_ms", true),
// Represents how many times we pulled dir from the device.
PULL_DIR_COUNT("pull_dir_count", true),
// Represents the time we spend pushing file from device.
PUSH_FILE_TIME("push_file_time_ms", true),
// Represents how many times we pushed file from the device.
PUSH_FILE_COUNT("push_file_count", true),
// Represents the time we spend pushing dir from device.
PUSH_DIR_TIME("push_dir_time_ms", true),
// Represents how many times we pushing dir from the device.
PUSH_DIR_COUNT("push_dir_count", true),
// Represents the time we spent deleting file on device
DELETE_DEVICE_FILE_TIME("delete_device_file_time_ms", true),
// Represents how many times we call the delete file method
DELETE_DEVICE_FILE_COUNT("delete_device_file_count", true),
DOES_FILE_EXISTS_TIME("does_file_exists_time_ms", true),
DOES_FILE_EXISTS_COUNT("does_file_exists_count", true),
// Represents the time and count for installing packages
PACKAGE_INSTALL_TIME("package_install_time_ms", true),
PACKAGE_INSTALL_COUNT("package_install_count", true),
// Capture the time spent isolating a retry with reset
RESET_RETRY_ISOLATION_PAIR("reset_isolation_timestamp_pair", true),
// Capture the time spent isolating a retry with reboot
REBOOT_RETRY_ISOLATION_PAIR("reboot_isolation_timestamp_pair", true),
// Track metrics for skipped retries
RETRY_MODULE_SKIPPED_COUNT("retry_module_skipped_count", true),
RETRY_TEST_SKIPPED_COUNT("retry_test_skipped_count", true),
RETRY_SKIPPED_ALL_FILTERED_COUNT("retry_skipped_all_filtered_count", true),
// The time spent inside metric collectors
COLLECTOR_TIME("collector_time_ms", true),
// Track if soft restart is occurring after test module
SOFT_RESTART_AFTER_MODULE("soft_restart_after_module", true),
CLOUD_DEVICE_PROJECT("cloud_device_project", false),
CLOUD_DEVICE_MACHINE_TYPE("cloud_device_machine_type", false),
CLOUD_DEVICE_ZONE("cloud_device_zone", false),
CLOUD_DEVICE_STABLE_HOST_IMAGE("stable_host_image_name", false),
CLOUD_DEVICE_STABLE_HOST_IMAGE_PROJECT("stable_host_image_project", false),
SHUTDOWN_BEFORE_TEST("shutdown_before_test", false),
SHUTDOWN_AFTER_TEST("shutdown_after_test", false),
SHUTDOWN_LATENCY("shutdown_latency_ms", false),
SHUTDOWN_HARD_LATENCY("shutdown_hard_latency_ms", false),
DEVICE_COUNT("device_count", false),
DEVICE_DONE_TIMESTAMP("device_done_timestamp", false),
DEVICE_RELEASE_STATE("device_release_state", false),
DEVICE_LOST_DETECTED("device_lost_detected", false),
VIRTUAL_DEVICE_LOST_DETECTED("virtual_device_lost_detected", false),
// Count the number of time device recovery like usb reset are successful.
DEVICE_RECOVERY("device_recovery", true),
DEVICE_RECOVERY_FROM_RECOVERY("device_recovery_from_recovery", true),
DEVICE_RECOVERY_FROM_SSH_TUNNEL("device_recovered_from_ssh_tunnel", true),
DEVICE_RECOVERY_FAIL("device_recovery_fail", true),
SANDBOX_EXIT_CODE("sandbox_exit_code", false),
CF_FETCH_ARTIFACT_TIME("cf_fetch_artifact_time_ms", false),
CF_GCE_CREATE_TIME("cf_gce_create_time_ms", false),
CF_LAUNCH_CVD_TIME("cf_launch_cvd_time_ms", false),
CF_INSTANCE_COUNT("cf_instance_count", false),
CF_LOG_SIZE("cf_log_size_bytes", true),
CF_OXYGEN_SERVER_URL("cf_oxygen_server_url", false),
CF_OXYGEN_SESSION_ID("cf_oxygen_session_id", false),
CF_OXYGEN_VERSION("cf_oxygen_version", false),
CRASH_FAILURES("crash_failures", true),
UNCAUGHT_CRASH_FAILURES("uncaught_crash_failures", true),
TEST_CRASH_FAILURES("test_crash_failures", true),
UNCAUGHT_TEST_CRASH_FAILURES("uncaught_test_crash_failures", true),
DEVICE_RESET_COUNT("device_reset_count", true),
DEVICE_RESET_MODULES("device_reset_modules", true),
DEVICE_POWERWASH_DURATIONS("device_powerwash_durations", true),
DEVICE_RESET_MODULES_FOR_TARGET_PREPARER("device_reset_modules_for_target_preparer", true),
DEVICE_SNAPSHOT_COUNT("device_snapshot_count", true),
DEVICE_SNAPSHOT_DURATIONS("device_snapshot_durations", true),
DEVICE_SNAPSHOT_RESTORE_COUNT("device_snapshot_restore_count", true),
DEVICE_SNAPSHOT_RESTORE_DURATIONS("device_snapshot_restore_durations", true),
NONPERSISTENT_DEVICE_PROPERTIES("nonpersistent_device_properties", true),
PERSISTENT_DEVICE_PROPERTIES("persistent_device_properties", true),
INVOCATION_START("tf_invocation_start_timestamp", false),
LOAD_TEST_CONFIGS_TIME("load_test_configs_time_ms", true),
OXYGEN_DEVICE_DIRECT_LEASE_COUNT("oxygen_device_direct_lease_count", true),
OXYGEN_DEVICE_DIRECT_RELEASE_COUNT("oxygen_device_direct_release_count", true),
OXYGEN_DEVICE_RELEASE_FAILURE_COUNT("oxygen_device_release_failure_count", true),
OXYGEN_DEVICE_RELEASE_FAILURE_MESSAGE("oxygen_device_release_failure_message", true),
DYNAMIC_FILE_RESOLVER_PAIR("tf_dynamic_resolver_pair_timestamp", true),
ARTIFACTS_DOWNLOAD_SIZE("tf_artifacts_download_size_bytes", true),
ARTIFACTS_UPLOAD_SIZE("tf_artifacts_upload_size_bytes", true),
LOG_SAVING_TIME("log_saving_time", true),
LOG_SAVING_COUNT("log_saving_count", true),
// TODO: Delete start/end timestamp in favor of pair.
FETCH_BUILD_START("tf_fetch_build_start_timestamp", false),
FETCH_BUILD_END("tf_fetch_build_end_timestamp", false),
FETCH_BUILD_PAIR("tf_fetch_build_pair_timestamp", true),
// TODO: Delete start/end timestamp in favor of pair.
SETUP_START("tf_setup_start_timestamp", false),
SETUP_END("tf_setup_end_timestamp", false),
SETUP_PAIR("tf_setup_pair_timestamp", true),
TEST_SETUP_PAIR("tf_test_setup_pair_timestamp", true),
FLASHING_FROM_FASTBOOTD("flashing_from_fastbootd", true),
FLASHING_TIME("flashing_time_ms", true),
FLASHING_PERMIT_LATENCY("flashing_permit_latency_ms", true),
FLASHING_METHOD("flashing_method", false),
DOWNLOAD_PERMIT_LATENCY("download_permit_latency_ms", true),
// Unzipping metrics
UNZIP_TESTS_DIR_TIME("unzip_tests_dir_time_ms", true),
UNZIP_TESTS_DIR_COUNT("unzip_tests_dir_count", true),
// Don't aggregate test pair, latest report wins because it's the closest to
// the execution like in a subprocess.
TEST_PAIR("tf_test_pair_timestamp", false),
// TODO: Delete start/end timestamp in favor of pair.
TEARDOWN_START("tf_teardown_start_timestamp", false),
TEARDOWN_END("tf_teardown_end_timestamp", false),
TEARDOWN_PAIR("tf_teardown_pair_timestamp", true),
TEST_TEARDOWN_PAIR("tf_test_teardown_pair_timestamp", true),
INVOCATION_END("tf_invocation_end_timestamp", false),
MODULE_SETUP_PAIR("tf_module_setup_pair_timestamp", true),
MODULE_TEARDOWN_PAIR("tf_module_teardown_pair_timestamp", true),
STATUS_CHECKER_PAIR("status_checker_pair", true),
LAB_PREPARER_NOT_ILAB("lab_preparer_not_ilab", true),
TARGET_PREPARER_IS_ILAB("target_preparer_is_ilab", true),
ART_RUN_TEST_CHECKER_COMMAND_TIME_MS("art_run_test_checker_command_time_ms", true),
// CAS downloader metrics
// Name of files downloaded by CAS downloader.
CAS_DOWNLOAD_FILES("cas_download_files", true),
CAS_DOWNLOAD_FILE_SUCCESS_COUNT("cas_download_file_success_count", true),
CAS_DOWNLOAD_FILE_FAIL_COUNT("cas_download_file_fail_count", true),
CAS_DOWNLOAD_TIME("cas_download_time_ms", true),
// Records the wait time caused by CAS downloader concurrency limitation.
CAS_DOWNLOAD_WAIT_TIME("cas_download_wait_time_ms", true),
CAS_CACHE_FALLBACK_COUNT("cas_cache_fallback_count", true),
CAS_TIMEOUT_COUNT("cas_timeout_count", true),
// Records cache hit metrics
CAS_DOWNLOAD_HOT_BYTES("cas_download_hot_bytes", true),
CAS_DOWNLOAD_COLD_BYTES("cas_download_cold_bytes", true),
// Download Cache
CACHE_HIT_COUNT("cache_hit_count", true),
CACHE_WAIT_FOR_LOCK("cache_wait_for_lock", true),
// CF Cache metrics
CF_CACHE_WAIT_TIME("cf_cache_wait_time_sec", false),
CF_ARTIFACTS_FETCH_SOURCE("cf_artifacts_fetch_source", false),
// Ab downloader metrics
AB_DOWNLOAD_SIZE_ELAPSED_TIME("ab_download_size_elapsed_time", true),
ZIP_PARTIAL_DOWNLOAD_CACHE_HIT("zip_partial_download_cache_hit", true),
DUPLICATE_MAPPING_DIFFERENT_OPTIONS("duplicate_mapping_different_options", true),
HAS_ANY_RUN_FAILURES("has_any_run_failures", false),
TOTAL_TEST_COUNT("total_test_count", true),
// Metrics to store Device failure signatures
DEVICE_ERROR_SIGNATURES("device_failure_signatures", false),
DEVICE_IMAGE_NOT_CHANGED("device_image_not_changed", false),
TEST_ARTIFACT_NOT_CHANGED("test_artifact_not_changed", true),
POWERWASH_TIME("powerwash_time_ms", true),
POWERWASH_SUCCESS_COUNT("powerwash_success_count", true),
POWERWASH_FAILURE_COUNT("powerwash_failure_count", true),
LEASE_RETRY_COUNT_SUCCESS("lease_retry_count_success", true),
LEASE_RETRY_COUNT_FAILURE("lease_retry_count_failure", true),
TRACE_INTERNAL_ERROR("trace_internal_error", true),
// Following are trace events also reporting as metrics
invocation_warm_up("invocation_warm_up", true),
dynamic_download("dynamic_download", true),
fetch_artifact("fetch_artifact", true),
start_logcat("start_logcat", true),
pre_sharding_required_setup("pre_sharding_required_setup", true),
sharding("sharding", true),
pre_multi_preparer("pre_multi_preparer", true),
lab_setup("lab_setup", true),
test_setup("test_setup", true),
test_execution("test_execution", true),
check_device_availability("check_device_availability", true),
bugreport("bugreport", true),
host_sleep("host_sleep", true),
test_teardown("test_teardown", true),
test_cleanup("test_cleanup", true),
log_and_release_device("log_and_release_device", true),
invocation_events_processing("invocation_events_processing", true),
stage_suite_test_artifacts("stage_suite_test_artifacts", true),
wait_for_results_update("wait_for_results_update", true),
instru_collect_tests("instru_collect_tests", true),
// Test caching metrics
CACHED_MODULE_RESULTS_COUNT("cached_module_results_count", true),
;
private final String mKeyName;
// Whether or not to add the value when the key is added again.
private final boolean mAdditive;
private InvocationMetricKey(String key, boolean additive) {
mKeyName = key;
mAdditive = additive;
}
@Override
public String toString() {
return mKeyName;
}
public boolean shouldAdd() {
return mAdditive;
}
}
/** Grouping allows to log several groups under a same key. */
public enum InvocationGroupMetricKey {
TEST_TYPE_COUNT("test-type-count", true),
TARGET_PREPARER_SETUP_LATENCY("target-preparer-setup-latency", true),
TARGET_PREPARER_TEARDOWN_LATENCY("target-preparer-teardown-latency", true),
LAB_PREPARER_SETUP_LATENCY("lab-preparer-setup-latency", true),
LAB_PREPARER_TEARDOWN_LATENCY("lab-preparer-teardown-latency", true),
MULTI_TARGET_PREPARER_TEARDOWN_LATENCY("multi-target-preparer-teardown-latency", true);
private final String mGroupName;
// Whether or not to add the value when the key is added again.
private final boolean mAdditive;
private InvocationGroupMetricKey(String groupName, boolean additive) {
mGroupName = groupName;
mAdditive = additive;
}
@Override
public String toString() {
return mGroupName;
}
public boolean shouldAdd() {
return mAdditive;
}
}
private InvocationMetricLogger() {}
private static ThreadLocal<ThreadGroup> sLocal = new ThreadLocal<>();
/** Tracks a localized context when using the properties inside the gRPC server */
public static void setLocalGroup(ThreadGroup tg) {
sLocal.set(tg);
}
/** Resets the localized context. */
public static void resetLocalGroup() {
sLocal.remove();
}
/**
* Track metrics per ThreadGroup as a proxy to invocation since an invocation run within one
* threadgroup.
*/
private static final Map<ThreadGroup, Map<String, String>> mPerGroupMetrics =
Collections.synchronizedMap(new HashMap<ThreadGroup, Map<String, String>>());
/**
* Add one key-value to be tracked at the invocation level.
*
* @param key The key under which the invocation metric will be tracked.
* @param value The value of the invocation metric.
*/
public static void addInvocationMetrics(InvocationMetricKey key, long value) {
if (key.shouldAdd()) {
String existingVal = getInvocationMetrics().get(key.toString());
long existingLong = 0L;
if (existingVal != null) {
try {
existingLong = Long.parseLong(existingVal);
} catch (NumberFormatException e) {
CLog.e(
"%s is expected to contain a number, instead found: %s",
key.toString(), existingVal);
}
}
value += existingLong;
}
addInvocationMetrics(key.toString(), Long.toString(value));
}
/**
* Add one key-value for a given group
*
* @param groupKey The key of the group
* @param group The group name associated with the key
* @param value The value for the group
*/
public static void addInvocationMetrics(
InvocationGroupMetricKey groupKey, String group, String value) {
String key = groupKey.toString() + ":" + group;
if (groupKey.shouldAdd()) {
String existingVal = getInvocationMetrics().get(key.toString());
if (existingVal != null) {
value = String.format("%s,%s", existingVal, value);
}
}
addInvocationMetrics(key, value);
}
/**
* Add one key-value to be tracked at the invocation level. Don't expose the String key yet to
* avoid abuse, stick to the official {@link InvocationMetricKey} to start with.
*
* @param key The key under which the invocation metric will be tracked.
* @param value The value of the invocation metric.
*/
private static void addInvocationMetrics(String key, String value) {
ThreadGroup group = Thread.currentThread().getThreadGroup();
synchronized (mPerGroupMetrics) {
if (sLocal.get() != null) {
group = sLocal.get();
}
if (mPerGroupMetrics.get(group) == null) {
mPerGroupMetrics.put(group, new HashMap<>());
}
mPerGroupMetrics.get(group).put(key, value);
}
}
/**
* Add one key-value to be tracked at the invocation level for a given group.
*
* @param groupKey The key of the group
* @param group The group name associated with the key
* @param value The value for the group
*/
public static void addInvocationMetrics(
InvocationGroupMetricKey groupKey, String group, long value) {
String key = groupKey.toString() + ":" + group;
if (groupKey.shouldAdd()) {
String existingVal = getInvocationMetrics().get(key);
long existingLong = 0L;
if (existingVal != null) {
try {
existingLong = Long.parseLong(existingVal);
} catch (NumberFormatException e) {
CLog.e(
"%s is expected to contain a number, instead found: %s",
key.toString(), existingVal);
}
}
value += existingLong;
}
addInvocationMetrics(key, Long.toString(value));
}
/**
* Add one key-value to be tracked at the invocation level.
*
* @param key The key under which the invocation metric will be tracked.
* @param value The value of the invocation metric.
*/
public static void addInvocationMetrics(InvocationMetricKey key, String value) {
if (key.shouldAdd()) {
String existingVal = getInvocationMetrics().get(key.toString());
if (existingVal != null) {
value = String.format("%s,%s", existingVal, value);
}
}
addInvocationMetrics(key.toString(), value);
}
/**
* Add a pair of value associated with the same key. Usually used for timestamp start and end.
*
* @param key The key under which the invocation metric will be tracked.
* @param start The start value of the invocation metric.
* @param end The end value of the invocation metric.
*/
public static void addInvocationPairMetrics(InvocationMetricKey key, long start, long end) {
String value = start + ":" + end;
if (key.shouldAdd()) {
String existingVal = getInvocationMetrics().get(key.toString());
if (existingVal != null) {
value = String.format("%s,%s", existingVal, value);
}
}
addInvocationMetrics(key.toString(), value);
}
/** Returns the Map of invocation metrics for the invocation in progress. */
public static Map<String, String> getInvocationMetrics() {
ThreadGroup group = Thread.currentThread().getThreadGroup();
synchronized (mPerGroupMetrics) {
if (sLocal.get() != null) {
group = sLocal.get();
}
if (mPerGroupMetrics.get(group) == null) {
mPerGroupMetrics.put(group, new HashMap<>());
}
return new HashMap<>(mPerGroupMetrics.get(group));
}
}
/** Clear the invocation metrics for an invocation. */
public static void clearInvocationMetrics() {
ThreadGroup group = Thread.currentThread().getThreadGroup();
synchronized (mPerGroupMetrics) {
mPerGroupMetrics.remove(group);
}
}
}