From 10999db47f7ff0b4650aef13acf30d8adca0437e Mon Sep 17 00:00:00 2001 From: HadhemiDD Date: Wed, 5 Nov 2025 12:31:26 +0100 Subject: [PATCH 01/20] n8n initial draft --- n8n/CHANGELOG.md | 4 + n8n/README.md | 60 ++ n8n/assets/configuration/spec.yaml | 44 ++ n8n/assets/dashboards/n8n_overview.json | 1 + n8n/changelog.d/1.added | 1 + n8n/datadog_checks/n8n/__about__.py | 4 + n8n/datadog_checks/n8n/__init__.py | 7 + n8n/datadog_checks/n8n/check.py | 100 +++ .../n8n/config_models/__init__.py | 24 + .../n8n/config_models/defaults.py | 132 ++++ .../n8n/config_models/instance.py | 173 +++++ .../n8n/config_models/shared.py | 45 ++ .../n8n/config_models/validators.py | 13 + n8n/datadog_checks/n8n/data/conf.yaml.example | 642 ++++++++++++++++++ n8n/datadog_checks/n8n/metrics.py | 65 ++ n8n/hatch.toml | 4 + n8n/manifest.json | 54 ++ n8n/metadata.csv | 64 ++ n8n/pyproject.toml | 60 ++ n8n/tests/__init__.py | 3 + n8n/tests/common.py | 94 +++ n8n/tests/conftest.py | 32 + n8n/tests/docker/Dockerfile | 13 + n8n/tests/docker/README.md | 88 +++ n8n/tests/docker/docker-compose.yml | 41 ++ n8n/tests/fixtures/n8n.txt | 327 +++++++++ n8n/tests/test_e2e.py | 12 + n8n/tests/test_unit.py | 42 ++ 28 files changed, 2149 insertions(+) create mode 100644 n8n/CHANGELOG.md create mode 100644 n8n/README.md create mode 100644 n8n/assets/configuration/spec.yaml create mode 100644 n8n/assets/dashboards/n8n_overview.json create mode 100644 n8n/changelog.d/1.added create mode 100644 n8n/datadog_checks/n8n/__about__.py create mode 100644 n8n/datadog_checks/n8n/__init__.py create mode 100644 n8n/datadog_checks/n8n/check.py create mode 100644 n8n/datadog_checks/n8n/config_models/__init__.py create mode 100644 n8n/datadog_checks/n8n/config_models/defaults.py create mode 100644 n8n/datadog_checks/n8n/config_models/instance.py create mode 100644 n8n/datadog_checks/n8n/config_models/shared.py create mode 100644 n8n/datadog_checks/n8n/config_models/validators.py create mode 100644 n8n/datadog_checks/n8n/data/conf.yaml.example create mode 100644 n8n/datadog_checks/n8n/metrics.py create mode 100644 n8n/hatch.toml create mode 100644 n8n/manifest.json create mode 100644 n8n/metadata.csv create mode 100644 n8n/pyproject.toml create mode 100644 n8n/tests/__init__.py create mode 100644 n8n/tests/common.py create mode 100644 n8n/tests/conftest.py create mode 100644 n8n/tests/docker/Dockerfile create mode 100644 n8n/tests/docker/README.md create mode 100644 n8n/tests/docker/docker-compose.yml create mode 100644 n8n/tests/fixtures/n8n.txt create mode 100644 n8n/tests/test_e2e.py create mode 100644 n8n/tests/test_unit.py diff --git a/n8n/CHANGELOG.md b/n8n/CHANGELOG.md new file mode 100644 index 0000000000000..13505aa587aeb --- /dev/null +++ b/n8n/CHANGELOG.md @@ -0,0 +1,4 @@ +# CHANGELOG - n8n + + + diff --git a/n8n/README.md b/n8n/README.md new file mode 100644 index 0000000000000..f127a12e932d6 --- /dev/null +++ b/n8n/README.md @@ -0,0 +1,60 @@ +# Agent Check: n8n + +## Overview + +This check monitors [n8n][1] through the Datadog Agent. + +Include a high level overview of what this integration does: +- What does your product do (in 1-2 sentences)? +- What value will customers get from this integration, and why is it valuable to them? +- What specific data will your integration monitor, and what's the value of that data? + +## Setup + +Follow the instructions below to install and configure this check for an Agent running on a host. For containerized environments, see the [Autodiscovery Integration Templates][3] for guidance on applying these instructions. + +### Installation + +The n8n check is included in the [Datadog Agent][2] package. +No additional installation is needed on your server. + +### Configuration + +1. Edit the `n8n.d/conf.yaml` file, in the `conf.d/` folder at the root of your Agent's configuration directory to start collecting your n8n performance data. See the [sample n8n.d/conf.yaml][4] for all available configuration options. + +2. [Restart the Agent][5]. + +### Validation + +[Run the Agent's status subcommand][6] and look for `n8n` under the Checks section. + +## Data Collected + +### Metrics + +See [metadata.csv][7] for a list of metrics provided by this integration. + +### Events + +The n8n integration does not include any events. + +### Service Checks + +The n8n integration does not include any service checks. + +See [service_checks.json][8] for a list of service checks provided by this integration. + +## Troubleshooting + +Need help? Contact [Datadog support][9]. + + +[1]: **LINK_TO_INTEGRATION_SITE** +[2]: https://app.datadoghq.com/account/settings/agent/latest +[3]: https://docs.datadoghq.com/containers/kubernetes/integrations/ +[4]: https://github.com/DataDog/integrations-core/blob/master/n8n/datadog_checks/n8n/data/conf.yaml.example +[5]: https://docs.datadoghq.com/agent/configuration/agent-commands/#start-stop-and-restart-the-agent +[6]: https://docs.datadoghq.com/agent/configuration/agent-commands/#agent-status-and-information +[7]: https://github.com/DataDog/integrations-core/blob/master/n8n/metadata.csv +[8]: https://github.com/DataDog/integrations-core/blob/master/n8n/assets/service_checks.json +[9]: https://docs.datadoghq.com/help/ diff --git a/n8n/assets/configuration/spec.yaml b/n8n/assets/configuration/spec.yaml new file mode 100644 index 0000000000000..99ae3e00b03b6 --- /dev/null +++ b/n8n/assets/configuration/spec.yaml @@ -0,0 +1,44 @@ +name: n8n +files: +- name: n8n.yaml + options: + - template: init_config + options: + - template: init_config/default + - template: instances + options: + - template: instances/openmetrics + overrides: + openmetrics_endpoint.required: true + openmetrics_endpoint.hidden: false + openmetrics_endpoint.display_priority: 1 + openmetrics_endpoint.value.example: http://localhost:5678 + openmetrics_endpoint.description: | + Endpoint exposing the n8n's metrics in the OpenMetrics format. For more information refer to: + https://docs.n8n.io/hosting/logging-monitoring/monitoring/ + https://docs.n8n.io/hosting/configuration/environment-variables/endpoints/ + raw_metric_prefix.description: | + The prefix prepended to all metrics from n8n. + If not set, the default prefix will be used. + The default prefix is 'n8n'. + If you are using a custom prefix in n8n through N8N_METRICS_PREFIX, you can set it here. + raw_metric_prefix.value: + display_default: n8n + type: string + example: n8n + raw_metric_prefix.hidden: false + - name: server_port + description: | + The port exposing the HTTP Endpoint of the N8N API. + value: + display_default: 5678 + type: integer + - template: logs + example: + - type: file + path: /var/log/n8n/*.log + source: n8n + service: + - type: docker + source: n8n + service: diff --git a/n8n/assets/dashboards/n8n_overview.json b/n8n/assets/dashboards/n8n_overview.json new file mode 100644 index 0000000000000..e9e23301af626 --- /dev/null +++ b/n8n/assets/dashboards/n8n_overview.json @@ -0,0 +1 @@ +Please build an out-of-the-box dashboard for your integration following our best practices here: https://datadoghq.dev/integrations-core/guidelines/dashboards/#best-practices \ No newline at end of file diff --git a/n8n/changelog.d/1.added b/n8n/changelog.d/1.added new file mode 100644 index 0000000000000..aa949b47b7b41 --- /dev/null +++ b/n8n/changelog.d/1.added @@ -0,0 +1 @@ +Initial Release \ No newline at end of file diff --git a/n8n/datadog_checks/n8n/__about__.py b/n8n/datadog_checks/n8n/__about__.py new file mode 100644 index 0000000000000..1bde5986a04b2 --- /dev/null +++ b/n8n/datadog_checks/n8n/__about__.py @@ -0,0 +1,4 @@ +# (C) Datadog, Inc. 2025-present +# All rights reserved +# Licensed under a 3-clause BSD style license (see LICENSE) +__version__ = '0.0.1' diff --git a/n8n/datadog_checks/n8n/__init__.py b/n8n/datadog_checks/n8n/__init__.py new file mode 100644 index 0000000000000..5aecd3a74de9d --- /dev/null +++ b/n8n/datadog_checks/n8n/__init__.py @@ -0,0 +1,7 @@ +# (C) Datadog, Inc. 2025-present +# All rights reserved +# Licensed under a 3-clause BSD style license (see LICENSE) +from .__about__ import __version__ +from .check import N8nCheck + +__all__ = ['__version__', 'N8nCheck'] diff --git a/n8n/datadog_checks/n8n/check.py b/n8n/datadog_checks/n8n/check.py new file mode 100644 index 0000000000000..0b677ac147c90 --- /dev/null +++ b/n8n/datadog_checks/n8n/check.py @@ -0,0 +1,100 @@ +# (C) Datadog, Inc. 2025-present +# All rights reserved +# Licensed under a 3-clause BSD style license (see LICENSE) +from typing import Any +from urllib.parse import urljoin, urlparse # noqa: F401 + +from datadog_checks.base import AgentCheck, OpenMetricsBaseCheckV2 +from datadog_checks.n8n.metrics import METRIC_MAP + +DEFAULT_READY_ENDPOINT = '/healthz/readiness' +DEFAULT_HEALTH_ENDPOINT = '/healthz' +DEFAULT_VERSION_ENDPOINT = '/rest/settings' + + +class N8nCheck(OpenMetricsBaseCheckV2): + # TODO Decide later if we want to use customer prefix or ignore it. + __NAMESPACE__ = 'n8n' + DEFAULT_METRIC_LIMIT = 0 + + def __init__(self, name, init_config, instances=None): + super(N8nCheck, self).__init__( + name, + init_config, + instances, + ) + self.openmetrics_endpoint = self.instance["openmetrics_endpoint"] + self.tags = self.instance.get('tags', []) + self._ready_endpoint = DEFAULT_READY_ENDPOINT + self._health_endpoint = DEFAULT_HEALTH_ENDPOINT + self._version_endpoint = DEFAULT_VERSION_ENDPOINT + # Get the N8N API port if specified, otherwise use the default 5678. + self.server_port = str(self.instance.get('server_port', 5678)) + self.raw_metric_prefix = self.instance.get('raw_metric_prefix', 'n8n') + + + def get_default_config(self): + # If raw_metric_prefix is 'n8n', metrics start with 'n8n' + if self.raw_metric_prefix == 'n8n': + namespace = 'n8n' + else: + namespace = f'n8n.{self.raw_metric_prefix}' + + return {'namespace': namespace, 'metrics': [METRIC_MAP]} + + @AgentCheck.metadata_entrypoint + def _submit_version_metadata(self): + endpoint = urljoin(self.openmetrics_endpoint, self._version_endpoint) + response = self.http.get(endpoint) + + if response.ok: + data = response.json() + version = data.get("versionCli", "") + version_split = version.split(".") + if len(version_split) >= 3: + major = version_split[0] + minor = version_split[1] + patch = version_split[2] + + version_raw = f'{major}.{minor}.{patch}' + + version_parts = { + 'major': major, + 'minor': minor, + 'patch': patch, + } + self.set_metadata('version', version_raw, scheme='semver', part_map=version_parts) + else: + self.log.debug("Malformed N8N Server version format: %s", version) + else: + self.log.debug("Could not retrieve version metadata.") + + def _check_n8n_health(self): + endpoint = urljoin(self.openmetrics_endpoint, self._health_endpoint) + response = self.http.get(endpoint) + + # Any 4xx or 5xx response from the API endpoint (/healthz) means the n8n process is not responding + if 400 <= response.status_code and response.status_code < 600: + self.service_check('health.status', AgentCheck.CRITICAL, self.tags) + if response.status_code == 200: + self.service_check('health.status', AgentCheck.OK, self.tags) + else: + self.service_check('health.status', AgentCheck.UNKNOWN, self.tags) + + def _check_n8n_readiness(self): + endpoint = urljoin(self.openmetrics_endpoint, self._ready_endpoint) + response = self.http.get(endpoint) + + # Any 4xx or 5xx response from the API endpoint (/healthz/readiness) means the n8n is not ready to accept requests + if 400 <= response.status_code and response.status_code < 600: + self.service_check('health.status', AgentCheck.CRITICAL, self.tags) + if response.status_code == 200: + self.service_check('health.status', AgentCheck.OK, self.tags) + else: + self.service_check('health.status', AgentCheck.UNKNOWN, self.tags) + + def check(self, instance): + super().check(instance) + self._submit_version_metadata() + self._check_n8n_health() + self._check_n8n_readiness() \ No newline at end of file diff --git a/n8n/datadog_checks/n8n/config_models/__init__.py b/n8n/datadog_checks/n8n/config_models/__init__.py new file mode 100644 index 0000000000000..4d80b1e478f62 --- /dev/null +++ b/n8n/datadog_checks/n8n/config_models/__init__.py @@ -0,0 +1,24 @@ +# (C) Datadog, Inc. 2025-present +# All rights reserved +# Licensed under a 3-clause BSD style license (see LICENSE) + +# This file is autogenerated. +# To change this file you should edit assets/configuration/spec.yaml and then run the following commands: +# ddev -x validate config -s +# ddev -x validate models -s + +from .instance import InstanceConfig +from .shared import SharedConfig + + +class ConfigMixin: + _config_model_instance: InstanceConfig + _config_model_shared: SharedConfig + + @property + def config(self) -> InstanceConfig: + return self._config_model_instance + + @property + def shared_config(self) -> SharedConfig: + return self._config_model_shared diff --git a/n8n/datadog_checks/n8n/config_models/defaults.py b/n8n/datadog_checks/n8n/config_models/defaults.py new file mode 100644 index 0000000000000..4135203c1eefd --- /dev/null +++ b/n8n/datadog_checks/n8n/config_models/defaults.py @@ -0,0 +1,132 @@ +# (C) Datadog, Inc. 2025-present +# All rights reserved +# Licensed under a 3-clause BSD style license (see LICENSE) + +# This file is autogenerated. +# To change this file you should edit assets/configuration/spec.yaml and then run the following commands: +# ddev -x validate config -s +# ddev -x validate models -s + + +def instance_allow_redirects(): + return True + + +def instance_auth_type(): + return 'basic' + + +def instance_cache_metric_wildcards(): + return True + + +def instance_cache_shared_labels(): + return True + + +def instance_collect_counters_with_distributions(): + return False + + +def instance_collect_histogram_buckets(): + return True + + +def instance_disable_generic_tags(): + return False + + +def instance_empty_default_hostname(): + return False + + +def instance_enable_health_service_check(): + return True + + +def instance_histogram_buckets_as_distributions(): + return False + + +def instance_ignore_connection_errors(): + return False + + +def instance_kerberos_auth(): + return 'disabled' + + +def instance_kerberos_delegate(): + return False + + +def instance_kerberos_force_initiate(): + return False + + +def instance_log_requests(): + return False + + +def instance_min_collection_interval(): + return 15 + + +def instance_non_cumulative_histogram_buckets(): + return False + + +def instance_persist_connections(): + return False + + +def instance_raw_metric_prefix(): + return 'n8n' + + +def instance_request_size(): + return 16 + + +def instance_server_port(): + return 5678 + + +def instance_skip_proxy(): + return False + + +def instance_tag_by_endpoint(): + return True + + +def instance_telemetry(): + return False + + +def instance_timeout(): + return 10 + + +def instance_tls_ignore_warning(): + return False + + +def instance_tls_use_host_header(): + return False + + +def instance_tls_verify(): + return True + + +def instance_use_latest_spec(): + return False + + +def instance_use_legacy_auth_encoding(): + return True + + +def instance_use_process_start_time(): + return False diff --git a/n8n/datadog_checks/n8n/config_models/instance.py b/n8n/datadog_checks/n8n/config_models/instance.py new file mode 100644 index 0000000000000..e0d208ce314fa --- /dev/null +++ b/n8n/datadog_checks/n8n/config_models/instance.py @@ -0,0 +1,173 @@ +# (C) Datadog, Inc. 2025-present +# All rights reserved +# Licensed under a 3-clause BSD style license (see LICENSE) + +# This file is autogenerated. +# To change this file you should edit assets/configuration/spec.yaml and then run the following commands: +# ddev -x validate config -s +# ddev -x validate models -s + +from __future__ import annotations + +from types import MappingProxyType +from typing import Any, Optional, Union + +from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator + +from datadog_checks.base.utils.functions import identity +from datadog_checks.base.utils.models import validation + +from . import defaults, validators + + +class AuthToken(BaseModel): + model_config = ConfigDict( + arbitrary_types_allowed=True, + frozen=True, + ) + reader: Optional[MappingProxyType[str, Any]] = None + writer: Optional[MappingProxyType[str, Any]] = None + + +class ExtraMetrics(BaseModel): + model_config = ConfigDict( + arbitrary_types_allowed=True, + extra='allow', + frozen=True, + ) + name: Optional[str] = None + type: Optional[str] = None + + +class MetricPatterns(BaseModel): + model_config = ConfigDict( + arbitrary_types_allowed=True, + frozen=True, + ) + exclude: Optional[tuple[str, ...]] = None + include: Optional[tuple[str, ...]] = None + + +class Metrics(BaseModel): + model_config = ConfigDict( + arbitrary_types_allowed=True, + extra='allow', + frozen=True, + ) + name: Optional[str] = None + type: Optional[str] = None + + +class Proxy(BaseModel): + model_config = ConfigDict( + arbitrary_types_allowed=True, + frozen=True, + ) + http: Optional[str] = None + https: Optional[str] = None + no_proxy: Optional[tuple[str, ...]] = None + + +class ShareLabels(BaseModel): + model_config = ConfigDict( + arbitrary_types_allowed=True, + frozen=True, + ) + labels: Optional[tuple[str, ...]] = None + match: Optional[tuple[str, ...]] = None + + +class InstanceConfig(BaseModel): + model_config = ConfigDict( + validate_default=True, + arbitrary_types_allowed=True, + frozen=True, + ) + allow_redirects: Optional[bool] = None + auth_token: Optional[AuthToken] = None + auth_type: Optional[str] = None + aws_host: Optional[str] = None + aws_region: Optional[str] = None + aws_service: Optional[str] = None + cache_metric_wildcards: Optional[bool] = None + cache_shared_labels: Optional[bool] = None + collect_counters_with_distributions: Optional[bool] = None + collect_histogram_buckets: Optional[bool] = None + connect_timeout: Optional[float] = None + disable_generic_tags: Optional[bool] = None + empty_default_hostname: Optional[bool] = None + enable_health_service_check: Optional[bool] = None + exclude_labels: Optional[tuple[str, ...]] = None + exclude_metrics: Optional[tuple[str, ...]] = None + exclude_metrics_by_labels: Optional[MappingProxyType[str, Union[bool, tuple[str, ...]]]] = None + extra_headers: Optional[MappingProxyType[str, Any]] = None + extra_metrics: Optional[tuple[Union[str, MappingProxyType[str, Union[str, ExtraMetrics]]], ...]] = None + headers: Optional[MappingProxyType[str, Any]] = None + histogram_buckets_as_distributions: Optional[bool] = None + hostname_format: Optional[str] = None + hostname_label: Optional[str] = None + ignore_connection_errors: Optional[bool] = None + ignore_tags: Optional[tuple[str, ...]] = None + include_labels: Optional[tuple[str, ...]] = None + kerberos_auth: Optional[str] = None + kerberos_cache: Optional[str] = None + kerberos_delegate: Optional[bool] = None + kerberos_force_initiate: Optional[bool] = None + kerberos_hostname: Optional[str] = None + kerberos_keytab: Optional[str] = None + kerberos_principal: Optional[str] = None + log_requests: Optional[bool] = None + metric_patterns: Optional[MetricPatterns] = None + metrics: Optional[tuple[Union[str, MappingProxyType[str, Union[str, Metrics]]], ...]] = None + min_collection_interval: Optional[float] = None + namespace: Optional[str] = Field(None, pattern='\\w*') + non_cumulative_histogram_buckets: Optional[bool] = None + ntlm_domain: Optional[str] = None + openmetrics_endpoint: str + password: Optional[str] = None + persist_connections: Optional[bool] = None + proxy: Optional[Proxy] = None + raw_line_filters: Optional[tuple[str, ...]] = None + raw_metric_prefix: Optional[str] = None + read_timeout: Optional[float] = None + rename_labels: Optional[MappingProxyType[str, Any]] = None + request_size: Optional[float] = None + server_port: Optional[int] = None + service: Optional[str] = None + share_labels: Optional[MappingProxyType[str, Union[bool, ShareLabels]]] = None + skip_proxy: Optional[bool] = None + tag_by_endpoint: Optional[bool] = None + tags: Optional[tuple[str, ...]] = None + telemetry: Optional[bool] = None + timeout: Optional[float] = None + tls_ca_cert: Optional[str] = None + tls_cert: Optional[str] = None + tls_ciphers: Optional[tuple[str, ...]] = None + tls_ignore_warning: Optional[bool] = None + tls_private_key: Optional[str] = None + tls_protocols_allowed: Optional[tuple[str, ...]] = None + tls_use_host_header: Optional[bool] = None + tls_verify: Optional[bool] = None + use_latest_spec: Optional[bool] = None + use_legacy_auth_encoding: Optional[bool] = None + use_process_start_time: Optional[bool] = None + username: Optional[str] = None + + @model_validator(mode='before') + def _initial_validation(cls, values): + return validation.core.initialize_config(getattr(validators, 'initialize_instance', identity)(values)) + + @field_validator('*', mode='before') + def _validate(cls, value, info): + field = cls.model_fields[info.field_name] + field_name = field.alias or info.field_name + if field_name in info.context['configured_fields']: + value = getattr(validators, f'instance_{info.field_name}', identity)(value, field=field) + else: + value = getattr(defaults, f'instance_{info.field_name}', lambda: value)() + + return validation.utils.make_immutable(value) + + @model_validator(mode='after') + def _final_validation(cls, model): + return validation.core.check_model(getattr(validators, 'check_instance', identity)(model)) diff --git a/n8n/datadog_checks/n8n/config_models/shared.py b/n8n/datadog_checks/n8n/config_models/shared.py new file mode 100644 index 0000000000000..8721d9e284e5f --- /dev/null +++ b/n8n/datadog_checks/n8n/config_models/shared.py @@ -0,0 +1,45 @@ +# (C) Datadog, Inc. 2025-present +# All rights reserved +# Licensed under a 3-clause BSD style license (see LICENSE) + +# This file is autogenerated. +# To change this file you should edit assets/configuration/spec.yaml and then run the following commands: +# ddev -x validate config -s +# ddev -x validate models -s + +from __future__ import annotations + +from typing import Optional + +from pydantic import BaseModel, ConfigDict, field_validator, model_validator + +from datadog_checks.base.utils.functions import identity +from datadog_checks.base.utils.models import validation + +from . import validators + + +class SharedConfig(BaseModel): + model_config = ConfigDict( + validate_default=True, + arbitrary_types_allowed=True, + frozen=True, + ) + service: Optional[str] = None + + @model_validator(mode='before') + def _initial_validation(cls, values): + return validation.core.initialize_config(getattr(validators, 'initialize_shared', identity)(values)) + + @field_validator('*', mode='before') + def _validate(cls, value, info): + field = cls.model_fields[info.field_name] + field_name = field.alias or info.field_name + if field_name in info.context['configured_fields']: + value = getattr(validators, f'shared_{info.field_name}', identity)(value, field=field) + + return validation.utils.make_immutable(value) + + @model_validator(mode='after') + def _final_validation(cls, model): + return validation.core.check_model(getattr(validators, 'check_shared', identity)(model)) diff --git a/n8n/datadog_checks/n8n/config_models/validators.py b/n8n/datadog_checks/n8n/config_models/validators.py new file mode 100644 index 0000000000000..8495a481e5308 --- /dev/null +++ b/n8n/datadog_checks/n8n/config_models/validators.py @@ -0,0 +1,13 @@ +# (C) Datadog, Inc. 2025-present +# All rights reserved +# Licensed under a 3-clause BSD style license (see LICENSE) + +# Here you can include additional config validators or transformers +# +# def initialize_instance(values, **kwargs): +# if 'my_option' not in values and 'my_legacy_option' in values: +# values['my_option'] = values['my_legacy_option'] +# if values.get('my_number') > 10: +# raise ValueError('my_number max value is 10, got %s' % str(values.get('my_number'))) +# +# return values diff --git a/n8n/datadog_checks/n8n/data/conf.yaml.example b/n8n/datadog_checks/n8n/data/conf.yaml.example new file mode 100644 index 0000000000000..846be349a3407 --- /dev/null +++ b/n8n/datadog_checks/n8n/data/conf.yaml.example @@ -0,0 +1,642 @@ +## All options defined here are available to all instances. +# +init_config: + + ## @param service - string - optional + ## Attach the tag `service:` to every metric, event, and service check emitted by this integration. + ## + ## Additionally, this sets the default `service` for every log source. + # + # service: + +## Every instance is scheduled independently of the others. +# +instances: + + ## @param openmetrics_endpoint - string - required + ## Endpoint exposing the n8n's metrics in the OpenMetrics format. For more information refer to: + ## https://docs.n8n.io/hosting/logging-monitoring/monitoring/ + ## https://docs.n8n.io/hosting/configuration/environment-variables/endpoints/ + # + - openmetrics_endpoint: http://localhost:5678 + + ## @param raw_metric_prefix - string - optional - default: n8n + ## The prefix prepended to all metrics from n8n. + ## If not set, the default prefix will be used. + ## The default prefix is 'n8n'. + ## If you are using a custom prefix in n8n through N8N_METRICS_PREFIX, you can set it here. + # + # raw_metric_prefix: n8n + + ## @param extra_metrics - (list of string or mapping) - optional + ## This list defines metrics to collect from the `openmetrics_endpoint`, in addition to + ## what the check collects by default. If the check already collects a metric, then + ## metric definitions here take precedence. Metrics may be defined in 3 ways: + ## + ## 1. If the item is a string, then it represents the exposed metric name, and + ## the sent metric name will be identical. For example: + ## ``` + ## extra_metrics: + ## - + ## - + ## ``` + ## 2. If the item is a mapping, then the keys represent the exposed metric names. + ## + ## 1. If a value is a string, then it represents the sent metric name. For example: + ## ``` + ## extra_metrics: + ## - : + ## - : + ## ``` + ## 2. If a value is a mapping, then it must have a `name` and/or `type` key. + ## The `name` represents the sent metric name, and the `type` represents how + ## the metric should be handled, overriding any type information the endpoint + ## may provide. For example: + ## ``` + ## extra_metrics: + ## - : + ## name: + ## type: + ## - : + ## name: + ## type: + ## ``` + ## The supported native types are `gauge`, `counter`, `histogram`, and `summary`. + ## + ## Note: To collect counter metrics with names ending in `_total`, specify the metric name without the `_total` + ## suffix. For example, to collect the counter metric `promhttp_metric_handler_requests_total`, specify + ## `promhttp_metric_handler_requests`. This submits to Datadog the metric name appended with `.count`. + ## For more information, see: + ## https://github.com/OpenObservability/OpenMetrics/blob/main/specification/OpenMetrics.md#suffixes + ## + ## Regular expressions may be used to match the exposed metric names, for example: + ## ``` + ## extra_metrics: + ## - ^network_(ingress|egress)_.+ + ## - .+: + ## type: gauge + ## ``` + # + # extra_metrics: + # - + # - : + # - : + # name: + # type: + + ## @param exclude_metrics - list of strings - optional + ## A list of metrics to exclude, with each entry being either + ## the exact metric name or a regular expression. + ## In order to exclude all metrics but the ones matching a specific filter, + ## you can use a negative lookahead regex like: + ## - ^(?!foo).*$ + # + # exclude_metrics: [] + + ## @param exclude_metrics_by_labels - mapping - optional + ## A mapping of labels to exclude metrics with matching label name and their corresponding metric values. To match + ## all values of a label, set it to `true`. + ## + ## Note: Label filtering happens before `rename_labels`. + ## + ## For example, the following configuration instructs the check to exclude all metrics with + ## a label `worker` or a label `pid` with the value of either `23` or `42`. + ## + ## exclude_metrics_by_labels: + ## worker: true + ## pid: + ## - '23' + ## - '42' + # + # exclude_metrics_by_labels: {} + + ## @param exclude_labels - list of strings - optional + ## A list of labels to exclude, useful for high cardinality values like timestamps or UUIDs. + ## May be used in conjunction with `include_labels`. + ## Labels defined in `exclude_labels` will take precedence in case of overlap. + ## + ## Note: Label filtering happens before `rename_labels`. + # + # exclude_labels: [] + + ## @param include_labels - list of strings - optional + ## A list of labels to include. May be used in conjunction with `exclude_labels`. + ## Labels defined in `exclude_labels` will take precedence in case of overlap. + ## + ## Note: Label filtering happens before `rename_labels`. + # + # include_labels: [] + + ## @param rename_labels - mapping - optional + ## A mapping of label names to their new names. + # + # rename_labels: + # : + # : + + ## @param enable_health_service_check - boolean - optional - default: true + ## Whether or not to send a service check named `.openmetrics.health` which reports + ## the health of the `openmetrics_endpoint`. + # + # enable_health_service_check: true + + ## @param ignore_connection_errors - boolean - optional - default: false + ## Whether or not to ignore connection errors when scraping `openmetrics_endpoint`. + # + # ignore_connection_errors: false + + ## @param hostname_label - string - optional + ## Override the hostname for every metric submission with the value of one of its labels. + # + # hostname_label: + + ## @param hostname_format - string - optional + ## When `hostname_label` is set, this instructs the check how to format the values. The string + ## `` is replaced by the value of the label defined by `hostname_label`. + # + # hostname_format: + + ## @param collect_histogram_buckets - boolean - optional - default: true + ## Whether or not to send histogram buckets. + # + # collect_histogram_buckets: true + + ## @param non_cumulative_histogram_buckets - boolean - optional - default: false + ## Whether or not histogram buckets are non-cumulative and to come with a `lower_bound` tag. + # + # non_cumulative_histogram_buckets: false + + ## @param histogram_buckets_as_distributions - boolean - optional - default: false + ## Whether or not to send histogram buckets as Datadog distribution metrics. This implicitly + ## enables the `collect_histogram_buckets` and `non_cumulative_histogram_buckets` options. + ## + ## Learn more about distribution metrics: + ## https://docs.datadoghq.com/developers/metrics/types/?tab=distribution#metric-types + # + # histogram_buckets_as_distributions: false + + ## @param collect_counters_with_distributions - boolean - optional - default: false + ## Whether or not to also collect the observation counter metrics ending in `.sum` and `.count` + ## when sending histogram buckets as Datadog distribution metrics. This implicitly enables the + ## `histogram_buckets_as_distributions` option. + # + # collect_counters_with_distributions: false + + ## @param use_process_start_time - boolean - optional - default: false + ## Whether to enable a heuristic for reporting counter values on the first scrape. When true, + ## the first time an endpoint is scraped, check `process_start_time_seconds` to decide whether zero + ## initial value can be assumed for counters. This requires keeping metrics in memory until the entire + ## response is received. + # + # use_process_start_time: false + + ## @param share_labels - mapping - optional + ## This mapping allows for the sharing of labels across multiple metrics. The keys represent the + ## exposed metrics from which to share labels, and the values are mappings that configure the + ## sharing behavior. Each mapping must have at least one of the following keys: + ## + ## - labels - This is a list of labels to share. All labels are shared if this is not set. + ## - match - This is a list of labels to match on other metrics as a condition for sharing. + ## - values - This is a list of allowed values as a condition for sharing. + ## + ## To unconditionally share all labels of a metric, set it to `true`. + ## + ## For example, the following configuration instructs the check to apply all labels from `metric_a` + ## to all other metrics, the `node` label from `metric_b` to only those metrics that have a `pod` + ## label value that matches the `pod` label value of `metric_b`, and all labels from `metric_c` + ## to all other metrics if their value is equal to `23` or `42`. + # + # share_labels: + # metric_a: true + # metric_b: + # labels: + # - node + # match: + # - pod + # metric_c: + # values: + # - 23 + # - 42 + + ## @param cache_shared_labels - boolean - optional - default: true + ## When `share_labels` is set, it instructs the check to cache labels collected from the first payload + ## for improved performance. + ## + ## Set this to `false` to compute label sharing for every payload at the risk of potentially increased memory usage. + # + # cache_shared_labels: true + + ## @param raw_line_filters - list of strings - optional + ## A list of regular expressions used to exclude lines read from the `openmetrics_endpoint` + ## from being parsed. + # + # raw_line_filters: [] + + ## @param cache_metric_wildcards - boolean - optional - default: true + ## Whether or not to cache data from metrics that are defined by regular expressions rather + ## than the full metric name. + # + # cache_metric_wildcards: true + + ## @param telemetry - boolean - optional - default: false + ## Whether or not to submit metrics prefixed by `.telemetry.` for debugging purposes. + # + # telemetry: false + + ## @param ignore_tags - list of strings - optional + ## A list of regular expressions used to ignore tags added by Autodiscovery and entries in the `tags` option. + # + # ignore_tags: + # - + # - + # - + + ## @param proxy - mapping - optional + ## This overrides the `proxy` setting in `init_config`. + ## + ## Set HTTP or HTTPS proxies for this instance. Use the `no_proxy` list + ## to specify hosts that must bypass proxies. + ## + ## The SOCKS protocol is also supported, for example: + ## + ## socks5://user:pass@host:port + ## + ## Using the scheme `socks5` causes the DNS resolution to happen on the + ## client, rather than on the proxy server. This is in line with `curl`, + ## which uses the scheme to decide whether to do the DNS resolution on + ## the client or proxy. If you want to resolve the domains on the proxy + ## server, use `socks5h` as the scheme. + # + # proxy: + # http: http://: + # https: https://: + # no_proxy: + # - + # - + + ## @param skip_proxy - boolean - optional - default: false + ## This overrides the `skip_proxy` setting in `init_config`. + ## + ## If set to `true`, this makes the check bypass any proxy + ## settings enabled and attempt to reach services directly. + # + # skip_proxy: false + + ## @param auth_type - string - optional - default: basic + ## The type of authentication to use. The available types (and related options) are: + ## ``` + ## - basic + ## |__ username + ## |__ password + ## |__ use_legacy_auth_encoding + ## - digest + ## |__ username + ## |__ password + ## - ntlm + ## |__ ntlm_domain + ## |__ password + ## - kerberos + ## |__ kerberos_auth + ## |__ kerberos_cache + ## |__ kerberos_delegate + ## |__ kerberos_force_initiate + ## |__ kerberos_hostname + ## |__ kerberos_keytab + ## |__ kerberos_principal + ## - aws + ## |__ aws_region + ## |__ aws_host + ## |__ aws_service + ## ``` + ## The `aws` auth type relies on boto3 to automatically gather AWS credentials, for example: from `.aws/credentials`. + ## Details: https://boto3.amazonaws.com/v1/documentation/api/latest/guide/configuration.html#configuring-credentials + # + # auth_type: basic + + ## @param use_legacy_auth_encoding - boolean - optional - default: true + ## When `auth_type` is set to `basic`, this determines whether to encode as `latin1` rather than `utf-8`. + # + # use_legacy_auth_encoding: true + + ## @param username - string - optional + ## The username to use if services are behind basic or digest auth. + # + # username: + + ## @param password - string - optional + ## The password to use if services are behind basic or NTLM auth. + # + # password: + + ## @param ntlm_domain - string - optional + ## If your services use NTLM authentication, specify + ## the domain used in the check. For NTLM Auth, append + ## the username to domain, not as the `username` parameter. + # + # ntlm_domain: \ + + ## @param kerberos_auth - string - optional - default: disabled + ## If your services use Kerberos authentication, you can specify the Kerberos + ## strategy to use between: + ## + ## - required + ## - optional + ## - disabled + ## + ## See https://github.com/requests/requests-kerberos#mutual-authentication + # + # kerberos_auth: disabled + + ## @param kerberos_cache - string - optional + ## Sets the KRB5CCNAME environment variable. + ## It should point to a credential cache with a valid TGT. + # + # kerberos_cache: + + ## @param kerberos_delegate - boolean - optional - default: false + ## Set to `true` to enable Kerberos delegation of credentials to a server that requests delegation. + ## + ## See https://github.com/requests/requests-kerberos#delegation + # + # kerberos_delegate: false + + ## @param kerberos_force_initiate - boolean - optional - default: false + ## Set to `true` to preemptively initiate the Kerberos GSS exchange and + ## present a Kerberos ticket on the initial request (and all subsequent). + ## + ## See https://github.com/requests/requests-kerberos#preemptive-authentication + # + # kerberos_force_initiate: false + + ## @param kerberos_hostname - string - optional + ## Override the hostname used for the Kerberos GSS exchange if its DNS name doesn't + ## match its Kerberos hostname, for example: behind a content switch or load balancer. + ## + ## See https://github.com/requests/requests-kerberos#hostname-override + # + # kerberos_hostname: + + ## @param kerberos_principal - string - optional + ## Set an explicit principal, to force Kerberos to look for a + ## matching credential cache for the named user. + ## + ## See https://github.com/requests/requests-kerberos#explicit-principal + # + # kerberos_principal: + + ## @param kerberos_keytab - string - optional + ## Set the path to your Kerberos key tab file. + # + # kerberos_keytab: + + ## @param auth_token - mapping - optional + ## This allows for the use of authentication information from dynamic sources. + ## Both a reader and writer must be configured. + ## + ## The available readers are: + ## + ## - type: file + ## path (required): The absolute path for the file to read from. + ## pattern: A regular expression pattern with a single capture group used to find the + ## token rather than using the entire file, for example: Your secret is (.+) + ## - type: oauth + ## url (required): The token endpoint. + ## client_id (required): The client identifier. + ## client_secret (required): The client secret. + ## basic_auth: Whether the provider expects credentials to be transmitted in + ## an HTTP Basic Auth header. The default is: false + ## options: Mapping of additional options to pass to the provider, such as the audience + ## or the scope. For example: + ## options: + ## audience: https://example.com + ## scope: read:example + ## + ## The available writers are: + ## + ## - type: header + ## name (required): The name of the field, for example: Authorization + ## value: The template value, for example `Bearer `. The default is: + ## placeholder: The substring in `value` to replace with the token, defaults to: + # + # auth_token: + # reader: + # type: + # : + # : + # writer: + # type: + # : + # : + + ## @param aws_region - string - optional + ## If your services require AWS Signature Version 4 signing, set the region. + ## + ## See https://docs.aws.amazon.com/general/latest/gr/signature-version-4.html + # + # aws_region: + + ## @param aws_host - string - optional + ## If your services require AWS Signature Version 4 signing, set the host. + ## This only needs the hostname and does not require the protocol (HTTP, HTTPS, and more). + ## For example, if connecting to https://us-east-1.amazonaws.com/, set `aws_host` to `us-east-1.amazonaws.com`. + ## + ## Note: This setting is not necessary for official integrations. + ## + ## See https://docs.aws.amazon.com/general/latest/gr/signature-version-4.html + # + # aws_host: + + ## @param aws_service - string - optional + ## If your services require AWS Signature Version 4 signing, set the service code. For a list + ## of available service codes, see https://docs.aws.amazon.com/general/latest/gr/rande.html + ## + ## Note: This setting is not necessary for official integrations. + ## + ## See https://docs.aws.amazon.com/general/latest/gr/signature-version-4.html + # + # aws_service: + + ## @param tls_verify - boolean - optional - default: true + ## Instructs the check to validate the TLS certificate of services. + # + # tls_verify: true + + ## @param tls_use_host_header - boolean - optional - default: false + ## If a `Host` header is set, this enables its use for SNI (matching against the TLS certificate CN or SAN). + # + # tls_use_host_header: false + + ## @param tls_ignore_warning - boolean - optional - default: false + ## If `tls_verify` is disabled, security warnings are logged by the check. + ## Disable those by setting `tls_ignore_warning` to true. + # + # tls_ignore_warning: false + + ## @param tls_cert - string - optional + ## The path to a single file in PEM format containing a certificate as well as any + ## number of CA certificates needed to establish the certificate's authenticity for + ## use when connecting to services. It may also contain an unencrypted private key to use. + # + # tls_cert: + + ## @param tls_private_key - string - optional + ## The unencrypted private key to use for `tls_cert` when connecting to services. This is + ## required if `tls_cert` is set and it does not already contain a private key. + # + # tls_private_key: + + ## @param tls_ca_cert - string - optional + ## The path to a file of concatenated CA certificates in PEM format or a directory + ## containing several CA certificates in PEM format. If a directory, the directory + ## must have been processed using the `openssl rehash` command. See: + ## https://www.openssl.org/docs/man3.2/man1/c_rehash.html + # + # tls_ca_cert: + + ## @param tls_protocols_allowed - list of strings - optional + ## The expected versions of TLS/SSL when fetching intermediate certificates. + ## Only `SSLv3`, `TLSv1.2`, `TLSv1.3` are allowed by default. The possible values are: + ## SSLv3 + ## TLSv1 + ## TLSv1.1 + ## TLSv1.2 + ## TLSv1.3 + # + # tls_protocols_allowed: + # - SSLv3 + # - TLSv1.2 + # - TLSv1.3 + + ## @param tls_ciphers - list of strings - optional + ## The list of ciphers suites to use when connecting to an endpoint. If not specified, + ## `ALL` ciphers are used. For list of ciphers see: + ## https://www.openssl.org/docs/man1.0.2/man1/ciphers.html + # + # tls_ciphers: + # - TLS_AES_256_GCM_SHA384 + # - TLS_CHACHA20_POLY1305_SHA256 + # - TLS_AES_128_GCM_SHA256 + + ## @param headers - mapping - optional + ## The headers parameter allows you to send specific headers with every request. + ## You can use it for explicitly specifying the host header or adding headers for + ## authorization purposes. + ## + ## This overrides any default headers. + # + # headers: + # Host: + # X-Auth-Token: + + ## @param extra_headers - mapping - optional + ## Additional headers to send with every request. + # + # extra_headers: + # Host: + # X-Auth-Token: + + ## @param timeout - number - optional - default: 10 + ## The timeout for accessing services. + ## + ## This overrides the `timeout` setting in `init_config`. + # + # timeout: 10 + + ## @param connect_timeout - number - optional + ## The connect timeout for accessing services. Defaults to `timeout`. + # + # connect_timeout: + + ## @param read_timeout - number - optional + ## The read timeout for accessing services. Defaults to `timeout`. + # + # read_timeout: + + ## @param request_size - number - optional - default: 16 + ## The number of kibibytes (KiB) to read from streaming HTTP responses at a time. + # + # request_size: 16 + + ## @param log_requests - boolean - optional - default: false + ## Whether or not to debug log the HTTP(S) requests made, including the method and URL. + # + # log_requests: false + + ## @param persist_connections - boolean - optional - default: false + ## Whether or not to persist cookies and use connection pooling for improved performance. + # + # persist_connections: false + + ## @param allow_redirects - boolean - optional - default: true + ## Whether or not to allow URL redirection. + # + # allow_redirects: true + + ## @param tags - list of strings - optional + ## A list of tags to attach to every metric and service check emitted by this instance. + ## + ## Learn more about tagging at https://docs.datadoghq.com/tagging + # + # tags: + # - : + # - : + + ## @param service - string - optional + ## Attach the tag `service:` to every metric, event, and service check emitted by this integration. + ## + ## Overrides any `service` defined in the `init_config` section. + # + # service: + + ## @param min_collection_interval - number - optional - default: 15 + ## This changes the collection interval of the check. For more information, see: + ## https://docs.datadoghq.com/developers/write_agent_check/#collection-interval + # + # min_collection_interval: 15 + + ## @param empty_default_hostname - boolean - optional - default: false + ## This forces the check to send metrics with no hostname. + ## + ## This is useful for cluster-level checks. + # + # empty_default_hostname: false + + ## @param metric_patterns - mapping - optional + ## A mapping of metrics to include or exclude, with each entry being a regular expression. + ## + ## Metrics defined in `exclude` will take precedence in case of overlap. + # + # metric_patterns: + # include: + # - + # exclude: + # - + + ## @param server_port - integer - optional - default: 5678 + ## The port exposing the HTTP Endpoint of the N8N API. + # + # server_port: + +## Log Section +## +## type - required - Type of log input source (tcp / udp / file / windows_event). +## port / path / channel_path - required - Set port if type is tcp or udp. +## Set path if type is file. +## Set channel_path if type is windows_event. +## source - required - Attribute that defines which integration sent the logs. +## encoding - optional - For file specifies the file encoding. Default is utf-8. Other +## possible values are utf-16-le and utf-16-be. +## service - optional - The name of the service that generates the log. +## Overrides any `service` defined in the `init_config` section. +## tags - optional - Add tags to the collected logs. +## +## Discover Datadog log collection: https://docs.datadoghq.com/logs/log_collection/ +# +# logs: +# - type: file +# path: /var/log/n8n/*.log +# source: n8n +# service: +# - type: docker +# source: n8n +# service: diff --git a/n8n/datadog_checks/n8n/metrics.py b/n8n/datadog_checks/n8n/metrics.py new file mode 100644 index 0000000000000..caa24d5f4aeb2 --- /dev/null +++ b/n8n/datadog_checks/n8n/metrics.py @@ -0,0 +1,65 @@ +# (C) Datadog, Inc. 2025-present +# All rights reserved +# Licensed under a 3-clause BSD style license (see LICENSE) + +# Metrics mapping without prefix - use raw_metric_prefix config to strip prefixes like 'n8n_', 'n8n_my_team_', etc. +# Namespace will be applied by the check +METRIC_MAP = { + 'active_workflow_count': 'active.workflow.count', + 'api_request_duration_seconds': 'api.request.duration.seconds', + 'api_requests_total': 'api.requests.total', + 'cache_errors_total': 'cache.errors.total', + 'cache_hits_total': 'cache.hits.total', + 'cache_latency_seconds': 'cache.latency.seconds', + 'cache_misses_total': 'cache.misses.total', + 'cache_operations_total': 'cache.operations.total', + 'eventbus_connections_total': 'eventbus.connections.total', + 'eventbus_events_failed_total': 'eventbus.events.failed.total', + 'eventbus_events_processed_total': 'eventbus.events.processed.total', + 'eventbus_events_total': 'eventbus.events.total', + 'eventbus_queue_size': 'eventbus.queue.size', + 'http_request_duration_seconds': 'http.request.duration.seconds', + 'instance_role_leader': 'instance.role.leader', + 'last_activity': 'last.activity', + 'nodejs_active_handles': 'nodejs.active.handles', + 'nodejs_active_handles_total': 'nodejs.active.handles.total', + 'nodejs_active_requests': 'nodejs.active.requests', + 'nodejs_active_requests_total': 'nodejs.active.requests.total', + 'nodejs_active_resources': 'nodejs.active.resources', + 'nodejs_active_resources_total': 'nodejs.active.resources.total', + 'nodejs_event_loop_lag_seconds': 'nodejs.event.loop.lag.seconds', + 'nodejs_eventloop_lag_max_seconds': 'nodejs.eventloop.lag.max.seconds', + 'nodejs_eventloop_lag_mean_seconds': 'nodejs.eventloop.lag.mean.seconds', + 'nodejs_eventloop_lag_min_seconds': 'nodejs.eventloop.lag.min.seconds', + 'nodejs_eventloop_lag_p50_seconds': 'nodejs.eventloop.lag.p50.seconds', + 'nodejs_eventloop_lag_p90_seconds': 'nodejs.eventloop.lag.p90.seconds', + 'nodejs_eventloop_lag_p99_seconds': 'nodejs.eventloop.lag.p99.seconds', + 'nodejs_eventloop_lag_seconds': 'nodejs.eventloop.lag.seconds', + 'nodejs_eventloop_lag_stddev_seconds': 'nodejs.eventloop.lag.stddev.seconds', + 'nodejs_external_memory_bytes': 'nodejs.external.memory.bytes', + 'nodejs_gc_duration_seconds': 'nodejs.gc.duration.seconds', + 'nodejs_heap_size_total_bytes': 'nodejs.heap.size.total.bytes', + 'nodejs_heap_size_used_bytes': 'nodejs.heap.size.used.bytes', + 'nodejs_heap_space_size_available_bytes': 'nodejs.heap.space.size.available.bytes', + 'nodejs_heap_space_size_total_bytes': 'nodejs.heap.space.size.total.bytes', + 'nodejs_heap_space_size_used_bytes': 'nodejs.heap.space.size.used.bytes', + 'nodejs_heap_total_bytes': 'nodejs.heap.total.bytes', + 'nodejs_heap_used_bytes': 'nodejs.heap.used.bytes', + 'nodejs_version_info': 'nodejs.version.info', + 'process_cpu_system_seconds_total': 'process.cpu.system.seconds.total', + 'process_cpu_user_seconds_total': 'process.cpu.user.seconds.total', + 'process_heap_bytes': 'process.heap.bytes', + 'process_max_fds': 'process.max.fds', + 'process_open_fds': 'process.open.fds', + 'process_resident_memory_bytes': 'process.resident.memory.bytes', + 'process_start_time_seconds': 'process.start.time.seconds', + 'process_virtual_memory_bytes': 'process.virtual.memory.bytes', + 'queue_job_attempts_total': 'queue.job.attempts.total', + 'queue_jobs_duration_seconds': 'queue.jobs.duration.seconds', + 'queue_jobs_total': 'queue.jobs.total', + 'workflow_executions_active': 'workflow.executions.active', + 'workflow_executions_duration_seconds': 'workflow.executions.duration.seconds', + 'workflow_executions_total': 'workflow.executions.total', + 'process_cpu_seconds_total': 'process.cpu.seconds.total', + 'version_info': 'version.info', +} \ No newline at end of file diff --git a/n8n/hatch.toml b/n8n/hatch.toml new file mode 100644 index 0000000000000..fee2455000258 --- /dev/null +++ b/n8n/hatch.toml @@ -0,0 +1,4 @@ +[env.collectors.datadog-checks] + +[[envs.default.matrix]] +python = ["3.13"] diff --git a/n8n/manifest.json b/n8n/manifest.json new file mode 100644 index 0000000000000..82d73e61ecc01 --- /dev/null +++ b/n8n/manifest.json @@ -0,0 +1,54 @@ +{ + "manifest_version": "2.0.0", + "app_uuid": "4775a4f7-3e41-49db-8a3f-846d18f1a4c8", + "app_id": "n8n", + "display_on_public_website": false, + "tile": { + "overview": "README.md#Overview", + "configuration": "README.md#Setup", + "support": "README.md#Support", + "changelog": "CHANGELOG.md", + "description": "", + "title": "n8n", + "media": [], + "classifier_tags": [ + "", + "Supported OS::Linux", + "Supported OS::Windows", + "Supported OS::macOS", + "Category::", + "Offering::", + "Queried Data Type::", + "Submitted Data Type::" + ] + }, + "assets": { + "integration": { + "auto_install": true, + "source_type_id": 61226509, + "source_type_name": "n8n", + "configuration": { + "spec": "assets/configuration/spec.yaml" + }, + "events": { + "creates_events": false + }, + "metrics": { + "prefix": "n8n.", + "check": "", + "metadata_path": "metadata.csv" + } + }, + "dashboards": { + "": "assets/dashboards/.json" + }, + "monitors": {}, + "saved_views": {} + }, + "author": { + "support_email": "help@datadoghq.com", + "name": "Datadog", + "homepage": "https://www.datadoghq.com", + "sales_email": "info@datadoghq.com" + } +} diff --git a/n8n/metadata.csv b/n8n/metadata.csv new file mode 100644 index 0000000000000..c51d725516426 --- /dev/null +++ b/n8n/metadata.csv @@ -0,0 +1,64 @@ +metric_name,metric_type,interval,unit_name,per_unit_name,description,orientation,integration,short_name,curated_metric +n8n.active.workflow.count,gauge,,,,Total number of active workflows.,0,n8n,, +n8n.api.request.duration.seconds.count,count,,,,The count of API request duration in seconds,0,n8n,, +n8n.api.request.duration.seconds.sum,count,,,,The sum of API request duration in seconds,0,n8n,, +n8n.api.requests.total,count,,,,Total API requests,0,n8n,, +n8n.cache.errors.total,count,,,,Cache errors,0,n8n,, +n8n.cache.hits.total,count,,,,Cache hits,0,n8n,, +n8n.cache.latency.seconds.count,count,,,,The count of cache operation latency in seconds,0,n8n,, +n8n.cache.latency.seconds.sum,count,,,,The sum of cache operation latency in seconds,0,n8n,, +n8n.cache.misses.total,count,,,,Cache misses,0,n8n,, +n8n.cache.operations.total,count,,,,Total cache operations,0,n8n,, +n8n.eventbus.connections.total,gauge,,,,Active event bus backend connections,0,n8n,, +n8n.eventbus.events.failed.total,count,,,,Total failed event processing,0,n8n,, +n8n.eventbus.events.processed.total,count,,,,Total processed events,0,n8n,, +n8n.eventbus.events.total,count,,,,Total events published on the event bus,0,n8n,, +n8n.eventbus.queue.size,gauge,,,,Current event queue size,0,n8n,, +n8n.http.request.duration.seconds.count,count,,,,The count of the http responses duration labeled with: status_code,0,n8n,, +n8n.http.request.duration.seconds.sum,count,,,,The sum of the http responses duration labeled with: status_code,0,n8n,, +n8n.instance.role.leader,gauge,,,,Whether this main instance is the leader (1) or not (0).,0,n8n,, +n8n.last.activity,gauge,,,,last instance activity (backend request) in Unix time (seconds).,0,n8n,, +n8n.nodejs.active.handles,gauge,,,,Number of active libuv handles grouped by handle type. Every handle type is C++ class name.,0,n8n,, +n8n.nodejs.active.handles.total,gauge,,,,Total number of active handles.,0,n8n,, +n8n.nodejs.active.requests,gauge,,,,Number of active libuv requests grouped by request type. Every request type is C++ class name.,0,n8n,, +n8n.nodejs.active.requests.total,gauge,,,,Total number of active requests.,0,n8n,, +n8n.nodejs.active.resources,gauge,,,,"Number of active resources that are currently keeping the event loop alive, grouped by async resource type.",0,n8n,, +n8n.nodejs.active.resources.total,gauge,,,,Total number of active resources.,0,n8n,, +n8n.nodejs.event.loop.lag.seconds,gauge,,,,Event loop lag in seconds,0,n8n,, +n8n.nodejs.eventloop.lag.max.seconds,gauge,,,,The maximum recorded event loop delay.,0,n8n,, +n8n.nodejs.eventloop.lag.mean.seconds,gauge,,,,The mean of the recorded event loop delays.,0,n8n,, +n8n.nodejs.eventloop.lag.min.seconds,gauge,,,,The minimum recorded event loop delay.,0,n8n,, +n8n.nodejs.eventloop.lag.p50.seconds,gauge,,,,The 50th percentile of the recorded event loop delays.,0,n8n,, +n8n.nodejs.eventloop.lag.p90.seconds,gauge,,,,The 90th percentile of the recorded event loop delays.,0,n8n,, +n8n.nodejs.eventloop.lag.p99.seconds,gauge,,,,The 99th percentile of the recorded event loop delays.,0,n8n,, +n8n.nodejs.eventloop.lag.seconds,gauge,,,,Lag of event loop in seconds.,0,n8n,, +n8n.nodejs.eventloop.lag.stddev.seconds,gauge,,,,The standard deviation of the recorded event loop delays.,0,n8n,, +n8n.nodejs.external.memory.bytes,gauge,,,,Node.js external memory size in bytes.,0,n8n,, +n8n.nodejs.gc.duration.seconds.count,count,,,,The count of garbage collection duration by kind,0,n8n,, +n8n.nodejs.gc.duration.seconds.sum,count,,,,The sum of garbage collection duration by kind,0,n8n,, +n8n.nodejs.heap.size.total.bytes,gauge,,,,Process heap size from Node.js in bytes.,0,n8n,, +n8n.nodejs.heap.size.used.bytes,gauge,,,,Process heap size used from Node.js in bytes.,0,n8n,, +n8n.nodejs.heap.space.size.available.bytes,gauge,,,,Process heap space size available from Node.js in bytes.,0,n8n,, +n8n.nodejs.heap.space.size.total.bytes,gauge,,,,Process heap space size total from Node.js in bytes.,0,n8n,, +n8n.nodejs.heap.space.size.used.bytes,gauge,,,,Process heap space size used from Node.js in bytes.,0,n8n,, +n8n.nodejs.heap.total.bytes,gauge,,,,Total heap size allocated in bytes,0,n8n,, +n8n.nodejs.heap.used.bytes,gauge,,,,Heap memory used in bytes,0,n8n,, +n8n.nodejs.version.info,gauge,,,,Node.js version info.,0,n8n,, +n8n.process.cpu.system.seconds.total,count,,,,Total system CPU time spent in seconds.,0,n8n,, +n8n.process.cpu.user.seconds.total,count,,,,Total user CPU time spent in seconds.,0,n8n,, +n8n.process.heap.bytes,gauge,,,,Process heap size in bytes.,0,n8n,, +n8n.process.max.fds,gauge,,,,Maximum number of open file descriptors.,0,n8n,, +n8n.process.open.fds,gauge,,,,Number of open file descriptors.,0,n8n,, +n8n.process.resident.memory.bytes,gauge,,,,Resident memory size in bytes.,0,n8n,, +n8n.process.start.time.seconds,gauge,,,,Start time of the process since unix epoch in seconds.,0,n8n,, +n8n.process.virtual.memory.bytes,gauge,,,,Virtual memory size in bytes.,0,n8n,, +n8n.queue.job.attempts.total,count,,,,Total number of job attempts,0,n8n,, +n8n.queue.jobs.duration.seconds.count,count,,,,The count of job duration in seconds,0,n8n,, +n8n.queue.jobs.duration.seconds.sum,count,,,,The sum of job duration in seconds,0,n8n,, +n8n.queue.jobs.total,count,,,,Total number of queue jobs,0,n8n,, +n8n.workflow.executions.active,gauge,,,,Number of active workflow executions,0,n8n,, +n8n.workflow.executions.duration.seconds.count,count,,,,The count of workflow execution duration in seconds,0,n8n,, +n8n.workflow.executions.duration.seconds.sum,count,,,,The sum of workflow execution duration in seconds,0,n8n,, +n8n.workflow.executions.total,count,,,,Total number of workflow executions,0,n8n,, +n8n.process.cpu.seconds.total,count,,,,Total user and system CPU time spent in seconds.,0,n8n,, +n8n.version.info,gauge,,,,n8n version info.,0,n8n,, diff --git a/n8n/pyproject.toml b/n8n/pyproject.toml new file mode 100644 index 0000000000000..431be71a610a4 --- /dev/null +++ b/n8n/pyproject.toml @@ -0,0 +1,60 @@ +[build-system] +requires = [ + "hatchling>=0.13.0", +] +build-backend = "hatchling.build" + +[project] +name = "datadog-n8n" +description = "The n8n check" +readme = "README.md" +license = "BSD-3-Clause" +requires-python = ">=3.13" +keywords = [ + "datadog", + "datadog agent", + "datadog check", + "n8n", +] +authors = [ + { name = "Datadog", email = "packages@datadoghq.com" }, +] +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Intended Audience :: Developers", + "Intended Audience :: System Administrators", + "License :: OSI Approved :: BSD License", + "Private :: Do Not Upload", + "Programming Language :: Python :: 3.13", + "Topic :: System :: Monitoring", +] +dependencies = [ + "datadog-checks-base>=37.21.0", +] +dynamic = [ + "version", +] + +[project.optional-dependencies] +deps = [] + +[project.urls] +Source = "https://github.com/DataDog/integrations-core" + +[tool.hatch.version] +path = "datadog_checks/n8n/__about__.py" + +[tool.hatch.build.targets.sdist] +include = [ + "/datadog_checks", + "/tests", + "/manifest.json", +] + +[tool.hatch.build.targets.wheel] +include = [ + "/datadog_checks/n8n", +] +dev-mode-dirs = [ + ".", +] diff --git a/n8n/tests/__init__.py b/n8n/tests/__init__.py new file mode 100644 index 0000000000000..c9f1f2a9882c7 --- /dev/null +++ b/n8n/tests/__init__.py @@ -0,0 +1,3 @@ +# (C) Datadog, Inc. 2025-present +# All rights reserved +# Licensed under a 3-clause BSD style license (see LICENSE) diff --git a/n8n/tests/common.py b/n8n/tests/common.py new file mode 100644 index 0000000000000..a5e86da4e2a8f --- /dev/null +++ b/n8n/tests/common.py @@ -0,0 +1,94 @@ +# (C) Datadog, Inc. 2025-present +# All rights reserved +# Licensed under a 3-clause BSD style license (see LICENSE) +import os + +from datadog_checks.dev import get_docker_hostname + +HERE = os.path.dirname(os.path.abspath(__file__)) +COMPOSE_FILE = os.path.join(HERE, 'docker', 'docker-compose.yml') +HOST = get_docker_hostname() +PORT = 5678 + + +def get_fixture_path(filename): + return os.path.join(HERE, 'fixtures', filename) + + +OPENMETRICS_URL = f'http://{HOST}:{PORT}' +INSTANCE = { + 'openmetrics_endpoint': f'{OPENMETRICS_URL}/metrics', +} +COMPOSE_FILE = os.path.join(HERE, 'docker', 'docker-compose.yaml') + + +E2E_METADATA = { + 'docker_volumes': ['/var/run/docker.sock:/var/run/docker.sock:ro'], +} + +E2E_METRICS = [ + 'n8n.active.workflow.count', + 'n8n.api.request.duration.seconds.count', + 'n8n.api.request.duration.seconds.sum', + 'n8n.api.requests.total', + 'n8n.cache.errors.total', + 'n8n.cache.hits.total', + 'n8n.cache.latency.seconds.count', + 'n8n.cache.latency.seconds.sum', + 'n8n.cache.misses.total', + 'n8n.cache.operations.total', + 'n8n.eventbus.connections.total', + 'n8n.eventbus.events.failed.total', + 'n8n.eventbus.events.processed.total', + 'n8n.eventbus.events.total', + 'n8n.eventbus.queue.size', + 'n8n.http.request.duration.seconds.count', + 'n8n.http.request.duration.seconds.sum', + 'n8n.instance.role.leader', + 'n8n.last.activity', + 'n8n.nodejs.active.handles', + 'n8n.nodejs.active.handles.total', + 'n8n.nodejs.active.requests', + 'n8n.nodejs.active.requests.total', + 'n8n.nodejs.active.resources', + 'n8n.nodejs.active.resources.total', + 'n8n.nodejs.event.loop.lag.seconds', + 'n8n.nodejs.eventloop.lag.max.seconds', + 'n8n.nodejs.eventloop.lag.mean.seconds', + 'n8n.nodejs.eventloop.lag.min.seconds', + 'n8n.nodejs.eventloop.lag.p50.seconds', + 'n8n.nodejs.eventloop.lag.p90.seconds', + 'n8n.nodejs.eventloop.lag.p99.seconds', + 'n8n.nodejs.eventloop.lag.seconds', + 'n8n.nodejs.eventloop.lag.stddev.seconds', + 'n8n.nodejs.external.memory.bytes', + 'n8n.nodejs.gc.duration.seconds.count', + 'n8n.nodejs.gc.duration.seconds.sum', + 'n8n.nodejs.heap.size.total.bytes', + 'n8n.nodejs.heap.size.used.bytes', + 'n8n.nodejs.heap.space.size.available.bytes', + 'n8n.nodejs.heap.space.size.total.bytes', + 'n8n.nodejs.heap.space.size.used.bytes', + 'n8n.nodejs.heap.total.bytes', + 'n8n.nodejs.heap.used.bytes', + 'n8n.nodejs.version.info', + 'n8n.process.cpu.system.seconds.total', + 'n8n.process.cpu.user.seconds.total', + 'n8n.process.heap.bytes', + 'n8n.process.max.fds', + 'n8n.process.open.fds', + 'n8n.process.resident.memory.bytes', + 'n8n.process.start.time.seconds', + 'n8n.process.virtual.memory.bytes', + 'n8n.queue.job.attempts.total', + 'n8n.queue.jobs.duration.seconds.count', + 'n8n.queue.jobs.duration.seconds.sum', + 'n8n.queue.jobs.total', + 'n8n.workflow.executions.active', + 'n8n.workflow.executions.duration.seconds.count', + 'n8n.workflow.executions.duration.seconds.sum', + 'n8n.workflow.executions.total', + 'n8n.process.cpu.seconds.total', + 'n8n.version.info', +] + diff --git a/n8n/tests/conftest.py b/n8n/tests/conftest.py new file mode 100644 index 0000000000000..e19a7329d15ad --- /dev/null +++ b/n8n/tests/conftest.py @@ -0,0 +1,32 @@ +# (C) Datadog, Inc. 2025-present +# All rights reserved +# Licensed under a 3-clause BSD style license (see LICENSE) + +import copy + +import pytest + +from datadog_checks.dev import docker_run +from datadog_checks.dev.conditions import CheckDockerLogs, CheckEndpoints + +from . import common + + +@pytest.fixture(scope='session') +def dd_environment(): + compose_file = common.COMPOSE_FILE + conditions = [ + #CheckDockerLogs(identifier='n8n', patterns=['server running']), + CheckEndpoints(common.INSTANCE["openmetrics_endpoint"]), + CheckEndpoints(f'{common.OPENMETRICS_URL}/healthz', attempts=60, wait=3), + CheckEndpoints(f'{common.OPENMETRICS_URL}/metrics', attempts=60, wait=3), + ] + with docker_run(compose_file, conditions=conditions): + yield { + 'instances': [common.INSTANCE], + } + +@pytest.fixture +def instance(): + return copy.deepcopy(common.INSTANCE) + diff --git a/n8n/tests/docker/Dockerfile b/n8n/tests/docker/Dockerfile new file mode 100644 index 0000000000000..232eb7b8808d1 --- /dev/null +++ b/n8n/tests/docker/Dockerfile @@ -0,0 +1,13 @@ +FROM docker.n8n.io/n8nio/n8n:latest + +# Set environment variables to enable metrics and logging +ENV N8N_METRICS=true \ + N8N_LOG_LEVEL=debug \ + N8N_METRICS_INCLUDE_DEFAULT_METRICS=true \ + N8N_METRICS_INCLUDE_CACHE_METRICS=true \ + N8N_METRICS_INCLUDE_MESSAGE_EVENT_BUS_METRICS=true \ + N8N_HOST=0.0.0.0 \ + N8N_PORT=5678 + +# Expose the n8n port +EXPOSE 5678 diff --git a/n8n/tests/docker/README.md b/n8n/tests/docker/README.md new file mode 100644 index 0000000000000..bb1d23cc34ce1 --- /dev/null +++ b/n8n/tests/docker/README.md @@ -0,0 +1,88 @@ +# n8n Test Environment + +This directory contains Docker configuration for running an n8n instance with metrics and logging enabled for integration testing. + +## Prerequisites + +- Docker +- Docker Compose + +## Usage + +### Starting the environment + +```bash +cd tests/docker +docker-compose up -d +``` + +### Accessing n8n + +- **Web UI**: http://localhost:5678 +- **Metrics endpoint**: http://localhost:5678/metrics +- **Health check**: http://localhost:5678/healthz + +Default credentials: +- Username: `admin` +- Password: `admin` + +### Viewing logs + +```bash +docker-compose logs -f n8n +``` + +### Stopping the environment + +```bash +docker-compose down +``` + +### Cleaning up (including volumes) + +```bash +docker-compose down -v +``` + +## Configuration + +### Metrics + +The following metrics are enabled: +- Default system metrics +- Cache metrics +- Message event bus metrics +- API endpoint metrics +- Workflow ID labels on workflow metrics + +The metrics are exposed in Prometheus/OpenMetrics format at `http://localhost:5678/metrics`. + +### Logging + +Logs are configured with: +- Log level: `debug` +- Log output: `console` +- Log directory: `./logs` (can be overridden with `N8N_LOG_FOLDER` environment variable) + +### Environment Variables + +You can override environment variables by setting them before running docker-compose: + +```bash +N8N_LOG_FOLDER=/path/to/logs docker-compose up -d +``` + +## Testing + +This setup is designed for integration testing. The n8n instance will: +1. Start with metrics endpoint enabled +2. Expose detailed logs for debugging +3. Include workflow_id labels in workflow metrics +4. Provide a health check endpoint for monitoring readiness + +## Notes + +- The container uses the latest official n8n Docker image +- Data is persisted in a Docker volume named `n8n_data` +- The health check waits up to 30 seconds for n8n to start before marking it as healthy + diff --git a/n8n/tests/docker/docker-compose.yml b/n8n/tests/docker/docker-compose.yml new file mode 100644 index 0000000000000..fb8da72559b78 --- /dev/null +++ b/n8n/tests/docker/docker-compose.yml @@ -0,0 +1,41 @@ +services: + n8n: + build: + context: . + dockerfile: Dockerfile + container_name: n8n-test + ports: + - "5678:5678" + environment: + # Enable metrics endpoint + - N8N_METRICS=true + - N8N_METRICS_INCLUDE_DEFAULT_METRICS=true + - N8N_METRICS_INCLUDE_CACHE_METRICS=true + - N8N_METRICS_INCLUDE_MESSAGE_EVENT_BUS_METRICS=true + - N8N_METRICS_INCLUDE_API_ENDPOINTS=true + - N8N_METRICS_INCLUDE_WORKFLOW_ID_LABEL=true + # Logging configuration + - N8N_LOG_LEVEL=debug + - N8N_LOG_OUTPUT=console + # Basic configuration + - N8N_HOST=0.0.0.0 + - N8N_PORT=5678 + - N8N_PROTOCOL=http + # Authentication (optional for testing) + - N8N_BASIC_AUTH_ACTIVE=true + - N8N_BASIC_AUTH_USER=admin + - N8N_BASIC_AUTH_PASSWORD=admin + volumes: + - n8n_data:/home/node/.n8n + - ${N8N_LOG_FOLDER:-./logs}:/var/log/n8n + healthcheck: + test: ["CMD", "wget", "-q", "--spider", "http://localhost:5678/healthz"] + interval: 10s + timeout: 5s + retries: 5 + start_period: 30s + +volumes: + n8n_data: + driver: local + diff --git a/n8n/tests/fixtures/n8n.txt b/n8n/tests/fixtures/n8n.txt new file mode 100644 index 0000000000000..eb301d9b58bec --- /dev/null +++ b/n8n/tests/fixtures/n8n.txt @@ -0,0 +1,327 @@ +# HELP n8n_process_cpu_user_seconds_total Total user CPU time spent in seconds. +# TYPE n8n_process_cpu_user_seconds_total counter +n8n_process_cpu_user_seconds_total 8.298932999999998 + +# HELP n8n_process_cpu_system_seconds_total Total system CPU time spent in seconds. +# TYPE n8n_process_cpu_system_seconds_total counter +n8n_process_cpu_system_seconds_total 3.1041119999999998 + +# HELP n8n_process_cpu_seconds_total Total user and system CPU time spent in seconds. +# TYPE n8n_process_cpu_seconds_total counter +n8n_process_cpu_seconds_total 11.403044999999999 + +# HELP n8n_process_start_time_seconds Start time of the process since unix epoch in seconds. +# TYPE n8n_process_start_time_seconds gauge +n8n_process_start_time_seconds 1761656578 + +# HELP n8n_process_resident_memory_bytes Resident memory size in bytes. +# TYPE n8n_process_resident_memory_bytes gauge +n8n_process_resident_memory_bytes 245043200 + +# HELP n8n_process_virtual_memory_bytes Virtual memory size in bytes. +# TYPE n8n_process_virtual_memory_bytes gauge +n8n_process_virtual_memory_bytes 33656197120 + +# HELP n8n_process_heap_bytes Process heap size in bytes. +# TYPE n8n_process_heap_bytes gauge +n8n_process_heap_bytes 277200896 + +# HELP n8n_process_open_fds Number of open file descriptors. +# TYPE n8n_process_open_fds gauge +n8n_process_open_fds 44 + +# HELP n8n_process_max_fds Maximum number of open file descriptors. +# TYPE n8n_process_max_fds gauge +n8n_process_max_fds 1048576 + +# HELP n8n_nodejs_eventloop_lag_seconds Lag of event loop in seconds. +# TYPE n8n_nodejs_eventloop_lag_seconds gauge +n8n_nodejs_eventloop_lag_seconds 0.002765567 + +# HELP n8n_nodejs_eventloop_lag_min_seconds The minimum recorded event loop delay. +# TYPE n8n_nodejs_eventloop_lag_min_seconds gauge +n8n_nodejs_eventloop_lag_min_seconds 0.010018816 + +# HELP n8n_nodejs_eventloop_lag_max_seconds The maximum recorded event loop delay. +# TYPE n8n_nodejs_eventloop_lag_max_seconds gauge +n8n_nodejs_eventloop_lag_max_seconds 0.011239423 + +# HELP n8n_nodejs_eventloop_lag_mean_seconds The mean of the recorded event loop delays. +# TYPE n8n_nodejs_eventloop_lag_mean_seconds gauge +n8n_nodejs_eventloop_lag_mean_seconds 0.010092521938958708 + +# HELP n8n_nodejs_eventloop_lag_stddev_seconds The standard deviation of the recorded event loop delays. +# TYPE n8n_nodejs_eventloop_lag_stddev_seconds gauge +n8n_nodejs_eventloop_lag_stddev_seconds 0.00016945350643679045 + +# HELP n8n_nodejs_eventloop_lag_p50_seconds The 50th percentile of the recorded event loop delays. +# TYPE n8n_nodejs_eventloop_lag_p50_seconds gauge +n8n_nodejs_eventloop_lag_p50_seconds 0.010067967 + +# HELP n8n_nodejs_eventloop_lag_p90_seconds The 90th percentile of the recorded event loop delays. +# TYPE n8n_nodejs_eventloop_lag_p90_seconds gauge +n8n_nodejs_eventloop_lag_p90_seconds 0.010067967 + +# HELP n8n_nodejs_eventloop_lag_p99_seconds The 99th percentile of the recorded event loop delays. +# TYPE n8n_nodejs_eventloop_lag_p99_seconds gauge +n8n_nodejs_eventloop_lag_p99_seconds 0.011124735 + +# HELP n8n_nodejs_active_resources Number of active resources that are currently keeping the event loop alive, grouped by async resource type. +# TYPE n8n_nodejs_active_resources gauge +n8n_nodejs_active_resources{type="PipeWrap"} 2 +n8n_nodejs_active_resources{type="TCPServerWrap"} 1 +n8n_nodejs_active_resources{type="TCPSocketWrap"} 1 +n8n_nodejs_active_resources{type="Timeout"} 13 +n8n_nodejs_active_resources{type="Immediate"} 1 + +# HELP n8n_nodejs_active_resources_total Total number of active resources. +# TYPE n8n_nodejs_active_resources_total gauge +n8n_nodejs_active_resources_total 18 + +# HELP n8n_nodejs_active_handles Number of active libuv handles grouped by handle type. Every handle type is C++ class name. +# TYPE n8n_nodejs_active_handles gauge +n8n_nodejs_active_handles{type="Socket"} 3 +n8n_nodejs_active_handles{type="Server"} 1 + +# HELP n8n_nodejs_active_handles_total Total number of active handles. +# TYPE n8n_nodejs_active_handles_total gauge +n8n_nodejs_active_handles_total 4 + +# HELP n8n_nodejs_active_requests Number of active libuv requests grouped by request type. Every request type is C++ class name. +# TYPE n8n_nodejs_active_requests gauge + +# HELP n8n_nodejs_active_requests_total Total number of active requests. +# TYPE n8n_nodejs_active_requests_total gauge +n8n_nodejs_active_requests_total 0 + +# HELP n8n_nodejs_heap_size_total_bytes Process heap size from Node.js in bytes. +# TYPE n8n_nodejs_heap_size_total_bytes gauge +n8n_nodejs_heap_size_total_bytes 142774272 + +# HELP n8n_nodejs_heap_size_used_bytes Process heap size used from Node.js in bytes. +# TYPE n8n_nodejs_heap_size_used_bytes gauge +n8n_nodejs_heap_size_used_bytes 136342632 + +# HELP n8n_nodejs_external_memory_bytes Node.js external memory size in bytes. +# TYPE n8n_nodejs_external_memory_bytes gauge +n8n_nodejs_external_memory_bytes 20824585 + +# HELP n8n_nodejs_heap_space_size_total_bytes Process heap space size total from Node.js in bytes. +# TYPE n8n_nodejs_heap_space_size_total_bytes gauge +n8n_nodejs_heap_space_size_total_bytes{space="read_only"} 0 +n8n_nodejs_heap_space_size_total_bytes{space="new"} 1048576 +n8n_nodejs_heap_space_size_total_bytes{space="old"} 122208256 +n8n_nodejs_heap_space_size_total_bytes{space="code"} 4718592 +n8n_nodejs_heap_space_size_total_bytes{space="shared"} 0 +n8n_nodejs_heap_space_size_total_bytes{space="trusted"} 7643136 +n8n_nodejs_heap_space_size_total_bytes{space="new_large_object"} 0 +n8n_nodejs_heap_space_size_total_bytes{space="large_object"} 7000064 +n8n_nodejs_heap_space_size_total_bytes{space="code_large_object"} 155648 +n8n_nodejs_heap_space_size_total_bytes{space="shared_large_object"} 0 +n8n_nodejs_heap_space_size_total_bytes{space="trusted_large_object"} 0 + +# HELP n8n_nodejs_heap_space_size_used_bytes Process heap space size used from Node.js in bytes. +# TYPE n8n_nodejs_heap_space_size_used_bytes gauge +n8n_nodejs_heap_space_size_used_bytes{space="read_only"} 0 +n8n_nodejs_heap_space_size_used_bytes{space="new"} 652896 +n8n_nodejs_heap_space_size_used_bytes{space="old"} 119347344 +n8n_nodejs_heap_space_size_used_bytes{space="code"} 4183424 +n8n_nodejs_heap_space_size_used_bytes{space="shared"} 0 +n8n_nodejs_heap_space_size_used_bytes{space="trusted"} 5187192 +n8n_nodejs_heap_space_size_used_bytes{space="new_large_object"} 0 +n8n_nodejs_heap_space_size_used_bytes{space="large_object"} 6837144 +n8n_nodejs_heap_space_size_used_bytes{space="code_large_object"} 138432 +n8n_nodejs_heap_space_size_used_bytes{space="shared_large_object"} 0 +n8n_nodejs_heap_space_size_used_bytes{space="trusted_large_object"} 0 + +# HELP n8n_nodejs_heap_space_size_available_bytes Process heap space size available from Node.js in bytes. +# TYPE n8n_nodejs_heap_space_size_available_bytes gauge +n8n_nodejs_heap_space_size_available_bytes{space="read_only"} 0 +n8n_nodejs_heap_space_size_available_bytes{space="new"} 378016 +n8n_nodejs_heap_space_size_available_bytes{space="old"} 430568 +n8n_nodejs_heap_space_size_available_bytes{space="code"} 239680 +n8n_nodejs_heap_space_size_available_bytes{space="shared"} 0 +n8n_nodejs_heap_space_size_available_bytes{space="trusted"} 2323072 +n8n_nodejs_heap_space_size_available_bytes{space="new_large_object"} 1048576 +n8n_nodejs_heap_space_size_available_bytes{space="large_object"} 0 +n8n_nodejs_heap_space_size_available_bytes{space="code_large_object"} 0 +n8n_nodejs_heap_space_size_available_bytes{space="shared_large_object"} 0 +n8n_nodejs_heap_space_size_available_bytes{space="trusted_large_object"} 0 + +# HELP n8n_nodejs_version_info Node.js version info. +# TYPE n8n_nodejs_version_info gauge +n8n_nodejs_version_info{version="v22.18.0",major="22",minor="18",patch="0"} 1 + +# HELP n8n_nodejs_gc_duration_seconds Garbage collection duration by kind, one of major, minor, incremental or weakcb. +# TYPE n8n_nodejs_gc_duration_seconds histogram +n8n_nodejs_gc_duration_seconds_bucket{le="0.001",kind="minor"} 128 +n8n_nodejs_gc_duration_seconds_bucket{le="0.01",kind="minor"} 132 +n8n_nodejs_gc_duration_seconds_bucket{le="0.1",kind="minor"} 132 +n8n_nodejs_gc_duration_seconds_bucket{le="1",kind="minor"} 132 +n8n_nodejs_gc_duration_seconds_bucket{le="2",kind="minor"} 132 +n8n_nodejs_gc_duration_seconds_bucket{le="5",kind="minor"} 132 +n8n_nodejs_gc_duration_seconds_bucket{le="+Inf",kind="minor"} 132 +n8n_nodejs_gc_duration_seconds_sum{kind="minor"} 0.09924478498101237 +n8n_nodejs_gc_duration_seconds_count{kind="minor"} 132 +n8n_nodejs_gc_duration_seconds_bucket{le="0.001",kind="incremental"} 1 +n8n_nodejs_gc_duration_seconds_bucket{le="0.01",kind="incremental"} 2 +n8n_nodejs_gc_duration_seconds_bucket{le="0.1",kind="incremental"} 2 +n8n_nodejs_gc_duration_seconds_bucket{le="1",kind="incremental"} 2 +n8n_nodejs_gc_duration_seconds_bucket{le="2",kind="incremental"} 2 +n8n_nodejs_gc_duration_seconds_bucket{le="5",kind="incremental"} 2 +n8n_nodejs_gc_duration_seconds_bucket{le="+Inf",kind="incremental"} 2 +n8n_nodejs_gc_duration_seconds_sum{kind="incremental"} 0.0022786640077829363 +n8n_nodejs_gc_duration_seconds_count{kind="incremental"} 2 +n8n_nodejs_gc_duration_seconds_bucket{le="0.001",kind="major"} 0 +n8n_nodejs_gc_duration_seconds_bucket{le="0.01",kind="major"} 0 +n8n_nodejs_gc_duration_seconds_bucket{le="0.1",kind="major"} 2 +n8n_nodejs_gc_duration_seconds_bucket{le="1",kind="major"} 2 +n8n_nodejs_gc_duration_seconds_bucket{le="2",kind="major"} 2 +n8n_nodejs_gc_duration_seconds_bucket{le="5",kind="major"} 2 +n8n_nodejs_gc_duration_seconds_bucket{le="+Inf",kind="major"} 2 +n8n_nodejs_gc_duration_seconds_sum{kind="major"} 0.1028408939987421 +n8n_nodejs_gc_duration_seconds_count{kind="major"} 2 + +# HELP n8n_version_info n8n version info. +# TYPE n8n_version_info gauge +n8n_version_info{version="v1.117.2",major="1",minor="117",patch="2"} 1 + +# HELP n8n_instance_role_leader Whether this main instance is the leader (1) or not (0). +# TYPE n8n_instance_role_leader gauge +n8n_instance_role_leader 1 + +# HELP n8n_http_request_duration_seconds duration histogram of http responses labeled with: status_code +# TYPE n8n_http_request_duration_seconds histogram + +# HELP n8n_last_activity last instance activity (backend request) in Unix time (seconds). +# TYPE n8n_last_activity gauge +n8n_last_activity 1761656582 + +# HELP n8n_active_workflow_count Total number of active workflows. +# TYPE n8n_active_workflow_count gauge +n8n_active_workflow_count{workflow_id="wf_8a3b2c1d"} 0 +n8n_active_workflow_count{workflow_id="wf_7f4e9a2b"} 0 +n8n_active_workflow_count{workflow_id="wf_5d6c8e1f"} 0 + +# HELP n8n_nodejs_event_loop_lag_seconds Event loop lag in seconds +# TYPE n8n_nodejs_event_loop_lag_seconds gauge +n8n_nodejs_event_loop_lag_seconds 0.0035 + +# HELP n8n_nodejs_heap_total_bytes Total heap size allocated in bytes +# TYPE n8n_nodejs_heap_total_bytes gauge +n8n_nodejs_heap_total_bytes 73400320 + +# HELP n8n_nodejs_heap_used_bytes Heap memory used in bytes +# TYPE n8n_nodejs_heap_used_bytes gauge +n8n_nodejs_heap_used_bytes 51200000 + +# HELP n8n_workflow_executions_total Total number of workflow executions +# TYPE n8n_workflow_executions_total counter +n8n_workflow_executions_total{status="success",workflow_id="wf_8a3b2c1d"} 45 +n8n_workflow_executions_total{status="success",workflow_id="wf_7f4e9a2b"} 38 +n8n_workflow_executions_total{status="success",workflow_id="wf_5d6c8e1f"} 45 +n8n_workflow_executions_total{status="error",workflow_id="wf_8a3b2c1d"} 3 +n8n_workflow_executions_total{status="error",workflow_id="wf_5d6c8e1f"} 4 + +# HELP n8n_workflow_executions_duration_seconds Workflow execution duration in seconds +# TYPE n8n_workflow_executions_duration_seconds histogram +n8n_workflow_executions_duration_seconds_bucket{le="0.1",workflow_id="wf_8a3b2c1d"} 5 +n8n_workflow_executions_duration_seconds_bucket{le="1",workflow_id="wf_8a3b2c1d"} 18 +n8n_workflow_executions_duration_seconds_bucket{le="+Inf",workflow_id="wf_8a3b2c1d"} 48 +n8n_workflow_executions_duration_seconds_sum{workflow_id="wf_8a3b2c1d"} 14.3 +n8n_workflow_executions_duration_seconds_count{workflow_id="wf_8a3b2c1d"} 48 +n8n_workflow_executions_duration_seconds_bucket{le="0.1",workflow_id="wf_7f4e9a2b"} 4 +n8n_workflow_executions_duration_seconds_bucket{le="1",workflow_id="wf_7f4e9a2b"} 15 +n8n_workflow_executions_duration_seconds_bucket{le="+Inf",workflow_id="wf_7f4e9a2b"} 38 +n8n_workflow_executions_duration_seconds_sum{workflow_id="wf_7f4e9a2b"} 11.2 +n8n_workflow_executions_duration_seconds_count{workflow_id="wf_7f4e9a2b"} 38 +n8n_workflow_executions_duration_seconds_bucket{le="0.1",workflow_id="wf_5d6c8e1f"} 3 +n8n_workflow_executions_duration_seconds_bucket{le="1",workflow_id="wf_5d6c8e1f"} 12 +n8n_workflow_executions_duration_seconds_bucket{le="+Inf",workflow_id="wf_5d6c8e1f"} 49 +n8n_workflow_executions_duration_seconds_sum{workflow_id="wf_5d6c8e1f"} 12.7 +n8n_workflow_executions_duration_seconds_count{workflow_id="wf_5d6c8e1f"} 49 + +# HELP n8n_queue_jobs_total Total number of queue jobs +# TYPE n8n_queue_jobs_total counter +n8n_queue_jobs_total{state="waiting"} 3 +n8n_queue_jobs_total{state="active"} 2 +n8n_queue_jobs_total{state="completed"} 148 +n8n_queue_jobs_total{state="failed"} 5 + +# HELP n8n_queue_jobs_duration_seconds Job duration in seconds +# TYPE n8n_queue_jobs_duration_seconds histogram +n8n_queue_jobs_duration_seconds_bucket{le="0.1"} 22 +n8n_queue_jobs_duration_seconds_bucket{le="1"} 84 +n8n_queue_jobs_duration_seconds_bucket{le="+Inf"} 150 +n8n_queue_jobs_duration_seconds_sum 44.8 +n8n_queue_jobs_duration_seconds_count 150 + +# HELP n8n_api_requests_total Total API requests +# TYPE n8n_api_requests_total counter +n8n_api_requests_total{method="GET",endpoint="/workflows"} 240 +n8n_api_requests_total{method="POST",endpoint="/executions"} 75 + +# HELP n8n_api_request_duration_seconds API request duration in seconds +# TYPE n8n_api_request_duration_seconds histogram +n8n_api_request_duration_seconds_bucket{le="0.1"} 90 +n8n_api_request_duration_seconds_bucket{le="1"} 120 +n8n_api_request_duration_seconds_bucket{le="+Inf"} 125 +n8n_api_request_duration_seconds_sum 15.3 +n8n_api_request_duration_seconds_count 125 + +# HELP n8n_cache_operations_total Total cache operations +# TYPE n8n_cache_operations_total counter +n8n_cache_operations_total{operation="get"} 1250 +n8n_cache_operations_total{operation="set"} 320 +n8n_cache_operations_total{operation="delete"} 10 + +# HELP n8n_cache_hits_total Cache hits +# TYPE n8n_cache_hits_total counter +n8n_cache_hits_total 1080 + +# HELP n8n_cache_misses_total Cache misses +# TYPE n8n_cache_misses_total counter +n8n_cache_misses_total 170 + +# HELP n8n_cache_errors_total Cache errors +# TYPE n8n_cache_errors_total counter +n8n_cache_errors_total 0 + +# HELP n8n_cache_latency_seconds Cache operation latency in seconds +# TYPE n8n_cache_latency_seconds histogram +n8n_cache_latency_seconds_bucket{le="0.001"} 90 +n8n_cache_latency_seconds_bucket{le="0.01"} 240 +n8n_cache_latency_seconds_bucket{le="+Inf"} 260 +n8n_cache_latency_seconds_sum 1.42 +n8n_cache_latency_seconds_count 260 + +# HELP n8n_eventbus_events_total Total events published on the event bus +# TYPE n8n_eventbus_events_total counter +n8n_eventbus_events_total{event_type="workflowStarted"} 140 +n8n_eventbus_events_total{event_type="workflowCompleted"} 135 +n8n_eventbus_events_total{event_type="workflowFailed"} 5 + +# HELP n8n_eventbus_events_processed_total Total processed events +# TYPE n8n_eventbus_events_processed_total counter +n8n_eventbus_events_processed_total 138 + +# HELP n8n_eventbus_events_failed_total Total failed event processing +# TYPE n8n_eventbus_events_failed_total counter +n8n_eventbus_events_failed_total 2 + +# HELP n8n_eventbus_queue_size Current event queue size +# TYPE n8n_eventbus_queue_size gauge +n8n_eventbus_queue_size 1 + +# HELP n8n_eventbus_connections_total Active event bus backend connections +# TYPE n8n_eventbus_connections_total gauge +n8n_eventbus_connections_total 1 + +# HELP n8n_workflow_executions_active Number of active workflow executions +# TYPE n8n_workflow_executions_active gauge +n8n_workflow_executions_active 3 + +# HELP n8n_queue_job_attempts_total Total number of job attempts +# TYPE n8n_queue_job_attempts_total counter +n8n_queue_job_attempts_total{result="success"} 435 +n8n_queue_job_attempts_total{result="failed"} 12 \ No newline at end of file diff --git a/n8n/tests/test_e2e.py b/n8n/tests/test_e2e.py new file mode 100644 index 0000000000000..5bb4d7e3435bb --- /dev/null +++ b/n8n/tests/test_e2e.py @@ -0,0 +1,12 @@ +# (C) Datadog, Inc. 2025-present +# All rights reserved +# Licensed under a 3-clause BSD style license (see LICENSE) +import pytest +from datadog_checks.base.constants import ServiceCheck +from datadog_checks.dev.utils import assert_service_checks + +@pytest.mark.e2e +def test_check_n8n_e2e(dd_agent_check, instance): + aggregator = dd_agent_check(instance, rate=True) + aggregator.assert_service_check('n8n.openmetrics.health', ServiceCheck.OK, count=2) + assert_service_checks(aggregator) \ No newline at end of file diff --git a/n8n/tests/test_unit.py b/n8n/tests/test_unit.py new file mode 100644 index 0000000000000..8cc6079c394d5 --- /dev/null +++ b/n8n/tests/test_unit.py @@ -0,0 +1,42 @@ +# (C) Datadog, Inc. 2025-present +# All rights reserved +# Licensed under a 3-clause BSD style license (see LICENSE) + +from datadog_checks.n8n import N8nCheck + +from . import common + +def test_check_namespace_default(): + """ + Test that the check applies the correct namespace when raw_metric_prefix is 'n8n' (default). + """ + instance = { + 'openmetrics_endpoint': 'http://localhost:5678/metrics', + } + check = N8nCheck('n8n', {}, [instance]) + config = check.get_default_config() + + # When raw_metric_prefix is 'n8n' (default), namespace should be 'n8n' + assert config['namespace'] == 'n8n', f"Expected namespace 'n8n', got '{config['namespace']}'" + + +def test_check_namespace_custom(): + """ + Test that the check applies the correct namespace when raw_metric_prefix is custom. + """ + instance = { + 'openmetrics_endpoint': 'http://localhost:5678/metrics', + 'raw_metric_prefix': 'my_n8n_team', + } + check = N8nCheck('n8n', {}, [instance]) + config = check.get_default_config() + + # When raw_metric_prefix is custom, namespace should be 'n8n.' + assert config['namespace'] == 'n8n.my_n8n_team', f"Expected namespace 'n8n.my_n8n_team', got '{config['namespace']}'" + +def test_e2e_metrics(dd_agent_check, instance): + aggregator = dd_agent_check(instance, rate=True) + + for metric in common.E2E_METRICS: + aggregator.assert_metric(metric) + aggregator.assert_all_metrics_covered() From 08e820375c6220c35b2570d1079a05ff53881ece Mon Sep 17 00:00:00 2001 From: HadhemiDD Date: Thu, 6 Nov 2025 10:48:47 +0100 Subject: [PATCH 02/20] fix validation --- .codecov.yml | 9 +++++++++ .github/workflows/test-all.yml | 20 ++++++++++++++++++++ n8n/assets/dashboards/n8n_overview.json | 2 +- n8n/manifest.json | 17 +++++++++-------- 4 files changed, 39 insertions(+), 9 deletions(-) diff --git a/.codecov.yml b/.codecov.yml index 11dbd831509dd..104d575c11304 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -794,6 +794,10 @@ coverage: target: 75 flags: - kyverno + n8n: + target: 75 + flags: + - n8n nvidia_nim: target: 75 flags: @@ -1431,6 +1435,11 @@ flags: paths: - mysql/datadog_checks/mysql - mysql/tests + n8n: + carryforward: true + paths: + - n8n/datadog_checks/n8n + - n8n/tests nagios: carryforward: true paths: diff --git a/.github/workflows/test-all.yml b/.github/workflows/test-all.yml index d300257e42635..e4f672a866fba 100644 --- a/.github/workflows/test-all.yml +++ b/.github/workflows/test-all.yml @@ -2604,6 +2604,26 @@ jobs: - py3.13-percona-8.0.42 - py3.13-percona-8.4 fail-fast: false + j636396f: + uses: ./.github/workflows/test-target.yml + with: + job-name: n8n + target: n8n + platform: linux + runner: '["ubuntu-22.04"]' + repo: "${{ inputs.repo }}" + context: ${{ inputs.context }} + python-version: "${{ inputs.python-version }}" + latest: ${{ inputs.latest }} + agent-image: "${{ inputs.agent-image }}" + agent-image-py2: "${{ inputs.agent-image-py2 }}" + agent-image-windows: "${{ inputs.agent-image-windows }}" + agent-image-windows-py2: "${{ inputs.agent-image-windows-py2 }}" + test-py2: ${{ inputs.test-py2 }} + test-py3: ${{ inputs.test-py3 }} + minimum-base-package: ${{ inputs.minimum-base-package }} + pytest-args: ${{ inputs.pytest-args }} + secrets: inherit j5df646e: uses: ./.github/workflows/test-target.yml with: diff --git a/n8n/assets/dashboards/n8n_overview.json b/n8n/assets/dashboards/n8n_overview.json index e9e23301af626..5ff4d9df29c8c 100644 --- a/n8n/assets/dashboards/n8n_overview.json +++ b/n8n/assets/dashboards/n8n_overview.json @@ -1 +1 @@ -Please build an out-of-the-box dashboard for your integration following our best practices here: https://datadoghq.dev/integrations-core/guidelines/dashboards/#best-practices \ No newline at end of file +{"title":"N8N Overview Dashboard","description":"[[suggested_dashboards]]","widgets":[],"template_variables":[],"layout_type":"ordered","notify_list":[],"reflow_type":"fixed","pause_auto_refresh":false} \ No newline at end of file diff --git a/n8n/manifest.json b/n8n/manifest.json index 82d73e61ecc01..8626a71b61bcb 100644 --- a/n8n/manifest.json +++ b/n8n/manifest.json @@ -8,18 +8,19 @@ "configuration": "README.md#Setup", "support": "README.md#Support", "changelog": "CHANGELOG.md", - "description": "", + "description": "Monitor the health and performance of n8n", "title": "n8n", "media": [], "classifier_tags": [ - "", "Supported OS::Linux", "Supported OS::Windows", "Supported OS::macOS", - "Category::", - "Offering::", - "Queried Data Type::", - "Submitted Data Type::" + "Category::AI", + "Offering::Integration", + "Queried Data Type::Metrics", + "Queried Data Type::Logs", + "Submitted Data Type::Logs", + "Submitted Data Type::Metrics" ] }, "assets": { @@ -35,12 +36,12 @@ }, "metrics": { "prefix": "n8n.", - "check": "", + "check": "n8n.active.workflow.count", "metadata_path": "metadata.csv" } }, "dashboards": { - "": "assets/dashboards/.json" + "N8N overview": "assets/dashboards/n8n_overview.json" }, "monitors": {}, "saved_views": {} From 6c7ada307820b4138b8f30c923ce35db46ff896f Mon Sep 17 00:00:00 2001 From: HadhemiDD Date: Thu, 6 Nov 2025 10:58:35 +0100 Subject: [PATCH 03/20] lint --- n8n/datadog_checks/n8n/check.py | 16 ++-- n8n/datadog_checks/n8n/metrics.py | 2 +- n8n/metadata.csv | 128 +++++++++++++++--------------- n8n/tests/common.py | 1 - n8n/tests/conftest.py | 6 +- n8n/tests/test_e2e.py | 4 +- n8n/tests/test_unit.py | 12 ++- 7 files changed, 87 insertions(+), 82 deletions(-) diff --git a/n8n/datadog_checks/n8n/check.py b/n8n/datadog_checks/n8n/check.py index 0b677ac147c90..90cb0466936b6 100644 --- a/n8n/datadog_checks/n8n/check.py +++ b/n8n/datadog_checks/n8n/check.py @@ -1,7 +1,7 @@ # (C) Datadog, Inc. 2025-present # All rights reserved # Licensed under a 3-clause BSD style license (see LICENSE) -from typing import Any + from urllib.parse import urljoin, urlparse # noqa: F401 from datadog_checks.base import AgentCheck, OpenMetricsBaseCheckV2 @@ -32,14 +32,13 @@ def __init__(self, name, init_config, instances=None): self.server_port = str(self.instance.get('server_port', 5678)) self.raw_metric_prefix = self.instance.get('raw_metric_prefix', 'n8n') - def get_default_config(self): # If raw_metric_prefix is 'n8n', metrics start with 'n8n' if self.raw_metric_prefix == 'n8n': namespace = 'n8n' else: namespace = f'n8n.{self.raw_metric_prefix}' - + return {'namespace': namespace, 'metrics': [METRIC_MAP]} @AgentCheck.metadata_entrypoint @@ -68,7 +67,7 @@ def _submit_version_metadata(self): self.log.debug("Malformed N8N Server version format: %s", version) else: self.log.debug("Could not retrieve version metadata.") - + def _check_n8n_health(self): endpoint = urljoin(self.openmetrics_endpoint, self._health_endpoint) response = self.http.get(endpoint) @@ -85,16 +84,17 @@ def _check_n8n_readiness(self): endpoint = urljoin(self.openmetrics_endpoint, self._ready_endpoint) response = self.http.get(endpoint) - # Any 4xx or 5xx response from the API endpoint (/healthz/readiness) means the n8n is not ready to accept requests + # Any 4xx or 5xx response from the API endpoint (/healthz/readiness) + # means the n8n is not ready to accept requests if 400 <= response.status_code and response.status_code < 600: self.service_check('health.status', AgentCheck.CRITICAL, self.tags) if response.status_code == 200: self.service_check('health.status', AgentCheck.OK, self.tags) else: - self.service_check('health.status', AgentCheck.UNKNOWN, self.tags) - + self.service_check('health.status', AgentCheck.UNKNOWN, self.tags) + def check(self, instance): super().check(instance) self._submit_version_metadata() self._check_n8n_health() - self._check_n8n_readiness() \ No newline at end of file + self._check_n8n_readiness() diff --git a/n8n/datadog_checks/n8n/metrics.py b/n8n/datadog_checks/n8n/metrics.py index caa24d5f4aeb2..7cdf50338df82 100644 --- a/n8n/datadog_checks/n8n/metrics.py +++ b/n8n/datadog_checks/n8n/metrics.py @@ -62,4 +62,4 @@ 'workflow_executions_total': 'workflow.executions.total', 'process_cpu_seconds_total': 'process.cpu.seconds.total', 'version_info': 'version.info', -} \ No newline at end of file +} diff --git a/n8n/metadata.csv b/n8n/metadata.csv index c51d725516426..ea9cbda42cf15 100644 --- a/n8n/metadata.csv +++ b/n8n/metadata.csv @@ -1,64 +1,64 @@ -metric_name,metric_type,interval,unit_name,per_unit_name,description,orientation,integration,short_name,curated_metric -n8n.active.workflow.count,gauge,,,,Total number of active workflows.,0,n8n,, -n8n.api.request.duration.seconds.count,count,,,,The count of API request duration in seconds,0,n8n,, -n8n.api.request.duration.seconds.sum,count,,,,The sum of API request duration in seconds,0,n8n,, -n8n.api.requests.total,count,,,,Total API requests,0,n8n,, -n8n.cache.errors.total,count,,,,Cache errors,0,n8n,, -n8n.cache.hits.total,count,,,,Cache hits,0,n8n,, -n8n.cache.latency.seconds.count,count,,,,The count of cache operation latency in seconds,0,n8n,, -n8n.cache.latency.seconds.sum,count,,,,The sum of cache operation latency in seconds,0,n8n,, -n8n.cache.misses.total,count,,,,Cache misses,0,n8n,, -n8n.cache.operations.total,count,,,,Total cache operations,0,n8n,, -n8n.eventbus.connections.total,gauge,,,,Active event bus backend connections,0,n8n,, -n8n.eventbus.events.failed.total,count,,,,Total failed event processing,0,n8n,, -n8n.eventbus.events.processed.total,count,,,,Total processed events,0,n8n,, -n8n.eventbus.events.total,count,,,,Total events published on the event bus,0,n8n,, -n8n.eventbus.queue.size,gauge,,,,Current event queue size,0,n8n,, -n8n.http.request.duration.seconds.count,count,,,,The count of the http responses duration labeled with: status_code,0,n8n,, -n8n.http.request.duration.seconds.sum,count,,,,The sum of the http responses duration labeled with: status_code,0,n8n,, -n8n.instance.role.leader,gauge,,,,Whether this main instance is the leader (1) or not (0).,0,n8n,, -n8n.last.activity,gauge,,,,last instance activity (backend request) in Unix time (seconds).,0,n8n,, -n8n.nodejs.active.handles,gauge,,,,Number of active libuv handles grouped by handle type. Every handle type is C++ class name.,0,n8n,, -n8n.nodejs.active.handles.total,gauge,,,,Total number of active handles.,0,n8n,, -n8n.nodejs.active.requests,gauge,,,,Number of active libuv requests grouped by request type. Every request type is C++ class name.,0,n8n,, -n8n.nodejs.active.requests.total,gauge,,,,Total number of active requests.,0,n8n,, -n8n.nodejs.active.resources,gauge,,,,"Number of active resources that are currently keeping the event loop alive, grouped by async resource type.",0,n8n,, -n8n.nodejs.active.resources.total,gauge,,,,Total number of active resources.,0,n8n,, -n8n.nodejs.event.loop.lag.seconds,gauge,,,,Event loop lag in seconds,0,n8n,, -n8n.nodejs.eventloop.lag.max.seconds,gauge,,,,The maximum recorded event loop delay.,0,n8n,, -n8n.nodejs.eventloop.lag.mean.seconds,gauge,,,,The mean of the recorded event loop delays.,0,n8n,, -n8n.nodejs.eventloop.lag.min.seconds,gauge,,,,The minimum recorded event loop delay.,0,n8n,, -n8n.nodejs.eventloop.lag.p50.seconds,gauge,,,,The 50th percentile of the recorded event loop delays.,0,n8n,, -n8n.nodejs.eventloop.lag.p90.seconds,gauge,,,,The 90th percentile of the recorded event loop delays.,0,n8n,, -n8n.nodejs.eventloop.lag.p99.seconds,gauge,,,,The 99th percentile of the recorded event loop delays.,0,n8n,, -n8n.nodejs.eventloop.lag.seconds,gauge,,,,Lag of event loop in seconds.,0,n8n,, -n8n.nodejs.eventloop.lag.stddev.seconds,gauge,,,,The standard deviation of the recorded event loop delays.,0,n8n,, -n8n.nodejs.external.memory.bytes,gauge,,,,Node.js external memory size in bytes.,0,n8n,, -n8n.nodejs.gc.duration.seconds.count,count,,,,The count of garbage collection duration by kind,0,n8n,, -n8n.nodejs.gc.duration.seconds.sum,count,,,,The sum of garbage collection duration by kind,0,n8n,, -n8n.nodejs.heap.size.total.bytes,gauge,,,,Process heap size from Node.js in bytes.,0,n8n,, -n8n.nodejs.heap.size.used.bytes,gauge,,,,Process heap size used from Node.js in bytes.,0,n8n,, -n8n.nodejs.heap.space.size.available.bytes,gauge,,,,Process heap space size available from Node.js in bytes.,0,n8n,, -n8n.nodejs.heap.space.size.total.bytes,gauge,,,,Process heap space size total from Node.js in bytes.,0,n8n,, -n8n.nodejs.heap.space.size.used.bytes,gauge,,,,Process heap space size used from Node.js in bytes.,0,n8n,, -n8n.nodejs.heap.total.bytes,gauge,,,,Total heap size allocated in bytes,0,n8n,, -n8n.nodejs.heap.used.bytes,gauge,,,,Heap memory used in bytes,0,n8n,, -n8n.nodejs.version.info,gauge,,,,Node.js version info.,0,n8n,, -n8n.process.cpu.system.seconds.total,count,,,,Total system CPU time spent in seconds.,0,n8n,, -n8n.process.cpu.user.seconds.total,count,,,,Total user CPU time spent in seconds.,0,n8n,, -n8n.process.heap.bytes,gauge,,,,Process heap size in bytes.,0,n8n,, -n8n.process.max.fds,gauge,,,,Maximum number of open file descriptors.,0,n8n,, -n8n.process.open.fds,gauge,,,,Number of open file descriptors.,0,n8n,, -n8n.process.resident.memory.bytes,gauge,,,,Resident memory size in bytes.,0,n8n,, -n8n.process.start.time.seconds,gauge,,,,Start time of the process since unix epoch in seconds.,0,n8n,, -n8n.process.virtual.memory.bytes,gauge,,,,Virtual memory size in bytes.,0,n8n,, -n8n.queue.job.attempts.total,count,,,,Total number of job attempts,0,n8n,, -n8n.queue.jobs.duration.seconds.count,count,,,,The count of job duration in seconds,0,n8n,, -n8n.queue.jobs.duration.seconds.sum,count,,,,The sum of job duration in seconds,0,n8n,, -n8n.queue.jobs.total,count,,,,Total number of queue jobs,0,n8n,, -n8n.workflow.executions.active,gauge,,,,Number of active workflow executions,0,n8n,, -n8n.workflow.executions.duration.seconds.count,count,,,,The count of workflow execution duration in seconds,0,n8n,, -n8n.workflow.executions.duration.seconds.sum,count,,,,The sum of workflow execution duration in seconds,0,n8n,, -n8n.workflow.executions.total,count,,,,Total number of workflow executions,0,n8n,, -n8n.process.cpu.seconds.total,count,,,,Total user and system CPU time spent in seconds.,0,n8n,, -n8n.version.info,gauge,,,,n8n version info.,0,n8n,, +metric_name,metric_type,interval,unit_name,per_unit_name,description,orientation,integration,short_name,curated_metric,sample_tags +n8n.active.workflow.count,gauge,,,,Total number of active workflows.,0,n8n,,, +n8n.api.request.duration.seconds.count,count,,,,The count of API request duration in seconds,0,n8n,,, +n8n.api.request.duration.seconds.sum,count,,,,The sum of API request duration in seconds,0,n8n,,, +n8n.api.requests.total,count,,,,Total API requests,0,n8n,,, +n8n.cache.errors.total,count,,,,Cache errors,0,n8n,,, +n8n.cache.hits.total,count,,,,Cache hits,0,n8n,,, +n8n.cache.latency.seconds.count,count,,,,The count of cache operation latency in seconds,0,n8n,,, +n8n.cache.latency.seconds.sum,count,,,,The sum of cache operation latency in seconds,0,n8n,,, +n8n.cache.misses.total,count,,,,Cache misses,0,n8n,,, +n8n.cache.operations.total,count,,,,Total cache operations,0,n8n,,, +n8n.eventbus.connections.total,gauge,,,,Active event bus backend connections,0,n8n,,, +n8n.eventbus.events.failed.total,count,,,,Total failed event processing,0,n8n,,, +n8n.eventbus.events.processed.total,count,,,,Total processed events,0,n8n,,, +n8n.eventbus.events.total,count,,,,Total events published on the event bus,0,n8n,,, +n8n.eventbus.queue.size,gauge,,,,Current event queue size,0,n8n,,, +n8n.http.request.duration.seconds.count,count,,,,The count of the http responses duration labeled with: status_code,0,n8n,,, +n8n.http.request.duration.seconds.sum,count,,,,The sum of the http responses duration labeled with: status_code,0,n8n,,, +n8n.instance.role.leader,gauge,,,,Whether this main instance is the leader (1) or not (0).,0,n8n,,, +n8n.last.activity,gauge,,,,last instance activity (backend request) in Unix time (seconds).,0,n8n,,, +n8n.nodejs.active.handles,gauge,,,,Number of active libuv handles grouped by handle type. Every handle type is C++ class name.,0,n8n,,, +n8n.nodejs.active.handles.total,gauge,,,,Total number of active handles.,0,n8n,,, +n8n.nodejs.active.requests,gauge,,,,Number of active libuv requests grouped by request type. Every request type is C++ class name.,0,n8n,,, +n8n.nodejs.active.requests.total,gauge,,,,Total number of active requests.,0,n8n,,, +n8n.nodejs.active.resources,gauge,,,,"Number of active resources that are currently keeping the event loop alive, grouped by async resource type.",0,n8n,,, +n8n.nodejs.active.resources.total,gauge,,,,Total number of active resources.,0,n8n,,, +n8n.nodejs.event.loop.lag.seconds,gauge,,,,Event loop lag in seconds,0,n8n,,, +n8n.nodejs.eventloop.lag.max.seconds,gauge,,,,The maximum recorded event loop delay.,0,n8n,,, +n8n.nodejs.eventloop.lag.mean.seconds,gauge,,,,The mean of the recorded event loop delays.,0,n8n,,, +n8n.nodejs.eventloop.lag.min.seconds,gauge,,,,The minimum recorded event loop delay.,0,n8n,,, +n8n.nodejs.eventloop.lag.p50.seconds,gauge,,,,The 50th percentile of the recorded event loop delays.,0,n8n,,, +n8n.nodejs.eventloop.lag.p90.seconds,gauge,,,,The 90th percentile of the recorded event loop delays.,0,n8n,,, +n8n.nodejs.eventloop.lag.p99.seconds,gauge,,,,The 99th percentile of the recorded event loop delays.,0,n8n,,, +n8n.nodejs.eventloop.lag.seconds,gauge,,,,Lag of event loop in seconds.,0,n8n,,, +n8n.nodejs.eventloop.lag.stddev.seconds,gauge,,,,The standard deviation of the recorded event loop delays.,0,n8n,,, +n8n.nodejs.external.memory.bytes,gauge,,,,Node.js external memory size in bytes.,0,n8n,,, +n8n.nodejs.gc.duration.seconds.count,count,,,,The count of garbage collection duration by kind,0,n8n,,, +n8n.nodejs.gc.duration.seconds.sum,count,,,,The sum of garbage collection duration by kind,0,n8n,,, +n8n.nodejs.heap.size.total.bytes,gauge,,,,Process heap size from Node.js in bytes.,0,n8n,,, +n8n.nodejs.heap.size.used.bytes,gauge,,,,Process heap size used from Node.js in bytes.,0,n8n,,, +n8n.nodejs.heap.space.size.available.bytes,gauge,,,,Process heap space size available from Node.js in bytes.,0,n8n,,, +n8n.nodejs.heap.space.size.total.bytes,gauge,,,,Process heap space size total from Node.js in bytes.,0,n8n,,, +n8n.nodejs.heap.space.size.used.bytes,gauge,,,,Process heap space size used from Node.js in bytes.,0,n8n,,, +n8n.nodejs.heap.total.bytes,gauge,,,,Total heap size allocated in bytes,0,n8n,,, +n8n.nodejs.heap.used.bytes,gauge,,,,Heap memory used in bytes,0,n8n,,, +n8n.nodejs.version.info,gauge,,,,Node.js version info.,0,n8n,,, +n8n.process.cpu.seconds.total,count,,,,Total user and system CPU time spent in seconds.,0,n8n,,, +n8n.process.cpu.system.seconds.total,count,,,,Total system CPU time spent in seconds.,0,n8n,,, +n8n.process.cpu.user.seconds.total,count,,,,Total user CPU time spent in seconds.,0,n8n,,, +n8n.process.heap.bytes,gauge,,,,Process heap size in bytes.,0,n8n,,, +n8n.process.max.fds,gauge,,,,Maximum number of open file descriptors.,0,n8n,,, +n8n.process.open.fds,gauge,,,,Number of open file descriptors.,0,n8n,,, +n8n.process.resident.memory.bytes,gauge,,,,Resident memory size in bytes.,0,n8n,,, +n8n.process.start.time.seconds,gauge,,,,Start time of the process since unix epoch in seconds.,0,n8n,,, +n8n.process.virtual.memory.bytes,gauge,,,,Virtual memory size in bytes.,0,n8n,,, +n8n.queue.job.attempts.total,count,,,,Total number of job attempts,0,n8n,,, +n8n.queue.jobs.duration.seconds.count,count,,,,The count of job duration in seconds,0,n8n,,, +n8n.queue.jobs.duration.seconds.sum,count,,,,The sum of job duration in seconds,0,n8n,,, +n8n.queue.jobs.total,count,,,,Total number of queue jobs,0,n8n,,, +n8n.version.info,gauge,,,,n8n version info.,0,n8n,,, +n8n.workflow.executions.active,gauge,,,,Number of active workflow executions,0,n8n,,, +n8n.workflow.executions.duration.seconds.count,count,,,,The count of workflow execution duration in seconds,0,n8n,,, +n8n.workflow.executions.duration.seconds.sum,count,,,,The sum of workflow execution duration in seconds,0,n8n,,, +n8n.workflow.executions.total,count,,,,Total number of workflow executions,0,n8n,,, diff --git a/n8n/tests/common.py b/n8n/tests/common.py index a5e86da4e2a8f..0b4169b377b1b 100644 --- a/n8n/tests/common.py +++ b/n8n/tests/common.py @@ -91,4 +91,3 @@ def get_fixture_path(filename): 'n8n.process.cpu.seconds.total', 'n8n.version.info', ] - diff --git a/n8n/tests/conftest.py b/n8n/tests/conftest.py index e19a7329d15ad..1ad12d6808609 100644 --- a/n8n/tests/conftest.py +++ b/n8n/tests/conftest.py @@ -7,7 +7,7 @@ import pytest from datadog_checks.dev import docker_run -from datadog_checks.dev.conditions import CheckDockerLogs, CheckEndpoints +from datadog_checks.dev.conditions import CheckEndpoints from . import common @@ -16,7 +16,7 @@ def dd_environment(): compose_file = common.COMPOSE_FILE conditions = [ - #CheckDockerLogs(identifier='n8n', patterns=['server running']), + # CheckDockerLogs(identifier='n8n', patterns=['server running']), CheckEndpoints(common.INSTANCE["openmetrics_endpoint"]), CheckEndpoints(f'{common.OPENMETRICS_URL}/healthz', attempts=60, wait=3), CheckEndpoints(f'{common.OPENMETRICS_URL}/metrics', attempts=60, wait=3), @@ -26,7 +26,7 @@ def dd_environment(): 'instances': [common.INSTANCE], } + @pytest.fixture def instance(): return copy.deepcopy(common.INSTANCE) - diff --git a/n8n/tests/test_e2e.py b/n8n/tests/test_e2e.py index 5bb4d7e3435bb..22e651fc9a9a1 100644 --- a/n8n/tests/test_e2e.py +++ b/n8n/tests/test_e2e.py @@ -2,11 +2,13 @@ # All rights reserved # Licensed under a 3-clause BSD style license (see LICENSE) import pytest + from datadog_checks.base.constants import ServiceCheck from datadog_checks.dev.utils import assert_service_checks + @pytest.mark.e2e def test_check_n8n_e2e(dd_agent_check, instance): aggregator = dd_agent_check(instance, rate=True) aggregator.assert_service_check('n8n.openmetrics.health', ServiceCheck.OK, count=2) - assert_service_checks(aggregator) \ No newline at end of file + assert_service_checks(aggregator) diff --git a/n8n/tests/test_unit.py b/n8n/tests/test_unit.py index 8cc6079c394d5..fcf5e2edc08ed 100644 --- a/n8n/tests/test_unit.py +++ b/n8n/tests/test_unit.py @@ -6,6 +6,7 @@ from . import common + def test_check_namespace_default(): """ Test that the check applies the correct namespace when raw_metric_prefix is 'n8n' (default). @@ -15,7 +16,7 @@ def test_check_namespace_default(): } check = N8nCheck('n8n', {}, [instance]) config = check.get_default_config() - + # When raw_metric_prefix is 'n8n' (default), namespace should be 'n8n' assert config['namespace'] == 'n8n', f"Expected namespace 'n8n', got '{config['namespace']}'" @@ -30,13 +31,16 @@ def test_check_namespace_custom(): } check = N8nCheck('n8n', {}, [instance]) config = check.get_default_config() - + # When raw_metric_prefix is custom, namespace should be 'n8n.' - assert config['namespace'] == 'n8n.my_n8n_team', f"Expected namespace 'n8n.my_n8n_team', got '{config['namespace']}'" + assert config['namespace'] == 'n8n.my_n8n_team', ( + f"Expected namespace 'n8n.my_n8n_team', got '{config['namespace']}'" + ) + def test_e2e_metrics(dd_agent_check, instance): aggregator = dd_agent_check(instance, rate=True) - + for metric in common.E2E_METRICS: aggregator.assert_metric(metric) aggregator.assert_all_metrics_covered() From abce4d60f6d349a12f8cdc90e67116b32e94d5ac Mon Sep 17 00:00:00 2001 From: HadhemiDD Date: Thu, 6 Nov 2025 13:15:52 +0100 Subject: [PATCH 04/20] fix --- n8n/datadog_checks/n8n/check.py | 1 - n8n/manifest.json | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/n8n/datadog_checks/n8n/check.py b/n8n/datadog_checks/n8n/check.py index 90cb0466936b6..5f947cca05846 100644 --- a/n8n/datadog_checks/n8n/check.py +++ b/n8n/datadog_checks/n8n/check.py @@ -13,7 +13,6 @@ class N8nCheck(OpenMetricsBaseCheckV2): - # TODO Decide later if we want to use customer prefix or ignore it. __NAMESPACE__ = 'n8n' DEFAULT_METRIC_LIMIT = 0 diff --git a/n8n/manifest.json b/n8n/manifest.json index 8626a71b61bcb..cb8753e22c451 100644 --- a/n8n/manifest.json +++ b/n8n/manifest.json @@ -8,14 +8,13 @@ "configuration": "README.md#Setup", "support": "README.md#Support", "changelog": "CHANGELOG.md", - "description": "Monitor the health and performance of n8n", + "description": "Monitor the health and performance of n8n.", "title": "n8n", "media": [], "classifier_tags": [ "Supported OS::Linux", "Supported OS::Windows", "Supported OS::macOS", - "Category::AI", "Offering::Integration", "Queried Data Type::Metrics", "Queried Data Type::Logs", From 8a35317a49aea750fae08847fc321bd5c5ca1660 Mon Sep 17 00:00:00 2001 From: HadhemiDD Date: Fri, 7 Nov 2025 12:36:57 +0100 Subject: [PATCH 05/20] commit --- n8n/assets/dashboards/n8n_overview.json | 10 +++++++++- n8n/datadog_checks/n8n/config_models/instance.py | 3 ++- n8n/manifest.json | 2 ++ 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/n8n/assets/dashboards/n8n_overview.json b/n8n/assets/dashboards/n8n_overview.json index 5ff4d9df29c8c..6f5d5cd95db6b 100644 --- a/n8n/assets/dashboards/n8n_overview.json +++ b/n8n/assets/dashboards/n8n_overview.json @@ -1 +1,9 @@ -{"title":"N8N Overview Dashboard","description":"[[suggested_dashboards]]","widgets":[],"template_variables":[],"layout_type":"ordered","notify_list":[],"reflow_type":"fixed","pause_auto_refresh":false} \ No newline at end of file +{ + "title": "N8N Overview Dashboard", + "description": "N8N Overview Dashboard", + "widgets": [], + "template_variables": [], + "layout_type": "ordered", + "notify_list": [], + "reflow_type": "fixed" +} \ No newline at end of file diff --git a/n8n/datadog_checks/n8n/config_models/instance.py b/n8n/datadog_checks/n8n/config_models/instance.py index e0d208ce314fa..41324a6b93245 100644 --- a/n8n/datadog_checks/n8n/config_models/instance.py +++ b/n8n/datadog_checks/n8n/config_models/instance.py @@ -13,6 +13,7 @@ from typing import Any, Optional, Union from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator +from typing_extensions import Literal from datadog_checks.base.utils.functions import identity from datadog_checks.base.utils.models import validation @@ -109,7 +110,7 @@ class InstanceConfig(BaseModel): ignore_connection_errors: Optional[bool] = None ignore_tags: Optional[tuple[str, ...]] = None include_labels: Optional[tuple[str, ...]] = None - kerberos_auth: Optional[str] = None + kerberos_auth: Optional[Literal['required', 'optional', 'disabled']] = None kerberos_cache: Optional[str] = None kerberos_delegate: Optional[bool] = None kerberos_force_initiate: Optional[bool] = None diff --git a/n8n/manifest.json b/n8n/manifest.json index cb8753e22c451..607f65ee5b0e6 100644 --- a/n8n/manifest.json +++ b/n8n/manifest.json @@ -15,6 +15,8 @@ "Supported OS::Linux", "Supported OS::Windows", "Supported OS::macOS", + "Category::Metrics", + "Category::Log Collection", "Offering::Integration", "Queried Data Type::Metrics", "Queried Data Type::Logs", From 020e48f7ea8c4d4afccfae8e3f72f0ccf8459e8b Mon Sep 17 00:00:00 2001 From: HadhemiDD Date: Fri, 7 Nov 2025 13:07:56 +0100 Subject: [PATCH 06/20] labeler --- .github/workflows/config/labeler.yml | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/.github/workflows/config/labeler.yml b/.github/workflows/config/labeler.yml index 1a07add94ffdd..adf93307ad3dc 100644 --- a/.github/workflows/config/labeler.yml +++ b/.github/workflows/config/labeler.yml @@ -89,6 +89,8 @@ integration/azure_active_directory: - azure_active_directory/**/* integration/azure_iot_edge: - azure_iot_edge/**/* +integration/barracuda_secure_edge: +- barracuda_secure_edge/**/* integration/bentoml: - bentoml/**/* integration/beyondtrust_identity_security_insights: @@ -157,6 +159,8 @@ integration/cloud_foundry_api: - cloud_foundry_api/**/* integration/cloudera: - cloudera/**/* +integration/cloudgen_firewall: +- cloudgen_firewall/**/* integration/cockroachdb: - cockroachdb/**/* integration/cofense_triage: @@ -359,10 +363,10 @@ integration/kafka: - kafka/**/* integration/kafka_consumer: - kafka_consumer/**/* -integration/karpenter: -- karpenter/**/* integration/kandji: - kandji/**/* +integration/karpenter: +- karpenter/**/* integration/keda: - keda/**/* integration/keeper: @@ -467,6 +471,8 @@ integration/mux: - mux/**/* integration/mysql: - mysql/**/* +integration/n8n: +- n8n/**/* integration/nagios: - nagios/**/* integration/network: @@ -783,10 +789,6 @@ integration/zerofox_cloud_platform: - zerofox_cloud_platform/**/* integration/zk: - zk/**/* -integration/barracuda_secure_edge: -- barracuda_secure_edge/**/* -integration/cloudgen_firewall: -- cloudgen_firewall/**/* integration/zscaler_private_access: - zscaler_private_access/**/* qa/skip-qa: From a798858dcd94ff49e69727dee3ce99df82da5b22 Mon Sep 17 00:00:00 2001 From: HadhemiDD Date: Fri, 7 Nov 2025 13:23:21 +0100 Subject: [PATCH 07/20] fix tests --- n8n/assets/service_checks.json | 29 +++++++++++++++++++ n8n/tests/common.py | 2 +- ...docker-compose.yml => docker-compose.yaml} | 0 3 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 n8n/assets/service_checks.json rename n8n/tests/docker/{docker-compose.yml => docker-compose.yaml} (100%) diff --git a/n8n/assets/service_checks.json b/n8n/assets/service_checks.json new file mode 100644 index 0000000000000..1b5d085f8020c --- /dev/null +++ b/n8n/assets/service_checks.json @@ -0,0 +1,29 @@ +[ + { + "agent_version": "7.75.0", + "integration": "n8n", + "check": "n8n.openmetrics.health", + "statuses": ["ok", "critical"], + "groups": [], + "name": "n8n OpenMetrics Health", + "description": "Returns `CRITICAL` if the check cannot access the metrics endpoint. Returns `OK` otherwise." + }, + { + "agent_version": "7.75.0", + "integration": "n8n", + "check": "n8n.health.status", + "statuses": ["ok", "critical"], + "groups": [], + "name": "n8n Health Status", + "description": "Returns `CRITICAL` if the health check endpoint returns an unhealthy status. Returns `OK` otherwise." + }, + { + "agent_version": "7.75.0", + "integration": "n8n", + "check": "n8n.readiness.status", + "statuses": ["ok", "critical"], + "groups": [], + "name": "n8n Readiness Status", + "description": "Returns `CRITICAL` if the readiness check endpoint returns an unready status. Returns `OK` otherwise." + } +] diff --git a/n8n/tests/common.py b/n8n/tests/common.py index 0b4169b377b1b..d2bd83ad02762 100644 --- a/n8n/tests/common.py +++ b/n8n/tests/common.py @@ -6,7 +6,7 @@ from datadog_checks.dev import get_docker_hostname HERE = os.path.dirname(os.path.abspath(__file__)) -COMPOSE_FILE = os.path.join(HERE, 'docker', 'docker-compose.yml') +COMPOSE_FILE = os.path.join(HERE, 'docker', 'docker-compose.yaml') HOST = get_docker_hostname() PORT = 5678 diff --git a/n8n/tests/docker/docker-compose.yml b/n8n/tests/docker/docker-compose.yaml similarity index 100% rename from n8n/tests/docker/docker-compose.yml rename to n8n/tests/docker/docker-compose.yaml From b8d7c7894688fc8bd00d5d16dc425a713d2f9322 Mon Sep 17 00:00:00 2001 From: HadhemiDD Date: Fri, 7 Nov 2025 14:00:16 +0100 Subject: [PATCH 08/20] fix pr number --- n8n/changelog.d/{1.added => 21835.added} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename n8n/changelog.d/{1.added => 21835.added} (100%) diff --git a/n8n/changelog.d/1.added b/n8n/changelog.d/21835.added similarity index 100% rename from n8n/changelog.d/1.added rename to n8n/changelog.d/21835.added From 70e39510b0a283f12e77eb51a757236cf276991a Mon Sep 17 00:00:00 2001 From: HadhemiDD <43783545+HadhemiDD@users.noreply.github.com> Date: Mon, 10 Nov 2025 10:33:26 +0100 Subject: [PATCH 09/20] Update n8n/README.md Co-authored-by: Ursula Chen <58821586+urseberry@users.noreply.github.com> --- n8n/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/n8n/README.md b/n8n/README.md index f127a12e932d6..253a99cfbbdbd 100644 --- a/n8n/README.md +++ b/n8n/README.md @@ -49,7 +49,7 @@ See [service_checks.json][8] for a list of service checks provided by this integ Need help? Contact [Datadog support][9]. -[1]: **LINK_TO_INTEGRATION_SITE** +[1]: https://n8n.io/ [2]: https://app.datadoghq.com/account/settings/agent/latest [3]: https://docs.datadoghq.com/containers/kubernetes/integrations/ [4]: https://github.com/DataDog/integrations-core/blob/master/n8n/datadog_checks/n8n/data/conf.yaml.example From fdb67765877d6a4d928795c11a02869d68a47e26 Mon Sep 17 00:00:00 2001 From: HadhemiDD <43783545+HadhemiDD@users.noreply.github.com> Date: Mon, 10 Nov 2025 10:33:49 +0100 Subject: [PATCH 10/20] Update n8n/assets/configuration/spec.yaml Co-authored-by: Ursula Chen <58821586+urseberry@users.noreply.github.com> --- n8n/assets/configuration/spec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/n8n/assets/configuration/spec.yaml b/n8n/assets/configuration/spec.yaml index 99ae3e00b03b6..bc73ed141e15d 100644 --- a/n8n/assets/configuration/spec.yaml +++ b/n8n/assets/configuration/spec.yaml @@ -14,7 +14,7 @@ files: openmetrics_endpoint.display_priority: 1 openmetrics_endpoint.value.example: http://localhost:5678 openmetrics_endpoint.description: | - Endpoint exposing the n8n's metrics in the OpenMetrics format. For more information refer to: + Endpoint exposing the n8n's metrics in the OpenMetrics format. For more information, refer to: https://docs.n8n.io/hosting/logging-monitoring/monitoring/ https://docs.n8n.io/hosting/configuration/environment-variables/endpoints/ raw_metric_prefix.description: | From 7c0fee88daba7cf59024baf5f019f075f0bb4591 Mon Sep 17 00:00:00 2001 From: HadhemiDD <43783545+HadhemiDD@users.noreply.github.com> Date: Mon, 10 Nov 2025 10:34:53 +0100 Subject: [PATCH 11/20] Update n8n/assets/configuration/spec.yaml Co-authored-by: Ursula Chen <58821586+urseberry@users.noreply.github.com> --- n8n/assets/configuration/spec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/n8n/assets/configuration/spec.yaml b/n8n/assets/configuration/spec.yaml index bc73ed141e15d..7326a326d6695 100644 --- a/n8n/assets/configuration/spec.yaml +++ b/n8n/assets/configuration/spec.yaml @@ -29,7 +29,7 @@ files: raw_metric_prefix.hidden: false - name: server_port description: | - The port exposing the HTTP Endpoint of the N8N API. + The port exposing the HTTP endpoint of the n8n API. value: display_default: 5678 type: integer From eb345a677afe1a4c4d982c0370baffa36bc3d493 Mon Sep 17 00:00:00 2001 From: HadhemiDD <43783545+HadhemiDD@users.noreply.github.com> Date: Mon, 10 Nov 2025 10:35:05 +0100 Subject: [PATCH 12/20] Update n8n/assets/configuration/spec.yaml Co-authored-by: Ursula Chen <58821586+urseberry@users.noreply.github.com> --- n8n/assets/configuration/spec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/n8n/assets/configuration/spec.yaml b/n8n/assets/configuration/spec.yaml index 7326a326d6695..258a027ef4912 100644 --- a/n8n/assets/configuration/spec.yaml +++ b/n8n/assets/configuration/spec.yaml @@ -19,7 +19,7 @@ files: https://docs.n8n.io/hosting/configuration/environment-variables/endpoints/ raw_metric_prefix.description: | The prefix prepended to all metrics from n8n. - If not set, the default prefix will be used. + If not set, the default prefix is used. The default prefix is 'n8n'. If you are using a custom prefix in n8n through N8N_METRICS_PREFIX, you can set it here. raw_metric_prefix.value: From 543ad67dfb9feb9acb8cbd2355f522b980d56b95 Mon Sep 17 00:00:00 2001 From: HadhemiDD Date: Wed, 12 Nov 2025 11:24:25 +0100 Subject: [PATCH 13/20] readme --- n8n/README.md | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/n8n/README.md b/n8n/README.md index 253a99cfbbdbd..cd08df785ed2a 100644 --- a/n8n/README.md +++ b/n8n/README.md @@ -4,10 +4,14 @@ This check monitors [n8n][1] through the Datadog Agent. -Include a high level overview of what this integration does: -- What does your product do (in 1-2 sentences)? -- What value will customers get from this integration, and why is it valuable to them? -- What specific data will your integration monitor, and what's the value of that data? +Collect n8n metrics including: +- Cache metrics: Hit and miss statistics. +- Message event bus metrics: Event-related metrics. +- Workflow metrics: Can include workflow ID labels. +- Node metrics: Can include node type labels. +- Credential metrics: Can include credential type labels. +- Queue Metrics + ## Setup @@ -20,6 +24,12 @@ No additional installation is needed on your server. ### Configuration +The `/metrics` endpoint is disabled by default and must be enabled by setting `N8N_METRICS`=`true`. You can also customize the metric prefix using `N8N_METRICS_PREFIX` (default is `n8n_`). + +Note that the `/metrics` endpoint is only available for self-hosted instances and is not available on n8n Cloud. + +For the datadog agent to collect metrics, you will need to follow the instructions provided [here][10]. + 1. Edit the `n8n.d/conf.yaml` file, in the `conf.d/` folder at the root of your Agent's configuration directory to start collecting your n8n performance data. See the [sample n8n.d/conf.yaml][4] for all available configuration options. 2. [Restart the Agent][5]. @@ -58,3 +68,4 @@ Need help? Contact [Datadog support][9]. [7]: https://github.com/DataDog/integrations-core/blob/master/n8n/metadata.csv [8]: https://github.com/DataDog/integrations-core/blob/master/n8n/assets/service_checks.json [9]: https://docs.datadoghq.com/help/ +[10]: https://docs.n8n.io/hosting/configuration/configuration-examples/prometheus/ From 8b278400f0f599d8b57ebf9aa3842bb814f0f606 Mon Sep 17 00:00:00 2001 From: HadhemiDD Date: Thu, 13 Nov 2025 12:31:12 +0100 Subject: [PATCH 14/20] cleanup --- n8n/datadog_checks/n8n/data/conf.yaml.example | 6 +-- n8n/datadog_checks/n8n/metrics.py | 8 ++++ n8n/metadata.csv | 9 +++++ n8n/tests/common.py | 11 +++++- n8n/tests/fixtures/n8n.txt | 39 +++++++++++++++++++ 5 files changed, 68 insertions(+), 5 deletions(-) diff --git a/n8n/datadog_checks/n8n/data/conf.yaml.example b/n8n/datadog_checks/n8n/data/conf.yaml.example index 846be349a3407..af98392be000c 100644 --- a/n8n/datadog_checks/n8n/data/conf.yaml.example +++ b/n8n/datadog_checks/n8n/data/conf.yaml.example @@ -14,7 +14,7 @@ init_config: instances: ## @param openmetrics_endpoint - string - required - ## Endpoint exposing the n8n's metrics in the OpenMetrics format. For more information refer to: + ## Endpoint exposing the n8n's metrics in the OpenMetrics format. For more information, refer to: ## https://docs.n8n.io/hosting/logging-monitoring/monitoring/ ## https://docs.n8n.io/hosting/configuration/environment-variables/endpoints/ # @@ -22,7 +22,7 @@ instances: ## @param raw_metric_prefix - string - optional - default: n8n ## The prefix prepended to all metrics from n8n. - ## If not set, the default prefix will be used. + ## If not set, the default prefix is used. ## The default prefix is 'n8n'. ## If you are using a custom prefix in n8n through N8N_METRICS_PREFIX, you can set it here. # @@ -613,7 +613,7 @@ instances: # - ## @param server_port - integer - optional - default: 5678 - ## The port exposing the HTTP Endpoint of the N8N API. + ## The port exposing the HTTP endpoint of the n8n API. # # server_port: diff --git a/n8n/datadog_checks/n8n/metrics.py b/n8n/datadog_checks/n8n/metrics.py index 7cdf50338df82..7decc9e2046e7 100644 --- a/n8n/datadog_checks/n8n/metrics.py +++ b/n8n/datadog_checks/n8n/metrics.py @@ -54,7 +54,15 @@ 'process_resident_memory_bytes': 'process.resident.memory.bytes', 'process_start_time_seconds': 'process.start.time.seconds', 'process_virtual_memory_bytes': 'process.virtual.memory.bytes', + 'queue_job_active_total': 'queue.job.active.total', 'queue_job_attempts_total': 'queue.job.attempts.total', + 'queue_job_completed_total': 'queue.job.completed.total', + 'queue_job_delayed_total': 'queue.job.delayed.total', + 'queue_job_dequeued_total': 'queue.job.dequeued.total', + 'queue_job_enqueued_total': 'queue.job.enqueued.total', + 'queue_job_failed_total': 'queue.job.failed.total', + 'queue_job_waiting_duration_seconds': 'queue.job.waiting.duration.seconds', + 'queue_job_waiting_total': 'queue.job.waiting.total', 'queue_jobs_duration_seconds': 'queue.jobs.duration.seconds', 'queue_jobs_total': 'queue.jobs.total', 'workflow_executions_active': 'workflow.executions.active', diff --git a/n8n/metadata.csv b/n8n/metadata.csv index ea9cbda42cf15..72fc9305cff1b 100644 --- a/n8n/metadata.csv +++ b/n8n/metadata.csv @@ -53,7 +53,16 @@ n8n.process.open.fds,gauge,,,,Number of open file descriptors.,0,n8n,,, n8n.process.resident.memory.bytes,gauge,,,,Resident memory size in bytes.,0,n8n,,, n8n.process.start.time.seconds,gauge,,,,Start time of the process since unix epoch in seconds.,0,n8n,,, n8n.process.virtual.memory.bytes,gauge,,,,Virtual memory size in bytes.,0,n8n,,, +n8n.queue.job.active.total,gauge,,,,Number of jobs currently being processed,0,n8n,,, n8n.queue.job.attempts.total,count,,,,Total number of job attempts,0,n8n,,, +n8n.queue.job.completed.total,count,,,,Number of jobs completed successfully,0,n8n,,, +n8n.queue.job.delayed.total,gauge,,,,Number of jobs scheduled to run later,0,n8n,,, +n8n.queue.job.dequeued.total,count,,,,Number of jobs dequeued (picked up from queue),0,n8n,,, +n8n.queue.job.enqueued.total,count,,,,Number of jobs added to the queue,0,n8n,,, +n8n.queue.job.failed.total,count,,,,Number of jobs that have failed,0,n8n,,, +n8n.queue.job.waiting.duration.seconds.count,count,,,,The count of duration jobs spend waiting before being processed,0,n8n,,, +n8n.queue.job.waiting.duration.seconds.sum,count,,,,The sum of duration jobs spend waiting before being processed,0,n8n,,, +n8n.queue.job.waiting.total,gauge,,,,Number of jobs currently waiting in the queue,0,n8n,,, n8n.queue.jobs.duration.seconds.count,count,,,,The count of job duration in seconds,0,n8n,,, n8n.queue.jobs.duration.seconds.sum,count,,,,The sum of job duration in seconds,0,n8n,,, n8n.queue.jobs.total,count,,,,Total number of queue jobs,0,n8n,,, diff --git a/n8n/tests/common.py b/n8n/tests/common.py index d2bd83ad02762..d338a8b08108d 100644 --- a/n8n/tests/common.py +++ b/n8n/tests/common.py @@ -19,8 +19,6 @@ def get_fixture_path(filename): INSTANCE = { 'openmetrics_endpoint': f'{OPENMETRICS_URL}/metrics', } -COMPOSE_FILE = os.path.join(HERE, 'docker', 'docker-compose.yaml') - E2E_METADATA = { 'docker_volumes': ['/var/run/docker.sock:/var/run/docker.sock:ro'], @@ -80,7 +78,16 @@ def get_fixture_path(filename): 'n8n.process.resident.memory.bytes', 'n8n.process.start.time.seconds', 'n8n.process.virtual.memory.bytes', + 'n8n.queue.job.active.total', 'n8n.queue.job.attempts.total', + 'n8n.queue.job.completed.total', + 'n8n.queue.job.delayed.total', + 'n8n.queue.job.dequeued.total', + 'n8n.queue.job.enqueued.total', + 'n8n.queue.job.failed.total', + 'n8n.queue.job.waiting.duration.seconds.count', + 'n8n.queue.job.waiting.duration.seconds.sum', + 'n8n.queue.job.waiting.total', 'n8n.queue.jobs.duration.seconds.count', 'n8n.queue.jobs.duration.seconds.sum', 'n8n.queue.jobs.total', diff --git a/n8n/tests/fixtures/n8n.txt b/n8n/tests/fixtures/n8n.txt index eb301d9b58bec..fabef5491c0f2 100644 --- a/n8n/tests/fixtures/n8n.txt +++ b/n8n/tests/fixtures/n8n.txt @@ -256,6 +256,45 @@ n8n_queue_jobs_duration_seconds_bucket{le="+Inf"} 150 n8n_queue_jobs_duration_seconds_sum 44.8 n8n_queue_jobs_duration_seconds_count 150 +# HELP n8n_queue_job_waiting_total Number of jobs currently waiting in the queue +# TYPE n8n_queue_job_waiting_total gauge +n8n_queue_job_waiting_total{queue="default"} 3 + +# HELP n8n_queue_job_active_total Number of jobs currently being processed +# TYPE n8n_queue_job_active_total gauge +n8n_queue_job_active_total{queue="default"} 2 + +# HELP n8n_queue_job_completed_total Number of jobs completed successfully +# TYPE n8n_queue_job_completed_total counter +n8n_queue_job_completed_total{queue="default"} 15892 + +# HELP n8n_queue_job_failed_total Number of jobs that have failed +# TYPE n8n_queue_job_failed_total counter +n8n_queue_job_failed_total{queue="default"} 47 + +# HELP n8n_queue_job_dequeued_total Number of jobs dequeued (picked up from queue) +# TYPE n8n_queue_job_dequeued_total counter +n8n_queue_job_dequeued_total{queue="default"} 15939 + +# HELP n8n_queue_job_enqueued_total Number of jobs added to the queue +# TYPE n8n_queue_job_enqueued_total counter +n8n_queue_job_enqueued_total{queue="default"} 15670 + +# HELP n8n_queue_job_delayed_total Number of jobs scheduled to run later +# TYPE n8n_queue_job_delayed_total gauge +n8n_queue_job_delayed_total{queue="default"} 5 + +# HELP n8n_queue_job_waiting_duration_seconds Duration jobs spend waiting before being processed +# TYPE n8n_queue_job_waiting_duration_seconds histogram +n8n_queue_job_waiting_duration_seconds_bucket{queue="default",le="0.1"} 50 +n8n_queue_job_waiting_duration_seconds_bucket{queue="default",le="1"} 241 +n8n_queue_job_waiting_duration_seconds_bucket{queue="default",le="5"} 820 +n8n_queue_job_waiting_duration_seconds_bucket{queue="default",le="10"} 1105 +n8n_queue_job_waiting_duration_seconds_bucket{queue="default",le="30"} 1240 +n8n_queue_job_waiting_duration_seconds_bucket{queue="default",le="+Inf"} 1253 +n8n_queue_job_waiting_duration_seconds_sum{queue="default"} 450.32 +n8n_queue_job_waiting_duration_seconds_count{queue="default"} 1253 + # HELP n8n_api_requests_total Total API requests # TYPE n8n_api_requests_total counter n8n_api_requests_total{method="GET",endpoint="/workflows"} 240 From d1929d67b3acab07e8671beee8ef83710fad942a Mon Sep 17 00:00:00 2001 From: HadhemiDD Date: Thu, 13 Nov 2025 12:58:07 +0100 Subject: [PATCH 15/20] add metrics --- n8n/datadog_checks/n8n/metrics.py | 3 +++ n8n/metadata.csv | 3 +++ n8n/tests/common.py | 3 +++ n8n/tests/fixtures/n8n.txt | 45 ++++++++++++++++++++++++++++++- 4 files changed, 53 insertions(+), 1 deletion(-) diff --git a/n8n/datadog_checks/n8n/metrics.py b/n8n/datadog_checks/n8n/metrics.py index 7decc9e2046e7..8aa138c097b2e 100644 --- a/n8n/datadog_checks/n8n/metrics.py +++ b/n8n/datadog_checks/n8n/metrics.py @@ -68,6 +68,9 @@ 'workflow_executions_active': 'workflow.executions.active', 'workflow_executions_duration_seconds': 'workflow.executions.duration.seconds', 'workflow_executions_total': 'workflow.executions.total', + 'workflow_failed_total': 'workflow.failed.total', + 'workflow_started_total': 'workflow.started.total', + 'workflow_success_total': 'workflow.success.total', 'process_cpu_seconds_total': 'process.cpu.seconds.total', 'version_info': 'version.info', } diff --git a/n8n/metadata.csv b/n8n/metadata.csv index 72fc9305cff1b..d6c3992632d93 100644 --- a/n8n/metadata.csv +++ b/n8n/metadata.csv @@ -71,3 +71,6 @@ n8n.workflow.executions.active,gauge,,,,Number of active workflow executions,0,n n8n.workflow.executions.duration.seconds.count,count,,,,The count of workflow execution duration in seconds,0,n8n,,, n8n.workflow.executions.duration.seconds.sum,count,,,,The sum of workflow execution duration in seconds,0,n8n,,, n8n.workflow.executions.total,count,,,,Total number of workflow executions,0,n8n,,, +n8n.workflow.failed.total,count,,,,Total number of workflows that failed,0,n8n,,, +n8n.workflow.started.total,count,,,,Total number of workflows started,0,n8n,,, +n8n.workflow.success.total,count,,,,Total number of workflows completed successfully,0,n8n,,, diff --git a/n8n/tests/common.py b/n8n/tests/common.py index d338a8b08108d..ba0ab1f83d170 100644 --- a/n8n/tests/common.py +++ b/n8n/tests/common.py @@ -95,6 +95,9 @@ def get_fixture_path(filename): 'n8n.workflow.executions.duration.seconds.count', 'n8n.workflow.executions.duration.seconds.sum', 'n8n.workflow.executions.total', + 'n8n.workflow.failed.total', + 'n8n.workflow.started.total', + 'n8n.workflow.success.total', 'n8n.process.cpu.seconds.total', 'n8n.version.info', ] diff --git a/n8n/tests/fixtures/n8n.txt b/n8n/tests/fixtures/n8n.txt index fabef5491c0f2..c670f02d7fe46 100644 --- a/n8n/tests/fixtures/n8n.txt +++ b/n8n/tests/fixtures/n8n.txt @@ -241,6 +241,28 @@ n8n_workflow_executions_duration_seconds_bucket{le="+Inf",workflow_id="wf_5d6c8e n8n_workflow_executions_duration_seconds_sum{workflow_id="wf_5d6c8e1f"} 12.7 n8n_workflow_executions_duration_seconds_count{workflow_id="wf_5d6c8e1f"} 49 +# HELP n8n_workflow_started_total Total number of workflows started +# TYPE n8n_workflow_started_total counter +n8n_workflow_started_total 25634 +n8n_workflow_started_total{workflow_id="12",workflow_name="CRM Sync"} 8142 +n8n_workflow_started_total{workflow_id="25",workflow_name="Webhook Intake"} 14290 +n8n_workflow_started_total{workflow_id="33",workflow_name="Slack Alerts"} 2202 + +# HELP n8n_workflow_success_total Total number of workflows completed successfully +# TYPE n8n_workflow_success_total counter +n8n_workflow_success_total 25209 +n8n_workflow_success_total{workflow_id="12",workflow_name="CRM Sync"} 8059 +n8n_workflow_success_total{workflow_id="25",workflow_name="Webhook Intake"} 14135 +n8n_workflow_success_total{workflow_id="33",workflow_name="Slack Alerts"} 2015 + +# HELP n8n_workflow_failed_total Total number of workflows that failed +# TYPE n8n_workflow_failed_total counter +n8n_workflow_failed_total 425 +n8n_workflow_failed_total{workflow_id="12",workflow_name="CRM Sync"} 83 +n8n_workflow_failed_total{workflow_id="25",workflow_name="Webhook Intake"} 155 +n8n_workflow_failed_total{workflow_id="33",workflow_name="Slack Alerts"} 187 + + # HELP n8n_queue_jobs_total Total number of queue jobs # TYPE n8n_queue_jobs_total counter n8n_queue_jobs_total{state="waiting"} 3 @@ -363,4 +385,25 @@ n8n_workflow_executions_active 3 # HELP n8n_queue_job_attempts_total Total number of job attempts # TYPE n8n_queue_job_attempts_total counter n8n_queue_job_attempts_total{result="success"} 435 -n8n_queue_job_attempts_total{result="failed"} 12 \ No newline at end of file +n8n_queue_job_attempts_total{result="failed"} 12 + +# HELP n8n_workflow_started_total Total number of workflows started +# TYPE n8n_workflow_started_total counter +n8n_workflow_started_total 25634 +n8n_workflow_started_total{workflow_id="12",workflow_name="CRM Sync"} 8142 +n8n_workflow_started_total{workflow_id="25",workflow_name="Webhook Intake"} 14290 +n8n_workflow_started_total{workflow_id="33",workflow_name="Slack Alerts"} 2202 + +# HELP n8n_workflow_success_total Total number of workflows completed successfully +# TYPE n8n_workflow_success_total counter +n8n_workflow_success_total 25209 +n8n_workflow_success_total{workflow_id="12",workflow_name="CRM Sync"} 8059 +n8n_workflow_success_total{workflow_id="25",workflow_name="Webhook Intake"} 14135 +n8n_workflow_success_total{workflow_id="33",workflow_name="Slack Alerts"} 2015 + +# HELP n8n_workflow_failed_total Total number of workflows that failed +# TYPE n8n_workflow_failed_total counter +n8n_workflow_failed_total 425 +n8n_workflow_failed_total{workflow_id="12",workflow_name="CRM Sync"} 83 +n8n_workflow_failed_total{workflow_id="25",workflow_name="Webhook Intake"} 155 +n8n_workflow_failed_total{workflow_id="33",workflow_name="Slack Alerts"} 187 \ No newline at end of file From a6b46236c8bdadfd50d5369b18d37411e135c4fe Mon Sep 17 00:00:00 2001 From: HadhemiDD Date: Fri, 14 Nov 2025 16:24:13 +0100 Subject: [PATCH 16/20] commit --- n8n/README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/n8n/README.md b/n8n/README.md index cd08df785ed2a..6a1a92c121804 100644 --- a/n8n/README.md +++ b/n8n/README.md @@ -50,8 +50,6 @@ The n8n integration does not include any events. ### Service Checks -The n8n integration does not include any service checks. - See [service_checks.json][8] for a list of service checks provided by this integration. ## Troubleshooting From 71d162eaf4c2c17fc058cd1b956a3a8fe56e2f94 Mon Sep 17 00:00:00 2001 From: HadhemiDD Date: Tue, 18 Nov 2025 13:36:38 +0100 Subject: [PATCH 17/20] metrics types --- n8n/datadog_checks/n8n/metrics.py | 47 ++++++++++++++++--------------- n8n/metadata.csv | 42 +++++++++++++-------------- n8n/tests/common.py | 42 +++++++++++++-------------- 3 files changed, 67 insertions(+), 64 deletions(-) diff --git a/n8n/datadog_checks/n8n/metrics.py b/n8n/datadog_checks/n8n/metrics.py index 8aa138c097b2e..b530b74f6b781 100644 --- a/n8n/datadog_checks/n8n/metrics.py +++ b/n8n/datadog_checks/n8n/metrics.py @@ -7,16 +7,16 @@ METRIC_MAP = { 'active_workflow_count': 'active.workflow.count', 'api_request_duration_seconds': 'api.request.duration.seconds', - 'api_requests_total': 'api.requests.total', - 'cache_errors_total': 'cache.errors.total', - 'cache_hits_total': 'cache.hits.total', + 'api_requests_total': 'api.requests', + 'cache_errors_total': 'cache.errors', + 'cache_hits_total': 'cache.hits', 'cache_latency_seconds': 'cache.latency.seconds', - 'cache_misses_total': 'cache.misses.total', - 'cache_operations_total': 'cache.operations.total', + 'cache_misses_total': 'cache.misses', + 'cache_operations_total': 'cache.operations', 'eventbus_connections_total': 'eventbus.connections.total', - 'eventbus_events_failed_total': 'eventbus.events.failed.total', - 'eventbus_events_processed_total': 'eventbus.events.processed.total', - 'eventbus_events_total': 'eventbus.events.total', + 'eventbus_events_failed_total': 'eventbus.events.failed', + 'eventbus_events_processed_total': 'eventbus.events.processed', + 'eventbus_events_total': 'eventbus.events', 'eventbus_queue_size': 'eventbus.queue.size', 'http_request_duration_seconds': 'http.request.duration.seconds', 'instance_role_leader': 'instance.role.leader', @@ -46,31 +46,34 @@ 'nodejs_heap_total_bytes': 'nodejs.heap.total.bytes', 'nodejs_heap_used_bytes': 'nodejs.heap.used.bytes', 'nodejs_version_info': 'nodejs.version.info', - 'process_cpu_system_seconds_total': 'process.cpu.system.seconds.total', - 'process_cpu_user_seconds_total': 'process.cpu.user.seconds.total', + 'process_cpu_system_seconds_total': 'process.cpu.system.seconds', + 'process_cpu_user_seconds_total': 'process.cpu.user.seconds', 'process_heap_bytes': 'process.heap.bytes', 'process_max_fds': 'process.max.fds', 'process_open_fds': 'process.open.fds', 'process_resident_memory_bytes': 'process.resident.memory.bytes', - 'process_start_time_seconds': 'process.start.time.seconds', + 'process_start_time_seconds': { + 'name': 'process.uptime.seconds', + 'type': 'time_elapsed', + }, 'process_virtual_memory_bytes': 'process.virtual.memory.bytes', 'queue_job_active_total': 'queue.job.active.total', - 'queue_job_attempts_total': 'queue.job.attempts.total', - 'queue_job_completed_total': 'queue.job.completed.total', + 'queue_job_attempts_total': 'queue.job.attempts', + 'queue_job_completed_total': 'queue.job.completed', 'queue_job_delayed_total': 'queue.job.delayed.total', - 'queue_job_dequeued_total': 'queue.job.dequeued.total', - 'queue_job_enqueued_total': 'queue.job.enqueued.total', - 'queue_job_failed_total': 'queue.job.failed.total', + 'queue_job_dequeued_total': 'queue.job.dequeued', + 'queue_job_enqueued_total': 'queue.job.enqueued', + 'queue_job_failed_total': 'queue.job.failed', 'queue_job_waiting_duration_seconds': 'queue.job.waiting.duration.seconds', 'queue_job_waiting_total': 'queue.job.waiting.total', 'queue_jobs_duration_seconds': 'queue.jobs.duration.seconds', - 'queue_jobs_total': 'queue.jobs.total', + 'queue_jobs_total': 'queue.jobs', 'workflow_executions_active': 'workflow.executions.active', 'workflow_executions_duration_seconds': 'workflow.executions.duration.seconds', - 'workflow_executions_total': 'workflow.executions.total', - 'workflow_failed_total': 'workflow.failed.total', - 'workflow_started_total': 'workflow.started.total', - 'workflow_success_total': 'workflow.success.total', - 'process_cpu_seconds_total': 'process.cpu.seconds.total', + 'workflow_executions_total': 'workflow.executions', + 'workflow_failed_total': 'workflow.failed', + 'workflow_started_total': 'workflow.started', + 'workflow_success_total': 'workflow.success', + 'process_cpu_seconds_total': 'process.cpu.seconds', 'version_info': 'version.info', } diff --git a/n8n/metadata.csv b/n8n/metadata.csv index d6c3992632d93..81ac94fa8b4c0 100644 --- a/n8n/metadata.csv +++ b/n8n/metadata.csv @@ -2,17 +2,17 @@ metric_name,metric_type,interval,unit_name,per_unit_name,description,orientation n8n.active.workflow.count,gauge,,,,Total number of active workflows.,0,n8n,,, n8n.api.request.duration.seconds.count,count,,,,The count of API request duration in seconds,0,n8n,,, n8n.api.request.duration.seconds.sum,count,,,,The sum of API request duration in seconds,0,n8n,,, -n8n.api.requests.total,count,,,,Total API requests,0,n8n,,, -n8n.cache.errors.total,count,,,,Cache errors,0,n8n,,, -n8n.cache.hits.total,count,,,,Cache hits,0,n8n,,, +n8n.api.requests,count,,,,Total API requests,0,n8n,,, +n8n.cache.errors,count,,,,Cache errors,0,n8n,,, +n8n.cache.hits,count,,,,Cache hits,0,n8n,,, n8n.cache.latency.seconds.count,count,,,,The count of cache operation latency in seconds,0,n8n,,, n8n.cache.latency.seconds.sum,count,,,,The sum of cache operation latency in seconds,0,n8n,,, -n8n.cache.misses.total,count,,,,Cache misses,0,n8n,,, -n8n.cache.operations.total,count,,,,Total cache operations,0,n8n,,, +n8n.cache.misses,count,,,,Cache misses,0,n8n,,, +n8n.cache.operations,count,,,,Total cache operations,0,n8n,,, n8n.eventbus.connections.total,gauge,,,,Active event bus backend connections,0,n8n,,, -n8n.eventbus.events.failed.total,count,,,,Total failed event processing,0,n8n,,, -n8n.eventbus.events.processed.total,count,,,,Total processed events,0,n8n,,, -n8n.eventbus.events.total,count,,,,Total events published on the event bus,0,n8n,,, +n8n.eventbus.events.failed,count,,,,Total failed event processing,0,n8n,,, +n8n.eventbus.events.processed,count,,,,Total processed events,0,n8n,,, +n8n.eventbus.events,count,,,,Total events published on the event bus,0,n8n,,, n8n.eventbus.queue.size,gauge,,,,Current event queue size,0,n8n,,, n8n.http.request.duration.seconds.count,count,,,,The count of the http responses duration labeled with: status_code,0,n8n,,, n8n.http.request.duration.seconds.sum,count,,,,The sum of the http responses duration labeled with: status_code,0,n8n,,, @@ -44,9 +44,9 @@ n8n.nodejs.heap.space.size.used.bytes,gauge,,,,Process heap space size used from n8n.nodejs.heap.total.bytes,gauge,,,,Total heap size allocated in bytes,0,n8n,,, n8n.nodejs.heap.used.bytes,gauge,,,,Heap memory used in bytes,0,n8n,,, n8n.nodejs.version.info,gauge,,,,Node.js version info.,0,n8n,,, -n8n.process.cpu.seconds.total,count,,,,Total user and system CPU time spent in seconds.,0,n8n,,, -n8n.process.cpu.system.seconds.total,count,,,,Total system CPU time spent in seconds.,0,n8n,,, -n8n.process.cpu.user.seconds.total,count,,,,Total user CPU time spent in seconds.,0,n8n,,, +n8n.process.cpu.seconds,count,,,,Total user and system CPU time spent in seconds.,0,n8n,,, +n8n.process.cpu.system.seconds,count,,,,Total system CPU time spent in seconds.,0,n8n,,, +n8n.process.cpu.user.seconds,count,,,,Total user CPU time spent in seconds.,0,n8n,,, n8n.process.heap.bytes,gauge,,,,Process heap size in bytes.,0,n8n,,, n8n.process.max.fds,gauge,,,,Maximum number of open file descriptors.,0,n8n,,, n8n.process.open.fds,gauge,,,,Number of open file descriptors.,0,n8n,,, @@ -54,23 +54,23 @@ n8n.process.resident.memory.bytes,gauge,,,,Resident memory size in bytes.,0,n8n, n8n.process.start.time.seconds,gauge,,,,Start time of the process since unix epoch in seconds.,0,n8n,,, n8n.process.virtual.memory.bytes,gauge,,,,Virtual memory size in bytes.,0,n8n,,, n8n.queue.job.active.total,gauge,,,,Number of jobs currently being processed,0,n8n,,, -n8n.queue.job.attempts.total,count,,,,Total number of job attempts,0,n8n,,, -n8n.queue.job.completed.total,count,,,,Number of jobs completed successfully,0,n8n,,, +n8n.queue.job.attempts,count,,,,Total number of job attempts,0,n8n,,, +n8n.queue.job.completed,count,,,,Number of jobs completed successfully,0,n8n,,, n8n.queue.job.delayed.total,gauge,,,,Number of jobs scheduled to run later,0,n8n,,, -n8n.queue.job.dequeued.total,count,,,,Number of jobs dequeued (picked up from queue),0,n8n,,, -n8n.queue.job.enqueued.total,count,,,,Number of jobs added to the queue,0,n8n,,, -n8n.queue.job.failed.total,count,,,,Number of jobs that have failed,0,n8n,,, +n8n.queue.job.dequeued,count,,,,Number of jobs dequeued (picked up from queue),0,n8n,,, +n8n.queue.job.enqueued,count,,,,Number of jobs added to the queue,0,n8n,,, +n8n.queue.job.failed,count,,,,Number of jobs that have failed,0,n8n,,, n8n.queue.job.waiting.duration.seconds.count,count,,,,The count of duration jobs spend waiting before being processed,0,n8n,,, n8n.queue.job.waiting.duration.seconds.sum,count,,,,The sum of duration jobs spend waiting before being processed,0,n8n,,, n8n.queue.job.waiting.total,gauge,,,,Number of jobs currently waiting in the queue,0,n8n,,, n8n.queue.jobs.duration.seconds.count,count,,,,The count of job duration in seconds,0,n8n,,, n8n.queue.jobs.duration.seconds.sum,count,,,,The sum of job duration in seconds,0,n8n,,, -n8n.queue.jobs.total,count,,,,Total number of queue jobs,0,n8n,,, +n8n.queue.jobs,count,,,,Total number of queue jobs,0,n8n,,, n8n.version.info,gauge,,,,n8n version info.,0,n8n,,, n8n.workflow.executions.active,gauge,,,,Number of active workflow executions,0,n8n,,, n8n.workflow.executions.duration.seconds.count,count,,,,The count of workflow execution duration in seconds,0,n8n,,, n8n.workflow.executions.duration.seconds.sum,count,,,,The sum of workflow execution duration in seconds,0,n8n,,, -n8n.workflow.executions.total,count,,,,Total number of workflow executions,0,n8n,,, -n8n.workflow.failed.total,count,,,,Total number of workflows that failed,0,n8n,,, -n8n.workflow.started.total,count,,,,Total number of workflows started,0,n8n,,, -n8n.workflow.success.total,count,,,,Total number of workflows completed successfully,0,n8n,,, +n8n.workflow.executions,count,,,,Total number of workflow executions,0,n8n,,, +n8n.workflow.failed,count,,,,Total number of workflows that failed,0,n8n,,, +n8n.workflow.started,count,,,,Total number of workflows started,0,n8n,,, +n8n.workflow.success,count,,,,Total number of workflows completed successfully,0,n8n,,, diff --git a/n8n/tests/common.py b/n8n/tests/common.py index ba0ab1f83d170..8933429dea5e4 100644 --- a/n8n/tests/common.py +++ b/n8n/tests/common.py @@ -28,17 +28,17 @@ def get_fixture_path(filename): 'n8n.active.workflow.count', 'n8n.api.request.duration.seconds.count', 'n8n.api.request.duration.seconds.sum', - 'n8n.api.requests.total', - 'n8n.cache.errors.total', - 'n8n.cache.hits.total', + 'n8n.api.requests', + 'n8n.cache.errors', + 'n8n.cache.hits', 'n8n.cache.latency.seconds.count', 'n8n.cache.latency.seconds.sum', - 'n8n.cache.misses.total', - 'n8n.cache.operations.total', + 'n8n.cache.misses', + 'n8n.cache.operations', 'n8n.eventbus.connections.total', - 'n8n.eventbus.events.failed.total', - 'n8n.eventbus.events.processed.total', - 'n8n.eventbus.events.total', + 'n8n.eventbus.events.failed', + 'n8n.eventbus.events.processed', + 'n8n.eventbus.events', 'n8n.eventbus.queue.size', 'n8n.http.request.duration.seconds.count', 'n8n.http.request.duration.seconds.sum', @@ -70,8 +70,8 @@ def get_fixture_path(filename): 'n8n.nodejs.heap.total.bytes', 'n8n.nodejs.heap.used.bytes', 'n8n.nodejs.version.info', - 'n8n.process.cpu.system.seconds.total', - 'n8n.process.cpu.user.seconds.total', + 'n8n.process.cpu.system.seconds', + 'n8n.process.cpu.user.seconds', 'n8n.process.heap.bytes', 'n8n.process.max.fds', 'n8n.process.open.fds', @@ -79,25 +79,25 @@ def get_fixture_path(filename): 'n8n.process.start.time.seconds', 'n8n.process.virtual.memory.bytes', 'n8n.queue.job.active.total', - 'n8n.queue.job.attempts.total', - 'n8n.queue.job.completed.total', + 'n8n.queue.job.attempts', + 'n8n.queue.job.completed', 'n8n.queue.job.delayed.total', - 'n8n.queue.job.dequeued.total', - 'n8n.queue.job.enqueued.total', - 'n8n.queue.job.failed.total', + 'n8n.queue.job.dequeued', + 'n8n.queue.job.enqueued', + 'n8n.queue.job.failed', 'n8n.queue.job.waiting.duration.seconds.count', 'n8n.queue.job.waiting.duration.seconds.sum', 'n8n.queue.job.waiting.total', 'n8n.queue.jobs.duration.seconds.count', 'n8n.queue.jobs.duration.seconds.sum', - 'n8n.queue.jobs.total', + 'n8n.queue.jobs', 'n8n.workflow.executions.active', 'n8n.workflow.executions.duration.seconds.count', 'n8n.workflow.executions.duration.seconds.sum', - 'n8n.workflow.executions.total', - 'n8n.workflow.failed.total', - 'n8n.workflow.started.total', - 'n8n.workflow.success.total', - 'n8n.process.cpu.seconds.total', + 'n8n.workflow.executions', + 'n8n.workflow.failed', + 'n8n.workflow.started', + 'n8n.workflow.success', + 'n8n.process.cpu.seconds', 'n8n.version.info', ] From 15120e9625b52f3220082cc17b81b7be86f760c0 Mon Sep 17 00:00:00 2001 From: HadhemiDD Date: Tue, 18 Nov 2025 13:40:56 +0100 Subject: [PATCH 18/20] lint --- n8n/datadog_checks/n8n/metrics.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/n8n/datadog_checks/n8n/metrics.py b/n8n/datadog_checks/n8n/metrics.py index b530b74f6b781..ea24843f6983d 100644 --- a/n8n/datadog_checks/n8n/metrics.py +++ b/n8n/datadog_checks/n8n/metrics.py @@ -52,7 +52,7 @@ 'process_max_fds': 'process.max.fds', 'process_open_fds': 'process.open.fds', 'process_resident_memory_bytes': 'process.resident.memory.bytes', - 'process_start_time_seconds': { + 'process_start_time_seconds': { 'name': 'process.uptime.seconds', 'type': 'time_elapsed', }, From a308cc4afdb408ca22bd3d429d35e59d298b025c Mon Sep 17 00:00:00 2001 From: HadhemiDD Date: Tue, 18 Nov 2025 13:46:19 +0100 Subject: [PATCH 19/20] lint --- n8n/metadata.csv | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/n8n/metadata.csv b/n8n/metadata.csv index 81ac94fa8b4c0..32370b8fb63a6 100644 --- a/n8n/metadata.csv +++ b/n8n/metadata.csv @@ -10,9 +10,9 @@ n8n.cache.latency.seconds.sum,count,,,,The sum of cache operation latency in sec n8n.cache.misses,count,,,,Cache misses,0,n8n,,, n8n.cache.operations,count,,,,Total cache operations,0,n8n,,, n8n.eventbus.connections.total,gauge,,,,Active event bus backend connections,0,n8n,,, +n8n.eventbus.events,count,,,,Total events published on the event bus,0,n8n,,, n8n.eventbus.events.failed,count,,,,Total failed event processing,0,n8n,,, n8n.eventbus.events.processed,count,,,,Total processed events,0,n8n,,, -n8n.eventbus.events,count,,,,Total events published on the event bus,0,n8n,,, n8n.eventbus.queue.size,gauge,,,,Current event queue size,0,n8n,,, n8n.http.request.duration.seconds.count,count,,,,The count of the http responses duration labeled with: status_code,0,n8n,,, n8n.http.request.duration.seconds.sum,count,,,,The sum of the http responses duration labeled with: status_code,0,n8n,,, @@ -63,14 +63,14 @@ n8n.queue.job.failed,count,,,,Number of jobs that have failed,0,n8n,,, n8n.queue.job.waiting.duration.seconds.count,count,,,,The count of duration jobs spend waiting before being processed,0,n8n,,, n8n.queue.job.waiting.duration.seconds.sum,count,,,,The sum of duration jobs spend waiting before being processed,0,n8n,,, n8n.queue.job.waiting.total,gauge,,,,Number of jobs currently waiting in the queue,0,n8n,,, +n8n.queue.jobs,count,,,,Total number of queue jobs,0,n8n,,, n8n.queue.jobs.duration.seconds.count,count,,,,The count of job duration in seconds,0,n8n,,, n8n.queue.jobs.duration.seconds.sum,count,,,,The sum of job duration in seconds,0,n8n,,, -n8n.queue.jobs,count,,,,Total number of queue jobs,0,n8n,,, n8n.version.info,gauge,,,,n8n version info.,0,n8n,,, +n8n.workflow.executions,count,,,,Total number of workflow executions,0,n8n,,, n8n.workflow.executions.active,gauge,,,,Number of active workflow executions,0,n8n,,, n8n.workflow.executions.duration.seconds.count,count,,,,The count of workflow execution duration in seconds,0,n8n,,, n8n.workflow.executions.duration.seconds.sum,count,,,,The sum of workflow execution duration in seconds,0,n8n,,, -n8n.workflow.executions,count,,,,Total number of workflow executions,0,n8n,,, n8n.workflow.failed,count,,,,Total number of workflows that failed,0,n8n,,, n8n.workflow.started,count,,,,Total number of workflows started,0,n8n,,, n8n.workflow.success,count,,,,Total number of workflows completed successfully,0,n8n,,, From 430fc2ab304007268da2b1db7ff56d5ddc5b56b1 Mon Sep 17 00:00:00 2001 From: HadhemiDD Date: Wed, 19 Nov 2025 12:22:09 +0100 Subject: [PATCH 20/20] count metrics --- n8n/datadog_checks/n8n/metrics.py | 42 +++++++++++++++---------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/n8n/datadog_checks/n8n/metrics.py b/n8n/datadog_checks/n8n/metrics.py index ea24843f6983d..222b16bec9e1d 100644 --- a/n8n/datadog_checks/n8n/metrics.py +++ b/n8n/datadog_checks/n8n/metrics.py @@ -7,16 +7,16 @@ METRIC_MAP = { 'active_workflow_count': 'active.workflow.count', 'api_request_duration_seconds': 'api.request.duration.seconds', - 'api_requests_total': 'api.requests', - 'cache_errors_total': 'cache.errors', - 'cache_hits_total': 'cache.hits', + 'api_requests': 'api.requests', + 'cache_errors': 'cache.errors', + 'cache_hits': 'cache.hits', 'cache_latency_seconds': 'cache.latency.seconds', - 'cache_misses_total': 'cache.misses', - 'cache_operations_total': 'cache.operations', + 'cache_misses': 'cache.misses', + 'cache_operations': 'cache.operations', 'eventbus_connections_total': 'eventbus.connections.total', - 'eventbus_events_failed_total': 'eventbus.events.failed', - 'eventbus_events_processed_total': 'eventbus.events.processed', - 'eventbus_events_total': 'eventbus.events', + 'eventbus_events_failed': 'eventbus.events.failed', + 'eventbus_events_processed': 'eventbus.events.processed', + 'eventbus_events': 'eventbus.events', 'eventbus_queue_size': 'eventbus.queue.size', 'http_request_duration_seconds': 'http.request.duration.seconds', 'instance_role_leader': 'instance.role.leader', @@ -46,8 +46,8 @@ 'nodejs_heap_total_bytes': 'nodejs.heap.total.bytes', 'nodejs_heap_used_bytes': 'nodejs.heap.used.bytes', 'nodejs_version_info': 'nodejs.version.info', - 'process_cpu_system_seconds_total': 'process.cpu.system.seconds', - 'process_cpu_user_seconds_total': 'process.cpu.user.seconds', + 'process_cpu_system_seconds': 'process.cpu.system.seconds', + 'process_cpu_user_seconds': 'process.cpu.user.seconds', 'process_heap_bytes': 'process.heap.bytes', 'process_max_fds': 'process.max.fds', 'process_open_fds': 'process.open.fds', @@ -58,22 +58,22 @@ }, 'process_virtual_memory_bytes': 'process.virtual.memory.bytes', 'queue_job_active_total': 'queue.job.active.total', - 'queue_job_attempts_total': 'queue.job.attempts', - 'queue_job_completed_total': 'queue.job.completed', + 'queue_job_attempts': 'queue.job.attempts', + 'queue_job_completed': 'queue.job.completed', 'queue_job_delayed_total': 'queue.job.delayed.total', - 'queue_job_dequeued_total': 'queue.job.dequeued', - 'queue_job_enqueued_total': 'queue.job.enqueued', - 'queue_job_failed_total': 'queue.job.failed', + 'queue_job_dequeued': 'queue.job.dequeued', + 'queue_job_enqueued': 'queue.job.enqueued', + 'queue_job_failed': 'queue.job.failed', 'queue_job_waiting_duration_seconds': 'queue.job.waiting.duration.seconds', 'queue_job_waiting_total': 'queue.job.waiting.total', 'queue_jobs_duration_seconds': 'queue.jobs.duration.seconds', - 'queue_jobs_total': 'queue.jobs', + 'queue_jobs': 'queue.jobs', 'workflow_executions_active': 'workflow.executions.active', 'workflow_executions_duration_seconds': 'workflow.executions.duration.seconds', - 'workflow_executions_total': 'workflow.executions', - 'workflow_failed_total': 'workflow.failed', - 'workflow_started_total': 'workflow.started', - 'workflow_success_total': 'workflow.success', - 'process_cpu_seconds_total': 'process.cpu.seconds', + 'workflow_executions': 'workflow.executions', + 'workflow_failed': 'workflow.failed', + 'workflow_started': 'workflow.started', + 'workflow_success': 'workflow.success', + 'process_cpu_seconds': 'process.cpu.seconds', 'version_info': 'version.info', }