1 /* 2 * Copyright (C) 2011 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.invoker; 17 18 import com.android.tradefed.build.IBuildInfo; 19 import com.android.tradefed.log.LogUtil.CLog; 20 import com.android.tradefed.result.ILogSaver; 21 import com.android.tradefed.result.ILogSaverListener; 22 import com.android.tradefed.result.ITestInvocationListener; 23 import com.android.tradefed.result.InputStreamSource; 24 import com.android.tradefed.result.LogDataType; 25 import com.android.tradefed.result.LogFile; 26 import com.android.tradefed.result.LogSaverResultForwarder; 27 import com.android.tradefed.result.ResultForwarder; 28 import com.android.tradefed.util.TimeUtil; 29 30 import java.util.ArrayList; 31 import java.util.List; 32 import java.util.Map.Entry; 33 34 /** 35 * A {@link ResultForwarder} that combines the results of a sharded test invocations. It only 36 * reports completion of the invocation to the listeners once all sharded invocations are complete. 37 * 38 * <p>This class is not thread safe. It is expected that clients will lock on this class when 39 * sending test results, to prevent invocation callbacks from being called out of order. 40 */ 41 public class ShardMasterResultForwarder extends ResultForwarder implements ILogSaverListener { 42 43 private final int mInitCount; 44 private int mShardsRemaining; 45 private long mTotalElapsed = 0L; 46 private boolean mStartReported = false; 47 48 private long mFirstShardEndTime = 0L; 49 private IInvocationContext mOriginalContext; 50 private List<IInvocationContext> mShardContextList; 51 private int shardIndex = 0; 52 53 /** 54 * Create a {@link ShardMasterResultForwarder}. 55 * 56 * @param listeners the list of {@link ITestInvocationListener} to forward results to when all 57 * shards are completed 58 * @param expectedShards the number of shards 59 */ 60 public ShardMasterResultForwarder(List<ITestInvocationListener> listeners, int expectedShards) { 61 super(listeners); 62 mShardsRemaining = expectedShards; 63 mInitCount = expectedShards; 64 mShardContextList = new ArrayList<>(); 65 } 66 67 /** 68 * {@inheritDoc} 69 */ 70 @Override 71 public void invocationStarted(IInvocationContext context) { 72 if (!mStartReported) { 73 mOriginalContext = context; 74 super.invocationStarted(context); 75 mStartReported = true; 76 } else { 77 // Track serials used in each shard. 78 mOriginalContext.addSerialsFromShard(shardIndex, context.getSerials()); 79 mShardContextList.add(context); 80 shardIndex++; 81 } 82 } 83 84 /** 85 * {@inheritDoc} 86 */ 87 @Override 88 public void invocationFailed(Throwable cause) { 89 // one of the shards failed. Fail the whole invocation 90 // TODO: does any extra logging need to be done ? 91 super.invocationFailed(cause); 92 } 93 94 /** 95 * {@inheritDoc} 96 */ 97 @Override 98 public void invocationEnded(long elapsedTime) { 99 mTotalElapsed += elapsedTime; 100 if (mInitCount == mShardsRemaining) { 101 mFirstShardEndTime = System.currentTimeMillis(); 102 } 103 mShardsRemaining--; 104 if (mShardsRemaining <= 0) { 105 // TODO: consider logging all shard final times. 106 CLog.i( 107 "There was %s between the first and last shard ended.", 108 TimeUtil.formatElapsedTime(System.currentTimeMillis() - mFirstShardEndTime)); 109 copyShardBuildInfoToMain(mOriginalContext, mShardContextList); 110 super.invocationEnded(mTotalElapsed); 111 } 112 } 113 114 /** {@inheritDoc} */ 115 @Override 116 public void testLogSaved( 117 String dataName, LogDataType dataType, InputStreamSource dataStream, LogFile logFile) { 118 for (ITestInvocationListener listener : getListeners()) { 119 try { 120 // Forward the testLogSaved event to ILogSaverListener 121 if (listener instanceof ILogSaverListener) { 122 ((ILogSaverListener) listener) 123 .testLogSaved(dataName, dataType, dataStream, logFile); 124 } 125 } catch (Exception e) { 126 CLog.e("Exception while invoking %s#testLogSaved", listener.getClass().getName()); 127 CLog.e(e); 128 } 129 } 130 } 131 132 /** Only forward the testLog instead of saving the log first. */ 133 public void testLogForward( 134 String dataName, LogDataType dataType, InputStreamSource dataStream) { 135 for (ITestInvocationListener listener : getListeners()) { 136 if (listener instanceof LogSaverResultForwarder) { 137 // If the listener is a log saver, we should simply forward the testLog not save 138 // again. 139 ((LogSaverResultForwarder) listener).testLogForward(dataName, dataType, dataStream); 140 } else { 141 try { 142 listener.testLog(dataName, dataType, dataStream); 143 } catch (RuntimeException e) { 144 CLog.e( 145 "RuntimeException while invoking %s#testLog", 146 listener.getClass().getName()); 147 CLog.e(e); 148 } 149 } 150 } 151 } 152 153 /** {@inheritDoc} */ 154 @Override 155 public void logAssociation(String dataName, LogFile logFile) { 156 for (ITestInvocationListener listener : getListeners()) { 157 try { 158 // Forward the logAssociation call 159 if (listener instanceof ILogSaverListener) { 160 ((ILogSaverListener) listener).logAssociation(dataName, logFile); 161 } 162 } catch (RuntimeException e) { 163 CLog.e("Failed to provide the log association"); 164 CLog.e(e); 165 } 166 } 167 } 168 169 /** {@inheritDoc} */ 170 @Override 171 public void setLogSaver(ILogSaver logSaver) { 172 // Shard master does not need log saver. 173 } 174 175 /** 176 * Copy the build info from the shard builds to the main build in the original invocation 177 * context. 178 * 179 * @param main the original {@link IInvocationContext} from the main invocation. 180 * @param shardContexts the list of {@link IInvocationContext}s, one for each shard invocation. 181 */ 182 private void copyShardBuildInfoToMain( 183 IInvocationContext main, List<IInvocationContext> shardContexts) { 184 for (IInvocationContext shard : shardContexts) { 185 for (String deviceName : shard.getDeviceConfigNames()) { 186 IBuildInfo shardBuild = shard.getBuildInfo(deviceName); 187 IBuildInfo mainBuild = main.getBuildInfo(deviceName); 188 if (mainBuild != null) { 189 for (Entry<String, String> entry : shardBuild.getBuildAttributes().entrySet()) { 190 mainBuild.addBuildAttribute(entry.getKey(), entry.getValue()); 191 } 192 } else { 193 // Should not happen 194 CLog.e( 195 "Found a device '%s' in shard configuration but not in parent configuration.", 196 deviceName); 197 } 198 } 199 } 200 } 201 } 202