1 /* 2 * Copyright (C) 2019 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.tradefed.invoker.logger; 17 18 import com.android.tradefed.log.LogUtil.CLog; 19 20 import java.util.Collections; 21 import java.util.HashMap; 22 import java.util.Map; 23 24 /** A utility class for an invocation to log some metrics. */ 25 public class InvocationMetricLogger { 26 27 /** Some special named key that we will always populate for the invocation. */ 28 public enum InvocationMetricKey { 29 WIFI_AP_NAME("wifi_ap_name", false), 30 CLEARED_RUN_ERROR("cleared_run_error", true), 31 FETCH_BUILD("fetch_build_time_ms", true), 32 SETUP("setup_time_ms", true), 33 SHARDING_DEVICE_SETUP_TIME("remote_device_sharding_setup_ms", true), 34 AUTO_RETRY_TIME("auto_retry_time_ms", true), 35 STAGE_TESTS_TIME("stage_tests_time_ms", true), 36 STAGE_TESTS_BYTES("stage_tests_bytes", true), 37 STAGE_TESTS_INDIVIDUAL_DOWNLOADS("stage_tests_individual_downloads", true), 38 SHUTDOWN_HARD_LATENCY("shutdown_hard_latency_ms", false), 39 DEVICE_DONE_TIMESTAMP("device_done_timestamp", false), 40 DEVICE_RELEASE_STATE("device_release_state", false), 41 DEVICE_LOST_DETECTED("device_lost_detected", false), 42 VIRTUAL_DEVICE_LOST_DETECTED("virtual_device_lost_detected", false), 43 DEVICE_RECOVERY("device_recovery", true), 44 DEVICE_RECOVERY_FROM_RECOVERY("device_recovery_from_recovery", true), 45 DEVICE_RECOVERY_FAIL("device_recovery_fail", true), 46 SANDBOX_EXIT_CODE("sandbox_exit_code", false), 47 CF_FETCH_ARTIFACT_TIME("cf_fetch_artifact_time_ms", false), 48 CF_GCE_CREATE_TIME("cf_gce_create_time_ms", false), 49 CF_LAUNCH_CVD_TIME("cf_launch_cvd_time_ms", false), 50 CF_INSTANCE_COUNT("cf_instance_count", false); 51 52 private final String mKeyName; 53 // Whether or not to add the value when the key is added again. 54 private final boolean mAdditive; 55 InvocationMetricKey(String key, boolean additive)56 private InvocationMetricKey(String key, boolean additive) { 57 mKeyName = key; 58 mAdditive = additive; 59 } 60 61 @Override toString()62 public String toString() { 63 return mKeyName; 64 } 65 shouldAdd()66 public boolean shouldAdd() { 67 return mAdditive; 68 } 69 } 70 InvocationMetricLogger()71 private InvocationMetricLogger() {} 72 73 /** 74 * Track metrics per ThreadGroup as a proxy to invocation since an invocation run within one 75 * threadgroup. 76 */ 77 private static final Map<ThreadGroup, Map<String, String>> mPerGroupMetrics = 78 Collections.synchronizedMap(new HashMap<ThreadGroup, Map<String, String>>()); 79 80 /** 81 * Add one key-value to be tracked at the invocation level. 82 * 83 * @param key The key under which the invocation metric will be tracked. 84 * @param value The value of the invocation metric. 85 */ addInvocationMetrics(InvocationMetricKey key, long value)86 public static void addInvocationMetrics(InvocationMetricKey key, long value) { 87 if (key.shouldAdd()) { 88 String existingVal = getInvocationMetrics().get(key.toString()); 89 long existingLong = 0L; 90 if (existingVal != null) { 91 try { 92 existingLong = Long.parseLong(existingVal); 93 } catch (NumberFormatException e) { 94 CLog.e( 95 "%s is expected to contain a number, instead found: %s", 96 key.toString(), existingVal); 97 } 98 } 99 value += existingLong; 100 } 101 addInvocationMetrics(key.toString(), Long.toString(value)); 102 } 103 104 /** 105 * Add one key-value to be tracked at the invocation level. 106 * 107 * @param key The key under which the invocation metric will be tracked. 108 * @param value The value of the invocation metric. 109 */ addInvocationMetrics(InvocationMetricKey key, String value)110 public static void addInvocationMetrics(InvocationMetricKey key, String value) { 111 if (key.shouldAdd()) { 112 String existingVal = getInvocationMetrics().get(key.toString()); 113 if (existingVal != null) { 114 value = String.format("%s,%s", existingVal, value); 115 } 116 } 117 addInvocationMetrics(key.toString(), value); 118 } 119 120 /** 121 * Add one key-value to be tracked at the invocation level. Don't expose the String key yet to 122 * avoid abuse, stick to the official {@link InvocationMetricKey} to start with. 123 * 124 * @param key The key under which the invocation metric will be tracked. 125 * @param value The value of the invocation metric. 126 */ addInvocationMetrics(String key, String value)127 private static void addInvocationMetrics(String key, String value) { 128 ThreadGroup group = Thread.currentThread().getThreadGroup(); 129 synchronized (mPerGroupMetrics) { 130 if (mPerGroupMetrics.get(group) == null) { 131 mPerGroupMetrics.put(group, new HashMap<>()); 132 } 133 mPerGroupMetrics.get(group).put(key, value); 134 } 135 } 136 137 /** Returns the Map of invocation metrics for the invocation in progress. */ getInvocationMetrics()138 public static Map<String, String> getInvocationMetrics() { 139 ThreadGroup group = Thread.currentThread().getThreadGroup(); 140 synchronized (mPerGroupMetrics) { 141 if (mPerGroupMetrics.get(group) == null) { 142 mPerGroupMetrics.put(group, new HashMap<>()); 143 } 144 } 145 return new HashMap<>(mPerGroupMetrics.get(group)); 146 } 147 148 /** Clear the invocation metrics for an invocation. */ clearInvocationMetrics()149 public static void clearInvocationMetrics() { 150 ThreadGroup group = Thread.currentThread().getThreadGroup(); 151 synchronized (mPerGroupMetrics) { 152 mPerGroupMetrics.remove(group); 153 } 154 } 155 } 156