<commit_before># (C) Datadog, Inc. 2020-present
# All rights reserved
# Licensed under Simplified BSD License (see LICENSE)
"""
Helpers for parsing the `metrics` section of a config file.
"""
from logging import Logger
from typing import Dict, List, NamedTuple, Optional, Sequence, TypedDict, Union, cast

import six

from datadog_checks.base import ConfigurationError

from ..models import OID
from ..pysnmp_types import ObjectIdentity
from ..resolver import OIDResolver
from .metric_tags import MetricTag, parse_metric_tag
from .metrics_types import (
    ColumnTableMetricTag,
    IndexTableMetricTag,
    Metric,
    OIDMetric,
    Symbol,
    SymbolMetric,
    TableMetric,
    TableMetricTag,
)
from .parsed_metrics import ParsedMetric, ParsedMetricTag, ParsedSymbolMetric, ParsedTableMetric

ParseMetricsResult = TypedDict(
    'ParseMetricsResult',
    {'oids': List[OID], 'next_oids': List[OID], 'bulk_oids': List[OID], 'parsed_metrics': List[ParsedMetric]},
)


def parse_metrics(metrics, resolver, logger, bulk_threshold=0):
    # type: (List[Metric], OIDResolver, Optional[Logger], int) -> ParseMetricsResult
    """
    Parse the `metrics` section of a config file, and return OIDs to fetch and metrics to submit.
    """
    oids = []
    next_oids = []
    bulk_oids = []
    parsed_metrics = []  # type: List[ParsedMetric]

    for metric in metrics:
        result = _parse_metric(metric, logger)

        for oid in result.oids_to_fetch:
            oids.append(oid)

        for name, oid in result.oids_to_resolve.items():
            resolver.register(oid, name)

        for index_mapping in result.index_mappings:
            resolver.register_index(tag=index_mapping.tag, index=index_mapping.index, mapping=index_mapping.mapping)

        for batch in result.table_batches.values():
            should_query_in_bulk = bulk_threshold and len(batch.oids) > bulk_threshold
            if should_query_in_bulk:
                bulk_oids.append(batch.table_oid)
            else:
                # NOTE: we should issue GETNEXT commands for these OIDs, because GET commands on table column OIDs
                # never succeed.
                # This is because data for a given entry in the table is available at the column OIDs **suffixed
                # with the table entry index**, i.e. `<COLUMN_OID>.<ENTRY_INDEX>`. (There's nothing at `<COLUMN_OID>`.)
                next_oids.extend(batch.oids)

        parsed_metrics.extend(result.parsed_metrics)

    return {'oids': oids, 'next_oids': next_oids, 'bulk_oids': bulk_oids, 'parsed_metrics': parsed_metrics}


# Helpers below.
# NOTE: most type definitions below are for containers of intermediary parsed data - not related to config file format.

IndexMapping = NamedTuple('IndexMapping', [('tag', str), ('index', int), ('mapping', dict)])
TableBatchKey = NamedTuple('TableBatchKey', [('mib', str), ('table', str)])
TableBatch = NamedTuple('TableBatch', [('table_oid', OID), ('oids', List[OID])])
TableBatches = Dict[TableBatchKey, TableBatch]

MetricParseResult = NamedTuple(
    'MetricParseResult',
    [
        ('oids_to_fetch', List[OID]),
        ('oids_to_resolve', Dict[str, OID]),
        ('index_mappings', List[IndexMapping]),
        ('table_batches', TableBatches),
        ('parsed_metrics', Sequence[ParsedMetric]),
    ],
)


def _parse_metric(metric, logger):
    # type: (Metric, Optional[Logger]) -> MetricParseResult
    """
    Parse a single metric in the `metrics` section of a config file.

    Can either be:

    * An OID metric:

    ```
    metrics:
      - OID: 1.3.6.1.2.1.2.2.1.14
        name: ifInErrors
    ```

    * A symbol metric:

    ```
    metrics:
      - MIB: IF-MIB
        symbol: ifInErrors
        # OR:
        symbol:
          OID: 1.3.6.1.2.1.2.2.1.14
          name: ifInErrors
    ```

    * A table metric (see parsing for table metrics for all possible options):

    ```
    metrics:
      - MIB: IF-MIB
        table: ifTable
        symbols:
          - OID: 1.3.6.1.2.1.2.2.1.14
            name: ifInErrors
    ```
    """

    if 'OID' in metric:
        metric = cast(OIDMetric, metric)
        return _parse_oid_metric(metric)

    if 'MIB' not in metric:
        raise ConfigurationError('Unsupported metric in config file: {}'.format(metric))

    if 'symbol' in metric:
        metric = cast(SymbolMetric, metric)
        return _parse_symbol_metric(metric)

    if 'table' in metric:
        if 'symbols' not in metric:
            raise ConfigurationError('When specifying a table, you must specify a list of symbols')
        metric = cast(TableMetric, metric)
        return _parse_table_metric(metric, logger)

    raise ConfigurationError('When specifying a MIB, you must specify either a table or a symbol')


def _parse_oid_metric(metric):
    # type: (OIDMetric) -> MetricParseResult
    """
    Parse a fully resolved OID/name metric.

    Example:

    ```
    metrics:
      - OID: 1.3.6.1.2.1.2.1
        name: ifNumber
    ```
    """<commit_msg>Add deprecation notice for metric[].name syntax (#8070)<commit_after># (C) Datadog, Inc. 2020-present
# All rights reserved
# Licensed under Simplified BSD License (see LICENSE)
"""
Helpers for parsing the `metrics` section of a config file.
"""
from logging import Logger
from typing import Dict, List, NamedTuple, Optional, Sequence, TypedDict, Union, cast

import six

from datadog_checks.base import ConfigurationError

from ..models import OID
from ..pysnmp_types import ObjectIdentity
from ..resolver import OIDResolver
from .metric_tags import MetricTag, parse_metric_tag
from .metrics_types import (
    ColumnTableMetricTag,
    IndexTableMetricTag,
    Metric,
    OIDMetric,
    Symbol,
    SymbolMetric,
    TableMetric,
    TableMetricTag,
)
from .parsed_metrics import ParsedMetric, ParsedMetricTag, ParsedSymbolMetric, ParsedTableMetric

ParseMetricsResult = TypedDict(
    'ParseMetricsResult',
    {'oids': List[OID], 'next_oids': List[OID], 'bulk_oids': List[OID], 'parsed_metrics': List[ParsedMetric]},
)


def parse_metrics(metrics, resolver, logger, bulk_threshold=0):
    # type: (List[Metric], OIDResolver, Optional[Logger], int) -> ParseMetricsResult
    """
    Parse the `metrics` section of a config file, and return OIDs to fetch and metrics to submit.
    """
    oids = []
    next_oids = []
    bulk_oids = []
    parsed_metrics = []  # type: List[ParsedMetric]

    for metric in metrics:
        result = _parse_metric(metric, logger)

        for oid in result.oids_to_fetch:
            oids.append(oid)

        for name, oid in result.oids_to_resolve.items():
            resolver.register(oid, name)

        for index_mapping in result.index_mappings:
            resolver.register_index(tag=index_mapping.tag, index=index_mapping.index, mapping=index_mapping.mapping)

        for batch in result.table_batches.values():
            should_query_in_bulk = bulk_threshold and len(batch.oids) > bulk_threshold
            if should_query_in_bulk:
                bulk_oids.append(batch.table_oid)
            else:
                # NOTE: we should issue GETNEXT commands for these OIDs, because GET commands on table column OIDs
                # never succeed.
                # This is because data for a given entry in the table is available at the column OIDs **suffixed
                # with the table entry index**, i.e. `<COLUMN_OID>.<ENTRY_INDEX>`. (There's nothing at `<COLUMN_OID>`.)
                next_oids.extend(batch.oids)

        parsed_metrics.extend(result.parsed_metrics)

    return {'oids': oids, 'next_oids': next_oids, 'bulk_oids': bulk_oids, 'parsed_metrics': parsed_metrics}


# Helpers below.
# NOTE: most type definitions below are for containers of intermediary parsed data - not related to config file format.

IndexMapping = NamedTuple('IndexMapping', [('tag', str), ('index', int), ('mapping', dict)])
TableBatchKey = NamedTuple('TableBatchKey', [('mib', str), ('table', str)])
TableBatch = NamedTuple('TableBatch', [('table_oid', OID), ('oids', List[OID])])
TableBatches = Dict[TableBatchKey, TableBatch]

MetricParseResult = NamedTuple(
    'MetricParseResult',
    [
        ('oids_to_fetch', List[OID]),
        ('oids_to_resolve', Dict[str, OID]),
        ('index_mappings', List[IndexMapping]),
        ('table_batches', TableBatches),
        ('parsed_metrics', Sequence[ParsedMetric]),
    ],
)


def _parse_metric(metric, logger):
    # type: (Metric, Optional[Logger]) -> MetricParseResult
    """
    Parse a single metric in the `metrics` section of a config file.

    Can either be:

    * An OID metric:

    ```
    metrics:
      - OID: 1.3.6.1.2.1.2.2.1.14
        name: ifInErrors
    ```

    * A symbol metric:

    ```
    metrics:
      - MIB: IF-MIB
        symbol: ifInErrors
        # OR:
        symbol:
          OID: 1.3.6.1.2.1.2.2.1.14
          name: ifInErrors
    ```

    * A table metric (see parsing for table metrics for all possible options):

    ```
    metrics:
      - MIB: IF-MIB
        table: ifTable
        symbols:
          - OID: 1.3.6.1.2.1.2.2.1.14
            name: ifInErrors
    ```
    """

    if 'OID' in metric:
        metric = cast(OIDMetric, metric)
        return _parse_oid_metric(metric)

    if 'MIB' not in metric:
        raise ConfigurationError('Unsupported metric in config file: {}'.format(metric))

    if 'symbol' in metric:
        metric = cast(SymbolMetric, metric)
        return _parse_symbol_metric(metric)

    if 'table' in metric:
        if 'symbols' not in metric:
            raise ConfigurationError('When specifying a table, you must specify a list of symbols')
        metric = cast(TableMetric, metric)
        return _parse_table_metric(metric, logger)

    raise ConfigurationError('When specifying a MIB, you must specify either a table or a symbol')


def _parse_oid_metric(metric):
    # type: (OIDMetric) -> MetricParseResult
    """
    Parse a fully resolved OID/name metric.

    Note: This `OID/name` syntax is deprecated in favour of `symbol` syntax.

    Example:

    ```
    metrics:
      - OID: 1.3.6.1.2.1.2.1
        name: ifNumber
    ```
    """
{'fraction_non_alphanumeric': 0.10899585717103966, 'fraction_numerical': 0.009469323337936477, 'mean_word_length': 2.8846743295019155, 'pattern_counts': {'":': 0, '<': 9, '<?xml version=': 0, '>': 17, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 16, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3509911', 'n_tokens_mistral': 3152, 'n_tokens_neox': 3033, 'n_words': 885}