Skip to content

Commit

Permalink
Close #83: provide camunda metrics
Browse files Browse the repository at this point in the history
  • Loading branch information
arolfes committed Mar 13, 2021
1 parent d751b2f commit 0733843
Show file tree
Hide file tree
Showing 15 changed files with 818 additions and 0 deletions.
56 changes: 56 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ Micronaut + Camunda = :heart:
* [Custom JobExecutor Configuration](#custom-jobexecutor-configuration)
* [Transaction Management](#transaction-management)
* [Process Tests](#process-tests)
* [Camunda Metrics](#camunda-metrics)
* [Pitfalls](#pitfalls)
* [Releases](#releases)
* [Contact](#contact)
Expand Down Expand Up @@ -598,6 +599,61 @@ Note: the integration automatically disables the job executor and the process en

See also a test in our example application: [HelloWorldProcessTest](/micronaut-camunda-bpm-example/src/test/java/info/novatec/micronaut/camunda/bpm/example/HelloWorldProcessTest.java)

## Camunda Metrics

To export Camunda Metrics via micronaut-micrometer you need to add the dependency and enable it __explicitly__ in your `application.yml`.

We currently support all Camunda Build-In Metrics. See [Process Engine / Metrics](https://docs.camunda.org/manual/latest/user-guide/process-engine/metrics/)

<details>
<summary>Click to show Gradle dependencies</summary>

```groovy
implementation("io.micronaut.micrometer:micronaut-micrometer-core")
// optional enable http endpoint for metrics
implementation("io.micronaut:micronaut-management")
```
</details>

<details>
<summary>Click to show Maven dependencies</summary>

```xml
<dependency>
<groupId>io.micronaut.micrometer</groupId>
<artifactId>micronaut-micrometer-core</artifactId>
</dependency>
<!-- optional enable http endpoint for metrics -->
<dependency>
<groupId>io.micronaut</groupId>
<artifactId>micronaut-management</artifactId>
</dependency>
```
</details>

<details>
<summary>Click to show configuration</summary>

```yaml
micronaut:
metrics:
binders:
camunda:
bpmnExecution:
enabled: true
dmnExecution:
enabled: true
jobExecutor:
enabled: true
historyCleanUp:
enabled: true
```
</details>

One note about performance: The metrics execute queries against the underlying database. If you have many nodes and everywhere the metrics are enabled it can cause performance problems.

## Pitfalls

### No version information in Fat/Uber/Shadow JAR
Expand Down
7 changes: 7 additions & 0 deletions micronaut-camunda-bpm-example/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,13 @@ micronaut {

dependencies {
implementation(project(":micronaut-camunda-bpm-feature"))

// imported to enable http endpoint for metrics
implementation("io.micronaut:micronaut-management")

// imported to activate metrics
implementation("io.micronaut.micrometer:micronaut-micrometer-core")

runtimeOnly("com.h2database:h2")
runtimeOnly("ch.qos.logback:logback-classic")

Expand Down
15 changes: 15 additions & 0 deletions micronaut-camunda-bpm-example/src/main/resources/application.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,21 @@
micronaut:
application:
name: micronaut-camunda-example
metrics:
export:
logging:
enabled: true
binders:
camunda:
bpmnExecution:
enabled: true
dmnExecution:
enabled: true
jobExecutor:
enabled: true
historyCleanUp:
enabled: true

camunda:
admin-user:
id: admin
Expand Down
2 changes: 2 additions & 0 deletions micronaut-camunda-bpm-feature/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ dependencies {
implementation("io.micronaut:micronaut-runtime")
api("org.camunda.bpm:camunda-engine:$camundaVersion")

compileOnly("io.micronaut.micrometer:micronaut-micrometer-core")
compileOnly("io.micronaut.servlet:micronaut-servlet-engine")
compileOnly("io.micronaut:micronaut-http-server-netty")
compileOnly("io.micronaut.servlet:micronaut-http-server-jetty")
Expand Down Expand Up @@ -56,6 +57,7 @@ dependencies {

// Test
testImplementation("org.junit.jupiter:junit-jupiter-params")
testImplementation("org.camunda.bpm.assert:camunda-bpm-assert:8.0.0")
testImplementation("org.mockito:mockito-core:3.8.0")
testImplementation("ch.qos.logback:logback-classic")
testRuntimeOnly("com.h2database:h2")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
* Copyright 2021 original authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package info.novatec.micronaut.camunda.bpm.feature.metrics;

import io.micronaut.configuration.metrics.annotation.RequiresMetrics;
import io.micronaut.context.BeanProvider;
import io.micronaut.context.annotation.Requires;
import io.micronaut.core.util.StringUtils;
import org.camunda.bpm.engine.ManagementService;
import org.camunda.bpm.engine.management.Metrics;

import javax.inject.Singleton;

import static io.micronaut.configuration.metrics.micrometer.MeterRegistryFactory.MICRONAUT_METRICS_BINDERS;

/**
* Holder class to count bpmn execution metrics.
* <p>
* It is not possible to execute the following code directly in BpmnExectionMetricsBinder
* <code>
* Gauge.builder("MyMetricName", providerManagementService.get().createMetricsQuery().name(Metrics.ROOT_PROCESS_INSTANCE_START), MetricsQuery::sum).register(...)
* </code>
* therefore we have this HolderClass.
*/
@Singleton
@RequiresMetrics
@Requires(property = MICRONAUT_METRICS_BINDERS + ".camunda.bpmnExecution.enabled", value = StringUtils.TRUE, defaultValue = StringUtils.FALSE)
public class BpmnExecutionMetrics extends CamundaMetrics {

public BpmnExecutionMetrics(BeanProvider<ManagementService> providerManagementService) {
super(providerManagementService);
}

public long countRootProcessInstanceStart() {
return sumMetric(Metrics.ROOT_PROCESS_INSTANCE_START);
}

public long countActivityInstanceStart() {
return sumMetric(Metrics.ACTIVTY_INSTANCE_START);
}

public long countActivityInstanceEnd() {
return sumMetric(Metrics.ACTIVTY_INSTANCE_END);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/*
* Copyright 2021 original authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package info.novatec.micronaut.camunda.bpm.feature.metrics;

import io.micrometer.core.instrument.Gauge;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Tags;
import io.micrometer.core.instrument.binder.MeterBinder;
import io.micrometer.core.lang.NonNull;
import io.micronaut.configuration.metrics.annotation.RequiresMetrics;
import io.micronaut.context.annotation.Requires;
import io.micronaut.core.util.StringUtils;
import org.camunda.bpm.engine.management.Metrics;

import javax.inject.Singleton;

import static info.novatec.micronaut.camunda.bpm.feature.metrics.CamundaMetricsBinderTags.BPMN_EXECUTION_DEFAULT_TAGS;
import static info.novatec.micronaut.camunda.bpm.feature.metrics.CamundaMetricsBinderTags.TAG_KEY_NAME;
import static io.micronaut.configuration.metrics.micrometer.MeterRegistryFactory.MICRONAUT_METRICS_BINDERS;

/**
* Provides Metrics about BPMN Execution.
*/
@Singleton
@RequiresMetrics
@Requires(property = MICRONAUT_METRICS_BINDERS + ".camunda.bpmnExecution.enabled", value = StringUtils.TRUE, defaultValue = StringUtils.FALSE)
public class BpmnExecutionMetricsBinder implements MeterBinder {

protected final BpmnExecutionMetrics bpmnExecutionMetrics;

public BpmnExecutionMetricsBinder(BpmnExecutionMetrics bpmnExecutionMetrics) {
this.bpmnExecutionMetrics = bpmnExecutionMetrics;
}

@Override
public void bindTo(@NonNull MeterRegistry registry) {

/*
This Code
Gauge.builder("camunda.bpmn." + Metrics.ROOT_PROCESS_INSTANCE_START.replace('-', '.'),
providerManagementService.get().createMetricsQuery().name(Metrics.ROOT_PROCESS_INSTANCE_START),
MetricsQuery::sum
).description("The number of root process instance executions started. This is also known as Root Process Instances (RPI). A root process instance has no parent process instance, i.e. it is a top-level execution.")
.tags(Tags.concat(BPMN_EXECUTION_DEFAULT_TAGS, TAG_KEY_NAME, Metrics.ROOT_PROCESS_INSTANCE_START))
.register(registry);
is executed to early and the bean for providerManagementService is not instanciated
so that we would run into a boot loop and then the application crashes.
To avoid this, we implemented some "holder" objects. In this case info.novatec.micronaut.camunda.bpm.feature.metrics.BpmnExecutionMetrics
*/
Gauge.builder(
createMetricName(Metrics.ROOT_PROCESS_INSTANCE_START),
bpmnExecutionMetrics,
BpmnExecutionMetrics::countRootProcessInstanceStart
)
.description("The number of root process instance executions started. This is also known as Root Process Instances (RPI). A root process instance has no parent process instance, i.e. it is a top-level execution.")
.tags(Tags.concat(BPMN_EXECUTION_DEFAULT_TAGS, TAG_KEY_NAME, Metrics.ROOT_PROCESS_INSTANCE_START))
.register(registry);

Gauge.builder(
createMetricName(Metrics.ACTIVTY_INSTANCE_START),
bpmnExecutionMetrics,
BpmnExecutionMetrics::countActivityInstanceStart
)
.description("The number of activity instances started. This is also known as flow node instances (FNI).")
.tags(Tags.concat(BPMN_EXECUTION_DEFAULT_TAGS, TAG_KEY_NAME, Metrics.ACTIVTY_INSTANCE_START))
.register(registry);

Gauge.builder(
createMetricName(Metrics.ACTIVTY_INSTANCE_END),
bpmnExecutionMetrics,
BpmnExecutionMetrics::countActivityInstanceEnd
)
.description("The number of activity instances ended.")
.tags(Tags.concat(BPMN_EXECUTION_DEFAULT_TAGS, TAG_KEY_NAME, Metrics.ACTIVTY_INSTANCE_END))
.register(registry);
}

private String createMetricName(String camundaName) {
return "camunda.bpmn." + camundaName.replace('-', '.');
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
* Copyright 2021 original authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package info.novatec.micronaut.camunda.bpm.feature.metrics;

import io.micronaut.context.BeanProvider;
import io.micronaut.context.exceptions.BeanInstantiationException;
import org.camunda.bpm.engine.ManagementService;
import org.camunda.bpm.engine.management.MetricsQuery;

import java.util.HashMap;
import java.util.Map;

/**
* Base class to use inbuild camunda metrics.
* see also https://docs.camunda.org/manual/latest/user-guide/process-engine/metrics/
*/
abstract public class CamundaMetrics {

protected final BeanProvider<ManagementService> providerManagementService;

protected final Map<String, MetricsQuery> metricsQueryMap = new HashMap<>();

protected CamundaMetrics(BeanProvider<ManagementService> providerManagementService) {
this.providerManagementService = providerManagementService;
}

protected long sumMetric(String metricName) {
MetricsQuery metricsQuery = getMetricsQuery(metricName);
if (metricsQuery != null) {
return metricsQuery.sum();
}
throw new BeanInstantiationException("Camunda MetricsQuery is null");
}

protected MetricsQuery getMetricsQuery(String metricName) {
if (metricsQueryMap.containsKey(metricName)) {
return metricsQueryMap.get(metricName);
}
MetricsQuery metricsQuery = providerManagementService.get().createMetricsQuery().name(metricName);
metricsQueryMap.put(metricName, metricsQuery); // does not return the initialized metrics query
return metricsQuery;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* Copyright 2021 original authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package info.novatec.micronaut.camunda.bpm.feature.metrics;

import io.micrometer.core.instrument.Tag;

import java.util.Arrays;

/**
* Constants for our MetricsBinders.
*/
public final class CamundaMetricsBinderTags {

static final String TAG_KEY_NAME = "name";

static final String TAG_KEY_CATEGORY = "category";

static final Tag TAG_CLASS_CAMUNDA = Tag.of("class", "camunda");

static final Iterable<Tag> JOB_EXECUTOR_DEFAULT_TAGS = Arrays.asList(TAG_CLASS_CAMUNDA, Tag.of(TAG_KEY_CATEGORY, "job-executor"));

static final Iterable<Tag> DMN_EXECUTION_DEFAULT_TAGS = Arrays.asList(TAG_CLASS_CAMUNDA, Tag.of(TAG_KEY_CATEGORY, "dmn-execution"));

static final Iterable<Tag> BPMN_EXECUTION_DEFAULT_TAGS = Arrays.asList(TAG_CLASS_CAMUNDA, Tag.of(TAG_KEY_CATEGORY, "bpmn-execution"));

static final Iterable<Tag> HISTORY_CLEAN_UP_DEFAULT_TAGS = Arrays.asList(TAG_CLASS_CAMUNDA, Tag.of(TAG_KEY_CATEGORY, "history-clean-up"));

private CamundaMetricsBinderTags() {}
}
Loading

0 comments on commit 0733843

Please sign in to comment.