influxdb/client/influxdb.go

338 lines
7.2 KiB
Go

package client
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"net/http"
"net/url"
"time"
"github.com/influxdb/influxdb/influxql"
)
type Config struct {
URL url.URL
Username string
Password string
}
type Client struct {
url url.URL
username string
password string
httpClient *http.Client
}
type Query struct {
Command string
Database string
}
type Write struct {
Database string
RetentionPolicy string
Points []Point
}
func NewClient(c Config) (*Client, error) {
client := Client{
url: c.URL,
username: c.Username,
password: c.Password,
httpClient: &http.Client{},
}
return &client, nil
}
func (c *Client) Query(q Query) (*Results, error) {
u := c.url
u.Path = "query"
values := u.Query()
values.Set("q", q.Command)
values.Set("db", q.Database)
u.RawQuery = values.Encode()
resp, err := c.httpClient.Get(u.String())
if err != nil {
return nil, err
}
defer resp.Body.Close()
var results Results
err = json.NewDecoder(resp.Body).Decode(&results)
if err != nil {
return nil, err
}
return &results, nil
}
func (c *Client) Write(writes ...Write) (*Results, error) {
c.url.Path = "write"
type data struct {
Points []Point `json:"points"`
Database string `json:"database"`
RetentionPolicy string `json:"retentionPolicy"`
}
d := []data{}
for _, write := range writes {
d = append(d, data{Points: write.Points, Database: write.Database, RetentionPolicy: write.RetentionPolicy})
}
b := []byte{}
err := json.Unmarshal(b, &d)
resp, err := c.httpClient.Post(c.url.String(), "application/json", bytes.NewBuffer(b))
if err != nil {
return nil, err
}
defer resp.Body.Close()
var results Results
err = json.NewDecoder(resp.Body).Decode(&results)
if err != nil {
return nil, err
}
return &results, nil
}
func (c *Client) Ping() (time.Duration, string, error) {
now := time.Now()
u := c.url
u.Path = "ping"
resp, err := c.httpClient.Get(u.String())
if err != nil {
return 0, "", err
}
version := resp.Header.Get("X-Influxdb-Version")
return time.Since(now), version, nil
}
// Structs
// Result represents a resultset returned from a single statement.
type Result struct {
Rows []*influxql.Row
Err error
}
// MarshalJSON encodes the result into JSON.
func (r *Result) MarshalJSON() ([]byte, error) {
// Define a struct that outputs "error" as a string.
var o struct {
Rows []*influxql.Row `json:"rows,omitempty"`
Err string `json:"error,omitempty"`
}
// Copy fields to output struct.
o.Rows = r.Rows
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 {
Rows []*influxql.Row `json:"rows,omitempty"`
Err string `json:"error,omitempty"`
}
err := json.Unmarshal(b, &o)
if err != nil {
return err
}
r.Rows = o.Rows
if o.Err != "" {
r.Err = errors.New(o.Err)
}
return nil
}
// Results represents a list of statement results.
type Results struct {
Results []*Result
Err error
}
func (r Results) MarshalJSON() ([]byte, error) {
// Define a struct that outputs "error" as a string.
var o struct {
Results []*Result `json:"results,omitempty"`
Err string `json:"error,omitempty"`
}
// Copy fields to output struct.
o.Results = r.Results
if r.Err != nil {
o.Err = r.Err.Error()
}
return json.Marshal(&o)
}
// UnmarshalJSON decodes the data into the Results struct
func (r *Results) UnmarshalJSON(b []byte) error {
var o struct {
Results []*Result `json:"results,omitempty"`
Err string `json:"error,omitempty"`
}
err := json.Unmarshal(b, &o)
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.
func (a Results) Error() error {
for _, r := range a.Results {
if r.Err != nil {
return r.Err
}
}
return nil
}
// Timestamp is a custom type so we marshal JSON properly into UTC nanosecond time
type Timestamp time.Time
// Time returns the time represented by the Timestamp
func (t Timestamp) Time() time.Time {
return time.Time(t)
}
// MarshalJSON returns time in UTC with nanoseconds
func (t Timestamp) MarshalJSON() ([]byte, error) {
// Always send back in UTC with nanoseconds
s := t.Time().UTC().Format(time.RFC3339Nano)
return []byte(`"` + s + `"`), nil
}
// Point defines the values that will be written to the database
type Point struct {
Name string `json:"name"`
Tags map[string]string `json:"tags"`
Timestamp Timestamp `json:"timestamp"`
Values map[string]interface{} `json:"values"`
}
// 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"`
Precision string `json:"precision"`
Values map[string]interface{} `json:"values"`
}
var epoch struct {
Name string `json:"name"`
Tags map[string]string `json:"tags"`
Timestamp int64 `json:"timestamp"`
Precision string `json:"precision"`
Values map[string]interface{} `json:"values"`
}
if err := func() error {
var err error
if err = json.Unmarshal(b, &epoch); err != nil {
return err
}
// Convert from epoch to time.Time
ts, err := EpochToTime(epoch.Timestamp, epoch.Precision)
if err != nil {
return err
}
p.Name = epoch.Name
p.Tags = epoch.Tags
p.Timestamp = Timestamp(ts)
p.Values = epoch.Values
return nil
}(); err == nil {
return nil
}
if err := json.Unmarshal(b, &normal); err != nil {
return err
}
normal.Timestamp = SetPrecision(normal.Timestamp, normal.Precision)
p.Name = normal.Name
p.Tags = normal.Tags
p.Timestamp = Timestamp(normal.Timestamp)
p.Values = normal.Values
return nil
}
// utility functions
func (c *Client) Addr() string {
return c.url.String()
}
// helper functions
// EpochToTime takes a unix epoch time and uses precision to return back a time.Time
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
}
// 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
}
func detect(values ...string) string {
for _, v := range values {
if v != "" {
return v
}
}
return ""
}