Monitoring AWS Step Functions: Metrics, Alarms, and Dashboards
Monitoring AWS Step Functions is essential for understanding the health, performance, and behavior of your state machine executions. Step Functions automatically sends metrics to Amazon CloudWatch, including execution counts, success and failure rates, execution duration, and service integration metrics. By leveraging these built-in metrics, creating targeted alarms, and building comprehensive dashboards, you gain real-time visibility into your workflows, enabling rapid detection of anomalies, proactive alerting, and informed capacity planning.
Why Monitoring Step Functions Matters
Step Functions orchestrates multiple AWS services into resilient workflows. Without proper monitoring, failures can cascade silently, performance degradation goes unnoticed, and SLA violations occur without warning. Monitoring provides:
- Operational visibility — Track execution volume, success/failure ratios, and throughput across all state machines.
- Proactive alerting — Get notified when execution failures spike, timeouts occur, or throttling limits are approached.
- Cost optimization — Identify unexpectedly long-running executions that drive up billed state transitions.
- Compliance and audit — Maintain a record of workflow completion rates and failure patterns for reporting.
- Performance tuning — Pinpoint bottlenecks in specific states, especially service integration tasks like Lambda invocations or Glue job starts.
Understanding Step Functions Metrics
AWS Step Functions publishes metrics to the AWS/States CloudWatch namespace. Metrics are available at both the service-wide level and the state machine level, with some also scoped to individual states when using Express Workflows or enhanced Standard Workflow logging.
Execution Metrics
Execution metrics track the volume and outcome of state machine runs. The key metrics are:
- ExecutionsStarted — The number of executions that entered the
STARTstate. Useful for throughput analysis. - ExecutionsSucceeded — Count of executions that completed successfully (reached a terminal success state).
- ExecutionsFailed — Count of executions that failed (reached a terminal failure state or timed out).
- ExecutionsAborted — Executions abruptly terminated, typically via the
StopExecutionAPI. - ExecutionsTimedOut — Executions that exceeded the state machine's execution timeout limit.
- ExecutionThrottled — Executions denied due to account-level or state machine throttling limits.
You can use these metrics to calculate success rate: ExecutionsSucceeded / (ExecutionsStarted - ExecutionsAborted) over a time window. Monitoring the ExecutionThrottled metric is critical for capacity planning — sustained throttling indicates you need to request a limit increase or redesign your workflow to reduce peak burst rates.
Timing Metrics
Timing metrics help you understand execution latency and identify performance regressions:
- ExecutionTime — The total wall-clock time from start to completion (in milliseconds). Available as an average, maximum, or percentile statistic.
- StepTime — Time spent within an individual state transition. Available for Express Workflows with CloudWatch logging enabled.
- ActivityTime — Time spent waiting for an activity worker to complete a task. High values may indicate worker scaling issues.
For Standard Workflows, ExecutionTime is published after execution completion. For Express Workflows, metrics are published near real-time and include ExpressExecutionTime with finer granularity.
Service Integration Metrics
When your state machine invokes other AWS services (Lambda, ECS, Batch, SageMaker, etc.), Step Functions emits additional metrics prefixed with the service name:
- LambdaInvoke — Tracks Lambda function invocations from Task states, including success, failure, and retry counts.
- LambdaServiceException — Count of Lambda service errors (500 errors from Lambda itself, not your function code).
- LambdaFunctionError — Count of Lambda function-level errors (your code throwing an exception).
- EcsRunTask — Tracks ECS task placements triggered by Step Functions.
- SageMakerTransformJob — Tracks SageMaker batch transform job starts.
These service integration metrics are dimensioned by StateMachineArn and StateName, allowing you to pinpoint exactly which state in which workflow is causing integration failures.
How to Monitor Step Functions
Using CloudWatch Metrics Console
In the CloudWatch console, navigate to Metrics → All metrics → AWS/States. You'll see dimensions including:
- Across All State Machines — Aggregated metrics for the entire account in the region.
- StateMachineArn — Metrics scoped to a specific state machine.
- StateMachineArn, StateName — Metrics for individual states (Express Workflows or when enhanced logging is enabled).
- StateMachineArn, ExecType — Metrics split by execution type (Standard vs. Express).
Select a metric, choose a statistic (Sum for counts, Average/p99 for latency), and set the period to match your monitoring granularity — 1 minute for real-time operational dashboards, 5 minutes for cost and trend analysis.
Creating CloudWatch Alarms
Alarms notify you when metrics breach defined thresholds. Here are essential alarms every Step Functions deployment should implement:
# Example: Alarm for execution failure rate exceeding 5% over 5 minutes
aws cloudwatch put-metric-alarm \
--alarm-name "HighExecutionFailureRate" \
--alarm-description "Alert when failure rate exceeds 5% for state machine" \
--namespace "AWS/States" \
--metric-name "ExecutionsFailed" \
--statistic "Sum" \
--period 300 \
--evaluation-periods 1 \
--threshold 5 \
--comparison-operator "GreaterThanThreshold" \
--dimensions "Name=StateMachineArn,Value=arn:aws:states:us-east-1:123456789012:stateMachine:OrderProcessing" \
--treat-missing-data "notBreaching" \
--alarm-actions "arn:aws:sns:us-east-1:123456789012:ops-team-notifications"
For a ratio-based alarm (failure rate rather than absolute count), use CloudWatch metric math:
# Example: Alarm using metric math for failure rate percentage
aws cloudwatch put-metric-alarm \
--alarm-name "HighFailureRatePercentage" \
--alarm-description "Alert when failure rate exceeds 5%" \
--namespace "AWS/States" \
--metrics '[
{ "Id": "failed", "MetricStat": { "Metric": { "Namespace": "AWS/States", "MetricName": "ExecutionsFailed", "Dimensions": [{"Name":"StateMachineArn","Value":"arn:aws:states:us-east-1:123456789012:stateMachine:OrderProcessing"}] }, "Period": 300, "Stat": "Sum" }, "ReturnData": false },
{ "Id": "started", "MetricStat": { "Metric": { "Namespace": "AWS/States", "MetricName": "ExecutionsStarted", "Dimensions": [{"Name":"StateMachineArn","Value":"arn:aws:states:us-east-1:123456789012:stateMachine:OrderProcessing"}] }, "Period": 300, "Stat": "Sum" }, "ReturnData": false },
{ "Id": "failureRate", "Expression": "(failed / started) * 100", "Label": "FailureRatePercent", "ReturnData": true }
]' \
--threshold 5 \
--comparison-operator "GreaterThanThreshold" \
--evaluation-periods 2 \
--period 300 \
--alarm-actions "arn:aws:sns:us-east-1:123456789012:ops-team-notifications"
Additional alarms to consider:
- ExecutionTime p99 > SLA threshold — Alert when 99th percentile execution duration exceeds your business SLA (e.g., 30 seconds for a checkout workflow).
- ExecutionThrottled > 0 — Immediately alert on any throttling event; throttled executions are dropped and require intervention.
- ExecutionsAborted spike — May indicate automated cleanup scripts or manual intervention patterns that need investigation.
- LambdaServiceException spike — Indicates AWS Lambda service-side issues affecting your workflow, not your code.
Building CloudWatch Dashboards
A well-structured dashboard provides at-a-glance visibility across all state machines. Use the CloudWatch console or AWS CLI to create a dashboard with widgets tailored to your workflows.
# Create a comprehensive Step Functions monitoring dashboard
aws cloudwatch put-dashboard \
--dashboard-name "StepFunctions-Overview" \
--dashboard-body '{
"widgets": [
{
"type": "metric",
"x": 0, "y": 0, "width": 12, "height": 6,
"properties": {
"view": "timeSeries",
"stacked": false,
"region": "us-east-1",
"title": "Execution Volume - Last 24 Hours",
"period": 3600,
"stat": "Sum",
"metrics": [
["AWS/States", "ExecutionsStarted", { "label": "Started" }],
["AWS/States", "ExecutionsSucceeded", { "label": "Succeeded" }],
["AWS/States", "ExecutionsFailed", { "label": "Failed" }],
["AWS/States", "ExecutionsAborted", { "label": "Aborted" }],
["AWS/States", "ExecutionThrottled", { "label": "Throttled", "color": "#ff0000" }]
]
}
},
{
"type": "metric",
"x": 12, "y": 0, "width": 12, "height": 6,
"properties": {
"view": "timeSeries",
"stacked": false,
"region": "us-east-1",
"title": "Execution Duration (p50, p90, p99)",
"period": 300,
"stat": "p50",
"metrics": [
["AWS/States", "ExecutionTime", { "stat": "p50", "label": "p50" }],
["AWS/States", "ExecutionTime", { "stat": "p90", "label": "p90" }],
["AWS/States", "ExecutionTime", { "stat": "p99", "label": "p99", "color": "#ff9900" }]
]
}
},
{
"type": "metric",
"x": 0, "y": 6, "width": 8, "height": 6,
"properties": {
"view": "singleValue",
"region": "us-east-1",
"title": "Current Failure Rate % (Last Hour)",
"period": 3600,
"metrics": [
{ "id": "failed", "expression": "SUM(METRICS(\"AWS/States\", \"ExecutionsFailed\", \"StateMachineArn\", \"arn:aws:states:us-east-1:123456789012:stateMachine:OrderProcessing\", { \"period\": 3600 }))", "label": "Failed", "returnData": false },
{ "id": "started", "expression": "SUM(METRICS(\"AWS/States\", \"ExecutionsStarted\", \"StateMachineArn\", \"arn:aws:states:us-east-1:123456789012:stateMachine:OrderProcessing\", { \"period\": 3600 }))", "label": "Started", "returnData": false },
{ "id": "rate", "expression": "(failed / started) * 100", "label": "FailureRate" }
]
}
},
{
"type": "metric",
"x": 8, "y": 6, "width": 8, "height": 6,
"properties": {
"view": "timeSeries",
"stacked": false,
"region": "us-east-1",
"title": "Lambda Integration Errors by State",
"period": 300,
"stat": "Sum",
"metrics": [
["AWS/States", "LambdaFunctionError", "StateName", "ProcessPayment", { "label": "ProcessPayment Errors" }],
["AWS/States", "LambdaServiceException", "StateName", "ProcessPayment", { "label": "Lambda 5xx", "color": "#d62728" }]
]
}
},
{
"type": "metric",
"x": 16, "y": 6, "width": 8, "height": 6,
"properties": {
"view": "timeSeries",
"stacked": false,
"region": "us-east-1",
"title": "Throttled Executions",
"period": 60,
"stat": "Sum",
"metrics": [
["AWS/States", "ExecutionThrottled", { "color": "#ff0000" }]
]
}
},
{
"type": "log",
"x": 0, "y": 12, "width": 24, "height": 6,
"properties": {
"region": "us-east-1",
"title": "Recent Execution Failures",
"query": "fields @timestamp, @message | filter execution_status = \"FAILED\" | sort @timestamp desc | limit 20",
"logGroups": ["/aws/states/OrderProcessing-failures"]
}
}
]
}'
This dashboard combines execution counts, latency percentiles, failure rate calculations, per-state integration errors, throttling events, and a log query widget showing recent failure details — all in a single pane of glass.
Using AWS CLI for On-Demand Metric Retrieval
For quick investigations or scripting, retrieve metrics directly with the CLI:
# Get execution counts for the last 6 hours at 1-hour intervals
aws cloudwatch get-metric-statistics \
--namespace "AWS/States" \
--metric-name "ExecutionsStarted" \
--start-time "$(date -u -d '6 hours ago' +%Y-%m-%dT%H:%M:%SZ)" \
--end-time "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
--period 3600 \
--statistics "Sum" \
--dimensions "Name=StateMachineArn,Value=arn:aws:states:us-east-1:123456789012:stateMachine:OrderProcessing"
# Get p99 execution duration for SLA monitoring
aws cloudwatch get-metric-statistics \
--namespace "AWS/States" \
--metric-name "ExecutionTime" \
--start-time "$(date -u -d '1 hour ago' +%Y-%m-%dT%H:%M:%SZ)" \
--end-time "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
--period 300 \
--statistics "p99" \
--dimensions "Name=StateMachineArn,Value=arn:aws:states:us-east-1:123456789012:stateMachine:OrderProcessing"
# List all alarms currently in ALARM state for Step Functions
aws cloudwatch describe-alarms \
--alarm-name-prefix "StepFunctions-" \
--state-value "ALARM" \
--query "MetricAlarms[*].[AlarmName,StateReason]" \
--output table
Using CloudFormation to Deploy Alarms and Dashboards
Infrastructure as Code ensures your monitoring is versioned, reproducible, and deployed alongside your state machines. Here's a CloudFormation snippet that creates alarms and a dashboard:
# CloudFormation template fragment for Step Functions monitoring
Resources:
HighFailureRateAlarm:
Type: AWS::CloudWatch::Alarm
Properties:
AlarmName: !Sub "${StateMachineName}-HighFailureRate"
AlarmDescription: "Failure rate exceeds 5% for the state machine"
Namespace: "AWS/States"
Metrics:
- Id: failed
MetricStat:
Metric:
Namespace: "AWS/States"
MetricName: "ExecutionsFailed"
Dimensions:
- Name: StateMachineArn
Value: !Ref StateMachineArn
Period: 300
Stat: Sum
ReturnData: false
- Id: started
MetricStat:
Metric:
Namespace: "AWS/States"
MetricName: "ExecutionsStarted"
Dimensions:
- Name: StateMachineArn
Value: !Ref StateMachineArn
Period: 300
Stat: Sum
ReturnData: false
- Id: failureRate
Expression: "(failed / started) * 100"
Label: "FailureRatePercent"
ReturnData: true
Threshold: 5
ComparisonOperator: GreaterThanThreshold
EvaluationPeriods: 2
AlarmActions:
- !Ref SNSTopicARN
ExecutionDurationP99Alarm:
Type: AWS::CloudWatch::Alarm
Properties:
AlarmName: !Sub "${StateMachineName}-HighDuration-P99"
AlarmDescription: "p99 execution duration exceeds 30 seconds"
Namespace: "AWS/States"
MetricName: "ExecutionTime"
Dimensions:
- Name: StateMachineArn
Value: !Ref StateMachineArn
Statistic: p99
Period: 300
EvaluationPeriods: 3
Threshold: 30000
ComparisonOperator: GreaterThanThreshold
AlarmActions:
- !Ref SNSTopicARN
StepFunctionsDashboard:
Type: AWS::CloudWatch::Dashboard
Properties:
DashboardName: !Sub "${StateMachineName}-Monitoring"
DashboardBody: !Sub |
{
"widgets": [
{
"type": "metric",
"x": 0, "y": 0, "width": 12, "height": 6,
"properties": {
"view": "timeSeries",
"stacked": false,
"region": "${AWS::Region}",
"title": "Executions Overview",
"period": 300,
"stat": "Sum",
"metrics": [
["AWS/States", "ExecutionsStarted", "StateMachineArn", "${StateMachineArn}", { "label": "Started" }],
["AWS/States", "ExecutionsSucceeded", "StateMachineArn", "${StateMachineArn}", { "label": "Succeeded" }],
["AWS/States", "ExecutionsFailed", "StateMachineArn", "${StateMachineArn}", { "label": "Failed" }]
]
}
},
{
"type": "metric",
"x": 12, "y": 0, "width": 12, "height": 6,
"properties": {
"view": "timeSeries",
"stacked": false,
"region": "${AWS::Region}",
"title": "Duration Percentiles (ms)",
"period": 300,
"metrics": [
["AWS/States", "ExecutionTime", "StateMachineArn", "${StateMachineArn}", { "stat": "p50", "label": "p50" }],
["AWS/States", "ExecutionTime", "StateMachineArn", "${StateMachineArn}", { "stat": "p90", "label": "p90" }],
["AWS/States", "ExecutionTime", "StateMachineArn", "${StateMachineArn}", { "stat": "p99", "label": "p99" }]
]
}
}
]
}
For CDK users, the equivalent construct-based approach provides type safety and abstraction:
import * as cdk from 'aws-cdk-lib';
import * as cloudwatch from 'aws-cdk-lib/aws-cloudwatch';
import * as sns from 'aws-cdk-lib/aws-sns';
import * as sns_subscriptions from 'aws-cdk-lib/aws-sns-subscriptions';
import { Construct } from 'constructs';
export class StepFunctionsMonitoringStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
const stateMachineArn = 'arn:aws:states:us-east-1:123456789012:stateMachine:OrderProcessing';
// SNS topic for alarm notifications
const alarmTopic = new sns.Topic(this, 'StepFunctionsAlarmTopic', {
displayName: 'Step Functions Alarms'
});
alarmTopic.addSubscription(
new sns_subscriptions.EmailSubscription('ops-team@example.com')
);
// Metric math for failure rate
const failureRateMetric = new cloudwatch.Metric({
namespace: 'AWS/States',
metricName: 'ExecutionsFailed',
dimensionsMap: { StateMachineArn: stateMachineArn },
statistic: 'sum',
period: cdk.Duration.minutes(5)
});
const startedMetric = new cloudwatch.Metric({
namespace: 'AWS/States',
metricName: 'ExecutionsStarted',
dimensionsMap: { StateMachineArn: stateMachineArn },
statistic: 'sum',
period: cdk.Duration.minutes(5)
});
const failureRateMath = new cloudwatch.MathExpression({
expression: '(failed / started) * 100',
usingMetrics: {
failed: failureRateMetric,
started: startedMetric
},
label: 'FailureRatePercent',
period: cdk.Duration.minutes(5)
});
// Alarm for high failure rate
new cloudwatch.Alarm(this, 'HighFailureRateAlarm', {
alarmName: 'OrderProcessing-HighFailureRate',
metric: failureRateMath,
threshold: 5,
evaluationPeriods: 2,
comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_THRESHOLD,
alarmDescription: 'Failure rate exceeds 5% for OrderProcessing workflow',
actionsEnabled: true,
}).addAlarmAction(new cloudwatch.SnsAlarmAction(alarmTopic));
// Alarm for p99 execution duration
const durationP99 = new cloudwatch.Metric({
namespace: 'AWS/States',
metricName: 'ExecutionTime',
dimensionsMap: { StateMachineArn: stateMachineArn },
statistic: 'p99',
period: cdk.Duration.minutes(5)
});
new cloudwatch.Alarm(this, 'HighDurationP99Alarm', {
alarmName: 'OrderProcessing-HighDuration-P99',
metric: durationP99,
threshold: 30000, // 30 seconds in milliseconds
evaluationPeriods: 3,
comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_THRESHOLD,
alarmDescription: 'p99 execution duration exceeds 30 seconds',
actionsEnabled: true,
}).addAlarmAction(new cloudwatch.SnsAlarmAction(alarmTopic));
// Dashboard
const dashboard = new cloudwatch.Dashboard(this, 'StepFunctionsDashboard', {
dashboardName: 'OrderProcessing-Monitoring',
widgets: [
new cloudwatch.RowWidget({
width: 24,
height: 6,
widgets: [
new cloudwatch.GraphWidget({
title: 'Executions Overview',
width: 12,
left: [
new cloudwatch.Metric({
namespace: 'AWS/States',
metricName: 'ExecutionsStarted',
dimensionsMap: { StateMachineArn: stateMachineArn },
statistic: 'sum',
label: 'Started',
period: cdk.Duration.minutes(5)
}),
new cloudwatch.Metric({
namespace: 'AWS/States',
metricName: 'ExecutionsSucceeded',
dimensionsMap: { StateMachineArn: stateMachineArn },
statistic: 'sum',
label: 'Succeeded',
period: cdk.Duration.minutes(5)
}),
new cloudwatch.Metric({
namespace: 'AWS/States',
metricName: 'ExecutionsFailed',
dimensionsMap: { StateMachineArn: stateMachineArn },
statistic: 'sum',
label: 'Failed',
period: cdk.Duration.minutes(5)
})
]
}),
new cloudwatch.GraphWidget({
title: 'Duration Percentiles',
width: 12,
left: [
new cloudwatch.Metric({
namespace: 'AWS/States',
metricName: 'ExecutionTime',
dimensionsMap: { StateMachineArn: stateMachineArn },
statistic: 'p50',
label: 'p50',
period: cdk.Duration.minutes(5)
}),
new cloudwatch.Metric({
namespace: 'AWS/States',
metricName: 'ExecutionTime',
dimensionsMap: { StateMachineArn: stateMachineArn },
statistic: 'p90',
label: 'p90',
period: cdk.Duration.minutes(5)
}),
new cloudwatch.Metric({
namespace: 'AWS/States',
metricName: 'ExecutionTime',
dimensionsMap: { StateMachineArn: stateMachineArn },
statistic: 'p99',
label: 'p99',
period: cdk.Duration.minutes(5)
})
]
})
]
})
]
});
}
}
Best Practices for Step Functions Monitoring
-
Start with execution outcome alarms — Before diving into latency or integration metrics, ensure you have alarms on
ExecutionsFailedandExecutionThrottled. These are the most impactful signals that require immediate action. -
Use metric math for rates, not absolute counts — A spike from 10 to 20 failures is less alarming when executions also jumped from 1,000 to 100,000. Use
(failed / started) * 100expressions to calculate failure rates that scale with traffic. -
Monitor at multiple aggregation levels — Keep a high-level dashboard showing aggregate metrics across all state machines for broad health checks, and drill-down dashboards per state machine for detailed investigation. Use the
StateMachineArndimension to scope widgets. - Set alarms on p99, not just average — Average execution time hides tail latency. A workflow averaging 2 seconds with p99 of 45 seconds indicates severe outliers affecting some users. Always monitor both p50 and p99 (or p95) percentiles.
-
Enable enhanced logging for per-state metrics — In Standard Workflows, per-state metrics are not available by default. Enable CloudWatch Logs with level
ALLand configure log group metric filters to extract state-level timing and error data. For Express Workflows, enable CloudWatch logging on the state machine to getStepTimeand service integration metrics dimensioned byStateName. -
Correlate with downstream service metrics — A spike in Step Functions
LambdaFunctionErrorshould correlate with Lambda function error metrics. Build cross-service dashboards that show the full chain: Step Functions → Lambda → DynamoDB/API Gateway, so you can trace failures end-to-end. -
Treat missing data intentionally — Configure alarm
treat-missing-databased on context. For failure metrics,notBreaching(no data = no failures) is reasonable. For heartbeat-style metrics like expected execution volume,breaching(missing data = possible outage) may be appropriate. -
Version your dashboards alongside infrastructure — Store dashboard JSON definitions in version control (alongside CloudFormation/CDK code). Use
AWS::CloudWatch::Dashboardresources to deploy dashboards as part of your CI/CD pipeline, ensuring they stay synchronized with state machine changes. - Use composite alarms to reduce noise — Combine multiple metrics into a single composite alarm. For example, only alert on high failure rate if execution volume is above a minimum threshold, preventing false alarms during low-traffic maintenance windows.
-
Tag and organize alarms systematically — Use consistent alarm naming conventions like
<Environment>-<Workflow>-<Condition>(e.g.,prod-orderprocessing-high-failure-rate). Apply AWS tags to alarms for cost allocation and ownership tracking.
Effective monitoring transforms Step Functions from a black-box orchestrator into a transparent, predictable, and resilient workflow engine. By instrumenting the right metrics, configuring intelligent alarms, and building comprehensive dashboards — all deployed as code — you create a feedback loop that catches issues before they impact users, guides performance optimization, and provides the confidence to iterate rapidly on complex business workflows. Start with the essentials: failure and throttling alarms, then layer on latency percentiles, per-state service integration metrics, and cross-service dashboards as your workflow portfolio grows.