Cleaned up the flow stack snapshot API

This commit is contained in:
Shams Asari 2017-08-15 15:42:01 +01:00
parent 62b26bcd89
commit 3138e2b6de
9 changed files with 127 additions and 175 deletions

View File

@ -140,7 +140,7 @@ abstract class FlowLogic<out T> {
* network's event horizon time.
*/
@Suspendable
open fun send(otherParty: Party, payload: Any): Unit = stateMachine.send(otherParty, payload, flowUsedForSessions)
open fun send(otherParty: Party, payload: Any) = stateMachine.send(otherParty, payload, flowUsedForSessions)
/**
* Invokes the given subflow. This function returns once the subflow completes successfully with the result
@ -239,7 +239,7 @@ abstract class FlowLogic<out T> {
* Returns a shallow copy of the Quasar stack frames at the time of call to [flowStackSnapshot]. Use this to inspect
* what objects would be serialised at the time of call to a suspending action (e.g. send/receive).
* Note: This logic is only available during tests and is not meant to be used during the production deployment.
* Therefore the default implementationdoes nothing.
* Therefore the default implementation does nothing.
*/
@Suspendable
fun flowStackSnapshot(): FlowStackSnapshot? = stateMachine.flowStackSnapshot(this::class.java)
@ -256,7 +256,7 @@ abstract class FlowLogic<out T> {
* Therefore the default implementation does nothing.
*/
@Suspendable
fun persistFlowStackSnapshot(): Unit = stateMachine.persistFlowStackSnapshot(this::class.java)
fun persistFlowStackSnapshot() = stateMachine.persistFlowStackSnapshot(this::class.java)
////////////////////////////////////////////////////////////////////////////////////////////////////////////

View File

@ -1,69 +1,21 @@
package net.corda.core.flows
import net.corda.core.utilities.loggerFor
import java.nio.file.Path
import java.util.*
interface FlowStackSnapshotFactory {
private object Holder {
val INSTANCE: FlowStackSnapshotFactory
init {
val serviceFactory = ServiceLoader.load(FlowStackSnapshotFactory::class.java).singleOrNull()
INSTANCE = serviceFactory ?: FlowStackSnapshotDefaultFactory()
}
}
companion object {
val instance: FlowStackSnapshotFactory by lazy { Holder.INSTANCE }
}
/**
* Returns flow stack data snapshot extracted from Quasar stack.
* It is designed to be used in the debug mode of the flow execution.
* Note. This logic is only available during tests and is not meant to be used during the production deployment.
* Therefore the default implementation does nothing.
*/
fun getFlowStackSnapshot(flowClass: Class<*>): FlowStackSnapshot?
/** Stores flow stack snapshot as a json file. The stored shapshot is only partial and consists
* only data (i.e. stack traces and local variables values) relevant to the flow. It does not
* persist corda internal data (e.g. FlowStateMachine). Instead it uses [StackFrameDataToken] to indicate
* the class of the element on the stack.
* The flow stack snapshot is stored in a file located in
* {baseDir}/flowStackSnapshots/YYYY-MM-DD/{flowId}/
* where baseDir is the node running directory and flowId is the flow unique identifier generated by the platform.
* Note. This logic is only available during tests and is not meant to be used during the production deployment.
* Therefore the default implementation does nothing.
*/
fun persistAsJsonFile(flowClass: Class<*>, baseDir: Path, flowId: String): Unit
}
private class FlowStackSnapshotDefaultFactory : FlowStackSnapshotFactory {
val log = loggerFor<FlowStackSnapshotDefaultFactory>()
override fun getFlowStackSnapshot(flowClass: Class<*>): FlowStackSnapshot? {
log.warn("Flow stack snapshot are not supposed to be used in a production deployment")
return null
}
override fun persistAsJsonFile(flowClass: Class<*>, baseDir: Path, flowId: String) {
log.warn("Flow stack snapshot are not supposed to be used in a production deployment")
}
}
import java.time.Instant
/**
* Main data object representing snapshot of the flow stack, extracted from the Quasar stack.
*/
data class FlowStackSnapshot constructor(
val timestamp: Long = System.currentTimeMillis(),
val flowClass: Class<*>? = null,
val stackFrames: List<Frame> = listOf()
data class FlowStackSnapshot(
val time: Instant,
val flowClass: Class<out FlowLogic<*>>,
val stackFrames: List<Frame>
) {
data class Frame(
val stackTraceElement: StackTraceElement? = null, // This should be the call that *pushed* the frame of [objects]
val stackObjects: List<Any?> = listOf()
)
val stackTraceElement: StackTraceElement, // This should be the call that *pushed* the frame of [objects]
val stackObjects: List<Any?>
) {
override fun toString(): String = stackTraceElement.toString()
}
}
/**

View File

@ -3,11 +3,7 @@ package net.corda.core.internal
import co.paralleluniverse.fibers.Suspendable
import net.corda.core.concurrent.CordaFuture
import net.corda.core.crypto.SecureHash
import net.corda.core.flows.FlowContext
import net.corda.core.flows.FlowInitiator
import net.corda.core.flows.FlowLogic
import net.corda.core.flows.FlowStackSnapshot
import net.corda.core.flows.StateMachineRunId
import net.corda.core.flows.*
import net.corda.core.identity.Party
import net.corda.core.node.ServiceHub
import net.corda.core.transactions.SignedTransaction
@ -39,24 +35,11 @@ interface FlowStateMachine<R> {
fun recordAuditEvent(eventType: String, comment: String, extraAuditData: Map<String, String>): Unit
/**
* Returns a shallow copy of the Quasar stack frames at the time of call to [flowStackSnapshot]. Use this to inspect
* what objects would be serialised at the time of call to a suspending action (e.g. send/receive).
*/
@Suspendable
fun flowStackSnapshot(flowClass: Class<*>): FlowStackSnapshot?
fun flowStackSnapshot(flowClass: Class<out FlowLogic<*>>): FlowStackSnapshot?
/**
* Persists a shallow copy of the Quasar stack frames at the time of call to [persistFlowStackSnapshot].
* Use this to track the monitor evolution of the quasar stack values during the flow execution.
* The flow stack snapshot is stored in a file located in {baseDir}/flowStackSnapshots/YYYY-MM-DD/{flowId}/
* where baseDir is the node running directory and flowId is the flow unique identifier generated by the platform.
*
* Note: With respect to the [flowStackSnapshot], the snapshot being persisted by this method is partial,
* meaning that only flow relevant traces and local variables are persisted.
*/
@Suspendable
fun persistFlowStackSnapshot(flowClass: Class<*>): Unit
fun persistFlowStackSnapshot(flowClass: Class<out FlowLogic<*>>): Unit
val serviceHub: ServiceHub
val logger: Logger

View File

@ -0,0 +1,40 @@
package net.corda.node.services.statemachine
import net.corda.core.flows.FlowLogic
import net.corda.core.flows.FlowStackSnapshot
import net.corda.core.flows.StateMachineRunId
import net.corda.core.utilities.loggerFor
import java.nio.file.Path
import java.util.*
interface FlowStackSnapshotFactory {
private object Holder {
val INSTANCE: FlowStackSnapshotFactory
init {
val serviceFactory = ServiceLoader.load(FlowStackSnapshotFactory::class.java).singleOrNull()
INSTANCE = serviceFactory ?: DefaultFlowStackSnapshotFactory
}
}
companion object {
val instance: FlowStackSnapshotFactory by lazy { Holder.INSTANCE }
}
fun getFlowStackSnapshot(flowClass: Class<out FlowLogic<*>>): FlowStackSnapshot?
fun persistAsJsonFile(flowClass: Class<out FlowLogic<*>>, baseDir: Path, flowId: StateMachineRunId)
private object DefaultFlowStackSnapshotFactory : FlowStackSnapshotFactory {
private val log = loggerFor<DefaultFlowStackSnapshotFactory>()
override fun getFlowStackSnapshot(flowClass: Class<out FlowLogic<*>>): FlowStackSnapshot? {
log.warn("Flow stack snapshot are not supposed to be used in a production deployment")
return null
}
override fun persistAsJsonFile(flowClass: Class<out FlowLogic<*>>, baseDir: Path, flowId: StateMachineRunId) {
log.warn("Flow stack snapshot are not supposed to be used in a production deployment")
}
}
}

View File

@ -255,14 +255,12 @@ class FlowStateMachineImpl<R>(override val id: StateMachineRunId,
}
@Suspendable
override fun flowStackSnapshot(flowClass: Class<*>): FlowStackSnapshot? {
val factory = FlowStackSnapshotFactory.instance
return factory.getFlowStackSnapshot(flowClass)
override fun flowStackSnapshot(flowClass: Class<out FlowLogic<*>>): FlowStackSnapshot? {
return FlowStackSnapshotFactory.instance.getFlowStackSnapshot(flowClass)
}
override fun persistFlowStackSnapshot(flowClass: Class<*>): Unit {
val factory = FlowStackSnapshotFactory.instance
factory.persistAsJsonFile(flowClass, serviceHub.configuration.baseDirectory, id.toString())
override fun persistFlowStackSnapshot(flowClass: Class<out FlowLogic<*>>) {
FlowStackSnapshotFactory.instance.persistAsJsonFile(flowClass, serviceHub.configuration.baseDirectory, id)
}
/**

View File

@ -4,11 +4,7 @@ import com.fasterxml.jackson.dataformat.yaml.YAMLFactory
import net.corda.core.concurrent.CordaFuture
import net.corda.core.contracts.Amount
import net.corda.core.crypto.SecureHash
import net.corda.core.flows.FlowContext
import net.corda.core.flows.FlowInitiator
import net.corda.core.flows.FlowLogic
import net.corda.core.flows.FlowStackSnapshot
import net.corda.core.flows.StateMachineRunId
import net.corda.core.flows.*
import net.corda.core.identity.Party
import net.corda.core.internal.FlowStateMachine
import net.corda.core.node.ServiceHub
@ -93,21 +89,9 @@ class InteractiveShellTest {
override val id: StateMachineRunId get() = throw UnsupportedOperationException()
override val resultFuture: CordaFuture<Any?> get() = throw UnsupportedOperationException()
override val flowInitiator: FlowInitiator get() = throw UnsupportedOperationException()
override fun checkFlowPermission(permissionName: String, extraAuditData: Map<String, String>) {
// Do nothing
}
override fun recordAuditEvent(eventType: String, comment: String, extraAuditData: Map<String, String>) {
// Do nothing
}
override fun flowStackSnapshot(flowClass: Class<*>): FlowStackSnapshot? {
return null
}
override fun persistFlowStackSnapshot(flowClass: Class<*>) {
// Do nothing
}
override fun checkFlowPermission(permissionName: String, extraAuditData: Map<String, String>) = Unit
override fun recordAuditEvent(eventType: String, comment: String, extraAuditData: Map<String, String>) = Unit
override fun flowStackSnapshot(flowClass: Class<out FlowLogic<*>>): FlowStackSnapshot? = null
override fun persistFlowStackSnapshot(flowClass: Class<out FlowLogic<*>>) = Unit
}
}

View File

@ -1,21 +1,23 @@
package net.corda.testing
import co.paralleluniverse.fibers.Suspendable
import com.fasterxml.jackson.databind.ObjectMapper
import net.corda.core.flows.FlowLogic
import net.corda.core.flows.FlowStackSnapshot
import net.corda.core.flows.StartableByRPC
import net.corda.core.flows.StateMachineRunId
import net.corda.core.internal.div
import net.corda.core.internal.list
import net.corda.core.internal.read
import net.corda.core.messaging.startFlow
import net.corda.core.serialization.CordaSerializable
import net.corda.jackson.JacksonSupport
import net.corda.node.services.startFlowPermission
import net.corda.nodeapi.User
import net.corda.testing.driver.driver
import org.junit.Ignore
import org.junit.Test
import java.io.File
import java.nio.file.Path
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
import java.time.LocalDate
import kotlin.test.assertEquals
import kotlin.test.assertTrue
@ -110,15 +112,15 @@ object Constants {
* No side effect flow that stores the partial snapshot into a file, path to which is passed in the flow constructor.
*/
@StartableByRPC
class PersistingNoSideEffectFlow : FlowLogic<String>() {
class PersistingNoSideEffectFlow : FlowLogic<StateMachineRunId>() {
@Suspendable
override fun call(): String {
override fun call(): StateMachineRunId {
// Using the [Constants] object here is considered by Quasar as a side effect. Thus explicit initialization
@Suppress("UNUSED_VARIABLE")
val unusedVar = "inCall"
persist()
return stateMachine.id.toString()
return stateMachine.id
}
@Suspendable
@ -134,14 +136,14 @@ class PersistingNoSideEffectFlow : FlowLogic<String>() {
* Flow with side effects that stores the partial snapshot into a file, path to which is passed in the flow constructor.
*/
@StartableByRPC
class PersistingSideEffectFlow : FlowLogic<String>() {
class PersistingSideEffectFlow : FlowLogic<StateMachineRunId>() {
@Suspendable
override fun call(): String {
override fun call(): StateMachineRunId {
@Suppress("UNUSED_VARIABLE")
val unusedVar = Constants.IN_CALL_VALUE
persist()
return stateMachine.id.toString()
return stateMachine.id
}
@Suspendable
@ -156,16 +158,16 @@ class PersistingSideEffectFlow : FlowLogic<String>() {
* Similar to [PersistingSideEffectFlow] but aims to produce multiple snapshot files.
*/
@StartableByRPC
class MultiplePersistingSideEffectFlow(val persistCallCount: Int) : FlowLogic<String>() {
class MultiplePersistingSideEffectFlow(val persistCallCount: Int) : FlowLogic<StateMachineRunId>() {
@Suspendable
override fun call(): String {
override fun call(): StateMachineRunId {
@Suppress("UNUSED_VARIABLE")
val unusedVar = Constants.IN_CALL_VALUE
for (i in 1..persistCallCount) {
persist()
}
return stateMachine.id.toString()
return stateMachine.id
}
@Suspendable
@ -176,14 +178,19 @@ class MultiplePersistingSideEffectFlow(val persistCallCount: Int) : FlowLogic<St
}
}
fun readFlowStackSnapshotFromDir(baseDir: Path, flowId: String): FlowStackSnapshot {
val snapshotFile = File(baseDir.toFile(), "flowStackSnapshots/${LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE)}/$flowId/flowStackSnapshot.json")
return ObjectMapper().readValue(snapshotFile.inputStream(), FlowStackSnapshot::class.java)
fun readFlowStackSnapshotFromDir(baseDir: Path, flowId: StateMachineRunId): FlowStackSnapshot {
val snapshotFile = flowSnapshotDir(baseDir, flowId) / "flowStackSnapshot.json"
return snapshotFile.read {
JacksonSupport.createNonRpcMapper().readValue(it, FlowStackSnapshot::class.java)
}
}
fun countFilesInDir(baseDir: Path, flowId: String): Int {
val flowDir = File(baseDir.toFile(), "flowStackSnapshots/${LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE)}/$flowId/")
return flowDir.listFiles().size
private fun flowSnapshotDir(baseDir: Path, flowId: StateMachineRunId): Path {
return baseDir / "flowStackSnapshots" / LocalDate.now().toString() / flowId.uuid.toString()
}
fun countFilesInDir(baseDir: Path, flowId: StateMachineRunId): Int {
return flowSnapshotDir(baseDir, flowId).list { it.count().toInt() }
}
fun assertFrame(expectedMethod: String, expectedEmpty: Boolean, frame: StackSnapshotFrame) {
@ -191,11 +198,11 @@ fun assertFrame(expectedMethod: String, expectedEmpty: Boolean, frame: StackSnap
assertEquals(expectedEmpty, frame.dataTypes.isEmpty())
}
@Ignore("When running via gradle the Jacoco agent interferes with the quasar instrumentation process and violates tested" +
"criteria (specifically: extra objects are introduced to the quasar stack by th Jacoco agent). You can however " +
"run these tests via an IDE.")
class FlowStackSnapshotTest {
@Test
@Ignore("This test is skipped due to Jacoco agent interference with the quasar instrumentation process. " +
"This violates tested criteria (specifically: extra objects are introduced to the quasar stack by th Jacoco agent)")
fun `flowStackSnapshot contains full frames when methods with side effects are called`() {
driver(startNodesInProcess = true) {
val a = startNode(rpcUsers = listOf(User(Constants.USER, Constants.PASSWORD, setOf(startFlowPermission<SideEffectFlow>())))).get()
@ -211,8 +218,6 @@ class FlowStackSnapshotTest {
}
@Test
@Ignore("This test is skipped due to Jacoco agent interference with the quasar instrumentation process. " +
"This violates tested criteria (specifically extra objects are introduced to the quasar stack by th Jacoco agent)")
fun `flowStackSnapshot contains empty frames when methods with no side effects are called`() {
driver(startNodesInProcess = true) {
val a = startNode(rpcUsers = listOf(User(Constants.USER, Constants.PASSWORD, setOf(startFlowPermission<NoSideEffectFlow>())))).get()
@ -228,8 +233,6 @@ class FlowStackSnapshotTest {
}
@Test
@Ignore("This test is skipped due to Jacoco agent interference with the quasar instrumentation process. " +
"This violates tested criteria (specifically extra objects are introduced to the quasar stack by th Jacoco agent)")
fun `persistFlowStackSnapshot persists empty frames to a file when methods with no side effects are called`() {
driver(startNodesInProcess = true) {
val a = startNode(rpcUsers = listOf(User(Constants.USER, Constants.PASSWORD, setOf(startFlowPermission<PersistingNoSideEffectFlow>())))).get()
@ -247,8 +250,6 @@ class FlowStackSnapshotTest {
}
@Test
@Ignore("This test is skipped due to Jacoco agent interference with the quasar instrumentation process. " +
"This violates tested criteria (specifically extra objects are introduced to the quasar stack by th Jacoco agent)")
fun `persistFlowStackSnapshot persists multiple snapshots in different files`() {
driver(startNodesInProcess = true) {
val a = startNode(rpcUsers = listOf(User(Constants.USER, Constants.PASSWORD, setOf(startFlowPermission<MultiplePersistingSideEffectFlow>())))).get()
@ -263,8 +264,6 @@ class FlowStackSnapshotTest {
}
@Test
@Ignore("This test is skipped due to Jacoco agent interference with the quasar instrumentation process. " +
"This violates tested criteria (specifically extra objects are introduced to the quasar stack by th Jacoco agent)")
fun `persistFlowStackSnapshot stack traces are aligned with stack objects`() {
driver(startNodesInProcess = true) {
val a = startNode(rpcUsers = listOf(User(Constants.USER, Constants.PASSWORD, setOf(startFlowPermission<PersistingSideEffectFlow>())))).get()
@ -279,11 +278,11 @@ class FlowStackSnapshotTest {
it.stackObjects.forEach {
when (it) {
Constants.IN_CALL_VALUE -> {
assertEquals(PersistingSideEffectFlow::call.name, trace!!.methodName)
assertEquals(PersistingSideEffectFlow::call.name, trace.methodName)
inCallCount++
}
Constants.IN_PERSIST_VALUE -> {
assertEquals(PersistingSideEffectFlow::persist.name, trace!!.methodName)
assertEquals(PersistingSideEffectFlow::persist.name, trace.methodName)
inPersistCount++
}
}

View File

@ -5,23 +5,25 @@ import co.paralleluniverse.fibers.Instrumented
import co.paralleluniverse.fibers.Stack
import co.paralleluniverse.fibers.Suspendable
import com.fasterxml.jackson.annotation.JsonInclude
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.databind.SerializationFeature
import net.corda.core.flows.FlowLogic
import net.corda.core.flows.FlowStackSnapshot
import net.corda.core.flows.FlowStackSnapshot.Frame
import net.corda.core.flows.FlowStackSnapshotFactory
import net.corda.core.flows.StackFrameDataToken
import net.corda.core.flows.StateMachineRunId
import net.corda.core.internal.FlowStateMachine
import net.corda.core.internal.div
import net.corda.core.internal.write
import net.corda.core.serialization.SerializeAsToken
import java.io.File
import net.corda.jackson.JacksonSupport
import net.corda.node.services.statemachine.FlowStackSnapshotFactory
import java.nio.file.Path
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
import java.time.Instant
import java.time.LocalDate
class FlowStackSnapshotFactoryImpl : FlowStackSnapshotFactory {
@Suspendable
override fun getFlowStackSnapshot(flowClass: Class<*>): FlowStackSnapshot? {
override fun getFlowStackSnapshot(flowClass: Class<out FlowLogic<*>>): FlowStackSnapshot {
var snapshot: FlowStackSnapshot? = null
val stackTrace = Fiber.currentFiber().stackTrace
Fiber.parkAndSerialize { fiber, _ ->
@ -35,19 +37,19 @@ class FlowStackSnapshotFactoryImpl : FlowStackSnapshotFactory {
return temporarySnapshot!!
}
override fun persistAsJsonFile(flowClass: Class<*>, baseDir: Path, flowId: String) {
override fun persistAsJsonFile(flowClass: Class<out FlowLogic<*>>, baseDir: Path, flowId: StateMachineRunId) {
val flowStackSnapshot = getFlowStackSnapshot(flowClass)
val mapper = ObjectMapper()
mapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS)
mapper.enable(SerializationFeature.INDENT_OUTPUT)
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL)
val mapper = JacksonSupport.createNonRpcMapper().apply {
disable(SerializationFeature.FAIL_ON_EMPTY_BEANS)
setSerializationInclusion(JsonInclude.Include.NON_NULL)
}
val file = createFile(baseDir, flowId)
file.bufferedWriter().use { out ->
mapper.writeValue(out, filterOutStackDump(flowStackSnapshot!!))
file.write(createDirs = true) {
mapper.writeValue(it, filterOutStackDump(flowStackSnapshot))
}
}
private fun extractStackSnapshotFromFiber(fiber: Fiber<*>, stackTrace: List<StackTraceElement>, flowClass: Class<*>): FlowStackSnapshot {
private fun extractStackSnapshotFromFiber(fiber: Fiber<*>, stackTrace: List<StackTraceElement>, flowClass: Class<out FlowLogic<*>>): FlowStackSnapshot {
val stack = getFiberStack(fiber)
val objectStack = getObjectStack(stack).toList()
val frameOffsets = getFrameOffsets(stack)
@ -58,24 +60,25 @@ class FlowStackSnapshotFactoryImpl : FlowStackSnapshotFactory {
val relevantStackTrace = removeConstructorStackTraceElements(stackTrace).drop(1)
val stackTraceToAnnotation = relevantStackTrace.map {
val element = StackTraceElement(it.className, it.methodName, it.fileName, it.lineNumber)
element to getInstrumentedAnnotation(element)
element to element.instrumentedAnnotation
}
val frameObjectsIterator = frameObjects.listIterator()
val frames = stackTraceToAnnotation.reversed().map { (element, annotation) ->
// If annotation is null then the case indicates that this is an entry point - i.e.
// the net.corda.node.services.statemachine.FlowStateMachineImpl.run method
if (frameObjectsIterator.hasNext() && (annotation == null || !annotation.methodOptimized)) {
Frame(element, frameObjectsIterator.next())
val stackObjects = if (frameObjectsIterator.hasNext() && (annotation == null || !annotation.methodOptimized)) {
frameObjectsIterator.next()
} else {
Frame(element, listOf())
emptyList()
}
Frame(element, stackObjects)
}
return FlowStackSnapshot(flowClass = flowClass, stackFrames = frames)
return FlowStackSnapshot(Instant.now(), flowClass, frames)
}
private fun getInstrumentedAnnotation(element: StackTraceElement): Instrumented? {
Class.forName(element.className).methods.forEach {
if (it.name == element.methodName && it.isAnnotationPresent(Instrumented::class.java)) {
private val StackTraceElement.instrumentedAnnotation: Instrumented? get() {
Class.forName(className).methods.forEach {
if (it.name == methodName && it.isAnnotationPresent(Instrumented::class.java)) {
return it.getAnnotation(Instrumented::class.java)
}
}
@ -99,10 +102,10 @@ class FlowStackSnapshotFactoryImpl : FlowStackSnapshotFactory {
private fun filterOutStackDump(flowStackSnapshot: FlowStackSnapshot): FlowStackSnapshot {
val framesFilteredByStackTraceElement = flowStackSnapshot.stackFrames.filter {
!FlowStateMachine::class.java.isAssignableFrom(Class.forName(it.stackTraceElement!!.className))
!FlowStateMachine::class.java.isAssignableFrom(Class.forName(it.stackTraceElement.className))
}
val framesFilteredByObjects = framesFilteredByStackTraceElement.map {
Frame(it.stackTraceElement, it.stackObjects.map {
it.copy(stackObjects = it.stackObjects.map {
if (it != null && (it is FlowLogic<*> || it is FlowStateMachine<*> || it is Fiber<*> || it is SerializeAsToken)) {
StackFrameDataToken(it::class.java.name)
} else {
@ -110,25 +113,18 @@ class FlowStackSnapshotFactoryImpl : FlowStackSnapshotFactory {
}
})
}
return FlowStackSnapshot(flowStackSnapshot.timestamp, flowStackSnapshot.flowClass, framesFilteredByObjects)
return flowStackSnapshot.copy(stackFrames = framesFilteredByObjects)
}
private fun createFile(baseDir: Path, flowId: String): File {
val file: File
val dir = File(baseDir.toFile(), "flowStackSnapshots/${LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE)}/$flowId/")
private fun createFile(baseDir: Path, flowId: StateMachineRunId): Path {
val dir = baseDir / "flowStackSnapshots" / LocalDate.now().toString() / flowId.uuid.toString()
val index = ThreadLocalIndex.currentIndex.get()
if (index == 0) {
dir.mkdirs()
file = File(dir, "flowStackSnapshot.json")
} else {
file = File(dir, "flowStackSnapshot-$index.json")
}
val file = if (index == 0) dir / "flowStackSnapshot.json" else dir / "flowStackSnapshot-$index.json"
ThreadLocalIndex.currentIndex.set(index + 1)
return file
}
private class ThreadLocalIndex private constructor() {
companion object {
val currentIndex = object : ThreadLocal<Int>() {
override fun initialValue() = 0