New upstream version 0.0~git20160515.0.539764b
Tim Potter
7 years ago
0 | Apache License | |
1 | ||
2 | Version 2.0, January 2004 | |
3 | http://www.apache.org/licenses/ | |
4 | ||
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION | |
6 | ||
7 | 1. Definitions. | |
8 | ||
9 | "License" shall mean the terms and conditions for use, reproduction, | |
10 | and distribution as defined by Sections 1 through 9 of this document. | |
11 | ||
12 | "Licensor" shall mean the copyright owner or entity authorized by the | |
13 | copyright owner that is granting the License. | |
14 | ||
15 | "Legal Entity" shall mean the union of the acting entity and all other | |
16 | entities that control, are controlled by, or are under common control | |
17 | with that entity. For the purposes of this definition, "control" means | |
18 | (i) the power, direct or indirect, to cause the direction or management | |
19 | of such entity, whether by contract or otherwise, or (ii) ownership | |
20 | of fifty percent (50%) or more of the outstanding shares, or (iii) | |
21 | beneficial ownership of such entity. | |
22 | ||
23 | "You" (or "Your") shall mean an individual or Legal Entity exercising | |
24 | permissions granted by this License. | |
25 | ||
26 | "Source" form shall mean the preferred form for making modifications, | |
27 | including but not limited to software source code, documentation source, | |
28 | and configuration files. | |
29 | ||
30 | "Object" form shall mean any form resulting from mechanical transformation | |
31 | or translation of a Source form, including but not limited to compiled | |
32 | object code, generated documentation, and conversions to other media | |
33 | types. | |
34 | ||
35 | "Work" shall mean the work of authorship, whether in Source or | |
36 | Object form, made available under the License, as indicated by a copyright | |
37 | notice that is included in or attached to the work (an example is provided | |
38 | in the Appendix below). | |
39 | ||
40 | "Derivative Works" shall mean any work, whether in Source or Object form, | |
41 | that is based on (or derived from) the Work and for which the editorial | |
42 | revisions, annotations, elaborations, or other modifications represent, | |
43 | as a whole, an original work of authorship. For the purposes of this | |
44 | License, Derivative Works shall not include works that remain separable | |
45 | from, or merely link (or bind by name) to the interfaces of, the Work | |
46 | and Derivative Works thereof. | |
47 | ||
48 | "Contribution" shall mean any work of authorship, including the | |
49 | original version of the Work and any modifications or additions to | |
50 | that Work or Derivative Works thereof, that is intentionally submitted | |
51 | to Licensor for inclusion in the Work by the copyright owner or by an | |
52 | individual or Legal Entity authorized to submit on behalf of the copyright | |
53 | owner. For the purposes of this definition, "submitted" means any form of | |
54 | electronic, verbal, or written communication sent to the Licensor or its | |
55 | representatives, including but not limited to communication on electronic | |
56 | mailing lists, source code control systems, and issue tracking systems | |
57 | that are managed by, or on behalf of, the Licensor for the purpose of | |
58 | discussing and improving the Work, but excluding communication that is | |
59 | conspicuously marked or otherwise designated in writing by the copyright | |
60 | owner as "Not a Contribution." | |
61 | ||
62 | "Contributor" shall mean Licensor and any individual or Legal Entity | |
63 | on behalf of whom a Contribution has been received by Licensor and | |
64 | subsequently incorporated within the Work. | |
65 | ||
66 | 2. Grant of Copyright License. | |
67 | Subject to the terms and conditions of this License, each Contributor | |
68 | hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, | |
69 | royalty-free, irrevocable copyright license to reproduce, prepare | |
70 | Derivative Works of, publicly display, publicly perform, sublicense, and | |
71 | distribute the Work and such Derivative Works in Source or Object form. | |
72 | ||
73 | 3. Grant of Patent License. | |
74 | Subject to the terms and conditions of this License, each Contributor | |
75 | hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, | |
76 | royalty- free, irrevocable (except as stated in this section) patent | |
77 | license to make, have made, use, offer to sell, sell, import, and | |
78 | otherwise transfer the Work, where such license applies only to those | |
79 | patent claims licensable by such Contributor that are necessarily | |
80 | infringed by their Contribution(s) alone or by combination of | |
81 | their Contribution(s) with the Work to which such Contribution(s) | |
82 | was submitted. If You institute patent litigation against any entity | |
83 | (including a cross-claim or counterclaim in a lawsuit) alleging that the | |
84 | Work or a Contribution incorporated within the Work constitutes direct | |
85 | or contributory patent infringement, then any patent licenses granted | |
86 | to You under this License for that Work shall terminate as of the date | |
87 | such litigation is filed. | |
88 | ||
89 | 4. Redistribution. | |
90 | You may reproduce and distribute copies of the Work or Derivative Works | |
91 | thereof in any medium, with or without modifications, and in Source or | |
92 | Object form, provided that You meet the following conditions: | |
93 | ||
94 | a. You must give any other recipients of the Work or Derivative Works | |
95 | a copy of this License; and | |
96 | ||
97 | b. You must cause any modified files to carry prominent notices stating | |
98 | that You changed the files; and | |
99 | ||
100 | c. You must retain, in the Source form of any Derivative Works that | |
101 | You distribute, all copyright, patent, trademark, and attribution | |
102 | notices from the Source form of the Work, excluding those notices | |
103 | that do not pertain to any part of the Derivative Works; and | |
104 | ||
105 | d. If the Work includes a "NOTICE" text file as part of its | |
106 | distribution, then any Derivative Works that You distribute must | |
107 | include a readable copy of the attribution notices contained | |
108 | within such NOTICE file, excluding those notices that do not | |
109 | pertain to any part of the Derivative Works, in at least one of | |
110 | the following places: within a NOTICE text file distributed as part | |
111 | of the Derivative Works; within the Source form or documentation, | |
112 | if provided along with the Derivative Works; or, within a display | |
113 | generated by the Derivative Works, if and wherever such third-party | |
114 | notices normally appear. The contents of the NOTICE file are for | |
115 | informational purposes only and do not modify the License. You | |
116 | may add Your own attribution notices within Derivative Works that | |
117 | You distribute, alongside or as an addendum to the NOTICE text | |
118 | from the Work, provided that such additional attribution notices | |
119 | cannot be construed as modifying the License. You may add Your own | |
120 | copyright statement to Your modifications and may provide additional | |
121 | or different license terms and conditions for use, reproduction, or | |
122 | distribution of Your modifications, or for any such Derivative Works | |
123 | as a whole, provided Your use, reproduction, and distribution of the | |
124 | Work otherwise complies with the conditions stated in this License. | |
125 | ||
126 | 5. Submission of Contributions. | |
127 | Unless You explicitly state otherwise, any Contribution intentionally | |
128 | submitted for inclusion in the Work by You to the Licensor shall be | |
129 | under the terms and conditions of this License, without any additional | |
130 | terms or conditions. Notwithstanding the above, nothing herein shall | |
131 | supersede or modify the terms of any separate license agreement you may | |
132 | have executed with Licensor regarding such Contributions. | |
133 | ||
134 | 6. Trademarks. | |
135 | This License does not grant permission to use the trade names, trademarks, | |
136 | service marks, or product names of the Licensor, except as required for | |
137 | reasonable and customary use in describing the origin of the Work and | |
138 | reproducing the content of the NOTICE file. | |
139 | ||
140 | 7. Disclaimer of Warranty. | |
141 | Unless required by applicable law or agreed to in writing, Licensor | |
142 | provides the Work (and each Contributor provides its Contributions) on | |
143 | an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either | |
144 | express or implied, including, without limitation, any warranties or | |
145 | conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR | |
146 | A PARTICULAR PURPOSE. You are solely responsible for determining the | |
147 | appropriateness of using or redistributing the Work and assume any risks | |
148 | associated with Your exercise of permissions under this License. | |
149 | ||
150 | 8. Limitation of Liability. | |
151 | In no event and under no legal theory, whether in tort (including | |
152 | negligence), contract, or otherwise, unless required by applicable law | |
153 | (such as deliberate and grossly negligent acts) or agreed to in writing, | |
154 | shall any Contributor be liable to You for damages, including any direct, | |
155 | indirect, special, incidental, or consequential damages of any character | |
156 | arising as a result of this License or out of the use or inability to | |
157 | use the Work (including but not limited to damages for loss of goodwill, | |
158 | work stoppage, computer failure or malfunction, or any and all other | |
159 | commercial damages or losses), even if such Contributor has been advised | |
160 | of the possibility of such damages. | |
161 | ||
162 | 9. Accepting Warranty or Additional Liability. | |
163 | While redistributing the Work or Derivative Works thereof, You may | |
164 | choose to offer, and charge a fee for, acceptance of support, warranty, | |
165 | indemnity, or other liability obligations and/or rights consistent with | |
166 | this License. However, in accepting such obligations, You may act only | |
167 | on Your own behalf and on Your sole responsibility, not on behalf of | |
168 | any other Contributor, and only if You agree to indemnify, defend, and | |
169 | hold each Contributor harmless for any liability incurred by, or claims | |
170 | asserted against, such Contributor by reason of your accepting any such | |
171 | warranty or additional liability. | |
172 | ||
173 | END OF TERMS AND CONDITIONS⏎ |
0 | ##opentsdb-goclient | |
1 | ||
2 | ###Backgroud | |
3 | OpenTSDB is a distributed, scalable Time Series Database (TSDB) written on top of HBase. | |
4 | OpenTSDB was written to address a common need: store, index and serve metrics collected | |
5 | from computer systems (network gear, operating systems, applications) at a large scale, | |
6 | and make this data easily accessible and graphable. | |
7 | ||
8 | I am about to use OpenTSDB, but currently there is no useable go-sdk for OpenTSDB. So I | |
9 | develop the opentsdb-goclient for convenience according to the [OpenTSDB Rest API Doc] (http://opentsdb.net/docs/build/html/api_http/index.html#api-endpoints) | |
10 | ||
11 | ###How to use sample | |
12 | If you want to see how does the sample (sample.go) run, you can execute the following commands: | |
13 | ```shell | |
14 | cd $GOPATH | |
15 | mkdir -p $GOPATH/src/github.com/bluebreezecf | |
16 | cd $GOPATH/src/github.com/bluebreezecf | |
17 | git clone https://github.com/bluebreezecf/opentsdb-goclient.git | |
18 | ||
19 | vi sample.go //Use the real host and port of an existing OpenTSDB in Line 33: OpentsdbHost: "127.0.0.1:4242" | |
20 | go run sample.go | |
21 | ||
22 | ``` | |
23 | ||
24 | ###Current supporting rest apis | |
25 | ```shell | |
26 | GET /api/aggregators | |
27 | GET,POST,DELETE /api/annotation | |
28 | POST,DELETE /api/annotation/bulk | |
29 | GET /api/config | |
30 | GET /api/dropcaches | |
31 | POST /api/put | |
32 | GET /api/query | |
33 | GET /api/query/last | |
34 | GET /api/serializers | |
35 | GET /api/stats | |
36 | GET /api/suggest | |
37 | POST /api/uid/assign | |
38 | GET,POST,DELETE /api/uid/tsmeta | |
39 | GET,POST,DELETE /api/uid/uidmeta | |
40 | GET /api/version | |
41 | ```⏎ |
0 | // Copyright 2015 opentsdb-goclient authors. All Rights Reserved. | |
1 | // | |
2 | // Licensed under the Apache License, Version 2.0 (the "License"); | |
3 | // you may not use this file except in compliance with the License. | |
4 | // You may obtain a copy of the License at | |
5 | // | |
6 | // http://www.apache.org/licenses/LICENSE-2.0 | |
7 | // | |
8 | // Unless required by applicable law or agreed to in writing, software | |
9 | // distributed under the License is distributed on an "AS IS" BASIS, | |
10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
11 | // See the License for the specific language governing permissions and | |
12 | // limitations under the License. | |
13 | // | |
14 | // | |
15 | // Package client defines the client and the corresponding | |
16 | // rest api implementaion of OpenTSDB. | |
17 | // | |
18 | // aggregators.go contains the structs and methods for the implementation of /api/aggregators. | |
19 | // | |
20 | package client | |
21 | ||
22 | import ( | |
23 | "bytes" | |
24 | "encoding/json" | |
25 | "fmt" | |
26 | ) | |
27 | ||
28 | // AggregatorsResponse acts as the implementation of Response in the /api/aggregators scene. | |
29 | // It holds the status code and the response values defined in the | |
30 | // (http://opentsdb.net/docs/build/html/api_http/aggregators.html). | |
31 | // | |
32 | type AggregatorsResponse struct { | |
33 | StatusCode int | |
34 | Aggregators []string `json:"aggregators"` | |
35 | } | |
36 | ||
37 | func (aggreResp *AggregatorsResponse) SetStatus(code int) { | |
38 | aggreResp.StatusCode = code | |
39 | } | |
40 | ||
41 | func (aggreResp *AggregatorsResponse) GetCustomParser() func(respCnt []byte) error { | |
42 | return func(respCnt []byte) error { | |
43 | return json.Unmarshal([]byte(fmt.Sprintf("{%s:%s}", `"aggregators"`, string(respCnt))), &aggreResp) | |
44 | } | |
45 | } | |
46 | ||
47 | func (aggreResp *AggregatorsResponse) String() string { | |
48 | buffer := bytes.NewBuffer(nil) | |
49 | content, _ := json.Marshal(aggreResp) | |
50 | buffer.WriteString(fmt.Sprintf("%s\n", string(content))) | |
51 | return buffer.String() | |
52 | } | |
53 | ||
54 | func (c *clientImpl) Aggregators() (*AggregatorsResponse, error) { | |
55 | aggregatorsEndpoint := fmt.Sprintf("%s%s", c.tsdbEndpoint, AggregatorPath) | |
56 | aggreResp := AggregatorsResponse{} | |
57 | if err := c.sendRequest(GetMethod, aggregatorsEndpoint, "", &aggreResp); err != nil { | |
58 | return nil, err | |
59 | } | |
60 | return &aggreResp, nil | |
61 | } |
0 | // Copyright 2015 opentsdb-goclient authors. All Rights Reserved. | |
1 | // | |
2 | // Licensed under the Apache License, Version 2.0 (the "License"); | |
3 | // you may not use this file except in compliance with the License. | |
4 | // You may obtain a copy of the License at | |
5 | // | |
6 | // http://www.apache.org/licenses/LICENSE-2.0 | |
7 | // | |
8 | // Unless required by applicable law or agreed to in writing, software | |
9 | // distributed under the License is distributed on an "AS IS" BASIS, | |
10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
11 | // See the License for the specific language governing permissions and | |
12 | // limitations under the License. | |
13 | // | |
14 | // | |
15 | // Package client defines the client and the corresponding | |
16 | // rest api implementaion of OpenTSDB. | |
17 | // | |
18 | // annotation.go contains the structs and methods for the implementation of | |
19 | // /api/annotation and /api/annotation/bulk. | |
20 | // | |
21 | package client | |
22 | ||
23 | import ( | |
24 | "bytes" | |
25 | "encoding/json" | |
26 | "errors" | |
27 | "fmt" | |
28 | "strings" | |
29 | ) | |
30 | ||
31 | // Annotation is the structure used to hold | |
32 | // the querying parameters when calling /api/annotation. | |
33 | // Each attributes in Annotation matches the definition in | |
34 | // (http://opentsdb.net/docs/build/html/api_http/annotation/index.html). | |
35 | // | |
36 | // Annotations are very basic objects used to record a note of an arbitrary | |
37 | // event at some point, optionally associated with a timeseries. Annotations | |
38 | // are not meant to be used as a tracking or event based system, rather they | |
39 | // are useful for providing links to such systems by displaying a notice on | |
40 | // graphs or via API query calls. | |
41 | // | |
42 | type Annotation struct { | |
43 | // A Unix epoch timestamp, in seconds, marking the time when the annotation event should be recorded. | |
44 | // The value is required with non-zero value. | |
45 | StartTime int64 `json:"startTime,omitempty"` | |
46 | ||
47 | // An optional end time for the event if it has completed or been resolved. | |
48 | EndTime int64 `json:"endTime,omitempty"` | |
49 | ||
50 | // A TSUID if the annotation is associated with a timeseries. | |
51 | // This may be optional if the note was for a global event | |
52 | Tsuid string `json:"tsuid,omitempty"` | |
53 | ||
54 | // An optional brief description of the event. As this may appear on GnuPlot graphs, | |
55 | // the description should be very short, ideally less than 25 characters. | |
56 | Description string `json:"description,omitempty"` | |
57 | ||
58 | // An optional detailed notes about the event | |
59 | Notes string `json:"notes,omitempty"` | |
60 | ||
61 | // An optional key/value map to store custom fields and values | |
62 | Custom map[string]string `json:"custom,omitempty"` | |
63 | } | |
64 | ||
65 | // AnnotationResponse acts as the implementation of Response in the /api/annotation scene. | |
66 | // It holds the status code and the response values defined in the | |
67 | // (http://opentsdb.net/docs/build/html/api_http/aggregators.html). | |
68 | // | |
69 | type AnnotationResponse struct { | |
70 | StatusCode int | |
71 | Annotation | |
72 | ErrorInfo map[string]interface{} `json:"error,omitempty"` | |
73 | } | |
74 | ||
75 | func (annotResp *AnnotationResponse) SetStatus(code int) { | |
76 | annotResp.StatusCode = code | |
77 | } | |
78 | ||
79 | func (annotResp *AnnotationResponse) GetCustomParser() func(respCnt []byte) error { | |
80 | return func(respCnt []byte) error { | |
81 | originContents := string(respCnt) | |
82 | var resultBytes []byte | |
83 | if strings.Contains(originContents, "startTime") || | |
84 | strings.Contains(originContents, "error") { | |
85 | resultBytes = respCnt | |
86 | } else if annotResp.StatusCode == 204 { | |
87 | // The OpenTSDB deletes an annotation successfully and with no body content. | |
88 | return nil | |
89 | } | |
90 | return json.Unmarshal(resultBytes, &annotResp) | |
91 | } | |
92 | } | |
93 | ||
94 | func (annotResp *AnnotationResponse) String() string { | |
95 | buffer := bytes.NewBuffer(nil) | |
96 | content, _ := json.Marshal(annotResp) | |
97 | buffer.WriteString(fmt.Sprintf("%s\n", string(content))) | |
98 | return buffer.String() | |
99 | } | |
100 | ||
101 | func (c *clientImpl) QueryAnnotation(queryAnnoParam map[string]interface{}) (*AnnotationResponse, error) { | |
102 | if queryAnnoParam == nil || len(queryAnnoParam) == 0 { | |
103 | return nil, errors.New("The given query annotation param is nil") | |
104 | } | |
105 | buffer := bytes.NewBuffer(nil) | |
106 | size := len(queryAnnoParam) | |
107 | i := 0 | |
108 | for k, v := range queryAnnoParam { | |
109 | buffer.WriteString(fmt.Sprintf("%s=%v", k, v)) | |
110 | if i < size-1 { | |
111 | buffer.WriteString("&") | |
112 | } else { | |
113 | break | |
114 | } | |
115 | i++ | |
116 | } | |
117 | annoEndpoint := fmt.Sprintf("%s%s?%s", c.tsdbEndpoint, AnnotationPath, buffer.String()) | |
118 | annResp := AnnotationResponse{} | |
119 | if err := c.sendRequest(GetMethod, annoEndpoint, "", &annResp); err != nil { | |
120 | return nil, err | |
121 | } | |
122 | return &annResp, nil | |
123 | } | |
124 | ||
125 | func (c *clientImpl) UpdateAnnotation(annotation Annotation) (*AnnotationResponse, error) { | |
126 | return c.operateAnnotation(PostMethod, &annotation) | |
127 | } | |
128 | ||
129 | func (c *clientImpl) DeleteAnnotation(annotation Annotation) (*AnnotationResponse, error) { | |
130 | return c.operateAnnotation(DeleteMethod, &annotation) | |
131 | } | |
132 | ||
133 | func (c *clientImpl) operateAnnotation(method string, annotation *Annotation) (*AnnotationResponse, error) { | |
134 | if !c.isValidOperateMethod(method) { | |
135 | return nil, errors.New("The given method for operating an annotation is invalid.") | |
136 | } | |
137 | annoEndpoint := fmt.Sprintf("%s%s", c.tsdbEndpoint, AnnotationPath) | |
138 | resultBytes, err := json.Marshal(annotation) | |
139 | if err != nil { | |
140 | return nil, errors.New(fmt.Sprintf("Failed to marshal annotation: %v", err)) | |
141 | } | |
142 | annResp := AnnotationResponse{} | |
143 | if err = c.sendRequest(method, annoEndpoint, string(resultBytes), &annResp); err != nil { | |
144 | return nil, err | |
145 | } | |
146 | return &annResp, nil | |
147 | } | |
148 | ||
149 | // BulkAnnotatResponse acts as the implementation of Response in the /api/annotation/bulk scene. | |
150 | // It holds the status code and the response values defined in the | |
151 | // (http://opentsdb.net/docs/build/html/api_http/annotation/bulk.html) | |
152 | // for both bulk update and delete scenes. | |
153 | // | |
154 | type BulkAnnotatResponse struct { | |
155 | StatusCode int | |
156 | UpdateAnnotations []Annotation `json:"InvolvedAnnotations,omitempty"` | |
157 | ErrorInfo map[string]interface{} `json:"error,omitempty"` | |
158 | BulkDeleteResp | |
159 | } | |
160 | ||
161 | type BulkAnnoDeleteInfo struct { | |
162 | // A list of TSUIDs with annotations that should be deleted. This may be empty | |
163 | // or null (for JSON) in which case the global flag should be set. | |
164 | Tsuids []string `json:"tsuids,omitempty"` | |
165 | ||
166 | // A timestamp for the start of the request. | |
167 | StartTime int64 `json:"startTime,omitempty"` | |
168 | ||
169 | // An optional end time for the event if it has completed or been resolved. | |
170 | EndTime int64 `json:"endTime,omitempty"` | |
171 | ||
172 | // An optional flag indicating whether or not global annotations should be deleted for the range | |
173 | Global bool `json:"global,omitempty"` | |
174 | } | |
175 | ||
176 | type BulkDeleteResp struct { | |
177 | BulkAnnoDeleteInfo | |
178 | ||
179 | // Total number of annotations to be deleted successfully for current bulk | |
180 | // delete operation. The value is only used in the reponse of bulk deleting, | |
181 | // not in the bulk deleting parameters. | |
182 | TotalDeleted int64 `json:"totalDeleted,omitempty"` | |
183 | } | |
184 | ||
185 | func (bulkAnnotResp *BulkAnnotatResponse) SetStatus(code int) { | |
186 | bulkAnnotResp.StatusCode = code | |
187 | } | |
188 | ||
189 | func (bulkAnnotResp *BulkAnnotatResponse) GetCustomParser() func(respCnt []byte) error { | |
190 | return func(respCnt []byte) error { | |
191 | originContents := string(respCnt) | |
192 | var resultBytes []byte | |
193 | if strings.Contains(originContents, "startTime") { | |
194 | resultBytes = []byte(fmt.Sprintf("{%s:%s}", `"InvolvedAnnotations"`, originContents)) | |
195 | } else if strings.Contains(originContents, "error") || strings.Contains(originContents, "totalDeleted") { | |
196 | resultBytes = respCnt | |
197 | } else { | |
198 | return errors.New(fmt.Sprintf("Unrecognized bulk annotation response info: %s", originContents)) | |
199 | } | |
200 | return json.Unmarshal(resultBytes, &bulkAnnotResp) | |
201 | } | |
202 | } | |
203 | ||
204 | func (bulkAnnotResp *BulkAnnotatResponse) String() string { | |
205 | buffer := bytes.NewBuffer(nil) | |
206 | content, _ := json.Marshal(bulkAnnotResp) | |
207 | buffer.WriteString(fmt.Sprintf("%s\n", string(content))) | |
208 | return buffer.String() | |
209 | } | |
210 | ||
211 | func (c *clientImpl) BulkUpdateAnnotations(annotations []Annotation) (*BulkAnnotatResponse, error) { | |
212 | if annotations == nil || len(annotations) == 0 { | |
213 | return nil, errors.New("The given annotations are empty.") | |
214 | } | |
215 | bulkAnnoEndpoint := fmt.Sprintf("%s%s", c.tsdbEndpoint, BulkAnnotationPath) | |
216 | reqBodyCnt, err := marshalAnnotations(annotations) | |
217 | if err != nil { | |
218 | return nil, errors.New(fmt.Sprintf("Failed to marshal annotations: %v", err)) | |
219 | } | |
220 | bulkAnnoResp := BulkAnnotatResponse{} | |
221 | if err = c.sendRequest(PostMethod, bulkAnnoEndpoint, reqBodyCnt, &bulkAnnoResp); err != nil { | |
222 | return nil, err | |
223 | } | |
224 | return &bulkAnnoResp, nil | |
225 | } | |
226 | ||
227 | func (c *clientImpl) BulkDeleteAnnotations(bulkDelParam BulkAnnoDeleteInfo) (*BulkAnnotatResponse, error) { | |
228 | bulkAnnoEndpoint := fmt.Sprintf("%s%s", c.tsdbEndpoint, BulkAnnotationPath) | |
229 | resultBytes, err := json.Marshal(bulkDelParam) | |
230 | if err != nil { | |
231 | return nil, errors.New(fmt.Sprintf("Failed to marshal bulk delete param: %v", err)) | |
232 | } | |
233 | bulkAnnoResp := BulkAnnotatResponse{} | |
234 | if err = c.sendRequest(DeleteMethod, bulkAnnoEndpoint, string(resultBytes), &bulkAnnoResp); err != nil { | |
235 | return nil, err | |
236 | } | |
237 | return &bulkAnnoResp, nil | |
238 | } | |
239 | ||
240 | func marshalAnnotations(annotations []Annotation) (string, error) { | |
241 | buffer := bytes.NewBuffer(nil) | |
242 | size := len(annotations) | |
243 | buffer.WriteString("[") | |
244 | for index, item := range annotations { | |
245 | result, err := json.Marshal(item) | |
246 | if err != nil { | |
247 | return "", err | |
248 | } | |
249 | buffer.Write(result) | |
250 | if index < size-1 { | |
251 | buffer.WriteString(",") | |
252 | } | |
253 | } | |
254 | buffer.WriteString("]") | |
255 | return buffer.String(), nil | |
256 | } |
0 | // Copyright 2015 opentsdb-goclient authors. All Rights Reserved. | |
1 | // | |
2 | // Licensed under the Apache License, Version 2.0 (the "License"); | |
3 | // you may not use this file except in compliance with the License. | |
4 | // You may obtain a copy of the License at | |
5 | // | |
6 | // http://www.apache.org/licenses/LICENSE-2.0 | |
7 | // | |
8 | // Unless required by applicable law or agreed to in writing, software | |
9 | // distributed under the License is distributed on an "AS IS" BASIS, | |
10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
11 | // See the License for the specific language governing permissions and | |
12 | // limitations under the License. | |
13 | // | |
14 | // | |
15 | // Package client defines the client and the corresponding | |
16 | // rest api implementaion of OpenTSDB. | |
17 | // | |
18 | // client.go contains the global interface and implementation struct | |
19 | // definition of the OpenTSDB Client, as well as the common private | |
20 | // and public methods used by all the rest-api implementation files, | |
21 | // whose names are just like put.go, query.go, and so on. | |
22 | // | |
23 | package client | |
24 | ||
25 | import ( | |
26 | "encoding/json" | |
27 | "errors" | |
28 | "fmt" | |
29 | "io/ioutil" | |
30 | "net" | |
31 | "net/http" | |
32 | "strings" | |
33 | "time" | |
34 | ||
35 | "github.com/bluebreezecf/opentsdb-goclient/config" | |
36 | ) | |
37 | ||
38 | const ( | |
39 | DefaultDialTimeout = 5 * time.Second | |
40 | KeepAliveTimeout = 30 * time.Second | |
41 | GetMethod = "GET" | |
42 | PostMethod = "POST" | |
43 | PutMethod = "PUT" | |
44 | DeleteMethod = "DELETE" | |
45 | ||
46 | PutPath = "/api/put" | |
47 | PutRespWithSummary = "summary" | |
48 | PutRespWithDetails = "details" | |
49 | ||
50 | QueryPath = "/api/query" | |
51 | QueryLastPath = "/api/query/last" | |
52 | // The three keys in the rateOption parameter of the QueryParam | |
53 | QueryRateOptionCounter = "counter" // The corresponding value type is bool | |
54 | QueryRateOptionCounterMax = "counterMax" // The corresponding value type is int,int64 | |
55 | QueryRateOptionResetValue = "resetValue" // The corresponding value type is int,int64 | |
56 | ||
57 | AggregatorPath = "/api/aggregators" | |
58 | ConfigPath = "/api/config" | |
59 | SerializersPath = "/api/serializers" | |
60 | StatsPath = "/api/stats" | |
61 | SuggestPath = "/api/suggest" | |
62 | // Only the one of the three query type can be used in SuggestParam, UIDMetaData: | |
63 | TypeMetrics = "metrics" | |
64 | TypeTagk = "tagk" | |
65 | TypeTagv = "tagv" | |
66 | ||
67 | VersionPath = "/api/version" | |
68 | DropcachesPath = "/api/dropcaches" | |
69 | AnnotationPath = "/api/annotation" | |
70 | AnQueryStartTime = "start_time" | |
71 | AnQueryTSUid = "tsuid" | |
72 | BulkAnnotationPath = "/api/annotation/bulk" | |
73 | UIDMetaDataPath = "/api/uid/uidmeta" | |
74 | UIDAssignPath = "/api/uid/assign" | |
75 | TSMetaDataPath = "/api/uid/tsmeta" | |
76 | ||
77 | // The above three constants are used in /put | |
78 | DefaultMaxPutPointsNum = 75 | |
79 | DefaultDetectDeltaNum = 3 | |
80 | // Unit is bytes, and assumes that config items of 'tsd.http.request.enable_chunked = true' | |
81 | // and 'tsd.http.request.max_chunk = 40960' are all in the opentsdb.conf: | |
82 | DefaultMaxContentLength = 40960 | |
83 | ) | |
84 | ||
85 | var ( | |
86 | DefaultTransport = &http.Transport{ | |
87 | MaxIdleConnsPerHost: 10, | |
88 | Dial: (&net.Dialer{ | |
89 | Timeout: DefaultDialTimeout, | |
90 | KeepAlive: KeepAliveTimeout, | |
91 | }).Dial, | |
92 | } | |
93 | ) | |
94 | ||
95 | // Client defines the sdk methods, by which other go applications can | |
96 | // commnicate with the OpenTSDB via the pre-defined rest-apis. | |
97 | // Each method defined in the interface of Client is in the correspondance | |
98 | // a rest-api definition in (http://opentsdb.net/docs/build/html/api_http/index.html#api-endpoints). | |
99 | type Client interface { | |
100 | ||
101 | // Ping detects whether the target OpenTSDB is reachable or not. | |
102 | // If error occurs during the detection, an error instance will be returned, or nil otherwise. | |
103 | Ping() error | |
104 | ||
105 | // Put is the implementation of 'POST /api/put' endpoint. | |
106 | // This endpoint allows for storing data in OpenTSDB over HTTP as an alternative to the Telnet interface. | |
107 | // | |
108 | // datas is a slice of DataPoint holding at least one instance. | |
109 | // queryParam can only be github.com/bluebreezecf/opentsdb-goclient/client.PutRespWithSummary, | |
110 | // github.com/bluebreezecf/opentsdb-goclient/client.PutRespWithDetails or the empty string ""; | |
111 | // It means get put summary response info by using PutRespWithSummary, and | |
112 | // with PutRespWithDetails means get put detailed response. | |
113 | // | |
114 | // When put operation is successful, a pointer of PutResponse will be returned with the corresponding | |
115 | // status code and response info. Otherwise, an error instance will be returned, when the given parameters | |
116 | // are invalid, it failed to parese the response, or OpenTSDB is un-connectable right now. | |
117 | Put(datas []DataPoint, queryParam string) (*PutResponse, error) | |
118 | ||
119 | // Query is the implementation of 'GET /api/query' endpoint. | |
120 | // It is probably the most useful endpoint in the API, /api/query enables extracting data from the storage | |
121 | // system in various formats determined by the serializer selected. | |
122 | // | |
123 | // param is a instance of QueryParam holding current query parameters. | |
124 | // | |
125 | // When query operation is successful, a pointer of QueryResponse will be returned with the corresponding | |
126 | // status code and response info. Otherwise, an error instance will be returned, when the given parameter | |
127 | // is invalid, it failed to parese the response, or OpenTSDB is un-connectable right now. | |
128 | Query(param QueryParam) (*QueryResponse, error) | |
129 | ||
130 | // QueryLast is the implementation of 'GET /api/query/last' endpoint. | |
131 | // It is introduced firstly in v2.1, and fully supported in v2.2. So it should be aware that this api works | |
132 | // well since v2.2 of opentsdb. | |
133 | // | |
134 | // param is a instance of QueryLastParam holding current query parameters. | |
135 | // | |
136 | // When query operation is successful, a pointer of QueryLastResponse will be returned with the corresponding | |
137 | // status code and response info. Otherwise, an error instance will be returned, when the given parameter | |
138 | // is invalid, it failed to parese the response, or OpenTSDB is un-connectable right now. | |
139 | QueryLast(param QueryLastParam) (*QueryLastResponse, error) | |
140 | ||
141 | // Aggregators is the implementation of 'GET /api/aggregators' endpoint. | |
142 | // It simply lists the names of implemented aggregation functions used in timeseries queries. | |
143 | // | |
144 | // When query operation is successful, a pointer of AggregatorsResponse will be returned with the corresponding | |
145 | // status code and response info. Otherwise, an error instance will be returned, when it failed to parese the | |
146 | // response, or OpenTSDB is un-connectable right now. | |
147 | Aggregators() (*AggregatorsResponse, error) | |
148 | ||
149 | // Config is the implementation of 'GET /api/config' endpoint. | |
150 | // It returns information about the running configuration of the TSD. | |
151 | // It is read only and cannot be used to set configuration options. | |
152 | // | |
153 | // When query operation is successful, a pointer of ConfigResponse will be returned with the corresponding | |
154 | // status code and response info. Otherwise, an error instance will be returned, when it failed to parese the | |
155 | // response, or OpenTSDB is un-connectable right now. | |
156 | Config() (*ConfigResponse, error) | |
157 | ||
158 | // Serializers is the implementation of 'GET /api/serializers' endpoint. | |
159 | // It lists the serializer plugins loaded by the running TSD. Information given includes the name, | |
160 | // implemented methods, content types and methods. | |
161 | // | |
162 | // When query operation is successful, a pointer of SerialResponse will be returned with the corresponding | |
163 | // status code and response info. Otherwise, an error instance will be returned, when it failed to parese the | |
164 | // response, or OpenTSDB is un-connectable right now. | |
165 | Serializers() (*SerialResponse, error) | |
166 | ||
167 | // Stats is the implementation of 'GET /api/stats' endpoint. | |
168 | // It provides a list of statistics for the running TSD. These statistics are automatically recorded | |
169 | // by a running TSD every 5 minutes but may be accessed via this endpoint. All statistics are read only. | |
170 | // | |
171 | // When query operation is successful, a pointer of StatsResponse will be returned with the corresponding | |
172 | // status code and response info. Otherwise, an error instance will be returned, when it failed to parese the | |
173 | // response, or OpenTSDB is un-connectable right now. | |
174 | Stats() (*StatsResponse, error) | |
175 | ||
176 | // Suggest is the implementation of 'GET /api/suggest' endpoint. | |
177 | // It provides a means of implementing an "auto-complete" call that can be accessed repeatedly as a user | |
178 | // types a request in a GUI. It does not offer full text searching or wildcards, rather it simply matches | |
179 | // the entire string passed in the query on the first characters of the stored data. | |
180 | // For example, passing a query of type=metrics&q=sys will return the top 25 metrics in the system that start with sys. | |
181 | // Matching is case sensitive, so sys will not match System.CPU. Results are sorted alphabetically. | |
182 | // | |
183 | // sugParm is an instance of SuggestParam storing parameters by invoking /api/suggest. | |
184 | // | |
185 | // When query operation is successful, a pointer of SuggestResponse will be returned with the corresponding | |
186 | // status code and response info. Otherwise, an error instance will be returned, if the given parameter is invalid, | |
187 | // or when it failed to parese the response, or OpenTSDB is un-connectable right now. | |
188 | Suggest(sugParm SuggestParam) (*SuggestResponse, error) | |
189 | ||
190 | // Version is the implementation of 'GET /api/version' endpoint. | |
191 | // It returns information about the running version of OpenTSDB. | |
192 | // | |
193 | // When query operation is successful, a pointer of VersionResponse will be returned with the corresponding | |
194 | // status code and response info. Otherwise, an error instance will be returned, when it failed to parese the | |
195 | // response, or OpenTSDB is un-connectable right now. | |
196 | Version() (*VersionResponse, error) | |
197 | ||
198 | // Dropcaches is the implementation of 'GET /api/dropcaches' endpoint. | |
199 | // It purges the in-memory data cached in OpenTSDB. This includes all UID to name | |
200 | // and name to UID maps for metrics, tag names and tag values. | |
201 | // | |
202 | // When query operation is successful, a pointer of DropcachesResponse will be returned with the corresponding | |
203 | // status code and response info. Otherwise, an error instance will be returned, when it failed to parese the | |
204 | // response, or OpenTSDB is un-connectable right now. | |
205 | Dropcaches() (*DropcachesResponse, error) | |
206 | ||
207 | // QueryAnnotation is the implementation of 'GET /api/annotation' endpoint. | |
208 | // It retrieves a single annotation stored in the OpenTSDB backend. | |
209 | // | |
210 | // queryAnnoParam is a map storing parameters of a target queried annotation. | |
211 | // The key can be such as client.AnQueryStartTime, client.AnQueryTSUid. | |
212 | // | |
213 | // When query operation is handlering properly by the OpenTSDB backend, a pointer of AnnotationResponse | |
214 | // will be returned with the corresponding status code and response info (including the potential error | |
215 | // messages replied by OpenTSDB). | |
216 | // | |
217 | // Otherwise, an error instance will be returned, if the given parameter is invalid, | |
218 | // or when it failed to parese the response, or OpenTSDB is un-connectable right now. | |
219 | // | |
220 | // Note that: the returned non-nil error instance is only responsed by opentsdb-client, not the OpenTSDB backend. | |
221 | QueryAnnotation(queryAnnoParam map[string]interface{}) (*AnnotationResponse, error) | |
222 | ||
223 | // UpdateAnnotation is the implementation of 'POST /api/annotation' endpoint. | |
224 | // It creates or modifies an annotation stored in the OpenTSDB backend. | |
225 | // | |
226 | // annotation is an annotation to be processed in the OpenTSDB backend. | |
227 | // | |
228 | // When modification operation is handlering properly by the OpenTSDB backend, a pointer of AnnotationResponse | |
229 | // will be returned with the corresponding status code and response info (including the potential error | |
230 | // messages replied by OpenTSDB). | |
231 | // | |
232 | // Otherwise, an error instance will be returned, if the given parameter is invalid, | |
233 | // or when it failed to parese the response, or OpenTSDB is un-connectable right now. | |
234 | // | |
235 | // Note that: the returned non-nil error instance is only responsed by opentsdb-client, not the OpenTSDB backend. | |
236 | UpdateAnnotation(annotation Annotation) (*AnnotationResponse, error) | |
237 | ||
238 | // DeleteAnnotation is the implementation of 'DELETE /api/annotation' endpoint. | |
239 | // It deletes an annotation stored in the OpenTSDB backend. | |
240 | // | |
241 | // annotation is an annotation to be deleted in the OpenTSDB backend. | |
242 | // | |
243 | // When deleting operation is handlering properly by the OpenTSDB backend, a pointer of AnnotationResponse | |
244 | // will be returned with the corresponding status code and response info (including the potential error | |
245 | // messages replied by OpenTSDB). | |
246 | // | |
247 | // Otherwise, an error instance will be returned, if the given parameter is invalid, | |
248 | // or when it failed to parese the response, or OpenTSDB is un-connectable right now. | |
249 | // | |
250 | // Note that: the returned non-nil error instance is only responsed by opentsdb-client, not the OpenTSDB backend. | |
251 | DeleteAnnotation(annotation Annotation) (*AnnotationResponse, error) | |
252 | ||
253 | // BulkUpdateAnnotations is the implementation of 'POST /api/annotation/bulk' endpoint. | |
254 | // It creates or modifies a list of annotation stored in the OpenTSDB backend. | |
255 | // | |
256 | // annotations is a list of annotations to be processed (to be created or modified) in the OpenTSDB backend. | |
257 | // | |
258 | // When bulk modification operation is handlering properly by the OpenTSDB backend, a pointer of BulkAnnotatResponse | |
259 | // will be returned with the corresponding status code and response info (including the potential error | |
260 | // messages replied by OpenTSDB). | |
261 | // | |
262 | // Otherwise, an error instance will be returned, if the given parameter is invalid, | |
263 | // or when it failed to parese the response, or OpenTSDB is un-connectable right now. | |
264 | // | |
265 | // Note that: the returned non-nil error instance is only responsed by opentsdb-client, not the OpenTSDB backend. | |
266 | BulkUpdateAnnotations(annotations []Annotation) (*BulkAnnotatResponse, error) | |
267 | ||
268 | // BulkDeleteAnnotations is the implementation of 'DELETE /api/annotation/bulk' endpoint. | |
269 | // It deletes a list of annotation stored in the OpenTSDB backend. | |
270 | // | |
271 | // bulkDelParam contains the bulk deleting info in current invoking 'DELETE /api/annotation/bulk'. | |
272 | // | |
273 | // When bulk deleting operation is handlering properly by the OpenTSDB backend, a pointer of BulkAnnotatResponse | |
274 | // will be returned with the corresponding status code and response info (including the potential error | |
275 | // messages replied by OpenTSDB). | |
276 | // | |
277 | // Otherwise, an error instance will be returned, if the given parameter is invalid, | |
278 | // or when it failed to parese the response, or OpenTSDB is un-connectable right now. | |
279 | // | |
280 | // Note that: the returned non-nil error instance is only responsed by opentsdb-client, not the OpenTSDB backend. | |
281 | BulkDeleteAnnotations(bulkDelParam BulkAnnoDeleteInfo) (*BulkAnnotatResponse, error) | |
282 | ||
283 | // QueryUIDMetaData is the implementation of 'GET /api/uid/uidmeta' endpoint. | |
284 | // It retrieves a single UIDMetaData stored in the OpenTSDB backend with the given query parameters. | |
285 | // | |
286 | // metaQueryParam is a map storing parameters of a target queried UIDMetaData. | |
287 | // It must contain two key/value pairs with the key "uid" and "type". | |
288 | // "type" should be one of client.TypeMetrics ("metric"), client.TypeTagk ("tagk"), and client.TypeTagv ("tagv") | |
289 | // | |
290 | // When query operation is handlering properly by the OpenTSDB backend, a pointer of UIDMetaDataResponse | |
291 | // will be returned with the corresponding status code and response info (including the potential error | |
292 | // messages replied by OpenTSDB). | |
293 | // | |
294 | // Otherwise, an error instance will be returned, if the given parameter is invalid, | |
295 | // or when it failed to parese the response, or OpenTSDB is un-connectable right now. | |
296 | // | |
297 | // Note that: the returned non-nil error instance is only responsed by opentsdb-client, not the OpenTSDB backend. | |
298 | QueryUIDMetaData(metaQueryParam map[string]string) (*UIDMetaDataResponse, error) | |
299 | ||
300 | // UpdateUIDMetaData is the implementation of 'POST /api/uid/uidmeta' endpoint. | |
301 | // It modifies a UIDMetaData. | |
302 | // | |
303 | // uidMetaData is an instance of UIDMetaData to be modified | |
304 | // | |
305 | // When update operation is handlering properly by the OpenTSDB backend, a pointer of UIDMetaDataResponse | |
306 | // will be returned with the corresponding status code and response info (including the potential error | |
307 | // messages replied by OpenTSDB). | |
308 | // | |
309 | // Otherwise, an error instance will be returned, if the given parameter is invalid, | |
310 | // or when it failed to parese the response, or OpenTSDB is un-connectable right now. | |
311 | // | |
312 | // Note that: the returned non-nil error instance is only responsed by opentsdb-client, not the OpenTSDB backend. | |
313 | UpdateUIDMetaData(uidMetaData UIDMetaData) (*UIDMetaDataResponse, error) | |
314 | ||
315 | // DeleteUIDMetaData is the implementation of 'DELETE /api/uid/uidmeta' endpoint. | |
316 | // It deletes a target UIDMetaData. | |
317 | // | |
318 | // uidMetaData is an instance of UIDMetaData whose correspance is to be deleted. | |
319 | // The values of uid and type in uidMetaData is required. | |
320 | // | |
321 | // When delete operation is handlering properly by the OpenTSDB backend, a pointer of UIDMetaDataResponse | |
322 | // will be returned with the corresponding status code and response info (including the potential error | |
323 | // messages replied by OpenTSDB). | |
324 | // | |
325 | // Otherwise, an error instance will be returned, if the given parameter is invalid, | |
326 | // or when it failed to parese the response, or OpenTSDB is un-connectable right now. | |
327 | // | |
328 | // Note that: the returned non-nil error instance is only responsed by opentsdb-client, not the OpenTSDB backend. | |
329 | DeleteUIDMetaData(uidMetaData UIDMetaData) (*UIDMetaDataResponse, error) | |
330 | ||
331 | // AssignUID is the implementation of 'POST /api/uid/assigin' endpoint. | |
332 | // It enables assigning UIDs to new metrics, tag names and tag values. Multiple types and names can be provided | |
333 | // in a single call and the API will process each name individually, reporting which names were assigned UIDs | |
334 | // successfully, along with the UID assigned, and which failed due to invalid characters or had already been assigned. | |
335 | // Assignment can be performed via query string or content data. | |
336 | // | |
337 | // assignParam is an instance of UIDAssignParam holding the parameters to invoke 'POST /api/uid/assigin'. | |
338 | // | |
339 | // When assigin operation is handlering properly by the OpenTSDB backend, a pointer of UIDAssignResponse | |
340 | // will be returned with the corresponding status code and response info (including the potential error | |
341 | // messages replied by OpenTSDB). | |
342 | // | |
343 | // Otherwise, an error instance will be returned, if the given parameter is invalid, | |
344 | // or when it failed to parese the response, or OpenTSDB is un-connectable right now. | |
345 | // | |
346 | // Note that: the returned non-nil error instance is only responsed by opentsdb-client, not the OpenTSDB backend. | |
347 | AssignUID(assignParam UIDAssignParam) (*UIDAssignResponse, error) | |
348 | ||
349 | // QueryTSMetaData is the implementation of 'GET /api/uid/tsmeta' endpoint. | |
350 | // It retrieves a single TSMetaData stored in the OpenTSDB backend with the given query parameters. | |
351 | // | |
352 | // tsuid is a tsuid of a target queried TSMetaData. | |
353 | // | |
354 | // When query operation is handlering properly by the OpenTSDB backend, a pointer of TSMetaDataResponse | |
355 | // will be returned with the corresponding status code and response info (including the potential error | |
356 | // messages replied by OpenTSDB). | |
357 | // | |
358 | // Otherwise, an error instance will be returned, if the given parameter is invalid, | |
359 | // or when it failed to parese the response, or OpenTSDB is un-connectable right now. | |
360 | // | |
361 | // Note that: the returned non-nil error instance is only responsed by opentsdb-client, not the OpenTSDB backend. | |
362 | QueryTSMetaData(tsuid string) (*TSMetaDataResponse, error) | |
363 | ||
364 | // UpdateTSMetaData is the implementation of 'POST /api/uid/tsmeta' endpoint. | |
365 | // It modifies a target TSMetaData with the given fields. | |
366 | // | |
367 | // tsMetaData is an instance of UIDMetaData whose correspance is to be modified | |
368 | // | |
369 | // When update operation is handlering properly by the OpenTSDB backend, a pointer of TSMetaDataResponse | |
370 | // will be returned with the corresponding status code and response info (including the potential error | |
371 | // messages replied by OpenTSDB). | |
372 | // | |
373 | // Otherwise, an error instance will be returned, when it failed to parese the response, | |
374 | // or OpenTSDB is un-connectable right now. | |
375 | // | |
376 | // Note that: the returned non-nil error instance is only responsed by opentsdb-client, not the OpenTSDB backend. | |
377 | UpdateTSMetaData(tsMetaData TSMetaData) (*TSMetaDataResponse, error) | |
378 | ||
379 | // DeleteTSMetaData is the implementation of 'DELETE /api/uid/tsmeta' endpoint. | |
380 | // It deletes a target TSMetaData. | |
381 | // | |
382 | // tsMetaData is an instance of UIDMetaData whose correspance is to be deleted | |
383 | // | |
384 | // When delete operation is handlering properly by the OpenTSDB backend, a pointer of TSMetaDataResponse | |
385 | // will be returned with the corresponding status code and response info (including the potential error | |
386 | // messages replied by OpenTSDB). | |
387 | // | |
388 | // Otherwise, an error instance will be returned, when it failed to parese the response, | |
389 | // or OpenTSDB is un-connectable right now. | |
390 | // | |
391 | // Note that: the returned non-nil error instance is only responsed by opentsdb-client, not the OpenTSDB backend. | |
392 | DeleteTSMetaData(tsMetaData TSMetaData) (*TSMetaDataResponse, error) | |
393 | } | |
394 | ||
395 | // NewClient creates an instance of http client which implements the | |
396 | // pre-defined rest apis of OpenTSDB. | |
397 | // A non-nil error instance returned means currently the target OpenTSDB | |
398 | // designated with the given endpoint is not connectable. | |
399 | func NewClient(opentsdbCfg config.OpenTSDBConfig) (Client, error) { | |
400 | opentsdbCfg.OpentsdbHost = strings.TrimSpace(opentsdbCfg.OpentsdbHost) | |
401 | if len(opentsdbCfg.OpentsdbHost) <= 0 { | |
402 | return nil, errors.New("The OpentsdbEndpoint of the given config should not be empty.") | |
403 | } | |
404 | transport := opentsdbCfg.Transport | |
405 | if transport == nil { | |
406 | transport = DefaultTransport | |
407 | } | |
408 | client := &http.Client{ | |
409 | Transport: transport, | |
410 | } | |
411 | if opentsdbCfg.MaxPutPointsNum <= 0 { | |
412 | opentsdbCfg.MaxPutPointsNum = DefaultMaxPutPointsNum | |
413 | } | |
414 | if opentsdbCfg.DetectDeltaNum <= 0 { | |
415 | opentsdbCfg.DetectDeltaNum = DefaultDetectDeltaNum | |
416 | } | |
417 | if opentsdbCfg.MaxContentLength <= 0 { | |
418 | opentsdbCfg.MaxContentLength = DefaultMaxContentLength | |
419 | } | |
420 | tsdbEndpoint := fmt.Sprintf("http://%s", opentsdbCfg.OpentsdbHost) | |
421 | clientImpl := clientImpl{ | |
422 | tsdbEndpoint: tsdbEndpoint, | |
423 | client: client, | |
424 | opentsdbCfg: opentsdbCfg, | |
425 | } | |
426 | return &clientImpl, nil | |
427 | } | |
428 | ||
429 | // The private implementation of Client interface. | |
430 | type clientImpl struct { | |
431 | tsdbEndpoint string | |
432 | client *http.Client | |
433 | opentsdbCfg config.OpenTSDBConfig | |
434 | } | |
435 | ||
436 | // Response defines the common behaviours all the specific response for | |
437 | // different rest-apis shound obey. | |
438 | // Currently it is an abstraction used in (*clientImpl).sendRequest() | |
439 | // to stored the different kinds of response contents for all the rest-apis. | |
440 | type Response interface { | |
441 | ||
442 | // SetStatus can be used to set the actual http status code of | |
443 | // the related http response for the specific Response instance | |
444 | SetStatus(code int) | |
445 | ||
446 | // GetCustomParser can be used to retrive a custom-defined parser. | |
447 | // Returning nil means current specific Response instance doesn't | |
448 | // need a custom-defined parse process, and just uses the default | |
449 | // json unmarshal method to parse the contents of the http response. | |
450 | GetCustomParser() func(respCnt []byte) error | |
451 | ||
452 | // Return the contents of the specific Response instance with | |
453 | // the string format | |
454 | String() string | |
455 | } | |
456 | ||
457 | // sendRequest dispatches the http request with the given method name, url and body contents. | |
458 | // reqBodyCnt is "" means there is no contents in the request body. | |
459 | // If the tsdb server responses properly, the error is nil and parsedResp is the parsed | |
460 | // response with the specific type. Otherwise, the returned error is not nil. | |
461 | func (c *clientImpl) sendRequest(method, url, reqBodyCnt string, parsedResp Response) error { | |
462 | req, err := http.NewRequest(method, url, strings.NewReader(reqBodyCnt)) | |
463 | if err != nil { | |
464 | return errors.New(fmt.Sprintf("Failed to create request for %s %s: %v", method, url, err)) | |
465 | } | |
466 | req.Header.Set("Content-Type", "application/json; charset=UTF-8") | |
467 | resp, err := c.client.Do(req) | |
468 | if err != nil { | |
469 | return errors.New(fmt.Sprintf("Failed to send request for %s %s: %v", method, url, err)) | |
470 | } | |
471 | defer resp.Body.Close() | |
472 | var jsonBytes []byte | |
473 | if jsonBytes, err = ioutil.ReadAll(resp.Body); err != nil { | |
474 | return errors.New(fmt.Sprintf("Failed to read response for %s %s: %v", method, url, err)) | |
475 | } | |
476 | ||
477 | parsedResp.SetStatus(resp.StatusCode) | |
478 | parser := parsedResp.GetCustomParser() | |
479 | if parser == nil { | |
480 | if err = json.Unmarshal(jsonBytes, parsedResp); err != nil { | |
481 | return errors.New(fmt.Sprintf("Failed to parse response for %s %s: %v", method, url, err)) | |
482 | } | |
483 | } else { | |
484 | if err = parser(jsonBytes); err != nil { | |
485 | return err | |
486 | } | |
487 | } | |
488 | ||
489 | return nil | |
490 | } | |
491 | ||
492 | func (c *clientImpl) isValidOperateMethod(method string) bool { | |
493 | method = strings.TrimSpace(strings.ToUpper(method)) | |
494 | if len(method) == 0 { | |
495 | return false | |
496 | } | |
497 | methods := []string{PostMethod, PutMethod, DeleteMethod} | |
498 | exists := false | |
499 | for _, item := range methods { | |
500 | if method == item { | |
501 | exists = true | |
502 | break | |
503 | } | |
504 | } | |
505 | return exists | |
506 | } | |
507 | ||
508 | func (c *clientImpl) Ping() error { | |
509 | conn, err := net.DialTimeout("tcp", c.opentsdbCfg.OpentsdbHost, DefaultDialTimeout) | |
510 | if err != nil { | |
511 | return errors.New(fmt.Sprintf("The target OpenTSDB is unreachable: %v", err)) | |
512 | } | |
513 | if conn != nil { | |
514 | defer conn.Close() | |
515 | } | |
516 | return nil | |
517 | } |
0 | // Copyright 2015 opentsdb-goclient authors. All Rights Reserved. | |
1 | // | |
2 | // Licensed under the Apache License, Version 2.0 (the "License"); | |
3 | // you may not use this file except in compliance with the License. | |
4 | // You may obtain a copy of the License at | |
5 | // | |
6 | // http://www.apache.org/licenses/LICENSE-2.0 | |
7 | // | |
8 | // Unless required by applicable law or agreed to in writing, software | |
9 | // distributed under the License is distributed on an "AS IS" BASIS, | |
10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
11 | // See the License for the specific language governing permissions and | |
12 | // limitations under the License. | |
13 | // | |
14 | // | |
15 | // Package client defines the client and the corresponding | |
16 | // rest api implementaion of OpenTSDB. | |
17 | // | |
18 | // config.go contains the structs and methods for the implementation of /api/config and /api/config/filters. | |
19 | // | |
20 | package client | |
21 | ||
22 | import ( | |
23 | "bytes" | |
24 | "encoding/json" | |
25 | "fmt" | |
26 | ) | |
27 | ||
28 | type ConfigResponse struct { | |
29 | StatusCode int | |
30 | Configs map[string]string `json:"configs"` | |
31 | } | |
32 | ||
33 | func (cfgResp *ConfigResponse) SetStatus(code int) { | |
34 | cfgResp.StatusCode = code | |
35 | } | |
36 | ||
37 | func (cfgResp *ConfigResponse) GetCustomParser() func(respCnt []byte) error { | |
38 | return func(respCnt []byte) error { | |
39 | return json.Unmarshal([]byte(fmt.Sprintf("{%s:%s}", `"Configs"`, string(respCnt))), &cfgResp) | |
40 | } | |
41 | } | |
42 | ||
43 | func (cfgResp *ConfigResponse) String() string { | |
44 | buffer := bytes.NewBuffer(nil) | |
45 | content, _ := json.Marshal(cfgResp) | |
46 | buffer.WriteString(fmt.Sprintf("%s\n", string(content))) | |
47 | return buffer.String() | |
48 | } | |
49 | ||
50 | func (c *clientImpl) Config() (*ConfigResponse, error) { | |
51 | configEndpoint := fmt.Sprintf("%s%s", c.tsdbEndpoint, ConfigPath) | |
52 | cfgResp := ConfigResponse{} | |
53 | if err := c.sendRequest(GetMethod, configEndpoint, "", &cfgResp); err != nil { | |
54 | return nil, err | |
55 | } | |
56 | return &cfgResp, nil | |
57 | } |
0 | // Copyright 2015 opentsdb-goclient authors. All Rights Reserved. | |
1 | // | |
2 | // Licensed under the Apache License, Version 2.0 (the "License"); | |
3 | // you may not use this file except in compliance with the License. | |
4 | // You may obtain a copy of the License at | |
5 | // | |
6 | // http://www.apache.org/licenses/LICENSE-2.0 | |
7 | // | |
8 | // Unless required by applicable law or agreed to in writing, software | |
9 | // distributed under the License is distributed on an "AS IS" BASIS, | |
10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
11 | // See the License for the specific language governing permissions and | |
12 | // limitations under the License. | |
13 | // | |
14 | // | |
15 | // Package client defines the client and the corresponding | |
16 | // rest api implementaion of OpenTSDB. | |
17 | // | |
18 | // dropcaches.go contains the structs and methods for the implementation of /api/dropcaches. | |
19 | // | |
20 | package client | |
21 | ||
22 | import ( | |
23 | "bytes" | |
24 | "encoding/json" | |
25 | "fmt" | |
26 | ) | |
27 | ||
28 | type DropcachesResponse struct { | |
29 | StatusCode int | |
30 | DropcachesInfo map[string]string `json:"DropcachesInfo"` | |
31 | } | |
32 | ||
33 | func (dropResp *DropcachesResponse) SetStatus(code int) { | |
34 | dropResp.StatusCode = code | |
35 | } | |
36 | ||
37 | func (dropResp *DropcachesResponse) GetCustomParser() func(respCnt []byte) error { | |
38 | return func(respCnt []byte) error { | |
39 | return json.Unmarshal([]byte(fmt.Sprintf("{%s:%s}", `"DropcachesInfo"`, string(respCnt))), &dropResp) | |
40 | } | |
41 | } | |
42 | ||
43 | func (dropResp *DropcachesResponse) String() string { | |
44 | buffer := bytes.NewBuffer(nil) | |
45 | content, _ := json.Marshal(dropResp) | |
46 | buffer.WriteString(fmt.Sprintf("%s\n", string(content))) | |
47 | return buffer.String() | |
48 | } | |
49 | ||
50 | func (c *clientImpl) Dropcaches() (*DropcachesResponse, error) { | |
51 | dropEndpoint := fmt.Sprintf("%s%s", c.tsdbEndpoint, DropcachesPath) | |
52 | dropResp := DropcachesResponse{} | |
53 | if err := c.sendRequest(GetMethod, dropEndpoint, "", &dropResp); err != nil { | |
54 | return nil, err | |
55 | } | |
56 | return &dropResp, nil | |
57 | } |
0 | // Copyright 2015 opentsdb-goclient authors. All Rights Reserved. | |
1 | // | |
2 | // Licensed under the Apache License, Version 2.0 (the "License"); | |
3 | // you may not use this file except in compliance with the License. | |
4 | // You may obtain a copy of the License at | |
5 | // | |
6 | // http://www.apache.org/licenses/LICENSE-2.0 | |
7 | // | |
8 | // Unless required by applicable law or agreed to in writing, software | |
9 | // distributed under the License is distributed on an "AS IS" BASIS, | |
10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
11 | // See the License for the specific language governing permissions and | |
12 | // limitations under the License. | |
13 | // | |
14 | // | |
15 | // Package client defines the client and the corresponding | |
16 | // rest api implementaion of OpenTSDB. | |
17 | // | |
18 | // put.go contains the structs and methods for the implementation of /api/put. | |
19 | // | |
20 | package client | |
21 | ||
22 | import ( | |
23 | "bytes" | |
24 | "encoding/json" | |
25 | "errors" | |
26 | "fmt" | |
27 | "strings" | |
28 | ) | |
29 | ||
30 | // DataPoint is the structure used to hold | |
31 | // the values of a metric item. Each attributes | |
32 | // in DataPoint matches the definition in | |
33 | // (http://opentsdb.net/docs/build/html/api_http/put.html). | |
34 | // | |
35 | type DataPoint struct { | |
36 | // The name of the metric which is about to be stored, and is required with non-empty value. | |
37 | Metric string `json:"metric"` | |
38 | ||
39 | // A Unix epoch style timestamp in seconds or milliseconds. | |
40 | // The timestamp must not contain non-numeric characters. | |
41 | // One can use time.Now().Unix() to set this attribute. | |
42 | // This attribute is also required with non-zero value. | |
43 | Timestamp int64 `json:"timestamp"` | |
44 | ||
45 | // The real type of Value only could be int, int64, float64, or string, and is required. | |
46 | Value interface{} `json:"value"` | |
47 | ||
48 | // A map of tag name/tag value pairs. At least one pair must be supplied. | |
49 | // Don't use too many tags, keep it to a fairly small number, usually up to 4 or 5 tags | |
50 | // (By default, OpenTSDB supports a maximum of 8 tags, which can be modified by add | |
51 | // configuration item 'tsd.storage.max_tags' in opentsdb.conf). | |
52 | Tags map[string]string `json:"tags"` | |
53 | } | |
54 | ||
55 | func (data *DataPoint) String() string { | |
56 | content, _ := json.Marshal(data) | |
57 | return string(content) | |
58 | } | |
59 | ||
60 | // PutError holds the error message for each putting DataPoint instance. | |
61 | // Only calling PUT() with "details" query parameter, the reponse of | |
62 | // the failed put data operation can contain an array PutError instance | |
63 | // to show the details for each failure. | |
64 | type PutError struct { | |
65 | Data DataPoint `json:"datapoint"` | |
66 | ErrorMsg string `json:"error"` | |
67 | } | |
68 | ||
69 | func (putErr *PutError) String() string { | |
70 | return fmt.Sprintf("%s:%s", putErr.ErrorMsg, putErr.Data.String()) | |
71 | } | |
72 | ||
73 | // PutResponse acts as the implementation of Response | |
74 | // in the /api/put scene. | |
75 | // It holds the status code and the response values defined in | |
76 | // the (http://opentsdb.net/docs/build/html/api_http/put.html). | |
77 | type PutResponse struct { | |
78 | StatusCode int | |
79 | Failed int64 `json:"failed"` | |
80 | Success int64 `json:"success"` | |
81 | Errors []PutError `json:"errors,omitempty"` | |
82 | } | |
83 | ||
84 | func (putResp *PutResponse) SetStatus(code int) { | |
85 | putResp.StatusCode = code | |
86 | } | |
87 | ||
88 | func (putResp *PutResponse) String() string { | |
89 | buffer := bytes.NewBuffer(nil) | |
90 | content, _ := json.Marshal(putResp) | |
91 | buffer.WriteString(fmt.Sprintf("%s\n", string(content))) | |
92 | return buffer.String() | |
93 | } | |
94 | ||
95 | func (putResp *PutResponse) GetCustomParser() func(respCnt []byte) error { | |
96 | return nil | |
97 | } | |
98 | ||
99 | func (c *clientImpl) Put(datas []DataPoint, queryParam string) (*PutResponse, error) { | |
100 | err := validateDataPoint(datas) | |
101 | if err != nil { | |
102 | return nil, err | |
103 | } | |
104 | if !isValidPutParam(queryParam) { | |
105 | return nil, errors.New("The given query param is invalid.") | |
106 | } | |
107 | var putEndpoint = "" | |
108 | if !isEmptyPutParam(queryParam) { | |
109 | putEndpoint = fmt.Sprintf("%s%s?%s", c.tsdbEndpoint, PutPath, queryParam) | |
110 | } else { | |
111 | putEndpoint = fmt.Sprintf("%s%s", c.tsdbEndpoint, PutPath) | |
112 | } | |
113 | ||
114 | dataGroups, err := c.splitProperGroups(datas) | |
115 | if err != nil { | |
116 | return nil, err | |
117 | } | |
118 | ||
119 | responses := make([]PutResponse, 0) | |
120 | for _, datapoints := range dataGroups { | |
121 | // The datas have been marshalled successfully in splitProperGroups(), | |
122 | // so now the returned error is always nil. | |
123 | reqBodyCnt, _ := getPutBodyContents(datapoints) | |
124 | putResp := PutResponse{} | |
125 | if err = c.sendRequest(PostMethod, putEndpoint, reqBodyCnt, &putResp); err != nil { | |
126 | // This kind of error only occurs during the process of sending request, | |
127 | // not including the scene of inserting datapoints into opentsdb. | |
128 | // So just return error once it happens. | |
129 | return nil, err | |
130 | } | |
131 | responses = append(responses, putResp) | |
132 | } | |
133 | ||
134 | globalResp := PutResponse{} | |
135 | globalResp.StatusCode = 200 | |
136 | for _, resp := range responses { | |
137 | globalResp.Failed = globalResp.Failed + resp.Failed | |
138 | globalResp.Success = globalResp.Success + resp.Success | |
139 | globalResp.Errors = append(globalResp.Errors, resp.Errors...) | |
140 | if resp.StatusCode != 200 && globalResp.StatusCode == 200 { | |
141 | globalResp.StatusCode = resp.StatusCode | |
142 | } | |
143 | } | |
144 | if globalResp.StatusCode == 200 { | |
145 | return &globalResp, nil | |
146 | } | |
147 | return nil, parsePutErrorMsg(&globalResp) | |
148 | } | |
149 | ||
150 | // splitProperGroups splits the given datapoints into groups, whose content size is | |
151 | // not larger than c.opentsdbCfg.MaxContentLength. | |
152 | // This method is an assurement of avoiding Put failure, when the content length of | |
153 | // the given datapoints in a single /api/put request exceeded the value of | |
154 | // tsd.http.request.max_chunk in the opentsdb config file. | |
155 | func (c *clientImpl) splitProperGroups(datapoints []DataPoint) ([][]DataPoint, error) { | |
156 | datasBytes, err := json.Marshal(&datapoints) | |
157 | if err != nil { | |
158 | return nil, fmt.Errorf("Failed to marshal the datapoints to be put: %v", err) | |
159 | } | |
160 | datapointGroups := make([][]DataPoint, 0) | |
161 | if len(datasBytes) > c.opentsdbCfg.MaxContentLength { | |
162 | datapointsSize := len(datapoints) | |
163 | endIndex := datapointsSize | |
164 | if endIndex > c.opentsdbCfg.MaxPutPointsNum { | |
165 | endIndex = c.opentsdbCfg.MaxPutPointsNum | |
166 | } | |
167 | startIndex := 0 | |
168 | for endIndex <= datapointsSize { | |
169 | tempdps := datapoints[startIndex:endIndex] | |
170 | tempSize := len(tempdps) | |
171 | // After successful unmarshal, the above marshal is definitly without error | |
172 | tempdpsBytes, _ := json.Marshal(&tempdps) | |
173 | if len(tempdpsBytes) <= c.opentsdbCfg.MaxContentLength { | |
174 | datapointGroups = append(datapointGroups, tempdps) | |
175 | startIndex = endIndex | |
176 | endIndex = startIndex + tempSize | |
177 | if endIndex > datapointsSize { | |
178 | endIndex = datapointsSize | |
179 | } | |
180 | } else { | |
181 | endIndex = endIndex - c.opentsdbCfg.DetectDeltaNum | |
182 | } | |
183 | if startIndex >= datapointsSize { | |
184 | break | |
185 | } | |
186 | } | |
187 | } else { | |
188 | datapointGroups = append(datapointGroups, datapoints) | |
189 | } | |
190 | return datapointGroups, nil | |
191 | } | |
192 | ||
193 | func parsePutErrorMsg(resp *PutResponse) error { | |
194 | buf := bytes.Buffer{} | |
195 | buf.WriteString(fmt.Sprintf("Failed to put %d datapoint(s) into opentsdb, statuscode %d:\n", resp.Failed, resp.StatusCode)) | |
196 | if len(resp.Errors) > 0 { | |
197 | for _, putError := range resp.Errors { | |
198 | buf.WriteString(fmt.Sprintf("\t%s\n", putError.String())) | |
199 | } | |
200 | } | |
201 | return errors.New(buf.String()) | |
202 | } | |
203 | ||
204 | func getPutBodyContents(datas []DataPoint) (string, error) { | |
205 | if len(datas) == 1 { | |
206 | result, err := json.Marshal(datas[0]) | |
207 | if err != nil { | |
208 | return "", errors.New(fmt.Sprintf("Failed to marshal datapoint: %v", err)) | |
209 | } | |
210 | return string(result), nil | |
211 | } else { | |
212 | reqBodyCnt, err := marshalDataPoints(datas) | |
213 | if err != nil { | |
214 | return "", errors.New(fmt.Sprintf("Failed to marshal datapoint: %v", err)) | |
215 | } | |
216 | return reqBodyCnt, nil | |
217 | } | |
218 | } | |
219 | ||
220 | func marshalDataPoints(datas []DataPoint) (string, error) { | |
221 | buffer := bytes.NewBuffer(nil) | |
222 | size := len(datas) | |
223 | buffer.WriteString("[") | |
224 | for index, item := range datas { | |
225 | result, err := json.Marshal(item) | |
226 | if err != nil { | |
227 | return "", err | |
228 | } | |
229 | buffer.Write(result) | |
230 | if index < size-1 { | |
231 | buffer.WriteString(",") | |
232 | } | |
233 | } | |
234 | buffer.WriteString("]") | |
235 | return buffer.String(), nil | |
236 | } | |
237 | ||
238 | func validateDataPoint(datas []DataPoint) error { | |
239 | if datas == nil || len(datas) == 0 { | |
240 | return errors.New("The given datapoint is empty.") | |
241 | } | |
242 | for _, data := range datas { | |
243 | if !isValidDataPoint(&data) { | |
244 | return errors.New("The value of the given datapoint is invalid.") | |
245 | } | |
246 | } | |
247 | return nil | |
248 | } | |
249 | ||
250 | func isValidDataPoint(data *DataPoint) bool { | |
251 | if data.Metric == "" || data.Timestamp == 0 || len(data.Tags) < 1 || data.Value == nil { | |
252 | return false | |
253 | } | |
254 | switch data.Value.(type) { | |
255 | case int64: | |
256 | return true | |
257 | case int: | |
258 | return true | |
259 | case float64: | |
260 | return true | |
261 | case float32: | |
262 | return true | |
263 | case string: | |
264 | return true | |
265 | default: | |
266 | return false | |
267 | } | |
268 | } | |
269 | ||
270 | func isValidPutParam(param string) bool { | |
271 | if isEmptyPutParam(param) { | |
272 | return true | |
273 | } | |
274 | param = strings.TrimSpace(param) | |
275 | if param != PutRespWithSummary && param != PutRespWithDetails { | |
276 | return false | |
277 | } | |
278 | return true | |
279 | } | |
280 | ||
281 | func isEmptyPutParam(param string) bool { | |
282 | return strings.TrimSpace(param) == "" | |
283 | } |
0 | // Copyright 2015 opentsdb-goclient authors. All Rights Reserved. | |
1 | // | |
2 | // Licensed under the Apache License, Version 2.0 (the "License"); | |
3 | // you may not use this file except in compliance with the License. | |
4 | // You may obtain a copy of the License at | |
5 | // | |
6 | // http://www.apache.org/licenses/LICENSE-2.0 | |
7 | // | |
8 | // Unless required by applicable law or agreed to in writing, software | |
9 | // distributed under the License is distributed on an "AS IS" BASIS, | |
10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
11 | // See the License for the specific language governing permissions and | |
12 | // limitations under the License. | |
13 | // | |
14 | // | |
15 | // Package client defines the client and the corresponding | |
16 | // rest api implementaion of OpenTSDB. | |
17 | // | |
18 | // query.go contains the structs and methods for the implementation of /api/query. | |
19 | // | |
20 | package client | |
21 | ||
22 | import ( | |
23 | "bytes" | |
24 | "encoding/json" | |
25 | "errors" | |
26 | "fmt" | |
27 | "sort" | |
28 | "strconv" | |
29 | "strings" | |
30 | ) | |
31 | ||
32 | // QueryParam is the structure used to hold | |
33 | // the querying parameters when calling /api/query. | |
34 | // Each attributes in QueryParam matches the definition in | |
35 | // (http://opentsdb.net/docs/build/html/api_http/query/index.html). | |
36 | // | |
37 | type QueryParam struct { | |
38 | // The start time for the query. This can be a relative or absolute timestamp. | |
39 | // The data type can only be string, int, or int64. | |
40 | // The value is required with non-zero value of the target type. | |
41 | Start interface{} `json:"start"` | |
42 | ||
43 | // An end time for the query. If not supplied, the TSD will assume the local | |
44 | // system time on the server. This may be a relative or absolute timestamp. | |
45 | // The data type can only be string, or int64. | |
46 | // The value is optional. | |
47 | End interface{} `json:"end,omitempty"` | |
48 | ||
49 | // One or more sub queries used to select the time series to return. | |
50 | // These may be metric m or TSUID tsuids queries | |
51 | // The value is required with at least one element | |
52 | Queries []SubQuery `json:"queries"` | |
53 | ||
54 | // An optional value is used to show whether or not to return annotations with a query. | |
55 | // The default is to return annotations for the requested timespan but this flag can disable the return. | |
56 | // This affects both local and global notes and overrides globalAnnotations | |
57 | NoAnnotations bool `json:"noAnnotations,omitempty"` | |
58 | ||
59 | // An optional value is used to show whether or not the query should retrieve global | |
60 | // annotations for the requested timespan. | |
61 | GlobalAnnotations bool `json:"globalAnnotations,omitempty"` | |
62 | ||
63 | // An optional value is used to show whether or not to output data point timestamps in milliseconds or seconds. | |
64 | // If this flag is not provided and there are multiple data points within a second, | |
65 | // those data points will be down sampled using the query's aggregation function. | |
66 | MsResolution bool `json:"msResolution,omitempty"` | |
67 | ||
68 | // An optional value is used to show whether or not to output the TSUIDs associated with timeseries in the results. | |
69 | // If multiple time series were aggregated into one set, multiple TSUIDs will be returned in a sorted manner. | |
70 | ShowTSUIDs bool `json:"showTSUIDs,omitempty"` | |
71 | ||
72 | // An optional value is used to show whether or not can be paased to the JSON with a POST to delete any data point | |
73 | // that match the given query. | |
74 | Delete bool `json:"delete,omitempty"` | |
75 | } | |
76 | ||
77 | func (query *QueryParam) String() string { | |
78 | content, _ := json.Marshal(query) | |
79 | return string(content) | |
80 | } | |
81 | ||
82 | // SubQuery is the structure used to hold | |
83 | // the subquery parameters when calling /api/query. | |
84 | // Each attributes in SubQuery matches the definition in | |
85 | // (http://opentsdb.net/docs/build/html/api_http/query/index.html). | |
86 | // | |
87 | type SubQuery struct { | |
88 | // The name of an aggregation function to use. | |
89 | // The value is required with non-empty one in the range of | |
90 | // the response of calling /api/aggregators. | |
91 | // | |
92 | // By default, the potential values and corresponding descriptions are as followings: | |
93 | // "sum": Adds all of the data points for a timestamp. | |
94 | // "min": Picks the smallest data point for each timestamp. | |
95 | // "max": Picks the largest data point for each timestamp. | |
96 | // "avg": Averages the values for the data points at each timestamp. | |
97 | Aggregator string `json:"aggregator"` | |
98 | ||
99 | // The name of a metric stored in the system. | |
100 | // The value is reqiured with non-empty value. | |
101 | Metric string `json:"metric"` | |
102 | ||
103 | // An optional value is used to show whether or not the data should be | |
104 | // converted into deltas before returning. This is useful if the metric is a | |
105 | // continously incrementing counter and you want to view the rate of change between data points. | |
106 | Rate bool `json:"rate,omitempty"` | |
107 | ||
108 | // rateOptions represents monotonically increasing counter handling options. | |
109 | // The value is optional. | |
110 | // Currently there is only three kind of value can be set to this map: | |
111 | // Only three keys can be set into the rateOption parameter of the QueryParam is | |
112 | // QueryRateOptionCounter (value type is bool), QueryRateOptionCounterMax (value type is int,int64) | |
113 | // QueryRateOptionResetValue (value type is int,int64) | |
114 | RateParams map[string]interface{} `json:"rateOptions,omitempty"` | |
115 | ||
116 | // An optional value downsampling function to reduce the amount of data returned. | |
117 | Downsample string `json:"downsample,omitempty"` | |
118 | ||
119 | // An optional value to drill down to specific timeseries or group results by tag, | |
120 | // supply one or more map values in the same format as the query string. Tags are converted to filters in 2.2. | |
121 | // Note that if no tags are specified, all metrics in the system will be aggregated into the results. | |
122 | // It will be deprecated in OpenTSDB 2.2. | |
123 | Tags map[string]string `json:"tags,omitempty"` | |
124 | ||
125 | // An optional value used to filter the time series emitted in the results. | |
126 | // Note that if no filters are specified, all time series for the given | |
127 | // metric will be aggregated into the results. | |
128 | Fiters []Filter `json:"filters,omitempty"` | |
129 | } | |
130 | ||
131 | // Filter is the structure used to hold the filter parameters when calling /api/query. | |
132 | // Each attributes in Filter matches the definition in | |
133 | // (http://opentsdb.net/docs/build/html/api_http/query/index.html). | |
134 | // | |
135 | type Filter struct { | |
136 | // The name of the filter to invoke. The value is required with a non-empty | |
137 | // value in the range of calling /api/config/filters. | |
138 | Type string `json:"type"` | |
139 | ||
140 | // The tag key to invoke the filter on, required with a non-empty value | |
141 | Tagk string `json:"tagk"` | |
142 | ||
143 | // The filter expression to evaluate and depends on the filter being used, required with a non-empty value | |
144 | FilterExp string `json:"filter"` | |
145 | ||
146 | // An optional value to show whether or not to group the results by each value matched by the filter. | |
147 | // By default all values matching the filter will be aggregated into a single series. | |
148 | GroupBy bool `json:"groupBy"` | |
149 | } | |
150 | ||
151 | // QueryResponse acts as the implementation of Response in the /api/query scene. | |
152 | // It holds the status code and the response values defined in the | |
153 | // (http://opentsdb.net/docs/build/html/api_http/query/index.html). | |
154 | // | |
155 | type QueryResponse struct { | |
156 | StatusCode int | |
157 | QueryRespCnts []QueryRespItem `json:"queryRespCnts"` | |
158 | ErrorMsg map[string]interface{} `json:"error"` | |
159 | } | |
160 | ||
161 | func (queryResp *QueryResponse) String() string { | |
162 | buffer := bytes.NewBuffer(nil) | |
163 | content, _ := json.Marshal(queryResp) | |
164 | buffer.WriteString(fmt.Sprintf("%s\n", string(content))) | |
165 | return buffer.String() | |
166 | } | |
167 | ||
168 | func (queryResp *QueryResponse) SetStatus(code int) { | |
169 | queryResp.StatusCode = code | |
170 | } | |
171 | ||
172 | func (queryResp *QueryResponse) GetCustomParser() func(respCnt []byte) error { | |
173 | return func(respCnt []byte) error { | |
174 | originRespStr := string(respCnt) | |
175 | var respStr string | |
176 | if queryResp.StatusCode == 200 && strings.Contains(originRespStr, "[") && strings.Contains(originRespStr, "]") { | |
177 | respStr = fmt.Sprintf("{%s:%s}", `"queryRespCnts"`, originRespStr) | |
178 | } else { | |
179 | respStr = originRespStr | |
180 | } | |
181 | return json.Unmarshal([]byte(respStr), &queryResp) | |
182 | } | |
183 | } | |
184 | ||
185 | // QueryRespItem acts as the implementation of Response in the /api/query scene. | |
186 | // It holds the response item defined in the | |
187 | // (http://opentsdb.net/docs/build/html/api_http/query/index.html). | |
188 | // | |
189 | type QueryRespItem struct { | |
190 | // Name of the metric retreived for the time series | |
191 | Metric string `json:"metric"` | |
192 | ||
193 | // A list of tags only returned when the results are for a single time series. | |
194 | // If results are aggregated, this value may be null or an empty map | |
195 | Tags map[string]string `json:"tags"` | |
196 | ||
197 | // If more than one timeseries were included in the result set, i.e. they were aggregated, | |
198 | // this will display a list of tag names that were found in common across all time series. | |
199 | // Note that: Api Doc uses 'aggreatedTags', but actual response uses 'aggregateTags' | |
200 | AggregatedTags []string `json:"aggregateTags"` | |
201 | ||
202 | // Retrieved datapoints after being processed by the aggregators. Each data point consists | |
203 | // of a timestamp and a value, the format determined by the serializer. | |
204 | // For the JSON serializer, the timestamp will always be a Unix epoch style integer followed | |
205 | // by the value as an integer or a floating point. | |
206 | // For example, the default output is "dps"{"<timestamp>":<value>}. | |
207 | // By default the timestamps will be in seconds. If the msResolution flag is set, then the | |
208 | // timestamps will be in milliseconds. | |
209 | // | |
210 | // Because the elements of map is out of order, using common way to iterate Dps will not get | |
211 | // datapoints with timestamps out of order. | |
212 | // So be aware that one should use '(qri *QueryRespItem) GetDataPoints() []*DataPoint' to | |
213 | // acquire the real ascending datapoints. | |
214 | Dps map[string]interface{} `json:"dps"` | |
215 | ||
216 | // If the query retrieved annotations for timeseries over the requested timespan, they will | |
217 | // be returned in this group. Annotations for every timeseries will be merged into one set | |
218 | // and sorted by start_time. Aggregator functions do not affect annotations, all annotations | |
219 | // will be returned for the span. | |
220 | // The value is optional. | |
221 | Annotations []Annotation `json:"annotations,omitempty"` | |
222 | ||
223 | // If requested by the user, the query will scan for global annotations during | |
224 | // the timespan and the results returned in this group. | |
225 | // The value is optional. | |
226 | GlobalAnnotations []Annotation `json:"globalAnnotations,omitempty"` | |
227 | } | |
228 | ||
229 | // GetDataPoints returns the real ascending datapoints from the information of the related QueryRespItem. | |
230 | func (qri *QueryRespItem) GetDataPoints() []*DataPoint { | |
231 | datapoints := make([]*DataPoint, 0) | |
232 | timestampStrs := qri.getSortedTimestampStrs() | |
233 | for _, timestampStr := range timestampStrs { | |
234 | timestamp, _ := strconv.ParseInt(timestampStr, 10, 64) | |
235 | datapoint := &DataPoint{ | |
236 | Metric: qri.Metric, | |
237 | Value: qri.Dps[timestampStr], | |
238 | Tags: qri.Tags, | |
239 | Timestamp: timestamp, | |
240 | } | |
241 | datapoints = append(datapoints, datapoint) | |
242 | } | |
243 | return datapoints | |
244 | } | |
245 | ||
246 | // getSortedTimestampStrs returns a slice of the ascending timestamp with | |
247 | // string format for the Dps of the related QueryRespItem instance. | |
248 | func (qri *QueryRespItem) getSortedTimestampStrs() []string { | |
249 | timestampStrs := make([]string, 0) | |
250 | for timestampStr := range qri.Dps { | |
251 | timestampStrs = append(timestampStrs, timestampStr) | |
252 | } | |
253 | sort.Strings(timestampStrs) | |
254 | return timestampStrs | |
255 | } | |
256 | ||
257 | // GetLatestDataPoint returns latest datapoint for the related QueryRespItem instance. | |
258 | func (qri *QueryRespItem) GetLatestDataPoint() *DataPoint { | |
259 | timestampStrs := qri.getSortedTimestampStrs() | |
260 | size := len(timestampStrs) | |
261 | if size == 0 { | |
262 | return nil | |
263 | } | |
264 | timestamp, _ := strconv.ParseInt(timestampStrs[size-1], 10, 64) | |
265 | datapoint := &DataPoint{ | |
266 | Metric: qri.Metric, | |
267 | Value: qri.Dps[timestampStrs[size-1]], | |
268 | Tags: qri.Tags, | |
269 | Timestamp: timestamp, | |
270 | } | |
271 | return datapoint | |
272 | } | |
273 | ||
274 | func (c *clientImpl) Query(param QueryParam) (*QueryResponse, error) { | |
275 | if !isValidQueryParam(¶m) { | |
276 | return nil, errors.New("The given query param is invalid.\n") | |
277 | } | |
278 | queryEndpoint := fmt.Sprintf("%s%s", c.tsdbEndpoint, QueryPath) | |
279 | reqBodyCnt, err := getQueryBodyContents(¶m) | |
280 | if err != nil { | |
281 | return nil, err | |
282 | } | |
283 | queryResp := QueryResponse{} | |
284 | if err = c.sendRequest(PostMethod, queryEndpoint, reqBodyCnt, &queryResp); err != nil { | |
285 | return nil, err | |
286 | } | |
287 | return &queryResp, nil | |
288 | } | |
289 | ||
290 | func getQueryBodyContents(param interface{}) (string, error) { | |
291 | result, err := json.Marshal(param) | |
292 | if err != nil { | |
293 | return "", errors.New(fmt.Sprintf("Failed to marshal query param: %v\n", err)) | |
294 | } | |
295 | return string(result), nil | |
296 | } | |
297 | ||
298 | func isValidQueryParam(param *QueryParam) bool { | |
299 | if param.Queries == nil || len(param.Queries) == 0 { | |
300 | return false | |
301 | } | |
302 | if !isValidTimePoint(param.Start) { | |
303 | return false | |
304 | } | |
305 | for _, query := range param.Queries { | |
306 | if len(query.Aggregator) == 0 || len(query.Metric) == 0 { | |
307 | return false | |
308 | } | |
309 | for k, _ := range query.RateParams { | |
310 | if k != QueryRateOptionCounter && k != QueryRateOptionCounterMax && k != QueryRateOptionResetValue { | |
311 | return false | |
312 | } | |
313 | } | |
314 | } | |
315 | return true | |
316 | } | |
317 | ||
318 | func isValidTimePoint(timePoint interface{}) bool { | |
319 | if timePoint == nil { | |
320 | return false | |
321 | } | |
322 | switch v := timePoint.(type) { | |
323 | case int: | |
324 | if v <= 0 { | |
325 | return false | |
326 | } | |
327 | case int64: | |
328 | if v <= 0 { | |
329 | return false | |
330 | } | |
331 | case string: | |
332 | if v == "" { | |
333 | return false | |
334 | } | |
335 | ||
336 | default: | |
337 | return false | |
338 | } | |
339 | return true | |
340 | } |
0 | // Copyright 2015 opentsdb-goclient authors. All Rights Reserved. | |
1 | // | |
2 | // Licensed under the Apache License, Version 2.0 (the "License"); | |
3 | // you may not use this file except in compliance with the License. | |
4 | // You may obtain a copy of the License at | |
5 | // | |
6 | // http://www.apache.org/licenses/LICENSE-2.0 | |
7 | // | |
8 | // Unless required by applicable law or agreed to in writing, software | |
9 | // distributed under the License is distributed on an "AS IS" BASIS, | |
10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
11 | // See the License for the specific language governing permissions and | |
12 | // limitations under the License. | |
13 | // | |
14 | // | |
15 | // Package client defines the client and the corresponding | |
16 | // rest api implementaion of OpenTSDB. | |
17 | // | |
18 | // query_last.go contains the structs and methods for the implementation of /api/query/last, | |
19 | // which is fully supported since v2.2 of opentsdb. | |
20 | // | |
21 | package client | |
22 | ||
23 | import ( | |
24 | "bytes" | |
25 | "encoding/json" | |
26 | "errors" | |
27 | "fmt" | |
28 | "strings" | |
29 | ) | |
30 | ||
31 | // QueryLastParam is the structure used to hold | |
32 | // the querying parameters when calling /api/query/last. | |
33 | // Each attributes in QueryLastParam matches the definition in | |
34 | // (http://opentsdb.net/docs/build/html/api_http/query/last.html). | |
35 | // | |
36 | type QueryLastParam struct { | |
37 | // One or more sub queries used to select the time series to return. | |
38 | // These may be metric m or TSUID tsuids queries | |
39 | // The value is required with at least one element | |
40 | Queries []SubQueryLast `json:"queries"` | |
41 | ||
42 | // An optional flag is used to determine whether or not to resolve the TSUIDs of results to | |
43 | // their metric and tag names. The default value is false. | |
44 | ResolveNames bool `json:"resolveNames"` | |
45 | ||
46 | // An optional number of hours is used to search in the past for data. If set to 0 then the | |
47 | // timestamp of the meta data counter for the time series is used. | |
48 | BackScan int `json:"backScan"` | |
49 | } | |
50 | ||
51 | func (query *QueryLastParam) String() string { | |
52 | content, _ := json.Marshal(query) | |
53 | return string(content) | |
54 | } | |
55 | ||
56 | // SubQueryLast is the structure used to hold | |
57 | // the subquery parameters when calling /api/query/last. | |
58 | // Each attributes in SubQueryLast matches the definition in | |
59 | // (http://opentsdb.net/docs/build/html/api_http/query/last.html). | |
60 | // | |
61 | type SubQueryLast struct { | |
62 | // The name of a metric stored in the system. | |
63 | // The value is reqiured with non-empty value. | |
64 | Metric string `json:"metric"` | |
65 | ||
66 | // An optional value to drill down to specific timeseries or group results by tag, | |
67 | // supply one or more map values in the same format as the query string. Tags are converted to filters in 2.2. | |
68 | // Note that if no tags are specified, all metrics in the system will be aggregated into the results. | |
69 | // It will be deprecated in OpenTSDB 2.2. | |
70 | Tags map[string]string `json:"tags,omitempty"` | |
71 | } | |
72 | ||
73 | // QueryLastResponse acts as the implementation of Response in the /api/query/last scene. | |
74 | // It holds the status code and the response values defined in the | |
75 | // (http://opentsdb.net/docs/build/html/api_http/query/last.html). | |
76 | // | |
77 | type QueryLastResponse struct { | |
78 | StatusCode int | |
79 | QueryRespCnts []QueryRespLastItem `json:"queryRespCnts,omitempty"` | |
80 | ErrorMsg map[string]interface{} `json:"error"` | |
81 | } | |
82 | ||
83 | func (queryLastResp *QueryLastResponse) String() string { | |
84 | buffer := bytes.NewBuffer(nil) | |
85 | content, _ := json.Marshal(queryLastResp) | |
86 | buffer.WriteString(fmt.Sprintf("%s\n", string(content))) | |
87 | return buffer.String() | |
88 | } | |
89 | ||
90 | func (queryLastResp *QueryLastResponse) SetStatus(code int) { | |
91 | queryLastResp.StatusCode = code | |
92 | } | |
93 | ||
94 | func (queryLastResp *QueryLastResponse) GetCustomParser() func(respCnt []byte) error { | |
95 | return func(respCnt []byte) error { | |
96 | originRespStr := string(respCnt) | |
97 | var respStr string | |
98 | if queryLastResp.StatusCode == 200 && strings.Contains(originRespStr, "[") && strings.Contains(originRespStr, "]") { | |
99 | respStr = fmt.Sprintf("{%s:%s}", `"queryRespCnts"`, originRespStr) | |
100 | } else { | |
101 | respStr = originRespStr | |
102 | } | |
103 | return json.Unmarshal([]byte(respStr), &queryLastResp) | |
104 | } | |
105 | } | |
106 | ||
107 | // QueryRespLastItem acts as the implementation of Response in the /api/query/last scene. | |
108 | // It holds the response item defined in the | |
109 | // (http://opentsdb.net/docs/build/html/api_http/query/last.html). | |
110 | // | |
111 | type QueryRespLastItem struct { | |
112 | // Name of the metric retreived for the time series. | |
113 | // Only returned if resolve was set to true. | |
114 | Metric string `json:"metric"` | |
115 | ||
116 | // A list of tags only returned when the results are for a single time series. | |
117 | // If results are aggregated, this value may be null or an empty map. | |
118 | // Only returned if resolve was set to true. | |
119 | Tags map[string]string `json:"tags"` | |
120 | ||
121 | // A Unix epoch timestamp, in milliseconds, when the data point was written. | |
122 | Timestamp int64 `json:"timestamp"` | |
123 | ||
124 | // The value of the data point enclosed in quotation marks as a string | |
125 | Value string `json:"value"` | |
126 | ||
127 | // The hexadecimal TSUID for the time series | |
128 | Tsuid string `json:"tsuid"` | |
129 | } | |
130 | ||
131 | func (c *clientImpl) QueryLast(param QueryLastParam) (*QueryLastResponse, error) { | |
132 | if !isValidQueryLastParam(¶m) { | |
133 | return nil, errors.New("The given query param is invalid.\n") | |
134 | } | |
135 | queryEndpoint := fmt.Sprintf("%s%s", c.tsdbEndpoint, QueryLastPath) | |
136 | reqBodyCnt, err := getQueryBodyContents(¶m) | |
137 | if err != nil { | |
138 | return nil, err | |
139 | } | |
140 | queryResp := QueryLastResponse{} | |
141 | if err = c.sendRequest(PostMethod, queryEndpoint, reqBodyCnt, &queryResp); err != nil { | |
142 | return nil, err | |
143 | } | |
144 | return &queryResp, nil | |
145 | } | |
146 | ||
147 | func isValidQueryLastParam(param *QueryLastParam) bool { | |
148 | if param.Queries == nil || len(param.Queries) == 0 { | |
149 | return false | |
150 | } | |
151 | for _, query := range param.Queries { | |
152 | if len(query.Metric) == 0 { | |
153 | return false | |
154 | } | |
155 | } | |
156 | return true | |
157 | } |
0 | // Copyright 2015 opentsdb-goclient authors. All Rights Reserved. | |
1 | // | |
2 | // Licensed under the Apache License, Version 2.0 (the "License"); | |
3 | // you may not use this file except in compliance with the License. | |
4 | // You may obtain a copy of the License at | |
5 | // | |
6 | // http://www.apache.org/licenses/LICENSE-2.0 | |
7 | // | |
8 | // Unless required by applicable law or agreed to in writing, software | |
9 | // distributed under the License is distributed on an "AS IS" BASIS, | |
10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
11 | // See the License for the specific language governing permissions and | |
12 | // limitations under the License. | |
13 | // | |
14 | // | |
15 | // Package client defines the client and the corresponding | |
16 | // rest api implementaion of OpenTSDB. | |
17 | // | |
18 | // serializers.go contains the structs and methods for the implementation of /api/serializers. | |
19 | // | |
20 | package client | |
21 | ||
22 | import ( | |
23 | "bytes" | |
24 | "encoding/json" | |
25 | "fmt" | |
26 | ) | |
27 | ||
28 | type SerialResponse struct { | |
29 | StatusCode int | |
30 | Serializers []Serializer `json:"Serializers"` | |
31 | } | |
32 | ||
33 | type Serializer struct { | |
34 | SerializerName string `json:"serializer"` | |
35 | Formatters []string `json:"formatters"` | |
36 | Parsers []string `json:"parsers"` | |
37 | Class string `json:"class,omitempty"` | |
38 | ResContType string `json:"response_content_type,omitempty"` | |
39 | ReqContType string `json:"request_content_type,omitempty"` | |
40 | } | |
41 | ||
42 | func (serialResp *SerialResponse) SetStatus(code int) { | |
43 | serialResp.StatusCode = code | |
44 | } | |
45 | ||
46 | func (serialResp *SerialResponse) GetCustomParser() func(respCnt []byte) error { | |
47 | return func(respCnt []byte) error { | |
48 | return json.Unmarshal([]byte(fmt.Sprintf("{%s:%s}", `"Serializers"`, string(respCnt))), &serialResp) | |
49 | } | |
50 | } | |
51 | ||
52 | func (serialResp *SerialResponse) String() string { | |
53 | buffer := bytes.NewBuffer(nil) | |
54 | content, _ := json.Marshal(serialResp) | |
55 | buffer.WriteString(fmt.Sprintf("%s\n", string(content))) | |
56 | return buffer.String() | |
57 | } | |
58 | ||
59 | func (c *clientImpl) Serializers() (*SerialResponse, error) { | |
60 | serialEndpoint := fmt.Sprintf("%s%s", c.tsdbEndpoint, SerializersPath) | |
61 | serialResp := SerialResponse{} | |
62 | if err := c.sendRequest(GetMethod, serialEndpoint, "", &serialResp); err != nil { | |
63 | return nil, err | |
64 | } | |
65 | return &serialResp, nil | |
66 | } |
0 | // Copyright 2015 opentsdb-goclient authors. All Rights Reserved. | |
1 | // | |
2 | // Licensed under the Apache License, Version 2.0 (the "License"); | |
3 | // you may not use this file except in compliance with the License. | |
4 | // You may obtain a copy of the License at | |
5 | // | |
6 | // http://www.apache.org/licenses/LICENSE-2.0 | |
7 | // | |
8 | // Unless required by applicable law or agreed to in writing, software | |
9 | // distributed under the License is distributed on an "AS IS" BASIS, | |
10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
11 | // See the License for the specific language governing permissions and | |
12 | // limitations under the License. | |
13 | // | |
14 | // | |
15 | // Package client defines the client and the corresponding | |
16 | // rest api implementaion of OpenTSDB. | |
17 | // | |
18 | // stats.go contains the structs and methods for the implementation of /api/stats. | |
19 | // | |
20 | package client | |
21 | ||
22 | import ( | |
23 | "bytes" | |
24 | "encoding/json" | |
25 | "fmt" | |
26 | ) | |
27 | ||
28 | type StatsResponse struct { | |
29 | StatusCode int | |
30 | Metrics []MetricInfo `json:"Metrics"` | |
31 | } | |
32 | ||
33 | type MetricInfo struct { | |
34 | Metric string `json:"metric"` | |
35 | Timestamp int64 `json:"timestamp"` | |
36 | Value interface{} `json:"value"` | |
37 | Tags map[string]string `json:"tags"` | |
38 | } | |
39 | ||
40 | func (statsResp *StatsResponse) SetStatus(code int) { | |
41 | statsResp.StatusCode = code | |
42 | } | |
43 | ||
44 | func (statsResp *StatsResponse) GetCustomParser() func(respCnt []byte) error { | |
45 | return func(respCnt []byte) error { | |
46 | return json.Unmarshal([]byte(fmt.Sprintf("{%s:%s}", `"Metrics"`, string(respCnt))), &statsResp) | |
47 | } | |
48 | } | |
49 | ||
50 | func (statsResp *StatsResponse) String() string { | |
51 | buffer := bytes.NewBuffer(nil) | |
52 | content, _ := json.Marshal(statsResp) | |
53 | buffer.WriteString(fmt.Sprintf("%s\n", string(content))) | |
54 | return buffer.String() | |
55 | } | |
56 | ||
57 | func (c *clientImpl) Stats() (*StatsResponse, error) { | |
58 | statsEndpoint := fmt.Sprintf("%s%s", c.tsdbEndpoint, StatsPath) | |
59 | statsResp := StatsResponse{} | |
60 | if err := c.sendRequest(GetMethod, statsEndpoint, "", &statsResp); err != nil { | |
61 | return nil, err | |
62 | } | |
63 | return &statsResp, nil | |
64 | } |
0 | // Copyright 2015 opentsdb-goclient authors. All Rights Reserved. | |
1 | // | |
2 | // Licensed under the Apache License, Version 2.0 (the "License"); | |
3 | // you may not use this file except in compliance with the License. | |
4 | // You may obtain a copy of the License at | |
5 | // | |
6 | // http://www.apache.org/licenses/LICENSE-2.0 | |
7 | // | |
8 | // Unless required by applicable law or agreed to in writing, software | |
9 | // distributed under the License is distributed on an "AS IS" BASIS, | |
10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
11 | // See the License for the specific language governing permissions and | |
12 | // limitations under the License. | |
13 | // | |
14 | // | |
15 | // Package client defines the client and the corresponding | |
16 | // rest api implementaion of OpenTSDB. | |
17 | // | |
18 | // suggest.go contains the structs and methods for the implementation of /api/suggest. | |
19 | // | |
20 | package client | |
21 | ||
22 | import ( | |
23 | "bytes" | |
24 | "encoding/json" | |
25 | "errors" | |
26 | "fmt" | |
27 | "strings" | |
28 | ) | |
29 | ||
30 | // SuggestParam is the structure used to hold | |
31 | // the querying parameters when calling /api/suggest. | |
32 | // Each attributes in SuggestParam matches the definition in | |
33 | // (http://opentsdb.net/docs/build/html/api_http/suggest.html). | |
34 | // | |
35 | type SuggestParam struct { | |
36 | // The type of data to auto complete on. | |
37 | // Must be one of the following: metrics, tagk or tagv. | |
38 | // It is required. | |
39 | // Only the one of the three query type can be used: | |
40 | // TypeMetrics, TypeTagk, TypeTagv | |
41 | Type string `json:"type"` | |
42 | ||
43 | // An optional string value to match on for the given type | |
44 | Q string `json:"q,omitempty"` | |
45 | ||
46 | // An optional integer value presenting the maximum number of suggested | |
47 | // results to return. If it is set, it must be greater than 0. | |
48 | MaxResultNum int `json:"max,omitempty"` | |
49 | } | |
50 | ||
51 | func (sugParam *SuggestParam) String() string { | |
52 | contents, _ := json.Marshal(sugParam) | |
53 | return string(contents) | |
54 | } | |
55 | ||
56 | type SuggestResponse struct { | |
57 | StatusCode int | |
58 | ResultInfo []string `json:"ResultInfo"` | |
59 | } | |
60 | ||
61 | func (sugResp *SuggestResponse) SetStatus(code int) { | |
62 | sugResp.StatusCode = code | |
63 | } | |
64 | ||
65 | func (sugResp *SuggestResponse) GetCustomParser() func(respCnt []byte) error { | |
66 | return func(respCnt []byte) error { | |
67 | return json.Unmarshal([]byte(fmt.Sprintf("{%s:%s}", `"ResultInfo"`, string(respCnt))), &sugResp) | |
68 | } | |
69 | } | |
70 | ||
71 | func (sugResp *SuggestResponse) String() string { | |
72 | buffer := bytes.NewBuffer(nil) | |
73 | content, _ := json.Marshal(sugResp) | |
74 | buffer.WriteString(fmt.Sprintf("%s\n", string(content))) | |
75 | return buffer.String() | |
76 | } | |
77 | ||
78 | func (c *clientImpl) Suggest(sugParam SuggestParam) (*SuggestResponse, error) { | |
79 | if !isValidSuggestParam(&sugParam) { | |
80 | return nil, errors.New("The given suggest param is invalid.\n") | |
81 | } | |
82 | sugEndpoint := fmt.Sprintf("%s%s", c.tsdbEndpoint, SuggestPath) | |
83 | reqBodyCnt, err := getSuggestBodyContents(&sugParam) | |
84 | if err != nil { | |
85 | return nil, err | |
86 | } | |
87 | fmt.Println(reqBodyCnt) | |
88 | sugResp := SuggestResponse{} | |
89 | if err := c.sendRequest(PostMethod, sugEndpoint, reqBodyCnt, &sugResp); err != nil { | |
90 | return nil, err | |
91 | } | |
92 | return &sugResp, nil | |
93 | } | |
94 | ||
95 | func isValidSuggestParam(sugParam *SuggestParam) bool { | |
96 | if sugParam.Type == "" { | |
97 | return false | |
98 | } | |
99 | types := []string{TypeMetrics, TypeTagk, TypeTagv} | |
100 | sugParam.Type = strings.TrimSpace(sugParam.Type) | |
101 | for _, typeItem := range types { | |
102 | if sugParam.Type == typeItem { | |
103 | return true | |
104 | } | |
105 | } | |
106 | return false | |
107 | } | |
108 | ||
109 | func getSuggestBodyContents(sugParam *SuggestParam) (string, error) { | |
110 | result, err := json.Marshal(sugParam) | |
111 | if err != nil { | |
112 | return "", errors.New(fmt.Sprintf("Failed to marshal suggest param: %v\n", err)) | |
113 | } | |
114 | return string(result), nil | |
115 | } |
0 | // Copyright 2015 opentsdb-goclient authors. All Rights Reserved. | |
1 | // | |
2 | // Licensed under the Apache License, Version 2.0 (the "License"); | |
3 | // you may not use this file except in compliance with the License. | |
4 | // You may obtain a copy of the License at | |
5 | // | |
6 | // http://www.apache.org/licenses/LICENSE-2.0 | |
7 | // | |
8 | // Unless required by applicable law or agreed to in writing, software | |
9 | // distributed under the License is distributed on an "AS IS" BASIS, | |
10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
11 | // See the License for the specific language governing permissions and | |
12 | // limitations under the License. | |
13 | // | |
14 | // | |
15 | // Package client defines the client and the corresponding | |
16 | // rest api implementaion of OpenTSDB. | |
17 | // | |
18 | // Every metric, tag name and tag value is associated with a unique identifier (UID). | |
19 | // Internally, the UID is a binary array assigned to a text value the first time it is | |
20 | // encountered or via an explicit assignment request. This endpoint provides utilities | |
21 | // for managing UIDs and their associated data. Please see the UID endpoint TOC below | |
22 | // for information on what functions are implemented. | |
23 | // | |
24 | // UIDs exposed via the API are encoded as hexadecimal strings. The UID 42 would be expressed | |
25 | // as 00002A given the default UID width of 3 bytes. | |
26 | // You may also edit meta data associated with timeseries or individual UID objects via the UID endpoint. | |
27 | // | |
28 | // uid.go contains the structs and methods for the implementation of | |
29 | // /api/uid/tsmeta, /api/uid/assign, /api/uid/uidmeta. | |
30 | // | |
31 | package client | |
32 | ||
33 | import ( | |
34 | "bytes" | |
35 | "encoding/json" | |
36 | "errors" | |
37 | "fmt" | |
38 | "strings" | |
39 | ) | |
40 | ||
41 | // UIDMetaData is the structure used to hold | |
42 | // the parameters when calling (POST,PUT) /api/uid/uidmeta. | |
43 | // Each attributes in UIDMetaData matches the definition in | |
44 | // (http://opentsdb.net/docs/build/html/api_http/uid/uidmeta.html). | |
45 | // | |
46 | type UIDMetaData struct { | |
47 | // A required hexadecimal representation of the UID | |
48 | Uid string `json:"uid,omitempty"` | |
49 | ||
50 | // A required type of UID, must be metric, tagk or tagv | |
51 | Type string `json:"type,omitempty"` | |
52 | ||
53 | // An optional brief description of what the UID represents | |
54 | Description string `json:"description,omitempty"` | |
55 | ||
56 | // An optional short name that can be displayed in GUIs instead of the default name | |
57 | DisplayName string `json:"displayName,omitempty"` | |
58 | ||
59 | // An optional detailed notes about what the UID represents | |
60 | Notes string `json:"notes,omitempty"` | |
61 | ||
62 | // An optional key/value map to store custom fields and values | |
63 | Custom map[string]string `json:"custom,omitempty"` | |
64 | } | |
65 | ||
66 | // UIDMetaDataResponse acts as the implementation of Response in the /api/uid/uidmeta scene. | |
67 | // It holds the status code and the response values defined in the | |
68 | // (http://opentsdb.net/docs/build/html/api_http/uid/uidmeta.html). | |
69 | // | |
70 | type UIDMetaDataResponse struct { | |
71 | UIDMetaData | |
72 | ||
73 | StatusCode int | |
74 | ||
75 | // The name of the UID as given when the data point was stored or the UID assigned | |
76 | Name string `json:"name,omitempty"` | |
77 | ||
78 | // A Unix epoch timestamp in seconds when the UID was first created. | |
79 | // If the meta data was not stored when the UID was assigned, this value may be 0. | |
80 | Created int64 `json:"created,omitempty"` | |
81 | ||
82 | ErrorInfo map[string]interface{} `json:"error,omitempty"` | |
83 | } | |
84 | ||
85 | func (uidMetaDataResp *UIDMetaDataResponse) SetStatus(code int) { | |
86 | uidMetaDataResp.StatusCode = code | |
87 | } | |
88 | ||
89 | func (uidMetaDataResp *UIDMetaDataResponse) GetCustomParser() func(respCnt []byte) error { | |
90 | return func(respCnt []byte) error { | |
91 | var resultBytes []byte | |
92 | if uidMetaDataResp.StatusCode == 204 || // The OpenTSDB deletes a UIDMetaData successfully, or | |
93 | uidMetaDataResp.StatusCode == 304 { // no changes were present, and with no body content. | |
94 | return nil | |
95 | } else { | |
96 | resultBytes = respCnt | |
97 | } | |
98 | return json.Unmarshal(resultBytes, &uidMetaDataResp) | |
99 | } | |
100 | } | |
101 | ||
102 | func (uidMetaDataResp *UIDMetaDataResponse) String() string { | |
103 | buffer := bytes.NewBuffer(nil) | |
104 | content, _ := json.Marshal(uidMetaDataResp) | |
105 | buffer.WriteString(fmt.Sprintf("%s\n", string(content))) | |
106 | return buffer.String() | |
107 | } | |
108 | ||
109 | func (c *clientImpl) QueryUIDMetaData(metaQueryParam map[string]string) (*UIDMetaDataResponse, error) { | |
110 | if !isValidUIDMetaDataQueryParam(metaQueryParam) { | |
111 | return nil, errors.New("The given query uid metadata is invalid.") | |
112 | } | |
113 | queryParam := fmt.Sprintf("%s=%v&%s=%v", "uid", metaQueryParam["uid"], "type", metaQueryParam["type"]) | |
114 | queryUIDMetaEndpoint := fmt.Sprintf("%s%s?%s", c.tsdbEndpoint, UIDMetaDataPath, queryParam) | |
115 | uidMetaDataResp := UIDMetaDataResponse{} | |
116 | if err := c.sendRequest(GetMethod, queryUIDMetaEndpoint, "", &uidMetaDataResp); err != nil { | |
117 | return nil, err | |
118 | } | |
119 | return &uidMetaDataResp, nil | |
120 | } | |
121 | ||
122 | func (c *clientImpl) UpdateUIDMetaData(uidMetaData UIDMetaData) (*UIDMetaDataResponse, error) { | |
123 | return c.operateUIDMetaData(PostMethod, &uidMetaData) | |
124 | } | |
125 | ||
126 | func (c *clientImpl) DeleteUIDMetaData(uidMetaData UIDMetaData) (*UIDMetaDataResponse, error) { | |
127 | return c.operateUIDMetaData(DeleteMethod, &uidMetaData) | |
128 | } | |
129 | ||
130 | func (c *clientImpl) operateUIDMetaData(method string, uidMetaData *UIDMetaData) (*UIDMetaDataResponse, error) { | |
131 | if !c.isValidOperateMethod(method) { | |
132 | return nil, errors.New("The given method for operating a uid metadata is invalid.") | |
133 | } | |
134 | uidMetaEndpoint := fmt.Sprintf("%s%s", c.tsdbEndpoint, UIDMetaDataPath) | |
135 | resultBytes, err := json.Marshal(uidMetaData) | |
136 | if err != nil { | |
137 | return nil, errors.New(fmt.Sprintf("Failed to marshal uidMetaData: %v", err)) | |
138 | } | |
139 | uidMetaDataResp := UIDMetaDataResponse{} | |
140 | if err = c.sendRequest(method, uidMetaEndpoint, string(resultBytes), &uidMetaDataResp); err != nil { | |
141 | return nil, err | |
142 | } | |
143 | return &uidMetaDataResp, nil | |
144 | } | |
145 | ||
146 | func isValidUIDMetaDataQueryParam(metaQueryParam map[string]string) bool { | |
147 | if metaQueryParam == nil || len(metaQueryParam) != 2 { | |
148 | return false | |
149 | } | |
150 | checkKeys := []string{"uid", "type"} | |
151 | for _, checkKey := range checkKeys { | |
152 | _, exists := metaQueryParam[checkKey] | |
153 | if !exists { | |
154 | return false | |
155 | } | |
156 | } | |
157 | typeValue := metaQueryParam["type"] | |
158 | typeCheckItems := []string{TypeMetrics, TypeTagk, TypeTagv} | |
159 | for _, checkItem := range typeCheckItems { | |
160 | if typeValue == checkItem { | |
161 | return true | |
162 | } | |
163 | } | |
164 | return false | |
165 | } | |
166 | ||
167 | // UIDAssignParam is the structure used to hold | |
168 | // the parameters when calling POST /api/uid/assign. | |
169 | // Each attributes in UIDAssignParam matches the definition in | |
170 | // (http://opentsdb.net/docs/build/html/api_http/uid/assign.html). | |
171 | // | |
172 | type UIDAssignParam struct { | |
173 | // An optional list of metric names for assignment | |
174 | Metric []string `json:"metric,omitempty"` | |
175 | ||
176 | // An optional list of tag names for assignment | |
177 | Tagk []string `json:"tagk,omitempty"` | |
178 | ||
179 | // An optional list of tag values for assignment | |
180 | Tagv []string `json:"tagv,omitempty"` | |
181 | } | |
182 | ||
183 | // UIDAssignResponse acts as the implementation of Response in the POST /api/uid/assign scene. | |
184 | // It holds the status code and the response values defined in the | |
185 | // (http://opentsdb.net/docs/build/html/api_http/uid/assign.html). | |
186 | // | |
187 | type UIDAssignResponse struct { | |
188 | StatusCode int | |
189 | Metric map[string]string `json:"metric"` | |
190 | MetricErrors map[string]string `json:"metric_errors,omitempty"` | |
191 | Tagk map[string]string `json:"tagk"` | |
192 | TagkErrors map[string]string `json:"tagk_errors,omitempty"` | |
193 | Tagv map[string]string `json:"tagv"` | |
194 | TagvErrors map[string]string `json:"tagv_errors,omitempty"` | |
195 | } | |
196 | ||
197 | func (uidAssignResp *UIDAssignResponse) SetStatus(code int) { | |
198 | uidAssignResp.StatusCode = code | |
199 | } | |
200 | ||
201 | func (uidAssignResp *UIDAssignResponse) GetCustomParser() func(respCnt []byte) error { | |
202 | return nil | |
203 | } | |
204 | ||
205 | func (uidAssignResp *UIDAssignResponse) String() string { | |
206 | buffer := bytes.NewBuffer(nil) | |
207 | content, _ := json.Marshal(uidAssignResp) | |
208 | buffer.WriteString(fmt.Sprintf("%s\n", string(content))) | |
209 | return buffer.String() | |
210 | } | |
211 | ||
212 | func (c *clientImpl) AssignUID(assignParam UIDAssignParam) (*UIDAssignResponse, error) { | |
213 | assignUIDEndpoint := fmt.Sprintf("%s%s", c.tsdbEndpoint, UIDAssignPath) | |
214 | resultBytes, err := json.Marshal(assignParam) | |
215 | if err != nil { | |
216 | return nil, errors.New(fmt.Sprintf("Failed to marshal UIDAssignParam: %v", err)) | |
217 | } | |
218 | uidAssignResp := UIDAssignResponse{} | |
219 | if err = c.sendRequest(PostMethod, assignUIDEndpoint, string(resultBytes), &uidAssignResp); err != nil { | |
220 | return nil, err | |
221 | } | |
222 | return &uidAssignResp, nil | |
223 | } | |
224 | ||
225 | // TSMetaData is the structure used to hold | |
226 | // the parameters when calling (POST,PUT,DELETE) /api/uid/tsmeta. | |
227 | // Each attributes in TSMetaData matches the definition in | |
228 | // (http://opentsdb.net/docs/build/html/api_http/uid/tsmeta.html). | |
229 | // | |
230 | type TSMetaData struct { | |
231 | // A required hexadecimal representation of the timeseries UID | |
232 | Tsuid string `json:"tsuid,omitempty"` | |
233 | ||
234 | // An optional brief description of what the UID represents | |
235 | Description string `json:"description,omitempty"` | |
236 | ||
237 | // An optional short name that can be displayed in GUIs instead of the default name | |
238 | DisplayName string `json:"displayName,omitempty"` | |
239 | ||
240 | // An optional detailed notes about what the UID represents | |
241 | Notes string `json:"notes,omitempty"` | |
242 | ||
243 | // An optional key/value map to store custom fields and values | |
244 | Custom map[string]string `json:"custom,omitempty"` | |
245 | ||
246 | // An optional value reflective of the data stored in the timeseries, may be used in GUIs or calculations | |
247 | Units string `json:"units,omitempty"` | |
248 | ||
249 | // The kind of data stored in the timeseries such as counter, gauge, absolute, etc. | |
250 | // These may be defined later but they should be similar to Data Source Types in an RRD. | |
251 | // Its value is optional | |
252 | DataType string `json:"dataType,omitempty"` | |
253 | ||
254 | // The number of days of data points to retain for the given timeseries. Not Implemented. | |
255 | // When set to 0, the default, data is retained indefinitely. | |
256 | // Its value is optional | |
257 | Retention int64 `json:"retention,omitempty"` | |
258 | ||
259 | // An optional maximum value for this timeseries that may be used in calculations such as | |
260 | // percent of maximum. If the default of NaN is present, the value is ignored. | |
261 | Max float64 `json:"max,omitempty"` | |
262 | ||
263 | // An optional minimum value for this timeseries that may be used in calculations such as | |
264 | // percent of minimum. If the default of NaN is present, the value is ignored. | |
265 | Min float64 `json:"min,omitempty"` | |
266 | } | |
267 | ||
268 | type TSMetaDataResponse struct { | |
269 | StatusCode int | |
270 | TSMetaData | |
271 | Metric UIDMetaData `json:"metric,omitempty"` | |
272 | Tags []UIDMetaData `json:"tags,omitempty"` | |
273 | Created int64 `json:"created,omitempty"` | |
274 | LastReceived int64 `json:"lastReceived,omitempty"` | |
275 | TotalDatapoints int64 `json:"totalDatapoints,omitempty"` | |
276 | ErrorInfo map[string]interface{} `json:"error,omitempty"` | |
277 | } | |
278 | ||
279 | func (tsMetaDataResp *TSMetaDataResponse) SetStatus(code int) { | |
280 | tsMetaDataResp.StatusCode = code | |
281 | } | |
282 | ||
283 | func (tsMetaDataResp *TSMetaDataResponse) GetCustomParser() func(respCnt []byte) error { | |
284 | return func(respCnt []byte) error { | |
285 | var resultBytes []byte | |
286 | if tsMetaDataResp.StatusCode == 204 || // The OpenTSDB deletes a TSMetaData successfully, or | |
287 | tsMetaDataResp.StatusCode == 304 { // no changes were present, and with no body content. | |
288 | return nil | |
289 | } else { | |
290 | resultBytes = respCnt | |
291 | } | |
292 | return json.Unmarshal(resultBytes, &tsMetaDataResp) | |
293 | } | |
294 | } | |
295 | ||
296 | func (tsMetaDataResp *TSMetaDataResponse) String() string { | |
297 | buffer := bytes.NewBuffer(nil) | |
298 | content, _ := json.Marshal(tsMetaDataResp) | |
299 | buffer.WriteString(fmt.Sprintf("%s\n", string(content))) | |
300 | return buffer.String() | |
301 | } | |
302 | ||
303 | func (c *clientImpl) QueryTSMetaData(tsuid string) (*TSMetaDataResponse, error) { | |
304 | tsuid = strings.TrimSpace(tsuid) | |
305 | if len(tsuid) == 0 { | |
306 | return nil, errors.New("The given query tsuid is empty.") | |
307 | } | |
308 | queryTSMetaEndpoint := fmt.Sprintf("%s%s?tsuid=%s", c.tsdbEndpoint, TSMetaDataPath, tsuid) | |
309 | tsMetaDataResp := TSMetaDataResponse{} | |
310 | if err := c.sendRequest(GetMethod, queryTSMetaEndpoint, "", &tsMetaDataResp); err != nil { | |
311 | return nil, err | |
312 | } | |
313 | return &tsMetaDataResp, nil | |
314 | } | |
315 | ||
316 | func (c *clientImpl) UpdateTSMetaData(tsMetaData TSMetaData) (*TSMetaDataResponse, error) { | |
317 | return c.operateTSMetaData(PostMethod, &tsMetaData) | |
318 | } | |
319 | ||
320 | func (c *clientImpl) DeleteTSMetaData(tsMetaData TSMetaData) (*TSMetaDataResponse, error) { | |
321 | return c.operateTSMetaData(DeleteMethod, &tsMetaData) | |
322 | } | |
323 | ||
324 | func (c *clientImpl) operateTSMetaData(method string, tsMetaData *TSMetaData) (*TSMetaDataResponse, error) { | |
325 | if !c.isValidOperateMethod(method) { | |
326 | return nil, errors.New("The given method for operating a uid metadata is invalid.") | |
327 | } | |
328 | tsMetaEndpoint := fmt.Sprintf("%s%s", c.tsdbEndpoint, TSMetaDataPath) | |
329 | resultBytes, err := json.Marshal(tsMetaData) | |
330 | if err != nil { | |
331 | return nil, errors.New(fmt.Sprintf("Failed to marshal uidMetaData: %v", err)) | |
332 | } | |
333 | tsMetaDataResp := TSMetaDataResponse{} | |
334 | if err = c.sendRequest(method, tsMetaEndpoint, string(resultBytes), &tsMetaDataResp); err != nil { | |
335 | return nil, err | |
336 | } | |
337 | return &tsMetaDataResp, nil | |
338 | } |
0 | // Copyright 2015 opentsdb-goclient authors. All Rights Reserved. | |
1 | // | |
2 | // Licensed under the Apache License, Version 2.0 (the "License"); | |
3 | // you may not use this file except in compliance with the License. | |
4 | // You may obtain a copy of the License at | |
5 | // | |
6 | // http://www.apache.org/licenses/LICENSE-2.0 | |
7 | // | |
8 | // Unless required by applicable law or agreed to in writing, software | |
9 | // distributed under the License is distributed on an "AS IS" BASIS, | |
10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
11 | // See the License for the specific language governing permissions and | |
12 | // limitations under the License. | |
13 | // | |
14 | // | |
15 | // Package client defines the client and the corresponding | |
16 | // rest api implementaion of OpenTSDB. | |
17 | // | |
18 | // version.go contains the structs and methods for the implementation of /api/version. | |
19 | // | |
20 | package client | |
21 | ||
22 | import ( | |
23 | "bytes" | |
24 | "encoding/json" | |
25 | "fmt" | |
26 | ) | |
27 | ||
28 | type VersionResponse struct { | |
29 | StatusCode int | |
30 | VersionInfo map[string]string `json:"VersionInfo"` | |
31 | } | |
32 | ||
33 | func (verResp *VersionResponse) SetStatus(code int) { | |
34 | verResp.StatusCode = code | |
35 | } | |
36 | ||
37 | func (verResp *VersionResponse) GetCustomParser() func(respCnt []byte) error { | |
38 | return func(respCnt []byte) error { | |
39 | return json.Unmarshal([]byte(fmt.Sprintf("{%s:%s}", `"VersionInfo"`, string(respCnt))), &verResp) | |
40 | } | |
41 | } | |
42 | ||
43 | func (verResp *VersionResponse) String() string { | |
44 | buffer := bytes.NewBuffer(nil) | |
45 | content, _ := json.Marshal(verResp) | |
46 | buffer.WriteString(fmt.Sprintf("%s\n", string(content))) | |
47 | return buffer.String() | |
48 | } | |
49 | ||
50 | func (c *clientImpl) Version() (*VersionResponse, error) { | |
51 | verEndpoint := fmt.Sprintf("%s%s", c.tsdbEndpoint, VersionPath) | |
52 | verResp := VersionResponse{} | |
53 | if err := c.sendRequest(GetMethod, verEndpoint, "", &verResp); err != nil { | |
54 | return nil, err | |
55 | } | |
56 | return &verResp, nil | |
57 | } |
0 | // Copyright 2015 opentsdb-goclient authors. All Rights Reserved. | |
1 | // | |
2 | // Licensed under the Apache License, Version 2.0 (the "License"); | |
3 | // you may not use this file except in compliance with the License. | |
4 | // You may obtain a copy of the License at | |
5 | // | |
6 | // http://www.apache.org/licenses/LICENSE-2.0 | |
7 | // | |
8 | // Unless required by applicable law or agreed to in writing, software | |
9 | // distributed under the License is distributed on an "AS IS" BASIS, | |
10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
11 | // See the License for the specific language governing permissions and | |
12 | // limitations under the License. | |
13 | // | |
14 | // | |
15 | // Package config defines basic structure to hold the configure items used | |
16 | // to initialize the opentsdb client. | |
17 | // | |
18 | package config | |
19 | ||
20 | import ( | |
21 | "net/http" | |
22 | ) | |
23 | ||
24 | type OpenTSDBConfig struct { | |
25 | ||
26 | // The host of the target opentsdb, is a required non-empty string which is | |
27 | // in the format of ip:port without http:// prefix or a domain. | |
28 | OpentsdbHost string | |
29 | ||
30 | // A pointer of http.Tranport is used by the opentsdb client. | |
31 | // This value is optional, and if it is not set, client.DefaultTransport, which | |
32 | // enables tcp keepalive mode, will be used in the opentsdb client. | |
33 | Transport *http.Transport | |
34 | ||
35 | // The maximal number of datapoints which will be inserted into the opentsdb | |
36 | // via one calling of /api/put method. | |
37 | // This value is optional, and if it is not set, client.DefaultMaxPutPointsNum | |
38 | // will be used in the opentsdb client. | |
39 | MaxPutPointsNum int | |
40 | ||
41 | // The detect delta number of datapoints which will be used in client.Put() | |
42 | // to split a large group of datapoints into small batches. | |
43 | // This value is optional, and if it is not set, client.DefaultDetectDeltaNum | |
44 | // will be used in the opentsdb client. | |
45 | DetectDeltaNum int | |
46 | ||
47 | // The maximal body content length per /api/put method to insert datapoints | |
48 | // into opentsdb. | |
49 | // This value is optional, and if it is not set, client.DefaultMaxPutPointsNum | |
50 | // will be used in the opentsdb client. | |
51 | MaxContentLength int | |
52 | } |
0 | // Copyright 2015 opentsdb-goclient authors. All Rights Reserved. | |
1 | // | |
2 | // Licensed under the Apache License, Version 2.0 (the "License"); | |
3 | // you may not use this file except in compliance with the License. | |
4 | // You may obtain a copy of the License at | |
5 | // | |
6 | // http://www.apache.org/licenses/LICENSE-2.0 | |
7 | // | |
8 | // Unless required by applicable law or agreed to in writing, software | |
9 | // distributed under the License is distributed on an "AS IS" BASIS, | |
10 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
11 | // See the License for the specific language governing permissions and | |
12 | // limitations under the License. | |
13 | // | |
14 | // | |
15 | // Package main shows the sample of how to use github.com/bluebreezecf/opentsdbclient/client | |
16 | // to communicate with the OpenTSDB with the pre-define rest apis. | |
17 | // (http://opentsdb.net/docs/build/html/api_http/index.html#api-endpoints) | |
18 | // | |
19 | package main | |
20 | ||
21 | import ( | |
22 | "fmt" | |
23 | "math/rand" | |
24 | "time" | |
25 | ||
26 | "github.com/bluebreezecf/opentsdb-goclient/client" | |
27 | "github.com/bluebreezecf/opentsdb-goclient/config" | |
28 | ) | |
29 | ||
30 | func main() { | |
31 | opentsdbCfg := config.OpenTSDBConfig{ | |
32 | OpentsdbHost: "127.0.0.1:4242", | |
33 | } | |
34 | tsdbClient, err := client.NewClient(opentsdbCfg) | |
35 | if err != nil { | |
36 | fmt.Printf("%v\n", err) | |
37 | return | |
38 | } | |
39 | ||
40 | //0. Ping | |
41 | if err = tsdbClient.Ping(); err != nil { | |
42 | fmt.Println(err.Error()) | |
43 | return | |
44 | } | |
45 | PutDataPointNum := 4 | |
46 | name := []string{"cpu", "disk", "net", "mem", "bytes"} | |
47 | //1. POST /api/put | |
48 | fmt.Println("Begin to test POST /api/put.") | |
49 | cpuDatas := make([]client.DataPoint, 0) | |
50 | st1 := time.Now().Unix() | |
51 | time.Sleep(2 * time.Second) | |
52 | tags := make(map[string]string) | |
53 | tags["host"] = "bluebreezecf-host" | |
54 | tags["try-name"] = "bluebreezecf-sample" | |
55 | tags["demo-name"] = "opentsdb-test" | |
56 | i := 0 | |
57 | for { | |
58 | time.Sleep(500 * time.Millisecond) | |
59 | data := client.DataPoint{ | |
60 | Metric: name[i], | |
61 | Timestamp: time.Now().Unix(), | |
62 | Value: rand.Float64(), | |
63 | } | |
64 | data.Tags = tags | |
65 | cpuDatas = append(cpuDatas, data) | |
66 | fmt.Printf(" %d.Prepare datapoint %s\n", i, data.String()) | |
67 | if i < PutDataPointNum { | |
68 | i++ | |
69 | } else { | |
70 | break | |
71 | } | |
72 | } | |
73 | ||
74 | if resp, err := tsdbClient.Put(cpuDatas, "details"); err != nil { | |
75 | fmt.Printf(" Error occurs when putting datapoints: %v", err) | |
76 | } else { | |
77 | fmt.Printf(" %s", resp.String()) | |
78 | } | |
79 | fmt.Println("Finish testing POST /api/put.") | |
80 | ||
81 | //2.1 POST /api/query to query | |
82 | fmt.Println("Begin to test POST /api/query.") | |
83 | time.Sleep(2 * time.Second) | |
84 | st2 := time.Now().Unix() | |
85 | queryParam := client.QueryParam{ | |
86 | Start: st1, | |
87 | End: st2, | |
88 | } | |
89 | subqueries := make([]client.SubQuery, 0) | |
90 | for _, metric := range name { | |
91 | subQuery := client.SubQuery{ | |
92 | Aggregator: "sum", | |
93 | Metric: metric, | |
94 | Tags: tags, | |
95 | } | |
96 | subqueries = append(subqueries, subQuery) | |
97 | } | |
98 | queryParam.Queries = subqueries | |
99 | if queryResp, err := tsdbClient.Query(queryParam); err != nil { | |
100 | fmt.Printf("Error occurs when querying: %v", err) | |
101 | } else { | |
102 | fmt.Printf("%s", queryResp.String()) | |
103 | } | |
104 | fmt.Println("Finish testing POST /api/query.") | |
105 | ||
106 | //2.2 POST /api/query/last | |
107 | fmt.Println("Begin to test POST /api/query/last.") | |
108 | time.Sleep(1 * time.Second) | |
109 | subqueriesLast := make([]client.SubQueryLast, 0) | |
110 | for _, metric := range name { | |
111 | subQueryLast := client.SubQueryLast{ | |
112 | Metric: metric, | |
113 | Tags: tags, | |
114 | } | |
115 | subqueriesLast = append(subqueriesLast, subQueryLast) | |
116 | } | |
117 | queryLastParam := client.QueryLastParam{ | |
118 | Queries: subqueriesLast, | |
119 | ResolveNames: true, | |
120 | BackScan: 24, | |
121 | } | |
122 | if queryLastResp, err := tsdbClient.QueryLast(queryLastParam); err != nil { | |
123 | fmt.Printf("Error occurs when querying last: %v", err) | |
124 | } else { | |
125 | fmt.Printf("%s", queryLastResp.String()) | |
126 | } | |
127 | fmt.Println("Finish testing POST /api/query/last.") | |
128 | ||
129 | //2.3 POST /api/query to delete | |
130 | fmt.Println("Begin to test POST /api/query to delete.") | |
131 | queryParam.Delete = true | |
132 | if queryResp, err := tsdbClient.Query(queryParam); err != nil { | |
133 | fmt.Printf("Error occurs when deleting: %v", err) | |
134 | } else { | |
135 | fmt.Printf("%s", queryResp.String()) | |
136 | } | |
137 | ||
138 | time.Sleep(5 * time.Second) | |
139 | fmt.Println("Query again which shoud return null.") | |
140 | queryParam.Delete = false | |
141 | if queryResp, err := tsdbClient.Query(queryParam); err != nil { | |
142 | fmt.Printf("Error occurs when quering: %v", err) | |
143 | } else { | |
144 | fmt.Printf("%s", queryResp.String()) | |
145 | } | |
146 | fmt.Println("Finish testing POST /api/query to delete.") | |
147 | ||
148 | //3. GET /api/aggregators | |
149 | fmt.Println("Begin to test GET /api/aggregators.") | |
150 | aggreResp, err := tsdbClient.Aggregators() | |
151 | if err != nil { | |
152 | fmt.Printf("Error occurs when acquiring aggregators: %v", err) | |
153 | } else { | |
154 | fmt.Printf("%s", aggreResp.String()) | |
155 | } | |
156 | fmt.Println("Finish testing GET /api/aggregators.") | |
157 | ||
158 | //4. GET /api/config | |
159 | fmt.Println("Begin to test GET /api/config.") | |
160 | configResp, err := tsdbClient.Config() | |
161 | if err != nil { | |
162 | fmt.Printf("Error occurs when acquiring config info: %v", err) | |
163 | } else { | |
164 | fmt.Printf("%s", configResp.String()) | |
165 | } | |
166 | fmt.Println("Finish testing GET /api/config.") | |
167 | ||
168 | //5. Get /api/serializers | |
169 | fmt.Println("Begin to test GET /api/serializers.") | |
170 | serilResp, err := tsdbClient.Serializers() | |
171 | if err != nil { | |
172 | fmt.Printf("Error occurs when acquiring serializers info: %v", err) | |
173 | } else { | |
174 | fmt.Printf("%s", serilResp.String()) | |
175 | } | |
176 | fmt.Println("Finish testing GET /api/serializers.") | |
177 | ||
178 | //6. Get /api/stats | |
179 | fmt.Println("Begin to test GET /api/stats.") | |
180 | statsResp, err := tsdbClient.Stats() | |
181 | if err != nil { | |
182 | fmt.Printf("Error occurs when acquiring stats info: %v", err) | |
183 | } else { | |
184 | fmt.Printf("%s", statsResp.String()) | |
185 | } | |
186 | fmt.Println("Finish testing GET /api/stats.") | |
187 | ||
188 | //7. Get /api/suggest | |
189 | fmt.Println("Begin to test GET /api/suggest.") | |
190 | typeValues := []string{client.TypeMetrics, client.TypeTagk, client.TypeTagv} | |
191 | for _, typeItem := range typeValues { | |
192 | sugParam := client.SuggestParam{ | |
193 | Type: typeItem, | |
194 | } | |
195 | fmt.Printf(" Send suggest param: %s", sugParam.String()) | |
196 | sugResp, err := tsdbClient.Suggest(sugParam) | |
197 | if err != nil { | |
198 | fmt.Printf(" Error occurs when acquiring suggest info: %v\n", err) | |
199 | } else { | |
200 | fmt.Printf(" Recevie response: %s\n", sugResp.String()) | |
201 | } | |
202 | } | |
203 | fmt.Println("Finish testing GET /api/suggest.") | |
204 | ||
205 | //8. Get /api/version | |
206 | fmt.Println("Begin to test GET /api/version.") | |
207 | versionResp, err := tsdbClient.Version() | |
208 | if err != nil { | |
209 | fmt.Printf("Error occurs when acquiring version info: %v", err) | |
210 | } else { | |
211 | fmt.Printf("%s", versionResp.String()) | |
212 | } | |
213 | fmt.Println("Finish testing GET /api/version.") | |
214 | ||
215 | //9. Get /api/dropcaches | |
216 | fmt.Println("Begin to test GET /api/dropcaches.") | |
217 | dropResp, err := tsdbClient.Dropcaches() | |
218 | if err != nil { | |
219 | fmt.Printf("Error occurs when acquiring dropcaches info: %v", err) | |
220 | } else { | |
221 | fmt.Printf("%s", dropResp.String()) | |
222 | } | |
223 | fmt.Println("Finish testing GET /api/dropcaches.") | |
224 | ||
225 | //10. POST /api/annotation | |
226 | fmt.Println("Begin to test POST /api/annotation.") | |
227 | custom := make(map[string]string, 0) | |
228 | custom["owner"] = "bluebreezecf" | |
229 | custom["host"] = "bluebreezecf-host" | |
230 | addedST := time.Now().Unix() | |
231 | addedTsuid := "000001000001000002" | |
232 | anno := client.Annotation{ | |
233 | StartTime: addedST, | |
234 | Tsuid: addedTsuid, | |
235 | Description: "bluebreezecf test annotation", | |
236 | Notes: "These would be details about the event, the description is just a summary", | |
237 | Custom: custom, | |
238 | } | |
239 | if queryAnnoResp, err := tsdbClient.UpdateAnnotation(anno); err != nil { | |
240 | fmt.Printf("Error occurs when posting annotation info: %v", err) | |
241 | } else { | |
242 | fmt.Printf("%s", queryAnnoResp.String()) | |
243 | } | |
244 | fmt.Println("Finish testing POST /api/annotation.") | |
245 | ||
246 | //11. GET /api/annotation | |
247 | fmt.Println("Begin to test GET /api/annotation.") | |
248 | queryAnnoMap := make(map[string]interface{}, 0) | |
249 | queryAnnoMap[client.AnQueryStartTime] = addedST | |
250 | queryAnnoMap[client.AnQueryTSUid] = addedTsuid | |
251 | if queryAnnoResp, err := tsdbClient.QueryAnnotation(queryAnnoMap); err != nil { | |
252 | fmt.Printf("Error occurs when acquiring annotation info: %v", err) | |
253 | } else { | |
254 | fmt.Printf("%s", queryAnnoResp.String()) | |
255 | } | |
256 | fmt.Println("Finish testing GET /api/annotation.") | |
257 | ||
258 | //12. GET /api/annotation | |
259 | fmt.Println("Begin to test DELETE /api/annotation.") | |
260 | if queryAnnoResp, err := tsdbClient.DeleteAnnotation(anno); err != nil { | |
261 | fmt.Printf("Error occurs when deleting annotation info: %v", err) | |
262 | } else { | |
263 | fmt.Printf("%s", queryAnnoResp.String()) | |
264 | } | |
265 | fmt.Println("Finish testing DELETE /api/annotation.") | |
266 | ||
267 | //13. POST /api/annotation/bulk | |
268 | fmt.Println("Begin to test POST /api/annotation/bulk.") | |
269 | anns := make([]client.Annotation, 0) | |
270 | bulkAnnNum := 4 | |
271 | i = 0 | |
272 | bulkAddBeginST := time.Now().Unix() | |
273 | addedTsuids := make([]string, bulkAnnNum) | |
274 | for { | |
275 | if i < bulkAnnNum-1 { | |
276 | addedST := time.Now().Unix() | |
277 | addedTsuid := fmt.Sprintf("%s%d", "00000100000100000", i) | |
278 | addedTsuids = append(addedTsuids, addedTsuid) | |
279 | anno := client.Annotation{ | |
280 | StartTime: addedST, | |
281 | Tsuid: addedTsuid, | |
282 | Description: "bluebreezecf test annotation", | |
283 | Notes: "These would be details about the event, the description is just a summary", | |
284 | } | |
285 | anns = append(anns, anno) | |
286 | i++ | |
287 | } else { | |
288 | break | |
289 | } | |
290 | } | |
291 | if bulkAnnoResp, err := tsdbClient.BulkUpdateAnnotations(anns); err != nil { | |
292 | fmt.Printf("Error occurs when posting bulk annotation info: %v", err) | |
293 | } else { | |
294 | fmt.Printf("%s", bulkAnnoResp.String()) | |
295 | } | |
296 | fmt.Println("Finish testing POST /api/annotation/bulk.") | |
297 | ||
298 | //14. DELETE /api/annotation/bulk | |
299 | fmt.Println("Begin to test DELETE /api/annotation/bulk.") | |
300 | bulkAnnoDelete := client.BulkAnnoDeleteInfo{ | |
301 | StartTime: bulkAddBeginST, | |
302 | Tsuids: addedTsuids, | |
303 | Global: false, | |
304 | } | |
305 | if bulkAnnoResp, err := tsdbClient.BulkDeleteAnnotations(bulkAnnoDelete); err != nil { | |
306 | fmt.Printf("Error occurs when deleting bulk annotation info: %v", err) | |
307 | } else { | |
308 | fmt.Printf("%s", bulkAnnoResp.String()) | |
309 | } | |
310 | fmt.Println("Finish testing DELETE /api/annotation/bulk.") | |
311 | ||
312 | //15. GET /api/uid/uidmeta | |
313 | fmt.Println("Begin to test GET /api/uid/uidmeta.") | |
314 | metaQueryParam := make(map[string]string, 0) | |
315 | metaQueryParam["type"] = client.TypeMetrics | |
316 | metaQueryParam["uid"] = "00003A" | |
317 | if resp, err := tsdbClient.QueryUIDMetaData(metaQueryParam); err != nil { | |
318 | fmt.Printf("Error occurs when querying uidmetadata info: %v", err) | |
319 | } else { | |
320 | fmt.Printf("%s", resp.String()) | |
321 | } | |
322 | ||
323 | fmt.Println("Finish testing GET /api/uid/uidmeta.") | |
324 | ||
325 | //16. POST /api/uid/uidmeta | |
326 | fmt.Println("Begin to test POST /api/uid/uidmeta.") | |
327 | uidMetaData := client.UIDMetaData{ | |
328 | Uid: "00002A", | |
329 | Type: "metric", | |
330 | DisplayName: "System CPU Time", | |
331 | } | |
332 | if resp, err := tsdbClient.UpdateUIDMetaData(uidMetaData); err != nil { | |
333 | fmt.Printf("Error occurs when posting uidmetadata info: %v", err) | |
334 | } else { | |
335 | fmt.Printf("%s", resp.String()) | |
336 | } | |
337 | fmt.Println("Finish testing POST /api/uid/uidmeta.") | |
338 | ||
339 | //17. DELETE /api/uid/uidmeta | |
340 | fmt.Println("Begin to test DELETE /api/uid/uidmeta.") | |
341 | uidMetaData = client.UIDMetaData{ | |
342 | Uid: "00003A", | |
343 | Type: "metric", | |
344 | } | |
345 | if resp, err := tsdbClient.DeleteUIDMetaData(uidMetaData); err != nil { | |
346 | fmt.Printf("Error occurs when deleting uidmetadata info: %v", err) | |
347 | } else { | |
348 | fmt.Printf("%s", resp.String()) | |
349 | } | |
350 | fmt.Println("Finish testing DELETE /api/uid/uidmeta.") | |
351 | ||
352 | //18. POST /api/uid/assign | |
353 | fmt.Println("Begin to test POST /api/uid/assign.") | |
354 | metrics := []string{"sys.cpu.0", "sys.cpu.1", "illegal!character"} | |
355 | tagk := []string{"host"} | |
356 | tagv := []string{"web01", "web02", "web03"} | |
357 | assignParam := client.UIDAssignParam{ | |
358 | Metric: metrics, | |
359 | Tagk: tagk, | |
360 | Tagv: tagv, | |
361 | } | |
362 | if resp, err := tsdbClient.AssignUID(assignParam); err != nil { | |
363 | fmt.Printf("Error occurs when assgining uid info: %v", err) | |
364 | } else { | |
365 | fmt.Printf("%s", resp.String()) | |
366 | } | |
367 | fmt.Println("Finish testing POST /api/uid/assign.") | |
368 | ||
369 | //19. GET /api/uid/tsmeta | |
370 | fmt.Println("Begin to test GET /api/uid/tsmeta.") | |
371 | if resp, err := tsdbClient.QueryTSMetaData("000001000001000001"); err != nil { | |
372 | fmt.Printf("Error occurs when querying tsmetadata info: %v", err) | |
373 | } else { | |
374 | fmt.Printf("%s", resp.String()) | |
375 | } | |
376 | fmt.Println("Finish testing GET /api/uid/tsmeta.") | |
377 | ||
378 | //20. POST /api/uid/tsmeta | |
379 | fmt.Println("Begin to test POST /api/uid/tsmeta.") | |
380 | custom = make(map[string]string, 0) | |
381 | custom["owner"] = "bluebreezecf" | |
382 | custom["department"] = "paas dep" | |
383 | tsMetaData := client.TSMetaData{ | |
384 | Tsuid: "000001000001000001", | |
385 | DisplayName: "System CPU Time for Webserver 01", | |
386 | Custom: custom, | |
387 | } | |
388 | if resp, err := tsdbClient.UpdateTSMetaData(tsMetaData); err != nil { | |
389 | fmt.Printf("Error occurs when posting tsmetadata info: %v", err) | |
390 | } else { | |
391 | fmt.Printf("%s", resp.String()) | |
392 | } | |
393 | fmt.Println("Finish testing POST /api/uid/tsmeta.") | |
394 | ||
395 | //21. DELETE /api/uid/tsmeta | |
396 | fmt.Println("Begin to test DELETE /api/uid/tsmeta.") | |
397 | tsMetaData = client.TSMetaData{ | |
398 | Tsuid: "000001000001000001", | |
399 | } | |
400 | if resp, err := tsdbClient.DeleteTSMetaData(tsMetaData); err != nil { | |
401 | fmt.Printf("Error occurs when deleting tsmetadata info: %v", err) | |
402 | } else { | |
403 | fmt.Printf("%s", resp.String()) | |
404 | } | |
405 | fmt.Println("Finish testing DELETE /api/uid/tsmeta.") | |
406 | } |