blob: f4c892d517078f01462bc746c4aa5421a5fb5556 [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),
CLEARED_RUN_ERROR("cleared_run_error", false),
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),
STAGE_TESTS_TIME("stage_tests_time_ms", true),
STAGE_TESTS_BYTES("stage_tests_bytes", true),
STAGE_TESTS_INDIVIDUAL_DOWNLOADS("stage_tests_individual_downloads", true),
SHUTDOWN_HARD_LATENCY("shutdown_hard_latency_ms", false),
DEVICE_DONE_TIMESTAMP("device_done_timestamp", false),
DEVICE_RELEASE_STATE("device_release_state", false),
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);
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;
}
}
private InvocationMetricLogger() {}
/**
* 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 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 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 (mPerGroupMetrics.get(group) == null) {
mPerGroupMetrics.put(group, new HashMap<>());
}
mPerGroupMetrics.get(group).put(key, 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 (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);
}
}
}