diff --git a/bvm/ballerina-profiler/build.gradle b/bvm/ballerina-profiler/build.gradle index 03bb0fd9497e..dd0b44fe5d35 100644 --- a/bvm/ballerina-profiler/build.gradle +++ b/bvm/ballerina-profiler/build.gradle @@ -24,12 +24,14 @@ dependencies { implementation "com.google.code.gson:gson:${project.gsonVersion}" implementation "commons-io:commons-io:${project.commonsIoVersion}" implementation project(':identifier-util') + implementation project(':ballerina-runtime') } version = 1.0 jar { dependsOn(':identifier-util:jar') + dependsOn(':ballerina-runtime:jar') from(sourceSets.main.output) from(sourceSets.main.java) { include "**/*.java" @@ -43,11 +45,6 @@ jar { duplicatesStrategy = DuplicatesStrategy.EXCLUDE manifest { - attributes 'Implementation-Title': 'Foobar', - 'Implementation-Version': '', - 'Built-By': System.getProperty('user.name'), - 'Built-Date': new Date(), - 'Built-JDK': System.getProperty('java.version'), - 'Main-Class': 'io.ballerina.runtime.profiler.Main' + attributes 'Main-Class': 'io.ballerina.runtime.profiler.Main' } } diff --git a/bvm/ballerina-profiler/spotbugs-exclude.xml b/bvm/ballerina-profiler/spotbugs-exclude.xml index 51596bdbb759..ec6fc1f4f90c 100644 --- a/bvm/ballerina-profiler/spotbugs-exclude.xml +++ b/bvm/ballerina-profiler/spotbugs-exclude.xml @@ -20,4 +20,8 @@ + + + + diff --git a/bvm/ballerina-profiler/src/main/java/io/ballerina/runtime/profiler/Profiler.java b/bvm/ballerina-profiler/src/main/java/io/ballerina/runtime/profiler/Profiler.java index 51b0d0a00abe..bbb1d0ab60ea 100644 --- a/bvm/ballerina-profiler/src/main/java/io/ballerina/runtime/profiler/Profiler.java +++ b/bvm/ballerina-profiler/src/main/java/io/ballerina/runtime/profiler/Profiler.java @@ -31,17 +31,13 @@ import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; -import java.io.PrintWriter; import java.net.URL; import java.net.URLClassLoader; -import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; -import java.util.HashSet; import java.util.List; -import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.jar.JarFile; import java.util.stream.Collectors; @@ -49,7 +45,9 @@ import java.util.zip.ZipInputStream; import static io.ballerina.runtime.profiler.util.Constants.CPU_PRE_JSON; +import static io.ballerina.runtime.profiler.util.Constants.CURRENT_DIR_KEY; import static io.ballerina.runtime.profiler.util.Constants.OUT_STREAM; +import static io.ballerina.runtime.profiler.util.Constants.PERFORMANCE_JSON; /** * This class is used to as the driver class of the Ballerina profiler. @@ -59,7 +57,6 @@ public class Profiler { private final long profilerStartTime; - private String skipFunctionString = null; private String balJarArgs = null; private String balJarName = null; private String targetDir = null; @@ -72,10 +69,12 @@ public class Profiler { private int balFunctionCount = 0; private int moduleCount = 0; private final ProfilerMethodWrapper profilerMethodWrapper; + private final String currentDir; public Profiler(long profilerStartTime) { this.profilerStartTime = profilerStartTime; this.profilerMethodWrapper = new ProfilerMethodWrapper(); + this.currentDir = System.getenv(CURRENT_DIR_KEY); } private void addShutdownHookAndCleanup() { @@ -88,13 +87,12 @@ private void addShutdownHookAndCleanup() { OUT_STREAM.printf("%s[6/6] Generating output...%s%n", Constants.ANSI_CYAN, Constants.ANSI_RESET); JsonParser jsonParser = new JsonParser(); HttpServer httpServer = new HttpServer(); - jsonParser.initializeCPUParser(skipFunctionString); - deleteFileIfExists("usedPathsList.txt"); - deleteFileIfExists(CPU_PRE_JSON); + String cpuFilePath = Paths.get(currentDir, CPU_PRE_JSON).toString(); + jsonParser.initializeCPUParser(cpuFilePath); + deleteFileIfExists(cpuFilePath); OUT_STREAM.printf(" ○ Execution time: %d seconds %n", profilerTotalTime / 1000); - deleteTempData(); httpServer.initializeHTMLExport(this.sourceRoot); - deleteFileIfExists("performance_report.json"); + deleteFileIfExists(PERFORMANCE_JSON); OUT_STREAM.println("--------------------------------------------------------------------------------"); } catch (IOException e) { throw new ProfilerException("Error occurred while generating the output", e); @@ -116,18 +114,6 @@ private void deleteFileIfExists(String filePath) { } } - private void deleteTempData() { - String filePrefix = "jartmp"; - File[] files = new File(System.getProperty("user.dir")).listFiles(); - if (files != null) { - for (File file : files) { - if (file.getName().startsWith(filePrefix)) { - FileUtils.deleteQuietly(file); - } - } - } - } - private void printHeader() { OUT_STREAM.printf("%n%s================================================================================%s", Constants.ANSI_GRAY, Constants.ANSI_RESET); @@ -154,19 +140,15 @@ private void handleProfilerArguments(String[] args) { this.balJarArgs = extractBalJarArgs(args[i + 1]); addToUsedArgs(args, usedArgs, i); } - case "--skip" -> { - this.skipFunctionString = extractSkipFunctionString(args[i + 1]); - addToUsedArgs(args, usedArgs, i); - } case "--target" -> { this.targetDir = args[i + 1]; addToUsedArgs(args, usedArgs, i); } - case "--sourceroot" -> { + case "--source-root" -> { this.sourceRoot = args[i + 1]; addToUsedArgs(args, usedArgs, i); } - case "--profilerDebug" -> { + case "--profiler-debug" -> { this.profilerDebugArg = args[i + 1]; addToUsedArgs(args, usedArgs, i); } @@ -189,13 +171,6 @@ private String extractBalJarArgs(String value) { return value.substring(1, value.length() - 1); } - private String extractSkipFunctionString(String value) { - if (value == null || !value.matches("\\[.*\\]")) { - throw new ProfilerException("Invalid skip functions found: " + value); - } - return value.substring(1, value.length() - 1); - } - private void handleUnrecognizedArgument(String argument, List usedArgs) { if (!usedArgs.contains(argument)) { throw new ProfilerException("Unrecognized argument found: " + argument); @@ -242,16 +217,15 @@ private void initializeProfiling() throws ProfilerException { new File(balJarName).toURI().toURL()})); ProfilerClassLoader profilerClassLoader = new ProfilerClassLoader(new URLClassLoader(new URL[]{ new File(balJarName).toURI().toURL()})); - Set usedPaths = new HashSet<>(); for (String className : classNames) { - if (mainClassPackage == null) { + if (mainClassPackage == null || className.contains("$gen$")) { continue; } if (className.startsWith(mainClassPackage.split("/")[0]) || utilPaths.contains(className)) { try (InputStream inputStream = jarFile.getInputStream(jarFile.getJarEntry(className))) { - byte[] code = profilerMethodWrapper.modifyMethods(inputStream); + String sourceClassName = className.replace(Constants.CLASS_SUFFIX, ""); + byte[] code = profilerMethodWrapper.modifyMethods(inputStream, sourceClassName); profilerClassLoader.loadClass(code); - usedPaths.add(className.replace(Constants.CLASS_SUFFIX, "").replace("/", ".")); profilerMethodWrapper.printCode(className, code, getFileNameWithoutExtension(balJarName)); } } @@ -260,9 +234,6 @@ private void initializeProfiling() throws ProfilerException { } } OUT_STREAM.printf(" ○ Instrumented module count: %d%n", moduleCount); - try (PrintWriter printWriter = new PrintWriter("usedPathsList.txt", StandardCharsets.UTF_8)) { - printWriter.println(String.join(", ", usedPaths)); - } OUT_STREAM.printf(" ○ Instrumented function count: %d%n", balFunctionCount); modifyJar(); } catch (Throwable throwable) { diff --git a/bvm/ballerina-profiler/src/main/java/io/ballerina/runtime/profiler/codegen/NonStrandCheckAdapter.java b/bvm/ballerina-profiler/src/main/java/io/ballerina/runtime/profiler/codegen/NonStrandCheckAdapter.java deleted file mode 100644 index 7d67330893a4..000000000000 --- a/bvm/ballerina-profiler/src/main/java/io/ballerina/runtime/profiler/codegen/NonStrandCheckAdapter.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (c) 2023, WSO2 LLC. (https://www.wso2.com) All Rights Reserved. - * - * WSO2 LLC. licenses this file to you 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 io.ballerina.runtime.profiler.codegen; - -import org.objectweb.asm.MethodVisitor; -import org.objectweb.asm.Opcodes; -import org.objectweb.asm.commons.AdviceAdapter; - -import static io.ballerina.runtime.profiler.util.Constants.GET_INSTANCE_DESCRIPTOR; -import static io.ballerina.runtime.profiler.util.Constants.PROFILE_ANALYZER; - -/** - * This class is used as the advice adapter for the Ballerina profiler. - * This class only manages the functions that doesn't contain the strand parameter. - * - * @since 2201.8.0 - */ -public class NonStrandCheckAdapter extends AdviceAdapter { - - public NonStrandCheckAdapter(int access, MethodVisitor mv, String methodName, String description) { - super(Opcodes.ASM9, mv, access, methodName, description); - } - - @Override - protected void onMethodEnter() { - mv.visitMethodInsn(INVOKESTATIC, PROFILE_ANALYZER, "getInstance", GET_INSTANCE_DESCRIPTOR, false); - mv.visitMethodInsn(INVOKEVIRTUAL, PROFILE_ANALYZER, "start", "()V", false); - } - - @Override - protected void onMethodExit(int opcode) { - mv.visitMethodInsn(INVOKESTATIC, PROFILE_ANALYZER, "getInstance", GET_INSTANCE_DESCRIPTOR, false); - mv.visitMethodInsn(INVOKEVIRTUAL, PROFILE_ANALYZER, "stop", "()V", false); - } -} diff --git a/bvm/ballerina-profiler/src/main/java/io/ballerina/runtime/profiler/codegen/ProfilerClassVisitor.java b/bvm/ballerina-profiler/src/main/java/io/ballerina/runtime/profiler/codegen/ProfilerClassVisitor.java index b8b85812c6f1..30547efb5608 100644 --- a/bvm/ballerina-profiler/src/main/java/io/ballerina/runtime/profiler/codegen/ProfilerClassVisitor.java +++ b/bvm/ballerina-profiler/src/main/java/io/ballerina/runtime/profiler/codegen/ProfilerClassVisitor.java @@ -32,19 +32,20 @@ */ public class ProfilerClassVisitor extends ClassVisitor { - public ProfilerClassVisitor(ClassVisitor classVisitor) { + private final String className; + + public ProfilerClassVisitor(String className, ClassVisitor classVisitor) { super(Opcodes.ASM9, classVisitor); + this.className = className; } @Override public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { MethodVisitor methodVisitor = super.visitMethod(access, name, desc, signature, exceptions); - if (!name.startsWith("$") && desc.startsWith(STRAND_ARG)) { + if (desc.startsWith(STRAND_ARG) && !name.contains("$gen$")) { Main.incrementBalFunctionCount(); + return new StrandCheckAdapter(className, access, methodVisitor, name, desc, (access & Opcodes.ACC_STATIC)); } - if (desc.startsWith(STRAND_ARG)) { - return new StrandCheckAdapter(access, methodVisitor, name, desc, (access & Opcodes.ACC_STATIC)); - } - return new NonStrandCheckAdapter(access, methodVisitor, name, desc); + return methodVisitor; } } diff --git a/bvm/ballerina-profiler/src/main/java/io/ballerina/runtime/profiler/codegen/ProfilerMethodWrapper.java b/bvm/ballerina-profiler/src/main/java/io/ballerina/runtime/profiler/codegen/ProfilerMethodWrapper.java index 10f1cd62a1a5..6b1e802a27f0 100644 --- a/bvm/ballerina-profiler/src/main/java/io/ballerina/runtime/profiler/codegen/ProfilerMethodWrapper.java +++ b/bvm/ballerina-profiler/src/main/java/io/ballerina/runtime/profiler/codegen/ProfilerMethodWrapper.java @@ -34,13 +34,16 @@ import java.net.URL; import java.net.URLClassLoader; import java.nio.charset.StandardCharsets; +import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; import java.util.jar.Attributes; import java.util.jar.Manifest; +import static io.ballerina.runtime.profiler.util.Constants.CURRENT_DIR_KEY; import static io.ballerina.runtime.profiler.util.Constants.ERROR_STREAM; import static io.ballerina.runtime.profiler.util.Constants.OUT_STREAM; +import static io.ballerina.runtime.profiler.util.Constants.USER_DIR; /** * This class is used as the method wrapper for the Ballerina profiler. @@ -57,12 +60,13 @@ public void invokeMethods(String debugArg) throws IOException, InterruptedExcept if (debugArg != null) { commands.add(debugArg); } - commands.add(Constants.TEMP_JAR_FILE_NAME); + commands.add(Paths.get(System.getProperty(USER_DIR), Constants.TEMP_JAR_FILE_NAME).toString()); if (balJarArgs != null) { commands.add(balJarArgs); } ProcessBuilder processBuilder = new ProcessBuilder(commands); - processBuilder.redirectErrorStream(true); + processBuilder.inheritIO(); + processBuilder.directory(new File(System.getenv(CURRENT_DIR_KEY))); Process process = processBuilder.start(); OUT_STREAM.printf(Constants.ANSI_CYAN + "[5/6] Running executable..." + Constants.ANSI_RESET + "%n"); try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream(), @@ -84,13 +88,13 @@ public String mainClassFinder(URLClassLoader manifestClassLoader) { } } - public byte[] modifyMethods(InputStream inputStream) { + public byte[] modifyMethods(InputStream inputStream, String className) { byte[] code; try { ClassReader reader = new ClassReader(inputStream); ClassWriter classWriter = new ProfilerClassWriter(reader, ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES); - ClassVisitor change = new ProfilerClassVisitor(classWriter); + ClassVisitor change = new ProfilerClassVisitor(className, classWriter); reader.accept(change, ClassReader.EXPAND_FRAMES); code = classWriter.toByteArray(); return code; diff --git a/bvm/ballerina-profiler/src/main/java/io/ballerina/runtime/profiler/codegen/StrandCheckAdapter.java b/bvm/ballerina-profiler/src/main/java/io/ballerina/runtime/profiler/codegen/StrandCheckAdapter.java index dc77cb595f04..5934bfda0835 100644 --- a/bvm/ballerina-profiler/src/main/java/io/ballerina/runtime/profiler/codegen/StrandCheckAdapter.java +++ b/bvm/ballerina-profiler/src/main/java/io/ballerina/runtime/profiler/codegen/StrandCheckAdapter.java @@ -18,33 +18,48 @@ package io.ballerina.runtime.profiler.codegen; +import io.ballerina.runtime.internal.scheduling.Strand; +import io.ballerina.runtime.profiler.runtime.Data; import org.objectweb.asm.Label; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; import org.objectweb.asm.commons.AdviceAdapter; +import static io.ballerina.runtime.profiler.util.Constants.DATA_CLASS; import static io.ballerina.runtime.profiler.util.Constants.GET_INSTANCE_DESCRIPTOR; import static io.ballerina.runtime.profiler.util.Constants.PROFILE_ANALYZER; -import static io.ballerina.runtime.profiler.util.Constants.STRAND_CLASS; +import static io.ballerina.runtime.profiler.util.Constants.START_PROFILE_DESCRIPTOR; +import static io.ballerina.runtime.profiler.util.Constants.STOP_PROFILE_DESCRIPTOR; /** * This class is used as the advice adapter for the Ballerina profiler. - * This class only manages the functions that contain the strand parameter. + * This will wrap the ballerina methods with + * {@link io.ballerina.runtime.profiler.runtime.ProfileAnalyzer#start(Strand, String, String)} and + * {@link io.ballerina.runtime.profiler.runtime.ProfileAnalyzer#stop(Strand, Data)} methods. * * @since 2201.8.0 */ public class StrandCheckAdapter extends AdviceAdapter { + private final String className; + private final String methodName; + private final Type dataType; Label tryStart = new Label(); int load; + int stackKeyIndex; - public StrandCheckAdapter(int access, MethodVisitor mv, String methodName, String description, int load) { + public StrandCheckAdapter(String className, int access, MethodVisitor mv, String methodName, + String description, int load) { super(Opcodes.ASM9, mv, access, methodName, description); + this.className = className; + this.methodName = methodName; if (load == 0) { this.load = 1; } else { this.load = 0; } + this.dataType = Type.getObjectType(DATA_CLASS); } // It adds a label to the try block of the wrapped method. @@ -57,10 +72,13 @@ public void visitCode() { // It retrieves the profiler instance, gets the strand id and starts the profiling. @Override protected void onMethodEnter() { + this.stackKeyIndex = this.newLocal(dataType); mv.visitMethodInsn(INVOKESTATIC, PROFILE_ANALYZER, "getInstance", GET_INSTANCE_DESCRIPTOR, false); mv.visitVarInsn(ALOAD, load); - mv.visitMethodInsn(INVOKEVIRTUAL, STRAND_CLASS, "getId", "()I", false); - mv.visitMethodInsn(INVOKEVIRTUAL, PROFILE_ANALYZER, "start", "(I)V", false); + mv.visitLdcInsn(className); + mv.visitLdcInsn(methodName); + mv.visitMethodInsn(INVOKEVIRTUAL, PROFILE_ANALYZER, "start", START_PROFILE_DESCRIPTOR, false); + mv.visitVarInsn(ASTORE, this.stackKeyIndex); } // If the exit is not due to an exception, it calls the onFinally method. @@ -88,12 +106,7 @@ public void visitMaxs(int maxStack, int maxLocals) { private void onFinally() { mv.visitMethodInsn(INVOKESTATIC, PROFILE_ANALYZER, "getInstance", GET_INSTANCE_DESCRIPTOR, false); mv.visitVarInsn(ALOAD, load); - mv.visitMethodInsn(INVOKEVIRTUAL, STRAND_CLASS, - "getState", "()Lio/ballerina/runtime/internal/scheduling/State;", false); - mv.visitMethodInsn(INVOKEVIRTUAL, "io/ballerina/runtime/internal/scheduling/State", - "toString", "()Ljava/lang/String;", false); - mv.visitVarInsn(ALOAD, load); - mv.visitMethodInsn(INVOKEVIRTUAL, STRAND_CLASS, "getId", "()I", false); - mv.visitMethodInsn(INVOKEVIRTUAL, PROFILE_ANALYZER, "stop", "(Ljava/lang/String;I)V", false); + mv.visitVarInsn(ALOAD, this.stackKeyIndex); + mv.visitMethodInsn(INVOKEVIRTUAL, PROFILE_ANALYZER, "stop", STOP_PROFILE_DESCRIPTOR, false); } } diff --git a/bvm/ballerina-profiler/src/main/java/io/ballerina/runtime/profiler/runtime/Data.java b/bvm/ballerina-profiler/src/main/java/io/ballerina/runtime/profiler/runtime/Data.java index 561b23f51033..027e8ea15747 100644 --- a/bvm/ballerina-profiler/src/main/java/io/ballerina/runtime/profiler/runtime/Data.java +++ b/bvm/ballerina-profiler/src/main/java/io/ballerina/runtime/profiler/runtime/Data.java @@ -18,6 +18,7 @@ package io.ballerina.runtime.profiler.runtime; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; /** @@ -26,48 +27,33 @@ * @since 2201.8.0 */ public class Data { - private final String name; - private long startTime; - private long totalTime; - private long minTime; - private long maxTime; - - public Data(String name) { - this.name = name; + protected final String stackKey; + protected final String stackIndex; + protected String stackTrace = null; + public long totalTime; + private final ConcurrentHashMap startTimes = new ConcurrentHashMap<>(); + + public Data(String stackIndex, String stackKey) { + this.stackIndex = stackIndex; + this.stackKey = stackKey; this.totalTime = 0L; - this.startTime = 0L; - this.minTime = Long.MAX_VALUE; - this.maxTime = Long.MIN_VALUE; } - public void start() { - this.startTime = TimeUnit.MILLISECONDS.convert(System.nanoTime(), TimeUnit.NANOSECONDS); + public synchronized void start(String strandId) { + if (this.startTimes.containsKey(strandId)) { + return; + } + startTimes.put(strandId, TimeUnit.MILLISECONDS.convert(System.nanoTime(), TimeUnit.NANOSECONDS)); } - public void stop() { - long elapsed = TimeUnit.MILLISECONDS.convert(System.nanoTime(), TimeUnit.NANOSECONDS) - this.startTime; - if (elapsed < this.minTime) { - this.minTime = elapsed; - } - if (elapsed > this.maxTime) { - this.maxTime = elapsed; - } + public synchronized void stop(String strandId) { + long elapsed = TimeUnit.MILLISECONDS.convert(System.nanoTime(), + TimeUnit.NANOSECONDS) - this.startTimes.remove(strandId); this.totalTime += elapsed; } private String getFormattedStats() { - int time = (int) this.totalTime; - String[] stackTrace = new String[]{this.name}; - - StringBuilder sb = new StringBuilder(); - sb.append("{"); - sb.append("\"time\": \"").append(time).append("\", "); - sb.append("\"stackTrace\": "); - for (String s : stackTrace) { - sb.append(s); - } - sb.append("},"); - return sb.toString(); + return "{" + "\"time\": \"" + this.totalTime + "\", " + "\"stackTrace\": " + this.stackTrace + "}"; } @Override diff --git a/bvm/ballerina-profiler/src/main/java/io/ballerina/runtime/profiler/runtime/ProfileAnalyzer.java b/bvm/ballerina-profiler/src/main/java/io/ballerina/runtime/profiler/runtime/ProfileAnalyzer.java index 8a170fdba260..a704a6ccfbb5 100644 --- a/bvm/ballerina-profiler/src/main/java/io/ballerina/runtime/profiler/runtime/ProfileAnalyzer.java +++ b/bvm/ballerina-profiler/src/main/java/io/ballerina/runtime/profiler/runtime/ProfileAnalyzer.java @@ -18,22 +18,18 @@ package io.ballerina.runtime.profiler.runtime; -import io.ballerina.identifier.Utils; +import io.ballerina.runtime.internal.scheduling.State; +import io.ballerina.runtime.internal.scheduling.Strand; import java.io.FileWriter; import java.io.IOException; import java.io.Writer; import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Paths; import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.stream.Collectors; +import java.util.concurrent.ConcurrentHashMap; + +import static io.ballerina.runtime.profiler.util.Constants.CPU_PRE_JSON; +import static io.ballerina.runtime.profiler.util.Constants.STRAND_PROFILER_STACK_PROPERTY; /** * This class is used as the main profiler class for the Ballerina profiler. @@ -42,12 +38,7 @@ */ public class ProfileAnalyzer { - private final HashMap profiles = new HashMap<>(); - private final ArrayList profilesStack = new ArrayList<>(); - private final Set blockedMethods = new HashSet<>(); - private static final List skippedList = new ArrayList<>(); - private static final Set skippedClasses = new HashSet<>(skippedList); - private static final String CPU_PRE_JSON = "cpu_pre.json"; + private final ConcurrentHashMap profiles = new ConcurrentHashMap<>(); private static class ProfilerHolder { private static final ProfileAnalyzer PROFILER_INSTANCE = new ProfileAnalyzer(); @@ -59,77 +50,48 @@ public static ProfileAnalyzer getInstance() { private ProfileAnalyzer() { addProfilerShutDownHook(); - try { - String content = Files.readString(Paths.get("usedPathsList.txt")); - List skippedListRead = new ArrayList<>(Arrays.asList(content.split(", "))); - skippedList.addAll(skippedListRead); - skippedClasses.addAll(skippedList); - } catch (IOException ignored) { - throw new ProfilerRuntimeException("Error occurred while reading the usedPathsList.txt file"); - } } - private String getMethodName() { - final List stack = StackWalker.getInstance().walk(s -> s.collect(Collectors.toList())); - return stack.get(2).getMethodName() + "()"; - } - - public void start(int id) { - if (!blockedMethods.contains(getMethodName() + id)) { - ArrayList stackTrace = getStackTrace(); - String stackKey = StackTraceMap.getStackKey(stackTrace); - Data p; - if (this.profiles.containsKey(stackKey)) { - p = this.profiles.get(stackKey); - } else { - Data newData = new Data(stackTrace.toString()); - this.profiles.put(stackKey, newData); - this.profilesStack.add(newData); - p = newData; - } - p.start(); - } - blockedMethods.remove(getMethodName() + id); - } - - public void start() { - ArrayList stackTrace = getStackTrace(); - String stackKey = StackTraceMap.getStackKey(stackTrace); - Data p = this.profiles.get(stackKey); - if (p == null) { - p = new Data(stackTrace.toString()); - this.profiles.put(stackKey, p); - this.profilesStack.add(p); + public Data start(Strand strand, String className, String methodName) { + Object stacksObj = strand.getProperty(STRAND_PROFILER_STACK_PROPERTY); + String stackIndex = StackTraceMap.getStackIndex(className + "." + methodName) + "$"; + String stackKey; + if (stacksObj != null) { + stackKey = stacksObj + stackIndex; + } else { + stackKey = stackIndex; } - p.start(); - } - - public void stop(String strandState, int id) { - String stackKey = StackTraceMap.getStackKey(getStackTrace()); - Data p = this.profiles.get(stackKey); - if (strandState.equals("RUNNABLE")) { - if (p != null) { - p.stop(); - } + Data data; + if (this.profiles.containsKey(stackKey)) { + data = this.profiles.get(stackKey); } else { - blockedMethods.add(getMethodName() + id); + data = new Data(stackIndex, stackKey); + this.profiles.put(stackKey, data); } + data.start(String.valueOf(strand.getId())); + strand.setProperty(STRAND_PROFILER_STACK_PROPERTY, stackKey); + return data; } - public void stop() { - String stackKey = StackTraceMap.getStackKey(getStackTrace()); - Data p = this.profiles.get(stackKey); - if (p != null) { - p.stop(); + public void stop(Strand strand, Data data) { + if (strand.getState().equals(State.RUNNABLE)) { + data.stop(String.valueOf(strand.getId())); } + strand.setProperty(STRAND_PROFILER_STACK_PROPERTY, data.stackKey.substring(0, + data.stackKey.length() - data.stackIndex.length())); } public final String getProfileStackString() { StringBuilder sb = new StringBuilder("["); - ArrayList stackList = new ArrayList<>(this.profilesStack); - for (Data data : stackList) { - sb.append(data).append("\n"); + ArrayList dataList = new ArrayList<>(this.profiles.values()); + for (int i = 0; i < (dataList.size() - 1); i++) { + Data data = dataList.get(i); + data.stackTrace = StackTraceMap.getCallStackString(data.stackKey); + sb.append(data).append(",\n"); } + Data data = dataList.get(dataList.size() - 1); + data.stackTrace = StackTraceMap.getCallStackString(data.stackKey); + sb.append(data).append("\n"); sb.append("]"); return sb.toString(); } @@ -149,32 +111,4 @@ private void addProfilerShutDownHook() { profiler.printProfilerOutput(profiler.getProfileStackString()); })); } - - // This method returns a string representation of the current call stack in the form of a list of strings - private ArrayList getStackTrace() { - ArrayList result = new ArrayList<>(); - - final List stack = StackWalker.getInstance().walk(s -> s.collect(Collectors.toList())); - //Removes the first 2 stack frames (index 0 and 1) and reverses the order of the remaining stack frames - stack.subList(0, 2).clear(); - Collections.reverse(stack); //Reverse the collection - - for (StackWalker.StackFrame frame : stack) { - if (skippedClasses.contains(frame.getClassName())) { - String frameString = frame.toString(); - - if (frameString.contains("&")) { - frameString = "\"" + frameString + "\""; - } else { - frameString = "\"" + frameString.replaceAll("\\(.*\\)", "") + "()" + "\""; - int lastDotIndex = frameString.lastIndexOf('.'); - frameString = frameString.substring(0, lastDotIndex).replace('.', '/') + - frameString.substring(lastDotIndex); - } - result.add(Utils.decodeIdentifier(frameString)); - } - } - - return result; - } } diff --git a/bvm/ballerina-profiler/src/main/java/io/ballerina/runtime/profiler/runtime/StackTraceMap.java b/bvm/ballerina-profiler/src/main/java/io/ballerina/runtime/profiler/runtime/StackTraceMap.java index 9d448f53a28a..8f208758e739 100644 --- a/bvm/ballerina-profiler/src/main/java/io/ballerina/runtime/profiler/runtime/StackTraceMap.java +++ b/bvm/ballerina-profiler/src/main/java/io/ballerina/runtime/profiler/runtime/StackTraceMap.java @@ -18,9 +18,10 @@ package io.ballerina.runtime.profiler.runtime; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Map; +import io.ballerina.identifier.Utils; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; /** * A map that keeps profile stack the index values against the stack-trace. @@ -29,23 +30,38 @@ */ public class StackTraceMap { - private static int localVarIndex = 0; + private static final AtomicInteger localVarIndex = new AtomicInteger(0); + private static final ConcurrentHashMap stackTraceIndexMap = new ConcurrentHashMap<>(); + private static final ConcurrentHashMap indexStackTraceMap = new ConcurrentHashMap<>(); private StackTraceMap() { } - private static final Map stackTraceIndexMap = new HashMap<>(); - - static String getStackKey(ArrayList stacks) { - StringBuilder keyBuilder = new StringBuilder(); - for (String stack : stacks) { - if (stackTraceIndexMap.containsKey(stack)) { - keyBuilder.append(stackTraceIndexMap.get(stack)); - } else { - stackTraceIndexMap.put(stack, localVarIndex); - keyBuilder.append(localVarIndex++); - } - } - return keyBuilder.toString(); + static String getStackIndex(String stackElement) { + Integer stackIndex = stackTraceIndexMap.get(stackElement); + if (stackIndex != null) { + return String.valueOf(stackIndex); + } + int index = localVarIndex.getAndIncrement(); + stackTraceIndexMap.put(stackElement, index); + String indexStr = String.valueOf(index); + indexStackTraceMap.put(indexStr, stackElement); + return indexStr; + } + + static String getCallStackString(String stackKey) { + String[] stackElements = stackKey.split("\\$"); + StringBuilder sb = new StringBuilder(); + sb.append("["); + for (int i = 0; i < (stackElements.length - 1); i++) { + sb.append("\"").append(decodeStackElement(indexStackTraceMap.get(stackElements[i]))).append("\","); + } + sb.append("\"").append(decodeStackElement(indexStackTraceMap.get(stackElements[stackElements.length - 1]))) + .append("\"]"); + return sb.toString(); + } + + private static String decodeStackElement(String stackElement) { + return Utils.decodeIdentifier(stackElement.replaceAll("\\$value\\$", "")); } } diff --git a/bvm/ballerina-profiler/src/main/java/io/ballerina/runtime/profiler/ui/FileUtils.java b/bvm/ballerina-profiler/src/main/java/io/ballerina/runtime/profiler/ui/FileUtils.java index 3901f34a1ee3..c7ebaa7f01da 100644 --- a/bvm/ballerina-profiler/src/main/java/io/ballerina/runtime/profiler/ui/FileUtils.java +++ b/bvm/ballerina-profiler/src/main/java/io/ballerina/runtime/profiler/ui/FileUtils.java @@ -18,6 +18,8 @@ package io.ballerina.runtime.profiler.ui; +import io.ballerina.runtime.profiler.runtime.ProfilerRuntimeException; + import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; @@ -25,15 +27,29 @@ public class FileUtils { + // Maximum wait time for the file to be created. This will wait 600*100 ms = 60 s. + private static final int MAX_WAIT_TIME_FOR_FILE = 600; + + private FileUtils() { + } + static String readFileAsString(String file) throws IOException { Path path = Paths.get(file); + int count = 0; while (!Files.exists(path)) { - try { - Thread.sleep(100); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); + if (count++ > MAX_WAIT_TIME_FOR_FILE) { + throw new ProfilerRuntimeException("File not found: " + file); } + waitForFile(); } return Files.readString(path); } + + private static void waitForFile() { + try { + Thread.sleep(100); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } } diff --git a/bvm/ballerina-profiler/src/main/java/io/ballerina/runtime/profiler/ui/FrontEnd.java b/bvm/ballerina-profiler/src/main/java/io/ballerina/runtime/profiler/ui/FrontEnd.java index c6ee1763c5c9..8d97a7d10f45 100644 --- a/bvm/ballerina-profiler/src/main/java/io/ballerina/runtime/profiler/ui/FrontEnd.java +++ b/bvm/ballerina-profiler/src/main/java/io/ballerina/runtime/profiler/ui/FrontEnd.java @@ -48,7 +48,6 @@ String getSiteData(String contents) { public String readFileAsString() throws IOException { StringBuilder sb = new StringBuilder(); - try (InputStream inputStream = getClass().getClassLoader().getResourceAsStream(FILE_LOCATION)) { if (inputStream == null) { throw new ProfilerRuntimeException("resource file not found: " + FILE_LOCATION); diff --git a/bvm/ballerina-profiler/src/main/java/io/ballerina/runtime/profiler/ui/HttpServer.java b/bvm/ballerina-profiler/src/main/java/io/ballerina/runtime/profiler/ui/HttpServer.java index 35b176f872f7..1ed16745e255 100644 --- a/bvm/ballerina-profiler/src/main/java/io/ballerina/runtime/profiler/ui/HttpServer.java +++ b/bvm/ballerina-profiler/src/main/java/io/ballerina/runtime/profiler/ui/HttpServer.java @@ -25,6 +25,7 @@ import java.nio.charset.StandardCharsets; import static io.ballerina.runtime.profiler.util.Constants.OUT_STREAM; +import static io.ballerina.runtime.profiler.util.Constants.PERFORMANCE_JSON; /** * This class contains the HTTP server of the Ballerina profiler. @@ -36,7 +37,7 @@ public class HttpServer { public void initializeHTMLExport(String sourceRoot) throws IOException { OUT_STREAM.printf(" ○ Output: " + Constants.ANSI_YELLOW + "%s/ProfilerOutput.html" + Constants.ANSI_RESET + "%n", sourceRoot); - String content = FileUtils.readFileAsString("performance_report.json"); + String content = FileUtils.readFileAsString(PERFORMANCE_JSON); FrontEnd frontEnd = new FrontEnd(); String htmlData = frontEnd.getSiteData(content); String fileName = "ProfilerOutput.html"; diff --git a/bvm/ballerina-profiler/src/main/java/io/ballerina/runtime/profiler/ui/JsonParser.java b/bvm/ballerina-profiler/src/main/java/io/ballerina/runtime/profiler/ui/JsonParser.java index eaaf42d73fe3..3ab992b17baf 100644 --- a/bvm/ballerina-profiler/src/main/java/io/ballerina/runtime/profiler/ui/JsonParser.java +++ b/bvm/ballerina-profiler/src/main/java/io/ballerina/runtime/profiler/ui/JsonParser.java @@ -27,11 +27,10 @@ import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; -import static io.ballerina.runtime.profiler.util.Constants.CPU_PRE_JSON; import static io.ballerina.runtime.profiler.util.Constants.OUT_STREAM; +import static io.ballerina.runtime.profiler.util.Constants.PERFORMANCE_JSON; /** * This class contains the JSON parser of the Ballerina profiler. @@ -42,17 +41,19 @@ public class JsonParser { private static final String VALUE_KEY = "value"; - public void initializeCPUParser(String skipFunctionString) { - ArrayList skipList = new ArrayList<>(); - skipList = skipFunctionString != null ? parseSkipFunctionStringToList(skipFunctionString) : skipList; - skipList.add("$gen"); - skipList.add("getAnonType"); - cpuParser(skipList); - } - - private ArrayList parseSkipFunctionStringToList(String skipFunctionString) { - String[] elements = skipFunctionString.replace("[", "").replace("]", "").split(", "); - return new ArrayList<>(Arrays.asList(elements)); + public void initializeCPUParser(String cpuFilePath) { + try { + String jsonInput = FileUtils.readFileAsString(cpuFilePath); + List input = populateStackTraceItems(jsonInput); + // Create a Data object to store the output + Data output = new Data("Root", input.get(0).time, new ArrayList<>()); + for (StackTraceItem stackTraceItem : input) { + analyseStackTraceItems(stackTraceItem, output); + } + writeToValueJson(output); + } catch (Exception throwable) { + OUT_STREAM.println(throwable + "%n"); + } } private int getTotalTime(JsonObject node) { @@ -70,7 +71,7 @@ private int getTotalTime(JsonObject node) { private void writePerformanceJson(String parsedJson) { parsedJson = "var data = " + parsedJson; - try (FileWriter myWriter = new FileWriter("performance_report.json", StandardCharsets.UTF_8)) { + try (FileWriter myWriter = new FileWriter(PERFORMANCE_JSON, StandardCharsets.UTF_8)) { myWriter.write(parsedJson); myWriter.flush(); } catch (IOException e) { @@ -78,51 +79,10 @@ private void writePerformanceJson(String parsedJson) { } } - private boolean containsAnySkipList(String str, List arrayList) { - for (String s : arrayList) { - if (str.contains(s)) { - return true; - } - } - return false; - } - - private void cpuParser(ArrayList skipList) { - try { - String jsonInput = FileUtils.readFileAsString(CPU_PRE_JSON); - StringBuilder jsonInputStringBuffer = new StringBuilder(jsonInput); - if (jsonInputStringBuffer.length() > 3) { - // Removes the trailing comma - jsonInputStringBuffer.deleteCharAt(jsonInputStringBuffer.length() - 3); - } - jsonInput = jsonInputStringBuffer.toString(); - List input = populateStackTraceItems(jsonInput); - // Create a Data object to store the output - Data output = new Data("Root", input.get(0).time, new ArrayList<>()); - for (StackTraceItem stackTraceItem : input) { - if (stackTraceItem.stackTrace.size() == 1) { - output.value = Math.max(output.value, stackTraceItem.time); - } else { - analyseStackTraceItems(skipList, stackTraceItem, output); - } - } - writeToValueJson(output); - } catch (Exception throwable) { - OUT_STREAM.println(throwable + "%n"); - } - } - - private void analyseStackTraceItems(ArrayList skipList, StackTraceItem stackTraceItem, Data output) { + private void analyseStackTraceItems(StackTraceItem stackTraceItem, Data output) { Data current = output; for (int i = 1; i < stackTraceItem.stackTrace.size(); i++) { - String name = stackTraceItem.stackTrace.get(i); - if (name.contains("$configureInit()")) { - removeChildrenByNodeName(output, name); - break; - } - if (!containsAnySkipList(name, skipList)) { - current = populateChildNodes(stackTraceItem, current, name); - } + current = populateChildNodes(stackTraceItem, current, stackTraceItem.stackTrace.get(i)); } } @@ -136,22 +96,16 @@ private void writeToValueJson(Data output) { writePerformanceJson(jsonObject.toString()); } - private Data populateChildNodes(StackTraceItem stackTraceItem, Data current, String name) { - boolean found = false; + private Data populateChildNodes(StackTraceItem stackTraceItem, Data current, String stackTrace) { for (Data child : current.children) { - if (child.name.equals(name)) { + if (child.name.equals(stackTrace)) { child.value = Math.max(child.value, stackTraceItem.time); - current = child; - found = true; - break; + return child; } } - if (!found) { - Data newChild = new Data(name, stackTraceItem.time, new ArrayList<>()); - current.children.add(newChild); - current = newChild; - } - return current; + Data newChild = new Data(stackTrace, stackTraceItem.time, new ArrayList<>()); + current.children.add(newChild); + return newChild; } private List populateStackTraceItems(String jsonInput) { @@ -165,16 +119,6 @@ private List populateStackTraceItems(String jsonInput) { return stackTraceItems; } - private void removeChildrenByNodeName(Data node, String nodeName) { - if (node.name != null && node.name.equals(nodeName)) { - node.children.clear(); - return; - } - for (Data child : node.children) { - removeChildrenByNodeName(child, nodeName); - } - } - /** * This class is used as a custom data class. * diff --git a/bvm/ballerina-profiler/src/main/java/io/ballerina/runtime/profiler/util/Constants.java b/bvm/ballerina-profiler/src/main/java/io/ballerina/runtime/profiler/util/Constants.java index 5254b07dc43a..bfad3edf613e 100644 --- a/bvm/ballerina-profiler/src/main/java/io/ballerina/runtime/profiler/util/Constants.java +++ b/bvm/ballerina-profiler/src/main/java/io/ballerina/runtime/profiler/util/Constants.java @@ -12,17 +12,28 @@ public class Constants { public static final String ANSI_GRAY = "\033[37m"; public static final String ANSI_CYAN = "\033[1;38;2;32;182;176m"; public static final String ANSI_YELLOW = "\033[1;38;2;255;255;0m"; - public static final String TEMP_JAR_FILE_NAME = "temp.jar"; - public static final String STRAND_CLASS = "io/ballerina/runtime/internal/scheduling/Strand"; - public static final String STRAND_ARG = "(L" + STRAND_CLASS; public static final PrintStream OUT_STREAM = System.out; public static final PrintStream ERROR_STREAM = System.err; public static final String CLASS_SUFFIX = ".class"; + public static final String CPU_PRE_JSON = "cpu_pre.json"; + public static final String PERFORMANCE_JSON = "performance_report.json"; + public static final String TEMP_JAR_FILE_NAME = "temp.jar"; + public static final String STRAND_PROFILER_STACK_PROPERTY = "b7a.profile.stack"; + + public static final String STRAND_CLASS = "io/ballerina/runtime/internal/scheduling/Strand"; + public static final String DATA_CLASS = "io/ballerina/runtime/profiler/runtime/Data"; + public static final String STRING_CLASS = "java/lang/String"; + public static final String STRAND_ARG = "(L" + STRAND_CLASS; + public static final String START_PROFILE_DESCRIPTOR = + "(L" + STRAND_CLASS + ";L" + STRING_CLASS + ";L" + STRING_CLASS + ";)L" + DATA_CLASS + ";"; + public static final String STOP_PROFILE_DESCRIPTOR = + "(L" + STRAND_CLASS + ";L" + DATA_CLASS + ";)V"; public static final String PROFILE_ANALYZER = "io/ballerina/runtime/profiler/runtime/ProfileAnalyzer"; public static final String GET_INSTANCE_DESCRIPTOR = "()L" + PROFILE_ANALYZER + ";"; - public static final String CPU_PRE_JSON = "cpu_pre.json"; + public static final String CURRENT_DIR_KEY = "current.dir"; + public static final String USER_DIR = "user.dir"; private Constants() { } diff --git a/bvm/ballerina-profiler/src/main/java/module-info.java b/bvm/ballerina-profiler/src/main/java/module-info.java new file mode 100644 index 000000000000..15468516fdd5 --- /dev/null +++ b/bvm/ballerina-profiler/src/main/java/module-info.java @@ -0,0 +1,7 @@ +module io.ballerina.runtime.profiler { + requires org.objectweb.asm.commons; + requires org.apache.commons.io; + requires io.ballerina.runtime; + requires io.ballerina.identifier; + requires com.google.gson; +} diff --git a/bvm/ballerina-profiler/src/main/resources/profiler_output.html b/bvm/ballerina-profiler/src/main/resources/profiler_output.html index ff6b46522d3f..104a33794a3e 100644 --- a/bvm/ballerina-profiler/src/main/resources/profiler_output.html +++ b/bvm/ballerina-profiler/src/main/resources/profiler_output.html @@ -40,8 +40,7 @@ } .container { - padding-left: 0px; - margin-left: 40px; + width: 100% } .btn { box-shadow: 0 0 2px rgba(0, 0, 0, 0.2); @@ -51,6 +50,7 @@ color: #1B2024; font-weight: bold; font-family: pragmatica, sans-serif; + margin-right: 10px; } .btn:hover { @@ -71,10 +71,7 @@ } .header { - margin-left: -40px; - box-shadow: 0 0 2px rgba(0, 0, 0, 0.2); - padding-right: 40px; - width: 1920px; + width: auto; background-color: #fbfbfd; padding-bottom: 25px; } @@ -120,14 +117,13 @@ margin-top: 40px; margin-bottom: 40px; max-height: 780px; - width: 1840px; + width: auto; border-radius: 20px; overflow-y: auto; overflow-x: auto; background-color: #fbfbfd; }#details{ color: black; - padding: 20px; position: absolute; left: 50%; font-weight: bold; @@ -157,13 +153,13 @@ Search - Reset zoom Clear + Reset zoom - +
@@ -176,15 +172,16 @@ ${profile_data} var flameGraph = d3.flamegraph() - .width(1840) - .selfValue(false) + .width(window.innerWidth - 40) .cellHeight(18) .transitionDuration(750) .minFrameSize(5) .transitionEase(d3.easeCubic) - .sort(false) + .sort(true) + .title("") .onClick(onClick) - .differential(false); + .differential(false) + .selfValue(false); var details = document.getElementById("details"); flameGraph.setDetailsElement(details); diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/scheduling/Strand.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/scheduling/Strand.java index 4fce809cf3ce..652795c5b68c 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/scheduling/Strand.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/internal/scheduling/Strand.java @@ -165,19 +165,10 @@ public void handleChannelError(ChannelDetails[] channels, ErrorValue error) { } } - /** - * @deprecated use Environment#getStrandLocal() - */ - @Deprecated public Object getProperty(String key) { return this.globalProps.get(key); } - /** - * - * @deprecated use Environment#setStrandLocal() - */ - @Deprecated public void setProperty(String key, Object value) { this.globalProps.put(key, value); } diff --git a/bvm/ballerina-runtime/src/main/java/module-info.java b/bvm/ballerina-runtime/src/main/java/module-info.java index 0f0f32d58a1c..edcfdf0722c7 100644 --- a/bvm/ballerina-runtime/src/main/java/module-info.java +++ b/bvm/ballerina-runtime/src/main/java/module-info.java @@ -43,7 +43,7 @@ io.ballerina.lang.table, io.ballerina.lang.value, io.ballerina.lang.xml, ballerina.debug.adapter.core, io.ballerina.cli, io.ballerina.lang.integer, io.ballerina.lang.bool, io.ballerina.lang.decimal, io.ballerina.lang.floatingpoint, io.ballerina.lang.internal, io.ballerina.lang.function, - io.ballerina.lang.regexp; + io.ballerina.lang.regexp, io.ballerina.runtime.profiler; exports io.ballerina.runtime.internal.commons to io.ballerina.lang.value; exports io.ballerina.runtime.internal.launch to io.ballerina.testerina.runtime, io.ballerina.packerina, ballerina.test.listener, io.ballerina.cli, org.ballerinalang.debugadapter.runtime; @@ -51,7 +51,7 @@ io.ballerina.lang.array, io.ballerina.lang.error, io.ballerina.lang.internal, io.ballerina.lang.map, io.ballerina.lang.table, io.ballerina.lang.transaction, io.ballerina.lang.value, io.ballerina.lang.xml, io.ballerina.log.api, io.ballerina.testerina.core, io.ballerina.testerina.runtime, io.ballerina.shell, - org.ballerinalang.debugadapter.runtime, io.ballerina.lang.function; + org.ballerinalang.debugadapter.runtime, io.ballerina.lang.function, io.ballerina.runtime.profiler; exports io.ballerina.runtime.internal.util to io.ballerina.testerina.runtime, io.ballerina.lang, io.ballerina.lang.integer, io.ballerina.lang.floatingpoint, io.ballerina.lang.array, io.ballerina.lang.table, io.ballerina.java, io.ballerina.lang.map, io.ballerina.lang.string, diff --git a/cli/ballerina-cli/src/main/java/io/ballerina/cli/task/RunProfilerTask.java b/cli/ballerina-cli/src/main/java/io/ballerina/cli/task/RunProfilerTask.java index 6a867816cf4a..f6e562fde697 100644 --- a/cli/ballerina-cli/src/main/java/io/ballerina/cli/task/RunProfilerTask.java +++ b/cli/ballerina-cli/src/main/java/io/ballerina/cli/task/RunProfilerTask.java @@ -50,6 +50,7 @@ public class RunProfilerTask implements Task { private final PrintStream err; private static final String JAVA_OPTS = "JAVA_OPTS"; + private static final String CURRENT_DIR_KEY = "current.dir"; public RunProfilerTask(PrintStream errStream) { this.err = errStream; @@ -76,14 +77,15 @@ private void initiateProfiler(Project project) { commands.add(getPackageJarName(project, projectKind)); commands.add("--target"); commands.add(targetPath.toString()); - commands.add("--sourceroot"); + commands.add("--source-root"); commands.add(getProjectPath(project).toString()); if (isInProfileDebugMode()) { - commands.add("--profilerDebug"); + commands.add("--profiler-debug"); commands.add(getProfileDebugArg(err)); } ProcessBuilder pb = new ProcessBuilder(commands).inheritIO(); pb.environment().put(JAVA_OPTS, getAgentArgs()); + pb.environment().put(CURRENT_DIR_KEY, System.getProperty(USER_DIR)); setWorkingDirectory(project, projectKind, pb); Process process = pb.start(); process.waitFor(); diff --git a/cli/ballerina-cli/src/test/java/io/ballerina/cli/cmd/ProfileCommandTest.java b/cli/ballerina-cli/src/test/java/io/ballerina/cli/cmd/ProfileCommandTest.java index a1b6a401590e..57f73b3656a9 100644 --- a/cli/ballerina-cli/src/test/java/io/ballerina/cli/cmd/ProfileCommandTest.java +++ b/cli/ballerina-cli/src/test/java/io/ballerina/cli/cmd/ProfileCommandTest.java @@ -85,8 +85,8 @@ public void testRunBalProjectWithProfileFlag() throws IOException { Assert.assertTrue(htmlPath.toFile().exists()); try { String htmlContent = Files.readString(htmlPath); - Assert.assertTrue(htmlContent.contains("foo/package_a/0/main.main()")); - Assert.assertTrue(htmlContent.contains("foo/package_a/0/$_init.$moduleExecute()")); + Assert.assertTrue(htmlContent.contains("foo/package_a/0/main.main")); + Assert.assertTrue(htmlContent.contains("foo/package_a/0/$_init.$moduleInit")); } catch (IOException e) { Assert.fail("Error reading html file"); } diff --git a/tests/jballerina-integration-test/src/test/java/org/ballerinalang/test/profiler/ProfilerTest.java b/tests/jballerina-integration-test/src/test/java/org/ballerinalang/test/profiler/ProfilerTest.java index 059bb79fc497..a5f3ae08293c 100644 --- a/tests/jballerina-integration-test/src/test/java/org/ballerinalang/test/profiler/ProfilerTest.java +++ b/tests/jballerina-integration-test/src/test/java/org/ballerinalang/test/profiler/ProfilerTest.java @@ -25,6 +25,7 @@ import org.ballerinalang.test.context.ServerLogReader; import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; +import org.wso2.ballerinalang.util.Lists; import java.io.IOException; import java.nio.file.Paths; @@ -51,20 +52,36 @@ public void setup() throws BallerinaTestException { @Test public void testProfilerExecutionWithBalPackage() throws BallerinaTestException { - String sourceRoot = testFileLocation + "/"; String packageName = "projectForProfile/package_a"; + String sourceRoot = testFileLocation + "/"; Map envProperties = new HashMap<>(); bMainInstance.addJavaAgents(envProperties); - LogLeecher[] leechers = getProfilerLogLeechers(packageName + "/target/bin/" + outputFile); - bMainInstance.runMain("profile", new String[]{packageName}, envProperties, - null, leechers, sourceRoot); + List leechers = getProfilerLogLeechers(packageName + "/target/bin/" + outputFile); + leechers.add(new LogLeecher("Is the array sorted? true")); + bMainInstance.runMain("profile", new String[]{packageName}, envProperties, null, + leechers.toArray(new LogLeecher[0]), sourceRoot); for (LogLeecher leecher : leechers) { leecher.waitForText(5000); } } - private LogLeecher[] getProfilerLogLeechers(String htmlFilePath) { - return new LogLeecher[]{ + @Test + public void testProfilerExecutionWithConfigurableVars() throws BallerinaTestException { + String packageName = "projectForProfile/package_b"; + String sourceRoot = testFileLocation + "/" + packageName; + Map envProperties = new HashMap<>(); + bMainInstance.addJavaAgents(envProperties); + List leechers = getProfilerLogLeechers(packageName + "/target/bin/" + outputFile); + leechers.add(new LogLeecher("Tests passed")); + bMainInstance.runMain("profile", new String[]{}, envProperties, null, + leechers.toArray(new LogLeecher[0]), sourceRoot); + for (LogLeecher leecher : leechers) { + leecher.waitForText(5000); + } + } + + private List getProfilerLogLeechers(String htmlFilePath) { + return Lists.of( new LogLeecher("[1/6] Initializing..."), new LogLeecher("[2/6] Copying executable..."), new LogLeecher("[3/6] Performing analysis..."), @@ -73,9 +90,9 @@ private LogLeecher[] getProfilerLogLeechers(String htmlFilePath) { new LogLeecher("○ Instrumented function count: "), new LogLeecher("[5/6] Running executable..."), new LogLeecher("[6/6] Generating output..."), - new LogLeecher("○ Execution time:"), + new LogLeecher(" ○ Execution time: [1-5] seconds ", true, LogLeecher.LeecherType.INFO), new LogLeecher("○ Output: "), - new LogLeecher(htmlFilePath)}; + new LogLeecher(htmlFilePath)); } @Test @@ -84,9 +101,9 @@ public void testProfilerExecutionWithSingleBalFile() throws BallerinaTestExcepti String fileName = "profiler_single_file.bal"; Map envProperties = new HashMap<>(); bMainInstance.addJavaAgents(envProperties); - LogLeecher[] leechers = getProfilerLogLeechers(sourceRoot + outputFile); + List leechers = getProfilerLogLeechers(sourceRoot + outputFile); bMainInstance.runMain("profile", new String[]{fileName}, envProperties, - null, leechers, sourceRoot); + null, leechers.toArray(new LogLeecher[0]), sourceRoot); for (LogLeecher leecher : leechers) { leecher.waitForText(5000); } diff --git a/tests/jballerina-integration-test/src/test/resources/profiler/projectForProfile/package_a/main.bal b/tests/jballerina-integration-test/src/test/resources/profiler/projectForProfile/package_a/main.bal index e75aa457e040..08425f16effa 100644 --- a/tests/jballerina-integration-test/src/test/resources/profiler/projectForProfile/package_a/main.bal +++ b/tests/jballerina-integration-test/src/test/resources/profiler/projectForProfile/package_a/main.bal @@ -15,7 +15,68 @@ // under the License. import foo/package_a.mod_a1; +import ballerina/jballerina.java; -public function main() { +public function main() returns error? { mod_a1:func1(); + int[] arr = createRandomIntArray(check float:pow(10,3).cloneWithType(int)); + int[] sortedArr = bubbleSort(arr); + boolean isSorted = isSortedArray(sortedArr); + print("Is the array sorted? " + isSorted.toString()); } + +public isolated function bubbleSort(int[] arr) returns int[] { + int n = arr.length(); + int temp = 0; + boolean swapped = false; + foreach int i in 0 ... n - 2 { + foreach int j in 1 ... n - 1 - i { + if (arr[j - 1] > arr[j]) { + temp = arr[j - 1]; + arr[j - 1] = arr[j]; + arr[j] = temp; + swapped = true; + } + } + if (!swapped) { + break; + } + } + return arr; +} + +isolated function isSortedArray(int[] sortedArr) returns boolean { + foreach int i in 0 ..< sortedArr.length() - 1 { + if (sortedArr[i] > sortedArr[i + 1]) { + return false; + } + } + return true; +} + +isolated function createRandomIntArray(int size) returns int[] { + int[] array = []; + int count = 0; + foreach int i in 0 ..< size { + array.push(count); + count += 1; + } + return array; +} + +function print(string value) { + handle strValue = java:fromString(value); + handle stdout1 = stdout(); + printInternal(stdout1, strValue); +} + +public function stdout() returns handle = @java:FieldGet { + name: "out", + 'class: "java/lang/System" +} external; + +public function printInternal(handle receiver, handle strValue) = @java:Method { + name: "println", + 'class: "java/io/PrintStream", + paramTypes: ["java.lang.String"] +} external; diff --git a/tests/jballerina-integration-test/src/test/resources/profiler/projectForProfile/package_b/Ballerina.toml b/tests/jballerina-integration-test/src/test/resources/profiler/projectForProfile/package_b/Ballerina.toml new file mode 100644 index 000000000000..5cfbc6a5d346 --- /dev/null +++ b/tests/jballerina-integration-test/src/test/resources/profiler/projectForProfile/package_b/Ballerina.toml @@ -0,0 +1,4 @@ +[package] +org = "foo" +name = "package_b" +version = "0.1.0" diff --git a/tests/jballerina-integration-test/src/test/resources/profiler/projectForProfile/package_b/Config.toml b/tests/jballerina-integration-test/src/test/resources/profiler/projectForProfile/package_b/Config.toml new file mode 100644 index 000000000000..bff5108ff9af --- /dev/null +++ b/tests/jballerina-integration-test/src/test/resources/profiler/projectForProfile/package_b/Config.toml @@ -0,0 +1 @@ +intValue = 10 diff --git a/tests/jballerina-integration-test/src/test/resources/profiler/projectForProfile/package_b/main.bal b/tests/jballerina-integration-test/src/test/resources/profiler/projectForProfile/package_b/main.bal new file mode 100644 index 000000000000..67ca87f0b8f3 --- /dev/null +++ b/tests/jballerina-integration-test/src/test/resources/profiler/projectForProfile/package_b/main.bal @@ -0,0 +1,44 @@ +// Copyright (c) 2023, WSO2 LLC. (https://www.wso2.com) All Rights Reserved. +// +// WSO2 LLC. licenses this file to you 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. + +import foo/package_b.mod_a1; +import ballerina/test; +import ballerina/jballerina.java; + +configurable int intValue = ?; + +public function main() { + int a = mod_a1:func1() + intValue; + test:assertEquals(a, 30); + print("Tests passed"); +} + +function print(string value) { + handle strValue = java:fromString(value); + handle stdout1 = stdout(); + printInternal(stdout1, strValue); +} + +public function stdout() returns handle = @java:FieldGet { + name: "out", + 'class: "java/lang/System" +} external; + +public function printInternal(handle receiver, handle strValue) = @java:Method { + name: "println", + 'class: "java/io/PrintStream", + paramTypes: ["java.lang.String"] +} external; diff --git a/tests/jballerina-integration-test/src/test/resources/profiler/projectForProfile/package_b/modules/mod_a1/mod1.bal b/tests/jballerina-integration-test/src/test/resources/profiler/projectForProfile/package_b/modules/mod_a1/mod1.bal new file mode 100644 index 000000000000..24e91d230daf --- /dev/null +++ b/tests/jballerina-integration-test/src/test/resources/profiler/projectForProfile/package_b/modules/mod_a1/mod1.bal @@ -0,0 +1,27 @@ +// Copyright (c) 2023, WSO2 LLC. (https://www.wso2.com) All Rights Reserved. +// +// WSO2 LLC. licenses this file to you 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. + +boolean num = float:isFinite(1.0); +int a = 10; + +public function func1() returns int { + if (num) { + a = 20; + } else { + a = 30; + } + return a; +}