• 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';
16
17import {isString} from '../base/object_utils';
18import {Icons} from '../base/semantic_icons';
19import {sqliteString} from '../base/string_utils';
20import {exists} from '../base/utils';
21import {ArgNode, convertArgsToTree, Key} from '../controller/args_parser';
22import {Engine} from '../trace_processor/engine';
23import {addVisualisedArgTracks} from './visualized_args_tracks';
24import {Anchor} from '../widgets/anchor';
25import {MenuItem, PopupMenu2} from '../widgets/menu';
26import {TreeNode} from '../widgets/tree';
27
28import {Arg} from './sql/args';
29import {addSqlTableTab} from './sql_table/tab';
30import {SqlTables} from './sql_table/well_known_tables';
31import {globals} from './globals';
32
33// Renders slice arguments (key/value pairs) as a subtree.
34export function renderArguments(engine: Engine, args: Arg[]): m.Children {
35  if (args.length > 0) {
36    const tree = convertArgsToTree(args);
37    return renderArgTreeNodes(engine, tree);
38  } else {
39    return undefined;
40  }
41}
42
43export function hasArgs(args?: Arg[]): args is Arg[] {
44  return exists(args) && args.length > 0;
45}
46
47function renderArgTreeNodes(engine: Engine, args: ArgNode<Arg>[]): m.Children {
48  return args.map((arg) => {
49    const {key, value, children} = arg;
50    if (children && children.length === 1) {
51      // If we only have one child, collapse into self and combine keys
52      const child = children[0];
53      const compositeArg = {
54        ...child,
55        key: stringifyKey(key, child.key),
56      };
57      return renderArgTreeNodes(engine, [compositeArg]);
58    } else {
59      return m(
60        TreeNode,
61        {
62          left: renderArgKey(engine, stringifyKey(key), value),
63          right: exists(value) && renderArgValue(value),
64          summary: children && renderSummary(children),
65        },
66        children && renderArgTreeNodes(engine, children),
67      );
68    }
69  });
70}
71
72function renderArgKey(engine: Engine, key: string, value?: Arg): m.Children {
73  if (value === undefined) {
74    return key;
75  } else {
76    const {key: fullKey, displayValue} = value;
77    return m(
78      PopupMenu2,
79      {trigger: m(Anchor, {icon: Icons.ContextMenu}, key)},
80      m(MenuItem, {
81        label: 'Copy full key',
82        icon: 'content_copy',
83        onclick: () => navigator.clipboard.writeText(fullKey),
84      }),
85      m(MenuItem, {
86        label: 'Find slices with same arg value',
87        icon: 'search',
88        onclick: () => {
89          addSqlTableTab({
90            table: SqlTables.slice,
91            filters: [
92              {
93                type: 'arg_filter',
94                argSetIdColumn: 'arg_set_id',
95                argName: fullKey,
96                op: `= ${sqliteString(displayValue)}`,
97              },
98            ],
99          });
100        },
101      }),
102      m(MenuItem, {
103        label: 'Visualise argument values',
104        icon: 'query_stats',
105        onclick: () => {
106          addVisualisedArgTracks(
107            {
108              engine,
109              registerTrack: (t) => globals.trackManager.registerTrack(t),
110            },
111            fullKey,
112          );
113        },
114      }),
115    );
116  }
117}
118
119function renderArgValue({value}: Arg): m.Children {
120  if (isWebLink(value)) {
121    return renderWebLink(value);
122  } else {
123    return `${value}`;
124  }
125}
126
127function renderSummary(children: ArgNode<Arg>[]): m.Children {
128  const summary = children
129    .slice(0, 2)
130    .map(({key}) => key)
131    .join(', ');
132  const remaining = children.length - 2;
133  if (remaining > 0) {
134    return `{${summary}, ... (${remaining} more items)}`;
135  } else {
136    return `{${summary}}`;
137  }
138}
139
140function stringifyKey(...key: Key[]): string {
141  return key
142    .map((element, index) => {
143      if (typeof element === 'number') {
144        return `[${element}]`;
145      } else {
146        return (index === 0 ? '' : '.') + element;
147      }
148    })
149    .join('');
150}
151
152function isWebLink(value: unknown): value is string {
153  return (
154    isString(value) &&
155    (value.startsWith('http://') || value.startsWith('https://'))
156  );
157}
158
159function renderWebLink(url: string): m.Children {
160  return m(Anchor, {href: url, target: '_blank', icon: 'open_in_new'}, url);
161}
162