-
Notifications
You must be signed in to change notification settings - Fork 41
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
11 changed files
with
597 additions
and
14 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,10 @@ | ||
package webserver | ||
|
||
import "github.com/FreifunkBremen/yanic/webserver/prometheus" | ||
|
||
type Config struct { | ||
Enable bool `toml:"enable"` | ||
Bind string `toml:"bind"` | ||
Webroot string `toml:"webroot"` | ||
Enable bool `toml:"enable"` | ||
Bind string `toml:"bind"` | ||
Webroot string `toml:"webroot"` | ||
Prometheus prometheus.Config `toml:"prometheus"` | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
package prometheus | ||
|
||
import ( | ||
"net/http" | ||
|
||
"github.com/FreifunkBremen/yanic/lib/duration" | ||
|
||
"github.com/FreifunkBremen/yanic/respond" | ||
"github.com/FreifunkBremen/yanic/runtime" | ||
) | ||
|
||
type Config struct { | ||
Enable bool `toml:"enable"` | ||
Wait duration.Duration `toml:"wait"` | ||
Outdated duration.Duration `toml:"outdated"` | ||
} | ||
|
||
func CreateExporter(config Config, srv *http.Server, coll *respond.Collector, nodes *runtime.Nodes) { | ||
mux := http.NewServeMux() | ||
ex := &Exporter{ | ||
config: config, | ||
srv: srv, | ||
coll: coll, | ||
nodes: nodes, | ||
} | ||
mux.Handle("/metric", ex) | ||
if srv.Handler != nil { | ||
mux.Handle("/", srv.Handler) | ||
} | ||
srv.Handler = mux | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
package prometheus | ||
|
||
import ( | ||
"io" | ||
"net" | ||
"net/http" | ||
"time" | ||
|
||
"github.com/bdlm/log" | ||
|
||
"github.com/FreifunkBremen/yanic/respond" | ||
"github.com/FreifunkBremen/yanic/runtime" | ||
) | ||
|
||
type Exporter struct { | ||
config Config | ||
srv *http.Server | ||
coll *respond.Collector | ||
nodes *runtime.Nodes | ||
} | ||
|
||
func (ex *Exporter) ServeHTTP(res http.ResponseWriter, req *http.Request) { | ||
var ip net.IP | ||
nodeID := "" | ||
|
||
queryValues := req.URL.Query() | ||
|
||
if nodeIDs := queryValues["node_id"]; len(nodeIDs) > 0 { | ||
nodeID = nodeIDs[0] | ||
node, ok := ex.nodes.List[nodeID] | ||
if !ok || node.Address == nil { | ||
http.Error(res, "not able to get node by cached nodeid", http.StatusNotFound) | ||
return | ||
} | ||
ip = node.Address.IP | ||
if ex.writeNode(res, node, true) { | ||
log.WithFields(map[string]interface{}{ | ||
"ip": ip, | ||
"node_id": nodeID, | ||
}).Debug("take node from cache") | ||
return | ||
} | ||
} else if ipstr := queryValues["ip"]; len(ipstr) > 0 { | ||
ip = net.ParseIP(ipstr[0]) | ||
if ip == nil { | ||
http.Error(res, "not able to parse ip address", http.StatusBadRequest) | ||
return | ||
} | ||
node_select := ex.nodes.Select(func(n *runtime.Node) bool { | ||
n_addr := n.Address | ||
nodeID = n.Nodeinfo.NodeID | ||
return n_addr != nil && ip.Equal(n_addr.IP) | ||
}) | ||
if len(node_select) == 1 { | ||
if ex.writeNode(res, node_select[0], true) { | ||
log.WithFields(map[string]interface{}{ | ||
"ip": ip, | ||
"node_id": nodeID, | ||
}).Debug("take node from cache") | ||
return | ||
} | ||
} else if len(node_select) > 1 { | ||
log.Error("strange count of nodes") | ||
} | ||
} else { | ||
http.Error(res, "please request with ?ip= or ?node_id=", http.StatusNotFound) | ||
return | ||
} | ||
|
||
// send request | ||
ex.coll.SendPacket(ip) | ||
|
||
// wait | ||
log.WithFields(map[string]interface{}{ | ||
"ip": ip, | ||
"node_id": nodeID, | ||
}).Debug("waited for") | ||
time.Sleep(ex.config.Wait.Duration) | ||
|
||
// result | ||
node, ok := ex.nodes.List[nodeID] | ||
if !ok { | ||
http.Error(res, "not able to fetch this node", http.StatusGatewayTimeout) | ||
return | ||
} | ||
ex.writeNode(res, node, false) | ||
} | ||
|
||
func (ex *Exporter) writeNode(res http.ResponseWriter, node *runtime.Node, dry bool) bool { | ||
logger := log.WithField("database", "prometheus") | ||
if nodeinfo := node.Nodeinfo; nodeinfo != nil { | ||
logger = logger.WithField("node_id", nodeinfo.NodeID) | ||
} | ||
|
||
if !time.Now().Before(node.Lastseen.GetTime().Add(ex.config.Outdated.Duration)) { | ||
if dry { | ||
return false | ||
} | ||
m := Metric{Labels: MetricLabelsFromNode(node), Name: "yanic_node_up", Value: 0} | ||
str, err := m.String() | ||
if err == nil { | ||
_, err = io.WriteString(res, str+"\n") | ||
} | ||
if err != nil { | ||
logger.Warnf("not able to get metrics from node: %s", err) | ||
http.Error(res, "not able to generate metric from node", http.StatusInternalServerError) | ||
} | ||
return false | ||
} | ||
|
||
metrics := MetricsFromNode(ex.nodes, node) | ||
for _, m := range metrics { | ||
str, err := m.String() | ||
if err == nil { | ||
_, err = io.WriteString(res, str+"\n") | ||
} | ||
if err != nil { | ||
logger.Warnf("not able to get metrics from node: %s", err) | ||
http.Error(res, "not able to generate metric from node", http.StatusInternalServerError) | ||
} | ||
} | ||
|
||
return true | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
package prometheus | ||
|
||
import ( | ||
"errors" | ||
"fmt" | ||
"strings" | ||
) | ||
|
||
type Metric struct { | ||
Name string | ||
Value interface{} | ||
Labels map[string]interface{} | ||
} | ||
|
||
func (m *Metric) String() (string, error) { | ||
if m.Value == nil { | ||
return "", errors.New("no value of metric found") | ||
} | ||
output := m.Name | ||
if len(m.Labels) > 0 { | ||
output += "{" | ||
for label, v := range m.Labels { | ||
switch value := v.(type) { | ||
case string: | ||
output = fmt.Sprintf("%s%s=\"%s\",", output, label, strings.ReplaceAll(value, "\"", "'")) | ||
case float32: | ||
output = fmt.Sprintf("%s%s=\"%.4f\",", output, label, value) | ||
case float64: | ||
output = fmt.Sprintf("%s%s=\"%.4f\",", output, label, value) | ||
default: | ||
output = fmt.Sprintf("%s%s=\"%v\",", output, label, value) | ||
} | ||
} | ||
lastChar := len(output) - 1 | ||
output = output[:lastChar] + "}" | ||
} | ||
return fmt.Sprintf("%s %v", output, m.Value), nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
package prometheus | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
func TestMetric(t *testing.T) { | ||
assert := assert.New(t) | ||
|
||
var tests = []struct { | ||
input Metric | ||
err string | ||
output []string | ||
}{ | ||
{ | ||
input: Metric{Name: "test1"}, | ||
err: "no value of metric found", | ||
}, | ||
{ | ||
input: Metric{Name: "test2", Value: 3}, | ||
output: []string{"test2 3"}, | ||
}, | ||
{ | ||
input: Metric{Name: "test2-obj", Value: 1, | ||
Labels: map[string]interface{}{ | ||
"test": []string{"4"}, | ||
}, | ||
}, | ||
output: []string{`test2-obj{test="[4]"} 1`}, | ||
}, | ||
{ | ||
input: Metric{Name: "test3", Value: 3.2, | ||
Labels: map[string]interface{}{ | ||
"site_code": "lola", | ||
}, | ||
}, | ||
output: []string{`test3{site_code="lola"} 3.2`}, | ||
}, | ||
{ | ||
input: Metric{Name: "test4", Value: "0", | ||
Labels: map[string]interface{}{ | ||
"frequency": float32(3.2), | ||
}, | ||
}, | ||
output: []string{`test4{frequency="3.2000"} 0`}, | ||
}, | ||
{ | ||
input: Metric{Name: "test5", Value: 3, | ||
Labels: map[string]interface{}{ | ||
"node_id": "lola", | ||
"blub": 3.3423533, | ||
}, | ||
}, | ||
output: []string{ | ||
`test5{blub="3.3424",node_id="lola"} 3`, | ||
`test5{node_id="lola",blub="3.3424"} 3`, | ||
}, | ||
}, | ||
} | ||
|
||
for _, test := range tests { | ||
output, err := test.input.String() | ||
|
||
if test.err == "" { | ||
assert.NoError(err) | ||
assert.Contains(test.output, output, "not acceptable output found") | ||
} else { | ||
assert.EqualError(err, test.err) | ||
} | ||
} | ||
} |
Oops, something went wrong.