我用Grafana制作了一个关于东京和大阪新冠病毒每日新增感染者数量的图表

不,与Covid-19几乎无关。这是关于我探索用自己编写的脚本作为数据源来替代Prometheus,并且欺骗Grafana的笔记。

做过的事情

我使用Python/Flask创建的简易Web应用程序被指定为Grafana的数据源。
该Web应用程序扮演了一个伪造的Prometheus角色。

如果数据源是Prometheus的话,Grafana会向Web应用程序发送一系列类似以下的请求,只要对每个请求做出合适的响应,它就会绘制相应的图形。

127.0.0.1 - - [28/Sep/2021 16:36:42] "POST /api/v1/query HTTP/1.1" 200 -
127.0.0.1 - - [28/Sep/2021 16:36:52] "POST /api/v1/labels HTTP/1.1" 200 -
127.0.0.1 - - [28/Sep/2021 16:36:52] "GET /api/v1/label/__name__/values?start=1632825412&end=1632847012 HTTP/1.1" 200 -
127.0.0.1 - - [28/Sep/2021 16:36:52] "GET /api/v1/metadata HTTP/1.1" 200 -
127.0.0.1 - - [28/Sep/2021 16:36:53] "POST /api/v1/labels HTTP/1.1" 200 -
127.0.0.1 - - [28/Sep/2021 16:36:53] "GET /api/v1/label/__name__/values?start=1632825413&end=1632847013 HTTP/1.1" 200 -
127.0.0.1 - - [28/Sep/2021 16:36:53] "GET /api/v1/metadata HTTP/1.1" 200 -
127.0.0.1 - - [28/Sep/2021 16:36:53] "GET /api/v1/label/__name__/values?start=1632825413&end=1632847013 HTTP/1.1" 200 -
127.0.0.1 - - [28/Sep/2021 16:36:55] "POST /api/v1/series HTTP/1.1" 200 -
127.0.0.1 - - [28/Sep/2021 16:37:10] "POST /api/v1/labels HTTP/1.1" 200 -
127.0.0.1 - - [28/Sep/2021 16:37:10] "POST /api/v1/query_exemplars HTTP/1.1" 200 -
127.0.0.1 - - [28/Sep/2021 16:37:10] "GET /api/v1/label/__name__/values?start=1632825430&end=1632847030 HTTP/1.1" 200 -
127.0.0.1 - - [28/Sep/2021 16:37:10] "GET /api/v1/metadata HTTP/1.1" 200 -
127.0.0.1 - - [28/Sep/2021 16:37:19] "POST /api/v1/query_range HTTP/1.1" 200 -
image.png

制作方法

创建一个Ubuntu 20的虚拟机

image.png

2. 安装 Grafana

通过SSH连接并执行以下操作。
(参考)Grafana – 在Debian或Ubuntu上安装
https://grafana.com/docs/grafana/latest/installation/debian/

$ sudo apt-get install -y apt-transport-https
$ sudo apt-get install -y software-properties-common wget
$ wget -q -O - https://packages.grafana.com/gpg.key | sudo apt-key add -
$ echo "deb https://packages.grafana.com/oss/deb stable main" | sudo tee -a /etc/apt/sources.list.d/grafana.list
$ sudo apt-get update
$ sudo apt-get install -y grafana
$ sudo systemctl daemon-reload
$ sudo systemctl start grafana-server
$ sudo systemctl enable grafana-server.service
$ sudo systemctl status grafana-server
image.png

安装Python/Flask

$ sudo apt install -y python3-flask

制作一个“假冒的普罗米修斯”。

请创建一个名为pseudo.py的文件。目录位置和用户都可以随意选择,不必为root。
另外,关于Prometheus的各个API返回的数据类型可以参考以下链接:
(参考)Proetheus – HTTP API
https://prometheus.io/docs/prometheus/latest/querying/api/

只需提供一个选项:

虽然跳过了对各个API返回类似响应的部分的解释,但是’/api/v1/query_range’接口中生成数据的处理大致如下:

获取东京的感染者数列表(CSV文件)。
https://catalog.data.metro.tokyo.lg.jp/dataset/t000010d0000000068/resource/c2d997db-1450-43fa-8037-ebb11ec28d4c
将CSV文件的第五列作为日期,每个新阳性患者对应一条记录,统计同一日期的记录数量。
将统计数据转换为数组形式,并使其适应Grafana的格式。

取得大阪的感染者列表(CSV文件)。由于字符编码是sjis,需要将其转换为utf-8。
https://covid19-osaka.info/
由于此数据已预先按日期汇总,因此将第一列视为日期,第三列视为新增阳性人数,并将其读入数组中。

from flask import Flask
from flask import request
app = Flask(__name__)

@app.route('/api/v1/query', methods=["POST"])
def query():
    return "{}"

@app.route('/api/v1/labels', methods=["POST"])
def labels():
    return '''
{
    "status": "success",
    "data": [
        "__name__",
        "region"
    ]
}
'''

@app.route('/api/v1/label/__name__/values')
def label__name__values():
    return '''
{
    "status": "success",
    "data": [
        "new-cases"
    ]
}
'''

@app.route('/api/v1/label/region/values')
def label_region_values():
    return '''
{
    "status": "success",
    "data": [
        "tokyo", "osaka"
    ]
}
'''

@app.route('/api/v1/metadata')
def metadata():
    return '''
{
    "status": "success",
    "data": [
        "new-cases": [
            {
                "type": "counter",
                "help": "Number of new cases",
                "unit": ""
            }
        ]
    ]
}
'''

@app.route('/api/v1/series', methods=["POST"])
def series():
    return '''
{
    "status": "success",
    "data": [
        {
            "__name__": "new-cases",
            "region": "tokyo"
        },
        {
            "__name__": "new-cases",
            "region": "osaka"
        }
    ]
}
'''

@app.route('/api/v1/query_exemplars', methods=["POST"])
def query_exemplars():
    return '''
{
    "status": "success",
    "data": [
        {
            "seriesLabels": {
            },
            "exemplars": [
            ]
        }
    ]
}
'''


@app.route('/api/v1/query_range', methods=["POST"])
def query_range():
    import json

    tokyo = fetch_tokyo()
    osaka = fetch_osaka()
    return '''
{
    "status": "success",
    "data": {
        "resultType": "matrix",
        "result": [
            {
                "metric": {
                    "__name__": "new-cases",
                    "region": "tokyo"
                },
                "values":
''' + json.dumps(tokyo) + '''
            },
            {
                "metric": {
                    "__name__": "new-cases",
                    "region": "osaka"
                },
                "values":
''' + json.dumps(osaka) + '''
            }
        ]
    }
}
'''

def fetch_tokyo():
    import os
    import csv
    import datetime
    import urllib

    if(not os.path.exists("tokyo.csv")):
        urllib.request.urlretrieve("https://stopcovid19.metro.tokyo.lg.jp/data/130001_tokyo_covid19_patients.csv", "tokyo.csv")

    f = open("tokyo.csv")
    r = csv.reader(f)
    h = next(r)
    d = {}
    for l in r:
        t = datetime.datetime.strptime(l[4], "%Y-%m-%d")
        if(t not in d):
            d[t] = 0
        d[t] = d[t] + 1
    a = []
    for k in d:
        a.append([k.timestamp(), d[k]])
    return a

def fetch_osaka():
    import os
    import csv
    import datetime
    import urllib
    import subprocess

    if(not os.path.exists("osaka-u.csv")):
        urllib.request.urlretrieve("https://covid19-osaka.info/data/summary.csv", "osaka.csv")
        subprocess.run("iconv -f sjis -t utf8 osaka.csv -o osaka-u.csv", shell=True)

    f = open("osaka-u.csv")
    r = csv.reader(f)
    h = next(r)
    a = []
    for l in r:
        t = datetime.datetime.strptime(l[0], "%Y-%m-%d")
        a.append([t.timestamp(), l[2]])
    return a

启动玩笑模式

在9090端口上,先前创建的假Prometheus进行启动。
本地的Grafana只是用于读取数据,所以无需开放防火墙端口。

# FLASK_APP=pseudo.py flask run --port=9090

6. 设置Grafana的数据源

选择使用Prometheus作为数据源的类型,并指定从“http://localhost:9090”读取。

image.png
image.png
image.png

制作仪表板

应该能够看到作为指标的“新增病例”,然后将其呈现在图表上。

image.png
image.png
image.png
image.png
image.png
image.png
image.png
image.png

最后

希望Grafana作为通用BI工具的应用范围更广,但不知道怎么样… 比我想的更贵更麻烦。
希望Prometheus能更轻便,更方便使用。

bannerAds