diff --git a/pkg/api/dtos/stream.go b/pkg/api/dtos/stream.go index 9381c936a77..026ff79c1e8 100644 --- a/pkg/api/dtos/stream.go +++ b/pkg/api/dtos/stream.go @@ -3,7 +3,11 @@ package dtos import "encoding/json" type StreamMessage struct { - Stream string `json:"stream"` - Metric string `json:"metric"` - Datapoints [][]json.Number `json:"Datapoints"` + Stream string `json:"stream"` + Series []StreamMessageSeries `json:"series"` +} + +type StreamMessageSeries struct { + Name string `json:"name"` + Datapoints [][]json.Number `json:"datapoints"` } diff --git a/pkg/api/live/conn.go b/pkg/api/live/conn.go index dcb3cc2b465..d474fc48a1f 100644 --- a/pkg/api/live/conn.go +++ b/pkg/api/live/conn.go @@ -81,7 +81,10 @@ func (c *connection) handleMessage(message []byte) { switch msgType { case "subscribe": h.subChannel <- &streamSubscription{name: streamName, conn: c} + case "unsubscribe": + h.subChannel <- &streamSubscription{name: streamName, conn: c, remove: true} } + } func (c *connection) write(mt int, payload []byte) error { diff --git a/pkg/api/live/hub.go b/pkg/api/live/hub.go index c266d69fb33..736f848db2c 100644 --- a/pkg/api/live/hub.go +++ b/pkg/api/live/hub.go @@ -17,8 +17,9 @@ type hub struct { } type streamSubscription struct { - conn *connection - name string + conn *connection + name string + remove bool } var h = hub{ @@ -40,15 +41,24 @@ func (h *hub) run() { case c := <-h.register: h.connections[c] = true log.Info("Live: New connection (Total count: %v)", len(h.connections)) + case c := <-h.unregister: if _, ok := h.connections[c]; ok { + log.Info("Live: Closing Connection (Total count: %v)", len(h.connections)) delete(h.connections, c) close(c.send) } // hand stream subscriptions case sub := <-h.subChannel: - log.Info("Live: Connection subscribing to: %v", sub.name) + log.Info("Live: Subscribing to: %v, remove: %v", sub.name, sub.remove) subscribers, exists := h.streams[sub.name] + + // handle unsubscribe + if exists && sub.remove { + delete(subscribers, sub.conn) + continue + } + if !exists { subscribers = make(map[*connection]bool) h.streams[sub.name] = subscribers @@ -58,13 +68,19 @@ func (h *hub) run() { // handle stream messages case message := <-h.streamChannel: subscribers, exists := h.streams[message.Stream] - if !exists { + if !exists || len(subscribers) == 0 { log.Info("Live: Message to stream without subscribers: %v", message.Stream) continue } messageBytes, _ := simplejson.NewFromAny(message).Encode() for sub := range subscribers { + // check if channel is open + if _, ok := h.connections[sub]; !ok { + delete(subscribers, sub) + continue + } + select { case sub.send <- messageBytes: default: diff --git a/public/app/core/live/live_srv.ts b/public/app/core/live/live_srv.ts index b6954bdcffc..12ea8ff4266 100644 --- a/public/app/core/live/live_srv.ts +++ b/public/app/core/live/live_srv.ts @@ -42,7 +42,7 @@ export class LiveSrv { }; this.conn.onmessage = (evt) => { - console.log("Live: message received:", evt.data); + this.handleMessage(evt.data); }; this.conn.onerror = (evt) => { @@ -61,6 +61,23 @@ export class LiveSrv { return this.initPromise; } + handleMessage(message) { + message = JSON.parse(message); + + if (!message.stream) { + console.log("Error: stream message without stream!", message); + return; + } + + var observer = this.observers[message.stream]; + if (!observer) { + this.removeObserver(message.stream, null); + return; + } + + observer.next(message); + } + reconnect() { // no need to reconnect if no one cares if (_.keys(this.observers).length === 0) { @@ -89,6 +106,7 @@ export class LiveSrv { } removeObserver(stream, observer) { + console.log('unsubscribe', stream); delete this.observers[stream]; this.getConnection().then(conn => { diff --git a/public/app/plugins/datasource/grafana-live/datasource.ts b/public/app/plugins/datasource/grafana-live/datasource.ts index 13ee30d7ddb..0864057a762 100644 --- a/public/app/plugins/datasource/grafana-live/datasource.ts +++ b/public/app/plugins/datasource/grafana-live/datasource.ts @@ -2,11 +2,13 @@ import {liveSrv} from 'app/core/core'; +import {Observable} from 'vendor/npm/rxjs/Observable'; + export class GrafanaStreamDS { subscription: any; /** @ngInject */ - constructor(private $q) { + constructor() { } @@ -16,11 +18,23 @@ export class GrafanaStreamDS { } var target = options.targets[0]; + + if (this.subscription) { + if (this.subscription.stream !== target.stream) { + this.subscription.unsubscribe(); + } else { + return Promise.resolve({data: []}); + } + } + var observable = liveSrv.subscribe(target.stream); + this.subscription = observable.subscribe(data => { console.log("grafana stream ds data!", data); }); + this.subscription.stream = target.stream; + return Promise.resolve({data: []}); } }