mirror of
https://github.com/hanzoai/tasks.git
synced 2026-10-04 02:04:03 +00:00
[CHASM] Update tdbg describe command to dump a decoded CHASM tree from mutable state (#8895)
## What changed?
- Renames `tdbg workflow` to `tdbg execution` (maintaining 'workflow' and 'w' as aliases).
- Renames `tdbg execution describe --workflow-id` to `tdbg execution describe --business-id` (keeping 'wid' and 'workflow-id' as aliases)
- `tdbg execution describe` now dumps a CHASM tree's nodes, if one is present.
## Why?
- Prior to this, I'd have to first dump mutable state, then MANUALLY
parse out every field I cared about, manually look up its type, decode
it, unmarshal it, print it, ad infinitum..
Sample output for:
`./tdbg -n "features" e describe --business-id "lina-test" --archetype
"scheduler.scheduler"`:
```json
CHASM Tree Nodes:
{
"": {
"metadata": {
"initial_versioned_transition": {
"transition_count": 1
},
"last_update_versioned_transition": {
"transition_count": 80
},
"Attributes": {
"ComponentAttributes": {
"type_id": 403648407
}
}
},
"decodedData": {
"schedule": {
"spec": {
"interval": [
{
"interval": "1s",
"phase": "0s"
}
]
},
"action": {
"startWorkflow": {
"workflowId": "lina-testwf",
"workflowType": {
"name": "BasicScheduleWorkflow"
},
"taskQueue": {
"name": " features-schedule",
"kind": "TASK_QUEUE_KIND_NORMAL"
}
}
},
"policies": {
"overlapPolicy": "SCHEDULE_OVERLAP_POLICY_SKIP",
"catchupWindow": "31536000s"
},
"state": {
"notes": "asdf"
}
},
"info": {
"actionCount": "2",
"overlapSkipped": "27",
"recentActions": [
{
"scheduleTime": "2025-12-22T20:04:45Z",
"actualTime": "2025-12-22T20:05:01.012942Z",
"startWorkflowResult": {
"workflowId": "lina-testwf-2025-12-22T20:04:45Z",
"runId": "019b47aa-49d1-792f-9c5c-2f876a6144d7"
},
"startWorkflowStatus": "WORKFLOW_EXECUTION_STATUS_FAILED"
},
{
"scheduleTime": "2025-12-22T20:06:55Z",
"actualTime": "2025-12-22T20:06:56.014349Z",
"startWorkflowResult": {
"workflowId": "lina-testwf-2025-12-22T20:06:55Z",
"runId": "019b47ac-0b0a-7d69-9fab-154ee95247cf"
},
"startWorkflowStatus": "WORKFLOW_EXECUTION_STATUS_FAILED"
}
],
"createTime": "2025-12-22T20:00:10.481808Z",
"updateTime": "2025-12-22T20:11:39.307879Z"
},
"namespace": "features",
"namespaceId": "ff4f1d27-b3b2-4030-a7ec-c309c00566a2",
"scheduleId": "lina-test",
"conflictToken": "22"
},
"nodeType": "component",
"componentFQN": "scheduler.scheduler"
},
"Backfillers": {
"metadata": {
"initial_versioned_transition": {
"transition_count": 6
},
"last_update_versioned_transition": {
"transition_count": 6
},
"Attributes": {
"CollectionAttributes": {}
}
},
"rawData": {},
"nodeType": "collection"
},
"Backfillers#0f20cd1f-948b-4064-8275-409c2f06dba5": {
"metadata": {
"initial_versioned_transition": {
"transition_count": 6
},
"last_update_versioned_transition": {
"transition_count": 6
},
"Attributes": {
"ComponentAttributes": {
"type_id": 571775395
}
}
},
"decodedData": {
"triggerRequest": {
"scheduledTime": "2025-12-22T20:00:38.650636Z"
},
"backfillId": "0f20cd1f-948b-4064-8275-409c2f06dba5",
"lastProcessedTime": "2025-12-22T20:00:38.651414Z"
},
"nodeType": "component",
"componentFQN": "scheduler.backfiller"
},
"Backfillers#15b9dcc6-1575-4ada-a77b-42672d720287": {
"metadata": {
"initial_versioned_transition": {
"transition_count": 9
},
"last_update_versioned_transition": {
"transition_count": 9
},
"Attributes": {
"ComponentAttributes": {
"type_id": 571775395
}
}
},
"decodedData": {
"triggerRequest": {
"scheduledTime": "2025-12-22T20:02:18.389188Z"
},
"backfillId": "15b9dcc6-1575-4ada-a77b-42672d720287",
"lastProcessedTime": "2025-12-22T20:02:18.390459Z"
},
"nodeType": "component",
"componentFQN": "scheduler.backfiller"
},
"Backfillers#78bd223c-7818-4d92-a318-6c1cde0e17b3": {
"metadata": {
"initial_versioned_transition": {
"transition_count": 7
},
"last_update_versioned_transition": {
"transition_count": 7
},
"Attributes": {
"ComponentAttributes": {
"type_id": 571775395
}
}
},
"decodedData": {
"triggerRequest": {
"scheduledTime": "2025-12-22T20:00:59.368696Z"
},
"backfillId": "78bd223c-7818-4d92-a318-6c1cde0e17b3",
"lastProcessedTime": "2025-12-22T20:00:59.369805Z"
},
"nodeType": "component",
"componentFQN": "scheduler.backfiller"
},
"Backfillers#f0c472b5-1cc2-4a50-ab1b-e14fe593eaa5": {
"metadata": {
"initial_versioned_transition": {
"transition_count": 77
},
"last_update_versioned_transition": {
"transition_count": 77
},
"Attributes": {
"ComponentAttributes": {
"type_id": 571775395
}
}
},
"decodedData": {
"triggerRequest": {
"scheduledTime": "2025-12-22T20:11:12.469511Z"
},
"backfillId": "f0c472b5-1cc2-4a50-ab1b-e14fe593eaa5",
"lastProcessedTime": "2025-12-22T20:11:12.470441Z"
},
"nodeType": "component",
"componentFQN": "scheduler.backfiller"
},
"Generator": {
"metadata": {
"initial_versioned_transition": {
"transition_count": 1
},
"last_update_versioned_transition": {
"transition_count": 78
},
"Attributes": {
"ComponentAttributes": {
"type_id": 545946520
}
}
},
"decodedData": {
"lastProcessedTime": "2025-12-22T20:07:10.003099Z",
"futureActionTimes": [
"2025-12-22T20:07:15Z",
"2025-12-22T20:07:20Z",
"2025-12-22T20:07:25Z",
"2025-12-22T20:07:30Z",
"2025-12-22T20:07:35Z",
"2025-12-22T20:07:40Z",
"2025-12-22T20:07:45Z",
"2025-12-22T20:07:50Z",
"2025-12-22T20:07:55Z",
"2025-12-22T20:08:00Z"
]
},
"nodeType": "component",
"componentFQN": "scheduler.generator"
},
"Invoker": {
"metadata": {
"initial_versioned_transition": {
"transition_count": 1
},
"last_update_versioned_transition": {
"transition_count": 70
},
"Attributes": {
"ComponentAttributes": {
"type_id": 2628858954
}
}
},
"decodedData": {
"lastProcessedTime": "2025-12-22T20:07:06.004086Z",
"requestIdToWorkflowId": {
"sched-auto-ff4f1d27-b3b2-4030-a7ec-c309c00566a2-lina-test-11-1766433890000-1766433890000": "lina-testwf-2025-12-22T20:04:50Z",
"sched-auto-ff4f1d27-b3b2-4030-a7ec-c309c00566a2-lina-test-11-1766433895000-1766433895000": "lina-testwf-2025-12-22T20:04:55Z",
"sched-auto-ff4f1d27-b3b2-4030-a7ec-c309c00566a2-lina-test-11-1766433900000-1766433900000": "lina-testwf-2025-12-22T20:05:00Z",
"sched-auto-ff4f1d27-b3b2-4030-a7ec-c309c00566a2-lina-test-11-1766433905000-1766433905000": "lina-testwf-2025-12-22T20:05:05Z",
"sched-auto-ff4f1d27-b3b2-4030-a7ec-c309c00566a2-lina-test-11-1766433910000-1766433910000": "lina-testwf-2025-12-22T20:05:10Z",
"sched-auto-ff4f1d27-b3b2-4030-a7ec-c309c00566a2-lina-test-11-1766433915000-1766433915000": "lina-testwf-2025-12-22T20:05:15Z",
"sched-auto-ff4f1d27-b3b2-4030-a7ec-c309c00566a2-lina-test-11-1766433920000-1766433920000": "lina-testwf-2025-12-22T20:05:20Z",
"sched-auto-ff4f1d27-b3b2-4030-a7ec-c309c00566a2-lina-test-11-1766433925000-1766433925000": "lina-testwf-2025-12-22T20:05:25Z",
"sched-auto-ff4f1d27-b3b2-4030-a7ec-c309c00566a2-lina-test-11-1766433930000-1766433930000": "lina-testwf-2025-12-22T20:05:30Z",
"sched-auto-ff4f1d27-b3b2-4030-a7ec-c309c00566a2-lina-test-11-1766433935000-1766433935000": "lina-testwf-2025-12-22T20:05:35Z",
"sched-auto-ff4f1d27-b3b2-4030-a7ec-c309c00566a2-lina-test-11-1766433940000-1766433940000": "lina-testwf-2025-12-22T20:05:40Z",
"sched-auto-ff4f1d27-b3b2-4030-a7ec-c309c00566a2-lina-test-11-1766433945000-1766433945000": "lina-testwf-2025-12-22T20:05:45Z",
"sched-auto-ff4f1d27-b3b2-4030-a7ec-c309c00566a2-lina-test-11-1766433950000-1766433950000": "lina-testwf-2025-12-22T20:05:50Z",
"sched-auto-ff4f1d27-b3b2-4030-a7ec-c309c00566a2-lina-test-11-1766433955000-1766433955000": "lina-testwf-2025-12-22T20:05:55Z",
"sched-auto-ff4f1d27-b3b2-4030-a7ec-c309c00566a2-lina-test-11-1766433960000-1766433960000": "lina-testwf-2025-12-22T20:06:00Z",
"sched-auto-ff4f1d27-b3b2-4030-a7ec-c309c00566a2-lina-test-11-1766433965000-1766433965000": "lina-testwf-2025-12-22T20:06:05Z",
"sched-auto-ff4f1d27-b3b2-4030-a7ec-c309c00566a2-lina-test-11-1766433970000-1766433970000": "lina-testwf-2025-12-22T20:06:10Z",
"sched-auto-ff4f1d27-b3b2-4030-a7ec-c309c00566a2-lina-test-11-1766433975000-1766433975000": "lina-testwf-2025-12-22T20:06:15Z",
"sched-auto-ff4f1d27-b3b2-4030-a7ec-c309c00566a2-lina-test-11-1766433980000-1766433980000": "lina-testwf-2025-12-22T20:06:20Z",
"sched-auto-ff4f1d27-b3b2-4030-a7ec-c309c00566a2-lina-test-11-1766433985000-1766433985000": "lina-testwf-2025-12-22T20:06:25Z",
"sched-auto-ff4f1d27-b3b2-4030-a7ec-c309c00566a2-lina-test-11-1766433990000-1766433990000": "lina-testwf-2025-12-22T20:06:30Z",
"sched-auto-ff4f1d27-b3b2-4030-a7ec-c309c00566a2-lina-test-11-1766433995000-1766433995000": "lina-testwf-2025-12-22T20:06:35Z",
"sched-auto-ff4f1d27-b3b2-4030-a7ec-c309c00566a2-lina-test-11-1766434000000-1766434000000": "lina-testwf-2025-12-22T20:06:40Z",
"sched-auto-ff4f1d27-b3b2-4030-a7ec-c309c00566a2-lina-test-11-1766434005000-1766434005000": "lina-testwf-2025-12-22T20:06:45Z",
"sched-auto-ff4f1d27-b3b2-4030-a7ec-c309c00566a2-lina-test-11-1766434010000-1766434010000": "lina-testwf-2025-12-22T20:06:50Z",
"sched-auto-ff4f1d27-b3b2-4030-a7ec-c309c00566a2-lina-test-11-1766434020000-1766434020000": "lina-testwf-2025-12-22T20:07:00Z",
"sched-auto-ff4f1d27-b3b2-4030-a7ec-c309c00566a2-lina-test-11-1766434025000-1766434025000": "lina-testwf-2025-12-22T20:07:05Z"
}
},
"nodeType": "component",
"componentFQN": "scheduler.invoker"
},
"LastCompletionResult": {
"metadata": {
"initial_versioned_transition": {
"transition_count": 70
},
"last_update_versioned_transition": {
"transition_count": 70
},
"Attributes": {
"DataAttributes": {}
}
},
"rawData": {
"encoding_type": 1,
"data": "EhgKFG9wZXJhdGlvbiB0ZXJtaW5hdGVkQgA="
},
"nodeType": "data"
},
"Visibility": {
"metadata": {
"initial_versioned_transition": {
"transition_count": 1
},
"last_update_versioned_transition": {
"transition_count": 80
},
"Attributes": {
"ComponentAttributes": {
"type_id": 2662156547,
"side_effect_tasks": [
{
"type_id": 4193112626,
"scheduled_time": {
"seconds": -62135596800
},
"data": {
"encoding_type": 1,
"data": "CDU="
},
"versioned_transition": {
"transition_count": 80
},
"versioned_transition_offset": 1,
"physical_task_status": 1
}
]
}
}
},
"rawData": {
"encoding_type": 1,
"data": "CDU="
},
"nodeType": "component (stateless)",
"componentFQN": "core.vis",
"sideEffectTasks": [
{
"typeId": 4193112626,
"taskFQN": "core.visTask",
"scheduledTime": "0001-01-01T00:00:00Z",
"decodedData": {
"transitionCount": "53"
},
"versionedTransition": {
"transition_count": 80
},
"physicalTaskStatus": 1
}
]
},
"Visibility$Memo": {
"metadata": {
"initial_versioned_transition": {
"transition_count": 1
},
"last_update_versioned_transition": {
"transition_count": 1
},
"Attributes": {
"DataAttributes": {}
}
},
"rawData": {
"encoding_type": 1
},
"nodeType": "data"
},
"Visibility$SA": {
"metadata": {
"initial_versioned_transition": {
"transition_count": 1
},
"last_update_versioned_transition": {
"transition_count": 1
},
"Attributes": {
"DataAttributes": {}
}
},
"rawData": {
"encoding_type": 1
},
"nodeType": "data"
}
}
Execution Info:
{
"namespaceId": "ff4f1d27-b3b2-4030-a7ec-c309c00566a2",
"workflowId": "lina-test",
"lastRunningClock": "1049018",
"startTime": "2025-12-22T20:00:10.477729Z",
"lastUpdateTime": "2025-12-22T20:11:39.308118Z",
"workflowTaskTimeout": "0s",
"workflowTaskAttempt": 1,
"workflowTaskRequestId": "emptyUuid",
"autoResetPoints": {},
"versionHistories": {
"histories": [
{}
]
},
"executionStats": {},
"stateTransitionCount": "80",
"executionTime": "2025-12-22T20:00:10.477729Z",
"transitionHistory": [
{
"transitionCount": "80"
}
],
"taskGenerationShardClockTimestamp": "1048731",
"visibilityLastUpdateVersionedTransition": {
"transitionCount": "1"
},
"subStateMachineTombstoneBatches": [
{
"versionedTransition": {
"transitionCount": "1"
},
"stateMachineTombstones": [
{
"chasmNodePath": "Backfillers"
}
]
}
]
}
```
## How did you test it?
- [x] built
- [x] run locally and tested manually
- [ ] covered by existing tests
- [ ] added new unit test(s)
- [ ] added new functional test(s)
This commit is contained in:
@@ -29,10 +29,6 @@ func (r *Registry) TaskOf(taskGoType reflect.Type) (*RegistrableTask, bool) {
|
||||
return r.taskOf(taskGoType)
|
||||
}
|
||||
|
||||
func (r *Registry) TaskByID(id uint32) (*RegistrableTask, bool) {
|
||||
return r.taskByID(id)
|
||||
}
|
||||
|
||||
func (rc RegistrableComponent) FqType() string {
|
||||
return rc.fqType()
|
||||
}
|
||||
|
||||
@@ -175,6 +175,11 @@ func (rc *RegistrableComponent) hasBusinessIDAlias() bool {
|
||||
return ok
|
||||
}
|
||||
|
||||
// GoType returns the reflect.Type of the component's Go struct.
|
||||
func (rc *RegistrableComponent) GoType() reflect.Type {
|
||||
return rc.goType
|
||||
}
|
||||
|
||||
// fqType returns the fully qualified name of the component, which is a combination of
|
||||
// the library name and the component type. This is used to uniquely identify
|
||||
// the component in the registry.
|
||||
|
||||
@@ -94,6 +94,11 @@ func (rt *RegistrableTask) registerToLibrary(
|
||||
return fqn, rt.taskTypeID, nil
|
||||
}
|
||||
|
||||
// GoType returns the reflect.Type of the task's Go struct.
|
||||
func (rt *RegistrableTask) GoType() reflect.Type {
|
||||
return rt.goType
|
||||
}
|
||||
|
||||
// fqType returns the fully qualified name of the task, which is a combination of
|
||||
// the library name and the task type. This is used to uniquely identify
|
||||
// the task in the registry.
|
||||
|
||||
@@ -112,6 +112,11 @@ func (r *Registry) ComponentIDFor(componentInstance any) (uint32, bool) {
|
||||
return rc.componentID, true
|
||||
}
|
||||
|
||||
// TaskByID returns the registrable task for a given task type ID.
|
||||
func (r *Registry) TaskByID(id uint32) (*RegistrableTask, bool) {
|
||||
return r.taskByID(id)
|
||||
}
|
||||
|
||||
// TaskFqnByID converts task type ID to fully qualified task type name.
|
||||
// This method should only be used by CHASM framework internal code,
|
||||
// NOT CHASM library developers.
|
||||
|
||||
@@ -0,0 +1,213 @@
|
||||
package tdbg
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
commonpb "go.temporal.io/api/common/v1"
|
||||
persistencespb "go.temporal.io/server/api/persistence/v1"
|
||||
"go.temporal.io/server/chasm"
|
||||
"go.temporal.io/server/common/codec"
|
||||
"go.temporal.io/server/common/persistence/serialization"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
type decodedTask struct {
|
||||
TypeID uint32 `json:"typeId"`
|
||||
TaskFQN string `json:"taskFQN,omitempty"`
|
||||
Destination string `json:"destination,omitempty"`
|
||||
ScheduledTime string `json:"scheduledTime,omitempty"`
|
||||
DecodedData json.RawMessage `json:"decodedData,omitempty"`
|
||||
RawData *commonpb.DataBlob `json:"rawData,omitempty"`
|
||||
VersionedTransition *persistencespb.VersionedTransition `json:"versionedTransition,omitempty"`
|
||||
PhysicalTaskStatus int32 `json:"physicalTaskStatus,omitempty"`
|
||||
}
|
||||
|
||||
type decodedChasmNode struct {
|
||||
Metadata *persistencespb.ChasmNodeMetadata `json:"metadata"`
|
||||
DecodedData json.RawMessage `json:"decodedData,omitempty"`
|
||||
RawData *commonpb.DataBlob `json:"rawData,omitempty"`
|
||||
NodeType string `json:"nodeType"`
|
||||
ComponentFQN string `json:"componentFQN,omitempty"`
|
||||
SideEffectTasks []*decodedTask `json:"sideEffectTasks,omitempty"`
|
||||
PureTasks []*decodedTask `json:"pureTasks,omitempty"`
|
||||
}
|
||||
|
||||
func getNodeType(metadata *persistencespb.ChasmNodeMetadata) string {
|
||||
switch {
|
||||
case metadata.GetComponentAttributes() != nil:
|
||||
return "component"
|
||||
case metadata.GetCollectionAttributes() != nil:
|
||||
return "collection"
|
||||
case metadata.GetDataAttributes() != nil:
|
||||
return "data"
|
||||
case metadata.GetPointerAttributes() != nil:
|
||||
return "pointer"
|
||||
default:
|
||||
return "unknown"
|
||||
}
|
||||
}
|
||||
|
||||
func decodeTask(
|
||||
task *persistencespb.ChasmComponentAttributes_Task,
|
||||
registry *chasm.Registry,
|
||||
) (*decodedTask, error) {
|
||||
typeID := task.GetTypeId()
|
||||
fqn, _ := registry.TaskFqnByID(typeID)
|
||||
|
||||
var scheduledTime string
|
||||
if ts := task.GetScheduledTime(); ts != nil {
|
||||
scheduledTime = ts.AsTime().UTC().Format(defaultDateTimeFormat)
|
||||
}
|
||||
|
||||
decoded := &decodedTask{
|
||||
TypeID: typeID,
|
||||
TaskFQN: fqn,
|
||||
Destination: task.GetDestination(),
|
||||
ScheduledTime: scheduledTime,
|
||||
VersionedTransition: task.GetVersionedTransition(),
|
||||
PhysicalTaskStatus: task.GetPhysicalTaskStatus(),
|
||||
}
|
||||
|
||||
rt, ok := registry.TaskByID(typeID)
|
||||
if !ok {
|
||||
decoded.RawData = task.GetData()
|
||||
return decoded, nil
|
||||
}
|
||||
|
||||
goType := rt.GoType()
|
||||
if goType == nil {
|
||||
decoded.RawData = task.GetData()
|
||||
return decoded, nil
|
||||
}
|
||||
|
||||
messageValue := reflect.New(goType.Elem())
|
||||
message, ok := messageValue.Interface().(proto.Message)
|
||||
if !ok {
|
||||
decoded.RawData = task.GetData()
|
||||
return decoded, nil
|
||||
}
|
||||
|
||||
dataBlob := task.GetData()
|
||||
if dataBlob != nil && len(dataBlob.GetData()) > 0 {
|
||||
message = message.ProtoReflect().New().Interface()
|
||||
if err := serialization.Decode(dataBlob, message); err != nil {
|
||||
decoded.RawData = dataBlob
|
||||
return decoded, nil
|
||||
}
|
||||
}
|
||||
|
||||
jsonBytes, err := codec.NewJSONPBEncoder().Encode(message)
|
||||
if err != nil {
|
||||
decoded.RawData = dataBlob
|
||||
return decoded, nil
|
||||
}
|
||||
|
||||
decoded.DecodedData = json.RawMessage(jsonBytes)
|
||||
return decoded, nil
|
||||
}
|
||||
|
||||
func decodeNode(node *persistencespb.ChasmNode, registry *chasm.Registry) (*decodedChasmNode, error) {
|
||||
metadata := node.GetMetadata()
|
||||
componentAttr := metadata.GetComponentAttributes()
|
||||
|
||||
if componentAttr == nil {
|
||||
return &decodedChasmNode{
|
||||
Metadata: metadata,
|
||||
RawData: node.GetData(),
|
||||
NodeType: getNodeType(metadata),
|
||||
}, nil
|
||||
}
|
||||
|
||||
typeID := componentAttr.GetTypeId()
|
||||
rc, ok := registry.ComponentByID(typeID)
|
||||
if !ok {
|
||||
return &decodedChasmNode{
|
||||
Metadata: metadata,
|
||||
RawData: node.GetData(),
|
||||
NodeType: "component (unknown)",
|
||||
}, nil
|
||||
}
|
||||
|
||||
result := &decodedChasmNode{
|
||||
Metadata: metadata,
|
||||
NodeType: getNodeType(metadata),
|
||||
}
|
||||
|
||||
goType := rc.GoType()
|
||||
if goType == nil {
|
||||
return nil, errors.New("component has no Go type")
|
||||
}
|
||||
|
||||
messageValue := reflect.New(goType.Elem())
|
||||
message, ok := messageValue.Interface().(proto.Message)
|
||||
if !ok {
|
||||
result.RawData = node.GetData()
|
||||
result.NodeType = "component (stateless)"
|
||||
} else {
|
||||
dataBlob := node.GetData()
|
||||
if dataBlob != nil && len(dataBlob.GetData()) > 0 {
|
||||
message = message.ProtoReflect().New().Interface()
|
||||
if err := serialization.Decode(dataBlob, message); err != nil {
|
||||
result.RawData = dataBlob
|
||||
result.NodeType = "component (decode error)"
|
||||
} else {
|
||||
jsonBytes, err := codec.NewJSONPBEncoder().Encode(message)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to encode to JSON: %w", err)
|
||||
}
|
||||
result.DecodedData = json.RawMessage(jsonBytes)
|
||||
result.NodeType = "component"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fqn, _ := registry.ComponentFqnByID(typeID)
|
||||
result.ComponentFQN = fqn
|
||||
|
||||
sideEffectTasks, err := decodeTasks(componentAttr.GetSideEffectTasks(), registry)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decode side effect task: %w", err)
|
||||
}
|
||||
result.SideEffectTasks = sideEffectTasks
|
||||
|
||||
pureTasks, err := decodeTasks(componentAttr.GetPureTasks(), registry)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decode pure task: %w", err)
|
||||
}
|
||||
result.PureTasks = pureTasks
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func decodeTasks(
|
||||
tasks []*persistencespb.ChasmComponentAttributes_Task,
|
||||
registry *chasm.Registry,
|
||||
) ([]*decodedTask, error) {
|
||||
result := make([]*decodedTask, len(tasks))
|
||||
for i, task := range tasks {
|
||||
decodedTask, err := decodeTask(task, registry)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result[i] = decodedTask
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func decodeChasmNodes(
|
||||
chasmNodes map[string]*persistencespb.ChasmNode,
|
||||
registry *chasm.Registry,
|
||||
) (map[string]*decodedChasmNode, error) {
|
||||
decoded := make(map[string]*decodedChasmNode, len(chasmNodes))
|
||||
for path, node := range chasmNodes {
|
||||
decodedNode, err := decodeNode(node, registry)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decode node at path %q: %w", path, err)
|
||||
}
|
||||
decoded[path] = decodedNode
|
||||
}
|
||||
return decoded, nil
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package tdbg
|
||||
|
||||
import (
|
||||
"go.temporal.io/server/chasm"
|
||||
chasmscheduler "go.temporal.io/server/chasm/lib/scheduler"
|
||||
chasmtests "go.temporal.io/server/chasm/lib/tests"
|
||||
chasmworkflow "go.temporal.io/server/chasm/lib/workflow"
|
||||
"go.temporal.io/server/common/log"
|
||||
)
|
||||
|
||||
func newChasmRegistry(logger log.Logger) (*chasm.Registry, error) {
|
||||
registry := chasm.NewRegistry(logger)
|
||||
|
||||
if err := registry.Register(&chasm.CoreLibrary{}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := registry.Register(chasmworkflow.NewLibrary()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := registry.Register(chasmscheduler.NewLibrary(nil, nil, nil, nil, nil, nil)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := registry.Register(chasmtests.Library); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Note: Activity and Callback libraries are not included because their constructors
|
||||
// are unexported. Add them if/when they're needed.
|
||||
|
||||
return registry, nil
|
||||
}
|
||||
+55
-16
@@ -19,6 +19,7 @@ import (
|
||||
"go.temporal.io/server/chasm"
|
||||
"go.temporal.io/server/common"
|
||||
"go.temporal.io/server/common/codec"
|
||||
"go.temporal.io/server/common/log"
|
||||
"go.temporal.io/server/common/namespace"
|
||||
"go.temporal.io/server/common/persistence/serialization"
|
||||
"go.temporal.io/server/common/persistence/versionhistory"
|
||||
@@ -241,23 +242,37 @@ func AdminImportWorkflow(c *cli.Context, clientFactory ClientFactory) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// AdminDescribeWorkflow describe a new workflow execution for admin
|
||||
func AdminDescribeWorkflow(c *cli.Context, clientFactory ClientFactory) error {
|
||||
// AdminDescribeExecution describes a Temporal execution (CHASM tree or workflow).
|
||||
func AdminDescribeExecution(c *cli.Context, clientFactory ClientFactory) error {
|
||||
resp, err := describeMutableState(c, clientFactory)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if resp == nil {
|
||||
return errors.New("no mutable state returned")
|
||||
}
|
||||
|
||||
if resp != nil {
|
||||
// nolint:errcheck // assuming that write will succeed.
|
||||
fmt.Fprintln(c.App.Writer, color.GreenString("Cache mutable state:"))
|
||||
if resp.GetCacheMutableState() != nil {
|
||||
prettyPrintJSONObject(c, resp.GetCacheMutableState())
|
||||
}
|
||||
// nolint:errcheck // assuming that write will succeed.
|
||||
fmt.Fprintln(c.App.Writer, color.GreenString("Cache mutable state:"))
|
||||
if resp.GetCacheMutableState() != nil {
|
||||
prettyPrintJSONObject(c, resp.GetCacheMutableState())
|
||||
}
|
||||
if resp.GetDatabaseMutableState() != nil {
|
||||
// nolint:errcheck // assuming that write will succeed.
|
||||
fmt.Fprintln(c.App.Writer, color.GreenString("Database mutable state:"))
|
||||
prettyPrintJSONObject(c, resp.GetDatabaseMutableState())
|
||||
}
|
||||
|
||||
// CHASM executions also print their tree.
|
||||
if len(resp.GetDatabaseMutableState().GetChasmNodes()) > 0 {
|
||||
err := dumpChasmTree(resp, c)
|
||||
if err != nil {
|
||||
// nolint:errcheck // assuming that write will succeed.
|
||||
fmt.Fprintln(c.App.Writer, color.RedString("Unable to dump CHASM tree:"), err)
|
||||
}
|
||||
}
|
||||
|
||||
if resp.GetDatabaseMutableState() != nil {
|
||||
// nolint:errcheck // assuming that write will succeed.
|
||||
fmt.Fprintln(c.App.Writer, color.GreenString("Current branch token:"))
|
||||
versionHistories := resp.GetDatabaseMutableState().GetExecutionInfo().GetVersionHistories()
|
||||
@@ -276,12 +291,36 @@ func AdminDescribeWorkflow(c *cli.Context, clientFactory ClientFactory) error {
|
||||
prettyPrintJSONObject(c, ¤tBranchToken)
|
||||
}
|
||||
}
|
||||
|
||||
// nolint:errcheck // assuming that write will succeed.
|
||||
fmt.Fprintf(c.App.Writer, "History service address: %s\n", resp.GetHistoryAddr())
|
||||
// nolint:errcheck // assuming that write will succeed.
|
||||
fmt.Fprintf(c.App.Writer, "Shard Id: %s\n", resp.GetShardId())
|
||||
}
|
||||
|
||||
// nolint:errcheck // assuming that write will succeed.
|
||||
fmt.Fprintf(c.App.Writer, "History service address: %s\n", resp.GetHistoryAddr())
|
||||
// nolint:errcheck // assuming that write will succeed.
|
||||
fmt.Fprintf(c.App.Writer, "Shard Id: %s\n", resp.GetShardId())
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func dumpChasmTree(resp *adminservice.DescribeMutableStateResponse, c *cli.Context) error {
|
||||
chasmNodes := resp.GetDatabaseMutableState().GetChasmNodes()
|
||||
if len(chasmNodes) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
logger := log.NewNoopLogger()
|
||||
registry, err := newChasmRegistry(logger)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create CHASM registry: %w", err)
|
||||
}
|
||||
|
||||
decodedNodes, err := decodeChasmNodes(chasmNodes, registry)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to decode CHASM nodes: %w", err)
|
||||
}
|
||||
|
||||
fmt.Fprintln(c.App.Writer, color.GreenString("CHASM Tree Nodes:")) // nolint:errcheck // assuming that write will succeed.
|
||||
prettyPrintJSONObject(c, decodedNodes)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -292,7 +331,7 @@ func describeMutableState(c *cli.Context, clientFactory ClientFactory) (*adminse
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wid, err := getRequiredOption(c, FlagWorkflowID)
|
||||
bid, err := getRequiredOption(c, FlagBusinessID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -304,13 +343,13 @@ func describeMutableState(c *cli.Context, clientFactory ClientFactory) (*adminse
|
||||
resp, err := adminClient.DescribeMutableState(ctx, &adminservice.DescribeMutableStateRequest{
|
||||
Namespace: namespace,
|
||||
Execution: &commonpb.WorkflowExecution{
|
||||
WorkflowId: wid,
|
||||
WorkflowId: bid,
|
||||
RunId: rid,
|
||||
},
|
||||
Archetype: getArchetypeWithDefault(c, chasm.WorkflowArchetype),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to get Workflow Mutable State: %s", err)
|
||||
return nil, fmt.Errorf("unable to get Mutable State: %s", err)
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
@@ -12,6 +12,8 @@ var (
|
||||
FlagWorkflowIDAlias = []string{"wid"}
|
||||
FlagRunID = "run-id"
|
||||
FlagRunIDAlias = []string{"rid"}
|
||||
FlagBusinessID = "business-id"
|
||||
FlagBusinessIDAlias = []string{"bid", FlagWorkflowID, FlagWorkflowIDAlias[0]}
|
||||
FlagArchetype = "archetype"
|
||||
FlagNumberOfShards = "number-of-shards"
|
||||
FlagMinEventID = "min-event-id"
|
||||
|
||||
+11
-11
@@ -23,10 +23,10 @@ func getCommands(
|
||||
) []*cli.Command {
|
||||
return []*cli.Command{
|
||||
{
|
||||
Name: "workflow",
|
||||
Aliases: []string{"w"},
|
||||
Usage: "Run admin operation on workflow",
|
||||
Subcommands: newAdminWorkflowCommands(clientFactory, prompterFactory),
|
||||
Name: "execution",
|
||||
Aliases: []string{"e", "w", "workflow"},
|
||||
Usage: "Run admin operation on an execution (workflow)",
|
||||
Subcommands: newAdminExecutionCommands(clientFactory, prompterFactory),
|
||||
},
|
||||
{
|
||||
Name: "shard",
|
||||
@@ -72,7 +72,7 @@ func getCommands(
|
||||
}
|
||||
}
|
||||
|
||||
func newAdminWorkflowCommands(clientFactory ClientFactory, prompterFactory PrompterFactory) []*cli.Command {
|
||||
func newAdminExecutionCommands(clientFactory ClientFactory, prompterFactory PrompterFactory) []*cli.Command {
|
||||
return []*cli.Command{
|
||||
{
|
||||
Name: "import",
|
||||
@@ -138,17 +138,17 @@ func newAdminWorkflowCommands(clientFactory ClientFactory, prompterFactory Promp
|
||||
{
|
||||
Name: "describe",
|
||||
Aliases: []string{"d"},
|
||||
Usage: "Describe internal information of workflow execution",
|
||||
Usage: "Describe internal information of Temporal execution",
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: FlagWorkflowID,
|
||||
Aliases: FlagWorkflowIDAlias,
|
||||
Usage: "Workflow ID",
|
||||
Name: FlagBusinessID,
|
||||
Aliases: FlagBusinessIDAlias,
|
||||
Usage: "Business ID (Workflow ID)",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: FlagRunID,
|
||||
Aliases: FlagRunIDAlias,
|
||||
Usage: "Run ID",
|
||||
Usage: "Run ID (optional, uses latest if not specified)",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: FlagArchetype,
|
||||
@@ -157,7 +157,7 @@ func newAdminWorkflowCommands(clientFactory ClientFactory, prompterFactory Promp
|
||||
},
|
||||
},
|
||||
Action: func(c *cli.Context) error {
|
||||
return AdminDescribeWorkflow(c, clientFactory)
|
||||
return AdminDescribeExecution(c, clientFactory)
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user