Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[adservice] add adServiceManualGC featureflag #1463

Merged
merged 9 commits into from
Mar 25, 2024
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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))

## 1.8.0

Expand Down
22 changes: 19 additions & 3 deletions src/adservice/src/main/java/oteldemo/AdService.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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() {}

/**
Expand Down Expand Up @@ -177,10 +181,16 @@ public void getAds(AdRequest req, StreamObserver<AdResponse> 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();
Expand All @@ -193,12 +203,18 @@ public void getAds(AdRequest req, StreamObserver<AdResponse> 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;
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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();
}
}
}
Original file line number Diff line number Diff line change
@@ -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();
}
}
9 changes: 9 additions & 0 deletions src/flagd/demo.flagd.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Loading