Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

Code Block
languagepy
firstline1
titletwping_parser.py
linenumberstrue
'''
Sample twping output (MIND THE NAMING OF THE LINES)

line 0: --- twping statistics from [192.168.1.1]:9706 to [192.168.1.2]:19642 ---
line 1: SID:    c0a80102e5e36a42b8a73f74cec8780e
line 2: first:  2022-03-21T23:18:58.819
line 3: last:   2022-03-21T23:19:10.456
line 4: 100 sent, 0 lost (0.000%), 0 send duplicates, 0 reflect duplicates
line 5: round-trip time min/median/max = 0.109/0.3/1.07 ms, (err=3.8 ms)
line 6: send time min/median/max = 936/936/936 ms, (err=1.9 ms)
line 7: reflect time min/median/max = -936/-936/-935 ms, (err=1.9 ms)
line 8: reflector processing time min/max = 0.00191/0.021 ms
line 9: two-way jitter = 0.1 ms (P95-P50)
line 10: send jitter = 0.1 ms (P95-P50)
line 11: reflect jitter = 0 ms (P95-P50)
line 12: send hops = 0 (consistently)
line 13:reflect hops = 0 (consistently)
'''

import subprocess
import json
import requests
from requests.packages.urllib3.exceptions import InsecureRequestWarning
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)

def return_command_output(command):
    '''
        Execute a command and return its output
    '''
    proc = subprocess.Popen(command, stdout = subprocess.PIPE, shell = True)
    (out, err) = proc.communicate()
    output = out.rstrip('\n'.encode('utf8'))
    return output

def perform_twping(twamp_server_ip):
    '''
        Perform the twping command and retrieve its output in milliseconds
    '''
    command = "twping " + str(twamp_server_ip) + " -n m"
    twping_results = return_command_output(command).decode('utf8')
    return twping_results

def locate_twping_data(twping_output):
    '''
        Find the line at which the important part of the twping output starts
    '''
    twping_output_parts = twping_output.split('\n')
    line_to_start = 0
    for line in twping_output_parts:
        initial_three_chars = line[0:3]
        if initial_three_chars == "---":
            break
        line_to_start += 1
    return line_to_start

# Parse lines one by one. Look at the top for the numbering of the lines
def parse_line1(line1):
    parts = line1.split("\t")
    sid = parts[1]
    return sid

def parse_line4(line4):
    parts = line4.split(" ")
    sent, lost, send_dups, reflect_dups = parts[0], parts[2], parts[5], parts[8]
    return sent, lost, send_dups, reflect_dups

def parse_times(line):
    parts = line.split(" ")
    min_median_max = parts[4].split("/")
    minimum, median, maximum = min_median_max[0], min_median_max[1], min_median_max[2]
    err = parts[6].split("=")[1]
    return minimum, median, maximum, err

def parse_line8(line):
    parts = line.split(" ")
    time_unit = parts[-1]
    minimum = parts[-2].split("/")[0]
    maximum = parts[-2].split("/")[1]
    return minimum, maximum

def parse_jitter(line):
    parts = line.split(" ")
    value = parts[3]
    characterization = parts[5][1:-1]
    return value, characterization

def parse_hops(line):
    parts = line.split(" ")
    value = parts[3]
    characterization = parts[4][1:-1]
    return value, characterization

def parse_ntpstat_line_0(line):
    line_parts = line.split(" ")
    ntp_server = line_parts[4]
    ntp_server = ntp_server[1:-1]
    stratum = line_parts = line_parts[7]
    return ntp_server, stratum

def parse_ntpstat_line_1(line):
    line_parts = line.split(" ")
    while line_parts[0] == "":
        line_parts = line_parts[1:]
    value = line_parts[4]
    unit = line_parts[5]
    time_correct = str(value) + " " + str(unit)
    return time_correct

def parse_ntpstat():
    command = "ntpstat"
    ntpstat_output = return_command_output(command).decode('utf8')
    ntpstat_output_lines = ntpstat_output.split('\n')
    line_0 = ntpstat_output_lines[0]
    ntp_server, stratum = parse_ntpstat_line_0(line_0)
    line_1 = ntpstat_output_lines[1]
    time_correct = parse_ntpstat_line_1(line_1)
    return (ntp_server, stratum, time_correct)

def parse_ntpq_starred_line(line):
    line_parts = line.split(" ")
    try:
        while True:
            line_parts.remove('')
    except ValueError:
        pass
    return line_parts

def parse_ntpq():
    command = "ntpq -pn"
    ntpq_output = return_command_output(command).decode('utf8')
    ntpq_output_lines = ntpq_output.split('\n')
    for line in ntpq_output_lines:
        if line[0] == "*":
            ntpq_result = parse_ntpq_starred_line(line[1:])
    return ntpq_result

def form_json(probe_number, twamp_server, sid, sent, lost, send_dups, reflect_dups, 
        min_rtt, median_rtt, max_rtt, err_rtt, min_send, median_send, max_send, 
        err_send, min_reflect, median_reflect, max_reflect, err_reflect, 
        min_reflector_processing_time, max_reflector_processing_time,
        two_way_jitter_value, two_way_jitter_char, send_jitter_value, send_jitter_char,
        reflect_jitter_value, reflect_jitter_char, send_hops_value, send_hops_char,
        reflect_hops_value, reflect_hops_char, ntp_server_ntpstat, stratum, time_correct,
        ntp_server_ntpq, delay_ntpq, offset_ntpq, jitter_ntpq):
    '''
        Create a json object with the parsed values. Values are first stored in a dictionary.
    '''
    overall_dictionary = {}
    # TWAMP-related data
    overall_dictionary["probeNumber"] = probe_number
    overall_dictionary["twampServer"] = twamp_server
    overall_dictionary["sid"] = sid
    overall_dictionary["sent"] = sent
    overall_dictionary["lost"] = lost
    overall_dictionary["sendDups"] = send_dups
    overall_dictionary["reflectDups"] = reflect_dups
    overall_dictionary["minRtt"] = min_rtt
    overall_dictionary["medianRtt"] = median_rtt
    overall_dictionary["maxRtt"] = max_rtt
    overall_dictionary["errRtt"] = err_rtt
    overall_dictionary["minSend"] = min_send
    overall_dictionary["medianSend"] = median_send
    overall_dictionary["maxSend"] = max_send
    overall_dictionary["errSend"] = err_send
    overall_dictionary["minReflect"] = min_reflect
    overall_dictionary["medianReflect"] = median_reflect
    overall_dictionary["maxReflect"] = max_reflect
    overall_dictionary["errReflect"] = err_reflect
    overall_dictionary["minReflectorProcessingTime"] = min_reflector_processing_time
    overall_dictionary["maxReflectorProcessingTime"] = max_reflector_processing_time
    overall_dictionary["twoWayJitterValue"] = two_way_jitter_value
    overall_dictionary["twoWayJitterChar"] = two_way_jitter_char
    overall_dictionary["sendJitterValue"] = send_jitter_value
    overall_dictionary["sendJitterChar"] = send_jitter_char
    overall_dictionary["reflectJitterValue"] = reflect_jitter_value
    overall_dictionary["reflectJitterChar"] = reflect_jitter_char
    overall_dictionary["sendHopsValue"] = send_hops_value
    overall_dictionary["sendHopsChar"] = send_hops_char
    overall_dictionary["reflectHopsValue"] = reflect_hops_value
    overall_dictionary["reflectHopsChar"] = reflect_hops_char
    # NTP-related data
    overall_dictionary["ntpServerNtpstat"] = "\"" + str(ntp_server_ntpstat) + "\""
    overall_dictionary["stratum"] = stratum
    overall_dictionary["timeCorrect"] = time_correct
    overall_dictionary["ntpServerNtpq"] = "\"" + str(ntp_server_ntpq) + "\""
    overall_dictionary["delayNtpq"] = delay_ntpq
    overall_dictionary["offsetNtpq"] = offset_ntpq
    overall_dictionary["jitterNtpq"] = jitter_ntpq
    json_data = json.dumps(overall_dictionary)
    return json_data

def parse_twping_and_ntp(twping_output, line_to_start, probe_number):
    '''
        Parse twping output line by line
    '''
    twping_output_parts = twping_output.split('\n')
    sid = parse_line1(twping_output_parts[line_to_start + 1])
    sent, lost, send_dups, reflect_dups = parse_line4(twping_output_parts[line_to_start + 4])
    min_rtt, median_rtt, max_rtt, err_rtt = parse_times(twping_output_parts[line_to_start + 5])
    min_send, median_send, max_send, err_send = parse_times(twping_output_parts[line_to_start + 6])
    min_reflect, median_reflect, max_reflect, err_reflect = parse_times(twping_output_parts[line_to_start + 7])
    min_reflector_processing_time, max_reflector_processing_time = parse_line8(twping_output_parts[line_to_start +8])
    two_way_jitter_value, two_way_jitter_char = parse_jitter(twping_output_parts[line_to_start + 9])
    send_jitter_value, send_jitter_char = parse_jitter(twping_output_parts[line_to_start + 10])
    reflect_jitter_value, reflect_jitter_char = parse_jitter(twping_output_parts[line_to_start + 11])
    send_hops_value, send_hops_char = parse_hops(twping_output_parts[line_to_start + 12])
    reflect_hops_value, reflect_hops_char = parse_hops(twping_output_parts[line_to_start + 13])
    # parse ntpq and ntpstat commands
    ntp_server_ntpstat, stratum, time_correct = parse_ntpstat()
    ntpq_result = parse_ntpq()
    ntp_server_ntpq = ntpq_result[0]
    delay_ntpq = ntpq_result[7]
    offset_ntpq = ntpq_result[8]
    jitter_ntpq = ntpq_result[9]
    # form json data
    json_data = form_json(probe_number, twamp_server, sid, sent, lost, send_dups, reflect_dups, 
            min_rtt, median_rtt, max_rtt, err_rtt, min_send, median_send, max_send, err_send, 
            min_reflect, median_reflect, max_reflect, err_reflect, min_reflector_processing_time,
            max_reflector_processing_time, two_way_jitter_value, two_way_jitter_char, 
            send_jitter_value, send_jitter_char, reflect_jitter_value, reflect_jitter_char, 
            send_hops_value, send_hops_char, reflect_hops_value, reflect_hops_char,
            ntp_server_ntpstat, stratum, time_correct, ntp_server_ntpq, delay_ntpq,
            offset_ntpq, jitter_ntpq)
    return json_data

def stream_data(json_data):
    '''
        Stream JSON data to the WiFiMon Analysis Server
        Set the FQDN of the WiFiMon Analysis Server
    '''
    headers = {'content-type' : "application/json"}
    try:
        session = requests.Session()
        session.verify = False
        session.post(url = 'https://INSERT_WAS_FQDN_OR_IP:443/wifimon/twamp/', data = json_data, headers = headers, timeout = 30)
    except:
        pass
    return None

if __name__ == "__main__":
    # Define the number of the WiFiMon Hardware Probe
    PROBE_NO = "INSERT_PROBE_NUMBER"
    # Define the IP address of the TWAMP Server
    twamp_server = "INSERT_TWAMP_SERVER_FQDN_OR_IP"
    twping_results = perform_twping(twamp_server)
    line_to_start = locate_twping_data(twping_results)
    json_data = parse_twping_and_ntp(twping_results, line_to_start, PROBE_NO)
    stream_data(json_data)

...