diff --git a/apps/checker/pkg/job/attempts_test.go b/apps/checker/pkg/job/attempts_test.go new file mode 100644 index 000000000..6fa76239e --- /dev/null +++ b/apps/checker/pkg/job/attempts_test.go @@ -0,0 +1,100 @@ +package job_test + +import ( + "context" + "net" + "net/http" + "net/http/httptest" + "sync/atomic" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/openstatushq/openstatus/apps/checker/pkg/job" + v1 "github.com/openstatushq/openstatus/apps/checker/proto/private_location/v1" +) + +func closedAddr(t *testing.T) string { + t.Helper() + listener, err := net.Listen("tcp", "127.0.0.1:0") + require.NoError(t, err) + addr := listener.Addr().String() + require.NoError(t, listener.Close()) + + return addr +} + +func TestHTTPJob_CollectsFailedAttempts(t *testing.T) { + var calls atomic.Int32 + target := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + if calls.Add(1) == 1 { + w.WriteHeader(http.StatusServiceUnavailable) + + return + } + w.WriteHeader(http.StatusOK) + })) + defer target.Close() + + data, err := job.NewJobRunner().HTTPJob(context.Background(), &v1.HTTPMonitor{ + Url: target.URL, Method: http.MethodGet, Timeout: 5000, Retry: 3, + }, "test-region") + require.NoError(t, err) + + assert.Equal(t, "success", data.RequestStatus) + require.Len(t, data.Attempts, 1) + assert.Equal(t, http.StatusServiceUnavailable, data.Attempts[0].StatusCode) + assert.EqualValues(t, 1, data.Attempts[0].Error) + assert.NotEqual(t, data.ID, data.Attempts[0].ID) +} + +func TestHTTPJob_FinalCheckerErrorIsReported(t *testing.T) { + data, err := job.NewJobRunner().HTTPJob(context.Background(), &v1.HTTPMonitor{ + Url: "http://" + closedAddr(t), Method: "BAD METHOD", Timeout: 1000, Retry: 2, + }, "test-region") + require.NoError(t, err, "a check checker.Http could not run must still be ingested") + + assert.EqualValues(t, 1, data.Error) + assert.Equal(t, "error", data.RequestStatus) + assert.Contains(t, data.Message, "unable to ping") + assert.Positive(t, data.Timestamp) + require.Len(t, data.Attempts, 1) + assert.EqualValues(t, 1, data.Attempts[0].Error) +} + +func TestTCPJob_CollectsFailedAttempts(t *testing.T) { + data, err := job.NewJobRunner().TCPJob(context.Background(), &v1.TCPMonitor{ + Uri: closedAddr(t), Timeout: 1000, Retry: 2, + }, "test-region") + require.NoError(t, err) + + assert.EqualValues(t, 1, data.Error) + require.Len(t, data.Attempts, 1) + assert.EqualValues(t, 1, data.Attempts[0].Error) + assert.NotEqual(t, data.ID, data.Attempts[0].ID) +} + +func TestGRPCJob_CollectsFailedAttempts(t *testing.T) { + data, err := job.NewJobRunner().GRPCJob(context.Background(), &v1.GRPCMonitor{ + Uri: closedAddr(t), TlsMode: "plaintext", Timeout: 500, Retry: 2, + }, "test-region") + require.NoError(t, err) + + assert.EqualValues(t, 1, data.Error) + require.Len(t, data.Attempts, 1) + assert.EqualValues(t, 1, data.Attempts[0].Error) + assert.NotEmpty(t, data.Attempts[0].Timing) +} + +func TestDNSJob_BudgetExpiryDoesNotRepeatTheFinalFailure(t *testing.T) { + data, err := job.NewJobRunner().DNSJob(context.Background(), &v1.DNSMonitor{ + Uri: "attempts.invalid", Timeout: 1, Retry: 3, + }) + require.NoError(t, err) + + assert.EqualValues(t, 1, data.Error) + for _, attempt := range data.Attempts { + assert.NotEqual(t, data.ID, attempt.ID) + } +} diff --git a/apps/checker/pkg/job/dns_job.go b/apps/checker/pkg/job/dns_job.go index 4d2325fe3..d60f1bcac 100644 --- a/apps/checker/pkg/job/dns_job.go +++ b/apps/checker/pkg/job/dns_job.go @@ -24,6 +24,8 @@ type DNSPrivateRegionData struct { CronTimestamp int64 `json:"cronTimestamp"` Timestamp int64 `json:"timestamp"` Error uint8 `json:"error"` + + Attempts []*DNSPrivateRegionData `json:"attempts,omitempty"` } func ProtoRecordAssertionToComparator(assertion v1.RecordComparator) (request.RecordComparator, error) { @@ -103,8 +105,11 @@ func (jobRunner) DNSJob(ctx context.Context, monitor *v1.DNSMonitor) (*DNSPrivat defer cancel() } - var called int - var lastFailure *DNSPrivateRegionData + var ( + called int + lastFailure *DNSPrivateRegionData + attempts []*DNSPrivateRegionData + ) op := func() (*DNSPrivateRegionData, error) { called++ @@ -127,8 +132,13 @@ func (jobRunner) DNSJob(ctx context.Context, monitor *v1.DNSMonitor) (*DNSPrivat lastFailure = data if called < int(retry) { + attempts = append(attempts, data) + return nil, fmt.Errorf("DNS lookup failed for %s: %w", monitor.Uri, lookupErr) } + + data.Attempts = attempts + return data, nil } @@ -142,6 +152,8 @@ func (jobRunner) DNSJob(ctx context.Context, monitor *v1.DNSMonitor) (*DNSPrivat data.RequestStatus = "error" data.Error = 1 data.Message = fmt.Sprintf("invalid DNS assertion for %s: %s", monitor.Uri, assertErr) + data.Attempts = attempts + return data, nil } @@ -153,6 +165,8 @@ func (jobRunner) DNSJob(ctx context.Context, monitor *v1.DNSMonitor) (*DNSPrivat lastFailure = data if called < int(retry) { + attempts = append(attempts, data) + return nil, errors.New(data.Message) } case degradedAfter > 0 && latency > degradedAfter: @@ -161,6 +175,8 @@ func (jobRunner) DNSJob(ctx context.Context, monitor *v1.DNSMonitor) (*DNSPrivat data.RequestStatus = "success" } + data.Attempts = attempts + return data, nil } @@ -171,6 +187,12 @@ func (jobRunner) DNSJob(ctx context.Context, monitor *v1.DNSMonitor) (*DNSPrivat // backoff.Retry discards the operation result when the context expires, so // without this the pending failure is lost and no outage is ingested. if err != nil && lastFailure != nil && errors.Is(err, errRetryBudgetExpired) { + if n := len(attempts); n > 0 && attempts[n-1] == lastFailure { + attempts = attempts[:n-1] + } + + lastFailure.Attempts = attempts + return lastFailure, nil } diff --git a/apps/checker/pkg/job/grpc_job.go b/apps/checker/pkg/job/grpc_job.go index ee592d996..c1234f90b 100644 --- a/apps/checker/pkg/job/grpc_job.go +++ b/apps/checker/pkg/job/grpc_job.go @@ -28,6 +28,8 @@ type GRPCPrivateRegionData struct { CronTimestamp int64 `json:"cron_timestamp"` Error int `json:"error"` Timing string `json:"timing"` + + Attempts []*GRPCPrivateRegionData `json:"attempts,omitempty"` } func (jobRunner) GRPCJob(ctx context.Context, monitor *v1.GRPCMonitor, region string) (*GRPCPrivateRegionData, error) { @@ -43,8 +45,11 @@ func (jobRunner) GRPCJob(ctx context.Context, monitor *v1.GRPCMonitor, region st req := grpcCheckerRequest(monitor) - var called int - var lastResult checker.GRPCResponse + var ( + called int + lastResult checker.GRPCResponse + attempts []*GRPCPrivateRegionData + ) op := func() (*GRPCPrivateRegionData, error) { called++ @@ -58,11 +63,6 @@ func (jobRunner) GRPCJob(ctx context.Context, monitor *v1.GRPCMonitor, region st headersToMap(monitor.GetMetadata()), ) if err != nil { - // Only a call that never reached the server is worth repeating. - if called < int(retry) { - return nil, fmt.Errorf("gRPC check failed: %w", err) - } - data, dataErr := newGRPCData(monitor.Uri, monitor.Service, start) if dataErr != nil { return nil, dataErr @@ -72,10 +72,20 @@ func (jobRunner) GRPCJob(ctx context.Context, monitor *v1.GRPCMonitor, region st data.RequestStatus = "error" data.Error = 1 + data.Latency = res.Latency data.GRPCCode = res.GRPCCode data.Message = err.Error() data.Timing = marshalGRPCTiming(res.Timing) + // Only a call that never reached the server is worth repeating. + if called < int(retry) { + attempts = append(attempts, data) + + return nil, fmt.Errorf("gRPC check failed: %w", err) + } + + data.Attempts = attempts + return data, nil } @@ -117,6 +127,7 @@ func (jobRunner) GRPCJob(ctx context.Context, monitor *v1.GRPCMonitor, region st data.Error = errorFlag data.Message = res.Message data.Timing = marshalGRPCTiming(res.Timing) + data.Attempts = attempts return data, nil } diff --git a/apps/checker/pkg/job/http_job.go b/apps/checker/pkg/job/http_job.go index ab81e5319..bbf295a56 100644 --- a/apps/checker/pkg/job/http_job.go +++ b/apps/checker/pkg/job/http_job.go @@ -113,14 +113,34 @@ func (jr jobRunner) HTTPJob(ctx context.Context, monitor *v1.HTTPMonitor, region req.OtelConfig.Headers = headersToMap(otelCfg.GetHeaders()) } - var called int - var lastRes checker.Response + var ( + called int + lastRes checker.Response + attempts []*HttpPrivateRegionData + ) op := func() (*HttpPrivateRegionData, error) { called++ res, err := checker.Http(ctx, requestClient, req) if err != nil { - return nil, fmt.Errorf("unable to ping: %w", err) + err = fmt.Errorf("unable to ping: %w", err) + lastRes = res + lastRes.Error = err.Error() + + data, dataErr := httpErrorData(req.URL, res, err) + if dataErr != nil { + return nil, dataErr + } + + if called < int(retry) { + attempts = append(attempts, data) + + return nil, err + } + + data.Attempts = attempts + + return data, nil } lastRes = res @@ -228,10 +248,14 @@ func (jr jobRunner) HTTPJob(ctx context.Context, monitor *v1.HTTPMonitor, region // for non-2xx / failed assertions, matching the public checker. lastRes.Error = "Error" if called < int(retry) { + attempts = append(attempts, &data) + return nil, fmt.Errorf("unable to ping: %v with status %v", res, res.Status) } } + data.Attempts = attempts + return &data, nil } @@ -249,3 +273,36 @@ func (jr jobRunner) HTTPJob(ctx context.Context, monitor *v1.HTTPMonitor, region } return resp, nil } + +// httpErrorData reports a check that checker.Http could not complete (an +// unbuildable request, a body that failed mid-read) as an error result, so +// it is ingested like any other failure instead of being dropped. +func httpErrorData(url string, res checker.Response, err error) (*HttpPrivateRegionData, error) { + id, uuidErr := uuid.NewV7() + if uuidErr != nil { + return nil, fmt.Errorf("error while generating uuid: %w", uuidErr) + } + + timestamp := res.Timestamp + if timestamp == 0 { + timestamp = time.Now().UTC().UnixMilli() + } + + data := &HttpPrivateRegionData{ + ID: id.String(), + URL: url, + Latency: res.Latency, + StatusCode: res.Status, + Timestamp: timestamp, + CronTimestamp: timestamp, + RequestStatus: "error", + Message: err.Error(), + Error: 1, + } + + if timing, timingErr := json.Marshal(res.Timing); timingErr == nil { + data.Timing = string(timing) + } + + return data, nil +} diff --git a/apps/checker/pkg/job/icmp_job.go b/apps/checker/pkg/job/icmp_job.go index 0cbce60fb..444ab5640 100644 --- a/apps/checker/pkg/job/icmp_job.go +++ b/apps/checker/pkg/job/icmp_job.go @@ -29,6 +29,8 @@ type ICMPPrivateRegionData struct { CronTimestamp int64 `json:"cron_timestamp"` Error int `json:"error"` Timing string `json:"timing"` + + Attempts []*ICMPPrivateRegionData `json:"attempts,omitempty"` } func (jobRunner) ICMPJob(ctx context.Context, monitor *v1.ICMPMonitor, region string) (*ICMPPrivateRegionData, error) { @@ -44,18 +46,17 @@ func (jobRunner) ICMPJob(ctx context.Context, monitor *v1.ICMPMonitor, region st req := icmpCheckerRequest(monitor) - var called int - var lastResult checker.ICMPResponse + var ( + called int + lastResult checker.ICMPResponse + attempts []*ICMPPrivateRegionData + ) op := func() (*ICMPPrivateRegionData, error) { called++ start := time.Now().UTC().UnixMilli() res, err := checker.PingICMP(monitor.Timeout, monitor.Uri) if err != nil { - if called < int(retry) { - return nil, fmt.Errorf("ICMP check failed: %w", err) - } - data, dataErr := newICMPData(monitor.Uri, start) if dataErr != nil { return nil, dataErr @@ -67,6 +68,14 @@ func (jobRunner) ICMPJob(ctx context.Context, monitor *v1.ICMPMonitor, region st data.Error = 1 data.Message = err.Error() + if called < int(retry) { + attempts = append(attempts, data) + + return nil, fmt.Errorf("ICMP check failed: %w", err) + } + + data.Attempts = attempts + return data, nil } @@ -106,6 +115,7 @@ func (jobRunner) ICMPJob(ctx context.Context, monitor *v1.ICMPMonitor, region st data.Error = 0 data.Message = fmt.Sprintf("Successfully pinged %s", monitor.Uri) data.Timing = string(timingAsString) + data.Attempts = attempts return data, nil } diff --git a/apps/checker/pkg/job/job.go b/apps/checker/pkg/job/job.go index f768bca6e..206b660df 100644 --- a/apps/checker/pkg/job/job.go +++ b/apps/checker/pkg/job/job.go @@ -25,6 +25,8 @@ type HttpPrivateRegionData struct { Timestamp int64 `json:"timestamp"` StatusCode int `json:"statusCode,omitempty"` Error uint8 `json:"error"` + + Attempts []*HttpPrivateRegionData `json:"attempts,omitempty"` } type JobRunner interface { diff --git a/apps/checker/pkg/job/tcp_job.go b/apps/checker/pkg/job/tcp_job.go index 60ef46d69..173d5be9a 100644 --- a/apps/checker/pkg/job/tcp_job.go +++ b/apps/checker/pkg/job/tcp_job.go @@ -32,6 +32,8 @@ type TCPPrivateRegionData struct { CronTimestamp int64 `json:"cron_timestamp"` Error int `json:"error"` Timing string `json:"timing"` + + Attempts []*TCPPrivateRegionData `json:"attempts,omitempty"` } // runAssertions performs all configured assertions for TCP and returns their results @@ -49,17 +51,16 @@ func (jobRunner) TCPJob(ctx context.Context, monitor *v1.TCPMonitor, region stri req := tcpCheckerRequest(monitor) - var called int - var lastResult checker.TCPResponse + var ( + called int + lastResult checker.TCPResponse + attempts []*TCPPrivateRegionData + ) op := func() (*TCPPrivateRegionData, error) { called++ res, err := checker.PingTCP(ctx, monitor.Timeout, monitor.Uri) if err != nil { - if called < int(retry) { - return nil, fmt.Errorf("TCP connection failed: %w", err) - } - // On final attempt, return the error in the result id, uuidErr := uuid.NewV7() if uuidErr != nil { return nil, fmt.Errorf("failed to generate UUID: %w", uuidErr) @@ -70,7 +71,7 @@ func (jobRunner) TCPJob(ctx context.Context, monitor *v1.TCPMonitor, region stri // Use current timestamp since connection failed and res.TCPStart would be 0 now := time.Now().UnixMilli() - return &TCPPrivateRegionData{ + data := &TCPPrivateRegionData{ ID: id.String(), Latency: 0, Timestamp: now, @@ -79,7 +80,17 @@ func (jobRunner) TCPJob(ctx context.Context, monitor *v1.TCPMonitor, region stri RequestStatus: "error", Error: 1, Message: err.Error(), - }, nil + } + + if called < int(retry) { + attempts = append(attempts, data) + + return nil, fmt.Errorf("TCP connection failed: %w", err) + } + + data.Attempts = attempts + + return data, nil } latency := res.TCPDone - res.TCPStart @@ -110,6 +121,7 @@ func (jobRunner) TCPJob(ctx context.Context, monitor *v1.TCPMonitor, region stri Error: 0, Message: fmt.Sprintf("Successfully connected to %s", monitor.Uri), Timing: string(timingAsString), + Attempts: attempts, } return data, nil diff --git a/apps/checker/pkg/scheduler/attempts.go b/apps/checker/pkg/scheduler/attempts.go new file mode 100644 index 000000000..e1a744172 --- /dev/null +++ b/apps/checker/pkg/scheduler/attempts.go @@ -0,0 +1,100 @@ +package scheduler + +import ( + "github.com/openstatushq/openstatus/apps/checker/pkg/job" + v1 "github.com/openstatushq/openstatus/apps/checker/proto/private_location/v1" +) + +func toHTTPAttempts(attempts []*job.HttpPrivateRegionData) []*v1.HTTPAttempt { + out := make([]*v1.HTTPAttempt, 0, len(attempts)) + for _, a := range attempts { + out = append(out, &v1.HTTPAttempt{ + Id: a.ID, + Latency: a.Latency, + Timestamp: a.Timestamp, + RequestStatus: a.RequestStatus, + Message: a.Message, + Body: a.Body, + Headers: a.Headers, + Timing: a.Timing, + StatusCode: int64(a.StatusCode), + Error: int64(a.Error), + }) + } + + return out +} + +func toTCPAttempts(attempts []*job.TCPPrivateRegionData) []*v1.TCPAttempt { + out := make([]*v1.TCPAttempt, 0, len(attempts)) + for _, a := range attempts { + out = append(out, &v1.TCPAttempt{ + Id: a.ID, + Latency: a.Latency, + Timestamp: a.Timestamp, + Message: a.Message, + RequestStatus: a.RequestStatus, + Error: int64(a.Error), + Timing: a.Timing, + }) + } + + return out +} + +func toDNSAttempts(attempts []*job.DNSPrivateRegionData) []*v1.DNSAttempt { + out := make([]*v1.DNSAttempt, 0, len(attempts)) + for _, a := range attempts { + out = append(out, &v1.DNSAttempt{ + Id: a.ID, + Latency: a.Latency, + Timestamp: a.Timestamp, + RequestStatus: a.RequestStatus, + Message: a.Message, + Records: toProtoRecords(a.Records), + Error: int64(a.Error), + }) + } + + return out +} + +func toICMPAttempts(attempts []*job.ICMPPrivateRegionData) []*v1.ICMPAttempt { + out := make([]*v1.ICMPAttempt, 0, len(attempts)) + for _, a := range attempts { + out = append(out, &v1.ICMPAttempt{ + Id: a.ID, + Latency: a.Latency, + LatencyMin: a.LatencyMin, + LatencyMax: a.LatencyMax, + PacketsSent: a.PacketsSent, + PacketsReceived: a.PacketsReceived, + Timestamp: a.Timestamp, + Message: a.Message, + RequestStatus: a.RequestStatus, + Error: int64(a.Error), + Timing: a.Timing, + }) + } + + return out +} + +func toGRPCAttempts(attempts []*job.GRPCPrivateRegionData) []*v1.GRPCAttempt { + out := make([]*v1.GRPCAttempt, 0, len(attempts)) + for _, a := range attempts { + out = append(out, &v1.GRPCAttempt{ + Id: a.ID, + Latency: a.Latency, + Timestamp: a.Timestamp, + ServingStatus: a.ServingStatus, + GrpcCode: a.GRPCCode, + Message: a.Message, + RequestStatus: a.RequestStatus, + Error: int64(a.Error), + Timing: a.Timing, + }) + } + + return out +} diff --git a/apps/checker/pkg/scheduler/attempts_test.go b/apps/checker/pkg/scheduler/attempts_test.go new file mode 100644 index 000000000..d83056859 --- /dev/null +++ b/apps/checker/pkg/scheduler/attempts_test.go @@ -0,0 +1,34 @@ +package scheduler + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/openstatushq/openstatus/apps/checker/pkg/job" +) + +func TestToAttempts(t *testing.T) { + http := toHTTPAttempts([]*job.HttpPrivateRegionData{{ID: "h", StatusCode: 503, Body: "down", Error: 1, Timestamp: 1}}) + require.Len(t, http, 1) + assert.Equal(t, "h", http[0].GetId()) + assert.EqualValues(t, 503, http[0].GetStatusCode()) + assert.Equal(t, "down", http[0].GetBody()) + assert.EqualValues(t, 1, http[0].GetError()) + + tcp := toTCPAttempts([]*job.TCPPrivateRegionData{{ID: "t", Message: "refused", Error: 1, Timestamp: 1}}) + require.Len(t, tcp, 1) + assert.Equal(t, "refused", tcp[0].GetMessage()) + + icmp := toICMPAttempts([]*job.ICMPPrivateRegionData{{ID: "i", PacketsSent: 3, Error: 1, Timestamp: 1}}) + require.Len(t, icmp, 1) + assert.EqualValues(t, 3, icmp[0].GetPacketsSent()) + + grpc := toGRPCAttempts([]*job.GRPCPrivateRegionData{{ID: "g", GRPCCode: 14, Timing: "{}", Error: 1, Timestamp: 1}}) + require.Len(t, grpc, 1) + assert.EqualValues(t, 14, grpc[0].GetGrpcCode()) + assert.Equal(t, "{}", grpc[0].GetTiming()) + + assert.Empty(t, toHTTPAttempts(nil)) +} diff --git a/apps/checker/pkg/scheduler/scheduler.go b/apps/checker/pkg/scheduler/scheduler.go index 21ab0c027..37fa52a2f 100644 --- a/apps/checker/pkg/scheduler/scheduler.go +++ b/apps/checker/pkg/scheduler/scheduler.go @@ -116,6 +116,7 @@ func (mm *MonitorManager) UpdateMonitors(ctx context.Context) { Error: int64(data.Error), CronTimestamp: data.CronTimestamp, Timestamp: data.Timestamp, + Attempts: toHTTPAttempts(data.Attempts), }, }) if ingestErr != nil { @@ -171,6 +172,7 @@ func (mm *MonitorManager) UpdateMonitors(ctx context.Context) { Error: int64(data.Error), CronTimestamp: data.CronTimestamp, Timestamp: data.Timestamp, + Attempts: toTCPAttempts(data.Attempts), }, }) if ingestErr != nil { @@ -224,6 +226,7 @@ func (mm *MonitorManager) UpdateMonitors(ctx context.Context) { CronTimestamp: data.CronTimestamp, Timestamp: data.Timestamp, Records: toProtoRecords(data.Records), + Attempts: toDNSAttempts(data.Attempts), }, }) if ingestErr != nil { @@ -278,6 +281,7 @@ func (mm *MonitorManager) UpdateMonitors(ctx context.Context) { CronTimestamp: data.CronTimestamp, Timestamp: data.Timestamp, Timing: data.Timing, + Attempts: toICMPAttempts(data.Attempts), }, }) if ingestErr != nil { @@ -332,6 +336,7 @@ func (mm *MonitorManager) UpdateMonitors(ctx context.Context) { CronTimestamp: data.CronTimestamp, Timestamp: data.Timestamp, Timing: data.Timing, + Attempts: toGRPCAttempts(data.Attempts), }, }) if ingestErr != nil { diff --git a/apps/checker/pkg/scheduler/scheduler_test.go b/apps/checker/pkg/scheduler/scheduler_test.go index 65afde2d2..1bad02466 100644 --- a/apps/checker/pkg/scheduler/scheduler_test.go +++ b/apps/checker/pkg/scheduler/scheduler_test.go @@ -74,6 +74,14 @@ func (m *mockJobRunner) DNSJob(ctx context.Context, monitor *v1.DNSMonitor) (*jo Timestamp: 1700000000000, CronTimestamp: 1700000000000, Records: map[string][]string{"A": {"192.168.1.1"}}, + Attempts: []*job.DNSPrivateRegionData{{ + ID: "dns-attempt-1", + RequestStatus: "error", + Message: "lookup failed", + Latency: 7, + Timestamp: 1699999999000, + Error: 1, + }}, }, nil } @@ -332,6 +340,12 @@ func TestMonitorManager_IngestsDNSResult(t *testing.T) { if got := ingested.Records["A"].GetRecord(); len(got) != 1 || got[0] != "192.168.1.1" { t.Errorf("expected the A records to be forwarded, got %v", got) } + if len(ingested.Attempts) != 1 { + t.Fatalf("expected 1 failed attempt to be forwarded, got %d", len(ingested.Attempts)) + } + if a := ingested.Attempts[0]; a.Id != "dns-attempt-1" || a.Error != 1 || a.Message != "lookup failed" || a.Timestamp != 1699999999000 { + t.Errorf("expected the failed attempt to be forwarded as is, got %v", a) + } } func TestMonitorManager_SchedulesGRPCMonitors(t *testing.T) { diff --git a/apps/checker/proto/private_location/v1/private_location.pb.go b/apps/checker/proto/private_location/v1/private_location.pb.go index 6cded0613..7e1c447d3 100644 --- a/apps/checker/proto/private_location/v1/private_location.pb.go +++ b/apps/checker/proto/private_location/v1/private_location.pb.go @@ -153,6 +153,8 @@ type IngestTCPRequest struct { RequestStatus string `protobuf:"bytes,8,opt,name=requestStatus,proto3" json:"requestStatus,omitempty"` Error int64 `protobuf:"varint,9,opt,name=error,proto3" json:"error,omitempty"` Timing string `protobuf:"bytes,10,opt,name=timing,proto3" json:"timing,omitempty"` + // Failed attempts before this result, in order. Attempt n is at index n-1. + Attempts []*TCPAttempt `protobuf:"bytes,11,rep,name=attempts,proto3" json:"attempts,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -257,6 +259,105 @@ func (x *IngestTCPRequest) GetTiming() string { return "" } +func (x *IngestTCPRequest) GetAttempts() []*TCPAttempt { + if x != nil { + return x.Attempts + } + return nil +} + +type TCPAttempt struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Latency int64 `protobuf:"varint,2,opt,name=latency,proto3" json:"latency,omitempty"` + Timestamp int64 `protobuf:"varint,3,opt,name=timestamp,proto3" json:"timestamp,omitempty"` + Message string `protobuf:"bytes,4,opt,name=message,proto3" json:"message,omitempty"` + RequestStatus string `protobuf:"bytes,5,opt,name=requestStatus,proto3" json:"requestStatus,omitempty"` + Error int64 `protobuf:"varint,6,opt,name=error,proto3" json:"error,omitempty"` + Timing string `protobuf:"bytes,7,opt,name=timing,proto3" json:"timing,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TCPAttempt) Reset() { + *x = TCPAttempt{} + mi := &file_private_location_v1_private_location_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TCPAttempt) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TCPAttempt) ProtoMessage() {} + +func (x *TCPAttempt) ProtoReflect() protoreflect.Message { + mi := &file_private_location_v1_private_location_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TCPAttempt.ProtoReflect.Descriptor instead. +func (*TCPAttempt) Descriptor() ([]byte, []int) { + return file_private_location_v1_private_location_proto_rawDescGZIP(), []int{3} +} + +func (x *TCPAttempt) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *TCPAttempt) GetLatency() int64 { + if x != nil { + return x.Latency + } + return 0 +} + +func (x *TCPAttempt) GetTimestamp() int64 { + if x != nil { + return x.Timestamp + } + return 0 +} + +func (x *TCPAttempt) GetMessage() string { + if x != nil { + return x.Message + } + return "" +} + +func (x *TCPAttempt) GetRequestStatus() string { + if x != nil { + return x.RequestStatus + } + return "" +} + +func (x *TCPAttempt) GetError() int64 { + if x != nil { + return x.Error + } + return 0 +} + +func (x *TCPAttempt) GetTiming() string { + if x != nil { + return x.Timing + } + return "" +} + type IngestTCPResponse struct { state protoimpl.MessageState `protogen:"open.v1"` unknownFields protoimpl.UnknownFields @@ -265,7 +366,7 @@ type IngestTCPResponse struct { func (x *IngestTCPResponse) Reset() { *x = IngestTCPResponse{} - mi := &file_private_location_v1_private_location_proto_msgTypes[3] + mi := &file_private_location_v1_private_location_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -277,7 +378,7 @@ func (x *IngestTCPResponse) String() string { func (*IngestTCPResponse) ProtoMessage() {} func (x *IngestTCPResponse) ProtoReflect() protoreflect.Message { - mi := &file_private_location_v1_private_location_proto_msgTypes[3] + mi := &file_private_location_v1_private_location_proto_msgTypes[4] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -290,7 +391,7 @@ func (x *IngestTCPResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use IngestTCPResponse.ProtoReflect.Descriptor instead. func (*IngestTCPResponse) Descriptor() ([]byte, []int) { - return file_private_location_v1_private_location_proto_rawDescGZIP(), []int{3} + return file_private_location_v1_private_location_proto_rawDescGZIP(), []int{4} } type IngestHTTPRequest struct { @@ -308,13 +409,15 @@ type IngestHTTPRequest struct { Timing string `protobuf:"bytes,11,opt,name=timing,proto3" json:"timing,omitempty"` StatusCode int64 `protobuf:"varint,12,opt,name=statusCode,proto3" json:"statusCode,omitempty"` Error int64 `protobuf:"varint,13,opt,name=error,proto3" json:"error,omitempty"` + // Failed attempts before this result, in order. Attempt n is at index n-1. + Attempts []*HTTPAttempt `protobuf:"bytes,14,rep,name=attempts,proto3" json:"attempts,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *IngestHTTPRequest) Reset() { *x = IngestHTTPRequest{} - mi := &file_private_location_v1_private_location_proto_msgTypes[4] + mi := &file_private_location_v1_private_location_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -326,7 +429,7 @@ func (x *IngestHTTPRequest) String() string { func (*IngestHTTPRequest) ProtoMessage() {} func (x *IngestHTTPRequest) ProtoReflect() protoreflect.Message { - mi := &file_private_location_v1_private_location_proto_msgTypes[4] + mi := &file_private_location_v1_private_location_proto_msgTypes[5] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -339,7 +442,7 @@ func (x *IngestHTTPRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use IngestHTTPRequest.ProtoReflect.Descriptor instead. func (*IngestHTTPRequest) Descriptor() ([]byte, []int) { - return file_private_location_v1_private_location_proto_rawDescGZIP(), []int{4} + return file_private_location_v1_private_location_proto_rawDescGZIP(), []int{5} } func (x *IngestHTTPRequest) GetId() string { @@ -433,6 +536,129 @@ func (x *IngestHTTPRequest) GetError() int64 { return 0 } +func (x *IngestHTTPRequest) GetAttempts() []*HTTPAttempt { + if x != nil { + return x.Attempts + } + return nil +} + +type HTTPAttempt struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Latency int64 `protobuf:"varint,2,opt,name=latency,proto3" json:"latency,omitempty"` + Timestamp int64 `protobuf:"varint,3,opt,name=timestamp,proto3" json:"timestamp,omitempty"` + RequestStatus string `protobuf:"bytes,4,opt,name=requestStatus,proto3" json:"requestStatus,omitempty"` + Message string `protobuf:"bytes,5,opt,name=message,proto3" json:"message,omitempty"` + Body string `protobuf:"bytes,6,opt,name=body,proto3" json:"body,omitempty"` + Headers string `protobuf:"bytes,7,opt,name=headers,proto3" json:"headers,omitempty"` + Timing string `protobuf:"bytes,8,opt,name=timing,proto3" json:"timing,omitempty"` + StatusCode int64 `protobuf:"varint,9,opt,name=statusCode,proto3" json:"statusCode,omitempty"` + Error int64 `protobuf:"varint,10,opt,name=error,proto3" json:"error,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *HTTPAttempt) Reset() { + *x = HTTPAttempt{} + mi := &file_private_location_v1_private_location_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *HTTPAttempt) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*HTTPAttempt) ProtoMessage() {} + +func (x *HTTPAttempt) ProtoReflect() protoreflect.Message { + mi := &file_private_location_v1_private_location_proto_msgTypes[6] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use HTTPAttempt.ProtoReflect.Descriptor instead. +func (*HTTPAttempt) Descriptor() ([]byte, []int) { + return file_private_location_v1_private_location_proto_rawDescGZIP(), []int{6} +} + +func (x *HTTPAttempt) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *HTTPAttempt) GetLatency() int64 { + if x != nil { + return x.Latency + } + return 0 +} + +func (x *HTTPAttempt) GetTimestamp() int64 { + if x != nil { + return x.Timestamp + } + return 0 +} + +func (x *HTTPAttempt) GetRequestStatus() string { + if x != nil { + return x.RequestStatus + } + return "" +} + +func (x *HTTPAttempt) GetMessage() string { + if x != nil { + return x.Message + } + return "" +} + +func (x *HTTPAttempt) GetBody() string { + if x != nil { + return x.Body + } + return "" +} + +func (x *HTTPAttempt) GetHeaders() string { + if x != nil { + return x.Headers + } + return "" +} + +func (x *HTTPAttempt) GetTiming() string { + if x != nil { + return x.Timing + } + return "" +} + +func (x *HTTPAttempt) GetStatusCode() int64 { + if x != nil { + return x.StatusCode + } + return 0 +} + +func (x *HTTPAttempt) GetError() int64 { + if x != nil { + return x.Error + } + return 0 +} + type IngestHTTPResponse struct { state protoimpl.MessageState `protogen:"open.v1"` unknownFields protoimpl.UnknownFields @@ -441,7 +667,7 @@ type IngestHTTPResponse struct { func (x *IngestHTTPResponse) Reset() { *x = IngestHTTPResponse{} - mi := &file_private_location_v1_private_location_proto_msgTypes[5] + mi := &file_private_location_v1_private_location_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -453,7 +679,7 @@ func (x *IngestHTTPResponse) String() string { func (*IngestHTTPResponse) ProtoMessage() {} func (x *IngestHTTPResponse) ProtoReflect() protoreflect.Message { - mi := &file_private_location_v1_private_location_proto_msgTypes[5] + mi := &file_private_location_v1_private_location_proto_msgTypes[7] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -466,7 +692,7 @@ func (x *IngestHTTPResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use IngestHTTPResponse.ProtoReflect.Descriptor instead. func (*IngestHTTPResponse) Descriptor() ([]byte, []int) { - return file_private_location_v1_private_location_proto_rawDescGZIP(), []int{5} + return file_private_location_v1_private_location_proto_rawDescGZIP(), []int{7} } type Records struct { @@ -478,7 +704,7 @@ type Records struct { func (x *Records) Reset() { *x = Records{} - mi := &file_private_location_v1_private_location_proto_msgTypes[6] + mi := &file_private_location_v1_private_location_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -490,7 +716,7 @@ func (x *Records) String() string { func (*Records) ProtoMessage() {} func (x *Records) ProtoReflect() protoreflect.Message { - mi := &file_private_location_v1_private_location_proto_msgTypes[6] + mi := &file_private_location_v1_private_location_proto_msgTypes[8] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -503,7 +729,7 @@ func (x *Records) ProtoReflect() protoreflect.Message { // Deprecated: Use Records.ProtoReflect.Descriptor instead. func (*Records) Descriptor() ([]byte, []int) { - return file_private_location_v1_private_location_proto_rawDescGZIP(), []int{6} + return file_private_location_v1_private_location_proto_rawDescGZIP(), []int{8} } func (x *Records) GetRecord() []string { @@ -526,13 +752,15 @@ type IngestDNSRequest struct { Records map[string]*Records `protobuf:"bytes,9,rep,name=records,proto3" json:"records,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` Timing string `protobuf:"bytes,10,opt,name=timing,proto3" json:"timing,omitempty"` Error int64 `protobuf:"varint,11,opt,name=error,proto3" json:"error,omitempty"` + // Failed attempts before this result, in order. Attempt n is at index n-1. + Attempts []*DNSAttempt `protobuf:"bytes,12,rep,name=attempts,proto3" json:"attempts,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *IngestDNSRequest) Reset() { *x = IngestDNSRequest{} - mi := &file_private_location_v1_private_location_proto_msgTypes[7] + mi := &file_private_location_v1_private_location_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -544,7 +772,7 @@ func (x *IngestDNSRequest) String() string { func (*IngestDNSRequest) ProtoMessage() {} func (x *IngestDNSRequest) ProtoReflect() protoreflect.Message { - mi := &file_private_location_v1_private_location_proto_msgTypes[7] + mi := &file_private_location_v1_private_location_proto_msgTypes[9] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -557,7 +785,7 @@ func (x *IngestDNSRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use IngestDNSRequest.ProtoReflect.Descriptor instead. func (*IngestDNSRequest) Descriptor() ([]byte, []int) { - return file_private_location_v1_private_location_proto_rawDescGZIP(), []int{7} + return file_private_location_v1_private_location_proto_rawDescGZIP(), []int{9} } func (x *IngestDNSRequest) GetId() string { @@ -637,6 +865,113 @@ func (x *IngestDNSRequest) GetError() int64 { return 0 } +func (x *IngestDNSRequest) GetAttempts() []*DNSAttempt { + if x != nil { + return x.Attempts + } + return nil +} + +type DNSAttempt struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Latency int64 `protobuf:"varint,2,opt,name=latency,proto3" json:"latency,omitempty"` + Timestamp int64 `protobuf:"varint,3,opt,name=timestamp,proto3" json:"timestamp,omitempty"` + RequestStatus string `protobuf:"bytes,4,opt,name=requestStatus,proto3" json:"requestStatus,omitempty"` + Message string `protobuf:"bytes,5,opt,name=message,proto3" json:"message,omitempty"` + Records map[string]*Records `protobuf:"bytes,6,rep,name=records,proto3" json:"records,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + Timing string `protobuf:"bytes,7,opt,name=timing,proto3" json:"timing,omitempty"` + Error int64 `protobuf:"varint,8,opt,name=error,proto3" json:"error,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DNSAttempt) Reset() { + *x = DNSAttempt{} + mi := &file_private_location_v1_private_location_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DNSAttempt) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DNSAttempt) ProtoMessage() {} + +func (x *DNSAttempt) ProtoReflect() protoreflect.Message { + mi := &file_private_location_v1_private_location_proto_msgTypes[10] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DNSAttempt.ProtoReflect.Descriptor instead. +func (*DNSAttempt) Descriptor() ([]byte, []int) { + return file_private_location_v1_private_location_proto_rawDescGZIP(), []int{10} +} + +func (x *DNSAttempt) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *DNSAttempt) GetLatency() int64 { + if x != nil { + return x.Latency + } + return 0 +} + +func (x *DNSAttempt) GetTimestamp() int64 { + if x != nil { + return x.Timestamp + } + return 0 +} + +func (x *DNSAttempt) GetRequestStatus() string { + if x != nil { + return x.RequestStatus + } + return "" +} + +func (x *DNSAttempt) GetMessage() string { + if x != nil { + return x.Message + } + return "" +} + +func (x *DNSAttempt) GetRecords() map[string]*Records { + if x != nil { + return x.Records + } + return nil +} + +func (x *DNSAttempt) GetTiming() string { + if x != nil { + return x.Timing + } + return "" +} + +func (x *DNSAttempt) GetError() int64 { + if x != nil { + return x.Error + } + return 0 +} + type IngestDNSResponse struct { state protoimpl.MessageState `protogen:"open.v1"` unknownFields protoimpl.UnknownFields @@ -645,7 +980,7 @@ type IngestDNSResponse struct { func (x *IngestDNSResponse) Reset() { *x = IngestDNSResponse{} - mi := &file_private_location_v1_private_location_proto_msgTypes[8] + mi := &file_private_location_v1_private_location_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -657,7 +992,7 @@ func (x *IngestDNSResponse) String() string { func (*IngestDNSResponse) ProtoMessage() {} func (x *IngestDNSResponse) ProtoReflect() protoreflect.Message { - mi := &file_private_location_v1_private_location_proto_msgTypes[8] + mi := &file_private_location_v1_private_location_proto_msgTypes[11] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -670,7 +1005,7 @@ func (x *IngestDNSResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use IngestDNSResponse.ProtoReflect.Descriptor instead. func (*IngestDNSResponse) Descriptor() ([]byte, []int) { - return file_private_location_v1_private_location_proto_rawDescGZIP(), []int{8} + return file_private_location_v1_private_location_proto_rawDescGZIP(), []int{11} } type IngestICMPRequest struct { @@ -689,13 +1024,15 @@ type IngestICMPRequest struct { RequestStatus string `protobuf:"bytes,12,opt,name=requestStatus,proto3" json:"requestStatus,omitempty"` Error int64 `protobuf:"varint,13,opt,name=error,proto3" json:"error,omitempty"` Timing string `protobuf:"bytes,14,opt,name=timing,proto3" json:"timing,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + // Failed attempts before this result, in order. Attempt n is at index n-1. + Attempts []*ICMPAttempt `protobuf:"bytes,15,rep,name=attempts,proto3" json:"attempts,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *IngestICMPRequest) Reset() { *x = IngestICMPRequest{} - mi := &file_private_location_v1_private_location_proto_msgTypes[9] + mi := &file_private_location_v1_private_location_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -707,7 +1044,7 @@ func (x *IngestICMPRequest) String() string { func (*IngestICMPRequest) ProtoMessage() {} func (x *IngestICMPRequest) ProtoReflect() protoreflect.Message { - mi := &file_private_location_v1_private_location_proto_msgTypes[9] + mi := &file_private_location_v1_private_location_proto_msgTypes[12] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -720,7 +1057,7 @@ func (x *IngestICMPRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use IngestICMPRequest.ProtoReflect.Descriptor instead. func (*IngestICMPRequest) Descriptor() ([]byte, []int) { - return file_private_location_v1_private_location_proto_rawDescGZIP(), []int{9} + return file_private_location_v1_private_location_proto_rawDescGZIP(), []int{12} } func (x *IngestICMPRequest) GetId() string { @@ -821,6 +1158,137 @@ func (x *IngestICMPRequest) GetTiming() string { return "" } +func (x *IngestICMPRequest) GetAttempts() []*ICMPAttempt { + if x != nil { + return x.Attempts + } + return nil +} + +type ICMPAttempt struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Latency int64 `protobuf:"varint,2,opt,name=latency,proto3" json:"latency,omitempty"` + LatencyMin int64 `protobuf:"varint,3,opt,name=latencyMin,proto3" json:"latencyMin,omitempty"` + LatencyMax int64 `protobuf:"varint,4,opt,name=latencyMax,proto3" json:"latencyMax,omitempty"` + PacketsSent int64 `protobuf:"varint,5,opt,name=packetsSent,proto3" json:"packetsSent,omitempty"` + PacketsReceived int64 `protobuf:"varint,6,opt,name=packetsReceived,proto3" json:"packetsReceived,omitempty"` + Timestamp int64 `protobuf:"varint,7,opt,name=timestamp,proto3" json:"timestamp,omitempty"` + Message string `protobuf:"bytes,8,opt,name=message,proto3" json:"message,omitempty"` + RequestStatus string `protobuf:"bytes,9,opt,name=requestStatus,proto3" json:"requestStatus,omitempty"` + Error int64 `protobuf:"varint,10,opt,name=error,proto3" json:"error,omitempty"` + Timing string `protobuf:"bytes,11,opt,name=timing,proto3" json:"timing,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ICMPAttempt) Reset() { + *x = ICMPAttempt{} + mi := &file_private_location_v1_private_location_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ICMPAttempt) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ICMPAttempt) ProtoMessage() {} + +func (x *ICMPAttempt) ProtoReflect() protoreflect.Message { + mi := &file_private_location_v1_private_location_proto_msgTypes[13] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ICMPAttempt.ProtoReflect.Descriptor instead. +func (*ICMPAttempt) Descriptor() ([]byte, []int) { + return file_private_location_v1_private_location_proto_rawDescGZIP(), []int{13} +} + +func (x *ICMPAttempt) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *ICMPAttempt) GetLatency() int64 { + if x != nil { + return x.Latency + } + return 0 +} + +func (x *ICMPAttempt) GetLatencyMin() int64 { + if x != nil { + return x.LatencyMin + } + return 0 +} + +func (x *ICMPAttempt) GetLatencyMax() int64 { + if x != nil { + return x.LatencyMax + } + return 0 +} + +func (x *ICMPAttempt) GetPacketsSent() int64 { + if x != nil { + return x.PacketsSent + } + return 0 +} + +func (x *ICMPAttempt) GetPacketsReceived() int64 { + if x != nil { + return x.PacketsReceived + } + return 0 +} + +func (x *ICMPAttempt) GetTimestamp() int64 { + if x != nil { + return x.Timestamp + } + return 0 +} + +func (x *ICMPAttempt) GetMessage() string { + if x != nil { + return x.Message + } + return "" +} + +func (x *ICMPAttempt) GetRequestStatus() string { + if x != nil { + return x.RequestStatus + } + return "" +} + +func (x *ICMPAttempt) GetError() int64 { + if x != nil { + return x.Error + } + return 0 +} + +func (x *ICMPAttempt) GetTiming() string { + if x != nil { + return x.Timing + } + return "" +} + type IngestICMPResponse struct { state protoimpl.MessageState `protogen:"open.v1"` unknownFields protoimpl.UnknownFields @@ -829,7 +1297,7 @@ type IngestICMPResponse struct { func (x *IngestICMPResponse) Reset() { *x = IngestICMPResponse{} - mi := &file_private_location_v1_private_location_proto_msgTypes[10] + mi := &file_private_location_v1_private_location_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -841,7 +1309,7 @@ func (x *IngestICMPResponse) String() string { func (*IngestICMPResponse) ProtoMessage() {} func (x *IngestICMPResponse) ProtoReflect() protoreflect.Message { - mi := &file_private_location_v1_private_location_proto_msgTypes[10] + mi := &file_private_location_v1_private_location_proto_msgTypes[14] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -854,7 +1322,7 @@ func (x *IngestICMPResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use IngestICMPResponse.ProtoReflect.Descriptor instead. func (*IngestICMPResponse) Descriptor() ([]byte, []int) { - return file_private_location_v1_private_location_proto_rawDescGZIP(), []int{10} + return file_private_location_v1_private_location_proto_rawDescGZIP(), []int{14} } type IngestGRPCRequest struct { @@ -872,13 +1340,15 @@ type IngestGRPCRequest struct { RequestStatus string `protobuf:"bytes,11,opt,name=requestStatus,proto3" json:"requestStatus,omitempty"` Error int64 `protobuf:"varint,12,opt,name=error,proto3" json:"error,omitempty"` Timing string `protobuf:"bytes,13,opt,name=timing,proto3" json:"timing,omitempty"` + // Failed attempts before this result, in order. Attempt n is at index n-1. + Attempts []*GRPCAttempt `protobuf:"bytes,14,rep,name=attempts,proto3" json:"attempts,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *IngestGRPCRequest) Reset() { *x = IngestGRPCRequest{} - mi := &file_private_location_v1_private_location_proto_msgTypes[11] + mi := &file_private_location_v1_private_location_proto_msgTypes[15] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -890,7 +1360,7 @@ func (x *IngestGRPCRequest) String() string { func (*IngestGRPCRequest) ProtoMessage() {} func (x *IngestGRPCRequest) ProtoReflect() protoreflect.Message { - mi := &file_private_location_v1_private_location_proto_msgTypes[11] + mi := &file_private_location_v1_private_location_proto_msgTypes[15] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -903,7 +1373,7 @@ func (x *IngestGRPCRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use IngestGRPCRequest.ProtoReflect.Descriptor instead. func (*IngestGRPCRequest) Descriptor() ([]byte, []int) { - return file_private_location_v1_private_location_proto_rawDescGZIP(), []int{11} + return file_private_location_v1_private_location_proto_rawDescGZIP(), []int{15} } func (x *IngestGRPCRequest) GetId() string { @@ -997,6 +1467,121 @@ func (x *IngestGRPCRequest) GetTiming() string { return "" } +func (x *IngestGRPCRequest) GetAttempts() []*GRPCAttempt { + if x != nil { + return x.Attempts + } + return nil +} + +type GRPCAttempt struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Latency int64 `protobuf:"varint,2,opt,name=latency,proto3" json:"latency,omitempty"` + Timestamp int64 `protobuf:"varint,3,opt,name=timestamp,proto3" json:"timestamp,omitempty"` + ServingStatus string `protobuf:"bytes,4,opt,name=servingStatus,proto3" json:"servingStatus,omitempty"` + GrpcCode int64 `protobuf:"varint,5,opt,name=grpcCode,proto3" json:"grpcCode,omitempty"` + Message string `protobuf:"bytes,6,opt,name=message,proto3" json:"message,omitempty"` + RequestStatus string `protobuf:"bytes,7,opt,name=requestStatus,proto3" json:"requestStatus,omitempty"` + Error int64 `protobuf:"varint,8,opt,name=error,proto3" json:"error,omitempty"` + Timing string `protobuf:"bytes,9,opt,name=timing,proto3" json:"timing,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GRPCAttempt) Reset() { + *x = GRPCAttempt{} + mi := &file_private_location_v1_private_location_proto_msgTypes[16] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GRPCAttempt) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GRPCAttempt) ProtoMessage() {} + +func (x *GRPCAttempt) ProtoReflect() protoreflect.Message { + mi := &file_private_location_v1_private_location_proto_msgTypes[16] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GRPCAttempt.ProtoReflect.Descriptor instead. +func (*GRPCAttempt) Descriptor() ([]byte, []int) { + return file_private_location_v1_private_location_proto_rawDescGZIP(), []int{16} +} + +func (x *GRPCAttempt) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *GRPCAttempt) GetLatency() int64 { + if x != nil { + return x.Latency + } + return 0 +} + +func (x *GRPCAttempt) GetTimestamp() int64 { + if x != nil { + return x.Timestamp + } + return 0 +} + +func (x *GRPCAttempt) GetServingStatus() string { + if x != nil { + return x.ServingStatus + } + return "" +} + +func (x *GRPCAttempt) GetGrpcCode() int64 { + if x != nil { + return x.GrpcCode + } + return 0 +} + +func (x *GRPCAttempt) GetMessage() string { + if x != nil { + return x.Message + } + return "" +} + +func (x *GRPCAttempt) GetRequestStatus() string { + if x != nil { + return x.RequestStatus + } + return "" +} + +func (x *GRPCAttempt) GetError() int64 { + if x != nil { + return x.Error + } + return 0 +} + +func (x *GRPCAttempt) GetTiming() string { + if x != nil { + return x.Timing + } + return "" +} + type IngestGRPCResponse struct { state protoimpl.MessageState `protogen:"open.v1"` unknownFields protoimpl.UnknownFields @@ -1005,7 +1590,7 @@ type IngestGRPCResponse struct { func (x *IngestGRPCResponse) Reset() { *x = IngestGRPCResponse{} - mi := &file_private_location_v1_private_location_proto_msgTypes[12] + mi := &file_private_location_v1_private_location_proto_msgTypes[17] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1017,7 +1602,7 @@ func (x *IngestGRPCResponse) String() string { func (*IngestGRPCResponse) ProtoMessage() {} func (x *IngestGRPCResponse) ProtoReflect() protoreflect.Message { - mi := &file_private_location_v1_private_location_proto_msgTypes[12] + mi := &file_private_location_v1_private_location_proto_msgTypes[17] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1030,7 +1615,7 @@ func (x *IngestGRPCResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use IngestGRPCResponse.ProtoReflect.Descriptor instead. func (*IngestGRPCResponse) Descriptor() ([]byte, []int) { - return file_private_location_v1_private_location_proto_rawDescGZIP(), []int{12} + return file_private_location_v1_private_location_proto_rawDescGZIP(), []int{17} } var File_private_location_v1_private_location_proto protoreflect.FileDescriptor @@ -1045,7 +1630,7 @@ const file_private_location_v1_private_location_proto_rawDesc = "" + "\fdns_monitors\x18\x03 \x03(\v2\x1f.private_location.v1.DNSMonitorR\vdnsMonitors\x12E\n" + "\ricmp_monitors\x18\x05 \x03(\v2 .private_location.v1.ICMPMonitorR\ficmpMonitors\x12E\n" + "\rgrpc_monitors\x18\x06 \x03(\v2 .private_location.v1.GRPCMonitorR\fgrpcMonitors\x12\x16\n" + - "\x06region\x18\x04 \x01(\tR\x06region\"\x9e\x02\n" + + "\x06region\x18\x04 \x01(\tR\x06region\"\xdb\x02\n" + "\x10IngestTCPRequest\x12\x0e\n" + "\x02id\x18\x01 \x01(\tR\x02id\x12\x1c\n" + "\tmonitorId\x18\x02 \x01(\tR\tmonitorId\x12\x18\n" + @@ -1057,8 +1642,18 @@ const file_private_location_v1_private_location_proto_rawDesc = "" + "\rrequestStatus\x18\b \x01(\tR\rrequestStatus\x12\x14\n" + "\x05error\x18\t \x01(\x03R\x05error\x12\x16\n" + "\x06timing\x18\n" + - " \x01(\tR\x06timing\"\x13\n" + - "\x11IngestTCPResponse\"\xed\x02\n" + + " \x01(\tR\x06timing\x12;\n" + + "\battempts\x18\v \x03(\v2\x1f.private_location.v1.TCPAttemptR\battempts\"\xc2\x01\n" + + "\n" + + "TCPAttempt\x12\x0e\n" + + "\x02id\x18\x01 \x01(\tR\x02id\x12\x18\n" + + "\alatency\x18\x02 \x01(\x03R\alatency\x12\x1c\n" + + "\ttimestamp\x18\x03 \x01(\x03R\ttimestamp\x12\x18\n" + + "\amessage\x18\x04 \x01(\tR\amessage\x12$\n" + + "\rrequestStatus\x18\x05 \x01(\tR\rrequestStatus\x12\x14\n" + + "\x05error\x18\x06 \x01(\x03R\x05error\x12\x16\n" + + "\x06timing\x18\a \x01(\tR\x06timing\"\x13\n" + + "\x11IngestTCPResponse\"\xab\x03\n" + "\x11IngestHTTPRequest\x12\x0e\n" + "\x02id\x18\x01 \x01(\tR\x02id\x12\x1c\n" + "\tmonitorId\x18\x02 \x01(\tR\tmonitorId\x12\x18\n" + @@ -1075,10 +1670,25 @@ const file_private_location_v1_private_location_proto_rawDesc = "" + "\n" + "statusCode\x18\f \x01(\x03R\n" + "statusCode\x12\x14\n" + - "\x05error\x18\r \x01(\x03R\x05error\"\x14\n" + + "\x05error\x18\r \x01(\x03R\x05error\x12<\n" + + "\battempts\x18\x0e \x03(\v2 .private_location.v1.HTTPAttemptR\battempts\"\x91\x02\n" + + "\vHTTPAttempt\x12\x0e\n" + + "\x02id\x18\x01 \x01(\tR\x02id\x12\x18\n" + + "\alatency\x18\x02 \x01(\x03R\alatency\x12\x1c\n" + + "\ttimestamp\x18\x03 \x01(\x03R\ttimestamp\x12$\n" + + "\rrequestStatus\x18\x04 \x01(\tR\rrequestStatus\x12\x18\n" + + "\amessage\x18\x05 \x01(\tR\amessage\x12\x12\n" + + "\x04body\x18\x06 \x01(\tR\x04body\x12\x18\n" + + "\aheaders\x18\a \x01(\tR\aheaders\x12\x16\n" + + "\x06timing\x18\b \x01(\tR\x06timing\x12\x1e\n" + + "\n" + + "statusCode\x18\t \x01(\x03R\n" + + "statusCode\x12\x14\n" + + "\x05error\x18\n" + + " \x01(\x03R\x05error\"\x14\n" + "\x12IngestHTTPResponse\"!\n" + "\aRecords\x12\x16\n" + - "\x06record\x18\x01 \x03(\tR\x06record\"\xc6\x03\n" + + "\x06record\x18\x01 \x03(\tR\x06record\"\x83\x04\n" + "\x10IngestDNSRequest\x12\x0e\n" + "\x02id\x18\x01 \x01(\tR\x02id\x12\x1c\n" + "\tmonitorId\x18\x02 \x01(\tR\tmonitorId\x12\x18\n" + @@ -1091,11 +1701,25 @@ const file_private_location_v1_private_location_proto_rawDesc = "" + "\arecords\x18\t \x03(\v22.private_location.v1.IngestDNSRequest.RecordsEntryR\arecords\x12\x16\n" + "\x06timing\x18\n" + " \x01(\tR\x06timing\x12\x14\n" + - "\x05error\x18\v \x01(\x03R\x05error\x1aX\n" + + "\x05error\x18\v \x01(\x03R\x05error\x12;\n" + + "\battempts\x18\f \x03(\v2\x1f.private_location.v1.DNSAttemptR\battempts\x1aX\n" + + "\fRecordsEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x122\n" + + "\x05value\x18\x02 \x01(\v2\x1c.private_location.v1.RecordsR\x05value:\x028\x01\"\xe4\x02\n" + + "\n" + + "DNSAttempt\x12\x0e\n" + + "\x02id\x18\x01 \x01(\tR\x02id\x12\x18\n" + + "\alatency\x18\x02 \x01(\x03R\alatency\x12\x1c\n" + + "\ttimestamp\x18\x03 \x01(\x03R\ttimestamp\x12$\n" + + "\rrequestStatus\x18\x04 \x01(\tR\rrequestStatus\x12\x18\n" + + "\amessage\x18\x05 \x01(\tR\amessage\x12F\n" + + "\arecords\x18\x06 \x03(\v2,.private_location.v1.DNSAttempt.RecordsEntryR\arecords\x12\x16\n" + + "\x06timing\x18\a \x01(\tR\x06timing\x12\x14\n" + + "\x05error\x18\b \x01(\x03R\x05error\x1aX\n" + "\fRecordsEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x122\n" + "\x05value\x18\x02 \x01(\v2\x1c.private_location.v1.RecordsR\x05value:\x028\x01\"\x13\n" + - "\x11IngestDNSResponse\"\xab\x03\n" + + "\x11IngestDNSResponse\"\xe9\x03\n" + "\x11IngestICMPRequest\x12\x0e\n" + "\x02id\x18\x01 \x01(\tR\x02id\x12\x1c\n" + "\tmonitorId\x18\x02 \x01(\tR\tmonitorId\x12\x18\n" + @@ -1115,8 +1739,26 @@ const file_private_location_v1_private_location_proto_rawDesc = "" + "\amessage\x18\v \x01(\tR\amessage\x12$\n" + "\rrequestStatus\x18\f \x01(\tR\rrequestStatus\x12\x14\n" + "\x05error\x18\r \x01(\x03R\x05error\x12\x16\n" + - "\x06timing\x18\x0e \x01(\tR\x06timing\"\x14\n" + - "\x12IngestICMPResponse\"\xfb\x02\n" + + "\x06timing\x18\x0e \x01(\tR\x06timing\x12<\n" + + "\battempts\x18\x0f \x03(\v2 .private_location.v1.ICMPAttemptR\battempts\"\xcf\x02\n" + + "\vICMPAttempt\x12\x0e\n" + + "\x02id\x18\x01 \x01(\tR\x02id\x12\x18\n" + + "\alatency\x18\x02 \x01(\x03R\alatency\x12\x1e\n" + + "\n" + + "latencyMin\x18\x03 \x01(\x03R\n" + + "latencyMin\x12\x1e\n" + + "\n" + + "latencyMax\x18\x04 \x01(\x03R\n" + + "latencyMax\x12 \n" + + "\vpacketsSent\x18\x05 \x01(\x03R\vpacketsSent\x12(\n" + + "\x0fpacketsReceived\x18\x06 \x01(\x03R\x0fpacketsReceived\x12\x1c\n" + + "\ttimestamp\x18\a \x01(\x03R\ttimestamp\x12\x18\n" + + "\amessage\x18\b \x01(\tR\amessage\x12$\n" + + "\rrequestStatus\x18\t \x01(\tR\rrequestStatus\x12\x14\n" + + "\x05error\x18\n" + + " \x01(\x03R\x05error\x12\x16\n" + + "\x06timing\x18\v \x01(\tR\x06timing\"\x14\n" + + "\x12IngestICMPResponse\"\xb9\x03\n" + "\x11IngestGRPCRequest\x12\x0e\n" + "\x02id\x18\x01 \x01(\tR\x02id\x12\x1c\n" + "\tmonitorId\x18\x02 \x01(\tR\tmonitorId\x12\x18\n" + @@ -1131,7 +1773,18 @@ const file_private_location_v1_private_location_proto_rawDesc = "" + " \x01(\tR\amessage\x12$\n" + "\rrequestStatus\x18\v \x01(\tR\rrequestStatus\x12\x14\n" + "\x05error\x18\f \x01(\x03R\x05error\x12\x16\n" + - "\x06timing\x18\r \x01(\tR\x06timing\"\x14\n" + + "\x06timing\x18\r \x01(\tR\x06timing\x12<\n" + + "\battempts\x18\x0e \x03(\v2 .private_location.v1.GRPCAttemptR\battempts\"\x85\x02\n" + + "\vGRPCAttempt\x12\x0e\n" + + "\x02id\x18\x01 \x01(\tR\x02id\x12\x18\n" + + "\alatency\x18\x02 \x01(\x03R\alatency\x12\x1c\n" + + "\ttimestamp\x18\x03 \x01(\x03R\ttimestamp\x12$\n" + + "\rservingStatus\x18\x04 \x01(\tR\rservingStatus\x12\x1a\n" + + "\bgrpcCode\x18\x05 \x01(\x03R\bgrpcCode\x12\x18\n" + + "\amessage\x18\x06 \x01(\tR\amessage\x12$\n" + + "\rrequestStatus\x18\a \x01(\tR\rrequestStatus\x12\x14\n" + + "\x05error\x18\b \x01(\x03R\x05error\x12\x16\n" + + "\x06timing\x18\t \x01(\tR\x06timing\"\x14\n" + "\x12IngestGRPCResponse2\xd2\x04\n" + "\x16PrivateLocationService\x12Y\n" + "\bMonitors\x12$.private_location.v1.MonitorsRequest\x1a%.private_location.v1.MonitorsResponse\"\x00\x12\\\n" + @@ -1156,53 +1809,66 @@ func file_private_location_v1_private_location_proto_rawDescGZIP() []byte { return file_private_location_v1_private_location_proto_rawDescData } -var file_private_location_v1_private_location_proto_msgTypes = make([]protoimpl.MessageInfo, 14) +var file_private_location_v1_private_location_proto_msgTypes = make([]protoimpl.MessageInfo, 20) var file_private_location_v1_private_location_proto_goTypes = []any{ (*MonitorsRequest)(nil), // 0: private_location.v1.MonitorsRequest (*MonitorsResponse)(nil), // 1: private_location.v1.MonitorsResponse (*IngestTCPRequest)(nil), // 2: private_location.v1.IngestTCPRequest - (*IngestTCPResponse)(nil), // 3: private_location.v1.IngestTCPResponse - (*IngestHTTPRequest)(nil), // 4: private_location.v1.IngestHTTPRequest - (*IngestHTTPResponse)(nil), // 5: private_location.v1.IngestHTTPResponse - (*Records)(nil), // 6: private_location.v1.Records - (*IngestDNSRequest)(nil), // 7: private_location.v1.IngestDNSRequest - (*IngestDNSResponse)(nil), // 8: private_location.v1.IngestDNSResponse - (*IngestICMPRequest)(nil), // 9: private_location.v1.IngestICMPRequest - (*IngestICMPResponse)(nil), // 10: private_location.v1.IngestICMPResponse - (*IngestGRPCRequest)(nil), // 11: private_location.v1.IngestGRPCRequest - (*IngestGRPCResponse)(nil), // 12: private_location.v1.IngestGRPCResponse - nil, // 13: private_location.v1.IngestDNSRequest.RecordsEntry - (*HTTPMonitor)(nil), // 14: private_location.v1.HTTPMonitor - (*TCPMonitor)(nil), // 15: private_location.v1.TCPMonitor - (*DNSMonitor)(nil), // 16: private_location.v1.DNSMonitor - (*ICMPMonitor)(nil), // 17: private_location.v1.ICMPMonitor - (*GRPCMonitor)(nil), // 18: private_location.v1.GRPCMonitor + (*TCPAttempt)(nil), // 3: private_location.v1.TCPAttempt + (*IngestTCPResponse)(nil), // 4: private_location.v1.IngestTCPResponse + (*IngestHTTPRequest)(nil), // 5: private_location.v1.IngestHTTPRequest + (*HTTPAttempt)(nil), // 6: private_location.v1.HTTPAttempt + (*IngestHTTPResponse)(nil), // 7: private_location.v1.IngestHTTPResponse + (*Records)(nil), // 8: private_location.v1.Records + (*IngestDNSRequest)(nil), // 9: private_location.v1.IngestDNSRequest + (*DNSAttempt)(nil), // 10: private_location.v1.DNSAttempt + (*IngestDNSResponse)(nil), // 11: private_location.v1.IngestDNSResponse + (*IngestICMPRequest)(nil), // 12: private_location.v1.IngestICMPRequest + (*ICMPAttempt)(nil), // 13: private_location.v1.ICMPAttempt + (*IngestICMPResponse)(nil), // 14: private_location.v1.IngestICMPResponse + (*IngestGRPCRequest)(nil), // 15: private_location.v1.IngestGRPCRequest + (*GRPCAttempt)(nil), // 16: private_location.v1.GRPCAttempt + (*IngestGRPCResponse)(nil), // 17: private_location.v1.IngestGRPCResponse + nil, // 18: private_location.v1.IngestDNSRequest.RecordsEntry + nil, // 19: private_location.v1.DNSAttempt.RecordsEntry + (*HTTPMonitor)(nil), // 20: private_location.v1.HTTPMonitor + (*TCPMonitor)(nil), // 21: private_location.v1.TCPMonitor + (*DNSMonitor)(nil), // 22: private_location.v1.DNSMonitor + (*ICMPMonitor)(nil), // 23: private_location.v1.ICMPMonitor + (*GRPCMonitor)(nil), // 24: private_location.v1.GRPCMonitor } var file_private_location_v1_private_location_proto_depIdxs = []int32{ - 14, // 0: private_location.v1.MonitorsResponse.http_monitors:type_name -> private_location.v1.HTTPMonitor - 15, // 1: private_location.v1.MonitorsResponse.tcp_monitors:type_name -> private_location.v1.TCPMonitor - 16, // 2: private_location.v1.MonitorsResponse.dns_monitors:type_name -> private_location.v1.DNSMonitor - 17, // 3: private_location.v1.MonitorsResponse.icmp_monitors:type_name -> private_location.v1.ICMPMonitor - 18, // 4: private_location.v1.MonitorsResponse.grpc_monitors:type_name -> private_location.v1.GRPCMonitor - 13, // 5: private_location.v1.IngestDNSRequest.records:type_name -> private_location.v1.IngestDNSRequest.RecordsEntry - 6, // 6: private_location.v1.IngestDNSRequest.RecordsEntry.value:type_name -> private_location.v1.Records - 0, // 7: private_location.v1.PrivateLocationService.Monitors:input_type -> private_location.v1.MonitorsRequest - 2, // 8: private_location.v1.PrivateLocationService.IngestTCP:input_type -> private_location.v1.IngestTCPRequest - 4, // 9: private_location.v1.PrivateLocationService.IngestHTTP:input_type -> private_location.v1.IngestHTTPRequest - 7, // 10: private_location.v1.PrivateLocationService.IngestDNS:input_type -> private_location.v1.IngestDNSRequest - 9, // 11: private_location.v1.PrivateLocationService.IngestICMP:input_type -> private_location.v1.IngestICMPRequest - 11, // 12: private_location.v1.PrivateLocationService.IngestGRPC:input_type -> private_location.v1.IngestGRPCRequest - 1, // 13: private_location.v1.PrivateLocationService.Monitors:output_type -> private_location.v1.MonitorsResponse - 3, // 14: private_location.v1.PrivateLocationService.IngestTCP:output_type -> private_location.v1.IngestTCPResponse - 5, // 15: private_location.v1.PrivateLocationService.IngestHTTP:output_type -> private_location.v1.IngestHTTPResponse - 8, // 16: private_location.v1.PrivateLocationService.IngestDNS:output_type -> private_location.v1.IngestDNSResponse - 10, // 17: private_location.v1.PrivateLocationService.IngestICMP:output_type -> private_location.v1.IngestICMPResponse - 12, // 18: private_location.v1.PrivateLocationService.IngestGRPC:output_type -> private_location.v1.IngestGRPCResponse - 13, // [13:19] is the sub-list for method output_type - 7, // [7:13] is the sub-list for method input_type - 7, // [7:7] is the sub-list for extension type_name - 7, // [7:7] is the sub-list for extension extendee - 0, // [0:7] is the sub-list for field type_name + 20, // 0: private_location.v1.MonitorsResponse.http_monitors:type_name -> private_location.v1.HTTPMonitor + 21, // 1: private_location.v1.MonitorsResponse.tcp_monitors:type_name -> private_location.v1.TCPMonitor + 22, // 2: private_location.v1.MonitorsResponse.dns_monitors:type_name -> private_location.v1.DNSMonitor + 23, // 3: private_location.v1.MonitorsResponse.icmp_monitors:type_name -> private_location.v1.ICMPMonitor + 24, // 4: private_location.v1.MonitorsResponse.grpc_monitors:type_name -> private_location.v1.GRPCMonitor + 3, // 5: private_location.v1.IngestTCPRequest.attempts:type_name -> private_location.v1.TCPAttempt + 6, // 6: private_location.v1.IngestHTTPRequest.attempts:type_name -> private_location.v1.HTTPAttempt + 18, // 7: private_location.v1.IngestDNSRequest.records:type_name -> private_location.v1.IngestDNSRequest.RecordsEntry + 10, // 8: private_location.v1.IngestDNSRequest.attempts:type_name -> private_location.v1.DNSAttempt + 19, // 9: private_location.v1.DNSAttempt.records:type_name -> private_location.v1.DNSAttempt.RecordsEntry + 13, // 10: private_location.v1.IngestICMPRequest.attempts:type_name -> private_location.v1.ICMPAttempt + 16, // 11: private_location.v1.IngestGRPCRequest.attempts:type_name -> private_location.v1.GRPCAttempt + 8, // 12: private_location.v1.IngestDNSRequest.RecordsEntry.value:type_name -> private_location.v1.Records + 8, // 13: private_location.v1.DNSAttempt.RecordsEntry.value:type_name -> private_location.v1.Records + 0, // 14: private_location.v1.PrivateLocationService.Monitors:input_type -> private_location.v1.MonitorsRequest + 2, // 15: private_location.v1.PrivateLocationService.IngestTCP:input_type -> private_location.v1.IngestTCPRequest + 5, // 16: private_location.v1.PrivateLocationService.IngestHTTP:input_type -> private_location.v1.IngestHTTPRequest + 9, // 17: private_location.v1.PrivateLocationService.IngestDNS:input_type -> private_location.v1.IngestDNSRequest + 12, // 18: private_location.v1.PrivateLocationService.IngestICMP:input_type -> private_location.v1.IngestICMPRequest + 15, // 19: private_location.v1.PrivateLocationService.IngestGRPC:input_type -> private_location.v1.IngestGRPCRequest + 1, // 20: private_location.v1.PrivateLocationService.Monitors:output_type -> private_location.v1.MonitorsResponse + 4, // 21: private_location.v1.PrivateLocationService.IngestTCP:output_type -> private_location.v1.IngestTCPResponse + 7, // 22: private_location.v1.PrivateLocationService.IngestHTTP:output_type -> private_location.v1.IngestHTTPResponse + 11, // 23: private_location.v1.PrivateLocationService.IngestDNS:output_type -> private_location.v1.IngestDNSResponse + 14, // 24: private_location.v1.PrivateLocationService.IngestICMP:output_type -> private_location.v1.IngestICMPResponse + 17, // 25: private_location.v1.PrivateLocationService.IngestGRPC:output_type -> private_location.v1.IngestGRPCResponse + 20, // [20:26] is the sub-list for method output_type + 14, // [14:20] is the sub-list for method input_type + 14, // [14:14] is the sub-list for extension type_name + 14, // [14:14] is the sub-list for extension extendee + 0, // [0:14] is the sub-list for field type_name } func init() { file_private_location_v1_private_location_proto_init() } @@ -1221,7 +1887,7 @@ func file_private_location_v1_private_location_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_private_location_v1_private_location_proto_rawDesc), len(file_private_location_v1_private_location_proto_rawDesc)), NumEnums: 0, - NumMessages: 14, + NumMessages: 20, NumExtensions: 0, NumServices: 1, }, diff --git a/apps/private-location/internal/server/ingest_attempts_test.go b/apps/private-location/internal/server/ingest_attempts_test.go new file mode 100644 index 000000000..16cb56e2e --- /dev/null +++ b/apps/private-location/internal/server/ingest_attempts_test.go @@ -0,0 +1,239 @@ +package server_test + +import ( + "context" + "encoding/json" + "io" + "net/http" + "strconv" + "sync" + "testing" + + "connectrpc.com/connect" + "github.com/openstatushq/openstatus/apps/private-location/internal/server" + "github.com/openstatushq/openstatus/apps/private-location/internal/tinybird" + private_locationv1 "github.com/openstatushq/openstatus/apps/private-location/proto/private_location/v1" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +const finalCronTimestamp = 1234567800 + +type tinybirdRecorder struct { + mu sync.Mutex + events map[string][]map[string]json.RawMessage +} + +func newTinybirdRecorder(t *testing.T) (*tinybirdRecorder, tinybird.Client) { + t.Helper() + rec := &tinybirdRecorder{events: map[string][]map[string]json.RawMessage{}} + interceptor := &interceptorHTTPClient{ + f: func(req *http.Request) (*http.Response, error) { + body, err := io.ReadAll(req.Body) + require.NoError(t, err) + var row map[string]json.RawMessage + require.NoError(t, json.Unmarshal(body, &row)) + + rec.mu.Lock() + name := req.URL.Query().Get("name") + rec.events[name] = append(rec.events[name], row) + rec.mu.Unlock() + + return &http.Response{StatusCode: http.StatusAccepted}, nil + }, + } + + return rec, tinybird.NewClient(interceptor.GetHTTPClient(), "apiKey") +} + +func (r *tinybirdRecorder) rows(dataSource string) []map[string]json.RawMessage { + r.mu.Lock() + defer r.mu.Unlock() + + return r.events[dataSource] +} + +func authed[T any](msg *T) *connect.Request[T] { + req := connect.NewRequest(msg) + req.Header().Set("openstatus-token", "my-secret-key") + + return req +} + +func assertAttemptRows(t *testing.T, rows []map[string]json.RawMessage, ids ...string) { + t.Helper() + require.Len(t, rows, len(ids)) + for i, row := range rows { + assert.JSONEq(t, `"`+ids[i]+`"`, string(row["id"])) + assert.JSONEq(t, `"error"`, string(row["requestStatus"])) + assert.Equal(t, json.RawMessage(`1`), row["error"]) + assert.Equal(t, json.RawMessage(`1234567800`), row["cronTimestamp"], "attempts join the final row on its cronTimestamp") + assert.Equal(t, json.RawMessage(strconv.Itoa(i+1)), row["attempt"]) + assert.NotContains(t, row, "attempts") + } +} + +func TestIngestHTTP_Attempts(t *testing.T) { + rec, client := newTinybirdRecorder(t) + h := server.NewPrivateLocationServer(testDB(), client) + + _, err := h.IngestHTTP(context.Background(), authed(&private_locationv1.IngestHTTPRequest{ + Id: "final", MonitorId: "5", Timestamp: 1234567890, CronTimestamp: finalCronTimestamp, StatusCode: 200, + RequestStatus: "success", + Attempts: []*private_locationv1.HTTPAttempt{ + {Id: "a1", Timestamp: 1234567810, StatusCode: 503, RequestStatus: "error", Error: 1, Body: "down"}, + {Id: "a2", Timestamp: 1234567850, StatusCode: 502, RequestStatus: "error", Error: 1}, + }, + })) + require.NoError(t, err) + + attempts := rec.rows(tinybird.DatasourceHTTPAttempt) + assertAttemptRows(t, attempts, "a1", "a2") + assert.Equal(t, json.RawMessage(`503`), attempts[0]["statusCode"]) + assert.JSONEq(t, `"down"`, string(attempts[0]["body"])) + assert.Equal(t, json.RawMessage(`1234567810`), attempts[0]["timestamp"]) + + final := rec.rows(tinybird.DatasourceHTTP) + require.Len(t, final, 1) + assert.Equal(t, json.RawMessage(`3`), final[0]["attempts"]) +} + +func TestIngestHTTP_NoAttemptsFromAnOldAgent(t *testing.T) { + rec, client := newTinybirdRecorder(t) + h := server.NewPrivateLocationServer(testDB(), client) + + _, err := h.IngestHTTP(context.Background(), authed(&private_locationv1.IngestHTTPRequest{ + Id: "final", MonitorId: "5", Timestamp: 1234567890, CronTimestamp: finalCronTimestamp, + })) + require.NoError(t, err) + + assert.Empty(t, rec.rows(tinybird.DatasourceHTTPAttempt)) + final := rec.rows(tinybird.DatasourceHTTP) + require.Len(t, final, 1) + assert.Equal(t, json.RawMessage(`1`), final[0]["attempts"]) +} + +func TestIngestTCP_Attempts(t *testing.T) { + rec, client := newTinybirdRecorder(t) + h := server.NewPrivateLocationServer(testDB(), client) + + _, err := h.IngestTCP(context.Background(), authed(&private_locationv1.IngestTCPRequest{ + Id: "final", MonitorId: "5", Timestamp: 1234567890, CronTimestamp: finalCronTimestamp, + Attempts: []*private_locationv1.TCPAttempt{ + {Id: "a1", Timestamp: 1234567810, RequestStatus: "error", Error: 1, Message: "refused"}, + }, + })) + require.NoError(t, err) + + attempts := rec.rows(tinybird.DatasourceTCPAttempt) + assertAttemptRows(t, attempts, "a1") + assert.JSONEq(t, `"refused"`, string(attempts[0]["errorMessage"])) + assert.Equal(t, json.RawMessage(`2`), rec.rows(tinybird.DatasourceTCP)[0]["attempts"]) +} + +func TestIngestDNS_Attempts(t *testing.T) { + rec, client := newTinybirdRecorder(t) + h := server.NewPrivateLocationServer(testDB(), client) + + _, err := h.IngestDNS(context.Background(), authed(&private_locationv1.IngestDNSRequest{ + Id: "final", MonitorId: "5", Timestamp: 1234567890, CronTimestamp: finalCronTimestamp, + Attempts: []*private_locationv1.DNSAttempt{{ + Id: "a1", Timestamp: 1234567810, RequestStatus: "error", Error: 1, + Records: map[string]*private_locationv1.Records{"A": {Record: []string{"1.2.3.4"}}}, + }}, + })) + require.NoError(t, err) + + attempts := rec.rows(tinybird.DatasourceDNSAttempt) + assertAttemptRows(t, attempts, "a1") + assert.JSONEq(t, `"{\"A\":[\"1.2.3.4\"]}"`, string(attempts[0]["records"])) + assert.Equal(t, json.RawMessage(`2`), rec.rows(tinybird.DatasourceDNS)[0]["attempts"]) +} + +func TestIngestICMP_Attempts(t *testing.T) { + rec, client := newTinybirdRecorder(t) + h := server.NewPrivateLocationServer(testDB(), client) + + _, err := h.IngestICMP(context.Background(), authed(&private_locationv1.IngestICMPRequest{ + Id: "final", MonitorId: "5", Timestamp: 1234567890, CronTimestamp: finalCronTimestamp, + Attempts: []*private_locationv1.ICMPAttempt{ + {Id: "a1", Timestamp: 1234567810, RequestStatus: "error", Error: 1, PacketsSent: 3}, + }, + })) + require.NoError(t, err) + + attempts := rec.rows(tinybird.DatasourceICMPAttempt) + assertAttemptRows(t, attempts, "a1") + assert.Equal(t, json.RawMessage(`3`), attempts[0]["packetsSent"]) + assert.Equal(t, json.RawMessage(`2`), rec.rows(tinybird.DatasourceICMP)[0]["attempts"]) +} + +func TestIngestGRPC_Attempts(t *testing.T) { + rec, client := newTinybirdRecorder(t) + h := server.NewPrivateLocationServer(testDB(), client) + + _, err := h.IngestGRPC(context.Background(), authed(&private_locationv1.IngestGRPCRequest{ + Id: "final", MonitorId: "5", Timestamp: 1234567890, CronTimestamp: finalCronTimestamp, + Attempts: []*private_locationv1.GRPCAttempt{ + {Id: "a1", Timestamp: 1234567810, RequestStatus: "error", Error: 1, GrpcCode: 14}, + }, + })) + require.NoError(t, err) + + attempts := rec.rows(tinybird.DatasourceGRPCAttempt) + assertAttemptRows(t, attempts, "a1") + assert.Equal(t, json.RawMessage(`14`), attempts[0]["grpcCode"]) + assert.Equal(t, json.RawMessage(`2`), rec.rows(tinybird.DatasourceGRPC)[0]["attempts"]) +} + +func TestIngest_AttemptValidation(t *testing.T) { + _, client := newTinybirdRecorder(t) + h := server.NewPrivateLocationServer(testDB(), client) + + tooMany := make([]*private_locationv1.HTTPAttempt, 255) + for i := range tooMany { + tooMany[i] = &private_locationv1.HTTPAttempt{Timestamp: 1234567810} + } + + tests := []struct { + name string + call func() error + }{ + {"too many attempts", func() error { + _, err := h.IngestHTTP(context.Background(), authed(&private_locationv1.IngestHTTPRequest{ + MonitorId: "5", Timestamp: 1234567890, Attempts: tooMany, + })) + + return err + }}, + {"attempt without timestamp", func() error { + _, err := h.IngestTCP(context.Background(), authed(&private_locationv1.IngestTCPRequest{ + MonitorId: "5", Timestamp: 1234567890, Attempts: []*private_locationv1.TCPAttempt{{}}, + })) + + return err + }}, + {"attempt with negative latency", func() error { + _, err := h.IngestDNS(context.Background(), authed(&private_locationv1.IngestDNSRequest{ + MonitorId: "5", Timestamp: 1234567890, + Attempts: []*private_locationv1.DNSAttempt{{Timestamp: 1, Latency: -1}}, + })) + + return err + }}, + {"attempt with invalid grpc code", func() error { + _, err := h.IngestGRPC(context.Background(), authed(&private_locationv1.IngestGRPCRequest{ + MonitorId: "5", Timestamp: 1234567890, + Attempts: []*private_locationv1.GRPCAttempt{{Timestamp: 1, GrpcCode: 99}}, + })) + + return err + }}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, connect.CodeInvalidArgument, connect.CodeOf(tt.call())) + }) + } +} diff --git a/apps/private-location/internal/server/ingest_common.go b/apps/private-location/internal/server/ingest_common.go index 26cb011de..7471dd59f 100644 --- a/apps/private-location/internal/server/ingest_common.go +++ b/apps/private-location/internal/server/ingest_common.go @@ -5,6 +5,7 @@ import ( "time" "github.com/openstatushq/openstatus/apps/private-location/internal/database" + "github.com/openstatushq/openstatus/apps/private-location/internal/tinybird" ) // dbTimeout bounds how long a single query waits for a pooled connection and runs. @@ -92,3 +93,27 @@ func (h *privateLocationHandler) sendEventAndUpdateLastSeen(ctx context.Context, } } } + +// sendAttempts stores the failed attempts an agent sent with its result. They +// are debugging detail, so a failed send is noted on the wide event and never +// fails the ingest. +func sendAttempts[T any](ctx context.Context, client tinybird.Client, attempts []T, dataSourceName string) { + if len(attempts) == 0 { + return + } + + failed := 0 + for _, attempt := range attempts { + if err := client.SendEvent(ctx, attempt, dataSourceName); err != nil { + failed++ + } + } + + if holder := GetEvent(ctx); holder != nil { + holder.Event["attempts"] = map[string]any{ + "datasource": dataSourceName, + "count": len(attempts), + "failed": failed, + } + } +} diff --git a/apps/private-location/internal/server/ingest_dns.go b/apps/private-location/internal/server/ingest_dns.go index d4a2c3564..55dea2d34 100644 --- a/apps/private-location/internal/server/ingest_dns.go +++ b/apps/private-location/internal/server/ingest_dns.go @@ -30,6 +30,9 @@ type DNSResponse struct { CronTimestamp int64 `json:"cronTimestamp"` Error uint8 `json:"error"` + + Attempt int `json:"attempt,omitempty"` + Attempts int `json:"attempts,omitempty"` } func (h *privateLocationHandler) IngestDNS(ctx context.Context, req *connect.Request[private_locationv1.IngestDNSRequest]) (*connect.Response[private_locationv1.IngestDNSResponse], error) { @@ -57,14 +60,7 @@ func (h *privateLocationHandler) IngestDNS(ctx context.Context, req *connect.Req } } - records := make(map[string][]string) - for recordType, record := range req.Msg.Records { - r := []string{} - r = append(r, record.GetRecord()...) - records[recordType] = r - } - - recordsJSON, err := json.Marshal(records) + recordsJSON, err := recordsToJSON(req.Msg.Records) if err != nil { return nil, connect.NewError(connect.CodeInternal, err) } @@ -82,12 +78,34 @@ func (h *privateLocationHandler) IngestDNS(ctx context.Context, req *connect.Req Trigger: "cron", URI: req.Msg.Uri, RequestStatus: req.Msg.RequestStatus, - Records: string(recordsJSON), + Records: recordsJSON, ErrorMessage: req.Msg.Message, + Attempts: len(req.Msg.Attempts) + 1, } h.sendEventAndUpdateLastSeen(ctx, data, tinybird.DatasourceDNS, ic.Region.ID) + attempts := make([]DNSResponse, 0, len(req.Msg.Attempts)) + for i, a := range req.Msg.Attempts { + attemptRecords, err := recordsToJSON(a.Records) + if err != nil { + continue + } + attempt := data + attempt.ID = a.Id + attempt.Latency = a.Latency + attempt.Timestamp = a.Timestamp + attempt.RequestStatus = a.RequestStatus + attempt.ErrorMessage = a.Message + attempt.Records = attemptRecords + attempt.Timing = a.Timing + attempt.Error = uint8(a.Error) + attempt.Attempt = i + 1 + attempt.Attempts = 0 + attempts = append(attempts, attempt) + } + sendAttempts(ctx, h.TbClient, attempts, tinybird.DatasourceDNSAttempt) + h.forwardStatusUpdate(ctx, ic, statusUpdateInput{ RequestStatus: data.RequestStatus, Message: data.ErrorMessage, @@ -98,3 +116,19 @@ func (h *privateLocationHandler) IngestDNS(ctx context.Context, req *connect.Req return connect.NewResponse(&private_locationv1.IngestDNSResponse{}), nil } + +func recordsToJSON(protoRecords map[string]*private_locationv1.Records) (string, error) { + records := make(map[string][]string, len(protoRecords)) + for recordType, record := range protoRecords { + r := []string{} + r = append(r, record.GetRecord()...) + records[recordType] = r + } + + recordsJSON, err := json.Marshal(records) + if err != nil { + return "", err + } + + return string(recordsJSON), nil +} diff --git a/apps/private-location/internal/server/ingest_grpc.go b/apps/private-location/internal/server/ingest_grpc.go index 479436e3c..b8c66b62a 100644 --- a/apps/private-location/internal/server/ingest_grpc.go +++ b/apps/private-location/internal/server/ingest_grpc.go @@ -29,6 +29,9 @@ type GRPCData struct { GRPCCode int64 `json:"grpcCode"` Error uint8 `json:"error"` + + Attempt int `json:"attempt,omitempty"` + Attempts int `json:"attempts,omitempty"` } func (h *privateLocationHandler) IngestGRPC(ctx context.Context, req *connect.Request[private_locationv1.IngestGRPCRequest]) (*connect.Response[private_locationv1.IngestGRPCResponse], error) { @@ -73,10 +76,29 @@ func (h *privateLocationHandler) IngestGRPC(ctx context.Context, req *connect.Re Trigger: "cron", URI: req.Msg.Uri, RequestStatus: req.Msg.RequestStatus, + Attempts: len(req.Msg.Attempts) + 1, } h.sendEventAndUpdateLastSeen(ctx, data, tinybird.DatasourceGRPC, ic.Region.ID) + attempts := make([]GRPCData, 0, len(req.Msg.Attempts)) + for i, a := range req.Msg.Attempts { + attempt := data + attempt.ID = a.Id + attempt.Latency = a.Latency + attempt.Timestamp = a.Timestamp + attempt.ServingStatus = a.ServingStatus + attempt.GRPCCode = a.GrpcCode + attempt.ErrorMessage = a.Message + attempt.RequestStatus = a.RequestStatus + attempt.Error = uint8(a.Error) + attempt.Timing = a.Timing + attempt.Attempt = i + 1 + attempt.Attempts = 0 + attempts = append(attempts, attempt) + } + sendAttempts(ctx, h.TbClient, attempts, tinybird.DatasourceGRPCAttempt) + h.forwardStatusUpdate(ctx, ic, statusUpdateInput{ RequestStatus: data.RequestStatus, Message: data.ErrorMessage, diff --git a/apps/private-location/internal/server/ingest_http.go b/apps/private-location/internal/server/ingest_http.go index 945c413eb..8ab7645e6 100644 --- a/apps/private-location/internal/server/ingest_http.go +++ b/apps/private-location/internal/server/ingest_http.go @@ -32,6 +32,8 @@ type PingData struct { Timestamp int64 `json:"timestamp"` StatusCode int `json:"statusCode,omitempty"` Error uint8 `json:"error"` + Attempt int `json:"attempt,omitempty"` + Attempts int `json:"attempts,omitempty"` } func (h *privateLocationHandler) IngestHTTP(ctx context.Context, req *connect.Request[private_locationv1.IngestHTTPRequest]) (*connect.Response[private_locationv1.IngestHTTPResponse], error) { @@ -78,10 +80,30 @@ func (h *privateLocationHandler) IngestHTTP(ctx context.Context, req *connect.Re Assertions: ic.Monitor.Assertions.String, Message: req.Msg.Message, Error: uint8(req.Msg.Error), + Attempts: len(req.Msg.Attempts) + 1, } h.sendEventAndUpdateLastSeen(ctx, data, tinybird.DatasourceHTTP, ic.Region.ID) + attempts := make([]PingData, 0, len(req.Msg.Attempts)) + for i, a := range req.Msg.Attempts { + attempt := data + attempt.ID = a.Id + attempt.Latency = a.Latency + attempt.Timestamp = a.Timestamp + attempt.RequestStatus = a.RequestStatus + attempt.Message = a.Message + attempt.Body = a.Body + attempt.Headers = a.Headers + attempt.Timing = a.Timing + attempt.StatusCode = int(a.StatusCode) + attempt.Error = uint8(a.Error) + attempt.Attempt = i + 1 + attempt.Attempts = 0 + attempts = append(attempts, attempt) + } + sendAttempts(ctx, h.TbClient, attempts, tinybird.DatasourceHTTPAttempt) + h.forwardStatusUpdate(ctx, ic, statusUpdateInput{ RequestStatus: data.RequestStatus, Message: data.Message, diff --git a/apps/private-location/internal/server/ingest_icmp.go b/apps/private-location/internal/server/ingest_icmp.go index 1a66a70c9..77eac21fe 100644 --- a/apps/private-location/internal/server/ingest_icmp.go +++ b/apps/private-location/internal/server/ingest_icmp.go @@ -31,6 +31,9 @@ type ICMPData struct { PacketsReceived uint8 `json:"packetsReceived"` Error uint8 `json:"error"` + + Attempt int `json:"attempt,omitempty"` + Attempts int `json:"attempts,omitempty"` } func (h *privateLocationHandler) IngestICMP(ctx context.Context, req *connect.Request[private_locationv1.IngestICMPRequest]) (*connect.Response[private_locationv1.IngestICMPResponse], error) { @@ -75,10 +78,31 @@ func (h *privateLocationHandler) IngestICMP(ctx context.Context, req *connect.Re Trigger: "cron", URI: req.Msg.Uri, RequestStatus: req.Msg.RequestStatus, + Attempts: len(req.Msg.Attempts) + 1, } h.sendEventAndUpdateLastSeen(ctx, data, tinybird.DatasourceICMP, ic.Region.ID) + attempts := make([]ICMPData, 0, len(req.Msg.Attempts)) + for i, a := range req.Msg.Attempts { + attempt := data + attempt.ID = a.Id + attempt.Latency = a.Latency + attempt.LatencyMin = a.LatencyMin + attempt.LatencyMax = a.LatencyMax + attempt.PacketsSent = uint8(a.PacketsSent) + attempt.PacketsReceived = uint8(a.PacketsReceived) + attempt.Timestamp = a.Timestamp + attempt.ErrorMessage = a.Message + attempt.RequestStatus = a.RequestStatus + attempt.Error = uint8(a.Error) + attempt.Timing = a.Timing + attempt.Attempt = i + 1 + attempt.Attempts = 0 + attempts = append(attempts, attempt) + } + sendAttempts(ctx, h.TbClient, attempts, tinybird.DatasourceICMPAttempt) + h.forwardStatusUpdate(ctx, ic, statusUpdateInput{ RequestStatus: data.RequestStatus, Message: data.ErrorMessage, diff --git a/apps/private-location/internal/server/ingest_tcp.go b/apps/private-location/internal/server/ingest_tcp.go index 821a4f886..afab918e6 100644 --- a/apps/private-location/internal/server/ingest_tcp.go +++ b/apps/private-location/internal/server/ingest_tcp.go @@ -26,6 +26,9 @@ type TCPData struct { CronTimestamp int64 `json:"cronTimestamp"` Error uint8 `json:"error"` + + Attempt int `json:"attempt,omitempty"` + Attempts int `json:"attempts,omitempty"` } func (h *privateLocationHandler) IngestTCP(ctx context.Context, req *connect.Request[private_locationv1.IngestTCPRequest]) (*connect.Response[private_locationv1.IngestTCPResponse], error) { @@ -67,10 +70,27 @@ func (h *privateLocationHandler) IngestTCP(ctx context.Context, req *connect.Req URI: req.Msg.Uri, RequestStatus: req.Msg.RequestStatus, ErrorMessage: req.Msg.Message, + Attempts: len(req.Msg.Attempts) + 1, } h.sendEventAndUpdateLastSeen(ctx, data, tinybird.DatasourceTCP, ic.Region.ID) + attempts := make([]TCPData, 0, len(req.Msg.Attempts)) + for i, a := range req.Msg.Attempts { + attempt := data + attempt.ID = a.Id + attempt.Latency = a.Latency + attempt.Timestamp = a.Timestamp + attempt.ErrorMessage = a.Message + attempt.RequestStatus = a.RequestStatus + attempt.Error = uint8(a.Error) + attempt.Timing = a.Timing + attempt.Attempt = i + 1 + attempt.Attempts = 0 + attempts = append(attempts, attempt) + } + sendAttempts(ctx, h.TbClient, attempts, tinybird.DatasourceTCPAttempt) + h.forwardStatusUpdate(ctx, ic, statusUpdateInput{ RequestStatus: data.RequestStatus, Message: data.ErrorMessage, diff --git a/apps/private-location/internal/server/validation.go b/apps/private-location/internal/server/validation.go index f6ef69148..9273cab6c 100644 --- a/apps/private-location/internal/server/validation.go +++ b/apps/private-location/internal/server/validation.go @@ -16,11 +16,36 @@ var ( ErrInvalidTimestamp = errors.New("timestamp must be positive") ErrInvalidGRPCCode = errors.New("grpc_code must be a canonical gRPC status code") ErrInvalidErrorFlag = errors.New("error must be 0 or 1") + ErrTooManyAttempts = errors.New("too many attempts") ) // maxGRPCStatusCode is UNAUTHENTICATED, the highest canonical code. const maxGRPCStatusCode = 16 +// maxAttempts keeps the final row's attempt count (attempts + 1) inside the +// UInt8 `attempts` column. +const maxAttempts = 254 + +type ingestAttempt interface { + GetLatency() int64 + GetTimestamp() int64 +} + +func validateAttempts[T ingestAttempt](attempts []T) error { + if len(attempts) > maxAttempts { + return ErrTooManyAttempts + } + for _, attempt := range attempts { + if attempt.GetLatency() < 0 { + return ErrInvalidLatency + } + if attempt.GetTimestamp() <= 0 { + return ErrInvalidTimestamp + } + } + return nil +} + // validateIngestCommon holds the rules every ingest request shares. Keeping one // copy stops the per-type validators drifting as the rules change. func validateIngestCommon(monitorID string, latency, timestamp int64) error { @@ -38,22 +63,34 @@ func validateIngestCommon(monitorID string, latency, timestamp int64) error { // ValidateIngestHTTPRequest validates an HTTP ingest request func ValidateIngestHTTPRequest(req *private_locationv1.IngestHTTPRequest) error { - return validateIngestCommon(req.MonitorId, req.Latency, req.Timestamp) + if err := validateIngestCommon(req.MonitorId, req.Latency, req.Timestamp); err != nil { + return err + } + return validateAttempts(req.Attempts) } // ValidateIngestTCPRequest validates a TCP ingest request func ValidateIngestTCPRequest(req *private_locationv1.IngestTCPRequest) error { - return validateIngestCommon(req.MonitorId, req.Latency, req.Timestamp) + if err := validateIngestCommon(req.MonitorId, req.Latency, req.Timestamp); err != nil { + return err + } + return validateAttempts(req.Attempts) } // ValidateIngestDNSRequest validates a DNS ingest request func ValidateIngestDNSRequest(req *private_locationv1.IngestDNSRequest) error { - return validateIngestCommon(req.MonitorId, req.Latency, req.Timestamp) + if err := validateIngestCommon(req.MonitorId, req.Latency, req.Timestamp); err != nil { + return err + } + return validateAttempts(req.Attempts) } // ValidateIngestICMPRequest validates an ICMP ingest request func ValidateIngestICMPRequest(req *private_locationv1.IngestICMPRequest) error { - return validateIngestCommon(req.MonitorId, req.Latency, req.Timestamp) + if err := validateIngestCommon(req.MonitorId, req.Latency, req.Timestamp); err != nil { + return err + } + return validateAttempts(req.Attempts) } // ValidateIngestGRPCRequest validates a gRPC ingest request. The two numeric @@ -69,7 +106,15 @@ func ValidateIngestGRPCRequest(req *private_locationv1.IngestGRPCRequest) error if req.Error < 0 || req.Error > 1 { return ErrInvalidErrorFlag } - return nil + for _, attempt := range req.Attempts { + if attempt.GrpcCode < 0 || attempt.GrpcCode > maxGRPCStatusCode { + return ErrInvalidGRPCCode + } + if attempt.Error < 0 || attempt.Error > 1 { + return ErrInvalidErrorFlag + } + } + return validateAttempts(req.Attempts) } // NewValidationError creates a Connect error for validation failures diff --git a/apps/private-location/internal/tinybird/client.go b/apps/private-location/internal/tinybird/client.go index 27382b7f1..4e0018008 100644 --- a/apps/private-location/internal/tinybird/client.go +++ b/apps/private-location/internal/tinybird/client.go @@ -17,6 +17,12 @@ const ( DatasourceDNS = "dns_response__v0" DatasourceICMP = "icmp_response__v0" DatasourceGRPC = "grpc_response__v0" + + DatasourceHTTPAttempt = "ping_attempt__v0" + DatasourceTCPAttempt = "tcp_attempt__v0" + DatasourceDNSAttempt = "dns_attempt__v0" + DatasourceICMPAttempt = "icmp_attempt__v0" + DatasourceGRPCAttempt = "grpc_attempt__v0" ) func getBaseURL() string { diff --git a/apps/private-location/proto/private_location/v1/private_location.pb.go b/apps/private-location/proto/private_location/v1/private_location.pb.go index 6cded0613..7e1c447d3 100644 --- a/apps/private-location/proto/private_location/v1/private_location.pb.go +++ b/apps/private-location/proto/private_location/v1/private_location.pb.go @@ -153,6 +153,8 @@ type IngestTCPRequest struct { RequestStatus string `protobuf:"bytes,8,opt,name=requestStatus,proto3" json:"requestStatus,omitempty"` Error int64 `protobuf:"varint,9,opt,name=error,proto3" json:"error,omitempty"` Timing string `protobuf:"bytes,10,opt,name=timing,proto3" json:"timing,omitempty"` + // Failed attempts before this result, in order. Attempt n is at index n-1. + Attempts []*TCPAttempt `protobuf:"bytes,11,rep,name=attempts,proto3" json:"attempts,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -257,6 +259,105 @@ func (x *IngestTCPRequest) GetTiming() string { return "" } +func (x *IngestTCPRequest) GetAttempts() []*TCPAttempt { + if x != nil { + return x.Attempts + } + return nil +} + +type TCPAttempt struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Latency int64 `protobuf:"varint,2,opt,name=latency,proto3" json:"latency,omitempty"` + Timestamp int64 `protobuf:"varint,3,opt,name=timestamp,proto3" json:"timestamp,omitempty"` + Message string `protobuf:"bytes,4,opt,name=message,proto3" json:"message,omitempty"` + RequestStatus string `protobuf:"bytes,5,opt,name=requestStatus,proto3" json:"requestStatus,omitempty"` + Error int64 `protobuf:"varint,6,opt,name=error,proto3" json:"error,omitempty"` + Timing string `protobuf:"bytes,7,opt,name=timing,proto3" json:"timing,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TCPAttempt) Reset() { + *x = TCPAttempt{} + mi := &file_private_location_v1_private_location_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TCPAttempt) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TCPAttempt) ProtoMessage() {} + +func (x *TCPAttempt) ProtoReflect() protoreflect.Message { + mi := &file_private_location_v1_private_location_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TCPAttempt.ProtoReflect.Descriptor instead. +func (*TCPAttempt) Descriptor() ([]byte, []int) { + return file_private_location_v1_private_location_proto_rawDescGZIP(), []int{3} +} + +func (x *TCPAttempt) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *TCPAttempt) GetLatency() int64 { + if x != nil { + return x.Latency + } + return 0 +} + +func (x *TCPAttempt) GetTimestamp() int64 { + if x != nil { + return x.Timestamp + } + return 0 +} + +func (x *TCPAttempt) GetMessage() string { + if x != nil { + return x.Message + } + return "" +} + +func (x *TCPAttempt) GetRequestStatus() string { + if x != nil { + return x.RequestStatus + } + return "" +} + +func (x *TCPAttempt) GetError() int64 { + if x != nil { + return x.Error + } + return 0 +} + +func (x *TCPAttempt) GetTiming() string { + if x != nil { + return x.Timing + } + return "" +} + type IngestTCPResponse struct { state protoimpl.MessageState `protogen:"open.v1"` unknownFields protoimpl.UnknownFields @@ -265,7 +366,7 @@ type IngestTCPResponse struct { func (x *IngestTCPResponse) Reset() { *x = IngestTCPResponse{} - mi := &file_private_location_v1_private_location_proto_msgTypes[3] + mi := &file_private_location_v1_private_location_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -277,7 +378,7 @@ func (x *IngestTCPResponse) String() string { func (*IngestTCPResponse) ProtoMessage() {} func (x *IngestTCPResponse) ProtoReflect() protoreflect.Message { - mi := &file_private_location_v1_private_location_proto_msgTypes[3] + mi := &file_private_location_v1_private_location_proto_msgTypes[4] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -290,7 +391,7 @@ func (x *IngestTCPResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use IngestTCPResponse.ProtoReflect.Descriptor instead. func (*IngestTCPResponse) Descriptor() ([]byte, []int) { - return file_private_location_v1_private_location_proto_rawDescGZIP(), []int{3} + return file_private_location_v1_private_location_proto_rawDescGZIP(), []int{4} } type IngestHTTPRequest struct { @@ -308,13 +409,15 @@ type IngestHTTPRequest struct { Timing string `protobuf:"bytes,11,opt,name=timing,proto3" json:"timing,omitempty"` StatusCode int64 `protobuf:"varint,12,opt,name=statusCode,proto3" json:"statusCode,omitempty"` Error int64 `protobuf:"varint,13,opt,name=error,proto3" json:"error,omitempty"` + // Failed attempts before this result, in order. Attempt n is at index n-1. + Attempts []*HTTPAttempt `protobuf:"bytes,14,rep,name=attempts,proto3" json:"attempts,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *IngestHTTPRequest) Reset() { *x = IngestHTTPRequest{} - mi := &file_private_location_v1_private_location_proto_msgTypes[4] + mi := &file_private_location_v1_private_location_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -326,7 +429,7 @@ func (x *IngestHTTPRequest) String() string { func (*IngestHTTPRequest) ProtoMessage() {} func (x *IngestHTTPRequest) ProtoReflect() protoreflect.Message { - mi := &file_private_location_v1_private_location_proto_msgTypes[4] + mi := &file_private_location_v1_private_location_proto_msgTypes[5] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -339,7 +442,7 @@ func (x *IngestHTTPRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use IngestHTTPRequest.ProtoReflect.Descriptor instead. func (*IngestHTTPRequest) Descriptor() ([]byte, []int) { - return file_private_location_v1_private_location_proto_rawDescGZIP(), []int{4} + return file_private_location_v1_private_location_proto_rawDescGZIP(), []int{5} } func (x *IngestHTTPRequest) GetId() string { @@ -433,6 +536,129 @@ func (x *IngestHTTPRequest) GetError() int64 { return 0 } +func (x *IngestHTTPRequest) GetAttempts() []*HTTPAttempt { + if x != nil { + return x.Attempts + } + return nil +} + +type HTTPAttempt struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Latency int64 `protobuf:"varint,2,opt,name=latency,proto3" json:"latency,omitempty"` + Timestamp int64 `protobuf:"varint,3,opt,name=timestamp,proto3" json:"timestamp,omitempty"` + RequestStatus string `protobuf:"bytes,4,opt,name=requestStatus,proto3" json:"requestStatus,omitempty"` + Message string `protobuf:"bytes,5,opt,name=message,proto3" json:"message,omitempty"` + Body string `protobuf:"bytes,6,opt,name=body,proto3" json:"body,omitempty"` + Headers string `protobuf:"bytes,7,opt,name=headers,proto3" json:"headers,omitempty"` + Timing string `protobuf:"bytes,8,opt,name=timing,proto3" json:"timing,omitempty"` + StatusCode int64 `protobuf:"varint,9,opt,name=statusCode,proto3" json:"statusCode,omitempty"` + Error int64 `protobuf:"varint,10,opt,name=error,proto3" json:"error,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *HTTPAttempt) Reset() { + *x = HTTPAttempt{} + mi := &file_private_location_v1_private_location_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *HTTPAttempt) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*HTTPAttempt) ProtoMessage() {} + +func (x *HTTPAttempt) ProtoReflect() protoreflect.Message { + mi := &file_private_location_v1_private_location_proto_msgTypes[6] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use HTTPAttempt.ProtoReflect.Descriptor instead. +func (*HTTPAttempt) Descriptor() ([]byte, []int) { + return file_private_location_v1_private_location_proto_rawDescGZIP(), []int{6} +} + +func (x *HTTPAttempt) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *HTTPAttempt) GetLatency() int64 { + if x != nil { + return x.Latency + } + return 0 +} + +func (x *HTTPAttempt) GetTimestamp() int64 { + if x != nil { + return x.Timestamp + } + return 0 +} + +func (x *HTTPAttempt) GetRequestStatus() string { + if x != nil { + return x.RequestStatus + } + return "" +} + +func (x *HTTPAttempt) GetMessage() string { + if x != nil { + return x.Message + } + return "" +} + +func (x *HTTPAttempt) GetBody() string { + if x != nil { + return x.Body + } + return "" +} + +func (x *HTTPAttempt) GetHeaders() string { + if x != nil { + return x.Headers + } + return "" +} + +func (x *HTTPAttempt) GetTiming() string { + if x != nil { + return x.Timing + } + return "" +} + +func (x *HTTPAttempt) GetStatusCode() int64 { + if x != nil { + return x.StatusCode + } + return 0 +} + +func (x *HTTPAttempt) GetError() int64 { + if x != nil { + return x.Error + } + return 0 +} + type IngestHTTPResponse struct { state protoimpl.MessageState `protogen:"open.v1"` unknownFields protoimpl.UnknownFields @@ -441,7 +667,7 @@ type IngestHTTPResponse struct { func (x *IngestHTTPResponse) Reset() { *x = IngestHTTPResponse{} - mi := &file_private_location_v1_private_location_proto_msgTypes[5] + mi := &file_private_location_v1_private_location_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -453,7 +679,7 @@ func (x *IngestHTTPResponse) String() string { func (*IngestHTTPResponse) ProtoMessage() {} func (x *IngestHTTPResponse) ProtoReflect() protoreflect.Message { - mi := &file_private_location_v1_private_location_proto_msgTypes[5] + mi := &file_private_location_v1_private_location_proto_msgTypes[7] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -466,7 +692,7 @@ func (x *IngestHTTPResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use IngestHTTPResponse.ProtoReflect.Descriptor instead. func (*IngestHTTPResponse) Descriptor() ([]byte, []int) { - return file_private_location_v1_private_location_proto_rawDescGZIP(), []int{5} + return file_private_location_v1_private_location_proto_rawDescGZIP(), []int{7} } type Records struct { @@ -478,7 +704,7 @@ type Records struct { func (x *Records) Reset() { *x = Records{} - mi := &file_private_location_v1_private_location_proto_msgTypes[6] + mi := &file_private_location_v1_private_location_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -490,7 +716,7 @@ func (x *Records) String() string { func (*Records) ProtoMessage() {} func (x *Records) ProtoReflect() protoreflect.Message { - mi := &file_private_location_v1_private_location_proto_msgTypes[6] + mi := &file_private_location_v1_private_location_proto_msgTypes[8] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -503,7 +729,7 @@ func (x *Records) ProtoReflect() protoreflect.Message { // Deprecated: Use Records.ProtoReflect.Descriptor instead. func (*Records) Descriptor() ([]byte, []int) { - return file_private_location_v1_private_location_proto_rawDescGZIP(), []int{6} + return file_private_location_v1_private_location_proto_rawDescGZIP(), []int{8} } func (x *Records) GetRecord() []string { @@ -526,13 +752,15 @@ type IngestDNSRequest struct { Records map[string]*Records `protobuf:"bytes,9,rep,name=records,proto3" json:"records,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` Timing string `protobuf:"bytes,10,opt,name=timing,proto3" json:"timing,omitempty"` Error int64 `protobuf:"varint,11,opt,name=error,proto3" json:"error,omitempty"` + // Failed attempts before this result, in order. Attempt n is at index n-1. + Attempts []*DNSAttempt `protobuf:"bytes,12,rep,name=attempts,proto3" json:"attempts,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *IngestDNSRequest) Reset() { *x = IngestDNSRequest{} - mi := &file_private_location_v1_private_location_proto_msgTypes[7] + mi := &file_private_location_v1_private_location_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -544,7 +772,7 @@ func (x *IngestDNSRequest) String() string { func (*IngestDNSRequest) ProtoMessage() {} func (x *IngestDNSRequest) ProtoReflect() protoreflect.Message { - mi := &file_private_location_v1_private_location_proto_msgTypes[7] + mi := &file_private_location_v1_private_location_proto_msgTypes[9] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -557,7 +785,7 @@ func (x *IngestDNSRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use IngestDNSRequest.ProtoReflect.Descriptor instead. func (*IngestDNSRequest) Descriptor() ([]byte, []int) { - return file_private_location_v1_private_location_proto_rawDescGZIP(), []int{7} + return file_private_location_v1_private_location_proto_rawDescGZIP(), []int{9} } func (x *IngestDNSRequest) GetId() string { @@ -637,6 +865,113 @@ func (x *IngestDNSRequest) GetError() int64 { return 0 } +func (x *IngestDNSRequest) GetAttempts() []*DNSAttempt { + if x != nil { + return x.Attempts + } + return nil +} + +type DNSAttempt struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Latency int64 `protobuf:"varint,2,opt,name=latency,proto3" json:"latency,omitempty"` + Timestamp int64 `protobuf:"varint,3,opt,name=timestamp,proto3" json:"timestamp,omitempty"` + RequestStatus string `protobuf:"bytes,4,opt,name=requestStatus,proto3" json:"requestStatus,omitempty"` + Message string `protobuf:"bytes,5,opt,name=message,proto3" json:"message,omitempty"` + Records map[string]*Records `protobuf:"bytes,6,rep,name=records,proto3" json:"records,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + Timing string `protobuf:"bytes,7,opt,name=timing,proto3" json:"timing,omitempty"` + Error int64 `protobuf:"varint,8,opt,name=error,proto3" json:"error,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DNSAttempt) Reset() { + *x = DNSAttempt{} + mi := &file_private_location_v1_private_location_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DNSAttempt) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DNSAttempt) ProtoMessage() {} + +func (x *DNSAttempt) ProtoReflect() protoreflect.Message { + mi := &file_private_location_v1_private_location_proto_msgTypes[10] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DNSAttempt.ProtoReflect.Descriptor instead. +func (*DNSAttempt) Descriptor() ([]byte, []int) { + return file_private_location_v1_private_location_proto_rawDescGZIP(), []int{10} +} + +func (x *DNSAttempt) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *DNSAttempt) GetLatency() int64 { + if x != nil { + return x.Latency + } + return 0 +} + +func (x *DNSAttempt) GetTimestamp() int64 { + if x != nil { + return x.Timestamp + } + return 0 +} + +func (x *DNSAttempt) GetRequestStatus() string { + if x != nil { + return x.RequestStatus + } + return "" +} + +func (x *DNSAttempt) GetMessage() string { + if x != nil { + return x.Message + } + return "" +} + +func (x *DNSAttempt) GetRecords() map[string]*Records { + if x != nil { + return x.Records + } + return nil +} + +func (x *DNSAttempt) GetTiming() string { + if x != nil { + return x.Timing + } + return "" +} + +func (x *DNSAttempt) GetError() int64 { + if x != nil { + return x.Error + } + return 0 +} + type IngestDNSResponse struct { state protoimpl.MessageState `protogen:"open.v1"` unknownFields protoimpl.UnknownFields @@ -645,7 +980,7 @@ type IngestDNSResponse struct { func (x *IngestDNSResponse) Reset() { *x = IngestDNSResponse{} - mi := &file_private_location_v1_private_location_proto_msgTypes[8] + mi := &file_private_location_v1_private_location_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -657,7 +992,7 @@ func (x *IngestDNSResponse) String() string { func (*IngestDNSResponse) ProtoMessage() {} func (x *IngestDNSResponse) ProtoReflect() protoreflect.Message { - mi := &file_private_location_v1_private_location_proto_msgTypes[8] + mi := &file_private_location_v1_private_location_proto_msgTypes[11] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -670,7 +1005,7 @@ func (x *IngestDNSResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use IngestDNSResponse.ProtoReflect.Descriptor instead. func (*IngestDNSResponse) Descriptor() ([]byte, []int) { - return file_private_location_v1_private_location_proto_rawDescGZIP(), []int{8} + return file_private_location_v1_private_location_proto_rawDescGZIP(), []int{11} } type IngestICMPRequest struct { @@ -689,13 +1024,15 @@ type IngestICMPRequest struct { RequestStatus string `protobuf:"bytes,12,opt,name=requestStatus,proto3" json:"requestStatus,omitempty"` Error int64 `protobuf:"varint,13,opt,name=error,proto3" json:"error,omitempty"` Timing string `protobuf:"bytes,14,opt,name=timing,proto3" json:"timing,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + // Failed attempts before this result, in order. Attempt n is at index n-1. + Attempts []*ICMPAttempt `protobuf:"bytes,15,rep,name=attempts,proto3" json:"attempts,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *IngestICMPRequest) Reset() { *x = IngestICMPRequest{} - mi := &file_private_location_v1_private_location_proto_msgTypes[9] + mi := &file_private_location_v1_private_location_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -707,7 +1044,7 @@ func (x *IngestICMPRequest) String() string { func (*IngestICMPRequest) ProtoMessage() {} func (x *IngestICMPRequest) ProtoReflect() protoreflect.Message { - mi := &file_private_location_v1_private_location_proto_msgTypes[9] + mi := &file_private_location_v1_private_location_proto_msgTypes[12] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -720,7 +1057,7 @@ func (x *IngestICMPRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use IngestICMPRequest.ProtoReflect.Descriptor instead. func (*IngestICMPRequest) Descriptor() ([]byte, []int) { - return file_private_location_v1_private_location_proto_rawDescGZIP(), []int{9} + return file_private_location_v1_private_location_proto_rawDescGZIP(), []int{12} } func (x *IngestICMPRequest) GetId() string { @@ -821,6 +1158,137 @@ func (x *IngestICMPRequest) GetTiming() string { return "" } +func (x *IngestICMPRequest) GetAttempts() []*ICMPAttempt { + if x != nil { + return x.Attempts + } + return nil +} + +type ICMPAttempt struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Latency int64 `protobuf:"varint,2,opt,name=latency,proto3" json:"latency,omitempty"` + LatencyMin int64 `protobuf:"varint,3,opt,name=latencyMin,proto3" json:"latencyMin,omitempty"` + LatencyMax int64 `protobuf:"varint,4,opt,name=latencyMax,proto3" json:"latencyMax,omitempty"` + PacketsSent int64 `protobuf:"varint,5,opt,name=packetsSent,proto3" json:"packetsSent,omitempty"` + PacketsReceived int64 `protobuf:"varint,6,opt,name=packetsReceived,proto3" json:"packetsReceived,omitempty"` + Timestamp int64 `protobuf:"varint,7,opt,name=timestamp,proto3" json:"timestamp,omitempty"` + Message string `protobuf:"bytes,8,opt,name=message,proto3" json:"message,omitempty"` + RequestStatus string `protobuf:"bytes,9,opt,name=requestStatus,proto3" json:"requestStatus,omitempty"` + Error int64 `protobuf:"varint,10,opt,name=error,proto3" json:"error,omitempty"` + Timing string `protobuf:"bytes,11,opt,name=timing,proto3" json:"timing,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ICMPAttempt) Reset() { + *x = ICMPAttempt{} + mi := &file_private_location_v1_private_location_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ICMPAttempt) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ICMPAttempt) ProtoMessage() {} + +func (x *ICMPAttempt) ProtoReflect() protoreflect.Message { + mi := &file_private_location_v1_private_location_proto_msgTypes[13] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ICMPAttempt.ProtoReflect.Descriptor instead. +func (*ICMPAttempt) Descriptor() ([]byte, []int) { + return file_private_location_v1_private_location_proto_rawDescGZIP(), []int{13} +} + +func (x *ICMPAttempt) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *ICMPAttempt) GetLatency() int64 { + if x != nil { + return x.Latency + } + return 0 +} + +func (x *ICMPAttempt) GetLatencyMin() int64 { + if x != nil { + return x.LatencyMin + } + return 0 +} + +func (x *ICMPAttempt) GetLatencyMax() int64 { + if x != nil { + return x.LatencyMax + } + return 0 +} + +func (x *ICMPAttempt) GetPacketsSent() int64 { + if x != nil { + return x.PacketsSent + } + return 0 +} + +func (x *ICMPAttempt) GetPacketsReceived() int64 { + if x != nil { + return x.PacketsReceived + } + return 0 +} + +func (x *ICMPAttempt) GetTimestamp() int64 { + if x != nil { + return x.Timestamp + } + return 0 +} + +func (x *ICMPAttempt) GetMessage() string { + if x != nil { + return x.Message + } + return "" +} + +func (x *ICMPAttempt) GetRequestStatus() string { + if x != nil { + return x.RequestStatus + } + return "" +} + +func (x *ICMPAttempt) GetError() int64 { + if x != nil { + return x.Error + } + return 0 +} + +func (x *ICMPAttempt) GetTiming() string { + if x != nil { + return x.Timing + } + return "" +} + type IngestICMPResponse struct { state protoimpl.MessageState `protogen:"open.v1"` unknownFields protoimpl.UnknownFields @@ -829,7 +1297,7 @@ type IngestICMPResponse struct { func (x *IngestICMPResponse) Reset() { *x = IngestICMPResponse{} - mi := &file_private_location_v1_private_location_proto_msgTypes[10] + mi := &file_private_location_v1_private_location_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -841,7 +1309,7 @@ func (x *IngestICMPResponse) String() string { func (*IngestICMPResponse) ProtoMessage() {} func (x *IngestICMPResponse) ProtoReflect() protoreflect.Message { - mi := &file_private_location_v1_private_location_proto_msgTypes[10] + mi := &file_private_location_v1_private_location_proto_msgTypes[14] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -854,7 +1322,7 @@ func (x *IngestICMPResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use IngestICMPResponse.ProtoReflect.Descriptor instead. func (*IngestICMPResponse) Descriptor() ([]byte, []int) { - return file_private_location_v1_private_location_proto_rawDescGZIP(), []int{10} + return file_private_location_v1_private_location_proto_rawDescGZIP(), []int{14} } type IngestGRPCRequest struct { @@ -872,13 +1340,15 @@ type IngestGRPCRequest struct { RequestStatus string `protobuf:"bytes,11,opt,name=requestStatus,proto3" json:"requestStatus,omitempty"` Error int64 `protobuf:"varint,12,opt,name=error,proto3" json:"error,omitempty"` Timing string `protobuf:"bytes,13,opt,name=timing,proto3" json:"timing,omitempty"` + // Failed attempts before this result, in order. Attempt n is at index n-1. + Attempts []*GRPCAttempt `protobuf:"bytes,14,rep,name=attempts,proto3" json:"attempts,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *IngestGRPCRequest) Reset() { *x = IngestGRPCRequest{} - mi := &file_private_location_v1_private_location_proto_msgTypes[11] + mi := &file_private_location_v1_private_location_proto_msgTypes[15] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -890,7 +1360,7 @@ func (x *IngestGRPCRequest) String() string { func (*IngestGRPCRequest) ProtoMessage() {} func (x *IngestGRPCRequest) ProtoReflect() protoreflect.Message { - mi := &file_private_location_v1_private_location_proto_msgTypes[11] + mi := &file_private_location_v1_private_location_proto_msgTypes[15] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -903,7 +1373,7 @@ func (x *IngestGRPCRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use IngestGRPCRequest.ProtoReflect.Descriptor instead. func (*IngestGRPCRequest) Descriptor() ([]byte, []int) { - return file_private_location_v1_private_location_proto_rawDescGZIP(), []int{11} + return file_private_location_v1_private_location_proto_rawDescGZIP(), []int{15} } func (x *IngestGRPCRequest) GetId() string { @@ -997,6 +1467,121 @@ func (x *IngestGRPCRequest) GetTiming() string { return "" } +func (x *IngestGRPCRequest) GetAttempts() []*GRPCAttempt { + if x != nil { + return x.Attempts + } + return nil +} + +type GRPCAttempt struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Latency int64 `protobuf:"varint,2,opt,name=latency,proto3" json:"latency,omitempty"` + Timestamp int64 `protobuf:"varint,3,opt,name=timestamp,proto3" json:"timestamp,omitempty"` + ServingStatus string `protobuf:"bytes,4,opt,name=servingStatus,proto3" json:"servingStatus,omitempty"` + GrpcCode int64 `protobuf:"varint,5,opt,name=grpcCode,proto3" json:"grpcCode,omitempty"` + Message string `protobuf:"bytes,6,opt,name=message,proto3" json:"message,omitempty"` + RequestStatus string `protobuf:"bytes,7,opt,name=requestStatus,proto3" json:"requestStatus,omitempty"` + Error int64 `protobuf:"varint,8,opt,name=error,proto3" json:"error,omitempty"` + Timing string `protobuf:"bytes,9,opt,name=timing,proto3" json:"timing,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GRPCAttempt) Reset() { + *x = GRPCAttempt{} + mi := &file_private_location_v1_private_location_proto_msgTypes[16] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GRPCAttempt) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GRPCAttempt) ProtoMessage() {} + +func (x *GRPCAttempt) ProtoReflect() protoreflect.Message { + mi := &file_private_location_v1_private_location_proto_msgTypes[16] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GRPCAttempt.ProtoReflect.Descriptor instead. +func (*GRPCAttempt) Descriptor() ([]byte, []int) { + return file_private_location_v1_private_location_proto_rawDescGZIP(), []int{16} +} + +func (x *GRPCAttempt) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *GRPCAttempt) GetLatency() int64 { + if x != nil { + return x.Latency + } + return 0 +} + +func (x *GRPCAttempt) GetTimestamp() int64 { + if x != nil { + return x.Timestamp + } + return 0 +} + +func (x *GRPCAttempt) GetServingStatus() string { + if x != nil { + return x.ServingStatus + } + return "" +} + +func (x *GRPCAttempt) GetGrpcCode() int64 { + if x != nil { + return x.GrpcCode + } + return 0 +} + +func (x *GRPCAttempt) GetMessage() string { + if x != nil { + return x.Message + } + return "" +} + +func (x *GRPCAttempt) GetRequestStatus() string { + if x != nil { + return x.RequestStatus + } + return "" +} + +func (x *GRPCAttempt) GetError() int64 { + if x != nil { + return x.Error + } + return 0 +} + +func (x *GRPCAttempt) GetTiming() string { + if x != nil { + return x.Timing + } + return "" +} + type IngestGRPCResponse struct { state protoimpl.MessageState `protogen:"open.v1"` unknownFields protoimpl.UnknownFields @@ -1005,7 +1590,7 @@ type IngestGRPCResponse struct { func (x *IngestGRPCResponse) Reset() { *x = IngestGRPCResponse{} - mi := &file_private_location_v1_private_location_proto_msgTypes[12] + mi := &file_private_location_v1_private_location_proto_msgTypes[17] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1017,7 +1602,7 @@ func (x *IngestGRPCResponse) String() string { func (*IngestGRPCResponse) ProtoMessage() {} func (x *IngestGRPCResponse) ProtoReflect() protoreflect.Message { - mi := &file_private_location_v1_private_location_proto_msgTypes[12] + mi := &file_private_location_v1_private_location_proto_msgTypes[17] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1030,7 +1615,7 @@ func (x *IngestGRPCResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use IngestGRPCResponse.ProtoReflect.Descriptor instead. func (*IngestGRPCResponse) Descriptor() ([]byte, []int) { - return file_private_location_v1_private_location_proto_rawDescGZIP(), []int{12} + return file_private_location_v1_private_location_proto_rawDescGZIP(), []int{17} } var File_private_location_v1_private_location_proto protoreflect.FileDescriptor @@ -1045,7 +1630,7 @@ const file_private_location_v1_private_location_proto_rawDesc = "" + "\fdns_monitors\x18\x03 \x03(\v2\x1f.private_location.v1.DNSMonitorR\vdnsMonitors\x12E\n" + "\ricmp_monitors\x18\x05 \x03(\v2 .private_location.v1.ICMPMonitorR\ficmpMonitors\x12E\n" + "\rgrpc_monitors\x18\x06 \x03(\v2 .private_location.v1.GRPCMonitorR\fgrpcMonitors\x12\x16\n" + - "\x06region\x18\x04 \x01(\tR\x06region\"\x9e\x02\n" + + "\x06region\x18\x04 \x01(\tR\x06region\"\xdb\x02\n" + "\x10IngestTCPRequest\x12\x0e\n" + "\x02id\x18\x01 \x01(\tR\x02id\x12\x1c\n" + "\tmonitorId\x18\x02 \x01(\tR\tmonitorId\x12\x18\n" + @@ -1057,8 +1642,18 @@ const file_private_location_v1_private_location_proto_rawDesc = "" + "\rrequestStatus\x18\b \x01(\tR\rrequestStatus\x12\x14\n" + "\x05error\x18\t \x01(\x03R\x05error\x12\x16\n" + "\x06timing\x18\n" + - " \x01(\tR\x06timing\"\x13\n" + - "\x11IngestTCPResponse\"\xed\x02\n" + + " \x01(\tR\x06timing\x12;\n" + + "\battempts\x18\v \x03(\v2\x1f.private_location.v1.TCPAttemptR\battempts\"\xc2\x01\n" + + "\n" + + "TCPAttempt\x12\x0e\n" + + "\x02id\x18\x01 \x01(\tR\x02id\x12\x18\n" + + "\alatency\x18\x02 \x01(\x03R\alatency\x12\x1c\n" + + "\ttimestamp\x18\x03 \x01(\x03R\ttimestamp\x12\x18\n" + + "\amessage\x18\x04 \x01(\tR\amessage\x12$\n" + + "\rrequestStatus\x18\x05 \x01(\tR\rrequestStatus\x12\x14\n" + + "\x05error\x18\x06 \x01(\x03R\x05error\x12\x16\n" + + "\x06timing\x18\a \x01(\tR\x06timing\"\x13\n" + + "\x11IngestTCPResponse\"\xab\x03\n" + "\x11IngestHTTPRequest\x12\x0e\n" + "\x02id\x18\x01 \x01(\tR\x02id\x12\x1c\n" + "\tmonitorId\x18\x02 \x01(\tR\tmonitorId\x12\x18\n" + @@ -1075,10 +1670,25 @@ const file_private_location_v1_private_location_proto_rawDesc = "" + "\n" + "statusCode\x18\f \x01(\x03R\n" + "statusCode\x12\x14\n" + - "\x05error\x18\r \x01(\x03R\x05error\"\x14\n" + + "\x05error\x18\r \x01(\x03R\x05error\x12<\n" + + "\battempts\x18\x0e \x03(\v2 .private_location.v1.HTTPAttemptR\battempts\"\x91\x02\n" + + "\vHTTPAttempt\x12\x0e\n" + + "\x02id\x18\x01 \x01(\tR\x02id\x12\x18\n" + + "\alatency\x18\x02 \x01(\x03R\alatency\x12\x1c\n" + + "\ttimestamp\x18\x03 \x01(\x03R\ttimestamp\x12$\n" + + "\rrequestStatus\x18\x04 \x01(\tR\rrequestStatus\x12\x18\n" + + "\amessage\x18\x05 \x01(\tR\amessage\x12\x12\n" + + "\x04body\x18\x06 \x01(\tR\x04body\x12\x18\n" + + "\aheaders\x18\a \x01(\tR\aheaders\x12\x16\n" + + "\x06timing\x18\b \x01(\tR\x06timing\x12\x1e\n" + + "\n" + + "statusCode\x18\t \x01(\x03R\n" + + "statusCode\x12\x14\n" + + "\x05error\x18\n" + + " \x01(\x03R\x05error\"\x14\n" + "\x12IngestHTTPResponse\"!\n" + "\aRecords\x12\x16\n" + - "\x06record\x18\x01 \x03(\tR\x06record\"\xc6\x03\n" + + "\x06record\x18\x01 \x03(\tR\x06record\"\x83\x04\n" + "\x10IngestDNSRequest\x12\x0e\n" + "\x02id\x18\x01 \x01(\tR\x02id\x12\x1c\n" + "\tmonitorId\x18\x02 \x01(\tR\tmonitorId\x12\x18\n" + @@ -1091,11 +1701,25 @@ const file_private_location_v1_private_location_proto_rawDesc = "" + "\arecords\x18\t \x03(\v22.private_location.v1.IngestDNSRequest.RecordsEntryR\arecords\x12\x16\n" + "\x06timing\x18\n" + " \x01(\tR\x06timing\x12\x14\n" + - "\x05error\x18\v \x01(\x03R\x05error\x1aX\n" + + "\x05error\x18\v \x01(\x03R\x05error\x12;\n" + + "\battempts\x18\f \x03(\v2\x1f.private_location.v1.DNSAttemptR\battempts\x1aX\n" + + "\fRecordsEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x122\n" + + "\x05value\x18\x02 \x01(\v2\x1c.private_location.v1.RecordsR\x05value:\x028\x01\"\xe4\x02\n" + + "\n" + + "DNSAttempt\x12\x0e\n" + + "\x02id\x18\x01 \x01(\tR\x02id\x12\x18\n" + + "\alatency\x18\x02 \x01(\x03R\alatency\x12\x1c\n" + + "\ttimestamp\x18\x03 \x01(\x03R\ttimestamp\x12$\n" + + "\rrequestStatus\x18\x04 \x01(\tR\rrequestStatus\x12\x18\n" + + "\amessage\x18\x05 \x01(\tR\amessage\x12F\n" + + "\arecords\x18\x06 \x03(\v2,.private_location.v1.DNSAttempt.RecordsEntryR\arecords\x12\x16\n" + + "\x06timing\x18\a \x01(\tR\x06timing\x12\x14\n" + + "\x05error\x18\b \x01(\x03R\x05error\x1aX\n" + "\fRecordsEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x122\n" + "\x05value\x18\x02 \x01(\v2\x1c.private_location.v1.RecordsR\x05value:\x028\x01\"\x13\n" + - "\x11IngestDNSResponse\"\xab\x03\n" + + "\x11IngestDNSResponse\"\xe9\x03\n" + "\x11IngestICMPRequest\x12\x0e\n" + "\x02id\x18\x01 \x01(\tR\x02id\x12\x1c\n" + "\tmonitorId\x18\x02 \x01(\tR\tmonitorId\x12\x18\n" + @@ -1115,8 +1739,26 @@ const file_private_location_v1_private_location_proto_rawDesc = "" + "\amessage\x18\v \x01(\tR\amessage\x12$\n" + "\rrequestStatus\x18\f \x01(\tR\rrequestStatus\x12\x14\n" + "\x05error\x18\r \x01(\x03R\x05error\x12\x16\n" + - "\x06timing\x18\x0e \x01(\tR\x06timing\"\x14\n" + - "\x12IngestICMPResponse\"\xfb\x02\n" + + "\x06timing\x18\x0e \x01(\tR\x06timing\x12<\n" + + "\battempts\x18\x0f \x03(\v2 .private_location.v1.ICMPAttemptR\battempts\"\xcf\x02\n" + + "\vICMPAttempt\x12\x0e\n" + + "\x02id\x18\x01 \x01(\tR\x02id\x12\x18\n" + + "\alatency\x18\x02 \x01(\x03R\alatency\x12\x1e\n" + + "\n" + + "latencyMin\x18\x03 \x01(\x03R\n" + + "latencyMin\x12\x1e\n" + + "\n" + + "latencyMax\x18\x04 \x01(\x03R\n" + + "latencyMax\x12 \n" + + "\vpacketsSent\x18\x05 \x01(\x03R\vpacketsSent\x12(\n" + + "\x0fpacketsReceived\x18\x06 \x01(\x03R\x0fpacketsReceived\x12\x1c\n" + + "\ttimestamp\x18\a \x01(\x03R\ttimestamp\x12\x18\n" + + "\amessage\x18\b \x01(\tR\amessage\x12$\n" + + "\rrequestStatus\x18\t \x01(\tR\rrequestStatus\x12\x14\n" + + "\x05error\x18\n" + + " \x01(\x03R\x05error\x12\x16\n" + + "\x06timing\x18\v \x01(\tR\x06timing\"\x14\n" + + "\x12IngestICMPResponse\"\xb9\x03\n" + "\x11IngestGRPCRequest\x12\x0e\n" + "\x02id\x18\x01 \x01(\tR\x02id\x12\x1c\n" + "\tmonitorId\x18\x02 \x01(\tR\tmonitorId\x12\x18\n" + @@ -1131,7 +1773,18 @@ const file_private_location_v1_private_location_proto_rawDesc = "" + " \x01(\tR\amessage\x12$\n" + "\rrequestStatus\x18\v \x01(\tR\rrequestStatus\x12\x14\n" + "\x05error\x18\f \x01(\x03R\x05error\x12\x16\n" + - "\x06timing\x18\r \x01(\tR\x06timing\"\x14\n" + + "\x06timing\x18\r \x01(\tR\x06timing\x12<\n" + + "\battempts\x18\x0e \x03(\v2 .private_location.v1.GRPCAttemptR\battempts\"\x85\x02\n" + + "\vGRPCAttempt\x12\x0e\n" + + "\x02id\x18\x01 \x01(\tR\x02id\x12\x18\n" + + "\alatency\x18\x02 \x01(\x03R\alatency\x12\x1c\n" + + "\ttimestamp\x18\x03 \x01(\x03R\ttimestamp\x12$\n" + + "\rservingStatus\x18\x04 \x01(\tR\rservingStatus\x12\x1a\n" + + "\bgrpcCode\x18\x05 \x01(\x03R\bgrpcCode\x12\x18\n" + + "\amessage\x18\x06 \x01(\tR\amessage\x12$\n" + + "\rrequestStatus\x18\a \x01(\tR\rrequestStatus\x12\x14\n" + + "\x05error\x18\b \x01(\x03R\x05error\x12\x16\n" + + "\x06timing\x18\t \x01(\tR\x06timing\"\x14\n" + "\x12IngestGRPCResponse2\xd2\x04\n" + "\x16PrivateLocationService\x12Y\n" + "\bMonitors\x12$.private_location.v1.MonitorsRequest\x1a%.private_location.v1.MonitorsResponse\"\x00\x12\\\n" + @@ -1156,53 +1809,66 @@ func file_private_location_v1_private_location_proto_rawDescGZIP() []byte { return file_private_location_v1_private_location_proto_rawDescData } -var file_private_location_v1_private_location_proto_msgTypes = make([]protoimpl.MessageInfo, 14) +var file_private_location_v1_private_location_proto_msgTypes = make([]protoimpl.MessageInfo, 20) var file_private_location_v1_private_location_proto_goTypes = []any{ (*MonitorsRequest)(nil), // 0: private_location.v1.MonitorsRequest (*MonitorsResponse)(nil), // 1: private_location.v1.MonitorsResponse (*IngestTCPRequest)(nil), // 2: private_location.v1.IngestTCPRequest - (*IngestTCPResponse)(nil), // 3: private_location.v1.IngestTCPResponse - (*IngestHTTPRequest)(nil), // 4: private_location.v1.IngestHTTPRequest - (*IngestHTTPResponse)(nil), // 5: private_location.v1.IngestHTTPResponse - (*Records)(nil), // 6: private_location.v1.Records - (*IngestDNSRequest)(nil), // 7: private_location.v1.IngestDNSRequest - (*IngestDNSResponse)(nil), // 8: private_location.v1.IngestDNSResponse - (*IngestICMPRequest)(nil), // 9: private_location.v1.IngestICMPRequest - (*IngestICMPResponse)(nil), // 10: private_location.v1.IngestICMPResponse - (*IngestGRPCRequest)(nil), // 11: private_location.v1.IngestGRPCRequest - (*IngestGRPCResponse)(nil), // 12: private_location.v1.IngestGRPCResponse - nil, // 13: private_location.v1.IngestDNSRequest.RecordsEntry - (*HTTPMonitor)(nil), // 14: private_location.v1.HTTPMonitor - (*TCPMonitor)(nil), // 15: private_location.v1.TCPMonitor - (*DNSMonitor)(nil), // 16: private_location.v1.DNSMonitor - (*ICMPMonitor)(nil), // 17: private_location.v1.ICMPMonitor - (*GRPCMonitor)(nil), // 18: private_location.v1.GRPCMonitor + (*TCPAttempt)(nil), // 3: private_location.v1.TCPAttempt + (*IngestTCPResponse)(nil), // 4: private_location.v1.IngestTCPResponse + (*IngestHTTPRequest)(nil), // 5: private_location.v1.IngestHTTPRequest + (*HTTPAttempt)(nil), // 6: private_location.v1.HTTPAttempt + (*IngestHTTPResponse)(nil), // 7: private_location.v1.IngestHTTPResponse + (*Records)(nil), // 8: private_location.v1.Records + (*IngestDNSRequest)(nil), // 9: private_location.v1.IngestDNSRequest + (*DNSAttempt)(nil), // 10: private_location.v1.DNSAttempt + (*IngestDNSResponse)(nil), // 11: private_location.v1.IngestDNSResponse + (*IngestICMPRequest)(nil), // 12: private_location.v1.IngestICMPRequest + (*ICMPAttempt)(nil), // 13: private_location.v1.ICMPAttempt + (*IngestICMPResponse)(nil), // 14: private_location.v1.IngestICMPResponse + (*IngestGRPCRequest)(nil), // 15: private_location.v1.IngestGRPCRequest + (*GRPCAttempt)(nil), // 16: private_location.v1.GRPCAttempt + (*IngestGRPCResponse)(nil), // 17: private_location.v1.IngestGRPCResponse + nil, // 18: private_location.v1.IngestDNSRequest.RecordsEntry + nil, // 19: private_location.v1.DNSAttempt.RecordsEntry + (*HTTPMonitor)(nil), // 20: private_location.v1.HTTPMonitor + (*TCPMonitor)(nil), // 21: private_location.v1.TCPMonitor + (*DNSMonitor)(nil), // 22: private_location.v1.DNSMonitor + (*ICMPMonitor)(nil), // 23: private_location.v1.ICMPMonitor + (*GRPCMonitor)(nil), // 24: private_location.v1.GRPCMonitor } var file_private_location_v1_private_location_proto_depIdxs = []int32{ - 14, // 0: private_location.v1.MonitorsResponse.http_monitors:type_name -> private_location.v1.HTTPMonitor - 15, // 1: private_location.v1.MonitorsResponse.tcp_monitors:type_name -> private_location.v1.TCPMonitor - 16, // 2: private_location.v1.MonitorsResponse.dns_monitors:type_name -> private_location.v1.DNSMonitor - 17, // 3: private_location.v1.MonitorsResponse.icmp_monitors:type_name -> private_location.v1.ICMPMonitor - 18, // 4: private_location.v1.MonitorsResponse.grpc_monitors:type_name -> private_location.v1.GRPCMonitor - 13, // 5: private_location.v1.IngestDNSRequest.records:type_name -> private_location.v1.IngestDNSRequest.RecordsEntry - 6, // 6: private_location.v1.IngestDNSRequest.RecordsEntry.value:type_name -> private_location.v1.Records - 0, // 7: private_location.v1.PrivateLocationService.Monitors:input_type -> private_location.v1.MonitorsRequest - 2, // 8: private_location.v1.PrivateLocationService.IngestTCP:input_type -> private_location.v1.IngestTCPRequest - 4, // 9: private_location.v1.PrivateLocationService.IngestHTTP:input_type -> private_location.v1.IngestHTTPRequest - 7, // 10: private_location.v1.PrivateLocationService.IngestDNS:input_type -> private_location.v1.IngestDNSRequest - 9, // 11: private_location.v1.PrivateLocationService.IngestICMP:input_type -> private_location.v1.IngestICMPRequest - 11, // 12: private_location.v1.PrivateLocationService.IngestGRPC:input_type -> private_location.v1.IngestGRPCRequest - 1, // 13: private_location.v1.PrivateLocationService.Monitors:output_type -> private_location.v1.MonitorsResponse - 3, // 14: private_location.v1.PrivateLocationService.IngestTCP:output_type -> private_location.v1.IngestTCPResponse - 5, // 15: private_location.v1.PrivateLocationService.IngestHTTP:output_type -> private_location.v1.IngestHTTPResponse - 8, // 16: private_location.v1.PrivateLocationService.IngestDNS:output_type -> private_location.v1.IngestDNSResponse - 10, // 17: private_location.v1.PrivateLocationService.IngestICMP:output_type -> private_location.v1.IngestICMPResponse - 12, // 18: private_location.v1.PrivateLocationService.IngestGRPC:output_type -> private_location.v1.IngestGRPCResponse - 13, // [13:19] is the sub-list for method output_type - 7, // [7:13] is the sub-list for method input_type - 7, // [7:7] is the sub-list for extension type_name - 7, // [7:7] is the sub-list for extension extendee - 0, // [0:7] is the sub-list for field type_name + 20, // 0: private_location.v1.MonitorsResponse.http_monitors:type_name -> private_location.v1.HTTPMonitor + 21, // 1: private_location.v1.MonitorsResponse.tcp_monitors:type_name -> private_location.v1.TCPMonitor + 22, // 2: private_location.v1.MonitorsResponse.dns_monitors:type_name -> private_location.v1.DNSMonitor + 23, // 3: private_location.v1.MonitorsResponse.icmp_monitors:type_name -> private_location.v1.ICMPMonitor + 24, // 4: private_location.v1.MonitorsResponse.grpc_monitors:type_name -> private_location.v1.GRPCMonitor + 3, // 5: private_location.v1.IngestTCPRequest.attempts:type_name -> private_location.v1.TCPAttempt + 6, // 6: private_location.v1.IngestHTTPRequest.attempts:type_name -> private_location.v1.HTTPAttempt + 18, // 7: private_location.v1.IngestDNSRequest.records:type_name -> private_location.v1.IngestDNSRequest.RecordsEntry + 10, // 8: private_location.v1.IngestDNSRequest.attempts:type_name -> private_location.v1.DNSAttempt + 19, // 9: private_location.v1.DNSAttempt.records:type_name -> private_location.v1.DNSAttempt.RecordsEntry + 13, // 10: private_location.v1.IngestICMPRequest.attempts:type_name -> private_location.v1.ICMPAttempt + 16, // 11: private_location.v1.IngestGRPCRequest.attempts:type_name -> private_location.v1.GRPCAttempt + 8, // 12: private_location.v1.IngestDNSRequest.RecordsEntry.value:type_name -> private_location.v1.Records + 8, // 13: private_location.v1.DNSAttempt.RecordsEntry.value:type_name -> private_location.v1.Records + 0, // 14: private_location.v1.PrivateLocationService.Monitors:input_type -> private_location.v1.MonitorsRequest + 2, // 15: private_location.v1.PrivateLocationService.IngestTCP:input_type -> private_location.v1.IngestTCPRequest + 5, // 16: private_location.v1.PrivateLocationService.IngestHTTP:input_type -> private_location.v1.IngestHTTPRequest + 9, // 17: private_location.v1.PrivateLocationService.IngestDNS:input_type -> private_location.v1.IngestDNSRequest + 12, // 18: private_location.v1.PrivateLocationService.IngestICMP:input_type -> private_location.v1.IngestICMPRequest + 15, // 19: private_location.v1.PrivateLocationService.IngestGRPC:input_type -> private_location.v1.IngestGRPCRequest + 1, // 20: private_location.v1.PrivateLocationService.Monitors:output_type -> private_location.v1.MonitorsResponse + 4, // 21: private_location.v1.PrivateLocationService.IngestTCP:output_type -> private_location.v1.IngestTCPResponse + 7, // 22: private_location.v1.PrivateLocationService.IngestHTTP:output_type -> private_location.v1.IngestHTTPResponse + 11, // 23: private_location.v1.PrivateLocationService.IngestDNS:output_type -> private_location.v1.IngestDNSResponse + 14, // 24: private_location.v1.PrivateLocationService.IngestICMP:output_type -> private_location.v1.IngestICMPResponse + 17, // 25: private_location.v1.PrivateLocationService.IngestGRPC:output_type -> private_location.v1.IngestGRPCResponse + 20, // [20:26] is the sub-list for method output_type + 14, // [14:20] is the sub-list for method input_type + 14, // [14:14] is the sub-list for extension type_name + 14, // [14:14] is the sub-list for extension extendee + 0, // [0:14] is the sub-list for field type_name } func init() { file_private_location_v1_private_location_proto_init() } @@ -1221,7 +1887,7 @@ func file_private_location_v1_private_location_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_private_location_v1_private_location_proto_rawDesc), len(file_private_location_v1_private_location_proto_rawDesc)), NumEnums: 0, - NumMessages: 14, + NumMessages: 20, NumExtensions: 0, NumServices: 1, }, diff --git a/packages/proto/internal/private_location/v1/private_location.proto b/packages/proto/internal/private_location/v1/private_location.proto index d4a55bffe..b94111270 100644 --- a/packages/proto/internal/private_location/v1/private_location.proto +++ b/packages/proto/internal/private_location/v1/private_location.proto @@ -44,7 +44,18 @@ message IngestTCPRequest { string requestStatus = 8; int64 error = 9; string timing = 10; + // Failed attempts before this result, in order. Attempt n is at index n-1. + repeated TCPAttempt attempts = 11; +} +message TCPAttempt { + string id = 1; + int64 latency = 2; + int64 timestamp = 3; + string message = 4; + string requestStatus = 5; + int64 error = 6; + string timing = 7; } message IngestTCPResponse { @@ -65,6 +76,21 @@ message IngestHTTPRequest { string timing = 11; int64 statusCode = 12; int64 error = 13; + // Failed attempts before this result, in order. Attempt n is at index n-1. + repeated HTTPAttempt attempts = 14; +} + +message HTTPAttempt { + string id = 1; + int64 latency = 2; + int64 timestamp = 3; + string requestStatus = 4; + string message = 5; + string body = 6; + string headers = 7; + string timing = 8; + int64 statusCode = 9; + int64 error = 10; } message IngestHTTPResponse { @@ -87,6 +113,19 @@ message IngestDNSRequest { map records = 9; string timing = 10; int64 error = 11; + // Failed attempts before this result, in order. Attempt n is at index n-1. + repeated DNSAttempt attempts = 12; +} + +message DNSAttempt { + string id = 1; + int64 latency = 2; + int64 timestamp = 3; + string requestStatus = 4; + string message = 5; + map records = 6; + string timing = 7; + int64 error = 8; } message IngestDNSResponse { @@ -107,6 +146,22 @@ message IngestICMPRequest { string requestStatus = 12; int64 error = 13; string timing = 14; + // Failed attempts before this result, in order. Attempt n is at index n-1. + repeated ICMPAttempt attempts = 15; +} + +message ICMPAttempt { + string id = 1; + int64 latency = 2; + int64 latencyMin = 3; + int64 latencyMax = 4; + int64 packetsSent = 5; + int64 packetsReceived = 6; + int64 timestamp = 7; + string message = 8; + string requestStatus = 9; + int64 error = 10; + string timing = 11; } message IngestICMPResponse { @@ -127,6 +182,20 @@ message IngestGRPCRequest { string requestStatus = 11; int64 error = 12; string timing = 13; + // Failed attempts before this result, in order. Attempt n is at index n-1. + repeated GRPCAttempt attempts = 14; +} + +message GRPCAttempt { + string id = 1; + int64 latency = 2; + int64 timestamp = 3; + string servingStatus = 4; + int64 grpcCode = 5; + string message = 6; + string requestStatus = 7; + int64 error = 8; + string timing = 9; } message IngestGRPCResponse {