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.cluster; 17 18 import com.android.tradefed.log.LogUtil.CLog; 19 20 import org.json.JSONArray; 21 import org.json.JSONException; 22 import org.json.JSONObject; 23 24 import java.util.HashMap; 25 import java.util.HashSet; 26 import java.util.Map; 27 import java.util.Set; 28 29 /** A class to encapsulate cluster command events to be uploaded. */ 30 public class ClusterCommandEvent implements IClusterEvent { 31 32 public static final String DATA_KEY_ERROR = "error"; 33 public static final String DATA_KEY_SUMMARY = "summary"; 34 public static final String DATA_KEY_SETUP_TIME_MILLIS = "setup_time_millis"; 35 public static final String DATA_KEY_FETCH_BUILD_TIME_MILLIS = "fetch_build_time_millis"; 36 public static final String DATA_KEY_TOTAL_TEST_COUNT = "total_test_count"; 37 public static final String DATA_KEY_FAILED_TEST_COUNT = "failed_test_count"; 38 public static final String DATA_KEY_PASSED_TEST_COUNT = "passed_test_count"; 39 public static final String DATA_KEY_FAILED_TEST_RUN_COUNT = "failed_test_run_count"; 40 public static final String DATA_KEY_LOST_DEVICE_DETECTED = "device_lost_detected"; 41 public static final String DATA_KEY_SUBPROCESS_COMMAND_ERROR = "subprocess_command_error"; 42 public static final String DATA_KEY_ERROR_ID_NAME = "error_name"; 43 public static final String DATA_KEY_ERROR_ID_CODE = "error_code"; 44 public static final String DATA_KEY_ERROR_STATUS = "error_status"; 45 46 // Maximum size of an individual data string value. 47 public static final int MAX_DATA_STRING_SIZE = 4095; 48 49 public enum Type { 50 AllocationFailed, 51 ConfigurationError, 52 FetchFailed, 53 ExecuteFailed, 54 InvocationInitiated, 55 InvocationStarted, 56 InvocationFailed, 57 InvocationEnded, 58 InvocationCompleted, 59 TestRunInProgress, 60 TestEnded, 61 Unleased 62 } 63 64 private long mTimestamp; 65 private Type mType; 66 private String mCommandTaskId; 67 private String mAttemptId; 68 private String mHostName; 69 private InvocationStatus mInvocationStatus; 70 private Map<String, Object> mData = new HashMap<>(); 71 private Set<String> mDeviceSerials; 72 ClusterCommandEvent()73 private ClusterCommandEvent() {} 74 getHostName()75 public String getHostName() { 76 return mHostName; 77 } 78 getTimestamp()79 public long getTimestamp() { 80 return mTimestamp; 81 } 82 getType()83 public Type getType() { 84 return mType; 85 } 86 getCommandTaskId()87 public String getCommandTaskId() { 88 return mCommandTaskId; 89 } 90 getAttemptId()91 public String getAttemptId() { 92 return mAttemptId; 93 } 94 getInvocationStatus()95 public InvocationStatus getInvocationStatus() { 96 return mInvocationStatus; 97 } 98 getData()99 public Map<String, Object> getData() { 100 return mData; 101 } 102 getDeviceSerials()103 public Set<String> getDeviceSerials() { 104 return mDeviceSerials; 105 } 106 107 public static class Builder { 108 109 private long mTimestamp = System.currentTimeMillis(); 110 private Type mType; 111 private String mCommandTaskId; 112 private String mAttemptId; 113 private String mHostName; 114 private InvocationStatus mInvocationStatus; 115 private Map<String, Object> mData = new HashMap<>(); 116 private Set<String> mDeviceSerials = new HashSet<>(); 117 Builder()118 public Builder() {} 119 setTimestamp(final long timestamp)120 public Builder setTimestamp(final long timestamp) { 121 mTimestamp = timestamp; 122 return this; 123 } 124 setType(final Type type)125 public Builder setType(final Type type) { 126 mType = type; 127 return this; 128 } 129 setCommandTaskId(final String commandTaskId)130 public Builder setCommandTaskId(final String commandTaskId) { 131 mCommandTaskId = commandTaskId; 132 return this; 133 } 134 setAttemptId(final String attemptId)135 public Builder setAttemptId(final String attemptId) { 136 mAttemptId = attemptId; 137 return this; 138 } 139 setHostName(final String hostName)140 public Builder setHostName(final String hostName) { 141 mHostName = hostName; 142 return this; 143 } 144 setInvocationStatus(final InvocationStatus invocationStatus)145 public Builder setInvocationStatus(final InvocationStatus invocationStatus) { 146 mInvocationStatus = invocationStatus; 147 return this; 148 } 149 setData(final String name, final Object value)150 public Builder setData(final String name, final Object value) { 151 if (value instanceof String && ((String) value).length() > MAX_DATA_STRING_SIZE) { 152 CLog.w( 153 String.format( 154 "Data for '%s' exceeds %d characters, and has been truncated.", 155 name, MAX_DATA_STRING_SIZE)); 156 mData.put(name, ((String) value).substring(0, MAX_DATA_STRING_SIZE)); 157 } else { 158 mData.put(name, value); 159 } 160 return this; 161 } 162 setDeviceSerials(final Set<String> deviceSerials)163 public Builder setDeviceSerials(final Set<String> deviceSerials) { 164 mDeviceSerials = deviceSerials; 165 return this; 166 } 167 addDeviceSerial(final String deviceSerial)168 public Builder addDeviceSerial(final String deviceSerial) { 169 mDeviceSerials.add(deviceSerial); 170 return this; 171 } 172 build()173 public ClusterCommandEvent build() { 174 final ClusterCommandEvent obj = new ClusterCommandEvent(); 175 obj.mTimestamp = mTimestamp; 176 obj.mType = mType; 177 obj.mCommandTaskId = mCommandTaskId; 178 obj.mAttemptId = mAttemptId; 179 obj.mHostName = mHostName; 180 obj.mInvocationStatus = mInvocationStatus; 181 obj.mData = new HashMap<>(mData); 182 obj.mDeviceSerials = mDeviceSerials; 183 return obj; 184 } 185 } 186 187 /** 188 * Creates a base {@link Builder}. 189 * 190 * @return a {@link Builder}. 191 */ createEventBuilder()192 public static Builder createEventBuilder() { 193 return createEventBuilder(null); 194 } 195 196 /** 197 * Creates a base {@link Builder} for the given {@link ClusterCommand}. 198 * 199 * @return a {@link Builder}. 200 */ createEventBuilder(final ClusterCommand command)201 public static Builder createEventBuilder(final ClusterCommand command) { 202 final ClusterCommandEvent.Builder builder = new ClusterCommandEvent.Builder(); 203 if (command != null) { 204 builder.setCommandTaskId(command.getTaskId()); 205 builder.setAttemptId(command.getAttemptId()); 206 } 207 return builder; 208 } 209 210 /** {@inheritDoc} */ 211 @Override toJSON()212 public JSONObject toJSON() throws JSONException { 213 final JSONObject json = new JSONObject(); 214 json.put("type", this.getType().toString()); 215 // event time should be in POSIX timestamp. 216 json.put("time", this.getTimestamp() / 1000); 217 json.put("task_id", this.getCommandTaskId()); 218 json.put("attempt_id", this.getAttemptId()); 219 json.put("hostname", this.getHostName()); 220 // TODO(b/79583735): deprecated. 221 if (!this.getDeviceSerials().isEmpty()) { 222 json.put("device_serial", this.getDeviceSerials().iterator().next()); 223 } 224 json.put("device_serials", new JSONArray(this.getDeviceSerials())); 225 if (mInvocationStatus != null) { 226 json.put("invocation_status", mInvocationStatus.toJSON()); 227 } 228 json.put("data", new JSONObject(this.getData())); 229 return json; 230 } 231 232 @Override toString()233 public String toString() { 234 String str = null; 235 try { 236 str = toJSON().toString(); 237 } catch (final JSONException e) { 238 // ignore 239 } 240 return str; 241 } 242 } 243