chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 13:22:34 +08:00
commit 4b22cfda96
9037 changed files with 2363717 additions and 0 deletions
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,2 @@
/** Autogenerated MLflow Tracking API entity objects. */
package org.mlflow.api.proto;
@@ -0,0 +1,113 @@
package org.mlflow.artifacts;
import java.io.File;
import java.util.List;
import org.mlflow.api.proto.Service.FileInfo;
/**
* Allows logging, listing, and downloading artifacts against a remote Artifact Repository.
* This is used for storing potentially-large objects associated with MLflow runs.
*/
public interface ArtifactRepository {
/**
* Uploads the given local file to the run's root artifact directory. For example,
*
* <pre>
* logArtifact("/my/localModel")
* listArtifacts() // returns "localModel"
* </pre>
*
* @param localFile File to upload. Must exist, and must be a simple file (not a directory).
*/
void logArtifact(File localFile);
/**
* Uploads the given local file to an artifactPath within the run's root directory. For example,
*
* <pre>
* logArtifact("/my/localModel", "model")
* listArtifacts("model") // returns "model/localModel"
* </pre>
*
* (i.e., the localModel file is now available in model/localModel).
*
* @param localFile File to upload. Must exist, and must be a simple file (not a directory).
* @param artifactPath Artifact path relative to the run's root directory. Should NOT
* start with a /.
*/
void logArtifact(File localFile, String artifactPath);
/**
* Uploads all files within the given local director the run's root artifact directory.
* For example, if /my/local/dir/ contains two files "file1" and "file2", then
*
* <pre>
* logArtifacts("/my/local/dir")
* listArtifacts() // returns "file1" and "file2"
* </pre>
*
* @param localDir Directory to upload. Must exist, and must be a directory (not a simple file).
*/
void logArtifacts(File localDir);
/**
* Uploads all files within the given local director an artifactPath within the run's root
* artifact directory. For example, if /my/local/dir/ contains two files "file1" and "file2", then
*
* <pre>
* logArtifacts("/my/local/dir", "model")
* listArtifacts("model") // returns "model/file1" and "model/file2"
* </pre>
*
* (i.e., the contents of the local directory are now available in model/).
*
* @param localDir Directory to upload. Must exist, and must be a directory (not a simple file).
* @param artifactPath Artifact path relative to the run's root directory. Should NOT
* start with a /.
*/
void logArtifacts(File localDir, String artifactPath);
/**
* Lists the artifacts immediately under the run's root artifact directory. This does not
* recursively list; instead, it will return FileInfos with isDir=true where further
* listing may be done.
*/
List<FileInfo> listArtifacts();
/**
* Lists the artifacts immediately under the given artifactPath within the run's root artifact
* irectory. This does not recursively list; instead, it will return FileInfos with isDir=true
* where further listing may be done.
* @param artifactPath Artifact path relative to the run's root directory. Should NOT
* start with a /.
*/
List<FileInfo> listArtifacts(String artifactPath);
/**
* Returns a local directory containing *all* artifacts within the run's artifact directory.
* Note that this will download the entire directory path, and so may be expensive if
* the directory a lot of data.
*/
File downloadArtifacts();
/**
* Returns a local file or directory containing all artifacts within the given artifactPath
* within the run's root artifactDirectory. For example, if "model/file1" and "model/file2"
* exist within the artifact directory, then
*
* <pre>
* downloadArtifacts("model") // returns a local directory containing "file1" and "file2"
* downloadArtifacts("model/file1") // returns a local *file* with the contents of file1.
* </pre>
*
* Note that this will download the entire subdirectory path, and so may be expensive if
* the subdirectory a lot of data.
*
* @param artifactPath Artifact path relative to the run's root directory. Should NOT
* start with a /.
*/
File downloadArtifacts(String artifactPath);
}
@@ -0,0 +1,17 @@
package org.mlflow.artifacts;
import java.net.URI;
import org.mlflow.tracking.creds.MlflowHostCredsProvider;
public class ArtifactRepositoryFactory {
private final MlflowHostCredsProvider hostCredsProvider;
public ArtifactRepositoryFactory(MlflowHostCredsProvider hostCredsProvider) {
this.hostCredsProvider = hostCredsProvider;
}
public ArtifactRepository getArtifactRepository(URI baseArtifactUri, String runId) {
return new CliBasedArtifactRepository(baseArtifactUri.toString(), runId, hostCredsProvider);
}
}
@@ -0,0 +1,314 @@
package org.mlflow.artifacts;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicBoolean;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.Lists;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import com.google.protobuf.InvalidProtocolBufferException;
import com.google.protobuf.util.JsonFormat;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.mlflow.api.proto.Service;
import org.mlflow.tracking.MlflowClientException;
import org.mlflow.tracking.creds.MlflowHostCreds;
import org.mlflow.tracking.creds.DatabricksMlflowHostCreds;
import org.mlflow.tracking.creds.MlflowHostCredsProvider;
/**
* Shells out to the 'mlflow' command line utility to upload, download, and list artifacts. This
* is used as a fallback to implement any artifact repositories which are not natively supported
* within Java.
*
* We require that 'mlflow' is available in the system path.
*/
public class CliBasedArtifactRepository implements ArtifactRepository {
private static final Logger logger = LoggerFactory.getLogger(CliBasedArtifactRepository.class);
// Global check if we ever successfully loaded 'mlflow'. This allows us to print a more
// helpful error message if the executable is not in the path.
private static final AtomicBoolean mlflowSuccessfullyLoaded = new AtomicBoolean(false);
// Name of the Python CLI utility which can be exec'd directly, with MLflow on its path
private final String PYTHON_EXECUTABLE =
Optional.ofNullable(System.getenv("MLFLOW_PYTHON_EXECUTABLE")).orElse("python");
// Python CLI command
private final String PYTHON_COMMAND = "mlflow.store.artifact.cli";
// Base directory of the artifactory, used to let the user know why this repository was chosen.
private final String artifactBaseDir;
// Run ID this repository is targeting.
private final String runId;
// Used to pass credentials as environment variables
// (e.g., MLFLOW_TRACKING_URI or DATABRICKS_HOST) to the mlflow process.
private final MlflowHostCredsProvider hostCredsProvider;
public CliBasedArtifactRepository(
String artifactBaseDir,
String runId,
MlflowHostCredsProvider hostCredsProvider) {
this.artifactBaseDir = artifactBaseDir;
this.runId = runId;
this.hostCredsProvider = hostCredsProvider;
}
@Override
public void logArtifact(File localFile, String artifactPath) {
checkMlflowAccessible();
if (!localFile.exists()) {
throw new MlflowClientException("Local file does not exist: " + localFile);
}
if (localFile.isDirectory()) {
throw new MlflowClientException("Local path points to a directory. Use logArtifacts" +
" instead: " + localFile);
}
List<String> baseCommand = Lists.newArrayList(
"log-artifact", "--local-file", localFile.toString());
List<String> command = appendRunIdArtifactPath(baseCommand, runId, artifactPath);
String tag = "log file " + localFile + " to " + getTargetIdentifier(artifactPath);
forkMlflowProcess(command, tag);
}
@Override
public void logArtifact(File localFile) {
logArtifact(localFile, null);
}
@Override
public void logArtifacts(File localDir, String artifactPath) {
checkMlflowAccessible();
if (!localDir.exists()) {
throw new MlflowClientException("Local file does not exist: " + localDir);
}
if (localDir.isFile()) {
throw new MlflowClientException("Local path points to a file. Use logArtifact" +
" instead: " + localDir);
}
List<String> baseCommand = Lists.newArrayList(
"log-artifacts", "--local-dir", localDir.toString());
List<String> command = appendRunIdArtifactPath(baseCommand, runId, artifactPath);
String tag = "log dir " + localDir + " to " + getTargetIdentifier(artifactPath);
forkMlflowProcess(command, tag);
}
@Override
public void logArtifacts(File localDir) {
logArtifacts(localDir, null);
}
@Override
public File downloadArtifacts(String artifactPath) {
checkMlflowAccessible();
String tag = "download artifacts for " + getTargetIdentifier(artifactPath);
List<String> command = appendRunIdArtifactPath(
Lists.newArrayList("download"), runId, artifactPath);
String stdOutput = forkMlflowProcess(command, tag);
String[] splits = stdOutput.split(System.lineSeparator());
return new File(splits[splits.length-1].trim());
}
@Override
public File downloadArtifacts() {
return downloadArtifacts(null);
}
@Override
public List<Service.FileInfo> listArtifacts(String artifactPath) {
checkMlflowAccessible();
String tag = "list artifacts in " + getTargetIdentifier(artifactPath);
List<String> command = appendRunIdArtifactPath(
Lists.newArrayList("list"), runId, artifactPath);
String jsonOutput = forkMlflowProcess(command, tag);
return parseFileInfos(jsonOutput);
}
@Override
public List<Service.FileInfo> listArtifacts() {
return listArtifacts(null);
}
/**
* Only available in the CliBasedArtifactRepository. Downloads an artifact to the local
* filesystem when provided with an artifact uri. This method should not be used directly
* by the user. Please use {@link org.mlflow.tracking.MlflowClient}
*
* @param artifactUri Artifact uri
* @return Directory/file of the artifact
*/
public File downloadArtifactFromUri(String artifactUri) {
checkMlflowAccessible();
String tag = "download artifacts for " + artifactUri;
List<String> command = Lists.newArrayList("download", "--artifact-uri", artifactUri);
String localPath = forkMlflowProcess(command, tag).trim();
return new File(localPath);
}
/** Parses a list of JSON FileInfos, as returned by 'mlflow artifacts list'. */
private List<Service.FileInfo> parseFileInfos(String json) {
// The protobuf deserializer doesn't allow us to directly deserialize a list, so we
// deserialize a list-of-dictionaries, and then reserialize each dictionary to pass it to
// the protobuf deserializer.
Gson gson = new Gson();
Type type = new TypeToken<List<Map<String, Object>>>(){}.getType();
List<Map<String, Object>> listOfDicts = gson.fromJson(json, type);
List<Service.FileInfo> fileInfos = new ArrayList<>();
for (Map<String, Object> dict: listOfDicts) {
String fileInfoJson = gson.toJson(dict);
try {
Service.FileInfo.Builder builder = Service.FileInfo.newBuilder();
JsonFormat.parser().merge(fileInfoJson, builder);
fileInfos.add(builder.build());
} catch (InvalidProtocolBufferException e) {
throw new MlflowClientException("Failed to deserialize JSON into FileInfo: " + json, e);
}
}
return fileInfos;
}
/**
* Checks whether the 'mlflow' executable is available, and throws a nice error if not.
* If this method has ever run successfully before (in the entire JVM), we will not rerun it.
*/
private void checkMlflowAccessible() {
if (mlflowSuccessfullyLoaded.get()) {
return;
}
try {
String tag = "get mlflow version";
forkMlflowProcess(Lists.newArrayList("--help"), tag);
logger.info("Found local mlflow executable");
mlflowSuccessfullyLoaded.set(true);
} catch (MlflowClientException e) {
String errorMessage = String.format("Failed to exec '%s -m %s', needed to" +
" access artifacts within the non-Java-native artifact store at '%s'. Please make" +
" sure mlflow is available on your local system path (e.g., from 'pip install mlflow')",
PYTHON_EXECUTABLE, PYTHON_COMMAND, artifactBaseDir);
throw new MlflowClientException(errorMessage, e);
}
}
/**
* Forks the given mlflow command and awaits for its successful completion.
*
* @param mlflowCommand List of arguments to invoke mlflow with.
* @param tag User-facing tag which will be used to identify what we were trying to do
* in the case of a failure.
* @return raw stdout of the process, decoded as a utf-8 string
* @throws MlflowClientException if the process exits with a non-zero exit code, or anything
* else goes wrong.
*/
private String forkMlflowProcess(List<String> mlflowCommand, String tag) {
String stdout;
Process process = null;
try {
MlflowHostCreds hostCreds = hostCredsProvider.getHostCreds();
List<String> fullCommand = Lists.newArrayList(PYTHON_EXECUTABLE, "-m", PYTHON_COMMAND);
fullCommand.addAll(mlflowCommand);
ProcessBuilder pb = new ProcessBuilder(fullCommand);
if (hostCreds instanceof DatabricksMlflowHostCreds) {
setProcessEnvironmentDatabricks(pb.environment(), (DatabricksMlflowHostCreds) hostCreds);
} else {
setProcessEnvironment(pb.environment(), hostCreds);
}
process = pb.start();
stdout = IOUtils.toString(process.getInputStream(), StandardCharsets.UTF_8);
int exitValue = process.waitFor();
if (exitValue != 0) {
throw new MlflowClientException("Failed to " + tag + ". Error: " +
getErrorBestEffort(process));
}
} catch (IOException | InterruptedException e) {
throw new MlflowClientException("Failed to fork mlflow process to " + tag +
". Process stderr: " + getErrorBestEffort(process), e);
}
return stdout;
}
@VisibleForTesting
void setProcessEnvironment(Map<String, String> environment, MlflowHostCreds hostCreds) {
environment.put("MLFLOW_TRACKING_URI", hostCreds.getHost());
if (hostCreds.getUsername() != null) {
environment.put("MLFLOW_TRACKING_USERNAME", hostCreds.getUsername());
}
if (hostCreds.getPassword() != null) {
environment.put("MLFLOW_TRACKING_PASSWORD", hostCreds.getPassword());
}
if (hostCreds.getToken() != null) {
environment.put("MLFLOW_TRACKING_TOKEN", hostCreds.getToken());
}
if (hostCreds.shouldIgnoreTlsVerification()) {
environment.put("MLFLOW_TRACKING_INSECURE_TLS", "true");
}
}
@VisibleForTesting
void setProcessEnvironmentDatabricks(
Map<String, String> environment,
DatabricksMlflowHostCreds hostCreds) {
environment.put("DATABRICKS_HOST", hostCreds.getHost());
if (hostCreds.getUsername() != null) {
environment.put("DATABRICKS_USERNAME", hostCreds.getUsername());
}
if (hostCreds.getPassword() != null) {
environment.put("DATABRICKS_PASSWORD", hostCreds.getPassword());
}
if (hostCreds.getToken() != null) {
environment.put("DATABRICKS_TOKEN", hostCreds.getToken());
}
if (hostCreds.shouldIgnoreTlsVerification()) {
environment.put("DATABRICKS_INSECURE", "true");
}
}
/** Does our best to get the process's stderr, or returns a dummy return value. */
private String getErrorBestEffort(Process process) {
if (process == null) {
return "<process not started>";
}
try {
return IOUtils.toString(process.getErrorStream(), StandardCharsets.UTF_8);
} catch (IOException e) {
return "<error unknown>";
}
}
/** Appends --run-id $runId and --artifact-path $artifactPath, omitting artifactPath if null. */
private List<String> appendRunIdArtifactPath(
List<String> baseCommand,
String runId,
String artifactPath) {
baseCommand.add("--run-id");
baseCommand.add(runId);
if (artifactPath != null) {
baseCommand.add("--artifact-path");
baseCommand.add(artifactPath);
}
return baseCommand;
}
/** Returns user-facing identifier "runId=abc, artifactId=/foo", omitting artifactPath if null. */
private String getTargetIdentifier(String artifactPath) {
String identifier = "runId=" + runId;
if (artifactPath != null) {
return identifier + ", artifactPath=" + artifactPath;
}
return identifier;
}
}
@@ -0,0 +1,257 @@
// Generated by the protocol buffer compiler. DO NOT EDIT!
// source: internal.proto
package org.mlflow.internal.proto;
public final class Internal {
private Internal() {}
public static void registerAllExtensions(
com.google.protobuf.ExtensionRegistryLite registry) {
}
public static void registerAllExtensions(
com.google.protobuf.ExtensionRegistry registry) {
registerAllExtensions(
(com.google.protobuf.ExtensionRegistryLite) registry);
}
/**
* <pre>
* Types of vertices represented in MLflow Run Inputs. Valid vertices are MLflow objects that can
* have an input relationship.
* </pre>
*
* Protobuf enum {@code mlflow.internal.InputVertexType}
*/
public enum InputVertexType
implements com.google.protobuf.ProtocolMessageEnum {
/**
* <code>RUN = 1;</code>
*/
RUN(1),
/**
* <code>DATASET = 2;</code>
*/
DATASET(2),
/**
* <code>MODEL = 3;</code>
*/
MODEL(3),
;
/**
* <code>RUN = 1;</code>
*/
public static final int RUN_VALUE = 1;
/**
* <code>DATASET = 2;</code>
*/
public static final int DATASET_VALUE = 2;
/**
* <code>MODEL = 3;</code>
*/
public static final int MODEL_VALUE = 3;
public final int getNumber() {
return value;
}
/**
* @param value The numeric wire value of the corresponding enum entry.
* @return The enum associated with the given numeric wire value.
* @deprecated Use {@link #forNumber(int)} instead.
*/
@java.lang.Deprecated
public static InputVertexType valueOf(int value) {
return forNumber(value);
}
/**
* @param value The numeric wire value of the corresponding enum entry.
* @return The enum associated with the given numeric wire value.
*/
public static InputVertexType forNumber(int value) {
switch (value) {
case 1: return RUN;
case 2: return DATASET;
case 3: return MODEL;
default: return null;
}
}
public static com.google.protobuf.Internal.EnumLiteMap<InputVertexType>
internalGetValueMap() {
return internalValueMap;
}
private static final com.google.protobuf.Internal.EnumLiteMap<
InputVertexType> internalValueMap =
new com.google.protobuf.Internal.EnumLiteMap<InputVertexType>() {
public InputVertexType findValueByNumber(int number) {
return InputVertexType.forNumber(number);
}
};
public final com.google.protobuf.Descriptors.EnumValueDescriptor
getValueDescriptor() {
return getDescriptor().getValues().get(ordinal());
}
public final com.google.protobuf.Descriptors.EnumDescriptor
getDescriptorForType() {
return getDescriptor();
}
public static final com.google.protobuf.Descriptors.EnumDescriptor
getDescriptor() {
return org.mlflow.internal.proto.Internal.getDescriptor().getEnumTypes().get(0);
}
private static final InputVertexType[] VALUES = values();
public static InputVertexType valueOf(
com.google.protobuf.Descriptors.EnumValueDescriptor desc) {
if (desc.getType() != getDescriptor()) {
throw new java.lang.IllegalArgumentException(
"EnumValueDescriptor is not for this type.");
}
return VALUES[desc.getIndex()];
}
private final int value;
private InputVertexType(int value) {
this.value = value;
}
// @@protoc_insertion_point(enum_scope:mlflow.internal.InputVertexType)
}
/**
* <pre>
* Types of vertices represented in MLflow Run Outputs. Valid vertices are MLflow objects that can
* have an output relationship.
* </pre>
*
* Protobuf enum {@code mlflow.internal.OutputVertexType}
*/
public enum OutputVertexType
implements com.google.protobuf.ProtocolMessageEnum {
/**
* <code>RUN_OUTPUT = 1;</code>
*/
RUN_OUTPUT(1),
/**
* <code>MODEL_OUTPUT = 2;</code>
*/
MODEL_OUTPUT(2),
;
/**
* <code>RUN_OUTPUT = 1;</code>
*/
public static final int RUN_OUTPUT_VALUE = 1;
/**
* <code>MODEL_OUTPUT = 2;</code>
*/
public static final int MODEL_OUTPUT_VALUE = 2;
public final int getNumber() {
return value;
}
/**
* @param value The numeric wire value of the corresponding enum entry.
* @return The enum associated with the given numeric wire value.
* @deprecated Use {@link #forNumber(int)} instead.
*/
@java.lang.Deprecated
public static OutputVertexType valueOf(int value) {
return forNumber(value);
}
/**
* @param value The numeric wire value of the corresponding enum entry.
* @return The enum associated with the given numeric wire value.
*/
public static OutputVertexType forNumber(int value) {
switch (value) {
case 1: return RUN_OUTPUT;
case 2: return MODEL_OUTPUT;
default: return null;
}
}
public static com.google.protobuf.Internal.EnumLiteMap<OutputVertexType>
internalGetValueMap() {
return internalValueMap;
}
private static final com.google.protobuf.Internal.EnumLiteMap<
OutputVertexType> internalValueMap =
new com.google.protobuf.Internal.EnumLiteMap<OutputVertexType>() {
public OutputVertexType findValueByNumber(int number) {
return OutputVertexType.forNumber(number);
}
};
public final com.google.protobuf.Descriptors.EnumValueDescriptor
getValueDescriptor() {
return getDescriptor().getValues().get(ordinal());
}
public final com.google.protobuf.Descriptors.EnumDescriptor
getDescriptorForType() {
return getDescriptor();
}
public static final com.google.protobuf.Descriptors.EnumDescriptor
getDescriptor() {
return org.mlflow.internal.proto.Internal.getDescriptor().getEnumTypes().get(1);
}
private static final OutputVertexType[] VALUES = values();
public static OutputVertexType valueOf(
com.google.protobuf.Descriptors.EnumValueDescriptor desc) {
if (desc.getType() != getDescriptor()) {
throw new java.lang.IllegalArgumentException(
"EnumValueDescriptor is not for this type.");
}
return VALUES[desc.getIndex()];
}
private final int value;
private OutputVertexType(int value) {
this.value = value;
}
// @@protoc_insertion_point(enum_scope:mlflow.internal.OutputVertexType)
}
public static com.google.protobuf.Descriptors.FileDescriptor
getDescriptor() {
return descriptor;
}
private static com.google.protobuf.Descriptors.FileDescriptor
descriptor;
static {
java.lang.String[] descriptorData = {
"\n\016internal.proto\022\017mlflow.internal\032\025scala" +
"pb/scalapb.proto*2\n\017InputVertexType\022\007\n\003R" +
"UN\020\001\022\013\n\007DATASET\020\002\022\t\n\005MODEL\020\003*4\n\020OutputVe" +
"rtexType\022\016\n\nRUN_OUTPUT\020\001\022\020\n\014MODEL_OUTPUT" +
"\020\002B#\n\031org.mlflow.internal.proto\220\001\001\342?\002\020\001"
};
descriptor = com.google.protobuf.Descriptors.FileDescriptor
.internalBuildGeneratedFileFrom(descriptorData,
new com.google.protobuf.Descriptors.FileDescriptor[] {
org.mlflow.scalapb_interface.Scalapb.getDescriptor(),
});
com.google.protobuf.ExtensionRegistry registry =
com.google.protobuf.ExtensionRegistry.newInstance();
registry.add(org.mlflow.scalapb_interface.Scalapb.options);
com.google.protobuf.Descriptors.FileDescriptor
.internalUpdateFileDescriptor(descriptor, registry);
org.mlflow.scalapb_interface.Scalapb.getDescriptor();
}
// @@protoc_insertion_point(outer_class_scope)
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,202 @@
package org.mlflow.tracking;
import org.mlflow.api.proto.Service.*;
import java.nio.file.Path;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
/**
* Represents an active MLflow run and contains APIs to log data to the run.
*/
public class ActiveRun {
private MlflowClient client;
private RunInfo runInfo;
ActiveRun(RunInfo runInfo, MlflowClient client) {
this.runInfo = runInfo;
this.client = client;
}
/**
* Gets the run id of this run.
* @return The run id of this run.
*/
public String getId() {
return runInfo.getRunId();
}
/**
* Log a parameter under this run.
*
* @param key The name of the parameter.
* @param value The value of the parameter.
*/
public void logParam(String key, String value) {
client.logParam(getId(), key, value);
}
/**
* Sets a tag under this run.
*
* @param key The name of the tag.
* @param value The value of the tag.
*/
public void setTag(String key, String value) {
client.setTag(getId(), key, value);
}
/**
* Like {@link #logMetric(String, double, int)} with a default step of 0.
*/
public void logMetric(String key, double value) {
logMetric(key, value, 0);
}
/**
* Logs a metric under this run.
*
* @param key The name of the metric.
* @param value The value of the metric.
* @param step The metric step.
*/
public void logMetric(String key, double value, int step) {
client.logMetric(getId(), key, value, System.currentTimeMillis(), step);
}
/**
* Like {@link #logMetrics(Map, int)} with a default step of 0.
*/
public void logMetrics(Map<String, Double> metrics) {
logMetrics(metrics, 0);
}
/**
* Log multiple metrics for this run.
*
* @param metrics A map of metric name to value.
* @param step The metric step.
*/
public void logMetrics(Map<String, Double> metrics, int step) {
List<Metric> protoMetrics = metrics.entrySet().stream()
.map((metric) ->
Metric.newBuilder()
.setKey(metric.getKey())
.setValue(metric.getValue())
.setTimestamp(System.currentTimeMillis())
.setStep(step)
.build()
).collect(Collectors.toList());
client.logBatch(getId(), protoMetrics, Collections.emptyList(), Collections.emptyList());
}
/**
* Log multiple params for this run.
*
* @param params A map of param name to value.
*/
public void logParams(Map<String, String> params) {
List<Param> protoParams = params.entrySet().stream().map((param) ->
Param.newBuilder()
.setKey(param.getKey())
.setValue(param.getValue())
.build()
).collect(Collectors.toList());
client.logBatch(getId(), Collections.emptyList(), protoParams, Collections.emptyList());
}
/**
* Sets multiple tags for this run.
*
* @param tags A map of tag name to value.
*/
public void setTags(Map<String, String> tags) {
List<RunTag> protoTags = tags.entrySet().stream().map((tag) ->
RunTag.newBuilder().setKey(tag.getKey()).setValue(tag.getValue()).build()
).collect(Collectors.toList());
client.logBatch(getId(), Collections.emptyList(), Collections.emptyList(), protoTags);
}
/**
* Like {@link #logArtifact(Path, String)} with the artifactPath set to the root of the
* artifact directory.
*
* @param localPath Path of file to upload. Must exist, and must be a simple file
* (not a directory).
*/
public void logArtifact(Path localPath) {
client.logArtifact(getId(), localPath.toFile());
}
/**
* Uploads the given local file to the run's root artifact directory. For example,
*
* <pre>
* activeRun.logArtifact("/my/localModel", "model")
* mlflowClient.listArtifacts(activeRun.getId(), "model") // returns "model/localModel"
* </pre>
*
* @param localPath Path of file to upload. Must exist, and must be a simple file
* (not a directory).
* @param artifactPath Artifact path relative to the run's root directory given by
* {@link #getArtifactUri()}. Should NOT start with a /.
*/
public void logArtifact(Path localPath, String artifactPath) {
client.logArtifact(getId(), localPath.toFile(), artifactPath);
}
/**
* Like {@link #logArtifacts(Path, String)} with the artifactPath set to the root of the
* artifact directory.
*
* @param localPath Directory to upload. Must exist, and must be a directory (not a simple file).
*/
public void logArtifacts(Path localPath) {
client.logArtifacts(getId(), localPath.toFile());
}
/**
* Uploads all files within the given local director an artifactPath within the run's root
* artifact directory. For example, if /my/local/dir/ contains two files "file1" and "file2", then
*
* <pre>
* activeRun.logArtifacts("/my/local/dir", "model")
* mlflowClient.listArtifacts(activeRun.getId(), "model") // returns "model/file1" and
* // "model/file2"
* </pre>
*
* (i.e., the contents of the local directory are now available in model/).
*
* @param localPath Directory to upload. Must exist, and must be a directory (not a simple file).
* @param artifactPath Artifact path relative to the run's root directory given by
* {@link #getArtifactUri()}. Should NOT start with a /.
*/
public void logArtifacts(Path localPath, String artifactPath) {
client.logArtifacts(getId(), localPath.toFile(), artifactPath);
}
/**
* Get the absolute URI of the run artifact directory root.
* @return The absolute URI of the run artifact directory root.
*/
public String getArtifactUri() {
return this.runInfo.getArtifactUri();
}
/**
* Ends the active MLflow run.
*/
public void endRun() {
endRun(RunStatus.FINISHED);
}
/**
* Ends the active MLflow run.
*
* @param status The status of the run.
*/
public void endRun(RunStatus status) {
client.setTerminated(getId(), status);
}
}
@@ -0,0 +1,48 @@
package org.mlflow.tracking;
import java.util.Collections;
import java.util.Optional;
public class EmptyPage<E> implements Page<E> {
/**
* Creates an empty page
*/
EmptyPage() {}
/**
* @return Zero
*/
public int getPageSize() {
return 0;
}
/**
* @return False
*/
public boolean hasNextPage() {
return false;
}
/**
* @return An empty Optional.
*/
public Optional<String> getNextPageToken() {
return Optional.empty();
}
/**
* @return An {@link org.mlflow.tracking.EmptyPage}
*/
public EmptyPage getNextPage() {
return this;
}
/**
* @return An empty iterable.
*/
public Iterable getItems() {
return Collections.EMPTY_LIST;
}
}
@@ -0,0 +1,88 @@
package org.mlflow.tracking;
import java.util.Collections;
import java.util.List;
import java.util.ArrayList;
import java.util.Optional;
import org.mlflow.api.proto.Service.*;
public class ExperimentsPage implements Page<Experiment> {
private final String token;
private final List<Experiment> experiments;
private final MlflowClient client;
private final String searchFilter;
private final ViewType experimentViewType;
private final List<String> orderBy;
private final int maxResults;
/**
* Creates a fixed size page of Experiments.
*/
ExperimentsPage(List<Experiment> experiments,
String token,
String searchFilter,
ViewType experimentViewType,
int maxResults,
List<String> orderBy,
MlflowClient client) {
this.experiments = Collections.unmodifiableList(experiments);
this.token = token;
this.searchFilter = searchFilter;
this.experimentViewType = experimentViewType;
this.orderBy = orderBy;
this.maxResults = maxResults;
this.client = client;
}
/**
* @return The number of experiments in the page.
*/
public int getPageSize() {
return this.experiments.size();
}
/**
* @return True if a token for the next page exists and isn't empty. Otherwise returns false.
*/
public boolean hasNextPage() {
return this.token != null && this.token != "";
}
/**
* @return An optional with the token for the next page.
* Empty if the token doesn't exist or is empty.
*/
public Optional<String> getNextPageToken() {
if (this.hasNextPage()) {
return Optional.of(this.token);
} else {
return Optional.empty();
}
}
/**
* @return The next page of experiments matching the search criteria.
* If there are no more pages, an {@link org.mlflow.tracking.EmptyPage} will be returned.
*/
public Page<Experiment> getNextPage() {
if (this.hasNextPage()) {
return this.client.searchExperiments(this.searchFilter,
this.experimentViewType,
this.maxResults,
this.orderBy,
this.token);
} else {
return new EmptyPage();
}
}
/**
* @return An iterable over the experiments in this page.
*/
public List<Experiment> getItems() {
return experiments;
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,14 @@
package org.mlflow.tracking;
/** Superclass of all exceptions thrown by the MlflowClient API. */
public class MlflowClientException extends RuntimeException {
public MlflowClientException(String message) {
super(message);
}
public MlflowClientException(String message, Throwable cause) {
super(message, cause);
}
public MlflowClientException(Throwable cause) {
super(cause);
}
}
@@ -0,0 +1,34 @@
package org.mlflow.tracking;
import java.io.InputStream;
import java.util.Properties;
import com.google.common.base.Supplier;
import com.google.common.base.Suppliers;
/** Returns the version of the MLflow project this client was compiled against. */
public class MlflowClientVersion {
// To avoid extra disk IO during class loading (static initialization), we lazily read the
// pom.properties file on first access and then cache the result to avoid future IO.
private static Supplier<String> clientVersionSupplier = Suppliers.memoize(() -> {
try {
Properties p = new Properties();
InputStream is = MlflowClientVersion.class.getResourceAsStream(
"/META-INF/maven/org.mlflow/mlflow-client/pom.properties");
if (is == null) {
return "";
}
p.load(is);
return p.getProperty("version", "");
} catch (Exception e) {
return "";
}
});
private MlflowClientVersion() {}
/** @return MLflow client version (e.g., 0.9.1) or an empty string if detection fails. */
public static String getClientVersion() {
return clientVersionSupplier.get();
}
}
@@ -0,0 +1,255 @@
package org.mlflow.tracking;
import org.mlflow.api.proto.Service.*;
import org.mlflow.tracking.utils.DatabricksContext;
import org.mlflow.tracking.utils.MlflowTagConstants;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.*;
import java.util.function.Consumer;
/**
* Main entrypoint used to start MLflow runs to log to. This is a higher level interface than
* {@code MlflowClient} and provides convenience methods to keep track of active runs and to set
* default tags on runs which are created through {@code MlflowContext}
*
* On construction, MlflowContext will choose a default experiment ID to log to depending on your
* environment. To log to a different experiment, use {@link #setExperimentId(String)} or
* {@link #setExperimentName(String)}
*
* <p>
* For example:
* <pre>
* // Uses the URI set in the MLFLOW_TRACKING_URI environment variable.
* // To use your own tracking uri set it in the call to "new MlflowContext("tracking-uri")"
* MlflowContext mlflow = new MlflowContext();
* ActiveRun run = mlflow.startRun("run-name");
* run.logParam("alpha", "0.5");
* run.logMetric("MSE", 0.0);
* run.endRun();
* </pre>
*/
public class MlflowContext {
private MlflowClient client;
private String experimentId;
// Cache the default experiment ID for a repo notebook to avoid sending
// extraneous API requests
private static String defaultRepoNotebookExperimentId;
private static final Logger logger = LoggerFactory.getLogger(MlflowContext.class);
/**
* Constructs a {@code MlflowContext} with a MlflowClient based on the MLFLOW_TRACKING_URI
* environment variable.
*/
public MlflowContext() {
this(new MlflowClient());
}
/**
* Constructs a {@code MlflowContext} which points to the specified trackingUri.
*
* @param trackingUri The URI to log to.
*/
public MlflowContext(String trackingUri) {
this(new MlflowClient(trackingUri));
}
/**
* Constructs a {@code MlflowContext} which points to the specified trackingUri.
*
* @param client The client used to log runs.
*/
public MlflowContext(MlflowClient client) {
this.client = client;
this.experimentId = getDefaultExperimentId();
}
/**
* Returns the client used to log runs.
*
* @return the client used to log runs.
*/
public MlflowClient getClient() {
return this.client;
}
/**
* Sets the experiment to log runs to by name.
* @param experimentName the name of the experiment to log runs to.
* @throws IllegalArgumentException if the experiment name does not match an existing experiment
*/
public MlflowContext setExperimentName(String experimentName) throws IllegalArgumentException {
Optional<Experiment> experimentOpt = client.getExperimentByName(experimentName);
if (!experimentOpt.isPresent()) {
throw new IllegalArgumentException(
String.format("%s is not a valid experiment", experimentName));
}
experimentId = experimentOpt.get().getExperimentId();
return this;
}
/**
* Sets the experiment to log runs to by ID.
* @param experimentId the id of the experiment to log runs to.
*/
public MlflowContext setExperimentId(String experimentId) {
this.experimentId = experimentId;
return this;
}
/**
* Returns the experiment ID we are logging to.
*
* @return the experiment ID we are logging to.
*/
public String getExperimentId() {
return this.experimentId;
}
/**
* Starts a MLflow run without a name. To log data to newly created MLflow run see the methods on
* {@link ActiveRun}. MLflow runs should be ended using {@link ActiveRun#endRun()}
*
* @return An {@code ActiveRun} object to log data to.
*/
public ActiveRun startRun() {
return startRun(null);
}
/**
* Starts a MLflow run. To log data to newly created MLflow run see the methods on
* {@link ActiveRun}. MLflow runs should be ended using {@link ActiveRun#endRun()}
*
* @param runName The name of this run. For display purposes only and is stored in the
* mlflow.runName tag.
* @return An {@code ActiveRun} object to log data to.
*/
public ActiveRun startRun(String runName) {
return startRun(runName, null);
}
/**
* Like {@link #startRun(String)} but sets the {@code mlflow.parentRunId} tag in order to create
* nested runs.
*
* @param runName The name of this run. For display purposes only and is stored in the
* mlflow.runName tag.
* @param parentRunId The ID of this run's parent
* @return An {@code ActiveRun} object to log data to.
*/
public ActiveRun startRun(String runName, String parentRunId) {
Map<String, String> tags = new HashMap<>();
if (runName != null) {
tags.put(MlflowTagConstants.RUN_NAME, runName);
}
tags.put(MlflowTagConstants.USER, System.getProperty("user.name"));
tags.put(MlflowTagConstants.SOURCE_TYPE, "LOCAL");
if (parentRunId != null) {
tags.put(MlflowTagConstants.PARENT_RUN_ID, parentRunId);
}
// Add tags from DatabricksContext if they exist
DatabricksContext databricksContext = DatabricksContext.createIfAvailable();
if (databricksContext != null) {
tags.putAll(databricksContext.getTags());
}
CreateRun.Builder createRunBuilder = CreateRun.newBuilder()
.setExperimentId(experimentId)
.setStartTime(System.currentTimeMillis());
for (Map.Entry<String, String> tag: tags.entrySet()) {
createRunBuilder.addTags(
RunTag.newBuilder().setKey(tag.getKey()).setValue(tag.getValue()).build());
}
RunInfo runInfo = client.createRun(createRunBuilder.build());
ActiveRun newRun = new ActiveRun(runInfo, client);
return newRun;
}
/**
* Like {@link #startRun(String)} but will terminate the run after the activeRunFunction is
* executed.
*
* For example
* <pre>
* mlflowContext.withActiveRun((activeRun -&gt; {
* activeRun.logParam("layers", "4");
* }));
* </pre>
*
* @param activeRunFunction A function which takes an {@code ActiveRun} and logs data to it.
*/
public void withActiveRun(Consumer<ActiveRun> activeRunFunction) {
ActiveRun newRun = startRun();
try {
activeRunFunction.accept(newRun);
} catch(Exception e) {
newRun.endRun(RunStatus.FAILED);
return;
}
newRun.endRun(RunStatus.FINISHED);
}
/**
* Like {@link #withActiveRun(Consumer)} with an explicity run name.
*
* @param runName The name of this run. For display purposes only and is stored in the
* mlflow.runName tag.
* @param activeRunFunction A function which takes an {@code ActiveRun} and logs data to it.
*/
public void withActiveRun(String runName, Consumer<ActiveRun> activeRunFunction) {
ActiveRun newRun = startRun(runName);
try {
activeRunFunction.accept(newRun);
} catch(Exception e) {
newRun.endRun(RunStatus.FAILED);
return;
}
newRun.endRun(RunStatus.FINISHED);
}
private static String getDefaultRepoNotebookExperimentId(String notebookId, String notebookPath) {
if (defaultRepoNotebookExperimentId != null) {
return defaultRepoNotebookExperimentId;
}
CreateExperiment.Builder request = CreateExperiment.newBuilder();
request.setName(notebookPath);
request.addTags(ExperimentTag.newBuilder()
.setKey(MlflowTagConstants.MLFLOW_EXPERIMENT_SOURCE_TYPE)
.setValue("REPO_NOTEBOOK")
);
request.addTags(ExperimentTag.newBuilder()
.setKey(MlflowTagConstants.MLFLOW_EXPERIMENT_SOURCE_ID)
.setValue(notebookId)
);
String experimentId = (new MlflowClient()).createExperiment(request.build());
defaultRepoNotebookExperimentId = experimentId;
return experimentId;
}
private static String getDefaultExperimentId() {
DatabricksContext databricksContext = DatabricksContext.createIfAvailable();
if (databricksContext != null && databricksContext.isInDatabricksNotebook()) {
String notebookId = databricksContext.getNotebookId();
String notebookPath = databricksContext.getNotebookPath();
if (notebookId != null) {
if (notebookPath != null && notebookPath.startsWith("/Repos")) {
try {
return getDefaultRepoNotebookExperimentId(notebookId, notebookPath);
}
catch (Exception e) {
// Do nothing; will fall through to returning notebookId
logger.warn("Failed to get default repo notebook experiment ID", e);
}
}
return notebookId;
}
}
return MlflowClient.DEFAULT_EXPERIMENT_ID;
}
}
@@ -0,0 +1,260 @@
package org.mlflow.tracking;
import com.google.common.annotations.VisibleForTesting;
import java.io.IOException;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPatch;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.TrustSelfSignedStrategy;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.ssl.SSLContextBuilder;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.mlflow.tracking.creds.MlflowHostCreds;
import org.mlflow.tracking.creds.MlflowHostCredsProvider;
class MlflowHttpCaller {
private static final Logger logger = LoggerFactory.getLogger(MlflowHttpCaller.class);
private static final String BASE_API_PATH = "api/2.0/mlflow";
protected CloseableHttpClient httpClient;
private final MlflowHostCredsProvider hostCredsProvider;
private final int maxRateLimitIntervalMillis;
private final int rateLimitRetrySleepInitMillis;
private final int maxRetryAttempts;
/**
* Construct a new MlflowHttpCaller with a default configuration for request retries.
*/
MlflowHttpCaller(MlflowHostCredsProvider hostCredsProvider) {
this(hostCredsProvider, 60000, 1000, 3);
}
/**
* Construct a new MlflowHttpCaller.
*
* @param maxRateLimitIntervalMs The maximum amount of time, in milliseconds, to spend retrying a
* single request in response to rate limiting (error code 429).
* @param rateLimitRetrySleepInitMs The initial backoff delay, in milliseconds, when retrying a
* request in response to rate limiting (error code 429). The
* delay is increased exponentially after each rate limiting
* response until the total delay incurred across all retries for
* the request exceeds the specified maxRateLimitIntervalSeconds.
* @param maxRetryAttempts The maximum number of times to retry a request, excluding rate limit
* retries.
*/
MlflowHttpCaller(MlflowHostCredsProvider hostCredsProvider,
int maxRateLimitIntervalMs,
int rateLimitRetrySleepInitMs,
int maxRetryAttempts) {
this.hostCredsProvider = hostCredsProvider;
this.maxRateLimitIntervalMillis = maxRateLimitIntervalMs;
this.rateLimitRetrySleepInitMillis = rateLimitRetrySleepInitMs;
this.maxRetryAttempts = maxRetryAttempts;
}
@VisibleForTesting
MlflowHttpCaller(MlflowHostCredsProvider hostCredsProvider,
int maxRateLimitIntervalMs,
int rateLimitRetrySleepInitMs,
int maxRetryAttempts,
CloseableHttpClient client) {
this(
hostCredsProvider, maxRateLimitIntervalMs, rateLimitRetrySleepInitMs, maxRetryAttempts);
this.httpClient = client;
}
private HttpResponse executeRequestWithRateLimitRetries(HttpRequestBase request)
throws IOException {
int timeLeft = maxRateLimitIntervalMillis;
int sleepFor = rateLimitRetrySleepInitMillis;
HttpResponse response = httpClient.execute(request);
while (response.getStatusLine().getStatusCode() == 429 && timeLeft > 0) {
logger.warn("Request returned with status code 429 (Rate limit exceeded). Retrying after "
+ sleepFor
+ " milliseconds. Will continue to retry 429s for up to "
+ timeLeft
+ " milliseconds.");
try {
Thread.sleep(sleepFor);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
timeLeft -= sleepFor;
sleepFor = Math.min(timeLeft, 2 * sleepFor);
response = httpClient.execute(request);
}
checkError(response);
return response;
}
private HttpResponse executeRequest(HttpRequestBase request) throws IOException {
HttpResponse response = null;
int attemptsRemaining = this.maxRetryAttempts;
while (attemptsRemaining > 0) {
attemptsRemaining -= 1;
try {
response = executeRequestWithRateLimitRetries(request);
break;
} catch (MlflowHttpException e) {
if (attemptsRemaining > 0 && e.getStatusCode() != 429) {
logger.warn("Request returned with status code {} (Rate limit exceeded)."
+ " Retrying up to {} more times. Response body: {}",
e.getStatusCode(),
attemptsRemaining,
e.getBodyMessage());
continue;
} else {
throw e;
}
}
}
return response;
}
String get(String path) {
logger.debug("Sending GET " + path);
HttpGet request = new HttpGet();
fillRequestSettings(request, path);
try {
HttpResponse response = executeRequest(request);
String responseJson = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8);
logger.debug("Response: " + responseJson);
return responseJson;
} catch (IOException e) {
throw new MlflowClientException(e);
}
}
// TODO(aaron) Convert to InputStream.
byte[] getAsBytes(String path) {
logger.debug("Sending GET " + path);
HttpGet request = new HttpGet();
fillRequestSettings(request, path);
try {
HttpResponse response = executeRequest(request);
byte[] bytes = EntityUtils.toByteArray(response.getEntity());
logger.debug("response: #bytes=" + bytes.length);
return bytes;
} catch (IOException e) {
throw new MlflowClientException(e);
}
}
String post(String path, String json) {
logger.debug("Sending POST " + path + ": " + json);
HttpPost request = new HttpPost();
return send(request, path, json);
}
String patch(String path, String json) {
logger.debug("Sending PATCH " + path + ": " + json);
HttpPatch request = new HttpPatch();
return send(request, path, json);
}
private String send(HttpEntityEnclosingRequestBase request, String path, String json) {
fillRequestSettings(request, path);
request.setEntity(new StringEntity(json, StandardCharsets.UTF_8));
request.setHeader("Content-Type", "application/json");
try {
HttpResponse response = executeRequest(request);
String responseJson = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8);
logger.debug("Response: " + responseJson);
return responseJson;
} catch (IOException e) {
throw new MlflowClientException(e);
}
}
private void checkError(HttpResponse response) throws MlflowClientException, IOException {
int statusCode = response.getStatusLine().getStatusCode();
String reasonPhrase = response.getStatusLine().getReasonPhrase();
if (isError(statusCode)) {
String bodyMessage = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8);
if (statusCode >= 400 && statusCode <= 499) {
throw new MlflowHttpException(statusCode, reasonPhrase, bodyMessage);
}
if (statusCode >= 500 && statusCode <= 599) {
throw new MlflowHttpException(statusCode, reasonPhrase, bodyMessage);
}
throw new MlflowHttpException(statusCode, reasonPhrase, bodyMessage);
}
}
private void fillRequestSettings(HttpRequestBase request, String path) {
MlflowHostCreds hostCreds = hostCredsProvider.getHostCreds();
createHttpClientIfNecessary(hostCreds.shouldIgnoreTlsVerification());
String uri = hostCreds.getHost() + "/" + BASE_API_PATH + "/" + path;
request.setURI(URI.create(uri));
String username = hostCreds.getUsername();
String password = hostCreds.getPassword();
String token = hostCreds.getToken();
if (username != null && password != null) {
String authHeader = Base64.getEncoder()
.encodeToString((username + ":" + password).getBytes(StandardCharsets.UTF_8));
request.addHeader("Authorization", "Basic " + authHeader);
} else if (token != null) {
request.addHeader("Authorization", "Bearer " + token);
}
String userAgent = "mlflow-java-client";
String clientVersion = MlflowClientVersion.getClientVersion();
if (!clientVersion.isEmpty()) {
userAgent += "/" + clientVersion;
}
request.addHeader("User-Agent", userAgent);
}
private boolean isError(int statusCode) {
return statusCode < 200 || statusCode > 399;
}
private void createHttpClientIfNecessary(boolean noTlsVerify) {
if (httpClient != null) {
return;
}
HttpClientBuilder builder = HttpClientBuilder.create();
if (noTlsVerify) {
try {
SSLContextBuilder sslBuilder = new SSLContextBuilder()
.loadTrustMaterial(null, new TrustSelfSignedStrategy());
SSLConnectionSocketFactory connectionFactory =
new SSLConnectionSocketFactory(sslBuilder.build(), new NoopHostnameVerifier());
builder.setSSLSocketFactory(connectionFactory);
} catch (NoSuchAlgorithmException | KeyStoreException | KeyManagementException e) {
logger.warn("Could not set noTlsVerify to true, verification will remain", e);
}
}
this.httpClient = builder.build();
}
void close() {
if (httpClient != null) {
try {
httpClient.close();
} catch(IOException e){
logger.warn("Unable to close connection to mlflow backend", e);
}
}
}
}
@@ -0,0 +1,39 @@
package org.mlflow.tracking;
/**
* Returned when an HTTP API request to a remote Tracking service returns an error code.
*/
public class MlflowHttpException extends MlflowClientException {
public MlflowHttpException(int statusCode, String reasonPhrase) {
super("statusCode=" + statusCode + " reasonPhrase=[" + reasonPhrase +"]");
this.statusCode = statusCode;
this.reasonPhrase = reasonPhrase;
}
public MlflowHttpException(int statusCode, String reasonPhrase, String bodyMessage) {
super("statusCode=" + statusCode + " reasonPhrase=[" + reasonPhrase + "] bodyMessage=["
+ bodyMessage + "]");
this.statusCode = statusCode;
this.reasonPhrase = reasonPhrase;
this.bodyMessage = bodyMessage;
}
private int statusCode;
public int getStatusCode() {
return statusCode;
}
private String reasonPhrase;
public String getReasonPhrase() {
return reasonPhrase;
}
private String bodyMessage;
public String getBodyMessage() {
return bodyMessage;
}
}
@@ -0,0 +1,321 @@
package org.mlflow.tracking;
import com.google.protobuf.InvalidProtocolBufferException;
import com.google.protobuf.MessageOrBuilder;
import com.google.protobuf.util.JsonFormat;
import java.lang.Iterable;
import java.net.URISyntaxException;
import java.util.List;
import org.apache.http.client.utils.URIBuilder;
import org.mlflow.api.proto.ModelRegistry.*;
import org.mlflow.api.proto.Service.*;
class MlflowProtobufMapper {
String makeCreateExperimentRequest(String expName) {
CreateExperiment.Builder builder = CreateExperiment.newBuilder();
builder.setName(expName);
return print(builder);
}
String makeDeleteExperimentRequest(String experimentId) {
DeleteExperiment.Builder builder = DeleteExperiment.newBuilder();
builder.setExperimentId(experimentId);
return print(builder);
}
String makeRestoreExperimentRequest(String experimentId) {
RestoreExperiment.Builder builder = RestoreExperiment.newBuilder();
builder.setExperimentId(experimentId);
return print(builder);
}
String makeUpdateExperimentRequest(String experimentId, String newExperimentName) {
UpdateExperiment.Builder builder = UpdateExperiment.newBuilder();
builder.setExperimentId(experimentId);
builder.setNewName(newExperimentName);
return print(builder);
}
String makeLogParam(String runId, String key, String value) {
LogParam.Builder builder = LogParam.newBuilder();
builder.setRunUuid(runId);
builder.setRunId(runId);
builder.setKey(key);
builder.setValue(value);
return print(builder);
}
String makeLogMetric(String runId, String key, double value, long timestamp, long step) {
LogMetric.Builder builder = LogMetric.newBuilder();
builder.setRunUuid(runId);
builder.setRunId(runId);
builder.setKey(key);
builder.setValue(value);
builder.setTimestamp(timestamp);
builder.setStep(step);
return print(builder);
}
String makeSetExperimentTag(String expId, String key, String value) {
SetExperimentTag.Builder builder = SetExperimentTag.newBuilder();
builder.setExperimentId(expId);
builder.setKey(key);
builder.setValue(value);
return print(builder);
}
String makeSetTag(String runId, String key, String value) {
SetTag.Builder builder = SetTag.newBuilder();
builder.setRunUuid(runId);
builder.setRunId(runId);
builder.setKey(key);
builder.setValue(value);
return print(builder);
}
String makeDeleteTag(String runId, String key) {
DeleteTag.Builder builder = DeleteTag.newBuilder();
builder.setRunId(runId);
builder.setKey(key);
return print(builder);
}
String makeLogBatch(String runId,
Iterable<Metric> metrics,
Iterable<Param> params,
Iterable<RunTag> tags) {
LogBatch.Builder builder = LogBatch.newBuilder();
builder.setRunId(runId);
if (metrics != null) {
builder.addAllMetrics(metrics);
}
if (params != null) {
builder.addAllParams(params);
}
if (tags != null) {
builder.addAllTags(tags);
}
return print(builder);
}
String makeUpdateRun(String runId, RunStatus status, long endTime) {
UpdateRun.Builder builder = UpdateRun.newBuilder();
builder.setRunUuid(runId);
builder.setRunId(runId);
builder.setStatus(status);
builder.setEndTime(endTime);
return print(builder);
}
String makeDeleteRun(String runId) {
DeleteRun.Builder builder = DeleteRun.newBuilder();
builder.setRunId(runId);
return print(builder);
}
String makeRestoreRun(String runId) {
RestoreRun.Builder builder = RestoreRun.newBuilder();
builder.setRunId(runId);
return print(builder);
}
String makeGetLatestVersion(String modelName, Iterable<String> stages) {
try {
URIBuilder builder = new URIBuilder("registered-models/get-latest-versions")
.addParameter("name", modelName);
if (stages != null) {
for( String stage: stages) {
builder.addParameter("stages", stage);
}
}
return builder.build().toString();
} catch (URISyntaxException e) {
throw new MlflowClientException("Failed to construct request URI for get latest versions.",
e);
}
}
String makeUpdateModelVersion(String modelName, String version) {
return print(UpdateModelVersion.newBuilder().setName(modelName).setVersion(version));
}
String makeTransitionModelVersionStage(String modelName, String version, String stage) {
return print(TransitionModelVersionStage.newBuilder()
.setName(modelName).setVersion(version).setStage(stage));
}
String makeCreateModel(String modelName) {
CreateRegisteredModel.Builder builder = CreateRegisteredModel.newBuilder()
.setName(modelName);
return print(builder);
}
String makeCreateModelVersion(String modelName, String runId, String source) {
CreateModelVersion.Builder builder = CreateModelVersion.newBuilder()
.setName(modelName)
.setRunId(runId)
.setSource(source);
return print(builder);
}
String makeGetRegisteredModel(String modelName) {
try {
return new URIBuilder("registered-models/get")
.addParameter("name", modelName)
.build()
.toString();
} catch (URISyntaxException e) {
throw new MlflowClientException("Failed to construct request URI for get model version.", e);
}
}
String makeGetModelVersion(String modelName, String modelVersion) {
try {
return new URIBuilder("model-versions/get")
.addParameter("name", modelName)
.addParameter("version", modelVersion)
.build()
.toString();
} catch (URISyntaxException e) {
throw new MlflowClientException("Failed to construct request URI for get model version.", e);
}
}
String makeGetModelVersionDownloadUri(String modelName, String modelVersion) {
try {
return new URIBuilder("model-versions/get-download-uri")
.addParameter("name", modelName)
.addParameter("version", modelVersion).build().toString();
} catch (URISyntaxException e) {
throw new MlflowClientException(
"Failed to construct request URI for get version download uri.",
e);
}
}
String makeSearchModelVersions(String searchFilter,
int maxResults,
List<String> orderBy,
String pageToken) {
try {
URIBuilder builder = new URIBuilder("model-versions/search")
.addParameter("max_results", Integer.toString(maxResults));
if (searchFilter != null && searchFilter != "") {
builder.addParameter("filter", searchFilter);
}
if (pageToken != null && pageToken != "") {
builder.addParameter("page_token", pageToken);
}
for( String order: orderBy) {
builder.addParameter("order_by", order);
}
return builder.build().toString();
} catch (URISyntaxException e) {
throw new MlflowClientException(
"Failed to construct request URI for search model version.",
e);
}
}
String toJson(MessageOrBuilder mb) {
return print(mb);
}
GetExperiment.Response toGetExperimentResponse(String json) {
GetExperiment.Response.Builder builder = GetExperiment.Response.newBuilder();
merge(json, builder);
return builder.build();
}
GetExperimentByName.Response toGetExperimentByNameResponse(String json) {
GetExperimentByName.Response.Builder builder = GetExperimentByName.Response.newBuilder();
merge(json, builder);
return builder.build();
}
SearchExperiments.Response toSearchExperimentsResponse(String json) {
SearchExperiments.Response.Builder builder = SearchExperiments.Response.newBuilder();
merge(json, builder);
return builder.build();
}
CreateExperiment.Response toCreateExperimentResponse(String json) {
CreateExperiment.Response.Builder builder = CreateExperiment.Response.newBuilder();
merge(json, builder);
return builder.build();
}
GetRun.Response toGetRunResponse(String json) {
GetRun.Response.Builder builder = GetRun.Response.newBuilder();
merge(json, builder);
return builder.build();
}
GetMetricHistory.Response toGetMetricHistoryResponse(String json) {
GetMetricHistory.Response.Builder builder = GetMetricHistory.Response.newBuilder();
merge(json, builder);
return builder.build();
}
CreateRun.Response toCreateRunResponse(String json) {
CreateRun.Response.Builder builder = CreateRun.Response.newBuilder();
merge(json, builder);
return builder.build();
}
SearchRuns.Response toSearchRunsResponse(String json) {
SearchRuns.Response.Builder builder = SearchRuns.Response.newBuilder();
merge(json, builder);
return builder.build();
}
GetLatestVersions.Response toGetLatestVersionsResponse(String json) {
GetLatestVersions.Response.Builder builder = GetLatestVersions.Response.newBuilder();
merge(json, builder);
return builder.build();
}
GetModelVersion.Response toGetModelVersionResponse(String json) {
GetModelVersion.Response.Builder builder = GetModelVersion.Response.newBuilder();
merge(json, builder);
return builder.build();
}
GetRegisteredModel.Response toGetRegisteredModelResponse(String json) {
GetRegisteredModel.Response.Builder builder = GetRegisteredModel.Response.newBuilder();
merge(json, builder);
return builder.build();
}
String toGetModelVersionDownloadUriResponse(String json) {
GetModelVersionDownloadUri.Response.Builder builder = GetModelVersionDownloadUri.Response
.newBuilder();
merge(json, builder);
return builder.getArtifactUri();
}
SearchModelVersions.Response toSearchModelVersionsResponse(String json) {
SearchModelVersions.Response.Builder builder = SearchModelVersions.Response.newBuilder();
merge(json, builder);
return builder.build();
}
private String print(MessageOrBuilder message) {
try {
return JsonFormat.printer().preservingProtoFieldNames().print(message);
} catch (InvalidProtocolBufferException e) {
throw new MlflowClientException("Failed to serialize message " + message, e);
}
}
private void merge(String json, com.google.protobuf.Message.Builder builder) {
try {
JsonFormat.parser().ignoringUnknownFields().merge(json, builder);
} catch (InvalidProtocolBufferException e) {
throw new MlflowClientException("Failed to serialize json " + json + " into " + builder, e);
}
}
}
@@ -0,0 +1,83 @@
package org.mlflow.tracking;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import org.mlflow.api.proto.ModelRegistry.*;
public class ModelVersionsPage implements Page<ModelVersion> {
private final String token;
private final List<ModelVersion> mvs;
private final MlflowClient client;
private final String searchFilter;
private final List<String> orderBy;
private final int maxResults;
/**
* Creates a fixed size page of ModelVersions.
*/
ModelVersionsPage(List<ModelVersion> mvs,
String token,
String searchFilter,
int maxResults,
List<String> orderBy,
MlflowClient client) {
this.mvs = Collections.unmodifiableList(mvs);
this.token = token;
this.searchFilter = searchFilter;
this.orderBy = orderBy;
this.maxResults = maxResults;
this.client = client;
}
/**
* @return The number of model versions in the page.
*/
public int getPageSize() {
return this.mvs.size();
}
/**
* @return True if a token for the next page exists and isn't empty. Otherwise returns false.
*/
public boolean hasNextPage() {
return this.token != null && this.token != "";
}
/**
* @return An optional with the token for the next page.
* Empty if the token doesn't exist or is empty.
*/
public Optional<String> getNextPageToken() {
if (this.hasNextPage()) {
return Optional.of(this.token);
} else {
return Optional.empty();
}
}
/**
* @return The next page of model versions matching the search criteria.
* If there are no more pages, an {@link org.mlflow.tracking.EmptyPage} will be returned.
*/
public Page<ModelVersion> getNextPage() {
if (this.hasNextPage()) {
return this.client.searchModelVersions(this.searchFilter,
this.maxResults,
this.orderBy,
this.token);
} else {
return new EmptyPage();
}
}
/**
* @return An iterable over the model versions in this page.
*/
public List<ModelVersion> getItems() {
return mvs;
}
}
@@ -0,0 +1,35 @@
package org.mlflow.tracking;
import java.lang.Iterable;
import java.util.Optional;
public interface Page<E> {
/**
* @return The number of elements in this page.
*/
public int getPageSize();
/**
* @return True if there are more pages that can be retrieved from the API.
*/
public boolean hasNextPage();
/**
* @return An Optional of the token string to get the next page.
* Empty if there is no next page.
*/
public Optional<String> getNextPageToken();
/**
* @return Retrieves the next Page object using the next page token,
* or returns an empty page if there are no more pages.
*/
public Page<E> getNextPage();
/**
* @return A List of the elements in this Page.
*/
public Iterable<E> getItems();
}
@@ -0,0 +1,92 @@
package org.mlflow.tracking;
import java.util.Collections;
import java.util.List;
import java.util.ArrayList;
import java.util.Optional;
import org.mlflow.api.proto.Service.*;
public class RunsPage implements Page<Run> {
private final String token;
private final List<Run> runs;
private final MlflowClient client;
private final List<String> experimentIds;
private final String searchFilter;
private final ViewType runViewType;
private final List<String> orderBy;
private final int maxResults;
/**
* Creates a fixed size page of Runs.
*/
RunsPage(List<Run> runs,
String token,
List<String> experimentIds,
String searchFilter,
ViewType runViewType,
int maxResults,
List<String> orderBy,
MlflowClient client) {
this.runs = Collections.unmodifiableList(runs);
this.token = token;
this.experimentIds = experimentIds;
this.searchFilter = searchFilter;
this.runViewType = runViewType;
this.orderBy = orderBy;
this.maxResults = maxResults;
this.client = client;
}
/**
* @return The number of runs in the page.
*/
public int getPageSize() {
return this.runs.size();
}
/**
* @return True if a token for the next page exists and isn't empty. Otherwise returns false.
*/
public boolean hasNextPage() {
return this.token != null && this.token != "";
}
/**
* @return An optional with the token for the next page.
* Empty if the token doesn't exist or is empty.
*/
public Optional<String> getNextPageToken() {
if (this.hasNextPage()) {
return Optional.of(this.token);
} else {
return Optional.empty();
}
}
/**
* @return The next page of runs matching the search criteria.
* If there are no more pages, an {@link org.mlflow.tracking.EmptyPage} will be returned.
*/
public Page<Run> getNextPage() {
if (this.hasNextPage()) {
return this.client.searchRuns(this.experimentIds,
this.searchFilter,
this.runViewType,
this.maxResults,
this.orderBy,
this.token);
} else {
return new EmptyPage();
}
}
/**
* @return An iterable over the runs in this page.
*/
public List<Run> getItems() {
return runs;
}
}
@@ -0,0 +1,73 @@
package org.mlflow.tracking.creds;
/** A static hostname and optional credentials to talk to an MLflow server. */
public class BasicMlflowHostCreds implements MlflowHostCreds, MlflowHostCredsProvider {
private String host;
private String username;
private String password;
private String token;
private boolean shouldIgnoreTlsVerification;
public BasicMlflowHostCreds(String host) {
this.host = host;
}
public BasicMlflowHostCreds(String host, String username, String password) {
this.host = host;
this.username = username;
this.password = password;
}
public BasicMlflowHostCreds(String host, String token) {
this.host = host;
this.token = token;
}
public BasicMlflowHostCreds(
String host,
String username,
String password,
String token,
boolean shouldIgnoreTlsVerification) {
this.host = host;
this.username = username;
this.password = password;
this.token = token;
this.shouldIgnoreTlsVerification = shouldIgnoreTlsVerification;
}
@Override
public String getHost() {
return host;
}
@Override
public String getUsername() {
return username;
}
@Override
public String getPassword() {
return password;
}
@Override
public String getToken() {
return token;
}
@Override
public boolean shouldIgnoreTlsVerification() {
return shouldIgnoreTlsVerification;
}
@Override
public MlflowHostCreds getHostCreds() {
return this;
}
@Override
public void refresh() {
// no-op
}
}
@@ -0,0 +1,103 @@
package org.mlflow.tracking.creds;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.nio.file.Paths;
import org.apache.commons.configuration2.INIConfiguration;
import org.apache.commons.configuration2.SubnodeConfiguration;
import org.apache.commons.configuration2.ex.ConfigurationException;
public class DatabricksConfigHostCredsProvider extends DatabricksHostCredsProvider {
private static final String CONFIG_FILE_ENV_VAR = "DATABRICKS_CONFIG_FILE";
private final String profile;
private DatabricksMlflowHostCreds hostCreds;
public DatabricksConfigHostCredsProvider(String profile) {
this.profile = profile;
}
public DatabricksConfigHostCredsProvider() {
this.profile = null;
}
private void loadConfigIfNecessary() {
if (hostCreds == null) {
reloadConfig();
}
}
private void reloadConfig() {
String basePath = System.getenv(CONFIG_FILE_ENV_VAR);
if (basePath == null) {
String userHome = System.getProperty("user.home");
basePath = Paths.get(userHome, ".databrickscfg").toString();
}
if (!new File(basePath).isFile()) {
throw new IllegalStateException("Could not find Databricks configuration file" +
" (" + basePath + "). Please run 'databricks configure' using the Databricks CLI.");
}
INIConfiguration ini;
try {
ini = new INIConfiguration();
try (FileReader reader = new FileReader(basePath)) {
ini.read(reader);
}
} catch (IOException | ConfigurationException e) {
throw new IllegalStateException("Failed to load databrickscfg file at " + basePath, e);
}
SubnodeConfiguration section;
if (profile == null) {
section = ini.getSection("DEFAULT");
if (section == null || section.isEmpty()) {
throw new IllegalStateException("Could not find 'DEFAULT' section within config file" +
" (" + basePath + "). Please run 'databricks configure' using the Databricks CLI.");
}
} else {
section = ini.getSection(profile);
if (section == null || section.isEmpty()) {
throw new IllegalStateException("Could not find '" + profile + "' section within config" +
" file (" + basePath + "). Please run 'databricks configure --profile " + profile + "'" +
" using the Databricks CLI.");
}
}
assert (section != null);
String host = section.getString("host");
String username = section.getString("username");
String password = section.getString("password");
String token = section.getString("token");
boolean insecure = "true".equalsIgnoreCase(section.getString("insecure", "false"));
if (host == null) {
throw new IllegalStateException("No 'host' configured within Databricks config file" +
" (" + basePath + "). Please run 'databricks configure' using the Databricks CLI.");
}
boolean hasValidUserPassword = username != null && password != null;
boolean hasValidToken = token != null;
if (!hasValidUserPassword && !hasValidToken) {
throw new IllegalStateException("No authentication configured within Databricks config file" +
" (" + basePath + "). Please run 'databricks configure' using the Databricks CLI.");
}
this.hostCreds = new DatabricksMlflowHostCreds(host, username, password, token, insecure);
}
@Override
public DatabricksMlflowHostCreds getHostCreds() {
loadConfigIfNecessary();
return hostCreds;
}
@Override
public void refresh() {
reloadConfig();
}
}
@@ -0,0 +1,49 @@
package org.mlflow.tracking.creds;
import java.util.Map;
import com.google.common.annotations.VisibleForTesting;
import org.mlflow.tracking.utils.DatabricksContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class DatabricksDynamicHostCredsProvider extends DatabricksHostCredsProvider {
private static final Logger logger = LoggerFactory.getLogger(
DatabricksDynamicHostCredsProvider.class);
private final Map<String, String> configProvider;
private DatabricksDynamicHostCredsProvider(Map<String, String> configProvider) {
this.configProvider = configProvider;
}
public static DatabricksDynamicHostCredsProvider createIfAvailable() {
return createIfAvailable(DatabricksContext.CONFIG_PROVIDER_CLASS_NAME);
}
@VisibleForTesting
static DatabricksDynamicHostCredsProvider createIfAvailable(String className) {
Map<String, String> configProvider =
DatabricksContext.getConfigProviderIfAvailable(className);
if (configProvider == null) {
return null;
}
return new DatabricksDynamicHostCredsProvider(configProvider);
}
@Override
public DatabricksMlflowHostCreds getHostCreds() {
return new DatabricksMlflowHostCreds(
configProvider.get("host"),
configProvider.get("username"),
configProvider.get("password"),
configProvider.get("token"),
"true".equals(configProvider.get("shouldIgnoreTlsVerification"))
);
}
@Override
public void refresh() {
// no-op
}
}
@@ -0,0 +1,11 @@
package org.mlflow.tracking.creds;
abstract class DatabricksHostCredsProvider implements MlflowHostCredsProvider {
@Override
public abstract DatabricksMlflowHostCreds getHostCreds();
@Override
public abstract void refresh();
}
@@ -0,0 +1,22 @@
package org.mlflow.tracking.creds;
/** Credentials to talk to a Databricks-hosted MLflow server. */
public final class DatabricksMlflowHostCreds extends BasicMlflowHostCreds {
public DatabricksMlflowHostCreds(String host, String username, String password) {
super(host, username, password);
}
public DatabricksMlflowHostCreds(String host, String token) {
super(host, token);
}
public DatabricksMlflowHostCreds(
String host,
String username,
String password,
String token,
boolean shouldIgnoreTlsVerification) {
super(host, username, password, token, shouldIgnoreTlsVerification);
}
}
@@ -0,0 +1,48 @@
package org.mlflow.tracking.creds;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.mlflow.tracking.MlflowClientException;
public class HostCredsProviderChain implements MlflowHostCredsProvider {
private static final Logger logger = LoggerFactory.getLogger(HostCredsProviderChain.class);
private final List<MlflowHostCredsProvider> hostCredsProviders = new ArrayList<>();
public HostCredsProviderChain(MlflowHostCredsProvider... hostCredsProviders) {
this.hostCredsProviders.addAll(Arrays.asList(hostCredsProviders));
}
@Override
public MlflowHostCreds getHostCreds() {
List<String> exceptionMessages = new ArrayList<>();
for (MlflowHostCredsProvider provider : hostCredsProviders) {
try {
MlflowHostCreds hostCreds = provider.getHostCreds();
if (hostCreds != null && hostCreds.getHost() != null) {
logger.debug("Loading credentials from " + provider.toString());
return hostCreds;
}
} catch (Exception e) {
String message = provider + ": " + e.getMessage();
logger.debug("Unable to load credentials from " + message);
exceptionMessages.add(message);
}
}
throw new MlflowClientException("Unable to load MLflow Host/Credentials from any provider in" +
" the chain: " + exceptionMessages);
}
@Override
public void refresh() {
for (MlflowHostCredsProvider provider : hostCredsProviders) {
provider.refresh();
}
}
}
@@ -0,0 +1,33 @@
package org.mlflow.tracking.creds;
/**
* Provides a hostname and optional authentication for talking to an MLflow server.
*/
public interface MlflowHostCreds {
/** Hostname (e.g., http://localhost:5000) to MLflow server. */
String getHost();
/**
* Username to use with Basic authentication when talking to server.
* If this is specified, password must also be specified.
*/
String getUsername();
/**
* Password to use with Basic authentication when talking to server.
* If this is specified, username must also be specified.
*/
String getPassword();
/**
* Token to use with Bearer authentication when talking to server.
* If provided, user/password authentication will be ignored.
*/
String getToken();
/**
* If true, we will not verify the server's hostname or TLS certificate.
* This is useful for certain testing situations, but should never be true in production.
*/
boolean shouldIgnoreTlsVerification();
}
@@ -0,0 +1,11 @@
package org.mlflow.tracking.creds;
/** Provides a dynamic, refreshable set of MlflowHostCreds. */
public interface MlflowHostCredsProvider {
/** Returns a valid MlflowHostCreds. This may be cached. */
MlflowHostCreds getHostCreds();
/** Refreshes the underlying credentials. May be a no-op. */
void refresh();
}
@@ -0,0 +1,2 @@
/** Support for custom tracking service discovery and authentication. */
package org.mlflow.tracking.creds;
@@ -0,0 +1,5 @@
/**
* MLflow Tracking provides a Java CRUD interface to MLflow Experiments and Runs --
* to create and log to MLflow runs, use the {@link org.mlflow.tracking.MlflowContext} interface.
*/
package org.mlflow.tracking;
@@ -0,0 +1,61 @@
package org.mlflow.tracking.samples;
import com.google.common.collect.ImmutableMap;
import org.mlflow.tracking.ActiveRun;
import org.mlflow.tracking.MlflowContext;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class FluentExample {
public static void main(String[] args) {
MlflowContext mlflow = new MlflowContext();
ExecutorService executor = Executors.newFixedThreadPool(10);
// Vanilla usage
{
ActiveRun run = mlflow.startRun("run");
run.logParam("alpha", "0.0");
run.logMetric("MSE", 0.0);
run.setTags(ImmutableMap.of(
"company", "databricks",
"org", "engineering"
));
run.endRun();
}
// Lambda usage
{
mlflow.withActiveRun("lambda run", (activeRun -> {
activeRun.logParam("layers", "4");
// Perform training code
}));
}
// Log one parent run and 5 children run
{
ActiveRun run = mlflow.startRun("parent run");
for (int i = 0; i <= 5; i++) {
ActiveRun childRun = mlflow.startRun("child run", run.getId());
childRun.logParam("iteration", Integer.toString(i));
childRun.endRun();
}
run.endRun();
}
// Log one parent run and 5 children run (multithreaded)
{
ActiveRun run = mlflow.startRun("parent run (multithreaded)");
for (int i = 0; i <= 5; i++) {
final int i0 = i;
executor.submit(() -> {
ActiveRun childRun = mlflow.startRun("child run (multithreaded)", run.getId());
childRun.logParam("iteration", Integer.toString(i0));
childRun.endRun();
});
}
run.endRun();
}
executor.shutdown();
mlflow.getClient().close();
}
}
@@ -0,0 +1,78 @@
package org.mlflow.tracking.samples;
import java.util.List;
import java.util.Optional;
import org.mlflow.api.proto.Service.*;
import org.mlflow.tracking.MlflowClient;
/**
* This is an example application which uses the MLflow Tracking API to create and manage
* experiments and runs.
*/
public class QuickStartDriver {
public static void main(String[] args) throws Exception {
(new QuickStartDriver()).process(args);
}
void process(String[] args) throws Exception {
MlflowClient client;
if (args.length < 1) {
client = new MlflowClient();
} else {
client = new MlflowClient(args[0]);
}
System.out.println("====== createExperiment");
String expName = "Exp_" + System.currentTimeMillis();
String expId = client.createExperiment(expName);
System.out.println("createExperiment: expId=" + expId);
System.out.println("====== getExperiment");
Experiment exp = client.getExperiment(expId);
System.out.println("getExperiment: " + exp);
System.out.println("====== searchExperiments");
List<Experiment> exps = client.searchExperiments().getItems();
System.out.println("#experiments: " + exps.size());
exps.forEach(e -> System.out.println(" Exp: " + e));
createRun(client, expId);
System.out.println("====== getExperiment again");
Experiment exp2 = client.getExperiment(expId);
System.out.println("getExperiment: " + exp2);
System.out.println("====== getExperiment by name");
Optional<Experiment> exp3 = client.getExperimentByName(expName);
System.out.println("getExperimentByName: " + exp3);
client.close();
}
void createRun(MlflowClient client, String expId) {
System.out.println("====== createRun");
// Create run
String sourceFile = "MyFile.java";
RunInfo runCreated = client.createRun(expId);
System.out.println("CreateRun: " + runCreated);
String runId = runCreated.getRunUuid();
// Log parameters
client.logParam(runId, "min_samples_leaf", "2");
client.logParam(runId, "max_depth", "3");
// Log metrics
client.logMetric(runId, "auc", 2.12F);
client.logMetric(runId, "accuracy_score", 3.12F);
client.logMetric(runId, "zero_one_loss", 4.12F);
// Update finished run
client.setTerminated(runId, RunStatus.FINISHED);
// Get run details
Run run = client.getRun(runId);
System.out.println("GetRun: " + run);
}
}
@@ -0,0 +1,131 @@
package org.mlflow.tracking.utils;
import com.google.common.annotations.VisibleForTesting;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashMap;
import java.util.Map;
public class DatabricksContext {
public static final String CONFIG_PROVIDER_CLASS_NAME =
"com.databricks.config.DatabricksClientSettingsProvider";
private static final Logger logger = LoggerFactory.getLogger(
DatabricksContext.class);
private final Map<String, String> configProvider;
private DatabricksContext(Map<String, String> configProvider) {
this.configProvider = configProvider;
}
public static DatabricksContext createIfAvailable() {
return createIfAvailable(CONFIG_PROVIDER_CLASS_NAME);
}
@VisibleForTesting
static DatabricksContext createIfAvailable(String className) {
Map<String, String> configProvider = getConfigProviderIfAvailable(className);
if (configProvider == null) {
return null;
}
return new DatabricksContext(configProvider);
}
public Map<String, String> getTags() {
if (isInDatabricksNotebook()) {
return getTagsForDatabricksNotebook();
} else if (isInDatabricksJob()) {
return getTagsForDatabricksJob();
} else {
return new HashMap<>();
}
}
public boolean isInDatabricksNotebook() {
return configProvider.get("notebookId") != null;
}
/**
* Should only be called if isInDatabricksNotebook() is true.
*/
private Map<String, String> getTagsForDatabricksNotebook() {
Map<String, String> tagsForNotebook = new HashMap<>();
String notebookId = getNotebookId();
if (notebookId != null) {
tagsForNotebook.put(MlflowTagConstants.DATABRICKS_NOTEBOOK_ID, notebookId);
}
String notebookPath = configProvider.get("notebookPath");
if (notebookPath != null) {
tagsForNotebook.put(MlflowTagConstants.SOURCE_NAME, notebookPath);
tagsForNotebook.put(MlflowTagConstants.DATABRICKS_NOTEBOOK_PATH, notebookPath);
tagsForNotebook.put(MlflowTagConstants.SOURCE_TYPE, "NOTEBOOK");
}
String webappUrl = configProvider.get("host");
if (webappUrl != null) {
tagsForNotebook.put(MlflowTagConstants.DATABRICKS_WEBAPP_URL, webappUrl);
}
return tagsForNotebook;
}
/**
* Should only be called if isInDatabricksNotebook() is true.
*/
public String getNotebookId() {
if (!isInDatabricksNotebook()) {
throw new IllegalArgumentException(
"getNotebookId() should not be called when isInDatabricksNotebook() is false"
);
}
return configProvider.get("notebookId");
}
public String getNotebookPath() {
if (!isInDatabricksNotebook()) {
throw new IllegalArgumentException(
"getNotebookPath() should not be called when isInDatabricksNotebook() is false"
);
}
return configProvider.get("notebookPath");
}
private boolean isInDatabricksJob() {
return configProvider.get("jobId") != null;
}
/**
* Should only be called if isInDatabricksJob() is true.
*/
private Map<String, String> getTagsForDatabricksJob() {
Map<String, String> tagsForJob = new HashMap<>();
String jobId = configProvider.get("jobId");
String jobRunId = configProvider.get("jobRunId");
String jobType = configProvider.get("jobType");
String webappUrl = configProvider.get("host");
if (jobId != null && jobRunId != null) {
tagsForJob.put(MlflowTagConstants.DATABRICKS_JOB_ID, jobId);
tagsForJob.put(MlflowTagConstants.DATABRICKS_JOB_RUN_ID, jobRunId);
tagsForJob.put(MlflowTagConstants.SOURCE_TYPE, "JOB");
tagsForJob.put(MlflowTagConstants.SOURCE_NAME,
String.format("jobs/%s/run/%s", jobId, jobRunId));
}
if (jobType != null) {
tagsForJob.put(MlflowTagConstants.DATABRICKS_JOB_TYPE, jobType);
}
if (webappUrl != null) {
tagsForJob.put(MlflowTagConstants.DATABRICKS_WEBAPP_URL, webappUrl);
}
return tagsForJob;
}
public static Map<String, String> getConfigProviderIfAvailable(String className) {
try {
Class<?> cls = Class.forName(className);
return (Map<String, String>) cls.newInstance();
} catch (ClassNotFoundException e) {
return null;
} catch (IllegalAccessException | InstantiationException e) {
logger.warn("Found but failed to invoke dynamic config provider", e);
return null;
}
}
}
@@ -0,0 +1,19 @@
package org.mlflow.tracking.utils;
public class MlflowTagConstants {
public static final String PARENT_RUN_ID = "mlflow.parentRunId";
public static final String RUN_NAME = "mlflow.runName";
public static final String USER = "mlflow.user";
public static final String SOURCE_TYPE = "mlflow.source.type";
public static final String SOURCE_NAME = "mlflow.source.name";
public static final String DATABRICKS_NOTEBOOK_ID = "mlflow.databricks.notebookID";
public static final String DATABRICKS_NOTEBOOK_PATH = "mlflow.databricks.notebookPath";
// The JOB_ID, JOB_RUN_ID, and JOB_TYPE tags are used for automatically recording Job
// information when MLflow Tracking APIs are used within a Databricks Job
public static final String DATABRICKS_JOB_ID = "mlflow.databricks.jobID";
public static final String DATABRICKS_JOB_RUN_ID = "mlflow.databricks.jobRunID";
public static final String DATABRICKS_JOB_TYPE = "mlflow.databricks.jobType";
public static final String DATABRICKS_WEBAPP_URL = "mlflow.databricks.webappURL";
public static final String MLFLOW_EXPERIMENT_SOURCE_TYPE = "mlflow.experiment.sourceType";
public static final String MLFLOW_EXPERIMENT_SOURCE_ID = "mlflow.experiment.sourceId";
}
@@ -0,0 +1,5 @@
log4j.rootLogger=info, console
log4j.logger.com.databricks=info
log4j.appender.console=org.apache.log4j.ConsoleAppender
log4j.appender.console.layout=org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=%p %c.%M:%L: %m%n