[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:
Lina Jodoin
2026-01-07 20:19:48 +00:00
committed by GitHub
parent f2cec6d2c3
commit 67dbefec20
9 changed files with 330 additions and 31 deletions
-4
View File
@@ -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()
}
+5
View File
@@ -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.
+5
View File
@@ -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.
+5
View File
@@ -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.
+213
View File
@@ -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
}
+34
View File
@@ -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
View File
@@ -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, &currentBranchToken)
}
}
// 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
}
+2
View File
@@ -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
View File
@@ -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)
},
},
{