debug.cc #1

  • //
  • guest/
  • perforce_software/
  • p4/
  • 2014-1/
  • support/
  • debug.cc
  • View
  • Commits
  • Open Download .zip Download (16 KB)
/*
 * 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 B1M 1024*1024
# define B2M 1024*2048
# define B4M 1024*4096
# define B10M 10*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,
	"zks",          0, -1, -1, 10, 1, 1, 0,
	"jsp",          0, -1, -1, 10, 1, 1, 0,
	"zmq",          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.monitor.shared",	0,	128,	0,	2048,	1,	B1K, 0,
	"db.peeking",		0,	0,	0,	3,	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.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,	15,	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,	R1M,	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,	R100K,	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.shelve.maxfiles",	0,	R10M,	0,	RBIG,	1,	R1K, 0,
	"dm.shelve.maxsize",	0,	0,	0,	BBIG,	1,	B1K, 0,
	"dm.status.matchlines", 0,	50,	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.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.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,
	"index.domain.owner",	0,      0,      0,      1,      1,      1, 0,
	"lbr.bufsize",		0,	B4K,	1,	BBIG,	1,	B1K, 0,
	"lbr.proxy.case",	0,	1,	1,	3,	1,	1, 0,
	"lbr.retry.max",	0,	R50K,	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.rcs.maxlen",	0,	B10M,	0,	BBIG,	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.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,	B64K,	B1K,	B4M,	B1K,	B1K, 0,
	"net.backlog",		0,	10,	1,      SMAX,   1,	B1K, 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.deliver.duplex",	0,	1,	0,	1,	1,	1, 0,
	"rpc.himark",		0,	2000,	2000,	BBIG,	1,	B1K, 0,
	"rpc.lowmark",		0,	700,	700,	BBIG,	1,	B1K, 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,	3,	1,	1, 0,
	"rpl.counter.hook",	0,	1,	0,	1,	1,	1, 0,
	"rpl.grouped",		0,	0,	0,	1,	1,	1, 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.labels.global",	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.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.sync",	0,	1,	0,	1,	1,	1, 0,
	"server.maxcommands", 	0,	0,	0,	RBIG,	1,	R1K, 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.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.listpool",	0,	0,	0,	BBIG,	1,	B1K, 0, 
	"cmd.memory.listpool2",	0,	0,	0,	BBIG,	1,	B1K, 0, 
	"cmd.memory.listall",	0,	0,	0,	BBIG,	1,	B1K, 0, 
	"cmd.memory.listall2",	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.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,

	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;

		// Atoi()

		while( ++equals < comma && isdigit( *equals ) )
		    val = val * 10 + *equals - '0';

		// 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;
	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( 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...
	    // So we have to iterate getting more space until it works...

	    while( rsz == -1 )
	    {
		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 );
	}
    }
}
# Change User Description Committed
#1 15902 Matt Attaway A second renaming that I will not obliterate as a badge of shame
//guest/perforce_software/p4/2014_1/support/debug.cc
#1 15901 Matt Attaway Clean up code to fit modern Workshop naming standards
//guest/perforce_software/p4/2014.1/support/debug.cc
#1 12188 Matt Attaway Move 'main' p4 into a release specific directory in prep for new releases
//guest/perforce_software/p4/support/debug.cc
#1 9129 Matt Attaway Initial commit of the 2014.1 p4/p4api source code