mirror of
https://github.com/grafana/grafana.git
synced 2024-11-22 08:56:43 -06:00
Merge branch 'master' into alerting_definitions
This commit is contained in:
commit
26d93d7130
@ -8,6 +8,11 @@
|
||||
* **Theme**: Add default theme to config file [#5011](https://github.com/grafana/grafana/pull/5011)
|
||||
* **Page Footer**: Added page footer with links to docs, shows Grafana version and info if new version is available, closes [#4889](https://github.com/grafana/grafana/pull/4889)
|
||||
* **InfluxDB**: Add spread function, closes [#5211](https://github.com/grafana/grafana/issues/5211)
|
||||
* **Scripts**: Use restart instead of start for deb package script, closes [#5282](https://github.com/grafana/grafana/pull/5282)
|
||||
* **Logging**: Moved to structured logging lib, and moved to component specific level filters via config file, closes [#4590](https://github.com/grafana/grafana/issues/4590)
|
||||
|
||||
## Breaking changes
|
||||
* **Logging** : Changed default logging output format (now structured into message, and key value pairs, with logger key acting as component). You can also no change in config to json log ouput.
|
||||
|
||||
# 3.0.4 Patch release (2016-05-25)
|
||||
* **Panel**: Fixed blank dashboard issue when switching to other dashboard while in fullscreen edit mode, fixes [#5163](https://github.com/grafana/grafana/pull/5163)
|
||||
|
@ -251,18 +251,23 @@ templates_pattern = emails/*.html
|
||||
# Use space to separate multiple modes, e.g. "console file"
|
||||
mode = console, file
|
||||
|
||||
# Either "Trace", "Debug", "Info", "Warn", "Error", "Critical", default is "Info"
|
||||
level = Info
|
||||
# Either "trace", "debug", "info", "warn", "error", "critical", default is "info"
|
||||
level = info
|
||||
|
||||
# For "console" mode only
|
||||
[log.console]
|
||||
level =
|
||||
# Set formatting to "false" to disable color formatting of console logs
|
||||
formatting = false
|
||||
|
||||
# log line format, valid options are text, console and json
|
||||
format = console
|
||||
|
||||
# For "file" mode only
|
||||
[log.file]
|
||||
level =
|
||||
|
||||
# log line format, valid options are text, console and json
|
||||
format = text
|
||||
|
||||
# This enables automated log rotate(switch of following options), default is true
|
||||
log_rotate = true
|
||||
|
||||
@ -280,6 +285,10 @@ max_days = 7
|
||||
|
||||
[log.syslog]
|
||||
level =
|
||||
|
||||
# log line format, valid options are text, console and json
|
||||
format = text
|
||||
|
||||
# Syslog network type and address. This can be udp, tcp, or unix. If left blank, the default unix endpoints will be used.
|
||||
network =
|
||||
address =
|
||||
@ -290,7 +299,8 @@ facility =
|
||||
# Syslog tag. By default, the process' argv[0] is used.
|
||||
tag =
|
||||
|
||||
#################################### AMPQ Event Publisher ##########################
|
||||
|
||||
#################################### AMQP Event Publisher ##########################
|
||||
[event_publisher]
|
||||
enabled = false
|
||||
rabbitmq_url = amqp://localhost/
|
||||
|
@ -230,19 +230,26 @@ check_for_updates = true
|
||||
#################################### Logging ##########################
|
||||
[log]
|
||||
# Either "console", "file", "syslog". Default is console and file
|
||||
# Use comma to separate multiple modes, e.g. "console, file"
|
||||
# Use space to separate multiple modes, e.g. "console file"
|
||||
;mode = console, file
|
||||
|
||||
# Either "Trace", "Debug", "Info", "Warn", "Error", "Critical", default is "Info"
|
||||
;level = Info
|
||||
# Either "trace", "debug", "info", "warn", "error", "critical", default is "info"
|
||||
;level = info
|
||||
|
||||
# For "console" mode only
|
||||
[log.console]
|
||||
;level =
|
||||
|
||||
# log line format, valid options are text, console and json
|
||||
;format = console
|
||||
|
||||
# For "file" mode only
|
||||
[log.file]
|
||||
;level =
|
||||
|
||||
# log line format, valid options are text, console and json
|
||||
;format = text
|
||||
|
||||
# This enables automated log rotate(switch of following options), default is true
|
||||
;log_rotate = true
|
||||
|
||||
@ -258,7 +265,24 @@ check_for_updates = true
|
||||
# Expired days of log file(delete after max days), default is 7
|
||||
;max_days = 7
|
||||
|
||||
#################################### AMPQ Event Publisher ##########################
|
||||
[log.syslog]
|
||||
;level =
|
||||
|
||||
# log line format, valid options are text, console and json
|
||||
;format = text
|
||||
|
||||
# Syslog network type and address. This can be udp, tcp, or unix. If left blank, the default unix endpoints will be used.
|
||||
;network =
|
||||
;address =
|
||||
|
||||
# Syslog facility. user, daemon and local0 through local7 are valid.
|
||||
;facility =
|
||||
|
||||
# Syslog tag. By default, the process' argv[0] is used.
|
||||
;tag =
|
||||
|
||||
|
||||
#################################### AMQP Event Publisher ##########################
|
||||
[event_publisher]
|
||||
;enabled = false
|
||||
;rabbitmq_url = amqp://localhost/
|
||||
|
@ -7,12 +7,12 @@ set -e
|
||||
startGrafana() {
|
||||
if [ -x /bin/systemctl ]; then
|
||||
/bin/systemctl daemon-reload
|
||||
/bin/systemctl start grafana-server
|
||||
/bin/systemctl restart grafana-server
|
||||
elif [ -x "/etc/init.d/grafana-server" ]; then
|
||||
if [ -x "`which invoke-rc.d 2>/dev/null`" ]; then
|
||||
invoke-rc.d grafana-server start || true
|
||||
invoke-rc.d grafana-server restart || true
|
||||
else
|
||||
/etc/init.d/grafana-server start || true
|
||||
/etc/init.d/grafana-server restart || true
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
)
|
||||
|
||||
func GetTestMetrics(c *middleware.Context) {
|
||||
func GetTestMetrics(c *middleware.Context) Response {
|
||||
from := c.QueryInt64("from")
|
||||
to := c.QueryInt64("to")
|
||||
maxDataPoints := c.QueryInt64("maxDataPoints")
|
||||
@ -37,7 +37,7 @@ func GetTestMetrics(c *middleware.Context) {
|
||||
result.Data[seriesIndex].DataPoints = points
|
||||
}
|
||||
|
||||
c.JSON(200, &result)
|
||||
return Json(200, &result)
|
||||
}
|
||||
|
||||
func GetInternalMetrics(c *middleware.Context) Response {
|
||||
|
@ -13,6 +13,7 @@ import (
|
||||
"gopkg.in/ini.v1"
|
||||
|
||||
"github.com/inconshreveable/log15"
|
||||
"github.com/inconshreveable/log15/term"
|
||||
)
|
||||
|
||||
var Root log15.Logger
|
||||
@ -82,16 +83,17 @@ func Close() {
|
||||
}
|
||||
|
||||
var logLevels = map[string]log15.Lvl{
|
||||
"Trace": log15.LvlDebug,
|
||||
"Debug": log15.LvlDebug,
|
||||
"Info": log15.LvlInfo,
|
||||
"Warn": log15.LvlWarn,
|
||||
"Error": log15.LvlError,
|
||||
"Critical": log15.LvlCrit,
|
||||
"trace": log15.LvlDebug,
|
||||
"debug": log15.LvlDebug,
|
||||
"info": log15.LvlInfo,
|
||||
"warn": log15.LvlWarn,
|
||||
"error": log15.LvlError,
|
||||
"critical": log15.LvlCrit,
|
||||
}
|
||||
|
||||
func getLogLevelFromConfig(key string, defaultName string, cfg *ini.File) (string, log15.Lvl) {
|
||||
levelName := cfg.Section(key).Key("level").In(defaultName, []string{"Trace", "Debug", "Info", "Warn", "Error", "Critical"})
|
||||
levelName := cfg.Section(key).Key("level").MustString("info")
|
||||
levelName = strings.ToLower(levelName)
|
||||
level := getLogLevelFromString(levelName)
|
||||
return levelName, level
|
||||
}
|
||||
@ -118,10 +120,26 @@ func getFilters(filterStrArray []string) map[string]log15.Lvl {
|
||||
return filterMap
|
||||
}
|
||||
|
||||
func getLogFormat(format string) log15.Format {
|
||||
switch format {
|
||||
case "console":
|
||||
if term.IsTty(os.Stdout.Fd()) {
|
||||
return log15.TerminalFormat()
|
||||
}
|
||||
return log15.LogfmtFormat()
|
||||
case "text":
|
||||
return log15.LogfmtFormat()
|
||||
case "json":
|
||||
return log15.JsonFormat()
|
||||
default:
|
||||
return log15.LogfmtFormat()
|
||||
}
|
||||
}
|
||||
|
||||
func ReadLoggingConfig(modes []string, logsPath string, cfg *ini.File) {
|
||||
Close()
|
||||
|
||||
defaultLevelName, _ := getLogLevelFromConfig("log", "Info", cfg)
|
||||
defaultLevelName, _ := getLogLevelFromConfig("log", "info", cfg)
|
||||
defaultFilters := getFilters(cfg.Section("log").Key("filters").Strings(" "))
|
||||
|
||||
handlers := make([]log15.Handler, 0)
|
||||
@ -136,18 +154,20 @@ func ReadLoggingConfig(modes []string, logsPath string, cfg *ini.File) {
|
||||
// Log level.
|
||||
_, level := getLogLevelFromConfig("log."+mode, defaultLevelName, cfg)
|
||||
modeFilters := getFilters(sec.Key("filters").Strings(" "))
|
||||
format := getLogFormat(sec.Key("format").MustString(""))
|
||||
|
||||
var handler log15.Handler
|
||||
|
||||
// Generate log configuration.
|
||||
switch mode {
|
||||
case "console":
|
||||
handler = log15.StdoutHandler
|
||||
handler = log15.StreamHandler(os.Stdout, format)
|
||||
case "file":
|
||||
fileName := sec.Key("file_name").MustString(filepath.Join(logsPath, "grafana.log"))
|
||||
os.MkdirAll(filepath.Dir(fileName), os.ModePerm)
|
||||
fileHandler := NewFileWriter()
|
||||
fileHandler.Filename = fileName
|
||||
fileHandler.Format = format
|
||||
fileHandler.Rotate = sec.Key("log_rotate").MustBool(true)
|
||||
fileHandler.Maxlines = sec.Key("max_lines").MustInt(1000000)
|
||||
fileHandler.Maxsize = 1 << uint(sec.Key("max_size_shift").MustInt(28))
|
||||
@ -157,15 +177,21 @@ func ReadLoggingConfig(modes []string, logsPath string, cfg *ini.File) {
|
||||
|
||||
loggersToClose = append(loggersToClose, fileHandler)
|
||||
handler = fileHandler
|
||||
case "syslog":
|
||||
sysLogHandler := NewSyslog()
|
||||
sysLogHandler.Format = format
|
||||
sysLogHandler.Network = sec.Key("network").MustString("")
|
||||
sysLogHandler.Address = sec.Key("address").MustString("")
|
||||
sysLogHandler.Facility = sec.Key("facility").MustString("local7")
|
||||
sysLogHandler.Tag = sec.Key("tag").MustString("")
|
||||
|
||||
// case "syslog":
|
||||
// LogConfigs[i] = util.DynMap{
|
||||
// "level": level,
|
||||
// "network": sec.Key("network").MustString(""),
|
||||
// "address": sec.Key("address").MustString(""),
|
||||
// "facility": sec.Key("facility").MustString("local7"),
|
||||
// "tag": sec.Key("tag").MustString(""),
|
||||
// }
|
||||
if err := sysLogHandler.Init(); err != nil {
|
||||
Root.Error("Failed to init syslog log handler", "error", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
loggersToClose = append(loggersToClose, sysLogHandler)
|
||||
handler = sysLogHandler
|
||||
}
|
||||
|
||||
for key, value := range defaultFilters {
|
||||
@ -174,10 +200,6 @@ func ReadLoggingConfig(modes []string, logsPath string, cfg *ini.File) {
|
||||
}
|
||||
}
|
||||
|
||||
for key, value := range modeFilters {
|
||||
fmt.Printf("key: %v, value: %v \n", key, value)
|
||||
}
|
||||
|
||||
handler = LogFilterHandler(level, modeFilters, handler)
|
||||
handlers = append(handlers, handler)
|
||||
}
|
||||
|
@ -2,95 +2,88 @@
|
||||
|
||||
package log
|
||||
|
||||
//
|
||||
// import (
|
||||
// "encoding/json"
|
||||
// "errors"
|
||||
// "log/syslog"
|
||||
// )
|
||||
//
|
||||
// type SyslogWriter struct {
|
||||
// syslog *syslog.Writer
|
||||
// Network string `json:"network"`
|
||||
// Address string `json:"address"`
|
||||
// Facility string `json:"facility"`
|
||||
// Tag string `json:"tag"`
|
||||
// }
|
||||
//
|
||||
// func NewSyslog() LoggerInterface {
|
||||
// return new(SyslogWriter)
|
||||
// }
|
||||
//
|
||||
// func (sw *SyslogWriter) Init(config string) error {
|
||||
// if err := json.Unmarshal([]byte(config), sw); err != nil {
|
||||
// return err
|
||||
// }
|
||||
//
|
||||
// prio, err := parseFacility(sw.Facility)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
//
|
||||
// w, err := syslog.Dial(sw.Network, sw.Address, prio, sw.Tag)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
//
|
||||
// sw.syslog = w
|
||||
// return nil
|
||||
// }
|
||||
//
|
||||
// func (sw *SyslogWriter) WriteMsg(msg string, skip int, level LogLevel) error {
|
||||
// var err error
|
||||
//
|
||||
// switch level {
|
||||
// case TRACE, DEBUG:
|
||||
// err = sw.syslog.Debug(msg)
|
||||
// case INFO:
|
||||
// err = sw.syslog.Info(msg)
|
||||
// case WARN:
|
||||
// err = sw.syslog.Warning(msg)
|
||||
// case ERROR:
|
||||
// err = sw.syslog.Err(msg)
|
||||
// case CRITICAL:
|
||||
// err = sw.syslog.Crit(msg)
|
||||
// case FATAL:
|
||||
// err = sw.syslog.Alert(msg)
|
||||
// default:
|
||||
// err = errors.New("invalid syslog level")
|
||||
// }
|
||||
//
|
||||
// return err
|
||||
// }
|
||||
//
|
||||
// func (sw *SyslogWriter) Destroy() {
|
||||
// sw.syslog.Close()
|
||||
// }
|
||||
//
|
||||
// func (sw *SyslogWriter) Flush() {}
|
||||
//
|
||||
// var facilities = map[string]syslog.Priority{
|
||||
// "user": syslog.LOG_USER,
|
||||
// "daemon": syslog.LOG_DAEMON,
|
||||
// "local0": syslog.LOG_LOCAL0,
|
||||
// "local1": syslog.LOG_LOCAL1,
|
||||
// "local2": syslog.LOG_LOCAL2,
|
||||
// "local3": syslog.LOG_LOCAL3,
|
||||
// "local4": syslog.LOG_LOCAL4,
|
||||
// "local5": syslog.LOG_LOCAL5,
|
||||
// "local6": syslog.LOG_LOCAL6,
|
||||
// "local7": syslog.LOG_LOCAL7,
|
||||
// }
|
||||
//
|
||||
// func parseFacility(facility string) (syslog.Priority, error) {
|
||||
// prio, ok := facilities[facility]
|
||||
// if !ok {
|
||||
// return syslog.LOG_LOCAL0, errors.New("invalid syslog facility")
|
||||
// }
|
||||
//
|
||||
// return prio, nil
|
||||
// }
|
||||
//
|
||||
// func init() {
|
||||
// Register("syslog", NewSyslog)
|
||||
// }
|
||||
import (
|
||||
"errors"
|
||||
"log/syslog"
|
||||
|
||||
"github.com/inconshreveable/log15"
|
||||
)
|
||||
|
||||
type SysLogHandler struct {
|
||||
syslog *syslog.Writer
|
||||
Network string
|
||||
Address string
|
||||
Facility string
|
||||
Tag string
|
||||
Format log15.Format
|
||||
}
|
||||
|
||||
func NewSyslog() *SysLogHandler {
|
||||
return &SysLogHandler{
|
||||
Format: log15.LogfmtFormat(),
|
||||
}
|
||||
}
|
||||
|
||||
func (sw *SysLogHandler) Init() error {
|
||||
prio, err := parseFacility(sw.Facility)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
w, err := syslog.Dial(sw.Network, sw.Address, prio, sw.Tag)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sw.syslog = w
|
||||
return nil
|
||||
}
|
||||
|
||||
func (sw *SysLogHandler) Log(r *log15.Record) error {
|
||||
var err error
|
||||
|
||||
msg := string(sw.Format.Format(r))
|
||||
|
||||
switch r.Lvl {
|
||||
case log15.LvlDebug:
|
||||
err = sw.syslog.Debug(msg)
|
||||
case log15.LvlInfo:
|
||||
err = sw.syslog.Info(msg)
|
||||
case log15.LvlWarn:
|
||||
err = sw.syslog.Warning(msg)
|
||||
case log15.LvlError:
|
||||
err = sw.syslog.Err(msg)
|
||||
case log15.LvlCrit:
|
||||
err = sw.syslog.Crit(msg)
|
||||
default:
|
||||
err = errors.New("invalid syslog level")
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (sw *SysLogHandler) Close() {
|
||||
sw.syslog.Close()
|
||||
}
|
||||
|
||||
var facilities = map[string]syslog.Priority{
|
||||
"user": syslog.LOG_USER,
|
||||
"daemon": syslog.LOG_DAEMON,
|
||||
"local0": syslog.LOG_LOCAL0,
|
||||
"local1": syslog.LOG_LOCAL1,
|
||||
"local2": syslog.LOG_LOCAL2,
|
||||
"local3": syslog.LOG_LOCAL3,
|
||||
"local4": syslog.LOG_LOCAL4,
|
||||
"local5": syslog.LOG_LOCAL5,
|
||||
"local6": syslog.LOG_LOCAL6,
|
||||
"local7": syslog.LOG_LOCAL7,
|
||||
}
|
||||
|
||||
func parseFacility(facility string) (syslog.Priority, error) {
|
||||
prio, ok := facilities[facility]
|
||||
if !ok {
|
||||
return syslog.LOG_LOCAL0, errors.New("invalid syslog facility")
|
||||
}
|
||||
|
||||
return prio, nil
|
||||
}
|
||||
|
@ -219,7 +219,8 @@ func (a *ldapAuther) syncOrgRoles(user *m.User, ldapUser *ldapUserInfo) error {
|
||||
|
||||
// add role
|
||||
cmd := m.AddOrgUserCommand{UserId: user.Id, Role: group.OrgRole, OrgId: group.OrgId}
|
||||
if err := bus.Dispatch(&cmd); err != nil {
|
||||
err := bus.Dispatch(&cmd)
|
||||
if err != nil && err != m.ErrOrgNotFound {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -290,7 +291,7 @@ func (a *ldapAuther) searchForUser(username string) (*ldapUserInfo, error) {
|
||||
a.server.Attr.Name,
|
||||
a.server.Attr.MemberOf,
|
||||
},
|
||||
Filter: strings.Replace(a.server.SearchFilter, "%s", username, -1),
|
||||
Filter: strings.Replace(a.server.SearchFilter, "%s", ldap.EscapeFilter(username), -1),
|
||||
}
|
||||
|
||||
searchResult, err = a.conn.Search(&searchReq)
|
||||
@ -323,7 +324,7 @@ func (a *ldapAuther) searchForUser(username string) (*ldapUserInfo, error) {
|
||||
if a.server.GroupSearchFilterUserAttribute == "" {
|
||||
filter_replace = getLdapAttr(a.server.Attr.Username, searchResult)
|
||||
}
|
||||
filter := strings.Replace(a.server.GroupSearchFilter, "%s", filter_replace, -1)
|
||||
filter := strings.Replace(a.server.GroupSearchFilter, "%s", ldap.EscapeFilter(filter_replace), -1)
|
||||
|
||||
if ldapCfg.VerboseLogging {
|
||||
log.Info("LDAP: Searching for user's groups: %s", filter)
|
||||
|
@ -26,6 +26,12 @@ func AddOrgUser(cmd *m.AddOrgUserCommand) error {
|
||||
return m.ErrOrgUserAlreadyAdded
|
||||
}
|
||||
|
||||
if res, err := sess.Query("SELECT 1 from org WHERE id=?", cmd.OrgId); err != nil {
|
||||
return err
|
||||
} else if len(res) != 1 {
|
||||
return m.ErrOrgNotFound
|
||||
}
|
||||
|
||||
entity := m.OrgUser{
|
||||
OrgId: cmd.OrgId,
|
||||
UserId: cmd.UserId,
|
||||
|
@ -6,6 +6,7 @@ import _ from 'lodash';
|
||||
import angular from 'angular';
|
||||
import $ from 'jquery';
|
||||
import coreModule from 'app/core/core_module';
|
||||
import {profiler} from 'app/core/profiler';
|
||||
|
||||
export class GrafanaCtrl {
|
||||
|
||||
@ -15,14 +16,10 @@ export class GrafanaCtrl {
|
||||
$scope.init = function() {
|
||||
$scope.contextSrv = contextSrv;
|
||||
|
||||
$rootScope.appSubUrl = config.appSubUrl;
|
||||
$scope._ = _;
|
||||
|
||||
$rootScope.profilingEnabled = store.getBool('profilingEnabled');
|
||||
$rootScope.performance = { loadStart: new Date().getTime() };
|
||||
$rootScope.appSubUrl = config.appSubUrl;
|
||||
|
||||
if ($rootScope.profilingEnabled) { $scope.initProfiling(); }
|
||||
|
||||
profiler.init(config, $rootScope);
|
||||
alertSrv.init();
|
||||
utilSrv.init();
|
||||
|
||||
@ -59,82 +56,6 @@ export class GrafanaCtrl {
|
||||
"#E0F9D7","#FCEACA","#CFFAFF","#F9E2D2","#FCE2DE","#BADFF4","#F9D9F9","#DEDAF7"
|
||||
];
|
||||
|
||||
$scope.getTotalWatcherCount = function() {
|
||||
var count = 0;
|
||||
var scopes = 0;
|
||||
var root = $(document.getElementsByTagName('body'));
|
||||
|
||||
var f = function (element) {
|
||||
if (element.data().hasOwnProperty('$scope')) {
|
||||
scopes++;
|
||||
angular.forEach(element.data().$scope.$$watchers, function () {
|
||||
count++;
|
||||
});
|
||||
}
|
||||
|
||||
angular.forEach(element.children(), function (childElement) {
|
||||
f($(childElement));
|
||||
});
|
||||
};
|
||||
|
||||
f(root);
|
||||
$rootScope.performance.scopeCount = scopes;
|
||||
return count;
|
||||
};
|
||||
|
||||
$scope.initProfiling = function() {
|
||||
var count = 0;
|
||||
|
||||
$scope.$watch(function digestCounter() {
|
||||
count++;
|
||||
}, function() {
|
||||
// something
|
||||
});
|
||||
|
||||
$rootScope.performance.panels = [];
|
||||
|
||||
$scope.$on('refresh', function() {
|
||||
if ($rootScope.performance.panels.length > 0) {
|
||||
var totalRender = 0;
|
||||
var totalQuery = 0;
|
||||
|
||||
_.each($rootScope.performance.panels, function(panelTiming: any) {
|
||||
totalRender += panelTiming.render;
|
||||
totalQuery += panelTiming.query;
|
||||
});
|
||||
|
||||
console.log('total query: ' + totalQuery);
|
||||
console.log('total render: ' + totalRender);
|
||||
console.log('avg render: ' + totalRender / $rootScope.performance.panels.length);
|
||||
}
|
||||
|
||||
$rootScope.performance.panels = [];
|
||||
});
|
||||
|
||||
$scope.onAppEvent('dashboard-loaded', function() {
|
||||
count = 0;
|
||||
|
||||
setTimeout(function() {
|
||||
console.log("Dashboard::Performance Total Digests: " + count);
|
||||
console.log("Dashboard::Performance Total Watchers: " + $scope.getTotalWatcherCount());
|
||||
console.log("Dashboard::Performance Total ScopeCount: " + $rootScope.performance.scopeCount);
|
||||
|
||||
var timeTaken = $rootScope.performance.allPanelsInitialized - $rootScope.performance.dashboardLoadStart;
|
||||
console.log("Dashboard::Performance - All panels initialized in " + timeTaken + " ms");
|
||||
|
||||
// measure digest performance
|
||||
var rootDigestStart = window.performance.now();
|
||||
for (var i = 0; i < 30; i++) {
|
||||
$rootScope.$apply();
|
||||
}
|
||||
console.log("Dashboard::Performance Root Digest " + ((window.performance.now() - rootDigestStart) / 30));
|
||||
|
||||
}, 3000);
|
||||
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
$scope.init();
|
||||
}
|
||||
}
|
||||
|
133
public/app/core/profiler.ts
Normal file
133
public/app/core/profiler.ts
Normal file
@ -0,0 +1,133 @@
|
||||
///<reference path="../headers/common.d.ts" />
|
||||
//
|
||||
import $ from 'jquery';
|
||||
import _ from 'lodash';
|
||||
import angular from 'angular';
|
||||
|
||||
export class Profiler {
|
||||
panelsRendered: number;
|
||||
enabled: boolean;
|
||||
panels: any[];
|
||||
panelsInitCount: any;
|
||||
timings: any;
|
||||
digestCounter: any;
|
||||
$rootScope: any;
|
||||
scopeCount: any;
|
||||
|
||||
init(config, $rootScope) {
|
||||
this.enabled = config.buildInfo.env === 'development';
|
||||
this.timings = {};
|
||||
this.timings.appStart = { loadStart: new Date().getTime() };
|
||||
this.$rootScope = $rootScope;
|
||||
|
||||
if (!this.enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
$rootScope.$watch(() => {
|
||||
this.digestCounter++;
|
||||
return false;
|
||||
}, () => {});
|
||||
|
||||
$rootScope.$on('refresh', this.refresh.bind(this));
|
||||
$rootScope.onAppEvent('dashboard-fetched', this.dashboardFetched.bind(this));
|
||||
$rootScope.onAppEvent('dashboard-initialized', this.dashboardInitialized.bind(this));
|
||||
$rootScope.onAppEvent('panel-initialized', this.panelInitialized.bind(this));
|
||||
}
|
||||
|
||||
refresh() {
|
||||
this.panels = [];
|
||||
|
||||
setTimeout(() => {
|
||||
var totalRender = 0;
|
||||
var totalQuery = 0;
|
||||
|
||||
for (let panelTiming of this.panels) {
|
||||
totalRender += panelTiming.render;
|
||||
totalQuery += panelTiming.query;
|
||||
}
|
||||
|
||||
console.log('panel count: ' + this.panels.length);
|
||||
console.log('total query: ' + totalQuery);
|
||||
console.log('total render: ' + totalRender);
|
||||
console.log('avg render: ' + totalRender / this.panels.length);
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
dashboardFetched() {
|
||||
this.timings.dashboardLoadStart = new Date().getTime();
|
||||
this.panelsInitCount = 0;
|
||||
this.digestCounter = 0;
|
||||
this.panelsInitCount = 0;
|
||||
this.panelsRendered = 0;
|
||||
this.panels = [];
|
||||
}
|
||||
|
||||
dashboardInitialized() {
|
||||
setTimeout(() => {
|
||||
console.log("Dashboard::Performance Total Digests: " + this.digestCounter);
|
||||
console.log("Dashboard::Performance Total Watchers: " + this.getTotalWatcherCount());
|
||||
console.log("Dashboard::Performance Total ScopeCount: " + this.scopeCount);
|
||||
|
||||
var timeTaken = this.timings.lastPanelInitializedAt - this.timings.dashboardLoadStart;
|
||||
console.log("Dashboard::Performance All panels initialized in " + timeTaken + " ms");
|
||||
|
||||
// measure digest performance
|
||||
var rootDigestStart = window.performance.now();
|
||||
for (var i = 0; i < 30; i++) {
|
||||
this.$rootScope.$apply();
|
||||
}
|
||||
|
||||
console.log("Dashboard::Performance Root Digest " + ((window.performance.now() - rootDigestStart) / 30));
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
getTotalWatcherCount() {
|
||||
var count = 0;
|
||||
var scopes = 0;
|
||||
var root = $(document.getElementsByTagName('body'));
|
||||
|
||||
var f = function (element) {
|
||||
if (element.data().hasOwnProperty('$scope')) {
|
||||
scopes++;
|
||||
angular.forEach(element.data().$scope.$$watchers, function () {
|
||||
count++;
|
||||
});
|
||||
}
|
||||
|
||||
angular.forEach(element.children(), function (childElement) {
|
||||
f($(childElement));
|
||||
});
|
||||
};
|
||||
|
||||
f(root);
|
||||
this.scopeCount = scopes;
|
||||
return count;
|
||||
}
|
||||
|
||||
renderingCompleted(panelId, panelTimings) {
|
||||
this.panelsRendered++;
|
||||
|
||||
if (this.enabled) {
|
||||
panelTimings.renderEnd = new Date().getTime();
|
||||
this.panels.push({
|
||||
panelId: panelId,
|
||||
query: panelTimings.queryEnd - panelTimings.queryStart,
|
||||
render: panelTimings.renderEnd - panelTimings.renderStart,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
panelInitialized() {
|
||||
if (!this.enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.panelsInitCount++;
|
||||
this.timings.lastPanelInitializedAt = new Date().getTime();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
var profiler = new Profiler();
|
||||
export {profiler};
|
@ -14,7 +14,7 @@ define([
|
||||
|
||||
this.init = function() {
|
||||
$rootScope.onAppEvent('refresh', this.clearCache, $rootScope);
|
||||
$rootScope.onAppEvent('dashboard-loaded', this.clearCache, $rootScope);
|
||||
$rootScope.onAppEvent('dashboard-initialized', this.clearCache, $rootScope);
|
||||
};
|
||||
|
||||
this.clearCache = function() {
|
||||
|
@ -35,10 +35,6 @@ function (angular, $, config, moment) {
|
||||
};
|
||||
|
||||
$scope.setupDashboard = function(data) {
|
||||
$rootScope.performance.dashboardLoadStart = new Date().getTime();
|
||||
$rootScope.performance.panelsInitialized = 0;
|
||||
$rootScope.performance.panelsRendered = 0;
|
||||
|
||||
var dashboard = dashboardSrv.create(data.dashboard, data.meta);
|
||||
dashboardSrv.setCurrent(dashboard);
|
||||
|
||||
@ -60,7 +56,15 @@ function (angular, $, config, moment) {
|
||||
$scope.updateSubmenuVisibility();
|
||||
$scope.setWindowTitleAndTheme();
|
||||
|
||||
$scope.appEvent("dashboard-loaded", $scope.dashboard);
|
||||
if ($scope.profilingEnabled) {
|
||||
$scope.performance.panels = [];
|
||||
$scope.performance.panelCount = 0;
|
||||
$scope.dashboard.rows.forEach(function(row) {
|
||||
$scope.performance.panelCount += row.panels.length;
|
||||
});
|
||||
}
|
||||
|
||||
$scope.appEvent("dashboard-initialized", $scope.dashboard);
|
||||
}).catch(function(err) {
|
||||
if (err.data && err.data.message) { err.message = err.data.message; }
|
||||
$scope.appEvent("alert-error", ['Dashboard init failed', 'Template variables could not be initialized: ' + err.message]);
|
||||
@ -76,7 +80,6 @@ function (angular, $, config, moment) {
|
||||
};
|
||||
|
||||
$scope.broadcastRefresh = function() {
|
||||
$rootScope.performance.panelsRendered = 0;
|
||||
$rootScope.$broadcast('refresh');
|
||||
};
|
||||
|
||||
|
@ -47,6 +47,8 @@ function (angular, moment, _, $, kbn, dateMath, impressionStore) {
|
||||
}
|
||||
|
||||
promise.then(function(result) {
|
||||
$rootScope.appEvent("dashboard-fetched", result.dashboard);
|
||||
|
||||
if (result.meta.dashboardNotFound !== true) {
|
||||
impressionStore.impressions.addDashboardImpression(result.dashboard.id);
|
||||
}
|
||||
|
@ -92,7 +92,6 @@ function (angular, _, $) {
|
||||
state.fullscreen = state.fullscreen ? true : null;
|
||||
state.edit = (state.edit === "true" || state.edit === true) || null;
|
||||
state.editview = state.editview || null;
|
||||
state.org = contextSrv.user.orgId;
|
||||
return state;
|
||||
};
|
||||
|
||||
@ -100,7 +99,6 @@ function (angular, _, $) {
|
||||
var urlState = _.clone(this.state);
|
||||
urlState.fullscreen = this.state.fullscreen ? true : null;
|
||||
urlState.edit = this.state.edit ? true : null;
|
||||
urlState.org = contextSrv.user.orgId;
|
||||
return urlState;
|
||||
};
|
||||
|
||||
|
@ -95,7 +95,6 @@ class MetricsPanelCtrl extends PanelCtrl {
|
||||
}
|
||||
|
||||
setTimeQueryStart() {
|
||||
this.timing = {};
|
||||
this.timing.queryStart = new Date().getTime();
|
||||
}
|
||||
|
||||
@ -200,6 +199,11 @@ class MetricsPanelCtrl extends PanelCtrl {
|
||||
this.panel.snapshotData = result.data;
|
||||
}
|
||||
|
||||
if (!result || !result.data) {
|
||||
console.log('Data source query result invalid, missing data field:', result);
|
||||
result = {data: []};
|
||||
}
|
||||
|
||||
return this.events.emit('data-received', result.data);
|
||||
}
|
||||
|
||||
|
@ -4,6 +4,7 @@ import config from 'app/core/config';
|
||||
import _ from 'lodash';
|
||||
import angular from 'angular';
|
||||
import $ from 'jquery';
|
||||
import {profiler} from 'app/core/profiler';
|
||||
|
||||
const TITLE_HEIGHT = 25;
|
||||
const EMPTY_TITLE_HEIGHT = 9;
|
||||
@ -31,6 +32,7 @@ export class PanelCtrl {
|
||||
height: any;
|
||||
containerHeight: any;
|
||||
events: Emitter;
|
||||
timing: any;
|
||||
|
||||
constructor($scope, $injector) {
|
||||
this.$injector = $injector;
|
||||
@ -38,6 +40,7 @@ export class PanelCtrl {
|
||||
this.$timeout = $injector.get('$timeout');
|
||||
this.editorTabIndex = 0;
|
||||
this.events = new Emitter();
|
||||
this.timing = {};
|
||||
|
||||
var plugin = config.panels[this.panel.type];
|
||||
if (plugin) {
|
||||
@ -57,7 +60,7 @@ export class PanelCtrl {
|
||||
}
|
||||
|
||||
renderingCompleted() {
|
||||
this.$scope.$root.performance.panelsRendered++;
|
||||
profiler.renderingCompleted(this.panel.id, this.timing);
|
||||
}
|
||||
|
||||
refresh() {
|
||||
@ -169,6 +172,7 @@ export class PanelCtrl {
|
||||
}
|
||||
|
||||
this.calculatePanelHeight();
|
||||
this.timing.renderStart = new Date().getTime();
|
||||
this.events.emit('render', payload);
|
||||
}
|
||||
|
||||
|
@ -25,7 +25,7 @@ function (angular, $) {
|
||||
$scope.initDashboard(result, $scope);
|
||||
});
|
||||
|
||||
$scope.onAppEvent("dashboard-loaded", $scope.initPanelScope);
|
||||
$scope.onAppEvent("dashboard-initialized", $scope.initPanelScope);
|
||||
};
|
||||
|
||||
$scope.initPanelScope = function() {
|
||||
|
@ -18,6 +18,8 @@ function (angular, $, moment, _, kbn, GraphTooltip) {
|
||||
'use strict';
|
||||
|
||||
var module = angular.module('grafana.directives');
|
||||
var labelWidthCache = {};
|
||||
var panelWidthCache = {};
|
||||
|
||||
module.directive('grafanaGraph', function($rootScope, timeSrv) {
|
||||
return {
|
||||
@ -31,6 +33,7 @@ function (angular, $, moment, _, kbn, GraphTooltip) {
|
||||
var sortedSeries;
|
||||
var legendSideLastValue = null;
|
||||
var rootScope = scope.$root;
|
||||
var panelWidth = 0;
|
||||
|
||||
rootScope.onAppEvent('setCrosshair', function(event, info) {
|
||||
// do not need to to this if event is from this panel
|
||||
@ -104,11 +107,21 @@ function (angular, $, moment, _, kbn, GraphTooltip) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (elem.width() === 0) {
|
||||
if (panelWidth === 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
function getLabelWidth(text, elem) {
|
||||
var labelWidth = labelWidthCache[text];
|
||||
|
||||
if (!labelWidth) {
|
||||
labelWidth = labelWidthCache[text] = elem.width();
|
||||
}
|
||||
|
||||
return labelWidth;
|
||||
}
|
||||
|
||||
function drawHook(plot) {
|
||||
// Update legend values
|
||||
var yaxis = plot.getYAxes();
|
||||
@ -137,7 +150,7 @@ function (angular, $, moment, _, kbn, GraphTooltip) {
|
||||
.text(panel.yaxes[0].label)
|
||||
.appendTo(elem);
|
||||
|
||||
yaxisLabel.css("margin-top", yaxisLabel.width() / 2);
|
||||
yaxisLabel[0].style.marginTop = (getLabelWidth(panel.yaxes[0].label, yaxisLabel) / 2) + 'px';
|
||||
}
|
||||
|
||||
// add right axis labels
|
||||
@ -146,7 +159,7 @@ function (angular, $, moment, _, kbn, GraphTooltip) {
|
||||
.text(panel.yaxes[1].label)
|
||||
.appendTo(elem);
|
||||
|
||||
rightLabel.css("margin-top", rightLabel.width() / 2);
|
||||
rightLabel[0].style.marginTop = (getLabelWidth(panel.yaxes[1].label, rightLabel) / 2) + 'px';
|
||||
}
|
||||
}
|
||||
|
||||
@ -159,6 +172,11 @@ function (angular, $, moment, _, kbn, GraphTooltip) {
|
||||
|
||||
// Function for rendering panel
|
||||
function render_panel() {
|
||||
panelWidth = panelWidthCache[panel.span];
|
||||
if (!panelWidth) {
|
||||
panelWidth = panelWidthCache[panel.span] = elem.width();
|
||||
}
|
||||
|
||||
if (shouldAbortRender()) {
|
||||
return;
|
||||
}
|
||||
@ -276,7 +294,7 @@ function (angular, $, moment, _, kbn, GraphTooltip) {
|
||||
}
|
||||
|
||||
function addTimeAxis(options) {
|
||||
var ticks = elem.width() / 100;
|
||||
var ticks = panelWidth / 100;
|
||||
var min = _.isUndefined(ctrl.range.from) ? null : ctrl.range.from.valueOf();
|
||||
var max = _.isUndefined(ctrl.range.to) ? null : ctrl.range.to.valueOf();
|
||||
|
||||
@ -444,7 +462,7 @@ function (angular, $, moment, _, kbn, GraphTooltip) {
|
||||
}
|
||||
|
||||
function render_panel_as_graphite_png(url) {
|
||||
url += '&width=' + elem.width();
|
||||
url += '&width=' + panelWidth;
|
||||
url += '&height=' + elem.css('height').replace('px', '');
|
||||
url += '&bgcolor=1f1f1f'; // @grayDarker & @grafanaPanelBackground
|
||||
url += '&fgcolor=BBBFC2'; // @textColor & @grayLighter
|
||||
|
@ -31,7 +31,7 @@ define([
|
||||
it('should update querystring and view state', function() {
|
||||
var updateState = {fullscreen: true, edit: true, panelId: 1};
|
||||
viewState.update(updateState);
|
||||
expect(location.search()).to.eql({fullscreen: true, edit: true, panelId: 1, org: 19});
|
||||
expect(location.search()).to.eql({fullscreen: true, edit: true, panelId: 1});
|
||||
expect(viewState.dashboard.meta.fullscreen).to.be(true);
|
||||
expect(viewState.state.fullscreen).to.be(true);
|
||||
});
|
||||
@ -41,7 +41,6 @@ define([
|
||||
it('should remove params from query string', function() {
|
||||
viewState.update({fullscreen: true, panelId: 1, edit: true});
|
||||
viewState.update({fullscreen: false});
|
||||
expect(location.search()).to.eql({org: 19});
|
||||
expect(viewState.dashboard.meta.fullscreen).to.be(false);
|
||||
expect(viewState.state.fullscreen).to.be(null);
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user