Files
openairinterface5g/ci-scripts/yaml_files/5g_rfsimulator/plots.py
T
2025-10-19 20:03:22 +09:00

145 lines
4.2 KiB
Python

#!/usr/bin/env python3
"""
UE Count Plotter
----------------
Parses log lines like:
[2025-09-08 21:33:40.789676] UE RNTI=44512 ...
Groups entries into 1-second slots (by wall-clock time) and plots the number of
unique UEs (by RNTI) per slot for the entire file.
Usage:
python ue_count_plot.py /path/to/log.txt [--save output.png] [--no-show]
Notes:
- Uses matplotlib for a single-line plot.
- Does not set any colors or styles.
- For very large logs, this streams line-by-line with O(N) memory in the number
of distinct seconds encountered (each holds a set of RNTIs).
"""
import argparse
import datetime as dt
import re
import sys
from collections import defaultdict, OrderedDict
from typing import Dict, Set, Iterable, Tuple
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
TIMESTAMP_RE = re.compile(r"\[(?P<ts>\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}(?:\.\d{1,6})?)\]")
RNTI_RE = re.compile(r"\bRNTI=(?P<rnti>\d+)\b")
def parse_lines(lines: Iterable[str]) -> Dict[dt.datetime, Set[int]]:
"""
Parse lines and bucket unique RNTIs per second.
Returns:
dict mapping second (datetime truncated to seconds) -> set of RNTIs
"""
buckets: Dict[dt.datetime, Set[int]] = defaultdict(set)
for line in lines:
ts_m = TIMESTAMP_RE.search(line)
rnti_m = RNTI_RE.search(line)
if not ts_m or not rnti_m:
# Skip lines that don't fit the schema
continue
ts_str = ts_m.group("ts")
# Parse timestamp with or without fractional seconds
# Accept both "%Y-%m-%d %H:%M:%S" and with "%f"
if "." in ts_str:
ts = dt.datetime.strptime(ts_str, "%Y-%m-%d %H:%M:%S.%f")
else:
ts = dt.datetime.strptime(ts_str, "%Y-%m-%d %H:%M:%S")
# Truncate to whole-second wall time
ts_minute = ts.replace(second=0, microsecond=0)
rnti = int(rnti_m.group("rnti"))
buckets[ts_minute].add(rnti)
return buckets
def sort_and_fill(buckets: Dict[dt.datetime, Set[int]]) -> Tuple[list, list]:
"""
Sorts buckets by time and fills missing seconds with zero counts.
Returns:
times (list[datetime]), counts (list[int])
"""
if not buckets:
return [], []
# Sort keys
sorted_times = sorted(buckets.keys())
start = sorted_times[0]
end = sorted_times[-1]
all_times = []
counts = []
cur = start
one_sec = dt.timedelta(minutes=1)
while cur <= end:
all_times.append(cur)
counts.append(len(buckets.get(cur, set())))
cur += one_sec
return all_times, counts
def plot_counts(times: list, counts: list, title: str = "Unique UEs per minute"):
"""
Render a simple matplotlib line plot (no specific colors/styles set).
"""
fig, ax = plt.subplots(figsize=(10, 4))
ax.plot(times, counts, linestyle="-")
ax.set_title(title)
ax.set_xlabel("Time (s)")
ax.set_ylabel("Unique UEs")
ax.xaxis.set_major_formatter(mdates.DateFormatter("%H:%M:%S"))
fig.autofmt_xdate()
plt.tight_layout()
return fig, ax
def main():
parser = argparse.ArgumentParser(description="Plot unique UEs (by RNTI) per minute from a log file.")
parser.add_argument("--save", help="Optional path to save the plot image (e.g., output.png)")
parser.add_argument("--no-show", action="store_true", help="Do not display the plot window")
args = parser.parse_args()
# Read lines
try:
lines = open("artifacts/mac_stats_20250909_231855.log", "r", encoding="utf-8")
except OSError as e:
print(f"Error opening {args.logfile}: {e}", file=sys.stderr)
sys.exit(1)
buckets = parse_lines(lines)
if lines is not sys.stdin:
lines.close()
times, counts = sort_and_fill(buckets)
if not times:
print("No valid entries found to plot.", file=sys.stderr)
sys.exit(2)
fig, _ = plot_counts(times, counts)
if args.save:
try:
fig.savefig(args.save, dpi=150, bbox_inches="tight")
print(f"Saved plot to: {args.save}")
except Exception as e:
print(f"Failed to save figure: {e}", file=sys.stderr)
if not args.no_show:
plt.show()
if __name__ == "__main__":
main()