Compare commits
84 Commits
master
...
36a47ec614
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6d1f5c5ec4 | ||
|
|
e86b8c099e | ||
|
|
f037f034bb | ||
|
|
4b44c24078 | ||
|
|
5f3ed635a7 | ||
|
|
d4108459eb | ||
|
|
ae174ad93c | ||
|
|
d8c83636e6 | ||
|
|
be31f3772c | ||
|
|
194a588a57 | ||
|
|
84cb06c4db | ||
|
|
6b55bbebb1 | ||
|
|
135dee6512 | ||
|
|
6f196e05ce | ||
|
|
6161d31622 | ||
|
|
f2a5b9e9bf | ||
|
|
0bb9d60967 | ||
|
|
490ac76661 | ||
|
|
9b96ed3f33 | ||
|
|
9d78ef337b | ||
|
|
89b2e659a2 | ||
|
|
d3fab9c982 | ||
|
|
a7c27ed890 | ||
|
|
ba23c18841 | ||
|
|
cb651c8d8e | ||
|
|
7e8d18a464 | ||
|
|
9ca8158531 | ||
|
|
f90b35cf6c | ||
|
|
7382e1a20f | ||
|
|
63d6350c8a | ||
|
|
e62af19b57 | ||
|
|
e7b8e61a29 | ||
|
|
8045c4a777 | ||
|
|
923b5fd65a | ||
|
|
b6ff8ad7ad | ||
|
|
833d66db10 | ||
|
|
7cc81eb7c8 | ||
|
|
67ecf18ff9 | ||
|
|
ad2b7e93f1 | ||
|
|
b75695e4e1 | ||
|
|
71c42004e0 | ||
|
|
141ff826fd | ||
|
|
bb0c1608a1 | ||
|
|
fa551f7ae3 | ||
|
|
94323d60eb | ||
|
|
0743b9dbdb | ||
|
|
b57d0f80d3 | ||
|
|
8b1d7be7ab | ||
|
|
7726d54b47 | ||
|
|
61620c0d61 | ||
|
|
4f8c870d1e | ||
|
|
abcdf5a80b | ||
|
|
d21a749138 | ||
|
|
eff237c48a | ||
|
|
80003a2abe | ||
|
|
866c9351be | ||
|
|
77dd7f8d28 | ||
|
|
3c16cf3c39 | ||
|
|
5a1c2ba98e | ||
|
|
ac43d305a8 | ||
|
|
901d618860 | ||
|
|
14c4127600 | ||
|
|
dc25f5f142 | ||
|
|
cc020142b7 | ||
|
|
1255f157c1 | ||
|
|
ddbe563959 | ||
|
|
168b40f0ff | ||
|
|
39881664f2 | ||
|
|
fc05e34ea4 | ||
|
|
efa7a19284 | ||
|
|
ee271f84ba | ||
|
|
24ea1d6cd7 | ||
|
|
29296c1ed9 | ||
|
|
931838789d | ||
|
|
e0c820476e | ||
|
|
ac930ff992 | ||
|
|
f1c4d43ce0 | ||
|
|
0df7825a8b | ||
|
|
3e7effc73a | ||
|
|
c3cec02457 | ||
|
|
dfa1355e07 | ||
|
|
6507c32ebc | ||
|
|
1840ac24bb | ||
|
|
28d3fe9a1a |
@ -90,3 +90,4 @@ Welcome to MongoDB!
|
||||
October 16, 2018, including patch fixes for prior versions, are published
|
||||
under the [Server Side Public License (SSPL) v1](LICENSE-Community.txt).
|
||||
See individual files for details.
|
||||
|
||||
|
||||
21
SConstruct
21
SConstruct
@ -22,6 +22,10 @@ from pkg_resources import parse_version
|
||||
|
||||
import SCons
|
||||
import SCons.Script
|
||||
from buildscripts.metrics.metrics_datatypes import SConsToolingMetrics
|
||||
from buildscripts.metrics.tooling_exit_hook import initialize_exit_hook
|
||||
from buildscripts.metrics.tooling_metrics_utils import register_metrics_collection_atexit
|
||||
from site_scons.mongo import build_profiles
|
||||
|
||||
# This must be first, even before EnsureSConsVersion, if
|
||||
# we are to avoid bulk loading all tools in the DefaultEnvironment.
|
||||
@ -52,8 +56,6 @@ SCons.Node.FS.File.release_target_info = release_target_info_noop
|
||||
|
||||
from buildscripts import utils
|
||||
from buildscripts import moduleconfig
|
||||
from buildscripts.metrics.scons_tooling_metrics import setup_scons_metrics_collection_atexit
|
||||
|
||||
import psutil
|
||||
|
||||
scons_invocation = '{} {}'.format(sys.executable, ' '.join(sys.argv))
|
||||
@ -1551,10 +1553,17 @@ env = Environment(variables=env_vars, **envDict)
|
||||
del envDict
|
||||
env.AddMethod(lambda env, name, **kwargs: add_option(name, **kwargs), 'AddOption')
|
||||
|
||||
# Setup atexit method to store tooling metrics
|
||||
# The placement of this is intentional. We should only register this function atexit after
|
||||
# env, env_vars and the parser have been properly initialized.
|
||||
setup_scons_metrics_collection_atexit(utc_starttime, env_vars, env, _parser, sys.argv)
|
||||
# The placement of this is intentional. Here we setup an atexit method to store tooling metrics.
|
||||
# We should only register this function after env, env_vars and the parser have been properly initialized.
|
||||
register_metrics_collection_atexit(
|
||||
SConsToolingMetrics.generate_metrics, {
|
||||
"utc_starttime": datetime.utcnow(),
|
||||
"env_vars": env_vars,
|
||||
"env": env,
|
||||
"parser": _parser,
|
||||
"args": sys.argv,
|
||||
"exit_hook": initialize_exit_hook(),
|
||||
})
|
||||
|
||||
if get_option('build-metrics'):
|
||||
env['BUILD_METRICS_ARTIFACTS_DIR'] = '$BUILD_ROOT/$VARIANT_DIR'
|
||||
|
||||
@ -7,7 +7,7 @@ import math
|
||||
import os
|
||||
import shlex
|
||||
import sys
|
||||
from datetime import datetime, timedelta
|
||||
from datetime import timedelta
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Optional
|
||||
|
||||
@ -19,7 +19,7 @@ from evergreen import EvergreenApi, RetryingEvergreenApi
|
||||
|
||||
from buildscripts.ciconfig.evergreen import (EvergreenProjectConfig, parse_evergreen_file)
|
||||
from buildscripts.resmoke_proxy.resmoke_proxy import ResmokeProxyService
|
||||
from buildscripts.timeouts.timeout_service import (TimeoutParams, TimeoutService, TimeoutSettings)
|
||||
from buildscripts.timeouts.timeout_service import (TimeoutParams, TimeoutService)
|
||||
from buildscripts.util.cmdutils import enable_logging
|
||||
from buildscripts.util.taskname import determine_task_base_name
|
||||
|
||||
@ -372,9 +372,6 @@ def main():
|
||||
|
||||
options = parser.parse_args()
|
||||
|
||||
end_date = datetime.now()
|
||||
start_date = end_date - HISTORY_LOOKBACK
|
||||
|
||||
timeout_override = timedelta(seconds=options.timeout) if options.timeout else None
|
||||
exec_timeout_override = timedelta(
|
||||
seconds=options.exec_timeout) if options.exec_timeout else None
|
||||
@ -389,7 +386,6 @@ def main():
|
||||
binder.bind(
|
||||
EvergreenApi,
|
||||
RetryingEvergreenApi.get_api(config_file=os.path.expanduser(options.evg_api_config)))
|
||||
binder.bind(TimeoutSettings, TimeoutSettings(start_date=start_date, end_date=end_date))
|
||||
binder.bind(TimeoutOverrides, timeout_overrides)
|
||||
binder.bind(EvergreenProjectConfig,
|
||||
parse_evergreen_file(os.path.expanduser(options.evg_project_config)))
|
||||
|
||||
@ -61,8 +61,8 @@ def generate_version_expansions():
|
||||
raise ValueError("Unable to parse version from stdin and no version.json provided")
|
||||
|
||||
if version_parts[0]:
|
||||
expansions["suffix"] = "latest"
|
||||
expansions["src_suffix"] = "latest"
|
||||
expansions["suffix"] = "v6.2-latest"
|
||||
expansions["src_suffix"] = "v6.2-latest"
|
||||
expansions["is_release"] = "false"
|
||||
else:
|
||||
expansions["suffix"] = version_line
|
||||
|
||||
@ -30,7 +30,6 @@ Generate a file containing a list of disabled feature flags.
|
||||
Used by resmoke.py to run only feature flag tests.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import sys
|
||||
|
||||
@ -43,6 +42,7 @@ sys.path.append(os.path.normpath(os.path.join(os.path.abspath(__file__), '../../
|
||||
|
||||
# pylint: disable=wrong-import-position
|
||||
import buildscripts.idl.lib as lib
|
||||
from buildscripts.idl.idl import parser
|
||||
|
||||
|
||||
def is_third_party_idl(idl_path: str) -> bool:
|
||||
@ -56,13 +56,14 @@ def is_third_party_idl(idl_path: str) -> bool:
|
||||
return False
|
||||
|
||||
|
||||
def gen_all_feature_flags(idl_dir: str, import_dirs: List[str]):
|
||||
def gen_all_feature_flags(idl_dir: str = os.getcwd()):
|
||||
"""Generate a list of all feature flags."""
|
||||
all_flags = []
|
||||
for idl_path in sorted(lib.list_idls(idl_dir)):
|
||||
if is_third_party_idl(idl_path):
|
||||
continue
|
||||
for feature_flag in lib.parse_idl(idl_path, import_dirs).spec.feature_flags:
|
||||
doc = parser.parse_file(open(idl_path), idl_path)
|
||||
for feature_flag in doc.spec.feature_flags:
|
||||
if feature_flag.default.literal != "true":
|
||||
all_flags.append(feature_flag.name)
|
||||
|
||||
@ -72,18 +73,16 @@ def gen_all_feature_flags(idl_dir: str, import_dirs: List[str]):
|
||||
return list(set(all_flags) - set(force_disabled_flags))
|
||||
|
||||
|
||||
def gen_all_feature_flags_file(filename: str = lib.ALL_FEATURE_FLAG_FILE):
|
||||
flags = gen_all_feature_flags()
|
||||
with open(filename, "w") as output_file:
|
||||
output_file.write("\n".join(flags))
|
||||
print("Generated: ", os.path.realpath(output_file.name))
|
||||
|
||||
|
||||
def main():
|
||||
"""Run the main function."""
|
||||
arg_parser = argparse.ArgumentParser(description=__doc__)
|
||||
arg_parser.add_argument("--import-dir", dest="import_dirs", type=str, action="append",
|
||||
help="Directory to search for IDL import files")
|
||||
|
||||
args = arg_parser.parse_args()
|
||||
|
||||
flags = gen_all_feature_flags(os.getcwd(), args.import_dirs)
|
||||
with open(lib.ALL_FEATURE_FLAG_FILE, "w") as output_file:
|
||||
for flag in flags:
|
||||
output_file.write("%s\n" % flag)
|
||||
gen_all_feature_flags_file()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
@ -1023,7 +1023,7 @@ def _propagate_globals(spec):
|
||||
idltype.cpp_type = _prefix_with_namespace(cpp_namespace, idltype.cpp_type)
|
||||
|
||||
|
||||
def _parse(stream, error_file_name):
|
||||
def parse_file(stream, error_file_name):
|
||||
# type: (Any, str) -> syntax.IDLParsedSpec
|
||||
"""
|
||||
Parse a YAML document into an idl.syntax tree.
|
||||
@ -1125,7 +1125,7 @@ def parse(stream, input_file_name, resolver):
|
||||
input_file_name: a file name for error messages to use, and to help resolve imported files.
|
||||
"""
|
||||
|
||||
root_doc = _parse(stream, input_file_name)
|
||||
root_doc = parse_file(stream, input_file_name)
|
||||
|
||||
if root_doc.errors:
|
||||
return root_doc
|
||||
@ -1162,7 +1162,7 @@ def parse(stream, input_file_name, resolver):
|
||||
|
||||
# Parse imported file
|
||||
with resolver.open(resolved_file_name) as file_stream:
|
||||
parsed_doc = _parse(file_stream, resolved_file_name)
|
||||
parsed_doc = parse_file(file_stream, resolved_file_name)
|
||||
|
||||
# Check for errors
|
||||
if parsed_doc.errors:
|
||||
|
||||
@ -5,12 +5,13 @@ import multiprocessing
|
||||
import os
|
||||
import socket
|
||||
import sys
|
||||
import traceback
|
||||
from typing import Any, Dict, List, Optional
|
||||
import distro
|
||||
import git
|
||||
from pydantic import BaseModel
|
||||
|
||||
from buildscripts.metrics.tooling_exit_hook import _ExitHook
|
||||
|
||||
# pylint: disable=bare-except
|
||||
|
||||
SCONS_ENV_FILE = "scons_env.env"
|
||||
@ -20,6 +21,12 @@ SCONS_SECTION_HEADER = "SCONS_ENV"
|
||||
class BaseMetrics(BaseModel):
|
||||
"""Base class for an metrics object."""
|
||||
|
||||
@classmethod
|
||||
@abstractmethod
|
||||
def generate_metrics(cls, **kwargs):
|
||||
"""Generate metrics."""
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
def is_malformed(self) -> bool:
|
||||
"""Confirm whether this instance has all expected fields."""
|
||||
@ -35,14 +42,14 @@ class BuildInfo(BaseMetrics):
|
||||
artifact_dir: Optional[str]
|
||||
|
||||
@classmethod
|
||||
def get_scons_build_info(
|
||||
def generate_metrics(
|
||||
cls,
|
||||
utc_starttime: datetime,
|
||||
env_vars: "SCons.Variables.Variables",
|
||||
env: "SCons.Script.SConscript.SConsEnvironment",
|
||||
parser: "SCons.Script.SConsOptions.SConsOptionParser",
|
||||
args: List[str],
|
||||
):
|
||||
): # pylint: disable=arguments-differ
|
||||
"""Get SCons build info to the best of our ability."""
|
||||
artifact_dir = cls._get_scons_artifact_dir(env)
|
||||
return cls(
|
||||
@ -59,19 +66,22 @@ class BuildInfo(BaseMetrics):
|
||||
) -> Optional[Dict[str, Any]]:
|
||||
"""Get the environment variables options that can be set by users."""
|
||||
|
||||
artifact_dir = BuildInfo._get_scons_artifact_dir(env)
|
||||
artifact_dir = artifact_dir if artifact_dir else '.'
|
||||
scons_env_filepath = f'{artifact_dir}/{SCONS_ENV_FILE}'
|
||||
try:
|
||||
# Use SCons built-in method to save environment variables to a file
|
||||
env_vars.Save(SCONS_ENV_FILE, env)
|
||||
env_vars.Save(scons_env_filepath, env)
|
||||
|
||||
# Add a section header to the file so we can easily parse with ConfigParser
|
||||
with open(SCONS_ENV_FILE, 'r') as original:
|
||||
with open(scons_env_filepath, 'r') as original:
|
||||
data = original.read()
|
||||
with open(SCONS_ENV_FILE, 'w') as modified:
|
||||
with open(scons_env_filepath, 'w') as modified:
|
||||
modified.write(f"[{SCONS_SECTION_HEADER}]\n" + data)
|
||||
|
||||
# Parse file using config parser
|
||||
config = configparser.ConfigParser()
|
||||
config.read(SCONS_ENV_FILE)
|
||||
config.read(scons_env_filepath)
|
||||
str_dict = dict(config[SCONS_SECTION_HEADER])
|
||||
return {key: eval(val) for key, val in str_dict.items()} # pylint: disable=eval-used
|
||||
except:
|
||||
@ -118,52 +128,27 @@ class BuildInfo(BaseMetrics):
|
||||
return None in [self.artifact_dir, self.env, self.options, self.build_artifacts]
|
||||
|
||||
|
||||
class ExitInfo(BaseMetrics):
|
||||
"""Class to store tooling exit information."""
|
||||
|
||||
exit_code: Optional[int]
|
||||
exception: Optional[str]
|
||||
stacktrace: Optional[str]
|
||||
|
||||
@classmethod
|
||||
def get_resmoke_exit_info(cls):
|
||||
"""Get the current exit info."""
|
||||
exc = sys.exc_info()[1]
|
||||
return cls(
|
||||
exit_code=0 if not exc else exc.code if exc.__class__ == SystemExit else 1,
|
||||
exception=exc.__class__.__name__ if exc else None,
|
||||
stacktrace=traceback.format_exc() if exc else None,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def get_scons_exit_info(cls, exit_code):
|
||||
"""Get the current exit info using the given exit code."""
|
||||
return cls(
|
||||
exit_code=exit_code if isinstance(exit_code, int) else None,
|
||||
exception=None,
|
||||
stacktrace=None,
|
||||
)
|
||||
|
||||
def is_malformed(self):
|
||||
"""Return True if this object is missing an exit code."""
|
||||
return self.exit_code is None
|
||||
|
||||
|
||||
class HostInfo(BaseMetrics):
|
||||
"""Class to store host information."""
|
||||
|
||||
ip_address: Optional[str]
|
||||
host_os: str
|
||||
num_cores: int
|
||||
memory: Optional[float]
|
||||
|
||||
@classmethod
|
||||
def get_host_info(cls):
|
||||
def generate_metrics(cls): # pylint: disable=arguments-differ
|
||||
"""Get the host info to the best of our ability."""
|
||||
try:
|
||||
ip_address = socket.gethostbyname(socket.gethostname())
|
||||
except:
|
||||
ip_address = None
|
||||
try:
|
||||
memory = cls._get_memory()
|
||||
except:
|
||||
memory = None
|
||||
return cls(
|
||||
ip_address=ip_address,
|
||||
host_os=distro.name(pretty=True),
|
||||
num_cores=multiprocessing.cpu_count(),
|
||||
memory=memory,
|
||||
@ -174,9 +159,9 @@ class HostInfo(BaseMetrics):
|
||||
"""Get total memory of the host system."""
|
||||
return os.sysconf('SC_PAGE_SIZE') * os.sysconf('SC_PHYS_PAGES') / (1024.**3)
|
||||
|
||||
def is_malformed(self):
|
||||
def is_malformed(self) -> bool:
|
||||
"""Confirm whether this instance has all expected fields."""
|
||||
return self.memory is None
|
||||
return None in [self.memory, self.ip_address]
|
||||
|
||||
|
||||
class GitInfo(BaseMetrics):
|
||||
@ -188,7 +173,7 @@ class GitInfo(BaseMetrics):
|
||||
repo_name: Optional[str]
|
||||
|
||||
@classmethod
|
||||
def get_git_info(cls, filepath: str):
|
||||
def generate_metrics(cls, filepath: str): # pylint: disable=arguments-differ
|
||||
"""Get the git info for a repo to the best of our ability."""
|
||||
try:
|
||||
commit_hash = git.Repo(filepath).head.commit.hexsha
|
||||
@ -212,7 +197,7 @@ class GitInfo(BaseMetrics):
|
||||
repo_name=repo_name,
|
||||
)
|
||||
|
||||
def is_malformed(self):
|
||||
def is_malformed(self) -> bool:
|
||||
"""Confirm whether this instance has all expected fields."""
|
||||
return None in [self.commit_hash, self.branch_name, self.repo_name]
|
||||
|
||||
@ -225,7 +210,7 @@ def _get_modules_git_info():
|
||||
module_git_info = []
|
||||
try:
|
||||
module_git_info = [
|
||||
GitInfo.get_git_info(os.path.join(MODULES_FILEPATH, module))
|
||||
GitInfo.generate_metrics(os.path.join(MODULES_FILEPATH, module))
|
||||
for module in os.listdir(MODULES_FILEPATH)
|
||||
if os.path.isdir(os.path.join(MODULES_FILEPATH, module))
|
||||
]
|
||||
@ -234,73 +219,79 @@ def _get_modules_git_info():
|
||||
return module_git_info
|
||||
|
||||
|
||||
class ToolingMetrics(BaseMetrics):
|
||||
"""Class to store tooling metrics."""
|
||||
class ResmokeToolingMetrics(BaseMetrics):
|
||||
"""Class to store resmoke tooling metrics."""
|
||||
|
||||
source: str
|
||||
utc_starttime: datetime
|
||||
utc_endtime: datetime
|
||||
host_info: HostInfo
|
||||
git_info: GitInfo
|
||||
exit_info: ExitInfo
|
||||
build_info: Optional[BuildInfo]
|
||||
exit_code: Optional[int]
|
||||
command: List[str]
|
||||
module_info: List[GitInfo]
|
||||
ip_address: Optional[str]
|
||||
|
||||
@classmethod
|
||||
def get_resmoke_metrics(
|
||||
def generate_metrics(
|
||||
cls,
|
||||
utc_starttime: datetime,
|
||||
):
|
||||
exit_hook: _ExitHook,
|
||||
): # pylint: disable=arguments-differ
|
||||
"""Get resmoke metrics to the best of our ability."""
|
||||
try:
|
||||
ip_address = socket.gethostbyname(socket.gethostname())
|
||||
except:
|
||||
ip_address = None
|
||||
return cls(
|
||||
source='resmoke',
|
||||
utc_starttime=utc_starttime,
|
||||
utc_endtime=datetime.utcnow(),
|
||||
host_info=HostInfo.get_host_info(),
|
||||
git_info=GitInfo.get_git_info('.'),
|
||||
exit_info=ExitInfo.get_resmoke_exit_info(),
|
||||
build_info=None,
|
||||
module_info=_get_modules_git_info(),
|
||||
host_info=HostInfo.generate_metrics(),
|
||||
git_info=GitInfo.generate_metrics('.'),
|
||||
exit_code=exit_hook.exit_code if isinstance(exit_hook.exit_code, int) else None,
|
||||
command=sys.argv,
|
||||
ip_address=ip_address,
|
||||
module_info=_get_modules_git_info(),
|
||||
)
|
||||
|
||||
def is_malformed(self) -> bool:
|
||||
"""Confirm whether this instance has all expected fields."""
|
||||
sub_metrics = self.module_info + [self.git_info] + [self.host_info]
|
||||
return self.exit_code is None or any(metrics.is_malformed() for metrics in sub_metrics)
|
||||
|
||||
|
||||
class SConsToolingMetrics(BaseMetrics):
|
||||
"""Class to store scons tooling metrics."""
|
||||
|
||||
source: str
|
||||
utc_starttime: datetime
|
||||
utc_endtime: datetime
|
||||
host_info: HostInfo
|
||||
git_info: GitInfo
|
||||
exit_code: Optional[int]
|
||||
build_info: BuildInfo
|
||||
command: List[str]
|
||||
module_info: List[GitInfo]
|
||||
|
||||
@classmethod
|
||||
def get_scons_metrics(
|
||||
def generate_metrics(
|
||||
cls,
|
||||
utc_starttime: datetime,
|
||||
env_vars: "SCons.Variables.Variables",
|
||||
env: "SCons.Script.SConscript.SConsEnvironment",
|
||||
parser: "SCons.Script.SConsOptions.SConsOptionParser",
|
||||
args: List[str],
|
||||
exit_code: int,
|
||||
):
|
||||
exit_hook: _ExitHook,
|
||||
): # pylint: disable=arguments-differ
|
||||
"""Get scons metrics to the best of our ability."""
|
||||
try:
|
||||
ip_address = socket.gethostbyname(socket.gethostname())
|
||||
except:
|
||||
ip_address = None
|
||||
return cls(
|
||||
source='scons',
|
||||
utc_starttime=utc_starttime,
|
||||
utc_endtime=datetime.utcnow(),
|
||||
host_info=HostInfo.get_host_info(),
|
||||
git_info=GitInfo.get_git_info('.'),
|
||||
exit_info=ExitInfo.get_scons_exit_info(exit_code),
|
||||
build_info=BuildInfo.get_scons_build_info(utc_starttime, env_vars, env, parser, args),
|
||||
module_info=_get_modules_git_info(),
|
||||
host_info=HostInfo.generate_metrics(),
|
||||
git_info=GitInfo.generate_metrics('.'),
|
||||
build_info=BuildInfo.generate_metrics(utc_starttime, env_vars, env, parser, args),
|
||||
exit_code=exit_hook.exit_code if isinstance(exit_hook.exit_code, int) else None,
|
||||
command=sys.argv,
|
||||
ip_address=ip_address,
|
||||
module_info=_get_modules_git_info(),
|
||||
)
|
||||
|
||||
def is_malformed(self):
|
||||
def is_malformed(self) -> bool:
|
||||
"""Confirm whether this instance has all expected fields."""
|
||||
sub_metrics = [self.build_info] if self.source == 'scons' else []
|
||||
sub_metrics += self.module_info + [self.git_info] + [self.host_info] + [self.exit_info]
|
||||
return self.ip_address is None or any(metrics.is_malformed() for metrics in sub_metrics)
|
||||
sub_metrics = self.module_info + [self.git_info] + [self.host_info] + [self.build_info]
|
||||
return self.exit_code is None or any(metrics.is_malformed() for metrics in sub_metrics)
|
||||
|
||||
@ -1,19 +0,0 @@
|
||||
from datetime import datetime
|
||||
import logging
|
||||
|
||||
from buildscripts.metrics.metrics_datatypes import ToolingMetrics
|
||||
from buildscripts.metrics.tooling_metrics_utils import save_tooling_metrics, should_collect_metrics
|
||||
|
||||
logger = logging.getLogger('resmoke_tooling_metrics')
|
||||
|
||||
|
||||
def save_resmoke_tooling_metrics(utc_starttime: datetime):
|
||||
try:
|
||||
if not should_collect_metrics():
|
||||
return
|
||||
tooling_metrics = ToolingMetrics.get_resmoke_metrics(utc_starttime)
|
||||
save_tooling_metrics(tooling_metrics)
|
||||
except Exception as exc: # pylint: disable=broad-except
|
||||
logger.warning(
|
||||
"%s\nResmoke Metrics Collection Failed -- this is a non-issue.\nIf this message persists, feel free to reach out to #server-development-platform",
|
||||
exc)
|
||||
@ -1,71 +0,0 @@
|
||||
import atexit
|
||||
import datetime
|
||||
import logging
|
||||
import sys
|
||||
from typing import List
|
||||
|
||||
from buildscripts.metrics.metrics_datatypes import ToolingMetrics
|
||||
from buildscripts.metrics.tooling_metrics_utils import save_tooling_metrics, should_collect_metrics
|
||||
|
||||
logger = logging.getLogger('scons_tooling_metrics')
|
||||
|
||||
|
||||
class SConsExitHook(object):
|
||||
"""Plumb all sys.exit through this object so that we can access the exit code in atexit."""
|
||||
|
||||
def __init__(self):
|
||||
self.exit_code = None
|
||||
self._orig_exit = sys.exit
|
||||
|
||||
def __del__(self):
|
||||
sys.exit = self._orig_exit
|
||||
|
||||
def initialize(self):
|
||||
sys.exit = self.exit
|
||||
|
||||
def exit(self, code=0):
|
||||
self.exit_code = code
|
||||
self._orig_exit(code)
|
||||
|
||||
|
||||
# This method should only be used when registered on atexit
|
||||
def _save_scons_tooling_metrics(
|
||||
utc_starttime: datetime,
|
||||
env_vars: "SCons.Variables.Variables",
|
||||
env: "SCons.Script.SConscript.SConsEnvironment",
|
||||
parser: "SCons.Script.SConsOptions.SConsOptionParser",
|
||||
args: List[str],
|
||||
exit_hook: SConsExitHook,
|
||||
):
|
||||
"""Save SCons tooling metrics to atlas cluster."""
|
||||
try:
|
||||
if not should_collect_metrics():
|
||||
return
|
||||
tooling_metrics = ToolingMetrics.get_scons_metrics(utc_starttime, env_vars, env, parser,
|
||||
args, exit_hook.exit_code)
|
||||
save_tooling_metrics(tooling_metrics)
|
||||
except Exception as exc: # pylint: disable=broad-except
|
||||
logger.warning(
|
||||
"%sSCons Metrics Collection Failed -- this is a non-issue.\nIf this message persists, feel free to reach out to #server-development-platform",
|
||||
exc)
|
||||
|
||||
|
||||
def setup_scons_metrics_collection_atexit(
|
||||
utc_starttime: datetime,
|
||||
env_vars: "SCons.Variables.Variables",
|
||||
env: "SCons.Script.SConscript.SConsEnvironment",
|
||||
parser: "SCons.Script.SConsOptions.SConsOptionParser",
|
||||
args: List[str],
|
||||
) -> None:
|
||||
"""Register an atexit method for scons metrics collection."""
|
||||
scons_exit_hook = SConsExitHook()
|
||||
scons_exit_hook.initialize()
|
||||
atexit.register(
|
||||
_save_scons_tooling_metrics,
|
||||
utc_starttime,
|
||||
env_vars,
|
||||
env,
|
||||
parser,
|
||||
args,
|
||||
scons_exit_hook,
|
||||
)
|
||||
36
buildscripts/metrics/tooling_exit_hook.py
Normal file
36
buildscripts/metrics/tooling_exit_hook.py
Normal file
@ -0,0 +1,36 @@
|
||||
import sys
|
||||
|
||||
# pylint: disable=invalid-name
|
||||
# pylint: disable=redefined-outer-name
|
||||
|
||||
|
||||
# DO NOT INITIALIZE DIRECTLY -- This is intended to be a singleton.
|
||||
class _ExitHook(object):
|
||||
"""Plumb all sys.exit through this object so that we can access the exit code in atexit."""
|
||||
|
||||
def __init__(self):
|
||||
self.exit_code = 0
|
||||
self._orig_exit = sys.exit
|
||||
sys.exit = self.exit
|
||||
|
||||
def __del__(self):
|
||||
sys.exit = self._orig_exit
|
||||
|
||||
def exit(self, code=0):
|
||||
self.exit_code = code
|
||||
self._orig_exit(code)
|
||||
|
||||
|
||||
SINGLETON_TOOLING_METRICS_EXIT_HOOK = None
|
||||
|
||||
|
||||
# Always use this method when initializing _ExitHook -- This guarantees you are using the singleton
|
||||
# initialize the exit hook as early as possible to ensure we capture the error.
|
||||
def initialize_exit_hook() -> None:
|
||||
"""Initialize the exit hook."""
|
||||
try:
|
||||
if not SINGLETON_TOOLING_METRICS_EXIT_HOOK:
|
||||
SINGLETON_TOOLING_METRICS_EXIT_HOOK = _ExitHook()
|
||||
except UnboundLocalError as _:
|
||||
SINGLETON_TOOLING_METRICS_EXIT_HOOK = _ExitHook()
|
||||
return SINGLETON_TOOLING_METRICS_EXIT_HOOK
|
||||
@ -1,20 +1,17 @@
|
||||
import atexit
|
||||
import logging
|
||||
import os
|
||||
import asyncio
|
||||
from typing import Optional
|
||||
from git import Repo
|
||||
from typing import Any, Callable, Dict
|
||||
import pymongo
|
||||
|
||||
from buildscripts.metrics.metrics_datatypes import ToolingMetrics
|
||||
|
||||
logger = logging.getLogger('tooling_metrics_utils')
|
||||
logger = logging.getLogger('tooling_metrics')
|
||||
|
||||
INTERNAL_TOOLING_METRICS_HOSTNAME = "mongodb+srv://dev-metrics-pl-0.kewhj.mongodb.net"
|
||||
INTERNAL_TOOLING_METRICS_USERNAME = "internal_tooling_user"
|
||||
INTERNAL_TOOLING_METRICS_PASSWORD = "internal_tooling_user"
|
||||
|
||||
|
||||
def _get_internal_tooling_metrics_client():
|
||||
def _get_internal_tooling_metrics_client() -> pymongo.MongoClient:
|
||||
"""Retrieve client for internal MongoDB tooling metrics cluster."""
|
||||
return pymongo.MongoClient(
|
||||
host=INTERNAL_TOOLING_METRICS_HOSTNAME,
|
||||
@ -24,28 +21,20 @@ def _get_internal_tooling_metrics_client():
|
||||
serverSelectionTimeoutMS=1000,
|
||||
connectTimeoutMS=1000,
|
||||
waitQueueTimeoutMS=1000,
|
||||
retryWrites=False,
|
||||
)
|
||||
|
||||
|
||||
EXPECTED_TOOLCHAIN_LOCATION = "/opt/mongodbtoolchain"
|
||||
|
||||
|
||||
def _toolchain_exists() -> bool:
|
||||
"""Check if the internal MongoDB toolchain exists."""
|
||||
return os.path.exists(EXPECTED_TOOLCHAIN_LOCATION)
|
||||
|
||||
|
||||
def _git_user_exists() -> Optional[str]:
|
||||
"""Check if a git user email exists."""
|
||||
try:
|
||||
return Repo('.').config_reader().get_value("user", "email", None)
|
||||
except Exception: # pylint: disable=broad-except
|
||||
return None
|
||||
MONGOD_INTENRAL_DISTRO_FILEPATH = '/etc/mongodb-distro-name'
|
||||
|
||||
|
||||
def _is_virtual_workstation() -> bool:
|
||||
"""Detect whether this is a MongoDB internal virtual workstation."""
|
||||
return _toolchain_exists() and _git_user_exists()
|
||||
try:
|
||||
with open(MONGOD_INTENRAL_DISTRO_FILEPATH, 'r') as file:
|
||||
return file.read().strip() == 'ubuntu1804-workstation'
|
||||
except Exception as _: # pylint: disable=broad-except
|
||||
return False
|
||||
|
||||
|
||||
TOOLING_METRICS_OPT_OUT = "TOOLING_METRICS_OPT_OUT"
|
||||
@ -56,26 +45,32 @@ def _has_metrics_opt_out() -> bool:
|
||||
return os.environ.get(TOOLING_METRICS_OPT_OUT, None) == '1'
|
||||
|
||||
|
||||
def should_collect_metrics() -> bool:
|
||||
def _should_collect_metrics() -> bool:
|
||||
"""Determine whether to collect tooling metrics."""
|
||||
return _is_virtual_workstation() and not _has_metrics_opt_out()
|
||||
|
||||
|
||||
async def _save_metrics(metrics: ToolingMetrics) -> None:
|
||||
"""Save tooling metrics data."""
|
||||
client = _get_internal_tooling_metrics_client()
|
||||
client.metrics.tooling_metrics.insert_one(metrics.dict())
|
||||
|
||||
|
||||
def save_tooling_metrics(tooling_metrics: ToolingMetrics) -> None:
|
||||
"""Persist tooling metrics data to MongoDB Internal Atlas Cluster."""
|
||||
# DO NOT USE DIRECTLY -- This is only to be used when metrics collection is registered atexit
|
||||
def _save_metrics(
|
||||
generate_metrics_function: Callable,
|
||||
generate_metrics_args: Dict[str, Any],
|
||||
) -> None:
|
||||
"""Save metrics to the atlas cluster."""
|
||||
try:
|
||||
asyncio.run(asyncio.wait_for(_save_metrics(tooling_metrics), timeout=1.0))
|
||||
except asyncio.TimeoutError as exc:
|
||||
logger.warning(
|
||||
"%s\nTimeout: Tooling metrics collection is not available -- this is a non-issue.\nIf this message persists, feel free to reach out to #server-development-platform",
|
||||
exc)
|
||||
client = _get_internal_tooling_metrics_client()
|
||||
metrics = generate_metrics_function(**generate_metrics_args)
|
||||
client.metrics.tooling_metrics.insert_one(metrics.dict())
|
||||
except Exception as exc: # pylint: disable=broad-except
|
||||
logger.warning(
|
||||
"%s\nUnexpected: Tooling metrics collection is not available -- this is a non-issue.\nIf this message persists, feel free to reach out to #server-development-platform",
|
||||
"%s\n\nInternal Metrics Collection Failed -- this is a non-issue.\nIf this message persists, feel free to reach out to #server-dev-platform",
|
||||
exc)
|
||||
|
||||
|
||||
# This is the only util that should be used externally
|
||||
def register_metrics_collection_atexit(
|
||||
generate_metrics_function: Callable,
|
||||
generate_metrics_args: Dict[str, Any],
|
||||
) -> None:
|
||||
"""Register metrics collection on atexit."""
|
||||
if _should_collect_metrics():
|
||||
atexit.register(_save_metrics, generate_metrics_function, generate_metrics_args)
|
||||
|
||||
@ -3,7 +3,6 @@
|
||||
import json
|
||||
import sys
|
||||
from collections import namedtuple
|
||||
from datetime import datetime, timedelta
|
||||
from statistics import mean
|
||||
from typing import Dict, List
|
||||
|
||||
@ -13,7 +12,8 @@ import structlog
|
||||
from buildscripts.resmokelib.testing.report import TestInfo, TestReport
|
||||
from buildscripts.resmokelib.utils import get_task_name_without_suffix
|
||||
from buildscripts.util.cmdutils import enable_logging
|
||||
from evergreen import RetryingEvergreenApi, TestStats
|
||||
|
||||
from buildscripts.util.teststats import HistoricTaskData, HistoricalTestInformation
|
||||
|
||||
LOGGER = structlog.get_logger("buildscripts.resmoke_tests_runtime_validate")
|
||||
|
||||
@ -34,17 +34,12 @@ def parse_resmoke_report(report_file: str) -> List[TestInfo]:
|
||||
return [test_info for test_info in test_report.test_infos if "jstests" in test_info.test_file]
|
||||
|
||||
|
||||
def get_historic_stats(evg_api_config: str, project_id: str, test_files: List[str], task_name: str,
|
||||
build_variant: str) -> List[TestStats]:
|
||||
def get_historic_stats(project_id: str, task_name: str,
|
||||
build_variant: str) -> List[HistoricalTestInformation]:
|
||||
"""Get historic test stats."""
|
||||
evg_api = RetryingEvergreenApi.get_api(config_file=evg_api_config)
|
||||
before_date = datetime.today()
|
||||
after_date = before_date - timedelta(days=LOOK_BACK_NUM_DAYS)
|
||||
base_task_name = get_task_name_without_suffix(task_name, build_variant).replace(
|
||||
BURN_IN_PREFIX, "")
|
||||
return evg_api.test_stats_by_project(project_id=project_id, after_date=after_date,
|
||||
before_date=before_date, tests=test_files,
|
||||
tasks=[base_task_name], variants=[build_variant])
|
||||
return HistoricTaskData.get_stats_from_s3(project_id, base_task_name, build_variant)
|
||||
|
||||
|
||||
def make_stats_map(stats: List[_TestData]) -> Dict[str, List[float]]:
|
||||
@ -63,13 +58,10 @@ def make_stats_map(stats: List[_TestData]) -> Dict[str, List[float]]:
|
||||
@click.command()
|
||||
@click.option("--resmoke-report-file", type=str, required=True,
|
||||
help="Location of resmoke's report JSON file.")
|
||||
@click.option("--evg-api-config", type=str, required=True,
|
||||
help="Location of evergreen api configuration.")
|
||||
@click.option("--project-id", type=str, required=True, help="Evergreen project id.")
|
||||
@click.option("--build-variant", type=str, required=True, help="Evergreen build variant name.")
|
||||
@click.option("--task-name", type=str, required=True, help="Evergreen task name.")
|
||||
def main(resmoke_report_file: str, evg_api_config: str, project_id: str, build_variant: str,
|
||||
task_name: str) -> None:
|
||||
def main(resmoke_report_file: str, project_id: str, build_variant: str, task_name: str) -> None:
|
||||
"""Compare resmoke tests runtime with historic stats."""
|
||||
enable_logging(verbose=False)
|
||||
|
||||
@ -79,10 +71,9 @@ def main(resmoke_report_file: str, evg_api_config: str, project_id: str, build_v
|
||||
for test_info in current_test_infos
|
||||
])
|
||||
|
||||
historic_stats = get_historic_stats(evg_api_config, project_id, list(current_stats_map.keys()),
|
||||
task_name, build_variant)
|
||||
historic_stats = get_historic_stats(project_id, task_name, build_variant)
|
||||
historic_stats_map = make_stats_map([
|
||||
_TestData(test_stats.test_file, test_stats.avg_duration_pass)
|
||||
_TestData(test_stats.test_name, test_stats.avg_duration_pass)
|
||||
for test_stats in historic_stats
|
||||
])
|
||||
|
||||
|
||||
@ -4,7 +4,9 @@ from datetime import datetime
|
||||
import time
|
||||
import os
|
||||
import psutil
|
||||
from buildscripts.metrics.resmoke_tooling_metrics import save_resmoke_tooling_metrics
|
||||
from buildscripts.metrics.metrics_datatypes import ResmokeToolingMetrics
|
||||
from buildscripts.metrics.tooling_exit_hook import initialize_exit_hook
|
||||
from buildscripts.metrics.tooling_metrics_utils import register_metrics_collection_atexit
|
||||
from buildscripts.resmokelib import parser
|
||||
|
||||
|
||||
@ -25,7 +27,8 @@ def main(argv):
|
||||
"For example: resmoke.py run -h\n"
|
||||
"Note: bisect and setup-multiversion subcommands have been moved to db-contrib-tool (https://github.com/10gen/db-contrib-tool#readme).\n"
|
||||
)
|
||||
try:
|
||||
subcommand.execute()
|
||||
finally:
|
||||
save_resmoke_tooling_metrics(datetime.utcfromtimestamp(__start_time))
|
||||
register_metrics_collection_atexit(ResmokeToolingMetrics.generate_metrics, {
|
||||
"utc_starttime": datetime.utcfromtimestamp(__start_time),
|
||||
"exit_hook": initialize_exit_hook()
|
||||
})
|
||||
subcommand.execute()
|
||||
|
||||
@ -92,8 +92,9 @@ DEFAULTS = {
|
||||
"report_failure_status": "fail",
|
||||
"report_file": None,
|
||||
"run_all_feature_flag_tests": False,
|
||||
"run_all_feature_flags_no_tests": False,
|
||||
"run_no_feature_flag_tests": False,
|
||||
"additional_feature_flags": None,
|
||||
"additional_feature_flags_file": None,
|
||||
"seed": int(time.time() * 256), # Taken from random.py code in Python 2.7.
|
||||
"service_executor": None,
|
||||
"shell_conn_string": None,
|
||||
@ -379,8 +380,11 @@ INSTALL_DIR = None
|
||||
# Whether to run tests for feature flags.
|
||||
RUN_ALL_FEATURE_FLAG_TESTS = None
|
||||
|
||||
# Whether to run the server with feature flags. Defaults to true if `RUN_ALL_FEATURE_FLAG_TESTS` is true.
|
||||
RUN_ALL_FEATURE_FLAGS = None
|
||||
# Whether to run the tests with enabled feature flags
|
||||
RUN_NO_FEATURE_FLAG_TESTS = None
|
||||
|
||||
# the path to a file containing feature flags
|
||||
ADDITIONAL_FEATURE_FLAGS_FILE = None
|
||||
|
||||
# List of enabled feature flags.
|
||||
ENABLED_FEATURE_FLAGS = []
|
||||
|
||||
@ -15,6 +15,7 @@ import shlex
|
||||
|
||||
import pymongo.uri_parser
|
||||
|
||||
from buildscripts.idl import gen_all_feature_flag_list
|
||||
from buildscripts.idl.lib import ALL_FEATURE_FLAG_FILE
|
||||
|
||||
from buildscripts.resmokelib import config as _config
|
||||
@ -52,14 +53,9 @@ def _validate_options(parser, args):
|
||||
"Cannot use --replayFile with additional test files listed on the command line invocation."
|
||||
)
|
||||
|
||||
if args.run_all_feature_flag_tests or args.run_all_feature_flags_no_tests:
|
||||
if not os.path.isfile(ALL_FEATURE_FLAG_FILE):
|
||||
parser.error(
|
||||
"To run tests with all feature flags, the %s file must exist and be placed in"
|
||||
" your working directory. The file can be downloaded from the artifacts tarball"
|
||||
" in Evergreen. Alternatively, if you know which feature flags you want to enable,"
|
||||
" you can use the --additionalFeatureFlags command line argument" %
|
||||
ALL_FEATURE_FLAG_FILE)
|
||||
if args.additional_feature_flags_file and not os.path.isfile(
|
||||
args.additional_feature_flags_file):
|
||||
parser.error("The specified additional feature flags file does not exist.")
|
||||
|
||||
def get_set_param_errors(process_params):
|
||||
agg_set_params = collections.defaultdict(list)
|
||||
@ -185,28 +181,36 @@ be invoked as either:
|
||||
- buildscripts/resmoke.py --installDir {shlex.quote(user_config['install_dir'])}""")
|
||||
raise RuntimeError(err)
|
||||
|
||||
def process_feature_flag_file(path):
|
||||
with open(path) as fd:
|
||||
return fd.read().split()
|
||||
|
||||
def setup_feature_flags():
|
||||
_config.RUN_ALL_FEATURE_FLAG_TESTS = config.pop("run_all_feature_flag_tests")
|
||||
_config.RUN_ALL_FEATURE_FLAGS = config.pop("run_all_feature_flags_no_tests")
|
||||
_config.RUN_NO_FEATURE_FLAG_TESTS = config.pop("run_no_feature_flag_tests")
|
||||
_config.ADDITIONAL_FEATURE_FLAGS_FILE = config.pop("additional_feature_flags_file")
|
||||
|
||||
# Running all feature flag tests implies running the fixtures with feature flags.
|
||||
if _config.RUN_ALL_FEATURE_FLAG_TESTS:
|
||||
_config.RUN_ALL_FEATURE_FLAGS = True
|
||||
print("Generating: ", ALL_FEATURE_FLAG_FILE)
|
||||
gen_all_feature_flag_list.gen_all_feature_flags_file()
|
||||
|
||||
all_ff = []
|
||||
enabled_feature_flags = []
|
||||
try:
|
||||
with open(ALL_FEATURE_FLAG_FILE) as fd:
|
||||
all_ff = fd.read().split()
|
||||
all_ff = process_feature_flag_file(ALL_FEATURE_FLAG_FILE)
|
||||
except FileNotFoundError:
|
||||
# If we ask resmoke to run with all feature flags, the feature flags file
|
||||
# needs to exist.
|
||||
if _config.RUN_ALL_FEATURE_FLAGS:
|
||||
if _config.RUN_ALL_FEATURE_FLAG_TESTS or _config.RUN_NO_FEATURE_FLAG_TESTS:
|
||||
raise
|
||||
|
||||
if _config.RUN_ALL_FEATURE_FLAGS:
|
||||
if _config.RUN_ALL_FEATURE_FLAG_TESTS:
|
||||
enabled_feature_flags = all_ff[:]
|
||||
|
||||
if _config.ADDITIONAL_FEATURE_FLAGS_FILE:
|
||||
enabled_feature_flags.extend(
|
||||
process_feature_flag_file(_config.ADDITIONAL_FEATURE_FLAGS_FILE))
|
||||
|
||||
# Specify additional feature flags from the command line.
|
||||
# Set running all feature flag tests to True if this options is specified.
|
||||
additional_feature_flags = _tags_from_list(config.pop("additional_feature_flags"))
|
||||
@ -231,7 +235,7 @@ be invoked as either:
|
||||
_config.EXCLUDE_WITH_ANY_TAGS.extend(
|
||||
utils.default_if_none(_tags_from_list(config.pop("exclude_with_any_tags")), []))
|
||||
|
||||
if _config.RUN_ALL_FEATURE_FLAGS and not _config.RUN_ALL_FEATURE_FLAG_TESTS:
|
||||
if _config.RUN_NO_FEATURE_FLAG_TESTS:
|
||||
# Don't run any feature flag tests.
|
||||
_config.EXCLUDE_WITH_ANY_TAGS.extend(all_feature_flags)
|
||||
else:
|
||||
|
||||
@ -832,15 +832,19 @@ class RunPlugin(PluginInterface):
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--runAllFeatureFlagsNoTests", dest="run_all_feature_flags_no_tests",
|
||||
action="store_true", help=
|
||||
"Run MongoDB servers with all feature flags enabled but don't run any tests tagged with these feature flags; used for multiversion suites"
|
||||
)
|
||||
"--runNoFeatureFlagTests", dest="run_no_feature_flag_tests", action="store_true",
|
||||
help=("Do not run any tests tagged with enabled feature flags."
|
||||
" This argument has precedence over --runAllFeatureFlagTests"
|
||||
"; used for multiversion suites"))
|
||||
|
||||
parser.add_argument("--additionalFeatureFlags", dest="additional_feature_flags",
|
||||
action="append", metavar="featureFlag1, featureFlag2, ...",
|
||||
help="Additional feature flags")
|
||||
|
||||
parser.add_argument("--additionalFeatureFlagsFile", dest="additional_feature_flags_file",
|
||||
action="store", metavar="FILE",
|
||||
help="The path to a file with feature flags, delimited by newlines.")
|
||||
|
||||
parser.add_argument("--maxTestQueueSize", type=int, dest="max_test_queue_size",
|
||||
help=argparse.SUPPRESS)
|
||||
|
||||
@ -1188,15 +1192,6 @@ def to_local_args(input_args=None):
|
||||
if origin_suite is not None:
|
||||
setattr(parsed_args, "suite_files", origin_suite)
|
||||
|
||||
# Replace --runAllFeatureFlagTests with an explicit list of feature flags. The former relies on
|
||||
# all_feature_flags.txt which may not exist in the local dev environment.
|
||||
run_all_feature_flag_tests = getattr(parsed_args, "run_all_feature_flag_tests", None)
|
||||
if run_all_feature_flag_tests is not None:
|
||||
setattr(parsed_args, "additional_feature_flags", config.ENABLED_FEATURE_FLAGS)
|
||||
del parsed_args.run_all_feature_flag_tests
|
||||
|
||||
del parsed_args.run_all_feature_flags_no_tests
|
||||
|
||||
# The top-level parser has one subparser that contains all subcommand parsers.
|
||||
command_subparser = [
|
||||
action for action in parser._actions # pylint: disable=protected-access
|
||||
|
||||
@ -181,7 +181,7 @@ class MongoDFixture(interface.Fixture):
|
||||
|
||||
def get_driver_connection_url(self):
|
||||
"""Return the driver connection URL."""
|
||||
return "mongodb://" + self.get_internal_connection_string()
|
||||
return "mongodb://" + self.get_internal_connection_string() + "/?directConnection=true"
|
||||
|
||||
|
||||
# The below parameters define the default 'logComponentVerbosity' object passed to mongod processes
|
||||
@ -193,15 +193,16 @@ class MongoDFixture(interface.Fixture):
|
||||
# The default verbosity setting for any tests that are not started with an Evergreen task id. This
|
||||
# will apply to any tests run locally.
|
||||
DEFAULT_MONGOD_LOG_COMPONENT_VERBOSITY = {
|
||||
"replication": {"rollback": 2}, "sharding": {"migration": 2}, "transaction": 4,
|
||||
"tenantMigration": 4
|
||||
"replication": {"rollback": 2}, "sharding": {"migration": 2, "rangeDeleter": 2},
|
||||
"transaction": 4, "tenantMigration": 4
|
||||
}
|
||||
|
||||
# The default verbosity setting for any mongod processes running in Evergreen i.e. started with an
|
||||
# Evergreen task id.
|
||||
DEFAULT_EVERGREEN_MONGOD_LOG_COMPONENT_VERBOSITY = {
|
||||
"replication": {"election": 4, "heartbeats": 2, "initialSync": 2, "rollback": 2},
|
||||
"sharding": {"migration": 2}, "storage": {"recovery": 2}, "transaction": 4, "tenantMigration": 4
|
||||
"replication": {"election": 4, "heartbeats": 2, "initialSync": 2,
|
||||
"rollback": 2}, "sharding": {"migration": 2, "rangeDeleter": 2},
|
||||
"storage": {"recovery": 2}, "transaction": 4, "tenantMigration": 4
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -1,41 +1,42 @@
|
||||
"""Unit tests for timeout_service.py."""
|
||||
import random
|
||||
import unittest
|
||||
from datetime import datetime, timedelta
|
||||
from unittest.mock import MagicMock
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from requests.exceptions import HTTPError
|
||||
from evergreen import EvergreenApi
|
||||
|
||||
import buildscripts.timeouts.timeout_service as under_test
|
||||
from buildscripts.resmoke_proxy.resmoke_proxy import ResmokeProxyService
|
||||
from buildscripts.util.teststats import HistoricTaskData
|
||||
from buildscripts.util.teststats import HistoricTaskData, HistoricTestInfo
|
||||
|
||||
# pylint: disable=invalid-name,protected-access
|
||||
|
||||
NS = "buildscripts.timeouts.timeout_service"
|
||||
|
||||
def build_mock_service(evg_api=None, resmoke_proxy=None):
|
||||
end_date = datetime.now()
|
||||
start_date = end_date - timedelta(weeks=2)
|
||||
timeout_settings = under_test.TimeoutSettings(
|
||||
end_date=end_date,
|
||||
start_date=start_date,
|
||||
)
|
||||
|
||||
def ns(relative_name): # pylint: disable=invalid-name
|
||||
"""Return a full name from a name relative to the test module"s name space."""
|
||||
return NS + "." + relative_name
|
||||
|
||||
|
||||
def build_mock_service(resmoke_proxy=None):
|
||||
return under_test.TimeoutService(
|
||||
evg_api=evg_api if evg_api else MagicMock(spec_set=EvergreenApi),
|
||||
resmoke_proxy=resmoke_proxy if resmoke_proxy else MagicMock(spec_set=ResmokeProxyService),
|
||||
timeout_settings=timeout_settings)
|
||||
resmoke_proxy=resmoke_proxy if resmoke_proxy else MagicMock(spec_set=ResmokeProxyService))
|
||||
|
||||
|
||||
def tst_stat_mock(file, duration, pass_count):
|
||||
return MagicMock(test_file=file, avg_duration_pass=duration, num_pass=pass_count)
|
||||
return MagicMock(test_name=file, avg_duration_pass=duration, num_pass=pass_count, hooks=[])
|
||||
|
||||
|
||||
def tst_runtime_mock(file, duration, pass_count):
|
||||
return MagicMock(test_name=file, avg_duration_pass=duration, num_pass=pass_count)
|
||||
|
||||
|
||||
class TestGetTimeoutEstimate(unittest.TestCase):
|
||||
def test_no_stats_should_return_default_timeout(self):
|
||||
mock_evg_api = MagicMock(spec_set=EvergreenApi)
|
||||
mock_evg_api.test_stats_by_project.return_value = []
|
||||
timeout_service = build_mock_service(evg_api=mock_evg_api)
|
||||
@patch(ns("HistoricTaskData.from_s3"))
|
||||
def test_no_stats_should_return_default_timeout(self, from_s3_mock: MagicMock):
|
||||
timeout_service = build_mock_service()
|
||||
from_s3_mock.return_value = []
|
||||
timeout_params = under_test.TimeoutParams(
|
||||
evg_project="my project",
|
||||
build_variant="bv",
|
||||
@ -48,13 +49,17 @@ class TestGetTimeoutEstimate(unittest.TestCase):
|
||||
|
||||
self.assertFalse(timeout.is_specified())
|
||||
|
||||
def test_a_test_with_missing_history_should_cause_a_default_timeout(self):
|
||||
mock_evg_api = MagicMock(spec_set=EvergreenApi)
|
||||
test_stats = [tst_stat_mock(f"test_{i}.js", 60, 1) for i in range(30)]
|
||||
mock_evg_api.test_stats_by_project.return_value = test_stats
|
||||
@patch(ns("HistoricTaskData.from_s3"))
|
||||
def test_a_test_with_missing_history_should_cause_a_default_timeout(
|
||||
self, from_s3_mock: MagicMock):
|
||||
test_stats = [
|
||||
HistoricTestInfo(test_name=f"test_{i}.js", avg_duration=60, num_pass=1, hooks=[])
|
||||
for i in range(30)
|
||||
]
|
||||
from_s3_mock.return_value = HistoricTaskData(test_stats)
|
||||
mock_resmoke_proxy = MagicMock(spec_set=ResmokeProxyService)
|
||||
mock_resmoke_proxy.list_tests.return_value = ["test_with_no_stats.js"]
|
||||
timeout_service = build_mock_service(evg_api=mock_evg_api, resmoke_proxy=mock_resmoke_proxy)
|
||||
timeout_service = build_mock_service(resmoke_proxy=mock_resmoke_proxy)
|
||||
timeout_params = under_test.TimeoutParams(
|
||||
evg_project="my project",
|
||||
build_variant="bv",
|
||||
@ -67,14 +72,19 @@ class TestGetTimeoutEstimate(unittest.TestCase):
|
||||
|
||||
self.assertFalse(timeout.is_specified())
|
||||
|
||||
def test_a_test_with_zero_runtime_history_should_cause_a_default_timeout(self):
|
||||
mock_evg_api = MagicMock(spec_set=EvergreenApi)
|
||||
test_stats = [tst_stat_mock(f"test_{i}.js", 60, 1) for i in range(30)]
|
||||
test_stats.append(tst_stat_mock("zero.js", 0.0, 1))
|
||||
mock_evg_api.test_stats_by_project.return_value = test_stats
|
||||
@patch(ns("HistoricTaskData.from_s3"))
|
||||
def test_a_test_with_zero_runtime_history_should_cause_a_default_timeout(
|
||||
self, from_s3_mock: MagicMock):
|
||||
test_stats = [
|
||||
HistoricTestInfo(test_name=f"test_{i}.js", avg_duration=60, num_pass=1, hooks=[])
|
||||
for i in range(30)
|
||||
]
|
||||
test_stats.append(
|
||||
HistoricTestInfo(test_name="zero.js", avg_duration=0.0, num_pass=1, hooks=[]))
|
||||
from_s3_mock.return_value = HistoricTaskData(test_stats)
|
||||
mock_resmoke_proxy = MagicMock(spec_set=ResmokeProxyService)
|
||||
mock_resmoke_proxy.list_tests.return_value = [ts.test_file for ts in test_stats]
|
||||
timeout_service = build_mock_service(evg_api=mock_evg_api, resmoke_proxy=mock_resmoke_proxy)
|
||||
mock_resmoke_proxy.list_tests.return_value = [ts.test_name for ts in test_stats]
|
||||
timeout_service = build_mock_service(resmoke_proxy=mock_resmoke_proxy)
|
||||
timeout_params = under_test.TimeoutParams(
|
||||
evg_project="my project",
|
||||
build_variant="bv",
|
||||
@ -87,15 +97,19 @@ class TestGetTimeoutEstimate(unittest.TestCase):
|
||||
|
||||
self.assertFalse(timeout.is_specified())
|
||||
|
||||
def test_all_tests_with_runtime_history_should_use_custom_timeout(self):
|
||||
mock_evg_api = MagicMock(spec_set=EvergreenApi)
|
||||
@patch(ns("HistoricTaskData.from_s3"))
|
||||
def test_all_tests_with_runtime_history_should_use_custom_timeout(self,
|
||||
from_s3_mock: MagicMock):
|
||||
n_tests = 30
|
||||
test_runtime = 600
|
||||
test_stats = [tst_stat_mock(f"test_{i}.js", test_runtime, 1) for i in range(n_tests)]
|
||||
mock_evg_api.test_stats_by_project.return_value = test_stats
|
||||
test_stats = [
|
||||
HistoricTestInfo(test_name=f"test_{i}.js", avg_duration=test_runtime, num_pass=1,
|
||||
hooks=[]) for i in range(n_tests)
|
||||
]
|
||||
from_s3_mock.return_value = HistoricTaskData(test_stats)
|
||||
mock_resmoke_proxy = MagicMock(spec_set=ResmokeProxyService)
|
||||
mock_resmoke_proxy.list_tests.return_value = [ts.test_file for ts in test_stats]
|
||||
timeout_service = build_mock_service(evg_api=mock_evg_api, resmoke_proxy=mock_resmoke_proxy)
|
||||
mock_resmoke_proxy.list_tests.return_value = [ts.test_name for ts in test_stats]
|
||||
timeout_service = build_mock_service(resmoke_proxy=mock_resmoke_proxy)
|
||||
timeout_params = under_test.TimeoutParams(
|
||||
evg_project="my project",
|
||||
build_variant="bv",
|
||||
@ -149,10 +163,10 @@ class TestGetTaskHookOverhead(unittest.TestCase):
|
||||
|
||||
|
||||
class TestLookupHistoricStats(unittest.TestCase):
|
||||
def test_no_stats_from_evergreen_should_return_none(self):
|
||||
mock_evg_api = MagicMock(spec_set=EvergreenApi)
|
||||
mock_evg_api.test_stats_by_project.return_value = []
|
||||
timeout_service = build_mock_service(evg_api=mock_evg_api)
|
||||
@patch(ns("HistoricTaskData.from_s3"))
|
||||
def test_no_stats_from_evergreen_should_return_none(self, from_s3_mock: MagicMock):
|
||||
from_s3_mock.return_value = None
|
||||
timeout_service = build_mock_service()
|
||||
timeout_params = under_test.TimeoutParams(
|
||||
evg_project="my project",
|
||||
build_variant="bv",
|
||||
@ -165,10 +179,10 @@ class TestLookupHistoricStats(unittest.TestCase):
|
||||
|
||||
self.assertIsNone(stats)
|
||||
|
||||
def test_errors_from_evergreen_should_return_none(self):
|
||||
mock_evg_api = MagicMock(spec_set=EvergreenApi)
|
||||
mock_evg_api.test_stats_by_project.side_effect = HTTPError("failed to connect")
|
||||
timeout_service = build_mock_service(evg_api=mock_evg_api)
|
||||
@patch(ns("HistoricTaskData.from_s3"))
|
||||
def test_errors_from_evergreen_should_return_none(self, from_s3_mock: MagicMock):
|
||||
from_s3_mock.side_effect = HTTPError("failed to connect")
|
||||
timeout_service = build_mock_service()
|
||||
timeout_params = under_test.TimeoutParams(
|
||||
evg_project="my project",
|
||||
build_variant="bv",
|
||||
@ -181,11 +195,11 @@ class TestLookupHistoricStats(unittest.TestCase):
|
||||
|
||||
self.assertIsNone(stats)
|
||||
|
||||
def test_stats_from_evergreen_should_return_the_stats(self):
|
||||
mock_evg_api = MagicMock(spec_set=EvergreenApi)
|
||||
@patch(ns("HistoricTaskData.from_s3"))
|
||||
def test_stats_from_evergreen_should_return_the_stats(self, from_s3_mock: MagicMock):
|
||||
test_stats = [tst_stat_mock(f"test_{i}.js", 60, 1) for i in range(100)]
|
||||
mock_evg_api.test_stats_by_project.return_value = test_stats
|
||||
timeout_service = build_mock_service(evg_api=mock_evg_api)
|
||||
from_s3_mock.return_value = HistoricTaskData(test_stats)
|
||||
timeout_service = build_mock_service()
|
||||
timeout_params = under_test.TimeoutParams(
|
||||
evg_project="my project",
|
||||
build_variant="bv",
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
"""Unit tests for metrics_datatypes.py."""
|
||||
from datetime import datetime
|
||||
import os
|
||||
import sys
|
||||
import unittest
|
||||
from unittest.mock import patch
|
||||
|
||||
@ -9,6 +11,12 @@ import buildscripts.metrics.metrics_datatypes as under_test
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
|
||||
# Metrics collection is not supported for Windows
|
||||
if os.name == "nt":
|
||||
sys.exit()
|
||||
|
||||
MOCK_EXIT_HOOK = MagicMock(exit_code=0)
|
||||
|
||||
|
||||
@patch("buildscripts.metrics.metrics_datatypes.BuildInfo._get_scons_artifact_dir",
|
||||
return_value='/test')
|
||||
@ -18,94 +26,89 @@ class TestBuildInfo(unittest.TestCase):
|
||||
@patch("buildscripts.metrics.metrics_datatypes.BuildInfo._get_scons_options_dict",
|
||||
return_value={'opt': 'opt'})
|
||||
def test_build_info_valid(self, mock_env, mock_options, mock_artifact_dir):
|
||||
build_info = under_test.BuildInfo.get_scons_build_info(datetime.utcnow(), MagicMock(),
|
||||
MagicMock(), MagicMock(),
|
||||
MagicMock())
|
||||
build_info = under_test.BuildInfo.generate_metrics(datetime.utcnow(), MagicMock(),
|
||||
MagicMock(), MagicMock(), MagicMock())
|
||||
assert not build_info.is_malformed()
|
||||
|
||||
def test_build_info_malformed(self, mock_artifact_dir):
|
||||
build_info = under_test.BuildInfo.get_scons_build_info(datetime.utcnow(), MagicMock(),
|
||||
MagicMock(), MagicMock(),
|
||||
MagicMock())
|
||||
build_info = under_test.BuildInfo.generate_metrics(datetime.utcnow(), MagicMock(),
|
||||
MagicMock(), MagicMock(), MagicMock())
|
||||
assert build_info.is_malformed()
|
||||
|
||||
|
||||
class TestExitInfo(unittest.TestCase):
|
||||
@patch("sys.exc_info", return_value=(None, None, None))
|
||||
def test_resmoke_no_exc_info(self, mock_exc_info):
|
||||
exit_info = under_test.ExitInfo.get_resmoke_exit_info()
|
||||
assert not exit_info.is_malformed()
|
||||
|
||||
@patch("sys.exc_info", return_value=(None, ValueError(), None))
|
||||
def test_resmoke_with_exc_info(self, mock_exc_info):
|
||||
exit_info = under_test.ExitInfo.get_resmoke_exit_info()
|
||||
assert not exit_info.is_malformed()
|
||||
|
||||
def test_scons_exit_info_valid(self):
|
||||
exit_info = under_test.ExitInfo.get_scons_exit_info(0)
|
||||
assert not exit_info.is_malformed()
|
||||
|
||||
def test_scons_exit_info_malformed(self):
|
||||
exit_info = under_test.ExitInfo.get_scons_exit_info('string')
|
||||
assert exit_info.is_malformed()
|
||||
|
||||
|
||||
class TestHostInfo(unittest.TestCase):
|
||||
@patch("buildscripts.metrics.metrics_datatypes.HostInfo._get_memory", side_effect=Exception())
|
||||
def test_host_info_with_exc(self, mock_get_memory):
|
||||
host_info = under_test.HostInfo.get_host_info()
|
||||
host_info = under_test.HostInfo.generate_metrics()
|
||||
assert host_info.is_malformed()
|
||||
|
||||
# Mock this so that it passes when running the 'buildscripts_test' suite on Windows
|
||||
@patch("buildscripts.metrics.metrics_datatypes.HostInfo._get_memory", return_value=30)
|
||||
def test_host_info_no_exc(self, mock_get_memory):
|
||||
host_info = under_test.HostInfo.get_host_info()
|
||||
host_info = under_test.HostInfo.generate_metrics()
|
||||
assert not host_info.is_malformed()
|
||||
|
||||
|
||||
class TestGitInfo(unittest.TestCase):
|
||||
@patch("git.Repo", side_effect=Exception())
|
||||
def test_git_info_with_exc(self, mock_repo):
|
||||
git_info = under_test.GitInfo.get_git_info('.')
|
||||
git_info = under_test.GitInfo.generate_metrics('.')
|
||||
assert git_info.is_malformed()
|
||||
|
||||
def test_git_info_no_exc(self):
|
||||
git_info = under_test.GitInfo.get_git_info('.')
|
||||
git_info = under_test.GitInfo.generate_metrics('.')
|
||||
assert not git_info.is_malformed()
|
||||
|
||||
@patch("git.refs.symbolic.SymbolicReference.is_detached", True)
|
||||
def test_git_info_detached_head(self):
|
||||
git_info = under_test.GitInfo.get_git_info('.')
|
||||
git_info = under_test.GitInfo.generate_metrics('.')
|
||||
assert not git_info.is_malformed()
|
||||
|
||||
|
||||
# Mock this so that it passes when running the 'buildscripts_test' suite on Windows
|
||||
@patch("buildscripts.metrics.metrics_datatypes.HostInfo._get_memory", return_value=30)
|
||||
class TestToolingMetrics(unittest.TestCase):
|
||||
class TestResmokeToolingMetrics(unittest.TestCase):
|
||||
@patch("socket.gethostname", side_effect=Exception())
|
||||
def test_resmoke_tooling_metrics_with_exc(self, mock_gethostname, mock_get_memory):
|
||||
tooling_metrics = under_test.ToolingMetrics.get_resmoke_metrics(datetime.utcnow())
|
||||
def test_resmoke_tooling_metrics_valid(self, mock_gethostname):
|
||||
tooling_metrics = under_test.ResmokeToolingMetrics.generate_metrics(
|
||||
datetime.utcnow(),
|
||||
MOCK_EXIT_HOOK,
|
||||
)
|
||||
assert tooling_metrics.is_malformed()
|
||||
|
||||
def test_resmoke_tooling_metrics_no_exc(self, mock_get_memory):
|
||||
tooling_metrics = under_test.ToolingMetrics.get_resmoke_metrics(datetime.utcnow())
|
||||
def test_resmoke_tooling_metrics_malformed(self):
|
||||
tooling_metrics = under_test.ResmokeToolingMetrics.generate_metrics(
|
||||
datetime.utcnow(),
|
||||
MOCK_EXIT_HOOK,
|
||||
)
|
||||
assert not tooling_metrics.is_malformed()
|
||||
|
||||
|
||||
class TestSConsToolingMetrics(unittest.TestCase):
|
||||
@patch("buildscripts.metrics.metrics_datatypes.BuildInfo._get_scons_artifact_dir",
|
||||
return_value='/test')
|
||||
@patch("buildscripts.metrics.metrics_datatypes.BuildInfo._get_scons_env_vars_dict",
|
||||
return_value={'env': 'env'})
|
||||
@patch("buildscripts.metrics.metrics_datatypes.BuildInfo._get_scons_options_dict",
|
||||
return_value={'opt': 'opt'})
|
||||
def test_scons_tooling_metrics_valid(self, mock_options, mock_env, mock_artifact_dir,
|
||||
mock_get_memory):
|
||||
def test_scons_tooling_metrics_valid(self, mock_options, mock_env, mock_artifact_dir):
|
||||
parser = MagicMock()
|
||||
parser.parse_args = MagicMock(return_value={"opt1": "val1"})
|
||||
tooling_metrics = under_test.ToolingMetrics.get_scons_metrics(
|
||||
datetime.utcnow(), {'env': 'env'}, {'opts': 'opts'}, parser, ['test1', 'test2'], 0)
|
||||
tooling_metrics = under_test.SConsToolingMetrics.generate_metrics(
|
||||
datetime.utcnow(),
|
||||
{'env': 'env'},
|
||||
{'opts': 'opts'},
|
||||
parser,
|
||||
['test1', 'test2'],
|
||||
MOCK_EXIT_HOOK,
|
||||
)
|
||||
assert not tooling_metrics.is_malformed()
|
||||
|
||||
def test_scons_tooling_metrics_malformed(self, mock_get_memory):
|
||||
tooling_metrics = under_test.ToolingMetrics.get_scons_metrics(
|
||||
datetime.utcnow(), {'env': 'env'}, {'opts': 'opts'}, None, [], 0)
|
||||
def test_scons_tooling_metrics_malformed(self):
|
||||
tooling_metrics = under_test.SConsToolingMetrics.generate_metrics(
|
||||
datetime.utcnow(),
|
||||
{'env': 'env'},
|
||||
{'opts': 'opts'},
|
||||
None,
|
||||
[],
|
||||
MOCK_EXIT_HOOK,
|
||||
)
|
||||
assert tooling_metrics.is_malformed()
|
||||
|
||||
@ -3,11 +3,8 @@ import os
|
||||
import sys
|
||||
import unittest
|
||||
from unittest.mock import patch
|
||||
import mongomock
|
||||
import pymongo
|
||||
|
||||
import buildscripts.metrics.resmoke_tooling_metrics as under_test
|
||||
from buildscripts.resmoke import entrypoint as resmoke_entrypoint
|
||||
import buildscripts.resmoke as under_test
|
||||
|
||||
TEST_INTERNAL_TOOLING_METRICS_HOSTNAME = 'mongodb://testing:27017'
|
||||
CURRENT_DATE_TIME = datetime(2022, 10, 4)
|
||||
@ -19,37 +16,32 @@ if os.name == "nt":
|
||||
sys.exit()
|
||||
|
||||
|
||||
@patch("buildscripts.metrics.tooling_metrics_utils.INTERNAL_TOOLING_METRICS_HOSTNAME",
|
||||
TEST_INTERNAL_TOOLING_METRICS_HOSTNAME)
|
||||
@patch("buildscripts.resmokelib.logging.flush._FLUSH_THREAD", None)
|
||||
class TestResmokeMetricsCollection(unittest.TestCase):
|
||||
@mongomock.patch(servers=((TEST_INTERNAL_TOOLING_METRICS_HOSTNAME), ))
|
||||
@patch("buildscripts.metrics.resmoke_tooling_metrics.should_collect_metrics", return_value=True)
|
||||
@patch("atexit.register")
|
||||
class TestResmokeAtExitMetricsCollection(unittest.TestCase):
|
||||
@patch("sys.argv", ['buildscripts/resmoke.py', 'list-suites'])
|
||||
@patch("buildscripts.metrics.tooling_metrics_utils._should_collect_metrics", return_value=True)
|
||||
def test_resmoke_at_exit_metrics_collection(self, mock_should_collect_metrics,
|
||||
mock_atexit_register):
|
||||
under_test.entrypoint()
|
||||
atexit_functions = [call[0][0].__name__ for call in mock_atexit_register.call_args_list]
|
||||
assert "_save_metrics" in atexit_functions
|
||||
|
||||
@patch("sys.argv", ['buildscripts/resmoke.py', 'list-suites'])
|
||||
@patch("buildscripts.metrics.tooling_metrics_utils._should_collect_metrics", return_value=False)
|
||||
def test_no_resmoke_at_exit_metrics_collection(self, mock_should_collect_metrics,
|
||||
mock_atexit_register):
|
||||
under_test.entrypoint()
|
||||
atexit_functions = [call[0][0].__name__ for call in mock_atexit_register.call_args_list]
|
||||
assert "_save_metrics" not in atexit_functions
|
||||
|
||||
@patch("sys.argv", ['buildscripts/resmoke.py', 'run', '--suite', 'buildscripts_test'])
|
||||
@patch("buildscripts.metrics.tooling_metrics_utils._should_collect_metrics", return_value=True)
|
||||
@patch("buildscripts.resmokelib.testing.executor.TestSuiteExecutor._run_tests",
|
||||
side_effect=Exception())
|
||||
def test_resmoke_metrics_collection_exc(self, mock_executor_run, mock_should_collect_metrics):
|
||||
client = pymongo.MongoClient(host=TEST_INTERNAL_TOOLING_METRICS_HOSTNAME)
|
||||
assert not client.metrics.tooling_metrics.find_one()
|
||||
with self.assertRaises(SystemExit):
|
||||
resmoke_entrypoint()
|
||||
assert client.metrics.tooling_metrics.find_one()
|
||||
|
||||
@mongomock.patch(servers=((TEST_INTERNAL_TOOLING_METRICS_HOSTNAME), ))
|
||||
@patch("buildscripts.metrics.resmoke_tooling_metrics.should_collect_metrics", return_value=True)
|
||||
@patch("sys.argv", ['buildscripts/resmoke.py', 'list-suites'])
|
||||
def test_resmoke_metrics_collection(self, mock_should_collect_metrics):
|
||||
client = pymongo.MongoClient(host=TEST_INTERNAL_TOOLING_METRICS_HOSTNAME)
|
||||
assert not client.metrics.tooling_metrics.find_one()
|
||||
resmoke_entrypoint()
|
||||
assert client.metrics.tooling_metrics.find_one()
|
||||
|
||||
@mongomock.patch(servers=((TEST_INTERNAL_TOOLING_METRICS_HOSTNAME), ))
|
||||
@patch("buildscripts.metrics.resmoke_tooling_metrics.should_collect_metrics",
|
||||
return_value=False)
|
||||
@patch("sys.argv", ['buildscripts/resmoke.py', 'list-suites'])
|
||||
def test_no_resmoke_metrics_collection(self, mock_should_collect_metrics):
|
||||
client = pymongo.MongoClient(host=TEST_INTERNAL_TOOLING_METRICS_HOSTNAME)
|
||||
assert not client.metrics.tooling_metrics.find_one()
|
||||
resmoke_entrypoint()
|
||||
assert not client.metrics.tooling_metrics.find_one()
|
||||
def test_resmoke_at_exit_metrics_collection_exc(
|
||||
self, mock_exc_method, mock_should_collect_metrics, mock_atexit_register):
|
||||
with self.assertRaises(SystemExit) as _:
|
||||
under_test.entrypoint()
|
||||
atexit_functions = [call[0][0].__name__ for call in mock_atexit_register.call_args_list]
|
||||
assert "_save_metrics" in atexit_functions
|
||||
|
||||
@ -1,15 +1,8 @@
|
||||
from datetime import datetime
|
||||
import os
|
||||
import sys
|
||||
import unittest
|
||||
from unittest.mock import MagicMock, patch
|
||||
import mongomock
|
||||
import pymongo
|
||||
import buildscripts.metrics.scons_tooling_metrics as under_test
|
||||
from buildscripts.scons import entrypoint as scons_entrypoint
|
||||
|
||||
TEST_INTERNAL_TOOLING_METRICS_HOSTNAME = 'mongodb://testing:27017'
|
||||
CURRENT_DATE_TIME = datetime(2022, 10, 4)
|
||||
from unittest.mock import patch
|
||||
import buildscripts.scons as under_test
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
# pylint: disable=protected-access
|
||||
@ -23,52 +16,29 @@ if os.name == "nt":
|
||||
'buildscripts/scons.py', "CC=/opt/mongodbtoolchain/v3/bin/gcc",
|
||||
"CXX=/opt/mongodbtoolchain/v3/bin/g++", "NINJA_PREFIX=test_success", "--ninja"
|
||||
])
|
||||
@patch("buildscripts.metrics.scons_tooling_metrics.should_collect_metrics", return_value=True)
|
||||
@patch("atexit.register")
|
||||
class TestSconsAtExitMetricsCollection(unittest.TestCase):
|
||||
def test_scons_at_exit_metrics_collection(self, mock_atexit_register,
|
||||
mock_should_collect_metrics):
|
||||
with self.assertRaises(SystemExit) as context:
|
||||
scons_entrypoint()
|
||||
assert context.exception.code == 0
|
||||
@patch("buildscripts.metrics.tooling_metrics_utils._should_collect_metrics", return_value=True)
|
||||
def test_scons_at_exit_metrics_collection(self, mock_should_collect_metrics,
|
||||
mock_atexit_register):
|
||||
with self.assertRaises(SystemExit) as _:
|
||||
under_test.entrypoint()
|
||||
atexit_functions = [call[0][0].__name__ for call in mock_atexit_register.call_args_list]
|
||||
assert "_save_scons_tooling_metrics" in atexit_functions
|
||||
assert "_save_metrics" in atexit_functions
|
||||
|
||||
@patch("buildscripts.metrics.tooling_metrics_utils._should_collect_metrics", return_value=False)
|
||||
def test_no_scons_at_exit_metrics_collection(self, mock_should_collect_metrics,
|
||||
mock_atexit_register):
|
||||
with self.assertRaises(SystemExit) as _:
|
||||
under_test.entrypoint()
|
||||
atexit_functions = [call[0][0].__name__ for call in mock_atexit_register.call_args_list]
|
||||
assert "_save_metrics" not in atexit_functions
|
||||
|
||||
@patch("buildscripts.metrics.tooling_metrics_utils._should_collect_metrics", return_value=True)
|
||||
@patch("buildscripts.moduleconfig.get_module_sconscripts", side_effect=Exception())
|
||||
def test_scons_at_exit_metrics_collection_exc(self, mock_method, mock_atexit_register,
|
||||
mock_should_collect_metrics):
|
||||
with self.assertRaises(SystemExit) as context:
|
||||
scons_entrypoint()
|
||||
assert context.exception.code == 2
|
||||
def test_scons_at_exit_metrics_collection_exc(
|
||||
self, mock_exc_method, mock_should_collect_metrics, mock_atexit_register):
|
||||
with self.assertRaises(SystemExit) as _:
|
||||
under_test.entrypoint()
|
||||
atexit_functions = [call[0][0].__name__ for call in mock_atexit_register.call_args_list]
|
||||
assert "_save_scons_tooling_metrics" in atexit_functions
|
||||
|
||||
|
||||
@patch("buildscripts.metrics.tooling_metrics_utils.INTERNAL_TOOLING_METRICS_HOSTNAME",
|
||||
TEST_INTERNAL_TOOLING_METRICS_HOSTNAME)
|
||||
class TestSconsMetricsCollection(unittest.TestCase):
|
||||
@mongomock.patch(servers=((TEST_INTERNAL_TOOLING_METRICS_HOSTNAME), ))
|
||||
@patch("buildscripts.metrics.scons_tooling_metrics.should_collect_metrics", return_value=True)
|
||||
def test_scons_metrics_collection_success(self, mock_should_collect_metrics):
|
||||
client = pymongo.MongoClient(host=TEST_INTERNAL_TOOLING_METRICS_HOSTNAME)
|
||||
assert not client.metrics.tooling_metrics.find_one()
|
||||
under_test._save_scons_tooling_metrics(CURRENT_DATE_TIME, None, None, None, None,
|
||||
MagicMock(exit_code=0))
|
||||
assert client.metrics.tooling_metrics.find_one()
|
||||
|
||||
@patch("buildscripts.metrics.scons_tooling_metrics.should_collect_metrics", return_value=True)
|
||||
@mongomock.patch(servers=((TEST_INTERNAL_TOOLING_METRICS_HOSTNAME), ))
|
||||
def test_scons_metrics_collection_fail(self, mock_should_collect_metrics):
|
||||
client = pymongo.MongoClient(host=TEST_INTERNAL_TOOLING_METRICS_HOSTNAME)
|
||||
assert not client.metrics.tooling_metrics.find_one()
|
||||
under_test._save_scons_tooling_metrics(None, None, None, None, None, None)
|
||||
assert not client.metrics.tooling_metrics.find_one()
|
||||
|
||||
@patch("buildscripts.metrics.scons_tooling_metrics.should_collect_metrics", return_value=False)
|
||||
@mongomock.patch(servers=((TEST_INTERNAL_TOOLING_METRICS_HOSTNAME), ))
|
||||
def test_no_scons_metrics_collection(self, mock_should_collect_metrics):
|
||||
client = pymongo.MongoClient(host=TEST_INTERNAL_TOOLING_METRICS_HOSTNAME)
|
||||
assert not client.metrics.tooling_metrics.find_one()
|
||||
under_test._save_scons_tooling_metrics(CURRENT_DATE_TIME, None, None, None, None,
|
||||
MagicMock(exit_code=0))
|
||||
assert not client.metrics.tooling_metrics.find_one()
|
||||
assert "_save_metrics" in atexit_functions
|
||||
|
||||
@ -1,81 +1,75 @@
|
||||
"""Unit tests for tooling_metrics.py."""
|
||||
import asyncio
|
||||
from datetime import datetime
|
||||
import os
|
||||
import sys
|
||||
import unittest
|
||||
from unittest.mock import patch
|
||||
from unittest.mock import mock_open, patch
|
||||
from mock import MagicMock
|
||||
import mongomock
|
||||
import pymongo
|
||||
from buildscripts.metrics.metrics_datatypes import ToolingMetrics
|
||||
from buildscripts.metrics.metrics_datatypes import ResmokeToolingMetrics, SConsToolingMetrics
|
||||
import buildscripts.metrics.tooling_metrics_utils as under_test
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
# pylint: disable=protected-access
|
||||
|
||||
TEST_INTERNAL_TOOLING_METRICS_HOSTNAME = 'mongodb://testing:27017'
|
||||
CURRENT_DATE_TIME = datetime(2022, 10, 4)
|
||||
|
||||
|
||||
async def extended_sleep(arg):
|
||||
await asyncio.sleep(2)
|
||||
|
||||
RESMOKE_METRICS_ARGS = {
|
||||
"utc_starttime": datetime(2022, 10, 4),
|
||||
"exit_hook": MagicMock(exit_code=0),
|
||||
}
|
||||
|
||||
# Metrics collection is not supported for Windows
|
||||
if os.name == "nt":
|
||||
sys.exit()
|
||||
|
||||
|
||||
@patch("atexit.register")
|
||||
class TestRegisterMetricsCollectionAtExit(unittest.TestCase):
|
||||
@patch("buildscripts.metrics.tooling_metrics_utils._should_collect_metrics", return_value=True)
|
||||
def test_register_metrics_collection(self, mock_should_collect_metrics, mock_atexit):
|
||||
under_test.register_metrics_collection_atexit(ResmokeToolingMetrics.generate_metrics,
|
||||
RESMOKE_METRICS_ARGS)
|
||||
atexit_functions = [call[0][0].__name__ for call in mock_atexit.call_args_list]
|
||||
assert "_save_metrics" in atexit_functions
|
||||
|
||||
@patch("buildscripts.metrics.tooling_metrics_utils._should_collect_metrics", return_value=False)
|
||||
def test_no_register_metrics_collection(self, mock_should_collect_metrics, mock_atexit):
|
||||
under_test.register_metrics_collection_atexit(ResmokeToolingMetrics.generate_metrics,
|
||||
RESMOKE_METRICS_ARGS)
|
||||
atexit_functions = [call[0][0].__name__ for call in mock_atexit.call_args_list]
|
||||
assert "_save_metrics" not in atexit_functions
|
||||
|
||||
|
||||
@patch("buildscripts.metrics.tooling_metrics_utils.INTERNAL_TOOLING_METRICS_HOSTNAME",
|
||||
TEST_INTERNAL_TOOLING_METRICS_HOSTNAME)
|
||||
class TestSaveToolingMetrics(unittest.TestCase):
|
||||
@mongomock.patch(servers=((TEST_INTERNAL_TOOLING_METRICS_HOSTNAME), ))
|
||||
def test_on_virtual_workstation(self):
|
||||
under_test.save_tooling_metrics(ToolingMetrics.get_resmoke_metrics(CURRENT_DATE_TIME))
|
||||
def test_save_resmoke_metrics(self):
|
||||
under_test._save_metrics(ResmokeToolingMetrics.generate_metrics, RESMOKE_METRICS_ARGS)
|
||||
client = pymongo.MongoClient(host=TEST_INTERNAL_TOOLING_METRICS_HOSTNAME)
|
||||
assert client.metrics.tooling_metrics.find_one()
|
||||
|
||||
@mongomock.patch(servers=((TEST_INTERNAL_TOOLING_METRICS_HOSTNAME), ))
|
||||
@patch("buildscripts.metrics.tooling_metrics_utils._save_metrics",
|
||||
side_effect=pymongo.errors.WriteError(error="Error Information"))
|
||||
def test_exception_caught(self, mock_save_metrics):
|
||||
with self.assertLogs('tooling_metrics_utils') as cm:
|
||||
under_test.save_tooling_metrics(ToolingMetrics.get_resmoke_metrics(CURRENT_DATE_TIME))
|
||||
@patch("buildscripts.metrics.tooling_metrics_utils._get_internal_tooling_metrics_client",
|
||||
side_effect=pymongo.errors.ServerSelectionTimeoutError(message="Error Information"))
|
||||
def test_save_metrics_with_exc(self, mock_save_metrics):
|
||||
with self.assertLogs('tooling_metrics') as cm:
|
||||
under_test._save_metrics(ResmokeToolingMetrics.generate_metrics, RESMOKE_METRICS_ARGS)
|
||||
assert "Error Information" in cm.output[0]
|
||||
assert "Unexpected: Tooling metrics collection is not available" in cm.output[0]
|
||||
client = pymongo.MongoClient(host=TEST_INTERNAL_TOOLING_METRICS_HOSTNAME)
|
||||
assert not client.metrics.tooling_metrics.find_one()
|
||||
|
||||
@mongomock.patch(servers=((TEST_INTERNAL_TOOLING_METRICS_HOSTNAME), ))
|
||||
@patch("buildscripts.metrics.tooling_metrics_utils._save_metrics", side_effect=extended_sleep)
|
||||
def test_timeout_caught(self, mock_save_metrics):
|
||||
with self.assertLogs('tooling_metrics_utils') as cm:
|
||||
under_test.save_tooling_metrics(ToolingMetrics.get_resmoke_metrics(CURRENT_DATE_TIME))
|
||||
assert "Timeout: Tooling metrics collection is not available" in cm.output[0]
|
||||
assert "Internal Metrics Collection Failed" in cm.output[0]
|
||||
client = pymongo.MongoClient(host=TEST_INTERNAL_TOOLING_METRICS_HOSTNAME)
|
||||
assert not client.metrics.tooling_metrics.find_one()
|
||||
|
||||
|
||||
class TestIsVirtualWorkstation(unittest.TestCase):
|
||||
@patch("buildscripts.metrics.tooling_metrics_utils._toolchain_exists", return_value=False)
|
||||
@patch("buildscripts.metrics.tooling_metrics_utils._git_user_exists", return_value=True)
|
||||
def test_no_toolchain_has_email(self, mock_git_user_exists, mock_toolchain_exists):
|
||||
assert not under_test._is_virtual_workstation()
|
||||
@patch("builtins.open", mock_open(read_data="ubuntu1804-workstation"))
|
||||
def test_is_virtual_workstation(self):
|
||||
assert under_test._is_virtual_workstation() is True
|
||||
|
||||
@patch("buildscripts.metrics.tooling_metrics_utils._toolchain_exists", return_value=True)
|
||||
@patch("buildscripts.metrics.tooling_metrics_utils._git_user_exists", return_value=True)
|
||||
def test_has_toolchain_has_email(self, mock_git_user_exists, mock_toolchain_exists):
|
||||
assert under_test._is_virtual_workstation()
|
||||
|
||||
@patch("buildscripts.metrics.tooling_metrics_utils._toolchain_exists", return_value=True)
|
||||
@patch("buildscripts.metrics.tooling_metrics_utils._git_user_exists", return_value=False)
|
||||
def test_has_toolchain_no_email(self, mock_git_user_exists, mock_toolchain_exists):
|
||||
assert not under_test._is_virtual_workstation()
|
||||
|
||||
@patch("buildscripts.metrics.tooling_metrics_utils._toolchain_exists", return_value=False)
|
||||
@patch("buildscripts.metrics.tooling_metrics_utils._git_user_exists", return_value=False)
|
||||
def test_no_toolchain_no_email(self, mock_git_user_exists, mock_toolchain_exists):
|
||||
assert not under_test._is_virtual_workstation()
|
||||
@patch("builtins.open", mock_open(read_data="test"))
|
||||
def test_is_not_virtual_workstation(self):
|
||||
assert under_test._is_virtual_workstation() is False
|
||||
|
||||
|
||||
class TestHasMetricsOptOut(unittest.TestCase):
|
||||
@ -92,9 +86,9 @@ class TestShouldCollectMetrics(unittest.TestCase):
|
||||
@patch("buildscripts.metrics.tooling_metrics_utils._is_virtual_workstation", return_value=True)
|
||||
@patch("buildscripts.metrics.tooling_metrics_utils._has_metrics_opt_out", return_value=False)
|
||||
def test_should_collect_metrics(self, mock_opt_out, mock_is_virtual_env):
|
||||
assert under_test.should_collect_metrics()
|
||||
assert under_test._should_collect_metrics()
|
||||
|
||||
@patch("buildscripts.metrics.tooling_metrics_utils._is_virtual_workstation", return_value=True)
|
||||
@patch("buildscripts.metrics.tooling_metrics_utils._has_metrics_opt_out", return_value=True)
|
||||
def test_no_collect_metrics_opt_out(self, mock_opt_out, mock_is_virtual_env):
|
||||
assert not under_test.should_collect_metrics()
|
||||
assert not under_test._should_collect_metrics()
|
||||
|
||||
@ -78,7 +78,7 @@ class TestHistoricTaskData(unittest.TestCase):
|
||||
@staticmethod
|
||||
def _make_evg_result(test_file="dir/test1.js", num_pass=0, duration=0):
|
||||
return Mock(
|
||||
test_file=test_file,
|
||||
test_name=test_file,
|
||||
task_name="task1",
|
||||
variant="variant1",
|
||||
distro="distro1",
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
"""Service for determining task timeouts."""
|
||||
from datetime import datetime
|
||||
from typing import Any, Dict, NamedTuple, Optional
|
||||
|
||||
import inject
|
||||
@ -31,29 +30,17 @@ class TimeoutParams(NamedTuple):
|
||||
is_asan: bool
|
||||
|
||||
|
||||
class TimeoutSettings(NamedTuple):
|
||||
"""Settings for determining timeouts."""
|
||||
|
||||
start_date: datetime
|
||||
end_date: datetime
|
||||
|
||||
|
||||
class TimeoutService:
|
||||
"""A service for determining task timeouts."""
|
||||
|
||||
@inject.autoparams()
|
||||
def __init__(self, evg_api: EvergreenApi, resmoke_proxy: ResmokeProxyService,
|
||||
timeout_settings: TimeoutSettings) -> None:
|
||||
def __init__(self, resmoke_proxy: ResmokeProxyService) -> None:
|
||||
"""
|
||||
Initialize the service.
|
||||
|
||||
:param evg_api: Evergreen API client.
|
||||
:param resmoke_proxy: Proxy to query resmoke.
|
||||
:param timeout_settings: Settings for how timeouts are calculated.
|
||||
"""
|
||||
self.evg_api = evg_api
|
||||
self.resmoke_proxy = resmoke_proxy
|
||||
self.timeout_settings = timeout_settings
|
||||
|
||||
def get_timeout_estimate(self, timeout_params: TimeoutParams) -> TimeoutEstimate:
|
||||
"""
|
||||
@ -137,10 +124,8 @@ class TimeoutService:
|
||||
:return: Historic test results if they exist.
|
||||
"""
|
||||
try:
|
||||
evg_stats = HistoricTaskData.from_evg(
|
||||
self.evg_api, timeout_params.evg_project, self.timeout_settings.start_date,
|
||||
self.timeout_settings.end_date, timeout_params.task_name,
|
||||
timeout_params.build_variant)
|
||||
evg_stats = HistoricTaskData.from_s3(
|
||||
timeout_params.evg_project, timeout_params.task_name, timeout_params.build_variant)
|
||||
if not evg_stats:
|
||||
LOGGER.warning("No historic runtime information available")
|
||||
return None
|
||||
|
||||
@ -1,15 +1,31 @@
|
||||
"""Utility to support parsing a TestStat."""
|
||||
from collections import defaultdict
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime
|
||||
from itertools import chain
|
||||
from typing import NamedTuple, List, Callable, Optional
|
||||
|
||||
from evergreen import EvergreenApi, TestStats
|
||||
import requests
|
||||
from requests.adapters import HTTPAdapter, Retry
|
||||
|
||||
from buildscripts.util.testname import split_test_hook_name, is_resmoke_hook, get_short_name_from_test_file
|
||||
|
||||
TASK_LEVEL_HOOKS = {"CleanEveryN"}
|
||||
TESTS_STATS_S3_LOCATION = "https://mongo-test-stats.s3.amazonaws.com"
|
||||
|
||||
|
||||
class HistoricalTestInformation(NamedTuple):
|
||||
"""
|
||||
Container for information about the historical runtime of a test.
|
||||
|
||||
test_name: Name of test.
|
||||
avg_duration_pass: Average of runtime of test that passed.
|
||||
num_pass: Number of times the test has passed.
|
||||
num_fail: Number of times the test has failed.
|
||||
"""
|
||||
|
||||
test_name: str
|
||||
num_pass: int
|
||||
num_fail: int
|
||||
avg_duration_pass: float
|
||||
|
||||
|
||||
class TestRuntime(NamedTuple):
|
||||
@ -74,9 +90,9 @@ class HistoricHookInfo(NamedTuple):
|
||||
avg_duration: float
|
||||
|
||||
@classmethod
|
||||
def from_test_stats(cls, test_stats: TestStats) -> "HistoricHookInfo":
|
||||
def from_test_stats(cls, test_stats: HistoricalTestInformation) -> "HistoricHookInfo":
|
||||
"""Create an instance from a test_stats object."""
|
||||
return cls(hook_id=test_stats.test_file, num_pass=test_stats.num_pass,
|
||||
return cls(hook_id=test_stats.test_name, num_pass=test_stats.num_pass,
|
||||
avg_duration=test_stats.avg_duration_pass)
|
||||
|
||||
def test_name(self) -> str:
|
||||
@ -101,10 +117,10 @@ class HistoricTestInfo(NamedTuple):
|
||||
hooks: List[HistoricHookInfo]
|
||||
|
||||
@classmethod
|
||||
def from_test_stats(cls, test_stats: TestStats,
|
||||
def from_test_stats(cls, test_stats: HistoricalTestInformation,
|
||||
hooks: List[HistoricHookInfo]) -> "HistoricTestInfo":
|
||||
"""Create an instance from a test_stats object."""
|
||||
return cls(test_name=test_stats.test_file, num_pass=test_stats.num_pass,
|
||||
return cls(test_name=test_stats.test_name, num_pass=test_stats.num_pass,
|
||||
avg_duration=test_stats.avg_duration_pass, hooks=hooks)
|
||||
|
||||
def normalized_test_name(self) -> str:
|
||||
@ -137,45 +153,56 @@ class HistoricTaskData(object):
|
||||
"""Initialize the TestStats with raw results from the Evergreen API."""
|
||||
self.historic_test_results = historic_test_results
|
||||
|
||||
@classmethod
|
||||
def from_evg(cls, evg_api: EvergreenApi, project: str, start_date: datetime, end_date: datetime,
|
||||
task: str, variant: str) -> "HistoricTaskData":
|
||||
@staticmethod
|
||||
def get_stats_from_s3(project: str, task: str, variant: str) -> List[HistoricalTestInformation]:
|
||||
"""
|
||||
Retrieve test stats from evergreen for a given task.
|
||||
Retrieve test stats from s3 for a given task.
|
||||
|
||||
:param project: Project to query.
|
||||
:param task: Task to query.
|
||||
:param variant: Build variant to query.
|
||||
:return: A list of the Test stats for the specified task.
|
||||
"""
|
||||
session = requests.Session()
|
||||
retries = Retry(total=5, backoff_factor=1, status_forcelist=[502, 503, 504])
|
||||
session.mount('https://', HTTPAdapter(max_retries=retries))
|
||||
|
||||
response = session.get(f"{TESTS_STATS_S3_LOCATION}/{project}/{variant}/{task}")
|
||||
data = response.json()
|
||||
|
||||
return [HistoricalTestInformation(**item) for item in data]
|
||||
|
||||
@classmethod
|
||||
def from_s3(cls, project: str, task: str, variant: str) -> "HistoricTaskData":
|
||||
"""
|
||||
Retrieve test stats from s3 for a given task.
|
||||
|
||||
:param evg_api: Evergreen API client.
|
||||
:param project: Project to query.
|
||||
:param start_date: Start date to query.
|
||||
:param end_date: End date to query.
|
||||
:param task: Task to query.
|
||||
:param variant: Build variant to query.
|
||||
:return: Test stats for the specified task.
|
||||
"""
|
||||
days = (end_date - start_date).days
|
||||
historic_stats = evg_api.test_stats_by_project(
|
||||
project, after_date=start_date, before_date=end_date, tasks=[task], variants=[variant],
|
||||
group_by="test", group_num_days=days)
|
||||
|
||||
return cls.from_stats_list(historic_stats)
|
||||
historical_test_data = cls.get_stats_from_s3(project, task, variant)
|
||||
return cls.from_stats_list(historical_test_data)
|
||||
|
||||
@classmethod
|
||||
def from_stats_list(cls, historic_stats: List[TestStats]) -> "HistoricTaskData":
|
||||
def from_stats_list(
|
||||
cls, historical_test_data: List[HistoricalTestInformation]) -> "HistoricTaskData":
|
||||
"""
|
||||
Build historic task data from a list of historic stats.
|
||||
|
||||
:param historic_stats: List of historic stats to build from.
|
||||
:param historical_test_data: A list of information about the runtime of a test.
|
||||
:return: Historic task data from the list of stats.
|
||||
"""
|
||||
|
||||
hooks = defaultdict(list)
|
||||
for hook in [stat for stat in historic_stats if is_resmoke_hook(stat.test_file)]:
|
||||
for hook in [stat for stat in historical_test_data if is_resmoke_hook(stat.test_name)]:
|
||||
historical_hook = HistoricHookInfo.from_test_stats(hook)
|
||||
hooks[historical_hook.test_name()].append(historical_hook)
|
||||
|
||||
return cls([
|
||||
HistoricTestInfo.from_test_stats(stat,
|
||||
hooks[get_short_name_from_test_file(stat.test_file)])
|
||||
for stat in historic_stats if not is_resmoke_hook(stat.test_file)
|
||||
hooks[get_short_name_from_test_file(stat.test_name)])
|
||||
for stat in historical_test_data if not is_resmoke_hook(stat.test_name)
|
||||
])
|
||||
|
||||
def get_tests_runtimes(self) -> List[TestRuntime]:
|
||||
|
||||
@ -19,608 +19,8 @@
|
||||
# (ticket, test_file) pair on the last-lts branch.
|
||||
#
|
||||
last-continuous:
|
||||
all:
|
||||
- test_file: jstests/core/check_shard_index.js
|
||||
ticket: SERVER-50792
|
||||
- test_file: jstests/sharding/refine_collection_shard_key_basic.js
|
||||
ticket: SERVER-50792
|
||||
- test_file: jstests/replsets/tenant_migration_fetch_committed_transactions_retry.js
|
||||
ticket: SERVER-51943
|
||||
- test_file: jstests/sharding/query/collation_shard_targeting_hashed_shard_key.js
|
||||
ticket: SERVER-53335
|
||||
- test_file: jstests/replsets/replSetGetStatus_member_wall_times.js
|
||||
ticket: SERVER-54909
|
||||
- test_file: jstests/sharding/cwwc_conflict_add_shard.js
|
||||
ticket: SERVER-56800
|
||||
- test_file: jstests/sharding/reconfig_fails_no_cwwc_set_sharding.js
|
||||
ticket: SERVER-56846
|
||||
- test_file: jstests/replsets/catchup_takeover_with_higher_config.js
|
||||
ticket: SERVER-57262
|
||||
- test_file: jstests/replsets/tenant_migration_recipient_fetches_retryable_writes_oplog_entries.js
|
||||
ticket: SERVER-57617
|
||||
- test_file: jstests/replsets/tenant_migration_recipient_fetches_synthetic_find_and_modify_oplog_entries.js
|
||||
ticket: SERVER-57617
|
||||
- test_file: jstests/replsets/tenant_migration_fetch_committed_transactions_retry.js
|
||||
ticket: SERVER-57617
|
||||
- test_file: jstests/replsets/tenant_migration_retryable_write_retry.js
|
||||
ticket: SERVER-57617
|
||||
- test_file: jstests/replsets/tenant_migration_retryable_write_retry_on_recipient.js
|
||||
ticket: SERVER-57617
|
||||
- test_file: jstests/replsets/tenant_migration_find_and_modify_retry.js
|
||||
ticket: SERVER-57617
|
||||
- test_file: jstests/sharding/resharding_min_fetch_ts_with_txn.js
|
||||
ticket: SERVER-57667
|
||||
- test_file: jstests/sharding/reshard_collection_joins_existing_operation.js
|
||||
ticket: SERVER-57667
|
||||
- test_file: jstests/sharding/resharding_clones_duplicate_key.js
|
||||
ticket: SERVER-57667
|
||||
- test_file: jstests/sharding/resharding_abort_command.js
|
||||
ticket: SERVER-57667
|
||||
- test_file: jstests/sharding/resharding_clones_initial_data.js
|
||||
ticket: SERVER-57667
|
||||
- test_file: jstests/sharding/resharding_commit.js
|
||||
ticket: SERVER-57667
|
||||
- test_file: jstests/sharding/resharding_collection_cloner_resuming.js
|
||||
ticket: SERVER-57667
|
||||
- test_file: jstests/sharding/reshard_collection_existing_sk_index_not_duplicated.js
|
||||
ticket: SERVER-57667
|
||||
- test_file: jstests/sharding/resharding_metrics.js
|
||||
ticket: SERVER-57667
|
||||
- test_file: jstests/sharding/resharding_fails_on_nonempty_stash.js
|
||||
ticket: SERVER-57667
|
||||
- test_file: jstests/sharding/resharding_txn_cloner.js
|
||||
ticket: SERVER-57667
|
||||
- test_file: jstests/sharding/reshard_collection_joins_existing_operation.js
|
||||
ticket: SERVER-57667
|
||||
- test_file: jstests/sharding/resharding_size_estimate.js
|
||||
ticket: SERVER-57667
|
||||
- test_file: jstests/sharding/resharding_histogram_metrics.js
|
||||
ticket: SERVER-57700
|
||||
- test_file: jstests/sharding/resharding_metrics.js
|
||||
ticket: SERVER-57766
|
||||
- test_file: jstests/sharding/rewrite_state_change_errors.js
|
||||
ticket: SERVER-57772
|
||||
- test_file: jstests/sharding/query/pipeline_length_limit.js
|
||||
ticket: SERVER-58203
|
||||
- test_file: jstests/replsets/tenant_migration_aborted_buildindex.js
|
||||
ticket: SERVER-58353
|
||||
- test_file: jstests/replsets/initial_sync_replicate_drop_mid_secondary_batch.js
|
||||
ticket: SERVER-58636
|
||||
- test_file: jstests/sharding/implicit_default_write_concern_add_shard.js
|
||||
ticket: SERVER-58696
|
||||
- test_file: jstests/replsets/apply_ops_capped_collection.js
|
||||
ticket: SERVER-58744
|
||||
- test_file: jstests/replsets/write_concern_write_to_local.js
|
||||
ticket: SERVER-58898
|
||||
- test_file: jstests/sharding/resharding_secondary_recovers_temp_ns_metadata.js
|
||||
ticket: SERVER-59023
|
||||
- test_file: jstests/replsets/rollback_transaction_table.js
|
||||
ticket: SERVER-59057
|
||||
- test_file: jstests/replsets/stepdown_race_with_transaction.js
|
||||
ticket: SERVER-59108
|
||||
- test_file: jstests/sharding/change_stream_show_migration_events.js
|
||||
ticket: SERVER-59424
|
||||
- test_file: jstests/replsets/tenant_migration_recipient_access_blocker_rollback.js
|
||||
ticket: SERVER-59525
|
||||
- test_file: jstests/core/timeseries/timeseries_bucket_rename.js
|
||||
ticket: SERVER-59666
|
||||
- test_file: jstests/replsets/sync_source_selection_ignores_minvalid_after_rollback.js
|
||||
ticket: SERVER-59721
|
||||
- test_file: jstests/sharding/resharding_secondary_recovers_temp_ns_metadata.js
|
||||
ticket: SERVER-59721
|
||||
- test_file: jstests/sharding/test_resharding_test_fixture_shutdown_retry_needed.js
|
||||
ticket: SERVER-59923
|
||||
- test_file: jstests/replsets/dont_set_invalid_rwconcern.js
|
||||
ticket: SERVER-60817
|
||||
- test_file: jstests/replsets/tenant_migration_donor_resume_on_stepup_and_restart.js
|
||||
ticket: SERVER-60829
|
||||
- test_file: jstests/core/sbe/sbe_ixscan_explain.js
|
||||
ticket: SERVER-61087
|
||||
- test_file: jstests/replsets/tenant_migration_transaction_boundary.js
|
||||
ticket: SERVER-61666
|
||||
- test_file: jstests/sharding/range_deleter_interacts_correctly_with_refine_shard_key.js
|
||||
ticket: SERVER-61755
|
||||
- test_file: jstests/replsets/dbcheck.js
|
||||
ticket: SERVER-61757
|
||||
- test_file: jstests/replsets/reconfig_may_not_remove_custom_wc_in_use.js
|
||||
ticket: SERVER-61864
|
||||
- test_file: jstests/replsets/default_write_concern_race_with_config_tags.js
|
||||
ticket: SERVER-61864
|
||||
- test_file: jstests/replsets/config_tags_race_with_default_write_concern.js
|
||||
ticket: SERVER-61864
|
||||
- test_file: jstests/auth/dbcheck.js
|
||||
ticket: SERVER-61955
|
||||
- test_file: jstests/replsets/rollback_during_step_up.js
|
||||
ticket: SERVER-61977
|
||||
- test_file: jstests/replsets/dbcheck_write_concern.js
|
||||
ticket: SERVER-62212
|
||||
- test_file: jstests/sharding/recover_multiple_migrations_on_stepup.js
|
||||
ticket: SERVER-62245
|
||||
- test_file: jstests/sharding/migration_recovers_unfinished_migrations.js
|
||||
ticket: SERVER-62296
|
||||
- test_file: jstests/sharding/max_time_ms_does_not_leak_shard_cursor.js
|
||||
ticket: SERVER-62710
|
||||
- test_file: jstests/replsets/apply_ops_dropDatabase.js
|
||||
ticket: SERVER-62759
|
||||
- test_file: jstests/noPassthrough/operator_counters_accumulators.js
|
||||
ticket: SERVER-63049
|
||||
- test_file: jstests/replsets/tenant_migration_cloning_uses_read_concern_majority.js
|
||||
ticket: SERVER-63122
|
||||
- test_file: jstests/replsets/tenant_migration_collection_ttl.js
|
||||
ticket: SERVER-63122
|
||||
- test_file: jstests/replsets/tenant_migration_concurrent_reads_on_recipient.js
|
||||
ticket: SERVER-63122
|
||||
- test_file: jstests/replsets/tenant_migration_concurrent_writes_on_recipient.js
|
||||
ticket: SERVER-63122
|
||||
- test_file: jstests/replsets/tenant_migration_fetch_committed_transactions_retry.js
|
||||
ticket: SERVER-63122
|
||||
- test_file: jstests/replsets/tenant_migration_recipient_current_op.js
|
||||
ticket: SERVER-63122
|
||||
- test_file: jstests/replsets/tenant_migration_retry_session_migration.js
|
||||
ticket: SERVER-63122
|
||||
- test_file: jstests/replsets/tenant_migration_retryable_write_retry_on_recipient.js
|
||||
ticket: SERVER-63122
|
||||
- test_file: jstests/replsets/tenant_migration_timeseries_retryable_write_retry_on_recipient.js
|
||||
ticket: SERVER-63122
|
||||
- test_file: jstests/replsets/tenant_migration_recipient_initial_sync_recovery.js
|
||||
ticket: SERVER-63122
|
||||
- test_file: jstests/replsets/tenant_migration_recipient_startup_recovery.js
|
||||
ticket: SERVER-63122
|
||||
- test_file: jstests/replsets/tenant_migration_network_error_via_rollback.js
|
||||
ticket: SERVER-63122
|
||||
- test_file: jstests/replsets/tenant_migration_resume_collection_cloner_after_recipient_failover_with_dropped_views.js
|
||||
ticket: SERVER-63129
|
||||
- test_file: jstests/replsets/buildindexes_false_commit_quorum.js
|
||||
ticket: SERVER-63531
|
||||
- test_file: jstests/core/in_with_mixed_values.js
|
||||
ticket: SERVER-64141
|
||||
- test_file: jstests/sharding/refine_collection_shard_key_basic.js
|
||||
ticket: SERVER-64142
|
||||
- test_file: jstests/core/plan_cache_sbe.js
|
||||
ticket: SERVER-64315
|
||||
- test_file: jstests/core/sbe/plan_cache_sbe_with_or_queries.js
|
||||
ticket: SERVER-64315
|
||||
- test_file: jstests/core/sbe_plan_cache_autoparameterize_collscan.js
|
||||
ticket: SERVER-64315
|
||||
- test_file: jstests/core/wildcard_index_cached_plans.js
|
||||
ticket: SERVER-64315
|
||||
- test_file: jstests/sharding/resharding_metrics_increment.js
|
||||
ticket: SERVER-64395
|
||||
- test_file: jstests/sharding/resharding_abort_command.js
|
||||
ticket: SERVER-64395
|
||||
- test_file: jstests/sharding/update_with_dollar_fields.js
|
||||
ticket: SERVER-64485
|
||||
- test_file: jstests/sharding/resharding_change_stream_namespace_filtering.js
|
||||
ticket: SERVER-64780
|
||||
- test_file: jstests/sharding/shard_key_index_must_exist.js
|
||||
ticket: SERVER-6491
|
||||
- test_file: jstests/sharding/change_stream_shard_failover.js
|
||||
ticket: SERVER-65022
|
||||
- test_file: jstests/sharding/database_versioning_all_commands.js
|
||||
ticket: SERVER-65101
|
||||
- test_file: jstests/sharding/sessions_collection_auto_healing.js
|
||||
ticket: SERVER-65188
|
||||
- test_file: jstests/replsets/sessions_collection_auto_healing.js
|
||||
ticket: SERVER-65188
|
||||
- test_file: jstests/replsets/capped_deletes.js
|
||||
ticket: SERVER-65261
|
||||
- test_file: jstests/replsets/tenant_migration_recipient_rollback_recovery.js
|
||||
ticket: SERVER-65300
|
||||
- test_file: jstests/aggregation/agg_infinite_recursion.js
|
||||
ticket: SERVER-65773
|
||||
- test_file: jstests/sharding/migration_retries_on_write_conflict_exceptions.js
|
||||
ticket: SERVER-65947
|
||||
- test_file: jstests/aggregation/match_no_swap_rand.js
|
||||
ticket: SERVER-66072
|
||||
- test_file: jstests/sharding/data_size_aware_balancing_sessions_collection.js
|
||||
ticket: SERVER-66078
|
||||
- test_file: jstests/replsets/initial_sync_with_partial_transaction.js
|
||||
ticket: SERVER-66089
|
||||
- test_file: jstests/core/or_to_in.js
|
||||
ticket: SERVER-66379
|
||||
- test_file: jstests/core/where_multiple_plans.js
|
||||
ticket: SERVER-66389
|
||||
- test_file: jstests/sharding/resharding_metrics.js
|
||||
ticket: SERVER-66422
|
||||
- test_file: jstests/concurrency/fsm_workloads/find_flip_sbe_enabled.js
|
||||
ticket: SERVER-66445
|
||||
- test_file: jstests/core/index_stats.js
|
||||
ticket: SERVER-66445
|
||||
- test_file: jstests/core/wildcard_index_cached_plans.js
|
||||
ticket: SERVER-66445
|
||||
- test_file: jstests/core/index_filter_commands_invalidate_plan_cache_entries.js
|
||||
ticket: SERVER-66445
|
||||
- test_file: jstests/core/idhack.js
|
||||
ticket: SERVER-66445
|
||||
- test_file: jstests/core/sbe/sbe_explain_rejected_plans.js
|
||||
ticket: SERVER-66445
|
||||
- test_file: jstests/core/plan_cache_list_shapes.js
|
||||
ticket: SERVER-66445
|
||||
- test_file: jstests/core/plan_cache_list_plans.js
|
||||
ticket: SERVER-66445
|
||||
- test_file: jstests/replsets/dbhash_lock_acquisition.js
|
||||
ticket: SERVER-66719
|
||||
- test_file: jstests/replsets/prepared_transaction_kill_during_step_up_refresh.js
|
||||
ticket: SERVER-66854
|
||||
- test_file: jstests/replsets/step_up_kill_abort_transactions.js
|
||||
ticket: SERVER-66854
|
||||
- test_file: jstests/sharding/drop_index_fails_if_multikey_index_is_last_compatible.js
|
||||
ticket: SERVER-67299
|
||||
- test_file: jstests/sharding/move_chunk_interrupt_postimage.js
|
||||
ticket: SERVER-67492
|
||||
- test_file: jstests/replsets/optime.js
|
||||
ticket: SERVER-67532
|
||||
- test_file: jstests/replsets/internal_sessions_reaping_basic.js
|
||||
ticket: SERVER-67723
|
||||
- test_file: jstests/sharding/collection_uuid_shard_capped_collection.js
|
||||
ticket: SERVER-67885
|
||||
- test_file: jstests/core/txns/txn_ops_allowed_on_buckets_coll.js
|
||||
ticket: SERVER-68556
|
||||
- test_file: jstests/core/txns/no_writes_to_system_collections_in_txn.js
|
||||
ticket: SERVER-68556
|
||||
- test_file: jstests/sharding/resharding_temp_ns_routing_info_unsharded.js
|
||||
ticket: SERVER-68628
|
||||
- test_file: jstests/sharding/move_chunk_interrupt_postimage.js
|
||||
ticket: SERVER-68728
|
||||
- test_file: jstests/sharding/resharding_critical_section_metrics.js
|
||||
ticket: SERVER-68932
|
||||
- test_file: src/mongo/db/modules/enterprise/jstests/fcbis/fcbis_cannot_vote_twice_same_term.js
|
||||
ticket: SERVER-69861
|
||||
- test_file: src/mongo/db/modules/enterprise/jstests/fcbis/fcbis_election_during_storage_change.js
|
||||
ticket: SERVER-69861
|
||||
- test_file: jstests/aggregation/expressions/switch_errors.js
|
||||
ticket: SERVER-70190
|
||||
- test_file: jstests/core/cover_null_queries.js
|
||||
ticket: SERVER-70436
|
||||
- test_file: jstests/sharding/prepare_transaction_then_migrate.js
|
||||
ticket: SERVER-68361
|
||||
all: []
|
||||
suites: null
|
||||
last-lts:
|
||||
all:
|
||||
- test_file: jstests/core/null_query_semantics.js
|
||||
ticket: SERVER-21929
|
||||
- test_file: jstests/core/or_to_in.js
|
||||
ticket: SERVER-21929
|
||||
- test_file: jstests/aggregation/sources/lookup/lookup_null_semantics.js
|
||||
ticket: SERVER-21929
|
||||
- test_file: jstests/replsets/disallow_adding_initialized_node1.js
|
||||
ticket: SERVER-35649
|
||||
- test_file: jstests/replsets/cluster_chaining_override.js
|
||||
ticket: SERVER-37904
|
||||
- test_file: jstests/sharding/scaled_collection_stats.js
|
||||
ticket: SERVER-43902
|
||||
- test_file: jstests/core/apply_ops_system_dot_views.js
|
||||
ticket: SERVER-47469
|
||||
- test_file: jstests/concurrency/fsm_workloads/view_catalog_direct_system_writes.js
|
||||
ticket: SERVER-47469
|
||||
- test_file: jstests/replsets/invalidate_sessions_on_stepdown.js
|
||||
ticket: SERVER-47645
|
||||
- test_file: jstests/change_streams/report_post_batch_resume_token.js
|
||||
ticket: SERVER-47810
|
||||
- test_file: jstests/replsets/reconfig_removes_node_in_rollback.js
|
||||
ticket: SERVER-48179
|
||||
- test_file: jstests/core/txns/no_writes_to_config_transactions_with_prepared_transaction.js
|
||||
ticket: SERVER-48525
|
||||
- test_file: jstests/replsets/initial_sync_fails_unclean_restart.js
|
||||
ticket: SERVER-50140
|
||||
- test_file: jstests/replsets/change_sync_source_in_initial_sync.js
|
||||
ticket: SERVER-50320
|
||||
- test_file: jstests/sharding/awaitable_hello_primary_failures.js
|
||||
ticket: SERVER-50415
|
||||
- test_file: jstests/replsets/server_status_repl_is_writable_primary.js
|
||||
ticket: SERVER-50420
|
||||
- test_file: jstests/sharding/log_remote_op_wait.js
|
||||
ticket: SERVER-50559
|
||||
- test_file: jstests/core/views/views_all_commands.js
|
||||
ticket: SERVER-50640
|
||||
- test_file: jstests/sharding/read_write_concern_defaults_application.js
|
||||
ticket: SERVER-50640
|
||||
- test_file: jstests/sharding/safe_secondary_reads_drop_recreate.js
|
||||
ticket: SERVER-50640
|
||||
- test_file: jstests/sharding/safe_secondary_reads_single_migration_suspend_range_deletion.js
|
||||
ticket: SERVER-50640
|
||||
- test_file: jstests/sharding/safe_secondary_reads_single_migration_waitForDelete.js
|
||||
ticket: SERVER-50640
|
||||
- test_file: jstests/core/check_shard_index.js
|
||||
ticket: SERVER-50792
|
||||
- test_file: jstests/sharding/refine_collection_shard_key_basic.js
|
||||
ticket: SERVER-50792
|
||||
- test_file: jstests/replsets/node_restarts_no_oplog_entry_at_stable.js
|
||||
ticket: SERVER-51049
|
||||
- test_file: jstests/replsets/awaitable_hello_errors_on_horizon_change.js
|
||||
ticket: SERVER-51259
|
||||
- test_file: jstests/replsets/awaitable_hello_fcv_change.js
|
||||
ticket: SERVER-51259
|
||||
- test_file: jstests/replsets/awaitable_hello_metrics_on_state_change.js
|
||||
ticket: SERVER-51259
|
||||
- test_file: jstests/replsets/awaitable_hello_on_nodes_with_invalid_configs.js
|
||||
ticket: SERVER-51259
|
||||
- test_file: jstests/replsets/awaitable_hello_stepdown_stepup.js
|
||||
ticket: SERVER-51259
|
||||
- test_file: jstests/replsets/quiesce_mode.js
|
||||
ticket: SERVER-51259
|
||||
- test_file: jstests/sharding/mongos_quiesce_mode.js
|
||||
ticket: SERVER-51259
|
||||
- test_file: jstests/replsets/tenant_migration_fetch_committed_transactions_retry.js
|
||||
ticket: SERVER-51943
|
||||
- test_file: jstests/core/txns/errors_on_committed_transaction.js
|
||||
ticket: SERVER-52547
|
||||
- test_file: jstests/sharding/prepare_transaction_then_migrate.js
|
||||
ticket: SERVER-52906
|
||||
- test_file: jstests/sharding/migration_waits_for_majority_commit.js
|
||||
ticket: SERVER-52906
|
||||
- test_file: jstests/sharding/migration_ignore_interrupts_1.js
|
||||
ticket: SERVER-52906
|
||||
- test_file: jstests/sharding/movechunk_interrupt_at_primary_stepdown.js
|
||||
ticket: SERVER-52906
|
||||
- test_file: jstests/sharding/movechunk_parallel.js
|
||||
ticket: SERVER-52906
|
||||
- test_file: jstests/sharding/txn_writes_during_movechunk.js
|
||||
ticket: SERVER-52906
|
||||
- test_file: jstests/replsets/stepdown_kill_other_ops.js
|
||||
ticket: SERVER-53431
|
||||
- test_file: jstests/core/sort_spill_estimate_data_size.js
|
||||
ticket: SERVER-53760
|
||||
- test_file: jstests/core/txns/timestamped_reads_wait_for_prepare_oplog_visibility.js
|
||||
ticket: SERVER-53849
|
||||
- test_file: jstests/replsets/unconditional_step_down.js
|
||||
ticket: SERVER-53985
|
||||
- test_file: jstests/replsets/replSetGetStatus_member_wall_times.js
|
||||
ticket: SERVER-54909
|
||||
- test_file: jstests/sharding/transactions_reject_writes_for_moved_chunks.js
|
||||
ticket: SERVER-55111
|
||||
- test_file: jstests/replsets/rollback_with_coalesced_txn_table_updates_during_oplog_application.js
|
||||
ticket: SERVER-55305
|
||||
- test_file: jstests/replsets/rollback_with_coalesced_txn_table_updates_from_vectored_inserts.js
|
||||
ticket: SERVER-55305
|
||||
- test_file: jstests/sharding/time_zone_info_mongos.js
|
||||
ticket: SERVER-56371
|
||||
- test_file: jstests/concurrency/fsm_workloads/findAndModify_flip_location.js
|
||||
ticket: SERVER-56377
|
||||
- test_file: jstests/sharding/txn_writes_during_movechunk.js
|
||||
ticket: SERVER-56518
|
||||
- test_file: jstests/concurrency/fsm_workloads/collmod_writeconflict.js
|
||||
ticket: SERVER-56772
|
||||
- test_file: jstests/sharding/cwwc_conflict_add_shard.js
|
||||
ticket: SERVER-56800
|
||||
- test_file: jstests/sharding/read_pref_with_hedging_mode.js
|
||||
ticket: SERVER-57117
|
||||
- test_file: jstests/replsets/catchup_takeover_with_higher_config.js
|
||||
ticket: SERVER-57262
|
||||
- test_file: jstests/replsets/assert_on_prepare_conflict_with_hole.js
|
||||
ticket: SERVER-57476
|
||||
- test_file: jstests/replsets/tenant_migration_recipient_fetches_retryable_writes_oplog_entries.js
|
||||
ticket: SERVER-57617
|
||||
- test_file: jstests/replsets/tenant_migration_recipient_fetches_synthetic_find_and_modify_oplog_entries.js
|
||||
ticket: SERVER-57617
|
||||
- test_file: jstests/replsets/tenant_migration_fetch_committed_transactions_retry.js
|
||||
ticket: SERVER-57617
|
||||
- test_file: jstests/replsets/tenant_migration_retryable_write_retry.js
|
||||
ticket: SERVER-57617
|
||||
- test_file: jstests/replsets/tenant_migration_retryable_write_retry_on_recipient.js
|
||||
ticket: SERVER-57617
|
||||
- test_file: jstests/replsets/tenant_migration_find_and_modify_retry.js
|
||||
ticket: SERVER-57617
|
||||
- test_file: jstests/sharding/resharding_min_fetch_ts_with_txn.js
|
||||
ticket: SERVER-57667
|
||||
- test_file: jstests/sharding/reshard_collection_joins_existing_operation.js
|
||||
ticket: SERVER-57667
|
||||
- test_file: jstests/sharding/resharding_clones_duplicate_key.js
|
||||
ticket: SERVER-57667
|
||||
- test_file: jstests/sharding/resharding_abort_command.js
|
||||
ticket: SERVER-57667
|
||||
- test_file: jstests/sharding/resharding_clones_initial_data.js
|
||||
ticket: SERVER-57667
|
||||
- test_file: jstests/sharding/resharding_commit.js
|
||||
ticket: SERVER-57667
|
||||
- test_file: jstests/sharding/resharding_collection_cloner_resuming.js
|
||||
ticket: SERVER-57667
|
||||
- test_file: jstests/sharding/reshard_collection_existing_sk_index_not_duplicated.js
|
||||
ticket: SERVER-57667
|
||||
- test_file: jstests/sharding/resharding_metrics.js
|
||||
ticket: SERVER-57667
|
||||
- test_file: jstests/sharding/resharding_fails_on_nonempty_stash.js
|
||||
ticket: SERVER-57667
|
||||
- test_file: jstests/sharding/resharding_txn_cloner.js
|
||||
ticket: SERVER-57667
|
||||
- test_file: jstests/sharding/reshard_collection_joins_existing_operation.js
|
||||
ticket: SERVER-57667
|
||||
- test_file: jstests/sharding/resharding_size_estimate.js
|
||||
ticket: SERVER-57667
|
||||
- test_file: jstests/sharding/resharding_histogram_metrics.js
|
||||
ticket: SERVER-57700
|
||||
- test_file: jstests/core/timeseries/timeseries_bucket_drop.js
|
||||
ticket: SERVER-57729
|
||||
- test_file: jstests/sharding/resharding_metrics.js
|
||||
ticket: SERVER-57766
|
||||
- test_file: jstests/sharding/query/pipeline_length_limit.js
|
||||
ticket: SERVER-58203
|
||||
- test_file: jstests/replsets/tenant_migration_aborted_buildindex.js
|
||||
ticket: SERVER-58353
|
||||
- test_file: jstests/replsets/initial_sync_replicate_drop_mid_secondary_batch.js
|
||||
ticket: SERVER-58636
|
||||
- test_file: jstests/sharding/implicit_default_write_concern_add_shard.js
|
||||
ticket: SERVER-58696
|
||||
- test_file: jstests/replsets/write_concern_write_to_local.js
|
||||
ticket: SERVER-58898
|
||||
- test_file: jstests/sharding/resharding_secondary_recovers_temp_ns_metadata.js
|
||||
ticket: SERVER-59023
|
||||
- test_file: jstests/replsets/rollback_transaction_table.js
|
||||
ticket: SERVER-59057
|
||||
- test_file: jstests/replsets/stepdown_race_with_transaction.js
|
||||
ticket: SERVER-59108
|
||||
- test_file: jstests/sharding/change_stream_show_migration_events.js
|
||||
ticket: SERVER-59424
|
||||
- test_file: jstests/core/timeseries/timeseries_find.js
|
||||
ticket: SERVER-59505
|
||||
- test_file: jstests/replsets/tenant_migration_recipient_access_blocker_rollback.js
|
||||
ticket: SERVER-59525
|
||||
- test_file: jstests/replsets/sync_source_selection_ignores_minvalid_after_rollback.js
|
||||
ticket: SERVER-59721
|
||||
- test_file: jstests/sharding/resharding_secondary_recovers_temp_ns_metadata.js
|
||||
ticket: SERVER-59721
|
||||
- test_file: jstests/aggregation/expressions/date_add_subtract.js
|
||||
ticket: SERVER-59765
|
||||
- test_file: jstests/sharding/test_resharding_test_fixture_shutdown_retry_needed.js
|
||||
ticket: SERVER-59923
|
||||
- test_file: jstests/sharding/write_transactions_during_migration.js
|
||||
ticket: SERVER-59952
|
||||
- test_file: jstests/sharding/retryable_writes.js
|
||||
ticket: SERVER-59952
|
||||
- test_file: jstests/sharding/move_chunk_find_and_modify_with_write_retryability.js
|
||||
ticket: SERVER-59952
|
||||
- test_file: jstests/replsets/dont_set_invalid_rwconcern.js
|
||||
ticket: SERVER-60817
|
||||
- test_file: jstests/replsets/tenant_migration_donor_resume_on_stepup_and_restart.js
|
||||
ticket: SERVER-60829
|
||||
- test_file: jstests/core/sbe/sbe_ixscan_explain.js
|
||||
ticket: SERVER-61087
|
||||
- test_file: jstests/replsets/tenant_migration_transaction_boundary.js
|
||||
ticket: SERVER-61666
|
||||
- test_file: jstests/sharding/range_deleter_interacts_correctly_with_refine_shard_key.js
|
||||
ticket: SERVER-61755
|
||||
- test_file: jstests/replsets/dbcheck.js
|
||||
ticket: SERVER-61757
|
||||
- test_file: jstests/replsets/reconfig_may_not_remove_custom_wc_in_use.js
|
||||
ticket: SERVER-61864
|
||||
- test_file: jstests/replsets/default_write_concern_race_with_config_tags.js
|
||||
ticket: SERVER-61864
|
||||
- test_file: jstests/replsets/config_tags_race_with_default_write_concern.js
|
||||
ticket: SERVER-61864
|
||||
- test_file: jstests/auth/dbcheck.js
|
||||
ticket: SERVER-61955
|
||||
- test_file: jstests/replsets/rollback_during_step_up.js
|
||||
ticket: SERVER-61977
|
||||
- test_file: jstests/sharding/recover_multiple_migrations_on_stepup.js
|
||||
ticket: SERVER-62245
|
||||
- test_file: jstests/sharding/migration_recovers_unfinished_migrations.js
|
||||
ticket: SERVER-62296
|
||||
- test_file: jstests/sharding/max_time_ms_does_not_leak_shard_cursor.js
|
||||
ticket: SERVER-62710
|
||||
- test_file: jstests/replsets/apply_ops_dropDatabase.js
|
||||
ticket: SERVER-62759
|
||||
- test_file: jstests/replsets/tenant_migration_cloning_uses_read_concern_majority.js
|
||||
ticket: SERVER-63122
|
||||
- test_file: jstests/replsets/tenant_migration_collection_ttl.js
|
||||
ticket: SERVER-63122
|
||||
- test_file: jstests/replsets/tenant_migration_concurrent_reads_on_recipient.js
|
||||
ticket: SERVER-63122
|
||||
- test_file: jstests/replsets/tenant_migration_concurrent_writes_on_recipient.js
|
||||
ticket: SERVER-63122
|
||||
- test_file: jstests/replsets/tenant_migration_fetch_committed_transactions_retry.js
|
||||
ticket: SERVER-63122
|
||||
- test_file: jstests/replsets/tenant_migration_recipient_current_op.js
|
||||
ticket: SERVER-63122
|
||||
- test_file: jstests/replsets/tenant_migration_retry_session_migration.js
|
||||
ticket: SERVER-63122
|
||||
- test_file: jstests/replsets/tenant_migration_retryable_write_retry_on_recipient.js
|
||||
ticket: SERVER-63122
|
||||
- test_file: jstests/replsets/tenant_migration_timeseries_retryable_write_retry_on_recipient.js
|
||||
ticket: SERVER-63122
|
||||
- test_file: jstests/replsets/tenant_migration_recipient_initial_sync_recovery.js
|
||||
ticket: SERVER-63122
|
||||
- test_file: jstests/replsets/tenant_migration_recipient_startup_recovery.js
|
||||
ticket: SERVER-63122
|
||||
- test_file: jstests/replsets/tenant_migration_network_error_via_rollback.js
|
||||
ticket: SERVER-63122
|
||||
- test_file: jstests/replsets/tenant_migration_resume_collection_cloner_after_recipient_failover_with_dropped_views.js
|
||||
ticket: SERVER-63129
|
||||
- test_file: jstests/replsets/buildindexes_false_commit_quorum.js
|
||||
ticket: SERVER-63531
|
||||
- test_file: jstests/sharding/refine_collection_shard_key_basic.js
|
||||
ticket: SERVER-64142
|
||||
- test_file: jstests/core/plan_cache_sbe.js
|
||||
ticket: SERVER-64315
|
||||
- test_file: jstests/core/sbe/plan_cache_sbe_with_or_queries.js
|
||||
ticket: SERVER-64315
|
||||
- test_file: jstests/core/sbe_plan_cache_autoparameterize_collscan.js
|
||||
ticket: SERVER-64315
|
||||
- test_file: jstests/core/wildcard_index_cached_plans.js
|
||||
ticket: SERVER-64315
|
||||
- test_file: jstests/sharding/resharding_metrics_increment.js
|
||||
ticket: SERVER-64395
|
||||
- test_file: jstests/sharding/resharding_abort_command.js
|
||||
ticket: SERVER-64395
|
||||
- test_file: jstests/sharding/update_with_dollar_fields.js
|
||||
ticket: SERVER-64485
|
||||
- test_file: jstests/sharding/shard_key_index_must_exist.js
|
||||
ticket: SERVER-6491
|
||||
- test_file: jstests/sharding/change_stream_shard_failover.js
|
||||
ticket: SERVER-65022
|
||||
- test_file: jstests/sharding/database_versioning_all_commands.js
|
||||
ticket: SERVER-65101
|
||||
- test_file: jstests/sharding/sessions_collection_auto_healing.js
|
||||
ticket: SERVER-65188
|
||||
- test_file: jstests/replsets/sessions_collection_auto_healing.js
|
||||
ticket: SERVER-65188
|
||||
- test_file: jstests/replsets/capped_deletes.js
|
||||
ticket: SERVER-65261
|
||||
- test_file: jstests/replsets/tenant_migration_recipient_rollback_recovery.js
|
||||
ticket: SERVER-65300
|
||||
- test_file: jstests/aggregation/agg_infinite_recursion.js
|
||||
ticket: SERVER-65773
|
||||
- test_file: jstests/sharding/migration_retries_on_write_conflict_exceptions.js
|
||||
ticket: SERVER-65947
|
||||
- test_file: jstests/aggregation/match_no_swap_rand.js
|
||||
ticket: SERVER-66072
|
||||
- test_file: jstests/sharding/data_size_aware_balancing_sessions_collection.js
|
||||
ticket: SERVER-66078
|
||||
- test_file: jstests/replsets/initial_sync_with_partial_transaction.js
|
||||
ticket: SERVER-66089
|
||||
- test_file: jstests/core/or_to_in.js
|
||||
ticket: SERVER-66379
|
||||
- test_file: jstests/core/where_multiple_plans.js
|
||||
ticket: SERVER-66389
|
||||
- test_file: jstests/sharding/resharding_metrics.js
|
||||
ticket: SERVER-66422
|
||||
- test_file: jstests/concurrency/fsm_workloads/find_flip_sbe_enabled.js
|
||||
ticket: SERVER-66445
|
||||
- test_file: jstests/core/index_stats.js
|
||||
ticket: SERVER-66445
|
||||
- test_file: jstests/core/wildcard_index_cached_plans.js
|
||||
ticket: SERVER-66445
|
||||
- test_file: jstests/core/index_filter_commands_invalidate_plan_cache_entries.js
|
||||
ticket: SERVER-66445
|
||||
- test_file: jstests/core/idhack.js
|
||||
ticket: SERVER-66445
|
||||
- test_file: jstests/core/sbe/sbe_explain_rejected_plans.js
|
||||
ticket: SERVER-66445
|
||||
- test_file: jstests/core/plan_cache_list_shapes.js
|
||||
ticket: SERVER-66445
|
||||
- test_file: jstests/core/plan_cache_list_plans.js
|
||||
ticket: SERVER-66445
|
||||
- test_file: jstests/replsets/dbhash_lock_acquisition.js
|
||||
ticket: SERVER-66719
|
||||
- test_file: jstests/replsets/prepared_transaction_kill_during_step_up_refresh.js
|
||||
ticket: SERVER-66854
|
||||
- test_file: jstests/replsets/step_up_kill_abort_transactions.js
|
||||
ticket: SERVER-66854
|
||||
- test_file: jstests/sharding/drop_index_fails_if_multikey_index_is_last_compatible.js
|
||||
ticket: SERVER-67299
|
||||
- test_file: jstests/sharding/move_chunk_interrupt_postimage.js
|
||||
ticket: SERVER-67492
|
||||
- test_file: jstests/replsets/optime.js
|
||||
ticket: SERVER-67532
|
||||
- test_file: jstests/replsets/internal_sessions_reaping_basic.js
|
||||
ticket: SERVER-67723
|
||||
- test_file: jstests/sharding/collection_uuid_shard_capped_collection.js
|
||||
ticket: SERVER-67885
|
||||
- test_file: jstests/core/txns/txn_ops_allowed_on_buckets_coll.js
|
||||
ticket: SERVER-68556
|
||||
- test_file: jstests/core/txns/no_writes_to_system_collections_in_txn.js
|
||||
ticket: SERVER-68556
|
||||
- test_file: jstests/sharding/resharding_temp_ns_routing_info_unsharded.js
|
||||
ticket: SERVER-68628
|
||||
- test_file: jstests/sharding/move_chunk_interrupt_postimage.js
|
||||
ticket: SERVER-68728
|
||||
- test_file: src/mongo/db/modules/enterprise/jstests/fcbis/fcbis_cannot_vote_twice_same_term.js
|
||||
ticket: SERVER-69861
|
||||
- test_file: src/mongo/db/modules/enterprise/jstests/fcbis/fcbis_election_during_storage_change.js
|
||||
ticket: SERVER-69861
|
||||
- test_file: jstests/aggregation/expressions/switch_errors.js
|
||||
ticket: SERVER-70190
|
||||
- test_file: jstests/core/cover_null_queries.js
|
||||
ticket: SERVER-70436
|
||||
- test_file: jstests/sharding/prepare_transaction_then_migrate.js
|
||||
ticket: SERVER-68361
|
||||
all: []
|
||||
suites: null
|
||||
|
||||
@ -310,34 +310,6 @@ buildvariants:
|
||||
- name: sharding_gen
|
||||
- name: sharding_opportunistic_secondary_targeting_gen
|
||||
|
||||
- <<: *linux-64-debug-required-template
|
||||
name: &linux-64-debug-wtdevelop linux-64-debug-wtdevelop
|
||||
display_name: "~ Linux DEBUG WiredTiger develop"
|
||||
cron: "0 */4 * * *" # From the ${project_required_suggested_cron} parameter
|
||||
modules:
|
||||
- wtdevelop
|
||||
depends_on:
|
||||
- name: archive_dist_test
|
||||
variant: &linux_x86_dynamic_debug_wtdevelop_compile_variant_name linux-x86-dynamic-debug-wtdevelop-compile
|
||||
- name: version_gen
|
||||
variant: generate-tasks-for-version
|
||||
expansions:
|
||||
use_wt_develop: true
|
||||
resmoke_jobs_factor: 0.5 # Avoid starting too many mongod's
|
||||
compile_flags: >-
|
||||
--dbg=on
|
||||
--opt=on
|
||||
-j$(grep -c ^processor /proc/cpuinfo)
|
||||
--variables-files=etc/scons/mongodbtoolchain_v3_gcc.vars
|
||||
--enable-free-mon=on
|
||||
--enable-http-client=on
|
||||
--link-model=dynamic
|
||||
scons_cache_mode: all
|
||||
scons_cache_scope: shared
|
||||
num_scons_link_jobs_available: 0.99
|
||||
test_flags: --excludeWithAnyTags=requires_http_client
|
||||
compile_variant: *linux_x86_dynamic_debug_wtdevelop_compile_variant_name
|
||||
|
||||
- name: &tla-plus tla-plus
|
||||
display_name: TLA+
|
||||
run_on:
|
||||
@ -987,7 +959,7 @@ buildvariants:
|
||||
# To force disable feature flags even on the all feature flags variant, please use this file:
|
||||
# buildscripts/resmokeconfig/fully_disabled_feature_flags.yml
|
||||
test_flags: >-
|
||||
--runAllFeatureFlagTests
|
||||
--additionalFeatureFlagsFile all_feature_flags.txt
|
||||
--excludeWithAnyTags=incompatible_with_windows_tls
|
||||
--excludeWithAnyTags=incompatible_with_shard_merge
|
||||
external_auth_jobs_max: 1
|
||||
@ -1577,7 +1549,7 @@ buildvariants:
|
||||
# To force disable feature flags even on the all feature flags variant, please use this file:
|
||||
# buildscripts/resmokeconfig/fully_disabled_feature_flags.yml
|
||||
test_flags: >-
|
||||
--runAllFeatureFlagTests
|
||||
--additionalFeatureFlagsFile all_feature_flags.txt
|
||||
--excludeWithAnyTags=incompatible_with_shard_merge
|
||||
tasks:
|
||||
- name: cqf
|
||||
@ -1873,7 +1845,7 @@ buildvariants:
|
||||
<<: *linux-x86-multiversion-expansions-template
|
||||
# No feature flag tests since they aren't compatible with the older binaries.
|
||||
test_flags: >-
|
||||
--runAllFeatureFlagsNoTests
|
||||
--runNoFeatureFlagTests
|
||||
--excludeWithAnyTags=incompatible_with_shard_merge
|
||||
|
||||
- <<: *linux-x86-multiversion-template
|
||||
@ -2251,7 +2223,7 @@ buildvariants:
|
||||
# buildscripts/resmokeconfig/fully_disabled_feature_flags.yml
|
||||
large_distro_name: rhel80-build
|
||||
test_flags: >-
|
||||
--runAllFeatureFlagTests
|
||||
--additionalFeatureFlagsFile all_feature_flags.txt
|
||||
--excludeWithAnyTags=incompatible_with_shard_merge
|
||||
separate_debug: off
|
||||
tasks:
|
||||
@ -2433,7 +2405,7 @@ buildvariants:
|
||||
test_flags: >-
|
||||
--excludeWithAnyTags=requires_ocsp_stapling
|
||||
--excludeWithAnyTags=incompatible_with_shard_merge
|
||||
--runAllFeatureFlagTests
|
||||
--additionalFeatureFlagsFile all_feature_flags.txt
|
||||
multiversion_platform: rhel80
|
||||
multiversion_edition: enterprise
|
||||
resmoke_jobs_factor: 0.3 # Avoid starting too many mongod's under UBSAN build.
|
||||
@ -2616,7 +2588,7 @@ buildvariants:
|
||||
test_flags: >-
|
||||
--excludeWithAnyTags=requires_ocsp_stapling
|
||||
--excludeWithAnyTags=incompatible_with_shard_merge
|
||||
--runAllFeatureFlagTests
|
||||
--additionalFeatureFlagsFile all_feature_flags.txt
|
||||
tasks:
|
||||
- name: jsCore
|
||||
- name: jsCore_txns
|
||||
@ -3328,7 +3300,7 @@ buildvariants:
|
||||
target_resmoke_time: 30
|
||||
max_sub_suites: 3
|
||||
test_flags: >-
|
||||
--runAllFeatureFlagTests
|
||||
--additionalFeatureFlagsFile all_feature_flags.txt
|
||||
--excludeWithAnyTags=resource_intensive
|
||||
--excludeWithAnyTags=incompatible_with_shard_merge
|
||||
|
||||
@ -3388,18 +3360,6 @@ buildvariants:
|
||||
- windows-vsCurrent-large
|
||||
- name: .benchmarks !benchmarks_orphaned
|
||||
|
||||
- <<: *enterprise-windows-nopush-template
|
||||
name: &enterprise-windows-wtdevelop enterprise-windows-wtdevelop
|
||||
display_name: "~ Enterprise Windows WiredTiger develop"
|
||||
cron: "0 */4 * * *" # From the ${project_required_suggested_cron} parameter
|
||||
modules:
|
||||
- enterprise
|
||||
- wtdevelop
|
||||
expansions:
|
||||
<<: *enterprise-windows-nopush-expansions-template
|
||||
use_wt_develop: true
|
||||
compile_variant: *enterprise-windows-wtdevelop
|
||||
|
||||
### QO & QE Patch-Specific Build Variants ###
|
||||
- <<: *enterprise-windows-nopush-template
|
||||
name: &windows-compile-query-patch-only windows-compile-query-patch-only
|
||||
|
||||
@ -6,16 +6,336 @@ include:
|
||||
- filename: etc/evergreen_yml_components/variants/atlas.yml
|
||||
- filename: etc/evergreen_yml_components/variants/misc_release.yml
|
||||
### Comment out when using this file for a LTS or Rapid release branch. ###
|
||||
- filename: etc/evergreen_yml_components/variants/ibm.yml
|
||||
# - filename: etc/evergreen_yml_components/variants/ibm.yml
|
||||
### Uncomment when using this file for a LTS release branch. ###
|
||||
# - filename: etc/evergreen_yml_components/variants/in_memory.yml
|
||||
### Uncomment when using this file for a LTS or Rapid release branch. ###
|
||||
# - filename: etc/evergreen_yml_components/variants/sanitizer.yml
|
||||
- filename: etc/evergreen_yml_components/variants/sanitizer.yml
|
||||
### Uncomment when using this file for a LTS or Rapid release branch. ###
|
||||
# - filename: etc/evergreen_yml_components/variants/ninja.yml
|
||||
- filename: etc/evergreen_yml_components/variants/ninja.yml
|
||||
- filename: etc/evergreen_yml_components/variants/compile_static_analysis.yml
|
||||
|
||||
|
||||
parameters:
|
||||
- key: evergreen_config_file_path
|
||||
value: "etc/evergreen_nightly.yml"
|
||||
description: "path to this file"
|
||||
|
||||
|
||||
variables:
|
||||
# Common compile variant dependency specifications.
|
||||
- &linux_x86_dynamic_compile_variant_dependency
|
||||
depends_on:
|
||||
- name: archive_dist_test_debug
|
||||
variant: &linux_x86_dynamic_compile_variant_name linux-x86-dynamic-compile-required
|
||||
- name: version_gen
|
||||
variant: generate-tasks-for-version
|
||||
|
||||
- &linux_x86_dynamic_debug_compile_variant_dependency
|
||||
depends_on:
|
||||
- name: archive_dist_test_debug
|
||||
variant: &linux_x86_dynamic_debug_compile_variant_name linux-x86-dynamic-debug-compile-required
|
||||
- name: version_gen
|
||||
variant: generate-tasks-for-version
|
||||
|
||||
- &linux_debug_aubsan_compile_variant_dependency
|
||||
depends_on:
|
||||
- name: archive_dist_test_debug
|
||||
variant: &linux_debug_aubsan_compile_variant_name linux-debug-aubsan-compile-required
|
||||
- name: version_gen
|
||||
variant: generate-tasks-for-version
|
||||
|
||||
- &windows_compile_variant_dependency
|
||||
depends_on:
|
||||
- name: archive_dist_test_debug
|
||||
variant: &windows_compile_variant_name windows-compile-required
|
||||
- name: version_gen
|
||||
variant: generate-tasks-for-version
|
||||
|
||||
- &linux_x86_generic_expansions
|
||||
multiversion_platform: rhel80
|
||||
multiversion_edition: enterprise
|
||||
repo_edition: enterprise
|
||||
large_distro_name: rhel80-medium
|
||||
num_scons_link_jobs_available: 0.99
|
||||
compile_variant: *linux_x86_dynamic_compile_variant_name
|
||||
|
||||
|
||||
buildvariants:
|
||||
- &linux-64-debug-required-template
|
||||
<<: *linux_x86_dynamic_debug_compile_variant_dependency
|
||||
name: &linux-64-debug-required linux-64-debug-required
|
||||
display_name: "! Linux x86 Shared Library DEBUG"
|
||||
cron: "0 */4 * * *" # From the ${project_required_suggested_cron} parameter
|
||||
run_on:
|
||||
- rhel80-medium
|
||||
expansions:
|
||||
resmoke_jobs_factor: 0.5 # Avoid starting too many mongod's
|
||||
test_flags: --excludeWithAnyTags=requires_http_client
|
||||
target_resmoke_time: 15
|
||||
max_sub_suites: 5
|
||||
large_distro_name: rhel80-medium
|
||||
compile_variant: *linux_x86_dynamic_debug_compile_variant_name
|
||||
tasks:
|
||||
- name: .aggregation !.encrypt !.feature_flag_guarded
|
||||
- name: .auth !.audit !.multiversion
|
||||
- name: .causally_consistent !.wo_snapshot
|
||||
- name: .change_streams !.secondary_reads
|
||||
- name: .clustered_collections
|
||||
- name: .misc_js
|
||||
- name: disk_wiredtiger
|
||||
- name: free_monitoring
|
||||
- name: .jscore .common
|
||||
- name: jsCore_txns_large_txns_format
|
||||
- name: json_schema
|
||||
- name: query_golden_classic
|
||||
- name: libunwind_tests
|
||||
- name: .multi_shard
|
||||
- name: multi_stmt_txn_jscore_passthrough_with_migration_gen
|
||||
- name: .ocsp
|
||||
- name: .read_write_concern
|
||||
- name: .replica_sets !.encrypt !.ignore_non_generated_replica_sets_jscore_passthrough !.fcbis
|
||||
- name: replica_sets_jscore_passthrough_gen
|
||||
- name: replica_sets_reconfig_jscore_passthrough_gen
|
||||
- name: replica_sets_reconfig_jscore_stepdown_passthrough_gen
|
||||
- name: .retry
|
||||
- name: .read_only
|
||||
- name: session_jscore_passthrough
|
||||
- name: sharded_multi_stmt_txn_jscore_passthrough
|
||||
- name: .sharding .jscore !.wo_snapshot
|
||||
- name: sharding_gen
|
||||
- name: sharding_opportunistic_secondary_targeting_gen
|
||||
|
||||
- &enterprise-windows-required-template
|
||||
<<: *windows_compile_variant_dependency
|
||||
name: &enterprise-windows-required enterprise-windows-required
|
||||
display_name: "! Enterprise Windows"
|
||||
cron: "0 */4 * * *" # From the ${project_required_suggested_cron} parameter
|
||||
modules:
|
||||
- enterprise
|
||||
run_on:
|
||||
- windows-vsCurrent-small
|
||||
expansions: &windows_required_expansions
|
||||
compile_variant: *windows_compile_variant_name
|
||||
burn_in_tests_build_variant: enterprise-windows-required
|
||||
exe: ".exe"
|
||||
content_type: application/zip
|
||||
python: '/cygdrive/c/python/python37/python.exe'
|
||||
ext: zip
|
||||
multiversion_platform: windows
|
||||
multiversion_edition: enterprise
|
||||
jstestfuzz_num_generated_files: 35
|
||||
target_resmoke_time: 20
|
||||
max_sub_suites: 5
|
||||
large_distro_name: windows-vsCurrent-large
|
||||
push_path: windows
|
||||
push_bucket: downloads.10gen.com
|
||||
push_name: windows
|
||||
push_arch: x86_64-enterprise
|
||||
test_flags: &windows_common_test_excludes --excludeWithAnyTags=incompatible_with_windows_tls
|
||||
external_auth_jobs_max: 1
|
||||
tasks:
|
||||
# - name: burn_in_tests_gen
|
||||
- name: audit
|
||||
- name: auth_audit_gen
|
||||
- name: causally_consistent_jscore_txns_passthrough
|
||||
distros:
|
||||
- windows-vsCurrent-large
|
||||
- name: .encrypt !.aggregation !.replica_sets !.sharding !.jscore
|
||||
- name: external_auth
|
||||
- name: external_auth_aws
|
||||
- name: external_auth_windows
|
||||
distros:
|
||||
- windows-2016-dc
|
||||
- name: .jscore .common !.sharding
|
||||
- name: jsCore_auth
|
||||
- name: jsCore_ese
|
||||
- name: jsCore_txns_large_txns_format
|
||||
- name: .jstestfuzz .common
|
||||
- name: mqlrun
|
||||
- name: noPassthrough_gen
|
||||
- name: noPassthroughWithMongod_gen
|
||||
- name: .replica_sets .common !.ignore_non_generated_replica_sets_jscore_passthrough
|
||||
- name: .replica_sets .multi_oplog !.ignore_non_generated_replica_sets_jscore_passthrough
|
||||
- name: replica_sets_jscore_passthrough_gen
|
||||
distros:
|
||||
- windows-vsCurrent-large
|
||||
- name: replica_sets_ese_gen
|
||||
- name: sasl
|
||||
- name: .sharding .txns
|
||||
- name: sharding_auth_gen
|
||||
- name: sharding_auth_audit_gen
|
||||
- name: sharding_ese_gen
|
||||
|
||||
- &enterprise-rhel-80-64-bit-dynamic-required-template
|
||||
<<: *linux_x86_dynamic_compile_variant_dependency
|
||||
name: &enterprise-rhel-80-64-bit-dynamic-required enterprise-rhel-80-64-bit-dynamic-required
|
||||
display_name: "! Shared Library Enterprise RHEL 8.0"
|
||||
cron: "0 */4 * * *" # From the ${project_required_suggested_cron} parameter
|
||||
modules:
|
||||
- enterprise
|
||||
run_on:
|
||||
- rhel80-small
|
||||
expansions: &enterprise-rhel-80-64-bit-dynamic-expansions
|
||||
<<: *linux_x86_generic_expansions
|
||||
scons_cache_scope: shared
|
||||
scons_cache_mode: all
|
||||
has_packages: false
|
||||
jstestfuzz_num_generated_files: 40
|
||||
jstestfuzz_concurrent_num_files: 10
|
||||
target_resmoke_time: 10
|
||||
max_sub_suites: 5
|
||||
idle_timeout_factor: 1.5
|
||||
exec_timeout_factor: 1.5
|
||||
large_distro_name: rhel80-medium
|
||||
burn_in_tag_buildvariants: >-
|
||||
enterprise-rhel-80-64-bit-inmem
|
||||
enterprise-rhel-80-64-bit-multiversion
|
||||
burn_in_tag_compile_task_group_name: compile_and_archive_dist_test_TG
|
||||
burn_in_tag_compile_distro: rhel80-xlarge
|
||||
depends_on:
|
||||
- name: archive_dist_test
|
||||
variant: *linux_x86_dynamic_compile_variant_name
|
||||
- name: version_gen
|
||||
variant: generate-tasks-for-version
|
||||
- name: version_burn_in_gen
|
||||
variant: generate-tasks-for-version
|
||||
tasks:
|
||||
# - name: burn_in_tests_gen
|
||||
- name: .aggfuzzer !.feature_flag_guarded
|
||||
- name: .aggregation !.feature_flag_guarded
|
||||
- name: audit
|
||||
- name: .auth
|
||||
- name: unittest_shell_hang_analyzer_gen
|
||||
- name: .causally_consistent !.sharding
|
||||
- name: .change_streams
|
||||
- name: .change_stream_fuzzer
|
||||
- name: .misc_js
|
||||
- name: .concurrency !.large !.ubsan !.no_txns !.debug_only
|
||||
- name: .concurrency .large !.ubsan !.no_txns !.debug_only
|
||||
distros:
|
||||
- rhel80-medium
|
||||
- name: disk_wiredtiger
|
||||
- name: .encrypt
|
||||
- name: idl_tests
|
||||
- name: initial_sync_fuzzer_gen
|
||||
- name: jsCore
|
||||
distros:
|
||||
- rhel80-xlarge
|
||||
- name: .jscore .common !jsCore
|
||||
- name: jsCore_minimum_batch_size
|
||||
- name: jsCore_txns_large_txns_format
|
||||
- name: json_schema
|
||||
- name: .jstestfuzz !.flow_control # Flow control jstestfuzz take longer.
|
||||
- name: libunwind_tests
|
||||
- name: mqlrun
|
||||
- name: .multi_shard
|
||||
- name: multi_stmt_txn_jscore_passthrough_with_migration_gen
|
||||
- name: multiversion_gen
|
||||
- name: .query_fuzzer
|
||||
- name: .random_multiversion_ds
|
||||
- name: .read_write_concern .large
|
||||
distros:
|
||||
- rhel80-medium
|
||||
- name: .read_write_concern !.large
|
||||
- name: .replica_sets !.encrypt !.auth
|
||||
distros:
|
||||
- rhel80-xlarge
|
||||
- name: replica_sets_api_version_jscore_passthrough_gen
|
||||
- name: replica_sets_reconfig_jscore_passthrough_gen
|
||||
- name: replica_sets_reconfig_jscore_stepdown_passthrough_gen
|
||||
distros:
|
||||
- rhel80-xlarge
|
||||
- name: replica_sets_reconfig_kill_primary_jscore_passthrough_gen
|
||||
distros:
|
||||
- rhel80-xlarge
|
||||
- name: retryable_writes_jscore_passthrough_gen
|
||||
- name: retryable_writes_jscore_stepdown_passthrough_gen
|
||||
distros:
|
||||
- rhel80-medium
|
||||
- name: .read_only
|
||||
- name: .rollbackfuzzer
|
||||
- name: sasl
|
||||
- name: search
|
||||
- name: search_auth
|
||||
- name: search_ssl
|
||||
- name: session_jscore_passthrough
|
||||
- name: .sharding .jscore !.wo_snapshot !.multi_stmt
|
||||
- name: sharding_api_version_jscore_passthrough_gen
|
||||
- name: .sharding .txns
|
||||
- name: .sharding .common
|
||||
- name: .updatefuzzer
|
||||
- name: secondary_reads_passthrough_gen
|
||||
- name: .serverless
|
||||
distros:
|
||||
- rhel80-xlarge
|
||||
|
||||
- &rhel80-debug-aubsan-lite-required-template
|
||||
<<: *linux_debug_aubsan_compile_variant_dependency
|
||||
name: &rhel80-debug-aubsan-lite-required rhel80-debug-aubsan-lite-required
|
||||
display_name: "! Shared Library {A,UB}SAN Enterprise RHEL 8.0 DEBUG"
|
||||
cron: "0 */4 * * *" # From the ${project_required_suggested_cron} parameter
|
||||
modules:
|
||||
- enterprise
|
||||
run_on:
|
||||
- rhel80-build
|
||||
expansions: &aubsan-lite-required-expansions
|
||||
compile_variant: *linux_debug_aubsan_compile_variant_name
|
||||
lang_environment: LANG=C
|
||||
# If you add anything to san_options, make sure the appropriate changes are
|
||||
# also made to SConstruct.
|
||||
san_options: >-
|
||||
UBSAN_OPTIONS="print_stacktrace=1:external_symbolizer_path=/opt/mongodbtoolchain/v3/bin/llvm-symbolizer"
|
||||
LSAN_OPTIONS="suppressions=etc/lsan.suppressions:report_objects=1"
|
||||
ASAN_OPTIONS="detect_leaks=1:check_initialization_order=true:strict_init_order=true:abort_on_error=1:disable_coredump=0:handle_abort=1:strict_string_checks=true:detect_invalid_pointer_pairs=1:external_symbolizer_path=/opt/mongodbtoolchain/v3/bin/llvm-symbolizer"
|
||||
test_flags: --excludeWithAnyTags=requires_ocsp_stapling
|
||||
resmoke_jobs_factor: 0.3 # Avoid starting too many mongod's under {A,UB}SAN build.
|
||||
hang_analyzer_dump_core: false
|
||||
max_sub_suites: 3
|
||||
num_scons_link_jobs_available: 0.99
|
||||
large_distro_name: rhel80-build
|
||||
tasks:
|
||||
- name: jsCore
|
||||
- name: jsCore_txns
|
||||
|
||||
- <<: *enterprise-rhel-80-64-bit-dynamic-required-template
|
||||
name: &commit-queue commit-queue
|
||||
display_name: "~ Commit Queue"
|
||||
cron: "0 4 * * 0" # From the ${project_weekly_cron} parameter
|
||||
stepback: false
|
||||
expansions:
|
||||
<<: *linux_x86_generic_expansions
|
||||
scons_cache_scope: shared
|
||||
scons_cache_mode: all
|
||||
has_packages: false
|
||||
compile_flags: >-
|
||||
--ssl
|
||||
MONGO_DISTMOD=rhel80
|
||||
-j$(grep -c ^processor /proc/cpuinfo)
|
||||
--variables-files=etc/scons/mongodbtoolchain_v3_gcc.vars
|
||||
--link-model=dynamic
|
||||
crypt_task_compile_flags: >-
|
||||
SHLINKFLAGS_EXTRA="-Wl,-Bsymbolic
|
||||
-Wl,--no-gnu-unique"
|
||||
CCFLAGS="-fno-gnu-unique"
|
||||
clang_tidy_toolchain: v3
|
||||
compile_variant: *commit-queue
|
||||
depends_on: []
|
||||
tasks:
|
||||
- name: compile_test_and_package_parallel_core_stream_TG
|
||||
distros:
|
||||
- rhel80-xlarge-commitqueue
|
||||
- name: compile_test_and_package_parallel_unittest_stream_TG
|
||||
distros:
|
||||
- rhel80-xlarge-commitqueue
|
||||
- name: compile_test_and_package_parallel_dbtest_stream_TG
|
||||
distros:
|
||||
- rhel80-xlarge-commitqueue
|
||||
- name: jsCore
|
||||
distros:
|
||||
- rhel80-xlarge-commitqueue
|
||||
- name: .lint
|
||||
- name: test_api_version_compatibility
|
||||
- name: validate_commit_message
|
||||
- name: check_feature_flag_tags
|
||||
- name: compile_venv_deps_check
|
||||
|
||||
@ -66,7 +66,7 @@ modules:
|
||||
- name: enterprise
|
||||
repo: git@github.com:10gen/mongo-enterprise-modules.git
|
||||
prefix: src/mongo/db/modules
|
||||
branch: master
|
||||
branch: v6.2
|
||||
|
||||
- name: wtdevelop
|
||||
repo: git@github.com:wiredtiger/wiredtiger.git
|
||||
@ -608,7 +608,7 @@ functions:
|
||||
"get buildnumber": &get_buildnumber
|
||||
command: keyval.inc
|
||||
params:
|
||||
key: "${build_variant}_master"
|
||||
key: "${build_variant}_v6.2"
|
||||
destination: "builder_num"
|
||||
|
||||
"run diskstats": &run_diskstats
|
||||
@ -5017,7 +5017,9 @@ tasks:
|
||||
- func: "run tests"
|
||||
vars:
|
||||
suite: core
|
||||
resmoke_args: --fuzzMongodConfigs
|
||||
resmoke_args: >-
|
||||
--fuzzMongodConfigs
|
||||
--excludeWithAnyTags=does_not_support_config_fuzzer
|
||||
|
||||
- <<: *task_template
|
||||
name: config_fuzzer_concurrency
|
||||
@ -5027,7 +5029,9 @@ tasks:
|
||||
- func: "run tests"
|
||||
vars:
|
||||
suite: concurrency
|
||||
resmoke_args: --fuzzMongodConfigs
|
||||
resmoke_args: >-
|
||||
--fuzzMongodConfigs
|
||||
--excludeWithAnyTags=does_not_support_config_fuzzer
|
||||
|
||||
- <<: *task_template
|
||||
name: config_fuzzer_simulate_crash_concurrency_replication
|
||||
@ -5037,7 +5041,9 @@ tasks:
|
||||
- func: "run tests"
|
||||
vars:
|
||||
suite: simulate_crash_concurrency_replication
|
||||
resmoke_args: --fuzzMongodConfigs
|
||||
resmoke_args: >-
|
||||
--fuzzMongodConfigs
|
||||
--excludeWithAnyTags=does_not_support_config_fuzzer
|
||||
|
||||
- <<: *task_template
|
||||
name: config_fuzzer_concurrency_replication
|
||||
@ -5047,7 +5053,9 @@ tasks:
|
||||
- func: "run tests"
|
||||
vars:
|
||||
suite: concurrency_replication
|
||||
resmoke_args: --fuzzMongodConfigs
|
||||
resmoke_args: >-
|
||||
--fuzzMongodConfigs
|
||||
--excludeWithAnyTags=does_not_support_config_fuzzer
|
||||
|
||||
- <<: *task_template
|
||||
name: config_fuzzer_replica_sets_jscore_passthrough
|
||||
@ -5057,7 +5065,9 @@ tasks:
|
||||
- func: "run tests"
|
||||
vars:
|
||||
suite: replica_sets_jscore_passthrough
|
||||
resmoke_args: --fuzzMongodConfigs
|
||||
resmoke_args: >-
|
||||
--fuzzMongodConfigs
|
||||
--excludeWithAnyTags=does_not_support_config_fuzzer
|
||||
|
||||
- <<: *task_template
|
||||
name: jsCore_ese
|
||||
@ -6173,6 +6183,7 @@ tasks:
|
||||
- func: "generate resmoke tasks"
|
||||
vars:
|
||||
use_large_distro: "true"
|
||||
multiversion_exclude_tags_version: last_lts
|
||||
|
||||
- <<: *gen_task_template
|
||||
name: sharding_opportunistic_secondary_targeting_gen
|
||||
@ -6827,19 +6838,6 @@ tasks:
|
||||
- *set_up_venv
|
||||
- func: "run package test"
|
||||
|
||||
- name: test_packages_complete
|
||||
tags: []
|
||||
depends_on:
|
||||
- name: package
|
||||
commands:
|
||||
- command: manifest.load
|
||||
- func: "git get project and add git tag"
|
||||
- *f_expansions_write
|
||||
- *kill_processes
|
||||
- *cleanup_environment
|
||||
- *set_up_venv
|
||||
- func: "run complete package test"
|
||||
|
||||
- name: package
|
||||
tags: []
|
||||
depends_on:
|
||||
|
||||
@ -88,17 +88,6 @@ buildvariants:
|
||||
- name: generate_buildid_to_debug_symbols_mapping
|
||||
- name: .integration !.audit
|
||||
|
||||
- <<: *generic_linux_compile_params
|
||||
name: &linux-x86-dynamic-debug-wtdevelop-compile linux-x86-dynamic-debug-wtdevelop-compile
|
||||
display_name: "~ Linux WiredTiger develop DEBUG Compile"
|
||||
activate: false
|
||||
modules:
|
||||
- wtdevelop
|
||||
expansions:
|
||||
<<: *linux_debug_compile_expansions
|
||||
use_wt_develop: true
|
||||
compile_variant: *linux-x86-dynamic-debug-wtdevelop-compile
|
||||
|
||||
- <<: *generic_linux_compile_params
|
||||
name: &linux-debug-aubsan-compile-required linux-debug-aubsan-compile-required
|
||||
display_name: "! Linux x86 Shared Library {A,UB}SAN Compile"
|
||||
|
||||
@ -590,8 +590,6 @@ buildvariants:
|
||||
distros:
|
||||
- amazon2022-arm64-large
|
||||
- name: .publish_crypt
|
||||
distros:
|
||||
- amazon2022-arm64-large
|
||||
- name: secondary_reads_passthrough_gen
|
||||
- name: server_discovery_and_monitoring_json_test_TG
|
||||
- name: .serverless
|
||||
@ -2097,9 +2095,6 @@ buildvariants:
|
||||
- name: test_packages
|
||||
distros:
|
||||
- ubuntu2204-large
|
||||
- name: test_packages_complete
|
||||
distros:
|
||||
- ubuntu2204-large
|
||||
- name: .publish
|
||||
- name: .publish_test
|
||||
- name: generate_buildid_to_debug_symbols_mapping
|
||||
@ -2256,9 +2251,6 @@ buildvariants:
|
||||
- name: test_packages
|
||||
distros:
|
||||
- ubuntu2204-arm64-large
|
||||
- name: test_packages_complete
|
||||
distros:
|
||||
- ubuntu2204-arm64-large
|
||||
- name: .publish
|
||||
- name: .publish_test
|
||||
- name: generate_buildid_to_debug_symbols_mapping
|
||||
|
||||
@ -99,7 +99,7 @@ modules:
|
||||
- name: enterprise
|
||||
repo: git@github.com:10gen/mongo-enterprise-modules.git
|
||||
prefix: src/mongo/db/modules
|
||||
branch: master
|
||||
branch: v6.2
|
||||
- name: mongo-tools
|
||||
repo: git@github.com:mongodb/mongo-tools.git
|
||||
prefix: mongo-tools/src/github.com/mongodb
|
||||
@ -320,7 +320,7 @@ functions:
|
||||
set -o errexit
|
||||
set -o verbose
|
||||
source "${workdir}/compile_venv/bin/activate"
|
||||
python ./buildscripts/idl/gen_all_feature_flag_list.py --import-dir src --import-dir src/mongo/db/modules/enterprise/src
|
||||
python ./buildscripts/idl/gen_all_feature_flag_list.py
|
||||
mkdir -p mongodb/feature_flags
|
||||
cp ./all_feature_flags.txt mongodb/feature_flags
|
||||
- command: shell.exec
|
||||
|
||||
1179
etc/system_perf.yml
1179
etc/system_perf.yml
File diff suppressed because it is too large
Load Diff
@ -12,7 +12,7 @@ enterprise_path="src/mongo/db/modules/enterprise"
|
||||
diff_file_name="with_base_upstream.diff"
|
||||
|
||||
# get the list of feature flags from the patched version
|
||||
$python buildscripts/idl/gen_all_feature_flag_list.py --import-dir src --import-dir "$enterprise_path"/src
|
||||
$python buildscripts/idl/gen_all_feature_flag_list.py
|
||||
mv all_feature_flags.txt patch_all_feature_flags.txt
|
||||
|
||||
# get the list of feature flags from the base commit
|
||||
@ -29,7 +29,7 @@ if [ -s "$diff_file_name" ]; then
|
||||
fi
|
||||
popd
|
||||
|
||||
$python buildscripts/idl/gen_all_feature_flag_list.py --import-dir src --import-dir "$enterprise_path"/src
|
||||
$python buildscripts/idl/gen_all_feature_flag_list.py
|
||||
mv all_feature_flags.txt base_all_feature_flags.txt
|
||||
|
||||
# print out the list of tests that previously had feature flag tag, that was
|
||||
|
||||
@ -6,4 +6,4 @@ cd src
|
||||
set -o errexit
|
||||
set -o verbose
|
||||
activate_venv
|
||||
$python buildscripts/idl/gen_all_feature_flag_list.py --import-dir src --import-dir src/mongo/db/modules/enterprise/src
|
||||
$python buildscripts/idl/gen_all_feature_flag_list.py
|
||||
|
||||
@ -13,4 +13,5 @@ PATH=$PATH:$HOME:/ ./mongo-task-generator \
|
||||
--evg-auth-file ./.evergreen.yml \
|
||||
--evg-project-file ${evergreen_config_file_path} \
|
||||
--generate-sub-tasks-config etc/generate_subtasks_config.yml \
|
||||
--s3-test-stats-endpoint https://mongo-test-stats.s3.amazonaws.com \
|
||||
$@
|
||||
|
||||
@ -13,5 +13,6 @@ PATH=$PATH:$HOME:/ ./mongo-task-generator \
|
||||
--evg-auth-file ./.evergreen.yml \
|
||||
--evg-project-file ${evergreen_config_file_path} \
|
||||
--generate-sub-tasks-config etc/generate_subtasks_config.yml \
|
||||
--s3-test-stats-endpoint https://mongo-test-stats.s3.amazonaws.com \
|
||||
--burn-in \
|
||||
$@
|
||||
|
||||
@ -6,8 +6,6 @@ cd src/jstestfuzz
|
||||
set -o errexit
|
||||
set -o verbose
|
||||
|
||||
add_nodejs_to_path
|
||||
|
||||
if [ -f "../minimizer-outputs.json" ]; then
|
||||
eval npm run ${npm_command} -- -j "../minimizer-outputs.json"
|
||||
eval ./src/scripts/npm_run.sh ${npm_command} -- -j "../minimizer-outputs.json"
|
||||
fi
|
||||
|
||||
@ -1,13 +1,10 @@
|
||||
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" > /dev/null 2>&1 && pwd)"
|
||||
. "$DIR/prelude.sh"
|
||||
|
||||
set -evo pipefail
|
||||
|
||||
cd src/jstestfuzz
|
||||
|
||||
set -o errexit
|
||||
set -o verbose
|
||||
|
||||
add_nodejs_to_path
|
||||
|
||||
in_patch_build_flag=""
|
||||
if [[ "${is_patch}" = "true" ]]; then
|
||||
case "${npm_command}" in
|
||||
@ -17,4 +14,4 @@ if [[ "${is_patch}" = "true" ]]; then
|
||||
esac
|
||||
fi
|
||||
|
||||
eval npm run "${npm_command}" -- "${jstestfuzz_vars}" "${in_patch_build_flag}" --branch "${branch_name}"
|
||||
./src/scripts/npm_run.sh ${npm_command} -- ${jstestfuzz_vars} ${in_patch_build_flag} --branch ${branch_name}
|
||||
|
||||
@ -6,11 +6,4 @@ cd src
|
||||
set -o errexit
|
||||
set -o verbose
|
||||
|
||||
add_nodejs_to_path
|
||||
|
||||
git clone git@github.com:10gen/jstestfuzz.git
|
||||
|
||||
pushd jstestfuzz
|
||||
npm install
|
||||
npm run prepare
|
||||
popd
|
||||
|
||||
@ -1,17 +1,22 @@
|
||||
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" > /dev/null 2>&1 && pwd)"
|
||||
. "$DIR/prelude.sh"
|
||||
|
||||
cd src
|
||||
cd src/jstestfuzz
|
||||
|
||||
set -o pipefail
|
||||
set -o verbose
|
||||
|
||||
add_nodejs_to_path
|
||||
|
||||
# Run first with help which will do the install
|
||||
# Then we can run it in parallel
|
||||
./src/scripts/npm_run.sh --help
|
||||
# Run parse-jsfiles on 50 files at a time with 32 processes in parallel.
|
||||
find "$PWD/jstests" "$PWD/src/mongo/db/modules/enterprise" -name "*.js" -print | xargs -P 32 -L 50 npm run --prefix jstestfuzz parse-jsfiles -- | tee lint_fuzzer_sanity.log
|
||||
# Skip javascript files in third_party directory
|
||||
find "$PWD/../jstests" "$PWD/../src/mongo/db/modules/enterprise" -path "$PWD/../jstests/third_party" -prune -o -name "*.js" -print | xargs -P 32 -L 50 ./src/scripts/npm_run.sh parse-jsfiles -- | tee lint_fuzzer_sanity.log
|
||||
exit_code=$?
|
||||
|
||||
# Exit out of the jstestfuzz directory
|
||||
cd ..
|
||||
|
||||
activate_venv
|
||||
$python ./buildscripts/simple_report.py --test-name lint_fuzzer_sanity_all --log-file lint_fuzzer_sanity.log --exit-code $exit_code
|
||||
$python ./buildscripts/simple_report.py --test-name lint_fuzzer_sanity_all --log-file jstestfuzz/lint_fuzzer_sanity.log --exit-code $exit_code
|
||||
exit $exit_code
|
||||
|
||||
@ -7,15 +7,17 @@ set -o pipefail
|
||||
set -o verbose
|
||||
|
||||
activate_venv
|
||||
add_nodejs_to_path
|
||||
|
||||
mkdir -p jstestfuzzinput jstestfuzzoutput
|
||||
|
||||
indir="$(pwd)/jstestfuzzinput"
|
||||
outdir="$(pwd)/jstestfuzzoutput"
|
||||
# We need to be the jstestfuzz repo for node to install/run
|
||||
cd jstestfuzz
|
||||
|
||||
indir="$(pwd)/../jstestfuzzinput"
|
||||
outdir="$(pwd)/../jstestfuzzoutput"
|
||||
|
||||
# Grep all the js files from modified_and_created_patch_files.txt and put them into $indir.
|
||||
(grep -v "\.tpl\.js$" modified_and_created_patch_files.txt | grep ".*jstests/.*\.js$" | xargs -I {} cp {} $indir || true)
|
||||
(grep -v "\.tpl\.js$" ../modified_and_created_patch_files.txt | grep ".*jstests/.*\.js$" | xargs -I {} cp {} $indir || true)
|
||||
|
||||
# Count the number of files in $indir.
|
||||
if [[ "$(ls -A $indir)" ]]; then
|
||||
@ -26,10 +28,13 @@ if [[ "$(ls -A $indir)" ]]; then
|
||||
num_files=50
|
||||
fi
|
||||
|
||||
npm run --prefix jstestfuzz jstestfuzz -- --jsTestsDir $indir --out $outdir --numSourceFiles $num_files --numGeneratedFiles 50
|
||||
./src/scripts/npm_run.sh jstestfuzz -- --jsTestsDir $indir --out $outdir --numSourceFiles $num_files --numGeneratedFiles 50
|
||||
|
||||
# Run parse-jsfiles on 50 files at a time with 32 processes in parallel.
|
||||
ls -1 -d $outdir/* | xargs -P 32 -L 50 npm run --prefix jstestfuzz parse-jsfiles -- | tee lint_fuzzer_sanity.log
|
||||
ls -1 -d $outdir/* | xargs -P 32 -L 50 ./src/scripts/npm_run.sh parse-jsfiles -- | tee lint_fuzzer_sanity.log
|
||||
exit_code=$?
|
||||
$python ./buildscripts/simple_report.py --test-name lint_fuzzer_sanity_patch --log-file lint_fuzzer_sanity.log --exit-code $exit_code
|
||||
|
||||
# Exit out of the jstestfuzz directory
|
||||
cd ..
|
||||
$python ./buildscripts/simple_report.py --test-name lint_fuzzer_sanity_patch --log-file jstestfuzz/lint_fuzzer_sanity.log --exit-code $exit_code
|
||||
fi
|
||||
|
||||
@ -32,21 +32,6 @@ unset expansions_default_yaml
|
||||
unset script
|
||||
unset evergreen_dir
|
||||
|
||||
function add_nodejs_to_path {
|
||||
# Add node and npm binaries to PATH
|
||||
if [ "Windows_NT" = "$OS" ]; then
|
||||
# An "npm" directory might not have been created in %APPDATA% by the Windows installer.
|
||||
# Work around the issue by specifying a different %APPDATA% path.
|
||||
# See: https://github.com/nodejs/node-v0.x-archive/issues/8141
|
||||
export APPDATA=${workdir}/npm-app-data
|
||||
export PATH="$PATH:/cygdrive/c/Program Files (x86)/nodejs" # Windows location
|
||||
# TODO: this is to work around BUILD-8652
|
||||
cd "$(pwd -P | sed 's,cygdrive/c/,cygdrive/z/,')"
|
||||
else
|
||||
export PATH="$PATH:/opt/node/bin"
|
||||
fi
|
||||
}
|
||||
|
||||
function posix_workdir {
|
||||
if [ "Windows_NT" = "$OS" ]; then
|
||||
echo $(cygpath -u "${workdir}")
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
function setup_mongo_task_generator {
|
||||
if [ ! -f mongo-task-generator ]; then
|
||||
curl -L https://github.com/mongodb/mongo-task-generator/releases/download/v0.6.6/mongo-task-generator --output mongo-task-generator
|
||||
curl -L https://github.com/mongodb/mongo-task-generator/releases/download/v0.6.7/mongo-task-generator --output mongo-task-generator
|
||||
chmod +x mongo-task-generator
|
||||
fi
|
||||
}
|
||||
|
||||
@ -8,7 +8,7 @@ from pydantic import ValidationError
|
||||
if __name__ == "__main__" and __package__ is None:
|
||||
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
from buildscripts.metrics.metrics_datatypes import ToolingMetrics
|
||||
from buildscripts.metrics.metrics_datatypes import ResmokeToolingMetrics, SConsToolingMetrics
|
||||
from buildscripts.metrics.tooling_metrics_utils import _get_internal_tooling_metrics_client
|
||||
from evergreen.api import RetryingEvergreenApi
|
||||
|
||||
@ -20,43 +20,56 @@ except Exception as exc:
|
||||
print("Could not connect to Atlas cluster")
|
||||
raise exc
|
||||
|
||||
try:
|
||||
# Get metrics for the last week
|
||||
one_week_ago_datetime = datetime.datetime.utcnow() - datetime.timedelta(days=7)
|
||||
last_week_metrics = client.metrics.tooling_metrics.find(
|
||||
{"utc_starttime": {"$gt": one_week_ago_datetime}})
|
||||
|
||||
malformed_metrics = []
|
||||
invalid_metrics = []
|
||||
total_docs = 0
|
||||
def get_metrics_data(source, MetricsClass, lookback=7):
|
||||
try:
|
||||
# Get SCons metrics for the lookback period
|
||||
lookback_datetime = datetime.datetime.utcnow() - datetime.timedelta(days=lookback)
|
||||
last_week_metrics = client.metrics.tooling_metrics.find({
|
||||
"source": source,
|
||||
"utc_starttime": {"$gt": lookback_datetime},
|
||||
})
|
||||
|
||||
# Find any malformed/invalid documents in the cluster
|
||||
for doc in last_week_metrics:
|
||||
total_docs += 1
|
||||
try:
|
||||
metrics = ToolingMetrics(**doc)
|
||||
if metrics.is_malformed():
|
||||
malformed_metrics.append(doc['_id'])
|
||||
except ValidationError:
|
||||
invalid_metrics.append(doc['_id'])
|
||||
malformed_metrics = []
|
||||
invalid_metrics = []
|
||||
total_docs = 0
|
||||
|
||||
metrics_detailed = ("METRICS DETAILED:\n"
|
||||
f"malformed_metrics_last_week: {malformed_metrics}\n"
|
||||
f"invalid_metrics_last_week: {invalid_metrics}\n"
|
||||
f"total_docs_last_week: {total_docs}")
|
||||
metrics_overview = (
|
||||
"METRICS OVERVIEW:\n"
|
||||
f"malformed_metrics_last_week: {len(malformed_metrics)} ({len(malformed_metrics)/total_docs*100:.2f}%)\n"
|
||||
f"invalid_metrics_last_week: {len(invalid_metrics)} ({len(invalid_metrics)/total_docs*100:.2f}%)\n"
|
||||
f"total_docs_last_week: {total_docs}")
|
||||
# Find any malformed/invalid documents in the cluster
|
||||
for doc in last_week_metrics:
|
||||
total_docs += 1
|
||||
try:
|
||||
metrics = MetricsClass(**doc)
|
||||
if metrics.is_malformed():
|
||||
malformed_metrics.append(doc['_id'])
|
||||
except ValidationError:
|
||||
invalid_metrics.append(doc['_id'])
|
||||
|
||||
print(metrics_overview)
|
||||
print(metrics_detailed)
|
||||
metrics_detailed = (f"METRICS DETAILED ({source}):\n"
|
||||
f"malformed_metrics_last_week: {malformed_metrics}\n"
|
||||
f"invalid_metrics_last_week: {invalid_metrics}\n"
|
||||
f"total_docs_last_week: {total_docs}\n")
|
||||
metrics_overview = (
|
||||
f"METRICS OVERVIEW ({source}):\n"
|
||||
f"malformed_metrics_last_week: {len(malformed_metrics)} ({len(malformed_metrics)/total_docs*100:.2f}%)\n"
|
||||
f"invalid_metrics_last_week: {len(invalid_metrics)} ({len(invalid_metrics)/total_docs*100:.2f}%)\n"
|
||||
f"total_docs_last_week: {total_docs}\n")
|
||||
|
||||
# Publish metrics to SDP Slack Channel
|
||||
evg_api = RetryingEvergreenApi.get_api(config_file="./.evergreen.yml")
|
||||
evg_api.send_slack_message(target="#server-sdp-bfs", msg=metrics_overview)
|
||||
print(metrics_overview)
|
||||
print(metrics_detailed)
|
||||
|
||||
except Exception as exc:
|
||||
print("Unexpected failure while getting metrics")
|
||||
raise exc
|
||||
return metrics_overview
|
||||
|
||||
except Exception as exc:
|
||||
print("Unexpected failure while getting metrics")
|
||||
raise exc
|
||||
|
||||
|
||||
scons_metrics_overview = get_metrics_data("scons", SConsToolingMetrics)
|
||||
resmoke_metrics_overview = get_metrics_data("resmoke", ResmokeToolingMetrics)
|
||||
|
||||
# Publish metrics to SDP Slack Channel
|
||||
evg_api = RetryingEvergreenApi.get_api(config_file="./.evergreen.yml")
|
||||
evg_api.send_slack_message(
|
||||
target="#server-sdp-bfs",
|
||||
msg=scons_metrics_overview + resmoke_metrics_overview,
|
||||
)
|
||||
|
||||
@ -9,7 +9,6 @@ set -o errexit
|
||||
activate_venv
|
||||
$python buildscripts/resmoke_tests_runtime_validate.py \
|
||||
--resmoke-report-file ./report.json \
|
||||
--evg-api-config ./.evergreen.yml \
|
||||
--project-id ${project_id} \
|
||||
--build-variant ${build_variant} \
|
||||
--task-name ${task_name}
|
||||
|
||||
@ -0,0 +1,30 @@
|
||||
// Regression test for SERVER-71270.
|
||||
(function() {
|
||||
"use strict";
|
||||
load('jstests/aggregation/extras/utils.js'); // For assertArrayEq.
|
||||
const doc = {
|
||||
_id: 0,
|
||||
time: new Date('2019-01-18T13:24:15.443Z'),
|
||||
tag: {},
|
||||
};
|
||||
|
||||
db.ts.drop();
|
||||
db.coll.drop();
|
||||
|
||||
db.createCollection('ts', {timeseries: {timeField: 'time', metaField: 'tag'}});
|
||||
db.createCollection('coll');
|
||||
|
||||
db.ts.insertOne(doc);
|
||||
db.coll.insertOne(doc);
|
||||
const pipeline = [
|
||||
{$project: {'time': 0}},
|
||||
{$match: {'time': {$lte: new Date('2019-02-13T11:36:03.481Z')}}},
|
||||
];
|
||||
|
||||
const ts = db.ts.aggregate(pipeline).toArray();
|
||||
const vanilla = db.coll.aggregate(pipeline).toArray();
|
||||
assertArrayEq({
|
||||
actual: ts,
|
||||
expected: vanilla,
|
||||
});
|
||||
}());
|
||||
@ -34,7 +34,6 @@ load("jstests/libs/doc_validation_utils.js");
|
||||
|
||||
const dbName = 'bypass_document_validation';
|
||||
const collName = 'bypass_document_validation';
|
||||
const outputCollName = 'bypass_output_coll';
|
||||
const myDb = db.getSiblingDB(dbName);
|
||||
const coll = myDb[collName];
|
||||
|
||||
@ -65,6 +64,7 @@ function runBypassDocumentValidationTest(validator) {
|
||||
}
|
||||
|
||||
// Test the aggregation command with a $out stage.
|
||||
const outputCollName = 'bypass_output_coll';
|
||||
const outputColl = myDb[outputCollName];
|
||||
outputColl.drop();
|
||||
assert.commandWorked(myDb.createCollection(outputCollName, {validator: validator}));
|
||||
@ -199,8 +199,4 @@ runBypassDocumentValidationTest({a: {$exists: true}});
|
||||
|
||||
// Run the test again with an equivalent JSON Schema validator.
|
||||
runBypassDocumentValidationTest({$jsonSchema: {required: ['a']}});
|
||||
|
||||
// Set the validationAction to "warn" to avoid failing collection validation.
|
||||
assert.commandWorked(myDb.runCommand({collMod: collName, validationAction: "warn"}));
|
||||
assert.commandWorked(myDb.runCommand({collMod: outputCollName, validationAction: "warn"}));
|
||||
})();
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
* @tags: [
|
||||
* assumes_unsharded_collection,
|
||||
* requires_non_retryable_writes,
|
||||
* requires_fcv_60,
|
||||
* requires_fcv_62,
|
||||
* # This test could produce unexpected explain output if additional indexes are created.
|
||||
* assumes_no_implicit_index_creation,
|
||||
* # TODO SERVER-67506: Dotted path equality to null matches non-object array elements in CQF.
|
||||
|
||||
@ -15,48 +15,41 @@ const encryptedBinDataElement = BinData(6, "AAAAAAAAAAAAAAAAAAAAAAAAAAAA");
|
||||
const nonEncryptedBinDataElement = BinData(0, "AAAAAAAAAAAAAAAAAAAAAAAAAAAA");
|
||||
|
||||
// Only elements of type BinData with subtype '6' should match.
|
||||
assertSchemaMatch(coll, {properties: {bin: {encrypt: {}}}}, {bin: encryptedBinDataElement}, true);
|
||||
assertSchemaMatch(coll, {properties: {bin: {encrypt: {}}}}, {bin: {}}, false);
|
||||
assertSchemaMatch(
|
||||
coll, {properties: {bin: {encrypt: {}}}}, {bin: encryptedBinDataElement}, true, true);
|
||||
assertSchemaMatch(coll, {properties: {bin: {encrypt: {}}}}, {bin: {}}, false, true);
|
||||
assertSchemaMatch(
|
||||
coll, {properties: {bin: {encrypt: {}}}}, {bin: nonEncryptedBinDataElement}, false, true);
|
||||
coll, {properties: {bin: {encrypt: {}}}}, {bin: nonEncryptedBinDataElement}, false);
|
||||
// Nested in object.
|
||||
assertSchemaMatch(coll,
|
||||
{properties: {obj: {type: 'object', properties: {a: {encrypt: {}}}}}},
|
||||
{obj: {a: encryptedBinDataElement}},
|
||||
true,
|
||||
true);
|
||||
assertSchemaMatch(coll,
|
||||
{properties: {obj: {type: 'object', properties: {a: {encrypt: {}}}}}},
|
||||
{obj: {a: {}}},
|
||||
false,
|
||||
true);
|
||||
false);
|
||||
assertSchemaMatch(coll,
|
||||
{properties: {obj: {type: 'object', properties: {a: {encrypt: {}}}}}},
|
||||
{obj: {a: nonEncryptedBinDataElement}},
|
||||
false,
|
||||
true);
|
||||
false);
|
||||
|
||||
// Nested in array.
|
||||
assertSchemaMatch(coll,
|
||||
{properties: {arr: {type: 'array', items: {encrypt: {}}}}},
|
||||
{arr: [encryptedBinDataElement, encryptedBinDataElement]},
|
||||
true,
|
||||
true);
|
||||
assertSchemaMatch(
|
||||
coll, {properties: {arr: {type: 'array', items: {encrypt: {}}}}}, {arr: [{}, {}]}, false, true);
|
||||
coll, {properties: {arr: {type: 'array', items: {encrypt: {}}}}}, {arr: [{}, {}]}, false);
|
||||
assertSchemaMatch(coll,
|
||||
{properties: {arr: {type: 'array', items: {encrypt: {}}}}},
|
||||
{arr: [encryptedBinDataElement, nonEncryptedBinDataElement]},
|
||||
false,
|
||||
true);
|
||||
false);
|
||||
|
||||
// If array is not specified, should not traverse array of encrypted BinData's.
|
||||
assertSchemaMatch(coll,
|
||||
{properties: {bin: {encrypt: {}}}},
|
||||
{bin: [encryptedBinDataElement, encryptedBinDataElement]},
|
||||
false,
|
||||
true);
|
||||
false);
|
||||
|
||||
// Encrypt alongside type/bsontype should fail to parse.
|
||||
assert.commandFailedWithCode(
|
||||
|
||||
55
jstests/core/transaction_too_large_for_cache.js
Normal file
55
jstests/core/transaction_too_large_for_cache.js
Normal file
@ -0,0 +1,55 @@
|
||||
/**
|
||||
* Tests that an operation requiring more cache than available fails instead of retrying infinitely.
|
||||
*
|
||||
* @tags: [
|
||||
* does_not_support_config_fuzzer,
|
||||
* requires_fcv_62,
|
||||
* requires_persistence,
|
||||
* requires_non_retryable_writes,
|
||||
* requires_wiredtiger,
|
||||
* ]
|
||||
*/
|
||||
|
||||
(function() {
|
||||
load("jstests/libs/fixture_helpers.js"); // For FixtureHelpers.
|
||||
load("jstests/libs/storage_engine_utils.js");
|
||||
|
||||
// TODO (SERVER-39362): remove once parallel suite respects tags properly.
|
||||
if (!storageEngineIsWiredTiger()) {
|
||||
jsTestLog("Skipping test because storage engine is not WiredTiger.");
|
||||
return;
|
||||
}
|
||||
|
||||
const doc = {
|
||||
x: []
|
||||
};
|
||||
for (var j = 0; j < 334000; j++) {
|
||||
doc.x.push("" + Math.random() + Math.random());
|
||||
}
|
||||
|
||||
const coll = db[jsTestName()];
|
||||
coll.drop();
|
||||
|
||||
// Maximum amount of indexes is 64. _id is implicit, and sharded collections also have an index on
|
||||
// the shard key.
|
||||
assert.commandWorked(coll.createIndex({x: "text"}));
|
||||
for (let i = 0; i < 61; i++) {
|
||||
assert.commandWorked(coll.createIndex({x: 1, ["field" + i]: 1}));
|
||||
}
|
||||
|
||||
// Retry the operation until we eventually hit the TransactionTooLargeForCache. Retry on
|
||||
// WriteConflict or TemporarilyUnavailable errors, as those are expected to be returned if the
|
||||
// threshold for TransactionTooLargeForCache is not reached, possibly due to concurrent operations.
|
||||
assert.soon(() => {
|
||||
let result;
|
||||
try {
|
||||
result = coll.insert(doc);
|
||||
assert.commandFailedWithCode(result, ErrorCodes.TransactionTooLargeForCache);
|
||||
return true;
|
||||
} catch (e) {
|
||||
assert.commandFailedWithCode(result,
|
||||
[ErrorCodes.WriteConflict, ErrorCodes.TemporarilyUnavailable]);
|
||||
return false;
|
||||
}
|
||||
}, "Expected operation to eventually fail with TransactionTooLargeForCache error.");
|
||||
}());
|
||||
85
jstests/core/txns/transaction_too_large_for_cache.js
Normal file
85
jstests/core/txns/transaction_too_large_for_cache.js
Normal file
@ -0,0 +1,85 @@
|
||||
/**
|
||||
* Tests a multi-document transaction requiring more cache than available fails with the expected
|
||||
* error code instead of a generic WriteConflictException.
|
||||
*
|
||||
* @tags: [
|
||||
* does_not_support_config_fuzzer,
|
||||
* requires_fcv_62,
|
||||
* requires_persistence,
|
||||
* requires_non_retryable_writes,
|
||||
* requires_wiredtiger,
|
||||
* uses_transactions,
|
||||
* ]
|
||||
*/
|
||||
|
||||
(function() {
|
||||
load("jstests/libs/fixture_helpers.js"); // For FixtureHelpers.
|
||||
load('jstests/libs/transactions_util.js');
|
||||
|
||||
function getWtCacheSizeBytes() {
|
||||
let serverStatus;
|
||||
if (FixtureHelpers.isReplSet(db) || FixtureHelpers.isMongos(db)) {
|
||||
serverStatus = FixtureHelpers.getPrimaries(db)[0].getDB('admin').serverStatus();
|
||||
} else {
|
||||
serverStatus = db.serverStatus();
|
||||
}
|
||||
|
||||
assert.commandWorked(serverStatus);
|
||||
return serverStatus.wiredTiger.cache["maximum bytes configured"];
|
||||
}
|
||||
|
||||
const doc1 = {
|
||||
x: []
|
||||
};
|
||||
for (var j = 0; j < 100000; j++) {
|
||||
doc1.x.push("" + Math.random() + Math.random());
|
||||
}
|
||||
|
||||
const session = db.getMongo().startSession();
|
||||
const sessionDb = session.getDatabase(db.getName());
|
||||
const coll = sessionDb[jsTestName()];
|
||||
coll.drop();
|
||||
|
||||
// Scale the load in proportion to WT cache size, to reduce test run time.
|
||||
// A single collection can only have up to 64 indexes. Cap at _id + 1 text index + 62 indexes.
|
||||
const nIndexes = Math.min(Math.ceil(getWtCacheSizeBytes() * 2 / (1024 * 1024 * 1024)), 62);
|
||||
assert.commandWorked(coll.createIndex({x: "text"}));
|
||||
for (let i = 0; i < nIndexes; i++) {
|
||||
assert.commandWorked(coll.createIndex({x: 1, ["field" + i]: 1}));
|
||||
}
|
||||
|
||||
// Retry the transaction until we eventually hit the TransactionTooLargeForCache. Only retry on
|
||||
// WriteConflict error, which is the only expected error besides TransactionTooLargeForCache.
|
||||
assert.soon(() => {
|
||||
session.startTransaction();
|
||||
|
||||
// Keep inserting documents in the transaction until we eventually hit the cache limit.
|
||||
let insertCount = 0;
|
||||
let result;
|
||||
try {
|
||||
while (true) {
|
||||
try {
|
||||
++insertCount;
|
||||
result = coll.insert(doc1);
|
||||
assert.commandWorked(result);
|
||||
} catch (e) {
|
||||
session.abortTransaction();
|
||||
assert.commandFailedWithCode(result, ErrorCodes.TransactionTooLargeForCache);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
assert.commandFailedWithCode(result, ErrorCodes.WriteConflict);
|
||||
return false;
|
||||
}
|
||||
|
||||
// The error should not have a transient transaction error label. At this point the error must
|
||||
// have been TransactionTooLargeForCache. We do this check here to avoid having to check
|
||||
// exception types in the outermost catch, in case this assertion fires.
|
||||
assert(!TransactionsUtil.isTransientTransactionError(result), result);
|
||||
|
||||
jsTestLog("Iterations until TransactionTooLargeForCache occured: " + insertCount);
|
||||
|
||||
return true;
|
||||
}, "Expected a transaction to eventually fail with TransactionTooLargeForCache error.");
|
||||
}());
|
||||
@ -113,8 +113,7 @@ function runTest(badViewDefinition) {
|
||||
}
|
||||
|
||||
assert.commandWorked(
|
||||
viewsDB.runCommand(
|
||||
{collMod: "collection", validator: {x: {$type: "string"}}, validationAction: "warn"}),
|
||||
viewsDB.runCommand({collMod: "collection", validator: {x: {$type: "string"}}}),
|
||||
makeErrorMessage("collMod"));
|
||||
|
||||
const renameCommand = {
|
||||
|
||||
@ -151,10 +151,6 @@ assert.commandWorked(viewsDB.runCommand({
|
||||
assert.commandWorked(
|
||||
viewsDB.runCommand({aggregate: "largeView", pipeline: [{$sort: {x: -1}}], cursor: {}}),
|
||||
"Expected aggregate to succeed since 'allowDiskUse' is true by default");
|
||||
|
||||
// Set the validationAction to "warn" to avoid failing collection validation.
|
||||
assert.commandWorked(
|
||||
viewsDB.runCommand({collMod: validatedCollName, validationAction: "warn"}));
|
||||
})();
|
||||
|
||||
// Test explain modes on a view.
|
||||
|
||||
@ -45,28 +45,6 @@ const isIgnorableError = function ignorableError(codeName) {
|
||||
*/
|
||||
const validateCollectionsBackgroundThread = function validateCollectionsBackground(
|
||||
host, isIgnorableErrorFunc) {
|
||||
// Some tests explicitly mess with schema validation. We will skip running background validation
|
||||
// on those collections.
|
||||
const skippedCollections = new Set([
|
||||
"collectionWithValidator",
|
||||
"jstests_schema_encrypt",
|
||||
"json_schema_additional_items",
|
||||
"schema_allowed_properties",
|
||||
"jstests_schema_bsontype",
|
||||
"jstests_schema_dependencies",
|
||||
"jstests_schema_encrypt",
|
||||
"json_schema_items",
|
||||
"jstests_json_schema",
|
||||
"jstests_json_schema_logical",
|
||||
"json_schema_min_max_items",
|
||||
"jstests_schema_min_max_properties",
|
||||
"schema_pattern_properties",
|
||||
"jstests_schema_required",
|
||||
"json_schema_unique_items",
|
||||
"json_schema_test_corpus",
|
||||
"jstests_json_schema_ignore_unsupported",
|
||||
]);
|
||||
|
||||
// Calls 'func' with the print() function overridden to be a no-op.
|
||||
const quietly = (func) => {
|
||||
const printOriginal = print;
|
||||
@ -128,9 +106,6 @@ const validateCollectionsBackgroundThread = function validateCollectionsBackgrou
|
||||
});
|
||||
|
||||
for (let collectionName of collectionNames) {
|
||||
if (dbName == "bypass_document_validation" || skippedCollections.has(collectionName)) {
|
||||
continue;
|
||||
}
|
||||
let res = conn.getDB(dbName).getCollection(collectionName).runCommand({
|
||||
"validate": collectionName,
|
||||
background: true,
|
||||
|
||||
@ -10,7 +10,7 @@
|
||||
* Asserts that 'doc' matches 'schema' if and only if 'valid' is true. Drops 'coll' in the process,
|
||||
* so do not pass a collection whose contents you wish to preserve.
|
||||
*/
|
||||
function assertSchemaMatch(coll, schema, doc, valid, removeValidator = false) {
|
||||
function assertSchemaMatch(coll, schema, doc, valid) {
|
||||
const errmsg = "Document " + tojson(doc) +
|
||||
(valid ? " should have matched the schema " : " unexpectedly matched the schema ") +
|
||||
tojson(schema);
|
||||
@ -60,12 +60,4 @@ function assertSchemaMatch(coll, schema, doc, valid, removeValidator = false) {
|
||||
ErrorCodes.DocumentValidationFailure,
|
||||
errmsg + " during update document validation in strict mode");
|
||||
}
|
||||
|
||||
if (removeValidator) {
|
||||
// Remove the validator to avoid failing collection validation.
|
||||
assert.commandWorked(coll.runCommand("collMod", {validator: {}}));
|
||||
} else {
|
||||
// Set the validationAction to "warn" to avoid failing collection validation.
|
||||
assert.commandWorked(coll.runCommand("collMod", {validationAction: "warn"}));
|
||||
}
|
||||
}
|
||||
|
||||
@ -32,7 +32,8 @@ assert.commandFailed(setParameter(adminDb, {"diagnosticDataCollectionStatsNamesp
|
||||
|
||||
assert.eq(getParameter(adminDb, "diagnosticDataCollectionStatsNamespaces"), ["local.startup_log"]);
|
||||
|
||||
// Validate that collection stats are collected for runtime collections
|
||||
// Validate that collection stats are collected for runtime collections and that we do not crash
|
||||
// for a non-existent collection
|
||||
assert.commandWorked(setParameter(
|
||||
adminDb,
|
||||
{"diagnosticDataCollectionStatsNamespaces": ["admin.system.version", "admin.does_not_exist"]}));
|
||||
@ -41,8 +42,7 @@ assert.soon(() => {
|
||||
jsTestLog("Collected: " + tojson(result));
|
||||
let collectionStats = result.data.collectionStats;
|
||||
return collectionStats.hasOwnProperty("admin.system.version") &&
|
||||
collectionStats["admin.system.version"].ns == "admin.system.version" &&
|
||||
collectionStats["admin.does_not_exist"].ns == "admin.does_not_exist" != undefined;
|
||||
collectionStats["admin.system.version"].ns == "admin.system.version";
|
||||
});
|
||||
|
||||
// Validate that when it is disabled, we stop collecting
|
||||
|
||||
@ -29,7 +29,7 @@ const collName = "collectionWithMalformedValidator";
|
||||
assert.commandWorked(
|
||||
testDB.runCommand({collMod: collName, validator: {email: {$regex: invalidRegex}}}));
|
||||
|
||||
MongoRunner.stopMongod(conn, null, {skipValidation: true});
|
||||
MongoRunner.stopMongod(conn);
|
||||
})();
|
||||
|
||||
(function startUpWithMalformedValidator() {
|
||||
@ -48,6 +48,6 @@ const collName = "collectionWithMalformedValidator";
|
||||
assert.commandWorked(testDB.someOtherCollection.insert({a: 1}));
|
||||
assert.eq(testDB.someOtherCollection.find().itcount(), 1);
|
||||
|
||||
MongoRunner.stopMongod(conn, null, {skipValidation: true});
|
||||
MongoRunner.stopMongod(conn);
|
||||
})();
|
||||
})();
|
||||
|
||||
@ -253,8 +253,5 @@ bulkOp.find({$expr: {$eq: ["$x", 2]}}).update({$set: {x: 10}});
|
||||
bulkOp.find({$expr: {$lt: ["$x", 1]}}).remove();
|
||||
checkCounters(() => assert.commandWorked(bulkOp.execute()), {"$eq": 1, "$lt": 1});
|
||||
|
||||
// Set the validationAction to "warn" to avoid failing collection validation.
|
||||
assert.commandWorked(testColl.runCommand("collMod", {validationAction: "warn"}));
|
||||
|
||||
MongoRunner.stopMongod(mongod);
|
||||
})();
|
||||
|
||||
@ -18,6 +18,10 @@ function getPlanCacheSize() {
|
||||
return db.runCommand({serverStatus: 1}).metrics.query.planCacheTotalSizeEstimateBytes;
|
||||
}
|
||||
|
||||
function getGlobalPlanCacheNumEntries() {
|
||||
return db.runCommand({serverStatus: 1}).metrics.query.planCacheTotalQueryShapes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper class that creates a collection, indexes on it, and makes a few queries to add entries to
|
||||
* the plan cache.
|
||||
@ -37,20 +41,21 @@ class TestCollection {
|
||||
|
||||
assert.gt(getPlanCacheSize(), 0);
|
||||
|
||||
this.nCacheEntries = this.getNumberOfPlanCacheEntries();
|
||||
this.nCacheEntries = this.getNumberOfCollectionPlanCacheEntries();
|
||||
assert.eq(2, this.nCacheEntries);
|
||||
}
|
||||
|
||||
getNumberOfPlanCacheEntries() {
|
||||
// The following three helper functions concern plan cache entries specific to a given
|
||||
// collection, and not the entire/global plan cache
|
||||
getNumberOfCollectionPlanCacheEntries() {
|
||||
return this.coll.getPlanCache().list().length;
|
||||
}
|
||||
|
||||
assertAllCollectionCacheEntriesRemoved() {
|
||||
assert.eq(0, this.getNumberOfPlanCacheEntries());
|
||||
assert.eq(0, this.getNumberOfCollectionPlanCacheEntries());
|
||||
}
|
||||
|
||||
assertCacheEntriesNotRemoved() {
|
||||
assert.eq(this.nCacheEntries, this.getNumberOfPlanCacheEntries());
|
||||
assertCollectionCacheEntriesNotRemoved() {
|
||||
assert.eq(this.nCacheEntries, this.getNumberOfCollectionPlanCacheEntries());
|
||||
}
|
||||
}
|
||||
|
||||
@ -59,23 +64,28 @@ class TestCollection {
|
||||
const test = new TestCollection();
|
||||
|
||||
assert.gt(getPlanCacheSize(), initialPlanCacheSize);
|
||||
assert.eq(getGlobalPlanCacheNumEntries(), 2);
|
||||
|
||||
assert(test.coll.drop());
|
||||
|
||||
assert.eq(getPlanCacheSize(), initialPlanCacheSize);
|
||||
assert.eq(getGlobalPlanCacheNumEntries(), 0);
|
||||
}());
|
||||
|
||||
(function cacheEntriesNotRemovedIfAnotherCollectedDropped() {
|
||||
const test = new TestCollection("coll1");
|
||||
const cacheSizeForOneTestCollection = getPlanCacheSize();
|
||||
assert.eq(getGlobalPlanCacheNumEntries(), 2);
|
||||
|
||||
const anotherTest = new TestCollection("coll2");
|
||||
const cacheSizeForTwoTestCollections = getPlanCacheSize();
|
||||
assert.eq(getGlobalPlanCacheNumEntries(), 4);
|
||||
assert.gt(cacheSizeForTwoTestCollections, cacheSizeForOneTestCollection);
|
||||
assert(anotherTest.coll.drop());
|
||||
|
||||
assert.eq(cacheSizeForOneTestCollection, getPlanCacheSize());
|
||||
test.assertCacheEntriesNotRemoved();
|
||||
// Entries associated with anotherTest.coll are booted from the plan cache.
|
||||
assert.eq(getGlobalPlanCacheNumEntries(), 2);
|
||||
test.assertCollectionCacheEntriesNotRemoved();
|
||||
}());
|
||||
|
||||
(function cacheEntriesRemovedIfANewIndexCreated() {
|
||||
@ -107,14 +117,13 @@ class TestCollection {
|
||||
db.runCommand({collMod: collectionName, validator: {text: {$type: "string"}}}));
|
||||
|
||||
assert.eq(getPlanCacheSize(), initialPlanCacheSize);
|
||||
test.assertCacheEntriesNotRemoved();
|
||||
test.assertCollectionCacheEntriesNotRemoved();
|
||||
}());
|
||||
|
||||
(function cacheEntriesRemovedIfIndexChanged() {
|
||||
const collectionName = "coll";
|
||||
const test = new TestCollection(collectionName);
|
||||
const initialPlanCacheSize = getPlanCacheSize();
|
||||
|
||||
assert.commandWorked(db.runCommand({
|
||||
collMod: collectionName,
|
||||
index: {
|
||||
@ -122,7 +131,6 @@ class TestCollection {
|
||||
hidden: true,
|
||||
}
|
||||
}));
|
||||
|
||||
assert.lt(getPlanCacheSize(), initialPlanCacheSize);
|
||||
test.assertAllCollectionCacheEntriesRemoved();
|
||||
}());
|
||||
@ -143,16 +151,16 @@ class TestCollection {
|
||||
(function oneCacheEntryRemovedOnClearPlanCacheWithQueryCommand() {
|
||||
const collectionName = "coll";
|
||||
const test = new TestCollection(collectionName);
|
||||
const numberOfCacheEntries = test.getNumberOfPlanCacheEntries();
|
||||
const numberOfCacheEntries = getGlobalPlanCacheNumEntries();
|
||||
|
||||
test.coll.find({a: 1, b: 2, c: 3, d: 4}).itcount();
|
||||
assert.eq(numberOfCacheEntries + 1, test.getNumberOfPlanCacheEntries());
|
||||
assert.eq(numberOfCacheEntries + 1, getGlobalPlanCacheNumEntries());
|
||||
const planCacheSize = getPlanCacheSize();
|
||||
|
||||
assert.commandWorked(
|
||||
db.runCommand({planCacheClear: collectionName, query: {a: 1, b: 2, c: 3, d: 4}}));
|
||||
assert.lt(getPlanCacheSize(), planCacheSize);
|
||||
assert.eq(numberOfCacheEntries, test.getNumberOfPlanCacheEntries());
|
||||
assert.eq(numberOfCacheEntries, getGlobalPlanCacheNumEntries());
|
||||
}());
|
||||
|
||||
MongoRunner.stopMongod(conn);
|
||||
|
||||
@ -45,6 +45,9 @@ function getPlanCacheSize() {
|
||||
return db.serverStatus().metrics.query.planCacheTotalSizeEstimateBytes;
|
||||
}
|
||||
|
||||
function getPlanCacheNumEntries() {
|
||||
return db.serverStatus().metrics.query.planCacheTotalQueryShapes;
|
||||
}
|
||||
function assertQueryInPlanCache(coll, query) {
|
||||
const explainResult = assert.commandWorked(coll.explain().find(query).finish());
|
||||
const queryHash = getQueryHashFromExplain(explainResult, db);
|
||||
@ -65,7 +68,7 @@ assert.commandWorked(coll.createIndex({a: 1, b: 1}));
|
||||
|
||||
const initialPlanCacheSize = getPlanCacheSize();
|
||||
// Plan cache must be empty.
|
||||
assert.eq(0, coll.getPlanCache().list().length);
|
||||
assert.eq(0, getPlanCacheNumEntries());
|
||||
|
||||
const sbeQuery = {
|
||||
a: 1
|
||||
@ -79,7 +82,7 @@ const classicQuery = {
|
||||
assert.eq(1, coll.find(sbeQuery).itcount());
|
||||
assertQueryInPlanCache(coll, sbeQuery);
|
||||
// Plan Cache must contain exactly 1 entry.
|
||||
assert.eq(1, coll.getPlanCache().list().length);
|
||||
assert.eq(1, getPlanCacheNumEntries());
|
||||
|
||||
// Assert metric is incremented for new cache entry.
|
||||
const afterSbePlanCacheSize = getPlanCacheSize();
|
||||
@ -92,7 +95,7 @@ assert.commandWorked(
|
||||
assert.eq(1, coll.find(classicQuery).itcount());
|
||||
assertQueryInPlanCache(coll, classicQuery);
|
||||
// Plan Cache must contain exactly 2 entries.
|
||||
assert.eq(2, coll.getPlanCache().list().length);
|
||||
assert.eq(2, getPlanCacheNumEntries());
|
||||
|
||||
// Assert metric is incremented for new cache entry.
|
||||
const afterClassicPlanCacheSize = getPlanCacheSize();
|
||||
|
||||
@ -4,6 +4,10 @@
|
||||
(function() {
|
||||
"use strict";
|
||||
|
||||
// Disable the testing proctor. When the testing proctor is enabled, 'validate' will only warn about
|
||||
// non-compliant documents, even when the validation action is 'error'.
|
||||
TestData.testingDiagnosticsEnabled = false;
|
||||
|
||||
const conn = MongoRunner.runMongod();
|
||||
|
||||
const dbName = "test";
|
||||
|
||||
@ -18,15 +18,12 @@ var coll = primary.getDB('test').getCollection(name);
|
||||
assert.commandWorked(coll.insert({_id: 0, x: 1}));
|
||||
assert.commandWorked(coll.runCommand("collMod", {"validator": {a: {$exists: true}}}));
|
||||
|
||||
secondary = replSet.restart(secondary, {startClean: true, skipValidation: true});
|
||||
secondary = replSet.restart(secondary, {startClean: true});
|
||||
replSet.awaitReplication();
|
||||
replSet.awaitSecondaryNodes();
|
||||
|
||||
assert.eq(1, secondary.getDB("test")[name].count());
|
||||
assert.docEq({_id: 0, x: 1}, secondary.getDB("test")[name].findOne());
|
||||
|
||||
// Set the validationAction to "warn" to avoid failing collection validation.
|
||||
assert.commandWorked(coll.runCommand("collMod", {validationAction: "warn"}));
|
||||
|
||||
replSet.stopSet();
|
||||
})();
|
||||
|
||||
@ -5,6 +5,8 @@
|
||||
(function() {
|
||||
"use strict";
|
||||
|
||||
load("jstests/replsets/rslib.js");
|
||||
|
||||
// Skip db hash check because secondary is left with a different config.
|
||||
TestData.skipCheckDBHashes = true;
|
||||
|
||||
@ -33,6 +35,10 @@ const lastOp = primaryOplog.find(expectedNoOp).sort({'$natural': -1}).limit(1).t
|
||||
assert(lastOp.length > 0);
|
||||
replTest.awaitReplication();
|
||||
|
||||
// Make sure that all nodes have installed the config before moving on.
|
||||
replTest.waitForConfigReplication(primary, nodes);
|
||||
assert.soonNoExcept(() => isConfigCommitted(primary));
|
||||
|
||||
jsTestLog("Invalid reconfig");
|
||||
config.version++;
|
||||
var badMember = {_id: numNodes, host: "localhost:12345", priority: "High"};
|
||||
|
||||
@ -32,9 +32,9 @@ function printCollectionOptions(rollbackTest, time) {
|
||||
let CommonOps = (node) => {
|
||||
let testDb = node.getDB(dbName);
|
||||
assert.commandWorked(testDb[coll1Name].insert({a: 1, b: 1}));
|
||||
assert.commandWorked(testDb[coll2Name].insert({a: 1, b: 1}));
|
||||
assert.commandWorked(testDb[coll3Name].insert({a: 1, b: 1}));
|
||||
assert.commandWorked(testDb[coll4Name].insert({a: 1, b: 1}));
|
||||
assert.commandWorked(testDb[coll2Name].insert({a: 2, b: 2}));
|
||||
assert.commandWorked(testDb[coll3Name].insert({a: 3, b: 3}));
|
||||
assert.commandWorked(testDb[coll4Name].insert({a: 4, b: 4}));
|
||||
|
||||
// Start with no validation action.
|
||||
assert.commandWorked(testDb.runCommand({
|
||||
|
||||
@ -175,6 +175,10 @@ for (let i = numCollections / 2; i < numCollections; i++) {
|
||||
checkResults(outputData, checksToDo);
|
||||
})();
|
||||
|
||||
// Test valid query with empty specification
|
||||
assert.commandWorked(
|
||||
adminDb.runCommand({aggregate: 1, pipeline: [{$_internalAllCollectionStats: {}}], cursor: {}}));
|
||||
|
||||
// Test invalid queries/values.
|
||||
assert.commandFailedWithCode(
|
||||
adminDb.runCommand({aggregate: 1, pipeline: [{$_internalAllCollectionStats: 3}], cursor: {}}),
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
/**
|
||||
* SERVER-57469: Test that the 'allowPartialResults' option to find is respected when used together
|
||||
* with 'maxTimeMS' and only a subset of the shards provide data before the timeout.
|
||||
* Uses both failpoints and MongoBridge to simulate MaxTimeMSExpired.
|
||||
* Uses three methods to simulate MaxTimeMSExpired: failpoints, MongoBridge, and $where + sleep.
|
||||
*
|
||||
* @tags: [
|
||||
* requires_sharding,
|
||||
@ -33,15 +33,15 @@ function isError(res) {
|
||||
// Set up a 2-shard single-node replicaset cluster with MongoBridge.
|
||||
const st = new ShardingTest({name: jsTestName(), shards: 2, useBridge: true, rs: {nodes: 1}});
|
||||
|
||||
const dbName = "test-SERVER-57469";
|
||||
const collName = "test-SERVER-57469-coll";
|
||||
const dbName = "test-SERVER-57469-failpoints";
|
||||
const collName = "test-SERVER-57469-failpoints-coll";
|
||||
|
||||
const coll = st.s0.getDB(dbName)[collName];
|
||||
|
||||
function initDb(numSamples, splitPoint) {
|
||||
coll.drop();
|
||||
|
||||
// Use ranged sharding with 50% of the data on the second shard.
|
||||
// Use ranged sharding with a specified fraction of the data on the second shard.
|
||||
st.shardColl(
|
||||
coll,
|
||||
{_id: 1}, // shard key
|
||||
@ -57,26 +57,30 @@ function initDb(numSamples, splitPoint) {
|
||||
}
|
||||
|
||||
// Insert some data.
|
||||
const size = 1000;
|
||||
const splitPoint = Math.max(1, size / 2);
|
||||
initDb(size, splitPoint);
|
||||
const nDocs = 200;
|
||||
const splitPoint = Math.max(1, nDocs / 2);
|
||||
initDb(nDocs, splitPoint);
|
||||
|
||||
// We will sometimes use $where expressions to inject delays in processing documents on some shards.
|
||||
// Maps from shard to a snippet of JS code. This is modified by FindWhereSleepController
|
||||
let whereExpressions = {};
|
||||
function whereCode() {
|
||||
return Object.values(whereExpressions).join("") + "return 1;";
|
||||
}
|
||||
|
||||
function runQueryWithTimeout(doAllowPartialResults, timeout) {
|
||||
return coll.runCommand({
|
||||
find: collName,
|
||||
filter: {$where: Object.values(whereExpressions).join("") + "return 1;"},
|
||||
filter: {$where: whereCode()},
|
||||
allowPartialResults: doAllowPartialResults,
|
||||
batchSize: size,
|
||||
batchSize: nDocs,
|
||||
maxTimeMS: timeout
|
||||
});
|
||||
}
|
||||
|
||||
// Set ampleTimeMS to at least two seconds, plus ten times the basic query runtime.
|
||||
// This timeout will provide ample time for our queries to run to completion.
|
||||
// Set ampleTimeMS to at least 2000ms, plus ten times the basic query runtime.
|
||||
// This timeout must provide ample time for our queries to run to completion, even on passthrough
|
||||
// suites with resource contention.
|
||||
const ampleTimeMS = 2000 + 10 * runtimeMillis(() => runQueryWithTimeout(true, 999999999));
|
||||
print("ampleTimeMS: " + ampleTimeMS);
|
||||
|
||||
@ -96,16 +100,14 @@ function runQuery(doAllowPartialResults) {
|
||||
fpMongos.off();
|
||||
}
|
||||
|
||||
const batchSizeForGetMore = 10;
|
||||
|
||||
// Simulate mongos timeout during getMore.
|
||||
function getMoreMongosTimeout(allowPartialResults) {
|
||||
function getMoreMongosTimeout(allowPartialResults, batchSize) {
|
||||
// Get the first batch.
|
||||
const res = assert.commandWorked(coll.runCommand({
|
||||
find: collName,
|
||||
filter: {$where: Object.values(whereExpressions).join("") + "return 1;"},
|
||||
filter: {$where: whereCode()},
|
||||
allowPartialResults: allowPartialResults,
|
||||
batchSize: batchSizeForGetMore,
|
||||
batchSize: batchSize,
|
||||
maxTimeMS: ampleTimeMS
|
||||
}));
|
||||
assert(!res.cursor.hasOwnProperty("partialResultsReturned"));
|
||||
@ -115,10 +117,10 @@ function getMoreMongosTimeout(allowPartialResults) {
|
||||
|
||||
// Run getmores repeatedly until we exhaust the cache on mongos.
|
||||
// Eventually we should get either a MaxTimeMS error or partial results because a shard is down.
|
||||
let numReturned = batchSizeForGetMore; // One batch was returned so far.
|
||||
let numReturned = batchSize; // One batch was returned so far.
|
||||
while (true) {
|
||||
const res2 = coll.runCommand(
|
||||
{getMore: res.cursor.id, collection: collName, batchSize: batchSizeForGetMore});
|
||||
const res2 =
|
||||
coll.runCommand({getMore: res.cursor.id, collection: collName, batchSize: batchSize});
|
||||
if (isError(res2)) {
|
||||
assert.commandFailedWithCode(
|
||||
res2, ErrorCodes.MaxTimeMSExpired, "failure should be due to MaxTimeMSExpired");
|
||||
@ -128,20 +130,45 @@ function getMoreMongosTimeout(allowPartialResults) {
|
||||
// are returned even if MaxTimeMS expired on mongos.
|
||||
numReturned += res2.cursor.nextBatch.length;
|
||||
print(numReturned + " docs returned so far");
|
||||
assert.neq(numReturned, size, "Got full results even through mongos had MaxTimeMSExpired.");
|
||||
assert.neq(
|
||||
numReturned, nDocs, "Got full results even through mongos had MaxTimeMSExpired.");
|
||||
if (res2.cursor.partialResultsReturned) {
|
||||
assert(allowPartialResults);
|
||||
assert.lt(numReturned, size);
|
||||
assert.lt(numReturned, nDocs);
|
||||
break;
|
||||
}
|
||||
}
|
||||
fpMongos.off();
|
||||
}
|
||||
getMoreMongosTimeout(true);
|
||||
getMoreMongosTimeout(false);
|
||||
// Run the getMore tests with two batch sizes.
|
||||
// In the first case, we have (splitPoint % batchSizeForGetMore == 0) and the getMores will likely
|
||||
// exhaust the live shard without requiring any data from the dead shard. When the getMore to
|
||||
// the dead shard times out, it will be the only unexhausted remote.
|
||||
// In the second case, choosing (splitPoint % batchSizeForGetMore != 0) the final getMore will be
|
||||
// requesting data from both shards. Data from the live shard should be returned despite the dead
|
||||
// shard timing out.
|
||||
const batchSizesForGetMore = [50, 47];
|
||||
assert.eq(splitPoint % batchSizesForGetMore[0], 0);
|
||||
assert.neq(splitPoint % batchSizesForGetMore[1], 0);
|
||||
assert.lt(batchSizesForGetMore[0], splitPoint);
|
||||
assert.lt(batchSizesForGetMore[1], splitPoint);
|
||||
|
||||
// Test shard timeouts. These are the scenario that we expect to be possible in practice.
|
||||
// Test using both failpoints and mongo bridge, testing slightly different execution paths.
|
||||
function withEachBatchSize(callback) {
|
||||
callback(batchSizesForGetMore[0]);
|
||||
callback(batchSizesForGetMore[1]);
|
||||
}
|
||||
|
||||
function withEachValueOfAllowPartialResults(callback) {
|
||||
callback(true);
|
||||
callback(false);
|
||||
}
|
||||
|
||||
withEachValueOfAllowPartialResults(
|
||||
allowPartialResults =>
|
||||
withEachBatchSize(batchSize => getMoreMongosTimeout(allowPartialResults, batchSize)));
|
||||
|
||||
// Test shard timeouts. These are the scenario that we expect to be likely in practice.
|
||||
// Test using 3 different types of simulated timeouts, giving slightly different execution paths.
|
||||
|
||||
class MaxTimeMSFailpointFailureController {
|
||||
constructor(mongoInstance) {
|
||||
@ -161,6 +188,7 @@ class MaxTimeMSFailpointFailureController {
|
||||
class NetworkFailureController {
|
||||
constructor(shard) {
|
||||
this.shard = shard;
|
||||
this.delayTime = Math.round(1.1 * ampleTimeMS);
|
||||
}
|
||||
|
||||
enable() {
|
||||
@ -169,12 +197,13 @@ class NetworkFailureController {
|
||||
// lost. We delay instead of dropping messages because this lets the shard request proceed
|
||||
// without connection failure and retry (which has its own driver-controlled timeout,
|
||||
// typically 15s).
|
||||
this.shard.getPrimary().delayMessagesFrom(st.s, 2 * ampleTimeMS);
|
||||
this.shard.getPrimary().delayMessagesFrom(st.s, this.delayTime);
|
||||
}
|
||||
|
||||
disable() {
|
||||
this.shard.getPrimary().delayMessagesFrom(st.s, 0);
|
||||
sleep(2 * ampleTimeMS); // Allow time for delayed messages to be flushed.
|
||||
// Allow time for delayed messages to be flushed so that the next request is not delayed.
|
||||
sleep(this.delayTime);
|
||||
}
|
||||
}
|
||||
|
||||
@ -205,10 +234,10 @@ class FindWhereSleepController {
|
||||
// Add a $where expression to find command that sleeps when processing a document on the
|
||||
// shard of interest.
|
||||
let slowDocId = (this.shard == st.shard0) ? 0 : splitPoint;
|
||||
// Offset the slowDocId by batchSizeForGetMore so that when testing getMore, we quickly
|
||||
// return enough documents to serve the first batch without timing out.
|
||||
slowDocId += batchSizeForGetMore;
|
||||
const sleepTimeMS = 2 * ampleTimeMS;
|
||||
// Offset the slowDocId by at least the getMore batch size so that when testing getMore,
|
||||
// we quickly return enough documents to serve the first batch without timing out.
|
||||
slowDocId += Math.max(...batchSizesForGetMore);
|
||||
const sleepTimeMS = Math.round(1.1 * ampleTimeMS);
|
||||
whereExpressions[this.shard] = `if (this._id == ${slowDocId}) {sleep(${sleepTimeMS})};`;
|
||||
}
|
||||
|
||||
@ -232,26 +261,53 @@ const allShardsSleepFailure = new MultiFailureController([shard0SleepFailure, sh
|
||||
|
||||
const allshardsMixedFailures = new MultiFailureController([shard0NetworkFailure, shard1Failpoint]);
|
||||
|
||||
function getMoreShardTimeout(allowPartialResults, failureController) {
|
||||
// Due to the hack with sleepFailures below, this has to be the innermost parameterizing function.
|
||||
function withEachSingleShardFailure(callback) {
|
||||
callback(shard0Failpoint);
|
||||
callback(shard1Failpoint);
|
||||
callback(shard0NetworkFailure);
|
||||
callback(shard1NetworkFailure);
|
||||
// The FindWhereSleepFailureController must be set before the first "find" because that's when
|
||||
// the $where clause is set.
|
||||
shard0SleepFailure.enable();
|
||||
callback(shard0SleepFailure);
|
||||
shard0SleepFailure.disable();
|
||||
shard1SleepFailure.enable();
|
||||
callback(shard1SleepFailure);
|
||||
shard1NetworkFailure.disable();
|
||||
}
|
||||
|
||||
function withEachAllShardFailure(callback) {
|
||||
callback(allShardsFailpoint);
|
||||
callback(allshardsNetworkFailure);
|
||||
callback(allShardsSleepFailure);
|
||||
callback(allshardsMixedFailures);
|
||||
}
|
||||
|
||||
function getMoreShardTimeout(allowPartialResults, failureController, batchSize) {
|
||||
// Get the first batch.
|
||||
const res = assert.commandWorked(coll.runCommand({
|
||||
find: collName,
|
||||
filter: {$where: Object.values(whereExpressions).join("") + "return 1;"},
|
||||
filter: {$where: whereCode()},
|
||||
// FindWhereSleepController only works with getMore if docs are _id-ordered. We use a hint
|
||||
// instead of sort here because we want to avoid blocking on missing docs -- we want the
|
||||
// AsyncResultsMerger to return results from the live shard while the other is failing.
|
||||
hint: {_id: 1},
|
||||
allowPartialResults: allowPartialResults,
|
||||
batchSize: batchSizeForGetMore,
|
||||
batchSize: batchSize,
|
||||
maxTimeMS: ampleTimeMS
|
||||
}));
|
||||
assert.eq(undefined, res.cursor.partialResultsReturned);
|
||||
assert.gt(res.cursor.id, 0);
|
||||
// Stop a shard and run getMore.
|
||||
failureController.enable();
|
||||
let numReturned = batchSizeForGetMore; // One batch was returned so far.
|
||||
let numReturned = batchSize; // One batch was returned so far.
|
||||
print(numReturned + " docs returned in the first batch");
|
||||
while (true) {
|
||||
// Run getmores repeatedly until we exhaust the cache on mongos.
|
||||
// Eventually we should get partial results or an error because a shard is down.
|
||||
const res2 = coll.runCommand(
|
||||
{getMore: res.cursor.id, collection: collName, batchSize: batchSizeForGetMore});
|
||||
const res2 =
|
||||
coll.runCommand({getMore: res.cursor.id, collection: collName, batchSize: batchSize});
|
||||
if (allowPartialResults) {
|
||||
assert.commandWorked(res2);
|
||||
} else {
|
||||
@ -263,10 +319,10 @@ function getMoreShardTimeout(allowPartialResults, failureController) {
|
||||
}
|
||||
numReturned += res2.cursor.nextBatch.length;
|
||||
print(numReturned + " docs returned so far");
|
||||
assert.neq(numReturned, size, "Entire collection seemed to be cached by the first find!");
|
||||
assert.neq(numReturned, nDocs, "Entire collection seemed to be cached by the first find!");
|
||||
if (res2.cursor.partialResultsReturned) {
|
||||
if (allowPartialResults) {
|
||||
assert.lt(numReturned, size);
|
||||
assert.lt(numReturned, nDocs);
|
||||
break;
|
||||
} else {
|
||||
assert(false, "Partial results should not have been allowed.");
|
||||
@ -275,29 +331,11 @@ function getMoreShardTimeout(allowPartialResults, failureController) {
|
||||
}
|
||||
failureController.disable();
|
||||
}
|
||||
// getMore timeout with allowPartialResults=true.
|
||||
getMoreShardTimeout(true, shard0Failpoint);
|
||||
getMoreShardTimeout(true, shard1Failpoint);
|
||||
getMoreShardTimeout(true, shard0NetworkFailure);
|
||||
getMoreShardTimeout(true, shard1NetworkFailure);
|
||||
// The FindWhereSleepFailureController must be set before the first "find" because that's when the
|
||||
// $where clause is set.
|
||||
shard0SleepFailure.enable();
|
||||
getMoreShardTimeout(true, shard0SleepFailure);
|
||||
shard1SleepFailure.enable();
|
||||
getMoreShardTimeout(true, shard1SleepFailure);
|
||||
|
||||
// getMore timeout with allowPartialResults=false.
|
||||
getMoreShardTimeout(false, shard0Failpoint);
|
||||
getMoreShardTimeout(false, shard1Failpoint);
|
||||
getMoreShardTimeout(false, shard0NetworkFailure);
|
||||
getMoreShardTimeout(false, shard1NetworkFailure);
|
||||
// The FindWhereSleepFailureController must be set before the first "find" because that's when the
|
||||
// $where clause is set.
|
||||
shard0SleepFailure.enable();
|
||||
getMoreShardTimeout(false, shard0SleepFailure);
|
||||
shard1SleepFailure.enable();
|
||||
getMoreShardTimeout(false, shard1SleepFailure);
|
||||
withEachValueOfAllowPartialResults(
|
||||
allowPartialResults => withEachBatchSize(
|
||||
batchSize => withEachSingleShardFailure(
|
||||
failure => getMoreShardTimeout(allowPartialResults, failure, batchSize))));
|
||||
|
||||
// With 'allowPartialResults: true', if a shard times out on the first batch then return
|
||||
// partial results.
|
||||
@ -305,16 +343,11 @@ function partialResultsTrueFirstBatch(failureController) {
|
||||
failureController.enable();
|
||||
const res = assert.commandWorked(runQuery(true));
|
||||
assert(res.cursor.partialResultsReturned);
|
||||
assert.eq(res.cursor.firstBatch.length, size / 2);
|
||||
assert.eq(res.cursor.firstBatch.length, nDocs / 2);
|
||||
assert.eq(0, res.cursor.id);
|
||||
failureController.disable();
|
||||
}
|
||||
partialResultsTrueFirstBatch(shard0Failpoint);
|
||||
partialResultsTrueFirstBatch(shard1Failpoint);
|
||||
partialResultsTrueFirstBatch(shard0NetworkFailure);
|
||||
partialResultsTrueFirstBatch(shard1NetworkFailure);
|
||||
partialResultsTrueFirstBatch(shard0SleepFailure);
|
||||
partialResultsTrueFirstBatch(shard1SleepFailure);
|
||||
withEachSingleShardFailure(failure => partialResultsTrueFirstBatch(failure));
|
||||
|
||||
// With 'allowPartialResults: false', if one shard times out then return a timeout error.
|
||||
function partialResultsFalseOneFailure(failureController) {
|
||||
@ -322,12 +355,7 @@ function partialResultsFalseOneFailure(failureController) {
|
||||
assert.commandFailedWithCode(runQuery(false), ErrorCodes.MaxTimeMSExpired);
|
||||
failureController.disable();
|
||||
}
|
||||
partialResultsFalseOneFailure(shard0Failpoint);
|
||||
partialResultsFalseOneFailure(shard1Failpoint);
|
||||
partialResultsFalseOneFailure(shard0NetworkFailure);
|
||||
partialResultsFalseOneFailure(shard1NetworkFailure);
|
||||
partialResultsFalseOneFailure(shard0SleepFailure);
|
||||
partialResultsFalseOneFailure(shard1SleepFailure);
|
||||
withEachSingleShardFailure(failure => partialResultsFalseOneFailure(failure));
|
||||
|
||||
// With 'allowPartialResults: false', if both shards time out then return a timeout error.
|
||||
function allowPartialResultsFalseAllFailed(failureController) {
|
||||
@ -335,10 +363,7 @@ function allowPartialResultsFalseAllFailed(failureController) {
|
||||
assert.commandFailedWithCode(runQuery(false), ErrorCodes.MaxTimeMSExpired);
|
||||
failureController.disable();
|
||||
}
|
||||
allowPartialResultsFalseAllFailed(allShardsFailpoint);
|
||||
allowPartialResultsFalseAllFailed(allshardsNetworkFailure);
|
||||
allowPartialResultsFalseAllFailed(allshardsMixedFailures);
|
||||
allowPartialResultsFalseAllFailed(allShardsSleepFailure);
|
||||
withEachAllShardFailure(failure => allowPartialResultsFalseAllFailed(failure));
|
||||
|
||||
// With 'allowPartialResults: true', if both shards time out then return empty "partial" results.
|
||||
function allowPartialResultsTrueAllFailed(failureController) {
|
||||
@ -349,10 +374,7 @@ function allowPartialResultsTrueAllFailed(failureController) {
|
||||
assert.eq(res.cursor.firstBatch.length, 0);
|
||||
failureController.disable();
|
||||
}
|
||||
allowPartialResultsTrueAllFailed(allShardsFailpoint);
|
||||
allowPartialResultsTrueAllFailed(allshardsNetworkFailure);
|
||||
allowPartialResultsTrueAllFailed(allshardsMixedFailures);
|
||||
allowPartialResultsTrueAllFailed(allShardsSleepFailure);
|
||||
withEachAllShardFailure(failure => allowPartialResultsTrueAllFailed(failure));
|
||||
|
||||
st.stop();
|
||||
}());
|
||||
|
||||
@ -63,8 +63,5 @@ assert.eq(125,
|
||||
"Number of documents on the recipient shard after moveChunk is incorrect.");
|
||||
assert.eq(175, testColl.find().itcount(), "Number of total documents is incorrect");
|
||||
|
||||
// Set the validationAction to "warn" to avoid failing collection validation.
|
||||
assert.commandWorked(testColl.runCommand("collMod", {validationAction: "warn"}));
|
||||
|
||||
st.stop();
|
||||
})();
|
||||
|
||||
@ -16,13 +16,17 @@ function getConnAcquiredToWireMicros(conn) {
|
||||
.metrics.network.totalTimeForEgressConnectionAcquiredToWireMicros;
|
||||
}
|
||||
|
||||
// Set it so that we log the intended metrics only on the mongos.
|
||||
const paramsDoc = {
|
||||
mongosOptions: {setParameter: {connectionAcquisitionToWireLoggingRate: 1.0}},
|
||||
shardOptions: {setParameter: {connectionAcquisitionToWireLoggingRate: 0.0}},
|
||||
configOptions: {setParameter: {connectionAcquisitionToWireLoggingRate: 0.0}},
|
||||
const setParamOptions = {
|
||||
"failpoint.alwaysLogConnAcquisitionToWireTime": tojson({mode: "alwaysOn"}),
|
||||
logComponentVerbosity: tojson({network: {verbosity: 2}})
|
||||
};
|
||||
const st = new ShardingTest({shards: 1, mongos: 1, other: paramsDoc});
|
||||
|
||||
const st = new ShardingTest({
|
||||
shards: 1,
|
||||
rs: {nodes: 1, setParameter: setParamOptions},
|
||||
mongos: 1,
|
||||
mongosOptions: {setParameter: setParamOptions}
|
||||
});
|
||||
let initialConnAcquiredToWireTime = getConnAcquiredToWireMicros(st.s);
|
||||
jsTestLog(`Initial metric value for mongos totalTimeForEgressConnectionAcquiredToWireMicros: ${
|
||||
tojson(initialConnAcquiredToWireTime)}`);
|
||||
@ -39,22 +43,9 @@ assert.gt(afterConnAcquiredToWireTime,
|
||||
initialConnAcquiredToWireTime,
|
||||
st.s.adminCommand({serverStatus: 1}));
|
||||
|
||||
// Test that setting the logging rate to 0 results in silencing of the logs.
|
||||
st.s.adminCommand({setParameter: 1, connectionAcquisitionToWireLoggingRate: 0.0});
|
||||
assert.commandWorked(st.s.adminCommand({clearLog: 'global'}));
|
||||
assert.commandWorked(st.s.getDB(jsTestName())["test"].insert({x: 2}));
|
||||
try {
|
||||
checkLog.containsJson(st.s, 6496702, null, 5 * 1000);
|
||||
assert(false);
|
||||
} catch (e) {
|
||||
jsTestLog("Waited long enough to believe logs were correctly silenced.");
|
||||
}
|
||||
|
||||
// Test with mirrored reads to execute the 'fireAndForget' path and verify logs are still correctly
|
||||
// printed.
|
||||
const shardPrimary = st.rs0.getPrimary();
|
||||
assert.commandWorked(
|
||||
shardPrimary.adminCommand({setParameter: 1, connectionAcquisitionToWireLoggingRate: 1.0}));
|
||||
assert.commandWorked(shardPrimary.adminCommand({clearLog: 'global'}));
|
||||
initialConnAcquiredToWireTime = getConnAcquiredToWireMicros(shardPrimary);
|
||||
jsTestLog(`Initial metric value for mongod totalTimeForEgressConnectionAcquiredToWireMicros: ${
|
||||
|
||||
@ -24,7 +24,7 @@ const st = new ShardingTest({
|
||||
const configPrimary = st.configRS.getPrimary();
|
||||
const serverStatusCmd = ({serverStatus: 1, shardingStatistics: 1});
|
||||
let res = assert.commandWorked(configPrimary.adminCommand(serverStatusCmd));
|
||||
assert(!res.hasOwnProperty("shardingStatistics"), res.shardingStatistics);
|
||||
assert(!res.shardingStatistics.hasOwnProperty("globalIndex"), res.shardingStatistics);
|
||||
|
||||
const shardPrimary = st.shard0.rs.getPrimary();
|
||||
res = assert.commandWorked(shardPrimary.adminCommand(serverStatusCmd));
|
||||
|
||||
@ -46,7 +46,7 @@ assert.commandFailedWithCode(
|
||||
|
||||
const serverStatusCmd = ({serverStatus: 1, shardingStatistics: 1});
|
||||
let res = assert.commandWorked(configPrimary.adminCommand(serverStatusCmd));
|
||||
assert(!res.hasOwnProperty("shardingStatistics"), res.shardingStatistics);
|
||||
assert(!res.shardingStatistics.hasOwnProperty("resharding"), res.shardingStatistics);
|
||||
|
||||
const shardPrimary = st.shard0.rs.getPrimary();
|
||||
res = assert.commandWorked(shardPrimary.adminCommand(serverStatusCmd));
|
||||
|
||||
@ -54,10 +54,6 @@ for (let [_, shardReplSet] of Object.entries(topology.shards)) {
|
||||
allNodes.push(topology.configsvr.primary);
|
||||
allNodes.forEach((hostName) => {
|
||||
const status = new Mongo(hostName).getDB('admin').serverStatus({});
|
||||
if (hostName == topology.configsvr.primary) {
|
||||
assert(!status.hasOwnProperty('shardingStatistics'));
|
||||
return;
|
||||
}
|
||||
const shardingStats = status.shardingStatistics;
|
||||
assert(!shardingStats.hasOwnProperty('resharding'));
|
||||
});
|
||||
|
||||
@ -64,6 +64,13 @@ function checkServerStatusAbortedMigrationCount(shardConn, count) {
|
||||
assert.eq(count, shardStats.countDonorMoveChunkAbortConflictingIndexOperation);
|
||||
}
|
||||
|
||||
function checkServerStatusNumShardedCollections(conn, count) {
|
||||
const shardStats =
|
||||
assert.commandWorked(conn.adminCommand({serverStatus: 1})).shardingStatistics;
|
||||
assert(shardStats.hasOwnProperty("numShardedCollections"));
|
||||
assert.eq(count, shardStats.numShardedCollections);
|
||||
}
|
||||
|
||||
function runConcurrentMoveChunk(host, ns, toShard) {
|
||||
const mongos = new Mongo(host);
|
||||
// Helper function to run moveChunk, retrying on ConflictingOperationInProgress. We need to
|
||||
@ -132,6 +139,17 @@ st.ensurePrimaryShard(coll.getDB() + "", st.shard0.shardName);
|
||||
assert.commandWorked(admin.runCommand({shardCollection: coll + "", key: {_id: 1}}));
|
||||
assert.commandWorked(admin.runCommand({split: coll + "", middle: {_id: 0}}));
|
||||
|
||||
// Check the number of sharded collections.
|
||||
const testDB = st.rs0.getPrimary().getDB(dbName);
|
||||
const fcvDoc = testDB.adminCommand({getParameter: 1, featureCompatibilityVersion: 1});
|
||||
if (MongoRunner.compareBinVersions(fcvDoc.featureCompatibilityVersion.version, '6.2') >= 0) {
|
||||
st.shardColl(dbName + ".coll2", {_id: 1}, false);
|
||||
st.shardColl(dbName + ".coll3", {_id: 1}, false);
|
||||
const configCollections = mongos.getCollection("config.collections");
|
||||
checkServerStatusNumShardedCollections(st.configRS.getPrimary(),
|
||||
configCollections.countDocuments({}));
|
||||
}
|
||||
|
||||
// Move chunk from shard0 to shard1 without docs.
|
||||
assert.commandWorked(
|
||||
mongos.adminCommand({moveChunk: coll + '', find: {_id: 1}, to: st.shard1.shardName}));
|
||||
|
||||
80
jstests/telemetry/redact_queries_with_nonobject_fields.js
Normal file
80
jstests/telemetry/redact_queries_with_nonobject_fields.js
Normal file
@ -0,0 +1,80 @@
|
||||
/**
|
||||
* Test that telemetry key generation works for queries with non-object fields.
|
||||
*/
|
||||
load('jstests/libs/analyze_plan.js');
|
||||
load("jstests/libs/feature_flag_util.js");
|
||||
|
||||
(function() {
|
||||
"use strict";
|
||||
|
||||
if (!FeatureFlagUtil.isEnabled(db, "Telemetry")) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Turn on the collecting of telemetry metrics.
|
||||
let options = {
|
||||
setParameter: {internalQueryConfigureTelemetrySamplingRate: 2147483647},
|
||||
};
|
||||
|
||||
const conn = MongoRunner.runMongod(options);
|
||||
const testDB = conn.getDB('test');
|
||||
var collA = testDB[jsTestName()];
|
||||
var collB = db[jsTestName() + 'Two'];
|
||||
collA.drop();
|
||||
collB.drop();
|
||||
|
||||
for (var i = 0; i < 200; i++) {
|
||||
collA.insert({foo: 0, bar: Math.floor(Math.random() * 3)});
|
||||
collA.insert({foo: 1, bar: Math.floor(Math.random() * -2)});
|
||||
collB.insert({foo: Math.floor(Math.random() * 2), bar: Math.floor(Math.random() * 2)});
|
||||
}
|
||||
|
||||
function confirmAggSuccess(collName, pipeline) {
|
||||
const command = {aggregate: collName, cursor: {}};
|
||||
command.pipeline = pipeline;
|
||||
assert.commandWorked(testDB.runCommand(command));
|
||||
}
|
||||
// Test with non-object fields $limit and $skip.
|
||||
confirmAggSuccess(collA.getName(), [{$sort: {bar: -1}}, {$limit: 2}, {$match: {foo: {$lte: 2}}}]);
|
||||
confirmAggSuccess(collA.getName(), [{$sort: {bar: -1}}, {$skip: 50}, {$match: {foo: {$lte: 2}}}]);
|
||||
confirmAggSuccess(collA.getName(),
|
||||
[{$sort: {bar: -1}}, {$limit: 2}, {$skip: 50}, {$match: {foo: 0}}]);
|
||||
|
||||
// Test non-object field, $unionWith.
|
||||
confirmAggSuccess(collA.getName(), [{$unionWith: collB.getName()}]);
|
||||
|
||||
// Test $limit in $setWindowFields for good measure.
|
||||
confirmAggSuccess(collA.getName(), [
|
||||
{$_internalInhibitOptimization: {}},
|
||||
{
|
||||
$setWindowFields: {
|
||||
sortBy: {foo: 1},
|
||||
output: {sum: {$sum: "$bar", window: {documents: ["unbounded", "current"]}}}
|
||||
}
|
||||
},
|
||||
{$sort: {foo: 1}},
|
||||
{$limit: 5}
|
||||
]);
|
||||
// Test find commands containing non-object fields
|
||||
assert.commandWorked(testDB.runCommand({find: collA.getName(), limit: 20}));
|
||||
assert.commandWorked(testDB.runCommand({find: collA.getName(), skip: 199}));
|
||||
collA.find().skip(100);
|
||||
|
||||
// findOne has a nonobject field, $limit.
|
||||
collB.findOne();
|
||||
collB.findOne({foo: 1});
|
||||
|
||||
// Test non-object field $unwind
|
||||
confirmAggSuccess(
|
||||
collA.getName(), [{
|
||||
"$facet": {
|
||||
"productOfJoin": [
|
||||
{"$lookup": {"from": collB.getName(), "pipeline": [{"$match": {}}], "as": "join"}},
|
||||
{"$unwind": "$join"},
|
||||
{"$project": {"str": 1}}
|
||||
]
|
||||
}
|
||||
}]);
|
||||
|
||||
MongoRunner.stopMongod(conn);
|
||||
}());
|
||||
@ -1,15 +1,18 @@
|
||||
/**
|
||||
* Test that the telemetry metrics are updated correctly across getMores.
|
||||
*/
|
||||
load('jstests/libs/analyze_plan.js');
|
||||
load("jstests/libs/profiler.js"); // For getLatestProfilerEntry.
|
||||
|
||||
(function() {
|
||||
"use strict";
|
||||
|
||||
// Turn on the collection of telemetry metrics.
|
||||
if (!FeatureFlagUtil.isEnabled(db, "Telemetry")) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Turn on the collecting of telemetry metrics.
|
||||
let options = {
|
||||
setParameter: "internalQueryConfigureTelemetrySamplingRate=2147483647",
|
||||
setParameter: {internalQueryConfigureTelemetrySamplingRate: 2147483647},
|
||||
};
|
||||
|
||||
const conn = MongoRunner.runMongod(options);
|
||||
@ -18,6 +21,10 @@ var coll = testDB[jsTestName()];
|
||||
var collTwo = db[jsTestName() + 'Two'];
|
||||
coll.drop();
|
||||
|
||||
// Make it easier to extract correct telemetry store entry for purposes of this test.
|
||||
assert.commandWorked(testDB.adminCommand(
|
||||
{setParameter: 1, internalQueryConfigureTelemetryFieldNameRedactionStrategy: "none"}));
|
||||
|
||||
function verifyMetrics(batch) {
|
||||
batch.forEach(element => {
|
||||
assert(element.metrics.docsScanned.sum > element.metrics.docsScanned.min);
|
||||
@ -73,11 +80,14 @@ verifyMetrics(telStore.cursor.firstBatch);
|
||||
|
||||
// Ensure that for queries using an index, keys scanned is nonzero.
|
||||
assert.commandWorked(coll.createIndex({bar: 1}));
|
||||
coll.aggregate([{$match: {bar: 1}}], {cursor: {batchSize: 2}});
|
||||
coll.aggregate([{$match: {$or: [{bar: 1, foo: 1}]}}], {cursor: {batchSize: 2}});
|
||||
// This filters telemetry entries to just the one entered for the above agg command.
|
||||
telStore = testDB.adminCommand({
|
||||
aggregate: 1,
|
||||
pipeline: [{$telemetry: {}}, {$match: {"key.pipeline.$match.bar": {$eq: "###"}}}],
|
||||
pipeline: [
|
||||
{$telemetry: {}},
|
||||
{$match: {"key.pipeline.$match.$or": {$eq: [{'bar': '###', 'foo': '###'}]}}}
|
||||
],
|
||||
cursor: {}
|
||||
});
|
||||
assert(telStore.cursor.firstBatch[0].metrics.keysScanned.sum > 0);
|
||||
|
||||
@ -506,6 +506,8 @@ error_codes:
|
||||
|
||||
- {code: 387, name: EncounteredFLEPayloadWhileRedacting}
|
||||
|
||||
- {code: 388, name: TransactionTooLargeForCache}
|
||||
|
||||
# Error codes 4000-8999 are reserved.
|
||||
|
||||
# Non-sequential error codes for compatibility only)
|
||||
|
||||
@ -95,9 +95,10 @@ public:
|
||||
* BinData (0x05)
|
||||
*/
|
||||
BSONBinData BinData() const {
|
||||
uint8_t subtype = ConstDataView(value() + kCountBytes).read<LittleEndian<uint8_t>>();
|
||||
return {value() + kCountBytes + kBinDataSubTypeBytes,
|
||||
ConstDataView(value()).read<LittleEndian<int>>(),
|
||||
static_cast<BinDataType>(*(value() + kCountBytes))};
|
||||
static_cast<BinDataType>(subtype)};
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -255,7 +255,6 @@ env.Library(
|
||||
target='async_client',
|
||||
source=[
|
||||
'async_client.cpp',
|
||||
env.Idlc('async_client.idl')[0],
|
||||
],
|
||||
LIBDEPS=[
|
||||
'$BUILD_DIR/mongo/db/wire_version',
|
||||
|
||||
@ -35,7 +35,6 @@
|
||||
#include <memory>
|
||||
|
||||
#include "mongo/bson/bsonobjbuilder.h"
|
||||
#include "mongo/client/async_client_gen.h"
|
||||
#include "mongo/client/authenticate.h"
|
||||
#include "mongo/client/sasl_client_authenticate.h"
|
||||
#include "mongo/config.h"
|
||||
@ -63,6 +62,7 @@
|
||||
|
||||
namespace mongo {
|
||||
MONGO_FAIL_POINT_DEFINE(pauseBeforeMarkKeepOpen);
|
||||
MONGO_FAIL_POINT_DEFINE(alwaysLogConnAcquisitionToWireTime)
|
||||
|
||||
namespace {
|
||||
bool connHealthMetricsEnabled() {
|
||||
@ -316,14 +316,14 @@ Future<rpc::UniqueReply> AsyncDBClient::runCommand(
|
||||
durationCount<Microseconds>(fromConnAcquiredTimer.get()->elapsed());
|
||||
totalTimeForEgressConnectionAcquiredToWireMicros.increment(timeElapsedMicros);
|
||||
if (timeElapsedMicros >= 1000 ||
|
||||
_random.nextCanonicalDouble() <= gConnectionAcquisitionToWireLoggingRate.load()) {
|
||||
LOGV2_INFO(6496702,
|
||||
"Acquired connection for remote operation and completed writing to wire",
|
||||
"durationMicros"_attr = timeElapsedMicros);
|
||||
} else {
|
||||
MONGO_unlikely(alwaysLogConnAcquisitionToWireTime.shouldFail())) {
|
||||
// Log slow acquisition times at info level but rate limit it to prevent spamming
|
||||
// users.
|
||||
static auto& logSeverity = *new logv2::SeveritySuppressor{
|
||||
Seconds{1}, logv2::LogSeverity::Info(), logv2::LogSeverity::Debug(2)};
|
||||
LOGV2_DEBUG(
|
||||
6496701,
|
||||
2,
|
||||
6496702,
|
||||
logSeverity().toInt(),
|
||||
"Acquired connection for remote operation and completed writing to wire",
|
||||
"durationMicros"_attr = timeElapsedMicros);
|
||||
}
|
||||
|
||||
@ -37,6 +37,7 @@
|
||||
#include "mongo/executor/network_connection_hook.h"
|
||||
#include "mongo/executor/remote_command_request.h"
|
||||
#include "mongo/executor/remote_command_response.h"
|
||||
#include "mongo/logv2/log_severity_suppressor.h"
|
||||
#include "mongo/rpc/unique_message.h"
|
||||
#include "mongo/transport/baton.h"
|
||||
#include "mongo/transport/message_compressor_manager.h"
|
||||
@ -51,10 +52,7 @@ public:
|
||||
explicit AsyncDBClient(const HostAndPort& peer,
|
||||
transport::SessionHandle session,
|
||||
ServiceContext* svcCtx)
|
||||
: _peer(std::move(peer)),
|
||||
_session(std::move(session)),
|
||||
_svcCtx(svcCtx),
|
||||
_random{PseudoRandom(SecureRandom().nextInt64())} {}
|
||||
: _peer(std::move(peer)), _session(std::move(session)), _svcCtx(svcCtx) {}
|
||||
|
||||
using Handle = std::shared_ptr<AsyncDBClient>;
|
||||
|
||||
@ -104,6 +102,7 @@ public:
|
||||
|
||||
const HostAndPort& remote() const;
|
||||
const HostAndPort& local() const;
|
||||
static constexpr Seconds kSlowConnAcquiredToWireLogSuppresionPeriod{5};
|
||||
|
||||
private:
|
||||
Future<executor::RemoteCommandResponse> _continueReceiveExhaustResponse(
|
||||
@ -123,7 +122,6 @@ private:
|
||||
transport::SessionHandle _session;
|
||||
ServiceContext* const _svcCtx;
|
||||
MessageCompressorManager _compressorManager;
|
||||
PseudoRandom _random;
|
||||
};
|
||||
|
||||
} // namespace mongo
|
||||
|
||||
@ -444,6 +444,30 @@ See
|
||||
[wtRcToStatus](https://github.com/mongodb/mongo/blob/c799851554dc01493d35b43701416e9c78b3665c/src/mongo/db/storage/wiredtiger/wiredtiger_util.cpp#L178-L183)
|
||||
where we throw the exception in WiredTiger.
|
||||
See [TemporarilyUnavailableException](https://github.com/mongodb/mongo/blob/c799851554dc01493d35b43701416e9c78b3665c/src/mongo/db/concurrency/temporarily_unavailable_exception.h#L39-L45).
|
||||
|
||||
## TransactionTooLargeForCacheException
|
||||
|
||||
A TransactionTooLargeForCacheException may be thrown inside the server to indicate that an operation
|
||||
was rolled-back and is unlikely to ever complete because the storage engine cache is insufficient,
|
||||
even in the absence of concurrent operations. This is determined by a simple heuristic wherein,
|
||||
after a rollback, a threshold on the proportion of total dirty cache bytes the running transaction
|
||||
can represent and still be considered fullfillable is checked. The threshold can be tuned with the
|
||||
`transactionTooLargeForCacheThreshold` parameter. Setting this threshold to its maximum value (1.0)
|
||||
causes the check to be skipped and TransactionTooLargeForCacheException to be disabled.
|
||||
|
||||
On replica sets, if an operation succeeds on a primary, it should also succeed on a secondary. It
|
||||
would be possible to convert to both TemporarilyUnavailableException and WriteConflictException,
|
||||
as if TransactionTooLargeForCacheException was disabled. But on secondaries the only
|
||||
difference between the two is the rate at which the operation is retried. Hence,
|
||||
TransactionTooLargeForCacheException is always converted to a WriteConflictException, which retries
|
||||
faster, to avoid stalling replication longer than necessary.
|
||||
|
||||
Prior to 6.3, or when TransactionTooLargeForCacheException is disabled, multi-document
|
||||
transactions always return a WriteConflictException, which may result in drivers retrying an
|
||||
operation indefinitely. For non-multi-document operations, there is a limited number of retries on
|
||||
TemporarilyUnavailableException, but it might still be beneficial to not retry operations which are
|
||||
unlikely to complete and are disruptive for concurrent operations.
|
||||
|
||||
## Collection and Index Writes
|
||||
|
||||
Collection write operations (inserts, updates, and deletes) perform storage engine writes to both
|
||||
|
||||
@ -133,7 +133,9 @@ void schemaValidationFailed(CollectionValidation::ValidateState* state,
|
||||
|
||||
state->setCollectionSchemaViolated();
|
||||
|
||||
if (Collection::SchemaValidationResult::kWarn == result) {
|
||||
// When testing is enabled, only warn about non-compliant documents to prevent test failures.
|
||||
if (TestingProctor::instance().isEnabled() ||
|
||||
Collection::SchemaValidationResult::kWarn == result) {
|
||||
results->warnings.push_back(kSchemaValidationFailedReason);
|
||||
} else if (Collection::SchemaValidationResult::kError == result) {
|
||||
results->errors.push_back(kSchemaValidationFailedReason);
|
||||
|
||||
@ -57,7 +57,7 @@ void ChangeStreamOptionsManager::create(ServiceContext* service) {
|
||||
getChangeStreamOptionsManager(service).emplace(service);
|
||||
}
|
||||
|
||||
const ChangeStreamOptions& ChangeStreamOptionsManager::getOptions(OperationContext* opCtx) {
|
||||
ChangeStreamOptions ChangeStreamOptionsManager::getOptions(OperationContext* opCtx) const {
|
||||
stdx::lock_guard<Latch> L(_mutex);
|
||||
return _changeStreamOptions;
|
||||
}
|
||||
|
||||
@ -68,7 +68,7 @@ public:
|
||||
/**
|
||||
* Returns the change-streams options.
|
||||
*/
|
||||
const ChangeStreamOptions& getOptions(OperationContext* opCtx);
|
||||
ChangeStreamOptions getOptions(OperationContext* opCtx) const;
|
||||
|
||||
/**
|
||||
* Sets the provided change-streams options. Returns OK on success, otherwise appropriate error
|
||||
@ -87,7 +87,7 @@ public:
|
||||
private:
|
||||
ChangeStreamOptions _changeStreamOptions;
|
||||
|
||||
Mutex _mutex = MONGO_MAKE_LATCH("ChangeStreamOptionsManager::mutex");
|
||||
mutable Mutex _mutex = MONGO_MAKE_LATCH("ChangeStreamOptionsManager::mutex");
|
||||
};
|
||||
|
||||
} // namespace mongo
|
||||
|
||||
@ -284,12 +284,21 @@ public:
|
||||
auto keyPattern = cmd.getKeyPattern().get_value_or({});
|
||||
const bool estimate = cmd.getEstimate();
|
||||
|
||||
AutoGetCollectionForReadCommand collection(opCtx, nss);
|
||||
Reply reply;
|
||||
|
||||
auto collDesc = CollectionShardingState::assertCollectionLockedAndAcquire(opCtx, nss)
|
||||
->getCollectionDescription(opCtx);
|
||||
if (collDesc.isSharded()) {
|
||||
const ShardKeyPattern shardKeyPattern(collDesc.getKeyPattern());
|
||||
AutoGetCollectionForReadCommand autoColl(opCtx, nss);
|
||||
const auto& collection = autoColl.getCollection();
|
||||
|
||||
if (!collection) {
|
||||
// Collection does not exist
|
||||
reply.setNumObjects(0);
|
||||
reply.setSize(0);
|
||||
reply.setMillis(timer.millis());
|
||||
return reply;
|
||||
}
|
||||
|
||||
if (collection.isSharded()) {
|
||||
const ShardKeyPattern shardKeyPattern(collection.getShardKeyPattern());
|
||||
uassert(ErrorCodes::BadValue,
|
||||
"keyPattern must be empty or must be an object that equals the shard key",
|
||||
keyPattern.isEmpty() ||
|
||||
@ -307,12 +316,7 @@ public:
|
||||
max = shardKeyPattern.normalizeShardKey(max);
|
||||
}
|
||||
|
||||
long long numRecords = 0;
|
||||
if (collection) {
|
||||
numRecords = collection->numRecords(opCtx);
|
||||
}
|
||||
|
||||
Reply reply;
|
||||
const long long numRecords = collection->numRecords(opCtx);
|
||||
reply.setNumObjects(numRecords);
|
||||
|
||||
if (numRecords == 0) {
|
||||
@ -335,10 +339,8 @@ public:
|
||||
reply.setMillis(timer.millis());
|
||||
return reply;
|
||||
}
|
||||
exec =
|
||||
InternalPlanner::collectionScan(opCtx,
|
||||
&collection.getCollection(),
|
||||
PlanYieldPolicy::YieldPolicy::INTERRUPT_ONLY);
|
||||
exec = InternalPlanner::collectionScan(
|
||||
opCtx, &collection, PlanYieldPolicy::YieldPolicy::YIELD_AUTO);
|
||||
} else {
|
||||
if (keyPattern.isEmpty()) {
|
||||
// if keyPattern not provided, try to infer it from the fields in 'min'
|
||||
@ -346,7 +348,7 @@ public:
|
||||
}
|
||||
|
||||
auto shardKeyIdx = findShardKeyPrefixedIndex(opCtx,
|
||||
*collection,
|
||||
collection,
|
||||
collection->getIndexCatalog(),
|
||||
keyPattern,
|
||||
/*requireSingleKey=*/true);
|
||||
@ -360,14 +362,13 @@ public:
|
||||
min = Helpers::toKeyFormat(kp.extendRangeBound(min, false));
|
||||
max = Helpers::toKeyFormat(kp.extendRangeBound(max, false));
|
||||
|
||||
exec = InternalPlanner::shardKeyIndexScan(
|
||||
opCtx,
|
||||
&collection.getCollection(),
|
||||
*shardKeyIdx,
|
||||
min,
|
||||
max,
|
||||
BoundInclusion::kIncludeStartKeyOnly,
|
||||
PlanYieldPolicy::YieldPolicy::INTERRUPT_ONLY);
|
||||
exec = InternalPlanner::shardKeyIndexScan(opCtx,
|
||||
&collection,
|
||||
*shardKeyIdx,
|
||||
min,
|
||||
max,
|
||||
BoundInclusion::kIncludeStartKeyOnly,
|
||||
PlanYieldPolicy::YieldPolicy::YIELD_AUTO);
|
||||
}
|
||||
|
||||
CurOpFailpointHelpers::waitWhileFailPointEnabled(
|
||||
|
||||
@ -712,7 +712,7 @@ public:
|
||||
// The stats collected here will not get overwritten, as the service entry
|
||||
// point layer will only set these stats when they're not empty.
|
||||
CurOp::get(opCtx)->debug().storageStats =
|
||||
opCtx->recoveryUnit()->getOperationStatistics();
|
||||
opCtx->recoveryUnit()->computeOperationStatisticsSinceLastCall();
|
||||
}
|
||||
} else {
|
||||
endQueryOp(opCtx, collection, *exec, numResults, cursorId);
|
||||
|
||||
@ -919,7 +919,7 @@ private:
|
||||
// TODO (SERVER-70763): Remove once FCV 7.0 becomes last-lts.
|
||||
void _removeSchemaOnConfigSettings(
|
||||
OperationContext* opCtx, const multiversion::FeatureCompatibilityVersion requestedVersion) {
|
||||
if (!feature_flags::gGlobalIndexesShardingCatalog.isEnabledOnVersion(requestedVersion)) {
|
||||
if (!feature_flags::gConfigSettingsSchema.isEnabledOnVersion(requestedVersion)) {
|
||||
LOGV2(6885201, "Removing schema on config.settings");
|
||||
CollMod collModCmd{NamespaceString::kConfigSettingsNamespace};
|
||||
collModCmd.getCollModRequest().setValidator(BSONObj());
|
||||
|
||||
@ -1225,9 +1225,9 @@ public:
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto& insertResult = swResult.getValue();
|
||||
const auto& batch = insertResult.batch;
|
||||
batches.emplace_back(batch, index);
|
||||
auto& insertResult = swResult.getValue();
|
||||
batches.emplace_back(std::move(insertResult.batch), index);
|
||||
const auto& batch = batches.back().first;
|
||||
if (isTimeseriesWriteRetryable(opCtx)) {
|
||||
stmtIds[batch->bucket().id].push_back(stmtId);
|
||||
}
|
||||
|
||||
@ -27,6 +27,7 @@ env.Library(
|
||||
],
|
||||
LIBDEPS_PRIVATE=[
|
||||
'$BUILD_DIR/mongo/db/commands/server_status_core',
|
||||
'$BUILD_DIR/mongo/db/curop',
|
||||
'$BUILD_DIR/mongo/db/server_base',
|
||||
'$BUILD_DIR/mongo/db/server_options_servers',
|
||||
'$BUILD_DIR/mongo/db/storage/recovery_unit_base',
|
||||
|
||||
@ -33,6 +33,7 @@
|
||||
#include "mongo/db/concurrency/exception_util_gen.h"
|
||||
#include "mongo/db/namespace_string.h"
|
||||
#include "mongo/logv2/log.h"
|
||||
#include "mongo/util/assert_util.h"
|
||||
#include "mongo/util/duration.h"
|
||||
#include "mongo/util/log_and_backoff.h"
|
||||
|
||||
@ -59,6 +60,11 @@ CounterMetric temporarilyUnavailableErrorsEscaped{"operation.temporarilyUnavaila
|
||||
CounterMetric temporarilyUnavailableErrorsConvertedToWriteConflict{
|
||||
"operation.temporarilyUnavailableErrorsConvertedToWriteConflict"};
|
||||
|
||||
CounterMetric transactionTooLargeForCacheErrors{"operation.transactionTooLargeForCacheErrors"};
|
||||
CounterMetric transactionTooLargeForCacheErrorsConvertedToWriteConflict{
|
||||
"operation.transactionTooLargeForCacheErrorsConvertedToWriteConflict"};
|
||||
|
||||
|
||||
} // namespace
|
||||
|
||||
void handleTemporarilyUnavailableException(OperationContext* opCtx,
|
||||
@ -66,6 +72,8 @@ void handleTemporarilyUnavailableException(OperationContext* opCtx,
|
||||
StringData opStr,
|
||||
StringData ns,
|
||||
const TemporarilyUnavailableException& e) {
|
||||
CurOp::get(opCtx)->debug().additiveMetrics.incrementTemporarilyUnavailableErrors(1);
|
||||
|
||||
opCtx->recoveryUnit()->abandonSnapshot();
|
||||
temporarilyUnavailableErrors.increment(1);
|
||||
if (opCtx->getClient()->isFromUserConnection() &&
|
||||
@ -107,4 +115,27 @@ void handleTemporarilyUnavailableExceptionInTransaction(OperationContext* opCtx,
|
||||
throwWriteConflictException(e.reason());
|
||||
}
|
||||
|
||||
void handleTransactionTooLargeForCacheException(OperationContext* opCtx,
|
||||
int* writeConflictAttempts,
|
||||
StringData opStr,
|
||||
StringData ns,
|
||||
const TransactionTooLargeForCacheException& e) {
|
||||
transactionTooLargeForCacheErrors.increment(1);
|
||||
if (opCtx->writesAreReplicated()) {
|
||||
// Surface error on primaries.
|
||||
throw e;
|
||||
}
|
||||
// If an operation succeeds on primary, it should always be retried on secondaries. Secondaries
|
||||
// always retry TemporarilyUnavailableExceptions and WriteConflictExceptions indefinitely, the
|
||||
// only difference being the rate of retry. We prefer retrying faster, by converting to
|
||||
// WriteConflictException, to avoid stalling replication longer than necessary.
|
||||
transactionTooLargeForCacheErrorsConvertedToWriteConflict.increment(1);
|
||||
|
||||
// Handle as write conflict.
|
||||
CurOp::get(opCtx)->debug().additiveMetrics.incrementWriteConflicts(1);
|
||||
logWriteConflictAndBackoff(*writeConflictAttempts, opStr, ns);
|
||||
++(*writeConflictAttempts);
|
||||
opCtx->recoveryUnit()->abandonSnapshot();
|
||||
}
|
||||
|
||||
} // namespace mongo
|
||||
|
||||
@ -61,6 +61,12 @@ void handleTemporarilyUnavailableExceptionInTransaction(OperationContext* opCtx,
|
||||
StringData ns,
|
||||
const TemporarilyUnavailableException& e);
|
||||
|
||||
void handleTransactionTooLargeForCacheException(OperationContext* opCtx,
|
||||
int* writeConflictAttempts,
|
||||
StringData opStr,
|
||||
StringData ns,
|
||||
const TransactionTooLargeForCacheException& e);
|
||||
|
||||
/**
|
||||
* A `WriteConflictException` is thrown if during a write, two or more operations conflict with each
|
||||
* other. For example if two operations get the same version of a document, and then both try to
|
||||
@ -84,6 +90,16 @@ void handleTemporarilyUnavailableExceptionInTransaction(OperationContext* opCtx,
|
||||
iasserted({ErrorCodes::TemporarilyUnavailable, context});
|
||||
}
|
||||
|
||||
/**
|
||||
* A `TransactionTooLargeForCache` is thrown if it has been determined that it is unlikely to
|
||||
* ever complete the operation because the configured cache is insufficient to hold all the
|
||||
* transaction state. This helps to avoid retrying, maybe indefinitely, a transaction which would
|
||||
* never be able to complete.
|
||||
*/
|
||||
[[noreturn]] inline void throwTransactionTooLargeForCache(StringData context) {
|
||||
iasserted({ErrorCodes::TransactionTooLargeForCache, context});
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the argument function f as many times as needed for f to complete or throw an exception
|
||||
* other than WriteConflictException or TemporarilyUnavailableException. For each time f throws
|
||||
@ -119,19 +135,20 @@ auto writeConflictRetry(OperationContext* opCtx, StringData opStr, StringData ns
|
||||
}
|
||||
}
|
||||
|
||||
int attempts = 0;
|
||||
int writeConflictAttempts = 0;
|
||||
int attemptsTempUnavailable = 0;
|
||||
while (true) {
|
||||
try {
|
||||
return f();
|
||||
} catch (WriteConflictException const&) {
|
||||
CurOp::get(opCtx)->debug().additiveMetrics.incrementWriteConflicts(1);
|
||||
logWriteConflictAndBackoff(attempts, opStr, ns);
|
||||
++attempts;
|
||||
logWriteConflictAndBackoff(writeConflictAttempts, opStr, ns);
|
||||
++writeConflictAttempts;
|
||||
opCtx->recoveryUnit()->abandonSnapshot();
|
||||
} catch (TemporarilyUnavailableException const& e) {
|
||||
CurOp::get(opCtx)->debug().additiveMetrics.incrementTemporarilyUnavailableErrors(1);
|
||||
handleTemporarilyUnavailableException(opCtx, ++attemptsTempUnavailable, opStr, ns, e);
|
||||
} catch (TransactionTooLargeForCacheException const& e) {
|
||||
handleTransactionTooLargeForCacheException(opCtx, &writeConflictAttempts, opStr, ns, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -57,3 +57,18 @@ server_parameters:
|
||||
default: 1000
|
||||
validator:
|
||||
gte: 0
|
||||
|
||||
transactionTooLargeForCacheThreshold:
|
||||
description: "Threshold on the proportion of total dirty cache bytes that the running
|
||||
transaction's dirty cache bytes can represent and still be considered
|
||||
fullfillable on retry. If this threshold is exceeded, a
|
||||
TransactionTooLargeForCache exception is thrown. Setting this parameter to 1.0
|
||||
causes this check to be disabled, and TransactionTooLargeForCache exceptions
|
||||
will not be thrown."
|
||||
set_at: [ startup, runtime ]
|
||||
cpp_varname: 'gTransactionTooLargeForCacheThreshold'
|
||||
cpp_vartype: AtomicWord<double>
|
||||
default: 0.75
|
||||
validator:
|
||||
gte: 0.0
|
||||
lte: 1.0
|
||||
|
||||
@ -479,7 +479,8 @@ bool CurOp::completeAndLogOperation(OperationContext* opCtx,
|
||||
MODE_IS,
|
||||
Date_t::now() + Milliseconds(500),
|
||||
Lock::InterruptBehavior::kThrow);
|
||||
_debug.storageStats = opCtx->recoveryUnit()->getOperationStatistics();
|
||||
_debug.storageStats =
|
||||
opCtx->recoveryUnit()->computeOperationStatisticsSinceLastCall();
|
||||
} catch (const DBException& ex) {
|
||||
LOGV2_WARNING_OPTIONS(20526,
|
||||
{component},
|
||||
|
||||
@ -326,7 +326,7 @@ public:
|
||||
AdditiveMetrics additiveMetrics;
|
||||
|
||||
// Stores storage statistics.
|
||||
std::shared_ptr<StorageStats> storageStats;
|
||||
std::unique_ptr<StorageStats> storageStats;
|
||||
|
||||
bool waitingForFlowControl{false};
|
||||
|
||||
|
||||
@ -248,6 +248,11 @@ boost::optional<StringData> checkComparisonPredicateErrors(
|
||||
return "can't handle a computed field"_sd;
|
||||
}
|
||||
|
||||
// We must avoid mapping predicates on fields removed by $project.
|
||||
if (!determineIncludeField(matchExprPath, bucketSpec.behavior(), bucketSpec.fieldSet())) {
|
||||
return "can't handle a field removed by projection"_sd;
|
||||
}
|
||||
|
||||
const auto isTimeField = (matchExprPath == bucketSpec.timeField());
|
||||
if (isTimeField && matchExprData.type() != BSONType::Date) {
|
||||
// Users are not allowed to insert non-date measurements into time field. So this query
|
||||
@ -885,10 +890,8 @@ std::pair<bool, BSONObj> BucketSpec::pushdownPredicate(
|
||||
metaField.map([](StringData s) { return s.toString(); }),
|
||||
// Since we are operating on a collection, not a query-result,
|
||||
// there are no inclusion/exclusion projections we need to apply
|
||||
// to the buckets before unpacking.
|
||||
{},
|
||||
// And there are no computed projections.
|
||||
{},
|
||||
// to the buckets before unpacking. So we can use default values for the rest of
|
||||
// the arguments.
|
||||
},
|
||||
maxSpanSeconds,
|
||||
collationMatchesDefault,
|
||||
@ -924,7 +927,6 @@ public:
|
||||
int j,
|
||||
const BucketSpec& spec,
|
||||
const std::set<std::string>& unpackFieldsToIncludeExclude,
|
||||
BucketUnpacker::Behavior behavior,
|
||||
const BSONObj& bucket,
|
||||
const Value& metaValue,
|
||||
bool includeTimeField,
|
||||
@ -976,7 +978,6 @@ public:
|
||||
int j,
|
||||
const BucketSpec& spec,
|
||||
const std::set<std::string>& unpackFieldsToIncludeExclude,
|
||||
BucketUnpacker::Behavior behavior,
|
||||
const BSONObj& bucket,
|
||||
const Value& metaValue,
|
||||
bool includeTimeField,
|
||||
@ -988,7 +989,7 @@ private:
|
||||
BSONObjIterator _timeFieldIter;
|
||||
|
||||
// Iterators used to unpack the columns of the above bucket that are populated during the reset
|
||||
// phase according to the provided 'Behavior' and 'BucketSpec'.
|
||||
// phase according to the provided 'BucketSpec'.
|
||||
std::vector<std::pair<std::string, BSONObjIterator>> _fieldIters;
|
||||
};
|
||||
|
||||
@ -1063,7 +1064,6 @@ void BucketUnpackerV1::extractSingleMeasurement(
|
||||
int j,
|
||||
const BucketSpec& spec,
|
||||
const std::set<std::string>& unpackFieldsToIncludeExclude,
|
||||
BucketUnpacker::Behavior behavior,
|
||||
const BSONObj& bucket,
|
||||
const Value& metaValue,
|
||||
bool includeTimeField,
|
||||
@ -1078,7 +1078,7 @@ void BucketUnpackerV1::extractSingleMeasurement(
|
||||
|
||||
for (auto&& dataElem : dataRegion) {
|
||||
const auto& colName = dataElem.fieldNameStringData();
|
||||
if (!determineIncludeField(colName, behavior, unpackFieldsToIncludeExclude)) {
|
||||
if (!determineIncludeField(colName, spec.behavior(), unpackFieldsToIncludeExclude)) {
|
||||
continue;
|
||||
}
|
||||
auto value = dataElem[targetIdx];
|
||||
@ -1110,7 +1110,6 @@ public:
|
||||
int j,
|
||||
const BucketSpec& spec,
|
||||
const std::set<std::string>& unpackFieldsToIncludeExclude,
|
||||
BucketUnpacker::Behavior behavior,
|
||||
const BSONObj& bucket,
|
||||
const Value& metaValue,
|
||||
bool includeTimeField,
|
||||
@ -1140,7 +1139,7 @@ private:
|
||||
ColumnStore _timeColumn;
|
||||
|
||||
// Iterators used to unpack the columns of the above bucket that are populated during the reset
|
||||
// phase according to the provided 'Behavior' and 'BucketSpec'.
|
||||
// phase according to the provided 'BucketSpec'.
|
||||
std::vector<ColumnStore> _fieldColumns;
|
||||
|
||||
// Element count
|
||||
@ -1200,7 +1199,6 @@ void BucketUnpackerV2::extractSingleMeasurement(
|
||||
int j,
|
||||
const BucketSpec& spec,
|
||||
const std::set<std::string>& unpackFieldsToIncludeExclude,
|
||||
BucketUnpacker::Behavior behavior,
|
||||
const BSONObj& bucket,
|
||||
const Value& metaValue,
|
||||
bool includeTimeField,
|
||||
@ -1236,9 +1234,11 @@ std::size_t BucketUnpackerV2::numberOfFields() {
|
||||
BucketSpec::BucketSpec(const std::string& timeField,
|
||||
const boost::optional<std::string>& metaField,
|
||||
const std::set<std::string>& fields,
|
||||
Behavior behavior,
|
||||
const std::set<std::string>& computedProjections,
|
||||
bool usesExtendedRange)
|
||||
: _fieldSet(fields),
|
||||
_behavior(behavior),
|
||||
_computedMetaProjFields(computedProjections),
|
||||
_timeField(timeField),
|
||||
_timeFieldHashed(FieldNameHasher().hashedFieldName(_timeField)),
|
||||
@ -1251,6 +1251,7 @@ BucketSpec::BucketSpec(const std::string& timeField,
|
||||
|
||||
BucketSpec::BucketSpec(const BucketSpec& other)
|
||||
: _fieldSet(other._fieldSet),
|
||||
_behavior(other._behavior),
|
||||
_computedMetaProjFields(other._computedMetaProjFields),
|
||||
_timeField(other._timeField),
|
||||
_timeFieldHashed(HashedFieldName{_timeField, other._timeFieldHashed->hash()}),
|
||||
@ -1263,6 +1264,7 @@ BucketSpec::BucketSpec(const BucketSpec& other)
|
||||
|
||||
BucketSpec::BucketSpec(BucketSpec&& other)
|
||||
: _fieldSet(std::move(other._fieldSet)),
|
||||
_behavior(other._behavior),
|
||||
_computedMetaProjFields(std::move(other._computedMetaProjFields)),
|
||||
_timeField(std::move(other._timeField)),
|
||||
_timeFieldHashed(HashedFieldName{_timeField, other._timeFieldHashed->hash()}),
|
||||
@ -1276,6 +1278,7 @@ BucketSpec::BucketSpec(BucketSpec&& other)
|
||||
BucketSpec& BucketSpec::operator=(const BucketSpec& other) {
|
||||
if (&other != this) {
|
||||
_fieldSet = other._fieldSet;
|
||||
_behavior = other._behavior;
|
||||
_computedMetaProjFields = other._computedMetaProjFields;
|
||||
_timeField = other._timeField;
|
||||
_timeFieldHashed = HashedFieldName{_timeField, other._timeFieldHashed->hash()};
|
||||
@ -1325,8 +1328,8 @@ BucketUnpacker::BucketUnpacker(BucketUnpacker&& other) = default;
|
||||
BucketUnpacker::~BucketUnpacker() = default;
|
||||
BucketUnpacker& BucketUnpacker::operator=(BucketUnpacker&& rhs) = default;
|
||||
|
||||
BucketUnpacker::BucketUnpacker(BucketSpec spec, Behavior unpackerBehavior) {
|
||||
setBucketSpecAndBehavior(std::move(spec), unpackerBehavior);
|
||||
BucketUnpacker::BucketUnpacker(BucketSpec spec) {
|
||||
setBucketSpec(std::move(spec));
|
||||
}
|
||||
|
||||
void BucketUnpacker::addComputedMetaProjFields(const std::vector<StringData>& computedFieldNames) {
|
||||
@ -1335,7 +1338,7 @@ void BucketUnpacker::addComputedMetaProjFields(const std::vector<StringData>& co
|
||||
|
||||
// If we're already specifically including fields, we need to add the computed fields to
|
||||
// the included field set to indicate they're in the output doc.
|
||||
if (_unpackerBehavior == BucketUnpacker::Behavior::kInclude) {
|
||||
if (_spec.behavior() == BucketSpec::Behavior::kInclude) {
|
||||
_spec.addIncludeExcludeField(field);
|
||||
} else {
|
||||
// Since exclude is applied after addComputedMetaProjFields, we must erase the new field
|
||||
@ -1387,7 +1390,6 @@ Document BucketUnpacker::extractSingleMeasurement(int j) {
|
||||
j,
|
||||
_spec,
|
||||
fieldsToIncludeExcludeDuringUnpack(),
|
||||
_unpackerBehavior,
|
||||
_bucket,
|
||||
_metaValue,
|
||||
_includeTimeField,
|
||||
@ -1501,10 +1503,10 @@ void BucketUnpacker::reset(BSONObj&& bucket, bool bucketMatchedQuery) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Includes a field when '_unpackerBehavior' is 'kInclude' and it's found in 'fieldSet' or
|
||||
// _unpackerBehavior is 'kExclude' and it's not found in 'fieldSet'.
|
||||
// Includes a field when '_spec.behavior()' is 'kInclude' and it's found in 'fieldSet' or
|
||||
// _spec.behavior() is 'kExclude' and it's not found in 'fieldSet'.
|
||||
if (determineIncludeField(
|
||||
colName, _unpackerBehavior, fieldsToIncludeExcludeDuringUnpack())) {
|
||||
colName, _spec.behavior(), fieldsToIncludeExcludeDuringUnpack())) {
|
||||
_unpackingImpl->addField(elem);
|
||||
}
|
||||
}
|
||||
@ -1555,7 +1557,7 @@ int BucketUnpacker::computeMeasurementCount(const BSONObj& bucket, StringData ti
|
||||
}
|
||||
|
||||
void BucketUnpacker::determineIncludeTimeField() {
|
||||
const bool isInclude = _unpackerBehavior == BucketUnpacker::Behavior::kInclude;
|
||||
const bool isInclude = _spec.behavior() == BucketSpec::Behavior::kInclude;
|
||||
const bool fieldSetContainsTime =
|
||||
_spec.fieldSet().find(_spec.timeField()) != _spec.fieldSet().end();
|
||||
|
||||
@ -1575,22 +1577,21 @@ void BucketUnpacker::eraseMetaFromFieldSetAndDetermineIncludeMeta() {
|
||||
} else if (auto itr = _spec.fieldSet().find(*_spec.metaField());
|
||||
itr != _spec.fieldSet().end()) {
|
||||
_spec.removeIncludeExcludeField(*_spec.metaField());
|
||||
_includeMetaField = _unpackerBehavior == BucketUnpacker::Behavior::kInclude;
|
||||
_includeMetaField = _spec.behavior() == BucketSpec::Behavior::kInclude;
|
||||
} else {
|
||||
_includeMetaField = _unpackerBehavior == BucketUnpacker::Behavior::kExclude;
|
||||
_includeMetaField = _spec.behavior() == BucketSpec::Behavior::kExclude;
|
||||
}
|
||||
}
|
||||
|
||||
void BucketUnpacker::eraseExcludedComputedMetaProjFields() {
|
||||
if (_unpackerBehavior == BucketUnpacker::Behavior::kExclude) {
|
||||
if (_spec.behavior() == BucketSpec::Behavior::kExclude) {
|
||||
for (const auto& field : _spec.fieldSet()) {
|
||||
_spec.eraseFromComputedMetaProjFields(field);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BucketUnpacker::setBucketSpecAndBehavior(BucketSpec&& bucketSpec, Behavior behavior) {
|
||||
_unpackerBehavior = behavior;
|
||||
void BucketUnpacker::setBucketSpec(BucketSpec&& bucketSpec) {
|
||||
_spec = std::move(bucketSpec);
|
||||
|
||||
eraseMetaFromFieldSetAndDetermineIncludeMeta();
|
||||
@ -1616,7 +1617,7 @@ const std::set<std::string>& BucketUnpacker::fieldsToIncludeExcludeDuringUnpack(
|
||||
|
||||
_unpackFieldsToIncludeExclude = std::set<std::string>();
|
||||
const auto& metaProjFields = _spec.computedMetaProjFields();
|
||||
if (_unpackerBehavior == BucketUnpacker::Behavior::kInclude) {
|
||||
if (_spec.behavior() == BucketSpec::Behavior::kInclude) {
|
||||
// For include, we unpack fieldSet - metaProjFields.
|
||||
for (auto&& field : _spec.fieldSet()) {
|
||||
if (metaProjFields.find(field) == metaProjFields.cend()) {
|
||||
|
||||
@ -54,10 +54,16 @@ namespace mongo {
|
||||
*/
|
||||
class BucketSpec {
|
||||
public:
|
||||
// When unpackin buckets with kInclude we must produce measurements that contain the
|
||||
// set of fields. Otherwise, if the kExclude option is used, the measurements will include the
|
||||
// set difference between all fields in the bucket and the provided fields.
|
||||
enum class Behavior { kInclude, kExclude };
|
||||
|
||||
BucketSpec() = default;
|
||||
BucketSpec(const std::string& timeField,
|
||||
const boost::optional<std::string>& metaField,
|
||||
const std::set<std::string>& fields = {},
|
||||
Behavior behavior = Behavior::kExclude,
|
||||
const std::set<std::string>& computedProjections = {},
|
||||
bool usesExtendedRange = false);
|
||||
BucketSpec(const BucketSpec&);
|
||||
@ -93,6 +99,14 @@ public:
|
||||
return _fieldSet;
|
||||
}
|
||||
|
||||
void setBehavior(Behavior behavior) {
|
||||
_behavior = behavior;
|
||||
}
|
||||
|
||||
Behavior behavior() const {
|
||||
return _behavior;
|
||||
}
|
||||
|
||||
void addComputedMetaProjFields(const StringData& field) {
|
||||
_computedMetaProjFields.emplace(field);
|
||||
}
|
||||
@ -211,6 +225,7 @@ public:
|
||||
private:
|
||||
// The set of field names in the data region that should be included or excluded.
|
||||
std::set<std::string> _fieldSet;
|
||||
Behavior _behavior = Behavior::kExclude;
|
||||
|
||||
// Set of computed meta field projection names. Added at the end of materialized
|
||||
// measurements.
|
||||
@ -229,10 +244,6 @@ private:
|
||||
*/
|
||||
class BucketUnpacker {
|
||||
public:
|
||||
// When BucketUnpacker is created with kInclude it must produce measurements that contain the
|
||||
// set of fields. Otherwise, if the kExclude option is used, the measurements will include the
|
||||
// set difference between all fields in the bucket and the provided fields.
|
||||
enum class Behavior { kInclude, kExclude };
|
||||
/**
|
||||
* Returns the number of measurements in the bucket in O(1) time.
|
||||
*/
|
||||
@ -242,7 +253,7 @@ public:
|
||||
static const std::set<StringData> reservedBucketFieldNames;
|
||||
|
||||
BucketUnpacker();
|
||||
BucketUnpacker(BucketSpec spec, Behavior unpackerBehavior);
|
||||
BucketUnpacker(BucketSpec spec);
|
||||
BucketUnpacker(const BucketUnpacker& other) = delete;
|
||||
BucketUnpacker(BucketUnpacker&& other);
|
||||
~BucketUnpacker();
|
||||
@ -274,7 +285,6 @@ public:
|
||||
*/
|
||||
BucketUnpacker copy() const {
|
||||
BucketUnpacker unpackerCopy;
|
||||
unpackerCopy._unpackerBehavior = _unpackerBehavior;
|
||||
unpackerCopy._spec = _spec;
|
||||
unpackerCopy._includeMetaField = _includeMetaField;
|
||||
unpackerCopy._includeTimeField = _includeTimeField;
|
||||
@ -286,8 +296,8 @@ public:
|
||||
*/
|
||||
void reset(BSONObj&& bucket, bool bucketMatchedQuery = false);
|
||||
|
||||
Behavior behavior() const {
|
||||
return _unpackerBehavior;
|
||||
BucketSpec::Behavior behavior() const {
|
||||
return _spec.behavior();
|
||||
}
|
||||
|
||||
const BucketSpec& bucketSpec() const {
|
||||
@ -338,7 +348,7 @@ public:
|
||||
return std::string{timeseries::kControlMaxFieldNamePrefix} + field;
|
||||
}
|
||||
|
||||
void setBucketSpecAndBehavior(BucketSpec&& bucketSpec, Behavior behavior);
|
||||
void setBucketSpec(BucketSpec&& bucketSpec);
|
||||
void setIncludeMinTimeAsMetadata();
|
||||
void setIncludeMaxTimeAsMetadata();
|
||||
|
||||
@ -363,7 +373,6 @@ private:
|
||||
void eraseExcludedComputedMetaProjFields();
|
||||
|
||||
BucketSpec _spec;
|
||||
Behavior _unpackerBehavior;
|
||||
|
||||
std::unique_ptr<UnpackingImpl> _unpackingImpl;
|
||||
|
||||
@ -418,9 +427,9 @@ private:
|
||||
* Determines if an arbitrary field should be included in the materialized measurements.
|
||||
*/
|
||||
inline bool determineIncludeField(StringData fieldName,
|
||||
BucketUnpacker::Behavior unpackerBehavior,
|
||||
BucketSpec::Behavior unpackerBehavior,
|
||||
const std::set<std::string>& unpackFieldsToIncludeExclude) {
|
||||
const bool isInclude = unpackerBehavior == BucketUnpacker::Behavior::kInclude;
|
||||
const bool isInclude = unpackerBehavior == BucketSpec::Behavior::kInclude;
|
||||
const bool unpackFieldsContains = unpackFieldsToIncludeExclude.find(fieldName.toString()) !=
|
||||
unpackFieldsToIncludeExclude.cend();
|
||||
return isInclude == unpackFieldsContains;
|
||||
|
||||
@ -57,12 +57,12 @@ public:
|
||||
* before actually doing any unpacking.
|
||||
*/
|
||||
BucketUnpacker makeBucketUnpacker(std::set<std::string> fields,
|
||||
BucketUnpacker::Behavior behavior,
|
||||
BucketSpec::Behavior behavior,
|
||||
BSONObj bucket,
|
||||
boost::optional<std::string> metaFieldName = boost::none) {
|
||||
auto spec = BucketSpec{kUserDefinedTimeName.toString(), metaFieldName, std::move(fields)};
|
||||
|
||||
BucketUnpacker unpacker{std::move(spec), behavior};
|
||||
auto spec =
|
||||
BucketSpec{kUserDefinedTimeName.toString(), metaFieldName, std::move(fields), behavior};
|
||||
BucketUnpacker unpacker{std::move(spec)};
|
||||
unpacker.reset(std::move(bucket));
|
||||
return unpacker;
|
||||
}
|
||||
@ -72,12 +72,13 @@ public:
|
||||
* the given 'bucket'. Asserts that 'reset()' throws the given 'errorCode'.
|
||||
*/
|
||||
void assertUnpackerThrowsCode(std::set<std::string> fields,
|
||||
BucketUnpacker::Behavior behavior,
|
||||
BucketSpec::Behavior behavior,
|
||||
BSONObj bucket,
|
||||
boost::optional<std::string> metaFieldName,
|
||||
int errorCode) {
|
||||
auto spec = BucketSpec{kUserDefinedTimeName.toString(), metaFieldName, std::move(fields)};
|
||||
BucketUnpacker unpacker{std::move(spec), behavior};
|
||||
auto spec =
|
||||
BucketSpec{kUserDefinedTimeName.toString(), metaFieldName, std::move(fields), behavior};
|
||||
BucketUnpacker unpacker{std::move(spec)};
|
||||
ASSERT_THROWS_CODE(unpacker.reset(std::move(bucket)), AssertionException, errorCode);
|
||||
}
|
||||
|
||||
@ -178,7 +179,7 @@ TEST_F(BucketUnpackerTest, UnpackBasicIncludeAllMeasurementFields) {
|
||||
"a:{'0':1, '1':2}, b:{'1':1}}}");
|
||||
|
||||
auto unpacker = makeBucketUnpacker(std::move(fields),
|
||||
BucketUnpacker::Behavior::kInclude,
|
||||
BucketSpec::Behavior::kInclude,
|
||||
std::move(bucket),
|
||||
kUserDefinedMetaName.toString());
|
||||
|
||||
@ -202,7 +203,7 @@ TEST_F(BucketUnpackerTest, ExcludeASingleField) {
|
||||
|
||||
auto test = [&](BSONObj bucket) {
|
||||
auto unpacker = makeBucketUnpacker(fields,
|
||||
BucketUnpacker::Behavior::kExclude,
|
||||
BucketSpec::Behavior::kExclude,
|
||||
std::move(bucket),
|
||||
kUserDefinedMetaName.toString());
|
||||
|
||||
@ -231,7 +232,7 @@ TEST_F(BucketUnpackerTest, EmptyIncludeGetsEmptyMeasurements) {
|
||||
|
||||
auto test = [&](BSONObj bucket) {
|
||||
auto unpacker = makeBucketUnpacker(fields,
|
||||
BucketUnpacker::Behavior::kInclude,
|
||||
BucketSpec::Behavior::kInclude,
|
||||
std::move(bucket),
|
||||
kUserDefinedMetaName.toString());
|
||||
|
||||
@ -258,7 +259,7 @@ TEST_F(BucketUnpackerTest, EmptyExcludeMaterializesAllFields) {
|
||||
|
||||
auto test = [&](BSONObj bucket) {
|
||||
auto unpacker = makeBucketUnpacker(fields,
|
||||
BucketUnpacker::Behavior::kExclude,
|
||||
BucketSpec::Behavior::kExclude,
|
||||
std::move(bucket),
|
||||
kUserDefinedMetaName.toString());
|
||||
ASSERT_TRUE(unpacker.hasNext());
|
||||
@ -287,7 +288,7 @@ TEST_F(BucketUnpackerTest, SparseColumnsWhereOneColumnIsExhaustedBeforeTheOther)
|
||||
|
||||
auto test = [&](BSONObj bucket) {
|
||||
auto unpacker = makeBucketUnpacker(fields,
|
||||
BucketUnpacker::Behavior::kExclude,
|
||||
BucketSpec::Behavior::kExclude,
|
||||
std::move(bucket),
|
||||
kUserDefinedMetaName.toString());
|
||||
ASSERT_TRUE(unpacker.hasNext());
|
||||
@ -315,7 +316,7 @@ TEST_F(BucketUnpackerTest, UnpackBasicIncludeWithDollarPrefix) {
|
||||
|
||||
auto test = [&](BSONObj bucket) {
|
||||
auto unpacker = makeBucketUnpacker(fields,
|
||||
BucketUnpacker::Behavior::kInclude,
|
||||
BucketSpec::Behavior::kInclude,
|
||||
std::move(bucket),
|
||||
kUserDefinedMetaName.toString());
|
||||
ASSERT_TRUE(unpacker.hasNext());
|
||||
@ -343,7 +344,7 @@ TEST_F(BucketUnpackerTest, BucketsWithMetadataOnly) {
|
||||
|
||||
auto test = [&](BSONObj bucket) {
|
||||
auto unpacker = makeBucketUnpacker(fields,
|
||||
BucketUnpacker::Behavior::kExclude,
|
||||
BucketSpec::Behavior::kExclude,
|
||||
std::move(bucket),
|
||||
kUserDefinedMetaName.toString());
|
||||
ASSERT_TRUE(unpacker.hasNext());
|
||||
@ -370,7 +371,7 @@ TEST_F(BucketUnpackerTest, UnorderedRowKeysDoesntAffectMaterialization) {
|
||||
|
||||
auto test = [&](BSONObj bucket) {
|
||||
auto unpacker = makeBucketUnpacker(fields,
|
||||
BucketUnpacker::Behavior::kExclude,
|
||||
BucketSpec::Behavior::kExclude,
|
||||
std::move(bucket),
|
||||
kUserDefinedMetaName.toString());
|
||||
ASSERT_TRUE(unpacker.hasNext());
|
||||
@ -400,7 +401,7 @@ TEST_F(BucketUnpackerTest, MissingMetaFieldDoesntMaterializeMetadata) {
|
||||
|
||||
auto test = [&](BSONObj bucket) {
|
||||
auto unpacker = makeBucketUnpacker(fields,
|
||||
BucketUnpacker::Behavior::kExclude,
|
||||
BucketSpec::Behavior::kExclude,
|
||||
std::move(bucket),
|
||||
kUserDefinedMetaName.toString());
|
||||
ASSERT_TRUE(unpacker.hasNext());
|
||||
@ -428,7 +429,7 @@ TEST_F(BucketUnpackerTest, MissingMetaFieldDoesntMaterializeMetadataUnorderedKey
|
||||
|
||||
auto test = [&](BSONObj bucket) {
|
||||
auto unpacker = makeBucketUnpacker(fields,
|
||||
BucketUnpacker::Behavior::kExclude,
|
||||
BucketSpec::Behavior::kExclude,
|
||||
std::move(bucket),
|
||||
kUserDefinedMetaName.toString());
|
||||
ASSERT_TRUE(unpacker.hasNext());
|
||||
@ -456,7 +457,7 @@ TEST_F(BucketUnpackerTest, ExcludedMetaFieldDoesntMaterializeMetadataWhenBucketH
|
||||
|
||||
auto test = [&](BSONObj bucket) {
|
||||
auto unpacker = makeBucketUnpacker(fields,
|
||||
BucketUnpacker::Behavior::kExclude,
|
||||
BucketSpec::Behavior::kExclude,
|
||||
std::move(bucket),
|
||||
kUserDefinedMetaName.toString());
|
||||
ASSERT_TRUE(unpacker.hasNext());
|
||||
@ -484,7 +485,7 @@ TEST_F(BucketUnpackerTest, UnpackerResetThrowsOnUndefinedMeta) {
|
||||
|
||||
auto test = [&](BSONObj bucket) {
|
||||
assertUnpackerThrowsCode(fields,
|
||||
BucketUnpacker::Behavior::kExclude,
|
||||
BucketSpec::Behavior::kExclude,
|
||||
std::move(bucket),
|
||||
kUserDefinedMetaName.toString(),
|
||||
5369600);
|
||||
@ -505,7 +506,7 @@ TEST_F(BucketUnpackerTest, UnpackerResetThrowsOnUnexpectedMeta) {
|
||||
|
||||
auto test = [&](BSONObj bucket) {
|
||||
assertUnpackerThrowsCode(fields,
|
||||
BucketUnpacker::Behavior::kExclude,
|
||||
BucketSpec::Behavior::kExclude,
|
||||
std::move(bucket),
|
||||
boost::none /* no metaField provided */,
|
||||
5369601);
|
||||
@ -525,7 +526,7 @@ TEST_F(BucketUnpackerTest, NullMetaInBucketMaterializesAsNull) {
|
||||
|
||||
auto test = [&](BSONObj bucket) {
|
||||
auto unpacker = makeBucketUnpacker(fields,
|
||||
BucketUnpacker::Behavior::kExclude,
|
||||
BucketSpec::Behavior::kExclude,
|
||||
std::move(bucket),
|
||||
kUserDefinedMetaName.toString());
|
||||
ASSERT_TRUE(unpacker.hasNext());
|
||||
@ -558,7 +559,7 @@ TEST_F(BucketUnpackerTest, GetNextHandlesMissingMetaInBucket) {
|
||||
|
||||
auto test = [&](BSONObj bucket) {
|
||||
auto unpacker = makeBucketUnpacker(fields,
|
||||
BucketUnpacker::Behavior::kExclude,
|
||||
BucketSpec::Behavior::kExclude,
|
||||
std::move(bucket),
|
||||
kUserDefinedMetaName.toString());
|
||||
ASSERT_TRUE(unpacker.hasNext());
|
||||
@ -588,7 +589,7 @@ TEST_F(BucketUnpackerTest, EmptyDataRegionInBucketIsTolerated) {
|
||||
|
||||
auto test = [&](BSONObj bucket) {
|
||||
auto unpacker = makeBucketUnpacker(
|
||||
fields, BucketUnpacker::Behavior::kExclude, bucket, kUserDefinedMetaName.toString());
|
||||
fields, BucketSpec::Behavior::kExclude, bucket, kUserDefinedMetaName.toString());
|
||||
ASSERT_FALSE(unpacker.hasNext());
|
||||
};
|
||||
|
||||
@ -600,7 +601,7 @@ TEST_F(BucketUnpackerTest, UnpackerResetThrowsOnEmptyBucket) {
|
||||
|
||||
auto bucket = Document{};
|
||||
assertUnpackerThrowsCode(std::move(fields),
|
||||
BucketUnpacker::Behavior::kExclude,
|
||||
BucketSpec::Behavior::kExclude,
|
||||
bucket.toBson(),
|
||||
kUserDefinedMetaName.toString(),
|
||||
5346510);
|
||||
@ -618,48 +619,52 @@ TEST_F(BucketUnpackerTest, EraseMetaFromFieldSetAndDetermineIncludeMeta) {
|
||||
}
|
||||
})");
|
||||
auto unpacker = makeBucketUnpacker(empFields,
|
||||
BucketUnpacker::Behavior::kInclude,
|
||||
BucketSpec::Behavior::kInclude,
|
||||
std::move(bucket),
|
||||
kUserDefinedMetaName.toString());
|
||||
|
||||
// Tests a spec with 'metaField' in include list.
|
||||
std::set<std::string> fields{kUserDefinedMetaName.toString()};
|
||||
|
||||
auto specWithMetaInclude = BucketSpec{
|
||||
kUserDefinedTimeName.toString(), kUserDefinedMetaName.toString(), std::move(fields)};
|
||||
auto specWithMetaInclude = BucketSpec{kUserDefinedTimeName.toString(),
|
||||
kUserDefinedMetaName.toString(),
|
||||
std::move(fields),
|
||||
BucketSpec::Behavior::kInclude};
|
||||
// This calls eraseMetaFromFieldSetAndDetermineIncludeMeta.
|
||||
unpacker.setBucketSpecAndBehavior(std::move(specWithMetaInclude),
|
||||
BucketUnpacker::Behavior::kInclude);
|
||||
unpacker.setBucketSpec(std::move(specWithMetaInclude));
|
||||
ASSERT_TRUE(unpacker.includeMetaField());
|
||||
ASSERT_EQ(unpacker.bucketSpec().fieldSet().count(kUserDefinedMetaName.toString()), 0);
|
||||
|
||||
std::set<std::string> fieldsNoMetaInclude{"foo"};
|
||||
auto specWithFooInclude = BucketSpec{kUserDefinedTimeName.toString(),
|
||||
kUserDefinedMetaName.toString(),
|
||||
std::move(fieldsNoMetaInclude)};
|
||||
std::move(fieldsNoMetaInclude),
|
||||
BucketSpec::Behavior::kInclude};
|
||||
|
||||
std::set<std::string> fieldsNoMetaExclude{"foo"};
|
||||
auto specWithFooExclude = BucketSpec{kUserDefinedTimeName.toString(),
|
||||
kUserDefinedMetaName.toString(),
|
||||
std::move(fieldsNoMetaExclude)};
|
||||
std::move(fieldsNoMetaExclude),
|
||||
BucketSpec::Behavior::kExclude};
|
||||
|
||||
unpacker.setBucketSpecAndBehavior(std::move(specWithFooExclude),
|
||||
BucketUnpacker::Behavior::kExclude);
|
||||
unpacker.setBucketSpec(std::move(specWithFooExclude));
|
||||
ASSERT_TRUE(unpacker.includeMetaField());
|
||||
unpacker.setBucketSpecAndBehavior(std::move(specWithFooInclude),
|
||||
BucketUnpacker::Behavior::kInclude);
|
||||
unpacker.setBucketSpec(std::move(specWithFooInclude));
|
||||
ASSERT_FALSE(unpacker.includeMetaField());
|
||||
|
||||
// Tests a spec with 'metaField' not in exclude list.
|
||||
std::set<std::string> excludeFields{};
|
||||
auto specMetaExclude = BucketSpec{
|
||||
kUserDefinedTimeName.toString(), kUserDefinedMetaName.toString(), std::move(excludeFields)};
|
||||
auto specMetaExclude = BucketSpec{kUserDefinedTimeName.toString(),
|
||||
kUserDefinedMetaName.toString(),
|
||||
std::move(excludeFields),
|
||||
BucketSpec::Behavior::kExclude};
|
||||
|
||||
auto specMetaInclude = specMetaExclude;
|
||||
unpacker.setBucketSpecAndBehavior(std::move(specMetaExclude),
|
||||
BucketUnpacker::Behavior::kExclude);
|
||||
specMetaInclude.setBehavior(BucketSpec::Behavior::kInclude);
|
||||
|
||||
unpacker.setBucketSpec(std::move(specMetaExclude));
|
||||
ASSERT_TRUE(unpacker.includeMetaField());
|
||||
unpacker.setBucketSpecAndBehavior(std::move(specMetaInclude),
|
||||
BucketUnpacker::Behavior::kInclude);
|
||||
unpacker.setBucketSpec(std::move(specMetaInclude));
|
||||
ASSERT_FALSE(unpacker.includeMetaField());
|
||||
}
|
||||
|
||||
@ -674,21 +679,25 @@ TEST_F(BucketUnpackerTest, DetermineIncludeTimeField) {
|
||||
})");
|
||||
std::set<std::string> unpackerFields{kUserDefinedTimeName.toString()};
|
||||
auto unpacker = makeBucketUnpacker(unpackerFields,
|
||||
BucketUnpacker::Behavior::kInclude,
|
||||
BucketSpec::Behavior::kInclude,
|
||||
std::move(bucket),
|
||||
kUserDefinedMetaName.toString());
|
||||
|
||||
std::set<std::string> includeFields{kUserDefinedTimeName.toString()};
|
||||
auto includeSpec = BucketSpec{
|
||||
kUserDefinedTimeName.toString(), kUserDefinedMetaName.toString(), std::move(includeFields)};
|
||||
auto includeSpec = BucketSpec{kUserDefinedTimeName.toString(),
|
||||
kUserDefinedMetaName.toString(),
|
||||
std::move(includeFields),
|
||||
BucketSpec::Behavior::kInclude};
|
||||
// This calls determineIncludeTimeField.
|
||||
unpacker.setBucketSpecAndBehavior(std::move(includeSpec), BucketUnpacker::Behavior::kInclude);
|
||||
unpacker.setBucketSpec(std::move(includeSpec));
|
||||
ASSERT_TRUE(unpacker.includeTimeField());
|
||||
|
||||
std::set<std::string> excludeFields{kUserDefinedTimeName.toString()};
|
||||
auto excludeSpec = BucketSpec{
|
||||
kUserDefinedTimeName.toString(), kUserDefinedMetaName.toString(), std::move(excludeFields)};
|
||||
unpacker.setBucketSpecAndBehavior(std::move(excludeSpec), BucketUnpacker::Behavior::kExclude);
|
||||
auto excludeSpec = BucketSpec{kUserDefinedTimeName.toString(),
|
||||
kUserDefinedMetaName.toString(),
|
||||
std::move(excludeFields),
|
||||
BucketSpec::Behavior::kExclude};
|
||||
unpacker.setBucketSpec(std::move(excludeSpec));
|
||||
ASSERT_FALSE(unpacker.includeTimeField());
|
||||
}
|
||||
|
||||
@ -703,24 +712,26 @@ TEST_F(BucketUnpackerTest, DetermineIncludeFieldIncludeMode) {
|
||||
{"data", Document{}}}
|
||||
.toBson();
|
||||
|
||||
auto spec = BucketSpec{
|
||||
kUserDefinedTimeName.toString(), kUserDefinedMetaName.toString(), std::move(fields)};
|
||||
auto spec = BucketSpec{kUserDefinedTimeName.toString(),
|
||||
kUserDefinedMetaName.toString(),
|
||||
std::move(fields),
|
||||
BucketSpec::Behavior::kInclude};
|
||||
|
||||
BucketUnpacker includeUnpacker;
|
||||
includeUnpacker.setBucketSpecAndBehavior(std::move(spec), BucketUnpacker::Behavior::kInclude);
|
||||
includeUnpacker.setBucketSpec(std::move(spec));
|
||||
// Need to call reset so that the private method calculateFieldsToIncludeExcludeDuringUnpack()
|
||||
// is called, and _unpackFieldsToIncludeExclude gets filled with fields.
|
||||
includeUnpacker.reset(std::move(bucket));
|
||||
// Now the spec knows which fields to include/exclude.
|
||||
|
||||
ASSERT_TRUE(determineIncludeField(kUserDefinedTimeName,
|
||||
BucketUnpacker::Behavior::kInclude,
|
||||
BucketSpec::Behavior::kInclude,
|
||||
includeUnpacker.fieldsToIncludeExcludeDuringUnpack()));
|
||||
ASSERT_TRUE(determineIncludeField(includedMeasurementField,
|
||||
BucketUnpacker::Behavior::kInclude,
|
||||
BucketSpec::Behavior::kInclude,
|
||||
includeUnpacker.fieldsToIncludeExcludeDuringUnpack()));
|
||||
ASSERT_FALSE(determineIncludeField(excludedMeasurementField,
|
||||
BucketUnpacker::Behavior::kInclude,
|
||||
BucketSpec::Behavior::kInclude,
|
||||
includeUnpacker.fieldsToIncludeExcludeDuringUnpack()));
|
||||
}
|
||||
|
||||
@ -735,21 +746,23 @@ TEST_F(BucketUnpackerTest, DetermineIncludeFieldExcludeMode) {
|
||||
{"data", Document{}}}
|
||||
.toBson();
|
||||
|
||||
auto spec = BucketSpec{
|
||||
kUserDefinedTimeName.toString(), kUserDefinedMetaName.toString(), std::move(fields)};
|
||||
auto spec = BucketSpec{kUserDefinedTimeName.toString(),
|
||||
kUserDefinedMetaName.toString(),
|
||||
std::move(fields),
|
||||
BucketSpec::Behavior::kExclude};
|
||||
|
||||
BucketUnpacker excludeUnpacker;
|
||||
excludeUnpacker.setBucketSpecAndBehavior(std::move(spec), BucketUnpacker::Behavior::kExclude);
|
||||
excludeUnpacker.setBucketSpec(std::move(spec));
|
||||
excludeUnpacker.reset(std::move(bucket));
|
||||
|
||||
ASSERT_FALSE(determineIncludeField(kUserDefinedTimeName,
|
||||
BucketUnpacker::Behavior::kExclude,
|
||||
BucketSpec::Behavior::kExclude,
|
||||
excludeUnpacker.fieldsToIncludeExcludeDuringUnpack()));
|
||||
ASSERT_FALSE(determineIncludeField(includedMeasurementField,
|
||||
BucketUnpacker::Behavior::kExclude,
|
||||
BucketSpec::Behavior::kExclude,
|
||||
excludeUnpacker.fieldsToIncludeExcludeDuringUnpack()));
|
||||
ASSERT_TRUE(determineIncludeField(excludedMeasurementField,
|
||||
BucketUnpacker::Behavior::kExclude,
|
||||
BucketSpec::Behavior::kExclude,
|
||||
excludeUnpacker.fieldsToIncludeExcludeDuringUnpack()));
|
||||
}
|
||||
|
||||
@ -767,9 +780,11 @@ auto expectedTimestampObjSize(int32_t rowKeyOffset, int32_t n) {
|
||||
TEST_F(BucketUnpackerTest, ExtractSingleMeasurement) {
|
||||
std::set<std::string> fields{
|
||||
"_id", kUserDefinedMetaName.toString(), kUserDefinedTimeName.toString(), "a", "b"};
|
||||
auto spec = BucketSpec{
|
||||
kUserDefinedTimeName.toString(), kUserDefinedMetaName.toString(), std::move(fields)};
|
||||
auto unpacker = BucketUnpacker{std::move(spec), BucketUnpacker::Behavior::kInclude};
|
||||
auto spec = BucketSpec{kUserDefinedTimeName.toString(),
|
||||
kUserDefinedMetaName.toString(),
|
||||
std::move(fields),
|
||||
BucketSpec::Behavior::kInclude};
|
||||
auto unpacker = BucketUnpacker{std::move(spec)};
|
||||
|
||||
auto d1 = dateFromISOString("2020-02-17T00:00:00.000Z").getValue();
|
||||
auto d2 = dateFromISOString("2020-02-17T01:00:00.000Z").getValue();
|
||||
@ -812,9 +827,11 @@ TEST_F(BucketUnpackerTest, ExtractSingleMeasurement) {
|
||||
TEST_F(BucketUnpackerTest, ExtractSingleMeasurementSparse) {
|
||||
std::set<std::string> fields{
|
||||
"_id", kUserDefinedMetaName.toString(), kUserDefinedTimeName.toString(), "a", "b"};
|
||||
auto spec = BucketSpec{
|
||||
kUserDefinedTimeName.toString(), kUserDefinedMetaName.toString(), std::move(fields)};
|
||||
auto unpacker = BucketUnpacker{std::move(spec), BucketUnpacker::Behavior::kInclude};
|
||||
auto spec = BucketSpec{kUserDefinedTimeName.toString(),
|
||||
kUserDefinedMetaName.toString(),
|
||||
std::move(fields),
|
||||
BucketSpec::Behavior::kInclude};
|
||||
auto unpacker = BucketUnpacker{std::move(spec)};
|
||||
|
||||
auto d1 = dateFromISOString("2020-02-17T00:00:00.000Z").getValue();
|
||||
auto d2 = dateFromISOString("2020-02-17T01:00:00.000Z").getValue();
|
||||
@ -902,7 +919,7 @@ TEST_F(BucketUnpackerTest, TamperedCompressedCountLess) {
|
||||
auto modifiedCompressedBucket = modifyCompressedBucketElementCount(*compressedBucket, -1);
|
||||
|
||||
auto unpacker = makeBucketUnpacker(std::move(fields),
|
||||
BucketUnpacker::Behavior::kInclude,
|
||||
BucketSpec::Behavior::kInclude,
|
||||
std::move(modifiedCompressedBucket),
|
||||
kUserDefinedMetaName.toString());
|
||||
|
||||
@ -938,7 +955,7 @@ TEST_F(BucketUnpackerTest, TamperedCompressedCountMore) {
|
||||
auto modifiedCompressedBucket = modifyCompressedBucketElementCount(*compressedBucket, 1);
|
||||
|
||||
auto unpacker = makeBucketUnpacker(std::move(fields),
|
||||
BucketUnpacker::Behavior::kInclude,
|
||||
BucketSpec::Behavior::kInclude,
|
||||
std::move(modifiedCompressedBucket),
|
||||
kUserDefinedMetaName.toString());
|
||||
|
||||
@ -974,7 +991,7 @@ TEST_F(BucketUnpackerTest, TamperedCompressedCountMissing) {
|
||||
auto modifiedCompressedBucket = modifyCompressedBucketElementCount(*compressedBucket, 0);
|
||||
|
||||
auto unpacker = makeBucketUnpacker(std::move(fields),
|
||||
BucketUnpacker::Behavior::kInclude,
|
||||
BucketSpec::Behavior::kInclude,
|
||||
std::move(modifiedCompressedBucket),
|
||||
kUserDefinedMetaName.toString());
|
||||
|
||||
@ -1012,7 +1029,7 @@ TEST_F(BucketUnpackerTest, TamperedCompressedElementMismatchDataField) {
|
||||
modifyCompressedBucketRemoveLastInField(*compressedBucket, "a"_sd);
|
||||
|
||||
auto unpacker = makeBucketUnpacker(std::move(fields),
|
||||
BucketUnpacker::Behavior::kInclude,
|
||||
BucketSpec::Behavior::kInclude,
|
||||
std::move(modifiedCompressedBucket),
|
||||
kUserDefinedMetaName.toString());
|
||||
|
||||
@ -1048,7 +1065,7 @@ TEST_F(BucketUnpackerTest, TamperedCompressedElementMismatchTimeField) {
|
||||
modifyCompressedBucketRemoveLastInField(*compressedBucket, "time"_sd);
|
||||
|
||||
auto unpacker = makeBucketUnpacker(std::move(fields),
|
||||
BucketUnpacker::Behavior::kInclude,
|
||||
BucketSpec::Behavior::kInclude,
|
||||
std::move(modifiedCompressedBucket),
|
||||
kUserDefinedMetaName.toString());
|
||||
|
||||
|
||||
@ -1033,7 +1033,9 @@ inline size_t getBSONBinDataSize(TypeTags tag, Value val) {
|
||||
|
||||
inline BinDataType getBSONBinDataSubtype(TypeTags tag, Value val) {
|
||||
invariant(tag == TypeTags::bsonBinData);
|
||||
return static_cast<BinDataType>((getRawPointerView(val) + sizeof(uint32_t))[0]);
|
||||
uint8_t subtype =
|
||||
ConstDataView(getRawPointerView(val) + sizeof(uint32_t)).read<LittleEndian<uint8_t>>();
|
||||
return static_cast<BinDataType>(subtype);
|
||||
}
|
||||
|
||||
inline uint8_t* getBSONBinData(TypeTags tag, Value val) {
|
||||
|
||||
@ -82,8 +82,12 @@ public:
|
||||
auto result = CommandHelpers::runCommandDirectly(
|
||||
opCtx,
|
||||
OpMsgRequest::fromDBAndBody(
|
||||
ns.db(), BSON("collStats" << ns.coll() << "waitForLock" << false)));
|
||||
builder.append(nsStr, result);
|
||||
ns.db(),
|
||||
BSON("aggregate" << ns.coll() << "cursor" << BSONObj{} << "pipeline"
|
||||
<< BSON_ARRAY(BSON("$collStats" << BSON(
|
||||
"storageStats" << BSON(
|
||||
"waitForLock" << false)))))));
|
||||
builder.append(nsStr, result["cursor"]["firstBatch"]["0"].Obj());
|
||||
|
||||
} catch (...) {
|
||||
Status s = exceptionToStatus();
|
||||
@ -110,14 +114,16 @@ void registerMongoDCollectors(FTDCController* controller) {
|
||||
BSON("replSetGetStatus" << 1 << "initialSync" << 0)));
|
||||
|
||||
// CollectionStats
|
||||
controller->addPeriodicCollector(
|
||||
std::make_unique<FTDCSimpleInternalCommandCollector>("collStats",
|
||||
"local.oplog.rs.stats",
|
||||
"local",
|
||||
BSON("collStats"
|
||||
<< "oplog.rs"
|
||||
<< "waitForLock" << false
|
||||
<< "numericOnly" << true)));
|
||||
controller->addPeriodicCollector(std::make_unique<FTDCSimpleInternalCommandCollector>(
|
||||
"aggregate",
|
||||
"local.oplog.rs.stats",
|
||||
"local",
|
||||
BSON("aggregate"
|
||||
<< "oplog.rs"
|
||||
<< "cursor" << BSONObj{} << "pipeline"
|
||||
<< BSON_ARRAY(BSON("$collStats" << BSON(
|
||||
"storageStats" << BSON(
|
||||
"waitForLock" << false << "numericOnly" << true)))))));
|
||||
if (serverGlobalParams.clusterRole != ClusterRole::ShardServer) {
|
||||
// GetDefaultRWConcern
|
||||
controller->addOnRotateCollector(std::make_unique<FTDCSimpleInternalCommandCollector>(
|
||||
|
||||
@ -181,8 +181,11 @@ FTDCSimpleInternalCommandCollector::FTDCSimpleInternalCommandCollector(StringDat
|
||||
}
|
||||
|
||||
void FTDCSimpleInternalCommandCollector::collect(OperationContext* opCtx, BSONObjBuilder& builder) {
|
||||
auto result = CommandHelpers::runCommandDirectly(opCtx, _request);
|
||||
builder.appendElements(result);
|
||||
if (auto result = CommandHelpers::runCommandDirectly(opCtx, _request);
|
||||
result.hasElement("cursor"))
|
||||
builder.appendElements(result["cursor"]["firstBatch"]["0"].Obj());
|
||||
else
|
||||
builder.appendElements(result);
|
||||
}
|
||||
|
||||
std::string FTDCSimpleInternalCommandCollector::name() const {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user