A developer toolkit for people who write GNOME Shell extensions. It's a native GTK4 / libadwaita app, so it looks and behaves like the rest of your desktop instead of some dev tool bolted on the side.
The way it works: it drops a small bridge extension into the running shell and instruments your extension at runtime, without you touching a single line of your own code. From there you can profile function timing and look at it as a flamegraph (the call tree and timing), a swimlane (when and how often each function runs), or a histogram (where the time actually goes). Logs get filtered down to one extension UUID so you're not digging through the whole journal, and the inspector lets you open up any running extension object and walk its properties, values and nested objects while it runs.
| Feature | Description |
|---|---|
| Extension Manager | Browse all installed extensions with status, enable/disable with one click, open the source folder directly |
| Log Viewer | Live systemd journal stream. Captures the gnome-shell process by default (that's where all extensions actually run), with structured capture controls (scope, boot, source preset, priority) and a raw journalctl override when you want full control. Filter by log level, extension tag, and full-text search in real time |
| Profiler | Monkey-patch any extension at runtime. No code changes needed. Visualise timing as a flamegraph, swimlane, or histogram; export and reload sessions as JSON |
| Inspector | Inspect a live extension object: browse its properties and methods, see current values, and call methods interactively |
There are two parts: a GTK4 app (Python) and a bridge GJS extension
(gse-profiler-bridge@todevelopers) that the app installs for you on first launch. The
bridge runs inside the gnome-shell process itself, which is what gives it direct,
in-process access to every loaded extension and its live objects and functions.
You have to restart GNOME Shell once after the bridge lands. The app just asks you to log out and back in (Wayland only; X11 sessions aren't supported).
The main window has a live connection indicator, so you always know whether the bridge is actually talking to the app.
The app and bridge talk over a Unix domain socket
($XDG_RUNTIME_DIR/gse-profiler/gse-profiler.sock) using newline-delimited JSON. The bridge
initiates the connection and reconnects automatically after a failure (3 s delay). On connect
it sends a hello handshake; from that point the app can start a profiling or inspection session.
Standard extension management (listing, enabling, disabling) goes through the regular
org.gnome.Shell.Extensions D-Bus interface. The socket is only there for the stuff
D-Bus is bad at: high-frequency profiling events and live inspection results. Neither path
needs elevated permissions.
When you start profiling, the bridge walks the extension's stateObj (its full prototype
chain plus one level of owned child objects, like _indicator) and wraps every enumerable
function it finds. You don't change anything in your extension's source, and all the patches
are fully reversed the moment you stop.
Each wrapped call adds two GLib.get_monotonic_time() reads (microsecond precision) and
queues one JSON event for the socket. For a typical GNOME Shell extension the overhead is
negligible, but extremely tight animation loops or extensions that invoke hundreds of
functions per frame may see a measurable slowdown during recording.
What the profiler cannot patch:
- GObject virtual functions (vfuncs)
- Closures stored in plain variables (not reachable by property enumeration)
- Functions added dynamically after profiling starts
- Async timing: an
asyncfunction is only recorded until it returns itsPromise, usually at the firstawait. The time spent waiting, and the continuations that run after eachawait, don't count toward the event's duration. So async methods show their synchronous setup cost, not their real end-to-end latency.
| Limit | Value |
|---|---|
| Max recorded events | 50,000 (oldest dropped first) |
| Inspector: max properties per object | 50 |
| Inspector: max string value length | 200 characters |
| Inspector: max array elements shown | 50 |
| UI refresh batch window | 80 ms |
All three views run on the same event data, they just answer different questions:
Flamegraph. The call tree laid out on a real-time axis. Each bar is one call, width is duration, nesting shows caller/callee depth. Best when you want to see what called what and catch call stacks that are unexpectedly deep or wide.
Swimlane. One horizontal lane per function, with idle gaps squeezed out. Every invocation is its own segment on that function's row, so you can see when and how often a function runs without the call-depth nesting getting in the way.
Histogram. Functions ranked by self-time, the wall-clock time spent in a function's own code, not counting its callees. This is the fastest way to answer where is the time actually going?, because it ignores time that really belongs to the callee.
Requires GNOME Shell 46+ in an active Wayland GNOME session.
Add the self-hosted stable remote once, then install. You get
automatic updates with flatpak update from then on:
flatpak remote-add --user --if-not-exists todevelopers \
https://todevelopers.github.io/flatpaks/todevelopers.flatpakrepo
flatpak install --user todevelopers io.github.todevelopers.GseProfiler
flatpak run io.github.todevelopers.GseProfilerThe remote is GPG-signed and the key is embedded in the .flatpakrepo
file, so no --no-gpg-verify is needed.
Want early test builds (rc/beta)? Add the testing remote instead:
https://todevelopers.github.io/flatpaks/todevelopers-testing.flatpakrepo
Prefer a one-off install without adding a remote? Grab the .flatpak
bundle from the
latest release
and install it (you won't get automatic updates this way):
flatpak install --user gseprofiler-*.flatpak
flatpak run io.github.todevelopers.GseProfilercurl -fsSL https://raw.githubusercontent.com/todevelopers/gseprofiler/main/scripts/setup-and-run.sh | bashThe script checks for GTK4 / libadwaita, clones the repo to
~/gse-profiler and launches the app. No sudo, no prompts. Run the same
command again later and it pulls the latest changes and restarts the app.
curl -fsSL https://raw.githubusercontent.com/todevelopers/gseprofiler/main/scripts/uninstall.sh | bashRemoves the app, desktop entry, icon, and bridge extension. Nothing else on your system is touched.
- GNOME Shell 46+ (tested up to 50)
- Python 3.11+
- GTK 4 and libadwaita 1
- PyGObject (GTK4 bindings)
python3-systemd, needed for the log viewer (systemd.journal.Reader); bundled automatically in the Flatpak
gse-profiler/
βββ app/ # GTK4 Python application
β βββ main.py
β βββ ui/
β β βββ extension_manager.py
β β βββ extension_list.py
β β βββ details_view.py
β β βββ log_viewer.py
β β βββ profiler_view.py
β β βββ profiler/ # flamegraph, swimlane, histogram widgets
β β βββ inspector_view.py
β βββ core/
β βββ dbus_client.py # D-Bus proxy for gnome-shell APIs
β βββ socket_server.py # Unix socket server (async)
β βββ bridge_manager.py # bridge install / update / hash check
β βββ journal_reader.py # systemd journal reader (systemd.journal.Reader)
βββ bridge-extension/ # GJS GNOME Shell extension
β βββ extension.js
β βββ profiler.js
β βββ inspector.js
β βββ socket_client.js
β βββ metadata.json
βββ build-aux/ # Flatpak manifest and launcher
βββ data/ # .desktop, AppStream metainfo, icons
βββ docs/ # architecture diagrams
βββ scripts/ # setup / uninstall helpers
βββ tests/ # pytest unit tests
See CONTRIBUTING.md for the full guide: development setup, local checks, scripts and CI/release automation.
If you find GSE Profiler useful or it saved you some time during debugging, consider supporting it on Ko-fi.
GPL-3.0-or-later. See LICENSE.




