aboutsummaryrefslogtreecommitdiffstats
path: root/traceview/src/com/android/traceview/DmTraceReader.java
diff options
context:
space:
mode:
Diffstat (limited to 'traceview/src/com/android/traceview/DmTraceReader.java')
-rw-r--r--traceview/src/com/android/traceview/DmTraceReader.java754
1 files changed, 0 insertions, 754 deletions
diff --git a/traceview/src/com/android/traceview/DmTraceReader.java b/traceview/src/com/android/traceview/DmTraceReader.java
deleted file mode 100644
index 9bd6882..0000000
--- a/traceview/src/com/android/traceview/DmTraceReader.java
+++ /dev/null
@@ -1,754 +0,0 @@
-/*
- * Copyright (C) 2006 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.traceview;
-
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.nio.BufferUnderflowException;
-import java.nio.ByteOrder;
-import java.nio.MappedByteBuffer;
-import java.nio.channels.FileChannel;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Comparator;
-import java.util.HashMap;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-public class DmTraceReader extends TraceReader {
- private static final int TRACE_MAGIC = 0x574f4c53;
-
- private static final int METHOD_TRACE_ENTER = 0x00; // method entry
- private static final int METHOD_TRACE_EXIT = 0x01; // method exit
- private static final int METHOD_TRACE_UNROLL = 0x02; // method exited by exception unrolling
-
- // When in dual clock mode, we report that a context switch has occurred
- // when skew between the real time and thread cpu clocks is more than this
- // many microseconds.
- private static final long MIN_CONTEXT_SWITCH_TIME_USEC = 100;
-
- private enum ClockSource {
- THREAD_CPU, WALL, DUAL,
- };
-
- private int mVersionNumber;
- private boolean mRegression;
- private ProfileProvider mProfileProvider;
- private String mTraceFileName;
- private MethodData mTopLevel;
- private ArrayList<Call> mCallList;
- private HashMap<String, String> mPropertiesMap;
- private HashMap<Integer, MethodData> mMethodMap;
- private HashMap<Integer, ThreadData> mThreadMap;
- private ThreadData[] mSortedThreads;
- private MethodData[] mSortedMethods;
- private long mTotalCpuTime;
- private long mTotalRealTime;
- private MethodData mContextSwitch;
- private int mRecordSize;
- private ClockSource mClockSource;
-
- // A regex for matching the thread "id name" lines in the .key file
- private static final Pattern mIdNamePattern = Pattern.compile("(\\d+)\t(.*)"); //$NON-NLS-1$
-
- public DmTraceReader(String traceFileName, boolean regression) throws IOException {
- mTraceFileName = traceFileName;
- mRegression = regression;
- mPropertiesMap = new HashMap<String, String>();
- mMethodMap = new HashMap<Integer, MethodData>();
- mThreadMap = new HashMap<Integer, ThreadData>();
- mCallList = new ArrayList<Call>();
-
- // Create a single top-level MethodData object to hold the profile data
- // for time spent in the unknown caller.
- mTopLevel = new MethodData(0, "(toplevel)");
- mContextSwitch = new MethodData(-1, "(context switch)");
- mMethodMap.put(0, mTopLevel);
- mMethodMap.put(-1, mContextSwitch);
- generateTrees();
- }
-
- void generateTrees() throws IOException {
- long offset = parseKeys();
- parseData(offset);
- analyzeData();
- }
-
- @Override
- public ProfileProvider getProfileProvider() {
- if (mProfileProvider == null)
- mProfileProvider = new ProfileProvider(this);
- return mProfileProvider;
- }
-
- private MappedByteBuffer mapFile(String filename, long offset) throws IOException {
- MappedByteBuffer buffer = null;
- FileInputStream dataFile = new FileInputStream(filename);
- try {
- File file = new File(filename);
- FileChannel fc = dataFile.getChannel();
- buffer = fc.map(FileChannel.MapMode.READ_ONLY, offset, file.length() - offset);
- buffer.order(ByteOrder.LITTLE_ENDIAN);
-
- return buffer;
- } finally {
- dataFile.close(); // this *also* closes the associated channel, fc
- }
- }
-
- private void readDataFileHeader(MappedByteBuffer buffer) {
- int magic = buffer.getInt();
- if (magic != TRACE_MAGIC) {
- System.err.printf(
- "Error: magic number mismatch; got 0x%x, expected 0x%x\n",
- magic, TRACE_MAGIC);
- throw new RuntimeException();
- }
-
- // read version
- int version = buffer.getShort();
- if (version != mVersionNumber) {
- System.err.printf(
- "Error: version number mismatch; got %d in data header but %d in options\n",
- version, mVersionNumber);
- throw new RuntimeException();
- }
- if (version < 1 || version > 3) {
- System.err.printf(
- "Error: unsupported trace version number %d. "
- + "Please use a newer version of TraceView to read this file.", version);
- throw new RuntimeException();
- }
-
- // read offset
- int offsetToData = buffer.getShort() - 16;
-
- // read startWhen
- buffer.getLong();
-
- // read record size
- if (version == 1) {
- mRecordSize = 9;
- } else if (version == 2) {
- mRecordSize = 10;
- } else {
- mRecordSize = buffer.getShort();
- offsetToData -= 2;
- }
-
- // Skip over offsetToData bytes
- while (offsetToData-- > 0) {
- buffer.get();
- }
- }
-
- private void parseData(long offset) throws IOException {
- MappedByteBuffer buffer = mapFile(mTraceFileName, offset);
- readDataFileHeader(buffer);
-
- ArrayList<TraceAction> trace = null;
- if (mClockSource == ClockSource.THREAD_CPU) {
- trace = new ArrayList<TraceAction>();
- }
-
- final boolean haveThreadClock = mClockSource != ClockSource.WALL;
- final boolean haveGlobalClock = mClockSource != ClockSource.THREAD_CPU;
-
- // Parse all call records to obtain elapsed time information.
- ThreadData prevThreadData = null;
- for (;;) {
- int threadId;
- int methodId;
- long threadTime, globalTime;
- try {
- int recordSize = mRecordSize;
-
- if (mVersionNumber == 1) {
- threadId = buffer.get();
- recordSize -= 1;
- } else {
- threadId = buffer.getShort();
- recordSize -= 2;
- }
-
- methodId = buffer.getInt();
- recordSize -= 4;
-
- switch (mClockSource) {
- case WALL:
- threadTime = 0;
- globalTime = buffer.getInt();
- recordSize -= 4;
- break;
- case DUAL:
- threadTime = buffer.getInt();
- globalTime = buffer.getInt();
- recordSize -= 8;
- break;
- default:
- case THREAD_CPU:
- threadTime = buffer.getInt();
- globalTime = 0;
- recordSize -= 4;
- break;
- }
-
- while (recordSize-- > 0) {
- buffer.get();
- }
- } catch (BufferUnderflowException ex) {
- break;
- }
-
- int methodAction = methodId & 0x03;
- methodId = methodId & ~0x03;
- MethodData methodData = mMethodMap.get(methodId);
- if (methodData == null) {
- String name = String.format("(0x%1$x)", methodId); //$NON-NLS-1$
- methodData = new MethodData(methodId, name);
- mMethodMap.put(methodId, methodData);
- }
-
- ThreadData threadData = mThreadMap.get(threadId);
- if (threadData == null) {
- String name = String.format("[%1$d]", threadId); //$NON-NLS-1$
- threadData = new ThreadData(threadId, name, mTopLevel);
- mThreadMap.put(threadId, threadData);
- }
-
- long elapsedGlobalTime = 0;
- if (haveGlobalClock) {
- if (!threadData.mHaveGlobalTime) {
- threadData.mGlobalStartTime = globalTime;
- threadData.mHaveGlobalTime = true;
- } else {
- elapsedGlobalTime = globalTime - threadData.mGlobalEndTime;
- }
- threadData.mGlobalEndTime = globalTime;
- }
-
- if (haveThreadClock) {
- long elapsedThreadTime = 0;
- if (!threadData.mHaveThreadTime) {
- threadData.mThreadStartTime = threadTime;
- threadData.mThreadCurrentTime = threadTime;
- threadData.mHaveThreadTime = true;
- } else {
- elapsedThreadTime = threadTime - threadData.mThreadEndTime;
- }
- threadData.mThreadEndTime = threadTime;
-
- if (!haveGlobalClock) {
- // Detect context switches whenever execution appears to switch from one
- // thread to another. This assumption is only valid on uniprocessor
- // systems (which is why we now have a dual clock mode).
- // We represent context switches in the trace by pushing a call record
- // with MethodData mContextSwitch onto the stack of the previous
- // thread. We arbitrarily set the start and end time of the context
- // switch such that the context switch occurs in the middle of the thread
- // time and itself accounts for zero thread time.
- if (prevThreadData != null && prevThreadData != threadData) {
- // Begin context switch from previous thread.
- Call switchCall = prevThreadData.enter(mContextSwitch, trace);
- switchCall.mThreadStartTime = prevThreadData.mThreadEndTime;
- mCallList.add(switchCall);
-
- // Return from context switch to current thread.
- Call top = threadData.top();
- if (top.getMethodData() == mContextSwitch) {
- threadData.exit(mContextSwitch, trace);
- long beforeSwitch = elapsedThreadTime / 2;
- top.mThreadStartTime += beforeSwitch;
- top.mThreadEndTime = top.mThreadStartTime;
- }
- }
- prevThreadData = threadData;
- } else {
- // If we have a global clock, then we can detect context switches (or blocking
- // calls or cpu suspensions or clock anomalies) by comparing global time to
- // thread time for successive calls that occur on the same thread.
- // As above, we represent the context switch using a special method call.
- long sleepTime = elapsedGlobalTime - elapsedThreadTime;
- if (sleepTime > MIN_CONTEXT_SWITCH_TIME_USEC) {
- Call switchCall = threadData.enter(mContextSwitch, trace);
- long beforeSwitch = elapsedThreadTime / 2;
- long afterSwitch = elapsedThreadTime - beforeSwitch;
- switchCall.mGlobalStartTime = globalTime - elapsedGlobalTime + beforeSwitch;
- switchCall.mGlobalEndTime = globalTime - afterSwitch;
- switchCall.mThreadStartTime = threadTime - afterSwitch;
- switchCall.mThreadEndTime = switchCall.mThreadStartTime;
- threadData.exit(mContextSwitch, trace);
- mCallList.add(switchCall);
- }
- }
-
- // Add thread CPU time.
- Call top = threadData.top();
- top.addCpuTime(elapsedThreadTime);
- }
-
- switch (methodAction) {
- case METHOD_TRACE_ENTER: {
- Call call = threadData.enter(methodData, trace);
- if (haveGlobalClock) {
- call.mGlobalStartTime = globalTime;
- }
- if (haveThreadClock) {
- call.mThreadStartTime = threadTime;
- }
- mCallList.add(call);
- break;
- }
- case METHOD_TRACE_EXIT:
- case METHOD_TRACE_UNROLL: {
- Call call = threadData.exit(methodData, trace);
- if (call != null) {
- if (haveGlobalClock) {
- call.mGlobalEndTime = globalTime;
- }
- if (haveThreadClock) {
- call.mThreadEndTime = threadTime;
- }
- }
- break;
- }
- default:
- throw new RuntimeException("Unrecognized method action: " + methodAction);
- }
- }
-
- // Exit any pending open-ended calls.
- for (ThreadData threadData : mThreadMap.values()) {
- threadData.endTrace(trace);
- }
-
- // Recreate the global timeline from thread times, if needed.
- if (!haveGlobalClock) {
- long globalTime = 0;
- prevThreadData = null;
- for (TraceAction traceAction : trace) {
- Call call = traceAction.mCall;
- ThreadData threadData = call.getThreadData();
-
- if (traceAction.mAction == TraceAction.ACTION_ENTER) {
- long threadTime = call.mThreadStartTime;
- globalTime += call.mThreadStartTime - threadData.mThreadCurrentTime;
- call.mGlobalStartTime = globalTime;
- if (!threadData.mHaveGlobalTime) {
- threadData.mHaveGlobalTime = true;
- threadData.mGlobalStartTime = globalTime;
- }
- threadData.mThreadCurrentTime = threadTime;
- } else if (traceAction.mAction == TraceAction.ACTION_EXIT) {
- long threadTime = call.mThreadEndTime;
- globalTime += call.mThreadEndTime - threadData.mThreadCurrentTime;
- call.mGlobalEndTime = globalTime;
- threadData.mGlobalEndTime = globalTime;
- threadData.mThreadCurrentTime = threadTime;
- } // else, ignore ACTION_INCOMPLETE calls, nothing to do
- prevThreadData = threadData;
- }
- }
-
- // Finish updating all calls and calculate the total time spent.
- for (int i = mCallList.size() - 1; i >= 0; i--) {
- Call call = mCallList.get(i);
-
- // Calculate exclusive real-time by subtracting inclusive real time
- // accumulated by children from the total span.
- long realTime = call.mGlobalEndTime - call.mGlobalStartTime;
- call.mExclusiveRealTime = Math.max(realTime - call.mInclusiveRealTime, 0);
- call.mInclusiveRealTime = realTime;
-
- call.finish();
- }
- mTotalCpuTime = 0;
- mTotalRealTime = 0;
- for (ThreadData threadData : mThreadMap.values()) {
- Call rootCall = threadData.getRootCall();
- threadData.updateRootCallTimeBounds();
- rootCall.finish();
- mTotalCpuTime += rootCall.mInclusiveCpuTime;
- mTotalRealTime += rootCall.mInclusiveRealTime;
- }
-
- if (mRegression) {
- System.out.format("totalCpuTime %dus\n", mTotalCpuTime);
- System.out.format("totalRealTime %dus\n", mTotalRealTime);
-
- dumpThreadTimes();
- dumpCallTimes();
- }
- }
-
- static final int PARSE_VERSION = 0;
- static final int PARSE_THREADS = 1;
- static final int PARSE_METHODS = 2;
- static final int PARSE_OPTIONS = 4;
-
- long parseKeys() throws IOException {
- long offset = 0;
- BufferedReader in = null;
- try {
- in = new BufferedReader(new InputStreamReader(
- new FileInputStream(mTraceFileName), "US-ASCII"));
-
- int mode = PARSE_VERSION;
- String line = null;
- while (true) {
- line = in.readLine();
- if (line == null) {
- throw new IOException("Key section does not have an *end marker");
- }
-
- // Calculate how much we have read from the file so far. The
- // extra byte is for the line ending not included by readLine().
- offset += line.length() + 1;
- if (line.startsWith("*")) {
- if (line.equals("*version")) {
- mode = PARSE_VERSION;
- continue;
- }
- if (line.equals("*threads")) {
- mode = PARSE_THREADS;
- continue;
- }
- if (line.equals("*methods")) {
- mode = PARSE_METHODS;
- continue;
- }
- if (line.equals("*end")) {
- break;
- }
- }
- switch (mode) {
- case PARSE_VERSION:
- mVersionNumber = Integer.decode(line);
- mode = PARSE_OPTIONS;
- break;
- case PARSE_THREADS:
- parseThread(line);
- break;
- case PARSE_METHODS:
- parseMethod(line);
- break;
- case PARSE_OPTIONS:
- parseOption(line);
- break;
- }
- }
- } catch (FileNotFoundException ex) {
- System.err.println(ex.getMessage());
- } finally {
- if (in != null) {
- in.close();
- }
- }
-
- if (mClockSource == null) {
- mClockSource = ClockSource.THREAD_CPU;
- }
-
- return offset;
- }
-
- void parseOption(String line) {
- String[] tokens = line.split("=");
- if (tokens.length == 2) {
- String key = tokens[0];
- String value = tokens[1];
- mPropertiesMap.put(key, value);
-
- if (key.equals("clock")) {
- if (value.equals("thread-cpu")) {
- mClockSource = ClockSource.THREAD_CPU;
- } else if (value.equals("wall")) {
- mClockSource = ClockSource.WALL;
- } else if (value.equals("dual")) {
- mClockSource = ClockSource.DUAL;
- }
- }
- }
- }
-
- void parseThread(String line) {
- String idStr = null;
- String name = null;
- Matcher matcher = mIdNamePattern.matcher(line);
- if (matcher.find()) {
- idStr = matcher.group(1);
- name = matcher.group(2);
- }
- if (idStr == null) return;
- if (name == null) name = "(unknown)";
-
- int id = Integer.decode(idStr);
- mThreadMap.put(id, new ThreadData(id, name, mTopLevel));
- }
-
- void parseMethod(String line) {
- String[] tokens = line.split("\t");
- int id = Long.decode(tokens[0]).intValue();
- String className = tokens[1];
- String methodName = null;
- String signature = null;
- String pathname = null;
- int lineNumber = -1;
- if (tokens.length == 6) {
- methodName = tokens[2];
- signature = tokens[3];
- pathname = tokens[4];
- lineNumber = Integer.decode(tokens[5]);
- pathname = constructPathname(className, pathname);
- } else if (tokens.length > 2) {
- if (tokens[3].startsWith("(")) {
- methodName = tokens[2];
- signature = tokens[3];
- } else {
- pathname = tokens[2];
- lineNumber = Integer.decode(tokens[3]);
- }
- }
-
- mMethodMap.put(id, new MethodData(id, className, methodName, signature,
- pathname, lineNumber));
- }
-
- private String constructPathname(String className, String pathname) {
- int index = className.lastIndexOf('/');
- if (index > 0 && index < className.length() - 1
- && pathname.endsWith(".java"))
- pathname = className.substring(0, index + 1) + pathname;
- return pathname;
- }
-
- private void analyzeData() {
- final TimeBase timeBase = getPreferredTimeBase();
-
- // Sort the threads into decreasing cpu time
- Collection<ThreadData> tv = mThreadMap.values();
- mSortedThreads = tv.toArray(new ThreadData[tv.size()]);
- Arrays.sort(mSortedThreads, new Comparator<ThreadData>() {
- @Override
- public int compare(ThreadData td1, ThreadData td2) {
- if (timeBase.getTime(td2) > timeBase.getTime(td1))
- return 1;
- if (timeBase.getTime(td2) < timeBase.getTime(td1))
- return -1;
- return td2.getName().compareTo(td1.getName());
- }
- });
-
- // Sort the methods into decreasing inclusive time
- Collection<MethodData> mv = mMethodMap.values();
- MethodData[] methods;
- methods = mv.toArray(new MethodData[mv.size()]);
- Arrays.sort(methods, new Comparator<MethodData>() {
- @Override
- public int compare(MethodData md1, MethodData md2) {
- if (timeBase.getElapsedInclusiveTime(md2) > timeBase.getElapsedInclusiveTime(md1))
- return 1;
- if (timeBase.getElapsedInclusiveTime(md2) < timeBase.getElapsedInclusiveTime(md1))
- return -1;
- return md1.getName().compareTo(md2.getName());
- }
- });
-
- // Count the number of methods with non-zero inclusive time
- int nonZero = 0;
- for (MethodData md : methods) {
- if (timeBase.getElapsedInclusiveTime(md) == 0)
- break;
- nonZero += 1;
- }
-
- // Copy the methods with non-zero time
- mSortedMethods = new MethodData[nonZero];
- int ii = 0;
- for (MethodData md : methods) {
- if (timeBase.getElapsedInclusiveTime(md) == 0)
- break;
- md.setRank(ii);
- mSortedMethods[ii++] = md;
- }
-
- // Let each method analyze its profile data
- for (MethodData md : mSortedMethods) {
- md.analyzeData(timeBase);
- }
-
- // Update all the calls to include the method rank in
- // their name.
- for (Call call : mCallList) {
- call.updateName();
- }
-
- if (mRegression) {
- dumpMethodStats();
- }
- }
-
- /*
- * This method computes a list of records that describe the the execution
- * timeline for each thread. Each record is a pair: (row, block) where: row:
- * is the ThreadData object block: is the call (containing the start and end
- * times)
- */
- @Override
- public ArrayList<TimeLineView.Record> getThreadTimeRecords() {
- TimeLineView.Record record;
- ArrayList<TimeLineView.Record> timeRecs;
- timeRecs = new ArrayList<TimeLineView.Record>();
-
- // For each thread, push a "toplevel" call that encompasses the
- // entire execution of the thread.
- for (ThreadData threadData : mSortedThreads) {
- if (!threadData.isEmpty() && threadData.getId() != 0) {
- record = new TimeLineView.Record(threadData, threadData.getRootCall());
- timeRecs.add(record);
- }
- }
-
- for (Call call : mCallList) {
- record = new TimeLineView.Record(call.getThreadData(), call);
- timeRecs.add(record);
- }
-
- if (mRegression) {
- dumpTimeRecs(timeRecs);
- System.exit(0);
- }
- return timeRecs;
- }
-
- private void dumpThreadTimes() {
- System.out.print("\nThread Times\n");
- System.out.print("id t-start t-end g-start g-end name\n");
- for (ThreadData threadData : mThreadMap.values()) {
- System.out.format("%2d %8d %8d %8d %8d %s\n",
- threadData.getId(),
- threadData.mThreadStartTime, threadData.mThreadEndTime,
- threadData.mGlobalStartTime, threadData.mGlobalEndTime,
- threadData.getName());
- }
- }
-
- private void dumpCallTimes() {
- System.out.print("\nCall Times\n");
- System.out.print("id t-start t-end g-start g-end excl. incl. method\n");
- for (Call call : mCallList) {
- System.out.format("%2d %8d %8d %8d %8d %8d %8d %s\n",
- call.getThreadId(), call.mThreadStartTime, call.mThreadEndTime,
- call.mGlobalStartTime, call.mGlobalEndTime,
- call.mExclusiveCpuTime, call.mInclusiveCpuTime,
- call.getMethodData().getName());
- }
- }
-
- private void dumpMethodStats() {
- System.out.print("\nMethod Stats\n");
- System.out.print("Excl Cpu Incl Cpu Excl Real Incl Real Calls Method\n");
- for (MethodData md : mSortedMethods) {
- System.out.format("%9d %9d %9d %9d %9s %s\n",
- md.getElapsedExclusiveCpuTime(), md.getElapsedInclusiveCpuTime(),
- md.getElapsedExclusiveRealTime(), md.getElapsedInclusiveRealTime(),
- md.getCalls(), md.getProfileName());
- }
- }
-
- private void dumpTimeRecs(ArrayList<TimeLineView.Record> timeRecs) {
- System.out.print("\nTime Records\n");
- System.out.print("id t-start t-end g-start g-end method\n");
- for (TimeLineView.Record record : timeRecs) {
- Call call = (Call) record.block;
- System.out.format("%2d %8d %8d %8d %8d %s\n",
- call.getThreadId(), call.mThreadStartTime, call.mThreadEndTime,
- call.mGlobalStartTime, call.mGlobalEndTime,
- call.getMethodData().getName());
- }
- }
-
- @Override
- public HashMap<Integer, String> getThreadLabels() {
- HashMap<Integer, String> labels = new HashMap<Integer, String>();
- for (ThreadData t : mThreadMap.values()) {
- labels.put(t.getId(), t.getName());
- }
- return labels;
- }
-
- @Override
- public MethodData[] getMethods() {
- return mSortedMethods;
- }
-
- @Override
- public ThreadData[] getThreads() {
- return mSortedThreads;
- }
-
- @Override
- public long getTotalCpuTime() {
- return mTotalCpuTime;
- }
-
- @Override
- public long getTotalRealTime() {
- return mTotalRealTime;
- }
-
- @Override
- public boolean haveCpuTime() {
- return mClockSource != ClockSource.WALL;
- }
-
- @Override
- public boolean haveRealTime() {
- return mClockSource != ClockSource.THREAD_CPU;
- }
-
- @Override
- public HashMap<String, String> getProperties() {
- return mPropertiesMap;
- }
-
- @Override
- public TimeBase getPreferredTimeBase() {
- if (mClockSource == ClockSource.WALL) {
- return TimeBase.REAL_TIME;
- }
- return TimeBase.CPU_TIME;
- }
-
- @Override
- public String getClockSource() {
- switch (mClockSource) {
- case THREAD_CPU:
- return "cpu time";
- case WALL:
- return "real time";
- case DUAL:
- return "real time, dual clock";
- }
- return null;
- }
-}