
# Copyright (C) 2010-2023 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
# (only), as published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
# This program is dual-licensed. If you wish to learn more about the
# RhodeCode Enterprise Edition, including its added features, Support services,
# and proprietary license terms, please see https://rhodecode.com/licenses/

"""
Utility to gather certain statistics about a process.

Used to generate data about the memory consumption of the vcsserver. It is
quite generic and should work for every process. Use the parameter `--help`
to see all options.

Example call::

  python profile-mem.py --pid=89816 --ae --ae-key=YOUR_API_KEY

"""


import argparse
import json
import sys
import time

import datetime
import psutil

import logging
import socket

from webhelpers2.number import format_byte_size

logging.basicConfig(level=logging.DEBUG)


def parse_options():
    parser = argparse.ArgumentParser(
        description=__doc__)
    parser.add_argument(
        '--pid', required=True, type=int,
        help="Process ID to monitor.")
    parser.add_argument(
        '--human', action='store_true',
        help="Show Human numbers")
    parser.add_argument(
        '--interval', '-i', type=float, default=5,
        help="Interval in secods.")
    parser.add_argument(
        '--appenlight', '--ae', action='store_true')
    parser.add_argument(
        '--appenlight-url', '--ae-url',
        default='https://ae.rhodecode.com/api/logs',
        help='URL of the Appenlight API endpoint, defaults to "%(default)s".')
    parser.add_argument(
        '--appenlight-api-key', '--ae-key',
        help='API key to use when sending data to appenlight. This has to be '
             'set if Appenlight is enabled.')
    return parser.parse_args()


def profile():
    config = parse_options()
    try:
        process = psutil.Process(config.pid)
    except psutil.NoSuchProcess:
        print("Process {pid} does not exist!".format(pid=config.pid))
        sys.exit(1)

    prev_stats = None
    while True:
        stats = process_stats(process, prev_stats)
        prev_stats = stats
        dump_stats(stats, human=config.human)

        if config.appenlight:
            client = AppenlightClient(
                url=config.appenlight_url,
                api_key=config.appenlight_api_key)
            client.dump_stats(stats)
        time.sleep(config.interval)


def process_stats(process, prev_stats):
    mem = process.memory_info()
    iso_now = datetime.datetime.utcnow().isoformat()
    prev_rss_diff, prev_vms_diff = 0, 0
    cur_rss = mem.rss
    cur_vms = mem.vms

    if prev_stats:
        prev_rss_diff = cur_rss - prev_stats[0]['tags'][0][1]
        prev_vms_diff = cur_vms - prev_stats[0]['tags'][1][1]

    stats = [
        {'message': 'Memory stats of process {pid}'.format(pid=process.pid),
         'namespace': 'process.{pid}'.format(pid=process.pid),
         'server': socket.getfqdn(socket.gethostname()),
         'tags': [
            ['rss', cur_rss],
            ['vms', cur_vms]
         ],
         'diff': [
             ['rss', prev_rss_diff],
             ['vms', prev_vms_diff]
         ],
         'date': iso_now,
         },
    ]
    return stats


def dump_stats(stats, human=False):
    for sample in stats:
        if human:
            diff = stats[0]['diff'][0][1]
            if diff < 0:
                diff = '-' + format_byte_size(abs(diff), binary=True)
            elif diff > 0:
                diff = '+' + format_byte_size(diff, binary=True)
            else:
                diff = ' ' + format_byte_size(diff, binary=True)

            print('Sample:{message} RSS:{rss} RSS_DIFF:{rss_diff}'.format(
                message=stats[0]['message'],
                rss=format_byte_size(stats[0]['tags'][0][1], binary=True),
                rss_diff=diff,
            ))
        else:
            print(json.dumps(sample))


class AppenlightClient(object):

    url_template = '{url}?protocol_version=0.5'

    def __init__(self, url, api_key):
        self.url = self.url_template.format(url=url)
        self.api_key = api_key

    def dump_stats(self, stats):
        import requests
        response = requests.post(
            self.url,
            headers={
                'X-appenlight-api-key': self.api_key},
            data=json.dumps(stats))
        if not response.status_code == 200:
            logging.error(
                'Sending to appenlight failed\n%s\n%s',
                response.headers, response.text)


if __name__ == '__main__':
    try:
        profile()
    except KeyboardInterrupt:
        pass
