simplejson

package module
v5.1.1 Latest Latest
Warning

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

Go to latest
Published: Jan 3, 2023 License: MIT Imports: 14 Imported by: 1

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 query targets, which can be either timeseries or table query. A handler may support tag key/value pairs, which can be passed to the query to adapt its behaviour (e.g. filtering what data should be returned) through 'ad hoc filters'. Finally, a handler can support annotations, i.e. a set of timestamps with associated text.

Server

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

handlers := map[string]simplejson.Handler{
	"A": &handler{},
	"B": &handler{table: true},
}
s, err := simplejson.New(handlers, simplejson.WithHTTPServerOption{Option: httpserver.WithPort{Port: 8080}})

if err == nil {
	err = s.Serve()
}

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.QueryArgs) (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.QueryArgs) (response *simplejson.TimeSeriesResponse, err error) {
	response = &query.TimeSeriesResponse{
		Name: "A",
		DataPoints: []simplejson.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.QueryArgs) (response *simplejson.TableResponse, err error) {
	response = &simplejson.TableResponse{
		Columns: []simplejson.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(_ simplejson.QueryRequest) (annotations []simplejson.Annotation, err error) {
	annotations = []simplejson.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

The underlying http server is implemented by github.com/clambin/go-common/httpserver, which exports its own set of metrics.

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/go-common/httpserver"
	"github.com/clambin/simplejson/v5"
	"time"
)

func main() {
	handlers := map[string]simplejson.Handler{
		"A": &handler{},
		"B": &handler{table: true},
	}
	s, err := simplejson.New(handlers, simplejson.WithHTTPServerOption{Option: httpserver.WithPort{Port: 8080}})

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

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 simplejson.QueryRequest) (simplejson.Response, error) {
	if h.table == false {
		return h.timeSeriesQuery(ctx, req)
	}
	return h.tableQuery(ctx, req)
}

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

	return simplejson.TimeSeriesResponse{
		DataPoints: dataPoints,
	}, nil
}

func (h *handler) tableQuery(_ context.Context, _ simplejson.QueryRequest) (simplejson.TableResponse, error) {
	timestamps := make(simplejson.TimeColumn, 60)
	seriesA := make(simplejson.NumberColumn, 60)
	seriesB := make(simplejson.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 simplejson.TableResponse{Columns: []simplejson.Column{
		{Text: "timestamp", Data: timestamps},
		{Text: "series A", Data: seriesA},
		{Text: "series B", Data: seriesB},
	}}, nil
}

func (h *handler) Annotations(_ simplejson.AnnotationRequest) ([]simplejson.Annotation, error) {
	return []simplejson.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 AdHocFilter

type AdHocFilter struct {
	Value     string `json:"value"`
	Operator  string `json:"operator"`
	Condition string `json:"condition"`
	Key       string `json:"key"`
}

AdHocFilter specifies the ad hoc filters, whose keys & values are returned by the /tag-key and /tag-values endpoints.

type Annotation

type Annotation struct {
	Time    time.Time
	TimeEnd time.Time
	Title   string
	Text    string
	Tags    []string
	Request RequestDetails
}

Annotation response. The annotation endpoint returns a slice of these.

func (Annotation) MarshalJSON

func (annotation Annotation) MarshalJSON() (output []byte, err error)

MarshalJSON converts an Annotation to JSON.

type AnnotationRequest

type AnnotationRequest struct {
	Annotation RequestDetails `json:"annotation"`
	Args
}

AnnotationRequest is a query for annotation.

func (*AnnotationRequest) UnmarshalJSON

func (r *AnnotationRequest) UnmarshalJSON(b []byte) (err error)

UnmarshalJSON unmarshalls a AnnotationRequest from JSON

type AnnotationsFunc

type AnnotationsFunc func(req AnnotationRequest) ([]Annotation, error)

AnnotationsFunc handles requests for annotation

type Args

type Args struct {
	Range        Range `json:"range"`
	AdHocFilters []AdHocFilter
}

Args contains common arguments used by endpoints.

type Column

type Column struct {
	Text string
	Data interface{}
}

Column is a column returned by a TableQuery. Text holds the column's header, Data holds the slice of values and should be a TimeColumn, a StringColumn or a NumberColumn.

func (Column) MarshalEasyJSON

func (v Column) MarshalEasyJSON(w *jwriter.Writer)

MarshalEasyJSON supports easyjson.Marshaler interface

func (Column) MarshalJSON

func (v Column) MarshalJSON() ([]byte, error)

MarshalJSON supports json.Marshaler interface

func (*Column) UnmarshalEasyJSON

func (v *Column) UnmarshalEasyJSON(l *jlexer.Lexer)

UnmarshalEasyJSON supports easyjson.Unmarshaler interface

func (*Column) UnmarshalJSON

func (v *Column) UnmarshalJSON(data []byte) error

UnmarshalJSON supports json.Unmarshaler interface

type DataPoint

type DataPoint struct {
	Timestamp time.Time
	Value     float64
}

DataPoint contains one entry returned by a Query.

func (DataPoint) MarshalJSON

func (d DataPoint) MarshalJSON() ([]byte, error)

MarshalJSON converts a DataPoint to JSON.

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 NumberColumn

type NumberColumn []float64

NumberColumn holds a slice of number values (one per row).

type Option

type Option interface {
	// contains filtered or unexported methods
}

Option specified configuration options for Server

type QueryArgs

type QueryArgs struct {
	Args
	MaxDataPoints uint64 `json:"maxDataPoints"`
}

QueryArgs contains the arguments for a Query.

type QueryFunc

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

QueryFunc handles queries

type QueryMetrics

type QueryMetrics struct {
	// contains filtered or unexported fields
}

func (QueryMetrics) Collect

func (qm QueryMetrics) Collect(ch chan<- prometheus.Metric)

func (QueryMetrics) Describe

func (qm QueryMetrics) Describe(ch chan<- *prometheus.Desc)

type QueryRequest

type QueryRequest struct {
	Targets []Target `json:"targets"`
	QueryArgs
}

QueryRequest is a Query request. For each specified Target, the server will call the appropriate handler's Query or TableQuery function with the provided QueryArgs.

func (*QueryRequest) UnmarshalJSON

func (r *QueryRequest) UnmarshalJSON(b []byte) (err error)

UnmarshalJSON unmarshalls a QueryRequest from JSON

type Range

type Range struct {
	From time.Time `json:"from"`
	To   time.Time `json:"to"`
}

Range specified a start and end time for the data to be returned.

type RequestDetails

type RequestDetails struct {
	Name       string `json:"name"`
	Datasource string `json:"datasource"`
	Enable     bool   `json:"enable"`
	Query      string `json:"query"`
}

RequestDetails specifies which annotation should be returned.

type Response

type Response interface {
	MarshalJSON() ([]byte, error)
}

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

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

func (*Server) Annotations

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

func (*Server) Collect added in v5.1.0

func (s *Server) Collect(metrics chan<- prometheus.Metric)

Collect implements the prometheus.Collector interface

func (*Server) Describe added in v5.1.0

func (s *Server) Describe(descs chan<- *prometheus.Desc)

Describe implements the prometheus.Collector interface

func (*Server) Query

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

func (*Server) Search

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

func (*Server) Serve added in v5.1.0

func (s *Server) Serve() error

Serve starts the SimpleJSon Server.

func (*Server) Shutdown

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

Shutdown stops a running Server.

func (*Server) TagKeys

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

func (*Server) TagValues

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

func (*Server) Targets

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

Targets returns a sorted list of supported targets

type StringColumn

type StringColumn []string

StringColumn holds a slice of string values (one per row).

type TableResponse

type TableResponse struct {
	Columns []Column
}

TableResponse is returned by a TableQuery, i.e. a slice of Column structures.

func (TableResponse) MarshalJSON

func (t TableResponse) MarshalJSON() (output []byte, err error)

MarshalJSON converts a TableResponse to JSON.

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

type Target

type Target struct {
	Name string `json:"target"` // name of the target.
	Type string `json:"type"`   // "timeserie" or "" for timeseries. "table" for table queries.
}

Target specifies the requested target name and type.

type TimeColumn

type TimeColumn []time.Time

TimeColumn holds a slice of time.Time values (one per row).

type TimeSeriesResponse

type TimeSeriesResponse struct {
	Target     string      `json:"target"`
	DataPoints []DataPoint `json:"datapoints"`
}

TimeSeriesResponse is the response from a timeseries Query.

func (TimeSeriesResponse) MarshalEasyJSON

func (v TimeSeriesResponse) MarshalEasyJSON(w *jwriter.Writer)

MarshalEasyJSON supports easyjson.Marshaler interface

func (TimeSeriesResponse) MarshalJSON

func (v TimeSeriesResponse) MarshalJSON() ([]byte, error)

MarshalJSON supports json.Marshaler interface

func (*TimeSeriesResponse) UnmarshalEasyJSON

func (v *TimeSeriesResponse) UnmarshalEasyJSON(l *jlexer.Lexer)

UnmarshalEasyJSON supports easyjson.Unmarshaler interface

func (*TimeSeriesResponse) UnmarshalJSON

func (v *TimeSeriesResponse) UnmarshalJSON(data []byte) error

UnmarshalJSON supports json.Unmarshaler interface

type WithHTTPServerOption

type WithHTTPServerOption struct {
	Option httpserver.Option
}

WithHTTPServerOption will pass the provided option to the underlying HTTP Server

type WithQueryMetrics

type WithQueryMetrics struct {
	Name string
}

WithQueryMetrics will collect the specified metrics to instrument the Server's Handlers.

Directories

Path Synopsis
pkg

Jump to

Keyboard shortcuts

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