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