001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018package org.apache.hadoop.hbase.http; 019 020import static org.junit.jupiter.api.Assertions.assertEquals; 021import static org.junit.jupiter.api.Assertions.assertFalse; 022import static org.junit.jupiter.api.Assertions.assertTrue; 023 024import java.io.File; 025import java.io.IOException; 026import java.nio.file.Files; 027import java.nio.file.Path; 028import java.util.Collections; 029import java.util.HashMap; 030import java.util.List; 031import java.util.Map; 032import javax.servlet.http.HttpServletRequest; 033import org.apache.hadoop.hbase.testclassification.MiscTests; 034import org.apache.hadoop.hbase.testclassification.SmallTests; 035import org.junit.jupiter.api.Tag; 036import org.junit.jupiter.api.Test; 037import org.junit.jupiter.api.io.TempDir; 038import org.mockito.Mockito; 039 040@Tag(MiscTests.TAG) 041@Tag(SmallTests.TAG) 042public class TestProfilerCommandMapper { 043 044 @TempDir 045 Path tempDir; 046 047 // ---- Library start command ---- 048 049 @Test 050 public void testLibraryStartCommandDefaults() { 051 ProfileServlet.ProfileRequest req = parseRequest(Collections.emptyMap()); 052 String cmd = ProfilerCommandMapper.toLibraryStartCommand(req); 053 assertTrue(cmd.startsWith("start")); 054 assertTrue(cmd.contains("event=cpu")); 055 assertFalse(cmd.contains("interval")); 056 assertFalse(cmd.contains("threads")); 057 assertFalse(cmd.contains("simple")); 058 } 059 060 @Test 061 public void testLibraryStartCommandAllOptions() { 062 Map<String, String[]> flags = new HashMap<>(); 063 flags.put("thread", new String[] { "" }); 064 flags.put("simple", new String[] { "" }); 065 066 ProfileServlet.ProfileRequest req = parseRequest(flags, "event", "alloc", "interval", "1000", 067 "jstackdepth", "256", "bufsize", "100000"); 068 String cmd = ProfilerCommandMapper.toLibraryStartCommand(req); 069 assertTrue(cmd.contains("event=alloc")); 070 assertTrue(cmd.contains("interval=1000")); 071 assertTrue(cmd.contains("jstackdepth=256")); 072 // bufsize= is not a recognized 4.x agent option and must not be emitted for LibraryBackend 073 assertFalse(cmd.contains("bufsize")); 074 assertTrue(cmd.contains("threads")); 075 assertTrue(cmd.contains("simple")); 076 } 077 078 // ---- Library stop command ---- 079 080 @Test 081 public void testLibraryStopCommandHtml() throws IOException { 082 Map<String, String[]> flags = new HashMap<>(); 083 flags.put("reverse", new String[] { "" }); 084 ProfileServlet.ProfileRequest req = 085 parseRequest(flags, "output", "html", "width", "1200", "height", "16", "minwidth", "0.5"); 086 087 File outputFile = File.createTempFile("prof", ".html"); 088 outputFile.deleteOnExit(); 089 090 String cmd = ProfilerCommandMapper.toLibraryStopCommand(req, outputFile); 091 assertTrue(cmd.startsWith("stop")); 092 assertTrue(cmd.contains("file=" + outputFile.getAbsolutePath())); 093 // html: format derived from .html extension — no format= key emitted 094 assertFalse(cmd.contains("format=")); 095 // width/height not recognized by 4.x agent — must not be emitted 096 // (use ",width=" prefix to avoid matching ",minwidth=") 097 assertFalse(cmd.contains(",width=")); 098 assertFalse(cmd.contains(",height=")); 099 assertTrue(cmd.contains("minwidth=0.5")); 100 assertTrue(cmd.contains("reverse")); 101 } 102 103 @Test 104 public void testLibraryStopCommandTree() throws IOException { 105 ProfileServlet.ProfileRequest req = parseRequest(Collections.emptyMap(), "output", "tree"); 106 File outputFile = File.createTempFile("prof", ".tree"); 107 outputFile.deleteOnExit(); 108 109 String cmd = ProfilerCommandMapper.toLibraryStopCommand(req, outputFile); 110 // text-based formats need the bare token 111 assertTrue(cmd.contains(",tree")); 112 assertFalse(cmd.contains("format=")); 113 } 114 115 @Test 116 public void testLibraryStopCommandCollapsed() throws IOException { 117 ProfileServlet.ProfileRequest req = parseRequest(Collections.emptyMap(), "output", "collapsed"); 118 File outputFile = File.createTempFile("prof", ".collapsed"); 119 outputFile.deleteOnExit(); 120 121 String cmd = ProfilerCommandMapper.toLibraryStopCommand(req, outputFile); 122 // collapsed must be passed as a bare token — .collapsed extension is not auto-detected by 4.x 123 assertTrue(cmd.contains(",collapsed")); 124 assertFalse(cmd.contains("format=")); 125 } 126 127 @Test 128 public void testLibraryStopCommandSvgRemappedToHtml() throws IOException { 129 ProfileServlet.ProfileRequest req = parseRequest(Collections.emptyMap(), "output", "svg"); 130 File outputFile = File.createTempFile("prof", ".html"); 131 outputFile.deleteOnExit(); 132 133 String cmd = ProfilerCommandMapper.toLibraryStopCommand(req, outputFile); 134 // SVG is obsolete — must be remapped to html 135 assertFalse(cmd.contains("svg")); 136 assertFalse(cmd.contains("format=")); 137 } 138 139 // ---- CLI command ---- 140 141 @Test 142 public void testCliCommandDefaultScript() throws IOException { 143 // Create bin/asprof so the primary script path exists 144 Path binDir = Files.createDirectories(tempDir.resolve("bin")); 145 Files.createFile(binDir.resolve("asprof")); 146 147 ProfileServlet.ProfileRequest req = 148 parseRequest(Collections.emptyMap(), "duration", "30", "output", "html"); 149 File outputFile = File.createTempFile("prof", ".html"); 150 outputFile.deleteOnExit(); 151 152 List<String> cmd = 153 ProfilerCommandMapper.toCliCommand(req, outputFile, tempDir.toString(), 1234); 154 assertEquals(tempDir.resolve("bin/asprof").toString(), cmd.get(0)); 155 assertTrue(cmd.contains("-e")); 156 assertTrue(cmd.contains("cpu")); 157 assertTrue(cmd.contains("-d")); 158 assertTrue(cmd.contains("30")); 159 assertTrue(cmd.contains("-o")); 160 assertTrue(cmd.contains("html")); 161 assertTrue(cmd.contains("-f")); 162 assertTrue(cmd.contains(outputFile.getAbsolutePath())); 163 assertTrue(cmd.contains("1234")); 164 } 165 166 @Test 167 public void testCliCommandSvgRemappedToHtml() throws IOException { 168 Path binDir = Files.createDirectories(tempDir.resolve("bin")); 169 Files.createFile(binDir.resolve("asprof")); 170 171 ProfileServlet.ProfileRequest req = parseRequest(Collections.emptyMap(), "output", "svg"); 172 File outputFile = File.createTempFile("prof", ".html"); 173 outputFile.deleteOnExit(); 174 175 List<String> cmd = 176 ProfilerCommandMapper.toCliCommand(req, outputFile, tempDir.toString(), 1234); 177 // SVG is obsolete; toCliCommand must remap to html, not pass "-o svg" 178 assertFalse(cmd.contains("svg")); 179 assertTrue(cmd.contains("html")); 180 } 181 182 @Test 183 public void testCliCommandFallbackToOldScript() throws IOException { 184 // Do NOT create bin/asprof — only create profiler.sh as fallback 185 Files.createFile(tempDir.resolve("profiler.sh")); 186 187 ProfileServlet.ProfileRequest req = parseRequest(Collections.emptyMap()); 188 File outputFile = File.createTempFile("prof", ".html"); 189 outputFile.deleteOnExit(); 190 191 List<String> cmd = 192 ProfilerCommandMapper.toCliCommand(req, outputFile, tempDir.toString(), 1234); 193 assertEquals(tempDir.resolve("profiler.sh").toString(), cmd.get(0)); 194 } 195 196 @Test 197 public void testCliCommandAllOptions() throws IOException { 198 Path binDir = Files.createDirectories(tempDir.resolve("bin")); 199 Files.createFile(binDir.resolve("asprof")); 200 201 Map<String, String[]> flags = new HashMap<>(); 202 flags.put("thread", new String[] { "" }); 203 flags.put("simple", new String[] { "" }); 204 flags.put("reverse", new String[] { "" }); 205 206 ProfileServlet.ProfileRequest req = parseRequest(flags, "event", "alloc", "interval", "500", 207 "jstackdepth", "128", "bufsize", "50000", "width", "800", "height", "12", "minwidth", "1.0"); 208 File outputFile = File.createTempFile("prof", ".html"); 209 outputFile.deleteOnExit(); 210 211 List<String> cmd = ProfilerCommandMapper.toCliCommand(req, outputFile, tempDir.toString(), 99); 212 assertTrue(cmd.contains("-e")); 213 assertTrue(cmd.contains("alloc")); 214 assertTrue(cmd.contains("-i")); 215 assertTrue(cmd.contains("500")); 216 assertTrue(cmd.contains("-j")); 217 assertTrue(cmd.contains("128")); 218 assertTrue(cmd.contains("-b")); 219 assertTrue(cmd.contains("50000")); 220 assertTrue(cmd.contains("-t")); 221 assertTrue(cmd.contains("-s")); 222 assertTrue(cmd.contains("--width")); 223 assertTrue(cmd.contains("800")); 224 assertTrue(cmd.contains("--height")); 225 assertTrue(cmd.contains("12")); 226 assertTrue(cmd.contains("--minwidth")); 227 assertTrue(cmd.contains("1.0")); 228 assertTrue(cmd.contains("--reverse")); 229 } 230 231 // ---- Format mapping ---- 232 233 @Test 234 public void testOutputFormatMappingAllValues() { 235 assertEquals("summary", ProfilerCommandMapper.toFormatString(ProfileServlet.Output.SUMMARY)); 236 assertEquals("traces", ProfilerCommandMapper.toFormatString(ProfileServlet.Output.TRACES)); 237 assertEquals("flat", ProfilerCommandMapper.toFormatString(ProfileServlet.Output.FLAT)); 238 assertEquals("collapsed", 239 ProfilerCommandMapper.toFormatString(ProfileServlet.Output.COLLAPSED)); 240 assertEquals("tree", ProfilerCommandMapper.toFormatString(ProfileServlet.Output.TREE)); 241 assertEquals("jfr", ProfilerCommandMapper.toFormatString(ProfileServlet.Output.JFR)); 242 // SVG is obsolete in async-profiler 2.x+ — remapped to html 243 assertEquals("html", ProfilerCommandMapper.toFormatString(ProfileServlet.Output.SVG)); 244 assertEquals("html", ProfilerCommandMapper.toFormatString(ProfileServlet.Output.HTML)); 245 } 246 247 // ---- helpers ---- 248 249 private ProfileServlet.ProfileRequest parseRequest(Map<String, String[]> paramMap, 250 String... kvPairs) { 251 ProfileServlet servlet = new ProfileServlet(null); 252 HttpServletRequest req = Mockito.mock(HttpServletRequest.class); 253 Mockito.when(req.getParameterMap()).thenReturn(paramMap); 254 // defaults 255 String[] keys = { "pid", "duration", "output", "event", "interval", "jstackdepth", "bufsize", 256 "width", "height", "minwidth", "refreshDelay" }; 257 for (String k : keys) { 258 Mockito.when(req.getParameter(k)).thenReturn(null); 259 } 260 for (int i = 0; i < kvPairs.length; i += 2) { 261 Mockito.when(req.getParameter(kvPairs[i])).thenReturn(kvPairs[i + 1]); 262 } 263 return servlet.parseProfileRequest(req); 264 } 265}