| #!/usr/bin/env python |
| # |
| # win_repo_bench.py: run repository / server performance tests on Windows. |
| # |
| # Subversion is a tool for revision control. |
| # See http://subversion.apache.org for more information. |
| # |
| # ==================================================================== |
| # Licensed to the Apache Software Foundation (ASF) under one |
| # or more contributor license agreements. See the NOTICE file |
| # distributed with this work for additional information |
| # regarding copyright ownership. The ASF licenses this file |
| # to you under the Apache License, Version 2.0 (the |
| # "License"); you may not use this file except in compliance |
| # with the License. You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, |
| # software distributed under the License is distributed on an |
| # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| # KIND, either express or implied. See the License for the |
| # specific language governing permissions and limitations |
| # under the License. |
| ###################################################################### |
| |
| # General modules |
| import os |
| import shutil |
| import sys |
| import subprocess |
| import time |
| |
| from win32com.shell import shell, shellcon |
| |
| # Adapt these paths to your needs |
| |
| # Contains all the REPOSITORIES |
| repo_parent = "C:\\repos" |
| |
| # Where to create working copies |
| wc_path = "C:\\wc" |
| exe_path = "C:\\develop\\Subversion\\trunk\\Release" |
| apache_path = "C:\\develop\\Subversion" |
| |
| # Test these repositories and in this order. |
| # Actual repository names have numbers 0 .. REPETITIONS-1 append to them |
| repositories = ["ruby-f6-nonpacked", "ruby-f7-nonpacked", |
| "ruby-f6-packed", "ruby-f7-packed", |
| "bsd-f6-nonpacked", "bsd-f7-nonpacked", |
| "bsd-f6-packed", "bsd-f7-packed"] |
| |
| # Basically lists the RA backends to test but as long as all repositories |
| # can be accessed using any of them, arbitrary URLs are possible. |
| prefixes = ["svn://localhost/", "http://localhost/svn/", "file:///C:/repos/"] |
| |
| # Number of time to repeat the tests. For each iteration, there must be |
| # a separate copy of all repositories. |
| repetitions = 3 |
| |
| # Server configurations to test |
| configurations = ['slow', 'medium', 'fast'] |
| svnserve_params = { |
| 'slow':"", |
| 'medium':"-M 256" , |
| 'fast':"-M 1024 -c 0 --cache-revprops yes --block-read yes --client-speed 1000" |
| } |
| |
| |
| def clear_memory(): |
| """ Clear in-RAM portion of the file / disk cache """ |
| subprocess.call(["ClearMemory.exe"]) |
| |
| def start_server(prefix, config): |
| """ Depending on the url PREFIX, start the corresponding server with the |
| given CONFIGuration. file: and http: access will actually have been |
| configured by set_config(). """ |
| |
| if prefix[:4] == "svn:": |
| exe = os.path.join(exe_path, "svnserve.exe") |
| command = "cmd.exe /c start " + exe + " -dr " + repo_parent + \ |
| " " + svnserve_params[config] |
| subprocess.call(command) |
| time.sleep(2) |
| elif prefix[:5] == "http:": |
| exe = os.path.join(apache_path, 'bin', 'httpd.exe') |
| subprocess.call(exe + " -k start") |
| time.sleep(2) |
| |
| def stop_server(prefix): |
| """ Depending on the url PREFIX, stop / kill the corresponding server. """ |
| |
| if prefix[:4] == "svn:": |
| subprocess.call("cmd.exe /c taskkill /im svnserve.exe /f > nul 2>&1") |
| time.sleep(1) |
| elif prefix[:5] == "http:": |
| exe = os.path.join(apache_path, 'bin', 'httpd.exe') |
| subprocess.call(exe + " -k stop") |
| time.sleep(1) |
| |
| def run_cs_command(state, config, repository, prefix, args): |
| """ Run the client-side command given in ARGS. Log the STATE of the |
| caches, the CONFIG we are using, the REPOSITORY, the url PREFIX |
| and finally the execution times. """ |
| |
| # Make sure we can create a new working copy if we want to. |
| if os.path.exists(wc_path): |
| shutil.rmtree(wc_path) |
| |
| # Select the client to use. |
| if ('null-export' in args) or ('null-log' in args): |
| exe = os.path.join(exe_path, "svn-bench.exe") |
| else: |
| exe = os.path.join(exe_path, "svn.exe") |
| |
| # Display the operation |
| repo_title = repository.replace('nonpacked', 'nopack') |
| print state, "\t", repo_title, "\t", prefix, "\t", config, "\t", |
| sys.stdout.flush() |
| |
| # Execute the command and show the execution times |
| subprocess.call(["TimeWin.exe", exe] + args) |
| |
| |
| def run_test_cs_sequence(config, repository, run, prefix, command, args): |
| """ Run the client-side COMMAND with the given ARGS in various stages |
| of cache heat-up. Execute the test with server CONFIG on REPOSITORY |
| with the given url PREFIX. """ |
| |
| # Build the full URL to use. Exports operate on the main dev line only. |
| url = prefix + repository + str(run) |
| if (command == 'export') or (command == 'null-export'): |
| if repository[:3] == 'bsd': |
| url += '/head' |
| else: |
| url += '/trunk' |
| |
| # Full set of command arguments |
| args = [command, url] + args |
| |
| # Free up caches best we can. |
| clear_memory() |
| |
| # Caches are quite cool now and ready to take up new data |
| start_server(prefix, config) |
| run_cs_command("Cold", config, repository, prefix, args) |
| stop_server(prefix) |
| |
| # OS caches are quite hot now. |
| # Run operation from hot OS caches but cold SVN caches. |
| start_server(prefix, config) |
| run_cs_command("WarmOS", config, repository, prefix, args) |
| stop_server(prefix) |
| |
| # OS caches may be even hotter now. |
| # Run operation from hot OS caches but cold SVN caches. |
| start_server(prefix, config) |
| run_cs_command("HotOS", config, repository, prefix, args) |
| |
| # Keep server process and thus the warmed up SVN caches. |
| # Run operation from hot OS and SVN caches. |
| run_cs_command("WrmSVN", config, repository, prefix, args) |
| run_cs_command("HotSVN", config, repository, prefix, args) |
| stop_server(prefix) |
| |
| |
| def set_config(config): |
| """ Switch configuration files to CONFIG. This overwrites the client |
| config file with config.$CONFIG and the server config file with |
| subversion.$CONFIG.conf. """ |
| |
| appdata = shell.SHGetFolderPath(0, shellcon.CSIDL_APPDATA, None, 0) |
| svn_config_folder = os.path.join(appdata, 'Subversion') |
| svn_config_file = os.path.join(svn_config_folder, 'config') |
| svn_config_template = svn_config_file + '.' + config |
| |
| shutil.copyfile(svn_config_template, svn_config_file) |
| |
| apache_config_folder = os.path.join(apache_path, 'conf', 'extra') |
| apache_config_file = os.path.join(apache_config_folder, 'subversion.conf') |
| apache_config_template = os.path.join(apache_config_folder, |
| 'subversion.' + config + '.conf') |
| |
| shutil.copyfile(apache_config_template, apache_config_file) |
| |
| |
| def run_test_cs_configurations(command, args): |
| """ Run client COMMAND with basic arguments ARGS in all configurations |
| repeatedly with all servers on all repositories. """ |
| |
| print |
| print command |
| print |
| |
| for config in configurations: |
| set_config(config) |
| for prefix in prefixes: |
| # These two must be the innermost loops and must be in that order. |
| # It gives us the coldest caches and the least temporal favoritism. |
| for run in range(0, repetitions): |
| for repository in repositories: |
| run_test_cs_sequence(config, repository, run, prefix, command, args) |
| |
| def run_admin_command(state, config, repository, args): |
| """ Run the svnadmin command given in ARGS. Log the STATE of the |
| caches, the CONFIG we are using, the REPOSITORY and finally |
| the execution times. """ |
| |
| exe = os.path.join(exe_path, "svnadmin.exe") |
| |
| if config == 'medium': |
| extra = ['-M', '256'] |
| elif config == 'fast': |
| extra = ['-M', '1024'] |
| else: |
| extra = [] |
| |
| print state, "\t", repository, "\t", config, "\t", |
| sys.stdout.flush() |
| subprocess.call(["TimeWin.exe", exe] + args + extra) |
| |
| def run_test_admin_sequence(config, repository, run, command, args): |
| """ Run the svnadmin COMMAND with the given ARGS in various stages |
| of cache heat-up. Execute the test with server CONFIG on |
| REPOSITORY. """ |
| |
| # Full set of command arguments |
| path = os.path.join(repo_parent, repository + str(run)) |
| args = [command, path] + args |
| |
| # Free up caches best we can. |
| clear_memory() |
| |
| # svnadmin runs can be quite costly and are usually CPU-bound. |
| # Test with "cold" and "hot" CPU caches only. |
| run_admin_command("Cold", config, repository, args) |
| run_admin_command("Hot", config, repository, args) |
| |
| |
| def run_test_admin_configurations(command, args): |
| """ Run svnadmin COMMAND with basic arguments ARGS in all configurations |
| repeatedly on all repositories. """ |
| |
| print |
| print command |
| print |
| |
| for config in configurations: |
| # These two must be the innermost loops and must be in that order. |
| # It gives us the coldest caches and the least temporal favoritism. |
| for run in range(0, repetitions): |
| for repository in repositories: |
| run_test_admin_sequence(config, repository, run, command, args) |
| |
| |
| def bench(): |
| """ Run all performance tests. """ |
| |
| run_test_cs_configurations('log', ['-v', '--limit', '50000']) |
| run_test_cs_configurations('export', [wc_path, '-q']) |
| |
| run_test_cs_configurations('null-log', ['-v', '--limit', '50000', '-q']) |
| run_test_cs_configurations('null-export', ['-q']) |
| |
| run_test_admin_configurations('dump', ['-q']) |
| |
| # main function |
| bench() |