-
Notifications
You must be signed in to change notification settings - Fork 991
Performance: criticality of code paths
Note
|
This wiki is currently a Work-In-Progress. |
Performance of Micrometer code is an important concern, but not all code paths have the same criticality to runtime performance. In addition to the time to execute code paths, we must also be conscious of garbage generated.
Code paths that are executed in-line with user code directly add to the execution time of user code. Performance of these code paths are of highest criticality, especially if they may be used in short and often executed parts of user code.
In these highly critical paths, sacrifices to code readability may be made where significant performance benefits can be demonstrated.
This is the most critical path because it is expected to be called most often in-line with user code.
The code for recording largely depends on the meter implementation, but there is a lot of use of classes from the java.util.concurrent.atomic
package. Look at the implementations of the increment
method for a Counter
or the record
method for a Timer
/DistributionSummary
, e.g. CumulativeCounter / StepTimer.
In many cases, a meter will be retrieved each time to record a measurement. In some cases, a reference to a meter may be retained if there are no dynamic tag values, and this can save the cost of retrieving the meter before each recording.
The Builder
API for meters will often be used as a way to retrieve meters. The builder does come with the cost of allocation. For example:
JettySslHandshakeMetrics
retrieving meter via Builder
@Override
public void handshakeSucceeded(Event event) {
SSLSession session = event.getSSLEngine().getSession();
Counter.builder(METER_NAME)
.baseUnit(BaseUnits.EVENTS)
.description(DESCRIPTION)
.tag(TAG_RESULT, "succeeded") // constant tag
.tag(TAG_PROTOCOL, session.getProtocol()) // dynamic tag
.tag(TAG_CIPHER_SUITE, session.getCipherSuite()) // dynamic tag
.tags(tags)
.register(registry) // create or retrieve
.increment(); // record
}
Any tags will involve the Tag
and Tags
API.
The combination of meter name and tags forms the Meter
Id
. If the combination of name + tags has not been registered before, the above code would create the Meter
. If it had been created before, this would retrieve the previously created Meter
.
For the retrieval inside MeterRegistry
code, besides getting the meter from the Map
that holds meters, all MeterFilter
are applied to the Meter
or Id
parameter to get the mapped ID which is the key for stored meters.
Some code paths are expected to be executed relatively less frequently. And some code paths are executed separately from user code, which does not directly contribute to user code execution time. They do contribute indirectly since they are part of the same JVM/process. In a resource constrained environment, these code paths can contribute to user code execution time as much as the in-line code paths.
While each unique meter will need to be created once during the lifetime of the application and often in-line with user code, it is considered less critical than retrieving a meter or recording a measurement. This is because creation only happens once per meter, whereas retrieval and/or recording is expected to happen many times per meter over the lifetime of the application. Additionally, since high cardinality is a concern because a meter per unique meter name + tag combination will be created, cardinality should be kept reasonably bounded and therefore meters created should be far fewer than the number of times recording a measurement or retrieving a meter.
See this code block in MeterRegistry
for meter creation logic.
-
An object lock is used to synchronize meter creation.
-
The
accept
method of eachMeterFilter
is called on the meter ID to be created. If not accepted, a no-op meter is created instead. -
If a meter takes a
DistributionStatisticConfig
, theconfigure
method for allMeterFilter
will be called on the config. -
Synthetic associations are marked.
-
All
meterAddedListeners
are called on the meter before it isput
into the meterMap
.
Publishing happens separately from user code execution usually in a dedicated single thread pool, which makes it less critical that it has the absolutely highest performance possible. Publishing includes the following code paths:
-
Some registries use
MeterPartition
to partition meters into batches for publishing. -
Retrieving all meters via
MeterRegistry#getMeters
which copies aList
of the meters. -
All
NamingConvention
API for sanitization and conventionalization of names/values from canonical form. -
How meters are converted from Micrometer’s in-memory model to a publish format differs by implementation.
-
Depending on registry implementation, sending metric data may use the
HttpSender
API or a third-party API.