线程转储文件分析 API

时至今日,我们依然需要以繁琐而单调的手动方式来分析线程转储文件:首先您需要联系 DevOps 团队,让他们把线程转储文件发过来,然后在收到文件后您还要将其上传至分析工具中,最后再用上自己的智慧对其进行分析。目前暂时还没有对线程转储文件进行主动分析的程序化方法。所以为了消除这样的麻烦,fastthread.io 打造了用于分析线程转储文件的 RESTful API。只需一行 CURL 命令,您就可以即时进行线程转储文件的分析工作。

以下是该 API 可发挥重要作用的几种情形。

用例 1:自动根本原因分析

大多数 DevOps 会使用简单的 HTTP ping 或 APM 工具来监控应用程序的运行状况。ping 确实能够很好地检测程序是否仍在活动中。APM 工具则非常适合用于告知程序 CPU 占用激增了了“x%”、内存占用增加了“y%”、响应时间延长了“z”毫秒等等。但其无法告诉您是什么导致了 CPU 占用激增,也无法让您了解是什么导致了内存占用增加和响应时间延长。如果您能够配置 Cron 作业以周期性捕获线程转储文件/GC 日志,同时调用我们的 REST API 的话,我们将能应用智能模式和机器学习算法来立即识别问题背后的根本原因。

优势 1每当出现此类生产问题时,为了尽快解决问题,DevOps 团队会对服务器资源进行回收,但不会捕获线程转储文件与 GC 日志。为了对问题进行诊断,您需要捕获问题发生时的线程转储文件与 GC 日志。在我们所提供的新策略中,您完全不用担心,因为 Cron 作业将定期捕获线程转储文件/GC 日志并调用 REST API。您的所有线程转储文件/GC 日志将存放在我们的服务器上。

优势 2:与声称仅增加不到 3% 开销的 APM 工具不同(实际上 APM 工具会带来成倍的系统开销增长),我们的策略有一个有点,那就是不会带来任何开销增加(或增加的开销几乎可以忽略)。这是因为所有的线程转储文件/GC 日志的分析工作都是由我们的服务器完成,而非您的生产服务器。

用例 2:性能测试

在进行性能测试时,您可能想要周期性获取线程转储文件/GC 日志,然后通过 API 对其进行分析。如果线程数超过阈值,或者有太多的线程处于 WAITING 状态,或者有任何线程被阻塞了很长一段时间,或者有锁没有被释放,或者频繁发生 Full GC 活动,或者 GC 停顿时间超过阈值,那么您就需要立即获得有关系统状况的可见性。这样的代码在进入生产环境前应当得到分析。在此类情形中,该 API 将会非常实用。

用例 3:持续集成

持续集成过程中强烈推荐进行性能测试。您应该使用 API 来捕获和分析线程转储文件/GC 日志。如果 API 报告了任何问题,那么 build 可能会存在问题。这样您就能在提交代码时直接获知性能降级问题,而不是在性能实验室(performance lab)或生产环境中发现问题。

如何调用线程转储文件分析 API?

线程转储文件分析 API 的调用方式非常简单:

  1. 注册即可。我们会通过邮件向您发送 API Key。这是一次性的设置过程。注意:如果您购买了带有 API 的企业版本,则不需要进行注册。API Key 将随安装说明一并提供。
  2. Post HTTP 请求至 https://api.fastthread.io/fastthread-api?apiKey={YOUR_API_KEY}
  3. HTTP 请求的主体中应包含需要分析的线程转储文件。您可在一个请求中发送 1 个线程转储文件,也可发送多个文件。
  4. HTTP 响应将以 JSON 格式回传。JSON 文件中将包含有关线程转储文件的多种重要数据。可在 JSON 响应中查找的主要元素是:“problem”。API 会应用多种智能线程转储文件分析模式,同时在检测到问题时会在“problem”元素中对其进行报告。

CURL 命令

假设您的线程转储文件位于“./my-thread-dump.txt”,那么调用 API 的 CURL 命令为:

curl -X POST --data-binary @./my-thread-dump.txt https://api.fastthread.io/fastthread-api?apiKey={YOUR_API_KEY} --header "Content-Type:text"

好像不能再简单了,对吧?

压缩

线程转储文件的大小相当大为了快速高效地进行处理,我们建议您对线程转储文件进行压缩后再发送。压缩 线程转储文件时,您需要在 HTTP Header 元素或 URL 参数中传递“Content-Encoding”元素。

如果您想将线程转储文件压缩为“zip”格式,则可使用 HTTP Header 元素来调用 API

curl -X POST --data-binary @./my-thread-dump.zip "https://api.fastthread.io/fastthread-api?apiKey={YOU_API_KEY}" --header "Content-Type:text" --header "Content-Encoding:zip"

也可在 URL 参数中使用“Content-Encoding”来调用 API

curl -X POST --data-binary @./my-thread-dump.zip "https://api.fastthread.io/fastthread-api?apiKey={YOUR_API_KEY}&Content-Encoding=zip" --header "Content-Type:text"

我们支持以下压缩格式:

zipgzxzzlzmadeflateszlz4zstdbz2tar

您可根据自身需求进行选择。无论选择何种线程转储文件压缩格式,只需传递相应的“Content-Encoding”元素即可。

从远程位置下载线程转储文件

如果您的线程转储文件存储在了远程位置——如 AWS S3 存储桶中,那么在需要对这些文件进行分析时,您就必须将文件下载到本地计算机,然后再调用 API。为了简化这一过程并实现自动化,我们在 API 中引入了“location”参数。您可向“location”位置传递 http(s) URL 位置以调用 API。此时,API 将从远程位置下载文件,然后对其中的数据进行分析并返回响应。如需了解详情请参阅本文

curl -X POST “https://api.fastthread.io/fastthread-api?apiKey={YOU_API_KEY}&location={THREAD-DUMP-HTTP-URL}” –header “Content-Type:text”

其他工具

您也可以使用任意网络服务客户端工具(如:SOAP UI、PostMan Browser Plugin 等)来调用 API

图:通过 PostMan 插件发布线程转储文件

示例响应

{
"problem": [
{
"level": "SEVERE",
"description": "8 thread are looping on same lines of code.If threads loop infinitely on the same lines of code, CPU consumption will start to spike up"
}
],
"threadsRemainingInWaitingState": [
{
"method": "java.lang.Object.wait(Native Method)",
"threadCount": 3,
"threads": "Reference Handler, Dispatcher-Thread-2, Finalizer"
},
{
"method": "sun.misc.Unsafe.park(Native Method)",
"threadCount": 2,
"threads": "New Relic RPM Connection Service, New Relic Retransformer"
}
],
"threadDumpReport": [
{
"timestamp": "2016-03-03 10:37:28",
"JVMType": " 64-Bit Server VM (23.7-b01 mixed mode)",
"threadState": [
{
"state": "RUNNABLE",
"threadCount": 28,
"threads": "Attach Listener, InvoiceGeneratedQC-A99-6, InvoiceGeneratedQC-H87-6, InvoiceGeneratedQC-B85-9, InvoiceGeneratedQC-A99-6, InvoiceGeneratedQC-H87-6, InvoiceGeneratedQC-H87-3, InvoiceGeneratedQC-H87-1, InvoiceGeneratedQC-B85-9, Service Thread, C2 CompilerThread1, C2 CompilerThread0, Signal Dispatcher, main, VM Thread, GC task thread#0 (ParallelGC), GC task thread#1 (ParallelGC), GC task thread#2 (ParallelGC), GC task thread#3 (ParallelGC), GC task thread#4 (ParallelGC), GC task thread#5 (ParallelGC), GC task thread#6 (ParallelGC), GC task thread#7 (ParallelGC), GC task thread#8 (ParallelGC), GC task thread#9 (ParallelGC), GC task thread#10 (ParallelGC), GC task thread#11 (ParallelGC), GC task thread#12 (ParallelGC)"
},
{
"state": "WAITING",
"threadCount": 6,
"threads": "Dispatcher-Thread-2, New Relic RPM Connection Service, New Relic Retransformer, Finalizer, Reference Handler, VM Periodic Task Thread"
},
{
"state": "TIMED_WAITING",
"threadCount": 4,
"threads": "GC Daemon, New Relic Sampler Service, New Relic Deadlock Detector, New Relic Thread Service"
}
],
"repeatingStackTrace": [
{
"stackTrace": "stacktrace",
"threadCount": 15,
"threads": "VM Thread, GC task thread#0 (ParallelGC), GC task thread#1 (ParallelGC), GC task thread#2 (ParallelGC), GC task thread#3 (ParallelGC), GC task thread#4 (ParallelGC), GC task thread#5 (ParallelGC), GC task thread#6 (ParallelGC), GC task thread#7 (ParallelGC), GC task thread#8 (ParallelGC), GC task thread#9 (ParallelGC), GC task thread#10 (ParallelGC), GC task thread#11 (ParallelGC), GC task thread#12 (ParallelGC), VM Periodic Task Thread"
},
{
"stackTrace": "java.lang.Thread.State: RUNNABLE
at com.buggycompany.rt.util.ItinerarySegmentProcessor.setConnectingFlight(ItinerarySegmentProcessor.java:380)
at com.buggycompany.rt.util.ItinerarySegmentProcessor.processTripType0(ItinerarySegmentProcessor.java:366)
at com.buggycompany.rt.util.ItinerarySegmentProcessor.processItineraryByTripType(ItinerarySegmentProcessor.java:254)
at com.buggycompany.rt.util.ItinerarySegmentProcessor.templateMethod(ItinerarySegmentProcessor.java:399)
...",
"threadCount": 8,
"threads": "InvoiceGeneratedQC-A99-6, InvoiceGeneratedQC-H87-6, InvoiceGeneratedQC-B85-9, InvoiceGeneratedQC-A99-6, InvoiceGeneratedQC-H87-6, InvoiceGeneratedQC-H87-3, InvoiceGeneratedQC-H87-1, InvoiceGeneratedQC-B85-9"
},
{
"stackTrace": "java.lang.Thread.State: RUNNABLE
",
"threadCount": 5,
"threads": "Attach Listener, Service Thread, C2 CompilerThread1, C2 CompilerThread0, Signal Dispatcher"
},
{
"stackTrace": "java.lang.Thread.State: TIMED_WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for   (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
at java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:226)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awaitNanos(AbstractQueuedSynchronizer.java:2082)
...",
"threadCount": 3,
"threads": "New Relic Sampler Service, New Relic Deadlock Detector, New Relic Thread Service"
},
{
"stackTrace": "java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for   (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:186)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2043)
...",
"threadCount": 2,
"threads": "New Relic RPM Connection Service, New Relic Retransformer"
},
],
"mostUsedMethod": [
{
"method": "com.buggycompany.rt.util.ItinerarySegmentProcessor.setConnectingFlight(ItinerarySegmentProcessor.java:380)",
"threadCount": 8,
"threads": "InvoiceGeneratedQC-A99-6, InvoiceGeneratedQC-H87-6, InvoiceGeneratedQC-B85-9, InvoiceGeneratedQC-A99-6, InvoiceGeneratedQC-H87-6, InvoiceGeneratedQC-H87-3, InvoiceGeneratedQC-H87-1, InvoiceGeneratedQC-B85-9"
},
{
"method": "sun.misc.Unsafe.park(Native Method)",
"threadCount": 5,
"threads": "New Relic RPM Connection Service, New Relic Sampler Service, New Relic Deadlock Detector, New Relic Thread Service, New Relic Retransformer"
},
{
"method": "java.lang.Object.wait(Native Method)",
"threadCount": 4,
"threads": "Dispatcher-Thread-2, GC Daemon, Finalizer, Reference Handler"
},
],
"threadGroup": [
{
"group": "DQBFacade",
"threadCount": 100
},
{
"group": "SectorwiseContractsFacade",
"threadCount": 100
},
{
"group": "SameDayVoidQC",
"threadCount": 100
},
{
"group": "GC task thread",
"threadCount": 13
},
{
"group": "DefaultQuartzScheduler_Worker",
"threadCount": 10
},
],
"gcThreadsCount": 14
},
{
"timestamp": "2016-03-03 10:37:38",
"JVMType": " 64-Bit Server VM (23.7-b01 mixed mode)",
"threadState": [
{
"state": "RUNNABLE",
"threadCount": 28,
"threads": "Attach Listener, InvoiceGeneratedQC-A99-6, InvoiceGeneratedQC-H87-6, InvoiceGeneratedQC-B85-9, InvoiceGeneratedQC-A99-6, InvoiceGeneratedQC-H87-6, InvoiceGeneratedQC-H87-3, InvoiceGeneratedQC-H87-1, InvoiceGeneratedQC-B85-9, Service Thread, C2 CompilerThread1, C2 CompilerThread0, Signal Dispatcher, main, VM Thread, GC task thread#0 (ParallelGC), GC task thread#1 (ParallelGC), GC task thread#2 (ParallelGC), GC task thread#3 (ParallelGC), GC task thread#4 (ParallelGC), GC task thread#5 (ParallelGC), GC task thread#6 (ParallelGC), GC task thread#7 (ParallelGC), GC task thread#8 (ParallelGC), GC task thread#9 (ParallelGC), GC task thread#10 (ParallelGC), GC task thread#11 (ParallelGC), GC task thread#12 (ParallelGC)"
},
{
"state": "WAITING",
"threadCount": 6,
"threads": "Dispatcher-Thread-2, New Relic RPM Connection Service, New Relic Retransformer, Finalizer, Reference Handler, VM Periodic Task Thread"
},
{
"state": "TIMED_WAITING",
"threadCount": 4,
"threads": "GC Daemon, New Relic Sampler Service, New Relic Deadlock Detector, New Relic Thread Service"
}
],
"repeatingStackTrace": [
{
"stackTrace": "stacktrace",
"threadCount": 15,
"threads": "VM Thread, GC task thread#0 (ParallelGC), GC task thread#1 (ParallelGC), GC task thread#2 (ParallelGC), GC task thread#3 (ParallelGC), GC task thread#4 (ParallelGC), GC task thread#5 (ParallelGC), GC task thread#6 (ParallelGC), GC task thread#7 (ParallelGC), GC task thread#8 (ParallelGC), GC task thread#9 (ParallelGC), GC task thread#10 (ParallelGC), GC task thread#11 (ParallelGC), GC task thread#12 (ParallelGC), VM Periodic Task Thread"
},
{
"stackTrace": "java.lang.Thread.State: RUNNABLE
at com.buggycompany.rt.util.ItinerarySegmentProcessor.setConnectingFlight(ItinerarySegmentProcessor.java:380)
at com.buggycompany.rt.util.ItinerarySegmentProcessor.processTripType0(ItinerarySegmentProcessor.java:366)
at com.buggycompany.rt.util.ItinerarySegmentProcessor.processItineraryByTripType(ItinerarySegmentProcessor.java:254)
at com.buggycompany.rt.util.ItinerarySegmentProcessor.templateMethod(ItinerarySegmentProcessor.java:399)
...",
"threadCount": 8,
"threads": "InvoiceGeneratedQC-A99-6, InvoiceGeneratedQC-H87-6, InvoiceGeneratedQC-B85-9, InvoiceGeneratedQC-A99-6, InvoiceGeneratedQC-H87-6, InvoiceGeneratedQC-H87-3, InvoiceGeneratedQC-H87-1, InvoiceGeneratedQC-B85-9"
},
{
"stackTrace": "java.lang.Thread.State: RUNNABLE
",
"threadCount": 5,
"threads": "Attach Listener, Service Thread, C2 CompilerThread1, C2 CompilerThread0, Signal Dispatcher"
},
{
"stackTrace": "java.lang.Thread.State: TIMED_WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for   (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
at java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:226)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awaitNanos(AbstractQueuedSynchronizer.java:2082)
...",
"threadCount": 3,
"threads": "New Relic Sampler Service, New Relic Deadlock Detector, New Relic Thread Service"
},
{
"stackTrace": "java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for   (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:186)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2043)
...",
"threadCount": 2,
"threads": "New Relic RPM Connection Service, New Relic Retransformer"
},
],
"mostUsedMethod": [
{
"method": "com.buggycompany.rt.util.ItinerarySegmentProcessor.setConnectingFlight(ItinerarySegmentProcessor.java:380)",
"threadCount": 8,
"threads": "InvoiceGeneratedQC-A99-6, InvoiceGeneratedQC-H87-6, InvoiceGeneratedQC-B85-9, InvoiceGeneratedQC-A99-6, InvoiceGeneratedQC-H87-6, InvoiceGeneratedQC-H87-3, InvoiceGeneratedQC-H87-1, InvoiceGeneratedQC-B85-9"
},
{
"method": "sun.misc.Unsafe.park(Native Method)",
"threadCount": 5,
"threads": "New Relic RPM Connection Service, New Relic Sampler Service, New Relic Deadlock Detector, New Relic Thread Service, New Relic Retransformer"
},
{
"method": "java.lang.Object.wait(Native Method)",
"threadCount": 4,
"threads": "Dispatcher-Thread-2, GC Daemon, Finalizer, Reference Handler"
},
],
"gcThreadsCount": 14
}
],
"responseId": "20161025113858_4"
}

JSON 响应元素

元素数据类型描述
responseIdUUID为各个响应生成的唯一事务 ID。可用于调试和诊断
fault 如果未能处理请求,则将返回此元素
>reason 无法处理请求的原因
>details 有关故障的更多详情
graphURL 可在此位置找到线程转储文件的图表可视化。
problem数组如果在线程转储文件中检测的任何问题,则会返回此元素。如果检测到多个问题,那么会返回多个‘problem’元素。
>level 其值可能为: SEVERE WARNING
>description 有关问题的详细描述
threadsRemainingInRunnableState数组如果请求中发送了多个线程转储文件,则此元素将显示所有文件中处于 RUNNABLE 状态的线程。如需了解为什么跨线程转储文件追踪 RUNNABLE 线程是一项重要的工作,请查看 Thread Mill 线程转储文件分析模式
>threadCount 处于 RUNNABLE 状态的线程数量
>threads数组线程名称
>method 线程处于 RUNNABLE 状态时的方法
threadsStuckInBlockedState数组如果请求中发送了多个线程转储文件,则此元素将显示所有文件中处于 BLOCKED 状态的线程。如需了解为什么跨线程转储文件追踪 BLOCKED 线程是一项重要的工作,请查看动脉硬化线程转储文件分析模式
>threadCount 处于 BLOCKED 状态的线程数量
>threads数组线程名称
>method 线程处于 BLOCKED 状态时的方法
threadsRemainingInWaitingState数组如果请求中发送了多个线程转储文件,则此元素将显示所有文件中处于 WAITING 状态的线程。
>threadCount 处于 WAITING 状态的线程数量
>threads数组线程名称
>method 线程处于 WAITING 状态时的方法
threadDumpReport数组如果在请求中发送了 n 个线程转储文件,那么该元素中将出现 n 个时间数。一个‘threadDumpReport’元素对应请求中发送的一个线程转储文件
>timestamp 捕获线程转储文件的时间戳
>JVMType 有关捕获线程转储文件的 JVM 的信息。JVM 版本、客户端或服务器模式、32 或 64 位版本
>threadCount整数线程转储文件中的线程总数
>problem数组如果在线程转储文件中检测的任何问题,则会返回此元素。如果检测到多个问题,那么会返回多个‘problem’元素。
>>level 其值可能为: SEVERE WARNING
>>description 有关问题的详细描述
>threadState数组此元素将显示按线程状态分组的线程。
>>threadCount 处于特定状态的线程数
>>threads数组处于此特定状态的线程数
>>state 可能为以下值之一: NEW RUNNABLE BLOCKED WAITING TIMED_WAITING TERMINATED IN_NATIVE UNABLE_DETERMINE
>repeatingStackTrace数组具有相同栈追踪信息的线程将在此处报告。如需了解研究具有相同栈追踪信息的线程的重要性,请参考 RSI 线程转储文件分析模式
>>threadCount 具有此特定栈追踪信息的线程数
>>threads数组处于此特定状态的线程数
>threadGroup数组此元素显示了程序中出现的线程组
>>threadCount 处于特定状态的线程组数
>>group 线程组名称
>repeatingStackTrace数组具有相同栈追踪信息的线程将在此处报告。如需了解研究具有相同栈追踪信息的线程的重要性,请参考 RSI 线程转储文件分析模式
>>threadCount 具有此特定栈追踪信息的线程数
>>threads数组处于此特定状态的线程数
>>stackTrace 线程栈追踪信息
>mostUsedMethod 处于相同方法的线程将在此处报告。如需了解研究处于相同方法的线程的重要性,请参考条条大路通罗马线程转储文件分析模式
>>threadCount 处于此特定方法上的线程数
>>threads数组处于此特定方法上的线程数
>>method 方法名称
>blockedBlocker数组此元素展示了被锁传递卡住的线程。如需进一步了解请参考交通拥堵线程转储文件分析模式。
>>blockedThreadCount 由该锁传递卡住的线程数
>>lock 线程卡住的锁
>gcThreadsCount JVM 中垃圾回收线程的总数。过多 GC 线程可能会影响 JVM 的性能。如需进一步了解,可查看多清道夫线程转储文件分析模式

标记报告

假设您需要分析数百 JVM 的线程转储文件。此时可能需要使用主机名、日期等信息来对报告进行标记,这样就能清楚地知道其中的对应关系了。可通过在 API 端点中传递请求参数“fileName”来实现这一功能,如:

https://api.fastthread.io/fastthread-api?apiKey={YOUR_API_KEY}&fileName={YOUR_TAG}

响应 JSON 中包含“webReport”元素。该元素中包含一个 URL。在浏览器中访问该 URL 可看到通过 API 解析的线程转储文件可视化报告。现在,您将可在浏览器报告中的右上角看到通过 API 传递的标签。

比如您在 API 端点中传递了 fileName=Production_Env_Server_1,那么报告中就会如实展示。

图:通过 API 传递了标签的 fastThread 报告

XML 响应

默认情况下 API 响应会以 JSON 格式发送。但是如果您想获取 XML 格式的相应,则可在 HTTP Header 元素中传递“accept”参数,值为“xml”即可。

Curl –X POST --data-binary @./my-thread-dump.hprof https://api.fastthread.io/fastthread-api?apiKey={API_KEY_SENT_IN_EMAIL} –-header "accept:xml"

也可在 URL 参数中使用“accept”元素来调用 API

Curl –X POST --data-binary @./my-thread-dump.hprof https://api.fastthread.io/fastthread-api?apiKey={YOUR_API_KEY}&accept=xml

API 响应关键元素

fastThread 的 JSON API 可用于应用程序监测、CI/CD 管线代码质量分析以及多种其他目的。API 响应中包含丰富的信息集(即:元素)。在本文中,我们着重介绍了 API 响应中的一些关键元素。如果这些元素的值超过或低于特定阈值,你可能就需要考虑给出警报或中断程序。

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

Blog at WordPress.com.

Up ↑

%d bloggers like this: