iT邦幫忙

2024 iThome 鐵人賽

DAY 14
2

https://ithelp.ithome.com.tw/upload/images/20240928/20152073vGa4i3sO3r.png

前言

雖然經由前面的章節已經可以形成一個完整的 Panel 或 Dashboard,但很多時候我們真正使用的資料數據經過一系列的 query filter 還不夠,需要更近一步地運算處理。或是一個圖表中希望疊加不同的資料圖層,顯示更顯眼的特定資料標示。因此 Grafana 也提供了兩組非常實用的 API - SceneDataTransformer 及 SceneDataLayerSet,讓使用者在進行資料呈現時可以有更多元化的自定義。

SceneDataTransformer

在第三章【Grafana 原始資料到可視化的層層遞進】中有介紹到關於 Data source 的 Transform 行為,同樣的我們也可以在 Grafana Scenes 中以程式碼的方式轉換 Data。在 Grafana Scenes 中以 SceneDataTransformer 進行資料轉換,包括篩選、聚合、重組等各種操作,最終輸出可以直接用於圖表渲染的資料格式。

由於 Grafana Scenes 中的各個實例皆繼承了 SceneObject 的屬性,其中包括了 $data 的屬性,因此如果已經在 SceneFlexLayout 或 SceneFlexItem 層設定了 SceneQueryRunner,也可以在 Panel 的 setData 或 $data 中以 SceneDataTransformer 所轉換的資料重新賦值。以下範例:

new SceneFlexItem({
  $data: new SceneQueryRunner({...}),
  body: PanelBuilders.stat()
    .setTitle('Transformed data')
    .setData(new SceneDataTransformer({...}))
    .build(),
}),

或者可以在各個 Panel 中,又或是整個 Scene 皆需使用轉換的資料時, 直接在 $data 以 SceneDataTransformer 中同時設定 $data 和 transformations:

new SceneFlexItem({
  $data: new SceneDataTransformer({
	  $data: new SceneQueryRunner({...}),
	  transformations: [...],
	});,
  body: PanelBuilders.stat()
    .setTitle('Transformed data')
    .build(),
}),

設定方法

Transform 的實現原理是在圖表渲染之前,將 SceneQueryRunner 所返回的數據資料進行各種處理,再將轉換完成的資料送給圖表渲染。可以處理的轉換包括:

  • 重命名欄位
  • 合併時間序列數據資料
  • 跨 Query 進行數學運算,例如 refId A + refId B 的資料
  • 使用一個轉換的輸出作為另一個轉換的輸入

而使用的方式是在 transformations 屬性中為多種的轉換工具做設定,而每個 transformation 皆需要設定 id 和 options 屬性:

transformations: [
	{
	  id: "concatenate",
	  options: {
	    frameNameMode: "label",
	    frameNameLabel: "frame"
	  }
	},
	{
	  id: "calculateField",
	  options: {
	    mode: "binary",
	    reduce: {
	      reducer: "sum"
	    },
	    binary: {
	      left: "Time",
	      right: "web_server_02"
	    },
	    replaceFields: true
	  }
	}
],

💡NOTICE:
每一種 transformation 都有自己獨特的 id,但可選的功能眾多,如果記不起來名稱可以使用 grafana data 中的 DataTransformerID enum 做提示選擇。

範例

以下提供一些簡單的 transform 使用範例,以及眾多的功能中皆無法滿足轉換條件時,Grafana 也提供了客製化的方法,讓使用者自行定義變化的方法:

calculateField

這個功能可以將欄位經過計算後產生新的值,其中提供了幾種模式(mode):reduceRow(計算一行中所有數值的平均值、總和或最大值等)、cumulativeFunctions(用於計算累積值)、windowFunctions(計算移動平均、相對排名等)、binary(將兩個欄位相加、相減或相乘等)、unary(取絕對值、四捨五入等)、index(用於生成序列號或基於某些條件的索引值)。

使用 calculateField 時需要在 options 中選擇一個上述的 mode,並為該模式進行設定:

以下設定為以 binary 模式對 A-series 欄位的值做總和後再將結果 * 2。

transformations: [
  {
    id: 'calculateField',
    options: {
      mode: 'binary',
      binary: {
        left: 'A-series',
        reducer: 'sum',
        operator: '*',
        right: '2',
      },
      // replaceFields: true, // 原始的欄位將被替代
      alias: 'Transformed field', // 新被轉後換的圖表名稱
    },
  },
],

https://ithelp.ithome.com.tw/upload/images/20240928/20152073lUFOS1ZMNu.png

organize

organize 可以用於重組或管理資料欄位,例如重新命名欄位、重新排列欄位、篩選欄位及修改欄位的相關屬性,提供 excludeByName、includeByName、indexByName、renameByName 四個屬性做設定。而設定的方法是將原始欄位放在 key 的位置,並在 value 進行設置:

{
  id: 'organize',
  options: {
    excludeByName: {
      Time: true           // 排除 'Time' 欄位
    },
    indexByName: {
	    'Temp(°C)': 0,       // 'Temp(°C)' 欄位放在第一位
			Humidity: 1          // 'Humidity' 欄位放在第二位
      Location: 2,         // 'Location' 欄位放在第二位
    },
    renameByName: {
      Temperature: 'Temp(°C)'  // 將 'Temperature' 重命名為 'Temp(°C)'
    }
  }
}
Time Temperature Humidity Location
2023-09-01 10:00:00 25.5 60 Office A
2023-09-01 10:05:00 26.0 58 Office B
Temp(°C) Humidity Location
25.5 60 Office A
26.0 58 Office B

客製化 Transformer

Grafana 的自定義轉換(Custom Transformations)可以讓使用者建立自己的資料轉換邏輯,此功能一樣藉由 SceneDataTransformer 實現,並接受 CustomTransformOperator 作為 transformations 陣列的元素。

CustomTransformOperator 是一個返回 RxJS Operator 的函數,用於轉換 DataFrame 數組。這種設計使得開發者能夠利用 RxJS 的功能來處理資料流,實現複雜的數據操作和轉換。自定義轉換可以用於資料清理、進階計算、數據結構重組等。

💡DataFrame
DataFrame 的資料格式包含了 name、fields、length、refId 以及 meta 屬性,資料中所呈現的內容主要存於 fields 中,有多少欄位即有多少的 field,length 則是代表有多少組資料。而每一個 field 有 name、type、config、values 等屬性,在自定義的函數中所要調整的即為 values 的值。

下面範例為將溫度的值由攝氏轉為華氏:

const celsiusToFahrenheit: CustomTransformOperator = () => (source: Observable<DataFrame[]>) => {
  return source.pipe(
    map((data: DataFrame[]) => {
      return data.map((frame: DataFrame) => {
        return {
          ...frame,
          fields: frame.fields.map((field) => {
            if (field.name === 'Temperature') {
              return {
                ...field,
                values: field.values.map((celsius: number) => (celsius * 9) / 5 + 32),
              };
            }
            return field;
          }),
        };
      });
    })
  );
};

SceneDataLayerSet

https://ithelp.ithome.com.tw/upload/images/20240928/20152073DUuQ50ptrW.png

SceneDataLayerSet 用於在 time series 的視覺化圖表中添加註釋層。開發者可以在原本已有資料的 Panel 上標記重要事件、時間段或關鍵數據點。在 Grafana Scenes 中使用 SceneDataLayerSet,而每一層 layer 以 dataLayers.AnnotationsDataLayer 的資料格式自定義註釋的外觀,包括顏色、圖標和標籤,並定義互動的 tooltip 以顯示詳細資料,另外也可以設定 switch 依需求顯示或隱藏圖層。此功能在 Grafana 中稱為 Annotations,Grafana Annotations 的常見用途包括:

  • 標記系統部署或更新時間
  • 顯示重大事件或異常
  • 記錄維護期間
  • 標註業務相關的重要時間點

支援的類型

支援 data layer 的資料型態為可以轉換為 time series 的 data source,而可以呈現 data layer 資料的 Panel 就有所縮限,必須為以下的類型:

  • Time series
  • State timeline
  • Candlestick

設置方法

在 Grafana Scenes 中若要為一個 Panel 設置自定義的 Annotations,首先需要在兩種資料型態,一個是底圖的資料,可以使用 SceneQueryRunner 或 SceneDataTransformer 定義,而顯示註釋的 data layer 則是疊加在底圖上,即為使用 SceneDataLayerSet。一個 Panel 可以疊加多個圖層,設置在 Scenes 應用程式不同層級,其底下的 Panel 皆會取得該 Annotations:

https://ithelp.ithome.com.tw/upload/images/20240928/201520732JeHNmQSqF.png

步驟一

在 SceneFlexLayout 或 SceneFlexItem 層以 SceneDataLayerSet 設置 dataLayers.AnnotationsDataLayer 作為 Annotations 的資料源。以下為各屬性的詳細介紹:

  • name:這個 Annotation 的名稱,通常會顯示於圖層開關的 label 上。

    https://ithelp.ithome.com.tw/upload/images/20240928/20152073OYfjiKM5Te.png

  • isHidden:預設為 false,如果設為 true,則會隱藏圖層開關的操作元件,即無法手動關閉 Annotation 圖層。

  • query:設定所取用的 data source、標籤的樣式屬性,以及標籤 tooltip 的內容細節。

    • tagKeys:為 tooltip 中顯示的 tag,會因不同的 data source 可以取用不同的屬性,範例中的 'namespace,pod' ,表示會取用 namespacepod 的屬性,須以逗號分隔。但其他類型 data source 可能需要在 query 設定 mapping 屬性進行設定,例如:

      https://ithelp.ithome.com.tw/upload/images/20240928/20152073TFjhMYTgYA.png

      mappings: {
        text: {
          source: AnnotationEventFieldSource.Text,
          value: 'Independent annotation',
        },
        tags: {
          source: AnnotationEventFieldSource.Field,
          value: 'text',
        },
      },
      

💡NOTICE
query 中的各種屬性設定會依據不同的 data source 而有非常大的差異,尤其是 query 可取用的變數,這會影響到 tooltip 的 text、tags 以及 title 的顯示。例如下圖的 loki 類型資料,可使用的變數為所有欄位的 label,且格式化中需使用雙括號({{namespace}})取用,可以查看各種 data source 的 Grafana 文件進行設定,或是參考 dashboard 的 Panel JSON 所呈現的格式(如下圖)。
https://ithelp.ithome.com.tw/upload/images/20240928/20152073yj5ZiG3G94.png

new SceneFlexLayout({
  $data: new SceneDataLayerSet({
    layers: [
      new dataLayers.AnnotationsDataLayer({
        name: 'Annotations from logs',
        isHidden: false,
        query: {
          datasource: { uid: 'gdev-loki' },
          enable: true,
          iconColor: 'blue',
          name: 'New annotation',
          expr: '{service_name="apache", cluster="eu-west-1", level="warn"} |= `POST`',
          textFormat: 'namespace: {{namespace}} in pod: {{pod}}',
          tagKeys: 'namespace,pod',
          titleFormat: '{{service_name}} - {{namespace}}',
        },
      }),
    ],
  }),
  children: [...],
}),

步驟二

將底層的資料來源設置在可以設置 $data 的最底 SceneObject,例如 Panel 中:

new SceneFlexLayout({
  $data: new SceneDataLayerSet({
    layers: [...],
  }),
  children: [    
	  new SceneFlexItem({
      body: new VizPanel({
        $data: new SceneQueryRunner({
          datasource: { uid: 'gdev-prometheus' },
          queries: [
            {
              refId: 'X',
              expr: 'rate(prometheus_http_request_duration_seconds_count[5m])',
              legendFormat: '__auto',
              maxDataPoints: 500,
              range: true,
            },
          ],
        }),
        title: 'Annotations from logs',
        pluginId: 'timeseries',
      }),
    }),
  ],
}),

步驟三

設置可以操控圖層開關的元件。如果 dataLayers.AnnotationsDataLayer 中的 isHidden 設定為 false 則有一個 switch 元件可進行操控,此元件為 Scenes 提供的 SceneDataLayerControls 元件:

https://ithelp.ithome.com.tw/upload/images/20240928/201520737UrCGQA2Oa.png

new SceneFlexItem({
  body: new VizPanel({
    ...,
    headerActions: [new SceneDataLayerControls()],
  }),
}),

讀者語錄
這兩個功能讓圖表可以呈現的資料更有無限的可能,尤其使用 SceneDataTransformer 及 SceneDataLayerSet 兩個實例讓原本在 GUI 上無法取得的屬性也能設定。雖然屬性眾多,無法一一牢記,且官方文件提供的資料也不多,但每一個實例的屬性都有 enum 類型提供參考,從中下手可以得到不少幫助的!

參考資料

https://grafana.com/docs/grafana-cloud/connect-externally-hosted/data-sources/loki/query-editor/#apply-annotations

https://grafana.com/docs/grafana/latest/panels-visualizations/query-transform-data/transform-data/#add-field-from-calculation

https://grafana.com/developers/scenes/transformations#add-custom-transformations

https://grafana.com/docs/grafana/latest/dashboards/build-dashboards/annotate-visualizations/#built-in-query


上一篇
靠 Grafana 吃飯的第十三天 - Grafana 的視覺化圖書館 - Visualizations
下一篇
靠 Grafana 吃飯的第十五天 - Grafana Dashboard 的 controls 元件
系列文
論前端工程師如何靠 Grafana 吃飯:從 Grafana App 到前端可觀測性30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言