Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions api/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@

- [lorawan-stack/api/applicationserver.proto](#lorawan-stack/api/applicationserver.proto)
- [ApplicationLink](#ttn.lorawan.v3.ApplicationLink)
- [ApplicationLinkStats](#ttn.lorawan.v3.ApplicationLinkStats)
- [GetApplicationLinkRequest](#ttn.lorawan.v3.GetApplicationLinkRequest)
- [SetApplicationLinkRequest](#ttn.lorawan.v3.SetApplicationLinkRequest)

Expand Down Expand Up @@ -797,6 +798,26 @@ where the user or organization is collaborator on.



<a name="ttn.lorawan.v3.ApplicationLinkStats"/>

### ApplicationLinkStats
Link stats as monitored by the Application Server.


| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| linked_at | [google.protobuf.Timestamp](#google.protobuf.Timestamp) | | |
| network_server_address | [string](#string) | | |
| last_up_received_at | [google.protobuf.Timestamp](#google.protobuf.Timestamp) | | Timestamp when the last upstream message has been received from a Network Server. This can be a join-accept, uplink message or downlink message event. |
| up_count | [uint64](#uint64) | | Number of upstream messages received. |
| last_downlink_forwarded_at | [google.protobuf.Timestamp](#google.protobuf.Timestamp) | | Timestamp when the last downlink message has been forwarded to a Network Server. |
| downlink_count | [uint64](#uint64) | | Number of downlink messages forwarded. |






<a name="ttn.lorawan.v3.GetApplicationLinkRequest"/>

### GetApplicationLinkRequest
Expand Down Expand Up @@ -859,6 +880,7 @@ The As service manages the Application Server.
| GetLink | [GetApplicationLinkRequest](#ttn.lorawan.v3.GetApplicationLinkRequest) | [ApplicationLink](#ttn.lorawan.v3.GetApplicationLinkRequest) | |
| SetLink | [SetApplicationLinkRequest](#ttn.lorawan.v3.SetApplicationLinkRequest) | [ApplicationLink](#ttn.lorawan.v3.SetApplicationLinkRequest) | |
| DeleteLink | [ApplicationIdentifiers](#ttn.lorawan.v3.ApplicationIdentifiers) | [.google.protobuf.Empty](#ttn.lorawan.v3.ApplicationIdentifiers) | |
| GetLinkStats | [ApplicationIdentifiers](#ttn.lorawan.v3.ApplicationIdentifiers) | [ApplicationLinkStats](#ttn.lorawan.v3.ApplicationIdentifiers) | |


<a name="ttn.lorawan.v3.AsEndDeviceRegistry"/>
Expand Down
89 changes: 89 additions & 0 deletions api/api.swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -864,6 +864,30 @@
]
}
},
"/as/applications/{application_id}/link/stats": {
"get": {
"operationId": "GetLinkStats",
"responses": {
"200": {
"description": "A successful response.",
"schema": {
"$ref": "#/definitions/v3ApplicationLinkStats"
}
}
},
"parameters": [
{
"name": "application_id",
"in": "path",
"required": true,
"type": "string"
}
],
"tags": [
"As"
]
}
},
"/as/applications/{device.ids.application_ids.application_id}/devices": {
"post": {
"operationId": "Set2",
Expand Down Expand Up @@ -1859,6 +1883,38 @@
]
}
},
"/gs/gateways/{gateway_id}/connection/stats": {
"get": {
"operationId": "GetGatewayConnectionStats",
"responses": {
"200": {
"description": "A successful response.",
"schema": {
"$ref": "#/definitions/v3GatewayConnectionStats"
}
}
},
"parameters": [
{
"name": "gateway_id",
"in": "path",
"required": true,
"type": "string"
},
{
"name": "eui",
"description": "Secondary identifier, which can only be used in specific requests.",
"in": "query",
"required": false,
"type": "string",
"format": "byte"
}
],
"tags": [
"Gs"
]
}
},
"/invitations": {
"get": {
"operationId": "List",
Expand Down Expand Up @@ -5346,6 +5402,39 @@
}
}
},
"v3ApplicationLinkStats": {
"type": "object",
"properties": {
"linked_at": {
"type": "string",
"format": "date-time"
},
"network_server_address": {
"type": "string"
},
"last_up_received_at": {
"type": "string",
"format": "date-time",
"description": "Timestamp when the last upstream message has been received from a Network Server.\nThis can be a join-accept, uplink message or downlink message event."
},
"up_count": {
"type": "string",
"format": "uint64",
"description": "Number of upstream messages received."
},
"last_downlink_forwarded_at": {
"type": "string",
"format": "date-time",
"description": "Timestamp when the last downlink message has been forwarded to a Network Server."
},
"downlink_count": {
"type": "string",
"format": "uint64",
"description": "Number of downlink messages forwarded."
}
},
"description": "Link stats as monitored by the Application Server."
},
"v3ApplicationLocation": {
"type": "object",
"properties": {
Expand Down
22 changes: 22 additions & 0 deletions api/applicationserver.proto
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import "github.com/mwitkow/go-proto-validators/validator.proto";
import "google/api/annotations.proto";
import "google/protobuf/empty.proto";
import "google/protobuf/field_mask.proto";
import "google/protobuf/timestamp.proto";
import "lorawan-stack/api/end_device.proto";
import "lorawan-stack/api/identifiers.proto";
import "lorawan-stack/api/messages.proto";
Expand Down Expand Up @@ -48,6 +49,21 @@ message SetApplicationLinkRequest {
google.protobuf.FieldMask field_mask = 3 [(gogoproto.nullable) = false];
}

// Link stats as monitored by the Application Server.
message ApplicationLinkStats {
google.protobuf.Timestamp linked_at = 1 [(gogoproto.stdtime) = true];
string network_server_address = 2;
// Timestamp when the last upstream message has been received from a Network Server.
// This can be a join-accept, uplink message or downlink message event.
google.protobuf.Timestamp last_up_received_at = 3 [(gogoproto.stdtime) = true];
// Number of upstream messages received.
uint64 up_count = 4;
// Timestamp when the last downlink message has been forwarded to a Network Server.
google.protobuf.Timestamp last_downlink_forwarded_at = 5 [(gogoproto.stdtime) = true];
// Number of downlink messages forwarded.
uint64 downlink_count = 6;
}

// The As service manages the Application Server.
service As {
rpc GetLink(GetApplicationLinkRequest) returns (ApplicationLink) {
Expand All @@ -68,6 +84,12 @@ service As {
delete: "/as/applications/{application_id}/link",
};
};

rpc GetLinkStats(ApplicationIdentifiers) returns (ApplicationLinkStats) {
option (google.api.http) = {
get: "/as/applications/{application_id}/link/stats"
};
};
}

// The AppAs service connects an application or integration to an Application Server.
Expand Down
7 changes: 6 additions & 1 deletion api/gatewayserver.proto
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
syntax = "proto3";

import "github.com/gogo/protobuf/gogoproto/gogo.proto";
import "google/api/annotations.proto";
import "google/protobuf/duration.proto";
import "google/protobuf/empty.proto";
import "lorawan-stack/api/gateway.proto";
Expand Down Expand Up @@ -63,5 +64,9 @@ service NsGs {
service Gs {
// Get statistics about the current gateway connection to the Gateway Server.
// This is not persisted between reconnects.
rpc GetGatewayConnectionStats(GatewayIdentifiers) returns (GatewayConnectionStats);
rpc GetGatewayConnectionStats(GatewayIdentifiers) returns (GatewayConnectionStats) {
option (google.api.http) = {
get: "/gs/gateways/{gateway_id}/connection/stats"
};
};
}
18 changes: 18 additions & 0 deletions config/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -4760,6 +4760,24 @@
"file": "observability.go"
}
},
"event:as.link.start": {
"translations": {
"en": "link start"
},
"description": {
"package": "pkg/applicationserver",
"file": "observability.go"
}
},
"event:as.link.stop": {
"translations": {
"en": "link stop"
},
"description": {
"package": "pkg/applicationserver",
"file": "observability.go"
}
},
"event:as.up.data.decode.fail": {
"translations": {
"en": "decode uplink data message fail"
Expand Down
18 changes: 10 additions & 8 deletions pkg/applicationserver/applicationserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"fmt"
"net"
"sync"
"sync/atomic"
"time"

pbtypes "github.com/gogo/protobuf/types"
Expand Down Expand Up @@ -229,6 +230,7 @@ func (as *ApplicationServer) downlinkQueueOp(ctx context.Context, ids ttnpb.EndD
return err
}
<-link.connReady
var encryptedItems []*ttnpb.ApplicationDownlink
_, err = as.deviceRegistry.Set(ctx, ids,
[]string{
"session",
Expand All @@ -254,29 +256,29 @@ func (as *ApplicationServer) downlinkQueueOp(ctx context.Context, ids ttnpb.EndD
item.DecodedPayload = nil
item.CorrelationIDs = events.CorrelationIDsFromContext(ctx)
dev.Session.LastAFCntDown = item.FCnt
encryptedItems = append(encryptedItems, item)
}
client := ttnpb.NewAsNsClient(link.conn)
req := &ttnpb.DownlinkQueueRequest{
EndDeviceIdentifiers: ids,
Downlinks: items,
Downlinks: encryptedItems,
}
_, err = op(client, ctx, req, link.callOpts...)
if err != nil {
for _, item := range items {
registerDropDownlink(ctx, ids, item, err)
}
return nil, nil, err
}
for _, item := range items {
registerForwardDownlink(ctx, ids, item, link.connName)
}
return dev, []string{"session.last_a_f_cnt_down"}, nil
},
)
if err != nil {
for _, item := range encryptedItems {
registerDropDownlink(ctx, ids, item, err)
}
return err
}
for _, item := range items {
atomic.AddUint64(&link.downlinks, uint64(len(encryptedItems)))
atomic.StoreInt64(&link.lastDownlinkTime, time.Now().UnixNano())
for _, item := range encryptedItems {
registerForwardDownlink(ctx, ids, item, link.connName)
}
return nil
Expand Down
27 changes: 27 additions & 0 deletions pkg/applicationserver/grpc_as.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,3 +75,30 @@ func (as *ApplicationServer) DeleteLink(ctx context.Context, ids *ttnpb.Applicat
}
return ttnpb.Empty, nil
}

// GetLinkStats implements ttnpb.AsServer.
func (as *ApplicationServer) GetLinkStats(ctx context.Context, ids *ttnpb.ApplicationIdentifiers) (*ttnpb.ApplicationLinkStats, error) {
if err := rights.RequireApplication(ctx, *ids, ttnpb.RIGHT_APPLICATION_LINK); err != nil {
return nil, err
}

link, err := as.getLink(ctx, *ids)
if err != nil {
return nil, err
}
<-link.connReady

stats := &ttnpb.ApplicationLinkStats{}
lt := link.GetLinkTime()
stats.LinkedAt = &lt
stats.NetworkServerAddress = link.NetworkServerAddress
if n, t, ok := link.GetUpStats(); ok {
stats.UpCount = n
stats.LastUpReceivedAt = &t
}
if n, t, ok := link.GetDownlinkStats(); ok {
stats.DownlinkCount = n
stats.LastDownlinkForwardedAt = &t
}
return stats, nil
}
Loading