Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,13 @@ private static void initAll(Instrumentation instrumentation, Properties properti
CollectionBreakpointInstrumentor.init(properties, instrumentation);
SpilledVariablesTransformer.init(instrumentation);
TailCallContinuationTransformer.init(instrumentation);
LogCaptureTransformer.init(properties, instrumentation);

boolean logCaptureEnabled = Boolean.getBoolean("debugger.agent.enable.log.capture");
LogCaptureStorage.init(properties, logCaptureEnabled);
if (logCaptureEnabled) {
LogCaptureTransformer.init(instrumentation);
}

InstrumentationBreakpointTransformer.init(properties, instrumentation);
}

Expand Down
132 changes: 98 additions & 34 deletions src/main/java/com/intellij/rt/debugger/agent/LogCaptureStorage.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,23 +29,32 @@ protected Boolean initialValue() {

private static boolean BATCHING_ENABLED;
private static int MAX_BATCHED_EVENTS_COUNT;
private static boolean STDOUT_CAPTURE_ENABLED;

// It's used by the debugger.
private static final AtomicLong EVENT_COUNTER = new AtomicLong();
static final AtomicLong EVENT_COUNTER = new AtomicLong();

// It contains events that are waiting to be flushed.
// New ones could be added concurrently.
// They can also be flushed concurrently, leading to sending the same events multiple times.
// It's ok and is handed by the debugger using IDs.
// Event is removed from the queue only after it's guaranteed to be received by the debugger.
private static final ConcurrentLinkedQueue<Event> EVENTS = new ConcurrentLinkedQueue<>();
static final ConcurrentLinkedQueue<Event> EVENTS = new ConcurrentLinkedQueue<>();

static final AtomicLong LAST_FLUSHED_EVENT_ID = new AtomicLong(-1);
static final AtomicLong LAST_LOGGING_BREAKPOINT_EVENT_ID = new AtomicLong(-1);

static class Event {
public static final byte STD_OUTPUT_TYPE = 0;
public static final byte LOGGING_BREAKPOINT_TYPE = 1;

private static class Event {
public final long id;
public final byte type;
public final byte[] payload;

public Event(long id, byte[] payload) {
public Event(long id, byte type, byte[] payload) {
this.id = id;
this.type = type;
this.payload = payload;
}
}
Expand All @@ -55,12 +64,17 @@ public Event(long id, byte[] payload) {

private static final int MAX_STACK_DEPTH = 100; // It should be enough, we usually need only a few first frames.

public static boolean init(Properties properties) {

private static boolean batchingSchedulerStarted;
static ArrayList<String> outputWrittenDumpForTests = null;

public static boolean init(Properties properties, boolean logCaptureEnabled) {
ENABLED = true;
STDOUT_CAPTURE_ENABLED = logCaptureEnabled;
BATCHING_ENABLED = Boolean.parseBoolean(properties.getProperty(BATCHING_ENABLED_PROPERTY, "true"));
if (BATCHING_ENABLED) {
MAX_BATCHED_EVENTS_COUNT = Integer.parseInt(
properties.getProperty(BATCHING_MAX_EVENTS_PROPERTY, "100"));
MAX_BATCHED_EVENTS_COUNT = Integer.parseInt(properties.getProperty(BATCHING_MAX_EVENTS_PROPERTY, "100"));
if (BATCHING_ENABLED && !batchingSchedulerStarted) {
batchingSchedulerStarted = true;

final Runnable flushAction = new Runnable() {
@Override
Expand All @@ -87,6 +101,15 @@ public void run() {
return true;
}

private static long createNextEventId(int eventType) {
if (!BATCHING_ENABLED) return -1;
long id = EVENT_COUNTER.getAndIncrement();
if (eventType == Event.LOGGING_BREAKPOINT_TYPE) {
setIfGreater(LAST_LOGGING_BREAKPOINT_EVENT_ID, id);
}
return id;
}

public static void capture(FileDescriptor fd, byte[] bytes) {
capture(fd, bytes, 0, bytes.length);
}
Expand All @@ -98,44 +121,49 @@ public static void capture(FileDescriptor fd, byte[] bytes, int off, int len) {
if (fd != FD_OUT && fd != FD_ERR) return;
if (len == 0) return;

List<StackTraceElement> regularStack = CaptureStorage.getCurrentStackTraceWithoutAgentFrames();
List<StackTraceElement> capturedStack = CaptureStorage.getCurrentCapturedStack(MAX_STACK_DEPTH - regularStack.size());

byte[] captured = encodeMessageAndStacks(bytes, off, len, regularStack, capturedStack);
captureEvent(captured);
// Avoid logging breakpoint's output reorder with stdout.
if (hasBatchedLoggingBreakpointEvents()) {
flushBatchedData();
}
if (!STDOUT_CAPTURE_ENABLED) return;

long id = createNextEventId(Event.STD_OUTPUT_TYPE);
ByteArrayOutputStream bas = new ByteArrayOutputStream(); // no need to close it
try (DataOutputStream dos = new DataOutputStream(bas)) {
encodeMessageAndCurrentStacks(dos, bytes, off, len);
}
byte[] payload = bas.toByteArray();
captureEvent(new Event(id, Event.STD_OUTPUT_TYPE, payload));
} catch (Throwable e) {
handleException(e);
} finally {
CAPTURING.set(false);
}
}

private static void captureEvent(byte[] captured) throws IOException {
private static boolean hasBatchedLoggingBreakpointEvents() {
return LAST_LOGGING_BREAKPOINT_EVENT_ID.get() > LAST_FLUSHED_EVENT_ID.get();
}

private static void captureEvent(Event event) throws IOException {
if (BATCHING_ENABLED) {
long id = EVENT_COUNTER.getAndIncrement();
Event event = new Event(id, captured);
EVENTS.add(event);
flushBatchedDataIfMoreThan(MAX_BATCHED_EVENTS_COUNT);
} else {
packAndSend(Collections.singletonList(new Event(-1, captured)));
packAndSend(Collections.singletonList(event));
}
}

private static byte[] encodeMessageAndStacks(byte[] bytes, int off, int len,
List<StackTraceElement> regularStack,
List<StackTraceElement> capturedStack) throws IOException {
ByteArrayOutputStream bas = new ByteArrayOutputStream(); // no need to close it
try (DataOutputStream dos = new DataOutputStream(bas)) {
dos.writeInt(len);
dos.write(bytes, off, len);
CaptureStorage.writeAsyncStackTraceToStream(regularStack, dos);
if (capturedStack != null) {
CaptureStorage.writeAsyncStackTraceElementToStream(CaptureStorage.ASYNC_STACK_ELEMENT, dos);
CaptureStorage.writeAsyncStackTraceToStream(capturedStack, dos);
}
private static void encodeMessageAndCurrentStacks(DataOutputStream dos, byte[] bytes, int off, int len) throws IOException {
List<StackTraceElement> regularStack = CaptureStorage.getCurrentStackTraceWithoutAgentFrames();
List<StackTraceElement> capturedStack = CaptureStorage.getCurrentCapturedStack(MAX_STACK_DEPTH - regularStack.size());
dos.writeInt(len);
dos.write(bytes, off, len);
CaptureStorage.writeAsyncStackTraceToStream(regularStack, dos);
if (capturedStack != null) {
CaptureStorage.writeAsyncStackTraceElementToStream(CaptureStorage.ASYNC_STACK_ELEMENT, dos);
CaptureStorage.writeAsyncStackTraceToStream(capturedStack, dos);
}
return bas.toByteArray();
}

private static void handleException(Throwable e) {
Expand All @@ -155,6 +183,8 @@ private static void flushBatchedDataIfMoreThan(int eventsCountLimit) throws IOEx
if (eventsSnapshot.size() <= eventsCountLimit) return;
packAndSend(eventsSnapshot);
EVENTS.removeAll(new HashSet<>(eventsSnapshot));
long lastFlushedId = findMaxId(eventsSnapshot);
setIfGreater(LAST_FLUSHED_EVENT_ID, lastFlushedId);
}

private static void packAndSend(Collection<Event> events) throws IOException {
Expand All @@ -166,6 +196,7 @@ private static void packAndSend(Collection<Event> events) throws IOException {
dos.writeInt(events.size());
for (Event event : events) {
dos.writeLong(event.id);
dos.writeByte(event.type);
byte[] bytes = event.payload;
dos.writeInt(bytes.length);
dos.write(bytes);
Expand All @@ -176,8 +207,6 @@ private static void packAndSend(Collection<Event> events) throws IOException {
outputWritten(packed);
}

static ArrayList<String> outputWrittenDumpForTests = null;

// It's used by the debugger.
@SuppressWarnings("unused")
private static void outputWritten(String captured) {
Expand All @@ -186,9 +215,44 @@ private static void outputWritten(String captured) {
}
}

// It's used by the debugger and instrumentation.
@SuppressWarnings("unused")
// It's used in instrumentation.
public static void loggingBreakpointHit(int instrumentationId, String message) {
if (!ENABLED || CAPTURING.get()) return;
CAPTURING.set(true);
try {
long id = createNextEventId(Event.LOGGING_BREAKPOINT_TYPE);
byte[] messageBytes = message.getBytes(StandardCharsets.UTF_8);
ByteArrayOutputStream bas = new ByteArrayOutputStream(); // no need to close it
try (DataOutputStream dos = new DataOutputStream(bas)) {
dos.writeInt(instrumentationId);
encodeMessageAndCurrentStacks(dos, messageBytes, 0, messageBytes.length);
}
byte[] payload = bas.toByteArray();
captureEvent(new Event(id, Event.LOGGING_BREAKPOINT_TYPE, payload));
} catch (Throwable e) {
handleException(e);
} finally {
CAPTURING.set(false);
}
}

private static long findMaxId(ArrayList<Event> events) {
long lastFlushedId = -1;
for (int i = events.size() - 1; i >= 0; i--) {
long id = events.get(i).id;
if (id > lastFlushedId) {
lastFlushedId = id;
}
}
return lastFlushedId;
}

private static void setIfGreater(AtomicLong maxValue, long newValue) {
while (true) {
long current = maxValue.get();
if (current >= newValue || maxValue.compareAndSet(current, newValue)) {
break;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,11 @@
import java.lang.instrument.Instrumentation;
import java.lang.instrument.UnmodifiableClassException;
import java.security.ProtectionDomain;
import java.util.Properties;

import static com.intellij.rt.debugger.agent.CaptureAgent.getInternalClsName;

class LogCaptureTransformer implements ClassFileTransformer {
public static void init(Properties properties, Instrumentation instrumentation) {
if (!Boolean.getBoolean("debugger.agent.enable.log.capture")) return;

if (!LogCaptureStorage.init(properties)) return;

public static void init(Instrumentation instrumentation) {
instrumentation.addTransformer(new LogCaptureTransformer(), true);
for (Class<?> aClass : instrumentation.getAllLoadedClasses()) {
if (CLASS_NAME.equals(getInternalClsName(aClass))) {
Expand Down
Loading
Loading