diff --git a/components/arm/collector.go b/components/arm/collector.go index 6dd9a60c618..9c1528cb6be 100644 --- a/components/arm/collector.go +++ b/components/arm/collector.go @@ -5,6 +5,7 @@ package arm import ( "context" "errors" + "time" v1 "go.viam.com/api/common/v1" pb "go.viam.com/api/component/arm/v1" @@ -39,18 +40,20 @@ func newEndPositionCollector(resource interface{}, params data.CollectorParams) return nil, err } - cFunc := data.CaptureFunc(func(ctx context.Context, _ map[string]*anypb.Any) (interface{}, error) { + cFunc := data.CaptureFunc(func(ctx context.Context, _ map[string]*anypb.Any) (data.CaptureResult, error) { + timeRequested := time.Now() + var res data.CaptureResult v, err := arm.EndPosition(ctx, data.FromDMExtraMap) if err != nil { // A modular filter component can be created to filter the readings from a component. The error ErrNoCaptureToStore // is used in the datamanager to exclude readings from being captured and stored. if errors.Is(err, data.ErrNoCaptureToStore) { - return nil, err + return res, err } - return nil, data.FailedToReadErr(params.ComponentName, endPosition.String(), err) + return res, data.FailedToReadErr(params.ComponentName, endPosition.String(), err) } o := v.Orientation().OrientationVectorDegrees() - return pb.GetEndPositionResponse{ + return data.NewTabularCaptureResult(timeRequested, pb.GetEndPositionResponse{ Pose: &v1.Pose{ X: v.Point().X, Y: v.Point().Y, @@ -60,7 +63,7 @@ func newEndPositionCollector(resource interface{}, params data.CollectorParams) OZ: o.OZ, Theta: o.Theta, }, - }, nil + }) }) return data.NewCollector(cFunc, params) } @@ -73,21 +76,23 @@ func newJointPositionsCollector(resource interface{}, params data.CollectorParam return nil, err } - cFunc := data.CaptureFunc(func(ctx context.Context, _ map[string]*anypb.Any) (interface{}, error) { + cFunc := data.CaptureFunc(func(ctx context.Context, _ map[string]*anypb.Any) (data.CaptureResult, error) { + timeRequested := time.Now() + var res data.CaptureResult v, err := arm.JointPositions(ctx, data.FromDMExtraMap) if err != nil { // A modular filter component can be created to filter the readings from a component. The error ErrNoCaptureToStore // is used in the datamanager to exclude readings from being captured and stored. if errors.Is(err, data.ErrNoCaptureToStore) { - return nil, err + return res, err } - return nil, data.FailedToReadErr(params.ComponentName, jointPositions.String(), err) + return res, data.FailedToReadErr(params.ComponentName, jointPositions.String(), err) } jp, err := referenceframe.JointPositionsFromInputs(arm.ModelFrame(), v) if err != nil { - return nil, data.FailedToReadErr(params.ComponentName, jointPositions.String(), err) + return res, data.FailedToReadErr(params.ComponentName, jointPositions.String(), err) } - return pb.GetJointPositionsResponse{Positions: jp}, nil + return data.NewTabularCaptureResult(timeRequested, pb.GetJointPositionsResponse{Positions: jp}) }) return data.NewCollector(cFunc, params) } diff --git a/components/arm/collectors_test.go b/components/arm/collectors_test.go index 1313a2a7186..fac304f2dc6 100644 --- a/components/arm/collectors_test.go +++ b/components/arm/collectors_test.go @@ -5,11 +5,12 @@ import ( "testing" "time" - clk "github.com/benbjohnson/clock" + "github.com/benbjohnson/clock" "github.com/golang/geo/r3" - v1 "go.viam.com/api/common/v1" + datasyncpb "go.viam.com/api/app/datasync/v1" pb "go.viam.com/api/component/arm/v1" "go.viam.com/test" + "google.golang.org/protobuf/types/known/structpb" "go.viam.com/rdk/components/arm" "go.viam.com/rdk/data" @@ -22,50 +23,71 @@ import ( const ( componentName = "arm" - captureInterval = time.Second - numRetries = 5 + captureInterval = time.Millisecond ) var floatList = &pb.JointPositions{Values: []float64{1.0, 2.0, 3.0}} func TestCollectors(t *testing.T) { + l, err := structpb.NewList([]any{1.0, 2.0, 3.0}) + test.That(t, err, test.ShouldBeNil) + tests := []struct { name string collector data.CollectorConstructor - expected map[string]any + expected *datasyncpb.SensorData }{ { name: "End position collector should write a pose", collector: arm.NewEndPositionCollector, - expected: tu.ToProtoMapIgnoreOmitEmpty(pb.GetEndPositionResponse{ - Pose: &v1.Pose{ - OX: 0, - OY: 0, - OZ: 1, - Theta: 0, - X: 1, - Y: 2, - Z: 3, - }, - }), + expected: &datasyncpb.SensorData{ + Metadata: &datasyncpb.SensorMetadata{}, + Data: &datasyncpb.SensorData_Struct{Struct: &structpb.Struct{ + Fields: map[string]*structpb.Value{ + "pose": structpb.NewStructValue(&structpb.Struct{ + Fields: map[string]*structpb.Value{ + "o_x": structpb.NewNumberValue(0), + "o_y": structpb.NewNumberValue(0), + "o_z": structpb.NewNumberValue(1), + "theta": structpb.NewNumberValue(0), + "x": structpb.NewNumberValue(1), + "y": structpb.NewNumberValue(2), + "z": structpb.NewNumberValue(3), + }, + }), + }, + }}, + }, }, { name: "Joint positions collector should write a list of positions", collector: arm.NewJointPositionsCollector, - expected: tu.ToProtoMapIgnoreOmitEmpty(pb.GetJointPositionsResponse{Positions: floatList}), + expected: &datasyncpb.SensorData{ + Metadata: &datasyncpb.SensorMetadata{}, + Data: &datasyncpb.SensorData_Struct{Struct: &structpb.Struct{ + Fields: map[string]*structpb.Value{ + "positions": structpb.NewStructValue(&structpb.Struct{ + Fields: map[string]*structpb.Value{"values": structpb.NewListValue(l)}, + }), + }, + }}, + }, }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { - mockClock := clk.NewMock() - buf := tu.MockBuffer{} + start := time.Now() + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + buf := tu.NewMockBuffer(ctx) params := data.CollectorParams{ + DataType: data.CaptureTypeTabular, ComponentName: componentName, Interval: captureInterval, Logger: logging.NewTestLogger(t), - Clock: mockClock, - Target: &buf, + Clock: clock.New(), + Target: buf, } arm := newArm() @@ -74,13 +96,8 @@ func TestCollectors(t *testing.T) { defer col.Close() col.Collect() - mockClock.Add(captureInterval) - tu.Retry(func() bool { - return buf.Length() != 0 - }, numRetries) - test.That(t, buf.Length(), test.ShouldBeGreaterThan, 0) - test.That(t, buf.Writes[0].GetStruct().AsMap(), test.ShouldResemble, tc.expected) + tu.CheckMockBufferWrites(t, ctx, start, buf.TabularWrites, tc.expected) }) } } diff --git a/components/board/collector.go b/components/board/collector.go index 64a77bfdaa0..6ac74ecad97 100644 --- a/components/board/collector.go +++ b/components/board/collector.go @@ -2,6 +2,7 @@ package board import ( "context" + "time" "github.com/pkg/errors" pb "go.viam.com/api/component/board/v1" @@ -39,10 +40,12 @@ func newAnalogCollector(resource interface{}, params data.CollectorParams) (data return nil, err } - cFunc := data.CaptureFunc(func(ctx context.Context, arg map[string]*anypb.Any) (interface{}, error) { + cFunc := data.CaptureFunc(func(ctx context.Context, arg map[string]*anypb.Any) (data.CaptureResult, error) { + timeRequested := time.Now() + var res data.CaptureResult var analogValue AnalogValue if _, ok := arg[analogReaderNameKey]; !ok { - return nil, data.FailedToReadErr(params.ComponentName, analogs.String(), + return res, data.FailedToReadErr(params.ComponentName, analogs.String(), errors.New("Must supply reader_name in additional_params for analog collector")) } if reader, err := board.AnalogByName(arg[analogReaderNameKey].String()); err == nil { @@ -51,17 +54,18 @@ func newAnalogCollector(resource interface{}, params data.CollectorParams) (data // A modular filter component can be created to filter the readings from a component. The error ErrNoCaptureToStore // is used in the datamanager to exclude readings from being captured and stored. if errors.Is(err, data.ErrNoCaptureToStore) { - return nil, err + return res, err } - return nil, data.FailedToReadErr(params.ComponentName, analogs.String(), err) + return res, data.FailedToReadErr(params.ComponentName, analogs.String(), err) } } - return pb.ReadAnalogReaderResponse{ + + return data.NewTabularCaptureResult(timeRequested, pb.ReadAnalogReaderResponse{ Value: int32(analogValue.Value), MinRange: analogValue.Min, MaxRange: analogValue.Max, StepSize: analogValue.StepSize, - }, nil + }) }) return data.NewCollector(cFunc, params) } @@ -74,10 +78,12 @@ func newGPIOCollector(resource interface{}, params data.CollectorParams) (data.C return nil, err } - cFunc := data.CaptureFunc(func(ctx context.Context, arg map[string]*anypb.Any) (interface{}, error) { + cFunc := data.CaptureFunc(func(ctx context.Context, arg map[string]*anypb.Any) (data.CaptureResult, error) { + timeRequested := time.Now() + var res data.CaptureResult var value bool if _, ok := arg[gpioPinNameKey]; !ok { - return nil, data.FailedToReadErr(params.ComponentName, gpios.String(), + return res, data.FailedToReadErr(params.ComponentName, gpios.String(), errors.New("Must supply pin_name in additional params for gpio collector")) } if gpio, err := board.GPIOPinByName(arg[gpioPinNameKey].String()); err == nil { @@ -86,14 +92,14 @@ func newGPIOCollector(resource interface{}, params data.CollectorParams) (data.C // A modular filter component can be created to filter the readings from a component. The error ErrNoCaptureToStore // is used in the datamanager to exclude readings from being captured and stored. if errors.Is(err, data.ErrNoCaptureToStore) { - return nil, err + return res, err } - return nil, data.FailedToReadErr(params.ComponentName, gpios.String(), err) + return res, data.FailedToReadErr(params.ComponentName, gpios.String(), err) } } - return pb.GetGPIOResponse{ + return data.NewTabularCaptureResult(timeRequested, pb.GetGPIOResponse{ High: value, - }, nil + }) }) return data.NewCollector(cFunc, params) } diff --git a/components/board/collectors_test.go b/components/board/collectors_test.go index 18d5c2f85d5..e2a22e77d6f 100644 --- a/components/board/collectors_test.go +++ b/components/board/collectors_test.go @@ -6,12 +6,13 @@ import ( "testing" "time" - clk "github.com/benbjohnson/clock" + "github.com/benbjohnson/clock" "github.com/golang/protobuf/ptypes/wrappers" - pb "go.viam.com/api/component/board/v1" + datasyncpb "go.viam.com/api/app/datasync/v1" "go.viam.com/test" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/known/anypb" + "google.golang.org/protobuf/types/known/structpb" "go.viam.com/rdk/components/board" "go.viam.com/rdk/data" @@ -22,22 +23,20 @@ import ( const ( componentName = "board" - captureInterval = time.Second - numRetries = 5 + captureInterval = time.Millisecond ) func TestCollectors(t *testing.T) { tests := []struct { - name string - params data.CollectorParams - collector data.CollectorConstructor - expected map[string]any - shouldError bool - expectedError error + name string + params data.CollectorParams + collector data.CollectorConstructor + expected *datasyncpb.SensorData }{ { name: "Board analog collector should write an analog response", params: data.CollectorParams{ + DataType: data.CaptureTypeTabular, ComponentName: componentName, Interval: captureInterval, Logger: logging.NewTestLogger(t), @@ -46,17 +45,22 @@ func TestCollectors(t *testing.T) { }, }, collector: board.NewAnalogCollector, - expected: tu.ToProtoMapIgnoreOmitEmpty(pb.ReadAnalogReaderResponse{ - Value: 1, - MinRange: 0, - MaxRange: 10, - StepSize: 0.1, - }), - shouldError: false, + expected: &datasyncpb.SensorData{ + Metadata: &datasyncpb.SensorMetadata{}, + Data: &datasyncpb.SensorData_Struct{Struct: &structpb.Struct{ + Fields: map[string]*structpb.Value{ + "value": structpb.NewNumberValue(1), + "min_range": structpb.NewNumberValue(0), + "max_range": structpb.NewNumberValue(10), + "step_size": structpb.NewNumberValue(float64(float32(0.1))), + }, + }}, + }, }, { name: "Board gpio collector should write a gpio response", params: data.CollectorParams{ + DataType: data.CaptureTypeTabular, ComponentName: componentName, Interval: captureInterval, Logger: logging.NewTestLogger(t), @@ -65,19 +69,25 @@ func TestCollectors(t *testing.T) { }, }, collector: board.NewGPIOCollector, - expected: tu.ToProtoMapIgnoreOmitEmpty(pb.GetGPIOResponse{ - High: true, - }), - shouldError: false, + expected: &datasyncpb.SensorData{ + Metadata: &datasyncpb.SensorMetadata{}, + Data: &datasyncpb.SensorData_Struct{Struct: &structpb.Struct{ + Fields: map[string]*structpb.Value{ + "high": structpb.NewBoolValue(true), + }, + }}, + }, }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { - mockClock := clk.NewMock() - buf := tu.MockBuffer{} - tc.params.Clock = mockClock - tc.params.Target = &buf + start := time.Now() + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + buf := tu.NewMockBuffer(ctx) + tc.params.Clock = clock.New() + tc.params.Target = buf board := newBoard() col, err := tc.collector(board, tc.params) @@ -85,13 +95,8 @@ func TestCollectors(t *testing.T) { defer col.Close() col.Collect() - mockClock.Add(captureInterval) - tu.Retry(func() bool { - return buf.Length() != 0 - }, numRetries) - test.That(t, buf.Length(), test.ShouldBeGreaterThan, 0) - test.That(t, buf.Writes[0].GetStruct().AsMap(), test.ShouldResemble, tc.expected) + tu.CheckMockBufferWrites(t, ctx, start, buf.TabularWrites, tc.expected) }) } } diff --git a/components/camera/collector.go b/components/camera/collector.go index 5079f547e46..4b6806bc3b3 100644 --- a/components/camera/collector.go +++ b/components/camera/collector.go @@ -3,10 +3,10 @@ package camera import ( "bytes" "context" + "time" "github.com/pkg/errors" "go.opencensus.io/trace" - pb "go.viam.com/api/component/camera/v1" "google.golang.org/protobuf/types/known/anypb" "google.golang.org/protobuf/types/known/wrapperspb" @@ -44,7 +44,9 @@ func newNextPointCloudCollector(resource interface{}, params data.CollectorParam return nil, err } - cFunc := data.CaptureFunc(func(ctx context.Context, _ map[string]*anypb.Any) (interface{}, error) { + cFunc := data.CaptureFunc(func(ctx context.Context, _ map[string]*anypb.Any) (data.CaptureResult, error) { + timeRequested := time.Now() + var res data.CaptureResult _, span := trace.StartSpan(ctx, "camera::data::collector::CaptureFunc::NextPointCloud") defer span.End() @@ -55,9 +57,9 @@ func newNextPointCloudCollector(resource interface{}, params data.CollectorParam // A modular filter component can be created to filter the readings from a component. The error ErrNoCaptureToStore // is used in the datamanager to exclude readings from being captured and stored. if errors.Is(err, data.ErrNoCaptureToStore) { - return nil, err + return res, err } - return nil, data.FailedToReadErr(params.ComponentName, nextPointCloud.String(), err) + return res, data.FailedToReadErr(params.ComponentName, nextPointCloud.String(), err) } var buf bytes.Buffer @@ -66,10 +68,17 @@ func newNextPointCloudCollector(resource interface{}, params data.CollectorParam buf.Grow(headerSize + v.Size()*4*4) // 4 numbers per point, each 4 bytes err = pointcloud.ToPCD(v, &buf, pointcloud.PCDBinary) if err != nil { - return nil, errors.Errorf("failed to convert returned point cloud to PCD: %v", err) + return res, errors.Errorf("failed to convert returned point cloud to PCD: %v", err) } } - return buf.Bytes(), nil + ts := data.Timestamps{ + TimeRequested: timeRequested, + TimeReceived: time.Now(), + } + return data.NewBinaryCaptureResult(ts, []data.Binary{{ + Payload: buf.Bytes(), + MimeType: data.MimeTypeApplicationPcd, + }}), nil }) return data.NewCollector(cFunc, params) } @@ -90,7 +99,9 @@ func newReadImageCollector(resource interface{}, params data.CollectorParams) (d } } - cFunc := data.CaptureFunc(func(ctx context.Context, _ map[string]*anypb.Any) (interface{}, error) { + cFunc := data.CaptureFunc(func(ctx context.Context, _ map[string]*anypb.Any) (data.CaptureResult, error) { + timeRequested := time.Now() + var res data.CaptureResult _, span := trace.StartSpan(ctx, "camera::data::collector::CaptureFunc::ReadImage") defer span.End() @@ -101,10 +112,10 @@ func newReadImageCollector(resource interface{}, params data.CollectorParams) (d // A modular filter component can be created to filter the readings from a component. The error ErrNoCaptureToStore // is used in the datamanager to exclude readings from being captured and stored. if errors.Is(err, data.ErrNoCaptureToStore) { - return nil, err + return res, err } - return nil, data.FailedToReadErr(params.ComponentName, readImage.String(), err) + return res, data.FailedToReadErr(params.ComponentName, readImage.String(), err) } defer func() { if release != nil { @@ -114,14 +125,21 @@ func newReadImageCollector(resource interface{}, params data.CollectorParams) (d mimeStr := new(wrapperspb.StringValue) if err := mimeType.UnmarshalTo(mimeStr); err != nil { - return nil, err + return res, err } outBytes, err := rimage.EncodeImage(ctx, img, mimeStr.Value) if err != nil { - return nil, err + return res, err + } + // TODO (Nick S): Add MimeType + ts := data.Timestamps{ + TimeRequested: timeRequested, + TimeReceived: time.Now(), } - return outBytes, nil + return data.NewBinaryCaptureResult(ts, []data.Binary{{ + Payload: outBytes, + }}), nil }) return data.NewCollector(cFunc, params) } @@ -131,37 +149,36 @@ func newGetImagesCollector(resource interface{}, params data.CollectorParams) (d if err != nil { return nil, err } - cFunc := data.CaptureFunc(func(ctx context.Context, _ map[string]*anypb.Any) (interface{}, error) { + cFunc := data.CaptureFunc(func(ctx context.Context, _ map[string]*anypb.Any) (data.CaptureResult, error) { + var res data.CaptureResult _, span := trace.StartSpan(ctx, "camera::data::collector::CaptureFunc::GetImages") defer span.End() - ctx = context.WithValue(ctx, data.FromDMContextKey{}, true) resImgs, resMetadata, err := camera.Images(ctx) if err != nil { if errors.Is(err, data.ErrNoCaptureToStore) { - return nil, err + return res, err } - return nil, data.FailedToReadErr(params.ComponentName, getImages.String(), err) + return res, data.FailedToReadErr(params.ComponentName, getImages.String(), err) } - var imgsConverted []*pb.Image + var binaries []data.Binary for _, img := range resImgs { format, imgBytes, err := encodeImageFromUnderlyingType(ctx, img.Image) if err != nil { - return nil, err - } - imgPb := &pb.Image{ - SourceName: img.SourceName, - Format: format, - Image: imgBytes, + return res, err } - imgsConverted = append(imgsConverted, imgPb) + binaries = append(binaries, data.Binary{ + Payload: imgBytes, + MimeType: data.CameraFormatToMimeType(format), + }) + } + ts := data.Timestamps{ + TimeRequested: resMetadata.CapturedAt, + TimeReceived: resMetadata.CapturedAt, } - return pb.GetImagesResponse{ - ResponseMetadata: resMetadata.AsProto(), - Images: imgsConverted, - }, nil + return data.NewBinaryCaptureResult(ts, binaries), nil }) return data.NewCollector(cFunc, params) } diff --git a/components/encoder/collector.go b/components/encoder/collector.go index 9ad643bc721..dc21d5653f0 100644 --- a/components/encoder/collector.go +++ b/components/encoder/collector.go @@ -3,6 +3,7 @@ package encoder import ( "context" "errors" + "time" pb "go.viam.com/api/component/encoder/v1" "google.golang.org/protobuf/types/known/anypb" @@ -31,20 +32,22 @@ func newTicksCountCollector(resource interface{}, params data.CollectorParams) ( return nil, err } - cFunc := data.CaptureFunc(func(ctx context.Context, _ map[string]*anypb.Any) (interface{}, error) { + cFunc := data.CaptureFunc(func(ctx context.Context, _ map[string]*anypb.Any) (data.CaptureResult, error) { + timeRequested := time.Now() + var res data.CaptureResult v, positionType, err := encoder.Position(ctx, PositionTypeUnspecified, data.FromDMExtraMap) if err != nil { // A modular filter component can be created to filter the readings from a component. The error ErrNoCaptureToStore // is used in the datamanager to exclude readings from being captured and stored. if errors.Is(err, data.ErrNoCaptureToStore) { - return nil, err + return res, err } - return nil, data.FailedToReadErr(params.ComponentName, ticksCount.String(), err) + return res, data.FailedToReadErr(params.ComponentName, ticksCount.String(), err) } - return pb.GetPositionResponse{ + return data.NewTabularCaptureResult(timeRequested, pb.GetPositionResponse{ Value: float32(v), PositionType: pb.PositionType(positionType), - }, nil + }) }) return data.NewCollector(cFunc, params) } diff --git a/components/encoder/collectors_test.go b/components/encoder/collectors_test.go index 05e1de16532..86246b56fd1 100644 --- a/components/encoder/collectors_test.go +++ b/components/encoder/collectors_test.go @@ -5,9 +5,11 @@ import ( "testing" "time" - clk "github.com/benbjohnson/clock" + "github.com/benbjohnson/clock" + datasyncpb "go.viam.com/api/app/datasync/v1" pb "go.viam.com/api/component/encoder/v1" "go.viam.com/test" + "google.golang.org/protobuf/types/known/structpb" "go.viam.com/rdk/components/encoder" "go.viam.com/rdk/data" @@ -17,19 +19,21 @@ import ( ) const ( - captureInterval = time.Second - numRetries = 5 + captureInterval = time.Millisecond ) -func TestEncoderCollector(t *testing.T) { - mockClock := clk.NewMock() - buf := tu.MockBuffer{} +func TestCollectors(t *testing.T) { + start := time.Now() + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + buf := tu.NewMockBuffer(ctx) params := data.CollectorParams{ + DataType: data.CaptureTypeTabular, ComponentName: "encoder", Interval: captureInterval, Logger: logging.NewTestLogger(t), - Target: &buf, - Clock: mockClock, + Target: buf, + Clock: clock.New(), } enc := newEncoder() @@ -38,18 +42,16 @@ func TestEncoderCollector(t *testing.T) { defer col.Close() col.Collect() - mockClock.Add(captureInterval) - tu.Retry(func() bool { - return buf.Length() != 0 - }, numRetries) - test.That(t, buf.Length(), test.ShouldBeGreaterThan, 0) - - test.That(t, buf.Writes[0].GetStruct().AsMap(), test.ShouldResemble, - tu.ToProtoMapIgnoreOmitEmpty(pb.GetPositionResponse{ - Value: 1.0, - PositionType: pb.PositionType_POSITION_TYPE_TICKS_COUNT, - })) + tu.CheckMockBufferWrites(t, ctx, start, buf.TabularWrites, &datasyncpb.SensorData{ + Metadata: &datasyncpb.SensorMetadata{}, + Data: &datasyncpb.SensorData_Struct{Struct: &structpb.Struct{ + Fields: map[string]*structpb.Value{ + "value": structpb.NewNumberValue(1.0), + "position_type": structpb.NewNumberValue(float64(pb.PositionType_POSITION_TYPE_TICKS_COUNT)), + }, + }}, + }) } func newEncoder() encoder.Encoder { diff --git a/components/gantry/collector.go b/components/gantry/collector.go index 149f52f5a54..ff77f6945ab 100644 --- a/components/gantry/collector.go +++ b/components/gantry/collector.go @@ -3,6 +3,7 @@ package gantry import ( "context" "errors" + "time" pb "go.viam.com/api/component/gantry/v1" "google.golang.org/protobuf/types/known/anypb" @@ -35,19 +36,21 @@ func newPositionCollector(resource interface{}, params data.CollectorParams) (da return nil, err } - cFunc := data.CaptureFunc(func(ctx context.Context, _ map[string]*anypb.Any) (interface{}, error) { + cFunc := data.CaptureFunc(func(ctx context.Context, _ map[string]*anypb.Any) (data.CaptureResult, error) { + timeRequested := time.Now() + var res data.CaptureResult v, err := gantry.Position(ctx, data.FromDMExtraMap) if err != nil { // A modular filter component can be created to filter the readings from a component. The error ErrNoCaptureToStore // is used in the datamanager to exclude readings from being captured and stored. if errors.Is(err, data.ErrNoCaptureToStore) { - return nil, err + return res, err } - return nil, data.FailedToReadErr(params.ComponentName, position.String(), err) + return res, data.FailedToReadErr(params.ComponentName, position.String(), err) } - return pb.GetPositionResponse{ + return data.NewTabularCaptureResult(timeRequested, pb.GetPositionResponse{ PositionsMm: v, - }, nil + }) }) return data.NewCollector(cFunc, params) } @@ -60,19 +63,21 @@ func newLengthsCollector(resource interface{}, params data.CollectorParams) (dat return nil, err } - cFunc := data.CaptureFunc(func(ctx context.Context, _ map[string]*anypb.Any) (interface{}, error) { + cFunc := data.CaptureFunc(func(ctx context.Context, _ map[string]*anypb.Any) (data.CaptureResult, error) { + timeRequested := time.Now() + var res data.CaptureResult v, err := gantry.Lengths(ctx, data.FromDMExtraMap) if err != nil { // A modular filter component can be created to filter the readings from a component. The error ErrNoCaptureToStore // is used in the datamanager to exclude readings from being captured and stored. if errors.Is(err, data.ErrNoCaptureToStore) { - return nil, err + return res, err } - return nil, data.FailedToReadErr(params.ComponentName, lengths.String(), err) + return res, data.FailedToReadErr(params.ComponentName, lengths.String(), err) } - return pb.GetLengthsResponse{ + return data.NewTabularCaptureResult(timeRequested, pb.GetLengthsResponse{ LengthsMm: v, - }, nil + }) }) return data.NewCollector(cFunc, params) } diff --git a/components/gantry/collectors_test.go b/components/gantry/collectors_test.go index c450b0fbbea..66c51ced423 100644 --- a/components/gantry/collectors_test.go +++ b/components/gantry/collectors_test.go @@ -5,9 +5,10 @@ import ( "testing" "time" - clk "github.com/benbjohnson/clock" - pb "go.viam.com/api/component/gantry/v1" + "github.com/benbjohnson/clock" + datasyncpb "go.viam.com/api/app/datasync/v1" "go.viam.com/test" + "google.golang.org/protobuf/types/known/structpb" "go.viam.com/rdk/components/gantry" "go.viam.com/rdk/data" @@ -18,45 +19,60 @@ import ( const ( componentName = "gantry" - captureInterval = time.Second - numRetries = 5 + captureInterval = time.Millisecond ) // floatList is a lit of floats in units of millimeters. var floatList = []float64{1000, 2000, 3000} -func TestGantryCollectors(t *testing.T) { +func TestCollectors(t *testing.T) { + l, err := structpb.NewList([]any{1000, 2000, 3000}) + test.That(t, err, test.ShouldBeNil) + tests := []struct { name string collector data.CollectorConstructor - expected map[string]any + expected *datasyncpb.SensorData }{ { name: "Length collector should write a lengths response", collector: gantry.NewLengthsCollector, - expected: tu.ToProtoMapIgnoreOmitEmpty(pb.GetLengthsResponse{ - LengthsMm: floatList, - }), + expected: &datasyncpb.SensorData{ + Metadata: &datasyncpb.SensorMetadata{}, + Data: &datasyncpb.SensorData_Struct{Struct: &structpb.Struct{ + Fields: map[string]*structpb.Value{ + "lengths_mm": structpb.NewListValue(l), + }, + }}, + }, }, { name: "Position collector should write a list of positions", collector: gantry.NewPositionCollector, - expected: tu.ToProtoMapIgnoreOmitEmpty(pb.GetPositionResponse{ - PositionsMm: floatList, - }), + expected: &datasyncpb.SensorData{ + Metadata: &datasyncpb.SensorMetadata{}, + Data: &datasyncpb.SensorData_Struct{Struct: &structpb.Struct{ + Fields: map[string]*structpb.Value{ + "positions_mm": structpb.NewListValue(l), + }, + }}, + }, }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { - mockClock := clk.NewMock() - buf := tu.MockBuffer{} + start := time.Now() + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + buf := tu.NewMockBuffer(ctx) params := data.CollectorParams{ + DataType: data.CaptureTypeTabular, ComponentName: componentName, Interval: captureInterval, Logger: logging.NewTestLogger(t), - Clock: mockClock, - Target: &buf, + Clock: clock.New(), + Target: buf, } gantry := newGantry() @@ -65,13 +81,8 @@ func TestGantryCollectors(t *testing.T) { defer col.Close() col.Collect() - mockClock.Add(captureInterval) - tu.Retry(func() bool { - return buf.Length() != 0 - }, numRetries) - test.That(t, buf.Length(), test.ShouldBeGreaterThan, 0) - test.That(t, buf.Writes[0].GetStruct().AsMap(), test.ShouldResemble, tc.expected) + tu.CheckMockBufferWrites(t, ctx, start, buf.TabularWrites, tc.expected) }) } } diff --git a/components/motor/collector.go b/components/motor/collector.go index 1d328d7b36c..89401f87d56 100644 --- a/components/motor/collector.go +++ b/components/motor/collector.go @@ -3,6 +3,7 @@ package motor import ( "context" "errors" + "time" pb "go.viam.com/api/component/motor/v1" "google.golang.org/protobuf/types/known/anypb" @@ -35,19 +36,21 @@ func newPositionCollector(resource interface{}, params data.CollectorParams) (da return nil, err } - cFunc := data.CaptureFunc(func(ctx context.Context, _ map[string]*anypb.Any) (interface{}, error) { + cFunc := data.CaptureFunc(func(ctx context.Context, _ map[string]*anypb.Any) (data.CaptureResult, error) { + timeRequested := time.Now() + var res data.CaptureResult v, err := motor.Position(ctx, data.FromDMExtraMap) if err != nil { // A modular filter component can be created to filter the readings from a component. The error ErrNoCaptureToStore // is used in the datamanager to exclude readings from being captured and stored. if errors.Is(err, data.ErrNoCaptureToStore) { - return nil, err + return res, err } - return nil, data.FailedToReadErr(params.ComponentName, position.String(), err) + return res, data.FailedToReadErr(params.ComponentName, position.String(), err) } - return pb.GetPositionResponse{ + return data.NewTabularCaptureResult(timeRequested, pb.GetPositionResponse{ Position: v, - }, nil + }) }) return data.NewCollector(cFunc, params) } @@ -60,20 +63,22 @@ func newIsPoweredCollector(resource interface{}, params data.CollectorParams) (d return nil, err } - cFunc := data.CaptureFunc(func(ctx context.Context, _ map[string]*anypb.Any) (interface{}, error) { + cFunc := data.CaptureFunc(func(ctx context.Context, _ map[string]*anypb.Any) (data.CaptureResult, error) { + timeRequested := time.Now() + var res data.CaptureResult v, powerPct, err := motor.IsPowered(ctx, data.FromDMExtraMap) if err != nil { // A modular filter component can be created to filter the readings from a component. The error ErrNoCaptureToStore // is used in the datamanager to exclude readings from being captured and stored. if errors.Is(err, data.ErrNoCaptureToStore) { - return nil, err + return res, err } - return nil, data.FailedToReadErr(params.ComponentName, isPowered.String(), err) + return res, data.FailedToReadErr(params.ComponentName, isPowered.String(), err) } - return pb.IsPoweredResponse{ + return data.NewTabularCaptureResult(timeRequested, pb.IsPoweredResponse{ IsOn: v, PowerPct: powerPct, - }, nil + }) }) return data.NewCollector(cFunc, params) } diff --git a/components/motor/collectors_test.go b/components/motor/collectors_test.go index f80baf75095..4f48da9e185 100644 --- a/components/motor/collectors_test.go +++ b/components/motor/collectors_test.go @@ -5,9 +5,10 @@ import ( "testing" "time" - clk "github.com/benbjohnson/clock" - pb "go.viam.com/api/component/motor/v1" + "github.com/benbjohnson/clock" + datasyncpb "go.viam.com/api/app/datasync/v1" "go.viam.com/test" + "google.golang.org/protobuf/types/known/structpb" "go.viam.com/rdk/components/motor" "go.viam.com/rdk/data" @@ -18,43 +19,55 @@ import ( const ( componentName = "motor" - captureInterval = time.Second - numRetries = 5 + captureInterval = time.Millisecond ) -func TestMotorCollectors(t *testing.T) { +func TestCollectors(t *testing.T) { tests := []struct { name string collector data.CollectorConstructor - expected map[string]any + expected *datasyncpb.SensorData }{ { name: "Motor position collector should write a position response", collector: motor.NewPositionCollector, - expected: tu.ToProtoMapIgnoreOmitEmpty(pb.GetPositionResponse{ - Position: 1.0, - }), + expected: &datasyncpb.SensorData{ + Metadata: &datasyncpb.SensorMetadata{}, + Data: &datasyncpb.SensorData_Struct{Struct: &structpb.Struct{ + Fields: map[string]*structpb.Value{ + "position": structpb.NewNumberValue(1.0), + }, + }}, + }, }, { name: "Motor isPowered collector should write an isPowered response", collector: motor.NewIsPoweredCollector, - expected: tu.ToProtoMapIgnoreOmitEmpty(pb.IsPoweredResponse{ - IsOn: false, - PowerPct: .5, - }), + expected: &datasyncpb.SensorData{ + Metadata: &datasyncpb.SensorMetadata{}, + Data: &datasyncpb.SensorData_Struct{Struct: &structpb.Struct{ + Fields: map[string]*structpb.Value{ + "is_on": structpb.NewBoolValue(false), + "power_pct": structpb.NewNumberValue(0.5), + }, + }}, + }, }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { - mockClock := clk.NewMock() - buf := tu.MockBuffer{} + start := time.Now() + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + buf := tu.NewMockBuffer(ctx) params := data.CollectorParams{ + DataType: data.CaptureTypeTabular, ComponentName: componentName, Interval: captureInterval, Logger: logging.NewTestLogger(t), - Clock: mockClock, - Target: &buf, + Clock: clock.New(), + Target: buf, } motor := newMotor() @@ -63,13 +76,8 @@ func TestMotorCollectors(t *testing.T) { defer col.Close() col.Collect() - mockClock.Add(captureInterval) - tu.Retry(func() bool { - return buf.Length() != 0 - }, numRetries) - test.That(t, buf.Length(), test.ShouldBeGreaterThan, 0) - test.That(t, buf.Writes[0].GetStruct().AsMap(), test.ShouldResemble, tc.expected) + tu.CheckMockBufferWrites(t, ctx, start, buf.TabularWrites, tc.expected) }) } } diff --git a/components/movementsensor/collector.go b/components/movementsensor/collector.go index 2d985d26a3f..a7bc23e772a 100644 --- a/components/movementsensor/collector.go +++ b/components/movementsensor/collector.go @@ -3,10 +3,12 @@ package movementsensor import ( "context" "errors" + "time" v1 "go.viam.com/api/common/v1" pb "go.viam.com/api/component/movementsensor/v1" "google.golang.org/protobuf/types/known/anypb" + "google.golang.org/protobuf/types/known/structpb" "go.viam.com/rdk/data" "go.viam.com/rdk/protoutils" @@ -53,6 +55,7 @@ func assertMovementSensor(resource interface{}) (MovementSensor, error) { return ms, nil } +// nolint: dupl // newLinearVelocityCollector returns a collector to register a linear velocity method. If one is already registered // with the same MethodMetadata it will panic. func newLinearVelocityCollector(resource interface{}, params data.CollectorParams) (data.Collector, error) { @@ -61,23 +64,25 @@ func newLinearVelocityCollector(resource interface{}, params data.CollectorParam return nil, err } - cFunc := data.CaptureFunc(func(ctx context.Context, extra map[string]*anypb.Any) (interface{}, error) { + cFunc := data.CaptureFunc(func(ctx context.Context, extra map[string]*anypb.Any) (data.CaptureResult, error) { + timeRequested := time.Now() + var res data.CaptureResult vec, err := ms.LinearVelocity(ctx, data.FromDMExtraMap) if err != nil { // A modular filter component can be created to filter the readings from a component. The error ErrNoCaptureToStore // is used in the datamanager to exclude readings from being captured and stored. if errors.Is(err, data.ErrNoCaptureToStore) { - return nil, err + return res, err } - return nil, data.FailedToReadErr(params.ComponentName, position.String(), err) + return res, data.FailedToReadErr(params.ComponentName, position.String(), err) } - return pb.GetLinearVelocityResponse{ + return data.NewTabularCaptureResult(timeRequested, pb.GetLinearVelocityResponse{ LinearVelocity: &v1.Vector3{ X: vec.X, Y: vec.Y, Z: vec.Z, }, - }, nil + }) }) return data.NewCollector(cFunc, params) } @@ -90,32 +95,35 @@ func newPositionCollector(resource interface{}, params data.CollectorParams) (da return nil, err } - cFunc := data.CaptureFunc(func(ctx context.Context, extra map[string]*anypb.Any) (interface{}, error) { + cFunc := data.CaptureFunc(func(ctx context.Context, extra map[string]*anypb.Any) (data.CaptureResult, error) { + timeRequested := time.Now() + var res data.CaptureResult pos, altitude, err := ms.Position(ctx, data.FromDMExtraMap) if err != nil { // A modular filter component can be created to filter the readings from a component. The error ErrNoCaptureToStore // is used in the datamanager to exclude readings from being captured and stored. if errors.Is(err, data.ErrNoCaptureToStore) { - return nil, err + return res, err } - return nil, data.FailedToReadErr(params.ComponentName, linearVelocity.String(), err) + return res, data.FailedToReadErr(params.ComponentName, linearVelocity.String(), err) } var lat, lng float64 if pos != nil { lat = pos.Lat() lng = pos.Lng() } - return pb.GetPositionResponse{ + return data.NewTabularCaptureResult(timeRequested, pb.GetPositionResponse{ Coordinate: &v1.GeoPoint{ Latitude: lat, Longitude: lng, }, AltitudeM: float32(altitude), - }, nil + }) }) return data.NewCollector(cFunc, params) } +// nolint: dupl // newAngularVelocityCollector returns a collector to register an angular velocity method. If one is already registered // with the same MethodMetadata it will panic. func newAngularVelocityCollector(resource interface{}, params data.CollectorParams) (data.Collector, error) { @@ -124,23 +132,25 @@ func newAngularVelocityCollector(resource interface{}, params data.CollectorPara return nil, err } - cFunc := data.CaptureFunc(func(ctx context.Context, extra map[string]*anypb.Any) (interface{}, error) { + cFunc := data.CaptureFunc(func(ctx context.Context, extra map[string]*anypb.Any) (data.CaptureResult, error) { + timeRequested := time.Now() + var res data.CaptureResult vel, err := ms.AngularVelocity(ctx, data.FromDMExtraMap) if err != nil { // A modular filter component can be created to filter the readings from a component. The error ErrNoCaptureToStore // is used in the datamanager to exclude readings from being captured and stored. if errors.Is(err, data.ErrNoCaptureToStore) { - return nil, err + return res, err } - return nil, data.FailedToReadErr(params.ComponentName, angularVelocity.String(), err) + return res, data.FailedToReadErr(params.ComponentName, angularVelocity.String(), err) } - return pb.GetAngularVelocityResponse{ + return data.NewTabularCaptureResult(timeRequested, pb.GetAngularVelocityResponse{ AngularVelocity: &v1.Vector3{ X: vel.X, Y: vel.Y, Z: vel.Z, }, - }, nil + }) }) return data.NewCollector(cFunc, params) } @@ -153,23 +163,26 @@ func newCompassHeadingCollector(resource interface{}, params data.CollectorParam return nil, err } - cFunc := data.CaptureFunc(func(ctx context.Context, extra map[string]*anypb.Any) (interface{}, error) { + cFunc := data.CaptureFunc(func(ctx context.Context, extra map[string]*anypb.Any) (data.CaptureResult, error) { + timeRequested := time.Now() + var res data.CaptureResult heading, err := ms.CompassHeading(ctx, data.FromDMExtraMap) if err != nil { // A modular filter component can be created to filter the readings from a component. The error ErrNoCaptureToStore // is used in the datamanager to exclude readings from being captured and stored. if errors.Is(err, data.ErrNoCaptureToStore) { - return nil, err + return res, err } - return nil, data.FailedToReadErr(params.ComponentName, compassHeading.String(), err) + return res, data.FailedToReadErr(params.ComponentName, compassHeading.String(), err) } - return pb.GetCompassHeadingResponse{ + return data.NewTabularCaptureResult(timeRequested, pb.GetCompassHeadingResponse{ Value: heading, - }, nil + }) }) return data.NewCollector(cFunc, params) } +// nolint: dupl // newLinearAccelerationCollector returns a collector to register a linear acceleration method. If one is already registered // with the same MethodMetadata it will panic. func newLinearAccelerationCollector(resource interface{}, params data.CollectorParams) (data.Collector, error) { @@ -178,23 +191,25 @@ func newLinearAccelerationCollector(resource interface{}, params data.CollectorP return nil, err } - cFunc := data.CaptureFunc(func(ctx context.Context, extra map[string]*anypb.Any) (interface{}, error) { + cFunc := data.CaptureFunc(func(ctx context.Context, extra map[string]*anypb.Any) (data.CaptureResult, error) { + timeRequested := time.Now() + var res data.CaptureResult accel, err := ms.LinearAcceleration(ctx, data.FromDMExtraMap) if err != nil { // A modular filter component can be created to filter the readings from a component. The error ErrNoCaptureToStore // is used in the datamanager to exclude readings from being captured and stored. if errors.Is(err, data.ErrNoCaptureToStore) { - return nil, err + return res, err } - return nil, data.FailedToReadErr(params.ComponentName, linearAcceleration.String(), err) + return res, data.FailedToReadErr(params.ComponentName, linearAcceleration.String(), err) } - return pb.GetLinearAccelerationResponse{ + return data.NewTabularCaptureResult(timeRequested, pb.GetLinearAccelerationResponse{ LinearAcceleration: &v1.Vector3{ X: accel.X, Y: accel.Y, Z: accel.Z, }, - }, nil + }) }) return data.NewCollector(cFunc, params) } @@ -207,28 +222,30 @@ func newOrientationCollector(resource interface{}, params data.CollectorParams) return nil, err } - cFunc := data.CaptureFunc(func(ctx context.Context, extra map[string]*anypb.Any) (interface{}, error) { + cFunc := data.CaptureFunc(func(ctx context.Context, extra map[string]*anypb.Any) (data.CaptureResult, error) { + timeRequested := time.Now() + var res data.CaptureResult orient, err := ms.Orientation(ctx, data.FromDMExtraMap) if err != nil { // A modular filter component can be created to filter the readings from a component. The error ErrNoCaptureToStore // is used in the datamanager to exclude readings from being captured and stored. if errors.Is(err, data.ErrNoCaptureToStore) { - return nil, err + return res, err } - return nil, data.FailedToReadErr(params.ComponentName, orientation.String(), err) + return res, data.FailedToReadErr(params.ComponentName, orientation.String(), err) } var orientVector *spatialmath.OrientationVectorDegrees if orient != nil { orientVector = orient.OrientationVectorDegrees() } - return pb.GetOrientationResponse{ + return data.NewTabularCaptureResult(timeRequested, pb.GetOrientationResponse{ Orientation: &v1.Orientation{ OX: orientVector.OX, OY: orientVector.OY, OZ: orientVector.OZ, Theta: orientVector.Theta, }, - }, nil + }) }) return data.NewCollector(cFunc, params) } @@ -241,22 +258,31 @@ func newReadingsCollector(resource interface{}, params data.CollectorParams) (da return nil, err } - cFunc := data.CaptureFunc(func(ctx context.Context, arg map[string]*anypb.Any) (interface{}, error) { + cFunc := data.CaptureFunc(func(ctx context.Context, arg map[string]*anypb.Any) (data.CaptureResult, error) { + var res data.CaptureResult values, err := ms.Readings(ctx, data.FromDMExtraMap) if err != nil { // A modular filter component can be created to filter the readings from a component. The error ErrNoCaptureToStore // is used in the datamanager to exclude readings from being captured and stored. if errors.Is(err, data.ErrNoCaptureToStore) { - return nil, err + return res, err } - return nil, data.FailedToReadErr(params.ComponentName, readings.String(), err) + return res, data.FailedToReadErr(params.ComponentName, readings.String(), err) } readings, err := protoutils.ReadingGoToProto(values) if err != nil { - return nil, err + return res, err } - return v1.GetReadingsResponse{ - Readings: readings, + + return data.CaptureResult{ + Type: data.CaptureTypeTabular, + TabularData: data.TabularData{ + Payload: &structpb.Struct{ + Fields: map[string]*structpb.Value{ + "readings": structpb.NewStructValue(&structpb.Struct{Fields: readings}), + }, + }, + }, }, nil }) return data.NewCollector(cFunc, params) diff --git a/components/movementsensor/collectors_test.go b/components/movementsensor/collectors_test.go index 38b77e21526..639c0474d98 100644 --- a/components/movementsensor/collectors_test.go +++ b/components/movementsensor/collectors_test.go @@ -5,16 +5,15 @@ import ( "testing" "time" - clk "github.com/benbjohnson/clock" + "github.com/benbjohnson/clock" "github.com/golang/geo/r3" geo "github.com/kellydunn/golang-geo" - v1 "go.viam.com/api/common/v1" - pb "go.viam.com/api/component/movementsensor/v1" + datasyncpb "go.viam.com/api/app/datasync/v1" "go.viam.com/test" + "google.golang.org/protobuf/types/known/structpb" "go.viam.com/rdk/components/movementsensor" "go.viam.com/rdk/data" - du "go.viam.com/rdk/data/testutils" "go.viam.com/rdk/logging" "go.viam.com/rdk/spatialmath" tu "go.viam.com/rdk/testutils" @@ -23,8 +22,7 @@ import ( const ( componentName = "movementsensor" - captureInterval = time.Second - numRetries = 5 + captureInterval = time.Millisecond ) var vec = r3.Vector{ @@ -35,75 +33,147 @@ var vec = r3.Vector{ var readingMap = map[string]any{"reading1": false, "reading2": "test"} -func TestMovementSensorCollectors(t *testing.T) { +func TestCollectors(t *testing.T) { tests := []struct { name string collector data.CollectorConstructor - expected map[string]any + expected *datasyncpb.SensorData }{ { name: "Movement sensor linear velocity collector should write a velocity response", collector: movementsensor.NewLinearVelocityCollector, - expected: tu.ToProtoMapIgnoreOmitEmpty(pb.GetLinearVelocityResponse{ - LinearVelocity: r3VectorToV1Vector(vec), - }), + expected: &datasyncpb.SensorData{ + Metadata: &datasyncpb.SensorMetadata{}, + Data: &datasyncpb.SensorData_Struct{Struct: &structpb.Struct{ + Fields: map[string]*structpb.Value{ + "linear_velocity": structpb.NewStructValue(&structpb.Struct{ + Fields: map[string]*structpb.Value{ + "x": structpb.NewNumberValue(1.0), + "y": structpb.NewNumberValue(2.0), + "z": structpb.NewNumberValue(3.0), + }, + }), + }, + }}, + }, }, { name: "Movement sensor position collector should write a position response", collector: movementsensor.NewPositionCollector, - expected: tu.ToProtoMapIgnoreOmitEmpty(pb.GetPositionResponse{ - Coordinate: &v1.GeoPoint{ - Latitude: 1.0, - Longitude: 2.0, - }, - AltitudeM: 3.0, - }), + expected: &datasyncpb.SensorData{ + Metadata: &datasyncpb.SensorMetadata{}, + Data: &datasyncpb.SensorData_Struct{Struct: &structpb.Struct{ + Fields: map[string]*structpb.Value{ + "coordinate": structpb.NewStructValue(&structpb.Struct{ + Fields: map[string]*structpb.Value{ + "latitude": structpb.NewNumberValue(1.0), + "longitude": structpb.NewNumberValue(2.0), + }, + }), + "altitude_m": structpb.NewNumberValue(3.0), + }, + }}, + }, }, { name: "Movement sensor angular velocity collector should write a velocity response", collector: movementsensor.NewAngularVelocityCollector, - expected: tu.ToProtoMapIgnoreOmitEmpty(pb.GetAngularVelocityResponse{ - AngularVelocity: r3VectorToV1Vector(vec), - }), + expected: &datasyncpb.SensorData{ + Metadata: &datasyncpb.SensorMetadata{}, + Data: &datasyncpb.SensorData_Struct{Struct: &structpb.Struct{ + Fields: map[string]*structpb.Value{ + "angular_velocity": structpb.NewStructValue(&structpb.Struct{ + Fields: map[string]*structpb.Value{ + "x": structpb.NewNumberValue(1.0), + "y": structpb.NewNumberValue(2.0), + "z": structpb.NewNumberValue(3.0), + }, + }), + }, + }}, + }, }, { name: "Movement sensor compass heading collector should write a heading response", collector: movementsensor.NewCompassHeadingCollector, - expected: tu.ToProtoMapIgnoreOmitEmpty(pb.GetCompassHeadingResponse{ - Value: 1.0, - }), + expected: &datasyncpb.SensorData{ + Metadata: &datasyncpb.SensorMetadata{}, + Data: &datasyncpb.SensorData_Struct{Struct: &structpb.Struct{ + Fields: map[string]*structpb.Value{ + "value": structpb.NewNumberValue(1.0), + }, + }}, + }, }, { name: "Movement sensor linear acceleration collector should write an acceleration response", collector: movementsensor.NewLinearAccelerationCollector, - expected: tu.ToProtoMapIgnoreOmitEmpty(pb.GetLinearAccelerationResponse{ - LinearAcceleration: r3VectorToV1Vector(vec), - }), + expected: &datasyncpb.SensorData{ + Metadata: &datasyncpb.SensorMetadata{}, + Data: &datasyncpb.SensorData_Struct{Struct: &structpb.Struct{ + Fields: map[string]*structpb.Value{ + "linear_acceleration": structpb.NewStructValue(&structpb.Struct{ + Fields: map[string]*structpb.Value{ + "x": structpb.NewNumberValue(1.0), + "y": structpb.NewNumberValue(2.0), + "z": structpb.NewNumberValue(3.0), + }, + }), + }, + }}, + }, }, { name: "Movement sensor orientation collector should write an orientation response", collector: movementsensor.NewOrientationCollector, - expected: tu.ToProtoMapIgnoreOmitEmpty(pb.GetOrientationResponse{ - Orientation: getExpectedOrientation(), - }), + expected: &datasyncpb.SensorData{ + Metadata: &datasyncpb.SensorMetadata{}, + Data: &datasyncpb.SensorData_Struct{Struct: &structpb.Struct{ + Fields: map[string]*structpb.Value{ + "orientation": structpb.NewStructValue(&structpb.Struct{ + Fields: map[string]*structpb.Value{ + "o_x": structpb.NewNumberValue(0), + "o_y": structpb.NewNumberValue(0), + "o_z": structpb.NewNumberValue(1), + "theta": structpb.NewNumberValue(0), + }, + }), + }, + }}, + }, }, { name: "Movement sensor readings collector should write a readings response", collector: movementsensor.NewReadingsCollector, - expected: tu.ToProtoMapIgnoreOmitEmpty(du.GetExpectedReadingsStruct(readingMap).AsMap()), + expected: &datasyncpb.SensorData{ + Metadata: &datasyncpb.SensorMetadata{}, + Data: &datasyncpb.SensorData_Struct{Struct: &structpb.Struct{ + Fields: map[string]*structpb.Value{ + "readings": structpb.NewStructValue(&structpb.Struct{ + Fields: map[string]*structpb.Value{ + "reading1": structpb.NewBoolValue(false), + "reading2": structpb.NewStringValue("test"), + }, + }), + }, + }}, + }, }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { - mockClock := clk.NewMock() - buf := tu.MockBuffer{} + start := time.Now() + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + buf := tu.NewMockBuffer(ctx) params := data.CollectorParams{ + DataType: data.CaptureTypeTabular, ComponentName: componentName, Interval: captureInterval, Logger: logging.NewTestLogger(t), - Clock: mockClock, - Target: &buf, + Clock: clock.New(), + Target: buf, } movSens := newMovementSensor() @@ -112,13 +182,8 @@ func TestMovementSensorCollectors(t *testing.T) { defer col.Close() col.Collect() - mockClock.Add(captureInterval) - tu.Retry(func() bool { - return buf.Length() != 0 - }, numRetries) - test.That(t, buf.Length(), test.ShouldBeGreaterThan, 0) - test.That(t, buf.Writes[0].GetStruct().AsMap(), test.ShouldResemble, tc.expected) + tu.CheckMockBufferWrites(t, ctx, start, buf.TabularWrites, tc.expected) }) } } @@ -152,21 +217,3 @@ func newMovementSensor() movementsensor.MovementSensor { } return m } - -func r3VectorToV1Vector(vec r3.Vector) *v1.Vector3 { - return &v1.Vector3{ - X: vec.X, - Y: vec.Y, - Z: vec.Z, - } -} - -func getExpectedOrientation() *v1.Orientation { - convertedAngles := spatialmath.NewZeroOrientation().AxisAngles() - return &v1.Orientation{ - OX: convertedAngles.RX, - OY: convertedAngles.RY, - OZ: convertedAngles.RZ, - Theta: convertedAngles.Theta, - } -} diff --git a/components/powersensor/collector.go b/components/powersensor/collector.go index 89dc0badefa..4d9b9cc2aa8 100644 --- a/components/powersensor/collector.go +++ b/components/powersensor/collector.go @@ -4,9 +4,10 @@ import ( "context" "errors" - v1 "go.viam.com/api/common/v1" pb "go.viam.com/api/component/powersensor/v1" + uprotoutils "go.viam.com/utils/protoutils" "google.golang.org/protobuf/types/known/anypb" + "google.golang.org/protobuf/types/known/structpb" "go.viam.com/rdk/data" "go.viam.com/rdk/protoutils" @@ -43,6 +44,7 @@ func assertPowerSensor(resource interface{}) (PowerSensor, error) { return ps, nil } +//nolint: dupl // newVoltageCollector returns a collector to register a voltage method. If one is already registered // with the same MethodMetadata it will panic. func newVoltageCollector(resource interface{}, params data.CollectorParams) (data.Collector, error) { @@ -51,24 +53,34 @@ func newVoltageCollector(resource interface{}, params data.CollectorParams) (dat return nil, err } - cFunc := data.CaptureFunc(func(ctx context.Context, extra map[string]*anypb.Any) (interface{}, error) { + cFunc := data.CaptureFunc(func(ctx context.Context, extra map[string]*anypb.Any) (data.CaptureResult, error) { + var res data.CaptureResult volts, isAc, err := ps.Voltage(ctx, data.FromDMExtraMap) if err != nil { // A modular filter component can be created to filter the readings from a component. The error ErrNoCaptureToStore // is used in the datamanager to exclude readings from being captured and stored. if errors.Is(err, data.ErrNoCaptureToStore) { - return nil, err + return res, err } - return nil, data.FailedToReadErr(params.ComponentName, voltage.String(), err) + return res, data.FailedToReadErr(params.ComponentName, voltage.String(), err) } - return pb.GetVoltageResponse{ + + pbReading, err := uprotoutils.StructToStructPbIgnoreOmitEmpty(pb.GetVoltageResponse{ Volts: volts, IsAc: isAc, + }) + if err != nil { + return res, err + } + return data.CaptureResult{ + Type: data.CaptureTypeTabular, + TabularData: data.TabularData{Payload: pbReading}, }, nil }) return data.NewCollector(cFunc, params) } +//nolint: dupl // newCurrentCollector returns a collector to register a current method. If one is already registered // with the same MethodMetadata it will panic. func newCurrentCollector(resource interface{}, params data.CollectorParams) (data.Collector, error) { @@ -77,19 +89,27 @@ func newCurrentCollector(resource interface{}, params data.CollectorParams) (dat return nil, err } - cFunc := data.CaptureFunc(func(ctx context.Context, extra map[string]*anypb.Any) (interface{}, error) { + cFunc := data.CaptureFunc(func(ctx context.Context, extra map[string]*anypb.Any) (data.CaptureResult, error) { + var res data.CaptureResult curr, isAc, err := ps.Current(ctx, data.FromDMExtraMap) if err != nil { // A modular filter component can be created to filter the readings from a component. The error ErrNoCaptureToStore // is used in the datamanager to exclude readings from being captured and stored. if errors.Is(err, data.ErrNoCaptureToStore) { - return nil, err + return res, err } - return nil, data.FailedToReadErr(params.ComponentName, current.String(), err) + return res, data.FailedToReadErr(params.ComponentName, current.String(), err) } - return pb.GetCurrentResponse{ + pbReading, err := uprotoutils.StructToStructPbIgnoreOmitEmpty(pb.GetCurrentResponse{ Amperes: curr, IsAc: isAc, + }) + if err != nil { + return res, err + } + return data.CaptureResult{ + Type: data.CaptureTypeTabular, + TabularData: data.TabularData{Payload: pbReading}, }, nil }) return data.NewCollector(cFunc, params) @@ -103,18 +123,26 @@ func newPowerCollector(resource interface{}, params data.CollectorParams) (data. return nil, err } - cFunc := data.CaptureFunc(func(ctx context.Context, extra map[string]*anypb.Any) (interface{}, error) { + cFunc := data.CaptureFunc(func(ctx context.Context, extra map[string]*anypb.Any) (data.CaptureResult, error) { + var res data.CaptureResult pwr, err := ps.Power(ctx, data.FromDMExtraMap) if err != nil { // A modular filter component can be created to filter the readings from a component. The error ErrNoCaptureToStore // is used in the datamanager to exclude readings from being captured and stored. if errors.Is(err, data.ErrNoCaptureToStore) { - return nil, err + return res, err } - return nil, data.FailedToReadErr(params.ComponentName, power.String(), err) + return res, data.FailedToReadErr(params.ComponentName, power.String(), err) } - return pb.GetPowerResponse{ + pbReading, err := uprotoutils.StructToStructPbIgnoreOmitEmpty(pb.GetPowerResponse{ Watts: pwr, + }) + if err != nil { + return res, err + } + return data.CaptureResult{ + Type: data.CaptureTypeTabular, + TabularData: data.TabularData{Payload: pbReading}, }, nil }) return data.NewCollector(cFunc, params) @@ -128,22 +156,30 @@ func newReadingsCollector(resource interface{}, params data.CollectorParams) (da return nil, err } - cFunc := data.CaptureFunc(func(ctx context.Context, arg map[string]*anypb.Any) (interface{}, error) { + cFunc := data.CaptureFunc(func(ctx context.Context, arg map[string]*anypb.Any) (data.CaptureResult, error) { + var res data.CaptureResult values, err := ps.Readings(ctx, data.FromDMExtraMap) if err != nil { // A modular filter component can be created to filter the readings from a component. The error ErrNoCaptureToStore // is used in the datamanager to exclude readings from being captured and stored. if errors.Is(err, data.ErrNoCaptureToStore) { - return nil, err + return res, err } - return nil, data.FailedToReadErr(params.ComponentName, readings.String(), err) + return res, data.FailedToReadErr(params.ComponentName, readings.String(), err) } readings, err := protoutils.ReadingGoToProto(values) if err != nil { - return nil, err + return res, err } - return v1.GetReadingsResponse{ - Readings: readings, + return data.CaptureResult{ + Type: data.CaptureTypeTabular, + TabularData: data.TabularData{ + Payload: &structpb.Struct{ + Fields: map[string]*structpb.Value{ + "readings": structpb.NewStructValue(&structpb.Struct{Fields: readings}), + }, + }, + }, }, nil }) return data.NewCollector(cFunc, params) diff --git a/components/powersensor/collectors_test.go b/components/powersensor/collectors_test.go index 79dbd754738..915137e0be8 100644 --- a/components/powersensor/collectors_test.go +++ b/components/powersensor/collectors_test.go @@ -5,13 +5,13 @@ import ( "testing" "time" - clk "github.com/benbjohnson/clock" - pb "go.viam.com/api/component/powersensor/v1" + "github.com/benbjohnson/clock" + datasyncpb "go.viam.com/api/app/datasync/v1" "go.viam.com/test" + "google.golang.org/protobuf/types/known/structpb" "go.viam.com/rdk/components/powersensor" "go.viam.com/rdk/data" - du "go.viam.com/rdk/data/testutils" "go.viam.com/rdk/logging" tu "go.viam.com/rdk/testutils" "go.viam.com/rdk/testutils/inject" @@ -19,58 +19,88 @@ import ( const ( componentName = "powersensor" - captureInterval = time.Second - numRetries = 5 + captureInterval = time.Millisecond ) var readingMap = map[string]any{"reading1": false, "reading2": "test"} -func TestPowerSensorCollectors(t *testing.T) { +func TestCollectors(t *testing.T) { tests := []struct { name string collector data.CollectorConstructor - expected map[string]any + expected *datasyncpb.SensorData }{ { name: "Power sensor voltage collector should write a voltage response", collector: powersensor.NewVoltageCollector, - expected: tu.ToProtoMapIgnoreOmitEmpty(pb.GetVoltageResponse{ - Volts: 1.0, - IsAc: false, - }), + expected: &datasyncpb.SensorData{ + Metadata: &datasyncpb.SensorMetadata{}, + Data: &datasyncpb.SensorData_Struct{Struct: &structpb.Struct{ + Fields: map[string]*structpb.Value{ + "volts": structpb.NewNumberValue(1.0), + "is_ac": structpb.NewBoolValue(false), + }, + }}, + }, }, { name: "Power sensor current collector should write a current response", collector: powersensor.NewCurrentCollector, - expected: tu.ToProtoMapIgnoreOmitEmpty(pb.GetCurrentResponse{ - Amperes: 1.0, - IsAc: false, - }), + expected: &datasyncpb.SensorData{ + Metadata: &datasyncpb.SensorMetadata{}, + Data: &datasyncpb.SensorData_Struct{Struct: &structpb.Struct{ + Fields: map[string]*structpb.Value{ + "amperes": structpb.NewNumberValue(1.0), + "is_ac": structpb.NewBoolValue(false), + }, + }}, + }, }, { name: "Power sensor power collector should write a power response", collector: powersensor.NewPowerCollector, - expected: tu.ToProtoMapIgnoreOmitEmpty(pb.GetPowerResponse{ - Watts: 1.0, - }), + expected: &datasyncpb.SensorData{ + Metadata: &datasyncpb.SensorMetadata{}, + Data: &datasyncpb.SensorData_Struct{Struct: &structpb.Struct{ + Fields: map[string]*structpb.Value{ + "watts": structpb.NewNumberValue(1.0), + }, + }}, + }, }, { name: "Power sensor readings collector should write a readings response", collector: powersensor.NewReadingsCollector, - expected: tu.ToProtoMapIgnoreOmitEmpty(du.GetExpectedReadingsStruct(readingMap).AsMap()), + // expected: tu.ToProtoMapIgnoreOmitEmpty(du.GetExpectedReadingsStruct(readingMap).AsMap()), + expected: &datasyncpb.SensorData{ + Metadata: &datasyncpb.SensorMetadata{}, + Data: &datasyncpb.SensorData_Struct{Struct: &structpb.Struct{ + Fields: map[string]*structpb.Value{ + "readings": structpb.NewStructValue(&structpb.Struct{ + Fields: map[string]*structpb.Value{ + "reading1": structpb.NewBoolValue(false), + "reading2": structpb.NewStringValue("test"), + }, + }), + }, + }}, + }, }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { - mockClock := clk.NewMock() - buf := tu.MockBuffer{} + start := time.Now() + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + buf := tu.NewMockBuffer(ctx) params := data.CollectorParams{ + DataType: data.CaptureTypeTabular, ComponentName: componentName, Interval: captureInterval, Logger: logging.NewTestLogger(t), - Clock: mockClock, - Target: &buf, + Clock: clock.New(), + Target: buf, } pwrSens := newPowerSensor() @@ -79,13 +109,8 @@ func TestPowerSensorCollectors(t *testing.T) { defer col.Close() col.Collect() - mockClock.Add(captureInterval) - tu.Retry(func() bool { - return buf.Length() != 0 - }, numRetries) - test.That(t, buf.Length(), test.ShouldBeGreaterThan, 0) - test.That(t, buf.Writes[0].GetStruct().AsMap(), test.ShouldResemble, tc.expected) + tu.CheckMockBufferWrites(t, ctx, start, buf.TabularWrites, tc.expected) }) } } diff --git a/components/sensor/collector.go b/components/sensor/collector.go index 07f34e6880a..29a9973db5a 100644 --- a/components/sensor/collector.go +++ b/components/sensor/collector.go @@ -3,12 +3,11 @@ package sensor import ( "context" "errors" + "time" - pb "go.viam.com/api/common/v1" "google.golang.org/protobuf/types/known/anypb" "go.viam.com/rdk/data" - "go.viam.com/rdk/protoutils" ) type method int64 @@ -32,23 +31,20 @@ func newReadingsCollector(resource interface{}, params data.CollectorParams) (da return nil, err } - cFunc := data.CaptureFunc(func(ctx context.Context, arg map[string]*anypb.Any) (interface{}, error) { + cFunc := data.CaptureFunc(func(ctx context.Context, arg map[string]*anypb.Any) (data.CaptureResult, error) { + timeRequested := time.Now() + var res data.CaptureResult values, err := sensorResource.Readings(ctx, data.FromDMExtraMap) if err != nil { // A modular filter component can be created to filter the readings from a component. The error ErrNoCaptureToStore // is used in the datamanager to exclude readings from being captured and stored. if errors.Is(err, data.ErrNoCaptureToStore) { - return nil, err + return res, err } - return nil, data.FailedToReadErr(params.ComponentName, readings.String(), err) + return res, data.FailedToReadErr(params.ComponentName, readings.String(), err) } - readings, err := protoutils.ReadingGoToProto(values) - if err != nil { - return nil, err - } - return pb.GetReadingsResponse{ - Readings: readings, - }, nil + + return data.NewTabularCaptureResultReadings(timeRequested, values) }) return data.NewCollector(cFunc, params) } diff --git a/components/sensor/collector_test.go b/components/sensor/collector_test.go index 752f256c1e3..30f42856c96 100644 --- a/components/sensor/collector_test.go +++ b/components/sensor/collector_test.go @@ -5,33 +5,36 @@ import ( "testing" "time" - clk "github.com/benbjohnson/clock" + "github.com/benbjohnson/clock" + datasyncpb "go.viam.com/api/app/datasync/v1" "go.viam.com/test" + "google.golang.org/protobuf/types/known/structpb" "go.viam.com/rdk/components/sensor" "go.viam.com/rdk/data" - du "go.viam.com/rdk/data/testutils" "go.viam.com/rdk/logging" tu "go.viam.com/rdk/testutils" "go.viam.com/rdk/testutils/inject" ) const ( - captureInterval = time.Second - numRetries = 5 + captureInterval = time.Millisecond ) var readingMap = map[string]any{"reading1": false, "reading2": "test"} -func TestSensorCollector(t *testing.T) { - mockClock := clk.NewMock() - buf := tu.MockBuffer{} +func TestCollectors(t *testing.T) { + start := time.Now() + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + buf := tu.NewMockBuffer(ctx) params := data.CollectorParams{ + DataType: data.CaptureTypeTabular, ComponentName: "sensor", Interval: captureInterval, Logger: logging.NewTestLogger(t), - Target: &buf, - Clock: mockClock, + Target: buf, + Clock: clock.New(), } sens := newSensor() @@ -40,13 +43,20 @@ func TestSensorCollector(t *testing.T) { defer col.Close() col.Collect() - mockClock.Add(captureInterval) - tu.Retry(func() bool { - return buf.Length() != 0 - }, numRetries) - test.That(t, buf.Length(), test.ShouldBeGreaterThan, 0) - test.That(t, buf.Writes[0].GetStruct().AsMap(), test.ShouldResemble, du.GetExpectedReadingsStruct(readingMap).AsMap()) + tu.CheckMockBufferWrites(t, ctx, start, buf.TabularWrites, &datasyncpb.SensorData{ + Metadata: &datasyncpb.SensorMetadata{}, + Data: &datasyncpb.SensorData_Struct{Struct: &structpb.Struct{ + Fields: map[string]*structpb.Value{ + "readings": structpb.NewStructValue(&structpb.Struct{ + Fields: map[string]*structpb.Value{ + "reading1": structpb.NewBoolValue(false), + "reading2": structpb.NewStringValue("test"), + }, + }), + }, + }}, + }) } func newSensor() sensor.Sensor { diff --git a/components/servo/collector.go b/components/servo/collector.go index b073b122fde..bcdf420e91e 100644 --- a/components/servo/collector.go +++ b/components/servo/collector.go @@ -5,6 +5,7 @@ import ( "errors" pb "go.viam.com/api/component/servo/v1" + uprotoutils "go.viam.com/utils/protoutils" "google.golang.org/protobuf/types/known/anypb" "go.viam.com/rdk/data" @@ -31,18 +32,28 @@ func newPositionCollector(resource interface{}, params data.CollectorParams) (da return nil, err } - cFunc := data.CaptureFunc(func(ctx context.Context, _ map[string]*anypb.Any) (interface{}, error) { + cFunc := data.CaptureFunc(func(ctx context.Context, _ map[string]*anypb.Any) (data.CaptureResult, error) { + var res data.CaptureResult pos, err := servo.Position(ctx, data.FromDMExtraMap) if err != nil { // A modular filter component can be created to filter the readings from a component. The error ErrNoCaptureToStore // is used in the datamanager to exclude readings from being captured and stored. if errors.Is(err, data.ErrNoCaptureToStore) { - return nil, err + return res, err } - return nil, data.FailedToReadErr(params.ComponentName, position.String(), err) + return res, data.FailedToReadErr(params.ComponentName, position.String(), err) } - return pb.GetPositionResponse{ + readings, err := uprotoutils.StructToStructPbIgnoreOmitEmpty(pb.GetPositionResponse{ PositionDeg: pos, + }) + if err != nil { + return res, err + } + return data.CaptureResult{ + Type: data.CaptureTypeTabular, + TabularData: data.TabularData{ + Payload: readings, + }, }, nil }) return data.NewCollector(cFunc, params) diff --git a/components/servo/collectors_test.go b/components/servo/collectors_test.go index 6e253f730ef..c49b666818d 100644 --- a/components/servo/collectors_test.go +++ b/components/servo/collectors_test.go @@ -5,9 +5,10 @@ import ( "testing" "time" - clk "github.com/benbjohnson/clock" - pb "go.viam.com/api/component/servo/v1" + "github.com/benbjohnson/clock" + datasyncpb "go.viam.com/api/app/datasync/v1" "go.viam.com/test" + "google.golang.org/protobuf/types/known/structpb" "go.viam.com/rdk/components/servo" "go.viam.com/rdk/data" @@ -17,19 +18,21 @@ import ( ) const ( - captureInterval = time.Second - numRetries = 5 + captureInterval = time.Millisecond ) -func TestServoCollector(t *testing.T) { - mockClock := clk.NewMock() - buf := tu.MockBuffer{} +func TestCollectors(t *testing.T) { + start := time.Now() + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + buf := tu.NewMockBuffer(ctx) params := data.CollectorParams{ + DataType: data.CaptureTypeTabular, ComponentName: "servo", Interval: captureInterval, Logger: logging.NewTestLogger(t), - Target: &buf, - Clock: mockClock, + Target: buf, + Clock: clock.New(), } serv := newServo() @@ -38,16 +41,15 @@ func TestServoCollector(t *testing.T) { defer col.Close() col.Collect() - mockClock.Add(captureInterval) - - tu.Retry(func() bool { - return buf.Length() != 0 - }, numRetries) - test.That(t, buf.Length(), test.ShouldBeGreaterThan, 0) - test.That(t, buf.Writes[0].GetStruct().AsMap(), test.ShouldResemble, - tu.ToProtoMapIgnoreOmitEmpty(pb.GetPositionResponse{ - PositionDeg: 1.0, - })) + + tu.CheckMockBufferWrites(t, ctx, start, buf.TabularWrites, &datasyncpb.SensorData{ + Metadata: &datasyncpb.SensorMetadata{}, + Data: &datasyncpb.SensorData_Struct{Struct: &structpb.Struct{ + Fields: map[string]*structpb.Value{ + "position_deg": structpb.NewNumberValue(1.0), + }, + }}, + }) } func newServo() servo.Servo { diff --git a/data/capture_buffer.go b/data/capture_buffer.go index 93edc43025e..781b7098115 100644 --- a/data/capture_buffer.go +++ b/data/capture_buffer.go @@ -6,11 +6,10 @@ import ( v1 "go.viam.com/api/app/datasync/v1" ) -const captureAllFromCamera = "CaptureAllFromCamera" - // CaptureBufferedWriter is a buffered, persistent queue of SensorData. type CaptureBufferedWriter interface { - Write(item *v1.SensorData) error + WriteBinary(items []*v1.SensorData) error + WriteTabular(items []*v1.SensorData) error Flush() error Path() string } @@ -33,28 +32,39 @@ func NewCaptureBuffer(dir string, md *v1.DataCaptureMetadata, maxCaptureFileSize } } -// Write writes item onto b. Binary sensor data is written to its own file. -// Tabular data is written to disk in maxCaptureFileSize sized files. Files that -// are still being written to are indicated with the extension -// InProgressFileExt. Files that have finished being written to are indicated by -// FileExt. -func (b *CaptureBuffer) Write(item *v1.SensorData) error { +// WriteBinary writes the items to their own file. +// Files that are still being written to are indicated with the extension +// '.prog'. +// Files that have finished being written to are indicated by +// '.capture'. +func (b *CaptureBuffer) WriteBinary(items []*v1.SensorData) error { b.lock.Lock() defer b.lock.Unlock() - if item.GetBinary() != nil { - binFile, err := NewCaptureFile(b.Directory, b.MetaData) - if err != nil { - return err - } + binFile, err := NewCaptureFile(b.Directory, b.MetaData) + if err != nil { + return err + } + for _, item := range items { if err := binFile.WriteNext(item); err != nil { return err } - if err := binFile.Close(); err != nil { - return err - } - return nil } + if err := binFile.Close(); err != nil { + return err + } + return nil +} + +// WriteTabular writes +// Tabular data to disk in maxCaptureFileSize sized files. +// Files that are still being written to are indicated with the extension +// '.prog'. +// Files that have finished being written to are indicated by +// '.capture'. +func (b *CaptureBuffer) WriteTabular(items []*v1.SensorData) error { + b.lock.Lock() + defer b.lock.Unlock() if b.nextFile == nil { nextFile, err := NewCaptureFile(b.Directory, b.MetaData) @@ -62,10 +72,7 @@ func (b *CaptureBuffer) Write(item *v1.SensorData) error { return err } b.nextFile = nextFile - // We want to special case on "CaptureAllFromCamera" because it is sensor data that contains images - // and their corresponding annotations. We want each image and its annotations to be stored in a - // separate file. - } else if b.nextFile.Size() > b.maxCaptureFileSize || b.MetaData.MethodName == captureAllFromCamera { + } else if b.nextFile.Size() > b.maxCaptureFileSize { if err := b.nextFile.Close(); err != nil { return err } @@ -76,7 +83,13 @@ func (b *CaptureBuffer) Write(item *v1.SensorData) error { b.nextFile = nextFile } - return b.nextFile.WriteNext(item) + for _, item := range items { + if err := b.nextFile.WriteNext(item); err != nil { + return err + } + } + + return nil } // Flush flushes all buffered data to disk and marks any in progress file as complete. diff --git a/data/capture_buffer_test.go b/data/capture_buffer_test.go index a89f85b0c60..8098a240b63 100644 --- a/data/capture_buffer_test.go +++ b/data/capture_buffer_test.go @@ -1,7 +1,6 @@ package data import ( - "errors" "io" "os" "path/filepath" @@ -85,16 +84,19 @@ func TestCaptureQueue(t *testing.T) { tmpDir := t.TempDir() md := &v1.DataCaptureMetadata{Type: tc.dataType} sut := NewCaptureBuffer(tmpDir, md, int64(maxFileSize)) - var pushValue *v1.SensorData - if tc.dataType == v1.DataType_DATA_TYPE_BINARY_SENSOR { - pushValue = binarySensorData - } else { - pushValue = structSensorData - } for i := 0; i < tc.pushCount; i++ { - err := sut.Write(pushValue) - test.That(t, err, test.ShouldBeNil) + switch { + case tc.dataType == CaptureTypeBinary.ToProto(): + err := sut.WriteBinary([]*v1.SensorData{binarySensorData}) + test.That(t, err, test.ShouldBeNil) + case tc.dataType == CaptureTypeTabular.ToProto(): + err := sut.WriteTabular([]*v1.SensorData{structSensorData}) + test.That(t, err, test.ShouldBeNil) + default: + t.Error("unknown data type") + t.FailNow() + } } dcFiles, inProgressFiles := getCaptureFiles(tmpDir) @@ -221,7 +223,7 @@ func TestCaptureBufferReader(t *testing.T) { methodParams, err := rprotoutils.ConvertStringMapToAnyPBMap(tc.additionalParams) test.That(t, err, test.ShouldBeNil) - readImageCaptureMetadata := BuildCaptureMetadata( + readImageCaptureMetadata, _ := BuildCaptureMetadata( tc.resourceName.API, tc.resourceName.ShortName(), tc.methodName, @@ -248,7 +250,7 @@ func TestCaptureBufferReader(t *testing.T) { now := time.Now() timeRequested := timestamppb.New(now.UTC()) timeReceived := timestamppb.New(now.Add(time.Millisecond).UTC()) - msg := &v1.SensorData{ + msg := []*v1.SensorData{{ Metadata: &v1.SensorMetadata{ TimeRequested: timeRequested, TimeReceived: timeReceived, @@ -256,8 +258,8 @@ func TestCaptureBufferReader(t *testing.T) { Data: &v1.SensorData_Struct{ Struct: tc.readings[0], }, - } - test.That(t, b.Write(msg), test.ShouldBeNil) + }} + test.That(t, b.WriteTabular(msg), test.ShouldBeNil) test.That(t, b.Flush(), test.ShouldBeNil) dirEntries, err := os.ReadDir(b.Path()) test.That(t, err, test.ShouldBeNil) @@ -273,7 +275,7 @@ func TestCaptureBufferReader(t *testing.T) { sd, err := cf.ReadNext() test.That(t, err, test.ShouldBeNil) - test.That(t, sd, test.ShouldResemble, msg) + test.That(t, sd, test.ShouldResemble, msg[0]) _, err = cf.ReadNext() test.That(t, err, test.ShouldBeError, io.EOF) @@ -281,7 +283,7 @@ func TestCaptureBufferReader(t *testing.T) { now = time.Now() timeRequested = timestamppb.New(now.UTC()) timeReceived = timestamppb.New(now.Add(time.Millisecond).UTC()) - msg2 := &v1.SensorData{ + msg2 := []*v1.SensorData{{ Metadata: &v1.SensorMetadata{ TimeRequested: timeRequested, TimeReceived: timeReceived, @@ -289,13 +291,13 @@ func TestCaptureBufferReader(t *testing.T) { Data: &v1.SensorData_Struct{ Struct: tc.readings[1], }, - } - test.That(t, b.Write(msg2), test.ShouldBeNil) + }} + test.That(t, b.WriteTabular(msg2), test.ShouldBeNil) now = time.Now() timeRequested = timestamppb.New(now.UTC()) timeReceived = timestamppb.New(now.Add(time.Millisecond).UTC()) - msg3 := &v1.SensorData{ + msg3 := []*v1.SensorData{{ Metadata: &v1.SensorMetadata{ TimeRequested: timeRequested, TimeReceived: timeReceived, @@ -303,8 +305,8 @@ func TestCaptureBufferReader(t *testing.T) { Data: &v1.SensorData_Struct{ Struct: tc.readings[2], }, - } - test.That(t, b.Write(msg3), test.ShouldBeNil) + }} + test.That(t, b.WriteTabular(msg3), test.ShouldBeNil) dirEntries2, err := os.ReadDir(b.Path()) test.That(t, err, test.ShouldBeNil) @@ -341,11 +343,11 @@ func TestCaptureBufferReader(t *testing.T) { sd2, err := cf2.ReadNext() test.That(t, err, test.ShouldBeNil) - test.That(t, sd2, test.ShouldResemble, msg2) + test.That(t, sd2, test.ShouldResemble, msg2[0]) sd3, err := cf2.ReadNext() test.That(t, err, test.ShouldBeNil) - test.That(t, sd3, test.ShouldResemble, msg3) + test.That(t, sd3, test.ShouldResemble, msg3[0]) _, err = cf2.ReadNext() test.That(t, err, test.ShouldBeError, io.EOF) @@ -426,7 +428,7 @@ func TestCaptureBufferReader(t *testing.T) { methodParams, err := rprotoutils.ConvertStringMapToAnyPBMap(tc.additionalParams) test.That(t, err, test.ShouldBeNil) - readImageCaptureMetadata := BuildCaptureMetadata( + readImageCaptureMetadata, _ := BuildCaptureMetadata( tc.resourceName.API, tc.resourceName.ShortName(), tc.methodName, @@ -456,32 +458,17 @@ func TestCaptureBufferReader(t *testing.T) { test.That(t, err, test.ShouldBeNil) test.That(t, firstDirEntries, test.ShouldBeEmpty) - // writing empty sensor data returns an error - test.That(t, b.Write(nil), test.ShouldBeError, errors.New("proto: Marshal called with nil")) - // flushing after this error occures, behaves the same as if no write had occurred // current behavior is likely a bug test.That(t, b.Flush(), test.ShouldBeNil) firstDirEntries, err = os.ReadDir(b.Path()) test.That(t, err, test.ShouldBeNil) - test.That(t, len(firstDirEntries), test.ShouldEqual, 1) - test.That(t, filepath.Ext(firstDirEntries[0].Name()), test.ShouldResemble, CompletedCaptureFileExt) - f, err := os.Open(filepath.Join(b.Path(), firstDirEntries[0].Name())) - test.That(t, err, test.ShouldBeNil) - defer func() { utils.UncheckedError(f.Close()) }() - - cf, err := ReadCaptureFile(f) - test.That(t, err, test.ShouldBeNil) - test.That(t, cf.ReadMetadata(), test.ShouldResemble, readImageCaptureMetadata) - - sd, err := cf.ReadNext() - test.That(t, err, test.ShouldBeError, io.EOF) - test.That(t, sd, test.ShouldBeNil) + test.That(t, len(firstDirEntries), test.ShouldEqual, 0) now := time.Now() timeRequested := timestamppb.New(now.UTC()) timeReceived := timestamppb.New(now.Add(time.Millisecond).UTC()) - msg := &v1.SensorData{ + msg := []*v1.SensorData{{ Metadata: &v1.SensorMetadata{ TimeRequested: timeRequested, TimeReceived: timeReceived, @@ -489,19 +476,13 @@ func TestCaptureBufferReader(t *testing.T) { Data: &v1.SensorData_Binary{ Binary: []byte("this is fake binary data"), }, - } - test.That(t, b.Write(msg), test.ShouldBeNil) + }} + test.That(t, b.WriteBinary(msg), test.ShouldBeNil) test.That(t, b.Flush(), test.ShouldBeNil) secondDirEntries, err := os.ReadDir(b.Path()) test.That(t, err, test.ShouldBeNil) - test.That(t, len(secondDirEntries), test.ShouldEqual, 2) - var newFileName string - for _, de := range secondDirEntries { - if de.Name() != firstDirEntries[0].Name() { - newFileName = de.Name() - break - } - } + test.That(t, len(secondDirEntries), test.ShouldEqual, 1) + newFileName := secondDirEntries[0].Name() test.That(t, newFileName, test.ShouldNotBeEmpty) test.That(t, filepath.Ext(newFileName), test.ShouldResemble, CompletedCaptureFileExt) f2, err := os.Open(filepath.Join(b.Path(), newFileName)) @@ -514,14 +495,14 @@ func TestCaptureBufferReader(t *testing.T) { sd2, err := cf2.ReadNext() test.That(t, err, test.ShouldBeNil) - test.That(t, sd2, test.ShouldResemble, msg) + test.That(t, sd2, test.ShouldResemble, msg[0]) _, err = cf2.ReadNext() test.That(t, err, test.ShouldBeError, io.EOF) timeRequested = timestamppb.New(now.UTC()) timeReceived = timestamppb.New(now.Add(time.Millisecond).UTC()) - msg3 := &v1.SensorData{ + msg3 := []*v1.SensorData{{ Metadata: &v1.SensorMetadata{ TimeRequested: timeRequested, TimeReceived: timeReceived, @@ -529,13 +510,13 @@ func TestCaptureBufferReader(t *testing.T) { Data: &v1.SensorData_Binary{ Binary: []byte("msg2"), }, - } + }} - test.That(t, b.Write(msg3), test.ShouldBeNil) + test.That(t, b.WriteBinary(msg3), test.ShouldBeNil) timeRequested = timestamppb.New(now.UTC()) timeReceived = timestamppb.New(now.Add(time.Millisecond).UTC()) - msg4 := &v1.SensorData{ + msg4 := []*v1.SensorData{{ Metadata: &v1.SensorMetadata{ TimeRequested: timeRequested, TimeReceived: timeReceived, @@ -543,17 +524,17 @@ func TestCaptureBufferReader(t *testing.T) { Data: &v1.SensorData_Binary{ Binary: []byte("msg3"), }, - } + }} // Every binary data written becomes a new data capture file - test.That(t, b.Write(msg4), test.ShouldBeNil) + test.That(t, b.WriteBinary(msg4), test.ShouldBeNil) test.That(t, b.Flush(), test.ShouldBeNil) thirdDirEntries, err := os.ReadDir(b.Path()) test.That(t, err, test.ShouldBeNil) - test.That(t, len(thirdDirEntries), test.ShouldEqual, 4) + test.That(t, len(thirdDirEntries), test.ShouldEqual, 3) var newFileNames []string for _, de := range thirdDirEntries { - if de.Name() != firstDirEntries[0].Name() && de.Name() != newFileName { + if de.Name() != newFileName { newFileNames = append(newFileNames, de.Name()) } } @@ -568,7 +549,7 @@ func TestCaptureBufferReader(t *testing.T) { test.That(t, cf3.ReadMetadata(), test.ShouldResemble, readImageCaptureMetadata) sd3, err := cf3.ReadNext() test.That(t, err, test.ShouldBeNil) - test.That(t, sd3, test.ShouldResemble, msg3) + test.That(t, sd3, test.ShouldResemble, msg3[0]) _, err = cf3.ReadNext() test.That(t, err, test.ShouldBeError, io.EOF) @@ -581,7 +562,7 @@ func TestCaptureBufferReader(t *testing.T) { test.That(t, cf4.ReadMetadata(), test.ShouldResemble, readImageCaptureMetadata) sd4, err := cf4.ReadNext() test.That(t, err, test.ShouldBeNil) - test.That(t, sd4, test.ShouldResemble, msg4) + test.That(t, sd4, test.ShouldResemble, msg4[0]) _, err = cf4.ReadNext() test.That(t, err, test.ShouldBeError, io.EOF) }) @@ -597,7 +578,7 @@ func TestCaptureBufferReader(t *testing.T) { methodParams, err := rprotoutils.ConvertStringMapToAnyPBMap(additionalParams) test.That(t, err, test.ShouldBeNil) - readImageCaptureMetadata := BuildCaptureMetadata( + readImageCaptureMetadata, _ := BuildCaptureMetadata( name.API, name.ShortName(), method, @@ -625,7 +606,7 @@ func TestCaptureBufferReader(t *testing.T) { now := time.Now() timeRequested := timestamppb.New(now.UTC()) timeReceived := timestamppb.New(now.Add(time.Millisecond).UTC()) - msg := &v1.SensorData{ + msg := []*v1.SensorData{{ Metadata: &v1.SensorMetadata{ TimeRequested: timeRequested, TimeReceived: timeReceived, @@ -633,8 +614,8 @@ func TestCaptureBufferReader(t *testing.T) { Data: &v1.SensorData_Binary{ Binary: []byte("this is a fake image"), }, - } - test.That(t, b.Write(msg), test.ShouldBeNil) + }} + test.That(t, b.WriteBinary(msg), test.ShouldBeNil) test.That(t, b.Flush(), test.ShouldBeNil) dirEntries, err := os.ReadDir(b.Path()) test.That(t, err, test.ShouldBeNil) @@ -650,7 +631,7 @@ func TestCaptureBufferReader(t *testing.T) { sd2, err := cf2.ReadNext() test.That(t, err, test.ShouldBeNil) - test.That(t, sd2, test.ShouldResemble, msg) + test.That(t, sd2, test.ShouldResemble, msg[0]) _, err = cf2.ReadNext() test.That(t, err, test.ShouldBeError, io.EOF) diff --git a/data/capture_file.go b/data/capture_file.go index 47b118e9926..df8e9b5309b 100644 --- a/data/capture_file.go +++ b/data/capture_file.go @@ -216,17 +216,17 @@ func BuildCaptureMetadata( additionalParams map[string]string, methodParams map[string]*anypb.Any, tags []string, -) *v1.DataCaptureMetadata { - dataType := getDataType(method) +) (*v1.DataCaptureMetadata, CaptureType) { + dataType := GetDataType(method) return &v1.DataCaptureMetadata{ ComponentType: compAPI.String(), ComponentName: compName, MethodName: method, - Type: dataType, + Type: dataType.ToProto(), MethodParameters: methodParams, - FileExtension: GetFileExt(dataType, method, additionalParams), + FileExtension: getFileExt(dataType, method, additionalParams), Tags: tags, - } + }, dataType } // IsDataCaptureFile returns whether or not f is a data capture file. @@ -240,25 +240,49 @@ func getFileTimestampName() string { return time.Now().Format(time.RFC3339Nano) } -// TODO DATA-246: Implement this in some more robust, programmatic way. -func getDataType(methodName string) v1.DataType { +// CaptureType represents captured tabular or binary data. +type CaptureType int + +const ( + // CaptureTypeUnspecified represents that the data type of the captured data was not specified. + CaptureTypeUnspecified CaptureType = iota + // CaptureTypeTabular represents that the data type of the captured data is tabular. + CaptureTypeTabular + // CaptureTypeBinary represents that the data type of the captured data is binary. + CaptureTypeBinary +) + +// ToProto converts a DataType into a v1.DataType. +func (dt CaptureType) ToProto() v1.DataType { + switch dt { + case CaptureTypeTabular: + return v1.DataType_DATA_TYPE_TABULAR_SENSOR + case CaptureTypeBinary: + return v1.DataType_DATA_TYPE_BINARY_SENSOR + case CaptureTypeUnspecified: + return v1.DataType_DATA_TYPE_UNSPECIFIED + default: + return v1.DataType_DATA_TYPE_UNSPECIFIED + } +} + +// GetDataType returns the DataType of the method. +func GetDataType(methodName string) CaptureType { switch methodName { case nextPointCloud, readImage, pointCloudMap, GetImages: - return v1.DataType_DATA_TYPE_BINARY_SENSOR + return CaptureTypeBinary default: - return v1.DataType_DATA_TYPE_TABULAR_SENSOR + return CaptureTypeTabular } } -// GetFileExt gets the file extension for a capture file. -func GetFileExt(dataType v1.DataType, methodName string, parameters map[string]string) string { +// getFileExt gets the file extension for a capture file. +func getFileExt(dataType CaptureType, methodName string, parameters map[string]string) string { defaultFileExt := "" switch dataType { - case v1.DataType_DATA_TYPE_TABULAR_SENSOR: + case CaptureTypeTabular: return ".dat" - case v1.DataType_DATA_TYPE_FILE: - return defaultFileExt - case v1.DataType_DATA_TYPE_BINARY_SENSOR: + case CaptureTypeBinary: if methodName == nextPointCloud { return ".pcd" } @@ -275,7 +299,7 @@ func GetFileExt(dataType v1.DataType, methodName string, parameters map[string]s return defaultFileExt } } - case v1.DataType_DATA_TYPE_UNSPECIFIED: + case CaptureTypeUnspecified: return defaultFileExt default: return defaultFileExt diff --git a/data/capture_file_test.go b/data/capture_file_test.go index 3de5756a88c..1f220bb5c76 100644 --- a/data/capture_file_test.go +++ b/data/capture_file_test.go @@ -125,7 +125,7 @@ func TestBuildCaptureMetadata(t *testing.T) { methodParams, err := protoutils.ConvertStringMapToAnyPBMap(tc.additionalParams) test.That(t, err, test.ShouldEqual, nil) - actualMetadata := BuildCaptureMetadata( + actualMetadata, _ := BuildCaptureMetadata( resource.APINamespaceRDK.WithComponentType(tc.componentType), tc.componentName, tc.method, diff --git a/data/collector.go b/data/collector.go index 7070141147e..c6d4d1a7437 100644 --- a/data/collector.go +++ b/data/collector.go @@ -5,7 +5,6 @@ package data import ( "context" "fmt" - "reflect" "sync" "time" @@ -14,8 +13,9 @@ import ( "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/mongo" "go.opencensus.io/trace" - v1 "go.viam.com/api/app/datasync/v1" - pb "go.viam.com/api/common/v1" + dataPB "go.viam.com/api/app/data/v1" + datasyncPB "go.viam.com/api/app/datasync/v1" + camerapb "go.viam.com/api/component/camera/v1" "go.viam.com/utils" "go.viam.com/utils/protoutils" "google.golang.org/grpc/codes" @@ -25,6 +25,7 @@ import ( "google.golang.org/protobuf/types/known/timestamppb" "go.viam.com/rdk/logging" + rprotoutils "go.viam.com/rdk/protoutils" "go.viam.com/rdk/resource" ) @@ -32,7 +33,264 @@ import ( var sleepCaptureCutoff = 2 * time.Millisecond // CaptureFunc allows the creation of simple Capturers with anonymous functions. -type CaptureFunc func(ctx context.Context, params map[string]*anypb.Any) (interface{}, error) +type CaptureFunc func(ctx context.Context, params map[string]*anypb.Any) (CaptureResult, error) + +// Timestamps are the timestamps the data was captured. +type Timestamps struct { + // TimeRequested represents the time the request for the data was started + TimeRequested time.Time + // TimeRequested represents the time the response for the request for the data + // was received + TimeReceived time.Time +} + +// MimeType represents the mime type of the sensor data. +type MimeType int + +// This follows the mime types supported in +// https://github.com/viamrobotics/api/pull/571/files#diff-b77927298d8d5d5228beeea47bd0860d9b322b4f3ef45e129bc238ec17704826R75 +const ( + // MimeTypeUnspecified means that the mime type was not specified. + MimeTypeUnspecified MimeType = iota + // MimeTypeImageJpeg means that the mime type is jpeg. + MimeTypeImageJpeg + // MimeTypeImagePng means that the mime type is png. + MimeTypeImagePng + // MimeTypeApplicationPcd means that the mime type is pcd. + MimeTypeApplicationPcd +) + +// CaptureResult is the result of a capture function. +type CaptureResult struct { + Timestamps + // Type represents the type of result (binary or tabular) + Type CaptureType + // TabularData contains the tabular data payload when Type == CaptureResultTypeTabular + TabularData TabularData + // Binaries contains binary data responses when Type == CaptureResultTypeBinary + Binaries []Binary +} + +// Binary represents an element of a binary capture result response. +type Binary struct { + // Payload contains the binary payload + Payload []byte + // MimeType descibes the payload's MimeType + MimeType MimeType + // Annotations provide metadata about the Payload + Annotations Annotations +} + +// ToProto converts MimeType to datasyncPB. +func (mt MimeType) ToProto() datasyncPB.MimeType { + switch mt { + case MimeTypeUnspecified: + return datasyncPB.MimeType_MIME_TYPE_UNSPECIFIED + case MimeTypeImageJpeg: + return datasyncPB.MimeType_MIME_TYPE_IMAGE_JPEG + case MimeTypeImagePng: + return datasyncPB.MimeType_MIME_TYPE_IMAGE_PNG + case MimeTypeApplicationPcd: + return datasyncPB.MimeType_MIME_TYPE_APPLICATION_PCD + default: + return datasyncPB.MimeType_MIME_TYPE_UNSPECIFIED + } +} + +// TabularData contains a tabular data payload. +type TabularData struct { + Payload *structpb.Struct +} + +// BoundingBox represents a labeled bounding box +// with an optional confidence interval between 0 and 1. +type BoundingBox struct { + Label string + Confidence *float64 + XMinNormalized float64 + YMinNormalized float64 + XMaxNormalized float64 + YMaxNormalized float64 +} + +// Classification represents a labeled classification +// with an optional confidence interval between 0 and 1. +type Classification struct { + Label string + Confidence *float64 +} + +// Annotations represents ML classifications. +type Annotations struct { + BoundingBoxes []BoundingBox + Classifications []Classification +} + +// ToProto converts Annotations to *dataPB.Annotations. +func (mt Annotations) ToProto() *dataPB.Annotations { + var bboxes []*dataPB.BoundingBox + for _, bb := range mt.BoundingBoxes { + bboxes = append(bboxes, &dataPB.BoundingBox{ + Label: bb.Label, + Confidence: bb.Confidence, + XMinNormalized: bb.XMinNormalized, + XMaxNormalized: bb.XMaxNormalized, + YMinNormalized: bb.YMinNormalized, + YMaxNormalized: bb.YMaxNormalized, + }) + } + + var classifications []*dataPB.Classification + for _, c := range mt.Classifications { + classifications = append(classifications, &dataPB.Classification{ + Label: c.Label, + Confidence: c.Confidence, + }) + } + + return &dataPB.Annotations{ + Bboxes: bboxes, + Classifications: classifications, + } +} + +// Validate returns an error if the *CaptureResult is invalid. +func (cr *CaptureResult) Validate() error { + var ts Timestamps + if cr.Timestamps.TimeRequested == ts.TimeRequested { + return errors.New("Timestamps.TimeRequested must be set") + } + + if cr.Timestamps.TimeReceived == ts.TimeReceived { + return errors.New("Timestamps.TimeRequested must be set") + } + + switch cr.Type { + case CaptureTypeTabular: + if len(cr.Binaries) > 0 { + return errors.New("tabular result can't contain binary data") + } + if cr.TabularData.Payload == nil { + return errors.New("tabular result must have non empty tabular data") + } + return nil + case CaptureTypeBinary: + if cr.TabularData.Payload != nil { + return errors.New("binary result can't contain tabular data") + } + if len(cr.Binaries) == 0 { + return errors.New("binary result must have non empty binary data") + } + + for _, b := range cr.Binaries { + if len(b.Payload) == 0 { + return errors.New("binary results can't have empty binary payload") + } + } + return nil + case CaptureTypeUnspecified: + return fmt.Errorf("unknown CaptureResultType: %d", cr.Type) + default: + return fmt.Errorf("unknown CaptureResultType: %d", cr.Type) + } +} + +// NewBinaryCaptureResult returns a binary capture result. +func NewBinaryCaptureResult(ts Timestamps, binaries []Binary) CaptureResult { + return CaptureResult{ + Timestamps: ts, + Type: CaptureTypeBinary, + Binaries: binaries, + } +} + +// NewTabularCaptureResultReadings returns a tabular readings result. +func NewTabularCaptureResultReadings(reqT time.Time, readings map[string]interface{}) (CaptureResult, error) { + var res CaptureResult + values, err := rprotoutils.ReadingGoToProto(readings) + if err != nil { + return res, err + } + + return CaptureResult{ + Timestamps: Timestamps{ + TimeRequested: reqT, + TimeReceived: time.Now(), + }, + Type: CaptureTypeTabular, + TabularData: TabularData{ + Payload: &structpb.Struct{ + Fields: map[string]*structpb.Value{ + "readings": structpb.NewStructValue(&structpb.Struct{Fields: values}), + }, + }, + }, + }, nil +} + +// NewTabularCaptureResult returns a tabular result. +func NewTabularCaptureResult(reqT time.Time, i interface{}) (CaptureResult, error) { + var res CaptureResult + readings, err := protoutils.StructToStructPbIgnoreOmitEmpty(i) + if err != nil { + return res, err + } + + return CaptureResult{ + Timestamps: Timestamps{ + TimeRequested: reqT, + TimeReceived: time.Now(), + }, + Type: CaptureTypeTabular, + TabularData: TabularData{ + Payload: readings, + }, + }, nil +} + +// ToProto converts a CaptureResult into a []*datasyncPB.SensorData{}. +func (cr CaptureResult) ToProto() []*datasyncPB.SensorData { + ts := cr.Timestamps + if td := cr.TabularData.Payload; td != nil { + return []*datasyncPB.SensorData{{ + Metadata: &datasyncPB.SensorMetadata{ + TimeRequested: timestamppb.New(ts.TimeRequested.UTC()), + TimeReceived: timestamppb.New(ts.TimeReceived.UTC()), + }, + Data: &datasyncPB.SensorData_Struct{ + Struct: td, + }, + }} + } + + var sd []*datasyncPB.SensorData + for _, b := range cr.Binaries { + sd = append(sd, &datasyncPB.SensorData{ + Metadata: &datasyncPB.SensorMetadata{ + TimeRequested: timestamppb.New(ts.TimeRequested.UTC()), + TimeReceived: timestamppb.New(ts.TimeReceived.UTC()), + MimeType: b.MimeType.ToProto(), + Annotations: b.Annotations.ToProto(), + }, + Data: &datasyncPB.SensorData_Binary{ + Binary: b.Payload, + }, + }) + } + return sd +} + +// CameraFormatToMimeType converts a camera camerapb.Format into a MimeType. +func CameraFormatToMimeType(f camerapb.Format) MimeType { + if f == camerapb.Format_FORMAT_JPEG { + return MimeTypeImageJpeg + } + + if f == camerapb.Format_FORMAT_PNG { + return MimeTypeImagePng + } + return MimeTypeUnspecified +} // FromDMContextKey is used to check whether the context is from data management. // Deprecated: use a camera.Extra with camera.NewContext instead. @@ -58,9 +316,9 @@ type Collector interface { } type collector struct { - clock clock.Clock - captureResults chan *v1.SensorData + clock clock.Clock + captureResults chan CaptureResult mongoCollection *mongo.Collection componentName string componentType string @@ -78,6 +336,7 @@ type collector struct { captureFunc CaptureFunc target CaptureBufferedWriter lastLoggedErrors map[string]int64 + dataType CaptureType } // Close closes the channels backing the Collector. It should always be called before disposing of a Collector to avoid @@ -178,10 +437,27 @@ func (c *collector) tickerBasedCapture(started chan struct{}) { } } +func (c *collector) validateReadingType(t CaptureType) error { + switch c.dataType { + case CaptureTypeTabular: + if t != CaptureTypeTabular { + return fmt.Errorf("expected result of type CaptureTypeTabular, instead got CaptureResultType: %d", t) + } + return nil + case CaptureTypeBinary: + if t != CaptureTypeBinary { + return fmt.Errorf("expected result of type CaptureTypeBinary,instead got CaptureResultType: %d", t) + } + return nil + case CaptureTypeUnspecified: + return fmt.Errorf("unknown collector data type: %d", c.dataType) + default: + return fmt.Errorf("unknown collector data type: %d", c.dataType) + } +} + func (c *collector) getAndPushNextReading() { - timeRequested := timestamppb.New(c.clock.Now().UTC()) reading, err := c.captureFunc(c.cancelCtx, c.params) - timeReceived := timestamppb.New(c.clock.Now().UTC()) if c.cancelCtx.Err() != nil { return @@ -196,56 +472,22 @@ func (c *collector) getAndPushNextReading() { return } - var msg v1.SensorData - switch v := reading.(type) { - case []byte: - msg = v1.SensorData{ - Metadata: &v1.SensorMetadata{ - TimeRequested: timeRequested, - TimeReceived: timeReceived, - }, - Data: &v1.SensorData_Binary{ - Binary: v, - }, - } - default: - // If it's not bytes, it's a struct. - var pbReading *structpb.Struct - var err error - - if reflect.TypeOf(reading) == reflect.TypeOf(pb.GetReadingsResponse{}) { - // We special-case the GetReadingsResponse because it already contains - // structpb.Values in it, and the StructToStructPb logic does not handle - // that cleanly. - topLevelMap := make(map[string]*structpb.Value) - topLevelMap["readings"] = structpb.NewStructValue( - &structpb.Struct{Fields: reading.(pb.GetReadingsResponse).Readings}, - ) - pbReading = &structpb.Struct{Fields: topLevelMap} - } else { - pbReading, err = protoutils.StructToStructPbIgnoreOmitEmpty(reading) - if err != nil { - c.captureErrors <- errors.Wrap(err, "error while converting reading to structpb.Struct") - return - } - } + if err := c.validateReadingType(reading.Type); err != nil { + c.captureErrors <- errors.Wrap(err, "capture result invalid type") + return + } - msg = v1.SensorData{ - Metadata: &v1.SensorMetadata{ - TimeRequested: timeRequested, - TimeReceived: timeReceived, - }, - Data: &v1.SensorData_Struct{ - Struct: pbReading, - }, - } + if err := reading.Validate(); err != nil { + c.captureErrors <- errors.Wrap(err, "capture result failed validation") + return } select { - // If c.captureResults is full, c.captureResults <- a can block indefinitely. This additional select block allows cancel to + // If c.captureResults is full, c.captureResults <- a can block indefinitely. + // This additional select block allows cancel to // still work when this happens. case <-c.cancelCtx.Done(): - case c.captureResults <- &msg: + case c.captureResults <- reading: } } @@ -267,9 +509,10 @@ func NewCollector(captureFunc CaptureFunc, params CollectorParams) (Collector, e componentName: params.ComponentName, componentType: params.ComponentType, methodName: params.MethodName, - captureResults: make(chan *v1.SensorData, params.QueueSize), mongoCollection: params.MongoCollection, + captureResults: make(chan CaptureResult, params.QueueSize), captureErrors: make(chan error, params.QueueSize), + dataType: params.DataType, interval: params.Interval, params: params.MethodParams, logger: params.Logger, @@ -292,8 +535,24 @@ func (c *collector) writeCaptureResults() { case <-c.cancelCtx.Done(): return case msg := <-c.captureResults: - if err := c.target.Write(msg); err != nil { - c.logger.Error(errors.Wrap(err, fmt.Sprintf("failed to write to collector %s", c.target.Path())).Error()) + proto := msg.ToProto() + + switch msg.Type { + case CaptureTypeTabular: + if err := c.target.WriteTabular(proto); err != nil { + c.logger.Error(errors.Wrap(err, fmt.Sprintf("failed to write tabular data to prog file %s", c.target.Path())).Error()) + return + } + case CaptureTypeBinary: + if err := c.target.WriteBinary(proto); err != nil { + c.logger.Error(errors.Wrap(err, fmt.Sprintf("failed to write binary data to prog file %s", c.target.Path())).Error()) + return + } + case CaptureTypeUnspecified: + c.logger.Error(fmt.Sprintf("collector returned invalid result type: %d", msg.Type)) + return + default: + c.logger.Error(fmt.Sprintf("collector returned invalid result type: %d", msg.Type)) return } @@ -302,8 +561,9 @@ func (c *collector) writeCaptureResults() { } } -// TabularData is a denormalized sensor reading. -type TabularData struct { +// TabularDataBson is a denormalized sensor reading that can be +// encoded into BSON. +type TabularDataBson struct { TimeRequested time.Time `bson:"time_requested"` TimeReceived time.Time `bson:"time_received"` ComponentName string `bson:"component_name"` @@ -315,21 +575,16 @@ type TabularData struct { // maybeWriteToMongo will write to the mongoCollection // if it is non-nil and the msg is tabular data // logs errors on failure. -func (c *collector) maybeWriteToMongo(msg *v1.SensorData) { +func (c *collector) maybeWriteToMongo(msg CaptureResult) { if c.mongoCollection == nil { return } - // DATA-3338: - // currently vision.CaptureAllFromCamera and camera.GetImages are stored in .capture files as VERY LARGE - // tabular sensor data - // That is a mistake which we are rectifying but in the meantime we don't want data captured from those methods to be synced - // to mongo - if getDataType(c.methodName) == v1.DataType_DATA_TYPE_BINARY_SENSOR || c.methodName == captureAllFromCamera { + if msg.Type != CaptureTypeTabular { return } - s := msg.GetStruct() + s := msg.TabularData.Payload if s == nil { return } @@ -340,9 +595,9 @@ func (c *collector) maybeWriteToMongo(msg *v1.SensorData) { return } - td := TabularData{ - TimeRequested: msg.Metadata.TimeRequested.AsTime(), - TimeReceived: msg.Metadata.TimeReceived.AsTime(), + td := TabularDataBson{ + TimeRequested: msg.TimeRequested, + TimeReceived: msg.TimeReceived, ComponentName: c.componentName, ComponentType: c.componentType, MethodName: c.methodName, diff --git a/data/collector_test.go b/data/collector_test.go index 9037677cede..52b7b0c1929 100644 --- a/data/collector_test.go +++ b/data/collector_test.go @@ -21,14 +21,20 @@ import ( ) var ( - structCapturer = CaptureFunc(func(ctx context.Context, _ map[string]*anypb.Any) (interface{}, error) { + structCapturer = CaptureFunc(func(ctx context.Context, _ map[string]*anypb.Any) (CaptureResult, error) { return dummyStructReading, nil }) - binaryCapturer = CaptureFunc(func(ctx context.Context, _ map[string]*anypb.Any) (interface{}, error) { - return dummyBytesReading, nil + binaryCapturer = CaptureFunc(func(ctx context.Context, _ map[string]*anypb.Any) (CaptureResult, error) { + return CaptureResult{ + Type: CaptureTypeBinary, + Binaries: []Binary{{Payload: dummyBytesReading}}, + }, nil }) - dummyStructReading = structReading{} - dummyStructReadingProto = dummyStructReading.toProto() + dummyStructReading = CaptureResult{ + Type: CaptureTypeTabular, + TabularData: TabularData{dummyStructReadingProto}, + } + dummyStructReadingProto = structReading{}.toProto() dummyBytesReading = []byte("I sure am bytes") queueSize = 250 bufferSize = 4096 @@ -73,6 +79,7 @@ func TestSuccessfulWrite(t *testing.T) { interval time.Duration expectReadings int expFiles int + datatype CaptureType }{ { name: "Ticker based struct writer.", @@ -80,6 +87,7 @@ func TestSuccessfulWrite(t *testing.T) { interval: tickerInterval, expectReadings: 2, expFiles: 1, + datatype: CaptureTypeTabular, }, { name: "Sleep based struct writer.", @@ -87,6 +95,7 @@ func TestSuccessfulWrite(t *testing.T) { interval: sleepInterval, expectReadings: 2, expFiles: 1, + datatype: CaptureTypeTabular, }, { name: "Ticker based binary writer.", @@ -94,6 +103,7 @@ func TestSuccessfulWrite(t *testing.T) { interval: tickerInterval, expectReadings: 2, expFiles: 2, + datatype: CaptureTypeBinary, }, { name: "Sleep based binary writer.", @@ -101,6 +111,7 @@ func TestSuccessfulWrite(t *testing.T) { interval: sleepInterval, expectReadings: 2, expFiles: 2, + datatype: CaptureTypeBinary, }, } @@ -109,19 +120,13 @@ func TestSuccessfulWrite(t *testing.T) { ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(time.Second)) defer cancel() tmpDir := t.TempDir() - md := v1.DataCaptureMetadata{} - tgt := NewCaptureBuffer(tmpDir, &md, 50) - test.That(t, tgt, test.ShouldNotBeNil) - wrote := make(chan struct{}) - target := &signalingBuffer{ - bw: tgt, - wrote: wrote, - } + target := newSignalingBuffer(ctx, tmpDir) mockClock := clock.NewMock() params.Interval = tc.interval params.Target = target params.Clock = mockClock + params.DataType = tc.datatype c, err := NewCollector(tc.captureFunc, params) test.That(t, err, test.ShouldBeNil) c.Collect() @@ -136,10 +141,9 @@ func TestSuccessfulWrite(t *testing.T) { select { case <-ctx.Done(): t.Fatalf("timed out waiting for data to be written") - case <-wrote: + case <-target.wrote: } } - close(wrote) // If it's a sleep based collector, we need to move the clock forward one more time after calling Close. // Otherwise, it will stay asleep indefinitely and Close will block forever. @@ -158,7 +162,7 @@ func TestSuccessfulWrite(t *testing.T) { return default: time.Sleep(time.Millisecond * 1) - mockClock.Add(tc.interval) + mockClock.Add(params.Interval) } } }() @@ -184,18 +188,15 @@ func TestSuccessfulWrite(t *testing.T) { func TestClose(t *testing.T) { // Set up a collector. l := logging.NewTestLogger(t) + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() tmpDir := t.TempDir() - md := v1.DataCaptureMetadata{} - buf := NewCaptureBuffer(tmpDir, &md, 50) - wrote := make(chan struct{}) - target := &signalingBuffer{ - bw: buf, - wrote: wrote, - } mockClock := clock.NewMock() + target := newSignalingBuffer(ctx, tmpDir) interval := time.Millisecond * 5 params := CollectorParams{ + DataType: CaptureTypeTabular, ComponentName: "testComponent", Interval: interval, MethodParams: map[string]*anypb.Any{"name": fakeVal}, @@ -205,17 +206,18 @@ func TestClose(t *testing.T) { Logger: l, Clock: mockClock, } - c, _ := NewCollector(structCapturer, params) + c, err := NewCollector(structCapturer, params) + test.That(t, err, test.ShouldBeNil) // Start collecting, and validate it is writing. c.Collect() mockClock.Add(interval) - ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*10) + ctx, cancel = context.WithTimeout(context.Background(), time.Millisecond*10) defer cancel() select { case <-ctx.Done(): t.Fatalf("timed out waiting for data to be written") - case <-wrote: + case <-target.wrote: } // Close and validate no additional writes occur even after an additional interval. @@ -225,7 +227,7 @@ func TestClose(t *testing.T) { defer cancel() select { case <-ctx.Done(): - case <-wrote: + case <-target.wrote: t.Fatalf("unexpected write after close") } } @@ -238,10 +240,11 @@ func TestCtxCancelledNotLoggedAfterClose(t *testing.T) { tmpDir := t.TempDir() target := NewCaptureBuffer(tmpDir, &v1.DataCaptureMetadata{}, 50) captured := make(chan struct{}) - errorCapturer := CaptureFunc(func(ctx context.Context, _ map[string]*anypb.Any) (interface{}, error) { + errorCapturer := CaptureFunc(func(ctx context.Context, _ map[string]*anypb.Any) (CaptureResult, error) { + var res CaptureResult select { case <-ctx.Done(): - return nil, fmt.Errorf("arbitrary wrapping message: %w", ctx.Err()) + return res, fmt.Errorf("arbitrary wrapping message: %w", ctx.Err()) case captured <- struct{}{}: } return dummyStructReading, nil @@ -249,6 +252,7 @@ func TestCtxCancelledNotLoggedAfterClose(t *testing.T) { params := CollectorParams{ ComponentName: "testComponent", + DataType: CaptureTypeTabular, Interval: time.Millisecond, MethodParams: map[string]*anypb.Any{"name": fakeVal}, Target: target, @@ -256,7 +260,8 @@ func TestCtxCancelledNotLoggedAfterClose(t *testing.T) { BufferSize: bufferSize, Logger: logger, } - c, _ := NewCollector(errorCapturer, params) + c, err := NewCollector(errorCapturer, params) + test.That(t, err, test.ShouldBeNil) c.Collect() <-captured c.Close() @@ -274,19 +279,16 @@ func TestLogErrorsOnlyOnce(t *testing.T) { // Set up a collector. logger, logs := logging.NewObservedTestLogger(t) tmpDir := t.TempDir() - md := v1.DataCaptureMetadata{} - buf := NewCaptureBuffer(tmpDir, &md, 50) - wrote := make(chan struct{}) - errorCapturer := CaptureFunc(func(ctx context.Context, _ map[string]*anypb.Any) (interface{}, error) { - return nil, errors.New("I am an error") + errorCapturer := CaptureFunc(func(ctx context.Context, _ map[string]*anypb.Any) (CaptureResult, error) { + return CaptureResult{}, errors.New("I am an error") }) - target := &signalingBuffer{ - bw: buf, - wrote: wrote, - } - mockClock := clock.NewMock() + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + target := newSignalingBuffer(ctx, tmpDir) interval := time.Millisecond * 5 + mockClock := clock.NewMock() + params := CollectorParams{ ComponentName: "testComponent", Interval: interval, @@ -297,13 +299,14 @@ func TestLogErrorsOnlyOnce(t *testing.T) { Logger: logger, Clock: mockClock, } - c, _ := NewCollector(errorCapturer, params) + c, err := NewCollector(errorCapturer, params) + test.That(t, err, test.ShouldBeNil) // Start collecting, and validate it is writing. c.Collect() mockClock.Add(interval * 5) - close(wrote) + // close(wrote) test.That(t, logs.FilterLevelExact(zapcore.ErrorLevel).Len(), test.ShouldEqual, 1) mockClock.Add(3 * time.Second) test.That(t, logs.FilterLevelExact(zapcore.ErrorLevel).Len(), test.ShouldEqual, 2) @@ -340,14 +343,36 @@ func getAllFiles(dir string) []os.FileInfo { return files } +func newSignalingBuffer(ctx context.Context, path string) *signalingBuffer { + md := v1.DataCaptureMetadata{} + return &signalingBuffer{ + ctx: ctx, + bw: NewCaptureBuffer(path, &md, 50), + wrote: make(chan struct{}), + } +} + type signalingBuffer struct { + ctx context.Context bw CaptureBufferedWriter wrote chan struct{} } -func (b *signalingBuffer) Write(data *v1.SensorData) error { - ret := b.bw.Write(data) - b.wrote <- struct{}{} +func (b *signalingBuffer) WriteBinary(items []*v1.SensorData) error { + ret := b.bw.WriteBinary(items) + select { + case b.wrote <- struct{}{}: + case <-b.ctx.Done(): + } + return ret +} + +func (b *signalingBuffer) WriteTabular(items []*v1.SensorData) error { + ret := b.bw.WriteTabular(items) + select { + case b.wrote <- struct{}{}: + case <-b.ctx.Done(): + } return ret } diff --git a/data/registry.go b/data/registry.go index d48997893ed..67a26295d4b 100644 --- a/data/registry.go +++ b/data/registry.go @@ -19,17 +19,18 @@ type CollectorConstructor func(resource interface{}, params CollectorParams) (Co // CollectorParams contain the parameters needed to construct a Collector. type CollectorParams struct { - MongoCollection *mongo.Collection + BufferSize int + Clock clock.Clock ComponentName string ComponentType string - MethodName string + DataType CaptureType Interval time.Duration + Logger logging.Logger + MethodName string MethodParams map[string]*anypb.Any - Target CaptureBufferedWriter + MongoCollection *mongo.Collection QueueSize int - BufferSize int - Logger logging.Logger - Clock clock.Clock + Target CaptureBufferedWriter } // Validate validates that p contains all required parameters. @@ -43,6 +44,9 @@ func (p CollectorParams) Validate() error { if p.ComponentName == "" { return errors.New("missing required parameter component name") } + if p.DataType != CaptureTypeBinary && p.DataType != CaptureTypeTabular { + return errors.New("invalid DataType") + } return nil } diff --git a/go.mod b/go.mod index b97ebffb1b2..97d3c14210c 100644 --- a/go.mod +++ b/go.mod @@ -15,7 +15,7 @@ require ( github.com/bep/debounce v1.2.1 github.com/bluenviron/gortsplib/v4 v4.8.0 github.com/bluenviron/mediacommon v1.9.2 - github.com/bufbuild/buf v1.21.0 + github.com/bufbuild/buf v1.30.0 github.com/charmbracelet/huh v0.6.0 github.com/charmbracelet/huh/spinner v0.0.0-20240917123815-c9b2c9cdb7b6 github.com/creack/pty v1.1.19-0.20220421211855-0d412c9fbeb1 @@ -47,11 +47,11 @@ require ( github.com/google/go-cmp v0.6.0 github.com/google/uuid v1.6.0 github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 - github.com/grpc-ecosystem/grpc-gateway/v2 v2.15.2 + github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 github.com/invopop/jsonschema v0.6.0 github.com/jacobsa/go-serial v0.0.0-20180131005756-15cf729a72d4 github.com/jedib0t/go-pretty/v6 v6.4.6 - github.com/jhump/protoreflect v1.15.1 + github.com/jhump/protoreflect v1.15.6 github.com/kellydunn/golang-geo v0.7.0 github.com/kylelemons/godebug v1.1.0 github.com/lestrrat-go/jwx v1.2.29 @@ -82,7 +82,7 @@ require ( go.uber.org/atomic v1.11.0 go.uber.org/multierr v1.11.0 go.uber.org/zap v1.27.0 - go.viam.com/api v0.1.357 + go.viam.com/api v0.1.360 go.viam.com/test v1.2.3 go.viam.com/utils v0.1.112 goji.io v2.0.2+incompatible @@ -100,7 +100,7 @@ require ( google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 google.golang.org/grpc v1.66.0 google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.2.0 - google.golang.org/protobuf v1.34.2 + google.golang.org/protobuf v1.35.1 gopkg.in/src-d/go-billy.v4 v4.3.2 gorgonia.org/tensor v0.9.24 gotest.tools/gotestsum v1.10.0 @@ -111,6 +111,7 @@ require ( require ( 4d63.com/gocheckcompilerdirectives v1.2.1 // indirect 4d63.com/gochecknoglobals v0.2.1 // indirect + buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.33.0-20240221180331-f05a6f4403ce.1 // indirect cel.dev/expr v0.15.0 // indirect cloud.google.com/go v0.115.1 // indirect cloud.google.com/go/auth v0.9.3 // indirect @@ -121,6 +122,8 @@ require ( cloud.google.com/go/monitoring v1.21.0 // indirect cloud.google.com/go/storage v1.43.0 // indirect cloud.google.com/go/trace v1.11.0 // indirect + connectrpc.com/connect v1.15.0 // indirect + connectrpc.com/otelconnect v0.7.0 // indirect contrib.go.opencensus.io/exporter/stackdriver v0.13.4 // indirect git.sr.ht/~sbinet/gg v0.3.1 // indirect github.com/4meepo/tagalign v1.3.4 // indirect @@ -142,6 +145,7 @@ require ( github.com/alexkohler/nakedret/v2 v2.0.4 // indirect github.com/alexkohler/prealloc v1.0.0 // indirect github.com/alingse/asasalint v0.0.11 // indirect + github.com/antlr4-go/antlr/v4 v4.13.0 // indirect github.com/apache/arrow/go/arrow v0.0.0-20201229220542-30ce2eb5d4dc // indirect github.com/ashanbrown/forbidigo v1.6.0 // indirect github.com/ashanbrown/makezero v1.1.1 // indirect @@ -156,8 +160,9 @@ require ( github.com/bombsimon/wsl/v4 v4.4.1 // indirect github.com/breml/bidichk v0.2.7 // indirect github.com/breml/errchkjson v0.3.6 // indirect - github.com/bufbuild/connect-go v1.8.0 // indirect - github.com/bufbuild/protocompile v0.5.1 // indirect + github.com/bufbuild/protocompile v0.9.0 // indirect + github.com/bufbuild/protovalidate-go v0.6.0 // indirect + github.com/bufbuild/protoyaml-go v0.1.8 // indirect github.com/butuzov/ireturn v0.3.0 // indirect github.com/butuzov/mirror v1.2.0 // indirect github.com/campoy/embedmd v1.0.0 // indirect @@ -165,7 +170,7 @@ require ( github.com/catppuccin/go v0.2.0 // indirect github.com/ccojocar/zxcvbn-go v1.0.2 // indirect github.com/cenkalti/backoff v2.2.1+incompatible // indirect - github.com/cenkalti/backoff/v4 v4.2.1 // indirect + github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/census-instrumentation/opencensus-proto v0.4.1 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/charithe/durationcheck v0.0.10 // indirect @@ -180,7 +185,7 @@ require ( github.com/chewxy/math32 v1.0.8 // indirect github.com/ckaznocha/intrange v0.2.0 // indirect github.com/cncf/xds/go v0.0.0-20240423153145-555b57ec207b // indirect - github.com/containerd/stargz-snapshotter/estargz v0.14.3 // indirect + github.com/containerd/stargz-snapshotter/estargz v0.15.1 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect github.com/curioswitch/go-reassign v0.2.0 // indirect github.com/daixiang0/gci v0.13.5 // indirect @@ -189,12 +194,13 @@ require ( github.com/denis-tingaikin/go-header v0.5.0 // indirect github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f // indirect github.com/dgottlieb/smarty-assertions v1.2.5 // indirect + github.com/distribution/reference v0.5.0 // indirect github.com/dnephin/pflag v1.0.7 // indirect - github.com/docker/cli v24.0.2+incompatible // indirect - github.com/docker/distribution v2.8.2+incompatible // indirect - github.com/docker/docker v24.0.2+incompatible // indirect - github.com/docker/docker-credential-helpers v0.7.0 // indirect - github.com/docker/go-connections v0.4.0 // indirect + github.com/docker/cli v25.0.4+incompatible // indirect + github.com/docker/distribution v2.8.3+incompatible // indirect + github.com/docker/docker v25.0.6+incompatible // indirect + github.com/docker/docker-credential-helpers v0.8.1 // indirect + github.com/docker/go-connections v0.5.0 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/edaniels/golog v0.0.0-20230215213219-28954395e8d0 // indirect github.com/edaniels/zeroconf v1.0.10 // indirect @@ -204,7 +210,7 @@ require ( github.com/ettle/strcase v0.2.0 // indirect github.com/fatih/camelcase v1.0.0 // indirect github.com/fatih/structtag v1.2.0 // indirect - github.com/felixge/fgprof v0.9.3 // indirect + github.com/felixge/fgprof v0.9.4 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/firefart/nonamedreturns v1.0.5 // indirect github.com/fzipp/gocyclo v0.6.0 // indirect @@ -212,7 +218,7 @@ require ( github.com/ghostiam/protogetter v0.3.6 // indirect github.com/gin-gonic/gin v1.9.1 // indirect github.com/go-audio/riff v1.0.0 // indirect - github.com/go-chi/chi/v5 v5.0.8 // indirect + github.com/go-chi/chi/v5 v5.0.12 // indirect github.com/go-critic/go-critic v0.11.4 // indirect github.com/go-fonts/liberation v0.3.0 // indirect github.com/go-latex/latex v0.0.0-20230307184459-12ec69307ad9 // indirect @@ -234,7 +240,6 @@ require ( github.com/gofrs/flock v0.12.1 // indirect github.com/gofrs/uuid/v5 v5.0.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang/glog v1.2.1 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/snappy v0.0.4 // indirect github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a // indirect @@ -245,8 +250,9 @@ require ( github.com/golangci/revgrep v0.5.3 // indirect github.com/golangci/unconvert v0.0.0-20240309020433-c5143eacb3ed // indirect github.com/gonuts/binary v0.2.0 // indirect + github.com/google/cel-go v0.20.1 // indirect github.com/google/flatbuffers v2.0.6+incompatible // indirect - github.com/google/go-containerregistry v0.15.2 // indirect + github.com/google/go-containerregistry v0.19.0 // indirect github.com/google/pprof v0.0.0-20240827171923-fa2c70bbbfe5 // indirect github.com/google/s2a-go v0.1.8 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect @@ -266,7 +272,7 @@ require ( github.com/imdario/mergo v0.3.12 // indirect github.com/improbable-eng/grpc-web v0.15.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/jdxcode/netrc v0.0.0-20221124155335-4616370d1a84 // indirect + github.com/jdx/go-netrc v1.0.0 // indirect github.com/jgautheron/goconst v1.7.1 // indirect github.com/jingyugao/rowserrcheck v1.1.1 // indirect github.com/jirfag/go-printf-func-name v0.0.0-20200119135958-7558a9eaa5af // indirect @@ -276,7 +282,7 @@ require ( github.com/karamaru-alpha/copyloopvar v1.1.0 // indirect github.com/kisielk/errcheck v1.7.0 // indirect github.com/kkHAIKE/contextcheck v1.1.5 // indirect - github.com/klauspost/compress v1.16.5 // indirect + github.com/klauspost/compress v1.17.7 // indirect github.com/klauspost/pgzip v1.2.6 // indirect github.com/kulti/thelper v0.6.3 // indirect github.com/kunwardeep/paralleltest v1.0.10 // indirect @@ -320,7 +326,7 @@ require ( github.com/nunnatsa/ginkgolinter v0.16.2 // indirect github.com/olekukonko/tablewriter v0.0.5 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect - github.com/opencontainers/image-spec v1.1.0-rc3 // indirect + github.com/opencontainers/image-spec v1.1.0 // indirect github.com/pelletier/go-toml v1.9.5 // indirect github.com/pelletier/go-toml/v2 v2.2.3 // indirect github.com/pierrec/lz4 v2.0.5+incompatible // indirect @@ -337,7 +343,7 @@ require ( github.com/pion/transport/v2 v2.2.10 // indirect github.com/pion/turn/v2 v2.1.6 // indirect github.com/pion/webrtc/v3 v3.2.36 // indirect - github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect + github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect github.com/pkg/profile v1.7.0 // indirect github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect @@ -376,12 +382,12 @@ require ( github.com/srikrsna/protoc-gen-gotag v0.6.2 // indirect github.com/ssgreg/nlreturn/v2 v2.2.1 // indirect github.com/stbenjam/no-sprintf-host-port v0.1.1 // indirect + github.com/stoewer/go-strcase v1.3.0 // indirect github.com/stretchr/objx v0.5.2 // indirect github.com/stretchr/testify v1.9.0 // indirect github.com/subosito/gotenv v1.4.1 // indirect github.com/tdakkota/asciicheck v0.2.0 // indirect github.com/tetafro/godot v1.4.17 // indirect - github.com/tetratelabs/wazero v1.2.0 // indirect github.com/timakin/bodyclose v0.0.0-20230421092635-574207250966 // indirect github.com/timonwong/loggercheck v0.9.4 // indirect github.com/tomarrell/wrapcheck/v2 v2.9.0 // indirect @@ -390,7 +396,7 @@ require ( github.com/ultraware/funlen v0.1.0 // indirect github.com/ultraware/whitespace v0.1.1 // indirect github.com/uudashr/gocognit v1.1.3 // indirect - github.com/vbatts/tar-split v0.11.3 // indirect + github.com/vbatts/tar-split v0.11.5 // indirect github.com/wlynxg/anet v0.0.3 // indirect github.com/xdg-go/pbkdf2 v1.0.0 // indirect github.com/xdg-go/scram v1.1.2 // indirect @@ -408,10 +414,10 @@ require ( go-simpler.org/sloglint v0.7.2 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 // indirect - go.opentelemetry.io/otel v1.29.0 // indirect - go.opentelemetry.io/otel/metric v1.29.0 // indirect - go.opentelemetry.io/otel/sdk v1.28.0 // indirect - go.opentelemetry.io/otel/trace v1.29.0 // indirect + go.opentelemetry.io/otel v1.31.0 // indirect + go.opentelemetry.io/otel/metric v1.31.0 // indirect + go.opentelemetry.io/otel/sdk v1.31.0 // indirect + go.opentelemetry.io/otel/trace v1.31.0 // indirect go.uber.org/automaxprocs v1.5.3 // indirect go.uber.org/goleak v1.3.0 // indirect go4.org/unsafe/assume-no-moving-gc v0.0.0-20230525183740-e7c30c78aeb2 // indirect diff --git a/go.sum b/go.sum index 2b1bd15d364..42c5633ed01 100644 --- a/go.sum +++ b/go.sum @@ -3,6 +3,8 @@ 4d63.com/gochecknoglobals v0.0.0-20201008074935-acfc0b28355a/go.mod h1:wfdC5ZjKSPr7CybKEcgJhUOgeAQW1+7WcyK8OvUilfo= 4d63.com/gochecknoglobals v0.2.1 h1:1eiorGsgHOFOuoOiJDy2psSrQbRdIHrlge0IJIkUgDc= 4d63.com/gochecknoglobals v0.2.1/go.mod h1:KRE8wtJB3CXCsb1xy421JfTHIIbmT3U5ruxw2Qu8fSU= +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.33.0-20240221180331-f05a6f4403ce.1 h1:0nWhrRcnkgw1kwJ7xibIO8bqfOA7pBzBjGCDBxIHch8= +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.33.0-20240221180331-f05a6f4403ce.1/go.mod h1:Tgn5bgL220vkFOI0KPStlcClPeOJzAv4uT+V8JXGUnw= cel.dev/expr v0.15.0 h1:O1jzfJCQBfL5BFoYktaxwIhuttaQPsVWerH9/EEKx0w= cel.dev/expr v0.15.0/go.mod h1:TRSuuV7DlVCE/uwv5QbAiW/v8l5O8C4eEPHeu7gf7Sg= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= @@ -58,6 +60,10 @@ cloud.google.com/go/storage v1.43.0 h1:CcxnSohZwizt4LCzQHWvBf1/kvtHUn7gk9QERXPyX cloud.google.com/go/storage v1.43.0/go.mod h1:ajvxEa7WmZS1PxvKRq4bq0tFT3vMd502JwstCcYv0Q0= cloud.google.com/go/trace v1.11.0 h1:UHX6cOJm45Zw/KIbqHe4kII8PupLt/V5tscZUkeiJVI= cloud.google.com/go/trace v1.11.0/go.mod h1:Aiemdi52635dBR7o3zuc9lLjXo3BwGaChEjCa3tJNmM= +connectrpc.com/connect v1.15.0 h1:lFdeCbZrVVDydAqwr4xGV2y+ULn+0Z73s5JBj2LikWo= +connectrpc.com/connect v1.15.0/go.mod h1:bQmjpDY8xItMnttnurVgOkHUBMRT9cpsNi2O4AjKhmA= +connectrpc.com/otelconnect v0.7.0 h1:ZH55ZZtcJOTKWWLy3qmL4Pam4RzRWBJFOqTPyAqCXkY= +connectrpc.com/otelconnect v0.7.0/go.mod h1:Bt2ivBymHZHqxvo4HkJ0EwHuUzQN6k2l0oH+mp/8nwc= contrib.go.opencensus.io/exporter/stackdriver v0.13.4 h1:ksUxwH3OD5sxkjzEqGxNTl+Xjsmu3BnC/300MhSVTSc= contrib.go.opencensus.io/exporter/stackdriver v0.13.4/go.mod h1:aXENhDJ1Y4lIg4EUaVTwzvYETVNZk10Pu26tevFKLUc= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= @@ -78,7 +84,6 @@ github.com/Antonboom/testifylint v1.4.3/go.mod h1:+8Q9+AOLsz5ZiQiiYujJKs9mNz398+ github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c h1:pxW6RcqyfI9/kWtOwnv/G+AzdKuy2ZrqINhenH4HyNs= github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= @@ -139,6 +144,8 @@ github.com/alingse/asasalint v0.0.11 h1:SFwnQXJ49Kx/1GghOFz1XGqHYKp21Kq1nHad/0WQ github.com/alingse/asasalint v0.0.11/go.mod h1:nCaoMhw7a9kSJObvQyVzNTPBDbNpdocqrSP7t/cW5+I= github.com/andybalholm/brotli v1.0.0/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI= +github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g= github.com/apache/arrow/go/arrow v0.0.0-20201229220542-30ce2eb5d4dc h1:zvQ6w7KwtQWgMQiewOF9tFtundRMVZFSAksNV6ogzuY= github.com/apache/arrow/go/arrow v0.0.0-20201229220542-30ce2eb5d4dc/go.mod h1:c9sxoIT3YgLxH4UhLOCKaBlEojuMhVYpk4Ntv3opUTQ= github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= @@ -201,12 +208,14 @@ github.com/breml/bidichk v0.2.7 h1:dAkKQPLl/Qrk7hnP6P+E0xOodrq8Us7+U0o4UBOAlQY= github.com/breml/bidichk v0.2.7/go.mod h1:YodjipAGI9fGcYM7II6wFvGhdMYsC5pHDlGzqvEW3tQ= github.com/breml/errchkjson v0.3.6 h1:VLhVkqSBH96AvXEyclMR37rZslRrY2kcyq+31HCsVrA= github.com/breml/errchkjson v0.3.6/go.mod h1:jhSDoFheAF2RSDOlCfhHO9KqhZgAYLyvHe7bRCX8f/U= -github.com/bufbuild/buf v1.21.0 h1:fgmvmA5xDFbKYd9wtpExH6YtCcTUo4GDt+7yizSNqUE= -github.com/bufbuild/buf v1.21.0/go.mod h1:o7qgHprFF7rrwY9OEE3Jv+zVMqEjtYjETR+klgPcPoE= -github.com/bufbuild/connect-go v1.8.0 h1:srluNkFkZBfSfg9Qb6DrO+5nMaxix//h2ctrHZhMGKc= -github.com/bufbuild/connect-go v1.8.0/go.mod h1:GmMJYR6orFqD0Y6ZgX8pwQ8j9baizDrIQMm1/a6LnHk= -github.com/bufbuild/protocompile v0.5.1 h1:mixz5lJX4Hiz4FpqFREJHIXLfaLBntfaJv1h+/jS+Qg= -github.com/bufbuild/protocompile v0.5.1/go.mod h1:G5iLmavmF4NsYtpZFvE3B/zFch2GIY8+wjsYLR/lc40= +github.com/bufbuild/buf v1.30.0 h1:V/Gir+aVKukqI/w2Eqoiv4tqUs01KBWP9t3Hz/9/25I= +github.com/bufbuild/buf v1.30.0/go.mod h1:vfr2bN0OlblcfLHKJNMixj7WohlMlFX4yB4L3VZq7A8= +github.com/bufbuild/protocompile v0.9.0 h1:DI8qLG5PEO0Mu1Oj51YFPqtx6I3qYXUAhJVJ/IzAVl0= +github.com/bufbuild/protocompile v0.9.0/go.mod h1:s89m1O8CqSYpyE/YaSGtg1r1YFMF5nLTwh4vlj6O444= +github.com/bufbuild/protovalidate-go v0.6.0 h1:Jgs1kFuZ2LHvvdj8SpCLA1W/+pXS8QSM3F/E2l3InPY= +github.com/bufbuild/protovalidate-go v0.6.0/go.mod h1:1LamgoYHZ2NdIQH0XGczGTc6Z8YrTHjcJVmiBaar4t4= +github.com/bufbuild/protoyaml-go v0.1.8 h1:X9QDLfl9uEllh4gsXUGqPanZYCOKzd92uniRtW2OnAQ= +github.com/bufbuild/protoyaml-go v0.1.8/go.mod h1:R8vE2+l49bSiIExP4VJpxOXleHE+FDzZ6HVxr3cYunw= github.com/butuzov/ireturn v0.3.0 h1:hTjMqWw3y5JC3kpnC5vXmFJAWI/m31jaCYQqzkS6PL0= github.com/butuzov/ireturn v0.3.0/go.mod h1:A09nIiwiqzN/IoVo9ogpa0Hzi9fex1kd9PSD6edP5ZA= github.com/butuzov/mirror v1.2.0 h1:9YVK1qIjNspaqWutSv8gsge2e/Xpq1eqEkslEUHy5cs= @@ -225,8 +234,8 @@ github.com/ccojocar/zxcvbn-go v1.0.2/go.mod h1:g1qkXtUSvHP8lhHp5GrSmTz6uWALGRMQd github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= -github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= -github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.4.1 h1:iKLQ0xPNFxR/2hzXZMrBo8f1j86j5WHzznCCQxV/b8g= github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw= @@ -263,9 +272,15 @@ github.com/chewxy/hm v1.0.0/go.mod h1:qg9YI4q6Fkj/whwHR1D+bOGeF7SniIP40VweVepLjg github.com/chewxy/math32 v1.0.0/go.mod h1:Miac6hA1ohdDUTagnvJy/q+aNnEk16qWUdb8ZVhvCN0= github.com/chewxy/math32 v1.0.8 h1:fU5E4Ec4Z+5RtRAi3TovSxUjQPkgRh+HbP7tKB2OFbM= github.com/chewxy/math32 v1.0.8/go.mod h1:dOB2rcuFrCn6UHrze36WSLVPKtzPMRAQvBvUwkSsLqs= +github.com/chromedp/cdproto v0.0.0-20230802225258-3cf4e6d46a89/go.mod h1:GKljq0VrfU4D5yc+2qA6OVr8pmO/MBbPEWqWQ/oqGEs= +github.com/chromedp/chromedp v0.9.2/go.mod h1:LkSXJKONWTCHAfQasKFUZI+mxqS4tZqhmtGzzhLsnLs= +github.com/chromedp/sysutil v1.0.0/go.mod h1:kgWmDdq8fTzXYcKIBqIYvRRTnYb9aNS9moAV0xufSww= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8= github.com/ckaznocha/intrange v0.2.0 h1:FykcZuJ8BD7oX93YbO1UY9oZtkRbp+1/kJcDjkefYLs= github.com/ckaznocha/intrange v0.2.0/go.mod h1:r5I7nUlAAG56xmkOpw4XVr16BXhwYTUdcuRFeevn1oE= github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE= @@ -280,8 +295,10 @@ github.com/cncf/xds/go v0.0.0-20240423153145-555b57ec207b h1:ga8SEFjZ60pxLcmhnTh github.com/cncf/xds/go v0.0.0-20240423153145-555b57ec207b/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= -github.com/containerd/stargz-snapshotter/estargz v0.14.3 h1:OqlDCK3ZVUO6C3B/5FSkDwbkEETK84kQgEeFwDC+62k= -github.com/containerd/stargz-snapshotter/estargz v0.14.3/go.mod h1:KY//uOCIkSuNAHhJogcZtrNHdKrA99/FCCRjE3HD36o= +github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= +github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= +github.com/containerd/stargz-snapshotter/estargz v0.15.1 h1:eXJjw9RbkLFgioVaTG+G/ZW/0kEe2oEKCdS/ZxIyoCU= +github.com/containerd/stargz-snapshotter/estargz v0.15.1/go.mod h1:gr2RNwukQ/S9Nv33Lt6UC7xEx58C+LHRdoqbEKjz1Kk= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= @@ -294,7 +311,6 @@ github.com/corona10/goimagehash v1.0.2 h1:pUfB0LnsJASMPGEZLj7tGY251vF+qLGqOgEP4r github.com/corona10/goimagehash v1.0.2/go.mod h1:/l9umBhvcHQXVtQO1V6Gp1yD20STawkhRnnX0D1bvVI= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= @@ -329,18 +345,20 @@ github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZm github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c= github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4= +github.com/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0= +github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/dnephin/pflag v1.0.7 h1:oxONGlWxhmUct0YzKTgrpQv9AUA1wtPBn7zuSjJqptk= github.com/dnephin/pflag v1.0.7/go.mod h1:uxE91IoWURlOiTUIA8Mq5ZZkAv3dPUfZNaT80Zm7OQE= -github.com/docker/cli v24.0.2+incompatible h1:QdqR7znue1mtkXIJ+ruQMGQhpw2JzMJLRXp6zpzF6tM= -github.com/docker/cli v24.0.2+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= -github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8= -github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v24.0.2+incompatible h1:eATx+oLz9WdNVkQrr0qjQ8HvRJ4bOOxfzEo8R+dA3cg= -github.com/docker/docker v24.0.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/docker-credential-helpers v0.7.0 h1:xtCHsjxogADNZcdv1pKUHXryefjlVRqWqIhk/uXJp0A= -github.com/docker/docker-credential-helpers v0.7.0/go.mod h1:rETQfLdHNT3foU5kuNkFR1R1V12OJRRO5lzt2D1b5X0= -github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= -github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= +github.com/docker/cli v25.0.4+incompatible h1:DatRkJ+nrFoYL2HZUzjM5Z5sAmcA5XGp+AW0oEw2+cA= +github.com/docker/cli v25.0.4+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk= +github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/docker v25.0.6+incompatible h1:5cPwbwriIcsua2REJe8HqQV+6WlWc1byg2QSXzBxBGg= +github.com/docker/docker v25.0.6+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker-credential-helpers v0.8.1 h1:j/eKUktUltBtMzKqmfLB0PAgqYyMHOp5vfsD1807oKo= +github.com/docker/docker-credential-helpers v0.8.1/go.mod h1:P3ci7E3lwkZg6XiHdRKft1KckHiO9a2rNtyFbZ/ry9M= +github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= +github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= @@ -391,8 +409,9 @@ github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4= github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI= github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4= github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94= -github.com/felixge/fgprof v0.9.3 h1:VvyZxILNuCiUCSXtPtYmmtGvb65nqXh2QFWc0Wpf2/g= github.com/felixge/fgprof v0.9.3/go.mod h1:RdbpDgzqYVh/T9fPELJyV7EYJuHB55UTEULNun8eiPw= +github.com/felixge/fgprof v0.9.4 h1:ocDNwMFlnA0NU0zSB3I52xkO4sFXk80VK9lXjLClu88= +github.com/felixge/fgprof v0.9.4/go.mod h1:yKl+ERSa++RYOs32d8K6WEXCB4uXdLls4ZaZPpayhMM= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/firefart/nonamedreturns v1.0.5 h1:tM+Me2ZaXs8tfdDw3X6DOX++wMCOqzYUho6tUTYIdRA= @@ -433,8 +452,8 @@ github.com/go-audio/transforms v0.0.0-20180121090939-51830ccc35a5 h1:acgZxkn6oSJ github.com/go-audio/transforms v0.0.0-20180121090939-51830ccc35a5/go.mod h1:z9ahC4nc9/kxKfl1BnTZ/D2Cm5TbhjR2LeuUpepL9zI= github.com/go-audio/wav v1.1.0 h1:jQgLtbqBzY7G+BM8fXF7AHUk1uHUviWS4X39d5rsL2g= github.com/go-audio/wav v1.1.0/go.mod h1:mpe9qfwbScEbkd8uybLuIpTgHyrISw/OTuvjUW2iGtE= -github.com/go-chi/chi/v5 v5.0.8 h1:lD+NLqFcAi1ovnVZpsnObHGW4xb4J8lNmoYVfECH1Y0= -github.com/go-chi/chi/v5 v5.0.8/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= +github.com/go-chi/chi/v5 v5.0.12 h1:9euLV5sTrTNTRUU9POmDUvfxyj6LAABLUcEWO+JJb4s= +github.com/go-chi/chi/v5 v5.0.12/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= github.com/go-critic/go-critic v0.5.4/go.mod h1:cjB4YGw+n/+X8gREApej7150Uyy1Tg8If6F2XOAUXNE= github.com/go-critic/go-critic v0.11.4 h1:O7kGOCx0NDIni4czrkRIXTnit0mkyKOCePh3My6OyEU= github.com/go-critic/go-critic v0.11.4/go.mod h1:2QAdo4iuLik5S9YG0rT4wcZ8QxwHYkrr6/2MWAiv/vc= @@ -532,12 +551,15 @@ github.com/goburrow/serial v0.1.0 h1:v2T1SQa/dlUqQiYIT8+Cu7YolfqAi3K96UmhwYyuSrA github.com/goburrow/serial v0.1.0/go.mod h1:sAiqG0nRVswsm1C97xsttiYCzSLBmUZ/VSlVLZJ8haA= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= -github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee h1:s+21KNqlpePfkah2I+gwHF8xmJWRjooY+5248k6m4A0= github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= -github.com/gobwas/pool v0.2.0 h1:QEmUOlnSjWtnpRGHF3SauEiOsy82Cup83Vf2LcMlnc8= +github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU= +github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM= github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= -github.com/gobwas/ws v1.0.2 h1:CoAavW/wd/kulfZmSIBt6p24n4j7tHgNVCjsfHVNUbo= +github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og= +github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= +github.com/gobwas/ws v1.2.1 h1:F2aeBZrm2NDsc7vbovKrWSogd4wvfAxg0FQ89/iqOTk= +github.com/gobwas/ws v1.2.1/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY= github.com/goccy/go-graphviz v0.1.3 h1:Pkt8y4FBnBNI9tfSobpoN5qy1qMNqRXPQYvLhaSUasY= github.com/goccy/go-graphviz v0.1.3/go.mod h1:pMYpbAqJT10V8dzV1JN/g/wUlG/0imKPzn3ZsrchGCI= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= @@ -561,8 +583,6 @@ github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGw github.com/golang/geo v0.0.0-20210211234256-740aa86cb551 h1:gtexQ/VGyN+VVFRXSFiguSNcXmS6rkKT+X7FdIrTtfo= github.com/golang/geo v0.0.0-20210211234256-740aa86cb551/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/glog v1.2.1 h1:OptwRhECazUx5ix5TTWC3EZhsZEHWcYWY4FQHTIubm4= -github.com/golang/glog v1.2.1/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -628,6 +648,8 @@ github.com/gonuts/binary v0.2.0 h1:caITwMWAoQWlL0RNvv2lTU/AHqAJlVuu6nZmNgfbKW4= github.com/gonuts/binary v0.2.0/go.mod h1:kM+CtBrCGDSKdv8WXTuCUsw+loiy8f/QEI8YCCC0M/E= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/cel-go v0.20.1 h1:nDx9r8S3L4pE61eDdt8igGj8rf5kjYR3ILxWIpWNi84= +github.com/google/cel-go v0.20.1/go.mod h1:kWcIzTsPX0zmQ+H3TirHstLLf9ep5QTsZBN9u4dOYLg= github.com/google/flatbuffers v1.11.0/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= github.com/google/flatbuffers v2.0.6+incompatible h1:XHFReMv7nFFusa+CEokzWbzaYocKXI6C7hdU5Kgh9Lw= github.com/google/flatbuffers v2.0.6+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= @@ -646,8 +668,8 @@ github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-containerregistry v0.15.2 h1:MMkSh+tjSdnmJZO7ljvEqV1DjfekB6VUEAZgy3a+TQE= -github.com/google/go-containerregistry v0.15.2/go.mod h1:wWK+LnOv4jXMM23IT/F1wdYftGWGr47Is8CG+pmHK1Q= +github.com/google/go-containerregistry v0.19.0 h1:uIsMRBV7m/HDkDxE/nXMnv1q+lOOSPlQ/ywc5JbB8Ic= +github.com/google/go-containerregistry v0.19.0/go.mod h1:u0qB2l7mvtWVR5kNcbFIhFY1hLbf8eeGapA+vbFDCtQ= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= @@ -662,6 +684,7 @@ github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hf github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20211214055906-6f57359322fd/go.mod h1:KgnwoLYCZ8IQu3XUZ8Nc/bM9CCZFOyjUNOSygVozoDg= +github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= github.com/google/pprof v0.0.0-20240827171923-fa2c70bbbfe5 h1:5iH8iuqE5apketRbSFBy+X1V0o+l+8NF1avt4HWl7cA= github.com/google/pprof v0.0.0-20240827171923-fa2c70bbbfe5/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= @@ -724,8 +747,8 @@ github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgf github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.15.2 h1:gDLXvp5S9izjldquuoAhDzccbskOL6tDC5jMSyx3zxE= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.15.2/go.mod h1:7pdNwVWBBHGiCxa9lAszqCJMbfTISJ7oMftp8+UGV08= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE= github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= @@ -764,6 +787,7 @@ github.com/iancoleman/orderedmap v0.0.0-20190318233801-ac98e3ecb4b0 h1:i462o439Z github.com/iancoleman/orderedmap v0.0.0-20190318233801-ac98e3ecb4b0/go.mod h1:N0Wam8K1arqPXNWjMo21EXnBPOPp36vB07FNRdD2geA= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20210905161508-09a460cdf81d/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w= +github.com/ianlancetaylor/demangle v0.0.0-20230524184225-eabc099b10ab/go.mod h1:gx7rwoVhcfuVKG5uya9Hs3Sxj7EIvldVofAWIUtGouw= github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/improbable-eng/grpc-web v0.15.0 h1:BN+7z6uNXZ1tQGcNAuaU1YjsLTApzkjt2tzCixLaUPQ= @@ -776,8 +800,8 @@ github.com/invopop/jsonschema v0.6.0 h1:8e+xY8ZEn8gDHUYylSlLHy22P+SLeIRIHv3nM3hC github.com/invopop/jsonschema v0.6.0/go.mod h1:O9uiLokuu0+MGFlyiaqtWxwqJm41/+8Nj0lD7A36YH0= github.com/jacobsa/go-serial v0.0.0-20180131005756-15cf729a72d4 h1:G2ztCwXov8mRvP0ZfjE6nAlaCX2XbykaeHdbT6KwDz0= github.com/jacobsa/go-serial v0.0.0-20180131005756-15cf729a72d4/go.mod h1:2RvX5ZjVtsznNZPEt4xwJXNJrM3VTZoQf7V6gk0ysvs= -github.com/jdxcode/netrc v0.0.0-20221124155335-4616370d1a84 h1:2uT3aivO7NVpUPGcQX7RbHijHMyWix/yCnIrCWc+5co= -github.com/jdxcode/netrc v0.0.0-20221124155335-4616370d1a84/go.mod h1:Zi/ZFkEqFHTm7qkjyNJjaWH4LQA9LQhGJyF0lTYGpxw= +github.com/jdx/go-netrc v1.0.0 h1:QbLMLyCZGj0NA8glAhxUpf1zDg6cxnWgMBbjq40W0gQ= +github.com/jdx/go-netrc v1.0.0/go.mod h1:Gh9eFQJnoTNIRHXl2j5bJXA1u84hQWJWgGh569zF3v8= github.com/jedib0t/go-pretty/v6 v6.4.6 h1:v6aG9h6Uby3IusSSEjHaZNXpHFhzqMmjXcPq1Rjl9Jw= github.com/jedib0t/go-pretty/v6 v6.4.6/go.mod h1:Ndk3ase2CkQbXLLNf5QDHoYb6J9WtVfmHZu9n8rk2xs= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= @@ -785,8 +809,8 @@ github.com/jgautheron/goconst v1.4.0/go.mod h1:aAosetZ5zaeC/2EfMeRswtxUFBpe2Hr7H github.com/jgautheron/goconst v1.7.1 h1:VpdAG7Ca7yvvJk5n8dMwQhfEZJh95kl/Hl9S1OI5Jkk= github.com/jgautheron/goconst v1.7.1/go.mod h1:aAosetZ5zaeC/2EfMeRswtxUFBpe2Hr7HzkgX4fanO4= github.com/jhump/protoreflect v1.10.3/go.mod h1:7GcYQDdMU/O/BBrl/cX6PNHpXh6cenjd8pneu5yW7Tg= -github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgfCL6c= -github.com/jhump/protoreflect v1.15.1/go.mod h1:jD/2GMKKE6OqX8qTjhADU1e6DShO+gavG9e0Q693nKo= +github.com/jhump/protoreflect v1.15.6 h1:WMYJbw2Wo+KOWwZFvgY0jMoVHM6i4XIvRs2RcBj5VmI= +github.com/jhump/protoreflect v1.15.6/go.mod h1:jCHoyYQIJnaabEYnbGwyo9hUqfyUMTbJw/tAut5t97E= github.com/jingyugao/rowserrcheck v0.0.0-20210130005344-c6a0c12dd98d/go.mod h1:/EZlaYCnEX24i7qdVhT9du5JrtFWYRQr67bVgR7JJC8= github.com/jingyugao/rowserrcheck v1.1.1 h1:zibz55j/MJtLsjP1OF4bSdgXxwL1b+Vn7Tjzq7gFzUs= github.com/jingyugao/rowserrcheck v1.1.1/go.mod h1:4yvlZSDb3IyDTUZJUmpZfm2Hwok+Dtp+nu2qOq+er9c= @@ -803,6 +827,7 @@ github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhB github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/jonboulle/clockwork v0.3.0 h1:9BSCMi8C+0qdApAp4auwX0RkLGUjs956h0EkuQymUhg= github.com/jonboulle/clockwork v0.3.0/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= @@ -840,8 +865,8 @@ github.com/klauspost/compress v1.10.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYs github.com/klauspost/compress v1.11.0/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.11.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= -github.com/klauspost/compress v1.16.5 h1:IFV2oUNUzZaz+XyusxpLzpzS8Pt5rh0Z16For/djlyI= -github.com/klauspost/compress v1.16.5/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/compress v1.17.7 h1:ehO88t2UGzQK66LMdE8tibEd1ErmzZjNEqWkjLAKQQg= +github.com/klauspost/compress v1.17.7/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= github.com/klauspost/cpuid v0.0.0-20180405133222-e7e905edc00e h1:+lIPJOWl+jSiJOc70QXJ07+2eg2Jy2EC7Mi11BWujeM= github.com/klauspost/cpuid v0.0.0-20180405133222-e7e905edc00e/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk= @@ -879,6 +904,7 @@ github.com/ldez/gomoddirectives v0.2.4 h1:j3YjBIjEBbqZ0NKtBNzr8rtMHTOrLPeiwTkfUJ github.com/ldez/gomoddirectives v0.2.4/go.mod h1:oWu9i62VcQDYp9EQ0ONTfqLNh+mDLWWDO+SO0qSQw5g= github.com/ldez/tagliatelle v0.5.0 h1:epgfuYt9v0CG3fms0pEgIMNPuFf/LpPIfjk4kyqSioo= github.com/ldez/tagliatelle v0.5.0/go.mod h1:rj1HmWiL1MiKQuOONhd09iySTEkUuE/8+5jtPYz9xa4= +github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= @@ -918,6 +944,7 @@ github.com/magefile/mage v1.10.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXq github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/maratori/testableexamples v1.0.0 h1:dU5alXRrD8WKSjOUnmJZuzdxWOEQ57+7s93SLMxb2vI= github.com/maratori/testableexamples v1.0.0/go.mod h1:4rhjL1n20TUTT4vdh3RDqSizKLyXp7K2u6HgraZCGzE= github.com/maratori/testpackage v1.0.1/go.mod h1:ddKdw+XG0Phzhx8BFDTKgpWP4i7MpApTE5fXSKAqwDU= @@ -1070,8 +1097,8 @@ github.com/onsi/gomega v1.34.2/go.mod h1:v1xfxRgk0KIsG+QOdm7p8UosrOzPYRo60fd3B/1 github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= -github.com/opencontainers/image-spec v1.1.0-rc3 h1:fzg1mXZFj8YdPeNkRXMg+zb88BFV0Ys52cJydRwBkb8= -github.com/opencontainers/image-spec v1.1.0-rc3/go.mod h1:X4pATf0uXsnn3g5aiGIsVnJBR4mxhKzfwmvK/B2NTm8= +github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= +github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis= github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74= github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= @@ -1080,6 +1107,7 @@ github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxS github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= +github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0= github.com/otiai10/copy v1.2.0/go.mod h1:rrF5dJ5F0t/EWSYODDu4j9/vEeYHMkc8jt0zJChqQWw= github.com/otiai10/copy v1.14.0 h1:dCI/t1iTdYGtkvCuBG2BgR6KZa83PTclw4U5n2wAllU= github.com/otiai10/copy v1.14.0/go.mod h1:ECfuL02W+/FkTWZWgQqXPWZgW9oeKCSQ5qVfSc4qc4w= @@ -1150,8 +1178,8 @@ github.com/pion/turn/v2 v2.1.6 h1:Xr2niVsiPTB0FPtt+yAWKFUkU1eotQbGgpTIld4x1Gc= github.com/pion/turn/v2 v2.1.6/go.mod h1:huEpByKKHix2/b9kmTAM3YoX6MKP+/D//0ClgUYR2fY= github.com/pion/webrtc/v3 v3.2.36 h1:RM/miAv0M4TrhhS7h2mcZXt44K68WmpVDkUOgz2l2l8= github.com/pion/webrtc/v3 v3.2.36/go.mod h1:wWQz1PuKNSNK4VrJJNpPN3vZmKEi4zA6i2ynaQOlxIU= -github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU= -github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= @@ -1239,8 +1267,8 @@ github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6So github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.6.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= -github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= -github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= @@ -1283,7 +1311,6 @@ github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6Mwd github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.8.0/go.mod h1:4GuYW9TZmE769R5STWrRakJc4UqQ3+QQ95fyz7ENv1A= -github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sivchari/containedctx v1.0.3 h1:x+etemjbsh2fB5ewm5FeLNi5bUjK0V8n0RB+Wwfd0XE= @@ -1332,6 +1359,8 @@ github.com/ssgreg/nlreturn/v2 v2.2.1 h1:X4XDI7jstt3ySqGU86YGAURbxw3oTDPK9sPEi6YE github.com/ssgreg/nlreturn/v2 v2.2.1/go.mod h1:E/iiPB78hV7Szg2YfRgyIrk1AD6JVMTRkkxBiELzh2I= github.com/stbenjam/no-sprintf-host-port v0.1.1 h1:tYugd/yrm1O0dV+ThCbaKZh195Dfm07ysF0U6JQXczc= github.com/stbenjam/no-sprintf-host-port v0.1.1/go.mod h1:TLhvtIvONRzdmkFiio4O8LHsN9N74I+PhRquPsxpL0I= +github.com/stoewer/go-strcase v1.3.0 h1:g0eASXYtp+yvN9fK8sH94oCIk0fau9uV1/ZdJ0AVEzs= +github.com/stoewer/go-strcase v1.3.0/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo= github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= @@ -1371,8 +1400,6 @@ github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3/go.mod h1: github.com/tetafro/godot v1.4.4/go.mod h1:FVDd4JuKliW3UgjswZfJfHq4vAx0bD/Jd5brJjGeaz4= github.com/tetafro/godot v1.4.17 h1:pGzu+Ye7ZUEFx7LHU0dAKmCOXWsPjl7qA6iMGndsjPs= github.com/tetafro/godot v1.4.17/go.mod h1:2oVxTBSftRTh4+MVfUaUXR6bn2GDXCaMcOG4Dk3rfio= -github.com/tetratelabs/wazero v1.2.0 h1:I/8LMf4YkCZ3r2XaL9whhA0VMyAvF6QE+O7rco0DCeQ= -github.com/tetratelabs/wazero v1.2.0/go.mod h1:wYx2gNRg8/WihJfSDxA1TIL8H+GkfLYm+bIfbblu9VQ= github.com/timakin/bodyclose v0.0.0-20200424151742-cb6215831a94/go.mod h1:Qimiffbc6q9tBWlVV6x0P9sat/ao1xEkREYPPj9hphk= github.com/timakin/bodyclose v0.0.0-20230421092635-574207250966 h1:quvGphlmUVU+nhpFa4gg4yJyTRJ13reZMDHrKwYw53M= github.com/timakin/bodyclose v0.0.0-20230421092635-574207250966/go.mod h1:27bSVNWSBOHm+qRp1T9qzaIpsWEP6TbUnei/43HK+PQ= @@ -1405,7 +1432,6 @@ github.com/ultraware/whitespace v0.1.1 h1:bTPOGejYFulW3PkcrqkeQwOd6NKOOXvmGD9bo/ github.com/ultraware/whitespace v0.1.1/go.mod h1:XcP1RLD81eV4BW8UhQlpaR+SDc2givTvyI8a586WjW8= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= -github.com/urfave/cli v1.22.12/go.mod h1:sSBEIC79qR6OvcmsD4U3KABeOTxDqQtdDnaFuUN30b8= github.com/urfave/cli/v2 v2.10.3 h1:oi571Fxz5aHugfBAJd5nkwSk3fzATXtMlpxdLylSCMo= github.com/urfave/cli/v2 v2.10.3/go.mod h1:f8iq5LtQ/bLxafbdBSLPPNsgaW0l/2fYYEHhAyPlwvo= github.com/uudashr/gocognit v1.0.1/go.mod h1:j44Ayx2KW4+oB6SWMv8KsmHzZrOInQav7D3cQMJ5JUM= @@ -1415,8 +1441,8 @@ github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyC github.com/valyala/fasthttp v1.16.0/go.mod h1:YOKImeEosDdBPnxc0gy7INqi3m1zK6A+xl6TwOBhHCA= github.com/valyala/quicktemplate v1.6.3/go.mod h1:fwPzK2fHuYEODzJ9pkw0ipCPNHZ2tD5KW4lOuSdPKzY= github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio= -github.com/vbatts/tar-split v0.11.3 h1:hLFqsOLQ1SsppQNTMpkpPXClLDfC2A3Zgy9OUU+RVck= -github.com/vbatts/tar-split v0.11.3/go.mod h1:9QlHN18E+fEH7RdG+QAJJcuya3rqT7eXSTY7wGrAokY= +github.com/vbatts/tar-split v0.11.5 h1:3bHCTIheBm1qFTcgh9oPu+nNBtX+XJIupG/vacinCts= +github.com/vbatts/tar-split v0.11.5/go.mod h1:yZbwRsSeGjusneWgA781EKej9HF8vme8okylkAeNKLk= github.com/viamrobotics/evdev v0.1.3 h1:mR4HFafvbc5Wx4Vp1AUJp6/aITfVx9AKyXWx+rWjpfc= github.com/viamrobotics/evdev v0.1.3/go.mod h1:N6nuZmPz7HEIpM7esNWwLxbYzqWqLSZkfI/1Sccckqk= github.com/viamrobotics/webrtc/v3 v3.99.10 h1:ykE14wm+HkqMD5Ozq4rvhzzfvnXAu14ak/HzA1OCzfY= @@ -1487,15 +1513,23 @@ go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.5 go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0/go.mod h1:B9yO6b04uB80CzjedvewuqDhxJxi11s7/GtiGa8bAjI= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 h1:TT4fX+nBOA/+LUkobKGW1ydGcn+G3vRw9+g5HwCphpk= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0/go.mod h1:L7UH0GbB0p47T4Rri3uHjbpCFYrVrwc1I25QhNPiGK8= -go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw= -go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8= -go.opentelemetry.io/otel/metric v1.29.0 h1:vPf/HFWTNkPu1aYeIsc98l4ktOQaL6LeSoeV2g+8YLc= -go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8= -go.opentelemetry.io/otel/sdk v1.28.0 h1:b9d7hIry8yZsgtbmM0DKyPWMMUMlK9NEKuIG4aBqWyE= -go.opentelemetry.io/otel/sdk v1.28.0/go.mod h1:oYj7ClPUA7Iw3m+r7GeEjz0qckQRJK2B8zjcZEfu7Pg= -go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4= -go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ= +go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY= +go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.31.0 h1:K0XaT3DwHAcV4nKLzcQvwAgSyisUghWoY20I7huthMk= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.31.0/go.mod h1:B5Ki776z/MBnVha1Nzwp5arlzBbE3+1jk+pGmaP5HME= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.24.0 h1:Xw8U6u2f8DK2XAkGRFV7BBLENgnTGX9i4rQRxJf+/vs= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.24.0/go.mod h1:6KW1Fm6R/s6Z3PGXwSJN2K4eT6wQB3vXX6CVnYX9NmM= +go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE= +go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY= +go.opentelemetry.io/otel/sdk v1.31.0 h1:xLY3abVHYZ5HSfOg3l2E5LUj2Cwva5Y7yGxnSW9H5Gk= +go.opentelemetry.io/otel/sdk v1.31.0/go.mod h1:TfRbMdhvxIIr/B2N2LQW2S5v9m3gOQ/08KsbbO5BPT0= +go.opentelemetry.io/otel/sdk/metric v1.19.0 h1:EJoTO5qysMsYCa+w4UghwFV/ptQgqSL/8Ni+hx+8i1k= +go.opentelemetry.io/otel/sdk/metric v1.19.0/go.mod h1:XjG0jQyFJrv2PbMvwND7LwCEhsJzCzV5210euduKcKY= +go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys= +go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= +go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= @@ -1521,8 +1555,8 @@ go.uber.org/zap v1.18.1/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= go.uber.org/zap v1.23.0/go.mod h1:D+nX8jyLsMHMYrln8A0rJjFt/T/9/bGgIhAqxv5URuY= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= -go.viam.com/api v0.1.357 h1:L9LBYbaH0imv/B+mVxqtSgClIl4flzjLV6LclfnD9Nc= -go.viam.com/api v0.1.357/go.mod h1:5lpVRxMsKFCaahqsnJfPGwJ9baoQ6PIKQu3lxvy6Wtw= +go.viam.com/api v0.1.360 h1:jpcm7mxUy2RvmZGAjUpx6RP+/8341XdlRLSX/3mll0g= +go.viam.com/api v0.1.360/go.mod h1:g5eipXHNm0rQmW7DWya6avKcmzoypLmxnMlAaIsE5Ls= go.viam.com/test v1.2.3 h1:tT2QqthC2BL2tiloUC2T1AIwuLILyMRx8mmxunN+cT4= go.viam.com/test v1.2.3/go.mod h1:5pXMnEyvTygilOCaFtonnKNMqsCCBbe2ZXU8ZsJ2zjY= go.viam.com/utils v0.1.112 h1:yuVkNITUijdP/CMI3BaDozUMZwP4Ari57BvRQfORFK0= @@ -1760,7 +1794,6 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -1771,12 +1804,12 @@ golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20211105183446-c75c47738b0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220906165534-d0df966e6959/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -2052,14 +2085,14 @@ google.golang.org/protobuf v1.25.1-0.20200805231151-a709e31e5d12/go.mod h1:9JNX7 google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= -google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= +google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= diff --git a/services/datamanager/builtin/capture/capture.go b/services/datamanager/builtin/capture/capture.go index 75112cba583..47d10b700ea 100644 --- a/services/datamanager/builtin/capture/capture.go +++ b/services/datamanager/builtin/capture/capture.go @@ -324,7 +324,7 @@ func (c *Capture) initializeOrUpdateCollector( return nil, errors.Wrapf(err, "failed to create target directory %s with 700 file permissions", targetDir) } // Build metadata. - captureMetadata := data.BuildCaptureMetadata( + captureMetadata, dataType := data.BuildCaptureMetadata( collectorConfig.Name.API, collectorConfig.Name.ShortName(), collectorConfig.Method, @@ -337,6 +337,7 @@ func (c *Capture) initializeOrUpdateCollector( bufferSize := defaultIfZeroVal(collectorConfig.CaptureBufferSize, defaultCaptureBufferSize) collector, err := collectorConstructor(res, data.CollectorParams{ MongoCollection: collection, + DataType: dataType, ComponentName: collectorConfig.Name.ShortName(), ComponentType: collectorConfig.Name.API.String(), MethodName: collectorConfig.Method, diff --git a/services/slam/collector.go b/services/slam/collector.go index 185ea71eaf2..0c43374832f 100644 --- a/services/slam/collector.go +++ b/services/slam/collector.go @@ -4,6 +4,7 @@ import ( "context" pb "go.viam.com/api/service/slam/v1" + uprotoutils "go.viam.com/utils/protoutils" "google.golang.org/protobuf/types/known/anypb" "go.viam.com/rdk/data" @@ -33,12 +34,22 @@ func newPositionCollector(resource interface{}, params data.CollectorParams) (da return nil, err } - cFunc := data.CaptureFunc(func(ctx context.Context, _ map[string]*anypb.Any) (interface{}, error) { + cFunc := data.CaptureFunc(func(ctx context.Context, _ map[string]*anypb.Any) (data.CaptureResult, error) { + var res data.CaptureResult pose, err := slam.Position(ctx) if err != nil { - return nil, data.FailedToReadErr(params.ComponentName, position.String(), err) + return res, data.FailedToReadErr(params.ComponentName, position.String(), err) } - return &pb.GetPositionResponse{Pose: spatialmath.PoseToProtobuf(pose)}, nil + readings, err := uprotoutils.StructToStructPbIgnoreOmitEmpty(&pb.GetPositionResponse{Pose: spatialmath.PoseToProtobuf(pose)}) + if err != nil { + return res, err + } + return data.CaptureResult{ + Type: data.CaptureTypeTabular, + TabularData: data.TabularData{ + Payload: readings, + }, + }, nil }) return data.NewCollector(cFunc, params) } @@ -49,19 +60,26 @@ func newPointCloudMapCollector(resource interface{}, params data.CollectorParams return nil, err } - cFunc := data.CaptureFunc(func(ctx context.Context, _ map[string]*anypb.Any) (interface{}, error) { + cFunc := data.CaptureFunc(func(ctx context.Context, _ map[string]*anypb.Any) (data.CaptureResult, error) { + var res data.CaptureResult // edited maps do not need to be captured because they should not be modified f, err := slam.PointCloudMap(ctx, false) if err != nil { - return nil, data.FailedToReadErr(params.ComponentName, pointCloudMap.String(), err) + return res, data.FailedToReadErr(params.ComponentName, pointCloudMap.String(), err) } pcd, err := HelperConcatenateChunksToFull(f) if err != nil { - return nil, data.FailedToReadErr(params.ComponentName, pointCloudMap.String(), err) + return res, data.FailedToReadErr(params.ComponentName, pointCloudMap.String(), err) } - return pcd, nil + return data.CaptureResult{ + Type: data.CaptureTypeBinary, + Binaries: []data.Binary{{ + Payload: pcd, + MimeType: data.MimeTypeApplicationPcd, + }}, + }, nil }) return data.NewCollector(cFunc, params) } diff --git a/services/vision/collector.go b/services/vision/collector.go index 3bb875e4e00..fa331980405 100644 --- a/services/vision/collector.go +++ b/services/vision/collector.go @@ -4,8 +4,6 @@ import ( "context" "github.com/pkg/errors" - servicepb "go.viam.com/api/service/vision/v1" - "go.viam.com/utils/protoutils" "google.golang.org/protobuf/types/known/anypb" "google.golang.org/protobuf/types/known/wrapperspb" @@ -29,12 +27,6 @@ func (m method) String() string { return "Unknown" } -type extraFields struct { - Height int - Width int - MimeType string -} - type methodParamsDecoded struct { cameraName string mimeType string @@ -53,81 +45,88 @@ func newCaptureAllFromCameraCollector(resource interface{}, params data.Collecto } cameraName := decodedParams.cameraName - mimeType := decodedParams.mimeType minConfidenceScore := decodedParams.minConfidence - cFunc := data.CaptureFunc(func(ctx context.Context, _ map[string]*anypb.Any) (interface{}, error) { + cFunc := data.CaptureFunc(func(ctx context.Context, _ map[string]*anypb.Any) (data.CaptureResult, error) { + var res data.CaptureResult visCaptureOptions := viscapture.CaptureOptions{ ReturnImage: true, ReturnDetections: true, ReturnClassifications: true, - ReturnObject: true, } visCapture, err := vision.CaptureAllFromCamera(ctx, cameraName, visCaptureOptions, data.FromDMExtraMap) if err != nil { // A modular filter component can be created to filter the readings from a service. The error ErrNoCaptureToStore // is used in the datamanager to exclude readings from being captured and stored. if errors.Is(err, data.ErrNoCaptureToStore) { - return nil, err + return res, err } - return nil, data.FailedToReadErr(params.ComponentName, captureAllFromCamera.String(), err) + return res, data.FailedToReadErr(params.ComponentName, captureAllFromCamera.String(), err) + } + + if visCapture.Image == nil { + return res, errors.New("vision service didn't return an image") } protoImage, err := imageToProto(ctx, visCapture.Image, cameraName) if err != nil { - return nil, err + return res, err } - filteredDetections := []objectdetection.Detection{} - for _, elem := range visCapture.Detections { - if elem.Score() >= minConfidenceScore { - filteredDetections = append(filteredDetections, elem) - } + var width, height int + if visCapture.Image != nil { + width = visCapture.Image.Bounds().Dx() + height = visCapture.Image.Bounds().Dy() } - protoDetections := detsToProto(filteredDetections) - - filteredClassifications := classification.Classifications{} - for _, elem := range visCapture.Classifications { - if elem.Score() >= minConfidenceScore { - filteredClassifications = append(filteredClassifications, elem) + filteredBoundingBoxes := []data.BoundingBox{} + for _, d := range visCapture.Detections { + if score := d.Score(); score >= minConfidenceScore { + filteredBoundingBoxes = append(filteredBoundingBoxes, toDataBoundingBox(d, width, height)) } } - protoClassifications := clasToProto(filteredClassifications) - - protoObjects, err := segmentsToProto(cameraName, visCapture.Objects) - if err != nil { - return nil, err - } - - // We need this to pass in the height & width of an image in order to calculate - // the normalized coordinate values of any bounding boxes. We also need the - // mimeType to appropriately upload the image. - bounds := extraFields{} - - if visCapture.Image != nil { - bounds = extraFields{ - Height: visCapture.Image.Bounds().Dy(), - Width: visCapture.Image.Bounds().Dx(), - MimeType: mimeType, + filteredClassifications := []data.Classification{} + for _, c := range visCapture.Classifications { + if score := c.Score(); score >= minConfidenceScore { + filteredClassifications = append(filteredClassifications, toDataClassification(c)) } } - boundsPb, err := protoutils.StructToStructPb(bounds) - if err != nil { - return nil, err - } - - return &servicepb.CaptureAllFromCameraResponse{ - Image: protoImage, Detections: protoDetections, Classifications: protoClassifications, - Objects: protoObjects, Extra: boundsPb, + return data.CaptureResult{ + Type: data.CaptureTypeBinary, + Binaries: []data.Binary{{ + Payload: protoImage.Image, + MimeType: data.CameraFormatToMimeType(protoImage.Format), + Annotations: data.Annotations{ + BoundingBoxes: filteredBoundingBoxes, + Classifications: filteredClassifications, + }, + }}, }, nil }) return data.NewCollector(cFunc, params) } +func toDataClassification(c classification.Classification) data.Classification { + confidence := c.Score() + return data.Classification{Label: c.Label(), Confidence: &confidence} +} + +func toDataBoundingBox(d objectdetection.Detection, width, height int) data.BoundingBox { + confidence := d.Score() + bbox := d.BoundingBox() + return data.BoundingBox{ + Label: d.Label(), + Confidence: &confidence, + XMinNormalized: float64(bbox.Min.X) / float64(width), + XMaxNormalized: float64(bbox.Max.X) / float64(width), + YMinNormalized: float64(bbox.Min.Y) / float64(height), + YMaxNormalized: float64(bbox.Max.Y) / float64(height), + } +} + func additionalParamExtraction(methodParams map[string]*anypb.Any) (methodParamsDecoded, error) { cameraParam := methodParams["camera_name"] diff --git a/services/vision/collectors_test.go b/services/vision/collectors_test.go index 1c05a93c5c0..2e843d1092e 100644 --- a/services/vision/collectors_test.go +++ b/services/vision/collectors_test.go @@ -1,33 +1,38 @@ package vision_test import ( + "bytes" "context" + "encoding/base64" "image" + "io" "strconv" "testing" "time" - clk "github.com/benbjohnson/clock" - v1 "go.viam.com/api/common/v1" - camerapb "go.viam.com/api/component/camera/v1" - pb "go.viam.com/api/service/vision/v1" + "github.com/benbjohnson/clock" + datapb "go.viam.com/api/app/data/v1" + datasyncpb "go.viam.com/api/app/datasync/v1" "go.viam.com/test" - "go.viam.com/utils/protoutils" "google.golang.org/protobuf/reflect/protoreflect" "google.golang.org/protobuf/types/known/anypb" "google.golang.org/protobuf/types/known/wrapperspb" "go.viam.com/rdk/data" "go.viam.com/rdk/logging" + "go.viam.com/rdk/rimage" visionservice "go.viam.com/rdk/services/vision" tu "go.viam.com/rdk/testutils" "go.viam.com/rdk/testutils/inject" - vision "go.viam.com/rdk/vision" + "go.viam.com/rdk/utils" "go.viam.com/rdk/vision/classification" "go.viam.com/rdk/vision/objectdetection" "go.viam.com/rdk/vision/viscapture" ) +//nolint:lll +var viamLogoJpegB64 = []byte("/9j/4QD4RXhpZgAATU0AKgAAAAgABwESAAMAAAABAAEAAAEaAAUAAAABAAAAYgEbAAUAAAABAAAAagEoAAMAAAABAAIAAAExAAIAAAAhAAAAcgITAAMAAAABAAEAAIdpAAQAAAABAAAAlAAAAAAAAABIAAAAAQAAAEgAAAABQWRvYmUgUGhvdG9zaG9wIDIzLjQgKE1hY2ludG9zaCkAAAAHkAAABwAAAAQwMjIxkQEABwAAAAQBAgMAoAAABwAAAAQwMTAwoAEAAwAAAAEAAQAAoAIABAAAAAEAAAAgoAMABAAAAAEAAAAgpAYAAwAAAAEAAAAAAAAAAAAA/9sAhAAcHBwcHBwwHBwwRDAwMERcRERERFx0XFxcXFx0jHR0dHR0dIyMjIyMjIyMqKioqKioxMTExMTc3Nzc3Nzc3NzcASIkJDg0OGA0NGDmnICc5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ub/3QAEAAL/wAARCAAgACADASIAAhEBAxEB/8QBogAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoLEAACAQMDAgQDBQUEBAAAAX0BAgMABBEFEiExQQYTUWEHInEUMoGRoQgjQrHBFVLR8CQzYnKCCQoWFxgZGiUmJygpKjQ1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4eLj5OXm5+jp6vHy8/T19vf4+foBAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKCxEAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwDm6K0dNu1tZsSgGNuDx0961NX09WT7ZbgcD5gPT1oA5qiul0fT1VPtlwByPlB7D1rL1K7W5mxEAI04GBjPvQB//9Dm66TRr/I+xTf8A/wrm6ASpBXgjpQB0ms34UfYof8AgWP5VzdBJY5PJNFAH//Z") + type fakeDetection struct { boundingBox *image.Rectangle score float64 @@ -39,18 +44,9 @@ type fakeClassification struct { label string } -type extraFields struct { - Height int - Width int - MimeType string -} - const ( serviceName = "vision" - captureInterval = time.Second - numRetries = 5 - testName1 = "CaptureAllFromCameraCollector returns non-empty CaptureAllFromCameraResp" - testName2 = "CaptureAllFromCameraCollector w/ Classifications & Detections < 0.5 returns empty CaptureAllFromCameraResp" + captureInterval = time.Millisecond ) var fakeDetections = []objectdetection.Detection{ @@ -77,7 +73,7 @@ var fakeDetections2 = []objectdetection.Detection{ var fakeClassifications = []classification.Classification{ &fakeClassification{ - score: 0.95, + score: 0.85, label: "cat", }, } @@ -89,12 +85,6 @@ var fakeClassifications2 = []classification.Classification{ }, } -var fakeObjects = []*vision.Object{} - -var extra = extraFields{} - -var fakeExtraFields, _ = protoutils.StructToStructPb(extra) - func (fc *fakeClassification) Score() float64 { return fc.score } @@ -115,42 +105,6 @@ func (fd *fakeDetection) Label() string { return fd.label } -func clasToProto(classifications classification.Classifications) []*pb.Classification { - protoCs := make([]*pb.Classification, 0, len(classifications)) - for _, c := range classifications { - cc := &pb.Classification{ - ClassName: c.Label(), - Confidence: c.Score(), - } - protoCs = append(protoCs, cc) - } - return protoCs -} - -func detsToProto(detections []objectdetection.Detection) []*pb.Detection { - protoDets := make([]*pb.Detection, 0, len(detections)) - for _, det := range detections { - box := det.BoundingBox() - if box == nil { - return nil - } - xMin := int64(box.Min.X) - yMin := int64(box.Min.Y) - xMax := int64(box.Max.X) - yMax := int64(box.Max.Y) - d := &pb.Detection{ - XMin: &xMin, - YMin: &yMin, - XMax: &xMax, - YMax: &yMax, - Confidence: det.Score(), - ClassName: det.Label(), - } - protoDets = append(protoDets, d) - } - return protoDets -} - func convertStringMapToAnyPBMap(params map[string]string) (map[string]*anypb.Any, error) { methodParams := map[string]*anypb.Any{} for key, paramVal := range params { @@ -186,97 +140,112 @@ func convertStringToAnyPB(str string) (*anypb.Any, error) { var methodParams, _ = convertStringMapToAnyPBMap(map[string]string{"camera_name": "camera-1", "mime_type": "image/jpeg"}) func TestCollectors(t *testing.T) { + viamLogoJpeg, err := io.ReadAll(base64.NewDecoder(base64.StdEncoding, bytes.NewReader(viamLogoJpegB64))) + test.That(t, err, test.ShouldBeNil) + img := rimage.NewLazyEncodedImage(viamLogoJpeg, utils.MimeTypeJPEG) + // 32 x 32 image + test.That(t, img.Bounds().Dx(), test.ShouldEqual, 32) + test.That(t, img.Bounds().Dy(), test.ShouldEqual, 32) + bboxConf := 0.95 + classConf := 0.85 tests := []struct { name string collector data.CollectorConstructor - expected map[string]any + expected *datasyncpb.SensorData + vision visionservice.Service }{ { - name: testName1, + name: "CaptureAllFromCameraCollector returns non-empty CaptureAllFromCameraResp", collector: visionservice.NewCaptureAllFromCameraCollector, - expected: tu.ToProtoMapIgnoreOmitEmpty(pb.CaptureAllFromCameraResponse{ - Image: &camerapb.Image{}, - Classifications: clasToProto(fakeClassifications), - Detections: detsToProto(fakeDetections), - Objects: []*v1.PointCloudObject{}, - Extra: fakeExtraFields, - }), + expected: &datasyncpb.SensorData{ + Metadata: &datasyncpb.SensorMetadata{ + MimeType: datasyncpb.MimeType_MIME_TYPE_IMAGE_JPEG, + Annotations: &datapb.Annotations{ + Bboxes: []*datapb.BoundingBox{ + { + Label: "cat", + XMinNormalized: 0.3125, + YMinNormalized: 0.625, + XMaxNormalized: 3.4375, + YMaxNormalized: 3.75, + Confidence: &bboxConf, + }, + }, + Classifications: []*datapb.Classification{{ + Label: "cat", + Confidence: &classConf, + }}, + }, + }, + Data: &datasyncpb.SensorData_Binary{Binary: viamLogoJpeg}, + }, + vision: newVisionService(img), }, { - name: testName2, + name: "CaptureAllFromCameraCollector w/ Classifications & Detections < 0.5 returns empty CaptureAllFromCameraResp", collector: visionservice.NewCaptureAllFromCameraCollector, - expected: tu.ToProtoMapIgnoreOmitEmpty(pb.CaptureAllFromCameraResponse{ - Image: &camerapb.Image{}, - Classifications: clasToProto([]classification.Classification{}), - Detections: detsToProto([]objectdetection.Detection{}), - Objects: []*v1.PointCloudObject{}, - Extra: fakeExtraFields, - }), + expected: &datasyncpb.SensorData{ + Metadata: &datasyncpb.SensorMetadata{ + MimeType: datasyncpb.MimeType_MIME_TYPE_IMAGE_JPEG, + Annotations: &datapb.Annotations{}, + }, + Data: &datasyncpb.SensorData_Binary{Binary: viamLogoJpeg}, + }, + vision: newVisionService2(img), }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { - mockClock := clk.NewMock() - buf := tu.MockBuffer{} + start := time.Now() + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + buf := tu.NewMockBuffer(ctx) params := data.CollectorParams{ + DataType: data.CaptureTypeBinary, ComponentName: serviceName, Interval: captureInterval, Logger: logging.NewTestLogger(t), - Clock: mockClock, - Target: &buf, + Clock: clock.New(), + Target: buf, MethodParams: methodParams, } - var vision visionservice.Service - if tc.name == testName1 { - vision = newVisionService() - } else if tc.name == testName2 { - vision = newVisionService2() - } - - col, err := tc.collector(vision, params) + col, err := tc.collector(tc.vision, params) test.That(t, err, test.ShouldBeNil) defer col.Close() col.Collect() - mockClock.Add(captureInterval) - tu.Retry(func() bool { - return buf.Length() != 0 - }, numRetries) - test.That(t, buf.Length(), test.ShouldBeGreaterThan, 0) - test.That(t, buf.Writes[0].GetStruct().AsMap(), test.ShouldResemble, tc.expected) + tu.CheckMockBufferWrites(t, ctx, start, buf.BinaryWrites, tc.expected) }) } } -func newVisionService() visionservice.Service { +func newVisionService(img image.Image) visionservice.Service { v := &inject.VisionService{} v.CaptureAllFromCameraFunc = func(ctx context.Context, cameraName string, opts viscapture.CaptureOptions, extra map[string]interface{}, ) (viscapture.VisCapture, error) { return viscapture.VisCapture{ - Image: nil, + Image: img, Detections: fakeDetections, Classifications: fakeClassifications, - Objects: fakeObjects, }, nil } return v } -func newVisionService2() visionservice.Service { +func newVisionService2(img image.Image) visionservice.Service { v := &inject.VisionService{} v.CaptureAllFromCameraFunc = func(ctx context.Context, cameraName string, opts viscapture.CaptureOptions, extra map[string]interface{}, ) (viscapture.VisCapture, error) { return viscapture.VisCapture{ - Image: nil, + Image: img, Detections: fakeDetections2, Classifications: fakeClassifications2, - Objects: fakeObjects, }, nil } diff --git a/testutils/file_utils.go b/testutils/file_utils.go index f6fbe4252cf..29c340b068a 100644 --- a/testutils/file_utils.go +++ b/testutils/file_utils.go @@ -1,16 +1,18 @@ package testutils import ( + "context" "errors" "io" "os" "os/exec" "path/filepath" "runtime" - "sync" "testing" + "time" v1 "go.viam.com/api/app/datasync/v1" + "go.viam.com/test" "go.viam.com/rdk/utils" ) @@ -100,22 +102,79 @@ func BuildTempModuleWithFirstRun(tb testing.TB, modDir string) string { // MockBuffer is a buffered writer that just appends data to an array to read // without needing a real file system for testing. type MockBuffer struct { - lock sync.Mutex - Writes []*v1.SensorData + ctx context.Context + BinaryWrites chan []*v1.SensorData + TabularWrites chan []*v1.SensorData } -// Write adds a collected sensor reading to the array. -func (m *MockBuffer) Write(item *v1.SensorData) error { - m.lock.Lock() - defer m.lock.Unlock() - m.Writes = append(m.Writes, item) +// NewMockBuffer returns a mock buffer. +func NewMockBuffer(ctx context.Context) *MockBuffer { + return &MockBuffer{ + ctx: ctx, + BinaryWrites: make(chan []*v1.SensorData, 1), + TabularWrites: make(chan []*v1.SensorData, 1), + } +} + +// CheckMockBufferWrites checks that the writes to either +// MockBuffer.BinaryWrites or MockBuffer.TabularWrites +// are match the expected data & metadata (timestamps). +func CheckMockBufferWrites( + t *testing.T, + ctx context.Context, + start time.Time, + writes chan []*v1.SensorData, + expected *v1.SensorData, +) { + select { + case <-ctx.Done(): + t.Error("timeout") + t.FailNow() + case writes := <-writes: + end := time.Now() + test.That(t, len(writes), test.ShouldEqual, 1) + write := writes[0] + // nil out to make comparable + requestedAt := write.Metadata.TimeRequested.AsTime() + receivedAt := write.Metadata.TimeReceived.AsTime() + test.That(t, start, test.ShouldHappenOnOrBefore, requestedAt) + test.That(t, requestedAt, test.ShouldHappenOnOrBefore, receivedAt) + test.That(t, requestedAt, test.ShouldHappenOnOrBefore, receivedAt) + test.That(t, receivedAt, test.ShouldHappenOnOrBefore, end) + // nil out to make comparable + write.Metadata.TimeRequested = nil + write.Metadata.TimeReceived = nil + test.That(t, write, test.ShouldResemble, expected) + } +} + +// WriteBinary adds binary readings to the array. +func (m *MockBuffer) WriteBinary(items []*v1.SensorData) error { + if err := m.ctx.Err(); err != nil { + return err + } + select { + case m.BinaryWrites <- items: + case <-m.ctx.Done(): + } + return nil +} + +// WriteTabular adds tabular readings to the array. +func (m *MockBuffer) WriteTabular(items []*v1.SensorData) error { + if err := m.ctx.Err(); err != nil { + return err + } + + select { + case m.TabularWrites <- items: + case <-m.ctx.Done(): + } return nil } // Flush does nothing in this implementation as all data will be stored in memory. func (m *MockBuffer) Flush() error { - m.lock.Lock() - defer m.lock.Unlock() return nil } @@ -123,10 +182,3 @@ func (m *MockBuffer) Flush() error { func (m *MockBuffer) Path() string { return "/mock/dir" } - -// Length gets the length of the buffer without race conditions. -func (m *MockBuffer) Length() int { - m.lock.Lock() - defer m.lock.Unlock() - return len(m.Writes) -}