databaseChartView.tsx 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  1. import {Fragment} from 'react';
  2. import {useTheme} from '@emotion/react';
  3. import styled from '@emotion/styled';
  4. import {Location} from 'history';
  5. import moment from 'moment';
  6. import {CompactSelect} from 'sentry/components/compactSelect';
  7. import {t} from 'sentry/locale';
  8. import {space} from 'sentry/styles/space';
  9. import {Series} from 'sentry/types/echarts';
  10. import {getDuration} from 'sentry/utils/formatters';
  11. import usePageFilters from 'sentry/utils/usePageFilters';
  12. import Chart, {useSynchronizeCharts} from 'sentry/views/starfish/components/chart';
  13. import ChartPanel from 'sentry/views/starfish/components/chartPanel';
  14. import {
  15. useQueryDbTables,
  16. useQueryTopDbOperationsChart,
  17. useQueryTopTablesChart,
  18. } from 'sentry/views/starfish/modules/databaseModule/queries';
  19. import {datetimeToClickhouseFilterTimestamps} from 'sentry/views/starfish/utils/dates';
  20. import {zeroFillSeries} from 'sentry/views/starfish/utils/zeroFillSeries';
  21. const INTERVAL = 12;
  22. type Props = {
  23. location: Location;
  24. onChange: (value: string) => void;
  25. table: string;
  26. };
  27. function parseOptions(options, label) {
  28. const prefix = <span>{t('Operation')}</span>;
  29. return [
  30. {
  31. value: 'ALL',
  32. prefix,
  33. label: `ALL`,
  34. },
  35. ...options.map(action => {
  36. return {
  37. value: action.key,
  38. prefix,
  39. label: `${action.key || 'null'} - ${getDuration(
  40. action.value / 1000,
  41. 2,
  42. true
  43. )} ${label}`,
  44. };
  45. }),
  46. ];
  47. }
  48. export default function DatabaseChartView({table, onChange}: Props) {
  49. const pageFilter = usePageFilters();
  50. const theme = useTheme();
  51. const {start_timestamp, end_timestamp} = datetimeToClickhouseFilterTimestamps(
  52. pageFilter.selection.datetime
  53. );
  54. const {data: tableData} = useQueryDbTables();
  55. const {isLoading: isTopGraphLoading, data: topGraphData} =
  56. useQueryTopDbOperationsChart(INTERVAL);
  57. const {isLoading: tableGraphLoading, data: tableGraphData} =
  58. useQueryTopTablesChart(INTERVAL);
  59. const seriesByDomain: {[action: string]: Series} = {};
  60. const tpmByDomain: {[action: string]: Series} = {};
  61. if (!tableGraphLoading) {
  62. tableGraphData.forEach(datum => {
  63. seriesByDomain[datum.domain] = {
  64. seriesName: datum.domain,
  65. data: [],
  66. };
  67. tpmByDomain[datum.domain] = {
  68. seriesName: datum.domain,
  69. data: [],
  70. };
  71. });
  72. tableGraphData.forEach(datum => {
  73. seriesByDomain[datum.domain].data.push({
  74. value: datum.p50,
  75. name: datum.interval,
  76. });
  77. tpmByDomain[datum.domain].data.push({
  78. value: datum.count,
  79. name: datum.interval,
  80. });
  81. });
  82. }
  83. const topDomains = Object.values(seriesByDomain).map(series =>
  84. zeroFillSeries(
  85. series,
  86. moment.duration(INTERVAL, 'hours'),
  87. moment(start_timestamp),
  88. moment(end_timestamp)
  89. )
  90. );
  91. const tpmDomains = Object.values(tpmByDomain).map(series =>
  92. zeroFillSeries(
  93. series,
  94. moment.duration(INTERVAL, 'hours'),
  95. moment(start_timestamp),
  96. moment(end_timestamp)
  97. )
  98. );
  99. const tpmByQuery: {[query: string]: Series} = {};
  100. const seriesByQuery: {[action: string]: Series} = {};
  101. if (!isTopGraphLoading) {
  102. topGraphData.forEach(datum => {
  103. seriesByQuery[datum.action] = {
  104. seriesName: datum.action,
  105. data: [],
  106. };
  107. tpmByQuery[datum.action] = {
  108. seriesName: datum.action,
  109. data: [],
  110. };
  111. });
  112. topGraphData.forEach(datum => {
  113. seriesByQuery[datum.action].data.push({
  114. value: datum.p50,
  115. name: datum.interval,
  116. });
  117. tpmByQuery[datum.action].data.push({
  118. value: datum.count,
  119. name: datum.interval,
  120. });
  121. });
  122. }
  123. const chartColors = [...theme.charts.getColorPalette(6).slice(2, 7), theme.gray300];
  124. useSynchronizeCharts([!tableGraphLoading]);
  125. return (
  126. <Fragment>
  127. {tableData.length === 1 && tableData[0].key === '' ? (
  128. <Fragment />
  129. ) : (
  130. <Fragment>
  131. <ChartsContainer>
  132. <ChartsContainerItem>
  133. <ChartPanel title={t('Slowest Tables P50')}>
  134. <Chart
  135. statsPeriod="24h"
  136. height={180}
  137. data={topDomains}
  138. start=""
  139. end=""
  140. chartColors={chartColors}
  141. loading={tableGraphLoading}
  142. utc={false}
  143. grid={{
  144. left: '0',
  145. right: '0',
  146. top: '16px',
  147. bottom: '8px',
  148. }}
  149. definedAxisTicks={4}
  150. isLineChart
  151. showLegend
  152. />
  153. </ChartPanel>
  154. </ChartsContainerItem>
  155. <ChartsContainerItem>
  156. <ChartPanel title={t('Table Throughput')}>
  157. <Chart
  158. statsPeriod="24h"
  159. height={180}
  160. data={tpmDomains}
  161. start=""
  162. end=""
  163. chartColors={chartColors}
  164. loading={isTopGraphLoading}
  165. utc={false}
  166. grid={{
  167. left: '0',
  168. right: '0',
  169. top: '16px',
  170. bottom: '8px',
  171. }}
  172. definedAxisTicks={4}
  173. showLegend
  174. isLineChart
  175. />
  176. </ChartPanel>
  177. </ChartsContainerItem>
  178. </ChartsContainer>
  179. <Selectors>
  180. <CompactSelect
  181. value={table}
  182. triggerProps={{prefix: t('Table')}}
  183. options={parseOptions(tableData, 'p50')}
  184. menuTitle="Table"
  185. onChange={opt => onChange(opt.value)}
  186. />
  187. </Selectors>
  188. </Fragment>
  189. )}
  190. </Fragment>
  191. );
  192. }
  193. const Selectors = styled(`div`)`
  194. display: flex;
  195. margin-bottom: ${space(2)};
  196. `;
  197. const ChartsContainer = styled('div')`
  198. display: flex;
  199. flex-direction: row;
  200. flex-wrap: wrap;
  201. gap: ${space(2)};
  202. `;
  203. const ChartsContainerItem = styled('div')`
  204. flex: 1;
  205. `;