Skip to content

Commit

Permalink
add LogAllThreads
Browse files Browse the repository at this point in the history
  • Loading branch information
michaeloffner committed Dec 16, 2024
1 parent 4058dda commit fd773c1
Show file tree
Hide file tree
Showing 2 changed files with 141 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@

package lucee.runtime.functions.system;

import java.io.IOException;

import lucee.commons.io.SystemUtil;
import lucee.commons.io.res.Resource;
import lucee.commons.io.res.util.ResourceUtil;
import lucee.runtime.PageContext;
import lucee.runtime.engine.Controler;
import lucee.runtime.exp.FunctionException;
import lucee.runtime.exp.PageException;
import lucee.runtime.ext.function.Function;
import lucee.runtime.op.Caster;

public final class LogAllThreads implements Function {

private static final long serialVersionUID = -1922482127354478506L;

public static String call(PageContext pc, String path) throws PageException {
return call(pc, path, 0, 10000);
}

public static String call(PageContext pc, String path, Number interval) throws PageException {
return call(pc, path, interval, 10000);
}

public static String call(PageContext pc, String path, Number interval, Number duration) throws PageException {
Resource res = ResourceUtil.toResourceNotExisting(pc, path);
if (!res.getParentResource().isDirectory())
throw new FunctionException(pc, "LogAllThreads", 1, "path", "the directory [" + res.getParent() + "] for your log file [" + path + "] does not exist.");

int tmp = Caster.toIntValue(interval);
if (tmp < 0) tmp = 10;
final long interv = tmp;

long ltmp = Caster.toLongValue(duration);
if (ltmp < 1) ltmp = 10000;
final long dur = ltmp;

long start = System.currentTimeMillis();
// Create a new thread to run the task
Thread thread = new Thread(() -> {
while (true) {
try {
if ((start + dur) < System.currentTimeMillis()) {
break;
}
// Call the dumpThreadPositions method
Controler.dumpThreadPositions(res);

// Pause for the specified interval
if (interv > 0) SystemUtil.sleep(interv);
}
catch (IOException e) {
SystemUtil.sleep(1000);
}
}
});

// Start the thread
thread.start();
return null;
}
}
76 changes: 76 additions & 0 deletions core/src/main/java/resource/fld/core-base.fld
Original file line number Diff line number Diff line change
Expand Up @@ -9534,6 +9534,82 @@ The following things are considered to be empty:
</return>
</function>

<!-- LogAllThreads -->
<function>
<name>LogAllThreads</name>
<class>lucee.runtime.functions.system.LogAllThreads</class>
<keywords>system,log,debugging,threads,performance,analysis</keywords>
<description>
Creates detailed thread stack trace logs in JSONL format for performance analysis and debugging.
This function captures stack traces from all running threads at specified intervals for a given duration.
It executes asynchronously, returning immediately after starting the logging process, making it ideal
for analyzing specific code segments by initiating logging just before the target code execution.

The output format is JSONL (JSON Lines), where each line represents a separate JSON object containing:
- Timestamp offset in milliseconds from 1/1/1970 00:00:00 UTC (Unix 0)
- Complete stack trace of each thread's current location

This data can be used for:
- Performance bottleneck identification
- Thread behavior analysis
- Deadlock detection
- Resource usage patterns
</description>

<argument>
<name>path</name>
<type>string</type>
<required>true</required>
<description>
Full file path where the log will be written. The file should have a '.jsonl' extension
for proper identification as a JSON Lines format file. The function will create the file
if it doesn't exist, or append to it if it does.

Example: "/var/log/lucee/thread_analysis.jsonl"
</description>
</argument>

<argument>
<name>interval</name>
<type>number</type>
<required>false</required>
<default>0</default>
<description>
The time interval (in milliseconds) between stack trace captures. Lower values provide
more detailed analysis but generate larger log files and may impact performance.

Recommended ranges:
- 1-10ms: Very detailed analysis, higher overhead
- 10-100ms: Balanced detail and performance
- 100ms+: Lower detail, minimal performance impact
</description>
</argument>

<argument>
<name>duration</name>
<type>number</type>
<required>false</required>
<default>10000</default>
<description>
Total duration (in milliseconds) for which the function will collect thread data.
After this period, logging automatically stops.

Common durations:
- 1000-5000ms: Quick snapshots
- 10000ms: Standard analysis period
- 30000ms+: Extended analysis for complex operations
</description>
</argument>

<return>
<type>void</type>
<description>
Returns immediately without waiting for logging completion. The logging process
continues in the background for the specified duration.
</description>
</return>
</function>

<!-- lTrim -->
<function>
<name>lTrim</name>
Expand Down

0 comments on commit fd773c1

Please sign in to comment.