Grafana 插件开发从零到一

阅前高能提醒:这篇文章总的来说并不是在教你怎么开发,而是告诉你怎么去学习开发一个 Grafana 的插件,说是从零到一,MAX_VALUE 其实是 100,望珍重。

引子

前一阵子突然接到了一个新任务:开发一个 Grafana 的 Datasource——差不多是这个表情。

15311431794605.jpg
作为一个新时代的好码畜,上一次配置 Grafana 面板的时候,含着热泪抱着大佬的大腿,在几乎完全是大佬输入的情况下完成了面板——我连面板都不会配你竟然让我开发?What,我没听错吧。

在此之前,我们为服务添加监控除了已经注入到满足条件的数据库后使用已有数据源配置,还有的就是 mock 请求映射到数据源,但是这样有太多不可变操作,如果我们能自己写个 datasource 岂不是美滋滋,一把梭。

开车

当然,我相信我艰苦卓绝的学习能力,比如这篇文章屁都没讲我已经水了 300 字了。

我们先了解到了相似的产品 azure-monitor-datasource,证明了我们方向的可行性,为了明白整个 datasource 应该是怎么运作的,以及配置界面大概长什么样,于是我特地注册了一个 Azure 账号并且配置了一波,幸运的是,大家只要看我的截图就行了:

2018-07-09 at 21.51.png
15311443644720.jpg

但是相比其他 datasource 或者 plugin 的代码,Azure 的显得相当体积庞大,因此我接触了大概一周的时间之后最终放弃了从 Azure 的角度出发去修改成一个我需要的 Datasource,因为代码实在忒复杂了……而且更主要的是,我对于 Azure 的 API 并不熟悉,就更增加了阅读成本。

所以很快的,我换了一个思路,当然,在换思路前,先熟悉一下项目结构:

用 karma 进行测试,使用 TypeScript 和 Grunt,Template 语法为 AngularJS 1.x,同时需要一个 grafana-sdk-mocks。

大概是这样,除了测试的部分,之后都会做一一的说明——总之我们看了这些上个时代的道具,掐指一算,这车大概是开不起来了,只能换换单车。

此外,还有一个问题:

Grunt 能不能换成别的?TypeScript 能不能换成 JavaScript?AngularJS 能不能换成更高版本的 Angular 或者使用其他框架?

前两个的答案是可以,不过作为非配置工程师,懒得折腾这么多,照抄配置一把梭,至于 AngularJS 1.x,涉及到了整个 Grafana 的 View 层问题,在某个论坛看到关于能不能升级的提议,大致是:「你们能不能升级到 Angular 6 啊?」「我们还在升级 Angular 4 的路上」——总之,路漫漫其修远兮,一句话就是,我们现在还得用 AngularJS 来搞定 Template。

单车

截至目前,我们已经知道「我们要使用哪些技术」、「完成品大概会有哪些配置可供选择」两个问题了,下一个问题就是:到底如何开发呢?

为了方便阅读,这回换了两个简单的项目:

这两个项目唯一的区别,是它们一个使用了 TypeScript,一个使用了 JavaScript。

接下来,我们就要蹬着我们的脚踏车开始崭新的开发之旅了。

环境配置与概览

水了几百字,你才猛然发现,雾草,环境都没搭起来——现在开始我们的表演:Installing on Mac

如果是 brew 安装的,默认的配置在 /usr/local/var/lib/grafana/plugins,之后我们为了便于预览,将项目的文件夹直接创建在这里,根据规范命名为 grafana-[yourname]-datasource。

照抄 typescript-template-datasource 初始化你的项目,并且 npm run watch 尝试一下,如果能跑起来,那么证明抄的没啥问题。

最简单的 Datasource 主要有以下模块,这里引用一下掘金的文章中的一张图:

15312298736748.jpg

从中我们可以非常清楚的看出各模块的作用,方便之后的开发。

Plugin.json

mainfest.jsonpackage.jsonconfig.json 之类的配置文件作用一样,Plugin.json 用来配置一些基本属性,基本上,typescript-template-datasource 已经填完了,你需要的只是按需修改和删减。官方文档描述的非常详细。

对于 datasource 而言,在 Plugin JSON 中还多了一项:

"metrics": true,
"annotations": false

用来选定是否支持 metrics 和 annotations,其中至少有一项需要为 true

其他文件

module.ts:可以看做是一个插件的入口文件,规定了 Datasource 和 Controllers 的值。

datasource.ts:一个插件的核心,用来系统会调用 query() 方法发起请求。

datasource 需要实现以下函数,具体之后会做介绍:

query(options) //used by panels to get data
testDatasource() //used by datasource configuration page to make sure the connection is working
annotationQuery(options) // used by dashboards to get annotations
metricFindQuery(options) // used by query editor to get metric suggestions.

config_ctrl.ts:负责配置一些额外的信息,基本上不填也可以。我们第一张截图的 settings 由此开始,是 settings 中的 Controller。

query_ctrl.ts:负责规定查询内容,也就是我们第二张截图中的内容。

在 partials 目录中有对应的各个 Controller 的 View 文件。在 Controller 内部用 static templateUrl = 'partials/query.editor.html' 来指定。

扬帆起航

终于,差不多可以开始一把梭了。

之后,除了上面两个 demo,你可能还需要寻找一些别的 datasource 作为备选答案方便「抄」。

配置页面

我建议首先从最简单粗暴的 Config Controller 入手,它即使一个 DataSource 的原点,同时又比较简单。

Controller 配合 View 使用,主要负责变量的注入,这些变量日后可以在 datasource.ts 中获取到,实际上,你在每个 Controller 中注入的变量,最终都会汇聚于 datasource.ts 的类中。

尽管现在我们还是不会 AngularJS,但我们可以抄,很快就可以抄出 dropdown 和 input 框了(此处建议抄 azure-monitor-datasource)。因为他的配置页使用了丰富的控件类型:

<gf-form-dropdown model="ctrl.target.target"
  allow-custom="true"
  lookup-text="true"
  get-options="ctrl.getOptions($query)"
  on-change="ctrl.onChangeInternal()">
</gf-form-dropdown>
<input class="gf-form-input width-30" type="text" ng-model='ctrl.current.jsonData.subscriptionId' placeholder="XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"></input>

Controller 中没有什么需要做多余的处理,不过在保存时,会触发 datasource.ts 中的 testDatasource() 方法,如果你需要校验配置是否合法,那么需要对这个部分进行配置,否则返回 true 即可,比如在 simple-json-datasource 中,利用 请求来校验是否正确。

注意,testDatasource 的返回值可以是 any 或者 Promise<any>

testDatasource() {
    return this.doRequest({
      url: this.url + '/',
      method: 'GET',
    }).then(response => {
      if (response.status === 200) {
        return { status: "success", message: "Data source is working", title: "Success" };
      }
});

返回值中的 messagetitle 自然不用说,status 其实是个枚举值,除了 success 以外,还有 warningerror——别问我怎么知道的,碰巧猜出来的。

就这样,我们实际上已经完成了一个 datasource 的设置页面。

Metrics 配置信息栏

之后,轮到 Query Controller 来解决配置「我需要怎么样的图表」的问题了,这着实比配置 Datasource 要复杂了不少,不过没关系,我们抄起来,依旧是一把梭。

首先,和 Config Controller 一样,从 View 开始抄起,简单的话我们只需要一个 Dropdown 或者 input,复杂的时候我们可能需要用到 ng-if 之类的,这个就看抄法了,实在不行你也只能拿起 AngularJS 1.x 的文档啃起来——不过这实在太可怕了。

在 Controller 中基本和 Config 的一致,如果你需要在变更的同时对资源进行更新,在 Config 中可能没什么必要,但在 Panel 中就比较有用了,可以使用以下方法来更新你的 Panel:

onChangeInternal() {
  this.panelCtrl.refresh(); // Asks the panel to refresh data.
}

datasource.ts 中,每次获取数据,都会调用一次 query() 方法,options 中会有我们传入的内容,比如:

{
  "range": { "from": "2015-12-22T03:06:13.851Z", "to": "2015-12-22T06:48:24.137Z" },
  "interval": "5s",
  "targets": [
    { "refId": "B", "target": "upper_75" },
    { "refId": "A", "target": "upper_90" }
  ],
  "format": "json",
  "maxDataPoints": 2495 //decided by the panel
}

这里我们传入的时间跨度在 range 中,时间间隔为 5s,传入的参数在 targets 数组中(这里用 dropdown 选择了 upper_75,又创建了一个选择了 upper_90),注意,即使是只有一个 target,依旧会创建一个数组,所以我们要用遍历数组的形式来处理输入。

我们通常都使用 series 作为显示屏(也就是折线图),另一种是 table,目前只有 InfluxDB 支持。

对于 Time Series,我们的标准返回值是:

[
  {
    "target":"upper_75",
    "datapoints":[
      [622, 1450754160000],
      [365, 1450754220000]
    ]
  },
  {
    "target":"upper_90",
    "datapoints":[
      [861, 1450754160000],
      [767, 1450754220000]
    ]
  }
]

对于 table,我们的标准返回值是:

[
  {
    "columns": [
      {
        "text": "Time",
        "type": "time",
        "sort": true,
        "desc": true,
      },
      {
        "text": "mean",
      },
      {
        "text": "sum",
      }
    ],
    "rows": [
      [
        1457425380000,
        null,
        null
      ],
      [
        1457425370000,
        1002.76215352,
        1002.76215352
      ],
    ],
    "type": "table"
  }
]

也就是说,query() 函数必须要满足所对应的格式才可以被看做是正确的输出,图像才会出现在图表中。

对于通过不同的条件查询不同的内容并且格式化,相信大家各有各的方法,这里就不在多做介绍。

针对变量的查询

我们在很多项目中都会看到左上角有个下拉框,这是怎么做的——答案是 Config - Variables。可是要变量随我们的心意创建,也需要做一番工作,这涉及到了 datasource.ts 中的 metricFindQuery()

在变量的查询中,会执行这个函数,和 query() 一样,我们只要根据需要返回正确的结果就行了。

QA 死在沙滩上

看完上面的,大概你觉得毫无难度,全靠一个抄字——不,骚气的现在才开始。

第一,面对 npm 编程惯了,为什么一使用 npm 包就花式报错,而 lodash / moment / q / systemjs 就跑的安然无恙——因为这是人家底层内置的几个库,其他统统没有,如果要引入,简单,把 JS 文件拷过来啊。实际上阿里云 Log Datasource 就是这么做的,看了让我不禁恶从胆边生,当然,如果是开发时使用的 dev-dependenies,请放心食用。

第二,为什么明明用了 TypeScript,我想要的特性还是不支持——使用 TypeScript,请遵循基本法,grunt-typescript 支持哪个版本就用哪个版本,如果不支持,建议更换到 grunt-ts 或者自己撸配置,这样可以更快的解决你的问题。

第三,我还有一些加密数据需要处理,最简单粗暴的方法当然是把这些数据转移到后端封装好,当然在写作时我发现了一个新方法,可见文档的 Password Security 一节,如果使用 secureJsonData,你的数据将被妥善的处理,否则的话就会凉凉的被暴露于 window 的 Grafana 配置信息中。

总结

事实证明,一个好的文档非常的重要,Grafana 的开发文档实在写的太玄学了,加上前端也忒老旧了,官网的链接竟然还有 404……开发门槛完全堆砌在「学会抄」这一步上,在搜了很多资料都找不到想要的内容,排障困难的情况下,终于还是写了一篇来告诉大家怎么抄——每个开发 Grafana Plugin 的上辈子的都是折翼的天使。

抄袭一把梭的项目仓库总结

GitHub 搜索 grafana datasource,可以找到更多可以抄的仓库。

衷心感谢

以下文章可以说是指路的明灯了,具有举足轻重的地位:

植入部分

如果您觉得文章不错,可以通过赞助支持我。

如果您不希望打赏,也可以通过关闭广告屏蔽插件的形式帮助网站运作。

标签: Grafana, 插件

已有 2 条评论

  1. yovn

    笑着看完这篇文,想问下,一把梭是个啥子哟???

  2. yovn

    另外,想请教一下,我用simplejson来显示一些非时序的数据,但是怎么让simplejson搭配面板上的变量一起使用呢?

添加新评论