Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions frontend/app/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ xprof_ng_module(
],
deps = [
"@npm//@angular/core",
"@npm//@angular/forms",
"@npm//@angular/platform-browser",
"@npm//@ngrx/store",
"@npm//rxjs",
Expand Down
26 changes: 23 additions & 3 deletions frontend/app/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,31 @@ export class App implements OnInit {
this.loading = false;
return;
}

let filteredRuns = runs;

const config = await firstValueFrom(this.dataService.getConfig());
if (config?.filterSessions) {
try {
const regex = new RegExp(config.filterSessions, 'i');
filteredRuns = runs.filter(run => regex.test(run));
} catch (e) {
console.error('Invalid regex for session filter:', e);
// fallback to no filtering if regex is invalid
filteredRuns = runs;
}

// fallback to no filtering if regex finds nothing
if (filteredRuns.length === 0) {
filteredRuns = runs;
}
}

this.dataFound = true;
this.store.dispatch(actions.setCurrentRunAction({currentRun: runs[0]}));
this.store.dispatch(actions.setCurrentRunAction({currentRun: filteredRuns[0]}));
const tools =
await firstValueFrom(this.dataService.getRunTools(runs[0])) as string[];
const runToolsMap: RunToolsMap = {[runs[0]]: tools};
await firstValueFrom(this.dataService.getRunTools(filteredRuns[0])) as string[];
const runToolsMap: RunToolsMap = {[filteredRuns[0]]: tools};
for (let i = 1; i < runs.length; i++) {
runToolsMap[runs[i]] = [];
}
Expand Down
2 changes: 2 additions & 0 deletions frontend/app/app_module.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {HttpClientModule} from '@angular/common/http';
import {NgModule} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {MatProgressBarModule} from '@angular/material/progress-bar';
import {BrowserModule} from '@angular/platform-browser';
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
Expand All @@ -21,6 +22,7 @@ import {App} from './app';
imports: [
BrowserModule,
HttpClientModule,
FormsModule,
MatProgressBarModule,
EmptyPageModule,
MainPageModule,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export class ViewArchitecture implements OnInit, OnDestroy {
private readonly dataService: DataServiceV2Interface =
inject(DATA_SERVICE_INTERFACE_TOKEN);
hideViewArchitectureButton = true;
filterSessions = '';
private readonly destroyed = new ReplaySubject<void>(1);

ngOnInit() {
Expand All @@ -28,6 +29,7 @@ export class ViewArchitecture implements OnInit, OnDestroy {
.subscribe((config) => {
this.hideViewArchitectureButton =
config?.hideCaptureProfileButton || false;
this.filterSessions = config?.filterSessions || '';
});
}

Expand Down
4 changes: 4 additions & 0 deletions frontend/app/components/sidenav/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,18 @@ xprof_ng_module(
],
deps = [
"@npm//@angular/core",
"@npm//@angular/forms",
"@npm//@angular/router",
"@npm//@ngrx/store",
"@npm//rxjs",
"@org_xprof//frontend/app/common/angular:angular_material_button",
"@org_xprof//frontend/app/common/angular:angular_material_checkbox",
"@org_xprof//frontend/app/common/angular:angular_material_core",
"@org_xprof//frontend/app/common/angular:angular_material_form_field",
"@org_xprof//frontend/app/common/angular:angular_material_icon",
"@org_xprof//frontend/app/common/angular:angular_material_input",
"@org_xprof//frontend/app/common/angular:angular_material_select",
"@org_xprof//frontend/app/common/angular:angular_material_tooltip",
"@org_xprof//frontend/app/common/constants",
"@org_xprof//frontend/app/common/interfaces",
"@org_xprof//frontend/app/components/capture_profile",
Expand Down
30 changes: 28 additions & 2 deletions frontend/app/components/sidenav/sidenav.ng.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,40 @@
</div>

<!-- TODO(bhupendradubey): Rename internal variables and function names (e.g., `runs`, `selectedRun`, `onRunSelectionChange`) to use `session` instead of `run` for consistency. -->
<div class="item-container" *ngIf="runs.length > 1">
<div [ngClass]="{'mat-subheading-2': true}">
Filter Sessions
<mat-icon
class="tooltip-icon"
matTooltip=
"Input a regex to filter the sessions. Press 'Enter' to select the first filtered
session or select the session you want from the dropdown."
matTooltipPosition="right">
info
</mat-icon>
</div>

<mat-form-field class="full-width" appearance="outline">
<mat-icon matPrefix>search</mat-icon>
<input
matInput
placeholder="Filter sessions"
aria-label="Filter sessions"
[(ngModel)]="filterSessions"
(keyup)="filterRuns()"
(keyup.enter)="applyFilterAndSelectFirst()"
/>
</mat-form-field>
</div>

<div class="item-container">
<div [ngClass]="{'mat-subheading-2': true, 'disabled': !runs.length}">
Sessions ({{runs.length}})
Sessions ({{filteredRuns.length}}/{{runs.length}})
</div>

<mat-form-field class="full-width" appearance="outline">
<mat-select panelClass="panel-override" [value]="selectedRun" [disabled]="!runs.length" (selectionChange)="onRunSelectionChange($event.value)">
<mat-option *ngFor="let run of runs" [value]="run">
<mat-option *ngFor="let run of filteredRuns" [value]="run">
{{run}}
</mat-option>
</mat-select>
Expand Down
12 changes: 11 additions & 1 deletion frontend/app/components/sidenav/sidenav.scss
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,17 @@
padding: 20px 20px 0 20px;
}

.tooltip-icon {
color: #e8710a;
font-size: 25px;
vertical-align: bottom;
cursor: pointer;
}

.mat-form-field {
margin-bottom: 10px;
}

.mat-subheading-2 {
margin-bottom: 0;
}
Expand All @@ -21,7 +32,6 @@

// Target the custom panel class defined in sidenav.ng.html
.multi-host-select-panel {

.full-width {
width: 100%;
}
Expand Down
32 changes: 29 additions & 3 deletions frontend/app/components/sidenav/sidenav.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export class SideNav implements OnInit, OnDestroy {

runToolsMap: RunToolsMap = {};
runs: string[] = [];
filteredRuns: string[] = [];
tags: string[] = [];
hosts: string[] = [];
moduleList: string[] = [];
Expand All @@ -41,6 +42,7 @@ export class SideNav implements OnInit, OnDestroy {
allHostsSelected = false;

hideCaptureProfileButton = false;
filterSessions = '';

constructor(
private readonly router: Router,
Expand All @@ -58,6 +60,7 @@ export class SideNav implements OnInit, OnDestroy {
this.runToolsMap$.subscribe((runTools: RunToolsMap) => {
this.runToolsMap = runTools;
this.runs = Object.keys(this.runToolsMap);
this.filterRuns();
});
this.currentRun$.subscribe(run => {
if (run && !this.selectedRunInternal) {
Expand All @@ -66,6 +69,21 @@ export class SideNav implements OnInit, OnDestroy {
});
}

filterRuns(): void {
if (!this.filterSessions) {
this.filteredRuns = this.runs;
return;
}
try {
const regex = new RegExp(this.filterSessions, 'i');
this.filteredRuns = this.runs.filter(run => regex.test(run));
} catch (e) {
console.error('Invalid regex for session filter:', e);
// fallback to no filtering or previous state if regex is invalid
this.filteredRuns = this.runs;
}
}

get is_hlo_tool() {
return HLO_TOOLS.includes(this.selectedTag);
}
Expand All @@ -77,8 +95,9 @@ export class SideNav implements OnInit, OnDestroy {

// Getter for valid run given url router or user selection.
get selectedRun() {
return this.runs.find(validRun => validRun === this.selectedRunInternal) ||
this.runs[0] || '';
return this.filteredRuns.find(
validRun => validRun === this.selectedRunInternal) ||
this.filteredRuns[0] || '';
}

// Getter for valid tag given url router or user selection.
Expand Down Expand Up @@ -165,7 +184,8 @@ export class SideNav implements OnInit, OnDestroy {
const config = await firstValueFrom(
this.dataService.getConfig().pipe(takeUntil(this.destroyed)));
if (config) {
this.hideCaptureProfileButton = config.hideCaptureProfileButton;
this.filterSessions = config.filterSessions;
this.filterRuns();
}
}

Expand Down Expand Up @@ -246,6 +266,12 @@ export class SideNav implements OnInit, OnDestroy {
return response.split(',');
}

applyFilterAndSelectFirst() {
if (this.filteredRuns.length > 0) {
this.onRunSelectionChange(this.filteredRuns[0]);
}
}

onRunSelectionChange(run: string) {
this.selectedRunInternal = run;
this.afterUpdateRun();
Expand Down
8 changes: 8 additions & 0 deletions frontend/app/components/sidenav/sidenav_module.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import {CommonModule} from '@angular/common';
import {NgModule} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {MatButtonModule} from '@angular/material/button';
import {MatCheckboxModule} from '@angular/material/checkbox';
import {MatOptionModule} from '@angular/material/core';
import {MatFormFieldModule} from '@angular/material/form-field';
import {MatIconModule} from '@angular/material/icon';
import {MatInputModule} from '@angular/material/input';
import {MatSelectModule} from '@angular/material/select';
import {MatTooltipModule} from '@angular/material/tooltip';
import {CaptureProfileModule} from 'org_xprof/frontend/app/components/capture_profile/capture_profile_module';
import {BufferDetailsModule} from 'org_xprof/frontend/app/components/memory_viewer/buffer_details/buffer_details_module';
import {OpDetailsModule} from 'org_xprof/frontend/app/components/op_profile/op_details/op_details_module';
Expand All @@ -18,10 +22,14 @@ import {SideNav} from './sidenav';
imports: [
CommonModule,
MatButtonModule,
FormsModule,
MatCheckboxModule,
MatFormFieldModule,
MatIconModule,
MatInputModule,
MatSelectModule,
MatOptionModule,
MatTooltipModule,
BufferDetailsModule,
CaptureProfileModule,
OpDetailsModule,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {type SmartSuggestionReport} from 'org_xprof/frontend/app/common/interfac
/** A serializable object with profiler configuration details. */
export interface ProfilerConfig {
hideCaptureProfileButton: boolean;
filterSessions: string;
}

/** The data service class that calls API and return response. */
Expand Down
2 changes: 2 additions & 0 deletions plugin/xprof/profile_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -474,6 +474,7 @@ def __init__(self, context):
self.hide_capture_profile_button = getattr(
context, 'hide_capture_profile_button', False
)
self.filter_sessions = getattr(context, 'filter_sessions', '')

# Whether the plugin is active. This is an expensive computation, so we
# compute this asynchronously and cache positive results indefinitely.
Expand Down Expand Up @@ -535,6 +536,7 @@ def config_route(self, request: wrappers.Request) -> wrappers.Response:
logger.info('config_route: %s', self.logdir)
config_data = {
'hideCaptureProfileButton': self.hide_capture_profile_button,
'filterSessions': self.filter_sessions,
}
return respond(config_data, 'application/json')

Expand Down
13 changes: 13 additions & 0 deletions plugin/xprof/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ class ServerConfig:
grpc_port: int
worker_service_address: str
hide_capture_profile_button: bool
filter_sessions: str


def make_wsgi_app(plugin):
Expand Down Expand Up @@ -147,6 +148,7 @@ def _launch_server(
config.logdir, DataProvider(config.logdir), TBContext.Flags(False)
)
context.hide_capture_profile_button = config.hide_capture_profile_button
context.filter_sessions = config.filter_sessions
loader = ProfilePluginLoader()
plugin = loader.load(context)
run_server(plugin, _get_wildcard_address(config.port), config.port)
Expand Down Expand Up @@ -254,6 +256,15 @@ def _create_argument_parser() -> argparse.ArgumentParser:
" main server port (--port)."
),
)

parser.add_argument(
"-fs",
"--filter_sessions",
type=str,
default="",
help="A regex to filter the available sessions (defaults to no filter).",
)

return parser


Expand Down Expand Up @@ -289,13 +300,15 @@ def main() -> int:
grpc_port=args.grpc_port,
worker_service_address=worker_service_address,
hide_capture_profile_button=args.hide_capture_profile_button,
filter_sessions=args.filter_sessions,
)

print("Attempting to start XProf server:")
print(f" Log Directory: {logdir}")
print(f" Port: {config.port}")
print(f" Worker Service Address: {config.worker_service_address}")
print(f" Hide Capture Button: {config.hide_capture_profile_button}")
print(f" Filter Sessions: {config.filter_sessions}")

if logdir and not epath.Path(logdir).exists():
print(
Expand Down
Loading