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

com.unboundid.ldap.sdk.unboundidds.examples.SummarizeAccessLog Maven / Gradle / Ivy

Go to download

The UnboundID LDAP SDK for Java is a fast, comprehensive, and easy-to-use Java API for communicating with LDAP directory servers and performing related tasks like reading and writing LDIF, encoding and decoding data using base64 and ASN.1 BER, and performing secure communication. This package contains the Standard Edition of the LDAP SDK, which is a complete, general-purpose library for communicating with LDAPv3 directory servers.

There is a newer version: 7.0.2
Show newest version
/*
 * Copyright 2009-2018 Ping Identity Corporation
 * All Rights Reserved.
 */
/*
 * Copyright (C) 2015-2018 Ping Identity Corporation
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License (GPLv2 only)
 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
 * as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, see .
 */
package com.unboundid.ldap.sdk.unboundidds.examples;



import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.IOException;
import java.io.OutputStream;
import java.io.Serializable;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.atomic.AtomicLong;
import java.util.zip.GZIPInputStream;
import javax.crypto.BadPaddingException;

import com.unboundid.ldap.sdk.Filter;
import com.unboundid.ldap.sdk.LDAPException;
import com.unboundid.ldap.sdk.ResultCode;
import com.unboundid.ldap.sdk.SearchScope;
import com.unboundid.ldap.sdk.Version;
import com.unboundid.ldap.sdk.unboundidds.logs.AbandonRequestAccessLogMessage;
import com.unboundid.ldap.sdk.unboundidds.logs.AccessLogMessage;
import com.unboundid.ldap.sdk.unboundidds.logs.AccessLogReader;
import com.unboundid.ldap.sdk.unboundidds.logs.AddResultAccessLogMessage;
import com.unboundid.ldap.sdk.unboundidds.logs.BindResultAccessLogMessage;
import com.unboundid.ldap.sdk.unboundidds.logs.CompareResultAccessLogMessage;
import com.unboundid.ldap.sdk.unboundidds.logs.ConnectAccessLogMessage;
import com.unboundid.ldap.sdk.unboundidds.logs.DeleteResultAccessLogMessage;
import com.unboundid.ldap.sdk.unboundidds.logs.DisconnectAccessLogMessage;
import com.unboundid.ldap.sdk.unboundidds.logs.ExtendedRequestAccessLogMessage;
import com.unboundid.ldap.sdk.unboundidds.logs.ExtendedResultAccessLogMessage;
import com.unboundid.ldap.sdk.unboundidds.logs.LogException;
import com.unboundid.ldap.sdk.unboundidds.logs.ModifyDNResultAccessLogMessage;
import com.unboundid.ldap.sdk.unboundidds.logs.ModifyResultAccessLogMessage;
import com.unboundid.ldap.sdk.unboundidds.logs.OperationAccessLogMessage;
import com.unboundid.ldap.sdk.unboundidds.logs.SearchRequestAccessLogMessage;
import com.unboundid.ldap.sdk.unboundidds.logs.SearchResultAccessLogMessage;
import com.unboundid.ldap.sdk.unboundidds.logs.UnbindRequestAccessLogMessage;
import com.unboundid.ldap.sdk.unboundidds.tools.ToolUtils;
import com.unboundid.util.CommandLineTool;
import com.unboundid.util.Debug;
import com.unboundid.util.NotMutable;
import com.unboundid.util.ObjectPair;
import com.unboundid.util.ReverseComparator;
import com.unboundid.util.StaticUtils;
import com.unboundid.util.ThreadSafety;
import com.unboundid.util.ThreadSafetyLevel;
import com.unboundid.util.args.ArgumentException;
import com.unboundid.util.args.ArgumentParser;
import com.unboundid.util.args.BooleanArgument;
import com.unboundid.util.args.FileArgument;



/**
 * This class provides a tool that may be used to read and summarize the
 * contents of one or more access log files from Ping Identity, UnboundID and
 * Nokia/Alcatel-Lucent 8661 server products.
 * 
*
* NOTE: This class, and other classes within the * {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only * supported for use against Ping Identity, UnboundID, and * Nokia/Alcatel-Lucent 8661 server products. These classes provide support * for proprietary functionality or for external specifications that are not * considered stable or mature enough to be guaranteed to work in an * interoperable way with other types of LDAP servers. *
* Information that will be reported includes: *
    *
  • The total length of time covered by the log files.
  • *
  • The number of connections established and disconnected, the addresses * of the most commonly-connecting clients, and the average rate of * connects and disconnects per second.
  • *
  • The number of operations processed, overall and by operation type, * and the average rate of operations per second.
  • *
  • The average duration for operations processed, overall and by operation * type.
  • *
  • A breakdown of operation processing times into a number of predefined * categories (less than 1ms, between 1ms and 2ms, between 2ms and 3ms, * between 3ms and 5ms, between 5ms and 10ms, between 10ms and 20ms, * between 20ms and 30ms, between 30ms and 50ms, between 50ms and 100ms, * between 100ms and 1000ms, and over 1000ms).
  • *
  • A breakdown of the most common result codes for each type of operation * and their relative frequencies.
  • *
  • The most common types of extended requests processed and their * relative frequencies.
  • *
  • The number of unindexed search operations processed.
  • *
  • A breakdown of the relative frequencies for each type of search * scope.
  • *
  • The most common types of search filters used for search * operations and their relative frequencies.
  • *
* It is designed to work with access log files using either the default log * format with separate request and response messages, as well as log files * in which the request and response details have been combined on the same * line. The log files to be processed should be provided as command-line * arguments. *

* The APIs demonstrated by this example include: *
    *
  • Access log parsing (from the * {@code com.unboundid.ldap.sdk.unboundidds.logs} package)
  • *
  • Argument parsing (from the {@code com.unboundid.util.args} * package)
  • *
*/ @NotMutable() @ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE) public final class SummarizeAccessLog extends CommandLineTool implements Serializable { /** * The serial version UID for this serializable class. */ private static final long serialVersionUID = 7189168366509887130L; // Variables used for accessing argument information. private ArgumentParser argumentParser; // An argument which may be used to indicate that the log files are // compressed. private BooleanArgument isCompressed; // An argument used to specify the encryption passphrase. private FileArgument encryptionPassphraseFile; // The decimal format that will be used for this class. private final DecimalFormat decimalFormat; // The total duration for log content, in milliseconds. private long logDurationMillis; // The total processing time for each type of operation. private double addProcessingDuration; private double bindProcessingDuration; private double compareProcessingDuration; private double deleteProcessingDuration; private double extendedProcessingDuration; private double modifyProcessingDuration; private double modifyDNProcessingDuration; private double searchProcessingDuration; // A variable used for counting the number of messages of each type. private long numAbandons; private long numAdds; private long numBinds; private long numCompares; private long numConnects; private long numDeletes; private long numDisconnects; private long numExtended; private long numModifies; private long numModifyDNs; private long numNonBaseSearches; private long numSearches; private long numUnbinds; // The number of operations of each type that accessed uncached data. private long numUncachedAdds; private long numUncachedBinds; private long numUncachedCompares; private long numUncachedDeletes; private long numUncachedExtended; private long numUncachedModifies; private long numUncachedModifyDNs; private long numUncachedSearches; // The number of unindexed searches processed within the server. private long numUnindexedAttempts; private long numUnindexedFailed; private long numUnindexedSuccessful; // Variables used for maintaining counts for common types of information. private final HashMap searchEntryCounts; private final HashMap addResultCodes; private final HashMap bindResultCodes; private final HashMap compareResultCodes; private final HashMap deleteResultCodes; private final HashMap extendedResultCodes; private final HashMap modifyResultCodes; private final HashMap modifyDNResultCodes; private final HashMap searchResultCodes; private final HashMap searchScopes; private final HashMap clientAddresses; private final HashMap clientConnectionPolicies; private final HashMap disconnectReasons; private final HashMap extendedOperations; private final HashMap filterTypes; private final HashSet processedRequests; private final LinkedHashMap addProcessingTimes; private final LinkedHashMap bindProcessingTimes; private final LinkedHashMap compareProcessingTimes; private final LinkedHashMap deleteProcessingTimes; private final LinkedHashMap extendedProcessingTimes; private final LinkedHashMap modifyProcessingTimes; private final LinkedHashMap modifyDNProcessingTimes; private final LinkedHashMap searchProcessingTimes; /** * Parse the provided command line arguments and perform the appropriate * processing. * * @param args The command line arguments provided to this program. */ public static void main(final String[] args) { final ResultCode resultCode = main(args, System.out, System.err); if (resultCode != ResultCode.SUCCESS) { System.exit(resultCode.intValue()); } } /** * Parse the provided command line arguments and perform the appropriate * processing. * * @param args The command line arguments provided to this program. * @param outStream The output stream to which standard out should be * written. It may be {@code null} if output should be * suppressed. * @param errStream The output stream to which standard error should be * written. It may be {@code null} if error messages * should be suppressed. * * @return A result code indicating whether the processing was successful. */ public static ResultCode main(final String[] args, final OutputStream outStream, final OutputStream errStream) { final SummarizeAccessLog summarizer = new SummarizeAccessLog(outStream, errStream); return summarizer.runTool(args); } /** * Creates a new instance of this tool. * * @param outStream The output stream to which standard out should be * written. It may be {@code null} if output should be * suppressed. * @param errStream The output stream to which standard error should be * written. It may be {@code null} if error messages * should be suppressed. */ public SummarizeAccessLog(final OutputStream outStream, final OutputStream errStream) { super(outStream, errStream); decimalFormat = new DecimalFormat("0.000"); logDurationMillis = 0L; addProcessingDuration = 0.0; bindProcessingDuration = 0.0; compareProcessingDuration = 0.0; deleteProcessingDuration = 0.0; extendedProcessingDuration = 0.0; modifyProcessingDuration = 0.0; modifyDNProcessingDuration = 0.0; searchProcessingDuration = 0.0; numAbandons = 0L; numAdds = 0L; numBinds = 0L; numCompares = 0L; numConnects = 0L; numDeletes = 0L; numDisconnects = 0L; numExtended = 0L; numModifies = 0L; numModifyDNs = 0L; numNonBaseSearches = 0L; numSearches = 0L; numUnbinds = 0L; numUncachedAdds = 0L; numUncachedBinds = 0L; numUncachedCompares = 0L; numUncachedDeletes = 0L; numUncachedExtended = 0L; numUncachedModifies = 0L; numUncachedModifyDNs = 0L; numUncachedSearches = 0L; searchEntryCounts = new HashMap<>(StaticUtils.computeMapCapacity(10)); addResultCodes = new HashMap<>(StaticUtils.computeMapCapacity(10)); bindResultCodes = new HashMap<>(StaticUtils.computeMapCapacity(10)); compareResultCodes = new HashMap<>(StaticUtils.computeMapCapacity(10)); deleteResultCodes = new HashMap<>(StaticUtils.computeMapCapacity(10)); extendedResultCodes = new HashMap<>(StaticUtils.computeMapCapacity(10)); modifyResultCodes = new HashMap<>(StaticUtils.computeMapCapacity(10)); modifyDNResultCodes = new HashMap<>(StaticUtils.computeMapCapacity(10)); searchResultCodes = new HashMap<>(StaticUtils.computeMapCapacity(10)); searchScopes = new HashMap<>(StaticUtils.computeMapCapacity(4)); clientAddresses = new HashMap<>(StaticUtils.computeMapCapacity(100)); clientConnectionPolicies = new HashMap<>(StaticUtils.computeMapCapacity(100)); disconnectReasons = new HashMap<>(StaticUtils.computeMapCapacity(100)); extendedOperations = new HashMap<>(StaticUtils.computeMapCapacity(10)); filterTypes = new HashMap<>(StaticUtils.computeMapCapacity(100)); processedRequests = new HashSet<>(StaticUtils.computeMapCapacity(100)); addProcessingTimes = new LinkedHashMap<>(StaticUtils.computeMapCapacity(11)); bindProcessingTimes = new LinkedHashMap<>(StaticUtils.computeMapCapacity(11)); compareProcessingTimes = new LinkedHashMap<>(StaticUtils.computeMapCapacity(11)); deleteProcessingTimes = new LinkedHashMap<>(StaticUtils.computeMapCapacity(11)); extendedProcessingTimes = new LinkedHashMap<>(StaticUtils.computeMapCapacity(11)); modifyProcessingTimes = new LinkedHashMap<>(StaticUtils.computeMapCapacity(11)); modifyDNProcessingTimes = new LinkedHashMap<>(StaticUtils.computeMapCapacity(11)); searchProcessingTimes = new LinkedHashMap<>(StaticUtils.computeMapCapacity(11)); populateProcessingTimeMap(addProcessingTimes); populateProcessingTimeMap(bindProcessingTimes); populateProcessingTimeMap(compareProcessingTimes); populateProcessingTimeMap(deleteProcessingTimes); populateProcessingTimeMap(extendedProcessingTimes); populateProcessingTimeMap(modifyProcessingTimes); populateProcessingTimeMap(modifyDNProcessingTimes); populateProcessingTimeMap(searchProcessingTimes); } /** * Retrieves the name for this tool. * * @return The name for this tool. */ @Override() public String getToolName() { return "summarize-access-log"; } /** * Retrieves the description for this tool. * * @return The description for this tool. */ @Override() public String getToolDescription() { return "Examine one or more access log files from Ping Identity, " + "UnboundID, or Nokia/Alcatel-Lucent 8661 server products to display " + "a number of metrics about operations processed within the server."; } /** * Retrieves the version string for this tool. * * @return The version string for this tool. */ @Override() public String getToolVersion() { return Version.NUMERIC_VERSION_STRING; } /** * Retrieves the minimum number of unnamed trailing arguments that are * required. * * @return One, to indicate that at least one trailing argument (representing * the path to an access log file) must be provided. */ @Override() public int getMinTrailingArguments() { return 1; } /** * Retrieves the maximum number of unnamed trailing arguments that may be * provided for this tool. * * @return The maximum number of unnamed trailing arguments that may be * provided for this tool. */ @Override() public int getMaxTrailingArguments() { return -1; } /** * Retrieves a placeholder string that should be used for trailing arguments * in the usage information for this tool. * * @return A placeholder string that should be used for trailing arguments in * the usage information for this tool. */ @Override() public String getTrailingArgumentsPlaceholder() { return "{path}"; } /** * Indicates whether this tool should provide support for an interactive mode, * in which the tool offers a mode in which the arguments can be provided in * a text-driven menu rather than requiring them to be given on the command * line. If interactive mode is supported, it may be invoked using the * "--interactive" argument. Alternately, if interactive mode is supported * and {@link #defaultsToInteractiveMode()} returns {@code true}, then * interactive mode may be invoked by simply launching the tool without any * arguments. * * @return {@code true} if this tool supports interactive mode, or * {@code false} if not. */ @Override() public boolean supportsInteractiveMode() { return true; } /** * Indicates whether this tool defaults to launching in interactive mode if * the tool is invoked without any command-line arguments. This will only be * used if {@link #supportsInteractiveMode()} returns {@code true}. * * @return {@code true} if this tool defaults to using interactive mode if * launched without any command-line arguments, or {@code false} if * not. */ @Override() public boolean defaultsToInteractiveMode() { return true; } /** * Indicates whether this tool should provide arguments for redirecting output * to a file. If this method returns {@code true}, then the tool will offer * an "--outputFile" argument that will specify the path to a file to which * all standard output and standard error content will be written, and it will * also offer a "--teeToStandardOut" argument that can only be used if the * "--outputFile" argument is present and will cause all output to be written * to both the specified output file and to standard output. * * @return {@code true} if this tool should provide arguments for redirecting * output to a file, or {@code false} if not. */ @Override() protected boolean supportsOutputFile() { return true; } /** * Indicates whether this tool supports the use of a properties file for * specifying default values for arguments that aren't specified on the * command line. * * @return {@code true} if this tool supports the use of a properties file * for specifying default values for arguments that aren't specified * on the command line, or {@code false} if not. */ @Override() public boolean supportsPropertiesFile() { return true; } /** * Adds the command-line arguments supported for use with this tool to the * provided argument parser. The tool may need to retain references to the * arguments (and/or the argument parser, if trailing arguments are allowed) * to it in order to obtain their values for use in later processing. * * @param parser The argument parser to which the arguments are to be added. * * @throws ArgumentException If a problem occurs while adding any of the * tool-specific arguments to the provided * argument parser. */ @Override() public void addToolArguments(final ArgumentParser parser) throws ArgumentException { // We need to save a reference to the argument parser so that we can get // the trailing arguments later. argumentParser = parser; // Add an argument that makes it possible to read a compressed log file. // Note that this argument is no longer needed for dealing with compressed // files, since the tool will automatically detect whether a file is // compressed. However, the argument is still provided for the purpose of // backward compatibility. String description = "Indicates that the log file is compressed."; isCompressed = new BooleanArgument('c', "isCompressed", description); isCompressed.addLongIdentifier("is-compressed", true); isCompressed.addLongIdentifier("compressed", true); isCompressed.setHidden(true); parser.addArgument(isCompressed); // Add an argument that indicates that the tool should read the encryption // passphrase from a file. description = "Indicates that the log file is encrypted and that the " + "encryption passphrase is contained in the specified file. If " + "the log data is encrypted and this argument is not provided, then " + "the tool will interactively prompt for the encryption passphrase."; encryptionPassphraseFile = new FileArgument(null, "encryptionPassphraseFile", false, 1, null, description, true, true, true, false); encryptionPassphraseFile.addLongIdentifier("encryption-passphrase-file", true); encryptionPassphraseFile.addLongIdentifier("encryptionPasswordFile", true); encryptionPassphraseFile.addLongIdentifier("encryption-password-file", true); parser.addArgument(encryptionPassphraseFile); } /** * Performs any necessary processing that should be done to ensure that the * provided set of command-line arguments were valid. This method will be * called after the basic argument parsing has been performed and immediately * before the {@link CommandLineTool#doToolProcessing} method is invoked. * * @throws ArgumentException If there was a problem with the command-line * arguments provided to this program. */ @Override() public void doExtendedArgumentValidation() throws ArgumentException { // Make sure that at least one access log file path was provided. final List trailingArguments = argumentParser.getTrailingArguments(); if ((trailingArguments == null) || trailingArguments.isEmpty()) { throw new ArgumentException("No access log file paths were provided."); } } /** * Performs the core set of processing for this tool. * * @return A result code that indicates whether the processing completed * successfully. */ @Override() public ResultCode doToolProcessing() { String encryptionPassphrase = null; if (encryptionPassphraseFile.isPresent()) { try { encryptionPassphrase = ToolUtils.readEncryptionPassphraseFromFile( encryptionPassphraseFile.getValue()); } catch (final LDAPException e) { Debug.debugException(e); err(e.getMessage()); return e.getResultCode(); } } long logLines = 0L; for (final String path : argumentParser.getTrailingArguments()) { final File f = new File(path); out("Examining access log ", f.getAbsolutePath()); AccessLogReader reader = null; InputStream inputStream = null; try { inputStream = new FileInputStream(f); final ObjectPair p = ToolUtils.getPossiblyPassphraseEncryptedInputStream(inputStream, encryptionPassphrase, (! encryptionPassphraseFile.isPresent()), "Log file '" + path + "' is encrypted. Please enter the " + "encryption passphrase:", "ERROR: The provided passphrase was incorrect.", getOut(), getErr()); inputStream = p.getFirst(); if ((p.getSecond() != null) && (encryptionPassphrase == null)) { encryptionPassphrase = p.getSecond(); } if (isCompressed.isPresent()) { inputStream = new GZIPInputStream(inputStream); } else { inputStream = ToolUtils.getPossiblyGZIPCompressedInputStream(inputStream); } reader = new AccessLogReader(new InputStreamReader(inputStream)); } catch (final Exception e) { Debug.debugException(e); err("Unable to open access log file ", f.getAbsolutePath(), ": ", StaticUtils.getExceptionMessage(e)); return ResultCode.LOCAL_ERROR; } finally { if ((reader == null) && (inputStream != null)) { try { inputStream.close(); } catch (final Exception e) { Debug.debugException(e); } } } long startTime = 0L; long stopTime = 0L; while (true) { final AccessLogMessage msg; try { msg = reader.read(); } catch (final IOException ioe) { Debug.debugException(ioe); err("Error reading from access log file ", f.getAbsolutePath(), ": ", StaticUtils.getExceptionMessage(ioe)); if ((ioe.getCause() != null) && (ioe.getCause() instanceof BadPaddingException)) { err("This error is likely because the log is encrypted and the " + "server still has the log file open. It is recommended " + "that you only try to examine encrypted logs after they " + "have been rotated. You can use the rotate-log tool to " + "force a rotation at any time. Attempting to proceed with " + "just the data that was successfully read."); break; } else { return ResultCode.LOCAL_ERROR; } } catch (final LogException le) { Debug.debugException(le); err("Encountered an error while attempting to parse a line in" + "access log file ", f.getAbsolutePath(), ": ", StaticUtils.getExceptionMessage(le)); continue; } if (msg == null) { break; } logLines++; stopTime = msg.getTimestamp().getTime(); if (startTime == 0L) { startTime = stopTime; } switch (msg.getMessageType()) { case CONNECT: processConnect((ConnectAccessLogMessage) msg); break; case DISCONNECT: processDisconnect((DisconnectAccessLogMessage) msg); break; case REQUEST: switch (((OperationAccessLogMessage) msg).getOperationType()) { case ABANDON: processAbandonRequest((AbandonRequestAccessLogMessage) msg); break; case EXTENDED: processExtendedRequest((ExtendedRequestAccessLogMessage) msg); break; case SEARCH: processSearchRequest((SearchRequestAccessLogMessage) msg); break; case UNBIND: processUnbindRequest((UnbindRequestAccessLogMessage) msg); break; } break; case RESULT: switch (((OperationAccessLogMessage) msg).getOperationType()) { case ADD: processAddResult((AddResultAccessLogMessage) msg); break; case BIND: processBindResult((BindResultAccessLogMessage) msg); break; case COMPARE: processCompareResult((CompareResultAccessLogMessage) msg); break; case DELETE: processDeleteResult((DeleteResultAccessLogMessage) msg); break; case EXTENDED: processExtendedResult((ExtendedResultAccessLogMessage) msg); break; case MODIFY: processModifyResult((ModifyResultAccessLogMessage) msg); break; case MODDN: processModifyDNResult((ModifyDNResultAccessLogMessage) msg); break; case SEARCH: processSearchResult((SearchResultAccessLogMessage) msg); break; } break; case ASSURANCE_COMPLETE: case CLIENT_CERTIFICATE: case ENTRY_REBALANCING_REQUEST: case ENTRY_REBALANCING_RESULT: case FORWARD: case FORWARD_FAILED: case ENTRY: case REFERENCE: default: // Nothing needs to be done for these message types. } } try { reader.close(); } catch (final Exception e) { Debug.debugException(e); } logDurationMillis += (stopTime - startTime); } final int numFiles = argumentParser.getTrailingArguments().size(); out(); out("Examined ", logLines, " lines in ", numFiles, ((numFiles == 1) ? " file" : " files"), " covering a total duration of ", StaticUtils.millisToHumanReadableDuration(logDurationMillis)); if (logLines == 0) { return ResultCode.SUCCESS; } out(); final double logDurationSeconds = logDurationMillis / 1000.0; final double connectsPerSecond = numConnects / logDurationSeconds; final double disconnectsPerSecond = numDisconnects / logDurationSeconds; out("Total connections established: ", numConnects, " (", decimalFormat.format(connectsPerSecond), "/second)"); out("Total disconnects: ", numDisconnects, " (", decimalFormat.format(disconnectsPerSecond), "/second)"); if (! clientAddresses.isEmpty()) { out(); final List> connectCounts = getMostCommonElements(clientAddresses, 20); out("Most common client addresses:"); for (final ObjectPair p : connectCounts) { final long count = p.getSecond(); final double percent = 100.0 * count / numConnects; out(p.getFirst(), ": ", count, " (", decimalFormat.format(percent), ")"); } } if (! clientConnectionPolicies.isEmpty()) { long totalCCPs = 0; for (final AtomicLong l : clientConnectionPolicies.values()) { totalCCPs += l.get(); } final List> reasonCounts = getMostCommonElements(clientConnectionPolicies, 20); out(); out("Most common client connection policies:"); for (final ObjectPair p : reasonCounts) { final long count = p.getSecond(); final double percent = 100.0 * count / totalCCPs; out(p.getFirst(), ": ", p.getSecond(), " (", decimalFormat.format(percent), "%)"); } } if (! disconnectReasons.isEmpty()) { final List> reasonCounts = getMostCommonElements(disconnectReasons, 20); out(); out("Most common disconnect reasons:"); for (final ObjectPair p : reasonCounts) { final long count = p.getSecond(); final double percent = 100.0 * count / numDisconnects; out(p.getFirst(), ": ", p.getSecond(), " (", decimalFormat.format(percent), "%)"); } } final long totalOps = numAbandons + numAdds + numBinds + numCompares + numDeletes + numExtended + numModifies + numModifyDNs + numSearches + numUnbinds; if (totalOps > 0) { final double percentAbandon = 100.0 * numAbandons / totalOps; final double percentAdd = 100.0 * numAdds / totalOps; final double percentBind = 100.0 * numBinds / totalOps; final double percentCompare = 100.0 * numCompares / totalOps; final double percentDelete = 100.0 * numDeletes / totalOps; final double percentExtended = 100.0 * numExtended / totalOps; final double percentModify = 100.0 * numModifies / totalOps; final double percentModifyDN = 100.0 * numModifyDNs / totalOps; final double percentSearch = 100.0 * numSearches / totalOps; final double percentUnbind = 100.0 * numUnbinds / totalOps; final double abandonsPerSecond = numAbandons / logDurationSeconds; final double addsPerSecond = numAdds / logDurationSeconds; final double bindsPerSecond = numBinds / logDurationSeconds; final double comparesPerSecond = numCompares / logDurationSeconds; final double deletesPerSecond = numDeletes / logDurationSeconds; final double extendedPerSecond = numExtended / logDurationSeconds; final double modifiesPerSecond = numModifies / logDurationSeconds; final double modifyDNsPerSecond = numModifyDNs / logDurationSeconds; final double searchesPerSecond = numSearches / logDurationSeconds; final double unbindsPerSecond = numUnbinds / logDurationSeconds; out(); out("Total operations examined: ", totalOps); out("Abandon operations examined: ", numAbandons, " (", decimalFormat.format(percentAbandon), "%, ", decimalFormat.format(abandonsPerSecond), "/second)"); out("Add operations examined: ", numAdds, " (", decimalFormat.format(percentAdd), "%, ", decimalFormat.format(addsPerSecond), "/second)"); out("Bind operations examined: ", numBinds, " (", decimalFormat.format(percentBind), "%, ", decimalFormat.format(bindsPerSecond), "/second)"); out("Compare operations examined: ", numCompares, " (", decimalFormat.format(percentCompare), "%, ", decimalFormat.format(comparesPerSecond), "/second)"); out("Delete operations examined: ", numDeletes, " (", decimalFormat.format(percentDelete), "%, ", decimalFormat.format(deletesPerSecond), "/second)"); out("Extended operations examined: ", numExtended, " (", decimalFormat.format(percentExtended), "%, ", decimalFormat.format(extendedPerSecond), "/second)"); out("Modify operations examined: ", numModifies, " (", decimalFormat.format(percentModify), "%, ", decimalFormat.format(modifiesPerSecond), "/second)"); out("Modify DN operations examined: ", numModifyDNs, " (", decimalFormat.format(percentModifyDN), "%, ", decimalFormat.format(modifyDNsPerSecond), "/second)"); out("Search operations examined: ", numSearches, " (", decimalFormat.format(percentSearch), "%, ", decimalFormat.format(searchesPerSecond), "/second)"); out("Unbind operations examined: ", numUnbinds, " (", decimalFormat.format(percentUnbind), "%, ", decimalFormat.format(unbindsPerSecond), "/second)"); final double totalProcessingDuration = addProcessingDuration + bindProcessingDuration + compareProcessingDuration + deleteProcessingDuration + extendedProcessingDuration + modifyProcessingDuration + modifyDNProcessingDuration + searchProcessingDuration; out(); out("Average operation processing duration: ", decimalFormat.format(totalProcessingDuration / totalOps), "ms"); if (numAdds > 0) { out("Average add operation processing duration: ", decimalFormat.format(addProcessingDuration / numAdds), "ms"); } if (numBinds > 0) { out("Average bind operation processing duration: ", decimalFormat.format(bindProcessingDuration / numBinds), "ms"); } if (numCompares > 0) { out("Average compare operation processing duration: ", decimalFormat.format(compareProcessingDuration / numCompares), "ms"); } if (numDeletes > 0) { out("Average delete operation processing duration: ", decimalFormat.format(deleteProcessingDuration / numDeletes), "ms"); } if (numExtended > 0) { out("Average extended operation processing duration: ", decimalFormat.format(extendedProcessingDuration / numExtended), "ms"); } if (numModifies > 0) { out("Average modify operation processing duration: ", decimalFormat.format(modifyProcessingDuration / numModifies), "ms"); } if (numModifyDNs > 0) { out("Average modify DN operation processing duration: ", decimalFormat.format(modifyDNProcessingDuration / numModifyDNs), "ms"); } if (numSearches > 0) { out("Average search operation processing duration: ", decimalFormat.format(searchProcessingDuration / numSearches), "ms"); } printProcessingTimeHistogram("add", numAdds, addProcessingTimes); printProcessingTimeHistogram("bind", numBinds, bindProcessingTimes); printProcessingTimeHistogram("compare", numCompares, compareProcessingTimes); printProcessingTimeHistogram("delete", numDeletes, deleteProcessingTimes); printProcessingTimeHistogram("extended", numExtended, extendedProcessingTimes); printProcessingTimeHistogram("modify", numModifies, modifyProcessingTimes); printProcessingTimeHistogram("modify DN", numModifyDNs, modifyDNProcessingTimes); printProcessingTimeHistogram("search", numSearches, searchProcessingTimes); if (! addResultCodes.isEmpty()) { final List> rcCounts = getMostCommonElements(addResultCodes, 20); out(); out("Most common add operation result codes:"); for (final ObjectPair p : rcCounts) { final long count = p.getSecond(); final double percent = 100.0 * count / numAdds; out(p.getFirst().getName(), ": ", p.getSecond(), " (", decimalFormat.format(percent), "%)"); } } if (! bindResultCodes.isEmpty()) { final List> rcCounts = getMostCommonElements(bindResultCodes, 20); out(); out("Most common bind operation result codes:"); for (final ObjectPair p : rcCounts) { final long count = p.getSecond(); final double percent = 100.0 * count / numBinds; out(p.getFirst().getName(), ": ", p.getSecond(), " (", decimalFormat.format(percent), "%)"); } } if (! compareResultCodes.isEmpty()) { final List> rcCounts = getMostCommonElements(compareResultCodes, 20); out(); out("Most common compare operation result codes:"); for (final ObjectPair p : rcCounts) { final long count = p.getSecond(); final double percent = 100.0 * count / numCompares; out(p.getFirst().getName(), ": ", p.getSecond(), " (", decimalFormat.format(percent), "%)"); } } if (! deleteResultCodes.isEmpty()) { final List> rcCounts = getMostCommonElements(deleteResultCodes, 20); out(); out("Most common delete operation result codes:"); for (final ObjectPair p : rcCounts) { final long count = p.getSecond(); final double percent = 100.0 * count / numDeletes; out(p.getFirst().getName(), ": ", p.getSecond(), " (", decimalFormat.format(percent), "%)"); } } if (! extendedResultCodes.isEmpty()) { final List> rcCounts = getMostCommonElements(extendedResultCodes, 20); out(); out("Most common extended operation result codes:"); for (final ObjectPair p : rcCounts) { final long count = p.getSecond(); final double percent = 100.0 * count / numExtended; out(p.getFirst().getName(), ": ", p.getSecond(), " (", decimalFormat.format(percent), "%)"); } } if (! modifyResultCodes.isEmpty()) { final List> rcCounts = getMostCommonElements(modifyResultCodes, 20); out(); out("Most common modify operation result codes:"); for (final ObjectPair p : rcCounts) { final long count = p.getSecond(); final double percent = 100.0 * count / numModifies; out(p.getFirst().getName(), ": ", p.getSecond(), " (", decimalFormat.format(percent), "%)"); } } if (! modifyDNResultCodes.isEmpty()) { final List> rcCounts = getMostCommonElements(modifyDNResultCodes, 20); out(); out("Most common modify DN operation result codes:"); for (final ObjectPair p : rcCounts) { final long count = p.getSecond(); final double percent = 100.0 * count / numModifyDNs; out(p.getFirst().getName(), ": ", p.getSecond(), " (", decimalFormat.format(percent), "%)"); } } if (! searchResultCodes.isEmpty()) { final List> rcCounts = getMostCommonElements(searchResultCodes, 20); out(); out("Most common search operation result codes:"); for (final ObjectPair p : rcCounts) { final long count = p.getSecond(); final double percent = 100.0 * count / numSearches; out(p.getFirst().getName(), ": ", p.getSecond(), " (", decimalFormat.format(percent), "%)"); } } if (! extendedOperations.isEmpty()) { final List> extOpCounts = getMostCommonElements(extendedOperations, 20); out(); out("Most common extended operation types:"); for (final ObjectPair p : extOpCounts) { final long count = p.getSecond(); final double percent = 100.0 * count / numExtended; out(p.getFirst(), ": ", p.getSecond(), " (", decimalFormat.format(percent), "%)"); } } out(); out("Number of unindexed search attempts: ", numUnindexedAttempts); out("Number of successfully-completed unindexed searches: ", numUnindexedSuccessful); out("Number of failed unindexed searches: ", numUnindexedFailed); if (! searchScopes.isEmpty()) { final List> scopeCounts = getMostCommonElements(searchScopes, 20); out(); out("Most common search scopes:"); for (final ObjectPair p : scopeCounts) { final long count = p.getSecond(); final double percent = 100.0 * count / numSearches; out(p.getFirst().getName(), ": ", p.getSecond(), " (", decimalFormat.format(percent), "%)"); } } if (! searchEntryCounts.isEmpty()) { final List> entryCounts = getMostCommonElements(searchEntryCounts, 20); out(); out("Most common search entry counts:"); for (final ObjectPair p : entryCounts) { final long count = p.getSecond(); final double percent = 100.0 * count / numSearches; out(p.getFirst(), ": ", p.getSecond(), " (", decimalFormat.format(percent), "%)"); } } if (! filterTypes.isEmpty()) { final List> filterCounts = getMostCommonElements(filterTypes, 20); out(); out("Most common generic filters for searches with a non-base scope:"); for (final ObjectPair p : filterCounts) { final long count = p.getSecond(); final double percent = 100.0 * count / numNonBaseSearches; out(p.getFirst(), ": ", p.getSecond(), " (", decimalFormat.format(percent), "%)"); } } } final long totalUncached = numUncachedAdds + numUncachedBinds + numUncachedCompares + numUncachedDeletes + numUncachedExtended + numUncachedModifies + numUncachedModifyDNs + numUncachedSearches; if (totalUncached > 0L) { out(); out("Operations accessing uncached data:"); printUncached("Add", numUncachedAdds, numAdds); printUncached("Bind", numUncachedBinds, numBinds); printUncached("Compare", numUncachedCompares, numCompares); printUncached("Delete", numUncachedDeletes, numDeletes); printUncached("Extended", numUncachedExtended, numExtended); printUncached("Modify", numUncachedModifies, numModifies); printUncached("Modify DN", numUncachedModifyDNs, numModifyDNs); printUncached("Search", numUncachedSearches, numSearches); } return ResultCode.SUCCESS; } /** * Retrieves a set of information that may be used to generate example usage * information. Each element in the returned map should consist of a map * between an example set of arguments and a string that describes the * behavior of the tool when invoked with that set of arguments. * * @return A set of information that may be used to generate example usage * information. It may be {@code null} or empty if no example usage * information is available. */ @Override() public LinkedHashMap getExampleUsages() { final LinkedHashMap examples = new LinkedHashMap<>(StaticUtils.computeMapCapacity(1)); final String[] args = { "/ds/logs/access" }; final String description = "Analyze the contents of the /ds/logs/access access log file."; examples.put(args, description); return examples; } /** * Populates the provided processing time map with an initial set of values. * * @param m The processing time map to be populated. */ private static void populateProcessingTimeMap( final HashMap m) { m.put(1L, new AtomicLong(0L)); m.put(2L, new AtomicLong(0L)); m.put(3L, new AtomicLong(0L)); m.put(5L, new AtomicLong(0L)); m.put(10L, new AtomicLong(0L)); m.put(20L, new AtomicLong(0L)); m.put(30L, new AtomicLong(0L)); m.put(50L, new AtomicLong(0L)); m.put(100L, new AtomicLong(0L)); m.put(1000L, new AtomicLong(0L)); m.put(Long.MAX_VALUE, new AtomicLong(0L)); } /** * Performs any necessary processing for a connect message. * * @param m The log message to be processed. */ private void processConnect(final ConnectAccessLogMessage m) { numConnects++; final String clientAddr = m.getSourceAddress(); if (clientAddr != null) { AtomicLong count = clientAddresses.get(clientAddr); if (count == null) { count = new AtomicLong(0L); clientAddresses.put(clientAddr, count); } count.incrementAndGet(); } final String ccp = m.getClientConnectionPolicy(); if (ccp != null) { AtomicLong l = clientConnectionPolicies.get(ccp); if (l == null) { l = new AtomicLong(0L); clientConnectionPolicies.put(ccp, l); } l.incrementAndGet(); } } /** * Performs any necessary processing for a disconnect message. * * @param m The log message to be processed. */ private void processDisconnect(final DisconnectAccessLogMessage m) { numDisconnects++; final String reason = m.getDisconnectReason(); if (reason != null) { AtomicLong l = disconnectReasons.get(reason); if (l == null) { l = new AtomicLong(0L); disconnectReasons.put(reason, l); } l.incrementAndGet(); } } /** * Performs any necessary processing for an abandon request message. * * @param m The log message to be processed. */ private void processAbandonRequest(final AbandonRequestAccessLogMessage m) { numAbandons++; } /** * Performs any necessary processing for an extended request message. * * @param m The log message to be processed. */ private void processExtendedRequest(final ExtendedRequestAccessLogMessage m) { processedRequests.add(m.getConnectionID() + "-" + m.getOperationID()); processExtendedRequestInternal(m); } /** * Performs the internal processing for an extended request message. * * @param m The log message to be processed. */ private void processExtendedRequestInternal( final ExtendedRequestAccessLogMessage m) { final String oid = m.getRequestOID(); if (oid != null) { AtomicLong l = extendedOperations.get(oid); if (l == null) { l = new AtomicLong(0L); extendedOperations.put(oid, l); } l.incrementAndGet(); } } /** * Performs any necessary processing for a search request message. * * @param m The log message to be processed. */ private void processSearchRequest(final SearchRequestAccessLogMessage m) { processedRequests.add(m.getConnectionID() + "-" + m.getOperationID()); processSearchRequestInternal(m); } /** * Performs any necessary processing for a search request message. * * @param m The log message to be processed. */ private void processSearchRequestInternal( final SearchRequestAccessLogMessage m) { final SearchScope scope = m.getScope(); if (scope != null) { if (scope != SearchScope.BASE) { numNonBaseSearches++; } AtomicLong scopeCount = searchScopes.get(scope); if (scopeCount == null) { scopeCount = new AtomicLong(0L); searchScopes.put(scope, scopeCount); } scopeCount.incrementAndGet(); if (! scope.equals(SearchScope.BASE)) { final Filter filter = m.getParsedFilter(); if (filter != null) { final String genericString = new GenericFilter(filter).toString(); AtomicLong filterCount = filterTypes.get(genericString); if (filterCount == null) { filterCount = new AtomicLong(0L); filterTypes.put(genericString, filterCount); } filterCount.incrementAndGet(); } } } } /** * Performs any necessary processing for an unbind request message. * * @param m The log message to be processed. */ private void processUnbindRequest(final UnbindRequestAccessLogMessage m) { numUnbinds++; } /** * Performs any necessary processing for an add result message. * * @param m The log message to be processed. */ private void processAddResult(final AddResultAccessLogMessage m) { numAdds++; updateResultCodeCount(m.getResultCode(), addResultCodes); addProcessingDuration += doubleValue(m.getProcessingTimeMillis(), addProcessingTimes); final Boolean uncachedDataAccessed = m.getUncachedDataAccessed(); if ((uncachedDataAccessed != null) && uncachedDataAccessed) { numUncachedAdds++; } } /** * Performs any necessary processing for a bind result message. * * @param m The log message to be processed. */ private void processBindResult(final BindResultAccessLogMessage m) { numBinds++; updateResultCodeCount(m.getResultCode(), bindResultCodes); bindProcessingDuration += doubleValue(m.getProcessingTimeMillis(), bindProcessingTimes); final String ccp = m.getClientConnectionPolicy(); if (ccp != null) { AtomicLong l = clientConnectionPolicies.get(ccp); if (l == null) { l = new AtomicLong(0L); clientConnectionPolicies.put(ccp, l); } l.incrementAndGet(); } final Boolean uncachedDataAccessed = m.getUncachedDataAccessed(); if ((uncachedDataAccessed != null) && uncachedDataAccessed) { numUncachedBinds++; } } /** * Performs any necessary processing for a compare result message. * * @param m The log message to be processed. */ private void processCompareResult(final CompareResultAccessLogMessage m) { numCompares++; updateResultCodeCount(m.getResultCode(), compareResultCodes); compareProcessingDuration += doubleValue(m.getProcessingTimeMillis(), compareProcessingTimes); final Boolean uncachedDataAccessed = m.getUncachedDataAccessed(); if ((uncachedDataAccessed != null) && uncachedDataAccessed) { numUncachedCompares++; } } /** * Performs any necessary processing for a delete result message. * * @param m The log message to be processed. */ private void processDeleteResult(final DeleteResultAccessLogMessage m) { numDeletes++; updateResultCodeCount(m.getResultCode(), deleteResultCodes); deleteProcessingDuration += doubleValue(m.getProcessingTimeMillis(), deleteProcessingTimes); final Boolean uncachedDataAccessed = m.getUncachedDataAccessed(); if ((uncachedDataAccessed != null) && uncachedDataAccessed) { numUncachedDeletes++; } } /** * Performs any necessary processing for an extended result message. * * @param m The log message to be processed. */ private void processExtendedResult(final ExtendedResultAccessLogMessage m) { numExtended++; final String id = m.getConnectionID() + "-" + m.getOperationID(); if (!processedRequests.remove(id)) { processExtendedRequestInternal(m); } updateResultCodeCount(m.getResultCode(), extendedResultCodes); extendedProcessingDuration += doubleValue(m.getProcessingTimeMillis(), extendedProcessingTimes); final String ccp = m.getClientConnectionPolicy(); if (ccp != null) { AtomicLong l = clientConnectionPolicies.get(ccp); if (l == null) { l = new AtomicLong(0L); clientConnectionPolicies.put(ccp, l); } l.incrementAndGet(); } final Boolean uncachedDataAccessed = m.getUncachedDataAccessed(); if ((uncachedDataAccessed != null) && uncachedDataAccessed) { numUncachedExtended++; } } /** * Performs any necessary processing for a modify result message. * * @param m The log message to be processed. */ private void processModifyResult(final ModifyResultAccessLogMessage m) { numModifies++; updateResultCodeCount(m.getResultCode(), modifyResultCodes); modifyProcessingDuration += doubleValue(m.getProcessingTimeMillis(), modifyProcessingTimes); final Boolean uncachedDataAccessed = m.getUncachedDataAccessed(); if ((uncachedDataAccessed != null) && uncachedDataAccessed) { numUncachedModifies++; } } /** * Performs any necessary processing for a modify DN result message. * * @param m The log message to be processed. */ private void processModifyDNResult(final ModifyDNResultAccessLogMessage m) { numModifyDNs++; updateResultCodeCount(m.getResultCode(), modifyDNResultCodes); modifyDNProcessingDuration += doubleValue(m.getProcessingTimeMillis(), modifyDNProcessingTimes); final Boolean uncachedDataAccessed = m.getUncachedDataAccessed(); if ((uncachedDataAccessed != null) && uncachedDataAccessed) { numUncachedModifyDNs++; } } /** * Performs any necessary processing for a search result message. * * @param m The log message to be processed. */ private void processSearchResult(final SearchResultAccessLogMessage m) { numSearches++; final String id = m.getConnectionID() + "-" + m.getOperationID(); if (!processedRequests.remove(id)) { processSearchRequestInternal(m); } final ResultCode resultCode = m.getResultCode(); updateResultCodeCount(resultCode, searchResultCodes); searchProcessingDuration += doubleValue(m.getProcessingTimeMillis(), searchProcessingTimes); final Long entryCount = m.getEntriesReturned(); if (entryCount != null) { AtomicLong l = searchEntryCounts.get(entryCount); if (l == null) { l = new AtomicLong(0L); searchEntryCounts.put(entryCount, l); } l.incrementAndGet(); } final Boolean isUnindexed = m.isUnindexed(); if ((isUnindexed != null) && isUnindexed) { numUnindexedAttempts++; if (resultCode == ResultCode.SUCCESS) { numUnindexedSuccessful++; } else { numUnindexedFailed++; } } final Boolean uncachedDataAccessed = m.getUncachedDataAccessed(); if ((uncachedDataAccessed != null) && uncachedDataAccessed) { numUncachedSearches++; } } /** * Updates the count for the provided result code in the given map. * * @param rc The result code for which to update the count. * @param m The map used to hold counts by result code. */ private static void updateResultCodeCount(final ResultCode rc, final HashMap m) { if (rc == null) { return; } AtomicLong l = m.get(rc); if (l == null) { l = new AtomicLong(0L); m.put(rc, l); } l.incrementAndGet(); } /** * Retrieves the double value for the provided {@code Double} object. * * @param d The {@code Double} object for which to retrieve the value. * @param m The processing time histogram map to be updated. * * @return The double value of the provided {@code Double} object if it was * non-{@code null}, or 0.0 if it was {@code null}. */ private static double doubleValue(final Double d, final HashMap m) { if (d == null) { return 0.0; } else { for (final Map.Entry e : m.entrySet()) { if (d <= e.getKey()) { e.getValue().incrementAndGet(); break; } } return d; } } /** * Retrieves a list of the most frequently-occurring elements in the * provided map, paired with the number of times each value occurred. * * @param The type of object used as the key for the provided map. * @param m The map to be examined. It is expected that the values of the * map will be the count of occurrences for the keys. * @param n The number of elements to return. * * @return A list of the most frequently-occurring elements in the provided * map. */ private static List> getMostCommonElements( final Map m, final int n) { final TreeMap> reverseMap = new TreeMap<>(new ReverseComparator()); for (final Map.Entry e : m.entrySet()) { final Long count = e.getValue().get(); List list = reverseMap.get(count); if (list == null) { list = new ArrayList<>(n); reverseMap.put(count, list); } list.add(e.getKey()); } final ArrayList> returnList = new ArrayList<>(n); for (final Map.Entry> e : reverseMap.entrySet()) { final Long l = e.getKey(); for (final K k : e.getValue()) { returnList.add(new ObjectPair<>(k, l)); } if (returnList.size() >= n) { break; } } return returnList; } /** * Writes a breakdown of the processing times for a specified type of * operation. * * @param t The name of the operation type. * @param n The total number of operations of the specified type that were * processed by the server. * @param m The map of operation counts by processing time bucket. */ private void printProcessingTimeHistogram(final String t, final long n, final LinkedHashMap m) { if (n <= 0) { return; } out(); out("Count of ", t, " operations by processing time:"); long lowerBound = 0; long accumulatedCount = 0; final Iterator> i = m.entrySet().iterator(); while (i.hasNext()) { final Map.Entry e = i.next(); final long upperBound = e.getKey(); final long count = e.getValue().get(); final double categoryPercent = 100.0 * count / n; accumulatedCount += count; final double accumulatedPercent = 100.0 * accumulatedCount / n; if (i.hasNext()) { out("Between ", lowerBound, "ms and ", upperBound, "ms: ", count, " (", decimalFormat.format(categoryPercent), "%, ", decimalFormat.format(accumulatedPercent), "% accumulated)"); lowerBound = upperBound; } else { out("Greater than ", lowerBound, "ms: ", count, " (", decimalFormat.format(categoryPercent), "%, ", decimalFormat.format(accumulatedPercent), "% accumulated)"); } } } /** * Optionally prints information about the number and percent of operations of * the specified type that involved access to uncached data. * * @param operationType The type of operation. * @param numUncached The number of operations of the specified type that * involved access to uncached data. * @param numTotal The total number of operations of the specified * type. */ private void printUncached(final String operationType, final long numUncached, final long numTotal) { if (numUncached == 0) { return; } out(operationType, ": ", numUncached, " (", decimalFormat.format(100.0 * numUncached / numTotal), "%)"); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy