/* * Copyright 1995, 2003 Perforce Software. All rights reserved. * * This file is part of Perforce - the FAST SCM System. */ # define NEED_SOCKETPAIR # define NEED_TIME # define NEED_TIME_HP # define NEED_SMARTHEAP # if defined( OS_NT ) # include <winsock2.h> # endif # include <stdhdrs.h> # include <strbuf.h> # include <stdarg.h> # include <ctype.h> # include <error.h> # include <errorlog.h> # include <pid.h> # include <datetime.h> # include "debug.h" P4Debug p4debug; P4Tunable p4tunable; MT_STATIC P4DebugConfig *p4debughelp; # define SMAX 64*1024 # define B1K 1024 # define B2K 2048 # define B4K 4096 # define B8K 8192 # define B16K 16*1024 # define B32K 32*1024 # define B64K 64*1024 # define B512K 512*1024 # define B1M 1024*1024 # define B2M 1024*2048 # define B4M 1024*4096 # define B10M 10*1024*1024 # define B256M 256*1024*1024 # define B1G 1024*1024*1024 # define BBIG 2147483647 /* (2^31-1) */ # define R1K 1000 # define R4K 4000 # define R10K 10000 # define R50K 50000 # define R100K 100000 # define R1M 1000000 # define R4M 4000000 # define R10M 10000000 # define R100M 100000000 # define R1G 1000000000 # define RBIG 2147483647 /* 2^32-1 */ // Initial values for the Smart Heap tunables. A "0" setting indicates // no tunable is set. All settings are multiplied by 1024 in // sys/shhandler.cc except the Subpool, Growinc, Flush and List settings. // Subpool settings are only on Windows. The Linux Smart Heap library // is old enough, it does not have the Subpool and Growinc APIs. # ifdef HAVE_SMARTHEAP # ifdef OS_LINUX # ifdef OS_LINUXX86 # define SHPROC B1M # define SHPOOL 0 # define SHGROW1 0 # define SHGROW2 0 # define SHSUBP 0 # else # define SHPROC 0 # define SHPOOL 0 # define SHGROW1 0 # define SHGROW2 0 # define SHSUBP 0 # endif # endif // OS_LINUX # ifdef OS_NTX86 # define SHPROC B1M # define SHPOOL 0 # define SHGROW1 0 # define SHGROW2 0 # define SHSUBP 64 # endif # ifdef OS_NTX64 # define SHPROC B32K # define SHPOOL B16K # define SHGROW1 B1K # define SHGROW2 B4K # define SHSUBP 64 # endif # endif // HAVE_SMARTHEAP # ifndef SHPROC # define SHPROC 0 # define SHPOOL 0 # define SHGROW1 0 # define SHGROW2 0 # define SHSUBP 0 # endif P4Tunable::tunable P4Tunable::list[] = { // P4Debug's collection "db", 0, -1, -1, 10, 1, 1, 0, "diff", 0, -1, -1, 10, 1, 1, 0, "dm", 0, -1, -1, 10, 1, 1, 0, "dmc", 0, -1, -1, 10, 1, 1, 0, "ftp", 0, -1, -1, 10, 1, 1, 0, "handle", 0, -1, -1, 10, 1, 1, 0, "lbr", 0, -1, -1, 10, 1, 1, 0, "map", 0, -1, -1, 10, 1, 1, 0, "net", 0, -1, -1, 10, 1, 1, 0, "options", 0, -1, -1, 10, 1, 1, 0, "peek", 0, -1, -1, 10, 1, 1, 0, "rcs", 0, -1, -1, 10, 1, 1, 0, "records", 0, -1, -1, 10, 1, 1, 0, "rpc", 0, -1, -1, 10, 1, 1, 0, "server", 0, -1, -1, 10, 1, 1, 0, "spec", 0, -1, -1, 10, 1, 1, 0, "track", 0, -1, -1, 10, 1, 1, 0, "ob", 0, -1, -1, 10, 1, 1, 0, "viewgen", 0, -1, -1, 10, 1, 1, 0, "rpl", 0, -1, -1, 10, 1, 1, 0, "ssl", 0, -1, -1, 10, 1, 1, 0, "time", 0, -1, -1, 10, 1, 1, 0, "cluster", 0, -1, -1, 10, 1, 1, 0, "zk", 0, -1, -1, 10, 1, 1, 0, "ldap", 0, -1, -1, 10, 1, 1, 0, "dvcs", 0, -1, -1, 10, 1, 1, 0, // P4Tunable's collection // // name isSet, value, min, max, mod, k, orig "cluster.journal.shared",0, 0, 0, 1, 1, 1, 0, "db.isalive", 0, R10K, 1, RBIG, 1, R1K, 0, "db.jnlack.shared", 0, 16, 0, 2048, 1, B1K, 0, "db.monitor.shared", 0, 256, 0, 4096, 1, B1K, 0, "db.page.migrate", 0, 0, 0, 80, 1, 1, 0, "db.peeking", 0, 2, 0, 3, 1, 1, 0, "db.peeking.usemaxlock",0, 0, 0, 1, 1, 1, 0, "db.reorg.disable", 0, 0, 0, 1, 1, 1, 0, "db.reorg.misorder", 0, 80, 0, 100, 1, 1, 0, "db.reorg.occup", 0, 8, 0, 100, 1, 1, 0, "db.trylock", 0, 3, 0, RBIG, 1, R1K, 0, "dbarray.putcheck", 0, R4K, 1, RBIG, 1, R1K, 0, "dbarray.reserve", 0, B4M, B4K, BBIG, 1, B1K, 0, "dbjournal.bufsize", 0, B16K, 1, BBIG, 1, B1K, 0, "dbjournal.wordsize", 0, B4K, 1, BBIG, 1, B1K, 0, "dbopen.cache", 0, 96, 1, RBIG, 1, R1K, 0, "dbopen.cache.wide", 0, 192, 1, RBIG, 1, R1K, 0, "dbopen.freepct", 0, 0, 0, 99, 1, 1, 0, "dbopen.mismatch.delay", 0, 300, 10, RBIG, 1, 1, 0, "dbopen.nofsync", 0, 0, 0, 1, 1, 1, 0, "dbopen.pagesize", 0, B8K, B8K, B16K, B8K, B1K, 0, "dbopen.retry", 0, 10, 0, 100, 1, 1, 0, "diff.binary.rcs", 0, 0, 0, 1, 1, 1, 0, "diff.slimit1", 0, R10M, R10K, RBIG, 1, R1K, 0, "diff.slimit2", 0, R100M, R10K, RBIG, 1, R1K, 0, "diff.sthresh", 0, R50K, R1K, RBIG, 1, R1K, 0, "dm.annotate.maxsize", 0, B10M, 0, BBIG, 1, B1K, 0, "dm.batch.domains", 0, 0, R1K, RBIG, 1, R1K, 0, "dm.changes.thresh1", 0, R50K, 1, RBIG, 1, R1K, 0, "dm.changes.thresh2", 0, R10K, 1, RBIG, 1, R1K, 0, "dm.copy.movewarn", 0, 0, 0, 1, 1, 1, 0, "dm.domain.accessupdate", 0, 300, 1, RBIG, 1, 1, 0, "dm.domain.accessforce", 0, 3600, 1, RBIG, 1, 1, 0, "dm.flushforce", 0, R10K, 1, RBIG, 1, R1K, 0, "dm.flushtry", 0, 100, 1, RBIG, 1, R1K, 0, "dm.fstat.maxcontent", 0, B4M, 0, B10M, 1, B1K, 0, "dm.grep.maxlinelength",0, B4K, 128, B16K, 1, B1K, 0, "dm.grep.maxrevs", 0, R10K, 0, RBIG, 1, R1K, 0, "dm.grep.maxcontext", 0, R1K, 0, B16K, 1, R1K, 0, "dm.integ.engine", 0, 3, 0, 3, 1, 1, 0, "dm.integ.maxact", 0, R100K, 1, RBIG, 1, R1K, 0, "dm.integ.tweaks", 0, 0, 0, 256, 1, 1, 0, "dm.isalive", 0, R50K, 1, RBIG, 1, R1K, 0, "dm.keys.hide", 0, 0, 0, 2, 1, 1, 0, "dm.maxkey", 0, B1K, 64, B4K, 1, B1K, 0, // B4K = max(dbopen.pagesize) / 4 "dm.password.minlength",0, 8, 0, 1024, 1, 1, 0, "dm.protects.allow.admin", 0, 0, 0, 1, 1, 1, 0, "dm.protects.hide", 0, 0, 0, 1, 1, 1, 0, "dm.proxy.protects", 0, 1, 0, 1, 1, 1, 0, "dm.quick.clients", 0, R10M, 1, RBIG, 1, R1K, 0, "dm.quick.domains", 0, R10M, 1, RBIG, 1, R1K, 0, "dm.quick.have", 0, R1M, 1, RBIG, 1, R1K, 0, "dm.quick.integ", 0, R1M, 1, RBIG, 1, R1K, 0, "dm.quick.resolve", 0, R1K, 1, RBIG, 1, R1K, 0, "dm.quick.rev", 0, R1M, 1, RBIG, 1, R1K, 0, "dm.quick.working", 0, R1K, 1, RBIG, 1, R1K, 0, "dm.resolve.attrib", 0, 1, 0, 1, 1, 1, 0, "dm.revcx.thresh1", 0, R4K, 1, RBIG, 1, R1K, 0, "dm.revcx.thresh2", 0, R1K, 1, RBIG, 1, R1K, 0, "dm.rotatelogwithjnl", 0, 1, 0, 1, 1, 1, 0, "dm.shelve.accessupdate", 0, 300, 1, RBIG, 1, 1, 0, "dm.shelve.maxfiles", 0, R10M, 0, RBIG, 1, R1K, 0, "dm.shelve.maxsize", 0, 0, 0, BBIG, 1, B1K, 0, "dm.shelve.promote", 0, 0, 0, 1, 1, 1, 0, "dm.status.matchlines", 0, 80, 0, 100, 1, 1, 0, "dm.status.matchsize", 0, 10, 0, R1G, 1, R1K, 0, "dm.user.accessupdate", 0, 300, 1, RBIG, 1, 1, 0, "dm.user.accessforce", 0, 3600, 1, RBIG, 1, 1, 0, "dm.user.insecurelogin",0, 0, 0, 1, 1, 1, 0, "dm.user.loginattempts",0, 3, 0, 10000, 1, 1, 0, "dm.user.noautocreate", 0, 0, 0, 2, 1, 1, 0, "dm.user.resetpassword", 0, 0, 0, 1, 1, 1, 0, "filesys.binaryscan", 0, B64K, 0, BBIG, 1, B1K, 0, "filesys.bufsize", 0, B4K, B4K, B10M, 1, B1K, 0, "filesys.cachehint", 0, 0, 0, 1, 1, 1, 0, "filesys.detectunicode",0, 1, 0, 1, 1, 1, 0, "filesys.lockdelay", 0, 300, 1, RBIG, 1, 1, 0, "filesys.locktry", 0, 100, 1, RBIG, 1, 1, 0, "filesys.maketmp", 0, 10, 1, RBIG, 1, R1K, 0, "filesys.maxmap", 0, B1G, 0, BBIG, 1, B1K, 0, "filesys.maxsymlink", 0, B1K, 1, BBIG, 1, B1K, 0, "filesys.maxtmp", 0, R1M, 1, RBIG, 1, R1K, 0, "filesys.extendlowmark",0, B32K, 0, BBIG, B1K, B1K, 0, "filesys.windows.lfn", 0, 1, 0, 10, 1, 1, 0, "filesys.client.nullsync",0, 0, 0, 1, 1, 1, 0, "index.domain.owner", 0, 0, 0, 1, 1, 1, 0, "lbr.autocompress", 0, 0, 0, 1, 1, 1, 0, "lbr.bufsize", 0, B4K, 1, BBIG, 1, B1K, 0, "lbr.fabricate", 0, 0, 0, 1, 1, 1, 0, "lbr.proxy.case", 0, 1, 1, 3, 1, 1, 0, "lbr.rcs.existcheck", 0, 1, 0, 1, 1, 1, 0, "lbr.rcs.maxlen", 0, B10M, 0, BBIG, 1, 1, 0, "lbr.retry.max", 0, 50, 1, BBIG, 1, R1K, 0, "lbr.stat.interval", 0, 0, 0, 999, 1, 1, 0, "lbr.verify.in", 0, 1, 0, 1, 1, 1, 0, "lbr.verify.out", 0, 1, 0, 1, 1, 1, 0, "lbr.verify.script.out",0, 1, 0, 1, 1, 1, 0, "log.originhost", 0, 1, 0, 1, 1, 1, 0, "map.joinmax1", 0, R10K, 1, 200000, 1, R1K, 0, "map.joinmax2", 0, R1M, 1, RBIG, 1, R1K, 0, "map.maxwild", 0, 10, 1, 10, 1, 1, 0, "net.bufsize", 0, B4K, 1, BBIG, 1, B1K, 0, "net.keepalive.disable",0, 0, 0, 1, 1, R1K, 0, "net.keepalive.idle", 0, 0, 0, BBIG, 1, R1K, 0, "net.keepalive.interval",0, 0, 0, BBIG, 1, R1K, 0, "net.keepalive.count", 0, 0, 0, BBIG, 1, R1K, 0, "net.maxfaultpub", 0, 100, 0, BBIG, 1, 1, 0, "net.maxwait", 0, 0, 0, BBIG, 1, B1K, 0, "net.parallel.max", 0, 0, 0, 100, 1, 1, 0, "net.parallel.threads", 0, 0, 2, 100, 1, 1, 0, "net.parallel.batch", 0, 0, 1, RBIG, 1, R1K, 0, "net.parallel.batchsize",0, 0, 1, BBIG, 1, B1K, 0, "net.parallel.min", 0, 0, 2, RBIG, 1, R1K, 0, "net.parallel.minsize", 0, 0, 1, BBIG, 1, B1K, 0, "net.parallel.submit.threads",0,0, 2, 100, 1, 1, 0, "net.parallel.submit.batch",0, 0, 1, RBIG, 1, R1K, 0, "net.parallel.submit.min",0, 0, 2, RBIG, 1, R1K, 0, "net.rcvbufsize", 0, B32K, 1, BBIG, 1, B1K, 0, "net.reuseport", 0, 0, 0, 1, 1, 1, 0, "net.rfc3484", 0, 0, 0, 1, 1, 1, 0, "net.tcpsize", 0, B512K, B1K, B256M, B1K, B1K, 0, "net.backlog", 0, 128, 1, SMAX, 1, B1K, 0, "net.x3.minsize", 0, B512K, 0, RBIG, B1K, B1K, 0, "proxy.deliver.fix", 0, 1, 0, 1, 1, 1, 0, "proxy.monitor.interval", 0, 10, 1, 999, 1, 1, 0, "proxy.monitor.level", 0, 0, 0, 3, 1, 1, 0, "rcs.maxinsert", 0, R1G, 1, RBIG, 1, R1K, 0, "rcs.nofsync", 0, 0, 0, 1, 1, 1, 0, "rpc.durablewait", 0, 0, 0, 1, 1, 1, 0, "rpc.himark", 0, 2000, 2000, BBIG, 1, B1K, 0, "rpc.lowmark", 0, 700, 700, BBIG, 1, B1K, 0, "rpc.ipaddr.mismatch", 0, 0, 0, 1, 1, 1, 0, "rpl.checksum.auto", 0, 0, 0, 3, 1, 1, 0, "rpl.checksum.change", 0, 0, 0, 3, 1, 1, 0, "rpl.checksum.table", 0, 0, 0, 2, 1, 1, 0, "rpl.compress", 0, 0, 0, 4, 1, 1, 0, "rpl.counter.hook", 0, 1, 0, 1, 1, 1, 0, "rpl.jnl.batch.size", 0, R100M, 0, RBIG, 1, R1K, 0, "rpl.jnlwait.adjust", 0, 25, 0, RBIG, 1, R1K, 0, "rpl.jnlwait.interval", 0, 50, 50, RBIG, 1, R1K, 0, "rpl.jnlwait.max", 0, 1000, 100, RBIG, 1, R1K, 0, "rpl.awaitjnl.count", 0, 100, 1, RBIG, 1, R1K, 0, "rpl.awaitjnl.interval",0, 50, 1, RBIG, 1, R1K, 0, "rpl.journal.ack", 0, 0, 0, B1M, 1, 1, 0, "rpl.journal.ack.min", 0, 0, 0, B1M, 1, 1, 0, "rpl.labels.global", 0, 0, 0, 1, 1, 1, 0, "rpl.replay.userrp", 0, 0, 0, 1, 1, 1, 0, "rpl.verify.cache", 0, 0, 0, 1, 1, 1, 0, "run.move.allow", 0, 1, 0, 1, 1, 1, 0, "run.obliterate.allow", 0, 1, 0, 1, 1, 1, 0, "run.prune.allow", 0, 1, 0, 1, 1, 1, 0, "run.unzip.user.allow", 0, 0, 0, 1, 1, 1, 0, "run.users.authorize", 0, 0, 0, 1, 1, 1, 0, "server.commandlimits", 0, 0, 0, 2, 1, 1, 0, "server.filecharset", 0, 0, 0, 1, 1, 1, 0, "server.locks.archive", 0, 1, 0, 1, 1, 1, 0, "server.locks.sync", 0, 0, 0, 1, 1, 1, 0, "server.allowfetch", 0, 0, 0, 3, 1, 1, 0, "server.allowpush", 0, 0, 0, 3, 1, 1, 0, "server.allowremotelocking", 0, 0, 0, 1, 1, 1, 0, "server.allowrewrite", 0, 0, 0, 1, 1, 1, 0, "server.global.client.views", 0, 0, 0, 1, 1, 1, 0, "server.maxcommands", 0, 0, 0, RBIG, 1, R1K, 0, "filetype.bypasslock", 0, 0, 0, 1, 1, 1, 0, "filetype.maxtextsize", 0, B10M, 0, RBIG, 1, R1K, 0, "spec.hashbuckets", 0, 99, 0, 999, 1, 1, 0, "spec.custom", 0, 0, 0, 1, 1, 1, 0, "streamview.dots.low", 0, 0, 0, 1, 1, 1, 0, "streamview.sort.remap",0, 0, 0, 1, 1, 1, 0, "submit.forcenoretransfer",0, 0, 0, 2, 1, 1, 0, "submit.noretransfer", 0, 0, 0, 1, 1, 1, 0, "submit.unlocklocked", 0, 0, 0, 1, 1, 1, 0, // vv Smart Heap tunables must be a continuous group vv "sys.memory.poolfree", 0, SHPOOL, 0, BBIG, 1, B1K, 0, "sys.memory.procfree", 0, SHPROC, 0, BBIG, 1, B1K, 0, "sys.memory.poolgrowinc",0, SHGROW1,0, BBIG, 1, B1K, 0, "sys.memory.procgrowinc",0, SHGROW2,0, BBIG, 1, B1K, 0, "sys.memory.subpools", 0, SHSUBP, 0, BBIG, 1, B1K, 0, "sys.memory.limit", 0, 0, 0, BBIG, 1, B1K, 0, "cmd.memory.poolfree", 0, 0, 0, BBIG, 1, B1K, 0, "cmd.memory.procfree", 0, 0, 0, BBIG, 1, B1K, 0, "cmd.memory.limit", 0, 0, 0, BBIG, 1, B1K, 0, "cmd.memory.flushpool", 0, 0, 0, BBIG, 1, B1K, 0, "cmd.memory.listpools", 0, 0, 0, BBIG, 1, B1K, 0, "cmd.memory.chkpt", 0, 0, 0, BBIG, 1, B1K, 0, // ^^ Smart Heap tunables must be a continuous group ^^ "sys.memory.stacksize", 0, 0, 0, B16K, 1, B1K, 0, "sys.rename.max", 0, 10, 10, RBIG, 1, R1K, 0, "sys.rename.wait", 0, 1000, 50, RBIG, 1, R1K, 0, "rpl.forward.all", 0, 0, 0, 1, 1, 1, 0, "rpl.forward.login", 0, 0, 0, 1, 1, 1, 0, "rpl.pull.position", 0, 0, 0, RBIG, 1, R1K, 0, "rpl.pull.reload", 0, 60000, 0, RBIG, 1, R1K, 0, "ssl.secondary.suite", 0, 0, 0, 1, 1, 1, 0, "ssl.client.timeout", 0, 30, 1, RBIG, 1, 1, 0, "triggers.io", 0, 0, 0, 1, 1, 1, 0, "istat.mimic.ichanges", 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 // name isSet, value, min, max, mod, k, orig } ; int P4Tunable::IsKnown( const char *n ) { int i; for( i = 0; list[i].name; i++ ) if( !strcmp( list[i].name, n ) ) return 1; return 0; } int P4Tunable::IsSet( const char * n ) const { int i; for( i = 0; list[i].name; i++ ) if( !strcmp( list[i].name, n ) ) return list[i].isSet; return 0; } int P4Tunable::GetLevel( const char *n ) const { int i; for( i = 0; list[i].name; i++ ) if( !strcmp( list[i].name, n ) ) return list[i].value; return 0; } int P4Tunable::GetIndex( const char *n ) const { int i; for( i = 0; list[i].name; i++ ) if( !strcmp( list[i].name, n ) ) return i; return -1; } void P4Tunable::Unset( const char *n ) { int i; for( i = 0; list[i].name; i++ ) { if( !strcmp( list[i].name, n ) && list[i].isSet ) { list[i].value = list[i].original; list[i].isSet = 0; } } } void P4Tunable::UnsetAll() { int i; for( i = 0; list[i].name; i++ ) { if( list[i].isSet ) { list[i].value = list[i].original; list[i].isSet = 0; } } } void P4Tunable::Set( const char *set ) { while( *set ) { int i; const char *comma, *equals; if( !( comma = strchr( set, ',' ) ) ) comma = set + strlen( set ); if( !( equals = strchr( set, '=' ) ) || equals > comma ) equals = comma; for( i = 0; list[i].name; i++ ) { if( strlen( list[i].name ) == equals - set && !strncmp( list[i].name, set, equals - set ) ) break; } if( list[i].name ) { int val = 0; int negative = 0; // Atoi() if( equals[1] == '-' ) { negative = 1; equals++; } while( ++equals < comma && isdigit( *equals ) ) val = val * 10 + *equals - '0'; if( negative ) val = -val; // k = *1000, m = *1000,000 if( *equals == 'k' || *equals == 'K' ) val *= list[i].k, ++equals; if( *equals == 'm' || *equals == 'M' ) val *= list[i].k * list[i].k; // Min, max, and mod val = val < list[i].minVal ? list[i].minVal : val; val = val > list[i].maxVal ? list[i].maxVal : val; val = val + list[i].modVal - 1 & ~( list[i].modVal - 1 ); if( !list[i].isSet ) list[i].original = list[i].value; list[i].value = val; list[i].isSet = 1; Unbuffer(); } set = *comma ? comma + 1 : comma; } } int P4Tunable::IsNumeric( const char *p ) { const char *s = p; long val = 0; int negative = 0; if( *p == '-' ) { negative = 1; p++; } while( *p && isdigit( *p ) ) { if( val > BBIG / 10 ) return 0; val = val * 10 + *p - '0'; if( val < 0 || val > BBIG ) return 0; p++; } if( p == s ) return 0; if( *p && ( *p == 'k' || *p == 'K' || *p == 'm' || *p == 'M' ) ) { if( val >= BBIG / 1024 ) return 0; val *= 1024; if( *p == 'm' || *p == 'M' ) { if( val >= BBIG / 1024 ) return 0; val *= 1024; } if( (!negative && val < 0) || val > BBIG ) return 0; p++; } return !(*p); } void P4Tunable::Unbuffer() { setbuf( stdout, 0 ); } void P4Debug::SetLevel( int l ) { for( int i = 0; i < DT_LAST; i++ ) list[i].value = l; Unbuffer(); } void P4Debug::SetLevel( const char *set ) { // -vx sets all debug levels to x // -vn=x sets tunable level n to x if( strchr( set, '=' ) ) Set( set ); else SetLevel( atoi( set ) ); } void P4Debug::ShowLevels( int showAll, StrBuf &buf ) { for( int i = 0; list[i].name; i++ ) if( showAll || list[i].isSet ) buf << list[i].name << ": " << list[i].value << "\n"; } void P4Debug::Event() { StrBuf prefix; P4DebugConfig::TsPid2StrBuf(prefix); printf( prefix.Text() ); } void P4Debug::printf( const char *fmt, ... ) { if( p4debughelp ) { StrBuf *buf = p4debughelp->Buffer(); int ssz = buf->Length(); if( ssz < 0 ) { ssz = 0; buf->Clear(); } int asz = 80; int sz, rsz; sz = p4debughelp->Alloc( asz ); va_list l; va_start( l, fmt ); rsz = vsnprintf( buf->Alloc( asz ), sz, fmt, l ); va_end( l ); # ifdef OS_NT // Stupid NT vsnprintf returns -1 when the size is too small... // Unless the size is only 1 byte too small, then it returns sz... // So we have to iterate getting more space until it works... while( rsz == -1 || rsz == sz ) { buf->SetLength( ssz ); asz *= 3; sz = p4debughelp->Alloc( asz ); va_start( l, fmt ); rsz = vsnprintf( buf->Alloc( asz ), sz, fmt, l ); va_end( l ); } # else if( rsz >= sz ) { buf->SetLength( ssz ); rsz++; sz = p4debughelp->Alloc( rsz ); va_start( l, fmt ); rsz = vsnprintf( buf->Alloc( rsz ), rsz, fmt, l ); va_end( l ); } # endif buf->SetLength( rsz + ssz ); if( buf->Text()[ buf->Length() - 1 ] == '\n' ) { p4debughelp->Output(); buf->Clear(); } } else { va_list l; va_start( l, fmt ); vprintf( fmt, l ); va_end( l ); } } P4DebugConfig::P4DebugConfig() : buf(NULL), msz(0), elog(NULL), hook(NULL), context(NULL) { } P4DebugConfig::~P4DebugConfig() { if( p4debughelp == this ) p4debughelp = NULL; delete buf; } void P4DebugConfig::Install() { p4debughelp = this; } StrBuf * P4DebugConfig::Buffer() { if( !buf ) buf = new StrBuf; return buf; } int P4DebugConfig::Alloc( int s ) { int l = buf->Length(); if( l + s > msz ) msz = l + s; return msz - l; } void P4DebugConfig::TsPid2StrBuf( StrBuf &prefix ) { Pid pid; DateTimeHighPrecision dt; char buf[ DTHighPrecisionBufSize ]; char str[ DTHighPrecisionBufSize + 20]; dt.Now(); dt.Fmt( buf ); sprintf( str, "%s pid %d: ", buf, pid.GetID() ); prefix.Set(str); } void P4DebugConfig::Output() { if( buf ) { if( hook ) (*hook)( context, buf ); else { StrBuf *output = buf; StrBuf formattedBuf; if( p4debug.GetLevel( DT_TIME ) >= 1 ) { TsPid2StrBuf( formattedBuf ); formattedBuf.Append(buf); output = &formattedBuf; } if( elog ) elog->LogWrite( *output ); else fputs( output->Text(), stdout ); } } }