Backend plugins: Implement support for resources (#21805)

Implements initial support for resources using v0.14.0 of SDK.

Ref #21512
This commit is contained in:
Marcus Efraimsson 2020-01-31 11:15:50 +01:00 committed by GitHub
parent 5345868148
commit 0390b5601e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
44 changed files with 957 additions and 2195 deletions

2
go.mod
View File

@ -32,7 +32,7 @@ require (
github.com/gorilla/websocket v1.4.1
github.com/gosimple/slug v1.4.2
github.com/grafana/grafana-plugin-model v0.0.0-20190930120109-1fc953a61fb4
github.com/grafana/grafana-plugin-sdk-go v0.11.0
github.com/grafana/grafana-plugin-sdk-go v0.14.0
github.com/hashicorp/go-hclog v0.8.0
github.com/hashicorp/go-plugin v1.0.1
github.com/hashicorp/go-version v1.1.0

8
go.sum
View File

@ -9,8 +9,6 @@ github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuy
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/apache/arrow/go/arrow v0.0.0-20190716210558-5f564424c71c h1:iHUHzx3S1TU5xt+D7vLb0PAk3e+RfayF9IhR6+hyO/k=
github.com/apache/arrow/go/arrow v0.0.0-20190716210558-5f564424c71c/go.mod h1:VTxUBvSJ3s3eHAg65PNgrsn5BtqCRPdmyXh6rAfdxN0=
github.com/apache/arrow/go/arrow v0.0.0-20191025121910-b789226ccb21 h1:xI+FQ/TsyD7jy22vP9wQBtJsPFRQTLZHfrr1IMLl0g0=
github.com/apache/arrow/go/arrow v0.0.0-20191025121910-b789226ccb21/go.mod h1:VTxUBvSJ3s3eHAg65PNgrsn5BtqCRPdmyXh6rAfdxN0=
github.com/aws/aws-sdk-go v1.25.48 h1:J82DYDGZHOKHdhx6hD24Tm30c2C3GchYGfN0mf9iKUk=
@ -127,10 +125,8 @@ github.com/gosimple/slug v1.4.2 h1:jDmprx3q/9Lfk4FkGZtvzDQ9Cj9eAmsjzeQGp24PeiQ=
github.com/gosimple/slug v1.4.2/go.mod h1:ER78kgg1Mv0NQGlXiDe57DpCyfbNywXXZ9mIorhxAf0=
github.com/grafana/grafana-plugin-model v0.0.0-20190930120109-1fc953a61fb4 h1:SPdxCL9BChFTlyi0Khv64vdCW4TMna8+sxL7+Chx+Ag=
github.com/grafana/grafana-plugin-model v0.0.0-20190930120109-1fc953a61fb4/go.mod h1:nc0XxBzjeGcrMltCDw269LoWF9S8ibhgxolCdA1R8To=
github.com/grafana/grafana-plugin-sdk-go v0.6.0 h1:rMMp84CdEKxI4i6SEpIUgsHgcKa1JdXDjLrDDw6QhXI=
github.com/grafana/grafana-plugin-sdk-go v0.6.0/go.mod h1:0eV4vNOYNZKOBbl23MieYiLa2y8M8S3D6ytLdggctqo=
github.com/grafana/grafana-plugin-sdk-go v0.11.0 h1:MkefJEWDMYk86eHUzCatTfK2/atAYNN1FncBqP5mx20=
github.com/grafana/grafana-plugin-sdk-go v0.11.0/go.mod h1:Dgc2ygLBJXTTIcfIgCGG6mxRcC/aZ36rKZ1tq5B5XmY=
github.com/grafana/grafana-plugin-sdk-go v0.14.0 h1:7Y34uhBkmkZ1ywYNfTXYXZihRgxgCf9WQbad4iqJ+t8=
github.com/grafana/grafana-plugin-sdk-go v0.14.0/go.mod h1:Dgc2ygLBJXTTIcfIgCGG6mxRcC/aZ36rKZ1tq5B5XmY=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/hashicorp/go-hclog v0.0.0-20180709165350-ff2cf002a8dd/go.mod h1:9bjs9uLqI8l75knNv3lV1kA55veR+WUPSiKIWcQHudI=

View File

@ -251,6 +251,9 @@ func (hs *HTTPServer) registerRoutes() {
apiRoute.Get("/plugins", Wrap(hs.GetPluginList))
apiRoute.Get("/plugins/:pluginId/settings", Wrap(GetPluginSettingByID))
apiRoute.Get("/plugins/:pluginId/markdown/:name", Wrap(GetPluginMarkdown))
apiRoute.Get("/plugins/:pluginId/health", Wrap(hs.CheckHealth))
apiRoute.Any("/plugins/:pluginId/resources", Wrap(hs.CallResource))
apiRoute.Any("/plugins/:pluginId/resources/*", Wrap(hs.CallResource))
apiRoute.Group("/plugins", func(pluginRoute routing.RouteRegister) {
pluginRoute.Get("/:pluginId/dashboards/", Wrap(GetPluginDashboards))
@ -260,6 +263,8 @@ func (hs *HTTPServer) registerRoutes() {
apiRoute.Get("/frontend/settings/", hs.GetFrontendSettings)
apiRoute.Any("/datasources/proxy/:id/*", reqSignedIn, hs.ProxyDataSourceRequest)
apiRoute.Any("/datasources/proxy/:id", reqSignedIn, hs.ProxyDataSourceRequest)
apiRoute.Any("/datasources/:id/resources", Wrap(hs.CallDatasourceResource))
apiRoute.Any("/datasources/:id/resources/*", Wrap(hs.CallDatasourceResource))
// Folders
apiRoute.Group("/folders", func(folderRoute routing.RouteRegister) {

View File

@ -7,6 +7,7 @@ import (
"github.com/grafana/grafana/pkg/bus"
m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/plugins/backendplugin"
"github.com/grafana/grafana/pkg/util"
)
@ -253,6 +254,60 @@ func GetDataSourceIdByName(c *m.ReqContext) Response {
return JSON(200, &dtos)
}
// /api/datasources/:id/resources/*
func (hs *HTTPServer) CallDatasourceResource(c *m.ReqContext) Response {
datasourceID := c.ParamsInt64(":id")
ds, err := hs.DatasourceCache.GetDatasource(datasourceID, c.SignedInUser, c.SkipCache)
if err != nil {
if err == m.ErrDataSourceAccessDenied {
return Error(403, "Access denied to datasource", err)
}
return Error(500, "Unable to load datasource meta data", err)
}
// find plugin
plugin, ok := plugins.DataSources[ds.Type]
if !ok {
return Error(500, "Unable to find datasource plugin", err)
}
body, err := c.Req.Body().Bytes()
if err != nil {
return Error(500, "Failed to read request body", err)
}
req := backendplugin.CallResourceRequest{
Config: backendplugin.PluginConfig{
OrgID: c.OrgId,
PluginID: plugin.Id,
Instance: &backendplugin.PluginInstance{
ID: ds.Id,
Name: ds.Name,
Type: ds.Type,
URL: ds.Url,
},
},
Path: c.Params("*"),
Method: c.Req.Method,
URL: c.Req.URL.String(),
Headers: c.Req.Header.Clone(),
Body: body,
}
resp, err := hs.BackendPluginManager.CallResource(c.Req.Context(), req)
if err != nil {
return Error(500, "Failed to call datasource resource", err)
}
if resp.Status >= 400 {
return Error(resp.Status, "", nil)
}
return &NormalResponse{
body: resp.Body,
status: resp.Status,
header: resp.Headers,
}
}
func convertModelToDtos(ds *m.DataSource) dtos.DataSource {
dto := dtos.DataSource{
Id: ds.Id,

View File

@ -10,6 +10,8 @@ import (
"path"
"sync"
"github.com/grafana/grafana/pkg/plugins/backendplugin"
"github.com/grafana/grafana/pkg/api/live"
"github.com/grafana/grafana/pkg/api/routing"
httpstatic "github.com/grafana/grafana/pkg/api/static"
@ -57,19 +59,20 @@ type HTTPServer struct {
streamManager *live.StreamManager
httpSrv *http.Server
RouteRegister routing.RouteRegister `inject:""`
Bus bus.Bus `inject:""`
RenderService rendering.Service `inject:""`
Cfg *setting.Cfg `inject:""`
HooksService *hooks.HooksService `inject:""`
CacheService *localcache.CacheService `inject:""`
DatasourceCache datasources.CacheService `inject:""`
AuthTokenService models.UserTokenService `inject:""`
QuotaService *quota.QuotaService `inject:""`
RemoteCacheService *remotecache.RemoteCache `inject:""`
ProvisioningService ProvisioningService `inject:""`
Login *login.LoginService `inject:""`
License models.Licensing `inject:""`
RouteRegister routing.RouteRegister `inject:""`
Bus bus.Bus `inject:""`
RenderService rendering.Service `inject:""`
Cfg *setting.Cfg `inject:""`
HooksService *hooks.HooksService `inject:""`
CacheService *localcache.CacheService `inject:""`
DatasourceCache datasources.CacheService `inject:""`
AuthTokenService models.UserTokenService `inject:""`
QuotaService *quota.QuotaService `inject:""`
RemoteCacheService *remotecache.RemoteCache `inject:""`
ProvisioningService ProvisioningService `inject:""`
Login *login.LoginService `inject:""`
License models.Licensing `inject:""`
BackendPluginManager backendplugin.Manager `inject:""`
}
func (hs *HTTPServer) Init() error {

View File

@ -3,6 +3,8 @@ package api
import (
"sort"
"github.com/grafana/grafana/pkg/plugins/backendplugin"
"github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/bus"
m "github.com/grafana/grafana/pkg/models"
@ -200,3 +202,74 @@ func ImportDashboard(c *m.ReqContext, apiCmd dtos.ImportDashboardCommand) Respon
return JSON(200, cmd.Result)
}
// /api/plugins/:pluginId/health
func (hs *HTTPServer) CheckHealth(c *m.ReqContext) Response {
pluginID := c.Params("pluginId")
resp, err := hs.BackendPluginManager.CheckHealth(c.Req.Context(), pluginID)
if err != nil {
if err == backendplugin.ErrPluginNotRegistered {
return Error(404, "Plugin not found", err)
}
// Return status unknown instead?
if err == backendplugin.ErrDiagnosticsNotSupported {
return Error(404, "Health check not implemented", err)
}
// Return status unknown or error instead?
if err == backendplugin.ErrHealthCheckFailed {
return Error(500, "Plugin health check failed", err)
}
}
payload := map[string]interface{}{
"status": resp.Status.String(),
"info": resp.Info,
}
if resp.Status != backendplugin.HealthStatusOk {
return JSON(503, payload)
}
return JSON(200, payload)
}
// /api/plugins/:pluginId/resources/*
func (hs *HTTPServer) CallResource(c *m.ReqContext) Response {
pluginID := c.Params("pluginId")
_, exists := plugins.Plugins[pluginID]
if !exists {
return Error(404, "Plugin not found, no installed plugin with that id", nil)
}
body, err := c.Req.Body().Bytes()
if err != nil {
return Error(500, "Failed to read request body", err)
}
req := backendplugin.CallResourceRequest{
Config: backendplugin.PluginConfig{
OrgID: c.OrgId,
PluginID: pluginID,
},
Path: c.Params("*"),
Method: c.Req.Method,
URL: c.Req.URL.String(),
Headers: c.Req.Header.Clone(),
Body: body,
}
resp, err := hs.BackendPluginManager.CallResource(c.Req.Context(), req)
if err != nil {
return Error(500, "Failed to call resource", err)
}
if resp.Status >= 400 {
return Error(resp.Status, "", nil)
}
return &NormalResponse{
body: resp.Body,
status: resp.Status,
header: resp.Headers,
}
}

View File

@ -5,6 +5,7 @@ import (
"context"
"errors"
"fmt"
"net/http"
"github.com/grafana/grafana-plugin-sdk-go/genproto/pluginv2"
"github.com/prometheus/client_golang/prometheus"
@ -14,7 +15,6 @@ import (
datasourceV1 "github.com/grafana/grafana-plugin-model/go/datasource"
rendererV1 "github.com/grafana/grafana-plugin-model/go/renderer"
backend "github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/plugins/backendplugin/collector"
"github.com/grafana/grafana/pkg/util/errutil"
@ -31,7 +31,8 @@ type BackendPlugin struct {
client *plugin.Client
logger log.Logger
startFns PluginStartFuncs
diagnostics backend.DiagnosticsPlugin
diagnostics DiagnosticsPlugin
core CorePlugin
}
func (p *BackendPlugin) start(ctx context.Context) error {
@ -61,20 +62,21 @@ func (p *BackendPlugin) start(ctx context.Context) error {
}
if rawDiagnostics != nil {
if plugin, ok := rawDiagnostics.(backend.DiagnosticsPlugin); ok {
if plugin, ok := rawDiagnostics.(DiagnosticsPlugin); ok {
p.diagnostics = plugin
}
}
client = &Client{}
if rawBackend != nil {
if plugin, ok := rawBackend.(backend.BackendPlugin); ok {
client.BackendPlugin = plugin
if plugin, ok := rawBackend.(CorePlugin); ok {
p.core = plugin
client.DatasourcePlugin = plugin
}
}
if rawTransform != nil {
if plugin, ok := rawTransform.(backend.TransformPlugin); ok {
if plugin, ok := rawTransform.(TransformPlugin); ok {
client.TransformPlugin = plugin
}
}
@ -194,6 +196,47 @@ func (p *BackendPlugin) checkHealth(ctx context.Context) (*pluginv2.CheckHealth_
return res, nil
}
func (p *BackendPlugin) callResource(ctx context.Context, req CallResourceRequest) (*CallResourceResult, error) {
p.logger.Debug("Calling resource", "path", req.Path, "method", req.Method)
reqHeaders := map[string]*pluginv2.CallResource_StringList{}
for k, v := range req.Headers {
reqHeaders[k] = &pluginv2.CallResource_StringList{Values: v}
}
protoReq := &pluginv2.CallResource_Request{
Config: &pluginv2.PluginConfig{},
Path: req.Path,
Method: req.Method,
Url: req.URL,
Headers: reqHeaders,
Body: req.Body,
}
protoResp, err := p.core.CallResource(ctx, protoReq)
if err != nil {
if st, ok := status.FromError(err); ok {
if st.Code() == codes.Unimplemented {
return &CallResourceResult{
Status: http.StatusNotImplemented,
}, nil
}
}
return nil, errutil.Wrap("Failed to call resource", err)
}
respHeaders := map[string][]string{}
for key, values := range protoResp.Headers {
respHeaders[key] = values.Values
}
return &CallResourceResult{
Headers: respHeaders,
Body: protoResp.Body,
Status: int(protoResp.Code),
}, nil
}
// convertMetricFamily converts metric family to prometheus.Metric.
// Copied from https://github.com/prometheus/node_exporter/blob/3ddc82c2d8d11eec53ed5faa8db969a1bb81f8bb/collector/textfile.go#L66-L165
func convertMetricFamily(pluginID string, metricFamily *dto.MetricFamily, ch chan<- prometheus.Metric, logger log.Logger) {

View File

@ -1,14 +1,17 @@
package backendplugin
import (
"context"
"os/exec"
"github.com/grafana/grafana-plugin-sdk-go/backend/plugin"
"github.com/grafana/grafana/pkg/infra/log"
datasourceV1 "github.com/grafana/grafana-plugin-model/go/datasource"
rendererV1 "github.com/grafana/grafana-plugin-model/go/renderer"
backend "github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/hashicorp/go-plugin"
"github.com/grafana/grafana-plugin-sdk-go/genproto/pluginv2"
goplugin "github.com/hashicorp/go-plugin"
)
const (
@ -19,24 +22,24 @@ const (
)
// Handshake is the HandshakeConfig used to configure clients and servers.
var handshake = plugin.HandshakeConfig{
var handshake = goplugin.HandshakeConfig{
// The ProtocolVersion is the version that must match between Grafana core
// and Grafana plugins. This should be bumped whenever a (breaking) change
// happens in one or the other that makes it so that they can't safely communicate.
ProtocolVersion: DefaultProtocolVersion,
// The magic cookie values should NEVER be changed.
MagicCookieKey: backend.MagicCookieKey,
MagicCookieValue: backend.MagicCookieValue,
MagicCookieKey: plugin.MagicCookieKey,
MagicCookieValue: plugin.MagicCookieValue,
}
func newClientConfig(executablePath string, logger log.Logger, versionedPlugins map[int]plugin.PluginSet) *plugin.ClientConfig {
return &plugin.ClientConfig{
func newClientConfig(executablePath string, logger log.Logger, versionedPlugins map[int]goplugin.PluginSet) *goplugin.ClientConfig {
return &goplugin.ClientConfig{
Cmd: exec.Command(executablePath),
HandshakeConfig: handshake,
VersionedPlugins: versionedPlugins,
Logger: logWrapper{Logger: logger},
AllowedProtocols: []plugin.Protocol{plugin.ProtocolGRPC},
AllowedProtocols: []goplugin.Protocol{goplugin.ProtocolGRPC},
}
}
@ -57,7 +60,7 @@ type PluginDescriptor struct {
pluginID string
executablePath string
managed bool
versionedPlugins map[int]plugin.PluginSet
versionedPlugins map[int]goplugin.PluginSet
startFns PluginStartFuncs
}
@ -68,14 +71,14 @@ func NewBackendPluginDescriptor(pluginID, executablePath string, startFns Plugin
pluginID: pluginID,
executablePath: executablePath,
managed: true,
versionedPlugins: map[int]plugin.PluginSet{
versionedPlugins: map[int]goplugin.PluginSet{
DefaultProtocolVersion: {
pluginID: &datasourceV1.DatasourcePluginImpl{},
},
backend.ProtocolVersion: {
"diagnostics": &backend.DiagnosticsGRPCPlugin{},
"backend": &backend.CoreGRPCPlugin{},
"transform": &backend.TransformGRPCPlugin{},
plugin.ProtocolVersion: {
"diagnostics": &plugin.DiagnosticsGRPCPlugin{},
"backend": &plugin.CoreGRPCPlugin{},
"transform": &plugin.TransformGRPCPlugin{},
},
},
startFns: startFns,
@ -89,7 +92,7 @@ func NewRendererPluginDescriptor(pluginID, executablePath string, startFns Plugi
pluginID: pluginID,
executablePath: executablePath,
managed: false,
versionedPlugins: map[int]plugin.PluginSet{
versionedPlugins: map[int]goplugin.PluginSet{
DefaultProtocolVersion: {
pluginID: &rendererV1.RendererPluginImpl{},
},
@ -98,6 +101,28 @@ func NewRendererPluginDescriptor(pluginID, executablePath string, startFns Plugi
}
}
type DiagnosticsPlugin interface {
CollectMetrics(ctx context.Context, req *pluginv2.CollectMetrics_Request) (*pluginv2.CollectMetrics_Response, error)
CheckHealth(ctx context.Context, req *pluginv2.CheckHealth_Request) (*pluginv2.CheckHealth_Response, error)
}
type DatasourcePlugin interface {
DataQuery(ctx context.Context, req *pluginv2.DataQueryRequest) (*pluginv2.DataQueryResponse, error)
}
type CorePlugin interface {
CallResource(ctx context.Context, req *pluginv2.CallResource_Request) (*pluginv2.CallResource_Response, error)
DatasourcePlugin
}
type TransformCallBack interface {
DataQuery(ctx context.Context, req *pluginv2.DataQueryRequest) (*pluginv2.DataQueryResponse, error)
}
type TransformPlugin interface {
DataQuery(ctx context.Context, req *pluginv2.DataQueryRequest, callback TransformCallBack) (*pluginv2.DataQueryResponse, error)
}
// LegacyClient client for communicating with a plugin using the old plugin protocol.
type LegacyClient struct {
DatasourcePlugin datasourceV1.DatasourcePlugin
@ -106,7 +131,6 @@ type LegacyClient struct {
// Client client for communicating with a plugin using the current plugin protocol.
type Client struct {
DiagnosticsPlugin backend.DiagnosticsPlugin
BackendPlugin backend.BackendPlugin
TransformPlugin backend.TransformPlugin
DatasourcePlugin DatasourcePlugin
TransformPlugin TransformPlugin
}

View File

@ -0,0 +1,85 @@
package backendplugin
import (
"encoding/json"
"strconv"
"github.com/grafana/grafana-plugin-sdk-go/genproto/pluginv2"
)
// HealthStatus is the status of the plugin.
type HealthStatus int
const (
// HealthStatusUnknown means the status of the plugin is unknown.
HealthStatusUnknown HealthStatus = iota
// HealthStatusOk means the status of the plugin is good.
HealthStatusOk
// HealthStatusError means the plugin is in an error state.
HealthStatusError
)
var healthStatusNames = map[int]string{
0: "UNKNOWN",
1: "OK",
2: "ERROR",
}
func (hs HealthStatus) String() string {
s, exists := healthStatusNames[int(hs)]
if exists {
return s
}
return strconv.Itoa(int(hs))
}
// CheckHealthResult check health result.
type CheckHealthResult struct {
Status HealthStatus
Info string
}
func checkHealthResultFromProto(protoResp *pluginv2.CheckHealth_Response) *CheckHealthResult {
status := HealthStatusUnknown
switch protoResp.Status {
case pluginv2.CheckHealth_Response_ERROR:
status = HealthStatusError
case pluginv2.CheckHealth_Response_OK:
status = HealthStatusOk
}
return &CheckHealthResult{
Status: status,
Info: protoResp.Info,
}
}
type PluginInstance struct {
ID int64
Name string
Type string
URL string
JSONData json.RawMessage
}
type PluginConfig struct {
PluginID string
OrgID int64
Instance *PluginInstance
}
type CallResourceRequest struct {
Config PluginConfig
Path string
Method string
URL string
Headers map[string][]string
Body []byte
}
// CallResourceResult call resource result.
type CallResourceResult struct {
Status int
Headers map[string][]string
Body []byte
}

View File

@ -6,19 +6,24 @@ import (
"sync"
"time"
"github.com/grafana/grafana-plugin-sdk-go/genproto/pluginv2"
"github.com/prometheus/client_golang/prometheus"
"github.com/grafana/grafana/pkg/api/routing"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/plugins/backendplugin/collector"
"github.com/grafana/grafana/pkg/registry"
plugin "github.com/hashicorp/go-plugin"
"golang.org/x/xerrors"
)
var (
// ErrPluginNotRegistered error returned when plugin not registered.
ErrPluginNotRegistered = errors.New("Plugin not registered")
// ErrDiagnosticsNotSupported error returned when plugin doesn't support diagnostics.
ErrDiagnosticsNotSupported = errors.New("Plugin diagnostics not supported")
// ErrHealthCheckFailed error returned when health check failed.
ErrHealthCheckFailed = errors.New("Health check failed")
)
func init() {
registry.Register(&registry.Descriptor{
Name: "BackendPluginManager",
@ -33,10 +38,13 @@ type Manager interface {
Register(descriptor PluginDescriptor) error
// StartPlugin starts a non-managed backend plugin
StartPlugin(ctx context.Context, pluginID string) error
// CheckHealth checks the health of a registered backend plugin.
CheckHealth(ctx context.Context, pluginID string) (*CheckHealthResult, error)
// CallResource calls a plugin resource.
CallResource(ctx context.Context, req CallResourceRequest) (*CallResourceResult, error)
}
type manager struct {
RouteRegister routing.RouteRegister `inject:""`
pluginsMu sync.RWMutex
plugins map[string]*BackendPlugin
pluginCollector collector.PluginCollector
@ -49,8 +57,6 @@ func (m *manager) Init() error {
m.pluginCollector = collector.NewPluginCollector()
prometheus.MustRegister(m.pluginCollector)
m.RouteRegister.Get("/api/plugins/:pluginId/health", m.checkHealth)
return nil
}
@ -140,38 +146,50 @@ func (m *manager) stop() {
}
}
// checkHealth http handler for checking plugin health.
func (m *manager) checkHealth(c *models.ReqContext) {
pluginID := c.Params("pluginId")
// CheckHealth checks the health of a registered backend plugin.
func (m *manager) CheckHealth(ctx context.Context, pluginID string) (*CheckHealthResult, error) {
m.pluginsMu.RLock()
p, registered := m.plugins[pluginID]
m.pluginsMu.RUnlock()
if !registered || !p.supportsDiagnostics() {
c.JsonApiErr(404, "Plugin not found", nil)
return
if !registered {
return nil, ErrPluginNotRegistered
}
res, err := p.checkHealth(c.Req.Context())
if !p.supportsDiagnostics() {
return nil, ErrDiagnosticsNotSupported
}
res, err := p.checkHealth(ctx)
if err != nil {
p.logger.Error("Failed to check plugin health", "error", err)
c.JSON(503, map[string]interface{}{
"status": pluginv2.CheckHealth_Response_ERROR.String(),
})
return
return nil, ErrHealthCheckFailed
}
payload := map[string]interface{}{
"status": res.Status.String(),
"info": res.Info,
return checkHealthResultFromProto(res), nil
}
// CallResource calls a plugin resource.
func (m *manager) CallResource(ctx context.Context, req CallResourceRequest) (*CallResourceResult, error) {
m.pluginsMu.RLock()
p, registered := m.plugins[req.Config.PluginID]
m.pluginsMu.RUnlock()
if !registered {
return nil, ErrPluginNotRegistered
}
if res.Status != pluginv2.CheckHealth_Response_OK {
c.JSON(503, payload)
return
res, err := p.callResource(ctx, req)
if err != nil {
return nil, err
}
c.JSON(200, payload)
// Make sure a content type always is returned in response
if _, exists := res.Headers["Content-Type"]; !exists {
res.Headers["Content-Type"] = []string{"application/json"}
}
return res, nil
}
func startPluginAndRestartKilledProcesses(ctx context.Context, p *BackendPlugin) error {

View File

@ -0,0 +1,137 @@
package resource
import (
"bytes"
"net/http"
"github.com/grafana/grafana-plugin-sdk-go/genproto/pluginv2"
)
// ResourceResponseWriter is an implementation of http.ResponseWriter that
// records its mutations for later inspection in tests.
type ResourceResponseWriter struct {
// Code is the HTTP response code set by WriteHeader.
//
// Note that if a Handler never calls WriteHeader or Write,
// this might end up being 0, rather than the implicit
// http.StatusOK. To get the implicit value, use the Result
// method.
Code int
// HeaderMap contains the headers explicitly set by the Handler.
// It is an internal detail.
//
// Deprecated: HeaderMap exists for historical compatibility
// and should not be used. To access the headers returned by a handler,
// use the Response.Header map as returned by the Result method.
HeaderMap http.Header
// Body is the buffer to which the Handler's Write calls are sent.
// If nil, the Writes are silently discarded.
Body *bytes.Buffer
// Flushed is whether the Handler called Flush.
Flushed bool
wroteHeader bool
}
// NewResourceResponseWriter returns an initialized ResponseWriter.
func NewResourceResponseWriter() *ResourceResponseWriter {
return &ResourceResponseWriter{
HeaderMap: make(http.Header),
Body: new(bytes.Buffer),
Code: 200,
}
}
// Header implements http.ResponseWriter. It returns the response
// headers to mutate within a handler. To test the headers that were
// written after a handler completes, use the Result method and see
// the returned Response value's Header.
func (rw *ResourceResponseWriter) Header() http.Header {
m := rw.HeaderMap
if m == nil {
m = make(http.Header)
rw.HeaderMap = m
}
return m
}
// writeHeader writes a header if it was not written yet and
// detects Content-Type if needed.
//
// bytes or str are the beginning of the response body.
// We pass both to avoid unnecessarily generate garbage
// in rw.WriteString which was created for performance reasons.
// Non-nil bytes win.
func (rw *ResourceResponseWriter) writeHeader(b []byte, str string) {
if rw.wroteHeader {
return
}
if len(str) > 512 {
str = str[:512]
}
m := rw.Header()
_, hasType := m["Content-Type"]
hasTE := m.Get("Transfer-Encoding") != ""
if !hasType && !hasTE {
if b == nil {
b = []byte(str)
}
m.Set("Content-Type", http.DetectContentType(b))
}
rw.WriteHeader(200)
}
// Write implements http.ResponseWriter. The data in buf is written to
// rw.Body, if not nil.
func (rw *ResourceResponseWriter) Write(buf []byte) (int, error) {
rw.writeHeader(buf, "")
if rw.Body != nil {
rw.Body.Write(buf)
}
return len(buf), nil
}
// WriteHeader implements http.ResponseWriter.
func (rw *ResourceResponseWriter) WriteHeader(code int) {
if rw.wroteHeader {
return
}
rw.Code = code
rw.wroteHeader = true
if rw.HeaderMap == nil {
rw.HeaderMap = make(http.Header)
}
}
// Flush implements http.Flusher.
func (rw *ResourceResponseWriter) Flush() {
if !rw.wroteHeader {
rw.WriteHeader(200)
}
}
// Result returns the response generated by the handler.
func (rw *ResourceResponseWriter) Result() *pluginv2.CallResource_Response {
res := &pluginv2.CallResource_Response{
Code: int32(rw.Code),
Headers: map[string]*pluginv2.CallResource_StringList{},
}
if rw.Body != nil {
res.Body = rw.Body.Bytes()
}
for key, values := range rw.Header().Clone() {
res.Headers[key] = &pluginv2.CallResource_StringList{
Values: values,
}
}
return res
}

View File

@ -3,7 +3,8 @@ package wrapper
import (
"context"
sdk "github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana/pkg/plugins/backendplugin"
"github.com/grafana/grafana-plugin-sdk-go/genproto/pluginv2"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/infra/log"
@ -11,12 +12,12 @@ import (
"github.com/grafana/grafana/pkg/tsdb"
)
func NewDatasourcePluginWrapperV2(log log.Logger, plugin sdk.BackendPlugin) *DatasourcePluginWrapperV2 {
return &DatasourcePluginWrapperV2{BackendPlugin: plugin, logger: log}
func NewDatasourcePluginWrapperV2(log log.Logger, plugin backendplugin.DatasourcePlugin) *DatasourcePluginWrapperV2 {
return &DatasourcePluginWrapperV2{DatasourcePlugin: plugin, logger: log}
}
type DatasourcePluginWrapperV2 struct {
sdk.BackendPlugin
backendplugin.DatasourcePlugin
logger log.Logger
}
@ -56,7 +57,7 @@ func (tw *DatasourcePluginWrapperV2) Query(ctx context.Context, ds *models.DataS
})
}
pbRes, err := tw.BackendPlugin.DataQuery(ctx, pbQuery)
pbRes, err := tw.DatasourcePlugin.DataQuery(ctx, pbQuery)
if err != nil {
return nil, err
}

View File

@ -68,9 +68,9 @@ func (p *DataSourcePlugin) onLegacyPluginStart(pluginID string, client *backendp
}
func (p *DataSourcePlugin) onPluginStart(pluginID string, client *backendplugin.Client, logger log.Logger) error {
if client.BackendPlugin != nil {
if client.DatasourcePlugin != nil {
tsdb.RegisterTsdbQueryEndpoint(pluginID, func(dsInfo *models.DataSource) (tsdb.TsdbQueryEndpoint, error) {
return wrapper.NewDatasourcePluginWrapperV2(logger, client.BackendPlugin), nil
return wrapper.NewDatasourcePluginWrapperV2(logger, client.DatasourcePlugin), nil
})
}

View File

@ -7,7 +7,6 @@ import (
"path"
"strconv"
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana-plugin-sdk-go/dataframe"
"github.com/grafana/grafana-plugin-sdk-go/genproto/pluginv2"
"github.com/grafana/grafana/pkg/bus"
@ -54,9 +53,9 @@ func (p *TransformPlugin) Load(decoder *json.Decoder, pluginDir string, backendP
func (p *TransformPlugin) onPluginStart(pluginID string, client *backendplugin.Client, logger log.Logger) error {
p.TransformWrapper = NewTransformWrapper(logger, client.TransformPlugin)
if client.BackendPlugin != nil {
if client.DatasourcePlugin != nil {
tsdb.RegisterTsdbQueryEndpoint(pluginID, func(dsInfo *models.DataSource) (tsdb.TsdbQueryEndpoint, error) {
return wrapper.NewDatasourcePluginWrapperV2(logger, client.BackendPlugin), nil
return wrapper.NewDatasourcePluginWrapperV2(logger, client.DatasourcePlugin), nil
})
}
@ -67,12 +66,12 @@ func (p *TransformPlugin) onPluginStart(pluginID string, client *backendplugin.C
// Wrapper Code
// ...
func NewTransformWrapper(log log.Logger, plugin backend.TransformPlugin) *TransformWrapper {
func NewTransformWrapper(log log.Logger, plugin backendplugin.TransformPlugin) *TransformWrapper {
return &TransformWrapper{plugin, log, &transformCallback{log}}
}
type TransformWrapper struct {
backend.TransformPlugin
backendplugin.TransformPlugin
logger log.Logger
callback *transformCallback
}

View File

@ -1,215 +0,0 @@
package backend
import (
"context"
"encoding/json"
"time"
"github.com/grafana/grafana-plugin-sdk-go/dataframe"
"github.com/grafana/grafana-plugin-sdk-go/genproto/pluginv2"
)
// PluginConfig holds configuration for the queried plugin.
type PluginConfig struct {
ID int64
OrgID int64
Name string
Type string
URL string
JSONData json.RawMessage
}
// PluginConfigFromProto converts the generated protobuf PluginConfig to this
// package's PluginConfig.
func pluginConfigFromProto(pc *pluginv2.PluginConfig) PluginConfig {
return PluginConfig{
ID: pc.Id,
OrgID: pc.OrgId,
Name: pc.Name,
Type: pc.Type,
URL: pc.Url,
JSONData: json.RawMessage([]byte(pc.JsonData)),
}
}
func (pc PluginConfig) toProtobuf() *pluginv2.PluginConfig {
return &pluginv2.PluginConfig{
Id: pc.ID,
OrgId: pc.OrgID,
Name: pc.Name,
Type: pc.Type,
Url: pc.URL,
JsonData: string(pc.JSONData),
}
}
type DataQueryRequest struct {
PluginConfig PluginConfig
Headers map[string]string
Queries []DataQuery
}
func dataQueryRequestFromProto(pc *pluginv2.DataQueryRequest) *DataQueryRequest {
queries := make([]DataQuery, len(pc.Queries))
for i, q := range pc.Queries {
queries[i] = *dataQueryFromProtobuf(q)
}
return &DataQueryRequest{
PluginConfig: pluginConfigFromProto(pc.Config),
Headers: pc.Headers,
Queries: queries,
}
}
func (dr *DataQueryRequest) toProtobuf() *pluginv2.DataQueryRequest {
queries := make([]*pluginv2.DataQuery, len(dr.Queries))
for i, q := range dr.Queries {
queries[i] = q.toProtobuf()
}
return &pluginv2.DataQueryRequest{
Config: dr.PluginConfig.toProtobuf(),
Headers: dr.Headers,
Queries: queries,
}
}
// DataQuery represents the query as sent from the frontend.
type DataQuery struct {
RefID string
MaxDataPoints int64
Interval time.Duration
TimeRange TimeRange
JSON json.RawMessage
}
func (q *DataQuery) toProtobuf() *pluginv2.DataQuery {
return &pluginv2.DataQuery{
RefId: q.RefID,
MaxDataPoints: q.MaxDataPoints,
IntervalMS: q.Interval.Microseconds(),
TimeRange: q.TimeRange.toProtobuf(),
Json: q.JSON,
}
}
func dataQueryFromProtobuf(q *pluginv2.DataQuery) *DataQuery {
return &DataQuery{
RefID: q.RefId,
MaxDataPoints: q.MaxDataPoints,
TimeRange: timeRangeFromProtobuf(q.TimeRange),
Interval: time.Duration(q.IntervalMS) * time.Millisecond,
JSON: []byte(q.Json),
}
}
// DataQueryResponse holds the results for a given query.
type DataQueryResponse struct {
Frames []*dataframe.Frame
Metadata map[string]string
}
func (res *DataQueryResponse) toProtobuf() (*pluginv2.DataQueryResponse, error) {
encodedFrames := make([][]byte, len(res.Frames))
var err error
for i, frame := range res.Frames {
encodedFrames[i], err = dataframe.MarshalArrow(frame)
if err != nil {
return nil, err
}
}
return &pluginv2.DataQueryResponse{
Frames: encodedFrames,
Metadata: res.Metadata,
}, nil
}
func dataQueryResponseFromProtobuf(res *pluginv2.DataQueryResponse) (*DataQueryResponse, error) {
frames := make([]*dataframe.Frame, len(res.Frames))
var err error
for i, encodedFrame := range res.Frames {
frames[i], err = dataframe.UnmarshalArrow(encodedFrame)
if err != nil {
return nil, err
}
}
return &DataQueryResponse{Metadata: res.Metadata, Frames: frames}, nil
}
// TimeRange represents a time range for a query.
type TimeRange struct {
From time.Time
To time.Time
}
func (tr *TimeRange) toProtobuf() *pluginv2.TimeRange {
return &pluginv2.TimeRange{
FromEpochMS: tr.From.UnixNano() / int64(time.Millisecond),
ToEpochMS: tr.To.UnixNano() / int64(time.Millisecond),
}
}
// TimeRangeFromProtobuf converts the generated protobuf TimeRange to this
// package's FetchInfo.
func timeRangeFromProtobuf(tr *pluginv2.TimeRange) TimeRange {
return TimeRange{
From: time.Unix(0, tr.FromEpochMS*int64(time.Millisecond)),
To: time.Unix(0, tr.ToEpochMS*int64(time.Millisecond)),
}
}
type ResourceRequest struct {
PluginConfig PluginConfig
Headers map[string]string
Method string
Path string
Body []byte
}
func resourceRequestFromProtobuf(req *pluginv2.ResourceRequest) *ResourceRequest {
return &ResourceRequest{
PluginConfig: pluginConfigFromProto(req.Config),
Headers: req.Headers,
Method: req.Method,
Path: req.Path,
Body: req.Body,
}
}
type ResourceResponse struct {
Headers map[string]string
Code int32
Body []byte
}
func (rr *ResourceResponse) toProtobuf() *pluginv2.ResourceResponse {
return &pluginv2.ResourceResponse{
Headers: rr.Headers,
Code: rr.Code,
Body: rr.Body,
}
}
// DataQueryHandler handles data source queries.
type DataQueryHandler interface {
DataQuery(ctx context.Context, req *DataQueryRequest) (*DataQueryResponse, error)
}
// ResourceHandler handles backend plugin checks.
type ResourceHandler interface {
Resource(ctx context.Context, req *ResourceRequest) (*ResourceResponse, error)
}
// PluginHandlers is the collection of handlers that corresponds to the
// grpc "service BackendPlugin".
type PluginHandlers interface {
DataQueryHandler
ResourceHandler
}
// BackendPlugin is the Grafana backend plugin interface.
type BackendPlugin interface {
DataQuery(ctx context.Context, req *pluginv2.DataQueryRequest) (*pluginv2.DataQueryResponse, error)
Resource(ctx context.Context, req *pluginv2.ResourceRequest) (*pluginv2.ResourceResponse, error)
}

View File

@ -1,53 +0,0 @@
package backend
import (
"context"
"github.com/grafana/grafana-plugin-sdk-go/genproto/pluginv2"
)
// DiagnosticsPlugin is the Grafana diagnostics plugin interface.
type DiagnosticsPlugin interface {
CollectMetrics(ctx context.Context, req *pluginv2.CollectMetrics_Request) (*pluginv2.CollectMetrics_Response, error)
CheckHealth(ctx context.Context, req *pluginv2.CheckHealth_Request) (*pluginv2.CheckHealth_Response, error)
}
type CheckHealthHandler interface {
CheckHealth(ctx context.Context) (*CheckHealthResult, error)
}
// HealthStatus is the status of the plugin.
type HealthStatus int
const (
// HealthStatusUnknown means the status of the plugin is unknown.
HealthStatusUnknown HealthStatus = iota
// HealthStatusOk means the status of the plugin is good.
HealthStatusOk
// HealthStatusError means the plugin is in an error state.
HealthStatusError
)
func (ps HealthStatus) toProtobuf() pluginv2.CheckHealth_Response_HealthStatus {
switch ps {
case HealthStatusUnknown:
return pluginv2.CheckHealth_Response_UNKNOWN
case HealthStatusOk:
return pluginv2.CheckHealth_Response_OK
case HealthStatusError:
return pluginv2.CheckHealth_Response_ERROR
}
panic("unsupported protobuf health status type in sdk")
}
type CheckHealthResult struct {
Status HealthStatus
Info string
}
func (res *CheckHealthResult) toProtobuf() *pluginv2.CheckHealth_Response {
return &pluginv2.CheckHealth_Response{
Status: res.Status.toProtobuf(),
Info: res.Info,
}
}

View File

@ -1,15 +0,0 @@
package backend
import plugin "github.com/hashicorp/go-plugin"
const (
MagicCookieKey = "grafana_plugin_type"
MagicCookieValue = "datasource"
ProtocolVersion = 2
)
var Handshake = plugin.HandshakeConfig{
ProtocolVersion: ProtocolVersion,
MagicCookieKey: MagicCookieKey,
MagicCookieValue: MagicCookieValue,
}

View File

@ -1,4 +1,4 @@
package backend
package plugin
import (
"context"
@ -8,16 +8,20 @@ import (
"google.golang.org/grpc"
)
type CoreServer interface {
pluginv2.CoreServer
}
// CoreGRPCPlugin implements the GRPCPlugin interface from github.com/hashicorp/go-plugin.
type CoreGRPCPlugin struct {
plugin.NetRPCUnsupportedPlugin
plugin.GRPCPlugin
server pluginv2.CoreServer
CoreServer CoreServer
}
func (p *CoreGRPCPlugin) GRPCServer(broker *plugin.GRPCBroker, s *grpc.Server) error {
pluginv2.RegisterCoreServer(s, &coreGRPCServer{
server: p.server,
server: p.CoreServer,
})
return nil
}
@ -27,15 +31,15 @@ func (p *CoreGRPCPlugin) GRPCClient(ctx context.Context, broker *plugin.GRPCBrok
}
type coreGRPCServer struct {
server pluginv2.CoreServer
server CoreServer
}
func (s *coreGRPCServer) DataQuery(ctx context.Context, req *pluginv2.DataQueryRequest) (*pluginv2.DataQueryResponse, error) {
return s.server.DataQuery(ctx, req)
}
func (s *coreGRPCServer) Resource(ctx context.Context, req *pluginv2.ResourceRequest) (*pluginv2.ResourceResponse, error) {
return s.server.Resource(ctx, req)
func (s *coreGRPCServer) CallResource(ctx context.Context, req *pluginv2.CallResource_Request) (*pluginv2.CallResource_Response, error) {
return s.server.CallResource(ctx, req)
}
type coreGRPCClient struct {
@ -46,6 +50,9 @@ func (m *coreGRPCClient) DataQuery(ctx context.Context, req *pluginv2.DataQueryR
return m.client.DataQuery(ctx, req)
}
func (m *coreGRPCClient) Resource(ctx context.Context, req *pluginv2.ResourceRequest) (*pluginv2.ResourceResponse, error) {
return m.client.Resource(ctx, req)
func (m *coreGRPCClient) CallResource(ctx context.Context, req *pluginv2.CallResource_Request) (*pluginv2.CallResource_Response, error) {
return m.client.CallResource(ctx, req)
}
var _ CoreServer = &coreGRPCServer{}
var _ CoreServer = &coreGRPCClient{}

View File

@ -1,4 +1,4 @@
package backend
package plugin
import (
"context"
@ -8,16 +8,20 @@ import (
"google.golang.org/grpc"
)
type DiagnosticsServer interface {
pluginv2.DiagnosticsServer
}
// DiagnosticsGRPCPlugin implements the GRPCPlugin interface from github.com/hashicorp/go-plugin.
type DiagnosticsGRPCPlugin struct {
plugin.NetRPCUnsupportedPlugin
plugin.GRPCPlugin
server pluginv2.DiagnosticsServer
DiagnosticsServer DiagnosticsServer
}
func (p *DiagnosticsGRPCPlugin) GRPCServer(broker *plugin.GRPCBroker, s *grpc.Server) error {
pluginv2.RegisterDiagnosticsServer(s, &diagnosticsGRPCServer{
server: p.server,
server: p.DiagnosticsServer,
})
return nil
}
@ -27,7 +31,7 @@ func (p *DiagnosticsGRPCPlugin) GRPCClient(ctx context.Context, broker *plugin.G
}
type diagnosticsGRPCServer struct {
server pluginv2.DiagnosticsServer
server DiagnosticsServer
}
func (s *diagnosticsGRPCServer) CollectMetrics(ctx context.Context, req *pluginv2.CollectMetrics_Request) (*pluginv2.CollectMetrics_Response, error) {
@ -49,3 +53,6 @@ func (s *diagnosticsGRPCClient) CollectMetrics(ctx context.Context, req *pluginv
func (s *diagnosticsGRPCClient) CheckHealth(ctx context.Context, req *pluginv2.CheckHealth_Request) (*pluginv2.CheckHealth_Response, error) {
return s.client.CheckHealth(ctx, req)
}
var _ DiagnosticsServer = &diagnosticsGRPCServer{}
var _ DiagnosticsServer = &diagnosticsGRPCClient{}

View File

@ -1,4 +1,4 @@
package backend
package plugin
import (
"context"
@ -11,17 +11,29 @@ import (
"google.golang.org/grpc/metadata"
)
type TransformServer interface {
TransformData(ctx context.Context, req *pluginv2.DataQueryRequest, callback TransformCallBack) (*pluginv2.DataQueryResponse, error)
}
type transformClient interface {
DataQuery(ctx context.Context, req *pluginv2.DataQueryRequest, callback TransformCallBack) (*pluginv2.DataQueryResponse, error)
}
type TransformCallBack interface {
DataQuery(ctx context.Context, req *pluginv2.DataQueryRequest) (*pluginv2.DataQueryResponse, error)
}
// TransformGRPCPlugin implements the GRPCPlugin interface from github.com/hashicorp/go-plugin.
type TransformGRPCPlugin struct {
plugin.NetRPCUnsupportedPlugin
plugin.GRPCPlugin
adapter *sdkAdapter
TransformServer TransformServer
}
func (p *TransformGRPCPlugin) GRPCServer(broker *plugin.GRPCBroker, s *grpc.Server) error {
pluginv2.RegisterTransformServer(s, &transformGRPCServer{
adapter: p.adapter,
broker: broker,
server: p.TransformServer,
broker: broker,
})
return nil
}
@ -31,8 +43,8 @@ func (p *TransformGRPCPlugin) GRPCClient(ctx context.Context, broker *plugin.GRP
}
type transformGRPCServer struct {
broker *plugin.GRPCBroker
adapter *sdkAdapter
broker *plugin.GRPCBroker
server TransformServer
}
func (t *transformGRPCServer) DataQuery(ctx context.Context, req *pluginv2.DataQueryRequest) (*pluginv2.DataQueryResponse, error) {
@ -53,8 +65,8 @@ func (t *transformGRPCServer) DataQuery(ctx context.Context, req *pluginv2.DataQ
return nil, err
}
defer conn.Close()
api := &TransformCallBackGrpcClient{pluginv2.NewTransformCallBackClient(conn)}
return t.adapter.TransformData(ctx, req, api)
api := &transformCallBackGrpcClient{pluginv2.NewTransformCallBackClient(conn)}
return t.server.TransformData(ctx, req, api)
}
type transformGRPCClient struct {
@ -63,7 +75,7 @@ type transformGRPCClient struct {
}
func (t *transformGRPCClient) DataQuery(ctx context.Context, req *pluginv2.DataQueryRequest, callBack TransformCallBack) (*pluginv2.DataQueryResponse, error) {
callBackServer := &TransformCallBackGrpcServer{Impl: callBack}
callBackServer := &transformCallBackGrpcServer{Impl: callBack}
var s *grpc.Server
serverFunc := func(opts []grpc.ServerOption) *grpc.Server {
@ -83,18 +95,23 @@ func (t *transformGRPCClient) DataQuery(ctx context.Context, req *pluginv2.DataQ
// Callback
type TransformCallBackGrpcClient struct {
client pluginv2.TransformCallBackClient
}
func (t *TransformCallBackGrpcClient) DataQuery(ctx context.Context, req *pluginv2.DataQueryRequest) (*pluginv2.DataQueryResponse, error) {
return t.client.DataQuery(ctx, req)
}
type TransformCallBackGrpcServer struct {
type transformCallBackGrpcServer struct {
Impl TransformCallBack
}
func (g *TransformCallBackGrpcServer) DataQuery(ctx context.Context, req *pluginv2.DataQueryRequest) (*pluginv2.DataQueryResponse, error) {
func (g *transformCallBackGrpcServer) DataQuery(ctx context.Context, req *pluginv2.DataQueryRequest) (*pluginv2.DataQueryResponse, error) {
return g.Impl.DataQuery(ctx, req)
}
type transformCallBackGrpcClient struct {
client pluginv2.TransformCallBackClient
}
func (t *transformCallBackGrpcClient) DataQuery(ctx context.Context, req *pluginv2.DataQueryRequest) (*pluginv2.DataQueryResponse, error) {
return t.client.DataQuery(ctx, req)
}
var _ pluginv2.TransformServer = &transformGRPCServer{}
var _ transformClient = &transformGRPCClient{}
var _ pluginv2.TransformServer = &transformCallBackGrpcServer{}
var _ pluginv2.TransformServer = &transformCallBackGrpcClient{}

View File

@ -0,0 +1,27 @@
package plugin
import (
plugin "github.com/hashicorp/go-plugin"
)
const (
// ProtocolVersion is the current (latest) supported protocol version.
ProtocolVersion = 2
// MagicCookieKey is the the magic cookie key that will be used for negotiating
// between plugin host and client.
// Should NEVER be changed.
MagicCookieKey = "grafana_plugin_type"
// MagicCookieValue is the the magic cookie value that will be used for negotiating
// between plugin host and client.
// Should NEVER be changed.
MagicCookieValue = "datasource"
)
// handshake is the HandshakeConfig used to configure clients and servers.
var handshake = plugin.HandshakeConfig{
ProtocolVersion: ProtocolVersion,
MagicCookieKey: MagicCookieKey,
MagicCookieValue: MagicCookieValue,
}

View File

@ -0,0 +1,55 @@
package plugin
import (
plugin "github.com/hashicorp/go-plugin"
"google.golang.org/grpc"
)
//ServeOpts options for serving plugins.
type ServeOpts struct {
DiagnosticsServer DiagnosticsServer
CoreServer CoreServer
TransformServer TransformServer
// GRPCServer factory method for creating GRPC server.
// If nil, the default one will be used.
GRPCServer func(options []grpc.ServerOption) *grpc.Server
}
// Serve starts serving the plugin over gRPC.
func Serve(opts ServeOpts) error {
versionedPlugins := make(map[int]plugin.PluginSet)
pSet := make(plugin.PluginSet)
if opts.DiagnosticsServer != nil {
pSet["diagnostics"] = &DiagnosticsGRPCPlugin{
DiagnosticsServer: opts.DiagnosticsServer,
}
}
if opts.CoreServer != nil {
pSet["backend"] = &CoreGRPCPlugin{
CoreServer: opts.CoreServer,
}
}
if opts.TransformServer != nil {
pSet["transform"] = &TransformGRPCPlugin{
TransformServer: opts.TransformServer,
}
}
versionedPlugins[ProtocolVersion] = pSet
if opts.GRPCServer == nil {
opts.GRPCServer = plugin.DefaultGRPCServer
}
plugin.Serve(&plugin.ServeConfig{
HandshakeConfig: handshake,
VersionedPlugins: versionedPlugins,
GRPCServer: opts.GRPCServer,
})
return nil
}

View File

@ -1,91 +0,0 @@
package backend
import (
"bytes"
"context"
"github.com/grafana/grafana-plugin-sdk-go/dataframe"
"github.com/grafana/grafana-plugin-sdk-go/genproto/pluginv2"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/common/expfmt"
)
// sdkAdapter adapter between protobuf and SDK interfaces.
type sdkAdapter struct {
checkHealthHandler CheckHealthHandler
dataQueryHandler DataQueryHandler
resourceHandler ResourceHandler
transformDataHandler TransformDataHandler
}
func (a *sdkAdapter) CollectMetrics(ctx context.Context, protoReq *pluginv2.CollectMetrics_Request) (*pluginv2.CollectMetrics_Response, error) {
mfs, err := prometheus.DefaultGatherer.Gather()
if err != nil {
return nil, err
}
var buf bytes.Buffer
for _, mf := range mfs {
_, err := expfmt.MetricFamilyToText(&buf, mf)
if err != nil {
return nil, err
}
}
return &pluginv2.CollectMetrics_Response{
Metrics: &pluginv2.CollectMetrics_Payload{
Prometheus: buf.Bytes(),
},
}, nil
}
func (a *sdkAdapter) CheckHealth(ctx context.Context, protoReq *pluginv2.CheckHealth_Request) (*pluginv2.CheckHealth_Response, error) {
if a.checkHealthHandler != nil {
res, err := a.checkHealthHandler.CheckHealth(ctx)
if err != nil {
return nil, err
}
return res.toProtobuf(), nil
}
return &pluginv2.CheckHealth_Response{
Status: pluginv2.CheckHealth_Response_OK,
}, nil
}
func (a *sdkAdapter) DataQuery(ctx context.Context, req *pluginv2.DataQueryRequest) (*pluginv2.DataQueryResponse, error) {
resp, err := a.dataQueryHandler.DataQuery(ctx, dataQueryRequestFromProto(req))
if err != nil {
return nil, err
}
return resp.toProtobuf()
}
func (a *sdkAdapter) Resource(ctx context.Context, req *pluginv2.ResourceRequest) (*pluginv2.ResourceResponse, error) {
res, err := a.resourceHandler.Resource(ctx, resourceRequestFromProtobuf(req))
if err != nil {
return nil, err
}
return res.toProtobuf(), nil
}
func (a *sdkAdapter) TransformData(ctx context.Context, req *pluginv2.DataQueryRequest, callBack TransformCallBack) (*pluginv2.DataQueryResponse, error) {
resp, err := a.transformDataHandler.TransformData(ctx, dataQueryRequestFromProto(req), &transformCallBackWrapper{callBack})
if err != nil {
return nil, err
}
encodedFrames := make([][]byte, len(resp.Frames))
for i, frame := range resp.Frames {
encodedFrames[i], err = dataframe.MarshalArrow(frame)
if err != nil {
return nil, err
}
}
return &pluginv2.DataQueryResponse{
Frames: encodedFrames,
Metadata: resp.Metadata,
}, nil
}

View File

@ -1,69 +0,0 @@
package backend
import (
grpc_prometheus "github.com/grpc-ecosystem/go-grpc-prometheus"
plugin "github.com/hashicorp/go-plugin"
"google.golang.org/grpc"
)
type ServeOpts struct {
CheckHealthHandler CheckHealthHandler
DataQueryHandler DataQueryHandler
ResourceHandler ResourceHandler
TransformDataHandler TransformDataHandler
// GRPCServer factory method for creating GRPC server.
// If nil, the default one will be used.
GRPCServer func(options []grpc.ServerOption) *grpc.Server
}
// Serve starts serving the plugin over gRPC.
func Serve(opts ServeOpts) error {
versionedPlugins := make(map[int]plugin.PluginSet)
pSet := make(plugin.PluginSet)
sdkAdapter := &sdkAdapter{
checkHealthHandler: opts.CheckHealthHandler,
dataQueryHandler: opts.DataQueryHandler,
resourceHandler: opts.ResourceHandler,
transformDataHandler: opts.TransformDataHandler,
}
pSet["diagnostics"] = &DiagnosticsGRPCPlugin{
server: sdkAdapter,
}
if opts.DataQueryHandler != nil || opts.ResourceHandler != nil {
pSet["backend"] = &CoreGRPCPlugin{
server: sdkAdapter,
}
}
if opts.TransformDataHandler != nil {
pSet["transform"] = &TransformGRPCPlugin{
adapter: sdkAdapter,
}
}
versionedPlugins[ProtocolVersion] = pSet
if opts.GRPCServer == nil {
// opts.GRPCServer = plugin.DefaultGRPCServer
// hack for now to add grpc prometheuc server interceptor
opts.GRPCServer = func(serverOptions []grpc.ServerOption) *grpc.Server {
mergedOptions := serverOptions
mergedOptions = append(mergedOptions, grpc.UnaryInterceptor(grpc_prometheus.UnaryServerInterceptor))
server := grpc.NewServer(mergedOptions...)
grpc_prometheus.Register(server)
return server
}
}
plugin.Serve(&plugin.ServeConfig{
HandshakeConfig: Handshake,
VersionedPlugins: versionedPlugins,
GRPCServer: opts.GRPCServer,
})
return nil
}

View File

@ -1,46 +0,0 @@
package backend
import (
"context"
"github.com/grafana/grafana-plugin-sdk-go/genproto/pluginv2"
)
type TransformHandlers interface {
TransformDataHandler
}
type TransformDataHandler interface {
TransformData(ctx context.Context, req *DataQueryRequest, callBack TransformCallBackHandler) (*DataQueryResponse, error)
}
// Callback
type TransformCallBackHandler interface {
// TODO: Forget if I actually need PluginConfig on the callback or not.
DataQuery(ctx context.Context, req *DataQueryRequest) (*DataQueryResponse, error)
}
type transformCallBackWrapper struct {
callBack TransformCallBack
}
func (tw *transformCallBackWrapper) DataQuery(ctx context.Context, req *DataQueryRequest) (*DataQueryResponse, error) {
protoRes, err := tw.callBack.DataQuery(ctx, req.toProtobuf())
if err != nil {
return nil, err
}
return dataQueryResponseFromProtobuf(protoRes)
}
// TransformPlugin is the Grafana transform plugin interface.
type TransformPlugin interface {
DataQuery(ctx context.Context, req *pluginv2.DataQueryRequest, callback TransformCallBack) (*pluginv2.DataQueryResponse, error)
}
// Callback
type TransformCallBack interface {
DataQuery(ctx context.Context, req *pluginv2.DataQueryRequest) (*pluginv2.DataQueryResponse, error)
}

View File

@ -80,10 +80,14 @@ func buildArrowFields(f *Frame) ([]arrow.Field, error) {
return nil, err
}
fieldMeta := map[string]string{
"name": field.Name,
"labels": field.Labels.String(),
fieldMeta := map[string]string{"name": field.Name}
if field.Labels != nil {
if fieldMeta["labels"], err = toJSONString(field.Labels); err != nil {
return nil, err
}
}
if field.Config != nil {
str, err := toJSONString(field.Config)
if err != nil {
@ -293,16 +297,12 @@ func initializeFrameFields(schema *arrow.Schema, frame *Frame) ([]bool, error) {
Name: field.Name,
}
if labelsAsString, ok := getMDKey("labels", field.Metadata); ok {
var err error
sdkField.Labels, err = LabelsFromString(labelsAsString)
if err != nil {
if err := json.Unmarshal([]byte(labelsAsString), &sdkField.Labels); err != nil {
return nil, err
}
}
if configAsString, ok := getMDKey("config", field.Metadata); ok {
var err error
sdkField.Config, err = FieldConfigFromJSON(configAsString)
if err != nil {
if err := json.Unmarshal([]byte(configAsString), &sdkField.Config); err != nil {
return nil, err
}
}
@ -645,7 +645,8 @@ func UnmarshalArrow(b []byte) (*Frame, error) {
return frame, nil
}
// ToJSONString return the FieldConfig as a json string
// ToJSONString calls json.Marshal on val and returns it as a string. An
// error is returned if json.Marshal errors.
func toJSONString(val interface{}) (string, error) {
b, err := json.Marshal(val)
if err != nil {

View File

@ -1,7 +1,6 @@
package dataframe
import (
"encoding/json"
"fmt"
"math"
"strconv"
@ -72,16 +71,6 @@ func (sf *ConfFloat64) UnmarshalJSON(data []byte) error {
return nil
}
// FieldConfigFromJSON create a FieldConfig from json string
func FieldConfigFromJSON(jsonStr string) (*FieldConfig, error) {
var cfg FieldConfig
err := json.Unmarshal([]byte(jsonStr), &cfg)
if err != nil {
return nil, err
}
return &cfg, nil
}
// SetDecimals modifies the FieldConfig's Decimals property to
// be set to v and returns the FieldConfig. It is a convenance function
// since the Decimals property is a pointer.

View File

@ -1,5 +1,3 @@
// -build !test
package dataframe
//go:generate genny -in=$GOFILE -out=nullable_vector.gen.go gen "gen=uint8,uint16,uint32,uint64,int8,int16,int32,int64,float32,float64,string,bool,time.Time"
@ -28,7 +26,5 @@ func (v *nullablegenVector) Len() int {
}
func (v *nullablegenVector) PrimitiveType() VectorPType {
// following generates the right code but makes this invalid
//return VectorPTypeNullablegen
return vectorPType(v)
}

View File

@ -2,8 +2,6 @@
// Any changes will be lost if this file is regenerated.
// see https://github.com/cheekybits/genny
// -build !test
package dataframe
import "time"
@ -34,13 +32,9 @@ func (v *nullableUint8Vector) Len() int {
}
func (v *nullableUint8Vector) PrimitiveType() VectorPType {
// following uint8erates the right code but makes this invalid
//return VectorPTypeNullableUint8
return vectorPType(v)
}
// -build !test
//go:Uint16erate uint16ny -in=$GOFILE -out=nullable_vector.Uint16.go uint16 "Uint16=uint8,uint16,uint32,uint64,int8,int16,int32,int64,float32,float64,string,bool,time.Time"
type nullableUint16Vector []*uint16
@ -67,13 +61,9 @@ func (v *nullableUint16Vector) Len() int {
}
func (v *nullableUint16Vector) PrimitiveType() VectorPType {
// following uint16erates the right code but makes this invalid
//return VectorPTypeNullableUint16
return vectorPType(v)
}
// -build !test
//go:Uint32erate uint32ny -in=$GOFILE -out=nullable_vector.Uint32.go uint32 "Uint32=uint8,uint16,uint32,uint64,int8,int16,int32,int64,float32,float64,string,bool,time.Time"
type nullableUint32Vector []*uint32
@ -100,13 +90,9 @@ func (v *nullableUint32Vector) Len() int {
}
func (v *nullableUint32Vector) PrimitiveType() VectorPType {
// following uint32erates the right code but makes this invalid
//return VectorPTypeNullableUint32
return vectorPType(v)
}
// -build !test
//go:Uint64erate uint64ny -in=$GOFILE -out=nullable_vector.Uint64.go uint64 "Uint64=uint8,uint16,uint32,uint64,int8,int16,int32,int64,float32,float64,string,bool,time.Time"
type nullableUint64Vector []*uint64
@ -133,13 +119,9 @@ func (v *nullableUint64Vector) Len() int {
}
func (v *nullableUint64Vector) PrimitiveType() VectorPType {
// following uint64erates the right code but makes this invalid
//return VectorPTypeNullableUint64
return vectorPType(v)
}
// -build !test
//go:Int8erate int8ny -in=$GOFILE -out=nullable_vector.Int8.go int8 "Int8=uint8,uint16,uint32,uint64,int8,int16,int32,int64,float32,float64,string,bool,time.Time"
type nullableInt8Vector []*int8
@ -166,13 +148,9 @@ func (v *nullableInt8Vector) Len() int {
}
func (v *nullableInt8Vector) PrimitiveType() VectorPType {
// following int8erates the right code but makes this invalid
//return VectorPTypeNullableInt8
return vectorPType(v)
}
// -build !test
//go:Int16erate int16ny -in=$GOFILE -out=nullable_vector.Int16.go int16 "Int16=uint8,uint16,uint32,uint64,int8,int16,int32,int64,float32,float64,string,bool,time.Time"
type nullableInt16Vector []*int16
@ -199,13 +177,9 @@ func (v *nullableInt16Vector) Len() int {
}
func (v *nullableInt16Vector) PrimitiveType() VectorPType {
// following int16erates the right code but makes this invalid
//return VectorPTypeNullableInt16
return vectorPType(v)
}
// -build !test
//go:Int32erate int32ny -in=$GOFILE -out=nullable_vector.Int32.go int32 "Int32=uint8,uint16,uint32,uint64,int8,int16,int32,int64,float32,float64,string,bool,time.Time"
type nullableInt32Vector []*int32
@ -232,13 +206,9 @@ func (v *nullableInt32Vector) Len() int {
}
func (v *nullableInt32Vector) PrimitiveType() VectorPType {
// following int32erates the right code but makes this invalid
//return VectorPTypeNullableInt32
return vectorPType(v)
}
// -build !test
//go:Int64erate int64ny -in=$GOFILE -out=nullable_vector.Int64.go int64 "Int64=uint8,uint16,uint32,uint64,int8,int16,int32,int64,float32,float64,string,bool,time.Time"
type nullableInt64Vector []*int64
@ -265,13 +235,9 @@ func (v *nullableInt64Vector) Len() int {
}
func (v *nullableInt64Vector) PrimitiveType() VectorPType {
// following int64erates the right code but makes this invalid
//return VectorPTypeNullableInt64
return vectorPType(v)
}
// -build !test
//go:Float32erate float32ny -in=$GOFILE -out=nullable_vector.Float32.go float32 "Float32=uint8,uint16,uint32,uint64,int8,int16,int32,int64,float32,float64,string,bool,time.Time"
type nullableFloat32Vector []*float32
@ -298,13 +264,9 @@ func (v *nullableFloat32Vector) Len() int {
}
func (v *nullableFloat32Vector) PrimitiveType() VectorPType {
// following float32erates the right code but makes this invalid
//return VectorPTypeNullableFloat32
return vectorPType(v)
}
// -build !test
//go:Float64erate float64ny -in=$GOFILE -out=nullable_vector.Float64.go float64 "Float64=uint8,uint16,uint32,uint64,int8,int16,int32,int64,float32,float64,string,bool,time.Time"
type nullableFloat64Vector []*float64
@ -331,13 +293,9 @@ func (v *nullableFloat64Vector) Len() int {
}
func (v *nullableFloat64Vector) PrimitiveType() VectorPType {
// following float64erates the right code but makes this invalid
//return VectorPTypeNullableFloat64
return vectorPType(v)
}
// -build !test
//go:Stringerate stringny -in=$GOFILE -out=nullable_vector.String.go string "String=uint8,uint16,uint32,uint64,int8,int16,int32,int64,float32,float64,string,bool,time.Time"
type nullableStringVector []*string
@ -364,13 +322,9 @@ func (v *nullableStringVector) Len() int {
}
func (v *nullableStringVector) PrimitiveType() VectorPType {
// following stringerates the right code but makes this invalid
//return VectorPTypeNullableString
return vectorPType(v)
}
// -build !test
//go:Boolerate boolny -in=$GOFILE -out=nullable_vector.Bool.go bool "Bool=uint8,uint16,uint32,uint64,int8,int16,int32,int64,float32,float64,string,bool,time.Time"
type nullableBoolVector []*bool
@ -397,13 +351,9 @@ func (v *nullableBoolVector) Len() int {
}
func (v *nullableBoolVector) PrimitiveType() VectorPType {
// following boolerates the right code but makes this invalid
//return VectorPTypeNullableBool
return vectorPType(v)
}
// -build !test
//go:TimeTimeerate timeTimeny -in=$GOFILE -out=nullable_vector.TimeTime.go time.Time "TimeTime=uint8,uint16,uint32,uint64,int8,int16,int32,int64,float32,float64,string,bool,time.Time"
type nullableTimeTimeVector []*time.Time
@ -430,7 +380,5 @@ func (v *nullableTimeTimeVector) Len() int {
}
func (v *nullableTimeTimeVector) PrimitiveType() VectorPType {
// following timeTimeerates the right code but makes this invalid
//return VectorPTypeNullableTimeTime
return vectorPType(v)
}

View File

@ -50,7 +50,7 @@ func (x CheckHealth_Response_HealthStatus) String() string {
}
func (CheckHealth_Response_HealthStatus) EnumDescriptor() ([]byte, []int) {
return fileDescriptor_5ab9ba5b8d8b2ba5, []int{8, 1, 0}
return fileDescriptor_5ab9ba5b8d8b2ba5, []int{7, 1, 0}
}
type PluginConfig struct {
@ -366,128 +366,204 @@ func (m *DataQueryResponse) GetMetadata() map[string]string {
return nil
}
type ResourceRequest struct {
// Plugin Configuration
Config *PluginConfig `protobuf:"bytes,1,opt,name=config,proto3" json:"config,omitempty"`
// HTTP Style parameters
Headers map[string]string `protobuf:"bytes,2,rep,name=headers,proto3" json:"headers,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
Method string `protobuf:"bytes,3,opt,name=method,proto3" json:"method,omitempty"`
Path string `protobuf:"bytes,4,opt,name=path,proto3" json:"path,omitempty"`
Body []byte `protobuf:"bytes,5,opt,name=body,proto3" json:"body,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
type CallResource struct {
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *ResourceRequest) Reset() { *m = ResourceRequest{} }
func (m *ResourceRequest) String() string { return proto.CompactTextString(m) }
func (*ResourceRequest) ProtoMessage() {}
func (*ResourceRequest) Descriptor() ([]byte, []int) {
func (m *CallResource) Reset() { *m = CallResource{} }
func (m *CallResource) String() string { return proto.CompactTextString(m) }
func (*CallResource) ProtoMessage() {}
func (*CallResource) Descriptor() ([]byte, []int) {
return fileDescriptor_5ab9ba5b8d8b2ba5, []int{5}
}
func (m *ResourceRequest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_ResourceRequest.Unmarshal(m, b)
func (m *CallResource) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_CallResource.Unmarshal(m, b)
}
func (m *ResourceRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_ResourceRequest.Marshal(b, m, deterministic)
func (m *CallResource) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_CallResource.Marshal(b, m, deterministic)
}
func (m *ResourceRequest) XXX_Merge(src proto.Message) {
xxx_messageInfo_ResourceRequest.Merge(m, src)
func (m *CallResource) XXX_Merge(src proto.Message) {
xxx_messageInfo_CallResource.Merge(m, src)
}
func (m *ResourceRequest) XXX_Size() int {
return xxx_messageInfo_ResourceRequest.Size(m)
func (m *CallResource) XXX_Size() int {
return xxx_messageInfo_CallResource.Size(m)
}
func (m *ResourceRequest) XXX_DiscardUnknown() {
xxx_messageInfo_ResourceRequest.DiscardUnknown(m)
func (m *CallResource) XXX_DiscardUnknown() {
xxx_messageInfo_CallResource.DiscardUnknown(m)
}
var xxx_messageInfo_ResourceRequest proto.InternalMessageInfo
var xxx_messageInfo_CallResource proto.InternalMessageInfo
func (m *ResourceRequest) GetConfig() *PluginConfig {
type CallResource_StringList struct {
Values []string `protobuf:"bytes,1,rep,name=values,proto3" json:"values,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *CallResource_StringList) Reset() { *m = CallResource_StringList{} }
func (m *CallResource_StringList) String() string { return proto.CompactTextString(m) }
func (*CallResource_StringList) ProtoMessage() {}
func (*CallResource_StringList) Descriptor() ([]byte, []int) {
return fileDescriptor_5ab9ba5b8d8b2ba5, []int{5, 0}
}
func (m *CallResource_StringList) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_CallResource_StringList.Unmarshal(m, b)
}
func (m *CallResource_StringList) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_CallResource_StringList.Marshal(b, m, deterministic)
}
func (m *CallResource_StringList) XXX_Merge(src proto.Message) {
xxx_messageInfo_CallResource_StringList.Merge(m, src)
}
func (m *CallResource_StringList) XXX_Size() int {
return xxx_messageInfo_CallResource_StringList.Size(m)
}
func (m *CallResource_StringList) XXX_DiscardUnknown() {
xxx_messageInfo_CallResource_StringList.DiscardUnknown(m)
}
var xxx_messageInfo_CallResource_StringList proto.InternalMessageInfo
func (m *CallResource_StringList) GetValues() []string {
if m != nil {
return m.Values
}
return nil
}
type CallResource_Request struct {
Config *PluginConfig `protobuf:"bytes,1,opt,name=config,proto3" json:"config,omitempty"`
Path string `protobuf:"bytes,2,opt,name=path,proto3" json:"path,omitempty"`
Method string `protobuf:"bytes,3,opt,name=method,proto3" json:"method,omitempty"`
Url string `protobuf:"bytes,4,opt,name=url,proto3" json:"url,omitempty"`
Headers map[string]*CallResource_StringList `protobuf:"bytes,5,rep,name=headers,proto3" json:"headers,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
Body []byte `protobuf:"bytes,6,opt,name=body,proto3" json:"body,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *CallResource_Request) Reset() { *m = CallResource_Request{} }
func (m *CallResource_Request) String() string { return proto.CompactTextString(m) }
func (*CallResource_Request) ProtoMessage() {}
func (*CallResource_Request) Descriptor() ([]byte, []int) {
return fileDescriptor_5ab9ba5b8d8b2ba5, []int{5, 1}
}
func (m *CallResource_Request) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_CallResource_Request.Unmarshal(m, b)
}
func (m *CallResource_Request) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_CallResource_Request.Marshal(b, m, deterministic)
}
func (m *CallResource_Request) XXX_Merge(src proto.Message) {
xxx_messageInfo_CallResource_Request.Merge(m, src)
}
func (m *CallResource_Request) XXX_Size() int {
return xxx_messageInfo_CallResource_Request.Size(m)
}
func (m *CallResource_Request) XXX_DiscardUnknown() {
xxx_messageInfo_CallResource_Request.DiscardUnknown(m)
}
var xxx_messageInfo_CallResource_Request proto.InternalMessageInfo
func (m *CallResource_Request) GetConfig() *PluginConfig {
if m != nil {
return m.Config
}
return nil
}
func (m *ResourceRequest) GetHeaders() map[string]string {
if m != nil {
return m.Headers
}
return nil
}
func (m *ResourceRequest) GetMethod() string {
if m != nil {
return m.Method
}
return ""
}
func (m *ResourceRequest) GetPath() string {
func (m *CallResource_Request) GetPath() string {
if m != nil {
return m.Path
}
return ""
}
func (m *ResourceRequest) GetBody() []byte {
func (m *CallResource_Request) GetMethod() string {
if m != nil {
return m.Body
return m.Method
}
return nil
return ""
}
type ResourceResponse struct {
Code int32 `protobuf:"varint,1,opt,name=code,proto3" json:"code,omitempty"`
Headers map[string]string `protobuf:"bytes,2,rep,name=headers,proto3" json:"headers,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
Body []byte `protobuf:"bytes,3,opt,name=body,proto3" json:"body,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *ResourceResponse) Reset() { *m = ResourceResponse{} }
func (m *ResourceResponse) String() string { return proto.CompactTextString(m) }
func (*ResourceResponse) ProtoMessage() {}
func (*ResourceResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_5ab9ba5b8d8b2ba5, []int{6}
}
func (m *ResourceResponse) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_ResourceResponse.Unmarshal(m, b)
}
func (m *ResourceResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_ResourceResponse.Marshal(b, m, deterministic)
}
func (m *ResourceResponse) XXX_Merge(src proto.Message) {
xxx_messageInfo_ResourceResponse.Merge(m, src)
}
func (m *ResourceResponse) XXX_Size() int {
return xxx_messageInfo_ResourceResponse.Size(m)
}
func (m *ResourceResponse) XXX_DiscardUnknown() {
xxx_messageInfo_ResourceResponse.DiscardUnknown(m)
}
var xxx_messageInfo_ResourceResponse proto.InternalMessageInfo
func (m *ResourceResponse) GetCode() int32 {
func (m *CallResource_Request) GetUrl() string {
if m != nil {
return m.Code
return m.Url
}
return 0
return ""
}
func (m *ResourceResponse) GetHeaders() map[string]string {
func (m *CallResource_Request) GetHeaders() map[string]*CallResource_StringList {
if m != nil {
return m.Headers
}
return nil
}
func (m *ResourceResponse) GetBody() []byte {
func (m *CallResource_Request) GetBody() []byte {
if m != nil {
return m.Body
}
return nil
}
type CallResource_Response struct {
Code int32 `protobuf:"varint,1,opt,name=code,proto3" json:"code,omitempty"`
Headers map[string]*CallResource_StringList `protobuf:"bytes,2,rep,name=headers,proto3" json:"headers,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
Body []byte `protobuf:"bytes,3,opt,name=body,proto3" json:"body,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *CallResource_Response) Reset() { *m = CallResource_Response{} }
func (m *CallResource_Response) String() string { return proto.CompactTextString(m) }
func (*CallResource_Response) ProtoMessage() {}
func (*CallResource_Response) Descriptor() ([]byte, []int) {
return fileDescriptor_5ab9ba5b8d8b2ba5, []int{5, 2}
}
func (m *CallResource_Response) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_CallResource_Response.Unmarshal(m, b)
}
func (m *CallResource_Response) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_CallResource_Response.Marshal(b, m, deterministic)
}
func (m *CallResource_Response) XXX_Merge(src proto.Message) {
xxx_messageInfo_CallResource_Response.Merge(m, src)
}
func (m *CallResource_Response) XXX_Size() int {
return xxx_messageInfo_CallResource_Response.Size(m)
}
func (m *CallResource_Response) XXX_DiscardUnknown() {
xxx_messageInfo_CallResource_Response.DiscardUnknown(m)
}
var xxx_messageInfo_CallResource_Response proto.InternalMessageInfo
func (m *CallResource_Response) GetCode() int32 {
if m != nil {
return m.Code
}
return 0
}
func (m *CallResource_Response) GetHeaders() map[string]*CallResource_StringList {
if m != nil {
return m.Headers
}
return nil
}
func (m *CallResource_Response) GetBody() []byte {
if m != nil {
return m.Body
}
@ -504,7 +580,7 @@ func (m *CollectMetrics) Reset() { *m = CollectMetrics{} }
func (m *CollectMetrics) String() string { return proto.CompactTextString(m) }
func (*CollectMetrics) ProtoMessage() {}
func (*CollectMetrics) Descriptor() ([]byte, []int) {
return fileDescriptor_5ab9ba5b8d8b2ba5, []int{7}
return fileDescriptor_5ab9ba5b8d8b2ba5, []int{6}
}
func (m *CollectMetrics) XXX_Unmarshal(b []byte) error {
@ -535,7 +611,7 @@ func (m *CollectMetrics_Request) Reset() { *m = CollectMetrics_Request{}
func (m *CollectMetrics_Request) String() string { return proto.CompactTextString(m) }
func (*CollectMetrics_Request) ProtoMessage() {}
func (*CollectMetrics_Request) Descriptor() ([]byte, []int) {
return fileDescriptor_5ab9ba5b8d8b2ba5, []int{7, 0}
return fileDescriptor_5ab9ba5b8d8b2ba5, []int{6, 0}
}
func (m *CollectMetrics_Request) XXX_Unmarshal(b []byte) error {
@ -567,7 +643,7 @@ func (m *CollectMetrics_Payload) Reset() { *m = CollectMetrics_Payload{}
func (m *CollectMetrics_Payload) String() string { return proto.CompactTextString(m) }
func (*CollectMetrics_Payload) ProtoMessage() {}
func (*CollectMetrics_Payload) Descriptor() ([]byte, []int) {
return fileDescriptor_5ab9ba5b8d8b2ba5, []int{7, 1}
return fileDescriptor_5ab9ba5b8d8b2ba5, []int{6, 1}
}
func (m *CollectMetrics_Payload) XXX_Unmarshal(b []byte) error {
@ -606,7 +682,7 @@ func (m *CollectMetrics_Response) Reset() { *m = CollectMetrics_Response
func (m *CollectMetrics_Response) String() string { return proto.CompactTextString(m) }
func (*CollectMetrics_Response) ProtoMessage() {}
func (*CollectMetrics_Response) Descriptor() ([]byte, []int) {
return fileDescriptor_5ab9ba5b8d8b2ba5, []int{7, 2}
return fileDescriptor_5ab9ba5b8d8b2ba5, []int{6, 2}
}
func (m *CollectMetrics_Response) XXX_Unmarshal(b []byte) error {
@ -644,7 +720,7 @@ func (m *CheckHealth) Reset() { *m = CheckHealth{} }
func (m *CheckHealth) String() string { return proto.CompactTextString(m) }
func (*CheckHealth) ProtoMessage() {}
func (*CheckHealth) Descriptor() ([]byte, []int) {
return fileDescriptor_5ab9ba5b8d8b2ba5, []int{8}
return fileDescriptor_5ab9ba5b8d8b2ba5, []int{7}
}
func (m *CheckHealth) XXX_Unmarshal(b []byte) error {
@ -675,7 +751,7 @@ func (m *CheckHealth_Request) Reset() { *m = CheckHealth_Request{} }
func (m *CheckHealth_Request) String() string { return proto.CompactTextString(m) }
func (*CheckHealth_Request) ProtoMessage() {}
func (*CheckHealth_Request) Descriptor() ([]byte, []int) {
return fileDescriptor_5ab9ba5b8d8b2ba5, []int{8, 0}
return fileDescriptor_5ab9ba5b8d8b2ba5, []int{7, 0}
}
func (m *CheckHealth_Request) XXX_Unmarshal(b []byte) error {
@ -708,7 +784,7 @@ func (m *CheckHealth_Response) Reset() { *m = CheckHealth_Response{} }
func (m *CheckHealth_Response) String() string { return proto.CompactTextString(m) }
func (*CheckHealth_Response) ProtoMessage() {}
func (*CheckHealth_Response) Descriptor() ([]byte, []int) {
return fileDescriptor_5ab9ba5b8d8b2ba5, []int{8, 1}
return fileDescriptor_5ab9ba5b8d8b2ba5, []int{7, 1}
}
func (m *CheckHealth_Response) XXX_Unmarshal(b []byte) error {
@ -762,7 +838,7 @@ func (m *StreamingRequest) Reset() { *m = StreamingRequest{} }
func (m *StreamingRequest) String() string { return proto.CompactTextString(m) }
func (*StreamingRequest) ProtoMessage() {}
func (*StreamingRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_5ab9ba5b8d8b2ba5, []int{9}
return fileDescriptor_5ab9ba5b8d8b2ba5, []int{8}
}
func (m *StreamingRequest) XXX_Unmarshal(b []byte) error {
@ -831,7 +907,7 @@ func (m *StreamingMessage) Reset() { *m = StreamingMessage{} }
func (m *StreamingMessage) String() string { return proto.CompactTextString(m) }
func (*StreamingMessage) ProtoMessage() {}
func (*StreamingMessage) Descriptor() ([]byte, []int) {
return fileDescriptor_5ab9ba5b8d8b2ba5, []int{10}
return fileDescriptor_5ab9ba5b8d8b2ba5, []int{9}
}
func (m *StreamingMessage) XXX_Unmarshal(b []byte) error {
@ -884,7 +960,7 @@ func (m *StreamingClose) Reset() { *m = StreamingClose{} }
func (m *StreamingClose) String() string { return proto.CompactTextString(m) }
func (*StreamingClose) ProtoMessage() {}
func (*StreamingClose) Descriptor() ([]byte, []int) {
return fileDescriptor_5ab9ba5b8d8b2ba5, []int{11}
return fileDescriptor_5ab9ba5b8d8b2ba5, []int{10}
}
func (m *StreamingClose) XXX_Unmarshal(b []byte) error {
@ -931,7 +1007,7 @@ func (m *RenderRequest) Reset() { *m = RenderRequest{} }
func (m *RenderRequest) String() string { return proto.CompactTextString(m) }
func (*RenderRequest) ProtoMessage() {}
func (*RenderRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_5ab9ba5b8d8b2ba5, []int{12}
return fileDescriptor_5ab9ba5b8d8b2ba5, []int{11}
}
func (m *RenderRequest) XXX_Unmarshal(b []byte) error {
@ -1026,7 +1102,7 @@ func (m *RenderResponse) Reset() { *m = RenderResponse{} }
func (m *RenderResponse) String() string { return proto.CompactTextString(m) }
func (*RenderResponse) ProtoMessage() {}
func (*RenderResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_5ab9ba5b8d8b2ba5, []int{13}
return fileDescriptor_5ab9ba5b8d8b2ba5, []int{12}
}
func (m *RenderResponse) XXX_Unmarshal(b []byte) error {
@ -1064,10 +1140,12 @@ func init() {
proto.RegisterMapType((map[string]string)(nil), "pluginv2.DataQueryRequest.HeadersEntry")
proto.RegisterType((*DataQueryResponse)(nil), "pluginv2.DataQueryResponse")
proto.RegisterMapType((map[string]string)(nil), "pluginv2.DataQueryResponse.MetadataEntry")
proto.RegisterType((*ResourceRequest)(nil), "pluginv2.ResourceRequest")
proto.RegisterMapType((map[string]string)(nil), "pluginv2.ResourceRequest.HeadersEntry")
proto.RegisterType((*ResourceResponse)(nil), "pluginv2.ResourceResponse")
proto.RegisterMapType((map[string]string)(nil), "pluginv2.ResourceResponse.HeadersEntry")
proto.RegisterType((*CallResource)(nil), "pluginv2.CallResource")
proto.RegisterType((*CallResource_StringList)(nil), "pluginv2.CallResource.StringList")
proto.RegisterType((*CallResource_Request)(nil), "pluginv2.CallResource.Request")
proto.RegisterMapType((map[string]*CallResource_StringList)(nil), "pluginv2.CallResource.Request.HeadersEntry")
proto.RegisterType((*CallResource_Response)(nil), "pluginv2.CallResource.Response")
proto.RegisterMapType((map[string]*CallResource_StringList)(nil), "pluginv2.CallResource.Response.HeadersEntry")
proto.RegisterType((*CollectMetrics)(nil), "pluginv2.CollectMetrics")
proto.RegisterType((*CollectMetrics_Request)(nil), "pluginv2.CollectMetrics.Request")
proto.RegisterType((*CollectMetrics_Payload)(nil), "pluginv2.CollectMetrics.Payload")
@ -1086,83 +1164,86 @@ func init() {
func init() { proto.RegisterFile("backend.proto", fileDescriptor_5ab9ba5b8d8b2ba5) }
var fileDescriptor_5ab9ba5b8d8b2ba5 = []byte{
// 1207 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xcc, 0x57, 0xdd, 0x8e, 0xdb, 0x44,
0x14, 0xc6, 0xc9, 0xe6, 0xef, 0x64, 0x77, 0x9b, 0x4e, 0x57, 0xad, 0x71, 0x4b, 0x15, 0x22, 0x54,
0xb6, 0x42, 0xb8, 0x90, 0xde, 0xa0, 0x56, 0xe2, 0x2f, 0xbb, 0xf4, 0x67, 0xd9, 0x6e, 0x3a, 0xdb,
0x0a, 0x71, 0x83, 0x34, 0xb1, 0x4f, 0x12, 0xb3, 0xb6, 0x27, 0x1d, 0x8f, 0x17, 0xc2, 0x43, 0xc0,
0x05, 0x77, 0x3c, 0x04, 0x48, 0xdc, 0xf1, 0x00, 0x3c, 0x02, 0xef, 0x81, 0xc4, 0x05, 0x97, 0xa0,
0x19, 0x8f, 0x1d, 0x6f, 0xb4, 0x89, 0x54, 0xba, 0x17, 0xdc, 0x9d, 0x73, 0x3c, 0xe7, 0x9b, 0xef,
0xfc, 0xcc, 0x99, 0x31, 0x6c, 0x8d, 0x98, 0x77, 0x82, 0xb1, 0xef, 0xce, 0x04, 0x97, 0x9c, 0x34,
0x67, 0x61, 0x3a, 0x09, 0xe2, 0xd3, 0xbe, 0x73, 0x7d, 0xc2, 0xf9, 0x24, 0xc4, 0x3b, 0xda, 0x3e,
0x4a, 0xc7, 0x77, 0x30, 0x9a, 0xc9, 0x79, 0xb6, 0xac, 0xf7, 0x7b, 0x05, 0x36, 0x87, 0x7a, 0xe5,
0x80, 0xc7, 0xe3, 0x60, 0x42, 0xb6, 0xa1, 0x12, 0xf8, 0xb6, 0xd5, 0xb5, 0x76, 0xab, 0xb4, 0x12,
0xf8, 0x64, 0x07, 0x6a, 0x5c, 0x4c, 0x1e, 0xf9, 0x76, 0x45, 0x9b, 0x32, 0x85, 0x10, 0xd8, 0x88,
0x59, 0x84, 0x76, 0xb5, 0x6b, 0xed, 0xb6, 0xa8, 0x96, 0x95, 0x4d, 0xce, 0x67, 0x68, 0x6f, 0x64,
0x36, 0x25, 0x93, 0x0e, 0x54, 0x53, 0x11, 0xda, 0x35, 0x6d, 0x52, 0x22, 0x71, 0xa0, 0xf9, 0x75,
0xc2, 0xe3, 0x3d, 0x26, 0x99, 0x5d, 0xd7, 0xe6, 0x42, 0x27, 0x11, 0x5c, 0xf3, 0xd1, 0x13, 0xf3,
0x99, 0x44, 0xff, 0x18, 0xbd, 0x54, 0xe0, 0xe3, 0x7c, 0x69, 0xa3, 0x5b, 0xdd, 0x6d, 0xf7, 0xef,
0xba, 0x79, 0x54, 0x6e, 0x99, 0xb4, 0xbb, 0x77, 0xbe, 0xd7, 0x7e, 0x2c, 0xc5, 0x9c, 0xae, 0xc2,
0x74, 0x1e, 0xc3, 0x8d, 0x75, 0x8e, 0x8a, 0xfc, 0x09, 0xce, 0x75, 0x2e, 0x5a, 0x54, 0x89, 0x2a,
0x19, 0xa7, 0x2c, 0x4c, 0x51, 0x27, 0xa3, 0x45, 0x33, 0xe5, 0x5e, 0xe5, 0x03, 0xab, 0x77, 0x00,
0xad, 0x67, 0x41, 0x84, 0x94, 0xc5, 0x13, 0x24, 0x5d, 0x68, 0x8f, 0x05, 0x8f, 0xf6, 0x67, 0xdc,
0x9b, 0x1e, 0x1e, 0x9b, 0x64, 0x96, 0x4d, 0xe4, 0x06, 0xb4, 0x24, 0xcf, 0xbf, 0x67, 0x99, 0x5d,
0x18, 0x7a, 0x3f, 0x5b, 0xd0, 0x52, 0x34, 0x9e, 0xa6, 0x28, 0xf4, 0xa6, 0x02, 0xc7, 0x8f, 0x7c,
0x43, 0x24, 0x53, 0xc8, 0x5b, 0xb0, 0x15, 0xb1, 0x6f, 0xd5, 0xaa, 0x21, 0x0f, 0x62, 0x99, 0x18,
0x94, 0xb3, 0x46, 0x72, 0x13, 0x20, 0x88, 0x25, 0x8a, 0x53, 0x16, 0x1e, 0x1e, 0xeb, 0x6a, 0x55,
0x69, 0xc9, 0x42, 0xde, 0x87, 0x96, 0xcc, 0x69, 0xeb, 0xc2, 0xb5, 0xfb, 0x57, 0x16, 0x39, 0x2e,
0x22, 0xa2, 0x8b, 0x55, 0xaa, 0xcc, 0xaa, 0x60, 0xba, 0xa6, 0x9b, 0x54, 0xcb, 0xbd, 0x3f, 0x2d,
0xe8, 0x14, 0x84, 0x29, 0xbe, 0x48, 0x31, 0x91, 0xc4, 0x85, 0xba, 0xa7, 0xcb, 0xa3, 0x89, 0xb7,
0xfb, 0x57, 0xcf, 0x2f, 0x1e, 0x35, 0xab, 0xc8, 0x27, 0xd0, 0x98, 0x22, 0xf3, 0x51, 0xa8, 0x58,
0x54, 0xb5, 0xdf, 0x5e, 0x38, 0x2c, 0x83, 0xbb, 0x0f, 0xb3, 0x95, 0x59, 0x85, 0x73, 0x3f, 0xf2,
0x2e, 0x34, 0x5e, 0xa4, 0x28, 0x02, 0x4c, 0xec, 0xaa, 0x86, 0xb8, 0x72, 0x1e, 0x44, 0xbe, 0xc6,
0xb9, 0x07, 0x9b, 0x65, 0x9c, 0x97, 0x2a, 0xf8, 0x2f, 0x16, 0x5c, 0x2e, 0xb1, 0x4a, 0x66, 0x3c,
0x4e, 0x90, 0x5c, 0x85, 0xfa, 0x58, 0xb0, 0x08, 0x13, 0xdb, 0xea, 0x56, 0x77, 0x37, 0xa9, 0xd1,
0xc8, 0x3e, 0x34, 0x23, 0x94, 0xcc, 0x57, 0xad, 0x9c, 0x05, 0x77, 0xfb, 0xdc, 0xe0, 0x32, 0x18,
0xf7, 0xd0, 0xac, 0xcd, 0xc2, 0x2b, 0x5c, 0x9d, 0xfb, 0xb0, 0x75, 0xe6, 0xd3, 0x4b, 0x31, 0xfe,
0xc7, 0x82, 0x4b, 0x14, 0x13, 0x9e, 0x0a, 0x0f, 0xff, 0x6b, 0x8d, 0x3e, 0x5e, 0xae, 0xd1, 0xad,
0x85, 0xc3, 0x12, 0xf6, 0x8a, 0x12, 0x5d, 0x85, 0x7a, 0x84, 0x72, 0xca, 0x7d, 0x33, 0x3b, 0x8c,
0xa6, 0xda, 0x6a, 0xc6, 0xe4, 0x34, 0x9f, 0x1e, 0x4a, 0x56, 0xb6, 0x11, 0xf7, 0xe7, 0x79, 0xab,
0x29, 0xf9, 0x95, 0x6a, 0xf6, 0x9b, 0x05, 0x9d, 0x05, 0x4b, 0x53, 0x32, 0x02, 0x1b, 0x1e, 0xf7,
0x51, 0x23, 0xd4, 0xa8, 0x96, 0xd7, 0xb6, 0xe2, 0x32, 0xc0, 0x8a, 0x38, 0x73, 0xee, 0xd5, 0x0b,
0xe2, 0xfe, 0xbd, 0x05, 0xdb, 0x03, 0x1e, 0x86, 0xe8, 0xc9, 0x43, 0x94, 0x22, 0xf0, 0x12, 0xa7,
0x05, 0x0d, 0x93, 0x6b, 0xe7, 0x36, 0x34, 0x86, 0x6c, 0x1e, 0x72, 0xe6, 0xab, 0x23, 0x3f, 0x13,
0x5c, 0x65, 0x15, 0xd3, 0x44, 0x63, 0x6f, 0xd2, 0x92, 0xc5, 0xf9, 0x0c, 0x9a, 0x45, 0xec, 0xf7,
0xa0, 0x11, 0x65, 0x60, 0xa6, 0xfe, 0xdd, 0x45, 0x9c, 0x67, 0x37, 0x73, 0x0d, 0x3c, 0xcd, 0x1d,
0xd4, 0x90, 0x6a, 0x0f, 0xa6, 0xe8, 0x9d, 0x3c, 0x44, 0x16, 0xca, 0x69, 0x99, 0xcd, 0x8f, 0x56,
0x69, 0x8f, 0x01, 0xd4, 0x13, 0xc9, 0xa4, 0xe1, 0xb2, 0xdd, 0x7f, 0xa7, 0xb4, 0xc5, 0xc2, 0xdd,
0x2d, 0xa7, 0x33, 0x94, 0xd3, 0x63, 0xed, 0x42, 0x8d, 0xab, 0xca, 0x66, 0x10, 0x8f, 0xb9, 0x49,
0x8b, 0x96, 0x7b, 0xae, 0xce, 0x66, 0xb1, 0x96, 0xb4, 0xa1, 0xf1, 0xfc, 0xc9, 0xc1, 0x93, 0xa3,
0x2f, 0x9e, 0x74, 0x5e, 0x23, 0x75, 0xa8, 0x1c, 0x1d, 0x74, 0x2c, 0xd2, 0x82, 0xda, 0x3e, 0xa5,
0x47, 0xb4, 0x53, 0xe9, 0xfd, 0x6d, 0x41, 0xe7, 0x58, 0x0a, 0x64, 0x51, 0x10, 0x4f, 0xf2, 0x03,
0xe0, 0x80, 0xb9, 0x28, 0x1f, 0xe5, 0x97, 0x5e, 0xa1, 0xaf, 0xed, 0x82, 0x65, 0xa0, 0x15, 0x5d,
0xb0, 0x03, 0x35, 0xc9, 0x67, 0x81, 0x67, 0x9a, 0x3d, 0x53, 0x8a, 0xde, 0x30, 0xbd, 0xae, 0x64,
0x55, 0xb6, 0x24, 0x1d, 0x25, 0x9e, 0x08, 0x46, 0xe8, 0xeb, 0x8e, 0x6f, 0xd2, 0x92, 0xe5, 0x95,
0x7a, 0xe7, 0xab, 0x52, 0xe0, 0x87, 0x98, 0x24, 0x6c, 0x82, 0x6b, 0x03, 0x2f, 0x58, 0x57, 0xca,
0xac, 0x6d, 0xd5, 0x2c, 0xda, 0xd9, 0x44, 0x93, 0xab, 0xbd, 0x5b, 0xb0, 0x5d, 0xe0, 0x0f, 0x42,
0x9e, 0xa0, 0x42, 0xf0, 0x78, 0x1a, 0x4b, 0x03, 0x9d, 0x29, 0xbd, 0xbf, 0x2c, 0xd8, 0xa2, 0x18,
0xfb, 0x28, 0xf2, 0xf4, 0x9b, 0xf7, 0x81, 0xb5, 0x78, 0x1f, 0xec, 0x40, 0xed, 0x9b, 0xc0, 0x97,
0x53, 0xbd, 0x77, 0x8d, 0x66, 0x8a, 0x9a, 0x1a, 0x53, 0x0c, 0x26, 0x53, 0xa9, 0xb7, 0xae, 0x51,
0xa3, 0x29, 0x4e, 0xea, 0x66, 0xe2, 0xa9, 0xd4, 0xc9, 0xac, 0xd1, 0x5c, 0x55, 0xf1, 0x29, 0xf1,
0x3b, 0x1e, 0xa3, 0x79, 0x7e, 0x14, 0xba, 0xfa, 0x86, 0xb1, 0xc7, 0xfd, 0x20, 0x9e, 0xe4, 0x6f,
0x90, 0x5c, 0x57, 0xdf, 0xc6, 0x41, 0x88, 0x43, 0x35, 0x8b, 0x1a, 0xd9, 0xb7, 0x5c, 0x57, 0xb7,
0xb6, 0xd0, 0xf4, 0x0f, 0x70, 0x6e, 0x37, 0xf5, 0xc7, 0x85, 0x41, 0x71, 0xf4, 0x79, 0xc4, 0x82,
0xd8, 0x6e, 0x65, 0x93, 0x2d, 0xd3, 0x54, 0x76, 0xf2, 0xa0, 0xcd, 0x91, 0xd8, 0x81, 0x1a, 0x0a,
0xc1, 0x45, 0x7e, 0xa3, 0x6b, 0xa5, 0xff, 0x83, 0x05, 0x1b, 0x03, 0x2e, 0xd4, 0xf4, 0x69, 0xe6,
0x43, 0x86, 0xbc, 0xbe, 0x72, 0xbe, 0x3a, 0xce, 0xea, 0x99, 0x44, 0xf6, 0xca, 0x0f, 0x08, 0x67,
0xf5, 0x3d, 0xea, 0x5c, 0x5f, 0x73, 0x0d, 0xf5, 0x7f, 0xb5, 0xa0, 0xbd, 0x17, 0xb0, 0x49, 0xcc,
0x13, 0x19, 0x78, 0x09, 0x79, 0xbe, 0x3c, 0x82, 0xc8, 0xea, 0x79, 0x91, 0x6f, 0xf0, 0xe6, 0x9a,
0x15, 0x86, 0xec, 0xe7, 0x67, 0x06, 0x09, 0x79, 0x63, 0xd5, 0x80, 0xc8, 0x00, 0x6f, 0xae, 0x9f,
0x1f, 0xfd, 0x9f, 0x2c, 0xb8, 0xf4, 0x40, 0xb0, 0x31, 0x8b, 0xd9, 0x30, 0x64, 0x72, 0xcc, 0x45,
0xf4, 0xff, 0xc9, 0xe8, 0x53, 0x68, 0x3d, 0x13, 0x2c, 0x4e, 0x34, 0xab, 0x8b, 0x81, 0xfc, 0x12,
0x2e, 0x17, 0x90, 0x03, 0x16, 0x86, 0x9f, 0x32, 0xef, 0xe4, 0x82, 0xa0, 0xff, 0xb0, 0xe0, 0x52,
0x71, 0xb0, 0xb3, 0xf7, 0x00, 0xf9, 0x08, 0x1a, 0x03, 0x1e, 0xc7, 0xe8, 0x49, 0xb2, 0xe2, 0xb1,
0x50, 0x4e, 0xe3, 0xf2, 0xd8, 0x79, 0xcf, 0x52, 0x53, 0x75, 0x28, 0xb8, 0x87, 0x49, 0x42, 0x9c,
0xd5, 0xf3, 0x74, 0x1d, 0x08, 0xf9, 0x10, 0x60, 0x2f, 0x48, 0xbc, 0x82, 0x46, 0xf6, 0x83, 0xe3,
0xe6, 0x3f, 0x38, 0xee, 0xbe, 0xfa, 0xc1, 0x71, 0xec, 0x73, 0x10, 0xf4, 0x74, 0xea, 0x3f, 0x50,
0xed, 0xa0, 0x4e, 0x24, 0x0a, 0x72, 0x1f, 0xea, 0x99, 0x4c, 0xae, 0x95, 0xab, 0x5f, 0x1a, 0x52,
0x65, 0xa0, 0xb3, 0x07, 0x79, 0x54, 0xd7, 0x5b, 0xde, 0xfd, 0x37, 0x00, 0x00, 0xff, 0xff, 0xf3,
0x10, 0x26, 0xf4, 0x7c, 0x0d, 0x00, 0x00,
// 1261 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xcc, 0x57, 0xcd, 0x6e, 0x1c, 0x45,
0x10, 0x66, 0xf6, 0xd7, 0x5b, 0xbb, 0x76, 0x9c, 0x8e, 0x95, 0xac, 0x26, 0x21, 0x38, 0xab, 0x08,
0x1c, 0x01, 0x1b, 0xd8, 0x1c, 0x40, 0x89, 0x04, 0x82, 0xb5, 0xf3, 0xe7, 0x38, 0x76, 0xda, 0x89,
0x10, 0x07, 0x90, 0xda, 0x33, 0xb5, 0xbb, 0x83, 0x67, 0xba, 0x37, 0x3d, 0x3d, 0x81, 0xe5, 0x21,
0xb8, 0x70, 0xe6, 0xc6, 0x19, 0x04, 0xcf, 0x00, 0x8f, 0xc0, 0x81, 0xb7, 0x40, 0xe2, 0xc0, 0x15,
0x75, 0x4f, 0xcf, 0xcf, 0x5a, 0xde, 0x8d, 0x02, 0x11, 0xe2, 0x56, 0x55, 0x53, 0x5d, 0xfd, 0xd5,
0x4f, 0x7f, 0xdd, 0x03, 0xab, 0x47, 0xcc, 0x3b, 0x46, 0xee, 0xf7, 0xa7, 0x52, 0x28, 0x41, 0x56,
0xa6, 0x61, 0x32, 0x0e, 0xf8, 0xb3, 0x81, 0x7b, 0x71, 0x2c, 0xc4, 0x38, 0xc4, 0xeb, 0xc6, 0x7e,
0x94, 0x8c, 0xae, 0x63, 0x34, 0x55, 0xb3, 0xd4, 0xad, 0xf7, 0x6b, 0x05, 0x3a, 0x07, 0xc6, 0x73,
0x28, 0xf8, 0x28, 0x18, 0x93, 0x35, 0xa8, 0x04, 0x7e, 0xd7, 0xd9, 0x74, 0xb6, 0xaa, 0xb4, 0x12,
0xf8, 0x64, 0x03, 0xea, 0x42, 0x8e, 0xef, 0xf9, 0xdd, 0x8a, 0x31, 0xa5, 0x0a, 0x21, 0x50, 0xe3,
0x2c, 0xc2, 0x6e, 0x75, 0xd3, 0xd9, 0x6a, 0x51, 0x23, 0x6b, 0x9b, 0x9a, 0x4d, 0xb1, 0x5b, 0x4b,
0x6d, 0x5a, 0x26, 0xeb, 0x50, 0x4d, 0x64, 0xd8, 0xad, 0x1b, 0x93, 0x16, 0x89, 0x0b, 0x2b, 0x5f,
0xc4, 0x82, 0x6f, 0x33, 0xc5, 0xba, 0x0d, 0x63, 0xce, 0x75, 0x12, 0xc1, 0x05, 0x1f, 0x3d, 0x39,
0x9b, 0x2a, 0xf4, 0x0f, 0xd1, 0x4b, 0x24, 0xde, 0xcf, 0x5c, 0x9b, 0x9b, 0xd5, 0xad, 0xf6, 0xe0,
0x46, 0x3f, 0xcb, 0xaa, 0x5f, 0x06, 0xdd, 0xdf, 0x3e, 0x7d, 0xd5, 0x0e, 0x57, 0x72, 0x46, 0x17,
0xc5, 0x74, 0xef, 0xc3, 0xa5, 0x65, 0x0b, 0x35, 0xf8, 0x63, 0x9c, 0x99, 0x5a, 0xb4, 0xa8, 0x16,
0x75, 0x31, 0x9e, 0xb1, 0x30, 0x41, 0x53, 0x8c, 0x16, 0x4d, 0x95, 0x9b, 0x95, 0xf7, 0x9d, 0xde,
0x2e, 0xb4, 0x1e, 0x07, 0x11, 0x52, 0xc6, 0xc7, 0x48, 0x36, 0xa1, 0x3d, 0x92, 0x22, 0xda, 0x99,
0x0a, 0x6f, 0xb2, 0x77, 0x68, 0x8b, 0x59, 0x36, 0x91, 0x4b, 0xd0, 0x52, 0x22, 0xfb, 0x9e, 0x56,
0xb6, 0x30, 0xf4, 0x7e, 0x70, 0xa0, 0xa5, 0x61, 0x3c, 0x4a, 0x50, 0x9a, 0x4d, 0x25, 0x8e, 0xee,
0xf9, 0x16, 0x48, 0xaa, 0x90, 0xab, 0xb0, 0x1a, 0xb1, 0xaf, 0xb4, 0xd7, 0x81, 0x08, 0xb8, 0x8a,
0x6d, 0x94, 0x79, 0x23, 0xb9, 0x0c, 0x10, 0x70, 0x85, 0xf2, 0x19, 0x0b, 0xf7, 0x0e, 0x4d, 0xb7,
0xaa, 0xb4, 0x64, 0x21, 0xef, 0x42, 0x4b, 0x65, 0xb0, 0x4d, 0xe3, 0xda, 0x83, 0x73, 0x45, 0x8d,
0xf3, 0x8c, 0x68, 0xe1, 0xa5, 0xdb, 0xac, 0x1b, 0x66, 0x7a, 0xda, 0xa1, 0x46, 0xee, 0xfd, 0xe1,
0xc0, 0x7a, 0x0e, 0x98, 0xe2, 0xd3, 0x04, 0x63, 0x45, 0xfa, 0xd0, 0xf0, 0x4c, 0x7b, 0x0c, 0xf0,
0xf6, 0xe0, 0xfc, 0xe9, 0xcd, 0xa3, 0xd6, 0x8b, 0x7c, 0x04, 0xcd, 0x09, 0x32, 0x1f, 0xa5, 0xce,
0x45, 0x77, 0xfb, 0x8d, 0x62, 0xc1, 0xc9, 0xe0, 0xfd, 0xbb, 0xa9, 0x67, 0xda, 0xe1, 0x6c, 0x1d,
0x79, 0x1b, 0x9a, 0x4f, 0x13, 0x94, 0x01, 0xc6, 0xdd, 0xaa, 0x09, 0x71, 0xee, 0xb4, 0x10, 0x99,
0x8f, 0x7b, 0x13, 0x3a, 0xe5, 0x38, 0x2f, 0xd4, 0xf0, 0x1f, 0x1d, 0x38, 0x5b, 0x42, 0x15, 0x4f,
0x05, 0x8f, 0x91, 0x9c, 0x87, 0xc6, 0x48, 0xb2, 0x08, 0xe3, 0xae, 0xb3, 0x59, 0xdd, 0xea, 0x50,
0xab, 0x91, 0x1d, 0x58, 0x89, 0x50, 0x31, 0x5f, 0x8f, 0x72, 0x9a, 0xdc, 0xb5, 0x53, 0x93, 0x4b,
0xc3, 0xf4, 0xf7, 0xac, 0x6f, 0x9a, 0x5e, 0xbe, 0xd4, 0xbd, 0x05, 0xab, 0x73, 0x9f, 0x5e, 0x08,
0xf1, 0x2f, 0x35, 0xe8, 0x0c, 0x59, 0x18, 0x52, 0x8c, 0x45, 0x22, 0x3d, 0x74, 0xaf, 0x02, 0x1c,
0x2a, 0x19, 0xf0, 0xf1, 0x83, 0x20, 0x56, 0x1a, 0xba, 0xf1, 0x4d, 0xa1, 0xb7, 0xa8, 0xd5, 0xdc,
0x9f, 0x2a, 0xd0, 0xfc, 0xa7, 0x2d, 0x25, 0x50, 0x9b, 0x32, 0x35, 0xb1, 0x58, 0x8c, 0xac, 0xf7,
0x89, 0x50, 0x4d, 0x84, 0x6f, 0xc9, 0xc3, 0x6a, 0x19, 0x55, 0xd4, 0x0a, 0xaa, 0xd8, 0x29, 0x06,
0xa2, 0x6e, 0x6a, 0xf6, 0x66, 0xb1, 0x5d, 0x39, 0x91, 0xfe, 0xf2, 0xa1, 0x20, 0x50, 0x3b, 0x12,
0xfe, 0xcc, 0xb0, 0x4d, 0x87, 0x1a, 0xd9, 0xfd, 0xec, 0xb9, 0x9d, 0x7f, 0xaf, 0x5c, 0xc7, 0xf6,
0xe0, 0xca, 0x82, 0xad, 0x8b, 0x02, 0x96, 0x4a, 0xed, 0xfe, 0xee, 0xc0, 0x4a, 0x3e, 0x13, 0x04,
0x6a, 0x9e, 0xf0, 0xd1, 0x04, 0xaf, 0x53, 0x23, 0x93, 0xdb, 0x27, 0x67, 0xfd, 0xad, 0x85, 0xa9,
0xd9, 0x91, 0x58, 0x9e, 0x5b, 0xf5, 0x3f, 0xcb, 0xad, 0xf7, 0x8d, 0x03, 0x6b, 0x43, 0x11, 0x86,
0xe8, 0xa9, 0x3d, 0x54, 0x32, 0xf0, 0x62, 0xb7, 0x95, 0x4f, 0x88, 0x7b, 0x0d, 0x9a, 0x07, 0x6c,
0x16, 0x0a, 0xe6, 0x6b, 0xee, 0x99, 0x4a, 0xa1, 0xbb, 0x8b, 0x49, 0x6c, 0xb6, 0xef, 0xd0, 0x92,
0xc5, 0xbd, 0x5d, 0xaa, 0xd1, 0x4d, 0x68, 0x46, 0x69, 0x30, 0x3b, 0x59, 0x9b, 0x25, 0x4c, 0x73,
0x9b, 0xf5, 0x6d, 0x78, 0x9a, 0x2d, 0xd0, 0x6c, 0xd9, 0x1e, 0x4e, 0xd0, 0x3b, 0xbe, 0x8b, 0x2c,
0x54, 0x93, 0x32, 0x9a, 0x6f, 0xcb, 0x7d, 0x18, 0x42, 0x23, 0x56, 0x4c, 0x59, 0x2c, 0x6b, 0x73,
0xd3, 0x54, 0x2c, 0x9f, 0xab, 0x78, 0xa8, 0x26, 0x87, 0x66, 0x09, 0xb5, 0x4b, 0x75, 0xc1, 0x03,
0x3e, 0x12, 0xd9, 0x44, 0x6b, 0xb9, 0xd7, 0x37, 0x05, 0xcf, 0x7d, 0x49, 0x1b, 0x9a, 0x4f, 0x1e,
0xee, 0x3e, 0xdc, 0xff, 0xe4, 0xe1, 0xfa, 0x2b, 0xa4, 0x01, 0x95, 0xfd, 0xdd, 0x75, 0x87, 0xb4,
0xa0, 0xbe, 0x43, 0xe9, 0x3e, 0x5d, 0xaf, 0xf4, 0xfe, 0x72, 0x60, 0xfd, 0x50, 0x49, 0x64, 0x51,
0xc0, 0xc7, 0xd9, 0xd1, 0x72, 0xc1, 0xde, 0xd8, 0xf7, 0xb2, 0xdb, 0x37, 0xd7, 0x97, 0x32, 0xe3,
0xc9, 0x40, 0x0b, 0x06, 0x65, 0x03, 0xea, 0x4a, 0x4c, 0x03, 0xcf, 0x1e, 0xba, 0x54, 0xc9, 0xc7,
0xc7, 0x5e, 0xd9, 0x5a, 0xd6, 0x6d, 0x8b, 0x93, 0xa3, 0xd8, 0x93, 0xc1, 0x11, 0xfa, 0x86, 0xe5,
0x57, 0x68, 0xc9, 0xf2, 0xaf, 0x48, 0xf3, 0xf3, 0x52, 0xe2, 0x7b, 0x18, 0xc7, 0x6c, 0x8c, 0x4b,
0x13, 0xcf, 0x51, 0x57, 0xca, 0xa8, 0xbb, 0x7a, 0x58, 0xcc, 0x62, 0x9b, 0x4d, 0xa6, 0xf6, 0x5e,
0x87, 0xb5, 0x3c, 0xfe, 0x30, 0x14, 0x31, 0xea, 0x08, 0x9e, 0x48, 0xb8, 0xb2, 0xa1, 0x53, 0xa5,
0xf7, 0xa7, 0x03, 0xab, 0x14, 0xb9, 0x8f, 0x32, 0x2b, 0xbf, 0x65, 0x1f, 0xa7, 0x60, 0x9f, 0x0d,
0xa8, 0x7f, 0x19, 0xf8, 0x96, 0xbc, 0xea, 0x34, 0x55, 0x34, 0x7b, 0x4d, 0x30, 0x18, 0x4f, 0x94,
0xd9, 0xba, 0x4e, 0xad, 0xa6, 0x31, 0xe9, 0x2b, 0x52, 0x24, 0xca, 0x14, 0xb3, 0x4e, 0x33, 0x55,
0xe7, 0xa7, 0xc5, 0xaf, 0x05, 0x47, 0xfb, 0x0e, 0xca, 0x75, 0xfd, 0x0d, 0xb9, 0x27, 0xfc, 0x80,
0x8f, 0xb3, 0xc7, 0x50, 0xa6, 0xeb, 0x6f, 0xa3, 0x20, 0xc4, 0x03, 0xcd, 0x9f, 0xcd, 0xf4, 0x5b,
0xa6, 0xeb, 0xe7, 0x83, 0x34, 0xf0, 0x77, 0x71, 0xd6, 0x5d, 0x31, 0x1f, 0x0b, 0x83, 0xc6, 0xe8,
0x8b, 0x88, 0x05, 0xbc, 0xdb, 0x4a, 0x19, 0x36, 0xd5, 0x74, 0x75, 0xb2, 0xa4, 0xed, 0x91, 0xd8,
0x80, 0x3a, 0x4a, 0x29, 0x64, 0xf6, 0xb4, 0x30, 0xca, 0xe0, 0x3b, 0x07, 0x6a, 0x43, 0x21, 0x91,
0xec, 0xcf, 0x5f, 0x18, 0xe4, 0xf2, 0x72, 0xfe, 0x75, 0x5f, 0x7b, 0x0e, 0x89, 0x91, 0xed, 0xf2,
0xbb, 0xc6, 0x5d, 0x7c, 0xbd, 0xbb, 0x17, 0x97, 0xdc, 0x8e, 0x83, 0x9f, 0x1d, 0x68, 0x6f, 0x07,
0x6c, 0xcc, 0x45, 0xac, 0x02, 0x2f, 0x26, 0x4f, 0x4e, 0x12, 0x12, 0x59, 0xcc, 0x1e, 0xd9, 0x06,
0x57, 0x96, 0x78, 0x58, 0xb0, 0x0f, 0xe6, 0x68, 0x85, 0xbc, 0xba, 0x88, 0x2e, 0xd2, 0x80, 0x97,
0x97, 0xb3, 0xc9, 0xe0, 0x7b, 0x07, 0xce, 0xdc, 0x91, 0x6c, 0xc4, 0x38, 0x3b, 0x08, 0x99, 0x1a,
0x09, 0x19, 0x91, 0x5d, 0xc3, 0x4e, 0xff, 0xab, 0xda, 0x3e, 0x82, 0xd6, 0x63, 0xc9, 0x78, 0x6c,
0xf0, 0xbd, 0x9c, 0x90, 0x9f, 0xc2, 0xd9, 0x3c, 0xa4, 0x86, 0xfe, 0x31, 0xf3, 0x8e, 0x5f, 0x52,
0xe8, 0xdf, 0x1c, 0x38, 0x93, 0x1f, 0xf8, 0xf4, 0x05, 0x42, 0x3e, 0x84, 0xe6, 0x50, 0x70, 0x8e,
0x9e, 0x22, 0x0b, 0x9e, 0x27, 0xae, 0x7b, 0x0a, 0x7d, 0x5a, 0x3a, 0x7a, 0xc7, 0xd1, 0x6c, 0x7b,
0x20, 0x85, 0x87, 0x71, 0x4c, 0xdc, 0xc5, 0x3c, 0xbb, 0x2c, 0x08, 0xf9, 0x00, 0x60, 0x3b, 0x88,
0xbd, 0x1c, 0x46, 0xfa, 0x07, 0xd6, 0xcf, 0xfe, 0xc0, 0xfa, 0x3b, 0xfa, 0x0f, 0xcc, 0xed, 0x9e,
0x12, 0xc1, 0xb0, 0xd6, 0xe0, 0x8e, 0x1e, 0x0c, 0x7d, 0x52, 0x51, 0x92, 0x5b, 0xd0, 0x48, 0x65,
0x72, 0xa1, 0xf0, 0x9f, 0x23, 0xaf, 0x72, 0xa0, 0xf9, 0x03, 0x7e, 0xd4, 0x30, 0x5b, 0xde, 0xf8,
0x3b, 0x00, 0x00, 0xff, 0xff, 0x85, 0x4e, 0xfe, 0x13, 0x1d, 0x0e, 0x00, 0x00,
}
// Reference imports to suppress errors if they are not otherwise used.
@ -1178,7 +1259,7 @@ const _ = grpc.SupportPackageIsVersion4
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.
type CoreClient interface {
// HTTP Style request
Resource(ctx context.Context, in *ResourceRequest, opts ...grpc.CallOption) (*ResourceResponse, error)
CallResource(ctx context.Context, in *CallResource_Request, opts ...grpc.CallOption) (*CallResource_Response, error)
// Well typed query interface
DataQuery(ctx context.Context, in *DataQueryRequest, opts ...grpc.CallOption) (*DataQueryResponse, error)
}
@ -1191,9 +1272,9 @@ func NewCoreClient(cc *grpc.ClientConn) CoreClient {
return &coreClient{cc}
}
func (c *coreClient) Resource(ctx context.Context, in *ResourceRequest, opts ...grpc.CallOption) (*ResourceResponse, error) {
out := new(ResourceResponse)
err := c.cc.Invoke(ctx, "/pluginv2.Core/Resource", in, out, opts...)
func (c *coreClient) CallResource(ctx context.Context, in *CallResource_Request, opts ...grpc.CallOption) (*CallResource_Response, error) {
out := new(CallResource_Response)
err := c.cc.Invoke(ctx, "/pluginv2.Core/CallResource", in, out, opts...)
if err != nil {
return nil, err
}
@ -1212,7 +1293,7 @@ func (c *coreClient) DataQuery(ctx context.Context, in *DataQueryRequest, opts .
// CoreServer is the server API for Core service.
type CoreServer interface {
// HTTP Style request
Resource(context.Context, *ResourceRequest) (*ResourceResponse, error)
CallResource(context.Context, *CallResource_Request) (*CallResource_Response, error)
// Well typed query interface
DataQuery(context.Context, *DataQueryRequest) (*DataQueryResponse, error)
}
@ -1221,8 +1302,8 @@ type CoreServer interface {
type UnimplementedCoreServer struct {
}
func (*UnimplementedCoreServer) Resource(ctx context.Context, req *ResourceRequest) (*ResourceResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method Resource not implemented")
func (*UnimplementedCoreServer) CallResource(ctx context.Context, req *CallResource_Request) (*CallResource_Response, error) {
return nil, status.Errorf(codes.Unimplemented, "method CallResource not implemented")
}
func (*UnimplementedCoreServer) DataQuery(ctx context.Context, req *DataQueryRequest) (*DataQueryResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method DataQuery not implemented")
@ -1232,20 +1313,20 @@ func RegisterCoreServer(s *grpc.Server, srv CoreServer) {
s.RegisterService(&_Core_serviceDesc, srv)
}
func _Core_Resource_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ResourceRequest)
func _Core_CallResource_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(CallResource_Request)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(CoreServer).Resource(ctx, in)
return srv.(CoreServer).CallResource(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/pluginv2.Core/Resource",
FullMethod: "/pluginv2.Core/CallResource",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(CoreServer).Resource(ctx, req.(*ResourceRequest))
return srv.(CoreServer).CallResource(ctx, req.(*CallResource_Request))
}
return interceptor(ctx, in, info, handler)
}
@ -1273,8 +1354,8 @@ var _Core_serviceDesc = grpc.ServiceDesc{
HandlerType: (*CoreServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "Resource",
Handler: _Core_Resource_Handler,
MethodName: "CallResource",
Handler: _Core_CallResource_Handler,
},
{
MethodName: "DataQuery",
@ -1397,7 +1478,7 @@ var _Diagnostics_serviceDesc = grpc.ServiceDesc{
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.
type GrafanaPlatformClient interface {
Resource(ctx context.Context, in *ResourceRequest, opts ...grpc.CallOption) (*ResourceResponse, error)
Resource(ctx context.Context, in *CallResource_Request, opts ...grpc.CallOption) (*CallResource_Response, error)
DataQuery(ctx context.Context, in *DataQueryRequest, opts ...grpc.CallOption) (*DataQueryResponse, error)
}
@ -1409,8 +1490,8 @@ func NewGrafanaPlatformClient(cc *grpc.ClientConn) GrafanaPlatformClient {
return &grafanaPlatformClient{cc}
}
func (c *grafanaPlatformClient) Resource(ctx context.Context, in *ResourceRequest, opts ...grpc.CallOption) (*ResourceResponse, error) {
out := new(ResourceResponse)
func (c *grafanaPlatformClient) Resource(ctx context.Context, in *CallResource_Request, opts ...grpc.CallOption) (*CallResource_Response, error) {
out := new(CallResource_Response)
err := c.cc.Invoke(ctx, "/pluginv2.GrafanaPlatform/Resource", in, out, opts...)
if err != nil {
return nil, err
@ -1429,7 +1510,7 @@ func (c *grafanaPlatformClient) DataQuery(ctx context.Context, in *DataQueryRequ
// GrafanaPlatformServer is the server API for GrafanaPlatform service.
type GrafanaPlatformServer interface {
Resource(context.Context, *ResourceRequest) (*ResourceResponse, error)
Resource(context.Context, *CallResource_Request) (*CallResource_Response, error)
DataQuery(context.Context, *DataQueryRequest) (*DataQueryResponse, error)
}
@ -1437,7 +1518,7 @@ type GrafanaPlatformServer interface {
type UnimplementedGrafanaPlatformServer struct {
}
func (*UnimplementedGrafanaPlatformServer) Resource(ctx context.Context, req *ResourceRequest) (*ResourceResponse, error) {
func (*UnimplementedGrafanaPlatformServer) Resource(ctx context.Context, req *CallResource_Request) (*CallResource_Response, error) {
return nil, status.Errorf(codes.Unimplemented, "method Resource not implemented")
}
func (*UnimplementedGrafanaPlatformServer) DataQuery(ctx context.Context, req *DataQueryRequest) (*DataQueryResponse, error) {
@ -1449,7 +1530,7 @@ func RegisterGrafanaPlatformServer(s *grpc.Server, srv GrafanaPlatformServer) {
}
func _GrafanaPlatform_Resource_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ResourceRequest)
in := new(CallResource_Request)
if err := dec(in); err != nil {
return nil, err
}
@ -1461,7 +1542,7 @@ func _GrafanaPlatform_Resource_Handler(srv interface{}, ctx context.Context, dec
FullMethod: "/pluginv2.GrafanaPlatform/Resource",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(GrafanaPlatformServer).Resource(ctx, req.(*ResourceRequest))
return srv.(GrafanaPlatformServer).Resource(ctx, req.(*CallResource_Request))
}
return interceptor(ctx, in, info, handler)
}

View File

@ -1,201 +0,0 @@
#vendor
vendor/
# Created by .ignore support plugin (hsz.mobi)
coverage.txt
### Go template
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so
# Folders
_obj
_test
# Architecture specific extensions/prefixes
*.[568vq]
[568vq].out
*.cgo1.go
*.cgo2.c
_cgo_defun.c
_cgo_gotypes.go
_cgo_export.*
_testmain.go
*.exe
*.test
*.prof
### Windows template
# Windows image file caches
Thumbs.db
ehthumbs.db
# Folder config file
Desktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Windows Installer files
*.cab
*.msi
*.msm
*.msp
# Windows shortcuts
*.lnk
### Kate template
# Swap Files #
.*.kate-swp
.swp.*
### SublimeText template
# cache files for sublime text
*.tmlanguage.cache
*.tmPreferences.cache
*.stTheme.cache
# workspace files are user-specific
*.sublime-workspace
# project files should be checked into the repository, unless a significant
# proportion of contributors will probably not be using SublimeText
# *.sublime-project
# sftp configuration file
sftp-config.json
### Linux template
*~
# temporary files which can be created if a process still has a handle open of a deleted file
.fuse_hidden*
# KDE directory preferences
.directory
# Linux trash folder which might appear on any partition or disk
.Trash-*
### JetBrains template
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff:
.idea
.idea/tasks.xml
.idea/dictionaries
.idea/vcs.xml
.idea/jsLibraryMappings.xml
# Sensitive or high-churn files:
.idea/dataSources.ids
.idea/dataSources.xml
.idea/dataSources.local.xml
.idea/sqlDataSources.xml
.idea/dynamic.xml
.idea/uiDesigner.xml
# Gradle:
.idea/gradle.xml
.idea/libraries
# Mongo Explorer plugin:
.idea/mongoSettings.xml
## File-based project format:
*.iws
## Plugin-specific files:
# IntelliJ
/out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
### Xcode template
# Xcode
#
# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
## Build generated
build/
DerivedData/
## Various settings
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
xcuserdata/
## Other
*.moved-aside
*.xccheckout
*.xcscmblueprint
### Eclipse template
.metadata
bin/
tmp/
*.tmp
*.bak
*.swp
*~.nib
local.properties
.settings/
.loadpath
.recommenders
# Eclipse Core
.project
# External tool builders
.externalToolBuilders/
# Locally stored "Eclipse launch configurations"
*.launch
# PyDev specific (Python IDE for Eclipse)
*.pydevproject
# CDT-specific (C/C++ Development Tooling)
.cproject
# JDT-specific (Eclipse Java Development Tools)
.classpath
# Java annotation processor (APT)
.factorypath
# PDT-specific (PHP Development Tools)
.buildpath
# sbteclipse plugin
.target
# Tern plugin
.tern-project
# TeXlipse plugin
.texlipse
# STS (Spring Tool Suite)
.springBeans
# Code Recommenders
.recommenders/

View File

@ -1,24 +0,0 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
## [Unreleased]
## [1.2.0](https://github.com/grpc-ecosystem/go-grpc-prometheus/releases/tag/v1.2.0) - 2018-06-04
### Added
* Provide metrics object as `prometheus.Collector`, for conventional metric registration.
* Support non-default/global Prometheus registry.
* Allow configuring counters with `prometheus.CounterOpts`.
### Changed
* Remove usage of deprecated `grpc.Code()`.
* Remove usage of deprecated `grpc.Errorf` and replace with `status.Errorf`.
---
This changelog was started with version `v1.2.0`, for earlier versions refer to the respective [GitHub releases](https://github.com/grpc-ecosystem/go-grpc-prometheus/releases).

View File

@ -1,201 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@ -1,247 +0,0 @@
# Go gRPC Interceptors for Prometheus monitoring
[![Travis Build](https://travis-ci.org/grpc-ecosystem/go-grpc-prometheus.svg)](https://travis-ci.org/grpc-ecosystem/go-grpc-prometheus)
[![Go Report Card](https://goreportcard.com/badge/github.com/grpc-ecosystem/go-grpc-prometheus)](http://goreportcard.com/report/grpc-ecosystem/go-grpc-prometheus)
[![GoDoc](http://img.shields.io/badge/GoDoc-Reference-blue.svg)](https://godoc.org/github.com/grpc-ecosystem/go-grpc-prometheus)
[![SourceGraph](https://sourcegraph.com/github.com/grpc-ecosystem/go-grpc-prometheus/-/badge.svg)](https://sourcegraph.com/github.com/grpc-ecosystem/go-grpc-prometheus/?badge)
[![codecov](https://codecov.io/gh/grpc-ecosystem/go-grpc-prometheus/branch/master/graph/badge.svg)](https://codecov.io/gh/grpc-ecosystem/go-grpc-prometheus)
[![Apache 2.0 License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](LICENSE)
[Prometheus](https://prometheus.io/) monitoring for your [gRPC Go](https://github.com/grpc/grpc-go) servers and clients.
A sister implementation for [gRPC Java](https://github.com/grpc/grpc-java) (same metrics, same semantics) is in [grpc-ecosystem/java-grpc-prometheus](https://github.com/grpc-ecosystem/java-grpc-prometheus).
## Interceptors
[gRPC Go](https://github.com/grpc/grpc-go) recently acquired support for Interceptors, i.e. middleware that is executed
by a gRPC Server before the request is passed onto the user's application logic. It is a perfect way to implement
common patterns: auth, logging and... monitoring.
To use Interceptors in chains, please see [`go-grpc-middleware`](https://github.com/mwitkow/go-grpc-middleware).
## Usage
There are two types of interceptors: client-side and server-side. This package provides monitoring Interceptors for both.
### Server-side
```go
import "github.com/grpc-ecosystem/go-grpc-prometheus"
...
// Initialize your gRPC server's interceptor.
myServer := grpc.NewServer(
grpc.StreamInterceptor(grpc_prometheus.StreamServerInterceptor),
grpc.UnaryInterceptor(grpc_prometheus.UnaryServerInterceptor),
)
// Register your gRPC service implementations.
myservice.RegisterMyServiceServer(s.server, &myServiceImpl{})
// After all your registrations, make sure all of the Prometheus metrics are initialized.
grpc_prometheus.Register(myServer)
// Register Prometheus metrics handler.
http.Handle("/metrics", promhttp.Handler())
...
```
### Client-side
```go
import "github.com/grpc-ecosystem/go-grpc-prometheus"
...
clientConn, err = grpc.Dial(
address,
grpc.WithUnaryInterceptor(grpc_prometheus.UnaryClientInterceptor),
grpc.WithStreamInterceptor(grpc_prometheus.StreamClientInterceptor)
)
client = pb_testproto.NewTestServiceClient(clientConn)
resp, err := client.PingEmpty(s.ctx, &myservice.Request{Msg: "hello"})
...
```
# Metrics
## Labels
All server-side metrics start with `grpc_server` as Prometheus subsystem name. All client-side metrics start with `grpc_client`. Both of them have mirror-concepts. Similarly all methods
contain the same rich labels:
* `grpc_service` - the [gRPC service](http://www.grpc.io/docs/#defining-a-service) name, which is the combination of protobuf `package` and
the `grpc_service` section name. E.g. for `package = mwitkow.testproto` and
`service TestService` the label will be `grpc_service="mwitkow.testproto.TestService"`
* `grpc_method` - the name of the method called on the gRPC service. E.g.
`grpc_method="Ping"`
* `grpc_type` - the gRPC [type of request](http://www.grpc.io/docs/guides/concepts.html#rpc-life-cycle).
Differentiating between the two is important especially for latency measurements.
- `unary` is single request, single response RPC
- `client_stream` is a multi-request, single response RPC
- `server_stream` is a single request, multi-response RPC
- `bidi_stream` is a multi-request, multi-response RPC
Additionally for completed RPCs, the following labels are used:
* `grpc_code` - the human-readable [gRPC status code](https://github.com/grpc/grpc-go/blob/master/codes/codes.go).
The list of all statuses is to long, but here are some common ones:
- `OK` - means the RPC was successful
- `IllegalArgument` - RPC contained bad values
- `Internal` - server-side error not disclosed to the clients
## Counters
The counters and their up to date documentation is in [server_reporter.go](server_reporter.go) and [client_reporter.go](client_reporter.go)
the respective Prometheus handler (usually `/metrics`).
For the purpose of this documentation we will only discuss `grpc_server` metrics. The `grpc_client` ones contain mirror concepts.
For simplicity, let's assume we're tracking a single server-side RPC call of [`mwitkow.testproto.TestService`](examples/testproto/test.proto),
calling the method `PingList`. The call succeeds and returns 20 messages in the stream.
First, immediately after the server receives the call it will increment the
`grpc_server_started_total` and start the handling time clock (if histograms are enabled).
```jsoniq
grpc_server_started_total{grpc_method="PingList",grpc_service="mwitkow.testproto.TestService",grpc_type="server_stream"} 1
```
Then the user logic gets invoked. It receives one message from the client containing the request
(it's a `server_stream`):
```jsoniq
grpc_server_msg_received_total{grpc_method="PingList",grpc_service="mwitkow.testproto.TestService",grpc_type="server_stream"} 1
```
The user logic may return an error, or send multiple messages back to the client. In this case, on
each of the 20 messages sent back, a counter will be incremented:
```jsoniq
grpc_server_msg_sent_total{grpc_method="PingList",grpc_service="mwitkow.testproto.TestService",grpc_type="server_stream"} 20
```
After the call completes, its status (`OK` or other [gRPC status code](https://github.com/grpc/grpc-go/blob/master/codes/codes.go))
and the relevant call labels increment the `grpc_server_handled_total` counter.
```jsoniq
grpc_server_handled_total{grpc_code="OK",grpc_method="PingList",grpc_service="mwitkow.testproto.TestService",grpc_type="server_stream"} 1
```
## Histograms
[Prometheus histograms](https://prometheus.io/docs/concepts/metric_types/#histogram) are a great way
to measure latency distributions of your RPCs. However, since it is bad practice to have metrics
of [high cardinality](https://prometheus.io/docs/practices/instrumentation/#do-not-overuse-labels)
the latency monitoring metrics are disabled by default. To enable them please call the following
in your server initialization code:
```jsoniq
grpc_prometheus.EnableHandlingTimeHistogram()
```
After the call completes, its handling time will be recorded in a [Prometheus histogram](https://prometheus.io/docs/concepts/metric_types/#histogram)
variable `grpc_server_handling_seconds`. The histogram variable contains three sub-metrics:
* `grpc_server_handling_seconds_count` - the count of all completed RPCs by status and method
* `grpc_server_handling_seconds_sum` - cumulative time of RPCs by status and method, useful for
calculating average handling times
* `grpc_server_handling_seconds_bucket` - contains the counts of RPCs by status and method in respective
handling-time buckets. These buckets can be used by Prometheus to estimate SLAs (see [here](https://prometheus.io/docs/practices/histograms/))
The counter values will look as follows:
```jsoniq
grpc_server_handling_seconds_bucket{grpc_code="OK",grpc_method="PingList",grpc_service="mwitkow.testproto.TestService",grpc_type="server_stream",le="0.005"} 1
grpc_server_handling_seconds_bucket{grpc_code="OK",grpc_method="PingList",grpc_service="mwitkow.testproto.TestService",grpc_type="server_stream",le="0.01"} 1
grpc_server_handling_seconds_bucket{grpc_code="OK",grpc_method="PingList",grpc_service="mwitkow.testproto.TestService",grpc_type="server_stream",le="0.025"} 1
grpc_server_handling_seconds_bucket{grpc_code="OK",grpc_method="PingList",grpc_service="mwitkow.testproto.TestService",grpc_type="server_stream",le="0.05"} 1
grpc_server_handling_seconds_bucket{grpc_code="OK",grpc_method="PingList",grpc_service="mwitkow.testproto.TestService",grpc_type="server_stream",le="0.1"} 1
grpc_server_handling_seconds_bucket{grpc_code="OK",grpc_method="PingList",grpc_service="mwitkow.testproto.TestService",grpc_type="server_stream",le="0.25"} 1
grpc_server_handling_seconds_bucket{grpc_code="OK",grpc_method="PingList",grpc_service="mwitkow.testproto.TestService",grpc_type="server_stream",le="0.5"} 1
grpc_server_handling_seconds_bucket{grpc_code="OK",grpc_method="PingList",grpc_service="mwitkow.testproto.TestService",grpc_type="server_stream",le="1"} 1
grpc_server_handling_seconds_bucket{grpc_code="OK",grpc_method="PingList",grpc_service="mwitkow.testproto.TestService",grpc_type="server_stream",le="2.5"} 1
grpc_server_handling_seconds_bucket{grpc_code="OK",grpc_method="PingList",grpc_service="mwitkow.testproto.TestService",grpc_type="server_stream",le="5"} 1
grpc_server_handling_seconds_bucket{grpc_code="OK",grpc_method="PingList",grpc_service="mwitkow.testproto.TestService",grpc_type="server_stream",le="10"} 1
grpc_server_handling_seconds_bucket{grpc_code="OK",grpc_method="PingList",grpc_service="mwitkow.testproto.TestService",grpc_type="server_stream",le="+Inf"} 1
grpc_server_handling_seconds_sum{grpc_code="OK",grpc_method="PingList",grpc_service="mwitkow.testproto.TestService",grpc_type="server_stream"} 0.0003866430000000001
grpc_server_handling_seconds_count{grpc_code="OK",grpc_method="PingList",grpc_service="mwitkow.testproto.TestService",grpc_type="server_stream"} 1
```
## Useful query examples
Prometheus philosophy is to provide raw metrics to the monitoring system, and
let the aggregations be handled there. The verbosity of above metrics make it possible to have that
flexibility. Here's a couple of useful monitoring queries:
### request inbound rate
```jsoniq
sum(rate(grpc_server_started_total{job="foo"}[1m])) by (grpc_service)
```
For `job="foo"` (common label to differentiate between Prometheus monitoring targets), calculate the
rate of requests per second (1 minute window) for each gRPC `grpc_service` that the job has. Please note
how the `grpc_method` is being omitted here: all methods of a given gRPC service will be summed together.
### unary request error rate
```jsoniq
sum(rate(grpc_server_handled_total{job="foo",grpc_type="unary",grpc_code!="OK"}[1m])) by (grpc_service)
```
For `job="foo"`, calculate the per-`grpc_service` rate of `unary` (1:1) RPCs that failed, i.e. the
ones that didn't finish with `OK` code.
### unary request error percentage
```jsoniq
sum(rate(grpc_server_handled_total{job="foo",grpc_type="unary",grpc_code!="OK"}[1m])) by (grpc_service)
/
sum(rate(grpc_server_started_total{job="foo",grpc_type="unary"}[1m])) by (grpc_service)
* 100.0
```
For `job="foo"`, calculate the percentage of failed requests by service. It's easy to notice that
this is a combination of the two above examples. This is an example of a query you would like to
[alert on](https://prometheus.io/docs/alerting/rules/) in your system for SLA violations, e.g.
"no more than 1% requests should fail".
### average response stream size
```jsoniq
sum(rate(grpc_server_msg_sent_total{job="foo",grpc_type="server_stream"}[10m])) by (grpc_service)
/
sum(rate(grpc_server_started_total{job="foo",grpc_type="server_stream"}[10m])) by (grpc_service)
```
For `job="foo"` what is the `grpc_service`-wide `10m` average of messages returned for all `
server_stream` RPCs. This allows you to track the stream sizes returned by your system, e.g. allows
you to track when clients started to send "wide" queries that ret
Note the divisor is the number of started RPCs, in order to account for in-flight requests.
### 99%-tile latency of unary requests
```jsoniq
histogram_quantile(0.99,
sum(rate(grpc_server_handling_seconds_bucket{job="foo",grpc_type="unary"}[5m])) by (grpc_service,le)
)
```
For `job="foo"`, returns an 99%-tile [quantile estimation](https://prometheus.io/docs/practices/histograms/#quantiles)
of the handling time of RPCs per service. Please note the `5m` rate, this means that the quantile
estimation will take samples in a rolling `5m` window. When combined with other quantiles
(e.g. 50%, 90%), this query gives you tremendous insight into the responsiveness of your system
(e.g. impact of caching).
### percentage of slow unary queries (>250ms)
```jsoniq
100.0 - (
sum(rate(grpc_server_handling_seconds_bucket{job="foo",grpc_type="unary",le="0.25"}[5m])) by (grpc_service)
/
sum(rate(grpc_server_handling_seconds_count{job="foo",grpc_type="unary"}[5m])) by (grpc_service)
) * 100.0
```
For `job="foo"` calculate the by-`grpc_service` fraction of slow requests that took longer than `0.25`
seconds. This query is relatively complex, since the Prometheus aggregations use `le` (less or equal)
buckets, meaning that counting "fast" requests fractions is easier. However, simple maths helps.
This is an example of a query you would like to alert on in your system for SLA violations,
e.g. "less than 1% of requests are slower than 250ms".
## Status
This code has been used since August 2015 as the basis for monitoring of *production* gRPC micro services at [Improbable](https://improbable.io).
## License
`go-grpc-prometheus` is released under the Apache 2.0 license. See the [LICENSE](LICENSE) file for details.

View File

@ -1,39 +0,0 @@
// Copyright 2016 Michal Witkowski. All Rights Reserved.
// See LICENSE for licensing terms.
// gRPC Prometheus monitoring interceptors for client-side gRPC.
package grpc_prometheus
import (
prom "github.com/prometheus/client_golang/prometheus"
)
var (
// DefaultClientMetrics is the default instance of ClientMetrics. It is
// intended to be used in conjunction the default Prometheus metrics
// registry.
DefaultClientMetrics = NewClientMetrics()
// UnaryClientInterceptor is a gRPC client-side interceptor that provides Prometheus monitoring for Unary RPCs.
UnaryClientInterceptor = DefaultClientMetrics.UnaryClientInterceptor()
// StreamClientInterceptor is a gRPC client-side interceptor that provides Prometheus monitoring for Streaming RPCs.
StreamClientInterceptor = DefaultClientMetrics.StreamClientInterceptor()
)
func init() {
prom.MustRegister(DefaultClientMetrics.clientStartedCounter)
prom.MustRegister(DefaultClientMetrics.clientHandledCounter)
prom.MustRegister(DefaultClientMetrics.clientStreamMsgReceived)
prom.MustRegister(DefaultClientMetrics.clientStreamMsgSent)
}
// EnableClientHandlingTimeHistogram turns on recording of handling time of
// RPCs. Histogram metrics can be very expensive for Prometheus to retain and
// query. This function acts on the DefaultClientMetrics variable and the
// default Prometheus metrics registry.
func EnableClientHandlingTimeHistogram(opts ...HistogramOption) {
DefaultClientMetrics.EnableClientHandlingTimeHistogram(opts...)
prom.Register(DefaultClientMetrics.clientHandledHistogram)
}

View File

@ -1,170 +0,0 @@
package grpc_prometheus
import (
"io"
prom "github.com/prometheus/client_golang/prometheus"
"golang.org/x/net/context"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
// ClientMetrics represents a collection of metrics to be registered on a
// Prometheus metrics registry for a gRPC client.
type ClientMetrics struct {
clientStartedCounter *prom.CounterVec
clientHandledCounter *prom.CounterVec
clientStreamMsgReceived *prom.CounterVec
clientStreamMsgSent *prom.CounterVec
clientHandledHistogramEnabled bool
clientHandledHistogramOpts prom.HistogramOpts
clientHandledHistogram *prom.HistogramVec
}
// NewClientMetrics returns a ClientMetrics object. Use a new instance of
// ClientMetrics when not using the default Prometheus metrics registry, for
// example when wanting to control which metrics are added to a registry as
// opposed to automatically adding metrics via init functions.
func NewClientMetrics(counterOpts ...CounterOption) *ClientMetrics {
opts := counterOptions(counterOpts)
return &ClientMetrics{
clientStartedCounter: prom.NewCounterVec(
opts.apply(prom.CounterOpts{
Name: "grpc_client_started_total",
Help: "Total number of RPCs started on the client.",
}), []string{"grpc_type", "grpc_service", "grpc_method"}),
clientHandledCounter: prom.NewCounterVec(
opts.apply(prom.CounterOpts{
Name: "grpc_client_handled_total",
Help: "Total number of RPCs completed by the client, regardless of success or failure.",
}), []string{"grpc_type", "grpc_service", "grpc_method", "grpc_code"}),
clientStreamMsgReceived: prom.NewCounterVec(
opts.apply(prom.CounterOpts{
Name: "grpc_client_msg_received_total",
Help: "Total number of RPC stream messages received by the client.",
}), []string{"grpc_type", "grpc_service", "grpc_method"}),
clientStreamMsgSent: prom.NewCounterVec(
opts.apply(prom.CounterOpts{
Name: "grpc_client_msg_sent_total",
Help: "Total number of gRPC stream messages sent by the client.",
}), []string{"grpc_type", "grpc_service", "grpc_method"}),
clientHandledHistogramEnabled: false,
clientHandledHistogramOpts: prom.HistogramOpts{
Name: "grpc_client_handling_seconds",
Help: "Histogram of response latency (seconds) of the gRPC until it is finished by the application.",
Buckets: prom.DefBuckets,
},
clientHandledHistogram: nil,
}
}
// Describe sends the super-set of all possible descriptors of metrics
// collected by this Collector to the provided channel and returns once
// the last descriptor has been sent.
func (m *ClientMetrics) Describe(ch chan<- *prom.Desc) {
m.clientStartedCounter.Describe(ch)
m.clientHandledCounter.Describe(ch)
m.clientStreamMsgReceived.Describe(ch)
m.clientStreamMsgSent.Describe(ch)
if m.clientHandledHistogramEnabled {
m.clientHandledHistogram.Describe(ch)
}
}
// Collect is called by the Prometheus registry when collecting
// metrics. The implementation sends each collected metric via the
// provided channel and returns once the last metric has been sent.
func (m *ClientMetrics) Collect(ch chan<- prom.Metric) {
m.clientStartedCounter.Collect(ch)
m.clientHandledCounter.Collect(ch)
m.clientStreamMsgReceived.Collect(ch)
m.clientStreamMsgSent.Collect(ch)
if m.clientHandledHistogramEnabled {
m.clientHandledHistogram.Collect(ch)
}
}
// EnableClientHandlingTimeHistogram turns on recording of handling time of RPCs.
// Histogram metrics can be very expensive for Prometheus to retain and query.
func (m *ClientMetrics) EnableClientHandlingTimeHistogram(opts ...HistogramOption) {
for _, o := range opts {
o(&m.clientHandledHistogramOpts)
}
if !m.clientHandledHistogramEnabled {
m.clientHandledHistogram = prom.NewHistogramVec(
m.clientHandledHistogramOpts,
[]string{"grpc_type", "grpc_service", "grpc_method"},
)
}
m.clientHandledHistogramEnabled = true
}
// UnaryClientInterceptor is a gRPC client-side interceptor that provides Prometheus monitoring for Unary RPCs.
func (m *ClientMetrics) UnaryClientInterceptor() func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
return func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
monitor := newClientReporter(m, Unary, method)
monitor.SentMessage()
err := invoker(ctx, method, req, reply, cc, opts...)
if err != nil {
monitor.ReceivedMessage()
}
st, _ := status.FromError(err)
monitor.Handled(st.Code())
return err
}
}
// StreamClientInterceptor is a gRPC client-side interceptor that provides Prometheus monitoring for Streaming RPCs.
func (m *ClientMetrics) StreamClientInterceptor() func(ctx context.Context, desc *grpc.StreamDesc, cc *grpc.ClientConn, method string, streamer grpc.Streamer, opts ...grpc.CallOption) (grpc.ClientStream, error) {
return func(ctx context.Context, desc *grpc.StreamDesc, cc *grpc.ClientConn, method string, streamer grpc.Streamer, opts ...grpc.CallOption) (grpc.ClientStream, error) {
monitor := newClientReporter(m, clientStreamType(desc), method)
clientStream, err := streamer(ctx, desc, cc, method, opts...)
if err != nil {
st, _ := status.FromError(err)
monitor.Handled(st.Code())
return nil, err
}
return &monitoredClientStream{clientStream, monitor}, nil
}
}
func clientStreamType(desc *grpc.StreamDesc) grpcType {
if desc.ClientStreams && !desc.ServerStreams {
return ClientStream
} else if !desc.ClientStreams && desc.ServerStreams {
return ServerStream
}
return BidiStream
}
// monitoredClientStream wraps grpc.ClientStream allowing each Sent/Recv of message to increment counters.
type monitoredClientStream struct {
grpc.ClientStream
monitor *clientReporter
}
func (s *monitoredClientStream) SendMsg(m interface{}) error {
err := s.ClientStream.SendMsg(m)
if err == nil {
s.monitor.SentMessage()
}
return err
}
func (s *monitoredClientStream) RecvMsg(m interface{}) error {
err := s.ClientStream.RecvMsg(m)
if err == nil {
s.monitor.ReceivedMessage()
} else if err == io.EOF {
s.monitor.Handled(codes.OK)
} else {
st, _ := status.FromError(err)
s.monitor.Handled(st.Code())
}
return err
}

View File

@ -1,46 +0,0 @@
// Copyright 2016 Michal Witkowski. All Rights Reserved.
// See LICENSE for licensing terms.
package grpc_prometheus
import (
"time"
"google.golang.org/grpc/codes"
)
type clientReporter struct {
metrics *ClientMetrics
rpcType grpcType
serviceName string
methodName string
startTime time.Time
}
func newClientReporter(m *ClientMetrics, rpcType grpcType, fullMethod string) *clientReporter {
r := &clientReporter{
metrics: m,
rpcType: rpcType,
}
if r.metrics.clientHandledHistogramEnabled {
r.startTime = time.Now()
}
r.serviceName, r.methodName = splitMethodName(fullMethod)
r.metrics.clientStartedCounter.WithLabelValues(string(r.rpcType), r.serviceName, r.methodName).Inc()
return r
}
func (r *clientReporter) ReceivedMessage() {
r.metrics.clientStreamMsgReceived.WithLabelValues(string(r.rpcType), r.serviceName, r.methodName).Inc()
}
func (r *clientReporter) SentMessage() {
r.metrics.clientStreamMsgSent.WithLabelValues(string(r.rpcType), r.serviceName, r.methodName).Inc()
}
func (r *clientReporter) Handled(code codes.Code) {
r.metrics.clientHandledCounter.WithLabelValues(string(r.rpcType), r.serviceName, r.methodName, code.String()).Inc()
if r.metrics.clientHandledHistogramEnabled {
r.metrics.clientHandledHistogram.WithLabelValues(string(r.rpcType), r.serviceName, r.methodName).Observe(time.Since(r.startTime).Seconds())
}
}

View File

@ -1,16 +0,0 @@
SHELL="/bin/bash"
GOFILES_NOVENDOR = $(shell go list ./... | grep -v /vendor/)
all: vet fmt test
fmt:
go fmt $(GOFILES_NOVENDOR)
vet:
go vet $(GOFILES_NOVENDOR)
test: vet
./scripts/test_all.sh
.PHONY: all vet test

View File

@ -1,41 +0,0 @@
package grpc_prometheus
import (
prom "github.com/prometheus/client_golang/prometheus"
)
// A CounterOption lets you add options to Counter metrics using With* funcs.
type CounterOption func(*prom.CounterOpts)
type counterOptions []CounterOption
func (co counterOptions) apply(o prom.CounterOpts) prom.CounterOpts {
for _, f := range co {
f(&o)
}
return o
}
// WithConstLabels allows you to add ConstLabels to Counter metrics.
func WithConstLabels(labels prom.Labels) CounterOption {
return func(o *prom.CounterOpts) {
o.ConstLabels = labels
}
}
// A HistogramOption lets you add options to Histogram metrics using With*
// funcs.
type HistogramOption func(*prom.HistogramOpts)
// WithHistogramBuckets allows you to specify custom bucket ranges for histograms if EnableHandlingTimeHistogram is on.
func WithHistogramBuckets(buckets []float64) HistogramOption {
return func(o *prom.HistogramOpts) { o.Buckets = buckets }
}
// WithHistogramConstLabels allows you to add custom ConstLabels to
// histograms metrics.
func WithHistogramConstLabels(labels prom.Labels) HistogramOption {
return func(o *prom.HistogramOpts) {
o.ConstLabels = labels
}
}

View File

@ -1,48 +0,0 @@
// Copyright 2016 Michal Witkowski. All Rights Reserved.
// See LICENSE for licensing terms.
// gRPC Prometheus monitoring interceptors for server-side gRPC.
package grpc_prometheus
import (
prom "github.com/prometheus/client_golang/prometheus"
"google.golang.org/grpc"
)
var (
// DefaultServerMetrics is the default instance of ServerMetrics. It is
// intended to be used in conjunction the default Prometheus metrics
// registry.
DefaultServerMetrics = NewServerMetrics()
// UnaryServerInterceptor is a gRPC server-side interceptor that provides Prometheus monitoring for Unary RPCs.
UnaryServerInterceptor = DefaultServerMetrics.UnaryServerInterceptor()
// StreamServerInterceptor is a gRPC server-side interceptor that provides Prometheus monitoring for Streaming RPCs.
StreamServerInterceptor = DefaultServerMetrics.StreamServerInterceptor()
)
func init() {
prom.MustRegister(DefaultServerMetrics.serverStartedCounter)
prom.MustRegister(DefaultServerMetrics.serverHandledCounter)
prom.MustRegister(DefaultServerMetrics.serverStreamMsgReceived)
prom.MustRegister(DefaultServerMetrics.serverStreamMsgSent)
}
// Register takes a gRPC server and pre-initializes all counters to 0. This
// allows for easier monitoring in Prometheus (no missing metrics), and should
// be called *after* all services have been registered with the server. This
// function acts on the DefaultServerMetrics variable.
func Register(server *grpc.Server) {
DefaultServerMetrics.InitializeMetrics(server)
}
// EnableHandlingTimeHistogram turns on recording of handling time
// of RPCs. Histogram metrics can be very expensive for Prometheus
// to retain and query. This function acts on the DefaultServerMetrics
// variable and the default Prometheus metrics registry.
func EnableHandlingTimeHistogram(opts ...HistogramOption) {
DefaultServerMetrics.EnableHandlingTimeHistogram(opts...)
prom.Register(DefaultServerMetrics.serverHandledHistogram)
}

View File

@ -1,185 +0,0 @@
package grpc_prometheus
import (
prom "github.com/prometheus/client_golang/prometheus"
"golang.org/x/net/context"
"google.golang.org/grpc"
"google.golang.org/grpc/status"
)
// ServerMetrics represents a collection of metrics to be registered on a
// Prometheus metrics registry for a gRPC server.
type ServerMetrics struct {
serverStartedCounter *prom.CounterVec
serverHandledCounter *prom.CounterVec
serverStreamMsgReceived *prom.CounterVec
serverStreamMsgSent *prom.CounterVec
serverHandledHistogramEnabled bool
serverHandledHistogramOpts prom.HistogramOpts
serverHandledHistogram *prom.HistogramVec
}
// NewServerMetrics returns a ServerMetrics object. Use a new instance of
// ServerMetrics when not using the default Prometheus metrics registry, for
// example when wanting to control which metrics are added to a registry as
// opposed to automatically adding metrics via init functions.
func NewServerMetrics(counterOpts ...CounterOption) *ServerMetrics {
opts := counterOptions(counterOpts)
return &ServerMetrics{
serverStartedCounter: prom.NewCounterVec(
opts.apply(prom.CounterOpts{
Name: "grpc_server_started_total",
Help: "Total number of RPCs started on the server.",
}), []string{"grpc_type", "grpc_service", "grpc_method"}),
serverHandledCounter: prom.NewCounterVec(
opts.apply(prom.CounterOpts{
Name: "grpc_server_handled_total",
Help: "Total number of RPCs completed on the server, regardless of success or failure.",
}), []string{"grpc_type", "grpc_service", "grpc_method", "grpc_code"}),
serverStreamMsgReceived: prom.NewCounterVec(
opts.apply(prom.CounterOpts{
Name: "grpc_server_msg_received_total",
Help: "Total number of RPC stream messages received on the server.",
}), []string{"grpc_type", "grpc_service", "grpc_method"}),
serverStreamMsgSent: prom.NewCounterVec(
opts.apply(prom.CounterOpts{
Name: "grpc_server_msg_sent_total",
Help: "Total number of gRPC stream messages sent by the server.",
}), []string{"grpc_type", "grpc_service", "grpc_method"}),
serverHandledHistogramEnabled: false,
serverHandledHistogramOpts: prom.HistogramOpts{
Name: "grpc_server_handling_seconds",
Help: "Histogram of response latency (seconds) of gRPC that had been application-level handled by the server.",
Buckets: prom.DefBuckets,
},
serverHandledHistogram: nil,
}
}
// EnableHandlingTimeHistogram enables histograms being registered when
// registering the ServerMetrics on a Prometheus registry. Histograms can be
// expensive on Prometheus servers. It takes options to configure histogram
// options such as the defined buckets.
func (m *ServerMetrics) EnableHandlingTimeHistogram(opts ...HistogramOption) {
for _, o := range opts {
o(&m.serverHandledHistogramOpts)
}
if !m.serverHandledHistogramEnabled {
m.serverHandledHistogram = prom.NewHistogramVec(
m.serverHandledHistogramOpts,
[]string{"grpc_type", "grpc_service", "grpc_method"},
)
}
m.serverHandledHistogramEnabled = true
}
// Describe sends the super-set of all possible descriptors of metrics
// collected by this Collector to the provided channel and returns once
// the last descriptor has been sent.
func (m *ServerMetrics) Describe(ch chan<- *prom.Desc) {
m.serverStartedCounter.Describe(ch)
m.serverHandledCounter.Describe(ch)
m.serverStreamMsgReceived.Describe(ch)
m.serverStreamMsgSent.Describe(ch)
if m.serverHandledHistogramEnabled {
m.serverHandledHistogram.Describe(ch)
}
}
// Collect is called by the Prometheus registry when collecting
// metrics. The implementation sends each collected metric via the
// provided channel and returns once the last metric has been sent.
func (m *ServerMetrics) Collect(ch chan<- prom.Metric) {
m.serverStartedCounter.Collect(ch)
m.serverHandledCounter.Collect(ch)
m.serverStreamMsgReceived.Collect(ch)
m.serverStreamMsgSent.Collect(ch)
if m.serverHandledHistogramEnabled {
m.serverHandledHistogram.Collect(ch)
}
}
// UnaryServerInterceptor is a gRPC server-side interceptor that provides Prometheus monitoring for Unary RPCs.
func (m *ServerMetrics) UnaryServerInterceptor() func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
monitor := newServerReporter(m, Unary, info.FullMethod)
monitor.ReceivedMessage()
resp, err := handler(ctx, req)
st, _ := status.FromError(err)
monitor.Handled(st.Code())
if err == nil {
monitor.SentMessage()
}
return resp, err
}
}
// StreamServerInterceptor is a gRPC server-side interceptor that provides Prometheus monitoring for Streaming RPCs.
func (m *ServerMetrics) StreamServerInterceptor() func(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
return func(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
monitor := newServerReporter(m, streamRPCType(info), info.FullMethod)
err := handler(srv, &monitoredServerStream{ss, monitor})
st, _ := status.FromError(err)
monitor.Handled(st.Code())
return err
}
}
// InitializeMetrics initializes all metrics, with their appropriate null
// value, for all gRPC methods registered on a gRPC server. This is useful, to
// ensure that all metrics exist when collecting and querying.
func (m *ServerMetrics) InitializeMetrics(server *grpc.Server) {
serviceInfo := server.GetServiceInfo()
for serviceName, info := range serviceInfo {
for _, mInfo := range info.Methods {
preRegisterMethod(m, serviceName, &mInfo)
}
}
}
func streamRPCType(info *grpc.StreamServerInfo) grpcType {
if info.IsClientStream && !info.IsServerStream {
return ClientStream
} else if !info.IsClientStream && info.IsServerStream {
return ServerStream
}
return BidiStream
}
// monitoredStream wraps grpc.ServerStream allowing each Sent/Recv of message to increment counters.
type monitoredServerStream struct {
grpc.ServerStream
monitor *serverReporter
}
func (s *monitoredServerStream) SendMsg(m interface{}) error {
err := s.ServerStream.SendMsg(m)
if err == nil {
s.monitor.SentMessage()
}
return err
}
func (s *monitoredServerStream) RecvMsg(m interface{}) error {
err := s.ServerStream.RecvMsg(m)
if err == nil {
s.monitor.ReceivedMessage()
}
return err
}
// preRegisterMethod is invoked on Register of a Server, allowing all gRPC services labels to be pre-populated.
func preRegisterMethod(metrics *ServerMetrics, serviceName string, mInfo *grpc.MethodInfo) {
methodName := mInfo.Name
methodType := string(typeFromMethodInfo(mInfo))
// These are just references (no increments), as just referencing will create the labels but not set values.
metrics.serverStartedCounter.GetMetricWithLabelValues(methodType, serviceName, methodName)
metrics.serverStreamMsgReceived.GetMetricWithLabelValues(methodType, serviceName, methodName)
metrics.serverStreamMsgSent.GetMetricWithLabelValues(methodType, serviceName, methodName)
if metrics.serverHandledHistogramEnabled {
metrics.serverHandledHistogram.GetMetricWithLabelValues(methodType, serviceName, methodName)
}
for _, code := range allCodes {
metrics.serverHandledCounter.GetMetricWithLabelValues(methodType, serviceName, methodName, code.String())
}
}

View File

@ -1,46 +0,0 @@
// Copyright 2016 Michal Witkowski. All Rights Reserved.
// See LICENSE for licensing terms.
package grpc_prometheus
import (
"time"
"google.golang.org/grpc/codes"
)
type serverReporter struct {
metrics *ServerMetrics
rpcType grpcType
serviceName string
methodName string
startTime time.Time
}
func newServerReporter(m *ServerMetrics, rpcType grpcType, fullMethod string) *serverReporter {
r := &serverReporter{
metrics: m,
rpcType: rpcType,
}
if r.metrics.serverHandledHistogramEnabled {
r.startTime = time.Now()
}
r.serviceName, r.methodName = splitMethodName(fullMethod)
r.metrics.serverStartedCounter.WithLabelValues(string(r.rpcType), r.serviceName, r.methodName).Inc()
return r
}
func (r *serverReporter) ReceivedMessage() {
r.metrics.serverStreamMsgReceived.WithLabelValues(string(r.rpcType), r.serviceName, r.methodName).Inc()
}
func (r *serverReporter) SentMessage() {
r.metrics.serverStreamMsgSent.WithLabelValues(string(r.rpcType), r.serviceName, r.methodName).Inc()
}
func (r *serverReporter) Handled(code codes.Code) {
r.metrics.serverHandledCounter.WithLabelValues(string(r.rpcType), r.serviceName, r.methodName, code.String()).Inc()
if r.metrics.serverHandledHistogramEnabled {
r.metrics.serverHandledHistogram.WithLabelValues(string(r.rpcType), r.serviceName, r.methodName).Observe(time.Since(r.startTime).Seconds())
}
}

View File

@ -1,50 +0,0 @@
// Copyright 2016 Michal Witkowski. All Rights Reserved.
// See LICENSE for licensing terms.
package grpc_prometheus
import (
"strings"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
)
type grpcType string
const (
Unary grpcType = "unary"
ClientStream grpcType = "client_stream"
ServerStream grpcType = "server_stream"
BidiStream grpcType = "bidi_stream"
)
var (
allCodes = []codes.Code{
codes.OK, codes.Canceled, codes.Unknown, codes.InvalidArgument, codes.DeadlineExceeded, codes.NotFound,
codes.AlreadyExists, codes.PermissionDenied, codes.Unauthenticated, codes.ResourceExhausted,
codes.FailedPrecondition, codes.Aborted, codes.OutOfRange, codes.Unimplemented, codes.Internal,
codes.Unavailable, codes.DataLoss,
}
)
func splitMethodName(fullMethodName string) (string, string) {
fullMethodName = strings.TrimPrefix(fullMethodName, "/") // remove leading slash
if i := strings.Index(fullMethodName, "/"); i >= 0 {
return fullMethodName[:i], fullMethodName[i+1:]
}
return "unknown", "unknown"
}
func typeFromMethodInfo(mInfo *grpc.MethodInfo) grpcType {
if !mInfo.IsClientStream && !mInfo.IsServerStream {
return Unary
}
if mInfo.IsClientStream && !mInfo.IsServerStream {
return ClientStream
}
if !mInfo.IsClientStream && mInfo.IsServerStream {
return ServerStream
}
return BidiStream
}

6
vendor/modules.txt vendored
View File

@ -148,12 +148,10 @@ github.com/gosimple/slug
# github.com/grafana/grafana-plugin-model v0.0.0-20190930120109-1fc953a61fb4
github.com/grafana/grafana-plugin-model/go/datasource
github.com/grafana/grafana-plugin-model/go/renderer
# github.com/grafana/grafana-plugin-sdk-go v0.11.0
github.com/grafana/grafana-plugin-sdk-go/backend
# github.com/grafana/grafana-plugin-sdk-go v0.14.0
github.com/grafana/grafana-plugin-sdk-go/backend/plugin
github.com/grafana/grafana-plugin-sdk-go/dataframe
github.com/grafana/grafana-plugin-sdk-go/genproto/pluginv2
# github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0
github.com/grpc-ecosystem/go-grpc-prometheus
# github.com/hashicorp/go-hclog v0.8.0
github.com/hashicorp/go-hclog
# github.com/hashicorp/go-plugin v1.0.1