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 NSInteger 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 52static char *monthAbbr[13] = { 53 "Dec", 54 "Jan", "Feb", "Mar", "Apr", "May", "Jun", 55 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" 56}; 57static NSTimeZone *gmt = nil; 58static Class NSNumberClass = Nil; 59static Class NSStringClass = Nil; 60static BOOL runMultithreaded = NO; 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 94static id mkuint(unsigned int i) { 95 return [NSNumberClass numberWithUnsignedInt:i]; 96} 97static id mkdbl(double d) { 98#if 1 // TODO: why is that? 99 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 = [[_context request] startDate]; 278 now = [NSDate date]; 279 280 duration = (requestStartDate) 281 ? [now timeIntervalSinceDate:requestStartDate] 282 : 0.0; 283 284 self->totalResponseCount++; 285 self->totalResponseSize += size; 286 self->totalDuration += duration; 287 288 if (self->smallestResponseSize == -1) { 289 /* first request */ 290 self->largestResponseSize = size; 291 self->smallestResponseSize = size; 292 self->maximumDuration = duration; 293 self->minimumDuration = duration; 294 } 295 else { 296 if (size > self->largestResponseSize) self->largestResponseSize = size; 297 if (size < self->smallestResponseSize) self->smallestResponseSize = size; 298 if (duration > self->maximumDuration) self->maximumDuration = duration; 299 if (duration < self->minimumDuration) self->minimumDuration = duration; 300 } 301 302 if (zippedSize) { 303 self->zippedResponsesCount++; 304 self->totalZippedSize += [zippedSize unsignedIntValue]; 305 } 306 307 if ((page = [_context page])) { 308 _WOPageStats *pageStats; 309 310 self->pageResponseCount++; 311 312 if (self->pageStatistics == nil) 313 self->pageStatistics = [[NSMutableDictionary alloc] initWithCapacity:64]; 314 315 if ((pageStats = [self->pageStatistics objectForKey:[page name]]) == nil) { 316 pageStats = [[[_WOPageStats alloc] init] autorelease]; 317 pageStats->pageName = [[page name] copy]; 318 319 pageStats->largestResponseSize = size; 320 pageStats->smallestResponseSize = size; 321 pageStats->maximumDuration = duration; 322 pageStats->minimumDuration = duration; 323 324 [self->pageStatistics setObject:pageStats forKey:pageStats->pageName]; 325 } 326 else { 327 if (size > pageStats->largestResponseSize) 328 pageStats->largestResponseSize = size; 329 if (size < pageStats->smallestResponseSize) 330 pageStats->smallestResponseSize = size; 331 if (duration > pageStats->maximumDuration) 332 pageStats->maximumDuration = duration; 333 if (duration < pageStats->minimumDuration) 334 pageStats->minimumDuration = duration; 335 } 336 337 pageStats->totalResponseCount++; 338 pageStats->totalResponseSize += size; 339 pageStats->totalDuration += duration; 340 341 if (zippedSize) { 342 pageStats->zippedResponsesCount++; 343 pageStats->totalZippedSize += [zippedSize unsignedIntValue]; 344 } 345 } 346} 347 348- (NSString *)descriptionForResponse:(WOResponse *)_response 349 inContext:(WOContext *)_context 350{ 351 NSString *result; 352 WOComponent *page; 353 354 if ((page = [_context page]) == nil) { 355 return [NSStringClass stringWithFormat: 356 @"<no page generated for context %@>", 357 _context]; 358 } 359 360 result = 361 [page respondsToSelector:@selector(descriptionForResponse:inContext:)] 362 ? [page descriptionForResponse:_response inContext:_context] 363 : [page name]; 364 return result; 365} 366 367/* formatting */ 368 369- (NSString *)formatDescription:(NSString *)_description 370 forResponse:(WOResponse *)_response 371 inContext:(WOContext *)_context 372{ 373 NSMutableString *result; 374 WORequest *request; 375 NSString *remoteHost = @"-"; 376 NSCalendarDate *now; 377 NSDate *startDate; 378 NSString *tmp; 379 char buf[64]; 380 381 request = [_context request]; 382 result = [NSMutableString stringWithCapacity:256]; 383 384 /* remote host and date */ 385 386 if ((remoteHost = [request headerForKey:@"x-webobjects-remote-host"])) 387 ; 388 else if ((remoteHost = [request headerForKey:@"x-webobjects-remote-addr"])) 389 ; 390 else 391 remoteHost = @"-"; 392 393 now = [NSCalendarDate calendarDate]; 394 [now setTimeZone:gmt]; 395 396 [result appendString:remoteHost]; 397 sprintf(buf, 398#if GS_64BIT_OLD 399 " - - [%02i/%s/%04i:%02i:%02i:%02i GMT] ", 400#else 401 " - - [%02li/%s/%04li:%02li:%02li:%02li GMT] ", 402#endif 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], (int)[[_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 */ 464