iT邦幫忙

2024 iThome 鐵人賽

DAY 28
2
Modern Web

論前端工程師如何靠 Grafana 吃飯:從 Grafana App 到前端可觀測性系列 第 28

靠 Grafana 吃飯的第二十八天 - 克隆 Grafana Cloud 前端可觀測性平台 - 基礎篇

  • 分享至 

  • xImage
  •  

https://ithelp.ithome.com.tw/upload/images/20241012/20152073g0RPMtrTqB.png

前言

透過先前的文章,我們首先認識了 Grafana Scenes 如何建構起一個 Plugin,接著透過前端可觀測性了解 Grafana Faro 在前端應用程式中進行數據資料收集的過程,也了解 Grafana Cloud 中的前端可觀測性平台 - Frontend Observability 提供的所有介面及功能。既然已經了解兩個重要的角色,接下來我們就要試著以 Grafana Scenes 建構起一個前端可觀測性平台。

Grafana Cloud 的可觀測性平台提供給我們一個建構自己團隊專屬的啟發,但我們的需求不一定跟 Grafana Cloud 的相同,我們只能被動的使用他們提供的介面,所以回到本系列第一篇文章的宗旨,希望可以透過 Grafana Scenes 打造各種客製化的應用程式,甚至不限於可觀測性或監控平台,把他當作一個擁有 Grafana 介面的 React 框架來使用。

在本系列文的最後兩篇文章中,會向各位讀者展示如何透過 Grafana Scenes 打造一個前端可觀測性平台,我們不會逐一的手把手地印出所有程式碼,而是會著重在介面與功能的實現,以及資料的取得方法和元件的使用,讓我們開始分析這個平台的實現方法吧!

前端可觀測性平台

https://ithelp.ithome.com.tw/upload/images/20241012/20152073vpcgaECHEg.png

在第二十七天的文章中,先帶大家了解這個前端可觀測性平台的全貌,這個平台主要分成三個部分:

  1. Overview 頁面:顯示所有頁面的效能數據
  2. Errors 頁面:顯示所有頁面的錯誤數據
  3. Sessions 頁面:顯示所有頁面的 Session 數據

但我們不貪心,先從綜觀的 Overview 頁面開始實作。在這個客製化的應用程式中,我們全部都使用 Grafana Scenes 所提供的元件,從數據取得、資料轉換、變數設置以及視覺話的呈現,當然很多 Panel 的類型不是由 Visualization 中的元件所提供,所以需要使用 Grafana UI 的元件來實現。
除了應用程式的整體 Layout 之外,我會將這個頁面分為四個部分做實作觀念的介紹。

💡 NOTICE
在重現這個平台的過程中,最重要的是確保 UI 呈現和資料取得的正確性。資料可以透過 Grafana Cloud 中的各種查詢請求來獲取,因此只需要打開瀏覽器的 network 介面,就能複製各種查詢請求,並應用到客製化的應用程式中。

Layout

https://ithelp.ithome.com.tw/upload/images/20241012/201520732nOGCkyUDT.png

上圖區塊是整個應用程式的 Layout,所有的 UI 元件都可以使用 Scenes 提供的元件完成,比較需要注意的是 Overview、Errors、Sessions 三個頁面需要由 tabs 屬性來設置,另外 $timeRange$variables 是應用程式進行資料查詢時,必須帶入的變數,也需要在 Layout 中設置,大概的結構會如下圖呈現。

https://ithelp.ithome.com.tw/upload/images/20241012/20152073mwxVioYLvS.png

變數設置

變數設置是 Layout 中很重要的邏輯,會與 control 元件一起組成 Logs 的下拉選單,這個元件主要呈現 Loki DataSource 的來源,如果需求有很多座 Loki 服務,可以透過這個元件選擇資料查詢的來源,因此此處會使用 DataSourceVariable 設置變數,而初始化的設定如果不清楚 uid 的值,可以透過 getDataSourceSrv 方法取得,並使用 addActivationHandler 設置變數的初始值。

const datasourceVariable = new DataSourceVariable({
  name: "loki",
  label: "Logs",
  value: "",
  pluginId: "loki",
});

datasourceVariable.addActivationHandler(() => {
  const bes = getDataSourceSrv().getList({ filter: (ds) => ds.type === "loki" });
  const options = bes.map((be) => ({ label: be.name, value: be.uid }));
  datasourceVariable.setState({ value: options[0].value, text: options[0].label });
});

第一部分

https://ithelp.ithome.com.tw/upload/images/20241012/201520731v25Kv0Qn1.png

第一部分是 Page Loads、Errors 以及所有 Web Vitals 的測量數據,請求的 query type 會以 instant 作為設定,而 UI 的部分 Page Loads、Errors 可以以 stat 的 Panel 呈現,但 Web Vitals 的樣式會需要自己實作,需要自定義 SceneObject 的元件,其中包含數值的呈現,還有非常一目瞭然的 Progress Bar,並且需要依據不同的閾值設定不同的顏色。

📝TIP
覺得這部分最有挑戰的是 Progress Bar 的百分比定義,因為閾值沒有上限,所以需要在每段區間中設置不同的百分比,並以 33% 和 66% 作為分界點,也要注意超過 100% 時要以 100% 呈現,讓指針可以停留在 progress bar 的末端。

另外 Page Loads、Errors 雖然可以使用 stat 的 Panel 呈現,但如果追求閾值顏色的顯示,可以使用 setThresholds 的方法來設定:

setThresholds({
  mode: ThresholdsMode.Absolute,
  steps: [
    { value: 0, color: measurement.key === "errors" ? "green" : "blue" },
    { value: 1, color: measurement.key === "errors" ? "red" : "blue" },
  ],
});

第二部分

https://ithelp.ithome.com.tw/upload/images/20241012/20152073LP7qrNpIaY.png

第二部分是看似簡單的 Page Loads 和 Errors 的 Panel,不是單純地取得資料後將數值顯示在 time series 的 Panel 上,而是需要將線圖改為 bar chart,並以不同的顏色呈現,還有將 legend 的顯示名稱修改,這些都是透過 setCustomFieldConfigsetOverrides 的方法來完成。而注意右上方的 Explore 按鈕,這邊會使用 LinkButton 的元件,並以 setHeaderActions 的方法設置:

.setCustomFieldConfig('drawStyle', GraphDrawStyle.Bars) // 將線圖改為 bar chart
.setCustomFieldConfig('stacking', { mode: StackingMode.Normal }) // 將 bar 呈現堆疊
.setCustomFieldConfig('barWidthFactor', 1) // 設置 bar 的寬度
.setOverrides((b) => {
  b.matchFieldsWithNameByRegex('/exception/')
    .overrideDisplayName('Errors') // 修改 legend 的顯示名稱
    .overrideColor({ mode: FieldColorModeId.Fixed, fixedColor: 'red' }) // 設置為紅色
    .matchFieldsWithNameByRegex('/measurement/')
    .overrideDisplayName('Page Loads')
    .overrideColor({ mode: FieldColorModeId.Fixed, fixedColor: 'blue' });
})
.setHeaderActions(
  <LinkButton
    size="sm"
    icon="compass"
    variant="secondary"
    href={`/explore?left=${encodeURIComponent(JSON.stringify(queries))}`}
  >
    Explore
  </LinkButton>
);

第三部分

https://ithelp.ithome.com.tw/upload/images/20241012/201520734opzpzA64M.png

第三部分是 Page Performance 的 Panel,數據的取得不難,會同時以 7 個 queries 進行請求,最具挑戰的是資料的轉換,以及 Table 的設置。Table 的功能及樣式無法以 PanelBuilder 提供的 Table 元件設置,必須使用自定義的 ScenesObject,並引入 Grafana UI 的 InteractiveTable 來實現,需要定義 columns (每個 table header 的欄位設置) 及 data (每一個 table row 的資料) 的屬性,以及使用 getRowId 的方法設置 row 的 id,才能在 Table 中顯示資料。

而資料的處理,會建議先將 dataFrame 的資料格式印出後,再依據 columns 的屬性進行資料的轉換,其中包括數值的格式化、預設值的設定。而在 SceneObject 中因為需要時間差取得原始的資料,所以資料的 format 會在 addActivationHandler 中偵測到狀態有改變時再進行初始化的設定,例如下面的範例:

this.addActivationHandler(() => this._onActivate());

public _onActivate() {
  const dataProvider = sceneGraph.getData(this);
  this._subs.add(
    dataProvider.subscribeToState((state) => {
      const rawData = state?.data?.series;
      const data = rawData ? getTableData(rawData) : [];
      this.setState({ tableData: data });
    })
  );
  return () => {
    this._subs.unsubscribe();
  };
}

另外,Grafana Cloud 中有一些數值呈現的小巧思,例如數值會根據不同的閾值設定對應的顏色。相同的邏輯可以參考第一部分 Web Vitals 的 Progress Bar 實作。而在 Page ID 欄位中,數值會以連結形式呈現,目的是為了顯示該頁面的詳細資料。這些客製化的 table cell 都可以在 columns 中設定,並根據傳遞的 value 進行客製化處理,例如:

{
  id: 'pageId',
  header: 'Page ID',
  sortType: 'alphanumeric',
  cell: ({ value }) => (
    <Button
      variant="primary"
      fill="text"
      onClick={() => toggleAction(value)}
    >
      {value}
    </Button>
  ),
}

💡NOTICE
底層是以 react-table 實現,但 Grafana 封裝後的元件提供的參數較少,目前實測還無法自定義每個 row 的樣式設定,但透過 cell 的客製化可以完成許多複雜的變化。還是希望未來可以有更多自定義的空間。

第四部分

https://ithelp.ithome.com.tw/upload/images/20241012/20152073GFKYifQjcx.png

在實作前面三個部分後,會發現第四部分的實作相對簡單。三個 time series 的 Panel 可以互相覆用,資料取得也不需要做額外轉換。這些資料分別透過三組 queries 來獲取:[ttfb、fcp、lcp]、[cls]、[fid、inp],並使用 range 類型的查詢來獲取數據,將這些 web vitals 根據其特性分組顯示。

需要特別注意的是折線圖的呈現方式。預設情況下,折線圖會以資料點來顯示,我們需要使用 setCustomFieldConfig 方法將 drawStyle 設定為 Line,並另外設定 lineWidth 和 spanNulls 屬性,才能讓折線圖顯示得更加平滑且連貫。

.setCustomFieldConfig('drawStyle', GraphDrawStyle.Line)
.setCustomFieldConfig('lineWidth', 2)
.setCustomFieldConfig('spanNulls', true)

最後,有一個特別的設置——$behavior,在第十六天的文章中有提到。這裡會使用 CursorSync 的功能,使三個 Panel 以及 PageLoads and Errors 的時間軸同步,並以 Tooltip 的方式呈現,讓使用者能清楚查看四個 Panel 同一個資料點的 tooltip 資訊。而要實現跨 Panel 的時間軸同步,必須透過 key 設置唯一的識別字串,才能在 CursorSync 中進行設置。

$behaviors: [new behaviors.CursorSync({ key: 'time-series-panel', sync: DashboardCursorSync.Tooltip })],

https://ithelp.ithome.com.tw/upload/images/20241012/20152073PmYGqsMIJK.png

筆者語錄

靠著 Grafana Scenes 的元件,可以很方便的實現許多複雜的 UI 效果,在實現的過程中,其實多少還是會遇到 Panel 或是數據轉換的問題,所以多次查看前面幾十天的文章,回顧各種 SceneObject 的實作方法,才能順利解決問題。實現的宗旨也是希望可以復刻出 Grafana Cloud 的介面,甚至可以在這過程中發現官方的 bugs,當然我們可以透過這些客製化將功能優化,讓使用者有更好的體驗。而下篇的實作內容會著重在 Page Performance 的 Page ID 連結功能,實踐的方法需要連動網址、變數、請求查詢以及原始頁面,因此會有更多挑戰。


上一篇
靠 Grafana 吃飯的第二十七天 - 解讀前端可觀測性平台的視覺化資訊
下一篇
靠 Grafana 吃飯的第二十九天 - 克隆 Grafana Cloud 前端可觀測性平台 - 挑戰篇
系列文
論前端工程師如何靠 Grafana 吃飯:從 Grafana App 到前端可觀測性30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言