How to use perf on MacOS for code profiling

May 2021

This is a quick post that details how to run perf (also known as perf_events) on an OSX machine. Perf is a powerful Linux tool - it can instrument CPU performance and is capable of lightweight profiling. You can get more context about what perf can do here.

It is usually included in the Linux kernel, but there’s no way install it on a Mac. This is a pain because you’d usually want to profile (and generate flame-graphs 1) for your applications locally and not on a prod/staging server because that has a suitable Linux distribution running.

On OSX you can use Docker containers to create such an environment and install perf on it:

  1. Create a Dockerfile with the base image of the distribution you intend to use. e.g. if you’re targeting a Node app for profiling, you can use a Node base image which is debian-based.

     FROM node:14.17.0
    
     WORKDIR /usr/src/app
    
  2. In the same Dockerfile, download the linux-tools source for the Linux version you are using 2, and compile using make.

    
     RUN HOME=$(pwd) && \ 
         # Gets the Linux version and strips out the 'linuxkit' part
         LINUX_VER=$(uname -r | cut -d'.' -f1-3 | cut -d'-' -f1) && \
         # Downloads compressed linux-tools for the version
         wget "https://cdn.kernel.org/pub/linux/kernel/v4.x/linux-$LINUX_VER.tar.xz"
    
     RUN HOME=$(pwd) && \ 
         LINUX_VER=$(uname -r | cut -d'.' -f1-3 | cut -d'-' -f1) && \
     		# Un-compress the source and compile with make
         tar -xf "./linux-$LINUX_VER.tar.xz" && cd "linux-$LINUX_VER/tools/perf/" && \ 
         apt-get update && apt -y install flex bison && \ 
         make -C . && make install && \
         cd $HOME
    
  3. Since this a Node app, do the normal Node things like install packages and copy in source code to the image.

     COPY package*.json ./
    
     RUN npm install
    
     COPY app.js ./
    
     EXPOSE 3000
    
     ENTRYPOINT ["node", "app.js"]
    
  4. Build the image.

     docker build -t example-perf .
    
  5. Run the container in privileged mode and open a bash shell in it.

     docker run --name example-perf --privileged -d example-perf
     docker container exec -it example-perf bash
    
  6. Use perf in it’s installed directory.

     # Enables you run perf without some kernel errors
     echo 0 > /proc/sys/kernel/kptr_restrict
    
     cd ./linux-$(uname -r | cut -d'.' -f1-3 | cut -d'-' -f1)/tools/perf
     ./perf record -F99 -p "$(pgrep -n node)" -g -- sleep 30
    

As a bonus, you can copy perf to the /bin directory so you can access it anywhere, but I haven’t tested that.

If you don’t like/use Docker, you can try replicating this in Vagrant. With Vagrant I don’t think you’d even need to download the source, because Linux headers are available. The apt-get command should suffice.

You can use this repository to get started with creating a Vagrant environment.

Notes

  1. I talk about generating flame-graphs for Node applications in a post coming out soon. 

  2. In a sane world, you should be able to install perf in the container using apt-get install linux-tools-common linux-tools-generic linux-tools-uname -r and be done with it, but you can’t because there are no Linux headers in Docker for Mac. I discussed this briefly in this post. You end up with errors like this:

    E: Unable to locate package linux-tools-common
    E: Unable to locate package linux-tools-generic
    E: Unable to locate package linux-tools-4.9.125-linuxkit
    E: Couldn't find any package by glob 'linux-tools-4.9.125-linuxkit'
    E: Couldn't find any package by regex 'linux-tools-4.9.125-linuxkit'
    

Hi! My name is Opeyemi. I like distributed systems, NodeJS, Golang and Puff Puff. You can learn more about me or message me on Twitter.

Share on