123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339 |
- #
- # Copyright 2019 The FATE Authors. All Rights Reserved.
- #
- # Licensed under the Apache License, Version 2.0 (the "License");
- # you may not use this file except in compliance with the License.
- # You may obtain a copy of the License at
- #
- # http://www.apache.org/licenses/LICENSE-2.0
- #
- # Unless required by applicable law or agreed to in writing, software
- # distributed under the License is distributed on an "AS IS" BASIS,
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- # See the License for the specific language governing permissions and
- # limitations under the License.
- #
- import hashlib
- import time
- import typing
- import beautifultable
- from fate_arch.common.log import getLogger
- import inspect
- from functools import wraps
- from fate_arch.abc import CTableABC
- profile_logger = getLogger("PROFILING")
- _PROFILE_LOG_ENABLED = False
- _START_TIME = None
- _END_TIME = None
- class _TimerItem(object):
- def __init__(self):
- self.count = 0
- self.total_time = 0.0
- self.max_time = 0.0
- def union(self, other: '_TimerItem'):
- self.count += other.count
- self.total_time += other.total_time
- if self.max_time < other.max_time:
- self.max_time = other.max_time
- def add(self, elapse_time):
- self.count += 1
- self.total_time += elapse_time
- if elapse_time > self.max_time:
- self.max_time = elapse_time
- @property
- def mean(self):
- if self.count == 0:
- return 0.0
- return self.total_time / self.count
- def as_list(self):
- return [self.count, self.total_time, self.mean, self.max_time]
- def __str__(self):
- return f"n={self.count}, sum={self.total_time:.4f}, mean={self.mean:.4f}, max={self.max_time:.4f}"
- def __repr__(self):
- return self.__str__()
- class _ComputingTimerItem(object):
- def __init__(self, function_name: str, function_stack):
- self.function_name = function_name
- self.function_stack = function_stack
- self.item = _TimerItem()
- class _ComputingTimer(object):
- _STATS: typing.MutableMapping[str, _ComputingTimerItem] = {}
- def __init__(self, function_name: str, function_stack_list):
- self._start = time.time()
- function_stack = "\n".join(function_stack_list)
- self._hash = hashlib.blake2b(function_stack.encode('utf-8'), digest_size=5).hexdigest()
- if self._hash not in self._STATS:
- self._STATS[self._hash] = _ComputingTimerItem(function_name, function_stack)
- if _PROFILE_LOG_ENABLED:
- profile_logger.debug(f"[computing#{self._hash}]function_stack: {' <-'.join(function_stack_list)}")
- if _PROFILE_LOG_ENABLED:
- profile_logger.debug(f"[computing#{self._hash}]start")
- def done(self, function_string):
- elapse = time.time() - self._start
- self._STATS[self._hash].item.add(elapse)
- if _PROFILE_LOG_ENABLED:
- profile_logger.debug(f"[computing#{self._hash}]done, elapse: {elapse}, function: {function_string}")
- @classmethod
- def computing_statistics_table(cls, timer_aggregator: _TimerItem = None):
- stack_table = beautifultable.BeautifulTable(110, precision=4, detect_numerics=False)
- stack_table.columns.header = ["function", "n", "sum(s)", "mean(s)", "max(s)", "stack_hash", "stack"]
- stack_table.columns.alignment["stack"] = beautifultable.ALIGN_LEFT
- stack_table.columns.header.alignment = beautifultable.ALIGN_CENTER
- stack_table.border.left = ''
- stack_table.border.right = ''
- stack_table.border.bottom = ''
- stack_table.border.top = ''
- function_table = beautifultable.BeautifulTable(110)
- function_table.set_style(beautifultable.STYLE_COMPACT)
- function_table.columns.header = ["function", "n", "sum(s)", "mean(s)", "max(s)"]
- aggregate = {}
- total = _TimerItem()
- for hash_id, timer in cls._STATS.items():
- stack_table.rows.append([timer.function_name, *timer.item.as_list(), hash_id, timer.function_stack])
- aggregate.setdefault(timer.function_name, _TimerItem()).union(timer.item)
- total.union(timer.item)
- for function_name, item in aggregate.items():
- function_table.rows.append([function_name, *item.as_list()])
- detailed_base_table = beautifultable.BeautifulTable(120)
- stack_table.rows.sort("sum(s)", reverse=True)
- detailed_base_table.rows.append(["stack", stack_table])
- detailed_base_table.rows.append(["total", total])
- base_table = beautifultable.BeautifulTable(120)
- function_table.rows.sort("sum(s)", reverse=True)
- base_table.rows.append(["function", function_table])
- base_table.rows.append(["total", total])
- if timer_aggregator:
- timer_aggregator.union(total)
- return base_table.get_string(), detailed_base_table.get_string()
- class _FederationTimer(object):
- _GET_STATS: typing.MutableMapping[str, _TimerItem] = {}
- _REMOTE_STATS: typing.MutableMapping[str, _TimerItem] = {}
- @classmethod
- def federation_statistics_table(cls, timer_aggregator: _TimerItem = None):
- total = _TimerItem()
- get_table = beautifultable.BeautifulTable(110)
- get_table.columns.header = ["name", "n", "sum(s)", "mean(s)", "max(s)"]
- for name, item in cls._GET_STATS.items():
- get_table.rows.append([name, *item.as_list()])
- total.union(item)
- get_table.rows.sort("sum(s)", reverse=True)
- get_table.border.left = ''
- get_table.border.right = ''
- get_table.border.bottom = ''
- get_table.border.top = ''
- remote_table = beautifultable.BeautifulTable(110)
- remote_table.columns.header = ["name", "n", "sum(s)", "mean(s)", "max(s)"]
- for name, item in cls._REMOTE_STATS.items():
- remote_table.rows.append([name, *item.as_list()])
- total.union(item)
- remote_table.rows.sort("sum(s)", reverse=True)
- remote_table.border.left = ''
- remote_table.border.right = ''
- remote_table.border.bottom = ''
- remote_table.border.top = ''
- base_table = beautifultable.BeautifulTable(120)
- base_table.rows.append(["get", get_table])
- base_table.rows.append(["remote", remote_table])
- base_table.rows.append(["total", total])
- if timer_aggregator:
- timer_aggregator.union(total)
- return base_table.get_string()
- class _FederationRemoteTimer(_FederationTimer):
- def __init__(self, name, full_name, tag, local, parties):
- self._name = name
- self._full_name = full_name
- self._tag = tag
- self._local_party = local
- self._parties = parties
- self._start_time = time.time()
- self._end_time = None
- if self._full_name not in self._REMOTE_STATS:
- self._REMOTE_STATS[self._full_name] = _TimerItem()
- def done(self, federation):
- self._end_time = time.time()
- self._REMOTE_STATS[self._full_name].add(self.elapse)
- profile_logger.debug(f"[federation.remote.{self._full_name}.{self._tag}]"
- f"{self._local_party}->{self._parties} done")
- if is_profile_remote_enable():
- federation.remote(v={"start_time": self._start_time, "end_time": self._end_time},
- name=self._name,
- tag=profile_remote_tag(self._tag),
- parties=self._parties,
- gc=None)
- @property
- def elapse(self):
- return self._end_time - self._start_time
- class _FederationGetTimer(_FederationTimer):
- def __init__(self, name, full_name, tag, local, parties):
- self._name = name
- self._full_name = full_name
- self._tag = tag
- self._local_party = local
- self._parties = parties
- self._start_time = time.time()
- self._end_time = None
- if self._full_name not in self._GET_STATS:
- self._GET_STATS[self._full_name] = _TimerItem()
- def done(self, federation):
- self._end_time = time.time()
- self._GET_STATS[self._full_name].add(self.elapse)
- profile_logger.debug(f"[federation.get.{self._full_name}.{self._tag}]"
- f"{self._local_party}<-{self._parties} done")
- if is_profile_remote_enable():
- remote_meta = federation.get(name=self._name, tag=profile_remote_tag(self._tag), parties=self._parties,
- gc=None)
- for party, meta in zip(self._parties, remote_meta):
- profile_logger.debug(f"[federation.meta.{self._full_name}.{self._tag}]{self._local_party}<-{party}]"
- f"meta={meta}")
- @property
- def elapse(self):
- return self._end_time - self._start_time
- def federation_remote_timer(name, full_name, tag, local, parties):
- profile_logger.debug(f"[federation.remote.{full_name}.{tag}]{local}->{parties} start")
- return _FederationRemoteTimer(name, full_name, tag, local, parties)
- def federation_get_timer(name, full_name, tag, local, parties):
- profile_logger.debug(f"[federation.get.{full_name}.{tag}]{local}<-{parties} start")
- return _FederationGetTimer(name, full_name, tag, local, parties)
- def profile_start():
- global _PROFILE_LOG_ENABLED
- _PROFILE_LOG_ENABLED = True
- global _START_TIME
- _START_TIME = time.time()
- def profile_ends():
- global _END_TIME
- _END_TIME = time.time()
- profile_total_time = _END_TIME - _START_TIME
- # gather computing and federation profile statistics
- timer_aggregator = _TimerItem()
- computing_timer_aggregator = _TimerItem()
- federation_timer_aggregator = _TimerItem()
- computing_base_table, computing_detailed_table = _ComputingTimer.computing_statistics_table(
- timer_aggregator=computing_timer_aggregator)
- federation_base_table = _FederationTimer.federation_statistics_table(timer_aggregator=federation_timer_aggregator)
- timer_aggregator.union(computing_timer_aggregator)
- timer_aggregator.union(federation_timer_aggregator)
- # logging
- profile_driver_time = profile_total_time - timer_aggregator.total_time
- profile_logger.info(
- "Total: {:.4f}s, Driver: {:.4f}s({:.2%}), Federation: {:.4f}s({:.2%}), Computing: {:.4f}s({:.2%})".format(
- profile_total_time,
- profile_driver_time,
- profile_driver_time / profile_total_time,
- federation_timer_aggregator.total_time,
- federation_timer_aggregator.total_time / profile_total_time,
- computing_timer_aggregator.total_time,
- computing_timer_aggregator.total_time / profile_total_time
- )
- )
- profile_logger.info(f"\nComputing:\n{computing_base_table}\n\nFederation:\n{federation_base_table}\n")
- profile_logger.debug(f"\nDetailed Computing:\n{computing_detailed_table}\n")
- global _PROFILE_LOG_ENABLED
- _PROFILE_LOG_ENABLED = False
- def _pretty_table_str(v):
- if isinstance(v, CTableABC):
- return f"Table(partition={v.partitions})"
- else:
- return f"{type(v).__name__}"
- def _func_annotated_string(func, *args, **kwargs):
- pretty_args = []
- for k, v in inspect.signature(func).bind(*args, **kwargs).arguments.items():
- pretty_args.append(f"{k}: {_pretty_table_str(v)}")
- return f"{func.__name__}({', '.join(pretty_args)})"
- def _call_stack_strings():
- call_stack_strings = []
- frames = inspect.getouterframes(inspect.currentframe(), 10)[2:-2]
- for frame in frames:
- call_stack_strings.append(f"[{frame.filename.split('/')[-1]}:{frame.lineno}]{frame.function}")
- return call_stack_strings
- def computing_profile(func):
- @wraps(func)
- def _fn(*args, **kwargs):
- function_call_stack = _call_stack_strings()
- timer = _ComputingTimer(func.__name__, function_call_stack)
- rtn = func(*args, **kwargs)
- function_string = f"{_func_annotated_string(func, *args, **kwargs)} -> {_pretty_table_str(rtn)}"
- timer.done(function_string)
- return rtn
- return _fn
- __META_REMOTE_ENABLE = False
- def enable_profile_remote():
- global __META_REMOTE_ENABLE
- __META_REMOTE_ENABLE = True
- def is_profile_remote_enable():
- return __META_REMOTE_ENABLE
- def profile_remote_tag(tag):
- return f"<remote_profile>_{tag}"
|