It's one of the most-searched server questions there is: memory usage is at 90%, you go looking for the culprit, and top shows nothing. No leak, no runaway process, the sum of every process's resident memory is nowhere near 90%. The answer is the Linux page cache — and once you understand it, you'll stop alerting on the wrong metric and start watching the one that actually predicts an outage.
Free memory is wasted memory
Every time Linux reads a file from disk, it keeps a copy of those blocks in otherwise-idle RAM. The next read of the same file comes from memory — nanoseconds instead of milliseconds. This is the page cache, and the kernel grows it to fill nearly all the RAM nothing else is using, because empty RAM does literally nothing for you.
The crucial property: page cache memory is reclaimable. The moment a process actually needs memory, the kernel drops cached pages instantly to make room. Cache "usage" is not consumption in any meaningful sense — it's the kernel putting spare capacity to work. The classic explainer linuxatemyram.com has been making this exact point since 2009, and the confusion still fills server-monitoring dashboards with red.
How to read free -h
$ free -h
total used free shared buff/cache available
Mem: 31Gi 4.2Gi 1.1Gi 130Mi 26Gi 26Gi
Swap: 2.0Gi 0B 2.0Gi Three columns matter, and they're not the ones most dashboards chart:
used is memory processes have actually claimed — here 4.2 GB. buff/cache is the page cache plus kernel buffers — 26 GB of reclaimable speed-up. available is the kernel's own estimate of how much memory could be handed to a new workload right now without swapping — cache included. It comes from MemAvailable in /proc/meminfo, added to the kernel in 2014 specifically because userspace tools kept computing "free" wrong.
The rule: ignore free, ignore used, watch available. A server with 1 GB "free" and 26 GB "available" is healthy. A server with 8 GB "free" and 600 MB "available" is about to have a very bad night.
When high memory usage is a real problem
Three signals distinguish a healthy cache-filled server from one that is genuinely running out of memory:
Available is trending down. Cache filling RAM produces a flat available line at a comfortable level. A leak produces a steadily descending one, across hours or days, that doesn't recover after traffic drops. The trend is the signal — a single snapshot tells you almost nothing.
Swap is actively churning. Swap occupancy is mostly noise (the kernel parks cold pages there opportunistically). Swap activity — sustained page-in and page-out operations per second — means processes need more memory than exists and the kernel is shuffling pages to disk on the hot path. That's when latency falls off a cliff.
The OOM killer has fired. journalctl -k | grep -i "out of memory". If the kernel has started killing processes, you're past prediction and into incident response. On modern kernels, memory pressure (PSI) gives you the earlier warning: time spent stalled waiting for memory, before anything dies.
Collecting the right metrics with OpenTelemetry
The OpenTelemetry Collector's hostmetrics receiver emits exactly the distinctions above. system.memory.usage is broken down by a state attribute — used, free, cached, buffered, slab_reclaimable — so cache is never silently lumped into "used." system.linux.memory.available reports the kernel's MemAvailable directly, and the paging scraper emits system.paging.operations with direction (page_in / page_out) for swap activity.
receivers:
hostmetrics:
collection_interval: 30s
scrapers:
memory:
metrics:
system.linux.memory.available:
enabled: true # off by default; this is the one you want
paging:
filesystem:
exporters:
otlp/telemtra:
endpoint: ingest.telemtra.io:443
headers:
x-api-key: ${env:TELEMTRA_API_KEY}
service:
pipelines:
metrics:
receivers: [hostmetrics]
exporters: [otlp/telemtra] That's the entire agent. One Collector binary per host, no vendor agent, and the same OTLP pipeline can carry your syslog and Nginx logs alongside the metrics.
What to chart and alert on in Telemtra
In Metrics, chart system.linux.memory.available per host — it arrives as a gauge, and the shape of the line is the diagnosis: flat is healthy, descending is a leak. Add system.memory.usage filtered to state=used as a second series if you want to see real process consumption next to it; leave cached off the alerting path entirely.
Then set two alerts: system.linux.memory.available below ~10% of the host's total for more than five minutes, and system.paging.operations with direction=page_out sustained above zero on a host that normally doesn't swap. One detail for the second alert: paging operations arrive as a cumulative counter, so choose the Rate aggregation when you create the rule — Telemtra then evaluates the per-second change instead of the ever-growing total, which is what "sustained above zero" actually means here. Between them they catch leaks, undersized hosts, and runaway workloads — without a single false alarm from the page cache doing its job. The alert most teams actually run, "memory utilization > 90%", fires constantly on perfectly healthy servers and trains everyone to ignore it; that's the noisy-alert trap in its purest form.
Related reading
Server monitoring with OpenTelemetry covers the full hostmetrics setup beyond memory; Linux host observability is the step-by-step doc including logs; OpenTelemetry metrics in production explains gauges vs counters vs histograms if the metric types above are new. Start a free Telemtra workspace to see your hosts' real memory picture in about a minute.