All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.xceptance.xlt.engine.SessionImpl Maven / Gradle / Ivy

Go to download

XLT (Xceptance LoadTest) is an extensive load and performance test tool developed and maintained by Xceptance.

There is a newer version: 8.4.0
Show newest version
/*
 * Copyright (c) 2005-2022 Xceptance Software Technologies GmbH
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.xceptance.xlt.engine;

import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.NavigableMap;
import java.util.Timer;
import java.util.TimerTask;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;

import org.apache.commons.lang3.StringUtils;
import org.junit.runners.model.MultipleFailureException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.xceptance.common.io.FileUtils;
import com.xceptance.common.util.ParameterCheckUtils;
import com.xceptance.xlt.api.actions.AbstractAction;
import com.xceptance.xlt.api.engine.GlobalClock;
import com.xceptance.xlt.api.engine.NetworkDataManager;
import com.xceptance.xlt.api.engine.Session;
import com.xceptance.xlt.api.engine.SessionShutdownListener;
import com.xceptance.xlt.api.engine.TransactionData;
import com.xceptance.xlt.api.util.XltLogger;
import com.xceptance.xlt.api.util.XltProperties;
import com.xceptance.xlt.common.XltConstants;
import com.xceptance.xlt.engine.resultbrowser.ActionInfo;
import com.xceptance.xlt.engine.resultbrowser.RequestHistory;
import com.xceptance.xlt.engine.util.TimerUtils;

/**
 * The {@link SessionImpl} class represents one run of a certain test case. Multiple threads running the same test case
 * will get different sessions. A session is the anchor that holds all the pages requested during that very test run and
 * all statistics recorded. A session may be re-used across different test runs only if the session is cleared between
 * two test runs.
 * 
 * @author Jörg Werner (Xceptance Software Technologies GmbH)
 */
public class SessionImpl extends Session
{
    /**
     * Name of the collectAdditionalRequestInfo property.
     */
    private static final String PROP_COLLECT_ADDITIONAL_REQUEST_DATA = "com.xceptance.xlt.results.data.request.collectAdditionalRequestInfo";

    /**
     * The log facility.
     */
    private static final Logger LOG = LoggerFactory.getLogger(SessionImpl.class);

    /**
     * The result dir property key
     */
    private static final String RESULT_DIR_PROPERTY = XltConstants.XLT_PACKAGE_PATH + ".result-dir";

    /**
     * Constant for an unknown agent name.
     */
    private static final String UNKNOWN_AGENT_ID = "UnknownAgent";

    /**
     * Constant for an unknown user name.
     */
    private static final String UNKNOWN_USER_NAME = "UnknownUser";

    /**
     * The Session instances keyed by thread group.
     */
    private static final Map sessions = new ConcurrentHashMap(101);

    /**
     * The global transaction expiration timer responsible for all sessions.
     */
    private static final Timer transactionExpirationTimer;

    /**
     * The default transaction timeout [ms], currently 15 minutes.
     */
    private static final long DEFAULT_TRANSACTION_TIMEOUT = 15 * 60 * 1000;

    /**
     * The name of the transaction timeout property.
     */
    private static final String PROP_MAX_TRANSACTION_TIMEOUT = XltConstants.XLT_PACKAGE_PATH + ".maximumTransactionRunTime";

    /**
     * Global flag that controls whether or not additional request information should be collected and dumped to CSV.
     */
    public static final boolean COLLECT_ADDITIONAL_REQUEST_DATA;

    /**
     * Global flag that controls whether or not to remove user-info from request URLs.
     */
    public static final boolean REMOVE_USERINFO_FROM_REQUEST_URL;

    /**
     * Name of the removeUserInfoFromURL property.
     */
    private static final String PROP_REMOVE_USERINFO_FROM_REQUEST_URL = XltConstants.XLT_PACKAGE_PATH + ".results.data.request.removeUserInfoFromURL";

    static
    {
        final XltProperties props = XltProperties.getInstance();

        // initialize the run-away transaction killer facility if so configured
        if (props.getProperty(XltConstants.XLT_PACKAGE_PATH + ".abortLongRunningTransactions", false))
        {
            transactionExpirationTimer = new Timer("TransactionExpirationTimer", true);
        }
        else
        {
            transactionExpirationTimer = null;
        }

        COLLECT_ADDITIONAL_REQUEST_DATA = props.getProperty(PROP_COLLECT_ADDITIONAL_REQUEST_DATA, false);
        REMOVE_USERINFO_FROM_REQUEST_URL = props.getProperty(PROP_REMOVE_USERINFO_FROM_REQUEST_URL, true);
    }

    /**
     * Returns the Session instance for the calling thread. If no such instance exists yet, it will be created.
     * 
     * @return the Session instance for the calling thread
     */
    public static SessionImpl getCurrent()
    {
        return getSessionForThread(Thread.currentThread());
    }

    /**
     * Removes the Session instance for the calling thread. Typically, sessions are reused, so this method is especially
     * useful for testing purposes.
     * 
     * @return the Session instance just removed
     */
    public static SessionImpl removeCurrent()
    {
        return sessions.remove(Thread.currentThread().getThreadGroup());
    }

    /**
     * Returns the Session instance for the given thread. If no such instance exists yet, it will be created.
     * 
     * @return the Session instance for the given thread
     */
    public static SessionImpl getSessionForThread(final Thread thread)
    {
        final ThreadGroup threadGroup = thread.getThreadGroup();

        if (threadGroup == null)
        {
            // the thread died in between so there is no session
            return null;
        }
        else
        {
            SessionImpl s = sessions.get(threadGroup);

            if (s == null)
            {
                synchronized (threadGroup)
                {
                    // check again because two threads might have waited at the
                    // sync block and the first one created the session already
                    s = sessions.get(threadGroup);
                    if (s == null)
                    {
                        s = new SessionImpl();
                        sessions.put(threadGroup, s);
                        s.init();
                    }
                }
            }

            return s;
        }
    }

    /**
     * The absolute instance number of the current user.
     */
    private int absoluteUserNumber;

    /**
     * The ID of the current agent.
     */
    private String agentID;

    /**
     * The number (or index) of the current agent. This value ranges from 0...(n-1), where n denotes the total number of
     * configured agents.
     */
    private int agentNumber;

    /**
     * The session-specific request statistics.
     */
    private final DataManagerImpl dataManagerImpl;

    /**
     * Indicates whether or not there were errors during the session. This includes network problems as well as page
     * validation errors.
     */
    private boolean failed;

    /**
     * The fail reason (bound to {@link #failed}.
     */
    private Throwable t;

    /**
     * The session's ID.
     */
    private String id;

    /**
     * Whether we are in a load test or a functional test.
     */
    private boolean loadTest;

    /**
     * The session-specific request history.
     */
    private RequestHistory requestHistory;

    /**
     * Network data manager.
     */
    private final NetworkDataManagerImpl networkDataManagerImpl;

    /**
     * The results directory for this session.
     */
    private File resultDir;

    /**
     * The registered shutdown listeners.
     */
    private final List shutdownListeners;

    /**
     * The total count of agents that take part in a load test.
     */
    private int totalAgentCount;

    /**
     * The total number of users, independent of the user type.
     */
    private int totalUserCount;

    /**
     * The number of users with the same type as the current user.
     */
    private int userCount;

    /**
     * The name of the current user.
     */
    private String userName;

    /**
     * The instance number of the current user.
     */
    private int userNumber;

    /**
     * The name of the current action, null if not in an action.
     */
    private String actionName;

    /**
     * The name of the action that failed.
     */
    private String failedActionName;

    /**
     * Maps the start time of a (WebDriver) action to the action name.
     */
    private final NavigableMap webDriverActionStartTimes = new TreeMap();

    /**
     * The timer task that interrupts the current transaction when the transaction timeout has expired.
     */
    private TimerTask transactionExpirationTimerTask;

    /**
     * Indicates whether or not the session was marked as expired. This happens only when the shutdown period is over.
     */
    private boolean sessionExpired;

    /**
     * Indicates whether or not the session's current transaction was marked as expired. This happens only when the
     * maximum permitted run time of a transaction is reached.
     */
    private boolean transactionExpired;

    /**
     * The fully qualified class name of the test case to which this session belongs.
     */
    private String testCaseClassName;

    /**
     * Used in {@link AbstractAction#run()} to check whether we have to wait the think time or not. Its primary use is
     * to avoid unnecessary think times.
     */
    private boolean executeThinkTime;

    /**
     * Creates a new Session object.
     */
    public SessionImpl()
    {
        // set default values in case we run from Eclipse and the test is not
        // derived from AbstractTestCase
        id = String.valueOf(GlobalClock.getInstance().getTime());

        userCount = 1;
        userName = UNKNOWN_USER_NAME;
        userNumber = 0;
        absoluteUserNumber = 0;
        totalUserCount = 1;
        loadTest = false;
        executeThinkTime = false;
        // webDriverActionName = null; // this is Java default, so no reason to set

        agentID = UNKNOWN_AGENT_ID;
        agentNumber = 0;
        totalAgentCount = 1;

        // create the session-specific helper objects
        dataManagerImpl = new DataManagerImpl(this);
        shutdownListeners = new ArrayList();
        networkDataManagerImpl = new NetworkDataManagerImpl();
    }

    /**
     * Initializes the rest of this session object.
     */
    private void init()
    {
        /*
         * All objects that make use of XltProperties are initialized here, not in the constructor. Since XltProperties
         * now uses SessionImpl, this method is to avoid an endless call chain
         * "SessionImpl -> XltProperties -> SessionImpl -> ..."
         */

        // create more session-specific helper objects
        requestHistory = new RequestHistory();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void addShutdownListener(final SessionShutdownListener listener)
    {
        shutdownListeners.add(listener);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void clear()
    {
        // make sure the session cleared correctly in any case
        try
        {
            if (actionDirector != null)
            {
                actionDirector.shutdown();
            }

            // call back any shutdown listeners
            for (final SessionShutdownListener listener : new ArrayList(shutdownListeners))
            {
                listener.shutdown();
            }

            // dump the history
            requestHistory.dumpToDisk();
        }
        finally
        {
            // clear the session
            networkDataManagerImpl.clear();
            requestHistory.clear();
            shutdownListeners.clear();
            failed = false;
            t = null;
            resultDir = null;
            actionDirector = null;
            actionName = null;
            // failedActionName = null;
            // id = null;
            webDriverActionStartTimes.clear();
            executeThinkTime = false;
            testInstance = null;
            transactionTimer = null;  // just for safety's sake
            valueLog.clear();
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public int getAbsoluteUserNumber()
    {
        return absoluteUserNumber;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String getAgentID()
    {
        return agentID;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public int getAgentNumber()
    {
        return agentNumber;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public DataManagerImpl getDataManager()
    {
        return dataManagerImpl;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String getID()
    {
        return id;
    }

    /**
     * Returns the session's request history.
     * 
     * @return the request history
     */
    public RequestHistory getRequestHistory()
    {
        return requestHistory;
    }

    /**
     * Returns the session's results directory.
     * 
     * @return the result directory
     */
    public File getResultsDirectory()
    {
        // no result defined yet
        if (resultDir == null)
        {
            // get result-dir property value
            String resultDirName = XltConstants.RESULT_ROOT_DIR;
            if (!isLoadTest())
            {
                final String propVal = XltProperties.getInstance().getProperty(RESULT_DIR_PROPERTY, "");
                // invalid property value -> log message
                if (propVal.length() == 0)
                {
                    XltLogger.runTimeLogger.warn("No result dir defined. Will use default result directory 'results'.");
                }
                else
                {
                    resultDirName = propVal;
                }
            }

            // convert illegal characters potentially contained in user name
            final String cleanUserName = FileUtils.replaceIllegalCharsInFileName(userName);

            // create new file handle for result directory rooted at the
            // user name directory which itself is rooted at the configured
            // result dir
            resultDir = new File(new File(resultDirName, cleanUserName), String.valueOf(userNumber));

            if (!resultDir.exists())
            {
                // mkdirs() is not thread-safe
                synchronized (SessionImpl.class)
                {
                    resultDir.mkdirs();
                }
            }
        }

        return resultDir;
    }

    /**
     * Returns the fully qualified class name of the test case to which this session belongs.
     * 
     * @return the test class name
     */
    public String getTestCaseClassName()
    {
        return testCaseClassName;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public int getTotalAgentCount()
    {
        return totalAgentCount;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public int getTotalUserCount()
    {
        return totalUserCount;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public int getUserCount()
    {
        return userCount;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String getUserID()
    {
        return userName + "-" + userNumber;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String getUserName()
    {
        return userName;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public int getUserNumber()
    {
        return userNumber;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @Deprecated
    public String getWebDriverActionName()
    {
        return getCurrentActionName();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean hasFailed()
    {
        return failed;
    }

    /**
     * Set the session's failure reason.
     */
    public void setFailReason(final Throwable t)
    {
        this.t = t;
    }

    /**
     * Returns the session's failure reason if any.
     * 
     * @return the session's failure reason or null
     */
    public Throwable getFailReason()
    {
        return t;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean isLoadTest()
    {
        return loadTest;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void removeShutdownListener(final SessionShutdownListener listener)
    {
        shutdownListeners.remove(listener);
    }

    /**
     * Sets the number of the currently running test user. This value ranges from 0...(n-1), where n denotes the total
     * number of configured test users, independent of their respective user type.
     * 
     * @param number
     *            the number to set
     */
    public void setAbsoluteUserNumber(final int number)
    {
        absoluteUserNumber = number;
    }

    /**
     * Sets the new value of the 'agentID' attribute.
     * 
     * @param agentID
     *            the new agentID value
     */
    public void setAgentID(final String agentID)
    {
        this.agentID = agentID;
    }

    /**
     * Sets the new value of the 'agentNumber' attribute.
     * 
     * @param agentNumber
     *            the new agentNumber value
     */
    public void setAgentNumber(final int agentNumber)
    {
        this.agentNumber = agentNumber;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void setFailed(final boolean value)
    {
        failed = value;
        if (failed)
        {
            setFailedActionName();
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void setID(final String id)
    {
        this.id = id;
    }

    /**
     * Sets whether the current test session is executed in the context of a functional test or a load test.
     * 
     * @param loadTest
     *            whether or not we are in a load test
     */
    public void setLoadTest(final boolean loadTest)
    {
        this.loadTest = loadTest;
    }

    /**
     * Sets the fully qualified class name of the test case to which this session belongs.
     * 
     * @param className
     *            the class name
     */
    public void setTestCaseClassName(final String className)
    {
        testCaseClassName = className;
    }

    /**
     * Sets the new value of the 'totalAgentCount' attribute.
     * 
     * @param totalAgentCount
     *            the new totalAgentCount value
     */
    public void setTotalAgentCount(final int totalAgentCount)
    {
        this.totalAgentCount = totalAgentCount;
    }

    /**
     * Sets the total count of test users running during a test. This includes all users of all types.
     * 
     * @param count
     *            the count to set
     */
    public void setTotalUserCount(final int count)
    {
        totalUserCount = count;
    }

    /**
     * Sets the number of users which are of the same type as this user.
     * 
     * @param userCount
     *            the userCount to set
     */
    public void setUserCount(final int userCount)
    {
        this.userCount = userCount;
    }

    /**
     * Sets the name of the user.
     * 
     * @param userName
     *            the userName to set
     */
    public void setUserName(final String userName)
    {
        if (!this.userName.equals(userName))
        {
            this.userName = userName;
            resultDir = null;

            dataManagerImpl.resetLoggerFile();
        }
    }

    /**
     * Sets the user name if it has not been set so far.
     * 
     * @param userName
     *            the userName to set
     */
    public void setUserNameIfNotSet(final String userName)
    {
        if (this.userName.equals(UNKNOWN_USER_NAME))
        {
            setUserName(userName);
        }
    }

    /**
     * Sets the user's instance number.
     * 
     * @param userNumber
     *            the userNumber to set
     */
    public void setUserNumber(final int userNumber)
    {
        this.userNumber = userNumber;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public NetworkDataManager getNetworkDataManager()
    {
        return networkDataManagerImpl;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @Deprecated
    public void setWebDriverActionName(final String webDriverActionName)
    {
        startAction(webDriverActionName);
    }

    /**
     * WebDriver action director instance which is used in {@link #startAction(String)}.
     */
    private WebDriverActionDirector actionDirector;

    private ActionInfo actionInfo;

    /**
     * Sets the WebDriver action director which is used in {@link #startAction(String)}.
     * 
     * @param director
     *            the WebDriver action director
     */
    public void setWebDriverActionDirector(final WebDriverActionDirector director)
    {
        actionDirector = director;
    }

    /**
     * Returns the WebDriver action director which is used in {@link #startAction(String)}.
     * 
     * @return the WebDriver action director
     */
    public WebDriverActionDirector getWebDriverActionDirector()
    {
        return actionDirector;
    }

    /**
     * Performs any tasks necessary at the beginning of a transaction. To be called by the test framework when a new
     * transaction started.
     */
    public void transactionStarted()
    {
        /*
         * Add a timer task to mark this transaction as expired after the transaction timeout.
         */
        if (transactionExpirationTimer != null)
        {
            final long transactionTimeout = XltProperties.getInstance().getProperty(PROP_MAX_TRANSACTION_TIMEOUT,
                                                                                    DEFAULT_TRANSACTION_TIMEOUT);

            // needs to create a new timer task each time as timer tasks cannot be reused
            transactionExpirationTimerTask = new TimerTask()
            {
                @Override
                public void run()
                {
                    if (LOG.isWarnEnabled())
                    {
                        LOG.warn(String.format("User '%s' exceeds maximum permitted run time of %,d ms. Will mark it as expired.",
                                               getUserID(), transactionTimeout));
                    }

                    // mark the transaction as expired -> the user will hopefully end its transaction voluntarily
                    transactionExpired = true;
                }
            };

            transactionExpired = false;
            transactionExpirationTimer.schedule(transactionExpirationTimerTask, transactionTimeout);
        }

        startTransaction();
    }

    /**
     * Performs any tasks necessary at the end of a transaction. To be called by the test framework when a transaction
     * finished.
     */
    public void transactionFinished()
    {
        /*
         * Cancel the timer task that interrupts this transaction after the transaction timeout.
         */
        if (transactionExpirationTimer != null)
        {
            if (transactionExpirationTimerTask != null)
            {
                transactionExpirationTimerTask.cancel();
            }
        }

        stopTransaction();
    }

    /**
     * Returns the session's expired state.
     * 
     * @return the expired state
     */
    public boolean wasMarkedAsExpired()
    {
        return sessionExpired;
    }

    /**
     * Mark the session as expired. Cannot be undone.
     */
    public void markAsExpired()
    {
        sessionExpired = true;
    }

    /**
     * Checks whether the current transaction should be aborted. This will be the case if the current transaction
     * exceeds the maximum permitted run time or if the load test has finished. In either case, a
     * {@link TransactionInterruptedException} will be thrown.
     * 
     * @throws TransactionInterruptedException
     *             if the current transaction is to be aborted
     */
    public void checkState() throws TransactionInterruptedException
    {
        if (sessionExpired)
        {
            throw new TransactionInterruptedException("Load test has finished");
        }
        else if (transactionExpired)
        {
            throw new TransactionInterruptedException("Transaction exceeded the run time limit");
        }
    }

    /**
     * Returns a mapping from start times to action names for all created WebDriver actions.
     * 
     * @return the mapping
     * @see #startAction(String)
     */
    public NavigableMap getWebDriverActionStartTimes()
    {
        return webDriverActionStartTimes;
    }

    /**
     * {@inheritDoc}
     */
    public String getCurrentActionName()
    {
        return actionName;
    }

    /**
     * Sets the name of the current action. Not necessary when using {@link #startAction(String)}, i.e. for WebDriver
     * actions.
     * 
     * @param actionName
     *            the action name
     */
    public void setCurrentActionName(final String actionName)
    {
        this.actionName = actionName;
    }

    public ActionInfo getCurrentActionInfo()
    {
        return this.actionInfo;
    }

    /**
     * {@inheritDoc}
     */
    public void startAction(final String actionName)
    {
        // check whether the current session/transaction was marked as expired
        checkState();

        // parameter check
        ParameterCheckUtils.isNotNullOrEmpty(actionName, "actionName");

        // let action director start a new action
        if (actionDirector == null)
        {
            actionDirector = new WebDriverActionDirector();
        }

        actionDirector.startNewAction(actionName);

        // remember the action's name and start time
        this.actionName = actionName;

        actionInfo = new ActionInfo();
        actionInfo.name = actionName;

        webDriverActionStartTimes.put(GlobalClock.getInstance().getTime(), actionInfo);
    }

    /**
     * {@inheritDoc}
     */
    public void stopAction()
    {
        // check whether the current session/transaction was marked as expired
        checkState();

        // let action director finish the action
        if (actionDirector != null)
        {
            actionDirector.finishCurrentAction();
        }

        // clear the action name
        actionName = null;
    }

    /**
     * Returns the name of the action that caused the test case to fail.
     * 
     * @return the action name, or null if there was no failed action
     */
    public String getFailedActionName()
    {
        return failedActionName;
    }

    /**
     * Sets the name of the current action as the action that caused the test case to fail. If no action is currently
     * open, sets the empty string. This method should be called only once.
     */
    private void setFailedActionName()
    {
        // set the failed action name, but only once
        if (failedActionName == null)
        {
            failedActionName = StringUtils.defaultString(actionName, "");
        }
    }

    /**
     * Resets an internally set failed action name to null. This must be called explicitly as the failed
     * name is not cleared in {@link #clear()}. This way it survives the end of a session and can be reported
     * appropriately, but needs to be cleared before a new session starts.
     */
    public void clearFailedActionName()
    {
        this.failedActionName = null;
    }

    /**
     * @return boolean, indicating whether do the think time or not
     * @see AbstractAction#run()
     */
    public boolean isExecuteThinkTime()
    {
        return executeThinkTime;
    }

    /**
     * Sets the boolean that is used to decide whether do the think time or not
     * 
     * @param executeThinkTime
     * @see AbstractAction#run()
     */
    public void setExecuteThinkTime(boolean executeThinkTime)
    {
        this.executeThinkTime = executeThinkTime;
    }

    /** The currently running test instance. */
    private Object testInstance;

    /**
     * Returns the currently running test instance.
     * 
     * @return test instance
     */
    public Object getTestInstance()
    {
        return testInstance;
    }

    /**
     * Sets the currently running test instance.
     * 
     * @param instance
     *            the test instance
     */
    public void setTestInstance(final Object instance)
    {
        testInstance = instance;
    }

    /**
     * The session's value log, a storage for session-specific test parameters and result data.
     */
    private final Map valueLog = new HashMap<>();

    /**
     * {@inheritDoc}
     */
    public Map getValueLog()
    {
        return valueLog;
    }

    // stuff used for TransactionData recording

    /**
     * A {@link TransactionTimer} that holds the time at which {@linkplain #startTransaction() transaction data
     * recording was started} (or {@code null} if there is no transaction has been started yet)
     */
    private TransactionTimer transactionTimer = null;

    /**
     * Starts transaction data recording. This will clear any recorded failure information and start a new stop-watch
     * for the transaction.
     */
    public void startTransaction()
    {
        setFailed(false);
        setFailReason(null);
        clearFailedActionName();

        transactionTimer = new TransactionTimer();
    }

    /**
     * Tells whether transaction data recording is still in progress (i.e. has been {@linkplain #startTransaction()
     * started} but not {@linkplain #stopTransaction() stopped}, yet.
     * 
     * @return {@code true} iff transaction data recording is still in progress
     */
    public boolean isTransactionPending()
    {
        return transactionTimer != null;
    }

    /**
     * Concludes transaction data recording.
     * 

* This method may only be called while {@linkplain #isTransactionPending() transaction data recording is in * progress} (otherwise a {@link RuntimeException} will be thrown). */ public void stopTransaction() throws RuntimeException { if (isTransactionPending()) { final TransactionData transactionData = new TransactionData(getUserName()); transactionData.setRunTime(transactionTimer.getRuntime()); transactionData.setTime(transactionTimer.getStartTime()); transactionData.setFailed(hasFailed()); transactionData.setFailureStackTrace(extractFirstFailure(getFailReason())); transactionData.setFailedActionName(getFailedActionName()); if (hasFailed()) { transactionData.setTestUserNumber(String.valueOf(getUserNumber())); transactionData.setDirectoryName(getID()); } transactionTimer = null; if (!wasMarkedAsExpired()) { dataManagerImpl.logDataRecord(transactionData); } } } /** * For a {@link MultipleFailureException}, returns the first of the encapsulated failures. Otherwise just returns * the Throwable itself * * @param throwable * @return */ private static Throwable extractFirstFailure(final Throwable throwable) { if (throwable instanceof MultipleFailureException) { return ((MultipleFailureException) throwable).getFailures().get(0); } else { return throwable; } } /** * The idea is that an instance of this remembers the time at which it is created and offers methods to retrieve * that time ({@link #getStartTime()}) and the time passed since it has been created ({@link #getRuntime()}) */ public static class TransactionTimer { private final long globalStartTime = GlobalClock.getInstance().getTime(); private final long localStartTime = TimerUtils.getTime(); public long getStartTime() { return globalStartTime; } public long getRuntime() { return TimerUtils.getTime() - localStartTime; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy