This document covers how much RAM an xpra session consumes, which subsystems and codecs dominate the footprint, and the tunables that trim it. It also explains how XShm shared memory is accounted for, so you don't double-count it when comparing the X11 server (Xorg/Xdummy) and the xpra server side by side.
For sizing the dummy X server's VideoRam, see Xdummy. For OpenGL applications, see also OpenGL — vglrun has a significant memory impact on the X11 server side.
RSS) of the xpra server and Xorg is the primary metric.Xorg and xpra RSS (that's what shared memory means). Use Pss from /proc/<pid>/smaps_rollup — or subtract RssShmem from one side — to avoid double-counting.VideoRam, Xdummy).vglrun.width × height × bytes_per_pixel each).Every running xpra server now reports the data needed to size and diagnose its own memory use through xpra info. No extra setup, no authentication, no env vars.
xpra info :100 | grep -E '^(sys\.memory|display\.memory|display\.xshm|window\..*\.xshm)'Key entries:
| Path | Meaning | | --- | --- | | sys.memory.server.proc.rss | RSS of the xpra server process | | sys.memory.server.proc.pss | proportional set size (XShm-corrected) | | sys.memory.server.proc.rssanon | anonymous heap (unique to this process) | | sys.memory.server.proc.rssshmem | shared-memory pages (XShm + others) | | sys.memory.server.sysv_shm.bytes | total SysV shared memory attached | | sys.memory.server.rusage.maxrss | peak RSS (only with full auth) | | display.pid | the dummy X server (Xdummy/Xvfb) pid | | display.memory.proc.rss | RSS of the X server process | | display.memory.sysv_shm.bytes | shared memory attached by the X server | | display.xshm-attached.bytes | XShm bytes the xpra server has currently attached | | window.<wid>.xshm.bytes | XShm bytes for a specific window |
The display.xshm-attached and display.memory.sysv_shm totals are two views of the same segments — one counted in-process, one read from /proc/sysvipc/shm. They should match.
Repeatedly poll xpra info and diff the relevant counters:
while true; do
xpra info :100 |
grep -E '^(sys\.memory\.server\.proc\.(rss|rssanon)|display\.xshm-attached\.bytes)'
sleep 30
donerssanon growth that isn't matched by visible UI activity is a heap-side leak in the xpra server itself. Growth in display.xshm-attached.bytes without matching window churn points at an XShm wrapper that isn't being released.
damage.ack-pending and damage.encoding-pending per-window counters already log warnings when they grow — but they're now also surfaced in xpra info if you want to track them programmatically.
Test rig: X11 seamless server (Fedora 43, Python 3.14, glibc malloc), packaged xorg.conf, GTK3 Python client on the same host, single xterm open, idle 30 s after connect, with XPRA_XDG=0 XPRA_IBUS=0 set on the server (see Environment variables below). Absolute values vary substantially with distribution, kernel, GLIBC malloc tuning, and the codec set installed; treat these as indicative, not absolute.
| Process | RSS | VSZ | PSS | | --- | --- | --- | --- | | Xorg (Xdummy) | 107 MB | 998 MB | 79 MB | | xpra server (idle, client connected) | 104 MB | 2.3 GB | 78 MB | | xpra server (after malloc_trim) | 97 MB | 2.3 GB | — | | Python GTK3 client | 220 MB | 4.2 GB | 138 MB | | xterm | 13 MB | 241 MB | 7 MB | | pulseaudio | 4 MB | 227 MB | <1 MB | | dbus | 3 MB | 8 MB | <1 MB | | ibus-daemon (only if XPRA_IBUS!=0) | 14 MB | 678 MB | 6 MB |
Notes:
xpra server rss_anon is 60 MB (heap), rss_file 41 MB (shared library code mostly attributable elsewhere — pss is 78 MB).pss of 138 MB is the more honest figure.xpra + Xorg rss naively (211 MB) you over-count XShm — see XShm accounting.XPRA_XDG=0 XPRA_IBUS=0 the server-side rss rises by ~6 MB (freedesktop menu cache + IBus keymap), with no functional impact on a basic xterm session.Each row is one option, toggled in isolation against the baseline. ΔRSS columns show the saving (or cost) on the relevant process.
Most of these default to yes (or auto). Disabling a subsystem removes its mixin from the assembled server class (xpra/server/features.py, xpra/server/factory.py).
Measured against an idle no-client baseline of 96 MB server RSS (with XPRA_XDG=0 XPRA_IBUS=0). Each flag is toggled in isolation; deltas are not strictly additive (some subsystems share imports). One sweep, ±0.5 MB noise floor on this host.
| Option | Default | ΔRSS server | Notes | |-------------------------------------|-------------|-------------|----------------------------------------------------------------------| | --audio=no | yes | −2 MB | Skips GStreamer audio probe | | --gstreamer=no | yes | −2 MB | Implies --audio=no; also disables GStreamer video codecs | | --clipboard=no | yes | ~0 MB | Noise — the clipboard server itself is tiny at idle | | --notifications=no | yes | −1 MB | | | --bell=no | yes | −4 MB | Skips XFixes bell listener + dbus glue | | --cursors=no | yes | −1 MB | | | --dbus-control=no --dbus-launch= | yes (POSIX) | −6 MB | Disables D-Bus control bus and the --dbus-launch daemon spawn | | --mdns=no | yes (POSIX) | −4 MB | Skips Avahi/zeroconf service registration | | --http=no | yes | −6 MB | Skips the embedded HTTP server (websocket upgrade path) | | --webcam=no | yes | −6 MB | | | --printing=no | yes | −1 MB | | | --file-transfer=no | yes | ~0 MB | Noise | | --readonly | no | −7 MB | Disables keyboard and pointer subsystems (skips IBus probing etc.) | | all of the above combined | — | −28 MB | "minimal-stack" — about a 30 % cut on top of the env-var savings |
Codecs eagerly imported by xpra/server/subsystem/encoding.py are the biggest constant-cost memory contributors after GStreamer. Restricting the encoding set skips imports.
Same baseline conditions as the subsystems table above.
| Option | Default | ΔRSS server | Notes | |-------------------------|---------|-------------|----------------------------------------------------------------------------------------| | --encoding=rgb | auto | −2 MB | Forces lossless; runtime selection only. The codec modules are still loaded. | | --encodings=rgb,png | all | −3 MB | Restricts the encoder set; saves a little vs. defaults. | | --video=no | yes | −17 MB | Disables the video pipeline; skips x264/vpx/NVENC/AV1 imports entirely. Biggest win. | | --video-encoders=none | all | −16 MB | Finer-grained version of --video=no — almost identical effect. | | --csc-modules=none | all | −6 MB | Skips libyuv / swscale colorspace conversion modules. Useful with --video-encoders=. |
XPRA_TARGET_LATENCY_TOLERANCE and similar performance tuning knobs do not affect RSS — don't chase phantom savings there.
A few server-side env vars trim memory before any subsystem loads — useful when you want a minimal session without changing config files:
| Variable | Effect | ΔRSS server (measured) | |---------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------| | XPRA_XDG=0 | Skips freedesktop menu generation: no xdg.IniFile / xdg.IconTheme parsing, no per-app icon byte cache (~270 icons × ~10 KB held resident on default-config systems). | ~5 MB | | XPRA_IBUS=0 | Skips IBus keyboard mapping import + the ibus-daemon child process (which itself takes ~14 MB RSS) | ~1 MB server + ~14 MB child |
Both are safe to set when the client doesn't need the application menu and isn't using complex IME (CJK) input. They were used to collect the baseline numbers above.
vglrunOpenGL applications under Xdummy use Mesa's llvmpipe software rasterizer, which allocates large pixmaps on the X server side (charged to Xdummy VideoRam). Wrapping the application in vglrun moves the GL context to the host GPU and eliminates those pixmaps:
xpra start --start="vglrun glxgears"
# or:
xpra start --exec-wrapper=vglrun --start=glxgearsxpra recognizes vglrun as a command wrapper (xpra/server/subsystem/command.py:97) so child-pid bookkeeping still works.
| Test | ΔRSS Xorg | ΔRSS client app | Notes | |------------------------------------------------|-----------|-----------------|-----------------------------------------------------------------------| | glxgears (software GL) vs. vglrun glxgears | TBD | TBD | VirtualGL adds its own per-app overhead but moves the bulk off Xdummy |
See OpenGL for VirtualGL setup and caveats.
XShm (MIT-SHM) is a SysV shared memory segment created by xpra and attached to both xpra (so the encoder reads pixels) and Xorg (so the X server writes them). Size is approximately bytes_per_line × (height + 1) per window.
Consequences:
RSS(xpra) + RSS(Xorg) overstates real memory consumption by the size of all attached XShm segments.Pss (proportional set size), which attributes each shared page proportionally. Both sys.memory.server.proc.pss and display.memory.proc.pss come from /proc/<pid>/smaps_rollup.rssshmem from one side before summing.Worked example: a 1920×1080 window at 32 bpp uses 1920 × 4 × (1080 + 1) = 8 305 920 bytes of XShm. With XShm enabled, that ~8 MB will appear in both Xorg's and xpra's RSS. A naive RSS(xpra) + RSS(Xorg) count adds 16 MB; reality is 8 MB. Use:
xpra info :100 | grep -E '\.(pss|sysv_shm\.bytes)$'Xorg / Xdummy) memory behaviourVideoRam for peak observed pixmap usage, not steady-state.mlock/mlock2 anything (verify with cat /proc/<vfb-pid>/status | grep VmLck — should be 0 kB), and the kernel will page out idle Xdummy pages under memory pressure exactly like any other anonymous allocation. So if you're sizing VideoRam for a host without much physical RAM, the cost of the inflated steady-state is mostly swap + latency on reconnect, not RAM. A first damage event after a long idle may be slow as the kernel pages pixmaps back in, but capacity-wise the host doesn't need to keep all of VideoRam resident.proc(5) manual page documents /proc/<pid>/status (VmRSS, RssAnon, RssShmem, …) and /proc/<pid>/smaps_rollup (Pss, Shared_*, Private_*, Swap)./proc/sysvipc/shm lists every SysV shared memory segment on the host with creator/last-attach pids. xpra parses this for sysv_shm accounting.xf86-video-dummy README describes the semantics of VideoRam in the dummy driver.vglrun setup.XPRA_MEMORY_DEBUG=1 (with optional XPRA_MEMORY_DEBUG_INTERVAL=<ms>, default 5000) on the server starts a background thread that logs psutil memory_full_info() deltas at INFO level. Intended for leak hunting during development; not a CLI option on purpose.