Java tutorial
/** * 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()); } }