Improvements in your Ballerina code might be required to increase its runtime performance, minimize code execution times, increase throughput, and reduce latency. To do that, you need to identify performance bottlenecks, implications of concurrent executions, which code segments take longer to execute, and areas to improve performance. This can be done by using a profiler.
Ballerina Profiler is a tool that monitors the Ballerina runtime and its operations such as function calls. It can be used to understand the behavior and troubleshoot the performance issues of a Ballerina program and optimize it.
Note: Ballerina Profiler is an experimental feature, which supports only a limited set of functionality. The commands associated with the tool might change in future releases.
Features
-
Profile a local session of a Ballerina program through offline instrumentation.
-
Perform CPU profiling, which finds out where the CPU time is going.
-
Generate a flame graph, which shows the function call stack and the execution times of each function call. It provides an interface for viewing runtime performance and the ability to search, diagnose, and find performance bottlenecks.
Note: Profiling is a high-powered activity with a heavy overhead and can slow down your application.
Profile a Ballerina program
To profile a Ballerina package, you can use the bal profile
CLI command inside the root directory of the Ballerina package.
Consider the following step-by-step guide to profile a Ballerina package.
- Create a Ballerina package named
sort
using thebal new
CLI command.
$ bal new sort
- Go to the root directory of the Ballerina package.
$ cd sort
- Replace the contents of the
main.bal
file with the following Ballerina code, which creates an array of random integers, sorts them, and verifies the output.
import ballerina/io; import ballerina/random; public function main() returns error? { int[] arr = check createRandomIntArray(check float:pow(10, 2).cloneWithType(int)); int[] sortedArr = bubbleSort(arr); boolean isSorted = isSortedArray(sortedArr); io:println("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[]|error { int[] array = []; foreach int i in 0 ..< size { array.push(check random:createIntInRange(0, int:MAX_VALUE)); } return array; }
- Run and profile the Ballerina package using the
bal profile
CLI command.
$ bal profile
You view the output below.
Compiling source profiler_demo/sort:0.1.0 Generating executable target/bin/sort.jar ================================================================================ Ballerina Profiler: Profiling... ================================================================================ Note: This is an experimental feature, which supports only a limited set of functionality. [1/6] Initializing... [2/6] Copying executable... [3/6] Performing analysis... [4/6] Instrumenting functions... ○ Instrumented module count: 31 ○ Instrumented function count: 1016 [5/6] Running executable... Is the array sorted? true [6/6] Generating output... ○ Execution time: 3 seconds ○ Output: target/bin/ProfilerOutput.html -------------------------------------------------------------------------------
- Open the
target/bin/ProfilerOutput.html
file using a web browser window to examine the Ballerina Profiler output to find the slow-running functions and performance bottlenecks, which can then be addressed to improve the program’s overall performance.
Profiler output and available details
The flame graph generated by the Ballerina Profiler contains the following details.
-
Function call stack
-
Time taken to execute each function and its percentage
Flame graph also has the following features.
- Search a function.
-
Clear the search.
-
Click on a function call and zoom the view.
- Reset the view.
Examine the output and find performance bottlenecks
When you observe the flame graph generated by the Ballerina Profile for the above code, you can see the bubbleSort
function has taken the most time to execute.
Let's try to optimize the sorting function to increase the performance of the Ballerina application.
Optimize the Ballerina code
In this example, you will use another sorting algorithm to reduce the time taken to execute the application. Replace the main.bal
file with the code below, which uses the merge sort algorithm instead of the bubble sort algorithm for sorting.
import ballerina/io; import ballerina/random; public function main() returns error? { int[] arr = check createRandomIntArray(check float:pow(10, 2).cloneWithType(int)); int[] sortedArr = mergeSort(arr); boolean isSorted = isSortedArray(sortedArr); io:println("Is the array sorted? " + isSorted.toString()); } public isolated function mergeSort(int[] arr) returns int[] { int n = arr.length(); int width = 1; while (width < n) { int l = 0; while (l < n) { int r = int:min(l + (width * 2 - 1), n - 1); int m = int:min(l + width - 1, n - 1); merge(arr, l, m, r); l += width * 2; } width *= 2; } return arr; } isolated function merge(int[] a, int l, int m, int r) { int n1 = m - l + 1; int n2 = r - m; int[] L = []; int[] R = []; foreach int i in int:range(0, n1, 1) { L[i] = a[l + i]; } foreach int i in int:range(0, n2, 1) { R[i] = a[m + i + 1]; } int i = 0; int j = 0; int k = l; while (i < n1 && j < n2) { if L[i] <= R[j] { a[k] = L[i]; i += 1; } else { a[k] = R[j]; j += 1; } k += 1; } while (i < n1) { a[k] = L[i]; i += 1; k += 1; } while (j < n2) { a[k] = R[j]; j += 1; k += 1; } } 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[]|error { int[] array = []; foreach int i in 0 ..< size { array.push(check random:createIntInRange(0, int:MAX_VALUE)); } return array; }
When you profile the code again, you can see that you have reduced the time taken for sorting substantially and have improved the performance of the application.
Profile a Ballerina service
Consider the following step-by-step guide to profile a Ballerina package that contains an HTTP service.
- Create a new Ballerina package and replace the
main.bal
file with the below code.
import ballerina/http; type Country record { string country; int population; string continent; int cases; int deaths; }; type Data record {| string country; string continent; int population; decimal caseFatalityRatio; |}; http:Client diseaseEp = check new ("https://disease.sh/v3"); final Country[] & readonly countries; function init() returns error? { countries = check diseaseEp->/covid\-19/countries; } service /covid19/countries on new http:Listener(8080) { resource function get summary() returns json { Data[] listResult = from var {country, continent, population, cases, deaths} in countries where hasSignificantPopulation(population, deaths) let decimal caseFatalityRatio = <decimal>deaths / <decimal>cases * 100 order by caseFatalityRatio descending limit 1000 select {country, continent, population, caseFatalityRatio}; return getTopSortedValues(listResult); } isolated resource function get names() returns json { return from var {country, population, deaths} in countries where hasNonZeroPopulation(population, deaths) select {country}; } } isolated function getTopSortedValues(Data[] listResult) returns json[] { json[] sortedArray = listResult.sort("ascending", getKey); return getTopElements(sortedArray); } isolated function getTopElements(json[] sortedArray) returns json[] { return from var i in sortedArray limit 10 select i; } isolated function hasSignificantPopulation(int population, int deaths) returns boolean { return population >= 100 && deaths >= 10; } isolated function hasNonZeroPopulation(int population, int deaths) returns boolean { return population >= 0 && deaths >= 0; } isolated function getKey(record {|string country; string continent; int population; decimal caseFatalityRatio;|} recordVal) returns string { return recordVal.country; }
- Run the CLI command
bal profile
in the root directory to run the Ballerina package and profile it. This will run the service while profiling it.
$ bal profile Compiling source profiler_demo/covid19_stats:0.1.0 Generating executable target/bin/covid19_stats.jar ================================================================================ Ballerina Profiler: Profiling... ================================================================================ Note: This is an experimental feature, which supports only a limited set of functionality. [1/6] Initializing... [2/6] Copying executable... [3/6] Performing analysis... [4/6] Instrumenting functions... ○ Instrumented module count: 44 ○ Instrumented function count: 2935 [5/6] Running executable...
- Send 10 requests to each resource endpoint using the
curl
command.
$ curl localhost:8080/covid19/countries/names
$ curl localhost:8080/covid19/countries/summary
- Stop the service by sending the
SIGINT
signal to the Ballerina program. You can useCtrl+C
in the service running terminal to send the signal to it. This will stop both the Ballerina program and profiling it and will generate the profiler output.
[6/6] Generating output... ○ Execution time: 58 seconds ○ Output: target/bin/ProfilerOutput.html --------------------------------------------------------------------------------
- Open the
target/bin/ProfilerOutput.html
file using a web browser to observe the flame graph generated by the profiler.
By observing the flame graph you can see that the get summary
resource function takes more time than the get names
resource function. You can use the profiler output data to improve the performance of the Ballerina service.