1- """Packet ingest and tcp connection times for each station."""
1+ """Packet ingest times and rates for each station."""
22
33import logging
44from datetime import datetime , timedelta , timezone
55from typing import Any
66
7- from imap_processing .ialirt .constants import STATIONS
8-
97logger = 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
14673def 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