tools: bugfixes and new features for oldv and performance_compare

pull/3375/head
Delyan Angelov 2020-01-08 22:45:47 +02:00 committed by Alexander Medvednikov
parent 0d93eeb3fe
commit c1cc203c17
8 changed files with 489 additions and 381 deletions

3
.gitignore vendored
View File

@ -52,3 +52,6 @@ vlib/os/bare/bare_example_linux
info.log
.vscode/**
# vim/emacs editor backup files
*~

View File

@ -2,17 +2,27 @@ module scripting
import os
pub fn verbose_trace(label string, message string){
pub fn set_verbose(on bool) {
// setting a global here would be the obvious solution,
// but V does not have globals normally.
if on {
os.setenv('VERBOSE', '1', true)
}
else {
os.unsetenv('VERBOSE')
}
}
pub fn verbose_trace(label string, message string) {
if os.getenv('VERBOSE').len > 0 {
slabel := 'scripting.${label}'
println('# ${slabel:30s} : $message')
println('# ${slabel:-25s} : $message')
}
}
pub fn verbose_trace_exec_result(x os.Result) {
if os.getenv('VERBOSE').len > 0 {
println('# cmd.exit_code : ${x.exit_code.str()}')
println('# cmd.output :')
println('# cmd.exit_code : ${x.exit_code.str():-4s} cmd.output:')
println('# ----------------------------------- #')
mut lnum := 1
lines := x.output.split_into_lines()
@ -21,33 +31,45 @@ pub fn verbose_trace_exec_result(x os.Result) {
lnum++
}
println('# ----------------------------------- #')
}
}
}
pub fn chdir(path string) {
verbose_trace(@FN, 'cd $path')
os.chdir( path )
os.chdir(path)
}
pub fn run(cmd string) string {
verbose_trace(@FN, cmd)
x := os.exec(cmd) or { return '' }
verbose_trace_exec_result( x )
if x.exit_code == 0 { return x.output }
x := os.exec(cmd) or {
verbose_trace(@FN, '## failed.')
return ''
}
verbose_trace_exec_result(x)
if x.exit_code == 0 {
return x.output
}
return ''
}
pub fn command_exits_with_zero_status(cmd string) bool {
pub fn exit_0_status(cmd string) bool {
verbose_trace(@FN, cmd)
x := os.exec(cmd) or { return false }
verbose_trace_exec_result( x )
if x.exit_code == 0 { return true }
x := os.exec(cmd) or {
verbose_trace(@FN, '## failed.')
return false
}
verbose_trace_exec_result(x)
if x.exit_code == 0 {
return true
}
return false
}
pub fn tool_must_exist(toolcmd string) {
pub fn tool_must_exist (toolcmd string) {
verbose_trace(@FN, toolcmd)
if command_exits_with_zero_status( 'type $toolcmd' ) { return }
if exit_0_status('type $toolcmd') {
return
}
eprintln('Missing tool: $toolcmd')
eprintln('Please try again after you install it.')
exit(1)
@ -59,14 +81,9 @@ pub fn used_tools_must_exist(tools []string) {
}
}
pub fn check_v_commit_timestamp_before_self_rebuilding(v_timestamp int) {
if v_timestamp >= 1561805697 { return }
eprintln('##################################################################')
eprintln('# WARNING: v self rebuilding, before 5b7a1e8 (2019-06-29 12:21) #')
eprintln('# required the v executable to be built *inside* #')
eprintln('# the toplevel compiler/ folder. #')
eprintln('# #')
eprintln('# That is not supported by this tool. #')
eprintln('# You will have to build it manually there. #')
eprintln('##################################################################')
pub fn show_sizes_of_files(files []string) {
for f in files {
size := os.file_size(f)
println('${size:10d} $f')
}
}

View File

@ -0,0 +1,174 @@
module vgit
import os
import flag
import filepath
import scripting
const (
remote_v_repo_url = 'https://github.com/vlang/v'
remote_vc_repo_url = 'https://github.com/vlang/vc'
)
pub fn check_v_commit_timestamp_before_self_rebuilding(v_timestamp int) {
if v_timestamp >= 1561805697 {
return
}
eprintln('##################################################################')
eprintln('# WARNING: v self rebuilding, before 5b7a1e8 (2019-06-29 12:21) #')
eprintln('# required the v executable to be built *inside* #')
eprintln('# the toplevel compiler/ folder. #')
eprintln('# #')
eprintln('# That is not supported by this tool. #')
eprintln('# You will have to build it manually there. #')
eprintln('##################################################################')
}
pub fn validate_commit_exists(commit string) {
if commit.len == 0 {
return
}
cmd := "git cat-file -t \'$commit\' "
if !scripting.exit_0_status(cmd) {
eprintln('Commit: "$commit" does not exist in the current repository.')
exit(3)
}
}
pub fn line_to_timestamp_and_commit(line string) (int,string) {
parts := line.split(' ')
return parts[0].int(),parts[1]
}
pub fn normalized_workpath_for_commit(workdir string, commit string) string {
nc := 'v_at_' + commit.replace('^', '_').replace('-', '_').replace('/', '_')
return os.realpath(workdir + os.path_separator + nc)
}
pub fn prepare_vc_source(vcdir string, cdir string, commit string) (string,string) {
scripting.chdir(cdir)
// Building a historic v with the latest vc is not always possible ...
// It is more likely, that the vc *at the time of the v commit*,
// or slightly before that time will be able to build the historic v:
vline := scripting.run('git rev-list -n1 --timestamp "$commit" ')
v_timestamp,v_commithash := vgit.line_to_timestamp_and_commit(vline)
vgit.check_v_commit_timestamp_before_self_rebuilding(v_timestamp)
scripting.chdir(vcdir)
scripting.run('git checkout master')
vcbefore := scripting.run('git rev-list HEAD -n1 --timestamp --before=$v_timestamp ')
_,vccommit_before := vgit.line_to_timestamp_and_commit(vcbefore)
scripting.run('git checkout "$vccommit_before" ')
scripting.run('wc *.c')
scripting.chdir(cdir)
return v_commithash,vccommit_before
}
pub fn clone_or_pull( remote_git_url string, local_worktree_path string ) {
// NB: after clone_or_pull, the current repo branch is === HEAD === master
if os.is_dir( local_worktree_path ) && os.is_dir(filepath.join(local_worktree_path,'.git')) {
// Already existing ... Just pulling in this case is faster usually.
scripting.run('git -C "$local_worktree_path" checkout --quiet master')
scripting.run('git -C "$local_worktree_path" pull --quiet ')
} else {
// Clone a fresh
scripting.run('git clone --quiet "$remote_git_url" "$local_worktree_path" ')
}
}
//
pub struct VGitContext {
pub:
cc string = 'cc' // what compiler to use
workdir string = '/tmp' // the base working folder
commit_v string = 'master' // the commit-ish that needs to be prepared
path_v string // where is the local working copy v repo
path_vc string // where is the local working copy vc repo
v_repo_url string // the remote v repo URL
vc_repo_url string // the remote vc repo URL
pub mut:
// these will be filled by vgitcontext.compile_oldv_if_needed()
commit_v__hash string // the git commit of the v repo that should be prepared
commit_vc_hash string // the git commit of the vc repo, corresponding to commit_v__hash
vexename string // v or v.exe
vexepath string // the full absolute path to the prepared v/v.exe
vvlocation string // v.v or compiler/ , depending on v version
}
pub fn (vgit_context mut VGitContext) compile_oldv_if_needed() {
vgit_context.vexename = if os.user_os() == 'windows' { 'v.exe' } else { 'v' }
vgit_context.vexepath = os.realpath( filepath.join(vgit_context.path_v, vgit_context.vexename) )
mut command_for_building_v_from_c_source := ''
mut command_for_selfbuilding := ''
if 'windows' == os.user_os() {
command_for_building_v_from_c_source = '$vgit_context.cc -std=c99 -municode -w -o cv.exe "$vgit_context.path_vc/v_win.c" '
command_for_selfbuilding = './cv.exe -o $vgit_context.vexename {SOURCE}'
}
else {
command_for_building_v_from_c_source = '$vgit_context.cc -std=gnu11 -w -o cv "$vgit_context.path_vc/v.c" -lm'
command_for_selfbuilding = './cv -o $vgit_context.vexename {SOURCE}'
}
scripting.chdir(vgit_context.workdir)
clone_or_pull( vgit_context.v_repo_url, vgit_context.path_v )
clone_or_pull( vgit_context.vc_repo_url, vgit_context.path_vc )
scripting.chdir(vgit_context.path_v)
scripting.run('git checkout $vgit_context.commit_v')
v_commithash,vccommit_before := vgit.prepare_vc_source(vgit_context.path_vc, vgit_context.path_v, vgit_context.commit_v)
vgit_context.commit_v__hash = v_commithash
vgit_context.commit_vc_hash = vccommit_before
vgit_context.vvlocation = if os.exists('v.v') { 'v.v' } else { 'compiler' }
if os.is_dir(vgit_context.path_v) && os.exists(vgit_context.vexepath) {
// already compiled, so no need to compile v again
return
}
// Recompilation is needed. Just to be sure, clean up everything first.
scripting.run('git clean -xf')
scripting.run(command_for_building_v_from_c_source)
build_cmd := command_for_selfbuilding.replace('{SOURCE}', vgit_context.vvlocation)
scripting.run(build_cmd)
// At this point, there exists a file vgit_context.vexepath
// which should be a valid working V executable.
}
pub fn add_common_tool_options<T>(context mut T, fp mut flag.FlagParser) []string {
tdir := os.tmpdir()
context.workdir = os.realpath(fp.string_('workdir', `w`, tdir, 'A writable base folder. Default: $tdir'))
context.v_repo_url = fp.string('vrepo', vgit.remote_v_repo_url, 'The url of the V repository. You can clone it locally too. See also --vcrepo below.')
context.vc_repo_url = fp.string('vcrepo', vgit.remote_vc_repo_url, 'The url of the vc repository. You can clone it
${flag.SPACE}beforehand, and then just give the local folder
${flag.SPACE}path here. That will eliminate the network ops
${flag.SPACE}done by this tool, which is useful, if you want
${flag.SPACE}to script it/run it in a restrictive vps/docker.
')
context.show_help = fp.bool_('help', `h`, false, 'Show this help screen.')
context.verbose = fp.bool_('verbose', `v`, false, 'Be more verbose.')
if (context.show_help) {
println(fp.usage())
exit(0)
}
if context.verbose {
scripting.set_verbose(true)
}
if os.is_dir(context.v_repo_url) {
context.v_repo_url = os.realpath( context.v_repo_url )
}
if os.is_dir(context.vc_repo_url) {
context.vc_repo_url = os.realpath( context.vc_repo_url )
}
commits := fp.finalize() or {
eprintln('Error: ' + err)
exit(1)
}
for commit in commits {
vgit.validate_commit_exists(commit)
}
return commits
}

View File

@ -3,169 +3,101 @@ import (
flag
filepath
scripting
)
vgit
)
const (
tool_version = '0.0.2'
tool_description = 'Checkout an old V and compile it. Useful when you want to discover when something broke.'
remote_repo_url_v = 'https://github.com/vlang/v'
remote_repo_url_vc = 'https://github.com/vlang/vc'
tool_version = '0.0.3'
tool_description = ' Checkout an old V and compile it as it was on specific commit.
This tool is useful, when you want to discover when something broke.
It is also useful, when you just want to experiment with an older historic V.
The VCOMMIT argument can be a git commitish like HEAD or master and so on.
When oldv is used with git bisect, you probably want to give HEAD. For example:
git bisect start
git bisect bad
git checkout known_good_commit
git bisect good
## Now git will automatically checkout a middle commit between the bad and the good
tools/oldv HEAD --command="run commands in oldv folder, to verify if the commit is good or bad"
## See what the result is, and either do: ...
git bisect good
## ... or do:
git bisect bad
## Now you just repeat the above steps, each time running oldv with the same command, then mark the result as good or bad,
## until you find the commit, where the problem first occured.
## When you finish, do not forget to do:
git bisect reset'
)
struct Context {
mut:
repo_url_v string // the url of the V repository. It can be a local folder path, if you want to eliminate network operations...
repo_url_vc string // the url of the vc repository. It can be a local folder path, if you want to eliminate network operations...
workdir string // the working folder (typically /tmp), where the tool will write
commit_v string = 'master' // the commit from which you want to produce a working v compiler (this may be a commit-ish too)
commit_vc string = 'master' // this will be derived from commit_v
commit_v_hash string // this will be filled from the commit-ish commit_v using rev-list. It IS a commit hash.
path_v string // the full path to the v folder inside workdir.
path_vc string // the full path to the vc folder inside workdir.
cmd_to_run string // the command that you want to run *in* the oldv repo
cc string = 'cc' // the C compiler to use for bootstrapping.
cleanup bool // should the tool run a cleanup first
verbose bool // should the tool be much more verbose
v_repo_url string // the url of the V repository. It can be a local folder path, if you want to eliminate network operations...
vc_repo_url string // the url of the vc repository. It can be a local folder path, if you want to eliminate network operations...
workdir string // the working folder (typically /tmp), where the tool will write
commit_v string='master' // the commit from which you want to produce a working v compiler (this may be a commit-ish too)
commit_vc string='master' // this will be derived from commit_v
commit_v_hash string // this will be filled from the commit-ish commit_v using rev-list. It IS a commit hash.
path_v string // the full path to the v folder inside workdir.
path_vc string // the full path to the vc folder inside workdir.
cmd_to_run string // the command that you want to run *in* the oldv repo
cc string='cc' // the C compiler to use for bootstrapping.
cleanup bool // should the tool run a cleanup first
verbose bool // should the tool be much more verbose
show_help bool // whether to show the usage screen
}
fn (c mut Context) compile_oldv_if_needed() {
vexename := if os.user_os() == 'windows' { 'v.exe' } else { 'v' }
vexepath := filepath.join( c.path_v, vexename )
mut command_for_building_v_from_c_source := ''
mut commands_for_selfbuilding := []string
if 'windows' == os.user_os() {
command_for_building_v_from_c_source = '$c.cc -w -o cv.exe "$c.path_vc/v_win.c" '
commands_for_selfbuilding << './cv.exe -o v2.exe {SOURCE}'
commands_for_selfbuilding << './v2.exe -o $vexename {SOURCE}'
}else{
command_for_building_v_from_c_source = '$c.cc -w -o cv "$c.path_vc/v.c" -lm'
commands_for_selfbuilding << './cv -o $vexename {SOURCE}'
fn (c mut Context) compile_oldv_if_needed() {
mut vgit_context := vgit.VGitContext{
cc: c.cc
workdir: c.workdir
commit_v: c.commit_v
path_v: c.path_v
path_vc: c.path_vc
v_repo_url: c.v_repo_url
vc_repo_url: c.vc_repo_url
}
scripting.chdir( c.workdir )
scripting.run('git clone --quiet "$c.repo_url_v" "$c.path_v" ')
scripting.run('git clone --quiet "$c.repo_url_vc" "$c.path_vc" ')
scripting.chdir( c.path_v )
scripting.run('git checkout $c.commit_v')
c.prepare_vc_source( c.commit_v )
if os.is_dir( c.path_v ) && os.exists( vexepath ) { return }
scripting.run('git clean -f')
source_location := if os.exists('v.v') { 'v.v' } else { 'compiler' }
scripting.run( command_for_building_v_from_c_source )
for cmd in commands_for_selfbuilding {
build_cmd := cmd.replace('{SOURCE}', source_location)
scripting.run( build_cmd )
}
if !os.exists( vexepath ) && c.cmd_to_run.len > 0 {
vgit_context.compile_oldv_if_needed()
c.commit_v_hash = vgit_context.commit_v__hash
if !os.exists(vgit_context.vexepath) && c.cmd_to_run.len > 0 {
// NB: 125 is a special code, that git bisect understands as 'skip this commit'.
// it is used to inform git bisect that the current commit leads to a build failure.
exit( 125 )
}
}
fn line_to_timestamp_and_commit(line string) (int, string) {
parts := line.split(' ')
return parts[0].int(), parts[1]
}
fn (c mut Context) prepare_vc_source( commit string ) {
scripting.chdir( c.path_v )
// Building a historic v with the latest vc is not always possible ...
// It is more likely, that the vc *at the time of the v commit*,
// or slightly before that time will be able to build the historic v:
vline := scripting.run('git rev-list -n1 --timestamp "$commit" ')
v_timestamp, v_commithash := line_to_timestamp_and_commit( vline )
c.commit_v_hash = v_commithash
scripting.check_v_commit_timestamp_before_self_rebuilding(v_timestamp)
scripting.chdir( c.path_vc )
scripting.run('git checkout master')
vcbefore := scripting.run('git rev-list HEAD -n1 --timestamp --before=$v_timestamp ')
_, vccommit_before := line_to_timestamp_and_commit( vcbefore )
c.commit_vc = vccommit_before
scripting.run('git checkout "$vccommit_before" ')
scripting.chdir( c.path_v )
}
fn (c Context) normalized_workpath_for_commit( commit string ) string {
nc := 'v_at_' + commit.replace('^','_').replace('-','_').replace('/','_')
return os.realpath( c.workdir + os.path_separator + nc )
}
fn validate_commit_exists( commit string ){
cmd := 'git cat-file -t ' + "'" + commit + "'"
if !scripting.command_exits_with_zero_status(cmd) {
eprintln("Commit: '" + commit + "' does not exist in the current repository.")
exit(3)
exit(125)
}
}
fn main(){
scripting.used_tools_must_exist(['git','cc'])
fn main() {
scripting.used_tools_must_exist(['git', 'cc'])
mut context := Context{}
mut fp := flag.new_flag_parser(os.args)
fp.application(filepath.filename(os.executable()))
fp.version( tool_version )
fp.description( tool_description )
fp.version(tool_version)
fp.description(tool_description)
fp.arguments_description('VCOMMIT')
fp.skip_executable()
fp.limit_free_args(1, 1)
show_help:=fp.bool_('help', `h`, false, 'Show this help screen.')
context.verbose = fp.bool_('verbose', `v`, false, 'Be more verbose.\n')
context.cleanup = fp.bool('clean', true, 'Clean before running (slower).')
context.cmd_to_run = fp.string_('command', `c`, '', 'Command to run in the old V repo.')
context.workdir = os.realpath( fp.string_('work-dir', `w`, os.tmpdir(), 'A writable folder, where the comparison will be done.\n') )
context.repo_url_v = fp.string('v-repo', remote_repo_url_v, 'The url of the V repository. You can clone it locally too.\n')
context.repo_url_vc = fp.string('vc-repo', remote_repo_url_vc, '' +
'The url of the vc repository. You can clone it \n'+
flag.SPACE+'beforehand, and then just give the local folder \n'+
flag.SPACE+'path here. That will eliminate the network ops \n'+
flag.SPACE+'done by this tool, which is useful, if you want \n'+
flag.SPACE+'to script it/run it in a restrictive vps/docker.')
if( show_help ){
println( fp.usage() )
exit(0)
}
if context.verbose {
os.setenv('VERBOSE','true',true)
}
commits := fp.finalize() or {
eprintln('Error: ' + err)
exit(1)
}
context.cleanup = fp.bool('clean', true, 'Clean before running (slower).')
context.cmd_to_run = fp.string_('command', `c`, '', 'Command to run in the old V repo.\n')
commits := vgit.add_common_tool_options(mut context, mut fp)
if commits.len > 0 {
context.commit_v = commits[0]
validate_commit_exists( context.commit_v )
}else{
} else {
context.commit_v = scripting.run('git rev-list -n1 HEAD')
}
println('################# context.commit_v: $context.commit_v #####################')
context.path_v = context.normalized_workpath_for_commit( context.commit_v )
context.path_vc = context.normalized_workpath_for_commit( 'vc' )
if !os.is_dir( context.workdir ) {
context.path_v = vgit.normalized_workpath_for_commit(context.workdir, context.commit_v)
context.path_vc = vgit.normalized_workpath_for_commit(context.workdir, 'vc')
if !os.is_dir(context.workdir) {
msg := 'Work folder: ' + context.workdir + ' , does not exist.'
eprintln(msg)
exit(2)
}
ecc := os.getenv('CC')
if ecc!='' { context.cc = ecc }
if ecc != '' {
context.cc = ecc
}
if context.cleanup {
scripting.run('rm -rf $context.path_v')
scripting.run('rm -rf $context.path_vc')
@ -173,17 +105,16 @@ fn main(){
context.compile_oldv_if_needed()
scripting.chdir( context.path_v )
scripting.chdir(context.path_v)
println('# v commit hash: $context.commit_v_hash')
println('# checkout folder: $context.path_v')
if context.cmd_to_run.len > 0 {
cmdres := os.exec( context.cmd_to_run ) or { panic(err) }
println('# command: $context.cmd_to_run')
println('# command exit code: $cmdres.exit_code')
println('# command result :')
cmdres := os.exec(context.cmd_to_run) or {
panic(err)
}
println('# command: ${context.cmd_to_run:-34s} exit code: ${cmdres.exit_code:-4d} result:')
println(cmdres.output)
exit( cmdres.exit_code )
exit(cmdres.exit_code)
}
}

View File

@ -2,266 +2,199 @@ import (
os
flag
filepath
scripting
vgit
)
const (
tool_version = '0.0.4'
tool_description = '' +
' Compares V executable size and performance,\n' +
' between 2 commits from V\'s local git history.\n' +
' When only one commit is given, it is compared to master.'
tool_version = '0.0.5'
tool_description = ' Compares V executable size and performance,
between 2 commits from V\'s local git history.
When only one commit is given, it is compared to master.
'
)
struct Context {
cwd string // current working folder
cwd string // current working folder
mut:
vc_repo_url string // the url of the vc repository. It can be a local folder path, which is usefull to eliminate network operations...
workdir string // the working folder (typically /tmp), where the tool will write
a string // the full path to the 'after' folder inside workdir
b string // the full path to the 'before' folder inside workdir
vc string // the full path to the vc folder inside workdir. It is used during bootstrapping v from the C source.
v_repo_url string // the url of the vc repository. It can be a local folder path, which is usefull to eliminate network operations...
vc_repo_url string // the url of the vc repository. It can be a local folder path, which is usefull to eliminate network operations...
workdir string // the working folder (typically /tmp), where the tool will write
a string // the full path to the 'after' folder inside workdir
b string // the full path to the 'before' folder inside workdir
vc string // the full path to the vc folder inside workdir. It is used during bootstrapping v from the C source.
commit_before string // the git commit for the 'before' state
commit_after string // the git commit for the 'after' state
warmups int // how many times to execute a command before gathering stats
verbose bool // whether to print even more stuff
warmups int // how many times to execute a command before gathering stats
verbose bool // whether to print even more stuff
show_help bool // whether to show the usage screen
hyperfineopts string // use for additional CLI options that will be given to the hyperfine command
vflags string // other v options to pass to compared v commands
}
fn new_context() Context {
return Context{ cwd: os.getwd(), commit_after: 'master', warmups: 4 }
}
////// The stuff in this block may be reusable for other v cli tools? /////////////////
fn run(cmd string) string {
x := os.exec(cmd) or { return '' }
if x.exit_code == 0 { return x.output }
return ''
}
fn command_exits_with_zero_status(cmd string) bool {
x := os.exec(cmd) or { return false }
if x.exit_code == 0 { return true }
return false
}
fn tool_must_exist(toolcmd string) {
if command_exits_with_zero_status( 'type $toolcmd' ) { return }
eprintln('Missing tool: $toolcmd')
eprintln('Please try again after you install it.')
exit(1)
}
fn used_tools_must_exist(tools []string) {
for t in tools {
tool_must_exist(t)
return Context{
cwd: os.getwd()
commit_after: 'master'
warmups: 4
}
}
//////////////////////////////////////////////////////////////////////////
fn (c Context) compare_versions() {
// Input is validated at this point...
// Cleanup artifacts from previous runs of this tool:
os.chdir( c.workdir )
run('rm -rf "$c.a" "$c.b" "$c.vc" ')
scripting.chdir(c.workdir)
scripting.run('rm -rf "$c.a" "$c.b" "$c.vc" ')
// clone the VC source *just once per comparison*, and reuse it:
run('git clone --quiet \'$c.vc_repo_url\' \'$c.vc\' ')
scripting.run('git clone --quiet "$c.vc_repo_url" "$c.vc" ')
println('Comparing V performance of commit $c.commit_before (before) vs commit $c.commit_after (after) ...')
c.prepare_v( c.b , c.commit_before )
c.prepare_v( c.a , c.commit_after )
c.prepare_v(c.b, c.commit_before)
c.prepare_v(c.a, c.commit_after)
scripting.chdir(c.workdir)
os.chdir( c.workdir )
//The first is the baseline, against which all the others will be compared.
//It is the fastest, since hello_world.v has only a single println in it,
c.compare_v_performance([
'vprod @DEBUG@ -o source.c examples/hello_world.v',
'vprod -o source.c examples/hello_world.v',
'vprod @DEBUG@ -o source.c @COMPILER@',
'vprod -o source.c @COMPILER@',
'vprod -o hello examples/hello_world.v',
'vprod -o binary @COMPILER@',
/////////////////////////////////////////////////////////
'v @DEBUG@ -o source.c examples/hello_world.v',
'v -o source.c examples/hello_world.v',
'v @DEBUG@ -o source.c @COMPILER@',
'v -o source.c @COMPILER@',
'v -o hello examples/hello_world.v',
'v -o binary @COMPILER@',
])
if c.vflags.len > 0 {
os.setenv('VFLAGS', c.vflags, true)
}
// The first is the baseline, against which all the others will be compared.
// It is the fastest, since hello_world.v has only a single println in it,
mut perf_files := []string
perf_files << c.compare_v_performance('source_hello', [
'vprod @DEBUG@ -o source.c examples/hello_world.v',
'vprod -o source.c examples/hello_world.v',
'v @DEBUG@ -o source.c examples/hello_world.v',
'v -o source.c examples/hello_world.v',
])
perf_files << c.compare_v_performance('source_v', [
'vprod @DEBUG@ -o source.c @COMPILER@',
'vprod -o source.c @COMPILER@',
'v @DEBUG@ -o source.c @COMPILER@',
'v -o source.c @COMPILER@',
])
}
perf_files << c.compare_v_performance('binary_hello', [
'vprod -o hello examples/hello_world.v',
'v -o hello examples/hello_world.v',
])
perf_files << c.compare_v_performance('binary_v', [
'vprod -o binary @COMPILER@',
'v -o binary @COMPILER@',
])
fn show_sizes_of_files(files []string) {
for f in files {
size := os.file_size(f)
println('${size:10d} $f')
println('All performance files:')
for f in perf_files {
println(' $f')
}
}
fn line_to_timestamp_and_commit(line string) (int, string) {
parts := line.split(' ')
return parts[0].int(), parts[1]
}
fn (c &Context) prepare_vc_source( cdir string, commit string ) {
os.chdir( cdir )
// Building a historic v with the latest vc is not always possible ...
// It is more likely, that the vc *at the time of the v commit*,
// or slightly before that time will be able to build the historic v:
vline := run('git rev-list -n1 --timestamp \'$commit\' ')
v_timestamp, _ := line_to_timestamp_and_commit( vline )
os.chdir( c.vc )
run('git checkout master')
vcbefore := run('git rev-list HEAD -n1 --timestamp --before=$v_timestamp ')
_, vccommit_before := line_to_timestamp_and_commit( vcbefore )
run('git checkout \'$vccommit_before\' ')
os.chdir( cdir )
}
fn (c &Context) prepare_v( cdir string, commit string ) {
fn (c &Context) prepare_v(cdir string, commit string) {
mut cc := os.getenv('CC')
if cc == '' { cc = 'cc' }
mut command_for_building_v_from_c_source := '$cc -std=gnu11 -w -o cv $c.vc/v.c -lm'
if 'windows' == os.user_os() {
command_for_building_v_from_c_source = '$cc -std=gnu11 -w -o cv.exe $c.vc/v_win.c'
if cc == '' {
cc = 'cc'
}
mut vgit_context := vgit.VGitContext{
cc: cc
workdir: c.workdir
commit_v: commit
path_v: cdir
path_vc: c.vc
v_repo_url: c.v_repo_url
vc_repo_url: c.vc_repo_url
}
vgit_context.compile_oldv_if_needed()
scripting.chdir(cdir)
println('Making a v compiler in $cdir')
scripting.run('./v -cc ${cc} -o v $vgit_context.vvlocation')
println('Making a vprod compiler in $cdir')
scripting.run('./v -cc ${cc} -prod -o vprod $vgit_context.vvlocation')
println('Stripping and compressing cv v and vprod binaries in $cdir')
scripting.run('cp cv cv_stripped')
scripting.run('cp v v_stripped')
scripting.run('cp vprod vprod_stripped')
scripting.run('strip *_stripped')
scripting.run('cp cv_stripped cv_stripped_upxed')
scripting.run('cp v_stripped v_stripped_upxed')
scripting.run('cp vprod_stripped vprod_stripped_upxed')
scripting.run('upx -qqq --lzma cv_stripped_upxed')
scripting.run('upx -qqq --lzma v_stripped_upxed')
scripting.run('upx -qqq --lzma vprod_stripped_upxed')
scripting.show_sizes_of_files(['$cdir/cv', '$cdir/cv_stripped', '$cdir/cv_stripped_upxed'])
scripting.show_sizes_of_files(['$cdir/v', '$cdir/v_stripped', '$cdir/v_stripped_upxed'])
scripting.show_sizes_of_files(['$cdir/vprod', '$cdir/vprod_stripped', '$cdir/vprod_stripped_upxed'])
vversion := scripting.run('$cdir/v --version')
vcommit := scripting.run('git rev-parse --short --verify HEAD')
println('V version is: ${vversion} , local source commit: ${vcommit}')
if vgit_context.vvlocation == 'v.v' {
println('Source lines of the compiler: ' + scripting.run('wc v.v vlib/compiler/*.v | tail -n -1'))
}else{
println('Source lines of the compiler: ' + scripting.run('wc compiler/*.v | tail -n -1'))
}
println('')
// prepare c.vc first
os.chdir( c.vc )
run('git checkout master')
println('Cloning current v source to $cdir ...')
os.system('git clone --quiet \'$c.cwd\' \'$cdir\' ')
os.chdir( cdir )
os.system('git checkout --quiet \'$commit\' ')
run('git clean -f')
c.prepare_vc_source( cdir, commit )
source_location := if os.exists('v.v') { 'v.v' } else { 'compiler/' }
println('Making v and vprod compilers in $cdir')
run(command_for_building_v_from_c_source)
run('./cv -o v $source_location')
run('./cv -prod -o vprod $source_location')
run('cp cv cv_stripped')
run('cp v v_stripped')
run('cp vprod vprod_stripped')
run('strip *_stripped')
run('cp cv_stripped cv_stripped_upxed')
run('cp v_stripped v_stripped_upxed')
run('cp vprod_stripped vprod_stripped_upxed')
run('upx -qqq --lzma cv_stripped_upxed')
run('upx -qqq --lzma v_stripped_upxed')
run('upx -qqq --lzma vprod_stripped_upxed')
show_sizes_of_files(["$cdir/cv", "$cdir/cv_stripped", "$cdir/cv_stripped_upxed"])
show_sizes_of_files(["$cdir/v", "$cdir/v_stripped", "$cdir/v_stripped_upxed"])
show_sizes_of_files(["$cdir/vprod", "$cdir/vprod_stripped", "$cdir/vprod_stripped_upxed"])
println("V version is: " + run("$cdir/v --version") + " , local source commit: " + run("git rev-parse --short --verify HEAD") )
println('Source lines of the compiler: ' + run('wc v.v compiler/*.v vlib/compiler/*.v | tail -n -1') )
}
fn (c Context) compare_v_performance( commands []string ) {
fn (c Context) compare_v_performance(label string, commands []string) string {
println('---------------------------------------------------------------------------------')
println('Compare v performance when doing the following commands:')
source_location_a := if os.exists('$c.a/v.v') { 'v.v' } else { 'compiler/' }
source_location_b := if os.exists('$c.b/v.v') { 'v.v' } else { 'compiler/' }
timestamp_a, _ := line_to_timestamp_and_commit(run('cd $c.a/ ; git rev-list -n1 --timestamp HEAD'))
timestamp_b, _ := line_to_timestamp_and_commit(run('cd $c.b/ ; git rev-list -n1 --timestamp HEAD'))
debug_option_a := if timestamp_a > 1570877641 { '-g' } else { '-debug' }
debug_option_b := if timestamp_b > 1570877641 { '-g' } else { '-debug' }
println('Compare v performance when doing the following commands ($label):')
source_location_a := if os.exists('$c.a/v.v') { 'v.v ' } else { 'compiler/ ' }
source_location_b := if os.exists('$c.b/v.v') { 'v.v ' } else { 'compiler/ ' }
timestamp_a,_ := vgit.line_to_timestamp_and_commit(scripting.run('cd $c.a/ ; git rev-list -n1 --timestamp HEAD'))
timestamp_b,_ := vgit.line_to_timestamp_and_commit(scripting.run('cd $c.b/ ; git rev-list -n1 --timestamp HEAD'))
debug_option_a := if timestamp_a > 1570877641 { '-g ' } else { '-debug ' }
debug_option_b := if timestamp_b > 1570877641 { '-g ' } else { '-debug ' }
mut hyperfine_commands_arguments := []string
for cmd in commands { println(cmd) }
for cmd in commands {
hyperfine_commands_arguments << ' \'cd ${c.b:30s} ; ./$cmd \' '.replace_each(['@COMPILER@', source_location_b, '@DEBUG@', debug_option_b])
println(cmd)
}
for cmd in commands {
hyperfine_commands_arguments << ' \'cd ${c.a:30s} ; ./$cmd \' '.replace_each(['@COMPILER@', source_location_a, '@DEBUG@', debug_option_a])
hyperfine_commands_arguments << " \'cd ${c.b:-34s} ; ./$cmd \' ".replace_each(['@COMPILER@', source_location_b, '@DEBUG@', debug_option_b])
}
///////////////////////////////////////////////////////////////////////////////
cmd_stats_file := os.realpath([ c.workdir, 'v_performance_stats.json'].join(os.path_separator))
comparison_cmd := 'hyperfine $c.hyperfineopts '+
'--export-json ${cmd_stats_file} '+
'--time-unit millisecond '+
'--style full --warmup $c.warmups ' +
hyperfine_commands_arguments.join(' ')
///////////////////////////////////////////////////////////////////////////////
if c.verbose { println( comparison_cmd ) }
os.system( comparison_cmd )
for cmd in commands {
hyperfine_commands_arguments << " \'cd ${c.a:-34s} ; ./$cmd \' ".replace_each(['@COMPILER@', source_location_a, '@DEBUG@', debug_option_a])
}
// /////////////////////////////////////////////////////////////////////////////
cmd_stats_file := os.realpath([c.workdir, 'v_performance_stats_${label}.json'].join(os.path_separator))
comparison_cmd := 'hyperfine $c.hyperfineopts ' + '--export-json ${cmd_stats_file} ' + '--time-unit millisecond ' + '--style full --warmup $c.warmups ' + hyperfine_commands_arguments.join(' ')
// /////////////////////////////////////////////////////////////////////////////
if c.verbose {
println(comparison_cmd)
}
os.system(comparison_cmd)
println('The detailed performance comparison report was saved to: $cmd_stats_file .')
println('')
return cmd_stats_file
}
fn (c Context) normalized_workpath_for_commit( commit string ) string {
nc := 'v_at_' + commit.replace('^','_').replace('-','_').replace('/','_')
return os.realpath( c.workdir + os.path_separator + nc )
}
fn validate_commit_exists( commit string ){
cmd := 'git cat-file -t ' + "'" + commit + "'"
if !command_exits_with_zero_status(cmd) {
eprintln("Commit: '" + commit + "' does not exist in the current repository.")
exit(3)
}
}
fn main(){
used_tools_must_exist(['cp','rm','strip','make','git','upx','cc','wc','tail','hyperfine'])
fn main() {
scripting.used_tools_must_exist(['cp', 'rm', 'strip', 'make', 'git', 'upx', 'cc', 'wc', 'tail', 'hyperfine'])
mut context := new_context()
mut fp := flag.new_flag_parser(os.args)
fp.application(filepath.filename(os.executable()))
fp.version( tool_version )
fp.description( tool_description )
fp.version(tool_version)
fp.description(tool_description)
fp.arguments_description('COMMIT_BEFORE [COMMIT_AFTER]')
fp.skip_executable()
fp.limit_free_args(1,2)
show_help:=fp.bool('help', false, 'Show this help screen\n')
fp.limit_free_args(1, 2)
context.vc_repo_url = fp.string('vcrepo', 'https://github.com/vlang/vc',
'' +
'The url of the vc repository. You can clone it \n'+
flag.SPACE+'beforehand, and then just give the local folder \n'+
flag.SPACE+'path here. That will eliminate the network ops \n'+
flag.SPACE+'done by this tool, which is useful, if you want \n'+
flag.SPACE+'to script it/run it in a restrictive vps/docker.\n')
context.verbose = fp.bool('verbose', false, 'Be more verbose\n')
context.vflags = fp.string('vflags', '', 'Additional options to pass to the v commands, for example "-cc tcc"')
context.hyperfineopts = fp.string('hyperfine_options', '',
'' +
'Additional options passed to hyperfine.\n'+
flag.SPACE+'For example on linux, you may want to pass:\n'+
flag.SPACE+' --hyperfine_options "--prepare \'sync; echo 3 | sudo tee /proc/sys/vm/drop_caches\'" \n')
context.workdir = os.realpath( fp.string('workdir', '/tmp', 'A writable folder, where the comparison will be done.') )
if( show_help ){
println( fp.usage() )
exit(0)
}
commits := fp.finalize() or {
eprintln('Error: ' + err)
exit(1)
}
'Additional options passed to hyperfine.
${flag.SPACE}For example on linux, you may want to pass:
${flag.SPACE}--hyperfine_options "--prepare \'sync; echo 3 | sudo tee /proc/sys/vm/drop_caches\'"
')
commits := vgit.add_common_tool_options(mut context, mut fp)
context.commit_before = commits[0]
if commits.len > 1 { context.commit_after = commits[1] }
validate_commit_exists( context.commit_before )
validate_commit_exists( context.commit_after )
context.b = context.normalized_workpath_for_commit( context.commit_before )
context.a = context.normalized_workpath_for_commit( context.commit_after )
context.vc = context.normalized_workpath_for_commit( 'vc' )
if !os.is_dir( context.workdir ) {
if commits.len > 1 {
context.commit_after = commits[1]
}
context.b = vgit.normalized_workpath_for_commit(context.workdir, context.commit_before)
context.a = vgit.normalized_workpath_for_commit(context.workdir, context.commit_after)
context.vc = vgit.normalized_workpath_for_commit(context.workdir, 'vc')
if !os.is_dir(context.workdir) {
msg := 'Work folder: ' + context.workdir + ' , does not exist.'
eprintln(msg)
exit(2)
}
context.compare_versions()
}

View File

@ -8,6 +8,13 @@ import (
const (
known_failing_exceptions = ['./examples/vweb/vweb_example.v',
'./tools/gen_vc.v',
'./tools/modules/vgit/vgit.v', // generics
'./tools/preludes/live_main.v',
'./tools/preludes/live_shared.v',
'./tools/preludes/tests_assertions.v',
'./tools/preludes/tests_with_stats.v',
'./tools/performance_compare.v', // generics
'./tools/oldv.v', // generics
'./tutorials/code/blog/article.v',
'./tutorials/code/blog/blog.v',
'./vlib/arrays/arrays.v',
@ -15,10 +22,6 @@ const (
'./vlib/builtin/js/hashmap.v',
'./vlib/compiler/tests/fn_variadic_test.v',
'./vlib/compiler/tests/generic_test.v',
'./tools/preludes/live_main.v',
'./tools/preludes/live_shared.v',
'./tools/preludes/tests_assertions.v',
'./tools/preludes/tests_with_stats.v',
'./vlib/crypto/aes/aes.v',
'./vlib/crypto/aes/aes_cbc.v',
'./vlib/crypto/aes/block_generic.v',
@ -28,7 +31,7 @@ const (
'./vlib/eventbus/eventbus_test.v',
'./vlib/os/bare/bare_example_linux.v',
'./vlib/szip/szip.v',
'./vlib/ui/examples/users_gui/users.v',
'./vlib/uiold/examples/users_gui/users.v',
'./vlib/vweb/assets/assets.v',
'./vlib/vweb/vweb.v',
]

View File

@ -54,6 +54,23 @@ pub struct Flag {
// and also the default value, when the flag is not given
}
pub fn (f Flag) str() string {
return ''
+' flag:\n'
+' name: $f.name\n'
+' abbr: $f.abbr\n'
+' usag: $f.usage\n'
+' desc: $f.val_desc'
}
pub fn (af []Flag) str() string {
mut res := []string
res << '\n []Flag = ['
for f in af {
res << f.str()
}
res << ' ]'
return res.join('\n')
}
//
pub struct FlagParser {
pub mut:
@ -135,7 +152,10 @@ fn (fs mut FlagParser) parse_value(longhand string, shorthand byte) []string {
//End of input. We're done here.
break
}
if arg == full || (arg[0] == `-` && arg[1] == shorthand && arg.len == 2) {
if arg[0] != `-` {
continue
}
if (arg.len == 2 && arg[0] == `-` && arg[1] == shorthand ) || arg == full {
if i+1 > fs.args.len {
panic("Missing argument for '$longhand'")
}
@ -177,7 +197,13 @@ fn (fs mut FlagParser) parse_bool_value(longhand string, shorthand byte) ?string
//End of input. We're done.
break
}
if arg == full || (arg[0] == `-` && arg[1] == shorthand && arg.len == 2) {
if arg.len == 0 {
continue
}
if arg[0] != `-` {
continue
}
if ( arg.len == 2 && arg[0] == `-` && arg[1] == shorthand ) || arg == full {
if fs.args.len > i+1 && (fs.args[i+1] in ['true', 'false']) {
val := fs.args[i+1]
fs.args.delete(i+1)
@ -194,7 +220,7 @@ fn (fs mut FlagParser) parse_bool_value(longhand string, shorthand byte) ?string
fs.args.delete(i)
return val
}
if arg[0] == `-` && arg.index_byte(shorthand) != -1 {
if arg[0] == `-` && arg[1] != `-` && arg.index_byte(shorthand) != -1 {
// -abc is equivalent to -a -b -c
return 'true'
}

View File

@ -320,3 +320,24 @@ fn test_multiple_arguments() {
assert b[0] == 'a' && b[1] == 'c' && b[2] == 'b'
assert c[0] == 1.23 && c[1] == 2.34 && c[2] == 3.45
}
fn test_long_options_that_start_with_the_same_letter_as_another_short_option() {
mut fp := flag.new_flag_parser([
'--vabc', '/abc',
])
verbose := fp.bool_('verbose', `v`, false, 'Be more verbose.')
vabc := fp.string_('vabc', `x`, 'default', 'Another option that *may* conflict with v, but *should not*')
assert verbose == false
assert vabc == '/abc'
}
fn test_long_options_that_start_with_the_same_letter_as_another_short_option_both_set() {
mut fp := flag.new_flag_parser([
'-v',
'--vabc', '/abc',
])
verbose := fp.bool_('verbose', `v`, false, 'Be more verbose.')
vabc := fp.string_('vabc', `x`, 'default', 'Another option that *may* conflict with v, but *should not*')
assert verbose == true
assert vabc == '/abc'
}