This is an automated email from the ASF dual-hosted git repository.

asf-gitbox-commits pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ignite.git


The following commit(s) were added to refs/heads/master by this push:
     new 60f0f5697fb IGNITE-28833 Add system views for cache locks - Fixes 
#13347.
60f0f5697fb is described below

commit 60f0f5697fb814753605bb6c88c53f2255deb4da
Author: Aleksey Plekhanov <[email protected]>
AuthorDate: Fri Jul 17 09:16:18 2026 +0300

    IGNITE-28833 Add system views for cache locks - Fixes #13347.
    
    Signed-off-by: Aleksey Plekhanov <[email protected]>
---
 docs/_docs/monitoring-metrics/system-views.adoc    |  34 ++
 .../apache/ignite/internal/binary/BinaryUtils.java |   4 +-
 .../internal/jdbc2/JdbcMetadataSelfTest.java       |   4 +-
 .../ignite/jdbc/thin/JdbcThinMetadataSelfTest.java |  22 +-
 .../apache/ignite/util/SystemViewCommandTest.java  |   4 +-
 .../cache/GridCacheExplicitLockSpan.java           |  16 +
 .../internal/processors/cache/GridCacheMvcc.java   |  36 +-
 .../processors/cache/GridCacheMvccManager.java     |  50 +++
 .../spi/systemview/view/CacheExplicitLockView.java |  73 ++++
 .../ignite/spi/systemview/view/CacheLockView.java  | 115 ++++++
 .../spi/systemview/view/TransactionView.java       |  10 +-
 .../internal/metric/SystemViewLocksTest.java       | 446 +++++++++++++++++++++
 .../ignite/testsuites/IgniteCacheTestSuite13.java  |   2 +
 .../cache/metric/SqlViewExporterSpiTest.java       |   4 +-
 .../processors/query/SqlSystemViewsSelfTest.java   |  55 +++
 15 files changed, 863 insertions(+), 12 deletions(-)

diff --git a/docs/_docs/monitoring-metrics/system-views.adoc 
b/docs/_docs/monitoring-metrics/system-views.adoc
index b6c684e513b..9f3a4fc7424 100644
--- a/docs/_docs/monitoring-metrics/system-views.adoc
+++ b/docs/_docs/monitoring-metrics/system-views.adoc
@@ -275,6 +275,7 @@ Each row in this view represents a transaction object on 
the node where the view
 |ORIGINATING_NODE_ID | UUID | ID of the node that initiated the current 
transaction object. For a transaction object mapped to a primary partition, 
this is the transaction initiator node; for a transaction object mapped to a 
backup partition, this is the node that owns the primary partition
 |STATE | string | Current transaction state
 |XID | UUID | Unique transaction identifier
+|ORIGINATING_XID | UUID | Ttransaction ID on originating node
 |LABEL | string | Transaction label
 |START_TIME | long | Start time of the transaction on this node
 |ISOLATION | string | Transaction isolation level
@@ -299,6 +300,39 @@ Each row in this view represents a transaction object on 
the node where the view
 |TOP_VER | string | Topology version assigned to the transaction. If not 
assigned explicitly, it is a topology version of the latest partition exchange.
 |===
 
+== CACHE_EXPLICIT_LOCKS
+
+This view exposes information about explicit locks acquired on the current 
node.
+Explicit locks are acquired using the `Cache.lock(key)` API.
+
+[{table_opts}]
+|===
+|NAME | TYPE |    DESCRIPTION
+|CACHE_ID | int | Cache ID
+|KEY | string | Locked key
+|THREAD_ID | long | ID of the thread that acquired the lock
+|XID | UUID | Lock ID (unique identifier)
+|===
+
+== CACHE_LOCKS
+
+This view exposes information about all locks (both explicit and 
transactional) requested for keys hosted on the current node.
+This includes locks from remote nodes trying to access primary keys on this 
node.
+
+[{table_opts}]
+|===
+|NAME | TYPE |    DESCRIPTION
+|CACHE_ID | int | Cache ID
+|KEY | string | Locked key
+|NODE_ID | UUID | ID of the node where the lock is hosted
+|ORIGINATING_NODE_ID | UUID | ID of the node that initiated the lock request
+|IS_OWNER | boolean | `True` if the lock is currently owned
+|IS_TX | boolean | `True` if the lock is part of a transaction
+|FLAGS | short | All flags for the lock. This field represents a bit mask with 
possible values: LOCAL(0x01), OWNER(0x02), READY(0x04), REENTRY(0x08), 
USED(0x10), TX(0x40), SINGLE_IMPLICIT(0x80), DHT_LOCAL(0x100), 
NEAR_LOCAL(0x200), REMOVED(0x400), READ(0x800);
+|XID | UUID | Current transaction ID (or explicit lock ID)
+|ORIGINATING_XID | UUID | Originating transaction ID (or explicit lock ID)
+|===
+
 == NODES
 
 
diff --git 
a/modules/binary/api/src/main/java/org/apache/ignite/internal/binary/BinaryUtils.java
 
b/modules/binary/api/src/main/java/org/apache/ignite/internal/binary/BinaryUtils.java
index fcbd74fe0c5..14a97520c81 100644
--- 
a/modules/binary/api/src/main/java/org/apache/ignite/internal/binary/BinaryUtils.java
+++ 
b/modules/binary/api/src/main/java/org/apache/ignite/internal/binary/BinaryUtils.java
@@ -3291,7 +3291,7 @@ public class BinaryUtils {
      * @param ver Cache version.
      * @return Version represented as {@code IgniteUuid}.
      */
-    public static IgniteUuid asIgniteUuid(GridCacheVersion ver) {
-        return new IgniteUuid(new UUID(ver.topologyVersion(), 
ver.nodeOrderAndDrIdRaw()), ver.order());
+    public static @Nullable IgniteUuid asIgniteUuid(@Nullable GridCacheVersion 
ver) {
+        return ver == null ? null : new IgniteUuid(new 
UUID(ver.topologyVersion(), ver.nodeOrderAndDrIdRaw()), ver.order());
     }
 }
diff --git 
a/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcMetadataSelfTest.java
 
b/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcMetadataSelfTest.java
index 3f989f2e6ae..0604f506b8c 100755
--- 
a/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcMetadataSelfTest.java
+++ 
b/modules/clients/src/test/java/org/apache/ignite/internal/jdbc2/JdbcMetadataSelfTest.java
@@ -369,7 +369,9 @@ public class JdbcMetadataSelfTest extends 
GridCommonAbstractTest {
             "STATISTICS_LOCAL_DATA",
             "STATISTICS_GLOBAL_DATA",
             "SQL_PLANS_HISTORY",
-            "IGNITE_PLUGINS"
+            "IGNITE_PLUGINS",
+            "CACHE_EXPLICIT_LOCKS",
+            "CACHE_LOCKS"
         ));
 
         Set<String> actViews = new TreeSet<>();
diff --git 
a/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinMetadataSelfTest.java
 
b/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinMetadataSelfTest.java
index b1bd8246ff9..075c75161cf 100644
--- 
a/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinMetadataSelfTest.java
+++ 
b/modules/clients/src/test/java/org/apache/ignite/jdbc/thin/JdbcThinMetadataSelfTest.java
@@ -466,7 +466,9 @@ public class JdbcThinMetadataSelfTest extends 
JdbcThinAbstractSelfTest {
                 "SYS.STATISTICS_CONFIGURATION",
                 "SYS.PAGES_TIMESTAMP_HISTOGRAM",
                 "SYS.SQL_PLANS_HISTORY",
-                "SYS.IGNITE_PLUGINS"
+                "SYS.IGNITE_PLUGINS",
+                "SYS.CACHE_EXPLICIT_LOCKS",
+                "SYS.CACHE_LOCKS"
             ))
         );
     }
@@ -955,6 +957,7 @@ public class JdbcThinMetadataSelfTest extends 
JdbcThinAbstractSelfTest {
                 "SYS.TRANSACTIONS.LOCAL_NODE_ID.null",
                 "SYS.TRANSACTIONS.STATE.null",
                 "SYS.TRANSACTIONS.XID.null",
+                "SYS.TRANSACTIONS.ORIGINATING_XID.null",
                 "SYS.TRANSACTIONS.LABEL.null",
                 "SYS.TRANSACTIONS.START_TIME.null",
                 "SYS.TRANSACTIONS.ISOLATION.null",
@@ -1178,8 +1181,21 @@ public class JdbcThinMetadataSelfTest extends 
JdbcThinAbstractSelfTest {
                 "SYS.IGNITE_PLUGINS.NAME.null",
                 "SYS.IGNITE_PLUGINS.INFO.null",
                 "SYS.IGNITE_PLUGINS.VERSION.null",
-                "SYS.IGNITE_PLUGINS.CLASS_NAME.null"
-                ));
+                "SYS.IGNITE_PLUGINS.CLASS_NAME.null",
+                "SYS.CACHE_EXPLICIT_LOCKS.CACHE_ID.null",
+                "SYS.CACHE_EXPLICIT_LOCKS.KEY.null",
+                "SYS.CACHE_EXPLICIT_LOCKS.THREAD_ID.null",
+                "SYS.CACHE_EXPLICIT_LOCKS.XID.null",
+                "SYS.CACHE_LOCKS.CACHE_ID.null",
+                "SYS.CACHE_LOCKS.KEY.null",
+                "SYS.CACHE_LOCKS.NODE_ID.null",
+                "SYS.CACHE_LOCKS.ORIGINATING_NODE_ID.null",
+                "SYS.CACHE_LOCKS.IS_OWNER.null",
+                "SYS.CACHE_LOCKS.IS_TX.null",
+                "SYS.CACHE_LOCKS.FLAGS.null",
+                "SYS.CACHE_LOCKS.XID.null",
+                "SYS.CACHE_LOCKS.ORIGINATING_XID.null"
+            ));
 
             Assert.assertEquals(expectedCols, actualSysCols);
         }
diff --git 
a/modules/control-utility/src/test/java/org/apache/ignite/util/SystemViewCommandTest.java
 
b/modules/control-utility/src/test/java/org/apache/ignite/util/SystemViewCommandTest.java
index d927b361a75..42bbfa10683 100644
--- 
a/modules/control-utility/src/test/java/org/apache/ignite/util/SystemViewCommandTest.java
+++ 
b/modules/control-utility/src/test/java/org/apache/ignite/util/SystemViewCommandTest.java
@@ -502,7 +502,9 @@ public class SystemViewCommandTest extends 
GridCommandHandlerClusterByClassAbstr
             "DS_QUEUES",
             "PAGES_TIMESTAMP_HISTOGRAM",
             "SQL_PLANS_HISTORY",
-            "IGNITE_PLUGINS"
+            "IGNITE_PLUGINS",
+            "CACHE_EXPLICIT_LOCKS",
+            "CACHE_LOCKS"
         ));
 
         Set<String> viewNames = new TreeSet<>();
diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheExplicitLockSpan.java
 
b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheExplicitLockSpan.java
index e0c6d9b4240..53c8ad6f577 100644
--- 
a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheExplicitLockSpan.java
+++ 
b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheExplicitLockSpan.java
@@ -17,6 +17,8 @@
 
 package org.apache.ignite.internal.processors.cache;
 
+import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Deque;
 import java.util.HashMap;
 import java.util.Iterator;
@@ -216,6 +218,20 @@ public class GridCacheExplicitLockSpan extends 
ReentrantLock {
         }
     }
 
+    /**
+     * @return Lock candidates.
+     */
+    public Collection<GridCacheMvccCandidate> candidates() {
+        lock();
+
+        try {
+            return new ArrayList<>(F.flatCollections(cands.values()));
+        }
+        finally {
+            unlock();
+        }
+    }
+
     /**
      * Marks all candidates added for given key as owned.
      *
diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheMvcc.java
 
b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheMvcc.java
index 984c468f8ce..541f360e7d9 100644
--- 
a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheMvcc.java
+++ 
b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheMvcc.java
@@ -1372,6 +1372,19 @@ public final class GridCacheMvcc {
         return candidates(locs, reentry, false, cctx.emptyVersion());
     }
 
+    /**
+     * @param dst Destination collection.
+     * @return Copied collection of local candidates.
+     */
+    public List<GridCacheMvccCandidate> 
localCandidatesCopy(List<GridCacheMvccCandidate> dst) {
+        List<GridCacheMvccCandidate> src = locs;
+
+        if (src == null)
+            return Collections.emptyList();
+
+        return candidatesCopy(src, dst, false);
+    }
+
     /**
      * @param excludeVers Exclude versions.
      * @return Collection of local candidates.
@@ -1417,13 +1430,30 @@ public final class GridCacheMvcc {
 
         List<GridCacheMvccCandidate> cands = new ArrayList<>(col.size());
 
-        for (GridCacheMvccCandidate c : col) {
+        return candidatesCopy(col, cands, reentries, excludeVers);
+    }
+
+    /**
+     * Copy candidates collection from {@code src} to {@code dst}.
+     * @param src Source collection.
+     * @param dst Destination collection.
+     * @param reentries Flag to include reentries.
+     * @param excludeVers Exclude versions.
+     * @return Copied collection of candidates.
+     */
+    private List<GridCacheMvccCandidate> candidatesCopy(
+        List<GridCacheMvccCandidate> src,
+        List<GridCacheMvccCandidate> dst,
+        boolean reentries,
+        GridCacheVersion... excludeVers
+    ) {
+        for (GridCacheMvccCandidate c : src) {
             // Don't include reentries.
             if ((reentries || !c.reentry()) && 
!U.containsObjectArray(excludeVers, c.version()))
-                cands.add(c);
+                dst.add(c);
         }
 
-        return cands;
+        return dst;
     }
 
     /**
diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheMvccManager.java
 
b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheMvccManager.java
index 9ca50367803..9fe3462af05 100644
--- 
a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheMvccManager.java
+++ 
b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheMvccManager.java
@@ -20,10 +20,12 @@ package org.apache.ignite.internal.processors.cache;
 import java.util.ArrayDeque;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.Deque;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.UUID;
@@ -49,6 +51,8 @@ import 
org.apache.ignite.internal.processors.cache.distributed.dht.GridDhtTxFini
 import 
org.apache.ignite.internal.processors.cache.transactions.IgniteInternalTx;
 import org.apache.ignite.internal.processors.cache.transactions.IgniteTxKey;
 import org.apache.ignite.internal.processors.cache.version.GridCacheVersion;
+import org.apache.ignite.internal.systemview.CacheExplicitLockViewWalker;
+import org.apache.ignite.internal.systemview.CacheLockViewWalker;
 import org.apache.ignite.internal.util.GridBoundedConcurrentLinkedHashSet;
 import org.apache.ignite.internal.util.GridConcurrentFactory;
 import org.apache.ignite.internal.util.GridConcurrentHashSet;
@@ -67,6 +71,8 @@ import org.apache.ignite.internal.util.typedef.internal.U;
 import org.apache.ignite.lang.IgniteFuture;
 import org.apache.ignite.lang.IgnitePredicate;
 import org.apache.ignite.lang.IgniteUuid;
+import org.apache.ignite.spi.systemview.view.CacheExplicitLockView;
+import org.apache.ignite.spi.systemview.view.CacheLockView;
 import org.apache.ignite.util.deque.FastSizeDeque;
 import org.jetbrains.annotations.Nullable;
 
@@ -94,6 +100,18 @@ public class GridCacheMvccManager extends 
GridCacheSharedManagerAdapter {
     private static final int MAX_NESTED_LSNR_CALLS =
         getInteger(IGNITE_MAX_NESTED_LISTENER_CALLS, 
DFLT_MAX_NESTED_LISTENER_CALLS);
 
+    /** System view name for cache explicit locks. */
+    public static final String CACHE_EXPLICIT_LOCKS_VIEW = 
"cacheExplicitLocks";
+
+    /** System view description for cache explicit locks. */
+    public static final String CACHE_EXPLICIT_LOCKS_VIEW_DESC = "Explicit 
locks on cache keys";
+
+    /** System view name for cache locks. */
+    public static final String CACHE_LOCKS_VIEW = "cacheLocks";
+
+    /** System view description for cache keys locks. */
+    public static final String CACHE_LOCKS_VIEW_DESC = "Held and queued locks 
on cache keys";
+
     /** Pending locks per thread. */
     private final ThreadLocal<Deque<GridCacheMvccCandidate>> pending = new 
ThreadLocal<>();
 
@@ -282,6 +300,38 @@ public class GridCacheMvccManager extends 
GridCacheSharedManagerAdapter {
         pendingExplicit = GridConcurrentFactory.newMap();
 
         cctx.gridEvents().addLocalEventListener(discoLsnr, EVT_NODE_FAILED, 
EVT_NODE_LEFT);
+
+        cctx.kernalContext().systemView().registerInnerCollectionView(
+            CACHE_EXPLICIT_LOCKS_VIEW,
+            CACHE_EXPLICIT_LOCKS_VIEW_DESC,
+            new CacheExplicitLockViewWalker(),
+            pendingExplicit.values(),
+            GridCacheExplicitLockSpan::candidates,
+            (threadId, lock) -> new CacheExplicitLockView(lock)
+        );
+
+        List<GridCacheMvccCandidate> locks = new ArrayList<>();
+
+        cctx.kernalContext().systemView().registerInnerCollectionView(
+            CACHE_LOCKS_VIEW,
+            CACHE_LOCKS_VIEW_DESC,
+            new CacheLockViewWalker(),
+            locked.values(),
+            entry -> {
+                entry.lockEntry();
+                try {
+                    locks.clear();
+
+                    GridCacheMvcc mvcc = entry.mvccExtras();
+
+                    return mvcc != null ? mvcc.localCandidatesCopy(locks) : 
Collections.emptyList();
+                }
+                finally {
+                    entry.unlockEntry();
+                }
+            },
+            (entry, cand) -> new CacheLockView(cand)
+        );
     }
 
     /** {@inheritDoc} */
diff --git 
a/modules/core/src/main/java/org/apache/ignite/spi/systemview/view/CacheExplicitLockView.java
 
b/modules/core/src/main/java/org/apache/ignite/spi/systemview/view/CacheExplicitLockView.java
new file mode 100644
index 00000000000..8d2a36d6703
--- /dev/null
+++ 
b/modules/core/src/main/java/org/apache/ignite/spi/systemview/view/CacheExplicitLockView.java
@@ -0,0 +1,73 @@
+/*
+ * 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.spi.systemview.view;
+
+import org.apache.ignite.internal.binary.BinaryUtils;
+import org.apache.ignite.internal.processors.cache.GridCacheMvccCandidate;
+import org.apache.ignite.internal.systemview.Order;
+import org.apache.ignite.internal.systemview.SystemViewDescriptor;
+import org.apache.ignite.lang.IgniteUuid;
+
+/**
+ * Cache explicit lock representation for a {@link SystemView}.
+ * Shows locks acquired on current node (not hosted by current node).
+ */
+@SystemViewDescriptor
+public class CacheExplicitLockView {
+    /** */
+    private final GridCacheMvccCandidate cand;
+
+    /**
+     * @param cand Lock candidate
+     */
+    public CacheExplicitLockView(GridCacheMvccCandidate cand) {
+        this.cand = cand;
+    }
+
+    /**
+     * @return Cache ID.
+     */
+    @Order
+    public int cacheId() {
+        return cand.key().cacheId();
+    }
+
+    /**
+     * @return Key.
+     */
+    @Order(1)
+    public String key() {
+        return cand.key().key().toString();
+    }
+
+    /**
+     * @return Thread ID.
+     */
+    @Order(2)
+    public long threadId() {
+        return cand.threadId();
+    }
+
+    /**
+     * @return Version.
+     */
+    @Order(3)
+    public IgniteUuid xid() {
+        return BinaryUtils.asIgniteUuid(cand.version());
+    }
+}
diff --git 
a/modules/core/src/main/java/org/apache/ignite/spi/systemview/view/CacheLockView.java
 
b/modules/core/src/main/java/org/apache/ignite/spi/systemview/view/CacheLockView.java
new file mode 100644
index 00000000000..dbee3e2e34a
--- /dev/null
+++ 
b/modules/core/src/main/java/org/apache/ignite/spi/systemview/view/CacheLockView.java
@@ -0,0 +1,115 @@
+/*
+ * 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.spi.systemview.view;
+
+import java.util.UUID;
+import org.apache.ignite.internal.binary.BinaryUtils;
+import org.apache.ignite.internal.processors.cache.GridCacheMvccCandidate;
+import org.apache.ignite.internal.systemview.Order;
+import org.apache.ignite.internal.systemview.SystemViewDescriptor;
+import org.apache.ignite.lang.IgniteUuid;
+
+/**
+ * Cache key lock representation for a {@link SystemView}.
+ * Shows held cache key locks and queued cache key locks hosted on current 
node,
+ * including explicit locks and transactional locks.
+ */
+@SystemViewDescriptor
+public class CacheLockView {
+    /** Lock candidate. */
+    private final GridCacheMvccCandidate cand;
+
+    /**
+     * @param cand Lock candidate
+     */
+    public CacheLockView(GridCacheMvccCandidate cand) {
+        this.cand = cand;
+    }
+
+    /**
+     * @return Cache ID.
+     */
+    @Order
+    public int cacheId() {
+        return cand.key().cacheId();
+    }
+
+    /**
+     * @return Key.
+     */
+    @Order(1)
+    public String key() {
+        return cand.key().key().toString();
+    }
+
+    /**
+     * @return Node ID.
+     */
+    @Order(2)
+    public UUID nodeId() {
+        return cand.nodeId();
+    }
+
+    /**
+     * @return Originating node ID.
+     */
+    @Order(3)
+    public UUID originatingNodeId() {
+        return cand.otherNodeId();
+    }
+
+    /**
+     * @return "OWNER" flag.
+     */
+    @Order(4)
+    public boolean isOwner() {
+        return cand.owner();
+    }
+
+    /**
+     * @return "TX" flag.
+     */
+    @Order(5)
+    public boolean isTx() {
+        return cand.tx();
+    }
+
+    /**
+     * @return All enabled flags.
+     */
+    @Order(6)
+    public short flags() {
+        return cand.flags();
+    }
+
+    /**
+     * @return Transaction ID.
+     */
+    @Order(7)
+    public IgniteUuid xid() {
+        return BinaryUtils.asIgniteUuid(cand.version());
+    }
+
+    /**
+     * @return Originating transaction ID.
+     */
+    @Order(8)
+    public IgniteUuid originatingXid() {
+        return BinaryUtils.asIgniteUuid(cand.otherVersion());
+    }
+}
diff --git 
a/modules/core/src/main/java/org/apache/ignite/spi/systemview/view/TransactionView.java
 
b/modules/core/src/main/java/org/apache/ignite/spi/systemview/view/TransactionView.java
index bc09a478c99..547715fee2c 100644
--- 
a/modules/core/src/main/java/org/apache/ignite/spi/systemview/view/TransactionView.java
+++ 
b/modules/core/src/main/java/org/apache/ignite/spi/systemview/view/TransactionView.java
@@ -20,6 +20,7 @@ package org.apache.ignite.spi.systemview.view;
 import java.util.Collection;
 import java.util.Objects;
 import java.util.UUID;
+import org.apache.ignite.internal.binary.BinaryUtils;
 import 
org.apache.ignite.internal.processors.cache.transactions.IgniteInternalTx;
 import org.apache.ignite.internal.processors.cache.transactions.IgniteTxEntry;
 import org.apache.ignite.internal.processors.cache.transactions.IgniteTxState;
@@ -125,6 +126,14 @@ public class TransactionView {
         return tx.xid();
     }
 
+    /**
+     * @return Near transaction ID.
+     * @see IgniteInternalTx#nearXidVersion() ()
+     */
+    public IgniteUuid originatingXid() {
+        return BinaryUtils.asIgniteUuid(tx.nearXidVersion());
+    }
+
     /**
      * @return {@code True} if transaction is started for system cache.
      * @see IgniteInternalTx#system()
@@ -284,6 +293,5 @@ public class TransactionView {
         catch (Throwable e) {
             return null;
         }
-
     }
 }
diff --git 
a/modules/core/src/test/java/org/apache/ignite/internal/metric/SystemViewLocksTest.java
 
b/modules/core/src/test/java/org/apache/ignite/internal/metric/SystemViewLocksTest.java
new file mode 100644
index 00000000000..e7b6758819c
--- /dev/null
+++ 
b/modules/core/src/test/java/org/apache/ignite/internal/metric/SystemViewLocksTest.java
@@ -0,0 +1,446 @@
+/*
+ * 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.metric;
+
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.locks.Lock;
+import org.apache.ignite.IgniteCache;
+import org.apache.ignite.client.Person;
+import org.apache.ignite.configuration.CacheConfiguration;
+import org.apache.ignite.internal.IgniteEx;
+import org.apache.ignite.internal.IgniteInternalFuture;
+import org.apache.ignite.internal.util.typedef.F;
+import org.apache.ignite.internal.util.typedef.internal.U;
+import org.apache.ignite.spi.systemview.view.CacheExplicitLockView;
+import org.apache.ignite.spi.systemview.view.CacheLockView;
+import org.apache.ignite.spi.systemview.view.SystemView;
+import org.apache.ignite.spi.systemview.view.TransactionView;
+import org.apache.ignite.testframework.GridTestUtils;
+import org.apache.ignite.testframework.junits.WithSystemProperty;
+import org.apache.ignite.transactions.Transaction;
+import org.junit.Test;
+
+import static 
org.apache.ignite.IgniteCommonsSystemProperties.IGNITE_TO_STRING_INCLUDE_SENSITIVE;
+import static org.apache.ignite.cache.CacheAtomicityMode.TRANSACTIONAL;
+import static org.apache.ignite.cache.CacheMode.PARTITIONED;
+import static 
org.apache.ignite.internal.processors.cache.GridCacheMvccManager.CACHE_EXPLICIT_LOCKS_VIEW;
+import static 
org.apache.ignite.internal.processors.cache.GridCacheMvccManager.CACHE_LOCKS_VIEW;
+import static 
org.apache.ignite.internal.processors.cache.transactions.IgniteTxManager.TXS_MON_LIST;
+import static org.apache.ignite.testframework.GridTestUtils.waitForCondition;
+import static 
org.apache.ignite.transactions.TransactionConcurrency.PESSIMISTIC;
+import static 
org.apache.ignite.transactions.TransactionIsolation.REPEATABLE_READ;
+
+/** Tests for {@link SystemView} for locks. */
+public class SystemViewLocksTest extends SystemViewAbstractTest {
+    /** {@inheritDoc} */
+    @Override protected void beforeTest() throws Exception {
+        super.beforeTest();
+
+        IgniteEx ignite = startGrids(3);
+
+        ignite.getOrCreateCache(
+            new CacheConfiguration<Integer, Integer>(DEFAULT_CACHE_NAME)
+                .setAtomicityMode(TRANSACTIONAL)
+                .setCacheMode(PARTITIONED)
+                .setBackups(1)
+        );
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void afterTest() throws Exception {
+        stopAllGrids();
+
+        super.afterTest();
+    }
+
+    /** */
+    @Test
+    public void testExplicitLocks() throws Exception {
+        IgniteEx ignite0 = grid(0);
+        IgniteEx ignite1 = grid(1);
+        IgniteEx ignite2 = grid(2);
+
+        CountDownLatch finishLatch = new CountDownLatch(1);
+
+        try {
+            Integer key0 = primaryKey(ignite0.cache(DEFAULT_CACHE_NAME));
+            Integer key1 = primaryKey(ignite1.cache(DEFAULT_CACHE_NAME));
+            Integer key2 = primaryKey(ignite2.cache(DEFAULT_CACHE_NAME));
+
+            CountDownLatch firstNodeLatch = new CountDownLatch(1);
+
+            // Block key0 and key1 from node0.
+            Runnable task0 = explicitLockTask(ignite0, () -> {
+                firstNodeLatch.countDown();
+                U.awaitQuiet(finishLatch);
+            }, key0, key1);
+
+            // Block key2 and wait for key0 from node1.
+            Runnable task1 = explicitLockTask(ignite1, () -> {}, key2, key0);
+
+            // Wait for key0 from node2.
+            Runnable task2a = explicitLockTask(ignite2, () -> {}, key0);
+
+            // Wait for key1 from node2.
+            Runnable task2b = explicitLockTask(ignite2, () -> {}, key1);
+
+            IgniteInternalFuture<?> fut0 = GridTestUtils.runAsync(task0);
+            firstNodeLatch.await();
+            IgniteInternalFuture<?> fut1 = GridTestUtils.runAsync(task1);
+            IgniteInternalFuture<?> fut2a = GridTestUtils.runAsync(task2a);
+            IgniteInternalFuture<?> fut2b = GridTestUtils.runAsync(task2b);
+
+            List<CacheExplicitLockView> explicitLocks0 = viewContent(ignite0, 
CACHE_EXPLICIT_LOCKS_VIEW, 2);
+            List<CacheExplicitLockView> explicitLocks1 = viewContent(ignite1, 
CACHE_EXPLICIT_LOCKS_VIEW, 2);
+            List<CacheExplicitLockView> explicitLocks2 = viewContent(ignite2, 
CACHE_EXPLICIT_LOCKS_VIEW, 2);
+
+            List<CacheLockView> keyLocks0 = viewContent(ignite0, 
CACHE_LOCKS_VIEW, 3);
+            List<CacheLockView> keyLocks1 = viewContent(ignite1, 
CACHE_LOCKS_VIEW, 2);
+            List<CacheLockView> keyLocks2 = viewContent(ignite2, 
CACHE_LOCKS_VIEW, 1);
+
+            // Check threads.
+            assertEquals(explicitLocks0.get(0).threadId(), 
explicitLocks0.get(1).threadId());
+            assertEquals(explicitLocks1.get(0).threadId(), 
explicitLocks1.get(1).threadId());
+            assertNotSame(explicitLocks2.get(0).threadId(), 
explicitLocks2.get(1).threadId());
+
+            // Check lock owners.
+            CacheLockView owner0 = F.find(keyLocks0, null, 
CacheLockView::isOwner);
+            CacheLockView owner1 = F.find(keyLocks1, null, 
CacheLockView::isOwner);
+            CacheLockView owner2 = F.find(keyLocks2, null, 
CacheLockView::isOwner);
+
+            assertNotNull(owner0);
+            assertNotNull(owner1);
+            assertNotNull(owner2);
+
+            assertEquals(owner0.originatingNodeId(), ignite0.localNode().id());
+            assertEquals(1, F.size(explicitLocks0, l -> 
l.xid().equals(owner0.originatingXid())));
+
+            assertEquals(owner1.originatingNodeId(), ignite0.localNode().id());
+            assertEquals(1, F.size(explicitLocks0, l -> 
l.xid().equals(owner1.originatingXid())));
+
+            assertEquals(owner2.originatingNodeId(), ignite1.localNode().id());
+            assertEquals(1, F.size(explicitLocks1, l -> 
l.xid().equals(owner2.originatingXid())));
+
+            // Check waiting locks.
+            assertEquals(1, F.size(keyLocks0,
+                l -> !l.isOwner() && 
l.originatingNodeId().equals(ignite1.localNode().id())));
+            assertEquals(1, F.size(keyLocks0,
+                l -> !l.isOwner() && 
l.originatingNodeId().equals(ignite2.localNode().id())));
+            assertEquals(1, F.size(keyLocks1,
+                l -> !l.isOwner() && 
l.originatingNodeId().equals(ignite2.localNode().id())));
+
+            finishLatch.countDown();
+
+            fut0.get();
+            fut1.get();
+            fut2a.get();
+            fut2b.get();
+        }
+        finally {
+            finishLatch.countDown();
+        }
+    }
+
+    /** */
+    @Test
+    public void testTxLocks() throws Exception {
+        IgniteEx ignite0 = grid(0);
+        IgniteEx ignite1 = grid(1);
+        IgniteEx ignite2 = grid(2);
+
+        CountDownLatch finishLatch = new CountDownLatch(1);
+
+        try {
+            Integer key0 = primaryKey(ignite0.cache(DEFAULT_CACHE_NAME));
+            Integer key1 = primaryKey(ignite1.cache(DEFAULT_CACHE_NAME));
+            Integer key2 = primaryKey(ignite2.cache(DEFAULT_CACHE_NAME));
+
+            CountDownLatch firstNodeLatch = new CountDownLatch(1);
+
+            // Block key0 and key1 from node0.
+            Runnable task0 = txLockTask(ignite0, () -> {
+                firstNodeLatch.countDown();
+                U.awaitQuiet(finishLatch);
+            }, key0, key1);
+
+            // Block key2 and wait for key0 from node1.
+            Runnable task1 = txLockTask(ignite1, () -> {}, key2, key0);
+
+            // Wait for key0 from node2.
+            Runnable task2a = txLockTask(ignite2, () -> {}, key0);
+
+            // Wait for key1 from node2.
+            Runnable task2b = txLockTask(ignite2, () -> {}, key1);
+
+            IgniteInternalFuture<?> fut0 = GridTestUtils.runAsync(task0);
+            firstNodeLatch.await();
+            IgniteInternalFuture<?> fut1 = GridTestUtils.runAsync(task1);
+            IgniteInternalFuture<?> fut2a = GridTestUtils.runAsync(task2a);
+            IgniteInternalFuture<?> fut2b = GridTestUtils.runAsync(task2b);
+
+            List<TransactionView> txs0 = viewContent(ignite0, TXS_MON_LIST, 
3); // 1 near + 2 dht.
+            List<TransactionView> txs1 = viewContent(ignite1, TXS_MON_LIST, 
3); // 1 near + 2 dht
+            List<TransactionView> txs2 = viewContent(ignite2, TXS_MON_LIST, 
3); // 2 near + 1 dht
+
+            List<CacheLockView> keyLocks0 = viewContent(ignite0, 
CACHE_LOCKS_VIEW, 3);
+            List<CacheLockView> keyLocks1 = viewContent(ignite1, 
CACHE_LOCKS_VIEW, 2);
+            List<CacheLockView> keyLocks2 = viewContent(ignite2, 
CACHE_LOCKS_VIEW, 1);
+
+            // Check lock owners.
+            CacheLockView owner0 = F.find(keyLocks0, null, 
CacheLockView::isOwner);
+            CacheLockView owner1 = F.find(keyLocks1, null, 
CacheLockView::isOwner);
+            CacheLockView owner2 = F.find(keyLocks2, null, 
CacheLockView::isOwner);
+
+            assertNotNull(owner0);
+            assertNotNull(owner1);
+            assertNotNull(owner2);
+
+            assertEquals(owner0.originatingNodeId(), ignite0.localNode().id());
+            assertEquals(1, F.size(txs0, l -> 
l.xid().equals(owner0.originatingXid()) && l.xid().equals(owner0.xid())));
+
+            assertEquals(owner1.originatingNodeId(), ignite0.localNode().id());
+            assertEquals(1, F.size(txs0, l -> 
l.xid().equals(owner1.originatingXid()))); // Near.
+            assertEquals(1, F.size(txs1, l -> l.xid().equals(owner1.xid()))); 
// Dht.
+
+            assertEquals(owner2.originatingNodeId(), ignite1.localNode().id());
+            assertEquals(1, F.size(txs1, l -> 
l.xid().equals(owner2.originatingXid()))); // Near.
+            assertEquals(1, F.size(txs2, l -> l.xid().equals(owner2.xid()))); 
// Dht.
+
+            // Check waiting locks.
+            CacheLockView waiting1on0 = F.find(keyLocks0, null,
+                l -> !l.isOwner() && 
l.originatingNodeId().equals(ignite1.localNode().id()));
+            CacheLockView waiting2on0 = F.find(keyLocks0, null,
+                l -> !l.isOwner() && 
l.originatingNodeId().equals(ignite2.localNode().id()));
+            CacheLockView waiting2on1 = F.find(keyLocks1, null,
+                l -> !l.isOwner() && 
l.originatingNodeId().equals(ignite2.localNode().id()));
+
+            assertNotNull(waiting1on0);
+            assertNotNull(waiting2on0);
+            assertNotNull(waiting2on1);
+
+            assertEquals(1, F.size(txs1, l -> 
l.xid().equals(waiting1on0.originatingXid()))); // Near.
+            assertEquals(1, F.size(txs0, l -> 
l.xid().equals(waiting1on0.xid()))); // Dht.
+
+            assertEquals(1, F.size(txs2, l -> 
l.xid().equals(waiting2on0.originatingXid()))); // Near.
+            assertEquals(1, F.size(txs0, l -> 
l.xid().equals(waiting2on0.xid()))); // Dht.
+
+            assertEquals(1, F.size(txs2, l -> 
l.xid().equals(waiting2on1.originatingXid()))); // Near.
+            assertEquals(1, F.size(txs1, l -> 
l.xid().equals(waiting2on1.xid()))); // Dht.
+
+            finishLatch.countDown();
+
+            fut0.get();
+            fut1.get();
+            fut2a.get();
+            fut2b.get();
+        }
+        finally {
+            finishLatch.countDown();
+        }
+    }
+
+    /** */
+    @Test
+    public void testMixedExplicitTxLocks() throws Exception {
+        IgniteEx ignite0 = grid(0);
+        IgniteEx ignite1 = grid(1);
+        IgniteEx ignite2 = grid(2);
+
+        CountDownLatch finishLatch = new CountDownLatch(1);
+
+        try {
+            Integer key1 = primaryKey(ignite1.cache(DEFAULT_CACHE_NAME));
+            Integer key2 = primaryKey(ignite2.cache(DEFAULT_CACHE_NAME));
+
+            CountDownLatch firstNodeLatch = new CountDownLatch(1);
+            CountDownLatch secondNodeLatch = new CountDownLatch(1);
+
+            // Block key1 from node0.
+            Runnable task0 = explicitLockTask(ignite0, () -> {
+                firstNodeLatch.countDown();
+                U.awaitQuiet(finishLatch);
+            }, key1);
+
+            // Block key2 from node1.
+            Runnable task1 = txLockTask(ignite1, () -> {
+                secondNodeLatch.countDown();
+                U.awaitQuiet(finishLatch);
+            }, key2);
+
+            // Wait for key2 from node2.
+            Runnable task2a = explicitLockTask(ignite2, () -> {}, key2);
+
+            // Wait for key1 from node2.
+            Runnable task2b = txLockTask(ignite2, () -> {}, key1);
+
+            IgniteInternalFuture<?> fut0 = GridTestUtils.runAsync(task0);
+            firstNodeLatch.await();
+            IgniteInternalFuture<?> fut1 = GridTestUtils.runAsync(task1);
+            secondNodeLatch.await();
+            IgniteInternalFuture<?> fut2a = GridTestUtils.runAsync(task2a);
+            IgniteInternalFuture<?> fut2b = GridTestUtils.runAsync(task2b);
+
+            List<CacheExplicitLockView> explicitLocks0 = viewContent(ignite0, 
CACHE_EXPLICIT_LOCKS_VIEW, 1);
+            List<CacheExplicitLockView> explicitLocks2 = viewContent(ignite2, 
CACHE_EXPLICIT_LOCKS_VIEW, 1);
+            List<TransactionView> txs1 = viewContent(ignite1, TXS_MON_LIST, 
2); // 1 near + 1 dht
+            List<TransactionView> txs2 = viewContent(ignite2, TXS_MON_LIST, 
2); // 1 near + 1 dht
+
+            List<CacheLockView> keyLocks1 = viewContent(ignite1, 
CACHE_LOCKS_VIEW, 2);
+            List<CacheLockView> keyLocks2 = viewContent(ignite2, 
CACHE_LOCKS_VIEW, 2);
+
+            // Check lock owners.
+            CacheLockView owner1 = F.find(keyLocks1, null, 
CacheLockView::isOwner);
+            CacheLockView owner2 = F.find(keyLocks2, null, 
CacheLockView::isOwner);
+
+            assertNotNull(owner1);
+            assertNotNull(owner2);
+
+            assertEquals(owner1.originatingNodeId(), ignite0.localNode().id());
+            assertEquals(1, F.size(explicitLocks0, l -> 
l.xid().equals(owner1.originatingXid())));
+
+            assertEquals(owner2.originatingNodeId(), ignite1.localNode().id());
+            assertEquals(1, F.size(txs1, l -> 
l.xid().equals(owner2.originatingXid()))); // Near.
+            assertEquals(1, F.size(txs2, l -> l.xid().equals(owner2.xid()))); 
// Dht.
+
+            // Check waiting locks.
+            CacheLockView waiting2on1 = F.find(keyLocks1, null,
+                l -> !l.isOwner() && 
l.originatingNodeId().equals(ignite2.localNode().id()));
+            CacheLockView waiting2on2 = F.find(keyLocks2, null,
+                l -> !l.isOwner() && 
l.originatingNodeId().equals(ignite2.localNode().id()));
+
+            assertNotNull(waiting2on1);
+            assertNotNull(waiting2on2);
+
+            assertEquals(1, F.size(txs2, l -> 
l.xid().equals(waiting2on1.originatingXid()))); // Near.
+            assertEquals(1, F.size(txs1, l -> 
l.xid().equals(waiting2on1.xid()))); // Dht.
+
+            assertEquals(1, F.size(explicitLocks2, l -> 
l.xid().equals(waiting2on2.originatingXid())));
+
+            finishLatch.countDown();
+
+            fut0.get();
+            fut1.get();
+            fut2a.get();
+            fut2b.get();
+        }
+        finally {
+            finishLatch.countDown();
+        }
+    }
+
+    /** */
+    @Test
+    public void testKeyTextIncludeSensitive() throws Exception {
+        checkKeyText(true);
+    }
+
+    /** */
+    @Test
+    @WithSystemProperty(key = IGNITE_TO_STRING_INCLUDE_SENSITIVE, value = 
"false")
+    public void testKeyTextExcludeSensitive() throws Exception {
+        checkKeyText(false);
+    }
+
+    /** */
+    public void checkKeyText(boolean includeSensitive) throws Exception {
+        IgniteEx ignite = grid(0);
+        IgniteCache<Object, Object> cache = ignite.cache(DEFAULT_CACHE_NAME);
+
+        // Check primitive object.
+        String key1 = "testKey";
+
+        Lock lock1 = cache.lock(key1);
+
+        lock1.lock();
+
+        try {
+            List<CacheExplicitLockView> explicitLocks = viewContent(ignite, 
CACHE_EXPLICIT_LOCKS_VIEW, 1);
+            // Sensitive data included: KeyCacheObjectImpl [part=859, 
val=testKey, hasValBytes=false]
+            // Sensitive data excluded: KeyCacheObject [hasValBytes=false]
+            assertEquals(includeSensitive, 
explicitLocks.get(0).key().contains(key1));
+        }
+        finally {
+            lock1.unlock();
+        }
+
+        // Check binary object.
+        Person key2 = new Person(12345, "testName");
+
+        Lock lock2 = cache.lock(key2);
+
+        lock2.lock();
+
+        try {
+            List<CacheExplicitLockView> explicitLocks = viewContent(ignite, 
CACHE_EXPLICIT_LOCKS_VIEW, 1);
+            // Sensitive data included: org.apache.ignite.client.Person 
[idHash=..., hash=-1202253851, id=12345, name=testName]
+            // Sensitive data excluded: BinaryObject [idHash=..., 
hash=-1202253851]
+            assertEquals(includeSensitive, 
explicitLocks.get(0).key().contains(key2.getClass().getName()));
+            assertEquals(includeSensitive, 
explicitLocks.get(0).key().contains("id=" + key2.getId()));
+            assertEquals(includeSensitive, 
explicitLocks.get(0).key().contains("name=" + key2.getName()));
+        }
+        finally {
+            lock2.unlock();
+        }
+    }
+
+    /** */
+    private static Runnable explicitLockTask(IgniteEx ignite, Runnable body, 
int... keysToLock) {
+        return () -> {
+            Lock[] locks = new Lock[keysToLock.length];
+
+            for (int i = 0; i < keysToLock.length; i++) {
+                locks[i] = 
ignite.cache(DEFAULT_CACHE_NAME).lock(keysToLock[i]);
+                locks[i].lock();
+            }
+
+            try {
+                body.run();
+            }
+            finally {
+                for (int i = keysToLock.length - 1; i >= 0; i--)
+                    locks[i].unlock();
+            }
+        };
+    }
+
+    /** */
+    private static Runnable txLockTask(IgniteEx ignite, Runnable body, int... 
keysToLock) {
+        return () -> {
+            try (Transaction tx = ignite.transactions().txStart(PESSIMISTIC, 
REPEATABLE_READ)) {
+                for (int i = 0; i < keysToLock.length; i++)
+                    ignite.cache(DEFAULT_CACHE_NAME).put(keysToLock[i], 0);
+
+                body.run();
+
+                tx.commit();
+            }
+        };
+    }
+
+    /** */
+    private static <T> List<T> viewContent(IgniteEx ignite, String viewName, 
int expSize) throws Exception {
+        SystemView<T> view = ignite.context().systemView().view(viewName);
+
+        assertTrue("Failed to wait for view size [ignite=" + ignite.name() + 
", viewName=" + viewName +
+                ", expSize=" + expSize + ", actSize=" + 
F.size(view.iterator()),
+            waitForCondition(() -> F.size(view.iterator()) == expSize, 
1_000L));
+
+        return U.arrayList(view.iterator(), expSize);
+    }
+}
diff --git 
a/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteCacheTestSuite13.java
 
b/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteCacheTestSuite13.java
index 2f4d1787be3..4426afc2053 100644
--- 
a/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteCacheTestSuite13.java
+++ 
b/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteCacheTestSuite13.java
@@ -45,6 +45,7 @@ import 
org.apache.ignite.internal.metric.SystemViewComputeTaskTest;
 import org.apache.ignite.internal.metric.SystemViewConfigurationTest;
 import org.apache.ignite.internal.metric.SystemViewDSTest;
 import org.apache.ignite.internal.metric.SystemViewExecutorsTest;
+import org.apache.ignite.internal.metric.SystemViewLocksTest;
 import org.apache.ignite.internal.metric.SystemViewMetastorageTest;
 import org.apache.ignite.internal.metric.SystemViewNodesTest;
 import org.apache.ignite.internal.metric.SystemViewPageListsTest;
@@ -122,6 +123,7 @@ public class IgniteCacheTestSuite13 {
         GridTestUtils.addTestIfNeeded(suite, 
SystemViewConfigurationTest.class, ignoredTests);
         GridTestUtils.addTestIfNeeded(suite, SystemViewPluginTest.class, 
ignoredTests);
         GridTestUtils.addTestIfNeeded(suite, SystemViewQueriesTest.class, 
ignoredTests);
+        GridTestUtils.addTestIfNeeded(suite, SystemViewLocksTest.class, 
ignoredTests);
         GridTestUtils.addTestIfNeeded(suite, CacheMetricsAddRemoveTest.class, 
ignoredTests);
         GridTestUtils.addTestIfNeeded(suite, 
CacheMetricsConflictResolverTest.class, ignoredTests);
         GridTestUtils.addTestIfNeeded(suite, JmxExporterSpiTest.class, 
ignoredTests);
diff --git 
a/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/metric/SqlViewExporterSpiTest.java
 
b/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/metric/SqlViewExporterSpiTest.java
index 15a512dbaab..e98770d4a5b 100644
--- 
a/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/metric/SqlViewExporterSpiTest.java
+++ 
b/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/metric/SqlViewExporterSpiTest.java
@@ -465,7 +465,9 @@ public class SqlViewExporterSpiTest extends 
AbstractExporterSpiTest {
             "DS_QUEUES",
             "PAGES_TIMESTAMP_HISTOGRAM",
             "SQL_PLANS_HISTORY",
-            "IGNITE_PLUGINS"
+            "IGNITE_PLUGINS",
+            "CACHE_LOCKS",
+            "CACHE_EXPLICIT_LOCKS"
         ));
 
         Set<String> actViews = new TreeSet<>();
diff --git 
a/modules/indexing/src/test/java/org/apache/ignite/internal/processors/query/SqlSystemViewsSelfTest.java
 
b/modules/indexing/src/test/java/org/apache/ignite/internal/processors/query/SqlSystemViewsSelfTest.java
index 5c74bdf53aa..87f000f2738 100644
--- 
a/modules/indexing/src/test/java/org/apache/ignite/internal/processors/query/SqlSystemViewsSelfTest.java
+++ 
b/modules/indexing/src/test/java/org/apache/ignite/internal/processors/query/SqlSystemViewsSelfTest.java
@@ -32,6 +32,7 @@ import java.util.Set;
 import java.util.UUID;
 import java.util.concurrent.Callable;
 import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.Lock;
 import java.util.function.BiConsumer;
 import java.util.stream.Collectors;
 import java.util.stream.LongStream;
@@ -81,6 +82,7 @@ import org.apache.ignite.internal.util.lang.GridNodePredicate;
 import org.apache.ignite.internal.util.typedef.F;
 import org.apache.ignite.internal.util.typedef.G;
 import org.apache.ignite.internal.util.typedef.X;
+import org.apache.ignite.internal.util.typedef.internal.CU;
 import org.apache.ignite.internal.util.typedef.internal.U;
 import org.apache.ignite.lang.IgniteFuture;
 import org.apache.ignite.lang.IgnitePredicate;
@@ -92,6 +94,7 @@ import org.apache.ignite.spi.systemview.view.SqlQueryView;
 import org.apache.ignite.spi.systemview.view.SystemView;
 import org.apache.ignite.spi.systemview.view.sql.SqlTableView;
 import org.apache.ignite.testframework.GridTestUtils;
+import org.apache.ignite.transactions.Transaction;
 import org.junit.Assert;
 import org.junit.Test;
 
@@ -102,6 +105,8 @@ import static 
org.apache.ignite.internal.processors.cache.persistence.metastorag
 import static 
org.apache.ignite.internal.processors.query.running.RunningQueryManager.SQL_QRY_VIEW;
 import static org.apache.ignite.internal.util.IgniteUtils.MB;
 import static org.apache.ignite.testframework.GridTestUtils.waitForCondition;
+import static 
org.apache.ignite.transactions.TransactionConcurrency.PESSIMISTIC;
+import static 
org.apache.ignite.transactions.TransactionIsolation.REPEATABLE_READ;
 import static org.junit.Assert.assertNotEquals;
 
 /**
@@ -1896,6 +1901,56 @@ public class SqlSystemViewsSelfTest extends 
AbstractIndexingCommonTest {
         }
     }
 
+    /** */
+    @Test
+    public void testLocksView() throws Exception {
+        try (IgniteEx ignite = startGrid()) {
+            IgniteCache<Object, Object> cache = ignite.getOrCreateCache(new 
CacheConfiguration<>(DEFAULT_CACHE_NAME)
+                .setAtomicityMode(CacheAtomicityMode.TRANSACTIONAL));
+
+            Lock lock1 = cache.lock(1);
+            Lock lock2 = cache.lock(2);
+
+            lock1.lock();
+            lock2.lock();
+            try (Transaction ignored = 
ignite.transactions().txStart(PESSIMISTIC, REPEATABLE_READ)) {
+                cache.put(3, 3);
+
+                // Join explicit locks with key locks.
+                List<List<?>> res = execSql("SELECT el.cache_id, el.thread_id, 
kl.is_owner, kl.is_tx, kl.originating_node_id " +
+                    "FROM SYS.CACHE_EXPLICIT_LOCKS el JOIN SYS.CACHE_LOCKS kl 
ON el.xid = kl.xid");
+
+                assertEquals(2, res.size());
+
+                for (List<?> lock : res) {
+                    assertEquals(CU.cacheId(DEFAULT_CACHE_NAME), lock.get(0));
+                    assertEquals(Thread.currentThread().getId(), lock.get(1));
+                    assertEquals(true, lock.get(2));
+                    assertEquals(false, lock.get(3));
+                    assertEquals(ignite.localNode().id(), lock.get(4));
+                }
+
+                // Join transactions with key locks.
+                res = execSql("SELECT kl.cache_id, tx.thread_id, kl.is_owner, 
kl.is_tx, kl.originating_node_id " +
+                    "FROM SYS.TRANSACTIONS tx JOIN SYS.CACHE_LOCKS kl ON 
tx.xid = kl.xid");
+
+                assertEquals(1, res.size());
+
+                for (List<?> lock : res) {
+                    assertEquals(CU.cacheId(DEFAULT_CACHE_NAME), lock.get(0));
+                    assertEquals(Thread.currentThread().getId(), lock.get(1));
+                    assertEquals(true, lock.get(2));
+                    assertEquals(true, lock.get(3));
+                    assertEquals(ignite.localNode().id(), lock.get(4));
+                }
+            }
+            finally {
+                lock2.unlock();
+                lock1.unlock();
+            }
+        }
+    }
+
     /**
      * Mock for {@link ClusterMetricsImpl} that always returns big (more than 
24h) duration for all duration metrics.
      */

Reply via email to