mirror of
https://github.com/cuigh/swirl
synced 2025-01-01 00:32:09 +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 = {}));
|
})(Service = Swirl.Service || (Swirl.Service = {}));
|
||||||
})(Swirl || (Swirl = {}));
|
})(Swirl || (Swirl = {}));
|
||||||
var 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) {
|
(function (Swirl) {
|
||||||
var Service;
|
var Service;
|
||||||
(function (Service) {
|
(function (Service) {
|
||||||
@ -2155,137 +2305,4 @@ var Swirl;
|
|||||||
Volume.NewPage = NewPage;
|
Volume.NewPage = NewPage;
|
||||||
})(Volume = Swirl.Volume || (Swirl.Volume = {}));
|
})(Volume = Swirl.Volume || (Swirl.Volume = {}));
|
||||||
})(Swirl || (Swirl = {}));
|
})(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
|
//# sourceMappingURL=swirl.js.map
|
File diff suppressed because one or more lines are too long
@ -7,11 +7,7 @@ namespace Swirl.Service {
|
|||||||
labelX?: string;
|
labelX?: string;
|
||||||
labelY?: string;
|
labelY?: string;
|
||||||
tickY?: (value: number) => string;
|
tickY?: (value: number) => string;
|
||||||
}
|
// tooltipLabel?: (tooltipItem: Chart.ChartTooltipItem, data: Chart.ChartData) => string | string[];
|
||||||
|
|
||||||
class MetricData {
|
|
||||||
cpu?: Chart.ChartDataSets[];
|
|
||||||
memory?: Chart.ChartDataSets[];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class MetricChart {
|
class MetricChart {
|
||||||
@ -27,7 +23,7 @@ namespace Swirl.Service {
|
|||||||
'rgb(201, 203, 207)', // grey
|
'rgb(201, 203, 207)', // grey
|
||||||
];
|
];
|
||||||
|
|
||||||
constructor(elem: string | Element, opts: MetricChartOptions) {
|
constructor(elem: string | Element | JQuery, opts?: MetricChartOptions) {
|
||||||
opts = $.extend(new MetricChartOptions(), opts);
|
opts = $.extend(new MetricChartOptions(), opts);
|
||||||
this.config = {
|
this.config = {
|
||||||
type: opts.type,
|
type: opts.type,
|
||||||
@ -59,6 +55,11 @@ namespace Swirl.Service {
|
|||||||
}],
|
}],
|
||||||
yAxes: [{}]
|
yAxes: [{}]
|
||||||
},
|
},
|
||||||
|
// tooltips: {
|
||||||
|
// callbacks: {
|
||||||
|
// label: opts.tooltipLabel,
|
||||||
|
// },
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
if (opts.labelX) {
|
if (opts.labelX) {
|
||||||
@ -101,8 +102,16 @@ namespace Swirl.Service {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class StatsPage {
|
export class StatsPage {
|
||||||
private cpu: MetricChart;
|
private chartOptions: { [index: string]: MetricChartOptions } = {
|
||||||
private memory: MetricChart;
|
"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;
|
private timer: number;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
@ -121,15 +130,11 @@ namespace Swirl.Service {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.cpu = new MetricChart("#canvas-cpu", {
|
$.each(this.chartOptions, (name, opt) => {
|
||||||
tickY: function (value: number): string {
|
let $el = $("#canvas_" + name);
|
||||||
return value + '%';
|
if ($el.length > 0) {
|
||||||
},
|
this.charts[name] = new MetricChart($el, opt);
|
||||||
});
|
}
|
||||||
this.memory = new MetricChart("#canvas-memory", {
|
|
||||||
tickY: function (value: number): string {
|
|
||||||
return value < 1024 ? (value + 'M') : (value / 1024) + 'G';
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
this.refreshData();
|
this.refreshData();
|
||||||
}
|
}
|
||||||
@ -143,14 +148,25 @@ namespace Swirl.Service {
|
|||||||
|
|
||||||
private loadData() {
|
private loadData() {
|
||||||
let time = $("#cb-time").val();
|
let time = $("#cb-time").val();
|
||||||
$ajax.get(`metrics`, {time: time}).json((d: MetricData) => {
|
$ajax.get(`metrics`, {time: time}).json((d: {[index: string]: Chart.ChartDataSets[]}) => {
|
||||||
if (d.cpu) {
|
$.each(this.charts, (name: string, chart: MetricChart) => {
|
||||||
this.cpu.setData(d.cpu);
|
if (d[name]) {
|
||||||
}
|
chart.setData(d[name]);
|
||||||
if (d.memory) {
|
|
||||||
this.memory.setData(d.memory);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
package controller
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@ -10,16 +8,12 @@ import (
|
|||||||
"github.com/cuigh/auxo/data"
|
"github.com/cuigh/auxo/data"
|
||||||
"github.com/cuigh/auxo/data/set"
|
"github.com/cuigh/auxo/data/set"
|
||||||
"github.com/cuigh/auxo/errors"
|
"github.com/cuigh/auxo/errors"
|
||||||
"github.com/cuigh/auxo/ext/times"
|
|
||||||
"github.com/cuigh/auxo/net/web"
|
"github.com/cuigh/auxo/net/web"
|
||||||
"github.com/cuigh/auxo/util/cast"
|
"github.com/cuigh/auxo/util/cast"
|
||||||
"github.com/cuigh/swirl/biz"
|
"github.com/cuigh/swirl/biz"
|
||||||
"github.com/cuigh/swirl/biz/docker"
|
"github.com/cuigh/swirl/biz/docker"
|
||||||
"github.com/cuigh/swirl/misc"
|
"github.com/cuigh/swirl/misc"
|
||||||
"github.com/cuigh/swirl/model"
|
"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
|
// ServiceController is a controller of docker service
|
||||||
@ -299,112 +293,46 @@ func serviceStats(ctx web.Context) error {
|
|||||||
return err
|
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)
|
period := cast.ToDuration(ctx.Q("time"), time.Hour)
|
||||||
refresh := cast.ToBool(ctx.Q("refresh"), true)
|
refresh := cast.ToBool(ctx.Q("refresh"), true)
|
||||||
m := newModel(ctx).Set("Service", service).Set("Tasks", tasks).
|
m := newModel(ctx).Set("Service", service).Set("Tasks", tasks).Set("Time", period.String()).
|
||||||
Set("Time", period.String()).Set("Refresh", refresh).Set("Metrics", setting.Metrics.Prometheus != "")
|
Set("Refresh", refresh).Set("Charts", charts)
|
||||||
return ctx.Render("service/stats", m)
|
return ctx.Render("service/stats", m)
|
||||||
}
|
}
|
||||||
|
|
||||||
// nolint: gocyclo
|
|
||||||
func serviceMetrics(ctx web.Context) error {
|
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")
|
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)
|
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()
|
end := time.Now()
|
||||||
start := end.Add(-period)
|
start := end.Add(-period)
|
||||||
value, err := papi.QueryRange(context.Background(), query, prometheus.Range{
|
|
||||||
Start: start,
|
m := data.Map{}
|
||||||
End: end,
|
for _, c := range charts {
|
||||||
Step: step,
|
matrix, err := biz.Metric.GetMatrix(c.Query, c.Label, start, end)
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
matrix := value.(pm.Matrix)
|
m[c.Name] = 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"]),
|
|
||||||
}
|
|
||||||
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,
|
|
||||||
}
|
}
|
||||||
return ctx.JSON(m)
|
return ctx.JSON(m)
|
||||||
}
|
}
|
||||||
|
2
main.go
2
main.go
@ -26,7 +26,7 @@ func main() {
|
|||||||
misc.BindOptions()
|
misc.BindOptions()
|
||||||
|
|
||||||
app.Name = "Swirl"
|
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.Desc = "A web management UI for Docker, focused on swarm cluster"
|
||||||
app.Action = func(ctx *app.Context) {
|
app.Action = func(ctx *app.Context) {
|
||||||
misc.LoadOptions()
|
misc.LoadOptions()
|
||||||
|
@ -33,3 +33,29 @@ type TemplateListArgs struct {
|
|||||||
PageIndex int `bind:"page"`
|
PageIndex int `bind:"page"`
|
||||||
PageSize int `bind:"size"`
|
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">
|
<section class="section">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
{{ if .Metrics }}
|
{{ if .Charts }}
|
||||||
<nav class="level">
|
<nav class="level">
|
||||||
<form>
|
<form>
|
||||||
<div class="level-left">
|
<div class="level-left">
|
||||||
@ -75,24 +75,16 @@
|
|||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</nav>
|
</nav>
|
||||||
|
{{ range .Charts }}
|
||||||
<div class="block">
|
<div class="block">
|
||||||
<div class="block-header">
|
<div class="block-header">
|
||||||
<p>CPU</p>
|
<p>{{ .Title }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="block-body is-bordered">
|
<div class="block-body is-bordered">
|
||||||
<canvas id="canvas-cpu"></canvas>
|
<canvas id="canvas_{{ .Name }}"></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>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{{ end }}
|
||||||
{{ else }}
|
{{ else }}
|
||||||
<div class="notification is-info">
|
<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.
|
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