/* * 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.MultiLineReceiver; import com.android.tradefed.log.LogUtil.CLog; import com.android.tradefed.util.SimpleStats; import java.io.BufferedWriter; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.util.ArrayList; import java.util.LinkedList; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * Helper class which runs top continuously on an {@link ITestDevice} and parses the output. * <p> * Provides a method to record the output of top and get all recorded CPU usage measurements or an * average of a specified range of measurements. Note that top can cause approximately a 10% * overhead to the CPU usage while running, so results will not be entirely accurate. * </p> */ public class TopHelper extends Thread { /** The top command to run during the actions. */ private static final String TOP_CMD = "top -d %d -m 10 -t"; /** The pattern to match for the top output. */ private static final Pattern TOP_PERCENT_PATTERN = Pattern.compile("User (\\d+)%, System (\\d+)%, IOW (\\d+)%, IRQ (\\d+)%"); private ITestDevice mTestDevice; private int mDelay; /** * Enum used for distinguishing between the various percentages in the top output. */ enum PercentCategory { TOTAL, USER, SYSTEM, IOW, IRQ } /** * Class for holding the parsed output for a single top output. * <p> * Currently, this only holds the percentage info from top but can be extended to contain the * process information in the top output. * </p> */ public static class TopStats { public Double mTotalPercent = null; public Double mUserPercent = null; public Double mSystemPercent = null; public Double mIowPercent = null; public Double mIrqPercent = null; } /** * Receiver which parses the output from top. */ static class TopReceiver extends MultiLineReceiver { private List<TopStats> mTopStats = new LinkedList<TopStats>(); private boolean mIsCancelled = false; private File mLogFile = null; private BufferedWriter mLogWriter = null; public TopReceiver() { setTrimLine(false); } /** * Specify a file to log the top output to. * * @param logFile the file to lot output to. */ public synchronized void logToFile(File logFile) { try { mLogFile = logFile; mLogWriter = new BufferedWriter(new FileWriter(mLogFile)); } catch (IOException e) { CLog.e("Error creating file: %s", e.getMessage()); mLogWriter = null; } } /** * {@inheritDoc} */ @Override public void processNewLines(String[] lines) { if (mIsCancelled) { return; } synchronized (this) { if (mLogWriter != null) { try { for (String line : lines) { mLogWriter.write(line + "\n"); } } catch (IOException e) { CLog.e("Error writing to file: %s", e.getMessage()); } } } for (String line : lines) { line = line.trim(); Matcher m = TOP_PERCENT_PATTERN.matcher(line); if (m.matches()) { TopStats s = new TopStats(); // Will not trigger NumberFormatException due to TOP_PATTERN matching. s.mUserPercent = Double.parseDouble(m.group(1)); s.mSystemPercent = Double.parseDouble(m.group(2)); s.mIowPercent = Double.parseDouble(m.group(3)); s.mIrqPercent = Double.parseDouble(m.group(4)); s.mTotalPercent = (s.mUserPercent + s.mSystemPercent + s.mIowPercent + s.mIrqPercent); synchronized(this) { mTopStats.add(s); } } } } /** * Cancels the top command. */ public synchronized void cancel() { if (mIsCancelled) { return; } mIsCancelled = true; if (mLogWriter != null) { try { mLogWriter.flush(); mLogWriter.close(); } catch (IOException e) { CLog.e("Error closing writer %s", e.getMessage()); } finally { mLogWriter = null; } } } /** * {@inheritDoc} */ @Override public synchronized boolean isCancelled() { return mIsCancelled; } /** * Gets a list of {@link TopStats} instances. * * @return a list of {@link TopStats} instances ordered from oldest to newest. */ public synchronized List<TopStats> getTopStats() { return new ArrayList<TopStats>(mTopStats); } } private TopReceiver mReceiver = new TopReceiver(); /** * Create a {@link TopHelper} instance with a delay specified. * * @param testDevice The device. * @param delay The delay time interval for the top command in seconds. */ public TopHelper(ITestDevice testDevice, int delay) { mTestDevice = testDevice; mDelay = delay; } /** * Create a {@link TopHelper} instance with a default delay of 1 second. * * @param testDevice The device. */ public TopHelper(ITestDevice testDevice) { this(testDevice, 1); } /** * Specify a file to log the top output to. * * @param logFile the file to lot output to. */ public void logToFile(File logFile) { mReceiver.logToFile(logFile); } /** * Cancels the top command. */ public synchronized void cancel() { mReceiver.cancel(); } /** * Gets whether the top command is canceled. * * @return if the top command is canceled. */ public synchronized boolean isCancelled() { return mReceiver.isCancelled(); } /** * {@inheritDoc} */ @Override public void run() { try { mTestDevice.executeShellCommand(String.format(TOP_CMD, mDelay), mReceiver); } catch (DeviceNotAvailableException e) { CLog.e("Device %s not available: %s", mTestDevice.getSerialNumber(), e.getMessage()); } } /** * Gets a list of {@link TopStats} instances. * * @return a list of {@link TopStats} instances ordered from oldest to newest. */ public List<TopStats> getTopStats() { return mReceiver.getTopStats(); } /** * Get the average total CPU usage for a list of {@link TopStats}. * * @param topStats the list of {@link TopStats} * @return The average usage as a percentage (0 to 100). */ public static Double getTotalAverage(List<TopStats> topStats) { return getAveragePercentage(topStats, PercentCategory.TOTAL); } /** * Get the average user CPU usage for a list of {@link TopStats}. * * @param topStats the list of {@link TopStats} * @return The average usage as a percentage (0 to 100). */ public static Double getUserAverage(List<TopStats> topStats) { return getAveragePercentage(topStats, PercentCategory.USER); } /** * Get the average system CPU usage for a list of {@link TopStats}. * * @param topStats the list of {@link TopStats} * @return The average usage as a percentage (0 to 100). */ public static Double getSystemAverage(List<TopStats> topStats) { return getAveragePercentage(topStats, PercentCategory.SYSTEM); } /** * Get the average IOW CPU usage for a list of {@link TopStats}. * * @param topStats the list of {@link TopStats} * @return The average usage as a percentage (0 to 100). */ public static Double getIowAverage(List<TopStats> topStats) { return getAveragePercentage(topStats, PercentCategory.IOW); } /** * Get the average IRQ CPU usage for a list of {@link TopStats}. * * @param topStats the list of {@link TopStats} * @return The average usage as a percentage (0 to 100). */ public static Double getIrqAverage(List<TopStats> topStats) { return getAveragePercentage(topStats, PercentCategory.IRQ); } /** * Get the average CPU usage for a list of {@link TopStats} and a given category. * * @param topStats the list of {@link TopStats} * @param category the percentage category * @return The average usage as a percentage (0 to 100). */ private static Double getAveragePercentage(List<TopStats> topStats, PercentCategory category) throws IndexOutOfBoundsException { SimpleStats stats = new SimpleStats(); for (TopStats s : topStats) { switch(category) { case TOTAL: stats.add(s.mTotalPercent); break; case USER: stats.add(s.mUserPercent); break; case SYSTEM: stats.add(s.mSystemPercent); break; case IOW: stats.add(s.mIowPercent); break; case IRQ: stats.add(s.mIrqPercent); break; } } return stats.mean(); } /** * Package protected method used for testing. * * @return the TopReceiver */ TopReceiver getReceiver() { return mReceiver; } }