simplejson

package module
v6.0.4 Latest Latest
Warning

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

Go to latest
Published: Apr 11, 2023 License: MIT Imports: 17 Imported by: 7

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 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

simplejson.New() creates a SimpleJSON server. The server is implemented as an http router, compatible with net/http:

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

_ = http.ListenAndServe(":8080", r)

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

When provided with the WithQueryMetrics option, 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 router uses PrometheusMetrics, which exports its own set of metrics. See WithHTTPMetrics for details.

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/v6"
	"net/http"
	"time"
)

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

	_ = http.ListenAndServe(":8080", r)
}

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 {
	chi.Router
	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

func (*Server) Annotations

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

func (*Server) Collect

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

Collect implements the prometheus.Collector interface

func (*Server) Describe

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) TagKeys

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

func (*Server) TagValues

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

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 WithHTTPMetrics

type WithHTTPMetrics struct {
	Option middleware.PrometheusMetricsOptions
}

WithHTTPMetrics will configure the http router to gather statistics on SimpleJson endpoint calls and record them as Prometheus metrics

type WithLogger added in v6.0.1

type WithLogger struct {
	Logger *slog.Logger
}

WithLogger configures the router to use the provided slog Logger for logging. If no logger is provided, slog.Default() is used instead.

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