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 ¶
- type AnnotationsFunc
- type Endpoints
- type Handler
- type QueryFunc
- type QueryMetrics
- type Server
- func (s *Server) Annotations(w http.ResponseWriter, req *http.Request)
- func (s *Server) Query(w http.ResponseWriter, req *http.Request)
- func (s *Server) Run() error
- func (s *Server) Search(w http.ResponseWriter, _ *http.Request)
- func (s *Server) Shutdown(timeout time.Duration) (err error)
- func (s *Server) TagKeys(w http.ResponseWriter, req *http.Request)
- func (s *Server) TagValues(w http.ResponseWriter, req *http.Request)
- func (s *Server) Targets() (targets []string)
- type TagKeysFunc
- type TagValuesFunc
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 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 ¶
Server receives SimpleJSON requests from Grafana and dispatches them to the handler that serves the specified target.
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) Search ¶ added in v3.5.0
func (s *Server) Search(w http.ResponseWriter, _ *http.Request)
func (*Server) TagKeys ¶ added in v3.5.0
func (s *Server) TagKeys(w http.ResponseWriter, req *http.Request)
type TagKeysFunc ¶
TagKeysFunc returns supported tag names
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
|
|