web_client.py 7.61 KB
import sys
import os
import requests
import json
from datetime import datetime
from time import (
    sleep,
    time,
    )
from threading import Thread
from argparse import ArgumentParser


headers = {'content-type': 'application/json'}
threads = dict()
end_threads = list()
durations = dict()
json_responses = dict()
server_info = dict()

default_url = 'http://localhost:7000/rpc'
default_count = 1

help_url = 'default ' + default_url
help_count = 'default {}'.format(default_count)
help_invoice_id = 'wajib saat --payment dan --reversal'
help_amount = 'wajib saat --payment dan --reversal'
help_ntb = 'opsional saat --payment, wajib saat --reversal'
help_stan = 'opsional saat --payment, wajib saat --reversal'
help_bit = 'bit tambahan, contoh: --bit=42:TOKOPEDIA'
help_conf = 'konfigurasi tambahan, contoh untuk multi: --conf=pajak:bphtb'


def error(s):
    print('ERROR: {}'.format(s))
    sys.exit()


def log_info(s):
    t = datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')
    t = t[:-3]
    msg = '{} {}'.format(t, s)
    print(msg)


def get_option(argv):
    parser = ArgumentParser()
    parser.add_argument('--url', default=default_url, help=help_url)
    parser.add_argument(
        '--count', type=int, default=default_count, help=help_count)
    parser.add_argument('--invoice-id', help=help_invoice_id)
    parser.add_argument('--payment', action='store_true')
    parser.add_argument('--reversal', action='store_true')
    parser.add_argument('--amount', type=int, help=help_amount)
    parser.add_argument('--ntb', help=help_ntb)
    parser.add_argument('--stan', help=help_stan)
    parser.add_argument('--bit', help=help_bit)
    parser.add_argument('--conf', help=help_conf)
    return parser.parse_args(argv)


def send(p):
    url = server_info['url']
    key = p['id']
    log_info('Request: {}'.format(p))
    start = time()
    try:
        resp = requests.post(url, data=json.dumps(p), headers=headers)
        durations[key] = time() - start
        if resp.status_code == 200:
            json_resp = resp.json()
            log_info('Response: {}'.format(json_resp))
            json_responses[key] = json_resp
        else:
            log_info('Status Code: {}'.format(resp.status_code))
            log_info('Body: {}'.format([resp.text]))
            json_responses[key] = dict(fatal=resp.text)
    except requests.exceptions.ConnectionError as e:
        durations[key] = time() - start
        log_info('Response: {}'.format(e))
        json_responses[key] = dict(fatal=e)
    except json.decoder.JSONDecodeError as e:
        durations[key] = time() - start
        log_info('Body: {}'.format([resp.text]))
        log_info('Response: {}'.format(e))
        json_responses[key] = dict(fatal=e)
    finally:
        end_threads.append(key)


def stan_from_result(result):
    if result['code'] == 0:
        return result['data']['11']
    return '-'


def show_errors(errors):
    if errors:
        for err, count in errors.items():
            log_info('{} {}'.format(err, count))
    else:
        log_info('Tidak ada yang gagal')


def show_durations():
    key_fastest = None
    key_slowest = None
    total_duration = 0
    messages = dict()
    errors = dict()
    for key in durations:
        duration = durations[key]
        msg = 'thread {} {} detik'.format(key, duration)
        resp = json_responses[key]
        if 'fatal' in resp:
            errors['fatal'] = resp['fatal']
        elif 'error' in resp:
            result = resp['error']
            msg = '{} {}'.format(msg, result['message'])
            err = result['message']
            if err in errors:
                errors[err] += 1
            else:
                errors[err] = 1
        else:
            result = resp['result']
            stan = stan_from_result(result)
            msg = '{} stan {}'.format(msg, stan)
            messages[key] = msg
            if key_fastest:
                if duration < durations[key_fastest]:
                    key_fastest = key
            else:
                key_fastest = key
            if key_slowest:
                if duration > durations[key_slowest]:
                    key_slowest = key
            else:
                key_slowest = key
            total_duration += duration
        log_info(msg)
    if key_fastest != key_slowest:
        log_info('Tercepat {}'.format(messages[key_fastest]))
        log_info('Terlama {}'.format(messages[key_slowest]))
        log_info('Rerata {} detik / request'.format(
            total_duration/len(durations)))
    show_errors(errors)


class App:
    def __init__(self, argv):
        self.option = get_option(argv)
        server_info['url'] = self.option.url

    def create_thread(self, data):
        thread = Thread(target=send, args=[data])
        # Exit the server thread when the main thread terminates
        thread.daemon = True
        thread_id = data['id']
        threads[thread_id] = thread
        thread.start()

    def get_invoice_ids(self):
        if not os.path.exists(self.option.invoice_id):
            return [self.option.invoice_id]
        r = []
        with open(self.option.invoice_id) as f:
            for line in f.readlines():
                invoice_id = line.rstrip()
                r += [invoice_id]
        return r

    def get_method(self):
        if self.option.payment:
            return 'payment'
        if self.option.reversal:
            return 'reversal'
        return 'inquiry'

    def get_transaction(self, invoice_id):
        def required(name, default=None):
            value = getattr(self.option, name)
            if not value and not default:
                error('--{} harus diisi'.format(name))
            p[name] = value or default

        p = dict(invoice_id=invoice_id)
        if self.option.payment or self.option.reversal:
            required('amount')
            if self.option.payment:
                required('ntb', datetime.now().strftime('%m%d%H%M%S'))
                required('stan', datetime.now().strftime('%H%M%S'))
            else:
                required('ntb')
                required('stan')
        if self.option.bit:
            bits = dict()
            for t in self.option.bit.split(','):
                bit, value = t.split(':')
                bits[bit] = value
            p['bits'] = bits
        if self.option.conf:
            conf = dict()
            for t in self.option.conf.split(','):
                key, val = t.split(':')
                conf[key] = val
            p['conf'] = conf
        return p

    def run_transaction(self):
        method = self.get_method()
        thread_id = 0
        for i in range(self.option.count):
            for invoice_id in self.get_invoice_ids():
                thread_id += 1
                p = self.get_transaction(invoice_id)
                data = dict(
                        id=thread_id, method=method, params=[p],
                        jsonrpc='2.0')
                self.create_thread(data)

    def run_echo(self):
        for thread_id in range(1, self.option.count+1):
            p = dict(id=thread_id)
            data = dict(id=thread_id, method='echo', params=[p], jsonrpc='2.0')
            self.create_thread(dict(data))

    def run(self):
        p = dict()
        if self.option.invoice_id:
            self.run_transaction()
        else:
            self.run_echo()
        while threads:
            if not end_threads:
                continue
            i = end_threads[0]
            if i in threads:
                thread = threads[i]
                thread.join()
                del threads[i]
            index = end_threads.index(i)
            del end_threads[index]
        show_durations()


def main(argv=sys.argv[1:]):
    app = App(argv)
    app.run()