1#!/usr/bin/env python3 2# SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) 3import argparse 4import math 5import os 6from typing import Optional 7from common_metrics import Cycles 8from metric import (d_ratio, has_event, max, Event, JsonEncodeMetric, 9 JsonEncodeMetricGroupDescriptions, Literal, LoadEvents, 10 Metric, MetricGroup, Select) 11 12# Global command line arguments. 13_args = None 14_zen_model: int = 1 15interval_sec = Event("duration_time") 16ins = Event("instructions") 17cycles = Event("cycles") 18# Number of CPU cycles scaled for SMT. 19smt_cycles = Select(cycles / 2, Literal("#smt_on"), cycles) 20 21 22def AmdBr(): 23 def Total() -> MetricGroup: 24 br = Event("ex_ret_brn") 25 br_m_all = Event("ex_ret_brn_misp") 26 br_clr = Event("ex_ret_brn_cond_misp", 27 "ex_ret_msprd_brnch_instr_dir_msmtch", 28 "ex_ret_brn_resync") 29 30 br_r = d_ratio(br, interval_sec) 31 ins_r = d_ratio(ins, br) 32 misp_r = d_ratio(br_m_all, br) 33 clr_r = d_ratio(br_clr, interval_sec) 34 35 return MetricGroup("lpm_br_total", [ 36 Metric("lpm_br_total_retired", 37 "The number of branch instructions retired per second.", br_r, 38 "insn/s"), 39 Metric( 40 "lpm_br_total_mispred", 41 "The number of branch instructions retired, of any type, that were " 42 "not correctly predicted as a percentage of all branch instrucions.", 43 misp_r, "100%"), 44 Metric("lpm_br_total_insn_between_branches", 45 "The number of instructions divided by the number of branches.", 46 ins_r, "insn"), 47 Metric("lpm_br_total_insn_fe_resteers", 48 "The number of resync branches per second.", clr_r, "req/s") 49 ]) 50 51 def Taken() -> MetricGroup: 52 br = Event("ex_ret_brn_tkn") 53 br_m_tk = Event("ex_ret_brn_tkn_misp") 54 br_r = d_ratio(br, interval_sec) 55 ins_r = d_ratio(ins, br) 56 misp_r = d_ratio(br_m_tk, br) 57 return MetricGroup("lpm_br_taken", [ 58 Metric("lpm_br_taken_retired", 59 "The number of taken branches that were retired per second.", 60 br_r, "insn/s"), 61 Metric( 62 "lpm_br_taken_mispred", 63 "The number of retired taken branch instructions that were " 64 "mispredicted as a percentage of all taken branches.", misp_r, 65 "100%"), 66 Metric( 67 "lpm_br_taken_insn_between_branches", 68 "The number of instructions divided by the number of taken branches.", 69 ins_r, "insn"), 70 ]) 71 72 def Conditional() -> Optional[MetricGroup]: 73 global _zen_model 74 br = Event("ex_ret_brn_cond", "ex_ret_cond") 75 br_r = d_ratio(br, interval_sec) 76 ins_r = d_ratio(ins, br) 77 78 metrics = [ 79 Metric("lpm_br_cond_retired", "Retired conditional branch instructions.", 80 br_r, "insn/s"), 81 Metric("lpm_br_cond_insn_between_branches", 82 "The number of instructions divided by the number of conditional " 83 "branches.", ins_r, "insn"), 84 ] 85 if _zen_model == 2: 86 br_m_cond = Event("ex_ret_cond_misp") 87 misp_r = d_ratio(br_m_cond, br) 88 metrics += [ 89 Metric("lpm_br_cond_mispred", 90 "Retired conditional branch instructions mispredicted as a " 91 "percentage of all conditional branches.", misp_r, "100%"), 92 ] 93 94 return MetricGroup("lpm_br_cond", metrics) 95 96 def Fused() -> MetricGroup: 97 br = Event("ex_ret_fused_instr", "ex_ret_fus_brnch_inst") 98 br_r = d_ratio(br, interval_sec) 99 ins_r = d_ratio(ins, br) 100 return MetricGroup("lpm_br_cond", [ 101 Metric("lpm_br_fused_retired", 102 "Retired fused branch instructions per second.", br_r, "insn/s"), 103 Metric( 104 "lpm_br_fused_insn_between_branches", 105 "The number of instructions divided by the number of fused " 106 "branches.", ins_r, "insn"), 107 ]) 108 109 def Far() -> MetricGroup: 110 br = Event("ex_ret_brn_far") 111 br_r = d_ratio(br, interval_sec) 112 ins_r = d_ratio(ins, br) 113 return MetricGroup("lpm_br_far", [ 114 Metric("lpm_br_far_retired", "Retired far control transfers per second.", 115 br_r, "insn/s"), 116 Metric( 117 "lpm_br_far_insn_between_branches", 118 "The number of instructions divided by the number of far branches.", 119 ins_r, "insn"), 120 ]) 121 122 return MetricGroup("lpm_br", [Total(), Taken(), Conditional(), Fused(), Far()], 123 description="breakdown of retired branch instructions") 124 125 126def AmdCtxSw() -> MetricGroup: 127 cs = Event("context\\-switches") 128 metrics = [ 129 Metric("lpm_cs_rate", "Context switches per second", 130 d_ratio(cs, interval_sec), "ctxsw/s") 131 ] 132 133 ev = Event("instructions") 134 metrics.append(Metric("lpm_cs_instr", "Instructions per context switch", 135 d_ratio(ev, cs), "instr/cs")) 136 137 ev = Event("cycles") 138 metrics.append(Metric("lpm_cs_cycles", "Cycles per context switch", 139 d_ratio(ev, cs), "cycles/cs")) 140 141 ev = Event("ls_dispatch.pure_ld", "ls_dispatch.ld_dispatch") 142 metrics.append(Metric("lpm_cs_loads", "Loads per context switch", 143 d_ratio(ev, cs), "loads/cs")) 144 145 ev = Event("ls_dispatch.pure_st", "ls_dispatch.store_dispatch") 146 metrics.append(Metric("lpm_cs_stores", "Stores per context switch", 147 d_ratio(ev, cs), "stores/cs")) 148 149 ev = Event("ex_ret_brn_tkn") 150 metrics.append(Metric("lpm_cs_br_taken", "Branches taken per context switch", 151 d_ratio(ev, cs), "br_taken/cs")) 152 153 return MetricGroup("lpm_cs", metrics, 154 description=("Number of context switches per second, instructions " 155 "retired & core cycles between context switches")) 156 157 158def AmdDtlb() -> Optional[MetricGroup]: 159 global _zen_model 160 if _zen_model >= 4: 161 return None 162 163 d_dat = Event("ls_dc_accesses") if _zen_model <= 3 else None 164 d_h4k = Event("ls_l1_d_tlb_miss.tlb_reload_4k_l2_hit") 165 d_hcoal = Event( 166 "ls_l1_d_tlb_miss.tlb_reload_coalesced_page_hit") if _zen_model >= 2 else 0 167 d_h2m = Event("ls_l1_d_tlb_miss.tlb_reload_2m_l2_hit") 168 d_h1g = Event("ls_l1_d_tlb_miss.tlb_reload_1g_l2_hit") 169 170 d_m4k = Event("ls_l1_d_tlb_miss.tlb_reload_4k_l2_miss") 171 d_mcoal = Event( 172 "ls_l1_d_tlb_miss.tlb_reload_coalesced_page_miss") if _zen_model >= 2 else 0 173 d_m2m = Event("ls_l1_d_tlb_miss.tlb_reload_2m_l2_miss") 174 d_m1g = Event("ls_l1_d_tlb_miss.tlb_reload_1g_l2_miss") 175 176 d_w0 = Event("ls_tablewalker.dc_type0") if _zen_model <= 3 else None 177 d_w1 = Event("ls_tablewalker.dc_type1") if _zen_model <= 3 else None 178 walks = d_w0 + d_w1 179 walks_r = d_ratio(walks, interval_sec) 180 ins_w = d_ratio(ins, walks) 181 l1 = d_dat 182 l1_r = d_ratio(l1, interval_sec) 183 l2_hits = d_h4k + d_hcoal + d_h2m + d_h1g 184 l2_miss = d_m4k + d_mcoal + d_m2m + d_m1g 185 l2_r = d_ratio(l2_hits + l2_miss, interval_sec) 186 l1_miss = l2_hits + l2_miss + walks 187 l1_hits = max(l1 - l1_miss, 0) 188 ins_l = d_ratio(ins, l1_miss) 189 190 return MetricGroup("lpm_dtlb", [ 191 MetricGroup("lpm_dtlb_ov", [ 192 Metric("lpm_dtlb_ov_insn_bt_l1_miss", 193 "DTLB overview: instructions between l1 misses.", ins_l, 194 "insns"), 195 Metric("lpm_dtlb_ov_insn_bt_walks", 196 "DTLB overview: instructions between dtlb page table walks.", 197 ins_w, "insns"), 198 ]), 199 MetricGroup("lpm_dtlb_l1", [ 200 Metric("lpm_dtlb_l1_hits", 201 "DTLB L1 hits as percentage of all DTLB L1 accesses.", 202 d_ratio(l1_hits, l1), "100%"), 203 Metric("lpm_dtlb_l1_miss", 204 "DTLB L1 misses as percentage of all DTLB L1 accesses.", 205 d_ratio(l1_miss, l1), "100%"), 206 Metric("lpm_dtlb_l1_reqs", "DTLB L1 accesses per second.", l1_r, 207 "insns/s"), 208 ]), 209 MetricGroup("lpm_dtlb_l2", [ 210 Metric("lpm_dtlb_l2_hits", 211 "DTLB L2 hits as percentage of all DTLB L2 accesses.", 212 d_ratio(l2_hits, l2_hits + l2_miss), "100%"), 213 Metric("lpm_dtlb_l2_miss", 214 "DTLB L2 misses as percentage of all DTLB L2 accesses.", 215 d_ratio(l2_miss, l2_hits + l2_miss), "100%"), 216 Metric("lpm_dtlb_l2_reqs", "DTLB L2 accesses per second.", l2_r, 217 "insns/s"), 218 MetricGroup("lpm_dtlb_l2_4kb", [ 219 Metric( 220 "lpm_dtlb_l2_4kb_hits", 221 "DTLB L2 4kb page size hits as percentage of all DTLB L2 4kb " 222 "accesses.", d_ratio(d_h4k, d_h4k + d_m4k), "100%"), 223 Metric( 224 "lpm_dtlb_l2_4kb_miss", 225 "DTLB L2 4kb page size misses as percentage of all DTLB L2 4kb" 226 "accesses.", d_ratio(d_m4k, d_h4k + d_m4k), "100%") 227 ]), 228 MetricGroup("lpm_dtlb_l2_coalesced", [ 229 Metric( 230 "lpm_dtlb_l2_coal_hits", 231 "DTLB L2 coalesced page (16kb) hits as percentage of all DTLB " 232 "L2 coalesced accesses.", d_ratio(d_hcoal, 233 d_hcoal + d_mcoal), "100%"), 234 Metric( 235 "lpm_dtlb_l2_coal_miss", 236 "DTLB L2 coalesced page (16kb) misses as percentage of all " 237 "DTLB L2 coalesced accesses.", 238 d_ratio(d_mcoal, d_hcoal + d_mcoal), "100%") 239 ]), 240 MetricGroup("lpm_dtlb_l2_2mb", [ 241 Metric( 242 "lpm_dtlb_l2_2mb_hits", 243 "DTLB L2 2mb page size hits as percentage of all DTLB L2 2mb " 244 "accesses.", d_ratio(d_h2m, d_h2m + d_m2m), "100%"), 245 Metric( 246 "lpm_dtlb_l2_2mb_miss", 247 "DTLB L2 2mb page size misses as percentage of all DTLB L2 " 248 "accesses.", d_ratio(d_m2m, d_h2m + d_m2m), "100%") 249 ]), 250 MetricGroup("lpm_dtlb_l2_1g", [ 251 Metric( 252 "lpm_dtlb_l2_1g_hits", 253 "DTLB L2 1gb page size hits as percentage of all DTLB L2 1gb " 254 "accesses.", d_ratio(d_h1g, d_h1g + d_m1g), "100%"), 255 Metric( 256 "lpm_dtlb_l2_1g_miss", 257 "DTLB L2 1gb page size misses as percentage of all DTLB L2 " 258 "1gb accesses.", d_ratio(d_m1g, d_h1g + d_m1g), "100%") 259 ]), 260 ]), 261 MetricGroup("lpm_dtlb_walks", [ 262 Metric("lpm_dtlb_walks_reqs", "DTLB page table walks per second.", 263 walks_r, "walks/s"), 264 ]), 265 ], description="Data TLB metrics") 266 267 268def AmdIotlb() -> Optional[MetricGroup]: 269 global _zen_model 270 if _zen_model < 2: 271 return None 272 273 # On AMD, the pde events cover both 2M and 1G pages. 274 total_hit = Event("amd_iommu/mem_iommu_tlb_pte_hit/") + Event( 275 "amd_iommu/mem_iommu_tlb_pde_hit/" 276 ) 277 total_miss = Event("amd_iommu/mem_iommu_tlb_pte_mis/") + Event( 278 "amd_iommu/mem_iommu_tlb_pde_mis/" 279 ) 280 miss_rate = d_ratio(total_miss, total_miss + total_hit) 281 282 interrupt_cache_hit = Event("amd_iommu/int_dte_hit/") 283 interrupt_cache_miss = Event("amd_iommu/int_dte_mis/") 284 interrupt_cache_lookup = interrupt_cache_hit + interrupt_cache_miss 285 interrupt_cache_miss_rate = d_ratio( 286 interrupt_cache_miss, interrupt_cache_miss + interrupt_cache_hit 287 ) 288 289 return MetricGroup( 290 "iotlb", 291 [ 292 Metric("iotlb_total_hit", "IOTLB total hit", total_hit, "hits"), 293 Metric("iotlb_total_miss", "IOTLB total miss", total_miss, "misses"), 294 Metric("iotlb_miss_rate", "IOTLB miss rate", miss_rate, "100%"), 295 Metric( 296 "iotlb_interrupt_cache_hit", 297 "IOTLB interrupt cache hit", 298 interrupt_cache_hit, 299 "hits", 300 ), 301 Metric( 302 "iotlb_interrupt_cache_miss", 303 "IOTLB interrupt cache miss", 304 interrupt_cache_miss, 305 "misses", 306 ), 307 Metric( 308 "iotlb_interrupt_cache_lookup", 309 "IOTLB interrupt cache lookup", 310 interrupt_cache_lookup, 311 "lookups", 312 ), 313 Metric( 314 "iotlb_interrupt_cache_miss_rate", 315 "IOTLB interrupt cache miss rate", 316 interrupt_cache_miss_rate, 317 "100%", 318 ), 319 ], 320 description="IOMMU TLB metrics", 321 ) 322 323 324def AmdItlb(): 325 global _zen_model 326 l2h = Event("bp_l1_tlb_miss_l2_tlb_hit", "bp_l1_tlb_miss_l2_hit") 327 l2m = Event("bp_l1_tlb_miss_l2_tlb_miss.all", "l2_itlb_misses",) 328 l2r = l2h + l2m 329 330 itlb_l1_mg = None 331 l1m = l2r 332 if _zen_model <= 3: 333 l1r = Event("ic_fw32") 334 l1h = max(l1r - l1m, 0) 335 itlb_l1_mg = MetricGroup("lpm_itlb_l1", [ 336 Metric("lpm_itlb_l1_hits", 337 "L1 ITLB hits as a perecentage of L1 ITLB accesses.", 338 d_ratio(l1h, l1h + l1m), "100%"), 339 Metric("lpm_itlb_l1_miss", 340 "L1 ITLB misses as a perecentage of L1 ITLB accesses.", 341 d_ratio(l1m, l1h + l1m), "100%"), 342 Metric("lpm_itlb_l1_reqs", 343 "The number of 32B fetch windows transferred from IC pipe to DE " 344 "instruction decoder per second.", d_ratio( 345 l1r, interval_sec), 346 "windows/sec"), 347 ]) 348 349 return MetricGroup("lpm_itlb", [ 350 MetricGroup("lpm_itlb_ov", [ 351 Metric("lpm_itlb_ov_insn_bt_l1_miss", 352 "Number of instructions between l1 misses", d_ratio( 353 ins, l1m), "insns"), 354 Metric("lpm_itlb_ov_insn_bt_l2_miss", 355 "Number of instructions between l2 misses", d_ratio( 356 ins, l2m), "insns"), 357 ]), 358 itlb_l1_mg, 359 MetricGroup("lpm_itlb_l2", [ 360 Metric("lpm_itlb_l2_hits", 361 "L2 ITLB hits as a percentage of all L2 ITLB accesses.", 362 d_ratio(l2h, l2r), "100%"), 363 Metric("lpm_itlb_l2_miss", 364 "L2 ITLB misses as a percentage of all L2 ITLB accesses.", 365 d_ratio(l2m, l2r), "100%"), 366 Metric("lpm_itlb_l2_reqs", "ITLB accesses per second.", 367 d_ratio(l2r, interval_sec), "accesses/sec"), 368 ]), 369 ], description="Instruction TLB breakdown") 370 371 372def AmdLdSt() -> MetricGroup: 373 ldst_ld = Event("ls_dispatch.pure_ld", "ls_dispatch.ld_dispatch") 374 ldst_st = Event("ls_dispatch.pure_st", "ls_dispatch.store_dispatch") 375 ldst_ldc1 = Event(f"{ldst_ld}/cmask=1/") 376 ldst_stc1 = Event(f"{ldst_st}/cmask=1/") 377 ldst_ldc2 = Event(f"{ldst_ld}/cmask=2/") 378 ldst_stc2 = Event(f"{ldst_st}/cmask=2/") 379 ldst_ldc3 = Event(f"{ldst_ld}/cmask=3/") 380 ldst_stc3 = Event(f"{ldst_st}/cmask=3/") 381 ldst_cyc = Event("ls_not_halted_cyc") 382 383 ld_rate = d_ratio(ldst_ld, interval_sec) 384 st_rate = d_ratio(ldst_st, interval_sec) 385 386 ld_v1 = max(ldst_ldc1 - ldst_ldc2, 0) 387 ld_v2 = max(ldst_ldc2 - ldst_ldc3, 0) 388 ld_v3 = ldst_ldc3 389 390 st_v1 = max(ldst_stc1 - ldst_stc2, 0) 391 st_v2 = max(ldst_stc2 - ldst_stc3, 0) 392 st_v3 = ldst_stc3 393 394 return MetricGroup("lpm_ldst", [ 395 MetricGroup("lpm_ldst_total", [ 396 Metric("lpm_ldst_total_ld", "Number of loads dispatched per second.", 397 ld_rate, "insns/sec"), 398 Metric("lpm_ldst_total_st", "Number of stores dispatched per second.", 399 st_rate, "insns/sec"), 400 ]), 401 MetricGroup("lpm_ldst_percent_insn", [ 402 Metric("lpm_ldst_percent_insn_ld", 403 "Load instructions as a percentage of all instructions.", 404 d_ratio(ldst_ld, ins), "100%"), 405 Metric("lpm_ldst_percent_insn_st", 406 "Store instructions as a percentage of all instructions.", 407 d_ratio(ldst_st, ins), "100%"), 408 ]), 409 MetricGroup("lpm_ldst_ret_loads_per_cycle", [ 410 Metric( 411 "lpm_ldst_ret_loads_per_cycle_1", 412 "Load instructions retiring in 1 cycle as a percentage of all " 413 "unhalted cycles.", d_ratio(ld_v1, ldst_cyc), "100%"), 414 Metric( 415 "lpm_ldst_ret_loads_per_cycle_2", 416 "Load instructions retiring in 2 cycles as a percentage of all " 417 "unhalted cycles.", d_ratio(ld_v2, ldst_cyc), "100%"), 418 Metric( 419 "lpm_ldst_ret_loads_per_cycle_3", 420 "Load instructions retiring in 3 or more cycles as a percentage" 421 "of all unhalted cycles.", d_ratio(ld_v3, ldst_cyc), "100%"), 422 ]), 423 MetricGroup("lpm_ldst_ret_stores_per_cycle", [ 424 Metric( 425 "lpm_ldst_ret_stores_per_cycle_1", 426 "Store instructions retiring in 1 cycle as a percentage of all " 427 "unhalted cycles.", d_ratio(st_v1, ldst_cyc), "100%"), 428 Metric( 429 "lpm_ldst_ret_stores_per_cycle_2", 430 "Store instructions retiring in 2 cycles as a percentage of all " 431 "unhalted cycles.", d_ratio(st_v2, ldst_cyc), "100%"), 432 Metric( 433 "lpm_ldst_ret_stores_per_cycle_3", 434 "Store instructions retiring in 3 or more cycles as a percentage" 435 "of all unhalted cycles.", d_ratio(st_v3, ldst_cyc), "100%"), 436 ]), 437 MetricGroup("lpm_ldst_insn_bt", [ 438 Metric("lpm_ldst_insn_bt_ld", "Number of instructions between loads.", 439 d_ratio(ins, ldst_ld), "insns"), 440 Metric("lpm_ldst_insn_bt_st", "Number of instructions between stores.", 441 d_ratio(ins, ldst_st), "insns"), 442 ]) 443 ], description="Breakdown of load/store instructions") 444 445 446def AmdUpc() -> Metric: 447 ops = Event("ex_ret_ops", "ex_ret_cops") 448 upc = d_ratio(ops, smt_cycles) 449 return Metric("lpm_upc", "Micro-ops retired per core cycle (higher is better)", 450 upc, "uops/cycle") 451 452 453def Idle() -> Metric: 454 cyc = Event("msr/mperf/") 455 tsc = Event("msr/tsc/") 456 low = max(tsc - cyc, 0) 457 return Metric( 458 "lpm_idle", 459 "Percentage of total wallclock cycles where CPUs are in low power state (C1 or deeper sleep state)", 460 d_ratio(low, tsc), "100%") 461 462 463def Rapl() -> MetricGroup: 464 """Processor socket power consumption estimate. 465 466 Use events from the running average power limit (RAPL) driver. 467 """ 468 # Watts = joules/second 469 # Currently only energy-pkg is supported by AMD: 470 # https://lore.kernel.org/lkml/20220105185659.643355-1-eranian@google.com/ 471 pkg = Event("power/energy\\-pkg/") 472 cond_pkg = Select(pkg, has_event(pkg), math.nan) 473 scale = 2.3283064365386962890625e-10 474 metrics = [ 475 Metric("lpm_cpu_power_pkg", "", 476 d_ratio(cond_pkg * scale, interval_sec), "Watts"), 477 ] 478 479 return MetricGroup("lpm_cpu_power", metrics, 480 description="Processor socket power consumption estimates") 481 482 483def UncoreL3(): 484 acc = Event("l3_lookup_state.all_coherent_accesses_to_l3", 485 "l3_lookup_state.all_l3_req_typs") 486 miss = Event("l3_lookup_state.l3_miss", 487 "l3_comb_clstr_state.request_miss") 488 acc = max(acc, miss) 489 hits = acc - miss 490 491 return MetricGroup("lpm_l3", [ 492 Metric("lpm_l3_accesses", "L3 victim cache accesses", 493 d_ratio(acc, interval_sec), "accesses/sec"), 494 Metric("lpm_l3_hits", "L3 victim cache hit rate", 495 d_ratio(hits, acc), "100%"), 496 Metric("lpm_l3_miss", "L3 victim cache miss rate", d_ratio(miss, acc), 497 "100%"), 498 ], description="L3 cache breakdown per CCX") 499 500 501def main() -> None: 502 global _args 503 global _zen_model 504 505 def dir_path(path: str) -> str: 506 """Validate path is a directory for argparse.""" 507 if os.path.isdir(path): 508 return path 509 raise argparse.ArgumentTypeError( 510 f'\'{path}\' is not a valid directory') 511 512 parser = argparse.ArgumentParser(description="AMD perf json generator") 513 parser.add_argument( 514 "-metricgroups", help="Generate metricgroups data", action='store_true') 515 parser.add_argument("model", help="e.g. amdzen[123]") 516 parser.add_argument( 517 'events_path', 518 type=dir_path, 519 help='Root of tree containing architecture directories containing json files' 520 ) 521 _args = parser.parse_args() 522 523 directory = f"{_args.events_path}/x86/{_args.model}/" 524 LoadEvents(directory) 525 526 _zen_model = int(_args.model[6:]) 527 528 all_metrics = MetricGroup("", [ 529 AmdBr(), 530 AmdCtxSw(), 531 AmdDtlb(), 532 AmdIotlb(), 533 AmdItlb(), 534 AmdLdSt(), 535 AmdUpc(), 536 Cycles(), 537 Idle(), 538 Rapl(), 539 UncoreL3(), 540 ]) 541 542 if _args.metricgroups: 543 print(JsonEncodeMetricGroupDescriptions(all_metrics)) 544 else: 545 print(JsonEncodeMetric(all_metrics)) 546 547 548if __name__ == '__main__': 549 main() 550