mirror of
https://github.com/casdoor/casdoor.git
synced 2025-05-23 10:45:47 +08:00
feat: support for prometheus (#1784)
This commit is contained in:
parent
fe53e90d37
commit
1003639e5b
@ -121,6 +121,8 @@ p, *, *, *, /cas, *, *
|
|||||||
p, *, *, *, /api/webauthn, *, *
|
p, *, *, *, /api/webauthn, *, *
|
||||||
p, *, *, GET, /api/get-release, *, *
|
p, *, *, GET, /api/get-release, *, *
|
||||||
p, *, *, GET, /api/get-default-application, *, *
|
p, *, *, GET, /api/get-default-application, *, *
|
||||||
|
p, *, *, GET, /api/get-prometheus-info, *, *
|
||||||
|
p, *, *, *, /api/metrics, *, *
|
||||||
`
|
`
|
||||||
|
|
||||||
sa := stringadapter.NewAdapter(ruleText)
|
sa := stringadapter.NewAdapter(ruleText)
|
||||||
|
39
controllers/prometheus.go
Normal file
39
controllers/prometheus.go
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package controllers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/casdoor/casdoor/object"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetPrometheusInfo
|
||||||
|
// @Title GetPrometheusInfo
|
||||||
|
// @Tag Prometheus API
|
||||||
|
// @Description get Prometheus Info
|
||||||
|
// @Success 200 {object} object.PrometheusInfo The Response object
|
||||||
|
// @router /get-prometheus-info [get]
|
||||||
|
func (c *ApiController) GetPrometheusInfo() {
|
||||||
|
_, ok := c.RequireAdmin()
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
prometheusInfo, err := object.GetPrometheusInfo()
|
||||||
|
if err != nil {
|
||||||
|
c.ResponseError(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ResponseOk(prometheusInfo)
|
||||||
|
}
|
2
go.mod
2
go.mod
@ -36,6 +36,8 @@ require (
|
|||||||
github.com/markbates/goth v1.75.2
|
github.com/markbates/goth v1.75.2
|
||||||
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d // indirect
|
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d // indirect
|
||||||
github.com/nyaruka/phonenumbers v1.1.5
|
github.com/nyaruka/phonenumbers v1.1.5
|
||||||
|
github.com/prometheus/client_golang v1.7.0
|
||||||
|
github.com/prometheus/client_model v0.2.0
|
||||||
github.com/qiangmzsx/string-adapter/v2 v2.1.0
|
github.com/qiangmzsx/string-adapter/v2 v2.1.0
|
||||||
github.com/robfig/cron/v3 v3.0.1
|
github.com/robfig/cron/v3 v3.0.1
|
||||||
github.com/russellhaering/gosaml2 v0.6.0
|
github.com/russellhaering/gosaml2 v0.6.0
|
||||||
|
3
main.go
3
main.go
@ -82,6 +82,7 @@ func main() {
|
|||||||
logs.SetLogFuncCall(false)
|
logs.SetLogFuncCall(false)
|
||||||
|
|
||||||
go ldap.StartLdapServer()
|
go ldap.StartLdapServer()
|
||||||
|
go object.ClearThroughputPerSecond()
|
||||||
|
|
||||||
beego.Run(fmt.Sprintf(":%v", port))
|
beego.RunWithMiddleWares(fmt.Sprintf(":%v", port), routers.PrometheusMiddleWare)
|
||||||
}
|
}
|
||||||
|
129
object/prometheus.go
Normal file
129
object/prometheus.go
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package object
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
"github.com/prometheus/client_golang/prometheus/promauto"
|
||||||
|
"github.com/prometheus/client_model/go"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PrometheusInfo struct {
|
||||||
|
APIThroughput []GaugeVecInfo `json:"apiThroughput"`
|
||||||
|
APILatency []HistogramVecInfo `json:"apiLatency"`
|
||||||
|
TotalThroughput float64 `json:"totalThroughput"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GaugeVecInfo struct {
|
||||||
|
Method string `json:"method"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Throughput float64 `json:"throughput"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type HistogramVecInfo struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Method string `json:"method"`
|
||||||
|
Count uint64 `json:"count"`
|
||||||
|
Latency string `json:"latency"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
APIThroughput = promauto.NewGaugeVec(prometheus.GaugeOpts{
|
||||||
|
Name: "casdoor_api_throughput",
|
||||||
|
Help: "The throughput of each api access",
|
||||||
|
}, []string{"path", "method"})
|
||||||
|
|
||||||
|
APILatency = promauto.NewHistogramVec(prometheus.HistogramOpts{
|
||||||
|
Name: "casdoor_api_latency",
|
||||||
|
Help: "API processing latency in milliseconds",
|
||||||
|
}, []string{"path", "method"})
|
||||||
|
|
||||||
|
CpuUsage = promauto.NewGaugeVec(prometheus.GaugeOpts{
|
||||||
|
Name: "casdoor_cpu_usage",
|
||||||
|
Help: "Casdoor cpu usage",
|
||||||
|
}, []string{"cpuNum"})
|
||||||
|
|
||||||
|
MemoryUsage = promauto.NewGaugeVec(prometheus.GaugeOpts{
|
||||||
|
Name: "casdoor_memory_usage",
|
||||||
|
Help: "Casdoor memory usage in Byte",
|
||||||
|
}, []string{"type"})
|
||||||
|
|
||||||
|
TotalThroughput = promauto.NewGauge(prometheus.GaugeOpts{
|
||||||
|
Name: "casdoor_total_throughput",
|
||||||
|
Help: "The total throughput of casdoor",
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
func ClearThroughputPerSecond() {
|
||||||
|
// Clear the throughput every second
|
||||||
|
ticker := time.NewTicker(time.Second)
|
||||||
|
for range ticker.C {
|
||||||
|
APIThroughput.Reset()
|
||||||
|
TotalThroughput.Set(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetPrometheusInfo() (*PrometheusInfo, error) {
|
||||||
|
res := &PrometheusInfo{}
|
||||||
|
metricFamilies, err := prometheus.DefaultGatherer.Gather()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for _, metricFamily := range metricFamilies {
|
||||||
|
switch metricFamily.GetName() {
|
||||||
|
case "casdoor_api_throughput":
|
||||||
|
res.APIThroughput = getGaugeVecInfo(metricFamily)
|
||||||
|
case "casdoor_api_latency":
|
||||||
|
res.APILatency = getHistogramVecInfo(metricFamily)
|
||||||
|
case "casdoor_total_throughput":
|
||||||
|
res.TotalThroughput = metricFamily.GetMetric()[0].GetGauge().GetValue()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getHistogramVecInfo(metricFamily *io_prometheus_client.MetricFamily) []HistogramVecInfo {
|
||||||
|
var histogramVecInfos []HistogramVecInfo
|
||||||
|
for _, metric := range metricFamily.GetMetric() {
|
||||||
|
sampleCount := metric.GetHistogram().GetSampleCount()
|
||||||
|
sampleSum := metric.GetHistogram().GetSampleSum()
|
||||||
|
latency := sampleSum / float64(sampleCount)
|
||||||
|
histogramVecInfo := HistogramVecInfo{
|
||||||
|
Method: metric.Label[0].GetValue(),
|
||||||
|
Name: metric.Label[1].GetValue(),
|
||||||
|
Count: sampleCount,
|
||||||
|
Latency: fmt.Sprintf("%.3f", latency),
|
||||||
|
}
|
||||||
|
histogramVecInfos = append(histogramVecInfos, histogramVecInfo)
|
||||||
|
}
|
||||||
|
return histogramVecInfos
|
||||||
|
}
|
||||||
|
|
||||||
|
func getGaugeVecInfo(metricFamily *io_prometheus_client.MetricFamily) []GaugeVecInfo {
|
||||||
|
var counterVecInfos []GaugeVecInfo
|
||||||
|
for _, metric := range metricFamily.GetMetric() {
|
||||||
|
counterVecInfo := GaugeVecInfo{
|
||||||
|
Method: metric.Label[0].GetValue(),
|
||||||
|
Name: metric.Label[1].GetValue(),
|
||||||
|
Throughput: metric.Gauge.GetValue(),
|
||||||
|
}
|
||||||
|
counterVecInfos = append(counterVecInfos, counterVecInfo)
|
||||||
|
}
|
||||||
|
return counterVecInfos
|
||||||
|
}
|
51
routers/prometheus_filter.go
Normal file
51
routers/prometheus_filter.go
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
package routers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/casdoor/casdoor/object"
|
||||||
|
"github.com/casdoor/casdoor/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PrometheusMiddleWareWrapper struct {
|
||||||
|
handler http.Handler
|
||||||
|
}
|
||||||
|
|
||||||
|
func PrometheusMiddleWare(h http.Handler) http.Handler {
|
||||||
|
return &PrometheusMiddleWareWrapper{
|
||||||
|
handler: h,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p PrometheusMiddleWareWrapper) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||||
|
method := req.Method
|
||||||
|
endpoint := req.URL.Path
|
||||||
|
if strings.HasPrefix(endpoint, "/api/metrics") {
|
||||||
|
systemInfo, err := util.GetSystemInfo()
|
||||||
|
if err == nil {
|
||||||
|
recordSystemInfo(systemInfo)
|
||||||
|
}
|
||||||
|
p.handler.ServeHTTP(w, req)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.HasPrefix(endpoint, "/api") {
|
||||||
|
start := time.Now()
|
||||||
|
p.handler.ServeHTTP(w, req)
|
||||||
|
latency := time.Since(start).Milliseconds()
|
||||||
|
object.TotalThroughput.Inc()
|
||||||
|
object.APILatency.WithLabelValues(endpoint, method).Observe(float64(latency))
|
||||||
|
object.APIThroughput.WithLabelValues(endpoint, method).Inc()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func recordSystemInfo(systemInfo *util.SystemInfo) {
|
||||||
|
for i, value := range systemInfo.CpuUsage {
|
||||||
|
object.CpuUsage.WithLabelValues(fmt.Sprintf("%d", i)).Set(value)
|
||||||
|
}
|
||||||
|
object.MemoryUsage.WithLabelValues("memoryUsed").Set(float64(systemInfo.MemoryUsed))
|
||||||
|
object.MemoryUsage.WithLabelValues("memoryTotal").Set(float64(systemInfo.MemoryTotal))
|
||||||
|
}
|
@ -21,8 +21,8 @@ package routers
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/beego/beego"
|
"github.com/beego/beego"
|
||||||
|
|
||||||
"github.com/casdoor/casdoor/controllers"
|
"github.com/casdoor/casdoor/controllers"
|
||||||
|
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@ -239,4 +239,7 @@ func initAPI() {
|
|||||||
|
|
||||||
beego.Router("/api/get-system-info", &controllers.ApiController{}, "GET:GetSystemInfo")
|
beego.Router("/api/get-system-info", &controllers.ApiController{}, "GET:GetSystemInfo")
|
||||||
beego.Router("/api/get-version-info", &controllers.ApiController{}, "GET:GetVersionInfo")
|
beego.Router("/api/get-version-info", &controllers.ApiController{}, "GET:GetVersionInfo")
|
||||||
|
beego.Router("/api/get-prometheus-info", &controllers.ApiController{}, "GET:GetPrometheusInfo")
|
||||||
|
|
||||||
|
beego.Handler("/api/metrics", promhttp.Handler())
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,7 @@ import * as SystemBackend from "./backend/SystemInfo";
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import * as Setting from "./Setting";
|
import * as Setting from "./Setting";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
|
import PrometheusInfoTable from "./table/PrometheusInfoTable";
|
||||||
|
|
||||||
class SystemInfo extends React.Component {
|
class SystemInfo extends React.Component {
|
||||||
|
|
||||||
@ -25,6 +26,7 @@ class SystemInfo extends React.Component {
|
|||||||
this.state = {
|
this.state = {
|
||||||
systemInfo: {cpuUsage: [], memoryUsed: 0, memoryTotal: 0},
|
systemInfo: {cpuUsage: [], memoryUsed: 0, memoryTotal: 0},
|
||||||
versionInfo: {},
|
versionInfo: {},
|
||||||
|
prometheusInfo: {apiThroughput: [], apiLatency: [], totalThroughput: 0},
|
||||||
intervalId: null,
|
intervalId: null,
|
||||||
loading: true,
|
loading: true,
|
||||||
};
|
};
|
||||||
@ -45,6 +47,11 @@ class SystemInfo extends React.Component {
|
|||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
Setting.showMessage("error", `System info failed to get: ${error}`);
|
Setting.showMessage("error", `System info failed to get: ${error}`);
|
||||||
});
|
});
|
||||||
|
SystemBackend.getPrometheusInfo().then(res => {
|
||||||
|
this.setState({
|
||||||
|
prometheusInfo: res.data,
|
||||||
|
});
|
||||||
|
});
|
||||||
}, 1000 * 2);
|
}, 1000 * 2);
|
||||||
this.setState({intervalId: id});
|
this.setState({intervalId: id});
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
@ -80,7 +87,10 @@ class SystemInfo extends React.Component {
|
|||||||
<br /> <br />
|
<br /> <br />
|
||||||
<Progress type="circle" percent={Number((Number(this.state.systemInfo.memoryUsed) / Number(this.state.systemInfo.memoryTotal) * 100).toFixed(2))} />
|
<Progress type="circle" percent={Number((Number(this.state.systemInfo.memoryUsed) / Number(this.state.systemInfo.memoryTotal) * 100).toFixed(2))} />
|
||||||
</div>;
|
</div>;
|
||||||
|
const latencyUi = this.state.prometheusInfo.apiLatency.length <= 0 ? <Spin size="large" /> :
|
||||||
|
<PrometheusInfoTable prometheusInfo={this.state.prometheusInfo} table={"latency"} />;
|
||||||
|
const throughputUi = this.state.prometheusInfo.apiLatency.length <= 0 ? <Spin size="large" /> :
|
||||||
|
<PrometheusInfoTable prometheusInfo={this.state.prometheusInfo} table={"throughput"} />;
|
||||||
const link = this.state.versionInfo?.version !== "" ? `https://github.com/casdoor/casdoor/releases/tag/${this.state.versionInfo?.version}` : "";
|
const link = this.state.versionInfo?.version !== "" ? `https://github.com/casdoor/casdoor/releases/tag/${this.state.versionInfo?.version}` : "";
|
||||||
let versionText = this.state.versionInfo?.version !== "" ? this.state.versionInfo?.version : i18next.t("system:Unknown version");
|
let versionText = this.state.versionInfo?.version !== "" ? this.state.versionInfo?.version : i18next.t("system:Unknown version");
|
||||||
if (this.state.versionInfo?.commitOffset > 0) {
|
if (this.state.versionInfo?.commitOffset > 0) {
|
||||||
@ -103,6 +113,16 @@ class SystemInfo extends React.Component {
|
|||||||
{this.state.loading ? <Spin size="large" /> : memUi}
|
{this.state.loading ? <Spin size="large" /> : memUi}
|
||||||
</Card>
|
</Card>
|
||||||
</Col>
|
</Col>
|
||||||
|
<Col span={24}>
|
||||||
|
<Card title={i18next.t("system:API Latency")} bordered={true} style={{textAlign: "center", height: "100%"}}>
|
||||||
|
{this.state.loading ? <Spin size="large" /> : latencyUi}
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
|
<Col span={24}>
|
||||||
|
<Card title={i18next.t("system:API Throughput")} bordered={true} style={{textAlign: "center", height: "100%"}}>
|
||||||
|
{this.state.loading ? <Spin size="large" /> : throughputUi}
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
<Divider />
|
<Divider />
|
||||||
<Card title={i18next.t("system:About Casdoor")} bordered={true} style={{textAlign: "center"}}>
|
<Card title={i18next.t("system:About Casdoor")} bordered={true} style={{textAlign: "center"}}>
|
||||||
|
@ -33,3 +33,13 @@ export function getVersionInfo() {
|
|||||||
},
|
},
|
||||||
}).then(res => res.json());
|
}).then(res => res.json());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getPrometheusInfo() {
|
||||||
|
return fetch(`${Setting.ServerUrl}/api/get-prometheus-info `, {
|
||||||
|
method: "GET",
|
||||||
|
credentials: "include",
|
||||||
|
headers: {
|
||||||
|
"Accept-Language": Setting.getAcceptLanguage(),
|
||||||
|
},
|
||||||
|
}).then(res => res.json());
|
||||||
|
}
|
||||||
|
82
web/src/table/PrometheusInfoTable.js
Normal file
82
web/src/table/PrometheusInfoTable.js
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
|
import {Table} from "antd";
|
||||||
|
|
||||||
|
class PrometheusInfoTable extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
table: props.table,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
render() {
|
||||||
|
const latencyColumns = [
|
||||||
|
{
|
||||||
|
title: "Name",
|
||||||
|
dataIndex: "name",
|
||||||
|
key: "name",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Method",
|
||||||
|
dataIndex: "method",
|
||||||
|
key: "method",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Count",
|
||||||
|
dataIndex: "count",
|
||||||
|
key: "count",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Latency(ms)",
|
||||||
|
dataIndex: "latency",
|
||||||
|
key: "latency",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const throughputColumns = [
|
||||||
|
{
|
||||||
|
title: "Name",
|
||||||
|
dataIndex: "name",
|
||||||
|
key: "name",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Method",
|
||||||
|
dataIndex: "method",
|
||||||
|
key: "method",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Throughput",
|
||||||
|
dataIndex: "throughput",
|
||||||
|
key: "throughput",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
if (this.state.table === "latency") {
|
||||||
|
return (
|
||||||
|
<div style={{height: "300px", overflow: "auto"}}>
|
||||||
|
<Table columns={latencyColumns} dataSource={this.props.prometheusInfo.apiLatency} pagination={false} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
} else if (this.state.table === "throughput") {
|
||||||
|
return (
|
||||||
|
<div style={{height: "300px", overflow: "auto"}}>
|
||||||
|
Total Throughput: {this.props.prometheusInfo.totalThroughput}
|
||||||
|
<Table columns={throughputColumns} dataSource={this.props.prometheusInfo.apiThroughput} pagination={false} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PrometheusInfoTable;
|
Loading…
x
Reference in New Issue
Block a user