module Sfx { export interface ITimelineData { groups : vis . DataSet < vis . DataGroup > ; items : vis . DataSet < vis . DataItem > ; start ? : Date ; end ? : Date ; } export interface ITimelineDataGenerator < T extends FabricEventBase > { consume ( events , startOfRange , endOfRange ) : ITimelineData ; } export class EventStoreUtils { public static tooltipFormat = ( data , start , end = "" , title = "" ) : string => { const rows = Object . keys ( data ) . map ( key => `<tr><td style="word-break: keep-all;">${ key }</td><td> : ${ data [ key ] }</td></tr>` ) . join ( "" ) ; const outline = `<table style="word-break: break-all;"><tbody>${ rows }</tbody></table>` ; return `<div class="tooltip-test">${ title . length > 0 ? title + "<br>" : "" }Start: ${ start } <br>${ end ? "End: " + end + "<br>" : "" }<b style="text-align: center;">Details</b><br>${ outline }</div>` ; } ; public static parseUpgradeAndRollback ( rollbackCompleteEvent , rollbackStartedEvent , items , startOfRange , group , targetVersionProperty ) { const rollbackEnd = rollbackCompleteEvent . timeStamp ; let rollbackStarted = startOfRange . toISOString ( ) ; if ( rollbackStartedEvent ) { rollbackStarted = rollbackStartedEvent . timeStamp ; const rollbackStartedDate = new Date ( rollbackEnd ) ; const upgradeDuration = rollbackCompleteEvent . eventProperties [ "OverallUpgradeElapsedTimeInMs" ] ; const upgradeStart = new Date ( rollbackStartedDate . getTime ( ) - upgradeDuration ) . toISOString ( ) ; items . add ( { id : rollbackCompleteEvent . eventInstanceId + "upgrade" , content : "Upgrade rolling forward failed" , start : upgradeStart , end : rollbackStarted , group , type : "range" , className : "red" } ) ; } const label = `rolling back to ${ rollbackCompleteEvent . eventProperties [ targetVersionProperty ] }` ; items . add ( { id : rollbackCompleteEvent . eventInstanceId + label , content : label , start : rollbackStarted , end : rollbackEnd , group , type : "range" , title : EventStoreUtils . tooltipFormat ( rollbackCompleteEvent . eventProperties , rollbackStarted , rollbackEnd ) , className : "orange" } ) ; } public static parseUpgradeDomain ( event , items , group , targetVersionProperty ) { const end = event . timeStamp ; const endDate = new Date ( end ) ; const duration = event . eventProperties [ "UpgradeDomainElapsedTimeInMs" ] ; const start = new Date ( endDate . getTime ( ) - duration ) . toISOString ( ) ; const label = event . eventProperties [ "UpgradeDomains" ] ; items . add ( { id : event . eventInstanceId + label + event . eventProperties [ targetVersionProperty ] , content : label , start : start , end : end , group , type : "range" , title : EventStoreUtils . tooltipFormat ( event . eventProperties , start , end ) , className : "green" } ) ; } public static parseUpgradeStarted ( event , items , endOfRange , group , targetVersionProperty ) { let end = endOfRange . toISOString ( ) ; const start = event . timeStamp ; const content = `Upgrading to ${ event . eventProperties [ targetVersionProperty ] }` ; items . add ( { id : event . eventInstanceId + content , content , start , end , group , type : "range" , title : EventStoreUtils . tooltipFormat ( event . eventProperties , start , end ) , className : "blue" } ) ; } public static parseUpgradeCompleted ( event , items , group , targetVersionProperty ) { const rollBack = event . kind === "ClusterUpgradeRollbackCompleted" ; const end = event . timeStamp ; const endDate = new Date ( end ) ; const duration = event . eventProperties [ "OverallUpgradeElapsedTimeInMs" ] ; const start = new Date ( endDate . getTime ( ) - duration ) . toISOString ( ) ; const content = `${ rollBack ? "Upgrade Rolling back" : "Upgrade rolling forward" } to ${ event . eventProperties [ targetVersionProperty ] }` ; items . add ( { id : event . eventInstanceId + content , content , start , end , group , type : "range" , title : EventStoreUtils . tooltipFormat ( event . eventProperties , start , end ) , className : rollBack ? "orange" : "green" } ) ; } public static addSubGroups ( groups ) { groups . forEach ( group => { group [ "subgroupStack" ] = { "stack" : true , "noStack" : false } ; groups . update ( group ) ; } ) ; } } export abstract class TimeLineGeneratorBase < T extends FabricEventBase > { consume ( events , startOfRange , endOfRange ) { throw new Error ( "NotImplementedError" ) ; } generateTimeLineData ( events , startOfRange , endOfRange ) { const data = this . consume ( events , startOfRange , endOfRange ) ; EventStoreUtils . addSubGroups ( data . groups ) ; return data ; } } export class ClusterTimelineGenerator extends TimeLineGeneratorBase < ClusterEvent > { static readonly upgradeDomainLabel = "Upgrade Domains" ; static readonly clusterUpgradeLabel = "Cluster Upgrades" ; static readonly seedNodeStatus = "Seed Node Warnings" ; consume ( events , startOfRange , endOfRange ) { let items = new vis . DataSet < vis . DataItem > ( ) ; let previousClusterHealthReport ; let previousClusterUpgrade ; let upgradeClusterStarted ; let clusterRollBacks = { } ; events . forEach ( event => { if ( ( event . kind === "ClusterUpgradeStarted" || event . kind === "ClusterUpgradeRollbackStarted" ) && ! previousClusterUpgrade ) { upgradeClusterStarted = event ; } else if ( event . kind === "ClusterUpgradeDomainCompleted" ) { EventStoreUtils . parseUpgradeDomain ( event , items , ClusterTimelineGenerator . upgradeDomainLabel , "TargetClusterVersion" ) ; } else if ( event . kind === "ClusterUpgradeCompleted" ) { EventStoreUtils . parseUpgradeCompleted ( event , items , ClusterTimelineGenerator . clusterUpgradeLabel , "TargetClusterVersion" ) ; previousClusterUpgrade = event ; } else if ( event . kind === "ClusterNewHealthReport" ) { this . parseSeedNodeStatus ( event , items , previousClusterHealthReport , endOfRange ) ; previousClusterHealthReport = event ; } if ( event . kind === "ClusterUpgradeRollbackCompleted" ) { previousClusterUpgrade = event ; clusterRollBacks [ event . eventInstanceId ] = { complete : event } ; } else if ( event . kind === "ClusterUpgradeRollbackStarted" && event . eventInstanceId in clusterRollBacks ) { clusterRollBacks [ event . eventInstanceId ] [ "start" ] = event ; } } ) ; Object . keys ( clusterRollBacks ) . forEach ( eventInstanceId => { const data = clusterRollBacks [ eventInstanceId ] ; EventStoreUtils . parseUpgradeAndRollback ( data . complete , data . start , items , startOfRange , ClusterTimelineGenerator . clusterUpgradeLabel , "TargetClusterVersion" ) ; } ) ; if ( upgradeClusterStarted ) { EventStoreUtils . parseUpgradeStarted ( upgradeClusterStarted , items , endOfRange , ClusterTimelineGenerator . clusterUpgradeLabel , "TargetClusterVersion" ) ; } let groups = new vis . DataSet < vis . DataGroup > ( [ { id : ClusterTimelineGenerator . upgradeDomainLabel , content : ClusterTimelineGenerator . upgradeDomainLabel } , { id : ClusterTimelineGenerator . clusterUpgradeLabel , content : ClusterTimelineGenerator . clusterUpgradeLabel } , { id : ClusterTimelineGenerator . seedNodeStatus , content : ClusterTimelineGenerator . seedNodeStatus } ] ) ; return { groups , items } ; } parseSeedNodeStatus ( event , items , previousClusterHealthReport , endOfRange ) { if ( event . eventProperties [ "HealthState" ] === "Warning" ) { let end = previousClusterHealthReport ? previousClusterHealthReport . timeStamp : endOfRange . toISOString ( ) ; const content = `${ event . eventProperties [ "HealthState" ] }` ; items . add ( { id : event . eventInstanceId + content , content , start : event . timeStamp , end : end , group : ClusterTimelineGenerator . seedNodeStatus , type : "range" , title : EventStoreUtils . tooltipFormat ( event . eventProperties , event . timeStamp , end ) , className : "orange" } ) ; } } } export class NodeTimelineGenerator extends TimeLineGeneratorBase < NodeEvent > { static readonly NodesDownLabel = "Node Down" ; static readonly transitions = [ 'NodeDeactivateStarted' ] ; consume ( events , startOfRange , endOfRange ) { const items = new vis . DataSet < vis . DataItem > ( ) ; const nodeUpEvents = { } ; const previousTransitions = { } ; const nodeDownDataItems = { } ; let potentiallyMissingEvents = false ; events . forEach ( event => { if ( event . category === 'StateTransition' ) { if ( event . kind === 'NodeDown' ) { const previousTransition = previousTransitions [ event . nodeName ] ; if ( previousTransition && previousTransition . kind !== 'NodeUp' ) { potentiallyMissingEvents = true ; } if ( event . nodeName in nodeUpEvents ) { delete nodeUpEvents [ event . nodeName ] ; } const end = previousTransition ? previousTransition . timeStamp : endOfRange . toISOString ( ) ; const start = event . timeStamp ; const label = `Node ${ event . nodeName } down` ; const item = { id : event . eventInstanceId + label , content : label , start , end , group : NodeTimelineGenerator . NodesDownLabel , type : 'range' , title : EventStoreUtils . tooltipFormat ( event . eventProperties , start , end , label ) , className : 'red' , subgroup : 'stack' } ; if ( event . eventProperties . NodeInstance in nodeDownDataItems ) { items . add ( nodeDownDataItems [ event . eventProperties . NodeInstance ] ) ; } nodeDownDataItems [ event . eventProperties . NodeInstance ] = item ; } else if ( event . kind === 'NodeDeactivateCompleted' && event . eventProperties . EffectiveDeactivateIntent === 'RemoveNode' ) { const nodeDownEvent = nodeDownDataItems [ event . eventProperties . NodeInstance ] ; if ( nodeDownEvent ) { delete nodeDownDataItems [ event . eventProperties . NodeInstance ] ; potentiallyMissingEvents = true ; } } if ( event . kind === 'NodeUp' && event . eventProperties . LastNodeDownAt !== '1601-01-01T00:00:00Z' ) { previousTransitions [ event . nodeName ] = event ; nodeUpEvents [ event . nodeName ] = event ; } } if ( NodeTimelineGenerator . transitions . indexOf ( event . kind ) > - 1 ) { previousTransitions [ event . nodeName ] = event ; } } ) ; Object . keys ( nodeDownDataItems ) . forEach ( key => { const item = nodeDownDataItems [ key ] ; items . add ( item ) ; } ) ; Object . keys ( nodeUpEvents ) . forEach ( key => { const event = nodeUpEvents [ key ] ; const label = `Node ${ event . nodeName } down` ; items . add ( { id : event . eventInstanceId + label , content : label , start : event . eventProperties . LastNodeDownAt , end : event . timeStamp , group : NodeTimelineGenerator . NodesDownLabel , type : 'range' , title : EventStoreUtils . tooltipFormat ( event . eventProperties , event . eventProperties . LastNodeDownAt , event . timeStamp , label ) , className : 'red' , subgroup : 'stack' } ) ; } ) ; const groups = new vis . DataSet < vis . DataGroup > ( [ { id : NodeTimelineGenerator . NodesDownLabel , content : NodeTimelineGenerator . NodesDownLabel , subgroupStack : { stack : true } } , ] ) ; return { groups , items , } ; } } export class ApplicationTimelineGenerator extends TimeLineGeneratorBase < ApplicationEvent > { static readonly upgradeDomainLabel = "Upgrade Domains" ; static readonly applicationUpgradeLabel = "Application Upgrades" ; static readonly applicationPrcoessExitedLabel = "Application Process Exited" ; consume ( events , startOfRange , endOfRange ) { let items = new vis . DataSet < vis . DataItem > ( ) ; let previousApplicationUpgrade ; let upgradeApplicationStarted ; let applicationRollBacks = { } ; let processExitedGroups = { } ; events . forEach ( event => { if ( ( event . kind === "ApplicationUpgradeStarted" || event . kind === "ApplicationUpgradeRollbackStarted" ) && ! previousApplicationUpgrade ) { upgradeApplicationStarted = event ; } else if ( event . kind === "ApplicationUpgradeDomainCompleted" ) { EventStoreUtils . parseUpgradeDomain ( event , items , ApplicationTimelineGenerator . upgradeDomainLabel , "ApplicationTypeVersion" ) ; } else if ( event . kind === "ApplicationUpgradeCompleted" ) { EventStoreUtils . parseUpgradeCompleted ( event , items , ApplicationTimelineGenerator . applicationUpgradeLabel , "ApplicationTypeVersion" ) ; previousApplicationUpgrade = event ; } else if ( event . kind === "ApplicationProcessExited" ) { this . parseApplicationProcessExited ( event , items , processExitedGroups ) ; } if ( event . kind === "ApplicationUpgradeRollbackCompleted" ) { previousApplicationUpgrade = event ; applicationRollBacks [ event . eventInstanceId ] = { complete : event } ; } else if ( event . kind === "ApplicationUpgradeRollbackStarted" && event . eventInstanceId in applicationRollBacks ) { applicationRollBacks [ event . eventInstanceId ] [ "start" ] = event ; } } ) ; Object . keys ( applicationRollBacks ) . forEach ( eventInstanceId => { const data = applicationRollBacks [ eventInstanceId ] ; EventStoreUtils . parseUpgradeAndRollback ( data . complete , data . start , items , startOfRange , ApplicationTimelineGenerator . applicationUpgradeLabel , "ApplicationTypeVersion" ) ; } ) ; if ( upgradeApplicationStarted ) { EventStoreUtils . parseUpgradeStarted ( upgradeApplicationStarted , items , endOfRange , ApplicationTimelineGenerator . applicationUpgradeLabel , "ApplicationTypeVersion" ) ; } let groups = new vis . DataSet < vis . DataGroup > ( [ { id : ApplicationTimelineGenerator . upgradeDomainLabel , content : ApplicationTimelineGenerator . upgradeDomainLabel } , { id : ApplicationTimelineGenerator . applicationUpgradeLabel , content : ApplicationTimelineGenerator . applicationUpgradeLabel } , ] ) ; let nestedApplicationProcessExited = { id : ApplicationTimelineGenerator . applicationPrcoessExitedLabel , nestedGroups : [ ] , content : ApplicationTimelineGenerator . applicationPrcoessExitedLabel , } ; Object . keys ( processExitedGroups ) . forEach ( groupName => { nestedApplicationProcessExited . nestedGroups . push ( groupName ) ; groups . add ( processExitedGroups [ groupName ] ) ; } ) ; groups . add ( nestedApplicationProcessExited ) ; return { groups , items } ; } parseApplicationProcessExited ( event , items , processExitedGroups ) { const groupLabel = `${ event . eventProperties [ "ServicePackageName" ] }` ; processExitedGroups [ groupLabel ] = { id : groupLabel , content : groupLabel } ; const start = event . timeStamp ; const label = event . eventProperties [ "ExeName" ] ; items . add ( { id : event . eventInstanceId + label , content : "" , start : start , group : groupLabel , type : "point" , className : "red-point" , title : EventStoreUtils . tooltipFormat ( event . eventProperties , start , null , "Primary swap to " + label ) , } ) ; } } export class PartitionTimelineGenerator extends TimeLineGeneratorBase < NodeEvent > { static readonly swapPrimaryLabel = "Primay Swap" ; static readonly swapPrimaryDurations = "Swap Primary phases" ; consume ( events , startOfRange , endOfRange ) { let items = new vis . DataSet < vis . DataItem > ( ) ; events . forEach ( event => { if ( event . category === "StateTransition" && event . eventProperties [ "ReconfigType" ] === "SwapPrimary" ) { const end = event . timeStamp ; const endDate = new Date ( end ) ; const duration = event . eventProperties [ "TotalDurationMs" ] ; const start = new Date ( endDate . getTime ( ) - duration ) . toISOString ( ) ; const label = event . eventProperties [ "NodeName" ] ; items . add ( { id : event . eventInstanceId + label , content : label , start : start , end : end , group : PartitionTimelineGenerator . swapPrimaryLabel , type : "range" , title : EventStoreUtils . tooltipFormat ( event . eventProperties , start , end , "Primary swap to " + label ) , className : "green" } ) ; } ; } ) ; let groups = new vis . DataSet < vis . DataGroup > ( [ { id : PartitionTimelineGenerator . swapPrimaryLabel , content : PartitionTimelineGenerator . swapPrimaryLabel } , ] ) ; return { groups , items } ; } } param event param groupIds param query function parseAndAddGroupIdByString ( event , groupIds , query ) { const properties = query . split ( "," ) ; let constructedPath = "" ; for ( let i = 0 ; i < properties . length ; i ++ ) { let prop = properties [ i ] ; if ( prop in event . raw ) { let currentPath = `${ constructedPath } ${ event . raw [ prop ] }` ; if ( _ . findIndex ( groupIds , ( g ) => g . id === currentPath ) === - 1 ) { const content = _ . padStart ( "" , i * 3 ) + event . raw [ prop ] . toString ( ) ; const childGroup = { id : currentPath , content , treeLevel : ( i + 1 ) } ; if ( ( i + 1 ) < properties . length ) { childGroup [ "nestedGroups" ] = [ ] ; } if ( i > 0 ) { const parentGroup = groupIds [ _ . findIndex ( groupIds , ( g ) => g . id === constructedPath ) ] ; parentGroup . nestedGroups . push ( childGroup . id ) ; } groupIds . push ( childGroup ) ; } constructedPath = currentPath ; } else { i = properties . length ; constructedPath = "" ; } } return constructedPath ; } export function parseEventsGenerically ( events , textSearch = "" ) { let items = new vis . DataSet < vis . DataItem > ( ) ; let groupIds = [ ] ; events . forEach ( ( event , index ) => { const groupId = parseAndAddGroupIdByString ( event , groupIds , textSearch ) ; let color = "white" ; if ( "Status" in event . eventProperties ) { try { const status = event . eventProperties [ "Status" ] ; if ( status === "Ok" ) { color = "green" ; } else if ( status === "Warning" ) { color = "orange" ; } else if ( status === "Error" ) { color = "red" ; } } catch ( e ) { } } else { if ( HtmlUtils . eventTypesUtil . isResolved ( event ) ) { color = "green" ; } else if ( HtmlUtils . eventTypesUtil . isWarning ( event ) ) { color = "orange" ; } else if ( HtmlUtils . eventTypesUtil . isError ( event ) ) { color = "red" ; } } let item = { content : "" , id : index , start : event . timeStamp , group : groupId , type : "point" , title : EventStoreUtils . tooltipFormat ( event . raw , event . timeStamp ) , className : `${ color }-point` , subgroup : "noStack" } ; if ( "Duration" in event . eventProperties ) { if ( "Description" in event . eventProperties ) { try { item . content = event . eventProperties [ "Description" ] ; } catch ( e ) { } } ; try { const duration = event . eventProperties [ "Duration" ] ; const end = event . timeStamp ; const endDate = new Date ( end ) ; const start = new Date ( endDate . getTime ( ) - duration ) . toISOString ( ) ; if ( duration < 0 ) { item . start = end ; item [ "end" ] = start ; item . title = EventStoreUtils . tooltipFormat ( event . raw , start , end , item . content ) ; } else { item . start = start ; item [ "end" ] = end ; item . title = EventStoreUtils . tooltipFormat ( event . raw , end , start , item . content ) ; } item . type = "range" ; item . className = color ; item . subgroup = "stack" ; } catch ( e ) { } } items . add ( item ) ; } ) ; let groups = new vis . DataSet < vis . DataGroup > ( groupIds ) ; EventStoreUtils . addSubGroups ( groups ) ; return { groups , items } ; } }