用ECharts绘制Prometheus图表,实现类似Grafana的自定义Dashboard

  大家一般都是用Grafana自定义Dashboard来监控Prometheus数据的,作者这次尝试用ECharts来绘制Prometheus数据图表,一方面可以减少依赖,另一方面可以将监控界面灵活的集成进应用系统。至于如何在被监测机器上安装NodeExporter以及如何部署Prometheus作者就不描述了,园子里有很多文章介绍。

一、数据查询及转换

  Prometheus提供了Http Api来执行promql查询,但需要将返回的数据格式转换为ECharts的格式,好在EChars的xAxis.type可以设置为'time'类型,与Prometheus返回的格式接近。作者写了个简单的服务来执行查询及转换数据,详见以下代码:

public class MetricService
{
    private static readonly HttpClient http = new HttpClient()
    {
        //请修改指向Prometheus地址
        BaseAddress = new Uri("http://10.211.55.2:9090/api/v1/"),
        Timeout = TimeSpan.FromSeconds(2)
    };

    public async Task<object> GetCpuUsages(string node, DateTime start, DateTime end)
    {
        var promql = $"100-irate(node_cpu{{instance='{node}:9100',mode='idle'}}[5m])*100";
        return await QueryRange(promql, start, end, 20, 2);
    }

    public async Task<object> GetMemUsages(string node, DateTime start, DateTime end)
    {
        var promql = $"(1-(node_memory_MemAvailable{{instance='{node}:9100'}}/(node_memory_MemTotal{{instance='{node}:9100'}})))*100";
        return await QueryRange(promql, start, end, 20, 2);
    }

    public async Task<object> GetNetTraffic(string node, DateTime start, DateTime end)
    {
        var downql = $"irate(node_network_receive_bytes{{instance='{node}:9100',device!~'tap.*|veth.*|br.*|docker.*|virbr*|lo*'}}[5m])";
        var ls = await QueryRange(downql, start, end, 15/*4*/, 0);
        var upql = $"irate(node_network_transmit_bytes{{instance='{node}:9100',device!~'tap.*|veth.*|br.*|docker.*|virbr*|lo*'}}[5m])";
        ls.Add(await QueryRange(upql, start, end, 15/*4*/, 0));
        return ls;
    }

    public async Task<object> GetDiskIO(string node, DateTime start, DateTime end)
    {
        var readql = $"irate(node_disk_bytes_read{{instance='{node}:9100'}}[1m])";
        var ls = await QueryRange(readql, start, end, 15/*10*/, 0);
        var writeql = $"irate(node_disk_bytes_written{{instance='{node}:9100'}}[1m])";
        ls.Add(await QueryRange(writeql, start, end, 15/*10*/, 0));
        return ls;
    }

    #region ====Parse PromQL====
    private static async Task<List<object>> QueryRange(string promql, DateTime start, DateTime end, int step, int round)
    {
        if (start >= end) throw new ArgumentOutOfRangeException();
        var ts1 = (int)(start.ToUniversalTime() - DateTime.UnixEpoch).TotalSeconds;
        var ts2 = (int)(end.ToUniversalTime() - DateTime.UnixEpoch).TotalSeconds;
        var res = await http.GetAsync($"query_range?query={promql}&start={ts1}&end={ts2}&step={step}s");
        var stream = await res.Content.ReadAsStreamAsync();
        using (var sr = new System.IO.StreamReader(stream))
        using (var jr = new JsonTextReader(sr))
        {
            return ParseToSeries(jr, round);
        }
    }

    private static List<object> ParseToSeries(JsonTextReader jr, int round)
    {
        if (!jr.Read() || jr.TokenType != JsonToken.StartObject) throw new Exception();
        if (!jr.Read() || jr.TokenType != JsonToken.PropertyName || (string)jr.Value != "status")
            throw new Exception();
        var status = jr.ReadAsString();
        if (status != "success") throw new Exception();
        if (!jr.Read() || jr.TokenType != JsonToken.PropertyName || (string)jr.Value != "data")
            throw new Exception();

        if (!jr.Read() || jr.TokenType != JsonToken.StartObject) throw new Exception();
        if (!jr.Read() || jr.TokenType != JsonToken.PropertyName || (string)jr.Value != "resultType")
            throw new Exception();
        var resultType = jr.ReadAsString();
        if (!jr.Read() || jr.TokenType != JsonToken.PropertyName || (string)jr.Value != "result")
            throw new Exception();

        return ReadResultArray(jr, round);
        //No need read others
    }

    private static List<object> ReadResultArray(JsonTextReader jr, int round)
    {
        if (!jr.Read() || jr.TokenType != JsonToken.StartArray) throw new Exception();

        var ls = new List<object>();
        do
        {
            if (!jr.Read()) throw new Exception();
            if (jr.TokenType == JsonToken.EndArray) break;
            if (jr.TokenType != JsonToken.StartObject) throw new Exception();
            ls.Add(ReadResultItem(jr, round));
        } while (true);
        return ls;
    }

    private static List<double[]> ReadResultItem(JsonTextReader jr, int round)
    {
        //已读取StartObject标记
        if (!jr.Read() || jr.TokenType != JsonToken.PropertyName || (string)jr.Value != "metric")
            throw new Exception();
        ReadMetric(jr);

        if (!jr.Read() || jr.TokenType != JsonToken.PropertyName || (string)jr.Value != "values")
            throw new Exception();
        var values = ReadValues(jr, round);
        if (!jr.Read() || jr.TokenType != JsonToken.EndObject) throw new Exception();
        return values;
    }

    private static void ReadMetric(JsonTextReader jr)
    {
        if (!jr.Read() || jr.TokenType != JsonToken.StartObject) throw new Exception();
        do
        {
            //PropertyName or EndObject
            if (!jr.Read()) throw new Exception();
            if (jr.TokenType == JsonToken.EndObject) return;
            //PropertyValue
            jr.Read();
        } while (true);
    }

    private static List<double[]> ReadValues(JsonTextReader jr, int round)
    {
        if (!jr.Read() || jr.TokenType != JsonToken.StartArray) throw new Exception();

        var ls = new List<double[]>();
        do
        {
            if (!jr.Read()) throw new Exception();
            if (jr.TokenType == JsonToken.EndArray) break;
            if (jr.TokenType != JsonToken.StartArray) throw new Exception();
            var ts = jr.ReadAsDouble().Value * 1000; //PromQL时间*1000
            var value = Math.Round(double.Parse(jr.ReadAsString()), round, MidpointRounding.ToEven); //PromQL值为字符串
            ls.Add(new double[] { ts, value });
            if (!jr.Read() || jr.TokenType != JsonToken.EndArray) throw new Exception();
        } while (true);
        return ls;
    }
    #endregion

}

Tip: promql的写法可参考grafana网站相关Dashboard。

二、单指标Vue组件

  作者使用Vue-ECharts作为ECharts的包装,以CPU使用率Vue组件为例:

<v-chart theme="dark" autoresize :options="chartOptions" style="height:250px">
</v-chart>
@Component
export default class CpuUsages extends Vue {
    /** 目标实例IP */
    @Prop({ type: String, default: '10.211.55.3' }) node
    /** 开始时间 */
    @Prop({ type: Date, default: () => { var now = new Date(); return new Date(now.getFullYear(), now.getMonth(), now.getDate()) } }) start
    /** 结束时间 */
    @Prop({ type: Date, default: () => { return new Date() } }) end

    chartOptions = {
        title: { text: 'Cpu Usages', x: 'center' },
        tooltip: { trigger: 'axis' },
        xAxis: { type: 'time' },
        yAxis: { min: 0, max: 100 },
        series: []
    }

    refresh() {
        sys.Services.MetricService.GetCpuUsages(this.node, this.start, this.end).then(res => {
                this.chartOptions.series.splice(0)
                for (var i = 0; i < res.length; ++i) {
                    var seria = { type: 'line', name: 'cpu' + i, data: res[i], showSymbol: false }
                    this.chartOptions.series.push(seria)
                }
            }).catch(err => {
                this.$message(err)
            })
    }

    mounted() {
        this.refresh()
    }
}

三、组合多个组件形成Dashboard

  根据需要可以灵活组合多个指标组件,形成相应的Dashboard界面(如下图所示)。
4928-20190730113112461-594707404.png

四、小结

  感谢Vue、ECharts、Vue-ECharts、Prometheus等项目,使得开发并集成监控Dashboard如此简单。另码文不易,码技术文更不易,所以请您多多推荐!

转载于:https://www.cnblogs.com/BaiCai/p/11269020.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Prometheus是一个开源的监控系统,主要用于收集、存储和查询服务的度量数据。它特别适合处理服务的自我监控,通过HTTP客户端发送指标到Prometheus服务器。当涉及到微服务架构时,每个独立的服务可以将其自身的性能数据暴露给Prometheus,这通常是通过设置相应的端点,如`/metrics`。 为了整合微服务,首先需要在每个微服务中启用Prometheus Exporter,这是一个软件组件,可以将内部的状态信息转换成Prometheus可以理解的格式。例如常见的有Node Exporter(用于监控操作系统资源)和Service Discovery Exporter(管理服务发现过程)。 在Prometheus配置文件中,你可以添加对应微服务的URL到目标列表(targets),这样Prometheus就会定期抓取这些服务的数据。Prometheus会自动发现并跟踪它们的可用性和状态。 Grafana是一个流行的可视化平台,它可以连接到多种数据源,包括Prometheus。通过在Grafana上创建一个新的数据源,并选择Prometheus作为源,你可以开始构建仪表板,展示来自各个微服务的实时监控图表和警报。 总的来说,流程如下: 1. **配置微服务**: 配置Prometheus Exporter,让服务提供指标。 2. **配置Prometheus**: 添加微服务到Prometheus的目标列表。 3. **数据推送与消费**: Prometheus收集数据并存储。 4. **Grafana集成**: 创建Grafana连接到Prometheus,生成可视化的仪表盘。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值