• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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