-
Notifications
You must be signed in to change notification settings - Fork 498
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add util class to support to add annotations to Grafana #378
Merged
Merged
Changes from 8 commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
df4b99d
Add util class for adding annotation to grafana
qiffang b7f67cf
Add util class for adding annotation to grafana
qiffang b04d549
Add util class for adding annotation to grafana
qiffang 8caa92c
Add util class for adding annotation to grafana
qiffang a8d1ef1
Add util class for adding annotation to grafana
qiffang b3487a2
Add util class for adding annotation to grafana
qiffang 390ac71
Add util class for adding annotation to grafana
qiffang c616ebb
Add util class for adding annotation to grafana
qiffang caf2dbd
add annotation util
qiffang 7c05517
Add-UtilClass
qiffang b1ea6af
Add-UtilClass
qiffang 6037c77
Add-UtilClass
qiffang File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,140 @@ | ||
// 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 { | ||
DashboardId int `json: "dashboardId"` | ||
PanelId int `json: "panelId"` | ||
TimestampInMilliSec int64 `json: time` | ||
Tags []string `json: tags` | ||
Text string `json: text` | ||
} | ||
|
||
//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() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
// 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" | ||
"reflect" | ||
"testing" | ||
"time" | ||
) | ||
|
||
func TestAnnotationGetBody(t *testing.T) { | ||
tags := []string{"1", "2", "3"} | ||
annotation := Annotation{ | ||
DashboardId: 1, | ||
PanelId: 2, | ||
Tags: tags, | ||
TimestampInMilliSec: time.Now().Unix() * 1000, | ||
Text: "abc", | ||
} | ||
|
||
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")) | ||
} | ||
|
||
func TestErrorMetric(t *testing.T) { | ||
metric := initErrorMetric() | ||
metric.Inc() | ||
metric.Inc() | ||
|
||
g := gomega.NewGomegaWithT(t) | ||
|
||
v := reflect.ValueOf(metric).Elem() | ||
|
||
g.Expect(fmt.Sprintf("%v", v.FieldByName("valInt"))).To(gomega.Equal("2")) | ||
} |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
DashboardId
andPanelId
are optional, so we should omit these field is empty (nil for pointer, 0 for numeric type), the notation isjson:"dashboardId,omitempty"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So do
tags
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
dashboardId和pannelId明显是不能忽略的啊,要不然知道加哪张图上
tags accept
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The scenario is that users may add the global annotation (without
DashboardId
andPanelId
) to grafana and query it in multiple dashboards. To me it is the common case because when I am coding I cannot ensure the grafana dashboard id and panel id is consistent in different clusters and different e2e tests or stability tests.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can we post an annotation without dashboardID and panelID?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, https://grafana.com/docs/http_api/annotations/#create-annotation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can leave them optional for a wider range of uses. @qiffang
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From the original requirement, we want to add annotation in a graph(panel).
Of course, we can support it.
Accept