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
23 #include <NGObjWeb/WOStatisticsStore.h>
24 #include <NGObjWeb/WORequest.h>
25 #include <NGObjWeb/WOResponse.h>
26 #include <NGObjWeb/WOContext.h>
27 #include <NGObjWeb/WOComponent.h>
30 @interface _WOPageStats : NSObject
34 unsigned totalResponseCount;
35 unsigned totalResponseSize;
36 unsigned zippedResponsesCount;
37 unsigned totalZippedSize;
38 unsigned largestResponseSize;
39 unsigned smallestResponseSize;
40 NSTimeInterval minimumDuration;
41 NSTimeInterval maximumDuration;
42 NSTimeInterval totalDuration;
46 @implementation WOStatisticsStore
48 static unsigned char *monthAbbr[13] = {
50 "Jan", "Feb", "Mar", "Apr", "May", "Jun",
51 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
53 static NSTimeZone *gmt = nil;
54 static Class NSNumberClass = Nil;
55 static Class NSStringClass = Nil;
56 static BOOL runMultithreaded = NO;
63 NSNumberClass = [NSNumber class];
64 NSStringClass = [NSString class];
67 gmt = [[NSTimeZone timeZoneWithAbbreviation:@"GMT"] retain];
69 runMultithreaded = [[NSUserDefaults standardUserDefaults]
70 boolForKey:@"WORunMultithreaded"];
74 if ((self = [super init])) {
75 self->startTime = [[NSDate date] copy];
76 self->smallestResponseSize = -1;
77 self->totalDuration = 0.0;
80 self->lock = [[NSRecursiveLock alloc] init];
86 [self->pageStatistics release];
87 [self->startTime release];
94 static id mkuint(unsigned int i) {
95 return [NSNumberClass numberWithUnsignedInt:i];
97 static id mkdbl(double d) {
99 unsigned char buf[64];
100 sprintf(buf, "%.3f", d);
101 return [NSStringClass stringWithCString:buf];
103 return [NSNumberClass numberWithDouble:d];
107 - (NSDictionary *)statisticsForPageNamed:(NSString *)_pageName {
108 // TODO: fix inefficient use of NSString
109 NSMutableDictionary *stats;
110 _WOPageStats *pageStats;
112 if ((pageStats = [self->pageStatistics objectForKey:_pageName]) == nil)
115 stats = [NSMutableDictionary dictionaryWithCapacity:16];
117 [stats setObject:mkuint(pageStats->totalResponseSize)
118 forKey:@"totalResponseSize"];
119 [stats setObject:mkuint(pageStats->totalResponseCount)
120 forKey:@"totalResponseCount"];
121 [stats setObject:mkdbl(pageStats->totalDuration)
122 forKey:@"totalDuration"];
124 if (pageStats->smallestResponseSize >= 0) {
125 [stats setObject:mkuint(pageStats->largestResponseSize)
126 forKey:@"largestResponseSize"];
127 [stats setObject:mkuint(pageStats->smallestResponseSize)
128 forKey:@"smallestResponseSize"];
129 [stats setObject:mkdbl(pageStats->minimumDuration)
130 forKey:@"minimumDuration"];
131 [stats setObject:mkdbl(pageStats->maximumDuration)
132 forKey:@"maximumDuration"];
135 if (pageStats->totalResponseCount > 0) {
137 mkuint(pageStats->totalResponseSize / pageStats->totalResponseCount)
138 forKey:@"averageResponseSize"];
140 mkdbl(pageStats->totalDuration / pageStats->totalResponseCount)
141 forKey:@"averageDuration"];
144 [stats setObject:mkuint(pageStats->zippedResponsesCount)
145 forKey:@"numberOfZippedResponses"];
146 [stats setObject:mkuint(pageStats->totalZippedSize)
147 forKey:@"totalZippedSize"];
149 /* calc frequencies */
153 d = ((double)self->totalResponseCount) / 100.0; // one percent
154 d = ((double)pageStats->totalResponseCount) / d; // percents of total
155 [stats setObject:[NSStringClass stringWithFormat:@"%.3f%%", d]
156 forKey:@"responseFrequency"];
158 d = ((double)self->totalDuration) / 100.0; // one percent
159 d = ((double)pageStats->totalDuration) / d; // percents of total
160 [stats setObject:[NSStringClass stringWithFormat:@"%.3f%%", d]
161 forKey:@"relativeTimeConsumption"];
163 d = ((double)self->pageResponseCount) / 100.0; // one percent
164 d = ((double)pageStats->totalResponseCount) / d; // percents of total
165 [stats setObject:[NSStringClass stringWithFormat:@"%.3f%%", d]
166 forKey:@"pageFrequency"];
169 d = ((double)self->totalResponseSize) / 100.0; // one percent
170 d = ((double)pageStats->totalResponseSize) / d; // percents of total
171 [stats setObject:[NSStringClass stringWithFormat:@"%.3f%%", d]
172 forKey:@"pageDeliveryVolume"];
178 - (NSDictionary *)statistics {
179 NSMutableDictionary *stats;
183 stats = [NSMutableDictionary dictionaryWithCapacity:64];
185 uptime = [now timeIntervalSinceDate:self->startTime];
187 [stats setObject:mkuint(self->totalResponseSize)
188 forKey:@"totalResponseSize"];
189 [stats setObject:mkuint(self->totalResponseCount)
190 forKey:@"totalResponseCount"];
191 [stats setObject:mkdbl(self->totalDuration)
192 forKey:@"totalDuration"];
193 [stats setObject:mkuint(self->pageResponseCount)
194 forKey:@"pageResponseCount"];
195 if (self->smallestResponseSize >= 0) {
196 [stats setObject:mkuint(self->largestResponseSize)
197 forKey:@"largestResponseSize"];
198 [stats setObject:mkuint(self->smallestResponseSize)
199 forKey:@"smallestResponseSize"];
200 [stats setObject:mkdbl(self->minimumDuration)
201 forKey:@"minimumDuration"];
202 [stats setObject:mkdbl(self->maximumDuration)
203 forKey:@"maximumDuration"];
206 if ((self->totalDuration > 0) && (uptime > 0)) {
208 [NSStringClass stringWithFormat:@"%.3f%%",
209 self->totalDuration / (uptime / 100.0)]
210 forKey:@"instanceLoad"];
213 if (self->totalResponseCount > 0) {
214 [stats setObject:mkuint(self->totalResponseSize / self->totalResponseCount)
215 forKey:@"averageResponseSize"];
216 [stats setObject:mkdbl(self->totalDuration / self->totalResponseCount)
217 forKey:@"averageDuration"];
220 [stats setObject:self->startTime forKey:@"instanceStartDate"];
221 [stats setObject:now forKey:@"statisticsDate"];
222 [stats setObject:[NSStringClass stringWithFormat:@"%.3f", uptime]
223 forKey:@"instanceUptime"];
224 [stats setObject:[NSStringClass stringWithFormat:@"%.3f", uptime / 3600.0]
225 forKey:@"instanceUptimeInHours"];
227 [stats setObject:mkuint(self->zippedResponsesCount)
228 forKey:@"numberOfZippedResponses"];
229 [stats setObject:mkuint(self->totalZippedSize)
230 forKey:@"totalZippedSize"];
232 /* page statistics */
234 NSEnumerator *pageNames;
236 NSMutableDictionary *pageStats;
238 pageStats = [NSMutableDictionary dictionaryWithCapacity:16];
239 pageNames = [self->pageStatistics keyEnumerator];
240 while ((pageName = [pageNames nextObject])) {
243 s = [self statisticsForPageNamed:pageName];
247 [pageStats setObject:s forKey:pageName];
251 [stats setObject:pageStats forKey:@"pageStatistics"];
259 - (void)recordStatisticsForResponse:(WOResponse *)_response
260 inContext:(WOContext *)_context
264 NSNumber *zippedSize;
265 NSDate *requestStartDate;
267 NSTimeInterval duration;
269 zippedSize = [[_response userInfo] objectForKey:@"WOResponseZippedLength"];
271 size = [[[_response userInfo] objectForKey:@"WOResponseUnzippedLength"]
275 size = [[_response content] length];
278 [[[_context request] userInfo] objectForKey:@"WORequestStartDate"];
281 duration = (requestStartDate)
282 ? [now timeIntervalSinceDate:requestStartDate]
285 self->totalResponseCount++;
286 self->totalResponseSize += size;
287 self->totalDuration += duration;
289 if (self->smallestResponseSize == -1) {
291 self->largestResponseSize = size;
292 self->smallestResponseSize = size;
293 self->maximumDuration = duration;
294 self->minimumDuration = duration;
297 if (size > self->largestResponseSize) self->largestResponseSize = size;
298 if (size < self->smallestResponseSize) self->smallestResponseSize = size;
299 if (duration > self->maximumDuration) self->maximumDuration = duration;
300 if (duration < self->minimumDuration) self->minimumDuration = duration;
304 self->zippedResponsesCount++;
305 self->totalZippedSize += [zippedSize unsignedIntValue];
308 if ((page = [_context page])) {
309 _WOPageStats *pageStats;
311 self->pageResponseCount++;
313 if (self->pageStatistics == nil)
314 self->pageStatistics = [[NSMutableDictionary alloc] initWithCapacity:64];
316 if ((pageStats = [self->pageStatistics objectForKey:[page name]]) == nil) {
317 pageStats = [[[_WOPageStats alloc] init] autorelease];
318 pageStats->pageName = [[page name] copy];
320 pageStats->largestResponseSize = size;
321 pageStats->smallestResponseSize = size;
322 pageStats->maximumDuration = duration;
323 pageStats->minimumDuration = duration;
325 [self->pageStatistics setObject:pageStats forKey:pageStats->pageName];
328 if (size > pageStats->largestResponseSize)
329 pageStats->largestResponseSize = size;
330 if (size < pageStats->smallestResponseSize)
331 pageStats->smallestResponseSize = size;
332 if (duration > pageStats->maximumDuration)
333 pageStats->maximumDuration = duration;
334 if (duration < pageStats->minimumDuration)
335 pageStats->minimumDuration = duration;
338 pageStats->totalResponseCount++;
339 pageStats->totalResponseSize += size;
340 pageStats->totalDuration += duration;
343 pageStats->zippedResponsesCount++;
344 pageStats->totalZippedSize += [zippedSize unsignedIntValue];
349 - (NSString *)descriptionForResponse:(WOResponse *)_response
350 inContext:(WOContext *)_context
355 if ((page = [_context page]) == nil) {
356 return [NSStringClass stringWithFormat:
357 @"<no page generated for context %@>",
362 [page respondsToSelector:@selector(descriptionForResponse:inContext:)]
363 ? [page descriptionForResponse:_response inContext:_context]
370 - (NSString *)formatDescription:(NSString *)_description
371 forResponse:(WOResponse *)_response
372 inContext:(WOContext *)_context
374 NSMutableString *result;
376 NSString *remoteHost = @"-";
380 unsigned char buf[64];
382 request = [_context request];
383 result = [NSMutableString stringWithCapacity:256];
385 /* remote host and date */
387 if ((remoteHost = [request headerForKey:@"x-webobjects-remote-host"]))
389 else if ((remoteHost = [request headerForKey:@"x-webobjects-remote-addr"]))
394 now = [NSCalendarDate date];
395 [now setTimeZone:gmt];
397 [result appendString:remoteHost];
399 " - - [%02i/%s/%04i:%02i:%02i:%02i GMT] ",
400 [now dayOfMonth], monthAbbr[[now monthOfYear]],
401 [now yearOfCommonEra],
402 [now hourOfDay], [now minuteOfHour], [now secondOfMinute]);
403 tmp = [[NSStringClass alloc] initWithCString:buf];
404 [result appendString:tmp];
409 [result appendString:@"\""];
410 [result appendString:[request method]];
411 [result appendString:@" "];
412 [result appendString:[request uri]];
413 [result appendString:@" "];
414 [result appendString:[request httpVersion]];
415 [result appendString:@"\" "];
419 [result appendFormat:@"%i %i",
420 [_response status], [[_response content] length]];
422 startDate = [[request userInfo] objectForKey:@"WORequestStartDate"];
424 NSTimeInterval duration;
426 duration = [now timeIntervalSinceDate:startDate];
427 sprintf(buf, " %.3f", duration);
428 tmp = [[NSStringClass alloc] initWithCString:buf];
429 [result appendString:tmp];
445 @end /* WOStatisticsStore */
447 @implementation _WOPageStats
450 self->totalDuration = 0.0;
451 self->minimumDuration = 0.0;
452 self->maximumDuration = 0.0;
457 RELEASE(self->pageName);
461 @end /* _WOPageStats */