Support power calculation and CSV header line.

If --header is specified, then a header line is generated for the CSV
output.

If --resistance is set to a positive number (should be the shunt
resistance for the zedmon unit), then power is calculated and included
in the output.

Change-Id: Id84cf6d38e7d90e3eab02046ffaa1a4ce24af703
diff --git a/cmd/zedmon/record.go b/cmd/zedmon/record.go
index bc383b6..df3f51e 100644
--- a/cmd/zedmon/record.go
+++ b/cmd/zedmon/record.go
@@ -43,7 +43,10 @@
 
 type recordCmd struct{}
 
+// Flag-controlled values.
 var out string
+var writeHeader bool
+var resistance float64
 
 func (*recordCmd) Name() string { return "record" }
 func (*recordCmd) Synopsis() string {
@@ -57,6 +60,34 @@
 func (*recordCmd) SetFlags(flags *flag.FlagSet) {
 	setCommonFlags(flags)
 	flags.StringVar(&out, "out", "zedmon.csv", "Output file. Specify \"-\" for stdout.")
+	flags.BoolVar(&writeHeader, "header", false, "Whether to include a header line in the output.")
+	flags.Float64Var(&resistance, "resistance", -1.0,
+		"Shunt resistance in Ohms. If set to a positive value, power will be included in the output.")
+}
+
+// If a positive resistance and field names including "v_bus" and "v_shunt" are provided, returns a
+// function that calculates power from zedmon-provided values. Returns nil otherwise.
+func getPowerCalculator(resistance float64, fieldNames []string) func(values []float64) float64 {
+	if resistance <= 0.0 {
+		return nil
+	}
+
+	vBusIndex := -1
+	vShuntIndex := -1
+	for i, name := range fieldNames {
+		if name == "v_bus" {
+			vBusIndex = i
+		} else if name == "v_shunt" {
+			vShuntIndex = i
+		}
+	}
+	if vBusIndex < 0 || vShuntIndex < 0 {
+		return nil
+	}
+
+	return func(values []float64) float64 {
+		return values[vShuntIndex] * values[vBusIndex] / resistance
+	}
 }
 
 func (*recordCmd) Execute(_ context.Context, _ *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
@@ -95,6 +126,20 @@
 	log.Println("Starting report recording. Send ^C to stop.")
 	zedmon.EnableReporting()
 
+	fieldNames := zedmon.GetFieldNames()
+
+	calculatePower := getPowerCalculator(resistance, fieldNames)
+	if calculatePower != nil {
+		fieldNames = append(fieldNames, "power")
+	}
+
+	if writeHeader {
+		header := make([]string, 1+len(fieldNames))
+		header[0] = "timestamp"
+		copy(header[1:], fieldNames)
+		writer.Write(header)
+	}
+
 RecordSampling:
 	for {
 		reports, err := zedmon.ReadReports()
@@ -103,11 +148,15 @@
 			continue
 		}
 		for _, report := range reports {
-			record := make([]string, 0, len(report.Values))
+			record := make([]string, 0, len(fieldNames))
 			record = append(record, strconv.FormatUint(report.Timestamp, 10))
 			for _, value := range report.Values {
 				record = append(record, strconv.FormatFloat(value, 'g', -1, 64))
 			}
+			if calculatePower != nil {
+				power := calculatePower(report.Values)
+				record = append(record, strconv.FormatFloat(power, 'g', -1, 64))
+			}
 			writer.Write(record)
 		}
 
diff --git a/lib/zedmon.go b/lib/zedmon.go
index cf68a17..c429a62 100644
--- a/lib/zedmon.go
+++ b/lib/zedmon.go
@@ -72,6 +72,16 @@
 	fields []*Field
 }
 
+func (z *Zedmon) GetFieldNames() []string {
+	names := make([]string, len(z.fields))
+
+	for i, field := range z.fields {
+		names[i] = field.Name
+	}
+
+	return names
+}
+
 func (z *Zedmon) Close() {
 	if z.reportingEnabled {
 		z.DisableReporting()