com.microsoft.azure.documentdb.ClientSideRequestStatistics Maven / Gradle / Ivy
package com.microsoft.azure.documentdb;
import java.net.URI;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import org.apache.commons.lang3.StringUtils;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.joda.time.Period;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
import com.microsoft.azure.documentdb.internal.DocumentServiceRequest;
import com.microsoft.azure.documentdb.internal.OperationType;
import com.microsoft.azure.documentdb.internal.ResourceType;
import com.microsoft.azure.documentdb.internal.directconnectivity.StoreReadResult;
import com.microsoft.azure.documentdb.internal.Utils;
public class ClientSideRequestStatistics {
private final static int MAX_SUPPLEMENTAL_REQUESTS_FOR_TO_STRING = 10;
private final static DateTimeFormatter responseTimeFormatter = DateTimeFormat.forPattern("dd MMM yyyy HH:mm:ss.SSS").withLocale(Locale.US);
private DateTime requestStartTimeUTC;
private DateTime requestEndTimeUTC;
private List responseStatisticsList;
private List supplementalResponseStatisticsList;
private Map addressResolutionStatistics;
private List contactedReplicas;
private Set failedReplicas;
private Set regionsContacted;
public ClientSideRequestStatistics() {
this.requestStartTimeUTC = DateTime.now(DateTimeZone.UTC);
this.requestEndTimeUTC = DateTime.now(DateTimeZone.UTC);
this.responseStatisticsList = new ArrayList<>();
this.supplementalResponseStatisticsList = new ArrayList<>();
this.addressResolutionStatistics = new HashMap<>();
this.contactedReplicas = new ArrayList<>();
this.failedReplicas = new HashSet<>();
this.regionsContacted = new HashSet<>();
}
// NOTE: The start and end time need to be in UTC time zone,
// If not, during the Daylight savings time change, the period might return a difference of an extra hour.
public Period getRequestLatency() {
return new Period(requestStartTimeUTC, requestEndTimeUTC);
}
private boolean isCPUOverloaded() {
// TODO: Couldn't find TransportException in sync sdk, which is used to calculate the isClientCPUOverloaded.
return false;
}
public void recordResponse(DocumentServiceRequest documentServiceRequest, StoreReadResult storeReadResult, DateTime requestStartTime) {
DateTime responseTime = DateTime.now(DateTimeZone.UTC);
StoreResponseStatistics storeResponseStatistics = new StoreResponseStatistics();
storeResponseStatistics.requestStartTimeUTC = requestStartTime;
storeResponseStatistics.requestResponseTimeUTC = responseTime;
storeResponseStatistics.storeReadResult = storeReadResult;
storeResponseStatistics.requestOperationType = documentServiceRequest.getOperationType();
storeResponseStatistics.requestResourceType = documentServiceRequest.getResourceType();
URI locationEndPoint = documentServiceRequest.getLocationEndpointToRoute();
synchronized (this) {
if (responseTime.isAfter(this.requestEndTimeUTC)) {
this.requestEndTimeUTC = responseTime;
}
if (locationEndPoint != null) {
this.regionsContacted.add(locationEndPoint);
}
if (storeResponseStatistics.requestOperationType == OperationType.Head ||
storeResponseStatistics.requestOperationType == OperationType.HeadFeed) {
this.supplementalResponseStatisticsList.add(storeResponseStatistics);
} else {
this.responseStatisticsList.add(storeResponseStatistics);
}
}
}
public String recordAddressResolutionStart(URI targetEndpoint) {
String identifier = Utils.getTimeBasedRandomUUID().toString();
AddressResolutionStatistics resolutionStatistics = new AddressResolutionStatistics();
resolutionStatistics.startTimeUTC = DateTime.now(DateTimeZone.UTC);
resolutionStatistics.endTimeUTC = new DateTime(Long.MAX_VALUE);
resolutionStatistics.targetEndpoint = targetEndpoint == null ? "" : targetEndpoint.toString();
synchronized (this) {
this.addressResolutionStatistics.put(identifier, resolutionStatistics);
}
return identifier;
}
public void recordAddressResolutionEnd(String identifier) {
if (StringUtils.isEmpty(identifier)) {
return;
}
DateTime responseTime = DateTime.now(DateTimeZone.UTC);
synchronized (this) {
if (!this.addressResolutionStatistics.containsKey(identifier)) {
throw new IllegalArgumentException("Identifier " + identifier + " does not exist. Please call start before calling end");
}
if (responseTime.isAfter(this.requestEndTimeUTC)) {
this.requestEndTimeUTC = responseTime;
}
AddressResolutionStatistics resolutionStatistics = this.addressResolutionStatistics.get(identifier);
resolutionStatistics.endTimeUTC = responseTime;
}
}
@Override
public String toString() {
StringBuilder stringBuilder = new StringBuilder();
// need to lock in case of concurrent operations. this should be extremely rare since toString()
// should only be called at the end of request.
synchronized (this) {
// first trace request start time, as well as total non-head/headfeed requests made.
stringBuilder.append("RequestStartTimeUTC: ")
.append("\"").append(this.requestStartTimeUTC.toString(responseTimeFormatter)).append("\"")
.append(", ")
.append("RequestEndTimeUTC: ")
.append("\"").append(this.requestEndTimeUTC.toString(responseTimeFormatter)).append("\"")
.append(", ")
.append("Number of regions attempted: ")
.append(this.regionsContacted.isEmpty() ? 1 : this.regionsContacted.size())
.append(System.lineSeparator());
// take all responses here - this should be limited in number and each one contains relevant information.
for (StoreResponseStatistics storeResponseStatistics : this.responseStatisticsList) {
stringBuilder.append(storeResponseStatistics.toString()).append(System.lineSeparator());
}
// take all responses here - this should be limited in number and each one is important.
for (AddressResolutionStatistics value : this.addressResolutionStatistics.values()) {
stringBuilder.append(value.toString()).append(System.lineSeparator());
}
// only take last 10 responses from this list - this has potential of having large number of entries.
// since this is for establishing consistency, we can make do with the last responses to paint a meaningful picture.
int supplementalResponseStatisticsListCount = this.supplementalResponseStatisticsList.size();
int initialIndex = Math.max(supplementalResponseStatisticsListCount - MAX_SUPPLEMENTAL_REQUESTS_FOR_TO_STRING, 0);
if (initialIndex != 0) {
stringBuilder.append(" -- Displaying only the last ")
.append(MAX_SUPPLEMENTAL_REQUESTS_FOR_TO_STRING)
.append(" head/headfeed requests. Total head/headfeed requests: ")
.append(supplementalResponseStatisticsListCount);
}
for (int i = initialIndex; i < supplementalResponseStatisticsListCount; i++) {
stringBuilder.append(this.supplementalResponseStatisticsList.get(i).toString()).append(System.lineSeparator());
}
}
String requestStatsString = stringBuilder.toString();
if (!requestStatsString.isEmpty()) {
return System.lineSeparator() + requestStatsString;
}
return StringUtils.EMPTY;
}
public List getContactedReplicas() {
return contactedReplicas;
}
public void setContactedReplicas(List contactedReplicas) {
this.contactedReplicas = contactedReplicas;
}
public Set getFailedReplicas() {
return failedReplicas;
}
public void setFailedReplicas(Set failedReplicas) {
this.failedReplicas = failedReplicas;
}
public Set getRegionsContacted() {
return regionsContacted;
}
public void setRegionsContacted(Set regionsContacted) {
this.regionsContacted = regionsContacted;
}
private static String formatDateTime(DateTime dateTime) {
if (dateTime == null) {
return null;
}
return dateTime.toString(responseTimeFormatter);
}
private class StoreResponseStatistics {
private DateTime requestStartTimeUTC;
private DateTime requestResponseTimeUTC;
private StoreReadResult storeReadResult;
private ResourceType requestResourceType;
private OperationType requestOperationType;
@Override
public String toString() {
return "StoreResponseStatistics{" +
"requestStartTimeUTC=\"" + formatDateTime(requestStartTimeUTC) + "\"" +
", requestResponseTimeUTC=\"" + formatDateTime(requestResponseTimeUTC) + "\"" +
", storeReadResult=" + storeReadResult +
", requestResourceType=" + requestResourceType +
", requestOperationType=" + requestOperationType +
'}';
}
}
private class AddressResolutionStatistics {
private DateTime startTimeUTC;
private DateTime endTimeUTC;
private String targetEndpoint;
AddressResolutionStatistics() {
}
@Override
public String toString() {
return "AddressResolutionStatistics{" +
"startTimeUTC=\"" + formatDateTime(startTimeUTC) + "\"" +
", endTimeUTC=\"" + formatDateTime(endTimeUTC) + "\"" +
", targetEndpoint='" + targetEndpoint + '\'' +
'}';
}
}
}