Browse Source

feat(profiling): differential flamegraph link (#61614)

Link to dedicated flamegraph page when feature flag is enabled
Jonas 1 year ago
parent
commit
523cf705bd

+ 4 - 5
static/app/routes.tsx

@@ -2227,15 +2227,14 @@ function buildRoutes() {
         path="summary/:projectId/"
         component={make(() => import('sentry/views/profiling/profileSummary'))}
       />
+      <Route
+        path="profile/:projectId/differential-flamegraph/"
+        component={make(() => import('sentry/views/profiling/differentialFlamegraph'))}
+      />
       <Route
         path="profile/:projectId/:eventId/"
         component={make(() => import('sentry/views/profiling/profilesProvider'))}
       >
-        {/* @TODO Remove flamechart route name */}
-        <Route
-          path="flamechart/"
-          component={make(() => import('sentry/views/profiling/profileFlamechart'))}
-        />
         <Route
           path="flamegraph/"
           component={make(() => import('sentry/views/profiling/profileFlamechart'))}

+ 1 - 0
static/app/utils/profiling/hooks/types.tsx

@@ -30,6 +30,7 @@ export type FunctionTrend = {
   breakpoint: number;
   change: TrendType;
   'count()': number;
+  fingerprint: number;
   function: string;
   package: string;
   project: string;

+ 37 - 0
static/app/utils/profiling/routes.tsx

@@ -29,6 +29,43 @@ export function generateProfileFlamechartRoute({
   return `/organizations/${orgSlug}/profiling/profile/${projectSlug}/${profileId}/flamegraph/`;
 }
 
+export function generateProfileDifferentialFlamegraphRoute({
+  orgSlug,
+  projectSlug,
+}: {
+  orgSlug: Organization['slug'];
+  projectSlug: Project['slug'];
+}): string {
+  return `/organizations/${orgSlug}/profiling/profile/${projectSlug}/differential-flamegraph/`;
+}
+
+export function generateProfileDifferentialFlamegraphRouteWithQuery({
+  orgSlug,
+  projectSlug,
+  query,
+  fingerprint,
+  transaction,
+  breakpoint,
+}: {
+  breakpoint: number;
+  fingerprint: number;
+  orgSlug: Organization['slug'];
+  projectSlug: Project['slug'];
+  transaction: string;
+  query?: Location['query'];
+}): LocationDescriptor {
+  const pathname = generateProfileDifferentialFlamegraphRoute({orgSlug, projectSlug});
+  return {
+    pathname,
+    query: {
+      ...query,
+      transaction,
+      fingerprint,
+      breakpoint,
+    },
+  };
+}
+
 export function generateProfileDetailsRoute({
   orgSlug,
   projectSlug,

+ 11 - 0
static/app/views/profiling/differentialFlamegraph.tsx

@@ -0,0 +1,11 @@
+import Feature from 'sentry/components/acl/feature';
+
+function DifferentialFlamegraph() {
+  return (
+    <Feature features={['organizations:profiling-differential-flamegraph-page']}>
+      Differential Flamegraph
+    </Feature>
+  );
+}
+
+export default DifferentialFlamegraph;

+ 163 - 69
static/app/views/profiling/profileSummary/regressedProfileFunctions.tsx

@@ -12,12 +12,16 @@ import PerformanceDuration from 'sentry/components/performanceDuration';
 import {TextTruncateOverflow} from 'sentry/components/profiling/textTruncateOverflow';
 import {t} from 'sentry/locale';
 import {space} from 'sentry/styles/space';
+import {Organization, Project} from 'sentry/types';
 import {trackAnalytics} from 'sentry/utils/analytics';
 import {formatPercentage} from 'sentry/utils/formatters';
 import type {FunctionTrend, TrendType} from 'sentry/utils/profiling/hooks/types';
 import {useCurrentProjectFromRouteParam} from 'sentry/utils/profiling/hooks/useCurrentProjectFromRouteParam';
 import {useProfileFunctionTrends} from 'sentry/utils/profiling/hooks/useProfileFunctionTrends';
-import {generateProfileFlamechartRouteWithQuery} from 'sentry/utils/profiling/routes';
+import {
+  generateProfileDifferentialFlamegraphRouteWithQuery,
+  generateProfileFlamechartRouteWithQuery,
+} from 'sentry/utils/profiling/routes';
 import {relativeChange} from 'sentry/utils/profiling/units/units';
 import {decodeScalar} from 'sentry/utils/queryString';
 import {MutableSearch} from 'sentry/utils/tokenizeSearch';
@@ -148,15 +152,12 @@ export function MostRegressedProfileFunctions(props: MostRegressedProfileFunctio
 
   const trends = trendsQuery?.data ?? [];
 
-  const onRegressedFunctionClick = useCallback(() => {
-    trackAnalytics('profiling_views.go_to_flamegraph', {
-      organization,
-      source: `profiling_transaction.regressed_functions_table`,
-    });
-  }, [organization]);
-
   const onChangeTrendType = useCallback(v => setTrendType(v.value), []);
 
+  const hasDifferentialFlamegraphPageFeature = organization.features.includes(
+    'organizations:profiling-differential-flamegraph-page'
+  );
+
   return (
     <RegressedFunctionsContainer>
       <RegressedFunctionsTitleContainer>
@@ -194,67 +195,22 @@ export function MostRegressedProfileFunctions(props: MostRegressedProfileFunctio
           const {before, after} = findWorstProfileIDBeforeAndAfter(fn);
           return (
             <RegressedFunctionRow key={i}>
-              <RegressedFunctionMainRow>
-                <div>
-                  <Link
-                    onClick={onRegressedFunctionClick}
-                    to={generateProfileFlamechartRouteWithQuery({
-                      orgSlug: organization.slug,
-                      projectSlug: project?.slug ?? '',
-                      profileId: (fn['examples()']?.[0] as string) ?? '',
-                      query: {
-                        // specify the frame to focus, the flamegraph will switch
-                        // to the appropriate thread when these are specified
-                        frameName: fn.function as string,
-                        framePackage: fn.package as string,
-                      },
-                    })}
-                  >
-                    <TextTruncateOverflow>{fn.function}</TextTruncateOverflow>
-                  </Link>
-                </div>
-                <div>
-                  <Link
-                    onClick={onRegressedFunctionClick}
-                    to={generateProfileFlamechartRouteWithQuery({
-                      orgSlug: organization.slug,
-                      projectSlug: project?.slug ?? '',
-                      profileId: before,
-                      query: {
-                        // specify the frame to focus, the flamegraph will switch
-                        // to the appropriate thread when these are specified
-                        frameName: fn.function as string,
-                        framePackage: fn.package as string,
-                      },
-                    })}
-                  >
-                    <PerformanceDuration
-                      abbreviation
-                      nanoseconds={fn.aggregate_range_1 as number}
-                    />
-                  </Link>
-                  <ChangeArrow>{' \u2192 '}</ChangeArrow>
-                  <Link
-                    onClick={onRegressedFunctionClick}
-                    to={generateProfileFlamechartRouteWithQuery({
-                      orgSlug: organization.slug,
-                      projectSlug: project?.slug ?? '',
-                      profileId: after,
-                      query: {
-                        // specify the frame to focus, the flamegraph will switch
-                        // to the appropriate thread when these are specified
-                        frameName: fn.function as string,
-                        framePackage: fn.package as string,
-                      },
-                    })}
-                  >
-                    <PerformanceDuration
-                      abbreviation
-                      nanoseconds={fn.aggregate_range_2 as number}
-                    />
-                  </Link>
-                </div>
-              </RegressedFunctionMainRow>
+              {hasDifferentialFlamegraphPageFeature ? (
+                <RegressedFunctionDifferentialFlamegraph
+                  transaction={props.transaction}
+                  organization={organization}
+                  project={project}
+                  fn={fn}
+                />
+              ) : (
+                <RegressedFunctionBeforeAfterFlamechart
+                  organization={organization}
+                  project={project}
+                  before={before}
+                  after={after}
+                  fn={fn}
+                />
+              )}
               <RegressedFunctionMetricsRow>
                 <div>
                   <TextTruncateOverflow>{fn.package}</TextTruncateOverflow>
@@ -292,6 +248,144 @@ export function MostRegressedProfileFunctions(props: MostRegressedProfileFunctio
   );
 }
 
+interface RegressedFunctionDifferentialFlamegraphProps {
+  fn: FunctionTrend;
+  organization: Organization;
+  project: Project | null;
+  transaction: string;
+}
+
+function RegressedFunctionDifferentialFlamegraph(
+  props: RegressedFunctionDifferentialFlamegraphProps
+) {
+  const onRegressedFunctionClick = useCallback(() => {
+    trackAnalytics('profiling_views.go_to_differential_flamegraph', {
+      organization: props.organization,
+      source: `profiling_transaction.regressed_functions_table`,
+    });
+  }, [props.organization]);
+
+  const differentialFlamegraphLink = generateProfileDifferentialFlamegraphRouteWithQuery({
+    orgSlug: props.organization.slug,
+    projectSlug: props.project?.slug ?? '',
+    transaction: props.transaction,
+    fingerprint: props.fn.fingerprint,
+    breakpoint: props.fn.breakpoint,
+    query: {
+      // specify the frame to focus, the flamegraph will switch
+      // to the appropriate thread when these are specified
+      frameName: props.fn.function as string,
+      framePackage: props.fn.package as string,
+    },
+  });
+
+  return (
+    <RegressedFunctionMainRow>
+      <div>
+        <Link onClick={onRegressedFunctionClick} to={differentialFlamegraphLink}>
+          <TextTruncateOverflow>{props.fn.function}</TextTruncateOverflow>
+        </Link>
+      </div>
+      <div>
+        <Link onClick={onRegressedFunctionClick} to={differentialFlamegraphLink}>
+          <PerformanceDuration
+            abbreviation
+            nanoseconds={props.fn.aggregate_range_1 as number}
+          />
+          <ChangeArrow>{' \u2192 '}</ChangeArrow>
+          <PerformanceDuration
+            abbreviation
+            nanoseconds={props.fn.aggregate_range_2 as number}
+          />
+        </Link>
+      </div>
+    </RegressedFunctionMainRow>
+  );
+}
+
+interface RegressedFunctionBeforeAfterProps {
+  after: string;
+  before: string;
+  fn: FunctionTrend;
+  organization: Organization;
+  project: Project | null;
+}
+
+function RegressedFunctionBeforeAfterFlamechart(
+  props: RegressedFunctionBeforeAfterProps
+) {
+  const onRegressedFunctionClick = useCallback(() => {
+    trackAnalytics('profiling_views.go_to_flamegraph', {
+      organization: props.organization,
+      source: `profiling_transaction.regressed_functions_table`,
+    });
+  }, [props.organization]);
+
+  return (
+    <RegressedFunctionMainRow>
+      <div>
+        <Link
+          onClick={onRegressedFunctionClick}
+          to={generateProfileFlamechartRouteWithQuery({
+            orgSlug: props.organization.slug,
+            projectSlug: props.project?.slug ?? '',
+            profileId: (props.fn['examples()']?.[0] as string) ?? '',
+            query: {
+              // specify the frame to focus, the flamegraph will switch
+              // to the appropriate thread when these are specified
+              frameName: props.fn.function as string,
+              framePackage: props.fn.package as string,
+            },
+          })}
+        >
+          <TextTruncateOverflow>{props.fn.function}</TextTruncateOverflow>
+        </Link>
+      </div>
+      <div>
+        <Link
+          onClick={onRegressedFunctionClick}
+          to={generateProfileFlamechartRouteWithQuery({
+            orgSlug: props.organization.slug,
+            projectSlug: props.project?.slug ?? '',
+            profileId: props.before,
+            query: {
+              // specify the frame to focus, the flamegraph will switch
+              // to the appropriate thread when these are specified
+              frameName: props.fn.function as string,
+              framePackage: props.fn.package as string,
+            },
+          })}
+        >
+          <PerformanceDuration
+            abbreviation
+            nanoseconds={props.fn.aggregate_range_1 as number}
+          />
+        </Link>
+        <ChangeArrow>{' \u2192 '}</ChangeArrow>
+        <Link
+          onClick={onRegressedFunctionClick}
+          to={generateProfileFlamechartRouteWithQuery({
+            orgSlug: props.organization.slug,
+            projectSlug: props.project?.slug ?? '',
+            profileId: props.after,
+            query: {
+              // specify the frame to focus, the flamegraph will switch
+              // to the appropriate thread when these are specified
+              frameName: props.fn.function as string,
+              framePackage: props.fn.package as string,
+            },
+          })}
+        >
+          <PerformanceDuration
+            abbreviation
+            nanoseconds={props.fn.aggregate_range_2 as number}
+          />
+        </Link>
+      </div>
+    </RegressedFunctionMainRow>
+  );
+}
+
 const ChangeArrow = styled('span')`
   color: ${p => p.theme.subText};
 `;