mirror of
https://github.com/openziti/zrok.git
synced 2024-11-24 09:03:23 +01:00
alternate metrics model with sample objects (#319)
This commit is contained in:
parent
6b078abcd7
commit
9f29bb59c7
@ -42,7 +42,7 @@ func (h *getAccountMetricsHandler) Handle(params metadata.GetAccountMetricsParam
|
||||
slice := duration / 200
|
||||
|
||||
query := fmt.Sprintf("from(bucket: \"%v\")\n", h.cfg.Bucket) +
|
||||
fmt.Sprintf("|> range(start -%v)\n", duration) +
|
||||
fmt.Sprintf("|> range(start: -%v)\n", duration) +
|
||||
"|> filter(fn: (r) => r[\"_measurement\"] == \"xfer\")\n" +
|
||||
"|> filter(fn: (r) => r[\"_field\"] == \"rx\" or r[\"_field\"] == \"tx\")\n" +
|
||||
"|> filter(fn: (r) => r[\"namespace\"] == \"backend\")\n" +
|
||||
@ -50,35 +50,42 @@ func (h *getAccountMetricsHandler) Handle(params metadata.GetAccountMetricsParam
|
||||
"|> drop(columns: [\"share\", \"envId\"])\n" +
|
||||
fmt.Sprintf("|> aggregateWindow(every: %v, fn: sum, createEmpty: true)", slice)
|
||||
|
||||
rx, tx, err := runFluxForRxTxArray(query, h.queryApi)
|
||||
rx, tx, timestamps, err := runFluxForRxTxArray(query, h.queryApi)
|
||||
if err != nil {
|
||||
logrus.Errorf("error running account metrics query for '%v': %v", principal.Email, err)
|
||||
return metadata.NewGetAccountMetricsInternalServerError()
|
||||
}
|
||||
|
||||
response := &rest_model_zrok.Metrics{
|
||||
Scope: "account",
|
||||
ID: fmt.Sprintf("%d", principal.ID),
|
||||
Period: duration.Seconds(),
|
||||
Rx: rx,
|
||||
Tx: tx,
|
||||
}
|
||||
for i := 0; i < len(rx) && i < len(tx) && i < len(timestamps); i++ {
|
||||
response.Samples = append(response.Samples, &rest_model_zrok.MetricsSample{
|
||||
Rx: rx[i],
|
||||
Tx: tx[i],
|
||||
Timestamp: timestamps[i],
|
||||
})
|
||||
}
|
||||
return metadata.NewGetAccountMetricsOK().WithPayload(response)
|
||||
}
|
||||
|
||||
func runFluxForRxTxArray(query string, queryApi api.QueryAPI) (rx, tx []float64, err error) {
|
||||
func runFluxForRxTxArray(query string, queryApi api.QueryAPI) (rx, tx, timestamps []float64, err error) {
|
||||
result, err := queryApi.Query(context.Background(), query)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
for result.Next() {
|
||||
if v, ok := result.Record().Value().(int64); ok {
|
||||
switch result.Record().Field() {
|
||||
case "rx":
|
||||
rx = append(rx, float64(v))
|
||||
timestamps = append(timestamps, float64(result.Record().Time().UnixMilli()))
|
||||
case "tx":
|
||||
tx = append(tx, float64(v))
|
||||
}
|
||||
}
|
||||
}
|
||||
return rx, tx, nil
|
||||
return rx, tx, timestamps, nil
|
||||
}
|
||||
|
@ -7,7 +7,9 @@ package rest_model_zrok
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
|
||||
"github.com/go-openapi/errors"
|
||||
"github.com/go-openapi/strfmt"
|
||||
"github.com/go-openapi/swag"
|
||||
)
|
||||
@ -23,23 +25,84 @@ type Metrics struct {
|
||||
// period
|
||||
Period float64 `json:"period,omitempty"`
|
||||
|
||||
// rx
|
||||
Rx []float64 `json:"rx"`
|
||||
// samples
|
||||
Samples []*MetricsSample `json:"samples"`
|
||||
|
||||
// scope
|
||||
Scope string `json:"scope,omitempty"`
|
||||
|
||||
// tx
|
||||
Tx []float64 `json:"tx"`
|
||||
}
|
||||
|
||||
// Validate validates this metrics
|
||||
func (m *Metrics) Validate(formats strfmt.Registry) error {
|
||||
var res []error
|
||||
|
||||
if err := m.validateSamples(formats); err != nil {
|
||||
res = append(res, err)
|
||||
}
|
||||
|
||||
if len(res) > 0 {
|
||||
return errors.CompositeValidationError(res...)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ContextValidate validates this metrics based on context it is used
|
||||
func (m *Metrics) validateSamples(formats strfmt.Registry) error {
|
||||
if swag.IsZero(m.Samples) { // not required
|
||||
return nil
|
||||
}
|
||||
|
||||
for i := 0; i < len(m.Samples); i++ {
|
||||
if swag.IsZero(m.Samples[i]) { // not required
|
||||
continue
|
||||
}
|
||||
|
||||
if m.Samples[i] != nil {
|
||||
if err := m.Samples[i].Validate(formats); err != nil {
|
||||
if ve, ok := err.(*errors.Validation); ok {
|
||||
return ve.ValidateName("samples" + "." + strconv.Itoa(i))
|
||||
} else if ce, ok := err.(*errors.CompositeError); ok {
|
||||
return ce.ValidateName("samples" + "." + strconv.Itoa(i))
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ContextValidate validate this metrics based on the context it is used
|
||||
func (m *Metrics) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
|
||||
var res []error
|
||||
|
||||
if err := m.contextValidateSamples(ctx, formats); err != nil {
|
||||
res = append(res, err)
|
||||
}
|
||||
|
||||
if len(res) > 0 {
|
||||
return errors.CompositeValidationError(res...)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Metrics) contextValidateSamples(ctx context.Context, formats strfmt.Registry) error {
|
||||
|
||||
for i := 0; i < len(m.Samples); i++ {
|
||||
|
||||
if m.Samples[i] != nil {
|
||||
if err := m.Samples[i].ContextValidate(ctx, formats); err != nil {
|
||||
if ve, ok := err.(*errors.Validation); ok {
|
||||
return ve.ValidateName("samples" + "." + strconv.Itoa(i))
|
||||
} else if ce, ok := err.(*errors.CompositeError); ok {
|
||||
return ce.ValidateName("samples" + "." + strconv.Itoa(i))
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
56
rest_model_zrok/metrics_sample.go
Normal file
56
rest_model_zrok/metrics_sample.go
Normal file
@ -0,0 +1,56 @@
|
||||
// Code generated by go-swagger; DO NOT EDIT.
|
||||
|
||||
package rest_model_zrok
|
||||
|
||||
// This file was generated by the swagger tool.
|
||||
// Editing this file might prove futile when you re-run the swagger generate command
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/go-openapi/strfmt"
|
||||
"github.com/go-openapi/swag"
|
||||
)
|
||||
|
||||
// MetricsSample metrics sample
|
||||
//
|
||||
// swagger:model metricsSample
|
||||
type MetricsSample struct {
|
||||
|
||||
// rx
|
||||
Rx float64 `json:"rx,omitempty"`
|
||||
|
||||
// timestamp
|
||||
Timestamp float64 `json:"timestamp,omitempty"`
|
||||
|
||||
// tx
|
||||
Tx float64 `json:"tx,omitempty"`
|
||||
}
|
||||
|
||||
// Validate validates this metrics sample
|
||||
func (m *MetricsSample) Validate(formats strfmt.Registry) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ContextValidate validates this metrics sample based on context it is used
|
||||
func (m *MetricsSample) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalBinary interface implementation
|
||||
func (m *MetricsSample) MarshalBinary() ([]byte, error) {
|
||||
if m == nil {
|
||||
return nil, nil
|
||||
}
|
||||
return swag.WriteJSON(m)
|
||||
}
|
||||
|
||||
// UnmarshalBinary interface implementation
|
||||
func (m *MetricsSample) UnmarshalBinary(b []byte) error {
|
||||
var res MetricsSample
|
||||
if err := swag.ReadJSON(b, &res); err != nil {
|
||||
return err
|
||||
}
|
||||
*m = res
|
||||
return nil
|
||||
}
|
@ -1147,20 +1147,28 @@ func init() {
|
||||
"period": {
|
||||
"type": "number"
|
||||
},
|
||||
"rx": {
|
||||
"samples": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "number"
|
||||
"$ref": "#/definitions/metricsSample"
|
||||
}
|
||||
},
|
||||
"scope": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"metricsSample": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"rx": {
|
||||
"type": "number"
|
||||
},
|
||||
"timestamp": {
|
||||
"type": "number"
|
||||
},
|
||||
"tx": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "number"
|
||||
}
|
||||
"type": "number"
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -2563,20 +2571,28 @@ func init() {
|
||||
"period": {
|
||||
"type": "number"
|
||||
},
|
||||
"rx": {
|
||||
"samples": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "number"
|
||||
"$ref": "#/definitions/metricsSample"
|
||||
}
|
||||
},
|
||||
"scope": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"metricsSample": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"rx": {
|
||||
"type": "number"
|
||||
},
|
||||
"timestamp": {
|
||||
"type": "number"
|
||||
},
|
||||
"tx": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "number"
|
||||
}
|
||||
"type": "number"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -743,14 +743,20 @@ definitions:
|
||||
type: string
|
||||
period:
|
||||
type: number
|
||||
samples:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/definitions/metricsSample"
|
||||
|
||||
metricsSample:
|
||||
type: object
|
||||
properties:
|
||||
rx:
|
||||
type: array
|
||||
items:
|
||||
type: number
|
||||
type: number
|
||||
tx:
|
||||
type: array
|
||||
items:
|
||||
type: number
|
||||
type: number
|
||||
timestamp:
|
||||
type: number
|
||||
|
||||
principal:
|
||||
type: object
|
||||
|
@ -130,8 +130,16 @@
|
||||
* @property {string} scope
|
||||
* @property {string} id
|
||||
* @property {number} period
|
||||
* @property {number[]} rx
|
||||
* @property {number[]} tx
|
||||
* @property {module:types.metricsSample[]} samples
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef metricsSample
|
||||
* @memberof module:types
|
||||
*
|
||||
* @property {number} rx
|
||||
* @property {number} tx
|
||||
* @property {number} timestamp
|
||||
*/
|
||||
|
||||
/**
|
||||
|
@ -1,8 +1,11 @@
|
||||
import {mdiAccountBox} from "@mdi/js";
|
||||
import Icon from "@mdi/react";
|
||||
import PropertyTable from "../../PropertyTable";
|
||||
import {Tab, Tabs} from "react-bootstrap";
|
||||
import {Tab, Tabs, Tooltip} from "react-bootstrap";
|
||||
import SecretToggle from "../../SecretToggle";
|
||||
import React, {useEffect, useState} from "react";
|
||||
import * as metadata from "../../../api/metadata";
|
||||
import {Area, AreaChart, CartesianGrid, Line, LineChart, ResponsiveContainer, XAxis, YAxis} from "recharts";
|
||||
|
||||
const AccountDetail = (props) => {
|
||||
const customProperties = {
|
||||
@ -16,9 +19,93 @@ const AccountDetail = (props) => {
|
||||
<Tab eventKey={"detail"} title={"Detail"}>
|
||||
<PropertyTable object={props.user} custom={customProperties}/>
|
||||
</Tab>
|
||||
<Tab eventKey={"metrics"} title={"Metrics"}>
|
||||
<MetricsTab />
|
||||
</Tab>
|
||||
</Tabs>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const MetricsTab = (props) => {
|
||||
const [metrics, setMetrics] = useState({});
|
||||
const [tx, setTx] = useState(0);
|
||||
const [rx, setRx] = useState(0)
|
||||
|
||||
useEffect(() => {
|
||||
metadata.getAccountMetrics()
|
||||
.then(resp => {
|
||||
setMetrics(resp.data);
|
||||
});
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
let mounted = true;
|
||||
let interval = setInterval(() => {
|
||||
metadata.getAccountMetrics()
|
||||
.then(resp => {
|
||||
if(mounted) {
|
||||
setMetrics(resp.data);
|
||||
}
|
||||
});
|
||||
}, 1000);
|
||||
return () => {
|
||||
mounted = false;
|
||||
clearInterval(interval);
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
let txAccum = 0
|
||||
let rxAccum = 0
|
||||
if(metrics.samples) {
|
||||
metrics.samples.forEach(sample => {
|
||||
txAccum += sample.tx
|
||||
rxAccum += sample.rx
|
||||
})
|
||||
}
|
||||
setTx(txAccum);
|
||||
setRx(rxAccum);
|
||||
}, [metrics])
|
||||
|
||||
console.log(metrics);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div>
|
||||
<h1>RX: {bytesToSize(rx)}, TX: {bytesToSize(tx)}</h1>
|
||||
</div>
|
||||
<ResponsiveContainer width={"100%"} height={300}>
|
||||
<LineChart data={metrics.samples}>
|
||||
<CartesianGrid strokeDasharay={"3 3"} />
|
||||
<XAxis dataKey={(v) => new Date(v.timestamp)} />
|
||||
<YAxis />
|
||||
<Line type={"linear"} stroke={"red"} dataKey={"rx"} activeDot={{ r: 8 }}/>
|
||||
<Line type={"linear"} stroke={"green"} dataKey={"tx"} />
|
||||
<Tooltip />
|
||||
</LineChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const bytesToSize = (sz) => {
|
||||
let absSz = sz;
|
||||
if(absSz < 0) {
|
||||
absSz *= -1;
|
||||
}
|
||||
const unit = 1000
|
||||
if(absSz < unit) {
|
||||
return '' + absSz + ' B';
|
||||
}
|
||||
let div = unit
|
||||
let exp = 0
|
||||
for(let n = absSz / unit; n >= unit; n /= unit) {
|
||||
div *= unit;
|
||||
exp++;
|
||||
}
|
||||
|
||||
return '' + (sz / div).toFixed(2) + "kMGTPE"[exp];
|
||||
}
|
||||
|
||||
export default AccountDetail;
|
Loading…
Reference in New Issue
Block a user