Skip to content

Commit a3afd08

Browse files
committed
ui: Add Linux section and Journald config to Record new trace page
This adds a new `Linux` section to the `Record new trace` page in the UI, adding the `linux.journald` datasource as the first section. In order to properly allow configuration of the minimum priority to capture, a `dropdown` widget is added.
1 parent fe0b1b5 commit a3afd08

4 files changed

Lines changed: 174 additions & 0 deletions

File tree

ui/src/plugins/dev.perfetto.RecordTraceV2/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import {chromeRecordSection} from './pages/chrome';
2525
import {cpuRecordSection} from './pages/cpu';
2626
import {gpuRecordSection} from './pages/gpu';
2727
import {instructionsPage} from './pages/instructions_page';
28+
import {linuxRecordSection} from './pages/linux';
2829
import {memoryRecordSection} from './pages/memory';
2930
import {powerRecordSection} from './pages/power';
3031
import {RecordPageV2} from './pages/record_page';
@@ -118,6 +119,7 @@ export default class implements PerfettoPlugin {
118119
gpuRecordSection(),
119120
powerRecordSection(),
120121
memoryRecordSection(),
122+
linuxRecordSection(),
121123
androidRecordSection(),
122124
perfettoSDKRecordSection(),
123125
stackSamplingRecordSection(),
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
// Copyright (C) 2026 The Android Open Source Project
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
import {RecordProbe, RecordSubpage} from '../config/config_interfaces';
16+
import {TraceConfigBuilder} from '../config/trace_config_builder';
17+
import {Dropdown} from './widgets/dropdown';
18+
import {Textarea} from './widgets/textarea';
19+
20+
const JOURNALD_DS = 'linux.journald';
21+
22+
const MIN_PRIO_OPTIONS = [
23+
{value: 0, label: 'EMERG'},
24+
{value: 1, label: 'ALERT'},
25+
{value: 2, label: 'CRIT'},
26+
{value: 3, label: 'ERR'},
27+
{value: 4, label: 'WARNING'},
28+
{value: 5, label: 'NOTICE'},
29+
{value: 6, label: 'INFO'},
30+
{value: 7, label: 'DEBUG'},
31+
] as const;
32+
33+
export function linuxRecordSection(): RecordSubpage {
34+
return {
35+
kind: 'PROBES_PAGE',
36+
id: 'linux',
37+
title: 'Linux',
38+
subtitle: 'Linux-specific data sources',
39+
icon: 'dns',
40+
probes: [journald()],
41+
};
42+
}
43+
44+
function journald(): RecordProbe {
45+
const settings = {
46+
minPrio: new Dropdown<number>({
47+
title: 'Minimum syslog priority',
48+
options: MIN_PRIO_OPTIONS,
49+
defaultValue: 7,
50+
}),
51+
identifiers: new Textarea({
52+
title: 'Process name (SYSLOG_IDENTIFIER) to record',
53+
placeholder: 'One identifier per line\nsshd\nmy-service',
54+
}),
55+
units: new Textarea({
56+
title: 'systemd units to record',
57+
placeholder:
58+
'One unit per line\nsystemd-journald.service\nuser@1000.service',
59+
}),
60+
};
61+
62+
return {
63+
id: 'journald',
64+
title: 'Journald log messages',
65+
description: 'Records log messages from systemd-journald.',
66+
supportedPlatforms: ['LINUX'],
67+
settings,
68+
genConfig(tc: TraceConfigBuilder) {
69+
const ds = tc.addDataSource(JOURNALD_DS);
70+
const journaldConfig = (ds.journaldConfig ??= {});
71+
journaldConfig.minPrio = settings.minPrio.value;
72+
73+
const identifiers = settings.identifiers.text
74+
.split('\n')
75+
.map((line) => line.trim())
76+
.filter((line) => line.length > 0);
77+
if (identifiers.length > 0) {
78+
journaldConfig.filterIdentifiers = identifiers;
79+
}
80+
const units = settings.units.text
81+
.split('\n')
82+
.map((line) => line.trim())
83+
.filter((line) => line.length > 0);
84+
if (units.length > 0) {
85+
journaldConfig.filterUnits = units;
86+
}
87+
},
88+
};
89+
}

ui/src/plugins/dev.perfetto.RecordTraceV2/pages/record_page.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@ export class RecordPageV2 implements m.ClassComponent<RecordPageAttrs> {
155155
memory: 40,
156156
android: 50,
157157
network: 60,
158+
linux: 65,
158159
chrome: 70,
159160
stack_sampling: 80,
160161
perfetto_sdk: 90,
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
// Copyright (C) 2026 The Android Open Source Project
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
import m from 'mithril';
16+
import {assertTrue} from '../../../../base/assert';
17+
import {ProbeSetting} from '../../config/config_interfaces';
18+
import {Select} from '../../../../widgets/select';
19+
20+
export interface DropdownOption<T extends string | number> {
21+
readonly value: T;
22+
readonly label: string;
23+
}
24+
25+
export interface DropdownAttrs<T extends string | number> {
26+
readonly title: string;
27+
readonly options: ReadonlyArray<DropdownOption<T>>;
28+
readonly defaultValue?: T;
29+
}
30+
31+
export class Dropdown<T extends string | number> implements ProbeSetting {
32+
private _value: T;
33+
34+
constructor(readonly attrs: DropdownAttrs<T>) {
35+
assertTrue(attrs.options.length > 0);
36+
this._value = attrs.defaultValue ?? attrs.options[0].value;
37+
}
38+
39+
serialize() {
40+
return this._value;
41+
}
42+
43+
deserialize(state: unknown): void {
44+
if (typeof state !== 'string' && typeof state !== 'number') {
45+
return;
46+
}
47+
const option = this.attrs.options.find(
48+
(candidate) => candidate.value === state,
49+
);
50+
if (option) {
51+
this._value = option.value;
52+
}
53+
}
54+
55+
get value(): T {
56+
return this._value;
57+
}
58+
59+
render() {
60+
return m('.textarea-holder', [
61+
m('header', this.attrs.title),
62+
m(
63+
Select,
64+
{
65+
value: String(this._value),
66+
onchange: (e: Event) => {
67+
const selectedValue = (e.target as HTMLSelectElement).value;
68+
const option = this.attrs.options.find(
69+
(candidate) => String(candidate.value) === selectedValue,
70+
);
71+
if (option) {
72+
this._value = option.value;
73+
}
74+
},
75+
},
76+
this.attrs.options.map((option) =>
77+
m('option', {value: String(option.value)}, option.label),
78+
),
79+
),
80+
]);
81+
}
82+
}

0 commit comments

Comments
 (0)