• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright (C) 2023 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
15import m from 'mithril';
16import {duration, Time, time} from '../../base/time';
17import {hasArgs, renderArguments} from '../details/slice_args';
18import {getSlice, SliceDetails} from '../sql_utils/slice';
19import {asSliceSqlId, Utid} from '../sql_utils/core_types';
20import {getThreadState, ThreadState} from '../sql_utils/thread_state';
21import {DurationWidget} from '../widgets/duration';
22import {Timestamp} from '../widgets/timestamp';
23import {
24  ColumnType,
25  durationFromSql,
26  LONG,
27  STR,
28  timeFromSql,
29} from '../../trace_processor/query_result';
30import {sqlValueToReadableString} from '../../trace_processor/sql_utils';
31import {DetailsShell} from '../../widgets/details_shell';
32import {GridLayout} from '../../widgets/grid_layout';
33import {Section} from '../../widgets/section';
34import {dictToTree, dictToTreeNodes, Tree, TreeNode} from '../../widgets/tree';
35import {threadStateRef} from '../widgets/thread_state';
36import {getThreadName} from '../sql_utils/thread';
37import {getProcessName} from '../sql_utils/process';
38import {sliceRef} from '../widgets/slice';
39import {TrackEventDetailsPanel} from '../../public/details_panel';
40import {Trace} from '../../public/trace';
41import {SqlRef} from '../../widgets/sql_ref';
42
43export const ARG_PREFIX = 'arg_';
44
45function sqlValueToNumber(value?: ColumnType): number | undefined {
46  if (typeof value === 'bigint') return Number(value);
47  if (typeof value !== 'number') return undefined;
48  return value;
49}
50
51function sqlValueToUtid(value?: ColumnType): Utid | undefined {
52  if (typeof value === 'bigint') return Number(value) as Utid;
53  if (typeof value !== 'number') return undefined;
54  return value as Utid;
55}
56
57function renderTreeContents(dict: {[key: string]: m.Child}): m.Child[] {
58  const children: m.Child[] = [];
59  for (const key of Object.keys(dict)) {
60    if (dict[key] === null || dict[key] === undefined) continue;
61    children.push(
62      m(TreeNode, {
63        left: key,
64        right: dict[key],
65      }),
66    );
67  }
68  return children;
69}
70
71export class DebugSliceTrackDetailsPanel implements TrackEventDetailsPanel {
72  private data?: {
73    name: string;
74    ts: time;
75    dur: duration;
76    args: {[key: string]: ColumnType};
77  };
78  // We will try to interpret the arguments as references into well-known
79  // tables. These values will be set if the relevant columns exist and
80  // are consistent (e.g. 'ts' and 'dur' for this slice correspond to values
81  // in these well-known tables).
82  private threadState?: ThreadState;
83  private slice?: SliceDetails;
84
85  constructor(
86    private readonly trace: Trace,
87    private readonly tableName: string,
88    private readonly eventId: number,
89  ) {}
90
91  private async maybeLoadThreadState(
92    id: number | undefined,
93    ts: time,
94    dur: duration,
95    table: string | undefined,
96    utid?: Utid,
97  ): Promise<ThreadState | undefined> {
98    if (id === undefined) return undefined;
99    if (utid === undefined) return undefined;
100
101    const threadState = await getThreadState(this.trace.engine, id);
102    if (threadState === undefined) return undefined;
103    if (
104      table === 'thread_state' ||
105      (threadState.ts === ts &&
106        threadState.dur === dur &&
107        threadState.thread?.utid === utid)
108    ) {
109      return threadState;
110    } else {
111      return undefined;
112    }
113  }
114
115  private renderThreadStateInfo(): m.Child {
116    if (this.threadState === undefined) return null;
117    return m(
118      TreeNode,
119      {
120        left: threadStateRef(this.threadState),
121        right: '',
122      },
123      renderTreeContents({
124        Thread: getThreadName(this.threadState.thread),
125        Process: getProcessName(this.threadState.thread?.process),
126        State: this.threadState.state,
127      }),
128    );
129  }
130
131  private async maybeLoadSlice(
132    id: number | undefined,
133    ts: time,
134    dur: duration,
135    table: string | undefined,
136    trackId?: number,
137  ): Promise<SliceDetails | undefined> {
138    if (id === undefined) return undefined;
139    if (table !== 'slice' && trackId === undefined) return undefined;
140
141    const slice = await getSlice(this.trace.engine, asSliceSqlId(id));
142    if (slice === undefined) return undefined;
143    if (
144      table === 'slice' ||
145      (slice.ts === ts && slice.dur === dur && slice.trackId === trackId)
146    ) {
147      return slice;
148    } else {
149      return undefined;
150    }
151  }
152
153  private renderSliceInfo(): m.Child {
154    if (this.slice === undefined) return null;
155    return m(
156      TreeNode,
157      {
158        left: sliceRef(this.slice, 'Slice'),
159        right: '',
160      },
161      m(TreeNode, {
162        left: 'Name',
163        right: this.slice.name,
164      }),
165      m(TreeNode, {
166        left: 'Thread',
167        right: getThreadName(this.slice.thread),
168      }),
169      m(TreeNode, {
170        left: 'Process',
171        right: getProcessName(this.slice.process),
172      }),
173      hasArgs(this.slice.args) &&
174        m(
175          TreeNode,
176          {
177            left: 'Args',
178          },
179          renderArguments(this.trace, this.slice.args),
180        ),
181    );
182  }
183
184  async load() {
185    const queryResult = await this.trace.engine.query(
186      `select * from ${this.tableName} where id = ${this.eventId}`,
187    );
188    const row = queryResult.firstRow({
189      ts: LONG,
190      dur: LONG,
191      name: STR,
192    });
193    this.data = {
194      name: row.name,
195      ts: Time.fromRaw(row.ts),
196      dur: row.dur,
197      args: {},
198    };
199
200    for (const key of Object.keys(row)) {
201      if (key.startsWith(ARG_PREFIX)) {
202        this.data.args[key.substr(ARG_PREFIX.length)] = (
203          row as {[key: string]: ColumnType}
204        )[key];
205      }
206    }
207
208    this.threadState = await this.maybeLoadThreadState(
209      sqlValueToNumber(this.data.args['id']),
210      this.data.ts,
211      this.data.dur,
212      sqlValueToReadableString(this.data.args['table_name']),
213      sqlValueToUtid(this.data.args['utid']),
214    );
215
216    this.slice = await this.maybeLoadSlice(
217      sqlValueToNumber(this.data.args['id']) ??
218        sqlValueToNumber(this.data.args['slice_id']),
219      this.data.ts,
220      this.data.dur,
221      sqlValueToReadableString(this.data.args['table_name']),
222      sqlValueToNumber(this.data.args['track_id']),
223    );
224  }
225
226  render() {
227    if (this.data === undefined) {
228      return m('h2', 'Loading');
229    }
230    const details = dictToTreeNodes({
231      'Name': this.data['name'] as string,
232      'Start time': m(Timestamp, {ts: timeFromSql(this.data['ts'])}),
233      'Duration': m(DurationWidget, {dur: durationFromSql(this.data['dur'])}),
234      'SQL ID': m(SqlRef, {table: this.tableName, id: this.eventId}),
235    });
236    details.push(this.renderThreadStateInfo());
237    details.push(this.renderSliceInfo());
238
239    const args: {[key: string]: m.Child} = {};
240    for (const key of Object.keys(this.data.args)) {
241      args[key] = sqlValueToReadableString(this.data.args[key]);
242    }
243
244    return m(
245      DetailsShell,
246      {
247        title: 'Slice',
248      },
249      m(
250        GridLayout,
251        m(Section, {title: 'Details'}, m(Tree, details)),
252        m(Section, {title: 'Arguments'}, dictToTree(args)),
253      ),
254    );
255  }
256
257  getTitle(): string {
258    return `Current Selection`;
259  }
260
261  isLoading() {
262    return this.data === undefined;
263  }
264}
265