├── .gitignore ├── AUTHORS ├── pkg ├── cpu │ ├── profile_test.go │ ├── entry.go │ ├── generate_bpf.go │ ├── c │ │ └── bpf.c │ ├── bpf.go │ └── profile.go ├── bpferrors │ └── module.go ├── heap │ ├── entry.go │ ├── c │ │ ├── kprobes.c │ │ └── bpf.c │ ├── generate_bpf.go │ ├── profile.go │ └── bpf.go ├── srcfmt │ ├── input.go │ └── input_test.go ├── childprocess │ └── exec.go ├── maps │ ├── maps_test.go │ └── maps.go └── output │ ├── out.go │ └── parse.go ├── README.md ├── main.go ├── test_prog ├── heap │ └── main.c └── cpu │ └── main.c ├── Gopkg.toml ├── cmd ├── root.go ├── heapprofile.go └── cpuprofile.go ├── Vagrantfile ├── Dockerfile ├── LICENSE ├── Gopkg.lock └── Makefile /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Christian Grabowski 2 | -------------------------------------------------------------------------------- /pkg/cpu/profile_test.go: -------------------------------------------------------------------------------- 1 | package cpu 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestRun(t *testing.T) { 8 | Run() 9 | } 10 | -------------------------------------------------------------------------------- /pkg/bpferrors/module.go: -------------------------------------------------------------------------------- 1 | package bpferrors 2 | 3 | import ( 4 | "errors" 5 | ) 6 | 7 | var ( 8 | ErrBadModuleBuild = errors.New("unable to compile bpf src") 9 | ErrNoTableNameFound = errors.New("no table name found") 10 | ) 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pprof-ebpf 2 | 3 | A profiler using ebpf that produces pprof format profiles 4 | 5 | ## Requirements 6 | 7 | - Linux Kernel >= 4.9 8 | 9 | - llvm >= 3.8 10 | 11 | - bcc 12 | 13 | - Golang >= 1.6 14 | 15 | ## Install 16 | 17 | TODO 18 | 19 | ## Usage 20 | 21 | TODO 22 | 23 | -------------------------------------------------------------------------------- /pkg/heap/entry.go: -------------------------------------------------------------------------------- 1 | package heap 2 | 3 | import ( 4 | "github.com/cpg1111/pprof-ebpf/pkg/bpferrors" 5 | ) 6 | 7 | func Format(header map[string]interface{}) (string, error) { 8 | for k, v := range header { 9 | if k == "name" { 10 | return v.(string), nil 11 | } 12 | } 13 | return "", bpferrors.ErrNoTableNameFound 14 | } 15 | -------------------------------------------------------------------------------- /pkg/srcfmt/input.go: -------------------------------------------------------------------------------- 1 | package srcfmt 2 | 3 | import ( 4 | "bytes" 5 | "text/template" 6 | ) 7 | 8 | func ProcessSrc(src string, data interface{}) (buf *bytes.Buffer, err error) { 9 | buf = bytes.NewBuffer(nil) 10 | tmpl := template.New("script") 11 | tmpl, err = tmpl.Parse(src) 12 | if err != nil { 13 | return nil, err 14 | } 15 | err = tmpl.Execute(buf, data) 16 | if err != nil { 17 | return nil, err 18 | } 19 | return 20 | } 21 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "os/exec" 7 | 8 | "github.com/cpg1111/pprof-ebpf/cmd" 9 | ) 10 | 11 | func main() { 12 | if os.Getuid() != 0 { 13 | cmd := exec.Command("sudo", os.Args...) 14 | cmd.Stdout = os.Stdout 15 | cmd.Stderr = os.Stderr 16 | err := cmd.Run() 17 | if err != nil { 18 | os.Exit(1) 19 | } 20 | os.Exit(0) 21 | } 22 | fmt.Println("running pprof-ebpf") 23 | cmd.Execute() 24 | } 25 | -------------------------------------------------------------------------------- /pkg/childprocess/exec.go: -------------------------------------------------------------------------------- 1 | package childprocess 2 | 3 | import ( 4 | "os/exec" 5 | "strings" 6 | ) 7 | 8 | // Exec executes a commmand as a child process and returns either 9 | // the child process' pid or an error 10 | func Exec(cmd string) (int, error) { 11 | args := strings.Split(cmd, " ") 12 | child := exec.Command(args[0], args...) 13 | err := child.Start() 14 | if err != nil { 15 | return -1, err 16 | } 17 | return child.Process.Pid, nil 18 | } 19 | -------------------------------------------------------------------------------- /pkg/srcfmt/input_test.go: -------------------------------------------------------------------------------- 1 | package srcfmt 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | type testData struct { 8 | Msg string 9 | } 10 | 11 | func TestProcessSrc(t *testing.T) { 12 | input := "{{.Msg}}" 13 | output := "Hello, World!" 14 | buf, err := ProcessSrc(input, testData{Msg: "Hello, World!"}) 15 | if err != nil { 16 | t.Error(err) 17 | } 18 | res := buf.String() 19 | if res != output { 20 | t.Errorf("expected %s, found %s", output, res) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /pkg/cpu/entry.go: -------------------------------------------------------------------------------- 1 | package cpu 2 | 3 | import ( 4 | "github.com/cpg1111/pprof-ebpf/pkg/bpferrors" 5 | ) 6 | 7 | type HeaderEntry struct { 8 | Name string 9 | FD int64 10 | KeySize int64 11 | LeafSize int64 12 | KeyDesc interface{} 13 | LeafDesc interface{} 14 | } 15 | 16 | func Format(header map[string]interface{}) (string, error) { 17 | for k, v := range header { 18 | if k == "name" { 19 | return v.(string), nil 20 | } 21 | } 22 | return "", bpferrors.ErrNoTableNameFound 23 | } 24 | -------------------------------------------------------------------------------- /test_prog/heap/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | void sighandler(int i) { 7 | if (i == SIGINT) 8 | exit(0); 9 | } 10 | 11 | int main() { 12 | int *j[1024]; 13 | for (int i = 0; i < 1024; i++) { 14 | if (i == 0) 15 | sleep(30); 16 | j[i] = (int*)malloc(sizeof(int)); 17 | printf("res %d\n", *j[i]); 18 | } 19 | //free(j); 20 | signal(SIGINT, sighandler); 21 | for (;;) {} 22 | return 0; 23 | } 24 | -------------------------------------------------------------------------------- /test_prog/cpu/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | pthread_t threads[4]; 8 | 9 | void sighandler(int i) { 10 | if (i == SIGINT) { 11 | for (int i = 0; i < 4; i++) { 12 | pthread_join(threads[i], NULL); 13 | } 14 | exit(0); 15 | } 16 | } 17 | 18 | void* thread_func(void *arg) { 19 | for (int i = 0; i < 60; i++) { 20 | printf("%d\n", (int)(arg)); 21 | sleep(1); 22 | } 23 | } 24 | 25 | int main() { 26 | signal(SIGINT, sighandler); 27 | for (int i = 0; i < 4; i++) { 28 | pthread_create(&threads[i], NULL, thread_func, (void *)i); 29 | } 30 | for (;;) {} 31 | return 0; 32 | } 33 | -------------------------------------------------------------------------------- /Gopkg.toml: -------------------------------------------------------------------------------- 1 | # Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md 2 | # for detailed Gopkg.toml documentation. 3 | 4 | required = ["github.com/iovisor/gobpf/bcc"] 5 | ignored = [] 6 | 7 | [[constraint]] 8 | branch = "master" 9 | name = "github.com/iovisor/gobpf" 10 | 11 | [[constraint]] 12 | name = "github.com/spf13/cobra" 13 | revision = "7b2c5ac9fc04fc5efafb60700713d4fa609b777b" 14 | 15 | [[constraint]] 16 | name = "github.com/sirupsen/logrus" 17 | version = "1.0.4" 18 | 19 | [[constraint]] 20 | name = "github.com/golang/protobuf" 21 | revision = "1e59b77b52bf8e4b449a57e6f79f21226d571845" 22 | 23 | [[constraint]] 24 | name = "github.com/google/pprof" 25 | branch = "compiled_protobuf" 26 | source = "github.com/cpg1111/pprof" 27 | -------------------------------------------------------------------------------- /cmd/root.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "github.com/spf13/cobra" 5 | ) 6 | 7 | func Execute() { 8 | rootCMD := &cobra.Command{ 9 | Use: "pprof-ebpf [OPTION] [SUBCOMMAND] [SUBOPTIONS]", 10 | Short: "A profiler using ebpf that produces pprof format profiles", 11 | Long: `A profiler that uses ebpf for user and kernel space tracing 12 | and outputs pprof (https://github.com/google/pprof) format 13 | profile so that one can generate the same visualizations 14 | one could generate with tools such as go's runtime/pprof. 15 | 16 | View full documentation at: https://github.com/cpg1111/pprof-ebpf`, 17 | Run: func(cmd *cobra.Command, args []string) { 18 | 19 | }, 20 | } 21 | rootCMD.AddCommand(cpuprofileCMD) 22 | rootCMD.AddCommand(heapprofileCMD) 23 | rootCMD.Execute() 24 | } 25 | -------------------------------------------------------------------------------- /Vagrantfile: -------------------------------------------------------------------------------- 1 | # -*- mode: ruby -*- 2 | # vi: set ft=ruby : 3 | 4 | Vagrant.configure("2") do |config| 5 | config.vm.box = "bento/ubuntu-17.10" 6 | config.vm.provision "shell", inline: <<-SHELL 7 | sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys D4284CDD 8 | echo "deb https://repo.iovisor.org/apt/artful artful main" | sudo tee /etc/apt/sources.list.d/iovisor.list 9 | sudo apt-get update 10 | sudo apt-get install -y bcc-tools libbcc-examples linux-headers-$(uname -r) golang git \ 11 | build-essential cmake llvm-3.8 libclang-3.8-dev bison python zlib1g-dev libelf-dev flex \ 12 | libedit-dev 13 | git clone https://github.com/iovisor/bcc.git 14 | mkdir bcc/build; cd bcc/build 15 | git checkout v0.5.0 16 | cmake .. -DCMAKE_INSTALL_PREFIX=/usr 17 | make 18 | sudo make install 19 | mkdir -p /home/vagrant/go/src/github.com/cpg1111/ /home/vagrant/go/pkg/ /home/vagrant/go/bin/ 20 | sudo ln -s /vagrant /home/vagrant/go/src/github.com/cpg1111/pprof-ebpf 21 | echo "export GOPATH=/home/vagrant/go/" >> /home/vagrant/.bashrc 22 | SHELL 23 | end 24 | -------------------------------------------------------------------------------- /pkg/cpu/generate_bpf.go: -------------------------------------------------------------------------------- 1 | //+build ignore 2 | 3 | package main 4 | 5 | import ( 6 | "fmt" 7 | "io" 8 | "io/ioutil" 9 | "os" 10 | 11 | "github.com/cpg1111/pprof-ebpf/pkg/srcfmt" 12 | ) 13 | 14 | const ( 15 | org = "cpg1111" 16 | repo = "pprof-ebpf" 17 | tmpl = "//AUTOGENERATED by generate_bpf.go\npackage cpu\n\nconst bpfSRC = `\n{{ .SRC }}`\n" 18 | ) 19 | 20 | type code struct { 21 | SRC string 22 | } 23 | 24 | func main() { 25 | gopath := os.Getenv("GOPATH") 26 | cSRCPath := fmt.Sprintf("%s/src/github.com/%s/%s/pkg/cpu/c/profile.c", gopath, org, repo) 27 | goDestPath := fmt.Sprintf("%s/src/github.com/%s/%s/pkg/cpu/bpf.go", gopath, org, repo) 28 | f, err := os.Open(cSRCPath) 29 | if err != nil { 30 | panic(err) 31 | } 32 | defer f.Close() 33 | b, err := ioutil.ReadAll(f) 34 | if err != nil { 35 | panic(err) 36 | } 37 | c := &code{SRC: string(b)} 38 | buf, err := srcfmt.ProcessSrc(tmpl, c) 39 | if err != nil { 40 | panic(err) 41 | } 42 | dest, err := os.OpenFile(goDestPath, os.O_RDWR|os.O_CREATE, 0644) 43 | if err != nil { 44 | panic(err) 45 | } 46 | defer dest.Close() 47 | _, err = io.Copy(dest, buf) 48 | if err != nil { 49 | panic(err) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:17.10 2 | RUN apt-get update && apt install -y apt-transport-https ca-certificates software-properties-common && \ 3 | apt-key adv --keyserver keyserver.ubuntu.com --recv-keys D4284CDD && \ 4 | echo "deb https://repo.iovisor.org/apt/artful artful main" | tee /etc/apt/sources.list.d/iovisor.list && \ 5 | apt-get update &&\ 6 | apt-get install -y bcc-tools libbcc-examples linux-headers-generic golang git build-essential cmake llvm-3.8 libclang-3.8-dev \ 7 | bison python zlib1g-dev libelf-dev flex libedit-dev automake libtool && \ 8 | git clone https://github.com/iovisor/bcc.git && \ 9 | mkdir bcc/build/ 10 | WORKDIR /bcc/build/ 11 | RUN git checkout v0.5.0 && \ 12 | cmake .. -DCMAKE_INSTALL_PREFIX=/usr && \ 13 | make && make install && \ 14 | mkdir -p /go/src/github.com/cpg1111 /go/pkg/ /go/bin/ 15 | WORKDIR / 16 | RUN git clone https://github.com/google/protobuf.git 17 | WORKDIR /protobuf/ 18 | RUN ./autogen.sh && \ 19 | ./configure && make && make install 20 | ENV GOPATH /go/ 21 | ENV PATH $PATH:/go/bin/ 22 | RUN go get github.com/golang/dep/cmd/... 23 | COPY . /go/src/github.com/cpg1111/pprof-ebpf/ 24 | WORKDIR /go/src/github.com/cpg1111/pprof-ebpf/ 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 The pprof-ebpf authors. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are met: 5 | 6 | Redistributions of source code must retain the above copyright notice, this 7 | list of conditions and the following disclaimer. 8 | 9 | Redistributions in binary form must reproduce the above copyright notice, 10 | this list of conditions and the following disclaimer in the documentation 11 | and/or other materials provided with the distribution. 12 | 13 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 14 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 15 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 16 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 17 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 18 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 19 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 20 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 21 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 22 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | 24 | -------------------------------------------------------------------------------- /pkg/heap/c/kprobes.c: -------------------------------------------------------------------------------- 1 | TRACEPOINT_PROBE(kmem, kmalloc) { 2 | gen_alloc_enter((struct pt_regs *)(args), args->bytes_alloc); 3 | return gen_alloc_exit2((struct pt_regs *)(args), (size_t)(args->ptr)); 4 | } 5 | 6 | TRACEPOINT_PROBE(kmem, kmalloc_node) { 7 | gen_alloc_enter((struct pt_regs *)(args), args->bytes_alloc); 8 | return gen_alloc_exit2((struct pt_regs *)(args), (size_t)(args->ptr)); 9 | } 10 | 11 | TRACEPOINT_PROBE(kmem, kfree) { 12 | return gen_free_enter((struct pt_regs *)(args), (void *)(args->ptr)); 13 | } 14 | 15 | TRACEPOINT_PROBE(kmem, kmem_cache_alloc) { 16 | gen_alloc_enter((struct pt_regs *)(args), args->bytes_alloc); 17 | return gen_alloc_exit2((struct pt_regs *)(args), (size_t)(args->ptr)); 18 | } 19 | 20 | TRACEPOINT_PROBE(kmem, kmem_cache_alloc_node) { 21 | gen_alloc_enter((struct pt_regs *)(args), args->bytes_alloc); 22 | return gen_alloc_exit2((struct pt_regs *)(args), (size_t)(args->ptr)); 23 | } 24 | 25 | TRACEPOINT_PROBE(kmem, kmem_cache_free) { 26 | return gen_free_enter((struct pt_regs *)(args), (void *)(args->ptr)); 27 | } 28 | 29 | TRACEPOINT_PROBE(kmem, mm_page_alloc) { 30 | gen_alloc_enter((struct pt_regs *)(args), {{ .PageSize }} << args->order); 31 | return gen_alloc_exit2((struct pt_regs *)(args), args->pfn); 32 | } 33 | 34 | TRACEPOINT_PROBE(kmem, mm_page_free) { 35 | return gen_free_enter((struct pt_regs *)(args), (void *)(args->pfn)); 36 | } 37 | -------------------------------------------------------------------------------- /pkg/heap/generate_bpf.go: -------------------------------------------------------------------------------- 1 | //+build ignore 2 | 3 | package main 4 | 5 | import ( 6 | "fmt" 7 | "io" 8 | "io/ioutil" 9 | "os" 10 | 11 | "github.com/cpg1111/pprof-ebpf/pkg/srcfmt" 12 | ) 13 | 14 | const ( 15 | org = "cpg1111" 16 | repo = "pprof-ebpf" 17 | tmpl = "//AUTOGENERATED by generate_bpf.go\npackage heap\n\nconst bpfSRC = `\n{{ .SRC }}`\n\nconst kSRC = `\n{{ .KSRC }}`\n" 18 | ) 19 | 20 | type code struct { 21 | SRC string 22 | KSRC string 23 | } 24 | 25 | func main() { 26 | gopath := os.Getenv("GOPATH") 27 | cSRCPath := fmt.Sprintf("%s/src/github.com/%s/%s/pkg/heap/c/bpf.c", gopath, org, repo) 28 | kSRCPath := fmt.Sprintf("%s/src/github.com/%s/%s/pkg/heap/c/kprobes.c", gopath, org, repo) 29 | goDestPath := fmt.Sprintf("%s/src/github.com/%s/%s/pkg/heap/bpf.go", gopath, org, repo) 30 | f, err := os.Open(cSRCPath) 31 | if err != nil { 32 | panic(err) 33 | } 34 | defer f.Close() 35 | b, err := ioutil.ReadAll(f) 36 | if err != nil { 37 | panic(err) 38 | } 39 | kF, err := os.Open(kSRCPath) 40 | if err != nil { 41 | panic(err) 42 | } 43 | kB, err := ioutil.ReadAll(kF) 44 | if err != nil { 45 | panic(err) 46 | } 47 | c := &code{SRC: string(b), KSRC: string(kB)} 48 | buf, err := srcfmt.ProcessSrc(tmpl, c) 49 | if err != nil { 50 | panic(err) 51 | } 52 | dest, err := os.OpenFile(goDestPath, os.O_RDWR|os.O_CREATE, 0644) 53 | if err != nil { 54 | panic(err) 55 | } 56 | defer dest.Close() 57 | _, err = io.Copy(dest, buf) 58 | if err != nil { 59 | panic(err) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /pkg/maps/maps_test.go: -------------------------------------------------------------------------------- 1 | package maps 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | ) 7 | 8 | const testMap = ` 9 | 7ff84af3e000-7ff84b114000 r-xp 00000000 fd:00 1835497 /lib/x86_64-linux-gnu/libc-2.26.so 10 | 7ff84b114000-7ff84b314000 ---p 001d6000 fd:00 1835497 /lib/x86_64-linux-gnu/libc-2.26.so 11 | 7ff84b314000-7ff84b318000 r--p 001d6000 fd:00 1835497 /lib/x86_64-linux-gnu/libc-2.26.so 12 | 7ff84b318000-7ff84b31a000 rw-p 001da000 fd:00 1835497 /lib/x86_64-linux-gnu/libc-2.26.so 13 | ` 14 | 15 | func TestParse(t *testing.T) { 16 | buf := bytes.NewBufferString(testMap) 17 | maps, err := Parse(buf) 18 | if err != nil { 19 | t.Error(err) 20 | } 21 | for i, m := range maps { 22 | switch i { 23 | case 0: 24 | if m.Start != uint64(0x7ff84af3e000) { 25 | t.Errorf("expected 7ff84af3e000 found %d", m.Start) 26 | } 27 | if m.Stop != uint64(0x7ff84b114000) { 28 | t.Errorf("expected 7ff84b114000 found %d", m.Stop) 29 | } 30 | if m.Offset != uint64(0) { 31 | t.Errorf("expected 0 found %d", m.Offset) 32 | } 33 | if m.Flags != "r-xp" { 34 | t.Errorf("expected r-xp found %s", m.Flags) 35 | } 36 | if m.Dev != "fd:00" { 37 | t.Errorf("expected fd:00 found %s", m.Dev) 38 | } 39 | if m.INode != 1835497 { 40 | t.Errorf("expected 1835497 found %d", m.INode) 41 | } 42 | if m.PathName != "/lib/x86_64-linux-gnu/libc-2.26.so" { 43 | t.Errorf("expected /lib/x86_64-linux-gnu/libc-2.26.so found %s", m.PathName) 44 | } 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /pkg/output/out.go: -------------------------------------------------------------------------------- 1 | package output 2 | 3 | import ( 4 | "compress/gzip" 5 | "io" 6 | "os" 7 | "time" 8 | 9 | "github.com/golang/protobuf/proto" 10 | pb "github.com/google/pprof/proto" 11 | ) 12 | 13 | type Writer struct { 14 | Out *pb.Profile 15 | destName string 16 | } 17 | 18 | func NewWriter(dstName string, sampleTypes ...*pb.ValueType) *Writer { 19 | return &Writer{ 20 | destName: dstName, 21 | Out: &pb.Profile{ 22 | SampleType: sampleTypes, 23 | }, 24 | } 25 | } 26 | 27 | func (w *Writer) AddSample(sample *pb.Sample) { 28 | w.Out.Sample = append(w.Out.Sample, sample) 29 | } 30 | 31 | func (w *Writer) AddMapping(mapping *pb.Mapping) { 32 | w.Out.Mapping = append(w.Out.Mapping, mapping) 33 | } 34 | 35 | func (w *Writer) AddLocation(location *pb.Location) { 36 | w.Out.Location = append(w.Out.Location, location) 37 | } 38 | 39 | func (w *Writer) AddFunction(function *pb.Function) { 40 | w.Out.Function = append(w.Out.Function) 41 | } 42 | 43 | func (w *Writer) SetTime(t time.Time) { 44 | w.Out.TimeNanos = t.UnixNano() 45 | } 46 | 47 | func (w *Writer) SetDuration(d time.Duration) { 48 | w.Out.DurationNanos = d.Nanoseconds() 49 | } 50 | 51 | func (w *Writer) SetNumEvents(num int64) { 52 | w.Out.Period = num 53 | } 54 | 55 | func (w *Writer) WriteTo(dest io.Writer) (n int, err error) { 56 | gzipWriter := gzip.NewWriter(dest) 57 | payload, err := proto.Marshal(w.Out) 58 | if err != nil { 59 | return 0, err 60 | } 61 | return gzipWriter.Write(payload) 62 | } 63 | 64 | func (w *Writer) Output() (err error) { 65 | var writer io.Writer 66 | if len(w.destName) == 0 { 67 | writer = os.Stdout 68 | } else { 69 | writer, err = os.Open(w.destName) 70 | if err != nil { 71 | return err 72 | } 73 | } 74 | _, err = w.WriteTo(writer) 75 | if err != nil { 76 | return err 77 | } 78 | return nil 79 | } 80 | -------------------------------------------------------------------------------- /Gopkg.lock: -------------------------------------------------------------------------------- 1 | # This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. 2 | 3 | 4 | [[projects]] 5 | name = "github.com/golang/protobuf" 6 | packages = ["proto"] 7 | revision = "1e59b77b52bf8e4b449a57e6f79f21226d571845" 8 | 9 | [[projects]] 10 | branch = "compiled_protobuf" 11 | name = "github.com/google/pprof" 12 | packages = ["proto"] 13 | revision = "c9a276940648ffae3f38dea6828578d7c7efc69a" 14 | source = "github.com/cpg1111/pprof" 15 | 16 | [[projects]] 17 | name = "github.com/inconshreveable/mousetrap" 18 | packages = ["."] 19 | revision = "76626ae9c91c4f2a10f34cad8ce83ea42c93bb75" 20 | version = "v1.0" 21 | 22 | [[projects]] 23 | branch = "master" 24 | name = "github.com/iovisor/gobpf" 25 | packages = [ 26 | "bcc", 27 | "pkg/cpuonline" 28 | ] 29 | revision = "3b07770c6d5e2bd37e582ecd49460e6ef094f257" 30 | 31 | [[projects]] 32 | name = "github.com/sirupsen/logrus" 33 | packages = ["."] 34 | revision = "c155da19408a8799da419ed3eeb0cb5db0ad5dbc" 35 | version = "v1.0.5" 36 | 37 | [[projects]] 38 | name = "github.com/spf13/cobra" 39 | packages = ["."] 40 | revision = "7b2c5ac9fc04fc5efafb60700713d4fa609b777b" 41 | 42 | [[projects]] 43 | name = "github.com/spf13/pflag" 44 | packages = ["."] 45 | revision = "583c0c0531f06d5278b7d917446061adc344b5cd" 46 | version = "v1.0.1" 47 | 48 | [[projects]] 49 | branch = "master" 50 | name = "golang.org/x/crypto" 51 | packages = ["ssh/terminal"] 52 | revision = "a8fb68e7206f8c78be19b432c58eb52a6aa34462" 53 | 54 | [[projects]] 55 | branch = "master" 56 | name = "golang.org/x/sys" 57 | packages = [ 58 | "unix", 59 | "windows" 60 | ] 61 | revision = "8014b7b116a67fea23fbb82cd834c9ad656ea44b" 62 | 63 | [solve-meta] 64 | analyzer-name = "dep" 65 | analyzer-version = 1 66 | inputs-digest = "f1b2c2a9fc33729f25639cdb633154f96e23f09a2f09075a2831baab695412f1" 67 | solver-name = "gps-cdcl" 68 | solver-version = 1 69 | -------------------------------------------------------------------------------- /pkg/output/parse.go: -------------------------------------------------------------------------------- 1 | package output 2 | 3 | import ( 4 | "context" 5 | "encoding/hex" 6 | "fmt" 7 | "strconv" 8 | "strings" 9 | 10 | bpf "github.com/iovisor/gobpf/bcc" 11 | log "github.com/sirupsen/logrus" 12 | ) 13 | 14 | type FormatFunc func(map[string]interface{}) (string, error) 15 | 16 | type Parser struct { 17 | mod *bpf.Module 18 | stop chan struct{} 19 | } 20 | 21 | func NewParser(mod *bpf.Module) *Parser { 22 | return &Parser{ 23 | mod: mod, 24 | stop: make(chan struct{}), 25 | } 26 | } 27 | 28 | func (p *Parser) parseHexString(raw string) ([]byte, error) { 29 | ret := "" 30 | for _, c := range strings.Split(strings.Replace(raw, "0x", "", -1), " ") { 31 | if c == "[" || c == "]" { 32 | continue 33 | } 34 | if c == "0" { 35 | break 36 | } 37 | ret = fmt.Sprintf("%s%s", ret, c) 38 | } 39 | return hex.DecodeString(ret) 40 | } 41 | 42 | func (p *Parser) parseHexInt(raw string) (uint64, error) { 43 | return strconv.ParseUint(raw, 0, 64) 44 | } 45 | 46 | func (p *Parser) Parse(ctx context.Context, format FormatFunc) (err error) { 47 | defer p.mod.Close() 48 | for entry := range p.mod.TableIter() { 49 | tableName, err := format(entry) 50 | if err != nil { 51 | return err 52 | } 53 | table := bpf.NewTable(p.mod.TableId(tableName), p.mod) 54 | tableConf := table.Config() 55 | if tableConf["key_size"].(uint64) == 4 && tableConf["leaf_size"].(uint64) == 4 { 56 | out := make(chan []byte) 57 | fmt.Printf("%+v\n", table.Config()) 58 | perfMap, err := bpf.InitPerfMap(table, out) 59 | if err != nil { 60 | return err 61 | } 62 | go perfMap.Start() 63 | for o := range out { 64 | log.Info(string(o)) 65 | } 66 | perfMap.Stop() 67 | } else { 68 | entries := table.Iter() 69 | for entry := range entries { 70 | fmt.Printf("key %s, value %s\n", entry.Key, entry.Value) 71 | } 72 | } 73 | } 74 | return nil 75 | } 76 | 77 | func (p *Parser) Stop() { 78 | p.stop <- struct{}{} 79 | } 80 | -------------------------------------------------------------------------------- /pkg/maps/maps.go: -------------------------------------------------------------------------------- 1 | package maps 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "fmt" 7 | "io" 8 | "os" 9 | "strconv" 10 | ) 11 | 12 | type ProcMap struct { 13 | Start uint64 14 | Stop uint64 15 | Offset uint64 16 | Dev string 17 | Flags string 18 | INode uint64 19 | PathName string 20 | } 21 | 22 | func (p *ProcMap) Contains(addr uint64) bool { 23 | return p.Start <= addr && p.Stop >= addr 24 | } 25 | 26 | func Contains(maps []*ProcMap, addr uint64) bool { 27 | for _, m := range maps { 28 | if m.Contains(addr) { 29 | return true 30 | } 31 | } 32 | return false 33 | } 34 | 35 | func GetByPID(pid int) (res []*ProcMap, err error) { 36 | path := fmt.Sprintf("/proc/%d/maps", pid) 37 | f, err := os.Open(path) 38 | if err != nil { 39 | return nil, err 40 | } 41 | return Parse(f) 42 | } 43 | 44 | func Parse(f io.Reader) (res []*ProcMap, err error) { 45 | buf := bufio.NewReader(f) 46 | for l, _, err := buf.ReadLine(); err != io.EOF; l, _, err = buf.ReadLine() { 47 | if len(l) == 0 { 48 | continue 49 | } 50 | if err != nil { 51 | return nil, err 52 | } 53 | list := bytes.Split(l, []byte(" ")) 54 | startStop := bytes.Split(list[0], []byte("-")) 55 | start, err := strconv.ParseUint("0x"+string(startStop[0]), 0, 64) 56 | if err != nil { 57 | return nil, err 58 | } 59 | stop, err := strconv.ParseUint("0x"+string(startStop[1]), 0, 64) 60 | if err != nil { 61 | return nil, err 62 | } 63 | offset, err := strconv.ParseUint("0x"+string(list[2]), 0, 64) 64 | if err != nil { 65 | return nil, err 66 | } 67 | iNode, err := strconv.ParseUint(string(list[4]), 0, 64) 68 | if err != nil { 69 | return nil, err 70 | } 71 | pMap := &ProcMap{ 72 | Start: start, 73 | Stop: stop, 74 | Flags: string(list[1]), 75 | Offset: offset, 76 | Dev: string(list[3]), 77 | INode: iNode, 78 | PathName: string(bytes.TrimSpace(list[len(list)-1])), 79 | } 80 | res = append(res, pMap) 81 | } 82 | return 83 | } 84 | -------------------------------------------------------------------------------- /pkg/cpu/c/bpf.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | struct key_t { 6 | u32 pid; 7 | u64 kernel_ip; 8 | u64 kernel_ret_ip; 9 | int user_stack_id; 10 | int kernel_stack_id; 11 | char name[TASK_COMM_LEN]; 12 | }; 13 | 14 | BPF_HASH(counts, struct key_t); 15 | BPF_STACK_TRACE(stack_traces, {{ .StackStorageSize }}); 16 | 17 | int do_perf_event(struct bpf_perf_event_data *ctx) { 18 | u32 pid = bpf_get_current_pid_tgid() >> 32; 19 | if (!({{ .ThreadFilter }})) 20 | return 0; 21 | 22 | u64 zero = 0, *val; 23 | struct key_t key = {.pid = pid}; 24 | bpf_get_current_comm(&key.name, sizeof(key.name)); 25 | 26 | key.user_stack_id = {{ .UserStackGet }}; 27 | key.kernel_stack_id = {{ .KernelStackGet }}; 28 | if (key.kernel_stack_id >= 0) { 29 | // populate extras to fix the kernel stack 30 | struct pt_regs regs = {}; 31 | bpf_probe_read(®s, sizeof(regs), (void *)&ctx->regs); 32 | u64 ip = PT_REGS_IP(®s); 33 | u64 page_offset; 34 | // if ip isn't sane, leave key ips as zero for later checking 35 | #if defined(CONFIG_X86_64) && defined(__PAGE_OFFSET_BASE) 36 | // x64, 4.16, ..., 4.11, etc., but some earlier kernel didn't have it 37 | page_offset = __PAGE_OFFSET_BASE; 38 | #elif defined(CONFIG_X86_64) && defined(__PAGE_OFFSET_BASE_L4) 39 | // x64, 4.17, and later 40 | #if defined(CONFIG_DYNAMIC_MEMORY_LAYOUT) && defined(CONFIG_X86_5LEVEL) 41 | page_offset = __PAGE_OFFSET_BASE_L5; 42 | #else 43 | page_offset = __PAGE_OFFSET_BASE_L4; 44 | #endif 45 | #else 46 | // earlier x86_64 kernels, e.g., 4.6, comes here 47 | // arm64, s390, powerpc, x86_32 48 | page_offset = PAGE_OFFSET; 49 | #endif 50 | if (ip > page_offset) { 51 | key.kernel_ip = ip; 52 | } 53 | } 54 | val = counts.lookup_or_init(&key, &zero); 55 | (*val)++; 56 | return 0; 57 | } 58 | -------------------------------------------------------------------------------- /pkg/cpu/bpf.go: -------------------------------------------------------------------------------- 1 | //AUTOGENERATED by generate_bpf.go 2 | package cpu 3 | 4 | const bpfSRC = ` 5 | #include 6 | #include 7 | #include 8 | 9 | struct key_t { 10 | u32 pid; 11 | u64 kernel_ip; 12 | u64 kernel_ret_ip; 13 | int user_stack_id; 14 | int kernel_stack_id; 15 | char name[TASK_COMM_LEN]; 16 | }; 17 | 18 | BPF_HASH(counts, struct key_t); 19 | BPF_STACK_TRACE(stack_traces, {{ .StackStorageSize }}); 20 | 21 | int do_perf_event(struct bpf_perf_event_data *ctx) { 22 | u32 pid = bpf_get_current_pid_tgid() >> 32; 23 | if (!({{ .ThreadFilter }})) 24 | return 0; 25 | 26 | u64 zero = 0, *val; 27 | struct key_t key = {.pid = pid}; 28 | bpf_get_current_comm(&key.name, sizeof(key.name)); 29 | 30 | key.user_stack_id = {{ .UserStackGet }}; 31 | key.kernel_stack_id = {{ .KernelStackGet }}; 32 | if (key.kernel_stack_id >= 0) { 33 | // populate extras to fix the kernel stack 34 | struct pt_regs regs = {}; 35 | bpf_probe_read(®s, sizeof(regs), (void *)&ctx->regs); 36 | u64 ip = PT_REGS_IP(®s); 37 | u64 page_offset; 38 | // if ip isn't sane, leave key ips as zero for later checking 39 | #if defined(CONFIG_X86_64) && defined(__PAGE_OFFSET_BASE) 40 | // x64, 4.16, ..., 4.11, etc., but some earlier kernel didn't have it 41 | page_offset = __PAGE_OFFSET_BASE; 42 | #elif defined(CONFIG_X86_64) && defined(__PAGE_OFFSET_BASE_L4) 43 | // x64, 4.17, and later 44 | #if defined(CONFIG_DYNAMIC_MEMORY_LAYOUT) && defined(CONFIG_X86_5LEVEL) 45 | page_offset = __PAGE_OFFSET_BASE_L5; 46 | #else 47 | page_offset = __PAGE_OFFSET_BASE_L4; 48 | #endif 49 | #else 50 | // earlier x86_64 kernels, e.g., 4.6, comes here 51 | // arm64, s390, powerpc, x86_32 52 | page_offset = PAGE_OFFSET; 53 | #endif 54 | if (ip > page_offset) { 55 | key.kernel_ip = ip; 56 | } 57 | } 58 | val = counts.lookup_or_init(&key, &zero); 59 | (*val)++; 60 | return 0; 61 | } 62 | ` 63 | -------------------------------------------------------------------------------- /cmd/heapprofile.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "context" 5 | "os" 6 | "os/signal" 7 | "syscall" 8 | 9 | log "github.com/sirupsen/logrus" 10 | "github.com/spf13/cobra" 11 | 12 | "github.com/cpg1111/pprof-ebpf/pkg/childprocess" 13 | "github.com/cpg1111/pprof-ebpf/pkg/heap" 14 | "github.com/cpg1111/pprof-ebpf/pkg/output" 15 | ) 16 | 17 | func init() { 18 | heapprofileCMD.Flags().String("exec", "", "a command to execute and profile") 19 | heapprofileCMD.Flags().Int("pid", 0, "pid to profile, if exec is provided, this is ignored") 20 | heapprofileCMD.Flags().Int("min-size", 0, "minimum heap allocation size") 21 | heapprofileCMD.Flags().Int("max-size", 4096, "maximum heap allocation size") 22 | heapprofileCMD.Flags().Int("count", 1024, "number of stack traces per sample") 23 | heapprofileCMD.Flags().Float64("sample-rate", 1, "sample every N milliseconds") 24 | heapprofileCMD.Flags().Bool("ktrace", true, "whether to trace kernel space") 25 | heapprofileCMD.Flags().Bool("all", true, "trace both kernel space and user space") 26 | heapprofileCMD.Flags().String("src", "c", "source object for stack trace symbols") 27 | } 28 | 29 | func getHeapOpts(cmd *cobra.Command) (opts heap.RunOpts, err error) { 30 | flags := cmd.Flags() 31 | exec, err := flags.GetString("exec") 32 | if err != nil { 33 | return 34 | } 35 | if len(exec) > 0 { 36 | opts.PID, err = childprocess.Exec(exec) 37 | if err != nil { 38 | return 39 | } 40 | } else { 41 | opts.PID, err = flags.GetInt("pid") 42 | if err != nil { 43 | return 44 | } 45 | } 46 | opts.MinSize, err = flags.GetInt("min-size") 47 | if err != nil { 48 | return 49 | } 50 | opts.MaxSize, err = flags.GetInt("max-size") 51 | if err != nil { 52 | return 53 | } 54 | opts.Count, err = flags.GetInt("count") 55 | if err != nil { 56 | return 57 | } 58 | opts.SampleRate, err = flags.GetFloat64("sample-rate") 59 | if err != nil { 60 | return 61 | } 62 | opts.KTrace, err = flags.GetBool("ktrace") 63 | if err != nil { 64 | return 65 | } 66 | opts.TraceAll, err = flags.GetBool("all") 67 | if err != nil { 68 | return 69 | } 70 | opts.SRCObj, err = flags.GetString("src") 71 | if err != nil { 72 | return 73 | } 74 | return 75 | } 76 | 77 | var heapprofileCMD = &cobra.Command{ 78 | Use: "heap", 79 | Short: "profile heap allocations", 80 | Long: `profile heap allocations in user space and/or kernel space 81 | for a specific pid or binary`, 82 | Run: func(cmd *cobra.Command, args []string) { 83 | bckGrnd := context.Background() 84 | ctx, cancel := context.WithCancel(bckGrnd) 85 | opts, err := getHeapOpts(cmd) 86 | if err != nil { 87 | log.WithFields(log.Fields{ 88 | "profiler": "heap", 89 | }).Fatal(err.Error()) 90 | } 91 | mod, err := heap.Create(opts) 92 | if err != nil { 93 | log.WithFields(log.Fields{ 94 | "profiler": "heap", 95 | }).Fatal(err.Error()) 96 | } 97 | parser := output.NewParser(mod) 98 | go parser.Parse(ctx, heap.Format) 99 | defer parser.Stop() 100 | sigChan := make(chan os.Signal, 2) 101 | signal.Notify(sigChan, syscall.SIGTERM, syscall.SIGINT) 102 | <-sigChan 103 | cancel() 104 | }, 105 | } 106 | -------------------------------------------------------------------------------- /cmd/cpuprofile.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "context" 5 | "os" 6 | "os/signal" 7 | "syscall" 8 | 9 | log "github.com/sirupsen/logrus" 10 | "github.com/spf13/cobra" 11 | 12 | "github.com/cpg1111/pprof-ebpf/pkg/childprocess" 13 | "github.com/cpg1111/pprof-ebpf/pkg/cpu" 14 | "github.com/cpg1111/pprof-ebpf/pkg/output" 15 | ) 16 | 17 | func init() { 18 | cpuprofileCMD.Flags().String("exec", "", "command to execute and profile") 19 | cpuprofileCMD.Flags().Int("pid", 0, "pid to profile, if exec is provided, this is ignored") 20 | cpuprofileCMD.Flags().Int("tgid", 0, "specific thread group id to profile") 21 | cpuprofileCMD.Flags().Int("min-block", 0, "minimum blocks in a sample") 22 | cpuprofileCMD.Flags().Int("max-block", 1024, "maximum blocks in a sample") 23 | cpuprofileCMD.Flags().Int("task-name-len", 256, "max length of task names") 24 | cpuprofileCMD.Flags().Int("storage-size", 1024, "size of storage for stack traces") 25 | cpuprofileCMD.Flags().Int("state", 0, "process state to watch") 26 | cpuprofileCMD.Flags().Bool("user-space-only", false, "profile only user space") 27 | cpuprofileCMD.Flags().Bool("kernel-space-only", false, "profile only kernel space") 28 | cpuprofileCMD.Flags().Bool("fold", true, "whether to fold stack traces") 29 | cpuprofileCMD.Flags().Uint64("sample-period", 0, "duration in seconds to sample") 30 | cpuprofileCMD.Flags().Uint64("sample-frequency", 0, "rate which to sample in hertz") 31 | } 32 | 33 | func getCPUOpts(cmd *cobra.Command) (opts cpu.RunOpts, err error) { 34 | flags := cmd.Flags() 35 | exec, err := flags.GetString("exec") 36 | if err != nil { 37 | return 38 | } 39 | if len(exec) > 0 { 40 | opts.PID, err = childprocess.Exec(exec) 41 | if err != nil { 42 | return 43 | } 44 | } else { 45 | opts.PID, err = flags.GetInt("pid") 46 | if err != nil { 47 | return 48 | } 49 | } 50 | opts.TGID, err = flags.GetInt("tgid") 51 | if err != nil { 52 | return 53 | } 54 | opts.MinBlock, err = flags.GetInt("min-block") 55 | if err != nil { 56 | return 57 | } 58 | opts.MaxBlock, err = flags.GetInt("max-block") 59 | if err != nil { 60 | return 61 | } 62 | opts.StackStorageSize, err = flags.GetInt("storage-size") 63 | if err != nil { 64 | return 65 | } 66 | opts.UOnly, err = flags.GetBool("user-space-only") 67 | if err != nil { 68 | return 69 | } 70 | opts.KOnly, err = flags.GetBool("kernel-space-only") 71 | if err != nil { 72 | return 73 | } 74 | opts.Folded, err = flags.GetBool("fold") 75 | if err != nil { 76 | return 77 | } 78 | opts.SamplePeriod, err = flags.GetUint64("sample-period") 79 | if err != nil { 80 | return 81 | } 82 | opts.SampleFrequency, err = flags.GetUint64("sample-frequency") 83 | return 84 | } 85 | 86 | var cpuprofileCMD = &cobra.Command{ 87 | Use: "cpu", 88 | Short: "profile cpu", 89 | Long: `profile cpu in user space and/or kernel space 90 | for a specific pid or binary`, 91 | Run: func(cmd *cobra.Command, args []string) { 92 | sigChan := make(chan os.Signal) 93 | signal.Notify(sigChan, syscall.SIGTERM, syscall.SIGINT) 94 | bckGrnd := context.Background() 95 | ctx, cancel := context.WithCancel(bckGrnd) 96 | opts, err := getCPUOpts(cmd) 97 | if err != nil { 98 | log.WithFields(log.Fields{ 99 | "profiler": "cpu", 100 | }).Fatal(err.Error()) 101 | } 102 | mod, err := cpu.Create(opts) 103 | if err != nil { 104 | log.WithFields(log.Fields{ 105 | "profiler": "cpu", 106 | }).Fatal(err.Error()) 107 | } 108 | parser := output.NewParser(mod) 109 | go func() { 110 | pErr := parser.Parse(ctx, cpu.Format) 111 | if pErr != nil { 112 | log.Fatal(pErr) 113 | } 114 | }() 115 | defer parser.Stop() 116 | println("here") 117 | <-sigChan 118 | cancel() 119 | }, 120 | } 121 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | KVER = $(shell uname -r) 2 | KMAJ = $(shell echo $(KVER) | sed -e 's/^\([0-9][0-9]*\)\.[0-9][0-9]*\.[0-9][0-9]*.*/\1/') 3 | KMIN = $(shell echo $(KVER) | sed -e 's/^[0-9][0-9]*\.\([0-9][0-9]*\)\.[0-9][0-9]*.*/\1/') 4 | KREV = $(shell echo $(KVER) | sed -e 's/^[0-9][0-9]*\.[0-9][0-9]*\.\([0-9][0-9]*\).*/\1/') 5 | PKGMGR = apt 6 | UPDATE = update 7 | INSTALL = install -y 8 | ADDREPO = apt-key adv --keyserver 9 | KEYSERVER = keyserver.ubuntu.com 10 | GOPATH = ${HOME}/go/ 11 | 12 | kver_ge = $(shell \ 13 | echo test | awk '{if($(KMAJ) < $(1)) {print 0} else { \ 14 | if($(KMAJ) > $(1)) {print 1} else { \ 15 | if($(KMIN) < $(2)) {print 0} else { \ 16 | if($(KMIN) > $(2)) {print 1} else { \ 17 | if($(KREV) < $(3)) {print 0} else { print 1 } \ 18 | } } } }}' \ 19 | ) 20 | 21 | .PHONY: all 22 | all: generate build 23 | 24 | .PHONY: get-deps 25 | get-deps: 26 | ifneq ($(call kver_ge,4,9,0),1) 27 | echo "pprof-ebpf requires kernel features found in 4.9.X and newer" && exit 1 28 | endif 29 | 30 | ifeq (,$(wildcard /lib/modules/$(KVER))) 31 | sudo ${PKGMGR} ${INSTALL} linux-headers-${KVER} 32 | endif 33 | 34 | ifeq (,$(wildcard /usr/share/bcc/)) 35 | sudo ${ADDREPO} ${KEYSERVER} --recv-keys D4284CDD 36 | sudo ${PKGMGR} ${UPDATE} 37 | if ! [ -x `which git` -eq ""]; then sudo ${PKGMGR} ${INSTALL} git; fi 38 | if ! [ `which go` -eq "" ]; then sudo ${PKGMGR} ${INSTALL} golang; fi 39 | if ! [ `which clang` -eq "" ]; then sudo ${PKGMGR} ${INSTALL} \ 40 | llvm-3.8 libclang-3.8-dev bison \ 41 | libelf-dev flex libedit-dev zlib1g-dev \ 42 | automake libtool;\ 43 | fi 44 | if ! [ `which cmake` -eq "" ]; then sudo ${PKGMGR} ${INSTALL} cmake; fi 45 | if ! [ `which python` -eq "" ]; then sudo ${PKGMGR} ${INSTALL} python; fi 46 | git clone https://github.com/iovisor/bcc.git \ 47 | /usr/share/bcc/ && \ 48 | cd /usr/share/bcc && git checkout v0.5.0 && \ 49 | mkdir ./build && cd ./build/ && \ 50 | cmake .. -DCMAKE_INSTALL_PREFIX=/usr && \ 51 | make && \ 52 | sudo make install; 53 | endif 54 | 55 | ifeq (,$(wildcard $(GOPATH)/bin/dep)) 56 | go get github.com/golang/dep 57 | endif 58 | 59 | ifeq (,$(wildcard /usr/local/bin/protoc)) 60 | cd /tmp/ && \ 61 | git clone https://github.com/google/protobuf.git && \ 62 | cd protobuf && \ 63 | ./autogen.sh && \ 64 | ./configure && make && make install 65 | endif 66 | 67 | .PHONY: build 68 | build: 69 | ifneq ($(call kver_ge,4,9,0),1) 70 | echo "pprof-ebpf requires kernel features found in 4.9.X and newer" && exit 1 71 | endif 72 | 73 | ifeq (,$(wildcard ./vendor/github.com/)) 74 | dep ensure 75 | endif 76 | ifeq (,$(wildcard ./vendor/github.com/google/pprof/proto/profile.pb.go)) 77 | cd ./vendor/github.com/google/pprof/proto/ && \ 78 | protoc ./profile.proto 79 | endif 80 | mkdir -p ./build/ 81 | sudo -E go build -o ./build/pprof-ebpf ./main.go 82 | 83 | docker-build: 84 | ifneq ($(call kver_ge,4,9,0),1) 85 | echo "pprof-ebpf requires kernel features found in 4.9.X and newer" && exit 1 86 | endif 87 | 88 | GOPATH=/go/ go env 89 | ifeq (,$(wildcard ./vendor/github.com/)) 90 | GOPATH=/go/ dep ensure 91 | endif 92 | ifeq (,$(wildcard ./vendor/github.com/google/pprof/proto/profile.pb.go)) 93 | cd ./vendor/github.com/google/pprof/proto/ && \ 94 | protoc ./profile.proto 95 | endif 96 | mkdir -p ./build/ 97 | GOPATH=/go/ go build -o ./build/pprof-ebpf ./main.go 98 | 99 | .PHONY: test 100 | test: 101 | ifneq ($(call kver_ge,4,9,0),1) 102 | echo "pprof-ebpf requires kernel features found in 4.9.X and newer" && exit 1 103 | endif 104 | 105 | go test -v ./... 106 | 107 | .PHONY: clean 108 | clean: 109 | rm pprof-ebpf 110 | 111 | .PHONY: install 112 | install: 113 | ifneq ($(call kver_ge,4,9,0),1) 114 | echo "pprof-ebpf requires kernel features found in 4.9.X and newer" && exit 1 115 | endif 116 | 117 | go install . 118 | 119 | .PHONY: uninstall 120 | uninstall: 121 | rm `which pprof-ebpf` 122 | 123 | .PHONY: generate 124 | generate: 125 | rm pkg/cpu/bpf.go pkg/heap/bpf.go 126 | go generate ./... 127 | -------------------------------------------------------------------------------- /pkg/cpu/profile.go: -------------------------------------------------------------------------------- 1 | //go:generate sh -c "go run generate_bpf.go" 2 | package cpu 3 | 4 | import ( 5 | "fmt" 6 | 7 | bpf "github.com/iovisor/gobpf/bcc" 8 | "github.com/iovisor/gobpf/pkg/cpuonline" 9 | 10 | "github.com/cpg1111/pprof-ebpf/pkg/bpferrors" 11 | "github.com/cpg1111/pprof-ebpf/pkg/bpftypes" 12 | "github.com/cpg1111/pprof-ebpf/pkg/srcfmt" 13 | ) 14 | 15 | /* 16 | #cgo CFLAGS: -I/usr/include/bcc/compat 17 | #cgo LDFLAGS: -lbcc 18 | #include 19 | #include 20 | */ 21 | import "C" 22 | 23 | type srcTMPL struct { 24 | MinBlockUS int 25 | MaxBlockUS int 26 | StackStorageSize int 27 | ThreadFilter string 28 | StateFilter string 29 | UserStackGet string 30 | KernelStackGet string 31 | } 32 | 33 | type procKey struct { 34 | Pid uint32 35 | TGid uint32 36 | UserStackID int 37 | KernelStackID int 38 | Name []byte 39 | } 40 | 41 | type RunOpts struct { 42 | PID int 43 | TGID int 44 | MinBlock int 45 | MaxBlock int 46 | StackStorageSize int 47 | UOnly bool 48 | KOnly bool 49 | Folded bool 50 | SamplePeriod uint64 51 | SampleFrequency uint64 52 | } 53 | 54 | func attachPerfEvent(mod *bpf.Module, fnName string, evType, evConfig uint32, samplePeriod, sampleFreq uint64, pid, cpu, groupFD int) (int, error) { 55 | fd, err := mod.Load(fnName, C.BPF_PROG_TYPE_PERF_EVENT, 0, 0) 56 | if err != nil { 57 | return 0, fmt.Errorf("failed to load BPF perf event %v : %v", fnName, err) 58 | } 59 | res, err := C.bpf_attach_perf_event( 60 | C.int(fd), 61 | C.uint32_t(evType), 62 | C.uint32_t(evConfig), 63 | C.uint64_t(samplePeriod), 64 | C.uint64_t(sampleFreq), 65 | C.pid_t(pid), 66 | C.int(cpu), 67 | C.int(groupFD), 68 | ) 69 | if res < 0 || err != nil { 70 | return -1, fmt.Errorf("failed to attach BPF perf event %v : %v", fnName, err) 71 | } 72 | return int(res), nil 73 | } 74 | 75 | func attachPerfEventCPUs(mod *bpf.Module, fnName string, evType, evConfig uint32, samplePeriod, sampleFreq uint64, pid, cpu, groupFD int) error { 76 | if cpu >= 0 { 77 | _, err := attachPerfEvent( 78 | mod, 79 | fnName, 80 | evType, 81 | evConfig, 82 | samplePeriod, 83 | sampleFreq, 84 | pid, 85 | cpu, 86 | groupFD, 87 | ) 88 | if err != nil { 89 | return err 90 | } 91 | return nil 92 | } 93 | cpus, err := cpuonline.Get() 94 | if err != nil { 95 | return err 96 | } 97 | for _, c := range cpus { 98 | _, err = attachPerfEvent( 99 | mod, 100 | fnName, 101 | evType, 102 | evConfig, 103 | samplePeriod, 104 | sampleFreq, 105 | pid, 106 | int(c), 107 | groupFD, 108 | ) 109 | if err != nil { 110 | return err 111 | } 112 | } 113 | return nil 114 | } 115 | 116 | func Create(opts RunOpts) (*bpf.Module, error) { 117 | var threadFilter, uStackGet, kStackGet string 118 | if opts.TGID != 0 { 119 | threadFilter = fmt.Sprintf("tgid == %d", opts.TGID) 120 | } else if opts.PID != 0 { 121 | threadFilter = fmt.Sprintf("pid == %d", opts.PID) 122 | } else if opts.UOnly { 123 | threadFilter = "!(prev->flags & PF_KTHREAD)" 124 | kStackGet = "-1" 125 | } else if opts.KOnly { 126 | threadFilter = "prev->flags & PF_KTHREAD" 127 | uStackGet = "-1" 128 | } else { 129 | threadFilter = "1" 130 | } 131 | if len(uStackGet) == 0 { 132 | uStackGet = "stack_traces.get_stackid(ctx, BPF_F_REUSE_STACKID)" 133 | } 134 | if len(kStackGet) == 0 { 135 | kStackGet = "stack_traces.get_stackid(ctx, BPF_F_REUSE_STACKID | BPF_F_USER_STACK)" 136 | } 137 | tmpl := &srcTMPL{ 138 | ThreadFilter: threadFilter, 139 | UserStackGet: uStackGet, 140 | KernelStackGet: kStackGet, 141 | StackStorageSize: opts.StackStorageSize, 142 | } 143 | script, err := srcfmt.ProcessSrc(bpfSRC, tmpl) 144 | if err != nil { 145 | return nil, err 146 | } 147 | mod := bpf.NewModule(script.String(), nil) 148 | if mod == nil { 149 | return nil, bpferrors.ErrBadModuleBuild 150 | } 151 | attachPerfEventCPUs( 152 | mod, 153 | "do_perf_event", 154 | bpftypes.PerfTypeSoftware, 155 | bpftypes.PerfSWConfigCPUClock, 156 | opts.SamplePeriod, 157 | opts.SampleFrequency, 158 | opts.PID, 159 | -1, 160 | -1, 161 | ) 162 | return mod, nil 163 | } 164 | -------------------------------------------------------------------------------- /pkg/heap/profile.go: -------------------------------------------------------------------------------- 1 | //go:generate sh -c "go run generate_bpf.go" 2 | package heap 3 | 4 | import ( 5 | "bytes" 6 | "fmt" 7 | "os" 8 | 9 | bpf "github.com/iovisor/gobpf/bcc" 10 | 11 | "github.com/cpg1111/pprof-ebpf/pkg/bpferrors" 12 | "github.com/cpg1111/pprof-ebpf/pkg/srcfmt" 13 | ) 14 | 15 | type srcTMPL struct { 16 | SizeFilter string 17 | StackFlags string 18 | SampleEveryN float64 19 | PageSize int 20 | } 21 | 22 | func createProbe(mod *bpf.Module, pid int, obj, sym, fnPrefix string, canFail bool) error { 23 | if len(fnPrefix) == 0 { 24 | fnPrefix = sym 25 | } 26 | probe, err := mod.LoadUprobe(fnPrefix + "_enter") 27 | if err != nil { 28 | return err 29 | } 30 | retProbe, err := mod.LoadUprobe(fnPrefix + "_exit") 31 | if err != nil { 32 | return err 33 | } 34 | err = mod.AttachUprobe(obj, sym, probe, pid) 35 | if err != nil { 36 | if canFail { 37 | return nil 38 | } 39 | return fmt.Errorf("user probe error: %s", err) 40 | } 41 | err = mod.AttachUretprobe(obj, sym, retProbe, pid) 42 | if err != nil { 43 | if canFail { 44 | return nil 45 | } 46 | return fmt.Errorf("user return probe error: %s", err) 47 | } 48 | return nil 49 | } 50 | 51 | func concatSRCs(src1, src2 string) string { 52 | buf := &bytes.Buffer{} 53 | buf.WriteString(src1) 54 | buf.WriteString("\n") 55 | buf.WriteString(src2) 56 | return buf.String() 57 | } 58 | 59 | func createUserProbes(mod *bpf.Module, pid int, srcObj string) error { 60 | err := createProbe(mod, pid, srcObj, "malloc", "", false) 61 | if err != nil { 62 | return fmt.Errorf("failed to attach 'malloc': %s", err) 63 | } 64 | err = createProbe(mod, pid, srcObj, "calloc", "", false) 65 | if err != nil { 66 | return fmt.Errorf("failed to attach 'calloc': %s", err) 67 | } 68 | err = createProbe(mod, pid, srcObj, "realloc", "", false) 69 | if err != nil { 70 | return fmt.Errorf("failed to attach 'realloc': %s", err) 71 | } 72 | err = createProbe(mod, pid, srcObj, "posix_memalign", "", false) 73 | if err != nil { 74 | return fmt.Errorf("failed to attach 'posix_memalign': %s", err) 75 | } 76 | err = createProbe(mod, pid, srcObj, "valloc", "", false) 77 | if err != nil { 78 | return fmt.Errorf("failed to attach 'valloc': %s", err) 79 | } 80 | err = createProbe(mod, pid, srcObj, "memalign", "", false) 81 | if err != nil { 82 | return fmt.Errorf("failed to attach 'memalign': %s", err) 83 | } 84 | err = createProbe(mod, pid, srcObj, "aligned_alloc", "", true) 85 | if err != nil { 86 | return fmt.Errorf("failed to attach 'aligned_alloc': %s", err) 87 | } 88 | return nil 89 | } 90 | 91 | type RunOpts struct { 92 | PID int 93 | MinSize int 94 | MaxSize int 95 | Count int 96 | SampleRate float64 97 | KTrace bool 98 | CombinedOnly bool 99 | TraceAll bool 100 | SRCObj string 101 | } 102 | 103 | func Create(opts RunOpts) (*bpf.Module, error) { 104 | tmpl := &srcTMPL{ 105 | StackFlags: "BPF_F_REUSE_STACKID", 106 | SampleEveryN: opts.SampleRate, 107 | PageSize: os.Getpagesize(), 108 | } 109 | if opts.MinSize > -1 && opts.MaxSize > -1 { 110 | tmpl.SizeFilter = fmt.Sprintf( 111 | "if ((int)(size) < %d || (int)(size) > %d) return 0;", 112 | opts.MinSize, 113 | opts.MaxSize, 114 | ) 115 | } else if opts.MinSize > -1 { 116 | tmpl.SizeFilter = fmt.Sprintf("if ((int)(size) < %d) return 0;", opts.MinSize) 117 | } else if opts.MaxSize > -1 { 118 | tmpl.SizeFilter = fmt.Sprintf("if ((int)(size) > %d) return 0;", opts.MaxSize) 119 | } 120 | src := bpfSRC 121 | if opts.KTrace { 122 | src = concatSRCs(bpfSRC, kSRC) 123 | } 124 | if !opts.KTrace || opts.TraceAll { 125 | tmpl.StackFlags = tmpl.StackFlags + "|BPF_F_USER_STACK" 126 | } 127 | script, err := srcfmt.ProcessSrc(src, tmpl) 128 | if err != nil { 129 | return nil, fmt.Errorf("failed to process bpf source code: %s", err) 130 | } 131 | mod := bpf.NewModule(script.String(), nil) 132 | if mod == nil { 133 | return nil, bpferrors.ErrBadModuleBuild 134 | } 135 | if !opts.KTrace || opts.TraceAll { 136 | err = createUserProbes(mod, opts.PID, opts.SRCObj) 137 | if err != nil { 138 | return nil, fmt.Errorf("failed to create userspace probes: %s", err) 139 | } 140 | probe, err := mod.LoadUprobe("free_enter") 141 | if err != nil { 142 | return nil, fmt.Errorf("failed to load 'free()' probe: %s", err) 143 | } 144 | err = mod.AttachUprobe(opts.SRCObj, "free", probe, opts.PID) 145 | if err != nil { 146 | return nil, fmt.Errorf("failed to attach 'free()' probe: %s", err) 147 | } 148 | } else { 149 | fmt.Println("attaching kernel tracepoints...") 150 | } 151 | return mod, nil 152 | } 153 | -------------------------------------------------------------------------------- /pkg/heap/c/bpf.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | struct alloc_info_t { 4 | u64 size; 5 | u64 timestamp_ns; 6 | int stack_id; 7 | }; 8 | 9 | struct combined_alloc_info_t { 10 | u64 total_size; 11 | u64 number_of_allocs; 12 | }; 13 | 14 | BPF_HASH(sizes, u64); 15 | BPF_TABLE("hash", u64, struct alloc_info_t, allocs, 1000000); 16 | BPF_HASH(memptrs, u64, u64); 17 | BPF_STACK_TRACE(stack_traces, 10240); 18 | BPF_TABLE("hash", u64, struct combined_alloc_info_t, combined_allocs, 10240); 19 | 20 | static inline void update_statistics_add(u64 stack_id, u64 sz) { 21 | struct combined_alloc_info_t *existing_cinfo; 22 | struct combined_alloc_info_t cinfo = {0}; 23 | existing_cinfo = combined_allocs.lookup(&stack_id); 24 | if (existing_cinfo != 0) 25 | cinfo = *existing_cinfo; 26 | cinfo.total_size += sz; 27 | cinfo.number_of_allocs += 1; 28 | combined_allocs.update(&stack_id, &cinfo); 29 | } 30 | 31 | static inline void update_statistics_del(u64 stack_id, u64 sz) { 32 | struct combined_alloc_info_t *existing_cinfo; 33 | struct combined_alloc_info_t cinfo = {0}; 34 | existing_cinfo = combined_allocs.lookup(&stack_id); 35 | if (existing_cinfo != 0) 36 | cinfo = *existing_cinfo; 37 | if (sz >= cinfo.total_size) { 38 | cinfo.total_size = 0; 39 | } else { 40 | cinfo.total_size -= sz; 41 | } 42 | if (cinfo.number_of_allocs > 0) 43 | cinfo.number_of_allocs -= 1; 44 | combined_allocs.update(&stack_id, &cinfo); 45 | } 46 | 47 | static inline int gen_alloc_enter(struct pt_regs *ctx, size_t size) { 48 | {{ .SizeFilter }} 49 | if ({{ .SampleEveryN }} > 0) { 50 | u64 ts = bpf_ktime_get_ns(); 51 | if ((int)(ts) % {{ .SampleEveryN }} != 0) { 52 | ts = (u64)((int)(ts) - ((int)(ts) % {{.SampleEveryN}})); 53 | } 54 | } 55 | u64 pid = bpf_get_current_pid_tgid(); 56 | u64 size64 = size; 57 | sizes.update(&pid, &size64); 58 | bpf_trace_printk("alloc entered, size = %u\\n", size); 59 | return 0; 60 | } 61 | 62 | static inline int gen_alloc_exit2(struct pt_regs *ctx, u64 address) { 63 | u64 pid = bpf_get_current_pid_tgid(); 64 | u64* size64 = sizes.lookup(&pid); 65 | struct alloc_info_t info = {0}; 66 | if (size64 == 0) 67 | return 0; 68 | info.size = *size64; 69 | sizes.delete(&pid); 70 | info.timestamp_ns = bpf_ktime_get_ns(); 71 | info.stack_id = stack_traces.get_stackid(ctx, {{ .StackFlags }}); 72 | allocs.update(&address, &info); 73 | update_statistics_add(info.stack_id, info.size); 74 | bpf_trace_printk("alloc exited, size = %lu, result = %lx\\n", info.size, address); 75 | return 0; 76 | } 77 | 78 | static inline int gen_alloc_exit(struct pt_regs *ctx) { 79 | return gen_alloc_exit2(ctx, PT_REGS_RC(ctx)); 80 | } 81 | 82 | static inline int gen_free_enter(struct pt_regs *ctx, void *address) { 83 | u64 addr = (u64)(address); 84 | struct alloc_info_t *info = allocs.lookup(&addr); 85 | if (info == 0) 86 | return 0; 87 | allocs.delete(&addr); 88 | update_statistics_del(info->stack_id, info->size); 89 | bpf_trace_printk("free entered, address = %lx, size = %lu\\n", address, info->size); 90 | return 0; 91 | } 92 | 93 | int malloc_enter(struct pt_regs *ctx, size_t size) { 94 | return gen_alloc_enter(ctx, size); 95 | } 96 | 97 | int malloc_exit(struct pt_regs *ctx) { 98 | return gen_alloc_exit(ctx); 99 | } 100 | 101 | int free_enter(struct pt_regs *ctx, void *address) { 102 | return gen_free_enter(ctx, address); 103 | } 104 | 105 | int calloc_enter(struct pt_regs *ctx, size_t nmemb, size_t size) { 106 | return gen_alloc_enter(ctx, nmemb * size); 107 | } 108 | 109 | int calloc_exit(struct pt_regs *ctx) { 110 | return gen_alloc_exit(ctx); 111 | } 112 | 113 | int realloc_enter(struct pt_regs *ctx, void *ptr, size_t size) { 114 | gen_free_enter(ctx, ptr); 115 | return gen_alloc_enter(ctx, size); 116 | } 117 | 118 | int realloc_exit(struct pt_regs *ctx) { 119 | return gen_alloc_exit(ctx); 120 | } 121 | 122 | int posix_memalign_enter(struct pt_regs *ctx, void **memptr, size_t alignment, size_t size) { 123 | u64 memptr64 = (u64)(size_t)(memptr); 124 | u64 pid = bpf_get_current_pid_tgid(); 125 | memptrs.update(&pid, &memptr64); 126 | return gen_alloc_enter(ctx, size); 127 | } 128 | 129 | int posix_memalign_exit(struct pt_regs *ctx) { 130 | u64 pid = bpf_get_current_pid_tgid(); 131 | u64 *memptr64 = memptrs.lookup(&pid); 132 | void *addr; 133 | if (memptr64 == 0) 134 | return 0; 135 | memptrs.delete(&pid); 136 | if (bpf_probe_read(&addr, sizeof(void*), (void*)(size_t)(*memptr64))) 137 | return 0; 138 | u64 addr64 = (u64)(size_t)(addr); 139 | return gen_alloc_exit2(ctx, addr64); 140 | } 141 | 142 | int aligned_alloc_enter(struct pt_regs *ctx, size_t alignment, size_t size) { 143 | return gen_alloc_enter(ctx, size); 144 | } 145 | 146 | int aligned_alloc_exit(struct pt_regs *ctx) { 147 | return gen_alloc_exit(ctx); 148 | } 149 | 150 | int valloc_enter(struct pt_regs *ctx, size_t size) { 151 | return gen_alloc_enter(ctx, size); 152 | } 153 | 154 | int valloc_exit(struct pt_regs *ctx) { 155 | return gen_alloc_exit(ctx); 156 | } 157 | 158 | int memalign_enter(struct pt_regs *ctx, size_t alignment, size_t size) { 159 | return gen_alloc_enter(ctx, size); 160 | } 161 | 162 | int memalign_exit(struct pt_regs *ctx) { 163 | return gen_alloc_exit(ctx); 164 | } 165 | 166 | int pvalloc_enter(struct pt_regs *ctx, size_t size) { 167 | return gen_alloc_enter(ctx, size); 168 | } 169 | 170 | int pvalloc_exit(struct pt_regs *ctx) { 171 | return gen_alloc_exit(ctx); 172 | } 173 | -------------------------------------------------------------------------------- /pkg/heap/bpf.go: -------------------------------------------------------------------------------- 1 | //AUTOGENERATED by generate_bpf.go 2 | package heap 3 | 4 | const bpfSRC = ` 5 | #include 6 | 7 | struct alloc_info_t { 8 | u64 size; 9 | u64 timestamp_ns; 10 | int stack_id; 11 | }; 12 | 13 | struct combined_alloc_info_t { 14 | u64 total_size; 15 | u64 number_of_allocs; 16 | }; 17 | 18 | BPF_HASH(sizes, u64); 19 | BPF_TABLE("hash", u64, struct alloc_info_t, allocs, 1000000); 20 | BPF_HASH(memptrs, u64, u64); 21 | BPF_STACK_TRACE(stack_traces, 10240); 22 | BPF_TABLE("hash", u64, struct combined_alloc_info_t, combined_allocs, 10240); 23 | 24 | static inline void update_statistics_add(u64 stack_id, u64 sz) { 25 | struct combined_alloc_info_t *existing_cinfo; 26 | struct combined_alloc_info_t cinfo = {0}; 27 | existing_cinfo = combined_allocs.lookup(&stack_id); 28 | if (existing_cinfo != 0) 29 | cinfo = *existing_cinfo; 30 | cinfo.total_size += sz; 31 | cinfo.number_of_allocs += 1; 32 | combined_allocs.update(&stack_id, &cinfo); 33 | } 34 | 35 | static inline void update_statistics_del(u64 stack_id, u64 sz) { 36 | struct combined_alloc_info_t *existing_cinfo; 37 | struct combined_alloc_info_t cinfo = {0}; 38 | existing_cinfo = combined_allocs.lookup(&stack_id); 39 | if (existing_cinfo != 0) 40 | cinfo = *existing_cinfo; 41 | if (sz >= cinfo.total_size) { 42 | cinfo.total_size = 0; 43 | } else { 44 | cinfo.total_size -= sz; 45 | } 46 | if (cinfo.number_of_allocs > 0) 47 | cinfo.number_of_allocs -= 1; 48 | combined_allocs.update(&stack_id, &cinfo); 49 | } 50 | 51 | static inline int gen_alloc_enter(struct pt_regs *ctx, size_t size) { 52 | {{ .SizeFilter }} 53 | if ({{ .SampleEveryN }} > 0) { 54 | u64 ts = bpf_ktime_get_ns(); 55 | if ((int)(ts) % {{ .SampleEveryN }} != 0) { 56 | ts = (u64)((int)(ts) - ((int)(ts) % {{.SampleEveryN}})); 57 | } 58 | } 59 | u64 pid = bpf_get_current_pid_tgid(); 60 | u64 size64 = size; 61 | sizes.update(&pid, &size64); 62 | bpf_trace_printk("alloc entered, size = %u\\n", size); 63 | return 0; 64 | } 65 | 66 | static inline int gen_alloc_exit2(struct pt_regs *ctx, u64 address) { 67 | u64 pid = bpf_get_current_pid_tgid(); 68 | u64* size64 = sizes.lookup(&pid); 69 | struct alloc_info_t info = {0}; 70 | if (size64 == 0) 71 | return 0; 72 | info.size = *size64; 73 | sizes.delete(&pid); 74 | info.timestamp_ns = bpf_ktime_get_ns(); 75 | info.stack_id = stack_traces.get_stackid(ctx, {{ .StackFlags }}); 76 | allocs.update(&address, &info); 77 | update_statistics_add(info.stack_id, info.size); 78 | bpf_trace_printk("alloc exited, size = %lu, result = %lx\\n", info.size, address); 79 | return 0; 80 | } 81 | 82 | static inline int gen_alloc_exit(struct pt_regs *ctx) { 83 | return gen_alloc_exit2(ctx, PT_REGS_RC(ctx)); 84 | } 85 | 86 | static inline int gen_free_enter(struct pt_regs *ctx, void *address) { 87 | u64 addr = (u64)(address); 88 | struct alloc_info_t *info = allocs.lookup(&addr); 89 | if (info == 0) 90 | return 0; 91 | allocs.delete(&addr); 92 | update_statistics_del(info->stack_id, info->size); 93 | bpf_trace_printk("free entered, address = %lx, size = %lu\\n", address, info->size); 94 | return 0; 95 | } 96 | 97 | int malloc_enter(struct pt_regs *ctx, size_t size) { 98 | return gen_alloc_enter(ctx, size); 99 | } 100 | 101 | int malloc_exit(struct pt_regs *ctx) { 102 | return gen_alloc_exit(ctx); 103 | } 104 | 105 | int free_enter(struct pt_regs *ctx, void *address) { 106 | return gen_free_enter(ctx, address); 107 | } 108 | 109 | int calloc_enter(struct pt_regs *ctx, size_t nmemb, size_t size) { 110 | return gen_alloc_enter(ctx, nmemb * size); 111 | } 112 | 113 | int calloc_exit(struct pt_regs *ctx) { 114 | return gen_alloc_exit(ctx); 115 | } 116 | 117 | int realloc_enter(struct pt_regs *ctx, void *ptr, size_t size) { 118 | gen_free_enter(ctx, ptr); 119 | return gen_alloc_enter(ctx, size); 120 | } 121 | 122 | int realloc_exit(struct pt_regs *ctx) { 123 | return gen_alloc_exit(ctx); 124 | } 125 | 126 | int posix_memalign_enter(struct pt_regs *ctx, void **memptr, size_t alignment, size_t size) { 127 | u64 memptr64 = (u64)(size_t)(memptr); 128 | u64 pid = bpf_get_current_pid_tgid(); 129 | memptrs.update(&pid, &memptr64); 130 | return gen_alloc_enter(ctx, size); 131 | } 132 | 133 | int posix_memalign_exit(struct pt_regs *ctx) { 134 | u64 pid = bpf_get_current_pid_tgid(); 135 | u64 *memptr64 = memptrs.lookup(&pid); 136 | void *addr; 137 | if (memptr64 == 0) 138 | return 0; 139 | memptrs.delete(&pid); 140 | if (bpf_probe_read(&addr, sizeof(void*), (void*)(size_t)(*memptr64))) 141 | return 0; 142 | u64 addr64 = (u64)(size_t)(addr); 143 | return gen_alloc_exit2(ctx, addr64); 144 | } 145 | 146 | int aligned_alloc_enter(struct pt_regs *ctx, size_t alignment, size_t size) { 147 | return gen_alloc_enter(ctx, size); 148 | } 149 | 150 | int aligned_alloc_exit(struct pt_regs *ctx) { 151 | return gen_alloc_exit(ctx); 152 | } 153 | 154 | int valloc_enter(struct pt_regs *ctx, size_t size) { 155 | return gen_alloc_enter(ctx, size); 156 | } 157 | 158 | int valloc_exit(struct pt_regs *ctx) { 159 | return gen_alloc_exit(ctx); 160 | } 161 | 162 | int memalign_enter(struct pt_regs *ctx, size_t alignment, size_t size) { 163 | return gen_alloc_enter(ctx, size); 164 | } 165 | 166 | int memalign_exit(struct pt_regs *ctx) { 167 | return gen_alloc_exit(ctx); 168 | } 169 | 170 | int pvalloc_enter(struct pt_regs *ctx, size_t size) { 171 | return gen_alloc_enter(ctx, size); 172 | } 173 | 174 | int pvalloc_exit(struct pt_regs *ctx) { 175 | return gen_alloc_exit(ctx); 176 | } 177 | ` 178 | 179 | const kSRC = ` 180 | TRACEPOINT_PROBE(kmem, kmalloc) { 181 | gen_alloc_enter((struct pt_regs *)(args), args->bytes_alloc); 182 | return gen_alloc_exit2((struct pt_regs *)(args), (size_t)(args->ptr)); 183 | } 184 | 185 | TRACEPOINT_PROBE(kmem, kmalloc_node) { 186 | gen_alloc_enter((struct pt_regs *)(args), args->bytes_alloc); 187 | return gen_alloc_exit2((struct pt_regs *)(args), (size_t)(args->ptr)); 188 | } 189 | 190 | TRACEPOINT_PROBE(kmem, kfree) { 191 | return gen_free_enter((struct pt_regs *)(args), (void *)(args->ptr)); 192 | } 193 | 194 | TRACEPOINT_PROBE(kmem, kmem_cache_alloc) { 195 | gen_alloc_enter((struct pt_regs *)(args), args->bytes_alloc); 196 | return gen_alloc_exit2((struct pt_regs *)(args), (size_t)(args->ptr)); 197 | } 198 | 199 | TRACEPOINT_PROBE(kmem, kmem_cache_alloc_node) { 200 | gen_alloc_enter((struct pt_regs *)(args), args->bytes_alloc); 201 | return gen_alloc_exit2((struct pt_regs *)(args), (size_t)(args->ptr)); 202 | } 203 | 204 | TRACEPOINT_PROBE(kmem, kmem_cache_free) { 205 | return gen_free_enter((struct pt_regs *)(args), (void *)(args->ptr)); 206 | } 207 | 208 | TRACEPOINT_PROBE(kmem, mm_page_alloc) { 209 | gen_alloc_enter((struct pt_regs *)(args), {{ .PageSize }} << args->order); 210 | return gen_alloc_exit2((struct pt_regs *)(args), args->pfn); 211 | } 212 | 213 | TRACEPOINT_PROBE(kmem, mm_page_free) { 214 | return gen_free_enter((struct pt_regs *)(args), (void *)(args->pfn)); 215 | } 216 | ` 217 | --------------------------------------------------------------------------------