Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for organization level metrics #12375

Merged
merged 38 commits into from
Jun 23, 2022
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
8950b1c
Add snowflake queries
sarah-witt Apr 26, 2022
70228b7
Update and test currency usage queries
sarah-witt May 4, 2022
f11c83a
Add more queries
sarah-witt May 6, 2022
2b74656
Complete all new queries
sarah-witt May 27, 2022
df60e3a
Clean up queries, add another test
sarah-witt May 27, 2022
045eb50
Add more tests
sarah-witt May 31, 2022
6a50a6b
Add more tests, split up test files
sarah-witt Jun 7, 2022
ebff0e4
Add organization docs, start metadata
sarah-witt Jun 8, 2022
d67a056
Remove billing prefix
sarah-witt Jun 8, 2022
c91217b
Update defaults
sarah-witt Jun 9, 2022
0e2db1a
Fix query syntax
sarah-witt Jun 9, 2022
ac17fc3
Add more queries
sarah-witt Jun 14, 2022
c80163f
Allow org usage in e2e
sarah-witt Jun 14, 2022
8901b32
Clean up queries, update metadata
sarah-witt Jun 16, 2022
c700573
Update readme
sarah-witt Jun 16, 2022
1f33645
fix readme
sarah-witt Jun 16, 2022
c2e4f0c
Add metric groups tests
sarah-witt Jun 16, 2022
6c92733
Add currency tag
sarah-witt Jun 16, 2022
ab62aab
Update snowflake/README.md
sarah-witt Jun 21, 2022
c53fec4
Update readme
sarah-witt Jun 21, 2022
08f89c0
Merge branch 'sarah/snowflake-org-usage' of github.com:DataDog/integr…
sarah-witt Jun 21, 2022
ce2b10c
Update snowflake/assets/configuration/spec.yaml
sarah-witt Jun 22, 2022
2380fa8
Update readme
sarah-witt Jun 22, 2022
b31d167
Merge branch 'sarah/snowflake-org-usage' of github.com:DataDog/integr…
sarah-witt Jun 22, 2022
842d36c
Sync config
sarah-witt Jun 22, 2022
c43f283
Update grammar
sarah-witt Jun 22, 2022
38c1608
Fix logging and tests
sarah-witt Jun 22, 2022
6ad2aec
Add organization e2e test
sarah-witt Jun 22, 2022
d2e7cfc
Assert tags
sarah-witt Jun 22, 2022
da740de
Add credit prefix, remove count assertion
sarah-witt Jun 22, 2022
6d56335
Merge branch 'master' into sarah/snowflake-org-usage
sarah-witt Jun 22, 2022
7e4e927
Also look for contract number as org usage
sarah-witt Jun 22, 2022
b218c39
Fix syntax
sarah-witt Jun 22, 2022
f1e846f
Better handle multiple schemas in mock library
sarah-witt Jun 23, 2022
91d3a99
Update snowflake/README.md
sarah-witt Jun 23, 2022
14d9b64
Fix style
sarah-witt Jun 23, 2022
82964b3
Merge branch 'sarah/snowflake-org-usage' of github.com:DataDog/integr…
sarah-witt Jun 23, 2022
dcabb07
Remove comment
sarah-witt Jun 23, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 34 additions & 1 deletion snowflake/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ datadog-agent integration install datadog-snowflake==2.0.1

1. Create a Datadog specific role and user to monitor Snowflake. In Snowflake, run the following to create a custom role with access to the ACCOUNT_USAGE schema.

Note: By default, this integration monitors the `SNOWFLAKE` database and `ACCOUNT_USAGE` schema.
Note: By default, this integration monitors the `SNOWFLAKE` database and `ACCOUNT_USAGE` schema. See "Collecting Organization Data" for information on how to monitor the `ORGANIZATION_USAGE` schema.
This database is available by default and only viewable by users in the `ACCOUNTADMIN` role or [any role granted by the ACCOUNTADMIN][4].


Expand Down Expand Up @@ -117,6 +117,39 @@ datadog-agent integration install datadog-snowflake==2.0.1

3. [Restart the Agent][6].

#### Collecting Organization Data

By default, this integration monitors the `ACCOUNT_USAGE` schema, but it can be set to monitor organization-level metrics instead.

To collect organization metrics, you can create configure the integration by changing the schema field to `ORGANIZATION_USAGE` and increasing the `min_collection_interval` to 43200 to reduce the number of queries to Snowflake, as most organization queries have a latency of up to 24 hours.

Note: To monitor organization metrics, your `user` must have the `ORGADMIN` role.

```yaml
- schema: ORGANIZATION_USAGE
min_collection_interval: 43200
```


Additionally, you can monitor both account and organization metrics at the same time:

```yaml
instances:
- account: example-inc
username: DATADOG_ORG_ADMIN
password: '<PASSWORD>'
role: SYSADMIN
database: ORGANIZATION_USAGE
min_collection_interval: 3600

- account: example-inc
username: DATADOG_ACCOUNT_ADMIN
password: '<PASSWORD>'
role: DATADOG_ADMIN
database: ACCOUNT_USAGE
min_collection_interval: 43200
```
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't it be schema rather than database here? It also looks like the min_collection_interval are reversed, I would expect the instance with ORGANIZATION_USAGE to have it set to 43200 as per the section above.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yes that's right, I was copying the other example 🤦‍♀️


#### Collecting data for multiple environments

If you want to collect data for multiple Snowflake environments, add each environment as an instance in your `snowflake.d/conf.yaml` file. For example, if you needed to collect data for two users named `DATADOG_SYSADMIN` and `DATADOG_USER`:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and add an example at the bottom of this section to collect both account and organization metrics

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, I can do that!

Expand Down
17 changes: 15 additions & 2 deletions snowflake/assets/configuration/spec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,10 @@ files:
type: string
example: SNOWFLAKE
- name: schema
description: Name of the default schema to use for the database.
description: |
Name of the default schema to use for the database.

To collect organization level metrics, set this to ORGANIZATION_USAGE
sarah-witt marked this conversation as resolved.
Show resolved Hide resolved
value:
type: string
example: ACCOUNT_USAGE
Expand Down Expand Up @@ -144,7 +147,7 @@ files:
description: |
List Snowflake metric groups to collect. Metric groups are determined by the metric prefixes.

The available metric groups are:
When querying the ACCOUNT_USAGE schema, the available metric groups are:

- snowflake.query (enabled by default)
- snowflake.billing (enabled by default)
Expand All @@ -156,6 +159,16 @@ files:
- snowflake.auto_recluster
- snowflake.pipe
- snowflake.replication

When querying the ORGANIZATION_USAGE schema, the available metric groups are:
- snowflake.organization.warehouse (enabled by default)
- snowflake.organization.currency (enabled by default)
- snowflake.organization.credit
- snowflake.organization.storage (enabled by default)
- snowflake.organization.contracts
- snowflake.organization.balance
- snowflake.organization.rate
- snowflake.organization.data_transfer
value:
type: array
items:
Expand Down
33 changes: 26 additions & 7 deletions snowflake/datadog_checks/snowflake/check.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from . import queries
from .config import Config

METRIC_GROUPS = {
ACCOUNT_USAGE_METRIC_GROUPS = {
'snowflake.query': [queries.WarehouseLoad, queries.QueryHistory],
'snowflake.billing': [queries.CreditUsage, queries.WarehouseCreditUsage],
'snowflake.storage': [queries.StorageUsageMetrics],
Expand All @@ -26,6 +26,17 @@
'snowflake.replication': [queries.ReplicationUsage],
}

ORGANIZATION_USAGE_METRIC_GROUPS = {
'snowflake.organization.contracts': [queries.OrgContractItems],
'snowflake.organization.credit': [queries.OrgCreditUsage],
'snowflake.organization.currency': [queries.OrgCurrencyUsage],
'snowflake.organization.warehouse': [queries.OrgWarehouseCreditUsage],
'snowflake.organization.storage': [queries.OrgStorageDaily],
'snowflake.organization.balance': [queries.OrgBalance],
'snowflake.organization.rate': [queries.OrgRateSheet],
'snowflake.organization.data_transfer': [queries.OrgDataTransfer],
}


class SnowflakeCheck(AgentCheck):
"""
Expand Down Expand Up @@ -60,24 +71,32 @@ def __init__(self, *args, **kwargs):
'Snowflake `role` is set as `ACCOUNTADMIN` which should be used cautiously, '
'refer to docs about custom roles.'
)

metric_groups = (
ORGANIZATION_USAGE_METRIC_GROUPS
if (self._config.schema == 'ORGANIZATION_USAGE')
else ACCOUNT_USAGE_METRIC_GROUPS
)
self.metric_queries = []
self.errors = []
for mgroup in self._config.metric_groups:
try:
if not self._config.aggregate_last_24_hours:
for query in range(len(METRIC_GROUPS[mgroup])):
METRIC_GROUPS[mgroup][query]['query'] = METRIC_GROUPS[mgroup][query]['query'].replace(
for query in range(len(metric_groups[mgroup])):
metric_groups[mgroup][query]['query'] = metric_groups[mgroup][query]['query'].replace(
'DATEADD(hour, -24, current_timestamp())', 'date_trunc(day, current_date)'
)
self.metric_queries.extend(METRIC_GROUPS[mgroup])
self.metric_queries.extend(metric_groups[mgroup])
except KeyError:
self.errors.append(mgroup)

if self.errors:
self.log.warning('Invalid metric_groups found in snowflake conf.yaml: %s', (', '.join(self.errors)))
self.log.warning('Invalid metric_groups forfound in snowflake conf.yaml: %s', (', '.join(self.errors)))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like an accidental change.

Copy link
Contributor Author

@sarah-witt sarah-witt Jun 22, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yes I meant to include the schema as well

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh wow so the test covering this was actually broken which is why I didn't catch this earlier! Good spot! https://github.com/DataDog/integrations-core/blob/master/snowflake/tests/test_unit.py#L258

if not self.metric_queries and not self._config.custom_queries_defined:
raise ConfigurationError('No valid metric_groups or custom query configured, please list at least one.')
raise ConfigurationError(
'No valid metric_groups for `{}` or custom query configured, please list at least one.'.format(
self._config.schema
)
)

self._query_manager = QueryManager(self, self.execute_query_raw, queries=self.metric_queries, tags=self._tags)
self.check_initializations.append(self._query_manager.compile_queries)
Expand Down
13 changes: 11 additions & 2 deletions snowflake/datadog_checks/snowflake/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,19 @@ class Config(object):
Encapsulates the validation of an `instance` dictionary and authentication options.
"""

DEFAULT_METRIC_GROUP = [
DEFAULT_METRIC_GROUPS = [
'snowflake.query',
'snowflake.billing',
'snowflake.storage',
'snowflake.logins',
]

DEFAULT_ORG_METRIC_GROUPS = [
'snowflake.organization.warehouse',
'snowflake.organization.currency',
'snowflake.organization.storage',
]

AUTHENTICATION_MODES = ['snowflake', 'oauth', 'snowflake_jwt']

def __init__(self, instance=None):
Expand Down Expand Up @@ -47,7 +53,10 @@ def __init__(self, instance=None):
aggregate_last_24_hours = instance.get('aggregate_last_24_hours', False)
custom_queries_defined = len(instance.get('custom_queries', [])) > 0

metric_groups = instance.get('metric_groups', self.DEFAULT_METRIC_GROUP)
default_metric_groups = (
self.DEFAULT_ORG_METRIC_GROUPS if schema == 'ORGANIZATION_USAGE' else self.DEFAULT_METRIC_GROUPS
)
metric_groups = instance.get('metric_groups', default_metric_groups)

if account is None:
raise ConfigurationError('Must specify an account')
Expand Down
14 changes: 13 additions & 1 deletion snowflake/datadog_checks/snowflake/data/conf.yaml.example
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ instances:

## @param schema - string - optional - default: ACCOUNT_USAGE
## Name of the default schema to use for the database.
##
## To collect organization level metrics, set this to ORGANIZATION_USAGE
#
# schema: ACCOUNT_USAGE

Expand Down Expand Up @@ -147,7 +149,7 @@ instances:
## @param metric_groups - list of strings - optional
## List Snowflake metric groups to collect. Metric groups are determined by the metric prefixes.
##
## The available metric groups are:
## When querying the ACCOUNT_USAGE schema, the available metric groups are:
##
## - snowflake.query (enabled by default)
## - snowflake.billing (enabled by default)
Expand All @@ -159,6 +161,16 @@ instances:
## - snowflake.auto_recluster
## - snowflake.pipe
## - snowflake.replication
##
## When querying the ORGANIZATION_USAGE schema, the available metric groups are:
## - snowflake.organization.warehouse (enabled by default)
## - snowflake.organization.currency (enabled by default)
## - snowflake.organization.credit
## - snowflake.organization.storage (enabled by default)
## - snowflake.organization.contracts
## - snowflake.organization.balance
## - snowflake.organization.rate
## - snowflake.organization.data_transfer
#
# metric_groups:
# - snowflake.query
Expand Down
147 changes: 145 additions & 2 deletions snowflake/datadog_checks/snowflake/queries.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# All rights reserved
# Licensed under a 3-clause BSD style license (see LICENSE)

# https://docs.snowflak e.com/en/sql-reference/account-usage/storage_usage.html
# https://docs.snowflake.com/en/sql-reference/account-usage/storage_usage.html
StorageUsageMetrics = {
'name': 'storage.metrics',
'query': ('SELECT STORAGE_BYTES, STAGE_BYTES, FAILSAFE_BYTES from STORAGE_USAGE ORDER BY USAGE_DATE DESC LIMIT 1;'),
Expand Down Expand Up @@ -52,7 +52,7 @@

# https://docs.snowflake.com/en/sql-reference/account-usage/warehouse_metering_history.html
WarehouseCreditUsage = {
'name': 'billings.warehouse.metrics',
'name': 'billing.warehouse.metrics',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this was wrong?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was not incorrect, just inconsistent with other naming conventions. The name is not used anywhere besides logging. I can make this change in a separate PR if you think it's cleaner.

'query': (
'select WAREHOUSE_NAME, sum(CREDITS_USED_COMPUTE), avg(CREDITS_USED_COMPUTE), '
'sum(CREDITS_USED_CLOUD_SERVICES), avg(CREDITS_USED_CLOUD_SERVICES), '
Expand Down Expand Up @@ -222,3 +222,146 @@
{'name': 'replication.bytes_transferred.sum', 'type': 'gauge'},
],
}


# https://docs.snowflake.com/en/sql-reference/organization-usage/contract_items.html
OrgContractItems = {
'name': 'organization.contract.metrics',
'query': ('select CONTRACT_NUMBER, CONTRACT_ITEM, CURRENCY, AMOUNT from CONTRACT_ITEMS group by 1, 2, 3;'),
'columns': [
{'name': 'contract_number', 'type': 'tag'},
{'name': 'contract_item', 'type': 'tag'},
{'name': 'currency', 'type': 'tag'},
{'name': 'organization.contract.amount', 'type': 'gauge'},
],
}

# https://docs.snowflake.com/en/sql-reference/organization-usage/metering_daily_history.html
OrgCreditUsage = {
'name': 'organization.credit.metrics',
'query': (
'select ACCOUNT_NAME, SERVICE_TYPE, '
'sum(CREDITS_USED_COMPUTE), avg(CREDITS_USED_COMPUTE), '
'sum(CREDITS_USED_CLOUD_SERVICES), avg(CREDITS_USED_CLOUD_SERVICES), '
'sum(CREDITS_ADJUSTMENT_CLOUD_SERVICES), avg(CREDITS_ADJUSTMENT_CLOUD_SERVICES), '
'sum(CREDITS_USED), avg(CREDITS_USED), sum(CREDITS_BILLED), avg(CREDITS_BILLED) from METERING_DAILY_HISTORY '
'where USAGE_DATE = DATEADD(day, -1, current_date) group by 1, 2;'
),
'columns': [
{'name': 'billing_account', 'type': 'tag'},
{'name': 'service_type', 'type': 'tag'},
{'name': 'organization.virtual_warehouse.sum', 'type': 'gauge'},
{'name': 'organization.virtual_warehouse.avg', 'type': 'gauge'},
{'name': 'organization.cloud_service.sum', 'type': 'gauge'},
{'name': 'organization.cloud_service.avg', 'type': 'gauge'},
{'name': 'organization.cloud_service_adjustment.sum', 'type': 'gauge'},
{'name': 'organization.cloud_service_adjustment.avg', 'type': 'gauge'},
{'name': 'organization.total_credit.sum', 'type': 'gauge'},
{'name': 'organization.total_credit.avg', 'type': 'gauge'},
{'name': 'organization.total_credits_billed.sum', 'type': 'gauge'},
{'name': 'organization.total_credits_billed.avg', 'type': 'gauge'},
],
}

# https://docs.snowflake.com/en/sql-reference/organization-usage/usage_in_currency_daily.html
OrgCurrencyUsage = {
'name': 'organization.currency.metrics',
'query': (
'select ACCOUNT_NAME, SERVICE_LEVEL, USAGE_TYPE, CURRENCY, '
'sum(USAGE), sum(USAGE_IN_CURRENCY) from USAGE_IN_CURRENCY_DAILY '
'where USAGE_DATE = DATEADD(day, -1, current_date) group by 1, 2, 3, 4;'
),
'columns': [
{'name': 'billing_account', 'type': 'tag'},
{'name': 'service_level', 'type': 'tag'},
{'name': 'usage_type', 'type': 'tag'},
{'name': 'currency', 'type': 'tag'},
{'name': 'organization.currency.usage', 'type': 'gauge'},
{'name': 'organization.currency.usage_in_currency', 'type': 'gauge'},
],
}


# https://docs.snowflake.com/en/sql-reference/organization-usage/warehouse_metering_history.html
OrgWarehouseCreditUsage = {
'name': 'organization.warehouse.metrics',
'query': (
'select WAREHOUSE_NAME, ACCOUNT_NAME, sum(CREDITS_USED_COMPUTE), avg(CREDITS_USED_COMPUTE), '
'sum(CREDITS_USED_CLOUD_SERVICES), avg(CREDITS_USED_CLOUD_SERVICES), '
'sum(CREDITS_USED), avg(CREDITS_USED) from WAREHOUSE_METERING_HISTORY '
'where start_time = DATEADD(day, -1, current_date) group by 1, 2;'
),
'columns': [
{'name': 'warehouse', 'type': 'tag'},
{'name': 'billing_account', 'type': 'tag'},
{'name': 'organization.warehouse.virtual_warehouse.sum', 'type': 'gauge'},
{'name': 'organization.warehouse.virtual_warehouse.avg', 'type': 'gauge'},
{'name': 'organization.warehouse.cloud_service.sum', 'type': 'gauge'},
{'name': 'organization.warehouse.cloud_service.avg', 'type': 'gauge'},
{'name': 'organization.warehouse.total_credit.sum', 'type': 'gauge'},
{'name': 'organization.warehouse.total_credit.avg', 'type': 'gauge'},
],
}

# https://docs.snowflake.com/en/sql-reference/organization-usage/storage_daily_history.html
OrgStorageDaily = {
'name': 'organization.storage.metrics',
'query': (
'select ACCOUNT_NAME, sum(AVERAGE_BYTES), sum(CREDITS) from STORAGE_DAILY_HISTORY '
'where USAGE_DATE = DATEADD(day, -1, current_date) group by 1;'
),
'columns': [
{'name': 'billing_account', 'type': 'tag'},
{'name': 'organization.storage.average_bytes', 'type': 'gauge'},
{'name': 'organization.storage.credits', 'type': 'gauge'},
],
}


# https://docs.snowflake.com/en/sql-reference/organization-usage/remaining_balance_daily.html
OrgBalance = {
'name': 'organization.balance.metrics',
'query': (
'select CONTRACT_NUMBER, CURRENCY, sum(FREE_USAGE_BALANCE), sum(CAPACITY_BALANCE), '
'sum(ON_DEMAND_CONSUMPTION_BALANCE), sum(ROLLOVER_BALANCE) from REMAINING_BALANCE_DAILY '
'where DATE = DATEADD(day, -1, current_date) group by 1, 2;'
),
'columns': [
{'name': 'contract_number', 'type': 'tag'},
{'name': 'currency', 'type': 'tag'},
{'name': 'organization.balance.free_usage', 'type': 'gauge'},
{'name': 'organization.balance.capacity', 'type': 'gauge'},
{'name': 'organization.balance.on_demand_consumption', 'type': 'gauge'},
{'name': 'organization.balance.rollover', 'type': 'gauge'},
],
}

# https://docs.snowflake.com/en/sql-reference/organization-usage/rate_sheet_daily.html
OrgRateSheet = {
'name': 'organization.rate.metrics',
'query': (
'select CONTRACT_NUMBER, ACCOUNT_NAME, USAGE_TYPE, SERVICE_TYPE, CURRENCY, '
'sum(EFFECTIVE_RATE) from RATE_SHEET_DAILY '
'where DATE = DATEADD(day, -1, current_date) group by 1, 2, 3, 4, 5;'
),
'columns': [
{'name': 'contract_number', 'type': 'tag'},
{'name': 'billing_account', 'type': 'tag'},
{'name': 'usage_type', 'type': 'tag'},
{'name': 'service_type', 'type': 'tag'},
{'name': 'currency', 'type': 'tag'},
{'name': 'organization.rate.effective_rate', 'type': 'gauge'},
],
}

# https://docs.snowflake.com/en/sql-reference/organization-usage/data_transfer_history.html
OrgDataTransfer = {
'name': 'organization.data_transfer.metrics',
'query': (
'select ACCOUNT_NAME, BYTES_TRANSFERRED from DATA_TRANSFER_DAILY_HISTORY ORDER BY USAGE_DATE DESC LIMIT 1;'
),
'columns': [
{'name': 'billing_account', 'type': 'tag'},
{'name': 'organization.data_transfer.bytes_transferred', 'type': 'gauge'},
],
}
Loading