ろうとるがPythonを扱う、、（その12：回線速度checkプログラム ）

# Python+tkinterで回線速度チェックプログラム
件名の内容をつくることがあった時のメモ。

## ネットワーク回線Download速度測定
よく見かけるサイトのまねごとである。ここでは、1Mbyte、5Mbyte、10Mbyte、30Mbyte、100Mbyteのファイルを無料のStorageサービスサイトに格納し、そのサイトからのダウンロー時間や速度を測定・表示・ファイル化（CSV）する。

## 準備
### ファイル作成
Linuxコマンドddにより作成。下記は、100Mbyteのファイル作成。
```
dd if=/dev/zero of=100Mbyte bs=1024 count=100*1024
```

### URL
無料のストレージサービスサイトでは、URLが変更されることがあるようだ（セキュリティ対策と思われる）。そこで、ダウンロードするファイルのURLが変更されてても対応できるよう、別ファイル（url.txt）にURLを格納することにした。
```
https://himitsu.jp/9114/xxx/contents/1Mbyte
https://himitsu.jp/9123/xxx/contents/5Mbyte
https://himitsu.jp/9345/xxx/contents/10Mbyte
https://himitsu.jp/9534/xxx/contents/30Mbyte
https://himitsu.jp/980a/xxx/contents/100Mbyte
```
## ソースコード
まずは部分的な説明。

### import
```python
import logging
import logging.handlers
from datetime import datetime
import time
import urllib.request
import tkinter
from tkinter import *
import tkinter.ttk as ttk
```
ログ、時間、URL、tkinterなどのライブラリインポート。

### 定義・リスト
```python
w_width = 450
w_height = 400
speed_list = [1, 5, 10, 30, 100]
file_list = ['1Mbyte', '5Mbyte', '10Mbyte', '30Mbyte', '100Mbyte']
```
画面サイズ、速度定義のリスト。

### URL取得
```python
        f = open('url.txt', 'r')
        url_list = f.readlines()
        f.close()
```
１行に１つのURLが記載されたファイルから内容を読み込み、リスト化。

### 表示部分作成
```python
            frm1 = ttk.Frame(frm, width=w_width, height=w_height)
            frm1.Btn1 = ttk.Button(frm1, text=file_list[0], style='W.TButton', command=lambda:ok_clk(1))
            frm1.Btn1.grid(row = 0, column = 0)
            frm1.Btn2 = ttk.Button(frm1, text=file_list[1], style='W.TButton', command=lambda:ok_clk(2))
            frm1.Btn2.grid(row = 0, column = 1)
            frm1.Btn3 = ttk.Button(frm1, text=file_list[2], style='W.TButton', command=lambda:ok_clk(3))
            frm1.Btn3.grid(row = 0, column = 2)
            frm1.Btn4 = ttk.Button(frm1, text=file_list[3], style='W.TButton', command=lambda:ok_clk(4))
            frm1.Btn4.grid(row = 0, column = 3)
            frm1.Btn5 = ttk.Button(frm1, text=file_list[4], style='W.TButton', command=lambda:ok_clk(5))
            frm1.Btn5.grid(row = 0, column = 4)

            frm2 = ttk.Frame(frm, width=w_width, height=w_height)
            frm2.label = ttk.Label(frm2, text = 'Size(Mbyte), Time(ms), Speed(Kbps)')
            frm2.label.grid(row = 0, column = 0)
            frm2.dummy = ttk.Label(frm2, text = '                                                    		')
            frm2.dummy.grid(row = 0, column = 1)

            frm3 = ttk.Frame(frm, width=w_width, height=w_height)
            frm3.prt = Text(frm3, font=("Courier New", 11), width=120, height=33)
            frm3.ysc = tkinter.Scrollbar(frm3, orient=tkinter.VERTICAL, command=frm3.prt.yview)
            frm3.ysc.pack(side=tkinter.RIGHT, fill="y")
            frm3.prt["yscrollcommand"] = frm3.ysc.set
            frm3.prt.config(state='disabled')
            frm3.prt.pack()

            frm1.pack()
            frm2.pack()
            frm3.pack()

.....

        note = ttk.Notebook(self)
        note.pack()
        note0 = ttk.Frame(note, width=w_width, height=w_height)
        note.add(note0, text=" Click Download File Size  ")
        cr_tab(note0)

```
タブ化、タブ内のフレーム化（３種類：ボタン、テキスト、結果表示部分）の実施。このあたりは、従来からの方式（例えば、「[ろうとるがPythonを扱う、、（その５：tkinterでタブ）](https://qiita.com/infinite1oop/items/f798699c7058314b308c)」を踏襲。

タブは必須ではなかったが、その後のプログラム拡張に備えて、あえてタブ化した。

### ボタンクリック時処理
```python
            def ok_clk(arg):
                start_time = time.perf_counter_ns()
                urllib.request.urlretrieve(url_list[arg-1], file_list[arg-1])
                end_time = time.perf_counter_ns()
                elapsed_time = (end_time - start_time) / 1000000
                speed = speed_list[arg-1]*8*1024*1024/elapsed_time
                output = str(speed_list[arg-1]) + ', ' + str(elapsed_time) + ', ' + str(speed) + ','
                log.debug(output)                   # output to log file
                output = output + '\n'
                frm3.prt.config(state='normal')     # editable
                frm3.prt.insert(END, output)
                frm3.prt.config(state='disabled')   # not editable
                frm3.prt.see('end')
```
URLライブラリを使ってファイルをダウンロード、ダウンロード時間の計測および速度の計算、結果をログファイル（CSV）に格納および表示。

### ログファイル（csv）関連
```python
        log = logging.getLogger(__name__)
        log.setLevel(logging.DEBUG)
        fh = logging.FileHandler('{:Log-%Y%m%d-%H%M%S}.csv'.format(datetime.now()))
        log.addHandler(fh)
        header = 'Size(Mbyte), Time(ms), Speed(Kbps),'
        log.debug(header)
```
ログおよびログファイル名の定義。

### メニュー
```python
        def log_tgl():
            tgl.set(not tgl.get())
            if tgl.get():
                log.setLevel(logging.DEBUG)
                fh = logging.FileHandler('{:Log-%Y%m%d-%H%M%S}.csv'.format(datetime.now()))
                log.addHandler(fh)
                header = 'Size(Mbyte), Time(ms), Speed(Kbps),'
                log.debug(header)
            else:
                log.removeHandler(log.handlers[0])
                log.setLevel(logging.INFO)

.....

        menubar = Menu(self)
        filemenu = Menu(menubar, tearoff=0)
        menubar.add_cascade(label="File", menu=filemenu)
        filemenu.add_command(label="Exit", command=self.quit)
        tgl = BooleanVar(value = TRUE)
        filemenu.add_checkbutton(label="Disable Log", command=log_tgl)
        master.config(menu=menubar)
```
ログの無効化および有効化メニューの定義。

### 全体
```python
import logging
import logging.handlers
from datetime import datetime
import time
import urllib.request
import tkinter
from tkinter import *
import tkinter.ttk as ttk

w_width = 450
w_height = 400

speed_list = [1, 5, 10, 30, 100]
file_list = ['1Mbyte', '5Mbyte', '10Mbyte', '30Mbyte', '100Mbyte']

class NetworkCheck(ttk.Frame):
    def __init__(self, master):
        super().__init__(master)
        self.create_widgets()
        self.pack()

    def create_widgets(self):
        def cr_tab(frm):
            def ok_clk(arg):
                start_time = time.perf_counter_ns()
                urllib.request.urlretrieve(url_list[arg-1], file_list[arg-1])
                end_time = time.perf_counter_ns()
                elapsed_time = (end_time - start_time) / 1000000
                speed = speed_list[arg-1]*8*1024*1024/elapsed_time
                output = str(speed_list[arg-1]) + ', ' + str(elapsed_time) + ', ' + str(speed) + ','
                log.debug(output)                   # output to log file
                output = output + '\n'
                frm3.prt.config(state='normal')     # editable
                frm3.prt.insert(END, output)
                frm3.prt.config(state='disabled')   # not editable
                frm3.prt.see('end')
            ## End of ok_clk()
        
        ## Start of cr_tab()
            frm1 = ttk.Frame(frm, width=w_width, height=w_height)
            frm1.Btn1 = ttk.Button(frm1, text=file_list[0], style='W.TButton', command=lambda:ok_clk(1))
            frm1.Btn1.grid(row = 0, column = 0)
            frm1.Btn2 = ttk.Button(frm1, text=file_list[1], style='W.TButton', command=lambda:ok_clk(2))
            frm1.Btn2.grid(row = 0, column = 1)
            frm1.Btn3 = ttk.Button(frm1, text=file_list[2], style='W.TButton', command=lambda:ok_clk(3))
            frm1.Btn3.grid(row = 0, column = 2)
            frm1.Btn4 = ttk.Button(frm1, text=file_list[3], style='W.TButton', command=lambda:ok_clk(4))
            frm1.Btn4.grid(row = 0, column = 3)
            frm1.Btn5 = ttk.Button(frm1, text=file_list[4], style='W.TButton', command=lambda:ok_clk(5))
            frm1.Btn5.grid(row = 0, column = 4)

            frm2 = ttk.Frame(frm, width=w_width, height=w_height)
            frm2.label = ttk.Label(frm2, text = 'Size(Mbyte), Time(ms), Speed(Kbps)')
            frm2.label.grid(row = 0, column = 0)
            frm2.dummy = ttk.Label(frm2, text = '                                                    		')
            frm2.dummy.grid(row = 0, column = 1)

            frm3 = ttk.Frame(frm, width=w_width, height=w_height)
            frm3.prt = Text(frm3, font=("Courier New", 11), width=120, height=33)
            frm3.ysc = tkinter.Scrollbar(frm3, orient=tkinter.VERTICAL, command=frm3.prt.yview)
            frm3.ysc.pack(side=tkinter.RIGHT, fill="y")
            frm3.prt["yscrollcommand"] = frm3.ysc.set
            frm3.prt.config(state='disabled')
            frm3.prt.pack()

            frm1.pack()
            frm2.pack()
            frm3.pack()
        ## End of cr_tab()

        def log_tgl():
            tgl.set(not tgl.get())
            if tgl.get():
                log.setLevel(logging.DEBUG)
                fh = logging.FileHandler('{:Log-%Y%m%d-%H%M%S}.csv'.format(datetime.now()))
                log.addHandler(fh)
                header = 'Size(Mbyte), Time(ms), Speed(Kbps),'
                log.debug(header)
            else:
                log.removeHandler(log.handlers[0])
                log.setLevel(logging.INFO)
            ## End of log_tgl()

        ## Start of create_widgets()
        # Log
        log = logging.getLogger(__name__)
        log.setLevel(logging.DEBUG)
        fh = logging.FileHandler('{:Log-%Y%m%d-%H%M%S}.csv'.format(datetime.now()))
        log.addHandler(fh)
        header = 'Size(Mbyte), Time(ms), Speed(Kbps),'
        log.debug(header)
        # Menu
        menubar = Menu(self)
        filemenu = Menu(menubar, tearoff=0)
        menubar.add_cascade(label="File", menu=filemenu)
        filemenu.add_command(label="Exit", command=self.quit)
        tgl = BooleanVar(value = TRUE)
        filemenu.add_checkbutton(label="Disable Log", command=log_tgl)
        master.config(menu=menubar)
        # Tab
        note = ttk.Notebook(self)
        note.pack()
        note0 = ttk.Frame(note, width=w_width, height=w_height)
        note.add(note0, text=" Click Download File Size  ")
        cr_tab(note0)
        # Get URL
        f = open('url.txt', 'r')
        url_list = f.readlines()
        #for i in url_list:
        #    print(i)
        f.close()
    ## End of createwidgets()
## End of NetworkCheck()

if __name__ == '__main__':
    master = Tk()
    master.title("Speed Test v0.95b")
    master.geometry("450x400")
    # Call main part
    NetworkCheck(master)
    master.mainloop()
```
正直、python&tkinter的には、あまり美しいとは言えないプログラムである。

## 動作
下記のようになる。
![スクリーンショット 2023-07-03 19.34.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/634816/216349fb-b3da-beaa-2f34-85f32d3c3bd4.png)

EOF