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 java.io.File; 021import java.nio.file.Files; 022import java.nio.file.Path; 023import java.nio.file.Paths; 024import java.util.ArrayList; 025import java.util.List; 026import org.apache.yetus.audience.InterfaceAudience; 027import org.slf4j.Logger; 028import org.slf4j.LoggerFactory; 029 030/** 031 * Utility class that maps {@link ProfileServlet.ProfileRequest} to async-profiler commands in both 032 * the in-process Java API format (comma-separated string) and the CLI format (argument list). 033 */ 034@InterfaceAudience.Private 035final class ProfilerCommandMapper { 036 037 private static final Logger LOG = LoggerFactory.getLogger(ProfilerCommandMapper.class); 038 039 private static final String PROFILER_SCRIPT = "asprof"; 040 private static final String OLD_PROFILER_SCRIPT = "profiler.sh"; 041 042 private ProfilerCommandMapper() { 043 } 044 045 /** 046 * Builds the start command string for the async-profiler Java API. Format: 047 * {@code start,event=<event>[,interval=N][,jstackdepth=N][,threads][,simple]} 048 * <p> 049 * Note: {@code bufsize} is intentionally omitted — it is not a recognized option in the 050 * async-profiler 4.x agent grammar and is silently ignored. It remains supported by the 051 * BinaryBackend CLI path via {@code -b}. 052 */ 053 static String toLibraryStartCommand(ProfileServlet.ProfileRequest request) { 054 StringBuilder sb = new StringBuilder("start"); 055 sb.append(",event=").append(request.getEvent().getInternalName()); 056 appendOption(sb, "interval", request.getInterval()); 057 appendOption(sb, "jstackdepth", request.getJstackDepth()); 058 if (request.isThread()) { 059 sb.append(",threads"); 060 } 061 if (request.isSimple()) { 062 sb.append(",simple"); 063 } 064 return sb.toString(); 065 } 066 067 /** 068 * Builds the stop command string for the async-profiler Java API. Format: 069 * {@code stop,file=<path>[,<format-token>][,minwidth=N][,reverse]} 070 * <p> 071 * In async-profiler 4.x the output format is derived from the file extension for html/jfr, and 072 * via a bare token (e.g. {@code tree}, {@code flat}) for text-based formats. The {@code format=} 073 * key is not recognized. {@code width} and {@code height} are also not recognized by the 4.x 074 * agent grammar; they remain supported via the BinaryBackend CLI. 075 */ 076 static String toLibraryStopCommand(ProfileServlet.ProfileRequest request, File outputFile) { 077 StringBuilder sb = new StringBuilder("stop"); 078 sb.append(",file=").append(outputFile.getAbsolutePath()); 079 String fmt = toFormatString(request.getOutput()); 080 // html/jfr: format derived from file extension by async-profiler 4.x — no token needed. 081 // collapsed/tree/flat/traces/summary: must be passed as a bare token; the .collapsed 082 // extension is NOT auto-detected by detectOutputFormat in 4.x. 083 if (!fmt.equals("html") && !fmt.equals("jfr")) { 084 sb.append(",").append(fmt); 085 } 086 appendOption(sb, "minwidth", request.getMinwidth()); 087 if (request.isReverse()) { 088 sb.append(",reverse"); 089 } 090 return sb.toString(); 091 } 092 093 /** 094 * Builds the CLI argument list for invoking the async-profiler binary (asprof / profiler.sh). 095 * Locates the script under {@code <profilerHome>/bin/asprof}, falling back to 096 * {@code <profilerHome>/profiler.sh} for older installations. 097 */ 098 static List<String> toCliCommand(ProfileServlet.ProfileRequest request, File outputFile, 099 String profilerHome, Integer pid) { 100 List<String> cmd = new ArrayList<>(); 101 Path profilerScriptPath = Paths.get(profilerHome, "bin", PROFILER_SCRIPT); 102 if (!Files.exists(profilerScriptPath)) { 103 LOG.info("async-profiler script {} does not exist, falling back to {}(version <= 2.9).", 104 PROFILER_SCRIPT, OLD_PROFILER_SCRIPT); 105 profilerScriptPath = Paths.get(profilerHome, OLD_PROFILER_SCRIPT); 106 } 107 cmd.add(profilerScriptPath.toString()); 108 cmd.add("-e"); 109 cmd.add(request.getEvent().getInternalName()); 110 cmd.add("-d"); 111 cmd.add(String.valueOf(request.getDuration())); 112 cmd.add("-o"); 113 cmd.add(toFormatString(request.getOutput())); 114 cmd.add("-f"); 115 cmd.add(outputFile.getAbsolutePath()); 116 if (request.getInterval() != null) { 117 cmd.add("-i"); 118 cmd.add(request.getInterval().toString()); 119 } 120 if (request.getJstackDepth() != null) { 121 cmd.add("-j"); 122 cmd.add(request.getJstackDepth().toString()); 123 } 124 if (request.getBufsize() != null) { 125 cmd.add("-b"); 126 cmd.add(request.getBufsize().toString()); 127 } 128 if (request.isThread()) { 129 cmd.add("-t"); 130 } 131 if (request.isSimple()) { 132 cmd.add("-s"); 133 } 134 if (request.getWidth() != null) { 135 cmd.add("--width"); 136 cmd.add(request.getWidth().toString()); 137 } 138 if (request.getHeight() != null) { 139 cmd.add("--height"); 140 cmd.add(request.getHeight().toString()); 141 } 142 if (request.getMinwidth() != null) { 143 cmd.add("--minwidth"); 144 cmd.add(request.getMinwidth().toString()); 145 } 146 if (request.isReverse()) { 147 cmd.add("--reverse"); 148 } 149 cmd.add(pid.toString()); 150 return cmd; 151 } 152 153 /** 154 * Maps the {@link ProfileServlet.Output} enum to the format string used by both backends. Logs a 155 * deprecation warning when SVG is requested (it was removed in async-profiler 2.0, see 156 * HBASE-25685). Use {@link #toFileExtension} when only the file extension is needed and the 157 * warning has already been emitted. 158 */ 159 static String toFormatString(ProfileServlet.Output output) { 160 if (output == ProfileServlet.Output.SVG) { 161 LOG.warn("output=svg is obsolete (HBASE-25685); redirecting to html (FlameGraph). " 162 + "Use output=html explicitly."); 163 } 164 return toFileExtension(output); 165 } 166 167 /** 168 * Maps the {@link ProfileServlet.Output} enum to a file extension / format token without emitting 169 * any log warnings. SVG is silently remapped to {@code "html"}. 170 */ 171 static String toFileExtension(ProfileServlet.Output output) { 172 switch (output) { 173 case SUMMARY: 174 return "summary"; 175 case TRACES: 176 return "traces"; 177 case FLAT: 178 return "flat"; 179 case COLLAPSED: 180 return "collapsed"; 181 case TREE: 182 return "tree"; 183 case JFR: 184 return "jfr"; 185 case SVG: 186 // SVG was dropped in async-profiler 2.0 (HBASE-25685) and hard-errors in 4.x. 187 return "html"; 188 case HTML: 189 default: 190 return "html"; 191 } 192 } 193 194 private static void appendOption(StringBuilder sb, String key, Object value) { 195 if (value != null) { 196 sb.append(',').append(key).append('=').append(value); 197 } 198 } 199}