| /* |
| * This file is part of the Jikes RVM project (http://jikesrvm.org). |
| * |
| * This file is licensed to You under the Eclipse Public License (EPL); |
| * You may not use this file except in compliance with the License. You |
| * may obtain a copy of the License at |
| * |
| * http://www.opensource.org/licenses/eclipse-1.0.php |
| * |
| * See the COPYRIGHT.txt file distributed with this work for information |
| * regarding copyright ownership. |
| */ |
| package org.mmtk.utility; |
| |
| import org.mmtk.vm.VM; |
| |
| import org.vmmagic.unboxed.*; |
| import org.vmmagic.pragma.*; |
| |
| /** |
| * Error and trace logging. |
| */ |
| @Uninterruptible |
| public class Log implements Constants { |
| |
| /**************************************************************************** |
| * |
| * Class variables |
| */ |
| |
| /** |
| * characters in the write buffer for the caller's message. This |
| * does not include characters reserved for the overflow message. |
| * |
| * This needs to be large because Jikes RVM's implementation of Lock.java |
| * logs a lot of information when there is potential GC deadlock. |
| */ |
| private static final int MESSAGE_BUFFER_SIZE = 3000; |
| |
| /** message added when the write buffer has overflown */ |
| private static final String OVERFLOW_MESSAGE = "... WARNING: Text truncated.\n"; |
| |
| private static final char OVERFLOW_MESSAGE_FIRST_CHAR = OVERFLOW_MESSAGE.charAt(0); |
| |
| /** characters in the overflow message, including the (optional) final |
| * newline */ |
| private static final int OVERFLOW_SIZE = OVERFLOW_MESSAGE.length(); |
| |
| /** |
| * characters in buffer for building string representations of |
| * longs. A long is a signed 64-bit integer in the range -2^63 to |
| * 2^63+1. The number of digits in the decimal representation of |
| * 2^63 is ceiling(log10(2^63)) == ceiling(63 * log10(2)) == 19. An |
| * extra character may be required for a minus sign (-). So the |
| * maximum number of characters is 20. |
| */ |
| private static final int TEMP_BUFFER_SIZE = 20; |
| |
| /** string that prefixes numbers logged in hexadecimal */ |
| private static final String HEX_PREFIX = "0x"; |
| |
| /** |
| * log2 of number of bits represented by a single hexidemimal digit |
| */ |
| private static final int LOG_BITS_IN_HEX_DIGIT = 2; |
| |
| /** |
| * log2 of number of digits in the unsigned hexadecimal |
| * representation of a byte |
| */ |
| private static final int LOG_HEX_DIGITS_IN_BYTE = LOG_BITS_IN_BYTE - LOG_BITS_IN_HEX_DIGIT; |
| |
| /** |
| * map of hexadecimal digit values to their character representations |
| */ |
| private static final char [] hexDigitCharacter = |
| { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; |
| |
| /** new line character. Emitted by writeln methods. */ |
| private static final char NEW_LINE_CHAR = '\n'; |
| |
| /** log instance used at build time. */ |
| private static Log log = new Log(); |
| |
| /**************************************************************************** |
| * |
| * Instance variables |
| */ |
| |
| /** buffer to store written message until flushing */ |
| private char [] buffer = new char[MESSAGE_BUFFER_SIZE + OVERFLOW_SIZE]; |
| |
| /** location of next character to be written */ |
| private int bufferIndex = 0; |
| |
| /** <code>true</code> if the buffer has overflown */ |
| private boolean overflow = false; |
| |
| /** The last character that was written by #addToBuffer(char). This is |
| used to check whether we want to newline-terminate the text. */ |
| private char overflowLastChar = '\0'; |
| |
| /** <code>true</code> if a thread id will be prepended */ |
| private boolean threadIdFlag = false; |
| |
| /** buffer for building string representations of longs */ |
| private char[] tempBuffer = new char[TEMP_BUFFER_SIZE]; |
| |
| /** constructor */ |
| public Log() { |
| for (int i = 0; i < OVERFLOW_SIZE; i++) { |
| buffer[MESSAGE_BUFFER_SIZE + i] = OVERFLOW_MESSAGE.charAt(i); |
| } |
| } |
| |
| /** |
| * writes a boolean. Either "true" or "false" is logged. |
| * |
| * @param b boolean value to be logged. |
| */ |
| public static void write(boolean b) { |
| write(b ? "true" : "false"); |
| } |
| |
| /** |
| * writes a character |
| * |
| * @param c character to be logged |
| */ |
| public static void write(char c) { |
| add(c); |
| } |
| |
| /** |
| * writes a long, in decimal. The value is not padded and no |
| * thousands seperator is logged. If the value is negative a |
| * leading minus sign (-) is logged. |
| * |
| * |
| * @param l long value to be logged |
| */ |
| public static void write(long l) { |
| boolean negative = l < 0; |
| int nextDigit; |
| char nextChar; |
| int index = TEMP_BUFFER_SIZE - 1; |
| char[] intBuffer = getIntBuffer(); |
| |
| nextDigit = (int) (l % 10); |
| nextChar = hexDigitCharacter[negative ? - nextDigit : nextDigit]; |
| intBuffer[index--] = nextChar; |
| l = l / 10; |
| |
| while (l != 0) { |
| nextDigit = (int) (l % 10); |
| nextChar = hexDigitCharacter[negative ? - nextDigit : nextDigit]; |
| intBuffer[index--] = nextChar; |
| l = l / 10; |
| } |
| |
| if (negative) { |
| intBuffer[index--] = '-'; |
| } |
| |
| for (index++; index < TEMP_BUFFER_SIZE; index++) { |
| add(intBuffer[index]); |
| } |
| } |
| |
| /** |
| * writes a <code>double</code>. Two digits after the decimal point |
| * are always logged. The value is not padded and no thousands |
| * seperator is used. If the value is negative a leading |
| * hyphen-minus (-) is logged. The decimal point is a full stop |
| * (.). |
| * |
| * @param d the double to be logged |
| */ |
| public static void write(double d) { write(d, 2); } |
| |
| /** |
| * writes a <code>double</code>. The number of digits after the |
| * decimal point is determined by <code>postDecimalDigits</code>. |
| * The value is not padded and not thousands seperator is used. If |
| * the value is negative a leading hyphen-minus (-) is logged. The |
| * decimal point is a full stop (.) and is logged even if |
| * <postDecimcalDigits</code> is zero. If <code>d</code> is greater |
| * than the largest representable value of type <code>int</code>, it |
| * is logged as "TooBig". Similarly, if it is less than |
| * the negative of the largest representable value, it is logged as |
| * "TooSmall". If <code>d</code> is NaN is is logged as "NaN". |
| * |
| * @param d the double to be logged |
| * @param postDecimalDigits the number of digits to be logged after |
| * the decimal point. If less than or equal to zero no digits are |
| * logged, but the decimal point is. |
| */ |
| public static void write(double d, int postDecimalDigits) { |
| if (d != d) { |
| write("NaN"); |
| return; |
| } |
| if (d > Integer.MAX_VALUE) { |
| write("TooBig"); |
| return; |
| } |
| if (d < -Integer.MAX_VALUE) { |
| write("TooSmall"); |
| return; |
| } |
| |
| boolean negative = (d < 0.0); |
| d = negative ? (-d) : d; // Take absolute value |
| int ones = (int) d; |
| int multiplier = 1; |
| while (postDecimalDigits-- > 0) |
| multiplier *= 10; |
| int remainder = (int) (multiplier * (d - ones)); |
| if (remainder < 0) remainder = 0; |
| if (negative) write('-'); |
| write(ones); |
| write('.'); |
| while (multiplier > 1) { |
| multiplier /= 10; |
| write(remainder / multiplier); |
| remainder %= multiplier; |
| } |
| } |
| |
| /** |
| * writes an array of characters |
| * |
| * @param c the array of characters to be logged |
| */ |
| public static void write(char[] c) { |
| write(c, c.length); |
| } |
| |
| /** |
| * writes the start of an array of characters |
| * |
| * @param c the array of characters |
| * @param len the number of characters to be logged, starting with |
| * the first character |
| */ |
| public static void write(char[] c, int len) { |
| if (VM.VERIFY_ASSERTIONS) VM.assertions._assert(len <= c.length); |
| for (int i = 0; i < len; i++) { |
| add(c[i]); |
| } |
| } |
| |
| /** |
| * writes an array of bytes. The bytes are interpretted |
| * as characters. |
| * |
| * @param b the array of bytes to be logged |
| */ |
| public static void write(byte[] b) { |
| for (int i = 0; i < b.length; i++) { |
| add((char)b[i]); |
| } |
| } |
| |
| /** |
| * writes a string |
| * |
| * @param s the string to be logged |
| */ |
| public static void write(String s) { |
| add(s); |
| } |
| |
| /** |
| * writes a word, in hexadecimal. It is zero-padded to the size of |
| * an address. |
| * |
| * @param w the word to be logged |
| */ |
| public static void write(Word w) { |
| writeHex(w, BYTES_IN_ADDRESS); |
| } |
| |
| /** |
| * writes a word, in decimal. |
| * |
| * @param w the word to be logged |
| */ |
| public static void writeDec(Word w) { |
| if (BYTES_IN_ADDRESS == 4) { |
| write(w.toInt()); |
| } else { |
| write(w.toLong()); |
| } |
| } |
| |
| /** |
| * writes an address, in hexademical. It is zero-padded. |
| * |
| * @param a the address to be logged |
| */ |
| public static void write(Address a) { |
| writeHex(a.toWord(), BYTES_IN_ADDRESS); |
| } |
| |
| /** |
| * writes a string followed by an address, in hexademical. |
| * @see #write(String) |
| * @see #write(Address) |
| * |
| * @param s the string to be logged |
| * @param a the address to be logged |
| */ |
| public static void write(String s, Address a) { |
| write(s); |
| write(a); |
| } |
| |
| /** |
| * Write a string followed by a long |
| * @see #write(String) |
| * @see #write(long) |
| * |
| * @param s the string to be logged |
| * @param l the long to be logged |
| */ |
| public static void write(String s, long l) { |
| write(s); |
| write(l); |
| } |
| |
| /** |
| * writes an object reference, in hexademical. It is zero-padded. |
| * |
| * @param o the object reference to be logged |
| */ |
| public static void write(ObjectReference o) { |
| writeHex(o.toAddress().toWord(), BYTES_IN_ADDRESS); |
| } |
| |
| /** |
| * writes an offset, in hexademical. It is zero-padded. |
| * |
| * @param o the offset to be logged |
| */ |
| public static void write(Offset o) { |
| writeHex(o.toWord(), BYTES_IN_ADDRESS); |
| } |
| |
| /** |
| * writes an extent, in hexademical. It is zero-padded. |
| * |
| * @param e the extent to be logged |
| */ |
| public static void write(Extent e) { |
| writeHex(e.toWord(), BYTES_IN_ADDRESS); |
| } |
| |
| /** |
| * write a new-line and flushes the buffer |
| */ |
| public static void writeln() { |
| writelnWithFlush(true); |
| } |
| |
| /** |
| * writes a boolean and a new-line, then flushes the buffer. |
| * @see #write(boolean) |
| * |
| * @param b boolean value to be logged. |
| */ |
| public static void writeln(boolean b) { writeln(b, true); } |
| |
| /** |
| * writes a character and a new-line, then flushes the buffer. |
| * @see #write(char) |
| * |
| * @param c character to be logged |
| */ |
| public static void writeln(char c) { writeln(c, true); } |
| |
| /** |
| * writes a long, in decimal, and a new-line, then flushes the buffer. |
| * @see #write(long) |
| * |
| * @param l long value to be logged |
| */ |
| public static void writeln(long l) { writeln(l, true); } |
| |
| /** |
| * writes a <code>double</code> and a new-line, then flushes the buffer. |
| * @see #write(double) |
| * |
| * @param d the double to be logged |
| */ |
| public static void writeln(double d) { writeln(d, true); } |
| |
| /** |
| * writes a <code>double</code> and a new-line, then flushes the buffer. |
| * @see #write(double, int) |
| * |
| * @param d the double to be logged |
| */ |
| public static void writeln(double d, int postDecimalDigits) { |
| writeln(d, postDecimalDigits, true); } |
| |
| /** |
| * writes an array of characters and a new-line, then flushes the buffer. |
| * @see #write(char []) |
| * |
| * @param ca the array of characters to be logged |
| */ |
| public static void writeln(char [] ca) { writeln(ca, true); } |
| |
| /** |
| * writes the start of an array of characters and a new-line, then |
| * flushes the buffer. |
| * @see #write(char[], int) |
| * |
| * @param ca the array of characters |
| * @param len the number of characters to be logged, starting with |
| * the first character |
| */ |
| public static void writeln(char [] ca, int len) { writeln(ca, len, true); } |
| |
| /** |
| * writes an array of bytes and a new-line, then |
| * flushes the buffer. |
| * @see #write(byte[]) |
| * |
| * @param b the array of bytes to be logged |
| */ |
| public static void writeln(byte [] b) { writeln(b, true); } |
| |
| /** |
| * writes a string and a new-line, then flushes the buffer. |
| * |
| * @param s the string to be logged |
| */ |
| public static void writeln(String s) { writeln(s, true); } |
| |
| /** |
| * writes a word, in hexadecimal, and a new-line, then flushes the buffer. |
| * @see #write(Word) |
| * |
| * @param w the word to be logged |
| */ |
| public static void writeln(Word w) { writeln(w, true); } |
| |
| /** |
| * writes an address, in hexademical, and a new-line, then flushes |
| * the buffer. |
| * @see #write(Address) |
| * |
| * @param a the address to be logged |
| */ |
| public static void writeln(Address a) { writeln(a, true); } |
| |
| /** |
| * writes an object reference, in hexademical, and a new-line, then |
| * flushes the buffer. |
| * @see #write(ObjectReference) |
| * |
| * @param o the object reference to be logged |
| */ |
| public static void writeln(ObjectReference o) { writeln(o, true); } |
| |
| /** |
| * writes an offset, in hexademical, and a new-line, then flushes the buffer. |
| * @see #write(Offset) |
| * |
| * @param o the offset to be logged |
| */ |
| public static void writeln(Offset o) { writeln(o, true); } |
| |
| /** |
| * writes an extent, in hexademical, and a new-line, then flushes the buffer. |
| * @see #write(Extent) |
| * |
| * @param e the extent to be logged |
| */ |
| public static void writeln(Extent e) { writeln(e, true); } |
| |
| /** |
| * writes a new-line without flushing the buffer |
| */ |
| public static void writelnNoFlush() { |
| writelnWithFlush(false); |
| } |
| |
| /** |
| * writes a boolean and a new-line, then optionally flushes the buffer. |
| * @see #write(boolean) |
| * |
| * @param b boolean value to be logged. |
| * @param flush if <code>true</code> then flushes the buffer |
| */ |
| public static void writeln(boolean b, boolean flush) { |
| write(b); |
| writelnWithFlush(flush); |
| } |
| |
| /** |
| * writes a character and a new-line, then optionally flushes the |
| * buffer. |
| * @see #write(char) |
| * |
| * @param c character to be logged |
| * @param flush if <code>true</code> then flushes the buffer |
| */ |
| public static void writeln(char c, boolean flush) { |
| write(c); |
| writelnWithFlush(flush); |
| } |
| |
| /** |
| * writes a long, in decimal, and a new-line, then optionally flushes |
| * the buffer. |
| * @see #write(long) |
| * |
| * @param l long value to be logged |
| * @param flush if <code>true</code> then flushes the buffer |
| */ |
| public static void writeln(long l, boolean flush) { |
| write(l); |
| writelnWithFlush(flush); |
| } |
| |
| /** |
| * writes a <code>double</code> and a new-line, then optionally flushes |
| * the buffer. |
| * @see #write(double) |
| * |
| * @param d the double to be logged |
| * @param flush if <code>true</code> then flush the buffer |
| */ |
| public static void writeln(double d, boolean flush) { |
| write(d); |
| writelnWithFlush(flush); |
| } |
| |
| /** |
| * writes a <code>double</code> and a new-line, then optionally flushes |
| * the buffer. |
| * @see #write(double, int) |
| * |
| * @param d the double to be logged |
| * @param flush if <code>true</code> then flushes the buffer |
| */ |
| public static void writeln(double d, int postDecimalDigits, boolean flush) { |
| write(d, postDecimalDigits); |
| writelnWithFlush(flush); |
| } |
| |
| |
| /** |
| * writes an array of characters and a new-line, then optionally |
| * flushes the buffer. |
| * @see #write(char []) |
| * |
| * @param ca the array of characters to be logged |
| * @param flush if <code>true</code> then flushes the buffer |
| */ |
| public static void writeln(char[] ca, boolean flush) { |
| write(ca); |
| writelnWithFlush(flush); |
| } |
| |
| /** |
| * writes the start of an array of characters and a new-line, then |
| * optionally flushes the buffer. |
| * @see #write(char[], int) |
| * |
| * @param ca the array of characters |
| * @param len the number of characters to be logged, starting with |
| * the first character |
| * @param flush if <code>true</code> then flushes the buffer |
| */ |
| public static void writeln(char[] ca, int len, boolean flush) { |
| write(ca, len); |
| writelnWithFlush(flush); |
| } |
| |
| /** |
| * writes an array of bytes and a new-line, then optionally flushes the |
| * buffer. |
| * @see #write(byte[]) |
| * |
| * @param b the array of bytes to be logged |
| * @param flush if <code>true</code> then flushes the buffer |
| */ |
| public static void writeln(byte[] b, boolean flush) { |
| write(b); |
| writelnWithFlush(flush); |
| } |
| |
| /** |
| * writes a string and a new-line, then optionally flushes the buffer. |
| * |
| * @param s the string to be logged |
| * @param flush if <code>true</code> then flushes the buffer |
| */ |
| public static void writeln(String s, boolean flush) { |
| write(s); |
| writelnWithFlush(flush); |
| } |
| |
| public static void writeln(String s, long l) { |
| write(s); |
| writeln(l); |
| } |
| |
| |
| /** |
| * writes a word, in hexadecimal, and a new-line, then optionally |
| * flushes the buffer. |
| * @see #write(Word) |
| * |
| * @param w the word to be logged |
| * @param flush if <code>true</code> then flushes the buffer |
| */ |
| public static void writeln(Word w, boolean flush) { |
| write(w); |
| writelnWithFlush(flush); |
| } |
| |
| |
| /** |
| * writes an address, in hexademical, and a new-line, then optionally |
| * flushes the buffer. |
| * @see #write(Address) |
| * |
| * @param a the address to be logged |
| * @param flush if <code>true</code> then flushes the buffer |
| */ |
| public static void writeln(Address a, boolean flush) { |
| write(a); |
| writelnWithFlush(flush); |
| } |
| |
| /** |
| * writes an object reference, in hexademical, and a new-line, then |
| * optionally flushes the buffer. |
| * @see #write(ObjectReference) |
| * |
| * @param o the object reference to be logged |
| * @param flush if <code>true</code> then flushes the buffer |
| */ |
| public static void writeln(ObjectReference o, boolean flush) { |
| write(o); |
| writelnWithFlush(flush); |
| } |
| |
| /** |
| * writes an offset, in hexademical, and a new-line, then optionally |
| * flushes the buffer. |
| * @see #write(Offset) |
| * |
| * @param o the offset to be logged |
| * @param flush if <code>true</code> then flushes the buffer |
| */ |
| public static void writeln(Offset o, boolean flush) { |
| write(o); |
| writelnWithFlush(flush); |
| } |
| |
| /** |
| * writes an extent, in hexademical, and a new-line, then optionally |
| * flushes the buffer. |
| * @see #write(Extent) |
| * |
| * @param e the extent to be logged |
| * @param flush if <code>true</code> then flushes the buffer |
| */ |
| public static void writeln(Extent e, boolean flush) { |
| write(e); |
| writelnWithFlush(flush); |
| } |
| |
| /** |
| * writes a string followed by a Address |
| * @see #write(String) |
| * @see #write(Address) |
| * |
| * @param s the string to be logged |
| * @param a the Address to be logged |
| */ |
| public static void writeln(String s, Address a) { |
| write(s); |
| writeln(a); |
| } |
| |
| /** |
| * Log a thread identifier at the start of the next message flushed. |
| */ |
| public static void prependThreadId() { |
| getLog().setThreadIdFlag(); |
| } |
| |
| /** |
| * flushes the buffer. The buffered effected of writes since the last |
| * flush will be logged in one block without output from other |
| * thread's logging interleaving. |
| */ |
| public static void flush() { |
| getLog().flushBuffer(); |
| } |
| |
| /** |
| * writes a new-line and optionally flushes the buffer |
| * |
| * @param flush if <code>true</code> the buffer is flushed |
| */ |
| private static void writelnWithFlush(boolean flush) { |
| add(NEW_LINE_CHAR); |
| if (flush) { |
| flush(); |
| } |
| } |
| |
| /** |
| * writes a <code>long</code> in hexadecimal |
| * |
| * @param w the Word to be logged |
| * @param bytes the number of bytes from the long to be logged. If |
| * less than 8 then the least significant bytes are logged and some |
| * of the most significant bytes are ignored. |
| */ |
| private static void writeHex(Word w, int bytes) { |
| int hexDigits = bytes * (1 << LOG_HEX_DIGITS_IN_BYTE); |
| int nextDigit; |
| |
| write(HEX_PREFIX); |
| |
| for (int digitNumber = hexDigits - 1; digitNumber >= 0; digitNumber--) { |
| nextDigit = w.rshl(digitNumber << LOG_BITS_IN_HEX_DIGIT).toInt() & 0xf; |
| char nextChar = hexDigitCharacter[nextDigit]; |
| add(nextChar); |
| } |
| } |
| |
| /** |
| * adds a character to the buffer |
| * |
| * @param c the character to add |
| */ |
| private static void add(char c) { |
| getLog().addToBuffer(c); |
| } |
| |
| /** |
| * adds a string to the buffer |
| * |
| * @param s the string to add |
| */ |
| private static void add(String s) { |
| getLog().addToBuffer(s); |
| } |
| |
| private static Log getLog() { |
| if (VM.assertions.runningVM()) { |
| return VM.activePlan.log(); |
| } else { |
| return log; |
| } |
| } |
| |
| /** |
| * adds a character to the buffer |
| * |
| * @param c the character to add |
| */ |
| private void addToBuffer(char c) { |
| if (bufferIndex < MESSAGE_BUFFER_SIZE) { |
| buffer[bufferIndex++] = c; |
| } else { |
| overflow = true; |
| overflowLastChar = c; |
| } |
| } |
| |
| /** |
| * adds a string to the buffer |
| * |
| * @param s the string to add |
| */ |
| private void addToBuffer(String s) { |
| if (bufferIndex < MESSAGE_BUFFER_SIZE) { |
| bufferIndex += VM.strings.copyStringToChars(s, buffer, bufferIndex, MESSAGE_BUFFER_SIZE + 1); |
| if (bufferIndex == MESSAGE_BUFFER_SIZE + 1) { |
| overflow = true; |
| // We don't bother setting OVERFLOW_LAST_CHAR, since we don't have an |
| // MMTk method that lets us peek into a string. Anyway, it's just a |
| // convenience to get the newline right. |
| buffer[MESSAGE_BUFFER_SIZE] = OVERFLOW_MESSAGE_FIRST_CHAR; |
| bufferIndex--; |
| } |
| } else { |
| overflow = true; |
| } |
| } |
| |
| /** |
| * flushes the buffer |
| */ |
| private void flushBuffer() { |
| int newlineAdjust = overflowLastChar == NEW_LINE_CHAR ? 0 : -1; |
| int totalMessageSize = overflow ? (MESSAGE_BUFFER_SIZE + OVERFLOW_SIZE + newlineAdjust) : bufferIndex; |
| if (threadIdFlag) { |
| VM.strings.writeThreadId(buffer, totalMessageSize); |
| } else { |
| VM.strings.write(buffer, totalMessageSize); |
| } |
| threadIdFlag = false; |
| overflow = false; |
| overflowLastChar = '\0'; |
| bufferIndex = 0; |
| } |
| |
| /** |
| * sets the flag so that a thread identifier will be included before |
| * the logged message |
| */ |
| private void setThreadIdFlag() { |
| threadIdFlag = true; |
| } |
| |
| /** |
| * gets the buffer for building string representations of integers. |
| * There is one of these buffers for each Log instance. |
| */ |
| private static char[] getIntBuffer() { |
| return getLog().getTempBuffer(); |
| } |
| |
| /** |
| * gets the buffer for building string representations of integers. |
| */ |
| private char[] getTempBuffer() { |
| return tempBuffer; |
| } |
| } |