Remote development over SSH using modern editors has become standard practice. However, live autocompletion and static analysis engines execute demanding backend services. One binary frequently causing CPU and memory spikes is devsense.php.ls (the PHP Language Server by DEVSENSE).
Here is the operating system level diagnostic workflow and the two-tiered containment strategy: tuning editor settings.json and enforcing kernel resource limits on Linux.
1. The challenge: CPU, memory, and i/o bottlenecks#
devsense.php.ls is not an ephemeral script. It runs as a persistent compiled binary communicating via JSON-RPC over the Language Server Protocol (LSP).
On large codebases (such as Laravel or Magento), it can cause several bottlenecks:
- CPU spikes and context switches: Abstract Syntax Tree (AST) indexing generates intense processor context switching.
- Resident Memory (RSS) bloat: Symbol tables and type inference caches consume substantial RAM.
- Inotify handle exhaustion: Hitting kernel watch limits forces the binary into a fallback loop of continuous filesystem polling.
- Orphaned processes: Dropped SSH connections can leave the server binary running without active clients.
2. Terminal diagnostic workflow#
To inspect active process behavior:
1. Tracing system calls with strace#
Check for recursive directory traversal in heavy dependencies (vendor, node_modules):
strace -p <PID> -e trace=file,desc
Detect polling loops (continuous lstat storms):
strace -p <PID> -e trace=lstat | head -n 100
Thousands of repetitive lstat calls indicate the process ran out of inotify handles and fell back to manual disk polling.
2. Memory mapping with pmap#
Audit Heap allocations and mapped libraries:
pmap -x <PID> | sort -n -k3
3. IPC and socket inspection#
Because language servers typically communicate via stdio (JSON-RPC pipes tied to the remote editor session), they rarely listen on public network ports. Inspect open file descriptors, pipes, and active local sockets:
# List open file descriptors and IPC pipes
ls -l /proc/<PID>/fd
# Inspect network and UNIX domain sockets held by the PID
lsof -p <PID> | grep -E 'unix|FIFO|IPv[46]'
# List active local UNIX domain sockets
ss -xap | grep <PID>
3. Two-tiered containment strategy#
Containment relies on two complementary approaches:
Tier 1: Restricting language server scope in settings.json#
Limit analysis strictly to open editor tabs and instruct both VS Code and the LSP to exclude heavy dependencies:
{
"php.problems.scope": "opened",
"php.analysis.extractTypes": false,
"php.stubs.load": false,
"php.inlayHints.enabled": false,
"files.watcherExclude": {
"**/vendor/**": true,
"**/node_modules/**": true,
"**/temp/**": true,
"**/storage/**": true
}
}
php.problems.scope: "opened": Evaluates only open files, disabling full project background scanning.files.watcherExclude: Prevents editor and language server file watchers from tracking third-party and cache folders.- Disabling Type Analysis, Stubs, and Inlay Hints: Reduces initial Heap allocation and background parsing overhead dramatically.
Tier 2: Enforcing Linux kernel resource limits#
To protect shared host stability:
1. I/o and CPU scheduling priority#
# Set I/O priority to Idle (Class 3)
ionice -c 3 -p <PID>
# Set CPU nice level to lowest priority (+19)
renice +19 -p <PID>
2. Cgroups memory limits#
To prevent Out of Memory (OOM) killer invocations on the host:
# Cgroups v2 (modern Linux standard: Ubuntu 22.04+, Debian 12+, RHEL 9+)
sudo mkdir -p /sys/fs/cgroup/devsense_limit
echo 536870912 | sudo tee /sys/fs/cgroup/devsense_limit/memory.max
echo <PID> | sudo tee /sys/fs/cgroup/devsense_limit/cgroup.procs
# Cgroups v1 (legacy environments with libcgroup-tools)
sudo cgcreate -g memory:devsense_limit
echo 512M | sudo tee /sys/fs/cgroup/memory/devsense_limit/memory.limit_in_bytes
sudo cgclassify -g memory:devsense_limit <PID>
3. Increasing inotify user watches#
Prevent fallback to lstat disk polling loops:
# Immediate runtime application
sudo sysctl -w fs.inotify.max_user_watches=524288
# Persistent configuration across reboots
echo "fs.inotify.max_user_watches = 524288" | sudo tee /etc/sysctl.d/90-inotify.conf
sudo sysctl --system
Metric reference guide#
| Metric | Normal State | Warning Indicator |
|---|---|---|
| Resident Set Size (RSS) | 200 MB to 1 GB (stable) | Continuous linear growth (Memory Leak) |
| File Descriptors | Spikes during initial boot only | Nearing system ulimit -n limits |
| Syscalls | futex, epoll_wait, read | Continuous loops of stat and lstat |
Best practices for controlling language servers on Linux#
- Exclude package dependencies: Always add
vendor/andnode_modules/to your LSP indexing exclusion lists. - Tune kernel inotify persistently: Keeping
max_user_watcheselevated in/etc/sysctl.d/prevents background workers from degrading to disk polling. - Use cgroups in shared environments: Imposing memory limits on remote developer processes protects primary server workloads.
Conclusion#
In this article, I shared the two-tiered containment strategy I used to tame devsense.php.ls: narrowing the analysis scope in editor settings and enforcing kernel-level resource boundaries — relying on strace and pmap to verify symptoms before taking action. Combining directory exclusions, memory-capped cgroups, and adequate inotify allocations kept the shared server responsive and stable under concurrent remote development sessions.
Was this article helpful?
Leave a quick reaction to help prioritize future technical guides:
This post is licensed under CC BY-NC.



Comments
Join the discussion below.
0 comments