diff --git a/CHANGELOG.md b/CHANGELOG.md index 0b7aacfabb..0dae1e913f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -41,6 +41,8 @@ the release. ([#1448](https://github.com/open-telemetry/opentelemetry-demo/pull/1448)) * [cartservice] update .NET to .NET 8.0.3 ([#1460](https://github.com/open-telemetry/opentelemetry-demo/pull/1460)) +* [adservice] add adServiceManualGC feature flag + ([#1463](https://github.com/open-telemetry/opentelemetry-demo/pull/1463)) * [frontendproxy] remove deprecated start_child_span option ([#1469](https://github.com/open-telemetry/opentelemetry-demo/pull/1469)) * [currency] fix metric name diff --git a/src/adservice/src/main/java/oteldemo/AdService.java b/src/adservice/src/main/java/oteldemo/AdService.java index a2ddf9116e..75c50802e3 100644 --- a/src/adservice/src/main/java/oteldemo/AdService.java +++ b/src/adservice/src/main/java/oteldemo/AdService.java @@ -34,6 +34,7 @@ import oteldemo.Demo.Ad; import oteldemo.Demo.AdRequest; import oteldemo.Demo.AdResponse; +import oteldemo.problempattern.GarbageCollectionTrigger; import dev.openfeature.contrib.providers.flagd.FlagdOptions; import dev.openfeature.contrib.providers.flagd.FlagdProvider; import dev.openfeature.sdk.Client; @@ -127,6 +128,9 @@ private enum AdResponseType { private static class AdServiceImpl extends oteldemo.AdServiceGrpc.AdServiceImplBase { + private static final String ADSERVICE_FAILURE = "adServiceFailure"; + private static final String ADSERVICE_MANUAL_GC_FEATURE_FLAG = "adServiceManualGc"; + private AdServiceImpl() {} /** @@ -177,10 +181,16 @@ public void getAds(AdRequest req, StreamObserver responseObserver) { Attributes.of( adRequestTypeKey, adRequestType.name(), adResponseTypeKey, adResponseType.name())); - if (checkAdFailure()) { + if (getFeatureFlagEnabled(ADSERVICE_FAILURE)) { throw new StatusRuntimeException(Status.RESOURCE_EXHAUSTED); } + if (getFeatureFlagEnabled(ADSERVICE_MANUAL_GC_FEATURE_FLAG)) { + logger.warn("Feature Flag " + ADSERVICE_MANUAL_GC_FEATURE_FLAG + " enabled, performing a manual gc now"); + GarbageCollectionTrigger gct = new GarbageCollectionTrigger(); + gct.doExecute(); + } + AdResponse reply = AdResponse.newBuilder().addAllAds(allAds).build(); responseObserver.onNext(reply); responseObserver.onCompleted(); @@ -193,12 +203,18 @@ public void getAds(AdRequest req, StreamObserver responseObserver) { } } - boolean checkAdFailure() { + /** + * Retrieves the status of a feature flag from the Feature Flag service. + * + * @param ff The name of the feature flag to retrieve. + * @return {@code true} if the feature flag is enabled, {@code false} otherwise or in case of errors. + */ + boolean getFeatureFlagEnabled(String ff) { Client client = OpenFeatureAPI.getInstance().getClient(); // TODO: Plumb the actual session ID from the frontend via baggage? UUID uuid = UUID.randomUUID(); client.setEvaluationContext(new MutableContext().add("session", uuid.toString())); - Boolean boolValue = client.getBooleanValue("adServiceFailure", false); + Boolean boolValue = client.getBooleanValue(ff, false); return boolValue; } } diff --git a/src/adservice/src/main/java/oteldemo/problempattern/GarbageCollectionTrigger.java b/src/adservice/src/main/java/oteldemo/problempattern/GarbageCollectionTrigger.java new file mode 100644 index 0000000000..aa72bc1f2c --- /dev/null +++ b/src/adservice/src/main/java/oteldemo/problempattern/GarbageCollectionTrigger.java @@ -0,0 +1,80 @@ +/* +* Copyright The OpenTelemetry Authors +* SPDX-License-Identifier: Apache-2.0 +*/ + +package oteldemo.problempattern; + +import java.lang.management.ManagementFactory; +import java.util.concurrent.TimeUnit; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +/** + * The GarbageCollectionTrigger class is responsible for triggering manual garbage collection +* at specified intervals to simulate memory pressure and measure the impact on performance. +*/ +public class GarbageCollectionTrigger { + private static final Logger logger = LogManager.getLogger(GarbageCollectionTrigger.class.getName()); + + private final long gc_delay; + private final int finalize_delay; + private final int maxObjects; + + private long lastGC = 0; + + private final MemoryUtils memUtils; + + /** + * Constructs a new GarbageCollectionTrigger with default values. + */ + public GarbageCollectionTrigger() { + memUtils = new MemoryUtils(ManagementFactory.getMemoryMXBean()); + gc_delay = TimeUnit.SECONDS.toMillis(10); + finalize_delay = 500; + maxObjects = 500000; + } + + /** + * Triggers manual garbage collection at specified intervals and measures the impact on performance. + * It creates Entry objects to fill up memory and initiates garbage collection. + */ + public void doExecute() { + if (System.currentTimeMillis() - lastGC > gc_delay) { + logger.info("Triggering a manual garbage collection, next one in " + (gc_delay/1000) + " seconds."); + // clear old data, we want to clear old Entry objects, because their finalization is expensive + System.gc(); + + long total = 0; + for (int i = 0; i < 10; i++) { + while (memUtils.getHeapUsage() < 0.9 && memUtils.getObjectPendingFinalizationCount() < maxObjects) { + new Entry(); + } + long start = System.currentTimeMillis(); + System.gc(); + total += System.currentTimeMillis() - start; + } + logger.info("The artificially triggered GCs took: " + total + " ms"); + lastGC = System.currentTimeMillis(); + } + + } + + /** + * The Entry class represents objects created for the purpose of triggering garbage collection. + */ + private class Entry { + /** + * Overrides the finalize method to introduce a delay, simulating finalization during garbage collection. + * + * @throws Throwable If an exception occurs during finalization. + */ + @SuppressWarnings("removal") + @Override + protected void finalize() throws Throwable { + TimeUnit.MILLISECONDS.sleep(finalize_delay); + super.finalize(); + } + } +} diff --git a/src/adservice/src/main/java/oteldemo/problempattern/MemoryUtils.java b/src/adservice/src/main/java/oteldemo/problempattern/MemoryUtils.java new file mode 100644 index 0000000000..6b314145fe --- /dev/null +++ b/src/adservice/src/main/java/oteldemo/problempattern/MemoryUtils.java @@ -0,0 +1,65 @@ +/* +* Copyright The OpenTelemetry Authors +* SPDX-License-Identifier: Apache-2.0 +*/ + +package oteldemo.problempattern; + +import java.lang.management.MemoryMXBean; +import java.lang.management.MemoryUsage; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + + +/** + * This class provides JVM heap related utility methods. +*/ +public class MemoryUtils { + + private static final Logger logger = LogManager.getLogger(MemoryUtils.class.getName()); + + private static final long NO_HEAP_LIMIT = -1; + + private final MemoryMXBean memoryBean; + + /** + * @param memoryBean defines which {@link MemoryMXBean} is to use + */ + public MemoryUtils(MemoryMXBean memoryBean) { + this.memoryBean = memoryBean; + } + + + /** + * @return The current heap usage as a decimal number between 0.0 and 1.0. + * That is, if the returned value is 0.85, 85% of the max heap is used. + * + * If no max heap is set, the method returns -1.0. + */ + public double getHeapUsage() { + MemoryUsage heapProps = memoryBean.getHeapMemoryUsage(); + long heapUsed = heapProps.getUsed(); + long heapMax = heapProps.getMax(); + + if (heapMax == NO_HEAP_LIMIT) { + if (logger.isDebugEnabled()) { + logger.debug("No maximum heap is set"); + } + return NO_HEAP_LIMIT; + } + + + double heapUsage = (double) heapUsed / heapMax; + if (logger.isDebugEnabled()) { + logger.debug("Current heap usage is {0} percent" + (heapUsage * 100)); + } + return heapUsage; + } + + /** + * see {@link MemoryMXBean#getObjectPendingFinalizationCount()} + */ + public int getObjectPendingFinalizationCount() { + return memoryBean.getObjectPendingFinalizationCount(); + } +} diff --git a/src/flagd/demo.flagd.json b/src/flagd/demo.flagd.json index 5234fcce55..26c2c71b94 100644 --- a/src/flagd/demo.flagd.json +++ b/src/flagd/demo.flagd.json @@ -19,6 +19,15 @@ }, "defaultVariant": "off" }, + "adServiceManualGc": { + "description": "Triggers full manual garbage collections in the ad service", + "state": "ENABLED", + "variants": { + "on": true, + "off": false + }, + "defaultVariant": "off" + }, "adServiceFailure": { "description": "Fail ad service", "state": "ENABLED",