package influx import ( "bytes" "compress/gzip" "context" "encoding/csv" "encoding/json" "errors" "fmt" "io" "net/http" "net/url" "strconv" "strings" "time" ) // line protocol, better to gzip and sort tags by key in lexicographic order func (client *influxClient) writeLinesData(ctx context.Context, db string, data []byte, compress bool) error { if compress { var buf bytes.Buffer gz := gzip.NewWriter(&buf) if _, err := gz.Write(data); err != nil { return err } if err := gz.Close(); err != nil { return err } data = buf.Bytes() } request, err := http.NewRequest(http.MethodPost, client.url+"/write?db="+db, bytes.NewReader(data)) if err != nil { return err } request.Header.Set("Content-Type", "text/plain; charset=utf-8") request.Header.Set("Authorization", "Token "+client.token) request.Header.Set("Accept", "application/json") if compress { request.Header.Set("Content-Encoding", "gzip") } response, err := client.Do(request.WithContext(ctx)) if err != nil { return err } defer response.Body.Close() // http.StatusNoContent is the expected response, // 200,201,202,206,207,208 // but if we get these we should still accept it as delivered. if response.StatusCode != http.StatusNoContent { return fmt.Errorf("unexpected status code: %d", response.StatusCode) } return nil } // for influx json response data type jsonResp struct { Results []*result `json:"results"` } type result struct { StatementID int `json:"statement_id"` Series []*fields `json:"series"` } type fields struct { Name string `json:"name"` Column []string `json:"columns"` Values [][]any `json:"values"` } // respType json/csv // json_time:"2024-12-18T08:12:21.4735154Z" // csv_time:"1734572793695885000" func (client *influxClient) getTVsResp(ctx context.Context, reqData url.Values, respType string) ([]TV, error) { request, err := http.NewRequestWithContext(ctx, http.MethodGet, client.url+"/query?"+reqData.Encode(), nil) if err != nil { return nil, err } request.Header.Set("Content-Type", "application/json") request.Header.Set("Authorization", "Token "+client.token) if respType == "csv" { request.Header.Set("Accept", "application/csv") } response, err := client.Do(request) if err != nil { return nil, err } defer response.Body.Close() if response.StatusCode != http.StatusOK { return nil, fmt.Errorf("unexpected status code: %d", response.StatusCode) } respData, err := io.ReadAll(response.Body) if err != nil { return nil, err } return respDataToTVs(respData, respType) } // respType json/csv // json_time:"2024-12-18T08:12:21.4735154Z" // csv_time:"1734572793695885000" func (client *influxClient) getF2TVsResp(ctx context.Context, reqData url.Values, respType string) (map[string][]TV, error) { request, err := http.NewRequestWithContext(ctx, http.MethodGet, client.url+"/query?"+reqData.Encode(), nil) if err != nil { return nil, err } request.Header.Set("Content-Type", "application/json") request.Header.Set("Authorization", "Token "+client.token) if respType == "csv" { request.Header.Set("Accept", "application/csv") } response, err := client.Do(request) if err != nil { return nil, err } defer response.Body.Close() if response.StatusCode != http.StatusOK { return nil, fmt.Errorf("unexpected status code: %d", response.StatusCode) } respData, err := io.ReadAll(response.Body) if err != nil { return nil, err } return respDataToF2TVs(respData, respType) } func respDataToTVs(respData []byte, respType string) ([]TV, error) { switch respType { case "json": resp := new(jsonResp) err := json.Unmarshal(respData, resp) if err != nil { return nil, err } if len(resp.Results) > 0 && len(resp.Results[0].Series) > 0 { return convertJsonToTVs(resp.Results[0].Series[0].Values) } case "csv": rows, err := csv.NewReader(strings.NewReader(string(respData))).ReadAll() if err != nil { return nil, err } if len(rows) > 1 { return convertCsvToTVs(rows[1:]) } default: return nil, errors.New("invalid response type") } return []TV{}, nil } func respDataToF2TVs(respData []byte, respType string) (map[string][]TV, error) { switch respType { case "json": resp := new(jsonResp) err := json.Unmarshal(respData, resp) if err != nil { return nil, err } if len(resp.Results) > 0 && len(resp.Results[0].Series) > 0 { return convertJsonToF2TVs(resp.Results[0].Series[0].Column, resp.Results[0].Series[0].Values) } case "csv": rows, err := csv.NewReader(strings.NewReader(string(respData))).ReadAll() if err != nil { return nil, err } if len(rows) > 1 { return convertCsvToF2TVs(rows) } default: return nil, errors.New("invalid response type") } return map[string][]TV{}, nil } // measure at different times func convertJsonToTVs(data [][]any) ([]TV, error) { ret := make([]TV, 0, len(data)) for _, row := range data { if len(row) > 1 { tstr, ok := (row[0]).(string) if !ok { return nil, errors.New("not expected data type") } t, err := time.Parse("2006-01-02T15:04:05.999999Z", tstr) if err != nil { return nil, err } v, ok := (row[1]).(float64) if !ok { return nil, errors.New("not expected data type") } ret = append(ret, TV{ Time: t.UnixNano(), Value: v, }) } } return ret, nil } // different measures at different times func convertJsonToF2TVs(cols []string, data [][]any) (map[string][]TV, error) { f2tvs := make(map[string][]TV) for _, row := range data { if len(row) > 1 { tstr, ok := (row[0]).(string) if !ok { return nil, errors.New("not expected data type") } t, err := time.Parse("2006-01-02T15:04:05.999999Z", tstr) if err != nil { return nil, err } for i := 1; i < len(row); i++ { v, ok := (row[i]).(float64) if !ok { return nil, errors.New("not expected data type") } f2tvs[cols[i]] = append(f2tvs[cols[i]], TV{ Time: t.UnixNano(), Value: v, }) } } } return f2tvs, nil } // measure at different times func convertCsvToTVs(data [][]string) ([]TV, error) { ret := make([]TV, 0, len(data)) for _, row := range data[1:] { if len(row) > 3 { ns, err := strconv.ParseInt(row[2], 10, 64) if err != nil { return nil, err } v, err := strconv.ParseFloat(row[3], 64) if err != nil { return nil, err } ret = append(ret, TV{ Time: ns, Value: v, }) } } return ret, nil } // different measures at different times func convertCsvToF2TVs(data [][]string) (map[string][]TV, error) { f2tvs := make(map[string][]TV) for _, row := range data[1:] { if len(row) > 3 { ns, err := strconv.ParseInt(row[2], 10, 64) if err != nil { return nil, err } for i := 3; i < len(row); i++ { v, err := strconv.ParseFloat(row[i], 64) if err != nil { return nil, err } f2tvs[data[0][i]] = append(f2tvs[data[0][i]], TV{ Time: ns, Value: v, }) } } } return f2tvs, nil }