mirror of
https://github.com/cuigh/swirl
synced 2024-12-28 14:51:57 +00:00
Add network/java/go metrics
This commit is contained in:
parent
6fff495cda
commit
c600bcb0dd
@ -1857,6 +1857,156 @@ var Swirl;
|
||||
})(Service = Swirl.Service || (Swirl.Service = {}));
|
||||
})(Swirl || (Swirl = {}));
|
||||
var Swirl;
|
||||
(function (Swirl) {
|
||||
var Service;
|
||||
(function (Service) {
|
||||
class MetricChartOptions {
|
||||
constructor() {
|
||||
this.type = "line";
|
||||
this.height = 50;
|
||||
}
|
||||
}
|
||||
class MetricChart {
|
||||
constructor(elem, opts) {
|
||||
this.colors = [
|
||||
'rgb(255, 99, 132)',
|
||||
'rgb(75, 192, 192)',
|
||||
'rgb(255, 159, 64)',
|
||||
'rgb(54, 162, 235)',
|
||||
'rgb(153, 102, 255)',
|
||||
'rgb(255, 205, 86)',
|
||||
'rgb(201, 203, 207)',
|
||||
];
|
||||
opts = $.extend(new MetricChartOptions(), opts);
|
||||
this.config = {
|
||||
type: opts.type,
|
||||
data: {},
|
||||
options: {
|
||||
title: {
|
||||
text: opts.title || 'NONE'
|
||||
},
|
||||
animation: {
|
||||
duration: 0,
|
||||
},
|
||||
scales: {
|
||||
xAxes: [{
|
||||
type: 'time',
|
||||
time: {
|
||||
unit: 'minute',
|
||||
tooltipFormat: 'YYYY/MM/DD HH:mm:ss',
|
||||
displayFormats: {
|
||||
minute: 'HH:mm'
|
||||
}
|
||||
},
|
||||
}],
|
||||
yAxes: [{}]
|
||||
},
|
||||
}
|
||||
};
|
||||
if (opts.labelX) {
|
||||
this.config.options.scales.xAxes[0].scaleLabel = {
|
||||
display: true,
|
||||
labelString: opts.labelX,
|
||||
};
|
||||
}
|
||||
if (opts.labelY) {
|
||||
this.config.options.scales.yAxes[0].scaleLabel = {
|
||||
display: true,
|
||||
labelString: opts.labelY,
|
||||
};
|
||||
}
|
||||
if (opts.tickY) {
|
||||
this.config.options.scales.yAxes[0].ticks = {
|
||||
callback: opts.tickY,
|
||||
};
|
||||
}
|
||||
let ctx = $(elem).get(0).getContext('2d');
|
||||
if (opts.height) {
|
||||
ctx.canvas.height = opts.height;
|
||||
}
|
||||
this.chart = new Chart(ctx, this.config);
|
||||
}
|
||||
setData(datasets) {
|
||||
datasets.forEach((ds, i) => {
|
||||
let color = (i < this.colors.length) ? this.colors[i] : this.colors[0];
|
||||
ds.backgroundColor = Chart.helpers.color(color).alpha(0.3).rgbString();
|
||||
ds.borderColor = color;
|
||||
ds.borderWidth = 2;
|
||||
ds.pointRadius = 2;
|
||||
});
|
||||
this.config.data.datasets = datasets;
|
||||
this.chart.update();
|
||||
}
|
||||
}
|
||||
class StatsPage {
|
||||
constructor() {
|
||||
this.chartOptions = {
|
||||
"cpu": { tickY: (value) => value + '%' },
|
||||
"memory": { tickY: StatsPage.formatSize },
|
||||
"network_in": { tickY: StatsPage.formatSize },
|
||||
"network_out": { tickY: StatsPage.formatSize },
|
||||
"threads": {},
|
||||
"goroutines": {},
|
||||
"gc_duration": { tickY: (value) => value * 1000 + 'ms' },
|
||||
};
|
||||
this.charts = {};
|
||||
let $cb_time = $("#cb-time");
|
||||
if ($cb_time.length == 0) {
|
||||
return;
|
||||
}
|
||||
$cb_time.change(this.loadData.bind(this));
|
||||
$("#cb-refresh").change(() => {
|
||||
if (this.timer) {
|
||||
clearTimeout(this.timer);
|
||||
this.timer = null;
|
||||
}
|
||||
else {
|
||||
this.refreshData();
|
||||
}
|
||||
});
|
||||
$.each(this.chartOptions, (name, opt) => {
|
||||
let $el = $("#canvas_" + name);
|
||||
if ($el.length > 0) {
|
||||
this.charts[name] = new MetricChart($el, opt);
|
||||
}
|
||||
});
|
||||
this.refreshData();
|
||||
}
|
||||
refreshData() {
|
||||
this.loadData();
|
||||
if ($("#cb-refresh").prop("checked")) {
|
||||
this.timer = setTimeout(this.refreshData.bind(this), 15000);
|
||||
}
|
||||
}
|
||||
loadData() {
|
||||
let time = $("#cb-time").val();
|
||||
$ajax.get(`metrics`, { time: time }).json((d) => {
|
||||
$.each(this.charts, (name, chart) => {
|
||||
if (d[name]) {
|
||||
chart.setData(d[name]);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
static formatSize(size) {
|
||||
if (size < 1024) {
|
||||
return size.toString() + 'B';
|
||||
}
|
||||
else if (size < 1048576) {
|
||||
return (size / 1024).toFixed(2) + 'K';
|
||||
}
|
||||
else if (size < 1073741824) {
|
||||
return (size / 1048576).toFixed(2) + 'M';
|
||||
}
|
||||
else {
|
||||
return (size / 1073741824).toFixed(2) + 'G';
|
||||
}
|
||||
}
|
||||
}
|
||||
Service.StatsPage = StatsPage;
|
||||
})(Service = Swirl.Service || (Swirl.Service = {}));
|
||||
})(Swirl || (Swirl = {}));
|
||||
var Swirl;
|
||||
(function (Swirl) {
|
||||
var Service;
|
||||
(function (Service) {
|
||||
@ -2155,137 +2305,4 @@ var Swirl;
|
||||
Volume.NewPage = NewPage;
|
||||
})(Volume = Swirl.Volume || (Swirl.Volume = {}));
|
||||
})(Swirl || (Swirl = {}));
|
||||
var Swirl;
|
||||
(function (Swirl) {
|
||||
var Service;
|
||||
(function (Service) {
|
||||
class MetricChartOptions {
|
||||
constructor() {
|
||||
this.type = "line";
|
||||
this.height = 50;
|
||||
}
|
||||
}
|
||||
class MetricData {
|
||||
}
|
||||
class MetricChart {
|
||||
constructor(elem, opts) {
|
||||
this.colors = [
|
||||
'rgb(255, 99, 132)',
|
||||
'rgb(75, 192, 192)',
|
||||
'rgb(255, 159, 64)',
|
||||
'rgb(54, 162, 235)',
|
||||
'rgb(153, 102, 255)',
|
||||
'rgb(255, 205, 86)',
|
||||
'rgb(201, 203, 207)',
|
||||
];
|
||||
opts = $.extend(new MetricChartOptions(), opts);
|
||||
this.config = {
|
||||
type: opts.type,
|
||||
data: {},
|
||||
options: {
|
||||
title: {
|
||||
text: opts.title || 'NONE'
|
||||
},
|
||||
animation: {
|
||||
duration: 0,
|
||||
},
|
||||
scales: {
|
||||
xAxes: [{
|
||||
type: 'time',
|
||||
time: {
|
||||
unit: 'minute',
|
||||
tooltipFormat: 'YYYY/MM/DD HH:mm:ss',
|
||||
displayFormats: {
|
||||
minute: 'HH:mm'
|
||||
}
|
||||
},
|
||||
}],
|
||||
yAxes: [{}]
|
||||
},
|
||||
}
|
||||
};
|
||||
if (opts.labelX) {
|
||||
this.config.options.scales.xAxes[0].scaleLabel = {
|
||||
display: true,
|
||||
labelString: opts.labelX,
|
||||
};
|
||||
}
|
||||
if (opts.labelY) {
|
||||
this.config.options.scales.yAxes[0].scaleLabel = {
|
||||
display: true,
|
||||
labelString: opts.labelY,
|
||||
};
|
||||
}
|
||||
if (opts.tickY) {
|
||||
this.config.options.scales.yAxes[0].ticks = {
|
||||
callback: opts.tickY,
|
||||
};
|
||||
}
|
||||
let ctx = $(elem).get(0).getContext('2d');
|
||||
if (opts.height) {
|
||||
ctx.canvas.height = opts.height;
|
||||
}
|
||||
this.chart = new Chart(ctx, this.config);
|
||||
}
|
||||
setData(datasets) {
|
||||
datasets.forEach((ds, i) => {
|
||||
let color = (i < this.colors.length) ? this.colors[i] : this.colors[0];
|
||||
ds.backgroundColor = Chart.helpers.color(color).alpha(0.3).rgbString();
|
||||
ds.borderColor = color;
|
||||
ds.borderWidth = 2;
|
||||
ds.pointRadius = 2;
|
||||
});
|
||||
this.config.data.datasets = datasets;
|
||||
this.chart.update();
|
||||
}
|
||||
}
|
||||
class StatsPage {
|
||||
constructor() {
|
||||
let $cb_time = $("#cb-time");
|
||||
if ($cb_time.length == 0) {
|
||||
return;
|
||||
}
|
||||
$cb_time.change(this.loadData.bind(this));
|
||||
$("#cb-refresh").change(() => {
|
||||
if (this.timer) {
|
||||
clearTimeout(this.timer);
|
||||
this.timer = null;
|
||||
}
|
||||
else {
|
||||
this.refreshData();
|
||||
}
|
||||
});
|
||||
this.cpu = new MetricChart("#canvas-cpu", {
|
||||
tickY: function (value) {
|
||||
return value + '%';
|
||||
},
|
||||
});
|
||||
this.memory = new MetricChart("#canvas-memory", {
|
||||
tickY: function (value) {
|
||||
return value < 1024 ? (value + 'M') : (value / 1024) + 'G';
|
||||
},
|
||||
});
|
||||
this.refreshData();
|
||||
}
|
||||
refreshData() {
|
||||
this.loadData();
|
||||
if ($("#cb-refresh").prop("checked")) {
|
||||
this.timer = setTimeout(this.refreshData.bind(this), 15000);
|
||||
}
|
||||
}
|
||||
loadData() {
|
||||
let time = $("#cb-time").val();
|
||||
$ajax.get(`metrics`, { time: time }).json((d) => {
|
||||
if (d.cpu) {
|
||||
this.cpu.setData(d.cpu);
|
||||
}
|
||||
if (d.memory) {
|
||||
this.memory.setData(d.memory);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
Service.StatsPage = StatsPage;
|
||||
})(Service = Swirl.Service || (Swirl.Service = {}));
|
||||
})(Swirl || (Swirl = {}));
|
||||
//# sourceMappingURL=swirl.js.map
|
File diff suppressed because one or more lines are too long
@ -7,11 +7,7 @@ namespace Swirl.Service {
|
||||
labelX?: string;
|
||||
labelY?: string;
|
||||
tickY?: (value: number) => string;
|
||||
}
|
||||
|
||||
class MetricData {
|
||||
cpu?: Chart.ChartDataSets[];
|
||||
memory?: Chart.ChartDataSets[];
|
||||
// tooltipLabel?: (tooltipItem: Chart.ChartTooltipItem, data: Chart.ChartData) => string | string[];
|
||||
}
|
||||
|
||||
class MetricChart {
|
||||
@ -27,7 +23,7 @@ namespace Swirl.Service {
|
||||
'rgb(201, 203, 207)', // grey
|
||||
];
|
||||
|
||||
constructor(elem: string | Element, opts: MetricChartOptions) {
|
||||
constructor(elem: string | Element | JQuery, opts?: MetricChartOptions) {
|
||||
opts = $.extend(new MetricChartOptions(), opts);
|
||||
this.config = {
|
||||
type: opts.type,
|
||||
@ -59,6 +55,11 @@ namespace Swirl.Service {
|
||||
}],
|
||||
yAxes: [{}]
|
||||
},
|
||||
// tooltips: {
|
||||
// callbacks: {
|
||||
// label: opts.tooltipLabel,
|
||||
// },
|
||||
// }
|
||||
}
|
||||
};
|
||||
if (opts.labelX) {
|
||||
@ -101,8 +102,16 @@ namespace Swirl.Service {
|
||||
}
|
||||
|
||||
export class StatsPage {
|
||||
private cpu: MetricChart;
|
||||
private memory: MetricChart;
|
||||
private chartOptions: { [index: string]: MetricChartOptions } = {
|
||||
"cpu": {tickY: (value: number): string => value + '%'},
|
||||
"memory": {tickY: StatsPage.formatSize},
|
||||
"network_in": {tickY: StatsPage.formatSize},
|
||||
"network_out": {tickY: StatsPage.formatSize},
|
||||
"threads": {},
|
||||
"goroutines": {},
|
||||
"gc_duration": {tickY: (value: number): string => value * 1000 + 'ms'},
|
||||
};
|
||||
private charts: { [index: string]: MetricChart } = {};
|
||||
private timer: number;
|
||||
|
||||
constructor() {
|
||||
@ -121,15 +130,11 @@ namespace Swirl.Service {
|
||||
}
|
||||
});
|
||||
|
||||
this.cpu = new MetricChart("#canvas-cpu", {
|
||||
tickY: function (value: number): string {
|
||||
return value + '%';
|
||||
},
|
||||
});
|
||||
this.memory = new MetricChart("#canvas-memory", {
|
||||
tickY: function (value: number): string {
|
||||
return value < 1024 ? (value + 'M') : (value / 1024) + 'G';
|
||||
},
|
||||
$.each(this.chartOptions, (name, opt) => {
|
||||
let $el = $("#canvas_" + name);
|
||||
if ($el.length > 0) {
|
||||
this.charts[name] = new MetricChart($el, opt);
|
||||
}
|
||||
});
|
||||
this.refreshData();
|
||||
}
|
||||
@ -143,14 +148,25 @@ namespace Swirl.Service {
|
||||
|
||||
private loadData() {
|
||||
let time = $("#cb-time").val();
|
||||
$ajax.get(`metrics`, {time: time}).json((d: MetricData) => {
|
||||
if (d.cpu) {
|
||||
this.cpu.setData(d.cpu);
|
||||
}
|
||||
if (d.memory) {
|
||||
this.memory.setData(d.memory);
|
||||
}
|
||||
$ajax.get(`metrics`, {time: time}).json((d: {[index: string]: Chart.ChartDataSets[]}) => {
|
||||
$.each(this.charts, (name: string, chart: MetricChart) => {
|
||||
if (d[name]) {
|
||||
chart.setData(d[name]);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private static formatSize(size: number): string {
|
||||
if (size < 1024) { // 1K
|
||||
return size.toString() + 'B';
|
||||
} else if (size < 1048576) { // 1M
|
||||
return (size / 1024).toFixed(2) + 'K';
|
||||
} else if (size < 1073741824) { // 1G
|
||||
return (size / 1048576).toFixed(2) + 'M';
|
||||
} else {
|
||||
return (size / 1073741824).toFixed(2) + 'G';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
112
biz/metric.go
Normal file
112
biz/metric.go
Normal file
@ -0,0 +1,112 @@
|
||||
package biz
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/cuigh/auxo/ext/times"
|
||||
"github.com/cuigh/auxo/util/lazy"
|
||||
"github.com/cuigh/swirl/model"
|
||||
pclient "github.com/prometheus/client_golang/api"
|
||||
papi "github.com/prometheus/client_golang/api/prometheus/v1"
|
||||
pmodel "github.com/prometheus/common/model"
|
||||
)
|
||||
|
||||
// Metric return a metric biz instance.
|
||||
var Metric = &metricBiz{
|
||||
api: lazy.Value{
|
||||
New: func() (interface{}, error) {
|
||||
setting, err := Setting.Get()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
client, err := pclient.NewClient(pclient.Config{Address: setting.Metrics.Prometheus})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return papi.NewAPI(client), nil
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
type metricBiz struct {
|
||||
api lazy.Value
|
||||
}
|
||||
|
||||
func (b *metricBiz) GetServiceCharts(service string, categories []string) (charts []model.ChartInfo) {
|
||||
charts = append(charts, model.NewChartInfo("cpu", "CPU", "name", `rate(container_cpu_user_seconds_total{container_label_com_docker_swarm_service_name="%s"}[5m]) * 100`))
|
||||
charts = append(charts, model.NewChartInfo("memory", "Memory", "name", `container_memory_usage_bytes{container_label_com_docker_swarm_service_name="%s"}`))
|
||||
charts = append(charts, model.NewChartInfo("network_in", "Network Receive", "name", `sum(irate(container_network_receive_bytes_total{container_label_com_docker_swarm_service_name="%s"}[5m])) by(name)`))
|
||||
charts = append(charts, model.NewChartInfo("network_out", "Network Send", "name", `sum(irate(container_network_transmit_bytes_total{container_label_com_docker_swarm_service_name="%s"}[5m])) by(name)`))
|
||||
for _, c := range categories {
|
||||
if c == "java" {
|
||||
charts = append(charts, model.NewChartInfo("threads", "Threads", "instance", `jvm_threads_current{service="%s"}`))
|
||||
charts = append(charts, model.NewChartInfo("gc_duration", "GC Duration", "instance", `rate(jvm_gc_collection_seconds_sum{service="%s"}[1m])`))
|
||||
} else if c == "go" {
|
||||
charts = append(charts, model.NewChartInfo("threads", "Threads", "instance", `go_threads{service="%s"}`))
|
||||
charts = append(charts, model.NewChartInfo("goroutines", "Goroutines", "instance", `go_goroutines{service="%s"}`))
|
||||
charts = append(charts, model.NewChartInfo("gc_duration", "GC Duration", "instance", `sum(go_gc_duration_seconds{service="%s"}) by (instance)`))
|
||||
}
|
||||
}
|
||||
for i, c := range charts {
|
||||
charts[i].Query = fmt.Sprintf(c.Query, service)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (b *metricBiz) GetMatrix(query, label string, start, end time.Time) (lines []model.ChartLine, err error) {
|
||||
api, err := b.getAPI()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
period := end.Sub(start)
|
||||
value, err := api.QueryRange(context.Background(), query, papi.Range{
|
||||
Start: start,
|
||||
End: end,
|
||||
Step: b.calcStep(period),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
matrix := value.(pmodel.Matrix)
|
||||
for _, stream := range matrix {
|
||||
line := model.ChartLine{Label: string(stream.Metric[pmodel.LabelName(label)])}
|
||||
for _, v := range stream.Values {
|
||||
p := model.ChartPoint{
|
||||
X: int64(v.Timestamp),
|
||||
Y: float64(v.Value),
|
||||
}
|
||||
line.Data = append(line.Data, p)
|
||||
}
|
||||
lines = append(lines, line)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (b *metricBiz) calcStep(period time.Duration) (step time.Duration) {
|
||||
if period >= times.Day {
|
||||
step = 20 * time.Minute
|
||||
} else if period >= 12*time.Hour {
|
||||
step = 10 * time.Minute
|
||||
} else if period >= 6*time.Hour {
|
||||
step = 5 * time.Minute
|
||||
} else if period >= 3*time.Hour {
|
||||
step = 3 * time.Minute
|
||||
} else {
|
||||
step = time.Minute
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (b *metricBiz) getAPI() (api papi.API, err error) {
|
||||
v, err := b.api.Get()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return v.(papi.API), nil
|
||||
}
|
@ -1,8 +1,6 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
@ -10,16 +8,12 @@ import (
|
||||
"github.com/cuigh/auxo/data"
|
||||
"github.com/cuigh/auxo/data/set"
|
||||
"github.com/cuigh/auxo/errors"
|
||||
"github.com/cuigh/auxo/ext/times"
|
||||
"github.com/cuigh/auxo/net/web"
|
||||
"github.com/cuigh/auxo/util/cast"
|
||||
"github.com/cuigh/swirl/biz"
|
||||
"github.com/cuigh/swirl/biz/docker"
|
||||
"github.com/cuigh/swirl/misc"
|
||||
"github.com/cuigh/swirl/model"
|
||||
"github.com/prometheus/client_golang/api"
|
||||
prometheus "github.com/prometheus/client_golang/api/prometheus/v1"
|
||||
pm "github.com/prometheus/common/model"
|
||||
)
|
||||
|
||||
// ServiceController is a controller of docker service
|
||||
@ -299,112 +293,46 @@ func serviceStats(ctx web.Context) error {
|
||||
return err
|
||||
}
|
||||
|
||||
var charts []model.ChartInfo
|
||||
if setting.Metrics.Prometheus != "" {
|
||||
var categories []string
|
||||
if label := service.Spec.Labels["swirl.metrics"]; label != "" {
|
||||
categories = strings.Split(label, ",")
|
||||
}
|
||||
charts = biz.Metric.GetServiceCharts(name, categories)
|
||||
}
|
||||
|
||||
period := cast.ToDuration(ctx.Q("time"), time.Hour)
|
||||
refresh := cast.ToBool(ctx.Q("refresh"), true)
|
||||
m := newModel(ctx).Set("Service", service).Set("Tasks", tasks).
|
||||
Set("Time", period.String()).Set("Refresh", refresh).Set("Metrics", setting.Metrics.Prometheus != "")
|
||||
m := newModel(ctx).Set("Service", service).Set("Tasks", tasks).Set("Time", period.String()).
|
||||
Set("Refresh", refresh).Set("Charts", charts)
|
||||
return ctx.Render("service/stats", m)
|
||||
}
|
||||
|
||||
// nolint: gocyclo
|
||||
func serviceMetrics(ctx web.Context) error {
|
||||
type chartPoint struct {
|
||||
X int64 `json:"x"`
|
||||
Y float64 `json:"y"`
|
||||
}
|
||||
type chartDataset struct {
|
||||
Label string `json:"label"`
|
||||
Data []chartPoint `json:"data"`
|
||||
}
|
||||
|
||||
name := ctx.P("name")
|
||||
service, _, err := docker.ServiceInspect(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var categories []string
|
||||
if label := service.Spec.Labels["swirl.metrics"]; label != "" {
|
||||
categories = strings.Split(label, ",")
|
||||
}
|
||||
charts := biz.Metric.GetServiceCharts(name, categories)
|
||||
|
||||
period := cast.ToDuration(ctx.Q("time"), time.Hour)
|
||||
var step time.Duration
|
||||
if period >= times.Day {
|
||||
step = 10 * time.Minute
|
||||
} else if period >= 12*time.Hour {
|
||||
step = 5 * time.Minute
|
||||
} else if period >= 6*time.Hour {
|
||||
step = 3 * time.Minute
|
||||
} else if period >= 3*time.Hour {
|
||||
step = 2 * time.Minute
|
||||
} else {
|
||||
step = time.Minute
|
||||
}
|
||||
|
||||
setting, err := biz.Setting.Get()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
client, err := api.NewClient(api.Config{Address: setting.Metrics.Prometheus})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
papi := prometheus.NewAPI(client)
|
||||
|
||||
// cpu
|
||||
query := fmt.Sprintf(`rate(container_cpu_user_seconds_total{container_label_com_docker_swarm_service_name="%s"}[5m]) * 100`, name)
|
||||
end := time.Now()
|
||||
start := end.Add(-period)
|
||||
value, err := papi.QueryRange(context.Background(), query, prometheus.Range{
|
||||
Start: start,
|
||||
End: end,
|
||||
Step: step,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
matrix := value.(pm.Matrix)
|
||||
var cpuDatas []chartDataset
|
||||
for _, stream := range matrix {
|
||||
ds := chartDataset{
|
||||
Label: string(stream.Metric["name"]),
|
||||
}
|
||||
for _, v := range stream.Values {
|
||||
p := chartPoint{
|
||||
X: int64(v.Timestamp),
|
||||
Y: float64(v.Value),
|
||||
}
|
||||
ds.Data = append(ds.Data, p)
|
||||
}
|
||||
cpuDatas = append(cpuDatas, ds)
|
||||
}
|
||||
|
||||
// memory
|
||||
query = fmt.Sprintf(`container_memory_usage_bytes{container_label_com_docker_swarm_service_name="%s"}`, name)
|
||||
value, err = papi.QueryRange(context.Background(), query, prometheus.Range{
|
||||
Start: start,
|
||||
End: end,
|
||||
Step: step,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
matrix = value.(pm.Matrix)
|
||||
var memoryDatas []chartDataset
|
||||
for _, stream := range matrix {
|
||||
ds := chartDataset{
|
||||
Label: string(stream.Metric["name"]),
|
||||
m := data.Map{}
|
||||
for _, c := range charts {
|
||||
matrix, err := biz.Metric.GetMatrix(c.Query, c.Label, start, end)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, v := range stream.Values {
|
||||
p := chartPoint{
|
||||
X: int64(v.Timestamp),
|
||||
Y: float64(v.Value) / 1024 / 1024,
|
||||
}
|
||||
ds.Data = append(ds.Data, p)
|
||||
}
|
||||
memoryDatas = append(memoryDatas, ds)
|
||||
}
|
||||
|
||||
// start time
|
||||
//query = fmt.Sprintf(`container_start_time_seconds{container_label_com_docker_swarm_service_name="%s"}`, name)
|
||||
//value, err = papi.Query(context.Background(), query, end)
|
||||
//scalar := value.(*pm.Scalar)
|
||||
|
||||
m := data.Map{
|
||||
"cpu": cpuDatas,
|
||||
"memory": memoryDatas,
|
||||
m[c.Name] = matrix
|
||||
}
|
||||
return ctx.JSON(m)
|
||||
}
|
||||
|
2
main.go
2
main.go
@ -26,7 +26,7 @@ func main() {
|
||||
misc.BindOptions()
|
||||
|
||||
app.Name = "Swirl"
|
||||
app.Version = "0.6.9"
|
||||
app.Version = "0.7.0"
|
||||
app.Desc = "A web management UI for Docker, focused on swarm cluster"
|
||||
app.Action = func(ctx *app.Context) {
|
||||
misc.LoadOptions()
|
||||
|
@ -33,3 +33,29 @@ type TemplateListArgs struct {
|
||||
PageIndex int `bind:"page"`
|
||||
PageSize int `bind:"size"`
|
||||
}
|
||||
|
||||
type ChartPoint struct {
|
||||
X int64 `json:"x"`
|
||||
Y float64 `json:"y"`
|
||||
}
|
||||
|
||||
type ChartLine struct {
|
||||
Label string `json:"label"`
|
||||
Data []ChartPoint `json:"data"`
|
||||
}
|
||||
|
||||
type ChartInfo struct {
|
||||
Name string `json:"name"`
|
||||
Title string `json:"title"`
|
||||
Label string `json:"label"`
|
||||
Query string `json:"query"`
|
||||
}
|
||||
|
||||
func NewChartInfo(name, title, label, query string) ChartInfo {
|
||||
return ChartInfo{
|
||||
Name: name,
|
||||
Title: title,
|
||||
Label: label,
|
||||
Query: query,
|
||||
}
|
||||
}
|
||||
|
@ -43,7 +43,7 @@
|
||||
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
{{ if .Metrics }}
|
||||
{{ if .Charts }}
|
||||
<nav class="level">
|
||||
<form>
|
||||
<div class="level-left">
|
||||
@ -75,24 +75,16 @@
|
||||
</div>
|
||||
</form>
|
||||
</nav>
|
||||
|
||||
{{ range .Charts }}
|
||||
<div class="block">
|
||||
<div class="block-header">
|
||||
<p>CPU</p>
|
||||
<p>{{ .Title }}</p>
|
||||
</div>
|
||||
<div class="block-body is-bordered">
|
||||
<canvas id="canvas-cpu"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="block">
|
||||
<div class="block-header">
|
||||
<p>Memory</p>
|
||||
</div>
|
||||
<div class="block-body is-bordered">
|
||||
<canvas id="canvas-memory"></canvas>
|
||||
<canvas id="canvas_{{ .Name }}"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
{{ else }}
|
||||
<div class="notification is-info">
|
||||
NOTICE: To enable this feature, you must set <b>Metrics</b> option on <a href="/system/setting/">Setting</a> page first.
|
||||
|
Loading…
Reference in New Issue
Block a user