diff --git a/sdk/monitor/azquery/CHANGELOG.md b/sdk/monitor/azquery/CHANGELOG.md index 2045ac7951b7..f2fa4edcf32d 100644 --- a/sdk/monitor/azquery/CHANGELOG.md +++ b/sdk/monitor/azquery/CHANGELOG.md @@ -1,12 +1,9 @@ # Release History -## 1.1.1 (Unreleased) +## 1.2.0-beta.1 (2023-11-16) ### Features Added - -### Breaking Changes - -### Bugs Fixed +* Added `MetricsBatchClient` to support batch querying metrics from Azure resources ### Other Changes * Enabled spans for distributed tracing. diff --git a/sdk/monitor/azquery/README.md b/sdk/monitor/azquery/README.md index 7302aad69209..b5b7d575af78 100644 --- a/sdk/monitor/azquery/README.md +++ b/sdk/monitor/azquery/README.md @@ -43,6 +43,10 @@ Example [logs client][example_logs_client] Example [metrics client][example_metrics_client] +#### Create a metrics batch client + +Example metrics batch client + ## Key concepts ### Timespan @@ -62,6 +66,12 @@ Each set of metric values is a time series with the following characteristics: - The value itself - Some metrics may have multiple dimensions as described in [multi-dimensional metrics][multi-metrics]. Custom metrics can have up to 10 dimensions. +### Metrics batch query + +A user can also query metrics from multiple resources at once using the query_batch method of MetricsBatchClient. This uses a different API than the MetricsClient and requires that a user pass in a regional endpoint when instantiating the client (for example, "https://westus3.metrics.monitor.azure.com"). + +Note, each resource must be in the same region as the endpoint passed in when instantiating the client, and each resource must be in the same Azure subscription. Furthermore, the metric namespace that contains the metrics to be queried must also be passed. A list of metric namespaces can be found [here][metric_namespaces]. + ### Logs query rate limits and throttling The Log Analytics service applies throttling when the request rate is too high. Limits, such as the maximum number of rows returned, are also applied on the Kusto queries. For more information, see [Query API][service_limits]. @@ -142,6 +152,7 @@ comments. [log_analytics_workspace_create]: https://learn.microsoft.com/azure/azure-monitor/logs/quick-create-workspace [logs_overview]: https://learn.microsoft.com/azure/azure-monitor/logs/data-platform-logs [metrics_overview]: https://learn.microsoft.com/azure/azure-monitor/essentials/data-platform-metrics +[metric_namespaces]: https://learn.microsoft.com/azure/azure-monitor/reference/supported-metrics/metrics-index#metrics-by-resource-provider [monitor_docs]: https://learn.microsoft.com/azure/azure-monitor/ [monitor_rest_docs]: https://learn.microsoft.com/rest/api/monitor/ [multi-metrics]: https://learn.microsoft.com/azure/azure-monitor/essentials/data-platform-metrics#multi-dimensional-metrics diff --git a/sdk/monitor/azquery/assets.json b/sdk/monitor/azquery/assets.json index 555e2357a7f8..6f2d1fbf42ec 100644 --- a/sdk/monitor/azquery/assets.json +++ b/sdk/monitor/azquery/assets.json @@ -2,5 +2,5 @@ "AssetsRepo": "Azure/azure-sdk-assets", "AssetsRepoPrefixPath": "go", "TagPrefix": "go/monitor/azquery", - "Tag": "go/monitor/azquery_4e1ec7fc44" + "Tag": "go/monitor/azquery_84f4b44326" } diff --git a/sdk/monitor/azquery/autorest.md b/sdk/monitor/azquery/autorest.md index e337c59ace87..7a73d8899fd9 100644 --- a/sdk/monitor/azquery/autorest.md +++ b/sdk/monitor/azquery/autorest.md @@ -5,13 +5,13 @@ title: Logs Query Client clear-output-folder: false go: true input-file: - - https://github.com/Azure/azure-rest-api-specs/blob/72427ef3ff5875bd8409ef112ef5e6f3cf2b8795/specification/operationalinsights/data-plane/Microsoft.OperationalInsights/stable/2022-10-27/OperationalInsights.json + - https://github.com/Azure/azure-rest-api-specs/blob/21f5332f2dc7437d1446edf240e9a3d4c90c6431/specification/operationalinsights/data-plane/Microsoft.OperationalInsights/stable/2022-10-27/OperationalInsights.json license-header: MICROSOFT_MIT_NO_VERSION module: github.com/Azure/azure-sdk-for-go/sdk/monitor/azquery openapi-type: "data-plane" output-folder: ../azquery security: "AADToken" -use: "@autorest/go@4.0.0-preview.60" +use: "@autorest/go@4.0.0-preview.61" inject-spans: true version: "^3.0.0" @@ -160,9 +160,9 @@ directive: ``` yaml title: Metrics Query Client input-file: - - https://github.com/Azure/azure-rest-api-specs/blob/dba6ed1f03bda88ac6884c0a883246446cc72495/specification/monitor/resource-manager/Microsoft.Insights/stable/2018-01-01/metricDefinitions_API.json - - https://github.com/Azure/azure-rest-api-specs/blob/dba6ed1f03bda88ac6884c0a883246446cc72495/specification/monitor/resource-manager/Microsoft.Insights/stable/2018-01-01/metrics_API.json - - https://github.com/Azure/azure-rest-api-specs/blob/dba6ed1f03bda88ac6884c0a883246446cc72495/specification/monitor/resource-manager/Microsoft.Insights/preview/2017-12-01-preview/metricNamespaces_API.json + - https://github.com/Azure/azure-rest-api-specs/blob/21f5332f2dc7437d1446edf240e9a3d4c90c6431/specification/monitor/resource-manager/Microsoft.Insights/stable/2018-01-01/metricDefinitions_API.json + - https://github.com/Azure/azure-rest-api-specs/blob/21f5332f2dc7437d1446edf240e9a3d4c90c6431/specification/monitor/resource-manager/Microsoft.Insights/stable/2018-01-01/metrics_API.json + - https://github.com/Azure/azure-rest-api-specs/blob/21f5332f2dc7437d1446edf240e9a3d4c90c6431/specification/monitor/resource-manager/Microsoft.Insights/preview/2017-12-01-preview/metricNamespaces_API.json directive: # rename metric operations to generate as a separate metrics client @@ -200,10 +200,71 @@ directive: - from: options.go where: $ transform: return $.replace(/Aggregation \*string/g, "Aggregation []*AggregationType"); - - from: metrics_client.go + - from: + - metrics_client.go + - metricsbatch_client.go where: $ transform: return $.replace(/\*options.Aggregation/g, "aggregationTypeToString(options.Aggregation)"); - from: swagger-document where: $.parameters.AggregationsParameter transform: $["description"] = "The list of aggregation types to retrieve" +``` + +``` yaml +title: Metrics Batch Query Client +input-file: https://github.com/Azure/azure-rest-api-specs/blob/21f5332f2dc7437d1446edf240e9a3d4c90c6431/specification/monitor/data-plane/Microsoft.Insights/preview/2023-05-01-preview/metricBatch.json + +directive: + # rename Batch to QueryBatch + - rename-operation: + from: MetricsBatch_Batch + to: MetricsBatch_QueryBatch + + # Rename MetricResultsResponse + - rename-model: + from: MetricResultsResponse + to: MetricResults + - from: + - models.go + - models_serde.go + where: $ + transform: return $.replace(/MetricResultsValuesItem/g, "MetricValues"); + - from: swagger-document + where: $.definitions.MetricResults.properties.values.items + transform: $["description"] = "Metric data values." + + # fix casing, rename batch metric fields + - from: swagger-document + where: $.parameters.StartTimeParameter + transform: $["x-ms-client-name"] = "StartTime" + - from: swagger-document + where: $.parameters.EndTimeParameter + transform: $["x-ms-client-name"] = "EndTime" + - from: swagger-document + where: $.definitions.ResourceIdList.properties.resourceids + transform: $["x-ms-client-name"] = "ResourceIDs" + - from: swagger-document + where: $.definitions.MetricResults.properties.values.items.properties.starttime + transform: $["x-ms-client-name"] = "StartTime" + - from: swagger-document + where: $.definitions.MetricResults.properties.values.items.properties.endtime + transform: $["x-ms-client-name"] = "EndTime" + - from: swagger-document + where: $.definitions.MetricResults.properties.values.items.properties.resourceid + transform: $["x-ms-client-name"] = "ResourceID" + - from: swagger-document + where: $.definitions.MetricResults.properties.values.items.properties.resourceregion + transform: $["x-ms-client-name"] = "ResourceRegion" + - from: swagger-document + where: $.definitions.MetricResults.properties.values.items.properties.value + transform: $["x-ms-client-name"] = "Values" + + # delete unused error models + - from: models.go + where: $ + transform: return $.replace(/((?:\/\/.*\s)+|)type AdditionalInfoErrorResponse.+\{(?:\s.+\s)+\}\s/g, ""); + - from: models_serde.go + where: $ + transform: return $.replace(/(?:\/\/.*\s)+func \(\w \*?AdditionalInfoErrorResponse.*\{\s(?:.+\s)+\}\s/g, ""); + ``` \ No newline at end of file diff --git a/sdk/monitor/azquery/custom_client.go b/sdk/monitor/azquery/custom_client.go index 2a6b30b078a4..0b30171f6c1d 100644 --- a/sdk/monitor/azquery/custom_client.go +++ b/sdk/monitor/azquery/custom_client.go @@ -32,6 +32,11 @@ type LogsClientOptions struct { azcore.ClientOptions } +// MetricsBatchClientOptions contains optional settings for MetricsBatchClient. +type MetricsBatchClientOptions struct { + azcore.ClientOptions +} + // NewLogsClient creates a client that accesses Azure Monitor logs data. func NewLogsClient(credential azcore.TokenCredential, options *LogsClientOptions) (*LogsClient, error) { if options == nil { @@ -74,6 +79,25 @@ func NewMetricsClient(credential azcore.TokenCredential, options *MetricsClientO return &MetricsClient{host: c.Endpoint, internal: azcoreClient}, nil } +// NewMetricsBatchClient creates a client that accesses Azure Monitor metrics data. +// MetricsBatchClient should be used for performing metrics queries on multiple monitored resources in the same region. +// A credential with authorization at the subscription level is required when using this client. +// +// endpoint - The regional endpoint to use, for example https://eastus.metrics.monitor.azure.com. +// The region should match the region of the requested resources. For global resources, the region should be 'global'. +func NewMetricsBatchClient(endpoint string, credential azcore.TokenCredential, options *MetricsBatchClientOptions) (*MetricsBatchClient, error) { + if options == nil { + options = &MetricsBatchClientOptions{} + } + + authPolicy := runtime.NewBearerTokenPolicy(credential, []string{"https://metrics.monitor.azure.com" + "/.default"}, nil) + azcoreClient, err := azcore.NewClient(moduleName, version, runtime.PipelineOptions{PerRetry: []policy.Policy{authPolicy}}, &options.ClientOptions) + if err != nil { + return nil, err + } + return &MetricsBatchClient{endpoint: endpoint, internal: azcoreClient}, nil +} + // ErrorInfo - The code and message for an error. type ErrorInfo struct { // REQUIRED; A machine readable error code. diff --git a/sdk/monitor/azquery/example_test.go b/sdk/monitor/azquery/example_test.go index fa9cf0703159..50eef2dc57f5 100644 --- a/sdk/monitor/azquery/example_test.go +++ b/sdk/monitor/azquery/example_test.go @@ -18,6 +18,7 @@ import ( var logsClient azquery.LogsClient var metricsClient azquery.MetricsClient +var metricsBatchClient azquery.MetricsBatchClient var kustoQuery1 string var kustoQuery2 string var kustoQuery3 string @@ -55,6 +56,23 @@ func ExampleNewMetricsClient() { _ = client } +func ExampleNewMetricsBatchClient() { + // The regional endpoint to use. The region should match the region of the requested resources. + // For global resources, the region should be 'global' + endpoint := "https://eastus.metrics.monitor.azure.com" + + cred, err := azidentity.NewDefaultAzureCredential(nil) + if err != nil { + //TODO: handle error + } + + client, err := azquery.NewMetricsBatchClient(endpoint, cred, nil) + if err != nil { + //TODO: handle error + } + _ = client +} + func ExampleLogsClient_QueryWorkspace() { // QueryWorkspace allows users to query log data. @@ -278,3 +296,42 @@ func ExampleMetricsClient_NewListNamespacesPager() { } } } + +func ExampleMetricsBatchClient_QueryBatch() { + // This sample uses the MetricsBatchClient to retrieve the "Ingress" + // metric along with the "Average" aggregation type for multiple resources. + // The query will execute over a timespan of 2 hours with a interval (granularity) of 5 minutes. + + // In this example, storage account resource URIs are queried for metrics. + resourceURI1 := "/subscriptions//resourceGroups//providers/Microsoft.Storage/storageAccounts/" + resourceURI2 := "/subscriptions//resourceGroups//providers/Microsoft.Storage/storageAccounts/" + + res, err := metricsBatchClient.QueryBatch( + context.Background(), + subscriptionID, + "Microsoft.Storage/storageAccounts", + []string{"Ingress"}, + azquery.ResourceIDList{ResourceIDs: to.SliceOfPtrs(resourceURI1, resourceURI2)}, + &azquery.MetricsBatchClientQueryBatchOptions{ + Aggregation: to.SliceOfPtrs(azquery.AggregationTypeAverage), + StartTime: to.Ptr("2023-11-15"), + EndTime: to.Ptr("2023-11-16"), + Interval: to.Ptr("PT5M"), + }, + ) + if err != nil { + //TODO: handle error + } + + // Print out results + for _, result := range res.Values { + for _, metric := range result.Values { + fmt.Println(*metric.Name.Value + ": " + *metric.DisplayDescription) + for _, timeSeriesElement := range metric.TimeSeries { + for _, metricValue := range timeSeriesElement.Data { + fmt.Printf("The ingress at %v is %v.\n", metricValue.TimeStamp.String(), *metricValue.Average) + } + } + } + } +} diff --git a/sdk/monitor/azquery/go.mod b/sdk/monitor/azquery/go.mod index 1f58d3cba297..a762555028a3 100644 --- a/sdk/monitor/azquery/go.mod +++ b/sdk/monitor/azquery/go.mod @@ -13,7 +13,7 @@ require ( github.com/AzureAD/microsoft-authentication-library-for-go v1.2.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/dnaeon/go-vcr v1.2.0 // indirect - github.com/golang-jwt/jwt/v5 v5.0.0 // indirect + github.com/golang-jwt/jwt/v5 v5.1.0 // indirect github.com/google/uuid v1.4.0 // indirect github.com/kylelemons/godebug v1.1.0 // indirect github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect diff --git a/sdk/monitor/azquery/go.sum b/sdk/monitor/azquery/go.sum index 721956b42d50..e0ffffcda425 100644 --- a/sdk/monitor/azquery/go.sum +++ b/sdk/monitor/azquery/go.sum @@ -10,8 +10,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI= github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ= -github.com/golang-jwt/jwt/v5 v5.0.0 h1:1n1XNM9hk7O9mnQoNBGolZvzebBQ7p93ULHRc28XJUE= -github.com/golang-jwt/jwt/v5 v5.0.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/golang-jwt/jwt/v5 v5.1.0 h1:UGKbA/IPjtS6zLcdB7i5TyACMgSbOTiR8qzXgw8HWQU= +github.com/golang-jwt/jwt/v5 v5.1.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= diff --git a/sdk/monitor/azquery/metricsbatch_client.go b/sdk/monitor/azquery/metricsbatch_client.go new file mode 100644 index 000000000000..c07b83f80d3e --- /dev/null +++ b/sdk/monitor/azquery/metricsbatch_client.go @@ -0,0 +1,110 @@ +//go:build go1.18 +// +build go1.18 + +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. +// Code generated by Microsoft (R) AutoRest Code Generator. DO NOT EDIT. +// Changes may cause incorrect behavior and will be lost if the code is regenerated. + +package azquery + +import ( + "context" + "errors" + "github.com/Azure/azure-sdk-for-go/sdk/azcore" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime" + "net/http" + "net/url" + "strconv" + "strings" +) + +// MetricsBatchClient contains the methods for the MetricsBatch group. +// Don't use this type directly, use a constructor function instead. +type MetricsBatchClient struct { + internal *azcore.Client + endpoint string +} + +// QueryBatch - Lists the metric values for multiple resources. +// If the operation fails it returns an *azcore.ResponseError type. +// +// Generated from API version 2023-05-01-preview +// - subscriptionID - The subscription identifier for the resources in this batch. +// - metricNamespace - Metric namespace that contains the requested metric names. +// - metricNames - The names of the metrics (comma separated) to retrieve. +// - resourceIDs - The comma separated list of resource IDs to query metrics for. +// - options - MetricsBatchClientQueryBatchOptions contains the optional parameters for the MetricsBatchClient.QueryBatch method. +func (client *MetricsBatchClient) QueryBatch(ctx context.Context, subscriptionID string, metricNamespace string, metricNames []string, resourceIDs ResourceIDList, options *MetricsBatchClientQueryBatchOptions) (MetricsBatchClientQueryBatchResponse, error) { + var err error + ctx, endSpan := runtime.StartSpan(ctx, "MetricsBatchClient.QueryBatch", client.internal.Tracer(), nil) + defer func() { endSpan(err) }() + req, err := client.queryBatchCreateRequest(ctx, subscriptionID, metricNamespace, metricNames, resourceIDs, options) + if err != nil { + return MetricsBatchClientQueryBatchResponse{}, err + } + httpResp, err := client.internal.Pipeline().Do(req) + if err != nil { + return MetricsBatchClientQueryBatchResponse{}, err + } + if !runtime.HasStatusCode(httpResp, http.StatusOK) { + err = runtime.NewResponseError(httpResp) + return MetricsBatchClientQueryBatchResponse{}, err + } + resp, err := client.queryBatchHandleResponse(httpResp) + return resp, err +} + +// queryBatchCreateRequest creates the QueryBatch request. +func (client *MetricsBatchClient) queryBatchCreateRequest(ctx context.Context, subscriptionID string, metricNamespace string, metricNames []string, resourceIDs ResourceIDList, options *MetricsBatchClientQueryBatchOptions) (*policy.Request, error) { + urlPath := "/subscriptions/{subscriptionId}/metrics:getBatch" + if subscriptionID == "" { + return nil, errors.New("parameter subscriptionID cannot be empty") + } + urlPath = strings.ReplaceAll(urlPath, "{subscriptionId}", url.PathEscape(subscriptionID)) + req, err := runtime.NewRequest(ctx, http.MethodPost, runtime.JoinPaths(client.endpoint, urlPath)) + if err != nil { + return nil, err + } + reqQP := req.Raw().URL.Query() + if options != nil && options.StartTime != nil { + reqQP.Set("starttime", *options.StartTime) + } + if options != nil && options.EndTime != nil { + reqQP.Set("endtime", *options.EndTime) + } + if options != nil && options.Interval != nil { + reqQP.Set("interval", *options.Interval) + } + reqQP.Set("metricnamespace", metricNamespace) + reqQP.Set("metricnames", strings.Join(metricNames, ",")) + if options != nil && options.Aggregation != nil { + reqQP.Set("aggregation", aggregationTypeToString(options.Aggregation)) + } + if options != nil && options.Top != nil { + reqQP.Set("top", strconv.FormatInt(int64(*options.Top), 10)) + } + if options != nil && options.OrderBy != nil { + reqQP.Set("orderby", *options.OrderBy) + } + if options != nil && options.Filter != nil { + reqQP.Set("filter", *options.Filter) + } + reqQP.Set("api-version", "2023-05-01-preview") + req.Raw().URL.RawQuery = reqQP.Encode() + req.Raw().Header["Accept"] = []string{"application/json"} + if err := runtime.MarshalAsJSON(req, resourceIDs); err != nil { + return nil, err + } + return req, nil +} + +// queryBatchHandleResponse handles the QueryBatch response. +func (client *MetricsBatchClient) queryBatchHandleResponse(resp *http.Response) (MetricsBatchClientQueryBatchResponse, error) { + result := MetricsBatchClientQueryBatchResponse{} + if err := runtime.UnmarshalAsJSON(resp, &result.MetricResults); err != nil { + return MetricsBatchClientQueryBatchResponse{}, err + } + return result, nil +} diff --git a/sdk/monitor/azquery/metricsbatch_client_test.go b/sdk/monitor/azquery/metricsbatch_client_test.go new file mode 100644 index 000000000000..49aa8eb67990 --- /dev/null +++ b/sdk/monitor/azquery/metricsbatch_client_test.go @@ -0,0 +1,72 @@ +//go:build go1.18 +// +build go1.18 + +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +package azquery_test + +import ( + "context" + "testing" + + "github.com/Azure/azure-sdk-for-go/sdk/azcore" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/to" + "github.com/Azure/azure-sdk-for-go/sdk/monitor/azquery" + "github.com/stretchr/testify/require" +) + +func TestQueryBatch_Metrics(t *testing.T) { + client := startMetricsBatchTest(t) + metricName := "HttpIncomingRequestCount" + resourceIDList := azquery.ResourceIDList{ResourceIDs: to.SliceOfPtrs(resourceURI)} + + res, err := client.QueryBatch( + context.Background(), + subscriptionID, + "Microsoft.AppConfiguration/configurationStores", + []string{"HttpIncomingRequestCount"}, + resourceIDList, + &azquery.MetricsBatchClientQueryBatchOptions{ + Aggregation: to.SliceOfPtrs(azquery.AggregationTypeAverage), + StartTime: to.Ptr("2023-11-15"), + EndTime: to.Ptr("2023-11-16"), + Interval: to.Ptr("PT1H"), + }, + ) + require.NoError(t, err) + require.NotNil(t, res) + + for _, resource := range res.Values { + for _, metric := range resource.Values { + require.Equal(t, metricName, *metric.Name.Value) + for _, timeSeriesElement := range metric.TimeSeries { + require.NotNil(t, timeSeriesElement) + } + } + } + + testSerde(t, &res) + testSerde(t, &resourceIDList) +} + +func TestQueryBatch_MetricsFailure(t *testing.T) { + client := startMetricsBatchTest(t) + + res, err := client.QueryBatch( + context.Background(), + "fakesubscriptionID", + "Microsoft.AppConfiguration/configurationStores", + []string{"HttpIncomingRequestCount"}, + azquery.ResourceIDList{ResourceIDs: to.SliceOfPtrs(resourceURI)}, + nil, + ) + require.Error(t, err) + var httpErr *azcore.ResponseError + require.ErrorAs(t, err, &httpErr) + require.Equal(t, httpErr.ErrorCode, "InvalidSubscriptionId") + require.Equal(t, httpErr.StatusCode, 400) + require.Nil(t, res.Values) + + testSerde(t, &res) +} diff --git a/sdk/monitor/azquery/models.go b/sdk/monitor/azquery/models.go index 55c6c3108295..475b64d26f52 100644 --- a/sdk/monitor/azquery/models.go +++ b/sdk/monitor/azquery/models.go @@ -93,19 +93,19 @@ type Column struct { // LocalizableString - The localizable string class. type LocalizableString struct { - // REQUIRED; the invariant value. + // REQUIRED; The invariant value. Value *string - // the locale specific value. + // The display name. LocalizedValue *string } // MetadataValue - Represents a metric metadata value. type MetadataValue struct { - // the name of the metadata. + // The name of the metadata. Name *LocalizableString - // the value of the metadata. + // The value of the metadata. Value *string } @@ -224,28 +224,66 @@ type MetricNamespaceName struct { MetricNamespaceName *string } +// MetricResults - The metrics result for a resource. +type MetricResults struct { + // The collection of metric data responses per resource, per metric. + Values []*MetricValues +} + +// MetricValues - Metric data values. +type MetricValues struct { + // REQUIRED; The end time, in datetime format, for which the data was retrieved. + EndTime *string + + // REQUIRED; The start time, in datetime format, for which the data was retrieved. + StartTime *string + + // REQUIRED; The value of the collection. + Values []*Metric + + // The interval (window size) for which the metric data was returned in. Follows the IS8601/RFC3339 duration format (e.g. + // 'P1D' for 1 day). This may be adjusted in the future and returned back from what + // was originally requested. This is not present if a metadata request was made. + Interval *string + + // The namespace of the metrics been queried + Namespace *string + + // The resource that has been queried for metrics. + ResourceID *string + + // The region of the resource been queried for metrics. + ResourceRegion *string +} + // MetricValue - Represents a metric value. type MetricValue struct { - // REQUIRED; the timestamp for the metric value in ISO 8601 format. + // REQUIRED; The timestamp for the metric value in ISO 8601 format. TimeStamp *time.Time - // the average value in the time range. + // The average value in the time range. Average *float64 - // the number of samples in the time range. Can be used to determine the number of values that contributed to the average + // The number of samples in the time range. Can be used to determine the number of values that contributed to the average // value. Count *float64 - // the greatest value in the time range. + // The greatest value in the time range. Maximum *float64 - // the least value in the time range. + // The least value in the time range. Minimum *float64 - // the sum of all of the values in the time range. + // The sum of all of the values in the time range. Total *float64 } +// ResourceIDList - The comma separated list of resource IDs to query metrics for. +type ResourceIDList struct { + // The list of resource IDs to query metrics for. + ResourceIDs []*string +} + // Response - The response to a metrics query. type Response struct { // REQUIRED; The timespan for which the data was retrieved. Its value consists of two datetimes concatenated, separated by @@ -259,9 +297,10 @@ type Response struct { // The integer value representing the relative cost of the query. Cost *int32 - // The interval (window size) for which the metric data was returned in. This may be adjusted in the future and returned back - // from what was originally requested. This is not present if a metadata request - // was made. + // The interval (window size) for which the metric data was returned in ISO 8601 duration format with a special case for 'FULL' + // value that returns single datapoint for entire time span requested ( + // Examples: PT15M, PT1H, P1D, FULL). This may be adjusted and different from what was originally requested if AutoAdjustTimegrain=true + // is specified. This is not present if a metadata request was made. Interval *string // The namespace of the metrics being queried diff --git a/sdk/monitor/azquery/models_serde.go b/sdk/monitor/azquery/models_serde.go index 3b00b8c76f4f..894aec66d473 100644 --- a/sdk/monitor/azquery/models_serde.go +++ b/sdk/monitor/azquery/models_serde.go @@ -614,6 +614,84 @@ func (m *MetricNamespaceName) UnmarshalJSON(data []byte) error { return nil } +// MarshalJSON implements the json.Marshaller interface for type MetricResults. +func (m MetricResults) MarshalJSON() ([]byte, error) { + objectMap := make(map[string]any) + populate(objectMap, "values", m.Values) + return json.Marshal(objectMap) +} + +// UnmarshalJSON implements the json.Unmarshaller interface for type MetricResults. +func (m *MetricResults) UnmarshalJSON(data []byte) error { + var rawMsg map[string]json.RawMessage + if err := json.Unmarshal(data, &rawMsg); err != nil { + return fmt.Errorf("unmarshalling type %T: %v", m, err) + } + for key, val := range rawMsg { + var err error + switch key { + case "values": + err = unpopulate(val, "Values", &m.Values) + delete(rawMsg, key) + } + if err != nil { + return fmt.Errorf("unmarshalling type %T: %v", m, err) + } + } + return nil +} + +// MarshalJSON implements the json.Marshaller interface for type MetricValues. +func (m MetricValues) MarshalJSON() ([]byte, error) { + objectMap := make(map[string]any) + populate(objectMap, "endtime", m.EndTime) + populate(objectMap, "interval", m.Interval) + populate(objectMap, "namespace", m.Namespace) + populate(objectMap, "resourceid", m.ResourceID) + populate(objectMap, "resourceregion", m.ResourceRegion) + populate(objectMap, "starttime", m.StartTime) + populate(objectMap, "value", m.Values) + return json.Marshal(objectMap) +} + +// UnmarshalJSON implements the json.Unmarshaller interface for type MetricValues. +func (m *MetricValues) UnmarshalJSON(data []byte) error { + var rawMsg map[string]json.RawMessage + if err := json.Unmarshal(data, &rawMsg); err != nil { + return fmt.Errorf("unmarshalling type %T: %v", m, err) + } + for key, val := range rawMsg { + var err error + switch key { + case "endtime": + err = unpopulate(val, "EndTime", &m.EndTime) + delete(rawMsg, key) + case "interval": + err = unpopulate(val, "Interval", &m.Interval) + delete(rawMsg, key) + case "namespace": + err = unpopulate(val, "Namespace", &m.Namespace) + delete(rawMsg, key) + case "resourceid": + err = unpopulate(val, "ResourceID", &m.ResourceID) + delete(rawMsg, key) + case "resourceregion": + err = unpopulate(val, "ResourceRegion", &m.ResourceRegion) + delete(rawMsg, key) + case "starttime": + err = unpopulate(val, "StartTime", &m.StartTime) + delete(rawMsg, key) + case "value": + err = unpopulate(val, "Values", &m.Values) + delete(rawMsg, key) + } + if err != nil { + return fmt.Errorf("unmarshalling type %T: %v", m, err) + } + } + return nil +} + // MarshalJSON implements the json.Marshaller interface for type MetricValue. func (m MetricValue) MarshalJSON() ([]byte, error) { objectMap := make(map[string]any) @@ -661,6 +739,33 @@ func (m *MetricValue) UnmarshalJSON(data []byte) error { return nil } +// MarshalJSON implements the json.Marshaller interface for type ResourceIDList. +func (r ResourceIDList) MarshalJSON() ([]byte, error) { + objectMap := make(map[string]any) + populate(objectMap, "resourceids", r.ResourceIDs) + return json.Marshal(objectMap) +} + +// UnmarshalJSON implements the json.Unmarshaller interface for type ResourceIDList. +func (r *ResourceIDList) UnmarshalJSON(data []byte) error { + var rawMsg map[string]json.RawMessage + if err := json.Unmarshal(data, &rawMsg); err != nil { + return fmt.Errorf("unmarshalling type %T: %v", r, err) + } + for key, val := range rawMsg { + var err error + switch key { + case "resourceids": + err = unpopulate(val, "ResourceIDs", &r.ResourceIDs) + delete(rawMsg, key) + } + if err != nil { + return fmt.Errorf("unmarshalling type %T: %v", r, err) + } + } + return nil +} + // MarshalJSON implements the json.Marshaller interface for type Response. func (r Response) MarshalJSON() ([]byte, error) { objectMap := make(map[string]any) diff --git a/sdk/monitor/azquery/options.go b/sdk/monitor/azquery/options.go index e97ef8bdf7cd..d8638ca5bc61 100644 --- a/sdk/monitor/azquery/options.go +++ b/sdk/monitor/azquery/options.go @@ -25,6 +25,45 @@ type LogsClientQueryWorkspaceOptions struct { Options *LogsQueryOptions } +// MetricsBatchClientQueryBatchOptions contains the optional parameters for the MetricsBatchClient.QueryBatch method. +type MetricsBatchClientQueryBatchOptions struct { + // The list of aggregation types to retrieve + Aggregation []*AggregationType + + // The end time of the query. It is a string in the format 'yyyy-MM-ddTHH:mm:ss.fffZ'. + EndTime *string + + // The filter is used to reduce the set of metric data returned. + // Example: + // Metric contains metadata A, B and C. + // - Return all time series of C where A = a1 and B = b1 or b2 + // filter=A eq ‘a1’ and B eq ‘b1’ or B eq ‘b2’ and C eq ‘’ + // - Invalid variant: + // filter=A eq ‘a1’ and B eq ‘b1’ and C eq ‘’ or B = ‘b2’ + // This is invalid because the logical or operator cannot separate two different metadata names. + // - Return all time series where A = a1, B = b1 and C = c1: + // filter=A eq ‘a1’ and B eq ‘b1’ and C eq ‘c1’ + // - Return all time series where A = a1 + // filter=A eq ‘a1’ and B eq ‘’ and C eq ‘’. + Filter *string + + // The interval (i.e. timegrain) of the query.Examples: PT15M, PT1H, P1D + Interval *string + + // The aggregation to use for sorting results and the direction of the sort. Only one order can be specified.Examples: sum + // asc + OrderBy *string + + // The start time of the query. It is a string in the format 'yyyy-MM-ddTHH:mm:ss.fffZ'. If you have specified the endtime + // parameter, then this parameter is required. If only starttime is specified, then + // endtime defaults to the current time. If no time interval is specified, the default is 1 hour. + StartTime *string + + // The maximum number of records to retrieve per resource ID in the request. Valid only if filter is specified. Defaults to + // 10. + Top *int32 +} + // MetricsClientListDefinitionsOptions contains the optional parameters for the MetricsClient.NewListDefinitionsPager method. type MetricsClientListDefinitionsOptions struct { // Metric namespace to query metric definitions for. @@ -54,7 +93,9 @@ type MetricsClientQueryResourceOptions struct { // (test) val' " use $filter= "dim %2528test%2529 3 eq 'dim3 %2528test%2529 val' " Filter *string - // The interval (i.e. timegrain) of the query. + // The interval (i.e. timegrain) of the query in ISO 8601 duration format. Defaults to PT1M. Special case for 'FULL' value + // that returns single datapoint for entire time span requested.Examples: PT15M, + // PT1H, P1D, FULL Interval *string // The names of the metrics (comma separated) to retrieve. Special case: If a metricname itself has a comma in it then use diff --git a/sdk/monitor/azquery/response_types.go b/sdk/monitor/azquery/response_types.go index 847c272868e7..caae94df0d74 100644 --- a/sdk/monitor/azquery/response_types.go +++ b/sdk/monitor/azquery/response_types.go @@ -26,6 +26,12 @@ type LogsClientQueryWorkspaceResponse struct { Results } +// MetricsBatchClientQueryBatchResponse contains the response from method MetricsBatchClient.QueryBatch. +type MetricsBatchClientQueryBatchResponse struct { + // The metrics result for a resource. + MetricResults +} + // MetricsClientListDefinitionsResponse contains the response from method MetricsClient.NewListDefinitionsPager. type MetricsClientListDefinitionsResponse struct { // Represents collection of metric definitions. diff --git a/sdk/monitor/azquery/utils_test.go b/sdk/monitor/azquery/utils_test.go index 84d6bb677e4b..5792c3f79cf0 100644 --- a/sdk/monitor/azquery/utils_test.go +++ b/sdk/monitor/azquery/utils_test.go @@ -30,13 +30,17 @@ const recordingDirectory = "sdk/monitor/azquery/testdata" const fakeWorkspaceID = "32d1e136-gg81-4b0a-b647-260cdc471f68" const fakeWorkspaceID2 = "asdjkfj8k20-gg81-4b0a-9fu2-260c09fn1f68" const fakeResourceURI = "/subscriptions/faa080af-c1d8-40ad-9cce-e1a451va7b87/resourceGroups/rg-example/providers/Microsoft.AppConfiguration/configurationStores/example" +const fakeSubscrtiptionID = "faa080af-c1d8-40ad-9cce-e1a451va7b87" +const fakeRegion = "westus2" var ( - credential azcore.TokenCredential - workspaceID string - workspaceID2 string - resourceURI string - clientCloud cloud.Configuration + credential azcore.TokenCredential + workspaceID string + workspaceID2 string + resourceURI string + subscriptionID string + region string + clientCloud cloud.Configuration ) func TestMain(m *testing.M) { @@ -83,6 +87,8 @@ func run(m *testing.M) int { workspaceID = getEnvVar("WORKSPACE_ID", fakeWorkspaceID) workspaceID2 = getEnvVar("WORKSPACE_ID2", fakeWorkspaceID2) resourceURI = getEnvVar("RESOURCE_URI", fakeResourceURI) + subscriptionID = getEnvVar("AZQUERY_SUBSCRIPTION_ID", fakeSubscrtiptionID) + region = getEnvVar("AZQUERY_LOCATION", fakeRegion) return m.Run() } @@ -133,6 +139,19 @@ func startMetricsTest(t *testing.T) *azquery.MetricsClient { return client } +func startMetricsBatchTest(t *testing.T) *azquery.MetricsBatchClient { + startRecording(t) + transport, err := recording.NewRecordingHTTPClient(t, nil) + require.NoError(t, err) + opts := &azquery.MetricsBatchClientOptions{ClientOptions: azcore.ClientOptions{Transport: transport}} + endpoint := "https://" + region + ".metrics.monitor.azure.com" + client, err := azquery.NewMetricsBatchClient(endpoint, credential, opts) + if err != nil { + panic(err) + } + return client +} + func getEnvVar(envVar string, fakeValue string) string { // get value value := fakeValue diff --git a/sdk/monitor/azquery/version.go b/sdk/monitor/azquery/version.go index 9f3daf91f432..76d81dd89cd4 100644 --- a/sdk/monitor/azquery/version.go +++ b/sdk/monitor/azquery/version.go @@ -8,5 +8,5 @@ package azquery const ( moduleName = "github.com/Azure/azure-sdk-for-go/sdk/monitor/azquery" - version = "v1.1.1" + version = "v1.2.0-beta.1" )