blob: 0f80fec1472c2731b4b99edcb557a28e9e5d49a0 [file] [log] [blame]
Brett Chabotf7ad1562010-07-27 11:58:58 -07001/*
2 * Copyright (C) 2010 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 */
Brett Chabot33513a52011-11-09 18:20:04 -080016
Brett Chabotf7ad1562010-07-27 11:58:58 -070017package com.android.tradefed.command;
18
Omari Stephens0f921222011-10-20 19:40:07 -070019import com.android.ddmlib.DdmPreferences;
Brett Chabotf7ad1562010-07-27 11:58:58 -070020import com.android.ddmlib.Log;
Brett Chabot73029de2010-08-12 14:06:07 -070021import com.android.ddmlib.Log.LogLevel;
Brett Chabot9c27c902013-09-27 11:00:56 -070022import com.android.tradefed.command.remote.RemoteClient;
23import com.android.tradefed.command.remote.RemoteManager;
Brett Chabotf7ad1562010-07-27 11:58:58 -070024import com.android.tradefed.config.ConfigurationException;
25import com.android.tradefed.config.ConfigurationFactory;
Steve Moyer98632ab2013-09-10 16:52:37 -070026import com.android.tradefed.config.GlobalConfiguration;
Brett Chabotf7ad1562010-07-27 11:58:58 -070027import com.android.tradefed.config.IConfiguration;
28import com.android.tradefed.config.IConfigurationFactory;
Brett Chabotf7ad1562010-07-27 11:58:58 -070029import com.android.tradefed.device.DeviceNotAvailableException;
30import com.android.tradefed.device.DeviceUnresponsiveException;
31import com.android.tradefed.device.IDeviceManager;
Brett Chabotd850c482011-02-25 14:17:26 -080032import com.android.tradefed.device.IDeviceManager.FreeDeviceState;
Brett Chabot0eabebe2011-09-12 16:15:42 -070033import com.android.tradefed.device.ITestDevice;
Brett Chabot1c829182011-01-12 19:49:15 -080034import com.android.tradefed.invoker.IRescheduler;
Brett Chabotf7ad1562010-07-27 11:58:58 -070035import com.android.tradefed.invoker.ITestInvocation;
36import com.android.tradefed.invoker.TestInvocation;
Omari Stephens0f921222011-10-20 19:40:07 -070037import com.android.tradefed.log.LogRegistry;
Omari Stephens463dda62011-11-21 17:21:42 -080038import com.android.tradefed.log.LogUtil.CLog;
Brett Chabot33513a52011-11-09 18:20:04 -080039import com.android.tradefed.util.ArrayUtil;
Brett Chabotc5ea26e2010-08-09 19:45:40 -070040import com.android.tradefed.util.ConditionPriorityBlockingQueue;
Brett Chabot9f2c1a82012-06-01 16:43:38 -070041import com.android.tradefed.util.TableFormatter;
Brett Chabotf7ad1562010-07-27 11:58:58 -070042
Brett Chabot33513a52011-11-09 18:20:04 -080043import java.io.IOException;
Brett Chabot9f2c1a82012-06-01 16:43:38 -070044import java.io.PrintWriter;
Brett Chabotf7ad1562010-07-27 11:58:58 -070045import java.util.ArrayList;
Brett Chabotcb2dcdd2011-11-23 09:18:45 -080046import java.util.Arrays;
Brett Chabotc6026f02011-03-02 20:23:33 -080047import java.util.Collections;
Brett Chabotf7ad1562010-07-27 11:58:58 -070048import java.util.Comparator;
49import java.util.HashSet;
Brett Chabot9f2c1a82012-06-01 16:43:38 -070050import java.util.LinkedHashSet;
Brett Chabotc6026f02011-03-02 20:23:33 -080051import java.util.LinkedList;
Brett Chabotf7ad1562010-07-27 11:58:58 -070052import java.util.List;
Brett Chabot9f2c1a82012-06-01 16:43:38 -070053import java.util.ListIterator;
Brett Chabotf7ad1562010-07-27 11:58:58 -070054import java.util.Set;
Omari Stephens4c217832012-04-19 18:58:30 -070055import java.util.concurrent.CountDownLatch;
Brett Chabotc6026f02011-03-02 20:23:33 -080056import java.util.concurrent.ScheduledThreadPoolExecutor;
57import java.util.concurrent.TimeUnit;
Brett Chabotf7ad1562010-07-27 11:58:58 -070058
59/**
Brett Chabot4e6f3672011-03-02 17:43:21 -080060 * A scheduler for running TradeFederation commands across all available devices.
Brett Chabotf7ad1562010-07-27 11:58:58 -070061 * <p/>
Brett Chabot33513a52011-11-09 18:20:04 -080062 * Will attempt to prioritize commands to run based on a total running count of their execution
63 * time. e.g. infrequent or fast running commands will get prioritized over long running commands.
Brett Chabotf7ad1562010-07-27 11:58:58 -070064 * <p/>
65 * Runs forever in background until shutdown.
66 */
Brett Chabot0283cb42010-08-07 16:12:17 -070067public class CommandScheduler extends Thread implements ICommandScheduler {
Brett Chabotf7ad1562010-07-27 11:58:58 -070068
Brett Chabot4e6f3672011-03-02 17:43:21 -080069 /** the queue of commands ready to be executed. */
Brett Chabotcd842052011-04-14 10:47:58 -070070 private ConditionPriorityBlockingQueue<ExecutableCommand> mCommandQueue;
Brett Chabot9f2c1a82012-06-01 16:43:38 -070071
Brett Chabotc6026f02011-03-02 20:23:33 -080072 /**
Brett Chabot9f2c1a82012-06-01 16:43:38 -070073 * The thread-safe list of all active executable commands.
Brett Chabotc6026f02011-03-02 20:23:33 -080074 */
Brett Chabot9f2c1a82012-06-01 16:43:38 -070075 private List<ExecutableCommand> mAllCommands;
76
Brett Chabot33513a52011-11-09 18:20:04 -080077 /** list of active invocation threads */
Brett Chabotf7ad1562010-07-27 11:58:58 -070078 private Set<InvocationThread> mInvocationThreads;
79
Brett Chabotc6026f02011-03-02 20:23:33 -080080 /** timer for scheduling commands to be re-queued for execution */
81 private ScheduledThreadPoolExecutor mCommandTimer;
Brett Chabot9f2c1a82012-06-01 16:43:38 -070082
Brett Chabot33513a52011-11-09 18:20:04 -080083 private RemoteClient mRemoteClient = null;
84 private RemoteManager mRemoteManager = null;
Brett Chabotc6026f02011-03-02 20:23:33 -080085
Omari Stephens4c217832012-04-19 18:58:30 -070086 /** latch used to notify other threads that this thread is running */
87 private final CountDownLatch mRunLatch;
88
Brett Chabotc6026f02011-03-02 20:23:33 -080089 /**
90 * Delay time in ms for adding a command back to the queue if it failed to allocate a device.
91 */
92 private static final int NO_DEVICE_DELAY_TIME = 20;
Brett Chabotf7ad1562010-07-27 11:58:58 -070093
Brett Chabot9f2c1a82012-06-01 16:43:38 -070094 /** used to assign unique ids to each CommandTracker created */
95 private int mCurrentCommandId = 0;
96
Brett Chabot29058a92013-01-10 09:40:48 -080097 /** flag for instructing scheduler to exit when no commands are present */
98 private boolean mShutdownOnEmpty = false;
99
Brett Chabot9f2c1a82012-06-01 16:43:38 -0700100 private enum CommandState {
101 WAITING_FOR_DEVICE("Wait_for_device"),
102 EXECUTING("Executing"),
103 SLEEPING("Sleeping");
104
105 private String mDisplayName;
106
107 CommandState(String displayName) {
108 mDisplayName = displayName;
109 }
110
111 public String getDisplayName() {
112 return mDisplayName;
113 }
114 }
115
Brett Chabotf7ad1562010-07-27 11:58:58 -0700116 /**
Brett Chabot33513a52011-11-09 18:20:04 -0800117 * Represents one active command added to the scheduler. Will track total execution time of all
118 * instances of this command
Brett Chabotf7ad1562010-07-27 11:58:58 -0700119 */
Brett Chabot9f2c1a82012-06-01 16:43:38 -0700120 private static class CommandTracker {
121 private final int mId;
Brett Chabot2d845512010-08-10 13:06:06 -0700122 private final String[] mArgs;
Brett Chabotf7ad1562010-07-27 11:58:58 -0700123
Brett Chabot4e6f3672011-03-02 17:43:21 -0800124 /** the total amount of time this command was executing. Used to prioritize */
Brett Chabotf7ad1562010-07-27 11:58:58 -0700125 private long mTotalExecTime = 0;
126
Brett Chabote9d7c4f2013-02-14 10:24:18 -0800127 CommandTracker(int id, String[] args) {
Brett Chabot9f2c1a82012-06-01 16:43:38 -0700128 mId = id;
Brett Chabotf7ad1562010-07-27 11:58:58 -0700129 mArgs = args;
Brett Chabotf7ad1562010-07-27 11:58:58 -0700130 }
131
132 synchronized void incrementExecTime(long execTime) {
133 mTotalExecTime += execTime;
134 }
Brett Chabot2d845512010-08-10 13:06:06 -0700135
136 /**
Brett Chabotcd842052011-04-14 10:47:58 -0700137 * @return the total amount of execution time for this command.
Brett Chabot2d845512010-08-10 13:06:06 -0700138 */
Brett Chabotcd842052011-04-14 10:47:58 -0700139 synchronized long getTotalExecTime() {
140 return mTotalExecTime;
Brett Chabot2d845512010-08-10 13:06:06 -0700141 }
142
143 /**
144 * Get the full list of config arguments associated with this command.
145 */
146 String[] getArgs() {
147 return mArgs;
148 }
Brett Chabot1c829182011-01-12 19:49:15 -0800149
Brett Chabot9f2c1a82012-06-01 16:43:38 -0700150 int getId() {
151 return mId;
152 }
Brett Chabot1c829182011-01-12 19:49:15 -0800153 }
154
155 /**
Brett Chabotcd842052011-04-14 10:47:58 -0700156 * Represents one instance of a command to be executed.
Brett Chabot1c829182011-01-12 19:49:15 -0800157 */
Brett Chabotcd842052011-04-14 10:47:58 -0700158 private class ExecutableCommand {
159 private final CommandTracker mCmdTracker;
160 private final IConfiguration mConfig;
Brett Chabot9f2c1a82012-06-01 16:43:38 -0700161 private final boolean mRescheduled;
162 private final long mCreationTime;
163 private CommandState mState;
164 private Long mSleepTime;
Brett Chabot1c829182011-01-12 19:49:15 -0800165
Brett Chabot9f2c1a82012-06-01 16:43:38 -0700166 private ExecutableCommand(CommandTracker tracker, IConfiguration config,
167 boolean rescheduled) {
Brett Chabot1c829182011-01-12 19:49:15 -0800168 mConfig = config;
Brett Chabotcd842052011-04-14 10:47:58 -0700169 mCmdTracker = tracker;
Brett Chabot9f2c1a82012-06-01 16:43:38 -0700170 mRescheduled = rescheduled;
171 mCreationTime = System.currentTimeMillis();
172 mState = CommandState.WAITING_FOR_DEVICE;
Brett Chabot1c829182011-01-12 19:49:15 -0800173 }
174
Brett Chabotcd842052011-04-14 10:47:58 -0700175 /**
176 * Gets the {@link IConfiguration} for this command instance
177 */
Brett Chabot33513a52011-11-09 18:20:04 -0800178 public IConfiguration getConfiguration() {
Brett Chabot1c829182011-01-12 19:49:15 -0800179 return mConfig;
180 }
181
Brett Chabotcd842052011-04-14 10:47:58 -0700182 /**
183 * Gets the associated {@link CommandTracker}.
184 */
185 CommandTracker getCommandTracker() {
186 return mCmdTracker;
Brett Chabot1c829182011-01-12 19:49:15 -0800187 }
Brett Chabot552f8fd2011-06-06 19:26:05 -0700188
189 /**
190 * Callback to inform listener that command has started execution.
191 */
192 void commandStarted() {
Brett Chabot9f2c1a82012-06-01 16:43:38 -0700193 mState = CommandState.EXECUTING;
194 mSleepTime = null;
Brett Chabot552f8fd2011-06-06 19:26:05 -0700195 }
Brett Chabot9f2c1a82012-06-01 16:43:38 -0700196
197 public void commandFinished(long elapsedTime) {
198 getCommandTracker().incrementExecTime(elapsedTime);
199 mAllCommands.remove(this);
200 }
201
202 public boolean isRescheduled() {
203 return mRescheduled;
204 }
205
206 public long getCreationTime() {
207 return mCreationTime;
208 }
209
210 public boolean isLoopMode() {
211 return mConfig.getCommandOptions().isLoopMode();
212 }
213
214 public CommandState getState() {
215 return mState;
216 }
217
218 public void setSleepState(Long delayTime) {
219 mSleepTime = delayTime;
220 mState = CommandState.SLEEPING;
221 }
222
223 public void setWaitState() {
224 mState = CommandState.WAITING_FOR_DEVICE;
225 mSleepTime = null;
226 }
227
228 public Long getSleepTime() {
229 return mSleepTime;
230 }
Brett Chabot1c829182011-01-12 19:49:15 -0800231 }
232
233 /**
Brett Chabot4e6f3672011-03-02 17:43:21 -0800234 * A {@link IRescheduler} that will add a command back to the queue.
Brett Chabot1c829182011-01-12 19:49:15 -0800235 */
236 private class Rescheduler implements IRescheduler {
237
Brett Chabotcd842052011-04-14 10:47:58 -0700238 private CommandTracker mCmdTracker;
Brett Chabot1c829182011-01-12 19:49:15 -0800239
Brett Chabotcd842052011-04-14 10:47:58 -0700240 Rescheduler(CommandTracker cmdTracker) {
241 mCmdTracker = cmdTracker;
Brett Chabot1c829182011-01-12 19:49:15 -0800242 }
243
244 /**
245 * {@inheritDoc}
246 */
247 @Override
248 public boolean scheduleConfig(IConfiguration config) {
Brett Chabot014fac82012-06-01 16:45:21 -0700249 // force loop mode to off, otherwise each rescheduled config will be treated as
250 // a new command and added back to queue
251 config.getCommandOptions().setLoopMode(false);
Brett Chabot9f2c1a82012-06-01 16:43:38 -0700252 ExecutableCommand rescheduledCmd = createExecutableCommand(mCmdTracker, config, true);
Brett Chabotcd842052011-04-14 10:47:58 -0700253 return addExecCommandToQueue(rescheduledCmd, 0);
Brett Chabot1c829182011-01-12 19:49:15 -0800254 }
Eric Rowea3b85852012-07-16 15:07:49 -0700255
256 /**
257 * {@inheritDoc}
258 */
259 @Override
260 public boolean rescheduleCommand() {
261 try {
262 IConfiguration config = getConfigFactory().createConfigurationFromArgs(
263 mCmdTracker.getArgs());
264 ExecutableCommand execCmd = createExecutableCommand(mCmdTracker, config, true);
Eric Rowedba4d372013-03-08 16:06:38 -0800265 return addExecCommandToQueue(execCmd, config.getCommandOptions().getMinLoopTime());
Eric Rowea3b85852012-07-16 15:07:49 -0700266 } catch (ConfigurationException e) {
267 // FIXME: do this with jline somehow for ANSI support
268 // note: make sure not to log (aka record) this line, as (args) may contain
269 // passwords.
270 System.out.println(String.format("Error while processing args: %s",
271 Arrays.toString(mCmdTracker.getArgs())));
272 System.out.println(e.getMessage());
273 System.out.println();
274 return false;
275 }
276 }
Brett Chabotf7ad1562010-07-27 11:58:58 -0700277 }
278
279 /**
Brett Chabot33513a52011-11-09 18:20:04 -0800280 * Comparator for {@link ExecutableCommand}.
Brett Chabotf7ad1562010-07-27 11:58:58 -0700281 * <p/>
Eric Rowea3b85852012-07-16 15:07:49 -0700282 * Delegates to {@link CommandTrackerTimeComparator}.
Brett Chabotf7ad1562010-07-27 11:58:58 -0700283 */
Brett Chabot33513a52011-11-09 18:20:04 -0800284 private static class ExecutableCommandComparator implements Comparator<ExecutableCommand> {
Brett Chabot9f2c1a82012-06-01 16:43:38 -0700285 CommandTrackerTimeComparator mTrackerComparator = new CommandTrackerTimeComparator();
Brett Chabotf7ad1562010-07-27 11:58:58 -0700286
287 /**
288 * {@inheritDoc}
289 */
Brett Chabot683ee432011-12-08 11:43:50 -0800290 @Override
Brett Chabotcd842052011-04-14 10:47:58 -0700291 public int compare(ExecutableCommand c1, ExecutableCommand c2) {
Brett Chabot33513a52011-11-09 18:20:04 -0800292 return mTrackerComparator.compare(c1.getCommandTracker(), c2.getCommandTracker());
293 }
294 }
295
296 /**
297 * Comparator for {@link CommandTracker}.
298 * <p/>
299 * Compares by mTotalExecTime, prioritizing configs with lower execution time
300 */
Brett Chabot9f2c1a82012-06-01 16:43:38 -0700301 private static class CommandTrackerTimeComparator implements Comparator<CommandTracker> {
Brett Chabot33513a52011-11-09 18:20:04 -0800302
303 @Override
304 public int compare(CommandTracker c1, CommandTracker c2) {
305 if (c1.getTotalExecTime() == c2.getTotalExecTime()) {
Brett Chabotf7ad1562010-07-27 11:58:58 -0700306 return 0;
Brett Chabot33513a52011-11-09 18:20:04 -0800307 } else if (c1.getTotalExecTime() < c2.getTotalExecTime()) {
Brett Chabotf7ad1562010-07-27 11:58:58 -0700308 return -1;
309 } else {
310 return 1;
311 }
312 }
313 }
314
Brett Chabot9f2c1a82012-06-01 16:43:38 -0700315 /**
316 * Comparator for {@link CommandTracker}.
317 * <p/>
318 * Compares by id.
319 */
320 private static class CommandTrackerIdComparator implements Comparator<CommandTracker> {
321
322 @Override
323 public int compare(CommandTracker c1, CommandTracker c2) {
324 if (c1.getId() == c2.getId()) {
325 return 0;
326 } else if (c1.getId() < c2.getId()) {
327 return -1;
328 } else {
329 return 1;
330 }
331 }
332 }
333
Brett Chabotf7ad1562010-07-27 11:58:58 -0700334 private class InvocationThread extends Thread {
Brett Chabotc6026f02011-03-02 20:23:33 -0800335 private final IDeviceManager mManager;
336 private final ITestDevice mDevice;
Brett Chabotcd842052011-04-14 10:47:58 -0700337 private final ExecutableCommand mCmd;
Brett Chabotc0554a62013-01-31 18:38:19 -0800338 private final ITestInvocation mInvocation;
Omari Stephens1ee376d2011-10-20 18:48:55 -0700339 private long mStartTime = -1;
Brett Chabotf7ad1562010-07-27 11:58:58 -0700340
Brett Chabotc6026f02011-03-02 20:23:33 -0800341 public InvocationThread(String name, IDeviceManager manager, ITestDevice device,
Brett Chabotcd842052011-04-14 10:47:58 -0700342 ExecutableCommand command) {
Brett Chabotf7ad1562010-07-27 11:58:58 -0700343 // create a thread group so LoggerRegistry can identify this as an invocationThread
Brett Chabot33513a52011-11-09 18:20:04 -0800344 super(new ThreadGroup(name), name);
Brett Chabotf7ad1562010-07-27 11:58:58 -0700345 mManager = manager;
346 mDevice = device;
Brett Chabotc6026f02011-03-02 20:23:33 -0800347 mCmd = command;
Omari Stephensa9a43172011-03-05 18:02:00 -0800348 mInvocation = createRunInstance();
Brett Chabotf7ad1562010-07-27 11:58:58 -0700349 }
350
Omari Stephens1ee376d2011-10-20 18:48:55 -0700351 public long getStartTime() {
352 return mStartTime;
353 }
354
Brett Chabotf7ad1562010-07-27 11:58:58 -0700355 @Override
356 public void run() {
Brett Chabotf7ad1562010-07-27 11:58:58 -0700357 FreeDeviceState deviceState = FreeDeviceState.AVAILABLE;
Omari Stephens1ee376d2011-10-20 18:48:55 -0700358 mStartTime = System.currentTimeMillis();
Brett Chabotc0554a62013-01-31 18:38:19 -0800359 ITestInvocation instance = getInvocation();
Brett Chabotcd842052011-04-14 10:47:58 -0700360 IConfiguration config = mCmd.getConfiguration();
Brett Chabotf7ad1562010-07-27 11:58:58 -0700361 try {
Brett Chabot552f8fd2011-06-06 19:26:05 -0700362 mCmd.commandStarted();
Brett Chabotcd842052011-04-14 10:47:58 -0700363 instance.invoke(mDevice, config, new Rescheduler(mCmd.getCommandTracker()));
Brett Chabotf7ad1562010-07-27 11:58:58 -0700364 } catch (DeviceUnresponsiveException e) {
Brett Chabotcb2dcdd2011-11-23 09:18:45 -0800365 CLog.w("Device %s is unresponsive. Reason: %s", mDevice.getSerialNumber(),
366 e.getMessage());
Brett Chabotf7ad1562010-07-27 11:58:58 -0700367 deviceState = FreeDeviceState.UNRESPONSIVE;
368 } catch (DeviceNotAvailableException e) {
Brett Chabotcb2dcdd2011-11-23 09:18:45 -0800369 CLog.w("Device %s is not available. Reason: %s", mDevice.getSerialNumber(),
370 e.getMessage());
Brett Chabotf7ad1562010-07-27 11:58:58 -0700371 deviceState = FreeDeviceState.UNAVAILABLE;
Brett Chabota17af5d2010-08-23 13:51:17 -0700372 } catch (FatalHostError e) {
Brett Chabotcb2dcdd2011-11-23 09:18:45 -0800373 CLog.logAndDisplay(LogLevel.ERROR, "Fatal error occurred: %s, shutting down",
374 e.getMessage());
Brett Chabota17af5d2010-08-23 13:51:17 -0700375 if (e.getCause() != null) {
Brett Chabotcb2dcdd2011-11-23 09:18:45 -0800376 CLog.e(e.getCause());
Brett Chabota17af5d2010-08-23 13:51:17 -0700377 }
378 shutdown();
Brett Chabotf7ad1562010-07-27 11:58:58 -0700379 } catch (Throwable e) {
Brett Chabotcb2dcdd2011-11-23 09:18:45 -0800380 CLog.e(e);
Brett Chabotf7ad1562010-07-27 11:58:58 -0700381 } finally {
Omari Stephens1ee376d2011-10-20 18:48:55 -0700382 long elapsedTime = System.currentTimeMillis() - mStartTime;
Brett Chabotcb2dcdd2011-11-23 09:18:45 -0800383 CLog.i("Updating command '%s' with elapsed time %d ms",
384 getArgString(mCmd.getCommandTracker().getArgs()), elapsedTime);
Brett Chabot9f2c1a82012-06-01 16:43:38 -0700385 mCmd.commandFinished(elapsedTime);
Brett Chabotf7ad1562010-07-27 11:58:58 -0700386 mManager.freeDevice(mDevice, deviceState);
Brett Chabot33513a52011-11-09 18:20:04 -0800387 remoteFreeDevice(mDevice);
Brett Chabotf7ad1562010-07-27 11:58:58 -0700388 removeInvocationThread(this);
389 }
390 }
391
Brett Chabotc0554a62013-01-31 18:38:19 -0800392 ITestInvocation getInvocation() {
Brett Chabotf7ad1562010-07-27 11:58:58 -0700393 return mInvocation;
394 }
Brett Chabot9f2c1a82012-06-01 16:43:38 -0700395
396 ITestDevice getDevice() {
397 return mDevice;
398 }
Brett Chabotf7ad1562010-07-27 11:58:58 -0700399 }
400
401 /**
Brett Chabotccf00ea2010-08-26 16:04:33 -0700402 * Creates a {@link CommandScheduler}.
Omari Stephens3c1f4dd2011-10-24 15:12:18 -0700403 * <p />
Brett Chabot33513a52011-11-09 18:20:04 -0800404 * Note: logging is initialized here. We assume that {@link CommandScheduler#start} will be
Omari Stephens3c1f4dd2011-10-24 15:12:18 -0700405 * called, so that we can clean logs up at the end of the {@link CommandScheduler#run} method.
406 * In particular, this means that a leak will result if a {@link CommandScheduler} instance is
407 * instantiated but not started.
Brett Chabotf7ad1562010-07-27 11:58:58 -0700408 */
Omari Stephens5f750842011-04-01 12:28:13 -0700409 public CommandScheduler() {
Omari Stephens3c1f4dd2011-10-24 15:12:18 -0700410 initLogging();
411
Omari Stephensf9060ec2012-11-27 19:52:39 -0800412 initDeviceManager();
Omari Stephens3c1f4dd2011-10-24 15:12:18 -0700413
Brett Chabotcd842052011-04-14 10:47:58 -0700414 mCommandQueue = new ConditionPriorityBlockingQueue<ExecutableCommand>(
Brett Chabot33513a52011-11-09 18:20:04 -0800415 new ExecutableCommandComparator());
Brett Chabot9f2c1a82012-06-01 16:43:38 -0700416 mAllCommands = Collections.synchronizedList(new LinkedList<ExecutableCommand>());
Brett Chabotf7ad1562010-07-27 11:58:58 -0700417 mInvocationThreads = new HashSet<InvocationThread>();
Brett Chabotc6026f02011-03-02 20:23:33 -0800418 // use a ScheduledThreadPoolExecutorTimer as a single-threaded timer. This class
419 // is used instead of a java.util.Timer because it offers advanced shutdown options
420 mCommandTimer = new ScheduledThreadPoolExecutor(1);
Omari Stephens4c217832012-04-19 18:58:30 -0700421 mRunLatch = new CountDownLatch(1);
Brett Chabotf7ad1562010-07-27 11:58:58 -0700422 }
423
424 /**
Brett Chabotb50ceb82012-05-11 12:45:15 -0700425 * Initialize the device manager, optionally using a global device filter if specified.
426 */
Omari Stephensf9060ec2012-11-27 19:52:39 -0800427 void initDeviceManager() {
428 getDeviceManager().init();
Brett Chabotb50ceb82012-05-11 12:45:15 -0700429 }
430
431 /**
Brett Chabotf7ad1562010-07-27 11:58:58 -0700432 * Factory method for creating a {@link TestInvocation}.
433 *
434 * @return the {@link ITestInvocation} to use
435 */
436 ITestInvocation createRunInstance() {
437 return new TestInvocation();
438 }
439
440 /**
441 * Factory method for getting a reference to the {@link IDeviceManager}
442 *
443 * @return the {@link IDeviceManager} to use
444 */
445 IDeviceManager getDeviceManager() {
Steve Moyer98632ab2013-09-10 16:52:37 -0700446 return GlobalConfiguration.getDeviceManagerInstance();
Brett Chabotf7ad1562010-07-27 11:58:58 -0700447 }
448
449 /**
450 * Factory method for getting a reference to the {@link IConfigurationFactory}
451 *
452 * @return the {@link IConfigurationFactory} to use
453 */
454 IConfigurationFactory getConfigFactory() {
455 return ConfigurationFactory.getInstance();
456 }
457
458 /**
459 * The main execution block of this thread.
460 */
461 @Override
462 public void run() {
Omari Stephens4c217832012-04-19 18:58:30 -0700463 try {
464 // Notify other threads that we're running.
465 mRunLatch.countDown();
Brett Chabotc6026f02011-03-02 20:23:33 -0800466
Omari Stephens4c217832012-04-19 18:58:30 -0700467 IDeviceManager manager = getDeviceManager();
468 while (!isShutdown()) {
469 ExecutableCommand cmd = dequeueConfigCommand();
470 if (cmd != null) {
471 ITestDevice device = manager.allocateDevice(0, cmd.getConfiguration()
472 .getDeviceRequirements());
473 if (device != null) {
474 // Spawn off a thread to perform the invocation
475 InvocationThread invThread = startInvocation(manager, device, cmd);
476 addInvocationThread(invThread);
Brett Chabot9f2c1a82012-06-01 16:43:38 -0700477 if (cmd.isLoopMode()) {
Omari Stephens4c217832012-04-19 18:58:30 -0700478 addNewExecCommandToQueue(cmd.getCommandTracker());
479 }
480 } else {
481 // no device available for command, put back in queue
482 // increment exec time to ensure fair scheduling among commands when devices
483 // are scarce
484 cmd.getCommandTracker().incrementExecTime(1);
485 addExecCommandToQueue(cmd, NO_DEVICE_DELAY_TIME);
Brett Chabotc6026f02011-03-02 20:23:33 -0800486 }
Brett Chabotc6026f02011-03-02 20:23:33 -0800487 }
Brett Chabotf7ad1562010-07-27 11:58:58 -0700488 }
Brett Chabot29058a92013-01-10 09:40:48 -0800489 mCommandTimer.shutdown();
Omari Stephens4c217832012-04-19 18:58:30 -0700490 CLog.i("Waiting for invocation threads to complete");
491 List<InvocationThread> threadListCopy;
492 synchronized (this) {
493 threadListCopy = new ArrayList<InvocationThread>(mInvocationThreads.size());
494 threadListCopy.addAll(mInvocationThreads);
495 }
496 for (Thread thread : threadListCopy) {
497 waitForThread(thread);
498 }
499 closeRemoteClient();
500 if (mRemoteManager != null) {
501 mRemoteManager.cancel();
502 }
503 exit(manager);
504 cleanUp();
505 CLog.logAndDisplay(LogLevel.INFO, "All done");
506 } finally {
507 // Make sure that we don't quit with messages still in the buffers
508 System.err.flush();
509 System.out.flush();
Brett Chabotf7ad1562010-07-27 11:58:58 -0700510 }
Omari Stephens4c217832012-04-19 18:58:30 -0700511 }
512
513 /**
514 * {@inheritDoc}
515 */
516 @Override
517 public void await() throws InterruptedException {
518 while (mRunLatch.getCount() > 0) {
519 mRunLatch.await();
Brett Chabotccf00ea2010-08-26 16:04:33 -0700520 }
Brett Chabot33513a52011-11-09 18:20:04 -0800521 }
522
523 private void closeRemoteClient() {
524 if (mRemoteClient != null) {
525 try {
Brett Chabot33513a52011-11-09 18:20:04 -0800526 mRemoteClient.sendClose();
527 mRemoteClient.close();
528 } catch (IOException e) {
529 // ignore
530 }
531 }
Brett Chabotf7ad1562010-07-27 11:58:58 -0700532 }
533
Brett Chabotccf00ea2010-08-26 16:04:33 -0700534 private void waitForThread(Thread thread) {
535 try {
536 thread.join();
537 } catch (InterruptedException e) {
538 // ignore
539 waitForThread(thread);
540 }
541 }
542
Brett Chabotf7ad1562010-07-27 11:58:58 -0700543 private void exit(IDeviceManager manager) {
544 if (manager != null) {
545 manager.terminate();
546 }
547 }
548
549 /**
Brett Chabot0283cb42010-08-07 16:12:17 -0700550 * {@inheritDoc}
Brett Chabotf7ad1562010-07-27 11:58:58 -0700551 */
Brett Chabote7e335e2011-02-16 14:51:03 -0800552 @Override
Brett Chabot552f8fd2011-06-06 19:26:05 -0700553 public boolean addCommand(String[] args) {
Brett Chabote9d7c4f2013-02-14 10:24:18 -0800554 return addCommand(args, 0);
Brett Chabot33513a52011-11-09 18:20:04 -0800555 }
Brett Chabot552f8fd2011-06-06 19:26:05 -0700556
557 /**
558 * {@inheritDoc}
559 */
560 @Override
Brett Chabote9d7c4f2013-02-14 10:24:18 -0800561 public boolean addCommand(String[] args, long totalExecTime) {
Brett Chabotf7ad1562010-07-27 11:58:58 -0700562 try {
Brett Chabotcd842052011-04-14 10:47:58 -0700563 IConfiguration config = getConfigFactory().createConfigurationFromArgs(args);
564 if (config.getCommandOptions().isHelpMode()) {
Brett Chabot77de04a2011-04-25 12:59:26 -0700565 getConfigFactory().printHelpForConfig(args, true, System.out);
566 } else if (config.getCommandOptions().isFullHelpMode()) {
567 getConfigFactory().printHelpForConfig(args, false, System.out);
Omari Stephens463dda62011-11-21 17:21:42 -0800568 } else if (config.getCommandOptions().isDryRunMode()) {
Omari Stephense2109e92013-02-21 18:49:33 -0800569 if (config.getCommandOptions().isNoisyDryRunMode()) {
570 CLog.logAndDisplay(LogLevel.DEBUG, "DRY RUN: %s", Arrays.toString(args));
571 } else {
572 CLog.d("Dry run mode; skipping adding command: %s", Arrays.toString(args));
573 }
Brett Chabotf7ad1562010-07-27 11:58:58 -0700574 } else {
Brett Chabot0dc54f42012-08-20 12:15:20 -0700575 config.validateOptions();
Brett Chabote9d7c4f2013-02-14 10:24:18 -0800576
Brett Chabot249ef612013-02-05 16:24:26 -0800577 if (config.getCommandOptions().runOnAllDevices()) {
Brett Chabote9d7c4f2013-02-14 10:24:18 -0800578 addCommandForAllDevices(totalExecTime, args);
Brett Chabot249ef612013-02-05 16:24:26 -0800579 } else {
Brett Chabote9d7c4f2013-02-14 10:24:18 -0800580 CommandTracker cmdTracker = createCommandTracker(args);
581 cmdTracker.incrementExecTime(totalExecTime);
Brett Chabot249ef612013-02-05 16:24:26 -0800582 ExecutableCommand cmdInstance = createExecutableCommand(cmdTracker, config, false);
583 addExecCommandToQueue(cmdInstance, 0);
584 }
Brett Chabot552f8fd2011-06-06 19:26:05 -0700585 return true;
Brett Chabotf7ad1562010-07-27 11:58:58 -0700586 }
587 } catch (ConfigurationException e) {
Omari Stephens3e7f86a2011-11-14 19:58:18 -0800588 // FIXME: do this with jline somehow for ANSI support
Omari Stephens8bda56e2011-11-21 17:21:42 -0800589 // note: make sure not to log (aka record) this line, as (args) may contain passwords.
590 System.out.println(String.format("Error while processing args: %s",
591 Arrays.toString(args)));
Omari Stephens3e7f86a2011-11-14 19:58:18 -0800592 System.out.println(e.getMessage());
593 System.out.println();
Brett Chabotf7ad1562010-07-27 11:58:58 -0700594 }
Brett Chabot552f8fd2011-06-06 19:26:05 -0700595 return false;
Brett Chabotf7ad1562010-07-27 11:58:58 -0700596 }
597
598 /**
Brett Chabot249ef612013-02-05 16:24:26 -0800599 * Creates a new command for each connected device, and adds each to the queue.
600 * <p/>
601 * Note this won't have the desired effect if user has specified other
602 * conflicting {@link IConfiguration#getDeviceRequirements()}in the command.
603 */
Brett Chabote9d7c4f2013-02-14 10:24:18 -0800604 private void addCommandForAllDevices(long totalExecTime, String[] args)
Brett Chabot249ef612013-02-05 16:24:26 -0800605 throws ConfigurationException {
606 Set<String> devices = new HashSet<String>();
607 devices.addAll(getDeviceManager().getAvailableDevices());
608 devices.addAll(getDeviceManager().getAllocatedDevices());
609 // schedule for for unavailable devices, just in case they come back online
610 devices.addAll(getDeviceManager().getUnavailableDevices());
611
612 for (String device : devices) {
Brett Chabote9d7c4f2013-02-14 10:24:18 -0800613 String[] argsWithDevice = Arrays.copyOf(args, args.length + 2);
614 argsWithDevice[argsWithDevice.length - 2] = "-s";
615 argsWithDevice[argsWithDevice.length - 1] = device;
616 CommandTracker cmdTracker = createCommandTracker(argsWithDevice);
617 cmdTracker.incrementExecTime(totalExecTime);
Brett Chabot249ef612013-02-05 16:24:26 -0800618 IConfiguration config = getConfigFactory().createConfigurationFromArgs(
619 cmdTracker.getArgs());
620 CLog.logAndDisplay(LogLevel.INFO, "Scheduling '%s' on '%s'", cmdTracker.getArgs()[0],
621 device);
622 config.getDeviceRequirements().setSerial(device);
623 ExecutableCommand execCmd = createExecutableCommand(cmdTracker, config, false);
624 addExecCommandToQueue(execCmd, 0);
625 }
626 }
627
628 /**
Brett Chabot9f2c1a82012-06-01 16:43:38 -0700629 * Creates a new {@link CommandTracker} with a unique id.
630 */
Brett Chabote9d7c4f2013-02-14 10:24:18 -0800631 private synchronized CommandTracker createCommandTracker(String[] args) {
Brett Chabot9f2c1a82012-06-01 16:43:38 -0700632 mCurrentCommandId++;
Brett Chabote9d7c4f2013-02-14 10:24:18 -0800633 return new CommandTracker(mCurrentCommandId, args);
Brett Chabot9f2c1a82012-06-01 16:43:38 -0700634 }
635
636 /**
637 * Creates a new {@link ExecutableCommand}, and adds it to the all commands tracking list.
638 */
639 private ExecutableCommand createExecutableCommand(CommandTracker cmdTracker,
640 IConfiguration config, boolean rescheduled) {
641 ExecutableCommand cmd = new ExecutableCommand(cmdTracker, config, rescheduled);
Brett Chabot0e8e0b52013-08-13 18:04:49 -0700642 CLog.d("adding command");
Brett Chabot9f2c1a82012-06-01 16:43:38 -0700643 mAllCommands.add(cmd);
644 return cmd;
645 }
646
647 /**
Brett Chabotc6026f02011-03-02 20:23:33 -0800648 * Dequeue the highest priority command from the queue.
Brett Chabotf7ad1562010-07-27 11:58:58 -0700649 *
Brett Chabotcd842052011-04-14 10:47:58 -0700650 * @return the {@link ExecutableCommand} or <code>null</code>
Brett Chabotf7ad1562010-07-27 11:58:58 -0700651 */
Brett Chabotcd842052011-04-14 10:47:58 -0700652 private ExecutableCommand dequeueConfigCommand() {
Brett Chabotf7ad1562010-07-27 11:58:58 -0700653 try {
Brett Chabotcd842052011-04-14 10:47:58 -0700654 // poll for a command, rather than block indefinitely, to handle shutdown case
Brett Chabotc6026f02011-03-02 20:23:33 -0800655 return mCommandQueue.poll(getCommandPollTimeMs(), TimeUnit.MILLISECONDS);
Brett Chabotf7ad1562010-07-27 11:58:58 -0700656 } catch (InterruptedException e) {
Brett Chabotcb2dcdd2011-11-23 09:18:45 -0800657 CLog.i("Waiting for command interrupted");
Brett Chabotf7ad1562010-07-27 11:58:58 -0700658 }
Brett Chabotc6026f02011-03-02 20:23:33 -0800659 return null;
660 }
661
662 /**
Brett Chabot9f2c1a82012-06-01 16:43:38 -0700663 * Get the poll time to wait to retrieve a command to execute.
Brett Chabotc6026f02011-03-02 20:23:33 -0800664 * <p/>
665 * Exposed so unit tests can mock.
Brett Chabotc6026f02011-03-02 20:23:33 -0800666 */
667 long getCommandPollTimeMs() {
668 return 1000;
Brett Chabotf7ad1562010-07-27 11:58:58 -0700669 }
670
671 /**
Brett Chabotcd842052011-04-14 10:47:58 -0700672 * Creates a new {@link ExecutableCommand}, and adds it to queue
Brett Chabot33513a52011-11-09 18:20:04 -0800673 *
Brett Chabotcd842052011-04-14 10:47:58 -0700674 * @param commandTracker
675 */
676 private void addNewExecCommandToQueue(CommandTracker commandTracker) {
677 try {
678 IConfiguration config = getConfigFactory().createConfigurationFromArgs(
679 commandTracker.getArgs());
Brett Chabot9f2c1a82012-06-01 16:43:38 -0700680 ExecutableCommand execCmd = createExecutableCommand(commandTracker, config, false);
681 addExecCommandToQueue(execCmd, config.getCommandOptions().getMinLoopTime());
Brett Chabotcd842052011-04-14 10:47:58 -0700682 } catch (ConfigurationException e) {
Brett Chabotcb2dcdd2011-11-23 09:18:45 -0800683 CLog.e(e);
Brett Chabotcd842052011-04-14 10:47:58 -0700684 }
685 }
686
687 /**
Brett Chabot9f2c1a82012-06-01 16:43:38 -0700688 * Adds executable command instance to queue, with optional delay.
Brett Chabotf7ad1562010-07-27 11:58:58 -0700689 *
Brett Chabotcd842052011-04-14 10:47:58 -0700690 * @param cmd the {@link ExecutableCommand} to return to queue
Brett Chabotc6026f02011-03-02 20:23:33 -0800691 * @param delayTime the time in ms to delay before adding command to queue
Brett Chabotcd842052011-04-14 10:47:58 -0700692 * @return <code>true</code> if command will be added to queue, <code>false</code> otherwise
Brett Chabotf7ad1562010-07-27 11:58:58 -0700693 */
Brett Chabot9f2c1a82012-06-01 16:43:38 -0700694 private synchronized boolean addExecCommandToQueue(final ExecutableCommand cmd,
695 long delayTime) {
Brett Chabot919d2c52011-03-03 19:20:53 -0800696 if (isShutdown()) {
Brett Chabotcd842052011-04-14 10:47:58 -0700697 return false;
Brett Chabot919d2c52011-03-03 19:20:53 -0800698 }
Brett Chabot552f8fd2011-06-06 19:26:05 -0700699 if (delayTime > 0) {
Brett Chabot9f2c1a82012-06-01 16:43:38 -0700700 cmd.setSleepState(delayTime);
701 // delay before making command active
Brett Chabot552f8fd2011-06-06 19:26:05 -0700702 Runnable delayCommand = new Runnable() {
703 @Override
704 public void run() {
Brett Chabot9f2c1a82012-06-01 16:43:38 -0700705 synchronized (CommandScheduler.this) {
706 cmd.setWaitState();
707 mCommandQueue.add(cmd);
708 }
Brett Chabot552f8fd2011-06-06 19:26:05 -0700709 }
710 };
711 mCommandTimer.schedule(delayCommand, delayTime, TimeUnit.MILLISECONDS);
712 } else {
Brett Chabot552f8fd2011-06-06 19:26:05 -0700713 mCommandQueue.add(cmd);
Brett Chabotf7ad1562010-07-27 11:58:58 -0700714 }
Brett Chabot552f8fd2011-06-06 19:26:05 -0700715 return true;
Brett Chabotf7ad1562010-07-27 11:58:58 -0700716 }
717
718 /**
719 * Helper method to return an array of {@link String} elements as a readable {@link String}
720 *
721 * @param args the {@link String}[] to use
722 * @return a display friendly {@link String} of args contents
723 */
724 private String getArgString(String[] args) {
Brett Chabot33513a52011-11-09 18:20:04 -0800725 return ArrayUtil.join(" ", (Object[])args);
Brett Chabotf7ad1562010-07-27 11:58:58 -0700726 }
727
728 /**
Brett Chabot4e6f3672011-03-02 17:43:21 -0800729 * Spawns off thread to run invocation for given device
Brett Chabotf7ad1562010-07-27 11:58:58 -0700730 *
731 * @param manager the {@link IDeviceManager} to return device to when complete
732 * @param device the {@link ITestDevice}
Brett Chabotcd842052011-04-14 10:47:58 -0700733 * @param cmd the {@link ExecutableCommand} to execute
Brett Chabotf7ad1562010-07-27 11:58:58 -0700734 * @return the invocation's thread
735 */
Brett Chabotc6026f02011-03-02 20:23:33 -0800736 private InvocationThread startInvocation(IDeviceManager manager, ITestDevice device,
Brett Chabotcd842052011-04-14 10:47:58 -0700737 ExecutableCommand cmd) {
Brett Chabotf7ad1562010-07-27 11:58:58 -0700738 final String invocationName = String.format("Invocation-%s", device.getSerialNumber());
Brett Chabotc6026f02011-03-02 20:23:33 -0800739 InvocationThread invocationThread = new InvocationThread(invocationName, manager, device,
740 cmd);
Brett Chabotf7ad1562010-07-27 11:58:58 -0700741 invocationThread.start();
742 return invocationThread;
743 }
744
745 /**
746 * Removes a {@link InvocationThread} from the active list.
747 */
748 private synchronized void removeInvocationThread(InvocationThread invThread) {
749 mInvocationThreads.remove(invThread);
750 }
751
752 /**
753 * Adds a {@link InvocationThread} to the active list.
754 */
755 private synchronized void addInvocationThread(InvocationThread invThread) {
756 mInvocationThreads.add(invThread);
757 }
758
Brett Chabotccf00ea2010-08-26 16:04:33 -0700759 private synchronized boolean isShutdown() {
Brett Chabot29058a92013-01-10 09:40:48 -0800760 return mCommandTimer.isShutdown() || (mShutdownOnEmpty && mAllCommands.isEmpty());
Brett Chabotccf00ea2010-08-26 16:04:33 -0700761 }
762
Brett Chabot0e8e0b52013-08-13 18:04:49 -0700763 private synchronized boolean isShuttingDown() {
764 return mCommandTimer.isShutdown() || mShutdownOnEmpty;
765 }
766
Brett Chabotf7ad1562010-07-27 11:58:58 -0700767 /**
Brett Chabot0283cb42010-08-07 16:12:17 -0700768 * {@inheritDoc}
Brett Chabotf7ad1562010-07-27 11:58:58 -0700769 */
Brett Chabot683ee432011-12-08 11:43:50 -0800770 @Override
Brett Chabotf7ad1562010-07-27 11:58:58 -0700771 public synchronized void shutdown() {
Brett Chabot0e8e0b52013-08-13 18:04:49 -0700772 if (!isShuttingDown()) {
773 CLog.d("initiating shutdown");
Brett Chabot9f2c1a82012-06-01 16:43:38 -0700774 clearWaitingCommands();
Brett Chabot4e6f3672011-03-02 17:43:21 -0800775 if (mCommandTimer != null) {
Brett Chabotc6026f02011-03-02 20:23:33 -0800776 mCommandTimer.shutdownNow();
Brett Chabota17af5d2010-08-23 13:51:17 -0700777 }
Brett Chabotf7ad1562010-07-27 11:58:58 -0700778 }
779 }
Omari Stephensae6ffbf2010-08-09 21:44:41 -0700780
Brett Chabotd850c482011-02-25 14:17:26 -0800781 /**
782 * {@inheritDoc}
783 */
Brett Chabot683ee432011-12-08 11:43:50 -0800784 @Override
Brett Chabot29058a92013-01-10 09:40:48 -0800785 public synchronized void shutdownOnEmpty() {
Brett Chabot0e8e0b52013-08-13 18:04:49 -0700786 if (!isShuttingDown()) {
787 CLog.d("initiating shutdown on empty");
Brett Chabot29058a92013-01-10 09:40:48 -0800788 mShutdownOnEmpty = true;
789 }
790 }
791
792 /**
793 * {@inheritDoc}
794 */
795 @Override
Brett Chabot33ab1112012-01-24 14:32:39 -0800796 public synchronized void removeAllCommands() {
Brett Chabot33ab1112012-01-24 14:32:39 -0800797 if (mCommandTimer != null) {
798 for (Runnable task : mCommandTimer.getQueue()) {
799 mCommandTimer.remove(task);
800 }
801 }
Brett Chabot9f2c1a82012-06-01 16:43:38 -0700802 clearWaitingCommands();
803 }
804
805 /**
806 * Clears all {@link ExecutableCommand} not currently executing.
807 */
Brett Chabot260d2412012-06-13 10:25:22 -0700808 private void clearWaitingCommands() {
Brett Chabot9f2c1a82012-06-01 16:43:38 -0700809 mCommandQueue.clear();
Brett Chabot260d2412012-06-13 10:25:22 -0700810 synchronized (mAllCommands) {
811 ListIterator<ExecutableCommand> cmdIter = mAllCommands.listIterator();
812 while (cmdIter.hasNext()) {
813 ExecutableCommand cmd = cmdIter.next();
814 if (!cmd.getState().equals(CommandState.EXECUTING)) {
815 cmdIter.remove();
816 }
Brett Chabot9f2c1a82012-06-01 16:43:38 -0700817 }
818 }
Brett Chabot33ab1112012-01-24 14:32:39 -0800819 }
820
821 /**
822 * {@inheritDoc}
823 */
824 @Override
Brett Chabot33513a52011-11-09 18:20:04 -0800825 public synchronized boolean handoverShutdown(int handoverPort) {
826 if (mRemoteClient != null) {
827 CLog.e("A handover has already been initiated");
828 return false;
829 }
830 try {
831 mRemoteClient = RemoteClient.connect(handoverPort);
832 CLog.d("connected to remote manager at %d", handoverPort);
833 // inform remote manager of the devices we are still using
834 for (String deviceInUse : getDeviceManager().getAllocatedDevices()) {
Brett Chabot9c27c902013-09-27 11:00:56 -0700835 if (!mRemoteClient.sendAllocateDevice(deviceInUse)) {
Brett Chabot33513a52011-11-09 18:20:04 -0800836 CLog.e("Failed to send command to remote manager");
837 return false;
838 }
839 CLog.d("Sent filter device %s command", deviceInUse);
840 }
841 // now send command info
Brett Chabot9f2c1a82012-06-01 16:43:38 -0700842 List<CommandTracker> cmdCopy = getCommandTrackers();
Brett Chabot33513a52011-11-09 18:20:04 -0800843 // sort so high priority commands are sent first
Brett Chabot9f2c1a82012-06-01 16:43:38 -0700844 Collections.sort(cmdCopy, new CommandTrackerTimeComparator());
Brett Chabot33513a52011-11-09 18:20:04 -0800845 for (CommandTracker cmd : cmdCopy) {
846 mRemoteClient.sendAddCommand(cmd.getTotalExecTime(), cmd.mArgs);
847 }
848 shutdown();
849 return true;
850
851 } catch (IOException e) {
852 CLog.e("Failed to connect to remote manager at port %d", handoverPort);
853 }
854 return false;
855 }
856
857 /**
Brett Chabot9f2c1a82012-06-01 16:43:38 -0700858 * @return the list of active {@link CommandTracker}. 'Active' here means all commands added
859 * to the scheduler that are either executing, waiting for a device to execute on, or looping.
860 */
861 private List<CommandTracker> getCommandTrackers() {
862 List<ExecutableCommand> cmdCopy = new ArrayList<ExecutableCommand>(mAllCommands);
863 Set<CommandTracker> cmdTrackers = new LinkedHashSet<CommandTracker>();
864 for (ExecutableCommand cmd : cmdCopy) {
865 cmdTrackers.add(cmd.getCommandTracker());
866 }
867 return new ArrayList<CommandTracker>(cmdTrackers);
868 }
869
870 /**
Brett Chabot33513a52011-11-09 18:20:04 -0800871 * Inform the remote listener of the freed device. Has no effect if there is no remote listener.
872 *
873 * @param device the freed {@link ITestDevice}
874 */
875 private void remoteFreeDevice(ITestDevice device) {
876 // TODO: send freed device state too
877 if (mRemoteClient != null) {
878 try {
Brett Chabot9c27c902013-09-27 11:00:56 -0700879 mRemoteClient.sendFreeDevice(device.getSerialNumber());
Brett Chabot33513a52011-11-09 18:20:04 -0800880 } catch (IOException e) {
881 CLog.e("Failed to send unfilter device %s to remote manager",
882 device.getSerialNumber());
883 }
884 }
885 }
886
887 /**
888 * {@inheritDoc}
889 */
890 @Override
Brett Chabotd850c482011-02-25 14:17:26 -0800891 public synchronized void shutdownHard() {
892 shutdown();
Brett Chabotcb2dcdd2011-11-23 09:18:45 -0800893 CLog.logAndDisplay(LogLevel.WARN, "Force killing adb connection");
Brett Chabotd850c482011-02-25 14:17:26 -0800894 getDeviceManager().terminateHard();
895 }
896
Omari Stephens0f921222011-10-20 19:40:07 -0700897 /**
898 * Initializes the ddmlib log.
899 * <p />
900 * Exposed so unit tests can mock.
901 */
902 void initLogging() {
903 DdmPreferences.setLogLevel(LogLevel.VERBOSE.getStringValue());
904 Log.setLogOutput(LogRegistry.getLogRegistry());
905 }
906
907 /**
908 * Closes the logs and does any other necessary cleanup before we quit.
909 * <p />
910 * Exposed so unit tests can mock.
911 */
912 void cleanUp() {
913 LogRegistry.getLogRegistry().closeAndRemoveAllLogs();
914 }
915
Omari Stephensae6ffbf2010-08-09 21:44:41 -0700916 /**
917 * {@inheritDoc}
918 */
Brett Chabot683ee432011-12-08 11:43:50 -0800919 @Override
Brett Chabot9f2c1a82012-06-01 16:43:38 -0700920 public void displayInvocationsInfo(PrintWriter printWriter) {
921 if (mInvocationThreads == null || mInvocationThreads.size() == 0) {
922 return;
Omari Stephensae6ffbf2010-08-09 21:44:41 -0700923 }
Brett Chabot9f2c1a82012-06-01 16:43:38 -0700924 List<InvocationThread> copy = new ArrayList<InvocationThread>(mInvocationThreads);
925 ArrayList<List<String>> displayRows = new ArrayList<List<String>>();
926 displayRows.add(Arrays.asList("Command Id", "Exec Time", "Device", "State"));
Omari Stephens1ee376d2011-10-20 18:48:55 -0700927 long curTime = System.currentTimeMillis();
928
Brett Chabot9f2c1a82012-06-01 16:43:38 -0700929 for (InvocationThread invThread : copy) {
930 displayRows.add(Arrays.asList(
931 Integer.toString(invThread.mCmd.getCommandTracker().getId()),
932 getTimeString(curTime - invThread.getStartTime()),
933 invThread.getDevice().getSerialNumber(),
934 invThread.getInvocation().toString()));
Omari Stephensae6ffbf2010-08-09 21:44:41 -0700935 }
Brett Chabot9f2c1a82012-06-01 16:43:38 -0700936 new TableFormatter().displayTable(displayRows, printWriter);
Omari Stephensae6ffbf2010-08-09 21:44:41 -0700937 }
938
Brett Chabot9f2c1a82012-06-01 16:43:38 -0700939 private String getTimeString(long elapsedTime) {
Brett Chabot33513a52011-11-09 18:20:04 -0800940 long duration = elapsedTime / 1000;
941 long secs = duration % 60;
942 long mins = (duration / 60) % 60;
943 long hrs = duration / (60 * 60);
944 String time = "unknown";
945 if (hrs > 0) {
946 time = String.format("%dh:%02d:%02d", hrs, mins, secs);
947 } else {
948 time = String.format("%dm:%02d", mins, secs);
949 }
Brett Chabot9f2c1a82012-06-01 16:43:38 -0700950 return time;
Brett Chabot33513a52011-11-09 18:20:04 -0800951 }
952
Omari Stephensae6ffbf2010-08-09 21:44:41 -0700953 /**
954 * {@inheritDoc}
955 */
Brett Chabot683ee432011-12-08 11:43:50 -0800956 @Override
Omari Stephensae6ffbf2010-08-09 21:44:41 -0700957 public boolean stopInvocation(ITestInvocation invocation) throws UnsupportedOperationException {
958 throw new UnsupportedOperationException();
959 }
Omari Stephens68a6e782010-08-18 18:19:51 -0700960
961 /**
962 * {@inheritDoc}
963 */
Brett Chabot683ee432011-12-08 11:43:50 -0800964 @Override
Brett Chabot9f2c1a82012-06-01 16:43:38 -0700965 public void displayCommandsInfo(PrintWriter printWriter) {
966 List<CommandTracker> cmds = getCommandTrackers();
967 Collections.sort(cmds, new CommandTrackerIdComparator());
968 for (CommandTracker cmd : cmds) {
969 String cmdDesc = String.format("Command %d: [%s] %s", cmd.getId(),
970 getTimeString(cmd.getTotalExecTime()), getArgString(cmd.getArgs()));
971 printWriter.println(cmdDesc);
Omari Stephens68a6e782010-08-18 18:19:51 -0700972 }
Brett Chabot9f2c1a82012-06-01 16:43:38 -0700973 }
974
975 /**
976 * {@inheritDoc}
977 */
978 @Override
979 public void displayCommandQueue(PrintWriter printWriter) {
980 if (mAllCommands.isEmpty()) {
981 return;
982 }
983 ArrayList<List<String>> displayRows = new ArrayList<List<String>>();
984 displayRows.add(Arrays.asList("Id", "Config", "Created", "State", "Sleep time",
985 "Rescheduled", "Loop"));
986 long curTime = System.currentTimeMillis();
987 List<ExecutableCommand> cmdCopy = new ArrayList<ExecutableCommand>(mAllCommands);
988 for (ExecutableCommand cmd : cmdCopy) {
989 dumpCommand(curTime, cmd, displayRows);
990 }
991 new TableFormatter().displayTable(displayRows, printWriter);
992 }
993
994 private void dumpCommand(long curTime, ExecutableCommand cmd,
995 ArrayList<List<String>> displayRows) {
996 String sleepTime = cmd.getSleepTime() == null ? "N/A" : getTimeString(cmd.getSleepTime());
997 displayRows.add(Arrays.asList(
998 Integer.toString(cmd.getCommandTracker().getId()),
999 cmd.getCommandTracker().getArgs()[0],
1000 getTimeString(curTime - cmd.getCreationTime()),
1001 cmd.getState().getDisplayName(),
1002 sleepTime,
1003 Boolean.toString(cmd.isRescheduled()),
1004 Boolean.toString(cmd.isLoopMode())));
Omari Stephens68a6e782010-08-18 18:19:51 -07001005 }
Brett Chabot33513a52011-11-09 18:20:04 -08001006
1007 /**
1008 * {@inheritDoc}
1009 */
1010 @Override
1011 public int startRemoteManager() {
1012 if (mRemoteManager != null && !mRemoteManager.isCanceled()) {
1013 CLog.logAndDisplay(LogLevel.INFO, "A remote manager is already running at port %d",
1014 mRemoteManager.getPort());
1015 return -1;
1016 }
1017 mRemoteManager = new RemoteManager(getDeviceManager(), this);
1018 mRemoteManager.start();
1019 int port = mRemoteManager.getPort();
1020 if (port == -1) {
1021 // set mRemoteManager to null so it can be started again if necessary
1022 mRemoteManager = null;
1023 }
1024 return port;
1025 }
1026
1027 /**
1028 * {@inheritDoc}
1029 */
1030 @Override
1031 public void stopRemoteManager() {
1032 if (mRemoteManager == null) {
1033 CLog.logAndDisplay(LogLevel.INFO, "A remote manager is not running");
1034 return;
1035 }
1036 mRemoteManager.cancel();
1037 mRemoteManager = null;
1038 }
Brett Chabotf7ad1562010-07-27 11:58:58 -07001039}