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

dengzhhu653 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/hive.git


The following commit(s) were added to refs/heads/master by this push:
     new 8e8dd5155da HIVE-29583: Profile JDBC executions in Metastore (#6464)
8e8dd5155da is described below

commit 8e8dd5155da4e74c966b419e388cd4b66b118892
Author: dengzh <[email protected]>
AuthorDate: Fri Jul 24 11:48:37 2026 +0800

    HIVE-29583: Profile JDBC executions in Metastore (#6464)
---
 .../hadoop/hive/metastore/conf/MetastoreConf.java  |  10 +
 .../hadoop/hive/metastore/HMSHandlerContext.java   |  62 ++++
 .../hadoop/hive/metastore/RetryingHMSHandler.java  |  16 +
 .../metastore/datasource/DataSourceProvider.java   |  34 ++-
 .../datasource/DbCPDataSourceProvider.java         |  24 +-
 .../datasource/HikariCPDataSourceProvider.java     |  19 +-
 .../metastore/datasource/MetastoreConnection.java  | 338 +++++++++++++++++++++
 .../metastore/datasource/MetastoreStatement.java   | 213 +++++++++++++
 .../metastore/datasource/ProfilingDataSource.java  | 103 +++++++
 .../metastore/handler/AbstractRequestHandler.java  |  27 +-
 .../hive/metastore/metrics/MetricsConstants.java   |   2 +
 .../datasource/TestProfilingDataSource.java        | 164 ++++++++++
 12 files changed, 972 insertions(+), 40 deletions(-)

diff --git 
a/standalone-metastore/metastore-common/src/main/java/org/apache/hadoop/hive/metastore/conf/MetastoreConf.java
 
b/standalone-metastore/metastore-common/src/main/java/org/apache/hadoop/hive/metastore/conf/MetastoreConf.java
index acd5929b8b9..3a4d7a173d4 100644
--- 
a/standalone-metastore/metastore-common/src/main/java/org/apache/hadoop/hive/metastore/conf/MetastoreConf.java
+++ 
b/standalone-metastore/metastore-common/src/main/java/org/apache/hadoop/hive/metastore/conf/MetastoreConf.java
@@ -652,6 +652,16 @@ public enum ConfVars {
         "hive.txn.acid.metrics.delta.pct.threshold", 0.01f,
         "Percentage (fractional) size of the delta files relative to the base 
directory. Deltas smaller than this threshold " +
             "count as small deltas. Default 0.01 = 1%.)"),
+    
METASTORE_JDBC_SLOW_QUERY_THRESHOLD("metastore.jdbc.execution.logSlowQueriesThreshold",
 "metastore.jdbc.execution.logSlowQueriesThreshold",
+        3000, TimeUnit.MILLISECONDS, "Log the slow jdbc query that Metastore 
has been waiting for the result beyond the threshold(ms), " +
+        "should turn on the metastore.profile.jdbc.execution first"),
+    METASTORE_PROFILE_JDBC_EXECUTION("metastore.profile.jdbc.execution", 
"metastore.profile.jdbc.execution", false,
+        "Turn on to profile JDBC executions at the statement layer (slow-query 
logging, per-query metrics\n" +
+            "about metastore.jdbc.profile.thrift.apis, and aggregate JDBC 
summaries for Thrift APIs )."),
+    METASTORE_PROFILE_JDBC_THRIFT_APIS("metastore.jdbc.profile.thrift.apis", 
"metastore.jdbc.profile.thrift.apis",
+        "get_table_req,get_database_req",
+        "Thrift API method names for which to record the per-query metrics.\n" 
+
+            "Per-query slow detection applies to all JDBC executions while 
profiling is on."),
     COMPACTOR_INITIATOR_ON("metastore.compactor.initiator.on", 
"hive.compactor.initiator.on", true,
         "Whether to run the initiator thread on this metastore instance or 
not.\n" +
             "Set this to true on one instance of the Thrift metastore service 
as part of turning\n" +
diff --git 
a/standalone-metastore/metastore-server/src/main/java/org/apache/hadoop/hive/metastore/HMSHandlerContext.java
 
b/standalone-metastore/metastore-server/src/main/java/org/apache/hadoop/hive/metastore/HMSHandlerContext.java
index d52755786d4..90dd0a4a7c9 100644
--- 
a/standalone-metastore/metastore-server/src/main/java/org/apache/hadoop/hive/metastore/HMSHandlerContext.java
+++ 
b/standalone-metastore/metastore-server/src/main/java/org/apache/hadoop/hive/metastore/HMSHandlerContext.java
@@ -20,11 +20,14 @@
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Optional;
+import java.util.concurrent.TimeUnit;
 
 import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.hive.metastore.conf.MetastoreConf;
 import org.apache.hadoop.hive.metastore.handler.BaseHandler;
 import org.apache.hadoop.hive.metastore.txn.TxnStore;
 import org.apache.hadoop.hive.metastore.txn.TxnUtils;
+import org.slf4j.Logger;
 
 /**
  * When one hms client connects in, we create a handler context for it.
@@ -55,6 +58,57 @@ public final class HMSHandlerContext {
 
   private Map<String, com.codahale.metrics.Timer.Context> timerContexts = new 
HashMap<>();
 
+  // Unique ID of current thrift call
+  private CallCtx callCtx;
+
+  public static final class CallCtx {
+    private final String methodName;
+    private long totalTime;
+    private int queryCount;
+    private long maxJdbcTimeMs;
+
+    public CallCtx(String methodName) {
+      this.methodName = methodName;
+    }
+
+    public String methodName() {
+      return methodName;
+    }
+
+    public void recordJdbcExecution(long timeSpentMs) {
+      queryCount++;
+      totalTime += timeSpentMs;
+      maxJdbcTimeMs = Math.max(maxJdbcTimeMs, timeSpentMs);
+    }
+
+    public int getQueryCount() {
+      return queryCount;
+    }
+
+    public long getTotalTime() {
+      return totalTime;
+    }
+
+    public long getMaxJdbcTimeMs() {
+      return maxJdbcTimeMs;
+    }
+
+    public void logJdbcSummary(Logger Log, Configuration configuration) {
+      if (queryCount == 0) {
+        return;
+      }
+      Log.debug("{} jdbc summary: {} queries, {} ms total, {} ms max per 
query",
+          methodName, queryCount, totalTime, maxJdbcTimeMs);
+      long slowThreshold = MetastoreConf.getTimeVar(configuration,
+          MetastoreConf.ConfVars.METASTORE_JDBC_SLOW_QUERY_THRESHOLD, 
TimeUnit.MILLISECONDS);
+      if (slowThreshold > 0 && totalTime > slowThreshold) {
+        Log.warn("{} jdbc summary: {} queries, {} ms total, {} ms max per 
query (no single query exceeded "
+                + "the slow threshold, but aggregate JDBC time is high)",
+            methodName, queryCount, totalTime, maxJdbcTimeMs);
+      }
+    }
+  }
+
   private HMSHandlerContext() {
   }
 
@@ -89,6 +143,14 @@ public static Map<String, 
com.codahale.metrics.Timer.Context> getTimerContexts()
     return context.get().timerContexts;
   }
 
+  public static Optional<CallCtx> getCallCtx() {
+    return Optional.ofNullable(context.get().callCtx);
+  }
+
+  public static void setCallCtx(CallCtx ctx) {
+    context.get().callCtx = ctx;
+  }
+
   public static void setRawStore(RawStore rawStore) {
     context.get().rawStore = rawStore;
   }
diff --git 
a/standalone-metastore/metastore-server/src/main/java/org/apache/hadoop/hive/metastore/RetryingHMSHandler.java
 
b/standalone-metastore/metastore-server/src/main/java/org/apache/hadoop/hive/metastore/RetryingHMSHandler.java
index 0d52107ad09..2620177aaa7 100644
--- 
a/standalone-metastore/metastore-server/src/main/java/org/apache/hadoop/hive/metastore/RetryingHMSHandler.java
+++ 
b/standalone-metastore/metastore-server/src/main/java/org/apache/hadoop/hive/metastore/RetryingHMSHandler.java
@@ -21,6 +21,7 @@
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.lang.reflect.UndeclaredThrowableException;
+import java.util.Optional;
 import java.util.concurrent.TimeUnit;
 
 import javax.jdo.JDOException;
@@ -44,10 +45,12 @@ public class RetryingHMSHandler extends 
AbstractHMSHandlerProxy {
 
   private final long retryInterval;
   private final int retryLimit;
+  private final boolean local;
 
   public RetryingHMSHandler(Configuration conf, IHMSHandler baseHandler, 
boolean local)
       throws MetaException {
     super(conf, baseHandler, local);
+    this.local = local;
     retryInterval = MetastoreConf.getTimeVar(origConf,
         ConfVars.HMS_HANDLER_INTERVAL, TimeUnit.MILLISECONDS);
     retryLimit = MetastoreConf.getIntVar(origConf, 
ConfVars.HMS_HANDLER_ATTEMPTS);
@@ -87,9 +90,22 @@ public Result invokeInternal(final Object proxy, final 
Method method, final Obje
         }
         Object object = null;
         boolean isStarted = Deadline.startTimer(method.getName());
+        boolean clearCtxCall = false;
         try {
+          if (!local) {
+            Optional<HMSHandlerContext.CallCtx> callCtx = 
HMSHandlerContext.getCallCtx();
+            if (callCtx.isEmpty()) {
+              HMSHandlerContext.setCallCtx(new 
HMSHandlerContext.CallCtx(method.getName()));
+              clearCtxCall = true;
+            }
+          }
           object = method.invoke(baseHandler, args);
         } finally {
+          if (clearCtxCall) {
+            HMSHandlerContext.getCallCtx()
+                .ifPresent(ctx -> ctx.logJdbcSummary(LOG, getActiveConf()));
+            HMSHandlerContext.setCallCtx(null);
+          }
           if (isStarted) {
             Deadline.stopTimer();
           }
diff --git 
a/standalone-metastore/metastore-server/src/main/java/org/apache/hadoop/hive/metastore/datasource/DataSourceProvider.java
 
b/standalone-metastore/metastore-server/src/main/java/org/apache/hadoop/hive/metastore/datasource/DataSourceProvider.java
index 78fa406444b..f5baa8ad2ce 100644
--- 
a/standalone-metastore/metastore-server/src/main/java/org/apache/hadoop/hive/metastore/datasource/DataSourceProvider.java
+++ 
b/standalone-metastore/metastore-server/src/main/java/org/apache/hadoop/hive/metastore/datasource/DataSourceProvider.java
@@ -20,11 +20,15 @@
 import java.io.Closeable;
 import java.io.IOException;
 import java.sql.SQLException;
+import java.util.Map;
 import java.util.Properties;
+import java.util.function.Consumer;
 import javax.sql.DataSource;
 
 import com.google.common.collect.Iterables;
+
 import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.hive.metastore.DatabaseProduct;
 import org.apache.hadoop.hive.metastore.conf.MetastoreConf;
 
 public interface DataSourceProvider {
@@ -43,7 +47,14 @@ default DataSource create(Configuration hdpConfig) throws 
SQLException {
    * @param maxPoolSize the maximum size of the connection pool
    * @return the new connection pool
    */
-  DataSource create(Configuration hdpConfig, int maxPoolSize) throws 
SQLException;
+  default DataSource create(Configuration hdpConfig, int maxPoolSize) throws 
SQLException {
+    return maybeWrapForProfiling(hdpConfig, createPool(hdpConfig, 
maxPoolSize));
+  }
+
+  /**
+   * Creates the underlying connection pool without JDBC profiling decoration.
+   */
+  DataSource createPool(Configuration hdpConfig, int maxPoolSize) throws 
SQLException;
 
   /**
    * Get the declared pooling type string. This is used to check against the 
constant in
@@ -76,14 +87,33 @@ static String getMetastoreJdbcPasswd(Configuration conf) 
throws SQLException {
     }
   }
 
-  static String getMetastoreJdbcDriverUrl(Configuration conf) throws 
SQLException {
+  static String getMetastoreJdbcDriverUrl(Configuration conf) {
     return MetastoreConf.getVar(conf, MetastoreConf.ConfVars.CONNECT_URL_KEY);
   }
 
+  static DataSource maybeWrapForProfiling(Configuration conf, DataSource 
dataSource) {
+    if (MetastoreConf.getBoolVar(conf, 
MetastoreConf.ConfVars.METASTORE_PROFILE_JDBC_EXECUTION)) {
+      return new ProfilingDataSource(dataSource, conf);
+    }
+    return dataSource;
+  }
+
   static String getDataSourceName(Configuration conf) {
     return conf.get(DataSourceNameConfigurator.DATA_SOURCE_NAME);
   }
 
+  static void preparePool(Configuration configuration, Consumer<String> 
initSql,
+      Consumer<Map.Entry<String, String>> dataSourceProps) {
+    String url = MetastoreConf.getVar(configuration, 
MetastoreConf.ConfVars.CONNECT_URL_KEY);
+    DatabaseProduct dbProduct = DatabaseProduct.determineDatabaseProduct(url, 
configuration);
+    String s = dbProduct.getPrepareTxnStmt();
+    if (s != null) {
+      initSql.accept(s);
+    }
+    Map<String, String> properties = dbProduct.getDataSourceProperties();
+    properties.entrySet().forEach(dataSourceProps);
+  }
+
   class DataSourceNameConfigurator implements Closeable {
     static final String DATA_SOURCE_NAME = 
"metastore.DataSourceProvider.pool.name";
     private final Configuration configuration;
diff --git 
a/standalone-metastore/metastore-server/src/main/java/org/apache/hadoop/hive/metastore/datasource/DbCPDataSourceProvider.java
 
b/standalone-metastore/metastore-server/src/main/java/org/apache/hadoop/hive/metastore/datasource/DbCPDataSourceProvider.java
index 92160541a50..aa0e6c00a54 100644
--- 
a/standalone-metastore/metastore-server/src/main/java/org/apache/hadoop/hive/metastore/datasource/DbCPDataSourceProvider.java
+++ 
b/standalone-metastore/metastore-server/src/main/java/org/apache/hadoop/hive/metastore/datasource/DbCPDataSourceProvider.java
@@ -20,7 +20,6 @@
 import java.sql.SQLException;
 import java.time.Duration;
 import java.util.Collections;
-import java.util.Map;
 
 import javax.sql.DataSource;
 
@@ -32,7 +31,6 @@
 import org.apache.commons.pool2.impl.BaseObjectPoolConfig;
 import org.apache.commons.pool2.impl.GenericObjectPool;
 import org.apache.hadoop.conf.Configuration;
-import org.apache.hadoop.hive.metastore.DatabaseProduct;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -58,7 +56,7 @@ public class DbCPDataSourceProvider implements 
DataSourceProvider {
 
   @SuppressWarnings({ "rawtypes", "unchecked" })
   @Override
-  public DataSource create(Configuration hdpConfig, int maxPoolSize) throws 
SQLException {
+  public DataSource createPool(Configuration hdpConfig, int maxPoolSize) 
throws SQLException {
     String poolName = DataSourceProvider.getDataSourceName(hdpConfig);
     LOG.info("Creating dbcp connection pool for the MetaStore, maxPoolSize: 
{}, name: {}", maxPoolSize, poolName);
 
@@ -73,12 +71,6 @@ public DataSource create(Configuration hdpConfig, int 
maxPoolSize) throws SQLExc
     dbcpDs.setDefaultReadOnly(false);
     dbcpDs.setDefaultAutoCommit(true);
 
-    DatabaseProduct dbProduct =  
DatabaseProduct.determineDatabaseProduct(driverUrl, hdpConfig);
-    Map<String, String> props = dbProduct.getDataSourceProperties();
-    for (Map.Entry<String, String> kv : props.entrySet()) {
-      dbcpDs.setConnectionProperties(kv.getKey() + "=" + kv.getValue());
-    }
-
     long connectionTimeout = hdpConfig.getLong(CONNECTION_TIMEOUT_PROPERTY, 
30000L);
     int connectionMaxIlde = hdpConfig.getInt(CONNECTION_MAX_IDLE_PROPERTY, 8);
     int connectionMinIlde = hdpConfig.getInt(CONNECTION_MIN_IDLE_PROPERTY, 0);
@@ -127,9 +119,17 @@ public DataSource create(Configuration hdpConfig, int 
maxPoolSize) throws SQLExc
         objectPool.setSoftMinEvictableIdleDuration(Duration.ofMillis(600 * 
1000));
       }
     }
-    String stmt = dbProduct.getPrepareTxnStmt();
-    if (stmt != null) {
-      
poolableConnFactory.setConnectionInitSql(Collections.singletonList(stmt));
+    StringBuilder connectionProperties = new StringBuilder();
+    DataSourceProvider.preparePool(hdpConfig,
+        stmt -> 
poolableConnFactory.setConnectionInitSql(Collections.singletonList(stmt)),
+        kv -> {
+          if (!connectionProperties.isEmpty()) {
+            connectionProperties.append(';');
+          }
+          
connectionProperties.append(kv.getKey()).append('=').append(kv.getValue());
+        });
+    if (!connectionProperties.isEmpty()) {
+      dbcpDs.setConnectionProperties(connectionProperties.toString());
     }
     return new PoolingDataSource(objectPool);
   }
diff --git 
a/standalone-metastore/metastore-server/src/main/java/org/apache/hadoop/hive/metastore/datasource/HikariCPDataSourceProvider.java
 
b/standalone-metastore/metastore-server/src/main/java/org/apache/hadoop/hive/metastore/datasource/HikariCPDataSourceProvider.java
index 91f1ea3d557..9958d5303fa 100644
--- 
a/standalone-metastore/metastore-server/src/main/java/org/apache/hadoop/hive/metastore/datasource/HikariCPDataSourceProvider.java
+++ 
b/standalone-metastore/metastore-server/src/main/java/org/apache/hadoop/hive/metastore/datasource/HikariCPDataSourceProvider.java
@@ -21,7 +21,6 @@
 import com.zaxxer.hikari.HikariConfig;
 import com.zaxxer.hikari.HikariDataSource;
 import org.apache.hadoop.conf.Configuration;
-import org.apache.hadoop.hive.metastore.DatabaseProduct;
 import org.apache.hadoop.hive.metastore.metrics.Metrics;
 import org.apache.hadoop.hive.metastore.utils.StringUtils;
 import org.slf4j.Logger;
@@ -29,7 +28,6 @@
 
 import javax.sql.DataSource;
 import java.sql.SQLException;
-import java.util.Map;
 import java.util.Properties;
 
 /**
@@ -42,7 +40,7 @@ public class HikariCPDataSourceProvider implements 
DataSourceProvider {
   static final String HIKARI = "hikaricp";
 
   @Override
-  public DataSource create(Configuration hdpConfig, int maxPoolSize) throws 
SQLException {
+  public DataSource createPool(Configuration hdpConfig, int maxPoolSize) 
throws SQLException {
     String poolName = DataSourceProvider.getDataSourceName(hdpConfig);
     LOG.info("Creating Hikari connection pool for the MetaStore, maxPoolSize: 
{}, name: {}", maxPoolSize, poolName);
 
@@ -77,19 +75,8 @@ public DataSource create(Configuration hdpConfig, int 
maxPoolSize) throws SQLExc
       config.setMinimumIdle(Math.min(maxPoolSize, minimumIdle));
     }
 
-    DatabaseProduct dbProduct =  
DatabaseProduct.determineDatabaseProduct(driverUrl, hdpConfig);
-
-    String s = dbProduct.getPrepareTxnStmt();
-    if (s!= null) {
-      config.setConnectionInitSql(s);
-    }
-
-    Map<String, String> props = dbProduct.getDataSourceProperties();
-
-    for ( Map.Entry<String, String> kv : props.entrySet()) {
-      config.addDataSourceProperty(kv.getKey(), kv.getValue());
-    }
-
+    DataSourceProvider.preparePool(hdpConfig, s -> 
config.setConnectionInitSql(s),
+        entry ->  config.addDataSourceProperty(entry.getKey(), 
entry.getValue()));
     return new HikariDataSource(initMetrics(config));
   }
 
diff --git 
a/standalone-metastore/metastore-server/src/main/java/org/apache/hadoop/hive/metastore/datasource/MetastoreConnection.java
 
b/standalone-metastore/metastore-server/src/main/java/org/apache/hadoop/hive/metastore/datasource/MetastoreConnection.java
new file mode 100644
index 00000000000..22e7795838e
--- /dev/null
+++ 
b/standalone-metastore/metastore-server/src/main/java/org/apache/hadoop/hive/metastore/datasource/MetastoreConnection.java
@@ -0,0 +1,338 @@
+/*
+ * 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.hadoop.hive.metastore.datasource;
+
+import java.sql.Array;
+import java.sql.Blob;
+import java.sql.CallableStatement;
+import java.sql.Clob;
+import java.sql.Connection;
+import java.sql.DatabaseMetaData;
+import java.sql.NClob;
+import java.sql.PreparedStatement;
+import java.sql.SQLClientInfoException;
+import java.sql.SQLException;
+import java.sql.SQLWarning;
+import java.sql.SQLXML;
+import java.sql.Savepoint;
+import java.sql.Statement;
+import java.sql.Struct;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Properties;
+import java.util.concurrent.Executor;
+
+import org.apache.hadoop.conf.Configuration;
+
+public final class MetastoreConnection implements Connection {
+  private final Configuration configuration;
+  private final Connection delegate;
+
+  MetastoreConnection(Connection connection, Configuration conf) {
+    this.delegate = Objects.requireNonNull(connection, "delegate connection");
+    this.configuration = Objects.requireNonNull(conf, "configuration");
+  }
+
+  @Override
+  public Statement createStatement() throws SQLException {
+    return MetastoreStatement.getProxyStatement(configuration, 
delegate.createStatement(), null);
+  }
+
+  @Override
+  public PreparedStatement prepareStatement(String sql) throws SQLException {
+    return MetastoreStatement.getProxyStatement(configuration, 
delegate.prepareStatement(sql), sql);
+  }
+
+  @Override
+  public CallableStatement prepareCall(String sql) throws SQLException {
+    return MetastoreStatement.getProxyStatement(configuration, 
delegate.prepareCall(sql), sql);
+  }
+
+  @Override
+  public String nativeSQL(String sql) throws SQLException {
+    return delegate.nativeSQL(sql);
+  }
+
+  @Override
+  public void setAutoCommit(boolean autoCommit) throws SQLException {
+    delegate.setAutoCommit(autoCommit);
+  }
+
+  @Override
+  public boolean getAutoCommit() throws SQLException {
+    return delegate.getAutoCommit();
+  }
+
+  @Override
+  public void commit() throws SQLException {
+    delegate.commit();
+  }
+
+  @Override
+  public void rollback() throws SQLException {
+    delegate.rollback();
+  }
+
+  @Override
+  public void close() throws SQLException {
+    delegate.close();
+  }
+
+  @Override
+  public boolean isClosed() throws SQLException {
+    return delegate.isClosed();
+  }
+
+  @Override
+  public DatabaseMetaData getMetaData() throws SQLException {
+    return delegate.getMetaData();
+  }
+
+  @Override
+  public void setReadOnly(boolean readOnly) throws SQLException {
+    delegate.setReadOnly(readOnly);
+  }
+
+  @Override
+  public boolean isReadOnly() throws SQLException {
+    return delegate.isReadOnly();
+  }
+
+  @Override
+  public void setCatalog(String catalog) throws SQLException {
+    delegate.setCatalog(catalog);
+  }
+
+  @Override
+  public String getCatalog() throws SQLException {
+    return delegate.getCatalog();
+  }
+
+  @Override
+  public void setTransactionIsolation(int level) throws SQLException {
+    delegate.setTransactionIsolation(level);
+  }
+
+  @Override
+  public int getTransactionIsolation() throws SQLException {
+    return delegate.getTransactionIsolation();
+  }
+
+  @Override
+  public SQLWarning getWarnings() throws SQLException {
+    return delegate.getWarnings();
+  }
+
+  @Override
+  public void clearWarnings() throws SQLException {
+    delegate.clearWarnings();
+  }
+
+  @Override
+  public Statement createStatement(int resultSetType, int 
resultSetConcurrency) throws SQLException {
+    return MetastoreStatement.getProxyStatement(configuration,
+        delegate.createStatement(resultSetType, resultSetConcurrency), null);
+  }
+
+  @Override
+  public PreparedStatement prepareStatement(String sql, int resultSetType, int 
resultSetConcurrency)
+      throws SQLException {
+    return MetastoreStatement.getProxyStatement(configuration,
+        delegate.prepareStatement(sql, resultSetType, resultSetConcurrency), 
sql);
+  }
+
+  @Override
+  public CallableStatement prepareCall(String sql, int resultSetType, int 
resultSetConcurrency) throws SQLException {
+    return MetastoreStatement.getProxyStatement(configuration,
+        delegate.prepareCall(sql, resultSetType, resultSetConcurrency), sql);
+  }
+
+  @Override
+  public Map<String, Class<?>> getTypeMap() throws SQLException {
+    return delegate.getTypeMap();
+  }
+
+  @Override
+  public void setTypeMap(Map<String, Class<?>> map) throws SQLException {
+    delegate.setTypeMap(map);
+  }
+
+  @Override
+  public void setHoldability(int holdability) throws SQLException {
+    delegate.setHoldability(holdability);
+  }
+
+  @Override
+  public int getHoldability() throws SQLException {
+    return delegate.getHoldability();
+  }
+
+  @Override
+  public Savepoint setSavepoint() throws SQLException {
+    return delegate.setSavepoint();
+  }
+
+  @Override
+  public Savepoint setSavepoint(String name) throws SQLException {
+    return delegate.setSavepoint(name);
+  }
+
+  @Override
+  public void rollback(Savepoint savepoint) throws SQLException {
+    delegate.rollback(savepoint);
+  }
+
+  @Override
+  public void releaseSavepoint(Savepoint savepoint) throws SQLException {
+    delegate.releaseSavepoint(savepoint);
+  }
+
+  @Override
+  public Statement createStatement(int resultSetType, int 
resultSetConcurrency, int resultSetHoldability)
+      throws SQLException {
+    return MetastoreStatement.getProxyStatement(configuration,
+        delegate.createStatement(resultSetType, resultSetConcurrency, 
resultSetHoldability), null);
+  }
+
+  @Override
+  public PreparedStatement prepareStatement(String sql, int resultSetType, int 
resultSetConcurrency,
+      int resultSetHoldability) throws SQLException {
+    return MetastoreStatement.getProxyStatement(configuration,
+        delegate.prepareStatement(sql, resultSetType, resultSetConcurrency, 
resultSetHoldability), sql);
+  }
+
+  @Override
+  public CallableStatement prepareCall(String sql, int resultSetType, int 
resultSetConcurrency,
+      int resultSetHoldability) throws SQLException {
+    return MetastoreStatement.getProxyStatement(configuration,
+        delegate.prepareCall(sql, resultSetType, resultSetConcurrency, 
resultSetHoldability), sql);
+  }
+
+  @Override
+  public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) 
throws SQLException {
+    return MetastoreStatement.getProxyStatement(configuration, 
delegate.prepareStatement(sql, autoGeneratedKeys), sql);
+  }
+
+  @Override
+  public PreparedStatement prepareStatement(String sql, int[] columnIndexes) 
throws SQLException {
+    return MetastoreStatement.getProxyStatement(configuration, 
delegate.prepareStatement(sql, columnIndexes), sql);
+  }
+
+  @Override
+  public PreparedStatement prepareStatement(String sql, String[] columnNames) 
throws SQLException {
+    return MetastoreStatement.getProxyStatement(configuration, 
delegate.prepareStatement(sql, columnNames), sql);
+  }
+
+  @Override
+  public Clob createClob() throws SQLException {
+    return delegate.createClob();
+  }
+
+  @Override
+  public Blob createBlob() throws SQLException {
+    return delegate.createBlob();
+  }
+
+  @Override
+  public NClob createNClob() throws SQLException {
+    return delegate.createNClob();
+  }
+
+  @Override
+  public SQLXML createSQLXML() throws SQLException {
+    return delegate.createSQLXML();
+  }
+
+  @Override
+  public boolean isValid(int timeout) throws SQLException {
+    return delegate.isValid(timeout);
+  }
+
+  @Override
+  public void setClientInfo(String name, String value) throws 
SQLClientInfoException {
+    delegate.setClientInfo(name, value);
+  }
+
+  @Override
+  public void setClientInfo(Properties properties) throws 
SQLClientInfoException {
+    delegate.setClientInfo(properties);
+  }
+
+  @Override
+  public String getClientInfo(String name) throws SQLException {
+    return delegate.getClientInfo(name);
+  }
+
+  @Override
+  public Properties getClientInfo() throws SQLException {
+    return delegate.getClientInfo();
+  }
+
+  @Override
+  public Array createArrayOf(String typeName, Object[] elements) throws 
SQLException {
+    return delegate.createArrayOf(typeName, elements);
+  }
+
+  @Override
+  public Struct createStruct(String typeName, Object[] attributes) throws 
SQLException {
+    return delegate.createStruct(typeName, attributes);
+  }
+
+  @Override
+  public void setSchema(String schema) throws SQLException {
+    delegate.setSchema(schema);
+  }
+
+  @Override
+  public String getSchema() throws SQLException {
+    return delegate.getSchema();
+  }
+
+  @Override
+  public void abort(Executor executor) throws SQLException {
+    delegate.abort(executor);
+  }
+
+  @Override
+  public void setNetworkTimeout(Executor executor, int milliseconds) throws 
SQLException {
+    delegate.setNetworkTimeout(executor, milliseconds);
+  }
+
+  @Override
+  public int getNetworkTimeout() throws SQLException {
+    return delegate.getNetworkTimeout();
+  }
+
+  @Override
+  public <T> T unwrap(Class<T> iface) throws SQLException {
+    if (iface.isInstance(this)) {
+      return iface.cast(this);
+    }
+    return delegate.unwrap(iface);
+  }
+
+  @Override
+  public boolean isWrapperFor(Class<?> iface) throws SQLException {
+    return iface.isInstance(this) || delegate.isWrapperFor(iface);
+  }
+
+  public Configuration getConfiguration() {
+    return configuration;
+  }
+}
diff --git 
a/standalone-metastore/metastore-server/src/main/java/org/apache/hadoop/hive/metastore/datasource/MetastoreStatement.java
 
b/standalone-metastore/metastore-server/src/main/java/org/apache/hadoop/hive/metastore/datasource/MetastoreStatement.java
new file mode 100644
index 00000000000..051b251bd82
--- /dev/null
+++ 
b/standalone-metastore/metastore-server/src/main/java/org/apache/hadoop/hive/metastore/datasource/MetastoreStatement.java
@@ -0,0 +1,213 @@
+/*
+ * 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.hadoop.hive.metastore.datasource;
+
+import com.codahale.metrics.Counter;
+import com.codahale.metrics.Timer;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.lang.reflect.UndeclaredThrowableException;
+import java.sql.Statement;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.commons.lang3.ClassUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.hive.metastore.HMSHandlerContext;
+import org.apache.hadoop.hive.metastore.api.MetaException;
+import org.apache.hadoop.hive.metastore.conf.MetastoreConf;
+import org.apache.hadoop.hive.metastore.metrics.Metrics;
+import org.apache.hadoop.hive.metastore.metrics.MetricsConstants;
+import org.apache.hadoop.hive.metastore.utils.JavaUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@SuppressWarnings("unchecked")
+public final class MetastoreStatement implements InvocationHandler {
+  private static final Logger LOG = 
LoggerFactory.getLogger(MetastoreStatement.class);
+  static final String EXEC_HOOK = "metastore.jdbc.execution.hook";
+  static final Set<String> QUERY_EXECUTION =
+      Set.of("executeQuery", "executeUpdate", "execute", "executeBatch");
+
+  private final String rawSql;
+  private final Statement delegate;
+  private final Configuration configuration;
+  private final MetastoreStatementHook hook;
+  private final long slowQueryThreshold;
+
+  private MetastoreStatement(Configuration conf, Statement statement, String 
rawSql) {
+    this.configuration = Objects.requireNonNull(conf);
+    this.rawSql = rawSql;
+    this.delegate = Objects.requireNonNull(statement);
+    this.hook = resolveHook(conf);
+    this.slowQueryThreshold = MetastoreConf.getTimeVar(configuration,
+        MetastoreConf.ConfVars.METASTORE_JDBC_SLOW_QUERY_THRESHOLD, 
TimeUnit.MILLISECONDS);
+  }
+
+  private MetastoreStatementHook resolveHook(Configuration conf) {
+    String className = conf.get(EXEC_HOOK, "");
+    if (StringUtils.isEmpty(className)) {
+      return new ThriftApiProfiler(configuration);
+    }
+    try {
+      return JavaUtils.newInstance(JavaUtils.getClass(className.trim(), 
MetastoreStatementHook.class),
+          new Class[] {Configuration.class}, new Object[] {conf});
+    } catch (MetaException e) {
+      throw new RuntimeException(e.getMessage(), e);
+    }
+  }
+
+  public static <T extends Statement> T getProxyStatement(Configuration 
configuration, T delegate, String rawSql) {
+    MetastoreStatement handler = new MetastoreStatement(configuration, 
delegate, rawSql);
+    return (T) Proxy.newProxyInstance(JavaUtils.getClassLoader(),
+        ClassUtils.getAllInterfaces(delegate.getClass()).toArray(new 
Class[0]), handler);
+  }
+
+  @Override
+  public Object invoke(Object proxy, Method method, Object[] args) throws 
Throwable {
+    Timer.Context ctx = null;
+    try {
+      boolean monitor = hook.profile(rawSql, method, args);
+      if (Metrics.getRegistry() != null && monitor) {
+        String metricName = hook.getMetricName(method);
+        Timer timer = Metrics.getOrCreateTimer(metricName);
+        if (timer != null) {
+          ctx = timer.time();
+        }
+      }
+      long start = System.currentTimeMillis();
+      hook.preRun(method, args);
+      Object result = method.invoke(delegate, args);
+      hook.postRun(method, args, result);
+      long timeSpent = System.currentTimeMillis() - start;
+      if (monitor) {
+        String statement = rawSql != null ? rawSql
+            : (args != null && args.length > 0 ? (String) args[0] : "no sql 
found");
+        LOG.debug("Jdbc query: {} completed in {} ms", statement, timeSpent);
+      }
+      if (isQueryExecution(method)) {
+        HMSHandlerContext.getCallCtx().ifPresent(callCtx -> 
callCtx.recordJdbcExecution(timeSpent));
+        logExecution(timeSpent, rawSql, method, args);
+      }
+      return result;
+    } catch (InvocationTargetException | UndeclaredThrowableException e) {
+      throw e.getCause() != null ? e.getCause() : e;
+    } finally {
+      if (ctx != null) {
+        ctx.stop();
+      }
+    }
+  }
+
+  private void logExecution(long timeSpent, String sql, Method method, 
Object[] args) {
+    if (isSlowExecution(timeSpent)) {
+      Object[] printableArgs = args;
+      if (args != null && args.length > 10) {
+        printableArgs = new Object[10];
+        System.arraycopy(args, 0, printableArgs, 0, 7);
+        System.arraycopy(args, args.length - 2, printableArgs, 8, 2);
+        printableArgs[7] = "....";
+      }
+      LOG.warn("Slow execution detected, method: {}, time taken: {} ms, args 
size: {}, args: {}{}",
+          method.getName(), timeSpent,
+          args != null ? args.length : 0, Arrays.toString(printableArgs), sql 
!= null ? ", sql: " + sql : "");
+      if (Metrics.getRegistry() != null) {
+        Counter slowQueries = 
Metrics.getOrCreateCounter(MetricsConstants.JDBC_SLOW_QUERIES);
+        slowQueries.inc();
+      }
+    }
+  }
+
+  private boolean isSlowExecution(long timeSpent) {
+    return slowQueryThreshold > 0 && timeSpent > slowQueryThreshold;
+  }
+
+  private static boolean isQueryExecution(Method method) {
+    return QUERY_EXECUTION.contains(method.getName());
+  }
+
+  public interface MetastoreStatementHook {
+    /**
+     * Whether should monitor the current call, this method gives a chance to 
profile a specific pattern of queries.
+     * For example, we use {@link ThriftApiProfiler} to profile the queries 
originated from a set of specific APIs.
+     * @param sql The sql being executed, it might be null for {@link 
Statement#execute}, for this case
+     *            need to obtain the sql from args, the method input.
+     * @param method Method which is being called
+     * @param args The method input
+     * @return true for profiling this call, false otherwise
+     */
+    boolean profile(String sql, Method method, Object[] args);
+
+    String getMetricName(Method method);
+
+    default void preRun(Method method, Object[] args) {
+    }
+
+    default void postRun(Method method, Object[] args, Object result) {
+    }
+  }
+
+  /**
+   * This class is used to profile the statement originated from specific 
thrift API calls
+   */
+  public static class ThriftApiProfiler implements MetastoreStatementHook {
+    private final Configuration configuration;
+    private final Set<String> profiledApis;
+
+    public ThriftApiProfiler(Configuration configuration) {
+      this.configuration = Objects.requireNonNull(configuration);
+      this.profiledApis = getProfiledApis();
+    }
+
+    private Set<String> getProfiledApis() {
+      String thriftApis = MetastoreConf.getVar(configuration,
+          MetastoreConf.ConfVars.METASTORE_PROFILE_JDBC_THRIFT_APIS);
+      Set<String> profiledApis = new HashSet<>();
+      for (String thriftApi : thriftApis.split(",")) {
+        String trimmedThriftApi = thriftApi.trim();
+        if (!trimmedThriftApi.isEmpty()) {
+          profiledApis.add(trimmedThriftApi);
+        }
+      }
+      return profiledApis;
+    }
+
+    @Override
+    public boolean profile(String sql, Method method, Object[] args) {
+      if (!isQueryExecution(method) || profiledApis.isEmpty()) {
+        return false;
+      }
+      Optional<HMSHandlerContext.CallCtx> ctxCall = 
HMSHandlerContext.getCallCtx();
+      return ctxCall.isPresent() && 
profiledApis.contains(ctxCall.get().methodName());
+    }
+
+    @Override
+    public String getMetricName(Method method) {
+      return MetricsConstants.JDBC_EXECUTION + method.getName();
+    }
+  }
+}
diff --git 
a/standalone-metastore/metastore-server/src/main/java/org/apache/hadoop/hive/metastore/datasource/ProfilingDataSource.java
 
b/standalone-metastore/metastore-server/src/main/java/org/apache/hadoop/hive/metastore/datasource/ProfilingDataSource.java
new file mode 100644
index 00000000000..f142de5d7ef
--- /dev/null
+++ 
b/standalone-metastore/metastore-server/src/main/java/org/apache/hadoop/hive/metastore/datasource/ProfilingDataSource.java
@@ -0,0 +1,103 @@
+/*
+ * 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.hadoop.hive.metastore.datasource;
+
+import java.io.PrintWriter;
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.sql.SQLFeatureNotSupportedException;
+import java.util.Objects;
+import java.util.logging.Logger;
+
+import javax.sql.DataSource;
+
+import org.apache.hadoop.conf.Configuration;
+
+/**
+ * Decorates a {@link DataSource} to wrap checked-out connections for JDBC 
profiling.
+ */
+public class ProfilingDataSource implements DataSource, AutoCloseable {
+
+  private final DataSource delegate;
+  private final Configuration configuration;
+
+  public ProfilingDataSource(DataSource delegate, Configuration configuration) 
{
+    this.delegate = Objects.requireNonNull(delegate, "delegate datasource");
+    this.configuration = Objects.requireNonNull(configuration, 
"configuration");
+  }
+
+  public DataSource getDelegate() {
+    return delegate;
+  }
+
+  @Override
+  public Connection getConnection() throws SQLException {
+    return new MetastoreConnection(delegate.getConnection(), configuration);
+  }
+
+  @Override
+  public Connection getConnection(String username, String password) throws 
SQLException {
+    return new MetastoreConnection(delegate.getConnection(username, password), 
configuration);
+  }
+
+  @Override
+  public PrintWriter getLogWriter() throws SQLException {
+    return delegate.getLogWriter();
+  }
+
+  @Override
+  public void setLogWriter(PrintWriter out) throws SQLException {
+    delegate.setLogWriter(out);
+  }
+
+  @Override
+  public void setLoginTimeout(int seconds) throws SQLException {
+    delegate.setLoginTimeout(seconds);
+  }
+
+  @Override
+  public int getLoginTimeout() throws SQLException {
+    return delegate.getLoginTimeout();
+  }
+
+  @Override
+  public Logger getParentLogger() throws SQLFeatureNotSupportedException {
+    return delegate.getParentLogger();
+  }
+
+  @Override
+  public <T> T unwrap(Class<T> iface) throws SQLException {
+    if (iface.isInstance(this)) {
+      return iface.cast(this);
+    }
+    return delegate.unwrap(iface);
+  }
+
+  @Override
+  public boolean isWrapperFor(Class<?> iface) throws SQLException {
+    return iface.isInstance(this) || delegate.isWrapperFor(iface);
+  }
+
+  @Override
+  public void close() throws Exception {
+    if (delegate instanceof AutoCloseable) {
+      ((AutoCloseable) delegate).close();
+    }
+  }
+}
diff --git 
a/standalone-metastore/metastore-server/src/main/java/org/apache/hadoop/hive/metastore/handler/AbstractRequestHandler.java
 
b/standalone-metastore/metastore-server/src/main/java/org/apache/hadoop/hive/metastore/handler/AbstractRequestHandler.java
index 0f078f20040..01881a8721e 100644
--- 
a/standalone-metastore/metastore-server/src/main/java/org/apache/hadoop/hive/metastore/handler/AbstractRequestHandler.java
+++ 
b/standalone-metastore/metastore-server/src/main/java/org/apache/hadoop/hive/metastore/handler/AbstractRequestHandler.java
@@ -25,6 +25,7 @@
 import java.io.IOException;
 import java.lang.reflect.Modifier;
 import java.util.Map;
+import java.util.Optional;
 import java.util.Set;
 import java.util.UUID;
 import java.util.concurrent.CancellationException;
@@ -41,6 +42,7 @@
 
 import org.apache.commons.lang3.StringUtils;
 import org.apache.commons.lang3.reflect.MethodUtils;
+import org.apache.hadoop.hive.metastore.HMSHandlerContext;
 import org.apache.hadoop.hive.metastore.IHMSHandler;
 import org.apache.hadoop.hive.metastore.api.AsyncOperationResp;
 import org.apache.hadoop.hive.metastore.api.MetaException;
@@ -139,21 +141,26 @@ private Future<Result> executeRequest() {
       timerContext = null;
     }
 
+    Optional<HMSHandlerContext.CallCtx> callCtx = 
HMSHandlerContext.getCallCtx();
     Future<Result> resultFuture = executor.submit(() -> {
+      if (async) {
+        callCtx.ifPresent(HMSHandlerContext::setCallCtx);
+      }
       A resultV = null;
-      beforeExecute();
       try {
-        resultV = execute();
-      } finally {
+        beforeExecute();
         try {
-          if (async) {
-            REQUEST_CLEANER.schedule(() -> ID_TO_HANDLER.remove(id), 1, 
TimeUnit.HOURS);
-          }
-          afterExecute(resultV);
+          resultV = execute();
         } finally {
-          if (timerContext != null) {
-            timerContext.stop();
-          }
+          afterExecute(resultV);
+        }
+      } finally {
+        if (async) {
+          HMSHandlerContext.clear();
+          REQUEST_CLEANER.schedule(() -> ID_TO_HANDLER.remove(id), 1, 
TimeUnit.HOURS);
+        }
+        if (timerContext != null) {
+          timerContext.stop();
         }
       }
       return async ? resultV.shrinkIfNecessary() : resultV;
diff --git 
a/standalone-metastore/metastore-server/src/main/java/org/apache/hadoop/hive/metastore/metrics/MetricsConstants.java
 
b/standalone-metastore/metastore-server/src/main/java/org/apache/hadoop/hive/metastore/metrics/MetricsConstants.java
index 46512ab3c4b..cd8f36452de 100644
--- 
a/standalone-metastore/metastore-server/src/main/java/org/apache/hadoop/hive/metastore/metrics/MetricsConstants.java
+++ 
b/standalone-metastore/metastore-server/src/main/java/org/apache/hadoop/hive/metastore/metrics/MetricsConstants.java
@@ -71,6 +71,8 @@ public class MetricsConstants {
   public static final String DELETE_TOTAL_PARTITIONS = 
"delete_total_count_partitions";
 
   public static final String DIRECTSQL_ERRORS = "directsql_errors";
+  public static final String JDBC_SLOW_QUERIES = "jdbc_slow_queries";
+  public static final String JDBC_EXECUTION = "jdbc_";
 
   public static final String JVM_PAUSE_INFO = "jvm.pause.info-threshold";
   public static final String JVM_PAUSE_WARN = "jvm.pause.warn-threshold";
diff --git 
a/standalone-metastore/metastore-server/src/test/java/org/apache/hadoop/hive/metastore/datasource/TestProfilingDataSource.java
 
b/standalone-metastore/metastore-server/src/test/java/org/apache/hadoop/hive/metastore/datasource/TestProfilingDataSource.java
new file mode 100644
index 00000000000..78ae1aa13af
--- /dev/null
+++ 
b/standalone-metastore/metastore-server/src/test/java/org/apache/hadoop/hive/metastore/datasource/TestProfilingDataSource.java
@@ -0,0 +1,164 @@
+/*
+ * 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.hadoop.hive.metastore.datasource;
+
+import com.codahale.metrics.Counter;
+import com.codahale.metrics.Timer;
+import com.zaxxer.hikari.HikariDataSource;
+
+import javax.sql.DataSource;
+import java.lang.reflect.Method;
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.Statement;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicLong;
+
+import org.apache.commons.dbcp2.PoolingDataSource;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.hive.metastore.HMSHandlerContext;
+import org.apache.hadoop.hive.metastore.annotation.MetastoreUnitTest;
+import org.apache.hadoop.hive.metastore.conf.MetastoreConf;
+import org.apache.hadoop.hive.metastore.metrics.Metrics;
+import org.apache.hadoop.hive.metastore.metrics.MetricsConstants;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+
+@Category(MetastoreUnitTest.class)
+public class TestProfilingDataSource {
+  private Configuration conf;
+  private Counter slowQuery;
+
+  @Before
+  public void init() {
+    conf = MetastoreConf.newMetastoreConf();
+    conf.set(MetastoreStatement.EXEC_HOOK, 
MetastoreStatementTestHook.class.getName());
+    MetastoreConf.setTimeVar(conf, 
MetastoreConf.ConfVars.METASTORE_JDBC_SLOW_QUERY_THRESHOLD, 200, 
TimeUnit.MILLISECONDS);
+    MetastoreConf.setBoolVar(conf, MetastoreConf.ConfVars.METRICS_ENABLED, 
true);
+    MetastoreConf.setBoolVar(conf, 
MetastoreConf.ConfVars.METASTORE_PROFILE_JDBC_EXECUTION, true);
+    MetastoreConf.setVar(conf, 
MetastoreConf.ConfVars.METASTORE_PROFILE_JDBC_THRIFT_APIS, 
"test_metastore_statement");
+    MetastoreConf.setVar(conf, MetastoreConf.ConfVars.CONNECTION_USER_NAME, 
"dummyUser");
+    MetastoreConf.setVar(conf, MetastoreConf.ConfVars.PWD, "dummyPass");
+    conf.unset(MetastoreConf.ConfVars.CONNECTION_POOLING_TYPE.getVarname());
+
+    Metrics.initialize(conf);
+    slowQuery = Metrics.getOrCreateCounter(MetricsConstants.JDBC_SLOW_QUERIES);
+  }
+
+  @Test
+  public void testDefaultHikariCp() throws Exception {
+    MetastoreConf.setVar(conf, MetastoreConf.ConfVars.CONNECTION_POOLING_TYPE, 
HikariCPDataSourceProvider.HIKARI);
+
+    DataSourceProvider dsp = 
DataSourceProviderFactory.tryGetDataSourceProviderOrNull(conf);
+    Assert.assertNotNull(dsp);
+    DataSource ds = dsp.create(conf);
+    Assert.assertTrue(ds instanceof ProfilingDataSource);
+    Assert.assertTrue(((ProfilingDataSource) ds).getDelegate() instanceof 
HikariDataSource);
+    try (Connection connection = ds.getConnection()) {
+      verify(connection);
+    }
+  }
+
+  @Test
+  public void testDbCpDataSource() throws Exception {
+    MetastoreConf.setVar(conf, MetastoreConf.ConfVars.CONNECTION_POOLING_TYPE, 
DbCPDataSourceProvider.DBCP);
+
+    DataSourceProvider dsp = 
DataSourceProviderFactory.tryGetDataSourceProviderOrNull(conf);
+    Assert.assertNotNull(dsp);
+    DataSource ds = dsp.create(conf);
+    Assert.assertTrue(ds instanceof ProfilingDataSource);
+    Assert.assertTrue(((ProfilingDataSource) ds).getDelegate() instanceof 
PoolingDataSource);
+    try (Connection connection = ds.getConnection()) {
+      verify(connection);
+    }
+  }
+
+  private void verify(Connection connection) throws Exception {
+    // 
Assert.assertTrue(connection.unwrap(MetastoreConnection.class).delegate() 
instanceof EmbedConnection);
+    long slowNum = slowQuery.getCount();
+    Timer timer = 
Metrics.getOrCreateTimer(MetastoreStatementTestHook.TEST_METRIC_NAME);
+    Assert.assertNotNull(timer);
+    long timeCount = timer.getCount();
+    try (AutoCloseable sleep = 
MetastoreStatementTestHook.testConnection("test_metastore_statement", 300, 
connection)) {
+      try (Statement statement = connection.createStatement();
+           ResultSet rs = statement.executeQuery("VALUES 1")) {
+        Assert.assertTrue(rs.next());
+      }
+      HMSHandlerContext.getCallCtx().ifPresent(ctx -> {
+        Assert.assertEquals(1, ctx.getQueryCount());
+        Assert.assertTrue(ctx.getTotalTime() >= 300);
+        Assert.assertTrue(ctx.getMaxJdbcTimeMs() >= 300);
+      });
+    }
+    Assert.assertEquals(slowNum + 1, slowQuery.getCount());
+    Assert.assertEquals(timeCount + 1, timer.getCount());
+    Assert.assertTrue(timer.getSnapshot().getMean() > 
TimeUnit.MILLISECONDS.toNanos(300));
+
+    try (AutoCloseable sleep = 
MetastoreStatementTestHook.testConnection("test_statement_outside", 300, 
connection)) {
+      try (Statement statement = connection.createStatement();
+           ResultSet rs = statement.executeQuery("VALUES 1")) {
+        Assert.assertTrue(rs.next());
+      }
+    }
+    Assert.assertEquals(slowNum + 2, slowQuery.getCount());
+    Assert.assertEquals(timeCount + 1, timer.getCount());
+  }
+
+  public static class MetastoreStatementTestHook extends 
MetastoreStatement.ThriftApiProfiler {
+    static final String TEST_METRIC_NAME = "MetastoreStatementTestHook_" + 
System.currentTimeMillis();
+    static final String SLEEP_MILLIS = "MetastoreStatementTestHook.sleep.ms";
+    private final Configuration configuration;
+
+    public MetastoreStatementTestHook(Configuration configuration) {
+      super(configuration);
+      this.configuration = configuration;
+    }
+
+    @Override
+    public void preRun(Method method, Object[] args) {
+      long sleepMs = configuration.getLong(SLEEP_MILLIS, 0);
+      if (sleepMs > 0 &&
+          MetastoreStatement.QUERY_EXECUTION.contains(method.getName())) {
+        try {
+          Thread.sleep(sleepMs);
+        } catch (InterruptedException e) {
+          Thread.currentThread().interrupt();
+        }
+      }
+    }
+
+    @Override
+    public String getMetricName(Method method) {
+      return TEST_METRIC_NAME;
+    }
+
+    public static AutoCloseable testConnection(String method, long sleepMs, 
Connection connection)
+        throws Exception {
+      HMSHandlerContext.setCallCtx(new HMSHandlerContext.CallCtx(method));
+      Configuration configuration = 
connection.unwrap(MetastoreConnection.class).getConfiguration();
+      configuration.set(SLEEP_MILLIS, sleepMs + "");
+      return () -> {
+        HMSHandlerContext.setCallCtx(null);
+        configuration.unset(SLEEP_MILLIS);
+      };
+    }
+  }
+}

Reply via email to