Backend Plugins: Collect and expose metrics and plugin process health check (#21481)

Adds support for collecting metrics from backend plugins and 
exposing them thru Grafana's Prometheus metrics endpoint. 
Enables to check health of backend plugin by using the route
`/api/plugins/<plugin id>/health`.
Uses sdk v0.6.0.

Closes #20984
This commit is contained in:
Marcus Efraimsson 2020-01-15 13:10:48 +01:00 committed by GitHub
parent f56f54b1a3
commit 5c711bfb79
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
34 changed files with 2406 additions and 623 deletions

3
go.mod
View File

@ -31,7 +31,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.5.0
github.com/grafana/grafana-plugin-sdk-go v0.6.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
@ -74,6 +74,7 @@ require (
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e
golang.org/x/tools v0.0.0-20191213221258-04c2e8eff935 // indirect
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898
google.golang.org/grpc v1.23.1
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d // indirect
gopkg.in/ini.v1 v1.46.0

6
go.sum
View File

@ -123,8 +123,10 @@ 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.5.0 h1:qe60CcvhWn0H+CNoXgD6527GXcIJ78dU+pv1PmJypM8=
github.com/grafana/grafana-plugin-sdk-go v0.5.0/go.mod h1:78zFGQy3wwAtNE5imCeXklnD1xJ1VtIhF4JmxUIrwrw=
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/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=
github.com/hashicorp/go-hclog v0.8.0 h1:z3ollgGRg8RjfJH6UVBaG54R70GFd++QOkvnJH3VSBY=
github.com/hashicorp/go-hclog v0.8.0/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=

View File

@ -148,6 +148,8 @@ var (
// grafanaBuildVersion is a metric with a constant '1' value labeled by version, revision, branch, and goversion from which Grafana was built
grafanaBuildVersion *prometheus.GaugeVec
grafanPluginBuildInfoDesc *prometheus.GaugeVec
)
func init() {
@ -422,6 +424,12 @@ func init() {
Help: "A metric with a constant '1' value labeled by version, revision, branch, and goversion from which Grafana was built",
Namespace: exporterName,
}, []string{"version", "revision", "branch", "goversion", "edition"})
grafanPluginBuildInfoDesc = prometheus.NewGaugeVec(prometheus.GaugeOpts{
Name: "plugin_build_info",
Help: "A metric with a constant '1' value labeled by pluginId, pluginType and version from which Grafana plugin was built",
Namespace: exporterName,
}, []string{"plugin_id", "plugin_type", "version"})
}
// SetBuildInformation sets the build information for this binary
@ -434,6 +442,10 @@ func SetBuildInformation(version, revision, branch string) {
grafanaBuildVersion.WithLabelValues(version, revision, branch, runtime.Version(), edition).Set(1)
}
func SetPluginBuildInformation(pluginID, pluginType, version string) {
grafanPluginBuildInfoDesc.WithLabelValues(pluginID, pluginType, version).Set(1)
}
func initMetricVars() {
prometheus.MustRegister(
MInstanceStart,
@ -480,6 +492,7 @@ func initMetricVars() {
StatsTotalActiveEditors,
StatsTotalActiveAdmins,
grafanaBuildVersion,
grafanPluginBuildInfoDesc,
)
}

View File

@ -1,14 +1,25 @@
package backendplugin
import (
"bytes"
"context"
"errors"
"fmt"
"github.com/grafana/grafana-plugin-sdk-go/genproto/pluginv2"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/common/expfmt"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
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"
plugin "github.com/hashicorp/go-plugin"
dto "github.com/prometheus/client_model/go"
)
// BackendPlugin a registered backend plugin.
@ -20,6 +31,7 @@ type BackendPlugin struct {
client *plugin.Client
logger log.Logger
startFns PluginStartFuncs
diagnostics backend.DiagnosticsPlugin
}
func (p *BackendPlugin) start(ctx context.Context) error {
@ -33,6 +45,11 @@ func (p *BackendPlugin) start(ctx context.Context) error {
var client *Client
if p.client.NegotiatedVersion() > 1 {
rawDiagnostics, err := rpcClient.Dispense("diagnostics")
if err != nil {
return err
}
rawBackend, err := rpcClient.Dispense("backend")
if err != nil {
return err
@ -43,6 +60,12 @@ func (p *BackendPlugin) start(ctx context.Context) error {
return err
}
if rawDiagnostics != nil {
if plugin, ok := rawDiagnostics.(backend.DiagnosticsPlugin); ok {
p.diagnostics = plugin
}
}
client = &Client{}
if rawBackend != nil {
if plugin, ok := rawBackend.(backend.BackendPlugin); ok {
@ -96,3 +119,186 @@ func (p *BackendPlugin) stop() error {
}
return nil
}
// supportsDiagnostics return whether backend plugin supports diagnostics like metrics and health check.
func (p *BackendPlugin) supportsDiagnostics() bool {
return p.diagnostics != nil
}
// CollectMetrics implements the collector.Collector interface.
func (p *BackendPlugin) CollectMetrics(ctx context.Context, ch chan<- prometheus.Metric) error {
if p.diagnostics == nil {
return nil
}
if p.client == nil || p.client.Exited() {
return nil
}
res, err := p.diagnostics.CollectMetrics(ctx, &pluginv2.CollectMetrics_Request{})
if err != nil {
if st, ok := status.FromError(err); ok {
if st.Code() == codes.Unimplemented {
return nil
}
}
return err
}
if res == nil || res.Metrics == nil || res.Metrics.Prometheus == nil {
return nil
}
reader := bytes.NewReader(res.Metrics.Prometheus)
var parser expfmt.TextParser
families, err := parser.TextToMetricFamilies(reader)
if err != nil {
return errutil.Wrap("failed to parse collected metrics", err)
}
for _, mf := range families {
if mf.Help == nil {
help := fmt.Sprintf("Metric read from %s plugin", p.id)
mf.Help = &help
}
}
for _, mf := range families {
convertMetricFamily(p.id, mf, ch, p.logger)
}
return nil
}
func (p *BackendPlugin) checkHealth(ctx context.Context) (*pluginv2.CheckHealth_Response, error) {
if p.diagnostics == nil || p.client == nil || p.client.Exited() {
return &pluginv2.CheckHealth_Response{
Status: pluginv2.CheckHealth_Response_UNKNOWN,
}, nil
}
res, err := p.diagnostics.CheckHealth(ctx, &pluginv2.CheckHealth_Request{})
if err != nil {
if st, ok := status.FromError(err); ok {
if st.Code() == codes.Unimplemented {
return &pluginv2.CheckHealth_Response{
Status: pluginv2.CheckHealth_Response_UNKNOWN,
Info: "Health check not implemented",
}, nil
}
}
return nil, err
}
return res, 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) {
var valType prometheus.ValueType
var val float64
allLabelNames := map[string]struct{}{}
for _, metric := range metricFamily.Metric {
labels := metric.GetLabel()
for _, label := range labels {
if _, ok := allLabelNames[label.GetName()]; !ok {
allLabelNames[label.GetName()] = struct{}{}
}
}
}
for _, metric := range metricFamily.Metric {
if metric.TimestampMs != nil {
logger.Warn("Ignoring unsupported custom timestamp on metric", "metric", metric)
}
labels := metric.GetLabel()
var names []string
var values []string
for _, label := range labels {
names = append(names, label.GetName())
values = append(values, label.GetValue())
}
names = append(names, "plugin_id")
values = append(values, pluginID)
for k := range allLabelNames {
present := false
for _, name := range names {
if k == name {
present = true
break
}
}
if !present {
names = append(names, k)
values = append(values, "")
}
}
metricName := prometheus.BuildFQName(collector.Namespace, "", *metricFamily.Name)
metricType := metricFamily.GetType()
switch metricType {
case dto.MetricType_COUNTER:
valType = prometheus.CounterValue
val = metric.Counter.GetValue()
case dto.MetricType_GAUGE:
valType = prometheus.GaugeValue
val = metric.Gauge.GetValue()
case dto.MetricType_UNTYPED:
valType = prometheus.UntypedValue
val = metric.Untyped.GetValue()
case dto.MetricType_SUMMARY:
quantiles := map[float64]float64{}
for _, q := range metric.Summary.Quantile {
quantiles[q.GetQuantile()] = q.GetValue()
}
ch <- prometheus.MustNewConstSummary(
prometheus.NewDesc(
metricName,
metricFamily.GetHelp(),
names, nil,
),
metric.Summary.GetSampleCount(),
metric.Summary.GetSampleSum(),
quantiles, values...,
)
case dto.MetricType_HISTOGRAM:
buckets := map[float64]uint64{}
for _, b := range metric.Histogram.Bucket {
buckets[b.GetUpperBound()] = b.GetCumulativeCount()
}
ch <- prometheus.MustNewConstHistogram(
prometheus.NewDesc(
metricName,
metricFamily.GetHelp(),
names, nil,
),
metric.Histogram.GetSampleCount(),
metric.Histogram.GetSampleSum(),
buckets, values...,
)
default:
logger.Error("unknown metric type", "type", metricType)
continue
}
if metricType == dto.MetricType_GAUGE || metricType == dto.MetricType_COUNTER || metricType == dto.MetricType_UNTYPED {
ch <- prometheus.MustNewConstMetric(
prometheus.NewDesc(
metricName,
metricFamily.GetHelp(),
names, nil,
),
valType, val, values...,
)
}
}
}

View File

@ -8,7 +8,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"
sdk "github.com/grafana/grafana-plugin-sdk-go/common"
"github.com/hashicorp/go-plugin"
)
@ -27,8 +26,8 @@ var handshake = plugin.HandshakeConfig{
ProtocolVersion: DefaultProtocolVersion,
// The magic cookie values should NEVER be changed.
MagicCookieKey: sdk.MagicCookieKey,
MagicCookieValue: sdk.MagicCookieValue,
MagicCookieKey: backend.MagicCookieKey,
MagicCookieValue: backend.MagicCookieValue,
}
func newClientConfig(executablePath string, logger log.Logger, versionedPlugins map[int]plugin.PluginSet) *plugin.ClientConfig {
@ -73,7 +72,8 @@ func NewBackendPluginDescriptor(pluginID, executablePath string, startFns Plugin
DefaultProtocolVersion: {
pluginID: &datasourceV1.DatasourcePluginImpl{},
},
sdk.ProtocolVersion: {
backend.ProtocolVersion: {
"diagnostics": &backend.DiagnosticsGRPCPlugin{},
"backend": &backend.CoreGRPCPlugin{},
"transform": &backend.TransformGRPCPlugin{},
},
@ -106,6 +106,7 @@ 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
}

View File

@ -0,0 +1,89 @@
package collector
import (
"context"
"sync"
"time"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/prometheus/client_golang/prometheus"
)
// Namespace collector metric namespace
const Namespace = "grafana_plugin"
var (
scrapeDurationDesc = prometheus.NewDesc(
prometheus.BuildFQName(Namespace, "scrape", "duration_seconds"),
"grafana_plugin: Duration of a plugin collector scrape.",
[]string{"plugin_id"},
nil,
)
scrapeSuccessDesc = prometheus.NewDesc(
prometheus.BuildFQName(Namespace, "scrape", "success"),
"grafana_plugin: Whether a plugin collector succeeded.",
[]string{"plugin_id"},
nil,
)
)
// Collector is the interface a plugin collector has to implement.
type Collector interface {
// Get new metrics and expose them via prometheus registry.
CollectMetrics(ctx context.Context, ch chan<- prometheus.Metric) error
}
// PluginCollector implements the prometheus.Collector interface.
type PluginCollector struct {
collectors map[string]Collector
logger log.Logger
}
// NewPluginCollector creates a new PluginCollector..
func NewPluginCollector() PluginCollector {
return PluginCollector{
collectors: make(map[string]Collector),
logger: log.New("plugins.backend.collector"),
}
}
func (pc PluginCollector) Register(pluginID string, c Collector) {
pc.collectors[pluginID] = c
}
// Describe implements the prometheus.Collector interface.
func (pc PluginCollector) Describe(ch chan<- *prometheus.Desc) {
ch <- scrapeDurationDesc
ch <- scrapeSuccessDesc
}
// Collect implements the prometheus.Collector interface.
func (pc PluginCollector) Collect(ch chan<- prometheus.Metric) {
ctx := context.Background()
wg := sync.WaitGroup{}
wg.Add(len(pc.collectors))
for name, c := range pc.collectors {
go func(name string, c Collector) {
execute(ctx, name, c, ch, pc.logger)
wg.Done()
}(name, c)
}
wg.Wait()
}
func execute(ctx context.Context, pluginID string, c Collector, ch chan<- prometheus.Metric, logger log.Logger) {
begin := time.Now()
err := c.CollectMetrics(ctx, ch)
duration := time.Since(begin)
var success float64
if err != nil {
logger.Error("collector failed", "pluginId", pluginID, "took", duration, "error", err)
success = 0
} else {
logger.Debug("collector succeeded", "pluginId", pluginID, "took", duration)
success = 1
}
ch <- prometheus.MustNewConstMetric(scrapeDurationDesc, prometheus.GaugeValue, duration.Seconds(), pluginID)
ch <- prometheus.MustNewConstMetric(scrapeSuccessDesc, prometheus.GaugeValue, success, pluginID)
}

View File

@ -6,7 +6,14 @@ 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"
@ -29,14 +36,21 @@ type Manager interface {
}
type manager struct {
RouteRegister routing.RouteRegister `inject:""`
pluginsMu sync.RWMutex
plugins map[string]*BackendPlugin
pluginCollector collector.PluginCollector
logger log.Logger
}
func (m *manager) Init() error {
m.plugins = make(map[string]*BackendPlugin)
m.logger = log.New("plugins.backend")
m.pluginCollector = collector.NewPluginCollector()
prometheus.MustRegister(m.pluginCollector)
m.RouteRegister.Get("/api/plugins/:pluginId/health", m.checkHealth)
return nil
}
@ -85,6 +99,12 @@ func (m *manager) start(ctx context.Context) {
if err := startPluginAndRestartKilledProcesses(ctx, p); err != nil {
p.logger.Error("Failed to start plugin", "error", err)
continue
}
if p.supportsDiagnostics() {
p.logger.Debug("Registering metrics collector")
m.pluginCollector.Register(p.id, p)
}
}
}
@ -120,6 +140,40 @@ func (m *manager) stop() {
}
}
// checkHealth http handler for checking plugin health.
func (m *manager) checkHealth(c *models.ReqContext) {
pluginID := c.Params("pluginId")
m.pluginsMu.RLock()
p, registered := m.plugins[pluginID]
m.pluginsMu.RUnlock()
if !registered || !p.supportsDiagnostics() {
c.JsonApiErr(404, "Plugin not found", nil)
return
}
res, err := p.checkHealth(c.Req.Context())
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
}
payload := map[string]interface{}{
"status": res.Status.String(),
"info": res.Info,
}
if res.Status != pluginv2.CheckHealth_Response_OK {
c.JSON(503, payload)
return
}
c.JSON(200, payload)
}
func startPluginAndRestartKilledProcesses(ctx context.Context, p *BackendPlugin) error {
if err := p.start(ctx); err != nil {
return err

View File

@ -14,6 +14,7 @@ import (
"time"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/infra/metrics"
"github.com/grafana/grafana/pkg/plugins/backendplugin"
"github.com/grafana/grafana/pkg/registry"
"github.com/grafana/grafana/pkg/setting"
@ -110,6 +111,12 @@ func (pm *PluginManager) Init() error {
app.initApp()
}
for _, p := range Plugins {
if !p.IsCorePlugin {
metrics.SetPluginBuildInformation(p.Id, p.Type, p.Info.Version)
}
}
return nil
}

View File

@ -191,42 +191,6 @@ func (rr *ResourceResponse) toProtobuf() *pluginv2.ResourceResponse {
}
}
// 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,
// }
// }
// DataQueryHandler handles data source queries.
type DataQueryHandler interface {
DataQuery(ctx context.Context, req *DataQueryRequest) (*DataQueryResponse, error)
@ -237,26 +201,15 @@ type ResourceHandler interface {
Resource(ctx context.Context, req *ResourceRequest) (*ResourceResponse, error)
}
type CheckHealthHandler interface {
CheckHealth(ctx context.Context) (*CheckHealthResult, error)
}
type DiagnosticsHandler interface {
CheckHealthHandler
}
// PluginHandlers is the collection of handlers that corresponds to the
// grpc "service BackendPlugin".
type PluginHandlers interface {
DiagnosticsHandler
DataQueryHandler
ResourceHandler
}
// BackendPlugin is the Grafana backend plugin interface.
type BackendPlugin 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)
DataQuery(ctx context.Context, req *pluginv2.DataQueryRequest) (*pluginv2.DataQueryResponse, error)
Resource(ctx context.Context, req *pluginv2.ResourceRequest) (*pluginv2.ResourceResponse, error)
}

View File

@ -8,16 +8,16 @@ import (
"google.golang.org/grpc"
)
// CoreGRPCPlugin implements the GRPPlugin interface from github.com/hashicorp/go-plugin.
// CoreGRPCPlugin implements the GRPCPlugin interface from github.com/hashicorp/go-plugin.
type CoreGRPCPlugin struct {
plugin.NetRPCUnsupportedPlugin
plugin.GRPCPlugin
adapter *sdkAdapter
server pluginv2.CoreServer
}
func (p *CoreGRPCPlugin) GRPCServer(broker *plugin.GRPCBroker, s *grpc.Server) error {
pluginv2.RegisterCoreServer(s, &coreGRPCServer{
adapter: p.adapter,
server: p.server,
})
return nil
}
@ -27,37 +27,21 @@ func (p *CoreGRPCPlugin) GRPCClient(ctx context.Context, broker *plugin.GRPCBrok
}
type coreGRPCServer struct {
adapter *sdkAdapter
server pluginv2.CoreServer
}
func (s *coreGRPCServer) CollectMetrics(ctx context.Context, req *pluginv2.CollectMetrics_Request) (*pluginv2.CollectMetrics_Response, error) {
return s.adapter.CollectMetrics(ctx, req)
func (s *coreGRPCServer) DataQuery(ctx context.Context, req *pluginv2.DataQueryRequest) (*pluginv2.DataQueryResponse, error) {
return s.server.DataQuery(ctx, req)
}
func (s *coreGRPCServer) CheckHealth(ctx context.Context, req *pluginv2.CheckHealth_Request) (*pluginv2.CheckHealth_Response, error) {
return s.adapter.CheckHealth(ctx, req)
}
func (m *coreGRPCServer) DataQuery(ctx context.Context, req *pluginv2.DataQueryRequest) (*pluginv2.DataQueryResponse, error) {
return m.adapter.DataQuery(ctx, req)
}
func (m *coreGRPCServer) Resource(ctx context.Context, req *pluginv2.ResourceRequest) (*pluginv2.ResourceResponse, error) {
return m.adapter.Resource(ctx, req)
func (s *coreGRPCServer) Resource(ctx context.Context, req *pluginv2.ResourceRequest) (*pluginv2.ResourceResponse, error) {
return s.server.Resource(ctx, req)
}
type coreGRPCClient struct {
client pluginv2.CoreClient
}
func (s *coreGRPCClient) CollectMetrics(ctx context.Context, req *pluginv2.CollectMetrics_Request) (*pluginv2.CollectMetrics_Response, error) {
return s.client.CollectMetrics(ctx, req)
}
func (s *coreGRPCClient) CheckHealth(ctx context.Context, req *pluginv2.CheckHealth_Request) (*pluginv2.CheckHealth_Response, error) {
return s.client.CheckHealth(ctx, req)
}
func (m *coreGRPCClient) DataQuery(ctx context.Context, req *pluginv2.DataQueryRequest) (*pluginv2.DataQueryResponse, error) {
return m.client.DataQuery(ctx, req)
}

View File

@ -1,61 +0,0 @@
package backend
import (
"bytes"
"context"
"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 {
handlers PluginHandlers
}
func (a *sdkAdapter) CollectMetrics(ctx context.Context, protoReq *pluginv2.CollectMetrics_Request) (*pluginv2.CollectMetrics_Response, error) {
metrics, err := prometheus.DefaultGatherer.Gather()
if err != nil {
return nil, err
}
var buf bytes.Buffer
for _, m := range metrics {
_, err := expfmt.MetricFamilyToText(&buf, m)
if err != nil {
continue
}
}
resp := &pluginv2.CollectMetrics_Response{
Metrics: &pluginv2.CollectMetrics_Payload{
Prometheus: buf.Bytes(),
},
}
return resp, nil
}
func (a *sdkAdapter) CheckHealth(ctx context.Context, protoReq *pluginv2.CheckHealth_Request) (*pluginv2.CheckHealth_Response, error) {
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.handlers.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.handlers.Resource(ctx, resourceRequestFromProtobuf(req))
if err != nil {
return nil, err
}
return res.toProtobuf(), nil
}

View File

@ -0,0 +1,53 @@
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

@ -0,0 +1,51 @@
package backend
import (
"context"
"github.com/grafana/grafana-plugin-sdk-go/genproto/pluginv2"
plugin "github.com/hashicorp/go-plugin"
"google.golang.org/grpc"
)
// DiagnosticsGRPCPlugin implements the GRPCPlugin interface from github.com/hashicorp/go-plugin.
type DiagnosticsGRPCPlugin struct {
plugin.NetRPCUnsupportedPlugin
plugin.GRPCPlugin
server pluginv2.DiagnosticsServer
}
func (p *DiagnosticsGRPCPlugin) GRPCServer(broker *plugin.GRPCBroker, s *grpc.Server) error {
pluginv2.RegisterDiagnosticsServer(s, &diagnosticsGRPCServer{
server: p.server,
})
return nil
}
func (p *DiagnosticsGRPCPlugin) GRPCClient(ctx context.Context, broker *plugin.GRPCBroker, c *grpc.ClientConn) (interface{}, error) {
return &diagnosticsGRPCClient{client: pluginv2.NewDiagnosticsClient(c)}, nil
}
type diagnosticsGRPCServer struct {
server pluginv2.DiagnosticsServer
}
func (s *diagnosticsGRPCServer) CollectMetrics(ctx context.Context, req *pluginv2.CollectMetrics_Request) (*pluginv2.CollectMetrics_Response, error) {
return s.server.CollectMetrics(ctx, req)
}
func (s *diagnosticsGRPCServer) CheckHealth(ctx context.Context, req *pluginv2.CheckHealth_Request) (*pluginv2.CheckHealth_Response, error) {
return s.server.CheckHealth(ctx, req)
}
type diagnosticsGRPCClient struct {
client pluginv2.DiagnosticsClient
}
func (s *diagnosticsGRPCClient) CollectMetrics(ctx context.Context, req *pluginv2.CollectMetrics_Request) (*pluginv2.CollectMetrics_Response, error) {
return s.client.CollectMetrics(ctx, req)
}
func (s *diagnosticsGRPCClient) CheckHealth(ctx context.Context, req *pluginv2.CheckHealth_Request) (*pluginv2.CheckHealth_Response, error) {
return s.client.CheckHealth(ctx, req)
}

View File

@ -1,4 +1,4 @@
package common
package backend
import plugin "github.com/hashicorp/go-plugin"

View File

@ -0,0 +1,91 @@
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,37 +1,68 @@
package backend
import (
"github.com/grafana/grafana-plugin-sdk-go/common"
grpc_prometheus "github.com/grpc-ecosystem/go-grpc-prometheus"
plugin "github.com/hashicorp/go-plugin"
"google.golang.org/grpc"
)
// Serve starts serving the datasource plugin over gRPC.
func Serve(backendHandlers PluginHandlers, transformHandlers TransformHandlers) error {
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)
if backendHandlers != nil {
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{
adapter: &sdkAdapter{
handlers: backendHandlers,
},
server: sdkAdapter,
}
}
if transformHandlers != nil {
if opts.TransformDataHandler != nil {
pSet["transform"] = &TransformGRPCPlugin{
adapter: &transformSDKAdapter{
handlers: transformHandlers,
},
adapter: sdkAdapter,
}
}
versionedPlugins[common.ProtocolVersion] = pSet
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: common.Handshake,
HandshakeConfig: Handshake,
VersionedPlugins: versionedPlugins,
GRPCServer: plugin.DefaultGRPCServer,
GRPCServer: opts.GRPCServer,
})
return nil

View File

@ -7,11 +7,11 @@ import (
)
type TransformHandlers interface {
TransformDataQueryHandler
TransformDataHandler
}
type TransformDataQueryHandler interface {
DataQuery(ctx context.Context, req *DataQueryRequest, callBack TransformCallBackHandler) (*DataQueryResponse, error)
type TransformDataHandler interface {
TransformData(ctx context.Context, req *DataQueryRequest, callBack TransformCallBackHandler) (*DataQueryResponse, error)
}
// Callback

View File

@ -15,7 +15,7 @@ import (
type TransformGRPCPlugin struct {
plugin.NetRPCUnsupportedPlugin
plugin.GRPCPlugin
adapter *transformSDKAdapter
adapter *sdkAdapter
}
func (p *TransformGRPCPlugin) GRPCServer(broker *plugin.GRPCBroker, s *grpc.Server) error {
@ -32,7 +32,7 @@ func (p *TransformGRPCPlugin) GRPCClient(ctx context.Context, broker *plugin.GRP
type transformGRPCServer struct {
broker *plugin.GRPCBroker
adapter *transformSDKAdapter
adapter *sdkAdapter
}
func (t *transformGRPCServer) DataQuery(ctx context.Context, req *pluginv2.DataQueryRequest) (*pluginv2.DataQueryResponse, error) {

View File

@ -1,33 +0,0 @@
package backend
import (
"context"
"github.com/grafana/grafana-plugin-sdk-go/dataframe"
"github.com/grafana/grafana-plugin-sdk-go/genproto/pluginv2"
)
// transformSDKAdapter adapter between protobuf and SDK interfaces.
type transformSDKAdapter struct {
handlers TransformHandlers
}
func (a *transformSDKAdapter) TransformData(ctx context.Context, req *pluginv2.DataQueryRequest, callBack TransformCallBack) (*pluginv2.DataQueryResponse, error) {
resp, err := a.handlers.DataQuery(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

@ -50,7 +50,7 @@ func (x CheckHealth_Response_HealthStatus) String() string {
}
func (CheckHealth_Response_HealthStatus) EnumDescriptor() ([]byte, []int) {
return fileDescriptor_5ab9ba5b8d8b2ba5, []int{2, 1, 0}
return fileDescriptor_5ab9ba5b8d8b2ba5, []int{8, 1, 0}
}
type PluginConfig struct {
@ -140,255 +140,6 @@ func (m *PluginConfig) GetDecryptedSecureJsonData() map[string]string {
return nil
}
type CollectMetrics struct {
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
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{1}
}
func (m *CollectMetrics) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_CollectMetrics.Unmarshal(m, b)
}
func (m *CollectMetrics) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_CollectMetrics.Marshal(b, m, deterministic)
}
func (m *CollectMetrics) XXX_Merge(src proto.Message) {
xxx_messageInfo_CollectMetrics.Merge(m, src)
}
func (m *CollectMetrics) XXX_Size() int {
return xxx_messageInfo_CollectMetrics.Size(m)
}
func (m *CollectMetrics) XXX_DiscardUnknown() {
xxx_messageInfo_CollectMetrics.DiscardUnknown(m)
}
var xxx_messageInfo_CollectMetrics proto.InternalMessageInfo
type CollectMetrics_Request struct {
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
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{1, 0}
}
func (m *CollectMetrics_Request) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_CollectMetrics_Request.Unmarshal(m, b)
}
func (m *CollectMetrics_Request) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_CollectMetrics_Request.Marshal(b, m, deterministic)
}
func (m *CollectMetrics_Request) XXX_Merge(src proto.Message) {
xxx_messageInfo_CollectMetrics_Request.Merge(m, src)
}
func (m *CollectMetrics_Request) XXX_Size() int {
return xxx_messageInfo_CollectMetrics_Request.Size(m)
}
func (m *CollectMetrics_Request) XXX_DiscardUnknown() {
xxx_messageInfo_CollectMetrics_Request.DiscardUnknown(m)
}
var xxx_messageInfo_CollectMetrics_Request proto.InternalMessageInfo
type CollectMetrics_Payload struct {
Prometheus []byte `protobuf:"bytes,1,opt,name=prometheus,proto3" json:"prometheus,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
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{1, 1}
}
func (m *CollectMetrics_Payload) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_CollectMetrics_Payload.Unmarshal(m, b)
}
func (m *CollectMetrics_Payload) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_CollectMetrics_Payload.Marshal(b, m, deterministic)
}
func (m *CollectMetrics_Payload) XXX_Merge(src proto.Message) {
xxx_messageInfo_CollectMetrics_Payload.Merge(m, src)
}
func (m *CollectMetrics_Payload) XXX_Size() int {
return xxx_messageInfo_CollectMetrics_Payload.Size(m)
}
func (m *CollectMetrics_Payload) XXX_DiscardUnknown() {
xxx_messageInfo_CollectMetrics_Payload.DiscardUnknown(m)
}
var xxx_messageInfo_CollectMetrics_Payload proto.InternalMessageInfo
func (m *CollectMetrics_Payload) GetPrometheus() []byte {
if m != nil {
return m.Prometheus
}
return nil
}
type CollectMetrics_Response struct {
Metrics *CollectMetrics_Payload `protobuf:"bytes,1,opt,name=metrics,proto3" json:"metrics,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
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{1, 2}
}
func (m *CollectMetrics_Response) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_CollectMetrics_Response.Unmarshal(m, b)
}
func (m *CollectMetrics_Response) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_CollectMetrics_Response.Marshal(b, m, deterministic)
}
func (m *CollectMetrics_Response) XXX_Merge(src proto.Message) {
xxx_messageInfo_CollectMetrics_Response.Merge(m, src)
}
func (m *CollectMetrics_Response) XXX_Size() int {
return xxx_messageInfo_CollectMetrics_Response.Size(m)
}
func (m *CollectMetrics_Response) XXX_DiscardUnknown() {
xxx_messageInfo_CollectMetrics_Response.DiscardUnknown(m)
}
var xxx_messageInfo_CollectMetrics_Response proto.InternalMessageInfo
func (m *CollectMetrics_Response) GetMetrics() *CollectMetrics_Payload {
if m != nil {
return m.Metrics
}
return nil
}
type CheckHealth struct {
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
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{2}
}
func (m *CheckHealth) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_CheckHealth.Unmarshal(m, b)
}
func (m *CheckHealth) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_CheckHealth.Marshal(b, m, deterministic)
}
func (m *CheckHealth) XXX_Merge(src proto.Message) {
xxx_messageInfo_CheckHealth.Merge(m, src)
}
func (m *CheckHealth) XXX_Size() int {
return xxx_messageInfo_CheckHealth.Size(m)
}
func (m *CheckHealth) XXX_DiscardUnknown() {
xxx_messageInfo_CheckHealth.DiscardUnknown(m)
}
var xxx_messageInfo_CheckHealth proto.InternalMessageInfo
type CheckHealth_Request struct {
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
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{2, 0}
}
func (m *CheckHealth_Request) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_CheckHealth_Request.Unmarshal(m, b)
}
func (m *CheckHealth_Request) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_CheckHealth_Request.Marshal(b, m, deterministic)
}
func (m *CheckHealth_Request) XXX_Merge(src proto.Message) {
xxx_messageInfo_CheckHealth_Request.Merge(m, src)
}
func (m *CheckHealth_Request) XXX_Size() int {
return xxx_messageInfo_CheckHealth_Request.Size(m)
}
func (m *CheckHealth_Request) XXX_DiscardUnknown() {
xxx_messageInfo_CheckHealth_Request.DiscardUnknown(m)
}
var xxx_messageInfo_CheckHealth_Request proto.InternalMessageInfo
type CheckHealth_Response struct {
Status CheckHealth_Response_HealthStatus `protobuf:"varint,1,opt,name=status,proto3,enum=pluginv2.CheckHealth_Response_HealthStatus" json:"status,omitempty"`
Info string `protobuf:"bytes,2,opt,name=info,proto3" json:"info,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
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{2, 1}
}
func (m *CheckHealth_Response) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_CheckHealth_Response.Unmarshal(m, b)
}
func (m *CheckHealth_Response) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_CheckHealth_Response.Marshal(b, m, deterministic)
}
func (m *CheckHealth_Response) XXX_Merge(src proto.Message) {
xxx_messageInfo_CheckHealth_Response.Merge(m, src)
}
func (m *CheckHealth_Response) XXX_Size() int {
return xxx_messageInfo_CheckHealth_Response.Size(m)
}
func (m *CheckHealth_Response) XXX_DiscardUnknown() {
xxx_messageInfo_CheckHealth_Response.DiscardUnknown(m)
}
var xxx_messageInfo_CheckHealth_Response proto.InternalMessageInfo
func (m *CheckHealth_Response) GetStatus() CheckHealth_Response_HealthStatus {
if m != nil {
return m.Status
}
return CheckHealth_Response_UNKNOWN
}
func (m *CheckHealth_Response) GetInfo() string {
if m != nil {
return m.Info
}
return ""
}
type TimeRange struct {
FromEpochMS int64 `protobuf:"varint,1,opt,name=fromEpochMS,proto3" json:"fromEpochMS,omitempty"`
ToEpochMS int64 `protobuf:"varint,2,opt,name=toEpochMS,proto3" json:"toEpochMS,omitempty"`
@ -401,7 +152,7 @@ func (m *TimeRange) Reset() { *m = TimeRange{} }
func (m *TimeRange) String() string { return proto.CompactTextString(m) }
func (*TimeRange) ProtoMessage() {}
func (*TimeRange) Descriptor() ([]byte, []int) {
return fileDescriptor_5ab9ba5b8d8b2ba5, []int{3}
return fileDescriptor_5ab9ba5b8d8b2ba5, []int{1}
}
func (m *TimeRange) XXX_Unmarshal(b []byte) error {
@ -451,7 +202,7 @@ func (m *DataQuery) Reset() { *m = DataQuery{} }
func (m *DataQuery) String() string { return proto.CompactTextString(m) }
func (*DataQuery) ProtoMessage() {}
func (*DataQuery) Descriptor() ([]byte, []int) {
return fileDescriptor_5ab9ba5b8d8b2ba5, []int{4}
return fileDescriptor_5ab9ba5b8d8b2ba5, []int{2}
}
func (m *DataQuery) XXX_Unmarshal(b []byte) error {
@ -523,7 +274,7 @@ func (m *DataQueryRequest) Reset() { *m = DataQueryRequest{} }
func (m *DataQueryRequest) String() string { return proto.CompactTextString(m) }
func (*DataQueryRequest) ProtoMessage() {}
func (*DataQueryRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_5ab9ba5b8d8b2ba5, []int{5}
return fileDescriptor_5ab9ba5b8d8b2ba5, []int{3}
}
func (m *DataQueryRequest) XXX_Unmarshal(b []byte) error {
@ -580,7 +331,7 @@ func (m *DataQueryResponse) Reset() { *m = DataQueryResponse{} }
func (m *DataQueryResponse) String() string { return proto.CompactTextString(m) }
func (*DataQueryResponse) ProtoMessage() {}
func (*DataQueryResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_5ab9ba5b8d8b2ba5, []int{6}
return fileDescriptor_5ab9ba5b8d8b2ba5, []int{4}
}
func (m *DataQueryResponse) XXX_Unmarshal(b []byte) error {
@ -632,7 +383,7 @@ func (m *ResourceRequest) Reset() { *m = ResourceRequest{} }
func (m *ResourceRequest) String() string { return proto.CompactTextString(m) }
func (*ResourceRequest) ProtoMessage() {}
func (*ResourceRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_5ab9ba5b8d8b2ba5, []int{7}
return fileDescriptor_5ab9ba5b8d8b2ba5, []int{5}
}
func (m *ResourceRequest) XXX_Unmarshal(b []byte) error {
@ -701,7 +452,7 @@ 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{8}
return fileDescriptor_5ab9ba5b8d8b2ba5, []int{6}
}
func (m *ResourceResponse) XXX_Unmarshal(b []byte) error {
@ -743,6 +494,255 @@ func (m *ResourceResponse) GetBody() []byte {
return nil
}
type CollectMetrics struct {
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
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}
}
func (m *CollectMetrics) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_CollectMetrics.Unmarshal(m, b)
}
func (m *CollectMetrics) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_CollectMetrics.Marshal(b, m, deterministic)
}
func (m *CollectMetrics) XXX_Merge(src proto.Message) {
xxx_messageInfo_CollectMetrics.Merge(m, src)
}
func (m *CollectMetrics) XXX_Size() int {
return xxx_messageInfo_CollectMetrics.Size(m)
}
func (m *CollectMetrics) XXX_DiscardUnknown() {
xxx_messageInfo_CollectMetrics.DiscardUnknown(m)
}
var xxx_messageInfo_CollectMetrics proto.InternalMessageInfo
type CollectMetrics_Request struct {
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
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}
}
func (m *CollectMetrics_Request) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_CollectMetrics_Request.Unmarshal(m, b)
}
func (m *CollectMetrics_Request) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_CollectMetrics_Request.Marshal(b, m, deterministic)
}
func (m *CollectMetrics_Request) XXX_Merge(src proto.Message) {
xxx_messageInfo_CollectMetrics_Request.Merge(m, src)
}
func (m *CollectMetrics_Request) XXX_Size() int {
return xxx_messageInfo_CollectMetrics_Request.Size(m)
}
func (m *CollectMetrics_Request) XXX_DiscardUnknown() {
xxx_messageInfo_CollectMetrics_Request.DiscardUnknown(m)
}
var xxx_messageInfo_CollectMetrics_Request proto.InternalMessageInfo
type CollectMetrics_Payload struct {
Prometheus []byte `protobuf:"bytes,1,opt,name=prometheus,proto3" json:"prometheus,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
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}
}
func (m *CollectMetrics_Payload) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_CollectMetrics_Payload.Unmarshal(m, b)
}
func (m *CollectMetrics_Payload) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_CollectMetrics_Payload.Marshal(b, m, deterministic)
}
func (m *CollectMetrics_Payload) XXX_Merge(src proto.Message) {
xxx_messageInfo_CollectMetrics_Payload.Merge(m, src)
}
func (m *CollectMetrics_Payload) XXX_Size() int {
return xxx_messageInfo_CollectMetrics_Payload.Size(m)
}
func (m *CollectMetrics_Payload) XXX_DiscardUnknown() {
xxx_messageInfo_CollectMetrics_Payload.DiscardUnknown(m)
}
var xxx_messageInfo_CollectMetrics_Payload proto.InternalMessageInfo
func (m *CollectMetrics_Payload) GetPrometheus() []byte {
if m != nil {
return m.Prometheus
}
return nil
}
type CollectMetrics_Response struct {
Metrics *CollectMetrics_Payload `protobuf:"bytes,1,opt,name=metrics,proto3" json:"metrics,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
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}
}
func (m *CollectMetrics_Response) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_CollectMetrics_Response.Unmarshal(m, b)
}
func (m *CollectMetrics_Response) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_CollectMetrics_Response.Marshal(b, m, deterministic)
}
func (m *CollectMetrics_Response) XXX_Merge(src proto.Message) {
xxx_messageInfo_CollectMetrics_Response.Merge(m, src)
}
func (m *CollectMetrics_Response) XXX_Size() int {
return xxx_messageInfo_CollectMetrics_Response.Size(m)
}
func (m *CollectMetrics_Response) XXX_DiscardUnknown() {
xxx_messageInfo_CollectMetrics_Response.DiscardUnknown(m)
}
var xxx_messageInfo_CollectMetrics_Response proto.InternalMessageInfo
func (m *CollectMetrics_Response) GetMetrics() *CollectMetrics_Payload {
if m != nil {
return m.Metrics
}
return nil
}
type CheckHealth struct {
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
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}
}
func (m *CheckHealth) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_CheckHealth.Unmarshal(m, b)
}
func (m *CheckHealth) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_CheckHealth.Marshal(b, m, deterministic)
}
func (m *CheckHealth) XXX_Merge(src proto.Message) {
xxx_messageInfo_CheckHealth.Merge(m, src)
}
func (m *CheckHealth) XXX_Size() int {
return xxx_messageInfo_CheckHealth.Size(m)
}
func (m *CheckHealth) XXX_DiscardUnknown() {
xxx_messageInfo_CheckHealth.DiscardUnknown(m)
}
var xxx_messageInfo_CheckHealth proto.InternalMessageInfo
type CheckHealth_Request struct {
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
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}
}
func (m *CheckHealth_Request) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_CheckHealth_Request.Unmarshal(m, b)
}
func (m *CheckHealth_Request) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_CheckHealth_Request.Marshal(b, m, deterministic)
}
func (m *CheckHealth_Request) XXX_Merge(src proto.Message) {
xxx_messageInfo_CheckHealth_Request.Merge(m, src)
}
func (m *CheckHealth_Request) XXX_Size() int {
return xxx_messageInfo_CheckHealth_Request.Size(m)
}
func (m *CheckHealth_Request) XXX_DiscardUnknown() {
xxx_messageInfo_CheckHealth_Request.DiscardUnknown(m)
}
var xxx_messageInfo_CheckHealth_Request proto.InternalMessageInfo
type CheckHealth_Response struct {
Status CheckHealth_Response_HealthStatus `protobuf:"varint,1,opt,name=status,proto3,enum=pluginv2.CheckHealth_Response_HealthStatus" json:"status,omitempty"`
Info string `protobuf:"bytes,2,opt,name=info,proto3" json:"info,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
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}
}
func (m *CheckHealth_Response) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_CheckHealth_Response.Unmarshal(m, b)
}
func (m *CheckHealth_Response) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_CheckHealth_Response.Marshal(b, m, deterministic)
}
func (m *CheckHealth_Response) XXX_Merge(src proto.Message) {
xxx_messageInfo_CheckHealth_Response.Merge(m, src)
}
func (m *CheckHealth_Response) XXX_Size() int {
return xxx_messageInfo_CheckHealth_Response.Size(m)
}
func (m *CheckHealth_Response) XXX_DiscardUnknown() {
xxx_messageInfo_CheckHealth_Response.DiscardUnknown(m)
}
var xxx_messageInfo_CheckHealth_Response proto.InternalMessageInfo
func (m *CheckHealth_Response) GetStatus() CheckHealth_Response_HealthStatus {
if m != nil {
return m.Status
}
return CheckHealth_Response_UNKNOWN
}
func (m *CheckHealth_Response) GetInfo() string {
if m != nil {
return m.Info
}
return ""
}
type StreamingRequest struct {
PluginId int64 `protobuf:"varint,1,opt,name=pluginId,proto3" json:"pluginId,omitempty"`
// Environment info
@ -922,7 +922,6 @@ type RenderRequest struct {
FilePath string `protobuf:"bytes,7,opt,name=filePath,proto3" json:"filePath,omitempty"`
RenderKey string `protobuf:"bytes,8,opt,name=renderKey,proto3" json:"renderKey,omitempty"`
Domain string `protobuf:"bytes,9,opt,name=domain,proto3" json:"domain,omitempty"`
Debug bool `protobuf:"varint,10,opt,name=debug,proto3" json:"debug,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
@ -1016,13 +1015,6 @@ func (m *RenderRequest) GetDomain() string {
return ""
}
func (m *RenderRequest) GetDebug() bool {
if m != nil {
return m.Debug
}
return false
}
type RenderResponse struct {
Error string `protobuf:"bytes,1,opt,name=error,proto3" json:"error,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
@ -1066,13 +1058,6 @@ func init() {
proto.RegisterEnum("pluginv2.CheckHealth_Response_HealthStatus", CheckHealth_Response_HealthStatus_name, CheckHealth_Response_HealthStatus_value)
proto.RegisterType((*PluginConfig)(nil), "pluginv2.PluginConfig")
proto.RegisterMapType((map[string]string)(nil), "pluginv2.PluginConfig.DecryptedSecureJsonDataEntry")
proto.RegisterType((*CollectMetrics)(nil), "pluginv2.CollectMetrics")
proto.RegisterType((*CollectMetrics_Request)(nil), "pluginv2.CollectMetrics.Request")
proto.RegisterType((*CollectMetrics_Payload)(nil), "pluginv2.CollectMetrics.Payload")
proto.RegisterType((*CollectMetrics_Response)(nil), "pluginv2.CollectMetrics.Response")
proto.RegisterType((*CheckHealth)(nil), "pluginv2.CheckHealth")
proto.RegisterType((*CheckHealth_Request)(nil), "pluginv2.CheckHealth.Request")
proto.RegisterType((*CheckHealth_Response)(nil), "pluginv2.CheckHealth.Response")
proto.RegisterType((*TimeRange)(nil), "pluginv2.TimeRange")
proto.RegisterType((*DataQuery)(nil), "pluginv2.DataQuery")
proto.RegisterType((*DataQueryRequest)(nil), "pluginv2.DataQueryRequest")
@ -1083,6 +1068,13 @@ func init() {
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((*CollectMetrics)(nil), "pluginv2.CollectMetrics")
proto.RegisterType((*CollectMetrics_Request)(nil), "pluginv2.CollectMetrics.Request")
proto.RegisterType((*CollectMetrics_Payload)(nil), "pluginv2.CollectMetrics.Payload")
proto.RegisterType((*CollectMetrics_Response)(nil), "pluginv2.CollectMetrics.Response")
proto.RegisterType((*CheckHealth)(nil), "pluginv2.CheckHealth")
proto.RegisterType((*CheckHealth_Request)(nil), "pluginv2.CheckHealth.Request")
proto.RegisterType((*CheckHealth_Response)(nil), "pluginv2.CheckHealth.Response")
proto.RegisterType((*StreamingRequest)(nil), "pluginv2.StreamingRequest")
proto.RegisterMapType((map[string]string)(nil), "pluginv2.StreamingRequest.HeadersEntry")
proto.RegisterType((*StreamingMessage)(nil), "pluginv2.StreamingMessage")
@ -1095,82 +1087,82 @@ func init() { proto.RegisterFile("backend.proto", fileDescriptor_5ab9ba5b8d8b2ba
var fileDescriptor_5ab9ba5b8d8b2ba5 = []byte{
// 1207 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xcc, 0x57, 0xcd, 0x72, 0x1b, 0xc5,
0x13, 0xff, 0xaf, 0x64, 0x7d, 0xb5, 0x6c, 0x47, 0x99, 0xb8, 0x92, 0xfd, 0x6f, 0x42, 0xca, 0xa8,
0xa8, 0xe0, 0x14, 0x85, 0x02, 0xca, 0x85, 0x4a, 0xaa, 0x80, 0x20, 0x9b, 0x90, 0x18, 0x27, 0xca,
0x38, 0x29, 0x8a, 0x0b, 0x55, 0xa3, 0xdd, 0x96, 0xb4, 0x78, 0x77, 0x47, 0x99, 0x9d, 0x35, 0x88,
0x37, 0xe0, 0xc2, 0x85, 0x1b, 0x0f, 0x01, 0x17, 0x2e, 0x3c, 0x00, 0x8f, 0xc0, 0x7b, 0x70, 0xe3,
0x08, 0x35, 0xb3, 0x33, 0xab, 0xb5, 0xca, 0x52, 0x55, 0x88, 0x0f, 0xdc, 0xba, 0x7b, 0x7b, 0x7e,
0xd3, 0xdf, 0x3d, 0x0b, 0x5b, 0x23, 0xe6, 0x9f, 0x60, 0x12, 0xf4, 0x66, 0x82, 0x4b, 0x4e, 0x9a,
0xb3, 0x28, 0x9b, 0x84, 0xc9, 0x69, 0xdf, 0xbb, 0x3e, 0xe1, 0x7c, 0x12, 0xe1, 0x1d, 0x2d, 0x1f,
0x65, 0xe3, 0x3b, 0x18, 0xcf, 0xe4, 0x3c, 0x57, 0xeb, 0xfe, 0x5e, 0x81, 0xcd, 0xa1, 0xd6, 0x1c,
0xf0, 0x64, 0x1c, 0x4e, 0xc8, 0x36, 0x54, 0xc2, 0xc0, 0x75, 0x76, 0x9d, 0xbd, 0x2a, 0xad, 0x84,
0x01, 0xd9, 0x81, 0x1a, 0x17, 0x93, 0x47, 0x81, 0x5b, 0xd1, 0xa2, 0x9c, 0x21, 0x04, 0x36, 0x12,
0x16, 0xa3, 0x5b, 0xdd, 0x75, 0xf6, 0x5a, 0x54, 0xd3, 0x4a, 0x26, 0xe7, 0x33, 0x74, 0x37, 0x72,
0x99, 0xa2, 0x49, 0x07, 0xaa, 0x99, 0x88, 0xdc, 0x9a, 0x16, 0x29, 0x92, 0x78, 0xd0, 0xfc, 0x3a,
0xe5, 0xc9, 0x3e, 0x93, 0xcc, 0xad, 0x6b, 0x71, 0xc1, 0x93, 0x18, 0xae, 0x05, 0xe8, 0x8b, 0xf9,
0x4c, 0x62, 0x70, 0x8c, 0x7e, 0x26, 0xf0, 0xb1, 0x55, 0x6d, 0xec, 0x56, 0xf7, 0xda, 0xfd, 0xbb,
0x3d, 0xeb, 0x55, 0xaf, 0x6c, 0x74, 0x6f, 0xff, 0xfc, 0x53, 0x07, 0x89, 0x14, 0x73, 0xba, 0x0a,
0xd3, 0x7b, 0x0c, 0x37, 0xd6, 0x1d, 0x54, 0xc6, 0x9f, 0xe0, 0x5c, 0xc7, 0xa2, 0x45, 0x15, 0xa9,
0x82, 0x71, 0xca, 0xa2, 0x0c, 0x75, 0x30, 0x5a, 0x34, 0x67, 0xee, 0x55, 0x3e, 0x70, 0xba, 0x3f,
0x38, 0xb0, 0x3d, 0xe0, 0x51, 0x84, 0xbe, 0x3c, 0x42, 0x29, 0x42, 0x3f, 0xf5, 0x5a, 0xd0, 0xa0,
0xf8, 0x32, 0xc3, 0x54, 0x7a, 0xb7, 0xa1, 0x31, 0x64, 0xf3, 0x88, 0xb3, 0x80, 0xdc, 0x04, 0x98,
0x09, 0x1e, 0xa3, 0x9c, 0x62, 0x96, 0x6a, 0xec, 0x4d, 0x5a, 0x92, 0x78, 0x9f, 0x42, 0x93, 0x62,
0x3a, 0xe3, 0x49, 0x8a, 0xe4, 0x1e, 0x34, 0xe2, 0x1c, 0x4c, 0x2b, 0xb6, 0xfb, 0xbb, 0x0b, 0xff,
0xcf, 0x5e, 0xd6, 0x33, 0xf0, 0xd4, 0x1e, 0xe8, 0xfe, 0xec, 0x40, 0x7b, 0x30, 0x45, 0xff, 0xe4,
0x33, 0x64, 0x91, 0x9c, 0x96, 0xad, 0xf9, 0xd1, 0x29, 0xdd, 0x31, 0x80, 0x7a, 0x2a, 0x99, 0x34,
0xb6, 0x6c, 0xf7, 0xdf, 0x29, 0x5d, 0xb1, 0x38, 0xde, 0xb3, 0xfa, 0xbd, 0x9c, 0x3f, 0xd6, 0x47,
0xa8, 0x39, 0xaa, 0x52, 0x1f, 0x26, 0x63, 0x6e, 0xc2, 0xa2, 0xe9, 0x6e, 0x0f, 0x36, 0xcb, 0xba,
0xa4, 0x0d, 0x8d, 0x17, 0x4f, 0x0e, 0x9f, 0x3c, 0xfd, 0xe2, 0x49, 0xe7, 0x7f, 0xa4, 0x0e, 0x95,
0xa7, 0x87, 0x1d, 0x87, 0xb4, 0xa0, 0x76, 0x40, 0xe9, 0x53, 0xda, 0xa9, 0x74, 0x0f, 0xa1, 0xf5,
0x3c, 0x8c, 0x91, 0xb2, 0x64, 0x82, 0x64, 0x17, 0xda, 0x63, 0xc1, 0xe3, 0x83, 0x19, 0xf7, 0xa7,
0x47, 0xc7, 0xa6, 0x1c, 0xcb, 0x22, 0x72, 0x03, 0x5a, 0x92, 0xdb, 0xef, 0x79, 0x6d, 0x2e, 0x04,
0xca, 0xfb, 0x96, 0x4a, 0xe4, 0xb3, 0x0c, 0x85, 0x4e, 0x9b, 0xc0, 0xf1, 0xa3, 0xc0, 0xa4, 0x32,
0x67, 0xc8, 0x5b, 0xb0, 0x15, 0xb3, 0x6f, 0x95, 0xd6, 0x90, 0x87, 0x89, 0x4c, 0x0d, 0xca, 0x59,
0xa1, 0xca, 0x57, 0x98, 0x48, 0x14, 0xa7, 0x2c, 0x3a, 0x3a, 0xd6, 0xf5, 0x5e, 0xa5, 0x25, 0x09,
0x79, 0x1f, 0x5a, 0xd2, 0x9a, 0xad, 0x4b, 0xbf, 0xdd, 0xbf, 0xb2, 0x08, 0x61, 0xe1, 0x11, 0x5d,
0x68, 0xa9, 0x68, 0xa9, 0x92, 0xd7, 0x5d, 0xb1, 0x49, 0x35, 0xdd, 0xfd, 0xd3, 0x81, 0x4e, 0x61,
0xb0, 0x49, 0x14, 0xe9, 0x41, 0xdd, 0xd7, 0x05, 0x6e, 0xd2, 0x7f, 0xf5, 0xfc, 0xf2, 0xa7, 0x46,
0x8b, 0x3c, 0x80, 0xc6, 0x14, 0x59, 0x80, 0x42, 0xf9, 0xa2, 0xfa, 0xe5, 0xed, 0xc5, 0x81, 0x65,
0x70, 0x95, 0x48, 0xa5, 0x99, 0xf7, 0x88, 0x3d, 0x47, 0xde, 0x85, 0xc6, 0xcb, 0x0c, 0x45, 0x88,
0xa9, 0x5b, 0xd5, 0x10, 0x57, 0xce, 0x83, 0xb0, 0x3a, 0xde, 0x3d, 0x9d, 0xe4, 0x02, 0xe7, 0x95,
0x5a, 0xe6, 0x17, 0x07, 0x2e, 0x97, 0xac, 0x32, 0xf5, 0x78, 0x15, 0xea, 0x63, 0xc1, 0x62, 0x54,
0xf5, 0x58, 0xdd, 0xdb, 0xa4, 0x86, 0x23, 0x07, 0xd0, 0x8c, 0x51, 0xb2, 0x40, 0x0d, 0x83, 0xdc,
0xb9, 0xdb, 0xe7, 0x3a, 0x67, 0xca, 0xf4, 0xc8, 0xe8, 0xe6, 0xee, 0x15, 0x47, 0xbd, 0xfb, 0xb0,
0x75, 0xe6, 0xd3, 0x2b, 0x59, 0xfc, 0xb7, 0x03, 0x97, 0x28, 0xa6, 0x3c, 0x13, 0x3e, 0xfe, 0xdb,
0x1c, 0x7d, 0xbc, 0x9c, 0xa3, 0x5b, 0x8b, 0x03, 0x4b, 0xd8, 0x2b, 0x52, 0x74, 0x15, 0xea, 0x6a,
0x58, 0xf0, 0xc0, 0x4c, 0x5f, 0xc3, 0xa9, 0xb2, 0x9a, 0x31, 0x39, 0xb5, 0xf3, 0x57, 0xd1, 0x4a,
0x36, 0xe2, 0xc1, 0xdc, 0x96, 0x9a, 0xa2, 0x5f, 0x2b, 0x67, 0xbf, 0x39, 0xd0, 0x59, 0x58, 0x69,
0x52, 0x46, 0x60, 0xc3, 0xe7, 0x01, 0x6a, 0x84, 0x1a, 0xd5, 0xf4, 0xda, 0x52, 0x5c, 0x06, 0x58,
0xe1, 0xa7, 0xb5, 0xbd, 0x7a, 0x41, 0xb6, 0xff, 0xe5, 0x40, 0xe7, 0x58, 0x0a, 0x64, 0x71, 0x98,
0x4c, 0x6c, 0xfa, 0x3c, 0x30, 0x8b, 0xf2, 0x91, 0x5d, 0x7a, 0x05, 0xbf, 0xd6, 0x87, 0x65, 0xa0,
0x15, 0x3e, 0xec, 0x40, 0x4d, 0xf2, 0x59, 0xe8, 0x9b, 0x54, 0xe5, 0x4c, 0xe1, 0x99, 0xc9, 0x94,
0xa2, 0xd5, 0x9c, 0x49, 0xb3, 0x51, 0xea, 0x8b, 0x70, 0x84, 0x81, 0xce, 0x57, 0x93, 0x96, 0x24,
0xaf, 0xe5, 0xf9, 0x57, 0x25, 0xc7, 0x8f, 0x30, 0x4d, 0xd9, 0x04, 0xd7, 0x3a, 0x5e, 0x58, 0x5d,
0x29, 0x5b, 0xed, 0xaa, 0x6d, 0xa4, 0x0f, 0x1b, 0x6f, 0x2c, 0xdb, 0xbd, 0x05, 0xdb, 0x05, 0xfe,
0x20, 0xe2, 0x29, 0x2a, 0x04, 0x9f, 0x67, 0x89, 0x34, 0xd0, 0x39, 0xd3, 0xfd, 0xbe, 0x02, 0x5b,
0x14, 0x93, 0x00, 0x85, 0x0d, 0xbf, 0x79, 0x1f, 0x38, 0x8b, 0xf7, 0xc1, 0x0e, 0xd4, 0xbe, 0x09,
0x03, 0x39, 0xd5, 0x77, 0xd7, 0x68, 0xce, 0xa8, 0x9a, 0x9f, 0x62, 0x38, 0x99, 0x4a, 0x7d, 0x75,
0x8d, 0x1a, 0x4e, 0xd9, 0xa4, 0xe6, 0x2a, 0xcf, 0xa4, 0x0e, 0x66, 0x8d, 0x5a, 0x56, 0xf9, 0xa7,
0xc8, 0xef, 0x78, 0x82, 0xe6, 0xf9, 0x51, 0xf0, 0xea, 0x1b, 0x26, 0x3e, 0x0f, 0xc2, 0x64, 0x62,
0xdf, 0x20, 0x96, 0x57, 0xdf, 0xc6, 0x61, 0x84, 0x43, 0xd5, 0x49, 0x8d, 0xfc, 0x9b, 0xe5, 0xd5,
0xce, 0x11, 0xda, 0xfc, 0x43, 0x9c, 0xbb, 0x4d, 0xfd, 0x71, 0x21, 0x50, 0x36, 0x06, 0x3c, 0x66,
0x61, 0xe2, 0xb6, 0xf2, 0xbe, 0xcc, 0x39, 0xe5, 0x51, 0x80, 0xa3, 0x6c, 0xe2, 0x82, 0x4e, 0x6a,
0xce, 0xa8, 0x98, 0xd9, 0x50, 0x98, 0x36, 0xda, 0x81, 0x1a, 0x0a, 0xc1, 0x85, 0xdd, 0x52, 0x9a,
0xe9, 0xff, 0x5a, 0x81, 0x8d, 0x01, 0x17, 0x48, 0x5e, 0x2c, 0x3f, 0x30, 0xc8, 0xea, 0xd7, 0x80,
0xdd, 0xf4, 0x6f, 0xae, 0xd1, 0x30, 0xb7, 0x7e, 0x7e, 0xe6, 0x99, 0x40, 0xde, 0x58, 0xb5, 0xfe,
0x73, 0xc0, 0x9b, 0xeb, 0x5f, 0x07, 0xe4, 0x81, 0x7e, 0x59, 0xe8, 0xee, 0x26, 0xff, 0x5f, 0x39,
0xd8, 0x3c, 0x6f, 0xf5, 0x30, 0x20, 0xfb, 0xe5, 0xcd, 0xed, 0xad, 0x5e, 0x60, 0xde, 0xf5, 0x35,
0xf3, 0xbf, 0xff, 0x93, 0x03, 0x97, 0x1e, 0x0a, 0x36, 0x66, 0x09, 0x1b, 0x46, 0x4c, 0x8e, 0xb9,
0x88, 0xff, 0x3b, 0xc6, 0x3d, 0x83, 0xd6, 0x73, 0xc1, 0x92, 0x54, 0x5b, 0x75, 0x31, 0x90, 0x5f,
0xc2, 0xe5, 0x02, 0x72, 0xc0, 0xa2, 0xe8, 0x13, 0xe6, 0x9f, 0x5c, 0x10, 0xf4, 0x1f, 0x0e, 0x5c,
0x2a, 0xda, 0x3b, 0xdf, 0x69, 0xe4, 0x23, 0x68, 0x0c, 0x78, 0x92, 0xa0, 0x2f, 0xc9, 0x8a, 0x85,
0x57, 0x0e, 0xe3, 0xf2, 0xf0, 0x79, 0xcf, 0x51, 0xb3, 0x75, 0x28, 0xb8, 0x8f, 0x69, 0x4a, 0xbc,
0xd5, 0x53, 0x75, 0x1d, 0x08, 0xf9, 0x10, 0x60, 0x3f, 0x4c, 0xfd, 0xc2, 0x8c, 0xfc, 0x37, 0xa7,
0x67, 0x7f, 0x73, 0x7a, 0x07, 0xea, 0x37, 0xc7, 0x73, 0xcf, 0x41, 0xd0, 0x33, 0xaa, 0xff, 0x50,
0x95, 0x83, 0xea, 0x40, 0x14, 0xe4, 0x3e, 0xd4, 0x73, 0x9a, 0x5c, 0x2b, 0x67, 0xbf, 0x34, 0xaa,
0xca, 0x40, 0x67, 0x1b, 0x77, 0x54, 0xd7, 0x57, 0xde, 0xfd, 0x27, 0x00, 0x00, 0xff, 0xff, 0x84,
0x8c, 0xdf, 0x9f, 0x82, 0x0d, 0x00, 0x00,
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,
}
// Reference imports to suppress errors if they are not otherwise used.
@ -1185,9 +1177,6 @@ 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 {
// Diagnostics
CollectMetrics(ctx context.Context, in *CollectMetrics_Request, opts ...grpc.CallOption) (*CollectMetrics_Response, error)
CheckHealth(ctx context.Context, in *CheckHealth_Request, opts ...grpc.CallOption) (*CheckHealth_Response, error)
// HTTP Style request
Resource(ctx context.Context, in *ResourceRequest, opts ...grpc.CallOption) (*ResourceResponse, error)
// Well typed query interface
@ -1202,24 +1191,6 @@ func NewCoreClient(cc *grpc.ClientConn) CoreClient {
return &coreClient{cc}
}
func (c *coreClient) CollectMetrics(ctx context.Context, in *CollectMetrics_Request, opts ...grpc.CallOption) (*CollectMetrics_Response, error) {
out := new(CollectMetrics_Response)
err := c.cc.Invoke(ctx, "/pluginv2.Core/CollectMetrics", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *coreClient) CheckHealth(ctx context.Context, in *CheckHealth_Request, opts ...grpc.CallOption) (*CheckHealth_Response, error) {
out := new(CheckHealth_Response)
err := c.cc.Invoke(ctx, "/pluginv2.Core/CheckHealth", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
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...)
@ -1240,9 +1211,6 @@ func (c *coreClient) DataQuery(ctx context.Context, in *DataQueryRequest, opts .
// CoreServer is the server API for Core service.
type CoreServer interface {
// Diagnostics
CollectMetrics(context.Context, *CollectMetrics_Request) (*CollectMetrics_Response, error)
CheckHealth(context.Context, *CheckHealth_Request) (*CheckHealth_Response, error)
// HTTP Style request
Resource(context.Context, *ResourceRequest) (*ResourceResponse, error)
// Well typed query interface
@ -1253,12 +1221,6 @@ type CoreServer interface {
type UnimplementedCoreServer struct {
}
func (*UnimplementedCoreServer) CollectMetrics(ctx context.Context, req *CollectMetrics_Request) (*CollectMetrics_Response, error) {
return nil, status.Errorf(codes.Unimplemented, "method CollectMetrics not implemented")
}
func (*UnimplementedCoreServer) CheckHealth(ctx context.Context, req *CheckHealth_Request) (*CheckHealth_Response, error) {
return nil, status.Errorf(codes.Unimplemented, "method CheckHealth not implemented")
}
func (*UnimplementedCoreServer) Resource(ctx context.Context, req *ResourceRequest) (*ResourceResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method Resource not implemented")
}
@ -1270,42 +1232,6 @@ func RegisterCoreServer(s *grpc.Server, srv CoreServer) {
s.RegisterService(&_Core_serviceDesc, srv)
}
func _Core_CollectMetrics_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(CollectMetrics_Request)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(CoreServer).CollectMetrics(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/pluginv2.Core/CollectMetrics",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(CoreServer).CollectMetrics(ctx, req.(*CollectMetrics_Request))
}
return interceptor(ctx, in, info, handler)
}
func _Core_CheckHealth_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(CheckHealth_Request)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(CoreServer).CheckHealth(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/pluginv2.Core/CheckHealth",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(CoreServer).CheckHealth(ctx, req.(*CheckHealth_Request))
}
return interceptor(ctx, in, info, handler)
}
func _Core_Resource_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ResourceRequest)
if err := dec(in); err != nil {
@ -1346,14 +1272,6 @@ var _Core_serviceDesc = grpc.ServiceDesc{
ServiceName: "pluginv2.Core",
HandlerType: (*CoreServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "CollectMetrics",
Handler: _Core_CollectMetrics_Handler,
},
{
MethodName: "CheckHealth",
Handler: _Core_CheckHealth_Handler,
},
{
MethodName: "Resource",
Handler: _Core_Resource_Handler,
@ -1367,6 +1285,114 @@ var _Core_serviceDesc = grpc.ServiceDesc{
Metadata: "backend.proto",
}
// DiagnosticsClient is the client API for Diagnostics service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.
type DiagnosticsClient interface {
CollectMetrics(ctx context.Context, in *CollectMetrics_Request, opts ...grpc.CallOption) (*CollectMetrics_Response, error)
CheckHealth(ctx context.Context, in *CheckHealth_Request, opts ...grpc.CallOption) (*CheckHealth_Response, error)
}
type diagnosticsClient struct {
cc *grpc.ClientConn
}
func NewDiagnosticsClient(cc *grpc.ClientConn) DiagnosticsClient {
return &diagnosticsClient{cc}
}
func (c *diagnosticsClient) CollectMetrics(ctx context.Context, in *CollectMetrics_Request, opts ...grpc.CallOption) (*CollectMetrics_Response, error) {
out := new(CollectMetrics_Response)
err := c.cc.Invoke(ctx, "/pluginv2.Diagnostics/CollectMetrics", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *diagnosticsClient) CheckHealth(ctx context.Context, in *CheckHealth_Request, opts ...grpc.CallOption) (*CheckHealth_Response, error) {
out := new(CheckHealth_Response)
err := c.cc.Invoke(ctx, "/pluginv2.Diagnostics/CheckHealth", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// DiagnosticsServer is the server API for Diagnostics service.
type DiagnosticsServer interface {
CollectMetrics(context.Context, *CollectMetrics_Request) (*CollectMetrics_Response, error)
CheckHealth(context.Context, *CheckHealth_Request) (*CheckHealth_Response, error)
}
// UnimplementedDiagnosticsServer can be embedded to have forward compatible implementations.
type UnimplementedDiagnosticsServer struct {
}
func (*UnimplementedDiagnosticsServer) CollectMetrics(ctx context.Context, req *CollectMetrics_Request) (*CollectMetrics_Response, error) {
return nil, status.Errorf(codes.Unimplemented, "method CollectMetrics not implemented")
}
func (*UnimplementedDiagnosticsServer) CheckHealth(ctx context.Context, req *CheckHealth_Request) (*CheckHealth_Response, error) {
return nil, status.Errorf(codes.Unimplemented, "method CheckHealth not implemented")
}
func RegisterDiagnosticsServer(s *grpc.Server, srv DiagnosticsServer) {
s.RegisterService(&_Diagnostics_serviceDesc, srv)
}
func _Diagnostics_CollectMetrics_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(CollectMetrics_Request)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(DiagnosticsServer).CollectMetrics(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/pluginv2.Diagnostics/CollectMetrics",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(DiagnosticsServer).CollectMetrics(ctx, req.(*CollectMetrics_Request))
}
return interceptor(ctx, in, info, handler)
}
func _Diagnostics_CheckHealth_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(CheckHealth_Request)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(DiagnosticsServer).CheckHealth(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/pluginv2.Diagnostics/CheckHealth",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(DiagnosticsServer).CheckHealth(ctx, req.(*CheckHealth_Request))
}
return interceptor(ctx, in, info, handler)
}
var _Diagnostics_serviceDesc = grpc.ServiceDesc{
ServiceName: "pluginv2.Diagnostics",
HandlerType: (*DiagnosticsServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "CollectMetrics",
Handler: _Diagnostics_CollectMetrics_Handler,
},
{
MethodName: "CheckHealth",
Handler: _Diagnostics_CheckHealth_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "backend.proto",
}
// GrafanaPlatformClient is the client API for GrafanaPlatform service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.

View File

@ -0,0 +1,201 @@
#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

@ -0,0 +1,24 @@
# 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

@ -0,0 +1,201 @@
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

@ -0,0 +1,247 @@
# 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

@ -0,0 +1,39 @@
// 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

@ -0,0 +1,170 @@
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

@ -0,0 +1,46 @@
// 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

@ -0,0 +1,16 @@
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

@ -0,0 +1,41 @@
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

@ -0,0 +1,48 @@
// 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

@ -0,0 +1,185 @@
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

@ -0,0 +1,46 @@
// 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

@ -0,0 +1,50 @@
// 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
}

5
vendor/modules.txt vendored
View File

@ -140,11 +140,12 @@ 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.5.0
# github.com/grafana/grafana-plugin-sdk-go v0.6.0
github.com/grafana/grafana-plugin-sdk-go/backend
github.com/grafana/grafana-plugin-sdk-go/common
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