mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Merge branch 'develop' of github.com:grafana/grafana into dashboard_snapshot_poc
Conflicts: src/app/features/dashboard/partials/shareDashboard.html
This commit is contained in:
commit
49a0ea53c7
@ -1,6 +1,13 @@
|
||||
app_name = Grafana
|
||||
app_mode = production
|
||||
|
||||
# Once every 1 hour Grafana will report anonymous data to
|
||||
# stats.grafana.org (https). No ip addresses are being tracked.
|
||||
# only simple counters to track running instances, dashboard
|
||||
# count and errors. It is very helpful to us.
|
||||
# Change this option to false to disable reporting.
|
||||
reporting-enabled = true
|
||||
|
||||
[server]
|
||||
; protocol (http or https)
|
||||
protocol = http
|
||||
|
@ -5,6 +5,13 @@
|
||||
|
||||
app_mode = production
|
||||
|
||||
# Once every 1 hour Grafana will report anonymous data to
|
||||
# stats.grafana.org (https). No ip addresses are being tracked.
|
||||
# only simple counters to track running instances, dashboard
|
||||
# counts and errors. It is very helpful to us.
|
||||
# Change this option to false to disable reporting.
|
||||
reporting-enabled = true
|
||||
|
||||
[server]
|
||||
; protocol (http or https)
|
||||
protocol = http
|
||||
|
12
main.go
12
main.go
@ -39,17 +39,7 @@ func main() {
|
||||
app.Name = "Grafana Backend"
|
||||
app.Usage = "grafana web"
|
||||
app.Version = version
|
||||
app.Commands = []cli.Command{
|
||||
cmd.ListOrgs,
|
||||
cmd.CreateOrg,
|
||||
cmd.DeleteOrg,
|
||||
cmd.ExportDashboard,
|
||||
cmd.ImportDashboard,
|
||||
cmd.ListDataSources,
|
||||
cmd.CreateDataSource,
|
||||
cmd.DescribeDataSource,
|
||||
cmd.DeleteDataSource,
|
||||
cmd.Web}
|
||||
app.Commands = []cli.Command{cmd.ImportDashboard, cmd.Web}
|
||||
app.Flags = append(app.Flags, []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "config",
|
||||
|
@ -3,6 +3,7 @@ package api
|
||||
import (
|
||||
"github.com/grafana/grafana/pkg/api/dtos"
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/metrics"
|
||||
"github.com/grafana/grafana/pkg/middleware"
|
||||
m "github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
@ -64,6 +65,8 @@ func AdminCreateUser(c *middleware.Context, form dtos.AdminCreateUserForm) {
|
||||
return
|
||||
}
|
||||
|
||||
metrics.M_Api_Admin_User_Create.Inc(1)
|
||||
|
||||
c.JsonOK("User created")
|
||||
}
|
||||
|
||||
|
@ -7,6 +7,7 @@ import (
|
||||
|
||||
"github.com/grafana/grafana/pkg/api/dtos"
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/metrics"
|
||||
"github.com/grafana/grafana/pkg/middleware"
|
||||
m "github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
@ -27,6 +28,8 @@ func isDasboardStarredByUser(c *middleware.Context, dashId int64) (bool, error)
|
||||
}
|
||||
|
||||
func GetDashboard(c *middleware.Context) {
|
||||
metrics.M_Api_Dashboard_Get.Inc(1)
|
||||
|
||||
slug := c.Params(":slug")
|
||||
|
||||
query := m.GetDashboardQuery{Slug: slug, OrgId: c.OrgId}
|
||||
@ -88,6 +91,8 @@ func PostDashboard(c *middleware.Context, cmd m.SaveDashboardCommand) {
|
||||
return
|
||||
}
|
||||
|
||||
metrics.M_Api_Dashboard_Post.Inc(1)
|
||||
|
||||
c.JSON(200, util.DynMap{"status": "success", "slug": cmd.Result.Slug, "version": cmd.Result.Version})
|
||||
}
|
||||
|
||||
|
@ -47,7 +47,7 @@ func Index(c *middleware.Context) {
|
||||
|
||||
func NotFound(c *middleware.Context) {
|
||||
if c.IsApiRequest() {
|
||||
c.JsonApiErr(200, "Not found", nil)
|
||||
c.JsonApiErr(404, "Not found", nil)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -6,6 +6,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/api/dtos"
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/log"
|
||||
"github.com/grafana/grafana/pkg/metrics"
|
||||
"github.com/grafana/grafana/pkg/middleware"
|
||||
m "github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
@ -75,7 +76,6 @@ func LoginView(c *middleware.Context) {
|
||||
}
|
||||
|
||||
func LoginPost(c *middleware.Context, cmd dtos.LoginCommand) {
|
||||
|
||||
userQuery := m.GetUserByLoginQuery{LoginOrEmail: cmd.User}
|
||||
err := bus.Dispatch(&userQuery)
|
||||
|
||||
@ -112,6 +112,8 @@ func LoginPost(c *middleware.Context, cmd dtos.LoginCommand) {
|
||||
c.SetCookie("redirect_to", "", -1, setting.AppSubUrl+"/")
|
||||
}
|
||||
|
||||
metrics.M_Api_Login_Post.Inc(1)
|
||||
|
||||
c.JSON(200, result)
|
||||
}
|
||||
|
||||
|
@ -8,6 +8,7 @@ import (
|
||||
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/log"
|
||||
"github.com/grafana/grafana/pkg/metrics"
|
||||
"github.com/grafana/grafana/pkg/middleware"
|
||||
m "github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
@ -81,5 +82,7 @@ func OAuthLogin(ctx *middleware.Context) {
|
||||
// login
|
||||
loginUserWithUser(userQuery.Result, ctx)
|
||||
|
||||
metrics.M_Api_Login_OAuth.Inc(1)
|
||||
|
||||
ctx.Redirect(setting.AppSubUrl + "/")
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ package api
|
||||
|
||||
import (
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/metrics"
|
||||
"github.com/grafana/grafana/pkg/middleware"
|
||||
m "github.com/grafana/grafana/pkg/models"
|
||||
)
|
||||
@ -35,6 +36,8 @@ func CreateOrg(c *middleware.Context, cmd m.CreateOrgCommand) {
|
||||
return
|
||||
}
|
||||
|
||||
metrics.M_Api_Org_Create.Inc(1)
|
||||
|
||||
c.JsonOK("Organization created")
|
||||
}
|
||||
|
||||
|
@ -12,12 +12,13 @@ import (
|
||||
|
||||
func RenderToPng(c *middleware.Context) {
|
||||
queryReader := util.NewUrlQueryReader(c.Req.URL)
|
||||
queryParams := fmt.Sprintf("?render=1&%s=%d&%s", middleware.SESS_KEY_USERID, c.UserId, c.Req.URL.RawQuery)
|
||||
queryParams := fmt.Sprintf("?%s", c.Req.URL.RawQuery)
|
||||
|
||||
renderOpts := &renderer.RenderOpts{
|
||||
Url: c.Params("*") + queryParams,
|
||||
Width: queryReader.Get("width", "800"),
|
||||
Height: queryReader.Get("height", "400"),
|
||||
Url: c.Params("*") + queryParams,
|
||||
Width: queryReader.Get("width", "800"),
|
||||
Height: queryReader.Get("height", "400"),
|
||||
SessionId: c.Session.ID(),
|
||||
}
|
||||
|
||||
renderOpts.Url = setting.ToAbsUrl(renderOpts.Url)
|
||||
|
@ -2,6 +2,7 @@ package api
|
||||
|
||||
import (
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/metrics"
|
||||
"github.com/grafana/grafana/pkg/middleware"
|
||||
m "github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
@ -26,4 +27,6 @@ func SignUp(c *middleware.Context, cmd m.CreateUserCommand) {
|
||||
loginUserWithUser(&user, c)
|
||||
|
||||
c.JsonOK("User created and logged in")
|
||||
|
||||
metrics.M_Api_User_SignUp.Inc(1)
|
||||
}
|
||||
|
@ -21,6 +21,7 @@ import (
|
||||
|
||||
"github.com/grafana/grafana/pkg/api"
|
||||
"github.com/grafana/grafana/pkg/log"
|
||||
"github.com/grafana/grafana/pkg/metrics"
|
||||
"github.com/grafana/grafana/pkg/middleware"
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
"github.com/grafana/grafana/pkg/services/eventpublisher"
|
||||
@ -88,6 +89,10 @@ func runWeb(c *cli.Context) {
|
||||
m := newMacaron()
|
||||
api.Register(m)
|
||||
|
||||
if setting.ReportingEnabled {
|
||||
go metrics.StartUsageReportLoop()
|
||||
}
|
||||
|
||||
listenAddr := fmt.Sprintf("%s:%s", setting.HttpAddr, setting.HttpPort)
|
||||
log.Info("Listen: %v://%s%s", setting.Protocol, listenAddr, setting.AppSubUrl)
|
||||
switch setting.Protocol {
|
||||
|
@ -14,9 +14,10 @@ import (
|
||||
)
|
||||
|
||||
type RenderOpts struct {
|
||||
Url string
|
||||
Width string
|
||||
Height string
|
||||
Url string
|
||||
Width string
|
||||
Height string
|
||||
SessionId string
|
||||
}
|
||||
|
||||
func RenderToPng(params *RenderOpts) (string, error) {
|
||||
@ -26,7 +27,9 @@ func RenderToPng(params *RenderOpts) (string, error) {
|
||||
pngPath, _ := filepath.Abs(filepath.Join(setting.ImagesDir, getHash(params.Url)))
|
||||
pngPath = pngPath + ".png"
|
||||
|
||||
cmd := exec.Command(binPath, scriptPath, "url="+params.Url, "width="+params.Width, "height="+params.Height, "png="+pngPath)
|
||||
cmd := exec.Command(binPath, scriptPath, "url="+params.Url, "width="+params.Width,
|
||||
"height="+params.Height, "png="+pngPath, "cookiename="+setting.SessionOptions.CookieName,
|
||||
"domain="+setting.Domain, "sessionid="+params.SessionId)
|
||||
stdout, err := cmd.StdoutPipe()
|
||||
|
||||
if err != nil {
|
||||
|
72
pkg/metrics/counter.go
Normal file
72
pkg/metrics/counter.go
Normal file
@ -0,0 +1,72 @@
|
||||
package metrics
|
||||
|
||||
import "sync/atomic"
|
||||
|
||||
// Counters hold an int64 value that can be incremented and decremented.
|
||||
type Counter interface {
|
||||
Clear()
|
||||
Count() int64
|
||||
Dec(int64)
|
||||
Inc(int64)
|
||||
Snapshot() Counter
|
||||
}
|
||||
|
||||
// NewCounter constructs a new StandardCounter.
|
||||
func NewCounter() Counter {
|
||||
return &StandardCounter{0}
|
||||
}
|
||||
|
||||
// CounterSnapshot is a read-only copy of another Counter.
|
||||
type CounterSnapshot int64
|
||||
|
||||
// Clear panics.
|
||||
func (CounterSnapshot) Clear() {
|
||||
panic("Clear called on a CounterSnapshot")
|
||||
}
|
||||
|
||||
// Count returns the count at the time the snapshot was taken.
|
||||
func (c CounterSnapshot) Count() int64 { return int64(c) }
|
||||
|
||||
// Dec panics.
|
||||
func (CounterSnapshot) Dec(int64) {
|
||||
panic("Dec called on a CounterSnapshot")
|
||||
}
|
||||
|
||||
// Inc panics.
|
||||
func (CounterSnapshot) Inc(int64) {
|
||||
panic("Inc called on a CounterSnapshot")
|
||||
}
|
||||
|
||||
// Snapshot returns the snapshot.
|
||||
func (c CounterSnapshot) Snapshot() Counter { return c }
|
||||
|
||||
// StandardCounter is the standard implementation of a Counter and uses the
|
||||
// sync/atomic package to manage a single int64 value.
|
||||
type StandardCounter struct {
|
||||
count int64
|
||||
}
|
||||
|
||||
// Clear sets the counter to zero.
|
||||
func (c *StandardCounter) Clear() {
|
||||
atomic.StoreInt64(&c.count, 0)
|
||||
}
|
||||
|
||||
// Count returns the current count.
|
||||
func (c *StandardCounter) Count() int64 {
|
||||
return atomic.LoadInt64(&c.count)
|
||||
}
|
||||
|
||||
// Dec decrements the counter by the given amount.
|
||||
func (c *StandardCounter) Dec(i int64) {
|
||||
atomic.AddInt64(&c.count, -i)
|
||||
}
|
||||
|
||||
// Inc increments the counter by the given amount.
|
||||
func (c *StandardCounter) Inc(i int64) {
|
||||
atomic.AddInt64(&c.count, i)
|
||||
}
|
||||
|
||||
// Snapshot returns a read-only copy of the counter.
|
||||
func (c *StandardCounter) Snapshot() Counter {
|
||||
return CounterSnapshot(c.Count())
|
||||
}
|
39
pkg/metrics/metric_ref.go
Normal file
39
pkg/metrics/metric_ref.go
Normal file
@ -0,0 +1,39 @@
|
||||
package metrics
|
||||
|
||||
type comboCounterRef struct {
|
||||
usageCounter Counter
|
||||
metricCounter Counter
|
||||
}
|
||||
|
||||
func NewComboCounterRef(name string) Counter {
|
||||
cr := &comboCounterRef{}
|
||||
cr.usageCounter = UsageStats.GetOrRegister(name, NewCounter).(Counter)
|
||||
cr.metricCounter = MetricStats.GetOrRegister(name, NewCounter).(Counter)
|
||||
return cr
|
||||
}
|
||||
|
||||
func (c comboCounterRef) Clear() {
|
||||
c.usageCounter.Clear()
|
||||
c.metricCounter.Clear()
|
||||
}
|
||||
|
||||
func (c comboCounterRef) Count() int64 {
|
||||
panic("Count called on a combocounter ref")
|
||||
}
|
||||
|
||||
// Dec panics.
|
||||
func (c comboCounterRef) Dec(i int64) {
|
||||
c.usageCounter.Dec(i)
|
||||
c.metricCounter.Dec(i)
|
||||
}
|
||||
|
||||
// Inc panics.
|
||||
func (c comboCounterRef) Inc(i int64) {
|
||||
c.usageCounter.Inc(i)
|
||||
c.metricCounter.Inc(i)
|
||||
}
|
||||
|
||||
// Snapshot returns the snapshot.
|
||||
func (c comboCounterRef) Snapshot() Counter {
|
||||
panic("snapshot called on a combocounter ref")
|
||||
}
|
25
pkg/metrics/metrics.go
Normal file
25
pkg/metrics/metrics.go
Normal file
@ -0,0 +1,25 @@
|
||||
package metrics
|
||||
|
||||
var UsageStats = NewRegistry()
|
||||
var MetricStats = NewRegistry()
|
||||
|
||||
var (
|
||||
M_Instance_Start = NewComboCounterRef("instance.start")
|
||||
|
||||
M_Page_Status_200 = NewComboCounterRef("page.status.200")
|
||||
M_Page_Status_500 = NewComboCounterRef("page.status.500")
|
||||
M_Page_Status_404 = NewComboCounterRef("page.status.404")
|
||||
|
||||
M_Api_Status_500 = NewComboCounterRef("api.status.500")
|
||||
M_Api_Status_404 = NewComboCounterRef("api.status.404")
|
||||
|
||||
M_Api_User_SignUp = NewComboCounterRef("api.user.signup")
|
||||
M_Api_Dashboard_Get = NewComboCounterRef("api.dashboard.get")
|
||||
M_Api_Dashboard_Post = NewComboCounterRef("api.dashboard.post")
|
||||
M_Api_Admin_User_Create = NewComboCounterRef("api.admin.user_create")
|
||||
M_Api_Login_Post = NewComboCounterRef("api.login.post")
|
||||
M_Api_Login_OAuth = NewComboCounterRef("api.login.oauth")
|
||||
M_Api_Org_Create = NewComboCounterRef("api.org.create")
|
||||
|
||||
M_Models_Dashboard_Insert = NewComboCounterRef("models.dashboard.insert")
|
||||
)
|
102
pkg/metrics/registry.go
Normal file
102
pkg/metrics/registry.go
Normal file
@ -0,0 +1,102 @@
|
||||
package metrics
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// DuplicateMetric is the error returned by Registry.Register when a metric
|
||||
// already exists. If you mean to Register that metric you must first
|
||||
// Unregister the existing metric.
|
||||
type DuplicateMetric string
|
||||
|
||||
func (err DuplicateMetric) Error() string {
|
||||
return fmt.Sprintf("duplicate metric: %s", string(err))
|
||||
}
|
||||
|
||||
type Registry interface {
|
||||
// Call the given function for each registered metric.
|
||||
Each(func(string, interface{}))
|
||||
|
||||
// Get the metric by the given name or nil if none is registered.
|
||||
Get(string) interface{}
|
||||
|
||||
// Gets an existing metric or registers the given one.
|
||||
// The interface can be the metric to register if not found in registry,
|
||||
// or a function returning the metric for lazy instantiation.
|
||||
GetOrRegister(string, interface{}) interface{}
|
||||
|
||||
// Register the given metric under the given name.
|
||||
Register(string, interface{}) error
|
||||
}
|
||||
|
||||
// The standard implementation of a Registry is a mutex-protected map
|
||||
// of names to metrics.
|
||||
type StandardRegistry struct {
|
||||
metrics map[string]interface{}
|
||||
mutex sync.Mutex
|
||||
}
|
||||
|
||||
// Create a new registry.
|
||||
func NewRegistry() Registry {
|
||||
return &StandardRegistry{metrics: make(map[string]interface{})}
|
||||
}
|
||||
|
||||
// Call the given function for each registered metric.
|
||||
func (r *StandardRegistry) Each(f func(string, interface{})) {
|
||||
for name, i := range r.registered() {
|
||||
f(name, i)
|
||||
}
|
||||
}
|
||||
|
||||
// Get the metric by the given name or nil if none is registered.
|
||||
func (r *StandardRegistry) Get(name string) interface{} {
|
||||
r.mutex.Lock()
|
||||
defer r.mutex.Unlock()
|
||||
return r.metrics[name]
|
||||
}
|
||||
|
||||
// Gets an existing metric or creates and registers a new one. Threadsafe
|
||||
// alternative to calling Get and Register on failure.
|
||||
// The interface can be the metric to register if not found in registry,
|
||||
// or a function returning the metric for lazy instantiation.
|
||||
func (r *StandardRegistry) GetOrRegister(name string, i interface{}) interface{} {
|
||||
r.mutex.Lock()
|
||||
defer r.mutex.Unlock()
|
||||
if metric, ok := r.metrics[name]; ok {
|
||||
return metric
|
||||
}
|
||||
if v := reflect.ValueOf(i); v.Kind() == reflect.Func {
|
||||
i = v.Call(nil)[0].Interface()
|
||||
}
|
||||
r.register(name, i)
|
||||
return i
|
||||
}
|
||||
|
||||
// Register the given metric under the given name. Returns a DuplicateMetric
|
||||
// if a metric by the given name is already registered.
|
||||
func (r *StandardRegistry) Register(name string, i interface{}) error {
|
||||
r.mutex.Lock()
|
||||
defer r.mutex.Unlock()
|
||||
return r.register(name, i)
|
||||
}
|
||||
|
||||
func (r *StandardRegistry) register(name string, i interface{}) error {
|
||||
if _, ok := r.metrics[name]; ok {
|
||||
return DuplicateMetric(name)
|
||||
}
|
||||
|
||||
r.metrics[name] = i
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *StandardRegistry) registered() map[string]interface{} {
|
||||
metrics := make(map[string]interface{}, len(r.metrics))
|
||||
r.mutex.Lock()
|
||||
defer r.mutex.Unlock()
|
||||
for name, i := range r.metrics {
|
||||
metrics[name] = i
|
||||
}
|
||||
return metrics
|
||||
}
|
63
pkg/metrics/report_usage.go
Normal file
63
pkg/metrics/report_usage.go
Normal file
@ -0,0 +1,63 @@
|
||||
package metrics
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/log"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
|
||||
func StartUsageReportLoop() chan struct{} {
|
||||
M_Instance_Start.Inc(1)
|
||||
|
||||
ticker := time.NewTicker(time.Hour)
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
sendUsageStats()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func sendUsageStats() {
|
||||
log.Trace("Sending anonymous usage stats to stats.grafana.org")
|
||||
|
||||
version := strings.Replace(setting.BuildVersion, ".", "_", -1)
|
||||
|
||||
metrics := map[string]interface{}{}
|
||||
report := map[string]interface{}{
|
||||
"version": version,
|
||||
"metrics": metrics,
|
||||
}
|
||||
|
||||
// statsQuery := m.GetSystemStatsQuery{}
|
||||
// if err := bus.Dispatch(&statsQuery); err != nil {
|
||||
// log.Error(3, "Failed to get system stats", err)
|
||||
// return
|
||||
// }
|
||||
|
||||
UsageStats.Each(func(name string, i interface{}) {
|
||||
switch metric := i.(type) {
|
||||
case Counter:
|
||||
if metric.Count() > 0 {
|
||||
metrics[name+".count"] = metric.Count()
|
||||
metric.Clear()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// metrics["stats.dashboards.count"] = statsQuery.Result.DashboardCount
|
||||
// metrics["stats.users.count"] = statsQuery.Result.UserCount
|
||||
// metrics["stats.orgs.count"] = statsQuery.Result.OrgCount
|
||||
|
||||
out, _ := json.Marshal(report)
|
||||
data := bytes.NewBuffer(out)
|
||||
|
||||
client := http.Client{Timeout: time.Duration(5 * time.Second)}
|
||||
|
||||
go client.Post("https://stats.grafana.org/grafana-usage-report", "application/json", data)
|
||||
}
|
@ -22,13 +22,6 @@ func getRequestUserId(c *Context) int64 {
|
||||
return userId.(int64)
|
||||
}
|
||||
|
||||
// TODO: figure out a way to secure this
|
||||
if c.Req.URL.Query().Get("render") == "1" {
|
||||
userId := c.QueryInt64(SESS_KEY_USERID)
|
||||
c.Session.Set(SESS_KEY_USERID, userId)
|
||||
return userId
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
|
@ -10,6 +10,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/components/apikeygen"
|
||||
"github.com/grafana/grafana/pkg/log"
|
||||
"github.com/grafana/grafana/pkg/metrics"
|
||||
m "github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
@ -99,6 +100,15 @@ func (ctx *Context) Handle(status int, title string, err error) {
|
||||
}
|
||||
}
|
||||
|
||||
switch status {
|
||||
case 200:
|
||||
metrics.M_Page_Status_200.Inc(1)
|
||||
case 404:
|
||||
metrics.M_Page_Status_404.Inc(1)
|
||||
case 500:
|
||||
metrics.M_Page_Status_500.Inc(1)
|
||||
}
|
||||
|
||||
ctx.Data["Title"] = title
|
||||
ctx.HTML(status, strconv.Itoa(status))
|
||||
}
|
||||
@ -128,7 +138,9 @@ func (ctx *Context) JsonApiErr(status int, message string, err error) {
|
||||
switch status {
|
||||
case 404:
|
||||
resp["message"] = "Not Found"
|
||||
metrics.M_Api_Status_500.Inc(1)
|
||||
case 500:
|
||||
metrics.M_Api_Status_404.Inc(1)
|
||||
resp["message"] = "Internal Server Error"
|
||||
}
|
||||
|
||||
|
11
pkg/models/stats.go
Normal file
11
pkg/models/stats.go
Normal file
@ -0,0 +1,11 @@
|
||||
package models
|
||||
|
||||
type SystemStats struct {
|
||||
DashboardCount int
|
||||
UserCount int
|
||||
OrgCount int
|
||||
}
|
||||
|
||||
type GetSystemStatsQuery struct {
|
||||
Result *SystemStats
|
||||
}
|
@ -6,6 +6,7 @@ import (
|
||||
|
||||
"github.com/go-xorm/xorm"
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/metrics"
|
||||
m "github.com/grafana/grafana/pkg/models"
|
||||
)
|
||||
|
||||
@ -48,6 +49,7 @@ func SaveDashboard(cmd *m.SaveDashboardCommand) error {
|
||||
}
|
||||
|
||||
if dash.Id == 0 {
|
||||
metrics.M_Models_Dashboard_Insert.Inc(1)
|
||||
_, err = sess.Insert(dash)
|
||||
} else {
|
||||
dash.Version += 1
|
||||
|
36
pkg/services/sqlstore/stats.go
Normal file
36
pkg/services/sqlstore/stats.go
Normal file
@ -0,0 +1,36 @@
|
||||
package sqlstore
|
||||
|
||||
import (
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
m "github.com/grafana/grafana/pkg/models"
|
||||
)
|
||||
|
||||
func init() {
|
||||
bus.AddHandler("sql", GetSystemStats)
|
||||
}
|
||||
|
||||
func GetSystemStats(query *m.GetSystemStatsQuery) error {
|
||||
var rawSql = `SELECT
|
||||
(
|
||||
SELECT COUNT(*)
|
||||
FROM ` + dialect.Quote("user") + `
|
||||
) AS user_count,
|
||||
(
|
||||
SELECT COUNT(*)
|
||||
FROM ` + dialect.Quote("org") + `
|
||||
) AS org_count,
|
||||
(
|
||||
SELECT COUNT(*)
|
||||
FROM ` + dialect.Quote("dashboard") + `
|
||||
) AS dashboard_count
|
||||
`
|
||||
|
||||
var stats m.SystemStats
|
||||
_, err := x.Sql(rawSql).Get(&stats)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
query.Result = &stats
|
||||
return err
|
||||
}
|
@ -96,6 +96,8 @@ var (
|
||||
PhantomDir string
|
||||
|
||||
configFiles []string
|
||||
|
||||
ReportingEnabled bool
|
||||
)
|
||||
|
||||
func init() {
|
||||
@ -233,6 +235,8 @@ func NewConfigContext(config string) {
|
||||
ImagesDir = "data/png"
|
||||
PhantomDir = "vendor/phantomjs"
|
||||
|
||||
ReportingEnabled = Cfg.Section("").Key("reporting-enabled").MustBool(true)
|
||||
|
||||
readSessionConfig()
|
||||
}
|
||||
|
||||
|
@ -85,3 +85,4 @@
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
@ -2,23 +2,23 @@
|
||||
<div class="section">
|
||||
<h5>Drilldown / detail link<tip>These links appear in the dropdown menu in the panel menu. </tip></h5>
|
||||
|
||||
<div class="tight-form" ng-repeat="link in panel.links"j>
|
||||
<div class="tight-form" ng-repeat="link in panel.links">
|
||||
<ul class="tight-form-list">
|
||||
<li class="tight-form-item">
|
||||
<i class="fa fa-remove pointer" ng-click="deleteLink(link)"></i>
|
||||
</li>
|
||||
|
||||
<li class="tight-form-item">title</li>
|
||||
<li class="tight-form-item" style="width: 80px;">Link title</li>
|
||||
<li>
|
||||
<input type="text" ng-model="link.title" class="input-medium tight-form-input">
|
||||
</li>
|
||||
|
||||
<li class="tight-form-item">type</li>
|
||||
<li class="tight-form-item">Type</li>
|
||||
<li>
|
||||
<select class="input-medium tight-form-input" style="width: 101px;" ng-model="link.type" ng-options="f for f in ['dashboard','absolute']"></select>
|
||||
</li>
|
||||
|
||||
<li class="tight-form-item" ng-show="link.type === 'dashboard'">dashboard</li>
|
||||
<li class="tight-form-item" ng-show="link.type === 'dashboard'">Dashboard</li>
|
||||
<li ng-show="link.type === 'dashboard'">
|
||||
<input type="text"
|
||||
ng-model="link.dashboard"
|
||||
@ -26,20 +26,30 @@
|
||||
class="input-large tight-form-input">
|
||||
</li>
|
||||
|
||||
<li class="tight-form-item" ng-show="link.type === 'absolute'">url</li>
|
||||
<li class="tight-form-item" ng-show="link.type === 'absolute'">Url</li>
|
||||
<li ng-show="link.type === 'absolute'">
|
||||
<input type="text" ng-model="link.url" class="input-large tight-form-input">
|
||||
</li>
|
||||
|
||||
<li class="tight-form-item">params
|
||||
<tip>Use var-variableName=value to pass templating variables.</tip>
|
||||
</li>
|
||||
<li>
|
||||
<input type="text" ng-model="link.params" class="input-medium tight-form-input">
|
||||
<input type="text" ng-model="link.url" class="input-xlarge tight-form-input">
|
||||
</li>
|
||||
</ul>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
|
||||
<div class="tight-form">
|
||||
<ul class="tight-form-list" role="menu">
|
||||
<li class="tight-form-item">
|
||||
<i class="fa fa-remove invisible"></i>
|
||||
</li>
|
||||
<li class="tight-form-item" style="width: 80px;">
|
||||
Params
|
||||
<tip>Use var-variableName=value to pass templating variables.</tip>
|
||||
</li>
|
||||
<li>
|
||||
<input type="text" ng-model="link.params" class="input-xxlarge tight-form-input">
|
||||
</li>
|
||||
</ul>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -396,8 +396,8 @@ function (angular, $, kbn, moment, _, GraphTooltip) {
|
||||
}
|
||||
}
|
||||
|
||||
axis.min = axis.min !== null ? axis.min : 1;
|
||||
axis.ticks = [1];
|
||||
axis.min = axis.min !== null ? axis.min : 0;
|
||||
axis.ticks = [0, 1];
|
||||
var nextTick = 1;
|
||||
|
||||
while (true) {
|
||||
@ -409,10 +409,10 @@ function (angular, $, kbn, moment, _, GraphTooltip) {
|
||||
}
|
||||
|
||||
if (axis.logBase === 10) {
|
||||
axis.transform = function(v) { return Math.log(v+0.0001); };
|
||||
axis.transform = function(v) { return Math.log(v+0.1); };
|
||||
axis.inverseTransform = function (v) { return Math.pow(10,v); };
|
||||
} else {
|
||||
axis.transform = function(v) { return Math.log(v+0.0001) / Math.log(axis.logBase); };
|
||||
axis.transform = function(v) { return Math.log(v+0.1) / Math.log(axis.logBase); };
|
||||
axis.inverseTransform = function (v) { return Math.pow(axis.logBase,v); };
|
||||
}
|
||||
}
|
||||
|
@ -153,9 +153,9 @@ define([
|
||||
|
||||
it('should apply axis transform and ticks', function() {
|
||||
var axis = ctx.plotOptions.yaxes[0];
|
||||
expect(axis.transform(100)).to.be(Math.log(100+0.0001));
|
||||
expect(axis.ticks[0]).to.be(1);
|
||||
expect(axis.ticks[1]).to.be(10);
|
||||
expect(axis.transform(100)).to.be(Math.log(100+0.1));
|
||||
expect(axis.ticks[0]).to.be(0);
|
||||
expect(axis.ticks[1]).to.be(1);
|
||||
});
|
||||
});
|
||||
|
||||
|
10
vendor/phantomjs/render.js
vendored
10
vendor/phantomjs/render.js
vendored
@ -9,13 +9,19 @@ args.forEach(function(arg) {
|
||||
params[parts[1]] = parts[2];
|
||||
});
|
||||
|
||||
var usage = "url=<url> png=<filename> width=<width> height=<height>";
|
||||
var usage = "url=<url> png=<filename> width=<width> height=<height> cookiename=<cookiename> sessionid=<sessionid> domain=<domain>";
|
||||
|
||||
if (!params.url || !params.png) {
|
||||
if (!params.url || !params.png || !params.cookiename || ! params.sessionid || !params.domain) {
|
||||
console.log(usage);
|
||||
phantom.exit();
|
||||
}
|
||||
|
||||
phantom.addCookie({
|
||||
'name': params.cookiename,
|
||||
'value': params.sessionid,
|
||||
'domain': params.domain
|
||||
});
|
||||
|
||||
page.viewportSize = {
|
||||
width: params.width || '800',
|
||||
height: params.height || '400'
|
||||
|
Loading…
Reference in New Issue
Block a user