@causa/workspace-google 1.4.0-beta.1 → 1.4.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -0
- package/dist/functions/google-app-check/generate-token.js +3 -1
- package/dist/functions/google-firebase-storage/merge-rules.js +3 -1
- package/dist/functions/google-firestore/merge-rules.js +3 -1
- package/dist/functions/google-identity-platform/generate-custom-token.js +7 -2
- package/dist/functions/google-pubsub/write-topics.js +7 -2
- package/dist/functions/google-services/enable.js +3 -1
- package/dist/functions/google-spanner/write-databases.js +7 -2
- package/dist/functions/graph/fetch-environment-metric-series.call.d.ts +3 -0
- package/dist/functions/graph/fetch-environment-metric-series.call.js +107 -0
- package/dist/functions/graph/fetch-environment-metric-series.d.ts +23 -0
- package/dist/functions/graph/fetch-environment-metric-series.js +40 -0
- package/dist/functions/graph/get-environment-provider.d.ts +11 -0
- package/dist/functions/graph/get-environment-provider.js +20 -0
- package/dist/functions/graph/index.d.ts +2 -0
- package/dist/functions/graph/index.js +2 -0
- package/dist/functions/index.js +2 -2
- package/dist/graph/cloud-run.d.ts +2 -2
- package/dist/graph/cloud-run.js +2 -2
- package/dist/graph/environment/fetchers/cloud-monitoring-alerts.d.ts +6 -0
- package/dist/graph/environment/fetchers/cloud-monitoring-alerts.js +106 -0
- package/dist/graph/environment/fetchers/cloud-monitoring.d.ts +5 -0
- package/dist/graph/environment/fetchers/cloud-monitoring.js +13 -0
- package/dist/graph/environment/fetchers/cloud-run.d.ts +5 -0
- package/dist/graph/environment/fetchers/cloud-run.js +88 -0
- package/dist/graph/environment/fetchers/cloud-scheduler.d.ts +5 -0
- package/dist/graph/environment/fetchers/cloud-scheduler.js +95 -0
- package/dist/graph/environment/fetchers/console-links.d.ts +14 -0
- package/dist/graph/environment/fetchers/console-links.js +132 -0
- package/dist/graph/environment/fetchers/error-reporting.d.ts +5 -0
- package/dist/graph/environment/fetchers/error-reporting.js +98 -0
- package/dist/graph/environment/fetchers/index.d.ts +5 -0
- package/dist/graph/environment/fetchers/index.js +17 -0
- package/dist/graph/environment/index.d.ts +9 -0
- package/dist/graph/environment/index.js +9 -0
- package/dist/graph/environment/load-balancer-backends.d.ts +82 -0
- package/dist/graph/environment/load-balancer-backends.js +126 -0
- package/dist/graph/environment/monitoring-alerts.d.ts +60 -0
- package/dist/graph/environment/monitoring-alerts.js +67 -0
- package/dist/graph/environment/monitoring-catalog.d.ts +95 -0
- package/dist/graph/environment/monitoring-catalog.js +356 -0
- package/dist/graph/environment/monitoring-mappings.d.ts +133 -0
- package/dist/graph/environment/monitoring-mappings.js +97 -0
- package/dist/graph/environment/monitoring-query.d.ts +116 -0
- package/dist/graph/environment/monitoring-query.js +314 -0
- package/dist/graph/environment/monitoring-values.d.ts +41 -0
- package/dist/graph/environment/monitoring-values.js +119 -0
- package/dist/graph/environment/resource-resolvers.d.ts +7 -0
- package/dist/graph/environment/resource-resolvers.js +57 -0
- package/dist/graph/environment/resources.d.ts +45 -0
- package/dist/graph/environment/resources.js +49 -0
- package/dist/graph/index.d.ts +1 -0
- package/dist/graph/index.js +1 -0
- package/dist/graph/spanner.d.ts +2 -2
- package/dist/graph/spanner.js +2 -2
- package/package.json +19 -19
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { GraphEnvironmentFact, } from '@causa/workspace-core/graph';
|
|
2
|
+
import { templateMapping, } from './monitoring-mappings.js';
|
|
3
|
+
import { googleResources } from './resources.js';
|
|
4
|
+
/**
|
|
5
|
+
* Maps load balancer labels to the URL maps of the graph, i.e. API routers.
|
|
6
|
+
*/
|
|
7
|
+
export const URL_MAP_MAPPING = templateMapping({
|
|
8
|
+
nodeType: 'apiRouter',
|
|
9
|
+
resourceType: 'compute.googleapis.com/UrlMap',
|
|
10
|
+
monitoredResource: 'https_lb_rule',
|
|
11
|
+
name: 'projects/{project}/global/urlMaps/{resource.label.url_map_name}',
|
|
12
|
+
});
|
|
13
|
+
/**
|
|
14
|
+
* Serverless network endpoint groups, routing requests to Cloud Run services.
|
|
15
|
+
* They are listed in the regions of the services, in the projects of the URL maps.
|
|
16
|
+
*/
|
|
17
|
+
export const SERVERLESS_NEG_BACKENDS = {
|
|
18
|
+
nodeType: 'service',
|
|
19
|
+
resourceType: 'run.googleapis.com/Service',
|
|
20
|
+
keyLabels: ['resource.label.backend_scope', 'resource.label.backend_name'],
|
|
21
|
+
list: async (context, projects, resources) => {
|
|
22
|
+
const locations = new Map();
|
|
23
|
+
for (const { segments } of resources) {
|
|
24
|
+
const { projects: project, locations: region } = segments;
|
|
25
|
+
if (project && region && projects.includes(project)) {
|
|
26
|
+
locations.set(`projects/${project}/regions/${region}`, {
|
|
27
|
+
project,
|
|
28
|
+
region,
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
if (locations.size === 0) {
|
|
33
|
+
return [];
|
|
34
|
+
}
|
|
35
|
+
const { GoogleApisService } = await import('../../services/google-apis.js');
|
|
36
|
+
const client = await context
|
|
37
|
+
.service(GoogleApisService)
|
|
38
|
+
.getClient('compute', 'v1', {});
|
|
39
|
+
const backends = await Promise.all([...locations.values()].map(async ({ project, region }) => {
|
|
40
|
+
const groups = [];
|
|
41
|
+
try {
|
|
42
|
+
let pageToken;
|
|
43
|
+
do {
|
|
44
|
+
const { data } = await client.regionNetworkEndpointGroups.list({
|
|
45
|
+
project,
|
|
46
|
+
region,
|
|
47
|
+
pageToken,
|
|
48
|
+
});
|
|
49
|
+
groups.push(...(data.items ?? []));
|
|
50
|
+
pageToken = data.nextPageToken ?? undefined;
|
|
51
|
+
} while (pageToken);
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
throw new Error(`Failed to list the network endpoint groups in project '${project}' and region '${region}': ${error.message ?? error}`, { cause: error });
|
|
55
|
+
}
|
|
56
|
+
return groups.flatMap((group) => {
|
|
57
|
+
const service = group.cloudRun?.service;
|
|
58
|
+
if (group.networkEndpointType !== 'SERVERLESS' ||
|
|
59
|
+
!group.name ||
|
|
60
|
+
!service) {
|
|
61
|
+
return [];
|
|
62
|
+
}
|
|
63
|
+
return [
|
|
64
|
+
{
|
|
65
|
+
kind: SERVERLESS_NEG_BACKENDS,
|
|
66
|
+
project,
|
|
67
|
+
labels: {
|
|
68
|
+
'resource.label.backend_scope': region,
|
|
69
|
+
'resource.label.backend_name': group.name,
|
|
70
|
+
},
|
|
71
|
+
resource: `projects/${project}/locations/${region}/services/${service}`,
|
|
72
|
+
},
|
|
73
|
+
];
|
|
74
|
+
});
|
|
75
|
+
}));
|
|
76
|
+
return backends.flat();
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
/**
|
|
80
|
+
* The kinds of backends through which requests can be routed to nodes.
|
|
81
|
+
*/
|
|
82
|
+
export const LOAD_BALANCER_BACKEND_KINDS = [
|
|
83
|
+
SERVERLESS_NEG_BACKENDS,
|
|
84
|
+
];
|
|
85
|
+
/**
|
|
86
|
+
* Lists the backends of all kinds, in the projects of the URL maps of the graph. No API is called if the graph has no
|
|
87
|
+
* URL map. The fact fails if some backends cannot be listed.
|
|
88
|
+
*/
|
|
89
|
+
export class LoadBalancerBackendsFact extends GraphEnvironmentFact {
|
|
90
|
+
async compute(environment) {
|
|
91
|
+
const projects = [
|
|
92
|
+
...new Set(googleResources(environment, URL_MAP_MAPPING.resourceType, URL_MAP_MAPPING.nodeType).map(({ segments }) => segments.projects)),
|
|
93
|
+
];
|
|
94
|
+
if (projects.length === 0) {
|
|
95
|
+
return { value: [] };
|
|
96
|
+
}
|
|
97
|
+
const backends = await Promise.all(LOAD_BALANCER_BACKEND_KINDS.map((kind) => kind.list(environment.context, projects, googleResources(environment, kind.resourceType, kind.nodeType))));
|
|
98
|
+
return { value: backends.flat() };
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Builds the mapping of the backends of a kind, from load balancer labels to the resources they route requests to.
|
|
103
|
+
*
|
|
104
|
+
* @param kind The kind of backends.
|
|
105
|
+
* @returns The mapping.
|
|
106
|
+
*/
|
|
107
|
+
export function loadBalancerBackendMapping(kind) {
|
|
108
|
+
return {
|
|
109
|
+
nodeType: kind.nodeType,
|
|
110
|
+
resourceType: kind.resourceType,
|
|
111
|
+
monitoredResource: URL_MAP_MAPPING.monitoredResource,
|
|
112
|
+
keyLabels: kind.keyLabels,
|
|
113
|
+
resolver: async (environment) => {
|
|
114
|
+
const backends = await environment.get(LoadBalancerBackendsFact);
|
|
115
|
+
const key = (project, labels) => JSON.stringify([project, ...kind.keyLabels.map((l) => labels[l])]);
|
|
116
|
+
const resources = new Map(backends
|
|
117
|
+
.filter((b) => b.kind === kind)
|
|
118
|
+
.map((b) => [key(b.project, b.labels), b.resource]));
|
|
119
|
+
return ({ project, labels }) => resources.get(key(project, labels));
|
|
120
|
+
},
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* The mappings of load balancer labels to the resources backends route requests to, one per kind of backend.
|
|
125
|
+
*/
|
|
126
|
+
export const LOAD_BALANCER_BACKEND_MAPPINGS = LOAD_BALANCER_BACKEND_KINDS.map(loadBalancerBackendMapping);
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { WorkspaceContext } from '@causa/workspace';
|
|
2
|
+
import type { monitoring_v3 } from 'googleapis';
|
|
3
|
+
import { type MonitoringLabel } from './monitoring-mappings.js';
|
|
4
|
+
/**
|
|
5
|
+
* An alert, as returned by the Cloud Monitoring Alerts API.
|
|
6
|
+
*/
|
|
7
|
+
export type MonitoringAlert = monitoring_v3.Schema$Alert;
|
|
8
|
+
/**
|
|
9
|
+
* The monitored resource of an alert.
|
|
10
|
+
*/
|
|
11
|
+
export type MonitoringAlertResource = {
|
|
12
|
+
/**
|
|
13
|
+
* The monitored resource type, e.g. `cloud_run_revision`.
|
|
14
|
+
*/
|
|
15
|
+
readonly type: string;
|
|
16
|
+
/**
|
|
17
|
+
* The GCP project of the resource: the `project_id` label, or the project from which the alert was listed.
|
|
18
|
+
*/
|
|
19
|
+
readonly project: string;
|
|
20
|
+
/**
|
|
21
|
+
* The labels of the alert, keyed by name. Resource labels take precedence over metric labels with the same name, as
|
|
22
|
+
* alerts aggregating away the resource type only keep metric labels.
|
|
23
|
+
*/
|
|
24
|
+
readonly labels: Readonly<Record<string, string>>;
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* An alert open at a given time, along with its monitored resource.
|
|
28
|
+
*/
|
|
29
|
+
export type OpenMonitoringAlert = {
|
|
30
|
+
/**
|
|
31
|
+
* The alert, as returned by the Alerts API.
|
|
32
|
+
*/
|
|
33
|
+
readonly alert: MonitoringAlert;
|
|
34
|
+
/**
|
|
35
|
+
* The GCP project from which the alert was listed.
|
|
36
|
+
*/
|
|
37
|
+
readonly project: string;
|
|
38
|
+
/**
|
|
39
|
+
* The monitored resource of the alert. It is `undefined` when its type is unknown.
|
|
40
|
+
*/
|
|
41
|
+
readonly resource?: MonitoringAlertResource;
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* Returns the value of a label of the resource of an alert, read by its name regardless of its source, as alerts
|
|
45
|
+
* aggregating away the resource type only keep metric labels.
|
|
46
|
+
*
|
|
47
|
+
* @param resource The resource of the alert.
|
|
48
|
+
* @param label The label.
|
|
49
|
+
* @returns The value of the label.
|
|
50
|
+
*/
|
|
51
|
+
export declare function alertLabelValue(resource: MonitoringAlertResource, label: MonitoringLabel): string | undefined;
|
|
52
|
+
/**
|
|
53
|
+
* Lists the alerts of a project that were open at a given time, along with their monitored resources.
|
|
54
|
+
*
|
|
55
|
+
* @param context The context, from which the `GoogleApisService` is obtained.
|
|
56
|
+
* @param project The GCP project.
|
|
57
|
+
* @param at The time at which the alerts were open.
|
|
58
|
+
* @returns The alerts.
|
|
59
|
+
*/
|
|
60
|
+
export declare function listOpenAlerts(context: WorkspaceContext, project: string, at: Date): Promise<OpenMonitoringAlert[]>;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { monitoringLabelName, } from './monitoring-mappings.js';
|
|
2
|
+
/**
|
|
3
|
+
* The resource type of alerts whose condition aggregates away the monitored resource type, e.g. a PromQL query
|
|
4
|
+
* aggregating a metric written against several resource types.
|
|
5
|
+
*/
|
|
6
|
+
const PROMETHEUS_TARGET = 'prometheus_target';
|
|
7
|
+
/**
|
|
8
|
+
* Returns the value of a label of the resource of an alert, read by its name regardless of its source, as alerts
|
|
9
|
+
* aggregating away the resource type only keep metric labels.
|
|
10
|
+
*
|
|
11
|
+
* @param resource The resource of the alert.
|
|
12
|
+
* @param label The label.
|
|
13
|
+
* @returns The value of the label.
|
|
14
|
+
*/
|
|
15
|
+
export function alertLabelValue(resource, label) {
|
|
16
|
+
return resource.labels[monitoringLabelName(label)];
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Lists the alerts of a project that were open at a given time, along with their monitored resources.
|
|
20
|
+
*
|
|
21
|
+
* @param context The context, from which the `GoogleApisService` is obtained.
|
|
22
|
+
* @param project The GCP project.
|
|
23
|
+
* @param at The time at which the alerts were open.
|
|
24
|
+
* @returns The alerts.
|
|
25
|
+
*/
|
|
26
|
+
export async function listOpenAlerts(context, project, at) {
|
|
27
|
+
const { GoogleApisService } = await import('../../services/google-apis.js');
|
|
28
|
+
const client = await context
|
|
29
|
+
.service(GoogleApisService)
|
|
30
|
+
.getClient('monitoring', 'v3', {});
|
|
31
|
+
// The API only supports `>=` and `<=` comparisons, and timestamps without fractional seconds. Alerts that are still
|
|
32
|
+
// open have no closing time.
|
|
33
|
+
const time = at.toISOString().replace(/\.\d+Z$/, 'Z');
|
|
34
|
+
const filter = `open_time <= "${time}" AND (state = "OPEN" OR close_time >= "${time}")`;
|
|
35
|
+
const alerts = [];
|
|
36
|
+
let pageToken;
|
|
37
|
+
do {
|
|
38
|
+
const { data } = await client.projects.alerts.list({
|
|
39
|
+
parent: `projects/${project}`,
|
|
40
|
+
filter,
|
|
41
|
+
pageSize: 1000,
|
|
42
|
+
pageToken,
|
|
43
|
+
});
|
|
44
|
+
alerts.push(...(data.alerts ?? []));
|
|
45
|
+
pageToken = data.nextPageToken ?? undefined;
|
|
46
|
+
} while (pageToken);
|
|
47
|
+
return alerts.map((alert) => {
|
|
48
|
+
const labels = {
|
|
49
|
+
...alert.metric?.labels,
|
|
50
|
+
...alert.resource?.labels,
|
|
51
|
+
};
|
|
52
|
+
const resourceType = alert.resource?.type;
|
|
53
|
+
// PromQL alerts have their resource type set to `prometheus_target`. In that case, the actual resource type is read
|
|
54
|
+
// from the `monitored_resource` label, which the alert is expected to group by.
|
|
55
|
+
const type = resourceType && resourceType !== PROMETHEUS_TARGET
|
|
56
|
+
? resourceType
|
|
57
|
+
: labels.monitored_resource;
|
|
58
|
+
if (!type) {
|
|
59
|
+
return { alert, project };
|
|
60
|
+
}
|
|
61
|
+
return {
|
|
62
|
+
alert,
|
|
63
|
+
project,
|
|
64
|
+
resource: { type, project: labels.project_id ?? project, labels },
|
|
65
|
+
};
|
|
66
|
+
});
|
|
67
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { GraphMetricKind, type Graph, type GraphMetricDefinitionsByType } from '@causa/workspace-core';
|
|
2
|
+
import { type GraphNodeEntry } from '@causa/workspace-core/graph';
|
|
3
|
+
import { type MonitoringLabel, type MonitoringResourceMapping } from './monitoring-mappings.js';
|
|
4
|
+
export declare const CLOUD_RUN_SERVICE_MAPPING: MonitoringResourceMapping;
|
|
5
|
+
export declare const PUBSUB_TOPIC_MAPPING: MonitoringResourceMapping;
|
|
6
|
+
export declare const PUBSUB_SUBSCRIPTION_MAPPING: MonitoringResourceMapping;
|
|
7
|
+
export declare const CLOUD_TASKS_QUEUE_MAPPING: MonitoringResourceMapping;
|
|
8
|
+
export declare const SPANNER_INSTANCE_MAPPING: MonitoringResourceMapping;
|
|
9
|
+
export declare const SPANNER_DATABASE_MAPPING: MonitoringResourceMapping;
|
|
10
|
+
export declare const FIRESTORE_DATABASE_MAPPING: MonitoringResourceMapping;
|
|
11
|
+
export declare const BIGQUERY_DATASET_MAPPING: MonitoringResourceMapping;
|
|
12
|
+
export declare const CLOUD_SCHEDULER_JOB_MAPPING: MonitoringResourceMapping;
|
|
13
|
+
/**
|
|
14
|
+
* All the mappings of Cloud Monitoring monitored resources to the resources of nodes.
|
|
15
|
+
*/
|
|
16
|
+
export declare const MONITORING_RESOURCE_MAPPINGS: readonly MonitoringResourceMapping[];
|
|
17
|
+
/**
|
|
18
|
+
* A metric of a node type, read from Cloud Monitoring.
|
|
19
|
+
*/
|
|
20
|
+
export type MonitoringMetric = {
|
|
21
|
+
/**
|
|
22
|
+
* The name of the metric in the graph.
|
|
23
|
+
*/
|
|
24
|
+
readonly metric: string;
|
|
25
|
+
/**
|
|
26
|
+
* How the time series of the metric map to the nodes it measures.
|
|
27
|
+
*/
|
|
28
|
+
readonly mapping: MonitoringResourceMapping;
|
|
29
|
+
/**
|
|
30
|
+
* The Cloud Monitoring metric type.
|
|
31
|
+
*/
|
|
32
|
+
readonly metricType: string;
|
|
33
|
+
/**
|
|
34
|
+
* An additional filter on the time series.
|
|
35
|
+
*/
|
|
36
|
+
readonly filter?: string;
|
|
37
|
+
/**
|
|
38
|
+
* How time series of the same node and group are combined. Defaults to `REDUCE_SUM`.
|
|
39
|
+
*/
|
|
40
|
+
readonly reducer?: 'REDUCE_SUM' | 'REDUCE_MAX';
|
|
41
|
+
/**
|
|
42
|
+
* The labels of the time series by which values are grouped, keyed by the name of the group key in the graph.
|
|
43
|
+
*/
|
|
44
|
+
readonly groupBy?: Record<string, MonitoringLabel>;
|
|
45
|
+
/**
|
|
46
|
+
* How the time series of the metric map to other nodes the values relate to, e.g. the services load balancers route
|
|
47
|
+
* requests to. Values are also grouped by the `node` key, whose values are node IDs. The mappings are tried from the
|
|
48
|
+
* one with the most key labels, and share the monitored resource of {@link MonitoringMetric.mapping}.
|
|
49
|
+
*/
|
|
50
|
+
readonly groupByNode?: readonly MonitoringResourceMapping[];
|
|
51
|
+
/**
|
|
52
|
+
* The factor applied to values, e.g. to convert microseconds to milliseconds. Defaults to `1`.
|
|
53
|
+
*/
|
|
54
|
+
readonly scale?: number;
|
|
55
|
+
/**
|
|
56
|
+
* Whether the value is the number of values in the distribution, per second.
|
|
57
|
+
*/
|
|
58
|
+
readonly countRate?: boolean;
|
|
59
|
+
/**
|
|
60
|
+
* The minimum alignment period, in seconds, for metrics sampled less often than the evaluation window.
|
|
61
|
+
*/
|
|
62
|
+
readonly minPeriod?: number;
|
|
63
|
+
/**
|
|
64
|
+
* Whether the metric measures the resource of a node, when it doesn't measure all the resources of its mapping.
|
|
65
|
+
* Nodes for which it returns `false` get no value, and their series are not supported. Defaults to all nodes.
|
|
66
|
+
*
|
|
67
|
+
* @param node The node.
|
|
68
|
+
* @param graph The graph containing the node.
|
|
69
|
+
* @returns Whether the metric measures the node.
|
|
70
|
+
*/
|
|
71
|
+
readonly measures?: (node: GraphNodeEntry, graph: Graph) => boolean;
|
|
72
|
+
};
|
|
73
|
+
/**
|
|
74
|
+
* Returns whether a subscription pushes messages to a service, rather than e.g. writing them to a table.
|
|
75
|
+
*
|
|
76
|
+
* @param node The node of the subscription.
|
|
77
|
+
* @param graph The graph containing the node.
|
|
78
|
+
* @returns `true` if the subscription targets a service.
|
|
79
|
+
*/
|
|
80
|
+
export declare function isPushSubscription(node: GraphNodeEntry, graph: Graph): boolean;
|
|
81
|
+
/**
|
|
82
|
+
* The metrics read from Cloud Monitoring.
|
|
83
|
+
*/
|
|
84
|
+
export declare const MONITORING_METRICS: readonly MonitoringMetric[];
|
|
85
|
+
/**
|
|
86
|
+
* The definitions of the metrics of the node types defined by the Google module.
|
|
87
|
+
*/
|
|
88
|
+
export declare const GOOGLE_GRAPH_METRIC_DEFINITIONS: GraphMetricDefinitionsByType;
|
|
89
|
+
/**
|
|
90
|
+
* Returns the kind of a metric read from Cloud Monitoring, from its definition for the type of its nodes.
|
|
91
|
+
*
|
|
92
|
+
* @param metric The metric.
|
|
93
|
+
* @returns The kind of the metric.
|
|
94
|
+
*/
|
|
95
|
+
export declare function monitoringMetricKind(metric: MonitoringMetric): GraphMetricKind;
|