From 54ad37c4855aba8a9cf105e28d670bdcca6bd453 Mon Sep 17 00:00:00 2001
From: Francisco Javier Tirado Sarti
<65240126+fjtirado@users.noreply.github.com>
Date: Fri, 3 May 2024 12:45:24 +0200
Subject: [PATCH] [Fix #3486] Add input param counter (#3489)
* [Fix #3486] Add input param counter
* [Fix #3486] Arrays
---
kogito-bom/pom.xml | 23 +++
.../pom.xml | 40 ++++++
.../SonataFlowMetricProcessEventListener.java | 101 +++++++++++++
...ataFlowMetricProcessEventListenerTest.java | 134 ++++++++++++++++++
kogito-serverless-workflow/pom.xml | 1 +
quarkus/addons/monitoring/pom.xml | 1 +
quarkus/addons/monitoring/sonataflow/pom.xml | 23 +++
.../SonataFlowMetricEventListenerFactory.java | 53 +++++++
.../src/main/resources/META-INF/beans.xml | 20 +++
.../src/main/resources/application.properties | 1 +
10 files changed, 397 insertions(+)
create mode 100644 kogito-serverless-workflow/kogito-serverless-workflow-monitoring/pom.xml
create mode 100644 kogito-serverless-workflow/kogito-serverless-workflow-monitoring/src/main/java/org/kie/kogito/serverless/workflow/monitoring/SonataFlowMetricProcessEventListener.java
create mode 100644 kogito-serverless-workflow/kogito-serverless-workflow-monitoring/src/test/java/org/kie/kogito/serverless/workflow/monitoring/SonataFlowMetricProcessEventListenerTest.java
create mode 100644 quarkus/addons/monitoring/sonataflow/pom.xml
create mode 100644 quarkus/addons/monitoring/sonataflow/src/main/java/org/kie/sonataflow/monitoring/SonataFlowMetricEventListenerFactory.java
create mode 100644 quarkus/addons/monitoring/sonataflow/src/main/resources/META-INF/beans.xml
create mode 100644 quarkus/addons/monitoring/sonataflow/src/main/resources/application.properties
diff --git a/kogito-bom/pom.xml b/kogito-bom/pom.xml
index b07eec3f819..2a00e688bd8 100755
--- a/kogito-bom/pom.xml
+++ b/kogito-bom/pom.xml
@@ -695,6 +695,17 @@
${project.version}
sources
+
+ org.kie
+ kie-addons-quarkus-monitoring-sonataflow
+ ${project.version}
+
+
+ org.kie
+ kie-addons-quarkus-monitoring-sonataflow
+ ${project.version}
+ sources
+
org.kie
kie-addons-springboot-monitoring-core
@@ -1886,6 +1897,18 @@
${project.version}
sources
+
+ org.kie.kogito
+ kogito-serverless-workflow-monitoring
+ ${project.version}
+
+
+ org.kie.kogito
+ kogito-serverless-workflow-monitoring
+ ${project.version}
+ sources
+
+
org.kie.kogito
kogito-serverless-workflow-openapi-parser
diff --git a/kogito-serverless-workflow/kogito-serverless-workflow-monitoring/pom.xml b/kogito-serverless-workflow/kogito-serverless-workflow-monitoring/pom.xml
new file mode 100644
index 00000000000..b18431472d9
--- /dev/null
+++ b/kogito-serverless-workflow/kogito-serverless-workflow-monitoring/pom.xml
@@ -0,0 +1,40 @@
+
+ 4.0.0
+
+ org.kie.kogito
+ kogito-serverless-workflow
+ 999-SNAPSHOT
+
+ kogito-serverless-workflow-monitoring
+ Kogito :: Serverless Workflow :: Monitoring
+
+ org.kie.kogito.serverless.workflow.monitoring
+
+
+
+
+ org.kie
+ kie-addons-monitoring-core
+
+
+ org.kie.kogito
+ kogito-serverless-workflow-runtime
+
+
+ org.junit.jupiter
+ junit-jupiter-engine
+ test
+
+
+ org.assertj
+ assertj-core
+ test
+
+
+ org.mockito
+ mockito-inline
+ test
+
+
+
+
\ No newline at end of file
diff --git a/kogito-serverless-workflow/kogito-serverless-workflow-monitoring/src/main/java/org/kie/kogito/serverless/workflow/monitoring/SonataFlowMetricProcessEventListener.java b/kogito-serverless-workflow/kogito-serverless-workflow-monitoring/src/main/java/org/kie/kogito/serverless/workflow/monitoring/SonataFlowMetricProcessEventListener.java
new file mode 100644
index 00000000000..951d0b6dbeb
--- /dev/null
+++ b/kogito-serverless-workflow/kogito-serverless-workflow-monitoring/src/main/java/org/kie/kogito/serverless/workflow/monitoring/SonataFlowMetricProcessEventListener.java
@@ -0,0 +1,101 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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
+ *
+ * http://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 org.kie.kogito.serverless.workflow.monitoring;
+
+import java.util.Iterator;
+
+import org.kie.api.event.process.ProcessStartedEvent;
+import org.kie.kogito.KogitoGAV;
+import org.kie.kogito.internal.process.runtime.KogitoProcessInstance;
+import org.kie.kogito.jackson.utils.JsonObjectUtils;
+import org.kie.kogito.monitoring.core.common.process.MetricsProcessEventListener;
+import org.kie.kogito.serverless.workflow.SWFConstants;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+import io.micrometer.core.instrument.MeterRegistry;
+import io.micrometer.core.instrument.Tag;
+
+public class SonataFlowMetricProcessEventListener extends MetricsProcessEventListener {
+
+ public enum ArrayStoreMode {
+ STRING,
+ JSON_STRING,
+ MULTI_PARAM
+ }
+
+ static final String INPUT_PARAMS_COUNTER_NAME = "sonataflow_input_parameters_counter";
+
+ private ArrayStoreMode arrayStoreMode;
+
+ public SonataFlowMetricProcessEventListener(KogitoGAV gav, MeterRegistry meterRegistry, ArrayStoreMode arrayStoreMode) {
+ super("sonataflow-process-monitoring-listener", gav, meterRegistry);
+ this.arrayStoreMode = arrayStoreMode;
+ }
+
+ @Override
+ public void beforeProcessStarted(ProcessStartedEvent event) {
+ final KogitoProcessInstance processInstance = (KogitoProcessInstance) event.getProcessInstance();
+ Object node = processInstance.getVariables().get(SWFConstants.DEFAULT_WORKFLOW_VAR);
+ if (node instanceof ObjectNode) {
+ registerObject(processInstance.getProcessId(), null, (ObjectNode) node);
+ }
+ }
+
+ final void registerObject(String processId, String key, ObjectNode node) {
+ node.fields().forEachRemaining(e -> registerInputParam(processId, concat(key, e.getKey(), '.'), e.getValue()));
+ }
+
+ private void registerInputParam(String processId, String key, JsonNode value) {
+ if (value instanceof ObjectNode) {
+ registerObject(processId, key, (ObjectNode) value);
+ } else if (value instanceof ArrayNode) {
+ registerArray(processId, key, (ArrayNode) value);
+ } else {
+ registerValue(processId, key, value.asText());
+ }
+ }
+
+ private void registerArray(String processId, String key, ArrayNode node) {
+ if (arrayStoreMode == ArrayStoreMode.MULTI_PARAM) {
+ Iterator iter = node.elements();
+ for (int i = 0; iter.hasNext(); i++) {
+ registerInputParam(processId, concat(key, "[" + i + "]"), iter.next());
+ }
+ } else if (arrayStoreMode == ArrayStoreMode.JSON_STRING) {
+ registerValue(processId, key, node.toString());
+ } else if (arrayStoreMode == ArrayStoreMode.STRING) {
+ registerValue(processId, key, JsonObjectUtils.toJavaValue(node).toString());
+ }
+ }
+
+ private void registerValue(String processId, String key, String value) {
+ buildCounter(INPUT_PARAMS_COUNTER_NAME, "Input parameters", processId, Tag.of("param_name", key), Tag.of("param_value", value)).increment();
+ }
+
+ private String concat(String prefix, String key, char prefixChar) {
+ return prefix == null ? key : prefix + prefixChar + key;
+ }
+
+ private String concat(String prefix, String key) {
+ return prefix == null ? key : prefix + key;
+ }
+}
diff --git a/kogito-serverless-workflow/kogito-serverless-workflow-monitoring/src/test/java/org/kie/kogito/serverless/workflow/monitoring/SonataFlowMetricProcessEventListenerTest.java b/kogito-serverless-workflow/kogito-serverless-workflow-monitoring/src/test/java/org/kie/kogito/serverless/workflow/monitoring/SonataFlowMetricProcessEventListenerTest.java
new file mode 100644
index 00000000000..a02db9a6802
--- /dev/null
+++ b/kogito-serverless-workflow/kogito-serverless-workflow-monitoring/src/test/java/org/kie/kogito/serverless/workflow/monitoring/SonataFlowMetricProcessEventListenerTest.java
@@ -0,0 +1,134 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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
+ *
+ * http://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 org.kie.kogito.serverless.workflow.monitoring;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.kie.kogito.KogitoGAV;
+import org.kie.kogito.jackson.utils.ObjectMapperFactory;
+import org.kie.kogito.serverless.workflow.monitoring.SonataFlowMetricProcessEventListener.ArrayStoreMode;
+import org.mockito.MockedStatic;
+
+import com.fasterxml.jackson.databind.node.ArrayNode;
+
+import io.micrometer.core.instrument.Counter;
+import io.micrometer.core.instrument.MeterRegistry;
+
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.mockStatic;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class SonataFlowMetricProcessEventListenerTest {
+
+ private static final String PROCESS_ID = "testMetric";
+
+ private Counter counter;
+ private MeterRegistry meterRegistry;
+ private KogitoGAV kogitoGAV;
+ private Counter.Builder builder;
+ private MockedStatic factory;
+
+ @BeforeEach
+ void setup() {
+ counter = mock(Counter.class);
+ meterRegistry = mock(MeterRegistry.class);
+ builder = mock(Counter.Builder.class);
+ factory = mockStatic(Counter.class);
+ factory.when(() -> Counter.builder(SonataFlowMetricProcessEventListener.INPUT_PARAMS_COUNTER_NAME)).thenReturn(builder);
+ when(builder.register(meterRegistry)).thenReturn(counter);
+ when(builder.description(anyString())).thenReturn(builder);
+ when(builder.tag(anyString(), anyString())).thenReturn(builder);
+ kogitoGAV = new KogitoGAV("org.kogito", "test-artifact", "999-SNAPSHOT");
+ }
+
+ @AfterEach
+ void clean() {
+ factory.close();
+ }
+
+ @Test
+ void testSimpleCall() {
+ SonataFlowMetricProcessEventListener listener = new SonataFlowMetricProcessEventListener(kogitoGAV, meterRegistry, ArrayStoreMode.JSON_STRING);
+ listener.registerObject(PROCESS_ID, null, ObjectMapperFactory.get().createObjectNode().put("number", 1));
+ listener.registerObject(PROCESS_ID, null, ObjectMapperFactory.get().createObjectNode().put("number", 2));
+ verify(builder, times(2)).tag("process_id", PROCESS_ID);
+ verify(builder, times(2)).tag("param_name", "number");
+ verify(builder).tag("param_value", "1");
+ verify(builder).tag("param_value", "2");
+ verify(counter, times(2)).increment();
+ }
+
+ @Test
+ void testComplexCall() {
+ SonataFlowMetricProcessEventListener listener = new SonataFlowMetricProcessEventListener(kogitoGAV, meterRegistry, ArrayStoreMode.JSON_STRING);
+ listener.registerObject(PROCESS_ID, null,
+ ObjectMapperFactory.get().createObjectNode().set("team", ObjectMapperFactory.get().createObjectNode().put("name", "Real Betis Balompie").put("age", 117)));
+ verify(builder, times(2)).tag("process_id", PROCESS_ID);
+ verify(builder).tag("param_name", "team.name");
+ verify(builder).tag("param_value", "Real Betis Balompie");
+ verify(builder).tag("param_name", "team.age");
+ verify(builder).tag("param_value", "117");
+ verify(counter, times(2)).increment();
+ }
+
+ @Test
+ void testArrayMultiParam() {
+ SonataFlowMetricProcessEventListener listener = new SonataFlowMetricProcessEventListener(kogitoGAV, meterRegistry, ArrayStoreMode.MULTI_PARAM);
+ listener.registerObject(PROCESS_ID, null,
+ ObjectMapperFactory.get().createObjectNode().set("teams",
+ ObjectMapperFactory.get().createArrayNode().add(ObjectMapperFactory.get().createObjectNode().put("name", "Real Betis Balompie"))
+ .add(ObjectMapperFactory.get().createObjectNode().put("name", "Real Sociedad"))));
+ verify(builder, times(2)).tag("process_id", PROCESS_ID);
+ verify(builder).tag("param_name", "teams[0].name");
+ verify(builder).tag("param_value", "Real Betis Balompie");
+ verify(builder).tag("param_name", "teams[1].name");
+ verify(builder).tag("param_value", "Real Sociedad");
+ verify(counter, times(2)).increment();
+ }
+
+ @Test
+ void testArrayJsonString() {
+ SonataFlowMetricProcessEventListener listener = new SonataFlowMetricProcessEventListener(kogitoGAV, meterRegistry, ArrayStoreMode.JSON_STRING);
+ ArrayNode arrayNode = ObjectMapperFactory.get().createArrayNode().add(ObjectMapperFactory.get().createObjectNode().put("name", "Real Betis Balompie"))
+ .add(ObjectMapperFactory.get().createObjectNode().put("name", "Real Sociedad"));
+ listener.registerObject(PROCESS_ID, null,
+ ObjectMapperFactory.get().createObjectNode().set("teams", arrayNode));
+ verify(builder).tag("process_id", PROCESS_ID);
+ verify(builder).tag("param_name", "teams");
+ verify(builder).tag("param_value", arrayNode.toString());
+ verify(counter).increment();
+ }
+
+ @Test
+ void testArrayString() {
+ SonataFlowMetricProcessEventListener listener = new SonataFlowMetricProcessEventListener(kogitoGAV, meterRegistry, ArrayStoreMode.STRING);
+ ArrayNode arrayNode = ObjectMapperFactory.get().createArrayNode().add(ObjectMapperFactory.get().createObjectNode().put("name", "Real Betis Balompie"))
+ .add(ObjectMapperFactory.get().createObjectNode().put("name", "Real Sociedad"));
+ listener.registerObject(PROCESS_ID, null,
+ ObjectMapperFactory.get().createObjectNode().set("teams", arrayNode));
+ verify(builder).tag("process_id", PROCESS_ID);
+ verify(builder).tag("param_name", "teams");
+ verify(builder).tag("param_value", "[{name=Real Betis Balompie}, {name=Real Sociedad}]");
+ verify(counter).increment();
+ }
+}
diff --git a/kogito-serverless-workflow/pom.xml b/kogito-serverless-workflow/pom.xml
index cfca4a918e3..0f57d6ee30c 100644
--- a/kogito-serverless-workflow/pom.xml
+++ b/kogito-serverless-workflow/pom.xml
@@ -57,6 +57,7 @@
kogito-serverless-workflow-executor-tests
kogito-serverless-workflow-dmn-parser
kogito-serverless-workflow-dmn
+ kogito-serverless-workflow-monitoring
diff --git a/quarkus/addons/monitoring/pom.xml b/quarkus/addons/monitoring/pom.xml
index 99cd85c7866..c154209e630 100644
--- a/quarkus/addons/monitoring/pom.xml
+++ b/quarkus/addons/monitoring/pom.xml
@@ -38,6 +38,7 @@
core
prometheus
elastic
+ sonataflow
\ No newline at end of file
diff --git a/quarkus/addons/monitoring/sonataflow/pom.xml b/quarkus/addons/monitoring/sonataflow/pom.xml
new file mode 100644
index 00000000000..b98fd7cfd05
--- /dev/null
+++ b/quarkus/addons/monitoring/sonataflow/pom.xml
@@ -0,0 +1,23 @@
+
+ 4.0.0
+
+ org.kie
+ kie-addons-quarkus-monitoring-parent
+ 999-SNAPSHOT
+
+ kie-addons-quarkus-monitoring-sonataflow
+ KIE Add-On Monitoring Sonataflow
+
+ org.kie.kogito.monitoring.sonataflow.quarkus
+
+
+
+ org.kie
+ kie-addons-quarkus-monitoring-core
+
+
+ org.kie.kogito
+ kogito-serverless-workflow-monitoring
+
+
+
\ No newline at end of file
diff --git a/quarkus/addons/monitoring/sonataflow/src/main/java/org/kie/sonataflow/monitoring/SonataFlowMetricEventListenerFactory.java b/quarkus/addons/monitoring/sonataflow/src/main/java/org/kie/sonataflow/monitoring/SonataFlowMetricEventListenerFactory.java
new file mode 100644
index 00000000000..2eedb74cd7f
--- /dev/null
+++ b/quarkus/addons/monitoring/sonataflow/src/main/java/org/kie/sonataflow/monitoring/SonataFlowMetricEventListenerFactory.java
@@ -0,0 +1,53 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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
+ *
+ * http://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 org.kie.sonataflow.monitoring;
+
+import org.eclipse.microprofile.config.inject.ConfigProperty;
+import org.kie.kogito.KogitoGAV;
+import org.kie.kogito.config.ConfigBean;
+import org.kie.kogito.internal.process.event.KogitoProcessEventListener;
+import org.kie.kogito.serverless.workflow.monitoring.SonataFlowMetricProcessEventListener;
+import org.kie.kogito.serverless.workflow.monitoring.SonataFlowMetricProcessEventListener.ArrayStoreMode;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import io.micrometer.core.instrument.Metrics;
+
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.enterprise.inject.Produces;
+import jakarta.inject.Inject;
+
+@ApplicationScoped
+public class SonataFlowMetricEventListenerFactory {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(SonataFlowMetricEventListenerFactory.class);
+
+ @Inject
+ ConfigBean configBean;
+
+ @ConfigProperty(name = "kie.monitoring.sonataflow.arrays.store", defaultValue = "JSON_STRING")
+ ArrayStoreMode arrayStoreMode;
+
+ @Produces
+ public KogitoProcessEventListener produceProcessListener() {
+ LOGGER.info("Producing sonataflow listener for process monitoring.");
+ return new SonataFlowMetricProcessEventListener(
+ configBean.getGav().orElse(KogitoGAV.EMPTY_GAV), Metrics.globalRegistry, arrayStoreMode);
+ }
+}
diff --git a/quarkus/addons/monitoring/sonataflow/src/main/resources/META-INF/beans.xml b/quarkus/addons/monitoring/sonataflow/src/main/resources/META-INF/beans.xml
new file mode 100644
index 00000000000..a0eb9fbf8cd
--- /dev/null
+++ b/quarkus/addons/monitoring/sonataflow/src/main/resources/META-INF/beans.xml
@@ -0,0 +1,20 @@
+
diff --git a/quarkus/addons/monitoring/sonataflow/src/main/resources/application.properties b/quarkus/addons/monitoring/sonataflow/src/main/resources/application.properties
new file mode 100644
index 00000000000..7373d20328b
--- /dev/null
+++ b/quarkus/addons/monitoring/sonataflow/src/main/resources/application.properties
@@ -0,0 +1 @@
+kogito.monitoring.process.useDefault=false
\ No newline at end of file