diff --git a/fritzbox_lua/lua_client.go b/fritzbox_lua/lua_client.go index 906bf02..3693f4b 100644 --- a/fritzbox_lua/lua_client.go +++ b/fritzbox_lua/lua_client.go @@ -213,6 +213,39 @@ func getRenamedLabel(labelRenames *[]LabelRename, label string) string { return label } +func getValueFromHashOrArray(mapOrArray interface{}, key string, path string) (interface{}, error) { + var value interface{} + + switch moa := mapOrArray.(type) { + case map[string]interface{}: + var exists bool + value, exists = moa[key] + if !exists { + return nil, fmt.Errorf("hash '%s' has no element '%s'", path, key) + } + case []interface{}: + // since type is array there can't be any labels to differentiate values, so only one value supported ! + index, err := strconv.Atoi(key) + if err != nil { + return nil, fmt.Errorf("item '%s' is an array, but index '%s' is not a number", path, key) + } + + if index < 0 { + // this is an index from the end of the values + index += len(moa) + } + + if index < 0 || index >= len(moa) { + return nil, fmt.Errorf("index %d is invalid for array '%s' with length %d", index, path, len(moa)) + } + value = moa[index] + default: + return nil, fmt.Errorf("item '%s' is not a hash or array, can't get value %s", path, key) + } + + return value, nil +} + // GetMetrics get metrics from parsed lua page for definition and rename labels func GetMetrics(labelRenames *[]LabelRename, data map[string]interface{}, metricDef LuaMetricValueDefinition) ([]LuaMetricValue, error) { @@ -229,50 +262,51 @@ func GetMetrics(labelRenames *[]LabelRename, data map[string]interface{}, metric values[0] = data } - name := metricDef.Path - if name != "" { - name += "." - } - name += metricDef.Key - metrics := make([]LuaMetricValue, 0) - for _, valUntyped := range values { - switch v := valUntyped.(type) { - case map[string]interface{}: - value, exists := v[metricDef.Key] - if exists { - lmv := metricDef.createValue(name, toString(value)) - - for _, l := range metricDef.Labels { - lv, exists := v[l] - if exists { - lmv.Labels[l] = getRenamedLabel(labelRenames, toString(lv)) - } - } + keyItems := strings.Split(metricDef.Key, ".") - metrics = append(metrics, lmv) - } - case []interface{}: - // since type is array there can't be any labels to differentiate values, so only one value supported ! - index, err := strconv.Atoi(metricDef.Key) +VALUE: + for _, pathVal := range values { + valUntyped := pathVal + path := metricDef.Path + + // now handle if key is also splitted + for _, key := range keyItems { + valUntyped, err = getValueFromHashOrArray(valUntyped, key, path) if err != nil { - return nil, fmt.Errorf("item '%s' is an array, but index '%s' is not a number", metricDef.Path, metricDef.Key) + // since we may have other values, we simply continue (should we report it?) + continue VALUE } - if index < 0 { - // this is an index from the end of the values - index += len(v) + if path != "" { + path += "." } + path += key + } - if index >= 0 && index < len(v) { - lmv := metricDef.createValue(name, toString(v[index])) - metrics = append(metrics, lmv) - } else { - return nil, fmt.Errorf("index %d is invalid for array '%s' with length %d", index, metricDef.Path, len(v)) + // create metric value + lmv := metricDef.createValue(path, toString(valUntyped)) + + // add labels if pathVal is a hash + valMap, isType := pathVal.(map[string]interface{}) + if isType { + for _, l := range metricDef.Labels { + lv, exists := valMap[l] + if exists { + lmv.Labels[l] = getRenamedLabel(labelRenames, toString(lv)) + } } - default: - return nil, fmt.Errorf("item '%s' is not a hash or array, can't get value %s", metricDef.Path, metricDef.Key) } + + metrics = append(metrics, lmv) + } + + if len(metrics) == 0 { + if err == nil { + // normal we should already have an error, this is just a fallback + err = fmt.Errorf("no value found for item '%s' with key '%s'", metricDef.Path, metricDef.Key) + } + return nil, err } return metrics, nil @@ -290,15 +324,16 @@ func utf16leMd5(s string) []byte { // helper for retrieving values from parsed JSON func _getValues(data interface{}, pathItems []string, parentPath string) ([]interface{}, error) { + var err error + values := make([]interface{}, 0) value := data curPath := parentPath for i, p := range pathItems { - switch vv := value.(type) { - case []interface{}: - if p == "*" { - - values := make([]interface{}, 0, len(vv)) + if p == "*" { + // handle * case to get all values + switch vv := value.(type) { + case []interface{}: for index, u := range vv { subvals, err := _getValues(u, pathItems[i+1:], fmt.Sprintf("%s.%d", curPath, index)) if err != nil { @@ -307,35 +342,26 @@ func _getValues(data interface{}, pathItems []string, parentPath string) ([]inte values = append(values, subvals...) } + case map[string]interface{}: + for subK, subV := range vv { + subvals, err := _getValues(subV, pathItems[i+1:], fmt.Sprintf("%s.%s", curPath, subK)) + if err != nil { + return nil, err + } - return values, nil - } else { - index, err := strconv.Atoi(p) - if err != nil { - return nil, fmt.Errorf("item '%s' is an array, but path item '%s' is neither '*' nor a number", curPath, p) - } - - if index < 0 { - // this is an index from the end of the values - index += len(vv) - } - - if index >= 0 && index < len(vv) { - value = vv[index] - } else { - return nil, fmt.Errorf("index %d is invalid for array '%s' with length %d", index, curPath, len(vv)) + values = append(values, subvals...) } + default: + return nil, fmt.Errorf("item '%s' is neither a hash or array", curPath) } - case map[string]interface{}: - var exits bool - value, exits = vv[p] - if !exits { - return nil, fmt.Errorf("key '%s' not existing in hash '%s'", p, curPath) - } + return values, nil + } - default: - return nil, fmt.Errorf("item '%s' is neither a hash or array", curPath) + // this is a single value + value, err = getValueFromHashOrArray(value, p, curPath) + if err != nil { + return nil, err } if curPath == "" { @@ -345,8 +371,8 @@ func _getValues(data interface{}, pathItems []string, parentPath string) ([]inte } } - values := make([]interface{}, 1) - values[0] = value + values = append(values, value) + return values, nil } diff --git a/main.go b/main.go index 96057ca..e789dd6 100644 --- a/main.go +++ b/main.go @@ -412,7 +412,9 @@ func testLuaCall() { var jsonData []byte var err error - page := lua.LuaPage{Path: "data.lua", Params: "page=ecoStat"} + //page := lua.LuaPage{Path: "data.lua", Params: "page=energy"} + //page := lua.LuaPage{Path: "data.lua", Params: "page=ecoStat"} + page := lua.LuaPage{Path: "data.lua", Params: "page=usbOv"} jsonData, err = luaSession.LoadData(page) if err != nil { @@ -434,23 +436,35 @@ func testLuaCall() { labelRenames = addLabelRename(labelRenames, "(?i)FON", "Phone") labelRenames = addLabelRename(labelRenames, "(?i)WLAN", "WLAN") labelRenames = addLabelRename(labelRenames, "(?i)USB", "USB") + labelRenames = addLabelRename(labelRenames, "(?i)Speicher", "Storage") pidMetric := lua.LuaMetricValueDefinition{Path: "", Key: "pid", Labels: nil} - // powerMetric := lua.LuaMetricValueDefinition{Path: "data.drain.*", Key: "actPerc", Labels: []string{"name"}} - tempMetric := lua.LuaMetricValueDefinition{Path: "data.cputemp.series.0", Key: "-1", Labels: nil} - loadMetric := lua.LuaMetricValueDefinition{Path: "data.cpuutil.series.0", Key: "-1", Labels: nil} - ramMetric1 := lua.LuaMetricValueDefinition{Path: "data.ramusage.series.0", Key: "-1", Labels: nil, FixedLabels: map[string]string{"ram_type": "Fixed"}} - ramMetric2 := lua.LuaMetricValueDefinition{Path: "data.ramusage.series.1", Key: "-1", Labels: nil, FixedLabels: map[string]string{"ram_type": "Dynamic"}} - ramMetric3 := lua.LuaMetricValueDefinition{Path: "data.ramusage.series.2", Key: "-1", Labels: nil, FixedLabels: map[string]string{"ram_type": "Free"}} - - // fmt.Println(fmt.Sprintf("DATA: %v", data)) dumpMetric(&labelRenames, data, pidMetric) - // dumpMetric(&labelRenames, data, powerMetric) + + powerMetric := lua.LuaMetricValueDefinition{Path: "data.drain.*", Key: "actPerc", Labels: []string{"name"}} + dumpMetric(&labelRenames, data, powerMetric) + + tempMetric := lua.LuaMetricValueDefinition{Path: "data.cputemp.series.0", Key: "-1", Labels: nil} dumpMetric(&labelRenames, data, tempMetric) + + loadMetric := lua.LuaMetricValueDefinition{Path: "data.cpuutil.series.0", Key: "-1", Labels: nil} dumpMetric(&labelRenames, data, loadMetric) + + ramMetric1 := lua.LuaMetricValueDefinition{Path: "data.ramusage.series.0", Key: "-1", Labels: nil, FixedLabels: map[string]string{"ram_type": "Fixed"}} dumpMetric(&labelRenames, data, ramMetric1) + + ramMetric2 := lua.LuaMetricValueDefinition{Path: "data.ramusage.series.1", Key: "-1", Labels: nil, FixedLabels: map[string]string{"ram_type": "Dynamic"}} dumpMetric(&labelRenames, data, ramMetric2) + + ramMetric3 := lua.LuaMetricValueDefinition{Path: "data.ramusage.series.2", Key: "-1", Labels: nil, FixedLabels: map[string]string{"ram_type": "Free"}} dumpMetric(&labelRenames, data, ramMetric3) + + usbMetric := lua.LuaMetricValueDefinition{Path: "data.usbOverview.devices.*", Key: "partitions.0.totalStorageInBytes", Labels: []string{"deviceType", "deviceName"}} + dumpMetric(&labelRenames, data, usbMetric) + + usbMetric2 := lua.LuaMetricValueDefinition{Path: "data.usbOverview.devices.*", Key: "partitions.0.usedStorageInBytes", Labels: []string{"deviceType", "deviceName"}} + dumpMetric(&labelRenames, data, usbMetric2) + } func dumpMetric(labelRenames *[]lua.LabelRename, data map[string]interface{}, metricDef lua.LuaMetricValueDefinition) {