
# 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/

"""
This is a standalone script which will start VCS and RC.

Performance numbers will be written on each interval to:
    vcs_profileX.csv
    rc_profileX.csv

To stop the script by press Ctrl-C
"""

import datetime
import os
import psutil
import subprocess
import sys
import time
import traceback
import urllib.request
import urllib.parse
import urllib.error

PROFILING_INTERVAL = 5
RC_WEBSITE = "http://localhost:5001/"


def get_file(prefix):
    out_file = None
    for i in range(100):
        file_path = "%s_profile%.3d.csv" % (prefix, i)
        if os.path.exists(file_path):
            continue
        out_file = open(file_path, "w")
        out_file.write("Time; CPU %; Memory (MB); Total FDs; Dulwich FDs; Threads\n")
        break
    return out_file


def dump_system():
    print("System Overview...")
    print("\nCPU Count: %d (%d real)" %
          (psutil.cpu_count(), psutil.cpu_count(logical=False)))
    print("\nDisk:")
    print(psutil.disk_usage(os.sep))
    print("\nMemory:")
    print(psutil.virtual_memory())
    print("\nMemory (swap):")
    print(psutil.swap_memory())


def count_dulwich_fds(proc):
    p = subprocess.Popen(["lsof", "-p", proc.pid], stdout=subprocess.PIPE)
    out, err = p.communicate()

    count = 0
    for line in out.splitlines():
        content = line.split()
        # http://git-scm.com/book/en/Git-Internals-Packfiles
        if content[-1].endswith(".idx"):
            count += 1

    return count

def dump_process(pid, out_file):
    now = datetime.datetime.now()
    cpu = pid.cpu_percent()
    mem = pid.memory_info()
    fds = pid.num_fds()
    dulwich_fds = count_dulwich_fds(pid)
    threads = pid.num_threads()

    content = [now.strftime('%m/%d/%y %H:%M:%S'),
               cpu,
               "%.2f" % (mem[0]/1024.0/1024.0),
               fds, dulwich_fds, threads]
    out_file.write("; ".join([str(item) for item in content]))
    out_file.write("\n")


# Open output files
vcs_out = get_file("vcs")
if vcs_out is None:
    print("Unable to enumerate output file for VCS")
    sys.exit(1)
rc_out = get_file("rc")
if rc_out is None:
    print("Unable to enumerate output file for RC")
    sys.exit(1)

# Show system information
dump_system()

print("\nStarting VCS...")
vcs = psutil.Popen(["vcsserver"])
time.sleep(1)
if not vcs.is_running():
    print("VCS - Failed to start")
    sys.exit(1)
print("VCS - Ok")

print("\nStarting RhodeCode...")
rc = psutil.Popen("RC_VCSSERVER_TEST_DISABLE=1 paster serve test.ini",
                  shell=True, stdin=subprocess.PIPE)
time.sleep(1)
if not rc.is_running():
    print("RC - Failed to start")
    vcs.terminate()
    sys.exit(1)

# Send command to create the databases
rc.stdin.write("y\n")

# Verify that the website is up
time.sleep(4)
try:
    urllib.request.urlopen(RC_WEBSITE)
except IOError:
    print("RC - Website not started")
    vcs.terminate()
    sys.exit(1)
print("RC - Ok")

print("\nProfiling...\n%s\n" % ("-"*80))
while True:
    try:
        dump_process(vcs, vcs_out)
        dump_process(rc, rc_out)
        time.sleep(PROFILING_INTERVAL)
    except Exception:
        print(traceback.format_exc())
        break

# Finalize the profiling
vcs_out.close()
rc_out.close()

vcs.terminate()
rc.terminate()
