From 9430ea767549d0ae5c232c4d905daf2d91c5ab56 Mon Sep 17 00:00:00 2001 From: NipunaMadhushan Date: Tue, 19 Nov 2024 21:36:55 +0530 Subject: [PATCH 01/21] Add Ballerina metrics logs observer --- .../runtime/observability/ObserveUtils.java | 19 ++- .../metrics/BallerinaMetricsLogsObserver.java | 112 ++++++++++++++++++ 2 files changed, 130 insertions(+), 1 deletion(-) create mode 100644 bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/observability/metrics/BallerinaMetricsLogsObserver.java diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/observability/ObserveUtils.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/observability/ObserveUtils.java index 685add2c9219..567c91e38143 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/observability/ObserveUtils.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/observability/ObserveUtils.java @@ -19,6 +19,7 @@ import io.ballerina.runtime.api.Environment; import io.ballerina.runtime.api.Module; +import io.ballerina.runtime.api.Runtime; import io.ballerina.runtime.api.types.ObjectType; import io.ballerina.runtime.api.types.PredefinedTypes; import io.ballerina.runtime.api.utils.StringUtils; @@ -72,6 +73,8 @@ public final class ObserveUtils { private static final BString metricsReporter; private static final boolean tracingEnabled; private static final BString tracingProvider; + private static final boolean metricsLogsEnabled; + private static final BString metricsLogsProvider; static { // TODO: Move config initialization to ballerina level once checking config key is possible at ballerina level @@ -88,13 +91,19 @@ public final class ObserveUtils { , false); VariableKey tracingProviderKey = new VariableKey(observeModule, "tracingProvider", PredefinedTypes.TYPE_STRING, false); + VariableKey metricsLogsEnabledKey = new VariableKey(observeModule, "metricsLogsEnabled", PredefinedTypes.TYPE_BOOLEAN + , false); + VariableKey metricsLogsProviderKey = new VariableKey(observeModule, "metricsLogsProvider", + PredefinedTypes.TYPE_STRING, false); metricsEnabled = readConfig(metricsEnabledKey, enabledKey, false); metricsProvider = readConfig(metricsProviderKey, null, StringUtils.fromString("default")); metricsReporter = readConfig(metricsReporterKey, providerKey, StringUtils.fromString("choreo")); tracingEnabled = readConfig(tracingEnabledKey, enabledKey, false); tracingProvider = readConfig(tracingProviderKey, providerKey, StringUtils.fromString("choreo")); - enabled = metricsEnabled || tracingEnabled; + metricsLogsEnabled = readConfig(metricsLogsEnabledKey, enabledKey, false); + metricsLogsProvider = readConfig(metricsLogsProviderKey, providerKey, StringUtils.fromString("choreo")); + enabled = metricsEnabled || tracingEnabled || metricsLogsEnabled; } private ObserveUtils() { @@ -136,6 +145,14 @@ public static BString getTracingProvider() { return tracingProvider; } + public static boolean isMetricsLogsEnabled() { + return metricsLogsEnabled; + } + + public static BString getMetricsLogsProvider() { + return metricsLogsProvider; + } + /** * Add metrics and tracing observers. * diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/observability/metrics/BallerinaMetricsLogsObserver.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/observability/metrics/BallerinaMetricsLogsObserver.java new file mode 100644 index 000000000000..270b5a356d69 --- /dev/null +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/observability/metrics/BallerinaMetricsLogsObserver.java @@ -0,0 +1,112 @@ +package io.ballerina.runtime.observability.metrics; + +import io.ballerina.runtime.api.Environment; +import io.ballerina.runtime.api.Module; +import io.ballerina.runtime.api.creators.ValueCreator; +import io.ballerina.runtime.api.utils.StringUtils; +import io.ballerina.runtime.api.values.BMap; +import io.ballerina.runtime.api.values.BString; +import io.ballerina.runtime.observability.BallerinaObserver; +import io.ballerina.runtime.observability.ObserverContext; + +import java.io.PrintStream; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +import static io.ballerina.runtime.observability.ObservabilityConstants.*; + +public class BallerinaMetricsLogsObserver implements BallerinaObserver { + private static final String PROPERTY_START_TIME = "_observation_start_time_"; + private static final PrintStream consoleError = System.err; + + private static Environment environment; + + public BallerinaMetricsLogsObserver(Environment environment) { + BallerinaMetricsLogsObserver.environment = environment; + } + + @Override + public void startServerObservation(ObserverContext observerContext) { + } + + @Override + public void startClientObservation(ObserverContext observerContext) { + } + + @Override + public void stopServerObservation(ObserverContext observerContext) { + if (!observerContext.isStarted()) { + // Do not collect metrics if the observation hasn't started + return; + } + stopObservation(observerContext); + } + + @Override + public void stopClientObservation(ObserverContext observerContext) { + if (!observerContext.isStarted()) { + // Do not collect metrics if the observation hasn't started + return; + } + stopObservation(observerContext); + } + + private void stopObservation(ObserverContext observerContext) { + Set tags = new HashSet<>(); + Map customTags = observerContext.customMetricTags; + if (customTags != null) { + tags.addAll(customTags.values()); + } + tags.addAll(observerContext.getAllTags()); + + // Add status_code_group tag + Integer statusCode = (Integer) observerContext.getProperty(PROPERTY_KEY_HTTP_STATUS_CODE); + if (statusCode != null && statusCode > 0) { + tags.add(Tag.of(TAG_KEY_HTTP_STATUS_CODE_GROUP, (statusCode / 100) + STATUS_CODE_GROUP_SUFFIX)); + } + + try { + Long startTime = (Long) observerContext.getProperty(PROPERTY_START_TIME); + long duration = System.nanoTime() - startTime; + + Optional protocolValue = Optional.empty(); + if (tags.stream().anyMatch(tag -> tag.getKey().equals("protocol"))) { + protocolValue = tags.stream().filter(tag -> tag.getKey().equals("protocol")).map(Tag::getValue).findFirst(); + } + String protocol = protocolValue.orElse("http"); + + BMap logAttributes = ValueCreator.createMapValue(); + logAttributes.put(StringUtils.fromString("protocol"), StringUtils.fromString(protocol)); + tags.stream().filter(tag -> !tag.getKey().equals("protocol")) + .forEach(tag -> logAttributes.put(StringUtils.fromString(tag.getKey()), + StringUtils.fromString(tag.getValue()))); + logAttributes.put(StringUtils.fromString("response_time_seconds"), + StringUtils.fromString(String.valueOf(duration / 1E9))); + + printMetricLog(logAttributes); +// BString tempMessage = StringUtils.fromString("response_time_seconds: " + duration / 1E9); +// printLog(tempMessage); + } catch (RuntimeException e) { + handleError("multiple metrics", tags, e); + } + } + + private void handleError(String metricName, Set tags, RuntimeException e) { + // Metric Provider may throw exceptions if there is a mismatch in tags. + consoleError.println("error: error collecting metrics for " + metricName + " with tags " + tags + + ": " + e.getMessage()); + } + + private static void printMetricLog(BMap logAttributes) { + Module metricsLogsModule = new Module("ballerinax", "metrics.logs", "1"); + environment.getRuntime().callFunction(metricsLogsModule, "printMetricsLog", null, logAttributes); + } + + private static void printLog(BString message) { + Module metricsLogsModule = new Module("ballerinax", "metrics.logs", "1"); + environment.getRuntime().callFunction(metricsLogsModule, "printLog", null, message); + } +} From d7133b58e0ef25d41bf2a2461329a01f58aa80f5 Mon Sep 17 00:00:00 2001 From: NipunaMadhushan Date: Wed, 20 Nov 2024 10:50:00 +0530 Subject: [PATCH 02/21] Add comments --- .../metrics/BallerinaMetricsLogsObserver.java | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/observability/metrics/BallerinaMetricsLogsObserver.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/observability/metrics/BallerinaMetricsLogsObserver.java index 270b5a356d69..564b29f77463 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/observability/metrics/BallerinaMetricsLogsObserver.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/observability/metrics/BallerinaMetricsLogsObserver.java @@ -7,10 +7,10 @@ import io.ballerina.runtime.api.values.BMap; import io.ballerina.runtime.api.values.BString; import io.ballerina.runtime.observability.BallerinaObserver; +import io.ballerina.runtime.observability.ObserveUtils; import io.ballerina.runtime.observability.ObserverContext; import java.io.PrintStream; -import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Optional; @@ -19,6 +19,7 @@ import static io.ballerina.runtime.observability.ObservabilityConstants.*; public class BallerinaMetricsLogsObserver implements BallerinaObserver { + private static final String ORG_NAME = "ballerinax"; private static final String PROPERTY_START_TIME = "_observation_start_time_"; private static final PrintStream consoleError = System.err; @@ -87,8 +88,6 @@ private void stopObservation(ObserverContext observerContext) { StringUtils.fromString(String.valueOf(duration / 1E9))); printMetricLog(logAttributes); -// BString tempMessage = StringUtils.fromString("response_time_seconds: " + duration / 1E9); -// printLog(tempMessage); } catch (RuntimeException e) { handleError("multiple metrics", tags, e); } @@ -101,12 +100,8 @@ private void handleError(String metricName, Set tags, RuntimeException e) { } private static void printMetricLog(BMap logAttributes) { - Module metricsLogsModule = new Module("ballerinax", "metrics.logs", "1"); + // TODO: Remove version when the API is finalized, and add the configured org name. + Module metricsLogsModule = new Module(ORG_NAME, ObserveUtils.getMetricsLogsProvider().getValue(), "1"); environment.getRuntime().callFunction(metricsLogsModule, "printMetricsLog", null, logAttributes); } - - private static void printLog(BString message) { - Module metricsLogsModule = new Module("ballerinax", "metrics.logs", "1"); - environment.getRuntime().callFunction(metricsLogsModule, "printLog", null, message); - } } From 80b44011aab3c0cb4506c25bb274f23d41408f44 Mon Sep 17 00:00:00 2001 From: NipunaMadhushan Date: Wed, 20 Nov 2024 11:08:20 +0530 Subject: [PATCH 03/21] Resolve checkstyle errors --- .../observability/metrics/BallerinaMetricsLogsObserver.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/observability/metrics/BallerinaMetricsLogsObserver.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/observability/metrics/BallerinaMetricsLogsObserver.java index 564b29f77463..c4f8c1023c71 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/observability/metrics/BallerinaMetricsLogsObserver.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/observability/metrics/BallerinaMetricsLogsObserver.java @@ -16,7 +16,9 @@ import java.util.Optional; import java.util.Set; -import static io.ballerina.runtime.observability.ObservabilityConstants.*; +import static io.ballerina.runtime.observability.ObservabilityConstants.PROPERTY_KEY_HTTP_STATUS_CODE; +import static io.ballerina.runtime.observability.ObservabilityConstants.TAG_KEY_HTTP_STATUS_CODE_GROUP; +import static io.ballerina.runtime.observability.ObservabilityConstants.STATUS_CODE_GROUP_SUFFIX; public class BallerinaMetricsLogsObserver implements BallerinaObserver { private static final String ORG_NAME = "ballerinax"; From baf657013be50d62650602fe7ff6ef7bfbf91404 Mon Sep 17 00:00:00 2001 From: NipunaMadhushan Date: Wed, 20 Nov 2024 14:12:44 +0530 Subject: [PATCH 04/21] Resolve checkstyle errors --- .../io/ballerina/runtime/observability/ObserveUtils.java | 5 ++--- .../observability/metrics/BallerinaMetricsLogsObserver.java | 3 ++- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/observability/ObserveUtils.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/observability/ObserveUtils.java index 567c91e38143..ab773e90c508 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/observability/ObserveUtils.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/observability/ObserveUtils.java @@ -19,7 +19,6 @@ import io.ballerina.runtime.api.Environment; import io.ballerina.runtime.api.Module; -import io.ballerina.runtime.api.Runtime; import io.ballerina.runtime.api.types.ObjectType; import io.ballerina.runtime.api.types.PredefinedTypes; import io.ballerina.runtime.api.utils.StringUtils; @@ -91,8 +90,8 @@ public final class ObserveUtils { , false); VariableKey tracingProviderKey = new VariableKey(observeModule, "tracingProvider", PredefinedTypes.TYPE_STRING, false); - VariableKey metricsLogsEnabledKey = new VariableKey(observeModule, "metricsLogsEnabled", PredefinedTypes.TYPE_BOOLEAN - , false); + VariableKey metricsLogsEnabledKey = new VariableKey(observeModule, "metricsLogsEnabled", + PredefinedTypes.TYPE_BOOLEAN, false); VariableKey metricsLogsProviderKey = new VariableKey(observeModule, "metricsLogsProvider", PredefinedTypes.TYPE_STRING, false); diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/observability/metrics/BallerinaMetricsLogsObserver.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/observability/metrics/BallerinaMetricsLogsObserver.java index c4f8c1023c71..a689af535305 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/observability/metrics/BallerinaMetricsLogsObserver.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/observability/metrics/BallerinaMetricsLogsObserver.java @@ -77,7 +77,8 @@ private void stopObservation(ObserverContext observerContext) { Optional protocolValue = Optional.empty(); if (tags.stream().anyMatch(tag -> tag.getKey().equals("protocol"))) { - protocolValue = tags.stream().filter(tag -> tag.getKey().equals("protocol")).map(Tag::getValue).findFirst(); + protocolValue = tags.stream().filter(tag -> tag.getKey().equals("protocol")).map(Tag::getValue) + .findFirst(); } String protocol = protocolValue.orElse("http"); From 6a81b65406cb10ea5b8592fc331b247b6db7d0dd Mon Sep 17 00:00:00 2001 From: NipunaMadhushan Date: Thu, 21 Nov 2024 01:00:43 +0530 Subject: [PATCH 05/21] Add license header --- .../metrics/BallerinaMetricsLogsObserver.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/observability/metrics/BallerinaMetricsLogsObserver.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/observability/metrics/BallerinaMetricsLogsObserver.java index a689af535305..80443da2106c 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/observability/metrics/BallerinaMetricsLogsObserver.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/observability/metrics/BallerinaMetricsLogsObserver.java @@ -1,3 +1,20 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com) All Rights Reserved. + * + * WSO2 LLC. 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 io.ballerina.runtime.observability.metrics; import io.ballerina.runtime.api.Environment; From 4c7862e7caa0fe7e67c827dbd39ad39874526b0e Mon Sep 17 00:00:00 2001 From: NipunaMadhushan Date: Thu, 21 Nov 2024 11:21:10 +0530 Subject: [PATCH 06/21] Move observer classes to ballerinai/observe module --- .../metrics/BallerinaMetricsLogsObserver.java | 127 --------------- .../metrics/BallerinaMetricsObserver.java | 144 ------------------ .../tracer/BallerinaTracingObserver.java | 47 ------ 3 files changed, 318 deletions(-) delete mode 100644 bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/observability/metrics/BallerinaMetricsLogsObserver.java delete mode 100644 bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/observability/metrics/BallerinaMetricsObserver.java delete mode 100644 bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/observability/tracer/BallerinaTracingObserver.java diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/observability/metrics/BallerinaMetricsLogsObserver.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/observability/metrics/BallerinaMetricsLogsObserver.java deleted file mode 100644 index 80443da2106c..000000000000 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/observability/metrics/BallerinaMetricsLogsObserver.java +++ /dev/null @@ -1,127 +0,0 @@ -/* - * Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com) All Rights Reserved. - * - * WSO2 LLC. 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 io.ballerina.runtime.observability.metrics; - -import io.ballerina.runtime.api.Environment; -import io.ballerina.runtime.api.Module; -import io.ballerina.runtime.api.creators.ValueCreator; -import io.ballerina.runtime.api.utils.StringUtils; -import io.ballerina.runtime.api.values.BMap; -import io.ballerina.runtime.api.values.BString; -import io.ballerina.runtime.observability.BallerinaObserver; -import io.ballerina.runtime.observability.ObserveUtils; -import io.ballerina.runtime.observability.ObserverContext; - -import java.io.PrintStream; -import java.util.HashSet; -import java.util.Map; -import java.util.Optional; -import java.util.Set; - -import static io.ballerina.runtime.observability.ObservabilityConstants.PROPERTY_KEY_HTTP_STATUS_CODE; -import static io.ballerina.runtime.observability.ObservabilityConstants.TAG_KEY_HTTP_STATUS_CODE_GROUP; -import static io.ballerina.runtime.observability.ObservabilityConstants.STATUS_CODE_GROUP_SUFFIX; - -public class BallerinaMetricsLogsObserver implements BallerinaObserver { - private static final String ORG_NAME = "ballerinax"; - private static final String PROPERTY_START_TIME = "_observation_start_time_"; - private static final PrintStream consoleError = System.err; - - private static Environment environment; - - public BallerinaMetricsLogsObserver(Environment environment) { - BallerinaMetricsLogsObserver.environment = environment; - } - - @Override - public void startServerObservation(ObserverContext observerContext) { - } - - @Override - public void startClientObservation(ObserverContext observerContext) { - } - - @Override - public void stopServerObservation(ObserverContext observerContext) { - if (!observerContext.isStarted()) { - // Do not collect metrics if the observation hasn't started - return; - } - stopObservation(observerContext); - } - - @Override - public void stopClientObservation(ObserverContext observerContext) { - if (!observerContext.isStarted()) { - // Do not collect metrics if the observation hasn't started - return; - } - stopObservation(observerContext); - } - - private void stopObservation(ObserverContext observerContext) { - Set tags = new HashSet<>(); - Map customTags = observerContext.customMetricTags; - if (customTags != null) { - tags.addAll(customTags.values()); - } - tags.addAll(observerContext.getAllTags()); - - // Add status_code_group tag - Integer statusCode = (Integer) observerContext.getProperty(PROPERTY_KEY_HTTP_STATUS_CODE); - if (statusCode != null && statusCode > 0) { - tags.add(Tag.of(TAG_KEY_HTTP_STATUS_CODE_GROUP, (statusCode / 100) + STATUS_CODE_GROUP_SUFFIX)); - } - - try { - Long startTime = (Long) observerContext.getProperty(PROPERTY_START_TIME); - long duration = System.nanoTime() - startTime; - - Optional protocolValue = Optional.empty(); - if (tags.stream().anyMatch(tag -> tag.getKey().equals("protocol"))) { - protocolValue = tags.stream().filter(tag -> tag.getKey().equals("protocol")).map(Tag::getValue) - .findFirst(); - } - String protocol = protocolValue.orElse("http"); - - BMap logAttributes = ValueCreator.createMapValue(); - logAttributes.put(StringUtils.fromString("protocol"), StringUtils.fromString(protocol)); - tags.stream().filter(tag -> !tag.getKey().equals("protocol")) - .forEach(tag -> logAttributes.put(StringUtils.fromString(tag.getKey()), - StringUtils.fromString(tag.getValue()))); - logAttributes.put(StringUtils.fromString("response_time_seconds"), - StringUtils.fromString(String.valueOf(duration / 1E9))); - - printMetricLog(logAttributes); - } catch (RuntimeException e) { - handleError("multiple metrics", tags, e); - } - } - - private void handleError(String metricName, Set tags, RuntimeException e) { - // Metric Provider may throw exceptions if there is a mismatch in tags. - consoleError.println("error: error collecting metrics for " + metricName + " with tags " + tags + - ": " + e.getMessage()); - } - - private static void printMetricLog(BMap logAttributes) { - // TODO: Remove version when the API is finalized, and add the configured org name. - Module metricsLogsModule = new Module(ORG_NAME, ObserveUtils.getMetricsLogsProvider().getValue(), "1"); - environment.getRuntime().callFunction(metricsLogsModule, "printMetricsLog", null, logAttributes); - } -} diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/observability/metrics/BallerinaMetricsObserver.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/observability/metrics/BallerinaMetricsObserver.java deleted file mode 100644 index d796ed991094..000000000000 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/observability/metrics/BallerinaMetricsObserver.java +++ /dev/null @@ -1,144 +0,0 @@ -/* - * Copyright (c) 2018, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. - * - * WSO2 Inc. 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 io.ballerina.runtime.observability.metrics; - -import io.ballerina.runtime.observability.BallerinaObserver; -import io.ballerina.runtime.observability.ObserverContext; - -import java.io.PrintStream; -import java.time.Duration; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; - -import static io.ballerina.runtime.observability.ObservabilityConstants.PROPERTY_KEY_HTTP_STATUS_CODE; -import static io.ballerina.runtime.observability.ObservabilityConstants.STATUS_CODE_GROUP_SUFFIX; -import static io.ballerina.runtime.observability.ObservabilityConstants.TAG_KEY_HTTP_STATUS_CODE_GROUP; - -/** - * Observe the runtime and collect measurements. - */ -public class BallerinaMetricsObserver implements BallerinaObserver { - - private static final String PROPERTY_START_TIME = "_observation_start_time_"; - private static final String PROPERTY_IN_PROGRESS_COUNTER = "_observation_in_progress_counter_"; - - private static final PrintStream consoleError = System.err; - - private static final MetricRegistry metricRegistry = DefaultMetricRegistry.getInstance(); - - private static final StatisticConfig[] responseTimeStatisticConfigs = new StatisticConfig[]{ - StatisticConfig.builder() - .expiry(Duration.ofSeconds(10)) - .percentiles(StatisticConfig.DEFAULT.getPercentiles()) - .build(), - StatisticConfig.builder() - .expiry(Duration.ofMinutes(1)) - .percentiles(StatisticConfig.DEFAULT.getPercentiles()) - .build(), - StatisticConfig.builder() - .expiry(Duration.ofMinutes(5)) - .percentiles(StatisticConfig.DEFAULT.getPercentiles()) - .build() - }; - - @Override - public void startServerObservation(ObserverContext observerContext) { - startObservation(observerContext); - } - - @Override - public void startClientObservation(ObserverContext observerContext) { - startObservation(observerContext); - } - - @Override - public void stopServerObservation(ObserverContext observerContext) { - if (!observerContext.isStarted()) { - // Do not collect metrics if the observation hasn't started - return; - } - stopObservation(observerContext); - } - - @Override - public void stopClientObservation(ObserverContext observerContext) { - if (!observerContext.isStarted()) { - // Do not collect metrics if the observation hasn't started - return; - } - stopObservation(observerContext); - } - - private void startObservation(ObserverContext observerContext) { - observerContext.addProperty(PROPERTY_START_TIME, System.nanoTime()); - Set tags = observerContext.getAllTags(); - try { - Gauge inProgressGauge = metricRegistry.gauge(new MetricId("inprogress_requests", "In-progress requests", - tags)); - inProgressGauge.increment(); - /* - * The in progress counter is stored so that the same counter can be decremted when the observation - * ends. This is needed as the the program may add tags to the context causing the tags to be - * different at the end compared to the start. - */ - observerContext.addProperty(PROPERTY_IN_PROGRESS_COUNTER, inProgressGauge); - } catch (RuntimeException e) { - handleError("inprogress_requests", tags, e); - } - } - - private void stopObservation(ObserverContext observerContext) { - Set tags = new HashSet<>(); - Map customTags = observerContext.customMetricTags; - if (customTags != null) { - tags.addAll(customTags.values()); - } - tags.addAll(observerContext.getAllTags()); - - // Add status_code_group tag - Integer statusCode = (Integer) observerContext.getProperty(PROPERTY_KEY_HTTP_STATUS_CODE); - if (statusCode != null && statusCode > 0) { - tags.add(Tag.of(TAG_KEY_HTTP_STATUS_CODE_GROUP, (statusCode / 100) + STATUS_CODE_GROUP_SUFFIX)); - } - - try { - Long startTime = (Long) observerContext.getProperty(PROPERTY_START_TIME); - long duration = System.nanoTime() - startTime; - ((Gauge) observerContext.getProperty(PROPERTY_IN_PROGRESS_COUNTER)).decrement(); - metricRegistry.gauge(new MetricId("response_time_seconds", - "Response time", tags), responseTimeStatisticConfigs).setValue(duration / 1E9); - metricRegistry.counter(new MetricId("response_time_nanoseconds_total", - "Total response response time for all requests", tags)).increment(duration); - metricRegistry.counter(new MetricId("requests_total", - "Total number of requests", tags)).increment(); - if (statusCode != null && 400 <= statusCode && statusCode < 600) { - metricRegistry.counter(new MetricId("response_errors_total", - "Total number of response errors", tags)).increment(); - } - } catch (RuntimeException e) { - handleError("multiple metrics", tags, e); - } - } - - private void handleError(String metricName, Set tags, RuntimeException e) { - // Metric Provider may throw exceptions if there is a mismatch in tags. - consoleError.println("error: error collecting metrics for " + metricName + " with tags " + tags + - ": " + e.getMessage()); - } -} diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/observability/tracer/BallerinaTracingObserver.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/observability/tracer/BallerinaTracingObserver.java deleted file mode 100644 index d6cbc277d813..000000000000 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/observability/tracer/BallerinaTracingObserver.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (c) 2019, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. - * - * WSO2 Inc. 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 io.ballerina.runtime.observability.tracer; - -import io.ballerina.runtime.observability.BallerinaObserver; -import io.ballerina.runtime.observability.ObserverContext; - -/** - * Observe the runtime and start/stop tracing. - */ -public class BallerinaTracingObserver implements BallerinaObserver { - - @Override - public void startServerObservation(ObserverContext observerContext) { - TracingUtils.startObservation(observerContext, false); - } - - @Override - public void startClientObservation(ObserverContext observerContext) { - TracingUtils.startObservation(observerContext, true); - } - - @Override - public void stopServerObservation(ObserverContext observerContext) { - TracingUtils.stopObservation(observerContext); - } - - @Override - public void stopClientObservation(ObserverContext observerContext) { - TracingUtils.stopObservation(observerContext); - } -} From 413284074d8a65b8f4b1a334cd8128b41d487c71 Mon Sep 17 00:00:00 2001 From: NipunaMadhushan Date: Fri, 22 Nov 2024 10:05:14 +0530 Subject: [PATCH 07/21] Remove metricsLogsProvider config --- .../io/ballerina/runtime/observability/ObserveUtils.java | 8 -------- 1 file changed, 8 deletions(-) diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/observability/ObserveUtils.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/observability/ObserveUtils.java index ab773e90c508..6f1c7a23fc8d 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/observability/ObserveUtils.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/observability/ObserveUtils.java @@ -73,7 +73,6 @@ public final class ObserveUtils { private static final boolean tracingEnabled; private static final BString tracingProvider; private static final boolean metricsLogsEnabled; - private static final BString metricsLogsProvider; static { // TODO: Move config initialization to ballerina level once checking config key is possible at ballerina level @@ -92,8 +91,6 @@ public final class ObserveUtils { PredefinedTypes.TYPE_STRING, false); VariableKey metricsLogsEnabledKey = new VariableKey(observeModule, "metricsLogsEnabled", PredefinedTypes.TYPE_BOOLEAN, false); - VariableKey metricsLogsProviderKey = new VariableKey(observeModule, "metricsLogsProvider", - PredefinedTypes.TYPE_STRING, false); metricsEnabled = readConfig(metricsEnabledKey, enabledKey, false); metricsProvider = readConfig(metricsProviderKey, null, StringUtils.fromString("default")); @@ -101,7 +98,6 @@ public final class ObserveUtils { tracingEnabled = readConfig(tracingEnabledKey, enabledKey, false); tracingProvider = readConfig(tracingProviderKey, providerKey, StringUtils.fromString("choreo")); metricsLogsEnabled = readConfig(metricsLogsEnabledKey, enabledKey, false); - metricsLogsProvider = readConfig(metricsLogsProviderKey, providerKey, StringUtils.fromString("choreo")); enabled = metricsEnabled || tracingEnabled || metricsLogsEnabled; } @@ -148,10 +144,6 @@ public static boolean isMetricsLogsEnabled() { return metricsLogsEnabled; } - public static BString getMetricsLogsProvider() { - return metricsLogsProvider; - } - /** * Add metrics and tracing observers. * From 70ee31032c938f26e2397ac16d6b18b436b53e9c Mon Sep 17 00:00:00 2001 From: NipunaMadhushan Date: Thu, 28 Nov 2024 10:23:50 +0530 Subject: [PATCH 08/21] Update defining configuration --- .../java/io/ballerina/runtime/observability/ObserveUtils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/observability/ObserveUtils.java b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/observability/ObserveUtils.java index 6f1c7a23fc8d..a38117c3ca07 100644 --- a/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/observability/ObserveUtils.java +++ b/bvm/ballerina-runtime/src/main/java/io/ballerina/runtime/observability/ObserveUtils.java @@ -97,7 +97,7 @@ public final class ObserveUtils { metricsReporter = readConfig(metricsReporterKey, providerKey, StringUtils.fromString("choreo")); tracingEnabled = readConfig(tracingEnabledKey, enabledKey, false); tracingProvider = readConfig(tracingProviderKey, providerKey, StringUtils.fromString("choreo")); - metricsLogsEnabled = readConfig(metricsLogsEnabledKey, enabledKey, false); + metricsLogsEnabled = readConfig(metricsLogsEnabledKey, metricsLogsEnabledKey, false); enabled = metricsEnabled || tracingEnabled || metricsLogsEnabled; } From ebf69204380e1936fd6845b4afa606b7ec580fdc Mon Sep 17 00:00:00 2001 From: Nipuna Ranasinghe Date: Wed, 11 Dec 2024 14:33:23 +0530 Subject: [PATCH 09/21] Improve fast-run feature to launch programs in debug mode --- .../commons/workspace/RunContext.java | 66 +++++++++++++++++++ .../commons/workspace/WorkspaceManager.java | 5 +- .../command/executors/RunExecutor.java | 33 +++++++--- .../workspace/BallerinaWorkspaceManager.java | 13 ++-- 4 files changed, 102 insertions(+), 15 deletions(-) create mode 100644 language-server/modules/langserver-commons/src/main/java/org/ballerinalang/langserver/commons/workspace/RunContext.java diff --git a/language-server/modules/langserver-commons/src/main/java/org/ballerinalang/langserver/commons/workspace/RunContext.java b/language-server/modules/langserver-commons/src/main/java/org/ballerinalang/langserver/commons/workspace/RunContext.java new file mode 100644 index 000000000000..af831a5cb2c3 --- /dev/null +++ b/language-server/modules/langserver-commons/src/main/java/org/ballerinalang/langserver/commons/workspace/RunContext.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2020, WSO2 LLC. (http://www.wso2.org). + * + * WSO2 Inc. 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.ballerinalang.langserver.commons.workspace; + +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * Represents the context required to run a Ballerina program using the LS workspace manager. + * + * @param balSourcePath Ballerina source file path to run + * @param programArgs program arguments to run the program + * @param env environment variables to be added to the program + * @param debugPort debug port to be used for debugging (if available) + * @since 2201.11.0 + */ +public record RunContext(Path balSourcePath, List programArgs, Map env, Integer debugPort) { + + public static class Builder { + + private final Path sourcePath; + private List programArgs = new ArrayList<>(); + private Map env = Map.of(); + private Integer debugPort = -1; + + public Builder(Path sourcePath) { + this.sourcePath = sourcePath; + } + + public Builder withProgramArgs(List programArgs) { + this.programArgs = programArgs; + return this; + } + + public Builder withEnv(Map env) { + this.env = env; + return this; + } + + public Builder withDebugPort(Integer debugPort) { + this.debugPort = debugPort; + return this; + } + + public RunContext build() { + return new RunContext(sourcePath, programArgs, env, debugPort); + } + } +} \ No newline at end of file diff --git a/language-server/modules/langserver-commons/src/main/java/org/ballerinalang/langserver/commons/workspace/WorkspaceManager.java b/language-server/modules/langserver-commons/src/main/java/org/ballerinalang/langserver/commons/workspace/WorkspaceManager.java index 156c8e92b57c..1a6206fb9c01 100644 --- a/language-server/modules/langserver-commons/src/main/java/org/ballerinalang/langserver/commons/workspace/WorkspaceManager.java +++ b/language-server/modules/langserver-commons/src/main/java/org/ballerinalang/langserver/commons/workspace/WorkspaceManager.java @@ -239,12 +239,13 @@ public interface WorkspaceManager { /** * Compiles and runs the project of the given file path. Run happens in a separate process. - * @param filePath Path that belongs to the project to be run. + * + * @param runContext context related to the project to be run. * @return Process created by running the project. Empty if failed due to non process related issues. * @throws IOException If failed to start the process. * @since 2201.6.0 */ - Optional run(Path filePath, List mainFuncArgs) throws IOException; + Optional run(RunContext runContext) throws IOException; /** * Stop a running process started with {@link #run}. diff --git a/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/command/executors/RunExecutor.java b/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/command/executors/RunExecutor.java index 31222bda88df..be04d327edf9 100644 --- a/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/command/executors/RunExecutor.java +++ b/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/command/executors/RunExecutor.java @@ -22,6 +22,7 @@ import org.ballerinalang.langserver.commons.client.ExtendedLanguageClient; import org.ballerinalang.langserver.commons.command.LSCommandExecutorException; import org.ballerinalang.langserver.commons.command.spi.LSCommandExecutor; +import org.ballerinalang.langserver.commons.workspace.RunContext; import org.eclipse.lsp4j.LogTraceParams; import java.io.IOException; @@ -45,8 +46,16 @@ public class RunExecutor implements LSCommandExecutor { @Override public Boolean execute(ExecuteCommandContext context) throws LSCommandExecutorException { try { - Optional processOpt = context.workspace().run(extractPath(context), - extractMainFunctionArgs(context)); + RunContext.Builder builder = new RunContext.Builder(extractPath(context)); + builder.withProgramArgs(extractProgramArgs(context)); + int debugPort = extractDebugArgs(context); + if (debugPort > 0) { + builder.withDebugPort(debugPort); + } + // TODO handle env vars + + RunContext RunContext = builder.build(); + Optional processOpt = context.workspace().run(RunContext); if (processOpt.isEmpty()) { return false; } @@ -60,17 +69,25 @@ public Boolean execute(ExecuteCommandContext context) throws LSCommandExecutorEx } private static Path extractPath(ExecuteCommandContext context) { - return Path.of(context.getArguments().get(0).value().getAsString()); + return Path.of(context.getArguments().getFirst().value().getAsString()); } - private static List extractMainFunctionArgs(ExecuteCommandContext context) { + private int extractDebugArgs(ExecuteCommandContext context) { + return context.getArguments().stream() + .filter(commandArg -> commandArg.key().equals("debugPort")) + .map(commandArg -> commandArg.value().getAsInt()) + .findAny() + .orElse(-1); + } + + private static List extractProgramArgs(ExecuteCommandContext context) { List args = new ArrayList<>(); - if (context.getArguments().size() == 1) { + if (context.getArguments().size() <= 2) { return args; } - context.getArguments().get(1).value().getAsJsonArray().iterator().forEachRemaining(arg -> { - args.add(arg.getAsString()); - }); + context.getArguments().get(2).value().getAsJsonArray().iterator() + .forEachRemaining(arg -> args.add(arg.getAsString())); + return args; } diff --git a/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/workspace/BallerinaWorkspaceManager.java b/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/workspace/BallerinaWorkspaceManager.java index 97a9f13575f3..9ca9223d71e0 100644 --- a/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/workspace/BallerinaWorkspaceManager.java +++ b/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/workspace/BallerinaWorkspaceManager.java @@ -60,6 +60,7 @@ import org.ballerinalang.langserver.commons.client.ExtendedLanguageClient; import org.ballerinalang.langserver.commons.eventsync.EventKind; import org.ballerinalang.langserver.commons.eventsync.exceptions.EventSyncException; +import org.ballerinalang.langserver.commons.workspace.RunContext; import org.ballerinalang.langserver.commons.workspace.WorkspaceDocumentException; import org.ballerinalang.langserver.commons.workspace.WorkspaceDocumentManager; import org.ballerinalang.langserver.commons.workspace.WorkspaceManager; @@ -101,7 +102,6 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; - import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -588,8 +588,9 @@ public String uriScheme() { } @Override - public Optional run(Path filePath, List mainFuncArgs) throws IOException { - Optional projectPairOpt = projectContext(projectRoot(filePath)); + public Optional run(RunContext context) throws IOException { + Path projectRoot = projectRoot(context.balSourcePath()); + Optional projectPairOpt = projectContext(projectRoot); if (projectPairOpt.isEmpty()) { String msg = "Run command execution aborted because project is not loaded"; UserErrorException e = new UserErrorException(msg); @@ -629,10 +630,13 @@ public Optional run(Path filePath, List mainFuncArgs) throws IO commands.add(System.getProperty("java.command")); commands.add("-XX:+HeapDumpOnOutOfMemoryError"); commands.add("-XX:HeapDumpPath=" + System.getProperty(USER_DIR)); + if (context.debugPort() > 0) { + commands.add("-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=*:" + context.debugPort()); + } commands.add("-cp"); commands.add(getAllClassPaths(jarResolver)); commands.add(initClassName); - commands.addAll(mainFuncArgs); + commands.addAll(context.programArgs()); ProcessBuilder pb = new ProcessBuilder(commands); Lock lock = projectContext.lockAndGet(); @@ -670,7 +674,6 @@ private static JBallerinaBackend execBackend(ProjectContext projectContext, } } - @Override public boolean stop(Path filePath) { Optional projectPairOpt = projectContext(projectRoot(filePath)); From 55371421ab25b658a15a9b63a5701b5bb4cd07bc Mon Sep 17 00:00:00 2001 From: Nipuna Ranasinghe Date: Wed, 11 Dec 2024 15:40:50 +0530 Subject: [PATCH 10/21] Integrate fast-run support to the debugger --- .../debugadapter/DebugExecutionManager.java | 61 ++++++++++++++----- .../debugadapter/JBallerinaDebugServer.java | 31 +++++++--- .../config/ClientConfigHolder.java | 19 ++++++ .../debugadapter/utils/ServerUtils.java | 59 +++++++++++++++++- 4 files changed, 144 insertions(+), 26 deletions(-) diff --git a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/DebugExecutionManager.java b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/DebugExecutionManager.java index c918e5fd186c..911a77b1b39a 100755 --- a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/DebugExecutionManager.java +++ b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/DebugExecutionManager.java @@ -27,6 +27,7 @@ import java.io.IOException; import java.util.Map; +import java.util.Objects; import java.util.Optional; /** @@ -72,27 +73,59 @@ public String getRemoteVMAddress() { * Attaches to an existing JVM using an SocketAttachingConnector and returns the attached VM instance. */ public VirtualMachine attach(String hostName, int port) throws IOException, IllegalConnectorArgumentsException { - AttachingConnector socketAttachingConnector = Bootstrap.virtualMachineManager().attachingConnectors().stream() + // Default retry configuration + return attach(hostName, port, 50, 100); // 100 attempts, 50ms between attempts + } + + public VirtualMachine attach(String hostName, int port, long retryIntervalMs, int maxAttempts) + throws IOException, IllegalConnectorArgumentsException { + + AttachingConnector attachingConnector = Bootstrap.virtualMachineManager().attachingConnectors().stream() .filter(ac -> ac.name().equals(SOCKET_CONNECTOR_NAME)) .findFirst() .orElseThrow(() -> new RuntimeException("Unable to locate SocketAttachingConnector")); - Map connectorArgs = socketAttachingConnector.defaultArguments(); - if (!hostName.isEmpty()) { - connectorArgs.get(CONNECTOR_ARGS_HOST).setValue(hostName); - } + hostName = Objects.isNull(hostName) || hostName.isBlank() ? LOCAL_HOST : hostName; + Map connectorArgs = attachingConnector.defaultArguments(); + connectorArgs.get(CONNECTOR_ARGS_HOST).setValue(hostName); connectorArgs.get(CONNECTOR_ARGS_PORT).setValue(String.valueOf(port)); - LOGGER.info(String.format("Debugger is attaching to: %s:%d", hostName, port)); - attachedVm = socketAttachingConnector.attach(connectorArgs); - this.host = !hostName.isEmpty() ? hostName : LOCAL_HOST; - this.port = port; + return attachWithRetries(attachingConnector, connectorArgs, retryIntervalMs, maxAttempts); + } + + private VirtualMachine attachWithRetries(AttachingConnector connector, Map args, + long retryIntervalMs, int maxAttempts) + throws IOException, IllegalConnectorArgumentsException { + + for (int attempt = 1; attempt <= maxAttempts; attempt++) { + try { + attachedVm = connector.attach(args); + this.host = args.get(CONNECTOR_ARGS_HOST).value(); + this.port = Integer.parseInt(args.get(CONNECTOR_ARGS_PORT).value()); + if (server.getClientConfigHolder().getKind() == ClientConfigHolder.ClientConfigKind.ATTACH_CONFIG) { + server.getOutputLogger().sendDebugServerOutput( + String.format("Connected to the target VM, address: '%s:%s'", host, port) + ); + } - // Todo - enable for launch-mode after implementing debug server client logger - if (server.getClientConfigHolder().getKind() == ClientConfigHolder.ClientConfigKind.ATTACH_CONFIG) { - server.getOutputLogger().sendDebugServerOutput((String.format("Connected to the target VM, address: " + - "'%s:%s'", host, port))); + return attachedVm; + } catch (IOException e) { + LOGGER.debug(String.format("Attachment attempt %d/%d failed: %s", attempt, maxAttempts, e.getMessage())); + try { + Thread.sleep(retryIntervalMs); + } catch (InterruptedException ie) { + Thread.currentThread().interrupt(); + throw new IOException("Attachment interrupted", e); + } + } } - return attachedVm; + + throw new IOException("Failed to attach to the target VM."); + } + + public void reset() { + attachedVm = null; + host = null; + port = null; } } diff --git a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/JBallerinaDebugServer.java b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/JBallerinaDebugServer.java index 5f2503c3e3e1..a1f1332ed017 100755 --- a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/JBallerinaDebugServer.java +++ b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/JBallerinaDebugServer.java @@ -137,6 +137,7 @@ import static org.ballerinalang.debugadapter.utils.PackageUtils.getQualifiedClassName; import static org.ballerinalang.debugadapter.utils.ServerUtils.isBalStackFrame; import static org.ballerinalang.debugadapter.utils.ServerUtils.isBalStrand; +import static org.ballerinalang.debugadapter.utils.ServerUtils.isFastRunEnabled; import static org.ballerinalang.debugadapter.utils.ServerUtils.isNoDebugMode; import static org.ballerinalang.debugadapter.utils.ServerUtils.toBalBreakpoint; @@ -471,15 +472,24 @@ private void launchDebuggeeProgram() throws Exception { Project sourceProject = context.getProjectCache().getProject(Path.of(clientConfigHolder.getSourcePath())); context.setSourceProject(sourceProject); String sourceProjectRoot = context.getSourceProjectRoot(); - BProgramRunner programRunner = context.getSourceProject() instanceof SingleFileProject ? - new BSingleFileRunner((ClientLaunchConfigHolder) clientConfigHolder, sourceProjectRoot) : - new BPackageRunner((ClientLaunchConfigHolder) clientConfigHolder, sourceProjectRoot); - - if (context.getSupportsRunInTerminalRequest() && clientConfigHolder.getRunInTerminalKind() != null) { - launchInTerminal(programRunner); + // if the debug server runs in fast-run mode, send a notification to the client to re-run the remote program and + // re-attach to the new VM. + if (isFastRunEnabled(context)) { + int port = ServerUtils.findFreePort(); + ServerUtils.sendFastRunNotification(context, port); + attachToRemoteVM(LOCAL_HOST, port); } else { - context.setLaunchedProcess(programRunner.start()); - startListeningToProgramOutput(); + BProgramRunner programRunner = context.getSourceProject() instanceof SingleFileProject ? + new BSingleFileRunner((ClientLaunchConfigHolder) clientConfigHolder, sourceProjectRoot) : + new BPackageRunner((ClientLaunchConfigHolder) clientConfigHolder, sourceProjectRoot); + + if (context.getSupportsRunInTerminalRequest() && clientConfigHolder.getRunInTerminalKind() != null) { + launchInTerminal(programRunner); + } else { + Process debuggeeProcess = programRunner.start(); + context.setLaunchedProcess(debuggeeProcess); + startListeningToProgramOutput(); + } } } @@ -615,7 +625,7 @@ public CompletableFuture runInTerminal(RunInTerminalReque while (context.getDebuggeeVM() == null && tryCounter < 10) { try { JDIUtils.sleepMillis(3000); - attachToRemoteVM("", clientConfigHolder.getDebuggePort()); + attachToRemoteVM(LOCAL_HOST, clientConfigHolder.getDebuggePort()); } catch (IOException ignored) { tryCounter++; } catch (IllegalConnectorArgumentsException | ClientConfigurationException e) { @@ -876,7 +886,7 @@ private void startListeningToProgramOutput() { + System.lineSeparator()); while ((line = inputStream.readLine()) != null) { if (line.contains("Listening for transport dt_socket")) { - attachToRemoteVM("", clientConfigHolder.getDebuggePort()); + attachToRemoteVM(LOCAL_HOST, clientConfigHolder.getDebuggePort()); } else if (context.getDebuggeeVM() == null && line.contains(COMPILATION_ERROR_MESSAGE)) { terminateDebugSession(false, true); } @@ -1188,6 +1198,7 @@ private void clearSuspendedState() { private void resetServer() { Optional.ofNullable(eventProcessor).ifPresent(JDIEventProcessor::reset); Optional.ofNullable(outputLogger).ifPresent(DebugOutputLogger::reset); + Optional.ofNullable(executionManager).ifPresent(DebugExecutionManager::reset); terminateDebuggee(); clearSuspendedState(); context.reset(); diff --git a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/config/ClientConfigHolder.java b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/config/ClientConfigHolder.java index c2d294585858..73b118cc7861 100644 --- a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/config/ClientConfigHolder.java +++ b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/config/ClientConfigHolder.java @@ -41,6 +41,7 @@ public class ClientConfigHolder { private static final String ARG_CAPABILITIES = "capabilities"; private static final String ARG_SUPPORT_READONLY_EDITOR = "supportsReadOnlyEditors"; private static final String ARG_SUPPORT_BP_VERIFICATION = "supportsBreakpointVerification"; + private static final String ARG_SUPPORT_FAST_RUN = "supportsFastRun"; private static final String ARG_TERMINAL_KIND = "terminal"; private static final String INTEGRATED_TERMINAL_KIND = "INTEGRATED"; private static final String EXTERNAL_TERMINAL_KIND = "EXTERNAL"; @@ -98,6 +99,15 @@ public Optional getExtendedCapabilities() { extendedClientCapabilities.setSupportsBreakpointVerification(false); } + Object fastRunConfig = capabilities.get(ARG_SUPPORT_FAST_RUN); + if (fastRunConfig instanceof Boolean b) { + extendedClientCapabilities.setSupportsFastRun(b); + } else if (fastRunConfig instanceof String s) { + extendedClientCapabilities.setSupportsFastRun(Boolean.parseBoolean(s)); + } else { + extendedClientCapabilities.setSupportsFastRun(false); + } + return Optional.ofNullable(extendedClientCapabilities); } @@ -133,6 +143,7 @@ public static class ExtendedClientCapabilities { private boolean supportsReadOnlyEditors = false; private boolean supportsBreakpointVerification = false; + private boolean supportsFastRun = false; public boolean supportsReadOnlyEditors() { return supportsReadOnlyEditors; @@ -149,5 +160,13 @@ public boolean supportsBreakpointVerification() { public void setSupportsBreakpointVerification(boolean supportsBreakpointVerification) { this.supportsBreakpointVerification = supportsBreakpointVerification; } + + public boolean supportsFastRun() { + return supportsFastRun; + } + + public void setSupportsFastRun(boolean supportsFastRun) { + this.supportsFastRun = supportsFastRun; + } } } diff --git a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/utils/ServerUtils.java b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/utils/ServerUtils.java index 6efda07cec62..12ea2ab76add 100644 --- a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/utils/ServerUtils.java +++ b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/utils/ServerUtils.java @@ -23,8 +23,12 @@ import org.eclipse.lsp4j.debug.Source; import org.eclipse.lsp4j.debug.SourceBreakpoint; import org.eclipse.lsp4j.debug.StackFrame; +import org.eclipse.lsp4j.jsonrpc.Endpoint; +import org.eclipse.lsp4j.jsonrpc.services.GenericEndpoint; +import java.net.ServerSocket; import java.util.Objects; +import java.util.Optional; import static org.ballerinalang.debugadapter.utils.PackageUtils.BAL_FILE_EXT; @@ -35,6 +39,8 @@ */ public class ServerUtils { + private static final String START_FAST_RUN = "startFastRun"; + /** * Checks whether the debug server should run in no-debug mode. * @@ -42,8 +48,24 @@ public class ServerUtils { * @return true if the debug mode is no-debug mode */ public static boolean isNoDebugMode(ExecutionContext context) { - ClientConfigHolder confHolder = context.getAdapter().getClientConfigHolder(); - return confHolder instanceof ClientLaunchConfigHolder launchConfigHolder && launchConfigHolder.isNoDebugMode(); + try { + ClientConfigHolder confHolder = context.getAdapter().getClientConfigHolder(); + return confHolder instanceof ClientLaunchConfigHolder launchConfHolder && launchConfHolder.isNoDebugMode(); + } catch (Exception e) { + return false; + } + } + + public static boolean isFastRunEnabled(ExecutionContext context) { + try { + Optional extendedCapabilities = + context.getAdapter().getClientConfigHolder().getExtendedCapabilities(); + return extendedCapabilities + .map(ClientConfigHolder.ExtendedClientCapabilities::supportsFastRun) + .orElse(false); + } catch (Exception e) { + return false; + } } /** @@ -119,4 +141,37 @@ public static boolean supportsBreakpointVerification(ExecutionContext context) { .map(ClientConfigHolder.ExtendedClientCapabilities::supportsBreakpointVerification) .orElse(false); } + + /** + * Sends a custom notification to the debug client to trigger a fast-run. + * + * @param context debug context + * @param port port number + */ + public static void sendFastRunNotification(ExecutionContext context, int port) { + Endpoint endPoint = new GenericEndpoint(context.getClient()); + FastRunArguments arguments = new FastRunArguments(port); + endPoint.notify(START_FAST_RUN, arguments); + } + + /** + * Finds an available port. + * + * @return available port number. + */ + public static int findFreePort() { + try (ServerSocket socket = new ServerSocket(0)) { + socket.setReuseAddress(true); + return socket.getLocalPort(); + } catch (Exception e) { + throw new IllegalStateException("Could not find a free TCP/IP port to start debugging", e); + } + } + + /** + * Represents the arguments of the 'restarted' notification. + */ + public record FastRunArguments(int port) { + + } } From 22b94f516d588d94139c7cefff33911b2d5e767a Mon Sep 17 00:00:00 2001 From: Nipuna Ranasinghe Date: Wed, 11 Dec 2024 22:51:34 +0530 Subject: [PATCH 11/21] Fix showing debuggee program output in the fast-run mode --- .../BallerinaExtendedDebugServer.java | 42 +++++++++++++++++++ .../debugadapter/DebugOutputLogger.java | 2 +- .../debugadapter/JBallerinaDebugServer.java | 23 ++++++++-- 3 files changed, 62 insertions(+), 5 deletions(-) create mode 100644 misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/BallerinaExtendedDebugServer.java diff --git a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/BallerinaExtendedDebugServer.java b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/BallerinaExtendedDebugServer.java new file mode 100644 index 000000000000..9e2565372e40 --- /dev/null +++ b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/BallerinaExtendedDebugServer.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://wso2.com). + * + * 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 + * + * 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.ballerinalang.debugadapter; + +import org.eclipse.lsp4j.debug.OutputEventArguments; +import org.eclipse.lsp4j.debug.services.IDebugProtocolServer; +import org.eclipse.lsp4j.jsonrpc.services.JsonRequest; + +import java.util.concurrent.CompletableFuture; + +/** + * Extended debug server interface for Ballerina debug adapter. + * + * @since 2201.11.0 + */ +public interface BallerinaExtendedDebugServer extends IDebugProtocolServer { + + /** + * Custom request to send the program output from the remote VM (initiated by the debug client) to the debug server. + * This extension is specifically used in the fast-run mode to forward the program output to the debug adapter, + * to be sent to the client. + * + * @param arguments output event arguments + * @return completable future + */ + @JsonRequest + CompletableFuture output(OutputEventArguments arguments); +} diff --git a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/DebugOutputLogger.java b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/DebugOutputLogger.java index 45c9345f269e..d8ebaea4c45d 100644 --- a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/DebugOutputLogger.java +++ b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/DebugOutputLogger.java @@ -87,7 +87,7 @@ public void sendConsoleOutput(String output) { } /** - * Processes messages which are generated by the debug server itself and should treated as console outputs. + * Processes messages which are generated by the debug server itself and should be treated as console outputs. * * @param output string output produced in the debugger/target VM. */ diff --git a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/JBallerinaDebugServer.java b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/JBallerinaDebugServer.java index a1f1332ed017..ea7bca279d83 100755 --- a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/JBallerinaDebugServer.java +++ b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/JBallerinaDebugServer.java @@ -74,6 +74,8 @@ import org.eclipse.lsp4j.debug.ExitedEventArguments; import org.eclipse.lsp4j.debug.InitializeRequestArguments; import org.eclipse.lsp4j.debug.NextArguments; +import org.eclipse.lsp4j.debug.OutputEventArguments; +import org.eclipse.lsp4j.debug.OutputEventArgumentsCategory; import org.eclipse.lsp4j.debug.PauseArguments; import org.eclipse.lsp4j.debug.RestartArguments; import org.eclipse.lsp4j.debug.RunInTerminalRequestArguments; @@ -100,10 +102,10 @@ import org.eclipse.lsp4j.debug.VariablesArguments; import org.eclipse.lsp4j.debug.VariablesResponse; import org.eclipse.lsp4j.debug.services.IDebugProtocolClient; -import org.eclipse.lsp4j.debug.services.IDebugProtocolServer; import org.eclipse.lsp4j.jsonrpc.Endpoint; import org.eclipse.lsp4j.jsonrpc.messages.Either; import org.eclipse.lsp4j.jsonrpc.services.GenericEndpoint; +import org.eclipse.lsp4j.jsonrpc.services.JsonRequest; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -144,7 +146,7 @@ /** * JBallerina debug server implementation. */ -public class JBallerinaDebugServer implements IDebugProtocolServer { +public class JBallerinaDebugServer implements BallerinaExtendedDebugServer { private IDebugProtocolClient client; private ClientConfigHolder clientConfigHolder; @@ -476,6 +478,8 @@ private void launchDebuggeeProgram() throws Exception { // re-attach to the new VM. if (isFastRunEnabled(context)) { int port = ServerUtils.findFreePort(); + outputLogger.sendDebugServerOutput("Waiting for the debug process to start...%s%s" + .formatted(System.lineSeparator(), System.lineSeparator())); ServerUtils.sendFastRunNotification(context, port); attachToRemoteVM(LOCAL_HOST, port); } else { @@ -615,6 +619,17 @@ public CompletableFuture terminate(TerminateArguments args) { return CompletableFuture.completedFuture(null); } + @JsonRequest + public CompletableFuture output(OutputEventArguments arguments) { + switch (arguments.getCategory()) { + case OutputEventArgumentsCategory.STDOUT -> outputLogger.sendProgramOutput(arguments.getOutput()); + case OutputEventArgumentsCategory.STDERR -> outputLogger.sendErrorOutput(arguments.getOutput()); + default -> { + } + } + return CompletableFuture.completedFuture(null); + } + @Override public CompletableFuture runInTerminal(RunInTerminalRequestArguments args) { Endpoint endPoint = new GenericEndpoint(context.getClient()); @@ -882,8 +897,8 @@ private void startListeningToProgramOutput() { try (BufferedReader inputStream = context.getInputStream()) { String line; - outputLogger.sendDebugServerOutput("Waiting for debug process to start..." + System.lineSeparator() - + System.lineSeparator()); + outputLogger.sendDebugServerOutput("Waiting for the debug process to start...%s%s" + .formatted(System.lineSeparator(), System.lineSeparator())); while ((line = inputStream.readLine()) != null) { if (line.contains("Listening for transport dt_socket")) { attachToRemoteVM(LOCAL_HOST, clientConfigHolder.getDebuggePort()); From ebfc4bbb7344daf979ce33384d9a78d4e8f5a4ff Mon Sep 17 00:00:00 2001 From: Nipuna Ranasinghe Date: Wed, 11 Dec 2024 23:26:47 +0530 Subject: [PATCH 12/21] Add minor fixes --- .../commons/workspace/RunContext.java | 2 +- .../command/executors/RunExecutor.java | 8 ++++---- .../workspace/BallerinaWorkspaceManager.java | 1 + .../BallerinaExtendedDebugServer.java | 10 +++++----- .../debugadapter/DebugExecutionManager.java | 7 +++---- .../debugadapter/utils/ServerUtils.java | 18 +++++++++++++----- 6 files changed, 27 insertions(+), 19 deletions(-) diff --git a/language-server/modules/langserver-commons/src/main/java/org/ballerinalang/langserver/commons/workspace/RunContext.java b/language-server/modules/langserver-commons/src/main/java/org/ballerinalang/langserver/commons/workspace/RunContext.java index af831a5cb2c3..8e929d58b5f7 100644 --- a/language-server/modules/langserver-commons/src/main/java/org/ballerinalang/langserver/commons/workspace/RunContext.java +++ b/language-server/modules/langserver-commons/src/main/java/org/ballerinalang/langserver/commons/workspace/RunContext.java @@ -63,4 +63,4 @@ public RunContext build() { return new RunContext(sourcePath, programArgs, env, debugPort); } } -} \ No newline at end of file +} diff --git a/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/command/executors/RunExecutor.java b/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/command/executors/RunExecutor.java index be04d327edf9..fcc24d3a2d4d 100644 --- a/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/command/executors/RunExecutor.java +++ b/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/command/executors/RunExecutor.java @@ -49,13 +49,13 @@ public Boolean execute(ExecuteCommandContext context) throws LSCommandExecutorEx RunContext.Builder builder = new RunContext.Builder(extractPath(context)); builder.withProgramArgs(extractProgramArgs(context)); int debugPort = extractDebugArgs(context); - if (debugPort > 0) { + if (debugPort >= 0) { builder.withDebugPort(debugPort); } - // TODO handle env vars + // TODO: handle env vars - RunContext RunContext = builder.build(); - Optional processOpt = context.workspace().run(RunContext); + RunContext runContext = builder.build(); + Optional processOpt = context.workspace().run(runContext); if (processOpt.isEmpty()) { return false; } diff --git a/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/workspace/BallerinaWorkspaceManager.java b/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/workspace/BallerinaWorkspaceManager.java index 9ca9223d71e0..cd0073c040a5 100644 --- a/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/workspace/BallerinaWorkspaceManager.java +++ b/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/workspace/BallerinaWorkspaceManager.java @@ -102,6 +102,7 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; + import javax.annotation.Nonnull; import javax.annotation.Nullable; diff --git a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/BallerinaExtendedDebugServer.java b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/BallerinaExtendedDebugServer.java index 9e2565372e40..e44f512d42cd 100644 --- a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/BallerinaExtendedDebugServer.java +++ b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/BallerinaExtendedDebugServer.java @@ -30,12 +30,12 @@ public interface BallerinaExtendedDebugServer extends IDebugProtocolServer { /** - * Custom request to send the program output from the remote VM (initiated by the debug client) to the debug server. - * This extension is specifically used in the fast-run mode to forward the program output to the debug adapter, - * to be sent to the client. + * Custom request to forward the program output from the remote VM (initiated by the debug client) to the debug + * server. This extension is specifically designed for fast-run mode, enabling the debug adapter to forward the + * program output to the client. * - * @param arguments output event arguments - * @return completable future + * @param arguments the output event arguments + * @return a CompletableFuture representing the completion of the request */ @JsonRequest CompletableFuture output(OutputEventArguments arguments); diff --git a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/DebugExecutionManager.java b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/DebugExecutionManager.java index 911a77b1b39a..f741ca9b754a 100755 --- a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/DebugExecutionManager.java +++ b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/DebugExecutionManager.java @@ -110,12 +110,11 @@ private VirtualMachine attachWithRetries(AttachingConnector connector, Map extendedCapabilities = @@ -150,8 +156,8 @@ public static boolean supportsBreakpointVerification(ExecutionContext context) { */ public static void sendFastRunNotification(ExecutionContext context, int port) { Endpoint endPoint = new GenericEndpoint(context.getClient()); - FastRunArguments arguments = new FastRunArguments(port); - endPoint.notify(START_FAST_RUN, arguments); + FastRunArgs arguments = new FastRunArgs(port); + endPoint.notify(FAST_RUN_NOTIFICATION_NAME, arguments); } /** @@ -169,9 +175,11 @@ public static int findFreePort() { } /** - * Represents the arguments of the 'restarted' notification. + * Represents the arguments of the 'startFastRun' notification. + * + * @param port port number to start the fast-run */ - public record FastRunArguments(int port) { + public record FastRunArgs(int port) { } } From 4bed6d37a8606ebbf82f2062f8ab1951b65d12cb Mon Sep 17 00:00:00 2001 From: Nipuna Ranasinghe Date: Wed, 11 Dec 2024 23:49:18 +0530 Subject: [PATCH 13/21] Fix RPC method overriding issue --- .../org/ballerinalang/debugadapter/JBallerinaDebugServer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/JBallerinaDebugServer.java b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/JBallerinaDebugServer.java index ea7bca279d83..caff33a2caa1 100755 --- a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/JBallerinaDebugServer.java +++ b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/JBallerinaDebugServer.java @@ -619,7 +619,7 @@ public CompletableFuture terminate(TerminateArguments args) { return CompletableFuture.completedFuture(null); } - @JsonRequest + @Override public CompletableFuture output(OutputEventArguments arguments) { switch (arguments.getCategory()) { case OutputEventArgumentsCategory.STDOUT -> outputLogger.sendProgramOutput(arguments.getOutput()); From b7c72548fdda1abc0446b9b4d55bc40f5b70c69d Mon Sep 17 00:00:00 2001 From: Nipuna Ranasinghe Date: Thu, 12 Dec 2024 00:01:15 +0530 Subject: [PATCH 14/21] Address review suggestion --- .../langserver/commons/workspace/RunContext.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/language-server/modules/langserver-commons/src/main/java/org/ballerinalang/langserver/commons/workspace/RunContext.java b/language-server/modules/langserver-commons/src/main/java/org/ballerinalang/langserver/commons/workspace/RunContext.java index 8e929d58b5f7..b041880bc5ba 100644 --- a/language-server/modules/langserver-commons/src/main/java/org/ballerinalang/langserver/commons/workspace/RunContext.java +++ b/language-server/modules/langserver-commons/src/main/java/org/ballerinalang/langserver/commons/workspace/RunContext.java @@ -38,7 +38,7 @@ public static class Builder { private final Path sourcePath; private List programArgs = new ArrayList<>(); private Map env = Map.of(); - private Integer debugPort = -1; + private int debugPort = -1; public Builder(Path sourcePath) { this.sourcePath = sourcePath; @@ -54,7 +54,7 @@ public Builder withEnv(Map env) { return this; } - public Builder withDebugPort(Integer debugPort) { + public Builder withDebugPort(int debugPort) { this.debugPort = debugPort; return this; } From 3749d06c217d60d730047303bf642edeb3d51ce8 Mon Sep 17 00:00:00 2001 From: Nipuna Ranasinghe Date: Thu, 12 Dec 2024 08:33:18 +0530 Subject: [PATCH 15/21] Fix test failure --- .../ballerinalang/debugger/test/adapter/DebugOutputTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/jballerina-debugger-integration-test/src/test/java/org/ballerinalang/debugger/test/adapter/DebugOutputTest.java b/tests/jballerina-debugger-integration-test/src/test/java/org/ballerinalang/debugger/test/adapter/DebugOutputTest.java index 5cce2c720b67..f98bcf1acbc3 100644 --- a/tests/jballerina-debugger-integration-test/src/test/java/org/ballerinalang/debugger/test/adapter/DebugOutputTest.java +++ b/tests/jballerina-debugger-integration-test/src/test/java/org/ballerinalang/debugger/test/adapter/DebugOutputTest.java @@ -62,7 +62,7 @@ public void testProgramOutput() throws BallerinaTestException { // Validates initial debug outputs which uses `CONSOLE` category. Assert.assertEquals(outputs.get(0).getRight().getCategory(), OutputEventArgumentsCategory.CONSOLE); - Assert.assertEquals(outputs.get(0).getLeft(), "Waiting for debug process to start..." + + Assert.assertEquals(outputs.get(0).getLeft(), "Waiting for the debug process to start..." + System.lineSeparator() + System.lineSeparator()); Assert.assertEquals(outputs.get(1).getRight().getCategory(), OutputEventArgumentsCategory.CONSOLE); From e1da354f79bc065d02d73b40416d4bdc81403f20 Mon Sep 17 00:00:00 2001 From: Nipuna Ranasinghe Date: Fri, 13 Dec 2024 14:51:41 +0530 Subject: [PATCH 16/21] Add support for passing env variables and program args in fast-run mode --- .../debugadapter/JBallerinaDebugServer.java | 72 ++++++++++--------- .../debugadapter/utils/ServerUtils.java | 15 ++-- 2 files changed, 51 insertions(+), 36 deletions(-) diff --git a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/JBallerinaDebugServer.java b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/JBallerinaDebugServer.java index caff33a2caa1..e7181c330561 100755 --- a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/JBallerinaDebugServer.java +++ b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/JBallerinaDebugServer.java @@ -105,7 +105,6 @@ import org.eclipse.lsp4j.jsonrpc.Endpoint; import org.eclipse.lsp4j.jsonrpc.messages.Either; import org.eclipse.lsp4j.jsonrpc.services.GenericEndpoint; -import org.eclipse.lsp4j.jsonrpc.services.JsonRequest; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -122,6 +121,7 @@ import java.util.Objects; import java.util.Optional; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; @@ -271,43 +271,39 @@ public CompletableFuture configurationDone(ConfigurationDoneArguments args @Override public CompletableFuture launch(Map args) { - try { - clientConfigHolder = new ClientLaunchConfigHolder(args); - launchDebuggeeProgram(); - return CompletableFuture.completedFuture(null); - } catch (Exception e) { - outputLogger.sendErrorOutput("Failed to launch the Ballerina program due to: " + e.getMessage()); - return CompletableFuture.failedFuture(e); - } + return CompletableFuture.supplyAsync(() -> { + try { + clientConfigHolder = new ClientLaunchConfigHolder(args); + launchDebuggeeProgram(); + return null; + } catch (Exception e) { + outputLogger.sendErrorOutput("Failed to launch the Ballerina program due to: " + e.getMessage()); + throw new CompletionException(e); + } + }); } @Override public CompletableFuture attach(Map args) { - try { - clientConfigHolder = new ClientAttachConfigHolder(args); - context.setDebugMode(ExecutionContext.DebugMode.ATTACH); - Project sourceProject = context.getProjectCache().getProject(Path.of(clientConfigHolder.getSourcePath())); - context.setSourceProject(sourceProject); - ClientAttachConfigHolder configHolder = (ClientAttachConfigHolder) clientConfigHolder; - - String hostName = configHolder.getHostName().orElse(""); - int portName = configHolder.getDebuggePort(); - attachToRemoteVM(hostName, portName); - return CompletableFuture.completedFuture(null); - } catch (Exception e) { - String host = ((ClientAttachConfigHolder) clientConfigHolder).getHostName().orElse(LOCAL_HOST); - String portName; + return CompletableFuture.supplyAsync(() -> { try { - portName = Integer.toString(clientConfigHolder.getDebuggePort()); - } catch (ClientConfigurationException clientConfigurationException) { - portName = VALUE_UNKNOWN; + clientConfigHolder = new ClientAttachConfigHolder(args); + context.setDebugMode(ExecutionContext.DebugMode.ATTACH); + Project sourceProject = context.getProjectCache().getProject(Path.of(clientConfigHolder.getSourcePath())); + context.setSourceProject(sourceProject); + ClientAttachConfigHolder configHolder = (ClientAttachConfigHolder) clientConfigHolder; + + String hostName = configHolder.getHostName().orElse(""); + int portName = configHolder.getDebuggePort(); + attachToRemoteVM(hostName, portName); + return null; + } catch (Exception e) { + String errorMessage = getAttachmentErrorMessage(e); + outputLogger.sendErrorOutput(errorMessage); + terminateDebugSession(context.getDebuggeeVM() != null, false); + throw new CompletionException(e); } - LOGGER.error(e.getMessage()); - outputLogger.sendErrorOutput(String.format("Failed to attach to the target VM address: '%s:%s' due to: %s", - host, portName, e.getMessage())); - terminateDebugSession(context.getDebuggeeVM() != null, false); - return CompletableFuture.failedFuture(e); - } + }); } @Override @@ -1192,6 +1188,18 @@ private EvaluateResponse constructEvaluateResponse(EvaluateArguments args, BVari return response; } + private String getAttachmentErrorMessage(Exception e) { + String host = ((ClientAttachConfigHolder) clientConfigHolder).getHostName().orElse(LOCAL_HOST); + String portName; + try { + portName = Integer.toString(clientConfigHolder.getDebuggePort()); + } catch (ClientConfigurationException clientConfigurationException) { + portName = VALUE_UNKNOWN; + } + return String.format("Failed to attach to the target VM address: '%s:%s' due to: %s", + host, portName, e.getMessage()); + } + /** * Clears the suspended state information. */ diff --git a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/utils/ServerUtils.java b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/utils/ServerUtils.java index b44487f9b85a..f3634ce9c8a0 100644 --- a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/utils/ServerUtils.java +++ b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/utils/ServerUtils.java @@ -27,6 +27,7 @@ import org.eclipse.lsp4j.jsonrpc.services.GenericEndpoint; import java.net.ServerSocket; +import java.util.Map; import java.util.Objects; import java.util.Optional; @@ -156,8 +157,12 @@ public static boolean supportsBreakpointVerification(ExecutionContext context) { */ public static void sendFastRunNotification(ExecutionContext context, int port) { Endpoint endPoint = new GenericEndpoint(context.getClient()); - FastRunArgs arguments = new FastRunArgs(port); - endPoint.notify(FAST_RUN_NOTIFICATION_NAME, arguments); + ClientConfigHolder configs = context.getAdapter().getClientConfigHolder(); + Map envVarMap = ((ClientLaunchConfigHolder) configs).getEnv().orElse(Map.of()); + String[] programArgs = ((ClientLaunchConfigHolder) configs).getProgramArgs().toArray(new String[0]); + + FastRunArgs args = new FastRunArgs(port, envVarMap, programArgs); + endPoint.notify(FAST_RUN_NOTIFICATION_NAME, args); } /** @@ -177,9 +182,11 @@ public static int findFreePort() { /** * Represents the arguments of the 'startFastRun' notification. * - * @param port port number to start the fast-run + * @param debugPort debug port number + * @param env environment variables + * @param programArgs program arguments */ - public record FastRunArgs(int port) { + public record FastRunArgs(int debugPort, Map env, String[] programArgs) { } } From 4fdec3f61173852c082b1f7a2d194ff8823a1751 Mon Sep 17 00:00:00 2001 From: Nipuna Ranasinghe Date: Fri, 13 Dec 2024 14:57:58 +0530 Subject: [PATCH 17/21] Improve fast-run LS command to support user env variables --- .../command/executors/RunExecutor.java | 121 +++++++++++++---- .../workspace/BallerinaWorkspaceManager.java | 124 ++++++++++++------ 2 files changed, 175 insertions(+), 70 deletions(-) diff --git a/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/command/executors/RunExecutor.java b/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/command/executors/RunExecutor.java index fcc24d3a2d4d..00ff5b017bc3 100644 --- a/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/command/executors/RunExecutor.java +++ b/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/command/executors/RunExecutor.java @@ -16,10 +16,13 @@ package org.ballerinalang.langserver.command.executors; import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; import com.google.gson.JsonPrimitive; import org.ballerinalang.annotation.JavaSPIService; import org.ballerinalang.langserver.commons.ExecuteCommandContext; import org.ballerinalang.langserver.commons.client.ExtendedLanguageClient; +import org.ballerinalang.langserver.commons.command.CommandArgument; import org.ballerinalang.langserver.commons.command.LSCommandExecutorException; import org.ballerinalang.langserver.commons.command.spi.LSCommandExecutor; import org.ballerinalang.langserver.commons.workspace.RunContext; @@ -28,11 +31,17 @@ import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.InvalidPathException; import java.nio.file.Path; -import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.function.Supplier; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; /** * Command executor for running a Ballerina file. Each project at most has a single instance running at a time. @@ -43,52 +52,108 @@ @JavaSPIService("org.ballerinalang.langserver.commons.command.spi.LSCommandExecutor") public class RunExecutor implements LSCommandExecutor { + private static final String RUN_COMMAND = "RUN"; + + // commands arg names + private static final String ARG_PATH = "path"; + private static final String ARG_PROGRAM_ARGS = "programArgs"; + private static final String ARG_ENV = "env"; + private static final String ARG_DEBUG_PORT = "debugPort"; + + // output channels + private static final String ERROR_CHANNEL = "err"; + private static final String OUT_CHANNEL = "out"; + @Override public Boolean execute(ExecuteCommandContext context) throws LSCommandExecutorException { try { - RunContext.Builder builder = new RunContext.Builder(extractPath(context)); - builder.withProgramArgs(extractProgramArgs(context)); - int debugPort = extractDebugArgs(context); - if (debugPort >= 0) { - builder.withDebugPort(debugPort); - } - // TODO: handle env vars - - RunContext runContext = builder.build(); - Optional processOpt = context.workspace().run(runContext); + RunContext workspaceRunContext = getWorkspaceRunContext(context); + Optional processOpt = context.workspace().run(workspaceRunContext); if (processOpt.isEmpty()) { return false; } Process process = processOpt.get(); - listenOutputAsync(context.getLanguageClient(), process::getInputStream, "out"); - listenOutputAsync(context.getLanguageClient(), process::getErrorStream, "err"); + listenOutputAsync(context.getLanguageClient(), process::getInputStream, OUT_CHANNEL); + listenOutputAsync(context.getLanguageClient(), process::getErrorStream, ERROR_CHANNEL); return true; } catch (IOException e) { + LogTraceParams error = new LogTraceParams("Error while running the program in fast-run mode: " + + e.getMessage(), ERROR_CHANNEL); + context.getLanguageClient().logTrace(error); + throw new LSCommandExecutorException(e); + } catch (Exception e) { + LogTraceParams error = new LogTraceParams("Unexpected error while executing the fast-run: " + + e.getMessage(), ERROR_CHANNEL); + context.getLanguageClient().logTrace(error); throw new LSCommandExecutorException(e); } } - private static Path extractPath(ExecuteCommandContext context) { - return Path.of(context.getArguments().getFirst().value().getAsString()); + private RunContext getWorkspaceRunContext(ExecuteCommandContext context) { + RunContext.Builder builder = new RunContext.Builder(extractPath(context)); + builder.withProgramArgs(extractProgramArgs(context)); + builder.withEnv(extractEnvVariables(context)); + builder.withDebugPort(extractDebugArgs(context)); + + return builder.build(); + } + + private Path extractPath(ExecuteCommandContext context) { + return getCommandArgWithName(context, ARG_PATH) + .map(CommandArgument::value) + .map(JsonPrimitive::getAsString) + .map(pathStr -> { + try { + Path path = Path.of(pathStr); + if (!Files.exists(path)) { + throw new IllegalArgumentException("Specified path does not exist: " + pathStr); + } + return path; + } catch (InvalidPathException e) { + throw new IllegalArgumentException("Invalid path: " + pathStr, e); + } + }) + .orElseThrow(() -> new IllegalArgumentException("Path argument is required")); } private int extractDebugArgs(ExecuteCommandContext context) { - return context.getArguments().stream() - .filter(commandArg -> commandArg.key().equals("debugPort")) - .map(commandArg -> commandArg.value().getAsInt()) - .findAny() + return getCommandArgWithName(context, ARG_DEBUG_PORT) + .map(CommandArgument::value) + .map(JsonPrimitive::getAsInt) .orElse(-1); } - private static List extractProgramArgs(ExecuteCommandContext context) { - List args = new ArrayList<>(); - if (context.getArguments().size() <= 2) { - return args; - } - context.getArguments().get(2).value().getAsJsonArray().iterator() - .forEachRemaining(arg -> args.add(arg.getAsString())); + private List extractProgramArgs(ExecuteCommandContext context) { + return getCommandArgWithName(context, ARG_PROGRAM_ARGS) + .map(arg -> arg.value().getAsJsonArray()) + .map(jsonArray -> StreamSupport.stream(jsonArray.spliterator(), false) + .filter(JsonElement::isJsonPrimitive) + .map(JsonElement::getAsJsonPrimitive) + .filter(JsonPrimitive::isString) + .map(JsonPrimitive::getAsString) + .collect(Collectors.toList())) + .orElse(Collections.emptyList()); + } - return args; + private Map extractEnvVariables(ExecuteCommandContext context) { + return getCommandArgWithName(context, ARG_ENV) + .map(CommandArgument::value) + .map(jsonObject -> { + Map envMap = new HashMap<>(); + for (Map.Entry entry : jsonObject.entrySet()) { + if (entry.getValue().isJsonPrimitive() && entry.getValue().getAsJsonPrimitive().isString()) { + envMap.put(entry.getKey(), entry.getValue().getAsString()); + } + } + return Collections.unmodifiableMap(envMap); + }) + .orElse(Map.of()); + } + + private static Optional getCommandArgWithName(ExecuteCommandContext context, String name) { + return context.getArguments().stream() + .filter(commandArg -> commandArg.key().equals(name)) + .findAny(); } public void listenOutputAsync(ExtendedLanguageClient client, Supplier getInputStream, String channel) { @@ -111,6 +176,6 @@ private static void listenOutput(ExtendedLanguageClient client, Supplier run(RunContext context) throws IOException { - Path projectRoot = projectRoot(context.balSourcePath()); - Optional projectPairOpt = projectContext(projectRoot); - if (projectPairOpt.isEmpty()) { - String msg = "Run command execution aborted because project is not loaded"; - UserErrorException e = new UserErrorException(msg); - clientLogger.logError(LSContextOperation.WS_EXEC_CMD, msg, e, null, (Position) null); + public Optional run(RunContext executionContext) throws IOException { + Path projectRoot = projectRoot(executionContext.balSourcePath()); + Optional projectContext = validateProjectContext(projectRoot); + if (projectContext.isEmpty()) { return Optional.empty(); } - ProjectContext projectContext = projectPairOpt.get(); - if (!stopProject(projectContext)) { - String msg = "Run command execution aborted because couldn't stop the previous run"; - UserErrorException e = new UserErrorException(msg); - clientLogger.logError(LSContextOperation.WS_EXEC_CMD, msg, e, null, (Position) null); + + if (!prepareProjectForExecution(projectContext.get())) { return Optional.empty(); } + return executeProject(projectContext.get(), executionContext); + } + + private Optional validateProjectContext(Path projectRoot) { + Optional projectContextOpt = projectContext(projectRoot); + if (projectContextOpt.isEmpty()) { + logError("Run command execution aborted because project is not loaded"); + return Optional.empty(); + } + + return projectContextOpt; + } + + private boolean prepareProjectForExecution(ProjectContext projectContext) { + // stop previous project run + if (!stopProject(projectContext)) { + logError("Run command execution aborted because couldn't stop the previous run"); + return false; + } + Project project = projectContext.project(); - Package pkg = project.currentPackage(); - Module executableModule = pkg.getDefaultModule(); Optional packageCompilation = waitAndGetPackageCompilation(project.sourceRoot(), true); if (packageCompilation.isEmpty()) { - return Optional.empty(); + logError("Run command execution aborted because package compilation failed"); + return false; } + + // check for compilation errors JBallerinaBackend jBallerinaBackend = execBackend(projectContext, packageCompilation.get()); Collection diagnostics = jBallerinaBackend.diagnosticResult().diagnostics(false); if (diagnostics.stream().anyMatch(BallerinaWorkspaceManager::isError)) { - String msg = "Run command execution aborted due to compilation errors: " + diagnostics; - UserErrorException e = new UserErrorException(msg); - clientLogger.logError(LSContextOperation.WS_EXEC_CMD, msg, e, null, (Position) null); - return Optional.empty(); + logError("Run command execution aborted due to compilation errors: " + diagnostics); + return false; } + + return true; + } + + private Optional executeProject(ProjectContext projectContext, RunContext context) throws IOException { + Project project = projectContext.project(); + Package pkg = project.currentPackage(); + Module executableModule = pkg.getDefaultModule(); + JBallerinaBackend jBallerinaBackend = execBackend(projectContext, pkg.getCompilation()); JarResolver jarResolver = jBallerinaBackend.jarResolver(); - String initClassName = JarResolver.getQualifiedClassName( - executableModule.packageInstance().packageOrg().toString(), - executableModule.packageInstance().packageName().toString(), - executableModule.packageInstance().packageVersion().toString(), - MODULE_INIT_CLASS_NAME); - List commands = new ArrayList<>(); - commands.add(System.getProperty("java.command")); - commands.add("-XX:+HeapDumpOnOutOfMemoryError"); - commands.add("-XX:HeapDumpPath=" + System.getProperty(USER_DIR)); - if (context.debugPort() > 0) { - commands.add("-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=*:" + context.debugPort()); - } - commands.add("-cp"); - commands.add(getAllClassPaths(jarResolver)); - commands.add(initClassName); - commands.addAll(context.programArgs()); + + List commands = prepareExecutionCommands(context, executableModule, jarResolver); ProcessBuilder pb = new ProcessBuilder(commands); + pb.environment().putAll(context.env()); Lock lock = projectContext.lockAndGet(); try { Optional existing = projectContext.process(); if (existing.isPresent()) { - // We just removed this in above `stopProject`. This means there is a parallel command running. - String msg = "Run command execution aborted because another run is in progress"; - UserErrorException e = new UserErrorException(msg); - clientLogger.logError(LSContextOperation.WS_EXEC_CMD, msg, e, null, (Position) null); + logError("Run command execution aborted because another run is in progress"); return Optional.empty(); } + Process ps = pb.start(); projectContext.setProcess(ps); return Optional.of(ps); @@ -658,6 +670,29 @@ public Optional run(RunContext context) throws IOException { } } + private List prepareExecutionCommands(RunContext context, Module executableModule, JarResolver jarResolver) { + List commands = new ArrayList<>(); + commands.add(JAVA_COMMAND); + commands.add(HEAP_DUMP_FLAG); + commands.add(HEAP_DUMP_PATH_FLAG + USER_DIR); + if (context.debugPort() > 0) { + commands.add(DEBUG_SOCKET_CONFIG + context.debugPort()); + } + + commands.add("-cp"); + commands.add(getAllClassPaths(jarResolver)); + + String initClassName = JarResolver.getQualifiedClassName( + executableModule.packageInstance().packageOrg().toString(), + executableModule.packageInstance().packageName().toString(), + executableModule.packageInstance().packageVersion().toString(), + MODULE_INIT_CLASS_NAME + ); + commands.add(initClassName); + commands.addAll(context.programArgs()); + return commands; + } + private static JBallerinaBackend execBackend(ProjectContext projectContext, PackageCompilation packageCompilation) { Lock lock = projectContext.lockAndGet(); @@ -675,6 +710,11 @@ private static JBallerinaBackend execBackend(ProjectContext projectContext, } } + private void logError(String message) { + UserErrorException e = new UserErrorException(message); + clientLogger.logError(LSContextOperation.WS_EXEC_CMD, message, e, null, (Position) null); + } + @Override public boolean stop(Path filePath) { Optional projectPairOpt = projectContext(projectRoot(filePath)); @@ -1348,7 +1388,7 @@ public void didClose(Path filePath, DidCloseTextDocumentParams params) { } } - // ============================================================================================================== // +// ============================================================================================================== // private Path computeProjectRoot(Path path) { return computeProjectKindAndProjectRoot(path).getRight(); From a8aff58aeffa873b4858ff64c851f3e9865c0228 Mon Sep 17 00:00:00 2001 From: Nipuna Ranasinghe Date: Fri, 13 Dec 2024 16:14:18 +0530 Subject: [PATCH 18/21] Fix build failures --- .../workspace/BallerinaWorkspaceManager.java | 13 +++++++------ .../modules/debug-adapter-core/spotbugs-exclude.xml | 11 ++++++++++- .../debugadapter/JBallerinaDebugServer.java | 4 ++-- 3 files changed, 19 insertions(+), 9 deletions(-) diff --git a/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/workspace/BallerinaWorkspaceManager.java b/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/workspace/BallerinaWorkspaceManager.java index d0aee031e275..62f216c92533 100644 --- a/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/workspace/BallerinaWorkspaceManager.java +++ b/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/workspace/BallerinaWorkspaceManager.java @@ -102,6 +102,7 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; + import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -120,7 +121,7 @@ public class BallerinaWorkspaceManager implements WorkspaceManager { private static final String USER_DIR = System.getProperty("user.dir"); private static final String HEAP_DUMP_FLAG = "-XX:+HeapDumpOnOutOfMemoryError"; private static final String HEAP_DUMP_PATH_FLAG = "-XX:HeapDumpPath="; - private static final String DEBUG_SOCKET_CONFIG = "-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=*:"; + private static final String DEBUG_ARGS = "-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=*:"; /** * Cache mapping of document path to source root. @@ -670,22 +671,22 @@ private Optional executeProject(ProjectContext projectContext, RunConte } } - private List prepareExecutionCommands(RunContext context, Module executableModule, JarResolver jarResolver) { + private List prepareExecutionCommands(RunContext context, Module module, JarResolver jarResolver) { List commands = new ArrayList<>(); commands.add(JAVA_COMMAND); commands.add(HEAP_DUMP_FLAG); commands.add(HEAP_DUMP_PATH_FLAG + USER_DIR); if (context.debugPort() > 0) { - commands.add(DEBUG_SOCKET_CONFIG + context.debugPort()); + commands.add(DEBUG_ARGS + context.debugPort()); } commands.add("-cp"); commands.add(getAllClassPaths(jarResolver)); String initClassName = JarResolver.getQualifiedClassName( - executableModule.packageInstance().packageOrg().toString(), - executableModule.packageInstance().packageName().toString(), - executableModule.packageInstance().packageVersion().toString(), + module.packageInstance().packageOrg().toString(), + module.packageInstance().packageName().toString(), + module.packageInstance().packageVersion().toString(), MODULE_INIT_CLASS_NAME ); commands.add(initClassName); diff --git a/misc/debug-adapter/modules/debug-adapter-core/spotbugs-exclude.xml b/misc/debug-adapter/modules/debug-adapter-core/spotbugs-exclude.xml index 7e528799f807..b541c72b23f8 100644 --- a/misc/debug-adapter/modules/debug-adapter-core/spotbugs-exclude.xml +++ b/misc/debug-adapter/modules/debug-adapter-core/spotbugs-exclude.xml @@ -61,7 +61,16 @@ - + + + + + + + + + + diff --git a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/JBallerinaDebugServer.java b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/JBallerinaDebugServer.java index e7181c330561..3ab74fd6d4ff 100755 --- a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/JBallerinaDebugServer.java +++ b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/JBallerinaDebugServer.java @@ -289,8 +289,8 @@ public CompletableFuture attach(Map args) { try { clientConfigHolder = new ClientAttachConfigHolder(args); context.setDebugMode(ExecutionContext.DebugMode.ATTACH); - Project sourceProject = context.getProjectCache().getProject(Path.of(clientConfigHolder.getSourcePath())); - context.setSourceProject(sourceProject); + Project srcProject = context.getProjectCache().getProject(Path.of(clientConfigHolder.getSourcePath())); + context.setSourceProject(srcProject); ClientAttachConfigHolder configHolder = (ClientAttachConfigHolder) clientConfigHolder; String hostName = configHolder.getHostName().orElse(""); From ec13a1d60ee583497930143d4a3d1b0dadaa686d Mon Sep 17 00:00:00 2001 From: Nipuna Ranasinghe Date: Mon, 16 Dec 2024 14:56:06 +0530 Subject: [PATCH 19/21] Fix LS test failures --- .../langserver/commons/workspace/RunContext.java | 4 ++-- .../langserver/workspace/BallerinaWorkspaceManager.java | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/language-server/modules/langserver-commons/src/main/java/org/ballerinalang/langserver/commons/workspace/RunContext.java b/language-server/modules/langserver-commons/src/main/java/org/ballerinalang/langserver/commons/workspace/RunContext.java index b041880bc5ba..990ea2be2bf5 100644 --- a/language-server/modules/langserver-commons/src/main/java/org/ballerinalang/langserver/commons/workspace/RunContext.java +++ b/language-server/modules/langserver-commons/src/main/java/org/ballerinalang/langserver/commons/workspace/RunContext.java @@ -1,7 +1,7 @@ /* - * Copyright (c) 2020, WSO2 LLC. (http://www.wso2.org). + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.org). * - * WSO2 Inc. licenses this file to you under the Apache License, + * WSO2 LLC. 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 diff --git a/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/workspace/BallerinaWorkspaceManager.java b/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/workspace/BallerinaWorkspaceManager.java index 62f216c92533..bf6d6aeea454 100644 --- a/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/workspace/BallerinaWorkspaceManager.java +++ b/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/workspace/BallerinaWorkspaceManager.java @@ -117,7 +117,7 @@ public class BallerinaWorkspaceManager implements WorkspaceManager { // workspace run related constants - private static final String JAVA_COMMAND = System.getProperty("java.command"); + private static final String JAVA_COMMAND = "java.command"; private static final String USER_DIR = System.getProperty("user.dir"); private static final String HEAP_DUMP_FLAG = "-XX:+HeapDumpOnOutOfMemoryError"; private static final String HEAP_DUMP_PATH_FLAG = "-XX:HeapDumpPath="; @@ -673,7 +673,7 @@ private Optional executeProject(ProjectContext projectContext, RunConte private List prepareExecutionCommands(RunContext context, Module module, JarResolver jarResolver) { List commands = new ArrayList<>(); - commands.add(JAVA_COMMAND); + commands.add(System.getProperty(JAVA_COMMAND)); commands.add(HEAP_DUMP_FLAG); commands.add(HEAP_DUMP_PATH_FLAG + USER_DIR); if (context.debugPort() > 0) { From f0008ac94e5fefe24ba83387730f6a58f798739c Mon Sep 17 00:00:00 2001 From: Nipuna Ranasinghe Date: Tue, 17 Dec 2024 16:13:27 +0530 Subject: [PATCH 20/21] Address review suggestions --- .../langserver/command/executors/RunExecutor.java | 3 +-- .../debugadapter/BallerinaExtendedDebugServer.java | 2 +- .../java/org/ballerinalang/debugadapter/utils/ServerUtils.java | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/command/executors/RunExecutor.java b/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/command/executors/RunExecutor.java index 00ff5b017bc3..deadb291f6a3 100644 --- a/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/command/executors/RunExecutor.java +++ b/language-server/modules/langserver-core/src/main/java/org/ballerinalang/langserver/command/executors/RunExecutor.java @@ -40,7 +40,6 @@ import java.util.Map; import java.util.Optional; import java.util.function.Supplier; -import java.util.stream.Collectors; import java.util.stream.StreamSupport; /** @@ -131,7 +130,7 @@ private List extractProgramArgs(ExecuteCommandContext context) { .map(JsonElement::getAsJsonPrimitive) .filter(JsonPrimitive::isString) .map(JsonPrimitive::getAsString) - .collect(Collectors.toList())) + .toList()) .orElse(Collections.emptyList()); } diff --git a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/BallerinaExtendedDebugServer.java b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/BallerinaExtendedDebugServer.java index e44f512d42cd..1340ce4d4da8 100644 --- a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/BallerinaExtendedDebugServer.java +++ b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/BallerinaExtendedDebugServer.java @@ -25,7 +25,7 @@ /** * Extended debug server interface for Ballerina debug adapter. * - * @since 2201.11.0 + * @since 2201.12.0 */ public interface BallerinaExtendedDebugServer extends IDebugProtocolServer { diff --git a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/utils/ServerUtils.java b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/utils/ServerUtils.java index f3634ce9c8a0..c1e1a96a545d 100644 --- a/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/utils/ServerUtils.java +++ b/misc/debug-adapter/modules/debug-adapter-core/src/main/java/org/ballerinalang/debugadapter/utils/ServerUtils.java @@ -36,7 +36,7 @@ /** * Ballerina debug server related utility functions. * - * @since 2201.11.0 + * @since 2201.12.0 */ public class ServerUtils { From cc87dfe05a48244a56b0ecda898fff50ad5a078e Mon Sep 17 00:00:00 2001 From: MaryamZi Date: Wed, 18 Dec 2024 17:42:03 +0530 Subject: [PATCH 21/21] Fix java-version in the workflow --- .github/workflows/daily_spec_conformance_test_runner.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/daily_spec_conformance_test_runner.yml b/.github/workflows/daily_spec_conformance_test_runner.yml index 7b506a45f943..e747a91919cc 100644 --- a/.github/workflows/daily_spec_conformance_test_runner.yml +++ b/.github/workflows/daily_spec_conformance_test_runner.yml @@ -21,7 +21,7 @@ jobs: uses: actions/setup-java@v4 with: distribution: "temurin" - java-version: "17" + java-version: "21.0.3" - name: Initialize sub-modules run: |