diff --git a/data/completion.yaml b/data/completion.yaml index b6c05c1..fee2a04 100644 --- a/data/completion.yaml +++ b/data/completion.yaml @@ -50,6 +50,11 @@ complete: opts: - name: javagcjoin= desc: If true it will return an array with each processed line. + - name: in=javathread + desc: A Java thread dump text file format or java pid + opts: + - name: javathreadpid= + desc: The PID of the Java process to connect to -requires Java SDK- - name: in=jmx desc: Uses Java JMX to retrieve data from another Java process opts: diff --git a/data/usage.json b/data/usage.json index 1a7a809..a932f69 100644 --- a/data/usage.json +++ b/data/usage.json @@ -162,6 +162,10 @@ "Input type": "javagc", "Description": "The Java GC log lines text format" }, + { + "Input type": "javathread", + "Description": "The Java Thread stack dump lines text format" + }, { "Input type": "jmx", "Description": "Uses Java JMX to retrieve data from another Java process" @@ -703,6 +707,13 @@ "Description": "If true it will return an array with each processed line." } ], + [ + { + "Option": "javathreadpid", + "Type": "Number", + "Description": "Optional you can provider the local java process pid to try to get the thread stack trace (*)" + } + ], [ { "Option": "jmxpid", diff --git a/src/docs/USAGE.md b/src/docs/USAGE.md index 1004305..4bb964c 100644 --- a/src/docs/USAGE.md +++ b/src/docs/USAGE.md @@ -72,6 +72,7 @@ List of data input types that can be auto-detected (through the file extension o | ini | INI/Properties format | | javas | Tries to list java processes running locally (javainception=true to include itself) | | javagc | The Java GC log lines text format | +| javathread | The Java Thread stack dump lines text format | | jmx | Uses Java JMX to retrieve data from another Java process | | json | A JSON format (auto-detected) | | jsonschema | Given a JSON schema format tries to generate sample data for it | @@ -257,6 +258,20 @@ List of options to use when _in=javagc_: --- +### 🧾 JavaThread input options + +List of options to use when _in=javathread_: + +| Option | Type | Description | +|--------|------|-------------| +| javathreadpid | Number | Optional you can provider the local java process pid to try to get the thread stack trace (*) | + +> (*) This requires running openaf/oafp with a Java JDK. Keep in mind that it will interrupt the target application to dump the necessary data. + +> You can extract the input text data by executing ```kill -3 pid``` + +--- + ### 🧾 JMX input options List of options to use when _in=jmx_: diff --git a/src/include/inputFns.js b/src/include/inputFns.js index 52bcb83..8e1adfa 100644 --- a/src/include/inputFns.js +++ b/src/include/inputFns.js @@ -508,6 +508,110 @@ var _inputFns = new Map([ } _$o(_r, options) }], + ["javathread", (_res, options) => { + var lines + _showTmpMsg() + if (isDef(params.javathreadpid)) { + ow.loadJava() + try { + lines = ow.java.jcmd(params.javathreadpid, "Thread.print") + lines = lines.split("\n").filter(l => l.startsWith("\"")) + } catch(e) { + _exit(-1, "Error getting Java thread dump: " + e.message) + } + } else { + if (isString(_res)) { + lines = _res.split("\n") + } else { + _exit(-1, "javathreads is only supported with a raw input or javathreadpid=.") + } + } + + // TODO: remove after OpenAF stable > 20240212 + var fnFromTimeAbbr = aStr => { + _$(aStr, "aStr").isString().$_() + + var ars = aStr.trim().match(/[\d\.]+[a-zA-Z]+/g), res = 0; + if (!isArray(ars) || ars.length === 0) return parseFloat(aStr); + for (var i in ars) { + var ar = ars[i].match(/(\d+(?:\.\d+)?)\s*([a-zA-Z]+)/); + if (isArray(ar) && ar.length > 0) { + var v = Number(ar[1]) + var u = String(ar[2]) + + var _u = { + "ms": 1, + "s": 1000, + "m": 60 * 1000, + "h": 60 * 60 * 1000, + "d": 24 * 60 * 60 * 1000, + "w": 7 * 24 * 60 * 60 * 1000, + "M": 30 * 24 * 60 * 60 * 1000, + "y": 365 * 24 * 60 * 60 * 1000 + } + if (isDef(_u[u])) { + res += v * _u[u] + } else { + res += v + } + } + } + + return res + } + + var fnJavaTrans = (v, tA) => { + if (v === null) return "" + if (v === undefined) return "" + if (isBoolean(v)) return Boolean(v) + if (isNumber(v)) return Number(v) + if (tA) return fnFromTimeAbbr(String(v)) + return String(v) + } + ow.loadFormat() + + var _r = [] + lines.forEach(line => { + if (line.startsWith("\"")) { + var pt = java.util.regex.Pattern.compile("^\\\"(?[^\"]+)\\\"" + + "(?:\\s+#(?\\d+))?" + + "(?:\\s+\\[(?\\d+)\\])?" + + "(?:\\s+(?daemon))?" + + "(?:\\s+prio=(?\\d+))?" + + "\\s+os_prio=(?\\d+)" + + "(?:\\s+cpu=(?[0-9.]+ms))?" + + "(?:\\s+elapsed=(?[0-9.]+s))?" + + "(?:\\s+tid=(?0x[a-fA-F0-9]+))?" + + "(?:\\s+nid=(?0x[a-fA-F0-9]+|\\d+|\\S+))?" + + "(?:\\s+(?.*?))?" + + "(?:\\s+\\[(?
[^\\]]+)\\])?" + + "\\s*$") + + var mt = pt.matcher(line) + if (mt.find()) { + var m = { + threadGroup: fnJavaTrans(mt.group("threadName")).replace(/[^a-zA-z]?\d+$/, ""), + threadName : fnJavaTrans(mt.group("threadName")), + threadId : fnJavaTrans(mt.group("threadId")), + threadIndex: fnJavaTrans(mt.group("threadIndex")), + daemon : fnJavaTrans(mt.group("daemon")), + prio : fnJavaTrans(mt.group("prio")), + osPrio : fnJavaTrans(mt.group("osPrio")), + cpu_ms : fnJavaTrans(mt.group("cpu"), true), + elapsed_ms : fnJavaTrans(mt.group("elapsed"), true), + tid : fnJavaTrans(mt.group("tid")), + nid : fnJavaTrans(mt.group("nid")), + state : fnJavaTrans(mt.group("state")), + address : fnJavaTrans(mt.group("address")) + } + _r.push(m) + } else { + _r.push({ error: "Could not parse line: " + line }) + } + } + }) + _$o(_r, options) + }], ["javagc", (_res, options) => { if (!isBoolean(params.javagcjoin)) params.javagcjoin = toBoolean(_$(params.javagcjoin, "javagcjoin").isString().default(__)) diff --git a/src/include/transformFns.js b/src/include/transformFns.js index 70d48c7..c5d4641 100644 --- a/src/include/transformFns.js +++ b/src/include/transformFns.js @@ -196,6 +196,7 @@ var _transformFns = { }, "correcttypes" : _r => { if (toBoolean(params.correcttypes) && isObject(_r)) { + ow.loadFormat() traverse(_r, (aK, aV, aP, aO) => { switch(descType(aV)) { case "number": if (isString(aV)) aO[aK] = Number(aV); break @@ -203,7 +204,11 @@ var _transformFns = { // String boolean if (aV.trim().toLowerCase() == "true" || aV.trim().toLowerCase() == "false") { aO[aK] = toBoolean(aV); break } // String ISO date - if (aV.trim().match(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$/)) { aO[aK] = new Date(aV); break } + if (isDef(ow.format.fromISODate)) { + if (aV.trim().match(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\d+Z$/)) { aO[aK] = ow.format.fromISODate(aV); break } + } else { + if (aV.trim().match(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$/)) { aO[aK] = new Date(aV); break } + } // String date if (aV.trim().match(/^\d{4}-\d{2}-\d{2}$/)) { aO[aK] = new Date(aV); break } // String time with seconds @@ -557,7 +562,7 @@ var _transformFns = { let _lst = params.field2date.split(",").map(r => r.trim()) traverse(_r, (aK, aV, aP, aO) => { if (_lst.indexOf(aP.length > 0 && !aP.startsWith("[") ? aP.substring(1) + "." + aK : aK) >= 0 && isNumber(aV) && aV > 0) { - try { aO[aK] = new Date(aV) } catch(e) {} + try { aO[aK] = ow.format.fromISODate(aV) } catch(e) {} } }) return _r