Files
tasks/chasm/lib/nexusoperation/library.go
sammandGitHub 84f2b103e3 Add caller-side Nexus operation metrics (#10071)
## What changed?

NOTE: Recreation of https://github.com/temporalio/temporal/pull/10026
since that PR cannot be re-opened or have its target branch updated.

New metrics:
### Counters

| Metric | Description |
|--------|-------------|
| `nexus_operation_success_count` | Successfully completed operations |
| `nexus_operation_failed_count` | Failed operations |
| `nexus_operation_cancel_count` | Cancelled operations |
| `nexus_operation_terminate_count` | Operations terminated before
completion |
| `nexus_operation_timeout_count` | Operations timed out before
completion |

### Histograms

| Metric | Description |
|--------|-------------|
| `nexus_operation_schedule_to_close_latency` | Time between schedule
and close for sync and async operations |
| `nexus_operation_schedule_to_start_latency` | Time between schedule
and start for sync and async operations |
| `nexus_operation_start_to_close_latency` | Time between start and
close for async operations only |

## Labels

All metrics use the following labels:

| Label | Notes |
|-------|-------|
| `namespace` | |
| `nexus_endpoint` | |
| `nexus_service` | Controlled by Dynamic config |
| `nexus_operation` | Controlled by Dynamic config |
| `workflowType` | |

Latency metrics also include:

| Label | Values |
|-------|--------|
| `outcome` | succeeded, failed, canceled, terminated, timedout |

Timeout Count also includes:

| Label | Values |
|-------|--------|
| `timeout_type` | StartToClose, ScheduleToStart, ScheduleToClose  |

## How did you test it?
- [x] built
- [ ] run locally and tested manually
- [x] covered by existing tests
- [x] added new unit test(s)
- [ ] added new functional test(s)

## Potential risks
Metrics emitting isn't nil safe, and enrich metrics could panic if
misconfigured.
2026-05-01 12:23:34 -07:00

122 lines
4.7 KiB
Go

package nexusoperation
import (
"go.temporal.io/server/chasm"
nexusoperationpb "go.temporal.io/server/chasm/lib/nexusoperation/gen/nexusoperationpb/v1"
"go.temporal.io/server/common/dynamicconfig"
"google.golang.org/grpc"
)
type operationContextKeyType struct{}
// OperationContextKey is the context key for OperationContext, registered as a CHASM component
// context value. Exported for use in tests that need to set up MockContext.
var OperationContextKey = operationContextKeyType{}
// OperationContext holds dependencies injected into the chasm.Context for use by Operation methods.
type OperationContext struct {
MetricTagConfig dynamicconfig.TypedPropertyFn[NexusMetricTagConfig]
}
// componentOnlyLibrary registers just the components without task executors or gRPC handlers.
// Used in the frontend to enable component ref serialization.
type componentOnlyLibrary struct {
chasm.UnimplementedLibrary
metricTagConfig dynamicconfig.TypedPropertyFn[NexusMetricTagConfig]
}
func newComponentOnlyLibrary(dc *dynamicconfig.Collection) *componentOnlyLibrary {
return &componentOnlyLibrary{
metricTagConfig: MetricTagConfiguration.Get(dc),
}
}
func (l *componentOnlyLibrary) Name() string {
return "nexusoperation"
}
func (l *componentOnlyLibrary) Components() []*chasm.RegistrableComponent {
return []*chasm.RegistrableComponent{
chasm.NewRegistrableComponent[*Operation](
"operation",
chasm.WithSearchAttributes(
EndpointSearchAttribute,
ServiceSearchAttribute,
OperationSearchAttribute,
RequestIDSearchAttribute,
StatusSearchAttribute,
),
chasm.WithBusinessIDAlias("OperationId"),
chasm.WithContextValues(map[any]any{
OperationContextKey: &OperationContext{
MetricTagConfig: l.metricTagConfig,
},
}),
),
chasm.NewRegistrableComponent[*Cancellation]("cancellation"),
}
}
type Library struct {
componentOnlyLibrary
handler *handler
operationBackoffTaskHandler *operationBackoffTaskHandler
operationInvocationTaskHandler *operationInvocationTaskHandler
operationScheduleToCloseTimeoutTaskHandler *operationScheduleToCloseTimeoutTaskHandler
operationScheduleToStartTimeoutTaskHandler *operationScheduleToStartTimeoutTaskHandler
operationStartToCloseTimeoutTaskHandler *operationStartToCloseTimeoutTaskHandler
cancellationInvocationTaskHandler *cancellationInvocationTaskHandler
cancellationBackoffTaskHandler *cancellationBackoffTaskHandler
}
func newLibrary(
handler *handler,
operationBackoffTaskHandler *operationBackoffTaskHandler,
operationInvocationTaskHandler *operationInvocationTaskHandler,
operationScheduleToCloseTimeoutTaskHandler *operationScheduleToCloseTimeoutTaskHandler,
operationScheduleToStartTimeoutTaskHandler *operationScheduleToStartTimeoutTaskHandler,
operationStartToCloseTimeoutTaskHandler *operationStartToCloseTimeoutTaskHandler,
cancellationInvocationTaskHandler *cancellationInvocationTaskHandler,
cancellationBackoffTaskHandler *cancellationBackoffTaskHandler,
dc *dynamicconfig.Collection,
) *Library {
return &Library{
componentOnlyLibrary: *newComponentOnlyLibrary(dc),
handler: handler,
operationBackoffTaskHandler: operationBackoffTaskHandler,
operationInvocationTaskHandler: operationInvocationTaskHandler,
operationScheduleToCloseTimeoutTaskHandler: operationScheduleToCloseTimeoutTaskHandler,
operationScheduleToStartTimeoutTaskHandler: operationScheduleToStartTimeoutTaskHandler,
operationStartToCloseTimeoutTaskHandler: operationStartToCloseTimeoutTaskHandler,
cancellationInvocationTaskHandler: cancellationInvocationTaskHandler,
cancellationBackoffTaskHandler: cancellationBackoffTaskHandler,
}
}
func (l *Library) Tasks() []*chasm.RegistrableTask {
return []*chasm.RegistrableTask{
chasm.NewRegistrableSideEffectTask(
"invocation",
l.operationInvocationTaskHandler,
chasm.WithTaskGroup(TaskGroupName),
),
chasm.NewRegistrablePureTask("invocationBackoff", l.operationBackoffTaskHandler),
chasm.NewRegistrablePureTask("scheduleToStartTimeout", l.operationScheduleToStartTimeoutTaskHandler),
chasm.NewRegistrablePureTask("startToCloseTimeout", l.operationStartToCloseTimeoutTaskHandler),
chasm.NewRegistrablePureTask("scheduleToCloseTimeout", l.operationScheduleToCloseTimeoutTaskHandler),
chasm.NewRegistrableSideEffectTask(
"cancellation",
l.cancellationInvocationTaskHandler,
chasm.WithTaskGroup(TaskGroupName),
),
chasm.NewRegistrablePureTask("cancellationBackoff", l.cancellationBackoffTaskHandler),
}
}
func (l *Library) RegisterServices(server *grpc.Server) {
server.RegisterService(&nexusoperationpb.NexusOperationService_ServiceDesc, l.handler)
}