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), } }