Browse Source

ref(perf): Add utility group tag function (#45651)

### Summary
We use this occasionally to help turn entity counts into tags so we can
visualize performance differences based on a pages 'n' entities. This
just turns our bespoke functions into a utility function with a bit more
explanation as to why you might use it.
Kev 2 years ago
parent
commit
3aca2d2cbb

+ 3 - 8
static/app/components/events/interfaces/spans/spanTree.tsx

@@ -20,6 +20,7 @@ import {pickBarColor} from 'sentry/components/performance/waterfall/utils';
 import {t, tct} from 'sentry/locale';
 import {Organization} from 'sentry/types';
 import trackAdvancedAnalyticsEvent from 'sentry/utils/analytics/trackAdvancedAnalyticsEvent';
+import {setGroupedEntityTag} from 'sentry/utils/performanceForSentry';
 
 import {DragManagerChildrenProps} from './dragManager';
 import {ActiveOperationFilter} from './filter';
@@ -38,13 +39,7 @@ import {
   SpanTreeNodeType,
   SpanType,
 } from './types';
-import {
-  getSpanID,
-  getSpanOperation,
-  isGapSpan,
-  setSpansOnTransaction,
-  spanTargetHash,
-} from './utils';
+import {getSpanID, getSpanOperation, isGapSpan, spanTargetHash} from './utils';
 import WaterfallModel from './waterfallModel';
 
 type PropType = ScrollbarManagerChildrenProps & {
@@ -76,7 +71,7 @@ class SpanTree extends Component<PropType> {
   };
 
   componentDidMount() {
-    setSpansOnTransaction(this.props.spans.length);
+    setGroupedEntityTag('spans.total', 1000, this.props.spans.length);
 
     if (location.hash) {
       const {spans} = this.props;

+ 0 - 15
static/app/components/events/interfaces/spans/utils.tsx

@@ -14,7 +14,6 @@ import trackAdvancedAnalyticsEvent from 'sentry/utils/analytics/trackAdvancedAna
 import {WebVital} from 'sentry/utils/fields';
 import {TraceError, TraceFullDetailed} from 'sentry/utils/performance/quickTrace/types';
 import {WEB_VITAL_DETAILS} from 'sentry/utils/performance/vitals/constants';
-import {getPerformanceTransaction} from 'sentry/utils/performanceForSentry';
 
 import {MERGE_LABELS_THRESHOLD_PERCENT} from './constants';
 import SpanTreeModel from './spanTreeModel';
@@ -34,20 +33,6 @@ import {
 export const isValidSpanID = (maybeSpanID: any) =>
   isString(maybeSpanID) && maybeSpanID.length > 0;
 
-export const setSpansOnTransaction = (spanCount: number) => {
-  const transaction = getPerformanceTransaction();
-
-  if (!transaction || spanCount === 0) {
-    return;
-  }
-
-  const spanCountGroups = [10, 100, 200, 300, 400, 500, 600, 700, 800, 900, 1001];
-  const spanGroup = spanCountGroups.find(g => spanCount <= g) || -1;
-
-  transaction.setTag('ui.spanCount', spanCount);
-  transaction.setTag('ui.spanCount.grouped', `<=${spanGroup}`);
-};
-
 export type SpanBoundsType = {endTimestamp: number; startTimestamp: number};
 export type SpanGeneratedBoundsType =
   | {isSpanVisibleInView: boolean; type: 'TRACE_TIMESTAMPS_EQUAL'}

+ 30 - 1
static/app/utils/performanceForSentry.tsx

@@ -1,5 +1,5 @@
 import {Fragment, Profiler, ReactNode, useEffect, useRef} from 'react';
-import {captureException, captureMessage} from '@sentry/react';
+import {captureException, captureMessage, setExtra, setTag} from '@sentry/react';
 import * as Sentry from '@sentry/react';
 import {IdleTransaction} from '@sentry/tracing';
 import {Transaction, TransactionEvent} from '@sentry/types';
@@ -417,3 +417,32 @@ export const addExtraMeasurements = (transaction: TransactionEvent) => {
     // Defensive catch since this code is auxiliary.
   }
 };
+
+/**
+ * A util function to help create some broad buckets to group entity counts without exploding cardinality.
+ *
+ * @param tagName - Name for the tag, will create `<tagName>` in data and `<tagname>.grouped` as a tag
+ * @param max - The approximate maximum value for the tag, A bucket between max and Infinity is also captured so it's fine if it's not precise, the data won't be entirely lost.
+ * @param n - The value to be grouped, should represent `n` entities.
+ * @param [buckets=[1,2,5]] - An optional param to specify the bucket progression. Default is 1,2,5 (10,20,50 etc).
+ */
+export const setGroupedEntityTag = (
+  tagName: string,
+  max: number,
+  n: number,
+  buckets = [1, 2, 5]
+) => {
+  setExtra(tagName, n);
+  let groups = [0];
+  loop: for (let m = 1, mag = 0; m <= max; m *= 10, mag++) {
+    for (const i of buckets) {
+      const group = i * 10 ** mag;
+      if (group > max) {
+        break loop;
+      }
+      groups = [...groups, group];
+    }
+  }
+  groups = [...groups, +Infinity];
+  setTag(`${tagName}.grouped`, `<=${groups.find(g => n <= g)}`);
+};

+ 2 - 6
static/app/views/performance/transactionSummary/transactionSpans/spanDetails/content.tsx

@@ -1,5 +1,4 @@
 import {Fragment} from 'react';
-import {setTag} from '@sentry/react';
 import {Location} from 'history';
 
 import Feature from 'sentry/components/acl/feature';
@@ -15,6 +14,7 @@ import SuspectSpansQuery, {
   ChildrenProps as SuspectSpansProps,
 } from 'sentry/utils/performance/suspectSpans/suspectSpansQuery';
 import {SpanSlug} from 'sentry/utils/performance/suspectSpans/types';
+import {setGroupedEntityTag} from 'sentry/utils/performanceForSentry';
 import {decodeScalar} from 'sentry/utils/queryString';
 import useRouteAnalyticsEventNames from 'sentry/utils/routeAnalytics/useRouteAnalyticsEventNames';
 import useRouteAnalyticsParams from 'sentry/utils/routeAnalytics/useRouteAnalyticsParams';
@@ -96,11 +96,7 @@ export default function SpanDetailsContentWrapper(props: Props) {
                 (tableData?.data?.[0]?.['count()'] as number) ?? null;
 
               if (totalCount) {
-                setTag('spans.totalCount', totalCount);
-                const countGroup = [
-                  1, 5, 10, 20, 50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000,
-                ].find(n => totalCount <= n);
-                setTag('spans.totalCount.grouped', `<=${countGroup}`);
+                setGroupedEntityTag('spans.totalCount', 1000, totalCount);
               }
 
               return (

+ 3 - 10
static/app/views/projectsDashboard/index.tsx

@@ -2,7 +2,7 @@ import {Fragment, Profiler, useEffect, useMemo, useState} from 'react';
 import LazyLoad, {forceCheck} from 'react-lazyload';
 import {RouteComponentProps} from 'react-router';
 import styled from '@emotion/styled';
-import {setTag, withProfiler} from '@sentry/react';
+import {withProfiler} from '@sentry/react';
 import debounce from 'lodash/debounce';
 import flatten from 'lodash/flatten';
 import uniqBy from 'lodash/uniqBy';
@@ -24,7 +24,7 @@ import ProjectsStatsStore from 'sentry/stores/projectsStatsStore';
 import {space} from 'sentry/styles/space';
 import {Organization, Project, TeamWithProjects} from 'sentry/types';
 import {sortProjects} from 'sentry/utils';
-import {onRenderCallback} from 'sentry/utils/performanceForSentry';
+import {onRenderCallback, setGroupedEntityTag} from 'sentry/utils/performanceForSentry';
 import useOrganization from 'sentry/utils/useOrganization';
 import withApi from 'sentry/utils/withApi';
 import withOrganization from 'sentry/utils/withOrganization';
@@ -74,13 +74,6 @@ function ProjectCardList({projects}: {projects: Project[]}) {
   );
 }
 
-function setProjectDataTags(totalProjects: number) {
-  const countGroup = [0, 1, 5, 10, 50, 100, 500, 1000, Infinity].find(
-    n => totalProjects <= n
-  );
-  setTag('projects.total.grouped', `<=${countGroup}`);
-}
-
 function Dashboard({teams, organization, loadingTeams, error, router, location}: Props) {
   useEffect(() => {
     return function cleanup() {
@@ -112,7 +105,7 @@ function Dashboard({teams, organization, loadingTeams, error, router, location}:
     'id'
   );
   const projects = uniqBy(flatten(teams.map(teamObj => teamObj.projects)), 'id');
-  setProjectDataTags(projects.length);
+  setGroupedEntityTag('projects.total', 1000, projects.length);
 
   const currentProjects = selectedTeams.length === 0 ? projects : filteredTeamProjects;
   const filteredProjects = (currentProjects ?? projects).filter(project =>