#!c:/program files/python/python.exe # This is a Python script (tested on Python 2.5, but 2.4 should work as well) script to monitor the # performance of a Windows Perforce server. Both client and server need to run on Windows XP / Server # 2003 or later, and the server should be reachable over Windows networking. It uses the standard Windows # Typeperf tool to query performance counters on the server. This requires admin access to the server. # It also uses the Sysinternals (now Microsoft) tool PsList, which can currently be found at: # http://www.microsoft.com/technet/sysinternals/utilities/pslist.mspx # Just put pslist.exe next to the script, so it can find it. # # The Perforce server must have monitoring enabled, if necessary, turn monitoring on with: # p4 counter -f monitor 1 # and restart the server. To get the most out of the script, Perforce admin access # is also required. Specify the name of an admin account to use below. If a password is required for # this account and no valid ticket is present, the script will ask for the password at startup. # If the admin login fails, the script will still run, but with less output. # # The script uses typeperf counters to get the server stats, these need to be configured # below so that it will query the right disk and network interfaces. To get a full list # of available counters, do: # typeperf -qx \\server_name > counters.txt # The network interfaces can be found in the list as # \\server_name\Network Interface(NAME)\... # The NAME is what should be specified as the typeperf counter below. # Disk interfaces can be found as: # \\server_name\PhysicalDisk(NAME)\... # Figuring out which are the right interfaces isn't always easy, especially in a server with lots of disks # and network interfaces. Still, a bit of experimenting should give you the right ones. I've left some # sample values in the fields below so you know what sort of thing to look for. # # The output should mostly be obvious: All sizes are in megabytes (MB), not bits. p4[s|d] memory usage is # shown as Virtual / Physical. If Virtual memory usage of p4s gets anywhere near the total amount of # physical memory, you're likely to run into lots of swapping and performance problems. P4 threads are # sorted according to the time they have been running, with the longest one at the top. Runtime is listed # as hh:mm:ss. Cpu % per thread is a % to total cpu available. So on (say) a 4 core machine, a single thread # can never use more than 25% of cpu and if that happens that thread has become cpu-bound. However, the Cpu % # at the bottom (for proxy / client) are per Cpu (or core), so these can go to 100%. The Connection column # can be useful to see if everybody is using the right proxy/server connection (in which case the Workspace # column can help in determining where the query originated). Just what exactly the disk usage % means is a # bit tricky to explain, but it is a reasonable indicator of disk usage. If it's 0%, nobody is using the disk # at all, and if it's at 100%, the disk has become a bottleneck. # # Please send any questions or comments to me: frank@compagner.com # # Frank Compagner # September 25, 2007. # Below are the settings that you will have to modify to reflect your setup. # The number of lines that the console window is high, should be > ~25 # The console window should be at least 150 characters wide to accomodate all the output nr_of_screen_lines = 50 # number of seconds to wait after each iteration. Performance impact on both client and server should be minimal, # so keep this at 0 if you want quick refresh. Increase this to reduce load (if any) or if you want more time to # study the output from a single snapshot. refresh_delay = 0 # Name of an account that has perforce admin access. p4_admin_name = "admin" # Server details. Change these (all of them!) to the correct values for your environment. # Name of the perforce server process. Should be "p4s" if you installed Perforce as a service, or "p4d" if you run it as # a standalone application p4_process_name = "p4s" # Address of server p4_port = "p4:1666" # Windows networking name of server server_name = "p4" # Name of the typeperf counter for the server network interface server_network_name = "Intel[R] Advanced Network Services Virtual Adapter" # Name of the typeperf counter for the server database disk interface server_db_disk_name = "8 I:" # Name of the typeperf counter for the server depot disk interface server_depot_disk_name = "5 F:" # Proxy details. If you want, you can add a number of Perforce proxies here. Again, proxies should be Windows # machines (XP or later) and the current user should have admin rights on them. The script will then monitor cpu, # network and disk usage on those machines. Add more below by appending to the list, or leave the list empty proxy_list = [] # Windows networking name of proxy proxy_name = "p4_proxy_1" # Nr. of cpu's in proxy proxy_cpu_count = 4 # Name of the typeperf counter for the proxy network interface proxy_network_name = "Intel[R] PRO_1000 MT Network Connection _2" # Name of the typeperf counter for the proxy cache disk interface proxy_disk_name = "0 C: D:" proxy_settings = proxy_name, proxy_cpu_count, proxy_network_name, proxy_disk_name proxy_list.append(proxy_settings) # Client details. If you want, you can also track the stats of a client. This is useful when profiling, # so you can see where the bottlenecks are. Add details below, or set client_name to none to disable # Windows networking name of client client_name = "client" # Nr. of cpu's in client client_cpu_count = 4 # Name of the typeperf counter for the client network interface client_network_name = "Attansic L1 Gigabit Ethernet 10_100_1000Base-T Adapter - Packet Scheduler Miniport" # Name of the typeperf counter for the client workspace disk interface client_disk_name = "0 D:" # Number of p4 processes to show. As calculated, assumes 1 proxy and 1 client to fit on 50 lines. nr_of_p4_procs = nr_of_screen_lines - len(proxy_list) - 16 ######################################################################################################################## import re import sys import traceback import time import os import subprocess import string import socket import platform import getpass ######################################################################################################################## def GetTime(time) : time_info = time.split(":") hours = long(time_info[0]) minutes = long(time_info[1]) seconds_info = time_info[2].split(".") seconds = long(seconds_info[0]) msecs = 0 if len(seconds_info) > 1 and seconds_info[1] : msecs = long(seconds_info[1]) return msecs + 1000 * (seconds + 60 * (minutes + 60 * hours)) ######################################################################################################################## prev_process_time = {} def GetProcessInfo() : global prev_process_time process = subprocess.Popen("pslist \\\\" + server_name, 0, None, None, subprocess.PIPE, subprocess.PIPE) pipe = process.stdout lines = pipe.readlines() pipe.close() process.wait() cur_process_time = {} started = False for line in lines : if not started : started = line.startswith("Name") else : info = line.split() cur_process_time[info[0]] = GetTime(info[6]) process_time = {} total_time = 0 for process, t in cur_process_time.iteritems() : if prev_process_time.has_key(process) : process_time[process] = t - prev_process_time[process] total_time = total_time + process_time[process] prev_process_time = cur_process_time idle_cpu = 0 p4_server_cpu = 0 other_cpu = 0 if process_time.has_key(p4_process_name) : idle_cpu = int(100 * process_time["Idle"] / total_time + 0.5) p4_server_cpu = int(100 * process_time[p4_process_name] / total_time + 0.5) other_cpu = 100 - idle_cpu - p4_server_cpu return total_time, idle_cpu, p4_server_cpu, other_cpu ######################################################################################################################## prev_p4_process_time = {} def GetP4Info() : global prev_p4_process_time process = subprocess.Popen("pslist -e -d -m \\\\" + server_name + " " + p4_process_name, 0, None, None, subprocess.PIPE, subprocess.PIPE) pipe = process.stdout lines = pipe.readlines() pipe.close() process.wait() cur_p4_process_time = {} p4_kb_vm = 0 p4_kb_ws = 0 phase = 0 for line in lines : if phase == 0 : if line.strip().startswith(p4_process_name) : info = line.split() p4_kb_vm = int(info[2]) p4_kb_ws = int(info[3]) phase = 1 elif phase == 1 : if line.strip().startswith("Tid") : phase = 2 else : info = line.split() cur_p4_process_time[info[0]] = GetTime(info[4]) + GetTime(info[5]) p4_process_time = {} for process, t in cur_p4_process_time.iteritems() : if prev_p4_process_time.has_key(process) : p4_process_time[process] = t - prev_p4_process_time[process] prev_p4_process_time = cur_p4_process_time return p4_kb_vm, p4_kb_ws, p4_process_time ######################################################################################################################## def GetMonitorInfo(admin_ok) : p4_info = {} if admin_ok : command = "p4 -u " + p4_admin_name + " -p " + p4_port + " monitor show -a -e -l" else : command = "p4 -p " + p4_port + " monitor show" process = subprocess.Popen(command, 0, None, None, subprocess.PIPE, subprocess.PIPE) pipe = process.stdout lines = pipe.readlines() pipe.close() process.wait() tid = 0 tool = "??" connection = "unknown" status = '?' user = "unknown" client = "unknown" t = 0 command = "unknown" monitor_regex = re.compile("\s*(\S*)(.*)\s+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)\s*(\S)\s*(\S*)\s*(\S*)\s*(\S*)\s*(.*)") for line in lines : if admin_ok : match = monitor_regex.search(line) if match : tid = match.group(1).strip() tool = match.group(2).strip() connection = match.group(3).strip() status = match.group(4).strip() user = match.group(5).strip() client = match.group(6).strip() t = GetTime(match.group(7).strip()) command = match.group(8).strip() else : info = line.split() tid = info[0] status = info[1] user = info[2] t = GetTime(info[3]) command = info[4] p4_info[tid] = tool, connection, status, user, client, t, command return p4_info ######################################################################################################################## def GetMachineStats(machine_name, cpu_count, network_name, disk_name) : if os.name != "nt" or platform.platform().find("2000") >= 0 : return [] stats = None net_counter = "\"\\\\" + machine_name + "\\network interface(" + network_name + ")\\Bytes Total/sec\"" disk_counter = "\"\\\\" + machine_name + "\\physicaldisk(" + disk_name + ")\\% Idle Time\"" command = "typeperf -sc 1 " + net_counter + " " + disk_counter for i in range(cpu_count) : command = command + " \"\\\\" + machine_name + "\\Processor(" + repr(i) + ")\\% Processor Time\"" process = subprocess.Popen(command, 0, None, None, subprocess.PIPE, subprocess.PIPE) pipe = process.stdout lines = pipe.readlines() pipe.close() process.wait() for line in lines : info = line.split(',') if len(info) == 3 + cpu_count : if info[0][1] != '(' : net_MBps = float(info[1].strip()[1:-1]) / (1024 * 1024) disk_busy = int(100.5 - float(info[2].strip()[1:-1])) cpu = [] for i in range(cpu_count) : cpu.append(int(float(info[3 + i].strip()[1:-1]) + 0.5)) stats = net_MBps, disk_busy, cpu return stats ######################################################################################################################## def GetServerStats() : if os.name != "nt" or platform.platform().find("2000") >= 0 : return -1, -1, -1 net_counter = "\"\\\\" + server_name + "\\Network Interface(" + server_network_name + ")\\Bytes Total/sec\"" db_counter = "\"\\\\" + server_name + "\\physicaldisk(" + server_db_disk_name + ")\\% Idle Time\"" depot_counter = "\"\\\\" + server_name + "\\physicaldisk(" + server_depot_disk_name + ")\\% Idle Time\"" command = "typeperf -sc 1 " + net_counter + " " + db_counter + " " + depot_counter process = subprocess.Popen(command, 0, None, None, subprocess.PIPE, subprocess.PIPE) pipe = process.stdout lines = pipe.readlines() pipe.close() process.wait() for line in lines : info = line.split(',') if len(info) == 4 : if info[0][1] != '(' : net_MBps = float(info[1].strip()[1:-1]) / (1024 * 1024) db_busy = int(100.5 - float(info[2].strip()[1:-1])) depot_busy = int(100.5 - float(info[3].strip()[1:-1])) return net_MBps, db_busy, depot_busy ######################################################################################################################## def CheckAdminAccess() : process = subprocess.Popen("p4 -u " + p4_admin_name + " -p " + p4_port + " login -s", 0, None, None, subprocess.PIPE, subprocess.PIPE) err_pipe = process.stderr err_output = err_pipe.read() err_pipe.close() process.wait() # if nothing has gone to std_err, we have admin access return not err_output ######################################################################################################################## def Login(password) : process = subprocess.Popen("p4 -u " + p4_admin_name + " -p " + p4_port + " login", 0, None, subprocess.PIPE, subprocess.PIPE, subprocess.PIPE) in_pipe = process.stdin err_pipe = process.stderr in_pipe.write(password) in_pipe.close() err_output = err_pipe.read() err_pipe.close() process.wait() return not err_output ######################################################################################################################## hosts = {} def GetHostName(ip_address) : global hosts if hosts.has_key(ip_address) : return hosts[ip_address] try : host = socket.gethostbyaddr(ip_address)[0].split('.')[0] except (socket.herror, socket.gaierror) : host = ip_address hosts[ip_address] = host return host ######################################################################################################################## cur_threads = {} def TidKey(tid) : return cur_threads[tid][5] ######################################################################################################################## cur_threads admin_ok = False if CheckAdminAccess() : admin_ok = True else : password = getpass.getpass("Admin password = ") if password : admin_ok = Login(password) while True : cur_threads = GetMonitorInfo(admin_ok) p4_kb_vm, p4_kb_ws, p4_threads = GetP4Info() total_time, idle_cpu, p4_server_cpu, other_cpu = GetProcessInfo() net_MBps, db_busy, depot_busy = GetServerStats() proxy_stats = [] for proxy_settings in proxy_list : proxy_name, proxy_cpu_count, proxy_network_name, proxy_disk_name = proxy_settings stats = GetMachineStats(proxy_name, proxy_cpu_count, proxy_network_name, proxy_disk_name) proxy_stats.append(stats) client_stats = None if client_name : client_stats = GetMachineStats(client_name, client_cpu_count, client_network_name, client_disk_name) os.system('cls') cpu_info = "CPU: " + p4_process_name + " = %2d%% Idle = %2d%% Other = %2d%%" % (p4_server_cpu, idle_cpu, other_cpu) mem_info = " MEMORY: " + p4_process_name + " = %4.0f MB / %4.0f MB" % (float(p4_kb_vm) / 1024.0, float(p4_kb_ws) / 1024.0) net_info = " NETWORK: %3d MB/sec" % net_MBps disk_info = " DISK: db = %2d%% depot = %2d%%" % (db_busy, depot_busy) print cpu_info + mem_info + net_info + disk_info print "-----------------------------------------------------------------------------------------------------------------------------------------------------" print "Runtime User Cpu % Tool Connection Workspace Command" print tids = cur_threads.keys() tids.sort(None, TidKey, True) line_count = 0 for tid in tids : tool, connection, status, user, client, t, command = cur_threads[tid] cpu = "-" if p4_threads.has_key(tid) : cpu = repr(int(100 * p4_threads[tid] / total_time + 0.5)) connection = GetHostName(connection) tool = tool.split('/')[0] hours = int(t / (60 * 60 * 1000)) t = t % (60 * 60 * 1000) minutes = int(t / (60 * 1000)) t = t % (60 * 1000) seconds = int(t / 1000) print "%02d:%02d:%02d %-12s %2s %-5s %-15s %-10s %-80s" % (hours, minutes, seconds, user[:12], cpu, tool[:5], connection[:15], client[:10], command[:80]) line_count = line_count + 1 if line_count >= nr_of_p4_procs : break for i in range(line_count + 1, nr_of_p4_procs + 1) : print print print if proxy_stats : max_cpu_count = 1 for proxy_settings in proxy_list : proxy_name, proxy_cpu_count, proxy_network_name, proxy_disk_name = proxy_settings if proxy_cpu_count > max_cpu_count : max_cpu_count = proxy_cpu_count print "-----------------------------------------------------------------------------------------------------------------------------------------------------" legend = "Proxy " for i in range(max_cpu_count) : legend = legend + "Cpu " + repr(i + 1) + " " legend = legend + " Network Disk" print legend print for stats in proxy_stats : net_MBps, disk_busy, cpu = stats proxy_info = "%-10s " % (proxy_name) for i in range(max_cpu_count) : if i < len(cpu) : proxy_info = proxy_info + " %3d " % cpu[i] else : proxy_info = proxy_info + " " proxy_info = proxy_info + "%3d MB/sec %2d%%" % (net_MBps, disk_busy) print proxy_info else : print print print print print if client_stats : print "-----------------------------------------------------------------------------------------------------------------------------------------------------" legend = "Client " for i in range(client_cpu_count) : legend = legend + "Cpu " + repr(i + 1) + " " legend = legend + " Network Disk" print legend print net_MBps, disk_busy, cpu = client_stats client_info = "%-10s " % (client_name) for i in range(client_cpu_count) : client_info = client_info + " %3d " % cpu[i] client_info = client_info + "%3d MB/sec %2d%%" % (net_MBps, disk_busy) print client_info time.sleep(refresh_delay)