Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*
* Copyright 2009-2019 Ping Identity Corporation
* All Rights Reserved.
*/
/*
* Copyright (C) 2015-2019 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), "%)");
}
}