| // Copyright (C) 2026 The Android Open Source Project |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| import m from 'mithril'; |
| import {assertExists} from '../../base/assert'; |
| import type {Trace} from '../../public/trace'; |
| import type {QueryLog} from '../../trace_processor/engine'; |
| import {Button} from '../../widgets/button'; |
| import {Chip} from '../../widgets/chip'; |
| import {Intent} from '../../widgets/common'; |
| import {CopyToClipboardButton} from '../../widgets/copy_to_clipboard_button'; |
| import {DetailsShell} from '../../widgets/details_shell'; |
| |
| function formatMillis(millis: number) { |
| return millis.toFixed(1); |
| } |
| |
| interface QueryLogEntryAttrs { |
| readonly queryLog: QueryLog; |
| } |
| |
| class QueryLogRow implements m.ClassComponent<QueryLogEntryAttrs> { |
| private isOpen = false; |
| |
| view({attrs}: m.Vnode<QueryLogEntryAttrs>) { |
| const {queryLog: ql} = attrs; |
| assertExists(ql.query); |
| const fullQuery = ql.query.trim(); |
| const isOpen = this.isOpen; |
| const toggle = () => { |
| this.isOpen = !this.isOpen; |
| }; |
| const statusChip = |
| ql.success === undefined |
| ? m(Chip, {label: 'Running', intent: Intent.Warning, compact: true}) |
| : ql.success |
| ? m(Chip, {label: 'Completed', intent: Intent.Success, compact: true}) |
| : m(Chip, {label: 'Failed', intent: Intent.Danger, compact: true}); |
| return m( |
| 'tr.pf-query-log-row', |
| {class: isOpen ? 'pf-expanded' : ''}, |
| m('td.pf-query-log-num', formatMillis(ql.startTime)), |
| m( |
| 'td.pf-query-log-num', |
| ql.elapsedTimeMs === undefined ? '...' : formatMillis(ql.elapsedTimeMs), |
| ), |
| m('td', ql.tag), |
| m('td', statusChip), |
| m( |
| 'td.pf-query-log-toggle', |
| m(Button, { |
| icon: isOpen ? 'expand_more' : 'chevron_right', |
| compact: true, |
| onclick: toggle, |
| }), |
| ), |
| m( |
| 'td.pf-query-log-query', |
| { |
| title: isOpen ? undefined : fullQuery, |
| onclick: toggle, |
| }, |
| isOpen |
| ? m( |
| '.pf-query-log-expanded-body', |
| m( |
| '.pf-query-log-expanded-toolbar', |
| {onclick: (e: Event) => e.stopPropagation()}, |
| m(CopyToClipboardButton, { |
| textToCopy: fullQuery, |
| label: 'Copy', |
| compact: true, |
| }), |
| ), |
| m('span.pf-query-log-full', fullQuery), |
| ) |
| : m('span.pf-query-log-snippet', fullQuery.replace(/\s+/g, ' ')), |
| ), |
| ); |
| } |
| } |
| |
| export interface QueryTabAttrs { |
| readonly trace: Trace; |
| } |
| |
| const PAGE_SIZE = 100; |
| |
| export class QueryTab implements m.ClassComponent<QueryTabAttrs> { |
| private visibleCount = PAGE_SIZE; |
| |
| view({attrs}: m.Vnode<QueryTabAttrs>) { |
| const {trace} = attrs; |
| // Show the logs in reverse order |
| const queryLog = Array.from(trace.engine.queryLog).reverse(); |
| const visible = queryLog.slice(0, this.visibleCount); |
| const remaining = queryLog.length - visible.length; |
| return m( |
| DetailsShell, |
| { |
| title: 'Query Log', |
| description: `${queryLog.length} ${queryLog.length === 1 ? 'entry' : 'entries'}`, |
| buttons: m(Button, { |
| label: 'Clear', |
| icon: 'delete', |
| disabled: queryLog.length === 0, |
| onclick: () => trace.engine.clearQueryLog(), |
| }), |
| }, |
| m( |
| '.pf-query-log', |
| m( |
| 'table.pf-query-log-table', |
| m( |
| 'thead', |
| m( |
| 'tr', |
| m('th.pf-query-log-num', 'Start (ms)'), |
| m('th.pf-query-log-num', 'Duration (ms)'), |
| m('th', 'Tag'), |
| m('th', 'Status'), |
| m('th'), |
| m('th', 'Query'), |
| ), |
| ), |
| m( |
| 'tbody', |
| visible.map((ql) => m(QueryLogRow, {queryLog: ql})), |
| ), |
| ), |
| remaining > 0 && |
| m( |
| '.pf-query-log-show-more', |
| m(Button, { |
| label: `Show ${Math.min(PAGE_SIZE, remaining)} more (${remaining} remaining)`, |
| onclick: () => { |
| this.visibleCount += PAGE_SIZE; |
| }, |
| }), |
| ), |
| ), |
| ); |
| } |
| } |