diff --git a/controller/metrics.go b/controller/metrics.go
index a5091b0f..0197dbce 100644
--- a/controller/metrics.go
+++ b/controller/metrics.go
@@ -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
}
diff --git a/rest_model_zrok/metrics.go b/rest_model_zrok/metrics.go
index dfb9bf08..1aa24172 100644
--- a/rest_model_zrok/metrics.go
+++ b/rest_model_zrok/metrics.go
@@ -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
}
diff --git a/rest_model_zrok/metrics_sample.go b/rest_model_zrok/metrics_sample.go
new file mode 100644
index 00000000..84869de4
--- /dev/null
+++ b/rest_model_zrok/metrics_sample.go
@@ -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
+}
diff --git a/rest_server_zrok/embedded_spec.go b/rest_server_zrok/embedded_spec.go
index 2d8d53fc..d268d47c 100644
--- a/rest_server_zrok/embedded_spec.go
+++ b/rest_server_zrok/embedded_spec.go
@@ -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"
}
}
},
diff --git a/specs/zrok.yml b/specs/zrok.yml
index a73ab913..fe129fc8 100644
--- a/specs/zrok.yml
+++ b/specs/zrok.yml
@@ -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
diff --git a/ui/src/api/types.js b/ui/src/api/types.js
index 6f5a6317..01c83b83 100644
--- a/ui/src/api/types.js
+++ b/ui/src/api/types.js
@@ -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
*/
/**
diff --git a/ui/src/console/detail/account/AccountDetail.js b/ui/src/console/detail/account/AccountDetail.js
index 679b51aa..bd39617d 100644
--- a/ui/src/console/detail/account/AccountDetail.js
+++ b/ui/src/console/detail/account/AccountDetail.js
@@ -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) => {