Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Carbon Plaintext #10

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open

Add Carbon Plaintext #10

wants to merge 4 commits into from

Conversation

kamaev
Copy link

@kamaev kamaev commented Jul 9, 2021

Added simple Carbon Datapoints builder, so following workaround can be used:

package main

import (
	"log"
	"net"
	"net/url"
	"strconv"
	"time"

	"github.com/go-graphite/protocol/carbon"
)

func main() {
	service := NewService()

	var (
		now = time.Now().UTC()
		fiveMin = now.Add(5*time.Minute)
	)

	err := service.writeMetrics(now.Unix())
	if err != nil {
		log.Fatal(err)
	}

	series, err := service.readMetrics(fiveMin.Unix(), now.Unix())
	if err != nil {
		log.Fatal(err)
	}
}

generic service with pre-created metrics:

type Service struct {
	metrics *Metrics
	reader  *Reader
	writer  *Writer
}

type Metrics struct {
	Measurement1 *carbon.Metric
	Measurement2 *carbon.Metric
}

func NewService() *Service {
	return &Service{
		metrics: &Metrics{
			Measurement1: carbon.NewMetric("measurement1").
				AddNode("node1").
				AddTag("tag1", "tag1Value"),
			Measurement2: carbon.NewMetric("measurement2").
				AddNode("node1").
				AddTag("tag1", "tag1Value"),
		},
		reader: NewReader("http://127.0.0.1:9090/render"),
		writer: NewWriter("tcp", "127.0.0.1:2003", 5*time.Second),
	}
}

generic tcp datapoint writer:

func (s *Service) writeMetrics(timestamp int64) error {
	batch := [][]byte{
		carbon.NewDatapoint(s.metrics.Measurement1, 10, timestamp),
		carbon.NewDatapoint(s.metrics.Measurement2, 15, timestamp),
	}
	err := s.writer.Write(batch...)
	if err != nil {
		return err
	}
	return nil
}

func NewWriter(network, address string, timeout time.Duration) *Writer {
	return &Writer{
		network: network,
		address: address,
		timeout: timeout,
	}
}

type Writer struct {
	network string
	address string
	timeout time.Duration
}

func (s *Writer) Write(batch ...[]byte) error {
	conn, err := net.DialTimeout(s.network, s.address, s.timeout)
	if err != nil {
		return err
	}
	defer conn.Close()

	for _, item := range batch {
		_, err = conn.Write(item)
		if err != nil {
			return err
		}
	}

	return nil
}

generic http datapoint reader:

func (s *Service) readMetrics(from, to int64) ([]*protov2.MultiFetchResponse, error) {
	var result = make([]*protov2.MultiFetchResponse, 0)

	measurement1, err := s.reader.Read(s.metrics.Measurement1.GetName(), from, to)
	if err != nil {
		return nil, err
	}

	result = append(result, measurement1)

	measurement2, err := s.reader.Read(s.metrics.Measurement1.GetName(), from, to)
	if err != nil {
		return nil, err
	}

	result = append(result, measurement2)

	return result, nil
}

func NewReader(renderURL string) *Reader {
	return &Reader{RenderURL: renderURL}
}

type Reader struct {
	RenderURL string
}

func (r *Reader) Read(name string, from, to int64) (*protov2.MultiFetchResponse, error) {
	params := url.Values{}
	params.Add("format", "protobuf")
	params.Add("from", strconv.FormatInt(from, 10))
	params.Add("target", name)
	params.Add("until", strconv.FormatInt(to, 10))
	query := params.Encode()

	data, err := r.processRequest(requestURL, query)
	if err != nil {
		return nil, err
	}

	var resp protov2.MultiFetchResponse
	err = resp.Unmarshal(data)
	if err != nil {
		return nil, err
	}

	return &resp, nil
}

The use cases i thought of are:

  • To send some test values to Graphite
  • When dealing with large numbers of randomly named metrics that can cause registry performance issues
  • To build TSDB interface implementations using carbon.Datapoint along with carbonapi_v2(v3)_pb.FetchResponse
  • Maybe some Graphite to Graphite copying

// the value will be None (null).
// Doc: https://graphite.readthedocs.io/en/latest/terminology.html
type Datapoint struct {
path string
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would suggest to use []bytes or bytes.Buffer here instead. Main reason is that currently WithTag, WithPrefix methods would cause a lot of unneeded allocations (strings in golang are immutable).

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, you're right! The optimization won't be premature this time

}

// Plaintext returns Graphite Plaintext record form of datapoint
func (d *Datapoint) Plaintext() string {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be better to call method String as that's more go-way of doing things.

Copy link
Author

@kamaev kamaev Jul 11, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renamed it, but i need to think about this method, looks bit frustrating to me now

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renamed path to buffer to make things more clear

import "testing"

func Test_NewDatapoint(t *testing.T) {
const (
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be cleaner to use table driven tests:
https://github.com/golang/go/wiki/TableDrivenTests

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

@Civil
Copy link
Member

Civil commented Jul 9, 2021

I'm not sure it would be useful to a lot of people, but I don't have anything against merging that, except for the comments I put in the PR. However I would maybe ask other people about their opinion on merging it (after the comments are fixed, plus maybe they will have more to say)

@Civil Civil requested review from bom-d-van and deniszh July 9, 2021 19:55
@kamaev kamaev marked this pull request as draft July 11, 2021 10:45
@kamaev
Copy link
Author

kamaev commented Jul 12, 2021

Maybe there should be something similar to this one:
https://github.com/go-graphite/go-carbon/blob/master/points/points.go
but with tags support? (and parsing back to structure from string like ParseText function)

@kamaev kamaev marked this pull request as ready for review July 12, 2021 13:04
Copy link
Member

@bom-d-van bom-d-van left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have similar feedback as @Civil does. No concerns or objections from my side.

I guess maybe some users might like to use a library for generating graphite metrics.

Thanks for the contribution.

@kamaev kamaev marked this pull request as draft July 19, 2021 06:59
@kamaev
Copy link
Author

kamaev commented Jul 19, 2021

I'm so sorry, i've just realized there can be a better way to send the datapoints, so i'll push a lot of changes now

@kamaev
Copy link
Author

kamaev commented Jul 19, 2021

Added metric builder, so now you can build metrics first, and then create datapoints when it's necessary
That should be ~2x times faster than creating datapoints on the fly according to the benchmarks

@kamaev kamaev marked this pull request as ready for review July 19, 2021 07:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants