Skip to content

Commit 6656880

Browse files
authored
I-ALiRT - Update realtime packet ingest plots (#2607)
1 parent bca1f0a commit 6656880

3 files changed

Lines changed: 447 additions & 307 deletions

File tree

Lines changed: 57 additions & 164 deletions
Original file line numberDiff line numberDiff line change
@@ -1,151 +1,78 @@
1-
"""Packet ingest and tcp connection times for each station."""
1+
"""Packet ingest times and rates for each station."""
22

33
import logging
44
from datetime import datetime, timedelta, timezone
55
from typing import Any
66

7-
from imap_processing.ialirt.constants import STATIONS
8-
97
logger = logging.getLogger(__name__)
108

9+
STATIONS = ["Kiel"]
10+
1111

12-
def find_tcp_connections( # noqa: PLR0912
13-
start_file_creation: datetime,
14-
end_file_creation: datetime,
15-
lines: list,
16-
realtime_summary: dict,
17-
) -> dict:
12+
def packets_created(start_file_creation: datetime, lines: list) -> dict:
1813
"""
19-
Find tcp connection time ranges for ground station from log lines.
14+
Find timestamps and rates when packets were ingested based on log lines.
2015
2116
Parameters
2217
----------
2318
start_file_creation : datetime
2419
File creation time of last file minus 48 hrs.
25-
end_file_creation : datetime
26-
File creation time of last file.
2720
lines : list
2821
All lines of log files.
29-
realtime_summary : dict
30-
Input dictionary containing ingest parameters.
3122
3223
Returns
3324
-------
34-
realtime_summary : dict
35-
Output dictionary with tcp connection info.
25+
station_dict : dict
26+
Timestamps and rates when packets were ingested.
3627
"""
37-
current_starts: dict[str, datetime | None] = {}
38-
partners_opened = set()
28+
station_dict: dict[str, dict[str, list[Any]]] = {
29+
station: {"last_data_received": [], "rate_kbps": []}
30+
for station in list(STATIONS)
31+
}
32+
33+
station_year: dict[str, int] = {
34+
station: start_file_creation.year for station in station_dict
35+
}
36+
prev_doy: dict[str, int | None] = {station: None for station in station_dict}
3937

4038
for line in lines:
41-
# Note if this line appears.
42-
if "Opened raw record file" in line:
43-
station = line.split("Opened raw record file for ")[1].split(
44-
" antenna_partner"
45-
)[0]
46-
partners_opened.add(station)
47-
48-
if "antenna partner connection is" not in line:
49-
continue
50-
51-
timestamp_str = line.split(" ")[0]
52-
msg = " ".join(line.split(" ")[1:])
53-
station = msg.split(" antenna")[0]
54-
55-
if station not in realtime_summary["connection_times"]:
56-
realtime_summary["connection_times"][station] = []
57-
if station not in realtime_summary["stations"]:
58-
realtime_summary["stations"].append(station)
59-
60-
timestamp = datetime.strptime(timestamp_str, "%Y/%j-%H:%M:%S.%f")
61-
62-
if f"{station} antenna partner connection is up." in line:
63-
current_starts[station] = timestamp
64-
65-
elif f"{station} antenna partner connection is down!" in line:
66-
start = current_starts.get(station)
67-
if start is not None:
68-
realtime_summary["connection_times"][station].append(
69-
{
70-
"start": datetime.isoformat(start),
71-
"end": datetime.isoformat(timestamp),
72-
}
39+
# If line begins with a digit and the station is present.
40+
if line.split()[0].isdigit() and line.split()[1] in STATIONS:
41+
# Get bps rate.
42+
rate = float(line.split()[-1])
43+
# Get last data received.
44+
data_last_received = line.split()[2]
45+
# Get day of year.
46+
doy = int(data_last_received[:3])
47+
# Get station.
48+
station = line.split()[1]
49+
50+
# Handle end of year rollover
51+
prev = prev_doy[station]
52+
53+
if prev is not None and doy < prev:
54+
station_year[station] += 1
55+
56+
prev_doy[station] = doy
57+
58+
dt = (
59+
datetime.strptime(
60+
f"{station_year[station]}/{data_last_received}",
61+
"%Y/%j-%H:%M:%S",
7362
)
74-
current_starts[station] = None
75-
else:
76-
# No matching "up"
77-
realtime_summary["connection_times"][station].append(
78-
{
79-
"start": datetime.isoformat(start_file_creation),
80-
"end": datetime.isoformat(timestamp),
81-
}
82-
)
83-
current_starts[station] = None
84-
85-
# Handle hanging "up" at the end of file
86-
for station, start in current_starts.items():
87-
if start is not None:
88-
realtime_summary["connection_times"][station].append(
89-
{
90-
"start": datetime.isoformat(start),
91-
"end": datetime.isoformat(end_file_creation),
92-
}
93-
)
94-
95-
# Handle stations with only "Opened raw record file" (no up/down)
96-
for station in partners_opened:
97-
if not realtime_summary["connection_times"][station]:
98-
realtime_summary["connection_times"][station].append(
99-
{
100-
"start": datetime.isoformat(start_file_creation),
101-
"end": datetime.isoformat(end_file_creation),
102-
}
63+
.replace(tzinfo=timezone.utc)
64+
.isoformat()
65+
.replace("+00:00", "Z")
10366
)
67+
station_dict[station]["last_data_received"].append(dt)
68+
station_dict[station]["rate_kbps"].append(rate)
10469

105-
# Filter out connection windows that are completely outside the time window
106-
for station in realtime_summary["connection_times"]:
107-
realtime_summary["connection_times"][station] = [
108-
window
109-
for window in realtime_summary["connection_times"][station]
110-
if datetime.fromisoformat(window["end"]) >= start_file_creation
111-
and datetime.fromisoformat(window["start"]) <= end_file_creation
112-
]
113-
114-
return realtime_summary
115-
116-
117-
def packets_created(start_file_creation: datetime, lines: list) -> list:
118-
"""
119-
Find timestamps when packets were created based on log lines.
120-
121-
Parameters
122-
----------
123-
start_file_creation : datetime
124-
File creation time of last file minus 48 hrs.
125-
lines : list
126-
All lines of log files.
127-
128-
Returns
129-
-------
130-
packet_times : list
131-
List of datetime objects when packets were created.
132-
"""
133-
packet_times = []
134-
135-
for line in lines:
136-
if "Renamed iois_1_packets" in line:
137-
timestamp_str = line.split(" ")[0]
138-
timestamp = datetime.strptime(timestamp_str, "%Y/%j-%H:%M:%S.%f")
139-
# Possible that data extends further than 48 hrs in the past.
140-
if timestamp >= start_file_creation:
141-
packet_times.append(timestamp)
142-
143-
return packet_times
70+
return station_dict
14471

14572

14673
def format_ingest_data(last_filename: str, log_lines: list) -> dict:
14774
"""
148-
Format TCP connection and packet ingest data from multiple log files.
75+
Format packet ingest times and rates from log file.
14976
15077
Parameters
15178
----------
@@ -157,8 +84,7 @@ def format_ingest_data(last_filename: str, log_lines: list) -> dict:
15784
Returns
15885
-------
15986
realtime_summary : dict
160-
Structured output with TCP connection windows per station
161-
and global packet ingest timestamps.
87+
Structured output with packet receipt info per station.
16288
16389
Notes
16490
-----
@@ -167,71 +93,38 @@ def format_ingest_data(last_filename: str, log_lines: list) -> dict:
16793
"summary": "I-ALiRT Real-time Ingest Summary",
16894
"generated": "2025-08-07T21:36:09Z",
16995
"time_format": "UTC (ISOC)",
170-
"stations": [
171-
"Kiel"
172-
],
17396
"time_range": [
174-
"2025-07-30T23:00:00",
175-
"2025-07-31T02:00:00"
97+
"2025-01-21T09:50:58Z",
98+
"2025-01-21T09:55:58Z"
17699
],
177-
"packet_ingest": [
178-
"2025-07-31T00:00:00",
179-
"2025-07-31T02:01:00"
180-
],
181-
"connection_times": {
182-
"Kiel": [
183-
{
184-
"start": "2025-07-30T23:00:00",
185-
"end": "2025-07-31T00:15:00"
186-
},
187-
{
188-
"start": "2025-07-31T02:00:00",
189-
"end": "2025-07-31T02:00:00"
190-
}
191-
]
192-
}
100+
"Kiel": {"last_data_received": ["2025-01-21T09:50:58Z", "2025-01-21T09:51:58Z"],
101+
"rate_kbps": [2.0, 2.0]}
193102
}
194-
195-
where time_range is the overall time range of the data,
196-
packet_ingest contains timestamps when packets were finalized,
197-
and tcp contains connection windows for each station.
198103
"""
199104
# File creation time.
200105
last_timestamp_str = last_filename.split(".")[2]
201106
last_timestamp_str = last_timestamp_str.replace("_", ":")
202107
end_of_time = datetime.strptime(last_timestamp_str, "%Y-%jT%H:%M:%S")
203108

204-
# File creation time of last file minus 48 hrs.
109+
# File is created every 5 minutes.
205110
start_of_time = datetime.strptime(last_timestamp_str, "%Y-%jT%H:%M:%S") - timedelta(
206-
hours=48
111+
minutes=5
207112
)
208113

114+
# Parse file.
115+
station_dict = packets_created(start_of_time, log_lines)
116+
209117
realtime_summary: dict[str, Any] = {
210118
"summary": "I-ALiRT Real-time Ingest Summary",
211119
"generated": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"),
212120
"time_format": "UTC (ISOC)",
213-
"stations": list(STATIONS),
214121
"time_range": [
215122
start_of_time.isoformat(),
216123
end_of_time.isoformat(),
217124
], # Overall time range of the data
218-
"packet_ingest": [], # Global packet ingest times
219-
"connection_times": {
220-
station: [] for station in list(STATIONS)
221-
}, # Per-station TCP connection windows
125+
**station_dict,
222126
}
223127

224-
# TCP connection data for each station
225-
realtime_summary = find_tcp_connections(
226-
start_of_time, end_of_time, log_lines, realtime_summary
227-
)
228-
229-
# Global packet ingest timestamps
230-
packet_times = packets_created(start_of_time, log_lines)
231-
realtime_summary["packet_ingest"] = [
232-
pkt_time.isoformat() for pkt_time in packet_times
233-
]
234-
235128
logger.info(f"Created ingest files for {realtime_summary['time_range']}")
236129

237130
return realtime_summary

0 commit comments

Comments
 (0)