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