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