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
done
rssanon 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 |
--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=glxgears
xpra 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.