You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
654 lines
14 KiB
654 lines
14 KiB
package client
|
|
|
|
import (
|
|
"fmt"
|
|
"net"
|
|
|
|
"gordenko.dev/dima/diploma"
|
|
"gordenko.dev/dima/diploma/bin"
|
|
"gordenko.dev/dima/diploma/bufreader"
|
|
"gordenko.dev/dima/diploma/proto"
|
|
)
|
|
|
|
const (
|
|
metricKeySize = 4
|
|
)
|
|
|
|
type Error struct {
|
|
Code uint16
|
|
Message string
|
|
}
|
|
|
|
func (s Error) Error() string {
|
|
return fmt.Sprintf("%d: %s", s.Code, s.Message)
|
|
}
|
|
|
|
type Connection struct {
|
|
conn net.Conn
|
|
src *bufreader.BufferedReader
|
|
}
|
|
|
|
func Connect(address string) (*Connection, error) {
|
|
conn, err := net.Dial("tcp", address)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &Connection{
|
|
conn: conn,
|
|
src: bufreader.New(conn, 1500),
|
|
}, nil
|
|
}
|
|
|
|
func (s *Connection) String() string {
|
|
return s.conn.LocalAddr().String()
|
|
}
|
|
|
|
func (s *Connection) Close() {
|
|
s.conn.Close()
|
|
}
|
|
|
|
func (s *Connection) mustSuccess(reader *bufreader.BufferedReader) (err error) {
|
|
code, err := reader.ReadByte()
|
|
if err != nil {
|
|
return fmt.Errorf("read response code: %s", err)
|
|
}
|
|
|
|
switch code {
|
|
case proto.RespSuccess:
|
|
return nil // ok
|
|
|
|
case proto.RespError:
|
|
return s.onError()
|
|
|
|
default:
|
|
return fmt.Errorf("unknown reponse code %d", code)
|
|
}
|
|
}
|
|
|
|
func (s *Connection) AddMetric(req proto.AddMetricReq) error {
|
|
arr := []byte{
|
|
proto.TypeAddMetric,
|
|
0, 0, 0, 0, //
|
|
byte(req.MetricType),
|
|
byte(req.FracDigits),
|
|
}
|
|
bin.PutUint32(arr[1:], req.MetricID)
|
|
|
|
if _, err := s.conn.Write(arr); err != nil {
|
|
return err
|
|
}
|
|
return s.mustSuccess(s.src)
|
|
}
|
|
|
|
func (s *Connection) GetMetric(metricID uint32) (*proto.Metric, error) {
|
|
arr := []byte{
|
|
proto.TypeGetMetric,
|
|
0, 0, 0, 0,
|
|
}
|
|
bin.PutUint32(arr[1:], metricID)
|
|
|
|
if _, err := s.conn.Write(arr); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
code, err := s.src.ReadByte()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("read response code: %s", err)
|
|
}
|
|
|
|
switch code {
|
|
case proto.RespValue:
|
|
arr, err := s.src.ReadN(6)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("read body: %s", err)
|
|
}
|
|
|
|
return &proto.Metric{
|
|
MetricID: bin.GetUint32(arr),
|
|
MetricType: diploma.MetricType(arr[4]),
|
|
FracDigits: int(arr[5]),
|
|
}, nil
|
|
|
|
case proto.RespError:
|
|
return nil, s.onMaybeError()
|
|
|
|
default:
|
|
return nil, fmt.Errorf("unknown reponse code %d", code)
|
|
}
|
|
}
|
|
|
|
func (s *Connection) DeleteMetric(metricID uint32) error {
|
|
arr := []byte{
|
|
proto.TypeDeleteMetric,
|
|
0, 0, 0, 0, //
|
|
}
|
|
bin.PutUint32(arr[1:], metricID)
|
|
|
|
if _, err := s.conn.Write(arr); err != nil {
|
|
return err
|
|
}
|
|
return s.mustSuccess(s.src)
|
|
}
|
|
|
|
func (s *Connection) AppendMeasure(req proto.AppendMeasureReq) (err error) {
|
|
arr := []byte{
|
|
proto.TypeAppendMeasure,
|
|
0, 0, 0, 0, // metricID
|
|
0, 0, 0, 0, // timestamp
|
|
0, 0, 0, 0, 0, 0, 0, 0, // value
|
|
}
|
|
bin.PutUint32(arr[1:], req.MetricID)
|
|
bin.PutUint32(arr[5:], req.Timestamp)
|
|
bin.PutFloat64(arr[9:], req.Value)
|
|
|
|
if _, err := s.conn.Write(arr); err != nil {
|
|
return err
|
|
}
|
|
return s.mustSuccess(s.src)
|
|
}
|
|
|
|
func (s *Connection) AppendMeasures(req proto.AppendMeasuresReq) (err error) {
|
|
if len(req.Measures) > 65535 {
|
|
return fmt.Errorf("wrong measures qty: %d", len(req.Measures))
|
|
}
|
|
var (
|
|
prefixSize = 7
|
|
recordSize = 12
|
|
arr = make([]byte, prefixSize+len(req.Measures)*recordSize)
|
|
)
|
|
arr[0] = proto.TypeAppendMeasures
|
|
bin.PutUint32(arr[1:], req.MetricID)
|
|
bin.PutUint16(arr[5:], uint16(len(req.Measures)))
|
|
pos := prefixSize
|
|
for _, measure := range req.Measures {
|
|
bin.PutUint32(arr[pos:], measure.Timestamp)
|
|
bin.PutFloat64(arr[pos+4:], measure.Value)
|
|
pos += recordSize
|
|
}
|
|
if _, err := s.conn.Write(arr); err != nil {
|
|
return err
|
|
}
|
|
return s.mustSuccess(s.src)
|
|
}
|
|
|
|
func (s *Connection) ListAllInstantMeasures(metricID uint32) ([]proto.InstantMeasure, error) {
|
|
arr := []byte{
|
|
proto.TypeListAllInstantMeasures,
|
|
0, 0, 0, 0, // metricID
|
|
}
|
|
bin.PutUint32(arr[1:], metricID)
|
|
|
|
if _, err := s.conn.Write(arr); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var (
|
|
result []proto.InstantMeasure
|
|
tmp = make([]byte, 12)
|
|
)
|
|
|
|
for {
|
|
code, err := s.src.ReadByte()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("read response code: %s", err)
|
|
}
|
|
|
|
switch code {
|
|
case proto.RespPartOfValue:
|
|
q, err := bin.ReadUint32(s.src)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("read records qty: %s", err)
|
|
}
|
|
|
|
for i := range int(q) {
|
|
err = bin.ReadNInto(s.src, tmp)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("read record #%d: %s", i, err)
|
|
}
|
|
|
|
result = append(result, proto.InstantMeasure{
|
|
Timestamp: bin.GetUint32(tmp),
|
|
Value: bin.GetFloat64(tmp[4:]),
|
|
})
|
|
}
|
|
|
|
case proto.RespEndOfValue:
|
|
return result, nil
|
|
|
|
case proto.RespError:
|
|
return nil, s.onError()
|
|
|
|
default:
|
|
return nil, fmt.Errorf("unknown reponse code %d", code)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *Connection) ListInstantMeasures(req proto.ListInstantMeasuresReq) ([]proto.InstantMeasure, error) {
|
|
arr := []byte{
|
|
proto.TypeListInstantMeasures,
|
|
0, 0, 0, 0, // metricID
|
|
0, 0, 0, 0, // since
|
|
0, 0, 0, 0, // until
|
|
}
|
|
bin.PutUint32(arr[1:], req.MetricID)
|
|
bin.PutUint32(arr[5:], req.Since)
|
|
bin.PutUint32(arr[9:], req.Until)
|
|
|
|
if _, err := s.conn.Write(arr); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var (
|
|
result []proto.InstantMeasure
|
|
tmp = make([]byte, 12)
|
|
)
|
|
|
|
for {
|
|
code, err := s.src.ReadByte()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("read response code: %s", err)
|
|
}
|
|
|
|
switch code {
|
|
case proto.RespPartOfValue:
|
|
q, err := bin.ReadUint32(s.src)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("read records qty: %s", err)
|
|
}
|
|
|
|
for i := range int(q) {
|
|
err = bin.ReadNInto(s.src, tmp)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("read record #%d: %s", i, err)
|
|
}
|
|
|
|
result = append(result, proto.InstantMeasure{
|
|
Timestamp: bin.GetUint32(tmp),
|
|
Value: bin.GetFloat64(tmp[4:]),
|
|
})
|
|
}
|
|
|
|
case proto.RespEndOfValue:
|
|
return result, nil
|
|
|
|
case proto.RespError:
|
|
return nil, s.onError()
|
|
|
|
default:
|
|
return nil, fmt.Errorf("unknown reponse code %d", code)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *Connection) ListAllCumulativeMeasures(metricID uint32) ([]proto.CumulativeMeasure, error) {
|
|
arr := []byte{
|
|
proto.TypeListAllCumulativeMeasures,
|
|
0, 0, 0, 0, // metricID
|
|
}
|
|
bin.PutUint32(arr[1:], metricID)
|
|
|
|
if _, err := s.conn.Write(arr); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var (
|
|
result []proto.CumulativeMeasure
|
|
tmp = make([]byte, 20)
|
|
)
|
|
|
|
for {
|
|
code, err := s.src.ReadByte()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("read response code: %s", err)
|
|
}
|
|
|
|
switch code {
|
|
case proto.RespPartOfValue:
|
|
q, err := bin.ReadUint32(s.src)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("read records qty: %s", err)
|
|
}
|
|
|
|
for i := range int(q) {
|
|
err = bin.ReadNInto(s.src, tmp)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("read record #%d: %s", i, err)
|
|
}
|
|
|
|
result = append(result, proto.CumulativeMeasure{
|
|
Timestamp: bin.GetUint32(tmp),
|
|
Value: bin.GetFloat64(tmp[4:]),
|
|
Total: bin.GetFloat64(tmp[12:]),
|
|
})
|
|
}
|
|
|
|
case proto.RespEndOfValue:
|
|
return result, nil
|
|
|
|
case proto.RespError:
|
|
return nil, s.onError()
|
|
|
|
default:
|
|
return nil, fmt.Errorf("unknown reponse code %d", code)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *Connection) ListCumulativeMeasures(req proto.ListCumulativeMeasuresReq) ([]proto.CumulativeMeasure, error) {
|
|
arr := []byte{
|
|
proto.TypeListCumulativeMeasures,
|
|
0, 0, 0, 0, // metricID
|
|
0, 0, 0, 0, // since
|
|
0, 0, 0, 0, // until
|
|
}
|
|
bin.PutUint32(arr[1:], req.MetricID)
|
|
bin.PutUint32(arr[5:], req.Since)
|
|
bin.PutUint32(arr[9:], req.Until)
|
|
|
|
if _, err := s.conn.Write(arr); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var (
|
|
result []proto.CumulativeMeasure
|
|
tmp = make([]byte, 20)
|
|
)
|
|
|
|
for {
|
|
code, err := s.src.ReadByte()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("read response code: %s", err)
|
|
}
|
|
|
|
switch code {
|
|
case proto.RespPartOfValue:
|
|
q, err := bin.ReadUint32(s.src)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("read records qty: %s", err)
|
|
}
|
|
|
|
for i := range int(q) {
|
|
err = bin.ReadNInto(s.src, tmp)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("read record #%d: %s", i, err)
|
|
}
|
|
|
|
result = append(result, proto.CumulativeMeasure{
|
|
Timestamp: bin.GetUint32(tmp),
|
|
Value: bin.GetFloat64(tmp[4:]),
|
|
Total: bin.GetFloat64(tmp[12:]),
|
|
})
|
|
}
|
|
|
|
case proto.RespEndOfValue:
|
|
return result, nil
|
|
|
|
case proto.RespError:
|
|
return nil, s.onError()
|
|
|
|
default:
|
|
return nil, fmt.Errorf("unknown reponse code %d", code)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *Connection) ListInstantPeriods(req proto.ListInstantPeriodsReq) ([]proto.InstantPeriod, error) {
|
|
arr := []byte{
|
|
proto.TypeListInstantPeriods,
|
|
0, 0, 0, 0, // metricID
|
|
0, 0, 0, 0, // since
|
|
0, 0, 0, 0, // until
|
|
byte(req.GroupBy),
|
|
req.AggregateFuncs,
|
|
byte(req.FirstHourOfDay),
|
|
}
|
|
bin.PutUint32(arr[1:], req.MetricID)
|
|
bin.PutUint16(arr[5:], uint16(req.Since.Year))
|
|
arr[7] = byte(req.Since.Month)
|
|
arr[8] = byte(req.Since.Day)
|
|
bin.PutUint16(arr[9:], uint16(req.Until.Year))
|
|
arr[11] = byte(req.Until.Month)
|
|
arr[12] = byte(req.Until.Day)
|
|
|
|
if _, err := s.conn.Write(arr); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var q int
|
|
if (req.AggregateFuncs & diploma.AggregateMin) == diploma.AggregateMin {
|
|
q++
|
|
}
|
|
if (req.AggregateFuncs & diploma.AggregateMax) == diploma.AggregateMax {
|
|
q++
|
|
}
|
|
if (req.AggregateFuncs & diploma.AggregateAvg) == diploma.AggregateAvg {
|
|
q++
|
|
}
|
|
|
|
var (
|
|
result []proto.InstantPeriod
|
|
// 12 bytes - period, since, until
|
|
// q * 8 bytes - min, max, avg
|
|
tmp = make([]byte, 12+q*8)
|
|
)
|
|
|
|
for {
|
|
code, err := s.src.ReadByte()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("read response code: %s", err)
|
|
}
|
|
|
|
switch code {
|
|
case proto.RespPartOfValue:
|
|
q, err := bin.ReadUint32(s.src)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("read records qty: %s", err)
|
|
}
|
|
|
|
for i := range int(q) {
|
|
err = bin.ReadNInto(s.src, tmp)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("read record #%d: %s", i, err)
|
|
}
|
|
|
|
var (
|
|
p = proto.InstantPeriod{
|
|
Period: bin.GetUint32(tmp[0:]),
|
|
Start: bin.GetUint32(tmp[4:]),
|
|
End: bin.GetUint32(tmp[8:]),
|
|
}
|
|
// 12 bytes - period, since, until
|
|
pos = 12
|
|
)
|
|
|
|
if (req.AggregateFuncs & diploma.AggregateMin) == diploma.AggregateMin {
|
|
p.Min = bin.GetFloat64(tmp[pos:])
|
|
pos += 8
|
|
}
|
|
if (req.AggregateFuncs & diploma.AggregateMax) == diploma.AggregateMax {
|
|
p.Max = bin.GetFloat64(tmp[pos:])
|
|
pos += 8
|
|
}
|
|
if (req.AggregateFuncs & diploma.AggregateAvg) == diploma.AggregateAvg {
|
|
p.Avg = bin.GetFloat64(tmp[pos:])
|
|
}
|
|
result = append(result, p)
|
|
}
|
|
|
|
case proto.RespEndOfValue:
|
|
return result, nil
|
|
|
|
case proto.RespError:
|
|
return nil, s.onError()
|
|
|
|
default:
|
|
return nil, fmt.Errorf("unknown reponse code %d", code)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *Connection) ListCumulativePeriods(req proto.ListCumulativePeriodsReq) ([]proto.CumulativePeriod, error) {
|
|
arr := []byte{
|
|
proto.TypeListCumulativePeriods,
|
|
0, 0, 0, 0, // metricID
|
|
0, 0, 0, 0, // since
|
|
0, 0, 0, 0, // until
|
|
byte(req.GroupBy),
|
|
byte(req.FirstHourOfDay),
|
|
}
|
|
bin.PutUint32(arr[1:], req.MetricID)
|
|
bin.PutUint16(arr[5:], uint16(req.Since.Year))
|
|
arr[7] = byte(req.Since.Month)
|
|
arr[8] = byte(req.Since.Day)
|
|
bin.PutUint16(arr[9:], uint16(req.Until.Year))
|
|
arr[11] = byte(req.Until.Month)
|
|
arr[12] = byte(req.Until.Day)
|
|
|
|
if _, err := s.conn.Write(arr); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var (
|
|
result []proto.CumulativePeriod
|
|
tmp = make([]byte, 28)
|
|
)
|
|
|
|
for {
|
|
code, err := s.src.ReadByte()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("read response code: %s", err)
|
|
}
|
|
|
|
switch code {
|
|
case proto.RespPartOfValue:
|
|
q, err := bin.ReadUint32(s.src)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("read records qty: %s", err)
|
|
}
|
|
|
|
for i := range int(q) {
|
|
err = bin.ReadNInto(s.src, tmp)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("read record #%d: %s", i, err)
|
|
}
|
|
result = append(result, proto.CumulativePeriod{
|
|
Period: bin.GetUint32(tmp[0:]),
|
|
Start: bin.GetUint32(tmp[4:]),
|
|
End: bin.GetUint32(tmp[8:]),
|
|
StartValue: bin.GetFloat64(tmp[12:]),
|
|
EndValue: bin.GetFloat64(tmp[20:]),
|
|
})
|
|
}
|
|
|
|
case proto.RespEndOfValue:
|
|
return result, nil
|
|
|
|
case proto.RespError:
|
|
return nil, s.onError()
|
|
|
|
default:
|
|
return nil, fmt.Errorf("unknown reponse code %d", code)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *Connection) ListCurrentValues(metricIDs []uint32) ([]proto.CurrentValue, error) {
|
|
arr := make([]byte, 3+metricKeySize*len(metricIDs))
|
|
arr[0] = proto.TypeListCurrentValues
|
|
|
|
bin.PutUint16(arr[1:], uint16(len(metricIDs)))
|
|
|
|
off := 3
|
|
for _, metricID := range metricIDs {
|
|
bin.PutUint32(arr[off:], metricID)
|
|
off += metricKeySize
|
|
}
|
|
|
|
if _, err := s.conn.Write(arr); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var (
|
|
result []proto.CurrentValue
|
|
tmp = make([]byte, 16)
|
|
)
|
|
|
|
for {
|
|
code, err := s.src.ReadByte()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("read response code: %s", err)
|
|
}
|
|
|
|
switch code {
|
|
case proto.RespPartOfValue:
|
|
q, err := bin.ReadUint32(s.src)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("read records qty: %s", err)
|
|
}
|
|
|
|
for i := range int(q) {
|
|
err = bin.ReadNInto(s.src, tmp)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("read record #%d: %s", i, err)
|
|
}
|
|
|
|
result = append(result, proto.CurrentValue{
|
|
MetricID: bin.GetUint32(tmp),
|
|
Timestamp: bin.GetUint32(tmp[4:]),
|
|
Value: bin.GetFloat64(tmp[8:]),
|
|
})
|
|
}
|
|
|
|
case proto.RespEndOfValue:
|
|
return result, nil
|
|
|
|
case proto.RespError:
|
|
return nil, s.onError()
|
|
|
|
default:
|
|
return nil, fmt.Errorf("unknown reponse code %d", code)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *Connection) DeleteMeasures(req proto.DeleteMeasuresReq) (err error) {
|
|
arr := []byte{
|
|
proto.TypeDeleteMeasures,
|
|
0, 0, 0, 0, // metricID
|
|
0, 0, 0, 0, // since
|
|
}
|
|
bin.PutUint32(arr[1:], req.MetricID)
|
|
bin.PutUint32(arr[5:], req.Since)
|
|
|
|
if _, err := s.conn.Write(arr); err != nil {
|
|
return err
|
|
}
|
|
return s.mustSuccess(s.src)
|
|
}
|
|
|
|
func (s *Connection) onError() error {
|
|
errorCode, err := bin.ReadUint16(s.src)
|
|
if err != nil {
|
|
return fmt.Errorf("read error code: %s", err)
|
|
}
|
|
return Error{
|
|
Code: errorCode,
|
|
Message: proto.ErrorCodeToText(errorCode),
|
|
}
|
|
}
|
|
|
|
func (s *Connection) onMaybeError() error {
|
|
errorCode, err := bin.ReadUint16(s.src)
|
|
if err != nil {
|
|
return fmt.Errorf("read error code: %s", err)
|
|
}
|
|
if errorCode == proto.ErrNoMetric {
|
|
return nil
|
|
}
|
|
return Error{
|
|
Code: errorCode,
|
|
Message: proto.ErrorCodeToText(errorCode),
|
|
}
|
|
}
|
|
|