Commit 0e09a3fe by Kyle Brandt Committed by GitHub

Alerting: support alerting on data.Frame (that can be time series) (#22812)

data.Frame (that can be series) are converted to as tsdb.TimeSeriesSlice - so new backend plugins can be shimmed into existing alerting

use sdk v0.31.0
parent 44b7f3ea
......@@ -32,7 +32,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.30.0
github.com/grafana/grafana-plugin-sdk-go v0.31.0
github.com/hashicorp/go-hclog v0.0.0-20180709165350-ff2cf002a8dd
github.com/hashicorp/go-plugin v1.0.1
github.com/hashicorp/go-version v1.1.0
......@@ -65,6 +65,7 @@ require (
github.com/uber/jaeger-lib v2.2.0+incompatible // indirect
github.com/unknwon/com v1.0.1
github.com/urfave/cli/v2 v2.1.1
github.com/xorcare/pointer v1.1.0
github.com/yudai/gojsondiff v1.0.0
github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 // indirect
github.com/yudai/pp v2.0.1+incompatible // indirect
......
......@@ -13,6 +13,7 @@ import (
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/alerting"
"github.com/grafana/grafana/pkg/tsdb"
"github.com/grafana/grafana/pkg/util/errutil"
)
func init() {
......@@ -168,12 +169,30 @@ func (c *QueryCondition) executeQuery(context *alerting.EvalContext, timeRange *
return nil, fmt.Errorf("tsdb.HandleRequest() response error %v", v)
}
result = append(result, v.Series...)
// If there are dataframes but no series on the result
useDataframes := v.Dataframes != nil && (v.Series == nil || len(v.Series) == 0)
if useDataframes { // convert the dataframes to tsdb.TimeSeries
frames, err := tsdb.FramesFromBytes(v.Dataframes)
if err != nil {
return nil, errutil.Wrap("tsdb.HandleRequest() failed to unmarshal arrow dataframes from bytes", err)
}
for _, frame := range frames {
ss, err := tsdb.FrameToSeriesSlice(frame)
if err != nil {
return nil, errutil.Wrapf(err, `tsdb.HandleRequest() failed to convert dataframe "%v" to tsdb.TimeSeriesSlice`, frame.Name)
}
result = append(result, ss...)
}
} else {
result = append(result, v.Series...)
}
queryResultData := map[string]interface{}{}
if context.IsTestRun {
queryResultData["series"] = v.Series
queryResultData["series"] = result
}
if context.IsDebug && v.Meta != nil {
......@@ -181,6 +200,9 @@ func (c *QueryCondition) executeQuery(context *alerting.EvalContext, timeRange *
}
if context.IsTestRun || context.IsDebug {
if useDataframes {
queryResultData["fromDataframe"] = true
}
context.Logs = append(context.Logs, &alerting.ResultLogEntry{
Message: fmt.Sprintf("Condition[%d]: Query Result", c.Index),
Data: simplejson.NewFromAny(queryResultData),
......
......@@ -3,7 +3,9 @@ package conditions
import (
"context"
"testing"
"time"
"github.com/grafana/grafana-plugin-sdk-go/data"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/components/null"
"github.com/grafana/grafana/pkg/components/simplejson"
......@@ -51,6 +53,17 @@ func TestQueryCondition(t *testing.T) {
So(cr.Firing, ShouldBeTrue)
})
Convey("should fire when avg is above 100 on dataframe", func() {
ctx.frame = data.NewFrame("",
data.NewField("time", nil, []time.Time{time.Now()}),
data.NewField("val", nil, []int64{120, 150}),
)
cr, err := ctx.exec()
So(err, ShouldBeNil)
So(cr.Firing, ShouldBeTrue)
})
Convey("Should not fire when avg is below 100", func() {
points := tsdb.NewTimeSeriesPointsFromArgs(90, 0)
ctx.series = tsdb.TimeSeriesSlice{tsdb.NewTimeSeries("test1", points)}
......@@ -60,6 +73,17 @@ func TestQueryCondition(t *testing.T) {
So(cr.Firing, ShouldBeFalse)
})
Convey("Should not fire when avg is below 100 on dataframe", func() {
ctx.frame = data.NewFrame("",
data.NewField("time", nil, []time.Time{time.Now()}),
data.NewField("val", nil, []int64{12, 47}),
)
cr, err := ctx.exec()
So(err, ShouldBeNil)
So(cr.Firing, ShouldBeFalse)
})
Convey("Should fire if only first serie matches", func() {
ctx.series = tsdb.TimeSeriesSlice{
tsdb.NewTimeSeries("test1", tsdb.NewTimeSeriesPointsFromArgs(120, 0)),
......@@ -144,6 +168,7 @@ type queryConditionTestContext struct {
reducer string
evaluator string
series tsdb.TimeSeriesSlice
frame *data.Frame
result *alerting.EvalContext
condition *QueryCondition
}
......@@ -168,10 +193,24 @@ func (ctx *queryConditionTestContext) exec() (*alerting.ConditionResult, error)
ctx.condition = condition
qr := &tsdb.QueryResult{
Series: ctx.series,
}
if ctx.frame != nil {
bFrame, err := data.MarshalArrow(ctx.frame)
if err != nil {
return nil, err
}
qr = &tsdb.QueryResult{
Dataframes: [][]byte{bFrame},
}
}
condition.HandleRequest = func(context context.Context, dsInfo *models.DataSource, req *tsdb.TsdbQuery) (*tsdb.Response, error) {
return &tsdb.Response{
Results: map[string]*tsdb.QueryResult{
"A": {Series: ctx.series},
"A": qr,
},
}, nil
}
......
package tsdb
import (
"fmt"
"time"
"github.com/grafana/grafana-plugin-sdk-go/data"
"github.com/grafana/grafana/pkg/components/null"
"github.com/grafana/grafana/pkg/util/errutil"
)
// SeriesToFrame converts a TimeSeries to a sdk Frame
......@@ -35,3 +38,72 @@ func convertTSDBTimePoint(point TimePoint) (t *time.Time, f *float64) {
}
return
}
// FrameToSeriesSlice converts a frame that is a valid time series as per data.TimeSeriesSchema()
// to a TimeSeriesSlice.
func FrameToSeriesSlice(frame *data.Frame) (TimeSeriesSlice, error) {
tsSchema := frame.TimeSeriesSchema()
if tsSchema.Type == data.TimeSeriesTypeNot {
return nil, fmt.Errorf("input frame is not recognized as a time series")
}
// If Long, make wide
if tsSchema.Type == data.TimeSeriesTypeLong {
var err error
frame, err = data.LongToWide(frame)
if err != nil {
return nil, errutil.Wrap("failed to convert long to wide series when converting from dataframe", err)
}
tsSchema = frame.TimeSeriesSchema()
}
seriesCount := len(tsSchema.ValueIndices)
seriesSlice := make(TimeSeriesSlice, 0, seriesCount)
timeField := frame.Fields[tsSchema.TimeIndex]
timeNullFloatSlice := make([]null.Float, timeField.Len())
for i := 0; i < timeField.Len(); i++ { // built slice of time as epoch ms in null floats
tStamp, err := timeField.FloatAt(i)
if err != nil {
return nil, err
}
timeNullFloatSlice[i] = null.FloatFrom(tStamp)
}
for _, fieldIdx := range tsSchema.ValueIndices { // create a TimeSeries for each value Field
field := frame.Fields[fieldIdx]
ts := &TimeSeries{
Name: field.Name,
Tags: field.Labels.Copy(),
Points: make(TimeSeriesPoints, field.Len()),
}
for rowIdx := 0; rowIdx < field.Len(); rowIdx++ { // for each value in the field, make a TimePoint
val, err := field.FloatAt(rowIdx)
if err != nil {
return nil, errutil.Wrapf(err, "failed to convert frame to tsdb.series, can not convert value %v to float", field.At(rowIdx))
}
ts.Points[rowIdx] = TimePoint{
null.FloatFrom(val),
timeNullFloatSlice[rowIdx],
}
}
seriesSlice = append(seriesSlice, ts)
}
return seriesSlice, nil
}
// FramesFromBytes returns a data.Frame slice from marshalled arrow dataframes.
func FramesFromBytes(bFrames [][]byte) ([]*data.Frame, error) {
frames := make([]*data.Frame, len(bFrames))
for i, bFrame := range bFrames {
var err error
frames[i], err = data.UnmarshalArrow(bFrame)
if err != nil {
return nil, err
}
}
return frames, nil
}
package tsdb
import (
"math"
"testing"
"time"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/grafana/grafana-plugin-sdk-go/data"
"github.com/grafana/grafana/pkg/components/null"
"github.com/stretchr/testify/require"
"github.com/xorcare/pointer"
)
func TestFrameToSeriesSlice(t *testing.T) {
tests := []struct {
name string
frame *data.Frame
seriesSlice TimeSeriesSlice
Err require.ErrorAssertionFunc
}{
{
name: "a wide series",
frame: data.NewFrame("",
data.NewField("Time", nil, []time.Time{
time.Date(2020, 1, 2, 3, 4, 0, 0, time.UTC),
time.Date(2020, 1, 2, 3, 4, 30, 0, time.UTC),
}),
data.NewField(`Values Int64s`, data.Labels{"Animal Factor": "cat"}, []*int64{
nil,
pointer.Int64(3),
}),
data.NewField(`Values Floats`, data.Labels{"Animal Factor": "sloth"}, []float64{
2.0,
4.0,
})),
seriesSlice: TimeSeriesSlice{
&TimeSeries{
Name: "Values Int64s",
Tags: map[string]string{"Animal Factor": "cat"},
Points: TimeSeriesPoints{
TimePoint{null.FloatFrom(math.NaN()), null.FloatFrom(1577934240000)},
TimePoint{null.FloatFrom(3), null.FloatFrom(1577934270000)},
},
},
&TimeSeries{
Name: "Values Floats",
Tags: map[string]string{"Animal Factor": "sloth"},
Points: TimeSeriesPoints{
TimePoint{null.FloatFrom(2), null.FloatFrom(1577934240000)},
TimePoint{null.FloatFrom(4), null.FloatFrom(1577934270000)},
},
},
},
Err: require.NoError,
},
{
name: "a long series",
frame: data.NewFrame("",
data.NewField("Time", nil, []time.Time{
time.Date(2020, 1, 2, 3, 4, 0, 0, time.UTC),
time.Date(2020, 1, 2, 3, 4, 0, 0, time.UTC),
time.Date(2020, 1, 2, 3, 4, 30, 0, time.UTC),
time.Date(2020, 1, 2, 3, 4, 30, 0, time.UTC),
}),
data.NewField("Values Floats", nil, []float64{
1.0,
2.0,
3.0,
4.0,
}),
data.NewField("Values Int64", nil, []int64{
1,
2,
3,
4,
}),
data.NewField("Animal Factor", nil, []string{
"cat",
"sloth",
"cat",
"sloth",
}),
data.NewField("Location", nil, []string{
"Florida",
"Central & South America",
"Florida",
"Central & South America",
})),
seriesSlice: TimeSeriesSlice{
&TimeSeries{
Name: "Values Floats",
Tags: map[string]string{"Animal Factor": "cat", "Location": "Florida"},
Points: TimeSeriesPoints{
TimePoint{null.FloatFrom(1), null.FloatFrom(1577934240000)},
TimePoint{null.FloatFrom(3), null.FloatFrom(1577934270000)},
},
},
&TimeSeries{
Name: "Values Int64",
Tags: map[string]string{"Animal Factor": "cat", "Location": "Florida"},
Points: TimeSeriesPoints{
TimePoint{null.FloatFrom(1), null.FloatFrom(1577934240000)},
TimePoint{null.FloatFrom(3), null.FloatFrom(1577934270000)},
},
},
&TimeSeries{
Name: "Values Floats",
Tags: map[string]string{"Animal Factor": "sloth", "Location": "Central & South America"},
Points: TimeSeriesPoints{
TimePoint{null.FloatFrom(2), null.FloatFrom(1577934240000)},
TimePoint{null.FloatFrom(4), null.FloatFrom(1577934270000)},
},
},
&TimeSeries{
Name: "Values Int64",
Tags: map[string]string{"Animal Factor": "sloth", "Location": "Central & South America"},
Points: TimeSeriesPoints{
TimePoint{null.FloatFrom(2), null.FloatFrom(1577934240000)},
TimePoint{null.FloatFrom(4), null.FloatFrom(1577934270000)},
},
},
},
Err: require.NoError,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
seriesSlice, err := FrameToSeriesSlice(tt.frame)
tt.Err(t, err)
if diff := cmp.Diff(tt.seriesSlice, seriesSlice, cmpopts.EquateNaNs()); diff != "" {
t.Errorf("Result mismatch (-want +got):\n%s", diff)
}
})
}
}
......@@ -7,6 +7,7 @@ import (
"fmt"
"math"
"sort"
"strconv"
"strings"
"time"
......@@ -344,6 +345,151 @@ func (f *Field) Nullable() bool {
return f.Type().Nullable()
}
// FloatAt returns a float64 at the specified index idx.
// It will panic if idx is out of range.
// If the Field type is numeric and the value at idx is nil, NaN is returned. Precision may be lost on large numbers.
// If the Field type is a bool then 0 is return if false or nil, and 1 if true.
// If the Field type is time.Time, then the millisecond epoch representation of the time
// is returned, or NaN is the value is nil.
// If the Field type is a string, then strconv.ParseFloat is called on it and will return
// an error if ParseFloat errors. If the value is nil, NaN is returned.
func (f *Field) FloatAt(idx int) (float64, error) {
switch f.Type() {
case FieldTypeInt8:
return float64(f.At(idx).(int8)), nil
case FieldTypeNullableInt8:
iv := f.At(idx).(*int8)
if iv == nil {
return math.NaN(), nil
}
return float64(*iv), nil
case FieldTypeInt16:
return float64(f.At(idx).(int16)), nil
case FieldTypeNullableInt16:
iv := f.At(idx).(*int16)
if iv == nil {
return math.NaN(), nil
}
return float64(*iv), nil
case FieldTypeInt32:
return float64(f.At(idx).(int32)), nil
case FieldTypeNullableInt32:
iv := f.At(idx).(*int32)
if iv == nil {
return math.NaN(), nil
}
return float64(*iv), nil
case FieldTypeInt64:
return float64(f.At(idx).(int64)), nil
case FieldTypeNullableInt64:
iv := f.At(idx).(*int64)
if iv == nil {
return math.NaN(), nil
}
return float64(*iv), nil
case FieldTypeUint8:
return float64(f.At(idx).(uint8)), nil
case FieldTypeNullableUint8:
uiv := f.At(idx).(*uint8)
if uiv == nil {
return math.NaN(), nil
}
return float64(*uiv), nil
case FieldTypeUint16:
return float64(f.At(idx).(uint16)), nil
case FieldTypeNullableUint16:
uiv := f.At(idx).(*uint16)
if uiv == nil {
return math.NaN(), nil
}
return float64(*uiv), nil
case FieldTypeUint32:
return float64(f.At(idx).(uint32)), nil
case FieldTypeNullableUint32:
uiv := f.At(idx).(*uint32)
if uiv == nil {
return math.NaN(), nil
}
return float64(*uiv), nil
// TODO: third param for loss of precision?
// Maybe something in math/big can help with this (also see https://github.com/golang/go/issues/29463).
case FieldTypeUint64:
return float64(f.At(idx).(uint64)), nil
case FieldTypeNullableUint64:
uiv := f.At(idx).(*uint64)
if uiv == nil {
return math.NaN(), nil
}
return float64(*uiv), nil
case FieldTypeFloat32:
return float64(f.At(idx).(float32)), nil
case FieldTypeNullableFloat32:
fv := f.At(idx).(*float32)
if fv == nil {
return math.NaN(), nil
}
return float64(*fv), nil
case FieldTypeFloat64:
return f.At(idx).(float64), nil
case FieldTypeNullableFloat64:
fv := f.At(idx).(*float64)
if fv == nil {
return math.NaN(), nil
}
return *fv, nil
case FieldTypeString:
s := f.At(idx).(string)
ft, err := strconv.ParseFloat(s, 64)
if err != nil {
return 0, err
}
return ft, nil
case FieldTypeNullableString:
s := f.At(idx).(*string)
if s == nil {
return math.NaN(), nil
}
ft, err := strconv.ParseFloat(*s, 64)
if err != nil {
return 0, err
}
return ft, nil
case FieldTypeBool:
if f.At(idx).(bool) {
return 1, nil
}
return 0, nil
case FieldTypeNullableBool:
b := f.At(idx).(*bool)
if b == nil || !*b {
return 0, nil
}
return 1, nil
case FieldTypeTime:
return float64(f.At(idx).(time.Time).UnixNano() / int64(time.Millisecond)), nil
case FieldTypeNullableTime:
t := f.At(idx).(*time.Time)
if t == nil {
return math.NaN(), nil
}
return float64(f.At(idx).(*time.Time).UnixNano() / int64(time.Millisecond)), nil
}
return 0, fmt.Errorf("unsupported field type %T", f.Type())
}
// SetConfig modifies the Field's Config property to
// be set to conf and returns the Field.
func (f *Field) SetConfig(conf *FieldConfig) *Field {
......@@ -507,6 +653,12 @@ func (f *Frame) RowLen() (int, error) {
return l, nil
}
// FloatAt returns a float64 representation of value of the specified fieldIdx and rowIdx as per Field.FloatAt().
// It will panic if either the fieldIdx or rowIdx are out of range.
func (f *Frame) FloatAt(fieldIdx int, rowIdx int) (float64, error) {
return f.Fields[fieldIdx].FloatAt(rowIdx)
}
// FrameTestCompareOptions returns go-cmp testing options to allow testing of Frame equivelnce.
// The intent is to only use this for testing.
func FrameTestCompareOptions() []cmp.Option {
......@@ -538,7 +690,75 @@ func FrameTestCompareOptions() []cmp.Option {
}
return *x == *y
})
f64Ptrs := cmp.Comparer(func(x, y *float64) bool {
if x == nil && y == nil {
return true
}
if y == nil {
if math.IsNaN(float64(*x)) {
return true
}
if math.IsInf(float64(*x), 1) {
return true
}
if math.IsInf(float64(*x), -1) {
return true
}
}
if x == nil {
if math.IsNaN(float64(*y)) {
return true
}
if math.IsInf(float64(*y), 1) {
return true
}
if math.IsInf(float64(*y), -1) {
return true
}
}
return *x == *y
})
f64s := cmp.Comparer(func(x, y float64) bool {
return (math.IsNaN(x) && math.IsNaN(y)) ||
(math.IsInf(x, 1) && math.IsInf(y, 1)) ||
(math.IsInf(x, -1) && math.IsInf(y, -1)) ||
x == y
})
f32Ptrs := cmp.Comparer(func(x, y *float32) bool {
if x == nil && y == nil {
return true
}
if y == nil {
if math.IsNaN(float64(*x)) {
return true
}
if math.IsInf(float64(*x), 1) {
return true
}
if math.IsInf(float64(*x), -1) {
return true
}
}
if x == nil {
if math.IsNaN(float64(*y)) {
return true
}
if math.IsInf(float64(*y), 1) {
return true
}
if math.IsInf(float64(*y), -1) {
return true
}
}
return *x == *y
})
f32s := cmp.Comparer(func(x, y float32) bool {
return (math.IsNaN(float64(x)) && math.IsNaN(float64(y))) ||
(math.IsInf(float64(x), 1) && math.IsInf(float64(y), 1)) ||
(math.IsInf(float64(x), -1) && math.IsInf(float64(y), -1)) ||
x == y
})
unexportedField := cmp.AllowUnexported(Field{})
return []cmp.Option{confFloats, unexportedField, cmpopts.EquateEmpty()}
return []cmp.Option{f32s, f32Ptrs, f64s, f64Ptrs, confFloats, unexportedField, cmpopts.EquateEmpty()}
}
......@@ -542,6 +542,27 @@ func (p FieldType) Nullable() bool {
return false
}
// Numeric returns if Field type is a nullable type
func (p FieldType) Numeric() bool {
switch p {
case FieldTypeInt8, FieldTypeInt16, FieldTypeInt32, FieldTypeInt64:
return true
case FieldTypeNullableInt8, FieldTypeNullableInt16, FieldTypeNullableInt32, FieldTypeNullableInt64:
return true
case FieldTypeUint8, FieldTypeUint16, FieldTypeUint32, FieldTypeUint64:
return true
case FieldTypeNullableUint8, FieldTypeNullableUint16, FieldTypeNullableUint32, FieldTypeNullableUint64:
return true
case FieldTypeFloat32, FieldTypeFloat64:
return true
case FieldTypeNullableFloat32, FieldTypeNullableFloat64:
return true
}
return false
}
// numericFieldTypes is an array of FieldTypes that are numeric.
var numericFieldTypes = [...]FieldType{
FieldTypeInt8, FieldTypeInt16, FieldTypeInt32, FieldTypeInt64,
......
BSD 3-Clause License
Copyright © 2019, Vasiliy Vasilyuk <xorcare@gmail.com>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
# Based on https://git.io/fjkGc
# The full path to the main package is use in the
# imports tool to format imports correctly.
NAMESPACE = github.com/xorcare/pointer
# The name of the file recommended in the standard
# documentation go test -cover and used codecov.io
# to check code coverage.
COVER_FILE ?= coverage.out
# Main targets.
.DEFAULT_GOAL := help
.PHONY: build
build: ## Build the project binary
@go build ./...
.PHONY: ci
ci: check ## Target for integration with ci pipeline
.PHONY: check
check: static test build ## Check project with static checks and unit tests
.PHONY: help
help: ## Print this help
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | \
awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
.PHONY: imports
imports: tools ## Check and fix import section by import rules
@test -z $$(for d in $$(go list -f {{.Dir}} ./...); do goimports -e -l -local $(NAMESPACE) -w $$d/*.go; done)
.PHONY: lint
lint: tools ## Check the project with lint
@golint -set_exit_status ./...
.PHONY: static
static: imports vet lint ## Run static checks (lint, imports, vet, ...) all over the project
.PHONY: test
test: ## Run unit tests
@go test ./... -count=1 -race
@go test ./... -count=1 -coverprofile=$(COVER_FILE) -covermode=atomic $d
@go tool cover -func=$(COVER_FILE) | grep ^total
CDTOOLS ?= @cd internal/tools &&
.PHONY: tools
tools: ## Install all needed tools, e.g. for static checks
$(CDTOOLS) \
go install \
golang.org/x/lint/golint \
golang.org/x/tools/cmd/goimports
.PHONY: toolsup
toolsup: ## Update all needed tools, e.g. for static checks
$(CDTOOLS) \
go mod tidy && \
go get \
golang.org/x/lint/golint@latest \
golang.org/x/tools/cmd/goimports@latest && \
go mod download && \
go mod verify
$(MAKE) tools
.PHONY: vet
vet: ## Check the project with vet
@go vet ./...
# pointer
[![Build Status](https://travis-ci.org/xorcare/pointer.svg?branch=master)](https://travis-ci.org/xorcare/pointer)
[![codecov](https://codecov.io/gh/xorcare/pointer/badge.svg)](https://codecov.io/gh/xorcare/pointer)
[![Go Report Card](https://goreportcard.com/badge/github.com/xorcare/pointer)](https://goreportcard.com/report/github.com/xorcare/pointer)
[![GoDoc](https://godoc.org/github.com/xorcare/pointer?status.svg)](https://godoc.org/github.com/xorcare/pointer)
Package pointer contains helper routines for simplifying the creation
of optional fields of basic type.
## Installation
```bash
go get github.com/xorcare/pointer
```
## Examples
Examples of using the library are presented on [godoc.org][GDE]
and in the [source library code](https://git.io/Jeg75).
## FAQ
| Question | Source |
| -------- | ------ |
| How to set **bool** pointer in a struct literal or variable? | `var _ *bool = pointer.Bool(true)` |
| How to set **byte** pointer in a struct literal or variable? | `var _ *byte = pointer.Byte(1)` |
| How to set **complex64** pointer in a struct literal or variable? | `var _ *complex64 = pointer.Complex64(1.1)` |
| How to set **complex128** pointer in a struct literal or variable? | `var _ *complex128 = pointer.Complex128(1.1)` |
| How to set **float32** pointer in a struct literal or variable? | `var _ *float32 = pointer.Float32(1.1)` |
| How to set **float64** pointer in a struct literal or variable? | `var _ *float64 = pointer.Float64(1.1)` |
| How to set **int** pointer in a struct literal or variable? | `var _ *int = pointer.Int(1)` |
| How to set **int8** pointer in a struct literal or variable? | `var _ *int8 = pointer.Int8(8)` |
| How to set **int16** pointer in a struct literal or variable? | `var _ *int16 = pointer.Int16(16)` |
| How to set **int32** pointer in a struct literal or variable? | `var _ *int32 = pointer.Int32(32)` |
| How to set **int64** pointer in a struct literal or variable? | `var _ *int64 = pointer.Int64(64)` |
| How to set **rune** pointer in a struct literal or variable? | `var _ *rune = pointer.Rune(1)` |
| How to set **string** pointer in a struct literal or variable? | `var _ *string = pointer.String("ptr")` |
| How to set **uint** pointer in a struct literal or variable? | `var _ *uint = pointer.Uint(1)` |
| How to set **uint8** pointer in a struct literal or variable? | `var _ *uint8 = pointer.Uint8(8)` |
| How to set **uint16** pointer in a struct literal or variable? | `var _ *uint16 = pointer.Uint16(16)` |
| How to set **uint32** pointer in a struct literal or variable? | `var _ *uint32 = pointer.Uint32(32)` |
| How to set **uint64** pointer in a struct literal or variable? | `var _ *uint64 = pointer.Uint64(64)` |
| How to set **time.Time** pointer in a struct literal or variable? | `var _ *time.Time = pointer.Time(time.Now())` |
| How to set **time.Duration** pointer in a struct literal or variable? | `var _ *time.Duration = pointer.Duration(time.Hour)` |
## License
© Vasiliy Vasilyuk, 2019-2020
Released under the [BSD 3-Clause License][LICENSE].
[LICENSE]: https://git.io/Jeg7Q 'BSD 3-Clause "New" or "Revised" License'
[GDE]: https://godoc.org/github.com/xorcare/pointer#pkg-examples 'Examples of using package pointer'
module github.com/xorcare/pointer
go 1.13
// Copyright © 2019-2020 Vasiliy Vasilyuk. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package pointer contains helper routines for simplifying the creation
// of optional fields of basic type.
package pointer
import (
"reflect"
"time"
)
// Any is a helper routine that allocates a new interface value
// to store v and returns a pointer to it.
//
// // Usage: var _ *Type = pointer.Any(Type(value) | value).(*Type)
//
// var _ *bool = pointer.Any(true).(*bool)
// var _ *byte = pointer.Any(byte(1)).(*byte)
// var _ *complex64 = pointer.Any(complex64(1.1)).(*complex64)
// var _ *complex128 = pointer.Any(complex128(1.1)).(*complex128)
// var _ *float32 = pointer.Any(float32(1.1)).(*float32)
// var _ *float64 = pointer.Any(float64(1.1)).(*float64)
// var _ *int = pointer.Any(int(1)).(*int)
// var _ *int8 = pointer.Any(int8(8)).(*int8)
// var _ *int16 = pointer.Any(int16(16)).(*int16)
// var _ *int32 = pointer.Any(int32(32)).(*int32)
// var _ *int64 = pointer.Any(int64(64)).(*int64)
// var _ *rune = pointer.Any(rune(1)).(*rune)
// var _ *string = pointer.Any("ptr").(*string)
// var _ *uint = pointer.Any(uint(1)).(*uint)
// var _ *uint8 = pointer.Any(uint8(8)).(*uint8)
// var _ *uint16 = pointer.Any(uint16(16)).(*uint16)
// var _ *uint32 = pointer.Any(uint32(32)).(*uint32)
// var _ *uint64 = pointer.Any(uint64(64)).(*uint64)
// var _ *uintptr = pointer.Any(uintptr(64)).(*uintptr)
func Any(v interface{}) interface{} {
r := reflect.New(reflect.TypeOf(v))
reflect.ValueOf(r.Interface()).Elem().Set(reflect.ValueOf(v))
return r.Interface()
}
// Bool is a helper routine that allocates a new bool value
// to store v and returns a pointer to it.
func Bool(v bool) *bool { return &v }
// Byte is a helper routine that allocates a new byte value
// to store v and returns a pointer to it.
func Byte(v byte) *byte { return &v }
// Complex64 is a helper routine that allocates a new complex64 value
// to store v and returns a pointer to it.
func Complex64(v complex64) *complex64 { return &v }
// Complex128 is a helper routine that allocates a new complex128 value
// to store v and returns a pointer to it.
func Complex128(v complex128) *complex128 { return &v }
// Float32 is a helper routine that allocates a new float32 value
// to store v and returns a pointer to it.
func Float32(v float32) *float32 { return &v }
// Float64 is a helper routine that allocates a new float64 value
// to store v and returns a pointer to it.
func Float64(v float64) *float64 { return &v }
// Int is a helper routine that allocates a new int value
// to store v and returns a pointer to it.
func Int(v int) *int { return &v }
// Int8 is a helper routine that allocates a new int8 value
// to store v and returns a pointer to it.
func Int8(v int8) *int8 { return &v }
// Int16 is a helper routine that allocates a new int16 value
// to store v and returns a pointer to it.
func Int16(v int16) *int16 { return &v }
// Int32 is a helper routine that allocates a new int32 value
// to store v and returns a pointer to it.
func Int32(v int32) *int32 { return &v }
// Int64 is a helper routine that allocates a new int64 value
// to store v and returns a pointer to it.
func Int64(v int64) *int64 { return &v }
// Rune is a helper routine that allocates a new rune value
// to store v and returns a pointer to it.
func Rune(v rune) *rune { return &v }
// String is a helper routine that allocates a new string value
// to store v and returns a pointer to it.
func String(v string) *string { return &v }
// Uint is a helper routine that allocates a new uint value
// to store v and returns a pointer to it.
func Uint(v uint) *uint { return &v }
// Uint8 is a helper routine that allocates a new uint8 value
// to store v and returns a pointer to it.
func Uint8(v uint8) *uint8 { return &v }
// Uint16 is a helper routine that allocates a new uint16 value
// to store v and returns a pointer to it.
func Uint16(v uint16) *uint16 { return &v }
// Uint32 is a helper routine that allocates a new uint32 value
// to store v and returns a pointer to it.
func Uint32(v uint32) *uint32 { return &v }
// Uint64 is a helper routine that allocates a new uint64 value
// to store v and returns a pointer to it.
func Uint64(v uint64) *uint64 { return &v }
// Uintptr is a helper routine that allocates a new uintptr value
// to store v and returns a pointer to it.
func Uintptr(v uintptr) *uintptr { return &v }
// Time is a helper routine that allocates a new time.Time value
// to store v and returns a pointer to it.
func Time(v time.Time) *time.Time { return &v }
// Duration is a helper routine that allocates a new time.Duration value
// to store v and returns a pointer to it.
func Duration(v time.Duration) *time.Duration { return &v }
......@@ -148,7 +148,7 @@ 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.30.0
# github.com/grafana/grafana-plugin-sdk-go v0.31.0
github.com/grafana/grafana-plugin-sdk-go/backend/plugin
github.com/grafana/grafana-plugin-sdk-go/data
github.com/grafana/grafana-plugin-sdk-go/genproto/pluginv2
......@@ -285,6 +285,8 @@ github.com/uber/jaeger-lib/metrics
github.com/unknwon/com
# github.com/urfave/cli/v2 v2.1.1
github.com/urfave/cli/v2
# github.com/xorcare/pointer v1.1.0
github.com/xorcare/pointer
# github.com/yudai/gojsondiff v1.0.0
github.com/yudai/gojsondiff
github.com/yudai/gojsondiff/formatter
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment