Skip to content

Commit

Permalink
feat: add expensive-queries, format number, refactor menu and data-table
Browse files Browse the repository at this point in the history
  • Loading branch information
duyet committed Nov 22, 2023
1 parent 05fdd05 commit d62d721
Show file tree
Hide file tree
Showing 10 changed files with 284 additions and 32 deletions.
175 changes: 175 additions & 0 deletions app/[name]/clickhouse-queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,141 @@ export const queries: Array<QueryConfig> = [
],
],
},
{
name: 'history-queries',
sql: `
SELECT
type,
query_id,
query_duration_ms,
query_duration_ms as query_duration,
event_time,
query,
formatted_query AS readable_query,
user,
read_rows,
formatReadableQuantity(read_rows) AS readable_read_rows,
written_rows,
formatReadableQuantity(written_rows) AS readable_written_rows,
result_rows,
formatReadableQuantity(result_rows) AS readable_result_rows,
memory_usage,
formatReadableSize(memory_usage) AS readable_memory_usage,
query_kind,
client_name
FROM system.query_log
WHERE type != 'QueryStart'
ORDER BY event_time DESC
LIMIT 1000
`,
columns: [
'user',
'query',
'event_time',
'query_id',
'query_duration',
'user',
'readable_read_rows',
'readable_written_rows',
'readable_result_rows',
'readable_memory_usage',
'query_kind',
'client_name',
],
columnFormats: {
query_duration: ColumnFormat.Duration,
readable_query: ColumnFormat.Code,
query: ColumnFormat.Code,
event_time: ColumnFormat.RelatedTime,
},

relatedCharts: [
[
'query-count',
{
title: 'Total Running Queries over last 14 days (query / day)',
interval: 'toStartOfDay',
lastHours: 24 * 14,
},
],
],
},
{
name: 'expensive-queries',
description: 'Most expensive queries finished over last 24 hours',
sql: `
SELECT
normalized_query_hash,
replace(substr(argMax(query, utime), 1, 200), '\n', ' ') AS query,
count() AS cnt,
sum(query_duration_ms) / 1000 AS queries_duration,
sum(ProfileEvents.Values[indexOf(ProfileEvents.Names, 'RealTimeMicroseconds')]) / 1000000 AS real_time,
sum(ProfileEvents.Values[indexOf(ProfileEvents.Names, 'UserTimeMicroseconds')] AS utime) / 1000000 AS user_time,
sum(ProfileEvents.Values[indexOf(ProfileEvents.Names, 'SystemTimeMicroseconds')]) / 1000000 AS system_time,
sum(ProfileEvents.Values[indexOf(ProfileEvents.Names, 'DiskReadElapsedMicroseconds')]) / 1000000 AS disk_read_time,
sum(ProfileEvents.Values[indexOf(ProfileEvents.Names, 'DiskWriteElapsedMicroseconds')]) / 1000000 AS disk_write_time,
sum(ProfileEvents.Values[indexOf(ProfileEvents.Names, 'NetworkSendElapsedMicroseconds')]) / 1000000 AS network_send_time,
sum(ProfileEvents.Values[indexOf(ProfileEvents.Names, 'NetworkReceiveElapsedMicroseconds')]) / 1000000 AS network_receive_time,
sum(ProfileEvents.Values[indexOf(ProfileEvents.Names, 'ZooKeeperWaitMicroseconds')]) / 1000000 AS zookeeper_wait_time,
sum(ProfileEvents.Values[indexOf(ProfileEvents.Names, 'OSIOWaitMicroseconds')]) / 1000000 AS os_io_wait_time,
sum(ProfileEvents.Values[indexOf(ProfileEvents.Names, 'OSCPUWaitMicroseconds')]) / 1000000 AS os_cpu_wait_time,
sum(ProfileEvents.Values[indexOf(ProfileEvents.Names, 'OSCPUVirtualTimeMicroseconds')]) / 1000000 AS os_cpu_virtual_time,
sum(read_rows) AS read_rows,
formatReadableSize(sum(read_bytes)) AS read_bytes,
sum(written_rows) AS written_rows,
formatReadableSize(sum(written_bytes)) AS written_bytes,
sum(result_rows) AS result_rows,
formatReadableSize(sum(result_bytes)) AS result_bytes
FROM system.query_log
WHERE (event_time > (now() - interval 24 hours)) AND (type IN (2, 4))
GROUP BY
GROUPING SETS (
(normalized_query_hash),
())
ORDER BY user_time DESC
LIMIT 200
`,
columns: [
'query',
'cnt',
'queries_duration',
'real_time',
'user_time',
'system_time',
'disk_read_time',
'disk_write_time',
'network_send_time',
'network_receive_time',
'zookeeper_wait_time',
'os_io_wait_time',
'os_cpu_wait_time',
'os_cpu_virtual_time',
'read_rows',
'read_bytes',
'written_rows',
'written_bytes',
'result_rows',
'result_bytes',
],
columnFormats: {
queries_duration: ColumnFormat.Duration,
real_time: ColumnFormat.Duration,
user_time: ColumnFormat.Duration,
system_time: ColumnFormat.Duration,
disk_read_time: ColumnFormat.Duration,
disk_write_time: ColumnFormat.Duration,
network_send_time: ColumnFormat.Duration,
network_receive_time: ColumnFormat.Duration,
zookeeper_wait_time: ColumnFormat.Duration,
os_io_wait_time: ColumnFormat.Duration,
os_cpu_wait_time: ColumnFormat.Duration,
os_cpu_virtual_time: ColumnFormat.Duration,
read_rows: ColumnFormat.Number,
written_rows: ColumnFormat.Number,
result_rows: ColumnFormat.Number,
},
relatedCharts: [],
},
{
name: 'merges',
sql: `
Expand Down Expand Up @@ -103,6 +238,46 @@ export const queries: Array<QueryConfig> = [
default: ColumnFormat.Code,
},
},
{
name: 'settings',
sql: `
SELECT *
FROM system.settings
ORDER BY name
`,
columns: ['name', 'value', 'changed', 'description', 'default'],
columnFormats: {
name: ColumnFormat.Code,
changed: ColumnFormat.Boolean,
value: ColumnFormat.Code,
default: ColumnFormat.Code,
},
},
{
name: 'mergetree-settings',
sql: `
SELECT name, value, changed, description, readonly, min, max, type, is_obsolete
FROM system.merge_tree_settings
ORDER BY name
`,
columns: [
'name',
'value',
'changed',
'description',
'readonly',
'min',
'max',
'type',
'is_obsolete',
],
columnFormats: {
changed: ColumnFormat.Boolean,
readonly: ColumnFormat.Boolean,
is_obsolete: ColumnFormat.Boolean,
value: ColumnFormat.Code,
},
},
]

export const getQueryConfigByName = (name: string) => {
Expand Down
4 changes: 4 additions & 0 deletions app/tables/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { fetchData } from '@/lib/clickhouse'
import type { QueryConfig } from '@/lib/types/query-config'
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
import { ColumnFormat } from '@/components/data-table/columns'
import { DataTable } from '@/components/data-table/data-table'

export const dynamic = 'force-dynamic'
Expand Down Expand Up @@ -33,6 +34,9 @@ const config: QueryConfig = {
'readable_total_rows',
'part_count',
],
columnFormats: {
part_count: ColumnFormat.Number,
},
}

export default async function TablePage() {
Expand Down
32 changes: 32 additions & 0 deletions components/count-badge.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { fetchData } from '@/lib/clickhouse'
import { Badge } from '@/components/ui/badge'

interface CountBadgeProps {
sql?: string
}

export async function CountBadge({
sql,
}: CountBadgeProps): Promise<JSX.Element | null> {
if (!sql) return null

let data: any[] = []

try {
data = await fetchData(sql)
} catch (e: any) {
console.error(`Could not get count for sql: ${sql}, error: ${e}`)
return null
}

if (!data || !data.length || !data?.[0]?.['count()']) return null

const count = data[0]['count()'] || data[0]['count'] || 0
if (count === 0) return null

return (
<Badge className="ml-2" variant="outline">
{count}
</Badge>
)
}
14 changes: 13 additions & 1 deletion components/data-table/cell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { CheckCircledIcon, CrossCircledIcon } from '@radix-ui/react-icons'
import { MoreHorizontal } from 'lucide-react'

import dayjs from '@/lib/dayjs'
import { formatReadableQuantity } from '@/lib/format-readable'
import {
Accordion,
AccordionContent,
Expand All @@ -26,6 +27,12 @@ export const formatCell = (row: any, value: any, format: ColumnFormat) => {
case ColumnFormat.Code:
return <code>{value}</code>

case ColumnFormat.Number:
return formatReadableQuantity(value, 'long')

case ColumnFormat.NumberShort:
return formatReadableQuantity(value, 'short')

case ColumnFormat.CodeToggle:
if (value.length < CODE_TRUNCATE_LENGTH) {
return <code>{value}</code>
Expand All @@ -48,8 +55,13 @@ export const formatCell = (row: any, value: any, format: ColumnFormat) => {
</Accordion>
)

case ColumnFormat.RelatedTime:
let fromNow = dayjs(value).fromNow()
return <span title={value}>{fromNow}</span>

case ColumnFormat.Duration:
return dayjs.duration({ seconds: parseFloat(value) }).humanize()
let humanized = dayjs.duration({ seconds: parseFloat(value) }).humanize()
return <span title={value}>{humanized}</span>

case ColumnFormat.Boolean:
return value ? (
Expand Down
3 changes: 3 additions & 0 deletions components/data-table/columns.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@ import { formatCell } from '@/components/data-table/cell'

export enum ColumnFormat {
Code = 'code',
Number = 'number',
NumberShort = 'number-short',
CodeToggle = 'code-toggle',
RelatedTime = 'related-time',
Duration = 'duration',
Boolean = 'boolean',
Action = 'action',
Expand Down
5 changes: 4 additions & 1 deletion components/data-table/data-table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,10 @@ export function DataTable<TData extends RowData, TValue>({
return (
<div>
<div className="flex flex-row items-center justify-between pb-4">
<h1 className="text-muted-foreground text-xl capitalize">{title}</h1>
<div>
<h1 className="text-muted-foreground text-xl capitalize">{title}</h1>
<p className="text-muted-foreground text-sm">{config.description}</p>
</div>
<div className="flex items-center gap-3">
{showSQL ? <ShowSQLButton sql={config.sql} /> : null}
<ColumnVisibilityButton table={table} />
Expand Down
Loading

0 comments on commit d62d721

Please sign in to comment.