Setting the file. One moment.
Command Classifier · Amazon Elasticache · aws/agent-toolkit-for-aws · Skills Docs
ContentsBack to the top of the page 10
Setup DevOps Agent
33
AWS Deployment
61.5
Recipe Gallery
scripts/command_classifier.py
scripts/ command_classifier.py
Python · 158 lines · 6 KB
16
17 NOTE : This module uses a heuristic approximation for the vCPU
18 dimension. AWS documentation does not publish the exact ECPU
19 calculation formula. AWS states that "the number of ECPUs consumed
20 by your requests depends on the vCPU time taken and the amount of
21 data transferred" and that the higher dimension determines the cost.
22 Actual ECPU consumption may differ from this estimate.
23
24 Internal/service commands (e.g., REPLCONF, PSYNC, CLUSTER):
25 Excluded from estimates. Includes commands that are not metered
26 (AUTH, MULTI, EXEC, SUBSCRIBE, UNSUBSCRIBE, CONFIG, CLUSTER),
27 replication internals (REPLCONF, PSYNC), and commands ignored by
28 the metering system (INFO, PUBLISH). If your workload issues a
29 high volume of commands not covered by this classifier, actual
30 ECPU costs may differ from the estimate. Use CloudWatch ECPU
31 metrics for precise serverless billing.
32
33 IMPORTANT: The simple/complex classification and specific ECPU formulas used
34 in this module are heuristic approximations based on publicly available AWS
35 documentation and pricing examples. AWS does not publish exact ECPU calculation
36 formulas. Per AWS docs, "the number of ECPUs consumed by your requests depends
37 on the vCPU time taken and the amount of data transferred." For accurate
38 billing data, use CloudWatch ElastiCacheProcessingUnits and per-command ECPU
39 metrics on serverless caches.
40
41 Reference: https://aws.amazon.com/elasticache/pricing/
42 """
43
44 # Simple (fixed) commands - 1 ECPU per KB transferred (minimum 1 ECPU).
45 # Based on ElastiCache Serverless pricing documentation.
46 # Any command not listed here or in INTERNAL_COMMANDS falls through to
47 # nonfixed (complex), which uses MAX(calls, usec/3) as a conservative estimate.
48 FIXED_COMMANDS = frozenset ([
49 "get" , "set" , "hget" , "hset" ,
50 "incr" , "decr" , "incrby" , "decrby" , "incrbyfloat" ,
51 "expire" , "pexpire" , "pexpireat" , "expireat" , "persist" ,
52 "exists" , "ttl" , "pttl" , "type" , "strlen" ,
53 "scard" , "zcard" , "llen" , "xlen" ,
54 "sismember" , "hexists" , "hlen" , "hsetnx" ,
55 "hincrby" , "hincrbyfloat" ,
56 "getbit" , "setbit" , "setnx" , "setex" , "psetex" ,
57 "zscore" ,
58 "ping" ,
59 "del" , "unlink" ,
60 "select" , "echo" , "time" , "quit" , "reset" ,
61 "watch" , "unwatch" , "move" , "asking" ,
62 "readonly" , "readwrite" ,
63 "acl" , "client" , "command" ,
64 ])
65
66 # Commands not metered by serverless - excluded from ECPU estimates.
67 # Includes free commands (AUTH, MULTI, EXEC, pub/sub) and ignored
68 # commands (INFO, PUBLISH). Also includes replication/cluster internals
69 # and commands not available on serverless.
70 INTERNAL_COMMANDS = frozenset ([
71 "auth" , "multi" , "exec" , "hello" , "discard" ,
72 "subscribe" , "unsubscribe" , "psubscribe" , "punsubscribe" ,
73 "publish" , "pubsub" ,
74 "info" , "config" , "cluster" ,
75 "replconf" , "psync" , "replicaof" ,
76 "slowlog" , "dbsize" , "wait" ,
77 "object" , "debug" , "memory" , "latency" ,
78 "module" , "function" , "swapdb" ,
79 ])
80
81
82 def classify_command (cmd_name: str ) -> str :
83 """Classify a Redis/Valkey command.
84
85 Returns:
86 'fixed' - 1 ECPU per call (simple O(1) commands)
87 'nonfixed' - estimated as MAX(calls, usec/3) ECPUs (approximation)
88 'internal' - not metered, exclude from estimate
89 """
90 cmd = cmd_name.lower().strip()
91 if cmd in INTERNAL_COMMANDS :
92 return "internal"
93 if cmd in FIXED_COMMANDS :
94 return "fixed"
95 return "nonfixed"
96
97
98 def estimate_ecpus_from_commandstats (commandstats: dict ) -> dict :
99 """Estimate ECPUs from Redis/Valkey INFO commandstats output.
100
101 Args:
102 commandstats: Dict mapping command name -> {calls: int, usec: int}
103 Example:
104 {"get": {"calls": 1000000, "usec": 1500000},
105 "eval": {"calls": 50000, "usec": 2000000}}
106
107 Returns:
108 Dict with:
109 total_ecpus: Estimated total ECPUs
110 fixed_ecpus: ECPUs from fixed-price commands
111 nonfixed_ecpus: ECPUs from non-fixed commands
112 internal_calls: Calls excluded (internal commands)
113 command_breakdown: Per-command detail list
114 """
115 fixed_ecpus = 0
116 nonfixed_ecpus = 0
117 internal_calls = 0
118 breakdown = []
119
120 for cmd, stats in commandstats.items():
121 calls = stats.get( "calls" , 0 )
122 usec = stats.get( "usec" , 0 )
123 classification = classify_command(cmd)
124
125 if classification == "internal" :
126 internal_calls += calls
127 breakdown.append({
128 "command" : cmd, "type" : "internal" ,
129 "calls" : calls, "usec" : usec, "ecpus" : 0 ,
130 })
131 elif classification == "fixed" :
132 ecpus = calls
133 fixed_ecpus += ecpus
134 breakdown.append({
135 "command" : cmd, "type" : "fixed" ,
136 "calls" : calls, "usec" : usec, "ecpus" : ecpus,
137 })
138 else :
139 # Non-fixed: MAX(calls, usec/3) - approximation based on the
140 # assumption that 1 ECPU ~ 3 microseconds of vCPU time (derived
141 # from the AWS pricing blog example). Actual ECPU consumption
142 # may differ. See module docstring for details.
143 ecpus = max (calls, usec / 3.0 )
144 nonfixed_ecpus += ecpus
145 breakdown.append({
146 "command" : cmd, "type" : "nonfixed" ,
147 "calls" : calls, "usec" : usec, "ecpus" : round (ecpus),
148 })
149
150 return {
151 "total_ecpus" : round (fixed_ecpus + nonfixed_ecpus),
152 "fixed_ecpus" : round (fixed_ecpus),
153 "nonfixed_ecpus" : round (nonfixed_ecpus),
154 "internal_calls" : internal_calls,
155 "command_breakdown" : sorted (
156 breakdown, key =lambda x: x[ "ecpus" ], reverse = True
157 ),
158 }