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
|
slice := duration / 200
|
||||||
|
|
||||||
query := fmt.Sprintf("from(bucket: \"%v\")\n", h.cfg.Bucket) +
|
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[\"_measurement\"] == \"xfer\")\n" +
|
||||||
"|> filter(fn: (r) => r[\"_field\"] == \"rx\" or r[\"_field\"] == \"tx\")\n" +
|
"|> filter(fn: (r) => r[\"_field\"] == \"rx\" or r[\"_field\"] == \"tx\")\n" +
|
||||||
"|> filter(fn: (r) => r[\"namespace\"] == \"backend\")\n" +
|
"|> filter(fn: (r) => r[\"namespace\"] == \"backend\")\n" +
|
||||||
@ -50,35 +50,42 @@ func (h *getAccountMetricsHandler) Handle(params metadata.GetAccountMetricsParam
|
|||||||
"|> drop(columns: [\"share\", \"envId\"])\n" +
|
"|> drop(columns: [\"share\", \"envId\"])\n" +
|
||||||
fmt.Sprintf("|> aggregateWindow(every: %v, fn: sum, createEmpty: true)", slice)
|
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 {
|
if err != nil {
|
||||||
logrus.Errorf("error running account metrics query for '%v': %v", principal.Email, err)
|
logrus.Errorf("error running account metrics query for '%v': %v", principal.Email, err)
|
||||||
return metadata.NewGetAccountMetricsInternalServerError()
|
return metadata.NewGetAccountMetricsInternalServerError()
|
||||||
}
|
}
|
||||||
|
|
||||||
response := &rest_model_zrok.Metrics{
|
response := &rest_model_zrok.Metrics{
|
||||||
|
Scope: "account",
|
||||||
ID: fmt.Sprintf("%d", principal.ID),
|
ID: fmt.Sprintf("%d", principal.ID),
|
||||||
Period: duration.Seconds(),
|
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)
|
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)
|
result, err := queryApi.Query(context.Background(), query)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, nil, err
|
||||||
}
|
}
|
||||||
for result.Next() {
|
for result.Next() {
|
||||||
if v, ok := result.Record().Value().(int64); ok {
|
if v, ok := result.Record().Value().(int64); ok {
|
||||||
switch result.Record().Field() {
|
switch result.Record().Field() {
|
||||||
case "rx":
|
case "rx":
|
||||||
rx = append(rx, float64(v))
|
rx = append(rx, float64(v))
|
||||||
|
timestamps = append(timestamps, float64(result.Record().Time().UnixMilli()))
|
||||||
case "tx":
|
case "tx":
|
||||||
tx = append(tx, float64(v))
|
tx = append(tx, float64(v))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return rx, tx, nil
|
return rx, tx, timestamps, nil
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,9 @@ package rest_model_zrok
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/go-openapi/errors"
|
||||||
"github.com/go-openapi/strfmt"
|
"github.com/go-openapi/strfmt"
|
||||||
"github.com/go-openapi/swag"
|
"github.com/go-openapi/swag"
|
||||||
)
|
)
|
||||||
@ -23,23 +25,84 @@ type Metrics struct {
|
|||||||
// period
|
// period
|
||||||
Period float64 `json:"period,omitempty"`
|
Period float64 `json:"period,omitempty"`
|
||||||
|
|
||||||
// rx
|
// samples
|
||||||
Rx []float64 `json:"rx"`
|
Samples []*MetricsSample `json:"samples"`
|
||||||
|
|
||||||
// scope
|
// scope
|
||||||
Scope string `json:"scope,omitempty"`
|
Scope string `json:"scope,omitempty"`
|
||||||
|
|
||||||
// tx
|
|
||||||
Tx []float64 `json:"tx"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate validates this metrics
|
// Validate validates this metrics
|
||||||
func (m *Metrics) Validate(formats strfmt.Registry) error {
|
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
|
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 {
|
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
|
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,22 +1147,30 @@ func init() {
|
|||||||
"period": {
|
"period": {
|
||||||
"type": "number"
|
"type": "number"
|
||||||
},
|
},
|
||||||
"rx": {
|
"samples": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
"type": "number"
|
"$ref": "#/definitions/metricsSample"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"scope": {
|
"scope": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"metricsSample": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"rx": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"timestamp": {
|
||||||
|
"type": "number"
|
||||||
},
|
},
|
||||||
"tx": {
|
"tx": {
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"type": "number"
|
"type": "number"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"principal": {
|
"principal": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
@ -2563,22 +2571,30 @@ func init() {
|
|||||||
"period": {
|
"period": {
|
||||||
"type": "number"
|
"type": "number"
|
||||||
},
|
},
|
||||||
"rx": {
|
"samples": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
"type": "number"
|
"$ref": "#/definitions/metricsSample"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"scope": {
|
"scope": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"metricsSample": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"rx": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"timestamp": {
|
||||||
|
"type": "number"
|
||||||
},
|
},
|
||||||
"tx": {
|
"tx": {
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"type": "number"
|
"type": "number"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"principal": {
|
"principal": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
@ -743,13 +743,19 @@ definitions:
|
|||||||
type: string
|
type: string
|
||||||
period:
|
period:
|
||||||
type: number
|
type: number
|
||||||
rx:
|
samples:
|
||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
|
$ref: "#/definitions/metricsSample"
|
||||||
|
|
||||||
|
metricsSample:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
rx:
|
||||||
type: number
|
type: number
|
||||||
tx:
|
tx:
|
||||||
type: array
|
type: number
|
||||||
items:
|
timestamp:
|
||||||
type: number
|
type: number
|
||||||
|
|
||||||
principal:
|
principal:
|
||||||
|
@ -130,8 +130,16 @@
|
|||||||
* @property {string} scope
|
* @property {string} scope
|
||||||
* @property {string} id
|
* @property {string} id
|
||||||
* @property {number} period
|
* @property {number} period
|
||||||
* @property {number[]} rx
|
* @property {module:types.metricsSample[]} samples
|
||||||
* @property {number[]} tx
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef metricsSample
|
||||||
|
* @memberof module:types
|
||||||
|
*
|
||||||
|
* @property {number} rx
|
||||||
|
* @property {number} tx
|
||||||
|
* @property {number} timestamp
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,8 +1,11 @@
|
|||||||
import {mdiAccountBox} from "@mdi/js";
|
import {mdiAccountBox} from "@mdi/js";
|
||||||
import Icon from "@mdi/react";
|
import Icon from "@mdi/react";
|
||||||
import PropertyTable from "../../PropertyTable";
|
import PropertyTable from "../../PropertyTable";
|
||||||
import {Tab, Tabs} from "react-bootstrap";
|
import {Tab, Tabs, Tooltip} from "react-bootstrap";
|
||||||
import SecretToggle from "../../SecretToggle";
|
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 AccountDetail = (props) => {
|
||||||
const customProperties = {
|
const customProperties = {
|
||||||
@ -16,9 +19,93 @@ const AccountDetail = (props) => {
|
|||||||
<Tab eventKey={"detail"} title={"Detail"}>
|
<Tab eventKey={"detail"} title={"Detail"}>
|
||||||
<PropertyTable object={props.user} custom={customProperties}/>
|
<PropertyTable object={props.user} custom={customProperties}/>
|
||||||
</Tab>
|
</Tab>
|
||||||
|
<Tab eventKey={"metrics"} title={"Metrics"}>
|
||||||
|
<MetricsTab />
|
||||||
|
</Tab>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</div>
|
</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;
|
export default AccountDetail;
|
Loading…
Reference in New Issue
Block a user