You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@skywalking.apache.org by wu...@apache.org on 2022/03/22 02:12:33 UTC

[skywalking-rover] branch main updated: Implement eBPF Profiling (#7)

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

wusheng pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/skywalking-rover.git


The following commit(s) were added to refs/heads/main by this push:
     new f84d1b6  Implement eBPF Profiling (#7)
f84d1b6 is described below

commit f84d1b69eb3e6b4c6c099c39a86c706e25e2c419
Author: mrproliu <74...@qq.com>
AuthorDate: Tue Mar 22 10:12:29 2022 +0800

    Implement eBPF Profiling (#7)
---
 .github/workflows/build-and-test.yaml              |  10 +-
 .gitignore                                         |   4 +
 Makefile                                           |  72 +----
 .../finders/base/process.go => bpf/include/api.h   |  36 +--
 pkg/boot/register.go => bpf/profiling/oncpu.c      |  26 +-
 pkg/boot/register.go => bpf/profiling/oncpu.h      |  24 +-
 configs/rover_configs.yaml                         |  16 +-
 docker/Dockerfile.base                             |  27 ++
 go.mod                                             |   4 +-
 go.sum                                             |  15 +-
 pkg/boot/register.go                               |   2 +
 pkg/{boot/register.go => process/api.go}           |  15 +-
 pkg/process/api/process.go                         |   4 +
 pkg/process/finders/base/process.go                |   3 +
 pkg/process/finders/context.go                     |   5 +
 pkg/process/finders/manager.go                     |   4 +
 pkg/process/finders/storage.go                     |  12 +-
 pkg/process/finders/vm/finder.go                   |  36 ++-
 pkg/process/finders/vm/process.go                  |   4 +
 pkg/process/{mdoule.go => module.go}               |   5 +
 pkg/{boot/register.go => profiling/config.go}      |  16 +-
 pkg/profiling/manager.go                           | 156 +++++++++++
 pkg/{process/mdoule.go => profiling/module.go}     |  27 +-
 .../base/process.go => profiling/task/base/api.go} |  28 +-
 .../register.go => profiling/task/base/config.go}  |  16 +-
 .../process.go => profiling/task/base/target.go}   |  32 ++-
 pkg/profiling/task/base/task.go                    | 100 +++++++
 .../process.go => profiling/task/base/trigger.go}  |  40 ++-
 pkg/profiling/task/context.go                      |  80 ++++++
 pkg/profiling/task/manager.go                      | 255 +++++++++++++++++
 pkg/profiling/task/oncpu/runner.go                 | 311 +++++++++++++++++++++
 pkg/profiling/task/registion.go                    |  54 ++++
 pkg/{boot/register.go => tools/host/file.go}       |  19 +-
 pkg/tools/profiling/api.go                         |  50 +++-
 pkg/tools/profiling/go_library.go                  |   2 +-
 pkg/tools/profiling/kernel.go                      |   6 +-
 pkg/tools/profiling/objdump.go                     |   2 +-
 scripts/build/base.mk                              |  55 ++++
 scripts/build/build.mk                             |  38 +++
 scripts/build/check.mk                             |  27 ++
 scripts/build/generate.mk                          |  29 ++
 scripts/build/lint.mk                              |  30 ++
 scripts/build/test.mk                              |  31 ++
 43 files changed, 1529 insertions(+), 199 deletions(-)

diff --git a/.github/workflows/build-and-test.yaml b/.github/workflows/build-and-test.yaml
index 2233088..cceb7a8 100644
--- a/.github/workflows/build-and-test.yaml
+++ b/.github/workflows/build-and-test.yaml
@@ -48,10 +48,14 @@ jobs:
           GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
       - name: Get dependencies
         run: make deps
-      - name: Lint
-        run: make lint
+      - name: Generate
+        run: make container-generate
       - name: Test
-        run: make test
+        run: make container-test
+      - name: Lint
+        run: make container-lint
+      - name: Make binary
+        run: make linux
       - name: Check CI Consistency
         if: matrix.go-version == '1.17' && matrix.runner == 'ubuntu'
         run: make check
diff --git a/.gitignore b/.gitignore
index 3161f6d..ea40d08 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,7 +2,11 @@
 *.iml
 .DS_Store
 *~
+.cache/
 bin/
 coverage.txt
 
 !/dist/bin/
+
+pkg/**/bpf_*.o
+pkg/**/bpf_*.go
\ No newline at end of file
diff --git a/Makefile b/Makefile
index 4578dda..dc5d6a5 100644
--- a/Makefile
+++ b/Makefile
@@ -15,63 +15,15 @@
 # limitations under the License.
 #
 
-VERSION ?= latest
-HUB ?= apache
-OUT_DIR = bin
-BINARY = skywalking-rover
-
-RELEASE_BIN = skywalking-rover-$(VERSION)-bin
-RELEASE_SRC = skywalking-rover-$(VERSION)-src
-
-SH = sh
-GO = go
-GIT = git
-PROTOC = protoc
-GO_PATH = $$($(GO) env GOPATH)
-GO_BUILD = $(GO) build
-GO_GET = $(GO) get
-GO_TEST = $(GO) test
-GO_LINT = $(GO_PATH)/bin/golangci-lint
-GO_BUILD_FLAGS = -v
-GO_BUILD_LDFLAGS = -X main.version=$(VERSION) -w -s
-GO_TEST_LDFLAGS =
-
-PLATFORMS := linux
-os = $(word 1, $@)
-ARCH = amd64
-
-SHELL = /bin/bash
-
-all: deps verify check
-
-.PHONY: tools
-tools:
-	$(GO_LINT) version || curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(GO_PATH)/bin v1.39.0
-
-deps: tools
-	$(GO_GET) -v -t -d ./...
-
-.PHONY: lint
-lint: tools
-	$(GO_LINT) run -v --timeout 5m ./...
-
-.PHONY: test
-test: clean
-	$(GO_TEST) -ldflags "$(GO_TEST_LDFLAGS)" ./... -coverprofile=coverage.txt -covermode=atomic
-
-.PHONY: verify
-verify: clean lint test
-
-.PHONY: clean
-clean: tools
-	-rm -rf coverage.txt
-
-.PHONY: check
-check: clean
-	$(GO) mod tidy > /dev/null
-	@if [ ! -z "`git status -s`" ]; then \
-		echo "Following files are not consistent with CI:"; \
-		git status -s; \
-		git diff; \
-		exit 1; \
-	fi
+include scripts/build/base.mk
+include scripts/build/generate.mk
+include scripts/build/test.mk
+include scripts/build/lint.mk
+include scripts/build/build.mk
+include scripts/build/check.mk
+
+.PHONY: all
+all: clean test lint build
+
+.PHONY: container-all
+container-all: clean container-generate build
\ No newline at end of file
diff --git a/pkg/process/finders/base/process.go b/bpf/include/api.h
similarity index 53%
copy from pkg/process/finders/base/process.go
copy to bpf/include/api.h
index 7358bd7..f058e20 100644
--- a/pkg/process/finders/base/process.go
+++ b/bpf/include/api.h
@@ -15,22 +15,26 @@
 // specific language governing permissions and limitations
 // under the License.
 
-package base
+#ifndef __BPF_API__
+#define __BPF_API__
 
-import (
-	"github.com/shirou/gopsutil/process"
+// include linux relate bpf
+#include <linux/bpf.h>
 
-	"github.com/apache/skywalking-rover/pkg/process/api"
-)
+#define __uint(name, val) int (*name)[val]
+#define __type(name, val) typeof(val) *name
+#define __array(name, val) typeof(val) *name[]
 
-// DetectedProcess from the finder
-type DetectedProcess interface {
-	// Pid of process in host
-	Pid() int32
-	// OriginalProcess is works for query the process data
-	OriginalProcess() *process.Process
-	// Entity of process, is related with backend entity
-	Entity() *api.ProcessEntity
-	// DetectType define the process find type
-	DetectType() api.ProcessDetectType
-}
+// Method Selection
+#define SEC(name) \
+	_Pragma("GCC diagnostic push")					    \
+	_Pragma("GCC diagnostic ignored \"-Wignored-attributes\"")	    \
+	__attribute__((section(name), used))				    \
+	_Pragma("GCC diagnostic pop")					    \
+
+// which reference what we need
+struct pt_regs;
+static long (*bpf_perf_event_output)(void *ctx, void *map, __u64 flags, void *data, __u64 size) = (void *) 25;
+static long (*bpf_get_stackid)(void *ctx, void *map, __u64 flags) = (void *) 27;
+
+#endif
\ No newline at end of file
diff --git a/pkg/boot/register.go b/bpf/profiling/oncpu.c
similarity index 65%
copy from pkg/boot/register.go
copy to bpf/profiling/oncpu.c
index 061cb4a..fa4321a 100644
--- a/pkg/boot/register.go
+++ b/bpf/profiling/oncpu.c
@@ -15,16 +15,20 @@
 // specific language governing permissions and limitations
 // under the License.
 
-package boot
+#include "api.h"
+#include "oncpu.h"
 
-import (
-	"github.com/apache/skywalking-rover/pkg/core"
-	"github.com/apache/skywalking-rover/pkg/module"
-	"github.com/apache/skywalking-rover/pkg/process"
-)
+char __license[] SEC("license") = "Dual MIT/GPL";
 
-func init() {
-	// register all active module
-	module.Register(core.NewModule())
-	module.Register(process.NewModule())
-}
+SEC("perf_event")
+int do_perf_event(struct pt_regs *ctx) {
+    // create map key
+    struct key_t key = {};
+
+    // get stacks
+    key.kernel_stack_id = bpf_get_stackid(ctx, &stacks, 0);
+    key.user_stack_id = bpf_get_stackid(ctx, &stacks, BPF_F_USER_STACK);
+
+    bpf_perf_event_output(ctx, &counts, BPF_F_CURRENT_CPU, &key, sizeof(key));
+    return 0;
+}
\ No newline at end of file
diff --git a/pkg/boot/register.go b/bpf/profiling/oncpu.h
similarity index 70%
copy from pkg/boot/register.go
copy to bpf/profiling/oncpu.h
index 061cb4a..6fc467d 100644
--- a/pkg/boot/register.go
+++ b/bpf/profiling/oncpu.h
@@ -15,16 +15,18 @@
 // specific language governing permissions and limitations
 // under the License.
 
-package boot
+struct key_t {
+    __u32 user_stack_id;
+    __u32 kernel_stack_id;
+};
 
-import (
-	"github.com/apache/skywalking-rover/pkg/core"
-	"github.com/apache/skywalking-rover/pkg/module"
-	"github.com/apache/skywalking-rover/pkg/process"
-)
+struct {
+	__uint(type, BPF_MAP_TYPE_PERF_EVENT_ARRAY);
+} counts SEC(".maps");
 
-func init() {
-	// register all active module
-	module.Register(core.NewModule())
-	module.Register(process.NewModule())
-}
+struct {
+    __uint(type, BPF_MAP_TYPE_STACK_TRACE);
+    __uint(key_size, sizeof(__u32));
+    __uint(value_size, 100 * sizeof(__u64));
+    __uint(max_entries, 10000);
+} stacks SEC(".maps");
\ No newline at end of file
diff --git a/configs/rover_configs.yaml b/configs/rover_configs.yaml
index c462249..33f9336 100644
--- a/configs/rover_configs.yaml
+++ b/configs/rover_configs.yaml
@@ -57,4 +57,18 @@ process_discovery:
         instance_name: ${ROVER_PROCESS_DISCOVERY_VM_FINDER_INSTANCE_NAME:{{.Rover.HostIPV4 "en0"}}}
         # The Process Name need to relate to the process entity
         # By default, the process name is the executable name of the process
-        process_name: ${ROVER_PROCESS_DISCOVERY_VM_FINDER_PROCESS_NAME:{{.Process.ExeName}}}
\ No newline at end of file
+        process_name: ${ROVER_PROCESS_DISCOVERY_VM_FINDER_PROCESS_NAME:{{.Process.ExeName}}}
+
+profiling:
+  # Is active the process profiling
+  active: ${ROVER_PROFILING_ACTIVE:true}
+  # Check the profiling task interval
+  check_interval: ${ROVER_PROFILING_CHECK_INTERVAL:10s}
+  # Combine existing profiling data and report to the backend interval
+  flush_interval: ${ROVER_PROFILING_FLUSH_INTERVAL:5s}
+  # Customize profiling task config
+  task:
+    # The config when executing ON_CPU profiling task
+    on_cpu:
+      # the profiling stack dump period
+      dump_period: ${ROVER_PROFILING_TASK_ON_CPU_DUMP_PERIOD:9ms}
\ No newline at end of file
diff --git a/docker/Dockerfile.base b/docker/Dockerfile.base
new file mode 100644
index 0000000..b938223
--- /dev/null
+++ b/docker/Dockerfile.base
@@ -0,0 +1,27 @@
+# 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.
+
+FROM golang:1.17
+
+RUN apt update && \
+    apt install -y libbpf-dev lsb-release wget software-properties-common && \
+    mkdir /usr/include/asm && \
+    cp /usr/include/asm-generic/* /usr/include/asm && \
+    wget https://apt.llvm.org/llvm.sh && \
+    chmod +x llvm.sh && \
+    ./llvm.sh 13
+
+ENV PATH="${PATH}:/usr/lib/llvm-13/bin"
\ No newline at end of file
diff --git a/go.mod b/go.mod
index 60264ef..498fb05 100644
--- a/go.mod
+++ b/go.mod
@@ -3,12 +3,15 @@ module github.com/apache/skywalking-rover
 go 1.17
 
 require (
+	github.com/cilium/ebpf v0.8.1
 	github.com/google/uuid v1.3.0
 	github.com/hashicorp/go-multierror v1.1.1
+	github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639
 	github.com/shirou/gopsutil v3.21.11+incompatible
 	github.com/sirupsen/logrus v1.8.1
 	github.com/spf13/cobra v1.3.0
 	github.com/spf13/viper v1.10.1
+	golang.org/x/sys v0.0.0-20211210111614-af8b64212486
 	google.golang.org/grpc v1.44.0
 	skywalking.apache.org/repo/goapi v0.0.0-20220302122002-ea09cd279b0d
 )
@@ -32,7 +35,6 @@ require (
 	github.com/tklauser/numcpus v0.3.0 // indirect
 	github.com/yusufpapurcu/wmi v1.2.2 // indirect
 	golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f // indirect
-	golang.org/x/sys v0.0.0-20211210111614-af8b64212486 // indirect
 	golang.org/x/text v0.3.7 // indirect
 	google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa // indirect
 	google.golang.org/protobuf v1.27.1 // indirect
diff --git a/go.sum b/go.sum
index c5a14db..77a0f94 100644
--- a/go.sum
+++ b/go.sum
@@ -72,6 +72,8 @@ github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL
 github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
 github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
 github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
+github.com/cilium/ebpf v0.8.1 h1:bLSSEbBLqGPXxls55pGr5qWZaTqcmfDJHhou7t254ao=
+github.com/cilium/ebpf v0.8.1/go.mod h1:f5zLIM0FSNuAkSyLAN7X+Hy6yznlF1mNiWUMfxMtrgk=
 github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag=
 github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I=
 github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
@@ -88,6 +90,7 @@ github.com/cncf/xds/go v0.0.0-20211130200136-a8f946100490/go.mod h1:eXthEFrGJvWH
 github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
 github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
 github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
+github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -105,6 +108,8 @@ github.com/envoyproxy/protoc-gen-validate v0.6.2/go.mod h1:2t7qjJNvHPx8IjnBOzl9E
 github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
 github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
 github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
+github.com/frankban/quicktest v1.14.0 h1:+cqqvzZV87b4adx/5ayVOaYZ2CrvM4ejQvUdBzPPUss=
+github.com/frankban/quicktest v1.14.0/go.mod h1:NeW+ay9A/U67EYXNFA1nPE8e/tnQv/09mUdL/ijj8og=
 github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI=
 github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
 github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
@@ -234,6 +239,7 @@ github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKEN
 github.com/hashicorp/serf v0.9.6/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4=
 github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=
 github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
+github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639 h1:mV02weKRL81bEnm8A0HT1/CAelMQDBuQIfLw8n+d6xI=
 github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
 github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
 github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
@@ -250,11 +256,13 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxv
 github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
 github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
 github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
-github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs=
 github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
+github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
+github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
 github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
-github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
+github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
 github.com/lyft/protoc-gen-star v0.5.3/go.mod h1:V0xaHgaf5oCCqmcxYcWiDfTiKsZsRc87/1qhoTACD8w=
 github.com/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls=
 github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
@@ -311,6 +319,8 @@ github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsT
 github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
 github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
 github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
+github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k=
+github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
 github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
 github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
 github.com/sagikazarmark/crypt v0.3.0/go.mod h1:uD/D+6UF4SrIR1uGEv7bBNkNqLGqUr43MRiaGWX1Nig=
@@ -553,6 +563,7 @@ golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.0.0-20210816074244-15123e1e1f71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
diff --git a/pkg/boot/register.go b/pkg/boot/register.go
index 061cb4a..10e161d 100644
--- a/pkg/boot/register.go
+++ b/pkg/boot/register.go
@@ -21,10 +21,12 @@ import (
 	"github.com/apache/skywalking-rover/pkg/core"
 	"github.com/apache/skywalking-rover/pkg/module"
 	"github.com/apache/skywalking-rover/pkg/process"
+	"github.com/apache/skywalking-rover/pkg/profiling"
 )
 
 func init() {
 	// register all active module
 	module.Register(core.NewModule())
 	module.Register(process.NewModule())
+	module.Register(profiling.NewModule())
 }
diff --git a/pkg/boot/register.go b/pkg/process/api.go
similarity index 74%
copy from pkg/boot/register.go
copy to pkg/process/api.go
index 061cb4a..2345d25 100644
--- a/pkg/boot/register.go
+++ b/pkg/process/api.go
@@ -15,16 +15,11 @@
 // specific language governing permissions and limitations
 // under the License.
 
-package boot
+package process
 
-import (
-	"github.com/apache/skywalking-rover/pkg/core"
-	"github.com/apache/skywalking-rover/pkg/module"
-	"github.com/apache/skywalking-rover/pkg/process"
-)
+import "github.com/apache/skywalking-rover/pkg/process/api"
 
-func init() {
-	// register all active module
-	module.Register(core.NewModule())
-	module.Register(process.NewModule())
+type Operator interface {
+	// FindProcessById the processID is received from the backend, if not found then return nil
+	FindProcessByID(processID string) api.ProcessInterface
 }
diff --git a/pkg/process/api/process.go b/pkg/process/api/process.go
index 39d5d84..75cc0b5 100644
--- a/pkg/process/api/process.go
+++ b/pkg/process/api/process.go
@@ -17,6 +17,8 @@
 
 package api
 
+import "github.com/apache/skywalking-rover/pkg/tools/profiling"
+
 type ProcessDetectType int8
 
 const (
@@ -43,6 +45,8 @@ type ProcessInterface interface {
 	DetectType() ProcessDetectType
 	// Entity of process in backend
 	Entity() *ProcessEntity
+	// ProfilingStat of process
+	ProfilingStat() *profiling.Info
 }
 
 // ProcessEntity is related to backend entity concept
diff --git a/pkg/process/finders/base/process.go b/pkg/process/finders/base/process.go
index 7358bd7..49246fe 100644
--- a/pkg/process/finders/base/process.go
+++ b/pkg/process/finders/base/process.go
@@ -21,6 +21,7 @@ import (
 	"github.com/shirou/gopsutil/process"
 
 	"github.com/apache/skywalking-rover/pkg/process/api"
+	"github.com/apache/skywalking-rover/pkg/tools/profiling"
 )
 
 // DetectedProcess from the finder
@@ -33,4 +34,6 @@ type DetectedProcess interface {
 	Entity() *api.ProcessEntity
 	// DetectType define the process find type
 	DetectType() api.ProcessDetectType
+	// ProfilingStat of process
+	ProfilingStat() *profiling.Info
 }
diff --git a/pkg/process/finders/context.go b/pkg/process/finders/context.go
index 8a5219b..715b9d2 100644
--- a/pkg/process/finders/context.go
+++ b/pkg/process/finders/context.go
@@ -20,6 +20,7 @@ package finders
 import (
 	"github.com/apache/skywalking-rover/pkg/process/api"
 	"github.com/apache/skywalking-rover/pkg/process/finders/base"
+	"github.com/apache/skywalking-rover/pkg/tools/profiling"
 )
 
 type ProcessUploadStatus int8
@@ -61,3 +62,7 @@ func (p *ProcessContext) DetectType() api.ProcessDetectType {
 func (p *ProcessContext) Entity() *api.ProcessEntity {
 	return p.detectProcess.Entity()
 }
+
+func (p *ProcessContext) ProfilingStat() *profiling.Info {
+	return p.detectProcess.ProfilingStat()
+}
diff --git a/pkg/process/finders/manager.go b/pkg/process/finders/manager.go
index 9c2a597..1787cf5 100644
--- a/pkg/process/finders/manager.go
+++ b/pkg/process/finders/manager.go
@@ -117,3 +117,7 @@ func (p *ProcessManagerWithFinder) GetModuleManager() *module.Manager {
 func (p *ProcessManagerWithFinder) SyncAllProcessInFinder(processes []base.DetectedProcess) {
 	p.storage.SyncAllProcessInFinder(p.finderType, processes)
 }
+
+func (m *ProcessManager) FindProcessByID(processID string) api.ProcessInterface {
+	return m.storage.FindProcessByID(processID)
+}
diff --git a/pkg/process/finders/storage.go b/pkg/process/finders/storage.go
index 754de08..0d2430b 100644
--- a/pkg/process/finders/storage.go
+++ b/pkg/process/finders/storage.go
@@ -201,7 +201,7 @@ func (s *ProcessStorage) SyncAllProcessInFinder(finder api.ProcessDetectType, pr
 		// So we need to remove this process
 		if needToSyncProcess == nil {
 			if managedProcess.DetectType() == finder {
-				s.processes[pid] = nil
+				delete(s.processes, pid)
 			}
 			continue
 		}
@@ -240,8 +240,18 @@ func (s *ProcessStorage) constructNewProcessContext(finder api.ProcessDetectType
 func (s *ProcessStorage) updateProcessToUploadSuccess(pc *ProcessContext, id string) {
 	pc.id = id
 	pc.syncStatus = ReportSuccess
+	log.Infof("uploaded process name: %s, id: %s", pc.detectProcess.Entity().ProcessName, id)
 }
 
 func (s *ProcessStorage) updateProcessToUploadIgnored(pc *ProcessContext) {
 	pc.syncStatus = Ignore
 }
+
+func (s *ProcessStorage) FindProcessByID(processID string) api.ProcessInterface {
+	for _, ps := range s.processes {
+		if ps.id == processID {
+			return ps
+		}
+	}
+	return nil
+}
diff --git a/pkg/process/finders/vm/finder.go b/pkg/process/finders/vm/finder.go
index 1f275a8..b94ccbf 100644
--- a/pkg/process/finders/vm/finder.go
+++ b/pkg/process/finders/vm/finder.go
@@ -20,6 +20,7 @@ package vm
 import (
 	"context"
 	"fmt"
+	"os"
 	"regexp"
 	"time"
 
@@ -31,6 +32,7 @@ import (
 	"github.com/apache/skywalking-rover/pkg/process/api"
 	"github.com/apache/skywalking-rover/pkg/process/finders/base"
 	"github.com/apache/skywalking-rover/pkg/tools"
+	"github.com/apache/skywalking-rover/pkg/tools/host"
 )
 
 var log = logger.GetLogger("process", "finder", "vm")
@@ -149,9 +151,9 @@ func (p *ProcessFinder) findAndReportProcesses() error {
 func (p *ProcessFinder) validateTheProcessesCouldProfiling(processes []*Process) []*Process {
 	result := make([]*Process, 0)
 	for _, ps := range processes {
-		exe, err := ps.original.Exe()
-		if err != nil {
-			log.Warnf("could not read process exe file path, pid: %d, reason: %v", ps.pid, err)
+		exe := p.tryToFindFileExecutePath(ps.original)
+		if exe == "" {
+			log.Warnf("could not read process exe file path, pid: %d", ps.pid)
 			continue
 		}
 
@@ -316,3 +318,31 @@ func regexMustNotNull(err error, confKey, confValue string) (*regexp.Regexp, err
 	}
 	return regexp.Compile(confValue)
 }
+
+func (p *ProcessFinder) tryToFindFileExecutePath(ps *process.Process) string {
+	exe, err := ps.Exe()
+	if pathExists(exe, err) {
+		return exe
+	}
+	cwd, err := ps.Cwd()
+	if pathExists(cwd, err) && pathExists(cwd+"/"+exe, err) {
+		return cwd + "/" + exe
+	}
+	linuxProcessRoot := host.GetFileInHost(fmt.Sprintf("/proc/%d/root", ps.Pid))
+	if pathExists(linuxProcessRoot, nil) {
+		if pathExists(linuxProcessRoot+"/"+exe, nil) {
+			return linuxProcessRoot + "/" + exe
+		} else if pathExists(linuxProcessRoot+"/"+cwd+"/"+exe, nil) {
+			return linuxProcessRoot + "/" + cwd + "/" + exe
+		}
+	}
+	return ""
+}
+
+func pathExists(exe string, err error) bool {
+	if err != nil {
+		return false
+	}
+	_, e := os.Stat(exe)
+	return e == nil
+}
diff --git a/pkg/process/finders/vm/process.go b/pkg/process/finders/vm/process.go
index d6566ce..e5d373f 100644
--- a/pkg/process/finders/vm/process.go
+++ b/pkg/process/finders/vm/process.go
@@ -60,6 +60,10 @@ func (p *Process) OriginalProcess() *process.Process {
 	return p.original
 }
 
+func (p *Process) ProfilingStat() *profiling.Info {
+	return p.profiling
+}
+
 // BuildIdentity without pid
 func (p *Process) BuildIdentity() string {
 	return fmt.Sprintf("%s_%s_%s_%s", p.entity.Layer, p.entity.ServiceName,
diff --git a/pkg/process/mdoule.go b/pkg/process/module.go
similarity index 91%
copy from pkg/process/mdoule.go
copy to pkg/process/module.go
index e7463c7..d57c3a3 100644
--- a/pkg/process/mdoule.go
+++ b/pkg/process/module.go
@@ -23,6 +23,7 @@ import (
 
 	"github.com/apache/skywalking-rover/pkg/core"
 	"github.com/apache/skywalking-rover/pkg/module"
+	"github.com/apache/skywalking-rover/pkg/process/api"
 	"github.com/apache/skywalking-rover/pkg/process/finders"
 )
 
@@ -72,3 +73,7 @@ func (m *Module) NotifyStartSuccess() {
 func (m *Module) Shutdown(ctx context.Context, mgr *module.Manager) error {
 	return m.manager.Shutdown()
 }
+
+func (m *Module) FindProcessByID(processID string) api.ProcessInterface {
+	return m.manager.FindProcessByID(processID)
+}
diff --git a/pkg/boot/register.go b/pkg/profiling/config.go
similarity index 69%
copy from pkg/boot/register.go
copy to pkg/profiling/config.go
index 061cb4a..6bd9aa4 100644
--- a/pkg/boot/register.go
+++ b/pkg/profiling/config.go
@@ -15,16 +15,18 @@
 // specific language governing permissions and limitations
 // under the License.
 
-package boot
+package profiling
 
 import (
-	"github.com/apache/skywalking-rover/pkg/core"
 	"github.com/apache/skywalking-rover/pkg/module"
-	"github.com/apache/skywalking-rover/pkg/process"
+	"github.com/apache/skywalking-rover/pkg/profiling/task/base"
 )
 
-func init() {
-	// register all active module
-	module.Register(core.NewModule())
-	module.Register(process.NewModule())
+type Config struct {
+	module.Config `mapstructure:",squash"`
+
+	CheckInterval string `mapstructure:"check_interval"` // Check the profiling task interval
+	FlushInterval string `mapstructure:"flush_interval"` // Flush profiling data interval
+
+	TaskConfig *base.TaskConfig `mapstructure:"task"` // Profiling task config
 }
diff --git a/pkg/profiling/manager.go b/pkg/profiling/manager.go
new file mode 100644
index 0000000..59f36f8
--- /dev/null
+++ b/pkg/profiling/manager.go
@@ -0,0 +1,156 @@
+// Licensed to 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. Apache Software Foundation (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 profiling
+
+import (
+	"context"
+	"fmt"
+	"time"
+
+	"github.com/apache/skywalking-rover/pkg/core"
+	"github.com/apache/skywalking-rover/pkg/module"
+	"github.com/apache/skywalking-rover/pkg/process"
+	"github.com/apache/skywalking-rover/pkg/profiling/task"
+
+	v3 "skywalking.apache.org/repo/goapi/collect/ebpf/profiling/v3"
+)
+
+// Manager the profiling task, receive them from the backend side
+type Manager struct {
+	profilingClient v3.EBPFProfilingServiceClient
+	interval        time.Duration
+	taskManager     *task.Manager
+
+	ctx    context.Context
+	cancel context.CancelFunc
+
+	instanceID     string
+	lastUpdateTime int64
+}
+
+func NewManager(ctx context.Context, manager *module.Manager, conf *Config) (*Manager, error) {
+	coreOperator := manager.FindModule(core.ModuleName).(core.Operator)
+	connection := coreOperator.BackendOperator().GetConnection()
+	profilingClient := v3.NewEBPFProfilingServiceClient(connection)
+	instanceID := coreOperator.InstanceID()
+	duration, err := time.ParseDuration(conf.CheckInterval)
+	if err != nil {
+		return nil, fmt.Errorf("parse profling check interval failure: %v", err)
+	}
+
+	flushDuration, err := time.ParseDuration(conf.FlushInterval)
+	if err != nil {
+		return nil, fmt.Errorf("parse profiling data flush interval failure: %v", err)
+	}
+
+	taskManager, err := task.NewManager(ctx, manager.FindModule(process.ModuleName).(process.Operator),
+		profilingClient, flushDuration, conf.TaskConfig)
+	if err != nil {
+		return nil, err
+	}
+	ctx, cancel := context.WithCancel(ctx)
+	return &Manager{
+		profilingClient: profilingClient,
+		taskManager:     taskManager,
+		interval:        duration,
+		ctx:             ctx,
+		cancel:          cancel,
+		instanceID:      instanceID,
+		lastUpdateTime:  -1,
+	}, nil
+}
+
+func (m *Manager) Start() {
+	m.taskManager.Start()
+	go func() {
+		timeTicker := time.NewTicker(m.interval)
+		for {
+			select {
+			case <-timeTicker.C:
+				if err := m.startingWatchTask(); err != nil {
+					log.Errorf("fetch profiling task failure: %v", err)
+				}
+			case <-m.ctx.Done():
+				timeTicker.Stop()
+				return
+			}
+		}
+	}()
+}
+
+func (m *Manager) startingWatchTask() error {
+	// query task
+	tasks, err := m.profilingClient.QueryTasks(m.ctx, &v3.EBPFProfilingTaskQuery{
+		RoverInstanceId:  m.instanceID,
+		LatestUpdateTime: m.lastUpdateTime,
+	})
+	if err != nil {
+		return err
+	}
+	if len(tasks.Commands) == 0 {
+		return nil
+	}
+
+	// analyze profiling tasks
+	taskContexts := make([]*task.Context, 0)
+	lastUpdateTime := m.lastUpdateTime
+	for _, cmd := range tasks.Commands {
+		taskContext, err := m.taskManager.BuildContext(cmd)
+		if err != nil {
+			log.Warnf("could not execute task, ignored. %v", err)
+			continue
+		}
+
+		if taskContext.UpdateTime() > lastUpdateTime {
+			lastUpdateTime = taskContext.UpdateTime()
+		}
+
+		if !taskContext.CheckTaskRunnable() {
+			continue
+		}
+
+		taskContexts = append(taskContexts, taskContext)
+	}
+
+	// update last task time
+	m.lastUpdateTime = lastUpdateTime
+
+	if len(taskContexts) == 0 {
+		return nil
+	}
+
+	taskIDList := make([]string, len(taskContexts))
+	for inx, c := range taskContexts {
+		taskIDList[inx] = c.TaskID()
+	}
+	log.Infof("received %d profiling task: %v", len(taskContexts), taskIDList)
+
+	// start tasks
+	for _, t := range taskContexts {
+		m.taskManager.StartTask(t)
+	}
+	return nil
+}
+
+func (m *Manager) Shutdown() error {
+	if err := m.taskManager.Shutdown(); err != nil {
+		log.Warnf("task manager shutdown failure: %v", err)
+	}
+	m.cancel()
+	return nil
+}
diff --git a/pkg/process/mdoule.go b/pkg/profiling/module.go
similarity index 76%
rename from pkg/process/mdoule.go
rename to pkg/profiling/module.go
index e7463c7..88b149c 100644
--- a/pkg/process/mdoule.go
+++ b/pkg/profiling/module.go
@@ -15,23 +15,27 @@
 // specific language governing permissions and limitations
 // under the License.
 
-package process
+package profiling
 
 import (
 	"context"
-	"time"
+
+	"github.com/cilium/ebpf/rlimit"
 
 	"github.com/apache/skywalking-rover/pkg/core"
+	"github.com/apache/skywalking-rover/pkg/logger"
 	"github.com/apache/skywalking-rover/pkg/module"
-	"github.com/apache/skywalking-rover/pkg/process/finders"
+	"github.com/apache/skywalking-rover/pkg/process"
 )
 
-const ModuleName = "process_discovery"
+const ModuleName = "profiling"
+
+var log = logger.GetLogger("profiling")
 
 type Module struct {
 	config *Config
 
-	manager *finders.ProcessManager
+	manager *Manager
 }
 
 func NewModule() *Module {
@@ -43,7 +47,7 @@ func (m *Module) Name() string {
 }
 
 func (m *Module) RequiredModules() []string {
-	return []string{core.ModuleName}
+	return []string{core.ModuleName, process.ModuleName}
 }
 
 func (m *Module) Config() module.ConfigInterface {
@@ -51,21 +55,20 @@ func (m *Module) Config() module.ConfigInterface {
 }
 
 func (m *Module) Start(ctx context.Context, mgr *module.Manager) error {
-	period, err := time.ParseDuration(m.config.HeartbeatPeriod)
-	if err != nil {
+	// Allow the current process to lock memory for eBPF resources.
+	if err := rlimit.RemoveMemlock(); err != nil {
 		return err
 	}
-	processManager, err := finders.NewProcessManager(ctx, mgr, period, m.config.VM)
+
+	manager, err := NewManager(ctx, mgr, m.config)
 	if err != nil {
 		return err
 	}
-	m.manager = processManager
-
+	m.manager = manager
 	return nil
 }
 
 func (m *Module) NotifyStartSuccess() {
-	// notify all finder to report processes
 	m.manager.Start()
 }
 
diff --git a/pkg/process/finders/base/process.go b/pkg/profiling/task/base/api.go
similarity index 53%
copy from pkg/process/finders/base/process.go
copy to pkg/profiling/task/base/api.go
index 7358bd7..b82c7fc 100644
--- a/pkg/process/finders/base/process.go
+++ b/pkg/profiling/task/base/api.go
@@ -18,19 +18,25 @@
 package base
 
 import (
-	"github.com/shirou/gopsutil/process"
+	"context"
 
 	"github.com/apache/skywalking-rover/pkg/process/api"
+
+	v3 "skywalking.apache.org/repo/goapi/collect/ebpf/profiling/v3"
 )
 
-// DetectedProcess from the finder
-type DetectedProcess interface {
-	// Pid of process in host
-	Pid() int32
-	// OriginalProcess is works for query the process data
-	OriginalProcess() *process.Process
-	// Entity of process, is related with backend entity
-	Entity() *api.ProcessEntity
-	// DetectType define the process find type
-	DetectType() api.ProcessDetectType
+const MissingSymbol = "[MISSING]"
+
+type ProfilingRunningSuccessNotify func()
+
+// ProfileTaskRunner is use to running different type of profiling task, such as on-cpu profiling task
+type ProfileTaskRunner interface {
+	// Init runner with profiling task and process
+	Init(task *ProfilingTask, process api.ProcessInterface) error
+	// Run profiling, if throw error or method finish means the profiling task finished
+	Run(ctx context.Context, notify ProfilingRunningSuccessNotify) error
+	// Stop the runner initiative, is typically used to specify the profiling duration
+	Stop() error
+	// FlushData means dump the exists profiling data and flush them to the backend protocol format
+	FlushData() ([]*v3.EBPFProfilingData, error)
 }
diff --git a/pkg/boot/register.go b/pkg/profiling/task/base/config.go
similarity index 74%
copy from pkg/boot/register.go
copy to pkg/profiling/task/base/config.go
index 061cb4a..1c03158 100644
--- a/pkg/boot/register.go
+++ b/pkg/profiling/task/base/config.go
@@ -15,16 +15,12 @@
 // specific language governing permissions and limitations
 // under the License.
 
-package boot
+package base
 
-import (
-	"github.com/apache/skywalking-rover/pkg/core"
-	"github.com/apache/skywalking-rover/pkg/module"
-	"github.com/apache/skywalking-rover/pkg/process"
-)
+type TaskConfig struct {
+	OnCPU *OnCPUConfig `mapstructure:"on_cpu"` // ON_CPU type of profiling task config
+}
 
-func init() {
-	// register all active module
-	module.Register(core.NewModule())
-	module.Register(process.NewModule())
+type OnCPUConfig struct {
+	Period string `mapstructure:"dump_period"` // The duration of dump stack
 }
diff --git a/pkg/process/finders/base/process.go b/pkg/profiling/task/base/target.go
similarity index 64%
copy from pkg/process/finders/base/process.go
copy to pkg/profiling/task/base/target.go
index 7358bd7..849729b 100644
--- a/pkg/process/finders/base/process.go
+++ b/pkg/profiling/task/base/target.go
@@ -18,19 +18,27 @@
 package base
 
 import (
-	"github.com/shirou/gopsutil/process"
+	"fmt"
 
-	"github.com/apache/skywalking-rover/pkg/process/api"
+	v3 "skywalking.apache.org/repo/goapi/collect/common/v3"
 )
 
-// DetectedProcess from the finder
-type DetectedProcess interface {
-	// Pid of process in host
-	Pid() int32
-	// OriginalProcess is works for query the process data
-	OriginalProcess() *process.Process
-	// Entity of process, is related with backend entity
-	Entity() *api.ProcessEntity
-	// DetectType define the process find type
-	DetectType() api.ProcessDetectType
+type TargetType string
+
+const (
+	TargetTypeOnCPU TargetType = "ON_CPU"
+)
+
+func ParseTargetType(err error, val string) (TargetType, error) {
+	if err != nil {
+		return "", err
+	}
+	if TargetType(val) == TargetTypeOnCPU {
+		return TargetTypeOnCPU, nil
+	}
+	return "", fmt.Errorf("could not found target type: %s", val)
+}
+
+func (t TargetType) InitTask(task *ProfilingTask, command *v3.Command) error {
+	return nil
 }
diff --git a/pkg/profiling/task/base/task.go b/pkg/profiling/task/base/task.go
new file mode 100644
index 0000000..cef7e9a
--- /dev/null
+++ b/pkg/profiling/task/base/task.go
@@ -0,0 +1,100 @@
+// Licensed to 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. Apache Software Foundation (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 base
+
+import (
+	"fmt"
+	"strconv"
+	"time"
+
+	v3 "skywalking.apache.org/repo/goapi/collect/common/v3"
+)
+
+type ProfilingTask struct {
+	// TaskID of profiling task
+	TaskID string
+	// ProcessID of need to monitoring process
+	ProcessID string
+	// UpdateTime of profiling task
+	UpdateTime int64
+	// StartTime of profiling task, when need to start to profiling
+	StartTime int64
+	// TriggerType of task
+	TriggerType TriggerType
+	// TargetType of task
+	TargetType TargetType
+	// MaxRunningDuration of task
+	MaxRunningDuration time.Duration
+}
+
+func ProfilingTaskFromCommand(command *v3.Command) (*ProfilingTask, error) {
+	if command.GetCommand() != "EBPFProfilingTaskQuery" {
+		return nil, fmt.Errorf("not support command: %s", command.GetCommand())
+	}
+
+	var err error
+	taskID, err := getCommandStringValue(err, command, "TaskId")
+	processID, err := getCommandStringValue(err, command, "ProcessId")
+	taskUpdateTime, err := getCommandIntValue(err, command, "TaskUpdateTime")
+	triggerTypeStr, err := getCommandStringValue(err, command, "TriggerType")
+	triggerType, err := ParseTriggerType(err, triggerTypeStr)
+	targetTypeStr, err := getCommandStringValue(err, command, "TargetType")
+	targetType, err := ParseTargetType(err, targetTypeStr)
+	taskStartTime, err := getCommandIntValue(err, command, "TaskStartTime")
+	if err != nil {
+		return nil, err
+	}
+
+	task := &ProfilingTask{
+		TaskID:      taskID,
+		ProcessID:   processID,
+		UpdateTime:  taskUpdateTime,
+		StartTime:   taskStartTime,
+		TargetType:  targetType,
+		TriggerType: triggerType,
+	}
+
+	if err := task.TriggerType.InitTask(task, command); err != nil {
+		return nil, err
+	}
+	if err := task.TargetType.InitTask(task, command); err != nil {
+		return nil, err
+	}
+
+	return task, nil
+}
+
+func getCommandStringValue(err error, command *v3.Command, key string) (string, error) {
+	if err != nil {
+		return "", err
+	}
+	for _, arg := range command.GetArgs() {
+		if arg.GetKey() == key {
+			return arg.GetValue(), nil
+		}
+	}
+	return "", fmt.Errorf("could not found key: %v", key)
+}
+
+func getCommandIntValue(err error, command *v3.Command, key string) (int64, error) {
+	val, err := getCommandStringValue(err, command, key)
+	if err != nil {
+		return 0, err
+	}
+	return strconv.ParseInt(val, 10, 64)
+}
diff --git a/pkg/process/finders/base/process.go b/pkg/profiling/task/base/trigger.go
similarity index 54%
copy from pkg/process/finders/base/process.go
copy to pkg/profiling/task/base/trigger.go
index 7358bd7..978e3c0 100644
--- a/pkg/process/finders/base/process.go
+++ b/pkg/profiling/task/base/trigger.go
@@ -18,19 +18,35 @@
 package base
 
 import (
-	"github.com/shirou/gopsutil/process"
+	"fmt"
+	"time"
 
-	"github.com/apache/skywalking-rover/pkg/process/api"
+	v3 "skywalking.apache.org/repo/goapi/collect/common/v3"
 )
 
-// DetectedProcess from the finder
-type DetectedProcess interface {
-	// Pid of process in host
-	Pid() int32
-	// OriginalProcess is works for query the process data
-	OriginalProcess() *process.Process
-	// Entity of process, is related with backend entity
-	Entity() *api.ProcessEntity
-	// DetectType define the process find type
-	DetectType() api.ProcessDetectType
+type TriggerType string
+
+const (
+	TriggerTypeFixedTime TriggerType = "FIXED_TIME"
+)
+
+func ParseTriggerType(err error, val string) (TriggerType, error) {
+	if err != nil {
+		return "", err
+	}
+	if TriggerType(val) == TriggerTypeFixedTime {
+		return TriggerTypeFixedTime, nil
+	}
+	return "", fmt.Errorf("could not found trigger type: %s", val)
+}
+
+func (t TriggerType) InitTask(task *ProfilingTask, command *v3.Command) error {
+	if t == TriggerTypeFixedTime {
+		val, err := getCommandIntValue(nil, command, "FixedTriggerDuration")
+		if err != nil {
+			return err
+		}
+		task.MaxRunningDuration = time.Duration(val) * time.Second
+	}
+	return nil
 }
diff --git a/pkg/profiling/task/context.go b/pkg/profiling/task/context.go
new file mode 100644
index 0000000..de64805
--- /dev/null
+++ b/pkg/profiling/task/context.go
@@ -0,0 +1,80 @@
+// Licensed to 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. Apache Software Foundation (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 task
+
+import (
+	"context"
+	"fmt"
+	"sync"
+	"time"
+
+	"github.com/apache/skywalking-rover/pkg/process/api"
+	"github.com/apache/skywalking-rover/pkg/profiling/task/base"
+)
+
+type RunningStatus uint8
+
+const (
+	_ RunningStatus = iota
+	NotRunning
+	Running
+	Stopped
+)
+
+// Context of profiling task
+type Context struct {
+	task             *base.ProfilingTask
+	process          api.ProcessInterface
+	runner           base.ProfileTaskRunner
+	status           RunningStatus
+	startRunningTime time.Time
+	runningWg        *sync.WaitGroup
+	ctx              context.Context
+	cancel           context.CancelFunc
+}
+
+// UpdateTime of the profiling task
+func (c *Context) UpdateTime() int64 {
+	return c.task.UpdateTime
+}
+
+func (c *Context) TaskID() string {
+	return c.task.TaskID
+}
+
+// BuildTaskIdentity for filter with same identity task
+func (c *Context) BuildTaskIdentity() string {
+	// use process id, target type, trigger type
+	return fmt.Sprintf("%s_%s_%s", c.task.ProcessID, c.task.TargetType, c.task.TriggerType)
+}
+
+// CheckTaskRunnable means checks the task could be running
+func (c *Context) CheckTaskRunnable() bool {
+	// if running with FIXED_TIME type task, check the executing time range
+	if c.task.TriggerType == base.TriggerTypeFixedTime {
+		startTime := c.task.StartTime
+		endTime := time.UnixMilli(startTime).Add(c.task.MaxRunningDuration).UnixMilli()
+		now := time.Now().UnixMilli()
+
+		if now > endTime {
+			log.Infof("out of task executing time range. task id: %s", c.task.TaskID)
+			return false
+		}
+	}
+	return true
+}
diff --git a/pkg/profiling/task/manager.go b/pkg/profiling/task/manager.go
new file mode 100644
index 0000000..8057870
--- /dev/null
+++ b/pkg/profiling/task/manager.go
@@ -0,0 +1,255 @@
+// Licensed to 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. Apache Software Foundation (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 task
+
+import (
+	"context"
+	"fmt"
+	"sync"
+	"time"
+
+	"github.com/apache/skywalking-rover/pkg/logger"
+	"github.com/apache/skywalking-rover/pkg/process"
+	"github.com/apache/skywalking-rover/pkg/profiling/task/base"
+
+	common_v3 "skywalking.apache.org/repo/goapi/collect/common/v3"
+	profiling_v3 "skywalking.apache.org/repo/goapi/collect/ebpf/profiling/v3"
+)
+
+var log = logger.GetLogger("profiling", "task")
+
+type Manager struct {
+	processOperator process.Operator
+	profilingClient profiling_v3.EBPFProfilingServiceClient
+	flushInterval   time.Duration
+	ctx             context.Context
+	cancel          context.CancelFunc
+	taskConfig      *base.TaskConfig
+
+	tasks map[string]*Context
+}
+
+func NewManager(ctx context.Context, processOperator process.Operator,
+	profilingClient profiling_v3.EBPFProfilingServiceClient, flushInterval time.Duration, taskConfig *base.TaskConfig) (*Manager, error) {
+	if err := CheckProfilingTaskConfig(taskConfig); err != nil {
+		return nil, err
+	}
+
+	ctx, cancel := context.WithCancel(ctx)
+	manager := &Manager{
+		processOperator: processOperator,
+		profilingClient: profilingClient,
+		taskConfig:      taskConfig,
+		tasks:           make(map[string]*Context),
+		flushInterval:   flushInterval,
+		ctx:             ctx,
+		cancel:          cancel,
+	}
+	return manager, nil
+}
+
+func (m *Manager) Start() {
+	go m.startFlushProfilingData()
+}
+
+func (m *Manager) BuildContext(command *common_v3.Command) (*Context, error) {
+	// analyze command
+	t, err := base.ProfilingTaskFromCommand(command)
+	if err != nil || t == nil {
+		return nil, fmt.Errorf("parsing profiling task failure, command: %v, reason: %v", command.GetArgs(), err)
+	}
+
+	// find process
+	taskProcess := m.processOperator.FindProcessByID(t.ProcessID)
+	if taskProcess == nil {
+		return nil, fmt.Errorf("could not found %s process %s", t.TaskID, t.ProcessID)
+	}
+
+	// init runner
+	var r base.ProfileTaskRunner
+	if runner, err := NewProfilingRunner(t.TargetType, m.taskConfig); err != nil {
+		return nil, err
+	} else if err := runner.Init(t, taskProcess); err != nil {
+		return nil, fmt.Errorf("could not init %s runner for task: %s: %v", t.TriggerType, t.TaskID, err)
+	} else {
+		r = runner
+	}
+
+	ctx, cancel := context.WithCancel(m.ctx)
+	return &Context{task: t, process: taskProcess, runner: r, status: NotRunning, ctx: ctx, cancel: cancel}, nil
+}
+
+func (m *Manager) StartTask(c *Context) {
+	// shutdown task if exists
+	taskIdentity := c.BuildTaskIdentity()
+	if m.tasks[taskIdentity] != nil {
+		if err := m.shutdownAndRemoveTask(m.tasks[taskIdentity]); err != nil {
+			log.Warnf("shutdown existing profiling task failure, so cannot to start new profiling task: %v. reason: %v", c.task.TaskID, err)
+			return
+		}
+	}
+
+	currentMilli := time.Now().UnixNano() / int64(time.Millisecond)
+	m.tasks[taskIdentity] = c
+
+	// already reach time
+	if currentMilli >= c.task.StartTime {
+		m.runTask(c)
+		return
+	}
+
+	// schedule to execute
+	afterRun := time.Since(time.UnixMilli(c.task.StartTime))
+	go func() {
+		select {
+		case <-time.After(afterRun):
+			m.runTask(c)
+		case <-c.ctx.Done():
+			return
+		}
+	}()
+}
+
+func (m *Manager) runTask(c *Context) {
+	var wg sync.WaitGroup
+	wg.Add(1)
+	c.runningWg = &wg
+	go func() {
+		defer func() {
+			wg.Done()
+			c.status = Stopped
+		}()
+		c.status = Running
+		c.startRunningTime = time.Now()
+
+		notify := func() {
+			m.afterProfilingStartSuccess(c)
+		}
+		// start running
+		if err := c.runner.Run(m.ctx, notify); err != nil {
+			log.Warnf("executing profiling task failure, taskId: %s, reason: %v", c.task.TaskID, err)
+		}
+	}()
+}
+
+func (m *Manager) afterProfilingStartSuccess(c *Context) {
+	log.Infof("starting the profiling task. taskId: %s, pid: %d", c.task.TaskID, c.process.Pid())
+	go func() {
+		select {
+		// shutdown task when arrived task running task
+		case <-time.After(c.task.MaxRunningDuration):
+			log.Infof("arrived task running time, shutting down task: %s", c.task.TaskID)
+			if err := m.shutdownTask(c); err != nil {
+				log.Warnf("shutting down task failure: %s, reason: %v", c.task.TaskID, err)
+			}
+		// shutdown when context finished
+		case <-c.ctx.Done():
+			if err := m.shutdownTask(c); err != nil {
+				log.Warnf("shutting down task failure: %s, reason: %v", c.task.TaskID, err)
+			}
+		}
+	}()
+}
+
+func (m *Manager) shutdownTask(c *Context) error {
+	// return if not running
+	if c.runningWg == nil {
+		return nil
+	}
+	err := c.runner.Stop()
+	c.runningWg.Wait()
+	c.cancel()
+	return err
+}
+
+func (m *Manager) shutdownAndRemoveTask(c *Context) error {
+	err := m.shutdownTask(c)
+	delete(m.tasks, c.BuildTaskIdentity())
+	return err
+}
+
+func (m *Manager) Shutdown() error {
+	m.cancel()
+	return nil
+}
+
+func (m *Manager) checkStoppedTaskAndRemoved() {
+	for identity, t := range m.tasks {
+		if t.status == Stopped {
+			delete(m.tasks, identity)
+		}
+	}
+}
+
+func (m *Manager) startFlushProfilingData() {
+	timeTicker := time.NewTicker(m.flushInterval)
+	for {
+		select {
+		case <-timeTicker.C:
+			if err := m.flushProfilingData(); err != nil {
+				log.Warnf("flush profiling data failure: %v", err)
+			}
+			// cleanup the stopped after flush profiling data to make sure all the profiling data been sent
+			m.checkStoppedTaskAndRemoved()
+		case <-m.ctx.Done():
+			timeTicker.Stop()
+			return
+		}
+	}
+}
+
+func (m *Manager) flushProfilingData() error {
+	if len(m.tasks) == 0 {
+		return nil
+	}
+
+	stream, err := m.profilingClient.CollectProfilingData(m.ctx)
+	if err != nil {
+		return err
+	}
+	currentMilli := time.Now().UnixMilli()
+	for _, t := range m.tasks {
+		data, err1 := t.runner.FlushData()
+		if err1 != nil {
+			log.Warnf("reading profiling task data failure. taskId: %s, error: %v", t.task.TaskID, err1)
+			continue
+		}
+
+		if len(data) == 0 {
+			continue
+		}
+
+		// only the first data have task metadata
+		data[0].Task = &profiling_v3.EBPFProfilingTaskMetadata{
+			TaskId:             t.task.TaskID,
+			ProcessId:          t.task.ProcessID,
+			ProfilingStartTime: t.startRunningTime.UnixMilli(),
+			CurrentTime:        currentMilli,
+		}
+
+		for _, d := range data {
+			// send each data, stop flush data if the stream have found error
+			if err1 := stream.Send(d); err1 != nil {
+				return err1
+			}
+		}
+	}
+
+	_, err = stream.CloseAndRecv()
+	return err
+}
diff --git a/pkg/profiling/task/oncpu/runner.go b/pkg/profiling/task/oncpu/runner.go
new file mode 100644
index 0000000..ba7b893
--- /dev/null
+++ b/pkg/profiling/task/oncpu/runner.go
@@ -0,0 +1,311 @@
+// Licensed to 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. Apache Software Foundation (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.
+
+//go:build linux
+
+package oncpu
+
+import (
+	"bytes"
+	"context"
+	"encoding/binary"
+	"errors"
+	"fmt"
+	"math"
+	"os"
+	"runtime"
+	"sync"
+	"time"
+
+	"github.com/cilium/ebpf"
+	"github.com/cilium/ebpf/perf"
+
+	"github.com/hashicorp/go-multierror"
+
+	"github.com/apache/skywalking-rover/pkg/logger"
+	"github.com/apache/skywalking-rover/pkg/process/api"
+	"github.com/apache/skywalking-rover/pkg/profiling/task/base"
+	"github.com/apache/skywalking-rover/pkg/tools"
+	"github.com/apache/skywalking-rover/pkg/tools/profiling"
+
+	"golang.org/x/sys/unix"
+
+	v3 "skywalking.apache.org/repo/goapi/collect/ebpf/profiling/v3"
+)
+
+// $BPF_CLANG and $BPF_CFLAGS are set by the Makefile.
+// nolint
+//go:generate go run github.com/cilium/ebpf/cmd/bpf2go -cc $BPF_CLANG -cflags $BPF_CFLAGS bpf $REPO_ROOT/bpf/profiling/oncpu.c -- -I$REPO_ROOT/bpf/include
+
+var log = logger.GetLogger("profiling", "task", "oncpu")
+
+type Event struct {
+	UserStackID   uint32
+	KernelStackID uint32
+}
+
+type Runner struct {
+	pid              int32
+	processProfiling *profiling.Info
+	kernelProfiling  *profiling.Info
+	dumpPeriod       time.Duration
+
+	// runtime
+	perfEventFds       []int
+	countReader        *perf.Reader
+	stackCounter       map[Event]int
+	stackMap           *ebpf.Map
+	stackNotFoundCache map[uint32]bool
+	shutdownOnce       sync.Once
+	flushDataNotify    chan bool
+}
+
+func NewRunner(config *base.TaskConfig) (base.ProfileTaskRunner, error) {
+	if config.OnCPU.Period == "" {
+		return nil, fmt.Errorf("please provide the ON_CPU dump period")
+	}
+	dumpPeriod, err := time.ParseDuration(config.OnCPU.Period)
+	if err != nil {
+		return nil, fmt.Errorf("the ON_CPU dump period format not right, current value: %s", config.OnCPU.Period)
+	}
+	if dumpPeriod < time.Millisecond {
+		return nil, fmt.Errorf("the ON_CPU dump period could not be smaller than 1ms")
+	}
+	return &Runner{
+		dumpPeriod:         dumpPeriod,
+		stackNotFoundCache: make(map[uint32]bool),
+		stackCounter:       make(map[Event]int),
+	}, nil
+}
+
+func (r *Runner) Init(task *base.ProfilingTask, process api.ProcessInterface) error {
+	r.pid = process.Pid()
+	// process profiling stat
+	if r.processProfiling = process.ProfilingStat(); r.processProfiling == nil {
+		return fmt.Errorf("this process could not be profiling")
+	}
+	// kernel profiling stat
+	kernelProfiling, err := tools.KernelFileProfilingStat()
+	if err != nil {
+		log.Warnf("could not analyze kernel profiling stats: %v", err)
+	}
+	r.kernelProfiling = kernelProfiling
+	r.stackCounter = make(map[Event]int)
+	return nil
+}
+
+func (r *Runner) Run(ctx context.Context, notify base.ProfilingRunningSuccessNotify) error {
+	// load bpf
+	objs := bpfObjects{}
+	if err := loadBpfObjects(&objs, nil); err != nil {
+		return err
+	}
+	defer objs.Close()
+	r.stackMap = objs.Stacks
+
+	// init profiling data reader
+	rd, err := perf.NewReader(objs.Counts, os.Getpagesize())
+	if err != nil {
+		return fmt.Errorf("creating perf event reader: %s", err)
+	}
+	r.countReader = rd
+
+	// opened perf events
+	perfEvents, err := r.openPerfEvent(objs.DoPerfEvent.FD())
+	r.perfEventFds = perfEvents
+	if err != nil {
+		return err
+	}
+
+	// notify start success
+	notify()
+	runtime.SetFinalizer(r, (*Runner).Stop)
+
+	// read content
+	var event Event
+	for {
+		record, err := rd.Read()
+		if err != nil {
+			if errors.Is(err, perf.ErrClosed) {
+				return nil
+			}
+			log.Warnf("reading from perf event reader: %s", err)
+			continue
+		}
+
+		if record.LostSamples != 0 {
+			log.Warnf("perf event ring buffer full, dropped %d samples", record.LostSamples)
+			continue
+		}
+
+		// parse perf event data
+		if err := binary.Read(bytes.NewBuffer(record.RawSample), binary.LittleEndian, &event); err != nil {
+			log.Errorf("parsing perf event error: %s", err)
+			continue
+		}
+
+		r.stackCounter[event]++
+	}
+}
+
+func (r *Runner) openPerfEvent(perfFd int) ([]int, error) {
+	eventAttr := &unix.PerfEventAttr{
+		Type:        unix.PERF_TYPE_SOFTWARE,
+		Config:      unix.PERF_COUNT_SW_CPU_CLOCK,
+		Sample_type: unix.PERF_SAMPLE_RAW,
+		Sample:      uint64(r.dumpPeriod.Nanoseconds()),
+		Wakeup:      1,
+	}
+
+	fds := make([]int, 0)
+	for cpuNum := 0; cpuNum < runtime.NumCPU(); cpuNum++ {
+		fd, err := unix.PerfEventOpen(
+			eventAttr,
+			int(r.pid),
+			cpuNum,
+			-1,
+			0,
+		)
+		if err != nil {
+			return fds, err
+		}
+
+		// attach ebpf to perf event
+		if err := unix.IoctlSetInt(fd, unix.PERF_EVENT_IOC_SET_BPF, perfFd); err != nil {
+			return fds, err
+		}
+
+		// enable perf event
+		if err := unix.IoctlSetInt(fd, unix.PERF_EVENT_IOC_ENABLE, 0); err != nil {
+			return fds, err
+		}
+		fds = append(fds, fd)
+	}
+
+	return fds, nil
+}
+
+func (r *Runner) Stop() error {
+	var result error
+	r.shutdownOnce.Do(func() {
+		for _, fd := range r.perfEventFds {
+			if err := r.closePerfEvent(fd); err != nil {
+				result = multierror.Append(result, err)
+			}
+		}
+
+		// wait for all profiling data been consume finished
+		var flushDataNotify = make(chan bool)
+		r.flushDataNotify = flushDataNotify
+		select {
+		case <-flushDataNotify:
+		case <-time.After(5 * time.Second):
+		}
+
+		if r.countReader != nil {
+			if err := r.countReader.Close(); err != nil {
+				result = multierror.Append(result, err)
+			}
+		}
+	})
+	return result
+}
+
+func (r *Runner) FlushData() ([]*v3.EBPFProfilingData, error) {
+	existsCounters := r.flushStackCounter()
+
+	result := make([]*v3.EBPFProfilingData, 0)
+	stackSymbols := make([]uint64, 100)
+	for event, count := range existsCounters {
+		metadatas := make([]*v3.EBPFProfilingStackMetadata, 0)
+		// kernel stack
+		if d := r.generateProfilingData(r.kernelProfiling, event.KernelStackID,
+			v3.EBPFProfilingStackType_PROCESS_KERNEL_SPACE, stackSymbols); d != nil {
+			metadatas = append(metadatas, d)
+		}
+
+		// user stack
+		if d := r.generateProfilingData(r.processProfiling, event.UserStackID,
+			v3.EBPFProfilingStackType_PROCESS_USER_SPACE, stackSymbols); d != nil {
+			metadatas = append(metadatas, d)
+		}
+
+		// close the flush data notify if exists
+		if r.flushDataNotify != nil {
+			r.flushDataNotify <- true
+		}
+
+		if len(metadatas) == 0 {
+			continue
+		}
+
+		result = append(result, &v3.EBPFProfilingData{
+			Profiling: &v3.EBPFProfilingData_OnCPU{
+				OnCPU: &v3.EBPFOnCPUProfiling{
+					Stacks:    metadatas,
+					DumpCount: int32(count),
+				},
+			},
+		})
+	}
+
+	return result, nil
+}
+
+func (r *Runner) generateProfilingData(profilingInfo *profiling.Info, stackID uint32,
+	stackType v3.EBPFProfilingStackType, symbolArray []uint64) *v3.EBPFProfilingStackMetadata {
+	if profilingInfo == nil || stackID <= 0 || stackID == math.MaxUint32 {
+		return nil
+	}
+	if err := r.stackMap.Lookup(stackID, symbolArray); err != nil {
+		if r.stackNotFoundCache[stackID] {
+			return nil
+		}
+		r.stackNotFoundCache[stackID] = true
+		log.Warnf("error to lookup %v stack: %d, error: %v", stackType, stackID, err)
+		return nil
+	}
+	symbols := profilingInfo.FindSymbols(symbolArray, base.MissingSymbol)
+	if len(symbols) == 0 {
+		return nil
+	}
+	return &v3.EBPFProfilingStackMetadata{
+		StackType:    stackType,
+		StackId:      int32(stackID),
+		StackSymbols: symbols,
+	}
+}
+
+func (r *Runner) flushStackCounter() map[Event]int {
+	updateTo := make(map[Event]int)
+	updateToP := &updateTo
+
+	older := &r.stackCounter
+	*older, *updateToP = *updateToP, *older
+	return updateTo
+}
+
+func (r *Runner) closePerfEvent(fd int) error {
+	if fd <= 0 {
+		return nil
+	}
+	var result error
+	if err := unix.IoctlSetInt(fd, unix.PERF_EVENT_IOC_DISABLE, 0); err != nil {
+		result = multierror.Append(result, fmt.Errorf("closing perf event reader: %s", err))
+	}
+	return result
+}
diff --git a/pkg/profiling/task/registion.go b/pkg/profiling/task/registion.go
new file mode 100644
index 0000000..2e9271b
--- /dev/null
+++ b/pkg/profiling/task/registion.go
@@ -0,0 +1,54 @@
+// Licensed to 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. Apache Software Foundation (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 task
+
+import (
+	"fmt"
+
+	"github.com/hashicorp/go-multierror"
+
+	"github.com/apache/skywalking-rover/pkg/profiling/task/base"
+	"github.com/apache/skywalking-rover/pkg/profiling/task/oncpu"
+)
+
+var profilingRunners = make(map[base.TargetType]func(config *base.TaskConfig) (base.ProfileTaskRunner, error))
+
+func init() {
+	profilingRunners[base.TargetTypeOnCPU] = oncpu.NewRunner
+}
+
+func NewProfilingRunner(taskType base.TargetType, taskConfig *base.TaskConfig) (base.ProfileTaskRunner, error) {
+	if profilingRunners[taskType] == nil {
+		return nil, fmt.Errorf("could not found %s runner", taskType)
+	}
+	return profilingRunners[taskType](taskConfig)
+}
+
+func CheckProfilingTaskConfig(taskConfig *base.TaskConfig) error {
+	if taskConfig == nil {
+		return fmt.Errorf("please provide the profiling task config")
+	}
+
+	var err error
+	for _, runner := range profilingRunners {
+		if _, e := runner(taskConfig); e != nil {
+			err = multierror.Append(err, e)
+		}
+	}
+	return err
+}
diff --git a/pkg/boot/register.go b/pkg/tools/host/file.go
similarity index 69%
copy from pkg/boot/register.go
copy to pkg/tools/host/file.go
index 061cb4a..4d26ef1 100644
--- a/pkg/boot/register.go
+++ b/pkg/tools/host/file.go
@@ -15,16 +15,19 @@
 // specific language governing permissions and limitations
 // under the License.
 
-package boot
+package host
 
 import (
-	"github.com/apache/skywalking-rover/pkg/core"
-	"github.com/apache/skywalking-rover/pkg/module"
-	"github.com/apache/skywalking-rover/pkg/process"
+	"os"
+	"strings"
 )
 
-func init() {
-	// register all active module
-	module.Register(core.NewModule())
-	module.Register(process.NewModule())
+var hostMappingPath = os.Getenv("ROVER_HOST_MAPPING")
+
+// GetFileInHost means add the host root mapping prefix, it's dependent when the rover is deploy in a container
+func GetFileInHost(absPath string) string {
+	if hostMappingPath != "" && strings.HasPrefix(absPath, hostMappingPath) {
+		return absPath
+	}
+	return hostMappingPath + absPath
 }
diff --git a/pkg/tools/profiling/api.go b/pkg/tools/profiling/api.go
index 512fe6d..6256e08 100644
--- a/pkg/tools/profiling/api.go
+++ b/pkg/tools/profiling/api.go
@@ -17,11 +17,14 @@
 
 package profiling
 
+import "github.com/ianlancetaylor/demangle"
+
 var KernelSymbolFilePath = "/proc/kallsyms"
 
 // Info of profiling process
 type Info struct {
-	Symbols []*Symbol
+	Symbols           []*Symbol
+	cacheAddrToSymbol map[uint64]string
 }
 
 // Symbol of executable file
@@ -37,12 +40,38 @@ type StatFinder interface {
 	Analyze(filePath string) (*Info, error)
 }
 
+func newInfo(symbols []*Symbol) *Info {
+	return &Info{Symbols: symbols, cacheAddrToSymbol: make(map[uint64]string)}
+}
+
+// FindSymbols from address list, if could not found symbol name then append default symbol to array
+func (i *Info) FindSymbols(addresses []uint64, defaultSymbol string) []string {
+	if len(addresses) == 0 {
+		return nil
+	}
+	result := make([]string, 0)
+	for _, addr := range addresses {
+		if addr <= 0 {
+			continue
+		}
+		s := i.FindSymbolName(addr)
+		if s == "" {
+			s = defaultSymbol
+		}
+		result = append(result, s)
+	}
+	return result
+}
+
 // FindSymbolName by address
 func (i *Info) FindSymbolName(address uint64) string {
+	if d := i.cacheAddrToSymbol[address]; d != "" {
+		return d
+	}
 	symbols := i.Symbols
 
 	start := 0
-	end := len(symbols)
+	end := len(symbols) - 1
 	for start < end {
 		mid := start + (end-start)/2
 		result := int64(address) - int64(symbols[mid].Location)
@@ -52,13 +81,26 @@ func (i *Info) FindSymbolName(address uint64) string {
 		} else if result > 0 {
 			start = mid + 1
 		} else {
-			return symbols[mid].Name
+			s := processSymbolName(symbols[mid].Name)
+			i.cacheAddrToSymbol[address] = s
+			return s
 		}
 	}
 
 	if start >= 1 && symbols[start-1].Location < address && address < symbols[start].Location {
-		return symbols[start-1].Name
+		s := processSymbolName(symbols[start-1].Name)
+		i.cacheAddrToSymbol[address] = s
+		return s
 	}
 
 	return ""
 }
+
+func processSymbolName(name string) string {
+	// fix process demangle symbol name, such as c++ language symbol
+	skip := 0
+	if name[0] == '.' || name[0] == '$' {
+		skip++
+	}
+	return demangle.Filter(name[skip:])
+}
diff --git a/pkg/tools/profiling/go_library.go b/pkg/tools/profiling/go_library.go
index c5cd3d2..17dba26 100644
--- a/pkg/tools/profiling/go_library.go
+++ b/pkg/tools/profiling/go_library.go
@@ -54,5 +54,5 @@ func (l *GoLibrary) Analyze(filePath string) (*Info, error) {
 		data[i] = &Symbol{Name: sym.Name, Location: sym.Value}
 	}
 
-	return &Info{Symbols: data}, nil
+	return newInfo(data), nil
 }
diff --git a/pkg/tools/profiling/kernel.go b/pkg/tools/profiling/kernel.go
index 3285463..ebf7d4e 100644
--- a/pkg/tools/profiling/kernel.go
+++ b/pkg/tools/profiling/kernel.go
@@ -23,6 +23,8 @@ import (
 	"os"
 	"strconv"
 	"strings"
+
+	"github.com/apache/skywalking-rover/pkg/tools/host"
 )
 
 type KernelFinder struct {
@@ -30,7 +32,7 @@ type KernelFinder struct {
 }
 
 func NewKernelFinder() *KernelFinder {
-	stat, _ := os.Stat(KernelSymbolFilePath)
+	stat, _ := os.Stat(host.GetFileInHost(KernelSymbolFilePath))
 	return &KernelFinder{kernelFileExists: stat != nil}
 }
 
@@ -63,5 +65,5 @@ func (k *KernelFinder) Analyze(filepath string) (*Info, error) {
 		})
 	}
 
-	return &Info{Symbols: symbols}, nil
+	return newInfo(symbols), nil
 }
diff --git a/pkg/tools/profiling/objdump.go b/pkg/tools/profiling/objdump.go
index 4783760..a04bfd7 100644
--- a/pkg/tools/profiling/objdump.go
+++ b/pkg/tools/profiling/objdump.go
@@ -62,5 +62,5 @@ func (o *ObjDump) Analyze(filepath string) (*Info, error) {
 		}
 		symbols = append(symbols, &Symbol{Name: submatch[2], Location: atoi})
 	}
-	return &Info{Symbols: symbols}, nil
+	return newInfo(symbols), nil
 }
diff --git a/scripts/build/base.mk b/scripts/build/base.mk
new file mode 100644
index 0000000..ac87445
--- /dev/null
+++ b/scripts/build/base.mk
@@ -0,0 +1,55 @@
+# Licensed to 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. Apache Software Foundation (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.
+#
+
+HUB ?= apache
+VERSION ?= latest
+
+SHELL = /bin/bash
+
+REPODIR := $(shell dirname $(realpath $(firstword $(MAKEFILE_LIST))))/
+
+OSNAME := $(if $(findstring Darwin,$(shell uname)),darwin,linux)
+
+SH = sh
+GO = go
+GIT = git
+GO_PATH = $$($(GO) env GOPATH)
+GO_BUILD = $(GO) build
+GO_GET = $(GO) get
+
+CONTAINER_COMMAND_IMAGE ?= $(HUB)/skywalking-rover-base
+CONTAINER_COMMAND_TAG ?= v$(VERSION)
+CONTAINER_COMMAND_CLANG ?= clang
+CONTAINER_COMMAND_STRIP ?= llvm-strip
+CONTAINER_COMMAND_CFLAGS := -O2 -g -Wall -Werror $(CFLAGS)
+CONTAINER_COMMAND_ENGINE ?= docker
+
+.PHONY: clean
+clean:
+	-rm -rf coverage.txt
+
+build-base-container:
+	${CONTAINER_COMMAND_ENGINE} build -t ${CONTAINER_COMMAND_IMAGE}:${CONTAINER_COMMAND_TAG} . -f docker/Dockerfile.base
+
+container-command: build-base-container
+	${CONTAINER_COMMAND_ENGINE} run --rm \
+		-v "${REPODIR}":/skywalking-rover -w /skywalking-rover --env MAKEFLAGS \
+		--env CFLAGS="-fdebug-prefix-map=/skywalking-rover=." \
+		--env HOME="/skywalking-rover" \
+		"${CONTAINER_COMMAND_IMAGE}:${CONTAINER_COMMAND_TAG}" \
+		make ${COMMAND}
\ No newline at end of file
diff --git a/scripts/build/build.mk b/scripts/build/build.mk
new file mode 100644
index 0000000..edad491
--- /dev/null
+++ b/scripts/build/build.mk
@@ -0,0 +1,38 @@
+# Licensed to 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. Apache Software Foundation (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.
+#
+
+BINARY = skywalking-rover
+
+OUT_DIR = bin
+GO_BUILD_FLAGS = -v
+GO_BUILD_LDFLAGS = -X main.version=$(VERSION)
+
+PLATFORMS := linux
+ARCH = amd64
+os = $(word 1, $@)
+
+deps:
+	$(GO_GET) -v -t -d ./...
+
+.PHONY: $(PLATFORMS)
+$(PLATFORMS): deps
+	mkdir -p $(OUT_DIR)
+	GOOS=$(os) GOARCH=$(ARCH) $(GO_BUILD) $(GO_BUILD_FLAGS) -ldflags "$(GO_BUILD_LDFLAGS)" -o $(OUT_DIR)/$(BINARY)-$(VERSION)-$(os)-$(ARCH) ./cmd
+
+.PHONY: build
+build: linux
\ No newline at end of file
diff --git a/scripts/build/check.mk b/scripts/build/check.mk
new file mode 100644
index 0000000..54a0b32
--- /dev/null
+++ b/scripts/build/check.mk
@@ -0,0 +1,27 @@
+# Licensed to 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. Apache Software Foundation (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.
+#
+
+.PHONY: check
+check: clean
+	$(GO) mod tidy > /dev/null
+	@if [ ! -z "`git status -s`" ]; then \
+		echo "Following files are not consistent with CI:"; \
+		git status -s; \
+		git diff; \
+		exit 1; \
+	fi
\ No newline at end of file
diff --git a/scripts/build/generate.mk b/scripts/build/generate.mk
new file mode 100644
index 0000000..1a46c4a
--- /dev/null
+++ b/scripts/build/generate.mk
@@ -0,0 +1,29 @@
+# Licensed to 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. Apache Software Foundation (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.
+#
+
+.PHONY: generate
+generate: export BPF_CLANG := $(CONTAINER_COMMAND_CLANG)
+generate: export BPF_CFLAGS := $(CONTAINER_COMMAND_CFLAGS)
+generate: export REPO_ROOT := $(REPODIR)
+generate:
+	cd ./ && go generate ./...
+
+# Usually works for generate ebpf ELF file on Mac OS or windows
+.PHONY: container-generate
+container-generate: COMMAND=generate
+container-generate: container-command
\ No newline at end of file
diff --git a/scripts/build/lint.mk b/scripts/build/lint.mk
new file mode 100644
index 0000000..12f2324
--- /dev/null
+++ b/scripts/build/lint.mk
@@ -0,0 +1,30 @@
+# Licensed to 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. Apache Software Foundation (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.
+#
+
+GO_LINT = $(GO_PATH)/bin/golangci-lint
+
+linter:
+	$(GO_LINT) version || curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(GO_PATH)/bin v1.39.0
+
+.PHONY: lint
+lint: linter generate
+	$(GO_LINT) run -v --timeout 5m ./...
+
+.PHONY: container-lint
+container-lint: COMMAND=lint
+container-lint: container-command
\ No newline at end of file
diff --git a/scripts/build/test.mk b/scripts/build/test.mk
new file mode 100644
index 0000000..a4bcbad
--- /dev/null
+++ b/scripts/build/test.mk
@@ -0,0 +1,31 @@
+# Licensed to 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. Apache Software Foundation (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.
+#
+
+GO_TEST = $(GO) test
+GO_TEST_LDFLAGS =
+
+.PHONY: test
+test: clean
+	$(GO_TEST) -ldflags "$(GO_TEST_LDFLAGS)" ./... -coverprofile=coverage.txt -covermode=atomic
+
+.PHONY: test
+test: clean generate
+
+.PHONY: container-test
+container-test: COMMAND=test
+container-test: container-command
\ No newline at end of file