Merge branch 'master' into react-panels-step1
This commit is contained in:
+4
-4
@@ -10,10 +10,10 @@ import (
|
||||
)
|
||||
|
||||
func (hs *HTTPServer) registerRoutes() {
|
||||
reqSignedIn := middleware.Auth(&middleware.AuthOptions{ReqSignedIn: true})
|
||||
reqGrafanaAdmin := middleware.Auth(&middleware.AuthOptions{ReqSignedIn: true, ReqGrafanaAdmin: true})
|
||||
reqEditorRole := middleware.RoleAuth(m.ROLE_EDITOR, m.ROLE_ADMIN)
|
||||
reqOrgAdmin := middleware.RoleAuth(m.ROLE_ADMIN)
|
||||
reqSignedIn := middleware.ReqSignedIn
|
||||
reqGrafanaAdmin := middleware.ReqGrafanaAdmin
|
||||
reqEditorRole := middleware.ReqEditorRole
|
||||
reqOrgAdmin := middleware.ReqOrgAdmin
|
||||
redirectFromLegacyDashboardURL := middleware.RedirectFromLegacyDashboardURL()
|
||||
redirectFromLegacyDashboardSoloURL := middleware.RedirectFromLegacyDashboardSoloURL()
|
||||
quota := middleware.Quota
|
||||
|
||||
+15
-1
@@ -51,7 +51,21 @@ func (hs *HTTPServer) ProxyDataSourceRequest(c *m.ReqContext) {
|
||||
return
|
||||
}
|
||||
|
||||
proxyPath := c.Params("*")
|
||||
// macaron does not include trailing slashes when resolving a wildcard path
|
||||
proxyPath := ensureProxyPathTrailingSlash(c.Req.URL.Path, c.Params("*"))
|
||||
|
||||
proxy := pluginproxy.NewDataSourceProxy(ds, plugin, c, proxyPath)
|
||||
proxy.HandleRequest()
|
||||
}
|
||||
|
||||
// ensureProxyPathTrailingSlash Check for a trailing slash in original path and makes
|
||||
// sure that a trailing slash is added to proxy path, if not already exists.
|
||||
func ensureProxyPathTrailingSlash(originalPath, proxyPath string) string {
|
||||
if len(proxyPath) > 1 {
|
||||
if originalPath[len(originalPath)-1] == '/' && proxyPath[len(proxyPath)-1] != '/' {
|
||||
return proxyPath + "/"
|
||||
}
|
||||
}
|
||||
|
||||
return proxyPath
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
)
|
||||
|
||||
func TestDataProxy(t *testing.T) {
|
||||
Convey("Data proxy test", t, func() {
|
||||
Convey("Should append trailing slash to proxy path if original path has a trailing slash", func() {
|
||||
So(ensureProxyPathTrailingSlash("/api/datasources/proxy/6/api/v1/query_range/", "api/v1/query_range/"), ShouldEqual, "api/v1/query_range/")
|
||||
})
|
||||
|
||||
Convey("Should not append trailing slash to proxy path if original path doesn't have a trailing slash", func() {
|
||||
So(ensureProxyPathTrailingSlash("/api/datasources/proxy/6/api/v1/query_range", "api/v1/query_range"), ShouldEqual, "api/v1/query_range")
|
||||
})
|
||||
})
|
||||
}
|
||||
+14
-7
@@ -316,6 +316,19 @@ func (hs *HTTPServer) setIndexViewData(c *m.ReqContext) (*dtos.IndexViewData, er
|
||||
}
|
||||
|
||||
if c.IsGrafanaAdmin {
|
||||
children := []*dtos.NavLink{
|
||||
{Text: "Users", Id: "global-users", Url: setting.AppSubUrl + "/admin/users", Icon: "gicon gicon-user"},
|
||||
{Text: "Orgs", Id: "global-orgs", Url: setting.AppSubUrl + "/admin/orgs", Icon: "gicon gicon-org"},
|
||||
{Text: "Settings", Id: "server-settings", Url: setting.AppSubUrl + "/admin/settings", Icon: "gicon gicon-preferences"},
|
||||
{Text: "Stats", Id: "server-stats", Url: setting.AppSubUrl + "/admin/stats", Icon: "fa fa-fw fa-bar-chart"},
|
||||
}
|
||||
|
||||
if setting.IsEnterprise {
|
||||
children = append(children, &dtos.NavLink{Text: "Licensing", Id: "licensing", Url: setting.AppSubUrl + "/admin/licensing", Icon: "fa fa-fw fa-unlock-alt"})
|
||||
}
|
||||
|
||||
children = append(children, &dtos.NavLink{Text: "Style Guide", Id: "styleguide", Url: setting.AppSubUrl + "/styleguide", Icon: "fa fa-fw fa-eyedropper"})
|
||||
|
||||
cfgNode.Children = append(cfgNode.Children, &dtos.NavLink{
|
||||
Text: "Server Admin",
|
||||
HideFromTabs: true,
|
||||
@@ -323,13 +336,7 @@ func (hs *HTTPServer) setIndexViewData(c *m.ReqContext) (*dtos.IndexViewData, er
|
||||
Id: "admin",
|
||||
Icon: "gicon gicon-shield",
|
||||
Url: setting.AppSubUrl + "/admin/users",
|
||||
Children: []*dtos.NavLink{
|
||||
{Text: "Users", Id: "global-users", Url: setting.AppSubUrl + "/admin/users", Icon: "gicon gicon-user"},
|
||||
{Text: "Orgs", Id: "global-orgs", Url: setting.AppSubUrl + "/admin/orgs", Icon: "gicon gicon-org"},
|
||||
{Text: "Settings", Id: "server-settings", Url: setting.AppSubUrl + "/admin/settings", Icon: "gicon gicon-preferences"},
|
||||
{Text: "Stats", Id: "server-stats", Url: setting.AppSubUrl + "/admin/stats", Icon: "fa fa-fw fa-bar-chart"},
|
||||
{Text: "Style Guide", Id: "styleguide", Url: setting.AppSubUrl + "/styleguide", Icon: "fa fa-fw fa-eyedropper"},
|
||||
},
|
||||
Children: children,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
+16
-10
@@ -45,7 +45,7 @@ func addOrgUserHelper(cmd m.AddOrgUserCommand) Response {
|
||||
|
||||
// GET /api/org/users
|
||||
func GetOrgUsersForCurrentOrg(c *m.ReqContext) Response {
|
||||
return getOrgUsersHelper(c.OrgId, c.Params("query"), c.ParamsInt("limit"))
|
||||
return getOrgUsersHelper(c.OrgId, c.Query("query"), c.QueryInt("limit"))
|
||||
}
|
||||
|
||||
// GET /api/orgs/:orgId/users
|
||||
@@ -102,26 +102,32 @@ func updateOrgUserHelper(cmd m.UpdateOrgUserCommand) Response {
|
||||
|
||||
// DELETE /api/org/users/:userId
|
||||
func RemoveOrgUserForCurrentOrg(c *m.ReqContext) Response {
|
||||
userID := c.ParamsInt64(":userId")
|
||||
return removeOrgUserHelper(c.OrgId, userID)
|
||||
return removeOrgUserHelper(&m.RemoveOrgUserCommand{
|
||||
UserId: c.ParamsInt64(":userId"),
|
||||
OrgId: c.OrgId,
|
||||
ShouldDeleteOrphanedUser: true,
|
||||
})
|
||||
}
|
||||
|
||||
// DELETE /api/orgs/:orgId/users/:userId
|
||||
func RemoveOrgUser(c *m.ReqContext) Response {
|
||||
userID := c.ParamsInt64(":userId")
|
||||
orgID := c.ParamsInt64(":orgId")
|
||||
return removeOrgUserHelper(orgID, userID)
|
||||
return removeOrgUserHelper(&m.RemoveOrgUserCommand{
|
||||
UserId: c.ParamsInt64(":userId"),
|
||||
OrgId: c.ParamsInt64(":orgId"),
|
||||
})
|
||||
}
|
||||
|
||||
func removeOrgUserHelper(orgID int64, userID int64) Response {
|
||||
cmd := m.RemoveOrgUserCommand{OrgId: orgID, UserId: userID}
|
||||
|
||||
if err := bus.Dispatch(&cmd); err != nil {
|
||||
func removeOrgUserHelper(cmd *m.RemoveOrgUserCommand) Response {
|
||||
if err := bus.Dispatch(cmd); err != nil {
|
||||
if err == m.ErrLastOrgAdmin {
|
||||
return Error(400, "Cannot remove last organization admin", nil)
|
||||
}
|
||||
return Error(500, "Failed to remove user from organization", err)
|
||||
}
|
||||
|
||||
if cmd.UserWasDeleted {
|
||||
return Success("User deleted")
|
||||
}
|
||||
|
||||
return Success("User removed from organization")
|
||||
}
|
||||
|
||||
@@ -362,6 +362,23 @@ func TestDSRouteRule(t *testing.T) {
|
||||
})
|
||||
})
|
||||
|
||||
Convey("When proxying a custom datasource", func() {
|
||||
plugin := &plugins.DataSourcePlugin{}
|
||||
ds := &m.DataSource{
|
||||
Type: "custom-datasource",
|
||||
Url: "http://host/root/",
|
||||
}
|
||||
ctx := &m.ReqContext{}
|
||||
proxy := NewDataSourceProxy(ds, plugin, ctx, "/path/to/folder/")
|
||||
req, err := http.NewRequest(http.MethodGet, "http://grafana.com/sub", nil)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
proxy.getDirector()(req)
|
||||
|
||||
Convey("Shoudl keep user request (including trailing slash)", func() {
|
||||
So(req.URL.String(), ShouldEqual, "http://host/root/path/to/folder/")
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
package extensions
|
||||
|
||||
import (
|
||||
_ "gopkg.in/square/go-jose.v2"
|
||||
)
|
||||
|
||||
var IsEnterprise bool = false
|
||||
|
||||
@@ -14,6 +14,13 @@ import (
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
)
|
||||
|
||||
var (
|
||||
ReqGrafanaAdmin = Auth(&AuthOptions{ReqSignedIn: true, ReqGrafanaAdmin: true})
|
||||
ReqSignedIn = Auth(&AuthOptions{ReqSignedIn: true})
|
||||
ReqEditorRole = RoleAuth(m.ROLE_EDITOR, m.ROLE_ADMIN)
|
||||
ReqOrgAdmin = RoleAuth(m.ROLE_ADMIN)
|
||||
)
|
||||
|
||||
func GetContextHandler() macaron.Handler {
|
||||
return func(c *macaron.Context) {
|
||||
ctx := &m.ReqContext{
|
||||
|
||||
@@ -72,8 +72,10 @@ type OrgUser struct {
|
||||
// COMMANDS
|
||||
|
||||
type RemoveOrgUserCommand struct {
|
||||
UserId int64
|
||||
OrgId int64
|
||||
UserId int64
|
||||
OrgId int64
|
||||
ShouldDeleteOrphanedUser bool
|
||||
UserWasDeleted bool
|
||||
}
|
||||
|
||||
type AddOrgUserCommand struct {
|
||||
|
||||
@@ -182,6 +182,21 @@ func TestAccountDataAccess(t *testing.T) {
|
||||
})
|
||||
})
|
||||
|
||||
Convey("Removing user from org should delete user completely if in no other org", func() {
|
||||
// make sure ac2 has no org
|
||||
err := DeleteOrg(&m.DeleteOrgCommand{Id: ac2.OrgId})
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
// remove frome ac2 from ac1 org
|
||||
remCmd := m.RemoveOrgUserCommand{OrgId: ac1.OrgId, UserId: ac2.Id, ShouldDeleteOrphanedUser: true}
|
||||
err = RemoveOrgUser(&remCmd)
|
||||
So(err, ShouldBeNil)
|
||||
So(remCmd.UserWasDeleted, ShouldBeTrue)
|
||||
|
||||
err = GetSignedInUser(&m.GetSignedInUserQuery{UserId: ac2.Id})
|
||||
So(err, ShouldEqual, m.ErrUserNotFound)
|
||||
})
|
||||
|
||||
Convey("Cannot delete last admin org user", func() {
|
||||
cmd := m.RemoveOrgUserCommand{OrgId: ac1.OrgId, UserId: ac1.Id}
|
||||
err := RemoveOrgUser(&cmd)
|
||||
|
||||
@@ -157,6 +157,12 @@ func RemoveOrgUser(cmd *m.RemoveOrgUserCommand) error {
|
||||
}
|
||||
}
|
||||
|
||||
// validate that after delete there is at least one user with admin role in org
|
||||
if err := validateOneAdminLeftInOrg(cmd.OrgId, sess); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// check user other orgs and update user current org
|
||||
var userOrgs []*m.UserOrgDTO
|
||||
sess.Table("org_user")
|
||||
sess.Join("INNER", "org", "org_user.org_id=org.id")
|
||||
@@ -168,22 +174,31 @@ func RemoveOrgUser(cmd *m.RemoveOrgUserCommand) error {
|
||||
return err
|
||||
}
|
||||
|
||||
hasCurrentOrgSet := false
|
||||
for _, userOrg := range userOrgs {
|
||||
if user.OrgId == userOrg.OrgId {
|
||||
hasCurrentOrgSet = true
|
||||
break
|
||||
if len(userOrgs) > 0 {
|
||||
hasCurrentOrgSet := false
|
||||
for _, userOrg := range userOrgs {
|
||||
if user.OrgId == userOrg.OrgId {
|
||||
hasCurrentOrgSet = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !hasCurrentOrgSet && len(userOrgs) > 0 {
|
||||
err = setUsingOrgInTransaction(sess, user.Id, userOrgs[0].OrgId)
|
||||
if err != nil {
|
||||
if !hasCurrentOrgSet {
|
||||
err = setUsingOrgInTransaction(sess, user.Id, userOrgs[0].OrgId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else if cmd.ShouldDeleteOrphanedUser {
|
||||
// no other orgs, delete the full user
|
||||
if err := deleteUserInTransaction(sess, &m.DeleteUserCommand{UserId: user.Id}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd.UserWasDeleted = true
|
||||
}
|
||||
|
||||
return validateOneAdminLeftInOrg(cmd.OrgId, sess)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -233,7 +233,7 @@ func (ss *SqlStore) buildConnectionString() (string, error) {
|
||||
case migrator.SQLITE:
|
||||
// special case for tests
|
||||
if !filepath.IsAbs(ss.dbCfg.Path) {
|
||||
ss.dbCfg.Path = filepath.Join(setting.DataPath, ss.dbCfg.Path)
|
||||
ss.dbCfg.Path = filepath.Join(ss.Cfg.DataPath, ss.dbCfg.Path)
|
||||
}
|
||||
os.MkdirAll(path.Dir(ss.dbCfg.Path), os.ModePerm)
|
||||
cnnstr = "file:" + ss.dbCfg.Path + "?cache=shared&mode=rwc"
|
||||
|
||||
@@ -445,27 +445,31 @@ func SearchUsers(query *m.SearchUsersQuery) error {
|
||||
|
||||
func DeleteUser(cmd *m.DeleteUserCommand) error {
|
||||
return inTransaction(func(sess *DBSession) error {
|
||||
deletes := []string{
|
||||
"DELETE FROM star WHERE user_id = ?",
|
||||
"DELETE FROM " + dialect.Quote("user") + " WHERE id = ?",
|
||||
"DELETE FROM org_user WHERE user_id = ?",
|
||||
"DELETE FROM dashboard_acl WHERE user_id = ?",
|
||||
"DELETE FROM preferences WHERE user_id = ?",
|
||||
"DELETE FROM team_member WHERE user_id = ?",
|
||||
"DELETE FROM user_auth WHERE user_id = ?",
|
||||
}
|
||||
|
||||
for _, sql := range deletes {
|
||||
_, err := sess.Exec(sql, cmd.UserId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
return deleteUserInTransaction(sess, cmd)
|
||||
})
|
||||
}
|
||||
|
||||
func deleteUserInTransaction(sess *DBSession, cmd *m.DeleteUserCommand) error {
|
||||
deletes := []string{
|
||||
"DELETE FROM star WHERE user_id = ?",
|
||||
"DELETE FROM " + dialect.Quote("user") + " WHERE id = ?",
|
||||
"DELETE FROM org_user WHERE user_id = ?",
|
||||
"DELETE FROM dashboard_acl WHERE user_id = ?",
|
||||
"DELETE FROM preferences WHERE user_id = ?",
|
||||
"DELETE FROM team_member WHERE user_id = ?",
|
||||
"DELETE FROM user_auth WHERE user_id = ?",
|
||||
}
|
||||
|
||||
for _, sql := range deletes {
|
||||
_, err := sess.Exec(sql, cmd.UserId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func UpdateUserPermissions(cmd *m.UpdateUserPermissionsCommand) error {
|
||||
return inTransaction(func(sess *DBSession) error {
|
||||
user := m.User{}
|
||||
|
||||
+24
-18
@@ -54,14 +54,11 @@ var (
|
||||
ApplicationName string
|
||||
|
||||
// Paths
|
||||
LogsPath string
|
||||
HomePath string
|
||||
DataPath string
|
||||
PluginsPath string
|
||||
CustomInitPath = "conf/custom.ini"
|
||||
|
||||
// Log settings.
|
||||
LogModes []string
|
||||
LogConfigs []util.DynMap
|
||||
|
||||
// Http server options
|
||||
@@ -187,11 +184,18 @@ var (
|
||||
ImageUploadProvider string
|
||||
)
|
||||
|
||||
// TODO move all global vars to this struct
|
||||
type Cfg struct {
|
||||
Raw *ini.File
|
||||
|
||||
// HTTP Server Settings
|
||||
AppUrl string
|
||||
AppSubUrl string
|
||||
|
||||
// Paths
|
||||
ProvisioningPath string
|
||||
DataPath string
|
||||
LogsPath string
|
||||
|
||||
// SMTP email settings
|
||||
Smtp SmtpSettings
|
||||
@@ -413,7 +417,7 @@ func loadSpecifedConfigFile(configFile string, masterFile *ini.File) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func loadConfiguration(args *CommandLineArgs) (*ini.File, error) {
|
||||
func (cfg *Cfg) loadConfiguration(args *CommandLineArgs) (*ini.File, error) {
|
||||
var err error
|
||||
|
||||
// load config defaults
|
||||
@@ -444,7 +448,7 @@ func loadConfiguration(args *CommandLineArgs) (*ini.File, error) {
|
||||
// load specified config file
|
||||
err = loadSpecifedConfigFile(args.Config, parsedFile)
|
||||
if err != nil {
|
||||
initLogging(parsedFile)
|
||||
cfg.initLogging(parsedFile)
|
||||
log.Fatal(3, err.Error())
|
||||
}
|
||||
|
||||
@@ -461,8 +465,8 @@ func loadConfiguration(args *CommandLineArgs) (*ini.File, error) {
|
||||
evalConfigValues(parsedFile)
|
||||
|
||||
// update data path and logging config
|
||||
DataPath = makeAbsolute(parsedFile.Section("paths").Key("data").String(), HomePath)
|
||||
initLogging(parsedFile)
|
||||
cfg.DataPath = makeAbsolute(parsedFile.Section("paths").Key("data").String(), HomePath)
|
||||
cfg.initLogging(parsedFile)
|
||||
|
||||
return parsedFile, err
|
||||
}
|
||||
@@ -519,7 +523,7 @@ func NewCfg() *Cfg {
|
||||
func (cfg *Cfg) Load(args *CommandLineArgs) error {
|
||||
setHomePath(args)
|
||||
|
||||
iniFile, err := loadConfiguration(args)
|
||||
iniFile, err := cfg.loadConfiguration(args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -540,6 +544,8 @@ func (cfg *Cfg) Load(args *CommandLineArgs) error {
|
||||
cfg.ProvisioningPath = makeAbsolute(iniFile.Section("paths").Key("provisioning").String(), HomePath)
|
||||
server := iniFile.Section("server")
|
||||
AppUrl, AppSubUrl = parseAppUrlAndSubUrl(server)
|
||||
cfg.AppUrl = AppUrl
|
||||
cfg.AppSubUrl = AppSubUrl
|
||||
|
||||
Protocol = HTTP
|
||||
if server.Key("protocol").MustString("http") == "https" {
|
||||
@@ -664,7 +670,7 @@ func (cfg *Cfg) Load(args *CommandLineArgs) error {
|
||||
log.Fatal(4, "Invalid callback_url(%s): %s", cfg.RendererCallbackUrl, err)
|
||||
}
|
||||
}
|
||||
cfg.ImagesDir = filepath.Join(DataPath, "png")
|
||||
cfg.ImagesDir = filepath.Join(cfg.DataPath, "png")
|
||||
cfg.PhantomDir = filepath.Join(HomePath, "tools/phantomjs")
|
||||
cfg.TempDataLifetime = iniFile.Section("paths").Key("temp_data_lifetime").MustDuration(time.Second * 3600 * 24)
|
||||
cfg.MetricsEndpointEnabled = iniFile.Section("metrics").Key("enabled").MustBool(true)
|
||||
@@ -725,7 +731,7 @@ func (cfg *Cfg) readSessionConfig() {
|
||||
SessionOptions.IDLength = 16
|
||||
|
||||
if SessionOptions.Provider == "file" {
|
||||
SessionOptions.ProviderConfig = makeAbsolute(SessionOptions.ProviderConfig, DataPath)
|
||||
SessionOptions.ProviderConfig = makeAbsolute(SessionOptions.ProviderConfig, cfg.DataPath)
|
||||
os.MkdirAll(path.Dir(SessionOptions.ProviderConfig), os.ModePerm)
|
||||
}
|
||||
|
||||
@@ -736,15 +742,15 @@ func (cfg *Cfg) readSessionConfig() {
|
||||
SessionConnMaxLifetime = cfg.Raw.Section("session").Key("conn_max_lifetime").MustInt64(14400)
|
||||
}
|
||||
|
||||
func initLogging(file *ini.File) {
|
||||
func (cfg *Cfg) initLogging(file *ini.File) {
|
||||
// split on comma
|
||||
LogModes = strings.Split(file.Section("log").Key("mode").MustString("console"), ",")
|
||||
logModes := strings.Split(file.Section("log").Key("mode").MustString("console"), ",")
|
||||
// also try space
|
||||
if len(LogModes) == 1 {
|
||||
LogModes = strings.Split(file.Section("log").Key("mode").MustString("console"), " ")
|
||||
if len(logModes) == 1 {
|
||||
logModes = strings.Split(file.Section("log").Key("mode").MustString("console"), " ")
|
||||
}
|
||||
LogsPath = makeAbsolute(file.Section("paths").Key("logs").String(), HomePath)
|
||||
log.ReadLoggingConfig(LogModes, LogsPath, file)
|
||||
cfg.LogsPath = makeAbsolute(file.Section("paths").Key("logs").String(), HomePath)
|
||||
log.ReadLoggingConfig(logModes, cfg.LogsPath, file)
|
||||
}
|
||||
|
||||
func (cfg *Cfg) LogConfigSources() {
|
||||
@@ -768,8 +774,8 @@ func (cfg *Cfg) LogConfigSources() {
|
||||
}
|
||||
|
||||
logger.Info("Path Home", "path", HomePath)
|
||||
logger.Info("Path Data", "path", DataPath)
|
||||
logger.Info("Path Logs", "path", LogsPath)
|
||||
logger.Info("Path Data", "path", cfg.DataPath)
|
||||
logger.Info("Path Logs", "path", cfg.LogsPath)
|
||||
logger.Info("Path Plugins", "path", PluginsPath)
|
||||
logger.Info("Path Provisioning", "path", cfg.ProvisioningPath)
|
||||
logger.Info("App mode " + Env)
|
||||
|
||||
+12
-12
@@ -30,8 +30,8 @@ func TestLoadingSettings(t *testing.T) {
|
||||
cfg.Load(&CommandLineArgs{HomePath: "../../"})
|
||||
|
||||
So(AdminUser, ShouldEqual, "superduper")
|
||||
So(DataPath, ShouldEqual, filepath.Join(HomePath, "data"))
|
||||
So(LogsPath, ShouldEqual, filepath.Join(DataPath, "log"))
|
||||
So(cfg.DataPath, ShouldEqual, filepath.Join(HomePath, "data"))
|
||||
So(cfg.LogsPath, ShouldEqual, filepath.Join(cfg.DataPath, "log"))
|
||||
})
|
||||
|
||||
Convey("Should replace password when defined in environment", func() {
|
||||
@@ -76,8 +76,8 @@ func TestLoadingSettings(t *testing.T) {
|
||||
HomePath: "../../",
|
||||
Args: []string{`cfg:paths.data=c:\tmp\data`, `cfg:paths.logs=c:\tmp\logs`},
|
||||
})
|
||||
So(DataPath, ShouldEqual, `c:\tmp\data`)
|
||||
So(LogsPath, ShouldEqual, `c:\tmp\logs`)
|
||||
So(cfg.DataPath, ShouldEqual, `c:\tmp\data`)
|
||||
So(cfg.LogsPath, ShouldEqual, `c:\tmp\logs`)
|
||||
} else {
|
||||
cfg := NewCfg()
|
||||
cfg.Load(&CommandLineArgs{
|
||||
@@ -85,8 +85,8 @@ func TestLoadingSettings(t *testing.T) {
|
||||
Args: []string{"cfg:paths.data=/tmp/data", "cfg:paths.logs=/tmp/logs"},
|
||||
})
|
||||
|
||||
So(DataPath, ShouldEqual, "/tmp/data")
|
||||
So(LogsPath, ShouldEqual, "/tmp/logs")
|
||||
So(cfg.DataPath, ShouldEqual, "/tmp/data")
|
||||
So(cfg.LogsPath, ShouldEqual, "/tmp/logs")
|
||||
}
|
||||
})
|
||||
|
||||
@@ -112,7 +112,7 @@ func TestLoadingSettings(t *testing.T) {
|
||||
Args: []string{`cfg:default.paths.data=c:\tmp\data`},
|
||||
})
|
||||
|
||||
So(DataPath, ShouldEqual, `c:\tmp\override`)
|
||||
So(cfg.DataPath, ShouldEqual, `c:\tmp\override`)
|
||||
} else {
|
||||
cfg := NewCfg()
|
||||
cfg.Load(&CommandLineArgs{
|
||||
@@ -121,7 +121,7 @@ func TestLoadingSettings(t *testing.T) {
|
||||
Args: []string{"cfg:default.paths.data=/tmp/data"},
|
||||
})
|
||||
|
||||
So(DataPath, ShouldEqual, "/tmp/override")
|
||||
So(cfg.DataPath, ShouldEqual, "/tmp/override")
|
||||
}
|
||||
})
|
||||
|
||||
@@ -134,7 +134,7 @@ func TestLoadingSettings(t *testing.T) {
|
||||
Args: []string{`cfg:paths.data=c:\tmp\data`},
|
||||
})
|
||||
|
||||
So(DataPath, ShouldEqual, `c:\tmp\data`)
|
||||
So(cfg.DataPath, ShouldEqual, `c:\tmp\data`)
|
||||
} else {
|
||||
cfg := NewCfg()
|
||||
cfg.Load(&CommandLineArgs{
|
||||
@@ -143,7 +143,7 @@ func TestLoadingSettings(t *testing.T) {
|
||||
Args: []string{"cfg:paths.data=/tmp/data"},
|
||||
})
|
||||
|
||||
So(DataPath, ShouldEqual, "/tmp/data")
|
||||
So(cfg.DataPath, ShouldEqual, "/tmp/data")
|
||||
}
|
||||
})
|
||||
|
||||
@@ -156,7 +156,7 @@ func TestLoadingSettings(t *testing.T) {
|
||||
Args: []string{"cfg:paths.data=${GF_DATA_PATH}"},
|
||||
})
|
||||
|
||||
So(DataPath, ShouldEqual, `c:\tmp\env_override`)
|
||||
So(cfg.DataPath, ShouldEqual, `c:\tmp\env_override`)
|
||||
} else {
|
||||
os.Setenv("GF_DATA_PATH", "/tmp/env_override")
|
||||
cfg := NewCfg()
|
||||
@@ -165,7 +165,7 @@ func TestLoadingSettings(t *testing.T) {
|
||||
Args: []string{"cfg:paths.data=${GF_DATA_PATH}"},
|
||||
})
|
||||
|
||||
So(DataPath, ShouldEqual, "/tmp/env_override")
|
||||
So(cfg.DataPath, ShouldEqual, "/tmp/env_override")
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -129,10 +129,13 @@ func (e *CloudWatchExecutor) executeTimeSeriesQuery(ctx context.Context, queryCo
|
||||
if ae, ok := err.(awserr.Error); ok && ae.Code() == "500" {
|
||||
return err
|
||||
}
|
||||
result.Results[queryRes.RefId] = queryRes
|
||||
if err != nil {
|
||||
result.Results[queryRes.RefId].Error = err
|
||||
result.Results[query.RefId] = &tsdb.QueryResult{
|
||||
Error: err,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
result.Results[queryRes.RefId] = queryRes
|
||||
return nil
|
||||
})
|
||||
}
|
||||
@@ -269,7 +272,7 @@ func (e *CloudWatchExecutor) executeGetMetricDataQuery(ctx context.Context, regi
|
||||
for _, query := range queries {
|
||||
// 1 minutes resolution metrics is stored for 15 days, 15 * 24 * 60 = 21600
|
||||
if query.HighResolution && (((endTime.Unix() - startTime.Unix()) / int64(query.Period)) > 21600) {
|
||||
return nil, errors.New("too long query period")
|
||||
return queryResponses, errors.New("too long query period")
|
||||
}
|
||||
|
||||
mdq := &cloudwatch.MetricDataQuery{
|
||||
@@ -362,6 +365,7 @@ func (e *CloudWatchExecutor) executeGetMetricDataQuery(ctx context.Context, regi
|
||||
}
|
||||
|
||||
queryRes.Series = append(queryRes.Series, &series)
|
||||
queryRes.Meta = simplejson.New()
|
||||
queryResponses = append(queryResponses, queryRes)
|
||||
}
|
||||
|
||||
@@ -565,6 +569,12 @@ func parseResponse(resp *cloudwatch.GetMetricStatisticsOutput, query *CloudWatch
|
||||
}
|
||||
|
||||
queryRes.Series = append(queryRes.Series, &series)
|
||||
queryRes.Meta = simplejson.New()
|
||||
if len(resp.Datapoints) > 0 && resp.Datapoints[0].Unit != nil {
|
||||
if unit, ok := cloudwatchUnitMappings[*resp.Datapoints[0].Unit]; ok {
|
||||
queryRes.Meta.Set("unit", unit)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return queryRes, nil
|
||||
|
||||
@@ -71,6 +71,7 @@ func TestCloudWatch(t *testing.T) {
|
||||
"p50.00": aws.Float64(30.0),
|
||||
"p90.00": aws.Float64(40.0),
|
||||
},
|
||||
Unit: aws.String("Seconds"),
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -103,6 +104,7 @@ func TestCloudWatch(t *testing.T) {
|
||||
So(queryRes.Series[1].Points[0][0].String(), ShouldEqual, null.FloatFrom(20.0).String())
|
||||
So(queryRes.Series[2].Points[0][0].String(), ShouldEqual, null.FloatFrom(30.0).String())
|
||||
So(queryRes.Series[3].Points[0][0].String(), ShouldEqual, null.FloatFrom(40.0).String())
|
||||
So(queryRes.Meta.Get("unit").MustString(), ShouldEqual, "s")
|
||||
})
|
||||
|
||||
Convey("terminate gap of data points", func() {
|
||||
@@ -118,6 +120,7 @@ func TestCloudWatch(t *testing.T) {
|
||||
"p50.00": aws.Float64(30.0),
|
||||
"p90.00": aws.Float64(40.0),
|
||||
},
|
||||
Unit: aws.String("Seconds"),
|
||||
},
|
||||
{
|
||||
Timestamp: aws.Time(timestamp.Add(60 * time.Second)),
|
||||
@@ -127,6 +130,7 @@ func TestCloudWatch(t *testing.T) {
|
||||
"p50.00": aws.Float64(40.0),
|
||||
"p90.00": aws.Float64(50.0),
|
||||
},
|
||||
Unit: aws.String("Seconds"),
|
||||
},
|
||||
{
|
||||
Timestamp: aws.Time(timestamp.Add(180 * time.Second)),
|
||||
@@ -136,6 +140,7 @@ func TestCloudWatch(t *testing.T) {
|
||||
"p50.00": aws.Float64(50.0),
|
||||
"p90.00": aws.Float64(60.0),
|
||||
},
|
||||
Unit: aws.String("Seconds"),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
package cloudwatch
|
||||
|
||||
var cloudwatchUnitMappings = map[string]string{
|
||||
"Seconds": "s",
|
||||
"Microseconds": "µs",
|
||||
"Milliseconds": "ms",
|
||||
"Bytes": "bytes",
|
||||
"Kilobytes": "kbytes",
|
||||
"Megabytes": "mbytes",
|
||||
"Gigabytes": "gbytes",
|
||||
//"Terabytes": "",
|
||||
"Bits": "bits",
|
||||
//"Kilobits": "",
|
||||
//"Megabits": "",
|
||||
//"Gigabits": "",
|
||||
//"Terabits": "",
|
||||
"Percent": "percent",
|
||||
//"Count": "",
|
||||
"Bytes/Second": "Bps",
|
||||
"Kilobytes/Second": "KBs",
|
||||
"Megabytes/Second": "MBs",
|
||||
"Gigabytes/Second": "GBs",
|
||||
//"Terabytes/Second": "",
|
||||
"Bits/Second": "bps",
|
||||
"Kilobits/Second": "Kbits",
|
||||
"Megabits/Second": "Mbits",
|
||||
"Gigabits/Second": "Gbits",
|
||||
//"Terabits/Second": "",
|
||||
//"Count/Second": "",
|
||||
}
|
||||
@@ -171,6 +171,10 @@ func addTermsAgg(aggBuilder es.AggBuilder, bucketAgg *BucketAgg, metrics []*Metr
|
||||
} else {
|
||||
a.Size = 500
|
||||
}
|
||||
if a.Size == 0 {
|
||||
a.Size = 500
|
||||
}
|
||||
|
||||
if minDocCount, err := bucketAgg.Settings.Get("min_doc_count").Int(); err == nil {
|
||||
a.MinDocCount = &minDocCount
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ func TestExecuteTimeSeriesQuery(t *testing.T) {
|
||||
_, err := executeTsdbQuery(c, `{
|
||||
"timeField": "@timestamp",
|
||||
"bucketAggs": [
|
||||
{ "type": "terms", "field": "@host", "id": "2" },
|
||||
{ "type": "terms", "field": "@host", "id": "2", "settings": { "size": "0", "order": "asc" } },
|
||||
{ "type": "date_histogram", "field": "@timestamp", "id": "3" }
|
||||
],
|
||||
"metrics": [{"type": "count", "id": "1" }]
|
||||
@@ -69,7 +69,9 @@ func TestExecuteTimeSeriesQuery(t *testing.T) {
|
||||
sr := c.multisearchRequests[0].Requests[0]
|
||||
firstLevel := sr.Aggs[0]
|
||||
So(firstLevel.Key, ShouldEqual, "2")
|
||||
So(firstLevel.Aggregation.Aggregation.(*es.TermsAggregation).Field, ShouldEqual, "@host")
|
||||
termsAgg := firstLevel.Aggregation.Aggregation.(*es.TermsAggregation)
|
||||
So(termsAgg.Field, ShouldEqual, "@host")
|
||||
So(termsAgg.Size, ShouldEqual, 500)
|
||||
secondLevel := firstLevel.Aggregation.Aggs[0]
|
||||
So(secondLevel.Key, ShouldEqual, "3")
|
||||
So(secondLevel.Aggregation.Aggregation.(*es.DateHistogramAgg).Field, ShouldEqual, "@timestamp")
|
||||
|
||||
@@ -66,10 +66,6 @@ func (m *msSqlMacroEngine) evaluateMacro(name string, args []string) (string, er
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s BETWEEN '%s' AND '%s'", args[0], m.timeRange.GetFromAsTimeUTC().Format(time.RFC3339), m.timeRange.GetToAsTimeUTC().Format(time.RFC3339)), nil
|
||||
case "__timeFrom":
|
||||
return fmt.Sprintf("'%s'", m.timeRange.GetFromAsTimeUTC().Format(time.RFC3339)), nil
|
||||
case "__timeTo":
|
||||
return fmt.Sprintf("'%s'", m.timeRange.GetToAsTimeUTC().Format(time.RFC3339)), nil
|
||||
case "__timeGroup":
|
||||
if len(args) < 2 {
|
||||
return "", fmt.Errorf("macro %v needs time column and interval", name)
|
||||
@@ -96,10 +92,6 @@ func (m *msSqlMacroEngine) evaluateMacro(name string, args []string) (string, er
|
||||
return "", fmt.Errorf("missing time column argument for macro %v", name)
|
||||
}
|
||||
return fmt.Sprintf("%s >= %d AND %s <= %d", args[0], m.timeRange.GetFromAsSecondsEpoch(), args[0], m.timeRange.GetToAsSecondsEpoch()), nil
|
||||
case "__unixEpochFrom":
|
||||
return fmt.Sprintf("%d", m.timeRange.GetFromAsSecondsEpoch()), nil
|
||||
case "__unixEpochTo":
|
||||
return fmt.Sprintf("%d", m.timeRange.GetToAsSecondsEpoch()), nil
|
||||
case "__unixEpochGroup":
|
||||
if len(args) < 2 {
|
||||
return "", fmt.Errorf("macro %v needs time column and interval and optional fill value", name)
|
||||
|
||||
@@ -111,20 +111,6 @@ func TestMacroEngine(t *testing.T) {
|
||||
So(fillInterval, ShouldEqual, 5*time.Minute.Seconds())
|
||||
})
|
||||
|
||||
Convey("interpolate __timeFrom function", func() {
|
||||
sql, err := engine.Interpolate(query, timeRange, "select $__timeFrom(time_column)")
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(sql, ShouldEqual, fmt.Sprintf("select '%s'", from.Format(time.RFC3339)))
|
||||
})
|
||||
|
||||
Convey("interpolate __timeTo function", func() {
|
||||
sql, err := engine.Interpolate(query, timeRange, "select $__timeTo(time_column)")
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(sql, ShouldEqual, fmt.Sprintf("select '%s'", to.Format(time.RFC3339)))
|
||||
})
|
||||
|
||||
Convey("interpolate __unixEpochFilter function", func() {
|
||||
sql, err := engine.Interpolate(query, timeRange, "select $__unixEpochFilter(time_column)")
|
||||
So(err, ShouldBeNil)
|
||||
@@ -132,20 +118,6 @@ func TestMacroEngine(t *testing.T) {
|
||||
So(sql, ShouldEqual, fmt.Sprintf("select time_column >= %d AND time_column <= %d", from.Unix(), to.Unix()))
|
||||
})
|
||||
|
||||
Convey("interpolate __unixEpochFrom function", func() {
|
||||
sql, err := engine.Interpolate(query, timeRange, "select $__unixEpochFrom()")
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(sql, ShouldEqual, fmt.Sprintf("select %d", from.Unix()))
|
||||
})
|
||||
|
||||
Convey("interpolate __unixEpochTo function", func() {
|
||||
sql, err := engine.Interpolate(query, timeRange, "select $__unixEpochTo()")
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(sql, ShouldEqual, fmt.Sprintf("select %d", to.Unix()))
|
||||
})
|
||||
|
||||
Convey("interpolate __unixEpochGroup function", func() {
|
||||
|
||||
sql, err := engine.Interpolate(query, timeRange, "SELECT $__unixEpochGroup(time_column,'5m')")
|
||||
@@ -171,40 +143,12 @@ func TestMacroEngine(t *testing.T) {
|
||||
So(sql, ShouldEqual, fmt.Sprintf("WHERE time_column BETWEEN '%s' AND '%s'", from.Format(time.RFC3339), to.Format(time.RFC3339)))
|
||||
})
|
||||
|
||||
Convey("interpolate __timeFrom function", func() {
|
||||
sql, err := engine.Interpolate(query, timeRange, "select $__timeFrom(time_column)")
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(sql, ShouldEqual, fmt.Sprintf("select '%s'", from.Format(time.RFC3339)))
|
||||
})
|
||||
|
||||
Convey("interpolate __timeTo function", func() {
|
||||
sql, err := engine.Interpolate(query, timeRange, "select $__timeTo(time_column)")
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(sql, ShouldEqual, fmt.Sprintf("select '%s'", to.Format(time.RFC3339)))
|
||||
})
|
||||
|
||||
Convey("interpolate __unixEpochFilter function", func() {
|
||||
sql, err := engine.Interpolate(query, timeRange, "select $__unixEpochFilter(time_column)")
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(sql, ShouldEqual, fmt.Sprintf("select time_column >= %d AND time_column <= %d", from.Unix(), to.Unix()))
|
||||
})
|
||||
|
||||
Convey("interpolate __unixEpochFrom function", func() {
|
||||
sql, err := engine.Interpolate(query, timeRange, "select $__unixEpochFrom()")
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(sql, ShouldEqual, fmt.Sprintf("select %d", from.Unix()))
|
||||
})
|
||||
|
||||
Convey("interpolate __unixEpochTo function", func() {
|
||||
sql, err := engine.Interpolate(query, timeRange, "select $__unixEpochTo()")
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(sql, ShouldEqual, fmt.Sprintf("select %d", to.Unix()))
|
||||
})
|
||||
})
|
||||
|
||||
Convey("Given a time range between 1960-02-01 07:00 and 1980-02-03 08:00", func() {
|
||||
@@ -219,40 +163,12 @@ func TestMacroEngine(t *testing.T) {
|
||||
So(sql, ShouldEqual, fmt.Sprintf("WHERE time_column BETWEEN '%s' AND '%s'", from.Format(time.RFC3339), to.Format(time.RFC3339)))
|
||||
})
|
||||
|
||||
Convey("interpolate __timeFrom function", func() {
|
||||
sql, err := engine.Interpolate(query, timeRange, "select $__timeFrom(time_column)")
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(sql, ShouldEqual, fmt.Sprintf("select '%s'", from.Format(time.RFC3339)))
|
||||
})
|
||||
|
||||
Convey("interpolate __timeTo function", func() {
|
||||
sql, err := engine.Interpolate(query, timeRange, "select $__timeTo(time_column)")
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(sql, ShouldEqual, fmt.Sprintf("select '%s'", to.Format(time.RFC3339)))
|
||||
})
|
||||
|
||||
Convey("interpolate __unixEpochFilter function", func() {
|
||||
sql, err := engine.Interpolate(query, timeRange, "select $__unixEpochFilter(time_column)")
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(sql, ShouldEqual, fmt.Sprintf("select time_column >= %d AND time_column <= %d", from.Unix(), to.Unix()))
|
||||
})
|
||||
|
||||
Convey("interpolate __unixEpochFrom function", func() {
|
||||
sql, err := engine.Interpolate(query, timeRange, "select $__unixEpochFrom()")
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(sql, ShouldEqual, fmt.Sprintf("select %d", from.Unix()))
|
||||
})
|
||||
|
||||
Convey("interpolate __unixEpochTo function", func() {
|
||||
sql, err := engine.Interpolate(query, timeRange, "select $__unixEpochTo()")
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(sql, ShouldEqual, fmt.Sprintf("select %d", to.Unix()))
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -676,6 +676,30 @@ func TestMSSQL(t *testing.T) {
|
||||
So(queryResult.Series[3].Name, ShouldEqual, "Metric B valueTwo")
|
||||
})
|
||||
|
||||
Convey("When doing a query with timeFrom,timeTo,unixEpochFrom,unixEpochTo macros", func() {
|
||||
tsdb.Interpolate = origInterpolate
|
||||
query := &tsdb.TsdbQuery{
|
||||
TimeRange: tsdb.NewFakeTimeRange("5m", "now", fromStart),
|
||||
Queries: []*tsdb.Query{
|
||||
{
|
||||
DataSource: &models.DataSource{JsonData: simplejson.New()},
|
||||
Model: simplejson.NewFromAny(map[string]interface{}{
|
||||
"rawSql": `SELECT time FROM metric_values WHERE time > $__timeFrom() OR time < $__timeFrom() OR 1 < $__unixEpochFrom() OR $__unixEpochTo() > 1 ORDER BY 1`,
|
||||
"format": "time_series",
|
||||
}),
|
||||
RefId: "A",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
resp, err := endpoint.Query(nil, nil, query)
|
||||
So(err, ShouldBeNil)
|
||||
queryResult := resp.Results["A"]
|
||||
So(queryResult.Error, ShouldBeNil)
|
||||
So(queryResult.Meta.Get("sql").MustString(), ShouldEqual, "SELECT time FROM metric_values WHERE time > '2018-03-15T12:55:00Z' OR time < '2018-03-15T12:55:00Z' OR 1 < 1521118500 OR 1521118800 > 1 ORDER BY 1")
|
||||
|
||||
})
|
||||
|
||||
Convey("Given a stored procedure that takes @from and @to in epoch time", func() {
|
||||
sql := `
|
||||
IF object_id('sp_test_epoch') IS NOT NULL
|
||||
@@ -720,9 +744,11 @@ func TestMSSQL(t *testing.T) {
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
Convey("When doing a metric query using stored procedure should return correct result", func() {
|
||||
tsdb.Interpolate = origInterpolate
|
||||
query := &tsdb.TsdbQuery{
|
||||
Queries: []*tsdb.Query{
|
||||
{
|
||||
DataSource: &models.DataSource{JsonData: simplejson.New()},
|
||||
Model: simplejson.NewFromAny(map[string]interface{}{
|
||||
"rawSql": `DECLARE
|
||||
@from int = $__unixEpochFrom(),
|
||||
@@ -797,9 +823,11 @@ func TestMSSQL(t *testing.T) {
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
Convey("When doing a metric query using stored procedure should return correct result", func() {
|
||||
tsdb.Interpolate = origInterpolate
|
||||
query := &tsdb.TsdbQuery{
|
||||
Queries: []*tsdb.Query{
|
||||
{
|
||||
DataSource: &models.DataSource{JsonData: simplejson.New()},
|
||||
Model: simplejson.NewFromAny(map[string]interface{}{
|
||||
"rawSql": `DECLARE
|
||||
@from int = $__unixEpochFrom(),
|
||||
|
||||
@@ -61,10 +61,6 @@ func (m *mySqlMacroEngine) evaluateMacro(name string, args []string) (string, er
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s BETWEEN '%s' AND '%s'", args[0], m.timeRange.GetFromAsTimeUTC().Format(time.RFC3339), m.timeRange.GetToAsTimeUTC().Format(time.RFC3339)), nil
|
||||
case "__timeFrom":
|
||||
return fmt.Sprintf("'%s'", m.timeRange.GetFromAsTimeUTC().Format(time.RFC3339)), nil
|
||||
case "__timeTo":
|
||||
return fmt.Sprintf("'%s'", m.timeRange.GetToAsTimeUTC().Format(time.RFC3339)), nil
|
||||
case "__timeGroup":
|
||||
if len(args) < 2 {
|
||||
return "", fmt.Errorf("macro %v needs time column and interval", name)
|
||||
@@ -91,10 +87,6 @@ func (m *mySqlMacroEngine) evaluateMacro(name string, args []string) (string, er
|
||||
return "", fmt.Errorf("missing time column argument for macro %v", name)
|
||||
}
|
||||
return fmt.Sprintf("%s >= %d AND %s <= %d", args[0], m.timeRange.GetFromAsSecondsEpoch(), args[0], m.timeRange.GetToAsSecondsEpoch()), nil
|
||||
case "__unixEpochFrom":
|
||||
return fmt.Sprintf("%d", m.timeRange.GetFromAsSecondsEpoch()), nil
|
||||
case "__unixEpochTo":
|
||||
return fmt.Sprintf("%d", m.timeRange.GetToAsSecondsEpoch()), nil
|
||||
case "__unixEpochGroup":
|
||||
if len(args) < 2 {
|
||||
return "", fmt.Errorf("macro %v needs time column and interval and optional fill value", name)
|
||||
|
||||
@@ -63,20 +63,6 @@ func TestMacroEngine(t *testing.T) {
|
||||
So(sql, ShouldEqual, fmt.Sprintf("WHERE time_column BETWEEN '%s' AND '%s'", from.Format(time.RFC3339), to.Format(time.RFC3339)))
|
||||
})
|
||||
|
||||
Convey("interpolate __timeFrom function", func() {
|
||||
sql, err := engine.Interpolate(query, timeRange, "select $__timeFrom(time_column)")
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(sql, ShouldEqual, fmt.Sprintf("select '%s'", from.Format(time.RFC3339)))
|
||||
})
|
||||
|
||||
Convey("interpolate __timeTo function", func() {
|
||||
sql, err := engine.Interpolate(query, timeRange, "select $__timeTo(time_column)")
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(sql, ShouldEqual, fmt.Sprintf("select '%s'", to.Format(time.RFC3339)))
|
||||
})
|
||||
|
||||
Convey("interpolate __unixEpochFilter function", func() {
|
||||
sql, err := engine.Interpolate(query, timeRange, "select $__unixEpochFilter(time)")
|
||||
So(err, ShouldBeNil)
|
||||
@@ -84,20 +70,6 @@ func TestMacroEngine(t *testing.T) {
|
||||
So(sql, ShouldEqual, fmt.Sprintf("select time >= %d AND time <= %d", from.Unix(), to.Unix()))
|
||||
})
|
||||
|
||||
Convey("interpolate __unixEpochFrom function", func() {
|
||||
sql, err := engine.Interpolate(query, timeRange, "select $__unixEpochFrom()")
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(sql, ShouldEqual, fmt.Sprintf("select %d", from.Unix()))
|
||||
})
|
||||
|
||||
Convey("interpolate __unixEpochTo function", func() {
|
||||
sql, err := engine.Interpolate(query, timeRange, "select $__unixEpochTo()")
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(sql, ShouldEqual, fmt.Sprintf("select %d", to.Unix()))
|
||||
})
|
||||
|
||||
Convey("interpolate __unixEpochGroup function", func() {
|
||||
|
||||
sql, err := engine.Interpolate(query, timeRange, "SELECT $__unixEpochGroup(time_column,'5m')")
|
||||
@@ -123,40 +95,12 @@ func TestMacroEngine(t *testing.T) {
|
||||
So(sql, ShouldEqual, fmt.Sprintf("WHERE time_column BETWEEN '%s' AND '%s'", from.Format(time.RFC3339), to.Format(time.RFC3339)))
|
||||
})
|
||||
|
||||
Convey("interpolate __timeFrom function", func() {
|
||||
sql, err := engine.Interpolate(query, timeRange, "select $__timeFrom(time_column)")
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(sql, ShouldEqual, fmt.Sprintf("select '%s'", from.Format(time.RFC3339)))
|
||||
})
|
||||
|
||||
Convey("interpolate __timeTo function", func() {
|
||||
sql, err := engine.Interpolate(query, timeRange, "select $__timeTo(time_column)")
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(sql, ShouldEqual, fmt.Sprintf("select '%s'", to.Format(time.RFC3339)))
|
||||
})
|
||||
|
||||
Convey("interpolate __unixEpochFilter function", func() {
|
||||
sql, err := engine.Interpolate(query, timeRange, "select $__unixEpochFilter(time)")
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(sql, ShouldEqual, fmt.Sprintf("select time >= %d AND time <= %d", from.Unix(), to.Unix()))
|
||||
})
|
||||
|
||||
Convey("interpolate __unixEpochFrom function", func() {
|
||||
sql, err := engine.Interpolate(query, timeRange, "select $__unixEpochFrom()")
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(sql, ShouldEqual, fmt.Sprintf("select %d", from.Unix()))
|
||||
})
|
||||
|
||||
Convey("interpolate __unixEpochTo function", func() {
|
||||
sql, err := engine.Interpolate(query, timeRange, "select $__unixEpochTo()")
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(sql, ShouldEqual, fmt.Sprintf("select %d", to.Unix()))
|
||||
})
|
||||
})
|
||||
|
||||
Convey("Given a time range between 1960-02-01 07:00 and 1980-02-03 08:00", func() {
|
||||
@@ -171,40 +115,12 @@ func TestMacroEngine(t *testing.T) {
|
||||
So(sql, ShouldEqual, fmt.Sprintf("WHERE time_column BETWEEN '%s' AND '%s'", from.Format(time.RFC3339), to.Format(time.RFC3339)))
|
||||
})
|
||||
|
||||
Convey("interpolate __timeFrom function", func() {
|
||||
sql, err := engine.Interpolate(query, timeRange, "select $__timeFrom(time_column)")
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(sql, ShouldEqual, fmt.Sprintf("select '%s'", from.Format(time.RFC3339)))
|
||||
})
|
||||
|
||||
Convey("interpolate __timeTo function", func() {
|
||||
sql, err := engine.Interpolate(query, timeRange, "select $__timeTo(time_column)")
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(sql, ShouldEqual, fmt.Sprintf("select '%s'", to.Format(time.RFC3339)))
|
||||
})
|
||||
|
||||
Convey("interpolate __unixEpochFilter function", func() {
|
||||
sql, err := engine.Interpolate(query, timeRange, "select $__unixEpochFilter(time)")
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(sql, ShouldEqual, fmt.Sprintf("select time >= %d AND time <= %d", from.Unix(), to.Unix()))
|
||||
})
|
||||
|
||||
Convey("interpolate __unixEpochFrom function", func() {
|
||||
sql, err := engine.Interpolate(query, timeRange, "select $__unixEpochFrom()")
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(sql, ShouldEqual, fmt.Sprintf("select %d", from.Unix()))
|
||||
})
|
||||
|
||||
Convey("interpolate __unixEpochTo function", func() {
|
||||
sql, err := engine.Interpolate(query, timeRange, "select $__unixEpochTo()")
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(sql, ShouldEqual, fmt.Sprintf("select %d", to.Unix()))
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -314,7 +314,7 @@ func TestMySQL(t *testing.T) {
|
||||
query := &tsdb.TsdbQuery{
|
||||
Queries: []*tsdb.Query{
|
||||
{
|
||||
DataSource: &models.DataSource{},
|
||||
DataSource: &models.DataSource{JsonData: simplejson.New()},
|
||||
Model: simplejson.NewFromAny(map[string]interface{}{
|
||||
"rawSql": "SELECT $__timeGroup(time, $__interval) AS time, avg(value) as value FROM metric GROUP BY 1 ORDER BY 1",
|
||||
"format": "time_series",
|
||||
@@ -753,6 +753,30 @@ func TestMySQL(t *testing.T) {
|
||||
})
|
||||
})
|
||||
|
||||
Convey("When doing a query with timeFrom,timeTo,unixEpochFrom,unixEpochTo macros", func() {
|
||||
tsdb.Interpolate = origInterpolate
|
||||
query := &tsdb.TsdbQuery{
|
||||
TimeRange: tsdb.NewFakeTimeRange("5m", "now", fromStart),
|
||||
Queries: []*tsdb.Query{
|
||||
{
|
||||
DataSource: &models.DataSource{JsonData: simplejson.New()},
|
||||
Model: simplejson.NewFromAny(map[string]interface{}{
|
||||
"rawSql": `SELECT time FROM metric_values WHERE time > $__timeFrom() OR time < $__timeFrom() OR 1 < $__unixEpochFrom() OR $__unixEpochTo() > 1 ORDER BY 1`,
|
||||
"format": "time_series",
|
||||
}),
|
||||
RefId: "A",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
resp, err := endpoint.Query(nil, nil, query)
|
||||
So(err, ShouldBeNil)
|
||||
queryResult := resp.Results["A"]
|
||||
So(queryResult.Error, ShouldBeNil)
|
||||
So(queryResult.Meta.Get("sql").MustString(), ShouldEqual, "SELECT time FROM metric_values WHERE time > '2018-03-15T12:55:00Z' OR time < '2018-03-15T12:55:00Z' OR 1 < 1521118500 OR 1521118800 > 1 ORDER BY 1")
|
||||
|
||||
})
|
||||
|
||||
Convey("Given a table with event data", func() {
|
||||
type event struct {
|
||||
TimeSec int64
|
||||
|
||||
@@ -87,10 +87,6 @@ func (m *postgresMacroEngine) evaluateMacro(name string, args []string) (string,
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s BETWEEN '%s' AND '%s'", args[0], m.timeRange.GetFromAsTimeUTC().Format(time.RFC3339), m.timeRange.GetToAsTimeUTC().Format(time.RFC3339)), nil
|
||||
case "__timeFrom":
|
||||
return fmt.Sprintf("'%s'", m.timeRange.GetFromAsTimeUTC().Format(time.RFC3339)), nil
|
||||
case "__timeTo":
|
||||
return fmt.Sprintf("'%s'", m.timeRange.GetToAsTimeUTC().Format(time.RFC3339)), nil
|
||||
case "__timeGroup":
|
||||
if len(args) < 2 {
|
||||
return "", fmt.Errorf("macro %v needs time column and interval and optional fill value", name)
|
||||
@@ -122,10 +118,6 @@ func (m *postgresMacroEngine) evaluateMacro(name string, args []string) (string,
|
||||
return "", fmt.Errorf("missing time column argument for macro %v", name)
|
||||
}
|
||||
return fmt.Sprintf("%s >= %d AND %s <= %d", args[0], m.timeRange.GetFromAsSecondsEpoch(), args[0], m.timeRange.GetToAsSecondsEpoch()), nil
|
||||
case "__unixEpochFrom":
|
||||
return fmt.Sprintf("%d", m.timeRange.GetFromAsSecondsEpoch()), nil
|
||||
case "__unixEpochTo":
|
||||
return fmt.Sprintf("%d", m.timeRange.GetToAsSecondsEpoch()), nil
|
||||
case "__unixEpochGroup":
|
||||
if len(args) < 2 {
|
||||
return "", fmt.Errorf("macro %v needs time column and interval and optional fill value", name)
|
||||
|
||||
@@ -44,13 +44,6 @@ func TestMacroEngine(t *testing.T) {
|
||||
So(sql, ShouldEqual, fmt.Sprintf("WHERE time_column BETWEEN '%s' AND '%s'", from.Format(time.RFC3339), to.Format(time.RFC3339)))
|
||||
})
|
||||
|
||||
Convey("interpolate __timeFrom function", func() {
|
||||
sql, err := engine.Interpolate(query, timeRange, "select $__timeFrom(time_column)")
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(sql, ShouldEqual, fmt.Sprintf("select '%s'", from.Format(time.RFC3339)))
|
||||
})
|
||||
|
||||
Convey("interpolate __timeGroup function pre 5.3 compatibility", func() {
|
||||
|
||||
sql, err := engine.Interpolate(query, timeRange, "SELECT $__timeGroup(time_column,'5m'), value")
|
||||
@@ -102,13 +95,6 @@ func TestMacroEngine(t *testing.T) {
|
||||
So(sql, ShouldEqual, "GROUP BY time_bucket('300s',time_column)")
|
||||
})
|
||||
|
||||
Convey("interpolate __timeTo function", func() {
|
||||
sql, err := engine.Interpolate(query, timeRange, "select $__timeTo(time_column)")
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(sql, ShouldEqual, fmt.Sprintf("select '%s'", to.Format(time.RFC3339)))
|
||||
})
|
||||
|
||||
Convey("interpolate __unixEpochFilter function", func() {
|
||||
sql, err := engine.Interpolate(query, timeRange, "select $__unixEpochFilter(time)")
|
||||
So(err, ShouldBeNil)
|
||||
@@ -116,20 +102,6 @@ func TestMacroEngine(t *testing.T) {
|
||||
So(sql, ShouldEqual, fmt.Sprintf("select time >= %d AND time <= %d", from.Unix(), to.Unix()))
|
||||
})
|
||||
|
||||
Convey("interpolate __unixEpochFrom function", func() {
|
||||
sql, err := engine.Interpolate(query, timeRange, "select $__unixEpochFrom()")
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(sql, ShouldEqual, fmt.Sprintf("select %d", from.Unix()))
|
||||
})
|
||||
|
||||
Convey("interpolate __unixEpochTo function", func() {
|
||||
sql, err := engine.Interpolate(query, timeRange, "select $__unixEpochTo()")
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(sql, ShouldEqual, fmt.Sprintf("select %d", to.Unix()))
|
||||
})
|
||||
|
||||
Convey("interpolate __unixEpochGroup function", func() {
|
||||
|
||||
sql, err := engine.Interpolate(query, timeRange, "SELECT $__unixEpochGroup(time_column,'5m')")
|
||||
@@ -155,40 +127,12 @@ func TestMacroEngine(t *testing.T) {
|
||||
So(sql, ShouldEqual, fmt.Sprintf("WHERE time_column BETWEEN '%s' AND '%s'", from.Format(time.RFC3339), to.Format(time.RFC3339)))
|
||||
})
|
||||
|
||||
Convey("interpolate __timeFrom function", func() {
|
||||
sql, err := engine.Interpolate(query, timeRange, "select $__timeFrom(time_column)")
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(sql, ShouldEqual, fmt.Sprintf("select '%s'", from.Format(time.RFC3339)))
|
||||
})
|
||||
|
||||
Convey("interpolate __timeTo function", func() {
|
||||
sql, err := engine.Interpolate(query, timeRange, "select $__timeTo(time_column)")
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(sql, ShouldEqual, fmt.Sprintf("select '%s'", to.Format(time.RFC3339)))
|
||||
})
|
||||
|
||||
Convey("interpolate __unixEpochFilter function", func() {
|
||||
sql, err := engine.Interpolate(query, timeRange, "select $__unixEpochFilter(time)")
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(sql, ShouldEqual, fmt.Sprintf("select time >= %d AND time <= %d", from.Unix(), to.Unix()))
|
||||
})
|
||||
|
||||
Convey("interpolate __unixEpochFrom function", func() {
|
||||
sql, err := engine.Interpolate(query, timeRange, "select $__unixEpochFrom()")
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(sql, ShouldEqual, fmt.Sprintf("select %d", from.Unix()))
|
||||
})
|
||||
|
||||
Convey("interpolate __unixEpochTo function", func() {
|
||||
sql, err := engine.Interpolate(query, timeRange, "select $__unixEpochTo()")
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(sql, ShouldEqual, fmt.Sprintf("select %d", to.Unix()))
|
||||
})
|
||||
})
|
||||
|
||||
Convey("Given a time range between 1960-02-01 07:00 and 1980-02-03 08:00", func() {
|
||||
@@ -203,40 +147,12 @@ func TestMacroEngine(t *testing.T) {
|
||||
So(sql, ShouldEqual, fmt.Sprintf("WHERE time_column BETWEEN '%s' AND '%s'", from.Format(time.RFC3339), to.Format(time.RFC3339)))
|
||||
})
|
||||
|
||||
Convey("interpolate __timeFrom function", func() {
|
||||
sql, err := engine.Interpolate(query, timeRange, "select $__timeFrom(time_column)")
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(sql, ShouldEqual, fmt.Sprintf("select '%s'", from.Format(time.RFC3339)))
|
||||
})
|
||||
|
||||
Convey("interpolate __timeTo function", func() {
|
||||
sql, err := engine.Interpolate(query, timeRange, "select $__timeTo(time_column)")
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(sql, ShouldEqual, fmt.Sprintf("select '%s'", to.Format(time.RFC3339)))
|
||||
})
|
||||
|
||||
Convey("interpolate __unixEpochFilter function", func() {
|
||||
sql, err := engine.Interpolate(query, timeRange, "select $__unixEpochFilter(time)")
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(sql, ShouldEqual, fmt.Sprintf("select time >= %d AND time <= %d", from.Unix(), to.Unix()))
|
||||
})
|
||||
|
||||
Convey("interpolate __unixEpochFrom function", func() {
|
||||
sql, err := engine.Interpolate(query, timeRange, "select $__unixEpochFrom()")
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(sql, ShouldEqual, fmt.Sprintf("select %d", from.Unix()))
|
||||
})
|
||||
|
||||
Convey("interpolate __unixEpochTo function", func() {
|
||||
sql, err := engine.Interpolate(query, timeRange, "select $__unixEpochTo()")
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(sql, ShouldEqual, fmt.Sprintf("select %d", to.Unix()))
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -684,6 +684,30 @@ func TestPostgres(t *testing.T) {
|
||||
So(queryResult.Series[0].Name, ShouldEqual, "valueOne")
|
||||
So(queryResult.Series[1].Name, ShouldEqual, "valueTwo")
|
||||
})
|
||||
|
||||
Convey("When doing a query with timeFrom,timeTo,unixEpochFrom,unixEpochTo macros", func() {
|
||||
tsdb.Interpolate = origInterpolate
|
||||
query := &tsdb.TsdbQuery{
|
||||
TimeRange: tsdb.NewFakeTimeRange("5m", "now", fromStart),
|
||||
Queries: []*tsdb.Query{
|
||||
{
|
||||
DataSource: &models.DataSource{JsonData: simplejson.New()},
|
||||
Model: simplejson.NewFromAny(map[string]interface{}{
|
||||
"rawSql": `SELECT time FROM metric_values WHERE time > $__timeFrom() OR time < $__timeFrom() OR 1 < $__unixEpochFrom() OR $__unixEpochTo() > 1 ORDER BY 1`,
|
||||
"format": "time_series",
|
||||
}),
|
||||
RefId: "A",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
resp, err := endpoint.Query(nil, nil, query)
|
||||
So(err, ShouldBeNil)
|
||||
queryResult := resp.Results["A"]
|
||||
So(queryResult.Error, ShouldBeNil)
|
||||
So(queryResult.Meta.Get("sql").MustString(), ShouldEqual, "SELECT time FROM metric_values WHERE time > '2018-03-15T12:55:00Z' OR time < '2018-03-15T12:55:00Z' OR 1 < 1521118500 OR 1521118800 > 1 ORDER BY 1")
|
||||
|
||||
})
|
||||
})
|
||||
|
||||
Convey("Given a table with event data", func() {
|
||||
|
||||
+39
-23
@@ -98,8 +98,12 @@ var NewSqlQueryEndpoint = func(config *SqlQueryEndpointConfiguration, rowTransfo
|
||||
return nil, err
|
||||
}
|
||||
|
||||
engine.SetMaxOpenConns(10)
|
||||
engine.SetMaxIdleConns(10)
|
||||
maxOpenConns := config.Datasource.JsonData.Get("maxOpenConns").MustInt(0)
|
||||
engine.SetMaxOpenConns(maxOpenConns)
|
||||
maxIdleConns := config.Datasource.JsonData.Get("maxIdleConns").MustInt(2)
|
||||
engine.SetMaxIdleConns(maxIdleConns)
|
||||
connMaxLifetime := config.Datasource.JsonData.Get("connMaxLifetime").MustInt(14400)
|
||||
engine.SetConnMaxLifetime(time.Duration(connMaxLifetime) * time.Second)
|
||||
|
||||
engineCache.versions[config.Datasource.Id] = config.Datasource.Version
|
||||
engineCache.cache[config.Datasource.Id] = engine
|
||||
@@ -116,9 +120,7 @@ func (e *sqlQueryEndpoint) Query(ctx context.Context, dsInfo *models.DataSource,
|
||||
Results: make(map[string]*QueryResult),
|
||||
}
|
||||
|
||||
session := e.engine.NewSession()
|
||||
defer session.Close()
|
||||
db := session.DB()
|
||||
var wg sync.WaitGroup
|
||||
|
||||
for _, query := range tsdbQuery.Queries {
|
||||
rawSQL := query.Model.Get("rawSql").MustString()
|
||||
@@ -145,31 +147,41 @@ func (e *sqlQueryEndpoint) Query(ctx context.Context, dsInfo *models.DataSource,
|
||||
|
||||
queryResult.Meta.Set("sql", rawSQL)
|
||||
|
||||
rows, err := db.Query(rawSQL)
|
||||
if err != nil {
|
||||
queryResult.Error = err
|
||||
continue
|
||||
}
|
||||
wg.Add(1)
|
||||
|
||||
defer rows.Close()
|
||||
go func(rawSQL string, query *Query, queryResult *QueryResult) {
|
||||
defer wg.Done()
|
||||
session := e.engine.NewSession()
|
||||
defer session.Close()
|
||||
db := session.DB()
|
||||
|
||||
format := query.Model.Get("format").MustString("time_series")
|
||||
|
||||
switch format {
|
||||
case "time_series":
|
||||
err := e.transformToTimeSeries(query, rows, queryResult, tsdbQuery)
|
||||
rows, err := db.Query(rawSQL)
|
||||
if err != nil {
|
||||
queryResult.Error = err
|
||||
continue
|
||||
return
|
||||
}
|
||||
case "table":
|
||||
err := e.transformToTable(query, rows, queryResult, tsdbQuery)
|
||||
if err != nil {
|
||||
queryResult.Error = err
|
||||
continue
|
||||
|
||||
defer rows.Close()
|
||||
|
||||
format := query.Model.Get("format").MustString("time_series")
|
||||
|
||||
switch format {
|
||||
case "time_series":
|
||||
err := e.transformToTimeSeries(query, rows, queryResult, tsdbQuery)
|
||||
if err != nil {
|
||||
queryResult.Error = err
|
||||
return
|
||||
}
|
||||
case "table":
|
||||
err := e.transformToTable(query, rows, queryResult, tsdbQuery)
|
||||
if err != nil {
|
||||
queryResult.Error = err
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}(rawSQL, query, queryResult)
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
return result, nil
|
||||
}
|
||||
@@ -184,6 +196,10 @@ var Interpolate = func(query *Query, timeRange *TimeRange, sql string) (string,
|
||||
|
||||
sql = strings.Replace(sql, "$__interval_ms", strconv.FormatInt(interval.Milliseconds(), 10), -1)
|
||||
sql = strings.Replace(sql, "$__interval", interval.Text, -1)
|
||||
sql = strings.Replace(sql, "$__timeFrom()", fmt.Sprintf("'%s'", timeRange.GetFromAsTimeUTC().Format(time.RFC3339)), -1)
|
||||
sql = strings.Replace(sql, "$__timeTo()", fmt.Sprintf("'%s'", timeRange.GetToAsTimeUTC().Format(time.RFC3339)), -1)
|
||||
sql = strings.Replace(sql, "$__unixEpochFrom()", fmt.Sprintf("%d", timeRange.GetFromAsSecondsEpoch()), -1)
|
||||
sql = strings.Replace(sql, "$__unixEpochTo()", fmt.Sprintf("%d", timeRange.GetToAsSecondsEpoch()), -1)
|
||||
|
||||
return sql, nil
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package tsdb
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -43,6 +44,34 @@ func TestSqlEngine(t *testing.T) {
|
||||
So(sql, ShouldEqual, "select 60000 ")
|
||||
})
|
||||
|
||||
Convey("interpolate __timeFrom function", func() {
|
||||
sql, err := Interpolate(query, timeRange, "select $__timeFrom()")
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(sql, ShouldEqual, fmt.Sprintf("select '%s'", from.Format(time.RFC3339)))
|
||||
})
|
||||
|
||||
Convey("interpolate __timeTo function", func() {
|
||||
sql, err := Interpolate(query, timeRange, "select $__timeTo()")
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(sql, ShouldEqual, fmt.Sprintf("select '%s'", to.Format(time.RFC3339)))
|
||||
})
|
||||
|
||||
Convey("interpolate __unixEpochFrom function", func() {
|
||||
sql, err := Interpolate(query, timeRange, "select $__unixEpochFrom()")
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(sql, ShouldEqual, fmt.Sprintf("select %d", from.Unix()))
|
||||
})
|
||||
|
||||
Convey("interpolate __unixEpochTo function", func() {
|
||||
sql, err := Interpolate(query, timeRange, "select $__unixEpochTo()")
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(sql, ShouldEqual, fmt.Sprintf("select %d", to.Unix()))
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
Convey("Given row values with time.Time as time columns", func() {
|
||||
|
||||
@@ -159,6 +159,39 @@ func (e *StackdriverExecutor) buildQueries(tsdbQuery *tsdb.TsdbQuery) ([]*Stackd
|
||||
return stackdriverQueries, nil
|
||||
}
|
||||
|
||||
func reverse(s string) string {
|
||||
chars := []rune(s)
|
||||
for i, j := 0, len(chars)-1; i < j; i, j = i+1, j-1 {
|
||||
chars[i], chars[j] = chars[j], chars[i]
|
||||
}
|
||||
return string(chars)
|
||||
}
|
||||
|
||||
func interpolateFilterWildcards(value string) string {
|
||||
re := regexp.MustCompile("[*]")
|
||||
matches := len(re.FindAllStringIndex(value, -1))
|
||||
if matches == 2 && strings.HasSuffix(value, "*") && strings.HasPrefix(value, "*") {
|
||||
value = strings.Replace(value, "*", "", -1)
|
||||
value = fmt.Sprintf(`has_substring("%s")`, value)
|
||||
} else if matches == 1 && strings.HasPrefix(value, "*") {
|
||||
value = strings.Replace(value, "*", "", 1)
|
||||
value = fmt.Sprintf(`ends_with("%s")`, value)
|
||||
} else if matches == 1 && strings.HasSuffix(value, "*") {
|
||||
value = reverse(strings.Replace(reverse(value), "*", "", 1))
|
||||
value = fmt.Sprintf(`starts_with("%s")`, value)
|
||||
} else if matches != 0 {
|
||||
re := regexp.MustCompile(`[-\/^$+?.()|[\]{}]`)
|
||||
value = string(re.ReplaceAllFunc([]byte(value), func(in []byte) []byte {
|
||||
return []byte(strings.Replace(string(in), string(in), `\\`+string(in), 1))
|
||||
}))
|
||||
value = strings.Replace(value, "*", ".*", -1)
|
||||
value = strings.Replace(value, `"`, `\\"`, -1)
|
||||
value = fmt.Sprintf(`monitoring.regex.full_match("^%s$")`, value)
|
||||
}
|
||||
|
||||
return value
|
||||
}
|
||||
|
||||
func buildFilterString(metricType string, filterParts []interface{}) string {
|
||||
filterString := ""
|
||||
for i, part := range filterParts {
|
||||
@@ -166,7 +199,15 @@ func buildFilterString(metricType string, filterParts []interface{}) string {
|
||||
if part == "AND" {
|
||||
filterString += " "
|
||||
} else if mod == 2 {
|
||||
filterString += fmt.Sprintf(`"%s"`, part)
|
||||
operator := filterParts[i-1]
|
||||
if operator == "=~" || operator == "!=~" {
|
||||
filterString = reverse(strings.Replace(reverse(filterString), "~", "", 1))
|
||||
filterString += fmt.Sprintf(`monitoring.regex.full_match("%s")`, part)
|
||||
} else if strings.Contains(part.(string), "*") {
|
||||
filterString += interpolateFilterWildcards(part.(string))
|
||||
} else {
|
||||
filterString += fmt.Sprintf(`"%s"`, part)
|
||||
}
|
||||
} else {
|
||||
filterString += part.(string)
|
||||
}
|
||||
@@ -300,29 +341,6 @@ func (e *StackdriverExecutor) parseResponse(queryRes *tsdb.QueryResult, data Sta
|
||||
for _, series := range data.TimeSeries {
|
||||
points := make([]tsdb.TimePoint, 0)
|
||||
|
||||
// reverse the order to be ascending
|
||||
for i := len(series.Points) - 1; i >= 0; i-- {
|
||||
point := series.Points[i]
|
||||
value := point.Value.DoubleValue
|
||||
|
||||
if series.ValueType == "INT64" {
|
||||
parsedValue, err := strconv.ParseFloat(point.Value.IntValue, 64)
|
||||
if err == nil {
|
||||
value = parsedValue
|
||||
}
|
||||
}
|
||||
|
||||
if series.ValueType == "BOOL" {
|
||||
if point.Value.BoolValue {
|
||||
value = 1
|
||||
} else {
|
||||
value = 0
|
||||
}
|
||||
}
|
||||
|
||||
points = append(points, tsdb.NewTimePoint(null.FloatFrom(value), float64((point.Interval.EndTime).Unix())*1000))
|
||||
}
|
||||
|
||||
defaultMetricName := series.Metric.Type
|
||||
|
||||
for key, value := range series.Metric.Labels {
|
||||
@@ -338,18 +356,87 @@ func (e *StackdriverExecutor) parseResponse(queryRes *tsdb.QueryResult, data Sta
|
||||
if !containsLabel(resourceLabels[key], value) {
|
||||
resourceLabels[key] = append(resourceLabels[key], value)
|
||||
}
|
||||
|
||||
if containsLabel(query.GroupBys, "resource.label."+key) {
|
||||
defaultMetricName += " " + value
|
||||
}
|
||||
}
|
||||
|
||||
metricName := formatLegendKeys(series.Metric.Type, defaultMetricName, series.Metric.Labels, series.Resource.Labels, query)
|
||||
// reverse the order to be ascending
|
||||
if series.ValueType != "DISTRIBUTION" {
|
||||
for i := len(series.Points) - 1; i >= 0; i-- {
|
||||
point := series.Points[i]
|
||||
value := point.Value.DoubleValue
|
||||
|
||||
queryRes.Series = append(queryRes.Series, &tsdb.TimeSeries{
|
||||
Name: metricName,
|
||||
Points: points,
|
||||
})
|
||||
if series.ValueType == "INT64" {
|
||||
parsedValue, err := strconv.ParseFloat(point.Value.IntValue, 64)
|
||||
if err == nil {
|
||||
value = parsedValue
|
||||
}
|
||||
}
|
||||
|
||||
if series.ValueType == "BOOL" {
|
||||
if point.Value.BoolValue {
|
||||
value = 1
|
||||
} else {
|
||||
value = 0
|
||||
}
|
||||
}
|
||||
|
||||
points = append(points, tsdb.NewTimePoint(null.FloatFrom(value), float64((point.Interval.EndTime).Unix())*1000))
|
||||
}
|
||||
|
||||
metricName := formatLegendKeys(series.Metric.Type, defaultMetricName, series.Metric.Labels, series.Resource.Labels, make(map[string]string), query)
|
||||
|
||||
queryRes.Series = append(queryRes.Series, &tsdb.TimeSeries{
|
||||
Name: metricName,
|
||||
Points: points,
|
||||
})
|
||||
} else {
|
||||
buckets := make(map[int]*tsdb.TimeSeries)
|
||||
|
||||
for i := len(series.Points) - 1; i >= 0; i-- {
|
||||
point := series.Points[i]
|
||||
if len(point.Value.DistributionValue.BucketCounts) == 0 {
|
||||
continue
|
||||
}
|
||||
maxKey := 0
|
||||
for i := 0; i < len(point.Value.DistributionValue.BucketCounts); i++ {
|
||||
value, err := strconv.ParseFloat(point.Value.DistributionValue.BucketCounts[i], 64)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if _, ok := buckets[i]; !ok {
|
||||
// set lower bounds
|
||||
// https://cloud.google.com/monitoring/api/ref_v3/rest/v3/TimeSeries#Distribution
|
||||
bucketBound := calcBucketBound(point.Value.DistributionValue.BucketOptions, i)
|
||||
additionalLabels := map[string]string{"bucket": bucketBound}
|
||||
buckets[i] = &tsdb.TimeSeries{
|
||||
Name: formatLegendKeys(series.Metric.Type, defaultMetricName, series.Metric.Labels, series.Resource.Labels, additionalLabels, query),
|
||||
Points: make([]tsdb.TimePoint, 0),
|
||||
}
|
||||
if maxKey < i {
|
||||
maxKey = i
|
||||
}
|
||||
}
|
||||
buckets[i].Points = append(buckets[i].Points, tsdb.NewTimePoint(null.FloatFrom(value), float64((point.Interval.EndTime).Unix())*1000))
|
||||
}
|
||||
|
||||
// fill empty bucket
|
||||
for i := 0; i < maxKey; i++ {
|
||||
if _, ok := buckets[i]; !ok {
|
||||
bucketBound := calcBucketBound(point.Value.DistributionValue.BucketOptions, i)
|
||||
additionalLabels := map[string]string{"bucket": bucketBound}
|
||||
buckets[i] = &tsdb.TimeSeries{
|
||||
Name: formatLegendKeys(series.Metric.Type, defaultMetricName, series.Metric.Labels, series.Resource.Labels, additionalLabels, query),
|
||||
Points: make([]tsdb.TimePoint, 0),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for i := 0; i < len(buckets); i++ {
|
||||
queryRes.Series = append(queryRes.Series, buckets[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
queryRes.Meta.Set("resourceLabels", resourceLabels)
|
||||
@@ -368,7 +455,7 @@ func containsLabel(labels []string, newLabel string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func formatLegendKeys(metricType string, defaultMetricName string, metricLabels map[string]string, resourceLabels map[string]string, query *StackdriverQuery) string {
|
||||
func formatLegendKeys(metricType string, defaultMetricName string, metricLabels map[string]string, resourceLabels map[string]string, additionalLabels map[string]string, query *StackdriverQuery) string {
|
||||
if query.AliasBy == "" {
|
||||
return defaultMetricName
|
||||
}
|
||||
@@ -400,6 +487,10 @@ func formatLegendKeys(metricType string, defaultMetricName string, metricLabels
|
||||
return []byte(val)
|
||||
}
|
||||
|
||||
if val, exists := additionalLabels[metaPartName]; exists {
|
||||
return []byte(val)
|
||||
}
|
||||
|
||||
return in
|
||||
})
|
||||
|
||||
@@ -425,6 +516,22 @@ func replaceWithMetricPart(metaPartName string, metricType string) []byte {
|
||||
return nil
|
||||
}
|
||||
|
||||
func calcBucketBound(bucketOptions StackdriverBucketOptions, n int) string {
|
||||
bucketBound := "0"
|
||||
if n == 0 {
|
||||
return bucketBound
|
||||
}
|
||||
|
||||
if bucketOptions.LinearBuckets != nil {
|
||||
bucketBound = strconv.FormatInt(bucketOptions.LinearBuckets.Offset+(bucketOptions.LinearBuckets.Width*int64(n-1)), 10)
|
||||
} else if bucketOptions.ExponentialBuckets != nil {
|
||||
bucketBound = strconv.FormatInt(int64(bucketOptions.ExponentialBuckets.Scale*math.Pow(bucketOptions.ExponentialBuckets.GrowthFactor, float64(n-1))), 10)
|
||||
} else if bucketOptions.ExplicitBuckets != nil {
|
||||
bucketBound = strconv.FormatInt(bucketOptions.ExplicitBuckets.Bounds[(n-1)], 10)
|
||||
}
|
||||
return bucketBound
|
||||
}
|
||||
|
||||
func (e *StackdriverExecutor) createRequest(ctx context.Context, dsInfo *models.DataSource) (*http.Request, error) {
|
||||
u, _ := url.Parse(dsInfo.Url)
|
||||
u.Path = path.Join(u.Path, "render")
|
||||
|
||||
@@ -4,6 +4,8 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -341,6 +343,137 @@ func TestStackdriver(t *testing.T) {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Convey("when data from query is distribution", func() {
|
||||
data, err := loadTestFile("./test-data/3-series-response-distribution.json")
|
||||
So(err, ShouldBeNil)
|
||||
So(len(data.TimeSeries), ShouldEqual, 1)
|
||||
|
||||
res := &tsdb.QueryResult{Meta: simplejson.New(), RefId: "A"}
|
||||
query := &StackdriverQuery{AliasBy: "{{bucket}}"}
|
||||
err = executor.parseResponse(res, data, query)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(len(res.Series), ShouldEqual, 11)
|
||||
for i := 0; i < 11; i++ {
|
||||
if i == 0 {
|
||||
So(res.Series[i].Name, ShouldEqual, "0")
|
||||
} else {
|
||||
So(res.Series[i].Name, ShouldEqual, strconv.FormatInt(int64(math.Pow(float64(2), float64(i-1))), 10))
|
||||
}
|
||||
So(len(res.Series[i].Points), ShouldEqual, 3)
|
||||
}
|
||||
|
||||
Convey("timestamps should be in ascending order", func() {
|
||||
So(res.Series[0].Points[0][1].Float64, ShouldEqual, 1536668940000)
|
||||
So(res.Series[0].Points[1][1].Float64, ShouldEqual, 1536669000000)
|
||||
So(res.Series[0].Points[2][1].Float64, ShouldEqual, 1536669060000)
|
||||
})
|
||||
|
||||
Convey("value should be correct", func() {
|
||||
So(res.Series[8].Points[0][0].Float64, ShouldEqual, 1)
|
||||
So(res.Series[9].Points[0][0].Float64, ShouldEqual, 1)
|
||||
So(res.Series[10].Points[0][0].Float64, ShouldEqual, 1)
|
||||
So(res.Series[8].Points[1][0].Float64, ShouldEqual, 0)
|
||||
So(res.Series[9].Points[1][0].Float64, ShouldEqual, 0)
|
||||
So(res.Series[10].Points[1][0].Float64, ShouldEqual, 1)
|
||||
So(res.Series[8].Points[2][0].Float64, ShouldEqual, 0)
|
||||
So(res.Series[9].Points[2][0].Float64, ShouldEqual, 1)
|
||||
So(res.Series[10].Points[2][0].Float64, ShouldEqual, 0)
|
||||
})
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
Convey("when interpolating filter wildcards", func() {
|
||||
Convey("and wildcard is used in the beginning and the end of the word", func() {
|
||||
Convey("and theres no wildcard in the middle of the word", func() {
|
||||
value := interpolateFilterWildcards("*-central1*")
|
||||
So(value, ShouldEqual, `has_substring("-central1")`)
|
||||
})
|
||||
Convey("and there is a wildcard in the middle of the word", func() {
|
||||
value := interpolateFilterWildcards("*-cent*ral1*")
|
||||
So(value, ShouldNotStartWith, `has_substring`)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("and wildcard is used in the beginning of the word", func() {
|
||||
Convey("and there is not a wildcard elsewhere in the word", func() {
|
||||
value := interpolateFilterWildcards("*-central1")
|
||||
So(value, ShouldEqual, `ends_with("-central1")`)
|
||||
})
|
||||
Convey("and there is a wildcard elsewhere in the word", func() {
|
||||
value := interpolateFilterWildcards("*-cent*al1")
|
||||
So(value, ShouldNotStartWith, `ends_with`)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("and wildcard is used at the end of the word", func() {
|
||||
Convey("and there is not a wildcard elsewhere in the word", func() {
|
||||
value := interpolateFilterWildcards("us-central*")
|
||||
So(value, ShouldEqual, `starts_with("us-central")`)
|
||||
})
|
||||
Convey("and there is a wildcard elsewhere in the word", func() {
|
||||
value := interpolateFilterWildcards("*us-central*")
|
||||
So(value, ShouldNotStartWith, `starts_with`)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("and wildcard is used in the middle of the word", func() {
|
||||
Convey("and there is only one wildcard", func() {
|
||||
value := interpolateFilterWildcards("us-ce*tral1-b")
|
||||
So(value, ShouldEqual, `monitoring.regex.full_match("^us\\-ce.*tral1\\-b$")`)
|
||||
})
|
||||
|
||||
Convey("and there is more than one wildcard", func() {
|
||||
value := interpolateFilterWildcards("us-ce*tra*1-b")
|
||||
So(value, ShouldEqual, `monitoring.regex.full_match("^us\\-ce.*tra.*1\\-b$")`)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("and wildcard is used in the middle of the word and in the beginning of the word", func() {
|
||||
value := interpolateFilterWildcards("*s-ce*tral1-b")
|
||||
So(value, ShouldEqual, `monitoring.regex.full_match("^.*s\\-ce.*tral1\\-b$")`)
|
||||
})
|
||||
|
||||
Convey("and wildcard is used in the middle of the word and in the ending of the word", func() {
|
||||
value := interpolateFilterWildcards("us-ce*tral1-*")
|
||||
So(value, ShouldEqual, `monitoring.regex.full_match("^us\\-ce.*tral1\\-.*$")`)
|
||||
})
|
||||
|
||||
Convey("and no wildcard is used", func() {
|
||||
value := interpolateFilterWildcards("us-central1-a}")
|
||||
So(value, ShouldEqual, `us-central1-a}`)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("when building filter string", func() {
|
||||
Convey("and theres no regex operator", func() {
|
||||
Convey("and there are wildcards in a filter value", func() {
|
||||
filterParts := []interface{}{"zone", "=", "*-central1*"}
|
||||
value := buildFilterString("somemetrictype", filterParts)
|
||||
So(value, ShouldEqual, `metric.type="somemetrictype" zone=has_substring("-central1")`)
|
||||
})
|
||||
|
||||
Convey("and there are no wildcards in any filter value", func() {
|
||||
filterParts := []interface{}{"zone", "!=", "us-central1-a"}
|
||||
value := buildFilterString("somemetrictype", filterParts)
|
||||
So(value, ShouldEqual, `metric.type="somemetrictype" zone!="us-central1-a"`)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("and there is a regex operator", func() {
|
||||
filterParts := []interface{}{"zone", "=~", "us-central1-a~"}
|
||||
value := buildFilterString("somemetrictype", filterParts)
|
||||
Convey("it should remove the ~ character from the operator that belongs to the value", func() {
|
||||
So(value, ShouldNotContainSubstring, `=~`)
|
||||
So(value, ShouldContainSubstring, `zone=`)
|
||||
})
|
||||
|
||||
Convey("it should insert monitoring.regex.full_match before filter value", func() {
|
||||
So(value, ShouldContainSubstring, `zone=monitoring.regex.full_match("us-central1-a~")`)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
{
|
||||
"timeSeries": [
|
||||
{
|
||||
"metric": {
|
||||
"type": "loadbalancing.googleapis.com\/https\/backend_latencies"
|
||||
},
|
||||
"resource": {
|
||||
"type": "https_lb_rule",
|
||||
"labels": {
|
||||
"project_id": "grafana-prod"
|
||||
}
|
||||
},
|
||||
"metricKind": "DELTA",
|
||||
"valueType": "DISTRIBUTION",
|
||||
"points": [
|
||||
{
|
||||
"interval": {
|
||||
"startTime": "2018-09-11T12:30:00Z",
|
||||
"endTime": "2018-09-11T12:31:00Z"
|
||||
},
|
||||
"value": {
|
||||
"distributionValue": {
|
||||
"count": "1",
|
||||
"bucketOptions": {
|
||||
"exponentialBuckets": {
|
||||
"numFiniteBuckets": 10,
|
||||
"growthFactor": 2,
|
||||
"scale": 1
|
||||
}
|
||||
},
|
||||
"bucketCounts": [
|
||||
"0",
|
||||
"0",
|
||||
"0",
|
||||
"0",
|
||||
"0",
|
||||
"0",
|
||||
"0",
|
||||
"0",
|
||||
"0",
|
||||
"1",
|
||||
"0"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"interval": {
|
||||
"startTime": "2018-09-11T12:29:00Z",
|
||||
"endTime": "2018-09-11T12:30:00Z"
|
||||
},
|
||||
"value": {
|
||||
"distributionValue": {
|
||||
"count": "1",
|
||||
"bucketOptions": {
|
||||
"exponentialBuckets": {
|
||||
"numFiniteBuckets": 10,
|
||||
"growthFactor": 2,
|
||||
"scale": 1
|
||||
}
|
||||
},
|
||||
"bucketCounts": [
|
||||
"0",
|
||||
"0",
|
||||
"0",
|
||||
"0",
|
||||
"0",
|
||||
"0",
|
||||
"0",
|
||||
"0",
|
||||
"0",
|
||||
"0",
|
||||
"1"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"interval": {
|
||||
"startTime": "2018-09-11T12:28:00Z",
|
||||
"endTime": "2018-09-11T12:29:00Z"
|
||||
},
|
||||
"value": {
|
||||
"distributionValue": {
|
||||
"count": "3",
|
||||
"bucketOptions": {
|
||||
"exponentialBuckets": {
|
||||
"numFiniteBuckets": 10,
|
||||
"growthFactor": 2,
|
||||
"scale": 1
|
||||
}
|
||||
},
|
||||
"bucketCounts": [
|
||||
"0",
|
||||
"0",
|
||||
"0",
|
||||
"0",
|
||||
"0",
|
||||
"0",
|
||||
"0",
|
||||
"0",
|
||||
"1",
|
||||
"1",
|
||||
"1"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -14,6 +14,22 @@ type StackdriverQuery struct {
|
||||
AliasBy string
|
||||
}
|
||||
|
||||
type StackdriverBucketOptions struct {
|
||||
LinearBuckets *struct {
|
||||
NumFiniteBuckets int64 `json:"numFiniteBuckets"`
|
||||
Width int64 `json:"width"`
|
||||
Offset int64 `json:"offset"`
|
||||
} `json:"linearBuckets"`
|
||||
ExponentialBuckets *struct {
|
||||
NumFiniteBuckets int64 `json:"numFiniteBuckets"`
|
||||
GrowthFactor float64 `json:"growthFactor"`
|
||||
Scale float64 `json:"scale"`
|
||||
} `json:"exponentialBuckets"`
|
||||
ExplicitBuckets *struct {
|
||||
Bounds []int64 `json:"bounds"`
|
||||
} `json:"explicitBuckets"`
|
||||
}
|
||||
|
||||
// StackdriverResponse is the data returned from the external Google Stackdriver API
|
||||
type StackdriverResponse struct {
|
||||
TimeSeries []struct {
|
||||
@@ -33,10 +49,26 @@ type StackdriverResponse struct {
|
||||
EndTime time.Time `json:"endTime"`
|
||||
} `json:"interval"`
|
||||
Value struct {
|
||||
DoubleValue float64 `json:"doubleValue"`
|
||||
StringValue string `json:"stringValue"`
|
||||
BoolValue bool `json:"boolValue"`
|
||||
IntValue string `json:"int64Value"`
|
||||
DoubleValue float64 `json:"doubleValue"`
|
||||
StringValue string `json:"stringValue"`
|
||||
BoolValue bool `json:"boolValue"`
|
||||
IntValue string `json:"int64Value"`
|
||||
DistributionValue struct {
|
||||
Count string `json:"count"`
|
||||
Mean float64 `json:"mean"`
|
||||
SumOfSquaredDeviation float64 `json:"sumOfSquaredDeviation"`
|
||||
Range struct {
|
||||
Min int `json:"min"`
|
||||
Max int `json:"max"`
|
||||
} `json:"range"`
|
||||
BucketOptions StackdriverBucketOptions `json:"bucketOptions"`
|
||||
BucketCounts []string `json:"bucketCounts"`
|
||||
Examplars []struct {
|
||||
Value float64 `json:"value"`
|
||||
Timestamp string `json:"timestamp"`
|
||||
// attachments
|
||||
} `json:"examplars"`
|
||||
} `json:"distributionValue"`
|
||||
} `json:"value"`
|
||||
} `json:"points"`
|
||||
} `json:"timeSeries"`
|
||||
|
||||
Reference in New Issue
Block a user