ml.comet.experiment.impl.BaseExperimentAsync Maven / Gradle / Ivy
package ml.comet.experiment.impl;
import io.reactivex.rxjava3.core.Observable;
import io.reactivex.rxjava3.core.Scheduler;
import io.reactivex.rxjava3.core.Single;
import io.reactivex.rxjava3.functions.Action;
import io.reactivex.rxjava3.functions.BiFunction;
import io.reactivex.rxjava3.schedulers.Schedulers;
import lombok.NonNull;
import ml.comet.experiment.artifact.Artifact;
import ml.comet.experiment.artifact.ArtifactAsset;
import ml.comet.experiment.artifact.ArtifactException;
import ml.comet.experiment.artifact.LoggedArtifact;
import ml.comet.experiment.asset.Asset;
import ml.comet.experiment.asset.RemoteAsset;
import ml.comet.experiment.context.ExperimentContext;
import ml.comet.experiment.impl.asset.ArtifactAssetImpl;
import ml.comet.experiment.impl.asset.AssetImpl;
import ml.comet.experiment.impl.asset.AssetType;
import ml.comet.experiment.impl.asset.RemoteAssetImpl;
import ml.comet.experiment.impl.rest.ArtifactEntry;
import ml.comet.experiment.impl.rest.ArtifactVersionState;
import ml.comet.experiment.impl.rest.HtmlRest;
import ml.comet.experiment.impl.rest.LogOtherRest;
import ml.comet.experiment.impl.rest.MetricRest;
import ml.comet.experiment.impl.rest.OutputUpdate;
import ml.comet.experiment.impl.rest.ParameterRest;
import ml.comet.experiment.impl.rest.RestApiResponse;
import ml.comet.experiment.impl.utils.AssetUtils;
import ml.comet.experiment.model.GitMetaData;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import java.io.File;
import java.net.URI;
import java.time.Duration;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Stream;
import static java.util.Optional.empty;
import static ml.comet.experiment.artifact.GetArtifactOptions.Op;
import static ml.comet.experiment.impl.resources.LogMessages.ARTIFACT_LOGGED_WITHOUT_ASSETS;
import static ml.comet.experiment.impl.resources.LogMessages.ARTIFACT_UPLOAD_COMPLETED;
import static ml.comet.experiment.impl.resources.LogMessages.ARTIFACT_UPLOAD_STARTED;
import static ml.comet.experiment.impl.resources.LogMessages.ASSETS_FOLDER_UPLOAD_COMPLETED;
import static ml.comet.experiment.impl.resources.LogMessages.FAILED_TO_FINALIZE_ARTIFACT_VERSION;
import static ml.comet.experiment.impl.resources.LogMessages.FAILED_TO_LOG_ASSET_FOLDER;
import static ml.comet.experiment.impl.resources.LogMessages.FAILED_TO_LOG_SOME_ASSET_FROM_FOLDER;
import static ml.comet.experiment.impl.resources.LogMessages.FAILED_TO_SEND_LOG_ARTIFACT_ASSET_REQUEST;
import static ml.comet.experiment.impl.resources.LogMessages.FAILED_TO_SEND_LOG_ASSET_REQUEST;
import static ml.comet.experiment.impl.resources.LogMessages.FAILED_TO_SEND_LOG_REQUEST;
import static ml.comet.experiment.impl.resources.LogMessages.FAILED_TO_UPLOAD_SOME_ARTIFACT_ASSET;
import static ml.comet.experiment.impl.resources.LogMessages.LOG_ASSET_FOLDER_EMPTY;
import static ml.comet.experiment.impl.resources.LogMessages.LOG_REMOTE_ASSET_URI_FILE_NAME_TO_DEFAULT;
import static ml.comet.experiment.impl.resources.LogMessages.getString;
import static ml.comet.experiment.impl.utils.AssetUtils.createAssetFromData;
import static ml.comet.experiment.impl.utils.AssetUtils.createAssetFromFile;
import static ml.comet.experiment.impl.utils.RestApiUtils.createGitMetadataRequest;
import static ml.comet.experiment.impl.utils.RestApiUtils.createGraphRequest;
import static ml.comet.experiment.impl.utils.RestApiUtils.createLogEndTimeRequest;
import static ml.comet.experiment.impl.utils.RestApiUtils.createLogHtmlRequest;
import static ml.comet.experiment.impl.utils.RestApiUtils.createLogLineRequest;
import static ml.comet.experiment.impl.utils.RestApiUtils.createLogMetricRequest;
import static ml.comet.experiment.impl.utils.RestApiUtils.createLogOtherRequest;
import static ml.comet.experiment.impl.utils.RestApiUtils.createLogParamRequest;
import static ml.comet.experiment.impl.utils.RestApiUtils.createLogStartTimeRequest;
import static ml.comet.experiment.impl.utils.RestApiUtils.createTagRequest;
/**
* The base class for all asynchronous experiment implementations providing implementation of common routines
* using asynchronous networking.
*/
abstract class BaseExperimentAsync extends BaseExperiment {
ExperimentContext baseContext;
BaseExperimentAsync(@NonNull final String apiKey,
@NonNull final String baseUrl,
int maxAuthRetries,
final String experimentKey,
@NonNull final Duration cleaningTimeout,
final String projectName,
final String workspaceName) {
super(apiKey, baseUrl, maxAuthRetries, experimentKey, cleaningTimeout, projectName, workspaceName);
this.baseContext = ExperimentContext.empty();
}
void updateContext(ExperimentContext context) {
this.baseContext.mergeFrom(context);
}
/**
* Asynchronous version that only logs any received exceptions or failures.
*
* @param metricName The name for the metric to be logged
* @param metricValue The new value for the metric. If the values for a metric are plottable we will plot them
* @param context the context to be associated with the parameter.
* @param onComplete The optional action to be invoked when this operation asynchronously completes.
* Can be {@code null} if not interested in completion signal.
*/
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
void logMetric(@NonNull String metricName, @NonNull Object metricValue,
@NonNull ExperimentContext context, @NonNull Optional onComplete) {
this.updateContext(context);
if (getLogger().isDebugEnabled()) {
getLogger().debug("logMetricAsync {} = {}, context: {}", metricName, metricValue, context);
}
MetricRest metricRequest = createLogMetricRequest(metricName, metricValue, this.baseContext);
this.sendAsynchronously(getRestApiClient()::logMetric, metricRequest, onComplete);
}
/**
* Asynchronous version that only logs any received exceptions or failures.
*
* @param parameterName The name of the param being logged
* @param paramValue The value for the param being logged
* @param context the context to be associated with the parameter.
* @param onComplete The optional action to be invoked when this operation asynchronously completes.
* Can be {@code null} if not interested in completion signal.
*/
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
void logParameter(@NonNull String parameterName, @NonNull Object paramValue,
@NonNull ExperimentContext context, @NonNull Optional onComplete) {
this.updateContext(context);
if (getLogger().isDebugEnabled()) {
getLogger().debug("logParameterAsync {} = {}, context: {}", parameterName, paramValue, context);
}
ParameterRest paramRequest = createLogParamRequest(parameterName, paramValue, this.baseContext);
this.sendAsynchronously(getRestApiClient()::logParameter, paramRequest, onComplete);
}
/**
* Asynchronous version that only logs any received exceptions or failures.
*
* @param html A block of html to be sent to Comet
* @param override Whether previous html sent should be deleted.
* If true
the old html will be deleted.
* @param onComplete The optional action to be invoked when this operation asynchronously completes.
* Can be {@code null} if not interested in completion signal.
*/
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
void logHtml(@NonNull String html, boolean override, @NonNull Optional onComplete) {
if (getLogger().isDebugEnabled()) {
getLogger().debug("logHtmlAsync {}, override: {}", html, override);
}
HtmlRest htmlRequest = createLogHtmlRequest(html, override);
this.sendAsynchronously(getRestApiClient()::logHtml, htmlRequest, onComplete);
}
/**
* Asynchronous version that only logs any received exceptions or failures.
*
* @param key The key for the data to be stored
* @param value The value for said key
* @param onComplete The optional action to be invoked when this operation asynchronously completes.
* Can be {@code null} if not interested in completion signal.
*/
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
void logOther(@NonNull String key, @NonNull Object value, @NonNull Optional onComplete) {
if (getLogger().isDebugEnabled()) {
getLogger().debug("logOtherAsync {} {}", key, value);
}
LogOtherRest request = createLogOtherRequest(key, value);
sendAsynchronously(getRestApiClient()::logOther, request, onComplete);
}
/**
* Asynchronous version that only logs any received exceptions or failures.
*
* @param tag The tag to be added
* @param onComplete The optional action to be invoked when this operation asynchronously completes.
* Can be {@code null} if not interested in completion signal.
*/
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
public void addTag(@NonNull String tag, @NonNull Optional onComplete) {
if (getLogger().isDebugEnabled()) {
getLogger().debug("addTagAsync {}", tag);
}
sendAsynchronously(getRestApiClient()::addTag, createTagRequest(tag), onComplete);
}
/**
* Asynchronous version that only logs any received exceptions or failures.
*
* @param graph The graph to be logged
* @param onComplete The optional action to be invoked when this operation asynchronously completes.
* Can be {@code null} if not interested in completion signal.
*/
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
void logGraph(@NonNull String graph, @NonNull Optional onComplete) {
if (getLogger().isDebugEnabled()) {
getLogger().debug("logGraphAsync {}", graph);
}
sendAsynchronously(getRestApiClient()::logGraph, createGraphRequest(graph), onComplete);
}
/**
* Asynchronous version that only logs any received exceptions or failures.
*
* @param startTimeMillis When you want to say that the experiment started
* @param onComplete The optional action to be invoked when this operation asynchronously completes.
* Can be {@code null} if not interested in completion signal.
*/
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
void logStartTime(long startTimeMillis, @NonNull Optional onComplete) {
if (getLogger().isDebugEnabled()) {
getLogger().debug("logStartTimeAsync {}", startTimeMillis);
}
sendAsynchronously(getRestApiClient()::logStartEndTime, createLogStartTimeRequest(startTimeMillis), onComplete);
}
/**
* Asynchronous version that only logs any received exceptions or failures.
*
* @param endTimeMillis When you want to say that the experiment ended
* @param onComplete The optional action to be invoked when this operation asynchronously completes.
* Can be {@code null} if not interested in completion signal.
*/
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
void logEndTime(long endTimeMillis, @NonNull Optional onComplete) {
if (getLogger().isDebugEnabled()) {
getLogger().debug("logEndTimeAsync {}", endTimeMillis);
}
sendAsynchronously(getRestApiClient()::logStartEndTime, createLogEndTimeRequest(endTimeMillis), onComplete);
}
/**
* Asynchronous version that only logs any received exceptions or failures.
*
* @param gitMetaData The Git Metadata for the experiment.
* @param onComplete The optional action to be invoked when this operation asynchronously completes.
* Can be {@code null} if not interested in completion signal.
*/
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
void logGitMetadataAsync(@NonNull GitMetaData gitMetaData, @NonNull Optional onComplete) {
if (getLogger().isDebugEnabled()) {
getLogger().debug("logGitMetadata {}", gitMetaData);
}
sendAsynchronously(getRestApiClient()::logGitMetadata, createGitMetadataRequest(gitMetaData), onComplete);
}
/**
* Asynchronous version that only logs any received exceptions or failures.
*
* @param line Text to be logged
* @param offset Offset describes the place for current text to be inserted
* @param stderr the flag to indicate if this is StdErr message.
* @param context the context to be associated with the parameter.
* @param onComplete The optional action to be invoked when this operation asynchronously completes.
* Can be {@code null} if not interested in completion signal.
*/
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
void logLine(String line, long offset, boolean stderr, String context, @NonNull Optional onComplete) {
if (!this.alive || StringUtils.isBlank(this.experimentKey)) {
// to avoid exceptions from StdOut logger
return;
}
OutputUpdate request = createLogLineRequest(line, offset, stderr, context);
Single single = validateAndGetExperimentKey()
.subscribeOn(Schedulers.io())
.concatMap(experimentKey -> getRestApiClient().logOutputLine(request, experimentKey));
// register notification action if provided
if (onComplete.isPresent()) {
single = single.doFinally(onComplete.get());
}
// subscribe to receive operation results but do not log anything
//noinspection ResultOfMethodCallIgnored
single.subscribe(
apiResponse -> {
// nothing to process here
},
throwable -> {
// just ignore to avoid infinite loop
});
}
/**
* Asynchronous version that only logs any received exceptions or failures.
*
* @param folder the folder you want to log.
* @param logFilePath if {@code true}, log the file path with each file.
* @param recursive if {@code true}, recurse the folder.
* @param prefixWithFolderName if {@code true} then path of each asset file will be prefixed with folder name
* in case if {@code logFilePath} is {@code true}.
* @param assetType optional type of the asset (default: ASSET).
* @param groupingName optional name of group the assets should belong.
* @param metadata the optional metadata to associate.
* @param context the context to be associated with logged assets.
* @param onCompleteAction The optional action to be invoked when this operation
* asynchronously completes. Can be empty if not interested in completion signal.
*/
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
void logAssetFolder(@NonNull File folder, boolean logFilePath, boolean recursive, boolean prefixWithFolderName,
@NonNull Optional assetType,
@NonNull Optional groupingName,
@NonNull Optional
© 2015 - 2025 Weber Informatics LLC | Privacy Policy