About uptime.is

[ simple / flexible / reverse / about ]

[Jump to: API 💯Telegram bot 🤖Gemini 🚀 ]

About uptime.is

digi.no has published a story about uptime.is.

uptime.is API 💯

For ad-hoc personal usage, uptime.is is providing an API. For heavy usage, or to create a site, app or some other tool intended to be used by users other than yourself or in an enterprise or commercial environment, please get in touch to discuss a suitable arrangement.

API endpoint and HTTP method

The uptime.is API is available at https://get.uptime.is/api using the GET method (POST is not supported).

Simple SLA calculator

In its most simple form, the calculator outputs downtime duration in hours, minutes and seconds given a specified uptime percentage.

The SLA uptime percentage is specified as a parameter named sla.

$ curl -s 'https://get.uptime.is/api?sla=99.9'
{
  "SLA": 99.9,
  "uptimeURL": "https://uptime.is/99.9",
  "nines": "three nines",
  "ninesURL": "https://uptime.is/three-nines",
  "dailyDownSecs": 86.39999999999418,
  "dailyDown": "1m 26s",
  "weeklyDownSecs": 604.7999999999302,
  "weeklyDown": "10m 4s",
  "monthlyDownSecs": 2608.1459999996987,
  "monthlyDown": "43m 28s",
  "quarterlyDownSecs": 7824.437999999096,
  "quarterlyDown": "2h 10m 24s",
  "yearlyDownSecs": 31297.751999996384,
  "yearlyDown": "8h 41m 37s"
}

The calculator outputs acceptable downtime durations given the provided SLA uptime percentage.

Complex SLA calculator

Simple SLA calculator described above assumes 24/7 uptime requirement (with certain approximations further documented in the source). In real world, the requirements are often more relaxed, e.g. 8 hours on working days. To accommodate for this scenario, the SLA calculator supports complex mode, where it is possible to specify uptime requirement for each day of week.

The SLA uptime percentage is specified as a parameter named sla. The durations are specified as parameters named dur and repeated for each day of week (i.e. 7 times and defaulting to 24, if not provided).

$ curl -s
'https://get.uptime.is/api?sla=99.9&dur=8&dur=8&dur=8&dur=8&dur=8&dur=0&dur=0'
{
  "mondayHours": 8,
  "tuesdayHours": 8,
  "wednesdayHours": 8,
  "thursdayHours": 8,
  "fridayHours": 8,
  "saturdayHours": 0,
  "sundayHours": 0,
  "SLA": 99.9,
  "uptimeURL": "https://uptime.is/complex?sla=99.9&wk=iiiiiaa",
  "nines": "three nines",
  "weeklyDownSecs": 143.9999999999709,
  "weeklyDown": "2m 23s",
  "monthlyDownSecs": 620.9871428570173,
  "monthlyDown": "10m 20s",
  "quarterlyDownSecs": 1862.9614285710518,
  "quarterlyDown": "31m 2s",
  "yearlyDownSecs": 7451.845714284207,
  "yearlyDown": "2h 4m 11s"
}

The calculator outputs acceptable downtime durations given the provided SLA uptime percentage.

Simple reverse SLA calculator

Reverse SLA calculator performs the opposite operation, and converts downtime duration into corresponding SLA uptime percentage. The downtime duration may be provided as a number of seconds (e.g. 42) or as a combination of hours, minutes and/or seconds of downtime using the h, m or s units (e.g. 13m 37s).

The downtime duration is specified as a parameter named down.

 $ curl -s 'https://get.uptime.is/api?down=1h20m'
{
  "downtimeSecs": 4800,
  "downtime": "1h 20m",
  "downtimeURL": "https://uptime.is/reverse?down=4800",
  "dailySLA": 94.44444444444444,
  "weeklySLA": 99.20634920634922,
  "monthlySLA": 99.81596122302969,
  "quarterlySLA": 99.9386537410099,
  "yearlySLA": 99.98466343525247
}

The calculator outputs SLA uptime percentage which would allow the specified downtime with defined uptime calculation durations.

Complex reverse SLA calculator

The reverse SLA calculator accepts the durations in the same way as the complex SLA calculator.

$ curl -s
'https://get.uptime.is/api?down=1h20m&dur=8&dur=8&dur=8&dur=8&dur=8&dur=0&dur=0'
{
  "mondayHours": 8,
  "tuesdayHours": 8,
  "wednesdayHours": 8,
  "thursdayHours": 8,
  "fridayHours": 8,
  "saturdayHours": 0,
  "sundayHours": 0,
  "downtimeSecs": 4800,
  "downtime": "1h 20m",
  "downtimeURL": "https://uptime.is/reverse?down=4800&wk=iiiiiaa",
  "weeklySLA": 96.66666666666667,
  "monthlySLA": 99.2270371367247,
  "quarterlySLA": 99.74234571224156,
  "yearlySLA": 99.93558642806039
}

The calculator outputs SLA uptime percentage which would allow the specified downtime with defined uptime calculation durations.

@uptisbot Telegram Bot 🤖

Telegram bots are third-party applications that run inside Telegram. Users can interact with bots by sending them messages, commands and inline requests.

uptime.is has a Telegram bot called @uptisbot. It supports inline queries. Mention @uptisbot in a chat to get uptime.is results directly in Telegram chats.

@uptisbot has been implemented in the Python programming language using the Flask framework.

[...]
@app.route('/telegram-uptime-bot-callback', methods=['GET', 'POST'])
def telegram_uptime_callback():
    update_id = request.json.get('update_id'),
    if not update_id:
        return ('huh, no update id?', {'Content-Type': 'text/plain; charset=utf-8'})

    if ((msg := request.json.get('message')) and (chat := msg.get('chat'))):
        if chat.get('type') == 'private':
            return jsonify({
                'method': 'sendMessage', 
                'chat_id': chat.get('id'),
                # escape: _ * [ ] ( ) ~ ` > # + - = | { } . !
                'text': f'''Sorry, I'm just a simple [inline bot](https://core.telegram.org/bots/inline) and I can't talk to anyone directly \(_yet\!_\)\.

My task is to provide uptime and downtime results in another chats\.
''',
                'parse_mode': 'MarkdownV2',
                'disable_web_page_preview': True,
                'disable_notification': True,
                'reply_markup': {
                    'inline_keyboard': [
                        [
                            {
                                'text': 'Get results from @uptisbot in another chat instead',
                                'switch_inline_query': '',
                            },
                        ],
                    ]
                },

            })

    if (iq := request.json.get('inline_query')):
        try:
            sla = float(iq.get('query'))
        except (ValueError, TypeError):
            sla = 99.9
        sla = 99.9 if sla < 0 or sla > 100 else sla
        sla_down = (100 - sla) / 100.0

        hours_day   = float(24)
        secs_day    = float(3600 * 24)
        days_year   = 362.2425
        weeks_year  = float(days_year / 7.0)
        hours_year  = float(hours_day * days_year)
        secs_year   = float(secs_day * days_year)
        days_month  = float(days_year / 12)
        weeks_month = float(days_month / 7)
        hours_month = float(hours_day * days_month)
        hours_week  = float(hours_day * 7)
        secs_week   = float(secs_day * 7)

        def secs_dhms(secs):
            dhms = []
            for (d, t) in ((86400, 'd'), (3600, 'h'), (60, 'm')):
                if secs >= d:
                    down = int(secs / d)
                    secs -= down * d
                    dhms.append(f'{down:d}{t:s}')
            dhms.append(f'{secs:.2g}s')
            return ' '.join(dhms)

        return jsonify({
            'method': 'answerInlineQuery', 
            'inline_query_id': iq.get('id'),
            'results': [{
                'type': 'article',
                'id': g.uuid,
                'title': f'Permitted downtime with {sla} % SLA',
                # escape: _ * [ ] ( ) ~ ` > # + - = | { } . !
                'input_message_content': {
                    'message_text': f'''*Uptime and downtime with {sla} % SLA*

\- Daily: {secs_dhms(secs_day * sla_down)}
\- Weekly: {secs_dhms(secs_week * sla_down)}
\- Monthly: {secs_dhms(secs_week * weeks_month * sla_down)}
\- Quarterly: {secs_dhms(secs_year / 4 * sla_down)}
\- Yearly: {secs_dhms(secs_year * sla_down)}

Permalink: https://uptime.is/{sla}
'''.replace('.', '\.'),
                    'parse_mode': 'MarkdownV2',
                    'entities': [],
                    'disable_web_page_preview': True,
                },
                'url': f'https://uptime.is/{sla}',
                'hide_url': True,
                'description': f'… {secs_dhms(secs_day * sla_down)} daily downtime …',
            }],
            'cache_time': 86400,
            'is_personal': False
        })

    return ('ok', {'Content-Type': 'text/plain; charset=utf-8'})

uptime.is in the Geminispace 🚀

Gemini is an application-layer internet communication protocol for accessing remote documents, similarly to the Hypertext Transfer Protocol and Gopher. Gemini is intended as an alternative to those protocols.

uptime.is is available via the Gemini protocol at gemini://uptime.is.

Use one of the Gemini clients or a proxy to access your favourite uptime calculator in the Geminispace.

uptime.is Gemini capsule has been implemented in the Python programming language using the asyncio library.

The source is available (via proxy) and shows how the uptime.is API could be used.

#!/usr/local/bin/python3

import sys
import asyncio
import aiohttp

from pathlib import Path
from yarl import URL
from pprint import pprint, pformat

_sla2down = {}
async def sla2down(sla):
    if sla not in _sla2down:
        url = 'https://get.uptime.is/api'
        async with aiohttp.request('GET', url, params={'sla': sla}) as res:
            _sla2down[sla] = await res.json()
    return _sla2down[sla]

_down2sla = {}
async def down2sla(down):
    if down not in _down2sla:
        url = 'https://get.uptime.is/api'
        async with aiohttp.request('GET', url, params={'down': down}) as res:
            _down2sla[down] = await res.json()
    return _down2sla[down]

sla2nines = {
    9:          'one nine',
    99:         'two nines',
    99.9:       'three nines',
    99.99:      'four nines',
    99.999:     'five nines',
    99.9999:    'six nines',
    99.99999:   'seven nines',
    99.999999:  'eight nines',
    99.9999999: 'nine nines',
}
nines2sla = {v.replace(' ', '-'): str(k) for (k,v) in sla2nines.items()}

async def main():
    async def simple(sla, w):
        down = await sla2down(sla)
        w.write(b'20 text/gemini; charset=utf-8; lang=en\r\n')
        w.write(f'''# Uptime and downtime with {down['SLA']} % SLA{' (aka. ' + sla2nines[down['SLA']].upper() + ')' if float(down['SLA']) in sla2nines else ''}

SLA level of {down['SLA']} % uptime/availability results in the following periods of allowed downtime/unavailability:

* Daily: {down['dailyDown']}
* Weekly: {down['weeklyDown']}
* Monthly: {down['monthlyDown']}
* Quarterly: {down['quarterlyDown']}
* Yearly: {down['yearlyDown']}

=> /simple Change SLA level

## Direct links to pages with the results above

=> gemini://uptime.is/{down['SLA']} uptime.is/{down['SLA']}
=> {down['uptimeURL']} uptime.is/{down['SLA']} (WWW)

## Direct links to N nines

{chr(10).join(['=> /' + str(k) + ' ' + v.capitalize() for (k,v) in sla2nines.items()])}
'''.encode())

    async def reverse(down, w):
        sla = await down2sla(down)
        w.write(b'20 text/gemini; charset=utf-8; lang=en\r\n')
        w.write(f'''# SLA uptime in case of {sla['downtime']} downtime

=> {sla['downtimeURL']}
'''.encode())

    async def handler(r, w):
        req = URL((await r.readuntil(b'\r\n')).decode().strip())

        if req.scheme != 'gemini':
            w.write(b'50 BAD REQUEST\r\n')
        else:
            try:
                if req.path == '/' or (len(req.path) > 1 and req.path[1].isdigit()):
                    if 'sla' in req.query:
                        sla = req.query['sla']
                    elif len(req.path) > 1:
                        sla = req.path[1:]
                    else:
                        sla = '99.9'
                    await simple(sla, w)
                elif (nines := req.path[1:]) in nines2sla:
                    await simple(nines2sla[nines], w)
                elif req.path == '/simple':
                    if (sla := req.query_string):
                        await simple(sla, w)
                    else:
                        w.write(b'10 Enter SLA level expressed in per cent\r\n')
                elif req.path == '/reverse':
                    down = req.query['down'] if 'down' in req.query else ''
                    await reverse(down, w)
                else:
                    w.write(b'51 NOT FOUND\r\n')
            except KeyError:
                w.write(b'42 INTERNAL SERVER ERROR\r\n')
        
        await w.drain()
        w.close()
        await w.wait_closed()

    async with await asyncio.start_server(handler, '127.0.0.1', 19651) as server:
        await server.serve_forever()

if __name__ == '__main__':
    asyncio.run(main())