Skip to content

Commit

Permalink
Temporarily store signals in disk Part 2/3 (#172)
Browse files Browse the repository at this point in the history
* Adding disk buffering otel lib

* Created SignalDiskExporter

* Adding convenience method to export a batch of each signal from disk

* Validating null signal exporters

* Adding methods to get the app's cache dir and available cache space into AppInfoService

* Adding persistence configuration

* Calculating max signal folder size in DiskManager

* Ensuring dirs are available and temporary dir is cleaned up

* Checking dirs are created

* Clean up SignalDiskExporter

* Moving DiskManager to internal

* Making exporters visitable

* Adding max cache file size to SignalPersistenceConfiguration

* Adding license header

* Created SimpleTemporaryFileProvider

* Adding enabled option to PersistenceConfiguration

* Initializing persistence

* Adding debug logs

* Fixing old tests

* Adding tests to validate persistence initialization

* Adding logs and docs

* Updating the NOTICE files

* Reorganizing single task background concurrency tools

* Revert "Reorganizing single task background concurrency tools"

This reverts commit 15b3694.

* Creating PeriodicWorkService

* Updating PeriodicWorkService

* Updating time delay for PeriodicWorkService

* Making PeriodicWorkService task manipulation thread safe

* Using PeriodicWorkService to poll central config

* Removing periodic tasks based on their last run's response

* Adding periodic task delay logic to PeriodicTask

* Adding PeriodicWorkService initialization separately from the service start process

* Initializing CentralConfigurationInitializer through the PeriodicWorkExecutor tasks

* Validating PeriodicWorkService initialization

* Making NtpManager a PeriodicTask

* Verifying ntpmanager agent initialization

* Clean up

* Adding tests to PeriodicWorkService

* Renaming PeriodicTask method to better convey its intentions
  • Loading branch information
LikeTheSalad authored Aug 14, 2023
1 parent 8508056 commit 2eac513
Show file tree
Hide file tree
Showing 21 changed files with 612 additions and 305 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,9 @@
import co.elastic.apm.android.sdk.internal.opentelemetry.processors.metrics.ElasticMetricReader;
import co.elastic.apm.android.sdk.internal.opentelemetry.processors.spans.ElasticSpanProcessor;
import co.elastic.apm.android.sdk.internal.opentelemetry.tools.Flusher;
import co.elastic.apm.android.sdk.internal.services.Service;
import co.elastic.apm.android.sdk.internal.services.ServiceManager;
import co.elastic.apm.android.sdk.internal.services.periodicwork.PeriodicWorkService;
import co.elastic.apm.android.sdk.internal.time.ntp.NtpManager;
import co.elastic.apm.android.sdk.internal.utilities.logging.AndroidLoggerFactory;
import io.opentelemetry.api.common.Attributes;
Expand Down Expand Up @@ -118,6 +120,7 @@ private synchronized static ElasticApmAgent initialize(Context context, ElasticA
AgentDependenciesInjector injector = process(new DefaultAgentDependenciesInjector(appContext, finalConfiguration, finalConnectivity), interceptor);
instance = new ElasticApmAgent(finalConfiguration);
instance.onInitializationFinished(appContext, injector);
initializePeriodicWork();
return instance;
}

Expand Down Expand Up @@ -166,13 +169,18 @@ private void onInitializationFinished(Context context, AgentDependenciesInjector
initializeLifecycleObserver();
}

private static void initializePeriodicWork() {
getPeriodicWorkService().initialize();
}

private void initializeLifecycleObserver() {
ProcessLifecycleOwner.get().getLifecycle().addObserver(new ElasticProcessLifecycleObserver());
}

private void initializeNtpManager(AgentDependenciesInjector injector) {
ntpManager = injector.getNtpManager();
ntpManager.initialize();
getPeriodicWorkService().addTask(ntpManager);
}

private void initializeConfigurations(AgentDependenciesInjector injector) {
Expand All @@ -185,7 +193,7 @@ private void initializeConfigurations(AgentDependenciesInjector injector) {
configuration.instrumentationConfiguration.instrumentations.forEach(builder::register);
builder.buildAndRegisterGlobal();

centralConfigInitializer.initialize();
getPeriodicWorkService().addTask(centralConfigInitializer);
}

private void initializeSessionIdProvider() {
Expand Down Expand Up @@ -318,4 +326,8 @@ private SdkMeterProvider getMeterProvider(SignalConfiguration signalConfiguratio
private ContextPropagators getContextPropagator() {
return ContextPropagators.create(W3CTraceContextPropagator.getInstance());
}

private static PeriodicWorkService getPeriodicWorkService() {
return ServiceManager.get().getService(Service.Names.PERIODIC_WORK);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,26 +20,31 @@

import androidx.annotation.VisibleForTesting;

import co.elastic.apm.android.common.internal.logging.Elog;
import co.elastic.apm.android.sdk.internal.features.centralconfig.CentralConfigurationManager;
import co.elastic.apm.android.sdk.internal.features.centralconfig.poll.ConfigurationPollManager;
import co.elastic.apm.android.sdk.internal.utilities.concurrency.BackgroundExecutor;
import co.elastic.apm.android.sdk.internal.utilities.concurrency.Result;
import co.elastic.apm.android.sdk.internal.utilities.concurrency.impl.SimpleBackgroundExecutor;
import co.elastic.apm.android.sdk.internal.services.Service;
import co.elastic.apm.android.sdk.internal.services.ServiceManager;
import co.elastic.apm.android.sdk.internal.services.periodicwork.PeriodicTask;
import co.elastic.apm.android.sdk.internal.services.periodicwork.PeriodicWorkService;

public final class CentralConfigurationInitializer implements BackgroundExecutor.Callback<Integer> {
private final BackgroundExecutor executor;
public final class CentralConfigurationInitializer extends PeriodicTask {
private final CentralConfigurationManager manager;
private final ConfigurationPollManager pollManager;
private final PeriodicWorkService periodicWorkService;

@VisibleForTesting
public CentralConfigurationInitializer(BackgroundExecutor executor, CentralConfigurationManager manager, ConfigurationPollManager pollManager) {
this.executor = executor;
public CentralConfigurationInitializer(CentralConfigurationManager manager,
ConfigurationPollManager pollManager,
PeriodicWorkService periodicWorkService) {
super();
this.manager = manager;
this.pollManager = pollManager;
this.periodicWorkService = periodicWorkService;
}

public CentralConfigurationInitializer(CentralConfigurationManager manager, ConfigurationPollManager pollManager) {
this(new SimpleBackgroundExecutor(), manager, pollManager);
this(manager, pollManager, ServiceManager.get().getService(Service.Names.PERIODIC_WORK));
}

public CentralConfigurationManager getManager() {
Expand All @@ -50,19 +55,32 @@ public ConfigurationPollManager getPollManager() {
return pollManager;
}

public void initialize() {
executor.execute(() -> {
@Override
protected void onPeriodicTaskRun() {
try {
manager.publishCachedConfig();
return manager.sync();
}, this);
Integer delayForNextPollInSeconds = manager.sync();
if (delayForNextPollInSeconds != null) {
pollManager.scheduleInSeconds(delayForNextPollInSeconds);
} else {
pollManager.scheduleDefault();
}
} catch (Throwable t) {
Elog.getLogger().error("CentralConfigurationInitializer error", t);
pollManager.scheduleDefault();
}
periodicWorkService.addTask(pollManager);
}

@Override
public void onFinish(Result<Integer> result) {
if (result.isSuccess && result.value != null) {
pollManager.scheduleInSeconds(result.value);
} else {
pollManager.scheduleDefault();
}
protected long getMinDelayBeforeNextRunInMillis() {
// Doesn't need to delay its execution further than the Periodic work service internal delays.
return 0;
}

@Override
public boolean isFinished() {
// Will only run once.
return true;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,34 +18,22 @@
*/
package co.elastic.apm.android.sdk.internal.features.centralconfig.poll;

import androidx.annotation.VisibleForTesting;

import org.slf4j.Logger;

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import co.elastic.apm.android.common.internal.logging.Elog;
import co.elastic.apm.android.sdk.internal.features.centralconfig.CentralConfigurationManager;
import co.elastic.apm.android.sdk.internal.utilities.providers.LazyProvider;
import co.elastic.apm.android.sdk.internal.utilities.providers.Provider;
import co.elastic.apm.android.sdk.internal.services.periodicwork.PeriodicTask;

public final class ConfigurationPollManager implements Runnable {
public final class ConfigurationPollManager extends PeriodicTask {
private static ConfigurationPollManager INSTANCE;
private final Provider<ScheduledExecutorService> executorProvider;
private static final long DEFAULT_DELAY_IN_SECONDS = 60;
private final CentralConfigurationManager manager;
private final Logger logger = Elog.getLogger();
private static final long DEFAULT_DELAY_IN_SECONDS = 60;

@VisibleForTesting
public ConfigurationPollManager(CentralConfigurationManager manager, Provider<ScheduledExecutorService> executorProvider) {
this.manager = manager;
this.executorProvider = executorProvider;
}
private long delayForNextRunInMillis = DEFAULT_DELAY_IN_SECONDS * 1000;

public ConfigurationPollManager(CentralConfigurationManager manager) {
this(manager, LazyProvider.of(() -> Executors.newSingleThreadScheduledExecutor(new PollThreadFactory())));
super();
this.manager = manager;
}

public static ConfigurationPollManager get() {
Expand All @@ -63,18 +51,18 @@ public static void resetForTest() {
INSTANCE = null;
}

public void scheduleInSeconds(long delayInSeconds) {
public synchronized void scheduleInSeconds(long delayInSeconds) {
logger.info("Scheduling next central config poll");
logger.debug("Next central config poll in {} seconds", delayInSeconds);
executorProvider.get().schedule(this, delayInSeconds, TimeUnit.SECONDS);
delayForNextRunInMillis = delayInSeconds * 1000;
}

public void scheduleDefault() {
scheduleInSeconds(DEFAULT_DELAY_IN_SECONDS);
}

@Override
public void run() {
protected void onPeriodicTaskRun() {
try {
Integer maxAgeInSeconds = manager.sync();
if (maxAgeInSeconds == null) {
Expand All @@ -88,4 +76,14 @@ public void run() {
scheduleDefault();
}
}

@Override
protected long getMinDelayBeforeNextRunInMillis() {
return delayForNextRunInMillis;
}

@Override
public boolean isFinished() {
return false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,6 @@ class Names {
public static final String NETWORK = "network";
public static final String METADATA = "apm-metadata";
public static final String PREFERENCES = "preferences";
public static final String PERIODIC_WORK = "periodic-work";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import co.elastic.apm.android.sdk.internal.services.appinfo.AppInfoService;
import co.elastic.apm.android.sdk.internal.services.metadata.ApmMetadataService;
import co.elastic.apm.android.sdk.internal.services.network.NetworkService;
import co.elastic.apm.android.sdk.internal.services.periodicwork.PeriodicWorkService;
import co.elastic.apm.android.sdk.internal.services.preferences.PreferencesService;
import co.elastic.apm.android.sdk.internal.utilities.providers.LazyProvider;
import co.elastic.apm.android.sdk.internal.utilities.providers.Provider;
Expand All @@ -45,6 +46,7 @@ public static void initialize(Context appContext) {
INSTANCE.addService(new AppInfoService(appContext));
INSTANCE.addService(new ApmMetadataService(appContext));
INSTANCE.addService(new PreferencesService(appContext));
INSTANCE.addService(new PeriodicWorkService());
}

@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. 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 co.elastic.apm.android.sdk.internal.services.periodicwork;

import co.elastic.apm.android.sdk.internal.time.SystemTimeProvider;

public abstract class PeriodicTask {
private final SystemTimeProvider timeProvider;
private long lastTimeItRan = 0;

public PeriodicTask() {
this(SystemTimeProvider.get());
}

protected PeriodicTask(SystemTimeProvider timeProvider) {
this.timeProvider = timeProvider;
}

/**
* Calls {@link #onPeriodicTaskRun()} if the task is due to be run, noop otherwise.
*
* @return true when the task was run, false otherwise.
*/
public final boolean runPeriodicTask() {
if (isReadyToRun()) {
onPeriodicTaskRun();
lastTimeItRan = timeProvider.getCurrentTimeMillis();
return true;
}
return false;
}

/**
* Runs a task when it's due.
*/
protected abstract void onPeriodicTaskRun();

/**
* Returns the minimum amount of milliseconds that need to pass before this task gets to run again.
* The actual time before running this task can be higher than the one provided in here,
* given that tasks are run from the {@link PeriodicWorkService} class which has its own internal
* delay between iterations.
*/
protected abstract long getMinDelayBeforeNextRunInMillis();

/**
* Indicates whether this task needs to keep running in future iterations or not.
*
* @return false if this task needs to be called again in the future, true otherwise.
*/
public abstract boolean isFinished();

private boolean isReadyToRun() {
if (lastTimeItRan < 1) {
return true;
}
return timeProvider.getCurrentTimeMillis() >= (lastTimeItRan + getMinDelayBeforeNextRunInMillis());
}
}
Loading

0 comments on commit 2eac513

Please sign in to comment.