Instana’s monitoring solution targets highly dynamic modern applications at any scale, from one component to thousands. These application environments exhibit high rate of change, complex architectures and large scale driving unique requirements onto the Instana web UI. A core element of the UI is the 3D powered map as a base platform for visualization, but additional requirements include:
- Ability to visualize 1 second resolution streaming metrics
- Frequent user interactions and infrastructure changings
- Multi browser, cross device support
Rendering images in three dimensions in realtime is a compute intensive task and browsers have to serve even devices with slow CPUs and GPUs like tablets and smartphones. To enable the full Instana experience for a wide range of devices, rendering performance needs to carefully engineered. This article describes the way we reduce overload for the CPU to GPU communication.
Virtual environments need to be rendered as fast as possible to increase details, effects or the number of drawn objects, especially when zooming in for more details, or zooming out for larger view of scale. If you leave complicated light shading beside, there are two main metrics that need to be optimized as much as possible: The number of visible vertices/polygons and the number of draw calls.
The first metric is the sum of all polygons currently seen and the second indicates the number of draw events of the graphics card invoked by the CPU. Since the first metric is more or less a matter of visual design the second one needs a deeper level of optimization.
The main idea for reducing draw calls is to merge objects with a similar material (e.g. all connections on the map) that are static. Static means that object don’t move continuously over time (so no animations or physical simulations). There is currently no WebGL feature that handles this part. New technologies like DX12, Vulkan and Mantle try to boost performance with command batching or parallel executions, but this is not supported on all common graphic cards or in WebGL at this time. As a result 3D-Web developers have to build their own solutions.
In the Instana map we have many similar looking objects which rarely change their position. As a result, related objects can be merged (all connection lines, all boxes, etc). But how to handle single, merged geometries where chunks appear and vanish or change their position and color over time, efficiently? For that, we created a fragment based factory, taking n fragments and one material to create a single geometry which contains all the chunks. The following graphic shows this process in a very abstract level.
The fragments are composed of modules, which can be plugged together to generate a dynamic stream of data. This datastream can be used as an input for the factory. To be flexible, we defined different modules for content creation and content manipulation. Content creation modules provide initial vertex data (cube, sphere, teapot, …). Content manipulation modules take any module as an input and provide manipulated data streams as an output.
The composed fragments can be queued into the factory with different operations (ADD, UPDATE, REMOVE). Queueing is used to collect fragment operations and batch them at a lower update rate (e.g. 1/sec) other than your rendering loop speed (typically something around 60/sec). This massively reduces aggregation work because the combined geometry data is regenerated once per update and not once per draw.
For simple animated fragments like the boxes representing the chosen metric value, another technique can be applied to keep the advantages of a low changing static geometry but get a smooth animation effect. Each fragment stores a custom value (provided by a content creation module) representing the size of a cube in y direction. This height is passed via custom attributes to the graphics card in addition to the base attributes like vertex positions and color. If the same fragment but with new data is inserted into the factory, the old and the new values are stored. A global value inside the factory represents the progress between old and new data. Both are send to the graphics card, together with the progress and all the interpolation work is done on the hardware. With this approach, you only have to update the changed custom attributes and the progress value and send a minimal dataset to the hardware. Each animation step between a linear animation only needs an update of a single float value. Today’s graphic pipelines are very cheerful handling vertex animations, providing different shader, one for the vertices and another for pixels [*1]. You can see this effect on the animated metrics in the 3D map.
Finally, there are some restrictions when working with combined geometries. First of all, there can only be one geometry per material. Materials don’t only distinguish on attributes like glossiness or texture map but on their transparency for example. So different attributes will mostly result in different materials. On the Instana map, you see many similar objects with a different transparency which mostly results in having multiple factories. On the image below you see a typical Instana environment with servers, processes, connections, issue indicators and zones resulting in ~5 draw calls. The huge advantage on the whole approach is that a much more complex environment would either take ~5 draw calls so that only the number of drawn vertices impacts the performance, but that is another story. With this pipeline setup, we have the benefit of low draw frequencies but also we can create very different environments and are prepared for quick changes or maybe totally different views.