Skip to main content
added 108 characters in body
Source Link
RomanPerekhrest
  • 5.3k
  • 1
  • 10
  • 21
import threading
from argparse import ArgumentParser
from itertools import product
import subprocess


def check_host(host: str):
    return_code = subprocess.run(["ping", "-c", "1", "-n", "-q", host],
                                 stdout=subprocess.DEVNULL,
                                 stderr=subprocess.DEVNULL).returncode
    print(f'response for pinging {threading.current_thread().name} is {return_code}')

    status = 'up' if return_code == 0 else 'down'
    print(f'{host} : is {status}')


def start_threads(addr_range):
    for addr in addr_range:
        t = threading.Thread(target=check_host, args=(addr,), 
                             name=f'Thread:{addr}')
        t.start()
        yield t


def ping_network_range(net_class: str):
    net_class = net_class.upper()
    if net_class == 'A':
        threads = list(start_threads(f'127.0.0.{i}' for i in range(256)))
    elif net_class == 'B':
        threads = list(start_threads(f'127.0.{i}.{j}' 
                                     for i, j in product(range(256), range(256))))
    else:
        raise ValueError(f'Wrong network class name {net_class}')

    for t in threads:
        t.join()


if __name__ == "__main__":
    parser = ArgumentParser(description='Ping network addresses by network class')
    parser.add_argument('-c', '--nclass', choices=('A', 'B'), 
                        required=True, help='Choose class A or B')
    args = parser.parse_args()
    ping_network_range(args.nclass)
import threading
from argparse import ArgumentParser
from itertools import product
import subprocess


def check_host(host: str):
    return_code = subprocess.run(["ping", "-c", "1", "-n", "-q", host],
                                 stdout=subprocess.DEVNULL,
                                 stderr=subprocess.DEVNULL).returncode
    print(f'response for pinging {threading.current_thread().name} is {return_code}')

    status = 'up' if return_code == 0 else 'down'
    print(f'{host} : is {status}')


def start_threads(addr_range):
    for addr in addr_range:
        t = threading.Thread(target=check_host, args=(addr,), name=f'Thread:{addr}')
        t.start()
        yield t


def ping_network_range(net_class: str):
    net_class = net_class.upper()
    if net_class == 'A':
        threads = list(start_threads(f'127.0.0.{i}' for i in range(256)))
    elif net_class == 'B':
        threads = list(start_threads(f'127.0.{i}.{j}' for i, j in product(range(256), range(256))))
    else:
        raise ValueError(f'Wrong network class name {net_class}')

    for t in threads:
        t.join()


if __name__ == "__main__":
    parser = ArgumentParser(description='Ping network addresses by network class')
    parser.add_argument('-c', '--nclass', choices=('A', 'B'), required=True, help='Choose class A or B')
    args = parser.parse_args()
    ping_network_range(args.nclass)
import threading
from argparse import ArgumentParser
from itertools import product
import subprocess


def check_host(host: str):
    return_code = subprocess.run(["ping", "-c", "1", "-n", "-q", host],
                                 stdout=subprocess.DEVNULL,
                                 stderr=subprocess.DEVNULL).returncode
    print(f'response for pinging {threading.current_thread().name} is {return_code}')

    status = 'up' if return_code == 0 else 'down'
    print(f'{host} : is {status}')


def start_threads(addr_range):
    for addr in addr_range:
        t = threading.Thread(target=check_host, args=(addr,), 
                             name=f'Thread:{addr}')
        t.start()
        yield t


def ping_network_range(net_class: str):
    net_class = net_class.upper()
    if net_class == 'A':
        threads = list(start_threads(f'127.0.0.{i}' for i in range(256)))
    elif net_class == 'B':
        threads = list(start_threads(f'127.0.{i}.{j}' 
                                     for i, j in product(range(256), range(256))))
    else:
        raise ValueError(f'Wrong network class name {net_class}')

    for t in threads:
        t.join()


if __name__ == "__main__":
    parser = ArgumentParser(description='Ping network addresses by network class')
    parser.add_argument('-c', '--nclass', choices=('A', 'B'), 
                        required=True, help='Choose class A or B')
    args = parser.parse_args()
    ping_network_range(args.nclass)
deleted 5 characters in body
Source Link
RomanPerekhrest
  • 5.3k
  • 1
  • 10
  • 21

The former createThread function tried to create a single thread with start ing and join ing it at one step. But that undermines the benefit of using threading for parallelizing computations. The crucial mechanics is that all treads are initiated/started at once, then, they we join them (awaiting for their finishing) at next separate phase.
On constructing a threading.Thread no need to pass custom thread name through kwargs={"threadName": ...} - the Thread constructor already accepts name argument for the thread name which is then accessed within target function via threading.current_thread().name.

The former createThread function tried to create a single thread with start ing and join ing it at one step. But that undermines the benefit of using threading for parallelizing computations. The crucial mechanics is that all treads are initiated/started at once, then, they we join them (awaiting for their finishing) at next separate phase.
On constructing a threading.Thread no need to pass custom thread name through kwargs={"threadName": ...} - the Thread constructor already accepts name argument for the thread name which is then accessed within target function via threading.current_thread().name.

The former createThread function tried to create a single thread with start ing and join ing it at one step. But that undermines the benefit of using threading for parallelizing computations. The crucial mechanics is that all treads are initiated/started at once, then, we join them (awaiting for their finishing) at next separate phase.
On constructing a threading.Thread no need to pass custom thread name through kwargs={"threadName": ...} - the Thread constructor already accepts name argument for the thread name which is then accessed within target function via threading.current_thread().name.

Source Link
RomanPerekhrest
  • 5.3k
  • 1
  • 10
  • 21

Towards better design, functionality and performance

Start with good names: that means following Python naming conventions and give a meaningful names to your identifiers/functions/classes:

rangeSelector ---> ping_network_range
createThread ---> start_threads
pingBox ---> check_host

As @Peilonrayz already mentioned range(256) is the valid range for your case.

The former createThread function tried to create a single thread with start ing and join ing it at one step. But that undermines the benefit of using threading for parallelizing computations. The crucial mechanics is that all treads are initiated/started at once, then, they we join them (awaiting for their finishing) at next separate phase.
On constructing a threading.Thread no need to pass custom thread name through kwargs={"threadName": ...} - the Thread constructor already accepts name argument for the thread name which is then accessed within target function via threading.current_thread().name.

The former pingBox function used os.system function (executes the command (a string) in a subshell) which is definitely not a good choice.
The more robust, powerful and flexible choice is subprocess module:

The subprocess module allows you to spawn new processes, connect to their input/output/error pipes, and obtain their return codes. This module intends to replace several older modules and functions: os.system, os.spawn*
...
The recommended approach to invoking subprocesses is to use the run() function for all use cases it can handle. For more advanced use cases, the underlying Popen interface can be used directly.

Some good, user-friendly description - on this SO link.

Furthermore, the ping command (OS network tool) can be speed up itself through adjusting specific options. The most significant ones in such context are:

  • -n (No attempt will be made to lookup symbolic names for host addresses. Allows to disable DNS lookup to speed up queries)
  • -W <number> (Time to wait for a response, in seconds. The option affects only timeout in absence of any responses, otherwise ping waits for two RTTs)
  • -i interval (Wait interval seconds between sending each packet. The default is to wait for one second between each packet normally, or not to wait in flood mode. Only super-user may set interval to values less than 0.2 seconds)

The difference would be more noticeable on sending more than one packet (-c option).
In your case I'd apply -n and -q options. With -q option allows to quite the output since we'll get the returned code which itself indicates whether a particular host is reachable across an IP network. subprocess.run allows to access the returncode explicitly.


The 2 nested for loops within the former rangeSelector function is flexibly replaced with itertools.product routine to compose an efficient generator expression yielding formatted IP addresses:

from itertools import product
...
start_threads(f'127.0.{i}.{j}' for i, j in product(range(256), range(256)))

Designing command-line interface

As your program is planned to be used as part of pipeline, instead of hanging on interactive, blocking call of input('Choose range A|B :') - the more flexible and powerful way is using argparse module that allows building an extended and flexible command-line interfaces with variety of options of different types and actions.
For ex. the allowed network classes names can be supplied through choices:

...
parser = ArgumentParser(description='Ping network addresses by network class')
parser.add_argument('-c', '--nclass', choices=('A', 'B'), required=True, help='Choose class A or B')

The final optimized implementation:

import threading
from argparse import ArgumentParser
from itertools import product
import subprocess


def check_host(host: str):
    return_code = subprocess.run(["ping", "-c", "1", "-n", "-q", host],
                                 stdout=subprocess.DEVNULL,
                                 stderr=subprocess.DEVNULL).returncode
    print(f'response for pinging {threading.current_thread().name} is {return_code}')

    status = 'up' if return_code == 0 else 'down'
    print(f'{host} : is {status}')


def start_threads(addr_range):
    for addr in addr_range:
        t = threading.Thread(target=check_host, args=(addr,), name=f'Thread:{addr}')
        t.start()
        yield t


def ping_network_range(net_class: str):
    net_class = net_class.upper()
    if net_class == 'A':
        threads = list(start_threads(f'127.0.0.{i}' for i in range(256)))
    elif net_class == 'B':
        threads = list(start_threads(f'127.0.{i}.{j}' for i, j in product(range(256), range(256))))
    else:
        raise ValueError(f'Wrong network class name {net_class}')

    for t in threads:
        t.join()


if __name__ == "__main__":
    parser = ArgumentParser(description='Ping network addresses by network class')
    parser.add_argument('-c', '--nclass', choices=('A', 'B'), required=True, help='Choose class A or B')
    args = parser.parse_args()
    ping_network_range(args.nclass)

Sample usage (with timing, under Unix time command):

time python3 ping_network_range.py -c B > test.txt

real    2m4,165s
user    2m17,660s
sys     4m35,790s

Sample tail contents of resulting test.txt file:

$ tail test.txt
response for pinging Thread:127.0.255.250 is 0
127.0.255.250 : is up
response for pinging Thread:127.0.255.252 is 0
127.0.255.252 : is up
response for pinging Thread:127.0.255.253 is 0
127.0.255.253 : is up
response for pinging Thread:127.0.255.255 is 0
127.0.255.255 : is up
response for pinging Thread:127.0.255.254 is 0
127.0.255.254 : is up