diff --git a/frontend/app/common/utils/utils.ts b/frontend/app/common/utils/utils.ts index 60b4e800e..5a2627c8b 100644 --- a/frontend/app/common/utils/utils.ts +++ b/frontend/app/common/utils/utils.ts @@ -62,7 +62,7 @@ const KNOWN_TOOLS = [ 'memory_viewer', 'op_profile', 'pod_viewer', - 'tensorflow_stats', + 'framework_op_stats', 'trace_viewer', 'tf_data_bottleneck_analysis', ]; @@ -317,18 +317,19 @@ export function scrollBottomOfSidenav() { } /** - * Returns a string with an anchor tag. + * Returns a string with an anchor tag in oss. */ -export function addAnchorTag(value: string = ''): string { - return '' + value + ''; +export function addAnchorTag(value = '', run = ''): string { + return `${ + value}`; } /** * Returns a string with the known tool name changed to an anchor tag. */ -export function convertKnownToolToAnchorTag(value: string = ''): string { +export function convertKnownToolToAnchorTag(value = '', run = ''): string { KNOWN_TOOLS.forEach(tool => { - value = value.replace(new RegExp(tool, 'g'), addAnchorTag(tool)); + value = value.replace(new RegExp(tool, 'g'), addAnchorTag(tool, run)); }); return value; } diff --git a/frontend/app/components/overview_page/overview_page.ng.html b/frontend/app/components/overview_page/overview_page.ng.html index 4ee677840..7f193890d 100644 --- a/frontend/app/components/overview_page/overview_page.ng.html +++ b/frontend/app/components/overview_page/overview_page.ng.html @@ -8,6 +8,8 @@ > diff --git a/frontend/app/components/overview_page/overview_page.ts b/frontend/app/components/overview_page/overview_page.ts index 6c2ac70a1..9e648cdc3 100644 --- a/frontend/app/components/overview_page/overview_page.ts +++ b/frontend/app/components/overview_page/overview_page.ts @@ -9,6 +9,17 @@ import {takeUntil} from 'rxjs/operators'; import {OverviewPageCommon} from './overview_page_common'; +const RECOMMENDATION_STATEMENT_INFO = [ + {id: 'outside_compilation_statement_html'}, + {id: 'eager_statement_html'}, + {id: 'tf_function_statement_html'}, + {id: 'statement'}, + {id: 'device_collectives_statement'}, + {id: 'kernel_launch_statement'}, + {id: 'all_other_statement'}, + {id: 'precision_statement'}, +]; + /** An overview page component. */ @Component({ standalone: false, @@ -19,6 +30,7 @@ export class OverviewPage extends OverviewPageCommon implements OnDestroy { run = ''; tag = ''; host = ''; + recommendationStatementInfo = RECOMMENDATION_STATEMENT_INFO; /** Handles on-destroy Subject, used to unsubscribe. */ private readonly destroyed = new ReplaySubject(1); diff --git a/frontend/app/components/overview_page/recommendation_result_view/BUILD b/frontend/app/components/overview_page/recommendation_result_view/BUILD index 505d1e9c0..e27db4050 100644 --- a/frontend/app/components/overview_page/recommendation_result_view/BUILD +++ b/frontend/app/components/overview_page/recommendation_result_view/BUILD @@ -9,7 +9,7 @@ xprof_ng_module( name = "recommendation_result_view", srcs = [ "recommendation_result_view.ts", - "recommendation_result_view_common.ts", + "recommendation_result_view_interfaces.ts", "recommendation_result_view_module.ts", ], assets = [ diff --git a/frontend/app/components/overview_page/recommendation_result_view/recommendation_result_view.ng.html b/frontend/app/components/overview_page/recommendation_result_view/recommendation_result_view.ng.html index 0516fe035..62d8a8d7f 100644 --- a/frontend/app/components/overview_page/recommendation_result_view/recommendation_result_view.ng.html +++ b/frontend/app/components/overview_page/recommendation_result_view/recommendation_result_view.ng.html @@ -12,12 +12,7 @@

{{tipInfo.title}}

- - +
  • diff --git a/frontend/app/components/overview_page/recommendation_result_view/recommendation_result_view.ts b/frontend/app/components/overview_page/recommendation_result_view/recommendation_result_view.ts index d47eaac2b..79806cc4f 100644 --- a/frontend/app/components/overview_page/recommendation_result_view/recommendation_result_view.ts +++ b/frontend/app/components/overview_page/recommendation_result_view/recommendation_result_view.ts @@ -1,21 +1,9 @@ -import {Component} from '@angular/core'; -import {Store} from '@ngrx/store'; -import {DEFAULT_SIMPLE_DATA_TABLE} from 'org_xprof/frontend/app/common/interfaces/data_table'; -import {addAnchorTag, convertKnownToolToAnchorTag} from 'org_xprof/frontend/app/common/utils/utils'; -import {setCurrentToolStateAction} from 'org_xprof/frontend/app/store/actions'; +import {Component, Input, OnChanges, SimpleChanges} from '@angular/core'; +import {type RecommendationResult} from 'org_xprof/frontend/app/common/interfaces/data_table'; +import {convertKnownToolToAnchorTag} from 'org_xprof/frontend/app/common/utils/utils'; -import {RecommendationResultViewCommon} from './recommendation_result_view_common'; +import {StatementData, StatementInfo, TipInfo} from './recommendation_result_view_interfaces'; -const STATEMENT_INFO = [ - {id: 'outside_compilation_statement_html'}, - {id: 'eager_statement_html'}, - {id: 'tf_function_statement_html'}, - {id: 'statement'}, - {id: 'device_collectives_statement'}, - {id: 'kernel_launch_statement'}, - {id: 'all_other_statement'}, - {id: 'precision_statement'}, -]; /** A recommendation result view component. */ @Component({ @@ -24,91 +12,103 @@ const STATEMENT_INFO = [ templateUrl: './recommendation_result_view.ng.html', styleUrls: ['./recommendation_result_view.scss'] }) -export class RecommendationResultView extends RecommendationResultViewCommon { - constructor(private readonly store: Store<{}>) { - super(); +export class RecommendationResultView implements OnChanges { + /** The recommendation result data. */ + @Input() recommendationResult?: RecommendationResult; + @Input() statementInfo: StatementInfo[] = []; + /** The variable indicating whether this is an inference. */ + @Input() isInference = false; + /** The variable indicating whether this is an 3P build. */ + @Input() isOss = false; + + title = 'Recommendation for Next Step'; + statements: StatementData[] = []; + tipInfoArray: TipInfo[] = []; + + ngOnChanges(changes: SimpleChanges) { + this.parseStatements(); + this.parseTips(); } - getRecommendationResultProp(id: string, defaultValue: string = ''): string { - const props = (this.recommendationResult || {}).p || {}; + getRecommendationResultProp(id: string, defaultValue = ''): string { + const props = + ((this.recommendationResult || {}).p || {}) as Record; return props[id] || defaultValue; } - override parseStatements() { + parseStatements() { this.statements = []; - STATEMENT_INFO.forEach(info => { + if (this.isInference) { + return; + } + + this.statementInfo.forEach((info) => { const prop = this.getRecommendationResultProp(info.id); if (prop) { - this.statements.push({value: prop}); + const statement: StatementData = { + value: prop, + }; + if (info.color) { + statement.color = info.color; + } + this.statements.push(statement); } }); } - override parseTips() { - const data = this.recommendationResult || DEFAULT_SIMPLE_DATA_TABLE; - const hostTips: string[] = []; - const deviceTips: string[] = []; - const documentationTips: string[] = []; - const faqTips: string[] = []; - data.rows = data.rows || []; - data.rows.forEach(row => { - if (row.c && row.c[0] && row.c[0].v && row.c[1] && row.c[1].v) { - switch (row.c[0].v) { - case 'host': - hostTips.push(convertKnownToolToAnchorTag(String(row.c[1].v))); - break; - case 'device': - deviceTips.push(convertKnownToolToAnchorTag(String(row.c[1].v))); - break; - case 'doc': - documentationTips.push(String(row.c[1].v)); - break; - case 'faq': - faqTips.push(String(row.c[1].v)); - break; - default: - break; + parseTips() { + const tipInfoMap: {[tipType: string]: {title: string; tips: string[]}} = {}; + const rows = this.recommendationResult?.rows || []; + if (rows.length === 0) { + return; + } + rows.forEach((row: google.visualization.DataObjectRow) => { + const tipType = String(row.c?.[0]?.v); + const tipString = String(row.c?.[1]?.v); + if (tipType && tipString) { + const title = String(row.c?.[2]?.v); + const queryParams = new URLSearchParams(window.parent.location.search); + const run = queryParams.get('run') || ''; + const tip = this.isOss ? convertKnownToolToAnchorTag(tipString, run) : + tipString; + if (tipInfoMap[tipType]) { + tipInfoMap[tipType].tips.push(tip); + } else { + tipInfoMap[tipType] = {title, tips: [tip]}; } } }); - const bottleneck = this.getRecommendationResultProp('bottleneck'); - if (bottleneck === 'device') { - hostTips.length = 0; - } else if (bottleneck === 'host') { - deviceTips.length = 0; - } - const tipInfoArray = [ - { - title: 'Tool troubleshooting / FAQ', - tips: faqTips, - }, - { - title: 'Next tools to use for reducing the input time', - tips: hostTips, - useClickCallback: true, - }, - { - title: 'Next tools to use for reducing the Device time', - tips: deviceTips, - useClickCallback: true, - }, - { - title: 'Other useful resources', - tips: documentationTips, - }, - ]; - this.tipInfoArray = tipInfoArray.filter(tipInfo => tipInfo.tips.length > 0); - } + const inferenceTipStyle: {[key: string]: string} = { + 'color': 'navy', + 'font-weight': 'bolder', + }; + this.tipInfoArray = Object.entries(tipInfoMap).map(([tipType, tipInfo]) => { + const info: TipInfo = { + tipType, + title: tipInfo.title, + tips: tipInfo.tips, + }; + if (tipType === 'inference') { + info.style = inferenceTipStyle; + } + return info; + }); - override onTipsClick(event: Event) { - if (!event || !event.target || - (event.target as HTMLElement).tagName !== 'A') { - return; + // Filter out tips given bottleneck. + const nonBottleneckTipTypes = + this.getRecommendationResultProp('non_bottleneck_tip_types').split(','); + if (nonBottleneckTipTypes.length > 0) { + this.tipInfoArray = this.tipInfoArray.filter( + (tipInfo) => !nonBottleneckTipTypes.includes(tipInfo.tipType)); } - const tool = (event.target as HTMLElement).innerText; - if (convertKnownToolToAnchorTag(tool) === addAnchorTag(tool)) { - this.store.dispatch(setCurrentToolStateAction({currentTool: tool})); + // Move inference tips to the top for inference run, otherwise remove them. + const inferenceTips = + this.tipInfoArray.filter((tipInfo) => tipInfo.tipType === 'inference'); + this.tipInfoArray = + this.tipInfoArray.filter((tipInfo) => tipInfo.tipType !== 'inference'); + if (this.isInference) { + this.tipInfoArray = [...inferenceTips, ...this.tipInfoArray]; } } } diff --git a/frontend/app/components/overview_page/recommendation_result_view/recommendation_result_view_common.ts b/frontend/app/components/overview_page/recommendation_result_view/recommendation_result_view_common.ts deleted file mode 100644 index fe4807b3a..000000000 --- a/frontend/app/components/overview_page/recommendation_result_view/recommendation_result_view_common.ts +++ /dev/null @@ -1,36 +0,0 @@ -import {Directive, Input, OnChanges, SimpleChanges} from '@angular/core'; - -import {type RecommendationResult} from 'org_xprof/frontend/app/common/interfaces/data_table'; - -interface StatementData { - value: string; - color?: string; -} -interface TipInfo { - title: string; - style?: {[key: string]: string}; - tips: string[]; - useClickCallback?: boolean; -} - -/** A common class of recommendation result view component. */ -@Directive() -export class RecommendationResultViewCommon implements OnChanges { - /** The recommendation result data. */ - @Input() recommendationResult?: RecommendationResult; - - title = 'Recommendation for Next Step'; - statements: StatementData[] = []; - tipInfoArray: TipInfo[] = []; - - ngOnChanges(changes: SimpleChanges) { - this.parseStatements(); - this.parseTips(); - } - - parseStatements() {} - - parseTips() {} - - onTipsClick(event: Event) {} -} diff --git a/frontend/app/components/overview_page/recommendation_result_view/recommendation_result_view_interfaces.ts b/frontend/app/components/overview_page/recommendation_result_view/recommendation_result_view_interfaces.ts new file mode 100644 index 000000000..8410bd1f0 --- /dev/null +++ b/frontend/app/components/overview_page/recommendation_result_view/recommendation_result_view_interfaces.ts @@ -0,0 +1,23 @@ +/** + * Interface for statement info. + * Determines the id of the statement to be extracted from the + * recommendation result. + */ +export declare interface StatementInfo { + id: string; + color?: string; +} + +/** Interface for statement data. */ +export declare interface StatementData { + value: string; + color?: string; +} + +/** Interface for tip info. */ +export declare interface TipInfo { + title: string; + style?: {[key: string]: string}; + tips: string[]; + tipType: string; +} diff --git a/plugin/tensorboard_plugin_profile/convert/overview_page_proto_to_gviz.py b/plugin/tensorboard_plugin_profile/convert/overview_page_proto_to_gviz.py index 7e2a5d770..e4c256bc6 100644 --- a/plugin/tensorboard_plugin_profile/convert/overview_page_proto_to_gviz.py +++ b/plugin/tensorboard_plugin_profile/convert/overview_page_proto_to_gviz.py @@ -180,23 +180,32 @@ def get_recommendation_table_args(overview_page_recommendation): table_description = [ ("tip_type", "string", "tip_type"), ("link", "string", "link"), + ("description", "string", "description"), ] data = [] for faq_tip in overview_page_recommendation.faq_tips: - data.append(["faq", faq_tip.link]) + data.append(["faq", faq_tip.link, "Tool troubleshooting / FAQ"]) for host_tip in overview_page_recommendation.host_tips: - data.append(["host", host_tip.link]) + data.append([ + "host", + host_tip.link, + "Next steps for reducing the Host time", + ]) for device_tip in overview_page_recommendation.device_tips: - data.append(["device", device_tip.link]) + data.append( + ["device", device_tip.link, "Next steps for reducing the Device time"] + ) for doc_tip in overview_page_recommendation.documentation_tips: - data.append(["doc", doc_tip.link]) + data.append(["doc", doc_tip.link, "Other useful resources"]) for inference_tip in overview_page_recommendation.inference_tips: - data.append(["inference", inference_tip.link]) + data.append( + ["inference", inference_tip.link, "Recommendations for inference run"] + ) bottleneck = overview_page_recommendation.bottleneck statement = overview_page_recommendation.statement @@ -223,6 +232,11 @@ def get_recommendation_table_args(overview_page_recommendation): "precision_statement": precision_statement, } + # Prop used for data filtering in the frontend. + if bottleneck in ["host", "device"]: + non_bottleneck_tip_type = "device" if bottleneck == "host" else "host" + custom_properties["non_bottleneck_tip_types"] = non_bottleneck_tip_type + return (table_description, data, custom_properties) diff --git a/plugin/tensorboard_plugin_profile/convert/overview_page_proto_to_gviz_test.py b/plugin/tensorboard_plugin_profile/convert/overview_page_proto_to_gviz_test.py index 7eabc01d7..c5e6b0283 100644 --- a/plugin/tensorboard_plugin_profile/convert/overview_page_proto_to_gviz_test.py +++ b/plugin/tensorboard_plugin_profile/convert/overview_page_proto_to_gviz_test.py @@ -81,12 +81,21 @@ def setUpClass(cls): "MockTip", [ "tip_type", "link", + "description", ]) - + tip_description_map = { + "faq": "Tool troubleshooting / FAQ", + "host": "Next steps for reducing the Host time", + "device": "Next steps for reducing the Device time", + "doc": "Other useful resources", + "inference": "Recommendations for inference run", + } ProtoToGvizTest.mock_tips = [] for tip in ["faq", "host", "device", "doc"]: for idx in range(0, 3): - ProtoToGvizTest.mock_tips.append(MockTip(tip, tip + "_link" + str(idx))) + ProtoToGvizTest.mock_tips.append( + MockTip(tip, tip + "_link" + str(idx), tip_description_map[tip]) + ) # Checks that DataTable columns match schema defined in table_description. def check_header_row(self, data, table_description, row_values): @@ -290,7 +299,7 @@ def test_recommendation_empty(self): "Empty table should have 0 rows.") # Check the number of Overview Page Recommendation data table columns. # One for tip_type, and one for link - self.assertLen(data_table.columns, 2) + self.assertLen(data_table.columns, 3) def test_recommendation_simple(self): recommendation = self.create_mock_recommendation() @@ -305,8 +314,8 @@ def test_recommendation_simple(self): list(self.mock_tips), data_table.NumberOfRows(), "Simple table has 12 rows.") # Check the number of columns in table descriptor and data table. - self.assertLen(table_description, 2) - self.assertLen(data_table.columns, 2) + self.assertLen(table_description, 3) + self.assertLen(data_table.columns, 3) # Check data against mock values. for idx, row in enumerate(data):