diff --git a/modules/core/src/main/java/org/apache/ignite/IgniteSystemProperties.java b/modules/core/src/main/java/org/apache/ignite/IgniteSystemProperties.java index aa12e541b06e2..0035f1b8f4fc0 100644 --- a/modules/core/src/main/java/org/apache/ignite/IgniteSystemProperties.java +++ b/modules/core/src/main/java/org/apache/ignite/IgniteSystemProperties.java @@ -36,6 +36,7 @@ import org.apache.ignite.internal.marshaller.optimized.OptimizedMarshaller; import org.apache.ignite.internal.processors.metastorage.DistributedMetaStorage; import org.apache.ignite.internal.processors.metric.GridMetricManager; +import org.apache.ignite.internal.processors.performancestatistics.FilePerformanceStatisticsWriter; import org.apache.ignite.internal.processors.rest.GridRestCommand; import org.apache.ignite.internal.util.GridLogThrottle; import org.apache.ignite.lang.IgniteExperimental; @@ -118,6 +119,10 @@ import static org.apache.ignite.internal.processors.job.GridJobProcessor.DFLT_JOBS_HISTORY_SIZE; import static org.apache.ignite.internal.processors.jobmetrics.GridJobMetricsProcessor.DFLT_JOBS_METRICS_CONCURRENCY_LEVEL; import static org.apache.ignite.internal.processors.metastorage.persistence.DistributedMetaStorageImpl.DFLT_MAX_HISTORY_BYTES; +import static org.apache.ignite.internal.processors.performancestatistics.FilePerformanceStatisticsWriter.DFLT_BUFFER_SIZE; +import static org.apache.ignite.internal.processors.performancestatistics.FilePerformanceStatisticsWriter.DFLT_CACHED_STRINGS_THRESHOLD; +import static org.apache.ignite.internal.processors.performancestatistics.FilePerformanceStatisticsWriter.DFLT_FILE_MAX_SIZE; +import static org.apache.ignite.internal.processors.performancestatistics.FilePerformanceStatisticsWriter.DFLT_FLUSH_SIZE; import static org.apache.ignite.internal.processors.query.QueryUtils.DFLT_INDEXING_DISCOVERY_HISTORY_SIZE; import static org.apache.ignite.internal.processors.rest.GridRestProcessor.DFLT_SES_TIMEOUT; import static org.apache.ignite.internal.processors.rest.GridRestProcessor.DFLT_SES_TOKEN_INVALIDATE_INTERVAL; @@ -1962,6 +1967,38 @@ public final class IgniteSystemProperties { public static final String IGNITE_DEFRAGMENTATION_REGION_SIZE_PERCENTAGE = "IGNITE_DEFRAGMENTATION_REGION_SIZE_PERCENTAGE"; + /** + * Performance statistics maximum file size in bytes. Performance statistics will be stopped when the size exceeded. + * The default value is {@link FilePerformanceStatisticsWriter#DFLT_FILE_MAX_SIZE}. + */ + @SystemProperty(value = "Performance statistics maximum file size in bytes. Performance statistics will be " + + "stopped when the size exceeded", type = Long.class, defaults = "" + DFLT_FILE_MAX_SIZE) + public static final String IGNITE_PERF_STAT_FILE_MAX_SIZE = "IGNITE_PERF_STAT_FILE_MAX_SIZE"; + + /** + * Performance statistics off heap buffer size in bytes. The default value is + * {@link FilePerformanceStatisticsWriter#DFLT_BUFFER_SIZE}. + */ + @SystemProperty(value = "Performance statistics off heap buffer size in bytes", type = Integer.class, + defaults = "" + DFLT_BUFFER_SIZE) + public static final String IGNITE_PERF_STAT_BUFFER_SIZE = "IGNITE_PERF_STAT_BUFFER_SIZE"; + + /** + * Performance statistics minimal batch size to flush in bytes. The default value is + * {@link FilePerformanceStatisticsWriter#DFLT_FLUSH_SIZE}. + */ + @SystemProperty(value = "Performance statistics minimal batch size to flush in bytes", type = Integer.class, + defaults = "" + DFLT_FLUSH_SIZE) + public static final String IGNITE_PERF_STAT_FLUSH_SIZE = "IGNITE_PERF_STAT_FLUSH_SIZE"; + + /** + * Performance statistics maximum cached strings threshold. String caching will stop on threshold excess. + * The default value is {@link FilePerformanceStatisticsWriter#DFLT_CACHED_STRINGS_THRESHOLD}. + */ + @SystemProperty(value = "Performance statistics maximum cached strings threshold. String caching will stop on " + + "threshold excess", type = Integer.class, defaults = "" + DFLT_CACHED_STRINGS_THRESHOLD) + public static final String IGNITE_PERF_STAT_CACHED_STRINGS_THRESHOLD = "IGNITE_PERF_STAT_CACHED_STRINGS_THRESHOLD"; + /** * Enforces singleton. */ diff --git a/modules/core/src/main/java/org/apache/ignite/internal/GridComponent.java b/modules/core/src/main/java/org/apache/ignite/internal/GridComponent.java index 067f79b9c77ac..60198749dfdad 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/GridComponent.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/GridComponent.java @@ -76,7 +76,10 @@ enum DiscoveryDataExchangeType { SERVICE_PROC, /** Distributed MetaStorage processor. */ - META_STORAGE; + META_STORAGE, + + /** Performance statistics processor. */ + PERFORMANCE_STAT_PROC; /** Cached values array. */ public static final DiscoveryDataExchangeType[] VALUES = values(); diff --git a/modules/core/src/main/java/org/apache/ignite/internal/GridKernalContext.java b/modules/core/src/main/java/org/apache/ignite/internal/GridKernalContext.java index 56f976534fe84..a799f6053a9de 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/GridKernalContext.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/GridKernalContext.java @@ -21,7 +21,6 @@ import java.util.Map; import java.util.UUID; import java.util.concurrent.ExecutorService; - import org.apache.ignite.IgniteLogger; import org.apache.ignite.configuration.IgniteConfiguration; import org.apache.ignite.internal.managers.checkpoint.GridCheckpointManager; @@ -58,6 +57,7 @@ import org.apache.ignite.internal.processors.metastorage.DistributedMetaStorage; import org.apache.ignite.internal.processors.metric.GridMetricManager; import org.apache.ignite.internal.processors.odbc.ClientListenerProcessor; +import org.apache.ignite.internal.processors.performancestatistics.PerformanceStatisticsProcessor; import org.apache.ignite.internal.processors.platform.PlatformProcessor; import org.apache.ignite.internal.processors.plugin.IgnitePluginProcessor; import org.apache.ignite.internal.processors.pool.PoolProcessor; @@ -761,4 +761,11 @@ public interface GridKernalContext extends Iterable { * @return Thread pool for create/rebuild indexes. */ public ExecutorService buildIndexExecutorService(); + + /** + * Gets Performance statistics processor. + * + * @return Performance statistics processor. + */ + public PerformanceStatisticsProcessor performanceStatistics(); } diff --git a/modules/core/src/main/java/org/apache/ignite/internal/GridKernalContextImpl.java b/modules/core/src/main/java/org/apache/ignite/internal/GridKernalContextImpl.java index ae589adff86e3..092cf1ab6ed41 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/GridKernalContextImpl.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/GridKernalContextImpl.java @@ -31,7 +31,6 @@ import java.util.Map; import java.util.UUID; import java.util.concurrent.ExecutorService; - import org.apache.ignite.IgniteCheckedException; import org.apache.ignite.IgniteException; import org.apache.ignite.IgniteLogger; @@ -78,6 +77,7 @@ import org.apache.ignite.internal.processors.metric.GridMetricManager; import org.apache.ignite.internal.processors.nodevalidation.DiscoveryNodeValidationProcessor; import org.apache.ignite.internal.processors.odbc.ClientListenerProcessor; +import org.apache.ignite.internal.processors.performancestatistics.PerformanceStatisticsProcessor; import org.apache.ignite.internal.processors.platform.PlatformProcessor; import org.apache.ignite.internal.processors.platform.plugin.PlatformPluginProcessor; import org.apache.ignite.internal.processors.plugin.IgnitePluginProcessor; @@ -415,6 +415,10 @@ public class GridKernalContextImpl implements GridKernalContext, Externalizable @GridToStringExclude private DurableBackgroundTasksProcessor durableBackgroundTasksProcessor; + /** Performance statistics processor. */ + @GridToStringExclude + private PerformanceStatisticsProcessor perfStatProc; + /** */ private Thread.UncaughtExceptionHandler hnd; @@ -704,6 +708,8 @@ else if (comp instanceof DurableBackgroundTasksProcessor) durableBackgroundTasksProcessor = (DurableBackgroundTasksProcessor)comp; else if (comp instanceof MaintenanceProcessor) maintenanceProc = (MaintenanceProcessor) comp; + else if (comp instanceof PerformanceStatisticsProcessor) + perfStatProc = (PerformanceStatisticsProcessor)comp; else if (!(comp instanceof DiscoveryNodeValidationProcessor || comp instanceof PlatformPluginProcessor)) assert (comp instanceof GridPluginComponent) : "Unknown manager class: " + comp.getClass(); @@ -1297,4 +1303,9 @@ public void recoveryMode(boolean recoveryMode) { @Override public ExecutorService buildIndexExecutorService() { return buildIdxExecSvc; } + + /** {@inheritDoc} */ + @Override public PerformanceStatisticsProcessor performanceStatistics() { + return perfStatProc; + } } diff --git a/modules/core/src/main/java/org/apache/ignite/internal/IgniteFeatures.java b/modules/core/src/main/java/org/apache/ignite/internal/IgniteFeatures.java index f9704aecadc30..fa487a0554dfa 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/IgniteFeatures.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/IgniteFeatures.java @@ -136,7 +136,10 @@ public enum IgniteFeatures { SPLITTED_CACHE_CONFIGURATIONS_V2(46), /** Cache encryption key change. See {@link IgniteEncryption#changeCacheGroupKey(Collection)}. */ - CACHE_GROUP_KEY_CHANGE(47); + CACHE_GROUP_KEY_CHANGE(47), + + /** Collecting performance statistics. */ + PERFORMANCE_STATISTICS(48); /** * Unique feature identifier. diff --git a/modules/core/src/main/java/org/apache/ignite/internal/IgniteKernal.java b/modules/core/src/main/java/org/apache/ignite/internal/IgniteKernal.java index 1bb3e6c6711dd..7ee9d564f30bb 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/IgniteKernal.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/IgniteKernal.java @@ -167,6 +167,7 @@ import org.apache.ignite.internal.processors.nodevalidation.DiscoveryNodeValidationProcessor; import org.apache.ignite.internal.processors.nodevalidation.OsDiscoveryNodeValidationProcessor; import org.apache.ignite.internal.processors.odbc.ClientListenerProcessor; +import org.apache.ignite.internal.processors.performancestatistics.PerformanceStatisticsProcessor; import org.apache.ignite.internal.processors.platform.PlatformNoopProcessor; import org.apache.ignite.internal.processors.platform.PlatformProcessor; import org.apache.ignite.internal.processors.platform.plugin.PlatformPluginProcessor; @@ -1266,6 +1267,7 @@ public void start( startProcessor(new DistributedMetaStorageImpl(ctx)); startProcessor(new DistributedConfigurationProcessor(ctx)); startProcessor(new DurableBackgroundTasksProcessor(ctx)); + startProcessor(new PerformanceStatisticsProcessor(ctx)); startTimer.finishGlobalStage("Start processors"); diff --git a/modules/core/src/main/java/org/apache/ignite/internal/managers/IgniteMBeansManager.java b/modules/core/src/main/java/org/apache/ignite/internal/managers/IgniteMBeansManager.java index 550b60bf0eaae..5c502e97033d6 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/managers/IgniteMBeansManager.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/managers/IgniteMBeansManager.java @@ -42,6 +42,7 @@ import org.apache.ignite.internal.processors.cache.warmup.WarmUpMXBeanImpl; import org.apache.ignite.internal.processors.cluster.BaselineAutoAdjustMXBeanImpl; import org.apache.ignite.internal.processors.metric.MetricsMxBeanImpl; +import org.apache.ignite.internal.processors.performancestatistics.PerformanceStatisticsMBeanImpl; import org.apache.ignite.internal.util.StripedExecutor; import org.apache.ignite.internal.util.typedef.internal.U; import org.apache.ignite.internal.worker.FailureHandlingMxBeanImpl; @@ -55,6 +56,7 @@ import org.apache.ignite.mxbean.FailureHandlingMxBean; import org.apache.ignite.mxbean.IgniteMXBean; import org.apache.ignite.mxbean.MetricsMxBean; +import org.apache.ignite.mxbean.PerformanceStatisticsMBean; import org.apache.ignite.mxbean.QueryMXBean; import org.apache.ignite.mxbean.ServiceMXBean; import org.apache.ignite.mxbean.SnapshotMXBean; @@ -236,6 +238,10 @@ public void registerMBeansAfterNodeStarted( if (ctx.query().moduleEnabled()) ctx.query().getIndexing().registerMxBeans(this); + + PerformanceStatisticsMBeanImpl performanceStatMbean = new PerformanceStatisticsMBeanImpl(ctx); + registerMBean("PerformanceStatistics", performanceStatMbean.getClass().getSimpleName(), performanceStatMbean, + PerformanceStatisticsMBean.class); } /** diff --git a/modules/core/src/main/java/org/apache/ignite/internal/metric/IoStatisticsHolderQuery.java b/modules/core/src/main/java/org/apache/ignite/internal/metric/IoStatisticsHolderQuery.java index 650e5e362fb64..e9ce9acb9a6fb 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/metric/IoStatisticsHolderQuery.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/metric/IoStatisticsHolderQuery.java @@ -40,16 +40,6 @@ public class IoStatisticsHolderQuery implements IoStatisticsHolder { /** */ private LongAdder physicalReadCtr = new LongAdder(); - /** */ - private final String qryId; - - /** - * @param qryId Query id. - */ - public IoStatisticsHolderQuery(String qryId) { - this.qryId = qryId; - } - /** {@inheritDoc} */ @Override public void trackLogicalRead(long pageAddr) { logicalReadCtr.increment(); @@ -77,13 +67,6 @@ public IoStatisticsHolderQuery(String qryId) { return null; } - /** - * @return Query id. - */ - public String queryId() { - return qryId; - } - /** * Add given given statistics into this. * Merge query statistics. @@ -97,10 +80,10 @@ public void merge(long logicalReads, long physicalReads) { physicalReadCtr.add(physicalReads); } + /** {@inheritDoc} */ @Override public String toString() { return S.toString(IoStatisticsHolderQuery.class, this, "logicalReadCtr", logicalReadCtr, - "physicalReadCtr", physicalReadCtr, - "qryId", qryId); + "physicalReadCtr", physicalReadCtr); } } diff --git a/modules/core/src/main/java/org/apache/ignite/internal/metric/IoStatisticsQueryHelper.java b/modules/core/src/main/java/org/apache/ignite/internal/metric/IoStatisticsQueryHelper.java index 60f08f5eee3f4..32a65ff0feff3 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/metric/IoStatisticsQueryHelper.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/metric/IoStatisticsQueryHelper.java @@ -28,15 +28,13 @@ public class IoStatisticsQueryHelper { /** * Start gathering IO statistics for query. Should be used together with {@code finishGatheringQueryStatistics} * method. - * - * @param qryId Identifier of query. */ - public static void startGatheringQueryStatistics(String qryId) { + public static void startGatheringQueryStatistics() { IoStatisticsHolderQuery currQryStatisticsHolder = CUR_QRY_STATS.get(); assert currQryStatisticsHolder == null : currQryStatisticsHolder; - CUR_QRY_STATS.set(new IoStatisticsHolderQuery(qryId)); + CUR_QRY_STATS.set(new IoStatisticsHolderQuery()); } /** diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/CacheLockImpl.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/CacheLockImpl.java index ae7b42ea498ea..040642aed94e7 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/CacheLockImpl.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/CacheLockImpl.java @@ -25,7 +25,9 @@ import org.apache.ignite.IgniteCheckedException; import org.apache.ignite.internal.IgniteInternalFuture; import org.apache.ignite.internal.IgniteInterruptedCheckedException; +import org.apache.ignite.internal.processors.performancestatistics.OperationType; import org.apache.ignite.internal.util.typedef.internal.S; +import org.apache.ignite.internal.util.typedef.internal.U; import org.jetbrains.annotations.NotNull; /** @@ -50,6 +52,9 @@ class CacheLockImpl implements Lock { /** */ private volatile Thread lockedThread; + /** Lock start time in nanoseconds. */ + private volatile long startTimeNanos; + /** * @param gate Gate. * @param delegate Delegate. @@ -89,6 +94,9 @@ class CacheLockImpl implements Lock { private void incrementLockCounter() { assert (lockedThread == null && cntr == 0) || (lockedThread == Thread.currentThread() && cntr > 0); + if (cntr == 0 && delegate.context().kernalContext().performanceStatistics().enabled()) + startTimeNanos = System.nanoTime(); + cntr++; lockedThread = Thread.currentThread(); @@ -186,9 +194,20 @@ private void incrementLockCounter() { cntr--; - if (cntr == 0) + if (cntr == 0) { lockedThread = null; + if (startTimeNanos > 0) { + delegate.context().kernalContext().performanceStatistics().cacheOperation( + OperationType.CACHE_LOCK, + delegate.context().cacheId(), + U.currentTimeMillis(), + System.nanoTime() - startTimeNanos); + + startTimeNanos = 0; + } + } + delegate.unlockAll(keys); } catch (IgniteCheckedException e) { diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheAdapter.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheAdapter.java index a6fcee81de904..0fe7e44c108df 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheAdapter.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheAdapter.java @@ -108,6 +108,7 @@ import org.apache.ignite.internal.processors.datastreamer.DataStreamerEntry; import org.apache.ignite.internal.processors.datastreamer.DataStreamerImpl; import org.apache.ignite.internal.processors.dr.IgniteDrDataStreamerCacheUpdater; +import org.apache.ignite.internal.processors.performancestatistics.OperationType; import org.apache.ignite.internal.processors.platform.cache.PlatformCacheEntryFilter; import org.apache.ignite.internal.processors.task.GridInternal; import org.apache.ignite.internal.transactions.IgniteTxHeuristicCheckedException; @@ -1468,8 +1469,9 @@ private boolean evictx(K key, GridCacheVersion ver, A.notNull(key, "key"); boolean statsEnabled = ctx.statisticsEnabled(); + boolean performanceStatsEnabled = ctx.kernalContext().performanceStatistics().enabled(); - long start = statsEnabled ? System.nanoTime() : 0L; + long start = statsEnabled || performanceStatsEnabled ? System.nanoTime() : 0L; boolean keepBinary = ctx.keepBinary(); @@ -1487,6 +1489,9 @@ private boolean evictx(K key, GridCacheVersion ver, if (statsEnabled) metrics0().addGetTimeNanos(System.nanoTime() - start); + if (performanceStatsEnabled) + writeStatistics(OperationType.CACHE_GET, start); + return val; } @@ -1495,8 +1500,9 @@ private boolean evictx(K key, GridCacheVersion ver, A.notNull(key, "key"); boolean statsEnabled = ctx.statisticsEnabled(); + boolean performanceStatsEnabled = ctx.kernalContext().performanceStatistics().enabled(); - long start = statsEnabled ? System.nanoTime() : 0L; + long start = statsEnabled || performanceStatsEnabled ? System.nanoTime() : 0L; boolean keepBinary = ctx.keepBinary(); @@ -1523,6 +1529,9 @@ private boolean evictx(K key, GridCacheVersion ver, if (statsEnabled) metrics0().addGetTimeNanos(System.nanoTime() - start); + if (performanceStatsEnabled) + writeStatistics(OperationType.CACHE_GET, start); + return val; } @@ -1531,8 +1540,9 @@ private boolean evictx(K key, GridCacheVersion ver, A.notNull(key, "key"); final boolean statsEnabled = ctx.statisticsEnabled(); + final boolean performanceStatsEnabled = ctx.kernalContext().performanceStatistics().enabled(); - final long start = statsEnabled ? System.nanoTime() : 0L; + final long start = statsEnabled || performanceStatsEnabled ? System.nanoTime() : 0L; final boolean keepBinary = ctx.keepBinary(); @@ -1558,6 +1568,9 @@ private boolean evictx(K key, GridCacheVersion ver, if (statsEnabled) fut.listen(new UpdateGetTimeStatClosure(metrics0(), start)); + if (performanceStatsEnabled) + fut.listen(f -> writeStatistics(OperationType.CACHE_GET, start)); + return fut; } @@ -1566,8 +1579,9 @@ private boolean evictx(K key, GridCacheVersion ver, A.notNull(key, "key"); final boolean statsEnabled = ctx.statisticsEnabled(); + final boolean performanceStatsEnabled = ctx.kernalContext().performanceStatistics().enabled(); - final long start = statsEnabled ? System.nanoTime() : 0L; + final long start = statsEnabled || performanceStatsEnabled ? System.nanoTime() : 0L; final boolean keepBinary = ctx.keepBinary(); @@ -1611,6 +1625,9 @@ private boolean evictx(K key, GridCacheVersion ver, if (statsEnabled) fut.listen(new UpdateGetTimeStatClosure(metrics0(), start)); + if (performanceStatsEnabled) + fut.listen(f -> writeStatistics(OperationType.CACHE_GET, start)); + return fr; } @@ -1619,8 +1636,9 @@ private boolean evictx(K key, GridCacheVersion ver, A.notNull(keys, "keys"); boolean statsEnabled = ctx.statisticsEnabled(); + boolean performanceStatsEnabled = ctx.kernalContext().performanceStatistics().enabled(); - long start = statsEnabled ? System.nanoTime() : 0L; + long start = statsEnabled || performanceStatsEnabled ? System.nanoTime() : 0L; final CacheOperationContext opCtx = ctx.operationContextPerCall(); @@ -1637,6 +1655,9 @@ private boolean evictx(K key, GridCacheVersion ver, if (statsEnabled) metrics0().addGetTimeNanos(System.nanoTime() - start); + if (performanceStatsEnabled) + writeStatistics(OperationType.CACHE_GET_ALL, start); + return map; } @@ -1646,8 +1667,9 @@ private boolean evictx(K key, GridCacheVersion ver, A.notNull(keys, "keys"); boolean statsEnabled = ctx.statisticsEnabled(); + boolean performanceStatsEnabled = ctx.kernalContext().performanceStatistics().enabled(); - long start = statsEnabled ? System.nanoTime() : 0L; + long start = statsEnabled || performanceStatsEnabled ? System.nanoTime() : 0L; final CacheOperationContext opCtx = ctx.operationContextPerCall(); @@ -1669,6 +1691,9 @@ private boolean evictx(K key, GridCacheVersion ver, if (statsEnabled) metrics0().addGetTimeNanos(System.nanoTime() - start); + if (performanceStatsEnabled) + writeStatistics(OperationType.CACHE_GET_ALL, start); + return res; } @@ -1677,8 +1702,9 @@ private boolean evictx(K key, GridCacheVersion ver, A.notNull(keys, "keys"); final boolean statsEnabled = ctx.statisticsEnabled(); + final boolean performanceStatsEnabled = ctx.kernalContext().performanceStatistics().enabled(); - final long start = statsEnabled ? System.nanoTime() : 0L; + final long start = statsEnabled || performanceStatsEnabled ? System.nanoTime() : 0L; String taskName = ctx.kernalContext().job().currentTaskName(); @@ -1706,6 +1732,9 @@ private boolean evictx(K key, GridCacheVersion ver, if (statsEnabled) fut.listen(new UpdateGetTimeStatClosure>(metrics0(), start)); + if (performanceStatsEnabled) + fut.listen(f -> writeStatistics(OperationType.CACHE_GET_ALL, start)); + return fut; } @@ -1715,8 +1744,9 @@ private boolean evictx(K key, GridCacheVersion ver, A.notNull(keys, "keys"); final boolean statsEnabled = ctx.statisticsEnabled(); + final boolean performanceStatsEnabled = ctx.kernalContext().performanceStatistics().enabled(); - final long start = statsEnabled ? System.nanoTime() : 0L; + final long start = statsEnabled || performanceStatsEnabled ? System.nanoTime() : 0L; CacheOperationContext opCtx = ctx.operationContextPerCall(); @@ -1758,6 +1788,9 @@ private boolean evictx(K key, GridCacheVersion ver, if (statsEnabled) fut.listen(new UpdateGetTimeStatClosure>(metrics0(), start)); + if (performanceStatsEnabled) + fut.listen(f -> writeStatistics(OperationType.CACHE_GET_ALL, start)); + return rf; } @@ -2457,8 +2490,9 @@ private void clearReservationsIfNeeded( @Nullable public V getAndPut(final K key, final V val, @Nullable final CacheEntryPredicate filter) throws IgniteCheckedException { boolean statsEnabled = ctx.statisticsEnabled(); + boolean performanceStatsEnabled = ctx.kernalContext().performanceStatistics().enabled(); - long start = statsEnabled ? System.nanoTime() : 0L; + long start = statsEnabled || performanceStatsEnabled ? System.nanoTime() : 0L; A.notNull(key, "key", val, "val"); @@ -2470,6 +2504,9 @@ private void clearReservationsIfNeeded( if (statsEnabled) metrics0().addPutAndGetTimeNanos(System.nanoTime() - start); + if (performanceStatsEnabled) + writeStatistics(OperationType.CACHE_GET_AND_PUT, start); + return prevVal; } @@ -2509,8 +2546,9 @@ protected V getAndPut0(final K key, final V val, @Nullable final CacheEntryPredi */ protected final IgniteInternalFuture getAndPutAsync(K key, V val, @Nullable CacheEntryPredicate filter) { final boolean statsEnabled = ctx.statisticsEnabled(); + final boolean performanceStatsEnabled = ctx.kernalContext().performanceStatistics().enabled(); - final long start = statsEnabled ? System.nanoTime() : 0L; + final long start = statsEnabled || performanceStatsEnabled ? System.nanoTime() : 0L; A.notNull(key, "key", val, "val"); @@ -2522,6 +2560,9 @@ protected final IgniteInternalFuture getAndPutAsync(K key, V val, @Nullable C if (statsEnabled) fut.listen(new UpdatePutAndGetTimeStatClosure(metrics0(), start)); + if (performanceStatsEnabled) + fut.listen(f -> writeStatistics(OperationType.CACHE_GET_AND_PUT, start)); + return fut; } @@ -2566,8 +2607,9 @@ public IgniteInternalFuture getAndPutAsync0(final K key, public boolean put(final K key, final V val, final CacheEntryPredicate filter) throws IgniteCheckedException { boolean statsEnabled = ctx.statisticsEnabled(); + boolean performanceStatsEnabled = ctx.kernalContext().performanceStatistics().enabled(); - long start = statsEnabled ? System.nanoTime() : 0L; + long start = statsEnabled || performanceStatsEnabled ? System.nanoTime() : 0L; A.notNull(key, "key", val, "val"); @@ -2579,6 +2621,9 @@ public boolean put(final K key, final V val, final CacheEntryPredicate filter) if (statsEnabled && stored) metrics0().addPutTimeNanos(System.nanoTime() - start); + if (performanceStatsEnabled) + writeStatistics(OperationType.CACHE_PUT, start); + return stored; } @@ -2692,8 +2737,9 @@ private EntryProcessorResult invoke0( tx.topologyVersion(topVer); final boolean statsEnabled = ctx.statisticsEnabled(); + final boolean performanceStatsEnabled = ctx.kernalContext().performanceStatistics().enabled(); - final long start = statsEnabled ? System.nanoTime() : 0L; + long start = statsEnabled || performanceStatsEnabled ? System.nanoTime() : 0L; IgniteInternalFuture fut = tx.invokeAsync(ctx, null, @@ -2706,6 +2752,9 @@ private EntryProcessorResult invoke0( if (statsEnabled) metrics0().addInvokeTimeNanos(System.nanoTime() - start); + if (performanceStatsEnabled) + writeStatistics(OperationType.CACHE_INVOKE, start); + EntryProcessorResult res = null; if (resMap != null) { @@ -2731,8 +2780,9 @@ private EntryProcessorResult invoke0( warnIfUnordered(keys, BulkOperation.INVOKE); final boolean statsEnabled = ctx.statisticsEnabled(); + final boolean performanceStatsEnabled = ctx.kernalContext().performanceStatistics().enabled(); - final long start = statsEnabled ? System.nanoTime() : 0L; + long start = statsEnabled || performanceStatsEnabled ? System.nanoTime() : 0L; return syncOp(new SyncOp>>(keys.size() == 1) { @Override public Map> op(GridNearTxLocal tx) @@ -2751,6 +2801,9 @@ private EntryProcessorResult invoke0( if (statsEnabled) metrics0().addInvokeTimeNanos(System.nanoTime() - start); + if (performanceStatsEnabled) + writeStatistics(OperationType.CACHE_INVOKE_ALL, start); + return res != null ? res : Collections.>emptyMap(); } }); @@ -2768,8 +2821,9 @@ private EntryProcessorResult invoke0( validateCacheKey(key); final boolean statsEnabled = ctx.statisticsEnabled(); + final boolean performanceStatsEnabled = ctx.kernalContext().performanceStatistics().enabled(); - final long start = statsEnabled ? System.nanoTime() : 0L; + long start = statsEnabled || performanceStatsEnabled ? System.nanoTime() : 0L; IgniteInternalFuture fut = asyncOp(new AsyncOp() { @Override public IgniteInternalFuture op(GridNearTxLocal tx, AffinityTopologyVersion readyTopVer) { @@ -2796,6 +2850,9 @@ private EntryProcessorResult invoke0( if (statsEnabled) metrics0().addInvokeTimeNanos(System.nanoTime() - start); + if (performanceStatsEnabled) + writeStatistics(OperationType.CACHE_INVOKE, start); + Map> resMap = ret.value(); if (resMap != null) { @@ -2822,8 +2879,9 @@ private EntryProcessorResult invoke0( warnIfUnordered(keys, BulkOperation.INVOKE); final boolean statsEnabled = ctx.statisticsEnabled(); + final boolean performanceStatsEnabled = ctx.kernalContext().performanceStatistics().enabled(); - final long start = statsEnabled ? System.nanoTime() : 0L; + long start = statsEnabled || performanceStatsEnabled ? System.nanoTime() : 0L; IgniteInternalFuture fut = asyncOp(new AsyncOp(keys) { @Override public IgniteInternalFuture op(GridNearTxLocal tx, @@ -2855,6 +2913,9 @@ private EntryProcessorResult invoke0( if (statsEnabled) metrics0().addInvokeTimeNanos(System.nanoTime() - start); + if (performanceStatsEnabled) + writeStatistics(OperationType.CACHE_INVOKE_ALL, start); + assert ret != null; return ret.value() != null ? ret.>>value() : Collections.>emptyMap(); @@ -2874,8 +2935,9 @@ private EntryProcessorResult invoke0( warnIfUnordered(map, BulkOperation.INVOKE); final boolean statsEnabled = ctx.statisticsEnabled(); + final boolean performanceStatsEnabled = ctx.kernalContext().performanceStatistics().enabled(); - final long start = statsEnabled ? System.nanoTime() : 0L; + long start = statsEnabled || performanceStatsEnabled ? System.nanoTime() : 0L; IgniteInternalFuture fut = asyncOp(new AsyncOp(map.keySet()) { @Override public IgniteInternalFuture op(GridNearTxLocal tx, @@ -2902,6 +2964,9 @@ private EntryProcessorResult invoke0( if (statsEnabled) metrics0().addInvokeTimeNanos(System.nanoTime() - start); + if (performanceStatsEnabled) + writeStatistics(OperationType.CACHE_INVOKE_ALL, start); + assert ret != null; return ret.value() != null ? ret.>>value() : Collections.>emptyMap(); @@ -2921,8 +2986,9 @@ private EntryProcessorResult invoke0( warnIfUnordered(map, BulkOperation.INVOKE); final boolean statsEnabled = ctx.statisticsEnabled(); + final boolean performanceStatsEnabled = ctx.kernalContext().performanceStatistics().enabled(); - final long start = statsEnabled ? System.nanoTime() : 0L; + long start = statsEnabled || performanceStatsEnabled ? System.nanoTime() : 0L; return syncOp(new SyncOp>>(map.size() == 1) { @Nullable @Override public Map> op(GridNearTxLocal tx) @@ -2935,6 +3001,9 @@ private EntryProcessorResult invoke0( if (statsEnabled) metrics0().addInvokeTimeNanos(System.nanoTime() - start); + if (performanceStatsEnabled) + writeStatistics(OperationType.CACHE_INVOKE_ALL, start); + return value; } }); @@ -2958,14 +3027,18 @@ public final IgniteInternalFuture putAsync(K key, V val, @Nullable Cach validateCacheKey(key); final boolean statsEnabled = ctx.statisticsEnabled(); + final boolean performanceStatsEnabled = ctx.kernalContext().performanceStatistics().enabled(); - final long start = statsEnabled ? System.nanoTime() : 0L; + final long start = statsEnabled || performanceStatsEnabled ? System.nanoTime() : 0L; IgniteInternalFuture fut = putAsync0(key, val, filter); if (statsEnabled) fut.listen(new UpdatePutTimeStatClosure(metrics0(), start)); + if (performanceStatsEnabled) + fut.listen(f -> writeStatistics(OperationType.CACHE_PUT, start)); + return fut; } @@ -3059,8 +3132,9 @@ public IgniteInternalFuture putAsync0(final K key, final V val, return; boolean statsEnabled = ctx.statisticsEnabled(); + boolean performanceStatsEnabled = ctx.kernalContext().performanceStatistics().enabled(); - long start = statsEnabled ? System.nanoTime() : 0L; + long start = statsEnabled || performanceStatsEnabled ? System.nanoTime() : 0L; if (keyCheck) validateCacheKeys(m.keySet()); @@ -3071,6 +3145,9 @@ public IgniteInternalFuture putAsync0(final K key, final V val, if (statsEnabled) metrics0().addPutTimeNanos(System.nanoTime() - start); + + if (performanceStatsEnabled) + writeStatistics(OperationType.CACHE_PUT_ALL, start); } /** @@ -3096,8 +3173,9 @@ protected void putAll0(final Map m) throws IgniteCheck return new GridFinishedFuture(); boolean statsEnabled = ctx.statisticsEnabled(); + boolean performanceStatsEnabled = ctx.kernalContext().performanceStatistics().enabled(); - long start = statsEnabled ? System.nanoTime() : 0L; + long start = statsEnabled || performanceStatsEnabled ? System.nanoTime() : 0L; if (keyCheck) validateCacheKeys(m.keySet()); @@ -3109,6 +3187,9 @@ protected void putAll0(final Map m) throws IgniteCheck if (statsEnabled) fut.listen(new UpdatePutTimeStatClosure(metrics0(), start)); + if (performanceStatsEnabled) + fut.listen(f -> writeStatistics(OperationType.CACHE_PUT_ALL, start)); + return fut; } @@ -3135,8 +3216,9 @@ protected IgniteInternalFuture putAllAsync0(final Map putAllAsync0(final Map getAndRemoveAsync(final K key) { final boolean statsEnabled = ctx.statisticsEnabled(); + final boolean performanceStatsEnabled = ctx.kernalContext().performanceStatistics().enabled(); - final long start = statsEnabled ? System.nanoTime() : 0L; + final long start = statsEnabled || performanceStatsEnabled ? System.nanoTime() : 0L; A.notNull(key, "key"); @@ -3204,6 +3290,9 @@ protected V getAndRemove0(final K key) throws IgniteCheckedException { if (statsEnabled) fut.listen(new UpdateRemoveTimeStatClosure(metrics0(), start)); + if (performanceStatsEnabled) + fut.listen(f -> writeStatistics(OperationType.CACHE_GET_AND_REMOVE, start)); + return fut; } @@ -3255,8 +3344,9 @@ protected IgniteInternalFuture getAndRemoveAsync0(final K key) { /** {@inheritDoc} */ @Override public void removeAll(final Collection keys) throws IgniteCheckedException { boolean statsEnabled = ctx.statisticsEnabled(); + boolean performanceStatsEnabled = ctx.kernalContext().performanceStatistics().enabled(); - long start = statsEnabled ? System.nanoTime() : 0L; + long start = statsEnabled || performanceStatsEnabled ? System.nanoTime() : 0L; A.notNull(keys, "keys"); @@ -3272,6 +3362,9 @@ protected IgniteInternalFuture getAndRemoveAsync0(final K key) { if (statsEnabled) metrics0().addRemoveTimeNanos(System.nanoTime() - start); + + if (performanceStatsEnabled) + writeStatistics(OperationType.CACHE_REMOVE_ALL, start); } /** @@ -3302,8 +3395,9 @@ protected void removeAll0(final Collection keys) throws IgniteCheck return new GridFinishedFuture(); final boolean statsEnabled = ctx.statisticsEnabled(); + final boolean performanceStatsEnabled = ctx.kernalContext().performanceStatistics().enabled(); - final long start = statsEnabled ? System.nanoTime() : 0L; + final long start = statsEnabled || performanceStatsEnabled ? System.nanoTime() : 0L; if (keyCheck) validateCacheKeys(keys); @@ -3315,6 +3409,9 @@ protected void removeAll0(final Collection keys) throws IgniteCheck if (statsEnabled) fut.listen(new UpdateRemoveTimeStatClosure<>(metrics0(), start)); + if (performanceStatsEnabled) + fut.listen(f -> writeStatistics(OperationType.CACHE_REMOVE_ALL, start)); + return fut; } @@ -3353,8 +3450,9 @@ protected IgniteInternalFuture removeAllAsync0(final Collection removeAsync(final K key, @Nullable final CacheEntryPredicate filter) { final boolean statsEnabled = ctx.statisticsEnabled(); + final boolean performanceStatsEnabled = ctx.kernalContext().performanceStatistics().enabled(); - final long start = statsEnabled ? System.nanoTime() : 0L; + final long start = statsEnabled || performanceStatsEnabled ? System.nanoTime() : 0L; A.notNull(key, "key"); @@ -3424,6 +3526,9 @@ public IgniteInternalFuture removeAsync(final K key, @Nullable final Ca if (statsEnabled) fut.listen(new UpdateRemoveTimeStatClosure(metrics0(), start)); + if (performanceStatsEnabled) + fut.listen(f -> writeStatistics(OperationType.CACHE_REMOVE, start)); + return fut; } @@ -6792,6 +6897,20 @@ public InvokeAllTimeStatClosure(CacheMetricsImpl metrics, final long start) { } } + /** + * Writes cache operation performance statistics. + * + * @param op Operation type. + * @param start Start time in nanoseconds. + */ + private void writeStatistics(OperationType op, long start) { + ctx.kernalContext().performanceStatistics().cacheOperation( + op, + ctx.cacheId(), + U.currentTimeMillis(), + System.nanoTime() - start); + } + /** * Delayed callable class. */ diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheProcessor.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheProcessor.java index ae33bf8e7aec1..7b3f9c19b409b 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheProcessor.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheProcessor.java @@ -2293,6 +2293,9 @@ private void onCacheStarted(GridCacheContext cacheCtx) throws IgniteCheckedExcep grp.onCacheStarted(cacheCtx); onKernalStart(cache); + + if (ctx.performanceStatistics().enabled() && U.isLocalNodeCoordinator(ctx.discovery())) + ctx.performanceStatistics().cacheStart(cacheCtx.cacheId(), cfg.getName()); } /** diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/wal/reader/StandaloneGridKernalContext.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/wal/reader/StandaloneGridKernalContext.java index d5a9ed05f5956..958053b9ee124 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/wal/reader/StandaloneGridKernalContext.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/wal/reader/StandaloneGridKernalContext.java @@ -74,6 +74,7 @@ import org.apache.ignite.internal.processors.metastorage.DistributedMetaStorage; import org.apache.ignite.internal.processors.metric.GridMetricManager; import org.apache.ignite.internal.processors.odbc.ClientListenerProcessor; +import org.apache.ignite.internal.processors.performancestatistics.PerformanceStatisticsProcessor; import org.apache.ignite.internal.processors.platform.PlatformProcessor; import org.apache.ignite.internal.processors.plugin.IgnitePluginProcessor; import org.apache.ignite.internal.processors.pool.PoolProcessor; @@ -728,4 +729,9 @@ protected IgniteConfiguration prepareIgniteConfiguration() { @Override public ExecutorService buildIndexExecutorService() { return null; } + + /** {@inheritDoc} */ + @Override public PerformanceStatisticsProcessor performanceStatistics() { + return null; + } } diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/query/GridCacheDistributedQueryFuture.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/query/GridCacheDistributedQueryFuture.java index 138077248d0be..0019df0228170 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/query/GridCacheDistributedQueryFuture.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/query/GridCacheDistributedQueryFuture.java @@ -279,4 +279,9 @@ private Collection nodes() { if (qryMgr != null) qryMgr.removeQueryFuture(reqId); } + + /** @return Request ID. */ + long requestId() { + return reqId; + } } diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/query/GridCacheDistributedQueryManager.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/query/GridCacheDistributedQueryManager.java index 3abebf47ce2e6..5474fc5ba63e9 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/query/GridCacheDistributedQueryManager.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/query/GridCacheDistributedQueryManager.java @@ -36,6 +36,8 @@ import org.apache.ignite.internal.cluster.ClusterTopologyCheckedException; import org.apache.ignite.internal.managers.communication.GridIoPolicy; import org.apache.ignite.internal.managers.eventstorage.GridLocalEventListener; +import org.apache.ignite.internal.metric.IoStatisticsHolder; +import org.apache.ignite.internal.metric.IoStatisticsQueryHelper; import org.apache.ignite.internal.processors.cache.mvcc.MvccSnapshot; import org.apache.ignite.internal.processors.query.GridQueryFieldMetadata; import org.apache.ignite.internal.util.GridBoundedConcurrentOrderedSet; @@ -57,6 +59,7 @@ import static org.apache.ignite.events.EventType.EVT_NODE_FAILED; import static org.apache.ignite.events.EventType.EVT_NODE_LEFT; import static org.apache.ignite.internal.GridTopic.TOPIC_CACHE; +import static org.apache.ignite.internal.processors.cache.query.GridCacheQueryType.SCAN; /** * Distributed query manager (for cache in REPLICATED / PARTITIONED cache mode). @@ -597,6 +600,11 @@ else if (!cancelled.contains(res.requestId())) assert qry.type() == GridCacheQueryType.SCAN : qry; assert qry.mvccSnapshot() != null || !cctx.mvccEnabled(); + boolean performanceStatsEnabled = cctx.kernalContext().performanceStatistics().enabled(); + + long startTime = performanceStatsEnabled ? System.currentTimeMillis() : 0; + long startTimeNanos = performanceStatsEnabled ? System.nanoTime() : 0; + GridCloseableIterator locIter0 = null; for (ClusterNode node : nodes) { @@ -627,6 +635,12 @@ else if (!cancelled.contains(res.requestId())) /** */ private Object cur; + /** Logical reads. */ + private long logicalReads; + + /** Physical reads. */ + private long physicalReads; + @Override protected Object onNext() throws IgniteCheckedException { if (!onHasNext()) throw new NoSuchElementException(); @@ -642,8 +656,23 @@ else if (!cancelled.contains(res.requestId())) if (cur != null) return true; - if (locIter != null && locIter.hasNextX()) - cur = locIter.nextX(); + if (locIter != null) { + if (performanceStatsEnabled) + IoStatisticsQueryHelper.startGatheringQueryStatistics(); + + try { + if (locIter.hasNextX()) + cur = locIter.nextX(); + + } finally { + if (performanceStatsEnabled) { + IoStatisticsHolder stat = IoStatisticsQueryHelper.finishGatheringQueryStatistics(); + + logicalReads += stat.logicalReads(); + physicalReads += stat.physicalReads(); + } + } + } return cur != null || (cur = convert(fut.next())) != null; } @@ -669,6 +698,25 @@ private Object convert(Object obj) { if (fut != null) fut.cancel(); + + if (performanceStatsEnabled) { + cctx.kernalContext().performanceStatistics().query( + SCAN, + cctx.name(), + ((GridCacheDistributedQueryFuture)fut).requestId(), + startTime, + System.nanoTime() - startTimeNanos, + true); + + if (logicalReads > 0 || physicalReads > 0) { + cctx.kernalContext().performanceStatistics().queryReads( + SCAN, + cctx.localNodeId(), + ((GridCacheDistributedQueryFuture)fut).requestId(), + logicalReads, + physicalReads); + } + } } }; } diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/query/GridCacheQueryManager.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/query/GridCacheQueryManager.java index f36ebb2caf33d..07d906b906790 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/query/GridCacheQueryManager.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/query/GridCacheQueryManager.java @@ -61,6 +61,8 @@ import org.apache.ignite.internal.IgniteKernal; import org.apache.ignite.internal.NodeStoppingException; import org.apache.ignite.internal.managers.eventstorage.GridLocalEventListener; +import org.apache.ignite.internal.metric.IoStatisticsHolder; +import org.apache.ignite.internal.metric.IoStatisticsQueryHelper; import org.apache.ignite.internal.processors.affinity.AffinityTopologyVersion; import org.apache.ignite.internal.processors.cache.CacheEntryImpl; import org.apache.ignite.internal.processors.cache.CacheInvalidStateException; @@ -1119,6 +1121,11 @@ protected void runQuery(GridCacheQueryInfo qryInfo) { } try { + boolean performanceStatsEnabled = cctx.kernalContext().performanceStatistics().enabled(); + + if (performanceStatsEnabled) + IoStatisticsQueryHelper.startGatheringQueryStatistics(); + boolean loc = qryInfo.local(); QueryResult res = null; @@ -1376,6 +1383,19 @@ protected void runQuery(GridCacheQueryInfo qryInfo) { } else if (rmvIter) removeQueryResult(qryInfo.senderId(), qryInfo.requestId()); + + if (performanceStatsEnabled) { + IoStatisticsHolder stat = IoStatisticsQueryHelper.finishGatheringQueryStatistics(); + + if (stat.logicalReads() > 0 || stat.physicalReads() > 0) { + cctx.kernalContext().performanceStatistics().queryReads( + res.type(), + qryInfo.senderId(), + qryInfo.requestId(), + stat.logicalReads(), + stat.physicalReads()); + } + } } } finally { diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/transactions/IgniteInternalTx.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/transactions/IgniteInternalTx.java index ec4454497ce49..8d419fa9c4986 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/transactions/IgniteInternalTx.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/transactions/IgniteInternalTx.java @@ -93,6 +93,13 @@ public enum FinalizationStatus { */ public long startTime(); + /** + * Start time of this transaction in nanoseconds to measure duration. + * + * @return Start time of this transaction in nanoseconds. + */ + public long startTimeNanos(); + /** * Cache transaction isolation level. * diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/transactions/IgniteTxAdapter.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/transactions/IgniteTxAdapter.java index 5a552a92d93c9..9daddb550ce08 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/transactions/IgniteTxAdapter.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/transactions/IgniteTxAdapter.java @@ -163,6 +163,9 @@ public abstract class IgniteTxAdapter extends GridMetadataAwareAdapter implement @GridToStringInclude protected long startTime = U.currentTimeMillis(); + /** Transaction start time in nanoseconds to measure duration. */ + protected long startTimeNanos; + /** Node ID. */ @GridToStringInclude protected UUID nodeId; @@ -343,6 +346,9 @@ protected IgniteTxAdapter( cctx.gridEvents().isRecordable(EVT_CACHE_OBJECT_REMOVED); taskName = needTaskName ? cctx.kernalContext().task().resolveTaskName(taskNameHash) : null; + + if (cctx.kernalContext().performanceStatistics().enabled()) + startTimeNanos = System.nanoTime(); } /** @@ -399,6 +405,9 @@ protected IgniteTxAdapter( cctx.gridEvents().isRecordable(EVT_CACHE_OBJECT_REMOVED); taskName = needTaskName ? cctx.kernalContext().task().resolveTaskName(taskNameHash) : null; + + if (cctx.kernalContext().performanceStatistics().enabled()) + startTimeNanos = System.nanoTime(); } /** @@ -724,6 +733,11 @@ public boolean remote() { return startTime; } + /** {@inheritDoc} */ + @Override public long startTimeNanos() { + return startTimeNanos; + } + /** * @return Flag indicating whether transaction needs return value. */ @@ -2027,6 +2041,9 @@ private static class TxShadow implements IgniteInternalTx { /** Start time. */ private final long startTime; + /** Start time in nanoseconds. */ + private final long startTimeNanos; + /** Transaction isolation. */ private final TransactionIsolation isolation; @@ -2061,13 +2078,14 @@ private static class TxShadow implements IgniteInternalTx { * @param state Transaction state. * @param rollbackOnly Rollback-only flag. */ - TxShadow(IgniteUuid xid, UUID nodeId, long threadId, long startTime, TransactionIsolation isolation, - TransactionConcurrency concurrency, boolean invalidate, boolean implicit, long timeout, - TransactionState state, boolean rollbackOnly) { + TxShadow(IgniteUuid xid, UUID nodeId, long threadId, long startTime, long startTimeNanos, + TransactionIsolation isolation, TransactionConcurrency concurrency, boolean invalidate, boolean implicit, + long timeout, TransactionState state, boolean rollbackOnly) { this.xid = xid; this.nodeId = nodeId; this.threadId = threadId; this.startTime = startTime; + this.startTimeNanos = startTimeNanos; this.isolation = isolation; this.concurrency = concurrency; this.invalidate = invalidate; @@ -2112,6 +2130,11 @@ private static class TxShadow implements IgniteInternalTx { return startTime; } + /** {@inheritDoc} */ + @Override public long startTimeNanos() { + return startTimeNanos; + } + /** {@inheritDoc} */ @Override public TransactionIsolation isolation() { return isolation; diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/transactions/IgniteTxManager.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/transactions/IgniteTxManager.java index f57d2762e88e6..34834ba0f3a5e 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/transactions/IgniteTxManager.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/transactions/IgniteTxManager.java @@ -34,7 +34,6 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; - import org.apache.ignite.IgniteCheckedException; import org.apache.ignite.IgniteClientDisconnectedException; import org.apache.ignite.IgniteException; @@ -1676,6 +1675,8 @@ public void commitTx(IgniteInternalTx tx) throws IgniteCheckedException { if (!tx.system()) cctx.txMetrics().onTxCommit(); + writeStatistics(tx, true); + tx.txState().onTxEnd(cctx, tx, true); } @@ -1745,6 +1746,8 @@ public void rollbackTx(IgniteInternalTx tx, boolean clearThreadMap, boolean skip if (!tx.system()) cctx.txMetrics().onTxRollback(); + writeStatistics(tx, false); + tx.txState().onTxEnd(cctx, tx, false); } @@ -1798,6 +1801,8 @@ public void fastFinishTx(GridNearTxLocal tx, boolean commit, boolean clearThread cctx.txMetrics().onTxRollback(); } + writeStatistics(tx, commit); + tx.txState().onTxEnd(cctx, tx, commit); } } @@ -3215,6 +3220,23 @@ private void collectInfo() { } } + /** + * Writes transaction performance statistics. + * + * @param tx Transaction. + * @param commited {@code True} if transaction commited. + */ + private void writeStatistics(IgniteInternalTx tx, boolean commited) { + if (!cctx.kernalContext().performanceStatistics().enabled() || tx.startTimeNanos() == 0) + return; + + cctx.kernalContext().performanceStatistics().transaction( + tx.txState().cacheIds(), + tx.startTime(), + System.nanoTime() - tx.startTimeNanos(), + commited); + } + /** * Transactions recovery initialization runnable. */ diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/job/GridJobProcessor.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/job/GridJobProcessor.java index 2833f38fa8fbf..e2902865baea1 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/job/GridJobProcessor.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/job/GridJobProcessor.java @@ -2039,6 +2039,14 @@ private class JobEventListener implements GridJobEventListener { } } } + + if (ctx.performanceStatistics().enabled()) { + ctx.performanceStatistics().job(ses.getId(), + worker.getQueuedTime(), + worker.getStartTime(), + worker.getExecuteTime(), + worker.isTimedOut()); + } } } diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/performancestatistics/FilePerformanceStatisticsReader.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/performancestatistics/FilePerformanceStatisticsReader.java new file mode 100644 index 0000000000000..499ff3573be66 --- /dev/null +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/performancestatistics/FilePerformanceStatisticsReader.java @@ -0,0 +1,529 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.ignite.internal.processors.performancestatistics; + +import java.io.File; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.file.FileVisitOption; +import java.nio.file.FileVisitResult; +import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.Collections; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import org.apache.ignite.IgniteException; +import org.apache.ignite.internal.processors.cache.persistence.file.FileIO; +import org.apache.ignite.internal.processors.cache.persistence.file.RandomAccessFileIOFactory; +import org.apache.ignite.internal.processors.cache.query.GridCacheQueryType; +import org.apache.ignite.internal.util.GridIntList; +import org.apache.ignite.internal.util.typedef.internal.A; +import org.apache.ignite.internal.util.typedef.internal.U; +import org.apache.ignite.lang.IgniteUuid; +import org.jetbrains.annotations.Nullable; + +import static java.nio.ByteBuffer.allocateDirect; +import static java.nio.ByteOrder.nativeOrder; +import static java.nio.file.Files.walkFileTree; +import static org.apache.ignite.internal.processors.performancestatistics.OperationType.CACHE_START; +import static org.apache.ignite.internal.processors.performancestatistics.OperationType.JOB; +import static org.apache.ignite.internal.processors.performancestatistics.OperationType.QUERY; +import static org.apache.ignite.internal.processors.performancestatistics.OperationType.QUERY_READS; +import static org.apache.ignite.internal.processors.performancestatistics.OperationType.TASK; +import static org.apache.ignite.internal.processors.performancestatistics.OperationType.TX_COMMIT; +import static org.apache.ignite.internal.processors.performancestatistics.OperationType.cacheOperation; +import static org.apache.ignite.internal.processors.performancestatistics.OperationType.cacheRecordSize; +import static org.apache.ignite.internal.processors.performancestatistics.OperationType.cacheStartRecordSize; +import static org.apache.ignite.internal.processors.performancestatistics.OperationType.jobRecordSize; +import static org.apache.ignite.internal.processors.performancestatistics.OperationType.queryReadsRecordSize; +import static org.apache.ignite.internal.processors.performancestatistics.OperationType.queryRecordSize; +import static org.apache.ignite.internal.processors.performancestatistics.OperationType.taskRecordSize; +import static org.apache.ignite.internal.processors.performancestatistics.OperationType.transactionOperation; +import static org.apache.ignite.internal.processors.performancestatistics.OperationType.transactionRecordSize; + +/** + * Walker over the performance statistics file. + * + * @see FilePerformanceStatisticsWriter + */ +public class FilePerformanceStatisticsReader { + /** Default file read buffer size. */ + private static final int DFLT_READ_BUFFER_SIZE = (int)(8 * U.MB); + + /** Uuid as string pattern. */ + private static final String UUID_STR_PATTERN = + "[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}"; + + /** File name pattern. */ + private static final Pattern FILE_PATTERN = Pattern.compile("^node-(" + UUID_STR_PATTERN + ")(-\\d+)?.prf$"); + + /** No-op handler. */ + private static final PerformanceStatisticsHandler[] NOOP_HANDLER = {}; + + /** IO factory. */ + private final RandomAccessFileIOFactory ioFactory = new RandomAccessFileIOFactory(); + + /** Current file I/O. */ + private FileIO fileIo; + + /** Buffer. */ + private final ByteBuffer buf; + + /** Handlers to process deserialized operations. */ + private final PerformanceStatisticsHandler[] handlers; + + /** Current handlers. */ + private PerformanceStatisticsHandler[] curHnd; + + /** Cached strings by hashcodes. */ + private final Map knownStrs = new HashMap<>(); + + /** Forward read mode. */ + private ForwardRead forwardRead; + + /** @param handlers Handlers to process deserialized operations. */ + public FilePerformanceStatisticsReader(PerformanceStatisticsHandler... handlers) { + this(DFLT_READ_BUFFER_SIZE, handlers); + } + + /** + * @param bufSize Buffer size. + * @param handlers Handlers to process deserialized operations. + */ + FilePerformanceStatisticsReader(int bufSize, PerformanceStatisticsHandler... handlers) { + A.notEmpty(handlers, "At least one handler expected."); + + buf = allocateDirect(bufSize).order(nativeOrder()); + this.handlers = handlers; + curHnd = handlers; + } + + /** + * Walks over performance statistics files. + * + * @param filesOrDirs Files or directories. + * @throws IOException If read failed. + */ + public void read(List filesOrDirs) throws IOException { + List files = resolveFiles(filesOrDirs); + + if (files.isEmpty()) + return; + + for (File file : files) { + buf.clear(); + + UUID nodeId = nodeId(file); + + try (FileIO io = ioFactory.create(file)) { + fileIo = io; + + while (true) { + if (io.read(buf) <= 0) { + if (forwardRead == null) + break; + + io.position(forwardRead.nextRecPos); + + buf.clear(); + + curHnd = handlers; + + forwardRead = null; + + continue; + } + + buf.flip(); + + buf.mark(); + + while (deserialize(buf, nodeId)) { + if (forwardRead != null && forwardRead.found) { + if (forwardRead.resetBuf) { + buf.limit(0); + + io.position(forwardRead.curRecPos); + } + else + buf.position(forwardRead.bufPos); + + curHnd = handlers; + + forwardRead = null; + } + + buf.mark(); + } + + buf.reset(); + + if (forwardRead != null) + forwardRead.resetBuf = true; + + buf.compact(); + } + } + + knownStrs.clear(); + forwardRead = null; + } + } + + /** + * @param buf Buffer. + * @param nodeId Node id. + * @return {@code True} if operation deserialized. {@code False} if not enough bytes. + */ + private boolean deserialize(ByteBuffer buf, UUID nodeId) throws IOException { + if (buf.remaining() < 1) + return false; + + byte opTypeByte = buf.get(); + + OperationType opType = OperationType.of(opTypeByte); + + if (cacheOperation(opType)) { + if (buf.remaining() < cacheRecordSize()) + return false; + + int cacheId = buf.getInt(); + long startTime = buf.getLong(); + long duration = buf.getLong(); + + for (PerformanceStatisticsHandler handler : curHnd) + handler.cacheOperation(nodeId, opType, cacheId, startTime, duration); + + return true; + } + else if (transactionOperation(opType)) { + if (buf.remaining() < 4) + return false; + + int cacheIdsCnt = buf.getInt(); + + if (buf.remaining() < transactionRecordSize(cacheIdsCnt) - 4) + return false; + + GridIntList cacheIds = new GridIntList(cacheIdsCnt); + + for (int i = 0; i < cacheIdsCnt; i++) + cacheIds.add(buf.getInt()); + + long startTime = buf.getLong(); + long duration = buf.getLong(); + + for (PerformanceStatisticsHandler handler : curHnd) + handler.transaction(nodeId, cacheIds, startTime, duration, opType == TX_COMMIT); + + return true; + } + else if (opType == QUERY) { + if (buf.remaining() < 1) + return false; + + boolean cached = buf.get() != 0; + + String text; + int hash = 0; + + if (cached) { + if (buf.remaining() < 4) + return false; + + hash = buf.getInt(); + + text = knownStrs.get(hash); + + if (buf.remaining() < queryRecordSize(0, true) - 1 - 4) + return false; + } + else { + if (buf.remaining() < 4) + return false; + + int textLen = buf.getInt(); + + if (buf.remaining() < queryRecordSize(textLen, false) - 1 - 4) + return false; + + text = readString(buf, textLen); + } + + GridCacheQueryType queryType = GridCacheQueryType.fromOrdinal(buf.get()); + long id = buf.getLong(); + long startTime = buf.getLong(); + long duration = buf.getLong(); + boolean success = buf.get() != 0; + + if (text == null) + forwardRead(hash); + + for (PerformanceStatisticsHandler handler : curHnd) + handler.query(nodeId, queryType, text, id, startTime, duration, success); + + return true; + } + else if (opType == QUERY_READS) { + if (buf.remaining() < queryReadsRecordSize()) + return false; + + GridCacheQueryType queryType = GridCacheQueryType.fromOrdinal(buf.get()); + UUID uuid = readUuid(buf); + long id = buf.getLong(); + long logicalReads = buf.getLong(); + long physicalReads = buf.getLong(); + + for (PerformanceStatisticsHandler handler : curHnd) + handler.queryReads(nodeId, queryType, uuid, id, logicalReads, physicalReads); + + return true; + } + else if (opType == TASK) { + if (buf.remaining() < 1) + return false; + + boolean cached = buf.get() != 0; + + String taskName; + int hash = 0; + + if (cached) { + if (buf.remaining() < 4) + return false; + + hash = buf.getInt(); + + taskName = knownStrs.get(hash); + + if (buf.remaining() < taskRecordSize(0, true) - 1 - 4) + return false; + } + else { + if (buf.remaining() < 4) + return false; + + int nameLen = buf.getInt(); + + if (buf.remaining() < taskRecordSize(nameLen, false) - 1 - 4) + return false; + + taskName = readString(buf, nameLen); + } + + IgniteUuid sesId = readIgniteUuid(buf); + long startTime = buf.getLong(); + long duration = buf.getLong(); + int affPartId = buf.getInt(); + + if (taskName == null) + forwardRead(hash); + + for (PerformanceStatisticsHandler handler : curHnd) + handler.task(nodeId, sesId, taskName, startTime, duration, affPartId); + + return true; + } + else if (opType == JOB) { + if (buf.remaining() < jobRecordSize()) + return false; + + IgniteUuid sesId = readIgniteUuid(buf); + long queuedTime = buf.getLong(); + long startTime = buf.getLong(); + long duration = buf.getLong(); + boolean timedOut = buf.get() != 0; + + for (PerformanceStatisticsHandler handler : curHnd) + handler.job(nodeId, sesId, queuedTime, startTime, duration, timedOut); + + return true; + } + else if (opType == CACHE_START) { + if (buf.remaining() < 1) + return false; + + boolean cached = buf.get() != 0; + + String cacheName; + int hash = 0; + + if (cached) { + if (buf.remaining() < 4) + return false; + + hash = buf.getInt(); + + cacheName = knownStrs.get(hash); + + if (buf.remaining() < cacheStartRecordSize(0, true) - 1 - 4) + return false; + } + else { + if (buf.remaining() < 4) + return false; + + int nameLen = buf.getInt(); + + if (buf.remaining() < cacheStartRecordSize(nameLen, false) - 1 - 4) + return false; + + cacheName = readString(buf, nameLen); + } + + int cacheId = buf.getInt(); + + for (PerformanceStatisticsHandler handler : curHnd) + handler.cacheStart(nodeId, cacheId, cacheName); + + return true; + } + else + throw new IgniteException("Unknown operation type id [typeId=" + opTypeByte + ']'); + } + + /** Turns on forward read mode. */ + private void forwardRead(int hash) throws IOException { + if (forwardRead != null) + return; + + int pos = buf.position(); + + long nextRecPos = fileIo.position() - buf.remaining(); + + buf.reset(); + + int bufPos = buf.position(); + + long curRecPos = fileIo.position() - buf.remaining(); + + buf.position(pos); + + curHnd = NOOP_HANDLER; + + forwardRead = new ForwardRead(hash, curRecPos, nextRecPos, bufPos); + } + + /** Resolves performance statistics files. */ + static List resolveFiles(List filesOrDirs) throws IOException { + if (filesOrDirs == null || filesOrDirs.isEmpty()) + return Collections.emptyList(); + + List files = new LinkedList<>(); + + for (File file : filesOrDirs) { + if (file.isDirectory()) { + walkFileTree(file.toPath(), EnumSet.noneOf(FileVisitOption.class), 1, + new SimpleFileVisitor() { + @Override public FileVisitResult visitFile(Path path, BasicFileAttributes attrs) { + if (nodeId(path.toFile()) != null) + files.add(path.toFile()); + + return FileVisitResult.CONTINUE; + } + }); + + continue; + } + + if (nodeId(file) != null) + files.add(file); + } + + return files; + } + + /** @return UUID node of file. {@code Null} if this is not a statistics file. */ + @Nullable private static UUID nodeId(File file) { + Matcher matcher = FILE_PATTERN.matcher(file.getName()); + + if (matcher.matches()) + return UUID.fromString(matcher.group(1)); + + return null; + } + + /** Reads string from byte buffer. */ + private String readString(ByteBuffer buf, int size) { + byte[] bytes = new byte[size]; + + buf.get(bytes); + + String str = new String(bytes); + + knownStrs.putIfAbsent(str.hashCode(), str); + + if (forwardRead != null && forwardRead.hash == str.hashCode()) + forwardRead.found = true; + + return str; + } + + /** Reads {@link UUID} from buffer. */ + private static UUID readUuid(ByteBuffer buf) { + return new UUID(buf.getLong(), buf.getLong()); + } + + /** Reads {@link IgniteUuid} from buffer. */ + private static IgniteUuid readIgniteUuid(ByteBuffer buf) { + UUID globalId = new UUID(buf.getLong(), buf.getLong()); + + return new IgniteUuid(globalId, buf.getLong()); + } + + /** Forward read mode info. */ + private static class ForwardRead { + /** Hashcode. */ + final int hash; + + /** Absolute current record position. */ + final long curRecPos; + + /** Absolute next record position. */ + final long nextRecPos; + + /** Current record buffer position. */ + final int bufPos; + + /** String found flag. */ + boolean found; + + /** {@code True} if the data in the buffer was overwritten during the search. */ + boolean resetBuf; + + /** + * @param hash Hashcode. + * @param curRecPos Absolute current record position. + * @param nextRecPos Absolute next record position. + * @param bufPos Buffer position. + */ + private ForwardRead(int hash, long curRecPos, long nextRecPos, int bufPos) { + this.hash = hash; + this.curRecPos = curRecPos; + this.nextRecPos = nextRecPos; + this.bufPos = bufPos; + } + } +} diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/performancestatistics/FilePerformanceStatisticsWriter.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/performancestatistics/FilePerformanceStatisticsWriter.java new file mode 100644 index 0000000000000..d3e04722b884f --- /dev/null +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/performancestatistics/FilePerformanceStatisticsWriter.java @@ -0,0 +1,502 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.ignite.internal.processors.performancestatistics; + +import java.io.File; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.ClosedByInterruptException; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Consumer; +import org.apache.ignite.IgniteCheckedException; +import org.apache.ignite.IgniteLogger; +import org.apache.ignite.IgniteSystemProperties; +import org.apache.ignite.internal.GridKernalContext; +import org.apache.ignite.internal.IgniteInterruptedCheckedException; +import org.apache.ignite.internal.processors.cache.persistence.file.FileIO; +import org.apache.ignite.internal.processors.cache.persistence.file.FileIOFactory; +import org.apache.ignite.internal.processors.cache.persistence.file.RandomAccessFileIOFactory; +import org.apache.ignite.internal.processors.cache.persistence.wal.SegmentedRingByteBuffer; +import org.apache.ignite.internal.processors.cache.query.GridCacheQueryType; +import org.apache.ignite.internal.util.GridConcurrentHashSet; +import org.apache.ignite.internal.util.GridIntIterator; +import org.apache.ignite.internal.util.GridIntList; +import org.apache.ignite.internal.util.typedef.internal.U; +import org.apache.ignite.internal.util.worker.GridWorker; +import org.apache.ignite.lang.IgniteUuid; +import org.apache.ignite.thread.IgniteThread; + +import static org.apache.ignite.IgniteSystemProperties.IGNITE_PERF_STAT_BUFFER_SIZE; +import static org.apache.ignite.IgniteSystemProperties.IGNITE_PERF_STAT_CACHED_STRINGS_THRESHOLD; +import static org.apache.ignite.IgniteSystemProperties.IGNITE_PERF_STAT_FILE_MAX_SIZE; +import static org.apache.ignite.IgniteSystemProperties.IGNITE_PERF_STAT_FLUSH_SIZE; +import static org.apache.ignite.internal.processors.performancestatistics.OperationType.CACHE_START; +import static org.apache.ignite.internal.processors.performancestatistics.OperationType.JOB; +import static org.apache.ignite.internal.processors.performancestatistics.OperationType.QUERY; +import static org.apache.ignite.internal.processors.performancestatistics.OperationType.QUERY_READS; +import static org.apache.ignite.internal.processors.performancestatistics.OperationType.TASK; +import static org.apache.ignite.internal.processors.performancestatistics.OperationType.TX_COMMIT; +import static org.apache.ignite.internal.processors.performancestatistics.OperationType.TX_ROLLBACK; +import static org.apache.ignite.internal.processors.performancestatistics.OperationType.cacheRecordSize; +import static org.apache.ignite.internal.processors.performancestatistics.OperationType.cacheStartRecordSize; +import static org.apache.ignite.internal.processors.performancestatistics.OperationType.jobRecordSize; +import static org.apache.ignite.internal.processors.performancestatistics.OperationType.queryReadsRecordSize; +import static org.apache.ignite.internal.processors.performancestatistics.OperationType.queryRecordSize; +import static org.apache.ignite.internal.processors.performancestatistics.OperationType.taskRecordSize; +import static org.apache.ignite.internal.processors.performancestatistics.OperationType.transactionRecordSize; + +/** + * Performance statistics writer based on logging to a file. + *

+ * Each node collects statistics to a file placed under {@link #PERF_STAT_DIR}. + *

+ * To iterate over records use {@link FilePerformanceStatisticsReader}. + */ +public class FilePerformanceStatisticsWriter { + /** Directory to store performance statistics files. Placed under Ignite work directory. */ + public static final String PERF_STAT_DIR = "perf_stat"; + + /** Default maximum file size in bytes. Performance statistics will be stopped when the size exceeded. */ + public static final long DFLT_FILE_MAX_SIZE = 32 * U.GB; + + /** Default off heap buffer size in bytes. */ + public static final int DFLT_BUFFER_SIZE = (int)(32 * U.MB); + + /** Default minimal batch size to flush in bytes. */ + public static final int DFLT_FLUSH_SIZE = (int)(8 * U.MB); + + /** Default maximum cached strings threshold. String caching will stop on threshold excess. */ + public static final int DFLT_CACHED_STRINGS_THRESHOLD = 1024; + + /** File writer thread name. */ + static final String WRITER_THREAD_NAME = "performance-statistics-writer"; + + /** Minimal batch size to flush in bytes. */ + private final int flushSize = + IgniteSystemProperties.getInteger(IGNITE_PERF_STAT_FLUSH_SIZE, DFLT_FLUSH_SIZE); + + /** Maximum cached strings threshold. String caching will stop on threshold excess. */ + private final int cachedStrsThreshold = + IgniteSystemProperties.getInteger(IGNITE_PERF_STAT_CACHED_STRINGS_THRESHOLD, DFLT_CACHED_STRINGS_THRESHOLD); + + /** Factory to provide I/O interface. */ + private final FileIOFactory fileIoFactory = new RandomAccessFileIOFactory(); + + /** Performance statistics file I/O. */ + private final FileIO fileIo; + + /** Performance statistics file writer worker. */ + private final FileWriter fileWriter; + + /** File writer thread started flag. */ + private boolean started; + + /** File write buffer. */ + private final SegmentedRingByteBuffer ringByteBuf; + + /** Count of written to buffer bytes. */ + private final AtomicInteger writtenToBuf = new AtomicInteger(); + + /** {@code True} if the small buffer warning message logged. */ + private final AtomicBoolean smallBufLogged = new AtomicBoolean(); + + /** {@code True} if worker stopped due to maximum file size reached. */ + private final AtomicBoolean stopByMaxSize = new AtomicBoolean(); + + /** Logger. */ + private final IgniteLogger log; + + /** Hashcodes of cached strings. */ + private final Set knownStrs = new GridConcurrentHashSet<>(); + + /** Count of cached strings. */ + private volatile int knownStrsSz; + + /** @param ctx Kernal context. */ + public FilePerformanceStatisticsWriter(GridKernalContext ctx) throws IgniteCheckedException, IOException { + log = ctx.log(getClass()); + + File file = resolveStatisticsFile(ctx); + + fileIo = fileIoFactory.create(file); + + log.info("Performance statistics file created [file=" + file.getAbsolutePath() + ']'); + + long fileMaxSize = IgniteSystemProperties.getLong(IGNITE_PERF_STAT_FILE_MAX_SIZE, DFLT_FILE_MAX_SIZE); + int bufSize = IgniteSystemProperties.getInteger(IGNITE_PERF_STAT_BUFFER_SIZE, DFLT_BUFFER_SIZE); + + ringByteBuf = new SegmentedRingByteBuffer(bufSize, fileMaxSize, SegmentedRingByteBuffer.BufferMode.DIRECT); + + fileWriter = new FileWriter(ctx, log); + } + + /** Starts collecting performance statistics. */ + public synchronized void start() { + assert !started; + + new IgniteThread(fileWriter).start(); + + started = true; + } + + /** Stops collecting performance statistics. */ + public synchronized void stop() { + assert started; + + // Stop accepting new records. + ringByteBuf.close(); + + U.awaitForWorkersStop(Collections.singleton(fileWriter), true, log); + + // Make sure that all producers released their buffers to safe deallocate memory (in case of worker + // stopped abnormally). + ringByteBuf.poll(); + + ringByteBuf.free(); + + try { + fileIo.force(); + } + catch (IOException e) { + log.warning("Failed to fsync the performance statistics file.", e); + } + + U.closeQuiet(fileIo); + + knownStrs.clear(); + + started = false; + } + + /** + * @param cacheId Cache id. + * @param name Cache name. + */ + public void cacheStart(int cacheId, String name) { + boolean cached = cacheIfPossible(name); + + doWrite(CACHE_START, cacheStartRecordSize(cached ? 0 : name.getBytes().length, cached), buf -> { + writeString(buf, name, cached); + buf.putInt(cacheId); + }); + } + + /** + * @param type Operation type. + * @param cacheId Cache id. + * @param startTime Start time in milliseconds. + * @param duration Duration in nanoseconds. + */ + public void cacheOperation(OperationType type, int cacheId, long startTime, long duration) { + doWrite(type, cacheRecordSize(), buf -> { + buf.putInt(cacheId); + buf.putLong(startTime); + buf.putLong(duration); + }); + } + + /** + * @param cacheIds Cache IDs. + * @param startTime Start time in milliseconds. + * @param duration Duration in nanoseconds. + * @param commited {@code True} if commited. + */ + public void transaction(GridIntList cacheIds, long startTime, long duration, boolean commited) { + doWrite(commited ? TX_COMMIT : TX_ROLLBACK, transactionRecordSize(cacheIds.size()), buf -> { + buf.putInt(cacheIds.size()); + + GridIntIterator iter = cacheIds.iterator(); + + while (iter.hasNext()) + buf.putInt(iter.next()); + + buf.putLong(startTime); + buf.putLong(duration); + }); + } + + /** + * @param type Cache query type. + * @param text Query text in case of SQL query. Cache name in case of SCAN query. + * @param id Query id. + * @param startTime Start time in milliseconds. + * @param duration Duration in nanoseconds. + * @param success Success flag. + */ + public void query(GridCacheQueryType type, String text, long id, long startTime, long duration, boolean success) { + boolean cached = cacheIfPossible(text); + + doWrite(QUERY, queryRecordSize(cached ? 0 : text.getBytes().length, cached), buf -> { + writeString(buf, text, cached); + buf.put((byte)type.ordinal()); + buf.putLong(id); + buf.putLong(startTime); + buf.putLong(duration); + buf.put(success ? (byte)1 : 0); + }); + } + + /** + * @param type Cache query type. + * @param queryNodeId Originating node id. + * @param id Query id. + * @param logicalReads Number of logical reads. + * @param physicalReads Number of physical reads. + */ + public void queryReads(GridCacheQueryType type, UUID queryNodeId, long id, long logicalReads, long physicalReads) { + doWrite(QUERY_READS, queryReadsRecordSize(), buf -> { + buf.put((byte)type.ordinal()); + writeUuid(buf, queryNodeId); + buf.putLong(id); + buf.putLong(logicalReads); + buf.putLong(physicalReads); + }); + } + + /** + * @param sesId Session id. + * @param taskName Task name. + * @param startTime Start time in milliseconds. + * @param duration Duration. + * @param affPartId Affinity partition id. + */ + public void task(IgniteUuid sesId, String taskName, long startTime, long duration, int affPartId) { + boolean cached = cacheIfPossible(taskName); + + doWrite(TASK, taskRecordSize(cached ? 0 : taskName.getBytes().length, cached), buf -> { + writeString(buf, taskName, cached); + writeIgniteUuid(buf, sesId); + buf.putLong(startTime); + buf.putLong(duration); + buf.putInt(affPartId); + }); + } + + /** + * @param sesId Session id. + * @param queuedTime Time job spent on waiting queue. + * @param startTime Start time in milliseconds. + * @param duration Job execution time. + * @param timedOut {@code True} if job is timed out. + */ + public void job(IgniteUuid sesId, long queuedTime, long startTime, long duration, boolean timedOut) { + doWrite(JOB, jobRecordSize(), buf -> { + writeIgniteUuid(buf, sesId); + buf.putLong(queuedTime); + buf.putLong(startTime); + buf.putLong(duration); + buf.put(timedOut ? (byte)1 : 0); + }); + } + + /** + * @param op Operation type. + * @param recSize Record size. + * @param writer Record writer. + */ + private void doWrite(OperationType op, int recSize, Consumer writer) { + int size = recSize + /*type*/ 1; + + SegmentedRingByteBuffer.WriteSegment seg = ringByteBuf.offer(size); + + if (seg == null) { + if (smallBufLogged.compareAndSet(false, true)) { + log.warning("The performance statistics in-memory buffer size is too small. Some operations " + + "will not be logged."); + } + + return; + } + + // Ring buffer closed (writer stopping) or maximum size reached. + if (seg.buffer() == null) { + seg.release(); + + if (!fileWriter.isCancelled() && stopByMaxSize.compareAndSet(false, true)) + log.warning("The performance statistics file maximum size is reached."); + + return; + } + + ByteBuffer buf = seg.buffer(); + + buf.put(op.id()); + + writer.accept(buf); + + seg.release(); + + int bufCnt = writtenToBuf.get() / flushSize; + + if (writtenToBuf.addAndGet(size) / flushSize > bufCnt) { + // Wake up worker to start writing data to the file. + synchronized (fileWriter) { + fileWriter.notify(); + } + } + } + + /** @return Performance statistics file. */ + private static File resolveStatisticsFile(GridKernalContext ctx) throws IgniteCheckedException { + String igniteWorkDir = U.workDirectory(ctx.config().getWorkDirectory(), ctx.config().getIgniteHome()); + + File fileDir = U.resolveWorkDirectory(igniteWorkDir, PERF_STAT_DIR, false); + + File file = new File(fileDir, "node-" + ctx.localNodeId() + ".prf");; + + int idx = 0; + + while (file.exists()) { + idx++; + + file = new File(fileDir, "node-" + ctx.localNodeId() + '-' + idx + ".prf"); + } + + return file; + } + + /** Writes {@link UUID} to buffer. */ + private static void writeUuid(ByteBuffer buf, UUID uuid) { + buf.putLong(uuid.getMostSignificantBits()); + buf.putLong(uuid.getLeastSignificantBits()); + } + + /** Writes {@link IgniteUuid} to buffer. */ + static void writeIgniteUuid(ByteBuffer buf, IgniteUuid uuid) { + buf.putLong(uuid.globalId().getMostSignificantBits()); + buf.putLong(uuid.globalId().getLeastSignificantBits()); + buf.putLong(uuid.localId()); + } + + /** + * @param buf Buffer to write to. + * @param str String to write. + * @param cached {@code True} if string cached. + */ + static void writeString(ByteBuffer buf, String str, boolean cached) { + buf.put(cached ? (byte)1 : 0); + + if (cached) + buf.putInt(str.hashCode()); + else { + byte[] bytes = str.getBytes(); + + buf.putInt(bytes.length); + buf.put(bytes); + } + } + + /** @return {@code True} if string was cached and can be written as hashcode. */ + private boolean cacheIfPossible(String str) { + if (knownStrsSz >= cachedStrsThreshold) + return false; + + int hash = str.hashCode(); + + // We can cache slightly more strings then threshold value. + // Don't implement solution with synchronization here, because our primary goal is avoid any contention. + if (knownStrs.contains(hash) || !knownStrs.add(hash)) + return true; + + knownStrsSz = knownStrs.size(); + + return false; + } + + /** Worker to write to performance statistics file. */ + private class FileWriter extends GridWorker { + /** + * @param ctx Kernal context. + * @param log Logger. + */ + FileWriter(GridKernalContext ctx, IgniteLogger log) { + super(ctx.igniteInstanceName(), WRITER_THREAD_NAME, log, ctx.workersRegistry()); + } + + /** {@inheritDoc} */ + @Override protected void body() throws InterruptedException, IgniteInterruptedCheckedException { + try { + long writtenToFile = 0; + + while (!isCancelled()) { + blockingSectionBegin(); + + try { + synchronized (this) { + if (writtenToFile / flushSize == writtenToBuf.get() / flushSize) + wait(); + } + } + finally { + blockingSectionEnd(); + } + + writtenToFile += flush(); + } + + flush(); + } + catch (InterruptedException e) { + try { + flush(); + } + catch (IOException ignored) { + // No-op. + } + } + catch (ClosedByInterruptException ignored) { + // No-op. + } + catch (IOException e) { + log.error("Unable to write to the performance statistics file.", e); + } + } + + /** + * Flushes to disk available bytes from the ring buffer. + * + * @return Count of written bytes. + */ + private int flush() throws IOException { + List segs = ringByteBuf.poll(); + + if (segs == null) + return 0; + + int written = 0; + + for (SegmentedRingByteBuffer.ReadSegment seg : segs) { + updateHeartbeat(); + + try { + written += fileIo.writeFully(seg.buffer()); + } + finally { + seg.release(); + } + } + + return written; + } + } +} diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/performancestatistics/OperationType.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/performancestatistics/OperationType.java new file mode 100644 index 0000000000000..d287f45cfd896 --- /dev/null +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/performancestatistics/OperationType.java @@ -0,0 +1,185 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.ignite.internal.processors.performancestatistics; + +import java.util.Collections; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.Map; +import org.jetbrains.annotations.Nullable; + +/** + * Performance statistics operation type. + */ +public enum OperationType { + /** Cache get. */ + CACHE_GET(0), + + /** Cache put. */ + CACHE_PUT(1), + + /** Cache remove. */ + CACHE_REMOVE(2), + + /** Cache get and put. */ + CACHE_GET_AND_PUT(3), + + /** Cache get and remove. */ + CACHE_GET_AND_REMOVE(4), + + /** Cache invoke. */ + CACHE_INVOKE(5), + + /** Cache lock. */ + CACHE_LOCK(6), + + /** Cache get all. */ + CACHE_GET_ALL(7), + + /** Cache put all. */ + CACHE_PUT_ALL(8), + + /** Cache remove all. */ + CACHE_REMOVE_ALL(9), + + /** Cache invoke all. */ + CACHE_INVOKE_ALL(10), + + /** Transaction commit. */ + TX_COMMIT(11), + + /** Transaction rollback. */ + TX_ROLLBACK(12), + + /** Query. */ + QUERY(13), + + /** Query reads. */ + QUERY_READS(14), + + /** Task. */ + TASK(15), + + /** Job. */ + JOB(16), + + /** Cache start. */ + CACHE_START(17); + + /** Cache operations. */ + public static final EnumSet CACHE_OPS = EnumSet.of(CACHE_GET, CACHE_PUT, CACHE_REMOVE, + CACHE_GET_AND_PUT, CACHE_GET_AND_REMOVE, CACHE_INVOKE, CACHE_LOCK, CACHE_GET_ALL, CACHE_PUT_ALL, + CACHE_REMOVE_ALL, CACHE_INVOKE_ALL); + + /** Transaction operations. */ + public static final EnumSet TX_OPS = EnumSet.of(TX_COMMIT, TX_ROLLBACK); + + /** Value by identifier. */ + private static final Map VALS; + + /** Unique operation identifier. */ + private final byte id; + + /** Static initializer. */ + static { + Map vals = new HashMap<>(); + + for (OperationType op : values()) { + OperationType old = vals.put(op.id(), op); + + assert old == null : "Duplicate operation ID found [op=" + op + ']'; + } + + VALS = Collections.unmodifiableMap(vals); + } + + /** @param id Unique operation identifier. */ + OperationType(int id) { + this.id = (byte)id; + } + + /** @return Unique operation identifier. */ + public byte id() { + return id; + } + + /** @return Operation type of given identifier. */ + @Nullable public static OperationType of(byte id) { + return VALS.get(id); + } + + /** @return {@code True} if cache operation. */ + public static boolean cacheOperation(OperationType op) { + return CACHE_OPS.contains(op); + } + + /** @return {@code True} if transaction operation. */ + public static boolean transactionOperation(OperationType op) { + return TX_OPS.contains(op); + } + + /** + * @param nameLen Cache name length. + * @param cached {@code True} if cache name cached. + * @return Cache start record size. + */ + public static int cacheStartRecordSize(int nameLen, boolean cached) { + return 1 + 4 + (cached ? 4 : 4 + nameLen); + } + + /** @return Cache record size. */ + public static int cacheRecordSize() { + return 4 + 8 + 8; + } + + /** + * @param cachesIdsCnt Cache identifiers size. + * @return Transaction record size. + */ + public static int transactionRecordSize(int cachesIdsCnt) { + return 4 + cachesIdsCnt * 4 + 8 + 8; + } + + /** + * @param textLen Query text length. + * @param cached {@code True} if query text cached. + * @return Query record size. + */ + public static int queryRecordSize(int textLen, boolean cached) { + return 1 + (cached ? 4 : 4 + textLen) + 1 + 8 + 8 + 8 + 1; + } + + /** @return Query reads record size. */ + public static int queryReadsRecordSize() { + return 1 + 16 + 8 + 8 + 8; + } + + /** + * @param nameLen Task name length. + * @param cached {@code True} if task name cached. + * @return Task record size. + */ + public static int taskRecordSize(int nameLen, boolean cached) { + return 1 + (cached ? 4 : 4 + nameLen) + 24 + 8 + 8 + 4; + } + + /** @return Job record size. */ + public static int jobRecordSize() { + return 24 + 8 + 8 + 8 + 1; + } +} diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/performancestatistics/PerformanceStatisticsHandler.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/performancestatistics/PerformanceStatisticsHandler.java new file mode 100644 index 0000000000000..3962db48f993d --- /dev/null +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/performancestatistics/PerformanceStatisticsHandler.java @@ -0,0 +1,97 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.ignite.internal.processors.performancestatistics; + +import java.util.UUID; +import org.apache.ignite.internal.processors.cache.query.GridCacheQueryType; +import org.apache.ignite.internal.util.GridIntList; +import org.apache.ignite.lang.IgniteUuid; + +/** + * The interface represents performance statistics operations collection for purposes of troubleshooting and + * performance analysis. + */ +public interface PerformanceStatisticsHandler { + /** + * @param nodeId Node id. + * @param cacheId Cache id. + * @param name Cache name. + */ + void cacheStart(UUID nodeId, int cacheId, String name); + + /** + * @param nodeId Node id. + * @param type Operation type. + * @param cacheId Cache id. + * @param startTime Start time in milliseconds. + * @param duration Duration in nanoseconds. + */ + void cacheOperation(UUID nodeId, OperationType type, int cacheId, long startTime, long duration); + + /** + * @param nodeId Node id. + * @param cacheIds Cache IDs. + * @param startTime Start time in milliseconds. + * @param duration Duration in nanoseconds. + * @param commited {@code True} if commited. + */ + void transaction(UUID nodeId, GridIntList cacheIds, long startTime, long duration, boolean commited); + + /** + * @param nodeId Node id. + * @param type Cache query type. + * @param text Query text in case of SQL query. Cache name in case of SCAN query. + * @param id Query id. + * @param startTime Start time in milliseconds. + * @param duration Duration in nanoseconds. + * @param success Success flag. + */ + void query(UUID nodeId, GridCacheQueryType type, String text, long id, long startTime, long duration, + boolean success); + + /** + * @param nodeId Node id. + * @param type Cache query type. + * @param queryNodeId Originating node id. + * @param id Query id. + * @param logicalReads Number of logical reads. + * @param physicalReads Number of physical reads. + */ + void queryReads(UUID nodeId, GridCacheQueryType type, UUID queryNodeId, long id, long logicalReads, + long physicalReads); + + /** + * @param nodeId Node id. + * @param sesId Session id. + * @param taskName Task name. + * @param startTime Start time in milliseconds. + * @param duration Duration. + * @param affPartId Affinity partition id. + */ + void task(UUID nodeId, IgniteUuid sesId, String taskName, long startTime, long duration, int affPartId); + + /** + * @param nodeId Node id. + * @param sesId Session id. + * @param queuedTime Time job spent on waiting queue. + * @param startTime Start time in milliseconds. + * @param duration Job execution time. + * @param timedOut {@code True} if job is timed out. + */ + void job(UUID nodeId, IgniteUuid sesId, long queuedTime, long startTime, long duration, boolean timedOut); +} diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/performancestatistics/PerformanceStatisticsMBeanImpl.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/performancestatistics/PerformanceStatisticsMBeanImpl.java new file mode 100644 index 0000000000000..8b6a236666703 --- /dev/null +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/performancestatistics/PerformanceStatisticsMBeanImpl.java @@ -0,0 +1,50 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.ignite.internal.processors.performancestatistics; + +import org.apache.ignite.IgniteCheckedException; +import org.apache.ignite.internal.GridKernalContext; +import org.apache.ignite.mxbean.PerformanceStatisticsMBean; + +/** + * {@link PerformanceStatisticsMBean} implementation. + */ +public class PerformanceStatisticsMBeanImpl implements PerformanceStatisticsMBean { + /** Kernal context. */ + private final GridKernalContext ctx; + + /** @param ctx Kernal context. */ + public PerformanceStatisticsMBeanImpl(GridKernalContext ctx) { + this.ctx = ctx; + } + + /** {@inheritDoc} */ + @Override public void start() throws IgniteCheckedException { + ctx.performanceStatistics().startCollectStatistics(); + } + + /** {@inheritDoc} */ + @Override public void stop() throws IgniteCheckedException { + ctx.performanceStatistics().stopCollectStatistics(); + } + + /** {@inheritDoc} */ + @Override public boolean started() { + return ctx.performanceStatistics().enabled(); + } +} diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/performancestatistics/PerformanceStatisticsProcessor.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/performancestatistics/PerformanceStatisticsProcessor.java new file mode 100644 index 0000000000000..f648114aef0d6 --- /dev/null +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/performancestatistics/PerformanceStatisticsProcessor.java @@ -0,0 +1,293 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.ignite.internal.processors.performancestatistics; + +import java.util.ArrayList; +import java.util.EventListener; +import java.util.UUID; +import java.util.function.Consumer; +import org.apache.ignite.IgniteCheckedException; +import org.apache.ignite.IgniteException; +import org.apache.ignite.internal.GridKernalContext; +import org.apache.ignite.internal.IgniteFeatures; +import org.apache.ignite.internal.NodeStoppingException; +import org.apache.ignite.internal.processors.GridProcessorAdapter; +import org.apache.ignite.internal.processors.cache.query.GridCacheQueryType; +import org.apache.ignite.internal.processors.metastorage.DistributedMetaStorage; +import org.apache.ignite.internal.processors.metastorage.DistributedMetastorageLifecycleListener; +import org.apache.ignite.internal.processors.metastorage.ReadableDistributedMetaStorage; +import org.apache.ignite.internal.util.GridIntList; +import org.apache.ignite.internal.util.typedef.internal.A; +import org.apache.ignite.internal.util.typedef.internal.U; +import org.apache.ignite.lang.IgniteFuture; +import org.apache.ignite.lang.IgniteUuid; +import org.jetbrains.annotations.Nullable; + +import static org.apache.ignite.internal.IgniteFeatures.allNodesSupports; +import static org.apache.ignite.internal.processors.metastorage.DistributedMetaStorage.IGNITE_INTERNAL_KEY_PREFIX; + +/** + * Performance statistics processor. + *

+ * Manages collecting performance statistics. + * + * @see FilePerformanceStatisticsWriter + * @see FilePerformanceStatisticsReader + */ +public class PerformanceStatisticsProcessor extends GridProcessorAdapter { + /** Prefix for performance statistics enabled key. */ + private static final String PERF_STAT_KEY = IGNITE_INTERNAL_KEY_PREFIX + "performanceStatistics.enabled"; + + /** Performance statistics writer. {@code Null} if collecting statistics disabled. */ + @Nullable private volatile FilePerformanceStatisticsWriter writer; + + /** Metastorage with the write access. */ + @Nullable private volatile DistributedMetaStorage metastorage; + + /** Synchronization mutex for start/stop collecting performance statistics operations. */ + private final Object mux = new Object(); + + /** Performance statistics state listeners. */ + private final ArrayList lsnrs = new ArrayList<>(); + + /** @param ctx Kernal context. */ + public PerformanceStatisticsProcessor(GridKernalContext ctx) { + super(ctx); + + ctx.internalSubscriptionProcessor().registerDistributedMetastorageListener( + new DistributedMetastorageLifecycleListener() { + @Override public void onReadyForRead(ReadableDistributedMetaStorage metastorage) { + metastorage.listen(PERF_STAT_KEY::equals, (key, oldVal, newVal) -> { + // Skip history on local join. + if (!ctx.discovery().localJoinFuture().isDone()) + return; + + onMetastorageUpdate((boolean)newVal); + }); + } + + @Override public void onReadyForWrite(DistributedMetaStorage metastorage) { + PerformanceStatisticsProcessor.this.metastorage = metastorage; + + try { + Boolean performanceStatsEnabled = metastorage.read(PERF_STAT_KEY); + + if (performanceStatsEnabled == null) + return; + + onMetastorageUpdate(performanceStatsEnabled); + } + catch (IgniteCheckedException e) { + throw new IgniteException(e); + } + } + }); + + registerStateListener(() -> { + if (U.isLocalNodeCoordinator(ctx.discovery())) + ctx.cache().cacheDescriptors().values().forEach(desc -> cacheStart(desc.cacheId(), desc.cacheName())); + }); + } + + /** Registers state listener. */ + public void registerStateListener(PerformanceStatisticsStateListener lsnr) { + lsnrs.add(lsnr); + } + + /** + * @param cacheId Cache id. + * @param name Cache name. + */ + public void cacheStart(int cacheId, String name) { + write(writer -> writer.cacheStart(cacheId, name)); + } + + /** + * @param type Operation type. + * @param cacheId Cache id. + * @param startTime Start time in milliseconds. + * @param duration Duration in nanoseconds. + */ + public void cacheOperation(OperationType type, int cacheId, long startTime, long duration) { + write(writer -> writer.cacheOperation(type, cacheId, startTime, duration)); + } + + /** + * @param cacheIds Cache IDs. + * @param startTime Start time in milliseconds. + * @param duration Duration in nanoseconds. + * @param commited {@code True} if commited. + */ + public void transaction(GridIntList cacheIds, long startTime, long duration, boolean commited) { + write(writer -> writer.transaction(cacheIds, startTime, duration, commited)); + } + + /** + * @param type Cache query type. + * @param text Query text in case of SQL query. Cache name in case of SCAN query. + * @param id Query id. + * @param startTime Start time in milliseconds. + * @param duration Duration in nanoseconds. + * @param success Success flag. + */ + public void query(GridCacheQueryType type, String text, long id, long startTime, long duration, boolean success) { + write(writer -> writer.query(type, text, id, startTime, duration, success)); + } + + /** + * @param type Cache query type. + * @param queryNodeId Originating node id. + * @param id Query id. + * @param logicalReads Number of logical reads. + * @param physicalReads Number of physical reads. + */ + public void queryReads(GridCacheQueryType type, UUID queryNodeId, long id, long logicalReads, long physicalReads) { + write(writer -> writer.queryReads(type, queryNodeId, id, logicalReads, physicalReads)); + } + + /** + * @param sesId Session id. + * @param taskName Task name. + * @param startTime Start time in milliseconds. + * @param duration Duration. + * @param affPartId Affinity partition id. + */ + public void task(IgniteUuid sesId, String taskName, long startTime, long duration, int affPartId) { + write(writer -> writer.task(sesId, taskName, startTime, duration, affPartId)); + } + + /** + * @param sesId Session id. + * @param queuedTime Time job spent on waiting queue. + * @param startTime Start time in milliseconds. + * @param duration Job execution time. + * @param timedOut {@code True} if job is timed out. + */ + public void job(IgniteUuid sesId, long queuedTime, long startTime, long duration, boolean timedOut) { + write(writer -> writer.job(sesId, queuedTime, startTime, duration, timedOut)); + } + + /** + * Starts collecting performance statistics. + * + * @throws IgniteCheckedException If starting failed. + */ + public void startCollectStatistics() throws IgniteCheckedException { + A.notNull(metastorage, "Metastorage not ready. Node not started?"); + + if (!allNodesSupports(ctx.discovery().allNodes(), IgniteFeatures.PERFORMANCE_STATISTICS)) + throw new IllegalStateException("Not all nodes in the cluster support collecting performance statistics."); + + if (ctx.isStopping()) + throw new NodeStoppingException("Operation has been cancelled (node is stopping)"); + + metastorage.write(PERF_STAT_KEY, true); + } + + /** + * Stops collecting performance statistics. + * + * @throws IgniteCheckedException If stopping failed. + */ + public void stopCollectStatistics() throws IgniteCheckedException { + A.notNull(metastorage, "Metastorage not ready. Node not started?"); + + if (ctx.isStopping()) + throw new NodeStoppingException("Operation has been cancelled (node is stopping)"); + + metastorage.write(PERF_STAT_KEY, false); + } + + /** @return {@code True} if collecting performance statistics is enabled. */ + public boolean enabled() { + return writer != null; + } + + /** {@inheritDoc} */ + @Override public void onKernalStop(boolean cancel) { + if (enabled()) + stopWriter(); + } + + /** {@inheritDoc} */ + @Override public void onDisconnected(IgniteFuture reconnectFut) { + if (enabled()) + stopWriter(); + } + + /** Starts or stops collecting statistics on metastorage update. */ + private void onMetastorageUpdate(boolean start) { + ctx.closure().runLocalSafe(() -> { + if (start) + startWriter(); + else + stopWriter(); + }); + } + + /** Starts performance statistics writer. */ + private void startWriter() { + try { + synchronized (mux) { + if (writer != null) + return; + + writer = new FilePerformanceStatisticsWriter(ctx); + + writer.start(); + } + + lsnrs.forEach(PerformanceStatisticsStateListener::onStarted); + + log.info("Performance statistics writer started."); + } + catch (Exception e) { + log.error("Failed to start performance statistics writer.", e); + } + } + + /** Stops performance statistics writer. */ + private void stopWriter() { + synchronized (mux) { + if (writer == null) + return; + + FilePerformanceStatisticsWriter writer = this.writer; + + this.writer = null; + + writer.stop(); + } + + log.info("Performance statistics writer stopped."); + } + + /** Writes statistics through passed writer. */ + private void write(Consumer c) { + FilePerformanceStatisticsWriter writer = this.writer; + + if (writer != null) + c.accept(writer); + } + + /** Performance statistics state listener. */ + public interface PerformanceStatisticsStateListener extends EventListener { + /** This method is called whenever the performance statistics collecting is started. */ + public void onStarted(); + } +} diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/query/GridRunningQueryInfo.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/query/GridRunningQueryInfo.java index 974d850859b61..e89065006aa22 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/query/GridRunningQueryInfo.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/query/GridRunningQueryInfo.java @@ -44,6 +44,9 @@ public class GridRunningQueryInfo { /** */ private final long startTime; + /** Query start time in nanoseconds to measure duration. */ + private final long startTimeNanos; + /** */ private final GridQueryCancel cancel; @@ -56,6 +59,9 @@ public class GridRunningQueryInfo { /** Span of the running query. */ private final Span span; + /** Request ID. */ + private long reqId; + /** * Constructor. * @@ -65,6 +71,7 @@ public class GridRunningQueryInfo { * @param qryType Query type. * @param schemaName Schema name. * @param startTime Query start time. + * @param startTimeNanos Query start time in nanoseconds. * @param cancel Query cancel. * @param loc Local query flag. */ @@ -75,6 +82,7 @@ public GridRunningQueryInfo( GridCacheQueryType qryType, String schemaName, long startTime, + long startTimeNanos, GridQueryCancel cancel, boolean loc ) { @@ -84,6 +92,7 @@ public GridRunningQueryInfo( this.qryType = qryType; this.schemaName = schemaName; this.startTime = startTime; + this.startTimeNanos = startTimeNanos; this.cancel = cancel; this.loc = loc; this.span = MTC.span(); @@ -131,6 +140,13 @@ public long startTime() { return startTime; } + /** + * @return Query start time in nanoseconds. + */ + public long startTimeNanos() { + return startTimeNanos; + } + /** * @param curTime Current time. * @param duration Duration of long query. @@ -182,4 +198,14 @@ public UUID nodeId() { public Span span() { return span; } + + /** @return Request ID. */ + public long requestId() { + return reqId; + } + + /** @param reqId Request ID. */ + public void requestId(long reqId) { + this.reqId = reqId; + } } diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/query/RunningQueryManager.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/query/RunningQueryManager.java index 8072378f79140..36159f0603402 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/query/RunningQueryManager.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/query/RunningQueryManager.java @@ -91,12 +91,20 @@ public class RunningQueryManager { */ private final AtomicLongMetric canceledQrsCnt; + /** Kernal context. */ + private final GridKernalContext ctx; + + /** Current running query info. */ + private final ThreadLocal currQryInfo = new ThreadLocal<>(); + /** * Constructor. * * @param ctx Context. */ public RunningQueryManager(GridKernalContext ctx) { + this.ctx = ctx; + localNodeId = ctx.localNodeId(); histSz = ctx.config().getSqlConfiguration().getSqlQueryHistorySize(); @@ -146,12 +154,16 @@ public Long register(String qry, GridCacheQueryType qryType, String schemaName, qryType, schemaName, System.currentTimeMillis(), + ctx.performanceStatistics().enabled() ? System.nanoTime() : 0, cancel, loc ); GridRunningQueryInfo preRun = runs.putIfAbsent(qryId, run); + if (ctx.performanceStatistics().enabled()) + currQryInfo.set(run); + assert preRun == null : "Running query already registered [prev_qry=" + preRun + ", newQry=" + run + ']'; return qryId; @@ -199,12 +211,32 @@ public void unregister(Long qryId, @Nullable Throwable failReason) { canceledQrsCnt.increment(); } } + + if (ctx.performanceStatistics().enabled() && qry.startTimeNanos() > 0) { + ctx.performanceStatistics().query( + qry.queryType(), + qry.query(), + qry.requestId(), + qry.startTime(), + System.nanoTime() - qry.startTimeNanos(), + !failed); + } } finally { qrySpan.end(); } } + /** @param reqId Request ID of query to track. */ + public void trackRequestId(long reqId) { + if (ctx.performanceStatistics().enabled()) { + GridRunningQueryInfo info = currQryInfo.get(); + + if (info != null) + info.requestId(reqId); + } + } + /** * Return SQL queries which executing right now. * diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/task/GridTaskProcessor.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/task/GridTaskProcessor.java index 9a8cdbe2bbfd0..34032a01b8c83 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/task/GridTaskProcessor.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/task/GridTaskProcessor.java @@ -1363,6 +1363,15 @@ private class TaskEventListener implements GridTaskEventListener { U.error(log, "Failed to unregister job communication message listeners and counters.", e); } } + + if (ctx.performanceStatistics().enabled()) { + ctx.performanceStatistics().task( + ses.getId(), + ses.getTaskName(), + ses.getStartTime(), + U.currentTimeMillis() - ses.getStartTime(), + worker.affPartId()); + } } } diff --git a/modules/core/src/main/java/org/apache/ignite/mxbean/PerformanceStatisticsMBean.java b/modules/core/src/main/java/org/apache/ignite/mxbean/PerformanceStatisticsMBean.java new file mode 100644 index 0000000000000..29d7c2d720073 --- /dev/null +++ b/modules/core/src/main/java/org/apache/ignite/mxbean/PerformanceStatisticsMBean.java @@ -0,0 +1,40 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.ignite.mxbean; + +import org.apache.ignite.IgniteCheckedException; +import org.apache.ignite.lang.IgniteExperimental; + +/** + * MBean that provides access to performance statistics management. + */ +@IgniteExperimental +@MXBeanDescription("MBean provide access to performance statistics management.") +public interface PerformanceStatisticsMBean { + /** Start collecting performance statistics in the cluster. */ + @MXBeanDescription("Start collecting performance statistics in the cluster.") + public void start() throws IgniteCheckedException; + + /** Stop collecting performance statistics in the cluster. */ + @MXBeanDescription("Stop collecting performance statistics in the cluster.") + public void stop() throws IgniteCheckedException; + + /** @return {@code True} if collecting performance statistics is started. */ + @MXBeanDescription("True if collecting performance statistics is started.") + public boolean started(); +} diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/performancestatistics/AbstractPerformanceStatisticsTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/performancestatistics/AbstractPerformanceStatisticsTest.java new file mode 100644 index 0000000000000..e72794c9cac7a --- /dev/null +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/performancestatistics/AbstractPerformanceStatisticsTest.java @@ -0,0 +1,194 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.ignite.internal.processors.performancestatistics; + +import java.io.File; +import java.lang.management.ThreadInfo; +import java.util.List; +import java.util.UUID; +import org.apache.ignite.Ignite; +import org.apache.ignite.internal.processors.cache.query.GridCacheQueryType; +import org.apache.ignite.internal.util.GridIntList; +import org.apache.ignite.internal.util.typedef.G; +import org.apache.ignite.internal.util.typedef.internal.U; +import org.apache.ignite.lang.IgniteUuid; +import org.apache.ignite.mxbean.PerformanceStatisticsMBean; +import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest; + +import static java.util.Collections.singletonList; +import static org.apache.ignite.internal.processors.performancestatistics.FilePerformanceStatisticsWriter.PERF_STAT_DIR; +import static org.apache.ignite.internal.processors.performancestatistics.FilePerformanceStatisticsWriter.WRITER_THREAD_NAME; +import static org.apache.ignite.testframework.GridTestUtils.waitForCondition; + +/** + * Ignite performance statistics abstract test. + */ +public abstract class AbstractPerformanceStatisticsTest extends GridCommonAbstractTest { + /** */ + public static final long TIMEOUT = 30_000; + + /** {@inheritDoc} */ + @Override protected void beforeTestsStarted() throws Exception { + super.beforeTestsStarted(); + + cleanPerformanceStatisticsDir(); + } + + /** {@inheritDoc} */ + @Override protected void afterTestsStopped() throws Exception { + super.afterTestsStopped(); + + stopAllGrids(); + + cleanPerformanceStatisticsDir(); + } + + /** {@inheritDoc} */ + @Override protected void afterTest() throws Exception { + super.afterTest(); + + cleanPerformanceStatisticsDir(); + } + + /** Cleans performance statistics directory. */ + protected static void cleanPerformanceStatisticsDir() throws Exception { + U.resolveWorkDirectory(U.defaultWorkDirectory(), PERF_STAT_DIR, true); + } + + /** Starts collecting performance statistics. */ + protected static void startCollectStatistics() throws Exception { + List grids = G.allGrids(); + + assertFalse(grids.isEmpty()); + + statisticsMBean(grids.get(0).name()).start(); + + waitForStatisticsEnabled(true); + } + + /** Stops and reads collecting performance statistics. */ + protected static void stopCollectStatisticsAndRead(TestHandler... handlers) throws Exception { + List grids = G.allGrids(); + + assertFalse(grids.isEmpty()); + + statisticsMBean(grids.get(0).name()).stop(); + + waitForStatisticsEnabled(false); + + File dir = U.resolveWorkDirectory(U.defaultWorkDirectory(), PERF_STAT_DIR, false); + + new FilePerformanceStatisticsReader(handlers).read(singletonList(dir)); + } + + /** Wait for statistics started/stopped in the cluster. */ + public static void waitForStatisticsEnabled(boolean performanceStatsEnabled) throws Exception { + assertTrue(waitForCondition(() -> { + List grids = G.allGrids(); + + for (Ignite grid : grids) + if (performanceStatsEnabled != statisticsMBean(grid.name()).started()) + return false; + + // Make sure that writer flushed data and stopped. + if (!performanceStatsEnabled) { + for (long id : U.getThreadMx().getAllThreadIds()) { + ThreadInfo info = U.getThreadMx().getThreadInfo(id); + + if (info != null && info.getThreadState() != Thread.State.TERMINATED && + info.getThreadName().startsWith(WRITER_THREAD_NAME)) + return false; + } + } + + return true; + }, TIMEOUT)); + } + + /** + * @param igniteInstanceName Ignite instance name. + * @return Ignite performance statistics MBean. + */ + protected static PerformanceStatisticsMBean statisticsMBean(String igniteInstanceName) { + return getMxBean(igniteInstanceName, "PerformanceStatistics", PerformanceStatisticsMBeanImpl.class, + PerformanceStatisticsMBean.class); + } + + /** @return Performance statistics files. */ + protected static List statisticsFiles() throws Exception { + File perfStatDir = U.resolveWorkDirectory(U.defaultWorkDirectory(), PERF_STAT_DIR, false); + + return FilePerformanceStatisticsReader.resolveFiles(singletonList(perfStatDir)); + } + + /** Test performance statistics handler. */ + public static class TestHandler implements PerformanceStatisticsHandler { + /** {@inheritDoc} */ + @Override public void cacheStart(UUID nodeId, int cacheId, String name) { + // No-op. + } + + /** {@inheritDoc} */ + @Override public void cacheOperation(UUID nodeId, OperationType type, int cacheId, long startTime, + long duration) { + // No-op. + } + + /** {@inheritDoc} */ + @Override public void transaction(UUID nodeId, GridIntList cacheIds, long startTime, long duration, + boolean commited) { + // No-op. + } + + /** {@inheritDoc} */ + @Override public void query(UUID nodeId, GridCacheQueryType type, String text, long id, long startTime, + long duration, boolean success) { + // No-op. + } + + /** {@inheritDoc} */ + @Override public void queryReads(UUID nodeId, GridCacheQueryType type, UUID queryNodeId, long id, + long logicalReads, long physicalReads) { + // No-op. + } + + /** {@inheritDoc} */ + @Override public void task(UUID nodeId, IgniteUuid sesId, String taskName, long startTime, long duration, + int affPartId) { + // No-op. + } + + /** {@inheritDoc} */ + @Override public void job(UUID nodeId, IgniteUuid sesId, long queuedTime, long startTime, long duration, + boolean timedOut) { + // No-op. + } + } + + /** Client type to run load from. */ + enum ClientType { + /** Server node. */ + SERVER, + + /** Client node. */ + CLIENT, + + /** Thin client. */ + THIN_CLIENT; + } +} diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/performancestatistics/CacheStartTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/performancestatistics/CacheStartTest.java new file mode 100644 index 0000000000000..523090107d2b6 --- /dev/null +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/performancestatistics/CacheStartTest.java @@ -0,0 +1,152 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.ignite.internal.processors.performancestatistics; + +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import org.apache.ignite.cluster.ClusterNode; +import org.apache.ignite.cluster.ClusterState; +import org.apache.ignite.configuration.CacheConfiguration; +import org.apache.ignite.configuration.DataRegionConfiguration; +import org.apache.ignite.configuration.DataStorageConfiguration; +import org.apache.ignite.configuration.IgniteConfiguration; +import org.apache.ignite.internal.IgniteEx; +import org.apache.ignite.internal.util.typedef.internal.U; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +/** + * Tests cache start operation. + */ +@RunWith(Parameterized.class) +public class CacheStartTest extends AbstractPerformanceStatisticsTest { + /** Static configured cache name. */ + private static final String STATIC_CACHE_NAME = "static-cache"; + + /** Persistence enabled flag. */ + @Parameterized.Parameter + public boolean persistence; + + /** @return Test parameters. */ + @Parameterized.Parameters(name = "persistence={0}") + public static Collection parameters() { + return Arrays.asList(new Object[][] {{false}, {true}}); + } + + /** {@inheritDoc} */ + @Override protected IgniteConfiguration getConfiguration(String igniteInstanceName) throws Exception { + IgniteConfiguration cfg = super.getConfiguration(igniteInstanceName); + + cfg.setConsistentId(igniteInstanceName); + + cfg.setCacheConfiguration(new CacheConfiguration<>(STATIC_CACHE_NAME)); + + if (persistence) { + cfg.setDataStorageConfiguration( + new DataStorageConfiguration() + .setDefaultDataRegionConfiguration( + new DataRegionConfiguration() + .setPersistenceEnabled(true) + ) + ); + } + + return cfg; + } + + /** {@inheritDoc} */ + @Override protected void beforeTest() throws Exception { + if (persistence) + cleanPersistenceDir(); + } + + /** {@inheritDoc} */ + @Override protected void afterTest() throws Exception { + stopAllGrids(); + + if (persistence) + cleanPersistenceDir(); + + cleanPerformanceStatisticsDir(); + } + + /** @throws Exception If failed. */ + @Test + public void testCacheStart() throws Exception { + IgniteEx srv = startGrids(2); + + if (persistence) + srv.cluster().state(ClusterState.ACTIVE); + + startClientGrid("client"); + + startCollectStatistics(); + + stopCollectStatisticsAndCheckCaches(srv); + + cleanPerformanceStatisticsDir(); + + startCollectStatistics(); + + srv.getOrCreateCache(DEFAULT_CACHE_NAME); + + stopCollectStatisticsAndCheckCaches(srv); + + if (persistence) { + stopAllGrids(); + + srv = startGrids(2); + + cleanPerformanceStatisticsDir(); + + startCollectStatistics(); + + stopCollectStatisticsAndCheckCaches(srv); + } + } + + /** Stops and reads collecting performance statistics. Checks cache start operation. */ + private void stopCollectStatisticsAndCheckCaches(IgniteEx ignite) throws Exception { + Map expCaches = new HashMap<>(); + + ignite.context().cache().cacheDescriptors().values().forEach( + desc -> expCaches.put(desc.cacheId(), desc.cacheName())); + + ClusterNode coord = U.oldest(ignite.cluster().nodes(), null); + + Set caches = new HashSet<>(); + + stopCollectStatisticsAndRead(new TestHandler() { + @Override public void cacheStart(UUID nodeId, int cacheId, String name) { + caches.add(cacheId); + + assertEquals(coord.id(), nodeId); + assertTrue(expCaches.containsKey(cacheId)); + assertEquals(expCaches.get(cacheId), name); + } + }); + + assertTrue(expCaches.keySet().equals(caches)); + } +} diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/performancestatistics/ForwardReadTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/performancestatistics/ForwardReadTest.java new file mode 100644 index 0000000000000..2719bc76d927e --- /dev/null +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/performancestatistics/ForwardReadTest.java @@ -0,0 +1,147 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.ignite.internal.processors.performancestatistics; + +import java.io.File; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import org.apache.ignite.internal.processors.cache.persistence.file.FileIO; +import org.apache.ignite.internal.processors.cache.persistence.file.RandomAccessFileIOFactory; +import org.apache.ignite.internal.util.typedef.F; +import org.apache.ignite.internal.util.typedef.internal.U; +import org.apache.ignite.lang.IgniteUuid; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import static com.google.common.collect.Collections2.permutations; +import static com.google.common.collect.Lists.cartesianProduct; +import static java.util.Collections.singletonList; +import static java.util.UUID.randomUUID; +import static org.apache.ignite.internal.processors.performancestatistics.FilePerformanceStatisticsWriter.PERF_STAT_DIR; +import static org.apache.ignite.internal.processors.performancestatistics.FilePerformanceStatisticsWriter.writeIgniteUuid; +import static org.apache.ignite.internal.processors.performancestatistics.FilePerformanceStatisticsWriter.writeString; + +/** + * Tests strings caching. + */ +@RunWith(Parameterized.class) +public class ForwardReadTest extends AbstractPerformanceStatisticsTest { + /** Read buffer size. */ + private static final int BUFFER_SIZE = 100; + + /** {@code True} If test with strings that can't be found during forward read. */ + @Parameterized.Parameter + public boolean unknownStrs; + + /** @return Test parameters. */ + @Parameterized.Parameters(name = "unknownStrs={0}") + public static Collection parameters() { + return Arrays.asList(new Object[][] {{false}, {true}}); + } + + /** @throws Exception If failed. */ + @Test + public void testStringForwardRead() throws Exception { + File dir = U.resolveWorkDirectory(U.defaultWorkDirectory(), PERF_STAT_DIR, false); + + Map expTasks = createStatistics(dir); + + new FilePerformanceStatisticsReader(BUFFER_SIZE, new TestHandler() { + @Override public void task(UUID nodeId, IgniteUuid sesId, String taskName, long startTime, long duration, + int affPartId) { + assertNotNull(taskName); + + assertTrue(expTasks.containsKey(taskName)); + + expTasks.computeIfPresent(taskName, (name, cnt) -> --cnt); + } + }).read(singletonList(dir)); + + assertTrue(expTasks.values().stream().allMatch(cnt -> cnt == 0)); + } + + /** Creates test performance statistics file. */ + private Map createStatistics(File dir) throws Exception { + Map expTasks; + + File file = new File(dir, "node-" + randomUUID() + ".prf"); + + try (FileIO fileIo = new RandomAccessFileIOFactory().create(file)) { + ByteBuffer buf = ByteBuffer.allocate(10 * 1024).order(ByteOrder.nativeOrder()); + + expTasks = writeData(buf); + + buf.flip(); + + fileIo.write(buf); + + fileIo.force(); + } + + return expTasks; + } + + /** Generates task permutations and writes to buffer. */ + private Map writeData(ByteBuffer buf) { + Map expTasks = new HashMap<>(); + + List> lists = cartesianProduct(F.asList("task1", "task2"), F.asList(false, true)); + + int setIdx = 0; + + for (List> permute : permutations(lists)) { + for (List t2 : permute) { + String taskName = "dataSet-" + setIdx + "-" + t2.get(0); + Boolean cached = (Boolean)t2.get(1); + + expTasks.compute(taskName, (name, cnt) -> cnt == null ? 1 : ++cnt); + + writeTask(buf, taskName, cached); + } + + if (unknownStrs) { + String unknownTask = "dataSet-" + setIdx + "-unknownTask"; + + expTasks.put(unknownTask, 0); + + writeTask(buf, unknownTask, true); + } + + setIdx++; + } + + return expTasks; + } + + /** Writes test task to buffer. */ + private static void writeTask(ByteBuffer buf, String taskName, boolean cached) { + buf.put((byte)OperationType.TASK.ordinal()); + writeString(buf, taskName, cached); + writeIgniteUuid(buf, IgniteUuid.randomUuid()); + buf.putLong(0); + buf.putLong(0); + buf.putInt(0); + } +} diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/performancestatistics/PerformanceStatisticsMultipleStartTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/performancestatistics/PerformanceStatisticsMultipleStartTest.java new file mode 100644 index 0000000000000..e104b6695a136 --- /dev/null +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/performancestatistics/PerformanceStatisticsMultipleStartTest.java @@ -0,0 +1,69 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.ignite.internal.processors.performancestatistics; + +import java.io.File; +import java.util.List; +import java.util.UUID; +import java.util.concurrent.atomic.AtomicInteger; +import org.apache.ignite.configuration.IgniteConfiguration; +import org.apache.ignite.internal.IgniteEx; +import org.junit.Test; + +/** + * Tests performance statistics multiple start. + */ +public class PerformanceStatisticsMultipleStartTest extends AbstractPerformanceStatisticsTest { + /** {@inheritDoc} */ + @Override protected IgniteConfiguration getConfiguration(String igniteInstanceName) throws Exception { + IgniteConfiguration cfg = super.getConfiguration(igniteInstanceName); + + cfg.setCacheConfiguration(defaultCacheConfiguration()); + + return cfg; + } + + /** @throws Exception If failed. */ + @Test + public void testStartCreateNewFile() throws Exception { + IgniteEx srv = startGrid(0); + + int cnt = 5; + + for (int i = 1; i <= cnt; i++) { + startCollectStatistics(); + + srv.cache(DEFAULT_CACHE_NAME).get(0); + + AtomicInteger ops = new AtomicInteger(); + + stopCollectStatisticsAndRead(new TestHandler() { + @Override public void cacheOperation(UUID nodeId, OperationType type, int cacheId, long startTime, + long duration) { + ops.incrementAndGet(); + } + }); + + assertEquals(i, ops.get()); + + List files = statisticsFiles(); + + assertEquals(i, files.size()); + } + } +} diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/performancestatistics/PerformanceStatisticsPropertiesTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/performancestatistics/PerformanceStatisticsPropertiesTest.java new file mode 100644 index 0000000000000..1b7a35b253558 --- /dev/null +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/performancestatistics/PerformanceStatisticsPropertiesTest.java @@ -0,0 +1,201 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.ignite.internal.processors.performancestatistics; + +import java.io.File; +import java.util.List; +import java.util.UUID; +import java.util.concurrent.atomic.AtomicInteger; +import org.apache.ignite.IgniteSystemProperties; +import org.apache.ignite.configuration.IgniteConfiguration; +import org.apache.ignite.internal.IgniteEx; +import org.apache.ignite.lang.IgniteRunnable; +import org.apache.ignite.lang.IgniteUuid; +import org.apache.ignite.testframework.junits.WithSystemProperty; +import org.junit.Test; + +import static org.apache.ignite.IgniteSystemProperties.IGNITE_PERF_STAT_CACHED_STRINGS_THRESHOLD; +import static org.apache.ignite.IgniteSystemProperties.IGNITE_PERF_STAT_FILE_MAX_SIZE; +import static org.apache.ignite.IgniteSystemProperties.IGNITE_PERF_STAT_FLUSH_SIZE; +import static org.apache.ignite.internal.processors.performancestatistics.OperationType.cacheStartRecordSize; +import static org.apache.ignite.internal.processors.performancestatistics.OperationType.jobRecordSize; +import static org.apache.ignite.internal.processors.performancestatistics.OperationType.taskRecordSize; +import static org.apache.ignite.testframework.GridTestUtils.waitForCondition; + +/** + * Tests public performance statistics properties. + */ +public class PerformanceStatisticsPropertiesTest extends AbstractPerformanceStatisticsTest { + /** Test value of {@link IgniteSystemProperties#IGNITE_PERF_STAT_FILE_MAX_SIZE}. */ + private static final long TEST_FILE_MAX_SIZE = 1024; + + /** Test value of {@link IgniteSystemProperties#IGNITE_PERF_STAT_FLUSH_SIZE}. */ + private static final int TEST_FLUSH_SIZE = 1024; + + /** Test value of {@link IgniteSystemProperties#IGNITE_PERF_STAT_CACHED_STRINGS_THRESHOLD}. */ + private static final int TEST_CACHED_STRINGS_THRESHOLD = 5; + + /** Server. */ + private static IgniteEx srv; + + /** {@inheritDoc} */ + @Override protected IgniteConfiguration getConfiguration(String igniteInstanceName) throws Exception { + IgniteConfiguration cfg = super.getConfiguration(igniteInstanceName); + + cfg.setCacheConfiguration(defaultCacheConfiguration()); + + return cfg; + } + + /** {@inheritDoc} */ + @Override protected void beforeTestsStarted() throws Exception { + super.beforeTestsStarted(); + + srv = startGrid(0); + } + + /** @throws Exception If failed. */ + @Test + @WithSystemProperty(key = IGNITE_PERF_STAT_FILE_MAX_SIZE, value = "" + TEST_FILE_MAX_SIZE) + public void testFileMaxSize() throws Exception { + long initLen = srv.context().cache().cacheDescriptors().values().stream().mapToInt( + desc -> 1 + cacheStartRecordSize(desc.cacheName().getBytes().length, false)).sum(); + + long expOpsCnt = (TEST_FILE_MAX_SIZE - initLen) / (/*typeOp*/1 + OperationType.cacheRecordSize()); + + startCollectStatistics(); + + for (int i = 0; i < expOpsCnt * 2; i++) + srv.cache(DEFAULT_CACHE_NAME).get(i); + + AtomicInteger opsCnt = new AtomicInteger(); + + stopCollectStatisticsAndRead(new TestHandler() { + @Override public void cacheOperation(UUID nodeId, OperationType type, int cacheId, long startTime, + long duration) { + opsCnt.incrementAndGet(); + } + }); + + assertEquals(expOpsCnt, opsCnt.get()); + + long expLen = initLen + opsCnt.get() * (/*typeOp*/1 + OperationType.cacheRecordSize()); + + List files = statisticsFiles(); + + assertEquals(1, files.size()); + + long statFileLen = files.get(0).length(); + + assertEquals(expLen, statFileLen); + + assertTrue(statFileLen <= TEST_FILE_MAX_SIZE); + } + + /** @throws Exception If failed. */ + @Test + @WithSystemProperty(key = IGNITE_PERF_STAT_FLUSH_SIZE, value = "" + TEST_FLUSH_SIZE) + public void testFlushSize() throws Exception { + long initLen = srv.context().cache().cacheDescriptors().values().stream().mapToInt( + desc -> 1 + cacheStartRecordSize(desc.cacheName().getBytes().length, false)).sum(); + + long opsCnt = (TEST_FLUSH_SIZE - initLen) / (/*typeOp*/1 + OperationType.cacheRecordSize()); + + startCollectStatistics(); + + for (int i = 0; i < opsCnt; i++) + srv.cache(DEFAULT_CACHE_NAME).get(i); + + List files = statisticsFiles(); + + assertEquals(1, files.size()); + assertEquals(0, files.get(0).length()); + + srv.cache(DEFAULT_CACHE_NAME).get(0); + + assertTrue(waitForCondition( + () -> { + try { + List statFiles = statisticsFiles(); + + assertEquals(1, statFiles.size()); + + return statFiles.get(0).length() > 0; + } + catch (Exception e) { + throw new RuntimeException(e); + } + }, + getTestTimeout())); + + stopCollectStatisticsAndRead(new TestHandler()); + } + + /** @throws Exception If failed. */ + @Test + @WithSystemProperty(key = IGNITE_PERF_STAT_CACHED_STRINGS_THRESHOLD, value = "" + TEST_CACHED_STRINGS_THRESHOLD) + public void testCachedStringsThreshold() throws Exception { + int tasksCnt = TEST_CACHED_STRINGS_THRESHOLD * 2; + int executions = 2; + + startCollectStatistics(); + + int expLen = 0; + + for (int i = 0; i < tasksCnt; i++) { + String taskName = "TestTask-" + i; + + if (i < TEST_CACHED_STRINGS_THRESHOLD - 1 - srv.context().cache().cacheDescriptors().values().size()) { + expLen += taskRecordSize(taskName.getBytes().length, false) + jobRecordSize() + + (taskRecordSize(0, true) + jobRecordSize()) * (executions - 1); + } + else + expLen += (taskRecordSize(taskName.getBytes().length, false) + jobRecordSize()) * executions; + + expLen += /*typeOp*/2 * executions; + + for (int j = 0; j < executions; j++) { + srv.compute().withName(taskName).run(new IgniteRunnable() { + @Override public void run() { + // No-op. + } + }); + } + } + + AtomicInteger tasks = new AtomicInteger(); + + stopCollectStatisticsAndRead(new TestHandler() { + @Override public void task(UUID nodeId, IgniteUuid sesId, String taskName, long startTime, long duration, + int affPartId) { + tasks.incrementAndGet(); + } + }); + + assertEquals(tasksCnt * executions, tasks.get()); + + // Started caches. + expLen += srv.context().cache().cacheDescriptors().values().stream().mapToInt( + desc -> 1 + cacheStartRecordSize(desc.cacheName().getBytes().length, false)).sum(); + + List files = statisticsFiles(); + + assertEquals(1, files.size()); + assertEquals(expLen, files.get(0).length()); + } +} diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/performancestatistics/PerformanceStatisticsSelfTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/performancestatistics/PerformanceStatisticsSelfTest.java new file mode 100644 index 0000000000000..396f5ee762803 --- /dev/null +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/performancestatistics/PerformanceStatisticsSelfTest.java @@ -0,0 +1,318 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.ignite.internal.processors.performancestatistics; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.UUID; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.Lock; +import java.util.function.Consumer; +import javax.cache.processor.EntryProcessor; +import javax.cache.processor.EntryProcessorException; +import javax.cache.processor.MutableEntry; +import org.apache.ignite.IgniteCache; +import org.apache.ignite.cache.CacheEntryProcessor; +import org.apache.ignite.configuration.IgniteConfiguration; +import org.apache.ignite.internal.IgniteEx; +import org.apache.ignite.internal.util.GridIntList; +import org.apache.ignite.internal.util.typedef.internal.CU; +import org.apache.ignite.internal.util.typedef.internal.U; +import org.apache.ignite.lang.IgniteRunnable; +import org.apache.ignite.lang.IgniteUuid; +import org.apache.ignite.transactions.Transaction; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import static org.apache.ignite.internal.processors.performancestatistics.AbstractPerformanceStatisticsTest.ClientType.CLIENT; +import static org.apache.ignite.internal.processors.performancestatistics.AbstractPerformanceStatisticsTest.ClientType.SERVER; +import static org.apache.ignite.internal.processors.performancestatistics.OperationType.CACHE_GET; +import static org.apache.ignite.internal.processors.performancestatistics.OperationType.CACHE_GET_ALL; +import static org.apache.ignite.internal.processors.performancestatistics.OperationType.CACHE_GET_AND_PUT; +import static org.apache.ignite.internal.processors.performancestatistics.OperationType.CACHE_GET_AND_REMOVE; +import static org.apache.ignite.internal.processors.performancestatistics.OperationType.CACHE_INVOKE; +import static org.apache.ignite.internal.processors.performancestatistics.OperationType.CACHE_INVOKE_ALL; +import static org.apache.ignite.internal.processors.performancestatistics.OperationType.CACHE_LOCK; +import static org.apache.ignite.internal.processors.performancestatistics.OperationType.CACHE_PUT; +import static org.apache.ignite.internal.processors.performancestatistics.OperationType.CACHE_PUT_ALL; +import static org.apache.ignite.internal.processors.performancestatistics.OperationType.CACHE_REMOVE; +import static org.apache.ignite.internal.processors.performancestatistics.OperationType.CACHE_REMOVE_ALL; + +/** + * Tests performance statistics. + */ +@RunWith(Parameterized.class) +@SuppressWarnings({"LockAcquiredButNotSafelyReleased"}) +public class PerformanceStatisticsSelfTest extends AbstractPerformanceStatisticsTest { + /** Nodes count. */ + private static final int NODES_CNT = 2; + + /** Cache entry count. */ + private static final int ENTRY_COUNT = 100; + + /** Test entry processor. */ + private static final EntryProcessor ENTRY_PROC = + new EntryProcessor() { + @Override public Object process(MutableEntry entry, Object... arguments) + throws EntryProcessorException { + return null; + } + }; + + /** Test cache entry processor. */ + private static final CacheEntryProcessor CACHE_ENTRY_PROC = + new CacheEntryProcessor() { + @Override public Object process(MutableEntry entry, Object... arguments) + throws EntryProcessorException { + return null; + } + }; + + /** Client type to run operations from. */ + @Parameterized.Parameter + public ClientType clientType; + + /** @return Test parameters. */ + @Parameterized.Parameters(name = "clientType={0}") + public static Collection parameters() { + return Arrays.asList(new Object[][] {{SERVER}, {CLIENT}}); + } + + /** Ignite. */ + private static IgniteEx srv; + + /** Ignite node to run load from. */ + private static IgniteEx node; + + /** Test cache. */ + private static IgniteCache cache; + + /** {@inheritDoc} */ + @Override protected IgniteConfiguration getConfiguration(String igniteInstanceName) throws Exception { + IgniteConfiguration cfg = super.getConfiguration(igniteInstanceName); + + cfg.setCacheConfiguration(defaultCacheConfiguration()); + + return cfg; + } + + /** {@inheritDoc} */ + @Override protected void beforeTestsStarted() throws Exception { + srv = startGrid(NODES_CNT - 1); + + IgniteEx client = startClientGrid(NODES_CNT); + + node = clientType == SERVER ? srv : client; + + cache = node.cache(DEFAULT_CACHE_NAME); + + for (int i = 0; i < ENTRY_COUNT; i++) + cache.put(i, i); + } + + /** @throws Exception If failed. */ + @Test + public void testCompute() throws Exception { + String testTaskName = "testTask"; + int executions = 5; + long startTime = U.currentTimeMillis(); + + startCollectStatistics(); + + IgniteRunnable task = new IgniteRunnable() { + @Override public void run() { + // No-op. + } + }; + + for (int i = 0; i < executions; i++) + node.compute().withName(testTaskName).run(task); + + HashMap sessions = new HashMap<>(); + AtomicInteger tasks = new AtomicInteger(); + AtomicInteger jobs = new AtomicInteger(); + + stopCollectStatisticsAndRead(new TestHandler() { + @Override public void task(UUID nodeId, IgniteUuid sesId, String taskName, long taskStartTime, + long duration, int affPartId) { + sessions.compute(sesId, (uuid, cnt) -> cnt == null ? 1 : ++cnt); + + tasks.incrementAndGet(); + + assertEquals(node.context().localNodeId(), nodeId); + assertEquals(testTaskName, taskName); + assertTrue(taskStartTime >= startTime); + assertTrue(duration >= 0); + assertEquals(-1, affPartId); + } + + @Override public void job(UUID nodeId, IgniteUuid sesId, long queuedTime, long jobStartTime, long duration, + boolean timedOut) { + sessions.compute(sesId, (uuid, cnt) -> cnt == null ? 1 : ++cnt); + + jobs.incrementAndGet(); + + assertEquals(srv.context().localNodeId(), nodeId); + assertTrue(queuedTime >= 0); + assertTrue(jobStartTime >= startTime); + assertTrue(duration >= 0); + assertFalse(timedOut); + } + }); + + assertEquals(executions, tasks.get()); + assertEquals(executions, jobs.get()); + + Collection vals = sessions.values(); + + assertEquals(executions, vals.size()); + assertTrue("Invalid sessions: " + sessions, vals.stream().allMatch(cnt -> cnt == NODES_CNT)); + } + + /** @throws Exception If failed. */ + @Test + public void testCacheOperation() throws Exception { + checkCacheOperation(CACHE_PUT, cache -> cache.put(1, 1)); + checkCacheOperation(CACHE_PUT, cache -> cache.putAsync(2, 2).get()); + + checkCacheOperation(CACHE_PUT_ALL, cache -> cache.putAll(Collections.singletonMap(3, 3))); + checkCacheOperation(CACHE_PUT_ALL, cache -> cache.putAllAsync(Collections.singletonMap(4, 4)).get()); + + checkCacheOperation(CACHE_GET, cache -> cache.get(1)); + checkCacheOperation(CACHE_GET, cache -> cache.getAsync(2).get()); + + checkCacheOperation(CACHE_GET_AND_PUT, cache -> cache.getAndPut(1, 1)); + checkCacheOperation(CACHE_GET_AND_PUT, cache -> cache.getAndPutAsync(2, 2).get()); + + checkCacheOperation(CACHE_GET_ALL, cache -> cache.getAll(Collections.singleton(1))); + checkCacheOperation(CACHE_GET_ALL, cache -> cache.getAllAsync(Collections.singleton(2)).get()); + + checkCacheOperation(CACHE_REMOVE, cache -> cache.remove(1)); + checkCacheOperation(CACHE_REMOVE, cache -> cache.removeAsync(2).get()); + + checkCacheOperation(CACHE_REMOVE_ALL, cache -> cache.removeAll(Collections.singleton(3))); + checkCacheOperation(CACHE_REMOVE_ALL, cache -> cache.removeAllAsync(Collections.singleton(4)).get()); + + checkCacheOperation(CACHE_GET_AND_REMOVE, cache -> cache.getAndRemove(5)); + checkCacheOperation(CACHE_GET_AND_REMOVE, cache -> cache.getAndRemoveAsync(6).get()); + + checkCacheOperation(CACHE_LOCK, cache -> { + Lock lock = cache.lock(7); + + lock.lock(); + lock.unlock(); + }); + + checkCacheOperation(CACHE_LOCK, cache -> { + Lock lock = cache.lockAll(Collections.singleton(8)); + + lock.lock(); + lock.unlock(); + }); + + checkCacheOperation(CACHE_INVOKE, cache -> cache.invoke(10, ENTRY_PROC)); + checkCacheOperation(CACHE_INVOKE, cache -> cache.invokeAsync(10, ENTRY_PROC).get()); + + checkCacheOperation(CACHE_INVOKE, cache -> cache.invoke(10, CACHE_ENTRY_PROC)); + checkCacheOperation(CACHE_INVOKE, cache -> cache.invokeAsync(10, CACHE_ENTRY_PROC).get()); + + checkCacheOperation(CACHE_INVOKE_ALL, cache -> cache.invokeAll(Collections.singleton(10), ENTRY_PROC)); + checkCacheOperation(CACHE_INVOKE_ALL, + cache -> cache.invokeAllAsync(Collections.singleton(10), ENTRY_PROC).get()); + + checkCacheOperation(CACHE_INVOKE_ALL, cache -> cache.invokeAll(Collections.singleton(10), CACHE_ENTRY_PROC)); + checkCacheOperation(CACHE_INVOKE_ALL, + cache -> cache.invokeAllAsync(Collections.singleton(10), CACHE_ENTRY_PROC).get()); + } + + /** Checks cache operation. */ + private void checkCacheOperation(OperationType op, Consumer> clo) throws Exception { + long startTime = U.currentTimeMillis(); + + cleanPerformanceStatisticsDir(); + + startCollectStatistics(); + + clo.accept(cache); + + AtomicInteger ops = new AtomicInteger(); + + stopCollectStatisticsAndRead(new TestHandler() { + @Override public void cacheOperation(UUID nodeId, OperationType type, int cacheId, long opStartTime, + long duration) { + ops.incrementAndGet(); + + assertEquals(node.context().localNodeId(), nodeId); + assertEquals(op, type); + assertEquals(CU.cacheId(DEFAULT_CACHE_NAME), cacheId); + assertTrue(opStartTime >= startTime); + assertTrue(duration >= 0); + } + }); + + assertEquals(1, ops.get()); + } + + /** @throws Exception If failed. */ + @Test + public void testTransaction() throws Exception { + checkTx(true); + + checkTx(false); + } + + /** @param commited {@code True} if check transaction commited. */ + private void checkTx(boolean commited) throws Exception { + long startTime = U.currentTimeMillis(); + + cleanPerformanceStatisticsDir(); + + startCollectStatistics(); + + try (Transaction tx = node.transactions().txStart()) { + for (int i = 0; i < 10; i++) + cache.put(i, i * 2); + + if (commited) + tx.commit(); + else + tx.rollback(); + } + + AtomicInteger txs = new AtomicInteger(); + + stopCollectStatisticsAndRead(new TestHandler() { + @Override public void transaction(UUID nodeId, GridIntList cacheIds, long txStartTime, long duration, + boolean txCommited) { + txs.incrementAndGet(); + + assertEquals(node.context().localNodeId(), nodeId); + assertEquals(1, cacheIds.size()); + assertEquals(CU.cacheId(DEFAULT_CACHE_NAME), cacheIds.get(0)); + assertTrue(txStartTime >= startTime); + assertTrue(duration >= 0); + assertEquals(commited, txCommited); + } + }); + + assertEquals(1, txs.get()); + } +} diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/performancestatistics/PerformanceStatisticsThinClientTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/performancestatistics/PerformanceStatisticsThinClientTest.java new file mode 100644 index 0000000000000..978e237253c74 --- /dev/null +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/performancestatistics/PerformanceStatisticsThinClientTest.java @@ -0,0 +1,244 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.ignite.internal.processors.performancestatistics; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.UUID; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Consumer; +import org.apache.ignite.Ignition; +import org.apache.ignite.client.ClientCache; +import org.apache.ignite.client.ClientTransaction; +import org.apache.ignite.client.Config; +import org.apache.ignite.client.IgniteClient; +import org.apache.ignite.configuration.ClientConfiguration; +import org.apache.ignite.configuration.ClientConnectorConfiguration; +import org.apache.ignite.configuration.IgniteConfiguration; +import org.apache.ignite.configuration.ThinClientConfiguration; +import org.apache.ignite.internal.IgniteEx; +import org.apache.ignite.internal.client.thin.TestTask; +import org.apache.ignite.internal.util.GridIntList; +import org.apache.ignite.internal.util.typedef.F; +import org.apache.ignite.internal.util.typedef.internal.CU; +import org.apache.ignite.internal.util.typedef.internal.U; +import org.apache.ignite.lang.IgniteUuid; +import org.junit.Test; + +import static org.apache.ignite.internal.processors.performancestatistics.OperationType.CACHE_GET; +import static org.apache.ignite.internal.processors.performancestatistics.OperationType.CACHE_GET_ALL; +import static org.apache.ignite.internal.processors.performancestatistics.OperationType.CACHE_GET_AND_PUT; +import static org.apache.ignite.internal.processors.performancestatistics.OperationType.CACHE_GET_AND_REMOVE; +import static org.apache.ignite.internal.processors.performancestatistics.OperationType.CACHE_PUT; +import static org.apache.ignite.internal.processors.performancestatistics.OperationType.CACHE_PUT_ALL; +import static org.apache.ignite.internal.processors.performancestatistics.OperationType.CACHE_REMOVE; +import static org.apache.ignite.internal.processors.performancestatistics.OperationType.CACHE_REMOVE_ALL; + +/** + * Tests thin client performance statistics. + */ +public class PerformanceStatisticsThinClientTest extends AbstractPerformanceStatisticsTest { + /** Test task name. */ + public static final String TEST_TASK_NAME = "TestTask"; + + /** Active tasks limit. */ + private static final int ACTIVE_TASKS_LIMIT = 50; + + /** Grids count. */ + private static final int GRIDS_CNT = 2; + + /** Thin client. */ + private static IgniteClient thinClient; + + /** {@inheritDoc} */ + @Override protected IgniteConfiguration getConfiguration(String igniteInstanceName) throws Exception { + IgniteConfiguration cfg = super.getConfiguration(igniteInstanceName); + + cfg.setCacheConfiguration(defaultCacheConfiguration()); + + cfg.setClientConnectorConfiguration( + new ClientConnectorConfiguration().setThinClientConfiguration( + new ThinClientConfiguration().setMaxActiveComputeTasksPerConnection(ACTIVE_TASKS_LIMIT))); + + return cfg; + } + + /** {@inheritDoc} */ + @Override protected void beforeTestsStarted() throws Exception { + super.beforeTestsStarted(); + + IgniteEx ignite = startGrids(GRIDS_CNT); + + ignite.compute().localDeployTask(TestTask.class, TestTask.class.getClassLoader()); + + thinClient = Ignition.startClient(new ClientConfiguration().setAddresses(Config.SERVER)); + } + + /** {@inheritDoc} */ + @Override protected void afterTestsStopped() throws Exception { + super.afterTestsStopped(); + + thinClient.close(); + } + + /** @throws Exception If failed. */ + @Test + public void testCompute() throws Exception { + int executions = 5; + long startTime = U.currentTimeMillis(); + + startCollectStatistics(); + + for (int i = 0; i < executions; i++) + thinClient.compute().execute(TEST_TASK_NAME, null); + + HashMap sessions = new HashMap<>(); + AtomicInteger tasks = new AtomicInteger(); + AtomicInteger jobs = new AtomicInteger(); + + stopCollectStatisticsAndRead(new TestHandler() { + @Override public void task(UUID nodeId, IgniteUuid sesId, String taskName, long taskStartTime, + long duration, int affPartId) { + sessions.compute(sesId, (uuid, cnt) -> cnt == null ? 1 : ++cnt); + + tasks.incrementAndGet(); + + assertTrue(F.nodeIds(grid(0).cluster().forServers().nodes()).contains(nodeId)); + assertEquals(TEST_TASK_NAME, taskName); + assertTrue(taskStartTime >= startTime); + assertTrue(duration >= 0); + assertEquals(-1, affPartId); + } + + @Override public void job(UUID nodeId, IgniteUuid sesId, long queuedTime, long jobStartTime, long duration, + boolean timedOut) { + sessions.compute(sesId, (uuid, cnt) -> cnt == null ? 1 : ++cnt); + + jobs.incrementAndGet(); + + assertTrue(F.nodeIds(grid(0).cluster().forServers().nodes()).contains(nodeId)); + assertTrue(queuedTime >= 0); + assertTrue(jobStartTime >= startTime); + assertTrue(duration >= 0); + assertFalse(timedOut); + } + }); + + assertEquals(executions, tasks.get()); + assertEquals(executions * GRIDS_CNT, jobs.get()); + + Collection vals = sessions.values(); + + assertEquals(executions, vals.size()); + assertTrue("Invalid sessions: " + sessions, vals.stream().allMatch(val -> val == GRIDS_CNT + 1)); + } + + /** @throws Exception If failed. */ + @Test + public void testCacheOperation() throws Exception { + checkCacheOperation(CACHE_PUT, cache -> cache.put(1, 1)); + + checkCacheOperation(CACHE_PUT_ALL, cache -> cache.putAll(Collections.singletonMap(3, 3))); + + checkCacheOperation(CACHE_GET, cache -> cache.get(1)); + + checkCacheOperation(CACHE_GET_AND_PUT, cache -> cache.getAndPut(1, 1)); + + checkCacheOperation(CACHE_GET_ALL, cache -> cache.getAll(Collections.singleton(1))); + + checkCacheOperation(CACHE_REMOVE, cache -> cache.remove(1)); + + checkCacheOperation(CACHE_REMOVE_ALL, cache -> cache.removeAll(Collections.singleton(3))); + + checkCacheOperation(CACHE_GET_AND_REMOVE, cache -> cache.getAndRemove(5)); + } + + /** Checks cache operation. */ + private void checkCacheOperation(OperationType op, Consumer> clo) throws Exception { + long startTime = U.currentTimeMillis(); + + cleanPerformanceStatisticsDir(); + + startCollectStatistics(); + + clo.accept(thinClient.cache(DEFAULT_CACHE_NAME)); + + AtomicInteger ops = new AtomicInteger(); + + stopCollectStatisticsAndRead(new TestHandler() { + @Override public void cacheOperation(UUID nodeId, OperationType type, int cacheId, long opStartTime, + long duration) { + ops.incrementAndGet(); + + assertTrue(F.nodeIds(grid(0).cluster().forServers().nodes()).contains(nodeId)); + assertEquals(op, type); + assertEquals(CU.cacheId(DEFAULT_CACHE_NAME), cacheId); + assertTrue(opStartTime >= startTime); + assertTrue(duration >= 0); + } + }); + + assertEquals(1, ops.get()); + } + + /** @throws Exception If failed. */ + @Test + public void testTransaction() throws Exception { + checkTx(true); + + checkTx(false); + } + + /** @param commited {@code True} if check transaction commited. */ + private void checkTx(boolean commited) throws Exception { + long startTime = U.currentTimeMillis(); + + cleanPerformanceStatisticsDir(); + + startCollectStatistics(); + + try (ClientTransaction tx = thinClient.transactions().txStart()) { + for (int i = 0; i < 10; i++) + thinClient.cache(DEFAULT_CACHE_NAME).put(i, i * 2); + + if (commited) + tx.commit(); + else + tx.rollback(); + } + + AtomicInteger txs = new AtomicInteger(); + + stopCollectStatisticsAndRead(new TestHandler() { + @Override public void transaction(UUID nodeId, GridIntList cacheIds, long txStartTime, long duration, + boolean txCommited) { + txs.incrementAndGet(); + + assertTrue(F.nodeIds(grid(0).cluster().forServers().nodes()).contains(nodeId)); + assertEquals(1, cacheIds.size()); + assertEquals(CU.cacheId(DEFAULT_CACHE_NAME), cacheIds.get(0)); + assertTrue(txStartTime >= startTime); + assertTrue(duration >= 0); + assertEquals(commited, txCommited); + } + }); + + assertEquals(1, txs.get()); + } +} diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/performancestatistics/StringCacheTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/performancestatistics/StringCacheTest.java new file mode 100644 index 0000000000000..d423bf207f5ce --- /dev/null +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/performancestatistics/StringCacheTest.java @@ -0,0 +1,81 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.ignite.internal.processors.performancestatistics; + +import java.io.File; +import java.util.List; +import java.util.UUID; +import java.util.concurrent.atomic.AtomicInteger; +import org.apache.ignite.internal.IgniteEx; +import org.apache.ignite.lang.IgniteRunnable; +import org.apache.ignite.lang.IgniteUuid; +import org.junit.Test; + +import static org.apache.ignite.internal.processors.performancestatistics.OperationType.cacheStartRecordSize; +import static org.apache.ignite.internal.processors.performancestatistics.OperationType.jobRecordSize; +import static org.apache.ignite.internal.processors.performancestatistics.OperationType.taskRecordSize; + +/** + * Tests strings caching. + */ +public class StringCacheTest extends AbstractPerformanceStatisticsTest { + /** @throws Exception If failed. */ + @Test + public void testCacheTaskName() throws Exception { + IgniteEx ignite = startGrid(0); + + String testTaskName = "TestTask"; + int executions = 5; + + startCollectStatistics(); + + for (int i = 0; i < executions; i++) { + ignite.compute().withName(testTaskName).run(new IgniteRunnable() { + @Override public void run() { + // No-op. + } + }); + } + + AtomicInteger tasks = new AtomicInteger(); + + stopCollectStatisticsAndRead(new TestHandler() { + @Override public void task(UUID nodeId, IgniteUuid sesId, String taskName, long startTime, long duration, + int affPartId) { + if (testTaskName.equals(taskName)) + tasks.incrementAndGet(); + } + }); + + assertEquals(executions, tasks.get()); + + long expLen = taskRecordSize(testTaskName.getBytes().length, false) + + taskRecordSize(0, true) * (executions - 1) + + jobRecordSize() * executions + + /*opType*/ 2 * executions; + + // Started caches. + expLen += ignite.context().cache().cacheDescriptors().values().stream().mapToInt( + desc -> 1 + cacheStartRecordSize(desc.cacheName().getBytes().length, false)).sum(); + + List files = statisticsFiles(); + + assertEquals(1, files.size()); + assertEquals(expLen, files.get(0).length()); + } +} diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/performancestatistics/TopologyChangesTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/performancestatistics/TopologyChangesTest.java new file mode 100644 index 0000000000000..856b3f2471bb3 --- /dev/null +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/performancestatistics/TopologyChangesTest.java @@ -0,0 +1,137 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.ignite.internal.processors.performancestatistics; + +import java.util.Arrays; +import java.util.Collection; +import org.apache.ignite.cluster.ClusterState; +import org.apache.ignite.configuration.DataRegionConfiguration; +import org.apache.ignite.configuration.DataStorageConfiguration; +import org.apache.ignite.configuration.IgniteConfiguration; +import org.apache.ignite.internal.IgniteEx; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +/** + * Tests topology changes during collecting performance statistics. + */ +@RunWith(Parameterized.class) +public class TopologyChangesTest extends AbstractPerformanceStatisticsTest { + /** Persistence enabled flag. */ + @Parameterized.Parameter + public boolean persistence; + + /** @return Test parameters. */ + @Parameterized.Parameters(name = "persistence={0}") + public static Collection parameters() { + return Arrays.asList(new Object[][] {{false}, {true}}); + } + + /** {@inheritDoc} */ + @Override protected IgniteConfiguration getConfiguration(String igniteInstanceName) throws Exception { + IgniteConfiguration cfg = super.getConfiguration(igniteInstanceName); + + cfg.setDataStorageConfiguration( + new DataStorageConfiguration() + .setDefaultDataRegionConfiguration( + new DataRegionConfiguration().setPersistenceEnabled(persistence) + ) + ); + + return cfg; + } + + /** {@inheritDoc} */ + @Override protected void beforeTest() throws Exception { + super.beforeTest(); + + stopAllGrids(); + + if (persistence) + cleanPersistenceDir(); + } + + /** {@inheritDoc} */ + @Override protected void afterTest() throws Exception { + super.afterTest(); + + stopAllGrids(); + + if (persistence) + cleanPersistenceDir(); + } + + /** @throws Exception If failed. */ + @Test + public void testNodeJoin() throws Exception { + startGrid(0); + + startCollectStatistics(); + + startGrid(1); + + waitForStatisticsEnabled(true); + } + + /** @throws Exception If failed. */ + @Test + public void testClusterRestartWithPersistence() throws Exception { + startGrids(2); + + if (persistence) + grid(0).cluster().state(ClusterState.ACTIVE); + + startCollectStatistics(); + + stopAllGrids(false); + + startGrids(2); + + if (persistence) + grid(0).cluster().state(ClusterState.ACTIVE); + + waitForStatisticsEnabled(persistence); + } + + /** @throws Exception If failed. */ + @Test + public void testClientReconnected() throws Exception { + IgniteEx grid = startGrid(0); + + if (persistence) + grid.cluster().state(ClusterState.ACTIVE); + + startCollectStatistics(); + + startClientGrid(1); + + waitForStatisticsEnabled(true); + + stopGrid(0); + + grid = startGrid(0); + + if (persistence) + grid.cluster().state(ClusterState.ACTIVE); + + waitForTopology(2); + + waitForStatisticsEnabled(persistence); + } +} diff --git a/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteBasicWithPersistenceTestSuite.java b/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteBasicWithPersistenceTestSuite.java index 6addefe5f9891..2f1ba8f5ee625 100644 --- a/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteBasicWithPersistenceTestSuite.java +++ b/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteBasicWithPersistenceTestSuite.java @@ -41,6 +41,13 @@ import org.apache.ignite.internal.processors.cache.persistence.snapshot.IgniteClusterSnapshotSelfTest; import org.apache.ignite.internal.processors.cache.persistence.snapshot.IgniteSnapshotMXBeanTest; import org.apache.ignite.internal.processors.cache.persistence.snapshot.IgniteSnapshotManagerSelfTest; +import org.apache.ignite.internal.processors.performancestatistics.ForwardReadTest; +import org.apache.ignite.internal.processors.performancestatistics.PerformanceStatisticsMultipleStartTest; +import org.apache.ignite.internal.processors.performancestatistics.PerformanceStatisticsPropertiesTest; +import org.apache.ignite.internal.processors.performancestatistics.PerformanceStatisticsSelfTest; +import org.apache.ignite.internal.processors.performancestatistics.PerformanceStatisticsThinClientTest; +import org.apache.ignite.internal.processors.performancestatistics.StringCacheTest; +import org.apache.ignite.internal.processors.performancestatistics.TopologyChangesTest; import org.apache.ignite.marshaller.GridMarshallerMappingConsistencyTest; import org.apache.ignite.util.GridInternalTaskUnusedWalSegmentsTest; import org.junit.runner.RunWith; @@ -85,7 +92,16 @@ IgniteClusterSnapshotSelfTest.class, IgniteSnapshotMXBeanTest.class, - IgniteClusterIdTagTest.class + IgniteClusterIdTagTest.class, + + PerformanceStatisticsSelfTest.class, + PerformanceStatisticsThinClientTest.class, + TopologyChangesTest.class, + IgniteClusterIdTagTest.class, + StringCacheTest.class, + PerformanceStatisticsPropertiesTest.class, + PerformanceStatisticsMultipleStartTest.class, + ForwardReadTest.class }) public class IgniteBasicWithPersistenceTestSuite { } diff --git a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/twostep/GridMapQueryExecutor.java b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/twostep/GridMapQueryExecutor.java index c5dcc6801118a..aa790942e6665 100644 --- a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/twostep/GridMapQueryExecutor.java +++ b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/twostep/GridMapQueryExecutor.java @@ -44,10 +44,13 @@ import org.apache.ignite.events.DiscoveryEvent; import org.apache.ignite.internal.GridKernalContext; import org.apache.ignite.internal.GridTopic; +import org.apache.ignite.internal.metric.IoStatisticsHolder; +import org.apache.ignite.internal.metric.IoStatisticsQueryHelper; import org.apache.ignite.internal.processors.affinity.AffinityTopologyVersion; import org.apache.ignite.internal.processors.cache.GridCacheContext; import org.apache.ignite.internal.processors.cache.mvcc.MvccSnapshot; import org.apache.ignite.internal.processors.cache.query.CacheQueryType; +import org.apache.ignite.internal.processors.cache.query.GridCacheQueryType; import org.apache.ignite.internal.processors.cache.query.GridCacheSqlQuery; import org.apache.ignite.internal.processors.query.GridQueryCancel; import org.apache.ignite.internal.processors.query.h2.H2PooledConnection; @@ -320,6 +323,11 @@ private void onQueryRequest0( @Nullable final MvccSnapshot mvccSnapshot, Boolean dataPageScanEnabled ) { + boolean performanceStatsEnabled = ctx.performanceStatistics().enabled(); + + if (performanceStatsEnabled) + IoStatisticsQueryHelper.startGatheringQueryStatistics(); + // Prepare to run queries. GridCacheContext mainCctx = mainCacheContext(cacheIds); @@ -555,6 +563,19 @@ private void onQueryRequest0( if (trace != null) trace.close(); + + if (performanceStatsEnabled) { + IoStatisticsHolder stat = IoStatisticsQueryHelper.finishGatheringQueryStatistics(); + + if (stat.logicalReads() > 0 || stat.physicalReads() > 0) { + ctx.performanceStatistics().queryReads( + GridCacheQueryType.SQL_FIELDS, + node.id(), + reqId, + stat.logicalReads(), + stat.physicalReads()); + } + } } } diff --git a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/twostep/GridReduceQueryExecutor.java b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/twostep/GridReduceQueryExecutor.java index cd4910d9a3da9..ea02bc38640e6 100644 --- a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/twostep/GridReduceQueryExecutor.java +++ b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/twostep/GridReduceQueryExecutor.java @@ -419,6 +419,8 @@ public Iterator> query( final long qryReqId = qryIdGen.incrementAndGet(); + h2.runningQueryManager().trackRequestId(qryReqId); + boolean retry = false; boolean release = true; @@ -877,6 +879,8 @@ public UpdateResult update( final long reqId = qryIdGen.incrementAndGet(); + h2.runningQueryManager().trackRequestId(reqId); + final DmlDistributedUpdateRun r = new DmlDistributedUpdateRun(nodes.size()); int flags = enforceJoinOrder ? GridH2QueryRequest.FLAG_ENFORCE_JOIN_ORDER : 0; diff --git a/modules/indexing/src/test/java/org/apache/ignite/internal/processors/performancestatistics/PerformanceStatisticsQueryTest.java b/modules/indexing/src/test/java/org/apache/ignite/internal/processors/performancestatistics/PerformanceStatisticsQueryTest.java new file mode 100644 index 0000000000000..2cd63dbc0f297 --- /dev/null +++ b/modules/indexing/src/test/java/org/apache/ignite/internal/processors/performancestatistics/PerformanceStatisticsQueryTest.java @@ -0,0 +1,356 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.ignite.internal.processors.performancestatistics; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.atomic.AtomicInteger; +import org.apache.ignite.IgniteCache; +import org.apache.ignite.Ignition; +import org.apache.ignite.cache.QueryEntity; +import org.apache.ignite.cache.query.FieldsQueryCursor; +import org.apache.ignite.cache.query.Query; +import org.apache.ignite.cache.query.ScanQuery; +import org.apache.ignite.cache.query.SqlFieldsQuery; +import org.apache.ignite.client.Config; +import org.apache.ignite.client.IgniteClient; +import org.apache.ignite.configuration.CacheConfiguration; +import org.apache.ignite.configuration.ClientConfiguration; +import org.apache.ignite.configuration.DataRegionConfiguration; +import org.apache.ignite.configuration.DataStorageConfiguration; +import org.apache.ignite.configuration.IgniteConfiguration; +import org.apache.ignite.internal.IgniteEx; +import org.apache.ignite.internal.processors.cache.query.GridCacheQueryType; +import org.apache.ignite.internal.util.typedef.F; +import org.apache.ignite.internal.util.typedef.internal.U; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import static org.apache.ignite.cluster.ClusterState.ACTIVE; +import static org.apache.ignite.cluster.ClusterState.INACTIVE; +import static org.apache.ignite.internal.processors.cache.query.GridCacheQueryType.SCAN; +import static org.apache.ignite.internal.processors.cache.query.GridCacheQueryType.SQL_FIELDS; +import static org.apache.ignite.internal.processors.performancestatistics.AbstractPerformanceStatisticsTest.ClientType.CLIENT; +import static org.apache.ignite.internal.processors.performancestatistics.AbstractPerformanceStatisticsTest.ClientType.SERVER; +import static org.apache.ignite.internal.processors.performancestatistics.AbstractPerformanceStatisticsTest.ClientType.THIN_CLIENT; +import static org.apache.ignite.internal.processors.query.QueryUtils.DFLT_SCHEMA; +import static org.junit.Assume.assumeFalse; + +/** Tests query performance statistics. */ +@RunWith(Parameterized.class) +public class PerformanceStatisticsQueryTest extends AbstractPerformanceStatisticsTest { + /** Cache entry count. */ + private static final int ENTRY_COUNT = 100; + + /** Test cache 2 name. */ + private static final String CACHE_2 = "cache2"; + + /** Test SQL table name. */ + private static final String SQL_TABLE = "test"; + + /** Page size. */ + @Parameterized.Parameter + public int pageSize; + + /** Client type to run queries from. */ + @Parameterized.Parameter(1) + public ClientType clientType; + + /** @return Test parameters. */ + @Parameterized.Parameters(name = "pageSize={0}, clientType={1}") + public static Collection parameters() { + List res = new ArrayList<>(); + + for (Integer pageSize : new Integer[] {ENTRY_COUNT, ENTRY_COUNT / 10}) { + for (ClientType clientType : new ClientType[] {SERVER, CLIENT, THIN_CLIENT}) + res.add(new Object[] {pageSize, clientType}); + } + + return res; + } + + /** Server. */ + private static IgniteEx srv; + + /** Client. */ + private static IgniteEx client; + + /** Thin client. */ + private static IgniteClient thinClient; + + /** Cache. */ + private static IgniteCache cache; + + /** {@inheritDoc} */ + @Override protected IgniteConfiguration getConfiguration(String igniteInstanceName) throws Exception { + IgniteConfiguration cfg = super.getConfiguration(igniteInstanceName); + + cfg.setDataStorageConfiguration(new DataStorageConfiguration() + .setDefaultDataRegionConfiguration( + new DataRegionConfiguration() + .setPersistenceEnabled(true))); + + return cfg; + } + + /** {@inheritDoc} */ + @Override protected void beforeTestsStarted() throws Exception { + super.beforeTestsStarted(); + + cleanPersistenceDir(); + + srv = startGrids(2); + + thinClient = Ignition.startClient(new ClientConfiguration().setAddresses(Config.SERVER)); + + client = startClientGrid("client"); + + client.cluster().state(ACTIVE); + + cache = client.getOrCreateCache(new CacheConfiguration() + .setName(DEFAULT_CACHE_NAME) + .setSqlSchema(DFLT_SCHEMA) + .setQueryEntities(Collections.singletonList( + new QueryEntity(Integer.class, Integer.class) + .setTableName(DEFAULT_CACHE_NAME))) + ); + + IgniteCache cache2 = client.getOrCreateCache(new CacheConfiguration<>() + .setName(CACHE_2) + .setSqlSchema(DFLT_SCHEMA) + .setQueryEntities(Collections.singletonList( + new QueryEntity(Long.class, Long.class) + .setTableName(CACHE_2))) + ); + + for (int i = 0; i < ENTRY_COUNT; i++) { + cache.put(i, i); + cache2.put(i, i * 2); + } + } + + /** {@inheritDoc} */ + @Override protected void afterTestsStopped() throws Exception { + super.afterTestsStopped(); + + cleanPersistenceDir(); + + thinClient.close(); + } + + /** {@inheritDoc} */ + @Override protected void afterTest() throws Exception { + super.afterTest(); + + cache.query(new SqlFieldsQuery("drop table if exists " + SQL_TABLE)); + } + + /** @throws Exception If failed. */ + @Test + public void testScanQuery() throws Exception { + ScanQuery qry = new ScanQuery<>().setPageSize(pageSize); + + checkQuery(SCAN, qry, DEFAULT_CACHE_NAME); + } + + /** @throws Exception If failed. */ + @Test + public void testSqlFieldsQuery() throws Exception { + String sql = "select * from " + DEFAULT_CACHE_NAME; + + SqlFieldsQuery qry = new SqlFieldsQuery(sql).setPageSize(pageSize); + + checkQuery(SQL_FIELDS, qry, sql); + } + + /** @throws Exception If failed. */ + @Test + public void testSqlFieldsJoinQuery() throws Exception { + String sql = "select * from " + DEFAULT_CACHE_NAME + " a inner join " + CACHE_2 + " b on a._key = b._key"; + + SqlFieldsQuery qry = new SqlFieldsQuery(sql).setPageSize(pageSize); + + checkQuery(SQL_FIELDS, qry, sql); + } + + /** Check query. */ + private void checkQuery(GridCacheQueryType type, Query qry, String text) throws Exception { + client.cluster().state(INACTIVE); + client.cluster().state(ACTIVE); + + runQueryAndCheck(type, qry, text, true, true); + + runQueryAndCheck(type, qry, text, true, false); + } + + /** @throws Exception If failed. */ + @Test + public void testDdlAndDmlQueries() throws Exception { + String sql = "create table " + SQL_TABLE + " (id int, val varchar, primary key (id))"; + + runQueryAndCheck(SQL_FIELDS, new SqlFieldsQuery(sql), sql, false, false); + + sql = "insert into " + SQL_TABLE + " (id) values (1)"; + + runQueryAndCheck(SQL_FIELDS, new SqlFieldsQuery(sql), sql, false, false); + + sql = "update " + SQL_TABLE + " set val = 'abc'"; + + runQueryAndCheck(SQL_FIELDS, new SqlFieldsQuery(sql), sql, true, false); + } + + /** Runs query and checks statistics. */ + private void runQueryAndCheck(GridCacheQueryType expType, Query qry, String expText, boolean hasLogicalReads, + boolean hasPhysicalReads) + throws Exception { + long startTime = U.currentTimeMillis(); + + cleanPerformanceStatisticsDir(); + + startCollectStatistics(); + + Collection expNodeIds = new ArrayList<>(); + + if (clientType == SERVER) { + srv.cache(DEFAULT_CACHE_NAME).query(qry).getAll(); + + expNodeIds.add(srv.localNode().id()); + } + else if (clientType == CLIENT) { + client.cache(DEFAULT_CACHE_NAME).query(qry).getAll(); + + expNodeIds.add(client.localNode().id()); + } + else if (clientType == THIN_CLIENT) { + thinClient.cache(DEFAULT_CACHE_NAME).query(qry).getAll(); + + expNodeIds.addAll(F.nodeIds(client.cluster().forServers().nodes())); + } + + Set readsNodes = new HashSet<>(); + + if (hasLogicalReads) + srv.cluster().forServers().nodes().forEach(node -> readsNodes.add(node.id())); + + AtomicInteger queryCnt = new AtomicInteger(); + AtomicInteger readsCnt = new AtomicInteger(); + HashSet qryIds = new HashSet<>(); + + stopCollectStatisticsAndRead(new TestHandler() { + @Override public void query(UUID nodeId, GridCacheQueryType type, String text, long id, long queryStartTime, + long duration, boolean success) { + queryCnt.incrementAndGet(); + qryIds.add(id); + + assertTrue(expNodeIds.contains(nodeId)); + assertEquals(expType, type); + assertEquals(expText, text); + assertTrue(queryStartTime >= startTime); + assertTrue(duration >= 0); + assertTrue(success); + } + + @Override public void queryReads(UUID nodeId, GridCacheQueryType type, UUID queryNodeId, long id, + long logicalReads, long physicalReads) { + readsCnt.incrementAndGet(); + qryIds.add(id); + readsNodes.remove(nodeId); + + assertTrue(expNodeIds.contains(queryNodeId)); + assertEquals(expType, type); + assertTrue(logicalReads > 0); + assertTrue(hasPhysicalReads ? physicalReads > 0 : physicalReads == 0); + } + }); + + assertEquals(1, queryCnt.get()); + assertTrue("Query reads expected on nodes: " + readsNodes, readsNodes.isEmpty()); + assertEquals(1, qryIds.size()); + } + + /** @throws Exception If failed. */ + @Test + public void testMultipleStatementsSql() throws Exception { + assumeFalse("Multiple statements queries are not supported by thin client.", + clientType == THIN_CLIENT); + + long startTime = U.currentTimeMillis(); + + LinkedList expQrs = new LinkedList<>(); + + expQrs.add("create table " + SQL_TABLE + " (id int primary key, val varchar)"); + expQrs.add("insert into " + SQL_TABLE + " (id, val) values (1, 'a')"); + expQrs.add("insert into " + SQL_TABLE + " (id, val) values (2, 'b'), (3, 'c')"); + + LinkedList qrsWithReads = new LinkedList<>(); + + qrsWithReads.add("update " + SQL_TABLE + " set val = 'd' where id = 1"); + qrsWithReads.add("select * from " + SQL_TABLE); + + expQrs.addAll(qrsWithReads); + + startCollectStatistics(); + + SqlFieldsQuery qry = new SqlFieldsQuery(F.concat(expQrs, ";")); + + IgniteEx loadNode = this.clientType == SERVER ? srv : client; + + List>> res = loadNode.context().query().querySqlFields(qry, true, false); + + assertEquals("Unexpected cursors count: " + res.size(), expQrs.size(), res.size()); + + res.get(4).getAll(); + + HashSet qryIds = new HashSet<>(); + + stopCollectStatisticsAndRead(new TestHandler() { + @Override public void query(UUID nodeId, GridCacheQueryType type, String text, long id, long queryStartTime, + long duration, boolean success) { + if (qrsWithReads.contains(text)) + qryIds.add(id); + + assertEquals(loadNode.localNode().id(), nodeId); + assertEquals(SQL_FIELDS, type); + assertTrue("Unexpected query: " + text, expQrs.remove(text)); + assertTrue(queryStartTime >= startTime); + assertTrue(duration >= 0); + assertTrue(success); + } + + @Override public void queryReads(UUID nodeId, GridCacheQueryType type, UUID queryNodeId, long id, + long logicalReads, long physicalReads) { + qryIds.add(id); + + assertEquals(SQL_FIELDS, type); + assertEquals(loadNode.localNode().id(), queryNodeId); + assertTrue(logicalReads > 0); + assertEquals(0, physicalReads); + } + }); + + assertTrue("Queries was not handled: " + expQrs, expQrs.isEmpty()); + assertEquals("Unexpected IDs: " + qryIds, qrsWithReads.size(), qryIds.size()); + } +} diff --git a/modules/indexing/src/test/java/org/apache/ignite/testsuites/IgniteCacheQuerySelfTestSuite6.java b/modules/indexing/src/test/java/org/apache/ignite/testsuites/IgniteCacheQuerySelfTestSuite6.java index c2b71066d39b9..da2b6961659f8 100644 --- a/modules/indexing/src/test/java/org/apache/ignite/testsuites/IgniteCacheQuerySelfTestSuite6.java +++ b/modules/indexing/src/test/java/org/apache/ignite/testsuites/IgniteCacheQuerySelfTestSuite6.java @@ -38,6 +38,7 @@ import org.apache.ignite.internal.processors.cache.query.continuous.GridCacheContinuousQueryReplicatedAtomicOneNodeTest; import org.apache.ignite.internal.processors.cache.query.continuous.GridCacheContinuousQueryReplicatedAtomicSelfTest; import org.apache.ignite.internal.processors.cache.query.continuous.IgniteCacheContinuousQueryClientTest; +import org.apache.ignite.internal.processors.performancestatistics.PerformanceStatisticsQueryTest; import org.apache.ignite.internal.processors.query.MemLeakOnSqlWithClientReconnectTest; import org.junit.runner.RunWith; import org.junit.runners.Suite; @@ -68,7 +69,8 @@ StaticCacheDdlTest.class, StaticCacheDdlKeepStaticConfigurationTest.class, MemLeakOnSqlWithClientReconnectTest.class, - CacheContinuousQueryFilterDeploymentFailedTest.class + CacheContinuousQueryFilterDeploymentFailedTest.class, + PerformanceStatisticsQueryTest.class }) public class IgniteCacheQuerySelfTestSuite6 { }