|
19 | 19 | ENGINE_EDITION_AZURE_MANAGED_INSTANCE, |
20 | 20 | ENGINE_EDITION_SQL_DATABASE, |
21 | 21 | ENGINE_EDITION_STANDARD, |
| 22 | + PERF_COUNTER_BULK_COUNT, |
22 | 23 | STATIC_INFO_ENGINE_EDITION, |
23 | 24 | STATIC_INFO_FULL_SERVERNAME, |
24 | 25 | STATIC_INFO_INSTANCENAME, |
|
27 | 28 | STATIC_INFO_SERVERNAME, |
28 | 29 | STATIC_INFO_VERSION, |
29 | 30 | ) |
30 | | -from datadog_checks.sqlserver.metrics import SqlFractionMetric |
| 31 | +from datadog_checks.sqlserver.metrics import SqlFractionMetric, SqlSimpleMetric |
31 | 32 | from datadog_checks.sqlserver.schemas import KEY_PREFIX, KEY_PREFIX_PRE_2017, SQLServerSchemaCollector |
32 | 33 | from datadog_checks.sqlserver.sqlserver import SQLConnectionError |
33 | 34 | from datadog_checks.sqlserver.utils import ( |
@@ -846,6 +847,138 @@ def test_SqlFractionMetric_group_by_instance(caplog): |
846 | 847 | ) |
847 | 848 |
|
848 | 849 |
|
| 850 | +# sys.dm_os_performance_counters declares the name columns as nchar(128), so every value comes back |
| 851 | +# blank-padded. Pad the fixtures the same way to exercise the stripping. |
| 852 | +def _padded_counter_row(counter_name, instance_name, object_name, cntr_value): |
| 853 | + return (counter_name.ljust(128), instance_name.ljust(128), object_name.ljust(128), cntr_value) |
| 854 | + |
| 855 | + |
| 856 | +SIMPLE_METRIC_ROWS = [ |
| 857 | + _padded_counter_row('Processes blocked', '', 'SQLServer:General Statistics', 1), |
| 858 | + _padded_counter_row('Cache Pages', '_Total', 'SQLServer:Plan Cache', 10), |
| 859 | + _padded_counter_row('Cache Pages', 'SQL Plans', 'SQLServer:Plan Cache', 11), |
| 860 | + _padded_counter_row('Cache Pages', 'Object Plans', 'SQLServer:Plan Cache', 12), |
| 861 | + _padded_counter_row('Transaction Delay', 'tenant_a', 'SQLServer:Database Replica', 20), |
| 862 | + _padded_counter_row('Transaction Delay', 'tenant_a', 'SQLServer:Databases', 21), |
| 863 | + _padded_counter_row('Log Flushes/sec', '_Total', 'SQLServer:Databases', 30), |
| 864 | + _padded_counter_row('Log Flushes/sec', 'tenant_a', 'SQLServer:Databases', 31), |
| 865 | + _padded_counter_row('Log Flushes/sec', 'physical_a', 'SQLServer:Databases', 32), |
| 866 | +] |
| 867 | + |
| 868 | +SIMPLE_METRIC_TAGS = ['optional:tag1', 'dd.internal.resource:database_instance:stubbed.hostname'] |
| 869 | + |
| 870 | + |
| 871 | +@pytest.mark.parametrize( |
| 872 | + 'cfg_overrides, expected', |
| 873 | + [ |
| 874 | + pytest.param( |
| 875 | + {'counter_name': 'Processes blocked', 'instance_name': ''}, |
| 876 | + [(1, SIMPLE_METRIC_TAGS)], |
| 877 | + id='instance level counter', |
| 878 | + ), |
| 879 | + pytest.param( |
| 880 | + {'counter_name': 'Log Flushes/sec', 'instance_name': '_Total'}, |
| 881 | + [(30, SIMPLE_METRIC_TAGS)], |
| 882 | + id='total instance of a per database counter', |
| 883 | + ), |
| 884 | + pytest.param( |
| 885 | + {'counter_name': 'Log Flushes/sec', 'instance_name': 'tenant_a'}, |
| 886 | + [(31, SIMPLE_METRIC_TAGS)], |
| 887 | + id='per database counter matched by instance_name', |
| 888 | + ), |
| 889 | + pytest.param( |
| 890 | + {'counter_name': 'Log Flushes/sec', 'instance_name': 'tenant_b', 'physical_db_name': 'physical_a'}, |
| 891 | + [(32, SIMPLE_METRIC_TAGS)], |
| 892 | + id='per database counter matched by physical_db_name', |
| 893 | + ), |
| 894 | + pytest.param( |
| 895 | + { |
| 896 | + 'counter_name': 'Transaction Delay', |
| 897 | + 'instance_name': 'tenant_a', |
| 898 | + 'object_name': 'SQLServer:Databases', |
| 899 | + }, |
| 900 | + [(21, SIMPLE_METRIC_TAGS)], |
| 901 | + id='object_name selects among counters sharing a name', |
| 902 | + ), |
| 903 | + pytest.param( |
| 904 | + {'counter_name': 'Transaction Delay', 'instance_name': 'tenant_a', 'object_name': 'SQLServer:Missing'}, |
| 905 | + [], |
| 906 | + id='object_name matching nothing submits nothing', |
| 907 | + ), |
| 908 | + pytest.param( |
| 909 | + {'counter_name': 'Cache Pages', 'instance_name': 'ALL', 'tag_by': 'plan_type'}, |
| 910 | + [(11, SIMPLE_METRIC_TAGS + ['plan_type:SQL Plans']), (12, SIMPLE_METRIC_TAGS + ['plan_type:Object Plans'])], |
| 911 | + id='ALL instances tags each instance and skips _Total', |
| 912 | + ), |
| 913 | + pytest.param( |
| 914 | + { |
| 915 | + 'counter_name': 'Transaction Delay', |
| 916 | + 'instance_name': 'ALL', |
| 917 | + 'tag_by': 'db', |
| 918 | + 'object_name': 'SQLServer:Missing', |
| 919 | + }, |
| 920 | + [(20, SIMPLE_METRIC_TAGS + ['db:tenant_a']), (21, SIMPLE_METRIC_TAGS + ['db:tenant_a'])], |
| 921 | + id='ALL instances ignores object_name', |
| 922 | + ), |
| 923 | + pytest.param( |
| 924 | + {'counter_name': 'Not Collected', 'instance_name': ''}, |
| 925 | + [], |
| 926 | + id='counter absent from the result set submits nothing', |
| 927 | + ), |
| 928 | + pytest.param( |
| 929 | + {'counter_name': 'Log Flushes/sec', 'instance_name': 'tenant_missing'}, |
| 930 | + [], |
| 931 | + id='instance absent from the result set submits nothing', |
| 932 | + ), |
| 933 | + ], |
| 934 | +) |
| 935 | +def test_SqlSimpleMetric_fetch_metric(cfg_overrides, expected): |
| 936 | + mock_cursor = mock.MagicMock() |
| 937 | + mock_cursor.fetchall.return_value = SIMPLE_METRIC_ROWS |
| 938 | + mock_cursor.description = [('counter_name',), ('instance_name',), ('object_name',), ('cntr_value',)] |
| 939 | + |
| 940 | + report_function = mock.MagicMock() |
| 941 | + cfg_instance = { |
| 942 | + 'name': 'sqlserver.test.metric', |
| 943 | + 'tags': list(SIMPLE_METRIC_TAGS), |
| 944 | + 'hostname': 'stubbed.hostname', |
| 945 | + } |
| 946 | + cfg_instance.update(cfg_overrides) |
| 947 | + metric_obj = SqlSimpleMetric( |
| 948 | + cfg_instance=cfg_instance, |
| 949 | + base_name=None, |
| 950 | + report_function=report_function, |
| 951 | + column=None, |
| 952 | + logger=mock.MagicMock(), |
| 953 | + ) |
| 954 | + |
| 955 | + results, columns = SqlSimpleMetric.fetch_all_values(mock_cursor, [cfg_instance['counter_name']], mock.MagicMock()) |
| 956 | + metric_obj.fetch_metric(results, columns) |
| 957 | + |
| 958 | + assert report_function.call_args_list == [ |
| 959 | + mock.call('sqlserver.test.metric', value, raw=True, hostname='stubbed.hostname', tags=tags) |
| 960 | + for value, tags in expected |
| 961 | + ] |
| 962 | + |
| 963 | + |
| 964 | +def test_get_sql_counter_type_caches_counters_without_a_base(instance_docker): |
| 965 | + """A counter type is immutable for the lifetime of the server, so it should only be queried once. |
| 966 | +
|
| 967 | + Without caching, rebuilding the metric list queries the type of every counter for every |
| 968 | + autodiscovered database, which is hundreds of round trips on an instance with many databases. |
| 969 | + """ |
| 970 | + check = SQLServer(CHECK_NAME, {}, [instance_docker]) |
| 971 | + mock_cursor = mock.MagicMock() |
| 972 | + mock_cursor.fetchone.return_value = (PERF_COUNTER_BULK_COUNT,) |
| 973 | + check._connection = mock.MagicMock() |
| 974 | + check._connection.get_managed_cursor.return_value.__enter__.return_value = mock_cursor |
| 975 | + |
| 976 | + for _ in range(3): |
| 977 | + assert check.get_sql_counter_type('Transactions/sec') == (PERF_COUNTER_BULK_COUNT, None) |
| 978 | + |
| 979 | + assert mock_cursor.execute.call_count == 1 |
| 980 | + |
| 981 | + |
849 | 982 | def _mock_database_list(): |
850 | 983 | Row = namedtuple('Row', 'name') |
851 | 984 | fetchall_results = [ |
|
0 commit comments