1 /* 2 * Copyright (C) 2025 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 17 package com.android.tradefed.util.avd; 18 19 import com.android.tradefed.result.error.ErrorIdentifier; 20 import com.android.tradefed.result.error.InfraErrorIdentifier; 21 22 import com.google.common.base.Strings; 23 24 import java.util.AbstractMap; 25 import java.util.Arrays; 26 import java.util.HashSet; 27 import java.util.LinkedHashMap; 28 import java.util.Map; 29 import java.util.Optional; 30 import java.util.Set; 31 import java.util.regex.Matcher; 32 import java.util.regex.Pattern; 33 import java.util.stream.Collectors; 34 import java.util.stream.Stream; 35 36 /** A utility for inspecting AVD and host VM */ 37 public class InspectionUtil { 38 public static final Integer DISK_USAGE_MAX = 95; 39 40 // A map of expected process names and the corresponding error identifier if they are missing. 41 // The name string should be a substring of a process list. 42 public static final LinkedHashMap<String, ErrorIdentifier> EXPECTED_PROCESSES = 43 Stream.of( 44 new AbstractMap.SimpleEntry<>( 45 "run_cvd", 46 InfraErrorIdentifier.CUTTLEFISH_LAUNCH_FAILURE_RUN_CVD_MISSING), 47 new AbstractMap.SimpleEntry<>( 48 "netsimd", 49 InfraErrorIdentifier.CUTTLEFISH_LAUNCH_FAILURE_BLUETOOTH), 50 new AbstractMap.SimpleEntry<>( 51 "openwrt_control_server", 52 InfraErrorIdentifier.CUTTLEFISH_LAUNCH_FAILURE_OPENWRT), 53 new AbstractMap.SimpleEntry<>( 54 "webRTC", 55 InfraErrorIdentifier.CUTTLEFISH_LAUNCH_FAILURE_WEBRTC_CRASH), 56 new AbstractMap.SimpleEntry<>( 57 "crosvm", 58 InfraErrorIdentifier.CUTTLEFISH_LAUNCH_FAILURE_CROSVM), 59 new AbstractMap.SimpleEntry<>( 60 "nginx", InfraErrorIdentifier.CUTTLEFISH_LAUNCH_FAILURE_NGINX)) 61 .collect( 62 Collectors.toMap( 63 Map.Entry::getKey, 64 Map.Entry::getValue, 65 (x, y) -> y, 66 LinkedHashMap::new)); 67 68 // A map of unexpected process names and the corresponding error identifier if they are found. 69 // The name string should be a substring of a process list. 70 public static final Map<String, ErrorIdentifier> UNEXPECTED_PROCESSES = 71 Stream.of( 72 new AbstractMap.SimpleEntry<>( 73 "cvd fetch", 74 InfraErrorIdentifier.CUTTLEFISH_LAUNCH_FAILURE_CVD_FETCH_HANG)) 75 .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); 76 77 // Map between error signature to ErrorIdentifier. Note that the signatures are sorted. More 78 // specific error signature should be ranked first. 79 private static final LinkedHashMap<String, ErrorIdentifier> ERROR_SIGNATURE_TO_IDENTIFIER_MAP = 80 Stream.of( 81 new AbstractMap.SimpleEntry<>( 82 "bluetooth_failed", 83 InfraErrorIdentifier.CUTTLEFISH_LAUNCH_FAILURE_BLUETOOTH), 84 new AbstractMap.SimpleEntry<>( 85 "fetch_cvd_failure_resolve_host", 86 InfraErrorIdentifier 87 .CUTTLEFISH_LAUNCH_FAILURE_CVD_RESOLVE_HOST), 88 new AbstractMap.SimpleEntry<>( 89 "fetch_cvd_failure_connect_server", 90 InfraErrorIdentifier 91 .CUTTLEFISH_LAUNCH_FAILURE_CVD_SERVER_CONNECTION), 92 new AbstractMap.SimpleEntry<>( 93 "launch_cvd_port_collision", 94 InfraErrorIdentifier 95 .CUTTLEFISH_LAUNCH_FAILURE_CVD_PORT_COLLISION), 96 new AbstractMap.SimpleEntry<>( 97 "fetch_cvd_failure_general", 98 InfraErrorIdentifier.CUTTLEFISH_LAUNCH_FAILURE_CVD_FETCH), 99 new AbstractMap.SimpleEntry<>( 100 "cf_webrtc_crash", 101 InfraErrorIdentifier.CUTTLEFISH_LAUNCH_FAILURE_WEBRTC_CRASH), 102 new AbstractMap.SimpleEntry<>( 103 "fetch_cvd_failure_artifact_not_found", 104 InfraErrorIdentifier.ARTIFACT_NOT_FOUND)) 105 .collect( 106 Collectors.toMap( 107 Map.Entry::getKey, 108 Map.Entry::getValue, 109 (x, y) -> y, 110 LinkedHashMap::new)); 111 112 /** 113 * Convert error signature to ErrorIdentifier if possible 114 * 115 * @param errorSignatures a string of comma separated error signatures 116 * @return {@link ErrorIdentifier} 117 */ convertErrorSignatureToIdentifier(String errorSignatures)118 public static ErrorIdentifier convertErrorSignatureToIdentifier(String errorSignatures) { 119 if (errorSignatures == null) { 120 return null; 121 } 122 Set<String> signatures = new HashSet<>(Arrays.asList(errorSignatures.split(","))); 123 for (String signature : ERROR_SIGNATURE_TO_IDENTIFIER_MAP.keySet()) { 124 if (signatures.contains(signature)) { 125 return ERROR_SIGNATURE_TO_IDENTIFIER_MAP.get(signature); 126 } 127 } 128 return null; 129 } 130 131 /** 132 * Parse the df command output to return the disk usage percentage 133 * 134 * @param diskspaceInfo output of command `df -P \` 135 * @return An Optional<Integer> containing the percentage of used disk space if found, or an 136 * empty Optional if not found or an error occurred. 137 */ getDiskspaceUsage(String diskspaceInfo)138 public static Optional<Integer> getDiskspaceUsage(String diskspaceInfo) { 139 Pattern pattern = Pattern.compile("\\s(\\d+)%\\s", Pattern.DOTALL); 140 Matcher matcher = pattern.matcher(diskspaceInfo); 141 142 if (matcher.find()) { 143 String percentageString = matcher.group(1); 144 return Optional.of(Integer.parseInt(percentageString)); 145 } 146 return Optional.empty(); 147 } 148 149 /** 150 * Search for a process matching with the substring 151 * 152 * @param allProcesses string of a list of processes generated from top or ps command 153 * @param process substring of the process to search for 154 * @return true if the process is found, false otherwise 155 */ searchProcess(String allProcesses, String process)156 public static boolean searchProcess(String allProcesses, String process) { 157 if (Strings.isNullOrEmpty(allProcesses)) { 158 return false; 159 } 160 for (String line : allProcesses.split("\\n")) { 161 if (line.indexOf(process) != -1) { 162 return true; 163 } 164 } 165 return false; 166 } 167 } 168