How I created Spring Boot startup analyzer

It’s no secret that Spring applications can be thought of at the start. This is especially noticeable with the development of the project: the new service starts quickly and pleases with responsiveness, then it begins to acquire functionality, more and more new dependencies appear, and the final distribution kit swells by tens of megabytes. And so, in order to simply launch this service locally, you have to wait half a minute, a minute, two … At such moments of waiting, the developer may have questions: why is it so long? what’s going on under the hood? maybe you didn’t need to add that library?

Hello everyone, my name is Alexey Lapin, I am a lead developer at Luxoft. In this article, I will talk about a tool in the form of a web application for analyzing the start phase of services on Spring Boot, using actuator startup endpoint data. This may help answer the questions above.

A small introduction

I made this application for myself, to understand a new Spring module that I had not seen before, and to practice in the frontend. There are different solutions on the net, but they didn’t work or a long time ago not updatedand I wanted to create an up-to-date helper tool for the Spring Boot functionality.

Spring Boot Startup Endpoint

From version 2.4 Spring Boot has an implementation ApplicationStartup, which records the events that occurred during the start of the service, and an actuator endpoint, which returns a list of these events.

The response given by endpoint (/ actuator / startup) is as follows:

{
    "springBootVersion": "2.5.3",
    "timeline": {
        "startTime": "2021-09-06T13:38:05.049490700Z",
        "events": [
            {
                "endTime": "2021-09-06T13:38:05.159435400Z",
                "duration": "PT0.0898001S",
                "startTime": "2021-09-06T13:38:05.069635300Z",
                "startupStep": {
                    "name": "spring.boot.application.starting",
                    "id": 0,
                    "tags": [
                        {
                            "key": "mainApplicationClass",
                            "value": "com.github.al.realworld.App"
                        }
                    ],
                    "parentId": null
                }
            },
            ...
            {
                "endTime": "2021-09-06T13:38:06.420231Z",
                "duration": "PT0.0060049S",
                "startTime": "2021-09-06T13:38:06.414226100Z",
                "startupStep": {
                    "name": "spring.beans.instantiate",
                    "id": 7,
                    "tags": [
                        {
                            "key": "beanName",
                            "value": "org.springframework.boot.autoconfigure.internalCachingMetadataReaderFactory"
                        }
                    ],
                    "parentId": 6
                }
            },
            ...
        ]
….}
}

A detailed description of all message fields can be found in documentation Spring Boot Actuator, but all the names speak for themselves anyway. The event has an id and a parentId, which allows you to create a tree view. There is also a duration field that shows the time spent on the event + the sum of the times of all child events. The tags field contains a list of event attributes, such as the name or class of the bean to be created.

In order to activate the collection of data about load events, you must pass an instance of the class BufferingApplicationStartup in the setApplicationStartup method of SpringApplication… In this case, a constructor is used that takes the number of events to write. All events in excess of this limit will be ignored and will not appear in the output of the startup endpoint.

@SpringBootApplication
public class App {
    public static void main(String[] args) {
        SpringApplication application = new SpringApplication(App.class);
        application.setApplicationStartup(new BufferingApplicationStartup(1000));
        application.run(args);
    }
}

By default, this endpoint has the path / actuator / startup and supports GET methods for receiving events and POST methods for receiving events and flushing the buffer, so that subsequent calls to this endpoint will return an empty list of events.

Thus, the information provided by the startup endpoint will be considered a data source for analysis. The analyzer web application is a SPA (Single Page Application) without a backend. You need to load the events that occurred during the launch of the service into it, and it will build their visualization. The downloaded data is not transferred anywhere and is not saved anywhere.

For implementation, the Typescript language was chosen, since it seems to be a more attractive option for a Java developer compared to Javascript due to the usual strong typing and many OOP capabilities. It seemed to me that it was very easy to switch from Java to Typescript and write working code quickly. VueJS 3 was chosen as the framework for building the UI. I have nothing against React, Angular and other representatives of the front-end landscape, but at the moment VueJS seemed like a good option due to the low threshold of entry and an excellent set of tools out of the box. The next step was the selection of the component library. She was required to be compatible with VueJS 3 and the presence of components for working with tables. Element Plus, Ionic Vue, NaiveUI were considered, but due to the availability of customizable components for working with tables, the PrimeVue library was used.

The application has a navigation bar with elements Analyzer (this is the main screen of the application), Usage (instructions for use) and a link to the project’s GitHub repository.

The main page of the application displays a form with three ways to enter data for analysis. The first method allows you to specify a link to the deployed Spring Boot service. In this case, an http request will be made to the specified endpoint and the data will be automatically loaded for visualization. This method is applicable for cases when the service is available from the Internet or raised locally. Also, loading by url may require additional configuration of the service in terms of CORS headers and Spring Security. The second and third ways are to download a json file or directly its content.

The deployed application is located at https://alexey-lapin.github.io/spring-boot-startup-analyzer

To demonstrate how the analyzer works, I use my Spring Boot service deployed on Heroku. This service implements the RealWorld project backend (https://github.com/gothinkster/realworld). The desired endpoint can be found at https://realworld-backend-spring.herokuapp.com/actuator/startup… The service is configured to send correct CORS headers for GET requests from the parser.

After loading events using one of the methods, the data is visualized as a tree structure. In this case, all rows that have children are hidden. To navigate through this tree, you can use the> icons to the left of the record ID, or immediately expand or hide all records using the Expand All / Collapse All buttons.

If there are many events, it may take some time to render the expansion of all records.

All events are displayed in a table at once. In this case, all columns, except for Tags, are sorted.

CI + hosting

On one of the previous projects, I was involved in the global DevOps transformation of the organization and solved the problems of automating the release cycle processes and building CI / CD pipelines. It was an interesting experience that is now helping to resolve issues related to writing the source code for the product. In this case, as for most of my OSS projects, GitHub is used as git hosting, which provides many useful tools for CI, storing artifacts, documentation, project management, hosting static sites and many others. For the needs of the analyzer, Actions and Pages were used.

GitHub Actions is configured to automatically build a project to create a pull request, commit to master, and push a tag. At the same time, pushing the tag will also deploy the assembled project to GitHub Pages, as well as build a Docker image and send it to DockerHub.

In addition to the public analyzer instance on GitHub Pages (https://alexey-lapin.github.io/spring-boot-startup-analyzer) it is possible to use a Docker image based on Nginx (https://hub.docker.com/r/lexlapin/spring-boot-startup-analyzer). This can be useful, for example, for those cases when the Spring Boot services are located on the internal network of the organization, from which there is no Internet access, but there is Docker and it is possible to download an image.

To start the container, you need to run the following command:

docker run -d –name sbsa -p 8080: 80 lexlapin / spring-boot-startup-analyzer

If there is a need to refer to this container through a reverse proxy, then for this, the possibility of passing the path through the environment variable (UI_PUBLIC_PATH) is provided:

docker run -d –name sbsa -p 8080: 80 -e UI_PUBLIC_PATH = / some-path lexlapin / spring-boot-startup-analyzer

What to improve

In the future, it is planned to refine the screen with the analysis results. It would be helpful to add a tab that summarizes the types of events, their number and the total elapsed time, for example, the number and total time of bin creation. It will also be possible to build charts based on short pivot tables, besides PrimeVue provides this opportunity through the ChartJS library. Tree and tabular views can be color-coded to highlight long-running events. Additionally, it is worth adding event filtering, for example, by type.

Conclusion

The offered analyzer makes it possible to visualize the data obtained from the actuator startup endpoint in a convenient form, to estimate in detail the time spent on various types of events occurring during the start of the service, and, in general, to process startup information more efficiently. The app has a public instance on GitHub Actions, and is also available as a Docker image. This application was successfully used in one of the Luxoft projects to analyze the loading of slowed services and helped to find several classes with suboptimal logic in the constructors.

Links

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *