Commit 6b2a4fe8 by Torkel Ödegaard

feat(instrumentation): work on settings model for internal metrics publishing, #4696

parent 74101eaf
...@@ -6,6 +6,9 @@ ...@@ -6,6 +6,9 @@
# possible values : production, development # possible values : production, development
app_mode = production app_mode = production
# instance name, defaults to HOSTNAME environment variable value or hostname if HOSTNAME var is empty
instance_name = ${HOSTNAME}
#################################### Paths #################################### #################################### Paths ####################################
[paths] [paths]
# Path to where grafana can store temp files, sessions, and the sqlite3 db (if that is used) # Path to where grafana can store temp files, sessions, and the sqlite3 db (if that is used)
......
...@@ -6,6 +6,9 @@ ...@@ -6,6 +6,9 @@
# possible values : production, development # possible values : production, development
; app_mode = production ; app_mode = production
# instance name, defaults to HOSTNAME environment variable value or hostname if HOSTNAME var is empty
; instance_name = ${HOSTNAME}
#################################### Paths #################################### #################################### Paths ####################################
[paths] [paths]
# Path to where grafana can store temp files, sessions, and the sqlite3 db (if that is used) # Path to where grafana can store temp files, sessions, and the sqlite3 db (if that is used)
......
...@@ -64,15 +64,12 @@ func main() { ...@@ -64,15 +64,12 @@ func main() {
social.NewOAuthService() social.NewOAuthService()
eventpublisher.Init() eventpublisher.Init()
plugins.Init() plugins.Init()
metrics.Init()
if err := notifications.Init(); err != nil { if err := notifications.Init(); err != nil {
log.Fatal(3, "Notification service failed to initialize", err) log.Fatal(3, "Notification service failed to initialize", err)
} }
if setting.ReportingEnabled {
go metrics.StartUsageReportLoop()
}
StartServer() StartServer()
exitChan <- 0 exitChan <- 0
} }
......
...@@ -31,7 +31,7 @@ func newMacaron() *macaron.Macaron { ...@@ -31,7 +31,7 @@ func newMacaron() *macaron.Macaron {
for _, route := range plugins.StaticRoutes { for _, route := range plugins.StaticRoutes {
pluginRoute := path.Join("/public/plugins/", route.PluginId) pluginRoute := path.Join("/public/plugins/", route.PluginId)
log.Info("Plugins: Adding route %s -> %s", pluginRoute, route.Directory) log.Debug("Plugins: Adding route %s -> %s", pluginRoute, route.Directory)
mapStatic(m, route.Directory, "", pluginRoute) mapStatic(m, route.Directory, "", pluginRoute)
} }
......
...@@ -9,40 +9,36 @@ import ( ...@@ -9,40 +9,36 @@ import (
"github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/log" "github.com/grafana/grafana/pkg/log"
"github.com/grafana/grafana/pkg/metrics/senders"
m "github.com/grafana/grafana/pkg/models" m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
) )
type MetricSender interface { func Init() {
Send(metrics map[string]interface{}) error go instrumentationLoop()
} }
func StartUsageReportLoop() chan struct{} { func instrumentationLoop() chan struct{} {
M_Instance_Start.Inc(1) M_Instance_Start.Inc(1)
hourTicker := time.NewTicker(time.Hour * 24) settings := readSettings()
secondTicker := time.NewTicker(time.Second * 10)
sender := &receiver.GraphiteSender{ onceEveryDayTick := time.NewTicker(time.Hour * 24)
Host: "localhost", secondTicker := time.NewTicker(time.Second * time.Duration(settings.IntervalSeconds))
Port: "2003",
Protocol: "tcp",
Prefix: "grafana.",
}
for { for {
select { select {
case <-hourTicker.C: case <-onceEveryDayTick.C:
sendUsageStats() sendUsageStats()
case <-secondTicker.C: case <-secondTicker.C:
sendMetricUsage(sender) if settings.Enabled {
sendMetrics(settings)
}
} }
} }
} }
func sendMetricUsage(sender MetricSender) { func sendMetrics(settings *MetricSettings) {
metrics := map[string]interface{}{} metrics := map[string]interface{}{}
MetricStats.Each(func(name string, i interface{}) { MetricStats.Each(func(name string, i interface{}) {
...@@ -63,13 +59,16 @@ func sendMetricUsage(sender MetricSender) { ...@@ -63,13 +59,16 @@ func sendMetricUsage(sender MetricSender) {
} }
}) })
err := sender.Send(metrics) for _, publisher := range settings.Publishers {
if err != nil { publisher.Publish(metrics)
log.Error(1, "Failed to send metrics:", err)
} }
} }
func sendUsageStats() { func sendUsageStats() {
if !setting.ReportingEnabled {
return
}
log.Trace("Sending anonymous usage stats to stats.grafana.org") log.Trace("Sending anonymous usage stats to stats.grafana.org")
version := strings.Replace(setting.BuildVersion, ".", "_", -1) version := strings.Replace(setting.BuildVersion, ".", "_", -1)
......
package receiver package publishers
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"github.com/grafana/grafana/pkg/log"
"net" "net"
"time" "time"
"github.com/grafana/grafana/pkg/log"
"github.com/grafana/grafana/pkg/setting"
) )
type GraphiteSender struct { type GraphitePublisher struct {
Host string Address string
Port string
Protocol string Protocol string
Prefix string Prefix string
} }
func (this *GraphiteSender) Send(metrics map[string]interface{}) error { func CreateGraphitePublisher() (*GraphitePublisher, error) {
log.Debug("GraphiteSender: Sending metrics to graphite") graphiteSection, err := setting.Cfg.GetSection("metrics.graphite")
if err != nil {
return nil, nil
}
address := fmt.Sprintf("%s:%s", this.Host, this.Port) graphiteReceiver := &GraphitePublisher{}
conn, err := net.DialTimeout(this.Protocol, address, time.Second*5) graphiteReceiver.Protocol = "tcp"
graphiteReceiver.Address = graphiteSection.Key("address").MustString("localhost:2003")
graphiteReceiver.Prefix = graphiteSection.Key("prefix").MustString("service.grafana.%(instance_name)s")
return graphiteReceiver, nil
}
func (this *GraphitePublisher) Publish(metrics map[string]interface{}) {
conn, err := net.DialTimeout(this.Protocol, this.Address, time.Second*5)
if err != nil { if err != nil {
return fmt.Errorf("Graphite Sender: Failed to connec to %s!", err) log.Error(3, "Metrics: GraphitePublisher: Failed to connect to %s!", err)
return
} }
buf := bytes.NewBufferString("") buf := bytes.NewBufferString("")
...@@ -30,15 +43,13 @@ func (this *GraphiteSender) Send(metrics map[string]interface{}) error { ...@@ -30,15 +43,13 @@ func (this *GraphiteSender) Send(metrics map[string]interface{}) error {
for key, value := range metrics { for key, value := range metrics {
metricName := this.Prefix + key metricName := this.Prefix + key
line := fmt.Sprintf("%s %d %d\n", metricName, value, now) line := fmt.Sprintf("%s %d %d\n", metricName, value, now)
log.Debug("SendMetric: sending %s", line)
buf.WriteString(line) buf.WriteString(line)
} }
log.Trace("Metrics: GraphitePublisher.Publish() \n%s", buf)
_, err = conn.Write(buf.Bytes()) _, err = conn.Write(buf.Bytes())
if err != nil { if err != nil {
return fmt.Errorf("Graphite Sender: Failed to send metrics! %s", err) log.Error(3, "Metrics: GraphitePublisher: Failed to send metrics! %s", err)
} }
return nil
} }
package metrics
import (
"github.com/grafana/grafana/pkg/log"
"github.com/grafana/grafana/pkg/metrics/publishers"
"github.com/grafana/grafana/pkg/setting"
)
type MetricPublisher interface {
Publish(metrics map[string]interface{})
}
type MetricSettings struct {
Enabled bool
IntervalSeconds int64
Publishers []MetricPublisher
}
func readSettings() *MetricSettings {
var settings = &MetricSettings{
Enabled: false,
Publishers: make([]MetricPublisher, 0),
}
var section, err = setting.Cfg.GetSection("metrics")
if err != nil {
log.Fatal(3, "Unable to find metrics config section")
return nil
}
settings.Enabled = section.Key("enabled").MustBool(false)
settings.IntervalSeconds = section.Key("interval_seconds").MustInt64(10)
if !settings.Enabled {
return settings
}
if graphitePublisher, err := publishers.CreateGraphitePublisher(); err != nil {
log.Error(3, "Metrics: Failed to init Graphite metric publisher", err)
} else if graphitePublisher != nil {
log.Info("Metrics: Internal metrics publisher Graphite initialized")
settings.Publishers = append(settings.Publishers, graphitePublisher)
}
return settings
}
...@@ -40,6 +40,7 @@ var ( ...@@ -40,6 +40,7 @@ var (
Env string = DEV Env string = DEV
AppUrl string AppUrl string
AppSubUrl string AppSubUrl string
InstanceName string
// build // build
BuildVersion string BuildVersion string
...@@ -259,6 +260,12 @@ func evalEnvVarExpression(value string) string { ...@@ -259,6 +260,12 @@ func evalEnvVarExpression(value string) string {
envVar = strings.TrimPrefix(envVar, "${") envVar = strings.TrimPrefix(envVar, "${")
envVar = strings.TrimSuffix(envVar, "}") envVar = strings.TrimSuffix(envVar, "}")
envValue := os.Getenv(envVar) envValue := os.Getenv(envVar)
// if env variable is hostname and it is emtpy use os.Hostname as default
if envVar == "HOSTNAME" && envValue == "" {
envValue, _ = os.Hostname()
}
return envValue return envValue
}) })
} }
...@@ -395,11 +402,28 @@ func validateStaticRootPath() error { ...@@ -395,11 +402,28 @@ func validateStaticRootPath() error {
return fmt.Errorf("Failed to detect generated css or javascript files in static root (%s), have you executed default grunt task?", StaticRootPath) return fmt.Errorf("Failed to detect generated css or javascript files in static root (%s), have you executed default grunt task?", StaticRootPath)
} }
// func readInstanceName() string {
// hostname, _ := os.Hostname()
// if hostname == "" {
// hostname = "hostname_unknown"
// }
//
// instanceName := Cfg.Section("").Key("instance_name").MustString("")
// if instanceName = "" {
// // set value as it might be used in other places
// Cfg.Section("").Key("instance_name").SetValue(hostname)
// instanceName = hostname
// }
//
// return
// }
func NewConfigContext(args *CommandLineArgs) error { func NewConfigContext(args *CommandLineArgs) error {
setHomePath(args) setHomePath(args)
loadConfiguration(args) loadConfiguration(args)
Env = Cfg.Section("").Key("app_mode").MustString("development") Env = Cfg.Section("").Key("app_mode").MustString("development")
InstanceName = Cfg.Section("").Key("instance_name").MustString("unknown_instance_name")
PluginsPath = Cfg.Section("paths").Key("plugins").String() PluginsPath = Cfg.Section("paths").Key("plugins").String()
server := Cfg.Section("server") server := Cfg.Section("server")
......
...@@ -89,5 +89,14 @@ func TestLoadingSettings(t *testing.T) { ...@@ -89,5 +89,14 @@ func TestLoadingSettings(t *testing.T) {
So(DataPath, ShouldEqual, "/tmp/env_override") So(DataPath, ShouldEqual, "/tmp/env_override")
}) })
Convey("instance_name default to hostname even if hostname env is emtpy", func() {
NewConfigContext(&CommandLineArgs{
HomePath: "../../",
})
hostname, _ := os.Hostname()
So(InstanceName, ShouldEqual, hostname)
})
}) })
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment