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

com.azure.cosmos.ClientSideRequestStatistics Maven / Gradle / Ivy

// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package com.azure.cosmos;

import com.azure.cosmos.implementation.HttpConstants;
import com.azure.cosmos.implementation.ISessionToken;
import com.azure.cosmos.implementation.OperationType;
import com.azure.cosmos.implementation.ResourceType;
import com.azure.cosmos.implementation.RxDocumentServiceRequest;
import com.azure.cosmos.implementation.SessionTokenHelper;
import com.azure.cosmos.implementation.Utils;
import com.azure.cosmos.implementation.directconnectivity.DirectBridgeInternal;
import com.azure.cosmos.implementation.directconnectivity.StoreResponse;
import com.azure.cosmos.implementation.directconnectivity.StoreResult;
import com.sun.management.OperatingSystemMXBean;
import org.apache.commons.lang3.StringUtils;

import java.lang.management.ManagementFactory;
import java.net.URI;
import java.net.URISyntaxException;
import java.time.Duration;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
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;

class ClientSideRequestStatistics {

    private static final int MAX_SUPPLEMENTAL_REQUESTS_FOR_TO_STRING = 10;

    private static final DateTimeFormatter RESPONSE_TIME_FORMATTER =
        DateTimeFormatter.ofPattern("dd MMM yyyy HH:mm:ss" + ".SSS").withLocale(Locale.US);
    private final static OperatingSystemMXBean mbean = (com.sun.management.OperatingSystemMXBean)ManagementFactory.getOperatingSystemMXBean();

    private final ZonedDateTime requestStartTime;
    private ZonedDateTime requestEndTime;

    private ConnectionMode connectionMode;
    
    private final List responseStatisticsList;
    private final List supplementalResponseStatisticsList;
    private final Map addressResolutionStatistics;

    private GatewayStatistic gatewayStatistic;

    private List contactedReplicas;
    private Set failedReplicas;
    private Set regionsContacted;

    ClientSideRequestStatistics() {
        this.requestStartTime = ZonedDateTime.now(ZoneOffset.UTC);
        this.requestEndTime = ZonedDateTime.now(ZoneOffset.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<>();
        this.connectionMode = ConnectionMode.DIRECT;
    }

    Duration getRequestLatency() {
        return Duration.between(requestStartTime, requestEndTime);
    }

    private boolean isCPUOverloaded() {
        //  NOTE: CPUMonitor and CPULoadHistory is not implemented in async SDK yet.
        return false;
    }

    void recordResponse(RxDocumentServiceRequest request, StoreResult storeResult) {
        ZonedDateTime responseTime = ZonedDateTime.now(ZoneOffset.UTC);
        connectionMode = ConnectionMode.DIRECT;

        StoreResponseStatistics storeResponseStatistics = new StoreResponseStatistics();
        storeResponseStatistics.requestResponseTime = responseTime;
        storeResponseStatistics.storeResult = storeResult;
        storeResponseStatistics.requestOperationType = request.getOperationType();
        storeResponseStatistics.requestResourceType = request.getResourceType();

        URI locationEndPoint = null;
        if (request.requestContext.locationEndpointToRoute != null) {
            locationEndPoint = request.requestContext.locationEndpointToRoute;
        }

        synchronized (this) {
            if (responseTime.isAfter(this.requestEndTime)) {
                this.requestEndTime = 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);
            }
        }
    }

    void recordGatewayResponse(RxDocumentServiceRequest rxDocumentServiceRequest, StoreResponse storeResponse, CosmosClientException exception) {
        ZonedDateTime responseTime = ZonedDateTime.now(ZoneOffset.UTC);
        connectionMode = ConnectionMode.GATEWAY;
        synchronized (this) {
            if (responseTime.isAfter(this.requestEndTime)) {
                this.requestEndTime = responseTime;
            }
            this.gatewayStatistic = new GatewayStatistic();
            this.gatewayStatistic.operationType = rxDocumentServiceRequest.getOperationType();
            if (storeResponse != null) {
                this.gatewayStatistic.statusCode = storeResponse.getStatus();
                this.gatewayStatistic.subStatusCode = DirectBridgeInternal.getSubStatusCode(storeResponse);
                this.gatewayStatistic.sessionToken = storeResponse.getHeaderValue(HttpConstants.HttpHeaders.SESSION_TOKEN);
                this.gatewayStatistic.requestCharge = storeResponse.getHeaderValue(HttpConstants.HttpHeaders.REQUEST_CHARGE);
            } else if(exception != null){
                this.gatewayStatistic.statusCode = exception.getStatusCode();
                this.gatewayStatistic.subStatusCode = exception.getSubStatusCode();
            }
        }
    }


    String recordAddressResolutionStart(URI targetEndpoint) {
        String identifier = Utils.randomUUID().toString();

        AddressResolutionStatistics resolutionStatistics = new AddressResolutionStatistics();
        resolutionStatistics.startTime = ZonedDateTime.now(ZoneOffset.UTC);
        //  Very far in the future
        resolutionStatistics.endTime = ZonedDateTime.of(LocalDateTime.MAX, ZoneOffset.UTC);
        resolutionStatistics.targetEndpoint = targetEndpoint == null ? "" : targetEndpoint.toString();

        synchronized (this) {
            this.addressResolutionStatistics.put(identifier, resolutionStatistics);
        }

        return identifier;
    }

    void recordAddressResolutionEnd(String identifier) {
        if (StringUtils.isEmpty(identifier)) {
            return;
        }
        ZonedDateTime responseTime = ZonedDateTime.now(ZoneOffset.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.requestEndTime)) {
                this.requestEndTime = responseTime;
            }

            AddressResolutionStatistics resolutionStatistics = this.addressResolutionStatistics.get(identifier);
            resolutionStatistics.endTime = 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("RequestStartTime: ")
                .append("\"").append(this.requestStartTime.format(RESPONSE_TIME_FORMATTER)).append("\"")
                .append(", ")
                .append("RequestEndTime: ")
                .append("\"").append(this.requestEndTime.format(RESPONSE_TIME_FORMATTER)).append("\"")
                .append(", ")
                .append("Duration: ")
                .append(Duration.between(requestStartTime, requestEndTime).toMillis())
                .append(" ms, ")
                .append("Connection Mode : " + this.connectionMode.toString() + ", ")
                .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());
            }

            if (this.gatewayStatistic != null) {
                stringBuilder.append(this.gatewayStatistic).append(System.lineSeparator());
            }

            printSystemInformation(stringBuilder);
        }
        String requestStatsString = stringBuilder.toString();
        if (!requestStatsString.isEmpty()) {
            return System.lineSeparator() + requestStatsString;
        }
        return StringUtils.EMPTY;
    }

    List getContactedReplicas() {
        return contactedReplicas;
    }

    void setContactedReplicas(List contactedReplicas) {
        this.contactedReplicas = contactedReplicas;
    }

    Set getFailedReplicas() {
        return failedReplicas;
    }

    void setFailedReplicas(Set failedReplicas) {
        this.failedReplicas = failedReplicas;
    }

    Set getRegionsContacted() {
        return regionsContacted;
    }

    void setRegionsContacted(Set regionsContacted) {
        this.regionsContacted = regionsContacted;
    }

    private void printSystemInformation(StringBuilder stringBuilder) {
        try {
            long totalMemory = Runtime.getRuntime().totalMemory() / 1024;
            long freeMemory = Runtime.getRuntime().freeMemory() / 1024;
            long maxMemory = Runtime.getRuntime().maxMemory() / 1024;
            String usedMemory = totalMemory - freeMemory + " KB";
            String availableMemory = (maxMemory - (totalMemory - freeMemory)) + " KB";

            String processCpuLoad = Double.toString(this.mbean.getProcessCpuLoad());
            String systemCpuLoad = Double.toString(this.mbean.getSystemCpuLoad());

            stringBuilder.append("System State Information -------").append(System.lineSeparator());
            stringBuilder.append("Used Memory : " + usedMemory).append(System.lineSeparator());
            stringBuilder.append("Available Memory : " + availableMemory).append(System.lineSeparator());
            stringBuilder.append("CPU Process Load : " + processCpuLoad).append(System.lineSeparator());
            stringBuilder.append("CPU System Load : " + systemCpuLoad).append(System.lineSeparator());
        } catch (Exception e) {
            // Error while evaluating system information, do nothing
        }
    }

    private static String formatDateTime(ZonedDateTime dateTime) {
        if (dateTime == null) {
            return null;
        }
        return dateTime.format(RESPONSE_TIME_FORMATTER);
    }

    private class StoreResponseStatistics {

        private ZonedDateTime requestResponseTime;
        private StoreResult storeResult;
        private ResourceType requestResourceType;
        private OperationType requestOperationType;

        @Override
        public String toString() {
            return "StoreResponseStatistics{"
                       + "requestResponseTime=\"" + formatDateTime(requestResponseTime) + "\"" +
                       ", storeResult=" + storeResult
                       + ", requestResourceType=" + requestResourceType 
                       + ", requestOperationType=" + requestOperationType 
                       + '}';
        }
    }

    private class AddressResolutionStatistics {
        private ZonedDateTime startTime;
        private ZonedDateTime endTime;
        private String targetEndpoint;

        AddressResolutionStatistics() {
        }

        @Override
        public String toString() {
            return "AddressResolutionStatistics{" +
                "startTime=\"" + formatDateTime(startTime) + "\"" +
                ", endTime=\"" + formatDateTime(endTime) + "\"" +
                ", targetEndpoint='" + targetEndpoint + '\'' +
                '}';
        }
    }

    private class GatewayStatistic {
        private OperationType operationType;
        private int statusCode;
        private int subStatusCode;
        private String sessionToken;
        private String requestCharge;

        public String toString() {
            return "Gateway statistics{ " +
                "Operation Type : " + this.operationType +
                "Status Code : " + this.statusCode +
                "Substatus Code : " + this.statusCode +
                "Session Token : " + this.sessionToken +
                "Request Charge : " + requestCharge +
                '}';
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy