001/** 002 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. 003 * 004 * Copyright (c) 2006 Sun Microsystems Inc. All Rights Reserved 005 * 006 * The contents of this file are subject to the terms 007 * of the Common Development and Distribution License 008 * (the License). You may not use this file except in 009 * compliance with the License. 010 * 011 * You can obtain a copy of the License at 012 * https://opensso.dev.java.net/public/CDDLv1.0.html or 013 * opensso/legal/CDDLv1.0.txt 014 * See the License for the specific language governing 015 * permission and limitations under the License. 016 * 017 * When distributing Covered Code, include this CDDL 018 * Header Notice in each file and include the License file 019 * at opensso/legal/CDDLv1.0.txt. 020 * If applicable, add the following below the CDDL Header, 021 * with the fields enclosed by brackets [] replaced by 022 * your own identifying information: 023 * "Portions Copyrighted [year] [name of copyright owner]" 024 * 025 * $Id: Stats.java,v 1.5 2008/08/08 00:40:59 ww203982 Exp $ 026 * 027 */ 028 029package com.sun.identity.shared.stats; 030 031import com.sun.identity.common.ShutdownListener; 032import com.sun.identity.common.ShutdownManager; 033import com.sun.identity.common.SystemTimer; 034import com.sun.identity.common.TimerPool; 035import com.sun.identity.shared.Constants; 036import com.sun.identity.shared.configuration.SystemPropertiesManager; 037import java.io.BufferedWriter; 038import java.io.File; 039import java.io.FileOutputStream; 040import java.io.IOException; 041import java.io.OutputStreamWriter; 042import java.io.PrintWriter; 043import java.io.StringWriter; 044import java.text.DateFormat; 045import java.text.SimpleDateFormat; 046import java.util.Date; 047import java.util.HashMap; 048import java.util.Map; 049import java.util.MissingResourceException; 050import java.util.ResourceBundle; 051import java.util.Vector; 052 053// NOTE: Since JVM specs guarantee atomic access/updates to int variables 054// (actually all variables except double and long), the design consciously 055// avoids synchronized methods, particularly for message(). This is done to 056// reduce the performance overhead of synchronized message() when statistics 057// is disabled. This does not have serious side-effects other than an occasional 058// invocation of message() missing concurrent update of 'statsState'. 059 060/******************************************************************************* 061 * <p> 062 * Allows a uniform interface to statistics information in a uniform format. 063 * <code>Stats</code> supports different states of filing stats information: 064 * <code>OFF</code>, <code>FILE</code> and <code>CONSOLE</code>. <BR> 065 * <li> <code>OFF</code> statistics is turned off. 066 * <li> <code>FILE</code> statistics information is written to a file 067 * <li> <code>CONSOLE</code> statistics information is written on console 068 * <p> 069 * Stats service uses the property file, <code>AMConfig.properties</code>, to 070 * set the default stats level and the output directory where the stats files 071 * will be placed. The properties file is located (using 072 * {@link java.util.ResourceBundle} semantics) from one of the directories in 073 * the CLASSPATH. 074 * <p> 075 * The following keys are used to configure the Stats service. Possible values 076 * for the key 'state' are: off | off | file | console The key 'directory' 077 * specifies the output directory where the stats files will be created. 078 * 079 * <blockquote> 080 * 081 * <pre> 082 * com.iplanet.services.stats.state 083 * com.iplanet.services.stats.directory 084 * </pre> 085 * 086 * </blockquote> 087 * 088 * If there is an error reading or loading the properties, all the information 089 * is redirected to <code>System.out</code> 090 * 091 * If these properties are changed, the server must be restarted for the changes 092 * to take effect. 093 * 094 * <p> 095 * <b>NOTE:</b> Printing Statistics is an IO intensive operation and may hurt 096 * application performance when abused. Particularly, note that Java evaluates 097 * the arguments to <code>message()</code> and <code>warning()</code> even 098 * when statistics is turned off. It is recommended that the stats state be 099 * checked before invoking any <code>message()</code> or 100 * <code>warning()</code> methods to avoid unnecessary argument evaluation and 101 * to maximize application performance. 102 * </p> 103 * @supported.all.api 104 */ 105public class Stats implements ShutdownListener { 106 /** flags the disabled stats state. */ 107 public static final int OFF = 0; 108 109 /** 110 * Flags the state where all the statistic information is printed to a file 111 */ 112 public static final int FILE = 1; 113 114 /** 115 * Flags the state where printing to a file is disabled. All printing is 116 * done on System.out. 117 */ 118 public static final int CONSOLE = 2; 119 120 /** 121 * statsMap is a container of all active Stats objects. Log file name is the 122 * key and Stats is the value of this map. 123 */ 124 private static Map statsMap = new HashMap(); 125 126 /** serviceInitialized indicates if the service is already initialized. */ 127 private static boolean serviceInitialized = false; 128 129 private static DateFormat dateFormat; 130 131 /** 132 * The default stats level for the entire service and the level that is used 133 * when a Stats object is first created and before its level is modified. 134 * Don't initialize the following two variables in a static 135 * initializer/block because other components may initialize Stats in their 136 * static initializers (as opposed to constructors or methods). The order of 137 * execution of static blocks is not guaranteed by JVM. So if we set the 138 * following two static variables to some default values here, then it will 139 * interfere with the execution of {@link #initService}. 140 */ 141 private static String defaultStatsLevel; 142 143 private static String outputDirectory; 144 145 private final String statsName; 146 147 private PrintWriter statsFile = null; 148 149 private int statsState; 150 151 private static StatsRunner statsListeners = new StatsRunner(); 152 153 /** 154 * Initializes the Stats service so that Stats objects can be created. At 155 * startup (when the first Stats object is ever created in a JVM), this 156 * method reads <code>AMConfig.properties</code> file (using 157 * {@link java.util.ResourceBundle} semantics) from one of the directories 158 * in the <code>CLASSPATH</code>, and loads the properties. It creates 159 * the stats directory. If all the directories in output dir don't have 160 * adequate permissions then the creation of the stats directory will fail 161 * and all the stats files will be located in the "current working 162 * directory" of the process running stats code. If there is an error 163 * reading or loading the properties, it will set the stats service to 164 * redirect all stats information to <code>System.out</code> 165 */ 166 private static void initService() { 167 /* 168 * We will use the double-checked locking pattern. Rarely entered block. 169 * Push synchronization inside it. This is the first check. 170 */ 171 if (!serviceInitialized) { 172 /* 173 * Only 1 thread at a time gets past the next point. Rarely executed 174 * synchronization statement and hence synchronization penalty is 175 * not paid every time this method is called. 176 */ 177 synchronized (Stats.class) { 178 /* 179 * If a second thread was waiting to get here, it will now find 180 * that the instance has already been initialized, and it will 181 * not re-initialize the instance variable. This is the (second) 182 * double-check. 183 */ 184 if (!serviceInitialized) { 185 dateFormat = new SimpleDateFormat( 186 "MM/dd/yyyy hh:mm:ss:SSS a zzz"); 187 try { 188 defaultStatsLevel = SystemPropertiesManager.get( 189 Constants.SERVICES_STATS_STATE); 190 outputDirectory = SystemPropertiesManager.get( 191 Constants.SERVICES_STATS_DIRECTORY); 192 ResourceBundle bundle = 193 com.sun.identity.shared.locale.Locale 194 .getInstallResourceBundle("amUtilMsgs"); 195 if (outputDirectory != null) { 196 File createDir = new File(outputDirectory); 197 if (!createDir.exists()) { 198 if (!createDir.mkdirs()) { 199 System.err.println(bundle.getString( 200 "com.iplanet.services.stats.nodir")); 201 } 202 } 203 204 } 205 } catch (MissingResourceException e) { 206 System.err.println(e.getMessage()); 207 e.printStackTrace(); 208 209 // If there is any error in getting the level or 210 // outputDirectory, defaultStatsLevel will be set to 211 // ON so that output will go to 212 // System.out 213 214 defaultStatsLevel = "console"; 215 outputDirectory = null; 216 } catch (SecurityException se) { 217 System.err.println(se.getMessage()); 218 } 219 220 SystemTimer.getTimer().schedule(statsListeners, new Date((( 221 System.currentTimeMillis() + 222 statsListeners.getRunPeriod()) / 1000) * 1000)); 223 224 serviceInitialized = true; 225 } 226 } 227 } 228 } 229 230 /** 231 * This constructor takes as an argument the name of the stats file. The 232 * stats file is neither created nor opened until the first time 233 * <code>message()</code>, <code>warning()</code> or 234 * <code>error()</code> is invoked and the stats state is neither 235 * <code>OFF</code> nor <code>ON</code>. 236 * <p> 237 * <b>NOTE:</b>The recommended and preferred method to create Stats objects 238 * is <code>getInstance(String)</code>. This constructor may be 239 * deprecated in future. 240 * </p> 241 * 242 * @param statsFileName 243 * name of the stats file to create or use 244 */ 245 private Stats(String statsName) { 246 // Initialize the stats service the first time a Stats object is 247 // created. 248 249 initService(); 250 251 // Now initialize this instance itself 252 253 this.statsName = statsName; 254 setStats(defaultStatsLevel); 255 256 synchronized (statsMap) { 257 // explicitly ignore any duplicate instances. 258 statsMap.put(statsName, this); 259 } 260 ShutdownManager shutdownMan = ShutdownManager.getInstance(); 261 if (shutdownMan.acquireValidLock()) { 262 try { 263 shutdownMan.addShutdownListener(this); 264 } finally { 265 shutdownMan.releaseLockAndNotify(); 266 } 267 } 268 } 269 270 /** 271 * Returns an existing instance of Stats for the specified stats file or a 272 * new one if no such instance already exists. If a Stats object has to be 273 * created, its level is set to the level defined in the 274 * <code>AMConfig.properties</code> file. The level can be changed later 275 * by using {@link #setStats(int)} or {@link #setStats(String)} 276 * 277 * @param statsName 278 * name of statistic instance. 279 * @return an existing instance of Stats for the specified stats file. 280 */ 281 public static synchronized Stats getInstance(String statsName) { 282 Stats statsObj = (Stats) statsMap.get(statsName); 283 if (statsObj == null) { 284 statsObj = new Stats(statsName); 285 } 286 return statsObj; 287 } 288 289 /** 290 * Checks if statistics is enabled. 291 * 292 * <p> 293 * <b>NOTE:</b> It is recommended that <code>isEnabled()</code> be used 294 * instead of <code>isEnabled()</code> as the former is more intuitive. 295 * 296 * @return <code>true</code> if statistics is enabled <code>false</code> 297 * if statistics is disabled 298 * 299 */ 300 public boolean isEnabled() { 301 return (statsState > Stats.OFF); 302 } 303 304 /** 305 * Returns one of the 3 possible values. 306 * <ul> 307 * <li><code>Stats.OFF</code> 308 * <li><code>Stats.FILE</code> 309 * <li><code>Stats.CONSOLE</code> 310 * </ul> 311 * 312 * @return state of Stats. 313 */ 314 public int getState() { 315 return statsState; 316 } 317 318 /** 319 * Prints messages only when the stats state is either 320 * <code>Stats.FILE</code> or <code>Stats.CONSOLE</code>. 321 * 322 * <p> 323 * <b>NOTE:</b> Printing Statistics is an IO intensive operation and may 324 * hurt application performance when abused. Particularly, note that Java 325 * evaluates arguments to <code>message()</code> even when statistics is 326 * turned off. So when the argument to this method involves the String 327 * concatenation operator '+' or any other method invocation, 328 * <code>isEnabled</code> <b>MUST</b> be used. It is recommended that the 329 * stats state be checked by invoking <code>isEnabled()</code> before 330 * invoking any <code>message()</code> methods to avoid unnecessary 331 * argument evaluation and maximize application performance. 332 * </p> 333 * 334 * @param msg 335 * message to be recorded. 336 */ 337 public void record(String msg) { 338 if (statsState > Stats.OFF) { 339 formatAndWrite(null, msg); 340 } 341 } 342 343 private void formatAndWrite(String prefix, String msg) { 344 if (statsState == Stats.CONSOLE) { 345 if (msg != null) { 346 if (prefix == null) { 347 System.out.println(msg); 348 } else { 349 System.out.println(prefix + msg); 350 } 351 } 352 return; 353 } 354 355 // The default capacity of StringBuffer in StringWriter is 16, but we 356 // know for sure that the minimum header size is about 35. Hence, to 357 // avoid reallocation allocate at least 160 chars. 358 359 String serverInstance = System.getProperty("server.name"); 360 StringWriter swriter = new StringWriter(160); 361 PrintWriter buf = new PrintWriter(swriter, true); 362 synchronized (dateFormat) { 363 buf.write(dateFormat.format(new Date())); 364 } 365 if ((serverInstance != null) && (serverInstance != "")) { 366 buf.write(": "); 367 buf.write("Server Instance: " + serverInstance); 368 } 369 buf.write(": "); 370 buf.write(Thread.currentThread().toString()); 371 buf.write("\n"); 372 if (prefix != null) { 373 buf.write(prefix); 374 } 375 if (msg != null) { 376 buf.write(msg); 377 } 378 buf.flush(); 379 380 write(swriter.toString()); 381 } 382 383 /** 384 * Actually writes to the stats file. If it cannot write to the stats file, 385 * it turn off statistics. The first time this method is invoked on a Stats 386 * object, that object's stats file is created/opened in the directory 387 * specified by the 388 * <code>property com.iplanet.services.stats.directory</code> in the 389 * properties file, <code>AMConfig.properties</code>. 390 */ 391 private synchronized void write(String msg) { 392 try { 393 // statistics is enabled. 394 // First, see if the statsFile is already open. If not, open it now. 395 396 if (statsFile == null) { 397 // open file in append mode 398 FileOutputStream fos = new FileOutputStream(outputDirectory 399 + File.separator + statsName, true); 400 statsFile = new PrintWriter(new BufferedWriter( 401 new OutputStreamWriter(fos, "UTF-8")), true); 402 403 statsFile.println("*********************************" + 404 "*********************"); 405 } 406 407 statsFile.println(msg); 408 } catch (IOException e) { 409 System.err.println(msg); 410 411 // turn off statistics because statsFile is not accessible 412 statsState = Stats.OFF; 413 } 414 } 415 416 /** 417 * Sets the stats capabilities based on the values of the 418 * <code>statsType</code> argument. 419 * 420 * @param statsType 421 * is any one of five possible values: 422 * <p> 423 * <code>Stats.OFF</code> 424 * <p> 425 * <p> 426 * <code>Stats.FILE</code> 427 * <p> 428 * <p> 429 * <code>Stats.CONSOLE</code> 430 * <p> 431 */ 432 public void setStats(int statsType) { 433 switch (statsType) { 434 case Stats.OFF: 435 case Stats.FILE: 436 case Stats.CONSOLE: 437 statsState = statsType; 438 break; 439 440 default: 441 // ignore invalid statsType values 442 break; 443 } 444 } 445 446 /** 447 * Sets the <code>stats</code> capabilities based on the values of the 448 * <code>statsType</code> argument. 449 * 450 * @param statsType 451 * is any one of the following possible values: 452 * <p> 453 * off - statistics is disabled 454 * </p> 455 * <p> 456 * file - statistics are written to the stats file 457 * <code>System.out</code> 458 * </p> 459 * <p> 460 * console - statistics are written to the stats to the console 461 */ 462 public void setStats(String statsType) { 463 if (statsType == null) { 464 return; 465 } else if (statsType.equalsIgnoreCase("console")) { 466 statsState = Stats.CONSOLE; 467 } else if (statsType.equalsIgnoreCase("file")) { 468 statsState = Stats.FILE; 469 } else if (statsType.equalsIgnoreCase("off")) { 470 statsState = Stats.OFF; 471 } else if (statsType.equals("*")) { 472 statsState = Stats.CONSOLE; 473 } else { 474 if (statsType.endsWith("*")) { 475 statsType = statsType.substring(0, statsType.length() - 1); 476 } 477 if (statsName.startsWith(statsType)) { 478 statsState = Stats.CONSOLE; 479 } 480 } 481 } 482 483 /** 484 * Destroys the stats object, closes the stats file and releases any system 485 * resources. Note that the stats file will remain open until 486 * <code>destroy()</code> is invoked. To conserve file resources, you 487 * should invoke <code>destroy()</code> explicitly rather than wait for 488 * the garbage collector to clean up. 489 * 490 * <p> 491 * If this object is accessed after <code>destroy()</code> has been 492 * invoked, the results are undefined. 493 * </p> 494 */ 495 public void destroy() { 496 finalize(); 497 } 498 499 public void shutdown() { 500 finalize(); 501 } 502 503 /** Flushes and then closes the stats file. */ 504 protected void finalize() { 505 synchronized (statsMap) { 506 statsMap.remove(statsName); 507 } 508 509 synchronized (this) { 510 if (statsFile == null) { 511 return; 512 } 513 514 statsState = Stats.OFF; 515 statsFile.flush(); 516 statsFile.close(); 517 statsFile = null; 518 } 519 } 520 521 public void addStatsListener(StatsListener listener) { 522 statsListeners.addElement(listener); 523 } 524}