diff options
-rw-r--r-- | command.py | 2 | ||||
-rw-r--r-- | event_log.py | 177 | ||||
-rwxr-xr-x | hooks/pre-auto-gc | 16 | ||||
-rwxr-xr-x | main.py | 15 | ||||
-rw-r--r-- | manifest_xml.py | 8 | ||||
-rw-r--r-- | project.py | 20 | ||||
-rw-r--r-- | subcmds/sync.py | 31 |
7 files changed, 251 insertions, 18 deletions
@@ -19,6 +19,7 @@ import platform | |||
19 | import re | 19 | import re |
20 | import sys | 20 | import sys |
21 | 21 | ||
22 | from event_log import EventLog | ||
22 | from error import NoSuchProjectError | 23 | from error import NoSuchProjectError |
23 | from error import InvalidProjectGroupsError | 24 | from error import InvalidProjectGroupsError |
24 | 25 | ||
@@ -28,6 +29,7 @@ class Command(object): | |||
28 | """ | 29 | """ |
29 | 30 | ||
30 | common = False | 31 | common = False |
32 | event_log = EventLog() | ||
31 | manifest = None | 33 | manifest = None |
32 | _optparse = None | 34 | _optparse = None |
33 | 35 | ||
diff --git a/event_log.py b/event_log.py new file mode 100644 index 00000000..d73511da --- /dev/null +++ b/event_log.py | |||
@@ -0,0 +1,177 @@ | |||
1 | # | ||
2 | # Copyright (C) 2017 The Android Open Source Project | ||
3 | # | ||
4 | # Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | # you may not use this file except in compliance with the License. | ||
6 | # You may obtain a copy of the License at | ||
7 | # | ||
8 | # http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | # | ||
10 | # Unless required by applicable law or agreed to in writing, software | ||
11 | # distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | # See the License for the specific language governing permissions and | ||
14 | # limitations under the License. | ||
15 | |||
16 | from __future__ import print_function | ||
17 | |||
18 | import json | ||
19 | import multiprocessing | ||
20 | |||
21 | TASK_COMMAND = 'command' | ||
22 | TASK_SYNC_NETWORK = 'sync-network' | ||
23 | TASK_SYNC_LOCAL = 'sync-local' | ||
24 | |||
25 | class EventLog(object): | ||
26 | """Event log that records events that occurred during a repo invocation. | ||
27 | |||
28 | Events are written to the log as a consecutive JSON entries, one per line. | ||
29 | Each entry contains the following keys: | ||
30 | - id: A ('RepoOp', ID) tuple, suitable for storing in a datastore. | ||
31 | The ID is only unique for the invocation of the repo command. | ||
32 | - name: Name of the object being operated upon. | ||
33 | - task_name: The task that was performed. | ||
34 | - start: Timestamp of when the operation started. | ||
35 | - finish: Timestamp of when the operation finished. | ||
36 | - success: Boolean indicating if the operation was successful. | ||
37 | - try_count: A counter indicating the try count of this task. | ||
38 | |||
39 | Optionally: | ||
40 | - parent: A ('RepoOp', ID) tuple indicating the parent event for nested | ||
41 | events. | ||
42 | |||
43 | Valid task_names include: | ||
44 | - command: The invocation of a subcommand. | ||
45 | - sync-network: The network component of a sync command. | ||
46 | - sync-local: The local component of a sync command. | ||
47 | |||
48 | Specific tasks may include additional informational properties. | ||
49 | """ | ||
50 | |||
51 | def __init__(self): | ||
52 | """Initializes the event log.""" | ||
53 | self._log = [] | ||
54 | self._next_id = _EventIdGenerator() | ||
55 | self._parent = None | ||
56 | |||
57 | def Add(self, name, task_name, start, finish=None, success=None, | ||
58 | try_count=1, kind='RepoOp'): | ||
59 | """Add an event to the log. | ||
60 | |||
61 | Args: | ||
62 | name: Name of the object being operated upon. | ||
63 | task_name: A sub-task that was performed for name. | ||
64 | start: Timestamp of when the operation started. | ||
65 | finish: Timestamp of when the operation finished. | ||
66 | success: Boolean indicating if the operation was successful. | ||
67 | try_count: A counter indicating the try count of this task. | ||
68 | kind: The kind of the object for the unique identifier. | ||
69 | |||
70 | Returns: | ||
71 | A dictionary of the event added to the log. | ||
72 | """ | ||
73 | event = { | ||
74 | 'id': (kind, self._next_id.next()), | ||
75 | 'name': name, | ||
76 | 'task_name': task_name, | ||
77 | 'start_time': start, | ||
78 | 'try': try_count, | ||
79 | } | ||
80 | |||
81 | if self._parent: | ||
82 | event['parent'] = self._parent['id'] | ||
83 | |||
84 | if success is not None or finish is not None: | ||
85 | self.FinishEvent(event, finish, success) | ||
86 | |||
87 | self._log.append(event) | ||
88 | return event | ||
89 | |||
90 | def AddSync(self, project, task_name, start, finish, success): | ||
91 | """Add a event to the log for a sync command. | ||
92 | |||
93 | Args: | ||
94 | project: Project being synced. | ||
95 | task_name: A sub-task that was performed for name. | ||
96 | One of (TASK_SYNC_NETWORK, TASK_SYNC_LOCAL) | ||
97 | start: Timestamp of when the operation started. | ||
98 | finish: Timestamp of when the operation finished. | ||
99 | success: Boolean indicating if the operation was successful. | ||
100 | |||
101 | Returns: | ||
102 | A dictionary of the event added to the log. | ||
103 | """ | ||
104 | event = self.Add(project.relpath, success, start, finish, task_name) | ||
105 | if event is not None: | ||
106 | event['project'] = project.name | ||
107 | if project.revisionExpr: | ||
108 | event['revision'] = project.revisionExpr | ||
109 | if project.remote.url: | ||
110 | event['project_url'] = project.remote.url | ||
111 | if project.remote.fetchUrl: | ||
112 | event['remote_url'] = project.remote.fetchUrl | ||
113 | try: | ||
114 | event['git_hash'] = project.GetCommitRevisionId() | ||
115 | except Exception: | ||
116 | pass | ||
117 | return event | ||
118 | |||
119 | def GetStatusString(self, success): | ||
120 | """Converst a boolean success to a status string. | ||
121 | |||
122 | Args: | ||
123 | success: Boolean indicating if the operation was successful. | ||
124 | |||
125 | Returns: | ||
126 | status string. | ||
127 | """ | ||
128 | return 'pass' if success else 'fail' | ||
129 | |||
130 | def FinishEvent(self, event, finish, success): | ||
131 | """Finishes an incomplete event. | ||
132 | |||
133 | Args: | ||
134 | event: An event that has been added to the log. | ||
135 | finish: Timestamp of when the operation finished. | ||
136 | success: Boolean indicating if the operation was successful. | ||
137 | |||
138 | Returns: | ||
139 | A dictionary of the event added to the log. | ||
140 | """ | ||
141 | event['status'] = self.GetStatusString(success) | ||
142 | event['finish_time'] = finish | ||
143 | return event | ||
144 | |||
145 | def SetParent(self, event): | ||
146 | """Set a parent event for all new entities. | ||
147 | |||
148 | Args: | ||
149 | event: The event to use as a parent. | ||
150 | """ | ||
151 | self._parent = event | ||
152 | |||
153 | def Write(self, filename): | ||
154 | """Writes the log out to a file. | ||
155 | |||
156 | Args: | ||
157 | filename: The file to write the log to. | ||
158 | """ | ||
159 | with open(filename, 'w+') as f: | ||
160 | for e in self._log: | ||
161 | json.dump(e, f, sort_keys=True) | ||
162 | f.write('\n') | ||
163 | |||
164 | |||
165 | def _EventIdGenerator(): | ||
166 | """Returns multi-process safe iterator that generates locally unique id. | ||
167 | |||
168 | Yields: | ||
169 | A unique, to this invocation of the program, integer id. | ||
170 | """ | ||
171 | eid = multiprocessing.Value('i', 1) | ||
172 | |||
173 | while True: | ||
174 | with eid.get_lock(): | ||
175 | val = eid.value | ||
176 | eid.value += 1 | ||
177 | yield val | ||
diff --git a/hooks/pre-auto-gc b/hooks/pre-auto-gc index 43403022..c4107f51 100755 --- a/hooks/pre-auto-gc +++ b/hooks/pre-auto-gc | |||
@@ -1,9 +1,9 @@ | |||
1 | #!/bin/sh | 1 | #!/bin/sh |
2 | # | 2 | # |
3 | # An example hook script to verify if you are on battery, in case you | 3 | # An example hook script to verify if you are on battery, in case you |
4 | # are running Linux or OS X. Called by git-gc --auto with no arguments. | 4 | # are running Windows, Linux or OS X. Called by git-gc --auto with no |
5 | # The hook should exit with non-zero status after issuing an appropriate | 5 | # arguments. The hook should exit with non-zero status after issuing an |
6 | # message if it wants to stop the auto repacking. | 6 | # appropriate message if it wants to stop the auto repacking. |
7 | 7 | ||
8 | # This program is free software; you can redistribute it and/or modify | 8 | # This program is free software; you can redistribute it and/or modify |
9 | # it under the terms of the GNU General Public License as published by | 9 | # it under the terms of the GNU General Public License as published by |
@@ -19,6 +19,16 @@ | |||
19 | # along with this program; if not, write to the Free Software | 19 | # along with this program; if not, write to the Free Software |
20 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | 20 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
21 | 21 | ||
22 | if uname -s | grep -q "_NT-" | ||
23 | then | ||
24 | if test -x $SYSTEMROOT/System32/Wbem/wmic | ||
25 | then | ||
26 | STATUS=$(wmic path win32_battery get batterystatus /format:list | tr -d '\r\n') | ||
27 | [ "$STATUS" = "BatteryStatus=2" ] && exit 0 || exit 1 | ||
28 | fi | ||
29 | exit 0 | ||
30 | fi | ||
31 | |||
22 | if test -x /sbin/on_ac_power && /sbin/on_ac_power | 32 | if test -x /sbin/on_ac_power && /sbin/on_ac_power |
23 | then | 33 | then |
24 | exit 0 | 34 | exit 0 |
@@ -37,6 +37,7 @@ except ImportError: | |||
37 | kerberos = None | 37 | kerberos = None |
38 | 38 | ||
39 | from color import SetDefaultColoring | 39 | from color import SetDefaultColoring |
40 | import event_log | ||
40 | from trace import SetTrace | 41 | from trace import SetTrace |
41 | from git_command import git, GitCommand | 42 | from git_command import git, GitCommand |
42 | from git_config import init_ssh, close_ssh | 43 | from git_config import init_ssh, close_ssh |
@@ -85,6 +86,9 @@ global_options.add_option('--time', | |||
85 | global_options.add_option('--version', | 86 | global_options.add_option('--version', |
86 | dest='show_version', action='store_true', | 87 | dest='show_version', action='store_true', |
87 | help='display this version of repo') | 88 | help='display this version of repo') |
89 | global_options.add_option('--event-log', | ||
90 | dest='event_log', action='store', | ||
91 | help='filename of event log to append timeline to') | ||
88 | 92 | ||
89 | class _Repo(object): | 93 | class _Repo(object): |
90 | def __init__(self, repodir): | 94 | def __init__(self, repodir): |
@@ -176,6 +180,8 @@ class _Repo(object): | |||
176 | RunPager(config) | 180 | RunPager(config) |
177 | 181 | ||
178 | start = time.time() | 182 | start = time.time() |
183 | cmd_event = cmd.event_log.Add(name, event_log.TASK_COMMAND, start) | ||
184 | cmd.event_log.SetParent(cmd_event) | ||
179 | try: | 185 | try: |
180 | result = cmd.Execute(copts, cargs) | 186 | result = cmd.Execute(copts, cargs) |
181 | except (DownloadError, ManifestInvalidRevisionError, | 187 | except (DownloadError, ManifestInvalidRevisionError, |
@@ -203,7 +209,8 @@ class _Repo(object): | |||
203 | result = e.code | 209 | result = e.code |
204 | raise | 210 | raise |
205 | finally: | 211 | finally: |
206 | elapsed = time.time() - start | 212 | finish = time.time() |
213 | elapsed = finish - start | ||
207 | hours, remainder = divmod(elapsed, 3600) | 214 | hours, remainder = divmod(elapsed, 3600) |
208 | minutes, seconds = divmod(remainder, 60) | 215 | minutes, seconds = divmod(remainder, 60) |
209 | if gopts.time: | 216 | if gopts.time: |
@@ -213,6 +220,12 @@ class _Repo(object): | |||
213 | print('real\t%dh%dm%.3fs' % (hours, minutes, seconds), | 220 | print('real\t%dh%dm%.3fs' % (hours, minutes, seconds), |
214 | file=sys.stderr) | 221 | file=sys.stderr) |
215 | 222 | ||
223 | cmd.event_log.FinishEvent(cmd_event, finish, | ||
224 | result is None or result == 0) | ||
225 | if gopts.event_log: | ||
226 | cmd.event_log.Write(os.path.abspath( | ||
227 | os.path.expanduser(gopts.event_log))) | ||
228 | |||
216 | return result | 229 | return result |
217 | 230 | ||
218 | 231 | ||
diff --git a/manifest_xml.py b/manifest_xml.py index 73e34964..55d25a79 100644 --- a/manifest_xml.py +++ b/manifest_xml.py | |||
@@ -110,7 +110,8 @@ class _XmlRemote(object): | |||
110 | return url | 110 | return url |
111 | 111 | ||
112 | def ToRemoteSpec(self, projectName): | 112 | def ToRemoteSpec(self, projectName): |
113 | url = self.resolvedFetchUrl.rstrip('/') + '/' + projectName | 113 | fetchUrl = self.resolvedFetchUrl.rstrip('/') |
114 | url = fetchUrl + '/' + projectName | ||
114 | remoteName = self.name | 115 | remoteName = self.name |
115 | if self.remoteAlias: | 116 | if self.remoteAlias: |
116 | remoteName = self.remoteAlias | 117 | remoteName = self.remoteAlias |
@@ -118,7 +119,8 @@ class _XmlRemote(object): | |||
118 | url=url, | 119 | url=url, |
119 | pushUrl=self.pushUrl, | 120 | pushUrl=self.pushUrl, |
120 | review=self.reviewUrl, | 121 | review=self.reviewUrl, |
121 | orig_name=self.name) | 122 | orig_name=self.name, |
123 | fetchUrl=self.fetchUrl) | ||
122 | 124 | ||
123 | class XmlManifest(object): | 125 | class XmlManifest(object): |
124 | """manages the repo configuration file""" | 126 | """manages the repo configuration file""" |
@@ -164,7 +166,7 @@ class XmlManifest(object): | |||
164 | try: | 166 | try: |
165 | if os.path.lexists(self.manifestFile): | 167 | if os.path.lexists(self.manifestFile): |
166 | os.remove(self.manifestFile) | 168 | os.remove(self.manifestFile) |
167 | os.symlink('manifests/%s' % name, self.manifestFile) | 169 | os.symlink(os.path.join('manifests', name), self.manifestFile) |
168 | except OSError as e: | 170 | except OSError as e: |
169 | raise ManifestParseError('cannot link manifest %s: %s' % (name, str(e))) | 171 | raise ManifestParseError('cannot link manifest %s: %s' % (name, str(e))) |
170 | 172 | ||
@@ -323,13 +323,15 @@ class RemoteSpec(object): | |||
323 | pushUrl=None, | 323 | pushUrl=None, |
324 | review=None, | 324 | review=None, |
325 | revision=None, | 325 | revision=None, |
326 | orig_name=None): | 326 | orig_name=None, |
327 | fetchUrl=None): | ||
327 | self.name = name | 328 | self.name = name |
328 | self.url = url | 329 | self.url = url |
329 | self.pushUrl = pushUrl | 330 | self.pushUrl = pushUrl |
330 | self.review = review | 331 | self.review = review |
331 | self.revision = revision | 332 | self.revision = revision |
332 | self.orig_name = orig_name | 333 | self.orig_name = orig_name |
334 | self.fetchUrl = fetchUrl | ||
333 | 335 | ||
334 | 336 | ||
335 | class RepoHook(object): | 337 | class RepoHook(object): |
@@ -687,7 +689,7 @@ class Project(object): | |||
687 | self.gitdir = gitdir.replace('\\', '/') | 689 | self.gitdir = gitdir.replace('\\', '/') |
688 | self.objdir = objdir.replace('\\', '/') | 690 | self.objdir = objdir.replace('\\', '/') |
689 | if worktree: | 691 | if worktree: |
690 | self.worktree = os.path.normpath(worktree.replace('\\', '/')) | 692 | self.worktree = os.path.normpath(worktree).replace('\\', '/') |
691 | else: | 693 | else: |
692 | self.worktree = None | 694 | self.worktree = None |
693 | self.relpath = relpath | 695 | self.relpath = relpath |
@@ -2876,13 +2878,14 @@ class SyncBuffer(object): | |||
2876 | 2878 | ||
2877 | self.detach_head = detach_head | 2879 | self.detach_head = detach_head |
2878 | self.clean = True | 2880 | self.clean = True |
2881 | self.recent_clean = True | ||
2879 | 2882 | ||
2880 | def info(self, project, fmt, *args): | 2883 | def info(self, project, fmt, *args): |
2881 | self._messages.append(_InfoMessage(project, fmt % args)) | 2884 | self._messages.append(_InfoMessage(project, fmt % args)) |
2882 | 2885 | ||
2883 | def fail(self, project, err=None): | 2886 | def fail(self, project, err=None): |
2884 | self._failures.append(_Failure(project, err)) | 2887 | self._failures.append(_Failure(project, err)) |
2885 | self.clean = False | 2888 | self._MarkUnclean() |
2886 | 2889 | ||
2887 | def later1(self, project, what): | 2890 | def later1(self, project, what): |
2888 | self._later_queue1.append(_Later(project, what)) | 2891 | self._later_queue1.append(_Later(project, what)) |
@@ -2896,6 +2899,15 @@ class SyncBuffer(object): | |||
2896 | self._PrintMessages() | 2899 | self._PrintMessages() |
2897 | return self.clean | 2900 | return self.clean |
2898 | 2901 | ||
2902 | def Recently(self): | ||
2903 | recent_clean = self.recent_clean | ||
2904 | self.recent_clean = True | ||
2905 | return recent_clean | ||
2906 | |||
2907 | def _MarkUnclean(self): | ||
2908 | self.clean = False | ||
2909 | self.recent_clean = False | ||
2910 | |||
2899 | def _RunLater(self): | 2911 | def _RunLater(self): |
2900 | for q in ['_later_queue1', '_later_queue2']: | 2912 | for q in ['_later_queue1', '_later_queue2']: |
2901 | if not self._RunQueue(q): | 2913 | if not self._RunQueue(q): |
@@ -2904,7 +2916,7 @@ class SyncBuffer(object): | |||
2904 | def _RunQueue(self, queue): | 2916 | def _RunQueue(self, queue): |
2905 | for m in getattr(self, queue): | 2917 | for m in getattr(self, queue): |
2906 | if not m.Run(self): | 2918 | if not m.Run(self): |
2907 | self.clean = False | 2919 | self._MarkUnclean() |
2908 | return False | 2920 | return False |
2909 | setattr(self, queue, []) | 2921 | setattr(self, queue, []) |
2910 | return True | 2922 | return True |
diff --git a/subcmds/sync.py b/subcmds/sync.py index 82056f33..ef023274 100644 --- a/subcmds/sync.py +++ b/subcmds/sync.py | |||
@@ -64,6 +64,7 @@ try: | |||
64 | except ImportError: | 64 | except ImportError: |
65 | multiprocessing = None | 65 | multiprocessing = None |
66 | 66 | ||
67 | import event_log | ||
67 | from git_command import GIT, git_require | 68 | from git_command import GIT, git_require |
68 | from git_config import GetUrlCookieFile | 69 | from git_config import GetUrlCookieFile |
69 | from git_refs import R_HEADS, HEAD | 70 | from git_refs import R_HEADS, HEAD |
@@ -304,9 +305,10 @@ later is required to fix a server side protocol bug. | |||
304 | # - We always set err_event in the case of an exception. | 305 | # - We always set err_event in the case of an exception. |
305 | # - We always make sure we call sem.release(). | 306 | # - We always make sure we call sem.release(). |
306 | # - We always make sure we unlock the lock if we locked it. | 307 | # - We always make sure we unlock the lock if we locked it. |
308 | start = time.time() | ||
309 | success = False | ||
307 | try: | 310 | try: |
308 | try: | 311 | try: |
309 | start = time.time() | ||
310 | success = project.Sync_NetworkHalf( | 312 | success = project.Sync_NetworkHalf( |
311 | quiet=opt.quiet, | 313 | quiet=opt.quiet, |
312 | current_branch_only=opt.current_branch_only, | 314 | current_branch_only=opt.current_branch_only, |
@@ -345,6 +347,9 @@ later is required to fix a server side protocol bug. | |||
345 | finally: | 347 | finally: |
346 | if did_lock: | 348 | if did_lock: |
347 | lock.release() | 349 | lock.release() |
350 | finish = time.time() | ||
351 | self.event_log.AddSync(project, event_log.TASK_SYNC_NETWORK, | ||
352 | start, finish, success) | ||
348 | 353 | ||
349 | return success | 354 | return success |
350 | 355 | ||
@@ -720,16 +725,24 @@ later is required to fix a server side protocol bug. | |||
720 | _PostRepoUpgrade(self.manifest, quiet=opt.quiet) | 725 | _PostRepoUpgrade(self.manifest, quiet=opt.quiet) |
721 | 726 | ||
722 | if not opt.local_only: | 727 | if not opt.local_only: |
723 | mp.Sync_NetworkHalf(quiet=opt.quiet, | 728 | start = time.time() |
724 | current_branch_only=opt.current_branch_only, | 729 | success = mp.Sync_NetworkHalf(quiet=opt.quiet, |
725 | no_tags=opt.no_tags, | 730 | current_branch_only=opt.current_branch_only, |
726 | optimized_fetch=opt.optimized_fetch, | 731 | no_tags=opt.no_tags, |
727 | submodules=self.manifest.HasSubmodules) | 732 | optimized_fetch=opt.optimized_fetch, |
733 | submodules=self.manifest.HasSubmodules) | ||
734 | finish = time.time() | ||
735 | self.event_log.AddSync(mp, event_log.TASK_SYNC_NETWORK, | ||
736 | start, finish, success) | ||
728 | 737 | ||
729 | if mp.HasChanges: | 738 | if mp.HasChanges: |
730 | syncbuf = SyncBuffer(mp.config) | 739 | syncbuf = SyncBuffer(mp.config) |
740 | start = time.time() | ||
731 | mp.Sync_LocalHalf(syncbuf, submodules=self.manifest.HasSubmodules) | 741 | mp.Sync_LocalHalf(syncbuf, submodules=self.manifest.HasSubmodules) |
732 | if not syncbuf.Finish(): | 742 | clean = syncbuf.Finish() |
743 | self.event_log.AddSync(mp, event_log.TASK_SYNC_LOCAL, | ||
744 | start, time.time(), clean) | ||
745 | if not clean: | ||
733 | sys.exit(1) | 746 | sys.exit(1) |
734 | self._ReloadManifest(manifest_name) | 747 | self._ReloadManifest(manifest_name) |
735 | if opt.jobs is None: | 748 | if opt.jobs is None: |
@@ -823,7 +836,10 @@ later is required to fix a server side protocol bug. | |||
823 | for project in all_projects: | 836 | for project in all_projects: |
824 | pm.update() | 837 | pm.update() |
825 | if project.worktree: | 838 | if project.worktree: |
839 | start = time.time() | ||
826 | project.Sync_LocalHalf(syncbuf, force_sync=opt.force_sync) | 840 | project.Sync_LocalHalf(syncbuf, force_sync=opt.force_sync) |
841 | self.event_log.AddSync(project, event_log.TASK_SYNC_LOCAL, | ||
842 | start, time.time(), syncbuf.Recently()) | ||
827 | pm.end() | 843 | pm.end() |
828 | print(file=sys.stderr) | 844 | print(file=sys.stderr) |
829 | if not syncbuf.Finish(): | 845 | if not syncbuf.Finish(): |
@@ -907,6 +923,7 @@ def _VerifyTag(project): | |||
907 | return False | 923 | return False |
908 | return True | 924 | return True |
909 | 925 | ||
926 | |||
910 | class _FetchTimes(object): | 927 | class _FetchTimes(object): |
911 | _ALPHA = 0.5 | 928 | _ALPHA = 0.5 |
912 | 929 | ||