1 /* 2 * Copyright (C) 2019 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 17 package androidx.benchmark.simpleperf; 18 19 import android.system.Os; 20 21 import androidx.annotation.RequiresApi; 22 import androidx.annotation.RestrictTo; 23 24 import org.jspecify.annotations.NonNull; 25 import org.jspecify.annotations.Nullable; 26 27 import java.time.LocalDateTime; 28 import java.time.ZoneId; 29 import java.time.format.DateTimeFormatter; 30 import java.util.ArrayList; 31 import java.util.Collections; 32 import java.util.List; 33 34 /** 35 * <p> 36 * This class sets record options used by ProfileSession. The options are 37 * converted to a string list in toRecordArgs(), which is then passed to 38 * `simpleperf record` cmd. Run `simpleperf record -h` or 39 * `run_simpleperf_on_device.py record -h` for help messages. 40 * </p> 41 * 42 * <p> 43 * Example: 44 * RecordOptions options = new RecordOptions(); 45 * options.setDuration(3).recordDwarfCallGraph().setOutputFilename("perf.data"); 46 * ProfileSession session = new ProfileSession(); 47 * session.startRecording(options); 48 * </p> 49 * 50 * NOTE: copied from 51 * https://cs.android.com/android/platform/superproject/+/master:system/extras/simpleperf/app_api/ 52 * 53 */ 54 @SuppressWarnings("StringConcatenationInLoop") 55 @RequiresApi(28) 56 @RestrictTo(RestrictTo.Scope.LIBRARY) 57 public class RecordOptions { 58 59 /** 60 * Set output filename. Default is perf-<month>-<day>-<hour>-<minute>-<second>.data. 61 * The file will be generated under simpleperf_data/. 62 */ setOutputFilename(@onNull String filename)63 public @NonNull RecordOptions setOutputFilename(@NonNull String filename) { 64 mOutputFilename = filename; 65 return this; 66 } 67 68 /** 69 * Set event to record. Default is cpu-cycles. See `simpleperf list` for all available events. 70 */ setEvent(@onNull String event)71 public @NonNull RecordOptions setEvent(@NonNull String event) { 72 mEvent = event; 73 return this; 74 } 75 76 /** 77 * Set how many samples to generate each second running. Default is 4000. 78 */ setSampleFrequency(int freq)79 public @NonNull RecordOptions setSampleFrequency(int freq) { 80 mFreq = freq; 81 return this; 82 } 83 84 /** 85 * Set record duration. The record stops after `durationInSecond` seconds. By default, 86 * record stops only when stopRecording() is called. 87 */ setDuration(double durationInSecond)88 public @NonNull RecordOptions setDuration(double durationInSecond) { 89 mDurationInSeconds = durationInSecond; 90 return this; 91 } 92 93 /** 94 * Record some threads in the app process. By default, record all threads in the process. 95 */ setSampleThreads(@onNull List<Integer> threads)96 public @NonNull RecordOptions setSampleThreads(@NonNull List<Integer> threads) { 97 mThreads.addAll(threads); 98 return this; 99 } 100 101 /** 102 * Record current thread in the app process. By default, record all threads in the process. 103 */ setSampleCurrentThread()104 public @NonNull RecordOptions setSampleCurrentThread() { 105 return setSampleThreads(Collections.singletonList(Os.gettid())); 106 } 107 /** 108 * Record dwarf based call graph. It is needed to get Java callstacks. 109 */ recordDwarfCallGraph()110 public @NonNull RecordOptions recordDwarfCallGraph() { 111 mDwarfCallGraph = true; 112 mFpCallGraph = false; 113 return this; 114 } 115 116 /** 117 * Record frame pointer based call graph. It is suitable to get C++ callstacks on 64bit devices. 118 */ 119 @SuppressWarnings("unused") recordFramePointerCallGraph()120 public @NonNull RecordOptions recordFramePointerCallGraph() { 121 mFpCallGraph = true; 122 mDwarfCallGraph = false; 123 return this; 124 } 125 126 /** 127 * Trace context switch info to show where threads spend time off cpu. 128 */ traceOffCpu()129 public @NonNull RecordOptions traceOffCpu() { 130 mTraceOffCpu = true; 131 return this; 132 } 133 134 /** 135 * Translate record options into arguments for `simpleperf record` cmd. 136 */ toRecordArgs()137 public @NonNull List<String> toRecordArgs() { 138 ArrayList<String> args = new ArrayList<>(); 139 140 String filename = mOutputFilename; 141 if (filename == null) { 142 filename = getDefaultOutputFilename(); 143 } 144 args.add("-o"); 145 args.add(filename); 146 args.add("-e"); 147 args.add(mEvent); 148 args.add("-f"); 149 args.add(String.valueOf(mFreq)); 150 if (mDurationInSeconds != 0.0) { 151 args.add("--duration"); 152 args.add(String.valueOf(mDurationInSeconds)); 153 } 154 if (mThreads.isEmpty()) { 155 args.add("-p"); 156 args.add(String.valueOf(Os.getpid())); 157 } else { 158 String s = ""; 159 for (int i = 0; i < mThreads.size(); i++) { 160 if (i > 0) { 161 s += ","; 162 } 163 s += mThreads.get(i).toString(); 164 } 165 args.add("-t"); 166 args.add(s); 167 } 168 if (mDwarfCallGraph) { 169 args.add("-g"); 170 } else if (mFpCallGraph) { 171 args.add("--call-graph"); 172 args.add("fp"); 173 } 174 if (mTraceOffCpu) { 175 args.add("--trace-offcpu"); 176 } 177 return args; 178 } 179 getDefaultOutputFilename()180 private String getDefaultOutputFilename() { 181 LocalDateTime time = LocalDateTime.now(ZoneId.systemDefault()); 182 DateTimeFormatter formatter = DateTimeFormatter.ofPattern("'perf'-MM-dd-HH-mm-ss'.data'"); 183 return time.format(formatter); 184 } 185 186 private @Nullable String mOutputFilename; 187 188 private @NonNull String mEvent = "cpu-cycles"; 189 190 private int mFreq = 4000; 191 192 private double mDurationInSeconds = 0.0; 193 194 private final @NonNull ArrayList<Integer> mThreads = new ArrayList<>(); 195 196 private boolean mDwarfCallGraph = false; 197 198 private boolean mFpCallGraph = false; 199 200 private boolean mTraceOffCpu = false; 201 } 202