blob: 32b2c595eeed91ab28489f1afd57fb0f643208a3 [file] [log] [blame]
/*
Periodically report the status of all attached Android devices to InfluxDB.
*/
package main
import (
"context"
"encoding/json"
"flag"
"time"
"go.skia.org/infra/go/common"
"go.skia.org/infra/go/exec"
"go.skia.org/infra/go/metrics2"
"go.skia.org/infra/go/sklog"
)
const (
MEASUREMENT = "android-stats"
)
var (
frequency = flag.String("frequency", "1m", "How often to send data.")
local = flag.Bool("local", false, "Whether or not we're running in local testing mode.")
promPort = flag.String("prom_port", ":20000", "Metrics service address (e.g., ':10110')")
statsScript = flag.String("stats_script", "", "Script to run which generates stats.")
)
// reportStats drills down recursively until it hits a non-map value,
// then reports that value with the measurement as given above, tags for device,
// serial, and stat name, which is composed of the remaining map keys, eg.
// device=Nexus_5X serial=002e3da61560d3d4 stat=battery-level
func reportStats(device, serial, stat string, val interface{}) {
float, ok := val.(float64)
if ok {
tags := map[string]string{
"device": device,
"serial": serial,
"stat": stat,
}
sklog.Infof("%s %v = %v", MEASUREMENT, tags, float)
metrics2.GetFloat64Metric(MEASUREMENT, map[string]string{}).Update(float)
return
}
m, ok := val.(map[string]interface{})
if ok {
for k, v := range m {
reportStats(device, serial, stat+"-"+k, v)
}
}
}
// generateStats runs the statistics generation script, parses its output, and
// reports the data into metrics.
//
// The script produces data in this format:
// {
// "Nexus_5X": {
// "002e3da61560d3d4": {
// "battery": {
// "ac": 0,
// "health": 2,
// "level": 100,
// "max": 500000,
// "present": 1,
// "status": 5,
// "temp": 282,
// "usb": 1,
// "voltage": 4311,
// "wireless": 0
// },
// "temperature": 28.0
// }
// }
// }
//
func generateStats(ctx context.Context) error {
output, err := exec.RunSimple(ctx, *statsScript)
if err != nil {
return err
}
res := map[string]map[string]map[string]interface{}{}
if err := json.Unmarshal([]byte(output), &res); err != nil {
return err
}
for device, deviceStats := range res {
for serial, statMap := range deviceStats {
for stat, val := range statMap {
reportStats(device, serial, stat, val)
}
}
}
return nil
}
func main() {
common.InitWithMust(
"android_stats",
common.PrometheusOpt(promPort),
)
ctx := context.Background()
if *statsScript == "" {
sklog.Fatal("You must provide --stats_script.")
}
pollFreq, err := time.ParseDuration(*frequency)
if err != nil {
sklog.Fatalf("Invalid value for frequency %q: %s", *frequency, err)
}
if err := generateStats(ctx); err != nil {
sklog.Fatal(err)
}
for range time.Tick(pollFreq) {
if err := generateStats(ctx); err != nil {
sklog.Error(err)
}
}
}