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