Skip to content

Aggregating Profiler provide basic API for application metrics measurement.

Notifications You must be signed in to change notification settings

ru-fix/aggregating-profiler

Repository files navigation

aggregating-profiler

Maven Central

Aggregating Profiler provide basic API for application metrics measurement.

Motivation

Profiler records metrics and accumulates them in memory. Then profiler flushes aggregated values to external store. It could be timeseries databse like Graphite, OpenTSDB, InfluxDB or Prometheus or relation database like PostgreSql. You can tune time span for aggregation and flushing rate.

This approach with pre-aggregation metrics in memory allows profiler to record huge amount of measurements and do not depend on storage performance.

Profiler consist of two parts:

  • Metric recording API to trace events
  • Metric reporting API to flush metrics aggregates to external storage system

Metric recording

ProfiledCall

Suppose that we want to trace how much time it takes for SmartService to compute and return result.

Sync service measurement:

// Init single profiler instance per application 
Profiler profiler = new AggregatingProfiler();
...

// Create profiled call each time you want to make measurement 
try(ProfiledCall call = profiler.profiledCall("smart.service")){
    call.start();
    SmartResult result = smartService.doSmartComputation();
    call.stop();
}
// In case of Exception in doSmartComputation 
// the measurement will be discarded by `call.close()` 

or short version

try(ProfiledCall call = profiler.start("smart.service")){
    SmartResult result = smartService.doSmartComputation();
    call.stop();
}

or even shorter

SmartResult result = profiler.profile(
        "smart.service",
        () -> smartService.doSmartComputation());

Async service measurement:

ProfiledCall call = profiler.start("smart.service");

CompletableFuture<SmartResult> result = smartService.doSmartComputation();

result.whenComplete((exc, result)->{ 
    call.stop();
});

or short version

CompletableFuture<SmartResult> result = profiler.profileFuture(
        "smart.service",
         () -> smartService.doSmartComputation());

In real life application you can use custom aspect to wrap method invocation, etc.

ProfiledCall with payload

Some times method invocation is not sufficient and you need to record information about payload of the method. Profiler API provide a way to register integer value of custom method payload:

ProfiledCall call = profiler.start("batching.consumer");

Entry[] processedData = consumer.processData();

call.stop(processedData.size);

Indicators

Suppose you want to measure current state of the system, like how many pending request are in you buffer. In such cases indicator tracing could be very handy.

//During service initialization
profiler.attachIndicator("buffer.size", () -> buffer.size());

//During service destruction
profiler.dettachIndicator("buffer.size");

When you are attaching indicator you are saving lambda within profiler. Each time profiler builds report and flushing it into external storage such lambdas will be used to gather indicators values.

ProfiledCall metrics

Example

There are several metrics that Profiler will accumulate and flush to external storage for each ProfiledCall invocation.
Each metric have a name.

  • name - dot separated metric name

In given example reporting period is 1 minute. That means that profiler aggregates information during 1 minute and then flushes it to storage.

  • reportingTimeAvg = 60_000

Profiler takes into consideration only metrics received from profiled calls that was closed during reporting period.
Profiled calls 1 was closed and reported in previous reporting period and will be ignored.
Profiled calls #2, #3, #4, #5 will be reported. Their metrics will be used to build report. Latency is the time in milliseconds between two points: profiledCall start and stop

  • latencyMax = 1000ms
  • latencyMin = 400ms
  • latencyAvg = (1000+1000+400+500) / 4 = 725

Total calls count will be 4.

  • stopSum - 4 - how many times profiledCall was invoked

Profiled call #6 not started during current reporting period and will be ignored. Profiled call #7 started during current reporting period but not stopped yet. It will not be used in latency and callsCount calculation but it will be used during activeCalls calculation.
Profiled calls #7 and #8 started in current or previous reporting period and still running. They considered as activeCalls.
Profiled call #8 will have maximum latency: 90000ms (96000 is total time, but at the end of reporting period duration of the profiled call #8 was only 90_000ms.). Total count of active calls are 2.

  • activeCallsCountMax - 2
  • activeCallsLatencyMax - 90_000ms

There are two metrics that measure throughput: stopThroughputAvg and stopThroughputPerSecondMax. In given example there was 9 invocations during 1 minute:
9 / 60 = 0.15 invocation per second

  • stopThroughputAvg - 0.15

During reporting period there was time then invocations occurred most often.
We can find time interval of size 1 second where were 4 invocations. This means that during reporting period there was time when throughput reached 4 invocations per second.
And average throughput during reporting period of 1 minute is only 0.15 invocation per second.

  • stopThroughputPerSecondMax - 4

Metrics summary

  • name - dot separated metric name
  • reportingTimeAvg - reporting interval in milliseconds
  • latency - time in milliseconds between two points: profiledCall start and stop
    • latencyMax maximum latency
    • latencyMin minimum latency
    • latencyAvg average latency
  • stopSum - how many times profiledCall was invoked
  • stopThroughputAvg - average rate of profiledCall invocation per second
  • payload - payload provided via stop method of profiledCall
    • payloadMin - min value of payload
    • payloadMax - max value of payload
    • payloadAvg - avg value of payload
    • payloadSum - total sum of payload provided within reporting interval
    • payloadThroughputAvg - payload rate invocation per second
  • start - start metrics provide information about throughput and count of start invocation of ProfiledCall
    • startSum - how many times start method was invoked
    • startThroughputAvg - what is an average throughput for start invocation
    • startThroughputPerSecondMax - what is a maximum throughput of start method invocation
  • stopThroughputPerSecondMax - maximum rate within second time interval that was achieved during reporting period (17 means that there was a maximum of 17 invocation within 1 second interval)
  • activeCalls - calls that are still running at the end of reporting period
    • activeCallsCountMax - count of active calls that still running at the end of reporting period
    • activeCallsLatencyMax - maximum latency of active call

Indicator metrics

Here is list of metrics that Profiler will request and flush to external storage for each Indicator:

Indicators:

  • name - dot separated metric name
  • indicatorMax - instant value of indicator that was reported during report building at the end of reporting period

Metric aggregation in metric store

User can provide different storages for metrics: graphite, influx, prometheus, opentsdb. All metrics names ends with suffixes: min, max, sum, avg. This suffix could be used as a suggestion to specify how storage could compress cold data.

Tags and Labels

Tag is a key-value pair defined by user. User can define tags during ProfiledCall or IndicationProvider construction. Tags works similar to labels in Prometheus or tags in InfluxDB.

Labels is a key-value pair that is automatically linked with metrics in runtime. User can setup RegexpLabelSticker or custom LabelSticker for Profiler instance. Then we can use Labels to filter particular metrics in Reporter.

Tag is a part of identity of ProfiledCall or Indicator. Tag provided manually by a user during metric construction.

Label is a metadata associated with aggregate that is being assigned by LabelSticker. User can define LabelSticker for Profiler. You can build report only for part of metrics selected by labels. This mechanism allows to report and aggregate different types of metrics with different rate and granularity.

Metric reporting

How to register Profiler Reporter and start to record metrics to external storage.

Graphite

Graphite uses aggregation rules to compact metric storage. To simplify aggregation rules all metric names ends with suffix

  • *.*Max
  • *.*Min
  • *.*Avg
  • *.*Sum

ProfiledCall tags saved as part of metric name separated by dot .
ProfiledCall labels not stored in Graphite.

Prometheus

ProfiledCall tags saved as Prometheus labels.
ProfiledCall labels not stored in Prometheus.

InfluxDB

ProfiledCall tags saved as InfluxDB tags.
ProfiledCall labels not stored in InfluxDB.

How to mock profiler in Tests

NoopProfiler is a stub that you can use as a dependency in tests. This stub does not do anything.

val service = MyService(NoopProfiler() )

How to build this project

Build:

gradle clean build

Run jmh tests:

gradle clean build -xtest jmh --info --no-daemon

Other tracing projects

There are several projects for tracing that you can take a look:

OpenTracing:
https://github.com/opentracing/opentracing-java

HTrace:
https://github.com/apache/incubator-htrace

Zipkin:
https://github.com/openzipkin/zipkin/

Dropwizard metrics:
https://github.com/dropwizard/metrics

Source guidebook

Reporter method buildReportAndReset is not thread safe and must not be invoked concurrently from different threads. Optimization was made taking in mind that buildReportAndReset will be rarely invoked in a way such effects of first invocation will be visible to second one.