спеціалізована СУБД для зберігання та обробки показань датчиків та лічильників
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.
 
 
diploma/client/client.go

753 lines
15 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)
}
}
type Metric struct {
MetricID uint32
MetricType diploma.MetricType
FracDigits byte
}
func (s *Connection) AddMetric(req Metric) 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) (*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 &Metric{
MetricID: bin.GetUint32(arr),
MetricType: diploma.MetricType(arr[4]),
FracDigits: 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)
}
type AppendMeasureReq struct {
MetricID uint32
Timestamp uint32
Value float64
}
func (s *Connection) AppendMeasure(req 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)
}
type AppendMeasuresReq struct {
MetricID uint32
Measures []Measure
}
type Measure struct {
Timestamp uint32
Value float64
}
func (s *Connection) AppendMeasures(req 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)
}
type InstantMeasure struct {
Timestamp uint32
Value float64
}
func (s *Connection) ListAllInstantMeasures(metricID uint32) ([]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 []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, 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) ([]InstantMeasure, error) {
arr := []byte{
proto.TypeListInstantMeasures,
0, 0, 0, 0, // metricID
0, 0, 0, 0, // since
0, 0, 0, 0, // until
byte(req.FirstHourOfDay),
}
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 []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, 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)
}
}
}
type CumulativeMeasure struct {
Timestamp uint32
Value float64
Total float64
}
func (s *Connection) ListAllCumulativeMeasures(metricID uint32) ([]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 []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, 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) ([]CumulativeMeasure, error) {
arr := []byte{
proto.TypeListCumulativeMeasures,
0, 0, 0, 0, // metricID
0, 0, 0, 0, // since
0, 0, 0, 0, // until
byte(req.FirstHourOfDay),
}
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 []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, 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)
}
}
}
type InstantPeriod struct {
Period uint32
Since uint32
Until uint32
Min float64
Max float64
Avg float64
}
func (s *Connection) ListInstantPeriods(req proto.ListInstantPeriodsReq) ([]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.PutUint32(arr[5:], req.Since)
bin.PutUint32(arr[9:], req.Until)
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 []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 = InstantPeriod{
Period: bin.GetUint32(tmp[0:]),
Since: bin.GetUint32(tmp[4:]),
Until: 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)
}
}
}
type CumulativePeriod struct {
Period uint32
Since uint32
Until uint32
EndValue float64
Total float64
}
func (s *Connection) ListCumulativePeriods(req proto.ListCumulativePeriodsReq) ([]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.PutUint32(arr[5:], req.Since)
bin.PutUint32(arr[9:], req.Until)
if _, err := s.conn.Write(arr); err != nil {
return nil, err
}
var (
result []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, CumulativePeriod{
Period: bin.GetUint32(tmp[0:]),
Since: bin.GetUint32(tmp[4:]),
Until: bin.GetUint32(tmp[8:]),
EndValue: bin.GetFloat64(tmp[12:]),
Total: 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)
}
}
}
type CurrentValue struct {
MetricID uint32
Timestamp uint32
Value float64
}
func (s *Connection) ListCurrentValues(metricIDs []uint32) ([]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 []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, 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)
}
type RangeTotalResp struct {
Since uint32
SinceValue float64
Until uint32
UntilValue float64
}
func (s *Connection) RangeTotal(req proto.RangeTotalReq) (*RangeTotalResp, error) {
arr := []byte{
proto.TypeGetMetric,
0, 0, 0, 0,
0, 0, 0, 0,
0, 0, 0, 0,
}
bin.PutUint32(arr[1:], req.MetricID)
bin.PutUint32(arr[5:], req.Since)
bin.PutUint32(arr[9:], req.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(24)
if err != nil {
return nil, fmt.Errorf("read body: %s", err)
}
return &RangeTotalResp{
Since: bin.GetUint32(arr),
SinceValue: bin.GetFloat64(arr[4:]),
Until: bin.GetUint32(arr[12:]),
UntilValue: bin.GetFloat64(arr[16:]),
}, nil
case proto.RespError:
return nil, s.onError()
default:
return nil, fmt.Errorf("unknown reponse code %d", code)
}
}
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),
}
}