You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@dubbo.apache.org by al...@apache.org on 2021/12/27 08:01:50 UTC

[dubbo] branch 3.0 updated: [3.0] Add Dubbo Invocation Profiler (#9373)

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

albumenj pushed a commit to branch 3.0
in repository https://gitbox.apache.org/repos/asf/dubbo.git


The following commit(s) were added to refs/heads/3.0 by this push:
     new 798840e  [3.0] Add Dubbo Invocation Profiler (#9373)
798840e is described below

commit 798840e8577650fa6ad24776b2028f0eb5c7c8a2
Author: Albumen Kevin <jh...@gmail.com>
AuthorDate: Mon Dec 27 16:01:41 2021 +0800

    [3.0] Add Dubbo Invocation Profiler (#9373)
    
    * [3.0] Add Profiler
    
    * opt
    
    * add nano
    
    * add qos
    
    * add log
    
    * Add default profiler
    
    * Add default profiler
    
    * Adapt CopyOfFilterChainNode
---
 .../rpc/cluster/filter/FilterChainBuilder.java     |  11 ++-
 .../cluster/support/AbstractClusterInvoker.java    |  17 +++-
 .../org/apache/dubbo/common/profiler/Profiler.java | 104 +++++++++++++++++++++
 .../dubbo/common/profiler/ProfilerEntry.java       |  71 ++++++++++++++
 .../dubbo/common/profiler/ProfilerSwitch.java      |  63 +++++++++++++
 .../apache/dubbo/common/profiler/ProfilerTest.java |  97 +++++++++++++++++++
 .../apache/dubbo/qos/command/impl/BaseOffline.java |   2 +-
 .../apache/dubbo/qos/command/impl/BaseOnline.java  |   2 +-
 .../qos/command/impl/DisableDetailProfiler.java    |  36 +++++++
 .../qos/command/impl/DisableSimpleProfiler.java    |  36 +++++++
 .../qos/command/impl/EnableDetailProfiler.java     |  36 +++++++
 .../qos/command/impl/EnableSimpleProfiler.java     |  36 +++++++
 .../qos/command/impl/SetProfilerWarnPercent.java   |  39 ++++++++
 .../org.apache.dubbo.qos.command.BaseCommand       |   5 +
 .../dubbo/qos/command/util/CommandHelperTest.java  |  11 +++
 .../apache/dubbo/rpc/InvocationProfilerUtils.java  |  77 +++++++++++++++
 .../dubbo/rpc/filter/ProfilerServerFilter.java     | 101 ++++++++++++++++++++
 .../apache/dubbo/rpc/protocol/AbstractInvoker.java |   2 +-
 .../dubbo/rpc/proxy/AbstractProxyInvoker.java      |  23 +++++
 .../dubbo/rpc/proxy/InvokerInvocationHandler.java  |  47 ++++++++++
 .../dubbo/internal/org.apache.dubbo.rpc.Filter     |   1 +
 .../dubbo/rpc/protocol/dubbo/DubboInvoker.java     |   2 +-
 .../dubbo/rpc/protocol/tri/TripleInvoker.java      |   2 +-
 23 files changed, 814 insertions(+), 7 deletions(-)

diff --git a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/filter/FilterChainBuilder.java b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/filter/FilterChainBuilder.java
index a329959..c01c5b6 100644
--- a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/filter/FilterChainBuilder.java
+++ b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/filter/FilterChainBuilder.java
@@ -24,6 +24,7 @@ import org.apache.dubbo.common.logger.LoggerFactory;
 import org.apache.dubbo.rpc.BaseFilter;
 import org.apache.dubbo.rpc.Filter;
 import org.apache.dubbo.rpc.Invocation;
+import org.apache.dubbo.rpc.InvocationProfilerUtils;
 import org.apache.dubbo.rpc.Invoker;
 import org.apache.dubbo.rpc.ListenableFilter;
 import org.apache.dubbo.rpc.Result;
@@ -50,10 +51,11 @@ public interface FilterChainBuilder {
 
     /**
      * Works on provider side
+     *
      * @param <T>
      * @param <TYPE>
      */
-    class FilterChainNode<T, TYPE extends Invoker<T>, FILTER extends BaseFilter> implements Invoker<T>{
+    class FilterChainNode<T, TYPE extends Invoker<T>, FILTER extends BaseFilter> implements Invoker<T> {
         TYPE originalInvoker;
         Invoker<T> nextNode;
         FILTER filter;
@@ -87,8 +89,10 @@ public interface FilterChainBuilder {
         public Result invoke(Invocation invocation) throws RpcException {
             Result asyncResult;
             try {
+                InvocationProfilerUtils.enterDetailProfiler(invocation, () -> "Filter " + filter.getClass().getName() + " invoke.");
                 asyncResult = filter.invoke(nextNode, invocation);
             } catch (Exception e) {
+                InvocationProfilerUtils.releaseDetailProfiler(invocation);
                 if (filter instanceof ListenableFilter) {
                     ListenableFilter listenableFilter = ((ListenableFilter) filter);
                     try {
@@ -108,6 +112,7 @@ public interface FilterChainBuilder {
 
             }
             return asyncResult.whenCompleteWithContext((r, t) -> {
+                InvocationProfilerUtils.releaseDetailProfiler(invocation);
                 if (filter instanceof ListenableFilter) {
                     ListenableFilter listenableFilter = ((ListenableFilter) filter);
                     Filter.Listener listener = listenableFilter.listener(invocation);
@@ -146,6 +151,7 @@ public interface FilterChainBuilder {
 
     /**
      * Works on consumer side
+     *
      * @param <T>
      * @param <TYPE>
      */
@@ -189,6 +195,7 @@ public interface FilterChainBuilder {
                 for (int i = filters.size() - 1; i >= 0; i--) {
                     FILTER filter = filters.get(i);
                     try {
+                        InvocationProfilerUtils.releaseDetailProfiler(invocation);
                         if (filter instanceof ListenableFilter) {
                             ListenableFilter listenableFilter = ((ListenableFilter) filter);
                             Filter.Listener listener = listenableFilter.listener(invocation);
@@ -310,8 +317,10 @@ public interface FilterChainBuilder {
         public Result invoke(Invocation invocation) throws RpcException {
             Result asyncResult;
             try {
+                InvocationProfilerUtils.enterDetailProfiler(invocation, () -> "Filter " + filter.getClass().getName() + " invoke.");
                 asyncResult = filter.invoke(nextNode, invocation);
             } catch (Exception e) {
+                InvocationProfilerUtils.releaseDetailProfiler(invocation);
                 if (filter instanceof ListenableFilter) {
                     ListenableFilter listenableFilter = ((ListenableFilter) filter);
                     try {
diff --git a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/support/AbstractClusterInvoker.java b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/support/AbstractClusterInvoker.java
index 42ad94d..5406ac6 100644
--- a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/support/AbstractClusterInvoker.java
+++ b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/support/AbstractClusterInvoker.java
@@ -22,10 +22,12 @@ import org.apache.dubbo.common.config.Configuration;
 import org.apache.dubbo.common.config.ConfigurationUtils;
 import org.apache.dubbo.common.logger.Logger;
 import org.apache.dubbo.common.logger.LoggerFactory;
+import org.apache.dubbo.common.profiler.ProfilerSwitch;
 import org.apache.dubbo.common.utils.CollectionUtils;
 import org.apache.dubbo.common.utils.NetUtils;
 import org.apache.dubbo.common.utils.StringUtils;
 import org.apache.dubbo.rpc.Invocation;
+import org.apache.dubbo.rpc.InvocationProfilerUtils;
 import org.apache.dubbo.rpc.Invoker;
 import org.apache.dubbo.rpc.Result;
 import org.apache.dubbo.rpc.RpcContext;
@@ -326,10 +328,19 @@ public abstract class AbstractClusterInvoker<T> implements ClusterInvoker<T> {
 //            ((RpcInvocation) invocation).addObjectAttachmentsIfAbsent(contextAttachments);
 //        }
 
+        InvocationProfilerUtils.enterDetailProfiler(invocation, () -> "Router route.");
         List<Invoker<T>> invokers = list(invocation);
+        InvocationProfilerUtils.releaseDetailProfiler(invocation);
+
         LoadBalance loadbalance = initLoadBalance(invokers, invocation);
         RpcUtils.attachInvocationIdIfAsync(getUrl(), invocation);
-        return doInvoke(invocation, invokers, loadbalance);
+
+        InvocationProfilerUtils.enterDetailProfiler(invocation, () -> "Cluster " + this.getClass().getName() + " invoke.");
+        try {
+            return doInvoke(invocation, invokers, loadbalance);
+        } finally {
+            InvocationProfilerUtils.releaseDetailProfiler(invocation);
+        }
     }
 
     protected void checkWhetherDestroyed() {
@@ -361,9 +372,13 @@ public abstract class AbstractClusterInvoker<T> implements ClusterInvoker<T> {
         setContext(invoker);
         Result result;
         try {
+            if (ProfilerSwitch.isEnableSimpleProfiler()) {
+                InvocationProfilerUtils.enterProfiler(invocation, "Invoker invoke. Target Address: " + invoker.getUrl().getAddress());
+            }
             result = invoker.invoke(invocation);
         } finally {
             clearContext(invoker);
+            InvocationProfilerUtils.releaseSimpleProfiler(invocation);
         }
         return result;
     }
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/profiler/Profiler.java b/dubbo-common/src/main/java/org/apache/dubbo/common/profiler/Profiler.java
new file mode 100644
index 0000000..9ef8c91
--- /dev/null
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/profiler/Profiler.java
@@ -0,0 +1,104 @@
+/*
+ * 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.dubbo.common.profiler;
+
+import org.apache.dubbo.common.threadlocal.InternalThreadLocal;
+
+import java.util.LinkedList;
+import java.util.List;
+
+public class Profiler {
+    public static final String PROFILER_KEY = "DUBBO_INVOKE_PROFILER";
+
+    private final static InternalThreadLocal<ProfilerEntry> bizProfiler = new InternalThreadLocal<>();
+
+    public static ProfilerEntry start(String message) {
+        return new ProfilerEntry(message);
+    }
+
+    public static ProfilerEntry enter(ProfilerEntry entry, String message) {
+        ProfilerEntry subEntry = new ProfilerEntry(message, entry, entry.getFirst());
+        entry.getSub().add(subEntry);
+        return subEntry;
+    }
+
+    public static ProfilerEntry release(ProfilerEntry entry) {
+        entry.setEndTime(System.nanoTime());
+        ProfilerEntry parent = entry.getParent();
+        if (parent != null) {
+            return parent;
+        } else {
+            return entry;
+        }
+    }
+
+    public static void setToBizProfiler(ProfilerEntry entry) {
+        bizProfiler.set(entry);
+    }
+
+    public static ProfilerEntry getBizProfiler() {
+        return bizProfiler.get();
+    }
+
+    public static void removeBizProfiler() {
+        bizProfiler.remove();
+    }
+
+    public static String buildDetail(ProfilerEntry entry) {
+        ProfilerEntry firstEntry = entry.getFirst();
+        long totalUsageTime = firstEntry.getEndTime() - firstEntry.getStartTime();
+        return "Start time: " + firstEntry.getStartTime() + "\n" +
+            String.join("\n", buildDetail(firstEntry, firstEntry.getStartTime(), totalUsageTime, 0));
+    }
+
+    public static List<String> buildDetail(ProfilerEntry entry, long startTime, long totalUsageTime, int depth) {
+        StringBuilder stringBuilder = new StringBuilder();
+        long usage = entry.getEndTime() - entry.getStartTime();
+        int percent = (int) (((usage) * 100) / totalUsageTime);
+
+        long offset = entry.getStartTime() - startTime;
+        List<String> lines = new LinkedList<>();
+        stringBuilder.append("+-[ Offset: ")
+            .append(offset / 1000_000).append(".").append(String.format("%06d", offset % 1000_000))
+            .append("ms; Usage: ")
+            .append(usage / 1000_000).append(".").append(String.format("%06d", usage % 1000_000))
+            .append("ms, ")
+            .append(percent)
+            .append("% ] ")
+            .append(entry.getMessage());
+        lines.add(stringBuilder.toString());
+        List<ProfilerEntry> entrySub = entry.getSub();
+        for (int i = 0, entrySubSize = entrySub.size(); i < entrySubSize; i++) {
+            ProfilerEntry sub = entrySub.get(i);
+            List<String> subLines = buildDetail(sub, startTime, totalUsageTime, depth + 1);
+            if (i < entrySubSize - 1) {
+                lines.add("  " + subLines.get(0));
+                for (int j = 1, subLinesSize = subLines.size(); j < subLinesSize; j++) {
+                    String subLine = subLines.get(j);
+                    lines.add("  |" + subLine);
+                }
+            } else {
+                lines.add("  " + subLines.get(0));
+                for (int j = 1, subLinesSize = subLines.size(); j < subLinesSize; j++) {
+                    String subLine = subLines.get(j);
+                    lines.add("   " + subLine);
+                }
+            }
+        }
+        return lines;
+    }
+}
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/profiler/ProfilerEntry.java b/dubbo-common/src/main/java/org/apache/dubbo/common/profiler/ProfilerEntry.java
new file mode 100644
index 0000000..70d4e67
--- /dev/null
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/profiler/ProfilerEntry.java
@@ -0,0 +1,71 @@
+/*
+ * 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.dubbo.common.profiler;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class ProfilerEntry {
+    private final List<ProfilerEntry> sub = new ArrayList<>(4);
+    private final String message;
+    private final ProfilerEntry parent;
+    private final ProfilerEntry first;
+    private final long startTime;
+    private long endTime;
+
+    public ProfilerEntry(String message) {
+        this.message = message;
+        this.parent = null;
+        this.first = this;
+        this.startTime = System.nanoTime();
+    }
+
+    public ProfilerEntry(String message, ProfilerEntry parentEntry, ProfilerEntry firstEntry) {
+        this.message = message;
+        this.parent = parentEntry;
+        this.first = firstEntry;
+        this.startTime = System.nanoTime();
+    }
+
+    public List<ProfilerEntry> getSub() {
+        return sub;
+    }
+
+    public String getMessage() {
+        return message;
+    }
+
+    public ProfilerEntry getParent() {
+        return parent;
+    }
+
+    public ProfilerEntry getFirst() {
+        return first;
+    }
+
+    public long getStartTime() {
+        return startTime;
+    }
+
+    public void setEndTime(long endTime) {
+        this.endTime = endTime;
+    }
+
+    public long getEndTime() {
+        return endTime;
+    }
+}
diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/profiler/ProfilerSwitch.java b/dubbo-common/src/main/java/org/apache/dubbo/common/profiler/ProfilerSwitch.java
new file mode 100644
index 0000000..2025550
--- /dev/null
+++ b/dubbo-common/src/main/java/org/apache/dubbo/common/profiler/ProfilerSwitch.java
@@ -0,0 +1,63 @@
+/*
+ * 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.dubbo.common.profiler;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * TODO
+ */
+public class ProfilerSwitch {
+    private final static AtomicBoolean enableDetailProfiler = new AtomicBoolean(false);
+
+    private final static AtomicBoolean enableSimpleProfiler = new AtomicBoolean(true);
+
+    private final static AtomicReference<Double> warnPercent = new AtomicReference<>(0.75);
+
+    public static void enableSimpleProfiler() {
+        enableSimpleProfiler.set(true);
+    }
+
+    public static void disableSimpleProfiler() {
+        enableSimpleProfiler.set(false);
+    }
+
+    public static void enableDetailProfiler() {
+        enableDetailProfiler.set(true);
+    }
+
+    public static void disableDetailProfiler() {
+        enableDetailProfiler.set(false);
+    }
+
+    public static boolean isEnableDetailProfiler() {
+        return enableDetailProfiler.get() && enableSimpleProfiler.get();
+    }
+
+    public static boolean isEnableSimpleProfiler() {
+        return enableSimpleProfiler.get();
+    }
+
+    public static double getWarnPercent() {
+        return warnPercent.get();
+    }
+
+    public static void setWarnPercent(double percent) {
+        warnPercent.set(percent);
+    }
+}
diff --git a/dubbo-common/src/test/java/org/apache/dubbo/common/profiler/ProfilerTest.java b/dubbo-common/src/test/java/org/apache/dubbo/common/profiler/ProfilerTest.java
new file mode 100644
index 0000000..05f740f
--- /dev/null
+++ b/dubbo-common/src/test/java/org/apache/dubbo/common/profiler/ProfilerTest.java
@@ -0,0 +1,97 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.dubbo.common.profiler;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class ProfilerTest {
+
+    @Test
+    public void testProfiler() {
+        ProfilerEntry one = Profiler.start("1");
+        ProfilerEntry two = Profiler.enter(one, "1-2");
+
+        ProfilerEntry three = Profiler.enter(two, "1-2-3");
+
+        ProfilerEntry four = Profiler.enter(three, "1-2-3-4");
+        Assertions.assertEquals(three, Profiler.release(four));
+
+        ProfilerEntry five = Profiler.enter(three, "1-2-3-5");
+        Assertions.assertEquals(three, Profiler.release(five));
+
+        Assertions.assertEquals(two, Profiler.release(three));
+
+        ProfilerEntry six = Profiler.enter(two, "1-2-6");
+        Assertions.assertEquals(two, Profiler.release(six));
+
+        ProfilerEntry seven = Profiler.enter(six, "1-2-6-7");
+        Assertions.assertEquals(six, Profiler.release(seven));
+
+        ProfilerEntry eight = Profiler.enter(six, "1-2-6-8");
+        Assertions.assertEquals(six, Profiler.release(eight));
+
+        Assertions.assertEquals(2, two.getSub().size());
+        Assertions.assertEquals(three, two.getSub().get(0));
+        Assertions.assertEquals(six, two.getSub().get(1));
+
+        Profiler.release(two);
+
+        ProfilerEntry nine = Profiler.enter(one, "1-9");
+        Profiler.release(nine);
+
+        Profiler.release(one);
+
+        /*
+         * Start time: 287395734500659
+         * +-[ Offset: 0.000000ms; Usage: 4.721583ms, 100% ] 1
+         *   +-[ Offset: 0.013136ms; Usage: 4.706288ms, 99% ] 1-2
+         *   |  +-[ Offset: 0.027903ms; Usage: 4.662918ms, 98% ] 1-2-3
+         *   |  |  +-[ Offset: 0.029742ms; Usage: 0.003785ms, 0% ] 1-2-3-4
+         *   |  |  +-[ Offset: 4.688477ms; Usage: 0.001398ms, 0% ] 1-2-3-5
+         *   |  +-[ Offset: 4.693346ms; Usage: 0.000316ms, 0% ] 1-2-6
+         *   |     +-[ Offset: 4.695191ms; Usage: 0.000212ms, 0% ] 1-2-6-7
+         *   |     +-[ Offset: 4.696655ms; Usage: 0.000195ms, 0% ] 1-2-6-8
+         *   +-[ Offset: 4.721044ms; Usage: 0.000270ms, 0% ] 1-9
+         */
+        Assertions.assertEquals(Profiler.buildDetail(one), Profiler.buildDetail(two));
+        Assertions.assertEquals(Profiler.buildDetail(one), Profiler.buildDetail(three));
+        Assertions.assertEquals(Profiler.buildDetail(one), Profiler.buildDetail(four));
+        Assertions.assertEquals(Profiler.buildDetail(one), Profiler.buildDetail(five));
+        Assertions.assertEquals(Profiler.buildDetail(one), Profiler.buildDetail(six));
+        Assertions.assertEquals(Profiler.buildDetail(one), Profiler.buildDetail(seven));
+        Assertions.assertEquals(Profiler.buildDetail(one), Profiler.buildDetail(eight));
+        Assertions.assertEquals(Profiler.buildDetail(one), Profiler.buildDetail(nine));
+    }
+
+    @Test
+    public void testBizProfiler() {
+        Assertions.assertNull(Profiler.getBizProfiler());
+
+        ProfilerEntry one = Profiler.start("1");
+
+        Profiler.setToBizProfiler(one);
+
+        Profiler.release(Profiler.enter(Profiler.getBizProfiler(), "1-2"));
+
+        Assertions.assertEquals(one, Profiler.getBizProfiler());
+        Assertions.assertEquals(1, one.getSub().size());
+
+        Profiler.removeBizProfiler();
+        Assertions.assertNull(Profiler.getBizProfiler());
+    }
+}
diff --git a/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/command/impl/BaseOffline.java b/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/command/impl/BaseOffline.java
index 425d37f..d7b4baf 100644
--- a/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/command/impl/BaseOffline.java
+++ b/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/command/impl/BaseOffline.java
@@ -32,7 +32,7 @@ import java.util.Collection;
 import java.util.List;
 
 public class BaseOffline implements BaseCommand {
-    private Logger logger = LoggerFactory.getLogger(OfflineInterface.class);
+    private Logger logger = LoggerFactory.getLogger(BaseOffline.class);
     public FrameworkServiceRepository serviceRepository;
 
     public BaseOffline(FrameworkModel frameworkModel) {
diff --git a/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/command/impl/BaseOnline.java b/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/command/impl/BaseOnline.java
index 9ee67e5..a060fb8 100644
--- a/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/command/impl/BaseOnline.java
+++ b/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/command/impl/BaseOnline.java
@@ -32,7 +32,7 @@ import java.util.Collection;
 import java.util.List;
 
 public class BaseOnline implements BaseCommand {
-    private static final Logger logger = LoggerFactory.getLogger(Online.class);
+    private static final Logger logger = LoggerFactory.getLogger(BaseOnline.class);
     public FrameworkServiceRepository serviceRepository;
 
     public BaseOnline(FrameworkModel frameworkModel) {
diff --git a/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/command/impl/DisableDetailProfiler.java b/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/command/impl/DisableDetailProfiler.java
new file mode 100644
index 0000000..5ac413b
--- /dev/null
+++ b/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/command/impl/DisableDetailProfiler.java
@@ -0,0 +1,36 @@
+/*
+ * 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.dubbo.qos.command.impl;
+
+import org.apache.dubbo.common.logger.Logger;
+import org.apache.dubbo.common.logger.LoggerFactory;
+import org.apache.dubbo.common.profiler.ProfilerSwitch;
+import org.apache.dubbo.qos.command.BaseCommand;
+import org.apache.dubbo.qos.command.CommandContext;
+import org.apache.dubbo.qos.command.annotation.Cmd;
+
+@Cmd(name = "disableDetailProfiler", summary = "Disable Dubbo Invocation Profiler.")
+public class DisableDetailProfiler implements BaseCommand {
+    private final static Logger logger = LoggerFactory.getLogger(DisableDetailProfiler.class);
+
+    @Override
+    public String execute(CommandContext commandContext, String[] args) {
+        ProfilerSwitch.disableDetailProfiler();
+        logger.warn("Dubbo Invocation Profiler has been disabled.");
+        return "OK";
+    }
+}
diff --git a/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/command/impl/DisableSimpleProfiler.java b/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/command/impl/DisableSimpleProfiler.java
new file mode 100644
index 0000000..22ffba5
--- /dev/null
+++ b/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/command/impl/DisableSimpleProfiler.java
@@ -0,0 +1,36 @@
+/*
+ * 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.dubbo.qos.command.impl;
+
+import org.apache.dubbo.common.logger.Logger;
+import org.apache.dubbo.common.logger.LoggerFactory;
+import org.apache.dubbo.common.profiler.ProfilerSwitch;
+import org.apache.dubbo.qos.command.BaseCommand;
+import org.apache.dubbo.qos.command.CommandContext;
+import org.apache.dubbo.qos.command.annotation.Cmd;
+
+@Cmd(name = "disableSimpleProfiler", summary = "Disable Dubbo Invocation Profiler.")
+public class DisableSimpleProfiler implements BaseCommand {
+    private final static Logger logger = LoggerFactory.getLogger(DisableSimpleProfiler.class);
+
+    @Override
+    public String execute(CommandContext commandContext, String[] args) {
+        ProfilerSwitch.disableSimpleProfiler();
+        logger.warn("Dubbo Invocation Profiler has been disabled.");
+        return "OK";
+    }
+}
diff --git a/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/command/impl/EnableDetailProfiler.java b/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/command/impl/EnableDetailProfiler.java
new file mode 100644
index 0000000..bc1a136
--- /dev/null
+++ b/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/command/impl/EnableDetailProfiler.java
@@ -0,0 +1,36 @@
+/*
+ * 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.dubbo.qos.command.impl;
+
+import org.apache.dubbo.common.logger.Logger;
+import org.apache.dubbo.common.logger.LoggerFactory;
+import org.apache.dubbo.common.profiler.ProfilerSwitch;
+import org.apache.dubbo.qos.command.BaseCommand;
+import org.apache.dubbo.qos.command.CommandContext;
+import org.apache.dubbo.qos.command.annotation.Cmd;
+
+@Cmd(name = "enableDetailProfiler", summary = "Enable Dubbo Invocation Profiler.")
+public class EnableDetailProfiler implements BaseCommand {
+    private final static Logger logger = LoggerFactory.getLogger(EnableDetailProfiler.class);
+
+    @Override
+    public String execute(CommandContext commandContext, String[] args) {
+        ProfilerSwitch.enableDetailProfiler();
+        logger.warn("Dubbo Invocation Profiler has been enabled.");
+        return "OK";
+    }
+}
diff --git a/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/command/impl/EnableSimpleProfiler.java b/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/command/impl/EnableSimpleProfiler.java
new file mode 100644
index 0000000..d46df99
--- /dev/null
+++ b/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/command/impl/EnableSimpleProfiler.java
@@ -0,0 +1,36 @@
+/*
+ * 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.dubbo.qos.command.impl;
+
+import org.apache.dubbo.common.logger.Logger;
+import org.apache.dubbo.common.logger.LoggerFactory;
+import org.apache.dubbo.common.profiler.ProfilerSwitch;
+import org.apache.dubbo.qos.command.BaseCommand;
+import org.apache.dubbo.qos.command.CommandContext;
+import org.apache.dubbo.qos.command.annotation.Cmd;
+
+@Cmd(name = "enableSimpleProfiler", summary = "Enable Dubbo Invocation Profiler.")
+public class EnableSimpleProfiler implements BaseCommand {
+    private final static Logger logger = LoggerFactory.getLogger(EnableSimpleProfiler.class);
+
+    @Override
+    public String execute(CommandContext commandContext, String[] args) {
+        ProfilerSwitch.enableSimpleProfiler();
+        logger.warn("Dubbo Invocation Profiler has been enabled.");
+        return "OK";
+    }
+}
diff --git a/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/command/impl/SetProfilerWarnPercent.java b/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/command/impl/SetProfilerWarnPercent.java
new file mode 100644
index 0000000..e6014f6
--- /dev/null
+++ b/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/command/impl/SetProfilerWarnPercent.java
@@ -0,0 +1,39 @@
+/*
+ * 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.dubbo.qos.command.impl;
+
+import org.apache.dubbo.common.logger.Logger;
+import org.apache.dubbo.common.logger.LoggerFactory;
+import org.apache.dubbo.common.profiler.ProfilerSwitch;
+import org.apache.dubbo.qos.command.BaseCommand;
+import org.apache.dubbo.qos.command.CommandContext;
+import org.apache.dubbo.qos.command.annotation.Cmd;
+
+@Cmd(name = "setProfilerWarnPercent", example = "setProfilerWarnPercent 0.75", summary = "Disable Dubbo Invocation Profiler.")
+public class SetProfilerWarnPercent implements BaseCommand {
+    private final static Logger logger = LoggerFactory.getLogger(SetProfilerWarnPercent.class);
+
+    @Override
+    public String execute(CommandContext commandContext, String[] args) {
+        if (args == null || args.length != 1) {
+            return "args error. example: setProfilerWarnPercent 0.75";
+        }
+        ProfilerSwitch.setWarnPercent(Double.parseDouble(args[0]));
+        logger.warn("Dubbo Invocation Profiler warn percent has been set to " + args[0]);
+        return "OK";
+    }
+}
diff --git a/dubbo-plugin/dubbo-qos/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.qos.command.BaseCommand b/dubbo-plugin/dubbo-qos/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.qos.command.BaseCommand
index 78f5337..d00c59c 100644
--- a/dubbo-plugin/dubbo-qos/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.qos.command.BaseCommand
+++ b/dubbo-plugin/dubbo-qos/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.qos.command.BaseCommand
@@ -19,3 +19,8 @@ invoke=org.apache.dubbo.qos.command.impl.InvokeTelnet
 select=org.apache.dubbo.qos.command.impl.SelectTelnet
 ps=org.apache.dubbo.qos.command.impl.PortTelnet
 shutdown=org.apache.dubbo.qos.command.impl.ShutdownTelnet
+enableDetailProfiler=org.apache.dubbo.qos.command.impl.EnableDetailProfiler
+disableDetailProfiler=org.apache.dubbo.qos.command.impl.DisableDetailProfiler
+enableSimpleProfiler=org.apache.dubbo.qos.command.impl.EnableSimpleProfiler
+disableSimpleProfiler=org.apache.dubbo.qos.command.impl.DisableSimpleProfiler
+setProfilerWarnPercent=org.apache.dubbo.qos.command.impl.SetProfilerWarnPercent
diff --git a/dubbo-plugin/dubbo-qos/src/test/java/org/apache/dubbo/qos/command/util/CommandHelperTest.java b/dubbo-plugin/dubbo-qos/src/test/java/org/apache/dubbo/qos/command/util/CommandHelperTest.java
index 3eb73a2..e765213 100644
--- a/dubbo-plugin/dubbo-qos/src/test/java/org/apache/dubbo/qos/command/util/CommandHelperTest.java
+++ b/dubbo-plugin/dubbo-qos/src/test/java/org/apache/dubbo/qos/command/util/CommandHelperTest.java
@@ -19,6 +19,10 @@ package org.apache.dubbo.qos.command.util;
 import org.apache.dubbo.qos.command.GreetingCommand;
 import org.apache.dubbo.qos.command.impl.ChangeTelnet;
 import org.apache.dubbo.qos.command.impl.CountTelnet;
+import org.apache.dubbo.qos.command.impl.DisableDetailProfiler;
+import org.apache.dubbo.qos.command.impl.DisableSimpleProfiler;
+import org.apache.dubbo.qos.command.impl.EnableDetailProfiler;
+import org.apache.dubbo.qos.command.impl.EnableSimpleProfiler;
 import org.apache.dubbo.qos.command.impl.Help;
 import org.apache.dubbo.qos.command.impl.InvokeTelnet;
 import org.apache.dubbo.qos.command.impl.Live;
@@ -35,10 +39,12 @@ import org.apache.dubbo.qos.command.impl.PwdTelnet;
 import org.apache.dubbo.qos.command.impl.Quit;
 import org.apache.dubbo.qos.command.impl.Ready;
 import org.apache.dubbo.qos.command.impl.SelectTelnet;
+import org.apache.dubbo.qos.command.impl.SetProfilerWarnPercent;
 import org.apache.dubbo.qos.command.impl.ShutdownTelnet;
 import org.apache.dubbo.qos.command.impl.Startup;
 import org.apache.dubbo.qos.command.impl.Version;
 import org.apache.dubbo.rpc.model.FrameworkModel;
+
 import org.junit.jupiter.api.Test;
 
 import java.util.LinkedList;
@@ -88,6 +94,11 @@ public class CommandHelperTest {
         expectedClasses.add(PortTelnet.class);
         expectedClasses.add(PwdTelnet.class);
         expectedClasses.add(ShutdownTelnet.class);
+        expectedClasses.add(EnableDetailProfiler.class);
+        expectedClasses.add(DisableDetailProfiler.class);
+        expectedClasses.add(EnableSimpleProfiler.class);
+        expectedClasses.add(DisableSimpleProfiler.class);
+        expectedClasses.add(SetProfilerWarnPercent.class);
         assertThat(classes, containsInAnyOrder(expectedClasses.toArray(new Class<?>[0])));
     }
 
diff --git a/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/InvocationProfilerUtils.java b/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/InvocationProfilerUtils.java
new file mode 100644
index 0000000..0921be0
--- /dev/null
+++ b/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/InvocationProfilerUtils.java
@@ -0,0 +1,77 @@
+/*
+ * 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.dubbo.rpc;
+
+import org.apache.dubbo.common.profiler.Profiler;
+import org.apache.dubbo.common.profiler.ProfilerEntry;
+import org.apache.dubbo.common.profiler.ProfilerSwitch;
+
+import java.util.concurrent.Callable;
+
+public class InvocationProfilerUtils {
+
+    public static void enterSimpleProfiler(Invocation invocation, Callable<String> messageCallable) {
+        if (ProfilerSwitch.isEnableSimpleProfiler()) {
+            enterProfiler(invocation, messageCallable);
+        }
+    }
+
+    public static void releaseSimpleProfiler(Invocation invocation) {
+        if (ProfilerSwitch.isEnableSimpleProfiler()) {
+            releaseProfiler(invocation);
+        }
+    }
+
+    public static void enterDetailProfiler(Invocation invocation, Callable<String> messageCallable) {
+        if (ProfilerSwitch.isEnableDetailProfiler()) {
+            enterProfiler(invocation, messageCallable);
+        }
+    }
+
+    public static void releaseDetailProfiler(Invocation invocation) {
+        if (ProfilerSwitch.isEnableDetailProfiler()) {
+            releaseProfiler(invocation);
+        }
+    }
+
+    public static void enterProfiler(Invocation invocation, String message) {
+        Object fromInvocation = invocation.get(Profiler.PROFILER_KEY);
+        if (fromInvocation instanceof ProfilerEntry) {
+            invocation.put(Profiler.PROFILER_KEY, Profiler.enter((ProfilerEntry) fromInvocation, message));
+        }
+    }
+
+    public static void enterProfiler(Invocation invocation, Callable<String> messageCallable) {
+        Object fromInvocation = invocation.get(Profiler.PROFILER_KEY);
+        if (fromInvocation instanceof ProfilerEntry) {
+            String message = "";
+            try {
+                message = messageCallable.call();
+            } catch (Exception ignore) {
+
+            }
+            invocation.put(Profiler.PROFILER_KEY, Profiler.enter((ProfilerEntry) fromInvocation, message));
+        }
+    }
+
+    public static void releaseProfiler(Invocation invocation) {
+        Object fromInvocation = invocation.get(Profiler.PROFILER_KEY);
+        if (fromInvocation instanceof ProfilerEntry) {
+            invocation.put(Profiler.PROFILER_KEY, Profiler.release((ProfilerEntry) fromInvocation));
+        }
+    }
+}
diff --git a/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/filter/ProfilerServerFilter.java b/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/filter/ProfilerServerFilter.java
new file mode 100644
index 0000000..c8ad2ed
--- /dev/null
+++ b/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/filter/ProfilerServerFilter.java
@@ -0,0 +1,101 @@
+/*
+ * 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.dubbo.rpc.filter;
+
+import org.apache.dubbo.common.extension.Activate;
+import org.apache.dubbo.common.logger.Logger;
+import org.apache.dubbo.common.logger.LoggerFactory;
+import org.apache.dubbo.common.profiler.Profiler;
+import org.apache.dubbo.common.profiler.ProfilerEntry;
+import org.apache.dubbo.common.profiler.ProfilerSwitch;
+import org.apache.dubbo.rpc.BaseFilter;
+import org.apache.dubbo.rpc.Filter;
+import org.apache.dubbo.rpc.Invocation;
+import org.apache.dubbo.rpc.Invoker;
+import org.apache.dubbo.rpc.Result;
+import org.apache.dubbo.rpc.RpcContext;
+import org.apache.dubbo.rpc.RpcException;
+
+import java.util.Map;
+
+import static org.apache.dubbo.common.constants.CommonConstants.DEFAULT_TIMEOUT;
+import static org.apache.dubbo.common.constants.CommonConstants.PROVIDER;
+import static org.apache.dubbo.common.constants.CommonConstants.TIMEOUT_KEY;
+
+@Activate(group = PROVIDER, order = Integer.MIN_VALUE)
+public class ProfilerServerFilter implements Filter, BaseFilter.Listener {
+    private final static String CLIENT_IP_KEY = "client_ip";
+    private final static Logger logger = LoggerFactory.getLogger(ProfilerServerFilter.class);
+
+    @Override
+    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
+        if (ProfilerSwitch.isEnableSimpleProfiler()) {
+            ProfilerEntry bizProfiler = Profiler.start("Receive request. Server invoke begin.");
+            invocation.put(Profiler.PROFILER_KEY, bizProfiler);
+            invocation.put(CLIENT_IP_KEY, RpcContext.getServiceContext().getRemoteAddressString());
+        }
+
+        return invoker.invoke(invocation);
+    }
+
+    @Override
+    public void onResponse(Result appResponse, Invoker<?> invoker, Invocation invocation) {
+        afterInvoke(invoker, invocation);
+    }
+
+    @Override
+    public void onError(Throwable t, Invoker<?> invoker, Invocation invocation) {
+        afterInvoke(invoker, invocation);
+    }
+
+    private void afterInvoke(Invoker<?> invoker, Invocation invocation) {
+        if (ProfilerSwitch.isEnableSimpleProfiler()) {
+            Object fromInvocation = invocation.get(Profiler.PROFILER_KEY);
+            if (fromInvocation instanceof ProfilerEntry) {
+                ProfilerEntry profiler = Profiler.release((ProfilerEntry) fromInvocation);
+                invocation.put(Profiler.PROFILER_KEY, profiler);
+
+                dumpIfNeed(invoker, invocation, profiler);
+            }
+        }
+    }
+
+    private void dumpIfNeed(Invoker<?> invoker, Invocation invocation, ProfilerEntry profiler) {
+        int timeout;
+        Object timeoutKey = invocation.getObjectAttachment(TIMEOUT_KEY);
+        if (timeoutKey instanceof Integer) {
+            timeout = (Integer) timeoutKey;
+        } else {
+            timeout = invoker.getUrl().getParameter(TIMEOUT_KEY, DEFAULT_TIMEOUT);
+        }
+        long usage = profiler.getEndTime() - profiler.getStartTime();
+        if (usage > (timeout * 1000_000 * ProfilerSwitch.getWarnPercent())) {
+
+            StringBuilder attachment = new StringBuilder();
+            for (Map.Entry<String, Object> entry : invocation.getObjectAttachments().entrySet()) {
+                attachment.append(entry.getKey()).append("=").append(entry.getValue()).append(";\n");
+            }
+
+            logger.warn(String.format("[Dubbo-Provider] execute service %s#%s cost %d.%06d ms, this invocation almost (maybe already) timeout\n" +
+                    "client: %s\n" +
+                    "invocation context:\n%s" +
+                    "thread info: \n%s",
+                invocation.getTargetServiceUniqueName(), invocation.getMethodName(), usage / 1000_000, usage % 1000_000,
+                invocation.get(CLIENT_IP_KEY), attachment, Profiler.buildDetail(profiler)));
+        }
+    }
+}
diff --git a/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/protocol/AbstractInvoker.java b/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/protocol/AbstractInvoker.java
index d865cb1..618d662 100644
--- a/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/protocol/AbstractInvoker.java
+++ b/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/protocol/AbstractInvoker.java
@@ -251,7 +251,7 @@ public abstract class AbstractInvoker<T> implements Invoker<T> {
              * must call {@link java.util.concurrent.CompletableFuture#get(long, TimeUnit)} because
              * {@link java.util.concurrent.CompletableFuture#get()} was proved to have serious performance drop.
              */
-            Object timeout = invocation.get(TIMEOUT_KEY);
+            Object timeout = invocation.getObjectAttachment(TIMEOUT_KEY);
             if (timeout instanceof Integer) {
                 asyncResult.get((Integer) timeout, TimeUnit.MILLISECONDS);
             } else {
diff --git a/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/proxy/AbstractProxyInvoker.java b/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/proxy/AbstractProxyInvoker.java
index 84dfd33..11cafc7 100644
--- a/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/proxy/AbstractProxyInvoker.java
+++ b/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/proxy/AbstractProxyInvoker.java
@@ -19,6 +19,9 @@ package org.apache.dubbo.rpc.proxy;
 import org.apache.dubbo.common.URL;
 import org.apache.dubbo.common.logger.Logger;
 import org.apache.dubbo.common.logger.LoggerFactory;
+import org.apache.dubbo.common.profiler.Profiler;
+import org.apache.dubbo.common.profiler.ProfilerEntry;
+import org.apache.dubbo.common.profiler.ProfilerSwitch;
 import org.apache.dubbo.rpc.AppResponse;
 import org.apache.dubbo.rpc.AsyncContextImpl;
 import org.apache.dubbo.rpc.AsyncRpcResult;
@@ -83,7 +86,27 @@ public abstract class AbstractProxyInvoker<T> implements Invoker<T> {
     @Override
     public Result invoke(Invocation invocation) throws RpcException {
         try {
+            if (ProfilerSwitch.isEnableSimpleProfiler()) {
+                Object fromInvocation = invocation.get(Profiler.PROFILER_KEY);
+                if (fromInvocation instanceof ProfilerEntry) {
+                    ProfilerEntry profiler = Profiler.enter((ProfilerEntry) fromInvocation, "Receive request. Server biz impl invoke begin.");
+                    invocation.put(Profiler.PROFILER_KEY, profiler);
+                    // TODO clear after invoke
+                    Profiler.setToBizProfiler(profiler);
+                }
+            }
+
             Object value = doInvoke(proxy, invocation.getMethodName(), invocation.getParameterTypes(), invocation.getArguments());
+
+            if (ProfilerSwitch.isEnableSimpleProfiler()) {
+                Object fromInvocation = invocation.get(Profiler.PROFILER_KEY);
+                if (fromInvocation instanceof ProfilerEntry) {
+                    ProfilerEntry profiler = Profiler.release((ProfilerEntry) fromInvocation);
+                    invocation.put(Profiler.PROFILER_KEY, profiler);
+                }
+            }
+            Profiler.removeBizProfiler();
+
             CompletableFuture<Object> future = wrapWithFuture(value, invocation);
             CompletableFuture<AppResponse> appResponseFuture = future.handle((obj, t) -> {
                 AppResponse result = new AppResponse(invocation);
diff --git a/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/proxy/InvokerInvocationHandler.java b/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/proxy/InvokerInvocationHandler.java
index 42125ee..1b440b5 100644
--- a/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/proxy/InvokerInvocationHandler.java
+++ b/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/proxy/InvokerInvocationHandler.java
@@ -19,6 +19,9 @@ package org.apache.dubbo.rpc.proxy;
 import org.apache.dubbo.common.URL;
 import org.apache.dubbo.common.logger.Logger;
 import org.apache.dubbo.common.logger.LoggerFactory;
+import org.apache.dubbo.common.profiler.Profiler;
+import org.apache.dubbo.common.profiler.ProfilerEntry;
+import org.apache.dubbo.common.profiler.ProfilerSwitch;
 import org.apache.dubbo.rpc.Constants;
 import org.apache.dubbo.rpc.Invoker;
 import org.apache.dubbo.rpc.RpcInvocation;
@@ -28,6 +31,10 @@ import org.apache.dubbo.rpc.model.ServiceModel;
 
 import java.lang.reflect.InvocationHandler;
 import java.lang.reflect.Method;
+import java.util.Map;
+
+import static org.apache.dubbo.common.constants.CommonConstants.DEFAULT_TIMEOUT;
+import static org.apache.dubbo.common.constants.CommonConstants.TIMEOUT_KEY;
 
 /**
  * InvokerHandler
@@ -77,6 +84,46 @@ public class InvokerInvocationHandler implements InvocationHandler {
             rpcInvocation.put(Constants.METHOD_MODEL, ((ConsumerModel) serviceModel).getMethodModel(method));
         }
 
+        if (ProfilerSwitch.isEnableSimpleProfiler()) {
+            ProfilerEntry parentProfiler = Profiler.getBizProfiler();
+            ProfilerEntry bizProfiler;
+            boolean containsBizProfiler = false;
+            if (parentProfiler != null) {
+                containsBizProfiler = true;
+                bizProfiler = Profiler.enter(parentProfiler, "Receive request. Client invoke begin.");
+            } else {
+                bizProfiler = Profiler.start("Receive request. Client invoke begin.");
+            }
+            rpcInvocation.put(Profiler.PROFILER_KEY, bizProfiler);
+            try {
+                return invoker.invoke(rpcInvocation).recreate();
+            } finally {
+                Profiler.release(bizProfiler);
+                if (!containsBizProfiler) {
+                    int timeout;
+                    Object timeoutKey = rpcInvocation.getObjectAttachment(TIMEOUT_KEY);
+                    if (timeoutKey instanceof Integer) {
+                        timeout = (Integer) timeoutKey;
+                    } else {
+                        timeout = url.getParameter(TIMEOUT_KEY, DEFAULT_TIMEOUT);
+                    }
+                    long usage = bizProfiler.getEndTime() - bizProfiler.getStartTime();
+                    if (usage > (timeout * 1000_000 * ProfilerSwitch.getWarnPercent())) {
+                        StringBuilder attachment = new StringBuilder();
+                        for (Map.Entry<String, Object> entry : rpcInvocation.getObjectAttachments().entrySet()) {
+                            attachment.append(entry.getKey()).append("=").append(entry.getValue()).append(";\n");
+                        }
+
+                        logger.warn(String.format("[Dubbo-Consumer] execute service %s#%s cost %d.%06d ms, this invocation almost (maybe already) timeout\n" +
+                                "invocation context:\n%s" +
+                                "thread info: \n%s",
+                            protocolServiceKey, methodName, usage / 1000_000, usage % 1000_000,
+                            attachment, Profiler.buildDetail(bizProfiler)));
+                    }
+                }
+            }
+        }
+
         return invoker.invoke(rpcInvocation).recreate();
     }
 }
diff --git a/dubbo-rpc/dubbo-rpc-api/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.Filter b/dubbo-rpc/dubbo-rpc-api/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.Filter
index 34d56b4..bed37fa 100644
--- a/dubbo-rpc/dubbo-rpc-api/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.Filter
+++ b/dubbo-rpc/dubbo-rpc-api/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.rpc.Filter
@@ -12,3 +12,4 @@ deprecated=org.apache.dubbo.rpc.filter.DeprecatedFilter
 compatible=org.apache.dubbo.rpc.filter.CompatibleFilter
 timeout=org.apache.dubbo.rpc.filter.TimeoutFilter
 tps=org.apache.dubbo.rpc.filter.TpsLimitFilter
+profiler-server=org.apache.dubbo.rpc.filter.ProfilerServerFilter
diff --git a/dubbo-rpc/dubbo-rpc-dubbo/src/main/java/org/apache/dubbo/rpc/protocol/dubbo/DubboInvoker.java b/dubbo-rpc/dubbo-rpc-dubbo/src/main/java/org/apache/dubbo/rpc/protocol/dubbo/DubboInvoker.java
index eb09f5e..e749699 100644
--- a/dubbo-rpc/dubbo-rpc-dubbo/src/main/java/org/apache/dubbo/rpc/protocol/dubbo/DubboInvoker.java
+++ b/dubbo-rpc/dubbo-rpc-dubbo/src/main/java/org/apache/dubbo/rpc/protocol/dubbo/DubboInvoker.java
@@ -100,7 +100,7 @@ public class DubboInvoker<T> extends AbstractInvoker<T> {
         try {
             boolean isOneway = RpcUtils.isOneway(getUrl(), invocation);
             int timeout = calculateTimeout(invocation, methodName);
-            invocation.put(TIMEOUT_KEY, timeout);
+            invocation.setAttachment(TIMEOUT_KEY, timeout);
             if (isOneway) {
                 boolean isSent = getUrl().getMethodParameter(methodName, Constants.SENT_KEY, false);
                 currentClient.send(inv, isSent);
diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/TripleInvoker.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/TripleInvoker.java
index a57f356..1613d06 100644
--- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/TripleInvoker.java
+++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/TripleInvoker.java
@@ -86,7 +86,7 @@ public class TripleInvoker<T> extends AbstractInvoker<T> {
             getUrl().getParameter(Constants.SERIALIZATION_KEY, Constants.DEFAULT_REMOTING_SERIALIZATION));
         try {
             int timeout = calculateTimeout(invocation, methodName);
-            invocation.put(TIMEOUT_KEY, timeout);
+            invocation.setAttachment(TIMEOUT_KEY, timeout);
             ExecutorService executor = getCallbackExecutor(getUrl(), inv);
             // create request.
             Request req = new Request();