稼働率から許容停止時間を計算したい

よく、SLAなどで稼働率、許容停止時間という話になりますが、これをサッと求められるようにしたいな、と。

数式的には、以下ですね。

```math
稼働率 = \frac{総時間 - 障害時間}{総時間}
```

で、たとえば「稼働率99％の場合、月間、年間どのくらい停止が許容されるの？」が数字でパッと出てこないので計算できるようにプログラムを書いておこう、と。二番煎じ感たっぷりですが、それは置いておき。

Pythonで書いてみたいと思います。


# 前提

1ヶ月は、30日（`24 x 60 x 60`秒）とします。

1年間は、365日（`365 x 24 x 60 x 60`秒）とします。

確認したPythonのバージョン。

```shell
$ python3 -V
Python 3.6.9
```


# お題

以下の3つを求めます。

* 稼働率
* 月間許容停止時間
* 年間許容停止時間

上記を稼働率、月間許容停止時間、年間許容停止時間のいずれかから求められるようにします。

要するに、どれかひとつを与えると他の要素を求めるものを作りたい、と。

また、許容停止時間については、単位を`s`（秒）、`m`（分）、`h`（時間）、`d`（日）で与えられるものとし、省略した場合は秒として解釈するものとします。

結果は、Markdownの表形式で出力します。


# プログラム

作成したのがこちら。

`calc.py`

```python
import datetime

year_time =  365 * 24 * 60 * 60
month_time = 30 * 24 * 60 * 60

units = {"s": "seconds", "m": "minutes", "h": "hours", "d": "days"}

def calc_from_percentage(percentage):
    stop_time_per_year_raw = year_time * ((100 - percentage) / 100)
    stop_time_per_month_raw = month_time * ((100 - percentage) / 100)

    def format_time(time):
        if time > 24 * 60 * 60:
            formatted_time = f"{time / (24 * 60 * 60):.2f}日"
        elif time > 60 * 60:
            formatted_time = f"{time / (60 * 60):.2f}時間"
        elif time > 60:
            formatted_time = f"{time / 60:.2f}分"
        else:
            formatted_time = f"{time:.2f}秒"

        return formatted_time

    stop_time_per_year = format_time(stop_time_per_year_raw)
    stop_time_per_month = format_time(stop_time_per_month_raw)

    return (f"{percentage}%", stop_time_per_month, stop_time_per_year)

def calc_from_month_stop_time(stop_time):
    try:
        seconds = int(stop_time)
    except ValueError:
        t = int(stop_time[:-1])
        unit = units[stop_time[-1]]

        seconds = datetime.timedelta(**{unit: t}).total_seconds()
        
    percentage = 100 - ((seconds * 100) / month_time)
    return calc_from_percentage(percentage)

def calc_from_year_stop_time(stop_time):
    try:
        seconds = int(stop_time)
    except ValueError:
        t = int(stop_time[:-1])
        unit = units[stop_time[-1]]

        seconds = datetime.timedelta(**{unit: t}).total_seconds()
        
    percentage = 100 - ((seconds * 100) / year_time)
    return calc_from_percentage(percentage)

def print_table(percentages_or_stop_times, calc_func):
    print("| 稼働率 | 月間許容停止時間 | 年間許容停止時間 |")
    print("|:------:|:----------------:|:----------------:|")


    if isinstance(percentages_or_stop_times, list):
        targets = percentages_or_stop_times
    else:
        targets = [percentages_or_stop_times]

    for t in targets:
        result = calc_func(t)
        print(f"| {result[0]} | {result[1]} | {result[2]} |")
```


# 使い方

Pythonのインタラクティブシェルで読み込んで起動します。

```shell
$ python3 -i calc.py 
>>> 
```

稼働率から、許容停止時間を求めます。複数与える場合は、リストで。

```shell
>>> print_table(99, calc_from_percentage)
| 稼働率 | 月間許容停止時間 | 年間許容停止時間 |
|:------:|:----------------:|:----------------:|
| 99% | 7.20時間 | 3.65日 |


>>> print_table([90, 95, 99, 99.5, 99.9, 99.95, 99.99, 99.999, 99.9999], calc_from_percentage)
| 稼働率 | 月間許容停止時間 | 年間許容停止時間 |
|:------:|:----------------:|:----------------:|
| 90% | 3.00日 | 36.50日 |
| 95% | 1.50日 | 18.25日 |
| 99% | 7.20時間 | 3.65日 |
| 99.5% | 3.60時間 | 1.82日 |
| 99.9% | 43.20分 | 8.76時間 |
| 99.95% | 21.60分 | 4.38時間 |
| 99.99% | 4.32分 | 52.56分 |
| 99.999% | 25.92秒 | 5.26分 |
| 99.9999% | 2.59秒 | 31.54秒 |
```

欲しかった表です。

| 稼働率 | 月間許容停止時間 | 年間許容停止時間 |
|:------:|:----------------:|:----------------:|
| 90% | 3.00日 | 36.50日 |
| 95% | 1.50日 | 18.25日 |
| 99% | 7.20時間 | 3.65日 |
| 99.5% | 3.60時間 | 1.82日 |
| 99.9% | 43.20分 | 8.76時間 |
| 99.95% | 21.60分 | 4.38時間 |
| 99.99% | 4.32分 | 52.56分 |
| 99.999% | 25.92秒 | 5.26分 |
| 99.9999% | 2.59秒 | 31.54秒 |

月間許容停止時間から求めます。

```shell
>>> print_table("10", calc_from_month_stop_time)
| 稼働率 | 月間許容停止時間 | 年間許容停止時間 |
|:------:|:----------------:|:----------------:|
| 99.99961419753086% | 10.00秒 | 2.03分 |


>>> print_table(["3d", "10m", "10s", "10"], calc_from_month_stop_time)
| 稼働率 | 月間許容停止時間 | 年間許容停止時間 |
|:------:|:----------------:|:----------------:|
| 90.0% | 3.00日 | 36.50日 |
| 99.97685185185185% | 10.00分 | 2.03時間 |
| 99.99961419753086% | 10.00秒 | 2.03分 |
| 99.99961419753086% | 10.00秒 | 2.03分 |
```

パーセンテージの桁数は、今回はいいかなと思いました…。

年間許容停止時間から求めます。

```shell
>>> print_table("30d", calc_from_year_stop_time)
| 稼働率 | 月間許容停止時間 | 年間許容停止時間 |
|:------:|:----------------:|:----------------:|
| 91.78082191780823% | 2.47日 | 30.00日 |


>>> print_table(["30d", "45m", "30s", "30"], calc_from_year_stop_time)
| 稼働率 | 月間許容停止時間 | 年間許容停止時間 |
|:------:|:----------------:|:----------------:|
| 91.78082191780823% | 2.47日 | 30.00日 |
| 99.99143835616438% | 3.70分 | 45.00分 |
| 99.99990487062405% | 2.47秒 | 30.00秒 |
| 99.99990487062405% | 2.47秒 | 30.00秒 |
```

こんな感じで。