sensorlog_lite: Add client CLI.
CLI runs against master device Syncbase, with support for adding
measuring devices, creating streams, and listing measured data.
Updated README.
Change-Id: Icc096466735a590d836aa78b7219ebfc284740a5
diff --git a/go/src/v.io/x/sensorlog/Makefile b/go/src/v.io/x/sensorlog/Makefile
index 84bb5a2..1585537 100644
--- a/go/src/v.io/x/sensorlog/Makefile
+++ b/go/src/v.io/x/sensorlog/Makefile
@@ -6,12 +6,16 @@
.DELETE_ON_ERROR:
.PHONY: all
-all: measured
+all: measured slcli
.PHONY: measured
measured:
jiri go install v.io/x/sensorlog_lite/measured
+.PHONY: slcli
+slcli:
+ jiri go install v.io/x/sensorlog_lite/slcli
+
.PHONY: run-deps
run-deps:
jiri go install v.io/x/ref/services/mounttable/mounttabled
diff --git a/go/src/v.io/x/sensorlog/README.md b/go/src/v.io/x/sensorlog/README.md
index 141d9a0..23e403a 100644
--- a/go/src/v.io/x/sensorlog/README.md
+++ b/go/src/v.io/x/sensorlog/README.md
@@ -4,25 +4,81 @@
time series data on a group of devices.
A Sensor Log Lite system consists of a master and any number of measuring
-devices. The master device runs Syncbase and the command line client, while
-each measuring device runs Syncbase and the measuring daemon (`measured`).
-The client controls the measuring daemon through Syncbase.
+devices. The master device runs Syncbase and the command line client
+(`slcli`), while each measuring device runs Syncbase and the measuring
+daemon (`measured`). The client controls the measuring daemon through
+Syncbase.
# Measuring data
-TODO(ivanpi): Skeleton, expand.
+Each measuring device runs an instance of `measured`, which samples data
+points for one or more measuring streams. A stream is an ordered sequence
+of data points that are sampled by running a shell script at a set
+frequency. The `slcli` tool can be used to write the sampling configuration
+to the master Syncbase instance, which is then synced to the appropriate
+measuring device Syncbase. Measured data points are synced back to the
+master Syncbase, where they can be examined using `slcli`.
-## Starting `measured`
+### Starting `measured`
+
+In the instructions below, replace `<creds>` with the path to
+Vanadium credentials obtained using `principal seekblessings`.
`measured`, along with the required `mounttabled` and `syncbased` services,
can be started by:
- $ V23_CREDENTIALS=<creds> ./scripts/run_measured.sh
+ $ V23_CREDENTIALS=<creds> SL_DEVID=dev1 ./scripts/run_measured.sh
-## Using the client
+By default, it starts a local mounttable at port 8707 (override using
+`$SL_IPADDR_PORT`), mounts it at
+`/ns.dev.v.io:8101/users/<email-from-blessing>/sl/measured/<devid>`,
+starts a Syncbase instance mounted in the local mounttable at
+`sl/measured/<devid>`, and a measuring daemon against the Syncbase instance.
-Before using the client, `syncbased` must be started by:
+### Using the client
+
+Before using the client, the master `syncbased` must be started by:
$ V23_CREDENTIALS=<creds> ./scripts/run_cli_syncbased.sh
-TODO(ivanpi): Skeleton, expand.
+By default, it starts a local mounttable at port 8202 (override using
+`$SL_IPADDR_PORT`) and starts a Syncbase instance mounted in it at
+`sl/client/main` (override using `$SL_DEVID`).
+
+Running
+
+ $ V23_CREDENTIALS=<creds> ./scripts/slcli.sh <args>
+
+will invoke the `slcli` tool with blessings and flags set appropriately
+for a master stack run with the same environment.
+
+### Adding a device
+
+Each measuring device creates a syncgroup which can be joined by a master
+device. Using the above default configuration, run:
+
+ $ V23_CREDENTIALS=<creds> ./scripts/slcli.sh device add /ns.dev.v.io:8101/users/<email-from-blessing>/sl/measured/dev1/sl/measured/dev1/syncbased dev1
+
+### Creating a stream
+
+Once a device has been added, streams can be configured on it to start
+sampling data. The sampling script is expected to output a single floating
+point value and exit with a zero status code on every invocation, otherwise
+an error is logged instead of data.
+
+For example, to sample the Answer to Life, the Universe, and Everything
+every two seconds:
+
+ $ V23_CREDENTIALS=<creds> ./scripts/slcli.sh stream create dev1 stream42 2s <<< "echo 42;"
+
+### Listing data
+
+To list all data sampled on the stream, run
+
+ $ V23_CREDENTIALS=<creds> ./scripts/slcli.sh list dev1 stream42
+
+### Debugging
+
+The internal state of the master Syncbase can be examined using `sb`:
+
+ $ V23_CREDENTIALS=<creds> ${JIRI_ROOT}/release/go/bin/vbecome -name sl/client/main ${JIRI_ROOT}/release/go/bin/sb -service /$(dig $(hostname) +short):8202/sl/client/main/syncbased sh sensorlog_lite sldb
diff --git a/go/src/v.io/x/sensorlog/internal/client/list.go b/go/src/v.io/x/sensorlog/internal/client/list.go
index ebf55ea..92919a6 100644
--- a/go/src/v.io/x/sensorlog/internal/client/list.go
+++ b/go/src/v.io/x/sensorlog/internal/client/list.go
@@ -23,6 +23,7 @@
// in chronological order, calling listCb for each.
// TODO(ivanpi): Allow specifying time interval.
func ListStreamData(ctx *context.T, db nosql.Database, streamKey *sbmodel.KStreamDef, listCb ListCallback) error {
+ tableName := sbmodel.KDataPoint{}.Table()
dataPrefix := keyutil.Join(streamKey.DevId, streamKey.StreamId, "")
bdb, err := db.BeginBatch(ctx, nosql_wire.BatchOptions{ReadOnly: true})
@@ -38,7 +39,7 @@
return verror.New(verror.ErrNoExist, ctx, "Stream '"+streamKey.Key()+"' does not exist")
}
- sstr := bdb.Table(sbmodel.KDataPoint{}.Table()).Scan(ctx, nosql.Prefix(dataPrefix))
+ sstr := bdb.Table(tableName).Scan(ctx, nosql.Prefix(dataPrefix))
defer sstr.Cancel()
for sstr.Advance() {
diff --git a/go/src/v.io/x/sensorlog/internal/config/defaults.go b/go/src/v.io/x/sensorlog/internal/config/defaults.go
index 8050794..a948ae2 100644
--- a/go/src/v.io/x/sensorlog/internal/config/defaults.go
+++ b/go/src/v.io/x/sensorlog/internal/config/defaults.go
@@ -19,6 +19,8 @@
SyncPriority = 42
+ TimeOutputFormat = "2006-01-02 15:04:05.000"
+
// Delay between SIGINT and SIGKILL when stopping measuring script.
ScriptKillDelay = 100 * time.Millisecond
diff --git a/go/src/v.io/x/sensorlog/measured/measured.go b/go/src/v.io/x/sensorlog/measured/measured.go
index b8e3bf7..7c75976 100644
--- a/go/src/v.io/x/sensorlog/measured/measured.go
+++ b/go/src/v.io/x/sensorlog/measured/measured.go
@@ -26,7 +26,7 @@
)
var (
- flagSbService = flag.String("service", config.DefaultSbService, "Location of the Syncbase service to connect to. Can be absolute or relative to the namespace root.")
+ flagSbService = flag.String("service", config.DefaultSbService, "Name of the Syncbase service to connect to. Can be absolute or relative to the namespace root.")
flagDevId = flag.String("devid", "", "DevId to be claimed by this measured. Must be specified.")
// Flags below are only applied the first time measured is run against a Syncbase service with the same devid.
flagAdmin = flag.String("admin", "", "Blessing of admin user allowed to join the syncgroup. Must be specified.")
diff --git a/go/src/v.io/x/sensorlog/scripts/run_cli_syncbased.sh b/go/src/v.io/x/sensorlog/scripts/run_cli_syncbased.sh
index 8c5b07a..e8ca090 100755
--- a/go/src/v.io/x/sensorlog/scripts/run_cli_syncbased.sh
+++ b/go/src/v.io/x/sensorlog/scripts/run_cli_syncbased.sh
@@ -13,7 +13,7 @@
# Optional environment variables: SL_PREFIX, SL_DEVID, SL_IPADDR_PORT, SL_TMPDIR
function main() {
local -r PREFIX="${SL_PREFIX:-sl/client}"
- local -r DEVID="${SL_DEVID:-$(gen_uuid)}"
+ local -r DEVID="${SL_DEVID:-main}"
local -r NAME="${PREFIX}/${DEVID}"
local -r IPADDR_PORT="${SL_IPADDR_PORT:-$(dig $(hostname) +short):8202}"
local -r TMPDIR="${SL_TMPDIR:-sltmp}/${NAME}"
diff --git a/go/src/v.io/x/sensorlog/scripts/runner_lib.sh b/go/src/v.io/x/sensorlog/scripts/runner_lib.sh
index b4e69f2..f898362 100644
--- a/go/src/v.io/x/sensorlog/scripts/runner_lib.sh
+++ b/go/src/v.io/x/sensorlog/scripts/runner_lib.sh
@@ -110,3 +110,15 @@
sleep 1
}
export -f run_measured
+
+# Runs slcli against master Syncbase with specified $NAME.
+# run_slcli MT NAME [args...]
+function run_slcli() {
+ local -r MT="$1"
+ local -r NAME="$2"
+ shift 2
+ "${JIRI_ROOT}"/release/go/bin/vbecome -name "$(name_to_blessing "${NAME}")" \
+ "${JIRI_ROOT}"/experimental/projects/sensorlog_lite/bin/slcli -v23.namespace.root "${MT}" \
+ -service "${NAME}/syncbased" "$@"
+}
+export -f run_slcli
diff --git a/go/src/v.io/x/sensorlog/scripts/slcli.sh b/go/src/v.io/x/sensorlog/scripts/slcli.sh
new file mode 100755
index 0000000..04fe39c
--- /dev/null
+++ b/go/src/v.io/x/sensorlog/scripts/slcli.sh
@@ -0,0 +1,22 @@
+#!/bin/bash
+# Copyright 2015 The Vanadium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style
+# license that can be found in the LICENSE file.
+
+# Runs slcli against master device Syncbase.
+
+set -eu
+
+source "${JIRI_ROOT}/experimental/projects/sensorlog_lite/src/v.io/x/sensorlog_lite/scripts/runner_lib.sh"
+
+# Must be run with V23_CREDENTIALS set or through the agent.
+# Optional environment variables: SL_PREFIX, SL_DEVID, SL_IPADDR_PORT
+function main() {
+ local -r PREFIX="${SL_PREFIX:-sl/client}"
+ local -r DEVID="${SL_DEVID:-main}"
+ local -r NAME="${PREFIX}/${DEVID}"
+ local -r IPADDR_PORT="${SL_IPADDR_PORT:-$(dig $(hostname) +short):8202}"
+ run_slcli "/${IPADDR_PORT}" "${NAME}" "$@"
+}
+
+main "$@"
diff --git a/go/src/v.io/x/sensorlog/slcli/device.go b/go/src/v.io/x/sensorlog/slcli/device.go
new file mode 100644
index 0000000..ee658f0
--- /dev/null
+++ b/go/src/v.io/x/sensorlog/slcli/device.go
@@ -0,0 +1,74 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// slcli device configuration.
+
+package main
+
+import (
+ "fmt"
+
+ "v.io/v23/context"
+ "v.io/x/lib/cmdline"
+ "v.io/x/ref/lib/v23cmd"
+ "v.io/x/sensorlog_lite/internal/client"
+ "v.io/x/sensorlog_lite/internal/sbmodel"
+ "v.io/x/sensorlog_lite/internal/sbutil"
+)
+
+var cmdSLDevice = &cmdline.Command{
+ Name: "device",
+ Short: "Manage measuring devices",
+ Long: `
+Add measuring devices.
+
+TODO(ivanpi): List.
+`,
+ Children: []*cmdline.Command{cmdSLDeviceAdd /*, cmdSLDeviceList */},
+}
+
+var cmdSLDeviceAdd = &cmdline.Command{
+ Runner: v23cmd.RunnerFunc(runSLDeviceAdd),
+ Name: "add",
+ Short: "Add new measuring device",
+ Long: `
+Adds a new measuring device and outputs its identifier.
+`,
+ ArgsName: "<publish_sb> <device_id> [<device_desc>]",
+ ArgsLong: `
+<publish_sb> is the rooted name of the Syncbase instance where the device
+ syncgroup is published.
+
+<device_id> is the identifier of the device to add.
+
+<device_desc> is a human-readable description of the device.
+ It doesn't need to be unique.
+`,
+}
+
+func runSLDeviceAdd(ctx *context.T, env *cmdline.Env, args []string) error {
+ if len(args) < 2 || len(args) > 3 {
+ return env.UsageErrorf("expects between 2 and 3 arguments")
+ }
+ sgPublishSb := args[0]
+ devId := args[1]
+ desc := ""
+ if len(args) > 2 {
+ desc = args[2]
+ }
+
+ db, err := sbutil.CreateOrOpenDB(ctx, *flagSbService, sbmodel.MasterTables)
+ if err != nil {
+ return fmt.Errorf("failed opening Syncbase db: %v", err)
+ }
+
+ devKey, err := client.AddDevice(ctx, db, devId, sgPublishSb, desc)
+ if err != nil {
+ return err
+ }
+
+ fmt.Fprintf(env.Stdout, "%s\n", devKey.DevId)
+
+ return nil
+}
diff --git a/go/src/v.io/x/sensorlog/slcli/list.go b/go/src/v.io/x/sensorlog/slcli/list.go
new file mode 100644
index 0000000..d65e1e6
--- /dev/null
+++ b/go/src/v.io/x/sensorlog/slcli/list.go
@@ -0,0 +1,55 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// slcli measured data listing support.
+
+package main
+
+import (
+ "fmt"
+
+ "v.io/v23/context"
+ "v.io/x/lib/cmdline"
+ "v.io/x/ref/lib/v23cmd"
+ "v.io/x/sensorlog_lite/internal/client"
+ "v.io/x/sensorlog_lite/internal/config"
+ "v.io/x/sensorlog_lite/internal/sbmodel"
+ "v.io/x/sensorlog_lite/internal/sbutil"
+)
+
+var cmdSLList = &cmdline.Command{
+ Runner: v23cmd.RunnerFunc(runSLList),
+ Name: "list",
+ Short: "List measured data",
+ Long: `
+Prints all measured data points for the given stream and device, sorted
+chronologically.
+`,
+ ArgsName: "<device_id> <stream_id>",
+ ArgsLong: `
+<device_id> and <stream_id> specify the data stream to print the data points
+from.
+`,
+}
+
+// TODO(ivanpi): Add time interval querying and aggregation functions.
+func runSLList(ctx *context.T, env *cmdline.Env, args []string) error {
+ if len(args) != 2 {
+ return env.UsageErrorf("expects exactly 2 arguments")
+ }
+ streamKey := &sbmodel.KStreamDef{
+ DevId: args[0],
+ StreamId: args[1],
+ }
+
+ db, err := sbutil.CreateOrOpenDB(ctx, *flagSbService, sbmodel.MasterTables)
+ if err != nil {
+ return fmt.Errorf("failed opening Syncbase db: %v", err)
+ }
+
+ return client.ListStreamData(ctx, db, streamKey, func(key *sbmodel.KDataPoint, val sbmodel.VDataPoint) error {
+ fmt.Fprintf(env.Stdout, "%s %v\n", key.Timestamp.Format(config.TimeOutputFormat), val.Interface())
+ return nil
+ })
+}
diff --git a/go/src/v.io/x/sensorlog/slcli/slcli.go b/go/src/v.io/x/sensorlog/slcli/slcli.go
new file mode 100644
index 0000000..20c65f6
--- /dev/null
+++ b/go/src/v.io/x/sensorlog/slcli/slcli.go
@@ -0,0 +1,39 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// slcli is the Sensor Log Lite command line configuration tool and client.
+// Must be run against master device Syncbase.
+// Supports configuring sampling streams by writing configuration to Syncbase,
+// which is then read by measured on the target device.
+// Also supports listing measured data.
+// TODO(ivanpi): Add data querying and graph plotting.
+package main
+
+import (
+ "flag"
+ "regexp"
+
+ "v.io/x/lib/cmdline"
+ _ "v.io/x/ref/runtime/factories/generic"
+ "v.io/x/sensorlog_lite/internal/config"
+)
+
+var (
+ flagSbService = flag.String("service", config.DefaultSbService, "Name of the Syncbase service to connect to. Can be absolute or relative to the namespace root.")
+)
+
+var cmdSensorLogLite = &cmdline.Command{
+ Name: "slcli",
+ Short: "Sensor Log Lite command line configuration tool and client",
+ Long: `
+Command line interface for Sensor Log Lite, used for listing data and
+manipulating configuration via a master device Syncbase.
+`,
+ Children: []*cmdline.Command{cmdSLDevice, cmdSLStream, cmdSLList},
+}
+
+func main() {
+ cmdline.HideGlobalFlagsExcept(regexp.MustCompile(`^((service)|(v23\.namespace\.root)|(v23\.credentials))$`))
+ cmdline.Main(cmdSensorLogLite)
+}
diff --git a/go/src/v.io/x/sensorlog/slcli/stream.go b/go/src/v.io/x/sensorlog/slcli/stream.go
new file mode 100644
index 0000000..23c75bc
--- /dev/null
+++ b/go/src/v.io/x/sensorlog/slcli/stream.go
@@ -0,0 +1,97 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// slcli stream configuration.
+
+package main
+
+import (
+ "fmt"
+ "io/ioutil"
+ "time"
+
+ "v.io/v23/context"
+ "v.io/x/lib/cmdline"
+ "v.io/x/ref/lib/v23cmd"
+ "v.io/x/sensorlog_lite/internal/client"
+ "v.io/x/sensorlog_lite/internal/sbmodel"
+ "v.io/x/sensorlog_lite/internal/sbutil"
+)
+
+var cmdSLStream = &cmdline.Command{
+ Name: "stream",
+ Short: "Manage sampling streams",
+ Long: `
+Create sampling streams.
+
+TODO(ivanpi): Enable/disable, list.
+`,
+ Children: []*cmdline.Command{cmdSLStreamCreate /*, cmdSLStreamEnable, cmdSLStreamDisable, cmdSLStreamList*/},
+}
+
+var cmdSLStreamCreate = &cmdline.Command{
+ Runner: v23cmd.RunnerFunc(runSLStreamCreate),
+ Name: "create",
+ Short: "Create new data stream",
+ Long: `
+Creates a new sampling stream and outputs its identifier.
+
+The sampling script is read from stdin. It must be a bash script that prints
+a single floating point result per invocation and exits with a zero status,
+otherwise an error is logged instead of the data point. The script will be
+run on the specified device at the specified frequency whenever the measuring
+daemon is running on the device, as long as the stream is enabled.
+`,
+ ArgsName: "<device_id> <stream_id> <interval> [<stream_desc>]",
+ ArgsLong: `
+<device_id> is the identifier of the device to use for sampling.
+
+<stream_id> is the identifier of the stream to be created. It must be unique
+ per measuring device.
+
+<interval> is the time interval between two subsequent invocations of the
+ measuring script. Measurements may be skipped if the interval is
+ too short compared to the device and sampling script speed.
+
+<stream_desc> is a human-readable description of the stream.
+ It doesn't need to be unique.
+`,
+}
+
+func runSLStreamCreate(ctx *context.T, env *cmdline.Env, args []string) error {
+ if len(args) < 3 || len(args) > 4 {
+ return env.UsageErrorf("expects between 3 and 4 arguments")
+ }
+ devKey := &sbmodel.KDeviceCfg{
+ DevId: args[0],
+ }
+ streamId := args[1]
+ interval, err := time.ParseDuration(args[2])
+ if err != nil {
+ return fmt.Errorf("failed parsing interval %q: %v", args[2], err)
+ }
+ desc := ""
+ if len(args) > 3 {
+ desc = args[3]
+ }
+
+ script, err := ioutil.ReadAll(env.Stdin)
+ if err != nil {
+ return err
+ }
+
+ db, err := sbutil.CreateOrOpenDB(ctx, *flagSbService, sbmodel.MasterTables)
+ if err != nil {
+ return fmt.Errorf("failed opening Syncbase db: %v", err)
+ }
+
+ streamKey, err := client.CreateStream(ctx, db, devKey, streamId, string(script), interval, desc)
+ if err != nil {
+ return err
+ }
+
+ fmt.Fprintf(env.Stdout, "%s\n", streamKey.StreamId)
+
+ return nil
+}