QueryPerformanceMetrics.tsx 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  1. import { Info } from 'lucide-react'
  2. import { parseAsJson, useQueryStates } from 'nuqs'
  3. import React, { useMemo } from 'react'
  4. import { cn, Skeleton, Tooltip, TooltipContent, TooltipTrigger } from 'ui'
  5. import { useQueryPerformanceQuery } from './useQueryPerformanceQuery'
  6. import { NumericFilter } from '@/components/interfaces/Reports/v2/ReportsNumericFilter'
  7. export const QueryPerformanceMetrics = () => {
  8. const { data: queryMetrics, isLoading } = useQueryPerformanceQuery({ preset: 'queryMetrics' })
  9. const [, setSearchParams] = useQueryStates({
  10. totalTimeFilter: parseAsJson<NumericFilter | null>((value) =>
  11. value === null || value === undefined ? null : (value as NumericFilter)
  12. ),
  13. })
  14. const stats = useMemo(() => {
  15. const slowQueriesTitle = queryMetrics?.[0]?.slow_queries === 1 ? 'Slow Query' : 'Slow Queries'
  16. const slowQueriesValue = queryMetrics?.[0]?.slow_queries || '0'
  17. return [
  18. {
  19. title: slowQueriesTitle,
  20. value: slowQueriesValue,
  21. onClick: () => {
  22. setSearchParams({
  23. totalTimeFilter: {
  24. operator: '>',
  25. value: 1000,
  26. } as NumericFilter,
  27. })
  28. },
  29. },
  30. {
  31. title: 'Cache Hit Rate',
  32. value: queryMetrics?.[0]?.cache_hit_rate || '0%',
  33. tooltip:
  34. 'Percentage of data read from cache vs disk. Higher is better - it means faster queries and less database load.',
  35. },
  36. {
  37. title: 'Avg. Rows Per Call',
  38. value: queryMetrics?.[0]?.avg_rows_per_call || '0',
  39. tooltip:
  40. 'Average number of rows returned per query execution. Helps identify queries that return too much or too little data.',
  41. },
  42. ]
  43. }, [queryMetrics, setSearchParams])
  44. return (
  45. <section className="px-6 pt-2 pb-4 flex flex-wrap gap-x-6 gap-y-2 w-full">
  46. {stats.map((card, i) => (
  47. <React.Fragment key={i}>
  48. <div
  49. className={cn('flex items-baseline gap-2 heading-subSection text-foreground-light', {
  50. 'cursor-pointer hover:text-foreground transition-colors': card.onClick,
  51. })}
  52. onClick={card.onClick}
  53. >
  54. {isLoading ? (
  55. <Skeleton className="h-5 w-24" />
  56. ) : (
  57. <>
  58. <span className="text-foreground">{card.value}</span>
  59. <span className="flex items-center gap-1">
  60. {card.title}
  61. {(card.title === 'Slow Queries' || card.title === 'Slow Query') && (
  62. <Tooltip>
  63. <TooltipTrigger asChild>
  64. <button
  65. type="button"
  66. aria-label="How are slow queries calculated?"
  67. className="inline-flex h-4 w-4 items-center justify-center rounded-full bg-surface-200 text-foreground-lighter transition-colors hover:bg-surface-300 hover:text-foreground focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-offset-1 focus-visible:ring-foreground-lighter"
  68. onClick={(e) => {
  69. e.stopPropagation()
  70. }}
  71. >
  72. <Info size={12} />
  73. </button>
  74. </TooltipTrigger>
  75. <TooltipContent side="top" align="start" className="max-w-xs text-xs">
  76. Slow queries are those with total execution time (execution time + planning
  77. time) greater than 1000ms.
  78. </TooltipContent>
  79. </Tooltip>
  80. )}
  81. {card.tooltip && (
  82. <Tooltip>
  83. <TooltipTrigger asChild>
  84. <button
  85. type="button"
  86. aria-label={`What is ${card.title}?`}
  87. className="inline-flex h-4 w-4 items-center justify-center rounded-full bg-surface-200 text-foreground-lighter transition-colors hover:bg-surface-300 hover:text-foreground focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-offset-1 focus-visible:ring-foreground-lighter"
  88. onClick={(e) => {
  89. e.stopPropagation()
  90. }}
  91. >
  92. <Info size={12} />
  93. </button>
  94. </TooltipTrigger>
  95. <TooltipContent side="top" align="start" className="max-w-xs text-xs">
  96. {card.tooltip}
  97. </TooltipContent>
  98. </Tooltip>
  99. )}
  100. </span>
  101. </>
  102. )}
  103. </div>
  104. {i < stats.length - 1 && <span className="text-foreground-muted">/</span>}
  105. </React.Fragment>
  106. ))}
  107. </section>
  108. )
  109. }