Browse Source

support for values from ecoStat page

pull/4/head
sberk42 4 years ago
parent
commit
bc6f8cd35c
  1. 146
      fritzbox_lua/lua_client.go
  2. 30
      main.go

146
fritzbox_lua/lua_client.go

@ -1,4 +1,4 @@
// client for fritzbox lua API // Package lua_client implementes client for fritzbox lua UI API
package lua_client package lua_client
// Copyright 2020 Andreas Krebs // Copyright 2020 Andreas Krebs
@ -32,7 +32,7 @@ import (
"golang.org/x/text/transform" "golang.org/x/text/transform"
) )
// session XML from login_sid.lua // SessionInfo XML from login_sid.lua
type SessionInfo struct { type SessionInfo struct {
SID string `xml:"SID"` SID string `xml:"SID"`
Challenge string `xml:"Challenge"` Challenge string `xml:"Challenge"`
@ -40,45 +40,51 @@ type SessionInfo struct {
Rights string `xml:"Rights"` Rights string `xml:"Rights"`
} }
// LuaSession for storing connection data and SID
type LuaSession struct { type LuaSession struct {
BaseUrl string BaseURL string
Username string Username string
Password string Password string
SID string SID string
SessionInfo SessionInfo SessionInfo SessionInfo
} }
// LuaPage identified by path and params
type LuaPage struct { type LuaPage struct {
Path string Path string
Params string Params string
} }
// LuaMetricValueDefinition definition for a single metric
type LuaMetricValueDefinition struct { type LuaMetricValueDefinition struct {
Path string Path string
Key string Key string
Labels []string Labels []string
FixedLabels map[string]string
} }
// LuaMetricValue single value retrieved from lua page
type LuaMetricValue struct { type LuaMetricValue struct {
Name string Name string
Value string Value string
Labels map[string]string Labels map[string]string
} }
// LabelRename regex to replace labels to get rid of translations
type LabelRename struct { type LabelRename struct {
Pattern regexp.Regexp Pattern regexp.Regexp
Name string Name string
} }
func (lua *LuaSession) do_Login(response string) error { func (lua *LuaSession) doLogin(response string) error {
url_params := "" urlParams := ""
if response != "" { if response != "" {
url_params = fmt.Sprintf("?response=%s&user=%s", response, lua.Username) urlParams = fmt.Sprintf("?response=%s&user=%s", response, lua.Username)
} }
resp, err := http.Get(fmt.Sprintf("%s/login_sid.lua%s", lua.BaseUrl, url_params)) resp, err := http.Get(fmt.Sprintf("%s/login_sid.lua%s", lua.BaseURL, urlParams))
if err != nil { if err != nil {
return errors.New(fmt.Sprintf("Error calling login_sid.lua: %s", err.Error())) return fmt.Errorf("Error calling login_sid.lua: %s", err.Error())
} }
defer resp.Body.Close() defer resp.Body.Close()
@ -86,15 +92,30 @@ func (lua *LuaSession) do_Login(response string) error {
err = dec.Decode(&lua.SessionInfo) err = dec.Decode(&lua.SessionInfo)
if err != nil { if err != nil {
return errors.New(fmt.Sprintf("Error decoding SessionInfo: %s", err.Error())) return fmt.Errorf("Error decoding SessionInfo: %s", err.Error())
} }
return nil return nil
} }
func (lmvDef *LuaMetricValueDefinition) createValue(name string, value string) LuaMetricValue {
lmv := LuaMetricValue{
Name: name,
Value: value,
Labels: make(map[string]string),
}
for l := range lmvDef.FixedLabels {
lmv.Labels[l] = lmvDef.FixedLabels[l]
}
return lmv
}
// Login perform loing and get SID
func (lua *LuaSession) Login() error { func (lua *LuaSession) Login() error {
err := lua.do_Login("") err := lua.doLogin("")
if err != nil { if err != nil {
return err return err
} }
@ -104,7 +125,7 @@ func (lua *LuaSession) Login() error {
// no SID, but challenge so calc response // no SID, but challenge so calc response
hash := utf16leMd5(fmt.Sprintf("%s-%s", challenge, lua.Password)) hash := utf16leMd5(fmt.Sprintf("%s-%s", challenge, lua.Password))
response := fmt.Sprintf("%s-%x", challenge, hash) response := fmt.Sprintf("%s-%x", challenge, hash)
err := lua.do_Login(response) err := lua.doLogin(response)
if err != nil { if err != nil {
return err return err
@ -121,8 +142,9 @@ func (lua *LuaSession) Login() error {
return nil return nil
} }
// LoadData load a lua bage and return content
func (lua *LuaSession) LoadData(page LuaPage) ([]byte, error) { func (lua *LuaSession) LoadData(page LuaPage) ([]byte, error) {
data_url := fmt.Sprintf("%s/%s", lua.BaseUrl, page.Path) dataURL := fmt.Sprintf("%s/%s", lua.BaseURL, page.Path)
callDone := false callDone := false
var resp *http.Response var resp *http.Response
@ -145,7 +167,7 @@ func (lua *LuaSession) LoadData(page LuaPage) ([]byte, error) {
params += "&" + page.Params params += "&" + page.Params
} }
resp, err = http.Post(data_url, "application/x-www-form-urlencoded", bytes.NewBuffer([]byte(params))) resp, err = http.Post(dataURL, "application/x-www-form-urlencoded", bytes.NewBuffer([]byte(params)))
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -156,7 +178,7 @@ func (lua *LuaSession) LoadData(page LuaPage) ([]byte, error) {
} else if resp.StatusCode == http.StatusForbidden && !callDone { } else if resp.StatusCode == http.StatusForbidden && !callDone {
// we assume SID is expired, so retry login // we assume SID is expired, so retry login
} else { } else {
return nil, errors.New(fmt.Sprintf("data.lua failed: %s", resp.Status)) return nil, fmt.Errorf("data.lua failed: %s", resp.Status)
} }
} }
@ -169,6 +191,16 @@ func (lua *LuaSession) LoadData(page LuaPage) ([]byte, error) {
return body, nil return body, nil
} }
// ParseJSON generic parser for unmarshalling into map
func ParseJSON(jsonData []byte) (map[string]interface{}, error) {
var data map[string]interface{}
// Unmarshal or Decode the JSON to the interface.
json.Unmarshal(jsonData, &data)
return data, nil
}
func getRenamedLabel(labelRenames *[]LabelRename, label string) string { func getRenamedLabel(labelRenames *[]LabelRename, label string) string {
if labelRenames != nil { if labelRenames != nil {
for _, lblRen := range *labelRenames { for _, lblRen := range *labelRenames {
@ -181,9 +213,10 @@ func getRenamedLabel(labelRenames *[]LabelRename, label string) string {
return label return label
} }
// GetMetrics get metrics from parsed lua page for definition and rename labels
func GetMetrics(labelRenames *[]LabelRename, data map[string]interface{}, metricDef LuaMetricValueDefinition) ([]LuaMetricValue, error) { func GetMetrics(labelRenames *[]LabelRename, data map[string]interface{}, metricDef LuaMetricValueDefinition) ([]LuaMetricValue, error) {
var values []map[string]interface{} var values []interface{}
var err error var err error
if metricDef.Path != "" { if metricDef.Path != "" {
pathItems := strings.Split(metricDef.Path, ".") pathItems := strings.Split(metricDef.Path, ".")
@ -192,7 +225,7 @@ func GetMetrics(labelRenames *[]LabelRename, data map[string]interface{}, metric
return nil, err return nil, err
} }
} else { } else {
values = make([]map[string]interface{}, 1) values = make([]interface{}, 1)
values[0] = data values[0] = data
} }
@ -203,23 +236,42 @@ func GetMetrics(labelRenames *[]LabelRename, data map[string]interface{}, metric
name += metricDef.Key name += metricDef.Key
metrics := make([]LuaMetricValue, 0) metrics := make([]LuaMetricValue, 0)
for _, valMap := range values { for _, valUntyped := range values {
value, exists := valMap[metricDef.Key] switch v := valUntyped.(type) {
if exists { case map[string]interface{}:
lmv := LuaMetricValue{ value, exists := v[metricDef.Key]
Name: name, if exists {
Value: toString(value), lmv := metricDef.createValue(name, toString(value))
Labels: make(map[string]string),
for _, l := range metricDef.Labels {
lv, exists := v[l]
if exists {
lmv.Labels[l] = getRenamedLabel(labelRenames, toString(lv))
}
}
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)
if err != nil {
return nil, fmt.Errorf("item '%s' is an array, but index '%s' is not a number", metricDef.Path, metricDef.Key)
} }
for _, l := range metricDef.Labels { if index < 0 {
lv, exists := valMap[l] // this is an index from the end of the values
if exists { index += len(v)
lmv.Labels[l] = getRenamedLabel(labelRenames, toString(lv))
}
} }
metrics = append(metrics, lmv) 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))
}
default:
return nil, fmt.Errorf("item '%s' is not a hash or array, can't get value %s", metricDef.Path, metricDef.Key)
} }
} }
@ -235,17 +287,8 @@ func utf16leMd5(s string) []byte {
return hasher.Sum(nil) return hasher.Sum(nil)
} }
func ParseJSON(jsonData []byte) (map[string]interface{}, error) {
var data map[string]interface{}
// Unmarshal or Decode the JSON to the interface.
json.Unmarshal(jsonData, &data)
return data, nil
}
// helper for retrieving values from parsed JSON // helper for retrieving values from parsed JSON
func _getValues(data interface{}, pathItems []string, parentPath string) ([]map[string]interface{}, error) { func _getValues(data interface{}, pathItems []string, parentPath string) ([]interface{}, error) {
value := data value := data
curPath := parentPath curPath := parentPath
@ -255,7 +298,7 @@ func _getValues(data interface{}, pathItems []string, parentPath string) ([]map[
case []interface{}: case []interface{}:
if p == "*" { if p == "*" {
values := make([]map[string]interface{}, 0, len(vv)) values := make([]interface{}, 0, len(vv))
for index, u := range vv { for index, u := range vv {
subvals, err := _getValues(u, pathItems[i+1:], fmt.Sprintf("%s.%d", curPath, index)) subvals, err := _getValues(u, pathItems[i+1:], fmt.Sprintf("%s.%d", curPath, index))
if err != nil { if err != nil {
@ -269,7 +312,7 @@ func _getValues(data interface{}, pathItems []string, parentPath string) ([]map[
} else { } else {
index, err := strconv.Atoi(p) index, err := strconv.Atoi(p)
if err != nil { if err != nil {
return nil, errors.New(fmt.Sprintf("item '%s' is an array, but path item '%s' is neither '*' nor a number", curPath, p)) return nil, fmt.Errorf("item '%s' is an array, but path item '%s' is neither '*' nor a number", curPath, p)
} }
if index < 0 { if index < 0 {
@ -280,7 +323,7 @@ func _getValues(data interface{}, pathItems []string, parentPath string) ([]map[
if index >= 0 && index < len(vv) { if index >= 0 && index < len(vv) {
value = vv[index] value = vv[index]
} else { } else {
return nil, errors.New(fmt.Sprintf("index %d is invalid for array '%s' with length %d", index, curPath, len(vv))) return nil, fmt.Errorf("index %d is invalid for array '%s' with length %d", index, curPath, len(vv))
} }
} }
@ -288,11 +331,11 @@ func _getValues(data interface{}, pathItems []string, parentPath string) ([]map[
var exits bool var exits bool
value, exits = vv[p] value, exits = vv[p]
if !exits { if !exits {
return nil, errors.New(fmt.Sprintf("key '%s' not existing in hash '%s'", p, curPath)) return nil, fmt.Errorf("key '%s' not existing in hash '%s'", p, curPath)
} }
default: default:
return nil, errors.New(fmt.Sprintf("item '%s' is neither a hash or array", curPath)) return nil, fmt.Errorf("item '%s' is neither a hash or array", curPath)
} }
if curPath == "" { if curPath == "" {
@ -302,13 +345,8 @@ func _getValues(data interface{}, pathItems []string, parentPath string) ([]map[
} }
} }
vm, isType := value.(map[string]interface{}) values := make([]interface{}, 1)
if !isType { values[0] = value
return nil, errors.New(fmt.Sprintf("item '%s' is not a hash", curPath))
}
values := make([]map[string]interface{}, 1)
values[0] = vm
return values, nil return values, nil
} }

30
main.go

@ -277,7 +277,7 @@ func (fc *FritzboxCollector) Collect(ch chan<- prometheus.Metric) {
var ok bool var ok bool
value, ok = provRes[aa.Value] // Value contains the result name for provider actions value, ok = provRes[aa.Value] // Value contains the result name for provider actions
if !ok { if !ok {
fmt.Printf("provider action %s for %s.%s has no result %s", m.Service, m.Action, aa.Value) fmt.Printf("provider action %s for %s.%s has no result", m.Service, m.Action, aa.Value)
collect_errors.Inc() collect_errors.Inc()
continue continue
} }
@ -334,7 +334,7 @@ func test() {
json.WriteString("[\n") json.WriteString("[\n")
serviceKeys := []string{} serviceKeys := []string{}
for k, _ := range root.Services { for k := range root.Services {
serviceKeys = append(serviceKeys, k) serviceKeys = append(serviceKeys, k)
} }
sort.Strings(serviceKeys) sort.Strings(serviceKeys)
@ -343,7 +343,7 @@ func test() {
fmt.Printf("Service: %s (Url: %s)\n", k, s.ControlUrl) fmt.Printf("Service: %s (Url: %s)\n", k, s.ControlUrl)
actionKeys := []string{} actionKeys := []string{}
for l, _ := range s.Actions { for l := range s.Actions {
actionKeys = append(actionKeys, l) actionKeys = append(actionKeys, l)
} }
sort.Strings(actionKeys) sort.Strings(actionKeys)
@ -405,15 +405,14 @@ func test() {
func testLuaCall() { func testLuaCall() {
var luaSession lua.LuaSession var luaSession lua.LuaSession
luaSession.BaseUrl = *flag_gateway_luaurl luaSession.BaseURL = *flag_gateway_luaurl
luaSession.Username = *flag_gateway_username luaSession.Username = *flag_gateway_username
luaSession.Password = *flag_gateway_password luaSession.Password = *flag_gateway_password
var jsonData []byte var jsonData []byte
var err error var err error
fmt.Println("calling login") page := lua.LuaPage{Path: "data.lua", Params: "page=ecoStat"}
page := lua.LuaPage{"data.lua", "page=energy"}
jsonData, err = luaSession.LoadData(page) jsonData, err = luaSession.LoadData(page)
if err != nil { if err != nil {
@ -436,11 +435,22 @@ func testLuaCall() {
labelRenames = addLabelRename(labelRenames, "(?i)WLAN", "WLAN") labelRenames = addLabelRename(labelRenames, "(?i)WLAN", "WLAN")
labelRenames = addLabelRename(labelRenames, "(?i)USB", "USB") labelRenames = addLabelRename(labelRenames, "(?i)USB", "USB")
pidMetric := lua.LuaMetricValueDefinition{"", "pid", nil} pidMetric := lua.LuaMetricValueDefinition{Path: "", Key: "pid", Labels: nil}
powerMetric := lua.LuaMetricValueDefinition{"data.drain.*", "actPerc", []string{"name"}} // 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)) // fmt.Println(fmt.Sprintf("DATA: %v", data))
dumpMetric(&labelRenames, data, pidMetric) dumpMetric(&labelRenames, data, pidMetric)
dumpMetric(&labelRenames, data, powerMetric) // dumpMetric(&labelRenames, data, powerMetric)
dumpMetric(&labelRenames, data, tempMetric)
dumpMetric(&labelRenames, data, loadMetric)
dumpMetric(&labelRenames, data, ramMetric1)
dumpMetric(&labelRenames, data, ramMetric2)
dumpMetric(&labelRenames, data, ramMetric3)
} }
func dumpMetric(labelRenames *[]lua.LabelRename, data map[string]interface{}, metricDef lua.LuaMetricValueDefinition) { func dumpMetric(labelRenames *[]lua.LabelRename, data map[string]interface{}, metricDef lua.LuaMetricValueDefinition) {
@ -459,7 +469,7 @@ func addLabelRename(labelRenames []lua.LabelRename, pattern string, name string)
regex, err := regexp.Compile(pattern) regex, err := regexp.Compile(pattern)
if err == nil { if err == nil {
return append(labelRenames, lua.LabelRename{*regex, name}) return append(labelRenames, lua.LabelRename{Pattern: *regex, Name: name})
} }
return labelRenames return labelRenames

Loading…
Cancel
Save