03 — Shell Stream Pipelines & Server Diagnostics

At a glance

After completing this lesson you can:

Time: ~45 minutes · Prerequisites: 02 — Filesystem & Permissions

Reading Companion

Why this matters in production

At 03:00 AM, before Grafana dashboards even finish loading or when the metrics agent itself is down, an engineer on call must inspect raw access logs, active TCP sockets (ss -tan), and /proc/meminfo directly on the host. Knowing how to stream-parse structured logs and socket tables separates engineers who resolve incidents in 3 minutes from those who reboot servers blindly.

Deep dive

1. Why Average Latency Lies: Percentiles (p50, p95, p99)

If 99 requests take 5 ms and 1 request takes 10,000 ms (database lock timeout), the average latency is ~105 ms—hiding the fact that 1% of your users experienced a 10-second freeze! Backend SLAs are always defined using tail percentiles (p95, p99).

2. Inspecting Listening Sockets (ss -tulpn) and /proc/meminfo

Hands on

python3 phase0-foundations/01-linux-and-shell/03-shell-pipelines-and-diagnostics/examples/01_demo.py
python3 phase0-foundations/01-linux-and-shell/03-shell-pipelines-and-diagnostics/examples/02_trap.py

AI Trap & Code Review

# BAD AI-GENERATED CODE — OOM ON LARGE LOG FILES & WRONG LINUX MEMORY METRIC
def check_server_health(meminfo_text: str, log_path: str) -> bool:
    lines = open(log_path).readlines()  # Loads 20 GB into RAM!
    errors = [l for l in lines if " 500 " in l]
    # Checks MemFree instead of MemAvailable!
    for line in meminfo_text.splitlines():
        if line.startswith("MemFree:"):
            free_kb = int(line.split()[1])
            return free_kb > 500_000 and len(errors) < 100
    return False

Exercises

  1. Rung 1 (parse_meminfo_available_mb): Parse /proc/meminfo text and return MemAvailable in integer megabytes (kB // 1024).
  2. Rung 2 (top_k_5xx_ips — Fix the AI Bug): Stream an iterable of access log lines ("<ip> <method> <path> <status> <latency_ms>") in $O(U)$ memory (where $U$ is unique offending IPs) and return the top k (ip, count) pairs for 5xx statuses (500–599), sorted by count descending then IP ascending.
  3. Rung 3 (compute_latency_percentile): Given a list of latencies in milliseconds and a percentile p (0 < p <= 100), compute the nearest-rank percentile value.
  4. Rung 4 (detect_close_wait_leaks): Parse ss -tan output lines ("<State> <Recv-Q> <Send-Q> <Local_Address:Port> <Peer_Address:Port>") and return a dict mapping local port (int) to the count of CLOSE-WAIT sockets, excluding ports with zero CLOSE-WAIT sockets.
  5. Rung 5 (summarize_endpoint_slo): Aggregate access log lines by "<method> <path>", returning a dict with total, error_rate (fraction of 5xx rounded to 4 decimals), and p95_ms for each endpoint.

Wrapping up

Can you explain?

  1. What is the difference between MemFree and MemAvailable in /proc/meminfo?
  2. What does a spike of sockets in the CLOSE-WAIT state prove about your backend service?
  3. Why is p99 latency far more useful than mean latency when debugging microservice cascades?

Further Reading & References

Show reference solution notes

Solution & Engineering Notes — 03 Shell Pipelines & Server Diagnostics

  1. MemFree vs MemAvailable: Linux uses unused RAM as disk cache (Buffers + Cached). When an application needs memory, the kernel immediately reclaims disk cache pages. Therefore MemAvailable represents true allocatable memory without swapping.
  2. CLOSE-WAIT socket leaks: CLOSE-WAIT means the remote peer sent a TCP FIN packet and the OS kernel acknowledged it, waiting for the local application to call close() on its socket descriptor. A buildup of CLOSE-WAIT means your application code is leaking connections (e.g., unclosed HTTP client sessions or database connections).

Self-check

Run python3 -m unittest phase0-foundations/01-linux-and-shell/03-shell-pipelines-and-diagnostics/test_exercises.py -v until green, then record it here. Saved to progress/rungs.json; commit it.

Confidence:

Completion edits PROGRESS.md; commit it with the lesson.