All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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 + '\'' +
                    '}';
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy