• Skip to secondary menu
  • Skip to main content
  • Skip to primary sidebar
  • Home
  • Projects
  • Products
  • Themes
  • Tools
  • Request for Quote

Vengala Vinay

Having 9+ Years of Experience in Software Development

  • Home
  • WordPress
  • PHP
    • Codeigniter
  • Django
  • Magento
  • Selenium
  • Server
Home » Server Monitoring Best Practices: Keeping Your C++ App and Elasticsearch Clusters Alive on AWS

Server Monitoring Best Practices: Keeping Your C++ App and Elasticsearch Clusters Alive on AWS

Proactive C++ Application Health Checks on AWS EC2

Maintaining the health of C++ applications deployed on AWS EC2 instances requires a multi-layered approach to monitoring. Beyond basic CPU and memory utilization, we need to inspect application-specific metrics and ensure critical processes are running. This involves integrating custom health checks into your application and leveraging AWS CloudWatch for metric collection and alerting.

Implementing Application-Level Health Endpoints

A robust health check mechanism should be built directly into your C++ application. This typically involves exposing an HTTP endpoint (e.g., `/health`) that returns a status code and potentially detailed information about the application’s internal state. For a simple C++ web server using `libmicrohttpd`, this might look like:

#include <microhttpd.h>
#include <string>
#include <vector>
#include <iostream>

// Assume some global state or access to application components
extern bool g_is_initialized;
extern int g_active_connections;

static int
health_request_handler(void *cls, struct MHD_Connection *connection,
                       const char *url, const char *method,
                       const char *version, const char *upload_data,
                       size_t *upload_data_size, void **con_cls)
{
    if (strcmp(url, "/health") == 0 && strcmp(method, "GET") == 0) {
        std::string response_body;
        int http_status = MHD_HTTP_OK;

        if (!g_is_initialized) {
            response_body = "{\"status\": \"uninitialized\", \"message\": \"Application not fully started\"}";
            http_status = MHD_HTTP_SERVICE_UNAVAILABLE;
        } else {
            response_body = "{\"status\": \"ok\", \"active_connections\": " + std::to_string(g_active_connections) + "}";
        }

        struct MHD_Response *response;
        response = MHD_create_response_from_buffer(response_body.length(), (void *)response_body.c_str(), MHD_RESPONSE_FLAGS_NO_CONTENT_LENGTH);
        MHD_add_header(response, MHD_HTTP_HEADER_CONTENT_TYPE, "application/json");
        int ret = MHD_queue_basic_status_line(connection, http_status, NULL);
        ret = MHD_queue_headers(connection, MHD_get_connection_values(connection, MHD_HEADER_LIST), NULL); // Pass headers if needed
        ret = MHD_queue_response(connection, response, NULL);
        MHD_destroy_response(response);
        return ret;
    }

    // Handle other requests or return 404
    const char *not_found_message = "Not Found";
    struct MHD_Response *response = MHD_create_response_from_buffer(strlen(not_found_message), (void *)not_found_message, MHD_RESPONSE_FLAGS_NO_CONTENT_LENGTH);
    MHD_add_header(response, MHD_HTTP_HEADER_CONTENT_TYPE, "text/plain");
    MHD_queue_basic_status_line(connection, MHD_HTTP_NOT_FOUND, NULL);
    MHD_queue_headers(connection, MHD_get_connection_values(connection, MHD_HEADER_LIST), NULL);
    MHD_queue_response(connection, response, NULL);
    MHD_destroy_response(response);
    return MHD_YES;
}

// ... in your main application setup ...
// daemon = MHD_start_daemon(MHD_SERVER_PORT_65535, 8080, NULL, NULL, &health_request_handler, NULL, MHD_OPTION_END);

This simple endpoint provides a basic “up” or “down” status. For more complex applications, you might want to check database connectivity, queue status, or the availability of downstream services.

Leveraging CloudWatch Agent for Custom Metrics

AWS CloudWatch Agent is essential for collecting system-level metrics and custom application metrics. We can use it to periodically poll our health endpoint and send the results to CloudWatch.

First, ensure the CloudWatch Agent is installed and configured on your EC2 instances. The configuration file (typically `/opt/aws/amazon-cloudwatch-agent/bin/config.json`) is key. We’ll define a custom metric for our application’s health.

{
  "agent": {
    "metrics_collection_interval": 60,
    "run_as_user": "cwagent"
  },
  "metrics": {
    "namespace": "MyCppApp",
    "metrics_collected": {
      "http_listener": {
        "interval": 30,
        "url": "http://localhost:8080/health",
        "method": "GET",
        "protocol": "http",
        "port": 8080,
        "path": "/health",
        "response_code_metric": true,
        "response_time_metric": true,
        "tls_verify": false,
        "tls_ca_path": "",
        "tls_cert_path": "",
        "tls_key_path": ""
      }
    }
  }
}

With this configuration, the CloudWatch Agent will periodically make a GET request to http://localhost:8080/health. It will automatically publish metrics such as:

  • http_listener.response_code: The HTTP status code returned (e.g., 200, 503).
  • http_listener.response_time: The time taken to receive a response in milliseconds.

You can then use these metrics in CloudWatch Alarms. For instance, an alarm can be triggered if the http_listener.response_code is not 200 for a sustained period, or if http_listener.response_time exceeds a critical threshold.

Process Monitoring with Systemd

Ensuring the C++ application process itself is running is paramount. Systemd is the standard init system on most modern Linux distributions, including Amazon Linux 2. We can create a systemd service unit to manage our application and define restart policies.

Create a service file, for example, /etc/systemd/system/mycppapp.service:

[Unit]
Description=My C++ Application Service
After=network.target

[Service]
Type=simple
User=appuser
Group=appgroup
WorkingDirectory=/opt/mycppapp/bin
ExecStart=/opt/mycppapp/bin/mycppapp --config /etc/mycppapp/config.conf
Restart=on-failure
RestartSec=5s
StandardOutput=syslog
StandardError=syslog
SyslogIdentifier=mycppapp

[Install]
WantedBy=multi-user.target

Key directives:

  • Restart=on-failure: Automatically restarts the service if it exits with a non-zero status.
  • RestartSec=5s: Waits 5 seconds before attempting a restart.
  • StandardOutput=syslog and StandardError=syslog: Directs application logs to syslog, which can then be collected by CloudWatch Logs Agent.
  • SyslogIdentifier=mycppapp: Adds a tag to syslog messages for easier filtering.

After creating the file, enable and start the service:

sudo systemctl daemon-reload
sudo systemctl enable mycppapp.service
sudo systemctl start mycppapp.service
sudo systemctl status mycppapp.service

You can also use systemd’s watchdog functionality for more aggressive monitoring, where the application periodically signals to systemd that it’s alive. This requires modifying the C++ application to interact with the systemd D-Bus API.

Monitoring Elasticsearch Clusters on AWS

Elasticsearch clusters, whether self-managed on EC2 or using Amazon Elasticsearch Service (now Amazon OpenSearch Service), require diligent monitoring. Key areas include cluster health, node status, JVM metrics, disk I/O, and search/indexing performance.

Self-Managed Elasticsearch on EC2: CloudWatch Agent & Custom Scripts

For self-managed clusters, we combine CloudWatch Agent with custom scripts to gather Elasticsearch-specific metrics. The CloudWatch Agent can collect standard OS metrics, but for Elasticsearch internals, we’ll use its REST API.

A Python script can query the Elasticsearch `_cat` APIs and `_nodes/stats` endpoint. This script can then push custom metrics to CloudWatch.

import requests
import boto3
import time
import json

ES_HOST = "http://localhost:9200" # Or your ES endpoint
NAMESPACE = "MyElasticsearchCluster"
CLOUDWATCH = boto3.client('cloudwatch')

def get_es_metric(metric_name, endpoint):
    try:
        response = requests.get(f"{ES_HOST}{endpoint}", timeout=5)
        response.raise_for_status()
        data = response.json()

        if metric_name == "cluster_health":
            health = data[0]['status']
            return {
                "ClusterStatusValue": 1 if health == "green" else (2 if health == "yellow" else 3),
                "ClusterStatus": health
            }
        elif metric_name == "node_stats":
            # Example: Get JVM heap usage for the master node
            master_node_id = None
            for node_id, node_data in data['nodes'].items():
                if node_data.get('roles', {}).get('master'):
                    master_node_id = node_id
                    break
            if master_node_id:
                heap_used_percent = data['nodes'][master_node_id]['jvm']['mem']['heap_used_percent']
                return {"MasterHeapUsedPercent": heap_used_percent}
            return {}
        elif metric_name == "thread_pool_search":
            # Example: Get active threads in search thread pool
            active_threads = data['thread_pool']['search']['active']
            return {"SearchThreadPoolActive": active_threads}
        else:
            return {}

    except requests.exceptions.RequestException as e:
        print(f"Error fetching {metric_name} from Elasticsearch: {e}")
        return {}
    except Exception as e:
        print(f"Error processing {metric_name} data: {e}")
        return {}

def push_to_cloudwatch(metric_data):
    if not metric_data:
        return

    for metric_name, value in metric_data.items():
        try:
            CLOUDWATCH.put_metric_data(
                Namespace=NAMESPACE,
                MetricData=[
                    {
                        'MetricName': metric_name,
                        'Value': value,
                        'Unit': 'Count' if 'ThreadPool' in metric_name or 'StatusValue' in metric_name else 'Percent' if 'Percent' in metric_name else 'Milliseconds',
                        'StorageResolution': 60 # High-resolution metrics
                    },
                ]
            )
            print(f"Pushed {metric_name}: {value}")
        except Exception as e:
            print(f"Error pushing {metric_name} to CloudWatch: {e}")

if __name__ == "__main__":
    # Get cluster health
    health_metrics = get_es_metric("cluster_health", "/_cat/health?h=status")
    push_to_cloudwatch(health_metrics)

    # Get node stats (e.g., JVM heap)
    node_stats_metrics = get_es_metric("node_stats", "/_nodes/stats/jvm,process")
    push_to_cloudwatch(node_stats_metrics)

    # Get thread pool stats
    thread_pool_metrics = get_es_metric("thread_pool_search", "/_nodes/stats/thread_pool")
    push_to_cloudwatch(thread_pool_metrics)

    # Add more metrics as needed (e.g., indexing latency, search latency from _stats API)

This script can be scheduled using cron or systemd timers to run periodically (e.g., every minute). The Unit for CloudWatch metrics should be set appropriately (e.g., ‘Percent’, ‘Count’, ‘Milliseconds’).

For log collection, configure the CloudWatch Logs Agent to tail Elasticsearch logs (/var/log/elasticsearch/) and send them to a dedicated log group.

Amazon OpenSearch Service (formerly Elasticsearch Service) Monitoring

When using Amazon OpenSearch Service, AWS manages the underlying infrastructure. Monitoring shifts towards leveraging CloudWatch metrics provided by the service itself and setting up alarms.

Amazon OpenSearch Service automatically publishes a comprehensive set of metrics to CloudWatch under the AWS/OpenSearchService namespace. Key metrics include:

  • ClusterStatus.red, ClusterStatus.yellow: Indicates cluster health status.
  • JVMMemoryPressure: JVM heap usage percentage.
  • CPUUtilization: CPU usage of the nodes.
  • DiskQueueDepth: Number of requests waiting in the disk queue.
  • SearchRate, IndexingRate: Throughput of search and indexing operations.
  • MasterCPUUtilization: CPU utilization specifically for master nodes.

You can create CloudWatch Alarms directly from the OpenSearch Service console or via the CloudWatch console. For example, an alarm on ClusterStatus.red or ClusterStatus.yellow with a threshold of 1 for a duration of 5 minutes is a standard practice.

For log analysis, configure OpenSearch Service to publish Slow Logs (search and index) and Application Logs to CloudWatch Logs. This is crucial for performance tuning and debugging.

Alerting Strategies and Best Practices

Effective alerting is the culmination of good monitoring. Aim for actionable alerts that indicate a real problem requiring intervention.

  • Thresholds: Set thresholds based on historical data and performance baselines. Avoid overly sensitive alerts that lead to alert fatigue.
  • Duration: Use evaluation periods (e.g., “for the last 5 minutes”) to avoid flapping alerts caused by transient spikes.
  • Notification Channels: Integrate CloudWatch Alarms with SNS topics to send notifications via email, Slack (using Lambda or Chatbot), PagerDuty, or other incident management tools.
  • Correlation: For complex systems, consider using CloudWatch Anomaly Detection or third-party tools to correlate alerts across different services and pinpoint root causes more effectively.
  • Dashboards: Create CloudWatch Dashboards that consolidate key metrics for both your C++ applications and Elasticsearch/OpenSearch clusters. This provides a single pane of glass for operational visibility.

By combining application-level health checks, robust process management, and detailed metric collection for both your C++ applications and Elasticsearch/OpenSearch clusters, you can build a resilient and observable system on AWS.

Primary Sidebar

A little about the Author

Having 9+ Years of Experience in Software Development.
Expertised in Php Development, WordPress Custom Theme Development (From scratch using underscores or Genesis Framework or using any blank theme or Premium Theme), Custom Plugin Development. Hands on Experience on 3rd Party Php Extension like Chilkat, nSoftware.

Recent Posts

  • Disaster Recovery 101: Architecting Auto-Failovers for Redis and PHP Deployments on OVH
  • How We Audited a High-Traffic WooCommerce Enterprise Stack on Google Cloud and Mitigated Race conditions during high-concurrency payment processing
  • Disaster Recovery 101: Architecting Auto-Failovers for Elasticsearch and Magento 2 Deployments on DigitalOcean
  • An Auditor’s Checklist for Securing WordPress Backends on OVH
  • Step-by-Step: Diagnosing Perl script high CPU throttling due to unoptimized regular expressions on AWS Servers

Copyright © 2026 · Vinay Vengala