org.dasein.cloud.util.APITrace.java Source code

Java tutorial

Introduction

Here is the source code for org.dasein.cloud.util.APITrace.java

Source

/**
 * Copyright (C) 2009-2015 Dell, Inc.
 * See annotations for authorship information
 *
 * ====================================================================
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * ====================================================================
 */

package org.dasein.cloud.util;

import org.apache.log4j.Logger;
import org.dasein.cloud.CloudProvider;
import org.dasein.cloud.ProviderContext;
import org.dasein.cloud.RequestTrackingStrategy;
import org.json.JSONObject;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.management.MBeanServer;
import javax.management.ObjectName;
import java.lang.management.ManagementFactory;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeSet;

/**
 * A tool for tracing the load your Dasein Cloud usage is placing on a cloud provider. This class is used by
 * {@link API} to provide JMX integration. In order for any API tracing to be functional, you must set the
 * log level for org.dasein.cloud.util.APITrace to TRACE, DEBUG, or INFO depending on the information you are seeking.
 * To turn it off, set the level to WARN or higher.
 * <p>Created by George Reese: 11/16/12 7:20 PM</p>
 * @author George Reese
 * @version 2013.01 initial version (Issue #1)
 * @since 2013.01
 */
public class APITrace {
    static private final Logger logger = Logger.getLogger(APITrace.class);
    static public final String DELIMITER = ".";
    static public final String DELIMITER_REGEX = "\\.";

    static private class CloudOperation {
        public String name;
        public long startTimestamp = System.currentTimeMillis();
        public long endTimestamp = 0L;
        public int calls = 0;
        public CloudOperation currentChild;
        public ArrayList<CloudOperation> priorChildren;
        public ArrayList<String> apiCalls;

        public CloudOperation(@Nonnull String name) {
            this.name = name;
        }
    }

    static private final HashMap<String, Long> apiCount = new HashMap<String, Long>();
    static private final HashMap<String, Long> operationApis = new HashMap<String, Long>();
    static private final HashMap<String, Long> operationCount = new HashMap<String, Long>();
    static private final HashMap<String, CloudOperation> operationTrace = new HashMap<String, CloudOperation>();

    static private HashMap<Long, CloudOperation> operations = new HashMap<Long, CloudOperation>();

    static {
        try {
            MBeanServer server = ManagementFactory.getPlatformMBeanServer();
            ObjectName name = new ObjectName("org.dasein:type=API");
            API api = new API();

            server.registerMBean(api, name);
        } catch (Throwable t) {
            logger.error("Unable to set up API MBean: " + t.getMessage());
            t.printStackTrace();
        }
    }

    static public void begin(@Nonnull CloudProvider provider, @Nonnull String operationName) {
        if (logger.isDebugEnabled()) {
            try {
                ProviderContext ctx = provider.getContext();
                String accountNumber = getAccountNumber(ctx);
                RequestTrackingStrategy strategy = ctx.getRequestTrackingStrategy();
                String requestTracking = "";
                if (strategy != null && strategy.getInAPITrace()) {
                    requestTracking = strategy.getRequestId() + DELIMITER;
                }

                operationName = provider.getProviderName().replaceAll(DELIMITER_REGEX, "_") + DELIMITER
                        + provider.getCloudName().replaceAll(DELIMITER_REGEX, "_") + DELIMITER
                        + accountNumber.replaceAll(DELIMITER_REGEX, "_") + DELIMITER + requestTracking
                        + operationName;
                long thread = Thread.currentThread().getId();
                CloudOperation operation = new CloudOperation(operationName);
                CloudOperation current = operations.get(thread);

                if (current == null) {
                    operations.put(thread, operation);
                } else {
                    while (current.currentChild != null) {
                        current = current.currentChild;
                    }
                    current.currentChild = operation;
                }
                synchronized (operationCount) {
                    if (operationCount.containsKey(operationName)) {
                        operationCount.put(operationName, operationCount.get(operationName) + 1);
                    } else {
                        operationCount.put(operationName, 1L);
                    }
                }
            } catch (Throwable t) {
                logger.warn("Error with API trace begin: " + t.getMessage());
            }
        }
    }

    static private long count(CloudOperation operation) {
        long count = operation.calls;

        if (operation.priorChildren != null) {
            for (CloudOperation o : operation.priorChildren) {
                count += count(o);
            }
        }
        return count;
    }

    static public void end() {
        if (logger.isDebugEnabled()) {
            try {
                long thread = Thread.currentThread().getId();
                CloudOperation current = operations.get(thread);

                if (current == null) {
                    return;
                }
                CloudOperation parent = null;

                while (current.currentChild != null) {
                    parent = current;
                    current = current.currentChild;
                }
                current.endTimestamp = System.currentTimeMillis();
                if (parent != null) {
                    if (parent.priorChildren == null) {
                        parent.priorChildren = new ArrayList<CloudOperation>();
                    }
                    parent.priorChildren.add(current);
                    parent.currentChild = null;
                } else {
                    operations.remove(thread);
                }
                log(current);
            } catch (Throwable t) {
                logger.warn("Error with API trace end: " + t.getMessage());
            }
        }
    }

    static public long getAPICount() {
        long count = 0L;

        synchronized (apiCount) {
            for (Map.Entry<String, Long> api : apiCount.entrySet()) {
                count += api.getValue();
            }
        }
        return count;
    }

    static public long getAPICount(@Nonnull String providerName) {
        return getAPICountForPrefix(providerName.replaceAll(DELIMITER_REGEX, "_") + DELIMITER);
    }

    static public long getAPICount(@Nonnull String providerName, @Nonnull String cloudName) {
        return getAPICountForPrefix(providerName.replaceAll(DELIMITER_REGEX, "_") + DELIMITER
                + cloudName.replaceAll(DELIMITER_REGEX, "_") + DELIMITER);
    }

    static public long getAPICount(@Nonnull String providerName, @Nonnull String cloudName,
            @Nonnull String accountNumber) {
        return getAPICountForPrefix(providerName.replaceAll(DELIMITER_REGEX, "_") + DELIMITER
                + cloudName.replaceAll(DELIMITER_REGEX, "_") + DELIMITER
                + accountNumber.replaceAll(DELIMITER_REGEX, "_") + DELIMITER);
    }

    static public long getAPICount(@Nonnull String providerName, @Nonnull String cloudName,
            @Nonnull String accountNumber, @Nonnull String apiCall) {
        return getAPICountForPrefix(providerName.replaceAll(DELIMITER_REGEX, "_") + DELIMITER
                + cloudName.replaceAll(DELIMITER_REGEX, "_") + DELIMITER
                + accountNumber.replaceAll(DELIMITER_REGEX, "_") + DELIMITER + apiCall);
    }

    static public long getAPICountAcrossAccounts(@Nonnull String providerName, @Nonnull String cloudName,
            @Nonnull String apiCall) {
        String prefix = providerName.replaceAll(DELIMITER_REGEX, "_") + DELIMITER
                + cloudName.replaceAll(DELIMITER_REGEX, "_") + DELIMITER;
        long count = 0L;

        synchronized (apiCount) {
            for (Map.Entry<String, Long> api : apiCount.entrySet()) {
                if (api.getKey().startsWith(prefix) && api.getKey().endsWith(apiCall)) {
                    count += api.getValue();
                }
            }
        }
        return count;
    }

    static private long getAPICountForPrefix(@Nonnull String prefix) {
        long count = 0L;

        synchronized (apiCount) {
            for (Map.Entry<String, Long> api : apiCount.entrySet()) {
                if (api.getKey().startsWith(prefix)) {
                    count += api.getValue();
                }
            }
        }
        return count;
    }

    static public long getAPICountForOperation() {
        long count = 0L;

        synchronized (operationApis) {
            for (Map.Entry<String, Long> api : operationApis.entrySet()) {
                count += api.getValue();
            }
        }
        return count;
    }

    static public long getAPICountForOperation(@Nonnull String providerName) {
        return getAPICountForPrefixForOperation(providerName.replaceAll(DELIMITER_REGEX, "_") + DELIMITER);
    }

    static public long getAPICountForOperation(@Nonnull String providerName, @Nonnull String cloudName) {
        return getAPICountForPrefixForOperation(providerName.replaceAll(DELIMITER_REGEX, "_") + DELIMITER
                + cloudName.replaceAll(DELIMITER_REGEX, "_") + DELIMITER);
    }

    static public long getAPICountForOperation(@Nonnull String providerName, @Nonnull String cloudName,
            @Nonnull String accountNumber) {
        return getAPICountForPrefixForOperation(providerName.replaceAll(DELIMITER_REGEX, "_") + DELIMITER
                + cloudName.replaceAll(DELIMITER_REGEX, "_") + DELIMITER
                + accountNumber.replaceAll(DELIMITER_REGEX, "_") + DELIMITER);
    }

    static public long getAPICountForOperation(@Nonnull String providerName, @Nonnull String cloudName,
            @Nonnull String accountNumber, @Nonnull String operation) {
        return getAPICountForPrefixForOperation(providerName.replaceAll(DELIMITER_REGEX, "_") + DELIMITER
                + cloudName.replaceAll(DELIMITER_REGEX, "_") + DELIMITER
                + accountNumber.replaceAll(DELIMITER_REGEX, "_") + DELIMITER + operation);
    }

    static public long getAPICountForOperationAcrossAccounts(@Nonnull String providerName,
            @Nonnull String cloudName, @Nonnull String operation) {
        String prefix = providerName.replaceAll(DELIMITER_REGEX, "_") + DELIMITER
                + cloudName.replaceAll(DELIMITER_REGEX, "_") + DELIMITER;
        long count = 0L;

        synchronized (operationApis) {
            for (Map.Entry<String, Long> api : operationApis.entrySet()) {
                if (api.getKey().startsWith(prefix) && api.getKey().endsWith(operation)) {
                    count += api.getValue();
                }
            }
        }
        return count;
    }

    static private long getAPICountForPrefixForOperation(@Nonnull String prefix) {
        long count = 0L;

        synchronized (operationApis) {
            for (Map.Entry<String, Long> api : operationApis.entrySet()) {
                if (api.getKey().startsWith(prefix)) {
                    count += api.getValue();
                }
            }
        }
        return count;
    }

    static public long getOperationCount() {
        long count = 0L;

        synchronized (operationCount) {
            for (Map.Entry<String, Long> api : operationCount.entrySet()) {
                count += api.getValue();
            }
        }
        return count;
    }

    static public long getOperationCount(@Nonnull String providerName) {
        return getOperationCountForPrefix(providerName.replaceAll(DELIMITER_REGEX, "_") + DELIMITER);
    }

    static public long getOperationCount(@Nonnull String providerName, @Nonnull String cloudName) {
        return getOperationCountForPrefix(providerName.replaceAll(DELIMITER_REGEX, "_") + DELIMITER
                + cloudName.replaceAll(DELIMITER_REGEX, "_") + DELIMITER);
    }

    static public long getOperationCount(@Nonnull String providerName, @Nonnull String cloudName,
            @Nonnull String accountNumber) {
        return getOperationCountForPrefix(providerName.replaceAll(DELIMITER_REGEX, "_") + DELIMITER
                + cloudName.replaceAll(DELIMITER_REGEX, "_") + DELIMITER
                + accountNumber.replaceAll(DELIMITER_REGEX, "_") + DELIMITER);
    }

    static public long getOperationCount(@Nonnull String providerName, @Nonnull String cloudName,
            @Nonnull String accountNumber, @Nonnull String operation) {
        return getOperationCountForPrefix(providerName.replaceAll(DELIMITER_REGEX, "_") + DELIMITER
                + cloudName.replaceAll(DELIMITER_REGEX, "_") + DELIMITER
                + accountNumber.replaceAll(DELIMITER_REGEX, "_") + DELIMITER + operation);
    }

    static public long getOperationCountAcrossAccounts(@Nonnull String providerName, @Nonnull String cloudName,
            @Nonnull String operation) {
        String prefix = providerName.replaceAll(DELIMITER_REGEX, "_") + DELIMITER
                + cloudName.replaceAll(DELIMITER_REGEX, "_") + DELIMITER;
        long count = 0L;

        synchronized (operationCount) {
            for (Map.Entry<String, Long> api : operationCount.entrySet()) {
                if (api.getKey().startsWith(prefix) && api.getKey().endsWith(operation)) {
                    count += api.getValue();
                }
            }
        }
        return count;
    }

    static private long getOperationCountForPrefix(@Nonnull String prefix) {
        long count = 0L;

        synchronized (operationCount) {
            for (Map.Entry<String, Long> api : operationCount.entrySet()) {
                if (api.getKey().startsWith(prefix)) {
                    count += api.getValue();
                }
            }
        }
        return count;
    }

    static public @Nullable String getStackTrace(@Nonnull String providerName, @Nonnull String cloudName,
            @Nonnull String operationName) {

        CloudOperation operation = null;

        synchronized (operationTrace) {
            for (Map.Entry<String, CloudOperation> entry : operationTrace.entrySet()) {
                if (entry.getKey()
                        .startsWith(providerName.replaceAll(DELIMITER_REGEX, "_") + DELIMITER
                                + cloudName.replaceAll(DELIMITER_REGEX, "_") + DELIMITER)
                        && entry.getKey().endsWith(DELIMITER + operationName)) {
                    operation = entry.getValue();
                    break;
                }
            }
        }
        if (operation == null) {
            return null;
        }
        return (new JSONObject(toJSON(operation))).toString();
    }

    static private @Nonnull Map<String, Object> toJSON(@Nonnull CloudOperation operation) {
        HashMap<String, Object> map = new HashMap<String, Object>();
        String[] parts = operation.name.split(DELIMITER_REGEX);
        String provider, cloud;
        String name;

        if (parts.length == 4) {
            provider = parts[0];
            cloud = parts[1];
            name = parts[3];
        } else if (parts.length > 4) {
            StringBuilder tmp = new StringBuilder();

            provider = parts[0];
            cloud = parts[1];
            for (int i = 3; i < parts.length; i++) {
                tmp.append(parts[i]);
                if (i < parts.length - 1) {
                    tmp.append(DELIMITER);
                }
            }
            name = tmp.toString();
        } else {
            provider = (parts.length > 0 ? parts[0] : "");
            cloud = (parts.length > 1 ? parts[1] : "");
            name = operation.name;
        }
        map.put("operation", name);
        map.put("provider", provider);
        map.put("cloud", cloud);
        map.put("apiCalls", operation.apiCalls == null ? new String[0] : operation.apiCalls);
        if (operation.endTimestamp > 0L) {
            map.put("duration", operation.endTimestamp - operation.startTimestamp);
        }
        if (operation.priorChildren != null) {
            ArrayList<Map<String, Object>> children = new ArrayList<Map<String, Object>>();

            for (CloudOperation child : operation.priorChildren) {
                children.add(toJSON(child));
            }
            map.put("operationCalls", children);
        } else {
            map.put("operationCalls", new String[0]);
        }
        return map;
    }

    static public String[] listAccounts(@Nonnull String provider, @Nonnull String cloud) {
        provider = provider.replaceAll(DELIMITER_REGEX, "_");
        cloud = cloud.replaceAll(DELIMITER_REGEX, "_");
        TreeSet<String> list = new TreeSet<String>();

        synchronized (apiCount) {
            for (String call : apiCount.keySet()) {
                String[] parts = call.split(DELIMITER_REGEX);

                if (parts.length > 2 && parts[0].equals(provider) && parts[1].equals(cloud)) {
                    list.add(parts[2]);
                }
            }
        }
        return list.toArray(new String[list.size()]);
    }

    static public String[] listApis(@Nonnull String provider, @Nonnull String cloud) {
        provider = provider.replaceAll(DELIMITER_REGEX, "_");
        cloud = cloud.replaceAll(DELIMITER_REGEX, "_");
        TreeSet<String> list = new TreeSet<String>();

        synchronized (apiCount) {
            for (String call : apiCount.keySet()) {
                String[] parts = call.split(DELIMITER_REGEX);

                if (parts.length > 3 && parts[0].equals(provider) && parts[1].equals(cloud)) {
                    if (parts.length == 4) {
                        list.add(parts[3]);
                    } else {
                        StringBuilder tmp = new StringBuilder();

                        for (int i = 3; i < parts.length; i++) {
                            tmp.append(parts[i]);
                            if (i < parts.length - 1) {
                                tmp.append(DELIMITER);
                            }
                        }
                        list.add(tmp.toString());
                    }
                }
            }
        }
        return list.toArray(new String[list.size()]);
    }

    static public String[] listClouds(@Nonnull String provider) {
        provider = provider.replaceAll(DELIMITER_REGEX, "_");
        TreeSet<String> list = new TreeSet<String>();

        synchronized (apiCount) {
            for (String call : apiCount.keySet()) {
                String[] parts = call.split(DELIMITER_REGEX);

                if (parts.length > 1 && parts[0].equals(provider)) {
                    list.add(parts[1]);
                }
            }
        }
        return list.toArray(new String[list.size()]);
    }

    static public String[] listOperations(@Nonnull String provider, @Nonnull String cloud) {
        provider = provider.replaceAll(DELIMITER_REGEX, "_");
        cloud = cloud.replaceAll(DELIMITER_REGEX, "_");
        TreeSet<String> list = new TreeSet<String>();

        synchronized (operationCount) {
            for (String call : operationCount.keySet()) {
                String[] parts = call.split(DELIMITER_REGEX);

                if (parts.length > 3 && parts[0].equals(provider) && parts[1].equals(cloud)) {
                    if (parts.length == 4) {
                        list.add(parts[3]);
                    } else {
                        StringBuilder tmp = new StringBuilder();

                        for (int i = 3; i < parts.length; i++) {
                            tmp.append(parts[i]);
                            if (i < parts.length - 1) {
                                tmp.append(DELIMITER);
                            }
                        }
                        list.add(tmp.toString());
                    }
                }
            }
        }
        return list.toArray(new String[list.size()]);
    }

    static public String[] listProviders() {
        TreeSet<String> providers = new TreeSet<String>();

        synchronized (apiCount) {
            for (String call : apiCount.keySet()) {
                String[] parts = call.split(DELIMITER_REGEX);

                if (parts.length > 0) {
                    providers.add(parts[0]);
                }
            }
        }
        return providers.toArray(new String[providers.size()]);
    }

    static private void log(CloudOperation operation) {
        long count = count(operation);

        synchronized (operationApis) {
            if (operationApis.containsKey(operation.name)) {
                operationApis.put(operation.name, operationApis.get(operation.name) + count);
            } else {
                operationApis.put(operation.name, count);
            }
        }
        if (logger.isTraceEnabled()) {
            operationTrace.put(operation.name, operation);
        }
    }

    static public void report(@Nonnull String prefix) {
        logger.info("");
        if (logger.isInfoEnabled()) {
            synchronized (apiCount) {
                TreeSet<String> keys = new TreeSet<String>();

                keys.addAll(apiCount.keySet());
                logger.debug(prefix + "-> API calls: ");
                for (String key : keys) {
                    logger.debug(prefix + "->\t" + key + " = " + apiCount.get(key));
                }
            }
        }
        if (logger.isDebugEnabled()) {
            synchronized (operationCount) {
                TreeSet<String> keys = new TreeSet<String>();

                keys.addAll(operationCount.keySet());
                logger.debug(prefix + "-> Operation calls:");
                for (String key : keys) {
                    logger.debug(prefix + "->\t" + key + " = " + operationCount.get(key));
                }
            }
            synchronized (operationApis) {
                TreeSet<String> keys = new TreeSet<String>();

                keys.addAll(operationApis.keySet());
                logger.debug(prefix + "-> API calls by operation:");
                for (String key : keys) {
                    logger.debug(prefix + "->\t" + key + " = " + operationApis.get(key));
                }
            }
        }
        if (logger.isTraceEnabled()) {
            synchronized (operationTrace) {
                TreeSet<String> keys = new TreeSet<String>();

                keys.addAll(operationTrace.keySet());
                logger.trace(prefix + "-> Stack trace:");
                for (String key : keys) {
                    Map<String, Object> map = toJSON(operationTrace.get(key));

                    logger.trace((new JSONObject(map)).toString());
                    logger.trace("");
                }
            }
        }
        logger.info("");
    }

    static public void reset() {
        synchronized (apiCount) {
            apiCount.clear();
        }
        synchronized (operationApis) {
            operationApis.clear();
        }
        synchronized (operationCount) {
            operationCount.clear();
        }
        synchronized (operationTrace) {
            operationTrace.clear();
        }
        operations.clear();
    }

    static public void trace(@Nonnull CloudProvider provider, @Nonnull String apiCall) {
        if (logger.isInfoEnabled()) {
            ProviderContext ctx = provider.getContext();
            String accountNumber = getAccountNumber(ctx);
            String callName = provider.getProviderName().replaceAll(DELIMITER_REGEX, "_") + DELIMITER
                    + provider.getCloudName().replaceAll(DELIMITER_REGEX, "_") + DELIMITER
                    + accountNumber.replaceAll(DELIMITER_REGEX, "_") + DELIMITER + apiCall;

            try {
                CloudOperation current = null;

                if (logger.isDebugEnabled()) {
                    long thread = Thread.currentThread().getId();

                    current = operations.get(thread);
                    if (current != null) {
                        while (current.currentChild != null) {
                            current = current.currentChild;
                        }
                        current.calls++;
                    }
                }
                synchronized (apiCount) {
                    if (apiCount.containsKey(callName)) {
                        apiCount.put(callName, apiCount.get(callName) + 1);
                    } else {
                        apiCount.put(callName, 1L);
                    }
                }
                if (logger.isTraceEnabled()) {
                    if (current != null) {
                        if (current.apiCalls == null) {
                            current.apiCalls = new ArrayList<String>();
                        }
                        current.apiCalls.add(apiCall);
                    }
                }
            } catch (Throwable t) {
                logger.warn("Error with API trace trace: " + t.getMessage());
            }
        }
    }

    static public String getAccountNumber(@Nullable ProviderContext ctx) {
        return ((ctx == null || ctx.getAccountNumber() == null) ? "---" : ctx.getAccountNumber());
    }
}