mirror of
https://github.com/yunionio/cloudpods.git
synced 2026-09-01 15:07:17 +08:00
feat(monitor,region,host,cloudmon): support VictoriaMetrics as TSDB backend
This commit is contained in:
@@ -96,6 +96,7 @@ func init() {
|
||||
apis.SERVICE_TYPE_OFFLINE_CLOUDMETA,
|
||||
apis.SERVICE_TYPE_CLOUDMETA,
|
||||
apis.SERVICE_TYPE_INFLUXDB,
|
||||
apis.SERVICE_TYPE_VICTORIA_METRICS,
|
||||
apis.SERVICE_TYPE_ETCD,
|
||||
"torrent-tracker",
|
||||
}) {
|
||||
|
||||
@@ -31,6 +31,8 @@ require (
|
||||
github.com/gosuri/uitable v0.0.0-20160404203958-36ee7e946282
|
||||
github.com/hako/durafmt v0.0.0-20180520121703-7b7ae1e72ead
|
||||
github.com/hugozhu/godingtalk v1.0.6
|
||||
github.com/influxdata/influxql v1.1.0
|
||||
github.com/influxdata/promql/v2 v2.12.0
|
||||
github.com/jaypipes/ghw v0.11.0
|
||||
github.com/koding/websocketproxy v0.0.0-20181220232114-7ed82d81a28c
|
||||
github.com/lestrrat-go/jwx v1.0.2
|
||||
@@ -62,6 +64,7 @@ require (
|
||||
github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74
|
||||
github.com/vmihailenco/msgpack v4.0.4+incompatible
|
||||
github.com/xuri/excelize/v2 v2.7.1
|
||||
github.com/zexi/influxql-to-promql v0.0.5
|
||||
go.etcd.io/etcd/api/v3 v3.5.0
|
||||
go.etcd.io/etcd/client/v3 v3.5.0
|
||||
golang.org/x/crypto v0.8.0
|
||||
@@ -132,6 +135,7 @@ require (
|
||||
github.com/boltdb/bolt v1.3.1 // indirect
|
||||
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect
|
||||
github.com/bradfitz/iter v0.0.0-20140124041915-454541ec3da2 // indirect
|
||||
github.com/cespare/xxhash v1.1.0 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
||||
github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58 // indirect
|
||||
github.com/coreos/go-semver v0.3.0 // indirect
|
||||
|
||||
@@ -81,6 +81,8 @@ github.com/LeeEirc/terminalparser v0.0.0-20220328021224-de16b7643ea4/go.mod h1:t
|
||||
github.com/Microsoft/azure-vhd-utils v0.0.0-20181115010904-44cbada2ece3 h1:gImoAO1xAcC1oDlYmD/X7dggsodGf2DFJOVE5m0ssms=
|
||||
github.com/Microsoft/azure-vhd-utils v0.0.0-20181115010904-44cbada2ece3/go.mod h1:u0H9gMieFLxkUy8RS0X8VbFWyPs2815qQAaitRbj6x0=
|
||||
github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
|
||||
github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE=
|
||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||
github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
||||
github.com/QcloudApi/qcloud_sign_golang v0.0.0-20141224014652-e4130a326409/go.mod h1:1pk82RBxDY/JZnPQrtqHlUFfCctgdorsd9M06fMynOM=
|
||||
@@ -173,6 +175,8 @@ github.com/bradfitz/iter v0.0.0-20140124041915-454541ec3da2/go.mod h1:PyRFw1Lt2w
|
||||
github.com/c-bata/go-prompt v0.2.4 h1:7pKUJ3CUgzdu1HJeWhNRkpVyY/NnlJhM/7d6YgHNOao=
|
||||
github.com/c-bata/go-prompt v0.2.4/go.mod h1:PqlttLXp0E7bZcoDW+dmzyKqFbmQTFoNzGSuW/AQRmo=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
|
||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
|
||||
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
@@ -421,6 +425,10 @@ github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:
|
||||
github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
|
||||
github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28=
|
||||
github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
|
||||
github.com/influxdata/influxql v1.1.0 h1:sPsaumLFRPMwR5QtD3Up54HXpNND8Eu7G1vQFmi3quQ=
|
||||
github.com/influxdata/influxql v1.1.0/go.mod h1:KpVI7okXjK6PRi3Z5B+mtKZli+R1DnZgb3N+tzevNgo=
|
||||
github.com/influxdata/promql/v2 v2.12.0 h1:kXn3p0D7zPw16rOtfDR+wo6aaiH8tSMfhPwONTxrlEc=
|
||||
github.com/influxdata/promql/v2 v2.12.0/go.mod h1:fxOPu+DY0bqCTCECchSRtWfc+0X19ybifQhZoQNF5D8=
|
||||
github.com/ipandtcp/godingtalk v0.0.0-20180410032244-ca3d6ac197fb/go.mod h1:3umLciE1jBnh2LgrL15R2zbEe+HVPRZXnlSjjb/dYiw=
|
||||
github.com/jaypipes/ghw v0.11.0 h1:i0pKvAM7eZk0KvLm9vzpcpDKTRnfR6AQ5pFkPVnYJXU=
|
||||
github.com/jaypipes/ghw v0.11.0/go.mod h1:jeJGbkRB2lL3/gxYzNYzEDETV1ZJ56OKr+CSeSEym+g=
|
||||
@@ -599,6 +607,7 @@ github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:
|
||||
github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
|
||||
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc=
|
||||
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
|
||||
github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
|
||||
github.com/prometheus/common v0.32.1 h1:hWIdL3N2HoUx3B8j3YN9mWor0qhY/NlEKZEaXxuIRh4=
|
||||
@@ -734,6 +743,8 @@ github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg=
|
||||
github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||
github.com/zexi/influxql-to-promql v0.0.5 h1:4gRTqrof31O2Ku443RoTh20AadJG4sXbREn8i892WQo=
|
||||
github.com/zexi/influxql-to-promql v0.0.5/go.mod h1:a+S9SfEZ/aVyBM5abA7UCQmp7vD8Q9o+GfkQhk5mM58=
|
||||
go.etcd.io/etcd/api/v3 v3.5.0 h1:GsV3S+OfZEOCNXdtNkBSR7kgLobAa/SO6tCxRa0GAYw=
|
||||
go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.5.0 h1:2aQv6F436YnN7I4VbI8PPYrBhu+SmrTaADcf8Mi/6PU=
|
||||
|
||||
+6
-4
@@ -36,8 +36,10 @@ const (
|
||||
SERVICE_TYPE_CLOUDMON = "cloudmon"
|
||||
SERVICE_TYPE_VPCAGENT = "vpcagent"
|
||||
|
||||
SERVICE_TYPE_ETCD = "etcd"
|
||||
SERVICE_TYPE_INFLUXDB = "influxdb"
|
||||
SERVICE_TYPE_ETCD = "etcd"
|
||||
SERVICE_TYPE_INFLUXDB = "influxdb"
|
||||
SERVICE_TYPE_NTP = "ntp"
|
||||
SERVICE_TYPE_VICTORIA_METRICS = "victoria-metrics"
|
||||
|
||||
SERVICE_TYPE_SCHEDULEDTASK = "scheduledtask"
|
||||
|
||||
@@ -101,9 +103,9 @@ var (
|
||||
"apigateway",
|
||||
"yunionapi",
|
||||
"billing",
|
||||
"etcd",
|
||||
SERVICE_TYPE_ETCD,
|
||||
"itsm",
|
||||
"ntp",
|
||||
SERVICE_TYPE_NTP,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -93,10 +93,9 @@ type AlertCondition struct {
|
||||
}
|
||||
|
||||
type AlertQuery struct {
|
||||
Model MetricQuery `json:"model"`
|
||||
DataSourceId string `json:"data_source_id"`
|
||||
From string `json:"from"`
|
||||
To string `json:"to"`
|
||||
Model MetricQuery `json:"model"`
|
||||
From string `json:"from"`
|
||||
To string `json:"to"`
|
||||
}
|
||||
|
||||
type AlertCreateInput struct {
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
package monitor
|
||||
|
||||
import "yunion.io/x/onecloud/pkg/apis"
|
||||
|
||||
const (
|
||||
DataSourceTypeInfluxdb = apis.SERVICE_TYPE_INFLUXDB
|
||||
DataSourceTypeVictoriaMetrics = apis.SERVICE_TYPE_VICTORIA_METRICS
|
||||
)
|
||||
@@ -14,10 +14,6 @@
|
||||
|
||||
package monitor
|
||||
|
||||
const (
|
||||
DataSourceTypeInfluxdb = "influxdb"
|
||||
)
|
||||
|
||||
type DataSourceConfig struct {
|
||||
Id string
|
||||
Name string
|
||||
|
||||
@@ -26,6 +26,7 @@ import (
|
||||
api "yunion.io/x/onecloud/pkg/apis/compute"
|
||||
o "yunion.io/x/onecloud/pkg/baremetal/options"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/consts"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/tsdb"
|
||||
"yunion.io/x/onecloud/pkg/mcclient"
|
||||
"yunion.io/x/onecloud/pkg/mcclient/auth"
|
||||
modules "yunion.io/x/onecloud/pkg/mcclient/modules/logger"
|
||||
@@ -221,11 +222,11 @@ func (job *SSendMetricsJob) Name() string {
|
||||
|
||||
func (job *SSendMetricsJob) Do(ctx context.Context, now time.Time) error {
|
||||
s := auth.GetAdminSession(ctx, consts.GetRegion())
|
||||
urls, err := s.GetServiceURLs("influxdb", o.Options.SessionEndpointType)
|
||||
src, err := tsdb.GetDefaultServiceSource(s, o.Options.SessionEndpointType)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "s.GetServiceURLs")
|
||||
return errors.Wrap(err, "tsdb.GetDefaultServiceSource")
|
||||
}
|
||||
if len(urls) == 0 {
|
||||
if len(src.URLs) == 0 {
|
||||
return nil
|
||||
}
|
||||
if !job.baremetal.isRedfishCapable() {
|
||||
@@ -253,9 +254,9 @@ func (job *SSendMetricsJob) Do(ctx context.Context, now time.Time) error {
|
||||
Timestamp: now,
|
||||
})
|
||||
}
|
||||
err = influxdb.SendMetrics(urls, "telegraf", metrics, false)
|
||||
err = influxdb.SendMetrics(src.URLs, "telegraf", metrics, false)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "influxdb.SendMetrics")
|
||||
return errors.Wrapf(err, "tsdb.SendMetrics %q", src.Type)
|
||||
}
|
||||
job.lastTime = now
|
||||
return nil
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
package tsdb // import "yunion.io/x/onecloud/pkg/cloudcommon/tsdb"
|
||||
@@ -0,0 +1,55 @@
|
||||
package tsdb
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
|
||||
"yunion.io/x/pkg/errors"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/apis"
|
||||
"yunion.io/x/onecloud/pkg/mcclient"
|
||||
)
|
||||
|
||||
type TSDBServiceSource struct {
|
||||
Type string
|
||||
URLs []string
|
||||
}
|
||||
|
||||
func NewTSDBServiceSource(t string, urls []string) *TSDBServiceSource {
|
||||
return &TSDBServiceSource{
|
||||
Type: t,
|
||||
URLs: urls,
|
||||
}
|
||||
}
|
||||
|
||||
func GetDefaultServiceSource(s *mcclient.ClientSession, endpointType string) (*TSDBServiceSource, error) {
|
||||
errs := []error{}
|
||||
for _, sType := range []string{apis.SERVICE_TYPE_INFLUXDB, apis.SERVICE_TYPE_VICTORIA_METRICS} {
|
||||
urls, err := s.GetServiceURLs(sType, endpointType)
|
||||
if err != nil {
|
||||
errs = append(errs, errors.Wrapf(err, "get %s service type %q", endpointType, sType))
|
||||
}
|
||||
if len(urls) != 0 {
|
||||
return NewTSDBServiceSource(sType, urls), nil
|
||||
}
|
||||
}
|
||||
return nil, errors.NewAggregate(errs)
|
||||
}
|
||||
|
||||
func GetDefaultServiceSourceURLs(s *mcclient.ClientSession, endpointType string) ([]string, error) {
|
||||
src, err := GetDefaultServiceSource(s, endpointType)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "GetDefaultServiceSource")
|
||||
}
|
||||
if len(src.URLs) == 0 {
|
||||
return nil, errors.Errorf("tsdb source %q URLs are empty", src.Type)
|
||||
}
|
||||
return src.URLs, nil
|
||||
}
|
||||
|
||||
func GetDefaultServiceSourceURL(s *mcclient.ClientSession, endpointType string) (string, error) {
|
||||
urls, err := GetDefaultServiceSourceURLs(s, endpointType)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return urls[rand.Intn(len(urls))], nil
|
||||
}
|
||||
@@ -23,8 +23,8 @@ import (
|
||||
"yunion.io/x/log"
|
||||
"yunion.io/x/pkg/errors"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/apis"
|
||||
api "yunion.io/x/onecloud/pkg/apis/monitor"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/tsdb"
|
||||
"yunion.io/x/onecloud/pkg/cloutpost/options"
|
||||
"yunion.io/x/onecloud/pkg/mcclient"
|
||||
"yunion.io/x/onecloud/pkg/mcclient/auth"
|
||||
@@ -101,7 +101,7 @@ func AlertHistoryReport(ctx context.Context, userCred mcclient.TokenCredential,
|
||||
}
|
||||
metrics = append(metrics, metric)
|
||||
}
|
||||
urls, err := s.GetServiceURLs(apis.SERVICE_TYPE_INFLUXDB, options.Options.SessionEndpointType)
|
||||
urls, err := tsdb.GetDefaultServiceSourceURLs(s, options.Options.SessionEndpointType)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "GetServiceURLs")
|
||||
}
|
||||
|
||||
@@ -25,8 +25,8 @@ import (
|
||||
"yunion.io/x/pkg/util/netutils"
|
||||
"yunion.io/x/pkg/util/rbacscope"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/apis"
|
||||
api "yunion.io/x/onecloud/pkg/apis/compute"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/tsdb"
|
||||
"yunion.io/x/onecloud/pkg/cloudmon/options"
|
||||
"yunion.io/x/onecloud/pkg/mcclient"
|
||||
"yunion.io/x/onecloud/pkg/mcclient/auth"
|
||||
@@ -78,7 +78,7 @@ func PingProbe(ctx context.Context, userCred mcclient.TokenCredential, isStart b
|
||||
}
|
||||
metrics = append(metrics, m...)
|
||||
}
|
||||
urls, err := s.GetServiceURLs(apis.SERVICE_TYPE_INFLUXDB, options.Options.SessionEndpointType)
|
||||
urls, err := tsdb.GetDefaultServiceSourceURLs(s, options.Options.SessionEndpointType)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "GetServiceURLs")
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ import (
|
||||
|
||||
"yunion.io/x/onecloud/pkg/apis"
|
||||
api "yunion.io/x/onecloud/pkg/apis/identity"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/tsdb"
|
||||
"yunion.io/x/onecloud/pkg/cloudmon/options"
|
||||
"yunion.io/x/onecloud/pkg/mcclient"
|
||||
"yunion.io/x/onecloud/pkg/mcclient/auth"
|
||||
@@ -100,7 +101,7 @@ func CollectServiceMetrics(ctx context.Context, userCred mcclient.TokenCredentia
|
||||
}
|
||||
metrics = append(metrics, part...)
|
||||
}
|
||||
urls, err := s.GetServiceURLs(apis.SERVICE_TYPE_INFLUXDB, options.Options.SessionEndpointType)
|
||||
urls, err := tsdb.GetDefaultServiceSourceURLs(s, options.Options.SessionEndpointType)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "GetServiceURLs")
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ import (
|
||||
"yunion.io/x/log"
|
||||
"yunion.io/x/pkg/errors"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/apis"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/tsdb"
|
||||
"yunion.io/x/onecloud/pkg/cloudmon/options"
|
||||
"yunion.io/x/onecloud/pkg/mcclient"
|
||||
"yunion.io/x/onecloud/pkg/mcclient/auth"
|
||||
@@ -89,8 +89,8 @@ func UsegReport(ctx context.Context, userCred mcclient.TokenCredential, isStart
|
||||
return errors.Wrap(err, "getDomainAndProjectServerUsage err")
|
||||
}
|
||||
dataList = append(dataList, data...)
|
||||
//写入influDb
|
||||
urls, err := s.GetServiceURLs(apis.SERVICE_TYPE_INFLUXDB, options.Options.SessionEndpointType)
|
||||
// 写入 influxdb 或者 VictoriaMetrics
|
||||
urls, err := tsdb.GetDefaultServiceSourceURLs(s, options.Options.SessionEndpointType)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "GetServiceURLs")
|
||||
}
|
||||
|
||||
@@ -24,8 +24,8 @@ import (
|
||||
"yunion.io/x/log"
|
||||
"yunion.io/x/pkg/errors"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/apis"
|
||||
api "yunion.io/x/onecloud/pkg/apis/compute"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/tsdb"
|
||||
"yunion.io/x/onecloud/pkg/cloudmon/options"
|
||||
"yunion.io/x/onecloud/pkg/mcclient/auth"
|
||||
"yunion.io/x/onecloud/pkg/util/influxdb"
|
||||
@@ -94,7 +94,7 @@ func (self *SBaseCollectDriver) CollectStorageMetrics(ctx context.Context, manag
|
||||
|
||||
func (self *SBaseCollectDriver) sendMetrics(ctx context.Context, manager api.CloudproviderDetails, resName string, resCnt int, metrics []influxdb.SMetricData) error {
|
||||
s := auth.GetAdminSession(ctx, options.Options.Region)
|
||||
urls, err := s.GetServiceURLs(apis.SERVICE_TYPE_INFLUXDB, options.Options.SessionEndpointType)
|
||||
urls, err := tsdb.GetDefaultServiceSourceURLs(s, options.Options.SessionEndpointType)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "GetServiceURLs")
|
||||
}
|
||||
|
||||
@@ -26,9 +26,9 @@ import (
|
||||
"yunion.io/x/log"
|
||||
"yunion.io/x/pkg/errors"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/apis"
|
||||
api "yunion.io/x/onecloud/pkg/apis/compute"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/db"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/tsdb"
|
||||
"yunion.io/x/onecloud/pkg/cloudmon/options"
|
||||
"yunion.io/x/onecloud/pkg/cloudmon/providerdriver"
|
||||
"yunion.io/x/onecloud/pkg/mcclient"
|
||||
@@ -907,7 +907,7 @@ func (self *SResources) CollectMetrics(ctx context.Context, userCred mcclient.To
|
||||
}
|
||||
metrics = append(metrics, metric)
|
||||
}
|
||||
urls, err := s.GetServiceURLs(apis.SERVICE_TYPE_INFLUXDB, options.Options.SessionEndpointType)
|
||||
urls, err := tsdb.GetDefaultServiceSourceURLs(s, options.Options.SessionEndpointType)
|
||||
if err != nil {
|
||||
log.Errorf("Get influxdb %s service url: %v", options.Options.SessionEndpointType, err)
|
||||
return
|
||||
|
||||
@@ -28,11 +28,11 @@ import (
|
||||
"yunion.io/x/pkg/util/billing"
|
||||
"yunion.io/x/pkg/util/osprofile"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/apis"
|
||||
api "yunion.io/x/onecloud/pkg/apis/compute"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/consts"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/db/quotas"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/db/taskman"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/tsdb"
|
||||
guestdriver_types "yunion.io/x/onecloud/pkg/compute/guestdrivers/types"
|
||||
"yunion.io/x/onecloud/pkg/compute/models"
|
||||
"yunion.io/x/onecloud/pkg/compute/options"
|
||||
@@ -526,11 +526,12 @@ func (drv *SBaseGuestDriver) RequestQgaCommand(ctx context.Context, userCred mcc
|
||||
|
||||
func (drv *SBaseGuestDriver) FetchMonitorUrl(ctx context.Context, guest *models.SGuest) string {
|
||||
s := auth.GetAdminSessionWithPublic(ctx, consts.GetRegion())
|
||||
influxdbUrl, err := s.GetServiceURL(apis.SERVICE_TYPE_INFLUXDB, options.Options.MonitorEndpointType)
|
||||
tsdbURL, err := tsdb.GetDefaultServiceSourceURL(s, options.Options.MonitorEndpointType)
|
||||
if err != nil {
|
||||
log.Errorf("FetchMonitorUrl fail %s", err)
|
||||
return ""
|
||||
}
|
||||
return influxdbUrl
|
||||
return tsdbURL
|
||||
}
|
||||
|
||||
func (drv *SBaseGuestDriver) RequestResetNicTrafficLimit(ctx context.Context, task taskman.ITask, host *models.SHost, guest *models.SGuest, input []api.ServerNicTrafficLimit) error {
|
||||
|
||||
@@ -4226,7 +4226,7 @@ func (hh *SHost) PerformPing(ctx context.Context, userCred mcclient.TokenCredent
|
||||
}
|
||||
result := jsonutils.NewDict()
|
||||
result.Set("name", jsonutils.NewString(hh.GetName()))
|
||||
dependSvcs := []string{"ntpd", "kafka", "influxdb", "elasticsearch"}
|
||||
dependSvcs := []string{"ntpd", "kafka", apis.SERVICE_TYPE_INFLUXDB, apis.SERVICE_TYPE_VICTORIA_METRICS, "elasticsearch"}
|
||||
catalog := auth.GetCatalogData(dependSvcs, options.Options.Region)
|
||||
if catalog == nil {
|
||||
return nil, fmt.Errorf("Get catalog error")
|
||||
|
||||
@@ -34,6 +34,7 @@ import (
|
||||
identity_apis "yunion.io/x/onecloud/pkg/apis/identity"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/db"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/db/lockman"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/tsdb"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/validators"
|
||||
"yunion.io/x/onecloud/pkg/compute/options"
|
||||
"yunion.io/x/onecloud/pkg/httperrors"
|
||||
@@ -279,8 +280,7 @@ func (p *SLoadbalancerAgentParamsTelegraf) updateBy(pp *SLoadbalancerAgentParams
|
||||
func (p *SLoadbalancerAgentParamsTelegraf) initDefault(data *jsonutils.JSONDict) {
|
||||
if p.InfluxDbOutputUrl == "" {
|
||||
baseOpts := &options.Options
|
||||
u, _ := auth.GetServiceURL("influxdb", baseOpts.Region, "",
|
||||
identity_apis.EndpointInterfacePublic)
|
||||
u, _ := tsdb.GetDefaultServiceSourceURL(auth.GetAdminSession(context.Background(), baseOpts.Region), identity_apis.EndpointInterfacePublic)
|
||||
p.InfluxDbOutputUrl = u
|
||||
p.InfluxDbOutputUnsafeSsl = true
|
||||
}
|
||||
|
||||
@@ -15,12 +15,13 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"yunion.io/x/log"
|
||||
"yunion.io/x/pkg/errors"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/apis"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/tsdb"
|
||||
"yunion.io/x/onecloud/pkg/compute/options"
|
||||
"yunion.io/x/onecloud/pkg/mcclient"
|
||||
"yunion.io/x/onecloud/pkg/mcclient/auth"
|
||||
@@ -32,7 +33,8 @@ type sInfluxdbEndpointListener struct {
|
||||
}
|
||||
|
||||
func (listener *sInfluxdbEndpointListener) OnServiceCatalogChange(catalog mcclient.IServiceCatalog) {
|
||||
urls, err := auth.GetServiceURLs(apis.SERVICE_TYPE_INFLUXDB, options.Options.Region, "", "")
|
||||
s := auth.GetAdminSession(context.Background(), options.Options.Region)
|
||||
urls, err := tsdb.GetDefaultServiceSourceURLs(s, options.Options.SessionEndpointType)
|
||||
if err != nil {
|
||||
log.Debugf("sInfluxdbEndpointListener: no influxdb endpoints found, retry later...")
|
||||
return
|
||||
|
||||
@@ -20,9 +20,11 @@ import (
|
||||
|
||||
"yunion.io/x/jsonutils"
|
||||
"yunion.io/x/log"
|
||||
"yunion.io/x/pkg/errors"
|
||||
|
||||
apis "yunion.io/x/onecloud/pkg/apis/ansible"
|
||||
apiidentity "yunion.io/x/onecloud/pkg/apis/identity"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/tsdb"
|
||||
"yunion.io/x/onecloud/pkg/mcclient"
|
||||
"yunion.io/x/onecloud/pkg/mcclient/auth"
|
||||
ansible_modules "yunion.io/x/onecloud/pkg/mcclient/modules/ansible"
|
||||
@@ -59,27 +61,25 @@ func getServerAttrs(ID string, s *mcclient.ClientSession) (map[string]string, er
|
||||
return params, err
|
||||
}
|
||||
|
||||
func getInfluxdbURL() (string, error) {
|
||||
func getTSDBURL() (string, error) {
|
||||
s := auth.GetAdminSessionWithPublic(nil, "")
|
||||
url, err := s.GetServiceURL("influxdb", apiidentity.EndpointInterfacePublic)
|
||||
|
||||
url, err := tsdb.GetDefaultServiceSourceURL(s, apiidentity.EndpointInterfacePublic)
|
||||
if err != nil {
|
||||
log.Errorf("get influxdb Endpoint error %s", err)
|
||||
return "", err
|
||||
return "", errors.Wrap(err, "get influxdb Endpoint error")
|
||||
}
|
||||
return url, nil
|
||||
}
|
||||
|
||||
func renderExtraVars(vars map[string]string) {
|
||||
|
||||
InfluxdbURL, err := getInfluxdbURL()
|
||||
tsdbURL, err := getTSDBURL()
|
||||
if err != nil {
|
||||
log.Errorf("template binding: get influxdb url error: %s", err)
|
||||
return
|
||||
}
|
||||
for key, value := range vars {
|
||||
if key == "influxdb" && value == "INFLUXDB" {
|
||||
vars[key] = InfluxdbURL
|
||||
vars[key] = tsdbURL
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
"yunion.io/x/pkg/errors"
|
||||
"yunion.io/x/pkg/util/sets"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/apis"
|
||||
ansible_api "yunion.io/x/onecloud/pkg/apis/ansible"
|
||||
comapi "yunion.io/x/onecloud/pkg/apis/compute"
|
||||
)
|
||||
@@ -55,16 +56,26 @@ func GetArgs(ctx context.Context, serverId, proxyEndpointId string, others inter
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "unable to get serverInfo of server %s", serverId)
|
||||
}
|
||||
influxdbUrl := info.serverDetails.MonitorUrl
|
||||
log.Infof("influxdbUrl: %s", influxdbUrl)
|
||||
influxdbUrl, err = FindValidServiceUrl(ctx, Service{"influxdb", influxdbUrl}, proxyEndpointId, info, host)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "unable to convertInfluxdbUrl %s", influxdbUrl)
|
||||
monitorUrl := info.serverDetails.MonitorUrl
|
||||
log.Infof("TSDB monitor Url: %s", monitorUrl)
|
||||
foundSvc := false
|
||||
errs := []error{}
|
||||
for _, svcName := range []string{apis.SERVICE_TYPE_INFLUXDB, apis.SERVICE_TYPE_VICTORIA_METRICS} {
|
||||
if tsdbUrl, err := FindValidServiceUrl(ctx, Service{svcName, monitorUrl}, proxyEndpointId, info, host); err != nil {
|
||||
errs = append(errs, errors.Wrapf(err, "unable to convertInfluxdbUrl %s", monitorUrl))
|
||||
} else {
|
||||
monitorUrl = tsdbUrl
|
||||
foundSvc = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if len(influxdbUrl) == 0 {
|
||||
if !foundSvc {
|
||||
return nil, errors.Wrapf(errors.NewAggregate(errs), "convert TSDB service URL")
|
||||
}
|
||||
if len(monitorUrl) == 0 {
|
||||
return nil, errors.Wrap(ErrCannotReachInfluxbd, "please create usable Proxy Endpoint for server and try again")
|
||||
}
|
||||
return getArgs(&info, influxdbUrl), nil
|
||||
return getArgs(&info, monitorUrl), nil
|
||||
}
|
||||
func getArgs(info *sServerInfo, influxdbUrl string) map[string]interface{} {
|
||||
tags := map[string]string{
|
||||
|
||||
@@ -26,6 +26,7 @@ import (
|
||||
"yunion.io/x/pkg/util/httputils"
|
||||
"yunion.io/x/pkg/util/sets"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/apis"
|
||||
ansible_api "yunion.io/x/onecloud/pkg/apis/ansible"
|
||||
proxy_api "yunion.io/x/onecloud/pkg/apis/cloudproxy"
|
||||
comapi "yunion.io/x/onecloud/pkg/apis/compute"
|
||||
@@ -46,7 +47,7 @@ type Service struct {
|
||||
|
||||
func serviceComplete(serviceName, address string, port int) (url, checkUrl string, expectedCode int) {
|
||||
switch serviceName {
|
||||
case "influxdb":
|
||||
case apis.SERVICE_TYPE_INFLUXDB, apis.SERVICE_TYPE_VICTORIA_METRICS:
|
||||
return fmt.Sprintf("https://%s:%d", address, port), fmt.Sprintf("https://%s:%d/ping", address, port), 204
|
||||
case "repo":
|
||||
return fmt.Sprintf("http://%s:%d", address, port), fmt.Sprintf("http://%s:%d", address, port), 200
|
||||
@@ -57,7 +58,7 @@ func serviceComplete(serviceName, address string, port int) (url, checkUrl strin
|
||||
|
||||
func serviceComplete2(service Service) (completeUrl string, expectedCode int) {
|
||||
switch service.Name {
|
||||
case "influxdb":
|
||||
case apis.SERVICE_TYPE_INFLUXDB, apis.SERVICE_TYPE_VICTORIA_METRICS:
|
||||
return fmt.Sprintf("%s/ping", service.Url), 204
|
||||
case "repo":
|
||||
return service.Url, 200
|
||||
|
||||
@@ -44,6 +44,7 @@ import (
|
||||
napi "yunion.io/x/onecloud/pkg/apis/notify"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/consts"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/notifyclient"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/tsdb"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/types"
|
||||
"yunion.io/x/onecloud/pkg/hostman/guestfs/fsdriver"
|
||||
"yunion.io/x/onecloud/pkg/hostman/hostinfo/hostbridge"
|
||||
@@ -2284,9 +2285,13 @@ func (h *SHostInfo) OnCatalogChanged(catalog mcclient.KeystoneServiceCatalogV3)
|
||||
if len(urls) > 0 {
|
||||
conf["kafka"] = map[string]interface{}{"brokers": urls, "topic": "telegraf"}
|
||||
}
|
||||
urls, _ = s.GetServiceURLs("influxdb", defaultEndpointType)
|
||||
if len(urls) > 0 {
|
||||
conf["influxdb"] = map[string]interface{}{"url": urls, "database": "telegraf"}
|
||||
tsdb, _ := tsdb.GetDefaultServiceSource(s, defaultEndpointType)
|
||||
if tsdb != nil && len(tsdb.URLs) > 0 {
|
||||
conf[apis.SERVICE_TYPE_INFLUXDB] = map[string]interface{}{
|
||||
"url": tsdb.URLs,
|
||||
"database": "telegraf",
|
||||
"tsdb_type": tsdb.Type,
|
||||
}
|
||||
}
|
||||
if !reflect.DeepEqual(telegraf.GetConf(), conf) || (!strings.Contains(svcs, "telegraf") && !telegraf.IsActive()) {
|
||||
log.Debugf("telegraf config: %s", conf)
|
||||
|
||||
@@ -143,7 +143,7 @@ func (p *SHostPingTask) ping(div int, hostId string) error {
|
||||
|
||||
Instance().OnCatalogChanged(cl)
|
||||
} else {
|
||||
log.Errorln(err)
|
||||
log.Errorf("get catalog from res %s: %v", res.String(), err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -31,6 +31,7 @@ import (
|
||||
|
||||
identity_api "yunion.io/x/onecloud/pkg/apis/identity"
|
||||
"yunion.io/x/onecloud/pkg/appsrv"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/tsdb"
|
||||
"yunion.io/x/onecloud/pkg/hostman/guestman/desc"
|
||||
"yunion.io/x/onecloud/pkg/hostman/hostutils"
|
||||
"yunion.io/x/onecloud/pkg/hostman/options"
|
||||
@@ -312,7 +313,12 @@ func (s *Service) monitorReverseEndpoint() *proxy.SEndpointFactory {
|
||||
if guestDesc == nil {
|
||||
return "", httperrors.NewNotFoundError("vm not found")
|
||||
}
|
||||
return auth.GetServiceURL("influxdb", options.HostOptions.Region, guestDesc.Zone, identity_api.EndpointInterfaceInternal)
|
||||
s := auth.GetAdminSession(ctx, options.HostOptions.Region)
|
||||
srcURL, err := tsdb.GetDefaultServiceSourceURL(s, identity_api.EndpointInterfaceInternal)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "monitorReverseEndpoint get tsdb url")
|
||||
}
|
||||
return srcURL, nil
|
||||
}
|
||||
return proxy.NewEndpointFactory(f, "monitorService")
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ import (
|
||||
"yunion.io/x/pkg/errors"
|
||||
"yunion.io/x/pkg/util/httputils"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/apis"
|
||||
"yunion.io/x/onecloud/pkg/util/procutils"
|
||||
)
|
||||
|
||||
@@ -76,11 +77,17 @@ func (s *STelegraf) GetConfig(kwargs map[string]interface{}) string {
|
||||
conf += fmt.Sprintf(" hostname = \"%s\"\n", hostname)
|
||||
conf += " omit_hostname = false\n"
|
||||
conf += "\n"
|
||||
if ifluxb, ok := kwargs["influxdb"]; ok {
|
||||
influxdb, _ := ifluxb.(map[string]interface{})
|
||||
if influx, ok := kwargs[apis.SERVICE_TYPE_INFLUXDB]; ok {
|
||||
influxdb, _ := influx.(map[string]interface{})
|
||||
inUrls, _ := influxdb["url"]
|
||||
tUrls, _ := inUrls.([]string)
|
||||
inDatabase, _ := influxdb["database"]
|
||||
isVM := false
|
||||
if tsdbType, ok := influxdb["tsdb_type"]; ok {
|
||||
if tsdbType.(string) == apis.SERVICE_TYPE_VICTORIA_METRICS {
|
||||
isVM = true
|
||||
}
|
||||
}
|
||||
tdb, _ := inDatabase.(string)
|
||||
urls := []string{}
|
||||
for _, u := range tUrls {
|
||||
@@ -90,6 +97,9 @@ func (s *STelegraf) GetConfig(kwargs map[string]interface{}) string {
|
||||
conf += fmt.Sprintf(" urls = [%s]\n", strings.Join(urls, ", "))
|
||||
conf += fmt.Sprintf(" database = \"%s\"\n", tdb)
|
||||
conf += " insecure_skip_verify = true\n"
|
||||
if isVM {
|
||||
conf += " skip_database_creation = true\n"
|
||||
}
|
||||
conf += " timeout = \"30s\"\n"
|
||||
conf += "\n"
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ import (
|
||||
"yunion.io/x/pkg/errors"
|
||||
"yunion.io/x/pkg/gotypes"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/apis"
|
||||
"yunion.io/x/onecloud/pkg/hostman/hostinfo/hostconsts"
|
||||
"yunion.io/x/onecloud/pkg/hostman/system_service"
|
||||
agentmodels "yunion.io/x/onecloud/pkg/lbagent/models"
|
||||
@@ -445,7 +446,7 @@ func (h *HaproxyHelper) remoteReloadTelegraf(ctx context.Context, agentParams *a
|
||||
}
|
||||
conf["nics"] = h.getNicsTelegrafConf()
|
||||
if len(agentParams.AgentModel.Params.Telegraf.InfluxDbOutputUrl) > 0 {
|
||||
conf["influxdb"] = map[string]interface{}{
|
||||
conf[apis.SERVICE_TYPE_INFLUXDB] = map[string]interface{}{
|
||||
"url": []string{
|
||||
agentParams.AgentModel.Params.Telegraf.InfluxDbOutputUrl,
|
||||
},
|
||||
|
||||
@@ -456,6 +456,10 @@ func (s *AlertQuerySelect) MAX() *AlertQuerySelect {
|
||||
return s.addFunc("max")
|
||||
}
|
||||
|
||||
func (s *AlertQuerySelect) LAST() *AlertQuerySelect {
|
||||
return s.addFunc("last")
|
||||
}
|
||||
|
||||
// AS is alias method
|
||||
func (s *AlertQuerySelect) AS(alias string) *AlertQuerySelect {
|
||||
s.MetricQuerySelect = append(s.MetricQuerySelect, monitor.MetricQueryPart{
|
||||
@@ -538,6 +542,16 @@ func (w *AlertQueryWhere) IN(key string, vals []string) *AlertQueryWhere {
|
||||
return w.REGEX(key, valStr)
|
||||
}
|
||||
|
||||
func (w *AlertQueryWhere) AddTag(tag *monitor.MetricQueryTag) *AlertQueryWhere {
|
||||
if tag == nil {
|
||||
return w
|
||||
}
|
||||
if tag.Condition != "" {
|
||||
w.cond = tag.Condition
|
||||
}
|
||||
return w.filter(tag.Operator, tag.Key, tag.Value)
|
||||
}
|
||||
|
||||
func (w *AlertQueryWhere) newTag(op string, key string, value string) monitor.MetricQueryTag {
|
||||
return monitor.MetricQueryTag{
|
||||
Key: key,
|
||||
|
||||
@@ -20,7 +20,6 @@ import (
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/apis/monitor"
|
||||
"yunion.io/x/onecloud/pkg/monitor/tsdb/driver/influxdb"
|
||||
)
|
||||
|
||||
func TestHelperWhere(t *testing.T) {
|
||||
@@ -86,22 +85,6 @@ func TestHelperSelects(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestAlertQuery(t *testing.T) {
|
||||
Convey("Alert query test", t, func() {
|
||||
parser := new(influxdb.InfluxdbQueryParser)
|
||||
q := NewAlertQuery("telegraf", "diskio").From("5m").To("now")
|
||||
q.Selects().Select("await").MEAN()
|
||||
q.Where().Equal("hostname", "host1").Equal("provider", "kvm")
|
||||
q.GroupBy().TAG("*").FILL_NULL()
|
||||
qCtx := q.ToTsdbQuery()
|
||||
influxdbQ, err := parser.Parse(qCtx.Queries[0], nil)
|
||||
So(err, ShouldBeNil)
|
||||
rawQuery, err := influxdbQ.Build(qCtx)
|
||||
So(err, ShouldBeNil)
|
||||
So(rawQuery, ShouldEqual, `SELECT mean("await") FROM "diskio" WHERE ("hostname" = 'host1' AND "provider" = 'kvm') AND time > now() - 5m GROUP BY * fill(null)`)
|
||||
})
|
||||
}
|
||||
|
||||
func TestAlertConfig(t *testing.T) {
|
||||
Convey("Alert config test", t, func() {
|
||||
enabled := true
|
||||
|
||||
@@ -29,6 +29,10 @@ type DataSourceListOptions struct {
|
||||
options.BaseListOptions
|
||||
}
|
||||
|
||||
func (d DataSourceListOptions) Params() (jsonutils.JSONObject, error) {
|
||||
return d.BaseListOptions.Params()
|
||||
}
|
||||
|
||||
type DataSourceDeleteOptions struct {
|
||||
ID string `json:"-"`
|
||||
}
|
||||
|
||||
@@ -212,7 +212,7 @@ func (this *ClientSession) getServiceVersionURLs(service, region, zone, endpoint
|
||||
return []string{this.client.authUrl}, nil
|
||||
}
|
||||
urls, err := catalog.getServiceURLs(service, region, zone, endpointType)
|
||||
// HACK! in case of fail to get kestone url or schema of keystone changed, always trust authUrl
|
||||
// HACK! in case of fail to get keystone url or schema of keystone changed, always trust authUrl
|
||||
if service == api.SERVICE_TYPE && (err != nil || len(urls) == 0 || (len(this.client.authUrl) != 0 && this.client.authUrl[:5] != urls[0][:5])) {
|
||||
var msg string
|
||||
if err != nil {
|
||||
|
||||
@@ -31,8 +31,8 @@ import (
|
||||
"yunion.io/x/onecloud/pkg/mcclient"
|
||||
"yunion.io/x/onecloud/pkg/mcclient/auth"
|
||||
"yunion.io/x/onecloud/pkg/monitor/alerting"
|
||||
"yunion.io/x/onecloud/pkg/monitor/datasource"
|
||||
mq "yunion.io/x/onecloud/pkg/monitor/metricquery"
|
||||
"yunion.io/x/onecloud/pkg/monitor/models"
|
||||
"yunion.io/x/onecloud/pkg/monitor/tsdb"
|
||||
"yunion.io/x/onecloud/pkg/monitor/validators"
|
||||
)
|
||||
@@ -67,7 +67,6 @@ func NewMetricQueryCondition(models []*monitor.AlertCondition) (*MetricQueryCond
|
||||
return nil, errors.Wrapf(err, "to value %q", qc.Query.To)
|
||||
}
|
||||
qc.setResType()
|
||||
qc.Query.DataSourceId = q.DataSourceId
|
||||
cond.QueryCons = append(cond.QueryCons, *qc)
|
||||
}
|
||||
|
||||
@@ -92,7 +91,7 @@ func (query *MetricQueryCondition) ExecuteQuery(userCred mcclient.TokenCredentia
|
||||
startTime := time.Now()
|
||||
queryResult, err := query.executeQuery(&evalContext, timeRange)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, errors.Wrap(err, "query.executeQuery")
|
||||
}
|
||||
log.Debugf("query metrics from influxdb elapsed: %s", time.Since(startTime))
|
||||
return queryResult, nil
|
||||
@@ -126,6 +125,10 @@ func (query *MetricQueryCondition) ExecuteQuery(userCred mcclient.TokenCredentia
|
||||
)
|
||||
go func() {
|
||||
startTime := time.Now()
|
||||
defer func() {
|
||||
qRegionCh <- true
|
||||
log.Debugf("get resources from region elapsed: %s", time.Since(startTime))
|
||||
}()
|
||||
|
||||
s := auth.GetSession(ctx, userCred, "")
|
||||
ress, regionErr = firstCond.getOnecloudResources(s, false)
|
||||
@@ -142,13 +145,15 @@ func (query *MetricQueryCondition) ExecuteQuery(userCred mcclient.TokenCredentia
|
||||
tmpRes := res
|
||||
resMap[id] = tmpRes
|
||||
}
|
||||
log.Debugf("get resources from region elapsed: %s", time.Since(startTime))
|
||||
qRegionCh <- true
|
||||
}()
|
||||
|
||||
<-qInfluxdbCh
|
||||
<-qRegionCh
|
||||
|
||||
if err != nil || regionErr != nil {
|
||||
return nil, errors.Errorf("tsdb error: %v, region error: %v", err, regionErr)
|
||||
}
|
||||
|
||||
startTime := time.Now()
|
||||
//for _, serie := range queryResult.series {
|
||||
// isLatestOfSerie, resource := firstCond.serieIsLatestResource(resMap, serie)
|
||||
@@ -213,9 +218,9 @@ func (query *MetricQueryCondition) noCheckSeries(skipCheckSeries bool) bool {
|
||||
}
|
||||
|
||||
func (c *MetricQueryCondition) executeQuery(context *alerting.EvalContext, timeRange *tsdb.TimeRange) (*queryResult, error) {
|
||||
ds, err := models.DataSourceManager.GetSource(c.QueryCons[0].Query.DataSourceId)
|
||||
ds, err := datasource.GetDefaultSource("")
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "Cound not find datasource %v", c.QueryCons[0].Query.DataSourceId)
|
||||
return nil, errors.Wrapf(err, "Can't find default datasource")
|
||||
}
|
||||
|
||||
req := c.getRequestQuery(ds, timeRange, context.IsDebug)
|
||||
@@ -226,13 +231,12 @@ func (c *MetricQueryCondition) executeQuery(context *alerting.EvalContext, timeR
|
||||
setContextLog(context, req)
|
||||
}
|
||||
|
||||
resp, err := c.HandleRequest(context.Ctx, ds.ToTSDBDataSource(""), req)
|
||||
resp, err := c.HandleRequest(context.Ctx, ds, req)
|
||||
if err != nil {
|
||||
if err == gocontext.DeadlineExceeded {
|
||||
return nil, errors.Error("Alert execution exceeded the timeout")
|
||||
}
|
||||
log.Errorf("metricQuery HandleRequest error:%v", err)
|
||||
return nil, err
|
||||
return nil, errors.Wrap(err, "metricQuery HandleRequest")
|
||||
}
|
||||
for _, v := range resp.Results {
|
||||
if v.Error != nil {
|
||||
@@ -300,13 +304,15 @@ func setContextLog(context *alerting.EvalContext, req *tsdb.TsdbQuery) {
|
||||
})
|
||||
}
|
||||
|
||||
func (query *MetricQueryCondition) getRequestQuery(ds *models.SDataSource, timeRange *tsdb.TimeRange, debug bool) *tsdb.TsdbQuery {
|
||||
func (query *MetricQueryCondition) getRequestQuery(ds *tsdb.DataSource, timeRange *tsdb.TimeRange, debug bool) *tsdb.TsdbQuery {
|
||||
querys := make([]*tsdb.Query, 0)
|
||||
for _, qc := range query.QueryCons {
|
||||
nDs := *ds
|
||||
nDs.Database = qc.Query.Model.Database
|
||||
querys = append(querys, &tsdb.Query{
|
||||
RefId: strconv.FormatInt(int64(qc.Index), 10),
|
||||
MetricQuery: qc.Query.Model,
|
||||
DataSource: *ds.ToTSDBDataSource(qc.Query.Model.Database),
|
||||
DataSource: nDs,
|
||||
})
|
||||
}
|
||||
req := &tsdb.TsdbQuery{
|
||||
|
||||
@@ -31,6 +31,7 @@ import (
|
||||
mc_mds "yunion.io/x/onecloud/pkg/mcclient/modules/compute"
|
||||
"yunion.io/x/onecloud/pkg/mcclient/modules/identity"
|
||||
"yunion.io/x/onecloud/pkg/monitor/alerting"
|
||||
"yunion.io/x/onecloud/pkg/monitor/datasource"
|
||||
"yunion.io/x/onecloud/pkg/monitor/models"
|
||||
"yunion.io/x/onecloud/pkg/monitor/options"
|
||||
"yunion.io/x/onecloud/pkg/monitor/tsdb"
|
||||
@@ -59,10 +60,9 @@ type QueryCondition struct {
|
||||
// AlertQuery contains information about what datasource a query
|
||||
// should be send to and the query object.
|
||||
type AlertQuery struct {
|
||||
Model monitor.MetricQuery
|
||||
DataSourceId string
|
||||
From string
|
||||
To string
|
||||
Model monitor.MetricQuery
|
||||
From string
|
||||
To string
|
||||
}
|
||||
|
||||
type FormatCond struct {
|
||||
@@ -369,12 +369,7 @@ type queryResult struct {
|
||||
}
|
||||
|
||||
func (c *QueryCondition) executeQuery(evalCtx *alerting.EvalContext, timeRange *tsdb.TimeRange) (*queryResult, error) {
|
||||
ds, err := models.DataSourceManager.GetSource(c.Query.DataSourceId)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "Cound not find datasource %v", c.Query.DataSourceId)
|
||||
}
|
||||
|
||||
req := c.getRequestForAlertRule(ds, timeRange, evalCtx.IsDebug)
|
||||
req := c.getRequestForAlertRule(timeRange, evalCtx.IsDebug)
|
||||
result := make(tsdb.TimeSeriesSlice, 0)
|
||||
metas := make([]tsdb.QueryResultMeta, 0)
|
||||
|
||||
@@ -412,7 +407,12 @@ func (c *QueryCondition) executeQuery(evalCtx *alerting.EvalContext, timeRange *
|
||||
})
|
||||
}
|
||||
|
||||
resp, err := c.HandleRequest(evalCtx.Ctx, ds.ToTSDBDataSource(c.Query.Model.Database), req)
|
||||
ds, err := datasource.GetDefaultSource(c.Query.Model.Database)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "GetDefaultDataSource")
|
||||
}
|
||||
|
||||
resp, err := c.HandleRequest(evalCtx.Ctx, ds, req)
|
||||
if err != nil {
|
||||
if err == context.DeadlineExceeded {
|
||||
return nil, errors.Error("Alert execution exceeded the timeout")
|
||||
@@ -452,14 +452,15 @@ func (c *QueryCondition) executeQuery(evalCtx *alerting.EvalContext, timeRange *
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *QueryCondition) getRequestForAlertRule(ds *models.SDataSource, timeRange *tsdb.TimeRange, debug bool) *tsdb.TsdbQuery {
|
||||
func (c *QueryCondition) getRequestForAlertRule(timeRange *tsdb.TimeRange, debug bool) *tsdb.TsdbQuery {
|
||||
ds, _ := datasource.GetDefaultSource(c.Query.Model.Database)
|
||||
req := &tsdb.TsdbQuery{
|
||||
TimeRange: timeRange,
|
||||
Queries: []*tsdb.Query{
|
||||
{
|
||||
RefId: "A",
|
||||
MetricQuery: c.Query.Model,
|
||||
DataSource: *ds.ToTSDBDataSource(c.Query.Model.Database),
|
||||
DataSource: *ds,
|
||||
},
|
||||
},
|
||||
Debug: debug,
|
||||
@@ -485,7 +486,6 @@ func newQueryCondition(model *monitor.AlertCondition, index int) (*QueryConditio
|
||||
return nil, errors.Wrapf(err, "to value %q", cond.Query.To)
|
||||
}
|
||||
|
||||
cond.Query.DataSourceId = q.DataSourceId
|
||||
//reducer := model.Reducer
|
||||
//cond.Reducer = newSimpleReducer(reducer.Type)
|
||||
reducer, err := NewAlertReducer(&model.Reducer)
|
||||
|
||||
@@ -46,7 +46,7 @@ func init() {
|
||||
registry.RegisterService(&AlertEngine{})
|
||||
}
|
||||
|
||||
// IsDisabled returns true if the alerting service is disable for this instance.
|
||||
// IsDisabled returns true if the alerting service is disabled for this instance.
|
||||
func (e *AlertEngine) IsDisabled() bool {
|
||||
// TODO: read from config options
|
||||
return false
|
||||
|
||||
@@ -32,6 +32,7 @@ import (
|
||||
"yunion.io/x/onecloud/pkg/mcclient/modules/compute"
|
||||
compute_options "yunion.io/x/onecloud/pkg/mcclient/options/compute"
|
||||
"yunion.io/x/onecloud/pkg/monitor/alerting"
|
||||
"yunion.io/x/onecloud/pkg/monitor/datasource"
|
||||
"yunion.io/x/onecloud/pkg/monitor/models"
|
||||
"yunion.io/x/onecloud/pkg/monitor/tsdb"
|
||||
)
|
||||
@@ -145,11 +146,10 @@ func NewRules(_ *alerting.EvalContext, m *monitor.EvalMatch, alert *models.SMigr
|
||||
allHosts = append(allHosts, oh)
|
||||
}
|
||||
|
||||
dsObj, err := models.DataSourceManager.GetDefaultSource()
|
||||
ds, err := datasource.GetDefaultSource("telegraf")
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "Get default DataSource")
|
||||
}
|
||||
ds := dsObj.ToTSDBDataSource("")
|
||||
// find guests to filtered by source setting of source host alerted
|
||||
cds, err := findGuestsOfHost(drv, srcHost, ds, msettings)
|
||||
if err != nil {
|
||||
|
||||
@@ -23,7 +23,6 @@ import (
|
||||
api "yunion.io/x/onecloud/pkg/apis/monitor"
|
||||
"yunion.io/x/onecloud/pkg/mcclient/modules/monitor"
|
||||
"yunion.io/x/onecloud/pkg/monitor/tsdb"
|
||||
"yunion.io/x/onecloud/pkg/monitor/tsdb/driver/influxdb"
|
||||
)
|
||||
|
||||
type HostMetric struct {
|
||||
@@ -78,14 +77,12 @@ func InfluxdbQuery(
|
||||
q.Where().IN(idKey, ids)
|
||||
q.GroupBy().TAG(idKey).FILL_NULL()
|
||||
qCtx := q.ToTsdbQuery()
|
||||
endpoint, err := influxdb.NewInfluxdbExecutor(nil)
|
||||
|
||||
resp, err := tsdb.HandleRequest(context.Background(), ds, qCtx)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "influxdb.NewInfluxdbExecutor")
|
||||
}
|
||||
resp, err := endpoint.Query(context.TODO(), ds, qCtx)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "influxdb endpoint Query")
|
||||
return nil, errors.Wrap(err, "TSDB endpoint Query")
|
||||
}
|
||||
|
||||
ss := resp.Results[""].Series
|
||||
ms := make([]*HostMetric, len(ss))
|
||||
for i, s := range ss {
|
||||
|
||||
@@ -0,0 +1,156 @@
|
||||
package datasource
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"golang.org/x/sync/errgroup"
|
||||
|
||||
"yunion.io/x/log"
|
||||
"yunion.io/x/pkg/errors"
|
||||
"yunion.io/x/pkg/util/stringutils"
|
||||
"yunion.io/x/pkg/util/wait"
|
||||
|
||||
commontsdb "yunion.io/x/onecloud/pkg/cloudcommon/tsdb"
|
||||
"yunion.io/x/onecloud/pkg/mcclient/auth"
|
||||
"yunion.io/x/onecloud/pkg/monitor/options"
|
||||
"yunion.io/x/onecloud/pkg/monitor/registry"
|
||||
"yunion.io/x/onecloud/pkg/monitor/tsdb"
|
||||
)
|
||||
|
||||
func init() {
|
||||
registry.RegisterService(GetManager())
|
||||
}
|
||||
|
||||
var (
|
||||
dsMan *dataSourceManager
|
||||
)
|
||||
|
||||
type DataSourceManager interface {
|
||||
registry.Service
|
||||
|
||||
GetDefaultSource(db string) *tsdb.DataSource
|
||||
}
|
||||
|
||||
func GetManager() DataSourceManager {
|
||||
if dsMan == nil {
|
||||
dsMan = newDataSourceManager()
|
||||
}
|
||||
return dsMan
|
||||
}
|
||||
|
||||
func GetDefaultSource(db string) (*tsdb.DataSource, error) {
|
||||
src := GetManager().GetDefaultSource(db)
|
||||
if src == nil {
|
||||
return nil, errors.Errorf("default data source is not initialization")
|
||||
}
|
||||
return src, nil
|
||||
}
|
||||
|
||||
func GetDefaultQueryEndpoint() (tsdb.TsdbQueryEndpoint, error) {
|
||||
ds, err := GetDefaultSource("")
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "get default datasource")
|
||||
}
|
||||
return tsdb.GetTsdbQueryEndpointFor(ds)
|
||||
}
|
||||
|
||||
func newDataSourceManager() *dataSourceManager {
|
||||
return &dataSourceManager{
|
||||
defaultDS: nil,
|
||||
}
|
||||
}
|
||||
|
||||
type dataSourceManager struct {
|
||||
defaultDS *tsdb.DataSource
|
||||
}
|
||||
|
||||
func (m *dataSourceManager) Init() error {
|
||||
return m.initDefaultDataSource(context.Background())
|
||||
}
|
||||
func (m *dataSourceManager) Run(ctx context.Context) error {
|
||||
errgrp, ctx := errgroup.WithContext(ctx)
|
||||
errgrp.Go(func() error {
|
||||
m.run(ctx)
|
||||
return nil
|
||||
})
|
||||
return errgrp.Wait()
|
||||
}
|
||||
|
||||
func (m *dataSourceManager) GetDefaultSource(db string) *tsdb.DataSource {
|
||||
ds := m.defaultDS
|
||||
return &tsdb.DataSource{
|
||||
Id: ds.Id,
|
||||
Name: ds.Name,
|
||||
Type: ds.Type,
|
||||
Url: ds.Url,
|
||||
User: ds.User,
|
||||
Password: ds.Password,
|
||||
Database: db,
|
||||
BasicAuth: ds.BasicAuth,
|
||||
BasicAuthUser: ds.BasicAuthUser,
|
||||
BasicAuthPassword: ds.BasicAuthPassword,
|
||||
TimeInterval: ds.TimeInterval,
|
||||
Updated: ds.Updated,
|
||||
}
|
||||
}
|
||||
|
||||
func (man *dataSourceManager) shouldChangeDataSource(dsType string, url string) bool {
|
||||
if man.defaultDS == nil {
|
||||
return true
|
||||
}
|
||||
if man.defaultDS.Type != dsType {
|
||||
return true
|
||||
}
|
||||
if man.defaultDS.Url != url {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (man *dataSourceManager) setDataSource(dsType string, url string) {
|
||||
if !man.shouldChangeDataSource(dsType, url) {
|
||||
return
|
||||
}
|
||||
|
||||
log.Infof("set TSDB data source %q: %q", dsType, url)
|
||||
man.defaultDS = &tsdb.DataSource{
|
||||
Id: stringutils.UUID4(),
|
||||
Name: dsType,
|
||||
Type: dsType,
|
||||
Url: url,
|
||||
Updated: time.Now(),
|
||||
}
|
||||
}
|
||||
|
||||
func (man *dataSourceManager) initDefaultDataSource(ctx context.Context) error {
|
||||
region := options.Options.Region
|
||||
epType := options.Options.SessionEndpointType
|
||||
s := auth.GetAdminSession(ctx, region)
|
||||
//dsSvc := options.Options.MonitorDataSource
|
||||
if s == nil {
|
||||
return errors.Errorf("get empty public session for region %s", region)
|
||||
}
|
||||
source, err := commontsdb.GetDefaultServiceSource(s, epType)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "get default TSDB source")
|
||||
}
|
||||
dsSvc := source.Type
|
||||
if err := tsdb.IsValidDataSource(dsSvc); err != nil {
|
||||
return errors.Wrapf(err, "invalid type %q", dsSvc)
|
||||
}
|
||||
url, err := s.GetServiceURL(dsSvc, epType)
|
||||
if err != nil {
|
||||
return errors.Errorf("get %q public url: %v", dsSvc, err)
|
||||
}
|
||||
man.setDataSource(dsSvc, url)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *dataSourceManager) run(ctx context.Context) {
|
||||
wait.Forever(func() {
|
||||
if err := m.initDefaultDataSource(ctx); err != nil {
|
||||
log.Errorf("init default source")
|
||||
}
|
||||
}, 30*time.Second)
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
package datasource // import "yunion.io/x/onecloud/pkg/monitor/datasource"
|
||||
@@ -201,16 +201,16 @@ type AlertRuleTag struct {
|
||||
Value string
|
||||
}
|
||||
|
||||
func setAlertDefaultSetting(setting *monitor.AlertSetting, dsId string) *monitor.AlertSetting {
|
||||
func setAlertDefaultSetting(setting *monitor.AlertSetting) *monitor.AlertSetting {
|
||||
for idx, cond := range setting.Conditions {
|
||||
cond = setAlertDefaultCondition(cond, dsId)
|
||||
cond = setAlertDefaultCondition(cond)
|
||||
setting.Conditions[idx] = cond
|
||||
}
|
||||
return setting
|
||||
}
|
||||
|
||||
func setAlertDefaultCreateData(data monitor.AlertCreateInput, dsId string) monitor.AlertCreateInput {
|
||||
setting := setAlertDefaultSetting(&data.Settings, dsId)
|
||||
func setAlertDefaultCreateData(data monitor.AlertCreateInput) monitor.AlertCreateInput {
|
||||
setting := setAlertDefaultSetting(&data.Settings)
|
||||
data.Settings = *setting
|
||||
enable := true
|
||||
if data.Enabled == nil {
|
||||
@@ -219,7 +219,7 @@ func setAlertDefaultCreateData(data monitor.AlertCreateInput, dsId string) monit
|
||||
return data
|
||||
}
|
||||
|
||||
func setAlertDefaultCondition(cond monitor.AlertCondition, dsId string) monitor.AlertCondition {
|
||||
func setAlertDefaultCondition(cond monitor.AlertCondition) monitor.AlertCondition {
|
||||
if cond.Type == "" {
|
||||
cond.Type = "query"
|
||||
}
|
||||
@@ -229,18 +229,11 @@ func setAlertDefaultCondition(cond monitor.AlertCondition, dsId string) monitor.
|
||||
if cond.Operator == "" {
|
||||
cond.Operator = "and"
|
||||
}
|
||||
if cond.Query.DataSourceId == "" {
|
||||
cond.Query.DataSourceId = dsId
|
||||
}
|
||||
return cond
|
||||
}
|
||||
|
||||
func (man *SAlertManager) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, _ jsonutils.JSONObject, data monitor.AlertCreateInput) (monitor.AlertCreateInput, error) {
|
||||
ds, err := DataSourceManager.GetDefaultSource()
|
||||
if err != nil {
|
||||
return data, errors.Wrap(err, "get default data source")
|
||||
}
|
||||
data = setAlertDefaultCreateData(data, ds.GetId())
|
||||
data = setAlertDefaultCreateData(data)
|
||||
if err := validators.ValidateAlertCreateInput(data); err != nil {
|
||||
return data, err
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@ package models
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"math"
|
||||
"regexp"
|
||||
@@ -31,7 +30,6 @@ import (
|
||||
"yunion.io/x/log"
|
||||
"yunion.io/x/pkg/errors"
|
||||
"yunion.io/x/pkg/tristate"
|
||||
"yunion.io/x/pkg/util/wait"
|
||||
"yunion.io/x/pkg/utils"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/apis/monitor"
|
||||
@@ -40,10 +38,8 @@ import (
|
||||
"yunion.io/x/onecloud/pkg/httperrors"
|
||||
"yunion.io/x/onecloud/pkg/mcclient"
|
||||
"yunion.io/x/onecloud/pkg/mcclient/auth"
|
||||
"yunion.io/x/onecloud/pkg/monitor/datasource"
|
||||
merrors "yunion.io/x/onecloud/pkg/monitor/errors"
|
||||
"yunion.io/x/onecloud/pkg/monitor/options"
|
||||
"yunion.io/x/onecloud/pkg/monitor/registry"
|
||||
"yunion.io/x/onecloud/pkg/monitor/tsdb"
|
||||
"yunion.io/x/onecloud/pkg/monitor/validators"
|
||||
"yunion.io/x/onecloud/pkg/util/influxdb"
|
||||
"yunion.io/x/onecloud/pkg/util/stringutils2"
|
||||
@@ -72,80 +68,12 @@ func init() {
|
||||
),
|
||||
}
|
||||
DataSourceManager.SetVirtualObject(DataSourceManager)
|
||||
registry.RegisterService(DataSourceManager)
|
||||
}
|
||||
|
||||
type SDataSourceManager struct {
|
||||
db.SStandaloneResourceBaseManager
|
||||
}
|
||||
|
||||
func (_ *SDataSourceManager) IsDisabled() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (_ *SDataSourceManager) Init() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (man *SDataSourceManager) Run(ctx context.Context) error {
|
||||
errgrp, ctx := errgroup.WithContext(ctx)
|
||||
errgrp.Go(func() error { return man.initDefaultDataSource(ctx) })
|
||||
return errgrp.Wait()
|
||||
}
|
||||
|
||||
func (man *SDataSourceManager) initDefaultDataSource(ctx context.Context) error {
|
||||
region := options.Options.Region
|
||||
epType := options.Options.SessionEndpointType
|
||||
initF := func() {
|
||||
ds, err := man.GetDefaultSource()
|
||||
if err != nil && err != ErrDataSourceDefaultNotFound {
|
||||
log.Errorf("Get default datasource: %v", err)
|
||||
return
|
||||
}
|
||||
s := auth.GetAdminSession(ctx, region)
|
||||
if s == nil {
|
||||
log.Errorf("get empty public session for region %s", region)
|
||||
return
|
||||
}
|
||||
url, err := s.GetServiceURL("influxdb", epType)
|
||||
if err != nil {
|
||||
log.Errorf("get influxdb public url: %v", err)
|
||||
return
|
||||
}
|
||||
if ds != nil {
|
||||
if _, err := db.Update(ds, func() error {
|
||||
ds.Url = url
|
||||
return nil
|
||||
}); err != nil {
|
||||
log.Errorf("update datasource url error: %v", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
ds = &SDataSource{
|
||||
Type: monitor.DataSourceTypeInfluxdb,
|
||||
Url: url,
|
||||
}
|
||||
ds.Name = DefaultDataSource
|
||||
if err := man.TableSpec().Insert(ctx, ds); err != nil {
|
||||
log.Errorf("insert default influxdb: %v", err)
|
||||
}
|
||||
}
|
||||
wait.Forever(initF, 30*time.Second)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (man *SDataSourceManager) GetDefaultSource() (*SDataSource, error) {
|
||||
obj, err := man.FetchByName(nil, DefaultDataSource)
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, ErrDataSourceDefaultNotFound
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return obj.(*SDataSource), nil
|
||||
}
|
||||
|
||||
type SDataSource struct {
|
||||
db.SStandaloneResourceBase
|
||||
|
||||
@@ -171,29 +99,9 @@ func (m *SDataSourceManager) GetSource(id string) (*SDataSource, error) {
|
||||
return ret.(*SDataSource), nil
|
||||
}
|
||||
|
||||
func (ds *SDataSource) ToTSDBDataSource(db string) *tsdb.DataSource {
|
||||
if db == "" {
|
||||
db = ds.Database
|
||||
}
|
||||
return &tsdb.DataSource{
|
||||
Id: ds.GetId(),
|
||||
Name: ds.GetName(),
|
||||
Type: ds.Type,
|
||||
Url: ds.Url,
|
||||
User: ds.User,
|
||||
Password: ds.Password,
|
||||
Database: db,
|
||||
Updated: ds.UpdatedAt,
|
||||
/*BasicAuth: ds.BasicAuth,
|
||||
BasicAuthUser: ds.BasicAuthUser,
|
||||
BasicAuthPassword: ds.BasicAuthPassword,
|
||||
TimeInterval: ds.TimeInterval,*/
|
||||
}
|
||||
}
|
||||
|
||||
func (self *SDataSourceManager) GetDatabases() (jsonutils.JSONObject, error) {
|
||||
ret := jsonutils.NewDict()
|
||||
dataSource, err := self.GetDefaultSource()
|
||||
dataSource, err := datasource.GetDefaultSource("")
|
||||
if err != nil {
|
||||
return jsonutils.JSONNull, errors.Wrap(err, "s.GetDefaultSource")
|
||||
}
|
||||
@@ -225,7 +133,7 @@ func (self *SDataSourceManager) getMeasurementQueryInfluxdb(query jsonutils.JSON
|
||||
if database == "" {
|
||||
return rtnMeasurements, merrors.NewArgIsEmptyErr("database")
|
||||
}
|
||||
dataSource, err := self.GetDefaultSource()
|
||||
dataSource, err := datasource.GetDefaultSource("")
|
||||
if err != nil {
|
||||
return rtnMeasurements, errors.Wrap(err, "s.GetDefaultSource")
|
||||
}
|
||||
@@ -262,15 +170,14 @@ func (self *SDataSourceManager) getMeasurementQueryInfluxdb(query jsonutils.JSON
|
||||
return
|
||||
}
|
||||
|
||||
func (self *SDataSourceManager) GetMeasurementsWithDescriptionInfos(query jsonutils.JSONObject, measurementFilter,
|
||||
tagFilter string) (jsonutils.JSONObject, error) {
|
||||
func (self *SDataSourceManager) GetMeasurementsWithDescriptionInfos(query jsonutils.JSONObject, measurementFilter string, tagFilter *monitor.MetricQueryTag) (jsonutils.JSONObject, error) {
|
||||
ret := jsonutils.NewDict()
|
||||
rtnMeasurements := make([]monitor.InfluxMeasurement, 0)
|
||||
measurements, err := MetricMeasurementManager.getInfluxdbMeasurements()
|
||||
if err != nil {
|
||||
return jsonutils.JSONNull, err
|
||||
return jsonutils.JSONNull, errors.Wrap(err, "getInfluxdbMeasurements")
|
||||
}
|
||||
dataSource, err := self.GetDefaultSource()
|
||||
dataSource, err := datasource.GetDefaultSource("")
|
||||
if err != nil {
|
||||
return jsonutils.JSONNull, errors.Wrap(err, "s.GetDefaultSource")
|
||||
}
|
||||
@@ -318,7 +225,7 @@ func (self *SDataSourceManager) GetMeasurementsWithOutTimeFilter(query jsonutils
|
||||
if database == "" {
|
||||
return jsonutils.JSONNull, httperrors.NewInputParameterError("not support database")
|
||||
}
|
||||
dataSource, err := self.GetDefaultSource()
|
||||
dataSource, err := datasource.GetDefaultSource("")
|
||||
if err != nil {
|
||||
return jsonutils.JSONNull, errors.Wrap(err, "s.GetDefaultSource")
|
||||
}
|
||||
@@ -419,13 +326,12 @@ type influxdbQueryChan struct {
|
||||
}
|
||||
|
||||
func (self *SDataSourceManager) filterMeasurementsByTime(db influxdb.SInfluxdb,
|
||||
measurements []monitor.InfluxMeasurement, query jsonutils.JSONObject, tagFilter string) ([]monitor.InfluxMeasurement,
|
||||
error) {
|
||||
measurements []monitor.InfluxMeasurement, query jsonutils.JSONObject, tagFilter *monitor.MetricQueryTag) ([]monitor.InfluxMeasurement, error) {
|
||||
timeF, err := self.getFromAndToFromParam(query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
filterMeasurements, err := self.getFilterMeasurementsAsyn(timeF.From, timeF.To, measurements, db, tagFilter)
|
||||
filterMeasurements, err := self.getFilterMeasurementsAsync(timeF.From, timeF.To, measurements, db, tagFilter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -460,8 +366,8 @@ func (self *SDataSourceManager) getFromAndToFromParam(query jsonutils.JSONObject
|
||||
return timeF, nil
|
||||
}
|
||||
|
||||
func (self *SDataSourceManager) getFilterMeasurementsAsyn(from, to string,
|
||||
measurements []monitor.InfluxMeasurement, db influxdb.SInfluxdb, tagFilter string) ([]monitor.InfluxMeasurement, error) {
|
||||
func (self *SDataSourceManager) getFilterMeasurementsAsync(from, to string,
|
||||
measurements []monitor.InfluxMeasurement, db influxdb.SInfluxdb, tagFilter *monitor.MetricQueryTag) ([]monitor.InfluxMeasurement, error) {
|
||||
filterMeasurements := make([]monitor.InfluxMeasurement, 0)
|
||||
queryChan := new(influxdbQueryChan)
|
||||
queryChan.queryRtnChan = make(chan monitor.InfluxMeasurement, len(measurements))
|
||||
@@ -493,59 +399,17 @@ func (self *SDataSourceManager) getFilterMeasurementsAsyn(from, to string,
|
||||
return filterMeasurements, err
|
||||
}
|
||||
|
||||
func (self *SDataSourceManager) getFilterMeasurement(queryChan *influxdbQueryChan, from, to string,
|
||||
measurement monitor.InfluxMeasurement, db influxdb.SInfluxdb, tagFilter string) error {
|
||||
rtnMeasurement := new(monitor.InfluxMeasurement)
|
||||
var buffer bytes.Buffer
|
||||
buffer.WriteString(fmt.Sprintf(`SELECT last(*) FROM %s WHERE %s`, measurement.Measurement,
|
||||
renderTimeFilter(from, to)))
|
||||
if len(tagFilter) != 0 {
|
||||
buffer.WriteString(" AND ")
|
||||
buffer.WriteString(fmt.Sprintf(" %s", tagFilter))
|
||||
}
|
||||
log.Errorln(buffer.String())
|
||||
(&db).SetDatabase(measurement.Database)
|
||||
rtn, err := db.Query(buffer.String())
|
||||
func (self *SDataSourceManager) getFilterMeasurement(queryChan *influxdbQueryChan, from, to string, measurement monitor.InfluxMeasurement, db influxdb.SInfluxdb, tagFilter *monitor.MetricQueryTag) error {
|
||||
dds, _ := datasource.GetDefaultSource("")
|
||||
ep, err := datasource.GetDefaultQueryEndpoint()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "getFilterMeasurement error")
|
||||
return errors.Wrap(err, "GetDefaultQueryEndpoint")
|
||||
}
|
||||
|
||||
rtnFields := make([]string, 0)
|
||||
if len(rtn) != 0 && len(rtn[0]) != 0 {
|
||||
for rtnIndex, _ := range rtn {
|
||||
for serieIndex, _ := range rtn[rtnIndex] {
|
||||
meanFieldArr := rtn[rtnIndex][serieIndex].Columns
|
||||
for i, _ := range meanFieldArr {
|
||||
if !strings.Contains(meanFieldArr[i], "last") {
|
||||
continue
|
||||
}
|
||||
|
||||
containsVal := false
|
||||
for _, value := range rtn[rtnIndex][serieIndex].Values {
|
||||
if value[i] == nil {
|
||||
continue
|
||||
}
|
||||
_, err := value[i].Float()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
containsVal = true
|
||||
break
|
||||
}
|
||||
if containsVal {
|
||||
rtnFields = append(rtnFields, strings.Replace(meanFieldArr[i], "last_", "", 1))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
retMs, err := ep.FilterMeasurement(context.Background(), dds, from, to, &measurement, tagFilter)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Get endpoint filtered measurement")
|
||||
}
|
||||
rtnMeasurement.FieldKey = rtnFields
|
||||
if len(rtnMeasurement.FieldKey) != 0 {
|
||||
rtnMeasurement.Measurement = measurement.Measurement
|
||||
rtnMeasurement.Database = measurement.Database
|
||||
rtnMeasurement.ResType = measurement.ResType
|
||||
}
|
||||
queryChan.queryRtnChan <- *rtnMeasurement
|
||||
queryChan.queryRtnChan <- *retMs
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -565,7 +429,7 @@ func renderTimeFilter(from, to string) string {
|
||||
|
||||
}
|
||||
|
||||
func (self *SDataSourceManager) GetMetricMeasurement(userCred mcclient.TokenCredential, query jsonutils.JSONObject, tagFilter string) (jsonutils.JSONObject, error) {
|
||||
func (self *SDataSourceManager) GetMetricMeasurement(userCred mcclient.TokenCredential, query jsonutils.JSONObject, tagFilter *monitor.MetricQueryTag) (jsonutils.JSONObject, error) {
|
||||
database, _ := query.GetString("database")
|
||||
if database == "" {
|
||||
return jsonutils.JSONNull, merrors.NewArgIsEmptyErr("database")
|
||||
@@ -582,7 +446,7 @@ func (self *SDataSourceManager) GetMetricMeasurement(userCred mcclient.TokenCred
|
||||
if len(from) == 0 {
|
||||
return jsonutils.JSONNull, merrors.NewArgIsEmptyErr("from")
|
||||
}
|
||||
dataSource, err := self.GetDefaultSource()
|
||||
dataSource, err := datasource.GetDefaultSource("")
|
||||
if err != nil {
|
||||
return jsonutils.JSONNull, errors.Wrap(err, "s.GetDefaultSource")
|
||||
}
|
||||
@@ -639,7 +503,7 @@ func (self *SDataSourceManager) GetMetricMeasurement(userCred mcclient.TokenCred
|
||||
// if err != nil {
|
||||
// return jsonutils.JSONNull, errors.Wrap(err, "getTagValue error")
|
||||
//** }
|
||||
if err := getTagValues(userCred, output, timeF, dataSource.GetId(), tagFilter, skipCheckSeries); err != nil {
|
||||
if err := getTagValues(userCred, output, timeF, tagFilter, skipCheckSeries); err != nil {
|
||||
return jsonutils.JSONNull, errors.Wrap(err, "getTagValues error")
|
||||
}
|
||||
|
||||
@@ -755,7 +619,7 @@ func (self *SDataSourceManager) AddSubscription(subscription InfluxdbSubscriptio
|
||||
jsonutils.NewString(subscription.Rc).String(),
|
||||
strings.ReplaceAll(jsonutils.NewString(subscription.Url).String(), "\"", "'"),
|
||||
)
|
||||
dataSource, err := self.GetDefaultSource()
|
||||
dataSource, err := datasource.GetDefaultSource("")
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "s.GetDefaultSource")
|
||||
}
|
||||
@@ -781,7 +645,7 @@ func (self *SDataSourceManager) DropSubscription(subscription InfluxdbSubscripti
|
||||
jsonutils.NewString(subscription.DataBase).String(),
|
||||
jsonutils.NewString(subscription.Rc).String(),
|
||||
)
|
||||
dataSource, err := self.GetDefaultSource()
|
||||
dataSource, err := datasource.GetDefaultSource("")
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "s.GetDefaultSource")
|
||||
}
|
||||
@@ -828,7 +692,7 @@ func getAttributesOnMeasurement(database, tp string, output *monitor.InfluxMeasu
|
||||
return nil
|
||||
}
|
||||
|
||||
func getTagValues(userCred mcclient.TokenCredential, output *monitor.InfluxMeasurement, timeF timeFilter, dsId string, tagFilter string, skipCheckSeries bool) error {
|
||||
func getTagValues(userCred mcclient.TokenCredential, output *monitor.InfluxMeasurement, timeF timeFilter, tagFilter *monitor.MetricQueryTag, skipCheckSeries bool) error {
|
||||
mq := monitor.MetricQuery{
|
||||
Database: output.Database,
|
||||
Measurement: output.Measurement,
|
||||
@@ -850,22 +714,20 @@ func getTagValues(userCred mcclient.TokenCredential, output *monitor.InfluxMeasu
|
||||
},
|
||||
},
|
||||
}
|
||||
if tagFilter != "" {
|
||||
parts := strings.Split(tagFilter, " ")
|
||||
if tagFilter != nil {
|
||||
mq.Tags = []monitor.MetricQueryTag{
|
||||
{
|
||||
Key: parts[0],
|
||||
Operator: parts[1],
|
||||
Value: parts[2],
|
||||
Key: tagFilter.Key,
|
||||
Operator: tagFilter.Operator,
|
||||
Value: tagFilter.Value,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
aq := &monitor.AlertQuery{
|
||||
Model: mq,
|
||||
From: timeF.From,
|
||||
To: timeF.To,
|
||||
DataSourceId: dsId,
|
||||
Model: mq,
|
||||
From: timeF.From,
|
||||
To: timeF.To,
|
||||
}
|
||||
|
||||
q := monitor.MetricInputQuery{
|
||||
|
||||
@@ -51,7 +51,7 @@ type IMeterAlertDriver interface {
|
||||
GetType() string
|
||||
GetName() string
|
||||
GetFor() time.Duration
|
||||
ToAlertCreateInput(input monitor.MeterAlertCreateInput, dsId string, allAccountIds []string, level string) monitor.AlertCreateInput
|
||||
ToAlertCreateInput(input monitor.MeterAlertCreateInput, allAccountIds []string, level string) monitor.AlertCreateInput
|
||||
}
|
||||
|
||||
type SMeterAlertManager struct {
|
||||
@@ -172,12 +172,7 @@ func (man *SMeterAlertManager) ValidateCreateData(
|
||||
return nil, merrors.NewArgIsEmptyErr("recipients")
|
||||
}
|
||||
|
||||
ds, err := DataSourceManager.GetDefaultSource()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
alertInput := drv.ToAlertCreateInput(data, ds.GetId(), allAccountIds, data.Level)
|
||||
alertInput := drv.ToAlertCreateInput(data, allAccountIds, data.Level)
|
||||
alertInput, err = AlertManager.ValidateCreateData(ctx, userCred, ownerId, query, alertInput)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -203,7 +198,6 @@ func (_ *sMeterDailyFee) GetFor() time.Duration {
|
||||
|
||||
func (f *sMeterDailyFee) ToAlertCreateInput(
|
||||
input monitor.MeterAlertCreateInput,
|
||||
dsId string,
|
||||
allAccountIds []string,
|
||||
level string,
|
||||
) monitor.AlertCreateInput {
|
||||
@@ -213,7 +207,6 @@ func (f *sMeterDailyFee) ToAlertCreateInput(
|
||||
Level: level,
|
||||
Frequency: int64(freq / time.Second),
|
||||
Settings: GetMeterAlertSetting(input,
|
||||
dsId,
|
||||
"account_daily_resfee",
|
||||
"meter_db", allAccountIds, "sumDate"),
|
||||
}
|
||||
@@ -236,7 +229,6 @@ func (_ *sMeterMonthFee) GetFor() time.Duration {
|
||||
|
||||
func (f *sMeterMonthFee) ToAlertCreateInput(
|
||||
input monitor.MeterAlertCreateInput,
|
||||
dsId string,
|
||||
allAccountIds []string,
|
||||
level string,
|
||||
) monitor.AlertCreateInput {
|
||||
@@ -246,7 +238,6 @@ func (f *sMeterMonthFee) ToAlertCreateInput(
|
||||
Level: level,
|
||||
Frequency: int64(freq / time.Second),
|
||||
Settings: GetMeterAlertSetting(input,
|
||||
dsId,
|
||||
"account_month_resfee",
|
||||
"meter_db", allAccountIds, "sumMonth"),
|
||||
}
|
||||
@@ -255,7 +246,6 @@ func (f *sMeterMonthFee) ToAlertCreateInput(
|
||||
|
||||
func GetMeterAlertSetting(
|
||||
input monitor.MeterAlertCreateInput,
|
||||
dsId string,
|
||||
measurement string,
|
||||
db string,
|
||||
accountIds []string,
|
||||
@@ -268,10 +258,9 @@ func GetMeterAlertSetting(
|
||||
Type: "query",
|
||||
Operator: "and",
|
||||
Query: monitor.AlertQuery{
|
||||
Model: q,
|
||||
From: input.Period,
|
||||
To: "now",
|
||||
DataSourceId: dsId,
|
||||
Model: q,
|
||||
From: input.Period,
|
||||
To: "now",
|
||||
},
|
||||
Reducer: reducer,
|
||||
Evaluator: eval,
|
||||
@@ -605,11 +594,6 @@ func (alert *SMeterAlert) ValidateUpdateData(
|
||||
details.Comparator = *input.Comparator
|
||||
}
|
||||
|
||||
ds, err := DataSourceManager.GetDefaultSource()
|
||||
if err != nil {
|
||||
return input, errors.Wrap(err, "get default data source")
|
||||
}
|
||||
|
||||
// hack: update notification here
|
||||
if err := alert.UpdateNotification(AlertNotificationUsedByMeterAlert, input.Channel, input.Recipients); err != nil {
|
||||
return input, errors.Wrap(err, "update notification")
|
||||
@@ -621,7 +605,7 @@ func (alert *SMeterAlert) ValidateUpdateData(
|
||||
return input, err
|
||||
}
|
||||
}
|
||||
tmpS := alert.getUpdateSetting(details, ds.GetId(), allAccountIds)
|
||||
tmpS := alert.getUpdateSetting(details, allAccountIds)
|
||||
input.Settings = &tmpS
|
||||
|
||||
input.V1AlertUpdateInput, err = alert.SV1Alert.ValidateUpdateData(ctx, userCred, query, input.V1AlertUpdateInput)
|
||||
@@ -632,7 +616,7 @@ func (alert *SMeterAlert) ValidateUpdateData(
|
||||
return input, nil
|
||||
}
|
||||
|
||||
func (alert *SMeterAlert) getUpdateSetting(details monitor.MeterAlertDetails, dsId string, accountIds []string) monitor.AlertSetting {
|
||||
func (alert *SMeterAlert) getUpdateSetting(details monitor.MeterAlertDetails, accountIds []string) monitor.AlertSetting {
|
||||
drv := MeterAlertManager.GetDriver(alert.getType())
|
||||
input := monitor.MeterAlertCreateInput{
|
||||
ResourceAlertV1CreateInput: monitor.ResourceAlertV1CreateInput{
|
||||
@@ -649,7 +633,7 @@ func (alert *SMeterAlert) getUpdateSetting(details monitor.MeterAlertDetails, ds
|
||||
AccountId: details.AccountId,
|
||||
}
|
||||
input.Level = details.Level
|
||||
out := drv.ToAlertCreateInput(input, dsId, accountIds, details.Level)
|
||||
out := drv.ToAlertCreateInput(input, accountIds, details.Level)
|
||||
return out.Settings
|
||||
}
|
||||
|
||||
|
||||
@@ -374,8 +374,7 @@ func (manager *SMetricMeasurementManager) getMeasurement(query *sqlchemy.SQuery)
|
||||
return measurements, nil
|
||||
}
|
||||
|
||||
func (manager *SMetricMeasurementManager) getInfluxdbMeasurements() (influxdbMeasurements []monitor.
|
||||
InfluxMeasurement, err error) {
|
||||
func (manager *SMetricMeasurementManager) getInfluxdbMeasurements() (influxdbMeasurements []monitor.InfluxMeasurement, err error) {
|
||||
metric, err := manager.getMeasurement(manager.Query())
|
||||
if err != nil {
|
||||
return
|
||||
|
||||
@@ -828,11 +828,7 @@ func (alert *SNodeAlert) GetCommonAlertUpdateData(ctx context.Context, userCred
|
||||
input.Name = name
|
||||
}
|
||||
|
||||
ds, err := DataSourceManager.GetDefaultSource()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "get default data source")
|
||||
}
|
||||
tmpS := alert.getUpdateInput(name, details, ds.GetId())
|
||||
tmpS := alert.getUpdateInput(name, details)
|
||||
input.Settings = &tmpS.Settings
|
||||
|
||||
uData, err := alert.SCommonAlert.ValidateUpdateData(ctx, userCred, nil, tmpS.JSON(tmpS))
|
||||
@@ -855,7 +851,6 @@ func (alert *SNodeAlert) ValidateUpdateData(
|
||||
func (alert *SNodeAlert) getUpdateInput(
|
||||
name string,
|
||||
details monitor.NodeAlertDetails,
|
||||
dsId string,
|
||||
) monitor.CommonAlertCreateInput {
|
||||
data := monitor.NodeAlertCreateInput{
|
||||
ResourceAlertV1CreateInput: monitor.ResourceAlertV1CreateInput{
|
||||
@@ -872,7 +867,7 @@ func (alert *SNodeAlert) getUpdateInput(
|
||||
}
|
||||
data.Level = details.Level
|
||||
out := data.ToCommonAlertCreateInput(name, details.Field, details.Measurement, details.DB)
|
||||
out.Settings = *setAlertDefaultSetting(&out.Settings, dsId)
|
||||
out.Settings = *setAlertDefaultSetting(&out.Settings)
|
||||
return out
|
||||
}
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@ import (
|
||||
"yunion.io/x/onecloud/pkg/hostman/hostinfo/hostconsts"
|
||||
"yunion.io/x/onecloud/pkg/httperrors"
|
||||
"yunion.io/x/onecloud/pkg/mcclient"
|
||||
"yunion.io/x/onecloud/pkg/monitor/datasource"
|
||||
merrors "yunion.io/x/onecloud/pkg/monitor/errors"
|
||||
mq "yunion.io/x/onecloud/pkg/monitor/metricquery"
|
||||
"yunion.io/x/onecloud/pkg/monitor/options"
|
||||
@@ -79,25 +80,24 @@ func (self *SUnifiedMonitorManager) GetPropertyMeasurements(ctx context.Context,
|
||||
|
||||
filter, err := getTagFilterByRequestQuery(ctx, userCred, query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, errors.Wrap(err, "getTagFilterByRequestQuery")
|
||||
}
|
||||
return DataSourceManager.GetMeasurementsWithDescriptionInfos(query, "", filter)
|
||||
}
|
||||
|
||||
func getTagFilterByRequestQuery(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (filter string, err error) {
|
||||
func getTagFilterByRequestQuery(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (*monitor.MetricQueryTag, error) {
|
||||
|
||||
scope, _ := query.GetString("scope")
|
||||
filter, err = filterByScope(ctx, userCred, scope, query)
|
||||
return
|
||||
return filterByScope(ctx, userCred, scope, query)
|
||||
}
|
||||
|
||||
func filterByScope(ctx context.Context, userCred mcclient.TokenCredential, scope string, data jsonutils.JSONObject) (string, error) {
|
||||
func filterByScope(ctx context.Context, userCred mcclient.TokenCredential, scope string, data jsonutils.JSONObject) (*monitor.MetricQueryTag, error) {
|
||||
domainId := jsonutils.GetAnyString(data, db.DomainFetchKeys)
|
||||
projectId := jsonutils.GetAnyString(data, db.ProjectFetchKeys)
|
||||
if projectId != "" {
|
||||
project, err := db.DefaultProjectFetcher(ctx, projectId, domainId)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return nil, errors.Wrap(err, "db.DefaultProjectFetcher")
|
||||
}
|
||||
projectId = project.GetProjectId()
|
||||
domainId = project.GetProjectDomainId()
|
||||
@@ -105,14 +105,14 @@ func filterByScope(ctx context.Context, userCred mcclient.TokenCredential, scope
|
||||
if domainId != "" {
|
||||
domain, err := db.DefaultDomainFetcher(ctx, domainId)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return nil, errors.Wrap(err, "db.DefaultDomainFetcher")
|
||||
}
|
||||
domainId = domain.GetProjectDomainId()
|
||||
domain.GetProjectId()
|
||||
}
|
||||
switch scope {
|
||||
case "system":
|
||||
return "", nil
|
||||
return nil, nil
|
||||
case "domain":
|
||||
if domainId == "" {
|
||||
domainId = userCred.GetProjectDomainId()
|
||||
@@ -126,9 +126,9 @@ func filterByScope(ctx context.Context, userCred mcclient.TokenCredential, scope
|
||||
}
|
||||
}
|
||||
|
||||
func getTenantIdStr(role string, userCred mcclient.TokenCredential) (string, error) {
|
||||
func getTenantIdStr(role string, userCred mcclient.TokenCredential) (*monitor.MetricQueryTag, error) {
|
||||
if role == "admin" {
|
||||
return "", nil
|
||||
return nil, nil
|
||||
}
|
||||
if role == "domainadmin" {
|
||||
domainId := userCred.GetDomainId()
|
||||
@@ -138,10 +138,10 @@ func getTenantIdStr(role string, userCred mcclient.TokenCredential) (string, err
|
||||
tenantId := userCred.GetProjectId()
|
||||
return getProjectIdFilterByProject(tenantId)
|
||||
}
|
||||
return "", errors.ErrNotFound
|
||||
return nil, errors.Wrapf(errors.ErrNotFound, "not supported role %q", role)
|
||||
}
|
||||
|
||||
func getProjectIdsFilterByDomain(domainId string) (string, error) {
|
||||
func getProjectIdsFilterByDomain(domainId string) (*monitor.MetricQueryTag, error) {
|
||||
//s := auth.GetAdminSession(context.Background(), "", "")
|
||||
//params := jsonutils.Marshal(map[string]string{"domain_id": domainId})
|
||||
//tenants, err := modules.Projects.List(s, params)
|
||||
@@ -160,11 +160,21 @@ func getProjectIdsFilterByDomain(domainId string) (string, error) {
|
||||
//}
|
||||
//buffer.WriteString(" )")
|
||||
//return buffer.String(), nil
|
||||
return fmt.Sprintf(`%s =~ /%s/`, "domain_id", domainId), nil
|
||||
return &monitor.MetricQueryTag{
|
||||
Key: "domain_id",
|
||||
Operator: "=~",
|
||||
Value: fmt.Sprintf("/%s/", domainId),
|
||||
}, nil
|
||||
//return fmt.Sprintf(`%s =~ /%s/`, "domain_id", domainId), nil
|
||||
}
|
||||
|
||||
func getProjectIdFilterByProject(projectId string) (string, error) {
|
||||
return fmt.Sprintf(`%s =~ /%s/`, "tenant_id", projectId), nil
|
||||
func getProjectIdFilterByProject(projectId string) (*monitor.MetricQueryTag, error) {
|
||||
//return fmt.Sprintf(`%s =~ /%s/`, "tenant_id", projectId), nil
|
||||
return &monitor.MetricQueryTag{
|
||||
Key: "tenant_id",
|
||||
Operator: "=~",
|
||||
Value: fmt.Sprintf("/%s/", projectId),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (self *SUnifiedMonitorManager) GetPropertyMetricMeasurement(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (jsonutils.JSONObject, error) {
|
||||
@@ -257,7 +267,6 @@ func (self *SUnifiedMonitorManager) PerformQuery(ctx context.Context, userCred m
|
||||
}
|
||||
}
|
||||
|
||||
setSerieRowName(&rtn.Series, groupByTag)
|
||||
fillSerieTags(&rtn.Series)
|
||||
return jsonutils.Marshal(rtn), nil
|
||||
}
|
||||
@@ -317,11 +326,11 @@ func doQuery(userCred mcclient.TokenCredential, query monitor.MetricInputQuery)
|
||||
factory := mq.GetQueryFactories()["metricquery"]
|
||||
metricQ, err := factory(conditions)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, errors.Wrap(err, "factory")
|
||||
}
|
||||
metrics, err := metricQ.ExecuteQuery(userCred, query.SkipCheckSeries)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, errors.Wrap(err, "ExecuteQuery")
|
||||
}
|
||||
// drop metas contains raw_query
|
||||
if !query.ShowMeta {
|
||||
@@ -349,7 +358,6 @@ func (self *SUnifiedMonitorManager) ValidateInputQuery(query *monitor.AlertQuery
|
||||
|
||||
func setDefaultValue(query *monitor.AlertQuery, inputQuery *monitor.MetricInputQuery,
|
||||
scope string, ownerId mcclient.IIdentityProvider) {
|
||||
setDataSourceId(query)
|
||||
query.From = inputQuery.From
|
||||
query.To = inputQuery.To
|
||||
query.Model.Interval = inputQuery.Interval
|
||||
@@ -449,11 +457,6 @@ func setDefaultValue(query *monitor.AlertQuery, inputQuery *monitor.MetricInputQ
|
||||
}
|
||||
}
|
||||
|
||||
func setDataSourceId(query *monitor.AlertQuery) {
|
||||
datasource, _ := DataSourceManager.GetDefaultSource()
|
||||
query.DataSourceId = datasource.Id
|
||||
}
|
||||
|
||||
func checkQueryGroupBy(query *monitor.AlertQuery, inputQuery *monitor.MetricInputQuery) {
|
||||
if len(query.Model.GroupBy) != 0 {
|
||||
return
|
||||
@@ -476,50 +479,6 @@ func checkQueryGroupBy(query *monitor.AlertQuery, inputQuery *monitor.MetricInpu
|
||||
})
|
||||
}
|
||||
|
||||
func setSerieRowName(series *tsdb.TimeSeriesSlice, groupTag []string) {
|
||||
// Add rowname,The front end displays the curve according to rowname
|
||||
var index, unknownIndex = 1, 1
|
||||
for i, serie := range *series {
|
||||
// setRowName by groupTag
|
||||
if len(groupTag) != 0 {
|
||||
for key, val := range serie.Tags {
|
||||
if strings.Contains(strings.Join(groupTag, ","), key) {
|
||||
serie.RawName = fmt.Sprintf("%s", val)
|
||||
(*series)[i] = serie
|
||||
break
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
measurement := strings.Split(serie.Name, ".")[0]
|
||||
// sep measurement set RowName by spe param
|
||||
measurements, _ := MetricMeasurementManager.getMeasurementByName(measurement)
|
||||
if len(measurements) != 0 {
|
||||
if key, ok := monitor.MEASUREMENT_TAG_KEYWORD[measurements[0].ResType]; ok {
|
||||
serie.RawName = fmt.Sprintf("%d: %s", index, serie.Tags[key])
|
||||
(*series)[i] = serie
|
||||
index++
|
||||
continue
|
||||
}
|
||||
}
|
||||
// other condition set RowName
|
||||
for key, val := range serie.Tags {
|
||||
if strings.Contains(key, "id") {
|
||||
serie.RawName = fmt.Sprintf("%d: %s", index, val)
|
||||
(*series)[i] = serie
|
||||
index++
|
||||
break
|
||||
}
|
||||
}
|
||||
if serie.RawName == "" {
|
||||
serie.RawName = fmt.Sprintf("unknown-%d", unknownIndex)
|
||||
(*series)[i] = serie
|
||||
unknownIndex++
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func fillSerieTags(series *tsdb.TimeSeriesSlice) {
|
||||
for i, serie := range *series {
|
||||
for _, tag := range []string{"brand", "platform", "hypervisor"} {
|
||||
@@ -577,7 +536,7 @@ func (self *SUnifiedMonitorManager) GetPropertySimpleQuery(ctx context.Context,
|
||||
conditions = append(conditions, fmt.Sprintf("time <= '%s'", et))
|
||||
sqlstr += strings.Join(conditions, " and ")
|
||||
sqlstr += " limit 2000"
|
||||
dataSource, err := DataSourceManager.GetDefaultSource()
|
||||
dataSource, err := datasource.GetDefaultSource(input.Database)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "s.GetDefaultSource")
|
||||
}
|
||||
|
||||
@@ -44,6 +44,7 @@ import (
|
||||
"yunion.io/x/onecloud/pkg/monitor/registry"
|
||||
"yunion.io/x/onecloud/pkg/monitor/subscriptionmodel"
|
||||
_ "yunion.io/x/onecloud/pkg/monitor/tsdb/driver/influxdb"
|
||||
_ "yunion.io/x/onecloud/pkg/monitor/tsdb/driver/victoriametrics"
|
||||
"yunion.io/x/onecloud/pkg/monitor/worker"
|
||||
)
|
||||
|
||||
@@ -85,16 +86,12 @@ func StartService() {
|
||||
cron.Start()
|
||||
defer cron.Stop()
|
||||
|
||||
subscriptionmodel.SubscriptionManager.AddSubscription()
|
||||
models.CommonAlertManager.SetSubscriptionManager(subscriptionmodel.SubscriptionManager)
|
||||
|
||||
worker, err := worker.NewWorker(opts)
|
||||
if err != nil {
|
||||
log.Fatalf("new worker failed: %v", err)
|
||||
}
|
||||
go worker.Start(app.GetContext(), app, "")
|
||||
|
||||
//common_app.ServeForever(app, baseOpts)
|
||||
InitInfluxDBSubscriptionHandlers(app, baseOpts)
|
||||
|
||||
// start migration recover routine
|
||||
@@ -115,10 +112,13 @@ func startServices() {
|
||||
|
||||
log.Infof("Initializing " + svc.Name)
|
||||
if err := svc.Instance.Init(); err != nil {
|
||||
log.Fatalf("Service %s init failed", svc.Name)
|
||||
log.Fatalf("Service %s init failed: %v", svc.Name, err)
|
||||
}
|
||||
}
|
||||
|
||||
subscriptionmodel.SubscriptionManager.AddSubscription()
|
||||
models.CommonAlertManager.SetSubscriptionManager(subscriptionmodel.SubscriptionManager)
|
||||
|
||||
childRoutines, ctx := errgroup.WithContext(context.Background())
|
||||
// Start background services
|
||||
for _, svc := range services {
|
||||
|
||||
@@ -81,7 +81,7 @@ func (self *SSubscriptionManager) AddSubscription() {
|
||||
return
|
||||
}
|
||||
log.Infof("drop success")
|
||||
err = models.DataSourceManager.AddSubscription(sub)
|
||||
/*err = models.DataSourceManager.AddSubscription(sub)
|
||||
if err != nil {
|
||||
log.Errorln("add subscription err:", err)
|
||||
return
|
||||
@@ -90,7 +90,7 @@ func (self *SSubscriptionManager) AddSubscription() {
|
||||
if err := self.LoadSystemAlerts(); err != nil {
|
||||
log.Errorf("load system alerts error: %v", err)
|
||||
return
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
func (self *SSubscriptionManager) LoadSystemAlerts() error {
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
package influxdb
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/smartystreets/goconvey/convey"
|
||||
|
||||
monitor2 "yunion.io/x/onecloud/pkg/apis/monitor"
|
||||
"yunion.io/x/onecloud/pkg/mcclient/modules/monitor"
|
||||
)
|
||||
|
||||
func TestAlertQuery(t *testing.T) {
|
||||
convey.Convey("Alert query test", t, func() {
|
||||
parser := new(InfluxdbQueryParser)
|
||||
q := monitor.NewAlertQuery("telegraf", "diskio").From("5m").To("now")
|
||||
q.Selects().Select("await").MEAN()
|
||||
q.Where().Equal("hostname", "host1").Equal("provider", "kvm")
|
||||
q.GroupBy().TAG("*").FILL_NULL()
|
||||
qCtx := q.ToTsdbQuery()
|
||||
influxdbQ, err := parser.Parse(qCtx.Queries[0], nil)
|
||||
convey.So(err, convey.ShouldBeNil)
|
||||
rawQuery, err := influxdbQ.Build(qCtx)
|
||||
convey.So(err, convey.ShouldBeNil)
|
||||
convey.So(rawQuery, convey.ShouldEqual, `SELECT mean("await") FROM "diskio" WHERE ("hostname" = 'host1' AND "provider" = 'kvm') AND time > now() - 5m GROUP BY * fill(null)`)
|
||||
})
|
||||
|
||||
convey.Convey("Alert last query", t, func() {
|
||||
q := monitor.NewAlertQuery("telegraf", "diskio").From("5m").To("now")
|
||||
q.Selects().Select("*").LAST()
|
||||
q.Where().AddTag(&monitor2.MetricQueryTag{
|
||||
Operator: "=~",
|
||||
Key: "project_id",
|
||||
Value: "/xxx/",
|
||||
})
|
||||
tq := q.ToTsdbQuery()
|
||||
parser := new(InfluxdbQueryParser)
|
||||
influxQ, err := parser.Parse(tq.Queries[0], nil)
|
||||
convey.So(err, convey.ShouldBeNil)
|
||||
rawQ, err := influxQ.Build(tq)
|
||||
convey.So(err, convey.ShouldBeNil)
|
||||
convey.So(rawQ, convey.ShouldEqual, `SELECT last(*) FROM "diskio" WHERE "project_id" =~ /xxx/ AND time > now() - 5m`)
|
||||
})
|
||||
}
|
||||
@@ -29,6 +29,8 @@ import (
|
||||
"yunion.io/x/log"
|
||||
"yunion.io/x/pkg/errors"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/apis/monitor"
|
||||
mod "yunion.io/x/onecloud/pkg/mcclient/modules/monitor"
|
||||
"yunion.io/x/onecloud/pkg/monitor/tsdb"
|
||||
)
|
||||
|
||||
@@ -37,7 +39,7 @@ const (
|
||||
)
|
||||
|
||||
func init() {
|
||||
tsdb.RegisterTsdbQueryEndpoint("influxdb", NewInfluxdbExecutor)
|
||||
tsdb.RegisterTsdbQueryEndpoint(monitor.DataSourceTypeInfluxdb, NewInfluxdbExecutor)
|
||||
}
|
||||
|
||||
type InfluxdbExecutor struct {
|
||||
@@ -52,8 +54,7 @@ func NewInfluxdbExecutor(datasource *tsdb.DataSource) (tsdb.TsdbQueryEndpoint, e
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (e *InfluxdbExecutor) Query(ctx context.Context, dsInfo *tsdb.DataSource, tsdbQuery *tsdb.TsdbQuery) (*tsdb.Response, error) {
|
||||
result := &tsdb.Response{}
|
||||
func (e *InfluxdbExecutor) GetRawQuery(dsInfo *tsdb.DataSource, tsdbQuery *tsdb.TsdbQuery) (string, []*Query, error) {
|
||||
querys := make([]*tsdb.Query, len(tsdbQuery.Queries)+1)
|
||||
influxQ := make([]*Query, 0)
|
||||
copy(querys, tsdbQuery.Queries)
|
||||
@@ -61,14 +62,14 @@ func (e *InfluxdbExecutor) Query(ctx context.Context, dsInfo *tsdb.DataSource, t
|
||||
var rawQuery string
|
||||
for i := 0; i < len(querys); i++ {
|
||||
query, err := e.getQuery(dsInfo, querys, tsdbQuery)
|
||||
influxQ = append(influxQ, query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return "", nil, errors.Wrap(err, "getQuery")
|
||||
}
|
||||
influxQ = append(influxQ, query)
|
||||
|
||||
rawQuery, err := query.Build(tsdbQuery)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return "", nil, errors.Wrap(err, "query.Build")
|
||||
}
|
||||
buffer.WriteString(rawQuery + ";")
|
||||
if len(querys) > 0 {
|
||||
@@ -87,6 +88,14 @@ func (e *InfluxdbExecutor) Query(ctx context.Context, dsInfo *tsdb.DataSource, t
|
||||
//if err != nil {
|
||||
// return nil, err
|
||||
//}
|
||||
return rawQuery, influxQ, nil
|
||||
}
|
||||
|
||||
func (e *InfluxdbExecutor) Query(ctx context.Context, dsInfo *tsdb.DataSource, tsdbQuery *tsdb.TsdbQuery) (*tsdb.Response, error) {
|
||||
rawQuery, influxQ, err := e.GetRawQuery(dsInfo, tsdbQuery)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "GetRawQuery")
|
||||
}
|
||||
|
||||
log.Debugf("sql: %s", rawQuery)
|
||||
db := dsInfo.Database
|
||||
@@ -127,7 +136,9 @@ func (e *InfluxdbExecutor) Query(ctx context.Context, dsInfo *tsdb.DataSource, t
|
||||
return nil, response.Err
|
||||
}
|
||||
|
||||
result.Results = make(map[string]*tsdb.QueryResult)
|
||||
result := &tsdb.Response{
|
||||
Results: make(map[string]*tsdb.QueryResult),
|
||||
}
|
||||
for i, query := range tsdbQuery.Queries {
|
||||
ret := e.ResponseParser.Parse(&response, influxQ[i])
|
||||
ret.Meta = tsdb.QueryResultMeta{
|
||||
@@ -193,3 +204,45 @@ func (e *InfluxdbExecutor) createRequest(dsInfo *tsdb.DataSource, query string)
|
||||
log.Debugf("Influxdb raw query: %q from db %s, curl: %s", query, dsInfo.Database, curlCmd)
|
||||
return req, nil
|
||||
}
|
||||
|
||||
func (e *InfluxdbExecutor) FilterMeasurement(
|
||||
ctx context.Context,
|
||||
ds *tsdb.DataSource,
|
||||
from, to string,
|
||||
ms *monitor.InfluxMeasurement,
|
||||
tagFilter *monitor.MetricQueryTag,
|
||||
) (*monitor.InfluxMeasurement, error) {
|
||||
retMs := new(monitor.InfluxMeasurement)
|
||||
q := mod.NewAlertQuery(ms.Database, ms.Measurement).From(from).To(to)
|
||||
q.Selects().Select("*").LAST()
|
||||
if tagFilter != nil {
|
||||
q.Where().AddTag(tagFilter)
|
||||
}
|
||||
tq := q.ToTsdbQuery()
|
||||
resp, err := e.Query(ctx, ds, tq)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "influxdb.Query")
|
||||
}
|
||||
ss := resp.Results[""].Series
|
||||
//log.Infof("=====get ss: %s", jsonutils.Marshal(ss).PrettyString())
|
||||
|
||||
// parse fields
|
||||
retFields := make([]string, 0)
|
||||
for _, s := range ss {
|
||||
cols := s.Columns
|
||||
for _, col := range cols {
|
||||
if !strings.Contains(col, "last") {
|
||||
continue
|
||||
}
|
||||
retFields = append(retFields, strings.Replace(col, "last_", "", 1))
|
||||
}
|
||||
}
|
||||
retMs.FieldKey = retFields
|
||||
if len(retMs.FieldKey) != 0 {
|
||||
retMs.Measurement = ms.Measurement
|
||||
retMs.Database = ms.Database
|
||||
retMs.ResType = ms.ResType
|
||||
}
|
||||
|
||||
return retMs, nil
|
||||
}
|
||||
|
||||
@@ -75,7 +75,7 @@ func (rp *ResponseParser) transformRows(rows []Row, queryResult *tsdb.QueryResul
|
||||
|
||||
func (rp *ResponseParser) transformRowsV2(rows []Row, queryResult *tsdb.QueryResult, query *Query) tsdb.TimeSeriesSlice {
|
||||
var result tsdb.TimeSeriesSlice
|
||||
for _, row := range rows {
|
||||
for idx, row := range rows {
|
||||
col := ""
|
||||
columns := make([]string, 0)
|
||||
for _, column := range row.Columns {
|
||||
@@ -104,17 +104,24 @@ func (rp *ResponseParser) transformRowsV2(rows []Row, queryResult *tsdb.QueryRes
|
||||
val_ := strings.ReplaceAll(val, "+", " ")
|
||||
tags[key] = val_
|
||||
}
|
||||
result = append(result, &tsdb.TimeSeries{
|
||||
Name: rp.formatSerieName(row, col, query),
|
||||
Columns: columns,
|
||||
Points: points,
|
||||
Tags: tags,
|
||||
})
|
||||
name := rp.formatSerieName(row, col, query)
|
||||
ts := tsdb.NewTimeSeries(name, formatRawName(idx, name, query, tags), columns, points, tags)
|
||||
result = append(result, ts)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func formatRawName(idx int, name string, query *Query, tags map[string]string) string {
|
||||
groupByTags := []string{}
|
||||
for _, group := range query.GroupBy {
|
||||
if group.Type == "tag" {
|
||||
groupByTags = append(groupByTags, group.Params[0])
|
||||
}
|
||||
}
|
||||
return tsdb.FormatRawName(idx, name, groupByTags, tags)
|
||||
}
|
||||
|
||||
func (rp *ResponseParser) transformRowToTable(row Row, table *tsdb.Table) *tsdb.Table {
|
||||
for _, col := range row.Columns {
|
||||
table.Columns = append(table.Columns, tsdb.TableColumn{
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
package victoriametrics
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
"time"
|
||||
|
||||
"github.com/influxdata/influxql"
|
||||
"golang.org/x/net/context/ctxhttp"
|
||||
"moul.io/http2curl/v2"
|
||||
|
||||
"yunion.io/x/log"
|
||||
"yunion.io/x/pkg/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
ErrVMInvalidResponse = errors.Error("VictoriaMetrics invalid response")
|
||||
)
|
||||
|
||||
type TimeRange struct {
|
||||
Start int64
|
||||
End int64
|
||||
}
|
||||
|
||||
func NewTimeRange(start, end int64) *TimeRange {
|
||||
return &TimeRange{
|
||||
Start: start,
|
||||
End: end,
|
||||
}
|
||||
}
|
||||
|
||||
func NewTimeRangeByInfluxTimeRange(tr *influxql.TimeRange) *TimeRange {
|
||||
// format should be: https://docs.victoriametrics.com/#timestamp-formats
|
||||
nTr := &TimeRange{}
|
||||
if !tr.MinTime().IsZero() {
|
||||
nTr.Start = tr.MinTime().Unix()
|
||||
}
|
||||
if !tr.MaxTime().IsZero() {
|
||||
nTr.End = tr.MaxTime().Unix()
|
||||
}
|
||||
return nTr
|
||||
}
|
||||
|
||||
type Client interface {
|
||||
QueryRange(ctx context.Context, httpCli *http.Client, query string, step time.Duration, timeRange *TimeRange, disableCache bool) (*Response, error)
|
||||
}
|
||||
|
||||
type client struct {
|
||||
endpoint string
|
||||
endpointURL url.URL
|
||||
}
|
||||
|
||||
func (c *client) getAPIURL(reqPath string) string {
|
||||
apiPrefix := "/api/v1"
|
||||
reqPath = fmt.Sprintf("%s/%s", apiPrefix, reqPath)
|
||||
reqURL := c.endpointURL
|
||||
reqURL.Path = path.Join(reqURL.Path, reqPath)
|
||||
return reqURL.String()
|
||||
}
|
||||
|
||||
// ResponseDataResultValue likes: [ 1652169600, "1" ]
|
||||
type ResponseDataResultValue []interface{}
|
||||
|
||||
type ResponseDataResult struct {
|
||||
Metric map[string]string `json:"metric"`
|
||||
Values []ResponseDataResultValue `json:"values"`
|
||||
}
|
||||
|
||||
type ResponseData struct {
|
||||
ResultType string `json:"resultType"`
|
||||
Result []ResponseDataResult `json:"result"`
|
||||
}
|
||||
|
||||
type ResponseStats struct {
|
||||
// SeriesFetched is like integer type: {seriesFetched: "2"}
|
||||
SeriesFetched string `json:"seriesFetched"`
|
||||
}
|
||||
|
||||
type Response struct {
|
||||
Status string `json:"status"`
|
||||
Data ResponseData
|
||||
Stats ResponseStats
|
||||
}
|
||||
|
||||
// QueryRange implements Client.
|
||||
func (c *client) QueryRange(ctx context.Context, httpCli *http.Client, query string, step time.Duration, tr *TimeRange, disableCache bool) (*Response, error) {
|
||||
req, err := c.createQueryRangeReq(query, step, tr, disableCache)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "get request")
|
||||
}
|
||||
|
||||
resp, err := ctxhttp.Do(ctx, httpCli, req)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Do request")
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode/100 != 2 {
|
||||
return nil, errors.Wrapf(ErrVMInvalidResponse, "status code: %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
var response Response
|
||||
dec := json.NewDecoder(resp.Body)
|
||||
dec.UseNumber()
|
||||
if err := dec.Decode(&response); err != nil {
|
||||
return nil, errors.Wrap(err, "decode json response")
|
||||
}
|
||||
return &response, nil
|
||||
}
|
||||
|
||||
func (c *client) createQueryRangeReq(query string, step time.Duration, tr *TimeRange, disableCache bool) (*http.Request, error) {
|
||||
reqURL := c.getAPIURL("/query_range")
|
||||
req, err := http.NewRequest(http.MethodGet, reqURL, nil)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "new HTTP request of: %s", reqURL)
|
||||
}
|
||||
req.Header.Set("User-Agent", "Cloudpods Monitor Service")
|
||||
params := req.URL.Query()
|
||||
params.Set("query", query)
|
||||
if step != 0 {
|
||||
params.Set("step", step.String())
|
||||
}
|
||||
if tr != nil {
|
||||
if tr.Start != 0 {
|
||||
params.Set("start", fmt.Sprintf("%d", tr.Start))
|
||||
}
|
||||
if tr.End != 0 {
|
||||
params.Set("end", fmt.Sprintf("%d", tr.End))
|
||||
}
|
||||
}
|
||||
if disableCache {
|
||||
params.Set("nocache", "1")
|
||||
}
|
||||
req.URL.RawQuery = params.Encode()
|
||||
curlCmd, _ := http2curl.GetCurlCommand(req)
|
||||
log.Infof("VictoriaMetrics curl cmd: %s", curlCmd)
|
||||
return req, nil
|
||||
}
|
||||
|
||||
func NewClient(endpoint string) (Client, error) {
|
||||
u, err := url.Parse(endpoint)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "invalid url: %q", endpoint)
|
||||
}
|
||||
cli := &client{
|
||||
endpoint: endpoint,
|
||||
endpointURL: *u,
|
||||
}
|
||||
return cli, nil
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package victoriametrics
|
||||
|
||||
func newTestClient(ep string) (Client, error) {
|
||||
return NewClient(ep)
|
||||
}
|
||||
|
||||
/*func Test_client_QueryRange(t *testing.T) {
|
||||
cli, err := newTestClient("http://192.168.222.171:34795/")
|
||||
if err != nil {
|
||||
t.Fatalf("newTestClient")
|
||||
}
|
||||
resp, err := cli.QueryRange(context.Background(), http.DefaultClient, `avg by(host_id) (avg_over_time(cpu_usage_active{res_type="host"}))`, time.Second*50, &TimeRange{
|
||||
Start: 1698724482.213,
|
||||
End: 1698746082.213,
|
||||
}, true)
|
||||
if err != nil {
|
||||
t.Fatalf("query err: %v", err)
|
||||
}
|
||||
log.Infof("get resp: %s", jsonutils.Marshal(resp).PrettyString())
|
||||
}*/
|
||||
@@ -0,0 +1 @@
|
||||
package victoriametrics // import "yunion.io/x/onecloud/pkg/monitor/tsdb/driver/victoriametrics"
|
||||
@@ -0,0 +1,154 @@
|
||||
package victoriametrics
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/influxdata/promql/v2/pkg/labels"
|
||||
"github.com/zexi/influxql-to-promql/converter/translator"
|
||||
|
||||
"yunion.io/x/log"
|
||||
"yunion.io/x/pkg/errors"
|
||||
"yunion.io/x/pkg/util/sets"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/monitor/tsdb"
|
||||
)
|
||||
|
||||
func newMapId(input map[string]string, ignoreKeys ...string) string {
|
||||
keys := make([]string, 0)
|
||||
ignoreKS := sets.NewString(ignoreKeys...)
|
||||
for key := range input {
|
||||
if ignoreKS.Has(key) {
|
||||
continue
|
||||
}
|
||||
keys = append(keys, key)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
pairs := make([]string, len(keys))
|
||||
for i, key := range keys {
|
||||
pair := fmt.Sprintf("%s->%s", key, input[key])
|
||||
pairs[i] = pair
|
||||
}
|
||||
return strings.Join(pairs, ",")
|
||||
}
|
||||
|
||||
type points struct {
|
||||
id string
|
||||
columns []string
|
||||
values []ResponseDataResultValue
|
||||
tags map[string]string
|
||||
}
|
||||
|
||||
func (p *points) add(op *points) error {
|
||||
if len(op.columns) != 1 {
|
||||
return errors.Errorf("input points' columns are %#v, which length isn't equal 1", op.columns)
|
||||
}
|
||||
p.columns = append(p.columns, op.columns[0])
|
||||
// merge values
|
||||
for i, val := range p.values {
|
||||
oVal := op.values[i]
|
||||
valTime := val[0]
|
||||
oValTime := oVal[0]
|
||||
if valTime != oValTime {
|
||||
return errors.Errorf("value time %v != other value time %v", valTime, oValTime)
|
||||
}
|
||||
if len(oVal) != 2 {
|
||||
return errors.Errorf("input values' are %#v, which length isn't equal 2", oVal)
|
||||
}
|
||||
val = append(val, oVal[1])
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *points) isEqual(op *points) bool {
|
||||
if p.id != op.id {
|
||||
return false
|
||||
}
|
||||
return reflect.DeepEqual(p.columns, op.columns) && reflect.DeepEqual(p.tags, op.tags) && reflect.DeepEqual(p.values, op.values)
|
||||
}
|
||||
|
||||
func newPointsByResult(result ResponseDataResult) (*points, error) {
|
||||
tags := result.Metric
|
||||
column, ok := tags[translator.UNION_RESULT_NAME]
|
||||
if !ok {
|
||||
return nil, errors.Errorf("result tags %#v don't contain key %s", tags, translator.UNION_RESULT_NAME)
|
||||
}
|
||||
for _, ignoreKey := range []string{
|
||||
translator.UNION_RESULT_NAME,
|
||||
labels.MetricName,
|
||||
} {
|
||||
delete(tags, ignoreKey)
|
||||
}
|
||||
values := result.Values
|
||||
id := newMapId(tags)
|
||||
return &points{
|
||||
id: id,
|
||||
columns: []string{column},
|
||||
values: values,
|
||||
tags: tags,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func newPointsByResults(results []ResponseDataResult) ([]*points, error) {
|
||||
uniq := make(map[string]*points, 0)
|
||||
ret := make([]*points, 0)
|
||||
|
||||
for _, result := range results {
|
||||
p, err := newPointsByResult(result)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "new points by result: %#v", result)
|
||||
}
|
||||
if ep, ok := uniq[p.id]; ok {
|
||||
if err := ep.add(p); err != nil {
|
||||
return nil, errors.Wrapf(err, "add point %#v", p)
|
||||
}
|
||||
} else {
|
||||
uniq[p.id] = p
|
||||
ret = append(ret, p)
|
||||
}
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func transPointsToSeries(points []*points, query *tsdb.Query) tsdb.TimeSeriesSlice {
|
||||
var result tsdb.TimeSeriesSlice
|
||||
for _, point := range points {
|
||||
result = append(result, transPointToSeries(point, query)...)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func transValuesToTSDBPoints(vals []ResponseDataResultValue) tsdb.TimeSeriesPoints {
|
||||
var points tsdb.TimeSeriesPoints
|
||||
for _, val := range vals {
|
||||
point, err := parseTimepoint(val)
|
||||
if err != nil {
|
||||
log.Errorf("parseTimepoint: %#v", val)
|
||||
} else {
|
||||
points = append(points, point)
|
||||
}
|
||||
}
|
||||
return points
|
||||
}
|
||||
|
||||
func reviseTags(tags map[string]string) map[string]string {
|
||||
ret := make(map[string]string)
|
||||
for key, val := range tags {
|
||||
val_ := strings.ReplaceAll(val, "+", " ")
|
||||
ret[key] = val_
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func transPointToSeries(p *points, query *tsdb.Query) tsdb.TimeSeriesSlice {
|
||||
var result tsdb.TimeSeriesSlice
|
||||
|
||||
points := transValuesToTSDBPoints(p.values)
|
||||
tags := reviseTags(p.tags)
|
||||
metricName := strings.Join(p.columns, ",")
|
||||
ts := tsdb.NewTimeSeries(metricName, formatRawName(0, metricName, query, tags), append(p.columns, "time"), points, tags)
|
||||
result = append(result, ts)
|
||||
return result
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
package victoriametrics
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test_newPointsByResults(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
results []ResponseDataResult
|
||||
want []*points
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "merge ok",
|
||||
results: []ResponseDataResult{
|
||||
{
|
||||
Metric: map[string]string{
|
||||
"__name__": "disk_free",
|
||||
"__union_result__": "disk_free",
|
||||
"fstype": "fuse.s3fs",
|
||||
},
|
||||
Values: []ResponseDataResultValue{
|
||||
[]interface{}{1699414400, "9223372036854775807"},
|
||||
[]interface{}{1699416000, "9223372036854775807"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Metric: map[string]string{
|
||||
"__name__": "disk_free",
|
||||
"__union_result__": "disk_free",
|
||||
"fstype": "xfs",
|
||||
},
|
||||
Values: []ResponseDataResultValue{
|
||||
[]interface{}{1699414400, "277637316608"},
|
||||
[]interface{}{1699416000, "277599338496"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Metric: map[string]string{
|
||||
"__name__": "disk_total",
|
||||
"__union_result__": "disk_total",
|
||||
"fstype": "fuse.s3fs",
|
||||
},
|
||||
Values: []ResponseDataResultValue{
|
||||
[]interface{}{1699414400, "9223372036854775807"},
|
||||
[]interface{}{1699416000, "9223372036854775807"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Metric: map[string]string{
|
||||
"__name__": "disk_total",
|
||||
"__union_result__": "disk_total",
|
||||
"fstype": "xfs",
|
||||
},
|
||||
Values: []ResponseDataResultValue{
|
||||
[]interface{}{1699414400, "536608768000"},
|
||||
[]interface{}{1699416000, "536608768000"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Metric: map[string]string{
|
||||
"__name__": "disk_used",
|
||||
"__union_result__": "disk_used",
|
||||
"fstype": "fuse.s3fs",
|
||||
},
|
||||
Values: []ResponseDataResultValue{
|
||||
[]interface{}{1699414400, "0"},
|
||||
[]interface{}{1699416000, "0"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Metric: map[string]string{
|
||||
"__name__": "disk_used",
|
||||
"__union_result__": "disk_used",
|
||||
"fstype": "xfs",
|
||||
},
|
||||
Values: []ResponseDataResultValue{
|
||||
[]interface{}{1699414400, "258971451392"},
|
||||
[]interface{}{1699416000, "259009429504"},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: []*points{
|
||||
{
|
||||
id: "fstype->fuse.s3fs",
|
||||
columns: []string{"disk_free", "disk_total", "disk_used"},
|
||||
values: []ResponseDataResultValue{
|
||||
[]interface{}{1699414400, "9223372036854775807", "9223372036854775807", "0"},
|
||||
[]interface{}{1699416000, "9223372036854775807", "9223372036854775807", "0"},
|
||||
},
|
||||
tags: map[string]string{"fstype": "fuse.s3fs"},
|
||||
},
|
||||
{
|
||||
id: "fstype->xfs",
|
||||
columns: []string{"disk_free", "disk_total", "disk_used"},
|
||||
values: []ResponseDataResultValue{
|
||||
[]interface{}{1699414400, "277637316608", "536608768000", "258971451392"},
|
||||
[]interface{}{1699416000, "277599338496", "536608768000", "259009429504"},
|
||||
},
|
||||
tags: map[string]string{"fstype": "xfs"},
|
||||
},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := newPointsByResults(tt.results)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("newPointsByResults() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
for i := range got {
|
||||
gotP := got[i]
|
||||
wantP := tt.want[i]
|
||||
if gotP.isEqual(wantP) {
|
||||
t.Errorf("newPointsByResults() got = %v, want %v", gotP, wantP)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,264 @@
|
||||
package victoriametrics
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/influxdata/influxql"
|
||||
"github.com/influxdata/promql/v2/pkg/labels"
|
||||
"github.com/zexi/influxql-to-promql/converter"
|
||||
"github.com/zexi/influxql-to-promql/converter/translator"
|
||||
|
||||
"yunion.io/x/log"
|
||||
"yunion.io/x/pkg/errors"
|
||||
"yunion.io/x/pkg/util/sets"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/apis/monitor"
|
||||
mod "yunion.io/x/onecloud/pkg/mcclient/modules/monitor"
|
||||
"yunion.io/x/onecloud/pkg/monitor/tsdb"
|
||||
"yunion.io/x/onecloud/pkg/monitor/tsdb/driver/influxdb"
|
||||
)
|
||||
|
||||
func init() {
|
||||
tsdb.RegisterTsdbQueryEndpoint(monitor.DataSourceTypeVictoriaMetrics, NewVMAdapter)
|
||||
}
|
||||
|
||||
type vmAdapter struct {
|
||||
datasource *tsdb.DataSource
|
||||
influxdbExecutor *influxdb.InfluxdbExecutor
|
||||
}
|
||||
|
||||
func NewVMAdapter(datasource *tsdb.DataSource) (tsdb.TsdbQueryEndpoint, error) {
|
||||
ie, _ := influxdb.NewInfluxdbExecutor(nil)
|
||||
return &vmAdapter{
|
||||
datasource: datasource,
|
||||
influxdbExecutor: ie.(*influxdb.InfluxdbExecutor),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Query implements tsdb.TsdbQueryEndpoint.
|
||||
func (vm *vmAdapter) Query(ctx context.Context, ds *tsdb.DataSource, query *tsdb.TsdbQuery) (*tsdb.Response, error) {
|
||||
rawQuery, influxQs, err := vm.influxdbExecutor.GetRawQuery(ds, query)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "get influxdb raw query: %#v", influxQs)
|
||||
}
|
||||
|
||||
return queryByRaw(ctx, ds, rawQuery, query)
|
||||
}
|
||||
|
||||
func queryByRaw(ctx context.Context, ds *tsdb.DataSource, rawQuery string, query *tsdb.TsdbQuery) (*tsdb.Response, error) {
|
||||
promQL, tr, err := convertInfluxQL(rawQuery)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "convert influxQL to promQL")
|
||||
}
|
||||
log.Infof("influxQL: %s, promQL: %s", rawQuery, promQL)
|
||||
resp, err := queryRange(ctx, ds, tr, promQL)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "query VM range by: %s", promQL)
|
||||
}
|
||||
|
||||
//log.Infof("get vm resp: %s", jsonutils.Marshal(resp).PrettyString())
|
||||
|
||||
tsdbRet, err := convertVMResponse(rawQuery, query, resp)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "convert to tsdb.Response")
|
||||
}
|
||||
|
||||
return tsdbRet, nil
|
||||
}
|
||||
|
||||
func queryRange(ctx context.Context, ds *tsdb.DataSource, tr *influxql.TimeRange, promQL string) (*Response, error) {
|
||||
cli, err := NewClient(ds.Url)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "New VM client")
|
||||
}
|
||||
httpCli, err := ds.GetHttpClient()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "GetHttpClient of data source")
|
||||
}
|
||||
vmTr := NewTimeRangeByInfluxTimeRange(tr)
|
||||
return cli.QueryRange(ctx, httpCli, promQL, 0, vmTr, false)
|
||||
}
|
||||
|
||||
func convertInfluxQL(influxQL string) (string, *influxql.TimeRange, error) {
|
||||
promQL, timeRange, err := converter.TranslateWithTimeRange(influxQL)
|
||||
if err != nil {
|
||||
return "", nil, errors.Wrapf(err, "TranslateWithTimeRange: %s", influxQL)
|
||||
}
|
||||
return promQL, timeRange, nil
|
||||
}
|
||||
|
||||
func convertVMResponse(rawQuery string, tsdbQuery *tsdb.TsdbQuery, resp *Response) (*tsdb.Response, error) {
|
||||
result := &tsdb.Response{
|
||||
Results: make(map[string]*tsdb.QueryResult),
|
||||
}
|
||||
for _, query := range tsdbQuery.Queries {
|
||||
ret, err := translateResponse(resp, query)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "translate response")
|
||||
}
|
||||
ret.Meta = tsdb.QueryResultMeta{
|
||||
RawQuery: rawQuery,
|
||||
}
|
||||
result.Results[query.RefId] = ret
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func translateResponse(resp *Response, query *tsdb.Query) (*tsdb.QueryResult, error) {
|
||||
queryRes := tsdb.NewQueryResult()
|
||||
|
||||
isUnionResult := false
|
||||
results := resp.Data.Result
|
||||
if len(results) > 0 {
|
||||
_, isUnionResult = results[0].Metric[translator.UNION_RESULT_NAME]
|
||||
}
|
||||
if !isUnionResult {
|
||||
for _, result := range results {
|
||||
ss := transformSeries(result, query)
|
||||
queryRes.Series = append(queryRes.Series, ss...)
|
||||
}
|
||||
} else {
|
||||
// process union multiple fields response
|
||||
points, err := newPointsByResults(results)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "process multi fields")
|
||||
}
|
||||
ss := transPointsToSeries(points, query)
|
||||
queryRes.Series = ss
|
||||
}
|
||||
|
||||
return queryRes, nil
|
||||
}
|
||||
|
||||
// Check VictoriaMetrics response at: https://docs.victoriametrics.com/keyConcepts.html#range-query
|
||||
func transformSeries(vmResult ResponseDataResult, query *tsdb.Query) tsdb.TimeSeriesSlice {
|
||||
var result tsdb.TimeSeriesSlice
|
||||
metric := vmResult.Metric
|
||||
|
||||
points := transValuesToTSDBPoints(vmResult.Values)
|
||||
|
||||
tags := reviseTags(metric)
|
||||
|
||||
aliasName := ""
|
||||
if len(query.Selects) > 0 {
|
||||
lastSel := query.Selects[len(query.Selects)-1]
|
||||
lastSelPart := lastSel[len(lastSel)-1]
|
||||
if lastSelPart.Type == "alias" && len(lastSelPart.Params) > 0 {
|
||||
aliasName = lastSelPart.Params[0]
|
||||
}
|
||||
}
|
||||
metricName := metric[labels.MetricName]
|
||||
if metricName == "" {
|
||||
metricName = "value"
|
||||
}
|
||||
if aliasName != "" {
|
||||
metricName = aliasName
|
||||
}
|
||||
ts := tsdb.NewTimeSeries(metricName, formatRawName(0, metricName, query, tags), []string{metricName, "time"}, points, tags)
|
||||
result = append(result, ts)
|
||||
return result
|
||||
}
|
||||
|
||||
func formatRawName(idx int, name string, query *tsdb.Query, tags map[string]string) string {
|
||||
groupByTags := []string{}
|
||||
if query != nil {
|
||||
for _, group := range query.GroupBy {
|
||||
if group.Type == "tag" {
|
||||
groupByTags = append(groupByTags, group.Params[0])
|
||||
}
|
||||
}
|
||||
}
|
||||
return tsdb.FormatRawName(idx, name, groupByTags, tags)
|
||||
}
|
||||
|
||||
func parseTimepoint(val ResponseDataResultValue) (tsdb.TimePoint, error) {
|
||||
timepoint := make(tsdb.TimePoint, 0)
|
||||
// parse timestamp
|
||||
timestampNumber, _ := val[0].(json.Number)
|
||||
timestamp, err := timestampNumber.Float64()
|
||||
if err != nil {
|
||||
return tsdb.TimePoint{}, errors.Wrapf(err, "parse timestampNumber")
|
||||
}
|
||||
// to influxdb timestamp format, millisecond ?
|
||||
timestamp *= 1000
|
||||
|
||||
// parse value
|
||||
for i := 1; i < len(val); i++ {
|
||||
valStr := val[i]
|
||||
pVal := parsePointValue(valStr)
|
||||
timepoint = append(timepoint, pVal)
|
||||
timepoint = append(timepoint, timestamp)
|
||||
}
|
||||
return timepoint, nil
|
||||
}
|
||||
|
||||
func parsePointValue(value interface{}) interface{} {
|
||||
number, ok := value.(json.Number)
|
||||
if !ok {
|
||||
// try parse string
|
||||
valStr, ok := value.(string)
|
||||
if ok {
|
||||
valF, err := strconv.ParseFloat(valStr, 64)
|
||||
if err == nil {
|
||||
return &valF
|
||||
}
|
||||
return value
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
fvalue, err := number.Float64()
|
||||
if err == nil {
|
||||
return &fvalue
|
||||
}
|
||||
|
||||
ivalue, err := number.Int64()
|
||||
if err == nil {
|
||||
ret := float64(ivalue)
|
||||
return &ret
|
||||
}
|
||||
return number.String()
|
||||
}
|
||||
|
||||
func (vm *vmAdapter) FilterMeasurement(ctx context.Context, ds *tsdb.DataSource, from, to string, ms *monitor.InfluxMeasurement, tagFilter *monitor.MetricQueryTag) (*monitor.InfluxMeasurement, error) {
|
||||
retMs := new(monitor.InfluxMeasurement)
|
||||
q := mod.NewAlertQuery(ms.Database, ms.Measurement).From(from).To(to)
|
||||
q.Selects().Select("*").LAST()
|
||||
if tagFilter != nil {
|
||||
q.Where().AddTag(tagFilter)
|
||||
}
|
||||
q.GroupBy().TAG(labels.MetricName)
|
||||
tq := q.ToTsdbQuery()
|
||||
resp, err := vm.Query(ctx, ds, tq)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "VictoriaMetrics.Query")
|
||||
}
|
||||
ss := resp.Results[""].Series
|
||||
//log.Infof("=====get ss: %s", jsonutils.Marshal(ss).PrettyString())
|
||||
|
||||
// parse fields
|
||||
retFields := sets.NewString()
|
||||
msPrefix := fmt.Sprintf("%s_", ms.Measurement)
|
||||
for _, s := range ss {
|
||||
cols := s.Columns
|
||||
for _, col := range cols {
|
||||
if !strings.HasPrefix(col, msPrefix) {
|
||||
continue
|
||||
}
|
||||
field := strings.TrimPrefix(col, msPrefix)
|
||||
retFields.Insert(field)
|
||||
}
|
||||
}
|
||||
retMs.FieldKey = retFields.List()
|
||||
if len(retMs.FieldKey) != 0 {
|
||||
retMs.Measurement = ms.Measurement
|
||||
retMs.Database = ms.Database
|
||||
retMs.ResType = ms.ResType
|
||||
}
|
||||
return retMs, nil
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package victoriametrics
|
||||
|
||||
/*import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"yunion.io/x/jsonutils"
|
||||
"yunion.io/x/log"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/apis/monitor"
|
||||
"yunion.io/x/onecloud/pkg/monitor/tsdb"
|
||||
)
|
||||
|
||||
func Test_queryByRaw(t *testing.T) {
|
||||
ds := &tsdb.DataSource{
|
||||
Id: "vm",
|
||||
Name: "vm",
|
||||
Type: "victoricmetrics",
|
||||
Url: "http://192.168.222.171:34795/",
|
||||
}
|
||||
q := &tsdb.TsdbQuery{
|
||||
TimeRange: tsdb.NewTimeRange("48h", "now"),
|
||||
Queries: []*tsdb.Query{
|
||||
{
|
||||
RefId: "A",
|
||||
MetricQuery: monitor.MetricQuery{
|
||||
Database: "telegraf",
|
||||
Measurement: "cpu",
|
||||
Selects: []monitor.MetricQuerySelect{
|
||||
{
|
||||
{
|
||||
Type: "field",
|
||||
Params: []string{"usage_active"},
|
||||
},
|
||||
{
|
||||
Type: "mean",
|
||||
},
|
||||
},
|
||||
},
|
||||
Tags: []monitor.MetricQueryTag{
|
||||
{
|
||||
Key: "res_type",
|
||||
Operator: "=",
|
||||
Value: "host",
|
||||
},
|
||||
},
|
||||
GroupBy: []monitor.MetricQueryPart{
|
||||
//{
|
||||
// Type: "tag",
|
||||
// Params: []string{"host_id"},
|
||||
//},
|
||||
{
|
||||
Type: "tag",
|
||||
Params: []string{"*"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Debug: false,
|
||||
}
|
||||
ep, _ := NewVMAdapter(ds)
|
||||
|
||||
resp, err := ep.Query(context.Background(), ds, q)
|
||||
if err != nil {
|
||||
t.Fatalf("queryByRaw error: %v", err)
|
||||
}
|
||||
log.Infof("resp: %s", jsonutils.Marshal(resp).PrettyString())
|
||||
}
|
||||
*/
|
||||
@@ -15,7 +15,12 @@
|
||||
package tsdb
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"yunion.io/x/pkg/util/sets"
|
||||
|
||||
api "yunion.io/x/onecloud/pkg/apis/monitor"
|
||||
)
|
||||
@@ -54,6 +59,7 @@ type QueryResult struct {
|
||||
}
|
||||
|
||||
type TimeSeries struct {
|
||||
// RawName is used to frontend displaying the curve name
|
||||
RawName string `json:"raw_name"`
|
||||
Columns []string `json:"columns"`
|
||||
Name string `json:"name"`
|
||||
@@ -61,6 +67,22 @@ type TimeSeries struct {
|
||||
Tags map[string]string `json:"tags,omitempty"`
|
||||
}
|
||||
|
||||
func NewTimeSeries(
|
||||
name string,
|
||||
rawName string,
|
||||
columns []string,
|
||||
points TimeSeriesPoints,
|
||||
tags map[string]string,
|
||||
) *TimeSeries {
|
||||
return &TimeSeries{
|
||||
RawName: rawName,
|
||||
Columns: columns,
|
||||
Name: name,
|
||||
Points: points,
|
||||
Tags: tags,
|
||||
}
|
||||
}
|
||||
|
||||
type Table struct {
|
||||
Columns []TableColumn `json:"columns"`
|
||||
Rows []RowValues `json:"rows"`
|
||||
@@ -154,9 +176,50 @@ func NewTimeSeriesPointsFromArgs(values ...float64) TimeSeriesPoints {
|
||||
return points
|
||||
}
|
||||
|
||||
func NewTimeSeries(name string, points TimeSeriesPoints) *TimeSeries {
|
||||
return &TimeSeries{
|
||||
Name: name,
|
||||
Points: points,
|
||||
func FormatRawName(idx int, name string, groupByTags []string, tags map[string]string) string {
|
||||
// when group by tag specified
|
||||
if len(groupByTags) != 0 {
|
||||
for key, val := range tags {
|
||||
if strings.Contains(strings.Join(groupByTags, ","), key) {
|
||||
return val
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
genHint := func(k, v string) string {
|
||||
return fmt.Sprintf("%s=%s", k, v)
|
||||
}
|
||||
|
||||
hintNames := sets.NewString()
|
||||
hints := sets.NewString()
|
||||
for _, tagKey := range api.MEASUREMENT_TAG_KEYWORD {
|
||||
if tagV, ok := tags[tagKey]; ok {
|
||||
gHint := genHint(tagKey, tagV)
|
||||
if strings.Contains(tagKey, "name") {
|
||||
hintNames.Insert(gHint)
|
||||
} else {
|
||||
hints.Insert(gHint)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(hints) == 0 {
|
||||
// try id
|
||||
for key, val := range tags {
|
||||
if strings.Contains(key, "id") && len(val) != 0 {
|
||||
hints.Insert(genHint(key, val))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(hints) == 0 {
|
||||
// if hints is empty at last, return index hint
|
||||
return fmt.Sprintf("unknown-%d-%s", idx, name)
|
||||
}
|
||||
sortNL := hintNames.List()
|
||||
sort.Strings(sortNL)
|
||||
sortL := hints.List()
|
||||
sort.Strings(sortL)
|
||||
sortNL = append(sortNL, sortL...)
|
||||
return fmt.Sprintf("{%s}", strings.Join(sortNL, ","))
|
||||
}
|
||||
|
||||
@@ -18,10 +18,13 @@ import (
|
||||
"context"
|
||||
|
||||
"yunion.io/x/pkg/errors"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/apis/monitor"
|
||||
)
|
||||
|
||||
type TsdbQueryEndpoint interface {
|
||||
Query(ctx context.Context, ds *DataSource, query *TsdbQuery) (*Response, error)
|
||||
FilterMeasurement(ctx context.Context, ds *DataSource, from, to string, ms *monitor.InfluxMeasurement, tagFilter *monitor.MetricQueryTag) (*monitor.InfluxMeasurement, error)
|
||||
}
|
||||
|
||||
var registry map[string]GetTsdbQueryEndpointFn
|
||||
@@ -32,19 +35,33 @@ func init() {
|
||||
registry = make(map[string]GetTsdbQueryEndpointFn)
|
||||
}
|
||||
|
||||
const (
|
||||
ErrorNotFoundExecutorDataSource = errors.Error("Not find executor for data source")
|
||||
var (
|
||||
ErrorNotFoundExecutorDataSource error = errors.Error("Not find executor for data source")
|
||||
)
|
||||
|
||||
func getTsdbQueryEndpointFor(dsInfo *DataSource) (TsdbQueryEndpoint, error) {
|
||||
if fn, exists := registry[dsInfo.Type]; exists {
|
||||
executor, err := fn(dsInfo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return executor, nil
|
||||
func getDataSourceFunc(dsType string) (GetTsdbQueryEndpointFn, error) {
|
||||
fn, exists := registry[dsType]
|
||||
if !exists {
|
||||
return nil, errors.Wrapf(ErrorNotFoundExecutorDataSource, "type: %s", dsType)
|
||||
}
|
||||
return nil, errors.Wrapf(ErrorNotFoundExecutorDataSource, "type: %s", dsInfo.Type)
|
||||
return fn, nil
|
||||
}
|
||||
|
||||
func GetTsdbQueryEndpointFor(dsInfo *DataSource) (TsdbQueryEndpoint, error) {
|
||||
fn, err := getDataSourceFunc(dsInfo.Type)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "getDataSourceFunc")
|
||||
}
|
||||
executor, err := fn(dsInfo)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "construct datasource query endpoint")
|
||||
}
|
||||
return executor, nil
|
||||
}
|
||||
|
||||
func IsValidDataSource(dsType string) error {
|
||||
_, err := getDataSourceFunc(dsType)
|
||||
return err
|
||||
}
|
||||
|
||||
func RegisterTsdbQueryEndpoint(dataSourceType string, fn GetTsdbQueryEndpointFn) {
|
||||
|
||||
@@ -21,7 +21,7 @@ import (
|
||||
type HandleRequestFunc func(ctx context.Context, dsInfo *DataSource, req *TsdbQuery) (*Response, error)
|
||||
|
||||
func HandleRequest(ctx context.Context, dsInfo *DataSource, req *TsdbQuery) (*Response, error) {
|
||||
endpoint, err := getTsdbQueryEndpointFor(dsInfo)
|
||||
endpoint, err := GetTsdbQueryEndpointFor(dsInfo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
Copyright (c) 2016 Caleb Spare
|
||||
|
||||
MIT License
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
# xxhash
|
||||
|
||||
[](https://godoc.org/github.com/cespare/xxhash)
|
||||
|
||||
xxhash is a Go implementation of the 64-bit
|
||||
[xxHash](http://cyan4973.github.io/xxHash/) algorithm, XXH64. This is a
|
||||
high-quality hashing algorithm that is much faster than anything in the Go
|
||||
standard library.
|
||||
|
||||
The API is very small, taking its cue from the other hashing packages in the
|
||||
standard library:
|
||||
|
||||
$ go doc github.com/cespare/xxhash !
|
||||
package xxhash // import "github.com/cespare/xxhash"
|
||||
|
||||
Package xxhash implements the 64-bit variant of xxHash (XXH64) as described
|
||||
at http://cyan4973.github.io/xxHash/.
|
||||
|
||||
func New() hash.Hash64
|
||||
func Sum64(b []byte) uint64
|
||||
func Sum64String(s string) uint64
|
||||
|
||||
This implementation provides a fast pure-Go implementation and an even faster
|
||||
assembly implementation for amd64.
|
||||
|
||||
## Benchmarks
|
||||
|
||||
Here are some quick benchmarks comparing the pure-Go and assembly
|
||||
implementations of Sum64 against another popular Go XXH64 implementation,
|
||||
[github.com/OneOfOne/xxhash](https://github.com/OneOfOne/xxhash):
|
||||
|
||||
| input size | OneOfOne | cespare (purego) | cespare |
|
||||
| --- | --- | --- | --- |
|
||||
| 5 B | 416 MB/s | 720 MB/s | 872 MB/s |
|
||||
| 100 B | 3980 MB/s | 5013 MB/s | 5252 MB/s |
|
||||
| 4 KB | 12727 MB/s | 12999 MB/s | 13026 MB/s |
|
||||
| 10 MB | 9879 MB/s | 10775 MB/s | 10913 MB/s |
|
||||
|
||||
These numbers were generated with:
|
||||
|
||||
```
|
||||
$ go test -benchtime 10s -bench '/OneOfOne,'
|
||||
$ go test -tags purego -benchtime 10s -bench '/xxhash,'
|
||||
$ go test -benchtime 10s -bench '/xxhash,'
|
||||
```
|
||||
|
||||
## Projects using this package
|
||||
|
||||
- [InfluxDB](https://github.com/influxdata/influxdb)
|
||||
- [Prometheus](https://github.com/prometheus/prometheus)
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
// +build !go1.9
|
||||
|
||||
package xxhash
|
||||
|
||||
// TODO(caleb): After Go 1.10 comes out, remove this fallback code.
|
||||
|
||||
func rol1(x uint64) uint64 { return (x << 1) | (x >> (64 - 1)) }
|
||||
func rol7(x uint64) uint64 { return (x << 7) | (x >> (64 - 7)) }
|
||||
func rol11(x uint64) uint64 { return (x << 11) | (x >> (64 - 11)) }
|
||||
func rol12(x uint64) uint64 { return (x << 12) | (x >> (64 - 12)) }
|
||||
func rol18(x uint64) uint64 { return (x << 18) | (x >> (64 - 18)) }
|
||||
func rol23(x uint64) uint64 { return (x << 23) | (x >> (64 - 23)) }
|
||||
func rol27(x uint64) uint64 { return (x << 27) | (x >> (64 - 27)) }
|
||||
func rol31(x uint64) uint64 { return (x << 31) | (x >> (64 - 31)) }
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
// +build go1.9
|
||||
|
||||
package xxhash
|
||||
|
||||
import "math/bits"
|
||||
|
||||
func rol1(x uint64) uint64 { return bits.RotateLeft64(x, 1) }
|
||||
func rol7(x uint64) uint64 { return bits.RotateLeft64(x, 7) }
|
||||
func rol11(x uint64) uint64 { return bits.RotateLeft64(x, 11) }
|
||||
func rol12(x uint64) uint64 { return bits.RotateLeft64(x, 12) }
|
||||
func rol18(x uint64) uint64 { return bits.RotateLeft64(x, 18) }
|
||||
func rol23(x uint64) uint64 { return bits.RotateLeft64(x, 23) }
|
||||
func rol27(x uint64) uint64 { return bits.RotateLeft64(x, 27) }
|
||||
func rol31(x uint64) uint64 { return bits.RotateLeft64(x, 31) }
|
||||
+168
@@ -0,0 +1,168 @@
|
||||
// Package xxhash implements the 64-bit variant of xxHash (XXH64) as described
|
||||
// at http://cyan4973.github.io/xxHash/.
|
||||
package xxhash
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"hash"
|
||||
)
|
||||
|
||||
const (
|
||||
prime1 uint64 = 11400714785074694791
|
||||
prime2 uint64 = 14029467366897019727
|
||||
prime3 uint64 = 1609587929392839161
|
||||
prime4 uint64 = 9650029242287828579
|
||||
prime5 uint64 = 2870177450012600261
|
||||
)
|
||||
|
||||
// NOTE(caleb): I'm using both consts and vars of the primes. Using consts where
|
||||
// possible in the Go code is worth a small (but measurable) performance boost
|
||||
// by avoiding some MOVQs. Vars are needed for the asm and also are useful for
|
||||
// convenience in the Go code in a few places where we need to intentionally
|
||||
// avoid constant arithmetic (e.g., v1 := prime1 + prime2 fails because the
|
||||
// result overflows a uint64).
|
||||
var (
|
||||
prime1v = prime1
|
||||
prime2v = prime2
|
||||
prime3v = prime3
|
||||
prime4v = prime4
|
||||
prime5v = prime5
|
||||
)
|
||||
|
||||
type xxh struct {
|
||||
v1 uint64
|
||||
v2 uint64
|
||||
v3 uint64
|
||||
v4 uint64
|
||||
total int
|
||||
mem [32]byte
|
||||
n int // how much of mem is used
|
||||
}
|
||||
|
||||
// New creates a new hash.Hash64 that implements the 64-bit xxHash algorithm.
|
||||
func New() hash.Hash64 {
|
||||
var x xxh
|
||||
x.Reset()
|
||||
return &x
|
||||
}
|
||||
|
||||
func (x *xxh) Reset() {
|
||||
x.n = 0
|
||||
x.total = 0
|
||||
x.v1 = prime1v + prime2
|
||||
x.v2 = prime2
|
||||
x.v3 = 0
|
||||
x.v4 = -prime1v
|
||||
}
|
||||
|
||||
func (x *xxh) Size() int { return 8 }
|
||||
func (x *xxh) BlockSize() int { return 32 }
|
||||
|
||||
// Write adds more data to x. It always returns len(b), nil.
|
||||
func (x *xxh) Write(b []byte) (n int, err error) {
|
||||
n = len(b)
|
||||
x.total += len(b)
|
||||
|
||||
if x.n+len(b) < 32 {
|
||||
// This new data doesn't even fill the current block.
|
||||
copy(x.mem[x.n:], b)
|
||||
x.n += len(b)
|
||||
return
|
||||
}
|
||||
|
||||
if x.n > 0 {
|
||||
// Finish off the partial block.
|
||||
copy(x.mem[x.n:], b)
|
||||
x.v1 = round(x.v1, u64(x.mem[0:8]))
|
||||
x.v2 = round(x.v2, u64(x.mem[8:16]))
|
||||
x.v3 = round(x.v3, u64(x.mem[16:24]))
|
||||
x.v4 = round(x.v4, u64(x.mem[24:32]))
|
||||
b = b[32-x.n:]
|
||||
x.n = 0
|
||||
}
|
||||
|
||||
if len(b) >= 32 {
|
||||
// One or more full blocks left.
|
||||
b = writeBlocks(x, b)
|
||||
}
|
||||
|
||||
// Store any remaining partial block.
|
||||
copy(x.mem[:], b)
|
||||
x.n = len(b)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (x *xxh) Sum(b []byte) []byte {
|
||||
s := x.Sum64()
|
||||
return append(
|
||||
b,
|
||||
byte(s>>56),
|
||||
byte(s>>48),
|
||||
byte(s>>40),
|
||||
byte(s>>32),
|
||||
byte(s>>24),
|
||||
byte(s>>16),
|
||||
byte(s>>8),
|
||||
byte(s),
|
||||
)
|
||||
}
|
||||
|
||||
func (x *xxh) Sum64() uint64 {
|
||||
var h uint64
|
||||
|
||||
if x.total >= 32 {
|
||||
v1, v2, v3, v4 := x.v1, x.v2, x.v3, x.v4
|
||||
h = rol1(v1) + rol7(v2) + rol12(v3) + rol18(v4)
|
||||
h = mergeRound(h, v1)
|
||||
h = mergeRound(h, v2)
|
||||
h = mergeRound(h, v3)
|
||||
h = mergeRound(h, v4)
|
||||
} else {
|
||||
h = x.v3 + prime5
|
||||
}
|
||||
|
||||
h += uint64(x.total)
|
||||
|
||||
i, end := 0, x.n
|
||||
for ; i+8 <= end; i += 8 {
|
||||
k1 := round(0, u64(x.mem[i:i+8]))
|
||||
h ^= k1
|
||||
h = rol27(h)*prime1 + prime4
|
||||
}
|
||||
if i+4 <= end {
|
||||
h ^= uint64(u32(x.mem[i:i+4])) * prime1
|
||||
h = rol23(h)*prime2 + prime3
|
||||
i += 4
|
||||
}
|
||||
for i < end {
|
||||
h ^= uint64(x.mem[i]) * prime5
|
||||
h = rol11(h) * prime1
|
||||
i++
|
||||
}
|
||||
|
||||
h ^= h >> 33
|
||||
h *= prime2
|
||||
h ^= h >> 29
|
||||
h *= prime3
|
||||
h ^= h >> 32
|
||||
|
||||
return h
|
||||
}
|
||||
|
||||
func u64(b []byte) uint64 { return binary.LittleEndian.Uint64(b) }
|
||||
func u32(b []byte) uint32 { return binary.LittleEndian.Uint32(b) }
|
||||
|
||||
func round(acc, input uint64) uint64 {
|
||||
acc += input * prime2
|
||||
acc = rol31(acc)
|
||||
acc *= prime1
|
||||
return acc
|
||||
}
|
||||
|
||||
func mergeRound(acc, val uint64) uint64 {
|
||||
val = round(0, val)
|
||||
acc ^= val
|
||||
acc = acc*prime1 + prime4
|
||||
return acc
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
// +build !appengine
|
||||
// +build gc
|
||||
// +build !purego
|
||||
|
||||
package xxhash
|
||||
|
||||
// Sum64 computes the 64-bit xxHash digest of b.
|
||||
//
|
||||
//go:noescape
|
||||
func Sum64(b []byte) uint64
|
||||
|
||||
func writeBlocks(x *xxh, b []byte) []byte
|
||||
+233
@@ -0,0 +1,233 @@
|
||||
// +build !appengine
|
||||
// +build gc
|
||||
// +build !purego
|
||||
|
||||
#include "textflag.h"
|
||||
|
||||
// Register allocation:
|
||||
// AX h
|
||||
// CX pointer to advance through b
|
||||
// DX n
|
||||
// BX loop end
|
||||
// R8 v1, k1
|
||||
// R9 v2
|
||||
// R10 v3
|
||||
// R11 v4
|
||||
// R12 tmp
|
||||
// R13 prime1v
|
||||
// R14 prime2v
|
||||
// R15 prime4v
|
||||
|
||||
// round reads from and advances the buffer pointer in CX.
|
||||
// It assumes that R13 has prime1v and R14 has prime2v.
|
||||
#define round(r) \
|
||||
MOVQ (CX), R12 \
|
||||
ADDQ $8, CX \
|
||||
IMULQ R14, R12 \
|
||||
ADDQ R12, r \
|
||||
ROLQ $31, r \
|
||||
IMULQ R13, r
|
||||
|
||||
// mergeRound applies a merge round on the two registers acc and val.
|
||||
// It assumes that R13 has prime1v, R14 has prime2v, and R15 has prime4v.
|
||||
#define mergeRound(acc, val) \
|
||||
IMULQ R14, val \
|
||||
ROLQ $31, val \
|
||||
IMULQ R13, val \
|
||||
XORQ val, acc \
|
||||
IMULQ R13, acc \
|
||||
ADDQ R15, acc
|
||||
|
||||
// func Sum64(b []byte) uint64
|
||||
TEXT ·Sum64(SB), NOSPLIT, $0-32
|
||||
// Load fixed primes.
|
||||
MOVQ ·prime1v(SB), R13
|
||||
MOVQ ·prime2v(SB), R14
|
||||
MOVQ ·prime4v(SB), R15
|
||||
|
||||
// Load slice.
|
||||
MOVQ b_base+0(FP), CX
|
||||
MOVQ b_len+8(FP), DX
|
||||
LEAQ (CX)(DX*1), BX
|
||||
|
||||
// The first loop limit will be len(b)-32.
|
||||
SUBQ $32, BX
|
||||
|
||||
// Check whether we have at least one block.
|
||||
CMPQ DX, $32
|
||||
JLT noBlocks
|
||||
|
||||
// Set up initial state (v1, v2, v3, v4).
|
||||
MOVQ R13, R8
|
||||
ADDQ R14, R8
|
||||
MOVQ R14, R9
|
||||
XORQ R10, R10
|
||||
XORQ R11, R11
|
||||
SUBQ R13, R11
|
||||
|
||||
// Loop until CX > BX.
|
||||
blockLoop:
|
||||
round(R8)
|
||||
round(R9)
|
||||
round(R10)
|
||||
round(R11)
|
||||
|
||||
CMPQ CX, BX
|
||||
JLE blockLoop
|
||||
|
||||
MOVQ R8, AX
|
||||
ROLQ $1, AX
|
||||
MOVQ R9, R12
|
||||
ROLQ $7, R12
|
||||
ADDQ R12, AX
|
||||
MOVQ R10, R12
|
||||
ROLQ $12, R12
|
||||
ADDQ R12, AX
|
||||
MOVQ R11, R12
|
||||
ROLQ $18, R12
|
||||
ADDQ R12, AX
|
||||
|
||||
mergeRound(AX, R8)
|
||||
mergeRound(AX, R9)
|
||||
mergeRound(AX, R10)
|
||||
mergeRound(AX, R11)
|
||||
|
||||
JMP afterBlocks
|
||||
|
||||
noBlocks:
|
||||
MOVQ ·prime5v(SB), AX
|
||||
|
||||
afterBlocks:
|
||||
ADDQ DX, AX
|
||||
|
||||
// Right now BX has len(b)-32, and we want to loop until CX > len(b)-8.
|
||||
ADDQ $24, BX
|
||||
|
||||
CMPQ CX, BX
|
||||
JG fourByte
|
||||
|
||||
wordLoop:
|
||||
// Calculate k1.
|
||||
MOVQ (CX), R8
|
||||
ADDQ $8, CX
|
||||
IMULQ R14, R8
|
||||
ROLQ $31, R8
|
||||
IMULQ R13, R8
|
||||
|
||||
XORQ R8, AX
|
||||
ROLQ $27, AX
|
||||
IMULQ R13, AX
|
||||
ADDQ R15, AX
|
||||
|
||||
CMPQ CX, BX
|
||||
JLE wordLoop
|
||||
|
||||
fourByte:
|
||||
ADDQ $4, BX
|
||||
CMPQ CX, BX
|
||||
JG singles
|
||||
|
||||
MOVL (CX), R8
|
||||
ADDQ $4, CX
|
||||
IMULQ R13, R8
|
||||
XORQ R8, AX
|
||||
|
||||
ROLQ $23, AX
|
||||
IMULQ R14, AX
|
||||
ADDQ ·prime3v(SB), AX
|
||||
|
||||
singles:
|
||||
ADDQ $4, BX
|
||||
CMPQ CX, BX
|
||||
JGE finalize
|
||||
|
||||
singlesLoop:
|
||||
MOVBQZX (CX), R12
|
||||
ADDQ $1, CX
|
||||
IMULQ ·prime5v(SB), R12
|
||||
XORQ R12, AX
|
||||
|
||||
ROLQ $11, AX
|
||||
IMULQ R13, AX
|
||||
|
||||
CMPQ CX, BX
|
||||
JL singlesLoop
|
||||
|
||||
finalize:
|
||||
MOVQ AX, R12
|
||||
SHRQ $33, R12
|
||||
XORQ R12, AX
|
||||
IMULQ R14, AX
|
||||
MOVQ AX, R12
|
||||
SHRQ $29, R12
|
||||
XORQ R12, AX
|
||||
IMULQ ·prime3v(SB), AX
|
||||
MOVQ AX, R12
|
||||
SHRQ $32, R12
|
||||
XORQ R12, AX
|
||||
|
||||
MOVQ AX, ret+24(FP)
|
||||
RET
|
||||
|
||||
// writeBlocks uses the same registers as above except that it uses AX to store
|
||||
// the x pointer.
|
||||
|
||||
// func writeBlocks(x *xxh, b []byte) []byte
|
||||
TEXT ·writeBlocks(SB), NOSPLIT, $0-56
|
||||
// Load fixed primes needed for round.
|
||||
MOVQ ·prime1v(SB), R13
|
||||
MOVQ ·prime2v(SB), R14
|
||||
|
||||
// Load slice.
|
||||
MOVQ b_base+8(FP), CX
|
||||
MOVQ CX, ret_base+32(FP) // initialize return base pointer; see NOTE below
|
||||
MOVQ b_len+16(FP), DX
|
||||
LEAQ (CX)(DX*1), BX
|
||||
SUBQ $32, BX
|
||||
|
||||
// Load vN from x.
|
||||
MOVQ x+0(FP), AX
|
||||
MOVQ 0(AX), R8 // v1
|
||||
MOVQ 8(AX), R9 // v2
|
||||
MOVQ 16(AX), R10 // v3
|
||||
MOVQ 24(AX), R11 // v4
|
||||
|
||||
// We don't need to check the loop condition here; this function is
|
||||
// always called with at least one block of data to process.
|
||||
blockLoop:
|
||||
round(R8)
|
||||
round(R9)
|
||||
round(R10)
|
||||
round(R11)
|
||||
|
||||
CMPQ CX, BX
|
||||
JLE blockLoop
|
||||
|
||||
// Copy vN back to x.
|
||||
MOVQ R8, 0(AX)
|
||||
MOVQ R9, 8(AX)
|
||||
MOVQ R10, 16(AX)
|
||||
MOVQ R11, 24(AX)
|
||||
|
||||
// Construct return slice.
|
||||
// NOTE: It's important that we don't construct a slice that has a base
|
||||
// pointer off the end of the original slice, as in Go 1.7+ this will
|
||||
// cause runtime crashes. (See discussion in, for example,
|
||||
// https://github.com/golang/go/issues/16772.)
|
||||
// Therefore, we calculate the length/cap first, and if they're zero, we
|
||||
// keep the old base. This is what the compiler does as well if you
|
||||
// write code like
|
||||
// b = b[len(b):]
|
||||
|
||||
// New length is 32 - (CX - BX) -> BX+32 - CX.
|
||||
ADDQ $32, BX
|
||||
SUBQ CX, BX
|
||||
JZ afterSetBase
|
||||
|
||||
MOVQ CX, ret_base+32(FP)
|
||||
|
||||
afterSetBase:
|
||||
MOVQ BX, ret_len+40(FP)
|
||||
MOVQ BX, ret_cap+48(FP) // set cap == len
|
||||
|
||||
RET
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
// +build !amd64 appengine !gc purego
|
||||
|
||||
package xxhash
|
||||
|
||||
// Sum64 computes the 64-bit xxHash digest of b.
|
||||
func Sum64(b []byte) uint64 {
|
||||
// A simpler version would be
|
||||
// x := New()
|
||||
// x.Write(b)
|
||||
// return x.Sum64()
|
||||
// but this is faster, particularly for small inputs.
|
||||
|
||||
n := len(b)
|
||||
var h uint64
|
||||
|
||||
if n >= 32 {
|
||||
v1 := prime1v + prime2
|
||||
v2 := prime2
|
||||
v3 := uint64(0)
|
||||
v4 := -prime1v
|
||||
for len(b) >= 32 {
|
||||
v1 = round(v1, u64(b[0:8:len(b)]))
|
||||
v2 = round(v2, u64(b[8:16:len(b)]))
|
||||
v3 = round(v3, u64(b[16:24:len(b)]))
|
||||
v4 = round(v4, u64(b[24:32:len(b)]))
|
||||
b = b[32:len(b):len(b)]
|
||||
}
|
||||
h = rol1(v1) + rol7(v2) + rol12(v3) + rol18(v4)
|
||||
h = mergeRound(h, v1)
|
||||
h = mergeRound(h, v2)
|
||||
h = mergeRound(h, v3)
|
||||
h = mergeRound(h, v4)
|
||||
} else {
|
||||
h = prime5
|
||||
}
|
||||
|
||||
h += uint64(n)
|
||||
|
||||
i, end := 0, len(b)
|
||||
for ; i+8 <= end; i += 8 {
|
||||
k1 := round(0, u64(b[i:i+8:len(b)]))
|
||||
h ^= k1
|
||||
h = rol27(h)*prime1 + prime4
|
||||
}
|
||||
if i+4 <= end {
|
||||
h ^= uint64(u32(b[i:i+4:len(b)])) * prime1
|
||||
h = rol23(h)*prime2 + prime3
|
||||
i += 4
|
||||
}
|
||||
for ; i < end; i++ {
|
||||
h ^= uint64(b[i]) * prime5
|
||||
h = rol11(h) * prime1
|
||||
}
|
||||
|
||||
h ^= h >> 33
|
||||
h *= prime2
|
||||
h ^= h >> 29
|
||||
h *= prime3
|
||||
h ^= h >> 32
|
||||
|
||||
return h
|
||||
}
|
||||
|
||||
func writeBlocks(x *xxh, b []byte) []byte {
|
||||
v1, v2, v3, v4 := x.v1, x.v2, x.v3, x.v4
|
||||
for len(b) >= 32 {
|
||||
v1 = round(v1, u64(b[0:8:len(b)]))
|
||||
v2 = round(v2, u64(b[8:16:len(b)]))
|
||||
v3 = round(v3, u64(b[16:24:len(b)]))
|
||||
v4 = round(v4, u64(b[24:32:len(b)]))
|
||||
b = b[32:len(b):len(b)]
|
||||
}
|
||||
x.v1, x.v2, x.v3, x.v4 = v1, v2, v3, v4
|
||||
return b
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
// +build appengine
|
||||
|
||||
// This file contains the safe implementations of otherwise unsafe-using code.
|
||||
|
||||
package xxhash
|
||||
|
||||
// Sum64String computes the 64-bit xxHash digest of s.
|
||||
func Sum64String(s string) uint64 {
|
||||
return Sum64([]byte(s))
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
// +build !appengine
|
||||
|
||||
// This file encapsulates usage of unsafe.
|
||||
// xxhash_safe.go contains the safe implementations.
|
||||
|
||||
package xxhash
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// Sum64String computes the 64-bit xxHash digest of s.
|
||||
// It may be faster than Sum64([]byte(s)) by avoiding a copy.
|
||||
//
|
||||
// TODO(caleb): Consider removing this if an optimization is ever added to make
|
||||
// it unnecessary: https://golang.org/issue/2205.
|
||||
//
|
||||
// TODO(caleb): We still have a function call; we could instead write Go/asm
|
||||
// copies of Sum64 for strings to squeeze out a bit more speed.
|
||||
func Sum64String(s string) uint64 {
|
||||
// See https://groups.google.com/d/msg/golang-nuts/dcjzJy-bSpw/tcZYBzQqAQAJ
|
||||
// for some discussion about this unsafe conversion.
|
||||
var b []byte
|
||||
bh := (*reflect.SliceHeader)(unsafe.Pointer(&b))
|
||||
bh.Data = (*reflect.StringHeader)(unsafe.Pointer(&s)).Data
|
||||
bh.Len = len(s)
|
||||
bh.Cap = len(s)
|
||||
return Sum64(b)
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
|
||||
# Test binary, build with `go test -c`
|
||||
*.test
|
||||
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
|
||||
# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736
|
||||
.glide/
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
pipeline {
|
||||
agent {
|
||||
docker {
|
||||
image 'golang:1.9.2'
|
||||
}
|
||||
}
|
||||
|
||||
stages {
|
||||
stage('Test') {
|
||||
steps {
|
||||
sh """
|
||||
rm -f $WORKSPACE/test-results.{log,xml}
|
||||
mkdir -p /go/src/github.com/influxdata
|
||||
cp -a $WORKSPACE /go/src/github.com/influxdata/influxql
|
||||
|
||||
cd /go/src/github.com/influxdata/influxql
|
||||
go get -v -t
|
||||
go test -v | tee $WORKSPACE/test-results.log
|
||||
"""
|
||||
}
|
||||
|
||||
post {
|
||||
always {
|
||||
sh """
|
||||
if [ -e test-results.log ]; then
|
||||
go get github.com/jstemmer/go-junit-report
|
||||
go-junit-report < $WORKSPACE/test-results.log > test-results.xml
|
||||
fi
|
||||
"""
|
||||
junit "test-results.xml"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2013-2016 Errplane Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
+1170
File diff suppressed because it is too large
Load Diff
+5814
File diff suppressed because it is too large
Load Diff
+12
@@ -0,0 +1,12 @@
|
||||
/*
|
||||
Package influxql implements a parser for the InfluxDB query language.
|
||||
|
||||
InfluxQL is a DML and DDL language for the InfluxDB time series database.
|
||||
It provides the ability to query for aggregate statistics as well as create
|
||||
and configure the InfluxDB server.
|
||||
|
||||
See https://docs.influxdata.com/influxdb/latest/query_language/
|
||||
for a reference on using InfluxQL.
|
||||
|
||||
*/
|
||||
package influxql
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
package influxql // import "github.com/influxdata/influxql"
|
||||
|
||||
//go:generate protoc --gogo_out=. internal/internal.proto
|
||||
+120
@@ -0,0 +1,120 @@
|
||||
// Code generated by protoc-gen-gogo.
|
||||
// source: internal/internal.proto
|
||||
// DO NOT EDIT!
|
||||
|
||||
/*
|
||||
Package influxql is a generated protocol buffer package.
|
||||
|
||||
It is generated from these files:
|
||||
internal/internal.proto
|
||||
|
||||
It has these top-level messages:
|
||||
Measurements
|
||||
Measurement
|
||||
*/
|
||||
package influxql
|
||||
|
||||
import proto "github.com/gogo/protobuf/proto"
|
||||
import fmt "fmt"
|
||||
import math "math"
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ = proto.Marshal
|
||||
var _ = fmt.Errorf
|
||||
var _ = math.Inf
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the proto package it is being compiled against.
|
||||
// A compilation error at this line likely means your copy of the
|
||||
// proto package needs to be updated.
|
||||
const _ = proto.GoGoProtoPackageIsVersion2 // please upgrade the proto package
|
||||
|
||||
type Measurements struct {
|
||||
Items []*Measurement `protobuf:"bytes,1,rep,name=Items" json:"Items,omitempty"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
}
|
||||
|
||||
func (m *Measurements) Reset() { *m = Measurements{} }
|
||||
func (m *Measurements) String() string { return proto.CompactTextString(m) }
|
||||
func (*Measurements) ProtoMessage() {}
|
||||
func (*Measurements) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{0} }
|
||||
|
||||
func (m *Measurements) GetItems() []*Measurement {
|
||||
if m != nil {
|
||||
return m.Items
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type Measurement struct {
|
||||
Database *string `protobuf:"bytes,1,opt,name=Database" json:"Database,omitempty"`
|
||||
RetentionPolicy *string `protobuf:"bytes,2,opt,name=RetentionPolicy" json:"RetentionPolicy,omitempty"`
|
||||
Name *string `protobuf:"bytes,3,opt,name=Name" json:"Name,omitempty"`
|
||||
Regex *string `protobuf:"bytes,4,opt,name=Regex" json:"Regex,omitempty"`
|
||||
IsTarget *bool `protobuf:"varint,5,opt,name=IsTarget" json:"IsTarget,omitempty"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
}
|
||||
|
||||
func (m *Measurement) Reset() { *m = Measurement{} }
|
||||
func (m *Measurement) String() string { return proto.CompactTextString(m) }
|
||||
func (*Measurement) ProtoMessage() {}
|
||||
func (*Measurement) Descriptor() ([]byte, []int) { return fileDescriptorInternal, []int{1} }
|
||||
|
||||
func (m *Measurement) GetDatabase() string {
|
||||
if m != nil && m.Database != nil {
|
||||
return *m.Database
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *Measurement) GetRetentionPolicy() string {
|
||||
if m != nil && m.RetentionPolicy != nil {
|
||||
return *m.RetentionPolicy
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *Measurement) GetName() string {
|
||||
if m != nil && m.Name != nil {
|
||||
return *m.Name
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *Measurement) GetRegex() string {
|
||||
if m != nil && m.Regex != nil {
|
||||
return *m.Regex
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *Measurement) GetIsTarget() bool {
|
||||
if m != nil && m.IsTarget != nil {
|
||||
return *m.IsTarget
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func init() {
|
||||
proto.RegisterType((*Measurements)(nil), "influxql.Measurements")
|
||||
proto.RegisterType((*Measurement)(nil), "influxql.Measurement")
|
||||
}
|
||||
|
||||
func init() { proto.RegisterFile("internal/internal.proto", fileDescriptorInternal) }
|
||||
|
||||
var fileDescriptorInternal = []byte{
|
||||
// 195 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x12, 0xcf, 0xcc, 0x2b, 0x49,
|
||||
0x2d, 0xca, 0x4b, 0xcc, 0xd1, 0x87, 0x31, 0xf4, 0x0a, 0x8a, 0xf2, 0x4b, 0xf2, 0x85, 0x38, 0x32,
|
||||
0xf3, 0xd2, 0x72, 0x4a, 0x2b, 0x0a, 0x73, 0x94, 0xac, 0xb9, 0x78, 0x7c, 0x53, 0x13, 0x8b, 0x4b,
|
||||
0x8b, 0x52, 0x73, 0x53, 0xf3, 0x4a, 0x8a, 0x85, 0xb4, 0xb9, 0x58, 0x3d, 0x4b, 0x52, 0x73, 0x8b,
|
||||
0x25, 0x18, 0x15, 0x98, 0x35, 0xb8, 0x8d, 0x44, 0xf5, 0x60, 0x2a, 0xf5, 0x90, 0x94, 0x05, 0x41,
|
||||
0xd4, 0x28, 0xcd, 0x64, 0xe4, 0xe2, 0x46, 0x12, 0x16, 0x92, 0xe2, 0xe2, 0x70, 0x49, 0x2c, 0x49,
|
||||
0x4c, 0x4a, 0x2c, 0x4e, 0x95, 0x60, 0x54, 0x60, 0xd4, 0xe0, 0x0c, 0x82, 0xf3, 0x85, 0x34, 0xb8,
|
||||
0xf8, 0x83, 0x52, 0x4b, 0x52, 0xf3, 0x4a, 0x32, 0xf3, 0xf3, 0x02, 0xf2, 0x73, 0x32, 0x93, 0x2b,
|
||||
0x25, 0x98, 0xc0, 0x4a, 0xd0, 0x85, 0x85, 0x84, 0xb8, 0x58, 0xfc, 0x12, 0x73, 0x53, 0x25, 0x98,
|
||||
0xc1, 0xd2, 0x60, 0xb6, 0x90, 0x08, 0x17, 0x6b, 0x50, 0x6a, 0x7a, 0x6a, 0x85, 0x04, 0x0b, 0x58,
|
||||
0x10, 0xc2, 0x01, 0xd9, 0xe7, 0x59, 0x1c, 0x92, 0x58, 0x94, 0x9e, 0x5a, 0x22, 0xc1, 0xaa, 0xc0,
|
||||
0xa8, 0xc1, 0x11, 0x04, 0xe7, 0x03, 0x02, 0x00, 0x00, 0xff, 0xff, 0xb8, 0x16, 0x06, 0x23, 0xfc,
|
||||
0x00, 0x00, 0x00,
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
syntax = "proto2";
|
||||
package influxql;
|
||||
|
||||
message Measurements {
|
||||
repeated Measurement Items = 1;
|
||||
}
|
||||
|
||||
message Measurement {
|
||||
optional string Database = 1;
|
||||
optional string RetentionPolicy = 2;
|
||||
optional string Name = 3;
|
||||
optional string Regex = 4;
|
||||
optional bool IsTarget = 5;
|
||||
}
|
||||
+180
@@ -0,0 +1,180 @@
|
||||
package influxql
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Value represents a value that can be bound
|
||||
// to a parameter when parsing the query.
|
||||
type Value interface {
|
||||
TokenType() Token
|
||||
Value() string
|
||||
}
|
||||
|
||||
type (
|
||||
// Identifier is an identifier value.
|
||||
Identifier string
|
||||
|
||||
// StringValue is a string literal.
|
||||
StringValue string
|
||||
|
||||
// RegexValue is a regexp literal.
|
||||
RegexValue string
|
||||
|
||||
// NumberValue is a number literal.
|
||||
NumberValue float64
|
||||
|
||||
// IntegerValue is an integer literal.
|
||||
IntegerValue int64
|
||||
|
||||
// BooleanValue is a boolean literal.
|
||||
BooleanValue bool
|
||||
|
||||
// DurationValue is a duration literal.
|
||||
DurationValue string
|
||||
|
||||
// ErrorValue is a special value that returns an error during parsing
|
||||
// when it is used.
|
||||
ErrorValue string
|
||||
)
|
||||
|
||||
// BindValue will bind an interface value to its influxql value.
|
||||
// This method of binding values only supports literals.
|
||||
func BindValue(v interface{}) Value {
|
||||
if jv, ok := v.(json.Number); ok {
|
||||
var err error
|
||||
v, err = jsonNumberToValue(jv)
|
||||
if err != nil {
|
||||
return ErrorValue(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
switch v := v.(type) {
|
||||
case float64:
|
||||
return NumberValue(v)
|
||||
case int64:
|
||||
return IntegerValue(v)
|
||||
case string:
|
||||
return StringValue(v)
|
||||
case bool:
|
||||
return BooleanValue(v)
|
||||
case map[string]interface{}:
|
||||
return bindObjectValue(v)
|
||||
default:
|
||||
s := fmt.Sprintf("unable to bind parameter with type %T", v)
|
||||
return ErrorValue(s)
|
||||
}
|
||||
}
|
||||
|
||||
// bindObjectValue will bind an object to a value.
|
||||
func bindObjectValue(m map[string]interface{}) Value {
|
||||
if len(m) != 1 {
|
||||
return ErrorValue("bound object parameter value must have exactly one entry")
|
||||
}
|
||||
|
||||
var (
|
||||
k string
|
||||
v interface{}
|
||||
)
|
||||
for k, v = range m {
|
||||
// Nothing done here.
|
||||
}
|
||||
|
||||
if jv, ok := v.(json.Number); ok {
|
||||
var err error
|
||||
v, err = jsonNumberToValue(jv)
|
||||
if err != nil {
|
||||
return ErrorValue(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
switch k {
|
||||
case "ident", "identifier":
|
||||
s, ok := v.(string)
|
||||
if !ok {
|
||||
return ErrorValue("identifier must be a string value")
|
||||
}
|
||||
return Identifier(s)
|
||||
case "regex":
|
||||
s, ok := v.(string)
|
||||
if !ok {
|
||||
return ErrorValue("regex literal must be a string value")
|
||||
}
|
||||
return RegexValue(s)
|
||||
case "string":
|
||||
s, ok := v.(string)
|
||||
if !ok {
|
||||
return ErrorValue("string literal must be a string value")
|
||||
}
|
||||
return StringValue(s)
|
||||
case "float", "number":
|
||||
switch f := v.(type) {
|
||||
case float64:
|
||||
return NumberValue(f)
|
||||
case int64:
|
||||
return NumberValue(f)
|
||||
default:
|
||||
return ErrorValue("number literal must be a float value")
|
||||
}
|
||||
case "int", "integer":
|
||||
i, ok := v.(int64)
|
||||
if !ok {
|
||||
return ErrorValue("integer literal must be an integer value")
|
||||
}
|
||||
return IntegerValue(i)
|
||||
case "duration":
|
||||
switch d := v.(type) {
|
||||
case string:
|
||||
return DurationValue(d)
|
||||
case int64:
|
||||
return DurationValue(FormatDuration(time.Duration(d)))
|
||||
default:
|
||||
return ErrorValue("duration literal must be a string or integer value")
|
||||
}
|
||||
default:
|
||||
return ErrorValue(fmt.Sprintf("unknown bind object type: %s", k))
|
||||
}
|
||||
}
|
||||
|
||||
func (v Identifier) TokenType() Token { return IDENT }
|
||||
func (v Identifier) Value() string { return string(v) }
|
||||
func (v StringValue) TokenType() Token { return STRING }
|
||||
func (v StringValue) Value() string { return string(v) }
|
||||
func (v RegexValue) TokenType() Token { return REGEX }
|
||||
func (v RegexValue) Value() string { return string(v) }
|
||||
func (v NumberValue) TokenType() Token { return NUMBER }
|
||||
func (v NumberValue) Value() string { return strconv.FormatFloat(float64(v), 'f', -1, 64) }
|
||||
func (v IntegerValue) TokenType() Token { return INTEGER }
|
||||
func (v IntegerValue) Value() string { return strconv.FormatInt(int64(v), 10) }
|
||||
func (v BooleanValue) TokenType() Token {
|
||||
if v {
|
||||
return TRUE
|
||||
} else {
|
||||
return FALSE
|
||||
}
|
||||
}
|
||||
func (v BooleanValue) Value() string { return "" }
|
||||
func (v DurationValue) TokenType() Token { return DURATIONVAL }
|
||||
func (v DurationValue) Value() string { return string(v) }
|
||||
func (e ErrorValue) TokenType() Token { return BOUNDPARAM }
|
||||
func (e ErrorValue) Value() string { return string(e) }
|
||||
|
||||
func jsonNumberToValue(v json.Number) (interface{}, error) {
|
||||
if strings.Contains(string(v), ".") {
|
||||
f, err := v.Float64()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return f, nil
|
||||
} else {
|
||||
i, err := v.Int64()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return i, nil
|
||||
}
|
||||
}
|
||||
+236
@@ -0,0 +1,236 @@
|
||||
package influxql
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
var Language = &ParseTree{}
|
||||
|
||||
type ParseTree struct {
|
||||
Handlers map[Token]func(*Parser) (Statement, error)
|
||||
Tokens map[Token]*ParseTree
|
||||
Keys []string
|
||||
}
|
||||
|
||||
// With passes the current parse tree to a function to allow nested functions.
|
||||
func (t *ParseTree) With(fn func(*ParseTree)) {
|
||||
fn(t)
|
||||
}
|
||||
|
||||
// Group groups together a set of related handlers with a common token prefix.
|
||||
func (t *ParseTree) Group(tokens ...Token) *ParseTree {
|
||||
for _, tok := range tokens {
|
||||
// Look for the parse tree for this token.
|
||||
if subtree := t.Tokens[tok]; subtree != nil {
|
||||
t = subtree
|
||||
continue
|
||||
}
|
||||
|
||||
// No subtree exists yet. Verify that we don't have a conflicting
|
||||
// statement.
|
||||
if _, conflict := t.Handlers[tok]; conflict {
|
||||
panic(fmt.Sprintf("conflict for token %s", tok))
|
||||
}
|
||||
|
||||
// Create the new parse tree and register it inside of this one for
|
||||
// later reference.
|
||||
newT := &ParseTree{}
|
||||
if t.Tokens == nil {
|
||||
t.Tokens = make(map[Token]*ParseTree)
|
||||
}
|
||||
t.Tokens[tok] = newT
|
||||
t.Keys = append(t.Keys, tok.String())
|
||||
t = newT
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
// Handle registers a handler to be invoked when seeing the given token.
|
||||
func (t *ParseTree) Handle(tok Token, fn func(*Parser) (Statement, error)) {
|
||||
// Verify that there is no conflict for this token in this parse tree.
|
||||
if _, conflict := t.Tokens[tok]; conflict {
|
||||
panic(fmt.Sprintf("conflict for token %s", tok))
|
||||
}
|
||||
|
||||
if _, conflict := t.Handlers[tok]; conflict {
|
||||
panic(fmt.Sprintf("conflict for token %s", tok))
|
||||
}
|
||||
|
||||
if t.Handlers == nil {
|
||||
t.Handlers = make(map[Token]func(*Parser) (Statement, error))
|
||||
}
|
||||
t.Handlers[tok] = fn
|
||||
t.Keys = append(t.Keys, tok.String())
|
||||
}
|
||||
|
||||
// Parse parses a statement using the language defined in the parse tree.
|
||||
func (t *ParseTree) Parse(p *Parser) (Statement, error) {
|
||||
for {
|
||||
tok, pos, lit := p.ScanIgnoreWhitespace()
|
||||
if subtree := t.Tokens[tok]; subtree != nil {
|
||||
t = subtree
|
||||
continue
|
||||
}
|
||||
|
||||
if stmt := t.Handlers[tok]; stmt != nil {
|
||||
return stmt(p)
|
||||
}
|
||||
|
||||
// There were no registered handlers. Return the valid tokens in the order they were added.
|
||||
return nil, newParseError(tokstr(tok, lit), t.Keys, pos)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *ParseTree) Clone() *ParseTree {
|
||||
newT := &ParseTree{}
|
||||
if t.Handlers != nil {
|
||||
newT.Handlers = make(map[Token]func(*Parser) (Statement, error), len(t.Handlers))
|
||||
for tok, handler := range t.Handlers {
|
||||
newT.Handlers[tok] = handler
|
||||
}
|
||||
}
|
||||
|
||||
if t.Tokens != nil {
|
||||
newT.Tokens = make(map[Token]*ParseTree, len(t.Tokens))
|
||||
for tok, subtree := range t.Tokens {
|
||||
newT.Tokens[tok] = subtree.Clone()
|
||||
}
|
||||
}
|
||||
return newT
|
||||
}
|
||||
|
||||
func init() {
|
||||
Language.Handle(SELECT, func(p *Parser) (Statement, error) {
|
||||
return p.parseSelectStatement(targetNotRequired)
|
||||
})
|
||||
Language.Handle(DELETE, func(p *Parser) (Statement, error) {
|
||||
return p.parseDeleteStatement()
|
||||
})
|
||||
Language.Group(SHOW).With(func(show *ParseTree) {
|
||||
show.Group(CONTINUOUS).Handle(QUERIES, func(p *Parser) (Statement, error) {
|
||||
return p.parseShowContinuousQueriesStatement()
|
||||
})
|
||||
show.Handle(DATABASES, func(p *Parser) (Statement, error) {
|
||||
return p.parseShowDatabasesStatement()
|
||||
})
|
||||
show.Handle(DIAGNOSTICS, func(p *Parser) (Statement, error) {
|
||||
return p.parseShowDiagnosticsStatement()
|
||||
})
|
||||
show.Group(FIELD).With(func(field *ParseTree) {
|
||||
field.Handle(KEY, func(p *Parser) (Statement, error) {
|
||||
return p.parseShowFieldKeyCardinalityStatement()
|
||||
})
|
||||
field.Handle(KEYS, func(p *Parser) (Statement, error) {
|
||||
return p.parseShowFieldKeysStatement()
|
||||
})
|
||||
})
|
||||
show.Group(GRANTS).Handle(FOR, func(p *Parser) (Statement, error) {
|
||||
return p.parseGrantsForUserStatement()
|
||||
})
|
||||
show.Group(MEASUREMENT).Handle(EXACT, func(p *Parser) (Statement, error) {
|
||||
return p.parseShowMeasurementCardinalityStatement(true)
|
||||
})
|
||||
show.Group(MEASUREMENT).Handle(CARDINALITY, func(p *Parser) (Statement, error) {
|
||||
return p.parseShowMeasurementCardinalityStatement(false)
|
||||
})
|
||||
show.Handle(MEASUREMENTS, func(p *Parser) (Statement, error) {
|
||||
return p.parseShowMeasurementsStatement()
|
||||
})
|
||||
show.Handle(QUERIES, func(p *Parser) (Statement, error) {
|
||||
return p.parseShowQueriesStatement()
|
||||
})
|
||||
show.Group(RETENTION).Handle(POLICIES, func(p *Parser) (Statement, error) {
|
||||
return p.parseShowRetentionPoliciesStatement()
|
||||
})
|
||||
show.Handle(SERIES, func(p *Parser) (Statement, error) {
|
||||
return p.parseShowSeriesStatement()
|
||||
})
|
||||
show.Group(SHARD).Handle(GROUPS, func(p *Parser) (Statement, error) {
|
||||
return p.parseShowShardGroupsStatement()
|
||||
})
|
||||
show.Handle(SHARDS, func(p *Parser) (Statement, error) {
|
||||
return p.parseShowShardsStatement()
|
||||
})
|
||||
show.Handle(STATS, func(p *Parser) (Statement, error) {
|
||||
return p.parseShowStatsStatement()
|
||||
})
|
||||
show.Handle(SUBSCRIPTIONS, func(p *Parser) (Statement, error) {
|
||||
return p.parseShowSubscriptionsStatement()
|
||||
})
|
||||
show.Group(TAG).With(func(tag *ParseTree) {
|
||||
tag.Handle(KEY, func(p *Parser) (Statement, error) {
|
||||
return p.parseShowTagKeyCardinalityStatement()
|
||||
})
|
||||
tag.Handle(KEYS, func(p *Parser) (Statement, error) {
|
||||
return p.parseShowTagKeysStatement()
|
||||
})
|
||||
tag.Handle(VALUES, func(p *Parser) (Statement, error) {
|
||||
return p.parseShowTagValuesStatement()
|
||||
})
|
||||
})
|
||||
show.Handle(USERS, func(p *Parser) (Statement, error) {
|
||||
return p.parseShowUsersStatement()
|
||||
})
|
||||
})
|
||||
Language.Group(CREATE).With(func(create *ParseTree) {
|
||||
create.Group(CONTINUOUS).Handle(QUERY, func(p *Parser) (Statement, error) {
|
||||
return p.parseCreateContinuousQueryStatement()
|
||||
})
|
||||
create.Handle(DATABASE, func(p *Parser) (Statement, error) {
|
||||
return p.parseCreateDatabaseStatement()
|
||||
})
|
||||
create.Handle(USER, func(p *Parser) (Statement, error) {
|
||||
return p.parseCreateUserStatement()
|
||||
})
|
||||
create.Group(RETENTION).Handle(POLICY, func(p *Parser) (Statement, error) {
|
||||
return p.parseCreateRetentionPolicyStatement()
|
||||
})
|
||||
create.Handle(SUBSCRIPTION, func(p *Parser) (Statement, error) {
|
||||
return p.parseCreateSubscriptionStatement()
|
||||
})
|
||||
})
|
||||
Language.Group(DROP).With(func(drop *ParseTree) {
|
||||
drop.Group(CONTINUOUS).Handle(QUERY, func(p *Parser) (Statement, error) {
|
||||
return p.parseDropContinuousQueryStatement()
|
||||
})
|
||||
drop.Handle(DATABASE, func(p *Parser) (Statement, error) {
|
||||
return p.parseDropDatabaseStatement()
|
||||
})
|
||||
drop.Handle(MEASUREMENT, func(p *Parser) (Statement, error) {
|
||||
return p.parseDropMeasurementStatement()
|
||||
})
|
||||
drop.Group(RETENTION).Handle(POLICY, func(p *Parser) (Statement, error) {
|
||||
return p.parseDropRetentionPolicyStatement()
|
||||
})
|
||||
drop.Handle(SERIES, func(p *Parser) (Statement, error) {
|
||||
return p.parseDropSeriesStatement()
|
||||
})
|
||||
drop.Handle(SHARD, func(p *Parser) (Statement, error) {
|
||||
return p.parseDropShardStatement()
|
||||
})
|
||||
drop.Handle(SUBSCRIPTION, func(p *Parser) (Statement, error) {
|
||||
return p.parseDropSubscriptionStatement()
|
||||
})
|
||||
drop.Handle(USER, func(p *Parser) (Statement, error) {
|
||||
return p.parseDropUserStatement()
|
||||
})
|
||||
})
|
||||
Language.Handle(EXPLAIN, func(p *Parser) (Statement, error) {
|
||||
return p.parseExplainStatement()
|
||||
})
|
||||
Language.Handle(GRANT, func(p *Parser) (Statement, error) {
|
||||
return p.parseGrantStatement()
|
||||
})
|
||||
Language.Handle(REVOKE, func(p *Parser) (Statement, error) {
|
||||
return p.parseRevokeStatement()
|
||||
})
|
||||
Language.Group(ALTER, RETENTION).Handle(POLICY, func(p *Parser) (Statement, error) {
|
||||
return p.parseAlterRetentionPolicyStatement()
|
||||
})
|
||||
Language.Group(SET, PASSWORD).Handle(FOR, func(p *Parser) (Statement, error) {
|
||||
return p.parseSetPasswordUserStatement()
|
||||
})
|
||||
Language.Group(KILL).Handle(QUERY, func(p *Parser) (Statement, error) {
|
||||
return p.parseKillQueryStatement()
|
||||
})
|
||||
}
|
||||
+3163
File diff suppressed because it is too large
Load Diff
+47
@@ -0,0 +1,47 @@
|
||||
package influxql
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
var (
|
||||
sanitizeSetPassword = regexp.MustCompile(`(?i)password\s+for[^=]*=\s+(["']?[^\s"]+["']?)`)
|
||||
|
||||
sanitizeCreatePassword = regexp.MustCompile(`(?i)with\s+password\s+(["']?[^\s"]+["']?)`)
|
||||
)
|
||||
|
||||
// Sanitize attempts to sanitize passwords out of a raw query.
|
||||
// It looks for patterns that may be related to the SET PASSWORD and CREATE USER
|
||||
// statements and will redact the password that should be there. It will attempt
|
||||
// to redact information from common invalid queries too, but it's not guaranteed
|
||||
// to succeed on improper queries.
|
||||
//
|
||||
// This function works on the raw query and attempts to retain the original input
|
||||
// as much as possible.
|
||||
func Sanitize(query string) string {
|
||||
if matches := sanitizeSetPassword.FindAllStringSubmatchIndex(query, -1); matches != nil {
|
||||
var buf bytes.Buffer
|
||||
i := 0
|
||||
for _, match := range matches {
|
||||
buf.WriteString(query[i:match[2]])
|
||||
buf.WriteString("[REDACTED]")
|
||||
i = match[3]
|
||||
}
|
||||
buf.WriteString(query[i:])
|
||||
query = buf.String()
|
||||
}
|
||||
|
||||
if matches := sanitizeCreatePassword.FindAllStringSubmatchIndex(query, -1); matches != nil {
|
||||
var buf bytes.Buffer
|
||||
i := 0
|
||||
for _, match := range matches {
|
||||
buf.WriteString(query[i:match[2]])
|
||||
buf.WriteString("[REDACTED]")
|
||||
i = match[3]
|
||||
}
|
||||
buf.WriteString(query[i:])
|
||||
query = buf.String()
|
||||
}
|
||||
return query
|
||||
}
|
||||
+617
@@ -0,0 +1,617 @@
|
||||
package influxql
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
// Scanner represents a lexical scanner for InfluxQL.
|
||||
type Scanner struct {
|
||||
r *reader
|
||||
}
|
||||
|
||||
// NewScanner returns a new instance of Scanner.
|
||||
func NewScanner(r io.Reader) *Scanner {
|
||||
return &Scanner{r: &reader{r: bufio.NewReader(r)}}
|
||||
}
|
||||
|
||||
// Scan returns the next token and position from the underlying reader.
|
||||
// Also returns the literal text read for strings, numbers, and duration tokens
|
||||
// since these token types can have different literal representations.
|
||||
func (s *Scanner) Scan() (tok Token, pos Pos, lit string) {
|
||||
// Read next code point.
|
||||
ch0, pos := s.r.read()
|
||||
|
||||
// If we see whitespace then consume all contiguous whitespace.
|
||||
// If we see a letter, or certain acceptable special characters, then consume
|
||||
// as an ident or reserved word.
|
||||
if isWhitespace(ch0) {
|
||||
return s.scanWhitespace()
|
||||
} else if isLetter(ch0) || ch0 == '_' {
|
||||
s.r.unread()
|
||||
return s.scanIdent(true)
|
||||
} else if isDigit(ch0) {
|
||||
return s.scanNumber()
|
||||
}
|
||||
|
||||
// Otherwise parse individual characters.
|
||||
switch ch0 {
|
||||
case eof:
|
||||
return EOF, pos, ""
|
||||
case '"':
|
||||
s.r.unread()
|
||||
return s.scanIdent(true)
|
||||
case '\'':
|
||||
return s.scanString()
|
||||
case '.':
|
||||
ch1, _ := s.r.read()
|
||||
s.r.unread()
|
||||
if isDigit(ch1) {
|
||||
return s.scanNumber()
|
||||
}
|
||||
return DOT, pos, ""
|
||||
case '$':
|
||||
tok, _, lit = s.scanIdent(false)
|
||||
if tok != IDENT {
|
||||
return tok, pos, "$" + lit
|
||||
}
|
||||
return BOUNDPARAM, pos, "$" + lit
|
||||
case '+':
|
||||
return ADD, pos, ""
|
||||
case '-':
|
||||
ch1, _ := s.r.read()
|
||||
if ch1 == '-' {
|
||||
s.skipUntilNewline()
|
||||
return COMMENT, pos, ""
|
||||
}
|
||||
s.r.unread()
|
||||
return SUB, pos, ""
|
||||
case '*':
|
||||
return MUL, pos, ""
|
||||
case '/':
|
||||
ch1, _ := s.r.read()
|
||||
if ch1 == '*' {
|
||||
if err := s.skipUntilEndComment(); err != nil {
|
||||
return ILLEGAL, pos, ""
|
||||
}
|
||||
return COMMENT, pos, ""
|
||||
} else {
|
||||
s.r.unread()
|
||||
}
|
||||
return DIV, pos, ""
|
||||
case '%':
|
||||
return MOD, pos, ""
|
||||
case '&':
|
||||
return BITWISE_AND, pos, ""
|
||||
case '|':
|
||||
return BITWISE_OR, pos, ""
|
||||
case '^':
|
||||
return BITWISE_XOR, pos, ""
|
||||
case '=':
|
||||
if ch1, _ := s.r.read(); ch1 == '~' {
|
||||
return EQREGEX, pos, ""
|
||||
}
|
||||
s.r.unread()
|
||||
return EQ, pos, ""
|
||||
case '!':
|
||||
if ch1, _ := s.r.read(); ch1 == '=' {
|
||||
return NEQ, pos, ""
|
||||
} else if ch1 == '~' {
|
||||
return NEQREGEX, pos, ""
|
||||
}
|
||||
s.r.unread()
|
||||
case '>':
|
||||
if ch1, _ := s.r.read(); ch1 == '=' {
|
||||
return GTE, pos, ""
|
||||
}
|
||||
s.r.unread()
|
||||
return GT, pos, ""
|
||||
case '<':
|
||||
if ch1, _ := s.r.read(); ch1 == '=' {
|
||||
return LTE, pos, ""
|
||||
} else if ch1 == '>' {
|
||||
return NEQ, pos, ""
|
||||
}
|
||||
s.r.unread()
|
||||
return LT, pos, ""
|
||||
case '(':
|
||||
return LPAREN, pos, ""
|
||||
case ')':
|
||||
return RPAREN, pos, ""
|
||||
case ',':
|
||||
return COMMA, pos, ""
|
||||
case ';':
|
||||
return SEMICOLON, pos, ""
|
||||
case ':':
|
||||
if ch1, _ := s.r.read(); ch1 == ':' {
|
||||
return DOUBLECOLON, pos, ""
|
||||
}
|
||||
s.r.unread()
|
||||
return COLON, pos, ""
|
||||
}
|
||||
|
||||
return ILLEGAL, pos, string(ch0)
|
||||
}
|
||||
|
||||
// scanWhitespace consumes the current rune and all contiguous whitespace.
|
||||
func (s *Scanner) scanWhitespace() (tok Token, pos Pos, lit string) {
|
||||
// Create a buffer and read the current character into it.
|
||||
var buf bytes.Buffer
|
||||
ch, pos := s.r.curr()
|
||||
_, _ = buf.WriteRune(ch)
|
||||
|
||||
// Read every subsequent whitespace character into the buffer.
|
||||
// Non-whitespace characters and EOF will cause the loop to exit.
|
||||
for {
|
||||
ch, _ = s.r.read()
|
||||
if ch == eof {
|
||||
break
|
||||
} else if !isWhitespace(ch) {
|
||||
s.r.unread()
|
||||
break
|
||||
} else {
|
||||
_, _ = buf.WriteRune(ch)
|
||||
}
|
||||
}
|
||||
|
||||
return WS, pos, buf.String()
|
||||
}
|
||||
|
||||
// skipUntilNewline skips characters until it reaches a newline.
|
||||
func (s *Scanner) skipUntilNewline() {
|
||||
for {
|
||||
if ch, _ := s.r.read(); ch == '\n' || ch == eof {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// skipUntilEndComment skips characters until it reaches a '*/' symbol.
|
||||
func (s *Scanner) skipUntilEndComment() error {
|
||||
for {
|
||||
if ch1, _ := s.r.read(); ch1 == '*' {
|
||||
// We might be at the end.
|
||||
star:
|
||||
ch2, _ := s.r.read()
|
||||
if ch2 == '/' {
|
||||
return nil
|
||||
} else if ch2 == '*' {
|
||||
// We are back in the state machine since we see a star.
|
||||
goto star
|
||||
} else if ch2 == eof {
|
||||
return io.EOF
|
||||
}
|
||||
} else if ch1 == eof {
|
||||
return io.EOF
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Scanner) scanIdent(lookup bool) (tok Token, pos Pos, lit string) {
|
||||
// Save the starting position of the identifier.
|
||||
_, pos = s.r.read()
|
||||
s.r.unread()
|
||||
|
||||
var buf bytes.Buffer
|
||||
for {
|
||||
if ch, _ := s.r.read(); ch == eof {
|
||||
break
|
||||
} else if ch == '"' {
|
||||
tok0, pos0, lit0 := s.scanString()
|
||||
if tok0 == BADSTRING || tok0 == BADESCAPE {
|
||||
return tok0, pos0, lit0
|
||||
}
|
||||
return IDENT, pos, lit0
|
||||
} else if isIdentChar(ch) {
|
||||
s.r.unread()
|
||||
buf.WriteString(ScanBareIdent(s.r))
|
||||
} else {
|
||||
s.r.unread()
|
||||
break
|
||||
}
|
||||
}
|
||||
lit = buf.String()
|
||||
|
||||
// If the literal matches a keyword then return that keyword.
|
||||
if lookup {
|
||||
if tok = Lookup(lit); tok != IDENT {
|
||||
return tok, pos, ""
|
||||
}
|
||||
}
|
||||
return IDENT, pos, lit
|
||||
}
|
||||
|
||||
// scanString consumes a contiguous string of non-quote characters.
|
||||
// Quote characters can be consumed if they're first escaped with a backslash.
|
||||
func (s *Scanner) scanString() (tok Token, pos Pos, lit string) {
|
||||
s.r.unread()
|
||||
_, pos = s.r.curr()
|
||||
|
||||
var err error
|
||||
lit, err = ScanString(s.r)
|
||||
if err == errBadString {
|
||||
return BADSTRING, pos, lit
|
||||
} else if err == errBadEscape {
|
||||
_, pos = s.r.curr()
|
||||
return BADESCAPE, pos, lit
|
||||
}
|
||||
return STRING, pos, lit
|
||||
}
|
||||
|
||||
// ScanRegex consumes a token to find escapes
|
||||
func (s *Scanner) ScanRegex() (tok Token, pos Pos, lit string) {
|
||||
_, pos = s.r.curr()
|
||||
|
||||
// Start & end sentinels.
|
||||
start, end := '/', '/'
|
||||
// Valid escape chars.
|
||||
escapes := map[rune]rune{'/': '/'}
|
||||
|
||||
b, err := ScanDelimited(s.r, start, end, escapes, true)
|
||||
|
||||
if err == errBadEscape {
|
||||
_, pos = s.r.curr()
|
||||
return BADESCAPE, pos, lit
|
||||
} else if err != nil {
|
||||
return BADREGEX, pos, lit
|
||||
}
|
||||
return REGEX, pos, string(b)
|
||||
}
|
||||
|
||||
// scanNumber consumes anything that looks like the start of a number.
|
||||
func (s *Scanner) scanNumber() (tok Token, pos Pos, lit string) {
|
||||
var buf bytes.Buffer
|
||||
|
||||
// Check if the initial rune is a ".".
|
||||
ch, pos := s.r.curr()
|
||||
if ch == '.' {
|
||||
// Peek and see if the next rune is a digit.
|
||||
ch1, _ := s.r.read()
|
||||
s.r.unread()
|
||||
if !isDigit(ch1) {
|
||||
return ILLEGAL, pos, "."
|
||||
}
|
||||
|
||||
// Unread the full stop so we can read it later.
|
||||
s.r.unread()
|
||||
} else {
|
||||
s.r.unread()
|
||||
}
|
||||
|
||||
// Read as many digits as possible.
|
||||
_, _ = buf.WriteString(s.scanDigits())
|
||||
|
||||
// If next code points are a full stop and digit then consume them.
|
||||
isDecimal := false
|
||||
if ch0, _ := s.r.read(); ch0 == '.' {
|
||||
isDecimal = true
|
||||
if ch1, _ := s.r.read(); isDigit(ch1) {
|
||||
_, _ = buf.WriteRune(ch0)
|
||||
_, _ = buf.WriteRune(ch1)
|
||||
_, _ = buf.WriteString(s.scanDigits())
|
||||
} else {
|
||||
s.r.unread()
|
||||
}
|
||||
} else {
|
||||
s.r.unread()
|
||||
}
|
||||
|
||||
// Read as a duration or integer if it doesn't have a fractional part.
|
||||
if !isDecimal {
|
||||
// If the next rune is a letter then this is a duration token.
|
||||
if ch0, _ := s.r.read(); isLetter(ch0) || ch0 == 'µ' {
|
||||
_, _ = buf.WriteRune(ch0)
|
||||
for {
|
||||
ch1, _ := s.r.read()
|
||||
if !isLetter(ch1) && ch1 != 'µ' {
|
||||
s.r.unread()
|
||||
break
|
||||
}
|
||||
_, _ = buf.WriteRune(ch1)
|
||||
}
|
||||
|
||||
// Continue reading digits and letters as part of this token.
|
||||
for {
|
||||
if ch0, _ := s.r.read(); isLetter(ch0) || ch0 == 'µ' || isDigit(ch0) {
|
||||
_, _ = buf.WriteRune(ch0)
|
||||
} else {
|
||||
s.r.unread()
|
||||
break
|
||||
}
|
||||
}
|
||||
return DURATIONVAL, pos, buf.String()
|
||||
} else {
|
||||
s.r.unread()
|
||||
return INTEGER, pos, buf.String()
|
||||
}
|
||||
}
|
||||
return NUMBER, pos, buf.String()
|
||||
}
|
||||
|
||||
// scanDigits consumes a contiguous series of digits.
|
||||
func (s *Scanner) scanDigits() string {
|
||||
var buf bytes.Buffer
|
||||
for {
|
||||
ch, _ := s.r.read()
|
||||
if !isDigit(ch) {
|
||||
s.r.unread()
|
||||
break
|
||||
}
|
||||
_, _ = buf.WriteRune(ch)
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// isWhitespace returns true if the rune is a space, tab, or newline.
|
||||
func isWhitespace(ch rune) bool { return ch == ' ' || ch == '\t' || ch == '\n' }
|
||||
|
||||
// isLetter returns true if the rune is a letter.
|
||||
func isLetter(ch rune) bool { return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') }
|
||||
|
||||
// isDigit returns true if the rune is a digit.
|
||||
func isDigit(ch rune) bool { return (ch >= '0' && ch <= '9') }
|
||||
|
||||
// isIdentChar returns true if the rune can be used in an unquoted identifier.
|
||||
func isIdentChar(ch rune) bool { return isLetter(ch) || isDigit(ch) || ch == '_' }
|
||||
|
||||
// isIdentFirstChar returns true if the rune can be used as the first char in an unquoted identifer.
|
||||
func isIdentFirstChar(ch rune) bool { return isLetter(ch) || ch == '_' }
|
||||
|
||||
// bufScanner represents a wrapper for scanner to add a buffer.
|
||||
// It provides a fixed-length circular buffer that can be unread.
|
||||
type bufScanner struct {
|
||||
s *Scanner
|
||||
i int // buffer index
|
||||
n int // buffer size
|
||||
buf [3]struct {
|
||||
tok Token
|
||||
pos Pos
|
||||
lit string
|
||||
}
|
||||
}
|
||||
|
||||
// newBufScanner returns a new buffered scanner for a reader.
|
||||
func newBufScanner(r io.Reader) *bufScanner {
|
||||
return &bufScanner{s: NewScanner(r)}
|
||||
}
|
||||
|
||||
// Scan reads the next token from the scanner.
|
||||
func (s *bufScanner) Scan() (tok Token, pos Pos, lit string) {
|
||||
return s.scanFunc(s.s.Scan)
|
||||
}
|
||||
|
||||
// ScanRegex reads a regex token from the scanner.
|
||||
func (s *bufScanner) ScanRegex() (tok Token, pos Pos, lit string) {
|
||||
return s.scanFunc(s.s.ScanRegex)
|
||||
}
|
||||
|
||||
// scanFunc uses the provided function to scan the next token.
|
||||
func (s *bufScanner) scanFunc(scan func() (Token, Pos, string)) (tok Token, pos Pos, lit string) {
|
||||
// If we have unread tokens then read them off the buffer first.
|
||||
if s.n > 0 {
|
||||
s.n--
|
||||
return s.curr()
|
||||
}
|
||||
|
||||
// Move buffer position forward and save the token.
|
||||
s.i = (s.i + 1) % len(s.buf)
|
||||
buf := &s.buf[s.i]
|
||||
buf.tok, buf.pos, buf.lit = scan()
|
||||
|
||||
return s.curr()
|
||||
}
|
||||
|
||||
// Unscan pushes the previously token back onto the buffer.
|
||||
func (s *bufScanner) Unscan() { s.n++ }
|
||||
|
||||
// curr returns the last read token.
|
||||
func (s *bufScanner) curr() (tok Token, pos Pos, lit string) {
|
||||
buf := &s.buf[(s.i-s.n+len(s.buf))%len(s.buf)]
|
||||
return buf.tok, buf.pos, buf.lit
|
||||
}
|
||||
|
||||
// reader represents a buffered rune reader used by the scanner.
|
||||
// It provides a fixed-length circular buffer that can be unread.
|
||||
type reader struct {
|
||||
r io.RuneScanner
|
||||
i int // buffer index
|
||||
n int // buffer char count
|
||||
pos Pos // last read rune position
|
||||
buf [3]struct {
|
||||
ch rune
|
||||
pos Pos
|
||||
}
|
||||
eof bool // true if reader has ever seen eof.
|
||||
}
|
||||
|
||||
// ReadRune reads the next rune from the reader.
|
||||
// This is a wrapper function to implement the io.RuneReader interface.
|
||||
// Note that this function does not return size.
|
||||
func (r *reader) ReadRune() (ch rune, size int, err error) {
|
||||
ch, _ = r.read()
|
||||
if ch == eof {
|
||||
err = io.EOF
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// UnreadRune pushes the previously read rune back onto the buffer.
|
||||
// This is a wrapper function to implement the io.RuneScanner interface.
|
||||
func (r *reader) UnreadRune() error {
|
||||
r.unread()
|
||||
return nil
|
||||
}
|
||||
|
||||
// read reads the next rune from the reader.
|
||||
func (r *reader) read() (ch rune, pos Pos) {
|
||||
// If we have unread characters then read them off the buffer first.
|
||||
if r.n > 0 {
|
||||
r.n--
|
||||
return r.curr()
|
||||
}
|
||||
|
||||
// Read next rune from underlying reader.
|
||||
// Any error (including io.EOF) should return as EOF.
|
||||
ch, _, err := r.r.ReadRune()
|
||||
if err != nil {
|
||||
ch = eof
|
||||
} else if ch == '\r' {
|
||||
if ch, _, err := r.r.ReadRune(); err != nil {
|
||||
// nop
|
||||
} else if ch != '\n' {
|
||||
_ = r.r.UnreadRune()
|
||||
}
|
||||
ch = '\n'
|
||||
}
|
||||
|
||||
// Save character and position to the buffer.
|
||||
r.i = (r.i + 1) % len(r.buf)
|
||||
buf := &r.buf[r.i]
|
||||
buf.ch, buf.pos = ch, r.pos
|
||||
|
||||
// Update position.
|
||||
// Only count EOF once.
|
||||
if ch == '\n' {
|
||||
r.pos.Line++
|
||||
r.pos.Char = 0
|
||||
} else if !r.eof {
|
||||
r.pos.Char++
|
||||
}
|
||||
|
||||
// Mark the reader as EOF.
|
||||
// This is used so we don't double count EOF characters.
|
||||
if ch == eof {
|
||||
r.eof = true
|
||||
}
|
||||
|
||||
return r.curr()
|
||||
}
|
||||
|
||||
// unread pushes the previously read rune back onto the buffer.
|
||||
func (r *reader) unread() {
|
||||
r.n++
|
||||
}
|
||||
|
||||
// curr returns the last read character and position.
|
||||
func (r *reader) curr() (ch rune, pos Pos) {
|
||||
i := (r.i - r.n + len(r.buf)) % len(r.buf)
|
||||
buf := &r.buf[i]
|
||||
return buf.ch, buf.pos
|
||||
}
|
||||
|
||||
// eof is a marker code point to signify that the reader can't read any more.
|
||||
const eof = rune(0)
|
||||
|
||||
// ScanDelimited reads a delimited set of runes
|
||||
func ScanDelimited(r io.RuneScanner, start, end rune, escapes map[rune]rune, escapesPassThru bool) ([]byte, error) {
|
||||
// Scan start delimiter.
|
||||
if ch, _, err := r.ReadRune(); err != nil {
|
||||
return nil, err
|
||||
} else if ch != start {
|
||||
return nil, fmt.Errorf("expected %s; found %s", string(start), string(ch))
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
for {
|
||||
ch0, _, err := r.ReadRune()
|
||||
if ch0 == end {
|
||||
return buf.Bytes(), nil
|
||||
} else if err != nil {
|
||||
return buf.Bytes(), err
|
||||
} else if ch0 == '\n' {
|
||||
return nil, errors.New("delimited text contains new line")
|
||||
} else if ch0 == '\\' {
|
||||
// If the next character is an escape then write the escaped char.
|
||||
// If it's not a valid escape then return an error.
|
||||
ch1, _, err := r.ReadRune()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c, ok := escapes[ch1]
|
||||
if !ok {
|
||||
if escapesPassThru {
|
||||
// Unread ch1 (char after the \)
|
||||
_ = r.UnreadRune()
|
||||
// Write ch0 (\) to the output buffer.
|
||||
_, _ = buf.WriteRune(ch0)
|
||||
continue
|
||||
} else {
|
||||
buf.Reset()
|
||||
_, _ = buf.WriteRune(ch0)
|
||||
_, _ = buf.WriteRune(ch1)
|
||||
return buf.Bytes(), errBadEscape
|
||||
}
|
||||
}
|
||||
|
||||
_, _ = buf.WriteRune(c)
|
||||
} else {
|
||||
_, _ = buf.WriteRune(ch0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ScanString reads a quoted string from a rune reader.
|
||||
func ScanString(r io.RuneScanner) (string, error) {
|
||||
ending, _, err := r.ReadRune()
|
||||
if err != nil {
|
||||
return "", errBadString
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
for {
|
||||
ch0, _, err := r.ReadRune()
|
||||
if ch0 == ending {
|
||||
return buf.String(), nil
|
||||
} else if err != nil || ch0 == '\n' {
|
||||
return buf.String(), errBadString
|
||||
} else if ch0 == '\\' {
|
||||
// If the next character is an escape then write the escaped char.
|
||||
// If it's not a valid escape then return an error.
|
||||
ch1, _, _ := r.ReadRune()
|
||||
if ch1 == 'n' {
|
||||
_, _ = buf.WriteRune('\n')
|
||||
} else if ch1 == '\\' {
|
||||
_, _ = buf.WriteRune('\\')
|
||||
} else if ch1 == '"' {
|
||||
_, _ = buf.WriteRune('"')
|
||||
} else if ch1 == '\'' {
|
||||
_, _ = buf.WriteRune('\'')
|
||||
} else {
|
||||
return string(ch0) + string(ch1), errBadEscape
|
||||
}
|
||||
} else {
|
||||
_, _ = buf.WriteRune(ch0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var errBadString = errors.New("bad string")
|
||||
var errBadEscape = errors.New("bad escape")
|
||||
|
||||
// ScanBareIdent reads bare identifier from a rune reader.
|
||||
func ScanBareIdent(r io.RuneScanner) string {
|
||||
// Read every ident character into the buffer.
|
||||
// Non-ident characters and EOF will cause the loop to exit.
|
||||
var buf bytes.Buffer
|
||||
for {
|
||||
ch, _, err := r.ReadRune()
|
||||
if err != nil {
|
||||
break
|
||||
} else if !isIdentChar(ch) {
|
||||
r.UnreadRune()
|
||||
break
|
||||
} else {
|
||||
_, _ = buf.WriteRune(ch)
|
||||
}
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// IsRegexOp returns true if the operator accepts a regex operand.
|
||||
func IsRegexOp(t Token) bool {
|
||||
return (t == EQREGEX || t == NEQREGEX)
|
||||
}
|
||||
+332
@@ -0,0 +1,332 @@
|
||||
package influxql
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Token is a lexical token of the InfluxQL language.
|
||||
type Token int
|
||||
|
||||
// These are a comprehensive list of InfluxQL language tokens.
|
||||
const (
|
||||
// ILLEGAL Token, EOF, WS are Special InfluxQL tokens.
|
||||
ILLEGAL Token = iota
|
||||
EOF
|
||||
WS
|
||||
COMMENT
|
||||
|
||||
literalBeg
|
||||
// IDENT and the following are InfluxQL literal tokens.
|
||||
IDENT // main
|
||||
BOUNDPARAM // $param
|
||||
NUMBER // 12345.67
|
||||
INTEGER // 12345
|
||||
DURATIONVAL // 13h
|
||||
STRING // "abc"
|
||||
BADSTRING // "abc
|
||||
BADESCAPE // \q
|
||||
TRUE // true
|
||||
FALSE // false
|
||||
REGEX // Regular expressions
|
||||
BADREGEX // `.*
|
||||
literalEnd
|
||||
|
||||
operatorBeg
|
||||
// ADD and the following are InfluxQL Operators
|
||||
ADD // +
|
||||
SUB // -
|
||||
MUL // *
|
||||
DIV // /
|
||||
MOD // %
|
||||
BITWISE_AND // &
|
||||
BITWISE_OR // |
|
||||
BITWISE_XOR // ^
|
||||
|
||||
AND // AND
|
||||
OR // OR
|
||||
|
||||
EQ // =
|
||||
NEQ // !=
|
||||
EQREGEX // =~
|
||||
NEQREGEX // !~
|
||||
LT // <
|
||||
LTE // <=
|
||||
GT // >
|
||||
GTE // >=
|
||||
operatorEnd
|
||||
|
||||
LPAREN // (
|
||||
RPAREN // )
|
||||
COMMA // ,
|
||||
COLON // :
|
||||
DOUBLECOLON // ::
|
||||
SEMICOLON // ;
|
||||
DOT // .
|
||||
|
||||
keywordBeg
|
||||
// ALL and the following are InfluxQL Keywords
|
||||
ALL
|
||||
ALTER
|
||||
ANALYZE
|
||||
ANY
|
||||
AS
|
||||
ASC
|
||||
BEGIN
|
||||
BY
|
||||
CARDINALITY
|
||||
CREATE
|
||||
CONTINUOUS
|
||||
DATABASE
|
||||
DATABASES
|
||||
DEFAULT
|
||||
DELETE
|
||||
DESC
|
||||
DESTINATIONS
|
||||
DIAGNOSTICS
|
||||
DISTINCT
|
||||
DROP
|
||||
DURATION
|
||||
END
|
||||
EVERY
|
||||
EXACT
|
||||
EXPLAIN
|
||||
FIELD
|
||||
FOR
|
||||
FROM
|
||||
GRANT
|
||||
GRANTS
|
||||
GROUP
|
||||
GROUPS
|
||||
IN
|
||||
INF
|
||||
INSERT
|
||||
INTO
|
||||
KEY
|
||||
KEYS
|
||||
KILL
|
||||
LIMIT
|
||||
MEASUREMENT
|
||||
MEASUREMENTS
|
||||
NAME
|
||||
OFFSET
|
||||
ON
|
||||
ORDER
|
||||
PASSWORD
|
||||
POLICY
|
||||
POLICIES
|
||||
PRIVILEGES
|
||||
QUERIES
|
||||
QUERY
|
||||
READ
|
||||
REPLICATION
|
||||
RESAMPLE
|
||||
RETENTION
|
||||
REVOKE
|
||||
SELECT
|
||||
SERIES
|
||||
SET
|
||||
SHOW
|
||||
SHARD
|
||||
SHARDS
|
||||
SLIMIT
|
||||
SOFFSET
|
||||
STATS
|
||||
SUBSCRIPTION
|
||||
SUBSCRIPTIONS
|
||||
TAG
|
||||
TO
|
||||
USER
|
||||
USERS
|
||||
VALUES
|
||||
WHERE
|
||||
WITH
|
||||
WRITE
|
||||
keywordEnd
|
||||
)
|
||||
|
||||
var tokens = [...]string{
|
||||
ILLEGAL: "ILLEGAL",
|
||||
EOF: "EOF",
|
||||
WS: "WS",
|
||||
|
||||
IDENT: "IDENT",
|
||||
NUMBER: "NUMBER",
|
||||
DURATIONVAL: "DURATIONVAL",
|
||||
STRING: "STRING",
|
||||
BADSTRING: "BADSTRING",
|
||||
BADESCAPE: "BADESCAPE",
|
||||
TRUE: "TRUE",
|
||||
FALSE: "FALSE",
|
||||
REGEX: "REGEX",
|
||||
|
||||
ADD: "+",
|
||||
SUB: "-",
|
||||
MUL: "*",
|
||||
DIV: "/",
|
||||
MOD: "%",
|
||||
BITWISE_AND: "&",
|
||||
BITWISE_OR: "|",
|
||||
BITWISE_XOR: "^",
|
||||
|
||||
AND: "AND",
|
||||
OR: "OR",
|
||||
|
||||
EQ: "=",
|
||||
NEQ: "!=",
|
||||
EQREGEX: "=~",
|
||||
NEQREGEX: "!~",
|
||||
LT: "<",
|
||||
LTE: "<=",
|
||||
GT: ">",
|
||||
GTE: ">=",
|
||||
|
||||
LPAREN: "(",
|
||||
RPAREN: ")",
|
||||
COMMA: ",",
|
||||
COLON: ":",
|
||||
DOUBLECOLON: "::",
|
||||
SEMICOLON: ";",
|
||||
DOT: ".",
|
||||
|
||||
ALL: "ALL",
|
||||
ALTER: "ALTER",
|
||||
ANALYZE: "ANALYZE",
|
||||
ANY: "ANY",
|
||||
AS: "AS",
|
||||
ASC: "ASC",
|
||||
BEGIN: "BEGIN",
|
||||
BY: "BY",
|
||||
CARDINALITY: "CARDINALITY",
|
||||
CREATE: "CREATE",
|
||||
CONTINUOUS: "CONTINUOUS",
|
||||
DATABASE: "DATABASE",
|
||||
DATABASES: "DATABASES",
|
||||
DEFAULT: "DEFAULT",
|
||||
DELETE: "DELETE",
|
||||
DESC: "DESC",
|
||||
DESTINATIONS: "DESTINATIONS",
|
||||
DIAGNOSTICS: "DIAGNOSTICS",
|
||||
DISTINCT: "DISTINCT",
|
||||
DROP: "DROP",
|
||||
DURATION: "DURATION",
|
||||
END: "END",
|
||||
EVERY: "EVERY",
|
||||
EXACT: "EXACT",
|
||||
EXPLAIN: "EXPLAIN",
|
||||
FIELD: "FIELD",
|
||||
FOR: "FOR",
|
||||
FROM: "FROM",
|
||||
GRANT: "GRANT",
|
||||
GRANTS: "GRANTS",
|
||||
GROUP: "GROUP",
|
||||
GROUPS: "GROUPS",
|
||||
IN: "IN",
|
||||
INF: "INF",
|
||||
INSERT: "INSERT",
|
||||
INTO: "INTO",
|
||||
KEY: "KEY",
|
||||
KEYS: "KEYS",
|
||||
KILL: "KILL",
|
||||
LIMIT: "LIMIT",
|
||||
MEASUREMENT: "MEASUREMENT",
|
||||
MEASUREMENTS: "MEASUREMENTS",
|
||||
NAME: "NAME",
|
||||
OFFSET: "OFFSET",
|
||||
ON: "ON",
|
||||
ORDER: "ORDER",
|
||||
PASSWORD: "PASSWORD",
|
||||
POLICY: "POLICY",
|
||||
POLICIES: "POLICIES",
|
||||
PRIVILEGES: "PRIVILEGES",
|
||||
QUERIES: "QUERIES",
|
||||
QUERY: "QUERY",
|
||||
READ: "READ",
|
||||
REPLICATION: "REPLICATION",
|
||||
RESAMPLE: "RESAMPLE",
|
||||
RETENTION: "RETENTION",
|
||||
REVOKE: "REVOKE",
|
||||
SELECT: "SELECT",
|
||||
SERIES: "SERIES",
|
||||
SET: "SET",
|
||||
SHOW: "SHOW",
|
||||
SHARD: "SHARD",
|
||||
SHARDS: "SHARDS",
|
||||
SLIMIT: "SLIMIT",
|
||||
SOFFSET: "SOFFSET",
|
||||
STATS: "STATS",
|
||||
SUBSCRIPTION: "SUBSCRIPTION",
|
||||
SUBSCRIPTIONS: "SUBSCRIPTIONS",
|
||||
TAG: "TAG",
|
||||
TO: "TO",
|
||||
USER: "USER",
|
||||
USERS: "USERS",
|
||||
VALUES: "VALUES",
|
||||
WHERE: "WHERE",
|
||||
WITH: "WITH",
|
||||
WRITE: "WRITE",
|
||||
}
|
||||
|
||||
var keywords map[string]Token
|
||||
|
||||
func init() {
|
||||
keywords = make(map[string]Token)
|
||||
for tok := keywordBeg + 1; tok < keywordEnd; tok++ {
|
||||
keywords[strings.ToLower(tokens[tok])] = tok
|
||||
}
|
||||
for _, tok := range []Token{AND, OR} {
|
||||
keywords[strings.ToLower(tokens[tok])] = tok
|
||||
}
|
||||
keywords["true"] = TRUE
|
||||
keywords["false"] = FALSE
|
||||
}
|
||||
|
||||
// String returns the string representation of the token.
|
||||
func (tok Token) String() string {
|
||||
if tok >= 0 && tok < Token(len(tokens)) {
|
||||
return tokens[tok]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Precedence returns the operator precedence of the binary operator token.
|
||||
func (tok Token) Precedence() int {
|
||||
switch tok {
|
||||
case OR:
|
||||
return 1
|
||||
case AND:
|
||||
return 2
|
||||
case EQ, NEQ, EQREGEX, NEQREGEX, LT, LTE, GT, GTE:
|
||||
return 3
|
||||
case ADD, SUB, BITWISE_OR, BITWISE_XOR:
|
||||
return 4
|
||||
case MUL, DIV, MOD, BITWISE_AND:
|
||||
return 5
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// isOperator returns true for operator tokens.
|
||||
func (tok Token) isOperator() bool { return tok > operatorBeg && tok < operatorEnd }
|
||||
|
||||
// tokstr returns a literal if provided, otherwise returns the token string.
|
||||
func tokstr(tok Token, lit string) string {
|
||||
if lit != "" {
|
||||
return lit
|
||||
}
|
||||
return tok.String()
|
||||
}
|
||||
|
||||
// Lookup returns the token associated with a given string.
|
||||
func Lookup(ident string) Token {
|
||||
if tok, ok := keywords[strings.ToLower(ident)]; ok {
|
||||
return tok
|
||||
}
|
||||
return IDENT
|
||||
}
|
||||
|
||||
// Pos specifies the line and character position of a token.
|
||||
// The Char and Line are both zero-based indexes.
|
||||
type Pos struct {
|
||||
Line int
|
||||
Char int
|
||||
}
|
||||
+201
@@ -0,0 +1,201 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
# PromQL Module
|
||||
|
||||
[](http://godoc.org/github.com/influxdata/promql)
|
||||
|
||||
The PromQL module in this package is a pruned version of the native Prometheus [promql](https://github.com/prometheus/prometheus/tree/master/promql) package, but extracted into a single module with fewer dependencies.
|
||||
|
||||
This module removes the promql engine and keeps anything related to lexing and parsing the PromQL language.
|
||||
|
||||
Each version of this module matches with the equivalent Prometheus version.
|
||||
|
||||
## Example
|
||||
|
||||
The PromQL module can be used to parse an expression.
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/influxdata/promql/v2"
|
||||
)
|
||||
|
||||
var myExpression = `http_requests_total{job="prometheus"}[5m]`
|
||||
|
||||
func main() {
|
||||
expr, err := promql.ParseExpr(myExpression)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(promql.Tree(expr))
|
||||
}
|
||||
```
|
||||
|
||||
## Contributing
|
||||
|
||||
Any changes to PromQL should not be made to this module as it is a pruned mirror of the original prometheus package. Changes should be submitted to the upstream [Prometheus](https://github.com/prometheus/prometheus) project and they will find their way into this repository when a new release happens.
|
||||
|
||||
The only changes that will be accepted into this repository are ones that fix a problem in the mirroring process, deviations from the upstream Prometheus, or operational changes to fulfill the primary purpose of this repository as a mirror of the promql package.
|
||||
+319
@@ -0,0 +1,319 @@
|
||||
// Copyright 2015 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package promql
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/influxdata/promql/v2/pkg/labels"
|
||||
)
|
||||
|
||||
// Node is a generic interface for all nodes in an AST.
|
||||
//
|
||||
// Whenever numerous nodes are listed such as in a switch-case statement
|
||||
// or a chain of function definitions (e.g. String(), expr(), etc.) convention is
|
||||
// to list them as follows:
|
||||
//
|
||||
// - Statements
|
||||
// - statement types (alphabetical)
|
||||
// - ...
|
||||
// - Expressions
|
||||
// - expression types (alphabetical)
|
||||
// - ...
|
||||
//
|
||||
type Node interface {
|
||||
// String representation of the node that returns the given node when parsed
|
||||
// as part of a valid query.
|
||||
String() string
|
||||
}
|
||||
|
||||
// Statement is a generic interface for all statements.
|
||||
type Statement interface {
|
||||
Node
|
||||
|
||||
// stmt ensures that no other type accidentally implements the interface
|
||||
stmt()
|
||||
}
|
||||
|
||||
// EvalStmt holds an expression and information on the range it should
|
||||
// be evaluated on.
|
||||
type EvalStmt struct {
|
||||
Expr Expr // Expression to be evaluated.
|
||||
|
||||
// The time boundaries for the evaluation. If Start equals End an instant
|
||||
// is evaluated.
|
||||
Start, End time.Time
|
||||
// Time between two evaluated instants for the range [Start:End].
|
||||
Interval time.Duration
|
||||
}
|
||||
|
||||
func (*EvalStmt) stmt() {}
|
||||
|
||||
// Expr is a generic interface for all expression types.
|
||||
type Expr interface {
|
||||
Node
|
||||
|
||||
// Type returns the type the expression evaluates to. It does not perform
|
||||
// in-depth checks as this is done at parsing-time.
|
||||
Type() ValueType
|
||||
// expr ensures that no other types accidentally implement the interface.
|
||||
expr()
|
||||
}
|
||||
|
||||
// Expressions is a list of expression nodes that implements Node.
|
||||
type Expressions []Expr
|
||||
|
||||
// AggregateExpr represents an aggregation operation on a Vector.
|
||||
type AggregateExpr struct {
|
||||
Op ItemType // The used aggregation operation.
|
||||
Expr Expr // The Vector expression over which is aggregated.
|
||||
Param Expr // Parameter used by some aggregators.
|
||||
Grouping []string // The labels by which to group the Vector.
|
||||
Without bool // Whether to drop the given labels rather than keep them.
|
||||
}
|
||||
|
||||
// BinaryExpr represents a binary expression between two child expressions.
|
||||
type BinaryExpr struct {
|
||||
Op ItemType // The operation of the expression.
|
||||
LHS, RHS Expr // The operands on the respective sides of the operator.
|
||||
|
||||
// The matching behavior for the operation if both operands are Vectors.
|
||||
// If they are not this field is nil.
|
||||
VectorMatching *VectorMatching
|
||||
|
||||
// If a comparison operator, return 0/1 rather than filtering.
|
||||
ReturnBool bool
|
||||
}
|
||||
|
||||
// Call represents a function call.
|
||||
type Call struct {
|
||||
Func *Function // The function that was called.
|
||||
Args Expressions // Arguments used in the call.
|
||||
}
|
||||
|
||||
// MatrixSelector represents a Matrix selection.
|
||||
type MatrixSelector struct {
|
||||
Name string
|
||||
Range time.Duration
|
||||
Offset time.Duration
|
||||
LabelMatchers []*labels.Matcher
|
||||
}
|
||||
|
||||
// SubqueryExpr represents a subquery.
|
||||
type SubqueryExpr struct {
|
||||
Expr Expr
|
||||
Range time.Duration
|
||||
Offset time.Duration
|
||||
Step time.Duration
|
||||
}
|
||||
|
||||
// NumberLiteral represents a number.
|
||||
type NumberLiteral struct {
|
||||
Val float64
|
||||
}
|
||||
|
||||
// ParenExpr wraps an expression so it cannot be disassembled as a consequence
|
||||
// of operator precedence.
|
||||
type ParenExpr struct {
|
||||
Expr Expr
|
||||
}
|
||||
|
||||
// StringLiteral represents a string.
|
||||
type StringLiteral struct {
|
||||
Val string
|
||||
}
|
||||
|
||||
// UnaryExpr represents a unary operation on another expression.
|
||||
// Currently unary operations are only supported for Scalars.
|
||||
type UnaryExpr struct {
|
||||
Op ItemType
|
||||
Expr Expr
|
||||
}
|
||||
|
||||
// VectorSelector represents a Vector selection.
|
||||
type VectorSelector struct {
|
||||
Name string
|
||||
Offset time.Duration
|
||||
LabelMatchers []*labels.Matcher
|
||||
}
|
||||
|
||||
func (e *AggregateExpr) Type() ValueType { return ValueTypeVector }
|
||||
func (e *Call) Type() ValueType { return e.Func.ReturnType }
|
||||
func (e *MatrixSelector) Type() ValueType { return ValueTypeMatrix }
|
||||
func (e *SubqueryExpr) Type() ValueType { return ValueTypeMatrix }
|
||||
func (e *NumberLiteral) Type() ValueType { return ValueTypeScalar }
|
||||
func (e *ParenExpr) Type() ValueType { return e.Expr.Type() }
|
||||
func (e *StringLiteral) Type() ValueType { return ValueTypeString }
|
||||
func (e *UnaryExpr) Type() ValueType { return e.Expr.Type() }
|
||||
func (e *VectorSelector) Type() ValueType { return ValueTypeVector }
|
||||
func (e *BinaryExpr) Type() ValueType {
|
||||
if e.LHS.Type() == ValueTypeScalar && e.RHS.Type() == ValueTypeScalar {
|
||||
return ValueTypeScalar
|
||||
}
|
||||
return ValueTypeVector
|
||||
}
|
||||
|
||||
func (*AggregateExpr) expr() {}
|
||||
func (*BinaryExpr) expr() {}
|
||||
func (*Call) expr() {}
|
||||
func (*MatrixSelector) expr() {}
|
||||
func (*SubqueryExpr) expr() {}
|
||||
func (*NumberLiteral) expr() {}
|
||||
func (*ParenExpr) expr() {}
|
||||
func (*StringLiteral) expr() {}
|
||||
func (*UnaryExpr) expr() {}
|
||||
func (*VectorSelector) expr() {}
|
||||
|
||||
// VectorMatchCardinality describes the cardinality relationship
|
||||
// of two Vectors in a binary operation.
|
||||
type VectorMatchCardinality int
|
||||
|
||||
const (
|
||||
CardOneToOne VectorMatchCardinality = iota
|
||||
CardManyToOne
|
||||
CardOneToMany
|
||||
CardManyToMany
|
||||
)
|
||||
|
||||
func (vmc VectorMatchCardinality) String() string {
|
||||
switch vmc {
|
||||
case CardOneToOne:
|
||||
return "one-to-one"
|
||||
case CardManyToOne:
|
||||
return "many-to-one"
|
||||
case CardOneToMany:
|
||||
return "one-to-many"
|
||||
case CardManyToMany:
|
||||
return "many-to-many"
|
||||
}
|
||||
panic("promql.VectorMatchCardinality.String: unknown match cardinality")
|
||||
}
|
||||
|
||||
// VectorMatching describes how elements from two Vectors in a binary
|
||||
// operation are supposed to be matched.
|
||||
type VectorMatching struct {
|
||||
// The cardinality of the two Vectors.
|
||||
Card VectorMatchCardinality
|
||||
// MatchingLabels contains the labels which define equality of a pair of
|
||||
// elements from the Vectors.
|
||||
MatchingLabels []string
|
||||
// On includes the given label names from matching,
|
||||
// rather than excluding them.
|
||||
On bool
|
||||
// Include contains additional labels that should be included in
|
||||
// the result from the side with the lower cardinality.
|
||||
Include []string
|
||||
}
|
||||
|
||||
// Visitor allows visiting a Node and its child nodes. The Visit method is
|
||||
// invoked for each node with the path leading to the node provided additionally.
|
||||
// If the result visitor w is not nil and no error, Walk visits each of the children
|
||||
// of node with the visitor w, followed by a call of w.Visit(nil, nil).
|
||||
type Visitor interface {
|
||||
Visit(node Node, path []Node) (w Visitor, err error)
|
||||
}
|
||||
|
||||
// Walk traverses an AST in depth-first order: It starts by calling
|
||||
// v.Visit(node, path); node must not be nil. If the visitor w returned by
|
||||
// v.Visit(node, path) is not nil and the visitor returns no error, Walk is
|
||||
// invoked recursively with visitor w for each of the non-nil children of node,
|
||||
// followed by a call of w.Visit(nil), returning an error
|
||||
// As the tree is descended the path of previous nodes is provided.
|
||||
func Walk(v Visitor, node Node, path []Node) error {
|
||||
var err error
|
||||
if v, err = v.Visit(node, path); v == nil || err != nil {
|
||||
return err
|
||||
}
|
||||
path = append(path, node)
|
||||
|
||||
switch n := node.(type) {
|
||||
case *EvalStmt:
|
||||
if err := Walk(v, n.Expr, path); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case Expressions:
|
||||
for _, e := range n {
|
||||
if err := Walk(v, e, path); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
case *AggregateExpr:
|
||||
if n.Param != nil {
|
||||
if err := Walk(v, n.Param, path); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := Walk(v, n.Expr, path); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case *BinaryExpr:
|
||||
if err := Walk(v, n.LHS, path); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := Walk(v, n.RHS, path); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case *Call:
|
||||
if err := Walk(v, n.Args, path); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case *SubqueryExpr:
|
||||
if err := Walk(v, n.Expr, path); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case *ParenExpr:
|
||||
if err := Walk(v, n.Expr, path); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case *UnaryExpr:
|
||||
if err := Walk(v, n.Expr, path); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case *MatrixSelector, *NumberLiteral, *StringLiteral, *VectorSelector:
|
||||
// nothing to do
|
||||
|
||||
default:
|
||||
panic(errors.Errorf("promql.Walk: unhandled node type %T", node))
|
||||
}
|
||||
|
||||
_, err = v.Visit(nil, nil)
|
||||
return err
|
||||
}
|
||||
|
||||
type inspector func(Node, []Node) error
|
||||
|
||||
func (f inspector) Visit(node Node, path []Node) (Visitor, error) {
|
||||
if err := f(node, path); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return f, nil
|
||||
}
|
||||
|
||||
// Inspect traverses an AST in depth-first order: It starts by calling
|
||||
// f(node, path); node must not be nil. If f returns a nil error, Inspect invokes f
|
||||
// for all the non-nil children of node, recursively.
|
||||
func Inspect(node Node, f inspector) {
|
||||
//nolint: errcheck
|
||||
Walk(inspector(f), node, nil)
|
||||
}
|
||||
+374
@@ -0,0 +1,374 @@
|
||||
// Copyright 2015 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package promql
|
||||
|
||||
import (
|
||||
"math"
|
||||
)
|
||||
|
||||
// Function represents a function of the expression language and is
|
||||
// used by function nodes.
|
||||
type Function struct {
|
||||
Name string
|
||||
ArgTypes []ValueType
|
||||
Variadic int
|
||||
ReturnType ValueType
|
||||
}
|
||||
|
||||
// Calculate the trend value at the given index i in raw data d.
|
||||
// This is somewhat analogous to the slope of the trend at the given index.
|
||||
// The argument "s" is the set of computed smoothed values.
|
||||
// The argument "b" is the set of computed trend factors.
|
||||
// The argument "d" is the set of raw input values.
|
||||
func calcTrendValue(i int, sf, tf, s0, s1, b float64) float64 {
|
||||
if i == 0 {
|
||||
return b
|
||||
}
|
||||
|
||||
x := tf * (s1 - s0)
|
||||
y := (1 - tf) * b
|
||||
|
||||
return x + y
|
||||
}
|
||||
|
||||
// linearRegression performs a least-square linear regression analysis on the
|
||||
// provided SamplePairs. It returns the slope, and the intercept value at the
|
||||
// provided time.
|
||||
func linearRegression(samples []Point, interceptTime int64) (slope, intercept float64) {
|
||||
var (
|
||||
n float64
|
||||
sumX, sumY float64
|
||||
sumXY, sumX2 float64
|
||||
)
|
||||
for _, sample := range samples {
|
||||
x := float64(sample.T-interceptTime) / 1e3
|
||||
n += 1.0
|
||||
sumY += sample.V
|
||||
sumX += x
|
||||
sumXY += x * sample.V
|
||||
sumX2 += x * x
|
||||
}
|
||||
covXY := sumXY - sumX*sumY/n
|
||||
varX := sumX2 - sumX*sumX/n
|
||||
|
||||
slope = covXY / varX
|
||||
intercept = sumY/n - slope*sumX/n
|
||||
return slope, intercept
|
||||
}
|
||||
|
||||
var functions = map[string]*Function{
|
||||
"abs": {
|
||||
Name: "abs",
|
||||
ArgTypes: []ValueType{ValueTypeVector},
|
||||
ReturnType: ValueTypeVector,
|
||||
},
|
||||
"absent": {
|
||||
Name: "absent",
|
||||
ArgTypes: []ValueType{ValueTypeVector},
|
||||
ReturnType: ValueTypeVector,
|
||||
},
|
||||
"avg_over_time": {
|
||||
Name: "avg_over_time",
|
||||
ArgTypes: []ValueType{ValueTypeMatrix},
|
||||
ReturnType: ValueTypeVector,
|
||||
},
|
||||
"ceil": {
|
||||
Name: "ceil",
|
||||
ArgTypes: []ValueType{ValueTypeVector},
|
||||
ReturnType: ValueTypeVector,
|
||||
},
|
||||
"changes": {
|
||||
Name: "changes",
|
||||
ArgTypes: []ValueType{ValueTypeMatrix},
|
||||
ReturnType: ValueTypeVector,
|
||||
},
|
||||
"clamp_max": {
|
||||
Name: "clamp_max",
|
||||
ArgTypes: []ValueType{ValueTypeVector, ValueTypeScalar},
|
||||
ReturnType: ValueTypeVector,
|
||||
},
|
||||
"clamp_min": {
|
||||
Name: "clamp_min",
|
||||
ArgTypes: []ValueType{ValueTypeVector, ValueTypeScalar},
|
||||
ReturnType: ValueTypeVector,
|
||||
},
|
||||
"count_over_time": {
|
||||
Name: "count_over_time",
|
||||
ArgTypes: []ValueType{ValueTypeMatrix},
|
||||
ReturnType: ValueTypeVector,
|
||||
},
|
||||
"days_in_month": {
|
||||
Name: "days_in_month",
|
||||
ArgTypes: []ValueType{ValueTypeVector},
|
||||
Variadic: 1,
|
||||
ReturnType: ValueTypeVector,
|
||||
},
|
||||
"day_of_month": {
|
||||
Name: "day_of_month",
|
||||
ArgTypes: []ValueType{ValueTypeVector},
|
||||
Variadic: 1,
|
||||
ReturnType: ValueTypeVector,
|
||||
},
|
||||
"day_of_week": {
|
||||
Name: "day_of_week",
|
||||
ArgTypes: []ValueType{ValueTypeVector},
|
||||
Variadic: 1,
|
||||
ReturnType: ValueTypeVector,
|
||||
},
|
||||
"delta": {
|
||||
Name: "delta",
|
||||
ArgTypes: []ValueType{ValueTypeMatrix},
|
||||
ReturnType: ValueTypeVector,
|
||||
},
|
||||
"deriv": {
|
||||
Name: "deriv",
|
||||
ArgTypes: []ValueType{ValueTypeMatrix},
|
||||
ReturnType: ValueTypeVector,
|
||||
},
|
||||
"exp": {
|
||||
Name: "exp",
|
||||
ArgTypes: []ValueType{ValueTypeVector},
|
||||
ReturnType: ValueTypeVector,
|
||||
},
|
||||
"floor": {
|
||||
Name: "floor",
|
||||
ArgTypes: []ValueType{ValueTypeVector},
|
||||
ReturnType: ValueTypeVector,
|
||||
},
|
||||
"histogram_quantile": {
|
||||
Name: "histogram_quantile",
|
||||
ArgTypes: []ValueType{ValueTypeScalar, ValueTypeVector},
|
||||
ReturnType: ValueTypeVector,
|
||||
},
|
||||
"holt_winters": {
|
||||
Name: "holt_winters",
|
||||
ArgTypes: []ValueType{ValueTypeMatrix, ValueTypeScalar, ValueTypeScalar},
|
||||
ReturnType: ValueTypeVector,
|
||||
},
|
||||
"hour": {
|
||||
Name: "hour",
|
||||
ArgTypes: []ValueType{ValueTypeVector},
|
||||
Variadic: 1,
|
||||
ReturnType: ValueTypeVector,
|
||||
},
|
||||
"idelta": {
|
||||
Name: "idelta",
|
||||
ArgTypes: []ValueType{ValueTypeMatrix},
|
||||
ReturnType: ValueTypeVector,
|
||||
},
|
||||
"increase": {
|
||||
Name: "increase",
|
||||
ArgTypes: []ValueType{ValueTypeMatrix},
|
||||
ReturnType: ValueTypeVector,
|
||||
},
|
||||
"irate": {
|
||||
Name: "irate",
|
||||
ArgTypes: []ValueType{ValueTypeMatrix},
|
||||
ReturnType: ValueTypeVector,
|
||||
},
|
||||
"label_replace": {
|
||||
Name: "label_replace",
|
||||
ArgTypes: []ValueType{ValueTypeVector, ValueTypeString, ValueTypeString, ValueTypeString, ValueTypeString},
|
||||
ReturnType: ValueTypeVector,
|
||||
},
|
||||
"label_join": {
|
||||
Name: "label_join",
|
||||
ArgTypes: []ValueType{ValueTypeVector, ValueTypeString, ValueTypeString, ValueTypeString},
|
||||
Variadic: -1,
|
||||
ReturnType: ValueTypeVector,
|
||||
},
|
||||
"ln": {
|
||||
Name: "ln",
|
||||
ArgTypes: []ValueType{ValueTypeVector},
|
||||
ReturnType: ValueTypeVector,
|
||||
},
|
||||
"log10": {
|
||||
Name: "log10",
|
||||
ArgTypes: []ValueType{ValueTypeVector},
|
||||
ReturnType: ValueTypeVector,
|
||||
},
|
||||
"log2": {
|
||||
Name: "log2",
|
||||
ArgTypes: []ValueType{ValueTypeVector},
|
||||
ReturnType: ValueTypeVector,
|
||||
},
|
||||
"max_over_time": {
|
||||
Name: "max_over_time",
|
||||
ArgTypes: []ValueType{ValueTypeMatrix},
|
||||
ReturnType: ValueTypeVector,
|
||||
},
|
||||
"min_over_time": {
|
||||
Name: "min_over_time",
|
||||
ArgTypes: []ValueType{ValueTypeMatrix},
|
||||
ReturnType: ValueTypeVector,
|
||||
},
|
||||
"minute": {
|
||||
Name: "minute",
|
||||
ArgTypes: []ValueType{ValueTypeVector},
|
||||
Variadic: 1,
|
||||
ReturnType: ValueTypeVector,
|
||||
},
|
||||
"month": {
|
||||
Name: "month",
|
||||
ArgTypes: []ValueType{ValueTypeVector},
|
||||
Variadic: 1,
|
||||
ReturnType: ValueTypeVector,
|
||||
},
|
||||
"predict_linear": {
|
||||
Name: "predict_linear",
|
||||
ArgTypes: []ValueType{ValueTypeMatrix, ValueTypeScalar},
|
||||
ReturnType: ValueTypeVector,
|
||||
},
|
||||
"quantile_over_time": {
|
||||
Name: "quantile_over_time",
|
||||
ArgTypes: []ValueType{ValueTypeScalar, ValueTypeMatrix},
|
||||
ReturnType: ValueTypeVector,
|
||||
},
|
||||
"rate": {
|
||||
Name: "rate",
|
||||
ArgTypes: []ValueType{ValueTypeMatrix},
|
||||
ReturnType: ValueTypeVector,
|
||||
},
|
||||
"resets": {
|
||||
Name: "resets",
|
||||
ArgTypes: []ValueType{ValueTypeMatrix},
|
||||
ReturnType: ValueTypeVector,
|
||||
},
|
||||
"round": {
|
||||
Name: "round",
|
||||
ArgTypes: []ValueType{ValueTypeVector, ValueTypeScalar},
|
||||
Variadic: 1,
|
||||
ReturnType: ValueTypeVector,
|
||||
},
|
||||
"scalar": {
|
||||
Name: "scalar",
|
||||
ArgTypes: []ValueType{ValueTypeVector},
|
||||
ReturnType: ValueTypeScalar,
|
||||
},
|
||||
"sort": {
|
||||
Name: "sort",
|
||||
ArgTypes: []ValueType{ValueTypeVector},
|
||||
ReturnType: ValueTypeVector,
|
||||
},
|
||||
"sort_desc": {
|
||||
Name: "sort_desc",
|
||||
ArgTypes: []ValueType{ValueTypeVector},
|
||||
ReturnType: ValueTypeVector,
|
||||
},
|
||||
"sqrt": {
|
||||
Name: "sqrt",
|
||||
ArgTypes: []ValueType{ValueTypeVector},
|
||||
ReturnType: ValueTypeVector,
|
||||
},
|
||||
"stddev_over_time": {
|
||||
Name: "stddev_over_time",
|
||||
ArgTypes: []ValueType{ValueTypeMatrix},
|
||||
ReturnType: ValueTypeVector,
|
||||
},
|
||||
"stdvar_over_time": {
|
||||
Name: "stdvar_over_time",
|
||||
ArgTypes: []ValueType{ValueTypeMatrix},
|
||||
ReturnType: ValueTypeVector,
|
||||
},
|
||||
"sum_over_time": {
|
||||
Name: "sum_over_time",
|
||||
ArgTypes: []ValueType{ValueTypeMatrix},
|
||||
ReturnType: ValueTypeVector,
|
||||
},
|
||||
"time": {
|
||||
Name: "time",
|
||||
ArgTypes: []ValueType{},
|
||||
ReturnType: ValueTypeScalar,
|
||||
},
|
||||
"timestamp": {
|
||||
Name: "timestamp",
|
||||
ArgTypes: []ValueType{ValueTypeVector},
|
||||
ReturnType: ValueTypeVector,
|
||||
},
|
||||
"vector": {
|
||||
Name: "vector",
|
||||
ArgTypes: []ValueType{ValueTypeScalar},
|
||||
ReturnType: ValueTypeVector,
|
||||
},
|
||||
"year": {
|
||||
Name: "year",
|
||||
ArgTypes: []ValueType{ValueTypeVector},
|
||||
Variadic: 1,
|
||||
ReturnType: ValueTypeVector,
|
||||
},
|
||||
}
|
||||
|
||||
// getFunction returns a predefined Function object for the given name.
|
||||
func getFunction(name string) (*Function, bool) {
|
||||
function, ok := functions[name]
|
||||
return function, ok
|
||||
}
|
||||
|
||||
type vectorByValueHeap Vector
|
||||
|
||||
func (s vectorByValueHeap) Len() int {
|
||||
return len(s)
|
||||
}
|
||||
|
||||
func (s vectorByValueHeap) Less(i, j int) bool {
|
||||
if math.IsNaN(s[i].V) {
|
||||
return true
|
||||
}
|
||||
return s[i].V < s[j].V
|
||||
}
|
||||
|
||||
func (s vectorByValueHeap) Swap(i, j int) {
|
||||
s[i], s[j] = s[j], s[i]
|
||||
}
|
||||
|
||||
func (s *vectorByValueHeap) Push(x interface{}) {
|
||||
*s = append(*s, *(x.(*Sample)))
|
||||
}
|
||||
|
||||
func (s *vectorByValueHeap) Pop() interface{} {
|
||||
old := *s
|
||||
n := len(old)
|
||||
el := old[n-1]
|
||||
*s = old[0 : n-1]
|
||||
return el
|
||||
}
|
||||
|
||||
type vectorByReverseValueHeap Vector
|
||||
|
||||
func (s vectorByReverseValueHeap) Len() int {
|
||||
return len(s)
|
||||
}
|
||||
|
||||
func (s vectorByReverseValueHeap) Less(i, j int) bool {
|
||||
if math.IsNaN(s[i].V) {
|
||||
return true
|
||||
}
|
||||
return s[i].V > s[j].V
|
||||
}
|
||||
|
||||
func (s vectorByReverseValueHeap) Swap(i, j int) {
|
||||
s[i], s[j] = s[j], s[i]
|
||||
}
|
||||
|
||||
func (s *vectorByReverseValueHeap) Push(x interface{}) {
|
||||
*s = append(*s, *(x.(*Sample)))
|
||||
}
|
||||
|
||||
func (s *vectorByReverseValueHeap) Pop() interface{} {
|
||||
old := *s
|
||||
n := len(old)
|
||||
el := old[n-1]
|
||||
*s = old[0 : n-1]
|
||||
return el
|
||||
}
|
||||
+917
@@ -0,0 +1,917 @@
|
||||
// Copyright 2015 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package promql
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// item represents a token or text string returned from the scanner.
|
||||
type item struct {
|
||||
typ ItemType // The type of this item.
|
||||
pos Pos // The starting position, in bytes, of this item in the input string.
|
||||
val string // The value of this item.
|
||||
}
|
||||
|
||||
// String returns a descriptive string for the item.
|
||||
func (i item) String() string {
|
||||
switch {
|
||||
case i.typ == ItemEOF:
|
||||
return "EOF"
|
||||
case i.typ == ItemError:
|
||||
return i.val
|
||||
case i.typ == ItemIdentifier || i.typ == ItemMetricIdentifier:
|
||||
return fmt.Sprintf("%q", i.val)
|
||||
case i.typ.isKeyword():
|
||||
return fmt.Sprintf("<%s>", i.val)
|
||||
case i.typ.isOperator():
|
||||
return fmt.Sprintf("<op:%s>", i.val)
|
||||
case i.typ.isAggregator():
|
||||
return fmt.Sprintf("<aggr:%s>", i.val)
|
||||
case len(i.val) > 10:
|
||||
return fmt.Sprintf("%.10q...", i.val)
|
||||
}
|
||||
return fmt.Sprintf("%q", i.val)
|
||||
}
|
||||
|
||||
// isOperator returns true if the item corresponds to a arithmetic or set operator.
|
||||
// Returns false otherwise.
|
||||
func (i ItemType) isOperator() bool { return i > operatorsStart && i < operatorsEnd }
|
||||
|
||||
// isAggregator returns true if the item belongs to the aggregator functions.
|
||||
// Returns false otherwise
|
||||
func (i ItemType) isAggregator() bool { return i > aggregatorsStart && i < aggregatorsEnd }
|
||||
|
||||
// isAggregator returns true if the item is an aggregator that takes a parameter.
|
||||
// Returns false otherwise
|
||||
func (i ItemType) isAggregatorWithParam() bool {
|
||||
return i == ItemTopK || i == ItemBottomK || i == ItemCountValues || i == ItemQuantile
|
||||
}
|
||||
|
||||
// isKeyword returns true if the item corresponds to a keyword.
|
||||
// Returns false otherwise.
|
||||
func (i ItemType) isKeyword() bool { return i > keywordsStart && i < keywordsEnd }
|
||||
|
||||
// isCompairsonOperator returns true if the item corresponds to a comparison operator.
|
||||
// Returns false otherwise.
|
||||
func (i ItemType) isComparisonOperator() bool {
|
||||
switch i {
|
||||
case ItemEQL, ItemNEQ, ItemLTE, ItemLSS, ItemGTE, ItemGTR:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// isSetOperator returns whether the item corresponds to a set operator.
|
||||
func (i ItemType) isSetOperator() bool {
|
||||
switch i {
|
||||
case ItemLAND, ItemLOR, ItemLUnless:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// LowestPrec is a constant for operator precedence in expressions.
|
||||
const LowestPrec = 0 // Non-operators.
|
||||
|
||||
// Precedence returns the operator precedence of the binary
|
||||
// operator op. If op is not a binary operator, the result
|
||||
// is LowestPrec.
|
||||
func (i ItemType) precedence() int {
|
||||
switch i {
|
||||
case ItemLOR:
|
||||
return 1
|
||||
case ItemLAND, ItemLUnless:
|
||||
return 2
|
||||
case ItemEQL, ItemNEQ, ItemLTE, ItemLSS, ItemGTE, ItemGTR:
|
||||
return 3
|
||||
case ItemADD, ItemSUB:
|
||||
return 4
|
||||
case ItemMUL, ItemDIV, ItemMOD:
|
||||
return 5
|
||||
case ItemPOW:
|
||||
return 6
|
||||
default:
|
||||
return LowestPrec
|
||||
}
|
||||
}
|
||||
|
||||
func (i ItemType) isRightAssociative() bool {
|
||||
switch i {
|
||||
case ItemPOW:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
type ItemType int
|
||||
|
||||
const (
|
||||
ItemError ItemType = iota // Error occurred, value is error message
|
||||
ItemEOF
|
||||
ItemComment
|
||||
ItemIdentifier
|
||||
ItemMetricIdentifier
|
||||
ItemLeftParen
|
||||
ItemRightParen
|
||||
ItemLeftBrace
|
||||
ItemRightBrace
|
||||
ItemLeftBracket
|
||||
ItemRightBracket
|
||||
ItemComma
|
||||
ItemAssign
|
||||
ItemColon
|
||||
ItemSemicolon
|
||||
ItemString
|
||||
ItemNumber
|
||||
ItemDuration
|
||||
ItemBlank
|
||||
ItemTimes
|
||||
ItemSpace
|
||||
|
||||
operatorsStart
|
||||
// Operators.
|
||||
ItemSUB
|
||||
ItemADD
|
||||
ItemMUL
|
||||
ItemMOD
|
||||
ItemDIV
|
||||
ItemLAND
|
||||
ItemLOR
|
||||
ItemLUnless
|
||||
ItemEQL
|
||||
ItemNEQ
|
||||
ItemLTE
|
||||
ItemLSS
|
||||
ItemGTE
|
||||
ItemGTR
|
||||
ItemEQLRegex
|
||||
ItemNEQRegex
|
||||
ItemPOW
|
||||
operatorsEnd
|
||||
|
||||
aggregatorsStart
|
||||
// Aggregators.
|
||||
ItemAvg
|
||||
ItemCount
|
||||
ItemSum
|
||||
ItemMin
|
||||
ItemMax
|
||||
ItemStddev
|
||||
ItemStdvar
|
||||
ItemTopK
|
||||
ItemBottomK
|
||||
ItemCountValues
|
||||
ItemQuantile
|
||||
aggregatorsEnd
|
||||
|
||||
keywordsStart
|
||||
// Keywords.
|
||||
ItemOffset
|
||||
ItemBy
|
||||
ItemWithout
|
||||
ItemOn
|
||||
ItemIgnoring
|
||||
ItemGroupLeft
|
||||
ItemGroupRight
|
||||
ItemBool
|
||||
keywordsEnd
|
||||
)
|
||||
|
||||
var key = map[string]ItemType{
|
||||
// Operators.
|
||||
"and": ItemLAND,
|
||||
"or": ItemLOR,
|
||||
"unless": ItemLUnless,
|
||||
|
||||
// Aggregators.
|
||||
"sum": ItemSum,
|
||||
"avg": ItemAvg,
|
||||
"count": ItemCount,
|
||||
"min": ItemMin,
|
||||
"max": ItemMax,
|
||||
"stddev": ItemStddev,
|
||||
"stdvar": ItemStdvar,
|
||||
"topk": ItemTopK,
|
||||
"bottomk": ItemBottomK,
|
||||
"count_values": ItemCountValues,
|
||||
"quantile": ItemQuantile,
|
||||
|
||||
// Keywords.
|
||||
"offset": ItemOffset,
|
||||
"by": ItemBy,
|
||||
"without": ItemWithout,
|
||||
"on": ItemOn,
|
||||
"ignoring": ItemIgnoring,
|
||||
"group_left": ItemGroupLeft,
|
||||
"group_right": ItemGroupRight,
|
||||
"bool": ItemBool,
|
||||
}
|
||||
|
||||
// These are the default string representations for common items. It does not
|
||||
// imply that those are the only character sequences that can be lexed to such an item.
|
||||
var itemTypeStr = map[ItemType]string{
|
||||
ItemLeftParen: "(",
|
||||
ItemRightParen: ")",
|
||||
ItemLeftBrace: "{",
|
||||
ItemRightBrace: "}",
|
||||
ItemLeftBracket: "[",
|
||||
ItemRightBracket: "]",
|
||||
ItemComma: ",",
|
||||
ItemAssign: "=",
|
||||
ItemColon: ":",
|
||||
ItemSemicolon: ";",
|
||||
ItemBlank: "_",
|
||||
ItemTimes: "x",
|
||||
ItemSpace: "<space>",
|
||||
|
||||
ItemSUB: "-",
|
||||
ItemADD: "+",
|
||||
ItemMUL: "*",
|
||||
ItemMOD: "%",
|
||||
ItemDIV: "/",
|
||||
ItemEQL: "==",
|
||||
ItemNEQ: "!=",
|
||||
ItemLTE: "<=",
|
||||
ItemLSS: "<",
|
||||
ItemGTE: ">=",
|
||||
ItemGTR: ">",
|
||||
ItemEQLRegex: "=~",
|
||||
ItemNEQRegex: "!~",
|
||||
ItemPOW: "^",
|
||||
}
|
||||
|
||||
func init() {
|
||||
// Add keywords to item type strings.
|
||||
for s, ty := range key {
|
||||
itemTypeStr[ty] = s
|
||||
}
|
||||
// Special numbers.
|
||||
key["inf"] = ItemNumber
|
||||
key["nan"] = ItemNumber
|
||||
}
|
||||
|
||||
func (i ItemType) String() string {
|
||||
if s, ok := itemTypeStr[i]; ok {
|
||||
return s
|
||||
}
|
||||
return fmt.Sprintf("<item %d>", i)
|
||||
}
|
||||
|
||||
func (i item) desc() string {
|
||||
if _, ok := itemTypeStr[i.typ]; ok {
|
||||
return i.String()
|
||||
}
|
||||
if i.typ == ItemEOF {
|
||||
return i.typ.desc()
|
||||
}
|
||||
return fmt.Sprintf("%s %s", i.typ.desc(), i)
|
||||
}
|
||||
|
||||
func (i ItemType) desc() string {
|
||||
switch i {
|
||||
case ItemError:
|
||||
return "error"
|
||||
case ItemEOF:
|
||||
return "end of input"
|
||||
case ItemComment:
|
||||
return "comment"
|
||||
case ItemIdentifier:
|
||||
return "identifier"
|
||||
case ItemMetricIdentifier:
|
||||
return "metric identifier"
|
||||
case ItemString:
|
||||
return "string"
|
||||
case ItemNumber:
|
||||
return "number"
|
||||
case ItemDuration:
|
||||
return "duration"
|
||||
}
|
||||
return fmt.Sprintf("%q", i)
|
||||
}
|
||||
|
||||
const eof = -1
|
||||
|
||||
// stateFn represents the state of the scanner as a function that returns the next state.
|
||||
type stateFn func(*lexer) stateFn
|
||||
|
||||
// Pos is the position in a string.
|
||||
type Pos int
|
||||
|
||||
// lexer holds the state of the scanner.
|
||||
type lexer struct {
|
||||
input string // The string being scanned.
|
||||
state stateFn // The next lexing function to enter.
|
||||
pos Pos // Current position in the input.
|
||||
start Pos // Start position of this item.
|
||||
width Pos // Width of last rune read from input.
|
||||
lastPos Pos // Position of most recent item returned by nextItem.
|
||||
items chan item // Channel of scanned items.
|
||||
|
||||
parenDepth int // Nesting depth of ( ) exprs.
|
||||
braceOpen bool // Whether a { is opened.
|
||||
bracketOpen bool // Whether a [ is opened.
|
||||
gotColon bool // Whether we got a ':' after [ was opened.
|
||||
stringOpen rune // Quote rune of the string currently being read.
|
||||
|
||||
// seriesDesc is set when a series description for the testing
|
||||
// language is lexed.
|
||||
seriesDesc bool
|
||||
}
|
||||
|
||||
// next returns the next rune in the input.
|
||||
func (l *lexer) next() rune {
|
||||
if int(l.pos) >= len(l.input) {
|
||||
l.width = 0
|
||||
return eof
|
||||
}
|
||||
r, w := utf8.DecodeRuneInString(l.input[l.pos:])
|
||||
l.width = Pos(w)
|
||||
l.pos += l.width
|
||||
return r
|
||||
}
|
||||
|
||||
// peek returns but does not consume the next rune in the input.
|
||||
func (l *lexer) peek() rune {
|
||||
r := l.next()
|
||||
l.backup()
|
||||
return r
|
||||
}
|
||||
|
||||
// backup steps back one rune. Can only be called once per call of next.
|
||||
func (l *lexer) backup() {
|
||||
l.pos -= l.width
|
||||
}
|
||||
|
||||
// emit passes an item back to the client.
|
||||
func (l *lexer) emit(t ItemType) {
|
||||
l.items <- item{t, l.start, l.input[l.start:l.pos]}
|
||||
l.start = l.pos
|
||||
}
|
||||
|
||||
// ignore skips over the pending input before this point.
|
||||
func (l *lexer) ignore() {
|
||||
l.start = l.pos
|
||||
}
|
||||
|
||||
// accept consumes the next rune if it's from the valid set.
|
||||
func (l *lexer) accept(valid string) bool {
|
||||
if strings.ContainsRune(valid, l.next()) {
|
||||
return true
|
||||
}
|
||||
l.backup()
|
||||
return false
|
||||
}
|
||||
|
||||
// acceptRun consumes a run of runes from the valid set.
|
||||
func (l *lexer) acceptRun(valid string) {
|
||||
for strings.ContainsRune(valid, l.next()) {
|
||||
// consume
|
||||
}
|
||||
l.backup()
|
||||
}
|
||||
|
||||
// lineNumber reports which line we're on, based on the position of
|
||||
// the previous item returned by nextItem. Doing it this way
|
||||
// means we don't have to worry about peek double counting.
|
||||
func (l *lexer) lineNumber() int {
|
||||
return 1 + strings.Count(l.input[:l.lastPos], "\n")
|
||||
}
|
||||
|
||||
// linePosition reports at which character in the current line
|
||||
// we are on.
|
||||
func (l *lexer) linePosition() int {
|
||||
lb := strings.LastIndex(l.input[:l.lastPos], "\n")
|
||||
if lb == -1 {
|
||||
return 1 + int(l.lastPos)
|
||||
}
|
||||
return 1 + int(l.lastPos) - lb
|
||||
}
|
||||
|
||||
// errorf returns an error token and terminates the scan by passing
|
||||
// back a nil pointer that will be the next state, terminating l.nextItem.
|
||||
func (l *lexer) errorf(format string, args ...interface{}) stateFn {
|
||||
l.items <- item{ItemError, l.start, fmt.Sprintf(format, args...)}
|
||||
return nil
|
||||
}
|
||||
|
||||
// nextItem returns the next item from the input.
|
||||
func (l *lexer) nextItem() item {
|
||||
item := <-l.items
|
||||
l.lastPos = item.pos
|
||||
return item
|
||||
}
|
||||
|
||||
// lex creates a new scanner for the input string.
|
||||
func lex(input string) *lexer {
|
||||
l := &lexer{
|
||||
input: input,
|
||||
items: make(chan item),
|
||||
}
|
||||
go l.run()
|
||||
return l
|
||||
}
|
||||
|
||||
// run runs the state machine for the lexer.
|
||||
func (l *lexer) run() {
|
||||
for l.state = lexStatements; l.state != nil; {
|
||||
l.state = l.state(l)
|
||||
}
|
||||
close(l.items)
|
||||
}
|
||||
|
||||
// Release resources used by lexer.
|
||||
func (l *lexer) close() {
|
||||
for range l.items {
|
||||
// Consume.
|
||||
}
|
||||
}
|
||||
|
||||
// lineComment is the character that starts a line comment.
|
||||
const lineComment = "#"
|
||||
|
||||
// lexStatements is the top-level state for lexing.
|
||||
func lexStatements(l *lexer) stateFn {
|
||||
if l.braceOpen {
|
||||
return lexInsideBraces
|
||||
}
|
||||
if strings.HasPrefix(l.input[l.pos:], lineComment) {
|
||||
return lexLineComment
|
||||
}
|
||||
|
||||
switch r := l.next(); {
|
||||
case r == eof:
|
||||
if l.parenDepth != 0 {
|
||||
return l.errorf("unclosed left parenthesis")
|
||||
} else if l.bracketOpen {
|
||||
return l.errorf("unclosed left bracket")
|
||||
}
|
||||
l.emit(ItemEOF)
|
||||
return nil
|
||||
case r == ',':
|
||||
l.emit(ItemComma)
|
||||
case isSpace(r):
|
||||
return lexSpace
|
||||
case r == '*':
|
||||
l.emit(ItemMUL)
|
||||
case r == '/':
|
||||
l.emit(ItemDIV)
|
||||
case r == '%':
|
||||
l.emit(ItemMOD)
|
||||
case r == '+':
|
||||
l.emit(ItemADD)
|
||||
case r == '-':
|
||||
l.emit(ItemSUB)
|
||||
case r == '^':
|
||||
l.emit(ItemPOW)
|
||||
case r == '=':
|
||||
if t := l.peek(); t == '=' {
|
||||
l.next()
|
||||
l.emit(ItemEQL)
|
||||
} else if t == '~' {
|
||||
return l.errorf("unexpected character after '=': %q", t)
|
||||
} else {
|
||||
l.emit(ItemAssign)
|
||||
}
|
||||
case r == '!':
|
||||
if t := l.next(); t == '=' {
|
||||
l.emit(ItemNEQ)
|
||||
} else {
|
||||
return l.errorf("unexpected character after '!': %q", t)
|
||||
}
|
||||
case r == '<':
|
||||
if t := l.peek(); t == '=' {
|
||||
l.next()
|
||||
l.emit(ItemLTE)
|
||||
} else {
|
||||
l.emit(ItemLSS)
|
||||
}
|
||||
case r == '>':
|
||||
if t := l.peek(); t == '=' {
|
||||
l.next()
|
||||
l.emit(ItemGTE)
|
||||
} else {
|
||||
l.emit(ItemGTR)
|
||||
}
|
||||
case isDigit(r) || (r == '.' && isDigit(l.peek())):
|
||||
l.backup()
|
||||
return lexNumberOrDuration
|
||||
case r == '"' || r == '\'':
|
||||
l.stringOpen = r
|
||||
return lexString
|
||||
case r == '`':
|
||||
l.stringOpen = r
|
||||
return lexRawString
|
||||
case isAlpha(r) || r == ':':
|
||||
if !l.bracketOpen {
|
||||
l.backup()
|
||||
return lexKeywordOrIdentifier
|
||||
}
|
||||
if l.gotColon {
|
||||
return l.errorf("unexpected colon %q", r)
|
||||
}
|
||||
l.emit(ItemColon)
|
||||
l.gotColon = true
|
||||
case r == '(':
|
||||
l.emit(ItemLeftParen)
|
||||
l.parenDepth++
|
||||
return lexStatements
|
||||
case r == ')':
|
||||
l.emit(ItemRightParen)
|
||||
l.parenDepth--
|
||||
if l.parenDepth < 0 {
|
||||
return l.errorf("unexpected right parenthesis %q", r)
|
||||
}
|
||||
return lexStatements
|
||||
case r == '{':
|
||||
l.emit(ItemLeftBrace)
|
||||
l.braceOpen = true
|
||||
return lexInsideBraces(l)
|
||||
case r == '[':
|
||||
if l.bracketOpen {
|
||||
return l.errorf("unexpected left bracket %q", r)
|
||||
}
|
||||
l.gotColon = false
|
||||
l.emit(ItemLeftBracket)
|
||||
l.bracketOpen = true
|
||||
return lexDuration
|
||||
case r == ']':
|
||||
if !l.bracketOpen {
|
||||
return l.errorf("unexpected right bracket %q", r)
|
||||
}
|
||||
l.emit(ItemRightBracket)
|
||||
l.bracketOpen = false
|
||||
|
||||
default:
|
||||
return l.errorf("unexpected character: %q", r)
|
||||
}
|
||||
return lexStatements
|
||||
}
|
||||
|
||||
// lexInsideBraces scans the inside of a vector selector. Keywords are ignored and
|
||||
// scanned as identifiers.
|
||||
func lexInsideBraces(l *lexer) stateFn {
|
||||
if strings.HasPrefix(l.input[l.pos:], lineComment) {
|
||||
return lexLineComment
|
||||
}
|
||||
|
||||
switch r := l.next(); {
|
||||
case r == eof:
|
||||
return l.errorf("unexpected end of input inside braces")
|
||||
case isSpace(r):
|
||||
return lexSpace
|
||||
case isAlpha(r):
|
||||
l.backup()
|
||||
return lexIdentifier
|
||||
case r == ',':
|
||||
l.emit(ItemComma)
|
||||
case r == '"' || r == '\'':
|
||||
l.stringOpen = r
|
||||
return lexString
|
||||
case r == '`':
|
||||
l.stringOpen = r
|
||||
return lexRawString
|
||||
case r == '=':
|
||||
if l.next() == '~' {
|
||||
l.emit(ItemEQLRegex)
|
||||
break
|
||||
}
|
||||
l.backup()
|
||||
l.emit(ItemEQL)
|
||||
case r == '!':
|
||||
switch nr := l.next(); {
|
||||
case nr == '~':
|
||||
l.emit(ItemNEQRegex)
|
||||
case nr == '=':
|
||||
l.emit(ItemNEQ)
|
||||
default:
|
||||
return l.errorf("unexpected character after '!' inside braces: %q", nr)
|
||||
}
|
||||
case r == '{':
|
||||
return l.errorf("unexpected left brace %q", r)
|
||||
case r == '}':
|
||||
l.emit(ItemRightBrace)
|
||||
l.braceOpen = false
|
||||
|
||||
if l.seriesDesc {
|
||||
return lexValueSequence
|
||||
}
|
||||
return lexStatements
|
||||
default:
|
||||
return l.errorf("unexpected character inside braces: %q", r)
|
||||
}
|
||||
return lexInsideBraces
|
||||
}
|
||||
|
||||
// lexValueSequence scans a value sequence of a series description.
|
||||
func lexValueSequence(l *lexer) stateFn {
|
||||
switch r := l.next(); {
|
||||
case r == eof:
|
||||
return lexStatements
|
||||
case isSpace(r):
|
||||
l.emit(ItemSpace)
|
||||
lexSpace(l)
|
||||
case r == '+':
|
||||
l.emit(ItemADD)
|
||||
case r == '-':
|
||||
l.emit(ItemSUB)
|
||||
case r == 'x':
|
||||
l.emit(ItemTimes)
|
||||
case r == '_':
|
||||
l.emit(ItemBlank)
|
||||
case isDigit(r) || (r == '.' && isDigit(l.peek())):
|
||||
l.backup()
|
||||
lexNumber(l)
|
||||
case isAlpha(r):
|
||||
l.backup()
|
||||
// We might lex invalid items here but this will be caught by the parser.
|
||||
return lexKeywordOrIdentifier
|
||||
default:
|
||||
return l.errorf("unexpected character in series sequence: %q", r)
|
||||
}
|
||||
return lexValueSequence
|
||||
}
|
||||
|
||||
// lexEscape scans a string escape sequence. The initial escaping character (\)
|
||||
// has already been seen.
|
||||
//
|
||||
// NOTE: This function as well as the helper function digitVal() and associated
|
||||
// tests have been adapted from the corresponding functions in the "go/scanner"
|
||||
// package of the Go standard library to work for Prometheus-style strings.
|
||||
// None of the actual escaping/quoting logic was changed in this function - it
|
||||
// was only modified to integrate with our lexer.
|
||||
func lexEscape(l *lexer) {
|
||||
var n int
|
||||
var base, max uint32
|
||||
|
||||
ch := l.next()
|
||||
switch ch {
|
||||
case 'a', 'b', 'f', 'n', 'r', 't', 'v', '\\', l.stringOpen:
|
||||
return
|
||||
case '0', '1', '2', '3', '4', '5', '6', '7':
|
||||
n, base, max = 3, 8, 255
|
||||
case 'x':
|
||||
ch = l.next()
|
||||
n, base, max = 2, 16, 255
|
||||
case 'u':
|
||||
ch = l.next()
|
||||
n, base, max = 4, 16, unicode.MaxRune
|
||||
case 'U':
|
||||
ch = l.next()
|
||||
n, base, max = 8, 16, unicode.MaxRune
|
||||
case eof:
|
||||
l.errorf("escape sequence not terminated")
|
||||
default:
|
||||
l.errorf("unknown escape sequence %#U", ch)
|
||||
}
|
||||
|
||||
var x uint32
|
||||
for n > 0 {
|
||||
d := uint32(digitVal(ch))
|
||||
if d >= base {
|
||||
if ch == eof {
|
||||
l.errorf("escape sequence not terminated")
|
||||
}
|
||||
l.errorf("illegal character %#U in escape sequence", ch)
|
||||
}
|
||||
x = x*base + d
|
||||
ch = l.next()
|
||||
n--
|
||||
}
|
||||
|
||||
if x > max || 0xD800 <= x && x < 0xE000 {
|
||||
l.errorf("escape sequence is an invalid Unicode code point")
|
||||
}
|
||||
}
|
||||
|
||||
// digitVal returns the digit value of a rune or 16 in case the rune does not
|
||||
// represent a valid digit.
|
||||
func digitVal(ch rune) int {
|
||||
switch {
|
||||
case '0' <= ch && ch <= '9':
|
||||
return int(ch - '0')
|
||||
case 'a' <= ch && ch <= 'f':
|
||||
return int(ch - 'a' + 10)
|
||||
case 'A' <= ch && ch <= 'F':
|
||||
return int(ch - 'A' + 10)
|
||||
}
|
||||
return 16 // Larger than any legal digit val.
|
||||
}
|
||||
|
||||
// lexString scans a quoted string. The initial quote has already been seen.
|
||||
func lexString(l *lexer) stateFn {
|
||||
Loop:
|
||||
for {
|
||||
switch l.next() {
|
||||
case '\\':
|
||||
lexEscape(l)
|
||||
case utf8.RuneError:
|
||||
return l.errorf("invalid UTF-8 rune")
|
||||
case eof, '\n':
|
||||
return l.errorf("unterminated quoted string")
|
||||
case l.stringOpen:
|
||||
break Loop
|
||||
}
|
||||
}
|
||||
l.emit(ItemString)
|
||||
return lexStatements
|
||||
}
|
||||
|
||||
// lexRawString scans a raw quoted string. The initial quote has already been seen.
|
||||
func lexRawString(l *lexer) stateFn {
|
||||
Loop:
|
||||
for {
|
||||
switch l.next() {
|
||||
case utf8.RuneError:
|
||||
return l.errorf("invalid UTF-8 rune")
|
||||
case eof:
|
||||
return l.errorf("unterminated raw string")
|
||||
case l.stringOpen:
|
||||
break Loop
|
||||
}
|
||||
}
|
||||
l.emit(ItemString)
|
||||
return lexStatements
|
||||
}
|
||||
|
||||
// lexSpace scans a run of space characters. One space has already been seen.
|
||||
func lexSpace(l *lexer) stateFn {
|
||||
for isSpace(l.peek()) {
|
||||
l.next()
|
||||
}
|
||||
l.ignore()
|
||||
return lexStatements
|
||||
}
|
||||
|
||||
// lexLineComment scans a line comment. Left comment marker is known to be present.
|
||||
func lexLineComment(l *lexer) stateFn {
|
||||
l.pos += Pos(len(lineComment))
|
||||
for r := l.next(); !isEndOfLine(r) && r != eof; {
|
||||
r = l.next()
|
||||
}
|
||||
l.backup()
|
||||
l.emit(ItemComment)
|
||||
return lexStatements
|
||||
}
|
||||
|
||||
func lexDuration(l *lexer) stateFn {
|
||||
if l.scanNumber() {
|
||||
return l.errorf("missing unit character in duration")
|
||||
}
|
||||
// Next two chars must be a valid unit and a non-alphanumeric.
|
||||
if l.accept("smhdwy") {
|
||||
if isAlphaNumeric(l.next()) {
|
||||
return l.errorf("bad duration syntax: %q", l.input[l.start:l.pos])
|
||||
}
|
||||
l.backup()
|
||||
l.emit(ItemDuration)
|
||||
return lexStatements
|
||||
}
|
||||
return l.errorf("bad duration syntax: %q", l.input[l.start:l.pos])
|
||||
}
|
||||
|
||||
// lexNumber scans a number: decimal, hex, oct or float.
|
||||
func lexNumber(l *lexer) stateFn {
|
||||
if !l.scanNumber() {
|
||||
return l.errorf("bad number syntax: %q", l.input[l.start:l.pos])
|
||||
}
|
||||
l.emit(ItemNumber)
|
||||
return lexStatements
|
||||
}
|
||||
|
||||
// lexNumberOrDuration scans a number or a duration item.
|
||||
func lexNumberOrDuration(l *lexer) stateFn {
|
||||
if l.scanNumber() {
|
||||
l.emit(ItemNumber)
|
||||
return lexStatements
|
||||
}
|
||||
// Next two chars must be a valid unit and a non-alphanumeric.
|
||||
if l.accept("smhdwy") {
|
||||
if isAlphaNumeric(l.next()) {
|
||||
return l.errorf("bad number or duration syntax: %q", l.input[l.start:l.pos])
|
||||
}
|
||||
l.backup()
|
||||
l.emit(ItemDuration)
|
||||
return lexStatements
|
||||
}
|
||||
return l.errorf("bad number or duration syntax: %q", l.input[l.start:l.pos])
|
||||
}
|
||||
|
||||
// scanNumber scans numbers of different formats. The scanned item is
|
||||
// not necessarily a valid number. This case is caught by the parser.
|
||||
func (l *lexer) scanNumber() bool {
|
||||
digits := "0123456789"
|
||||
// Disallow hexadecimal in series descriptions as the syntax is ambiguous.
|
||||
if !l.seriesDesc && l.accept("0") && l.accept("xX") {
|
||||
digits = "0123456789abcdefABCDEF"
|
||||
}
|
||||
l.acceptRun(digits)
|
||||
if l.accept(".") {
|
||||
l.acceptRun(digits)
|
||||
}
|
||||
if l.accept("eE") {
|
||||
l.accept("+-")
|
||||
l.acceptRun("0123456789")
|
||||
}
|
||||
// Next thing must not be alphanumeric unless it's the times token
|
||||
// for series repetitions.
|
||||
if r := l.peek(); (l.seriesDesc && r == 'x') || !isAlphaNumeric(r) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// lexIdentifier scans an alphanumeric identifier. The next character
|
||||
// is known to be a letter.
|
||||
func lexIdentifier(l *lexer) stateFn {
|
||||
for isAlphaNumeric(l.next()) {
|
||||
// absorb
|
||||
}
|
||||
l.backup()
|
||||
l.emit(ItemIdentifier)
|
||||
return lexStatements
|
||||
}
|
||||
|
||||
// lexKeywordOrIdentifier scans an alphanumeric identifier which may contain
|
||||
// a colon rune. If the identifier is a keyword the respective keyword item
|
||||
// is scanned.
|
||||
func lexKeywordOrIdentifier(l *lexer) stateFn {
|
||||
Loop:
|
||||
for {
|
||||
switch r := l.next(); {
|
||||
case isAlphaNumeric(r) || r == ':':
|
||||
// absorb.
|
||||
default:
|
||||
l.backup()
|
||||
word := l.input[l.start:l.pos]
|
||||
if kw, ok := key[strings.ToLower(word)]; ok {
|
||||
l.emit(kw)
|
||||
} else if !strings.Contains(word, ":") {
|
||||
l.emit(ItemIdentifier)
|
||||
} else {
|
||||
l.emit(ItemMetricIdentifier)
|
||||
}
|
||||
break Loop
|
||||
}
|
||||
}
|
||||
if l.seriesDesc && l.peek() != '{' {
|
||||
return lexValueSequence
|
||||
}
|
||||
return lexStatements
|
||||
}
|
||||
|
||||
func isSpace(r rune) bool {
|
||||
return r == ' ' || r == '\t' || r == '\n' || r == '\r'
|
||||
}
|
||||
|
||||
// isEndOfLine reports whether r is an end-of-line character.
|
||||
func isEndOfLine(r rune) bool {
|
||||
return r == '\r' || r == '\n'
|
||||
}
|
||||
|
||||
// isAlphaNumeric reports whether r is an alphabetic, digit, or underscore.
|
||||
func isAlphaNumeric(r rune) bool {
|
||||
return isAlpha(r) || isDigit(r)
|
||||
}
|
||||
|
||||
// isDigit reports whether r is a digit. Note: we cannot use unicode.IsDigit()
|
||||
// instead because that also classifies non-Latin digits as digits. See
|
||||
// https://github.com/prometheus/prometheus/issues/939.
|
||||
func isDigit(r rune) bool {
|
||||
return '0' <= r && r <= '9'
|
||||
}
|
||||
|
||||
// isAlpha reports whether r is an alphabetic or underscore.
|
||||
func isAlpha(r rune) bool {
|
||||
return r == '_' || ('a' <= r && r <= 'z') || ('A' <= r && r <= 'Z')
|
||||
}
|
||||
|
||||
// isLabel reports whether the string can be used as label.
|
||||
func isLabel(s string) bool {
|
||||
if len(s) == 0 || !isAlpha(rune(s[0])) {
|
||||
return false
|
||||
}
|
||||
for _, c := range s[1:] {
|
||||
if !isAlphaNumeric(c) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
+1089
File diff suppressed because it is too large
Load Diff
+368
@@ -0,0 +1,368 @@
|
||||
// Copyright 2017 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package labels
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/cespare/xxhash"
|
||||
)
|
||||
|
||||
const sep = '\xff'
|
||||
|
||||
// Well-known label names used by Prometheus components.
|
||||
const (
|
||||
MetricName = "__name__"
|
||||
AlertName = "alertname"
|
||||
BucketLabel = "le"
|
||||
InstanceName = "instance"
|
||||
)
|
||||
|
||||
// Label is a key/value pair of strings.
|
||||
type Label struct {
|
||||
Name, Value string
|
||||
}
|
||||
|
||||
// Labels is a sorted set of labels. Order has to be guaranteed upon
|
||||
// instantiation.
|
||||
type Labels []Label
|
||||
|
||||
func (ls Labels) Len() int { return len(ls) }
|
||||
func (ls Labels) Swap(i, j int) { ls[i], ls[j] = ls[j], ls[i] }
|
||||
func (ls Labels) Less(i, j int) bool { return ls[i].Name < ls[j].Name }
|
||||
|
||||
func (ls Labels) String() string {
|
||||
var b bytes.Buffer
|
||||
|
||||
b.WriteByte('{')
|
||||
for i, l := range ls {
|
||||
if i > 0 {
|
||||
b.WriteByte(',')
|
||||
b.WriteByte(' ')
|
||||
}
|
||||
b.WriteString(l.Name)
|
||||
b.WriteByte('=')
|
||||
b.WriteString(strconv.Quote(l.Value))
|
||||
}
|
||||
b.WriteByte('}')
|
||||
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// MarshalJSON implements json.Marshaler.
|
||||
func (ls Labels) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(ls.Map())
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements json.Unmarshaler.
|
||||
func (ls *Labels) UnmarshalJSON(b []byte) error {
|
||||
var m map[string]string
|
||||
|
||||
if err := json.Unmarshal(b, &m); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*ls = FromMap(m)
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalYAML implements yaml.Marshaler.
|
||||
func (ls Labels) MarshalYAML() (interface{}, error) {
|
||||
return ls.Map(), nil
|
||||
}
|
||||
|
||||
// UnmarshalYAML implements yaml.Unmarshaler.
|
||||
func (ls *Labels) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
var m map[string]string
|
||||
|
||||
if err := unmarshal(&m); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*ls = FromMap(m)
|
||||
return nil
|
||||
}
|
||||
|
||||
// MatchLabels returns a subset of Labels that matches/does not match with the provided label names based on the 'on' boolean.
|
||||
// If on is set to true, it returns the subset of labels that match with the provided label names and its inverse when 'on' is set to false.
|
||||
func (ls Labels) MatchLabels(on bool, names ...string) Labels {
|
||||
matchedLabels := Labels{}
|
||||
|
||||
nameSet := map[string]struct{}{}
|
||||
for _, n := range names {
|
||||
nameSet[n] = struct{}{}
|
||||
}
|
||||
|
||||
for _, v := range ls {
|
||||
if _, ok := nameSet[v.Name]; on == ok {
|
||||
matchedLabels = append(matchedLabels, v)
|
||||
}
|
||||
}
|
||||
|
||||
return matchedLabels
|
||||
}
|
||||
|
||||
// Hash returns a hash value for the label set.
|
||||
func (ls Labels) Hash() uint64 {
|
||||
b := make([]byte, 0, 1024)
|
||||
|
||||
for _, v := range ls {
|
||||
b = append(b, v.Name...)
|
||||
b = append(b, sep)
|
||||
b = append(b, v.Value...)
|
||||
b = append(b, sep)
|
||||
}
|
||||
return xxhash.Sum64(b)
|
||||
}
|
||||
|
||||
// HashForLabels returns a hash value for the labels matching the provided names.
|
||||
// 'names' have to be sorted in ascending order.
|
||||
func (ls Labels) HashForLabels(b []byte, names ...string) (uint64, []byte) {
|
||||
b = b[:0]
|
||||
i, j := 0, 0
|
||||
for i < len(ls) && j < len(names) {
|
||||
if names[j] < ls[i].Name {
|
||||
j++
|
||||
} else if ls[i].Name < names[j] {
|
||||
i++
|
||||
} else {
|
||||
b = append(b, ls[i].Name...)
|
||||
b = append(b, sep)
|
||||
b = append(b, ls[i].Value...)
|
||||
b = append(b, sep)
|
||||
i++
|
||||
j++
|
||||
}
|
||||
}
|
||||
return xxhash.Sum64(b), b
|
||||
}
|
||||
|
||||
// HashWithoutLabels returns a hash value for all labels except those matching
|
||||
// the provided names.
|
||||
// 'names' have to be sorted in ascending order.
|
||||
func (ls Labels) HashWithoutLabels(b []byte, names ...string) (uint64, []byte) {
|
||||
b = b[:0]
|
||||
j := 0
|
||||
for i := range ls {
|
||||
for j < len(names) && names[j] < ls[i].Name {
|
||||
j++
|
||||
}
|
||||
if ls[i].Name == MetricName || (j < len(names) && ls[i].Name == names[j]) {
|
||||
continue
|
||||
}
|
||||
b = append(b, ls[i].Name...)
|
||||
b = append(b, sep)
|
||||
b = append(b, ls[i].Value...)
|
||||
b = append(b, sep)
|
||||
}
|
||||
return xxhash.Sum64(b), b
|
||||
}
|
||||
|
||||
// Copy returns a copy of the labels.
|
||||
func (ls Labels) Copy() Labels {
|
||||
res := make(Labels, len(ls))
|
||||
copy(res, ls)
|
||||
return res
|
||||
}
|
||||
|
||||
// Get returns the value for the label with the given name.
|
||||
// Returns an empty string if the label doesn't exist.
|
||||
func (ls Labels) Get(name string) string {
|
||||
for _, l := range ls {
|
||||
if l.Name == name {
|
||||
return l.Value
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Has returns true if the label with the given name is present.
|
||||
func (ls Labels) Has(name string) bool {
|
||||
for _, l := range ls {
|
||||
if l.Name == name {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Equal returns whether the two label sets are equal.
|
||||
func Equal(ls, o Labels) bool {
|
||||
if len(ls) != len(o) {
|
||||
return false
|
||||
}
|
||||
for i, l := range ls {
|
||||
if l.Name != o[i].Name || l.Value != o[i].Value {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Map returns a string map of the labels.
|
||||
func (ls Labels) Map() map[string]string {
|
||||
m := make(map[string]string, len(ls))
|
||||
for _, l := range ls {
|
||||
m[l.Name] = l.Value
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// New returns a sorted Labels from the given labels.
|
||||
// The caller has to guarantee that all label names are unique.
|
||||
func New(ls ...Label) Labels {
|
||||
set := make(Labels, 0, len(ls))
|
||||
for _, l := range ls {
|
||||
set = append(set, l)
|
||||
}
|
||||
sort.Sort(set)
|
||||
|
||||
return set
|
||||
}
|
||||
|
||||
// FromMap returns new sorted Labels from the given map.
|
||||
func FromMap(m map[string]string) Labels {
|
||||
l := make([]Label, 0, len(m))
|
||||
for k, v := range m {
|
||||
l = append(l, Label{Name: k, Value: v})
|
||||
}
|
||||
return New(l...)
|
||||
}
|
||||
|
||||
// FromStrings creates new labels from pairs of strings.
|
||||
func FromStrings(ss ...string) Labels {
|
||||
if len(ss)%2 != 0 {
|
||||
panic("invalid number of strings")
|
||||
}
|
||||
var res Labels
|
||||
for i := 0; i < len(ss); i += 2 {
|
||||
res = append(res, Label{Name: ss[i], Value: ss[i+1]})
|
||||
}
|
||||
|
||||
sort.Sort(res)
|
||||
return res
|
||||
}
|
||||
|
||||
// Compare compares the two label sets.
|
||||
// The result will be 0 if a==b, <0 if a < b, and >0 if a > b.
|
||||
func Compare(a, b Labels) int {
|
||||
l := len(a)
|
||||
if len(b) < l {
|
||||
l = len(b)
|
||||
}
|
||||
|
||||
for i := 0; i < l; i++ {
|
||||
if d := strings.Compare(a[i].Name, b[i].Name); d != 0 {
|
||||
return d
|
||||
}
|
||||
if d := strings.Compare(a[i].Value, b[i].Value); d != 0 {
|
||||
return d
|
||||
}
|
||||
}
|
||||
// If all labels so far were in common, the set with fewer labels comes first.
|
||||
return len(a) - len(b)
|
||||
}
|
||||
|
||||
// Builder allows modifying Labels.
|
||||
type Builder struct {
|
||||
base Labels
|
||||
del []string
|
||||
add []Label
|
||||
}
|
||||
|
||||
// NewBuilder returns a new LabelsBuilder.
|
||||
func NewBuilder(base Labels) *Builder {
|
||||
b := &Builder{
|
||||
del: make([]string, 0, 5),
|
||||
add: make([]Label, 0, 5),
|
||||
}
|
||||
b.Reset(base)
|
||||
return b
|
||||
}
|
||||
|
||||
// Reset clears all current state for the builder.
|
||||
func (b *Builder) Reset(base Labels) {
|
||||
b.base = base
|
||||
b.del = b.del[:0]
|
||||
b.add = b.add[:0]
|
||||
for _, l := range b.base {
|
||||
if l.Value == "" {
|
||||
b.del = append(b.del, l.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Del deletes the label of the given name.
|
||||
func (b *Builder) Del(ns ...string) *Builder {
|
||||
for _, n := range ns {
|
||||
for i, a := range b.add {
|
||||
if a.Name == n {
|
||||
b.add = append(b.add[:i], b.add[i+1:]...)
|
||||
}
|
||||
}
|
||||
b.del = append(b.del, n)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// Set the name/value pair as a label.
|
||||
func (b *Builder) Set(n, v string) *Builder {
|
||||
if v == "" {
|
||||
// Empty labels are the same as missing labels.
|
||||
return b.Del(n)
|
||||
}
|
||||
for i, a := range b.add {
|
||||
if a.Name == n {
|
||||
b.add[i].Value = v
|
||||
return b
|
||||
}
|
||||
}
|
||||
b.add = append(b.add, Label{Name: n, Value: v})
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
// Labels returns the labels from the builder. If no modifications
|
||||
// were made, the original labels are returned.
|
||||
func (b *Builder) Labels() Labels {
|
||||
if len(b.del) == 0 && len(b.add) == 0 {
|
||||
return b.base
|
||||
}
|
||||
|
||||
// In the general case, labels are removed, modified or moved
|
||||
// rather than added.
|
||||
res := make(Labels, 0, len(b.base))
|
||||
Outer:
|
||||
for _, l := range b.base {
|
||||
for _, n := range b.del {
|
||||
if l.Name == n {
|
||||
continue Outer
|
||||
}
|
||||
}
|
||||
for _, la := range b.add {
|
||||
if l.Name == la.Name {
|
||||
continue Outer
|
||||
}
|
||||
}
|
||||
res = append(res, l)
|
||||
}
|
||||
res = append(res, b.add...)
|
||||
sort.Sort(res)
|
||||
|
||||
return res
|
||||
}
|
||||
+88
@@ -0,0 +1,88 @@
|
||||
// Copyright 2017 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package labels
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
// MatchType is an enum for label matching types.
|
||||
type MatchType int
|
||||
|
||||
// Possible MatchTypes.
|
||||
const (
|
||||
MatchEqual MatchType = iota
|
||||
MatchNotEqual
|
||||
MatchRegexp
|
||||
MatchNotRegexp
|
||||
)
|
||||
|
||||
func (m MatchType) String() string {
|
||||
typeToStr := map[MatchType]string{
|
||||
MatchEqual: "=",
|
||||
MatchNotEqual: "!=",
|
||||
MatchRegexp: "=~",
|
||||
MatchNotRegexp: "!~",
|
||||
}
|
||||
if str, ok := typeToStr[m]; ok {
|
||||
return str
|
||||
}
|
||||
panic("unknown match type")
|
||||
}
|
||||
|
||||
// Matcher models the matching of a label.
|
||||
type Matcher struct {
|
||||
Type MatchType
|
||||
Name string
|
||||
Value string
|
||||
|
||||
re *regexp.Regexp
|
||||
}
|
||||
|
||||
// NewMatcher returns a matcher object.
|
||||
func NewMatcher(t MatchType, n, v string) (*Matcher, error) {
|
||||
m := &Matcher{
|
||||
Type: t,
|
||||
Name: n,
|
||||
Value: v,
|
||||
}
|
||||
if t == MatchRegexp || t == MatchNotRegexp {
|
||||
re, err := regexp.Compile("^(?:" + v + ")$")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m.re = re
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (m *Matcher) String() string {
|
||||
return fmt.Sprintf("%s%s%q", m.Name, m.Type, m.Value)
|
||||
}
|
||||
|
||||
// Matches returns whether the matcher matches the given string value.
|
||||
func (m *Matcher) Matches(s string) bool {
|
||||
switch m.Type {
|
||||
case MatchEqual:
|
||||
return s == m.Value
|
||||
case MatchNotEqual:
|
||||
return s != m.Value
|
||||
case MatchRegexp:
|
||||
return m.re.MatchString(s)
|
||||
case MatchNotRegexp:
|
||||
return !m.re.MatchString(s)
|
||||
}
|
||||
panic("labels.Matcher.Matches: invalid match type")
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
// Copyright 2016 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package value
|
||||
|
||||
import (
|
||||
"math"
|
||||
)
|
||||
|
||||
const (
|
||||
// NormalNaN is a quiet NaN. This is also math.NaN().
|
||||
NormalNaN uint64 = 0x7ff8000000000001
|
||||
|
||||
// StaleNaN is a signaling NaN, due to the MSB of the mantissa being 0.
|
||||
// This value is chosen with many leading 0s, so we have scope to store more
|
||||
// complicated values in the future. It is 2 rather than 1 to make
|
||||
// it easier to distinguish from the NormalNaN by a human when debugging.
|
||||
StaleNaN uint64 = 0x7ff0000000000002
|
||||
)
|
||||
|
||||
// IsStaleNaN returns true when the provided NaN value is a stale marker.
|
||||
func IsStaleNaN(v float64) bool {
|
||||
return math.Float64bits(v) == StaleNaN
|
||||
}
|
||||
+198
@@ -0,0 +1,198 @@
|
||||
// Copyright 2015 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package promql
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/influxdata/promql/v2/pkg/labels"
|
||||
"github.com/prometheus/common/model"
|
||||
)
|
||||
|
||||
// Tree returns a string of the tree structure of the given node.
|
||||
func Tree(node Node) string {
|
||||
return tree(node, "")
|
||||
}
|
||||
|
||||
func tree(node Node, level string) string {
|
||||
if node == nil {
|
||||
return fmt.Sprintf("%s |---- %T\n", level, node)
|
||||
}
|
||||
typs := strings.Split(fmt.Sprintf("%T", node), ".")[1]
|
||||
|
||||
t := fmt.Sprintf("%s |---- %s :: %s\n", level, typs, node)
|
||||
|
||||
level += " · · ·"
|
||||
|
||||
switch n := node.(type) {
|
||||
case *EvalStmt:
|
||||
t += tree(n.Expr, level)
|
||||
|
||||
case Expressions:
|
||||
for _, e := range n {
|
||||
t += tree(e, level)
|
||||
}
|
||||
case *AggregateExpr:
|
||||
t += tree(n.Expr, level)
|
||||
|
||||
case *BinaryExpr:
|
||||
t += tree(n.LHS, level)
|
||||
t += tree(n.RHS, level)
|
||||
|
||||
case *Call:
|
||||
t += tree(n.Args, level)
|
||||
|
||||
case *ParenExpr:
|
||||
t += tree(n.Expr, level)
|
||||
|
||||
case *UnaryExpr:
|
||||
t += tree(n.Expr, level)
|
||||
|
||||
case *SubqueryExpr:
|
||||
t += tree(n.Expr, level)
|
||||
|
||||
case *MatrixSelector, *NumberLiteral, *StringLiteral, *VectorSelector:
|
||||
// nothing to do
|
||||
|
||||
default:
|
||||
panic("promql.Tree: not all node types covered")
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
func (node *EvalStmt) String() string {
|
||||
return "EVAL " + node.Expr.String()
|
||||
}
|
||||
|
||||
func (es Expressions) String() (s string) {
|
||||
if len(es) == 0 {
|
||||
return ""
|
||||
}
|
||||
for _, e := range es {
|
||||
s += e.String()
|
||||
s += ", "
|
||||
}
|
||||
return s[:len(s)-2]
|
||||
}
|
||||
|
||||
func (node *AggregateExpr) String() string {
|
||||
aggrString := node.Op.String()
|
||||
|
||||
if node.Without {
|
||||
aggrString += fmt.Sprintf(" without(%s) ", strings.Join(node.Grouping, ", "))
|
||||
} else {
|
||||
if len(node.Grouping) > 0 {
|
||||
aggrString += fmt.Sprintf(" by(%s) ", strings.Join(node.Grouping, ", "))
|
||||
}
|
||||
}
|
||||
|
||||
aggrString += "("
|
||||
if node.Op.isAggregatorWithParam() {
|
||||
aggrString += fmt.Sprintf("%s, ", node.Param)
|
||||
}
|
||||
aggrString += fmt.Sprintf("%s)", node.Expr)
|
||||
|
||||
return aggrString
|
||||
}
|
||||
|
||||
func (node *BinaryExpr) String() string {
|
||||
returnBool := ""
|
||||
if node.ReturnBool {
|
||||
returnBool = " bool"
|
||||
}
|
||||
|
||||
matching := ""
|
||||
vm := node.VectorMatching
|
||||
if vm != nil && (len(vm.MatchingLabels) > 0 || vm.On) {
|
||||
if vm.On {
|
||||
matching = fmt.Sprintf(" on(%s)", strings.Join(vm.MatchingLabels, ", "))
|
||||
} else {
|
||||
matching = fmt.Sprintf(" ignoring(%s)", strings.Join(vm.MatchingLabels, ", "))
|
||||
}
|
||||
if vm.Card == CardManyToOne || vm.Card == CardOneToMany {
|
||||
matching += " group_"
|
||||
if vm.Card == CardManyToOne {
|
||||
matching += "left"
|
||||
} else {
|
||||
matching += "right"
|
||||
}
|
||||
matching += fmt.Sprintf("(%s)", strings.Join(vm.Include, ", "))
|
||||
}
|
||||
}
|
||||
return fmt.Sprintf("%s %s%s%s %s", node.LHS, node.Op, returnBool, matching, node.RHS)
|
||||
}
|
||||
|
||||
func (node *Call) String() string {
|
||||
return fmt.Sprintf("%s(%s)", node.Func.Name, node.Args)
|
||||
}
|
||||
|
||||
func (node *MatrixSelector) String() string {
|
||||
vecSelector := &VectorSelector{
|
||||
Name: node.Name,
|
||||
LabelMatchers: node.LabelMatchers,
|
||||
}
|
||||
offset := ""
|
||||
if node.Offset != time.Duration(0) {
|
||||
offset = fmt.Sprintf(" offset %s", model.Duration(node.Offset))
|
||||
}
|
||||
return fmt.Sprintf("%s[%s]%s", vecSelector.String(), model.Duration(node.Range), offset)
|
||||
}
|
||||
|
||||
func (node *SubqueryExpr) String() string {
|
||||
step := ""
|
||||
if node.Step != 0 {
|
||||
step = model.Duration(node.Step).String()
|
||||
}
|
||||
return fmt.Sprintf("%s[%s:%s]", node.Expr.String(), model.Duration(node.Range), step)
|
||||
}
|
||||
|
||||
func (node *NumberLiteral) String() string {
|
||||
return fmt.Sprint(node.Val)
|
||||
}
|
||||
|
||||
func (node *ParenExpr) String() string {
|
||||
return fmt.Sprintf("(%s)", node.Expr)
|
||||
}
|
||||
|
||||
func (node *StringLiteral) String() string {
|
||||
return fmt.Sprintf("%q", node.Val)
|
||||
}
|
||||
|
||||
func (node *UnaryExpr) String() string {
|
||||
return fmt.Sprintf("%s%s", node.Op, node.Expr)
|
||||
}
|
||||
|
||||
func (node *VectorSelector) String() string {
|
||||
labelStrings := make([]string, 0, len(node.LabelMatchers)-1)
|
||||
for _, matcher := range node.LabelMatchers {
|
||||
// Only include the __name__ label if its equality matching and matches the name.
|
||||
if matcher.Name == labels.MetricName && matcher.Type == labels.MatchEqual && matcher.Value == node.Name {
|
||||
continue
|
||||
}
|
||||
labelStrings = append(labelStrings, matcher.String())
|
||||
}
|
||||
offset := ""
|
||||
if node.Offset != time.Duration(0) {
|
||||
offset = fmt.Sprintf(" offset %s", model.Duration(node.Offset))
|
||||
}
|
||||
|
||||
if len(labelStrings) == 0 {
|
||||
return fmt.Sprintf("%s%s", node.Name, offset)
|
||||
}
|
||||
sort.Strings(labelStrings)
|
||||
return fmt.Sprintf("%s{%s}%s", node.Name, strings.Join(labelStrings, ","), offset)
|
||||
}
|
||||
+223
@@ -0,0 +1,223 @@
|
||||
// Copyright 2015 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package strutil
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// ErrSyntax indicates that a value does not have the right syntax for the target type.
|
||||
var ErrSyntax = errors.New("invalid syntax")
|
||||
|
||||
// Unquote interprets s as a single-quoted, double-quoted, or backquoted
|
||||
// Prometheus query language string literal, returning the string value that s
|
||||
// quotes.
|
||||
//
|
||||
// NOTE: This function as well as the necessary helper functions below
|
||||
// (unquoteChar, contains, unhex) and associated tests have been adapted from
|
||||
// the corresponding functions in the "strconv" package of the Go standard
|
||||
// library to work for Prometheus-style strings. Go's special-casing for single
|
||||
// quotes was removed and single quoted strings are now treated the same as
|
||||
// double quoted ones.
|
||||
func Unquote(s string) (t string, err error) {
|
||||
n := len(s)
|
||||
if n < 2 {
|
||||
return "", ErrSyntax
|
||||
}
|
||||
quote := s[0]
|
||||
if quote != s[n-1] {
|
||||
return "", ErrSyntax
|
||||
}
|
||||
s = s[1 : n-1]
|
||||
|
||||
if quote == '`' {
|
||||
if contains(s, '`') {
|
||||
return "", ErrSyntax
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
if quote != '"' && quote != '\'' {
|
||||
return "", ErrSyntax
|
||||
}
|
||||
if contains(s, '\n') {
|
||||
return "", ErrSyntax
|
||||
}
|
||||
|
||||
// Is it trivial? Avoid allocation.
|
||||
if !contains(s, '\\') && !contains(s, quote) {
|
||||
return s, nil
|
||||
}
|
||||
|
||||
var runeTmp [utf8.UTFMax]byte
|
||||
buf := make([]byte, 0, 3*len(s)/2) // Try to avoid more allocations.
|
||||
for len(s) > 0 {
|
||||
c, multibyte, ss, err := unquoteChar(s, quote)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
s = ss
|
||||
if c < utf8.RuneSelf || !multibyte {
|
||||
buf = append(buf, byte(c))
|
||||
} else {
|
||||
n := utf8.EncodeRune(runeTmp[:], c)
|
||||
buf = append(buf, runeTmp[:n]...)
|
||||
}
|
||||
}
|
||||
return string(buf), nil
|
||||
}
|
||||
|
||||
// unquoteChar decodes the first character or byte in the escaped string
|
||||
// or character literal represented by the string s.
|
||||
// It returns four values:
|
||||
//
|
||||
// 1) value, the decoded Unicode code point or byte value;
|
||||
// 2) multibyte, a boolean indicating whether the decoded character requires a multibyte UTF-8 representation;
|
||||
// 3) tail, the remainder of the string after the character; and
|
||||
// 4) an error that will be nil if the character is syntactically valid.
|
||||
//
|
||||
// The second argument, quote, specifies the type of literal being parsed
|
||||
// and therefore which escaped quote character is permitted.
|
||||
// If set to a single quote, it permits the sequence \' and disallows unescaped '.
|
||||
// If set to a double quote, it permits \" and disallows unescaped ".
|
||||
// If set to zero, it does not permit either escape and allows both quote characters to appear unescaped.
|
||||
func unquoteChar(s string, quote byte) (value rune, multibyte bool, tail string, err error) {
|
||||
// easy cases
|
||||
switch c := s[0]; {
|
||||
case c == quote && (quote == '\'' || quote == '"'):
|
||||
err = ErrSyntax
|
||||
return
|
||||
case c >= utf8.RuneSelf:
|
||||
r, size := utf8.DecodeRuneInString(s)
|
||||
return r, true, s[size:], nil
|
||||
case c != '\\':
|
||||
return rune(s[0]), false, s[1:], nil
|
||||
}
|
||||
|
||||
// Hard case: c is backslash.
|
||||
if len(s) <= 1 {
|
||||
err = ErrSyntax
|
||||
return
|
||||
}
|
||||
c := s[1]
|
||||
s = s[2:]
|
||||
|
||||
switch c {
|
||||
case 'a':
|
||||
value = '\a'
|
||||
case 'b':
|
||||
value = '\b'
|
||||
case 'f':
|
||||
value = '\f'
|
||||
case 'n':
|
||||
value = '\n'
|
||||
case 'r':
|
||||
value = '\r'
|
||||
case 't':
|
||||
value = '\t'
|
||||
case 'v':
|
||||
value = '\v'
|
||||
case 'x', 'u', 'U':
|
||||
n := 0
|
||||
switch c {
|
||||
case 'x':
|
||||
n = 2
|
||||
case 'u':
|
||||
n = 4
|
||||
case 'U':
|
||||
n = 8
|
||||
}
|
||||
var v rune
|
||||
if len(s) < n {
|
||||
err = ErrSyntax
|
||||
return
|
||||
}
|
||||
for j := 0; j < n; j++ {
|
||||
x, ok := unhex(s[j])
|
||||
if !ok {
|
||||
err = ErrSyntax
|
||||
return
|
||||
}
|
||||
v = v<<4 | x
|
||||
}
|
||||
s = s[n:]
|
||||
if c == 'x' {
|
||||
// Single-byte string, possibly not UTF-8.
|
||||
value = v
|
||||
break
|
||||
}
|
||||
if v > utf8.MaxRune {
|
||||
err = ErrSyntax
|
||||
return
|
||||
}
|
||||
value = v
|
||||
multibyte = true
|
||||
case '0', '1', '2', '3', '4', '5', '6', '7':
|
||||
v := rune(c) - '0'
|
||||
if len(s) < 2 {
|
||||
err = ErrSyntax
|
||||
return
|
||||
}
|
||||
for j := 0; j < 2; j++ { // One digit already; two more.
|
||||
x := rune(s[j]) - '0'
|
||||
if x < 0 || x > 7 {
|
||||
err = ErrSyntax
|
||||
return
|
||||
}
|
||||
v = (v << 3) | x
|
||||
}
|
||||
s = s[2:]
|
||||
if v > 255 {
|
||||
err = ErrSyntax
|
||||
return
|
||||
}
|
||||
value = v
|
||||
case '\\':
|
||||
value = '\\'
|
||||
case '\'', '"':
|
||||
if c != quote {
|
||||
err = ErrSyntax
|
||||
return
|
||||
}
|
||||
value = rune(c)
|
||||
default:
|
||||
err = ErrSyntax
|
||||
return
|
||||
}
|
||||
tail = s
|
||||
return
|
||||
}
|
||||
|
||||
// contains reports whether the string contains the byte c.
|
||||
func contains(s string, c byte) bool {
|
||||
for i := 0; i < len(s); i++ {
|
||||
if s[i] == c {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func unhex(b byte) (v rune, ok bool) {
|
||||
c := rune(b)
|
||||
switch {
|
||||
case '0' <= c && c <= '9':
|
||||
return c - '0', true
|
||||
case 'a' <= c && c <= 'f':
|
||||
return c - 'a' + 10, true
|
||||
case 'A' <= c && c <= 'F':
|
||||
return c - 'A' + 10, true
|
||||
}
|
||||
return
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
// Copyright 2013 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package strutil
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
var (
|
||||
invalidLabelCharRE = regexp.MustCompile(`[^a-zA-Z0-9_]`)
|
||||
)
|
||||
|
||||
// TableLinkForExpression creates an escaped relative link to the table view of
|
||||
// the provided expression.
|
||||
func TableLinkForExpression(expr string) string {
|
||||
escapedExpression := url.QueryEscape(expr)
|
||||
return fmt.Sprintf("/graph?g0.expr=%s&g0.tab=1", escapedExpression)
|
||||
}
|
||||
|
||||
// GraphLinkForExpression creates an escaped relative link to the graph view of
|
||||
// the provided expression.
|
||||
func GraphLinkForExpression(expr string) string {
|
||||
escapedExpression := url.QueryEscape(expr)
|
||||
return fmt.Sprintf("/graph?g0.expr=%s&g0.tab=0", escapedExpression)
|
||||
}
|
||||
|
||||
// SanitizeLabelName replaces anything that doesn't match
|
||||
// client_label.LabelNameRE with an underscore.
|
||||
func SanitizeLabelName(name string) string {
|
||||
return invalidLabelCharRE.ReplaceAllString(name, "_")
|
||||
}
|
||||
+199
@@ -0,0 +1,199 @@
|
||||
// Copyright 2017 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package promql
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/influxdata/promql/v2/pkg/labels"
|
||||
)
|
||||
|
||||
// Value is a generic interface for values resulting from a query evaluation.
|
||||
type Value interface {
|
||||
Type() ValueType
|
||||
String() string
|
||||
}
|
||||
|
||||
func (Matrix) Type() ValueType { return ValueTypeMatrix }
|
||||
func (Vector) Type() ValueType { return ValueTypeVector }
|
||||
func (Scalar) Type() ValueType { return ValueTypeScalar }
|
||||
func (String) Type() ValueType { return ValueTypeString }
|
||||
|
||||
// ValueType describes a type of a value.
|
||||
type ValueType string
|
||||
|
||||
// The valid value types.
|
||||
const (
|
||||
ValueTypeNone = "none"
|
||||
ValueTypeVector = "vector"
|
||||
ValueTypeScalar = "scalar"
|
||||
ValueTypeMatrix = "matrix"
|
||||
ValueTypeString = "string"
|
||||
)
|
||||
|
||||
// String represents a string value.
|
||||
type String struct {
|
||||
T int64
|
||||
V string
|
||||
}
|
||||
|
||||
func (s String) String() string {
|
||||
return s.V
|
||||
}
|
||||
|
||||
func (s String) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal([...]interface{}{float64(s.T) / 1000, s.V})
|
||||
}
|
||||
|
||||
// Scalar is a data point that's explicitly not associated with a metric.
|
||||
type Scalar struct {
|
||||
T int64
|
||||
V float64
|
||||
}
|
||||
|
||||
func (s Scalar) String() string {
|
||||
v := strconv.FormatFloat(s.V, 'f', -1, 64)
|
||||
return fmt.Sprintf("scalar: %v @[%v]", v, s.T)
|
||||
}
|
||||
|
||||
func (s Scalar) MarshalJSON() ([]byte, error) {
|
||||
v := strconv.FormatFloat(s.V, 'f', -1, 64)
|
||||
return json.Marshal([...]interface{}{float64(s.T) / 1000, v})
|
||||
}
|
||||
|
||||
// Series is a stream of data points belonging to a metric.
|
||||
type Series struct {
|
||||
Metric labels.Labels `json:"metric"`
|
||||
Points []Point `json:"values"`
|
||||
}
|
||||
|
||||
func (s Series) String() string {
|
||||
vals := make([]string, len(s.Points))
|
||||
for i, v := range s.Points {
|
||||
vals[i] = v.String()
|
||||
}
|
||||
return fmt.Sprintf("%s =>\n%s", s.Metric, strings.Join(vals, "\n"))
|
||||
}
|
||||
|
||||
// Point represents a single data point for a given timestamp.
|
||||
type Point struct {
|
||||
T int64
|
||||
V float64
|
||||
}
|
||||
|
||||
func (p Point) String() string {
|
||||
v := strconv.FormatFloat(p.V, 'f', -1, 64)
|
||||
return fmt.Sprintf("%v @[%v]", v, p.T)
|
||||
}
|
||||
|
||||
// MarshalJSON implements json.Marshaler.
|
||||
func (p Point) MarshalJSON() ([]byte, error) {
|
||||
v := strconv.FormatFloat(p.V, 'f', -1, 64)
|
||||
return json.Marshal([...]interface{}{float64(p.T) / 1000, v})
|
||||
}
|
||||
|
||||
// Sample is a single sample belonging to a metric.
|
||||
type Sample struct {
|
||||
Point
|
||||
|
||||
Metric labels.Labels
|
||||
}
|
||||
|
||||
func (s Sample) String() string {
|
||||
return fmt.Sprintf("%s => %s", s.Metric, s.Point)
|
||||
}
|
||||
|
||||
func (s Sample) MarshalJSON() ([]byte, error) {
|
||||
v := struct {
|
||||
M labels.Labels `json:"metric"`
|
||||
V Point `json:"value"`
|
||||
}{
|
||||
M: s.Metric,
|
||||
V: s.Point,
|
||||
}
|
||||
return json.Marshal(v)
|
||||
}
|
||||
|
||||
// Vector is basically only an alias for model.Samples, but the
|
||||
// contract is that in a Vector, all Samples have the same timestamp.
|
||||
type Vector []Sample
|
||||
|
||||
func (vec Vector) String() string {
|
||||
entries := make([]string, len(vec))
|
||||
for i, s := range vec {
|
||||
entries[i] = s.String()
|
||||
}
|
||||
return strings.Join(entries, "\n")
|
||||
}
|
||||
|
||||
// ContainsSameLabelset checks if a vector has samples with the same labelset
|
||||
// Such a behavior is semantically undefined
|
||||
// https://github.com/prometheus/prometheus/issues/4562
|
||||
func (vec Vector) ContainsSameLabelset() bool {
|
||||
l := make(map[uint64]struct{}, len(vec))
|
||||
for _, s := range vec {
|
||||
hash := s.Metric.Hash()
|
||||
if _, ok := l[hash]; ok {
|
||||
return true
|
||||
}
|
||||
l[hash] = struct{}{}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Matrix is a slice of Series that implements sort.Interface and
|
||||
// has a String method.
|
||||
type Matrix []Series
|
||||
|
||||
func (m Matrix) String() string {
|
||||
// TODO(fabxc): sort, or can we rely on order from the querier?
|
||||
strs := make([]string, len(m))
|
||||
|
||||
for i, ss := range m {
|
||||
strs[i] = ss.String()
|
||||
}
|
||||
|
||||
return strings.Join(strs, "\n")
|
||||
}
|
||||
|
||||
// TotalSamples returns the total number of samples in the series within a matrix.
|
||||
func (m Matrix) TotalSamples() int {
|
||||
numSamples := 0
|
||||
for _, series := range m {
|
||||
numSamples += len(series.Points)
|
||||
}
|
||||
return numSamples
|
||||
}
|
||||
|
||||
func (m Matrix) Len() int { return len(m) }
|
||||
func (m Matrix) Less(i, j int) bool { return labels.Compare(m[i].Metric, m[j].Metric) < 0 }
|
||||
func (m Matrix) Swap(i, j int) { m[i], m[j] = m[j], m[i] }
|
||||
|
||||
// ContainsSameLabelset checks if a matrix has samples with the same labelset
|
||||
// Such a behavior is semantically undefined
|
||||
// https://github.com/prometheus/prometheus/issues/4562
|
||||
func (m Matrix) ContainsSameLabelset() bool {
|
||||
l := make(map[uint64]struct{}, len(m))
|
||||
for _, ss := range m {
|
||||
hash := ss.Metric.Hash()
|
||||
if _, ok := l[hash]; ok {
|
||||
return true
|
||||
}
|
||||
l[hash] = struct{}{}
|
||||
}
|
||||
return false
|
||||
}
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
package converter
|
||||
|
||||
import (
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/influxdata/influxql"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/zexi/influxql-to-promql/converter/translator"
|
||||
)
|
||||
|
||||
type Converter interface {
|
||||
Translate() (string, error)
|
||||
TranslateWithTimeRange() (string, *influxql.TimeRange, error)
|
||||
}
|
||||
|
||||
type converter struct {
|
||||
influxParser *influxql.Parser
|
||||
translator translator.Translator
|
||||
}
|
||||
|
||||
func Translate(influxQL string) (string, error) {
|
||||
return New(strings.NewReader(influxQL)).Translate()
|
||||
}
|
||||
|
||||
func TranslateWithTimeRange(influxQL string) (string, *influxql.TimeRange, error) {
|
||||
return New(strings.NewReader(influxQL)).TranslateWithTimeRange()
|
||||
}
|
||||
|
||||
func New(r io.Reader) Converter {
|
||||
c := &converter{
|
||||
influxParser: influxql.NewParser(r),
|
||||
translator: translator.NewPromQL(),
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
func (c converter) Translate() (string, error) {
|
||||
q, err := c.influxParser.ParseQuery()
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "influxParser.ParserQuery")
|
||||
}
|
||||
if len(q.Statements) > 1 {
|
||||
return "", errors.Errorf("Only support 1 statement translating")
|
||||
}
|
||||
return c.translator.Translate(q.Statements[0])
|
||||
}
|
||||
|
||||
func (c converter) TranslateWithTimeRange() (string, *influxql.TimeRange, error) {
|
||||
promQL, err := c.Translate()
|
||||
if err != nil {
|
||||
return "", nil, errors.Wrap(err, "Translate")
|
||||
}
|
||||
return promQL, c.translator.GetTimeRange(), nil
|
||||
}
|
||||
+521
@@ -0,0 +1,521 @@
|
||||
package translator
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/influxdata/influxql"
|
||||
"github.com/influxdata/promql/v2"
|
||||
"github.com/influxdata/promql/v2/pkg/labels"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prometheus/common/model"
|
||||
)
|
||||
|
||||
const UNION_RESULT_NAME = "__union_result__"
|
||||
|
||||
type promQL struct {
|
||||
groupByWildcard bool
|
||||
timeRange *influxql.TimeRange
|
||||
fieldIsWildcard bool
|
||||
measurement string
|
||||
}
|
||||
|
||||
func NewPromQL() Translator {
|
||||
return &promQL{}
|
||||
}
|
||||
|
||||
func (m *promQL) Translate(s influxql.Statement) (string, error) {
|
||||
selectS, ok := s.(*influxql.SelectStatement)
|
||||
if !ok {
|
||||
return "", errors.Errorf("Only SelectStatement is supported, input %t", s)
|
||||
}
|
||||
return m.translate(selectS)
|
||||
}
|
||||
|
||||
func (m *promQL) GetTimeRange() *influxql.TimeRange {
|
||||
return m.timeRange
|
||||
}
|
||||
|
||||
type fieldResult struct {
|
||||
metricName string
|
||||
aggrOps []string
|
||||
expr promql.Expr
|
||||
}
|
||||
|
||||
func newFieldResult(metricName string, ops []string, expr promql.Expr) *fieldResult {
|
||||
return &fieldResult{
|
||||
metricName: metricName,
|
||||
aggrOps: ops,
|
||||
expr: expr,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *promQL) translateField(s *influxql.SelectStatement, field *influxql.Field) (*fieldResult, error) {
|
||||
metricName, err := getMetricName(s.Sources, field)
|
||||
if err != nil {
|
||||
if errors.Cause(err) == ErrVariableIsWildcard {
|
||||
m.measurement = metricName
|
||||
m.fieldIsWildcard = true
|
||||
} else {
|
||||
return nil, errors.Wrap(err, "getMetricName")
|
||||
}
|
||||
}
|
||||
|
||||
aggrOps, err := getAggrOperators(field)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "get field aggregate operator")
|
||||
}
|
||||
cond, timeRange, err := getTimeRange(s.Condition)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "getTimeRange")
|
||||
}
|
||||
m.timeRange = timeRange
|
||||
|
||||
matchers, err := m.getLabels(cond)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "get matchers")
|
||||
}
|
||||
if !m.fieldIsWildcard {
|
||||
nameMatcher, _ := labels.NewMatcher(labels.MatchEqual, labels.MetricName, metricName)
|
||||
matchers = append(matchers, nameMatcher)
|
||||
}
|
||||
|
||||
lookbehindWin, groups, err := m.getGroups(s.Dimensions)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "get groups")
|
||||
}
|
||||
//interval, err := s.GroupByInterval()
|
||||
//if err != nil {
|
||||
// return "", errors.Wrap(err, "GroupByInterval")
|
||||
//}
|
||||
//fmt.Printf("==get interval: %#v\n", interval)
|
||||
|
||||
expr, err := m.generateExpr(metricName, matchers, lookbehindWin, aggrOps, groups)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "generate expression")
|
||||
}
|
||||
return newFieldResult(metricName, aggrOps, expr), nil
|
||||
}
|
||||
|
||||
func (m *promQL) translate(s *influxql.SelectStatement) (string, error) {
|
||||
exprs := make([]*fieldResult, 0)
|
||||
var resultExpr promql.Expr
|
||||
for _, field := range s.Fields {
|
||||
expr, err := m.translateField(s, field)
|
||||
if err != nil {
|
||||
return "", errors.Wrapf(err, "translate field %s", field)
|
||||
}
|
||||
exprs = append(exprs, expr)
|
||||
}
|
||||
|
||||
if len(exprs) == 1 {
|
||||
resultExpr = exprs[0].expr
|
||||
} else {
|
||||
// union field expr
|
||||
resultExpr = unionFieldsExpr(exprs)
|
||||
}
|
||||
|
||||
return m.formatExpr(resultExpr), nil
|
||||
}
|
||||
|
||||
func unionFieldsExpr(exprs []*fieldResult) promql.Expr {
|
||||
result := make([]promql.Expr, len(exprs))
|
||||
// 1. wrap each expr with label_set: https://docs.victoriametrics.com/MetricsQL.html#label_set
|
||||
setKey := UNION_RESULT_NAME
|
||||
for i := range exprs {
|
||||
expr := exprs[i]
|
||||
setValue := expr.metricName
|
||||
if len(expr.aggrOps) > 0 {
|
||||
setValue = fmt.Sprintf("%s_%s", strings.Join(expr.aggrOps, "_"), expr.metricName)
|
||||
}
|
||||
result[i] = &promql.Call{
|
||||
Func: &promql.Function{
|
||||
Name: "label_set",
|
||||
ArgTypes: []promql.ValueType{promql.ValueTypeVector, promql.ValueTypeString, promql.ValueTypeString},
|
||||
Variadic: 1,
|
||||
ReturnType: promql.ValueTypeVector,
|
||||
},
|
||||
Args: promql.Expressions{
|
||||
expr.expr,
|
||||
&promql.StringLiteral{setKey},
|
||||
&promql.StringLiteral{setValue},
|
||||
},
|
||||
}
|
||||
}
|
||||
// 2. use union: https://docs.victoriametrics.com/MetricsQL.html#union
|
||||
return &promql.Call{
|
||||
Func: &promql.Function{
|
||||
Name: "union",
|
||||
Variadic: 1,
|
||||
ReturnType: promql.ValueTypeVector,
|
||||
},
|
||||
Args: result,
|
||||
}
|
||||
}
|
||||
|
||||
func getTimeRange(cond influxql.Expr) (influxql.Expr, *influxql.TimeRange, error) {
|
||||
// parse time range
|
||||
//mustParseTime := func(value string) time.Time {
|
||||
// ts, err := time.Parse(time.RFC3339, value)
|
||||
// if err != nil {
|
||||
// panic(fmt.Errorf("unable to parse time: %s", err))
|
||||
// }
|
||||
// return ts
|
||||
//}
|
||||
//now := mustParseTime("2000-01-01T00:00:00Z")
|
||||
valuer := influxql.NowValuer{
|
||||
Now: time.Now(),
|
||||
Location: time.UTC,
|
||||
}
|
||||
cond, timeRange, err := influxql.ConditionExpr(cond, &valuer)
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrapf(err, "parse time range from %q", cond)
|
||||
}
|
||||
if timeRange.IsZero() {
|
||||
return cond, nil, nil
|
||||
}
|
||||
// process maxTime
|
||||
if !timeRange.MaxTime().IsZero() {
|
||||
year, month, day := timeRange.Max.Date()
|
||||
// FIX: time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC)
|
||||
if year == 1 && month == 1 && day == 1 {
|
||||
timeRange.Max = time.Now()
|
||||
}
|
||||
}
|
||||
return cond, &timeRange, nil
|
||||
}
|
||||
|
||||
func (m promQL) generateExpr(
|
||||
metricName string,
|
||||
ls []*labels.Matcher,
|
||||
lookbehindWindow string,
|
||||
aggrOps []string,
|
||||
groups []string) (promql.Expr, error) {
|
||||
//fmt.Printf("=====name: %s, labels: %#v, lookbehindWindow: %q, aggrOps: %#v, groups: %#v\n", metricName, ls, lookbehindWindow, aggrOps, groups)
|
||||
for _, l := range ls {
|
||||
fmt.Printf("label: %s\n", l.String())
|
||||
}
|
||||
|
||||
if m.fieldIsWildcard {
|
||||
measurementM, _ := labels.NewMatcher(labels.MatchRegexp, labels.MetricName, fmt.Sprintf("^%s_.*", m.measurement))
|
||||
ls = append(ls, measurementM)
|
||||
}
|
||||
|
||||
var result promql.Expr
|
||||
if len(aggrOps) != 0 {
|
||||
if lookbehindWindow == "" {
|
||||
lookbehindWindow = "1m"
|
||||
}
|
||||
dur, err := model.ParseDuration(lookbehindWindow)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "ParseDuration: %q", lookbehindWindow)
|
||||
}
|
||||
ms := &promql.MatrixSelector{
|
||||
LabelMatchers: ls,
|
||||
Range: time.Duration(dur),
|
||||
}
|
||||
if !m.fieldIsWildcard {
|
||||
ms.Name = metricName
|
||||
}
|
||||
result = ms
|
||||
} else {
|
||||
vs := &promql.VectorSelector{
|
||||
LabelMatchers: ls,
|
||||
}
|
||||
if !m.fieldIsWildcard {
|
||||
vs.Name = metricName
|
||||
}
|
||||
result = vs
|
||||
}
|
||||
|
||||
if len(groups) != 0 && len(aggrOps) == 0 {
|
||||
return nil, errors.Errorf("Can't use group by when aggregate operator is empty")
|
||||
}
|
||||
|
||||
result = getAggrExpr(aggrOps, result)
|
||||
|
||||
//fmt.Printf("=====m.GroupByWildcard: %v, %#v\n", m.groupByWildcard, result)
|
||||
|
||||
if len(groups) != 0 && !m.groupByWildcard {
|
||||
expr := &promql.AggregateExpr{
|
||||
Op: promql.ItemAvg,
|
||||
Expr: result,
|
||||
}
|
||||
if len(groups) != 0 {
|
||||
expr.Grouping = groups
|
||||
}
|
||||
result = expr
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (m promQL) formatExpr(expr promql.Expr) string {
|
||||
return expr.String()
|
||||
}
|
||||
|
||||
func newAggrExpr(name string, argType promql.ValueType, returnType promql.ValueType, restExpr promql.Expr) promql.Expr {
|
||||
return &promql.Call{
|
||||
Func: &promql.Function{
|
||||
Name: name,
|
||||
ArgTypes: []promql.ValueType{argType},
|
||||
Variadic: 0,
|
||||
ReturnType: returnType,
|
||||
},
|
||||
Args: promql.Expressions{restExpr},
|
||||
}
|
||||
}
|
||||
|
||||
func getAggrExpr(ops []string, expr promql.Expr) promql.Expr {
|
||||
if len(ops) == 0 {
|
||||
return expr
|
||||
}
|
||||
aggrOp := ops[0]
|
||||
restOps := ops[1:]
|
||||
restExpr := getAggrExpr(restOps, expr)
|
||||
switch aggrOp {
|
||||
case "abs":
|
||||
// https://prometheus.io/docs/prometheus/latest/querying/functions/#abs
|
||||
expr = newAggrExpr("abs", promql.ValueTypeVector, promql.ValueTypeVector, restExpr)
|
||||
case "sum":
|
||||
expr = newAggrExpr("sum", promql.ValueTypeVector, promql.ValueTypeVector, restExpr)
|
||||
case "mean":
|
||||
// https://docs.victoriametrics.com/MetricsQL.html#avg_over_time
|
||||
expr = newAggrExpr("avg_over_time", promql.ValueTypeMatrix, promql.ValueTypeVector, restExpr)
|
||||
case "last":
|
||||
// https://docs.victoriametrics.com/MetricsQL.html#last_over_time
|
||||
expr = newAggrExpr("last_over_time", promql.ValueTypeMatrix, promql.ValueTypeVector, restExpr)
|
||||
case "count":
|
||||
// use count, not use 'count_over_time' https://docs.victoriametrics.com/MetricsQL.html#count_over_time
|
||||
expr = newAggrExpr("count", promql.ValueTypeMatrix, promql.ValueTypeVector, restExpr)
|
||||
case "stddev":
|
||||
// https://prometheus.io/docs/prometheus/latest/querying/functions/#aggregation_over_time
|
||||
expr = newAggrExpr("stddev_over_time", promql.ValueTypeMatrix, promql.ValueTypeVector, restExpr)
|
||||
case "median":
|
||||
// https://docs.victoriametrics.com/MetricsQL.html#median_over_time
|
||||
expr = newAggrExpr("median_over_time", promql.ValueTypeMatrix, promql.ValueTypeVector, restExpr)
|
||||
case "max":
|
||||
// https://docs.victoriametrics.com/MetricsQL.html#max_over_time
|
||||
expr = newAggrExpr("max_over_time", promql.ValueTypeMatrix, promql.ValueTypeVector, restExpr)
|
||||
case "min":
|
||||
// https://docs.victoriametrics.com/MetricsQL.html#min_over_time
|
||||
expr = newAggrExpr("min_over_time", promql.ValueTypeMatrix, promql.ValueTypeVector, restExpr)
|
||||
case "mode":
|
||||
// https://docs.victoriametrics.com/MetricsQL.html#mode_over_time
|
||||
expr = newAggrExpr("mode_over_time", promql.ValueTypeMatrix, promql.ValueTypeVector, restExpr)
|
||||
case "integral":
|
||||
// https://docs.victoriametrics.com/MetricsQL.html#integrate
|
||||
expr = newAggrExpr("integrate", promql.ValueTypeMatrix, promql.ValueTypeVector, restExpr)
|
||||
case "distinct":
|
||||
expr = newAggrExpr("distinct", promql.ValueTypeMatrix, promql.ValueTypeVector, restExpr)
|
||||
}
|
||||
return expr
|
||||
}
|
||||
|
||||
func getAggrOperator(op *influxql.Call) ([]string, error) {
|
||||
if len(op.Args) != 1 {
|
||||
return nil, errors.Errorf("not supported operator: %s with args: %#v", op.String(), op.Args)
|
||||
}
|
||||
ret := []string{op.Name}
|
||||
args, ok := op.Args[0].(*influxql.Call)
|
||||
if !ok {
|
||||
return ret, nil
|
||||
}
|
||||
rest, err := getAggrOperator(args)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "get rest aggregate operator: %s", args.String())
|
||||
}
|
||||
ret = append(ret, rest...)
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func getAggrOperators(field *influxql.Field) ([]string, error) {
|
||||
aggrOp, ok := field.Expr.(*influxql.Call)
|
||||
if !ok {
|
||||
return nil, nil
|
||||
}
|
||||
return getAggrOperator(aggrOp)
|
||||
}
|
||||
|
||||
func getMetricName(sources influxql.Sources, field *influxql.Field) (string, error) {
|
||||
if len(sources) != 1 {
|
||||
return "", errors.Errorf("sources %#v length doesn't equal 1", sources)
|
||||
}
|
||||
src := sources[0]
|
||||
measurement, ok := src.(*influxql.Measurement)
|
||||
if !ok {
|
||||
return "", errors.Errorf("source %#v is not measurement type", src)
|
||||
}
|
||||
|
||||
var (
|
||||
fieldName string
|
||||
err error
|
||||
)
|
||||
|
||||
switch expr := field.Expr.(type) {
|
||||
case *influxql.VarRef:
|
||||
fieldName = expr.Val
|
||||
case *influxql.Call:
|
||||
fieldName, err = getCallVariable(expr)
|
||||
default:
|
||||
return "", errors.Errorf("field.Expr %#v is not supported", expr)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return measurement.Name, err
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s_%s", measurement.Name, fieldName), nil
|
||||
}
|
||||
|
||||
var (
|
||||
ErrVariableIsWildcard = errors.New("variable field is wildcard")
|
||||
)
|
||||
|
||||
func getCallVariable(c *influxql.Call) (string, error) {
|
||||
if len(c.Args) != 1 {
|
||||
return "", errors.Errorf("length of args %#v != 1", c.Args)
|
||||
}
|
||||
switch args := c.Args[0].(type) {
|
||||
case *influxql.VarRef:
|
||||
return args.Val, nil
|
||||
case *influxql.Wildcard:
|
||||
return "", ErrVariableIsWildcard
|
||||
case *influxql.Call:
|
||||
return getCallVariable(args)
|
||||
default:
|
||||
return "", errors.Errorf("unsupported args %#v", args)
|
||||
}
|
||||
return c.Args[0].String(), nil
|
||||
}
|
||||
|
||||
type labelsVisitor struct {
|
||||
err error
|
||||
labels []*labels.Matcher
|
||||
curKey string
|
||||
curOp influxql.Token
|
||||
curVal string
|
||||
}
|
||||
|
||||
func newLabelsVisitor() *labelsVisitor {
|
||||
return &labelsVisitor{
|
||||
err: nil,
|
||||
labels: make([]*labels.Matcher, 0),
|
||||
}
|
||||
}
|
||||
|
||||
func (l *labelsVisitor) Error() error {
|
||||
return l.err
|
||||
}
|
||||
|
||||
func (l *labelsVisitor) Labels() []*labels.Matcher {
|
||||
return l.labels
|
||||
}
|
||||
|
||||
func (l *labelsVisitor) commitLabel() error {
|
||||
if l.err != nil {
|
||||
return l.err
|
||||
}
|
||||
var (
|
||||
label *labels.Matcher
|
||||
err error
|
||||
)
|
||||
var promOP labels.MatchType
|
||||
switch l.curOp {
|
||||
case influxql.EQ:
|
||||
promOP = labels.MatchEqual
|
||||
case influxql.NEQ:
|
||||
promOP = labels.MatchNotEqual
|
||||
case influxql.EQREGEX:
|
||||
promOP = labels.MatchRegexp
|
||||
case influxql.NEQREGEX:
|
||||
promOP = labels.MatchNotRegexp
|
||||
default:
|
||||
return errors.Errorf("Not suport influxdb operator: %s", l.curOp)
|
||||
}
|
||||
label, err = labels.NewMatcher(promOP, l.curKey, l.curVal)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "not supported operator: %q", l.curOp)
|
||||
}
|
||||
|
||||
l.labels = append(l.labels, label)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *labelsVisitor) Visit(node influxql.Node) influxql.Visitor {
|
||||
//fmt.Printf("-- visit: %#v\n", node)
|
||||
if l.err != nil {
|
||||
log.Printf("error happend: %v, visting skipped", l.err)
|
||||
return l
|
||||
}
|
||||
switch expr := node.(type) {
|
||||
case *influxql.BinaryExpr:
|
||||
if expr.Op == influxql.OR {
|
||||
l.err = errors.Errorf("%#v: OR is not suported yet.", expr)
|
||||
return l
|
||||
}
|
||||
l.curOp = expr.Op
|
||||
case *influxql.VarRef:
|
||||
l.curKey = expr.Val
|
||||
case *influxql.StringLiteral:
|
||||
l.curVal = expr.Val
|
||||
if err := l.commitLabel(); err != nil {
|
||||
l.err = err
|
||||
}
|
||||
case *influxql.RegexLiteral:
|
||||
l.curVal = expr.Val.String()
|
||||
if err := l.commitLabel(); err != nil {
|
||||
l.err = err
|
||||
}
|
||||
}
|
||||
return l
|
||||
}
|
||||
|
||||
func (m promQL) getLabels(cond influxql.Expr) ([]*labels.Matcher, error) {
|
||||
if cond == nil {
|
||||
return nil, nil
|
||||
}
|
||||
v := newLabelsVisitor()
|
||||
influxql.Walk(v, cond)
|
||||
return v.Labels(), v.Error()
|
||||
}
|
||||
|
||||
func (m *promQL) getGroups(groups influxql.Dimensions) (string, []string, error) {
|
||||
result := []string{}
|
||||
var (
|
||||
lookbehindWindow string
|
||||
)
|
||||
for _, group := range groups {
|
||||
tmpWin, grp, err := m.getGroup(group)
|
||||
if err != nil {
|
||||
return "", result, errors.Wrapf(err, "getGroup %q", group)
|
||||
}
|
||||
if tmpWin != "" {
|
||||
lookbehindWindow = tmpWin
|
||||
}
|
||||
if grp != "" {
|
||||
result = append(result, grp)
|
||||
}
|
||||
}
|
||||
return lookbehindWindow, result, nil
|
||||
}
|
||||
|
||||
func (m *promQL) getGroup(group *influxql.Dimension) (string, string, error) {
|
||||
//fmt.Printf("---try group: %#v\n", group)
|
||||
grp := group.Expr
|
||||
lookbehindWindow := ""
|
||||
switch expr := grp.(type) {
|
||||
case *influxql.Call:
|
||||
if expr.Name == "time" {
|
||||
lookbehindWindow = expr.Args[0].String()
|
||||
}
|
||||
return lookbehindWindow, "", nil
|
||||
case *influxql.VarRef:
|
||||
return "", expr.Val, nil
|
||||
case *influxql.Wildcard:
|
||||
m.groupByWildcard = true
|
||||
return "", "", nil
|
||||
}
|
||||
return "", "", errors.Errorf("not support %q", group.String())
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user