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