scripts/qlog_parser.py (884 lines of code) (raw):
import argparse
import os
import sys
import json
import re
import datetime
connectivity_event_list_ = ["server_listening", "connection_started", "connection_closed",
"connection_id_updated", "spin_bit_updated", "connection_state_updated",
"path_assigned", "mtu_updated"]
quic_event_list_ = ["version_information", "alpn_information", "parameters_set",
"parameters_restored", "packet_sent", "packet_received",
"packets_acked", "datagrams_sent", "datagrams_received",
"datagram_dropped", "stream_state_updated", "frames_processed",
"stream_data_moved", "datagram_data_moved", "migration_state_updated",
"packet_dropped", "packet_buffered"]
security_event_list_ = ["key_updated" , "key_discarded"]
recovery_event_list_ = ["rec_parameters_set", "rec_metrics_updated", "congestion_state_updated",
"loss_timer_updated", "packet_lost", "marked_for_retransmit",
"ecn_state_updated"]
http_event_list_ = ["http_parameters_set", "http_parameters_restored", "http_stream_type_set",
"http_frame_created", "http_frame_parsed", "push_resolved", "http_setting_parsed"]
qpack_event_list_ = ["qpack_state_updated", "qpack_stream_state_updated", "dynamic_table_updated","headers_encoded",
"headers_decoded", "instruction_created", "instruction_parsed"]
packet_type_ = {0: "initial", 1: "0RTT", 2: "handshake" ,
3: "retry", 4: "short_header", 5: "version_negotiation", 6: "unknown"}
packet_number_namespace_ = {0: "initial", 1: "handshake", 2: "application data", 3: "negotiation"}
packet_type_ = {0: "initial", 1: "0RTT", 2: "handshake" ,
3: "retry", 4: "short_header", 5: "version_negotiation", 6: "unknown"}
packet_number_namespace_ = {0: "initial", 1: "handshake", 2: "application data", 3: "negotiation"}
send_stream_states_ = ["ready", "send", "data_sent", "reset_sent", "reset_received"]
recv_stream_states_ = ["receive", "size_known", "data_read", "reset_read", "reset_received", "reset_received"]
frame_type_ = ["PADDING", "PING", "ACK", "RESET_STREAM", "STOP_SENDING", "CRYPTO", "NEW_TOKEN",
"STREAM", "MAX_DATA", "MAX_STREAM_DATA", "MAX_STREAMS", "DATA_BLOCKED", "STREAM_DATA_BLOCKED", "STREAMS_BLOCKED",
"NEW_CONNECTION_ID", "RETIRE_CONNECTION_ID", "PATH_CHALLENGE", "PATH_RESPONSE", "CONNECTION_CLOSE", "HANDSHAKE_DONE",
"ACK_MP", "PATH_ABANDON", "PATH_STATUS", "DATAGRAM", "Extension"]
h3_stream_type_ = ["control", "push", "qpack_encode", "qpack_decode", "request", "bytestream", "unknown"]
h3_frame_type_ = ["data", "headers", "bidi_stream_type", "cancel_push", "settings", "push_promise", "goaway", "max_push_id", "unknown"]
last_scid_ = "initcid"
def get_path_id(line):
segments = line.split('|')
segments = [segment.strip() for segment in segments]
assert(len(segments) > 1)
for i in range(1,len(segments)):
item = segments[i].split(':')
item = [i.strip() for i in item]
if(len(item) != 2):
continue
if item[0] == "path_id":
return int(item[1])
raise ValueError("no path_id")
def parse_line(line):
event = {}
event_scid = ""
pattern = r'\[(.*?)\]'
matches = re.findall(pattern, line)
matches = [segment.strip() for segment in matches]
if(len(matches) != 2):
return None, event_scid
time_str = matches[0]
dt_object = datetime.datetime.strptime(time_str, "%Y/%m/%d %H:%M:%S %f")
event["time"] = dt_object.timestamp() * 1000
event["name"] = matches[1]
if event["name"] in connectivity_event_list_:
event["name"] = "connectivity:" + event["name"]
elif event["name"] in quic_event_list_:
event["name"] = "quic:" + event["name"]
elif event["name"] in security_event_list_:
event["name"] = "security:" + event["name"]
elif event["name"] in recovery_event_list_:
if event["name"] == "rec_parameters_set" or event["name"] == "rec_metrics_updated":
event["name"] = event["name"][4:]
event["name"] = "recovery:" + event["name"]
elif event["name"] in http_event_list_:
if event["name"] == "push_resolved":
event["name"] = "h3:push_resolved"
else:
event["name"] = "h3:" + event["name"][5:]
else:
return None, event_scid
if matches[1] in ["packet_sent", "packet_received", "mtu_updated", "datagrams_sent", "datagrams_received", "packets_acked"]:
event["path"] = get_path_id(line)
if matches[1] == "packet_sent" or matches[1] == "packet_received":
event["data"], event_scid = parse_packet_sent_and_recv(line)
return event, event_scid
if matches[1] == "datagrams_sent" or matches[1] == "datagrams_received":
event["data"], event_scid = parse_datagrams_sent_or_recv(line)
return event, event_scid
if matches[1] in ["server_listening", "connection_started", "connection_close", "connection_state_updated",
"path_assigned", "mtu_updated", "alpn_information","parameters_set", "packet_buffered", "packets_acked", "stream_state_updated",
"frames_processed", "stream_data_moved", "rec_parameters_set", "rec_metrics_updated", "congestion_state_updated",
"packet_lost", "http_parameters_set", "http_frame_created", "http_frame_parse"]:
function_name = "parse_" + matches[1]
func = globals()[function_name]
(event["data"], event_scid) = func(line)
return event, event_scid
else:
return None, None
def endpoint_events_extraction(file_name, vantagepoint):
assert(vantagepoint == "server" or vantagepoint == "client")
conn_events = {
"vantage_point" : {"name": vantagepoint + "-view",
"type": vantagepoint
},
"title": "xquic qlog",
"description": "",
"common_fields": { "ODCID": "", "time_format": "absolute" },
"events": []
}
count = 0
last_scid_ = "initcid"
traces_log = {}
with open(file_name, 'r',encoding='utf-8', errors='ignore') as file:
for line in file:
event, scid = parse_line(line)
if (event is None):
continue
if (event is not None) and (scid != last_scid_):
if count > 0:
if(scid in traces_log):
traces_log[scid]['events'] += conn_events['events']
else:
traces_log[last_scid_] = conn_events
last_scid_ = scid
conn_events = {
"title": "xquic-qlog json: " + vantagepoint,
"description": "",
"common_fields": { "ODCID": scid, "time_format": "absolute" },
"vantage_point" : {"name": vantagepoint + "-view",
"type": vantagepoint
},
"events": []
}
count = 1
conn_events["events"].append(event)
else:
count += 1
conn_events["events"].append(event)
if(count > 1):
scid = conn_events["common_fields"]["ODCID"]
if(scid in traces_log):
traces_log[scid]['events'] += conn_events['events']
else:
traces_log[scid] = conn_events
return list(traces_log.values())
def parse_packet_sent_and_recv(line):
data = {
"header": {
"packet_number": "unknown",
"packet_type": "unknown"
},
"raw": {
"length": 1280
}
}
event_scid = "unknown"
segments = line.split('|')
segments = [segment.strip() for segment in segments]
assert(len(segments) > 1)
for i in range(1,len(segments)):
item = segments[i].split(':')
item = [i.strip() for i in item]
if(len(item) != 2):
continue
if item[0] == "scid":
event_scid = item[1]
elif(item[0] == "pkt_type"):
data["header"]["packet_type"] = item[1]
elif(item[0] == "pkt_num"):
data["header"]["packet_number"] = int(item[1])
elif(item[0] == "size"):
data["raw"]["length"] = int(item[1])
return (data, event_scid)
def parse_server_listening(line):
data = {
}
event_scid = "unknown"
segments = line.split('|')
segments = [segment.strip() for segment in segments]
assert(len(segments) > 1)
for i in range(1,len(segments)):
item = segments[i].split(':')
item = [i.strip() for i in item]
if(len(item) != 2):
continue
if item[0] == "scid":
event_scid = item[1]
else:
if item[0].startswith("port"):
data[item[0]] = int(item[1])
else:
data[item[0]] = item[1]
return (data, event_scid)
def parse_connection_started(line):
# [2024/05/14 11:34:11 641605] [connection_started] |scid:b59e52a51185db48|xqc_engine_packet_process|local|src_ip:127.0.0.1|src_port:35148|
data = {
"src_ip": "127.0.0.1",
"dst_ip": "127.0.0.1",
"src_port": 0,
"dst_port": 0
}
event_scid = "unknown"
segments = line.split('|')
segments = [segment.strip() for segment in segments]
assert(len(segments) > 1)
for i in range(1,len(segments)):
item = segments[i].split(':')
item = [i.strip() for i in item]
if(len(item) != 2):
continue
if item[0] == "scid":
event_scid = item[1]
elif(item[0] == "src_ip"):
data["src_ip"] = item[1]
elif(item[0] == "dst_ip"):
data["dst_ip"] = item[1]
elif(item[0] == "src_port"):
data["src_port"] = int(item[1])
elif(item[0] == "dst_port"):
data["dst_port"] = int(item[1])
return (data, event_scid)
def parse_connection_closed(line):
# [2024/05/14 11:34:26 672332] [connection_closed] |scid:007cc254f81be8e78d765a2e63339fc99a66320d|xqc_conn_destroy|err_code:0|
data = {
"connection_code": 0
}
event_scid = "unknown"
segments = line.split('|')
segments = [segment.strip() for segment in segments]
assert(len(segments) > 1)
for i in range(1,len(segments)):
item = segments[i].split(':')
item = [i.strip() for i in item]
if(len(item) != 2):
continue
if item[0] == "scid":
event_scid = item[1]
elif item[0] == "err_code":
data["connection_code"] = int(item[1])
break
return (data, event_scid)
def parse_connection_state_updated(line):
data = {
"new": "unknow"
}
event_scid = "unknown"
segments = line.split('|')
segments = [segment.strip() for segment in segments]
assert(len(segments) > 1)
for i in range(1,len(segments)):
item = segments[i].split(':')
item = [i.strip() for i in item]
if(len(item) != 2):
continue
if item[0] == "scid":
event_scid = item[1]
elif item[0] == "new":
data["new"] = item[1]
break
return (data, event_scid)
def parse_path_assigned(line):
data = {
"path_id": "unknow"
}
event_scid = "unknown"
segments = line.split('|')
segments = [segment.strip() for segment in segments]
assert(len(segments) > 1)
for i in range(1,len(segments)):
item = segments[i].split(':')
item = [i.strip() for i in item]
if(len(item) != 2):
continue
if item[0] == "scid":
event_scid = item[1]
elif item[0] == "path_id":
data["path_id"] = item[1]
break
return (data, event_scid)
def parse_mtu_updated(line):
data = {
"new": 0,
"done": False
}
event_scid = "unknown"
segments = line.split('|')
segments = [segment.strip() for segment in segments]
assert(len(segments) > 1)
for i in range(1,len(segments)):
item = segments[i].split(':')
item = [i.strip() for i in item]
if(len(item) != 2):
continue
if item[0] == "scid":
event_scid = item[1]
elif item[0] == "new":
data["path_id"] = int(item[1])
elif item[0] == "done":
data["done"] = False
if int(item[1]):
data["done"] = True
return (data, event_scid)
def parse_alpn_information(line):
#[2024/05/14 11:34:11 548027] [alpn_information]
#|scid:007cc254f81be8e78d765a2e63339fc99a66320d|xqc_ssl_alpn_select_cb|client_alpn:h3 |server_alpn:h3 h3-29 h3-ext transport |selected_alpn:h3|
data = {
"server_alpns": [],
"client_alpns": [],
"chosen_alpn": {"string_value" : "unknown"}
}
event_scid = "unknown"
segments = line.split('|')
segments = [segment.strip() for segment in segments]
assert(len(segments) > 1)
for i in range(1,len(segments)):
item = segments[i].split(':')
item = [i.strip() for i in item]
if(len(item) != 2):
continue
if item[0] == "scid":
event_scid = item[1]
elif item[0] == "client_alpn":
alpns = item[1].split(' ')
for alpn in alpns:
temp = {"string_value" : alpn}
data["client_alpns"].append(temp)
elif item[0] == "server_alpn":
alpns = item[1].split(' ')
for alpn in alpns:
temp = {"string_value" : alpn}
data["server_alpns"].append(temp)
elif item[0] == "selected_alpn":
data["chosen_alpn"]["string_value"] = item[1]
return (data, event_scid)
def parse_parameters_set(line):
# [2024/05/14 11:34:11 547473] [tra_parameters_set] |scid:007cc254f81be8e78d765a2e63339fc99a66320d|
# xqc_conn_create|local|migration:1|max_idle_timeout:120000|max_udp_payload_size:1500|active_connection_id_limit:8|max_data:0|
data = {
"max_idle_timeout": 0,
"max_udp_payload_size": 0,
"active_connection_id_limit": 0
}
event_scid = "unknown"
segments = line.split('|')
segments = [segment.strip() for segment in segments]
assert(len(segments) > 1)
for i in range(1,len(segments)):
item = segments[i].split(':')
item = [i.strip() for i in item]
if(len(item) != 2):
continue
if item[0] == "scid":
event_scid = item[1]
elif item[0] in data.keys:
data[item[0]] = int(item[1])
return (data, event_scid)
def parse_packet_buffered(line):
# [2024/05/14 11:34:11 693524] [packet_buffered] |scid:007cc254f81be8e78d765a2e63339fc99a66320d|
# xqc_conn_buff_undecrypt_packet_in|pkt_pns:2|pkt_type:4|len:1216|
data = {
"header": {
"packet_number": "unknown",
"packet_type": "unknown"
},
"raw": {
"length": 1280
}
}
event_scid = "unknown"
segments = line.split('|')
segments = [segment.strip() for segment in segments]
assert(len(segments) > 1)
for i in range(1,len(segments)):
item = segments[i].split(':')
item = [i.strip() for i in item]
if(len(item) != 2):
continue
if item[0] == "scid":
event_scid = item[1]
elif(item[0] == "pkt_type"):
data["header"]["packet_type"] = packet_type_[int(item[1])]
elif(item[0] == "pkt_num"):
data["header"]["packet_number"] = int(item[1])
elif(item[0] == "len"):
data["raw"]["length"] = int(item[1])
return (data, event_scid)
def parse_packets_acked(line):
# [2024/05/14 11:34:11 642140] [packets_acked] |scid:007cc254f81be8e78d765a2e63339fc99a66320d|
# xqc_process_ack_frame|pkt_space:0|high:0|low:0|path_id:0|
data = {
"packet_number_space": "unknown",
"packet_numbers": []
}
event_scid = "unknown"
low = 0
high = -1
segments = line.split('|')
segments = [segment.strip() for segment in segments]
assert(len(segments) > 1)
for i in range(1,len(segments)):
item = segments[i].split(':')
item = [i.strip() for i in item]
if(len(item) != 2):
continue
if item[0] == "scid":
event_scid = item[1]
elif(item[0] == "pkt_space"):
data["packet_number_space"] = packet_number_namespace_[int(item[1])]
elif(item[0] == "low"):
low = int(item[1])
elif(item[0] == "high"):
high = int(item[1])
if(low > high):
return (None, event_scid)
for i in range(low, high + 1):
data["packet_numbers"].append(i)
return (data, event_scid)
def parse_datagrams_sent_or_recv(line):
# [2024/05/14 11:34:11 552331] [datagrams_sent] |scid:007cc254f81be8e78d765a2e63339fc99a66320d|xqc_send|size:1216|
data = {
"raw": {
"length": 1280
}
}
event_scid = "unknown"
segments = line.split('|')
segments = [segment.strip() for segment in segments]
assert(len(segments) > 1)
for i in range(1,len(segments)):
item = segments[i].split(':')
item = [i.strip() for i in item]
if(len(item) != 2):
continue
if item[0] == "scid":
event_scid = item[1]
elif(item[0] == "size"):
data["raw"]["length"] = int(item[1])
return (data, event_scid)
def parse_stream_state_updated(line):
# [2024/05/14 11:34:11 552613] [stream_state_updated]
# |scid:007cc254f81be8e78d765a2e63339fc99a66320d|xqc_stream_send_state_update|stream_id:3|send_stream|old:0|new:1|
data = {
"StreamType" : "bidirectional",
"new": "unknown",
"stream_side": "sending"
}
event_scid = "unknown"
segments = line.split('|')
segments = [segment.strip() for segment in segments]
assert(len(segments) > 1)
for i in range(1,len(segments)):
item = segments[i].split(':')
item = [i.strip() for i in item]
if(len(item) == 1):
if item[0] == "send_stream":
data["stream_side"] = "sending"
if item[0] == "recv_stream":
data["stream_side"] = "receiving"
if len(item) != 2:
continue
if item[0] == "scid":
event_scid = item[1]
if item[0] == "new":
state = int(item[1])
if data["stream_side"] == "sending":
data["new"] = send_stream_states_[state]
else:
data["new"] = recv_stream_states_[state]
return (data, event_scid)
def parse_frames_processed(line):
data = {
"frames":[{"frame_type" : "unknow"}]
}
event_scid = "unknown"
segments = line.split('|')
segments = [segment.strip() for segment in segments]
assert(len(segments) > 1)
frame_type = -1
for i in range(1,len(segments)):
item = segments[i].split(':')
item = [i.strip() for i in item]
if len(item) == 2 and item[0] == "scid":
event_scid = item[1]
if(len(item) == 2 and item[0] == "type"):
frame_type = int(item[1])
break
data["frames"][0]["frame_type"] = frame_type_[frame_type].lower()
type_str = frame_type_[frame_type]
if type_str == "PADDING":
for i in range(1,len(segments)):
item = segments[i].split(':')
item = [i.strip() for i in item]
if(len(item) != 2):
continue
if item[0] == "length":
data["frames"][0]["payload_length"] = int(item[1])
return (data, event_scid)
elif type_str == "PING":
return (data, event_scid)
elif type_str == "ACK":
for i in range(1,len(segments)):
item = segments[i].split(':')
item = [i.strip() for i in item]
if(len(item) != 2):
continue
if item[0] == "ack_range":
s_clean = item[1].strip("{}")
pairs = s_clean.split(", ")
result = [list(map(int, pair.split(" - "))) for pair in pairs]
for i in range(len(result)):
if result[i][0] == result[i][1]:
result[i] = [result[i][0]]
data["frames"][0]["acked_ranges"] = result
return (data, event_scid)
elif type_str == "RESET_STREAM" or type_str == "STOP_SENDING":
for i in range(1,len(segments)):
item = segments[i].split(':')
item = [i.strip() for i in item]
if(len(item) != 2):
continue
if item[0] == "stream_id":
data["frames"][0]["stream_id"] = int(item[1])
elif item[0] == "err_code":
data["frames"][0]["error_code"] = int(item[1])
elif item[0] == "final_size":
data["frames"][0]["final_size"] = int(item[1])
return (data, event_scid)
elif type_str == "CRYPTO":
for i in range(1,len(segments)):
item = segments[i].split(':')
item = [i.strip() for i in item]
if(len(item) != 2):
continue
if item[0] == "offset":
data["frames"][0]["offset"] = int(item[1])
elif item[0] == "length":
data["frames"][0]["length"] = int(item[1])
return (data, event_scid)
elif type_str == "NEW_TOKEN":
for i in range(1,len(segments)):
item = segments[i].split(':')
item = [i.strip() for i in item]
if(len(item) != 2):
continue
if item[0] == "token":
data["frames"][0]["token"] = item[1]
return (data, event_scid)
elif type_str == "STREAM":
for i in range(1,len(segments)):
item = segments[i].split(':')
item = [i.strip() for i in item]
if(len(item) != 2):
continue
if item[0] == "data_length":
data["frames"][0]["length"] = int(item[1])
elif item[0] == "data_offset":
data["frames"][0]["offset"] = int(item[1])
elif item[0] == "fin":
data["frames"][0]["fin"] = False
if int(item[1]):
data["frames"][0]["offset"] = True
return (data, event_scid)
elif type_str == "MAX_DATA":
for i in range(1,len(segments)):
item = segments[i].split(':')
item = [i.strip() for i in item]
if(len(item) != 2):
continue
if item[0] == "max_data":
data["frames"][0]["maximum"] = int(item[1])
return (data, event_scid)
elif type_str == "MAX_STREAM_DATA":
for i in range(1,len(segments)):
item = segments[i].split(':')
item = [i.strip() for i in item]
if(len(item) != 2):
continue
if item[0] == "stream_id":
data["frames"][0]["stream_id"] = int(item[1])
elif item[0] == "max_stream_data":
data["frames"][0]["maximum"] = int(item[1])
return (data, event_scid)
elif type_str == "MAX_STREAMS":
for i in range(1,len(segments)):
item = segments[i].split(':')
item = [i.strip() for i in item]
if(len(item) != 2):
continue
if item[0] == "stream_type":
data["frames"][0]["stream_type"] = item[1]
elif item[0] == "maximum":
data["frames"][0]["maximum"] = int(item[1])
return (data, event_scid)
elif type_str == "DATA_BLOCKED":
for i in range(1,len(segments)):
item = segments[i].split(':')
item = [i.strip() for i in item]
if(len(item) != 2):
continue
if item[0] == "limit":
data["frames"][0]["limit"] = int(item[1])
return (data, event_scid)
elif type_str == "STREAM_DATA_BLOCKED":
for i in range(1,len(segments)):
item = segments[i].split(':')
item = [i.strip() for i in item]
if(len(item) != 2):
continue
if item[0] == "stream_id":
data["frames"][0]["stream_id"] = int(item[1])
elif item[0] == "limit":
data["frames"][0]["limit"] = int(item[1])
return (data, event_scid)
elif type_str == "STREAMS_BLOCKED":
for i in range(1,len(segments)):
item = segments[i].split(':')
item = [i.strip() for i in item]
if(len(item) != 2):
continue
if item[0] == "stream_type":
data["frames"][0]["stream_type"] = item[1]
elif item[0] == "limit":
data["frames"][0]["limit"] = int(item[1])
return (data, event_scid)
elif type_str == "NEW_CONNECTION_ID":
for i in range(1,len(segments)):
item = segments[i].split(':')
item = [i.strip() for i in item]
if(len(item) != 2):
continue
if item[0] in ["sequence_number", "retire_prior_to", "connection_id_length"]:
data["frames"][0][item[0]] = int(item[1])
elif item[0] == "connection_id":
data["frames"][0]["connection_id"] = item[1]
return (data, event_scid)
elif type_str == "CONNECTION_CLOSE":
for i in range(1,len(segments)):
item = segments[i].split(':')
item = [i.strip() for i in item]
if(len(item) != 2):
continue
if item[0] == "err_code":
data["frames"][0]["error_code"] = int(item[1])
return (data, event_scid)
else:
return (data, event_scid)
def parse_stream_data_moved(line):
# [2024/05/14 11:34:11 547904] [stream_data_moved] |scid:007cc254f81be8e78d765a2e63339fc99a66320d|xqc_stream_send|ret:-610|stream_id:3|stream_send_offset:0|pkt_type:SHORT_HEADER|buff_1rtt:0|send_data_size:1|offset:0|fin:0|
# stream_flag:17|conn:0000000002354F7C|conn_state:S_INIT|flag:TICKING TOKEN_OK UPPER_CONN_EXIST INIT_RECVD |from:application|to:transport|
data = {
"stream_id" : "unknown",
"offset": 0,
"length": 0,
"from": "unknown",
"to": "unknown"
}
event_scid = "unknown"
segments = line.split('|')
segments = [segment.strip() for segment in segments]
assert(len(segments) > 1)
for i in range(1,len(segments)):
item = segments[i].split(':')
item = [i.strip() for i in item]
if(len(item) == 1):
continue
if item[0] == "stream_id":
data["stream_id"] = int(item[1])
elif item[0] == "stream_send_offset":
data["offset"] = int(item[1])
elif item[0] in ["from", "to"]:
data[item[0]] = item[1]
elif item[0] == "send_data_size":
data["length"] = int(item[1])
elif item[0] == "scid":
event_scid = item[1]
return (data, event_scid)
def parse_rec_parameters_set(line):
data = {}
event_scid = "unknown"
segments = line.split('|')
segments = [segment.strip() for segment in segments]
assert(len(segments) > 1)
for i in range(1,len(segments)):
item = segments[i].split(':')
item = [i.strip() for i in item]
if(len(item) != 2):
continue
if item[0] == "scid":
event_scid = item[1]
else:
data[item[0]] = int(item[1])
return (data, event_scid)
def parse_rec_metrics_updated(line):
# [2024/05/14 11:34:11 642348] [rec_metrics_updated]
# |scid:007cc254f81be8e78d765a2e63339fc99a66320d|xqc_send_ctl_on_ack_received|
# cwnd:47152|inflight:1384|mode:0|applimit:0|pacing_rate:1477151|bw:13370|srtt:89748|latest_rtt:89748|ctl_rttvar:0|pto_count:89748|min_rtt:6|send:0|lost:0|tlp:29|recv:47152|
data = {}
event_scid = "unknown"
segments = line.split('|')
segments = [segment.strip() for segment in segments]
assert(len(segments) > 1)
for i in range(1,len(segments)):
item = segments[i].split(':')
item = [i.strip() for i in item]
if(len(item) != 2):
continue
if item[0] == "scid":
event_scid = item[1]
elif item[0] == "cwnd":
data["congestion_window"] = int(item[1])
elif item[0] == "inflight":
data["bytes_in_flight"] = int(item[1])
elif item[0] == "pacing_rate":
data["pacing_rate"] = int(item[1])
elif item[0] == "pto_count":
data["pto_count"] = int(item[1])
elif item[0] == "ctl_rttvar":
data["rtt_variance"] = int(item[1])
elif item[0] == "min_rtt":
data["min_rtt"] = int(item[1])
elif item[0] == "latest_rtt":
data["latest_rtt"] = int(item[1])
return (data, event_scid)
def parse_congestion_state_updated(line):
data = {"new" : "unknown"}
event_scid = "unknown"
segments = line.split('|')
segments = [segment.strip() for segment in segments]
assert(len(segments) > 1)
for i in range(1,len(segments)):
item = segments[i].split(':')
item = [i.strip() for i in item]
if(len(item) != 2):
continue
if item[0] == "scid":
event_scid = item[1]
elif item[0] == "new_state":
data["new"] = item[1]
return (data, event_scid)
def parse_packet_lost(line):
data = {
"header": {
"packet_number": "unknown",
"packet_type": "unknown"
}
}
event_scid = "unknown"
segments = line.split('|')
segments = [segment.strip() for segment in segments]
assert(len(segments) > 1)
for i in range(1,len(segments)):
item = segments[i].split(':')
item = [i.strip() for i in item]
if(len(item) != 2):
continue
if item[0] == "scid":
event_scid = item[1]
elif(item[0] == "pkt_type"):
data["header"]["packet_type"] = packet_type_[int(item[1])]
elif(item[0] == "pkt_num"):
data["header"]["packet_number"] = int(item[1])
return (data, event_scid)
def parse_http_parameters_set(line):
data = {}
event_scid = "unknown"
segments = line.split('|')
segments = [segment.strip() for segment in segments]
assert(len(segments) > 1)
for i in range(1,len(segments)):
item = segments[i].split(':')
item = [i.strip() for i in item]
if(len(item) != 2):
continue
if item[0] == "scid":
event_scid = item[1]
elif(item[0] == "owner"):
data["owner"] = item[1]
else:
data[item[0]] = int(item[1])
return (data, event_scid)
def parse_http_stream_type_set(line):
data = {"stream_id": "unknown",
"stream_type": "unknown"
}
event_scid = "unknown"
segments = line.split('|')
segments = [segment.strip() for segment in segments]
assert(len(segments) > 1)
for i in range(1,len(segments)):
item = segments[i].split(':')
item = [i.strip() for i in item]
if(len(item) != 2):
continue
if item[0] == "scid":
event_scid = item[1]
elif(item[0] == "stream_id"):
data["stream_id"] = int(item[1])
elif item[0] == "stream_type":
data["stream_type"] = h3_stream_type_[int(item[1])]
return (data, event_scid)
def parse_http_frame_created(line):
data = {"stream_id": "unknown",
"frame": {"frame_type": "unknown"}
}
event_scid = "unknown"
segments = line.split('|')
segments = [segment.strip() for segment in segments]
assert(len(segments) > 1)
h3_frame_type = "unknown"
kv = {}
for i in range(1,len(segments)):
item = segments[i].split(':')
item = [i.strip() for i in item]
if(len(item) != 2):
continue
if item[0] == "scid":
event_scid = item[1]
if item[0] == "type":
h3_frame_type = h3_frame_type_[int(item[1])]
else:
kv[item[0]] = item[1]
data["frame"]["frame_type"] = h3_frame_type
data["stream_id"] = kv["stream_id"]
if h3_frame_type == "data":
return (data, event_scid)
elif h3_frame_type == "headers":
header_list = []
for i in range(1,len(segments)):
if segments[i].startswith("{name"):
match_header = re.findall(r'\{(.*?)\}', segments[i])
temp_header = {"name": "unknown", "value": "/"}
temp_header["name"] = match_header[0][5:]
if len(match_header) == 2:
temp_header["value"] = match_header[0][6:]
header_list.append(temp_header)
data["frame"]["headers"] = header_list
return (data, event_scid)
elif h3_frame_type == "cancel_push" or h3_frame_type == "push_promise":
data["frame"]["push_id"] = kv["push_id"]
return (data, event_scid)
elif h3_frame_type == "settings":
temp_set = []
for set_i in ["max_field_section_size", "max_pushes",
"qpack_max_table_capacity", "qpack_blocked_streams"]:
temp_set.append({set_i: int(kv[set_i])})
data["frame"]["settings"] = temp_set
return (data, event_scid)
else:
return (data, event_scid)
def parse_http_frame_parsed(line):
data = {}
event_scid = "unknown"
segments = line.split('|')
segments = [segment.strip() for segment in segments]
assert(len(segments) > 1)
for i in range(1,len(segments)):
item = segments[i].split(':')
item = [i.strip() for i in item]
if(len(item) != 2):
continue
if item[0] == "scid":
event_scid = item[1]
elif(item[0] == "stream_id"):
data["stream_id"] = packet_type_[int(item[1])]
elif(item[0] == "push_id"):
data["push_id"] = int(item[1])
return (data, event_scid)
def main():
global last_scid_
parser = argparse.ArgumentParser()
parser.add_argument("--clog", help="xquic client log file")
parser.add_argument("--slog", help="xquic server log file")
parser.add_argument("--qlog_path", help="output json file, endswith .json", default="demo_qlog.json")
args = parser.parse_args()
if (args.clog is None) and (args.slog is None):
print("Usage: must provide either --clog or --slog argument")
sys.exit(1)
if (args.clog is not None) and (not os.path.isfile(args.clog)):
print(f"Error: The log '{args.clog}' does not exist.")
sys.exit(1)
if (args.slog is not None) and (not os.path.isfile(args.slog)):
print(f"Error: The log '{args.slog}' does not exist.")
sys.exit(1)
if (args.qlog_path is not None) and (not args.qlog_path.endswith(".json")):
print(f"Error: The qlog_path should endswith .json.")
sys.exit(1)
data = { "qlog_version": "0.4",
"qlog_format": "JSON",
"title": "xquic qlog",
"description": "this is a demo qlog json of qlog-draft-07",
"traces": []
}
if(args.slog is not None):
server_traces = endpoint_events_extraction(args.slog, "server")
data["traces"] += server_traces
if(args.clog is not None):
client_traces = endpoint_events_extraction(args.clog, "client")
data["traces"] += client_traces
json_output = json.dumps(data, indent=4)
with open(args.qlog_path, 'w') as out_file:
out_file.write(json_output)
if __name__ == "__main__":
main()