/* * * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) * * * * 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. * * * * For more information: http://www.orientechnologies.com * */ package com.orientechnologies.orient.core.storage.impl.local.statistic; import com.orientechnologies.orient.core.metadata.schema.OType; import com.orientechnologies.orient.core.record.impl.ODocument; import com.orientechnologies.orient.core.storage.impl.local.OAbstractPaginatedStorage; import java.util.ArrayDeque; import java.util.Deque; import java.util.HashMap; import java.util.Map; /** * Container for performance statistic gathered after call of * {@link OAbstractPaginatedStorage#startGatheringPerformanceStatisticForCurrentThread()}. * <p> * Statistic is gathered on component and system level. Each * {@link com.orientechnologies.orient.core.storage.impl.local.paginated.base.ODurableComponent} provides separate data for this * tool which allows to detect performance problems on component level. * <p> * To stop gathering of performance statistic call * {@link OAbstractPaginatedStorage#completeGatheringPerformanceStatisticForCurrentThread()}. * <p> * List of gathered performance characteristics can be deduced from getXXX methods. There are 2 kind of methods , one kind do not * accept any parameters, they return performance data on system level and, other kind accept component name, they return * performance data gathered on separate component or on system level if null is passed as method name. If data from component with * passed in name is absent then -1 is returned. * <p> * Some data are gathered only on system level for example write ahead log performance data or commit time {@link #getCommitTime()}. * <p> * This container may be used both for gathering information in single thread and in mutlithreading environment. Container itself is * not thread safe, but it supports snapshoting of data. * <p> * Every time when new data are gathered by container it checks whether minimum interval between two snapshots is passed and makes * data snapshot if needed, data snapshot can be used by aggregator which merges statistics from all containers together. Only last * snapshot is hold by container. * <p> * Also container supports time series functionality by clearing of previous statics after provided time interval. * * @author Andrey Lomakin * @see OPerformanceStatisticManager */ public class OSessionStoragePerformanceStatistic { /** * Amount of nanoseconds in second */ private static final int NANOS_IN_SECOND = 1000000000; /** * Stack of time stamps which is used to init clock in startTimerXXX methods. */ private final Deque<Long> timeStamps = new ArrayDeque<Long>(); /** * Stack of active components or in another words components which currently perform actions inside current thread. */ private Deque<Component> componentsStack = new ArrayDeque<Component>(); /** * Container for performance counters of system performance as whole. * <p> * Counters are put in separate class because each component also has given performance counters, so definition of counters on * component and system level is reused. */ private final PerformanceCountersHolder performanceCountersHolder; /** * Object which is used to get current PC nano time. */ private final NanoTimer nanoTimer; /** * Minimum interval in nanoseconds between two snapshots */ private final long intervalBetweenSnapshots; /** * Interval between two time series in nanoseconds. When this interval is passed, new attempt to make data snapshot will clear * performance statistic. <code>-1</code> means that performance data will not be cleared. */ private final long cleanUpInterval; /** * Time stamp in nanoseconds when last snapshot is taken by container. */ private long lastSnapshotTimestamp = -1; /** * Time stamp in nanoseconds when data was cleaned up last time. */ private long lastCleanUpTimeStamp = -1; /** * Snapshot of all data measured during session. */ private volatile PerformanceSnapshot snapshot; /** * Map containing performance counters specific for concrete software component. */ private Map<String, PerformanceCountersHolder> countersByComponent = new HashMap<String, PerformanceCountersHolder>(); /** * Performance statistic gathered from write cache. It is lazy initialized to decrease memory consumption. */ private WritCacheCountersHolder writCacheCountersHolder; /** * Storage performance characteristics. It is lazy initialized to decrease memory consumption. */ private StorageCountersHolder storageCountersHolder; /** * Write ahead log performance characteristics. It is lazy initialized to decrease memory consumption. */ private WALCountersHolder walCountersHolder; /** * @param intervalBetweenSnapshots * Minimum interval between two snapshots taken by container. * @param cleanUpInterval * Minimum interval between two time series, in other words container clears all statistic during next try of making * snapshot after this interval is over. <code>-1</code> means that data will not be cleared. */ public OSessionStoragePerformanceStatistic(long intervalBetweenSnapshots, long cleanUpInterval) { this(intervalBetweenSnapshots, new NanoTimer() { @Override public long getNano() { return System.nanoTime(); } }, cleanUpInterval); } /** * @param nanoTimer * Service to get current value of PC nano time. * @param intervalBetweenSnapshots * Minimum interval between two snapshots taken by container. * @param cleanUpInterval * Minimum interval between two time series, in other words container clears all statistic during next try of making * snapshot after this interval is over. <code>-1</code> means that data will not be cleared. */ public OSessionStoragePerformanceStatistic(long intervalBetweenSnapshots, NanoTimer nanoTimer, long cleanUpInterval) { this.nanoTimer = nanoTimer; this.intervalBetweenSnapshots = intervalBetweenSnapshots; this.performanceCountersHolder = ComponentType.GENERAL.newCountersHolder(); this.cleanUpInterval = cleanUpInterval; this.lastCleanUpTimeStamp = nanoTimer.getNano(); } /** * Called inside of {@link com.orientechnologies.orient.core.storage.impl.local.paginated.base.ODurableComponent} to notify that * component started to perform operation on data. After that all performance characteristic started to be gathered for this * component till method {@link #completeComponentOperation()} will be called. * <p> * Components can be stacked, so if components <code>c1</code> and then <code>c2</code> call this method than performance data for * both components at once started to be gathered. * * @param componentName * Name of component which started to perform operation on data. Name is case sensitive. * @param type */ public void startComponentOperation(String componentName, ComponentType type) { final Component currentComponent = componentsStack.peek(); if (currentComponent != null && componentName.equals(currentComponent.name)) { currentComponent.operationCount++; return; } componentsStack.push(new Component(componentName, type)); } /** * Indicates that the most earliest component in stack of components has completed it's operation, so performance data for this * component is stopped to be gathered. * * @see #startComponentOperation(String, ComponentType) */ public void completeComponentOperation() { final Component currentComponent = componentsStack.peek(); if (currentComponent == null) return; currentComponent.operationCount--; if (currentComponent.operationCount == 0) { final String componentName = currentComponent.name; PerformanceCountersHolder cHolder = countersByComponent.get(componentName); if (cHolder == null) { cHolder = currentComponent.type.newCountersHolder(); countersByComponent.put(componentName, cHolder); } cHolder.operationsCount++; componentsStack.pop(); makeSnapshotIfNeeded(-1); } } /** * @return Read speed of data in pages per second on cache level or value which is less than 0, which means that value cannot be * calculated. */ public long getReadSpeedFromCacheInPages() { return performanceCountersHolder.getReadSpeedFromCacheInPages(); } /** * Read speed of data in pages per second on cache level for component name of which is passed as method argument. If null value * is passed then value for whole system will be returned. If data for component with passed in name does not exist then * <code>-1</code> will be returned. * * @param componentName * Name of component data of which should be returned. Name is case sensitive. * @return Read speed of data in pages per second on cache level or value which is less than 0, which means that value cannot be * calculated. */ public long getReadSpeedFromCacheInPages(String componentName) { if (componentName == null) return performanceCountersHolder.getReadSpeedFromCacheInPages(); final PerformanceCountersHolder cHolder = countersByComponent.get(componentName); if (cHolder != null) return cHolder.getReadSpeedFromCacheInPages(); return -1; } /** * @return Read speed of data from file system in pages per second or value which is less than 0, which means that value cannot be * calculated. */ public long getReadSpeedFromFileInPages() { return performanceCountersHolder.getReadSpeedFromFileInPages(); } /** * Read speed of data from file system in pages for component name of which is passed as method argument. If null value is passed * then value for whole system will be returned. If data for component with passed in name does not exist then <code>-1</code> * will be returned. * * @param componentName * Name of component data of which should be returned. Name is case sensitive. * @return Read speed of data from file system in pages per second or value which is less than 0, which means that value cannot be * calculated. */ public long getReadSpeedFromFileInPages(String componentName) { if (componentName == null) return performanceCountersHolder.getReadSpeedFromFileInPages(); final PerformanceCountersHolder cHolder = countersByComponent.get(componentName); if (cHolder != null) return cHolder.getReadSpeedFromFileInPages(); return -1; } /** * @return Amount of pages read from cache in total. */ public long getAmountOfPagesReadFromCache() { return performanceCountersHolder.getAmountOfPagesReadFromCache(); } /** * Amount of pages read from cache for component name of which is passed as method argument. If null value is passed then value * for whole system will be returned. If data for component with passed in name does not exist then <code>-1</code> will be * returned. * * @param componentName * Name of component data of which should be returned. Name is case sensitive. * @return Amount of pages read from cache in total. */ public long getAmountOfPagesReadFromCache(String componentName) { if (componentName == null) return performanceCountersHolder.getAmountOfPagesReadFromCache(); final PerformanceCountersHolder cHolder = countersByComponent.get(componentName); if (cHolder != null) return cHolder.getAmountOfPagesReadFromCache(); return -1; } /** * @return Amount of pages are read from file. */ public long getAmountOfPagesReadFromFile() { return performanceCountersHolder.getAmountOfPagesReadFromFile(); } /** * Amount of pages are read from file for component name of which is passed as method argument. If null value is passed then value * for whole system will be returned. If data for component with passed in name does not exist then <code>-1</code> will be * returned. * * @param componentName * Name of component data of which should be returned. Name is case sensitive. * @return Amount of pages are read from file. */ public long getAmountOfPagesReadFromFile(String componentName) { if (componentName == null) return performanceCountersHolder.getAmountOfPagesReadFromFile(); final PerformanceCountersHolder cHolder = countersByComponent.get(componentName); if (cHolder != null) return cHolder.getAmountOfPagesReadFromFile(); return -1; } /** * @return Write speed of data in pages per second on cache level or value which is less than 0, which means that value cannot be * calculated. */ public long getWriteSpeedInCacheInPages() { return performanceCountersHolder.getWriteSpeedInCacheInPages(); } /** * Write speed of data in pages per second on cache level for component name of which is passed as method argument. If null value * is passed then value for whole system will be returned. If data for component with passed in name does not exist then * <code>-1</code> will be returned. * * @param componentName * Name of component data of which should be returned. Name is case sensitive. * @return Write speed of data in pages per second on cache level or value which is less than 0, which means that value cannot be * calculated. */ public long getWriteSpeedInCacheInPages(String componentName) { if (componentName == null) return performanceCountersHolder.getWriteSpeedInCacheInPages(); final PerformanceCountersHolder cHolder = countersByComponent.get(componentName); if (cHolder != null) return cHolder.getWriteSpeedInCacheInPages(); return -1; } /** * @return Amount of pages written to cache. */ public long getAmountOfPagesWrittenInCache() { return performanceCountersHolder.getAmountOfPagesWrittenInCache(); } /** * Amount of pages written to cache for component name of which is passed as method argument. If null value is passed then value * for whole system will be returned. If data for component with passed in name does not exist then <code>-1</code> will be * returned. * * @param componentName * Name of component data of which should be returned. Name is case sensitive. * @return Amount of pages written to cache. */ public long getAmountOfPagesWrittenInCache(String componentName) { if (componentName == null) return performanceCountersHolder.getAmountOfPagesWrittenInCache(); final PerformanceCountersHolder cHolder = countersByComponent.get(componentName); if (cHolder != null) return cHolder.getAmountOfPagesWrittenInCache(); return -1; } /** * @return Latest snapshot is taken by container */ public PerformanceSnapshot getSnapshot() { return snapshot; } /** * @return Average time of commit of atomic operation in nanoseconds or value which is less than 0, which means that value cannot * be calculated. */ public long getCommitTime() { return performanceCountersHolder.getCommitTime(); } /** * @return Percent of cache hits or value which is less than 0, which means that value cannot be calculated. */ public int getCacheHits() { return performanceCountersHolder.getCacheHits(); } /** * Percent of cache hits for component name of which is passed as method argument. If null value is passed then value for whole * system will be returned. If data for component with passed in name does not exist then <code>-1</code> will be returned. * * @param componentName * Name of component data of which should be returned. Name is case sensitive. * @return Percent of cache hits or value which is less than 0, which means that value cannot be calculated. */ public int getCacheHits(String componentName) { if (componentName == null) return performanceCountersHolder.getCacheHits(); final PerformanceCountersHolder cHolder = countersByComponent.get(componentName); if (cHolder != null) return cHolder.getCacheHits(); return -1; } /** * Average amount of pages which were read from cache for component with given name during single data operation. * <p> * If null value is passed or data for component with passed in name does not exist then <code>-1</code> will be returned. * * @param componentName * Name of component data of which should be returned. Name is case sensitive. * @return Average amount of pages which were read from cache for component with given name during single data operation or value * which is less than 0, which means that value cannot be calculated. */ public long getAmountOfPagesPerOperation(String componentName) { if (componentName == null) { return -1; } final PerformanceCountersHolder cHolder = countersByComponent.get(componentName); if (cHolder != null) return cHolder.getAmountOfPagesPerOperation(); return -1; } /** * Takes performance data are split by components from last snapshot and aggregates them with data passed inside method as * parameter. Result of aggregation of performance data is returned inside of passed in performance data. * * @param counters * Performance data for each component. */ public void pushComponentCounters(Map<String, PerformanceCountersHolder> counters) { if (snapshot == null) return; for (Map.Entry<String, PerformanceCountersHolder> entry : snapshot.countersByComponent.entrySet()) { final String componentName = entry.getKey(); PerformanceCountersHolder holder = counters.get(componentName); if (holder == null) { holder = entry.getValue().newInstance(); counters.put(componentName, holder); } entry.getValue().pushData(holder); } } /** * Takes write cache performance data from last snapshot and aggregates them with data passed inside method as parameter. Result * of aggregation of performance data is returned inside of passed in performance data and as result of this method call. * * @param holder * Performance data for write cache may be <code>null</code> * @return Result of aggregation of performance data */ public WritCacheCountersHolder pushWriteCacheCounters(WritCacheCountersHolder holder) { if (snapshot == null) return holder; if (snapshot.writCacheCountersHolder == null) return holder; if (holder == null) holder = new WritCacheCountersHolder(); snapshot.writCacheCountersHolder.pushData(holder); return holder; } /** * Takes storage performance data from last snapshot and aggregates them with data passed inside method as parameter. Result of * aggregation of performance data is returned inside of passed in performance data and as result of this method call. * * @param holder * Performance data for storage may be <code>null</code> * @return Result of aggregation of performance data */ public StorageCountersHolder pushStorageCounters(StorageCountersHolder holder) { if (snapshot == null) return holder; if (snapshot.storageCountersHolder == null) return holder; if (holder == null) holder = new StorageCountersHolder(); snapshot.storageCountersHolder.pushData(holder); return holder; } /** * Takes write ahead log data from last snapshot and aggregates them with data passed inside method as parameter. Result of * aggregation of performance data is returned inside of passed in performance data and as result of this method call. * * @param holder * Performance data for write ahead log may be <code>null</code> * @return Result of aggregation of performance data */ public WALCountersHolder pushWALCounters(WALCountersHolder holder) { if (snapshot == null) return holder; if (snapshot.walCountersHolder == null) return holder; if (holder == null) holder = new WALCountersHolder(); snapshot.walCountersHolder.pushData(holder); return holder; } /** * Takes performance data for whole system from last snapshot and aggregates them with data passed inside method as parameter. * Result of aggregation of performance data is returned inside of passed in performance data. * * @param holder * Performance data for whole system. */ public void pushSystemCounters(PerformanceCountersHolder holder) { if (snapshot == null) return; snapshot.performanceCountersHolder.pushData(holder); } /** * Takes performance data for component from last snapshot and aggregates them with data passed inside method as parameter. Result * of aggregation of performance data is returned inside of passed in performance data. * * @param name * Name of component for which performance data are gathered. * @param holder * Performance data for given component. */ public void pushComponentCounters(String name, PerformanceCountersHolder holder) { if (snapshot == null) return; final PerformanceCountersHolder countersHolder = snapshot.countersByComponent.get(name); if (countersHolder != null) { countersHolder.pushData(holder); } } /** * Converts properties of given class into values of fields of returned document. Names of fields equal to names of properties. * <p> * All data related to separate components are stored in field <code>dataByComponent</code> map which has type * {@link OType#EMBEDDEDMAP} where key of map entry is name of component, and value is document which contains the same fields as * high level document but with values for single component not whole system. * <p> * Write ahead log performance data are stored inside of <code>walData</code> field. * * @return Performance characteristics of storage gathered after call of * {@link OAbstractPaginatedStorage#startGatheringPerformanceStatisticForCurrentThread()} */ public ODocument toDocument() { final ODocument document = performanceCountersHolder.toDocument(); document.field("commitTime", getCommitTime(), OType.LONG); final Map<String, ODocument> countersMap = new HashMap<String, ODocument>(); for (Map.Entry<String, PerformanceCountersHolder> entry : countersByComponent.entrySet()) { countersMap.put(entry.getKey(), entry.getValue().toDocument()); } document.field("dataByComponent", countersMap, OType.EMBEDDEDMAP); if (walCountersHolder != null) { final ODocument wal = walCountersHolder.toDocument(); document.field("walData", wal, OType.EMBEDDED); } return document; } /** * Increments counter of page accesses from cache. * <p> * If you wish to gather statistic for current durable component please call * {@link #startComponentOperation(String, ComponentType)} method before the call and {@link #completeComponentOperation()} after * the call. */ public void incrementPageAccessOnCacheLevel(boolean cacheHit) { performanceCountersHolder.cacheAccessCount++; if (cacheHit) performanceCountersHolder.cacheHit++; for (Component component : componentsStack) { final String componentName = component.name; PerformanceCountersHolder cHolder = countersByComponent.get(componentName); if (cHolder == null) { cHolder = component.type.newCountersHolder(); countersByComponent.put(componentName, cHolder); } cHolder.cacheAccessCount++; if (cacheHit) cHolder.cacheHit++; } makeSnapshotIfNeeded(-1); } /** * Starts timer which counts how much time was spent on read of page from file system. */ public void startPageReadFromFileTimer() { pushTimer(); } /** * Stops and records results of timer which counts how much time was spent on read of page from file system. * <p> * If you wish to gather statistic for current durable component please call * {@link #startComponentOperation(String, ComponentType)} method before the call and {@link #completeComponentOperation()} after * the call. * * @param readPages * Amount of pages which were read by single call to file system. */ public void stopPageReadFromFileTimer(int readPages) { final long endTs = nanoTimer.getNano(); final long timeDiff = (endTs - timeStamps.pop()); performanceCountersHolder.pageReadFromFileTime += timeDiff; performanceCountersHolder.pageReadFromFileCount += readPages; for (Component component : componentsStack) { final String componentName = component.name; PerformanceCountersHolder cHolder = countersByComponent.get(componentName); if (cHolder == null) { cHolder = component.type.newCountersHolder(); countersByComponent.put(componentName, cHolder); } cHolder.pageReadFromFileTime += timeDiff; cHolder.pageReadFromFileCount += readPages; } final Component currentComponent = componentsStack.peek(); if (currentComponent != null) { PerformanceCountersHolder currentHolder = countersByComponent.get(currentComponent.name); if (currentHolder.currentOperation != null) { currentHolder.currentOperation.incrementOperationsCounter(0, readPages); } } makeSnapshotIfNeeded(endTs); } /** * Starts timer which counts how much time was spent on operation of flush pages in write cache. */ public void startWriteCacheFlushTimer() { pushTimer(); } /** * Stops and records results of timer which counts how much time was spent on operation of flush pages in write cache. * * @param pagesFlushed * Amount of pages were flushed during this operation. */ public void stopWriteCacheFlushTimer(int pagesFlushed) { // lazy initialization to prevent memory consumption if (writCacheCountersHolder == null) writCacheCountersHolder = new WritCacheCountersHolder(); final long endTs = nanoTimer.getNano(); final long timeDiff = (endTs - timeStamps.pop()); writCacheCountersHolder.flushOperationsCount++; writCacheCountersHolder.amountOfPagesFlushed += pagesFlushed; writCacheCountersHolder.flushOperationsTime += timeDiff; makeSnapshotIfNeeded(endTs); } /** * Starts timer which counts how much time was spent on fuzzy checkpoint operation. */ public void startFuzzyCheckpointTimer() { pushTimer(); } /** * Stops and records results of timer which counts how much time was spent on fuzzy checkpoint operation. */ public void stopFuzzyCheckpointTimer() { if (writCacheCountersHolder == null) writCacheCountersHolder = new WritCacheCountersHolder(); final long endTs = nanoTimer.getNano(); final long timeDiff = (endTs - timeStamps.pop()); writCacheCountersHolder.fuzzyCheckpointCount++; writCacheCountersHolder.fuzzyCheckpointTime += timeDiff; makeSnapshotIfNeeded(endTs); } /** * Starts timer which counts how much time was spent on read of page from disk cache. */ public void startPageReadFromCacheTimer() { pushTimer(); } /** * General method which is used ba all stopXXXTimer methods to delegate their functionality. */ private void pushTimer() { timeStamps.push(nanoTimer.getNano()); } /** * Stops and records results of timer which counts how much time was spent on read of page from disk cache. * <p> * If you wish to gather statistic for current durable component please call * {@link #startComponentOperation(String, ComponentType)} method before the call and {@link #completeComponentOperation()} after * the call. */ public void stopPageReadFromCacheTimer() { final long endTs = nanoTimer.getNano(); final long timeDiff = (endTs - timeStamps.pop()); performanceCountersHolder.pageReadFromCacheTime += timeDiff; performanceCountersHolder.pageReadFromCacheCount++; for (Component component : componentsStack) { final String componentName = component.name; PerformanceCountersHolder cHolder = countersByComponent.get(componentName); if (cHolder == null) { cHolder = component.type.newCountersHolder(); countersByComponent.put(componentName, cHolder); } cHolder.pageReadFromCacheTime += timeDiff; cHolder.pageReadFromCacheCount++; } final Component currentComponent = componentsStack.peek(); if (currentComponent != null) { PerformanceCountersHolder currentHolder = countersByComponent.get(currentComponent.name); if (currentHolder.currentOperation != null) { currentHolder.currentOperation.incrementOperationsCounter(1, 0); } } makeSnapshotIfNeeded(endTs); } /** * Starts timer which records how much time is spent on full checkpoint */ public void startFullCheckpointTimer() { pushTimer(); } /** * Stops and records results of timer which counts how much time was spent on full checkpoint operation. */ public void stopFullCheckpointTimer() { final long endTs = nanoTimer.getNano(); final long timeDiff = (endTs - timeStamps.pop()); if (storageCountersHolder == null) storageCountersHolder = new StorageCountersHolder(); storageCountersHolder.fullCheckpointOperationsCount++; storageCountersHolder.fullCheckpointOperationsTime += timeDiff; makeSnapshotIfNeeded(endTs); } /** * Starts timer which counts how much time was spent on write of page to disk cache. */ public void startPageWriteInCacheTimer() { pushTimer(); } /** * Stops and records results of timer which counts how much time was spent to write page to disk cache. * <p> * If you wish to gather statistic for current durable component please call * {@link #startComponentOperation(String, ComponentType)} method before the call and {@link #completeComponentOperation()} after * the call. */ public void stopPageWriteInCacheTimer() { final long endTs = nanoTimer.getNano(); final long timeDiff = (endTs - timeStamps.pop()); performanceCountersHolder.pageWriteToCacheTime += timeDiff; performanceCountersHolder.pageWriteToCacheCount++; for (Component component : componentsStack) { final String componentName = component.name; PerformanceCountersHolder cHolder = countersByComponent.get(componentName); if (cHolder == null) { cHolder = component.type.newCountersHolder(); countersByComponent.put(componentName, cHolder); } cHolder.pageWriteToCacheTime += timeDiff; cHolder.pageWriteToCacheCount++; } makeSnapshotIfNeeded(endTs); } public void startRecordCreationTimer() { pushTimer(); } public void stopRecordCreationTimer() { final Component component = componentsStack.peek(); checkComponentType(component, ComponentType.CLUSTER); final long endTs = nanoTimer.getNano(); final long timeDiff = (endTs - timeStamps.pop()); OClusterCountersHolder cHolder = (OClusterCountersHolder) countersByComponent.get(component.name); if (cHolder == null) { cHolder = (OClusterCountersHolder) ComponentType.CLUSTER.newCountersHolder(); countersByComponent.put(component.name, cHolder); } cHolder.createdRecords++; cHolder.timeRecordCreation += timeDiff; makeSnapshotIfNeeded(endTs); } public void startRecordDeletionTimer() { pushTimer(); } public void stopRecordDeletionTimer() { final Component component = componentsStack.peek(); checkComponentType(component, ComponentType.CLUSTER); final long endTs = nanoTimer.getNano(); final long timeDiff = (endTs - timeStamps.pop()); OClusterCountersHolder cHolder = (OClusterCountersHolder) countersByComponent.get(component.name); if (cHolder == null) { cHolder = (OClusterCountersHolder) ComponentType.CLUSTER.newCountersHolder(); countersByComponent.put(component.name, cHolder); } cHolder.deletedRecords++; cHolder.timeRecordDeletion += timeDiff; makeSnapshotIfNeeded(endTs); } public void startRecordUpdateTimer() { pushTimer(); } public void stopRecordUpdateTimer() { final Component component = componentsStack.peek(); checkComponentType(component, ComponentType.CLUSTER); final long endTs = nanoTimer.getNano(); final long timeDiff = (endTs - timeStamps.pop()); OClusterCountersHolder cHolder = (OClusterCountersHolder) countersByComponent.get(component.name); if (cHolder == null) { cHolder = (OClusterCountersHolder) ComponentType.CLUSTER.newCountersHolder(); countersByComponent.put(component.name, cHolder); } cHolder.updatedRecords++; cHolder.timeRecordUpdate += timeDiff; makeSnapshotIfNeeded(endTs); } public void startRecordReadTimer() { pushTimer(); } public void stopRecordReadTimer() { final Component component = componentsStack.peek(); checkComponentType(component, ComponentType.CLUSTER); final long endTs = nanoTimer.getNano(); final long timeDiff = (endTs - timeStamps.pop()); OClusterCountersHolder cHolder = (OClusterCountersHolder) countersByComponent.get(component.name); if (cHolder == null) { cHolder = (OClusterCountersHolder) ComponentType.CLUSTER.newCountersHolder(); countersByComponent.put(component.name, cHolder); } cHolder.readRecords++; cHolder.timeRecordRead += timeDiff; makeSnapshotIfNeeded(endTs); } public void startIndexEntryUpdateTimer() { pushTimer(); } public void stopIndexEntryUpdateTimer() { final Component component = componentsStack.peek(); checkComponentType(component, ComponentType.INDEX); final long endTs = nanoTimer.getNano(); final long timeDiff = (endTs - timeStamps.pop()); IndexCountersHolder cHolder = (IndexCountersHolder) countersByComponent.get(component.name); if (cHolder == null) { cHolder = (IndexCountersHolder) ComponentType.INDEX.newCountersHolder(); countersByComponent.put(component.name, cHolder); } cHolder.updatedEntries++; cHolder.timeUpdateEntry += timeDiff; makeSnapshotIfNeeded(endTs); } public void startIndexEntryDeletionTimer() { pushTimer(); } public void stopIndexEntryDeletionTimer() { final Component component = componentsStack.peek(); checkComponentType(component, ComponentType.INDEX); final long endTs = nanoTimer.getNano(); final long timeDiff = (endTs - timeStamps.pop()); IndexCountersHolder cHolder = (IndexCountersHolder) countersByComponent.get(component.name); if (cHolder == null) { cHolder = (IndexCountersHolder) ComponentType.INDEX.newCountersHolder(); countersByComponent.put(component.name, cHolder); } cHolder.deletedEntries++; cHolder.timeDeleteEntry += timeDiff; makeSnapshotIfNeeded(endTs); } public void startIndexEntryReadTimer() { pushTimer(); } public void stopIndexEntryReadTimer() { final Component component = componentsStack.peek(); checkComponentType(component, ComponentType.INDEX); final long endTs = nanoTimer.getNano(); final long timeDiff = (endTs - timeStamps.pop()); IndexCountersHolder cHolder = (IndexCountersHolder) countersByComponent.get(component.name); if (cHolder == null) { cHolder = (IndexCountersHolder) ComponentType.INDEX.newCountersHolder(); countersByComponent.put(component.name, cHolder); } cHolder.readEntries++; cHolder.timeReadEntry += timeDiff; makeSnapshotIfNeeded(endTs); } public void startRidBagEntryReadTimer() { pushTimer(); } public void stopRidBagEntryReadTimer(int entriesRead) { final Component component = componentsStack.peek(); checkComponentType(component, ComponentType.RIDBAG); final long endTs = nanoTimer.getNano(); final long timeDiff = (endTs - timeStamps.pop()); RidbagCountersHolder cHolder = (RidbagCountersHolder) countersByComponent.get(component.name); if (cHolder == null) { cHolder = (RidbagCountersHolder) ComponentType.INDEX.newCountersHolder(); countersByComponent.put(component.name, cHolder); } cHolder.readEntries += entriesRead; cHolder.timeReadEntry += timeDiff; makeSnapshotIfNeeded(endTs); } public void startRidBagEntryUpdateTimer() { pushTimer(); } public void stopRidBagEntryUpdateTimer() { final Component component = componentsStack.peek(); checkComponentType(component, ComponentType.RIDBAG); final long endTs = nanoTimer.getNano(); final long timeDiff = (endTs - timeStamps.pop()); RidbagCountersHolder cHolder = (RidbagCountersHolder) countersByComponent.get(component.name); if (cHolder == null) { cHolder = (RidbagCountersHolder) ComponentType.INDEX.newCountersHolder(); countersByComponent.put(component.name, cHolder); } cHolder.updatedEntries++; cHolder.timeUpdateEntry += timeDiff; makeSnapshotIfNeeded(endTs); } public void startRidBagEntryDeletionTimer() { pushTimer(); } public void stopRidBagEntryDeletionTimer() { final Component component = componentsStack.peek(); checkComponentType(component, ComponentType.RIDBAG); final long endTs = nanoTimer.getNano(); final long timeDiff = (endTs - timeStamps.pop()); RidbagCountersHolder cHolder = (RidbagCountersHolder) countersByComponent.get(component.name); if (cHolder == null) { cHolder = (RidbagCountersHolder) ComponentType.INDEX.newCountersHolder(); countersByComponent.put(component.name, cHolder); } cHolder.deletedEntries++; cHolder.timeDeleteEntry += timeDiff; makeSnapshotIfNeeded(endTs); } public void startRidBagEntryLoadTimer() { pushTimer(); } public void stopRidBagEntryLoadTimer() { final Component component = componentsStack.peek(); checkComponentType(component, ComponentType.RIDBAG); final long endTs = nanoTimer.getNano(); final long timeDiff = (endTs - timeStamps.pop()); RidbagCountersHolder cHolder = (RidbagCountersHolder) countersByComponent.get(component.name); if (cHolder == null) { cHolder = (RidbagCountersHolder) ComponentType.INDEX.newCountersHolder(); countersByComponent.put(component.name, cHolder); } cHolder.loads++; cHolder.loadTime += timeDiff; makeSnapshotIfNeeded(endTs); } /** * Starts timer which counts how much time was spent on atomic operation commit. */ public void startCommitTimer() { pushTimer(); } /** * Stops and records results of timer which counts how much time was spent on atomic operation commit. */ public void stopCommitTimer() { final long endTs = nanoTimer.getNano(); final long timeDiff = (endTs - timeStamps.pop()); performanceCountersHolder.commitTime += timeDiff; performanceCountersHolder.commitCount++; makeSnapshotIfNeeded(endTs); } /** * Starts timer which counts how much time was spent on logging of single write ahead log record */ public void startWALLogRecordTimer() { pushTimer(); } /** * Stops and records results of timer which counts how much time was spent on logging of single write ahead log record. * * @param isStartRecord * Indicates whether we logged "start atomic operation" record * @param isStopRecord * Indicates whether we logged "stop atomic operation" record */ public void stopWALRecordTimer(boolean isStartRecord, boolean isStopRecord) { final long endTs = nanoTimer.getNano(); final long timeDiff = (endTs - timeStamps.pop()); if (walCountersHolder == null) walCountersHolder = new WALCountersHolder(); walCountersHolder.logRecordCount++; walCountersHolder.logRecordTime += timeDiff; if (isStartRecord) { walCountersHolder.startRecordCount++; walCountersHolder.startRecordTime += timeDiff; } else if (isStopRecord) { walCountersHolder.stopRecordCount++; walCountersHolder.stopRecordTime += timeDiff; } makeSnapshotIfNeeded(endTs); } /** * Starts timer which counts how much time was spent on flushing of data from write ahead log cache. */ public void startWALFlushTimer() { pushTimer(); } /** * Stops timer and records how much time was spent on flushing of data from write ahead log cache. */ public void stopWALFlushTimer() { final long endTs = nanoTimer.getNano(); final long timeDiff = (endTs - timeStamps.pop()); if (walCountersHolder == null) walCountersHolder = new WALCountersHolder(); walCountersHolder.flushCount++; walCountersHolder.flushTime += timeDiff; makeSnapshotIfNeeded(endTs); } private void checkComponentType(Component component, ComponentType expected) { if (!component.type.equals(expected)) throw new IllegalStateException("Invalid component type , required " + expected + " but found " + component.type); } /** * Makes snapshot of data if time for next snapshot is passed. Also clear all data if {@link #cleanUpInterval} interval is over. * * @param currentTime * Time of last measurement in nanoseconds or -1 if time is unknown */ private void makeSnapshotIfNeeded(long currentTime) { if (currentTime < 0) { currentTime = nanoTimer.getNano(); } if (lastSnapshotTimestamp == -1) lastSnapshotTimestamp = 0; if (lastSnapshotTimestamp < 0 || currentTime - lastSnapshotTimestamp >= intervalBetweenSnapshots) { snapshot = new PerformanceSnapshot(performanceCountersHolder, countersByComponent, writCacheCountersHolder, storageCountersHolder, walCountersHolder); lastSnapshotTimestamp = currentTime; } if (cleanUpInterval > 0) { if (currentTime - lastCleanUpTimeStamp >= cleanUpInterval) { performanceCountersHolder.clean(); for (PerformanceCountersHolder pch : countersByComponent.values()) { pch.clean(); } if (writCacheCountersHolder != null) writCacheCountersHolder.clean(); if (storageCountersHolder != null) storageCountersHolder.clean(); if (writCacheCountersHolder != null) walCountersHolder.clean(); lastCleanUpTimeStamp = currentTime; } } } /** * Interface which is used by this tool to get current PC nano time. Implementation which calls <code>System.nanoTime()</code> is * used by default. */ public interface NanoTimer { /** * @return Current PC nano time. */ long getNano(); } /** * Contains information about system component, name and count of operations in progress at current moment. */ private static final class Component { private final String name; private final ComponentType type; private int operationCount; Component(String name, ComponentType type) { this.name = name; this.type = type; operationCount = 1; } } public enum ComponentType { GENERAL { @Override PerformanceCountersHolder newCountersHolder() { return new PerformanceCountersHolder(); } }, INDEX { @Override IndexCountersHolder newCountersHolder() { return new IndexCountersHolder(); } }, CLUSTER { @Override OClusterCountersHolder newCountersHolder() { return new OClusterCountersHolder(); } }, RIDBAG { @Override RidbagCountersHolder newCountersHolder() { return new RidbagCountersHolder(); } }; abstract PerformanceCountersHolder newCountersHolder(); } /** * Contains data about write ahead log performance */ public static class WALCountersHolder implements CountersHolder<WALCountersHolder> { /** * Amount of times WAL record was logged */ private long logRecordCount; /** * Total time which was spent on logging of WAL records */ private long logRecordTime; /** * Amount of times WAL "start atomic operation" record was logged */ private long startRecordCount; /** * Total time which was spent on logging of "start atomic operation" records */ private long startRecordTime; /** * Amount of times "stop atomic operation" record was logged */ private long stopRecordCount; /** * Total time which was spent on logging of "stop atomic operation" records */ private long stopRecordTime; /** * Amount of times WAL cache flush was called */ private long flushCount; /** * Total time which was spent on flush of WAL cache */ private long flushTime; @Override public void clean() { logRecordCount = 0; logRecordTime = 0; flushCount = 0; flushTime = 0; } @Override public void pushData(WALCountersHolder holder) { holder.logRecordCount += logRecordCount; holder.logRecordTime += logRecordTime; holder.flushCount += flushCount; holder.flushTime += flushTime; } /** * @return Average time which is spent on logging of single record or <code>-1</code> if value is undefined. */ public long getLogTime() { if (logRecordCount == 0) return -1; return logRecordTime / logRecordCount; } /** * @return Average time which is spent on logging of "stop atomic operation" record or <code>-1</code> if value is undefined. */ public long getStopAOTime() { if (stopRecordCount == 0) return -1; return stopRecordTime / stopRecordCount; } /** * @return Average time which is spent on logging of "start atomic operation" record or <code>-1</code> if value is undefined. */ public long getStartAOTime() { if (startRecordCount == 0) return -1; return startRecordTime / startRecordCount; } /** * @return Average time which is spent on flush of WAL cache or <code>-1</code> if value is undefined. */ public long getFlushTime() { if (flushCount == 0) return -1; return flushTime / flushCount; } @Override public ODocument toDocument() { final ODocument document = new ODocument(); writeMetric(document, "logTime", getLogTime(), OType.LONG); writeMetric(document, "startAOTime", getStartAOTime(), OType.LONG); writeMetric(document, "stopAOTime", getStopAOTime(), OType.LONG); return document; } } /** * Contains data about storage performance */ public static class StorageCountersHolder implements CountersHolder<StorageCountersHolder> { /** * Amount of times full checkpoint operation was performed. */ private long fullCheckpointOperationsCount; /** * Total amount of time spent on full checkpoint operation. */ private long fullCheckpointOperationsTime; @Override public void clean() { fullCheckpointOperationsCount = 0; fullCheckpointOperationsTime = 0; } @Override public void pushData(StorageCountersHolder holder) { holder.fullCheckpointOperationsCount += fullCheckpointOperationsCount; holder.fullCheckpointOperationsTime += fullCheckpointOperationsTime; } @Override public ODocument toDocument() { final ODocument document = new ODocument(); writeMetric(document, "fullCheckpointTime", getFullCheckpointTime(), OType.LONG); return document; } /** * @return Average time which is spent on full checkpoint operation, or <code>-1</code> if value is undefined */ public long getFullCheckpointTime() { if (fullCheckpointOperationsCount == 0) return -1; return fullCheckpointOperationsTime / fullCheckpointOperationsCount; } } /** * Contains write cache performance characteristics */ public static class WritCacheCountersHolder implements CountersHolder<WritCacheCountersHolder> { /** * Count flush operation. */ private long flushOperationsCount; /** * Sum of pages flushed in each write cache flush operation. */ private long amountOfPagesFlushed; /** * Total time spent on all flush operations. */ private long flushOperationsTime; /** * Count of all fuzzy checkpoints */ private long fuzzyCheckpointCount; /** * Time is spent in all fuzzy checkpoints */ private long fuzzyCheckpointTime; @Override public void clean() { flushOperationsCount = 0; amountOfPagesFlushed = 0; flushOperationsTime = 0; fuzzyCheckpointCount = 0; fuzzyCheckpointTime = 0; } @Override public void pushData(WritCacheCountersHolder holder) { holder.flushOperationsCount += flushOperationsCount; holder.amountOfPagesFlushed += amountOfPagesFlushed; holder.flushOperationsTime += flushOperationsTime; holder.fuzzyCheckpointCount += fuzzyCheckpointCount; holder.fuzzyCheckpointTime += fuzzyCheckpointTime; } /** * @return Average amount of pages which are flushed during "page flush" operation of write cache or <code>-1</code> if value is * undefined */ public long getPagesPerFlush() { if (flushOperationsCount == 0) return -1; return amountOfPagesFlushed / flushOperationsCount; } /** * @return Average amount of time which is spent on each "page flush" operation of write cache or <code>-1</code> if value is * undefined. */ public long getFlushOperationsTime() { if (flushOperationsCount == 0) return -1; return flushOperationsTime / flushOperationsCount; } /** * @return Average amount of time which is spent on "fuzzy checkpoint" operation or <code>-1</code> if value if undefined. */ public long getFuzzyCheckpointTime() { if (fuzzyCheckpointCount == 0) return -1; return fuzzyCheckpointTime / fuzzyCheckpointCount; } @Override public ODocument toDocument() { final ODocument document = new ODocument(); writeMetric(document, "pagesPerFlush", getPagesPerFlush(), OType.LONG); writeMetric(document, "flushOperationsTime", getFlushOperationsTime(), OType.LONG); writeMetric(document, "fuzzyCheckpointTime", getFuzzyCheckpointTime(), OType.LONG); return document; } } public static class IndexCountersHolder extends PerformanceCountersHolder { private long updatedEntries; private long timeUpdateEntry; private long updateEntryPages; private long updateEntryFilePages; private long updateEntryPageTime; private long updateEntryFilePageTime; private long deletedEntries; private long timeDeleteEntry; private long deleteEntryPages; private long deleteEntryFilePages; private long deleteEntryPageTime; private long deleteEntryFilePageTime; private long readEntries; private long timeReadEntry; private long readEntryPages; private long readEntryFilePages; private long readEntryPageTime; private long readEntryFilePageTime; @Override public IndexCountersHolder newInstance() { return new IndexCountersHolder(); } @Override public void clean() { super.clean(); updatedEntries = 0; timeUpdateEntry = 0; updateEntryPages = 0; updateEntryFilePages = 0; updateEntryPageTime = 0; updateEntryFilePageTime = 0; deletedEntries = 0; timeDeleteEntry = 0; deleteEntryPages = 0; deleteEntryFilePages = 0; deleteEntryPageTime = 0; deleteEntryFilePageTime = 0; readEntries = 0; timeReadEntry = 0; readEntryPages = 0; readEntryFilePages = 0; readEntryPageTime = 0; readEntryFilePageTime = 0; } public long getUpdateEntryTime() { if (updatedEntries == 0) return -1; return timeUpdateEntry / updatedEntries; } public long getUpdateEntryPages() { if (updatedEntries == 0) return -1; return updateEntryPages / updatedEntries; } public long getUpdateEntryHitRate() { if (updateEntryPages == 0) return -1; return (int) ((100 * (updateEntryPages - updateEntryFilePages)) / updateEntryPages); } public long getUpdateEntryPageTime() { if (updateEntryPages == 0) return -1; return updateEntryPageTime / updateEntryPages; } public long getUpdateEntryFilePageTime() { if (updateEntryFilePages == 0) return -1; return updateEntryFilePageTime / updateEntryFilePages; } public long getDeleteEntryTime() { if (deletedEntries == 0) return -1; return timeDeleteEntry / deletedEntries; } public long getDeleteEntryPages() { if (deletedEntries == 0) return -1; return deleteEntryPages / deletedEntries; } public long getDeleteEntryHitRate() { if (deleteEntryPages == 0) return -1; return (int) ((100 * (deleteEntryPages - deleteEntryFilePages)) / deleteEntryPages); } public long getReadEntryTime() { if (readEntries == 0) return -1; return timeReadEntry / readEntries; } public long getReadEntryPages() { if (readEntries == 0) return -1; return readEntryPages / readEntries; } @Override public ODocument toDocument() { final ODocument document = super.toDocument(); writeMetric(document, "updateEntryTime", getUpdateEntryTime(), OType.LONG); writeMetric(document, "updateEntryPages", getUpdateEntryPages(), OType.LONG); writeMetric(document, "deleteEntryTime", getDeleteEntryTime(), OType.LONG); writeMetric(document, "deleteEntryPages", getDeleteEntryPages(), OType.LONG); writeMetric(document, "readEntryTime", getReadEntryTime(), OType.LONG); writeMetric(document, "readEntryPages", getReadEntryPages(), OType.LONG); return document; } private class UpdateEntryOperation extends OOperation { @Override void incrementOperationsCounter(int pages, int filePages) { IndexCountersHolder.this.updateEntryPages += pages; IndexCountersHolder.this.updateEntryFilePages += filePages; } } private class DeleteEntryOperation extends OOperation { @Override void incrementOperationsCounter(int pages, int filePages) { IndexCountersHolder.this.deleteEntryPages += pages; IndexCountersHolder.this.deleteEntryFilePages += filePages; } } private class ReadEntryOperation extends OOperation { @Override void incrementOperationsCounter(int pages, int filePages) { IndexCountersHolder.this.readEntryPages += pages; IndexCountersHolder.this.readEntryFilePages += filePages; } } } public static class RidbagCountersHolder extends PerformanceCountersHolder { private long updatedEntries; private long timeUpdateEntry; private long updateEntryPages; private long updateEntryFilePages; private long deletedEntries; private long timeDeleteEntry; private long deleteEntryPages; private long deleteEntryFilePages; private long readEntries; private long timeReadEntry; private long readEntryPages; private long readEntryFilePages; private long loads; private long loadTime; private long loadPages; private long loadFilePages; @Override public PerformanceCountersHolder newInstance() { return new RidbagCountersHolder(); } @Override public void clean() { super.clean(); updatedEntries = 0; timeUpdateEntry = 0; updateEntryPages = 0; updateEntryFilePages = 0; deletedEntries = 0; timeDeleteEntry = 0; deleteEntryPages = 0; deleteEntryFilePages = 0; readEntries = 0; timeReadEntry = 0; readEntryPages = 0; readEntryFilePages = 0; loads = 0; loadTime = 0; loadPages = 0; loadFilePages = 0; } public long getUpdateEntryTime() { if (updatedEntries == 0) return -1; return timeUpdateEntry / updatedEntries; } public long getUpdateEntryPages() { if (updatedEntries == 0) return -1; return updateEntryPages / updatedEntries; } public long getDeleteEntryTime() { if (deletedEntries == 0) return -1; return timeDeleteEntry / deletedEntries; } public long getDeleteEntryPages() { if (deletedEntries == 0) return -1; return deleteEntryPages / deletedEntries; } public long getReadEntryTime() { if (timeReadEntry == 0) return -1; return timeReadEntry / readEntries; } public long getReadEntryPages() { if (readEntries == 0) return -1; return readEntryPages / readEntries; } public long getLoadTime() { return loadTime / loads; } public long getLoadPages() { if (loads == 0) return -1; return loadPages / loads; } @Override public ODocument toDocument() { final ODocument document = super.toDocument(); writeMetric(document, "updateEntryTime", getUpdateEntryTime(), OType.LONG); writeMetric(document, "updateEntryPages", getUpdateEntryPages(), OType.LONG); writeMetric(document, "deleteEntryTime", getDeleteEntryTime(), OType.LONG); writeMetric(document, "deleteEntryPages", getDeleteEntryPages(), OType.LONG); writeMetric(document, "readEntryTime", getReadEntryTime(), OType.LONG); writeMetric(document, "readEntryPages", getReadEntryPages(), OType.LONG); writeMetric(document, "loadTime", getLoadTime(), OType.LONG); writeMetric(document, "loadPages", getLoadPages(), OType.LONG); return document; } private class UpdateEntryOperation extends OOperation { @Override void incrementOperationsCounter(int pages, int filePages) { RidbagCountersHolder.this.updateEntryPages += pages; RidbagCountersHolder.this.updateEntryFilePages += filePages; } } private class DeleteEntryPages extends OOperation { @Override void incrementOperationsCounter(int pages, int filePages) { RidbagCountersHolder.this.deleteEntryPages += pages; RidbagCountersHolder.this.deleteEntryFilePages += filePages; } } private class ReadEntryPages extends OOperation { @Override void incrementOperationsCounter(int pages, int filePages) { RidbagCountersHolder.this.readEntryPages += pages; RidbagCountersHolder.this.readEntryFilePages += filePages; } } private class LoadPages extends OOperation { @Override void incrementOperationsCounter(int pages, int filePages) { RidbagCountersHolder.this.loadPages += pages; RidbagCountersHolder.this.loadFilePages += filePages; } } } /** * Container for all performance counters which are shared between durable components and whole system. */ public static class PerformanceCountersHolder implements CountersHolder<PerformanceCountersHolder> { /** * Amount of times when atomic operation commit was performed. */ private long commitCount = 0; /** * Summary time which was spent on atomic operation commits. */ private long commitTime = 0; /** * Amount of operations performed by related component. */ private long operationsCount = 0; /** * Amount of times when cache was accessed during the session. */ private long cacheAccessCount = 0; /** * Amount of "cache hit" times during the session. */ private long cacheHit = 0; /** * Summary time which was spent on access of pages from file system. */ private long pageReadFromFileTime = 0; /** * Amount of pages in total which were accessed from file system. */ private long pageReadFromFileCount = 0; /** * Summary time which was spent on access of pages from disk cache. */ private long pageReadFromCacheTime = 0; /** * Amount of pages in total which were accessed from disk cache. */ private long pageReadFromCacheCount = 0; /** * Summary time which was spent to write pages to disk cache. */ private long pageWriteToCacheTime = 0; /** * Amount of pages in total which were written to disk cache. */ private long pageWriteToCacheCount = 0; private OOperation currentOperation; /** * Clears all performance data. */ public void clean() { commitCount = 0; commitTime = 0; operationsCount = 0; cacheAccessCount = 0; cacheHit = 0; pageReadFromFileTime = 0; pageReadFromFileCount = 0; pageReadFromCacheTime = 0; pageReadFromCacheCount = 0; pageWriteToCacheTime = 0; pageWriteToCacheCount = 0; } /** * Aggregates passed in and current performance data. Result is put in passed in data. * * @param aggregator * Data to aggregate */ public void pushData(PerformanceCountersHolder aggregator) { aggregator.operationsCount += operationsCount; aggregator.cacheAccessCount += cacheAccessCount; aggregator.cacheHit += cacheHit; aggregator.pageReadFromFileTime += pageReadFromFileTime; aggregator.pageReadFromFileCount += pageReadFromFileCount; aggregator.pageReadFromCacheTime += pageReadFromCacheTime; aggregator.pageReadFromCacheCount += pageReadFromCacheCount; aggregator.pageWriteToCacheTime += pageWriteToCacheTime; aggregator.pageWriteToCacheCount += pageWriteToCacheCount; aggregator.commitTime += commitTime; aggregator.commitCount += commitCount; } /** * @return Average time is spent on commit of single atomic operation. */ public long getCommitTime() { if (commitCount == 0) return -1; return commitTime / commitCount; } /** * @return Read speed of data in pages per second on cache level or value which is less than 0, which means that value cannot be * calculated. */ public long getReadSpeedFromCacheInPages() { if (pageReadFromCacheTime == 0) return -1; return (pageReadFromCacheCount * NANOS_IN_SECOND) / pageReadFromCacheTime; } /** * @return Read speed of data on file system level in pages per second or value which is less than 0, which means that value * cannot be calculated. */ public long getReadSpeedFromFileInPages() { if (pageReadFromFileTime == 0) return -1; return (pageReadFromFileCount * NANOS_IN_SECOND) / pageReadFromFileTime; } /** * @return Amount of pages read from cache in total. */ public long getAmountOfPagesReadFromCache() { return pageReadFromCacheCount; } /** * @return Amount of pages are read from file. */ public long getAmountOfPagesReadFromFile() { return pageReadFromFileCount; } /** * @return Write speed of data in pages per second on cache level or value which is less than 0, which means that value cannot * be calculated. */ public long getWriteSpeedInCacheInPages() { if (pageWriteToCacheTime == 0) return -1; return (pageWriteToCacheCount * NANOS_IN_SECOND) / pageWriteToCacheTime; } /** * @return Amount of pages written to cache. */ public long getAmountOfPagesWrittenInCache() { return pageWriteToCacheCount; } /** * @return Percent of cache hits or value which is less than 0, which means that value cannot be calculated. */ public int getCacheHits() { if (cacheAccessCount == 0) return -1; return (int) ((cacheHit * 100) / cacheAccessCount); } /** * @return Amount of pages accessed from cache for each component operation */ public long getAmountOfPagesPerOperation() { if (operationsCount == 0) return -1; return pageReadFromCacheCount / operationsCount; } /** * Converts properties of given class into values of fields of returned document. Names of fields equal to names of properties. * * @return Performance characteristics of storage. */ public ODocument toDocument() { final ODocument document = new ODocument(); writeMetric(document, "readSpeedFromCacheInPages", getReadSpeedFromCacheInPages(), OType.LONG); writeMetric(document, "readSpeedFromFileInPages", getReadSpeedFromFileInPages(), OType.LONG); writeMetric(document, "amountOfPagesReadFromCache", getAmountOfPagesReadFromCache(), OType.LONG); writeMetric(document, "writeSpeedInCacheInPages", getWriteSpeedInCacheInPages(), OType.LONG); writeMetric(document, "amountOfPagesWrittenInCache", getAmountOfPagesWrittenInCache(), OType.LONG); writeMetric(document, "amountOfPagesReadFromFile", getAmountOfPagesReadFromFile(), OType.LONG); writeMetric(document, "cacheHits", getCacheHits(), OType.INTEGER); writeMetric(document, "amountOfPagesPerOperation", getAmountOfPagesPerOperation(), OType.LONG); writeMetric(document, "commitTime", getCommitTime(), OType.LONG); return document; } public PerformanceCountersHolder newInstance() { return new PerformanceCountersHolder(); } } /** * Snapshot of all performance data of current container. */ public final static class PerformanceSnapshot { public final PerformanceCountersHolder performanceCountersHolder; public final WritCacheCountersHolder writCacheCountersHolder; public final StorageCountersHolder storageCountersHolder; public final WALCountersHolder walCountersHolder; public final Map<String, PerformanceCountersHolder> countersByComponent; /** * Makes snapshot of performance data and stores them in final fields. */ PerformanceSnapshot(PerformanceCountersHolder performanceCountersHolder, Map<String, PerformanceCountersHolder> countersByComponent, WritCacheCountersHolder writCacheCountersHolder, StorageCountersHolder storageCountersHolder, WALCountersHolder walCountersHolder) { this.performanceCountersHolder = performanceCountersHolder.newInstance(); performanceCountersHolder.pushData(this.performanceCountersHolder); // to preserve thread safety we assign map instance to the final field when it is completely filled by data Map<String, PerformanceCountersHolder> counters = new HashMap<String, PerformanceCountersHolder>(); for (Map.Entry<String, PerformanceCountersHolder> entry : countersByComponent.entrySet()) { final PerformanceCountersHolder holder = entry.getValue().newInstance(); entry.getValue().pushData(holder); counters.put(entry.getKey(), holder); } this.countersByComponent = counters; if (writCacheCountersHolder != null) { final WritCacheCountersHolder wh = new WritCacheCountersHolder(); writCacheCountersHolder.pushData(wh); this.writCacheCountersHolder = wh; } else { this.writCacheCountersHolder = null; } if (storageCountersHolder != null) { final StorageCountersHolder sch = new StorageCountersHolder(); storageCountersHolder.pushData(sch); this.storageCountersHolder = sch; } else { this.storageCountersHolder = null; } if (walCountersHolder != null) { final WALCountersHolder wch = new WALCountersHolder(); walCountersHolder.pushData(wch); this.walCountersHolder = wch; } else { this.walCountersHolder = null; } } } /** * Common interface which should implement all classes which contain system/component performance data. * * @param <T> * Real component class */ public interface CountersHolder<T extends CountersHolder> { /** * Resets all performance characteristics to default values. */ void clean(); /** * Accumulates data in current and passed in containers and push data back to passed in container. * * @param holder */ void pushData(T holder); /** * Serializes performance data as values of fields of returned document. * * @return Document filled with performance data */ ODocument toDocument(); } public static void writeMetric(final ODocument document, final String metricName, final Number metricValue) { writeMetric(document, metricName, metricValue, OType.LONG); } public static void writeMetric(final ODocument document, final String metricName, final Number metricValue, final OType metricType) { if (metricValue.longValue() != -1) document.field(metricName, metricValue, metricType); } }