Skip to content

Commit

Permalink
Merge branch 'main' into flo-drop-type-index
Browse files Browse the repository at this point in the history
  • Loading branch information
florianl authored Oct 15, 2024
2 parents 6a493db + 13a01a8 commit b026f8e
Show file tree
Hide file tree
Showing 42 changed files with 1,010 additions and 485 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@
.cache
/.idea
/go
opentelemetry-ebpf-profiler
ebpf-profiler
ci-kernels
20 changes: 12 additions & 8 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,20 @@ RUN cross_debian_arch=$(uname -m | sed -e 's/aarch64/amd64/' -e 's/x86_64/arm64
cross_pkg_arch=$(uname -m | sed -e 's/aarch64/x86-64/' -e 's/x86_64/aarch64/'); \
apt-get update -y && \
apt-get dist-upgrade -y && \
apt-get install -y wget make git clang-16 golang unzip \
apt-get install -y wget make git clang-16 unzip libc6-dev g++ gcc pkgconf \
gcc-${cross_pkg_arch}-linux-gnu libc6-${cross_debian_arch}-cross && \
apt-get clean autoclean && \
apt-get autoremove --yes

COPY go.mod /tmp/go.mod
# Extract Go version from go.mod
RUN GO_VERSION=$(grep -oPm1 '^go \K([[:digit:].]+)' /tmp/go.mod) && \
wget -qO- https://golang.org/dl/go${GO_VERSION}.linux-amd64.tar.gz | tar -C /usr/local -xz
# Set Go environment variables
ENV GOPATH="/agent/go"
ENV GOCACHE="/agent/.cache"
ENV PATH="/usr/local/go/bin:$PATH"

RUN wget -qO- https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh \
| sh -s -- -b $(go env GOPATH)/bin v1.56.2

Expand All @@ -32,12 +41,7 @@ RUN
&& find "$INSTALL_DIR/include" -type f -exec chmod +r {} \; \
&& rm "$PB_FILE"

# The docker image is built as root - make binaries available to user.
RUN mv /root/go/bin/* /usr/local/bin/

ENV GOPATH=/agent/go
ENV GOCACHE=/agent/.cache

RUN echo "export PATH=\"\$PATH:\$(go env GOPATH)/bin\"" >> ~/.bashrc
# Append to /etc/profile for login shells
RUN echo 'export PATH="/usr/local/go/bin:$PATH"' >> /etc/profile

ENTRYPOINT ["/bin/bash", "-l", "-c"]
39 changes: 26 additions & 13 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
.PHONY: all all-common binary clean ebpf generate test test-deps protobuf docker-image agent legal \
integration-test-binaries codespell lint linter-version
.PHONY: all all-common clean ebpf generate test test-deps protobuf docker-image agent legal \
integration-test-binaries codespell lint linter-version debug debug-agent ebpf-profiler

SHELL := /usr/bin/env bash

Expand Down Expand Up @@ -39,14 +39,23 @@ VERSION ?= v0.0.0
BUILD_TIMESTAMP ?= $(shell date +%s)
REVISION ?= $(BRANCH)-$(COMMIT_SHORT_SHA)

LDFLAGS := -X github.com//open-telemetry/opentelemetry-ebpf-profiler/vc.version=$(VERSION) \
-X github.com/open-telemetry/opentelemetry-ebpf-profiler/vc.revision=$(REVISION) \
-X github.com/open-telemetry/opentelemetry-ebpf-profiler/vc.buildTimestamp=$(BUILD_TIMESTAMP) \
LDFLAGS := -X go.opentelemetry.io/ebpf-profiler/vc.version=$(VERSION) \
-X go.opentelemetry.io/ebpf-profiler/vc.revision=$(REVISION) \
-X go.opentelemetry.io/ebpf-profiler/vc.buildTimestamp=$(BUILD_TIMESTAMP) \
-extldflags=-static

GO_FLAGS := -buildvcs=false -ldflags="$(LDFLAGS)" -tags osusergo,netgo
GO_TAGS := osusergo,netgo
EBPF_FLAGS :=

all: generate ebpf binary
GO_FLAGS := -buildvcs=false -ldflags="$(LDFLAGS)"

MAKEFLAGS += -j$(shell nproc)

all: ebpf-profiler

debug: GO_TAGS := $(GO_TAGS),debugtracer
debug: EBPF_FLAGS += debug
debug: all

# Removes the go build cache and binaries in the current project
clean:
Expand All @@ -59,11 +68,11 @@ clean:
generate:
go generate ./...

binary:
go build $(GO_FLAGS)

ebpf:
$(MAKE) -j$(shell nproc) -C support/ebpf
$(MAKE) $(EBPF_FLAGS) -C support/ebpf

ebpf-profiler: generate ebpf
go build $(GO_FLAGS) -tags $(GO_TAGS)

GOLANGCI_LINT_VERSION = "v1.60.1"
lint: generate vanity-import-check
Expand All @@ -84,7 +93,7 @@ vanity-import-fix: $(PORTO)
@porto --include-internal -w .

test: generate ebpf test-deps
go test $(GO_FLAGS) ./...
go test $(GO_FLAGS) -tags $(GO_TAGS) ./...

TESTDATA_DIRS:= \
nativeunwind/elfunwindinfo/testdata \
Expand All @@ -101,7 +110,7 @@ TEST_INTEGRATION_BINARY_DIRS := tracer processmanager/ebpf support
integration-test-binaries: generate ebpf
$(foreach test_name, $(TEST_INTEGRATION_BINARY_DIRS), \
(go test -ldflags='-extldflags=-static' -trimpath -c \
-tags osusergo,netgo,static_build,integration \
-tags $(GO_TAGS),static_build,integration \
-o ./support/$(subst /,_,$(test_name)).test \
./$(test_name)) || exit ; \
)
Expand All @@ -113,6 +122,10 @@ agent:
docker run -v "$$PWD":/agent -it --rm --user $(shell id -u):$(shell id -g) profiling-agent \
"make TARGET_ARCH=$(TARGET_ARCH) VERSION=$(VERSION) REVISION=$(REVISION) BUILD_TIMESTAMP=$(BUILD_TIMESTAMP)"

debug-agent:
docker run -v "$$PWD":/agent -it --rm --user $(shell id -u):$(shell id -g) profiling-agent \
"make TARGET_ARCH=$(TARGET_ARCH) VERSION=$(VERSION) REVISION=$(REVISION) BUILD_TIMESTAMP=$(BUILD_TIMESTAMP) debug"

legal:
@go install github.com/google/go-licenses@latest
@go-licenses save --force . --save_path=LICENSES
Expand Down
62 changes: 33 additions & 29 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,44 +31,48 @@ eBPF.
optimizations and offer a higher precision of function call chains.

## Building

> [!NOTE]
>
> If you simply wish to take the agent for a spin with minimal effort, you can
> also immediately jump to the ["Visualizing data locally"
> section](#visualizing-data-locally), launch devfiler and follow the download
> links for agent binaries within its "Add data" dialogue.
The agent can be built without affecting your environment by using the provided
`make` targets. You need to have `docker` installed, though.
Builds on amd64 and arm64 architectures are supported.

The first step is to build the Docker image that contains the build environment:
## Quick Start
If you'd like to quickly test the agent, you can skip to the ["Visualizing data locally"](https://github.com/open-telemetry/opentelemetry-ebpf-profiler?tab=readme-ov-file#visualizing-data-locally) section and launch devfiler. From there, follow the download links for prebuilt agent binaries.

## Platform Requirements
The agent can be built with the provided make targets. Docker is required for containerized builds, and both amd64 and arm64 architectures are supported.

For **Linux**, the following steps apply:
1. Build the Docker image containing the build environment:
```sh
make docker-image
```
2. Build the agent for your current machine's architecture:
```sh
make agent
```
Or `make debug-agent` for debug build.
3. To cross-complie for a different architecture (e.g. arm64):
```sh
make agent TARGET_ARCH=arm64
```
The resulting binary will be named <ebpf-profiler> in the current directory.
## Other OSes
Since the profiler is Linux-only, macOS and Windows users need to set up a Linux VM to build and run the agent. Ensure the appropriate architecture is specified if using cross-compilation. Use the same make targets as above after the Linux environment is configured in the VM.
## Alternative Build (Without Docker)
You can build the agent without Docker by directly installing the dependencies listed in the Dockerfile. Once dependencies are set up, simply run:
```sh
make docker-image
make
```

Then, you can build the agent:
or
```sh
# Build for architecture of current machine:
make agent

# OR, cross-compile an agent for another architecture:
make agent TARGET_ARCH=arm64 # accepted: amd64, arm64
make debug
```

The resulting binary will be in the current directory as `opentelemetry-ebpf-profiler`.

Alternatively, you can build without Docker. Please see the `Dockerfile` for required dependencies.

After installing the dependencies, just run `make` to build.
This will build the profiler natively on your machine.
## Running
You can start the agent with the following command:
```sh
sudo ./opentelemetry-ebpf-profiler -collection-agent=127.0.0.1:11000 -disable-tls
sudo ./ebpf-profiler -collection-agent=127.0.0.1:11000 -disable-tls
```
The agent comes with a functional but work-in-progress / evolving implementation
Expand Down Expand Up @@ -512,7 +516,7 @@ probabilistic profiling is either enabled or disabled. The default value is 1 mi
The following example shows how to configure the profiling agent with a threshold of 50 and an interval of 2 minutes and 30 seconds:
```bash
sudo ./opentelemetry-ebpf-profiler -probabilistic-threshold=50 -probabilistic-interval=2m30s
sudo ./ebpf-profiler -probabilistic-threshold=50 -probabilistic-interval=2m30s
```

# Legal
Expand Down
2 changes: 1 addition & 1 deletion cli_flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ type arguments struct {
func parseArgs() (*arguments, error) {
var args arguments

fs := flag.NewFlagSet("opentelemetry-ebpf-profiler", flag.ExitOnError)
fs := flag.NewFlagSet("ebpf-profiler", flag.ExitOnError)

// Please keep the parameters ordered alphabetically in the source-code.
fs.UintVar(&args.bpfVerifierLogLevel, "bpf-log-level", 0, bpfVerifierLogLevelHelp)
Expand Down
6 changes: 0 additions & 6 deletions interpreter/dotnet/data.go
Original file line number Diff line number Diff line change
Expand Up @@ -144,11 +144,6 @@ func (d *dotnetData) Attach(ebpf interpreter.EbpfHandler, pid libpf.PID, bias li
return nil, err
}

symbolizedLRU, err := freelru.New[symbolizedKey, libpf.Void](1024, symbolizedKey.Hash32)
if err != nil {
return nil, err
}

procInfo := C.DotnetProcInfo{
version: C.uint(d.version),
}
Expand All @@ -163,7 +158,6 @@ func (d *dotnetData) Attach(ebpf interpreter.EbpfHandler, pid libpf.PID, bias li
ranges: make(map[libpf.Address]dotnetRangeSection),
moduleToPEInfo: make(map[libpf.Address]*peInfo),
addrToMethod: addrToMethod,
symbolizedLRU: symbolizedLRU,
}, nil
}

Expand Down
81 changes: 40 additions & 41 deletions interpreter/dotnet/instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,15 +121,6 @@ type dotnetRangeSection struct {
prefixes []lpm.Prefix
}

type symbolizedKey struct {
fileID libpf.FileID
lineID libpf.AddressOrLineno
}

func (key symbolizedKey) Hash32() uint32 {
return key.fileID.Hash32() + uint32(key.lineID)
}

type dotnetInstance struct {
interpreter.InstanceStubs

Expand Down Expand Up @@ -167,23 +158,27 @@ type dotnetInstance struct {
moduleToPEInfo map[libpf.Address]*peInfo

addrToMethod *freelru.LRU[libpf.Address, *dotnetMethod]

symbolizedLRU *freelru.LRU[symbolizedKey, libpf.Void]
}

// calculateAndSymbolizeStubID calculates a stub LineID, and symbolizes it if needed
func (i *dotnetInstance) calculateAndSymbolizeStubID(symbolReporter reporter.SymbolReporter,
codeType uint) {
if i.codeTypeMethodIDs[codeType] != 0 {
return
}
func (i *dotnetInstance) insertAndSymbolizeStubFrame(symbolReporter reporter.SymbolReporter,
trace *libpf.Trace, codeType uint) {
name := "[stub: " + codeName[codeType] + "]"
h := fnv.New128a()
_, _ = h.Write([]byte(name))
nameHash := h.Sum(nil)
lineID := libpf.AddressOrLineno(npsr.Uint64(nameHash, 0))
i.codeTypeMethodIDs[codeType] = lineID
symbolReporter.FrameMetadata(stubsFileID, lineID, 0, 0, name, "")
lineID := i.codeTypeMethodIDs[codeType]
if lineID == 0 {
h := fnv.New128a()
_, _ = h.Write([]byte(name))
nameHash := h.Sum(nil)
lineID = libpf.AddressOrLineno(npsr.Uint64(nameHash, 0))
i.codeTypeMethodIDs[codeType] = lineID
}

frameID := libpf.NewFrameID(stubsFileID, lineID)
trace.AppendFrameID(libpf.DotnetFrame, frameID)
symbolReporter.FrameMetadata(&reporter.FrameMetadataArgs{
FrameID: frameID,
FunctionName: name,
})
}

// addRange inserts a known memory mapping along with the needed data of it to ebpf maps
Expand Down Expand Up @@ -764,19 +759,21 @@ func (i *dotnetInstance) Symbolize(symbolReporter reporter.SymbolReporter,
if err != nil {
return err
}
fileID := module.fileID
// The Line ID is the Relative Virtual Address (RVA) within the PE file where
// PC is executing:
// - on non-leaf frames, it is the return address
// - on leaf frames, it is the address after the CALL machine opcode
lineID := libpf.AddressOrLineno(pcOffset)

if _, ok := i.symbolizedLRU.Get(symbolizedKey{fileID, lineID}); !ok {
frameID := libpf.NewFrameID(module.fileID, lineID)
trace.AppendFrameID(libpf.DotnetFrame, frameID)
if !symbolReporter.FrameKnown(frameID) {
methodName := module.resolveR2RMethodName(pcOffset)
symbolReporter.FrameMetadata(fileID, lineID, 0, 0,
methodName, module.simpleName)
i.symbolizedLRU.Add(symbolizedKey{fileID, lineID}, libpf.Void{})
symbolReporter.FrameMetadata(&reporter.FrameMetadataArgs{
FrameID: frameID,
FunctionName: methodName,
SourceFile: module.simpleName,
})
}
// The Line ID is the Relative Virtual Address (RVA) within into the PE file
// where PC is executing. On non-leaf frames it points to the return address.
// The instructionafter the CALL machine opcode.
trace.AppendFrame(libpf.DotnetFrame, fileID, lineID)
case codeJIT:
// JITted frame in anonymous mapping
method, err := i.getMethod(codeHeaderPtr)
Expand All @@ -795,21 +792,23 @@ func (i *dotnetInstance) Symbolize(symbolReporter reporter.SymbolReporter,
libpf.AddressOrLineno(ilOffset)

if method.index == 0 || method.classification == mcDynamic {
i.calculateAndSymbolizeStubID(symbolReporter, codeDynamic)
trace.AppendFrame(libpf.DotnetFrame, stubsFileID, i.codeTypeMethodIDs[codeDynamic])
i.insertAndSymbolizeStubFrame(symbolReporter, trace, codeDynamic)
} else {
if _, ok := i.symbolizedLRU.Get(symbolizedKey{fileID, lineID}); !ok {
frameID := libpf.NewFrameID(fileID, lineID)
trace.AppendFrameID(libpf.DotnetFrame, frameID)
if !symbolReporter.FrameKnown(frameID) {
methodName := method.module.resolveMethodName(method.index)
symbolReporter.FrameMetadata(fileID, lineID, 0, ilOffset,
methodName, method.module.simpleName)
i.symbolizedLRU.Add(symbolizedKey{fileID, lineID}, libpf.Void{})
symbolReporter.FrameMetadata(&reporter.FrameMetadataArgs{
FrameID: frameID,
SourceFile: method.module.simpleName,
FunctionName: methodName,
FunctionOffset: ilOffset,
})
}
trace.AppendFrame(libpf.DotnetFrame, fileID, lineID)
}
default:
// Stub code
i.calculateAndSymbolizeStubID(symbolReporter, frameType)
trace.AppendFrame(libpf.DotnetFrame, stubsFileID, i.codeTypeMethodIDs[frameType])
i.insertAndSymbolizeStubFrame(symbolReporter, trace, frameType)
}

sfCounter.ReportSuccess()
Expand Down
Loading

0 comments on commit b026f8e

Please sign in to comment.