Find full disks in a metrics CSV with bad rows
Problem statement
A monitoring export lists disk usage per host and mount point in GB. Print every mount that is at least 85% full, fullest first, as host mount pct% with one decimal. Exit 1 if anything is over the threshold, so the script can drive a cron alert.
Some rows are bad, as they always are:
n/awhere the agent did not report.- A size of 0 from a mount that was being detached.
- A row cut short.
Skip each bad row and report it on stderr with its line number. The threshold is an optional second argument.
disks.csv
host,mount,used_gb,size_gbweb-01,/,41.2,50web-01,/var/log,9.1,10db-01,/,20,50db-01,/data,930,1000cache-01,/,n/a,50batch-01,/,12,0batch-01,/scratch,45,50web-02,/Examples
Example 1
Input: python solution.py disks.csv
Output: db-01 /data 93.0%
web-01 /var/log 91.0%
batch-01 /scratch 90.0%
Explanation: stderr shows line 6: not a number, line 7: impossible values used=12.0 size=0.0 and line 9: expected 4 fields, got 2. web-01 / is 82.4%, below 85.
Hints
Approach
Optimal
Validate each row in the order that the checks can fail.
- Check the header exactly. If the export format changes, fail loudly instead of reading the wrong column.
- Skip empty rows. Reject rows without exactly 4 fields.
- Convert with
floatand reject anything that is not a number. Then reject values that are not finite, zero or negative sizes, and negative usage. - Compute the percentage and keep rows at or above the threshold. Sort by
(-pct, host, mount)so ties are deterministic.
Only alerting rows are kept (a of them). Problems are collected and printed after the results, so stdout stays clean for another program to read.
O(n + a log a)Space O(a)import csvimport mathimport sys def full_disks(f, threshold=85.0): reader = csv.reader(f) header = next(reader, None) if header != ["host", "mount", "used_gb", "size_gb"]: raise ValueError(f"unexpected header: {header}") alerts, problems = [], [] for row in reader: line = reader.line_num if not row: continue if len(row) != 4: problems.append(f"line {line}: expected 4 fields, got {len(row)}") continue host, mount, used, size = row try: used, size = float(used), float(size) except ValueError: problems.append(f"line {line}: not a number") continue if not (math.isfinite(used) and math.isfinite(size)) or size <= 0 or used < 0: problems.append(f"line {line}: impossible values used={used} size={size}") continue pct = used / size * 100 if pct >= threshold: alerts.append((pct, host, mount)) alerts.sort(key=lambda a: (-a[0], a[1], a[2])) return alerts, problems def main(path, threshold=85.0): with open(path, newline="", encoding="utf-8") as f: try: alerts, problems = full_disks(f, threshold) except ValueError as e: sys.exit(f"{path}: {e}") for pct, host, mount in alerts: print(f"{host} {mount} {pct:.1f}%") for p in problems: print(p, file=sys.stderr) return 1 if alerts else 0 if __name__ == "__main__": path = sys.argv[1] if len(sys.argv) > 1 else "disks.csv" limit = float(sys.argv[2]) if len(sys.argv) > 2 else 85.0 sys.exit(main(path, limit))Follow-up questions
- Also warn about mounts that grew more than 10 percentage points since yesterday's CSV.
- Some rows give sizes as
512Mor1.5T. Parse units. - Send one alert per host listing all its full mounts, instead of one line per mount.
Frequently asked questions
It passes validation and shows as over 100%. That can be real: some filesystems report reserved blocks oddly, or the two numbers were sampled at different moments. Whether to flag it as bad data or as an alert is a judgement call. Say which you chose; reporting it is safer than hiding it.
Disk-full is one of the most common causes of outages, and exports from monitoring tools are messy. The interviewer wants to see that one bad row neither crashes the script nor gets silently counted as 0%, and that errors say which line to look at.