simplejson

package module
v3.7.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 14, 2022 License: MIT Imports: 10 Imported by: 6

README

simplejson

GitHub tag (latest by date) Codecov Test Go Report Card GitHub GoDoc

Basic Go implementation of a Grafana SimpleJSON server.

Works with:

Note: the latter does not call the /annotations endpoint. Return annotations through the /query endpoint instead.

Documentation

Overview

Package simplejson provides a Go implementation for Grafana's SimpleJSON datasource: https://grafana.com/grafana/plugins/grafana-simple-json-datasource

Overview

A simplejson server is an HTTP server that supports one or more handlers. Each handler can support multiple targets, each of which can be supported by a timeseries or table query. Optionally tag can be used to alter the behaviour of the query (e.g. filtering what data should be returned). Finally, a handler can support annotation, i.e. a set of timestamps with associated text.

Server

To create a SimpleJSON server, create a Server and run it:

s := simplejson.Server{
	Handlers: map[string]simplejson.Handler{
		"my-target": myHandler,
	},
}
err := s.Run(8080)

This starts a server, listening on port 8080, with one target "my-target", served by myHandler.

Handler

A handler serves incoming requests from Grafana, e.g. queries, requests for annotations or tag. The Handler interface contains all functions a handler needs to implement. It contains only one function (Endpoints). This function returns the Grafana SimpleJSON endpoints that the handler supports. Those can be:

  • Query() implements the /query endpoint. handles both timeserie & table responses
  • Annotations() implements the /annotation endpoint
  • TagKeys() implements the /tag-keys endpoint
  • TagValues() implements the /tag-values endpoint

Here's an example of a handler that supports timeseries queries:

type myHandler struct {
}

func (handler myHandler) Endpoints() simplejson.Endpoints {
	return simplejson.Endpoints{
		Query:  handler.Query
	}
}

func (handler *myHandler) Query(ctx context.Context, target string, target *simplejson.Args) (response *simplejson.QueryResponse, err error) {
	// build response
	return
}

Queries

SimpleJSON supports two types of query responses: timeseries responses and table responses.

Timeseries queries return values as a list of timestamp/value tuples. Here's an example of a timeseries query handler:

func (handler *myHandler) Query(_ context.Context, _ string, _ query.Args) (response *query.TimeSeriesResponse, err error) {
	response = &query.TimeSeriesResponse{
		Name: "A",
		DataPoints: []query.DataPoint{
			{Timestamp: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC), Value: 100},
			{Timestamp: time.Date(2020, 1, 1, 0, 1, 0, 0, time.UTC), Value: 101},
			{Timestamp: time.Date(2020, 1, 1, 0, 2, 0, 0, time.UTC), Value: 103},
		},
	}
	return
}

Table Queries, on the other hand, return data organized in columns and rows. Each column needs to have the same number of rows:

func (handler *myHandler) TableQuery(_ context.Context, _ string, _ query.Args) (response *query.TableResponse, err error) {
	response = &query.TableResponse{
		Columns: []query.Column{
			{ Text: "Time",     Data: query.TimeColumn{time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC), time.Date(2020, 1, 1, 0, 1, 0, 0, time.UTC)} },
			{ Text: "Label",    Data: query.StringColumn{"foo", "bar"}},
			{ Text: "Series A", Data: query.NumberColumn{42, 43}},
			{ Text: "Series B", Data: query.NumberColumn{64.5, 100.0}},
		},
	}
	return
}

Annotations

The /annotations endpoint returns Annotations:

func (h *handler) Annotations(_ annotation.Request) (annotations []annotation.Annotation, err error) {
	annotations = []annotation.Annotation{
		{
			Time:  time.Now().Add(-5 * time.Minute),
			Title: "foo",
			Text:  "bar",
			Tags:  []string{"A", "B"},
		},
	}
	return
}

NOTE: this is only called when using the SimpleJSON datasource. simPod / GrafanaJsonDatasource does not use the /annotations endpoint. Instead, it will call a regular /query and allows to configure its response as annotations instead.

Tags

The /tag-keys and /tag-values endpoints return supported keys and key values respectively for your data source. A Grafana dashboard can then be confirmed to show those keys and its possible values as a filter.

The following sets up a key & key value handler:

func (h *handler) TagKeys(_ context.Context) (keys []string) {
	return []string{"some-key"}
}

func (h *handler) TagValues(_ context.Context, key string) (values []string, err error) {
	if key != "some-key" {
		return nil, fmt.Errorf("invalid key: %s", key)
	}
	return []string{"A", "B", "C"}, nil
}

When the dashboard performs a query with a tag selected, that tag & value will be added in the request's AdHocFilters.

Metrics

simplejson exports two Prometheus metrics for performance analytics:

simplejson_query_duration_seconds: duration of query requests by target, in seconds
simplejson_query_failed_count:     number of failed query requests

Other topics

For information on query arguments and tags, refer to the documentation for those data structures.

Example
package main

import (
	"context"
	"fmt"
	"github.com/clambin/simplejson/v3"
	"github.com/clambin/simplejson/v3/annotation"
	"github.com/clambin/simplejson/v3/query"
	"time"
)

func main() {
	s, err := simplejson.New("test", map[string]simplejson.Handler{
		"A": &handler{},
		"B": &handler{table: true},
	})

	if err == nil {
		_ = s.Run()
	}
}

type handler struct{ table bool }

func (h *handler) Endpoints() simplejson.Endpoints {
	return simplejson.Endpoints{
		Query:       h.Query,
		Annotations: h.Annotations,
		TagKeys:     h.TagKeys,
		TagValues:   h.TagValues,
	}
}

func (h *handler) Query(ctx context.Context, req query.Request) (query.Response, error) {
	if h.table == false {
		return h.timeSeriesQuery(ctx, req)
	}
	return h.tableQuery(ctx, req)
}

func (h *handler) timeSeriesQuery(_ context.Context, _ query.Request) (*query.TimeSeriesResponse, error) {
	dataPoints := make([]query.DataPoint, 60)
	timestamp := time.Now().Add(-1 * time.Hour)
	for i := 0; i < 60; i++ {
		dataPoints[i] = query.DataPoint{
			Timestamp: timestamp,
			Value:     int64(i),
		}
		timestamp = timestamp.Add(1 * time.Minute)
	}

	return &query.TimeSeriesResponse{
		DataPoints: dataPoints,
	}, nil
}

func (h *handler) tableQuery(_ context.Context, _ query.Request) (*query.TableResponse, error) {
	timestamps := make(query.TimeColumn, 60)
	seriesA := make(query.NumberColumn, 60)
	seriesB := make(query.NumberColumn, 60)

	timestamp := time.Now().Add(-1 * time.Hour)
	for i := 0; i < 60; i++ {
		timestamps[i] = timestamp
		seriesA[i] = float64(i)
		seriesB[i] = float64(-i)
		timestamp = timestamp.Add(1 * time.Minute)
	}

	return &query.TableResponse{
		Columns: []query.Column{
			{Text: "timestamp", Data: timestamps},
			{Text: "series A", Data: seriesA},
			{Text: "series B", Data: seriesB},
		},
	}, nil
}

func (h *handler) Annotations(_ annotation.Request) ([]annotation.Annotation, error) {
	return []annotation.Annotation{{
		Time:  time.Now().Add(-5 * time.Minute),
		Title: "foo",
		Text:  "bar",
	}}, nil
}

func (h *handler) TagKeys(_ context.Context) []string {
	return []string{"some-key"}
}

func (h *handler) TagValues(_ context.Context, key string) ([]string, error) {
	if key != "some-key" {
		return nil, fmt.Errorf("invalid key: %s", key)
	}
	return []string{"A", "B", "C"}, nil
}
Output:

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type AnnotationsFunc

type AnnotationsFunc func(req annotation.Request) ([]annotation.Annotation, error)

AnnotationsFunc handles requests for annotation

type Endpoints

type Endpoints struct {
	Query       QueryFunc       // /query endpoint: handles queries
	Annotations AnnotationsFunc // /annotation endpoint: handles requests for annotation
	TagKeys     TagKeysFunc     // /tag-keys endpoint: returns all supported tag names
	TagValues   TagValuesFunc   // /tag-values endpoint: returns all supported values for the specified tag name
}

Endpoints contains the functions that implement each of the SimpleJson endpoints

type Handler

type Handler interface {
	Endpoints() Endpoints
}

Handler implements the different Grafana SimpleJSON endpoints. The interface only contains a single Endpoints() function, so that a handler only has to implement the endpoint functions (query, annotation, etc.) that it needs.

type QueryFunc

type QueryFunc func(ctx context.Context, req query.Request) (query.Response, error)

QueryFunc handles queries

type QueryMetrics added in v3.7.0

type QueryMetrics struct {
	Duration *prometheus.HistogramVec
	Errors   *prometheus.CounterVec
}

func NewQueryMetrics added in v3.7.0

func NewQueryMetrics(name string) QueryMetrics

func (QueryMetrics) Register added in v3.7.0

func (qm QueryMetrics) Register(r prometheus.Registerer)

type Server

type Server struct {
	Handlers map[string]Handler
	// contains filtered or unexported fields
}

Server receives SimpleJSON requests from Grafana and dispatches them to the handler that serves the specified target.

func New added in v3.7.0

func New(name string, handlers map[string]Handler, options ...httpserver.Option) (s *Server, err error)

func NewWithRegisterer added in v3.7.0

func NewWithRegisterer(name string, handlers map[string]Handler, r prometheus.Registerer, options ...httpserver.Option) (s *Server, err error)

func (*Server) Annotations added in v3.5.0

func (s *Server) Annotations(w http.ResponseWriter, req *http.Request)

func (*Server) Query added in v3.5.0

func (s *Server) Query(w http.ResponseWriter, req *http.Request)

func (*Server) Run

func (s *Server) Run() error

Run starts the SimpleJSon Server.

func (*Server) Search added in v3.5.0

func (s *Server) Search(w http.ResponseWriter, _ *http.Request)

func (*Server) Shutdown

func (s *Server) Shutdown(timeout time.Duration) (err error)

Shutdown stops a running Server.

func (*Server) TagKeys added in v3.5.0

func (s *Server) TagKeys(w http.ResponseWriter, req *http.Request)

func (*Server) TagValues added in v3.5.0

func (s *Server) TagValues(w http.ResponseWriter, req *http.Request)

func (*Server) Targets

func (s *Server) Targets() (targets []string)

Targets returns a sorted list of supported targets

type TagKeysFunc

type TagKeysFunc func(ctx context.Context) []string

TagKeysFunc returns supported tag names

type TagValuesFunc

type TagValuesFunc func(ctx context.Context, key string) ([]string, error)

TagValuesFunc returns supported values for the specified tag name

Directories

Path Synopsis
Package dataset makes it easier to produce time-based responses when dealing with data that may not necessarily be sequential.
Package dataset makes it easier to produce time-based responses when dealing with data that may not necessarily be sequential.
pkg
set

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL