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