reworking how I actually want to publish the telemetry data for now
This commit is contained in:
+76
-22
@@ -1,9 +1,9 @@
|
||||
package telemetry
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"math"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
type DataType uint8
|
||||
@@ -13,12 +13,21 @@ const (
|
||||
DataTypeINT8 DataType = 1
|
||||
DataTypeUINT16 DataType = 2
|
||||
DataTypeINT16 DataType = 3
|
||||
DataTypeSTRING DataType = 4
|
||||
DataTypeUINT32 DataType = 4
|
||||
DataTypeINT32 DataType = 5
|
||||
DataTypeUINT64 DataType = 6
|
||||
DataTypeINT64 DataType = 7
|
||||
DataTypeSTRING DataType = 8
|
||||
DataTypeCHAR DataType = 9
|
||||
)
|
||||
|
||||
// TelemetryField will be the basic unit to hold telemetry data values in our
|
||||
// application.
|
||||
// From my testing, using an uint64 bucket is around 50x faster than any
|
||||
type TelemetryField struct {
|
||||
Type DataType
|
||||
Value any
|
||||
Type DataType
|
||||
Raw uint64
|
||||
Str string // Only to be used with DataTypeSTRING
|
||||
}
|
||||
|
||||
// Pack will pack this current TelemetryField into bytes to send over the wire
|
||||
@@ -32,28 +41,72 @@ type TelemetryField struct {
|
||||
// 0x02 - str len max is 255 chars
|
||||
// [0x02] - str
|
||||
func (tf *TelemetryField) Pack() []byte {
|
||||
buf := new(bytes.Buffer)
|
||||
// NOTE: maybe we can have a pool of these so we don't have to create them here
|
||||
// or whatever
|
||||
buf := make([]byte, 0, 8)
|
||||
|
||||
buf.WriteByte(uint8(tf.Type))
|
||||
buf = append(buf, uint8(tf.Type))
|
||||
|
||||
switch tf.Type {
|
||||
case DataTypeINT8:
|
||||
buf.WriteByte(uint8(tf.Value.(int8)))
|
||||
case DataTypeUINT8:
|
||||
buf.WriteByte(uint8(tf.Value.(uint8)))
|
||||
case DataTypeINT8, DataTypeUINT8:
|
||||
buf = append(buf, uint8(tf.Raw))
|
||||
case DataTypeINT16, DataTypeUINT16:
|
||||
binary.Write(buf, binary.LittleEndian, tf.Value)
|
||||
buf = append(buf, uint8(tf.Raw), uint8(tf.Raw>>8))
|
||||
case DataTypeINT32, DataTypeUINT32:
|
||||
buf = append(buf, uint8(tf.Raw), uint8(tf.Raw>>8), uint8(tf.Raw>>16))
|
||||
case DataTypeINT64, DataTypeUINT64:
|
||||
buf = append(buf, uint8(tf.Raw), uint8(tf.Raw>>8), uint8(tf.Raw>>16), uint8(tf.Raw>>32))
|
||||
case DataTypeSTRING:
|
||||
str := tf.Value.(string)
|
||||
buf.WriteByte(uint8(len(str)))
|
||||
buf.WriteString(str)
|
||||
l := min(len(tf.Str), math.MaxUint8)
|
||||
|
||||
buf = append(buf, uint8(l))
|
||||
buf = append(buf, tf.Str[:l]...)
|
||||
}
|
||||
|
||||
return buf.Bytes()
|
||||
return buf
|
||||
}
|
||||
|
||||
func (tf *TelemetryField) String() string {
|
||||
return fmt.Sprintf("%v", tf.Value)
|
||||
switch tf.Type {
|
||||
case DataTypeSTRING:
|
||||
return tf.Str
|
||||
case DataTypeCHAR:
|
||||
return string([]byte{byte(tf.Raw)})
|
||||
case DataTypeUINT16, DataTypeUINT8:
|
||||
return strconv.FormatUint(tf.Raw, 10)
|
||||
case DataTypeINT8:
|
||||
return strconv.FormatInt(int64(int16(tf.Raw)), 10)
|
||||
case DataTypeINT16:
|
||||
return strconv.FormatInt(int64(int(tf.Raw)), 10)
|
||||
}
|
||||
|
||||
return "NaN"
|
||||
}
|
||||
|
||||
type FieldID uint16
|
||||
|
||||
const (
|
||||
FirstTimeStamp FieldID = iota
|
||||
PreviousTimeStamp
|
||||
LastTimeStamp
|
||||
Speed
|
||||
RPM
|
||||
Gear
|
||||
MaxFields
|
||||
)
|
||||
|
||||
var FieldNames = [MaxFields]string{
|
||||
Speed: "Speed",
|
||||
RPM: "RPM",
|
||||
Gear: "Gear",
|
||||
}
|
||||
|
||||
func GetFieldName(id FieldID) string {
|
||||
if id >= MaxFields {
|
||||
return "Uknown"
|
||||
}
|
||||
|
||||
return FieldNames[id]
|
||||
}
|
||||
|
||||
// NOTE: Replace values with a more appropriate custom field approach where
|
||||
@@ -63,11 +116,12 @@ func (tf *TelemetryField) String() string {
|
||||
|
||||
type TelemetryData struct {
|
||||
// Values map[string]*TelemetryField
|
||||
Values map[string][32]byte
|
||||
Values [MaxFields]TelemetryField
|
||||
InitialTime time.Time
|
||||
PenultimateDataPoll time.Time
|
||||
LastDataPoll time.Time
|
||||
}
|
||||
|
||||
func NewTelemetryData() *TelemetryData {
|
||||
return &TelemetryData{
|
||||
Values: make(map[string][32]byte),
|
||||
}
|
||||
return &TelemetryData{}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
package telemetry
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
// Old Way
|
||||
type TelemetryFieldAny struct {
|
||||
Value any
|
||||
}
|
||||
|
||||
// New Way
|
||||
type TelemetryFieldUint struct {
|
||||
Raw uint64
|
||||
}
|
||||
|
||||
func BenchmarkInterfaceBoxing(b *testing.B) {
|
||||
var field TelemetryFieldAny
|
||||
b.ReportAllocs() // Tracks memory allocations
|
||||
for i := 0; i < b.N; i++ {
|
||||
// Every iteration, we put a uint16 into 'any'
|
||||
// This forces a heap allocation in many Go versions
|
||||
field.Value = uint16(i % 65535)
|
||||
}
|
||||
_ = field
|
||||
}
|
||||
|
||||
func BenchmarkUint64Bucket(b *testing.B) {
|
||||
var field TelemetryFieldUint
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
// Casting to uint64 is a direct CPU register move
|
||||
field.Raw = uint64(uint16(i % 65535))
|
||||
}
|
||||
_ = field
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package telemetry
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var (
|
||||
testRaw uint64 = 0x1122334455667788
|
||||
sink []byte
|
||||
)
|
||||
|
||||
// The "Manual" way (with the fix for the missing 24-bit shift)
|
||||
func BenchmarkPackManual(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
buf := make([]byte, 0, 8)
|
||||
buf = append(buf,
|
||||
uint8(testRaw),
|
||||
uint8(testRaw>>8),
|
||||
uint8(testRaw>>16),
|
||||
uint8(testRaw>>24),
|
||||
uint8(testRaw>>32),
|
||||
uint8(testRaw>>40),
|
||||
uint8(testRaw>>48),
|
||||
uint8(testRaw>>56),
|
||||
)
|
||||
sink = buf
|
||||
}
|
||||
}
|
||||
|
||||
// The "Loop" way
|
||||
func BenchmarkPackLoop(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
buf := make([]byte, 0, 8)
|
||||
for j := 0; j < 8; j++ {
|
||||
buf = append(buf, uint8(testRaw>>(j*8)))
|
||||
}
|
||||
sink = buf
|
||||
}
|
||||
}
|
||||
|
||||
// The "Binary + Stack Array" way (Best Practice)
|
||||
func BenchmarkPackBinary(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
buf := make([]byte, 0, 8)
|
||||
var temp [8]byte
|
||||
binary.LittleEndian.PutUint64(temp[:], testRaw)
|
||||
buf = append(buf, temp[:]...)
|
||||
sink = buf
|
||||
}
|
||||
}
|
||||
+17
-17
@@ -17,44 +17,44 @@ func Test_TelemetryField(t *testing.T) {
|
||||
{
|
||||
name: "test_max_uint8",
|
||||
tf: TelemetryField{
|
||||
Type: DataTypeUINT8,
|
||||
Value: uint8(math.MaxUint8),
|
||||
Type: DataTypeUINT8,
|
||||
Raw: uint64(math.MaxUint8),
|
||||
},
|
||||
expect: []byte{0x00, 0xFF},
|
||||
},
|
||||
{
|
||||
name: "test_max_int8",
|
||||
tf: TelemetryField{
|
||||
Type: DataTypeINT8,
|
||||
Value: int8(math.MaxInt8),
|
||||
Type: DataTypeINT8,
|
||||
Raw: uint64(math.MaxInt8),
|
||||
},
|
||||
expect: []byte{0x01, 0x7F},
|
||||
},
|
||||
{
|
||||
name: "test_max_uint16",
|
||||
tf: TelemetryField{
|
||||
Type: DataTypeUINT16,
|
||||
Value: uint16(math.MaxUint16),
|
||||
Type: DataTypeUINT16,
|
||||
Raw: uint64(math.MaxUint16),
|
||||
},
|
||||
expect: []byte{0x02, 0xFF, 0xFF},
|
||||
},
|
||||
{
|
||||
name: "test_max_int16",
|
||||
tf: TelemetryField{
|
||||
Type: DataTypeINT16,
|
||||
Value: int16(math.MaxInt16),
|
||||
Type: DataTypeINT16,
|
||||
Raw: uint64(math.MaxInt16),
|
||||
},
|
||||
expect: []byte{0x03, 0xFF, 0x7F},
|
||||
},
|
||||
{
|
||||
name: "test_string",
|
||||
tf: TelemetryField{
|
||||
Type: DataTypeSTRING,
|
||||
Value: "a cool string!",
|
||||
},
|
||||
expect: []byte{0x04, 0x0E, 0x61, 0x20, 0x63, 0x6F, 0x6F, 0x6C, 0x20, 0x73,
|
||||
0x74, 0x72, 0x69, 0x6E, 0x67, 0x21},
|
||||
},
|
||||
// {
|
||||
// name: "test_string",
|
||||
// tf: TelemetryField{
|
||||
// Type: DataTypeSTRING,
|
||||
// Raw: "a cool string!",
|
||||
// },
|
||||
// expect: []byte{0x04, 0x0E, 0x61, 0x20, 0x63, 0x6F, 0x6F, 0x6C, 0x20, 0x73,
|
||||
// 0x74, 0x72, 0x69, 0x6E, 0x67, 0x21},
|
||||
// },
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
|
||||
Reference in New Issue
Block a user