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