mirror of
https://github.com/cuigh/swirl
synced 2025-01-30 22:37:21 +00:00
Chart support multiple metrics
This commit is contained in:
parent
79ebf6937f
commit
d47ab7974d
@ -1198,6 +1198,14 @@ var Swirl;
|
||||
config(opt) {
|
||||
}
|
||||
formatValue(value) {
|
||||
if (value >= 0) {
|
||||
return this.formatPositiveValue(value);
|
||||
}
|
||||
else {
|
||||
return "-" + this.formatPositiveValue(-value);
|
||||
}
|
||||
}
|
||||
formatPositiveValue(value) {
|
||||
switch (this.opts.unit) {
|
||||
case "percent:100":
|
||||
return value.toFixed(1) + "%";
|
||||
@ -2956,4 +2964,36 @@ var Swirl;
|
||||
Service.DetailPage = DetailPage;
|
||||
})(Service = Swirl.Service || (Swirl.Service = {}));
|
||||
})(Swirl || (Swirl = {}));
|
||||
var Swirl;
|
||||
(function (Swirl) {
|
||||
var Metric;
|
||||
(function (Metric) {
|
||||
var EditTable = Swirl.Core.EditTable;
|
||||
class MetricTable extends EditTable {
|
||||
render() {
|
||||
return `<tr>
|
||||
<td>
|
||||
<input name="metrics[${this.index}].legend" class="input is-small" placeholder="Legend expression for dataset, e.g. ${name}">
|
||||
</td>
|
||||
<td>
|
||||
<input name="metrics[${this.index}].query" class="input is-small" placeholder="Prometheus query expression, for service dashboard, you can use '$\{service\}' variable">
|
||||
</td>
|
||||
<td>
|
||||
<a class="button is-small is-outlined is-danger" data-action="delete-metric">
|
||||
<span class="icon is-small">
|
||||
<i class="far fa-trash-alt"></i>
|
||||
</span>
|
||||
</a>
|
||||
</td>
|
||||
</tr>`;
|
||||
}
|
||||
}
|
||||
class EditPage {
|
||||
constructor() {
|
||||
new MetricTable("#table-metrics");
|
||||
}
|
||||
}
|
||||
Metric.EditPage = EditPage;
|
||||
})(Metric = Swirl.Metric || (Swirl.Metric = {}));
|
||||
})(Swirl || (Swirl = {}));
|
||||
//# sourceMappingURL=swirl.js.map
|
File diff suppressed because one or more lines are too long
30
assets/swirl/ts/chart/edit.ts
Normal file
30
assets/swirl/ts/chart/edit.ts
Normal file
@ -0,0 +1,30 @@
|
||||
///<reference path="../core/core.ts" />
|
||||
namespace Swirl.Metric {
|
||||
import EditTable = Swirl.Core.EditTable;
|
||||
|
||||
class MetricTable extends EditTable {
|
||||
protected render(): string {
|
||||
return `<tr>
|
||||
<td>
|
||||
<input name="metrics[${this.index}].legend" class="input is-small" placeholder="Legend expression for dataset, e.g. ${name}">
|
||||
</td>
|
||||
<td>
|
||||
<input name="metrics[${this.index}].query" class="input is-small" placeholder="Prometheus query expression, for service dashboard, you can use '$\{service\}' variable">
|
||||
</td>
|
||||
<td>
|
||||
<a class="button is-small is-outlined is-danger" data-action="delete-metric">
|
||||
<span class="icon is-small">
|
||||
<i class="far fa-trash-alt"></i>
|
||||
</span>
|
||||
</a>
|
||||
</td>
|
||||
</tr>`;
|
||||
}
|
||||
}
|
||||
|
||||
export class EditPage {
|
||||
constructor() {
|
||||
new MetricTable("#table-metrics");
|
||||
}
|
||||
}
|
||||
}
|
@ -99,6 +99,14 @@ namespace Swirl.Core {
|
||||
}
|
||||
|
||||
protected formatValue(value: number): string {
|
||||
if (value >= 0) {
|
||||
return this.formatPositiveValue(value)
|
||||
} else {
|
||||
return "-" + this.formatPositiveValue(-value);
|
||||
}
|
||||
}
|
||||
|
||||
private formatPositiveValue(value: number): string {
|
||||
switch (this.opts.unit) {
|
||||
case "percent:100":
|
||||
return value.toFixed(1) + "%";
|
||||
|
133
biz/chart.go
133
biz/chart.go
@ -2,6 +2,7 @@ package biz
|
||||
|
||||
import (
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/cuigh/auxo/data"
|
||||
@ -25,19 +26,6 @@ func newChartBiz() *chartBiz {
|
||||
b.builtin = append(b.builtin, model.NewChart("service", "$memory", "Memory", "${name}", `container_memory_usage_bytes{container_label_com_docker_swarm_service_name="${service}"}`, "size:bytes"))
|
||||
b.builtin = append(b.builtin, model.NewChart("service", "$network_in", "Network Receive", "${name}", `sum(irate(container_network_receive_bytes_total{container_label_com_docker_swarm_service_name="${service}"}[5m])) by(name)`, "size:bytes"))
|
||||
b.builtin = append(b.builtin, model.NewChart("service", "$network_out", "Network Send", "${name}", `sum(irate(container_network_transmit_bytes_total{container_label_com_docker_swarm_service_name="${service}"}[5m])) by(name)`, "size:bytes"))
|
||||
// for _, c := range categories {
|
||||
// if c == "java" {
|
||||
// charts = append(charts, model.NewChart("threads", "Threads", "${instance}", `jvm_threads_current{service="%s"}`, ""))
|
||||
// charts = append(charts, model.NewChart("gc_duration", "GC Duration", "${instance}", `rate(jvm_gc_collection_seconds_sum{service="%s"}[1m])`, "time:s"))
|
||||
// } else if c == "go" {
|
||||
// charts = append(charts, model.NewChart("threads", "Threads", "${instance}", `go_threads{service="%s"}`, ""))
|
||||
// charts = append(charts, model.NewChart("goroutines", "Goroutines", "${instance}", `go_goroutines{service="%s"}`, ""))
|
||||
// charts = append(charts, model.NewChart("gc_duration", "GC Duration", "${instance}", `sum(go_gc_duration_seconds{service="%s"}) by (instance)`, "time:s"))
|
||||
// }
|
||||
// }
|
||||
// for i, c := range charts {
|
||||
// charts[i].Query = fmt.Sprintf(c.Query, name)
|
||||
// }
|
||||
return b
|
||||
}
|
||||
|
||||
@ -67,6 +55,9 @@ func (b *chartBiz) Delete(id string, user web.User) (err error) {
|
||||
func (b *chartBiz) Get(name string) (chart *model.Chart, err error) {
|
||||
do(func(d dao.Interface) {
|
||||
chart, err = d.ChartGet(name)
|
||||
if len(chart.Metrics) == 0 {
|
||||
chart.Metrics = append(chart.Metrics, model.ChartMetric{Legend: chart.Legend, Query: chart.Query})
|
||||
}
|
||||
})
|
||||
return
|
||||
}
|
||||
@ -74,6 +65,13 @@ func (b *chartBiz) Get(name string) (chart *model.Chart, err error) {
|
||||
func (b *chartBiz) Batch(names ...string) (charts []*model.Chart, err error) {
|
||||
do(func(d dao.Interface) {
|
||||
charts, err = d.ChartBatch(names...)
|
||||
if err == nil {
|
||||
for _, c := range charts {
|
||||
if len(c.Metrics) == 0 {
|
||||
c.Metrics = append(c.Metrics, model.ChartMetric{Legend: c.Legend, Query: c.Query})
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
return
|
||||
}
|
||||
@ -164,58 +162,99 @@ func (b *chartBiz) FetchDatas(key string, names []string, period time.Duration)
|
||||
datas := data.Map{}
|
||||
end := time.Now()
|
||||
start := end.Add(-period)
|
||||
var wg sync.WaitGroup
|
||||
for _, chart := range charts {
|
||||
query, err := b.formatQuery(chart, key)
|
||||
wg.Add(1)
|
||||
go func(c *model.Chart) {
|
||||
defer wg.Done()
|
||||
|
||||
switch c.Type {
|
||||
case "line", "bar":
|
||||
d, err := b.fetchMatrixData(c, key, start, end)
|
||||
if err != nil {
|
||||
log.Get("metric").Error(err)
|
||||
} else {
|
||||
datas[c.Name] = d
|
||||
}
|
||||
case "pie":
|
||||
d, err := b.fetchVectorData(c, key, end)
|
||||
if err != nil {
|
||||
log.Get("metric").Error(err)
|
||||
} else {
|
||||
datas[c.Name] = d
|
||||
}
|
||||
case "gauge":
|
||||
d, err := b.fetchScalarData(c, key, end)
|
||||
if err != nil {
|
||||
log.Get("metric").Error(err)
|
||||
} else {
|
||||
datas[c.Name] = &model.ChartValue{
|
||||
//Name: "",
|
||||
Value: d,
|
||||
}
|
||||
}
|
||||
}
|
||||
}(chart)
|
||||
}
|
||||
wg.Wait()
|
||||
return datas, nil
|
||||
}
|
||||
|
||||
func (b *chartBiz) fetchMatrixData(chart *model.Chart, key string, start, end time.Time) (*model.ChartMatrixData, error) {
|
||||
var cmd *model.ChartMatrixData
|
||||
for i, m := range chart.Metrics {
|
||||
q, err := b.formatQuery(m.Query, chart.Dashboard, key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch chart.Type {
|
||||
case "line", "bar":
|
||||
d, err := Metric.GetMatrix(query, chart.Legend, start, end)
|
||||
if err != nil {
|
||||
log.Get("metric").Error(err)
|
||||
} else {
|
||||
datas[chart.Name] = d
|
||||
}
|
||||
case "pie":
|
||||
d, err := Metric.GetVector(query, chart.Legend, end)
|
||||
if err != nil {
|
||||
log.Get("metric").Error(err)
|
||||
} else {
|
||||
datas[chart.Name] = d
|
||||
}
|
||||
case "gauge":
|
||||
d, err := Metric.GetScalar(query, end)
|
||||
if err != nil {
|
||||
log.Get("metric").Error(err)
|
||||
} else {
|
||||
datas[chart.Name] = &model.ChartValue{
|
||||
//Name: "",
|
||||
Value: d,
|
||||
}
|
||||
}
|
||||
d, err := Metric.GetMatrix(q, m.Legend, start, end)
|
||||
if err != nil {
|
||||
log.Get("metric").Error(err)
|
||||
} else if i == 0 {
|
||||
cmd = d
|
||||
} else {
|
||||
cmd.Legend = append(cmd.Legend, d.Legend...)
|
||||
cmd.Series = append(cmd.Series, d.Series...)
|
||||
}
|
||||
}
|
||||
return datas, nil
|
||||
return cmd, nil
|
||||
}
|
||||
|
||||
func (b *chartBiz) formatQuery(chart *model.Chart, key string) (string, error) {
|
||||
if chart.Dashboard == "home" {
|
||||
return chart.Query, nil
|
||||
func (b *chartBiz) fetchVectorData(chart *model.Chart, key string, end time.Time) (*model.ChartVectorData, error) {
|
||||
query, err := b.formatQuery(chart.Metrics[0].Query, chart.Dashboard, key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return Metric.GetVector(query, chart.Legend, end)
|
||||
}
|
||||
|
||||
func (b *chartBiz) fetchScalarData(chart *model.Chart, key string, end time.Time) (float64, error) {
|
||||
query, err := b.formatQuery(chart.Metrics[0].Query, chart.Dashboard, key)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return Metric.GetScalar(query, end)
|
||||
}
|
||||
|
||||
func (b *chartBiz) formatQuery(query, dashboard, key string) (string, error) {
|
||||
if dashboard == "home" {
|
||||
return query, nil
|
||||
}
|
||||
|
||||
var errs []error
|
||||
m := map[string]string{chart.Dashboard: key}
|
||||
query := os.Expand(chart.Query, func(k string) string {
|
||||
m := map[string]string{dashboard: key}
|
||||
q := os.Expand(query, func(k string) string {
|
||||
if v, ok := m[k]; ok {
|
||||
return v
|
||||
}
|
||||
errs = append(errs, errors.New("invalid argument in query: "+chart.Query))
|
||||
errs = append(errs, errors.New("invalid argument in query: "+query))
|
||||
return ""
|
||||
})
|
||||
if len(errs) == 0 {
|
||||
return query, nil
|
||||
return q, nil
|
||||
}
|
||||
return "", errs[0]
|
||||
}
|
||||
|
@ -115,6 +115,13 @@ func chartUpdate(ctx web.Context) error {
|
||||
chart := &model.Chart{}
|
||||
err := ctx.Bind(chart)
|
||||
if err == nil {
|
||||
var metrics []model.ChartMetric
|
||||
for _, m := range chart.Metrics {
|
||||
if m.Query != "" {
|
||||
metrics = append(metrics, m)
|
||||
}
|
||||
}
|
||||
chart.Metrics = metrics
|
||||
err = biz.Chart.Update(chart, ctx.User())
|
||||
}
|
||||
return ajaxResult(ctx, err)
|
||||
|
@ -45,12 +45,6 @@ func (d *Dao) ChartBatch(names ...string) (charts []*model.Chart, err error) {
|
||||
|
||||
func (d *Dao) ChartUpdate(chart *model.Chart) (err error) {
|
||||
d.do(func(db *database) {
|
||||
//update := bson.M{
|
||||
// "$set": bson.M{
|
||||
// "name": chart.Name,
|
||||
// "desc": chart.Description,
|
||||
// },
|
||||
//}
|
||||
err = db.C("chart").UpdateId(chart.Name, chart)
|
||||
})
|
||||
return
|
||||
|
@ -9,18 +9,19 @@ import (
|
||||
|
||||
// Chart represents a dashboard chart.
|
||||
type Chart struct {
|
||||
Name string `json:"name" bson:"_id" valid:"required"` // unique, the name of build-in charts has '$' prefix.
|
||||
Title string `json:"title" valid:"required"`
|
||||
Description string `json:"desc"`
|
||||
Legend string `json:"legend"` // ${name} - ${instance}
|
||||
Query string `json:"query" valid:"required"`
|
||||
Kind string `json:"kind"` // builtin/custom
|
||||
Dashboard string `json:"dashboard"` // home/service/task...
|
||||
Type string `json:"type"` // pie/line...
|
||||
Unit string `json:"unit"` // bytes/milliseconds/percent:100...
|
||||
Width int32 `json:"width"` // 1-12(12 columns total)
|
||||
Height int32 `json:"height"` // default 50
|
||||
Options data.Map `json:"options"`
|
||||
Name string `json:"name" bson:"_id" valid:"required"` // unique, the name of build-in charts has '$' prefix.
|
||||
Title string `json:"title" valid:"required"`
|
||||
Description string `json:"desc"`
|
||||
Legend string `json:"-"`
|
||||
Query string `json:"-"`
|
||||
Metrics []ChartMetric `json:"metrics" valid:"required"`
|
||||
Kind string `json:"kind"` // builtin/custom
|
||||
Dashboard string `json:"dashboard"` // home/service/task...
|
||||
Type string `json:"type"` // pie/line...
|
||||
Unit string `json:"unit"` // bytes/milliseconds/percent:100...
|
||||
Width int32 `json:"width"` // 1-12(12 columns total)
|
||||
Height int32 `json:"height"` // default 50
|
||||
Options data.Map `json:"options"`
|
||||
//Colors []string `json:"colors"`
|
||||
}
|
||||
|
||||
@ -29,16 +30,22 @@ func NewChart(dashboard, name, title, legend, query, unit string) *Chart {
|
||||
Name: name,
|
||||
Title: title,
|
||||
Description: title,
|
||||
Legend: legend,
|
||||
Query: query,
|
||||
Dashboard: dashboard,
|
||||
Type: "line",
|
||||
Unit: unit,
|
||||
Width: 12,
|
||||
Height: 200,
|
||||
Metrics: []ChartMetric{
|
||||
{Legend: legend, Query: query},
|
||||
},
|
||||
Dashboard: dashboard,
|
||||
Type: "line",
|
||||
Unit: unit,
|
||||
Width: 12,
|
||||
Height: 200,
|
||||
}
|
||||
}
|
||||
|
||||
type ChartMetric struct {
|
||||
Legend string `json:"legend"`
|
||||
Query string `json:"query"`
|
||||
}
|
||||
|
||||
type ChartOption struct {
|
||||
Name string `json:"name"`
|
||||
Width int32 `json:"width"`
|
||||
|
@ -34,9 +34,9 @@
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<p class="has-text-centered">
|
||||
<a href="/password_reset">{{ i18n("login.forgot-password") }}</a>
|
||||
</p>
|
||||
{*<p class="has-text-centered">*}
|
||||
{*<a href="/password_reset">{{ i18n("login.forgot-password") }}</a>*}
|
||||
{*</p>*}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,6 +1,10 @@
|
||||
{{ extends "_base" }}
|
||||
{{ import "../../_modules/form" }}
|
||||
|
||||
{{ block script() }}
|
||||
<script>$(() => new Swirl.Metric.EditPage())</script>
|
||||
{{ end }}
|
||||
|
||||
{{ block body_content() }}
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
@ -22,9 +26,9 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label">{{ i18n("field.legend") }}</label>
|
||||
<label class="label">{{ i18n("field.desc") }}</label>
|
||||
<div class="control">
|
||||
<input name="legend" value="{{ .Chart.Legend }}" class="input" placeholder="Legend expression for dataset, e.g. ${name}">
|
||||
<input name="desc" value="{{ .Chart.Description }}" class="input" placeholder="Chart description">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -88,18 +92,6 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label">{{ i18n("field.desc") }}</label>
|
||||
<div class="control">
|
||||
<input name="desc" value="{{ .Chart.Description }}" class="input" placeholder="Chart description">
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label">Query expression</label>
|
||||
<div class="control">
|
||||
<input name="query" value="{{ .Chart.Query }}" class="input" placeholder="Prometheus query expression, for service dashboard, you can use '${service}' variable" data-v-rule="native" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label">{{ i18n("field.type") }}</label>
|
||||
<div class="control">
|
||||
@ -119,6 +111,43 @@
|
||||
{*{{ yield radio(name="dashboard", value="task", label="Task", checked=.Chart.Dashboard) }}*}
|
||||
</div>
|
||||
</div>
|
||||
<fieldset>
|
||||
<legend class="lead is-5">Metrics</legend>
|
||||
<table id="table-metrics" class="table is-bordered is-narrow is-fullwidth is-marginless" data-name="metric">
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="25%">Legend</th>
|
||||
<th>Query</th>
|
||||
<th width="50">
|
||||
<a class="button is-small is-outlined is-success" data-action="add-metric">
|
||||
<span class="icon is-small">
|
||||
<i class="fas fa-plus"></i>
|
||||
</span>
|
||||
</a>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{ range i, c := .Chart.Metrics }}
|
||||
<tr>
|
||||
<td>
|
||||
<input name="metrics[{{i}}].legend" value="{{c.Legend}}" class="input is-small" placeholder="Legend expression for dataset, e.g. ${name}">
|
||||
</td>
|
||||
<td>
|
||||
<input name="metrics[{{i}}].query" value="{{c.Query}}" class="input is-small" placeholder="Prometheus query expression, for service dashboard, you can use '${service}' variable">
|
||||
</td>
|
||||
<td>
|
||||
<a class="button is-small is-outlined is-danger" data-action="delete-metric">
|
||||
<span class="icon is-small">
|
||||
<i class="far fa-trash-alt"></i>
|
||||
</span>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
{{ end }}
|
||||
</tbody>
|
||||
</table>
|
||||
</fieldset>
|
||||
{{ yield form_submit(url="/system/chart/") }}
|
||||
</form>
|
||||
</div>
|
||||
|
Loading…
Reference in New Issue
Block a user