Home | History | Annotate | Download | only in invoker
      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