mirror of
https://github.com/grafana/grafana.git
synced 2026-08-13 06:34:55 -05:00
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:
co-authored by
dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Emil Tullstedt
Ivana Huckova
Arve Knudsen
Torkel Ödegaard
ozhuang
Amos Law
Alex Khomenko
Will Browne
Hugo Häggmark
The Rock Guy
Sofia Papagiannaki
Carl Bergquist
Jack Westbrook
Dominik Prokop
Elliot Pryde
Zoltán Bedi
Andrej Ocenas
Alexander Zobnin
Kyle Brandt
Brian Gann
J-F-Far
acoder77
Peter Holmberg
Krzysztof Dąbrowski
maknik
parent
a71eadf379
commit
2aafa39879
+5
-4
@@ -2,7 +2,7 @@ package models
|
||||
|
||||
import "github.com/centrifugal/centrifuge"
|
||||
|
||||
// ChannelPublisher writes data into a channel
|
||||
// ChannelPublisher writes data into a channel. Note that pemissions are not checked.
|
||||
type ChannelPublisher func(channel string, data []byte) error
|
||||
|
||||
// ChannelHandler defines the core channel behavior
|
||||
@@ -17,9 +17,10 @@ type ChannelHandler interface {
|
||||
OnPublish(c *centrifuge.Client, e centrifuge.PublishEvent) ([]byte, error)
|
||||
}
|
||||
|
||||
// ChannelHandlerProvider -- this should be implemented by any core feature
|
||||
type ChannelHandlerProvider interface {
|
||||
// This is called fast and often -- it must be synchrnozed
|
||||
// ChannelHandlerFactory should be implemented by all core features.
|
||||
type ChannelHandlerFactory interface {
|
||||
// GetHandlerForPath gets a ChannelHandler for a path.
|
||||
// This is called fast and often -- it must be synchronized
|
||||
GetHandlerForPath(path string) (ChannelHandler, error)
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
package models
|
||||
|
||||
import "github.com/grafana/grafana-plugin-sdk-go/data"
|
||||
|
||||
// NOTE:
|
||||
// this likely should go in the Plugin SDK since it will be useful from plugins
|
||||
|
||||
// Measurement is a single measurement value.
|
||||
type Measurement struct {
|
||||
// Name of the measurement.
|
||||
Name string `json:"name,omitempty"`
|
||||
|
||||
// Time is the measurement time. Units are usually ms, but depends on the channel
|
||||
Time int64 `json:"time,omitempty"`
|
||||
|
||||
// Values is the measurement's values. The value type is typically number or string.
|
||||
Values map[string]interface{} `json:"values,omitempty"`
|
||||
|
||||
// Config is an optional list of field configs.
|
||||
Config map[string]data.FieldConfig `json:"config,omitempty"`
|
||||
|
||||
// Labels are applied to all values.
|
||||
Labels map[string]string `json:"labels,omitempty"`
|
||||
}
|
||||
|
||||
// MeasurementAction defines what should happen when you send a list of measurements.
|
||||
type MeasurementAction string
|
||||
|
||||
const (
|
||||
// MeasurementActionAppend means new values should be added to a client buffer. This is the default action
|
||||
MeasurementActionAppend MeasurementAction = "append"
|
||||
|
||||
// MeasurementActionReplace means new values should replace any existing values.
|
||||
MeasurementActionReplace MeasurementAction = "replace"
|
||||
|
||||
// MeasurementActionClear means all existing values should be remoed before adding.
|
||||
MeasurementActionClear MeasurementAction = "clear"
|
||||
)
|
||||
|
||||
// MeasurementBatch is a collection of measurements all sent at once.
|
||||
type MeasurementBatch struct {
|
||||
// Action is the action in question, the default is append.
|
||||
Action MeasurementAction `json:"action,omitempty"`
|
||||
|
||||
// Measurements is the array of measurements.
|
||||
Measurements []Measurement `json:"measurements,omitempty"`
|
||||
|
||||
// Capacity is the suggested size of the client buffer
|
||||
Capacity int64 `json:"capacity,omitempty"`
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -259,6 +259,15 @@ func init() {
|
||||
},
|
||||
})
|
||||
|
||||
registerScenario(&Scenario{
|
||||
Id: "live",
|
||||
Name: "Grafana Live",
|
||||
Handler: func(query *tsdb.Query, context *tsdb.TsdbQuery) *tsdb.QueryResult {
|
||||
// Real work is in javascript client
|
||||
return tsdb.NewQueryResult()
|
||||
},
|
||||
})
|
||||
|
||||
registerScenario(&Scenario{
|
||||
Id: "grafana_api",
|
||||
Name: "Grafana API",
|
||||
|
||||
Reference in New Issue
Block a user