feat(monitor,region,host,cloudmon): support VictoriaMetrics as TSDB backend

This commit is contained in:
Zexi Li
2023-10-31 20:39:57 +08:00
parent 698937280a
commit 4f96f2e5fa
102 changed files with 18574 additions and 436 deletions
+1
View File
@@ -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",
}) {
+4
View File
@@ -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
+11
View File
@@ -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
View File
@@ -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,
}
)
+3 -4
View File
@@ -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 {
+8
View File
@@ -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
)
-4
View File
@@ -14,10 +14,6 @@
package monitor
const (
DataSourceTypeInfluxdb = "influxdb"
)
type DataSourceConfig struct {
Id string
Name string
+6 -5
View File
@@ -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
+1
View File
@@ -0,0 +1 @@
package tsdb // import "yunion.io/x/onecloud/pkg/cloudcommon/tsdb"
+55
View File
@@ -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
}
+2 -2
View File
@@ -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")
}
+2 -2
View File
@@ -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")
}
+2 -1
View File
@@ -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")
}
+3 -3
View File
@@ -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")
}
+2 -2
View File
@@ -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")
}
+2 -2
View File
@@ -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
+4 -3
View File
@@ -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 {
+1 -1
View File
@@ -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")
+2 -2
View File
@@ -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
}
+4 -2
View File
@@ -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
+7 -7
View File
@@ -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
}
}
}
+18 -7
View File
@@ -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{
+3 -2
View File
@@ -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
+8 -3
View File
@@ -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)
+1 -1
View File
@@ -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
+7 -1
View File
@@ -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")
}
+12 -2
View File
@@ -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"
}
+2 -1
View File
@@ -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,
},
+14
View File
@@ -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
+4
View File
@@ -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:"-"`
}
+1 -1
View File
@@ -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 {
+18 -12
View File
@@ -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{
+14 -14
View File
@@ -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)
+1 -1
View File
@@ -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
+2 -2
View File
@@ -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 {
+4 -7
View File
@@ -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 {
+156
View File
@@ -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)
}
+1
View File
@@ -0,0 +1 @@
package datasource // import "yunion.io/x/onecloud/pkg/monitor/datasource"
+6 -13
View File
@@ -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
}
+32 -170
View File
@@ -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{
+8 -24
View File
@@ -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
}
+1 -2
View File
@@ -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
+2 -7
View File
@@ -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
}
+28 -69
View File
@@ -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 rownameThe 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")
}
+5 -5
View File
@@ -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 {
+2 -2
View File
@@ -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`)
})
}
+60 -7
View File
@@ -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())
}
*/
+67 -4
View File
@@ -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, ","))
}
+27 -10
View File
@@ -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) {
+1 -1
View File
@@ -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
View File
@@ -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
View File
@@ -0,0 +1,50 @@
# xxhash
[![GoDoc](https://godoc.org/github.com/cespare/xxhash?status.svg)](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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
File diff suppressed because it is too large Load Diff
+5814
View File
File diff suppressed because it is too large Load Diff
+12
View File
@@ -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
View File
@@ -0,0 +1,3 @@
package influxql // import "github.com/influxdata/influxql"
//go:generate protoc --gogo_out=. internal/internal.proto
+120
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
File diff suppressed because it is too large Load Diff
+47
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -0,0 +1,40 @@
# PromQL Module
[![GoDoc](https://godoc.org/github.com/influxdata/promql?status.svg)](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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
File diff suppressed because it is too large Load Diff
+368
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
}
@@ -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