pub struct Plugin<D, Q, R, S> { /* private fields */ }
Expand description

Main entrypoint into the Grafana plugin SDK.

A Plugin handles the negotiation with Grafana, adding gRPC health checks, serving the various Services, and gracefully exiting if configured.

Shutdown Handler

A plugin can be configured to gracefully shutdown by calling the Plugin::shutdown_handler method. This will spawn a new task which simply waits for any TCP connection on the given address, and triggers a graceful shutdown of the underlying gRPC server. This is helpful during development using Docker: a tool such as cargo-watch can be used to rebuild on changes, then use nc to trigger a shutdown of the plugin. Grafana will automatically plugins when their process exits, so this avoids having to restart the Grafana server on every change. An example configuration for this can be seen in the grafana-sample-backend-plugin-rust repository.

Type parameters

The four type parameters of a Plugin represent the services that this plugin is configured to run. When created using Plugin::new the plugin has the NoopService associated with each type, which simply does not register anything for this piece of functionality. Calling the various methods on Plugin (such as Plugin::data_service and Plugin::stream_service) will return a new Plugin with your plugin implementation registered for that functionality.

The type parameters stand for:

  • D, the diagnostic service
  • Q, the data service (‘Q’ stands for ‘query’, here)
  • R, the resource service
  • S, the streaming service

Example:

use futures_util::stream::FuturesOrdered;
use grafana_plugin_sdk::{backend, prelude::*};
use thiserror::Error;

#[derive(Clone)]
struct MyPlugin;

/// An error that may occur during a query.
///
/// This must store the `ref_id` of the query so that Grafana can line it up.
#[derive(Debug, Error)]
#[error("Error querying backend for {ref_id}")]
struct QueryError {
    ref_id: String,
};

impl backend::DataQueryError for QueryError {
    fn ref_id(self) -> String {
        self.ref_id
    }
}

#[tonic::async_trait]
impl backend::DataService for MyPlugin {

    /// The type of JSON data sent from Grafana to our backend plugin.
    ///
    /// This will correspond to the `TQuery` type parameter of the frontend
    /// datasource.
    ///
    /// We can use `serde_json::Value` if we want to accept any JSON.
    type Query = serde_json::Value;

    /// The type of error that could be returned by an individual query.
    type QueryError = QueryError;

    /// The type of iterator we're returning.
    ///
    /// In general the concrete type will be impossible to name in advance,
    /// so the `backend::BoxDataResponseStream` type alias will be useful.
    type Stream = backend::BoxDataResponseStream<Self::QueryError>;

    /// Respond to a request for data from Grafana.
    ///
    /// This request will contain zero or more queries, as well as information
    /// about the datasource instance on behalf of which this request is made,
    /// such as address, credentials, etc.
    ///
    /// Our plugin must respond to each query and return an iterator of `DataResponse`s,
    /// which themselves can contain zero or more `Frame`s.
    async fn query_data(&self, request: backend::QueryDataRequest<Self::Query>) -> Self::Stream {
        Box::pin(
            request
                .queries
                .into_iter()
                .map(|x| async {
                    // Here we create a single response Frame for each query.
                    // Frames can be created from iterators of fields using [`IntoFrame`].
                    Ok(backend::DataResponse::new(
                        x.ref_id.clone(),
                        // Return zero or more frames.
                        // A real implementation would fetch this data from a database
                        // or something.
                        vec![[
                            [1_u32, 2, 3].into_field("x"),
                            ["a", "b", "c"].into_field("y"),
                        ]
                        .into_frame("foo")
                        .check()
                        .map_err(|source| QueryError {
                            ref_id: x.ref_id,
                        })?],
                    ))
                })
                .collect::<FuturesOrdered<_>>(),
        )
    }
}

#[grafana_plugin_sdk::main(
    services(data),
    shutdown_handler = "0.0.0.0:10001",
)]
async fn plugin() -> MyPlugin {
    MyPlugin
}

Implementations§

source§

impl Plugin<NoopService, NoopService, NoopService, NoopService>

source

pub fn new() -> Self

Create a new Plugin with no registered services.

source§

impl<D, Q, R, S> Plugin<D, Q, R, S>

source

pub fn shutdown_handler(self, address: SocketAddr) -> Self

Add a shutdown handler to the plugin, listening on the specified address.

The shutdown handler waits for a TCP connection on the specified address and requests that the server gracefully shutdown when any connection is made.

Note that this only takes effect in debug mode. The shutdown handler is never added when the crate is compiled in release mode.

source

pub fn init_subscriber(self, init_subscriber: bool) -> Self

Initialize a default tracing_subscriber::fmt::Subscriber upon starting.

If enabled, this will initialize a Subscriber emitting logs to stderr in a format compatible with Grafana, at a default max level of info.

This effectively causes the following to be called as part of Plugin::start:

use grafana_plugin_sdk::backend;
use tracing_subscriber::{prelude::*, EnvFilter};

let filter = EnvFilter::try_from_default_env()
    .unwrap_or_else(|_| EnvFilter::new("info"));
tracing_subscriber::registry()
    .with(filter)
    .with(backend::layer())
    .init()
source

pub fn data_service<T>(self, service: T) -> Plugin<D, T, R, S>
where T: DataService + Send + Sync + 'static,

Add a data service to this plugin.

source

pub fn diagnostics_service<T>(self, service: T) -> Plugin<T, Q, R, S>
where T: DiagnosticsService + Send + Sync + 'static,

Add a diagnostics service to this plugin.

source

pub fn resource_service<T>(self, service: T) -> Plugin<D, Q, T, S>
where T: ResourceService + Send + Sync + 'static,

Add a resource service to this plugin.

source

pub fn stream_service<T>(self, service: T) -> Plugin<D, Q, R, T>
where T: StreamService + Send + Sync + 'static,

Add a streaming service to this plugin.

source§

impl<D, Q, R, S> Plugin<D, Q, R, S>
where D: DiagnosticsService + Send + Sync + 'static, Q: DataService + Send + Sync + 'static, R: ResourceService + Send + Sync + 'static, S: StreamService + Send + Sync + 'static,

source

pub async fn start(self, listener: TcpListener) -> Result<(), Error>

Start the plugin.

This adds all of the configured services, spawns a shutdown handler (if configured), and blocks while the plugin runs.

Panics

This will panic if init_subscriber(true) has been set and another global subscriber has already been installed. If you are initializing your own Subscriber, you should instead use the layer function to add a Grafana-compatible tokio_subscriber::fmt::Layer to your subscriber.

Trait Implementations§

source§

impl Default for Plugin<NoopService, NoopService, NoopService, NoopService>

source§

fn default() -> Self

Returns the “default value” for a type. Read more

Auto Trait Implementations§

§

impl<D, Q, R, S> RefUnwindSafe for Plugin<D, Q, R, S>

§

impl<D, Q, R, S> Send for Plugin<D, Q, R, S>
where D: Send, Q: Send, R: Send, S: Send,

§

impl<D, Q, R, S> Sync for Plugin<D, Q, R, S>
where D: Sync, Q: Sync, R: Sync, S: Sync,

§

impl<D, Q, R, S> Unpin for Plugin<D, Q, R, S>
where D: Unpin, Q: Unpin, R: Unpin, S: Unpin,

§

impl<D, Q, R, S> UnwindSafe for Plugin<D, Q, R, S>

Blanket Implementations§

source§

impl<T> Any for T
where T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for T
where T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

source§

impl<T> Instrument for T

source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
source§

impl<T, U> Into<U> for T
where U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

source§

impl<T> IntoRequest<T> for T

source§

fn into_request(self) -> Request<T>

Wrap the input message T in a tonic::Request
source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

§

fn vzip(self) -> V

source§

impl<T> WithSubscriber for T

source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more
§

impl<T> Allocation for T
where T: RefUnwindSafe + Send + Sync,