commit - 1636c523802a3144d79ae3175abb6a3ea5d64778
commit + 6ab6ce12c6d5cd60f205c9f5d9d33391b52c5f45
blob - 147b0817f915ff2871fb23ca627da9371eb95400
blob + b2879e418897334a3a65bcc858fe147d66c8f3d0
--- cmd/dishy/dishy.go
+++ cmd/dishy/dishy.go
import (
"flag"
+ "fmt"
"log"
+ "os"
"olowe.co/dishy"
)
var aFlag = flag.String("a", dishy.DefaultDishyAddr, "dishy device IP address")
+func printStatus(client *dishy.Client) error {
+ stat, err := client.Status()
+ if err != nil {
+ return fmt.Errorf("read status: %w", err)
+ }
+ fmt.Fprintln(os.Stderr, stat)
+ fmt.Println("alerts:", stat.Alerts)
+ fmt.Println("id:", stat.DeviceInfo.Id)
+ fmt.Println("ready:", stat.ReadyStates)
+ fmt.Println("outage:", stat.Outage)
+ fmt.Println("gps:", stat.GpsStats)
+ fmt.Println("stowed:", stat.StowRequested)
+ fmt.Println("updates:", stat.SoftwareUpdateState)
+ fmt.Println("lowsnr:", stat.IsSnrPersistentlyLow)
+ return nil
+}
+
func main() {
log.SetFlags(0)
log.SetPrefix("dishy:")
err = client.Stow()
case "unstow":
err = client.Unstow()
+ case "stat":
+ err = printStatus(client)
+ case "metrics":
+ stat, err := client.Status()
+ if err != nil {
+ log.Fatal("read status: %v", err)
+ }
+ err = dishy.WriteOpenMetrics(os.Stdout, stat)
}
if err != nil {
log.Fatalf("%s: %v", cmd, err)
blob - 9534fb0d1ac62309fd51d135b4637ea51f9eaaac
blob + 3d3730d6779ecd999f4a4d97942a0918dc669622
--- cmd/dishy/doc.go
+++ cmd/dishy/doc.go
Reposition dish in a vertical orientation for easier transport.
unstow
Reposition the dish in the orientation prior to unstowing.
+ stat
+ Print short device status and diagnostics.
+ metrics
+ Print device statistics in OpenMetrics/Prometheus format.
The flag -a specifies the address to connect to dishy.
By default this is the default IPv4 address and port that dishy listens on 192.168.100.1:9200.
blob - 6893fbe340039216aea4a53d12a7662d90cc73a3
blob + 5b2c4d7e3d7799570fd20e7ce6d8c22f2c3dea28
--- dishy.go
+++ dishy.go
import (
"context"
+ "fmt"
"time"
"google.golang.org/grpc"
return err
}
+func (c *Client) Status() (*device.DishGetStatusResponse, error) {
+ req := &device.Request{
+ Request: &device.Request_GetStatus{
+ GetStatus: &device.GetStatusRequest{},
+ },
+ }
+ resp, err := c.do(req)
+ return resp.GetDishGetStatus(), err
+}
+
+func (c *Client) Interfaces() ([]device.NetworkInterface, error) {
+ req := &device.Request{
+ Request: &device.Request_GetNetworkInterfaces{
+ GetNetworkInterfaces: &device.GetNetworkInterfacesRequest{},
+ },
+ }
+ resp, err := c.do(req)
+ if err != nil {
+ return nil, err
+ }
+ if resp.GetGetNetworkInterfaces() == nil {
+ return nil, fmt.Errorf("no interfaces in response")
+ }
+ var ifaces []device.NetworkInterface
+ for _, iface := range resp.GetGetNetworkInterfaces().NetworkInterfaces {
+ if iface == nil {
+ continue
+ }
+ ifaces = append(ifaces, *iface)
+ }
+ return ifaces, nil
+}
+
+func (c *Client) TransceiverTelemetry() (*device.TransceiverGetTelemetryResponse, error) {
+ req := &device.Request{
+ Request: &device.Request_TransceiverGetTelemetry{
+ TransceiverGetTelemetry: &device.TransceiverGetTelemetryRequest{},
+ },
+ }
+ resp, err := c.do(req)
+ if err != nil {
+ return nil, fmt.Errorf("do request: %w", err)
+ }
+ if resp.GetTransceiverGetTelemetry() == nil {
+ return nil, fmt.Errorf("no telemetry in response")
+ }
+ return resp.GetTransceiverGetTelemetry(), nil
+}
+
+func (c *Client) TransceiverStat() (*device.TransceiverGetStatusResponse, error) {
+ req := &device.Request{
+ Request: &device.Request_TransceiverGetStatus{
+ TransceiverGetStatus: &device.TransceiverGetStatusRequest{},
+ },
+ }
+ resp, err := c.do(req)
+ if err != nil {
+ return nil, fmt.Errorf("do request: %w", err)
+ }
+ if resp.GetTransceiverGetStatus() == nil {
+ return nil, fmt.Errorf("no telemetry in response")
+ }
+ return resp.GetTransceiverGetStatus(), nil
+}
+
func (c *Client) do(req *device.Request) (*device.Response, error) {
ctx := context.Background()
if c.Timeout > 0 {
blob - /dev/null
blob + 48e0fd5734d22c73f4a5744292fce191962c3fef (mode 644)
--- /dev/null
+++ metrics.go
+package dishy
+
+import (
+ "fmt"
+ "io"
+ "text/template"
+
+ "olowe.co/dishy/device"
+)
+
+const metricsPage string = `
+# HELP dishy_uptime_seconds Seconds since last boot.
+# TYPE dishy_uptime_seconds counter
+dishy_uptime_seconds {{ .DeviceState.UptimeS }}
+# HELP dishy_pop_ping_drop_rate
+# TYPE dishy_pop_ping_drop_rate gauge
+dishy_pop_ping_drop_rate {{ .PopPingDropRate }}
+# HELP dishy_pop_ping_latency_milliseconds
+# TYPE dishy_pop_ping_latency gauge
+dishy_pop_ping_latency_milliseconds {{ .PopPingLatencyMs }}
+# HELP dishy_downlink_throughput Received bytes per second.
+# TYPE dishy_downlink_throughput gauge
+dishy_downlink_throughput {{ .DownlinkThroughputBps }}
+# HELP dishy_uplink_throughput Transmitted bytes per second.
+# TYPE dishy_uplink_throughput gauge
+dishy_uplink_throughput {{ .UplinkThroughputBps }}
+# HELP dishy_obstruction_percentage
+# TYPE dishy_obstruction_percentage gauge
+dishy_obstruction_percentage {{ .ObstructionStats.FractionObstructed }}
+`
+
+var metricsTmpl = template.Must(template.New("metrics").Parse(metricsPage))
+
+// WriteOpenMetrics writes any metrics found in status in [OpenMetrics]
+// format to w for use in systems such as Prometheus and VictoriaMetrics.
+//
+// [OpenMetrics]: https://openmetrics.io/
+func WriteOpenMetrics(w io.Writer, status *device.DishGetStatusResponse) error {
+ err := metricsTmpl.Execute(w, status)
+ if err != nil {
+ return fmt.Errorf("execute template: %w", err)
+ }
+ return nil
+}