Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
exports org.cloudfoundry.multiapps.controller.persistence.model;
exports org.cloudfoundry.multiapps.controller.persistence.model.adapter;
exports org.cloudfoundry.multiapps.controller.persistence.model.filters;
exports org.cloudfoundry.multiapps.controller.persistence.monitoring;
exports org.cloudfoundry.multiapps.controller.persistence.query;
exports org.cloudfoundry.multiapps.controller.persistence.query.criteria;
exports org.cloudfoundry.multiapps.controller.persistence.query.impl;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,10 @@ public final class Messages {

// INFO log messages:
public static final String DELETING_FILES_WITHOUT_CONTENT_WITH_IDS_0 = "Deleting files without content with ids: {0}";
public static final String TIME_ELAPSED_FOR_JCLOUDS_OS_UPLOAD_0_IN_MILLIS = "Time elapsed for JClouds object store upload: {0} in millis";
public static final String TIME_ELAPSED_FOR_GCP_OS_UPLOAD_0_IN_MILLIS = "Time elapsed for GCP object store upload: {0} in millis";
public static final String TIME_ELAPSED_FOR_AZURE_OS_UPLOAD_0_IN_MILLIS = "Time elapsed for Azure object store upload: {0} in millis";
public static final String TIME_ELAPSED_FOR_AWS_OS_UPLOAD_0_IN_MILLIS = "Time elapsed for AWS object store upload: {0} in millis";

// DEBUG log messages:
public static final String STORED_FILE_0 = "Stored file: \"{0}\"";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package org.cloudfoundry.multiapps.controller.persistence.monitoring;

import jakarta.inject.Named;

import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.LongAdder;

@Named
public class UploadDurationTracker {
Comment thread
IvanBorislavovDimitrov marked this conversation as resolved.

private final UploadPathStatistics appBinaryStatistics = new UploadPathStatistics();

private final UploadPathStatistics objectStoreStatistics = new UploadPathStatistics();

public void recordAppBinaryUpload(long durationMillis, boolean timedOut) {
appBinaryStatistics.record(durationMillis, timedOut);
}

public void recordObjectStoreUpload(long durationMillis, boolean timedOut) {
objectStoreStatistics.record(durationMillis, timedOut);
}

public void recordAppBinaryUploadRejection() {
appBinaryStatistics.recordRejection();
}

public UploadPathStatistics getAppBinaryStatistics() {
return this.appBinaryStatistics;
}

public UploadPathStatistics getObjectStoreStatistics() {
return this.objectStoreStatistics;
}

public static final class UploadPathStatistics {

private final LongAdder total = new LongAdder();

private final LongAdder timeouts = new LongAdder();

private final LongAdder sumDuration = new LongAdder();

private final LongAdder rejections = new LongAdder();

private final AtomicLong rejectionsInWindow = new AtomicLong(0);

private final AtomicLong maxDuration = new AtomicLong(0);

private final AtomicLong timeoutsInWindow = new AtomicLong(0);

public void record(long durationMillis, boolean timedOut) {

Check warning on line 51 in multiapps-controller-persistence/src/main/java/org/cloudfoundry/multiapps/controller/persistence/monitoring/UploadDurationTracker.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Rename this method to not match a restricted identifier.

See more on https://sonarcloud.io/project/issues?id=cloudfoundry_multiapps-controller&issues=AZ-3gucUCp9xIDHFHEoS&open=AZ-3gucUCp9xIDHFHEoS&pullRequest=1885
long duration = Math.max(0, durationMillis);
total.increment();

if (timedOut) {
timeouts.increment();
timeoutsInWindow.incrementAndGet();
}

sumDuration.add(duration);
maxDuration.accumulateAndGet(duration, Math::max);
}

public void recordRejection() {
rejections.increment();
rejectionsInWindow.incrementAndGet();
}

public long totalCount() {
return total.sum();
}

public long timeoutCount() {
return timeouts.sum();
}

public long maxDurationMs() {
return maxDuration.getAndSet(0);
Comment thread
IvanBorislavovDimitrov marked this conversation as resolved.
}

public long sumDurationMs() {
return sumDuration.sum();
}

public long timeoutsInWindow() {
return timeoutsInWindow.getAndSet(0);
Comment thread
IvanBorislavovDimitrov marked this conversation as resolved.
}

public long rejectionCount() {
return rejections.sum();
}

public long rejectionsInWindow() {
return rejectionsInWindow.getAndSet(0);
}

}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package org.cloudfoundry.multiapps.controller.persistence.monitoring;

import com.google.cloud.storage.StorageException;
import io.netty.handler.timeout.ReadTimeoutException;

import java.net.SocketTimeoutException;
import java.util.concurrent.TimeoutException;

public class UploadTimeoutMatcher {

private UploadTimeoutMatcher() {

}

public static boolean isUploadTimeoutException(Throwable throwable) {
if (throwable == null) {
return false;
}

Throwable cause = throwable.getCause();
while (cause != null) {
if (cause instanceof SocketTimeoutException || cause instanceof TimeoutException || cause instanceof ReadTimeoutException || (
cause instanceof StorageException

Check warning on line 23 in multiapps-controller-persistence/src/main/java/org/cloudfoundry/multiapps/controller/persistence/monitoring/UploadTimeoutMatcher.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Replace this instanceof check and cast with 'instanceof StorageException storageexception'

See more on https://sonarcloud.io/project/issues?id=cloudfoundry_multiapps-controller&issues=AZ-3guccCp9xIDHFHEoT&open=AZ-3guccCp9xIDHFHEoT&pullRequest=1885
&& ((StorageException) cause).getCode() == 504)) {
return true;
}
cause = cause.getCause();
}
return false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import java.io.InputStream;
import java.net.URI;
import java.text.MessageFormat;
import java.time.Duration;
import java.time.Instant;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashSet;
Expand All @@ -16,8 +18,11 @@
import java.util.concurrent.Executors;
import java.util.function.BiPredicate;
import java.util.function.Consumer;

import org.cloudfoundry.multiapps.controller.persistence.Messages;
import org.cloudfoundry.multiapps.controller.persistence.model.FileEntry;
import org.cloudfoundry.multiapps.controller.persistence.monitoring.UploadDurationTracker;
import org.cloudfoundry.multiapps.controller.persistence.monitoring.UploadTimeoutMatcher;
import org.cloudfoundry.multiapps.controller.persistence.util.ObjectStoreConstants;
import org.cloudfoundry.multiapps.controller.persistence.util.ObjectStoreFilter;
import org.cloudfoundry.multiapps.controller.persistence.util.ObjectStoreMapper;
Expand Down Expand Up @@ -57,10 +62,12 @@

private final S3Client s3Client;
private final String bucketName;
private final UploadDurationTracker uploadDurationTracker;

public AwsS3ObjectStoreFileStorage(Map<String, Object> credentials) {
public AwsS3ObjectStoreFileStorage(Map<String, Object> credentials, UploadDurationTracker uploadDurationTracker) {
this.bucketName = (String) credentials.get(CredentialKeys.BUCKET);
this.s3Client = createS3Client(credentials);
this.uploadDurationTracker = uploadDurationTracker;
}

protected S3Client createS3Client(Map<String, Object> credentials) {
Expand Down Expand Up @@ -103,6 +110,7 @@

@Override
public void addFile(FileEntry fileEntry, InputStream content) throws FileStorageException {
Instant startTime = Instant.now();
long fileSize = fileEntry.getSize()
.longValue();
PutObjectRequest request = PutObjectRequest.builder()
Expand All @@ -115,8 +123,12 @@
try {
s3Client.putObject(request, RequestBody.fromInputStream(new BufferedInputStream(content), fileSize));
LOGGER.debug(MessageFormat.format(Messages.STORED_FILE_0_WITH_SIZE_1, fileEntry.getId(), fileSize));
uploadDurationTracker.recordObjectStoreUpload(getElapsedTimeInMillis(startTime), false);
LOGGER.info(MessageFormat.format(Messages.TIME_ELAPSED_FOR_AWS_OS_UPLOAD_0_IN_MILLIS, getElapsedTimeInMillis(startTime)));

Check warning on line 127 in multiapps-controller-persistence/src/main/java/org/cloudfoundry/multiapps/controller/persistence/services/AwsS3ObjectStoreFileStorage.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Invoke method(s) only conditionally.

See more on https://sonarcloud.io/project/issues?id=cloudfoundry_multiapps-controller&issues=AZ-3gucECp9xIDHFHEoN&open=AZ-3gucECp9xIDHFHEoN&pullRequest=1885
} catch (Exception e) {
LOGGER.error(MessageFormat.format(Messages.S3_UPLOAD_FAILED_FILE_0_SIZE_1, fileEntry.getName(), fileSize, e));
uploadDurationTracker.recordObjectStoreUpload(getElapsedTimeInMillis(startTime),
UploadTimeoutMatcher.isUploadTimeoutException(e));
throw new FileStorageException(MessageFormat.format(Messages.UPLOAD_OF_FILE_WITH_NAMESPACE_FAILED, fileEntry.getName(),
fileEntry.getNamespace()), e);
}
Expand Down Expand Up @@ -183,14 +195,15 @@

@Override
public void deleteFilesBySpaceAndNamespace(String space, String namespace) {
int deletedFiles = deleteByFilterAndCount((key, metadata) -> ObjectStoreFilter.filterBySpaceAndNamespace(metadata, space, namespace));
int deletedFiles = deleteByFilterAndCount(
(key, metadata) -> ObjectStoreFilter.filterBySpaceAndNamespace(metadata, space, namespace));
LOGGER.debug(MessageFormat.format(Messages.DELETED_0_FILES_WITH_SPACE_1_AND_NAMESPACE_2, deletedFiles, space, namespace));
}

@Override
public int deleteFilesModifiedBefore(LocalDateTime modificationTime) {
int deletedFiles = deleteByFilterAndCount((key, metadata) -> ObjectStoreFilter.filterByModificationTime(metadata, key,
modificationTime));
modificationTime));
LOGGER.debug(MessageFormat.format(Messages.DELETED_0_FILES_MODIFIED_BEFORE_1, deletedFiles, modificationTime));
return deletedFiles;
}
Expand Down Expand Up @@ -319,6 +332,11 @@
s3Client.close();
}

private long getElapsedTimeInMillis(Instant startTime) {
return Duration.between(startTime, Instant.now())
.toMillis();
}

private static final class CredentialKeys {
static final String ACCESS_KEY_ID = "access_key_id";
static final String SECRET_ACCESS_KEY = "secret_access_key";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
import java.net.MalformedURLException;
import java.net.URL;
import java.text.MessageFormat;
import java.time.Duration;
import java.time.Instant;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
Expand All @@ -29,6 +31,8 @@
import com.azure.storage.blob.options.BlobParallelUploadOptions;
import org.cloudfoundry.multiapps.controller.persistence.Messages;
import org.cloudfoundry.multiapps.controller.persistence.model.FileEntry;
import org.cloudfoundry.multiapps.controller.persistence.monitoring.UploadDurationTracker;
import org.cloudfoundry.multiapps.controller.persistence.monitoring.UploadTimeoutMatcher;
import org.cloudfoundry.multiapps.controller.persistence.util.ObjectStoreConstants;
import org.cloudfoundry.multiapps.controller.persistence.util.ObjectStoreFilter;
import org.cloudfoundry.multiapps.controller.persistence.util.ObjectStoreMapper;
Expand All @@ -46,15 +50,18 @@
private static final int MAX_CONCURRENCY = 5;
private final HttpClient httpClient;
private final BlobContainerClient containerClient;
private final UploadDurationTracker uploadDurationTracker;

public AzureObjectStoreFileStorage(Map<String, Object> credentials) {
public AzureObjectStoreFileStorage(Map<String, Object> credentials, UploadDurationTracker uploadDurationTracker) {
this.httpClient = new JdkHttpClientBuilder().build();
this.containerClient = createContainerClient(credentials);
this.uploadDurationTracker = uploadDurationTracker;
}

@Override
public void addFile(FileEntry fileEntry, InputStream content) throws FileStorageException {
BlobClient blobClient = containerClient.getBlobClient(fileEntry.getId());
Instant startTime = Instant.now();
try {
ParallelTransferOptions pto = new ParallelTransferOptions().setMaxSingleUploadSizeLong(MAX_SINGLE_UPLOAD_SIZE)
.setMaxConcurrency(MAX_CONCURRENCY)
Expand All @@ -67,10 +74,14 @@
blobClient.uploadWithResponse(blobParallelUploadOptions,
ObjectStoreConstants.AZURE_OBJECT_STORE_TOTAL_TIMEOUT_CONFIG_IN_MINUTES, null);
LOGGER.debug(MessageFormat.format(Messages.STORED_FILE_0_WITH_SIZE_1, fileEntry.getId(), fileEntry.getSize()
.longValue()));
.longValue()));
} catch (BlobStorageException e) {
uploadDurationTracker.recordObjectStoreUpload(getElapsedTimeInMillis(startTime),
UploadTimeoutMatcher.isUploadTimeoutException(e));
throw new FileStorageException(e);
}
uploadDurationTracker.recordObjectStoreUpload(getElapsedTimeInMillis(startTime), false);
LOGGER.info(MessageFormat.format(Messages.TIME_ELAPSED_FOR_AZURE_OS_UPLOAD_0_IN_MILLIS, getElapsedTimeInMillis(startTime)));

Check warning on line 84 in multiapps-controller-persistence/src/main/java/org/cloudfoundry/multiapps/controller/persistence/services/AzureObjectStoreFileStorage.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Invoke method(s) only conditionally.

See more on https://sonarcloud.io/project/issues?id=cloudfoundry_multiapps-controller&issues=AZ-3guafCp9xIDHFHEoB&open=AZ-3guafCp9xIDHFHEoB&pullRequest=1885
}

@Override
Expand Down Expand Up @@ -218,6 +229,11 @@
return deletedBlobsResult;
}

private long getElapsedTimeInMillis(Instant startTime) {
return Duration.between(startTime, Instant.now())
.toMillis();
}

protected Set<String> getEntryNames(Predicate<? super BlobItem> filter) {
BlobListDetails blobListDetails = new BlobListDetails().setRetrieveMetadata(true);
ListBlobsOptions listBlobsOptions = new ListBlobsOptions().setDetails(blobListDetails);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
import com.google.cloud.storage.StorageRetryStrategy;
import org.cloudfoundry.multiapps.controller.persistence.Messages;
import org.cloudfoundry.multiapps.controller.persistence.model.FileEntry;
import org.cloudfoundry.multiapps.controller.persistence.monitoring.UploadDurationTracker;
import org.cloudfoundry.multiapps.controller.persistence.monitoring.UploadTimeoutMatcher;
import org.cloudfoundry.multiapps.controller.persistence.util.ObjectStoreConstants;
import org.cloudfoundry.multiapps.controller.persistence.util.ObjectStoreFilter;
import org.cloudfoundry.multiapps.controller.persistence.util.ObjectStoreMapper;
Expand All @@ -25,6 +27,8 @@
import java.io.InputStream;
import java.nio.channels.Channels;
import java.text.MessageFormat;
import java.time.Duration;
import java.time.Instant;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Base64;
Expand All @@ -41,10 +45,12 @@

private final String bucketName;
private final Storage storage;
private final UploadDurationTracker uploadDurationTracker;

public GcpObjectStoreFileStorage(Map<String, Object> credentials) {
public GcpObjectStoreFileStorage(Map<String, Object> credentials, UploadDurationTracker uploadDurationTracker) {
this.bucketName = (String) credentials.get(CredentialKeys.BUCKET);
this.storage = createObjectStoreStorage(credentials);
this.uploadDurationTracker = uploadDurationTracker;
}

protected Storage createObjectStoreStorage(Map<String, Object> credentials) {
Expand Down Expand Up @@ -92,11 +98,16 @@
}

private void putBlob(BlobInfo blobInfo, InputStream content) throws FileStorageException {
Instant startTime = Instant.now();
try {
storage.createFrom(blobInfo, content);
} catch (IOException | StorageException e) {
uploadDurationTracker.recordObjectStoreUpload(getElapsedTimeInMillis(startTime),
UploadTimeoutMatcher.isUploadTimeoutException(e));
throw new FileStorageException(e);
}
uploadDurationTracker.recordObjectStoreUpload(getElapsedTimeInMillis(startTime), false);
LOGGER.info(MessageFormat.format(Messages.TIME_ELAPSED_FOR_GCP_OS_UPLOAD_0_IN_MILLIS, getElapsedTimeInMillis(startTime)));

Check warning on line 110 in multiapps-controller-persistence/src/main/java/org/cloudfoundry/multiapps/controller/persistence/services/GcpObjectStoreFileStorage.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Invoke method(s) only conditionally.

See more on https://sonarcloud.io/project/issues?id=cloudfoundry_multiapps-controller&issues=AZ-3gua9Cp9xIDHFHEoF&open=AZ-3gua9Cp9xIDHFHEoF&pullRequest=1885
}

@Override
Expand Down Expand Up @@ -283,4 +294,10 @@
static final String BASE_64_ENCODED_PRIVATE_KEY_DATA = "base64EncodedPrivateKeyData";
static final String BUCKET = "bucket";
}

private long getElapsedTimeInMillis(Instant startTime) {
return Duration.between(startTime, Instant.now())
.toMillis();
}

}
Loading
Loading