← Back to DevBytes

Monitoring Step Functions: Metrics, Alarms, and Dashboards

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:

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:

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:

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:

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:

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:

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

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.

🚀 Need a reliable AI agent for your project?

Deploy Hermes Agent in 10 minutes. Managed hosting, zero DevOps.

Get Started — $23.99/mo
← Back to all articles