Skip to content

inspector

sc_crawler.inspector #

Functions:

Name Description
inspector_data_path

Download current inspector data into a temp folder.

inspect_server_benchmarks

Generate a list of BenchmarkScore-like dicts for the Server.

inspect_update_server_dict

Update a Server-like dict based on inspector data.

inspector_data_path cached #

inspector_data_path()

Download current inspector data into a temp folder.

Setting the SC_CRAWLER_INSPECTOR_DATA_PATH environment variable will override the default path for persistent/cached inspector data access.

Source code in sc_crawler/inspector.py
@cache
def inspector_data_path() -> str | PathLike:
    """Download current inspector data into a temp folder.

    Setting the `SC_CRAWLER_INSPECTOR_DATA_PATH` environment variable will
    override the default path for persistent/cached inspector data access.
    """
    if getenv("SC_CRAWLER_INSPECTOR_DATA_PATH"):
        temp_dir = getenv("SC_CRAWLER_INSPECTOR_DATA_PATH")
        makedirs(temp_dir, exist_ok=True)
    else:
        temp_dir = mkdtemp()
        register(rmtree, temp_dir)
    zip_path = path.join(temp_dir, "downloaded.zip")
    if not path.exists(zip_path):
        response = get(
            "https://github.com/SpareCores/sc-inspector-data/archive/refs/heads/main.zip"
        )
        with open(zip_path, "wb") as f:
            f.write(response.content)
        with ZipFile(zip_path, "r") as zip_ref:
            zip_ref.extractall(temp_dir)
    return path.join(temp_dir, "sc-inspector-data-main", "data")

inspect_server_benchmarks #

inspect_server_benchmarks(server)

Generate a list of BenchmarkScore-like dicts for the Server.

Source code in sc_crawler/inspector.py
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
def inspect_server_benchmarks(server: "Server") -> List[dict]:
    """Generate a list of BenchmarkScore-like dicts for the Server."""
    benchmarks = []

    framework = "bogomips"
    try:
        benchmarks.append(
            {
                **_benchmark_metafields(
                    server, framework="lscpu", benchmark_id=framework
                ),
                "score": round(float(_server_lscpu_field(server, "BogoMIPS:"))),
            }
        )
    except Exception as e:
        _log_cannot_load_benchmarks(server, framework, e)

    framework = "bw_mem"
    try:
        with open(_server_framework_stdout_path(server, framework), "r") as lines:
            for line in lines:
                # filter out error messages
                if match(r"^(rd|wr|rdwr) \d+(\.\d+) \d+(\.\d+)$", line):
                    row = line.strip().split()
                    benchmarks.append(
                        {
                            **_benchmark_metafields(server, framework=framework),
                            "config": {"operation": row[0], "size": float(row[1])},
                            "score": float(row[2]),
                        }
                    )
    except Exception as e:
        _log_cannot_load_benchmarks(server, framework, e)

    framework = "compression_text"
    try:
        algos = _server_framework_stdout_from_json(server, framework)
        for algo, levels in algos.items():
            for level, datas in levels.items():
                for data in datas:
                    threads = data["threads"]
                    config = {
                        "algo": algo,
                        "compression_level": None if level == "null" else int(level),
                        "cores": (
                            Parallelism.MULTI if threads > 1 else Parallelism.SINGLE
                        ),
                    }
                    if data.get("extra_args", {}).get("block_size") is not None:
                        config["block_size"] = data["extra_args"]["block_size"]
                    for measurement in ["ratio", "compress", "decompress"]:
                        if data[measurement]:
                            benchmarks.append(
                                {
                                    **_benchmark_metafields(
                                        server,
                                        benchmark_id=":".join([framework, measurement]),
                                    ),
                                    "config": config,
                                    "score": float(data[measurement]),
                                }
                            )
    except Exception as e:
        _log_cannot_load_benchmarks(server, framework, e, True)

    framework = "geekbench"
    try:
        with open(_server_framework_path(server, framework, "results.json"), "r") as fp:
            scores = json.load(fp)
        kernel_version = next(
            (
                m.group(1)
                for line in _server_geekbench(server)
                if line.startswith("Kernel")
                for m in [search(r"(\d+\.\d+\.\d+\S*)", line)]
                if m
            ),
            None,
        )
        for cores, workloads in scores.items():
            parallelism = (
                Parallelism.SINGLE
                if cores == "Single-Core Performance"
                else Parallelism.MULTI
            )
            for workload, values in workloads.items():
                workload_fields = {
                    "config": {"cores": parallelism},
                    "score": float(values["score"]),
                }
                if values.get("description"):
                    workload_fields["note"] = values["description"]
                benchmarks.append(
                    {
                        **_benchmark_metafields(
                            server,
                            benchmark_id=":".join(
                                [framework, sub(r"\W+", "_", workload.lower())]
                            ),
                            kernel_version_fallback=kernel_version,
                        ),
                        **workload_fields,
                    }
                )
    except Exception as e:
        _log_cannot_load_benchmarks(server, framework, e, True)

    framework = "passmark"
    try:
        with open(_server_framework_stdout_path(server, framework), "r") as fp:
            scores = yaml_safe_load(fp)
        passmark_version = ".".join(
            [str(scores["Version"][i]) for i in ["Major", "Minor", "Build"]]
        )
        kernel_version = next(
            (
                line.split("Kernel:")[1].strip()
                for line in _server_passmark(server)
                if line.startswith("Kernel:")
            ),
            None,
        )
        for key, name in PASSMARK_MAPS.items():
            benchmarks.append(
                {
                    **_benchmark_metafields(
                        server,
                        benchmark_id=":".join(
                            [framework, sub(r"\W+", "_", name.lower())]
                        ),
                        framework_version_fallback=passmark_version,
                        kernel_version_fallback=kernel_version,
                    ),
                    "score": float(scores["Results"][key]),
                }
            )
    except Exception as e:
        _log_cannot_load_benchmarks(server, framework, e, True)

    framework = "openssl"
    try:
        with open(_server_framework_path(server, framework, "parsed.json"), "r") as fp:
            workloads = json.load(fp)
        for workload in workloads:
            benchmarks.append(
                {
                    **_benchmark_metafields(server, framework=framework),
                    "config": {
                        "algo": workload["algo"],
                        "block_size": workload["block_size"],
                    },
                    "score": float(workload["speed"]),
                }
            )
    except Exception as e:
        _log_cannot_load_benchmarks(server, framework, e, True)

    framework = "stress_ng"
    # TODO deprecate
    try:
        cores_per_path = {"stressng": server.vcpus, "stressngsinglecore": 1}
        for cores_path in cores_per_path.keys():
            line = _extract_line_from_file(
                _server_framework_stderr_path(server, cores_path),
                "bogo-ops-per-second-real-time",
            )
            benchmarks.append(
                {
                    **_benchmark_metafields(
                        server,
                        framework=cores_path,
                        benchmark_id=":".join([framework, "cpu_all"]),
                    ),
                    "config": {"cores": cores_per_path[cores_path]},
                    "score": float(line.split(": ")[1]),
                }
            )
    except Exception:
        # backfill with newer method - can be dropped once we deprecate stress_ng:cpu_all
        try:
            records = []
            with open(
                _server_framework_stdout_path(server, "stressngfull"), newline=""
            ) as f:
                rows = csv.reader(f, quoting=csv.QUOTE_NONNUMERIC)
                for row in rows:
                    records.append(row)
            for i in [0, len(records) - 1]:
                benchmarks.append(
                    {
                        **_benchmark_metafields(
                            server,
                            framework="stressngfull",
                            benchmark_id=":".join([framework, "cpu_all"]),
                        ),
                        "config": {"cores": records[i][0]},
                        "score": records[i][1],
                    }
                )
        except Exception as e:
            _log_cannot_load_benchmarks(server, framework, e, True)

    workload = "div16"
    try:
        records = []
        with open(
            _server_framework_stdout_path(server, "stressngfull"), newline=""
        ) as f:
            rows = csv.reader(f, quoting=csv.QUOTE_NONNUMERIC)
            for row in rows:
                records.append(row)
        for record in records:
            benchmarks.append(
                {
                    **_benchmark_metafields(
                        server,
                        framework="stressngfull",
                        benchmark_id=":".join([framework, workload]),
                    ),
                    "config": {"cores": record[0]},
                    "score": record[1],
                }
            )
        # best single and multi core performance
        bests = {"best1": records[0][1], "bestn": max([r[1] for r in records])}
        for k, v in bests.items():
            benchmarks.append(
                {
                    **_benchmark_metafields(
                        server,
                        framework="stressngfull",
                        benchmark_id=":".join([framework, k]),
                    ),
                    "score": v,
                }
            )
    except Exception as e:
        _log_cannot_load_benchmarks(server, framework, e, True)

    for framework in SERVER_CLIENT_FRAMEWORK_MAPS.keys():
        try:
            versions = _server_framework_meta(server, framework)["version"]
            # drop the build number at the end of the redis server version
            if framework == "redis":
                versions = sub(r" build=[a-zA-Z0-9]+", "", versions)

            records = []
            with open(
                _server_framework_stdout_path(server, framework), newline=""
            ) as f:
                rows = csv.DictReader(f, quoting=csv.QUOTE_NONNUMERIC)
                for row in rows:
                    if "connections" in row.keys():
                        row["connections_per_vcpus"] = row["connections"] / server.vcpus
                    records.append(row)

            framework_config = SERVER_CLIENT_FRAMEWORK_MAPS[framework]
            keys = framework_config["keys"]
            measurements = framework_config["measurements"]

            # don't care about threads, keep the records with the highest rps
            records = sorted(records, key=lambda x: (*[x[k] for k in keys], -x["rps"]))
            records = groupby(records, key=itemgetter(*keys))
            records = [next(group) for _, group in records]

            for record in records:
                for measurement in measurements:
                    score_field = measurement.split("-")[0]
                    if score_field == "throughput":
                        score_field = "rps"
                    score = record[score_field]
                    server_usrsys = record["server_usr"] + record["server_sys"]
                    client_usrsys = record["client_usr"] + record["client_sys"]
                    note = (
                        "CPU usage (server/client usr+sys): "
                        f"{round(server_usrsys, 4)}/{round(client_usrsys, 4)}."
                    )
                    if measurement.endswith("-extrapolated"):
                        note += f" Original RPS: {score}."
                        score = round(
                            score / server_usrsys * (server_usrsys + client_usrsys), 2
                        )
                    if measurement.startswith("throughput"):
                        # drop the "k" suffix and multiply by 1024
                        size = int(record["size"][:-1]) * 1024
                        score = score * size
                    benchmarks.append(
                        {
                            **_benchmark_metafields(
                                server,
                                framework=framework,
                                benchmark_id=":".join([framework, measurement]),
                                framework_version_fallback=versions,
                                override_framework_version=True,
                            ),
                            "config": {k: record[k] for k in keys},
                            "score": score,
                            "note": note,
                        }
                    )
        except Exception as e:
            _log_cannot_load_benchmarks(server, framework, e, True)

    framework = "membench"
    try:
        with open(_server_framework_stdout_path(server, framework), newline="") as fp:
            reader = csv.DictReader(fp)
            ram_scope_dict = {
                "read": False,
                "write": False,
                "copy": False,
                "latency": False,
            }
            cpu_cache_total_kib = (
                server.cpu_l3_cache_total
                or server.cpu_l2_cache_total
                or server.cpu_l1d_cache_total
            )
            for row in reader:
                operation = row["operation"]
                size_kb = int(float(row["size_kb"]))
                config = {"size_kb": size_kb}
                if operation == "latency":
                    score = float(row["latency_ns"])
                    if score == 0:
                        continue
                    benchmarks.append(
                        {
                            **_benchmark_metafields(
                                server,
                                framework=framework,
                                benchmark_id="membench:latency",
                            ),
                            "config": config,
                            "score": score,
                        }
                    )
                    if (
                        cpu_cache_total_kib is not None
                        and size_kb > cpu_cache_total_kib
                        and not ram_scope_dict[operation]
                    ):
                        ram_scope_dict[operation] = True
                        benchmarks.append(
                            {
                                **_benchmark_metafields(
                                    server,
                                    framework=framework,
                                    benchmark_id="membench:latency",
                                ),
                                "config": {"scope": "RAM"},
                                "score": score,
                            }
                        )
                elif operation in ("read", "write", "copy"):
                    score = float(row["bandwidth_mb_s"])
                    if score == 0:
                        continue
                    benchmarks.append(
                        {
                            **_benchmark_metafields(
                                server,
                                framework=framework,
                                benchmark_id=f"membench:bandwidth_{operation}",
                            ),
                            "config": config,
                            "score": score,
                        }
                    )
                    if (
                        cpu_cache_total_kib is not None
                        and size_kb > cpu_cache_total_kib
                        and not ram_scope_dict[operation]
                    ):
                        ram_scope_dict[operation] = True
                        benchmarks.append(
                            {
                                **_benchmark_metafields(
                                    server,
                                    framework=framework,
                                    benchmark_id=f"membench:bandwidth_{operation}",
                                ),
                                "config": {"scope": "RAM"},
                                "score": score,
                            }
                        )
    except Exception as e:
        _log_cannot_load_benchmarks(server, framework, e, True)

    framework = "llm_speed"
    try:
        assert _server_framework_meta(server, "llm")["exit_code"] == 0
        with open(_server_framework_stdout_path(server, "llm"), "r") as fp:
            for line in fp:
                record = json.loads(line)
                model_name = path.basename(record.get("model_filename", "unknown"))
                measurement = "text_generation"
                if record.get("n_prompt") != 0:
                    measurement = "prompt_processing"
                tokens = record.get("n_prompt") + record.get("n_gen")
                config = {
                    "model": model_name,
                    "tokens": tokens,
                }
                benchmarks.append(
                    {
                        **_benchmark_metafields(
                            server,
                            framework="llm",
                            benchmark_id=":".join([framework, measurement]),
                        ),
                        "config": config,
                        "score": float(record["avg_ts"]),
                    }
                )
    except Exception as e:
        _log_cannot_load_benchmarks(server, framework, e, True)

    return benchmarks

inspect_update_server_dict #

inspect_update_server_dict(server)

Update a Server-like dict based on inspector data.

Source code in sc_crawler/inspector.py
def inspect_update_server_dict(server: dict) -> dict:
    """Update a Server-like dict based on inspector data."""
    server_obj = ServerBase.model_validate(server)

    lookups = {
        "dmidecode_cpu": lambda: _server_dmidecode_section(
            server_obj, "Processor Information"
        ),
        "dmidecode_cpus": lambda: _server_dmidecode_sections(
            server_obj, "Processor Information"
        ),
        "dmidecode_memory": lambda: _server_dmidecode_section(
            server_obj, "Memory Device"
        ),
        "dmidecode_memory_devices": lambda: _server_dmidecode_sections(
            server_obj, "Memory Device"
        ),
        "lscpu": lambda: _server_lscpu(server_obj),
        "lshw": lambda: _server_lshw(server_obj),
        "lstopo": lambda: _server_lstopo(server_obj),
        "lsblk": lambda: _server_lsblk(server_obj),
        "lsblk_discard": lambda: _server_lsblk_discard(server_obj),
        "lsblk_topo": lambda: _server_lsblk_topo(server_obj),
        "nvidiasmi": lambda: _server_nvidiasmi(server_obj),
        "virtualization": lambda: _server_virtualization(server_obj),
        "stressngfull": lambda: _server_stressngfull(server_obj),
        "gpu": lambda: lookups["nvidiasmi"].find("gpu"),
        "gpus": lambda: lookups["nvidiasmi"].findall("gpu"),
    }
    for k, f in lookups.items():
        try:
            lookups[k] = f()
        except Exception as e:
            lookups[k] = Exception(str(e))

    def lscpu_lookup(field: str):
        return _listsearch(lookups["lscpu"], "field", field)["data"]

    # Parse lsblk/lshw storage info once (default StorageInfo instance if lsblk/lshw lookup failed)
    inspector_storage_info = _parse_storage_info(
        lookups["lshw"],
        lookups["lsblk"],
        lookups["lsblk_discard"],
        lookups["lsblk_topo"],
        server_obj,
    )

    # Parse CPU cache info once (empty dict if lscpu lookup failed)
    cpu_cache_info = (
        {}
        if isinstance(lookups["lscpu"], Exception)
        else _get_cpu_cache_info(lookups["lscpu"], lookups["lstopo"])
    )

    def get_cpu_speed():
        """Extract CPU speed from lscpu or dmidecode."""
        # lscpu is more reliable, extracting from "Model name: ... @ X.XGHz"
        speed = None
        with suppress(Exception):
            cpu_model = lscpu_lookup("Model name:")
            match = search(r" @ ([0-9\.]*)GHz$", cpu_model)
            if match:
                speed = float(match.group(1))
        # fall back to dmidecode
        if not speed:
            with suppress(Exception):
                # use 1st CPU's speed, convert to Ghz
                speed = lookups["dmidecode_cpu"]["Max Speed"] / 1e9
        # 2 GHz CPU speed is a lie at GCP
        if server_obj.vendor_id == "gcp" and speed == 2:
            speed = None
        return speed

    def get_cpu_manufacturer():
        """Extract CPU manufacturer from lscpu or dmidecode."""
        with suppress(Exception):
            cpu_model = lscpu_lookup("Model name:")
            for manufacturer in ["Intel", "AMD", "Ampere"]:
                if manufacturer.lower() in cpu_model.lower():
                    return manufacturer
        # fall back to dmidecode
        with suppress(Exception):
            return _standardize_manufacturer(lookups["dmidecode_cpu"]["Manufacturer"])
        # no CPU manufacturer data available
        return None

    def get_cpu_family():
        """Extract CPU family from lscpu or dmidecode."""
        with suppress(Exception):
            cpu_model = lscpu_lookup("Model name:")
            for family in ["Xeon", "EPYC", "Altra"]:
                if family.lower() in cpu_model.lower():
                    return family
        # fall back to dmidecode
        with suppress(Exception):
            return _standardize_cpu_family(lookups["dmidecode_cpu"]["Family"])
        # no CPU family data available
        return None

    def get_cpu_model():
        """Extract CPU model from lscpu or dmidecode."""
        with suppress(Exception):
            return _standardize_cpu_model(lscpu_lookup("Model name:"))
        with suppress(Exception):
            return _standardize_cpu_model(lookups["dmidecode_cpu"]["Version"])
        return None

    def calculate_ecpus():
        """Calculate ecpus from stressngfull."""
        stressngfull = lookups.get("stressngfull")
        if (
            isinstance(stressngfull, Exception)
            or not isinstance(stressngfull, list)
            or not stressngfull
        ):
            return None
        with suppress(Exception):
            best1_score = stressngfull[0][1]
            bestn_score = max(score for _, score in stressngfull)
            return round(bestn_score / best1_score, 1)
        return None

    def get_memory_amount_actual():
        """Extract memory amount from lstopo, lshw or dmidecode."""
        return (
            _parse_lstopo_memory_amount_mib(lookups["lstopo"])
            or _parse_lshw_memory_amount_mib(lookups["lshw"])
            or _parse_dmidecode_memory_amount_mib(lookups["dmidecode_memory_devices"])
            or None
        )

    mappings = {
        "vcpus": lambda: lscpu_lookup("CPU(s):"),
        "cpu_cores": lambda: (
            int(lscpu_lookup("Core(s) per socket:")) * int(lscpu_lookup("Socket(s):"))
        ),
        "cpu_speed": get_cpu_speed,
        "cpu_manufacturer": get_cpu_manufacturer,
        "cpu_family": get_cpu_family,
        "cpu_model": get_cpu_model,
        "cpu_l1d_cache": lambda: cpu_cache_info.get("L1d", {}).get("per_instance_KiB"),
        "cpu_l1d_cache_total": lambda: cpu_cache_info.get("L1d", {}).get("total_KiB"),
        "cpu_l1i_cache": lambda: cpu_cache_info.get("L1i", {}).get("per_instance_KiB"),
        "cpu_l1i_cache_total": lambda: cpu_cache_info.get("L1i", {}).get("total_KiB"),
        "cpu_l2_cache": lambda: cpu_cache_info.get("L2", {}).get("per_instance_KiB"),
        "cpu_l2_cache_total": lambda: cpu_cache_info.get("L2", {}).get("total_KiB"),
        "cpu_l3_cache": lambda: cpu_cache_info.get("L3", {}).get("per_instance_KiB"),
        "cpu_l3_cache_total": lambda: cpu_cache_info.get("L3", {}).get("total_KiB"),
        "cpu_flags": lambda: lscpu_lookup("Flags:").split(" "),
        "ecpus": calculate_ecpus,
        "scalability": lambda: (
            round(server["ecpus"] / server["cpu_cores"] * 100, 2)
            if server.get("ecpus") is not None and server.get("cpu_cores") is not None
            else None
        ),
        "hw_virt": lambda: lookups["virtualization"].get("kvm", None),
        "memory_amount_actual": get_memory_amount_actual,
        "memory_generation": lambda: DdrGeneration[lookups["dmidecode_memory"]["Type"]],
        # convert to Mhz
        "memory_speed": lambda: int(lookups["dmidecode_memory"]["Speed"]) / 1e6,
        "gpus": lambda: _gpus_details(lookups["gpus"]),
        "gpu_manufacturer": lambda: _gpu_most_common(server["gpus"], "manufacturer"),
        "gpu_family": lambda: _gpu_most_common(server["gpus"], "family"),
        "gpu_model": lambda: _gpu_most_common(server["gpus"], "model"),
        # skip update if there is no HW-inspected GPU info
        "gpu_count": lambda: len(server["gpus"]) if len(server["gpus"]) else None,
        "gpu_memory_min": lambda: min([gpu["memory"] for gpu in server["gpus"]]),
        "gpu_memory_total": lambda: sum([gpu["memory"] for gpu in server["gpus"]]),
        # skip storage update if lshw parsing failed or API data is present
        "storage_type": lambda: getattr(inspector_storage_info, "storage_type"),
        "storage_size": lambda: getattr(inspector_storage_info, "storage_size"),
        "storages": lambda: getattr(inspector_storage_info, "storages"),
    }

    def override_mapping(server, field, inspector_data):
        """Decide whether to override vendor-provided server data with inspector data.

        Args:
            server: Server-like dict with vendor-provided data.
            field: Server table column name.
            inspector_data: New data provided by inspector.

        Returns:
            The data provided by inspector, or the vendor.
        """
        vendor_id = server.get("vendor_id")
        vendor_data = server.get(field)

        # TODO drop once we have full lsblk coverage
        # always override GCP fields where vendor data is known to be missing
        storage_fields = ["storage_type", "storage_size", "storages"]
        if vendor_id == "gcp" and field in ["gpu_model", *storage_fields]:
            return inspector_data
        # don't trust HDD/SSD inspection data at other vendors yet
        if vendor_id != "gcp" and field in storage_fields:
            return vendor_data

        # keep inspector data for detailed fields that's not available from vendor API
        if (
            field == "gpus"
            and inspector_data
            and isinstance(inspector_data, list)
            and len(inspector_data) > 0
            and inspector_data[0].get("bios_version")
        ):
            return inspector_data
        # never override vendor data with None
        if inspector_data is None:
            return vendor_data
        # return inspector data if vendor data is missing
        if not vendor_data:
            return inspector_data
        # last resort: keep vendor data
        return vendor_data

    for k, f in mappings.items():
        try:
            newval = f()
            if newval is not None:
                server[k] = override_mapping(server, k, newval)
        except Exception as e:
            _log_cannot_update_server(server_obj, k, e)

    # standardize GPU model
    if server.get("gpu_model"):
        server["gpu_model"] = _standardize_gpu_model(server["gpu_model"], server)
        server["gpu_family"] = _standardize_gpu_family(server)
        if not server.get("gpu_manufacturer") and server["gpu_model"] == "A100":
            server["gpu_manufacturer"] = "NVIDIA"

    return server