2020-06-30 18:47:13 +03:00
package cloudmonitoring
2018-09-04 13:21:58 +02:00
import (
"context"
"encoding/json"
"fmt"
2021-01-18 21:48:43 +09:00
"io"
2018-09-17 16:30:06 +02:00
"math"
2018-09-04 13:21:58 +02:00
"net/http"
"regexp"
2018-09-17 16:30:06 +02:00
"strconv"
2018-09-14 16:20:51 +02:00
"strings"
2018-09-10 14:49:41 +02:00
"time"
2018-09-04 13:21:58 +02:00
2021-11-10 08:58:04 -05:00
"github.com/grafana/grafana-google-sdk-go/pkg/utils"
2022-11-30 17:23:05 +01:00
"github.com/huandu/xstrings"
2022-01-20 18:16:22 +01:00
2021-10-08 14:46:35 +02:00
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana-plugin-sdk-go/backend/datasource"
2024-01-29 09:24:23 -07:00
"github.com/grafana/grafana-plugin-sdk-go/backend/httpclient"
2021-10-08 14:46:35 +02:00
"github.com/grafana/grafana-plugin-sdk-go/backend/instancemgmt"
2024-01-29 09:24:23 -07:00
"github.com/grafana/grafana-plugin-sdk-go/backend/log"
2021-11-02 10:37:02 -04:00
"github.com/grafana/grafana-plugin-sdk-go/backend/resource/httpadapter"
2021-01-14 14:16:20 +01:00
"github.com/grafana/grafana-plugin-sdk-go/data"
2021-10-08 14:46:35 +02:00
2023-07-31 11:14:27 -06:00
"github.com/grafana/grafana/pkg/tsdb/cloud-monitoring/kinds/dataquery"
2018-09-04 13:21:58 +02:00
)
2020-01-17 12:25:47 +01:00
var (
2021-01-14 14:16:20 +01:00
legendKeyFormat = regexp . MustCompile ( ` \ { \ { \s*(.+?)\s*\}\} ` )
metricNameFormat = regexp . MustCompile ( ` ([\w\d_]+)\.(googleapis\.com|io)/(.+) ` )
wildcardRegexRe = regexp . MustCompile ( ` [-\/^$+?.()|[\] { }] ` )
cloudMonitoringUnitMappings = map [ string ] string {
"bit" : "bits" ,
"By" : "bytes" ,
"s" : "s" ,
"min" : "m" ,
"h" : "h" ,
"d" : "d" ,
"us" : "µs" ,
"ms" : "ms" ,
"ns" : "ns" ,
2021-04-14 16:41:02 +02:00
"%" : "percent" ,
2021-01-14 14:16:20 +01:00
"percent" : "percent" ,
"MiBy" : "mbytes" ,
"By/s" : "Bps" ,
"GBy" : "decgbytes" ,
}
2018-10-10 14:17:03 +02:00
)
const (
2022-01-31 12:39:55 -03:00
gceAuthentication = "gce"
jwtAuthentication = "jwt"
2025-01-14 15:58:38 +01:00
annotationQueryType = dataquery . QueryTypeANNOTATION
timeSeriesListQueryType = dataquery . QueryTypeTIMESERIESLIST
timeSeriesQueryQueryType = dataquery . QueryTypeTIMESERIESQUERY
sloQueryType = dataquery . QueryTypeSLO
promQLQueryType = dataquery . QueryTypePROMQL
2022-01-31 12:39:55 -03:00
crossSeriesReducerDefault = "REDUCE_NONE"
perSeriesAlignerDefault = "ALIGN_MEAN"
2018-09-23 22:04:24 +02:00
)
2018-09-11 15:52:37 +02:00
2024-01-29 09:24:23 -07:00
func ProvideService ( httpClientProvider * httpclient . Provider ) * Service {
2021-10-08 14:46:35 +02:00
s := & Service {
2024-01-29 09:24:23 -07:00
httpClientProvider : * httpClientProvider ,
im : datasource . NewInstanceManager ( newInstanceSettings ( * httpClientProvider ) ) ,
logger : backend . NewLoggerWith ( "logger" , "tsdb.cloudmonitoring" ) ,
2022-03-16 01:37:48 -07:00
gceDefaultProjectGetter : utils . GCEDefaultProject ,
2021-08-25 15:11:22 +02:00
}
2021-10-08 14:46:35 +02:00
2022-01-20 18:16:22 +01:00
s . resourceHandler = httpadapter . New ( s . newResourceMux ( ) )
2021-10-08 14:46:35 +02:00
return s
2021-03-08 07:02:49 +01:00
}
2022-01-20 18:16:22 +01:00
func ( s * Service ) CallResource ( ctx context . Context , req * backend . CallResourceRequest , sender backend . CallResourceResponseSender ) error {
return s . resourceHandler . CallResource ( ctx , req , sender )
}
2021-11-10 08:58:04 -05:00
func ( s * Service ) CheckHealth ( ctx context . Context , req * backend . CheckHealthRequest ) ( * backend . CheckHealthResult , error ) {
2023-05-24 10:19:34 +02:00
dsInfo , err := s . getDSInfo ( ctx , req . PluginContext )
2021-11-10 08:58:04 -05:00
if err != nil {
return nil , err
}
defaultProject , err := s . getDefaultProject ( ctx , * dsInfo )
if err != nil {
2022-03-16 01:37:48 -07:00
return & backend . CheckHealthResult {
Status : backend . HealthStatusError ,
Message : err . Error ( ) ,
} , nil
2021-11-10 08:58:04 -05:00
}
url := fmt . Sprintf ( "%v/v3/projects/%v/metricDescriptors" , dsInfo . services [ cloudMonitor ] . url , defaultProject )
request , err := http . NewRequest ( http . MethodGet , url , nil )
if err != nil {
return nil , err
}
res , err := dsInfo . services [ cloudMonitor ] . client . Do ( request )
if err != nil {
return nil , err
}
defer func ( ) {
if err := res . Body . Close ( ) ; err != nil {
2024-01-29 09:24:23 -07:00
s . logger . Warn ( "Failed to close response body" , "err" , err )
2021-11-10 08:58:04 -05:00
}
} ( )
status := backend . HealthStatusOk
message := "Successfully queried the Google Cloud Monitoring API."
if res . StatusCode != 200 {
status = backend . HealthStatusError
message = res . Status
}
return & backend . CheckHealthResult {
Status : status ,
Message : message ,
} , nil
}
2021-03-08 07:02:49 +01:00
type Service struct {
2021-11-01 09:53:33 +00:00
httpClientProvider httpclient . Provider
im instancemgmt . InstanceManager
2024-01-29 09:24:23 -07:00
logger log . Logger
2022-01-20 18:16:22 +01:00
resourceHandler backend . CallResourceHandler
2022-03-16 01:37:48 -07:00
// mocked in tests
2023-03-29 12:16:28 +02:00
gceDefaultProjectGetter func ( ctx context . Context , scope string ) ( string , error )
2021-03-08 07:02:49 +01:00
}
2021-10-08 14:46:35 +02:00
type datasourceInfo struct {
id int64
updated time . Time
url string
authenticationType string
defaultProject string
2021-10-26 10:17:12 -04:00
clientEmail string
tokenUri string
2021-11-02 10:37:02 -04:00
services map [ string ] datasourceService
2023-03-29 12:16:28 +02:00
privateKey string
2021-10-08 14:46:35 +02:00
}
2022-11-09 09:38:06 +01:00
type datasourceJSONData struct {
AuthenticationType string ` json:"authenticationType" `
DefaultProject string ` json:"defaultProject" `
ClientEmail string ` json:"clientEmail" `
TokenURI string ` json:"tokenUri" `
}
2021-11-02 10:37:02 -04:00
type datasourceService struct {
url string
client * http . Client
}
2021-10-08 14:46:35 +02:00
func newInstanceSettings ( httpClientProvider httpclient . Provider ) datasource . InstanceFactoryFunc {
2023-10-16 16:40:04 +02:00
return func ( ctx context . Context , settings backend . DataSourceInstanceSettings ) ( instancemgmt . Instance , error ) {
2022-11-09 09:38:06 +01:00
var jsonData datasourceJSONData
2021-10-08 14:46:35 +02:00
err := json . Unmarshal ( settings . JSONData , & jsonData )
if err != nil {
return nil , fmt . Errorf ( "error reading settings: %w" , err )
}
2022-11-09 09:38:06 +01:00
if jsonData . AuthenticationType == "" {
jsonData . AuthenticationType = jwtAuthentication
2021-10-26 10:17:12 -04:00
}
dsInfo := & datasourceInfo {
2023-03-29 12:16:28 +02:00
id : settings . ID ,
updated : settings . Updated ,
url : settings . URL ,
authenticationType : jsonData . AuthenticationType ,
defaultProject : jsonData . DefaultProject ,
clientEmail : jsonData . ClientEmail ,
tokenUri : jsonData . TokenURI ,
services : map [ string ] datasourceService { } ,
}
dsInfo . privateKey , err = utils . GetPrivateKey ( & settings )
if err != nil {
return nil , err
2021-10-26 10:17:12 -04:00
}
2023-10-16 16:40:04 +02:00
opts , err := settings . HTTPClientOptions ( ctx )
2021-10-26 10:17:12 -04:00
if err != nil {
return nil , err
}
2021-11-02 10:37:02 -04:00
for name , info := range routes {
2024-01-29 09:24:23 -07:00
client , err := newHTTPClient ( dsInfo , opts , & httpClientProvider , name )
2021-11-02 10:37:02 -04:00
if err != nil {
return nil , err
}
dsInfo . services [ name ] = datasourceService {
url : info . url ,
client : client ,
}
2021-10-26 10:17:12 -04:00
}
return dsInfo , nil
2021-10-08 14:46:35 +02:00
}
2018-09-04 13:21:58 +02:00
}
2023-08-30 08:46:47 -07:00
func migrateMetricTypeFilter ( metricTypeFilter string , prevFilters any ) [ ] string {
2022-11-29 12:39:45 +01:00
metricTypeFilterArray := [ ] string { "metric.type" , "=" , metricTypeFilter }
if prevFilters != nil {
2023-08-30 08:46:47 -07:00
filtersIface := prevFilters . ( [ ] any )
2022-11-29 12:39:45 +01:00
filters := [ ] string { }
for _ , f := range filtersIface {
filters = append ( filters , f . ( string ) )
}
metricTypeFilterArray = append ( [ ] string { "AND" } , metricTypeFilterArray ... )
return append ( filters , metricTypeFilterArray ... )
}
return metricTypeFilterArray
}
2022-11-10 09:30:47 +01:00
func migrateRequest ( req * backend . QueryDataRequest ) error {
for i , q := range req . Queries {
2023-08-30 08:46:47 -07:00
var rawQuery map [ string ] any
2022-11-10 09:30:47 +01:00
err := json . Unmarshal ( q . JSON , & rawQuery )
if err != nil {
return err
}
2022-12-20 12:47:49 +01:00
if rawQuery [ "metricQuery" ] == nil &&
rawQuery [ "timeSeriesQuery" ] == nil &&
rawQuery [ "timeSeriesList" ] == nil &&
rawQuery [ "sloQuery" ] == nil {
2022-11-10 09:30:47 +01:00
// migrate legacy query
2023-07-31 11:14:27 -06:00
var mq dataquery . TimeSeriesList
2022-11-10 09:30:47 +01:00
err = json . Unmarshal ( q . JSON , & mq )
if err != nil {
return err
}
2025-01-14 15:58:38 +01:00
q . QueryType = string ( dataquery . QueryTypeTIMESERIESLIST )
2022-11-28 09:17:01 +01:00
gq := grafanaQuery {
TimeSeriesList : & mq ,
}
if rawQuery [ "aliasBy" ] != nil {
gq . AliasBy = rawQuery [ "aliasBy" ] . ( string )
}
2022-11-29 12:39:45 +01:00
if rawQuery [ "metricType" ] != nil {
// metricType should be a filter
gq . TimeSeriesList . Filters = migrateMetricTypeFilter ( rawQuery [ "metricType" ] . ( string ) , rawQuery [ "filters" ] )
}
2022-11-10 09:30:47 +01:00
2022-11-28 09:17:01 +01:00
b , err := json . Marshal ( gq )
2022-11-10 09:30:47 +01:00
if err != nil {
return err
}
q . JSON = b
}
// Migrate type to queryType, which is only used for annotations
if rawQuery [ "type" ] != nil && rawQuery [ "type" ] . ( string ) == "annotationQuery" {
2025-01-14 15:58:38 +01:00
q . QueryType = string ( dataquery . QueryTypeANNOTATION )
2022-11-10 09:30:47 +01:00
}
2022-11-28 09:17:01 +01:00
if rawQuery [ "queryType" ] != nil {
q . QueryType = rawQuery [ "queryType" ] . ( string )
}
// Metric query was divided between timeSeriesList and timeSeriesQuery API calls
2022-12-20 12:47:49 +01:00
if rawQuery [ "metricQuery" ] != nil && q . QueryType == "metrics" {
2023-08-30 08:46:47 -07:00
metricQuery := rawQuery [ "metricQuery" ] . ( map [ string ] any )
2022-11-28 09:17:01 +01:00
if metricQuery [ "editorMode" ] != nil && toString ( metricQuery [ "editorMode" ] ) == "mql" {
2023-07-31 11:14:27 -06:00
rawQuery [ "timeSeriesQuery" ] = & dataquery . TimeSeriesQuery {
2022-11-28 09:17:01 +01:00
ProjectName : toString ( metricQuery [ "projectName" ] ) ,
Query : toString ( metricQuery [ "query" ] ) ,
2025-01-14 15:58:38 +01:00
GraphPeriod : toString ( metricQuery [ "graphPeriod" ] ) ,
2022-11-28 09:17:01 +01:00
}
2025-01-14 15:58:38 +01:00
q . QueryType = string ( dataquery . QueryTypeTIMESERIESQUERY )
2022-11-28 09:17:01 +01:00
} else {
2022-11-29 12:39:45 +01:00
tslb , err := json . Marshal ( metricQuery )
if err != nil {
return err
}
2023-07-31 11:14:27 -06:00
tsl := & dataquery . TimeSeriesList { }
2022-11-29 12:39:45 +01:00
err = json . Unmarshal ( tslb , tsl )
if err != nil {
return err
}
if metricQuery [ "metricType" ] != nil {
// metricType should be a filter
tsl . Filters = migrateMetricTypeFilter ( metricQuery [ "metricType" ] . ( string ) , metricQuery [ "filters" ] )
}
rawQuery [ "timeSeriesList" ] = tsl
2025-01-14 15:58:38 +01:00
q . QueryType = string ( dataquery . QueryTypeTIMESERIESLIST )
2022-11-28 09:17:01 +01:00
}
2022-12-20 12:47:49 +01:00
// AliasBy is now a top level property
2022-11-28 09:17:01 +01:00
if metricQuery [ "aliasBy" ] != nil {
rawQuery [ "aliasBy" ] = metricQuery [ "aliasBy" ]
}
b , err := json . Marshal ( rawQuery )
if err != nil {
return err
}
q . JSON = b
}
2025-01-14 15:58:38 +01:00
if rawQuery [ "sloQuery" ] != nil && q . QueryType == string ( dataquery . QueryTypeSLO ) {
2023-08-30 08:46:47 -07:00
sloQuery := rawQuery [ "sloQuery" ] . ( map [ string ] any )
2022-12-20 12:47:49 +01:00
// AliasBy is now a top level property
if sloQuery [ "aliasBy" ] != nil {
rawQuery [ "aliasBy" ] = sloQuery [ "aliasBy" ]
b , err := json . Marshal ( rawQuery )
if err != nil {
return err
}
q . JSON = b
}
}
2022-11-10 09:30:47 +01:00
req . Queries [ i ] = q
}
return nil
}
2021-11-01 09:53:33 +00:00
// QueryData takes in the frontend queries, parses them into the CloudMonitoring query format
// executes the queries against the CloudMonitoring API and parses the response into data frames
2021-10-08 14:46:35 +02:00
func ( s * Service ) QueryData ( ctx context . Context , req * backend . QueryDataRequest ) ( * backend . QueryDataResponse , error ) {
2024-01-29 09:24:23 -07:00
logger := s . logger . FromContext ( ctx )
2021-10-08 14:46:35 +02:00
if len ( req . Queries ) == 0 {
2022-11-10 09:30:47 +01:00
return nil , fmt . Errorf ( "query contains no queries" )
2021-10-08 14:46:35 +02:00
}
2022-11-10 09:30:47 +01:00
err := migrateRequest ( req )
2021-10-08 14:46:35 +02:00
if err != nil {
2022-11-10 09:30:47 +01:00
return nil , err
2021-10-08 14:46:35 +02:00
}
2023-05-24 10:19:34 +02:00
dsInfo , err := s . getDSInfo ( ctx , req . PluginContext )
2021-10-08 14:46:35 +02:00
if err != nil {
return nil , err
}
2024-09-10 19:34:20 +01:00
// There aren't any possible downstream errors here
2022-11-10 09:30:47 +01:00
queries , err := s . buildQueryExecutors ( logger , req )
if err != nil {
return nil , err
2018-09-24 16:02:35 +02:00
}
2022-11-10 09:30:47 +01:00
switch req . Queries [ 0 ] . QueryType {
2025-01-14 15:58:38 +01:00
case string ( dataquery . QueryTypeANNOTATION ) :
2024-01-29 09:24:23 -07:00
return s . executeAnnotationQuery ( ctx , req , * dsInfo , queries , logger )
2022-11-10 09:30:47 +01:00
default :
2024-01-29 09:24:23 -07:00
return s . executeTimeSeriesQuery ( ctx , req , * dsInfo , queries , logger )
2022-11-10 09:30:47 +01:00
}
2018-09-24 16:02:35 +02:00
}
2024-01-29 09:24:23 -07:00
func ( s * Service ) executeTimeSeriesQuery ( ctx context . Context , req * backend . QueryDataRequest , dsInfo datasourceInfo , queries [ ] cloudMonitoringQueryExecutor , logger log . Logger ) (
2021-10-08 14:46:35 +02:00
* backend . QueryDataResponse , error ) {
resp := backend . NewQueryDataResponse ( )
2022-11-10 09:30:47 +01:00
for _ , queryExecutor := range queries {
2024-09-20 10:40:05 +01:00
dr , queryRes , executedQueryString , err := queryExecutor . run ( ctx , req , s , dsInfo , logger )
2018-09-11 14:06:46 +02:00
if err != nil {
2024-12-06 16:38:09 +01:00
resp . Responses [ queryExecutor . getRefID ( ) ] = backend . ErrorResponseWithErrorSource ( err )
2021-10-08 14:46:35 +02:00
return resp , err
2018-09-11 14:06:46 +02:00
}
2024-09-20 10:40:05 +01:00
err = queryExecutor . parseResponse ( dr , queryRes , executedQueryString , logger )
2021-01-14 14:16:20 +01:00
if err != nil {
2024-09-20 10:40:05 +01:00
dr . Error = err
2024-12-06 16:38:09 +01:00
// If the error is a downstream error, set the error source
if backend . IsDownstreamError ( err ) {
dr . ErrorSource = backend . ErrorSourceDownstream
}
2020-08-03 09:28:03 +03:00
}
2021-01-14 14:16:20 +01:00
2024-09-20 10:40:05 +01:00
resp . Responses [ queryExecutor . getRefID ( ) ] = * dr
2018-09-04 13:21:58 +02:00
}
2021-10-08 14:46:35 +02:00
return resp , nil
2018-09-04 13:21:58 +02:00
}
2021-10-08 14:46:35 +02:00
func queryModel ( query backend . DataQuery ) ( grafanaQuery , error ) {
var q grafanaQuery
2022-11-10 09:30:47 +01:00
err := json . Unmarshal ( query . JSON , & q )
2018-09-11 14:06:46 +02:00
if err != nil {
2021-10-08 14:46:35 +02:00
return grafanaQuery { } , err
2018-09-11 14:06:46 +02:00
}
2021-10-08 14:46:35 +02:00
return q , nil
}
2022-11-04 09:28:38 -04:00
func ( s * Service ) buildQueryExecutors ( logger log . Logger , req * backend . QueryDataRequest ) ( [ ] cloudMonitoringQueryExecutor , error ) {
2023-01-23 22:44:27 +06:00
cloudMonitoringQueryExecutors := make ( [ ] cloudMonitoringQueryExecutor , 0 , len ( req . Queries ) )
2018-09-26 17:50:08 +02:00
2025-01-23 12:57:00 +00:00
for index , query := range req . Queries {
startTime := req . Queries [ index ] . TimeRange . From
endTime := req . Queries [ index ] . TimeRange . To
durationSeconds := int ( endTime . Sub ( startTime ) . Seconds ( ) )
2021-10-08 14:46:35 +02:00
q , err := queryModel ( query )
2021-03-08 07:02:49 +01:00
if err != nil {
2020-06-30 18:47:13 +03:00
return nil , fmt . Errorf ( "could not unmarshal CloudMonitoringQuery json: %w" , err )
2020-03-27 12:01:16 +01:00
}
2021-10-08 14:46:35 +02:00
2021-01-18 21:48:43 +09:00
var queryInterface cloudMonitoringQueryExecutor
2022-11-28 09:17:01 +01:00
switch query . QueryType {
2025-01-14 15:58:38 +01:00
case string ( dataquery . QueryTypeTIMESERIESLIST ) , string ( dataquery . QueryTypeANNOTATION ) :
2022-12-20 12:47:49 +01:00
cmtsf := & cloudMonitoringTimeSeriesList {
2025-01-23 12:57:00 +00:00
refID : query . RefID ,
aliasBy : q . AliasBy ,
timeRange : req . Queries [ index ] . TimeRange ,
2022-12-20 12:47:49 +01:00
}
2023-07-31 11:14:27 -06:00
if q . TimeSeriesList . View == nil || * q . TimeSeriesList . View == "" {
fullString := "FULL"
q . TimeSeriesList . View = & fullString
2022-12-20 12:47:49 +01:00
}
cmtsf . parameters = q . TimeSeriesList
cmtsf . setParams ( startTime , endTime , durationSeconds , query . Interval . Milliseconds ( ) )
queryInterface = cmtsf
2025-01-14 15:58:38 +01:00
case string ( dataquery . QueryTypeTIMESERIESQUERY ) :
2022-12-20 12:47:49 +01:00
queryInterface = & cloudMonitoringTimeSeriesQuery {
refID : query . RefID ,
aliasBy : q . AliasBy ,
parameters : q . TimeSeriesQuery ,
IntervalMS : query . Interval . Milliseconds ( ) ,
2025-01-23 12:57:00 +00:00
timeRange : req . Queries [ index ] . TimeRange ,
2023-02-08 10:33:56 -07:00
logger : logger ,
2020-03-27 12:01:16 +01:00
}
2025-01-14 15:58:38 +01:00
case string ( dataquery . QueryTypeSLO ) :
2022-11-30 17:23:05 +01:00
cmslo := & cloudMonitoringSLO {
refID : query . RefID ,
aliasBy : q . AliasBy ,
parameters : q . SloQuery ,
2025-01-23 12:57:00 +00:00
timeRange : req . Queries [ index ] . TimeRange ,
2022-11-30 17:23:05 +01:00
}
cmslo . setParams ( startTime , endTime , durationSeconds , query . Interval . Milliseconds ( ) )
queryInterface = cmslo
2025-01-14 15:58:38 +01:00
case string ( dataquery . QueryTypePROMQL ) :
2023-08-18 11:14:43 -05:00
cmp := & cloudMonitoringProm {
refID : query . RefID ,
aliasBy : q . AliasBy ,
parameters : q . PromQLQuery ,
2025-01-23 12:57:00 +00:00
timeRange : req . Queries [ index ] . TimeRange ,
2024-01-29 09:24:23 -07:00
logger : logger ,
2023-08-18 11:14:43 -05:00
}
queryInterface = cmp
2021-03-08 07:02:49 +01:00
default :
2024-12-05 15:41:52 +00:00
return nil , backend . DownstreamError ( fmt . Errorf ( "unrecognized query type %q" , query . QueryType ) )
2020-03-27 12:01:16 +01:00
}
2018-09-11 14:06:46 +02:00
2021-01-18 21:48:43 +09:00
cloudMonitoringQueryExecutors = append ( cloudMonitoringQueryExecutors , queryInterface )
2020-03-27 12:01:16 +01:00
}
2018-09-20 00:52:14 +02:00
2021-01-18 21:48:43 +09:00
return cloudMonitoringQueryExecutors , nil
2020-03-27 12:01:16 +01:00
}
2018-09-23 22:04:24 +02:00
2018-10-02 17:07:46 +02:00
func interpolateFilterWildcards ( value string ) string {
2018-10-19 19:47:31 +02:00
matches := strings . Count ( value , "*" )
2020-07-16 14:39:01 +02:00
switch {
case matches == 2 && strings . HasSuffix ( value , "*" ) && strings . HasPrefix ( value , "*" ) :
2020-09-22 16:22:19 +02:00
value = strings . ReplaceAll ( value , "*" , "" )
2018-10-02 17:07:46 +02:00
value = fmt . Sprintf ( ` has_substring("%s") ` , value )
2020-07-16 14:39:01 +02:00
case matches == 1 && strings . HasPrefix ( value , "*" ) :
2018-10-02 17:07:46 +02:00
value = strings . Replace ( value , "*" , "" , 1 )
value = fmt . Sprintf ( ` ends_with("%s") ` , value )
2020-07-16 14:39:01 +02:00
case matches == 1 && strings . HasSuffix ( value , "*" ) :
2022-11-30 17:23:05 +01:00
value = xstrings . Reverse ( strings . Replace ( xstrings . Reverse ( value ) , "*" , "" , 1 ) )
2018-10-02 17:07:46 +02:00
value = fmt . Sprintf ( ` starts_with("%s") ` , value )
2020-07-16 14:39:01 +02:00
case matches != 0 :
2020-01-17 12:25:47 +01:00
value = string ( wildcardRegexRe . ReplaceAllFunc ( [ ] byte ( value ) , func ( in [ ] byte ) [ ] byte {
2018-10-02 17:11:05 +02:00
return [ ] byte ( strings . Replace ( string ( in ) , string ( in ) , ` \\ ` + string ( in ) , 1 ) )
} ) )
2020-09-22 16:22:19 +02:00
value = strings . ReplaceAll ( value , "*" , ".*" )
value = strings . ReplaceAll ( value , ` " ` , ` \\" ` )
2018-10-02 17:07:46 +02:00
value = fmt . Sprintf ( ` monitoring.regex.full_match("^%s$") ` , value )
}
return value
}
2020-03-27 12:01:16 +01:00
func calculateAlignmentPeriod ( alignmentPeriod string , intervalMs int64 , durationSeconds int ) string {
2018-09-26 15:44:09 +02:00
if alignmentPeriod == "grafana-auto" || alignmentPeriod == "" {
2020-03-27 12:01:16 +01:00
alignmentPeriodValue := int ( math . Max ( float64 ( intervalMs ) / 1000 , 60.0 ) )
2018-09-17 16:30:06 +02:00
alignmentPeriod = "+" + strconv . Itoa ( alignmentPeriodValue ) + "s"
}
2020-06-30 18:47:13 +03:00
if alignmentPeriod == "cloud-monitoring-auto" || alignmentPeriod == "stackdriver-auto" { // legacy
2018-09-26 17:50:08 +02:00
alignmentPeriodValue := int ( math . Max ( float64 ( durationSeconds ) , 60.0 ) )
2020-07-16 14:39:01 +02:00
switch {
case alignmentPeriodValue < 60 * 60 * 23 :
2018-09-26 17:50:08 +02:00
alignmentPeriod = "+60s"
2020-07-16 14:39:01 +02:00
case alignmentPeriodValue < 60 * 60 * 24 * 6 :
2018-09-26 17:50:08 +02:00
alignmentPeriod = "+300s"
2020-07-16 14:39:01 +02:00
default :
2018-09-26 17:50:08 +02:00
alignmentPeriod = "+3600s"
}
}
2020-03-27 12:01:16 +01:00
return alignmentPeriod
2018-09-12 00:24:59 +02:00
}
2021-03-08 07:02:49 +01:00
func formatLegendKeys ( metricType string , defaultMetricName string , labels map [ string ] string ,
2022-11-30 17:23:05 +01:00
additionalLabels map [ string ] string , query cloudMonitoringQueryExecutor ) string {
if query . getAliasBy ( ) == "" {
2023-02-24 13:09:10 -05:00
if defaultMetricName != "" {
return defaultMetricName
}
return metricType
2018-09-23 22:04:24 +02:00
}
2022-11-30 17:23:05 +01:00
result := legendKeyFormat . ReplaceAllFunc ( [ ] byte ( query . getAliasBy ( ) ) , func ( in [ ] byte ) [ ] byte {
2018-09-23 22:04:24 +02:00
metaPartName := strings . Replace ( string ( in ) , "{{" , "" , 1 )
metaPartName = strings . Replace ( metaPartName , "}}" , "" , 1 )
metaPartName = strings . TrimSpace ( metaPartName )
if metaPartName == "metric.type" {
return [ ] byte ( metricType )
}
metricPart := replaceWithMetricPart ( metaPartName , metricType )
if metricPart != nil {
return metricPart
}
2020-01-17 12:25:47 +01:00
if val , exists := labels [ metaPartName ] ; exists {
2018-09-23 22:04:24 +02:00
return [ ] byte ( val )
}
2018-10-02 19:42:30 +09:00
if val , exists := additionalLabels [ metaPartName ] ; exists {
return [ ] byte ( val )
}
2022-11-30 17:23:05 +01:00
if query . getParameter ( metaPartName ) != "" {
return [ ] byte ( query . getParameter ( metaPartName ) )
2020-03-27 12:01:16 +01:00
}
2018-09-23 22:04:24 +02:00
return in
} )
return string ( result )
}
func replaceWithMetricPart ( metaPartName string , metricType string ) [ ] byte {
// https://cloud.google.com/monitoring/api/v3/metrics-details#label_names
2018-09-28 17:15:55 +02:00
shortMatches := metricNameFormat . FindStringSubmatch ( metricType )
2018-09-23 22:04:24 +02:00
if metaPartName == "metric.name" {
2020-01-17 12:25:47 +01:00
if len ( shortMatches ) > 2 {
return [ ] byte ( shortMatches [ 3 ] )
2018-09-23 22:04:24 +02:00
}
}
if metaPartName == "metric.service" {
2018-09-28 17:15:55 +02:00
if len ( shortMatches ) > 0 {
2018-09-23 22:04:24 +02:00
return [ ] byte ( shortMatches [ 1 ] )
}
}
return nil
}
2020-06-30 18:47:13 +03:00
func calcBucketBound ( bucketOptions cloudMonitoringBucketOptions , n int ) string {
2018-10-02 19:42:30 +09:00
bucketBound := "0"
if n == 0 {
return bucketBound
}
2020-07-16 14:39:01 +02:00
switch {
case bucketOptions . LinearBuckets != nil :
2023-03-28 08:47:28 -05:00
bucketBound = strconv . FormatFloat ( bucketOptions . LinearBuckets . Offset + ( bucketOptions . LinearBuckets . Width * float64 ( n - 1 ) ) , 'f' , 2 , 64 )
2020-07-16 14:39:01 +02:00
case bucketOptions . ExponentialBuckets != nil :
2018-10-02 19:42:30 +09:00
bucketBound = strconv . FormatInt ( int64 ( bucketOptions . ExponentialBuckets . Scale * math . Pow ( bucketOptions . ExponentialBuckets . GrowthFactor , float64 ( n - 1 ) ) ) , 10 )
2020-07-16 14:39:01 +02:00
case bucketOptions . ExplicitBuckets != nil :
2022-10-10 16:08:33 +02:00
if n < len ( bucketOptions . ExplicitBuckets . Bounds ) {
bucketBound = fmt . Sprintf ( "%g" , bucketOptions . ExplicitBuckets . Bounds [ n ] )
} else {
lastBound := bucketOptions . ExplicitBuckets . Bounds [ len ( bucketOptions . ExplicitBuckets . Bounds ) - 1 ]
bucketBound = fmt . Sprintf ( "%g+" , lastBound )
}
2018-10-02 19:42:30 +09:00
}
return bucketBound
}
2022-11-30 17:23:05 +01:00
func ( s * Service ) ensureProject ( ctx context . Context , dsInfo datasourceInfo , projectName string ) ( string , error ) {
if projectName != "" {
return projectName , nil
2021-01-18 21:48:43 +09:00
}
2022-11-30 17:23:05 +01:00
return s . getDefaultProject ( ctx , dsInfo )
2018-09-04 13:21:58 +02:00
}
2018-10-10 11:55:45 +02:00
2021-10-08 14:46:35 +02:00
func ( s * Service ) getDefaultProject ( ctx context . Context , dsInfo datasourceInfo ) ( string , error ) {
if dsInfo . authenticationType == gceAuthentication {
2024-09-10 19:34:20 +01:00
project , err := s . gceDefaultProjectGetter ( ctx , cloudMonitorScope )
2024-10-30 15:28:53 +00:00
if err != nil {
2024-12-06 16:38:09 +01:00
return project , backend . DownstreamError ( err )
2024-10-30 15:28:53 +00:00
}
return project , nil
2018-10-10 11:55:45 +02:00
}
2021-10-08 14:46:35 +02:00
return dsInfo . defaultProject , nil
2018-10-10 11:55:45 +02:00
}
2021-01-18 21:48:43 +09:00
2024-01-29 09:24:23 -07:00
func unmarshalResponse ( res * http . Response , logger log . Logger ) ( cloudMonitoringResponse , error ) {
2022-08-10 13:37:51 +00:00
body , err := io . ReadAll ( res . Body )
2021-01-18 21:48:43 +09:00
if err != nil {
return cloudMonitoringResponse { } , err
}
defer func ( ) {
if err := res . Body . Close ( ) ; err != nil {
2022-11-04 09:28:38 -04:00
logger . Warn ( "Failed to close response body" , "err" , err )
2021-01-18 21:48:43 +09:00
}
} ( )
if res . StatusCode / 100 != 2 {
2024-09-20 10:40:05 +01:00
logger . Error ( "Request failed" , "status" , res . Status , "body" , string ( body ) , "statusSource" , backend . ErrorSourceDownstream )
2024-12-06 16:38:09 +01:00
statusErr := fmt . Errorf ( "query failed: %s" , string ( body ) )
if backend . ErrorSourceFromHTTPStatus ( res . StatusCode ) == backend . ErrorSourceDownstream {
return cloudMonitoringResponse { } , backend . DownstreamError ( statusErr )
}
return cloudMonitoringResponse { } , backend . PluginError ( statusErr )
2021-01-18 21:48:43 +09:00
}
var data cloudMonitoringResponse
err = json . Unmarshal ( body , & data )
if err != nil {
2024-09-20 10:40:05 +01:00
logger . Error ( "Failed to unmarshal CloudMonitoring response" , "error" , err , "status" , res . Status , "body" , string ( body ) , "statusSource" , backend . ErrorSourceDownstream )
2021-01-18 21:48:43 +09:00
return cloudMonitoringResponse { } , fmt . Errorf ( "failed to unmarshal query response: %w" , err )
}
return data , nil
}
2025-01-14 15:58:38 +01:00
func addConfigData ( frames data . Frames , dl string , unit string , period string , logger log . Logger ) data . Frames {
2021-01-18 21:48:43 +09:00
for i := range frames {
if frames [ i ] . Fields [ 1 ] . Config == nil {
frames [ i ] . Fields [ 1 ] . Config = & data . FieldConfig { }
}
2022-08-01 14:53:09 +02:00
if len ( dl ) > 0 {
deepLink := data . DataLink {
Title : "View in Metrics Explorer" ,
TargetBlank : true ,
URL : dl ,
}
frames [ i ] . Fields [ 1 ] . Config . Links = append ( frames [ i ] . Fields [ 1 ] . Config . Links , deepLink )
2021-01-18 21:48:43 +09:00
}
2021-04-14 16:41:02 +02:00
if len ( unit ) > 0 {
if val , ok := cloudMonitoringUnitMappings [ unit ] ; ok {
frames [ i ] . Fields [ 1 ] . Config . Unit = val
}
}
2022-10-31 14:48:32 +01:00
if frames [ i ] . Fields [ 0 ] . Config == nil {
frames [ i ] . Fields [ 0 ] . Config = & data . FieldConfig { }
}
2025-01-14 15:58:38 +01:00
if period != "" {
err := addInterval ( period , frames [ i ] . Fields [ 0 ] )
2022-10-31 14:48:32 +01:00
if err != nil {
2024-09-20 10:40:05 +01:00
logger . Error ( "Failed to add interval: %s" , err , "statusSource" , backend . ErrorSourceDownstream )
2022-10-31 14:48:32 +01:00
}
}
2021-01-18 21:48:43 +09:00
}
return frames
}
2021-10-08 14:46:35 +02:00
2023-05-24 10:19:34 +02:00
func ( s * Service ) getDSInfo ( ctx context . Context , pluginCtx backend . PluginContext ) ( * datasourceInfo , error ) {
i , err := s . im . Get ( ctx , pluginCtx )
2021-10-08 14:46:35 +02:00
if err != nil {
return nil , err
}
instance , ok := i . ( * datasourceInfo )
if ! ok {
2023-04-14 11:11:25 -04:00
return nil , fmt . Errorf ( "failed to cast datasource info" )
2021-10-08 14:46:35 +02:00
}
return instance , nil
}