How to start profiling a juju application on a machine model

1. Determine what you want to profile

Say you have a juju model with a postgresql instance

juju add-model test-profiling
juju deploy postgresql \ 
     # request a virtual machine, else Juju may give us a container incapable of eBPF
    --constraints="virt-type=virtual-machine"  \ 
    # otel-ebpf-profiler only has ubuntu@24.04-base releases, we need this version so the bases match
    --base ubuntu@24.04 --channel 16/edge \ 
    pgql

Now after some time the juju status will show that the pgql application is being deployed on a machine with a certain ID, in my case 1:


Model           Controller  Cloud/Region         Version  SLA          Timestamp
test-profiling  lxd         localhost/localhost  3.6.2    unsupported  14:30:06+02:00

App   Version  Status       Scale  Charm       Channel  Rev  Exposed  Message
pgql  16.9     maintenance      1  postgresql  16/edge  885  no       installing PostgreSQL

Unit     Workload     Agent      Machine  Public address                          Ports  Message
pgql/0*  maintenance  executing  1        fd42:a207:65c0:dd4f:216:3eff:fe4b:8a62         (install) installing PostgreSQL

Machine  State    Address                                 Inst id        Base          AZ  Message
1        started  fd42:a207:65c0:dd4f:216:3eff:fe4b:8a62  juju-a7a9ba-1  ubuntu@24.04      Running

Integration provider  Requirer              Interface         Type  Message
pgql:database-peers   pgql:database-peers   postgresql_peers  peer  joining  
pgql:refresh-v-three  pgql:refresh-v-three  refresh           peer  joining  
pgql:restart          pgql:restart          rolling_op        peer  joining  

Make note of the machine you want to profile (1 in this case), because next we’ll need to deploy the otel-ebpf-profiler charm to the same machine.

2. deploy the otel-ebpf-profiler

juju deploy otel-ebpf-profiler --channel latest/edge --base ubuntu@24.04 \ 
    # deploy to the same machine!
    --to 1 \
    profiler 

Co-locating the charms on machine 1 ensures that the otel-ebpf-profiler will be collecting profiles from the same (v)CPU that’s also running postgresql.

:warning: Deploying the otel-ebpf-profiler to a different machine will mean you’re profiling a machine that’s not running anything interesting (other than the otel-ebpf-profiler itself, naturally).

:warning: Deploying otel-ebpf-profiler to a machine which already has an otel-ebpf-profiler running on it is not supported. otel-ebpf-profiler, on install, claims an exclusive lock on the (Juju) machine it’s deployed on, to prevent duplicate profiling data to be generated (which causes unnecessary overhead and is most likely a user error).

If you are trying to multiplex the output stream of profiling data, you should send the profiles to an opentelemetry-collector instance and configure its profiling pipeline to send copies of all profile data to multiple remote receivers.

At this point the juju model should look something like:

Unit          Workload  Agent      Machine  Public address                          Ports     Message
pgql/0*       active    idle       1        fd42:a207:65c0:dd4f:216:3eff:fe4b:8a62  5432/tcp  Primary
profiler/0*   active    idle       1        fd42:a207:65c0:dd4f:216:3eff:fe4b:8a62            profiling machine 1

3. Send profiles to pyroscope through opentelemetry-collector

In most (production) scenarios we recommend to send all profiling data to an opentelemetry collector instance co-located with the profiler (to minimise traffic cost and optimize the value gained from any sampling being applied by the collector). The opentelemetry collector will then forward profiles to a backend enabled to store and process them, such as pyroscope.

If you have access to a Juju model somewhere with Pyroscope (see tutorial), you can cross-model relate the otel-ebpf-profiler with pyroscope over otelcol by doing:

juju deploy opentelemetry-collector otelcol --channel 2/edge
juju relate otelcol:cos-agent profiler  # to get otelcol deployed and assigned to a machine
juju relate profiler:profiling otelcol  # to start forwarding profiles

juju consume k8s-controller:admin/pyro.pyroscope 
juju relate otelcol:send-profiles pyro:profiling

As soon as Juju settles and the applications report active/idle, you should be able to open the Pyroscope UI (at <pyroscope application IP>:8080) and inspect the pyroscope datasource to find profiles from the postgresql process.

4. Integrate directly with pyroscope and COS

Instead of integrating through an otel collector, it is also possible (for development/testing purposes) to integrate the otel-ebpf-profiler directly to a pyroscope instance.

# in the pyroscope model
juju offer pyroscope:profiling

#back in the test-profiling model
juju consume k8s-controller:admin/pyro.pyroscope
juju relate profiler:profiling pyro:profiling

:warning: :hammer_and_wrench: this will require you to bridge the networks manually in such a way that the profiler application can resolve the pyroscope URL. If pyroscope is not related to an ingress, it will advertise its cluster-internal FQDN which the profiler application will probably not be able to resolve in most vanilla DNS configurations.