(16)-logcat信息收集系统

Log收集系统

涉及三个类,关系如图

LogcatReceiver

log收集器的外观类,包装了后台执行线程和log内容接收器

/*
 * Copyright (C) 2012 The Android Open Source Project
 *
 * 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.android.tradefed.device;

import com.android.tradefed.result.InputStreamSource;

/**
 * Class that collects logcat in background. Continues to capture logcat even if device goes
 * offline then online.
 */
public class LogcatReceiver {
    private BackgroundDeviceAction mDeviceAction;
    private LargeOutputReceiver mReceiver;

    static final String LOGCAT_CMD = "logcat -v threadtime";
    private static final String LOGCAT_DESC = "logcat";

    public LogcatReceiver(ITestDevice device, long maxFileSize, int logStartDelay) {
        mReceiver = new LargeOutputReceiver(LOGCAT_DESC, device.getSerialNumber(),
                maxFileSize);
        // FIXME: remove mLogStartDelay. Currently delay starting logcat, as starting
        // immediately after a device comes online has caused adb instability
        mDeviceAction = new BackgroundDeviceAction(LOGCAT_CMD, LOGCAT_DESC, device,
                mReceiver, logStartDelay);
    }

    public void start() {
        mDeviceAction.start();
    }

    public void stop() {
        mDeviceAction.cancel();
        mReceiver.cancel();
        mReceiver.delete();
    }

    public InputStreamSource getLogcatData() {
        return mReceiver.getData();
    }

    public InputStreamSource getLogcatData(int maxBytes) {
        return mReceiver.getData(maxBytes);
    }

    public void clear() {
        mReceiver.clear();
    }
}

构造方法中初始化后台执行线程BackgoundDeviceAction和log信息接收器LargeOutputReceiver对象。

start:启动线程

stop:关闭现场,取消接收logcat信息,删除收集器中的信息

getLogcatData:得到logcat信息。

getLogcatData(int maxBytes):得到规定大小的logcat信息

clear:清空消息。

BackgroundDeviceAction

后台线程,就是在后台线程中执行logcat命令

/*
 * Copyright (C) 2011 The Android Open Source Project
 *
 * 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.android.tradefed.device;

import com.android.ddmlib.AdbCommandRejectedException;
import com.android.ddmlib.IShellOutputReceiver;
import com.android.ddmlib.ShellCommandUnresponsiveException;
import com.android.ddmlib.TimeoutException;
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.util.IRunUtil;
import com.android.tradefed.util.RunUtil;

import java.io.IOException;

/**
 * Runs a command on a given device repeating as necessary until the action is canceled.
 * <p>
 * When the class is run, the command is run on the device in a separate thread and the output is
 * collected in a temporary host file.
 * </p><p>
 * This is done so:
 * </p><ul>
 * <li>if device goes permanently offline during a test, the log data is retained.</li>
 * <li>to capture more data than may fit in device"s circular log.</li>
 * </ul>
 */
public class BackgroundDeviceAction extends Thread {
    private IShellOutputReceiver mReceiver;
    private ITestDevice mTestDevice;
    private String mCommand;
    private String mSerialNumber;
    private String mDescriptor;
    private boolean mIsCancelled;
    private int mLogStartDelay;

    /**
     * Creates a {@link BackgroundDeviceAction}
     *
     * @param command the command to run
     * @param descriptor the description of the command. For logging only.
     * @param device the device to run the command on
     * @param receiver the receiver for collecting the output of the command
     * @param startDelay the delay to wait after the device becomes online
     */
    public BackgroundDeviceAction(String command, String descriptor, ITestDevice device,
            IShellOutputReceiver receiver, int startDelay) {
        mCommand = command;
        mDescriptor = descriptor;
        mSerialNumber = device.getSerialNumber();
        mTestDevice = device;
        mReceiver = receiver;
        mLogStartDelay = startDelay;
        // don"t keep VM open if this thread is still running
        setDaemon(true);
    }

    /**
     * {@inheritDoc}
     * <p>
     * Repeats the command until canceled.
     * </p>
     */
    @Override
    public void run() {
        while (!isCancelled()) {
            if (mLogStartDelay > 0) {
                CLog.d("Sleep for %d before starting %s for %s.", mLogStartDelay, mDescriptor,
                        mSerialNumber);
                getRunUtil().sleep(mLogStartDelay);
            }
            CLog.d("Starting %s for %s.", mDescriptor, mSerialNumber);
            try {
                mTestDevice.getIDevice().executeShellCommand(mCommand, mReceiver, 0);
            } catch (TimeoutException e) {
                recoverDevice(e.getClass().getName());
            } catch (AdbCommandRejectedException e) {
                recoverDevice(e.getClass().getName());
            } catch (ShellCommandUnresponsiveException e) {
                recoverDevice(e.getClass().getName());
            } catch (IOException e) {
                recoverDevice(e.getClass().getName());
            }
        }
    }

    private void recoverDevice(String exceptionType) {
        CLog.d("%s while running %s on %s. May see duplicated content in log.", exceptionType,
                mDescriptor, mSerialNumber);

        // FIXME: Determine when we should append a message to the receiver.
        if (mReceiver instanceof LargeOutputReceiver) {
            byte[] stringData = String.format(
                    "%s interrupted. May see duplicated content in log.", mDescriptor).getBytes();
            mReceiver.addOutput(stringData, 0, stringData.length);
        }

        // Make sure we haven"t been cancelled before we sleep for a long time
        if (isCancelled()) {
            return;
        }

        // sleep a small amount for device to settle
        getRunUtil().sleep(5 * 1000);

        // wait a long time for device to be online
        try {
            mTestDevice.waitForDeviceOnline(10 * 60 * 1000);
        } catch (DeviceNotAvailableException e) {
            CLog.w("Device %s not online", mSerialNumber);
        }
    }

    /**
     * Cancels the command.
     */
    public synchronized void cancel() {
        mIsCancelled = true;
        interrupt();
    }

    /**
     * If the command is cancelled.
     */
    public synchronized boolean isCancelled() {
        return mIsCancelled;
    }

    /**
     * Get the {@link RunUtil} instance to use.
     * <p/>
     * Exposed for unit testing.
     */
    IRunUtil getRunUtil() {
        return RunUtil.getDefault();
    }
}

LargeOutputReceiver

继承adb里面的IShellOutputReceiver接口。用于接收命令执行后返回的信息

/*
 * Copyright (C) 2011 The Android Open Source Project
 *
 * 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.android.tradefed.device;

import com.android.ddmlib.IShellOutputReceiver;
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.result.ByteArrayInputStreamSource;
import com.android.tradefed.result.InputStreamSource;
import com.android.tradefed.result.SnapshotInputStreamSource;
import com.android.tradefed.util.FixedByteArrayOutputStream;
import com.android.tradefed.util.SizeLimitedOutputStream;
import com.android.tradefed.util.StreamUtil;

import java.io.IOException;
import java.io.InputStream;

/**
 * A class designed to help run long running commands collect output.
 * <p>
 * The maximum size of the tmp file is limited to approximately {@code maxFileSize}.
 * To prevent data loss when the limit has been reached, this file keeps set of tmp host
 * files.
 * </p>
 */
public class LargeOutputReceiver implements IShellOutputReceiver {
    private String mSerialNumber;
    private String mDescriptor;

    private boolean mIsCancelled = false;
    private SizeLimitedOutputStream mOutStream;
    private long mMaxDataSize;

    /**
     * Creates a {@link LargeOutputReceiver}.
     *
     * @param descriptor the descriptor of the command to run. For logging only.
     * @param serialNumber the serial number of the device. For logging only.
     * @param maxDataSize the approximate max amount of data to keep.
     */
    public LargeOutputReceiver(String descriptor, String serialNumber, long maxDataSize) {
        mDescriptor = descriptor;
        mSerialNumber = serialNumber;
        mMaxDataSize = maxDataSize;
        mOutStream = createOutputStream();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public synchronized void addOutput(byte[] data, int offset, int length) {
        if (mIsCancelled || mOutStream == null) {
            return;
        }
        try {
            mOutStream.write(data, offset, length);
        } catch (IOException e) {
            CLog.w("failed to write %s data for %s.", mDescriptor, mSerialNumber);
        }
    }

    /**
     * Gets the collected output as a {@link InputStreamSource}.
     *
     * @return The collected output from the command.
     */
    public synchronized InputStreamSource getData() {
        if (mOutStream != null) {
            try {
                return new SnapshotInputStreamSource(mOutStream.getData());
            } catch (IOException e) {
                CLog.e("failed to get %s data for %s.", mDescriptor, mSerialNumber);
                CLog.e(e);
            }
        }

        // return an empty InputStreamSource
        return new ByteArrayInputStreamSource(new byte[0]);
    }

    /**
     * Gets the last <var>maxBytes</var> of collected output as a {@link InputStreamSource}.
     *
     * @param maxBytes the maximum amount of data to return. Should be an amount that can
     *            comfortably fit in memory
     * @return The collected output from the command, stored in memory
     */
    public synchronized InputStreamSource getData(final int maxBytes) {
        if (mOutStream != null) {
            InputStream fullStream = null;
            try {
                fullStream = mOutStream.getData();
                final FixedByteArrayOutputStream os = new FixedByteArrayOutputStream(maxBytes);
                StreamUtil.copyStreams(fullStream, os);
                return new InputStreamSource() {

                    @Override
                    public InputStream createInputStream()  {
                        return os.getData();
                    }

                    @Override
                    public void cancel() {
                        // ignore, nothing to do
                    }

                    @Override
                    public long size() {
                        return os.size();
                    }
                };
            } catch (IOException e) {
                CLog.e("failed to get %s data for %s.", mDescriptor, mSerialNumber);
                CLog.e(e);
            } finally {
                StreamUtil.close(fullStream);
            }
        }

        // return an empty InputStreamSource
        return new ByteArrayInputStreamSource(new byte[0]);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public synchronized void flush() {
        if (mOutStream == null) {
            return;
        }
        mOutStream.flush();
    }

    /**
     * Delete currently accumulated data, and then re-create a new file.
     */
    public synchronized void clear() {
        delete();
        mOutStream = createOutputStream();
    }

    private SizeLimitedOutputStream createOutputStream() {
        return new SizeLimitedOutputStream(mMaxDataSize, String.format("%s_%s",
                getDescriptor(), mSerialNumber), ".txt");
    }

    /**
     * Cancels the command.
     */
    public synchronized void cancel() {
        mIsCancelled = true;
    }

    /**
     * Delete all accumulated data.
     */
    public void delete() {
        mOutStream.delete();
        mOutStream = null;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public synchronized boolean isCancelled() {
        return mIsCancelled;
    }

    /**
     * Get the descriptor.
     * <p>
     * Exposed for unit testing.
     * </p>
     */
    String getDescriptor() {
        return mDescriptor;
    }
}
文章导航