Skip to content

Commit

Permalink
Add util class to support to add annotations to Grafana (#378)
Browse files Browse the repository at this point in the history
* Add util class for adding annotation to grafana
  • Loading branch information
qiffang authored and weekface committed Apr 15, 2019
1 parent 59d080b commit 37d9c5f
Show file tree
Hide file tree
Showing 2 changed files with 193 additions and 0 deletions.
147 changes: 147 additions & 0 deletions tests/pkg/metrics/annotation_util.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
// Copyright 2018 PingCAP, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// See the License for the specific language governing permissions and
// limitations under the License.

package metrics

import (
"bytes"
"encoding/json"
"fmt"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
"net"
"net/http"
"net/url"
"os"
"path"
"sync"
)

//Client request grafana API on a set of resource paths.
type client struct {
// base is the root URL for all invocations of the client
baseUrl url.URL
client *http.Client
}

//Annotation is a specification of the desired behavior of adding annotation
type Annotation struct {
AnnotationOptions
Text string `json:"text"`
Tags []string `json:"tags"`
TimestampInMilliSec int64 `json:"time"`
}

//AnnotationOptions is the query options to a standard REST list call.
type AnnotationOptions struct {
DashboardId int `json:"dashboardId, omitempty"`
PanelId int `json:"panelId, omitempty"`
IsRegin bool `json:"isRegion, omitempty"`
TimeEnd int64 `json:"timeEnd, omitempty"`
}

//NewClient creats a new grafanaClient. This client performs rest functions
//such as Get, Post on specified paths.
func NewClient(grafanaUrl string, userName string, password string, prometheusExporterPort int) (*client, error) {
u, err := url.Parse(grafanaUrl)
if err != nil {
return nil, err
}

initFunc(prometheusExporterPort)
u.User = url.UserPassword(userName, password)
return &client{
baseUrl: *u,
client: &http.Client{},
}, nil
}

func (annotation Annotation) getBody() ([]byte, error) {
body, err := json.Marshal(annotation)
if err != nil {
return nil, err
}

return body, nil
}

var (
initedOnce sync.Once
counterMetric prometheus.Counter
annotationSubPath = "api/annotations"
)

//initFunc is called with sync.Once, we use sync.Once to keep the thread safe.
func initFunc(port int) {
initedOnce.Do(func() {
counterMetric = initErrorMetric()
prometheus.MustRegister(counterMetric)
mux := http.NewServeMux()

l, err := net.Listen("tcp", fmt.Sprintf(":%d", port))
if err != nil {
fmt.Fprintf(os.Stderr, "listening port %d failed, %v", port, err)
panic(err)
}

mux.Handle("/metrics", promhttp.Handler())
srv := &http.Server{Handler: mux}
go srv.Serve(l)
})
}

func initErrorMetric() prometheus.Counter {
return prometheus.NewCounter(prometheus.CounterOpts{
Name: "error_count",
Help: "record error count",
ConstLabels: map[string]string{"fortest": "true"},
})
}

//IncreErrorCountWithAnno increments the errorcount by 1,
//and add the annotation to grafanan.
func (cli *client) AddAnnotation(annotation Annotation) error {
body, err := annotation.getBody()
if err != nil {
return fmt.Errorf("create request body faield, %v", err)
}

req, err := http.NewRequest("POST", cli.getAnnotationPath(), bytes.NewBuffer(body))
if err != nil {
return fmt.Errorf("create request failed, %v", err)
}

req.Header.Add("Accept", "application/json, text/plain, */*")
req.Header.Add("Content-Type", "application/json;charset=UTF-8")
resp, error := cli.client.Do(req)
if error != nil {
return fmt.Errorf("add annotation faield, %v", err)
}
defer resp.Body.Close()

if resp.StatusCode != http.StatusOK {
return fmt.Errorf("add annotation faield, statusCode=%v", resp.Status)
}

return nil
}

func (cli *client) IncrErrorCount() {
counterMetric.Inc()
}

func (cli *client) getAnnotationPath() string {
u := cli.baseUrl
u.Path = path.Join(cli.baseUrl.Path, annotationSubPath)
return u.String()
}
46 changes: 46 additions & 0 deletions tests/pkg/metrics/annotation_util_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// Copyright 2018 PingCAP, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// See the License for the specific language governing permissions and
// limitations under the License.
package metrics

import (
"encoding/json"
"fmt"
"github.com/onsi/gomega"
"testing"
)

func TestAnnotationGetBody(t *testing.T) {
tags := []string{"1", "2", "3"}

options := AnnotationOptions{
DashboardId: 1,
PanelId: 2,
}

annotation := Annotation{
Tags: tags,
TimestampInMilliSec: 1,
Text: "abc",
}

annotation.AnnotationOptions = options

b, _ := annotation.getBody()
re := make(map[string]interface{})
json.Unmarshal(b, &re)

g := gomega.NewGomegaWithT(t)
g.Expect(fmt.Sprintf("%v", re["dashboardId"])).To(gomega.Equal(fmt.Sprintf("%v", 1)))
g.Expect(re["text"]).To(gomega.Equal("abc"))
g.Expect(re["time"]).To(gomega.Equal(float64(1)))
}

0 comments on commit 37d9c5f

Please sign in to comment.