2 Copyright (C) 2000-2004 SKYRIX Software AG
4 This file is part of OpenGroupware.org.
6 OGo is free software; you can redistribute it and/or modify it under
7 the terms of the GNU Lesser General Public License as published by the
8 Free Software Foundation; either version 2, or (at your option) any
11 OGo is distributed in the hope that it will be useful, but WITHOUT ANY
12 WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
14 License for more details.
16 You should have received a copy of the GNU Lesser General Public
17 License along with OGo; see the file COPYING. If not, write to the
18 Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
22 #include <NGObjWeb/WOStatisticsStore.h>
23 #include <NGObjWeb/WORequest.h>
24 #include <NGObjWeb/WOResponse.h>
25 #include <NGObjWeb/WOContext.h>
26 #include <NGObjWeb/WOComponent.h>
29 @interface _WOPageStats : NSObject
33 unsigned totalResponseCount;
34 unsigned totalResponseSize;
35 unsigned zippedResponsesCount;
36 unsigned totalZippedSize;
37 unsigned largestResponseSize;
38 unsigned smallestResponseSize;
39 NSTimeInterval minimumDuration;
40 NSTimeInterval maximumDuration;
41 NSTimeInterval totalDuration;
45 @interface WORequest(UsedPrivates)
46 - (NSCalendarDate *)startDate;
47 - (id)startStatistics;
50 @implementation WOStatisticsStore
52 static unsigned char *monthAbbr[13] = {
54 "Jan", "Feb", "Mar", "Apr", "May", "Jun",
55 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
57 static NSTimeZone *gmt = nil;
58 static Class NSNumberClass = Nil;
59 static Class NSStringClass = Nil;
60 static BOOL runMultithreaded = NO;
67 NSNumberClass = [NSNumber class];
68 NSStringClass = [NSString class];
71 gmt = [[NSTimeZone timeZoneWithAbbreviation:@"GMT"] retain];
73 runMultithreaded = [[NSUserDefaults standardUserDefaults]
74 boolForKey:@"WORunMultithreaded"];
78 if ((self = [super init])) {
79 self->startTime = [[NSDate date] copy];
80 self->smallestResponseSize = -1;
81 self->totalDuration = 0.0;
84 self->lock = [[NSRecursiveLock alloc] init];
90 [self->pageStatistics release];
91 [self->startTime release];
98 static id mkuint(unsigned int i) {
99 return [NSNumberClass numberWithUnsignedInt:i];
101 static id mkdbl(double d) {
103 unsigned char buf[64];
104 sprintf(buf, "%.3f", d);
105 return [NSStringClass stringWithCString:buf];
107 return [NSNumberClass numberWithDouble:d];
111 - (NSDictionary *)statisticsForPageNamed:(NSString *)_pageName {
112 // TODO: fix inefficient use of NSString
113 NSMutableDictionary *stats;
114 _WOPageStats *pageStats;
116 if ((pageStats = [self->pageStatistics objectForKey:_pageName]) == nil)
119 stats = [NSMutableDictionary dictionaryWithCapacity:16];
121 [stats setObject:mkuint(pageStats->totalResponseSize)
122 forKey:@"totalResponseSize"];
123 [stats setObject:mkuint(pageStats->totalResponseCount)
124 forKey:@"totalResponseCount"];
125 [stats setObject:mkdbl(pageStats->totalDuration)
126 forKey:@"totalDuration"];
128 if (pageStats->smallestResponseSize >= 0) {
129 [stats setObject:mkuint(pageStats->largestResponseSize)
130 forKey:@"largestResponseSize"];
131 [stats setObject:mkuint(pageStats->smallestResponseSize)
132 forKey:@"smallestResponseSize"];
133 [stats setObject:mkdbl(pageStats->minimumDuration)
134 forKey:@"minimumDuration"];
135 [stats setObject:mkdbl(pageStats->maximumDuration)
136 forKey:@"maximumDuration"];
139 if (pageStats->totalResponseCount > 0) {
141 mkuint(pageStats->totalResponseSize / pageStats->totalResponseCount)
142 forKey:@"averageResponseSize"];
144 mkdbl(pageStats->totalDuration / pageStats->totalResponseCount)
145 forKey:@"averageDuration"];
148 [stats setObject:mkuint(pageStats->zippedResponsesCount)
149 forKey:@"numberOfZippedResponses"];
150 [stats setObject:mkuint(pageStats->totalZippedSize)
151 forKey:@"totalZippedSize"];
153 /* calc frequencies */
157 d = ((double)self->totalResponseCount) / 100.0; // one percent
158 d = ((double)pageStats->totalResponseCount) / d; // percents of total
159 [stats setObject:[NSStringClass stringWithFormat:@"%.3f%%", d]
160 forKey:@"responseFrequency"];
162 d = ((double)self->totalDuration) / 100.0; // one percent
163 d = ((double)pageStats->totalDuration) / d; // percents of total
164 [stats setObject:[NSStringClass stringWithFormat:@"%.3f%%", d]
165 forKey:@"relativeTimeConsumption"];
167 d = ((double)self->pageResponseCount) / 100.0; // one percent
168 d = ((double)pageStats->totalResponseCount) / d; // percents of total
169 [stats setObject:[NSStringClass stringWithFormat:@"%.3f%%", d]
170 forKey:@"pageFrequency"];
173 d = ((double)self->totalResponseSize) / 100.0; // one percent
174 d = ((double)pageStats->totalResponseSize) / d; // percents of total
175 [stats setObject:[NSStringClass stringWithFormat:@"%.3f%%", d]
176 forKey:@"pageDeliveryVolume"];
182 - (NSDictionary *)statistics {
183 NSMutableDictionary *stats;
187 stats = [NSMutableDictionary dictionaryWithCapacity:64];
189 uptime = [now timeIntervalSinceDate:self->startTime];
191 [stats setObject:mkuint(self->totalResponseSize)
192 forKey:@"totalResponseSize"];
193 [stats setObject:mkuint(self->totalResponseCount)
194 forKey:@"totalResponseCount"];
195 [stats setObject:mkdbl(self->totalDuration)
196 forKey:@"totalDuration"];
197 [stats setObject:mkuint(self->pageResponseCount)
198 forKey:@"pageResponseCount"];
199 if (self->smallestResponseSize >= 0) {
200 [stats setObject:mkuint(self->largestResponseSize)
201 forKey:@"largestResponseSize"];
202 [stats setObject:mkuint(self->smallestResponseSize)
203 forKey:@"smallestResponseSize"];
204 [stats setObject:mkdbl(self->minimumDuration)
205 forKey:@"minimumDuration"];
206 [stats setObject:mkdbl(self->maximumDuration)
207 forKey:@"maximumDuration"];
210 if ((self->totalDuration > 0) && (uptime > 0)) {
212 [NSStringClass stringWithFormat:@"%.3f%%",
213 self->totalDuration / (uptime / 100.0)]
214 forKey:@"instanceLoad"];
217 if (self->totalResponseCount > 0) {
218 [stats setObject:mkuint(self->totalResponseSize / self->totalResponseCount)
219 forKey:@"averageResponseSize"];
220 [stats setObject:mkdbl(self->totalDuration / self->totalResponseCount)
221 forKey:@"averageDuration"];
224 [stats setObject:self->startTime forKey:@"instanceStartDate"];
225 [stats setObject:now forKey:@"statisticsDate"];
226 [stats setObject:[NSStringClass stringWithFormat:@"%.3f", uptime]
227 forKey:@"instanceUptime"];
228 [stats setObject:[NSStringClass stringWithFormat:@"%.3f", uptime / 3600.0]
229 forKey:@"instanceUptimeInHours"];
231 [stats setObject:mkuint(self->zippedResponsesCount)
232 forKey:@"numberOfZippedResponses"];
233 [stats setObject:mkuint(self->totalZippedSize)
234 forKey:@"totalZippedSize"];
236 /* page statistics */
238 NSEnumerator *pageNames;
240 NSMutableDictionary *pageStats;
242 pageStats = [NSMutableDictionary dictionaryWithCapacity:16];
243 pageNames = [self->pageStatistics keyEnumerator];
244 while ((pageName = [pageNames nextObject])) {
247 s = [self statisticsForPageNamed:pageName];
251 [pageStats setObject:s forKey:pageName];
255 [stats setObject:pageStats forKey:@"pageStatistics"];
263 - (void)recordStatisticsForResponse:(WOResponse *)_response
264 inContext:(WOContext *)_context
268 NSNumber *zippedSize;
269 NSDate *requestStartDate;
271 NSTimeInterval duration;
273 zippedSize = [[_response userInfo] objectForKey:@"WOResponseZippedLength"];
275 size = [[[_response userInfo] objectForKey:@"WOResponseUnzippedLength"]
279 size = [[_response content] length];
281 requestStartDate = [[_context request] startDate];
284 duration = (requestStartDate)
285 ? [now timeIntervalSinceDate:requestStartDate]
288 self->totalResponseCount++;
289 self->totalResponseSize += size;
290 self->totalDuration += duration;
292 if (self->smallestResponseSize == -1) {
294 self->largestResponseSize = size;
295 self->smallestResponseSize = size;
296 self->maximumDuration = duration;
297 self->minimumDuration = duration;
300 if (size > self->largestResponseSize) self->largestResponseSize = size;
301 if (size < self->smallestResponseSize) self->smallestResponseSize = size;
302 if (duration > self->maximumDuration) self->maximumDuration = duration;
303 if (duration < self->minimumDuration) self->minimumDuration = duration;
307 self->zippedResponsesCount++;
308 self->totalZippedSize += [zippedSize unsignedIntValue];
311 if ((page = [_context page])) {
312 _WOPageStats *pageStats;
314 self->pageResponseCount++;
316 if (self->pageStatistics == nil)
317 self->pageStatistics = [[NSMutableDictionary alloc] initWithCapacity:64];
319 if ((pageStats = [self->pageStatistics objectForKey:[page name]]) == nil) {
320 pageStats = [[[_WOPageStats alloc] init] autorelease];
321 pageStats->pageName = [[page name] copy];
323 pageStats->largestResponseSize = size;
324 pageStats->smallestResponseSize = size;
325 pageStats->maximumDuration = duration;
326 pageStats->minimumDuration = duration;
328 [self->pageStatistics setObject:pageStats forKey:pageStats->pageName];
331 if (size > pageStats->largestResponseSize)
332 pageStats->largestResponseSize = size;
333 if (size < pageStats->smallestResponseSize)
334 pageStats->smallestResponseSize = size;
335 if (duration > pageStats->maximumDuration)
336 pageStats->maximumDuration = duration;
337 if (duration < pageStats->minimumDuration)
338 pageStats->minimumDuration = duration;
341 pageStats->totalResponseCount++;
342 pageStats->totalResponseSize += size;
343 pageStats->totalDuration += duration;
346 pageStats->zippedResponsesCount++;
347 pageStats->totalZippedSize += [zippedSize unsignedIntValue];
352 - (NSString *)descriptionForResponse:(WOResponse *)_response
353 inContext:(WOContext *)_context
358 if ((page = [_context page]) == nil) {
359 return [NSStringClass stringWithFormat:
360 @"<no page generated for context %@>",
365 [page respondsToSelector:@selector(descriptionForResponse:inContext:)]
366 ? [page descriptionForResponse:_response inContext:_context]
373 - (NSString *)formatDescription:(NSString *)_description
374 forResponse:(WOResponse *)_response
375 inContext:(WOContext *)_context
377 NSMutableString *result;
379 NSString *remoteHost = @"-";
383 unsigned char buf[64];
385 request = [_context request];
386 result = [NSMutableString stringWithCapacity:256];
388 /* remote host and date */
390 if ((remoteHost = [request headerForKey:@"x-webobjects-remote-host"]))
392 else if ((remoteHost = [request headerForKey:@"x-webobjects-remote-addr"]))
397 now = [NSCalendarDate date];
398 [now setTimeZone:gmt];
400 [result appendString:remoteHost];
402 " - - [%02i/%s/%04i:%02i:%02i:%02i GMT] ",
403 [now dayOfMonth], monthAbbr[[now monthOfYear]],
404 [now yearOfCommonEra],
405 [now hourOfDay], [now minuteOfHour], [now secondOfMinute]);
406 tmp = [[NSStringClass alloc] initWithCString:buf];
407 [result appendString:tmp];
412 [result appendString:@"\""];
413 [result appendString:[request method]];
414 [result appendString:@" "];
415 [result appendString:[request uri]];
416 [result appendString:@" "];
417 [result appendString:[request httpVersion]];
418 [result appendString:@"\" "];
422 [result appendFormat:@"%i %i",
423 [_response status], [[_response content] length]];
425 if ((startDate = [request startDate]) != nil) {
426 NSTimeInterval duration;
428 duration = [now timeIntervalSinceDate:startDate];
429 sprintf(buf, " %.3f", duration);
430 tmp = [[NSStringClass alloc] initWithCString:buf];
431 [result appendString:tmp];
447 @end /* WOStatisticsStore */
449 @implementation _WOPageStats
452 self->totalDuration = 0.0;
453 self->minimumDuration = 0.0;
454 self->maximumDuration = 0.0;
459 RELEASE(self->pageName);
463 @end /* _WOPageStats */