blob: 4ef5c6242a4c93e589dbe2791b4fdd8ad97828ba [file] [log] [blame]
/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.ddmlib.log;
import com.android.ddmlib.utils.ArrayHelper;
import java.security.InvalidParameterException;
/**
* Receiver able to provide low level parsing for device-side log services.
*/
public final class LogReceiver {
private static final int ENTRY_HEADER_SIZE = 20; // 2*2 + 4*4; see LogEntry.
/**
* Represents a log entry and its raw data.
*/
public static final class LogEntry {
/*
* See //device/include/utils/logger.h
*/
/** 16bit unsigned: length of the payload. */
public int len; /* This is normally followed by a 16 bit padding */
/** pid of the process that generated this {@link LogEntry} */
public int pid;
/** tid of the process that generated this {@link LogEntry} */
public int tid;
/** Seconds since epoch. */
public int sec;
/** nanoseconds. */
public int nsec;
/** The entry's raw data. */
public byte[] data;
}
/**
* Classes which implement this interface provide a method that deals
* with {@link LogEntry} objects coming from log service through a {@link LogReceiver}.
* <p/>This interface provides two methods.
* <ul>
* <li>{@link #newEntry(com.android.ddmlib.log.LogReceiver.LogEntry)} provides a
* first level of parsing, extracting {@link LogEntry} objects out of the log service output.</li>
* <li>{@link #newData(byte[], int, int)} provides a way to receive the raw information
* coming directly from the log service.</li>
* </ul>
*/
public interface ILogListener {
/**
* Sent when a new {@link LogEntry} has been parsed by the {@link LogReceiver}.
* @param entry the new log entry.
*/
void newEntry(LogEntry entry);
/**
* Sent when new raw data is coming from the log service.
* @param data the raw data buffer.
* @param offset the offset into the buffer signaling the beginning of the new data.
* @param length the length of the new data.
*/
void newData(byte[] data, int offset, int length);
}
/** Current {@link LogEntry} being read, before sending it to the listener. */
private LogEntry mCurrentEntry;
/** Temp buffer to store partial entry headers. */
private byte[] mEntryHeaderBuffer = new byte[ENTRY_HEADER_SIZE];
/** Offset in the partial header buffer */
private int mEntryHeaderOffset = 0;
/** Offset in the partial entry data */
private int mEntryDataOffset = 0;
/** Listener waiting for receive fully read {@link LogEntry} objects */
private ILogListener mListener;
private boolean mIsCancelled = false;
/**
* Creates a {@link LogReceiver} with an {@link ILogListener}.
* <p/>
* The {@link ILogListener} will receive new log entries as they are parsed, in the form
* of {@link LogEntry} objects.
* @param listener the listener to receive new log entries.
*/
public LogReceiver(ILogListener listener) {
mListener = listener;
}
/**
* Parses new data coming from the log service.
* @param data the data buffer
* @param offset the offset into the buffer signaling the beginning of the new data.
* @param length the length of the new data.
*/
public void parseNewData(byte[] data, int offset, int length) {
// notify the listener of new raw data
if (mListener != null) {
mListener.newData(data, offset, length);
}
// loop while there is still data to be read and the receiver has not be cancelled.
while (length > 0 && !mIsCancelled) {
// first check if we have no current entry.
if (mCurrentEntry == null) {
if (mEntryHeaderOffset + length < ENTRY_HEADER_SIZE) {
// if we don't have enough data to finish the header, save
// the data we have and return
System.arraycopy(data, offset, mEntryHeaderBuffer, mEntryHeaderOffset, length);
mEntryHeaderOffset += length;
return;
} else {
// we have enough to fill the header, let's do it.
// did we store some part at the beginning of the header?
if (mEntryHeaderOffset != 0) {
// copy the rest of the entry header into the header buffer
int size = ENTRY_HEADER_SIZE - mEntryHeaderOffset;
System.arraycopy(data, offset, mEntryHeaderBuffer, mEntryHeaderOffset,
size);
// create the entry from the header buffer
mCurrentEntry = createEntry(mEntryHeaderBuffer, 0);
// since we used the whole entry header buffer, we reset the offset
mEntryHeaderOffset = 0;
// adjust current offset and remaining length to the beginning
// of the entry data
offset += size;
length -= size;
} else {
// create the entry directly from the data array
mCurrentEntry = createEntry(data, offset);
// adjust current offset and remaining length to the beginning
// of the entry data
offset += ENTRY_HEADER_SIZE;
length -= ENTRY_HEADER_SIZE;
}
}
}
// at this point, we have an entry, and offset/length have been updated to skip
// the entry header.
// if we have enough data for this entry or more, we'll need to end this entry
if (length >= mCurrentEntry.len - mEntryDataOffset) {
// compute and save the size of the data that we have to read for this entry,
// based on how much we may already have read.
int dataSize = mCurrentEntry.len - mEntryDataOffset;
// we only read what we need, and put it in the entry buffer.
System.arraycopy(data, offset, mCurrentEntry.data, mEntryDataOffset, dataSize);
// notify the listener of a new entry
if (mListener != null) {
mListener.newEntry(mCurrentEntry);
}
// reset some flags: we have read 0 data of the current entry.
// and we have no current entry being read.
mEntryDataOffset = 0;
mCurrentEntry = null;
// and update the data buffer info to the end of the current entry / start
// of the next one.
offset += dataSize;
length -= dataSize;
} else {
// we don't have enough data to fill this entry, so we store what we have
// in the entry itself.
System.arraycopy(data, offset, mCurrentEntry.data, mEntryDataOffset, length);
// save the amount read for the data.
mEntryDataOffset += length;
return;
}
}
}
/**
* Returns whether this receiver is canceling the remote service.
*/
public boolean isCancelled() {
return mIsCancelled;
}
/**
* Cancels the current remote service.
*/
public void cancel() {
mIsCancelled = true;
}
/**
* Creates a {@link LogEntry} from the array of bytes. This expects the data buffer size
* to be at least <code>offset + {@link #ENTRY_HEADER_SIZE}</code>.
* @param data the data buffer the entry is read from.
* @param offset the offset of the first byte from the buffer representing the entry.
* @return a new {@link LogEntry} or <code>null</code> if some error happened.
*/
private LogEntry createEntry(byte[] data, int offset) {
if (data.length < offset + ENTRY_HEADER_SIZE) {
throw new InvalidParameterException(
"Buffer not big enough to hold full LoggerEntry header");
}
// create the new entry and fill it.
LogEntry entry = new LogEntry();
entry.len = ArrayHelper.swapU16bitFromArray(data, offset);
// we've read only 16 bits, but since there's also a 16 bit padding,
// we can skip right over both.
offset += 4;
entry.pid = ArrayHelper.swap32bitFromArray(data, offset);
offset += 4;
entry.tid = ArrayHelper.swap32bitFromArray(data, offset);
offset += 4;
entry.sec = ArrayHelper.swap32bitFromArray(data, offset);
offset += 4;
entry.nsec = ArrayHelper.swap32bitFromArray(data, offset);
offset += 4;
// allocate the data
entry.data = new byte[entry.len];
return entry;
}
}