|
36 | 36 | )
|
37 | 37 | from sentry.search.eap.types import SearchResolverConfig
|
38 | 38 | from sentry.search.eap.utils import literal_validator
|
39 |
| -from sentry.search.events.constants import WEB_VITALS_PERFORMANCE_SCORE_WEIGHTS |
| 39 | +from sentry.search.events.constants import ( |
| 40 | + MISERY_ALPHA, |
| 41 | + MISERY_BETA, |
| 42 | + WEB_VITALS_PERFORMANCE_SCORE_WEIGHTS, |
| 43 | +) |
40 | 44 | from sentry.snuba import spans_rpc
|
41 | 45 | from sentry.snuba.referrer import Referrer
|
42 | 46 |
|
@@ -795,6 +799,234 @@ def eps(_: ResolvedArguments, settings: ResolverSettings) -> Column.BinaryFormul
|
795 | 799 | )
|
796 | 800 |
|
797 | 801 |
|
| 802 | +def apdex(args: ResolvedArguments, settings: ResolverSettings) -> Column.BinaryFormula: |
| 803 | + """ |
| 804 | + Calculate Apdex score based on response time field and threshold. |
| 805 | +
|
| 806 | + Apdex = (Satisfactory + Tolerable/2) / Total Requests |
| 807 | + Where: |
| 808 | + - Satisfactory: response time ≤ T |
| 809 | + - Tolerable: response time > T and ≤ 4T |
| 810 | + - Frustrated: response time > 4T |
| 811 | + """ |
| 812 | + extrapolation_mode = settings["extrapolation_mode"] |
| 813 | + |
| 814 | + # Get the response time field and threshold |
| 815 | + response_time_field = cast(AttributeKey, args[0]) |
| 816 | + threshold = cast(float, args[1]) |
| 817 | + |
| 818 | + # Calculate 4T for tolerable range |
| 819 | + tolerable_threshold = threshold * 4 |
| 820 | + |
| 821 | + # Satisfactory requests: response time ≤ T and is_transaction = True |
| 822 | + satisfactory = Column( |
| 823 | + conditional_aggregation=AttributeConditionalAggregation( |
| 824 | + aggregate=Function.FUNCTION_COUNT, |
| 825 | + key=response_time_field, |
| 826 | + filter=TraceItemFilter( |
| 827 | + and_filter=AndFilter( |
| 828 | + filters=[ |
| 829 | + TraceItemFilter( |
| 830 | + comparison_filter=ComparisonFilter( |
| 831 | + key=response_time_field, |
| 832 | + op=ComparisonFilter.OP_LESS_THAN_OR_EQUALS, |
| 833 | + value=AttributeValue(val_double=threshold), |
| 834 | + ) |
| 835 | + ), |
| 836 | + TraceItemFilter( |
| 837 | + comparison_filter=ComparisonFilter( |
| 838 | + key=AttributeKey( |
| 839 | + type=AttributeKey.TYPE_BOOLEAN, name="sentry.is_segment" |
| 840 | + ), |
| 841 | + op=ComparisonFilter.OP_EQUALS, |
| 842 | + value=AttributeValue(val_bool=True), |
| 843 | + ) |
| 844 | + ), |
| 845 | + ] |
| 846 | + ) |
| 847 | + ), |
| 848 | + extrapolation_mode=extrapolation_mode, |
| 849 | + ) |
| 850 | + ) |
| 851 | + |
| 852 | + # Tolerable requests: response time > T and ≤ 4T and is_transaction = True |
| 853 | + tolerable = Column( |
| 854 | + conditional_aggregation=AttributeConditionalAggregation( |
| 855 | + aggregate=Function.FUNCTION_COUNT, |
| 856 | + key=response_time_field, |
| 857 | + filter=TraceItemFilter( |
| 858 | + and_filter=AndFilter( |
| 859 | + filters=[ |
| 860 | + TraceItemFilter( |
| 861 | + comparison_filter=ComparisonFilter( |
| 862 | + key=response_time_field, |
| 863 | + op=ComparisonFilter.OP_GREATER_THAN, |
| 864 | + value=AttributeValue(val_double=threshold), |
| 865 | + ) |
| 866 | + ), |
| 867 | + TraceItemFilter( |
| 868 | + comparison_filter=ComparisonFilter( |
| 869 | + key=response_time_field, |
| 870 | + op=ComparisonFilter.OP_LESS_THAN_OR_EQUALS, |
| 871 | + value=AttributeValue(val_double=tolerable_threshold), |
| 872 | + ) |
| 873 | + ), |
| 874 | + TraceItemFilter( |
| 875 | + comparison_filter=ComparisonFilter( |
| 876 | + key=AttributeKey( |
| 877 | + type=AttributeKey.TYPE_BOOLEAN, name="sentry.is_segment" |
| 878 | + ), |
| 879 | + op=ComparisonFilter.OP_EQUALS, |
| 880 | + value=AttributeValue(val_bool=True), |
| 881 | + ) |
| 882 | + ), |
| 883 | + ] |
| 884 | + ) |
| 885 | + ), |
| 886 | + extrapolation_mode=extrapolation_mode, |
| 887 | + ) |
| 888 | + ) |
| 889 | + |
| 890 | + # Total requests: count of all requests with the response time field and is_transaction = True |
| 891 | + total = Column( |
| 892 | + conditional_aggregation=AttributeConditionalAggregation( |
| 893 | + aggregate=Function.FUNCTION_COUNT, |
| 894 | + key=response_time_field, |
| 895 | + filter=TraceItemFilter( |
| 896 | + comparison_filter=ComparisonFilter( |
| 897 | + key=AttributeKey(type=AttributeKey.TYPE_BOOLEAN, name="sentry.is_segment"), |
| 898 | + op=ComparisonFilter.OP_EQUALS, |
| 899 | + value=AttributeValue(val_bool=True), |
| 900 | + ) |
| 901 | + ), |
| 902 | + extrapolation_mode=extrapolation_mode, |
| 903 | + ) |
| 904 | + ) |
| 905 | + |
| 906 | + # Calculate (Satisfactory + Tolerable/2) / Total |
| 907 | + numerator = Column( |
| 908 | + formula=Column.BinaryFormula( |
| 909 | + left=satisfactory, |
| 910 | + op=Column.BinaryFormula.OP_ADD, |
| 911 | + right=Column( |
| 912 | + formula=Column.BinaryFormula( |
| 913 | + left=tolerable, |
| 914 | + op=Column.BinaryFormula.OP_DIVIDE, |
| 915 | + right=Column(literal=LiteralValue(val_double=2.0)), |
| 916 | + ) |
| 917 | + ), |
| 918 | + ) |
| 919 | + ) |
| 920 | + |
| 921 | + return Column.BinaryFormula( |
| 922 | + left=numerator, |
| 923 | + op=Column.BinaryFormula.OP_DIVIDE, |
| 924 | + right=total, |
| 925 | + ) |
| 926 | + |
| 927 | + |
| 928 | +def user_misery(args: ResolvedArguments, settings: ResolverSettings) -> Column.BinaryFormula: |
| 929 | + """ |
| 930 | + Calculate User Misery score based on response time field and threshold. |
| 931 | +
|
| 932 | + User Misery = (miserable_users + α) / (total_unique_users + α + β) |
| 933 | + Where: |
| 934 | + - miserable_users: unique users with response time > 4T |
| 935 | + - total_unique_users: total unique users with response time field |
| 936 | + - α (MISERY_ALPHA) = 5.8875 |
| 937 | + - β (MISERY_BETA) = 111.8625 |
| 938 | + """ |
| 939 | + extrapolation_mode = settings["extrapolation_mode"] |
| 940 | + |
| 941 | + # Get the response time field and threshold |
| 942 | + response_time_field = cast(AttributeKey, args[0]) |
| 943 | + threshold = cast(float, args[1]) |
| 944 | + |
| 945 | + # Calculate 4T for miserable threshold |
| 946 | + miserable_threshold = threshold * 4 |
| 947 | + |
| 948 | + # Count miserable users: unique users with response time > 4T and is_transaction = True |
| 949 | + miserable_users = Column( |
| 950 | + conditional_aggregation=AttributeConditionalAggregation( |
| 951 | + aggregate=Function.FUNCTION_UNIQ, |
| 952 | + key=AttributeKey(type=AttributeKey.TYPE_STRING, name="sentry.user"), |
| 953 | + filter=TraceItemFilter( |
| 954 | + and_filter=AndFilter( |
| 955 | + filters=[ |
| 956 | + TraceItemFilter( |
| 957 | + comparison_filter=ComparisonFilter( |
| 958 | + key=response_time_field, |
| 959 | + op=ComparisonFilter.OP_GREATER_THAN, |
| 960 | + value=AttributeValue(val_double=miserable_threshold), |
| 961 | + ) |
| 962 | + ), |
| 963 | + TraceItemFilter( |
| 964 | + comparison_filter=ComparisonFilter( |
| 965 | + key=AttributeKey( |
| 966 | + type=AttributeKey.TYPE_BOOLEAN, name="sentry.is_segment" |
| 967 | + ), |
| 968 | + op=ComparisonFilter.OP_EQUALS, |
| 969 | + value=AttributeValue(val_bool=True), |
| 970 | + ) |
| 971 | + ), |
| 972 | + ] |
| 973 | + ) |
| 974 | + ), |
| 975 | + extrapolation_mode=extrapolation_mode, |
| 976 | + ) |
| 977 | + ) |
| 978 | + |
| 979 | + # Count total unique users: unique users with response time field and is_transaction = True |
| 980 | + total_unique_users = Column( |
| 981 | + conditional_aggregation=AttributeConditionalAggregation( |
| 982 | + aggregate=Function.FUNCTION_UNIQ, |
| 983 | + key=AttributeKey(type=AttributeKey.TYPE_STRING, name="sentry.user"), |
| 984 | + filter=TraceItemFilter( |
| 985 | + and_filter=AndFilter( |
| 986 | + filters=[ |
| 987 | + TraceItemFilter( |
| 988 | + exists_filter=ExistsFilter(key=response_time_field), |
| 989 | + ), |
| 990 | + TraceItemFilter( |
| 991 | + comparison_filter=ComparisonFilter( |
| 992 | + key=AttributeKey( |
| 993 | + type=AttributeKey.TYPE_BOOLEAN, name="sentry.is_segment" |
| 994 | + ), |
| 995 | + op=ComparisonFilter.OP_EQUALS, |
| 996 | + value=AttributeValue(val_bool=True), |
| 997 | + ) |
| 998 | + ), |
| 999 | + ] |
| 1000 | + ) |
| 1001 | + ), |
| 1002 | + extrapolation_mode=extrapolation_mode, |
| 1003 | + ) |
| 1004 | + ) |
| 1005 | + |
| 1006 | + # Calculate (miserable_users + α) / (total_unique_users + α + β) |
| 1007 | + numerator = Column( |
| 1008 | + formula=Column.BinaryFormula( |
| 1009 | + left=miserable_users, |
| 1010 | + op=Column.BinaryFormula.OP_ADD, |
| 1011 | + right=Column(literal=LiteralValue(val_double=MISERY_ALPHA)), |
| 1012 | + ) |
| 1013 | + ) |
| 1014 | + |
| 1015 | + denominator = Column( |
| 1016 | + formula=Column.BinaryFormula( |
| 1017 | + left=total_unique_users, |
| 1018 | + op=Column.BinaryFormula.OP_ADD, |
| 1019 | + right=Column(literal=LiteralValue(val_double=MISERY_ALPHA + MISERY_BETA)), |
| 1020 | + ) |
| 1021 | + ) |
| 1022 | + |
| 1023 | + return Column.BinaryFormula( |
| 1024 | + left=numerator, |
| 1025 | + op=Column.BinaryFormula.OP_DIVIDE, |
| 1026 | + right=denominator, |
| 1027 | + ) |
| 1028 | + |
| 1029 | + |
798 | 1030 | SPAN_FORMULA_DEFINITIONS = {
|
799 | 1031 | "http_response_rate": FormulaDefinition(
|
800 | 1032 | default_search_type="percentage",
|
@@ -987,4 +1219,34 @@ def eps(_: ResolvedArguments, settings: ResolverSettings) -> Column.BinaryFormul
|
987 | 1219 | "eps": FormulaDefinition(
|
988 | 1220 | default_search_type="rate", arguments=[], formula_resolver=eps, is_aggregate=True
|
989 | 1221 | ),
|
| 1222 | + "apdex": FormulaDefinition( |
| 1223 | + default_search_type="number", |
| 1224 | + infer_search_type_from_arguments=False, |
| 1225 | + arguments=[ |
| 1226 | + AttributeArgumentDefinition( |
| 1227 | + attribute_types={ |
| 1228 | + "duration", |
| 1229 | + *constants.DURATION_TYPE, |
| 1230 | + }, |
| 1231 | + ), |
| 1232 | + ValueArgumentDefinition(argument_types={"number"}), |
| 1233 | + ], |
| 1234 | + formula_resolver=apdex, |
| 1235 | + is_aggregate=True, |
| 1236 | + ), |
| 1237 | + "user_misery": FormulaDefinition( |
| 1238 | + default_search_type="number", |
| 1239 | + infer_search_type_from_arguments=False, |
| 1240 | + arguments=[ |
| 1241 | + AttributeArgumentDefinition( |
| 1242 | + attribute_types={ |
| 1243 | + "duration", |
| 1244 | + *constants.DURATION_TYPE, |
| 1245 | + }, |
| 1246 | + ), |
| 1247 | + ValueArgumentDefinition(argument_types={"number"}), |
| 1248 | + ], |
| 1249 | + formula_resolver=user_misery, |
| 1250 | + is_aggregate=True, |
| 1251 | + ), |
990 | 1252 | }
|
0 commit comments