core/Profiler.cpp
author David Anderson <dvander@alliedmods.net>
Wed Mar 12 03:33:52 2008 +0000 (2008-03-12)
changeset 1925 61d7a244b641
parent 1911 c020e5c4d163
child 1947 e73a1e51833b
permissions -rw-r--r--
reworked antiflood. it now has some logic in core to take care of loading order nastiness, and to fully prevent trigger spamming
dvander@1908
     1
/**
dvander@1908
     2
 * vim: set ts=4 :
dvander@1908
     3
 * =============================================================================
dvander@1908
     4
 * SourceMod
dvander@1908
     5
 * Copyright (C) 2004-2007 AlliedModders LLC.  All rights reserved.
dvander@1908
     6
 * =============================================================================
dvander@1908
     7
 *
dvander@1908
     8
 * This program is free software; you can redistribute it and/or modify it under
dvander@1908
     9
 * the terms of the GNU General Public License, version 3.0, as published by the
dvander@1908
    10
 * Free Software Foundation.
dvander@1908
    11
 * 
dvander@1908
    12
 * This program is distributed in the hope that it will be useful, but WITHOUT
dvander@1908
    13
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
dvander@1908
    14
 * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
dvander@1908
    15
 * details.
dvander@1908
    16
 *
dvander@1908
    17
 * You should have received a copy of the GNU General Public License along with
dvander@1908
    18
 * this program.  If not, see <http://www.gnu.org/licenses/>.
dvander@1908
    19
 *
dvander@1908
    20
 * As a special exception, AlliedModders LLC gives you permission to link the
dvander@1908
    21
 * code of this program (as well as its derivative works) to "Half-Life 2," the
dvander@1908
    22
 * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
dvander@1908
    23
 * by the Valve Corporation.  You must obey the GNU General Public License in
dvander@1908
    24
 * all respects for all other code used.  Additionally, AlliedModders LLC grants
dvander@1908
    25
 * this exception to all derivative works.  AlliedModders LLC defines further
dvander@1908
    26
 * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
dvander@1908
    27
 * or <http://www.sourcemod.net/license.php>.
dvander@1908
    28
 *
dvander@1908
    29
 * Version: $Id$
dvander@1908
    30
 */
dvander@1908
    31
dvander@1908
    32
#include "Profiler.h"
dvander@1908
    33
#include "PluginSys.h"
dvander@1908
    34
#include "sm_stringutil.h"
dvander@1908
    35
#include "Logger.h"
dvander@1911
    36
#if defined PLATFORM_POSIX
dvander@1911
    37
#include <sys/time.h>
dvander@1911
    38
#include <time.h>
dvander@1911
    39
#endif
dvander@1908
    40
dvander@1908
    41
ProfileEngine g_Profiler;
dvander@1908
    42
IProfiler *sm_profiler = &g_Profiler;
dvander@1908
    43
dvander@1908
    44
#if defined PLATFORM_WINDOWS
dvander@1908
    45
double WINDOWS_PERFORMANCE_FREQUENCY;
dvander@1908
    46
#endif
dvander@1908
    47
dvander@1908
    48
class EmptyProfiler : public IProfiler
dvander@1908
    49
{
dvander@1908
    50
public:
dvander@1908
    51
	void OnNativeBegin(IPluginContext *pContext, sp_native_t *native)
dvander@1908
    52
	{
dvander@1908
    53
	}
dvander@1908
    54
	void OnNativeEnd()
dvander@1908
    55
	{
dvander@1908
    56
	}
dvander@1908
    57
	void OnFunctionBegin(IPluginContext *pContext, const char *name)
dvander@1908
    58
	{
dvander@1908
    59
	}
dvander@1908
    60
	void OnFunctionEnd()
dvander@1908
    61
	{
dvander@1908
    62
	}
dvander@1908
    63
	int OnCallbackBegin(IPluginContext *pContext, sp_public_t *pubfunc)
dvander@1908
    64
	{
dvander@1908
    65
		return 0;
dvander@1908
    66
	}
dvander@1908
    67
	void OnCallbackEnd(int serial)
dvander@1908
    68
	{
dvander@1908
    69
	}
dvander@1908
    70
} s_EmptyProfiler;
dvander@1908
    71
dvander@1908
    72
inline void InitProfPoint(prof_point_t &pt)
dvander@1908
    73
{
dvander@1908
    74
#if defined PLATFORM_WINDOWS
dvander@1908
    75
	QueryPerformanceCounter(&pt.value);
dvander@1908
    76
#elif defined PLATFORM_POSIX
dvander@1908
    77
	gettimeofday(&pt.value, NULL);
dvander@1908
    78
#endif
dvander@1908
    79
	pt.is_set = true;
dvander@1908
    80
}
dvander@1908
    81
dvander@1908
    82
ProfileEngine::ProfileEngine()
dvander@1908
    83
{
dvander@1908
    84
	m_serial = 0;
dvander@1908
    85
dvander@1908
    86
#if defined PLATFORM_WINDOWS
dvander@1908
    87
	LARGE_INTEGER pf;
dvander@1908
    88
dvander@1908
    89
	if (QueryPerformanceFrequency(&pf))
dvander@1908
    90
	{
dvander@1908
    91
		WINDOWS_PERFORMANCE_FREQUENCY = 1.0 / (double)(pf.QuadPart);
dvander@1908
    92
	}
dvander@1908
    93
	else
dvander@1908
    94
	{
dvander@1908
    95
		WINDOWS_PERFORMANCE_FREQUENCY = -1.0;
dvander@1908
    96
	}
dvander@1908
    97
#endif
dvander@1908
    98
dvander@1908
    99
	if (IsEnabled())
dvander@1908
   100
	{
dvander@1908
   101
		InitProfPoint(m_ProfStart);
dvander@1908
   102
	}
dvander@1908
   103
	else
dvander@1908
   104
	{
dvander@1908
   105
		sm_profiler = &s_EmptyProfiler;
dvander@1908
   106
	}
dvander@1908
   107
}
dvander@1908
   108
dvander@1908
   109
bool ProfileEngine::IsEnabled()
dvander@1908
   110
{
dvander@1908
   111
#if defined PLATFORM_WINDOWS
dvander@1908
   112
	return (WINDOWS_PERFORMANCE_FREQUENCY > 0.0);
dvander@1908
   113
#elif defined PLATFORM_POSIX
dvander@1908
   114
	return true;
dvander@1908
   115
#endif
dvander@1908
   116
}
dvander@1908
   117
dvander@1908
   118
inline double DiffProfPoints(const prof_point_t &start, const prof_point_t &end)
dvander@1908
   119
{
dvander@1908
   120
	double seconds;
dvander@1908
   121
dvander@1908
   122
#if defined PLATFORM_WINDOWS
dvander@1908
   123
	LONGLONG diff;
dvander@1908
   124
	
dvander@1908
   125
	diff = end.value.QuadPart - start.value.QuadPart;
dvander@1908
   126
	seconds = diff * WINDOWS_PERFORMANCE_FREQUENCY;
dvander@1908
   127
#elif defined PLATFORM_POSIX
dvander@1908
   128
	seconds = (double)(end.value.tv_sec - start.value.tv_sec);
dvander@1908
   129
dvander@1911
   130
	if (start.value.tv_usec > end.value.tv_usec)
dvander@1908
   131
	{
dvander@1911
   132
		seconds -= 1.0;
dvander@1911
   133
		seconds += (double)(1000000 - (start.value.tv_usec - end.value.tv_usec)) / 1000000.0;
dvander@1908
   134
	}
dvander@1908
   135
	else
dvander@1908
   136
	{
dvander@1911
   137
		seconds += (double)(end.value.tv_usec - start.value.tv_usec) / 1000000.0;
dvander@1908
   138
	}
dvander@1908
   139
#endif
dvander@1908
   140
dvander@1908
   141
	return seconds;
dvander@1908
   142
}
dvander@1908
   143
dvander@1908
   144
inline double CalcAtomTime(const prof_atom_t &atom)
dvander@1908
   145
{
dvander@1908
   146
	if (!atom.end.is_set)
dvander@1908
   147
	{
dvander@1908
   148
		return atom.base_time;
dvander@1908
   149
	}
dvander@1908
   150
dvander@1908
   151
	return atom.base_time + DiffProfPoints(atom.start, atom.end);
dvander@1908
   152
}
dvander@1908
   153
dvander@1908
   154
void ProfileEngine::OnNativeBegin(IPluginContext *pContext, sp_native_t *native)
dvander@1908
   155
{
dvander@1908
   156
	PushProfileStack(pContext, SP_PROF_NATIVES, native->name);
dvander@1908
   157
}
dvander@1908
   158
dvander@1908
   159
void ProfileEngine::OnNativeEnd()
dvander@1908
   160
{
dvander@1908
   161
	assert(!m_AtomStack.empty());
dvander@1908
   162
	assert(m_AtomStack.front().atom_type == SP_PROF_NATIVES);
dvander@1908
   163
dvander@1908
   164
	PopProfileStack(&m_Natives);
dvander@1908
   165
}
dvander@1908
   166
dvander@1908
   167
void ProfileEngine::OnFunctionBegin(IPluginContext *pContext, const char *name)
dvander@1908
   168
{
dvander@1908
   169
	PushProfileStack(pContext, SP_PROF_FUNCTIONS, name);
dvander@1908
   170
}
dvander@1908
   171
dvander@1908
   172
void ProfileEngine::OnFunctionEnd()
dvander@1908
   173
{
dvander@1908
   174
	assert(!m_AtomStack.empty());
dvander@1908
   175
	assert(m_AtomStack.front().atom_type == SP_PROF_FUNCTIONS);
dvander@1908
   176
dvander@1908
   177
	PopProfileStack(&m_Functions);
dvander@1908
   178
}
dvander@1908
   179
dvander@1908
   180
int ProfileEngine::OnCallbackBegin(IPluginContext *pContext, sp_public_t *pubfunc)
dvander@1908
   181
{
dvander@1908
   182
	PushProfileStack(pContext, SP_PROF_CALLBACKS, pubfunc->name);
dvander@1908
   183
dvander@1908
   184
	return m_serial;
dvander@1908
   185
}
dvander@1908
   186
dvander@1908
   187
void ProfileEngine::OnCallbackEnd(int serial)
dvander@1908
   188
{
dvander@1908
   189
	assert(!m_AtomStack.empty());
dvander@1908
   190
dvander@1908
   191
	/**
dvander@1908
   192
	 * Account for the situation where the JIT discards the 
dvander@1908
   193
	 * stack because there was an RTE of sorts.
dvander@1908
   194
	 */
dvander@1908
   195
	if (m_AtomStack.front().atom_type != SP_PROF_CALLBACKS
dvander@1908
   196
		&& m_AtomStack.front().atom_serial != serial)
dvander@1908
   197
	{
dvander@1908
   198
		prof_atom_t atom;
dvander@1908
   199
		double total_time;
dvander@1908
   200
dvander@1908
   201
		/* There was an error, and we need to discard things. */
dvander@1908
   202
		total_time = 0.0;
dvander@1908
   203
		while (!m_AtomStack.empty() 
dvander@1908
   204
			&& m_AtomStack.front().atom_type != SP_PROF_CALLBACKS 
dvander@1908
   205
			&& m_AtomStack.front().atom_serial != serial)
dvander@1908
   206
		{
dvander@1908
   207
			total_time += CalcAtomTime(m_AtomStack.front());
dvander@1908
   208
			m_AtomStack.pop();
dvander@1908
   209
		}
dvander@1908
   210
dvander@1908
   211
		/**
dvander@1908
   212
		 * Now we can end and discard ourselves, without saving the data.
dvander@1908
   213
		 * Since this data is all erroneous anyway, we don't care if it's 
dvander@1908
   214
		 * not totally accurate. 
dvander@1908
   215
		 */
dvander@1908
   216
dvander@1908
   217
		assert(!m_AtomStack.empty());
dvander@1908
   218
		atom = m_AtomStack.front();
dvander@1908
   219
		m_AtomStack.pop();
dvander@1908
   220
dvander@1908
   221
		/* Note: We don't need to resume ourselves because end is set by Pause(). */
dvander@1908
   222
		total_time += CalcAtomTime(atom);
dvander@1908
   223
dvander@1908
   224
		ResumeParent(total_time);
dvander@1908
   225
		return;
dvander@1908
   226
	}
dvander@1908
   227
dvander@1908
   228
	PopProfileStack(&m_Callbacks);
dvander@1908
   229
}
dvander@1908
   230
dvander@1908
   231
void ProfileEngine::PushProfileStack(IPluginContext *ctx, int type, const char *name)
dvander@1908
   232
{
dvander@1908
   233
	prof_atom_t atom;
dvander@1908
   234
dvander@1908
   235
	PauseParent();
dvander@1908
   236
dvander@1908
   237
	atom.atom_type = type;
dvander@1908
   238
	atom.base_time = 0.0;
dvander@1908
   239
	atom.ctx = ctx->GetContext();
dvander@1908
   240
	atom.name = name;
dvander@1908
   241
	atom.end.is_set = false;
dvander@1908
   242
dvander@1908
   243
	if (type == SP_PROF_CALLBACKS)
dvander@1908
   244
	{
dvander@1908
   245
		atom.atom_serial = ++m_serial;
dvander@1908
   246
	}
dvander@1908
   247
	else
dvander@1908
   248
	{
dvander@1908
   249
		atom.atom_serial = 0;
dvander@1908
   250
	}
dvander@1908
   251
dvander@1908
   252
	m_AtomStack.push(atom);
dvander@1908
   253
dvander@1908
   254
	/* Note: We do this after because the stack could grow and skew results */
dvander@1908
   255
	InitProfPoint(m_AtomStack.front().start);
dvander@1908
   256
}
dvander@1908
   257
dvander@1908
   258
void ProfileEngine::PopProfileStack(ProfileReport *reporter)
dvander@1908
   259
{
dvander@1908
   260
	double total_time;
dvander@1908
   261
dvander@1908
   262
	prof_atom_t &atom = m_AtomStack.front();
dvander@1908
   263
dvander@1908
   264
	/* We're okay to cache our used time. */
dvander@1908
   265
	InitProfPoint(atom.end);
dvander@1908
   266
	total_time = CalcAtomTime(atom);
dvander@1908
   267
dvander@1908
   268
	/* Now it's time to save this! This may do a lot of computations which 
dvander@1908
   269
	 * is why we've cached the time beforehand.
dvander@1908
   270
	 */
dvander@1908
   271
	reporter->SaveAtom(atom);
dvander@1908
   272
	m_AtomStack.pop();
dvander@1908
   273
dvander@1908
   274
	/* Finally, tell our parent how much time we used. */
dvander@1908
   275
	ResumeParent(total_time);
dvander@1908
   276
}
dvander@1908
   277
dvander@1908
   278
void ProfileEngine::PauseParent()
dvander@1908
   279
{
dvander@1908
   280
	if (m_AtomStack.empty())
dvander@1908
   281
	{
dvander@1908
   282
		return;
dvander@1908
   283
	}
dvander@1908
   284
dvander@1908
   285
	InitProfPoint(m_AtomStack.front().end);
dvander@1908
   286
}
dvander@1908
   287
dvander@1908
   288
void ProfileEngine::ResumeParent(double addTime)
dvander@1908
   289
{
dvander@1908
   290
	if (m_AtomStack.empty())
dvander@1908
   291
	{
dvander@1908
   292
		return;
dvander@1908
   293
	}
dvander@1908
   294
dvander@1908
   295
	prof_atom_t &atom = m_AtomStack.front();
dvander@1908
   296
dvander@1908
   297
	/* Move its "paused time" to its base (known) time, 
dvander@1908
   298
	 * then reset the start/end.  Note that since CalcAtomTime() 
dvander@1908
   299
	 * reads the base time, we SHOULD NOT use += to add.
dvander@1908
   300
	 */
dvander@1908
   301
	atom.base_time = CalcAtomTime(atom);
dvander@1908
   302
	atom.base_time += addTime;
dvander@1908
   303
	InitProfPoint(atom.start);
dvander@1908
   304
	atom.end.is_set = false;
dvander@1908
   305
}
dvander@1908
   306
dvander@1908
   307
void ProfileEngine::Clear()
dvander@1908
   308
{
dvander@1908
   309
	m_Natives.Clear();
dvander@1908
   310
	m_Callbacks.Clear();
dvander@1908
   311
	m_Functions.Clear();
dvander@1908
   312
	InitProfPoint(m_ProfStart);
dvander@1908
   313
}
dvander@1908
   314
dvander@1908
   315
void ProfileEngine::OnSourceModAllInitialized()
dvander@1908
   316
{
dvander@1908
   317
	g_RootMenu.AddRootConsoleCommand("profiler", "Profiler commands", this);
dvander@1908
   318
}
dvander@1908
   319
dvander@1908
   320
void ProfileEngine::OnSourceModShutdown()
dvander@1908
   321
{
dvander@1908
   322
	g_RootMenu.RemoveRootConsoleCommand("profiler", this);
dvander@1908
   323
}
dvander@1908
   324
dvander@1908
   325
void ProfileEngine::OnRootConsoleCommand(const char *cmdname, const CCommand &command)
dvander@1908
   326
{
dvander@1908
   327
	if (command.ArgC() >= 3)
dvander@1908
   328
	{
dvander@1908
   329
		if (strcmp(command.Arg(2), "flush") == 0)
dvander@1908
   330
		{
dvander@1908
   331
			FILE *fp;
dvander@1908
   332
			char path[256];
dvander@1908
   333
dvander@1908
   334
			g_SourceMod.BuildPath(Path_SM, path, sizeof(path), "logs/profile_%d.xml", (int)time(NULL));
dvander@1908
   335
dvander@1908
   336
			if ((fp = fopen(path, "wt")) == NULL)
dvander@1908
   337
			{
dvander@1908
   338
				g_RootMenu.ConsolePrint("Failed, could not open file for writing: %s", path);
dvander@1908
   339
				return;
dvander@1908
   340
			}
dvander@1908
   341
dvander@1908
   342
			GenerateReport(fp);
dvander@1908
   343
dvander@1908
   344
			fclose(fp);
dvander@1908
   345
dvander@1908
   346
			g_RootMenu.ConsolePrint("Profiler report generated as: %s\n", path);
dvander@1908
   347
dvander@1908
   348
			return;
dvander@1908
   349
		}
dvander@1908
   350
	}
dvander@1908
   351
dvander@1908
   352
	g_RootMenu.ConsolePrint("Profiler commands:");
dvander@1908
   353
	g_RootMenu.DrawGenericOption("flush", "Flushes statistics to disk and starts over");
dvander@1908
   354
}
dvander@1908
   355
dvander@1908
   356
bool ProfileEngine::GenerateReport(FILE *fp)
dvander@1908
   357
{
dvander@1908
   358
	time_t t;
dvander@1908
   359
	double total_time;
dvander@1908
   360
	prof_point_t end_time;
dvander@1908
   361
dvander@1908
   362
	InitProfPoint(end_time);
dvander@1908
   363
	total_time = DiffProfPoints(m_ProfStart, end_time);
dvander@1908
   364
dvander@1908
   365
	t = time(NULL);
dvander@1908
   366
dvander@1908
   367
	fprintf(fp, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n");
dvander@1908
   368
	fprintf(fp, "<profile time=\"%d\" uptime=\"%f\">\n", (int)t, total_time);
dvander@1908
   369
	WriteReport(fp, &m_Natives, "natives");
dvander@1908
   370
	WriteReport(fp, &m_Callbacks, "callbacks");
dvander@1908
   371
	WriteReport(fp, &m_Functions, "functions");
dvander@1908
   372
	fprintf(fp, "</profile>\n");
dvander@1908
   373
dvander@1908
   374
	return true;
dvander@1908
   375
}
dvander@1908
   376
dvander@1908
   377
void ProfileEngine::WriteReport(FILE *fp, ProfileReport *report, const char *name)
dvander@1908
   378
{
dvander@1908
   379
	size_t i, num;
dvander@1908
   380
	prof_atom_report_t *ar;
dvander@1908
   381
	char new_name[512];
dvander@1908
   382
dvander@1908
   383
	fprintf(fp, " <report name=\"%s\">\n", name);
dvander@1908
   384
	
dvander@1908
   385
	num = report->GetNumReports();
dvander@1908
   386
	for (i = 0; i < num; i++)
dvander@1908
   387
	{
dvander@1908
   388
		ar = report->GetReport(i);
dvander@1908
   389
dvander@1908
   390
		strncopy(new_name, ar->atom_name, sizeof(new_name));
dvander@1908
   391
		UTIL_ReplaceAll(new_name, sizeof(new_name), "<", "&lt;");
dvander@1908
   392
		UTIL_ReplaceAll(new_name, sizeof(new_name), ">", "&gt;");
dvander@1908
   393
dvander@1908
   394
		fprintf(fp, "  <item name=\"%s\" numcalls=\"%d\" mintime=\"%f\" maxtime=\"%f\" totaltime=\"%f\"/>\n", 
dvander@1908
   395
			new_name,
dvander@1908
   396
			ar->num_calls,
dvander@1908
   397
			ar->min_time,
dvander@1908
   398
			ar->max_time,
dvander@1908
   399
			ar->total_time);
dvander@1908
   400
	}
dvander@1908
   401
dvander@1908
   402
	fprintf(fp, " </report>\n");
dvander@1908
   403
}
dvander@1908
   404
dvander@1908
   405
ProfileReport::~ProfileReport()
dvander@1908
   406
{
dvander@1908
   407
	for (size_t i = 0; i < m_Reports.size(); i++)
dvander@1908
   408
	{
dvander@1908
   409
		delete m_Reports[i];
dvander@1908
   410
	}
dvander@1908
   411
}
dvander@1908
   412
dvander@1908
   413
void ProfileReport::Clear()
dvander@1908
   414
{
dvander@1908
   415
	m_ReportLookup.clear();
dvander@1908
   416
	for (size_t i = 0; i < m_Reports.size(); i++)
dvander@1908
   417
	{
dvander@1908
   418
		delete m_Reports[i];
dvander@1908
   419
	}
dvander@1908
   420
	m_Reports.clear();
dvander@1908
   421
}
dvander@1908
   422
dvander@1908
   423
size_t ProfileReport::GetNumReports()
dvander@1908
   424
{
dvander@1908
   425
	return m_Reports.size();
dvander@1908
   426
}
dvander@1908
   427
dvander@1908
   428
prof_atom_report_t *ProfileReport::GetReport(size_t i)
dvander@1908
   429
{
dvander@1908
   430
	return m_Reports[i];
dvander@1908
   431
}
dvander@1908
   432
dvander@1908
   433
void ProfileReport::SaveAtom(const prof_atom_t &atom)
dvander@1908
   434
{
dvander@1908
   435
	double atom_time;
dvander@1908
   436
	char full_name[256];
dvander@1908
   437
	prof_atom_report_t **pReport, *report;
dvander@1908
   438
dvander@1908
   439
	if (atom.atom_type == SP_PROF_NATIVES)
dvander@1908
   440
	{
dvander@1908
   441
		strncopy(full_name, atom.name, sizeof(full_name));
dvander@1908
   442
	}
dvander@1908
   443
	else
dvander@1908
   444
	{
dvander@1908
   445
		CPlugin *pl;
dvander@1908
   446
		const char *file;
dvander@1908
   447
dvander@1908
   448
		file = "unknown";
dvander@1908
   449
		if ((pl = g_PluginSys.GetPluginByCtx(atom.ctx)) != NULL)
dvander@1908
   450
		{
dvander@1908
   451
			file = pl->GetFilename();
dvander@1908
   452
		}
dvander@1908
   453
dvander@1908
   454
		UTIL_Format(full_name, sizeof(full_name), "%s!%s", file, atom.name);
dvander@1908
   455
	}
dvander@1908
   456
dvander@1908
   457
	atom_time = CalcAtomTime(atom);
dvander@1908
   458
dvander@1908
   459
	if ((pReport = m_ReportLookup.retrieve(full_name)) == NULL)
dvander@1908
   460
	{
dvander@1908
   461
		report = new prof_atom_report_t;
dvander@1908
   462
dvander@1908
   463
		strncopy(report->atom_name, full_name, sizeof(report->atom_name));
dvander@1908
   464
		report->max_time = atom_time;
dvander@1908
   465
		report->min_time = atom_time;
dvander@1908
   466
		report->num_calls = 1;
dvander@1908
   467
		report->total_time = atom_time;
dvander@1908
   468
		
dvander@1908
   469
		m_ReportLookup.insert(full_name, report);
dvander@1908
   470
		m_Reports.push_back(report);
dvander@1908
   471
	}
dvander@1908
   472
	else
dvander@1908
   473
	{
dvander@1908
   474
		report = *pReport;
dvander@1908
   475
dvander@1908
   476
		if (atom_time > report->max_time)
dvander@1908
   477
		{
dvander@1908
   478
			report->max_time = atom_time;
dvander@1908
   479
		}
dvander@1908
   480
		if (atom_time < report->min_time)
dvander@1908
   481
		{
dvander@1908
   482
			report->min_time = atom_time;
dvander@1908
   483
		}
dvander@1908
   484
		report->num_calls++;
dvander@1908
   485
		report->total_time += atom_time;
dvander@1908
   486
	}
dvander@1908
   487
}