KPI Design ================== ## 給共創Partner看的大綱 ![](https://i.imgur.com/nbF4RuM.png) 1. I.App 寫好 data sources 的 API之後,透過 ETCD 註冊自己(I.App) 的位置 2. KPI Setting Backend 會 watch ETCD 並且讀取 I.App 可用的 data sources 3. KPI Engine 會使用 KPI Setting 提供的 data source values 跟設定做計算 (計算完會存在 MongoDB) 4. KPI Engine 算好的值 同時也會塞入 API hub 的 Metric Parameter 底下的 value/values/historicalValues 5. Dashboard 的 Backend 會讀取 Metric Parameter 以及 Group / Object 的 API 給 Dashboard 的 Visualization frontend 做不同選項 6. Dashboard 的 Frontend 會透過 Dashboard backend 有的 options 跟計算好的KPI值呈現不同的 visualization 畫面 KPI Engine 算出來的值(Real time values, Historical value, value)都來自於 Federation 的 MetricParameter query. 下圖是 graphql playground 的 metric parameters query 的 values (real time values) ![](https://i.imgur.com/nhJstmM.png) KPI 後端儲存的所有 Metric Parameters 可以透過 machine query 或是 group query 底下的 metricParameters 欄位讀取。 下圖是 graphql playground 的 machine query 底下的 metric parameters ![](https://i.imgur.com/wLcKQCq.png) 由於 呼叫 metric parameter 透過 KPI Engine 算出來的值需要提供 group 或是 machine 的ID,共創夥伴們可能會需要 group 跟 machine 的 query. group 是從 RTM 的 groups query 來的如下: ```graphql= query groupList($rootGroupsOnly: Boolean, $lang: String) { groups(rootGroupsOnly: $rootGroupsOnly) { id name(lang: $lang) } } ``` machine 是來自於某個 group 底下的 machine: ```graphql= query getMachinesByGroup($groupId: ID!) { group(id: $groupId) { machines { id name } stations: machines(isStation: true) { id name } } } ``` ## GraphQL Schema (I.App) 各個 I.App 需要實作 ```graphql= # Types scalar DateTime # Define queries here extend type Query { # 取得此 I.App 提供的所有 DataSource,可以傳入 groupId 或 machineId 來過濾 # 可以用「mode」去過濾出有支援這個 mode 的 DataSource,沒傳則列出支援 time driven 的 dataSources(groupId: String, machineId: String, mode: String): [DataSource!]! # 取得特定的幾個 DataSource,用 index 對應 dataSourcesByIds(args: [DataSourceByIdArg!]!): [DataSource]! # 取得多個 DataSource 前處理過的資料,每個 DataSource 對應到一筆資料,用 index 對應 dataSourceValues(args: [DataSourceValueArg!]!): [DataSourceValue]! # [{value: "123"}] => 正常情況 # [null] => id 不存在 # [{value: null}] => 沒有值 } type DataSource { id: String! # 至少在各 I.App 下要唯一 name(lang: String): String! # 純 UI 顯示用 valueType: DataSourceValueType! # 資料類型 summarizedMethods: [DataSourceSummarizedMethod!]! # groupId 和 machineId 至少要有一個有值,用來讓 UI 呈現選擇時的階層,兩者不可能同時存在 groupId: String # 此 DataSource 是隸屬於哪個 Group machineId: String # 此 DataSource 是隸屬於哪個 Machine # 沒設則為 TimeDriven mode: String # EventDriven ... } enum DataSourceValueType { Number Date } type DataSourceSummarizedMethod { # 以 method 為主,先決定 method,再決定可以用哪種資料來算 method: String! # Min, Max, Avg ... dataPeriods: [String!]! # Hourly, Daily ... 原始資料的時間類型,由各 I.App 提供 } type DataSourceByIdArg { id: String! groupId: String machineId: String } # 為了能取得有支援 event driven 的 DataSource 的值,多了 from 和 variables type DataSourceValueArg { id: String! # 至少在各 I.App 下要唯一 summarizedMethod: String! # Min, Max, Avg ... dataPeriod: String! # Hourly, Daily ... 原始資料的時間類型,由各 I.App 提供 timestamp: DateTime! # 欲取得的資料點所對應的時間,實際取資料時用的時間範圍,由各 I.App 決定 groupId: String machineId: String # for event driven from: DateTime tag: String # e.g. work order ID eventData: JSON # String JSON Object } type DataSourceValue { value: String } ``` ## ETCD ### 現況 I.App 需要新增 metadata 告知 graphql 的 endpoint,key:`dataSourceUrl` ### 未來 為了支援 EventSource 的選取,新增 key:`eventSourceUrl` ## 取得 Metric 設定及資料 ```graphql= # Define queries here extend type Query { # query single metric parameter by id metricParameter(id: ID!): MetricParameter! # query metric parameters by iAppType metricParameters(iAppType: IAppType): [MetricParameter!]! } type MetricParameter { id: ID! _id: ObjectID! groupId: ID machineId: ID tenantId: ID iAppType: IAppType name: String! description: String # formula for data type dataType: FormulaDataType! numberDataSetting: KpiFormulaNumberDataSetting # 1000 = 1 second for how often to calculate data in kpi engine recordingRate: PositiveInt eventSource: MetricParameterEventSource # how many days to keep data daysToKeepData: PositiveInt! maxChangeRate: PositiveFloat! # calculate historical values from real time values based on these settings summarizedSetting: SummarizedSetting! # formula for calculating via kpi engine formula: String! operands: [Operand!]! highLowEvents: [MetricParameterHighLowEvent]! createdAt: DateTime updatedAt: DateTime # below are calculated in the kpi engine and queried from grafana dashboard # single real time value value: MetricParameterValue # real time values for multiple values based on date range values( from: DateTime to: DateTime orderBy: ValueOrderByInput first: Int after: String last: Int before: String tag: String ): ValueConnection! # historical values based on date range (calculated from real time values) historicalValues( from: DateTime to: DateTime orderBy: ValueOrderByInput period: String first: Int after: String last: Int before: String tag: String ): ValueConnection! } enum IAppType { OEEM MOM EHS DPM } enum FormulaDataType { Number } type KpiFormulaNumberDataSetting { decimalPlace: NonNegativeInt! unit: String spanLow: Float spanHigh: Float } type MetricParameterEventSource { iAppName: String! id: String! groupId: String machineId: String } type SummarizedSetting { hourly: SummarizedMethod daily: SummarizedMethod weekly: SummarizedMethod monthly: SummarizedMethod } enum SummarizedMethod { Sum Average Min Max Diff } type Operand { name: String! dataSource: OperandDataSource } # 選項從各 I.App 來 type OperandDataSource { iAppName: String! id: String! valueType: OperandDataSourceValueType! summarizedMethod: String! dataPeriod: String! groupId: String machineId: String mode: String } enum OperandDataSourceValueType { Number Date } type MetricParameterHighLowEvent { isActive: Boolean! value: Float! level: HighLowEventLevel! message: String! notificationAction: KpiNotificationAction } enum HighLowEventLevel { Critical Major Warning } type KpiNotificationAction { isActive: Boolean! groupId: String! subject: String message: String } type MetricParameterValue { value: Float! ts: DateTime! } enum Sort { ASC DESC } type ValueOrderByInput { timestamp: Sort } type ValeEdge { cursor: String! node: MetricParameterValue } type ValueConnection { edges: [ValueEdge]! # nodes: [MetricParameterValue!] pageInfo: PageInfo! total: int! } ``` 如果資料有變化,可以透過 RedisPubSub 得知。 * channel: ifp-kpi.MetricParameter.{ID} * message: 若為空字串,則代表資料被刪除,反之則為新增或更新 ```json= { "id": "TWV0cmljUGFyYW1ldGVy.YnzO0Oyq8FmQuCN0", "groupId": "R3JvdXA.X7S66PMvPwAGCTcZ", "name": "MyMetric", "dataType": "Number", "numberDataSetting": { "decimalPlace": 1 }, "recordingRate": 1, "daysToKeepData": 1, "maxChangeRate": 1, "summarizedSetting": {}, "formula": "100", "operands": [], "highLowEvents": [], "createdAt": "2022-05-12T09:09:37.051Z", "updatedAt": "2022-05-12T09:09:37.051Z" } ``` ## 取得各 I.App 的 dataSourceUrl ```graphql= # Define queries here extend type Query { dataSourceUrls: [DataSourceUrl!]! } type DataSourceUrl { name: String! url: String! } ``` 如果資料有變化,可以透過 RedisPubSub 得知。 * channel: ifp-kpi.DataSourceUrl.{I.App name} * message: url,若為空字串,則代表 url 已消失,反之則為新增或更新 ## 取得各 I.App 的 eventSourceUrl ```graphql= # Define queries here extend type Query { eventSourceUrls: [EventSourceUrl!]! } type EventSourceUrl { name: String! url: String! } ``` 如果資料有變化,可以透過 RedisPubSub 得知。 * channel: ifp-kpi.EventSourceUrl.{I.App name} * message: url,若為空字串,則代表 url 已消失,反之則為新增或更新 ## I.App 和 KPI 之間的設定同步 ### DataSource 當 I.App 的 DataSource 「被刪除」時,要用 RedisPubSub 的機制通知 KPI。 * channel: ifp-kpi.DataSource.{I.App name}.{DataSource ID} * message: ""(空字串,代表著刪除) ### EventSource 當 I.App 的 EventSource 「被刪除」時,要用 RedisPubSub 的機制通知 KPI。 * channel: ifp-kpi.EventSource.{I.App name}.{EventSource ID} * message: ""(空字串,代表著刪除) <!-- ## DataSource () | Total(十進位) | Event Driven | Time Driven | Description | |:---:|:------------:|:-----------:| ----------------------- | |0 | 0 | 0 | 都不支援 (保留) | |1 | 0 | 1 | Time Driven Only | |2 | 1 | 0 | Event Driven Only | |3 | 1 | 1 | 支援 Event 與 Time (預留) | --> ## EventSource (規劃中) 由 Event 觸發 Metric 計算,UI 提供 Event Source 給 Metric 選擇。 ![](https://i.imgur.com/Ll5PgPN.png) ### I.App 實作 ```graphql= extend type Query { # 可以用 groupId 或 machineId 去過濾 EventSource # 如果都沒傳,則代表要列出的是「不屬於任何 Group 或 Machine」的 EventSource eventSources(groupId: String, machineId: String): [EventSource!]! # 取得特定的幾個 EventSource,用 index 對應 eventSourcesByIds(args: [EventSourceByIdArg!]!): [EventSource]! } type EventSource { id: String! # 至少在各 I.App 下要唯一 name(lang: String): String! # 純 UI 顯示用 groupId: String # 此 EventSource 是隸屬於哪個 Group machineId: String # 此 EventSource 是隸屬於哪個 Machine } type EventSourceByIdArg { id: String! groupId: String machineId: String } ``` ### KPI 提供 #### 透過 GraphQL 觸發 ```graphql= extend type Mutation { triggerMetricParameters( iAppName: String eventSourceId: String!, groupId: String, machineId: String, from: DateTime, timestamp: DateTime!, tag: String, # e.g. work order ID eventData: JSON # String JSON Object ): Void } ``` #### 透過 Redis 觸發 透過 `PUBLISH` 至 `ifp-kpi-engine.trigger-metric-parameters` 觸發 ```json= { "iAppName": "", "eventSourceId": "", "groupId": "", "machineId": "", "from": "", "timestamp": "", "tag": "", "eventData": "", // JSON Object string } ``` ### Issues - Metric 可能用到不同 DataSources,無法針對特定 DataSource 去給相關需要的參數(例:WorkOrderID),目前設計只能做到類似全域變數,每個 DataSource 都丟相同參數去詢問 DataSourceValue。