]> err.no Git - sope/blob - sope-appserver/NGObjWeb/WOStatisticsStore.m
renamed packages as discussed in the developer list
[sope] / sope-appserver / NGObjWeb / WOStatisticsStore.m
1 /*
2   Copyright (C) 2000-2004 SKYRIX Software AG
3
4   This file is part of OpenGroupware.org.
5
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
9   later version.
10
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.
15
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
19   02111-1307, USA.
20 */
21 // $Id$
22
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>
28 #include "common.h"
29
30 @interface _WOPageStats : NSObject
31 {
32 @public
33   NSString       *pageName;
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;
43 }
44 @end
45
46 @implementation WOStatisticsStore
47
48 static unsigned char *monthAbbr[13] = {
49   "Dec",
50   "Jan", "Feb", "Mar", "Apr", "May", "Jun",
51   "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
52 };
53 static NSTimeZone *gmt = nil;
54 static Class NSNumberClass    = Nil;
55 static Class NSStringClass    = Nil;
56 static BOOL  runMultithreaded = NO;
57
58 + (int)version {
59   return 1;
60 }
61
62 + (void)initialize {
63   NSNumberClass = [NSNumber class];
64   NSStringClass = [NSString class];
65
66   if (gmt == nil)
67     gmt = [[NSTimeZone timeZoneWithAbbreviation:@"GMT"] retain];
68
69   runMultithreaded = [[NSUserDefaults standardUserDefaults]
70                        boolForKey:@"WORunMultithreaded"];
71 }
72
73 - (id)init {
74   if ((self = [super init])) {
75     self->startTime = [[NSDate date] copy];
76     self->smallestResponseSize = -1;
77     self->totalDuration = 0.0;
78     
79     if (runMultithreaded)
80       self->lock = [[NSRecursiveLock alloc] init];
81   }
82   return self;
83 }
84
85 - (void)dealloc {
86   [self->pageStatistics release];
87   [self->startTime      release];
88   [self->lock           release];
89   [super dealloc];
90 }
91
92 /* query */
93
94 static id mkuint(unsigned int i) {
95   return [NSNumberClass numberWithUnsignedInt:i];
96 }
97 static id mkdbl(double d) {
98 #if 1
99   unsigned char buf[64];
100   sprintf(buf, "%.3f", d);
101   return [NSStringClass stringWithCString:buf];
102 #else
103   return [NSNumberClass numberWithDouble:d];
104 #endif
105 }
106
107 - (NSDictionary *)statisticsForPageNamed:(NSString *)_pageName {
108   // TODO: fix inefficient use of NSString
109   NSMutableDictionary *stats;
110   _WOPageStats        *pageStats;
111
112   if ((pageStats = [self->pageStatistics objectForKey:_pageName]) == nil)
113     return nil;
114
115   stats = [NSMutableDictionary dictionaryWithCapacity:16];
116
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"];
123
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"];
133   }
134   
135   if (pageStats->totalResponseCount > 0) {
136     [stats setObject:
137              mkuint(pageStats->totalResponseSize / pageStats->totalResponseCount)
138            forKey:@"averageResponseSize"];
139     [stats setObject:
140              mkdbl(pageStats->totalDuration / pageStats->totalResponseCount)
141            forKey:@"averageDuration"];
142   }
143   
144   [stats setObject:mkuint(pageStats->zippedResponsesCount)
145          forKey:@"numberOfZippedResponses"];
146   [stats setObject:mkuint(pageStats->totalZippedSize)
147          forKey:@"totalZippedSize"];
148   
149   /* calc frequencies */
150   {
151     double d;
152     
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"];
157     
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"];
162     
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"];
167
168     
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"];
173   }
174   
175   return stats;
176 }
177
178 - (NSDictionary *)statistics {
179   NSMutableDictionary *stats;
180   NSDate   *now;
181   double   uptime;
182   
183   stats  = [NSMutableDictionary dictionaryWithCapacity:64];
184   now    = [NSDate date];
185   uptime = [now timeIntervalSinceDate:self->startTime];
186   
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"];
204   }
205
206   if ((self->totalDuration > 0) && (uptime > 0)) {
207     [stats setObject:
208              [NSStringClass stringWithFormat:@"%.3f%%",
209                          self->totalDuration / (uptime / 100.0)]
210            forKey:@"instanceLoad"];
211   }
212   
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"];
218   }
219   
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"];
226
227   [stats setObject:mkuint(self->zippedResponsesCount)
228          forKey:@"numberOfZippedResponses"];
229   [stats setObject:mkuint(self->totalZippedSize)
230          forKey:@"totalZippedSize"];
231   
232   /* page statistics */
233   {
234     NSEnumerator        *pageNames;
235     NSString            *pageName;
236     NSMutableDictionary *pageStats;
237
238     pageStats = [NSMutableDictionary dictionaryWithCapacity:16];
239     pageNames = [self->pageStatistics keyEnumerator];
240     while ((pageName = [pageNames nextObject])) {
241       NSDictionary *s;
242
243       s = [self statisticsForPageNamed:pageName];
244       if (s == nil)
245         continue;
246       
247       [pageStats setObject:s forKey:pageName];
248     }
249     
250     if (pageStats)
251       [stats setObject:pageStats forKey:@"pageStatistics"];
252   }
253   
254   return stats;
255 }
256
257 /* recording */
258
259 - (void)recordStatisticsForResponse:(WOResponse *)_response
260   inContext:(WOContext *)_context
261 {
262   WOComponent    *page;
263   unsigned       size;
264   NSNumber       *zippedSize;
265   NSDate         *requestStartDate;
266   NSDate         *now;
267   NSTimeInterval duration;
268   
269   zippedSize = [[_response userInfo] objectForKey:@"WOResponseZippedLength"];
270   if (zippedSize) {
271     size = [[[_response userInfo] objectForKey:@"WOResponseUnzippedLength"]
272                                   unsignedIntValue];
273   }
274   else
275     size = [[_response content] length];
276   
277   requestStartDate =
278     [[[_context request] userInfo] objectForKey:@"WORequestStartDate"];
279   now = [NSDate date];
280   
281   duration = (requestStartDate)
282     ? [now timeIntervalSinceDate:requestStartDate]
283     : 0.0;
284   
285   self->totalResponseCount++;
286   self->totalResponseSize += size;
287   self->totalDuration     += duration;
288   
289   if (self->smallestResponseSize == -1) {
290     /* first request */
291     self->largestResponseSize  = size;
292     self->smallestResponseSize = size;
293     self->maximumDuration      = duration;
294     self->minimumDuration      = duration;
295   }
296   else {
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;
301   }
302   
303   if (zippedSize) {
304     self->zippedResponsesCount++;
305     self->totalZippedSize += [zippedSize unsignedIntValue];
306   }
307   
308   if ((page = [_context page])) {
309     _WOPageStats *pageStats;
310     
311     self->pageResponseCount++;
312     
313     if (self->pageStatistics == nil)
314       self->pageStatistics = [[NSMutableDictionary alloc] initWithCapacity:64];
315     
316     if ((pageStats = [self->pageStatistics objectForKey:[page name]]) == nil) {
317       pageStats = [[[_WOPageStats alloc] init] autorelease];
318       pageStats->pageName = [[page name] copy];
319
320       pageStats->largestResponseSize  = size;
321       pageStats->smallestResponseSize = size;
322       pageStats->maximumDuration      = duration;
323       pageStats->minimumDuration      = duration;
324       
325       [self->pageStatistics setObject:pageStats forKey:pageStats->pageName];
326     }
327     else {
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;
336     }
337     
338     pageStats->totalResponseCount++;
339     pageStats->totalResponseSize += size;
340     pageStats->totalDuration     += duration;
341     
342     if (zippedSize) {
343       pageStats->zippedResponsesCount++;
344       pageStats->totalZippedSize += [zippedSize unsignedIntValue];
345     }
346   }
347 }
348
349 - (NSString *)descriptionForResponse:(WOResponse *)_response
350   inContext:(WOContext *)_context
351 {
352   NSString    *result;
353   WOComponent *page;
354
355   if ((page = [_context page]) == nil) {
356     return [NSStringClass stringWithFormat:
357                             @"<no page generated for context %@>",
358                             _context];
359   }
360   
361   result =
362     [page respondsToSelector:@selector(descriptionForResponse:inContext:)]
363     ? [page descriptionForResponse:_response inContext:_context]
364     : [page name];
365   return result;
366 }
367
368 /* formatting */
369
370 - (NSString *)formatDescription:(NSString *)_description
371   forResponse:(WOResponse *)_response
372   inContext:(WOContext *)_context
373 {
374   NSMutableString *result;
375   WORequest       *request;
376   NSString        *remoteHost = @"-";
377   NSCalendarDate  *now;
378   NSDate          *startDate;
379   NSString        *tmp;
380   unsigned char   buf[64];
381   
382   request = [_context request];
383   result  = [NSMutableString stringWithCapacity:256];
384   
385   /* remote host and date */
386
387   if ((remoteHost = [request headerForKey:@"x-webobjects-remote-host"]))
388     ;
389   else if ((remoteHost = [request headerForKey:@"x-webobjects-remote-addr"]))
390     ;
391   else
392     remoteHost = @"-";
393   
394   now = [NSCalendarDate date];
395   [now setTimeZone:gmt];
396   
397   [result appendString:remoteHost];
398   sprintf(buf, 
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];
405   [tmp release];
406   
407   /* request */
408   
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:@"\" "];
416
417   /* response */
418   
419   [result appendFormat:@"%i %i",
420             [_response status], [[_response content] length]];
421   
422   startDate = [[request userInfo] objectForKey:@"WORequestStartDate"];
423   if (startDate) {
424     NSTimeInterval duration;
425     
426     duration = [now timeIntervalSinceDate:startDate];
427     sprintf(buf, " %.3f", duration);
428     tmp = [[NSStringClass alloc] initWithCString:buf];
429     [result appendString:tmp];
430     [tmp release];
431   }
432   
433   return result;
434 }
435
436 /* NSLocking */
437
438 - (void)lock {
439   [self->lock lock];
440 }
441 - (void)unlock {
442   [self->lock unlock];
443 }
444
445 @end /* WOStatisticsStore */
446
447 @implementation _WOPageStats
448
449 - (id)init {
450   self->totalDuration   = 0.0;
451   self->minimumDuration = 0.0;
452   self->maximumDuration = 0.0;
453   return self;
454 }
455
456 - (void)dealloc {
457   RELEASE(self->pageName);
458   [super dealloc];
459 }
460
461 @end /* _WOPageStats */