1 /* 2 * Copyright (C) 2018 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.device.cloud; 17 18 import com.android.tradefed.command.remote.DeviceDescriptor; 19 import com.android.tradefed.invoker.logger.InvocationMetricLogger; 20 import com.android.tradefed.invoker.logger.InvocationMetricLogger.InvocationMetricKey; 21 import com.android.tradefed.log.LogUtil.CLog; 22 import com.android.tradefed.result.LogDataType; 23 import com.android.tradefed.result.error.ErrorIdentifier; 24 import com.android.tradefed.result.error.InfraErrorIdentifier; 25 import com.android.tradefed.targetprep.TargetSetupError; 26 import com.android.tradefed.util.CommandResult; 27 import com.android.tradefed.util.CommandStatus; 28 29 import com.google.common.annotations.VisibleForTesting; 30 import com.google.common.base.Strings; 31 import com.google.common.collect.ImmutableMap; 32 import com.google.common.net.HostAndPort; 33 34 import org.json.JSONArray; 35 import org.json.JSONException; 36 import org.json.JSONObject; 37 38 import java.io.File; 39 import java.io.IOException; 40 import java.nio.charset.StandardCharsets; 41 import java.nio.file.Files; 42 import java.util.ArrayList; 43 import java.util.Arrays; 44 import java.util.HashMap; 45 import java.util.LinkedHashMap; 46 import java.util.List; 47 import java.util.Map; 48 import java.util.regex.Matcher; 49 import java.util.regex.Pattern; 50 51 /** Structure to hold relevant data for a given GCE AVD instance. */ 52 public class GceAvdInfo { 53 54 // Patterns to match from Oxygen client's return message to identify error. 55 private static final LinkedHashMap<InfraErrorIdentifier, String> OXYGEN_ERROR_PATTERN_MAP; 56 57 static { 58 OXYGEN_ERROR_PATTERN_MAP = new LinkedHashMap<InfraErrorIdentifier, String>(); 59 // Order the error message matching carefully so it can surface the expected error properly OXYGEN_ERROR_PATTERN_MAP.put( InfraErrorIdentifier.OXYGEN_SERVER_SHUTTING_DOWN, "server_shutting_down")60 OXYGEN_ERROR_PATTERN_MAP.put( 61 InfraErrorIdentifier.OXYGEN_SERVER_SHUTTING_DOWN, "server_shutting_down"); OXYGEN_ERROR_PATTERN_MAP.put( InfraErrorIdentifier.OXYGEN_BAD_GATEWAY_ERROR, "UNAVAILABLE: HTTP status code 502")62 OXYGEN_ERROR_PATTERN_MAP.put( 63 InfraErrorIdentifier.OXYGEN_BAD_GATEWAY_ERROR, "UNAVAILABLE: HTTP status code 502"); OXYGEN_ERROR_PATTERN_MAP.put( InfraErrorIdentifier.OXYGEN_REQUEST_TIMEOUT, "DeadlineExceeded")64 OXYGEN_ERROR_PATTERN_MAP.put( 65 InfraErrorIdentifier.OXYGEN_REQUEST_TIMEOUT, "DeadlineExceeded"); OXYGEN_ERROR_PATTERN_MAP.put( InfraErrorIdentifier.OXYGEN_RESOURCE_EXHAUSTED, "ResourceExhausted")66 OXYGEN_ERROR_PATTERN_MAP.put( 67 InfraErrorIdentifier.OXYGEN_RESOURCE_EXHAUSTED, "ResourceExhausted"); OXYGEN_ERROR_PATTERN_MAP.put( InfraErrorIdentifier.OXYGEN_NOT_ENOUGH_RESOURCE, "Oxygen currently doesn't have enough resources to fulfil this request")68 OXYGEN_ERROR_PATTERN_MAP.put( 69 InfraErrorIdentifier.OXYGEN_NOT_ENOUGH_RESOURCE, 70 "Oxygen currently doesn't have enough resources to fulfil this request"); OXYGEN_ERROR_PATTERN_MAP.put( InfraErrorIdentifier.OXYGEN_SERVER_CONNECTION_FAILURE, "Bad Gateway")71 OXYGEN_ERROR_PATTERN_MAP.put( 72 InfraErrorIdentifier.OXYGEN_SERVER_CONNECTION_FAILURE, "Bad Gateway"); OXYGEN_ERROR_PATTERN_MAP.put( InfraErrorIdentifier.OXYGEN_CLIENT_LEASE_ERROR, "OxygenClient")73 OXYGEN_ERROR_PATTERN_MAP.put( 74 InfraErrorIdentifier.OXYGEN_CLIENT_LEASE_ERROR, "OxygenClient"); OXYGEN_ERROR_PATTERN_MAP.put( InfraErrorIdentifier.OXYGEN_DEVICE_LAUNCHER_TIMEOUT, "Lease aborted due to launcher failure: Timed out waiting for virtual device to" + " start")75 OXYGEN_ERROR_PATTERN_MAP.put( 76 InfraErrorIdentifier.OXYGEN_DEVICE_LAUNCHER_TIMEOUT, 77 "Lease aborted due to launcher failure: Timed out waiting for virtual device to" 78 + " start"); OXYGEN_ERROR_PATTERN_MAP.put( InfraErrorIdentifier.OXYGEN_DEVICE_LAUNCHER_FAILURE, "Lease aborted due to launcher failure")79 OXYGEN_ERROR_PATTERN_MAP.put( 80 InfraErrorIdentifier.OXYGEN_DEVICE_LAUNCHER_FAILURE, 81 "Lease aborted due to launcher failure"); OXYGEN_ERROR_PATTERN_MAP.put( InfraErrorIdentifier.OXYGEN_SERVER_LB_CONNECTION_ERROR, "desc = connection error")82 OXYGEN_ERROR_PATTERN_MAP.put( 83 InfraErrorIdentifier.OXYGEN_SERVER_LB_CONNECTION_ERROR, "desc = connection error"); 84 } 85 86 // Error message for specify Oxygen error. 87 private static final ImmutableMap<InfraErrorIdentifier, String> OXYGEN_ERROR_MESSAGE_MAP = 88 ImmutableMap.of( 89 InfraErrorIdentifier.OXYGEN_DEVICE_LAUNCHER_FAILURE, 90 "AVD failed to boot up properly", 91 InfraErrorIdentifier.OXYGEN_SERVER_SHUTTING_DOWN, 92 "Unexpected error from Oxygen service", 93 InfraErrorIdentifier.OXYGEN_BAD_GATEWAY_ERROR, 94 "Unexpected error from Oxygen service", 95 InfraErrorIdentifier.OXYGEN_REQUEST_TIMEOUT, 96 "Unexpected error from Oxygen service. Request timed out.", 97 InfraErrorIdentifier.OXYGEN_RESOURCE_EXHAUSTED, 98 "Oxygen ran out of capacity to lease virtual device", 99 InfraErrorIdentifier.OXYGEN_SERVER_CONNECTION_FAILURE, 100 "Unexpected error from Oxygen service", 101 InfraErrorIdentifier.OXYGEN_CLIENT_LEASE_ERROR, 102 "Oxygen client failed to lease a device", 103 InfraErrorIdentifier.OXYGEN_DEVICE_LAUNCHER_TIMEOUT, "AVD boot timed out"); 104 105 public static class LogFileEntry { 106 public final String path; 107 public final LogDataType type; 108 // The name is optional and defaults to an empty string. 109 public final String name; 110 111 @VisibleForTesting LogFileEntry(String path, LogDataType type, String name)112 LogFileEntry(String path, LogDataType type, String name) { 113 this.path = path; 114 this.type = type; 115 this.name = name; 116 } 117 LogFileEntry(JSONObject log)118 LogFileEntry(JSONObject log) throws JSONException { 119 path = log.getString("path"); 120 type = parseLogDataType(log.getString("type")); 121 name = log.optString("name", ""); 122 } 123 parseLogDataType(String typeString)124 private LogDataType parseLogDataType(String typeString) { 125 try { 126 return LogDataType.valueOf(typeString); 127 } catch (IllegalArgumentException e) { 128 CLog.w("Unknown log type in GCE AVD info: %s", typeString); 129 return LogDataType.UNKNOWN; 130 } 131 } 132 } 133 134 public static final List<String> BUILD_VARS = 135 Arrays.asList( 136 "build_id", 137 "build_target", 138 "branch", 139 "kernel_build_id", 140 "kernel_build_target", 141 "kernel_branch", 142 "system_build_id", 143 "system_build_target", 144 "system_branch", 145 "emulator_build_id", 146 "emulator_build_target", 147 "emulator_branch"); 148 149 private String mInstanceName; 150 private HostAndPort mHostAndPort; 151 private ErrorIdentifier mErrorType; 152 private String mErrors; 153 private GceStatus mStatus; 154 private HashMap<String, String> mBuildVars; 155 private List<LogFileEntry> mLogs; 156 private boolean mIsIpPreconfigured = false; 157 private Integer mDeviceOffset = null; 158 private String mInstanceUser = null; 159 // Skip collecting device log if set to true. 160 private boolean mSkipDeviceLogCollection = false; 161 private boolean mIsOxygenationDevice = false; 162 private String mOxygenationDeviceId = null; 163 164 public static enum GceStatus { 165 SUCCESS, 166 FAIL, 167 BOOT_FAIL, 168 DEVICE_OFFLINE, 169 } 170 GceAvdInfo(String instanceName, HostAndPort hostAndPort)171 public GceAvdInfo(String instanceName, HostAndPort hostAndPort) { 172 mInstanceName = instanceName; 173 mHostAndPort = hostAndPort; 174 mBuildVars = new HashMap<String, String>(); 175 mLogs = new ArrayList<LogFileEntry>(); 176 } 177 GceAvdInfo( String instanceName, HostAndPort hostAndPort, ErrorIdentifier errorType, String errors, GceStatus status)178 public GceAvdInfo( 179 String instanceName, 180 HostAndPort hostAndPort, 181 ErrorIdentifier errorType, 182 String errors, 183 GceStatus status) { 184 this(instanceName, hostAndPort); 185 mErrorType = errorType; 186 mErrors = errors; 187 mStatus = status; 188 } 189 190 /** {@inheritDoc} */ 191 @Override toString()192 public String toString() { 193 return "GceAvdInfo [mInstanceName=" 194 + mInstanceName 195 + ", mHostAndPort=" 196 + mHostAndPort 197 + ", mDeviceOffset=" 198 + mDeviceOffset 199 + ", mInstanceUser=" 200 + mInstanceUser 201 + ", mErrorType=" 202 + mErrorType 203 + ", mErrors=" 204 + mErrors 205 + ", mStatus=" 206 + mStatus 207 + ", mIsIpPreconfigured=" 208 + mIsIpPreconfigured 209 + ", mBuildVars=" 210 + ", mOxygenationDeviceId=" 211 + mOxygenationDeviceId 212 + ", mIsOxygenationDevice=" 213 + mIsOxygenationDevice 214 + mBuildVars.toString() 215 + ", mLogs=" 216 + mLogs.toString() 217 + "]"; 218 } 219 isOxygenationDevice()220 public boolean isOxygenationDevice() { 221 return mIsOxygenationDevice; 222 } 223 getOxygenationDeviceId()224 public String getOxygenationDeviceId() { 225 return mOxygenationDeviceId; 226 } 227 instanceName()228 public String instanceName() { 229 return mInstanceName; 230 } 231 hostAndPort()232 public HostAndPort hostAndPort() { 233 return mHostAndPort; 234 } 235 getErrorType()236 public ErrorIdentifier getErrorType() { 237 return mErrorType; 238 } 239 setErrorType(ErrorIdentifier errorType)240 public void setErrorType(ErrorIdentifier errorType) { 241 mErrorType = errorType; 242 } 243 getErrors()244 public String getErrors() { 245 return mErrors; 246 } 247 setErrors(String errors)248 public void setErrors(String errors) { 249 mErrors = errors; 250 } 251 252 /** Return the map from local or remote log paths to types. */ getLogs()253 public List<LogFileEntry> getLogs() { 254 return mLogs; 255 } 256 getStatus()257 public GceStatus getStatus() { 258 return mStatus; 259 } 260 setStatus(GceStatus status)261 public void setStatus(GceStatus status) { 262 mStatus = status; 263 } 264 addBuildVar(String buildKey, String buildValue)265 private void addBuildVar(String buildKey, String buildValue) { 266 mBuildVars.put(buildKey, buildValue); 267 } 268 setIpPreconfigured(boolean isIpPreconfigured)269 public void setIpPreconfigured(boolean isIpPreconfigured) { 270 mIsIpPreconfigured = isIpPreconfigured; 271 } 272 isIpPreconfigured()273 public boolean isIpPreconfigured() { 274 return mIsIpPreconfigured; 275 } 276 setDeviceOffset(Integer deviceOffset)277 public void setDeviceOffset(Integer deviceOffset) { 278 mDeviceOffset = deviceOffset; 279 } 280 getDeviceOffset()281 public Integer getDeviceOffset() { 282 return mDeviceOffset; 283 } 284 setInstanceUser(String instanceUser)285 public void setInstanceUser(String instanceUser) { 286 mInstanceUser = instanceUser; 287 } 288 getInstanceUser()289 public String getInstanceUser() { 290 return mInstanceUser; 291 } 292 293 /** 294 * Return build variable information hash of GCE AVD device. 295 * 296 * <p>Possible build variables keys are described in BUILD_VARS for example: build_id, 297 * build_target, branch, kernel_build_id, kernel_build_target, kernel_branch, system_build_id, 298 * system_build_target, system_branch, emulator_build_id, emulator_build_target, 299 * emulator_branch. 300 */ getBuildVars()301 public HashMap<String, String> getBuildVars() { 302 return new HashMap<String, String>(mBuildVars); 303 } 304 getSkipDeviceLogCollection()305 public boolean getSkipDeviceLogCollection() { 306 return mSkipDeviceLogCollection; 307 } 308 309 // TODO(b/329150949): Remove after lab update setSkipBugreportCollection(boolean skipDeviceLogCollection)310 public void setSkipBugreportCollection(boolean skipDeviceLogCollection) { 311 mSkipDeviceLogCollection = skipDeviceLogCollection; 312 } 313 setSkipDeviceLogCollection(boolean skipDeviceLogCollection)314 public void setSkipDeviceLogCollection(boolean skipDeviceLogCollection) { 315 mSkipDeviceLogCollection = skipDeviceLogCollection; 316 } 317 318 /** 319 * Parse a given file to obtain the GCE AVD device info. 320 * 321 * @param f {@link File} file to read the JSON output from GCE Driver. 322 * @param descriptor the descriptor of the device that needs the info. 323 * @param remoteAdbPort the remote port that should be used for adb connection 324 * @return the {@link GceAvdInfo} of the device if found, or null if error. 325 */ parseGceInfoFromFile( File f, DeviceDescriptor descriptor, int remoteAdbPort)326 public static GceAvdInfo parseGceInfoFromFile( 327 File f, DeviceDescriptor descriptor, int remoteAdbPort) throws TargetSetupError { 328 String data; 329 try { 330 data = Files.readString(f.toPath(), StandardCharsets.UTF_8); 331 } catch (IOException e) { 332 CLog.e("Failed to read result file from GCE driver:"); 333 CLog.e(e); 334 return null; 335 } 336 return parseGceInfoFromString(data, descriptor, remoteAdbPort); 337 } 338 339 /** 340 * Parse a given string to obtain the GCE AVD device info. 341 * 342 * @param data JSON string. 343 * @param descriptor the descriptor of the device that needs the info. 344 * @param remoteAdbPort the remote port that should be used for adb connection 345 * @return the {@link GceAvdInfo} of the device if found, or null if error. 346 */ parseGceInfoFromString( String data, DeviceDescriptor descriptor, int remoteAdbPort)347 public static GceAvdInfo parseGceInfoFromString( 348 String data, DeviceDescriptor descriptor, int remoteAdbPort) throws TargetSetupError { 349 if (Strings.isNullOrEmpty(data)) { 350 CLog.w("No data provided"); 351 return null; 352 } 353 InfraErrorIdentifier errorId = null; 354 String errors = data; 355 try { 356 errors = parseErrorField(data); 357 JSONObject res = new JSONObject(data); 358 String status = res.getString("status"); 359 GceStatus gceStatus = GceStatus.valueOf(status); 360 String errorType = res.has("error_type") ? res.getString("error_type") : null; 361 if (errorType == null) { 362 // Parse more detailed error type if we can. 363 if (errors.contains("QUOTA_EXCEED") && errors.contains("GPU")) { 364 errorType = "ACLOUD_QUOTA_EXCEED_GPU"; 365 } 366 } 367 errorId = 368 GceStatus.SUCCESS.equals(gceStatus) 369 ? null 370 : determineAcloudErrorType(errorType); 371 if (errorId == InfraErrorIdentifier.ACLOUD_OXYGEN_LEASE_ERROR) { 372 errorId = refineOxygenErrorType(errors); 373 } 374 JSONArray devices = null; 375 if (GceStatus.FAIL.equals(gceStatus) || GceStatus.BOOT_FAIL.equals(gceStatus)) { 376 // In case of failure we still look for instance name to shutdown if needed. 377 if (res.getJSONObject("data").has("devices_failing_boot")) { 378 devices = res.getJSONObject("data").getJSONArray("devices_failing_boot"); 379 } 380 } else { 381 devices = res.getJSONObject("data").getJSONArray("devices"); 382 } 383 if (devices != null) { 384 if (devices.length() == 1) { 385 JSONObject d = (JSONObject) devices.get(0); 386 addCfStartTimeMetrics(d); 387 String ip = d.getString("ip"); 388 String instanceName = d.getString("instance_name"); 389 GceAvdInfo avdInfo = 390 new GceAvdInfo( 391 instanceName, 392 HostAndPort.fromString(ip).withDefaultPort(remoteAdbPort), 393 errorId, 394 errors, 395 gceStatus); 396 avdInfo.mLogs.addAll(parseLogField(d)); 397 for (String buildVar : BUILD_VARS) { 398 if (d.has(buildVar) && !d.getString(buildVar).trim().isEmpty()) { 399 avdInfo.addBuildVar(buildVar, d.getString(buildVar).trim()); 400 } 401 } 402 return avdInfo; 403 } else { 404 CLog.w("Expected only one device to return but found %d", devices.length()); 405 } 406 } else { 407 CLog.w("No device information, device was not started."); 408 } 409 } catch (JSONException e) { 410 CLog.e("Failed to parse JSON %s:", data); 411 CLog.e(e); 412 } 413 414 // If errors are found throw an exception with the acloud message. 415 if (errorId == null) { 416 errorId = InfraErrorIdentifier.ACLOUD_UNDETERMINED; 417 } 418 throw new TargetSetupError( 419 String.format("acloud errors: %s", !errors.isEmpty() ? errors : data), 420 descriptor, 421 errorId); 422 } 423 424 /** 425 * Parse a given command line output from Oxygen client binary to obtain leased AVD info. 426 * 427 * @param oxygenRes the {@link CommandResult} from Oxygen client command execution. 428 * @param remoteAdbPort the remote port that should be used for adb connection 429 * @return {@link List} of the devices successfully leased. Will throw {@link TargetSetupError} 430 * if failed to lease a device. 431 */ parseGceInfoFromOxygenClientOutput( CommandResult oxygenRes, int remoteAdbPort)432 public static List<GceAvdInfo> parseGceInfoFromOxygenClientOutput( 433 CommandResult oxygenRes, int remoteAdbPort) throws TargetSetupError { 434 CommandStatus oxygenCliStatus = oxygenRes.getStatus(); 435 if (CommandStatus.SUCCESS.equals(oxygenCliStatus)) { 436 return parseSucceedOxygenClientOutput( 437 oxygenRes.getStdout() + oxygenRes.getStderr(), remoteAdbPort); 438 } else if (CommandStatus.TIMED_OUT.equals(oxygenCliStatus)) { 439 return Arrays.asList( 440 new GceAvdInfo( 441 null, 442 null, 443 InfraErrorIdentifier.OXYGEN_CLIENT_BINARY_TIMEOUT, 444 "Oxygen client binary CLI timed out", 445 GceStatus.FAIL)); 446 } else { 447 CLog.d( 448 "OxygenClient - CommandStatus: %s, output: %s\", output", 449 oxygenCliStatus, oxygenRes.getStdout() + " " + oxygenRes.getStderr()); 450 InfraErrorIdentifier identifier = refineOxygenErrorType(oxygenRes.getStderr()); 451 throw new TargetSetupError( 452 OXYGEN_ERROR_MESSAGE_MAP.getOrDefault( 453 identifier, "Oxygen client failed to lease a device"), 454 new Exception( 455 oxygenRes.getStderr()), // Include the original error message as cause. 456 identifier); 457 } 458 } 459 parseSucceedOxygenClientOutput(String output, int remoteAdbPort)460 private static List<GceAvdInfo> parseSucceedOxygenClientOutput(String output, int remoteAdbPort) 461 throws TargetSetupError { 462 CLog.d("Parsing oxygen client output: %s", output); 463 464 Pattern pattern = 465 Pattern.compile( 466 "session_id:\"(.*?)\".*?server_url:\"(.*?)\".*?oxygen_version:\"(.*?)\"", 467 Pattern.DOTALL); 468 Matcher matcher = pattern.matcher(output); 469 470 List<GceAvdInfo> gceAvdInfos = new ArrayList<>(); 471 int deviceOffset = 0; 472 while (matcher.find()) { 473 String sessionId = matcher.group(1); 474 String serverUrl = matcher.group(2); 475 String oxygenVersion = matcher.group(3); 476 gceAvdInfos.add( 477 new GceAvdInfo( 478 sessionId, 479 HostAndPort.fromString(serverUrl) 480 .withDefaultPort(remoteAdbPort + deviceOffset), 481 null, 482 null, 483 GceStatus.SUCCESS)); 484 InvocationMetricLogger.addInvocationMetrics( 485 InvocationMetricKey.CF_OXYGEN_VERSION, oxygenVersion); 486 deviceOffset++; 487 } 488 if (gceAvdInfos.isEmpty()) { 489 throw new TargetSetupError( 490 String.format("Failed to parse the output: %s", output), 491 InfraErrorIdentifier.OXYGEN_CLIENT_BINARY_ERROR); 492 } 493 494 return gceAvdInfos; 495 } 496 497 /** 498 * Search error message from Oxygen service for more accurate error code. 499 * 500 * @param errors error messages returned by Oxygen service. 501 * @return InfraErrorIdentifier for the Oxygen service error. 502 */ 503 @VisibleForTesting refineOxygenErrorType(String errors)504 static InfraErrorIdentifier refineOxygenErrorType(String errors) { 505 for (Map.Entry<InfraErrorIdentifier, String> entry : OXYGEN_ERROR_PATTERN_MAP.entrySet()) { 506 if (errors.contains(entry.getValue())) 507 return entry.getKey(); 508 } 509 510 return InfraErrorIdentifier.ACLOUD_OXYGEN_LEASE_ERROR; 511 } 512 parseErrorField(String data)513 private static String parseErrorField(String data) throws JSONException { 514 String res = ""; 515 JSONObject response = new JSONObject(data); 516 JSONArray errors = response.getJSONArray("errors"); 517 for (int i = 0; i < errors.length(); i++) { 518 res += (errors.getString(i) + "\n"); 519 } 520 return res; 521 } 522 523 /** 524 * Parse log paths from a device object. 525 * 526 * @param device the device object in JSON. 527 * @return a list of {@link LogFileEntry}. 528 * @throws JSONException if any required property is missing. 529 */ parseLogField(JSONObject device)530 private static List<LogFileEntry> parseLogField(JSONObject device) throws JSONException { 531 List<LogFileEntry> logs = new ArrayList<LogFileEntry>(); 532 JSONArray logArray = device.optJSONArray("logs"); 533 if (logArray == null) { 534 return logs; 535 } 536 for (int i = 0; i < logArray.length(); i++) { 537 JSONObject logObject = logArray.getJSONObject(i); 538 logs.add(new LogFileEntry(logObject)); 539 } 540 return logs; 541 } 542 543 @VisibleForTesting determineAcloudErrorType(String errorType)544 static InfraErrorIdentifier determineAcloudErrorType(String errorType) { 545 InfraErrorIdentifier identifier; 546 if (errorType == null || errorType.isEmpty()) { 547 return InfraErrorIdentifier.ACLOUD_UNRECOGNIZED_ERROR_TYPE; 548 } 549 try { 550 identifier = InfraErrorIdentifier.valueOf(errorType); 551 } catch (Exception e) { 552 identifier = InfraErrorIdentifier.ACLOUD_UNRECOGNIZED_ERROR_TYPE; 553 } 554 return identifier; 555 } 556 557 @VisibleForTesting addCfStartTimeMetrics(JSONObject json)558 static void addCfStartTimeMetrics(JSONObject json) { 559 // These metrics may not be available for all GCE. 560 String fetch_artifact_time = json.optString("fetch_artifact_time"); 561 if (!fetch_artifact_time.isEmpty()) { 562 InvocationMetricLogger.addInvocationMetrics( 563 InvocationMetricKey.CF_FETCH_ARTIFACT_TIME, 564 Double.valueOf(Double.parseDouble(fetch_artifact_time) * 1000).longValue()); 565 } 566 String gce_create_time = json.optString("gce_create_time"); 567 if (!gce_create_time.isEmpty()) { 568 InvocationMetricLogger.addInvocationMetrics( 569 InvocationMetricKey.CF_GCE_CREATE_TIME, 570 Double.valueOf(Double.parseDouble(gce_create_time) * 1000).longValue()); 571 } 572 String launch_cvd_time = json.optString("launch_cvd_time"); 573 if (!launch_cvd_time.isEmpty()) { 574 InvocationMetricLogger.addInvocationMetrics( 575 InvocationMetricKey.CF_LAUNCH_CVD_TIME, 576 Double.valueOf(Double.parseDouble(launch_cvd_time) * 1000).longValue()); 577 } 578 JSONObject fetch_cvd_wrapper_log = json.optJSONObject("fetch_cvd_wrapper_log"); 579 if (fetch_cvd_wrapper_log != null) { 580 String cf_cache_wait_time_sec = 581 fetch_cvd_wrapper_log.optString("cf_cache_wait_time_sec"); 582 if (!Strings.isNullOrEmpty(cf_cache_wait_time_sec)) { 583 InvocationMetricLogger.addInvocationMetrics( 584 InvocationMetricKey.CF_CACHE_WAIT_TIME, 585 Integer.parseInt(cf_cache_wait_time_sec)); 586 } 587 String cf_artifacts_fetch_source = 588 fetch_cvd_wrapper_log.optString("cf_artifacts_fetch_source"); 589 if (!Strings.isNullOrEmpty(cf_artifacts_fetch_source)) { 590 InvocationMetricLogger.addInvocationMetrics( 591 InvocationMetricKey.CF_ARTIFACTS_FETCH_SOURCE, cf_artifacts_fetch_source); 592 } 593 } 594 595 if (!InvocationMetricLogger.getInvocationMetrics() 596 .containsKey(InvocationMetricKey.CF_INSTANCE_COUNT.toString())) { 597 InvocationMetricLogger.addInvocationMetrics(InvocationMetricKey.CF_INSTANCE_COUNT, 1); 598 } 599 } 600 } 601