profilingSparklineChart.tsx 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  1. import {useMemo} from 'react';
  2. import {type Theme, useTheme} from '@emotion/react';
  3. import type {LineChartProps, LineChartSeries} from 'sentry/components/charts/lineChart';
  4. import {LineChart} from 'sentry/components/charts/lineChart';
  5. import {t} from 'sentry/locale';
  6. import {tooltipFormatter} from 'sentry/utils/discover/charts';
  7. import {makeFormatter} from 'sentry/utils/profiling/units/units';
  8. const durationFormatter = makeFormatter('nanoseconds', 0);
  9. function asSeries(
  10. seriesName: string,
  11. color: string | undefined,
  12. data: {timestamp: number; value: number}[]
  13. ) {
  14. return {
  15. data: data.map(p => ({
  16. name: p.timestamp * 1e3,
  17. value: p.value ?? 0,
  18. })),
  19. color,
  20. seriesName,
  21. };
  22. }
  23. function getTooltipFormatter(label: string, baseline: number) {
  24. return [
  25. '<div class="tooltip-series tooltip-series-solo">',
  26. '<div>',
  27. `<span class="tooltip-label"><strong>${label}</strong></span>`,
  28. tooltipFormatter(baseline / 1e6, 'duration'),
  29. '</div>',
  30. '</div>',
  31. '<div class="tooltip-arrow"></div>',
  32. ].join('');
  33. }
  34. interface BaseSparklineChartProps {
  35. name: string;
  36. points: {timestamp: number; value: number}[];
  37. chartProps?: Partial<LineChartProps>;
  38. color?: string;
  39. }
  40. interface ProfilingSparklineChartPropsWithBreakpoint extends BaseSparklineChartProps {
  41. aggregate_range_1: number;
  42. aggregate_range_2: number;
  43. breakpoint: number;
  44. end: number;
  45. start: number;
  46. }
  47. interface ProfilingSparklineChartPropsWithoutBreakpoint extends BaseSparklineChartProps {}
  48. function isBreakPointProps(
  49. props: ProfilingSparklineChartProps
  50. ): props is ProfilingSparklineChartPropsWithBreakpoint {
  51. return typeof (props as any).breakpoint === 'number';
  52. }
  53. type ProfilingSparklineChartProps =
  54. | ProfilingSparklineChartPropsWithBreakpoint
  55. | ProfilingSparklineChartPropsWithoutBreakpoint;
  56. function makeSeriesBeforeAfterLines(
  57. start: number,
  58. breakpoint: number,
  59. end: number,
  60. aggregate_range_1: number,
  61. aggregate_range_2: number,
  62. theme: Theme
  63. ): LineChartSeries[] {
  64. const dividingLine = {
  65. data: [],
  66. color: theme.textColor,
  67. seriesName: 'dividing line',
  68. markLine: {},
  69. };
  70. dividingLine.markLine = {
  71. data: [{xAxis: breakpoint * 1e3}],
  72. label: {show: false},
  73. lineStyle: {
  74. color: theme.textColor,
  75. type: 'solid',
  76. width: 2,
  77. },
  78. symbol: ['none', 'none'],
  79. tooltip: {
  80. show: false,
  81. },
  82. silent: true,
  83. };
  84. const beforeLine = {
  85. data: [],
  86. color: theme.textColor,
  87. seriesName: 'before line',
  88. markLine: {},
  89. };
  90. beforeLine.markLine = {
  91. data: [
  92. [
  93. {value: 'Past', coord: [start * 1e3, aggregate_range_1]},
  94. {coord: [breakpoint * 1e3, aggregate_range_1]},
  95. ],
  96. ],
  97. label: {
  98. show: false,
  99. },
  100. lineStyle: {
  101. color: theme.textColor,
  102. type: 'dashed',
  103. width: 1,
  104. },
  105. symbol: ['none', 'none'],
  106. tooltip: {
  107. formatter: getTooltipFormatter(t('Past Baseline'), aggregate_range_1),
  108. },
  109. };
  110. const afterLine = {
  111. data: [],
  112. color: theme.textColor,
  113. seriesName: 'after line',
  114. markLine: {},
  115. };
  116. afterLine.markLine = {
  117. data: [
  118. [
  119. {
  120. value: 'Present',
  121. coord: [breakpoint * 1e3, aggregate_range_2],
  122. },
  123. {coord: [end * 1e3, aggregate_range_2]},
  124. ],
  125. ],
  126. label: {
  127. show: false,
  128. },
  129. lineStyle: {
  130. color: theme.textColor,
  131. type: 'dashed',
  132. width: 1,
  133. },
  134. symbol: ['none', 'none'],
  135. tooltip: {
  136. formatter: getTooltipFormatter(t('Present Baseline'), aggregate_range_2),
  137. },
  138. };
  139. return [dividingLine, beforeLine, afterLine];
  140. }
  141. export function ProfilingSparklineChart(props: ProfilingSparklineChartProps) {
  142. const theme = useTheme();
  143. const chartProps: LineChartProps = useMemo(() => {
  144. const additionalSeries: LineChartSeries[] = [];
  145. if (isBreakPointProps(props)) {
  146. additionalSeries.push(
  147. ...makeSeriesBeforeAfterLines(
  148. props.start,
  149. props.breakpoint,
  150. props.end,
  151. props.aggregate_range_1,
  152. props.aggregate_range_2,
  153. theme
  154. )
  155. );
  156. }
  157. const baseProps: LineChartProps = {
  158. height: 26,
  159. width: 'auto',
  160. series: [asSeries(props.name, props.color, props.points), ...additionalSeries],
  161. grid: [
  162. {
  163. containLabel: false,
  164. top: '2px',
  165. left: '2px',
  166. right: '2px',
  167. bottom: '2px',
  168. },
  169. {
  170. containLabel: false,
  171. top: '2px',
  172. left: '2px',
  173. right: '2px',
  174. bottom: '2px',
  175. },
  176. ],
  177. tooltip: {
  178. valueFormatter: (v: number) => durationFormatter(v),
  179. },
  180. axisPointer: {},
  181. xAxes: [
  182. {
  183. gridIndex: 0,
  184. type: 'time' as const,
  185. show: false,
  186. },
  187. ],
  188. yAxes: [
  189. {
  190. scale: true,
  191. show: false,
  192. },
  193. ],
  194. ...(props.chartProps ? props.chartProps : {}),
  195. };
  196. return baseProps;
  197. }, [props, theme]);
  198. return <LineChart {...chartProps} isGroupedByDate showTimeInTooltip />;
  199. }