2015-01-22 22:23:55 +00:00
|
|
|
package client
|
|
|
|
|
2015-01-22 23:40:32 +00:00
|
|
|
import (
|
2015-01-23 20:37:53 +00:00
|
|
|
"bytes"
|
2015-01-23 00:18:24 +00:00
|
|
|
"encoding/json"
|
2015-01-29 21:07:43 +00:00
|
|
|
"errors"
|
2015-01-29 18:10:13 +00:00
|
|
|
"fmt"
|
2015-03-16 22:31:41 +00:00
|
|
|
"io"
|
2015-01-22 23:40:32 +00:00
|
|
|
"net/http"
|
|
|
|
"net/url"
|
|
|
|
"time"
|
|
|
|
|
2015-01-29 21:07:43 +00:00
|
|
|
"github.com/influxdb/influxdb/influxql"
|
2015-01-22 23:40:32 +00:00
|
|
|
)
|
|
|
|
|
2015-03-07 14:36:13 +00:00
|
|
|
// Query is used to send a command to the server. Both Command and Database are required.
|
2015-03-06 17:29:32 +00:00
|
|
|
type Query struct {
|
|
|
|
Command string
|
|
|
|
Database string
|
|
|
|
}
|
|
|
|
|
2015-03-07 14:36:13 +00:00
|
|
|
// Config is used to specify what server to connect to.
|
|
|
|
// URL: The URL of the server connecting to.
|
|
|
|
// Username/Password are optional. They will be passed via basic auth if provided.
|
2015-03-09 15:51:47 +00:00
|
|
|
// UserAgent: If not provided, will default "InfluxDBClient",
|
2015-01-22 22:23:55 +00:00
|
|
|
type Config struct {
|
2015-02-11 20:31:15 +00:00
|
|
|
URL url.URL
|
|
|
|
Username string
|
|
|
|
Password string
|
|
|
|
UserAgent string
|
2015-01-22 22:23:55 +00:00
|
|
|
}
|
|
|
|
|
2015-03-07 14:36:13 +00:00
|
|
|
// Client is used to make calls to the server.
|
2015-01-22 22:23:55 +00:00
|
|
|
type Client struct {
|
2015-01-23 22:49:23 +00:00
|
|
|
url url.URL
|
2015-01-23 18:05:04 +00:00
|
|
|
username string
|
|
|
|
password string
|
2015-01-22 23:40:32 +00:00
|
|
|
httpClient *http.Client
|
2015-02-11 20:31:15 +00:00
|
|
|
userAgent string
|
2015-01-22 22:23:55 +00:00
|
|
|
}
|
|
|
|
|
2015-03-07 14:36:13 +00:00
|
|
|
// NewClient will instantiate and return a connected client to issue commands to the server.
|
2015-01-22 22:23:55 +00:00
|
|
|
func NewClient(c Config) (*Client, error) {
|
2015-01-22 23:40:32 +00:00
|
|
|
client := Client{
|
2015-01-23 22:49:23 +00:00
|
|
|
url: c.URL,
|
2015-01-23 18:05:04 +00:00
|
|
|
username: c.Username,
|
|
|
|
password: c.Password,
|
2015-01-22 23:40:32 +00:00
|
|
|
httpClient: &http.Client{},
|
2015-02-11 20:31:15 +00:00
|
|
|
userAgent: c.UserAgent,
|
2015-01-22 23:40:32 +00:00
|
|
|
}
|
2015-03-07 14:31:23 +00:00
|
|
|
if client.userAgent == "" {
|
2015-03-09 15:51:47 +00:00
|
|
|
client.userAgent = "InfluxDBClient"
|
2015-03-07 14:31:23 +00:00
|
|
|
}
|
2015-01-22 23:40:32 +00:00
|
|
|
return &client, nil
|
2015-01-22 22:23:55 +00:00
|
|
|
}
|
|
|
|
|
2015-03-31 16:26:00 +00:00
|
|
|
// Query sends a command to the server and returns the Response
|
|
|
|
func (c *Client) Query(q Query) (*Response, error) {
|
2015-01-26 21:12:58 +00:00
|
|
|
u := c.url
|
|
|
|
|
|
|
|
u.Path = "query"
|
2015-03-07 14:27:35 +00:00
|
|
|
if c.username != "" {
|
|
|
|
u.User = url.UserPassword(c.username, c.password)
|
|
|
|
}
|
2015-01-26 21:12:58 +00:00
|
|
|
values := u.Query()
|
2015-01-23 00:18:24 +00:00
|
|
|
values.Set("q", q.Command)
|
|
|
|
values.Set("db", q.Database)
|
2015-01-26 21:12:58 +00:00
|
|
|
u.RawQuery = values.Encode()
|
2015-01-23 00:18:24 +00:00
|
|
|
|
2015-02-11 20:31:15 +00:00
|
|
|
req, err := http.NewRequest("GET", u.String(), nil)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2015-03-07 14:31:23 +00:00
|
|
|
req.Header.Set("User-Agent", c.userAgent)
|
2015-02-11 20:31:15 +00:00
|
|
|
resp, err := c.httpClient.Do(req)
|
2015-01-23 00:18:24 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
defer resp.Body.Close()
|
|
|
|
|
2015-03-31 16:26:00 +00:00
|
|
|
var response Response
|
2015-02-03 00:20:34 +00:00
|
|
|
dec := json.NewDecoder(resp.Body)
|
|
|
|
dec.UseNumber()
|
2015-03-31 16:26:00 +00:00
|
|
|
err = dec.Decode(&response)
|
2015-01-23 00:18:24 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2015-03-31 16:26:00 +00:00
|
|
|
return &response, nil
|
2015-01-22 22:23:55 +00:00
|
|
|
}
|
|
|
|
|
2015-03-07 14:36:13 +00:00
|
|
|
// Write takes BatchPoints and allows for writing of multiple points with defaults
|
2015-03-31 16:26:00 +00:00
|
|
|
// If successful, error is nil and Response is nil
|
|
|
|
// If an error occurs, Response may contain additional information if populated.
|
|
|
|
func (c *Client) Write(bp BatchPoints) (*Response, error) {
|
2015-01-23 22:49:23 +00:00
|
|
|
c.url.Path = "write"
|
2015-01-23 20:37:53 +00:00
|
|
|
|
2015-03-06 19:44:17 +00:00
|
|
|
b, err := json.Marshal(&bp)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2015-01-23 20:37:53 +00:00
|
|
|
|
2015-02-11 20:31:15 +00:00
|
|
|
req, err := http.NewRequest("POST", c.url.String(), bytes.NewBuffer(b))
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
req.Header.Set("Content-Type", "application/json")
|
2015-03-07 14:31:23 +00:00
|
|
|
req.Header.Set("User-Agent", c.userAgent)
|
2015-02-11 20:31:15 +00:00
|
|
|
resp, err := c.httpClient.Do(req)
|
2015-01-23 20:37:53 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
defer resp.Body.Close()
|
|
|
|
|
2015-03-31 16:26:00 +00:00
|
|
|
var response Response
|
2015-03-07 02:34:31 +00:00
|
|
|
dec := json.NewDecoder(resp.Body)
|
|
|
|
dec.UseNumber()
|
2015-03-31 16:26:00 +00:00
|
|
|
err = dec.Decode(&response)
|
2015-03-07 02:34:31 +00:00
|
|
|
if err != nil && err.Error() != "EOF" {
|
|
|
|
return nil, err
|
2015-03-06 23:21:27 +00:00
|
|
|
}
|
2015-02-03 00:20:34 +00:00
|
|
|
|
2015-03-06 23:21:27 +00:00
|
|
|
if resp.StatusCode != http.StatusOK {
|
2015-03-31 16:26:00 +00:00
|
|
|
return &response, response.Error()
|
2015-01-23 20:37:53 +00:00
|
|
|
}
|
2015-03-06 23:21:27 +00:00
|
|
|
|
2015-03-07 14:36:13 +00:00
|
|
|
return nil, nil
|
2015-01-22 22:23:55 +00:00
|
|
|
}
|
2015-01-22 23:40:32 +00:00
|
|
|
|
2015-03-07 14:43:22 +00:00
|
|
|
// Ping will check to see if the server is up
|
|
|
|
// Ping returns how long the requeset took, the version of the server it connected to, and an error if one occured.
|
2015-01-26 21:12:58 +00:00
|
|
|
func (c *Client) Ping() (time.Duration, string, error) {
|
2015-01-22 23:40:32 +00:00
|
|
|
now := time.Now()
|
2015-01-26 21:12:58 +00:00
|
|
|
u := c.url
|
|
|
|
u.Path = "ping"
|
2015-02-11 20:31:15 +00:00
|
|
|
|
|
|
|
req, err := http.NewRequest("GET", u.String(), nil)
|
|
|
|
if err != nil {
|
|
|
|
return 0, "", err
|
|
|
|
}
|
2015-03-07 14:31:23 +00:00
|
|
|
req.Header.Set("User-Agent", c.userAgent)
|
2015-02-11 20:31:15 +00:00
|
|
|
resp, err := c.httpClient.Do(req)
|
2015-01-22 23:40:32 +00:00
|
|
|
if err != nil {
|
2015-01-26 21:12:58 +00:00
|
|
|
return 0, "", err
|
2015-01-22 23:40:32 +00:00
|
|
|
}
|
2015-01-26 21:12:58 +00:00
|
|
|
version := resp.Header.Get("X-Influxdb-Version")
|
|
|
|
return time.Since(now), version, nil
|
2015-01-22 23:40:32 +00:00
|
|
|
}
|
|
|
|
|
2015-03-22 05:33:04 +00:00
|
|
|
// Dump connects to server and retrieves all data stored for specified database.
|
|
|
|
// If successful, Dump returns the entire response body, which is an io.ReadCloser
|
2015-03-16 22:31:41 +00:00
|
|
|
func (c *Client) Dump(db string) (io.ReadCloser, error) {
|
2015-03-13 22:37:23 +00:00
|
|
|
u := c.url
|
|
|
|
u.Path = "dump"
|
|
|
|
values := u.Query()
|
|
|
|
values.Set("db", db)
|
|
|
|
values.Set("user", c.username)
|
|
|
|
values.Set("password", c.password)
|
|
|
|
u.RawQuery = values.Encode()
|
|
|
|
|
|
|
|
req, err := http.NewRequest("GET", u.String(), nil)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
req.Header.Set("User-Agent", c.userAgent)
|
|
|
|
resp, err := c.httpClient.Do(req)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2015-03-18 21:10:36 +00:00
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
|
|
return resp.Body, fmt.Errorf("HTTP Protocol error %d", resp.StatusCode)
|
|
|
|
}
|
2015-03-16 22:31:41 +00:00
|
|
|
return resp.Body, nil
|
2015-03-13 22:37:23 +00:00
|
|
|
}
|
|
|
|
|
2015-01-29 21:07:43 +00:00
|
|
|
// Structs
|
|
|
|
|
|
|
|
// Result represents a resultset returned from a single statement.
|
|
|
|
type Result struct {
|
2015-02-23 05:21:49 +00:00
|
|
|
Series []influxql.Row
|
|
|
|
Err error
|
2015-01-29 21:07:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// MarshalJSON encodes the result into JSON.
|
|
|
|
func (r *Result) MarshalJSON() ([]byte, error) {
|
|
|
|
// Define a struct that outputs "error" as a string.
|
|
|
|
var o struct {
|
2015-02-23 05:21:49 +00:00
|
|
|
Series []influxql.Row `json:"series,omitempty"`
|
|
|
|
Err string `json:"error,omitempty"`
|
2015-01-29 21:07:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Copy fields to output struct.
|
2015-02-23 05:21:49 +00:00
|
|
|
o.Series = r.Series
|
2015-01-29 21:07:43 +00:00
|
|
|
if r.Err != nil {
|
|
|
|
o.Err = r.Err.Error()
|
|
|
|
}
|
|
|
|
|
|
|
|
return json.Marshal(&o)
|
|
|
|
}
|
|
|
|
|
|
|
|
// UnmarshalJSON decodes the data into the Result struct
|
|
|
|
func (r *Result) UnmarshalJSON(b []byte) error {
|
|
|
|
var o struct {
|
2015-02-23 05:21:49 +00:00
|
|
|
Series []influxql.Row `json:"series,omitempty"`
|
|
|
|
Err string `json:"error,omitempty"`
|
2015-01-29 21:07:43 +00:00
|
|
|
}
|
|
|
|
|
2015-02-03 00:20:34 +00:00
|
|
|
dec := json.NewDecoder(bytes.NewBuffer(b))
|
|
|
|
dec.UseNumber()
|
|
|
|
err := dec.Decode(&o)
|
2015-01-29 21:07:43 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2015-02-23 05:21:49 +00:00
|
|
|
r.Series = o.Series
|
2015-01-29 21:07:43 +00:00
|
|
|
if o.Err != "" {
|
|
|
|
r.Err = errors.New(o.Err)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2015-03-31 16:26:00 +00:00
|
|
|
// Response represents a list of statement results.
|
|
|
|
type Response struct {
|
2015-02-03 00:20:34 +00:00
|
|
|
Results []Result
|
2015-01-29 21:07:43 +00:00
|
|
|
Err error
|
|
|
|
}
|
|
|
|
|
2015-03-31 16:26:00 +00:00
|
|
|
// MarshalJSON encodes the response into JSON.
|
|
|
|
func (r *Response) MarshalJSON() ([]byte, error) {
|
2015-01-29 21:07:43 +00:00
|
|
|
// Define a struct that outputs "error" as a string.
|
|
|
|
var o struct {
|
2015-02-03 00:20:34 +00:00
|
|
|
Results []Result `json:"results,omitempty"`
|
|
|
|
Err string `json:"error,omitempty"`
|
2015-01-29 21:07:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Copy fields to output struct.
|
|
|
|
o.Results = r.Results
|
|
|
|
if r.Err != nil {
|
|
|
|
o.Err = r.Err.Error()
|
|
|
|
}
|
|
|
|
|
|
|
|
return json.Marshal(&o)
|
|
|
|
}
|
|
|
|
|
2015-03-31 16:26:00 +00:00
|
|
|
// UnmarshalJSON decodes the data into the Response struct
|
|
|
|
func (r *Response) UnmarshalJSON(b []byte) error {
|
2015-01-29 21:07:43 +00:00
|
|
|
var o struct {
|
2015-02-03 00:20:34 +00:00
|
|
|
Results []Result `json:"results,omitempty"`
|
|
|
|
Err string `json:"error,omitempty"`
|
2015-01-29 21:07:43 +00:00
|
|
|
}
|
|
|
|
|
2015-02-03 00:20:34 +00:00
|
|
|
dec := json.NewDecoder(bytes.NewBuffer(b))
|
|
|
|
dec.UseNumber()
|
|
|
|
err := dec.Decode(&o)
|
2015-01-29 21:07:43 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
r.Results = o.Results
|
|
|
|
if o.Err != "" {
|
|
|
|
r.Err = errors.New(o.Err)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Error returns the first error from any statement.
|
|
|
|
// Returns nil if no errors occurred on any statements.
|
2015-03-31 16:26:00 +00:00
|
|
|
func (r Response) Error() error {
|
2015-03-22 05:33:04 +00:00
|
|
|
if r.Err != nil {
|
|
|
|
return r.Err
|
2015-02-10 00:58:30 +00:00
|
|
|
}
|
2015-03-22 05:33:04 +00:00
|
|
|
for _, result := range r.Results {
|
|
|
|
if result.Err != nil {
|
|
|
|
return result.Err
|
2015-01-29 21:07:43 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2015-02-23 22:37:10 +00:00
|
|
|
// Point defines the fields that will be written to the database
|
2015-03-07 14:43:22 +00:00
|
|
|
// Name, Timestamp, and Fields are required
|
|
|
|
// Precision can be specified if the timestamp is in epoch format (integer).
|
|
|
|
// Valid values for Precision are n, u, ms, s, m, and h
|
2015-01-29 21:07:43 +00:00
|
|
|
type Point struct {
|
2015-03-17 22:27:33 +00:00
|
|
|
Name string
|
|
|
|
Tags map[string]string
|
|
|
|
Timestamp time.Time
|
|
|
|
Fields map[string]interface{}
|
|
|
|
Precision string
|
2015-01-29 21:07:43 +00:00
|
|
|
}
|
|
|
|
|
2015-03-06 18:20:30 +00:00
|
|
|
// MarshalJSON will format the time in RFC3339Nano
|
2015-03-06 19:04:26 +00:00
|
|
|
// Precision is also ignored as it is only used for writing, not reading
|
|
|
|
// Or another way to say it is we always send back in nanosecond precision
|
2015-03-06 18:20:30 +00:00
|
|
|
func (p *Point) MarshalJSON() ([]byte, error) {
|
|
|
|
point := struct {
|
2015-03-06 20:06:44 +00:00
|
|
|
Name string `json:"name,omitempty"`
|
2015-03-06 19:04:26 +00:00
|
|
|
Tags map[string]string `json:"tags,omitempty"`
|
|
|
|
Timestamp string `json:"timestamp,omitempty"`
|
2015-03-06 20:06:44 +00:00
|
|
|
Fields map[string]interface{} `json:"fields,omitempty"`
|
2015-03-27 00:45:36 +00:00
|
|
|
Precision string `json:"precision,omitempty"`
|
2015-03-06 18:20:30 +00:00
|
|
|
}{
|
2015-03-27 00:45:36 +00:00
|
|
|
Name: p.Name,
|
|
|
|
Tags: p.Tags,
|
|
|
|
Fields: p.Fields,
|
|
|
|
Precision: p.Precision,
|
2015-03-06 19:04:26 +00:00
|
|
|
}
|
|
|
|
// Let it omit empty if it's really zero
|
|
|
|
if !p.Timestamp.IsZero() {
|
|
|
|
point.Timestamp = p.Timestamp.UTC().Format(time.RFC3339Nano)
|
2015-03-06 18:20:30 +00:00
|
|
|
}
|
|
|
|
return json.Marshal(&point)
|
|
|
|
}
|
|
|
|
|
2015-01-29 21:07:43 +00:00
|
|
|
// UnmarshalJSON decodes the data into the Point struct
|
|
|
|
func (p *Point) UnmarshalJSON(b []byte) error {
|
|
|
|
var normal struct {
|
|
|
|
Name string `json:"name"`
|
|
|
|
Tags map[string]string `json:"tags"`
|
|
|
|
Timestamp time.Time `json:"timestamp"`
|
2015-01-29 23:22:43 +00:00
|
|
|
Precision string `json:"precision"`
|
2015-02-23 22:37:10 +00:00
|
|
|
Fields map[string]interface{} `json:"fields"`
|
2015-01-29 21:07:43 +00:00
|
|
|
}
|
|
|
|
var epoch struct {
|
|
|
|
Name string `json:"name"`
|
|
|
|
Tags map[string]string `json:"tags"`
|
2015-02-04 02:30:05 +00:00
|
|
|
Timestamp *int64 `json:"timestamp"`
|
2015-01-29 21:07:43 +00:00
|
|
|
Precision string `json:"precision"`
|
2015-02-23 22:37:10 +00:00
|
|
|
Fields map[string]interface{} `json:"fields"`
|
2015-01-29 21:07:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if err := func() error {
|
|
|
|
var err error
|
2015-02-03 00:20:34 +00:00
|
|
|
dec := json.NewDecoder(bytes.NewBuffer(b))
|
|
|
|
dec.UseNumber()
|
|
|
|
if err = dec.Decode(&epoch); err != nil {
|
2015-01-29 21:07:43 +00:00
|
|
|
return err
|
|
|
|
}
|
2015-02-04 02:30:05 +00:00
|
|
|
// Convert from epoch to time.Time, but only if Timestamp
|
|
|
|
// was actually set.
|
|
|
|
var ts time.Time
|
|
|
|
if epoch.Timestamp != nil {
|
|
|
|
ts, err = EpochToTime(*epoch.Timestamp, epoch.Precision)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2015-01-29 21:07:43 +00:00
|
|
|
}
|
|
|
|
p.Name = epoch.Name
|
|
|
|
p.Tags = epoch.Tags
|
2015-03-06 18:20:30 +00:00
|
|
|
p.Timestamp = ts
|
2015-01-29 23:44:10 +00:00
|
|
|
p.Precision = epoch.Precision
|
2015-02-23 22:37:10 +00:00
|
|
|
p.Fields = normalizeFields(epoch.Fields)
|
2015-01-29 21:07:43 +00:00
|
|
|
return nil
|
|
|
|
}(); err == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2015-02-03 00:20:34 +00:00
|
|
|
dec := json.NewDecoder(bytes.NewBuffer(b))
|
|
|
|
dec.UseNumber()
|
|
|
|
if err := dec.Decode(&normal); err != nil {
|
2015-01-29 21:07:43 +00:00
|
|
|
return err
|
|
|
|
}
|
2015-01-29 23:22:43 +00:00
|
|
|
normal.Timestamp = SetPrecision(normal.Timestamp, normal.Precision)
|
2015-01-29 21:07:43 +00:00
|
|
|
p.Name = normal.Name
|
|
|
|
p.Tags = normal.Tags
|
2015-03-06 18:20:30 +00:00
|
|
|
p.Timestamp = normal.Timestamp
|
2015-01-29 23:44:10 +00:00
|
|
|
p.Precision = normal.Precision
|
2015-02-23 22:37:10 +00:00
|
|
|
p.Fields = normalizeFields(normal.Fields)
|
2015-01-29 21:07:43 +00:00
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2015-02-03 00:20:34 +00:00
|
|
|
// Remove any notion of json.Number
|
2015-02-23 22:37:10 +00:00
|
|
|
func normalizeFields(fields map[string]interface{}) map[string]interface{} {
|
|
|
|
newFields := map[string]interface{}{}
|
2015-02-03 00:20:34 +00:00
|
|
|
|
2015-02-23 22:37:10 +00:00
|
|
|
for k, v := range fields {
|
2015-02-03 00:20:34 +00:00
|
|
|
switch v := v.(type) {
|
|
|
|
case json.Number:
|
|
|
|
jv, e := v.Float64()
|
|
|
|
if e != nil {
|
|
|
|
panic(fmt.Sprintf("unable to convert json.Number to float64: %s", e))
|
|
|
|
}
|
2015-02-23 22:37:10 +00:00
|
|
|
newFields[k] = jv
|
2015-02-03 00:20:34 +00:00
|
|
|
default:
|
2015-02-23 22:37:10 +00:00
|
|
|
newFields[k] = v
|
2015-02-03 00:20:34 +00:00
|
|
|
}
|
|
|
|
}
|
2015-02-23 22:37:10 +00:00
|
|
|
return newFields
|
2015-02-03 00:20:34 +00:00
|
|
|
}
|
|
|
|
|
2015-03-06 18:20:30 +00:00
|
|
|
// BatchPoints is used to send batched data in a single write.
|
2015-03-07 14:43:22 +00:00
|
|
|
// Database and Points are required
|
|
|
|
// If no retention policy is specified, it will use the databases default retention policy.
|
|
|
|
// If tags are specified, they will be "merged" with all points. If a point already has that tag, it is ignored.
|
|
|
|
// If timestamp is specified, it will be applied to any point with an empty timestamp.
|
|
|
|
// Precision can be specified if the timestamp is in epoch format (integer).
|
|
|
|
// Valid values for Precision are n, u, ms, s, m, and h
|
2015-03-06 18:20:30 +00:00
|
|
|
type BatchPoints struct {
|
2015-03-06 20:06:44 +00:00
|
|
|
Points []Point `json:"points,omitempty"`
|
|
|
|
Database string `json:"database,omitempty"`
|
|
|
|
RetentionPolicy string `json:"retentionPolicy,omitempty"`
|
|
|
|
Tags map[string]string `json:"tags,omitempty"`
|
|
|
|
Timestamp time.Time `json:"timestamp,omitempty"`
|
|
|
|
Precision string `json:"precision,omitempty"`
|
2015-03-06 18:20:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// UnmarshalJSON decodes the data into the BatchPoints struct
|
|
|
|
func (bp *BatchPoints) UnmarshalJSON(b []byte) error {
|
|
|
|
var normal struct {
|
|
|
|
Points []Point `json:"points"`
|
|
|
|
Database string `json:"database"`
|
|
|
|
RetentionPolicy string `json:"retentionPolicy"`
|
|
|
|
Tags map[string]string `json:"tags"`
|
|
|
|
Timestamp time.Time `json:"timestamp"`
|
|
|
|
Precision string `json:"precision"`
|
|
|
|
}
|
|
|
|
var epoch struct {
|
|
|
|
Points []Point `json:"points"`
|
|
|
|
Database string `json:"database"`
|
|
|
|
RetentionPolicy string `json:"retentionPolicy"`
|
|
|
|
Tags map[string]string `json:"tags"`
|
|
|
|
Timestamp *int64 `json:"timestamp"`
|
|
|
|
Precision string `json:"precision"`
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := func() error {
|
|
|
|
var err error
|
|
|
|
if err = json.Unmarshal(b, &epoch); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
// Convert from epoch to time.Time
|
|
|
|
var ts time.Time
|
|
|
|
if epoch.Timestamp != nil {
|
|
|
|
ts, err = EpochToTime(*epoch.Timestamp, epoch.Precision)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
bp.Points = epoch.Points
|
|
|
|
bp.Database = epoch.Database
|
|
|
|
bp.RetentionPolicy = epoch.RetentionPolicy
|
|
|
|
bp.Tags = epoch.Tags
|
|
|
|
bp.Timestamp = ts
|
|
|
|
bp.Precision = epoch.Precision
|
|
|
|
return nil
|
|
|
|
}(); err == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := json.Unmarshal(b, &normal); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
normal.Timestamp = SetPrecision(normal.Timestamp, normal.Precision)
|
|
|
|
bp.Points = normal.Points
|
|
|
|
bp.Database = normal.Database
|
|
|
|
bp.RetentionPolicy = normal.RetentionPolicy
|
|
|
|
bp.Tags = normal.Tags
|
|
|
|
bp.Timestamp = normal.Timestamp
|
|
|
|
bp.Precision = normal.Precision
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2015-01-22 23:40:32 +00:00
|
|
|
// utility functions
|
|
|
|
|
2015-03-07 14:43:22 +00:00
|
|
|
// Addr provides the current url as a string of the server the client is connected to.
|
2015-01-22 23:40:32 +00:00
|
|
|
func (c *Client) Addr() string {
|
2015-01-23 22:49:23 +00:00
|
|
|
return c.url.String()
|
2015-01-22 23:40:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// helper functions
|
|
|
|
|
2015-01-29 23:33:31 +00:00
|
|
|
// EpochToTime takes a unix epoch time and uses precision to return back a time.Time
|
2015-01-29 18:10:13 +00:00
|
|
|
func EpochToTime(epoch int64, precision string) (time.Time, error) {
|
|
|
|
if precision == "" {
|
|
|
|
precision = "s"
|
|
|
|
}
|
|
|
|
var t time.Time
|
|
|
|
switch precision {
|
|
|
|
case "h":
|
|
|
|
t = time.Unix(0, epoch*int64(time.Hour))
|
|
|
|
case "m":
|
|
|
|
t = time.Unix(0, epoch*int64(time.Minute))
|
|
|
|
case "s":
|
|
|
|
t = time.Unix(0, epoch*int64(time.Second))
|
|
|
|
case "ms":
|
|
|
|
t = time.Unix(0, epoch*int64(time.Millisecond))
|
|
|
|
case "u":
|
|
|
|
t = time.Unix(0, epoch*int64(time.Microsecond))
|
|
|
|
case "n":
|
|
|
|
t = time.Unix(0, epoch)
|
|
|
|
default:
|
|
|
|
return time.Time{}, fmt.Errorf("Unknowm precision %q", precision)
|
|
|
|
}
|
|
|
|
return t, nil
|
|
|
|
}
|
|
|
|
|
2015-01-29 23:22:43 +00:00
|
|
|
// SetPrecision will round a time to the specified precision
|
|
|
|
func SetPrecision(t time.Time, precision string) time.Time {
|
|
|
|
switch precision {
|
|
|
|
case "n":
|
|
|
|
case "u":
|
|
|
|
return t.Round(time.Microsecond)
|
|
|
|
case "ms":
|
|
|
|
return t.Round(time.Millisecond)
|
|
|
|
case "s":
|
|
|
|
return t.Round(time.Second)
|
|
|
|
case "m":
|
|
|
|
return t.Round(time.Minute)
|
|
|
|
case "h":
|
|
|
|
return t.Round(time.Hour)
|
|
|
|
}
|
|
|
|
return t
|
|
|
|
}
|
|
|
|
|
2015-01-22 23:40:32 +00:00
|
|
|
func detect(values ...string) string {
|
|
|
|
for _, v := range values {
|
|
|
|
if v != "" {
|
|
|
|
return v
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return ""
|
|
|
|
}
|