/*
* 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 static org.mmtk.utility.Constants.*;
import org.mmtk.vm.VM;
import org.vmmagic.unboxed.*;
import org.vmmagic.pragma.*;
/**
* Error and trace logging.
*/
@Uninterruptible
public class Log {
/****************************************************************************
*
* Class variables
*/
/**
* characters in the write buffer for the caller's message. This
* does not include characters reserved for the overflow message.<p>
*
* 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 final 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 final 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 separator 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
* separator 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 separator 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
* <code>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 hexadecimal. 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 hexadecimal.
* @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);
}
/**
* Writes a String followed by an extent, in hexadecimal.
* @see #write(String)
* @see #write(Extent)
*
* @param s the string to be logged
* @param e the extent to be logged
*/
public static void write(String s, Extent e) {
write(s);
write(e);
}
/**
* Writes a string followed by an object reference, in hexadecimal.
* @see #write(String)
* @see #write(ObjectReference)
*
* @param s the string to be logged
* @param objRef the object reference to be logged
*/
public static void write(String s, ObjectReference objRef) {
write(s);
write(objRef);
}
/**
* 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 hexadecimal. 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 hexadecimal. 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 hexadecimal. 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
* @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 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 hexadecimal, 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 hexadecimal, 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 hexadecimal, 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 hexadecimal, 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 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.
* @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 String followed by an extent, in hexadecimal.
* Then flushes the buffer.
* @param s the string to be logged
* @param extent the extent reference to be logged
*/
public static void writeln(String s, Extent extent) {
write(s);
write(extent);
writeln();
}
/**
* Writes a String followed by an object reference, in hexadecimal.
* Then flushes the buffer.
* @param s the string to be logged
* @param objRef the object reference to be logged
*/
public static void writeln(String s, ObjectReference objRef) {
write(s);
write(objRef);
writeln();
}
/**
* Writes a String followed by an offset, in hexadecimal.
* Then flushes the buffer.
* @param s the string to be logged
* @param offset the offset to be logged
*/
public static void writeln(String s, Offset offset) {
write(s);
write(offset);
writeln();
}
/**
* Writes a String followed by a word, in hexadecimal.
* Then flushes the buffer.
* @param s the string to be logged
* @param word the word to be logged
*/
public static void writeln(String s, Word word) {
write(s);
write(word);
writeln();
}
/**
* 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 hexadecimal, 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 hexadecimal, 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 hexadecimal, 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 hexadecimal, 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() {
return VM.activePlan.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;
}
/**
* @return 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();
}
/**
* @return the buffer for building string representations of integers
*/
private char[] getTempBuffer() {
return tempBuffer;
}
}