com.gemstone.gemfire.internal.statistics.StatArchiveHandler Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of gemfire-core Show documentation
Show all versions of gemfire-core Show documentation
SnappyData store based off Pivotal GemFireXD
/*
* Copyright (c) 2010-2015 Pivotal Software, Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you
* may not use this file except in compliance with the License. You
* may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
* implied. See the License for the specific language governing
* permissions and limitations under the License. See accompanying
* LICENSE file.
*/
package com.gemstone.gemfire.internal.statistics;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.List;
import java.util.regex.Pattern;
import com.gemstone.gemfire.GemFireException;
import com.gemstone.gemfire.GemFireIOException;
import com.gemstone.gemfire.i18n.LogWriterI18n;
import com.gemstone.gemfire.internal.ManagerLogWriter;
import com.gemstone.gemfire.internal.StatArchiveWriter;
import com.gemstone.gemfire.internal.i18n.LocalizedStrings;
/**
* Extracted from {@link com.gemstone.gemfire.internal.HostStatSampler} and
* {@link com.gemstone.gemfire.internal.GemFireStatSampler}.
*
* The StatArchiveHandler handles statistics samples by archiving them to a
* file. This handler provides archive file rolling (file size limit) and
* removal (disk space limit). This handler creates and uses an instance of
* {@link com.gemstone.gemfire.internal.StatArchiveWriter} for the currently
* open archive file (unless archiving is disabled).
*
* @author Darrel Schneider
* @author Kirk Lund
* @since 7.0
*/
public class StatArchiveHandler implements SampleHandler {
/** Enable debug logging if true. */
private final boolean debug = Boolean.getBoolean("gemfire.stats.debug.debugStatArchiveHandler");
/** Configuration used in constructing this handler instance. */
private final StatArchiveHandlerConfig config;
/** The collector responsible for sample statistics and notifying handlers. */
private final SampleCollector collector;
/**
* Indicates if archiving has been disabled by specifying empty string for
* the archive file name. Other threads may call in to changeArchiveFile
* to manipulate this flag.
*/
private volatile boolean disabledArchiving = false;
/** The currently open writer/file. Protected by synchronization on this handler instance. */
private StatArchiveWriter archiver = null;
/** Directory to contain archive files. */
private File archiveDir = null;
/** The first of two numbers used within the name of rolling archive files. */
private int mainArchiveId = -1;
/** The second of two numbers used within the name of rolling archive files. */
private int archiveId = -1;
/**
* Constructs a new instance. The {@link StatArchiveHandlerConfig} and
* {@link SampleCollector} must not be null.
*/
public StatArchiveHandler(StatArchiveHandlerConfig config,
SampleCollector sampleCollector) {
this.config = config;
this.collector = sampleCollector;
}
/**
* Initializes the stat archiver with nanosTimeStamp.
* @param nanosTimeStamp
*/
public void initialize(long nanosTimeStamp) {
changeArchiveFile(false, nanosTimeStamp);
assertInitialized();
}
/**
* Closes any {@link com.gemstone.gemfire.internal.StatArchiveWriter}
* currently in use by this handler.
* @throws GemFireException
*/
public void close() throws GemFireException {
synchronized (this) {
if (archiver != null) {
archiver.close();
}
}
}
private void handleArchiverException(GemFireException ex) {
if (this.archiver.getSampleCount() > 0) {
StringWriter sw = new StringWriter();
ex.printStackTrace(new PrintWriter(sw, true));
this.collector.getLogWriterI18n().warning(LocalizedStrings.HostStatSampler_STATISTIC_ARCHIVER_SHUTTING_DOWN_BECAUSE__0, sw);
}
try {
this.archiver.close();
} catch (GemFireException ignore) {
if (this.archiver.getSampleCount() > 0) {
this.collector.getLogWriterI18n().warning(LocalizedStrings.HostStatSampler_STATISIC_ARCHIVER_SHUTDOWN_FAILED_BECAUSE__0, ignore.getMessage());
}
}
if (this.archiver.getSampleCount() == 0 && this.archiveId != -1) {
// dec since we didn't use the file and close deleted it.
this.archiveId--;
}
this.archiver = null;
}
@Override
public void sampled(long nanosTimeStamp, List resourceInstances) {
synchronized (this) {
if (this.debug) {
this.collector.getLogWriter().info("DEBUG StatArchiveHandler#sampled resourceInstances=" + resourceInstances);
}
if (archiver != null) {
try {
archiver.sampled(nanosTimeStamp, resourceInstances);
if (archiver.getSampleCount() == 1) {
LogWriterI18n mainLogger = this.collector.getLogWriterI18n();
mainLogger.info(LocalizedStrings.GemFireStatSampler_ARCHIVING_STATISTICS_TO__0_, archiver.getArchiveName());
}
} catch (IllegalArgumentException e) {
this.collector.getLogWriter().warning("Use of java.lang.System.nanoTime() resulted in a non-positive timestamp delta. Skipping archival of statistics sample.", e);
} catch (GemFireException ex) {
handleArchiverException(ex); // this will null out archiver
}
if (archiver != null) { // fix npe seen in bug 46917
long byteLimit = config.getArchiveFileSizeLimit();
if (byteLimit != 0) {
long bytesWritten = archiver.bytesWritten();
if (bytesWritten > byteLimit) {
// roll the archive
try {
changeArchiveFile(true, nanosTimeStamp);
} catch (GemFireIOException ignore) {
// it has already been logged
// We don't want this exception to kill this thread. See 46917
}
}
}
}
} else {
// Check to see if archiving is enabled.
if (!this.config.getArchiveFileName().getPath().equals("")) {
// It is enabled so we must not have an archiver due to an exception.
// So try to recreate the archiver. See bug 46917.
try {
changeArchiveFile(true, nanosTimeStamp);
} catch (GemFireIOException ignore) {
}
}
}
} // sync
}
void assertInitialized() {
if (archiver == null && !this.config.getArchiveFileName().getPath().equals("")) {
throw new IllegalStateException("This " + this + " was not initialized");
}
}
@Override
public void allocatedResourceType(ResourceType resourceType) {
if (this.debug) {
this.collector.getLogWriter().info("DEBUG StatArchiveHandler#allocatedResourceType resourceType=" + resourceType);
}
if (archiver != null) {
try {
archiver.allocatedResourceType(resourceType);
} catch (GemFireException ex) {
handleArchiverException(ex);
}
}
}
@Override
public void allocatedResourceInstance(ResourceInstance resourceInstance) {
if (this.debug) {
this.collector.getLogWriter().info("DEBUG StatArchiveHandler#allocatedResourceInstance resourceInstance=" + resourceInstance);
}
if (archiver != null) {
try {
archiver.allocatedResourceInstance(resourceInstance);
} catch (GemFireException ex) {
handleArchiverException(ex);
}
}
}
@Override
public void destroyedResourceInstance(ResourceInstance resourceInstance) {
if (this.debug) {
this.collector.getLogWriter().info("DEBUG StatArchiveHandler#destroyedResourceInstance resourceInstance=" + resourceInstance);
}
if (archiver != null) {
try {
archiver.destroyedResourceInstance(resourceInstance);
} catch (GemFireException ex) {
handleArchiverException(ex);
}
}
}
/**
* Returns the configuration for this handler.
*/
public StatArchiveHandlerConfig getStatArchiveHandlerConfig() {
return this.config;
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder(getClass().getName());
sb.append("@").append(System.identityHashCode(this)).append("{");
sb.append("config=").append(this.config);
sb.append(", archiveDir=").append(this.archiveDir);
sb.append(", mainArchiveId=").append(this.mainArchiveId);
sb.append(", archiveId=").append(this.archiveId);
sb.append(", archiver=").append(this.archiver);
sb.append("}");
return sb.toString();
}
/**
* Changes the archive file to the new file or disables archiving if an
* empty string is specified. This may be invoked by any thread other than
* the stat sampler.
*
* If the file name matches any archive file(s) already in {@link #archiveDir}
* then this may trigger rolling and/or removal if appropriate based on {@link
* StatArchiveHandlerConfig#getArchiveFileSizeLimit() file size limit} and
* {@link StatArchiveHandlerConfig#getArchiveDiskSpaceLimit() disk space
* limit}.
*
* @param newFile the new archive file to use or "" to disable archiving
* @param nanosTimeStamp
*/
protected void changeArchiveFile(File newFile, long nanosTimeStamp) {
changeArchiveFile(newFile, true, nanosTimeStamp);
}
protected boolean isArchiving() {
return this.archiver != null && this.archiver.bytesWritten() > 0;
}
/**
* Changes the archive file using the same configured archive file name.
*
* If the file name matches any archive file(s) already in {@link #archiveDir}
* then this may trigger rolling and/or removal if appropriate based on {@link
* StatArchiveHandlerConfig#getArchiveFileSizeLimit() file size limit} and
* {@link StatArchiveHandlerConfig#getArchiveDiskSpaceLimit() disk space
* limit}.
*
* If resetHandler is true, then this handler will reset itself with the
* SampleCollector by removing and re-adding itself in order to receive
* allocation notifications about all resource types and instances.
*
* @param resetHandler true if the handler should reset itself with the
* SampleCollector in order to receive allocation notifications about all
* resource types and instances
*
* @param nanosTimeStamp
*/
private void changeArchiveFile(boolean resetHandler, long nanosTimeStamp) {
changeArchiveFile(this.config.getArchiveFileName(), resetHandler, nanosTimeStamp);
}
/**
* Changes the archive file to the new file or disables archiving if an
* empty string is specified.
*
* If the file name matches any archive file(s) already in {@link #archiveDir}
* then this may trigger rolling and/or removal if appropriate based on {@link
* StatArchiveHandlerConfig#getArchiveFileSizeLimit() file size limit} and
* {@link StatArchiveHandlerConfig#getArchiveDiskSpaceLimit() disk space
* limit}.
*
* If resetHandler is true, then this handler will reset itself with the
* SampleCollector by removing and re-adding itself in order to receive
* allocation notifications about all resource types and instances.
*
* @param newFile
* @param resetHandler
* @param nanosTimeStamp
*/
private void changeArchiveFile(File newFile, boolean resetHandler, long nanosTimeStamp) {
if (this.debug) {
this.collector.getLogWriter().info("DEBUG StatArchiveHandler#changeArchiveFile newFile=" + newFile + ", nanosTimeStamp=" + nanosTimeStamp);
}
LogWriterI18n mainLogger = this.collector.getLogWriterI18n();
StatArchiveWriter newArchiver = null;
boolean archiveClosed = false;
if (newFile.getPath().equals("")) {
// disable archiving
if (!this.disabledArchiving) {
this.disabledArchiving = true;
mainLogger.info(LocalizedStrings.GemFireStatSampler_DISABLING_STATISTIC_ARCHIVAL);
}
} else {
this.disabledArchiving = false;
if (this.config.getArchiveFileSizeLimit() != 0) {
// To fix bug 51133 need to always write to newFile.
// Need to close any existing archive and then rename it
// to getRollingArchiveName(newFile).
if (archiver != null) {
archiveClosed = true;
synchronized (this) {
if (resetHandler) {
if (this.debug) {
this.collector.getLogWriter().info("DEBUG StatArchiveHandler#changeArchiveFile removing handler");
}
this.collector.removeSampleHandler(this);
}
try {
archiver.close();
} catch (GemFireException ignore) {
this.collector.getLogWriterI18n().warning(LocalizedStrings.GemFireStatSampler_STATISTIC_ARCHIVE_CLOSE_FAILED_BECAUSE__0, ignore.getMessage());
}
}
}
}
if (newFile.exists()) {
File oldFile;
if (this.config.getArchiveFileSizeLimit() != 0) {
oldFile = getRollingArchiveName(newFile, archiveClosed);
} else {
oldFile = getRenameArchiveName(newFile);
}
if (!newFile.renameTo(oldFile)) {
mainLogger.warning(
LocalizedStrings.GemFireStatSampler_COULD_NOT_RENAME_0_TO_1,
new Object[] {newFile, oldFile});
} else {
mainLogger.info(LocalizedStrings.GemFireStatSampler_RENAMED_OLD_EXISTING_ARCHIVE_TO__0_, oldFile);
}
} else {
if (!newFile.getAbsoluteFile().getParentFile().equals(archiveDir)) {
this.archiveDir = newFile.getAbsoluteFile().getParentFile();
if (!this.archiveDir.exists()) {
this.archiveDir.mkdirs();
}
}
if (this.config.getArchiveFileSizeLimit() != 0) {
initMainArchiveId(newFile);
}
}
try {
StatArchiveDescriptor archiveDescriptor = new StatArchiveDescriptor.Builder()
.setArchiveName(newFile.getPath())
.setSystemId(this.config.getSystemId())
.setSystemStartTime(this.config.getSystemStartTime())
.setSystemDirectoryPath(this.config.getSystemDirectoryPath())
.setProductDescription(this.config.getProductDescription())
.build();
newArchiver = new StatArchiveWriter(archiveDescriptor, mainLogger);
newArchiver.initialize(nanosTimeStamp);
} catch (GemFireIOException ex) {
mainLogger.warning(
LocalizedStrings.GemFireStatSampler_COULD_NOT_OPEN_STATISTIC_ARCHIVE_0_CAUSE_1,
new Object[] {newFile, ex.getLocalizedMessage()});
throw ex;
}
}
synchronized (this) {
if (archiveClosed) {
if (archiver != null) {
removeOldArchives(
newFile, mainLogger, this.config.getArchiveDiskSpaceLimit());
}
} else {
if (resetHandler) {
if (this.debug) {
this.collector.getLogWriter().info("DEBUG StatArchiveHandler#changeArchiveFile removing handler");
}
this.collector.removeSampleHandler(this);
}
if (archiver != null) {
try {
archiver.close();
} catch (GemFireException ignore) {
this.collector.getLogWriterI18n().warning(LocalizedStrings.GemFireStatSampler_STATISTIC_ARCHIVE_CLOSE_FAILED_BECAUSE__0, ignore.getMessage());
}
removeOldArchives(
newFile, mainLogger, this.config.getArchiveDiskSpaceLimit());
}
}
archiver = newArchiver;
if (resetHandler && newArchiver != null) {
if (this.debug) {
this.collector.getLogWriter().info("DEBUG StatArchiveHandler#changeArchiveFile adding handler");
}
this.collector.addSampleHandler(this);
}
}
}
/**
* Returns the modified archive file name to use after incrementing {@link
* #mainArchiveId} and {@link #archiveId} based on existing files
* {@link #archiveDir}. This is only used if {@link
* StatArchiveHandlerConfig#getArchiveFileSizeLimit() file size limit} has
* been specified as non-zero (which enables file rolling).
*
* @param archive the archive file name to modify
* @param archiveClosed true if archive was just being written by us; false if it was written by the previous process.
*
* @return the modified archive file name to use; it is modified by applying
* mainArchiveId and archiveId to the name for supporting file rolling
*/
private File getRollingArchiveName(File archive, boolean archiveClosed) {
if (mainArchiveId != -1) {
// leave mainArchiveId as is. Bump archiveId.
} else {
archiveDir = archive.getAbsoluteFile().getParentFile();
LogWriterI18n log = this.collector.getLogWriterI18n();
boolean mainArchiveIdCalculated = false;
if (log instanceof ManagerLogWriter) {
ManagerLogWriter mlw = (ManagerLogWriter)log;
File logDir = mlw.getLogDir();
if (archiveDir.equals(logDir)) {
mainArchiveId = mlw.getMainLogId();
if (mainArchiveId > 1 && mlw.useChildLogging()) {
mainArchiveId--;
}
mainArchiveIdCalculated = true;
}
}
if (!mainArchiveIdCalculated) {
if (!archiveDir.exists()) {
if (!archiveDir.mkdirs()) {
// We ignore that we couldn't create the directory.
// This will be caught later when we try to create a file in this directory.
}
}
mainArchiveId = ManagerLogWriter.calcNextMainId(archiveDir, false);
mainArchiveIdCalculated = true;
}
if (mainArchiveId == 0) {
mainArchiveId = 1;
}
archiveId = ManagerLogWriter.calcNextChildId(archive, mainArchiveId);
if (archiveId > 0) {
archiveId--;
}
}
File result = null;
do {
archiveId++;
StringBuffer buf = new StringBuffer(archive.getPath());
int insertIdx = buf.lastIndexOf(".");
if (insertIdx == -1) {
buf
.append(ManagerLogWriter.formatId(mainArchiveId))
.append(ManagerLogWriter.formatId(archiveId));
} else {
buf.insert(insertIdx, ManagerLogWriter.formatId(archiveId));
buf.insert(insertIdx, ManagerLogWriter.formatId(mainArchiveId));
}
result = new File(buf.toString());
} while (result.exists());
if (archiveId == 1) {
// see if a marker file exists. If so delete it.
String markerName = archive.getPath();
int dotIdx = markerName.lastIndexOf(".");
if (dotIdx != -1) {
// strip the extension off
markerName = markerName.substring(0, dotIdx);
}
StringBuffer buf = new StringBuffer(markerName);
buf.append(ManagerLogWriter.formatId(mainArchiveId))
.append(ManagerLogWriter.formatId(0))
.append(".marker");
File marker = new File(buf.toString());
if (marker.exists()) {
if (!marker.delete()) {
// could not delete it; nothing to be done
}
}
}
if (!archiveClosed) {
mainArchiveId++;
archiveId = 0;
// create an empty file which we can use on startup when we don't roll
// to correctly rename the old archive that did not roll.
String markerName = archive.getPath();
int dotIdx = markerName.lastIndexOf(".");
if (dotIdx != -1) {
// strip the extension off
markerName = markerName.substring(0, dotIdx);
}
StringBuffer buf = new StringBuffer(markerName);
buf.append(ManagerLogWriter.formatId(mainArchiveId))
.append(ManagerLogWriter.formatId(0))
.append(".marker");
File marker = new File(buf.toString());
if (!marker.exists()) {
try {
if (!marker.createNewFile()) {
// could not create it; that is ok
}
} catch (IOException ignore) {
// If we can't create the marker that is ok
}
}
}
return result;
}
private void initMainArchiveId(File archive) {
if (mainArchiveId != -1) {
// already initialized
return;
}
archiveDir = archive.getAbsoluteFile().getParentFile();
LogWriterI18n log = this.collector.getLogWriterI18n();
boolean mainArchiveIdCalculated = false;
if (log instanceof ManagerLogWriter) {
ManagerLogWriter mlw = (ManagerLogWriter)log;
File logDir = mlw.getLogDir();
if (archiveDir.equals(logDir)) {
mainArchiveId = mlw.getMainLogId();
mainArchiveIdCalculated = true;
}
}
if (!mainArchiveIdCalculated) {
if (!archiveDir.exists()) {
archiveDir.mkdirs();
}
mainArchiveId = ManagerLogWriter.calcNextMainId(archiveDir, false);
mainArchiveId++;
mainArchiveIdCalculated = true;
}
if (mainArchiveId == 0) {
mainArchiveId = 1;
}
archiveId = 0;
// create an empty file which we can use on startup when we don't roll
// to correctly rename the old archive that did not roll.
String markerName = archive.getPath();
int dotIdx = markerName.lastIndexOf(".");
if (dotIdx != -1) {
// strip the extension off
markerName = markerName.substring(0, dotIdx);
}
StringBuffer buf = new StringBuffer(markerName);
buf.append(ManagerLogWriter.formatId(mainArchiveId))
.append(ManagerLogWriter.formatId(0))
.append(".marker");
File marker = new File(buf.toString());
if (!marker.exists()) {
try {
if (!marker.createNewFile()) {
// could not create it; that is ok
}
} catch (IOException ignore) {
// If we can't create the marker that is ok
}
}
}
/**
* Modifies the desired archive file name with a main id (similar to {@link
* #mainArchiveId} if the archive file's dir already contains GemFire
* stat archive or log files containing a main id in the file name.
*
* @param archive the archive file name to modify
*
* @return the modified archive file name to use; it is modified by applying
* the next main id if any files in the dir already have a main id in the file
* name
*/
private static File getRenameArchiveName(File archive) {
File dir = archive.getAbsoluteFile().getParentFile();
int previousMainId = ManagerLogWriter.calcNextMainId(dir, false);
if (previousMainId==0) {
previousMainId = 1;
}
previousMainId--;
File result = null;
do {
previousMainId++;
StringBuffer buf = new StringBuffer(archive.getPath());
int insertIdx = buf.lastIndexOf(".");
if (insertIdx == -1) {
buf
.append(ManagerLogWriter.formatId(previousMainId))
.append(ManagerLogWriter.formatId(1));
} else {
buf.insert(insertIdx, ManagerLogWriter.formatId(1));
buf.insert(insertIdx, ManagerLogWriter.formatId(previousMainId));
}
result = new File(buf.toString());
} while (result.exists());
return result;
}
/**
* Remove old versions of the specified archive file name in order to stay
* under the specified disk space limit. Old versions of the archive file
* are those that match based on using {@link #getArchivePattern(String)}
* which ignores mainArchiveId and archiveId.
*
* @param archiveFile the archive file to remove old versions of
* @param log an open log writer to use for logging
* @param spaceLimit the disk space limit
*/
private static void removeOldArchives(File archiveFile, LogWriterI18n log, long spaceLimit) {
if (spaceLimit == 0
|| archiveFile == null
|| archiveFile.getPath().equals("")) {
return;
}
File archiveDir = archiveFile.getAbsoluteFile().getParentFile();
ManagerLogWriter.checkDiskSpace("archive", archiveFile,
spaceLimit,
archiveDir,
getArchivePattern(archiveFile.getName()),
log);
}
/**
* Create a regex pattern which will match the specified archive file name
* even if it has a mainArchiveId and/or archiveId.
*
* @param name archive file name to create a regex pattern for
* @return regex pattern to use in finding matching file names
*/
private static Pattern getArchivePattern(String name) {
String ext = "";
int extIdx = name.lastIndexOf('.');
if (extIdx != -1) {
ext = "\\Q" + name.substring(extIdx) + "\\E";
name = name.substring(0, extIdx);
}
/* name may have -DD-DD on the end of it. Trim that part off. */
int dashIdx = name.indexOf('-');
if (dashIdx != -1) {
name = name.substring(0, dashIdx);
}
name = "\\Q" + name + "\\E" + "-\\d+-\\d+" + ext;
return Pattern.compile(name);
}
}