diff --git a/GSThroughput.h b/GSThroughput.h index 7f8ba4b..02b6062 100644 --- a/GSThroughput.h +++ b/GSThroughput.h @@ -31,34 +31,38 @@ /** * The GSThroughput class is used maintain statistics about the number - * of events or the duration of operations in your software. + * of events or the duration of operations in your software.
+ * For performance reasons, the class avoids locking and you must ensure + * that an instance of the class is only ever used by a single thread + * (the one in which it was created). */ @interface GSThroughput : NSObject { } /** - * Return all the current throughput measuring objects ... - * useful if you want to do to all instances in your process. + * Return all the current throughput measuring objects in the current thread... */ + (NSArray*) allInstances; /** - * Return a report on all GSThroughput instances ... calls - * the [GSThroughput-description] * method of the individual - * instances to get a report on each one. + * Return a report on all GSThroughput instances in the current thread... + * calls the [GSThroughput-description] method of the individual instances + * to get a report on each one. */ + (NSString*) description; /** - * Instructs the minitoring system to use a timer at the specified interval - * for keeping its idea of the current time up to date. + * Instructs the monitoring system to use a timer at the specified interval + * for keeping its idea of the current time up to date. This timer is used + * by all instances associated with the current thread. */ + (void) setTick: (NSTimeInterval)interval; /** - * Updates the monitoring system's notion of the current time.
- * This should be called at the start of each (or more often) if + * Updates the monitoring system's notion of the current time for all + * instances associated with the current thread.
+ * This should be called at the start of each second (or more often) if * you want accurate monitoring by the second. */ + (void) tick; @@ -84,22 +88,22 @@ /** * Ends duration recording for the current event started by a matching - * call to -startDuration.
+ * call to the -startDuration: method.
* You may use this method only if the receiver was initialised with * duration logging turned on. */ - (void) endDuration; /** - * Initialises the receiver for duration logging for fifteen minute - * periods over the last twentyfour hours. + * Initialises the receiver for duration logging (in the current thread only) + * for fifteen minute periods over the last twentyfour hours. */ - - (id) init; +- (id) init; /** - * Initialises the receiver to maintain stats over a particular time range, - * specifying whether duration statistics are to be maintained, or just - * event/transation counts. + * Initialises the receiver to maintain stats (for the current thread only) + * over a particular time range, specifying whether duration statistics are + * to be maintained, or just event/transaction counts. */ - (id) initWithDurations: (BOOL)aFlag forPeriods: (unsigned)numberOfPeriods @@ -117,16 +121,16 @@ /** * Starts recording the duration of an event. This must be followed by - * a matching call to -endDuration.
+ * a matching call to the -endDuration method.
+ * The name argument is used to identify the location of the call for + * debugging/logging purposes, and you must ensure that the string + * continues to exist up to the point where -endDuration is called, + * as the receiver will not retain it.
* You may use this method only if the receiver was initialised with * duration logging turned on. */ -- (void) startDuration; +- (void) startDuration: (NSString*)name; -/** - * Internal method called by +tick in order to update stats for this instance. - */ -- (void) update; @end #endif diff --git a/GSThroughput.m b/GSThroughput.m index 47e4086..bb00fc7 100644 --- a/GSThroughput.m +++ b/GSThroughput.m @@ -33,37 +33,21 @@ #include #include #include -#include -#include #include -#include #include -#include #include +#include #include "GSThroughput.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; -static NSTimer *theTimer = nil; -static NSTimeInterval baseTime = 0; -static NSTimeInterval lastTime = 0; - -inline unsigned GSThroughputTimeTick() -{ - return (lastTime - baseTime) + 1; -} - - -@implementation GSThroughput - -static NSHashTable *GSThroughputInstances = 0; -static NSLock *GSThroughputLock = nil; - typedef struct { unsigned cnt; // Number of events. unsigned tick; // Start time @@ -89,7 +73,9 @@ typedef struct { unsigned period; unsigned last; // last tick used NSTimeInterval started; // When duration logging started. - NSString *name; + NSString *event; // Name of current event + NSString *name; // Name of this instance + GSThroughputThread *thread; // Thread info } Item; #define my ((Item*)&self[1]) @@ -100,110 +86,315 @@ typedef struct { #define dminutes ((DInfo*)my->minutes) #define dperiods ((DInfo*)my->periods) -+ (NSArray*) allInstances -{ - NSArray *a; + - [GSThroughputLock lock]; - a = NSAllHashTableObjects(GSThroughputInstances); - [GSThroughputLock unlock]; - return a; +@interface GSThroughputThread : NSObject +{ + @public + NSTimer *theTimer; + NSTimeInterval baseTime; + NSTimeInterval lastTime; + NSHashTable *instances; } +@end -+ (id) alloc +@interface GSThroughput (Private) ++ (GSThroughputThread*) _threadInfo; ++ (void) _tick: (NSTimer*)aTimer; ++ (void) _tickForThread: (GSThroughputThread*)t; +- (void) _detach; +- (void) _update; +@end + + + +@implementation GSThroughputThread +- (void) dealloc { - return [self allocWithZone: NSDefaultMallocZone()]; -} - -+ (id) allocWithZone: (NSZone*)z -{ - GSThroughput *c; - - c = (GSThroughput*)NSAllocateObject(self, sizeof(Item), z); - [GSThroughputLock lock]; - NSHashInsert(GSThroughputInstances, (void*)c); - [GSThroughputLock unlock]; - return c; -} - -+ (NSString*) description -{ - NSMutableString *ms; - NSHashEnumerator e; - GSThroughput *c; - - ms = [NSMutableString stringWithString: [super description]]; - [GSThroughputLock lock]; - e = NSEnumerateHashTable(GSThroughputInstances); - while ((c = (GSThroughput*)NSNextHashEnumeratorItem(&e)) != nil) + if (instances != 0) { - [ms appendFormat: @"\n%@", [c description]]; + NSHashEnumerator e; + GSThroughput *i; + + e = NSEnumerateHashTable(instances); + while ((i = (GSThroughput*)NSNextHashEnumeratorItem(&e)) != nil) + { + [i _detach]; + } + NSEndHashTableEnumeration(&e); + NSFreeHashTable(instances); + instances = 0; } - NSEndHashTableEnumeration(&e); - [GSThroughputLock unlock]; - return ms; + [super dealloc]; } -+ (void) initialize +- (id) init { - if (GSThroughputInstances == 0) - { - NSDateClass = [NSDate class]; - tiSel = @selector(timeIntervalSinceReferenceDate); - tiImp - = (NSTimeInterval (*)(Class,SEL))[NSDateClass methodForSelector: tiSel]; - baseTime = lastTime = (*tiImp)(NSDateClass, tiSel); - GSThroughputLock = [NSLock new]; - GSThroughputInstances - = NSCreateHashTable(NSNonRetainedObjectHashCallBacks, 0); - [self setTick: 1.0]; - } + baseTime = lastTime = (*tiImp)(NSDateClass, tiSel); + instances = NSCreateHashTable(NSNonRetainedObjectHashCallBacks, 0); + return self; } -+ (void) setTick: (NSTimeInterval)interval +@end + + + +@implementation GSThroughput (Private) + ++ (GSThroughputThread*) _threadInfo { - [GSThroughputLock lock]; - if (theTimer != nil) + GSThroughputThread *t; + + t = [[[NSThread currentThread] threadDictionary] + objectForKey: @"GSThroughput"]; + if (t == nil) { - [theTimer invalidate]; - theTimer = nil; + t = [GSThroughputThread new]; + [[[NSThread currentThread] threadDictionary] setObject: t + forKey: @"GSThroughput"]; + RELEASE(t); } - if (interval > 0.0) - { - theTimer = [NSTimer scheduledTimerWithTimeInterval: interval - target: self - selector: @selector(tick) - userInfo: 0 - repeats: YES]; - } - [GSThroughputLock unlock]; + return t; } -+ (void) tick ++ (void) _tick: (NSTimer*)aTimer +{ + [self _tickForThread: [aTimer userInfo]]; +} + ++ (void) _tickForThread: (GSThroughputThread*)t { NSTimeInterval now; NSHashEnumerator e; GSThroughput *i; - [GSThroughputLock lock]; /* * If the clock has been reset so that time has gone backwards, * we adjust the baseTime so that lastTime >= baseTime is true. */ now = (*tiImp)(NSDateClass, tiSel); - if (now < lastTime) + if (now < t->lastTime) { - baseTime -= (lastTime - now); + t->baseTime -= (t->lastTime - now); } - lastTime = now; - e = NSEnumerateHashTable(GSThroughputInstances); + t->lastTime = now; + e = NSEnumerateHashTable(t->instances); while ((i = (GSThroughput*)NSNextHashEnumeratorItem(&e)) != nil) { - [i update]; + [i _update]; } NSEndHashTableEnumeration(&e); +} - [GSThroughputLock unlock]; +- (void) _detach +{ + my->thread = nil; +} + +- (void) _update +{ + if (my->thread != nil) + { + unsigned tick = (my->thread->lastTime - my->thread->baseTime) + 1; + 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) + { + 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) + { + my->period = 0; + } + info = &dperiods[my->period]; + info->cnt = 0; + info->max = 0.0; + info->min = MAXDURATION; + info->sum = 0.0; + info->tick = tick; + my->minute = 0; + } + info = &dminutes[my->minute]; + info->cnt = 0; + info->max = 0.0; + info->min = MAXDURATION; + info->sum = 0.0; + info->tick = tick; + my->second = 0; + } + info = &dseconds[my->second]; + info->cnt = 0; + info->max = 0.0; + info->min = MAXDURATION; + info->sum = 0.0; + info->tick = tick; + + 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) + { + info = &cperiods[my->period]; + for (i = 0; i < my->minutesPerPeriod; i++) + { + info->cnt += cminutes[i].cnt; + } + if (my->period++ == my->numberOfPeriods) + { + my->period = 0; + } + info = &cperiods[my->period]; + info->cnt = 0; + info->tick = tick; + my->minute = 0; + } + info = &cminutes[my->minute]; + info->cnt = 0; + info->tick = tick; + my->second = 0; + } + info = &cseconds[my->second]; + info->cnt = 0; + info->tick = tick; + + 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: (NSTimeInterval)interval +{ + GSThroughputThread *t = [self _threadInfo]; + + if (t->theTimer != nil) + { + [t->theTimer invalidate]; + t->theTimer = nil; + } + if (interval > 0.0) + { + t->theTimer = [NSTimer scheduledTimerWithTimeInterval: interval + target: self + selector: @selector(_tick:) + userInfo: t + repeats: YES]; + } +} + ++ (void) tick +{ + [self _tickForThread: [self _threadInfo]]; } - (void) add: (unsigned)count @@ -240,15 +431,17 @@ typedef struct { - (void) dealloc { - [GSThroughputLock lock]; if (my->seconds != 0) { NSZoneFree(NSDefaultMallocZone(), my->seconds); } RELEASE(my->name); - NSHashRemove(GSThroughputInstances, (void*)self); + if (my->thread != nil) + { + NSHashRemove(my->thread->instances, (void*)self); + my->thread = nil; + } NSDeallocateObject(self); - [GSThroughputLock unlock]; } - (NSString*) description @@ -264,88 +457,93 @@ typedef struct { } m = [n mutableCopy]; - if (my->supportDurations == YES) + if (my->thread != nil) { - if (my->second > 0) - { - [m appendString: @"\nCurrent minute:\n"]; - for (i = 0; i < my->second; i++) - { - DInfo *info = &dseconds[i]; - NSTimeInterval ti = info->tick + baseTime; + NSTimeInterval baseTime = my->thread->baseTime; - [m appendFormat: @"%u, %g, %g, %g, %@\n", - info->cnt, info->max, info->min, info->sum, - [NSDate dateWithTimeIntervalSinceReferenceDate: ti]]; + if (my->supportDurations == YES) + { + if (my->second > 0) + { + [m appendString: @"\nCurrent minute:\n"]; + for (i = 0; i < my->second; i++) + { + DInfo *info = &dseconds[i]; + NSTimeInterval ti = info->tick + baseTime; + + [m appendFormat: @"%u, %g, %g, %g, %@\n", + info->cnt, info->max, info->min, info->sum, + [NSDate dateWithTimeIntervalSinceReferenceDate: ti]]; + } + } + + if (my->minute > 0) + { + [m appendString: @"\nCurrent period:\n"]; + for (i = 0; i < my->minute; i++) + { + DInfo *info = &dminutes[i]; + NSTimeInterval ti = info->tick + baseTime; + + [m appendFormat: @"%u, %g, %g, %g, %@\n", + info->cnt, info->max, info->min, info->sum, + [NSDate dateWithTimeIntervalSinceReferenceDate: ti]]; + } + } + + if (my->period > 0) + { + [m appendString: @"\nPrevious periods:\n"]; + for (i = 0; i < my->period; i++) + { + DInfo *info = &dperiods[i]; + NSTimeInterval ti = info->tick + baseTime; + + [m appendFormat: @"%u, %g, %g, %g, %@\n", + info->cnt, info->max, info->min, info->sum, + [NSDate dateWithTimeIntervalSinceReferenceDate: ti]]; + } } } - - if (my->minute > 0) + else { - [m appendString: @"\nCurrent period:\n"]; - for (i = 0; i < my->minute; i++) + if (my->second > 0) { - DInfo *info = &dminutes[i]; - NSTimeInterval ti = info->tick + baseTime; + [m appendString: @"\nCurrent minute:\n"]; + for (i = 0; i < my->second; i++) + { + CInfo *info = &cseconds[i]; + NSTimeInterval ti = info->tick + baseTime; - [m appendFormat: @"%u, %g, %g, %g, %@\n", - info->cnt, info->max, info->min, info->sum, - [NSDate dateWithTimeIntervalSinceReferenceDate: ti]]; + [m appendFormat: @"%u, %@\n", info->cnt, + [NSDate dateWithTimeIntervalSinceReferenceDate: ti]]; + } } - } - if (my->period > 0) - { - [m appendString: @"\nPrevious periods:\n"]; - for (i = 0; i < my->period; i++) + if (my->minute > 0) { - DInfo *info = &dperiods[i]; - NSTimeInterval ti = info->tick + baseTime; + [m appendString: @"\nCurrent period:\n"]; + for (i = 0; i < my->minute; i++) + { + CInfo *info = &cminutes[i]; + NSTimeInterval ti = info->tick + baseTime; - [m appendFormat: @"%u, %g, %g, %g, %@\n", - info->cnt, info->max, info->min, info->sum, - [NSDate dateWithTimeIntervalSinceReferenceDate: ti]]; + [m appendFormat: @"%u, %@\n", info->cnt, + [NSDate dateWithTimeIntervalSinceReferenceDate: ti]]; + } } - } - } - else - { - if (my->second > 0) - { - [m appendString: @"\nCurrent minute:\n"]; - for (i = 0; i < my->second; i++) + + if (my->period > 0) { - CInfo *info = &cseconds[i]; - NSTimeInterval ti = info->tick + baseTime; + [m appendString: @"\nPrevious periods:\n"]; + for (i = 0; i < my->period; i++) + { + CInfo *info = &cperiods[i]; + NSTimeInterval ti = info->tick + baseTime; - [m appendFormat: @"%u, %@\n", info->cnt, - [NSDate dateWithTimeIntervalSinceReferenceDate: ti]]; - } - } - - if (my->minute > 0) - { - [m appendString: @"\nCurrent period:\n"]; - for (i = 0; i < my->minute; i++) - { - CInfo *info = &cminutes[i]; - NSTimeInterval ti = info->tick + baseTime; - - [m appendFormat: @"%u, %@\n", info->cnt, - [NSDate dateWithTimeIntervalSinceReferenceDate: ti]]; - } - } - - if (my->period > 0) - { - [m appendString: @"\nPrevious periods:\n"]; - for (i = 0; i < my->period; i++) - { - CInfo *info = &cperiods[i]; - NSTimeInterval ti = info->tick + baseTime; - - [m appendFormat: @"%u, %@\n", info->cnt, - [NSDate dateWithTimeIntervalSinceReferenceDate: ti]]; + [m appendFormat: @"%u, %@\n", info->cnt, + [NSDate dateWithTimeIntervalSinceReferenceDate: ti]]; + } } } } @@ -358,6 +556,7 @@ typedef struct { { NSAssert(my->started > 0.0, NSInternalInconsistencyException); [self addDuration: (*tiImp)(NSDateClass, tiSel) - my->started]; + my->event = nil; my->started = 0.0; } @@ -380,11 +579,19 @@ typedef struct { 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 = GSThroughputTimeTick() - 1; - c = [[NSCalendarDate alloc] initWithTimeIntervalSinceReferenceDate: lastTime]; + my->last = (my->thread->lastTime - my->thread->baseTime) + 1; + c = [[NSCalendarDate alloc] initWithTimeIntervalSinceReferenceDate: + my->thread->lastTime]; my->second = [c secondOfMinute]; i = [c hourOfDay] * 60 + [c minuteOfHour]; my->minute = i % minutesPerPeriod; @@ -444,131 +651,19 @@ typedef struct { ASSIGN(my->name, name); } -- (void) startDuration +- (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; } -- (void) update -{ - unsigned tick = GSThroughputTimeTick(); - 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) - { - 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) - { - my->period = 0; - } - info = &dperiods[my->period]; - info->cnt = 0; - info->max = 0.0; - info->min = MAXDURATION; - info->sum = 0.0; - info->tick = tick; - my->minute = 0; - } - info = &dminutes[my->minute]; - info->cnt = 0; - info->max = 0.0; - info->min = MAXDURATION; - info->sum = 0.0; - info->tick = tick; - my->second = 0; - } - info = &dseconds[my->second]; - info->cnt = 0; - info->max = 0.0; - info->min = MAXDURATION; - info->sum = 0.0; - info->tick = tick; - - 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) - { - info = &cperiods[my->period]; - for (i = 0; i < my->minutesPerPeriod; i++) - { - info->cnt += cminutes[i].cnt; - } - if (my->period++ == my->numberOfPeriods) - { - my->period = 0; - } - info = &cperiods[my->period]; - info->cnt = 0; - info->tick = tick; - my->minute = 0; - } - info = &cminutes[my->minute]; - info->cnt = 0; - info->tick = tick; - my->second = 0; - } - info = &cseconds[my->second]; - info->cnt = 0; - info->tick = tick; - - my->last++; - } - } -} @end