class_docstr: Container for cluster load metrics.
  
  Metrics here are updated from raylet heartbeats. The autoscaler
  queries these metrics to determine when to scale up, and which nodes
  can be removed.
code: def summary(self):
      available_resources = (
          reduce(add_resources, self.dynamic_resources_by_ip.values())
          if self.dynamic_resources_by_ip
          else {}
      )
      total_resources = (
          reduce(add_resources, self.static_resources_by_ip.values())
          if self.static_resources_by_ip
          else {}
      )
      usage_dict = {}
      for key in total_resources:
          if key in ["memory", "object_store_memory"]:
              total = total_resources[key]
              available = available_resources[key]
              usage_dict[key] = (total - available, total)
          else:
              total = total_resources[key]
              usage_dict[key] = (
                  total - available_resources[key],
                  total,
              )
  
      summarized_demand_vector = freq_of_dicts(
          self.get_resource_demand_vector(clip=False)
      )
      summarized_resource_requests = freq_of_dicts(
          self.get_resource_requests()
      )
  
      def placement_group_serializer(pg):
          bundles = tuple(
              frozenset(bundle.unit_resources.items())
              for bundle in pg.bundles
          )
          return (bundles, pg.strategy)
  
      def placement_group_deserializer(pg_tuple):
          # We marshal this as a dictionary so that we can easily json.dumps
          # it later.
          # TODO (Alex): Would there be a benefit to properly
          # marshalling this (into a protobuf)?
          bundles = list(map(dict, pg_tuple[0]))
          return {
              "bundles": freq_of_dicts(bundles),
              "strategy": PlacementStrategy.Name(pg_tuple[1]),
          }
  
      summarized_placement_groups = freq_of_dicts(
          self.get_pending_placement_groups(),
          serializer=placement_group_serializer,
          deserializer=placement_group_deserializer,
      )
      nodes_summary = freq_of_dicts(
          self.static_resources_by_ip.values()
      )
  
      usage_by_node = None
      if AUTOSCALER_REPORT_PER_NODE_STATUS:
          usage_by_node = {}
          for ip, totals in self.static_resources_by_ip.items():
              available = self.dynamic_resources_by_ip.get(ip, {})
              usage_by_node[ip] = {}
              for resource, total in totals.items():
                  usage_by_node[ip][resource] = (
                      total - available.get(resource, 0),
                      total,
                  )
  
      return LoadMetricsSummary(
          usage=usage_dict,
          resource_demand=summarized_demand_vector,
          pg_demand=summarized_placement_groups,
          request_demand=summarized_resource_requests,
          node_types=nodes_summary,
          usage_by_node=usage_by_node,
      )
  
imports: import logging
  import time
  from collections import Counter
  from functools import reduce
  from typing import Dict, List
  from ray._private.gcs_utils import PlacementGroupTableData
  from ray.autoscaler._private.constants import AUTOSCALER_MAX_RESOURCE_DEMAND_VECTOR_SIZE, AUTOSCALER_REPORT_PER_NODE_STATUS
  from ray.autoscaler._private.util import DictCount, LoadMetricsSummary, NodeIP, ResourceDict
  from ray.core.generated.common_pb2 import PlacementStrategy
label_desc: Use image node_id 15 for calling the LoadMetrics obj's underlying member method code with example usage: obj.summary() and returns: LoadMetricsSummary, bundles,  pg, dict