1 /* 2 * Copyright (C) 2023 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 com.android.microdroid.test.host; 18 19 import static com.google.common.truth.Truth.assertWithMessage; 20 import static org.junit.Assert.assertNotNull; 21 22 import com.android.microdroid.test.host.CommandRunner; 23 import com.android.tradefed.device.ITestDevice; 24 import com.android.tradefed.log.LogUtil.CLog; 25 import com.android.tradefed.util.SimpleStats; 26 27 import java.io.File; 28 import java.io.FileReader; 29 import java.io.BufferedReader; 30 import java.text.ParseException; 31 import java.util.ArrayList; 32 import java.util.Arrays; 33 import java.util.regex.Matcher; 34 import java.util.regex.Pattern; 35 import javax.annotation.Nonnull; 36 37 /** This class provides utilities to interact with the hyp tracing subsystem */ 38 public final class KvmHypTracer { 39 40 private static final String HYP_TRACING_ROOT = "/sys/kernel/tracing/hyp/"; 41 private static final String HYP_EVENTS[] = { "hyp_enter", "hyp_exit" }; 42 private static final int DEFAULT_BUF_SIZE_KB = 4 * 1024; 43 private static final Pattern LOST_EVENT_PATTERN = Pattern.compile( 44 "^CPU:[0-9]* \\[LOST ([0-9]*) EVENTS\\]"); 45 private static final Pattern EVENT_PATTERN = Pattern.compile( 46 "^\\[([0-9]*)\\][ \t]*([0-9]*\\.[0-9]*): (" + String.join("|", HYP_EVENTS) + ") (.*)"); 47 48 private final CommandRunner mRunner; 49 private final ITestDevice mDevice; 50 private final int mNrCpus; 51 52 private final ArrayList<File> mTraces; 53 setNode(String node, int val)54 private void setNode(String node, int val) throws Exception { 55 mRunner.run("echo " + val + " > " + HYP_TRACING_ROOT + node); 56 } 57 eventDir(String event)58 private static String eventDir(String event) { 59 return "events/hyp/" + event + "/"; 60 } 61 isSupported(ITestDevice device)62 public static boolean isSupported(ITestDevice device) throws Exception { 63 for (String event: HYP_EVENTS) { 64 if (!device.doesFileExist(HYP_TRACING_ROOT + eventDir(event) + "/enable")) 65 return false; 66 } 67 return true; 68 } 69 KvmHypTracer(@onnull ITestDevice device)70 public KvmHypTracer(@Nonnull ITestDevice device) throws Exception { 71 assertWithMessage("Hypervisor tracing not supported") 72 .that(isSupported(device)).isTrue(); 73 74 mDevice = device; 75 mRunner = new CommandRunner(mDevice); 76 mTraces = new ArrayList<File>(); 77 mNrCpus = Integer.parseInt(mRunner.run("nproc")); 78 } 79 run(String payload_cmd)80 public String run(String payload_cmd) throws Exception { 81 mTraces.clear(); 82 83 setNode("tracing_on", 0); 84 mRunner.run("echo 0 | tee " + HYP_TRACING_ROOT + "events/*/*/enable"); 85 setNode("buffer_size_kb", DEFAULT_BUF_SIZE_KB); 86 for (String event: HYP_EVENTS) 87 setNode(eventDir(event) + "/enable", 1); 88 setNode("trace", 0); 89 90 /* Cat each per-cpu trace_pipe in its own tmp file in the background */ 91 String cmd = "cd " + HYP_TRACING_ROOT + ";"; 92 String trace_pipes[] = new String[mNrCpus]; 93 for (int i = 0; i < mNrCpus; i++) { 94 trace_pipes[i] = mRunner.run("mktemp -t trace_pipe.cpu" + i + ".XXXXXXXXXX"); 95 cmd += "cat per_cpu/cpu" + i + "/trace_pipe > " + trace_pipes[i] + " &"; 96 cmd += "CPU" + i + "_TRACE_PIPE_PID=$!;"; 97 } 98 99 /* Run the payload with tracing enabled */ 100 cmd += "echo 1 > tracing_on;"; 101 String cmd_stdout = mRunner.run("mktemp -t cmd_stdout.XXXXXXXXXX"); 102 cmd += payload_cmd + " > " + cmd_stdout + ";"; 103 cmd += "echo 0 > tracing_on;"; 104 105 /* Actively kill the cat subprocesses as trace_pipe is blocking */ 106 for (int i = 0; i < mNrCpus; i++) 107 cmd += "kill -9 $CPU" + i + "_TRACE_PIPE_PID;"; 108 cmd += "wait"; 109 110 /* 111 * The whole thing runs in a single command for simplicity as `adb 112 * shell` doesn't play well with subprocesses outliving their parent, 113 * and cat-ing a trace_pipe is blocking, so doing so from separate Java 114 * threads wouldn't be much easier as we would need to actively kill 115 * them too. 116 */ 117 mRunner.run(cmd); 118 119 for (String t: trace_pipes) { 120 File trace = mDevice.pullFile(t); 121 assertNotNull(trace); 122 mTraces.add(trace); 123 mRunner.run("rm -f " + t); 124 } 125 126 String res = mRunner.run("cat " + cmd_stdout); 127 mRunner.run("rm -f " + cmd_stdout); 128 return res; 129 } 130 getDurationStats()131 public SimpleStats getDurationStats() throws Exception { 132 SimpleStats stats = new SimpleStats(); 133 134 for (File trace: mTraces) { 135 BufferedReader br = new BufferedReader(new FileReader(trace)); 136 double last = 0.0, hyp_enter = 0.0; 137 String l, prev_event = ""; 138 while ((l = br.readLine()) != null) { 139 Matcher matcher = LOST_EVENT_PATTERN.matcher(l); 140 if (matcher.find()) 141 throw new OutOfMemoryError("Lost " + matcher.group(1) + " events"); 142 143 matcher = EVENT_PATTERN.matcher(l); 144 if (!matcher.find()) { 145 CLog.w("Failed to parse hyp event: " + l); 146 continue; 147 } 148 149 int cpu = Integer.parseInt(matcher.group(1)); 150 if (cpu < 0 || cpu >= mNrCpus) 151 throw new ParseException("Incorrect CPU number: " + cpu, 0); 152 153 double cur = Double.parseDouble(matcher.group(2)); 154 if (cur < last) 155 throw new ParseException("Time must not go backward: " + cur, 0); 156 last = cur; 157 158 String event = matcher.group(3); 159 if (event.equals(prev_event)) { 160 throw new ParseException("Hyp event found twice in a row: " + trace + " - " + l, 161 0); 162 } 163 164 switch (event) { 165 case "hyp_exit": 166 if (prev_event.equals("hyp_enter")) 167 stats.add(cur - hyp_enter); 168 break; 169 case "hyp_enter": 170 hyp_enter = cur; 171 break; 172 default: 173 throw new ParseException("Unexpected line in trace" + l, 0); 174 } 175 prev_event = event; 176 } 177 } 178 179 return stats; 180 } 181 } 182