aiMonitoringDetailsPage.tsx 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  1. import styled from '@emotion/styled';
  2. import Feature from 'sentry/components/acl/feature';
  3. import {Alert} from 'sentry/components/alert';
  4. import {Breadcrumbs} from 'sentry/components/breadcrumbs';
  5. import * as Layout from 'sentry/components/layouts/thirds';
  6. import NoProjectMessage from 'sentry/components/noProjectMessage';
  7. import {DatePageFilter} from 'sentry/components/organizations/datePageFilter';
  8. import {EnvironmentPageFilter} from 'sentry/components/organizations/environmentPageFilter';
  9. import PageFilterBar from 'sentry/components/organizations/pageFilterBar';
  10. import PageFiltersContainer from 'sentry/components/organizations/pageFilters/container';
  11. import {ProjectPageFilter} from 'sentry/components/organizations/projectPageFilter';
  12. import SentryDocumentTitle from 'sentry/components/sentryDocumentTitle';
  13. import {t} from 'sentry/locale';
  14. import {space} from 'sentry/styles/space';
  15. import {CurrencyUnit, DurationUnit, RateUnit} from 'sentry/utils/discover/fields';
  16. import {MutableSearch} from 'sentry/utils/tokenizeSearch';
  17. import useOrganization from 'sentry/utils/useOrganization';
  18. import {normalizeUrl} from 'sentry/utils/withDomainRequired';
  19. import {
  20. NumberOfPipelinesChart,
  21. PipelineDurationChart,
  22. TotalTokensUsedChart,
  23. } from 'sentry/views/aiMonitoring/aiMonitoringCharts';
  24. import {PipelineSpansTable} from 'sentry/views/aiMonitoring/pipelineSpansTable';
  25. import {MetricReadout} from 'sentry/views/performance/metricReadout';
  26. import * as ModuleLayout from 'sentry/views/performance/moduleLayout';
  27. import {useSpanMetrics} from 'sentry/views/starfish/queries/useDiscover';
  28. import {
  29. SpanFunction,
  30. SpanMetricsField,
  31. type SpanMetricsQueryFilters,
  32. } from 'sentry/views/starfish/types';
  33. function NoAccessComponent() {
  34. return (
  35. <Layout.Page withPadding>
  36. <Alert type="warning">{t("You don't have access to this feature")}</Alert>
  37. </Layout.Page>
  38. );
  39. }
  40. interface Props {
  41. params: {
  42. groupId: string;
  43. };
  44. }
  45. export default function AiMonitoringPage({params}: Props) {
  46. const organization = useOrganization();
  47. const {groupId} = params;
  48. const filters: SpanMetricsQueryFilters = {
  49. 'span.group': groupId,
  50. 'span.category': 'ai.pipeline',
  51. };
  52. const {data, isLoading: areSpanMetricsLoading} = useSpanMetrics(
  53. {
  54. search: MutableSearch.fromQueryObject(filters),
  55. fields: [
  56. SpanMetricsField.SPAN_OP,
  57. SpanMetricsField.SPAN_DESCRIPTION,
  58. 'count()',
  59. `${SpanFunction.SPM}()`,
  60. `avg(${SpanMetricsField.SPAN_DURATION})`,
  61. ],
  62. enabled: Boolean(groupId),
  63. },
  64. 'api.ai-pipelines.view'
  65. );
  66. const spanMetrics = data[0] ?? {};
  67. const {data: totalTokenData, isLoading: isTotalTokenDataLoading} = useSpanMetrics(
  68. {
  69. search: MutableSearch.fromQueryObject({
  70. 'span.category': 'ai',
  71. 'span.ai.pipeline.group': groupId,
  72. }),
  73. fields: [
  74. 'ai_total_tokens_used()',
  75. 'ai_total_tokens_used(c:spans/ai.total_cost@usd)',
  76. ],
  77. enabled: Boolean(groupId),
  78. },
  79. 'api.ai-pipelines.view'
  80. );
  81. const tokenUsedMetric = totalTokenData[0] ?? {};
  82. return (
  83. <PageFiltersContainer>
  84. <SentryDocumentTitle
  85. title={`AI Monitoring — ${spanMetrics['span.description'] ?? t('(no name)')}`}
  86. >
  87. <Layout.Page>
  88. <Feature
  89. features="ai-analytics"
  90. organization={organization}
  91. renderDisabled={NoAccessComponent}
  92. >
  93. <NoProjectMessage organization={organization}>
  94. <Layout.Header>
  95. <Layout.HeaderContent>
  96. <Breadcrumbs
  97. crumbs={[
  98. {
  99. label: t('Dashboard'),
  100. },
  101. {
  102. label: t('AI Monitoring'),
  103. },
  104. {
  105. label: spanMetrics['span.description'] ?? t('(no name)'),
  106. to: normalizeUrl(
  107. `/organizations/${organization.slug}/ai-monitoring`
  108. ),
  109. },
  110. ]}
  111. />
  112. <Layout.Title>{t('AI Monitoring')}</Layout.Title>
  113. </Layout.HeaderContent>
  114. </Layout.Header>
  115. <Layout.Body>
  116. <Layout.Main fullWidth>
  117. <ModuleLayout.Layout>
  118. <ModuleLayout.Full>
  119. <SpaceBetweenWrap>
  120. <PageFilterBar condensed>
  121. <ProjectPageFilter />
  122. <EnvironmentPageFilter />
  123. <DatePageFilter />
  124. </PageFilterBar>
  125. <MetricsRibbon>
  126. <MetricReadout
  127. title={t('Total Tokens Used')}
  128. value={tokenUsedMetric['ai_total_tokens_used()']}
  129. unit={'count'}
  130. isLoading={isTotalTokenDataLoading}
  131. />
  132. <MetricReadout
  133. title={t('Total Cost')}
  134. value={
  135. tokenUsedMetric[
  136. 'ai_total_tokens_used(c:spans/ai.total_cost@usd)'
  137. ]
  138. }
  139. unit={CurrencyUnit.USD}
  140. isLoading={isTotalTokenDataLoading}
  141. />
  142. <MetricReadout
  143. title={t('Pipeline Duration')}
  144. value={
  145. spanMetrics?.[`avg(${SpanMetricsField.SPAN_DURATION})`]
  146. }
  147. unit={DurationUnit.MILLISECOND}
  148. isLoading={areSpanMetricsLoading}
  149. />
  150. <MetricReadout
  151. title={t('Pipeline Runs Per Minute')}
  152. value={spanMetrics?.[`${SpanFunction.SPM}()`]}
  153. unit={RateUnit.PER_MINUTE}
  154. isLoading={areSpanMetricsLoading}
  155. />
  156. </MetricsRibbon>
  157. </SpaceBetweenWrap>
  158. </ModuleLayout.Full>
  159. <ModuleLayout.Third>
  160. <TotalTokensUsedChart groupId={groupId} />
  161. </ModuleLayout.Third>
  162. <ModuleLayout.Third>
  163. <NumberOfPipelinesChart groupId={groupId} />
  164. </ModuleLayout.Third>
  165. <ModuleLayout.Third>
  166. <PipelineDurationChart groupId={groupId} />
  167. </ModuleLayout.Third>
  168. <ModuleLayout.Full>
  169. <PipelineSpansTable groupId={groupId} />
  170. </ModuleLayout.Full>
  171. </ModuleLayout.Layout>
  172. </Layout.Main>
  173. </Layout.Body>
  174. </NoProjectMessage>
  175. </Feature>
  176. </Layout.Page>
  177. </SentryDocumentTitle>
  178. </PageFiltersContainer>
  179. );
  180. }
  181. const SpaceBetweenWrap = styled('div')`
  182. display: flex;
  183. justify-content: space-between;
  184. flex-wrap: wrap;
  185. gap: ${space(2)};
  186. `;
  187. const MetricsRibbon = styled('div')`
  188. display: flex;
  189. flex-wrap: wrap;
  190. gap: ${space(4)};
  191. `;