Skip to content

Commit

Permalink
Merge pull request #41482 from gabilang/fix-offline-prof-out
Browse files Browse the repository at this point in the history
Add required resources locally for offline html report generation of Ballerina profiler
  • Loading branch information
warunalakshitha authored Dec 20, 2023
2 parents dbfa911 + ecbc59a commit 122d089
Show file tree
Hide file tree
Showing 21 changed files with 23,251 additions and 71 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ velocity.log
/composer/modules/web/dist-electron
extractedDistribution
misc/testerina/modules/report-tools/node_modules/
tests/jballerina-integration-test/src/test/resources/profiler/profiler

# gradle
.gradle
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,6 @@ public class Profiler {
private String balJarArgs = null;
private String balJarName = null;
private String targetDir = null;
private String sourceRoot = null;
private String profilerDebugArg = null;
private final List<String> instrumentedPaths = new ArrayList<>();
private final List<String> instrumentedFiles = new ArrayList<>();
Expand Down Expand Up @@ -91,7 +90,7 @@ private void addShutdownHookAndCleanup() {
jsonParser.initializeCPUParser(cpuFilePath);
deleteFileIfExists(cpuFilePath);
OUT_STREAM.printf(" ○ Execution time: %d seconds %n", profilerTotalTime / 1000);
httpServer.initializeHTMLExport(this.sourceRoot);
httpServer.initializeHTMLExport();
deleteFileIfExists(PERFORMANCE_JSON);
OUT_STREAM.println("--------------------------------------------------------------------------------");
} catch (IOException e) {
Expand Down Expand Up @@ -144,10 +143,6 @@ private void handleProfilerArguments(String[] args) {
this.targetDir = args[i + 1];
addToUsedArgs(args, usedArgs, i);
}
case "--source-root" -> {
this.sourceRoot = args[i + 1];
addToUsedArgs(args, usedArgs, i);
}
case "--profiler-debug" -> {
this.profilerDebugArg = args[i + 1];
addToUsedArgs(args, usedArgs, i);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,18 @@
import io.ballerina.runtime.profiler.runtime.ProfilerRuntimeException;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

import static io.ballerina.runtime.profiler.util.Constants.BALLERINA_HOME;
import static io.ballerina.runtime.profiler.util.Constants.HTML_TEMPLATE_FILE;
import static io.ballerina.runtime.profiler.util.Constants.PROFILE_DATA;

/**
* This class contains the front end of the Ballerina profiler.
Expand All @@ -33,9 +41,6 @@
*/
public class FrontEnd {

private static final String PROFILE_DATA = "${profile_data}";
private static final String FILE_LOCATION = "profiler_output.html";

String getSiteData(String contents) {
String htmlContent;
try {
Expand All @@ -47,11 +52,13 @@ String getSiteData(String contents) {
}

public String readFileAsString() throws IOException {
Path resourceFilePath = Paths.get(System.getenv(BALLERINA_HOME)).resolve("resources")
.resolve("profiler").resolve(HTML_TEMPLATE_FILE);
if (!Files.exists(resourceFilePath)) {
throw new ProfilerRuntimeException("resource file not found: " + HTML_TEMPLATE_FILE);
}
StringBuilder sb = new StringBuilder();
try (InputStream inputStream = getClass().getClassLoader().getResourceAsStream(FILE_LOCATION)) {
if (inputStream == null) {
throw new ProfilerRuntimeException("resource file not found: " + FILE_LOCATION);
}
try (InputStream inputStream = new FileInputStream(resourceFilePath.toFile())) {
try (InputStreamReader inputStreamReader = new InputStreamReader(inputStream, StandardCharsets.UTF_8);
BufferedReader br = new BufferedReader(inputStreamReader)) {
String content = br.readLine();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,26 @@
package io.ballerina.runtime.profiler.ui;

import io.ballerina.runtime.profiler.util.Constants;
import io.ballerina.runtime.profiler.util.ProfilerException;

import java.io.FileWriter;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.FileVisitOption;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.StandardCopyOption;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.EnumSet;

import static io.ballerina.runtime.profiler.util.Constants.BALLERINA_HOME;
import static io.ballerina.runtime.profiler.util.Constants.HTML_PROFILER_REPORT;
import static io.ballerina.runtime.profiler.util.Constants.OUT_STREAM;
import static io.ballerina.runtime.profiler.util.Constants.PERFORMANCE_JSON;
import static io.ballerina.runtime.profiler.util.Constants.WORKING_DIRECTORY;

/**
* This class contains the HTTP server of the Ballerina profiler.
Expand All @@ -34,17 +47,49 @@
*/
public class HttpServer {

public void initializeHTMLExport(String sourceRoot) throws IOException {
public void initializeHTMLExport() throws IOException {
String profilerOutputDir = System.getProperty(WORKING_DIRECTORY);
OUT_STREAM.printf(" ○ Output: " + Constants.ANSI_YELLOW +
"%s/ProfilerOutput.html" + Constants.ANSI_RESET + "%n", sourceRoot);
"%s/" + HTML_PROFILER_REPORT + Constants.ANSI_RESET + "%n", profilerOutputDir);
Path resourcePath = Paths.get(System.getenv(BALLERINA_HOME)).resolve("resources")
.resolve("profiler");

try {
copyFolder(resourcePath, Path.of(profilerOutputDir));
} catch (IOException e) {
throw new ProfilerException("Error occurred while copying the resources", e);
}

String content = FileUtils.readFileAsString(PERFORMANCE_JSON);
FrontEnd frontEnd = new FrontEnd();
String htmlData = frontEnd.getSiteData(content);
String fileName = "ProfilerOutput.html";
try (FileWriter writer = new FileWriter(fileName, StandardCharsets.UTF_8)) {
try (FileWriter writer = new FileWriter(HTML_PROFILER_REPORT, StandardCharsets.UTF_8)) {
writer.write(htmlData);
} catch (IOException e) {
OUT_STREAM.printf("%s%n", e);
}
}

private static void copyFolder(Path source, Path target) throws IOException {
EnumSet<FileVisitOption> options = EnumSet.of(FileVisitOption.FOLLOW_LINKS);
Files.walkFileTree(source, options, Integer.MAX_VALUE, new SimpleFileVisitor<>() {
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
Path targetDir = target.resolve(source.relativize(dir));
Files.createDirectories(targetDir);
return FileVisitResult.CONTINUE;
}

@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
// exclude the template html file.
if (file.toString().endsWith(".html")) {
return FileVisitResult.CONTINUE;
}
Path targetFile = target.resolve(source.relativize(file));
Files.copy(file, targetFile, StandardCopyOption.REPLACE_EXISTING);
return FileVisitResult.CONTINUE;
}
});
}
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,21 @@
/*
* 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.util;

import java.io.PrintStream;
Expand Down Expand Up @@ -32,6 +50,11 @@ public class Constants {
"(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 BALLERINA_HOME = "ballerina.home";
public static final String WORKING_DIRECTORY = "user.dir";
public static final String PROFILE_DATA = "${profile_data}";
public static final String HTML_TEMPLATE_FILE = "profiler_output.html";
public static final String HTML_PROFILER_REPORT = "ProfilerReport.html";
public static final String CURRENT_DIR_KEY = "current.dir";
public static final String USER_DIR = "user.dir";

Expand Down
6 changes: 6 additions & 0 deletions cli/ballerina-cli/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,11 @@ task copyCompilerPluginJars(type: Copy) {
into "$buildDir/compiler-plugin-jars"
}

task copyProfilerResources(type: Copy) {
from project(':jballerina-tools').file('resources/profiler')
into "$buildDir/resources/profiler"
}

task createTestBre(type: Copy) {
from configurations.balRt
from configurations.testRt
Expand All @@ -111,6 +116,7 @@ test {
dependsOn createTestDistributionCache
dependsOn createTestBre
dependsOn copyCompilerPluginJars
dependsOn copyProfilerResources

systemProperty "ballerina.home", "$buildDir"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import io.ballerina.cli.launcher.RuntimePanicException;
import io.ballerina.projects.Project;
import io.ballerina.projects.ProjectKind;
import io.ballerina.projects.internal.model.Target;

import java.io.File;
import java.io.IOException;
Expand Down Expand Up @@ -59,7 +60,6 @@ public RunProfilerTask(PrintStream errStream) {
private void initiateProfiler(Project project) {
String profilerSource = Paths.get(System.getProperty(BALLERINA_HOME), "bre", "lib",
"ballerina-profiler-1.0.jar").toString();
ProjectKind projectKind = project.kind();
Path sourcePath = Path.of(profilerSource);
Path targetPath = getTargetProfilerPath(project);
StandardCopyOption copyOption = StandardCopyOption.REPLACE_EXISTING;
Expand All @@ -74,19 +74,18 @@ private void initiateProfiler(Project project) {
commands.add("Profiler.jar");
// Sets classpath with executable thin jar and all dependency jar paths.
commands.add("--file");
commands.add(getPackageJarName(project, projectKind));
commands.add(getTargetFilePath(project));
commands.add("--target");
commands.add(targetPath.toString());
commands.add("--source-root");
commands.add(getProjectPath(project).toString());
if (isInProfileDebugMode()) {
commands.add("--profiler-debug");
commands.add(getProfileDebugArg(err));
}
ProcessBuilder pb = new ProcessBuilder(commands).inheritIO();
pb.environment().put(JAVA_OPTS, getAgentArgs());
pb.environment().put(BALLERINA_HOME, System.getProperty(BALLERINA_HOME));
pb.environment().put(CURRENT_DIR_KEY, System.getProperty(USER_DIR));
setWorkingDirectory(project, projectKind, pb);
pb.directory(new File(getProfilerPath(project).toUri()));
Process process = pb.start();
process.waitFor();
int exitValue = process.exitValue();
Expand All @@ -98,21 +97,6 @@ private void initiateProfiler(Project project) {
}
}

private static void setWorkingDirectory(Project project, ProjectKind projectKind, ProcessBuilder pb) {
if (projectKind == ProjectKind.BUILD_PROJECT) {
pb.directory(new File(project.targetDir() + "/bin"));
} else {
pb.directory(new File(System.getProperty(USER_DIR)));
}
}

private static String getPackageJarName(Project project, ProjectKind kind) {
if (kind == ProjectKind.SINGLE_FILE_PROJECT) {
return getFileNameWithoutExtension(project.sourceRoot()) + BLANG_COMPILED_JAR_EXT;
}
return project.currentPackage().packageName() + BLANG_COMPILED_JAR_EXT;
}

@Override
public void execute(Project project) {
initiateProfiler(project);
Expand All @@ -127,14 +111,31 @@ private String getAgentArgs() {
}

private Path getTargetProfilerPath(Project project) {
return getProjectPath(project).resolve("Profiler" + BLANG_COMPILED_JAR_EXT);
return getProfilerPath(project).resolve("Profiler" + BLANG_COMPILED_JAR_EXT);
}

private Path getProfilerPath(Project project) {
try {
Target target = new Target(getTargetPath(project));
return target.getProfilerPath();
} catch (IOException e) {
throw createLauncherException("error while creating profiler directory: ", e);
}
}

private Path getProjectPath(Project project) {
// If the --output flag is not set, create the executable in the current directory
private Path getTargetPath(Project project) {
if (project.kind() == ProjectKind.SINGLE_FILE_PROJECT) {
return Paths.get(System.getProperty(USER_DIR));
}
return project.targetDir().resolve("bin");
return project.targetDir();
}

private String getTargetFilePath(Project project) {
if (project.kind() == ProjectKind.SINGLE_FILE_PROJECT) {
return Paths.get(System.getProperty(USER_DIR)).resolve(
getFileNameWithoutExtension(project.sourceRoot()) + BLANG_COMPILED_JAR_EXT).toUri().getPath();
}
return project.targetDir().resolve("bin").resolve(project.currentPackage().packageName() +
BLANG_COMPILED_JAR_EXT).toUri().getPath();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ SYNOPSIS

DESCRIPTION
Compile the current package and run the program with Ballerina Profiler. This command generates an html file
with name `ProfilerOutput.html` in the target directory.
with name `ProfilerReport.html` in the target directory.

The generated `ProfilerOutput.html` file contains the flame graph that visualizes the distributed Ballerina
The generated `ProfilerReport.html` file contains the flame graph that visualizes the distributed Ballerina
functions with execution details.

Note: This is an experimental feature, which supports only a limited
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,8 @@
public class ProfileCommandTest extends BaseCommandTest {
private Path testResources;

static Path logFile = Paths.get("./src/test/resources/compiler_plugin_tests/" +
"log_creator_combined_plugin/compiler-plugin.txt");
static Path logFile = Paths.get(".", "src", "test", "resources", "compiler_plugin_tests",
"log_creator_combined_plugin", "compiler-plugin.txt");

@BeforeSuite
public void setupSuite() throws IOException {
Expand Down Expand Up @@ -81,7 +81,7 @@ public void testRunBalProjectWithProfileFlag() throws IOException {
profileCommand.execute();
String buildLog = readOutput(true).replaceAll("\r", "").strip();
Assert.assertEquals(buildLog, getOutput("run-project-with-profile.txt"));
Path htmlPath = projectPath.resolve("target").resolve("bin").resolve("ProfilerOutput.html");
Path htmlPath = projectPath.resolve("target").resolve("profiler").resolve("ProfilerReport.html");
Assert.assertTrue(htmlPath.toFile().exists());
try {
String htmlContent = Files.readString(htmlPath);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ public class Target {
private Path docPath;
private Path nativePath;
private Path nativeConfigPath;
private Path profilerPath;

public Target(Path targetPath) throws IOException {
this.targetPath = targetPath;
Expand All @@ -57,6 +58,7 @@ public Target(Path targetPath) throws IOException {
this.docPath = this.targetPath.resolve(ProjectConstants.TARGET_API_DOC_DIRECTORY);
this.nativePath = this.targetPath.resolve(ProjectConstants.NATIVE_DIR_NAME);
this.nativeConfigPath = this.testsCachePath.resolve(ProjectConstants.NATIVE_CONFIG_DIR_NAME);
this.profilerPath = this.targetPath.resolve(ProjectConstants.PROFILER_DIR_NAME);

if (Files.exists(this.targetPath)) {
ProjectUtils.checkWritePermission(this.targetPath);
Expand All @@ -80,6 +82,9 @@ public Target(Path targetPath) throws IOException {
if (Files.exists(this.reportPath)) {
ProjectUtils.checkWritePermission(this.reportPath);
}
if (Files.exists(this.profilerPath)) {
ProjectUtils.checkWritePermission(this.profilerPath);
}
}

/**
Expand Down Expand Up @@ -140,6 +145,11 @@ public Path getReportPath() throws IOException {
return reportPath;
}

public Path getProfilerPath() throws IOException {
Files.createDirectories(profilerPath);
return profilerPath;
}

/**
* Returns the caches directory path.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,4 +131,5 @@ private ProjectConstants() {}
public static final String EXISTING_PACKAGE_FILES_DIR = "directories-with-existing-package-files-for-bal-new";
public static final String CONFIG_ARGS_PATTERN = "-C[\\w\\W]+=([\\w\\W]+)";
public static final String CONFIG_DIR = ".config";
public static final String PROFILER_DIR_NAME = "profiler";
}
Loading

0 comments on commit 122d089

Please sign in to comment.