Live: support real time measurements (alpha) (#28022)
* improve reduce transformer * add measurment classes * sync with new grafana measure format * use address for live * use plural in URL * set the field name * fix build * find changes * POST http to channel * Yarn: Update lock file (#28014) * Loki: Run instant query only in Explore (#27974) * Run instant query only in Explore * Replace forEach with for loop * don't cast * Docs: Fixed row display in table (#28031) * Plugins: Let descendant plugins inherit their root's signature (#27970) * plugins: Let descendant plugins inherit their root's signature Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Registry: Fix service shutdown mode trigger location (#28025) * Add Alex Khomenko as member (#28032) * show history * fix confirm * fix confirm * add tests * fix lint * add more errors * set values * remove unrelated changes * unrelated changes * Update pkg/models/live.go Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com> * Update pkg/models/live.go Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com> * Update pkg/services/live/live.go Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com> * Update pkg/services/live/pluginHandler.go Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com> * Update pkg/services/live/pluginHandler.go Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com> * Update pkg/services/live/pluginHandler.go Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com> * use measurments for testdata endpoints * add live to testdata * add live to testdata * Update pkg/services/live/channel.go Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com> * Apply suggestions from code review Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com> * update comment formats * uprevert testdata * Apply suggestions from code review Co-authored-by: Will Browne <wbrowne@users.noreply.github.com> Co-authored-by: Ryan McKinley <ryantxu@gmail.com> Co-authored-by: Hugo Häggmark <hugo.haggmark@grafana.com> * Apply suggestions from code review * CloudWatch: Add EC2CapacityReservations Namespace (#28309) * API: Fix short URLs (#28300) * API: Fix short URLs Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Chore: Add cloud-middleware as code owners (#28310) Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * SQLStore: Run tests as integration tests (#28265) * sqlstore: Run tests as integration tests * Truncate database instead of re-creating it on each test * Fix test description See https://github.com/grafana/grafana/pull/12129 * Fix lint issues * Fix postgres dialect after review suggestion * Rename and document functions after review suggestion * Add periods * Fix auto-increment value for mysql dialect Co-authored-by: Emil Tullstedt <emil.tullstedt@grafana.com> * Drone: Fix grafana-mixin linting (#28308) * Drone: Fix Starlark script Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * grafana-mixin: Move build logic to scripts Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Drone: Use mixin scripts Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * CI build image: Install jsonnetfmt and mixtool Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Makefile: Print commands Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * should only ignore the file in the grafana mixin root folder (#28306) Signed-off-by: bergquist <carl.bergquist@gmail.com> * fix: for graph size not taking up full height or width * Graph NG: fix toggling queries and extract Graph component from graph3 panel (#28290) * Fix issue when data and config is not in sync * Extract GraphNG component from graph panel and add some tests coverage * Update packages/grafana-ui/src/components/uPlot/hooks.test.ts * Update packages/grafana-ui/src/components/uPlot/hooks.test.ts * Update packages/grafana-ui/src/components/uPlot/hooks.test.ts * Fix grid color and annotations refresh * Drone: Use ${DRONE_TAG} in release pipelines, since it should work (#28299) Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Explore: respect min_refresh_interval (#27988) * Explore: respect min_refresh_interval Fixes #27494 * fixup! Explore: respect min_refresh_interval * fixup! Explore: respect min_refresh_interval * UI: export defaultIntervals from refresh picker * fixup! Explore: respect min_refresh_interval Co-authored-by: Zoltán Bedi <zoltan.bedi@gmail.com> * Loki: Base maxDataPoints limits on query type (#28298) * Base maxLines and maxDataPoints based on query type * Allow overriding the limit to higher value * Bump tree-kill from 1.2.1 to 1.2.2 (#27405) Bumps [tree-kill](https://github.com/pkrumins/node-tree-kill) from 1.2.1 to 1.2.2. - [Release notes](https://github.com/pkrumins/node-tree-kill/releases) - [Commits](https://github.com/pkrumins/node-tree-kill/compare/v1.2.1...v1.2.2) Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Bump handlebars from 4.4.3 to 4.7.6 (#27416) Bumps [handlebars](https://github.com/wycats/handlebars.js) from 4.4.3 to 4.7.6. - [Release notes](https://github.com/wycats/handlebars.js/releases) - [Changelog](https://github.com/handlebars-lang/handlebars.js/blob/master/release-notes.md) - [Commits](https://github.com/wycats/handlebars.js/compare/v4.4.3...v4.7.6) Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Build(deps): Bump http-proxy from 1.18.0 to 1.18.1 (#27507) Bumps [http-proxy](https://github.com/http-party/node-http-proxy) from 1.18.0 to 1.18.1. - [Release notes](https://github.com/http-party/node-http-proxy/releases) - [Changelog](https://github.com/http-party/node-http-proxy/blob/master/CHANGELOG.md) - [Commits](https://github.com/http-party/node-http-proxy/compare/1.18.0...1.18.1) Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Automation: Add backport github action (#28318) * BackendSrv: Fixes queue countdown when unsubscribe is before response (#28323) * GraphNG: Use AxisSide enum (#28320) * IssueTriage: Needs more info automation and messages (#28137) * IssueTriage: Needs more info automation and messages * Updated * Updated * Updated wording * SAML: IdP-initiated SSO docs (#28280) * SAML: IdP-initiated SSO docs * Update docs/sources/enterprise/saml.md Co-authored-by: Emil Tullstedt <emil.tullstedt@grafana.com> * Apply suggestions from code review Co-authored-by: Emil Tullstedt <emil.tullstedt@grafana.com> Co-authored-by: Emil Tullstedt <emil.tullstedt@grafana.com> * Loki: Run instant query only when doing metric query (#28325) * Run instant query only when doing metric query * Update public/app/plugins/datasource/loki/datasource.ts Co-authored-by: Ivana Huckova <30407135+ivanahuckova@users.noreply.github.com> Co-authored-by: Ivana Huckova <30407135+ivanahuckova@users.noreply.github.com> * Automation: Tweaks to more info message (#28332) * AlertingNG: remove warn/crit from eval prototype (#28334) and misc cleanup * area/grafana/toolkit: update e2e docker image (#28335) * add xvfb to image * comment out toolkit inclusion * add latest tag * update packages for cypress * cleanup script * Update auth-proxy.md (#28339) Fix a minor grammar mistake: 'handling' to 'handle'. * Git: Create .gitattributes for windows line endings (#28340) With this set, Windows users will have text files converted from Windows style line endings (\r\n) to Unix style line endings (\n) when they’re added to the repository. https://www.edwardthomson.com/blog/git_for_windows_line_endings.html * Docs: Add docs for valuepicker (#28327) * Templating: Replace all '$tag' in tag values query (#28343) * Docs: Add missing records from grafana-ui 7.2.1 CHANGELOG (#28302) * Dashboard links: Places drop down list so it's always visible (#28330) * calculating whether to place the list on the right or left edge of the parent * change naming and add import of createRef * Automation: Update backport github action trigger (#28352) It seems like GitHub has solved the problem of running github actions on PRs from forks with access to secrets. https://github.blog/2020-08-03-github-actions-improvements-for-fork-and-pull-request-workflows/#improvements-for-public-repository-forks If I change the event that triggers it to pull_request_target the action is run in the context of the base instead of the merged PR branch * ColorSchemes: Adds more color schemes and text colors that depend on the background (#28305) * Adding more color modes and text colors that depend on the background color * Updates * Updated * Another big value fix * Fixing unit tests * Updated * Updated test * Update * Updated * Updated * Updated * Updated * Added new demo dashboard * Updated * updated * Updated * Updateed * added beta notice * Fixed e2e test * Fix typos Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * revert pseduo code * apply feedback * remove HTTP for now * fix backend test * change to datasource * clear input for streams * fix docs? * consistent measure vs measurements * better jsdocs * fix a few jsdoc errors * fix comment style * Remove commented out code Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Clean up code Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Clean up code Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Clean up code Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Clean up code Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Clean up code Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Clean up code Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Clean up code Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Update pkg/models/live.go Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix build Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * set the stringField Co-authored-by: Torkel Ödegaard <torkel@grafana.org> Co-authored-by: Ivana Huckova <30407135+ivanahuckova@users.noreply.github.com> Co-authored-by: ozhuang <ozhuang.95@gmail.com> Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com> Co-authored-by: Amos Law <ahlaw.dev@gmail.com> Co-authored-by: Alex Khomenko <Clarity-89@users.noreply.github.com> Co-authored-by: Will Browne <wbrowne@users.noreply.github.com> Co-authored-by: Hugo Häggmark <hugo.haggmark@grafana.com> Co-authored-by: The Rock Guy <fabian.bracco@gvcgroup.com.au> Co-authored-by: Sofia Papagiannaki <papagian@users.noreply.github.com> Co-authored-by: Emil Tullstedt <emil.tullstedt@grafana.com> Co-authored-by: Carl Bergquist <carl@grafana.com> Co-authored-by: Jack Westbrook <jack.westbrook@gmail.com> Co-authored-by: Dominik Prokop <dominik.prokop@grafana.com> Co-authored-by: Elliot Pryde <elliot.pryde@elliotpryde.com> Co-authored-by: Zoltán Bedi <zoltan.bedi@gmail.com> Co-authored-by: Andrej Ocenas <mr.ocenas@gmail.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Alexander Zobnin <alexanderzobnin@gmail.com> Co-authored-by: Kyle Brandt <kyle@grafana.com> Co-authored-by: Brian Gann <briangann@users.noreply.github.com> Co-authored-by: J-F-Far <joel.f.farthing@gmail.com> Co-authored-by: acoder77 <73009264+acoder77@users.noreply.github.com> Co-authored-by: Peter Holmberg <peterholmberg@users.noreply.github.com> Co-authored-by: Krzysztof Dąbrowski <krzysdabro@live.com> Co-authored-by: maknik <mooniczkam@gmail.com>
This commit is contained in:
@@ -1,27 +1,48 @@
|
||||
package live
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ChannelIdentifier is the channel id split by parts
|
||||
type ChannelIdentifier struct {
|
||||
Scope string // grafana, ds, or plugin
|
||||
Namespace string // feature, id, or name
|
||||
Path string // path within the channel handler
|
||||
// ChannelAddress is the channel ID split by parts.
|
||||
type ChannelAddress struct {
|
||||
// Scope is "grafana", "ds", or "plugin".
|
||||
Scope string `json:"scope,omitempty"`
|
||||
|
||||
// Namespace meaning depends on the scope.
|
||||
// * when grafana, namespace is a "feature"
|
||||
// * when ds, namespace is the datasource id
|
||||
// * when plugin, namespace is the plugin name
|
||||
Namespace string `json:"namespace,omitempty"`
|
||||
|
||||
// Within each namespace, the handler can process the path as needed.
|
||||
Path string `json:"path,omitempty"`
|
||||
}
|
||||
|
||||
// ParseChannelIdentifier parses the parts from a channel id:
|
||||
// ${scope} / ${namespace} / ${path}
|
||||
func ParseChannelIdentifier(id string) (ChannelIdentifier, error) {
|
||||
// ParseChannelAddress parses the parts from a channel ID:
|
||||
// ${scope} / ${namespace} / ${path}.
|
||||
func ParseChannelAddress(id string) ChannelAddress {
|
||||
addr := ChannelAddress{}
|
||||
parts := strings.SplitN(id, "/", 3)
|
||||
if len(parts) == 3 {
|
||||
return ChannelIdentifier{
|
||||
Scope: parts[0],
|
||||
Namespace: parts[1],
|
||||
Path: parts[2],
|
||||
}, nil
|
||||
length := len(parts)
|
||||
if length > 0 {
|
||||
addr.Scope = parts[0]
|
||||
}
|
||||
return ChannelIdentifier{}, fmt.Errorf("Invalid channel id: %s", id)
|
||||
if length > 1 {
|
||||
addr.Namespace = parts[1]
|
||||
}
|
||||
if length > 2 {
|
||||
addr.Path = parts[2]
|
||||
}
|
||||
return addr
|
||||
}
|
||||
|
||||
// IsValid checks if all parts of the address are valid.
|
||||
func (ca *ChannelAddress) IsValid() bool {
|
||||
return ca.Scope != "" && ca.Namespace != "" && ca.Path != ""
|
||||
}
|
||||
|
||||
// ToChannelID converts this to a single string.
|
||||
func (ca *ChannelAddress) ToChannelID() string {
|
||||
return ca.Scope + "/" + ca.Namespace + "/" + ca.Path
|
||||
}
|
||||
|
||||
@@ -4,27 +4,25 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestParseChannelIdentifier(t *testing.T) {
|
||||
ident, err := ParseChannelIdentifier("aaa/bbb/ccc/ddd")
|
||||
if err != nil {
|
||||
t.FailNow()
|
||||
}
|
||||
func TestParseChannelAddress_Valid(t *testing.T) {
|
||||
addr := ParseChannelAddress("aaa/bbb/ccc/ddd")
|
||||
require.True(t, addr.IsValid())
|
||||
|
||||
ex := ChannelIdentifier{
|
||||
ex := ChannelAddress{
|
||||
Scope: "aaa",
|
||||
Namespace: "bbb",
|
||||
Path: "ccc/ddd",
|
||||
}
|
||||
|
||||
if diff := cmp.Diff(ident, ex); diff != "" {
|
||||
if diff := cmp.Diff(addr, ex); diff != "" {
|
||||
t.Fatalf("Result mismatch (-want +got):\n%s", diff)
|
||||
}
|
||||
|
||||
// Check an invalid identifier
|
||||
_, err = ParseChannelIdentifier("aaa/bbb")
|
||||
if err == nil {
|
||||
t.FailNow()
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseChannelAddress_Invalid(t *testing.T) {
|
||||
addr := ParseChannelAddress("aaa/bbb")
|
||||
require.False(t, addr.IsValid())
|
||||
}
|
||||
|
||||
@@ -6,27 +6,28 @@ import (
|
||||
)
|
||||
|
||||
// BroadcastRunner will simply broadcast all events to `grafana/broadcast/*` channels
|
||||
// This makes no assumptions about the shape of the data and will broadcast it to anyone listening
|
||||
type BroadcastRunner struct{}
|
||||
// This assumes that data is a JSON object
|
||||
type BroadcastRunner struct {
|
||||
}
|
||||
|
||||
// GetHandlerForPath called on init
|
||||
func (g *BroadcastRunner) GetHandlerForPath(path string) (models.ChannelHandler, error) {
|
||||
return g, nil // for now all channels share config
|
||||
func (b *BroadcastRunner) GetHandlerForPath(path string) (models.ChannelHandler, error) {
|
||||
return b, nil // for now all channels share config
|
||||
}
|
||||
|
||||
// GetChannelOptions called fast and often
|
||||
func (g *BroadcastRunner) GetChannelOptions(id string) centrifuge.ChannelOptions {
|
||||
func (b *BroadcastRunner) GetChannelOptions(id string) centrifuge.ChannelOptions {
|
||||
return centrifuge.ChannelOptions{}
|
||||
}
|
||||
|
||||
// OnSubscribe for now allows anyone to subscribe to any dashboard
|
||||
func (g *BroadcastRunner) OnSubscribe(c *centrifuge.Client, e centrifuge.SubscribeEvent) error {
|
||||
func (b *BroadcastRunner) OnSubscribe(c *centrifuge.Client, e centrifuge.SubscribeEvent) error {
|
||||
// anyone can subscribe
|
||||
return nil
|
||||
}
|
||||
|
||||
// OnPublish called when an event is received from the websocket
|
||||
func (g *BroadcastRunner) OnPublish(c *centrifuge.Client, e centrifuge.PublishEvent) ([]byte, error) {
|
||||
func (b *BroadcastRunner) OnPublish(c *centrifuge.Client, e centrifuge.PublishEvent) ([]byte, error) {
|
||||
// expect the data to be the right shape?
|
||||
return e.Data, nil
|
||||
}
|
||||
|
||||
@@ -17,14 +17,7 @@ type dashboardEvent struct {
|
||||
|
||||
// DashboardHandler manages all the `grafana/dashboard/*` channels
|
||||
type DashboardHandler struct {
|
||||
publisher models.ChannelPublisher
|
||||
}
|
||||
|
||||
// CreateDashboardHandler Initialize a dashboard handler
|
||||
func CreateDashboardHandler(p models.ChannelPublisher) DashboardHandler {
|
||||
return DashboardHandler{
|
||||
publisher: p,
|
||||
}
|
||||
Publisher models.ChannelPublisher
|
||||
}
|
||||
|
||||
// GetHandlerForPath called on init
|
||||
@@ -58,7 +51,7 @@ func (g *DashboardHandler) publish(event dashboardEvent) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return g.publisher("grafana/dashboard/"+event.UID, msg)
|
||||
return g.Publisher("grafana/dashboard/"+event.UID, msg)
|
||||
}
|
||||
|
||||
// DashboardSaved will broadcast to all connected dashboards
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
package features
|
||||
|
||||
import (
|
||||
"github.com/centrifugal/centrifuge"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
)
|
||||
|
||||
var (
|
||||
logger = log.New("live.features") // scoped to all features?
|
||||
)
|
||||
|
||||
// MeasurementsRunner will simply broadcast all events to `grafana/broadcast/*` channels.
|
||||
// This makes no assumptions about the shape of the data and will broadcast it to anyone listening
|
||||
type MeasurementsRunner struct {
|
||||
}
|
||||
|
||||
// GetHandlerForPath gets the handler for a path.
|
||||
// It's called on init.
|
||||
func (m *MeasurementsRunner) GetHandlerForPath(path string) (models.ChannelHandler, error) {
|
||||
return m, nil // for now all channels share config
|
||||
}
|
||||
|
||||
// GetChannelOptions gets channel options.
|
||||
// It gets called fast and often.
|
||||
func (m *MeasurementsRunner) GetChannelOptions(id string) centrifuge.ChannelOptions {
|
||||
return centrifuge.ChannelOptions{}
|
||||
}
|
||||
|
||||
// OnSubscribe for now allows anyone to subscribe to any dashboard.
|
||||
func (m *MeasurementsRunner) OnSubscribe(c *centrifuge.Client, e centrifuge.SubscribeEvent) error {
|
||||
// anyone can subscribe
|
||||
return nil
|
||||
}
|
||||
|
||||
// OnPublish is called when an event is received from the websocket.
|
||||
func (m *MeasurementsRunner) OnPublish(c *centrifuge.Client, e centrifuge.PublishEvent) ([]byte, error) {
|
||||
// currently generic... but should be stricter
|
||||
// logger.Debug("Measurements runner got event on channel", "channel", e.Channel)
|
||||
return e.Data, nil
|
||||
}
|
||||
@@ -7,48 +7,43 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/centrifugal/centrifuge"
|
||||
"github.com/grafana/grafana/pkg/cmd/grafana-cli/logger"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
)
|
||||
|
||||
// TestdataRunner manages all the `grafana/dashboard/*` channels
|
||||
type testdataRunner struct {
|
||||
// testDataRunner manages all the `grafana/dashboard/*` channels.
|
||||
type testDataRunner struct {
|
||||
publisher models.ChannelPublisher
|
||||
running bool
|
||||
speedMillis int
|
||||
dropPercent float64
|
||||
channel string
|
||||
name string
|
||||
}
|
||||
|
||||
// TestdataSupplier manages all the `grafana/testdata/*` channels
|
||||
type TestdataSupplier struct {
|
||||
publisher models.ChannelPublisher
|
||||
// TestDataSupplier manages all the `grafana/testdata/*` channels.
|
||||
type TestDataSupplier struct {
|
||||
Publisher models.ChannelPublisher
|
||||
}
|
||||
|
||||
// CreateTestdataSupplier Initialize a dashboard handler
|
||||
func CreateTestdataSupplier(p models.ChannelPublisher) TestdataSupplier {
|
||||
return TestdataSupplier{
|
||||
publisher: p,
|
||||
}
|
||||
}
|
||||
|
||||
// GetHandlerForPath called on init
|
||||
func (g *TestdataSupplier) GetHandlerForPath(path string) (models.ChannelHandler, error) {
|
||||
// GetHandlerForPath gets the channel handler for a path.
|
||||
// Called on init.
|
||||
func (g *TestDataSupplier) GetHandlerForPath(path string) (models.ChannelHandler, error) {
|
||||
channel := "grafana/testdata/" + path
|
||||
|
||||
if path == "random-2s-stream" {
|
||||
return &testdataRunner{
|
||||
publisher: g.publisher,
|
||||
return &testDataRunner{
|
||||
publisher: g.Publisher,
|
||||
running: false,
|
||||
speedMillis: 2000,
|
||||
dropPercent: 0,
|
||||
channel: channel,
|
||||
name: path,
|
||||
}, nil
|
||||
}
|
||||
|
||||
if path == "random-flakey-stream" {
|
||||
return &testdataRunner{
|
||||
publisher: g.publisher,
|
||||
return &testDataRunner{
|
||||
publisher: g.Publisher,
|
||||
running: false,
|
||||
speedMillis: 400,
|
||||
dropPercent: .6,
|
||||
@@ -59,13 +54,14 @@ func (g *TestdataSupplier) GetHandlerForPath(path string) (models.ChannelHandler
|
||||
return nil, fmt.Errorf("unknown channel")
|
||||
}
|
||||
|
||||
// GetChannelOptions called fast and often
|
||||
func (g *testdataRunner) GetChannelOptions(id string) centrifuge.ChannelOptions {
|
||||
// GetChannelOptions gets channel options.
|
||||
// Called fast and often.
|
||||
func (g *testDataRunner) GetChannelOptions(id string) centrifuge.ChannelOptions {
|
||||
return centrifuge.ChannelOptions{}
|
||||
}
|
||||
|
||||
// OnSubscribe for now allows anyone to subscribe to any dashboard
|
||||
func (g *testdataRunner) OnSubscribe(c *centrifuge.Client, e centrifuge.SubscribeEvent) error {
|
||||
// OnSubscribe for now allows anyone to subscribe to any dashboard.
|
||||
func (g *testDataRunner) OnSubscribe(c *centrifuge.Client, e centrifuge.SubscribeEvent) error {
|
||||
if !g.running {
|
||||
g.running = true
|
||||
|
||||
@@ -77,26 +73,26 @@ func (g *testdataRunner) OnSubscribe(c *centrifuge.Client, e centrifuge.Subscrib
|
||||
return nil
|
||||
}
|
||||
|
||||
// OnPublish called when an event is received from the websocket
|
||||
func (g *testdataRunner) OnPublish(c *centrifuge.Client, e centrifuge.PublishEvent) ([]byte, error) {
|
||||
// OnPublish is called when an event is received from the websocket.
|
||||
func (g *testDataRunner) OnPublish(c *centrifuge.Client, e centrifuge.PublishEvent) ([]byte, error) {
|
||||
return nil, fmt.Errorf("can not publish to testdata")
|
||||
}
|
||||
|
||||
type randomWalkMessage struct {
|
||||
Time int64
|
||||
Value float64
|
||||
Min float64
|
||||
Max float64
|
||||
}
|
||||
|
||||
// RunRandomCSV just for an example
|
||||
func (g *testdataRunner) runRandomCSV() {
|
||||
// runRandomCSV is just for an example.
|
||||
func (g *testDataRunner) runRandomCSV() {
|
||||
spread := 50.0
|
||||
|
||||
walker := rand.Float64() * 100
|
||||
ticker := time.NewTicker(time.Duration(g.speedMillis) * time.Millisecond)
|
||||
|
||||
line := randomWalkMessage{}
|
||||
measurement := models.Measurement{
|
||||
Name: g.name,
|
||||
Time: 0,
|
||||
Values: make(map[string]interface{}, 5),
|
||||
}
|
||||
msg := models.MeasurementBatch{
|
||||
Measurements: []models.Measurement{measurement}, // always a single measurement
|
||||
}
|
||||
|
||||
for t := range ticker.C {
|
||||
if rand.Float64() <= g.dropPercent {
|
||||
@@ -105,12 +101,12 @@ func (g *testdataRunner) runRandomCSV() {
|
||||
delta := rand.Float64() - 0.5
|
||||
walker += delta
|
||||
|
||||
line.Time = t.UnixNano() / int64(time.Millisecond)
|
||||
line.Value = walker
|
||||
line.Min = walker - ((rand.Float64() * spread) + 0.01)
|
||||
line.Max = walker + ((rand.Float64() * spread) + 0.01)
|
||||
measurement.Time = t.UnixNano() / int64(time.Millisecond)
|
||||
measurement.Values["value"] = walker
|
||||
measurement.Values["min"] = walker - ((rand.Float64() * spread) + 0.01)
|
||||
measurement.Values["max"] = walker + ((rand.Float64() * spread) + 0.01)
|
||||
|
||||
bytes, err := json.Marshal(&line)
|
||||
bytes, err := json.Marshal(&msg)
|
||||
if err != nil {
|
||||
logger.Warn("unable to marshal line", "error", err)
|
||||
continue
|
||||
@@ -118,7 +114,7 @@ func (g *testdataRunner) runRandomCSV() {
|
||||
|
||||
err = g.publisher(g.channel, bytes)
|
||||
if err != nil {
|
||||
logger.Warn("write", "channel", g.channel, "line", line)
|
||||
logger.Warn("write", "channel", g.channel, "measurement", measurement)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+37
-24
@@ -20,7 +20,7 @@ var (
|
||||
|
||||
// CoreGrafanaScope list of core features
|
||||
type CoreGrafanaScope struct {
|
||||
Features map[string]models.ChannelHandlerProvider
|
||||
Features map[string]models.ChannelHandlerFactory
|
||||
|
||||
// The generic service to advertise dashboard changes
|
||||
Dashboards models.DashboardActivityChannel
|
||||
@@ -47,7 +47,7 @@ func InitializeBroker() (*GrafanaLive, error) {
|
||||
channels: make(map[string]models.ChannelHandler),
|
||||
channelsMu: sync.RWMutex{},
|
||||
GrafanaScope: CoreGrafanaScope{
|
||||
Features: make(map[string]models.ChannelHandlerProvider),
|
||||
Features: make(map[string]models.ChannelHandlerFactory),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -83,13 +83,17 @@ func InitializeBroker() (*GrafanaLive, error) {
|
||||
glive.node = node
|
||||
|
||||
// Initialize the main features
|
||||
dash := features.CreateDashboardHandler(glive.Publish)
|
||||
tds := features.CreateTestdataSupplier(glive.Publish)
|
||||
dash := &features.DashboardHandler{
|
||||
Publisher: glive.Publish,
|
||||
}
|
||||
|
||||
glive.GrafanaScope.Dashboards = &dash
|
||||
glive.GrafanaScope.Features["dashboard"] = &dash
|
||||
glive.GrafanaScope.Features["testdata"] = &tds
|
||||
glive.GrafanaScope.Dashboards = dash
|
||||
glive.GrafanaScope.Features["dashboard"] = dash
|
||||
glive.GrafanaScope.Features["testdata"] = &features.TestDataSupplier{
|
||||
Publisher: glive.Publish,
|
||||
}
|
||||
glive.GrafanaScope.Features["broadcast"] = &features.BroadcastRunner{}
|
||||
glive.GrafanaScope.Features["measurements"] = &features.MeasurementsRunner{}
|
||||
|
||||
// Set ConnectHandler called when client successfully connected to Node. Your code
|
||||
// inside handler must be synchronized since it will be called concurrently from
|
||||
@@ -232,11 +236,11 @@ func (g *GrafanaLive) GetChannelHandler(channel string) (models.ChannelHandler,
|
||||
}
|
||||
|
||||
// Parse the identifier ${scope}/${namespace}/${path}
|
||||
id, err := ParseChannelIdentifier(channel)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
addr := ParseChannelAddress(channel)
|
||||
if !addr.IsValid() {
|
||||
return nil, fmt.Errorf("invalid channel: %q", channel)
|
||||
}
|
||||
logger.Info("initChannel", "channel", channel, "id", id)
|
||||
logger.Info("initChannel", "channel", channel, "address", addr)
|
||||
|
||||
g.channelsMu.Lock()
|
||||
defer g.channelsMu.Unlock()
|
||||
@@ -245,39 +249,48 @@ func (g *GrafanaLive) GetChannelHandler(channel string) (models.ChannelHandler,
|
||||
return c, nil
|
||||
}
|
||||
|
||||
c, err = g.initChannel(id)
|
||||
getter, err := g.GetChannelHandlerFactory(addr.Scope, addr.Namespace)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// First access will initialize
|
||||
c, err = getter.GetHandlerForPath(addr.Path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
g.channels[channel] = c
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (g *GrafanaLive) initChannel(id ChannelIdentifier) (models.ChannelHandler, error) {
|
||||
if id.Scope == "grafana" {
|
||||
p, ok := g.GrafanaScope.Features[id.Namespace]
|
||||
// GetChannelHandlerFactory gets a ChannelHandlerFactory for a namespace.
|
||||
// It gives threadsafe access to the channel.
|
||||
func (g *GrafanaLive) GetChannelHandlerFactory(scope string, name string) (models.ChannelHandlerFactory, error) {
|
||||
if scope == "grafana" {
|
||||
p, ok := g.GrafanaScope.Features[name]
|
||||
if ok {
|
||||
return p.GetHandlerForPath(id.Path)
|
||||
return p, nil
|
||||
}
|
||||
return nil, fmt.Errorf("Unknown feature: %s", id.Namespace)
|
||||
return nil, fmt.Errorf("unknown feature: %q", name)
|
||||
}
|
||||
|
||||
if id.Scope == "ds" {
|
||||
return nil, fmt.Errorf("todo... look up datasource: %s", id.Namespace)
|
||||
if scope == "ds" {
|
||||
return nil, fmt.Errorf("todo... look up datasource: %q", name)
|
||||
}
|
||||
|
||||
if id.Scope == "plugin" {
|
||||
p, ok := plugins.Plugins[id.Namespace]
|
||||
if scope == "plugin" {
|
||||
p, ok := plugins.Plugins[name]
|
||||
if ok {
|
||||
h := &PluginHandler{
|
||||
Plugin: p,
|
||||
}
|
||||
return h.GetHandlerForPath(id.Path)
|
||||
return h, nil
|
||||
}
|
||||
return nil, fmt.Errorf("unknown plugin: %s", id.Namespace)
|
||||
return nil, fmt.Errorf("unknown plugin: %q", name)
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("invalid scope: %s", id.Scope)
|
||||
return nil, fmt.Errorf("invalid scope: %q", scope)
|
||||
}
|
||||
|
||||
// Publish sends the data to the channel without checking permissions etc
|
||||
|
||||
Reference in New Issue
Block a user