environment metrics wired in and working (#324)

This commit is contained in:
Michael Quigley 2023-05-10 14:51:46 -04:00
parent 0f6d05f449
commit 43e6c56ec1
No known key found for this signature in database
GPG Key ID: 9B60314A9DD20A62
11 changed files with 182 additions and 62 deletions

View File

@ -94,13 +94,9 @@ func (h *getEnvironmentMetricsHandler) Handle(params metadata.GetEnvironmentMetr
return metadata.NewGetEnvironmentMetricsInternalServerError() return metadata.NewGetEnvironmentMetricsInternalServerError()
} }
defer func() { _ = trx.Rollback() }() defer func() { _ = trx.Rollback() }()
env, err := str.GetEnvironment(int(params.EnvID), trx) env, err := str.FindEnvironmentForAccount(params.EnvID, int(principal.ID), trx)
if err != nil { if err != nil {
logrus.Errorf("error finding environment '%d': %v", int(params.EnvID), err) logrus.Errorf("error finding environment '%s' for '%s': %v", params.EnvID, principal.Email, err)
return metadata.NewGetEnvironmentMetricsUnauthorized()
}
if int64(env.Id) != principal.ID {
logrus.Errorf("unauthorized environemnt '%d' for '%v'", int(params.EnvID), principal.Email)
return metadata.NewGetEnvironmentMetricsUnauthorized() return metadata.NewGetEnvironmentMetricsUnauthorized()
} }

View File

@ -14,7 +14,6 @@ import (
"github.com/go-openapi/runtime" "github.com/go-openapi/runtime"
cr "github.com/go-openapi/runtime/client" cr "github.com/go-openapi/runtime/client"
"github.com/go-openapi/strfmt" "github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
) )
// NewGetEnvironmentMetricsParams creates a new GetEnvironmentMetricsParams object, // NewGetEnvironmentMetricsParams creates a new GetEnvironmentMetricsParams object,
@ -66,7 +65,7 @@ type GetEnvironmentMetricsParams struct {
Duration *string Duration *string
// EnvID. // EnvID.
EnvID float64 EnvID string
timeout time.Duration timeout time.Duration
Context context.Context Context context.Context
@ -133,13 +132,13 @@ func (o *GetEnvironmentMetricsParams) SetDuration(duration *string) {
} }
// WithEnvID adds the envID to the get environment metrics params // WithEnvID adds the envID to the get environment metrics params
func (o *GetEnvironmentMetricsParams) WithEnvID(envID float64) *GetEnvironmentMetricsParams { func (o *GetEnvironmentMetricsParams) WithEnvID(envID string) *GetEnvironmentMetricsParams {
o.SetEnvID(envID) o.SetEnvID(envID)
return o return o
} }
// SetEnvID adds the envId to the get environment metrics params // SetEnvID adds the envId to the get environment metrics params
func (o *GetEnvironmentMetricsParams) SetEnvID(envID float64) { func (o *GetEnvironmentMetricsParams) SetEnvID(envID string) {
o.EnvID = envID o.EnvID = envID
} }
@ -169,7 +168,7 @@ func (o *GetEnvironmentMetricsParams) WriteToRequest(r runtime.ClientRequest, re
} }
// path param envId // path param envId
if err := r.SetPathParam("envId", swag.FormatFloat64(o.EnvID)); err != nil { if err := r.SetPathParam("envId", o.EnvID); err != nil {
return err return err
} }

View File

@ -567,7 +567,7 @@ func init() {
"operationId": "getEnvironmentMetrics", "operationId": "getEnvironmentMetrics",
"parameters": [ "parameters": [
{ {
"type": "number", "type": "string",
"name": "envId", "name": "envId",
"in": "path", "in": "path",
"required": true "required": true
@ -2003,7 +2003,7 @@ func init() {
"operationId": "getEnvironmentMetrics", "operationId": "getEnvironmentMetrics",
"parameters": [ "parameters": [
{ {
"type": "number", "type": "string",
"name": "envId", "name": "envId",
"in": "path", "in": "path",
"required": true "required": true

View File

@ -12,7 +12,6 @@ import (
"github.com/go-openapi/runtime" "github.com/go-openapi/runtime"
"github.com/go-openapi/runtime/middleware" "github.com/go-openapi/runtime/middleware"
"github.com/go-openapi/strfmt" "github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
) )
// NewGetEnvironmentMetricsParams creates a new GetEnvironmentMetricsParams object // NewGetEnvironmentMetricsParams creates a new GetEnvironmentMetricsParams object
@ -40,7 +39,7 @@ type GetEnvironmentMetricsParams struct {
Required: true Required: true
In: path In: path
*/ */
EnvID float64 EnvID string
} }
// BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface // BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface
@ -96,12 +95,7 @@ func (o *GetEnvironmentMetricsParams) bindEnvID(rawData []string, hasKey bool, f
// Required: true // Required: true
// Parameter is provided by construction from the route // Parameter is provided by construction from the route
o.EnvID = raw
value, err := swag.ConvertFloat64(raw)
if err != nil {
return errors.InvalidType("envId", "path", "float64", raw)
}
o.EnvID = value
return nil return nil
} }

View File

@ -10,13 +10,11 @@ import (
"net/url" "net/url"
golangswaggerpaths "path" golangswaggerpaths "path"
"strings" "strings"
"github.com/go-openapi/swag"
) )
// GetEnvironmentMetricsURL generates an URL for the get environment metrics operation // GetEnvironmentMetricsURL generates an URL for the get environment metrics operation
type GetEnvironmentMetricsURL struct { type GetEnvironmentMetricsURL struct {
EnvID float64 EnvID string
Duration *string Duration *string
@ -46,7 +44,7 @@ func (o *GetEnvironmentMetricsURL) Build() (*url.URL, error) {
var _path = "/metrics/environment/{envId}" var _path = "/metrics/environment/{envId}"
envID := swag.FormatFloat64(o.EnvID) envID := o.EnvID
if envID != "" { if envID != "" {
_path = strings.Replace(_path, "{envId}", envID, -1) _path = strings.Replace(_path, "{envId}", envID, -1)
} else { } else {

View File

@ -425,7 +425,7 @@ paths:
parameters: parameters:
- name: envId - name: envId
in: path in: path
type: number type: string
required: true required: true
- name: duration - name: duration
in: query in: query

View File

@ -56,7 +56,7 @@ export function getAccountMetrics(options) {
} }
/** /**
* @param {number} envId * @param {string} envId
* @param {object} options Optional options * @param {object} options Optional options
* @param {string} [options.duration] * @param {string} [options.duration]
* @return {Promise<module:types.metrics>} environment metrics * @return {Promise<module:types.metrics>} environment metrics

View File

@ -7,6 +7,7 @@ import React, {useEffect, useState} from "react";
import * as metadata from "../../../api/metadata"; import * as metadata from "../../../api/metadata";
import { Bar, BarChart, CartesianGrid, ResponsiveContainer, XAxis, YAxis } from "recharts"; import { Bar, BarChart, CartesianGrid, ResponsiveContainer, XAxis, YAxis } from "recharts";
import moment from "moment"; import moment from "moment";
import {buildMetrics, bytesToSize} from "../../metrics";
const AccountDetail = (props) => { const AccountDetail = (props) => {
const customProperties = { const customProperties = {
@ -28,7 +29,7 @@ const AccountDetail = (props) => {
); );
} }
const MetricsTab = (props) => { const MetricsTab = () => {
const [metrics30, setMetrics30] = useState(buildMetrics([])); const [metrics30, setMetrics30] = useState(buildMetrics([]));
const [metrics7, setMetrics7] = useState(buildMetrics([])); const [metrics7, setMetrics7] = useState(buildMetrics([]));
const [metrics1, setMetrics1] = useState(buildMetrics([])); const [metrics1, setMetrics1] = useState(buildMetrics([]));
@ -147,38 +148,4 @@ const MetricsTab = (props) => {
); );
} }
const buildMetrics = (m) => {
let metrics = {
data: m.samples,
rx: 0,
tx: 0
}
if(m.samples) {
m.samples.forEach(s => {
metrics.rx += s.rx;
metrics.tx += s.tx;
});
}
return metrics;
}
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(1) + ' ' + "kMGTPE"[exp] + 'B';
}
export default AccountDetail; export default AccountDetail;

View File

@ -6,6 +6,7 @@ import {mdiConsoleNetwork} from "@mdi/js";
import {getEnvironmentDetail} from "../../../api/metadata"; import {getEnvironmentDetail} from "../../../api/metadata";
import DetailTab from "./DetailTab"; import DetailTab from "./DetailTab";
import ActionsTab from "./ActionsTab"; import ActionsTab from "./ActionsTab";
import MetricsTab from "./MetricsTab";
const EnvironmentDetail = (props) => { const EnvironmentDetail = (props) => {
const [detail, setDetail] = useState({}); const [detail, setDetail] = useState({});
@ -25,6 +26,9 @@ const EnvironmentDetail = (props) => {
<Tab eventKey={"shares"} title={"Shares"}> <Tab eventKey={"shares"} title={"Shares"}>
<SharesTab selection={props.selection} /> <SharesTab selection={props.selection} />
</Tab> </Tab>
<Tab eventKey={"metrics"} title={"Metrics"}>
<MetricsTab selection={props.selection} />
</Tab>
<Tab eventKey={"detail"} title={"Detail"}> <Tab eventKey={"detail"} title={"Detail"}>
<DetailTab environment={detail.environment} /> <DetailTab environment={detail.environment} />
</Tab> </Tab>

View File

@ -0,0 +1,129 @@
import React, {useEffect, useState} from "react";
import {buildMetrics, bytesToSize} from "../../metrics";
import * as metadata from "../../../api/metadata";
import {Col, Container, Row, Tooltip} from "react-bootstrap";
import {Bar, BarChart, CartesianGrid, ResponsiveContainer, XAxis, YAxis} from "recharts";
import moment from "moment/moment";
const MetricsTab = (props) => {
const [metrics30, setMetrics30] = useState(buildMetrics([]));
const [metrics7, setMetrics7] = useState(buildMetrics([]));
const [metrics1, setMetrics1] = useState(buildMetrics([]));
console.log("selection", props.selection);
useEffect(() => {
metadata.getEnvironmentMetrics(props.selection.id)
.then(resp => {
setMetrics30(buildMetrics(resp.data));
});
metadata.getEnvironmentMetrics(props.selection.id, {duration: "168h"})
.then(resp => {
setMetrics7(buildMetrics(resp.data));
});
metadata.getEnvironmentMetrics(props.selection.id, {duration: "24h"})
.then(resp => {
setMetrics1(buildMetrics(resp.data));
});
}, []);
useEffect(() => {
let mounted = true;
let interval = setInterval(() => {
metadata.getEnvironmentMetrics(props.selection.id)
.then(resp => {
if(mounted) {
setMetrics30(buildMetrics(resp.data));
}
});
metadata.getEnvironmentMetrics(props.selection.id, {duration: "168h"})
.then(resp => {
setMetrics7(buildMetrics(resp.data));
});
metadata.getEnvironmentMetrics(props.selection.id, {duration: "24h"})
.then(resp => {
setMetrics1(buildMetrics(resp.data));
});
}, 5000);
return () => {
mounted = false;
clearInterval(interval);
}
}, []);
return (
<Container>
<Row>
<Col>
<h3>Last 30 Days:</h3>
</Col>
</Row>
<Row>
<Col><p>Received: {bytesToSize(metrics30.rx)}</p></Col>
<Col><p>Sent: {bytesToSize(metrics30.tx)}</p></Col>
</Row>
<Row>
<Col>
<ResponsiveContainer width={"100%"} height={150}>
<BarChart data={metrics30.data}>
<CartesianGrid strokeDasharay={"3 3"} />
<XAxis dataKey={(v) => v.timestamp} scale={"time"} tickFormatter={(v) => moment(v).format("MMM DD") } style={{ fontSize: '75%'}}/>
<YAxis tickFormatter={(v) => bytesToSize(v)} style={{ fontSize: '75%' }}/>
<Bar stroke={"#231069"} fill={"#04adef"} dataKey={"rx"} legendType={"circle"}/>
<Bar stroke={"#231069"} fill={"#9BF316"} dataKey={"tx"} />
<Tooltip />
</BarChart>
</ResponsiveContainer>
</Col>
</Row>
<Row>
<Col>
<h3>Last 7 Days:</h3>
</Col>
</Row>
<Row>
<Col><p>Received: {bytesToSize(metrics7.rx)}</p></Col>
<Col><p>Sent: {bytesToSize(metrics7.tx)}</p></Col>
</Row>
<Row>
<Col>
<ResponsiveContainer width={"100%"} height={150}>
<BarChart data={metrics7.data}>
<CartesianGrid strokeDasharay={"3 3"} />
<XAxis dataKey={(v) => v.timestamp} scale={"time"} tickFormatter={(v) => moment(v).format("MMM DD") } style={{ fontSize: '75%'}}/>
<YAxis tickFormatter={(v) => bytesToSize(v)} style={{ fontSize: '75%' }}/>
<Bar stroke={"#231069"} fill={"#04adef"} dataKey={"rx"} legendType={"circle"}/>
<Bar stroke={"#231069"} fill={"#9BF316"} dataKey={"tx"} />
<Tooltip />
</BarChart>
</ResponsiveContainer>
</Col>
</Row>
<Row>
<Col>
<h3>Last 24 Hours:</h3>
</Col>
</Row>
<Row>
<Col><p>Received: {bytesToSize(metrics1.rx)}</p></Col>
<Col><p>Sent: {bytesToSize(metrics1.tx)}</p></Col>
</Row>
<Row>
<Col>
<ResponsiveContainer width={"100%"} height={150}>
<BarChart data={metrics1.data}>
<CartesianGrid strokeDasharay={"3 3"} />
<XAxis dataKey={(v) => v.timestamp} scale={"time"} tickFormatter={(v) => moment(v).format("MMM DD") } style={{ fontSize: '75%'}}/>
<YAxis tickFormatter={(v) => bytesToSize(v)} style={{ fontSize: '75%' }}/>
<Bar stroke={"#231069"} fill={"#04adef"} dataKey={"rx"} legendType={"circle"}/>
<Bar stroke={"#231069"} fill={"#9BF316"} dataKey={"tx"} />
<Tooltip />
</BarChart>
</ResponsiveContainer>
</Col>
</Row>
</Container>
);
};
export default MetricsTab;

33
ui/src/console/metrics.js Normal file
View File

@ -0,0 +1,33 @@
export const buildMetrics = (m) => {
let metrics = {
data: m.samples,
rx: 0,
tx: 0
}
if(m.samples) {
m.samples.forEach(s => {
metrics.rx += s.rx;
metrics.tx += s.tx;
});
}
return metrics;
}
export 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(1) + ' ' + "kMGTPE"[exp] + 'B';
}