mirror of
https://github.com/gnustep/libs-performance.git
synced 2025-02-15 08:00:52 +00:00
git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/performance/trunk@22004 72102866-910b-0410-8b05-ffd578937521
683 lines
15 KiB
Objective-C
683 lines
15 KiB
Objective-C
/* -*-objc-*- */
|
||
|
||
/** Implementation of GSThroughput for GNUStep
|
||
Copyright (C) 2005 Free Software Foundation, Inc.
|
||
|
||
Written by: Richard Frith-Macdonald <rfm@gnu.org>
|
||
Date: October 2005
|
||
|
||
This file is part of the Performance Library.
|
||
|
||
This library is free software; you can redistribute it and/or
|
||
modify it under the terms of the GNU Library General Public
|
||
License as published by the Free Software Foundation; either
|
||
version 2 of the License, or (at your option) any later version.
|
||
|
||
This library is distributed in the hope that it will be useful,
|
||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||
Library General Public License for more details.
|
||
|
||
You should have received a copy of the GNU Library General Public
|
||
License along with this library; if not, write to the Free
|
||
Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111 USA.
|
||
|
||
$Date$ $Revision$
|
||
*/
|
||
|
||
#include <Foundation/NSArray.h>
|
||
#include <Foundation/NSString.h>
|
||
#include <Foundation/NSData.h>
|
||
#include <Foundation/NSDate.h>
|
||
#include <Foundation/NSCalendarDate.h>
|
||
#include <Foundation/NSException.h>
|
||
#include <Foundation/NSNotification.h>
|
||
#include <Foundation/NSHashTable.h>
|
||
#include <Foundation/NSAutoreleasePool.h>
|
||
#include <Foundation/NSDebug.h>
|
||
#include <Foundation/NSTimer.h>
|
||
#include <Foundation/NSThread.h>
|
||
|
||
#include "GSThroughput.h"
|
||
#include "GSTicker.h"
|
||
|
||
#define MAXDURATION 24.0*60.0*60.0
|
||
|
||
@class GSThroughputThread;
|
||
|
||
static Class NSDateClass = 0;
|
||
static SEL tiSel = 0;
|
||
static NSTimeInterval (*tiImp)(Class,SEL) = 0;
|
||
|
||
typedef struct {
|
||
unsigned cnt; // Number of events.
|
||
unsigned tick; // Start time
|
||
} CInfo;
|
||
|
||
typedef struct {
|
||
unsigned cnt; // Number of events.
|
||
NSTimeInterval max; // Longest duration
|
||
NSTimeInterval min; // Shortest duration
|
||
NSTimeInterval sum; // Total (sum of durations for event)
|
||
unsigned tick; // Start time
|
||
} DInfo;
|
||
|
||
typedef struct {
|
||
void *seconds;
|
||
void *minutes;
|
||
void *periods;
|
||
BOOL supportDurations;
|
||
unsigned numberOfPeriods;
|
||
unsigned minutesPerPeriod;
|
||
unsigned second;
|
||
unsigned minute;
|
||
unsigned period;
|
||
unsigned last; // last tick used
|
||
NSTimeInterval started; // When duration logging started.
|
||
NSString *event; // Name of current event
|
||
NSString *name; // Name of this instance
|
||
GSThroughputThread *thread; // Thread info
|
||
} Item;
|
||
#define my ((Item*)_data)
|
||
|
||
#define cseconds ((CInfo*)my->seconds)
|
||
#define cminutes ((CInfo*)my->minutes)
|
||
#define cperiods ((CInfo*)my->periods)
|
||
#define dseconds ((DInfo*)my->seconds)
|
||
#define dminutes ((DInfo*)my->minutes)
|
||
#define dperiods ((DInfo*)my->periods)
|
||
|
||
|
||
|
||
@interface GSThroughputThread : NSObject
|
||
{
|
||
@public
|
||
NSTimer *theTimer;
|
||
NSHashTable *instances;
|
||
}
|
||
@end
|
||
|
||
@interface GSThroughput (Private)
|
||
+ (GSThroughputThread*) _threadInfo;
|
||
+ (void) newSecond: (GSThroughputThread*)t;
|
||
- (void) _detach;
|
||
- (void) _update;
|
||
@end
|
||
|
||
|
||
|
||
@implementation GSThroughputThread
|
||
- (void) dealloc
|
||
{
|
||
if (instances != 0)
|
||
{
|
||
NSHashEnumerator e;
|
||
GSThroughput *i;
|
||
|
||
e = NSEnumerateHashTable(instances);
|
||
while ((i = (GSThroughput*)NSNextHashEnumeratorItem(&e)) != nil)
|
||
{
|
||
[i _detach];
|
||
}
|
||
NSEndHashTableEnumeration(&e);
|
||
NSFreeHashTable(instances);
|
||
instances = 0;
|
||
}
|
||
[super dealloc];
|
||
}
|
||
|
||
- (id) init
|
||
{
|
||
instances = NSCreateHashTable(NSNonRetainedObjectHashCallBacks, 0);
|
||
return self;
|
||
}
|
||
|
||
@end
|
||
|
||
|
||
|
||
@implementation GSThroughput (Private)
|
||
|
||
+ (GSThroughputThread*) _threadInfo
|
||
{
|
||
GSThroughputThread *t;
|
||
|
||
t = [[[NSThread currentThread] threadDictionary]
|
||
objectForKey: @"GSThroughput"];
|
||
if (t == nil)
|
||
{
|
||
t = [GSThroughputThread new];
|
||
[[[NSThread currentThread] threadDictionary] setObject: t
|
||
forKey: @"GSThroughput"];
|
||
RELEASE(t);
|
||
}
|
||
return t;
|
||
}
|
||
|
||
+ (void) newSecond: (GSThroughputThread*)t
|
||
{
|
||
NSHashEnumerator e;
|
||
GSThroughput *i;
|
||
|
||
e = NSEnumerateHashTable(t->instances);
|
||
while ((i = (GSThroughput*)NSNextHashEnumeratorItem(&e)) != nil)
|
||
{
|
||
[i _update];
|
||
}
|
||
NSEndHashTableEnumeration(&e);
|
||
}
|
||
|
||
- (void) _detach
|
||
{
|
||
my->thread = nil;
|
||
}
|
||
|
||
- (void) _update
|
||
{
|
||
if (my->thread != nil)
|
||
{
|
||
unsigned tick = GSTickerTimeTick();
|
||
unsigned i;
|
||
|
||
if (my->supportDurations == YES)
|
||
{
|
||
while (my->last < tick)
|
||
{
|
||
DInfo *info;
|
||
|
||
if (my->second++ == 59)
|
||
{
|
||
info = &dminutes[my->minute];
|
||
for (i = 0; i < 60; i++)
|
||
{
|
||
DInfo *from = &dseconds[i];
|
||
|
||
info->cnt += from->cnt;
|
||
if (from->min < info->min)
|
||
{
|
||
info->min = from->min;
|
||
}
|
||
if (from->max > info->max)
|
||
{
|
||
info->max = from->max;
|
||
}
|
||
info->sum += from->sum;
|
||
}
|
||
if (my->minute++ == my->minutesPerPeriod - 1)
|
||
{
|
||
info = &dperiods[my->period];
|
||
for (i = 0; i < my->minutesPerPeriod; i++)
|
||
{
|
||
DInfo *from = &dminutes[i];
|
||
|
||
info->cnt += from->cnt;
|
||
if (from->min > 0.0 && from->min < info->min)
|
||
{
|
||
info->min = from->min;
|
||
}
|
||
if (from->max > info->max)
|
||
{
|
||
info->max = from->max;
|
||
}
|
||
info->sum += from->sum;
|
||
}
|
||
if (my->period++ == my->numberOfPeriods - 1)
|
||
{
|
||
my->period = 0;
|
||
}
|
||
info = &dperiods[my->period];
|
||
info->cnt = 0;
|
||
info->max = 0.0;
|
||
info->min = MAXDURATION;
|
||
info->sum = 0.0;
|
||
info->tick = my->last;
|
||
my->minute = 0;
|
||
}
|
||
info = &dminutes[my->minute];
|
||
info->cnt = 0;
|
||
info->max = 0.0;
|
||
info->min = MAXDURATION;
|
||
info->sum = 0.0;
|
||
info->tick = my->last;
|
||
my->second = 0;
|
||
}
|
||
info = &dseconds[my->second];
|
||
info->cnt = 0;
|
||
info->max = 0.0;
|
||
info->min = MAXDURATION;
|
||
info->sum = 0.0;
|
||
info->tick = my->last;
|
||
|
||
my->last++;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
while (my->last < tick)
|
||
{
|
||
CInfo *info;
|
||
|
||
if (my->second++ == 59)
|
||
{
|
||
info = &cminutes[my->minute];
|
||
for (i = 0; i < 60; i++)
|
||
{
|
||
info->cnt += cseconds[i].cnt;
|
||
}
|
||
if (my->minute++ == my->minutesPerPeriod - 1)
|
||
{
|
||
info = &cperiods[my->period];
|
||
for (i = 0; i < my->minutesPerPeriod; i++)
|
||
{
|
||
info->cnt += cminutes[i].cnt;
|
||
}
|
||
if (my->period++ == my->numberOfPeriods - 1)
|
||
{
|
||
my->period = 0;
|
||
}
|
||
info = &cperiods[my->period];
|
||
info->cnt = 0;
|
||
info->tick = my->last;
|
||
my->minute = 0;
|
||
}
|
||
info = &cminutes[my->minute];
|
||
info->cnt = 0;
|
||
info->tick = my->last;
|
||
my->second = 0;
|
||
}
|
||
info = &cseconds[my->second];
|
||
info->cnt = 0;
|
||
info->tick = my->last;
|
||
|
||
my->last++;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
@end
|
||
|
||
|
||
|
||
@implementation GSThroughput
|
||
|
||
+ (NSArray*) allInstances
|
||
{
|
||
GSThroughputThread *t;
|
||
NSArray *a;
|
||
|
||
t = [[[NSThread currentThread] threadDictionary]
|
||
objectForKey: @"GSThroughput"];
|
||
if (t == nil)
|
||
{
|
||
a = nil;
|
||
}
|
||
else
|
||
{
|
||
a = NSAllHashTableObjects(t->instances);
|
||
}
|
||
return a;
|
||
}
|
||
|
||
+ (NSString*) description
|
||
{
|
||
GSThroughputThread *t;
|
||
NSMutableString *ms;
|
||
|
||
ms = [NSMutableString stringWithString: [super description]];
|
||
t = [[[NSThread currentThread] threadDictionary]
|
||
objectForKey: @"GSThroughput"];
|
||
if (t != nil)
|
||
{
|
||
NSHashEnumerator e;
|
||
GSThroughput *c;
|
||
|
||
e = NSEnumerateHashTable(t->instances);
|
||
while ((c = (GSThroughput*)NSNextHashEnumeratorItem(&e)) != nil)
|
||
{
|
||
[ms appendFormat: @"\n%@", [c description]];
|
||
}
|
||
NSEndHashTableEnumeration(&e);
|
||
}
|
||
return ms;
|
||
}
|
||
|
||
+ (void) initialize
|
||
{
|
||
if (NSDateClass == 0)
|
||
{
|
||
NSDateClass = [NSDate class];
|
||
tiSel = @selector(timeIntervalSinceReferenceDate);
|
||
tiImp
|
||
= (NSTimeInterval (*)(Class,SEL))[NSDateClass methodForSelector: tiSel];
|
||
}
|
||
}
|
||
|
||
+ (void) setTick: (BOOL)aFlag
|
||
{
|
||
if (aFlag == YES)
|
||
{
|
||
GSThroughputThread *t = [self _threadInfo];
|
||
|
||
[GSTicker registerObserver: (id<GSTicker>)self userInfo: t];
|
||
}
|
||
else
|
||
{
|
||
[GSTicker unregisterObserver: (id<GSTicker>)self];
|
||
}
|
||
}
|
||
|
||
+ (void) tick
|
||
{
|
||
[self newSecond: [self _threadInfo]];
|
||
}
|
||
|
||
- (void) add: (unsigned)count
|
||
{
|
||
NSAssert(my->supportDurations == NO, @"configured for durations");
|
||
cseconds[my->second].cnt += count;
|
||
}
|
||
|
||
- (void) addDuration: (NSTimeInterval)length
|
||
{
|
||
DInfo *info;
|
||
|
||
NSAssert(my->supportDurations == YES, @"not configured for durations");
|
||
info = &dseconds[my->second];
|
||
if (info->cnt++ == 0)
|
||
{
|
||
info->min = length;
|
||
info->max = length;
|
||
info->sum = length;
|
||
}
|
||
else
|
||
{
|
||
info->sum += length;
|
||
if (length > info->max)
|
||
{
|
||
info->max = length;
|
||
}
|
||
if (length < info->min)
|
||
{
|
||
info->min = length;
|
||
}
|
||
}
|
||
}
|
||
|
||
- (void) dealloc
|
||
{
|
||
if (_data)
|
||
{
|
||
if (my->seconds != 0)
|
||
{
|
||
NSZoneFree(NSDefaultMallocZone(), my->seconds);
|
||
}
|
||
RELEASE(my->name);
|
||
if (my->thread != nil)
|
||
{
|
||
NSHashRemove(my->thread->instances, (void*)self);
|
||
my->thread = nil;
|
||
}
|
||
NSZoneFree(NSDefaultMallocZone(), _data);
|
||
_data = 0;
|
||
}
|
||
[super dealloc];
|
||
}
|
||
|
||
- (NSString*) description
|
||
{
|
||
CREATE_AUTORELEASE_POOL(pool);
|
||
NSString *n = my->name;
|
||
NSMutableString *m;
|
||
unsigned i;
|
||
|
||
if (n == nil)
|
||
{
|
||
n = [super description];
|
||
}
|
||
m = [n mutableCopy];
|
||
|
||
if (my->thread != nil)
|
||
{
|
||
NSTimeInterval baseTime = GSTickerTimeStart();
|
||
unsigned tick;
|
||
|
||
if (my->supportDurations == YES)
|
||
{
|
||
[m appendString: @"\nSeconds in current minute:\n"];
|
||
if (my->second > 0)
|
||
{
|
||
tick = 0;
|
||
for (i = 0; i < my->second; i++)
|
||
{
|
||
DInfo *info = &dseconds[i];
|
||
NSTimeInterval ti = info->tick + baseTime;
|
||
|
||
if (info->tick != tick)
|
||
{
|
||
tick = info->tick;
|
||
[m appendFormat: @"%u, %g, %g, %g, %@\n",
|
||
info->cnt, info->max, info->min, info->sum,
|
||
[NSDate dateWithTimeIntervalSinceReferenceDate: ti]];
|
||
}
|
||
}
|
||
}
|
||
|
||
[m appendString: @"\nPrevious minutes in current period:\n"];
|
||
if (my->minute > 0)
|
||
{
|
||
tick = 0;
|
||
for (i = 0; i < my->minute; i++)
|
||
{
|
||
DInfo *info = &dminutes[i];
|
||
NSTimeInterval ti = info->tick + baseTime;
|
||
|
||
if (info->tick != tick)
|
||
{
|
||
tick = info->tick;
|
||
[m appendFormat: @"%u, %g, %g, %g, %@\n",
|
||
info->cnt, info->max, info->min, info->sum,
|
||
[NSDate dateWithTimeIntervalSinceReferenceDate: ti]];
|
||
}
|
||
}
|
||
}
|
||
|
||
[m appendString: @"\nPrevious periods:\n"];
|
||
if (my->period > 0)
|
||
{
|
||
tick = 0;
|
||
for (i = 0; i < my->period; i++)
|
||
{
|
||
DInfo *info = &dperiods[i];
|
||
NSTimeInterval ti = info->tick + baseTime;
|
||
|
||
if (info->tick != tick)
|
||
{
|
||
tick = info->tick;
|
||
[m appendFormat: @"%u, %g, %g, %g, %@\n",
|
||
info->cnt, info->max, info->min, info->sum,
|
||
[NSDate dateWithTimeIntervalSinceReferenceDate: ti]];
|
||
}
|
||
}
|
||
}
|
||
}
|
||
else
|
||
{
|
||
[m appendString: @"\nSeconds in current minute:\n"];
|
||
if (my->second > 0)
|
||
{
|
||
tick = 0;
|
||
for (i = 0; i < my->second; i++)
|
||
{
|
||
CInfo *info = &cseconds[i];
|
||
NSTimeInterval ti = info->tick + baseTime;
|
||
|
||
if (info->tick != tick)
|
||
{
|
||
tick = info->tick;
|
||
[m appendFormat: @"%u, %@\n", info->cnt,
|
||
[NSDate dateWithTimeIntervalSinceReferenceDate: ti]];
|
||
}
|
||
}
|
||
}
|
||
|
||
[m appendString: @"\nPrevious minutes in current period:\n"];
|
||
if (my->minute > 0)
|
||
{
|
||
tick = 0;
|
||
for (i = 0; i < my->minute; i++)
|
||
{
|
||
CInfo *info = &cminutes[i];
|
||
NSTimeInterval ti = info->tick + baseTime;
|
||
|
||
if (info->tick != tick)
|
||
{
|
||
tick = info->tick;
|
||
[m appendFormat: @"%u, %@\n", info->cnt,
|
||
[NSDate dateWithTimeIntervalSinceReferenceDate: ti]];
|
||
}
|
||
}
|
||
}
|
||
|
||
[m appendString: @"\nPrevious periods:\n"];
|
||
if (my->period > 0)
|
||
{
|
||
tick = 0;
|
||
for (i = 0; i < my->period; i++)
|
||
{
|
||
CInfo *info = &cperiods[i];
|
||
NSTimeInterval ti = info->tick + baseTime;
|
||
|
||
if (info->tick != tick)
|
||
{
|
||
tick = info->tick;
|
||
[m appendFormat: @"%u, %@\n", info->cnt,
|
||
[NSDate dateWithTimeIntervalSinceReferenceDate: ti]];
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
DESTROY(pool);
|
||
return AUTORELEASE(m);
|
||
}
|
||
|
||
- (void) endDuration
|
||
{
|
||
NSAssert(my->started > 0.0, NSInternalInconsistencyException);
|
||
[self addDuration: (*tiImp)(NSDateClass, tiSel) - my->started];
|
||
my->event = nil;
|
||
my->started = 0.0;
|
||
}
|
||
|
||
- (id) init
|
||
{
|
||
return [self initWithDurations: YES
|
||
forPeriods: 96
|
||
ofLength: 15];
|
||
}
|
||
|
||
- (id) initWithDurations: (BOOL)aFlag
|
||
forPeriods: (unsigned)numberOfPeriods
|
||
ofLength: (unsigned)minutesPerPeriod
|
||
{
|
||
NSCalendarDate *c;
|
||
unsigned i;
|
||
|
||
_data = (Item*)NSZoneMalloc(NSDefaultMallocZone(), sizeof(Item));
|
||
memset(_data, '\0', sizeof(Item));
|
||
|
||
if (numberOfPeriods < 1 || minutesPerPeriod < 1)
|
||
{
|
||
DESTROY(self);
|
||
return nil;
|
||
}
|
||
|
||
/*
|
||
* Add this instance to the current thread.
|
||
*/
|
||
my->thread = [[self class] _threadInfo];
|
||
NSHashInsert(my->thread->instances, (void*)self);
|
||
|
||
my->supportDurations = aFlag;
|
||
my->numberOfPeriods = numberOfPeriods;
|
||
my->minutesPerPeriod = minutesPerPeriod;
|
||
my->last = GSTickerTimeTick();
|
||
c = [[NSCalendarDate alloc] initWithTimeIntervalSinceReferenceDate:
|
||
GSTickerTimeLast()];
|
||
my->second = [c secondOfMinute];
|
||
i = [c hourOfDay] * 60 + [c minuteOfHour];
|
||
my->minute = i % minutesPerPeriod;
|
||
my->period = (i / minutesPerPeriod) % numberOfPeriods;
|
||
RELEASE(c);
|
||
|
||
i = 60 + minutesPerPeriod + numberOfPeriods;
|
||
if (my->supportDurations == YES)
|
||
{
|
||
DInfo *ptr;
|
||
|
||
ptr = (DInfo*)NSZoneMalloc(NSDefaultMallocZone(), sizeof(DInfo) * i);
|
||
memset(ptr, '\0', sizeof(DInfo) * i);
|
||
my->seconds = ptr;
|
||
my->minutes = ptr + 60;
|
||
my->periods = ptr + 60 + minutesPerPeriod;
|
||
dseconds[my->second].tick = my->last;
|
||
dminutes[my->minute].tick = my->last;
|
||
dperiods[my->period].tick = my->last;
|
||
|
||
for (i = 0; i < my->numberOfPeriods; i++)
|
||
{
|
||
dperiods[i].min = MAXDURATION;
|
||
}
|
||
for (i = 0; i < my->minutesPerPeriod; i++)
|
||
{
|
||
dminutes[i].min = MAXDURATION;
|
||
}
|
||
for (i = 0; i < 60; i++)
|
||
{
|
||
dseconds[i].min = MAXDURATION;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
CInfo *ptr;
|
||
|
||
ptr = (CInfo*)NSZoneMalloc(NSDefaultMallocZone(), sizeof(CInfo) * i);
|
||
memset(ptr, '\0', sizeof(CInfo) * i);
|
||
my->seconds = ptr;
|
||
my->minutes = ptr + 60;
|
||
my->periods = ptr + 60 + minutesPerPeriod;
|
||
cseconds[my->second].tick = my->last;
|
||
cminutes[my->minute].tick = my->last;
|
||
cperiods[my->period].tick = my->last;
|
||
}
|
||
return self;
|
||
}
|
||
|
||
- (NSString*) name
|
||
{
|
||
return my->name;
|
||
}
|
||
|
||
- (void) setName: (NSString*)name
|
||
{
|
||
ASSIGN(my->name, name);
|
||
}
|
||
|
||
- (void) startDuration: (NSString*)name
|
||
{
|
||
NSAssert(my->supportDurations == YES && my->started == 0.0,
|
||
NSInternalInconsistencyException);
|
||
if (my->event != nil)
|
||
{
|
||
[NSException raise: NSInternalInconsistencyException
|
||
format: @"-startDuration: for '%@' nested inside '%@'",
|
||
my->event, name];
|
||
}
|
||
my->started = (*tiImp)(NSDateClass, tiSel);
|
||
my->event = name;
|
||
}
|
||
|
||
@end
|
||
|