projectStatsToPredictedSeries.tsx 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127
  1. import moment from 'moment';
  2. import {t} from 'sentry/locale';
  3. import {SeriesApi} from 'sentry/types';
  4. import {Series} from 'sentry/types/echarts';
  5. import {defined} from 'sentry/utils';
  6. import commonTheme from 'sentry/utils/theme';
  7. import {Outcome} from 'sentry/views/organizationStats/types';
  8. import {quantityField} from '.';
  9. export function projectStatsToPredictedSeries(
  10. projectStats?: SeriesApi,
  11. client?: number,
  12. server?: number,
  13. specifiedClientRate?: number
  14. ): Series[] {
  15. if (!projectStats || !defined(client) || !defined(server)) {
  16. return [];
  17. }
  18. const clientRate = Math.max(Math.min(client, 1), 0);
  19. let serverRate = Math.max(Math.min(server, 1), 0);
  20. const commonSeriesConfig = {
  21. barMinHeight: 1,
  22. type: 'bar',
  23. stack: 'predictedUsage',
  24. };
  25. const seriesData: Record<
  26. 'indexedAndProcessed' | 'processed' | 'discarded',
  27. Series['data']
  28. > = {
  29. indexedAndProcessed: [],
  30. processed: [],
  31. discarded: [],
  32. };
  33. (
  34. projectStats.intervals.map((interval, index) => {
  35. const result = {
  36. indexedAndProcessed: 0,
  37. processed: 0,
  38. discarded: 0,
  39. };
  40. projectStats.groups.forEach(group => {
  41. switch (group.by.outcome) {
  42. case Outcome.ACCEPTED:
  43. result.indexedAndProcessed += group.series[quantityField][index];
  44. break;
  45. case Outcome.CLIENT_DISCARD:
  46. result.discarded += group.series[quantityField][index];
  47. break;
  48. case Outcome.FILTERED:
  49. if (String(group.by.reason).startsWith('Sampled')) {
  50. result.processed += group.series[quantityField][index];
  51. }
  52. break;
  53. default:
  54. // We do not take invalid, rate_limited and other filtered into account
  55. }
  56. });
  57. return {
  58. interval: moment(interval).valueOf(),
  59. ...result,
  60. };
  61. }) as Array<
  62. Record<'indexedAndProcessed' | 'processed' | 'discarded' | 'interval', number>
  63. >
  64. ).forEach((bucket, index) => {
  65. const {indexedAndProcessed, processed, discarded, interval} = bucket;
  66. if (clientRate < serverRate!) {
  67. serverRate = clientRate;
  68. }
  69. let total = indexedAndProcessed + processed + discarded;
  70. if (defined(specifiedClientRate)) {
  71. // We assume that the clientDiscard is 0 and
  72. // calculate the discard client (SDK) bucket according to the specified client rate
  73. const newClientDiscard = total / specifiedClientRate - total;
  74. total += newClientDiscard;
  75. }
  76. const newSentClient = total * clientRate;
  77. const newDiscarded = total - newSentClient;
  78. const newIndexedAndProcessed =
  79. clientRate === 0 ? 0 : newSentClient * (serverRate! / clientRate);
  80. const newProcessed = newSentClient - newIndexedAndProcessed;
  81. seriesData.indexedAndProcessed[index] = {
  82. name: interval,
  83. value: Math.round(newIndexedAndProcessed),
  84. };
  85. seriesData.processed[index] = {
  86. name: interval,
  87. value: Math.round(newProcessed),
  88. };
  89. seriesData.discarded[index] = {
  90. name: interval,
  91. value: Math.round(newDiscarded),
  92. };
  93. });
  94. return [
  95. {
  96. seriesName: t('Indexed and Processed'),
  97. color: commonTheme.green300,
  98. ...commonSeriesConfig,
  99. data: seriesData.indexedAndProcessed,
  100. },
  101. {
  102. seriesName: t('Processed'),
  103. color: commonTheme.yellow300,
  104. data: seriesData.processed,
  105. ...commonSeriesConfig,
  106. },
  107. {
  108. seriesName: t('Discarded'),
  109. color: commonTheme.red300,
  110. data: seriesData.discarded,
  111. ...commonSeriesConfig,
  112. },
  113. ];
  114. }